diff --git "a/.github/ISSUE_TEMPLATE/revis\303\243o-preprint.md" "b/.github/ISSUE_TEMPLATE/revis\303\243o-preprint.md" new file mode 100644 index 00000000..d99d0fb0 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/revis\303\243o-preprint.md" @@ -0,0 +1,23 @@ +--- +name: Revisão preprint +about: Relate um ou mais problemas em um PDF do preprint +title: "[ERRATA] capítulo , " +labels: impresso +assignees: ramalho + +--- + +**Versão do Preprint** +Ex. 2a pré-impressão (veja na página 2 do PDF) + +**Página lógica** +É o número da página que aparece no rodapé. Ex. 26 é a primeira página do Capítulo 1 + +**Defeito** +Descreva o problema, incluindo trechos do texto para facilitar a busca. +Coloque a solução se for fácil. Ex. "o jogo de turco é massa" , deveria ser "truco". + +Pode repetir as seções **Página lógica** e **Defeito** no mesmo _issue_. + +**PR (opcional) ** +Se souber e quiser fazer um _pull request_, primeiro cadastre o issue depois o PR referenciando o issue (ou mais de um). O PR corrigindo vários erros deve deve alterar somente um capítulo. Se o mesmo erro ocorre em vários capítulos, submeta um PR somente para corrigir este erro nos vários capítulos. diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 87726986..daed35d1 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -8,8 +8,8 @@ jobs: Publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-node@v6 with: node-version: 18 diff --git a/.gitignore b/.gitignore index 21b797ab..27a15667 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,8 @@ +exports/ *.bkp +*.docx .DS_Store -# Arquivos renderizados -*.html - # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 00000000..3b47f2e4 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.3.9 diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..0ff18f4a --- /dev/null +++ b/Gemfile @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +ruby '3.3.9' + +gem 'asciidoctor', '2.0.26' +gem 'asciidoctor-pdf', '2.3.24' +gem 'rouge', '4.6.0' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..58fc3b67 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,69 @@ +GEM + remote: https://rubygems.org/ + specs: + Ascii85 (2.0.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + afm (1.0.0) + asciidoctor (2.0.26) + asciidoctor-pdf (2.3.24) + asciidoctor (~> 2.0) + concurrent-ruby (~> 1.3) + matrix (~> 0.4) + prawn (~> 2.4.0) + prawn-icon (~> 3.0.0) + prawn-svg (~> 0.34.0) + prawn-table (~> 0.2.0) + prawn-templates (~> 0.1.0) + treetop (~> 1.6.0) + ttfunk (~> 1.7.0) + concurrent-ruby (1.3.5) + css_parser (1.21.1) + addressable + hashery (2.1.2) + matrix (0.4.3) + pdf-core (0.9.0) + pdf-reader (2.15.0) + Ascii85 (>= 1.0, < 3.0, != 2.0.0) + afm (>= 0.2.1, < 2) + hashery (~> 2.0) + ruby-rc4 + ttfunk + polyglot (0.3.5) + prawn (2.4.0) + pdf-core (~> 0.9.0) + ttfunk (~> 1.7) + prawn-icon (3.0.0) + prawn (>= 1.1.0, < 3.0.0) + prawn-svg (0.34.2) + css_parser (~> 1.6) + matrix (~> 0.4.2) + prawn (>= 0.11.1, < 3) + rexml (~> 3.2) + prawn-table (0.2.2) + prawn (>= 1.3.0, < 3.0.0) + prawn-templates (0.1.2) + pdf-reader (~> 2.0) + prawn (~> 2.2) + public_suffix (6.0.2) + rexml (3.4.4) + rouge (4.6.0) + ruby-rc4 (0.1.5) + treetop (1.6.18) + polyglot (~> 0.3) + ttfunk (1.7.0) + +PLATFORMS + ruby + x86_64-darwin-24 + +DEPENDENCIES + asciidoctor (= 2.0.26) + asciidoctor-pdf (= 2.3.24) + rouge (= 4.6.0) + +RUBY VERSION + ruby 3.3.9p170 + +BUNDLED WITH + 2.5.22 diff --git a/Livro.adoc b/Livro.adoc deleted file mode 100644 index 76e6c205..00000000 --- a/Livro.adoc +++ /dev/null @@ -1,80 +0,0 @@ -= Python Fluente, Segunda Edição (2023) -:doctype: book -:author: Luciano Ramalho -:lang: pt_BR -:language: asciidoctor -include::atributos-pt_BR.adoc[] -:xrefstyle: short -:sectnums: -:sectlinks: -:data-uri: -:toc: left -:toclevels: 2 -:!chapter-signifier: - -include::Prefacio.adoc[] - -[[data_structures_part]] -= Parte I: Estruturas de dados - -include::capitulos/cap01.adoc[] - -include::capitulos/cap02.adoc[] - -include::capitulos/cap03.adoc[] - -include::capitulos/cap04.adoc[] - -include::capitulos/cap05.adoc[] - -include::capitulos/cap06.adoc[] - -[[function_objects_part]] -= Parte II: Funções como objetos - -include::capitulos/cap07.adoc[] - -include::capitulos/cap08.adoc[] - -include::capitulos/cap09.adoc[] - -include::capitulos/cap10.adoc[] - -[[classes_protocols_part]] -= Parte III: Classes e protocolos - -include::capitulos/cap11.adoc[] - -include::capitulos/cap12.adoc[] - -include::capitulos/cap13.adoc[] - -include::capitulos/cap14.adoc[] - -include::capitulos/cap15.adoc[] - -include::capitulos/cap16.adoc[] - -[[control_flow_part]] -= Parte IV: Controle de fluxo - -include::capitulos/cap17.adoc[] - -include::capitulos/cap18.adoc[] - -include::capitulos/cap19.adoc[] - -include::capitulos/cap20.adoc[] - -include::capitulos/cap21.adoc[] - -[[metaprog_part]] -= Parte V: Metaprogramação - -include::capitulos/cap22.adoc[] - -include::capitulos/cap23.adoc[] - -include::capitulos/cap24.adoc[] - -include::Posfacio.adoc[] \ No newline at end of file diff --git a/README.adoc b/README.adoc index 3c0f9f92..099525f1 100644 --- a/README.adoc +++ b/README.adoc @@ -1,7 +1,7 @@ :xrefstyle: short :note-caption: Nota -# Python Fluente, Segunda Edição +# Python Fluente, 2ª Edição Tradução oficial de https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/[__Fluent Python, Second Edition__]. Traduzida por https://github.com/pauloc[@pauloc], @@ -63,11 +63,11 @@ https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode[texto completo da li [NOTE] ==== -Esta *não é uma obra derivada* da tradução de Python Fluente, 1ª edição, -publicada pela Editora Novatec em 2015. Qualquer coincidência é acidental. - Esta tradução foi feita diretamente do texto original em inglês de __Fluent Python, Second Edition__, com autorização da O'Reilly Media. + +Esta *não é uma obra derivada* da tradução de Python Fluente, 1ª edição, +publicada pela Editora Novatec em 2015. Qualquer coincidência é acidental. ==== -__Python Fluente, Segunda Edição__ © 2023 Luciano Ramalho. +__Python Fluente, 2ª Edição__ © 2023 Luciano Ramalho. diff --git a/atributos-pt_BR.adoc b/atributos-pt_BR.adoc index 49631dc7..ba053094 100644 --- a/atributos-pt_BR.adoc +++ b/atributos-pt_BR.adoc @@ -18,11 +18,17 @@ :appendix-caption: Apêndice :appendix-refsig: {appendix-caption} :toc-title: Sumário -ifdef::preface-title[:preface-title: Prefácio] +:preface-title: Prefácio :example-caption: Exemplo :figure-caption: Figura -ifdef::listing-caption[:listing-caption: Listagem] +:listing-caption: Listagem :table-caption: Tabela :untitled-label: Sem título :last-update-label: HTML gerado em :version-label: Versão +:colophon: Copyright +// Substituições +:dunder: __ +:rt-arrow: -> +:lte: <= +:iadd: += diff --git a/build.sh b/build.sh deleted file mode 100755 index cd991c29..00000000 --- a/build.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -set -e # exit when any command fails -asciidoctor Livro.adoc -o index.html -open index.html diff --git a/calango/README.md b/calango/README.md new file mode 100644 index 00000000..69b8eab0 --- /dev/null +++ b/calango/README.md @@ -0,0 +1,26 @@ +# Calango + +Processador de referências cruzadas para a publicação do +_Python Fluente Segunda Edição_ impresso em três columes. + +## Objetivo + +Encontrar e substituir as referências cruzadas que apontam +para volumes diferentes. + +Por exemplo, no Capítulo 1, próximo da linha 744 há essa referência: + +````adoc +Como implementar [...] será visto no <>, +```` + +Como aponta para um capítulo do volume 2, precisa ser reescrita assim: + +```adoc +Como implementar [...] será visto no Capítulo 12 [vol.2, fpy.li/xyz], +``` + +Onde fpy.li/xyz direciona para: + +https://pythonfluente.com/2/#ch_seq_methods + diff --git a/calango/pyproject.toml b/calango/pyproject.toml new file mode 100644 index 00000000..4824c8a6 --- /dev/null +++ b/calango/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "calango" +version = "0.1.0" +description = "Processador de refeências cruzadas" +readme = "README.md" +authors = [ + { name = "Luciano Ramalho", email = "luciano@ramalho.org" } +] +requires-python = ">=3.14" +dependencies = [ + "beautifulsoup4>=4.13.5", +] + +[project.scripts] +calango = "calango:main" +hello = "calango:hello" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[dependency-groups] +dev = [ + "pytest>=8.4.1", +] diff --git a/calango/src/calango/__init__.py b/calango/src/calango/__init__.py new file mode 100644 index 00000000..10f54958 --- /dev/null +++ b/calango/src/calango/__init__.py @@ -0,0 +1,4 @@ +def main() -> None: + print("Hello from calango!") + + diff --git a/calango/tools/farxrefs.py b/calango/tools/farxrefs.py new file mode 100644 index 00000000..68617882 --- /dev/null +++ b/calango/tools/farxrefs.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +""" +update map of xrefs to different volumes +""" + +import subprocess +import sys + +def main(): + + print() + +def capture_stderr(command): + result = subprocess.run(command, shell=True, stderr=subprocess.PIPE, text=True) + return result.stderr + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/calango/uv.lock b/calango/uv.lock new file mode 100644 index 00000000..53be0be0 --- /dev/null +++ b/calango/uv.lock @@ -0,0 +1,114 @@ +version = 1 +revision = 2 +requires-python = ">=3.14" + +[[package]] +name = "beautifulsoup4" +version = "4.13.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/2e/3e5079847e653b1f6dc647aa24549d68c6addb4c595cc0d902d1b19308ad/beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695", size = 622954, upload-time = "2025-08-24T14:06:13.168Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/eb/f4151e0c7377a6e08a38108609ba5cede57986802757848688aeedd1b9e8/beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a", size = 105113, upload-time = "2025-08-24T14:06:14.884Z" }, +] + +[[package]] +name = "calango" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "beautifulsoup4" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [{ name = "beautifulsoup4", specifier = ">=4.13.5" }] + +[package.metadata.requires-dev] +dev = [{ name = "pytest", specifier = ">=8.4.1" }] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] diff --git a/capitulos/cap01.adoc b/capitulos/cap01.adoc deleted file mode 100644 index 5d92d204..00000000 --- a/capitulos/cap01.adoc +++ /dev/null @@ -1,679 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo - -[[data_model]] - -== O modelo de dados do Python - -// [quote, Jim Hugunin, creator of Jython, cocreator of AspectJ, and architect of the .Net DLR] -// ____ -// Guido's sense of the aesthetics of language design is amazing. I've met many fine language designers who could build theoretically beautiful languages that no one would ever use, but Guido is one of those rare people who can build a language that is just slightly less theoretically beautiful but thereby is a joy to write programs in.footnote:[https://fpy.li/1-1["Story of Jython"], written as a foreword to pass:[Jython Essentials by Samuele Pedroni and Noel Rappin (O'Reilly).]] -// ____ -++++ -
-

O senso estético de Guido para o design de linguagens é incrível. Conheci muitos projetistas capazes de criar linguagens teoricamente lindas, que ninguém jamais usaria. Mas Guido é uma daquelas raras pessoas capaz criar uma linguagem só um pouco menos teoricamente linda que, por isso mesmo, é uma delícia para programar.

- -

Jim Hugunin, criador do Jython, co-criador do AspectJ, arquiteto do DLR (Dynamic Language Runtime) do .Net. "Story of Jython" (_A História do Jython_) (EN), escrito como prefácio ao Jython Essentials (EN), de Samuele Pedroni e Noel Rappin (O'Reilly).

-
-++++ - -Uma das melhores qualidades do Python é sua consistência. Após trabalhar com Python por algum tempo é possível intuir, de uma maneira informada e correta, o funcionamento de recursos que você acabou de conhecer. - -Entretanto, se você aprendeu outra linguagem orientada a objetos antes do Python, pode achar estranho usar `len(collection)` em vez de `collection.len()`. -Essa aparente esquisitice é a ponta de um iceberg que, quando compreendido de forma apropriada, é a chave para tudo aquilo que chamamos de _pythônico_. -O iceberg se chama o Modelo de Dados do Python, e é a API que usamos para fazer nossos objetos lidarem bem com os aspectos mais idiomáticos da linguagem. - -É((("Python Data Model", "overview of"))) possível pensar no modelo de dados como uma descrição do Python na forma de uma framework. Ele formaliza as interfaces dos elementos constituintes da própria linguagem, como sequências, funções, iteradores, corrotinas, classes, gerenciadores de contexto e assim por diante. - -Quando usamos uma framework, gastamos um bom tempo programando métodos que são chamados por ela. O mesmo acontece quando nos valemos do Modelo de Dados do Python para criar novas classes. O interpretador do Python invoca((("special methods", "purpose of"))) métodos especiais para realizar operações básicas sobre os objetos, muitas vezes acionados por uma sintaxe especial. -Os((("special methods", "naming conventions")))((("__ (double underscore)")))((("double underscore (__)"))) nomes dos métodos especiais são sempre precedidos e seguidos de dois sublinhados. -Por exemplo, a sintaxe `obj[key]` está amparada no método especial `+__getitem__+`. -Para resolver `my_collection[key]`, o interpretador chama `+my_collection.__getitem__(key)+`. - -Implementamos métodos especiais quando queremos que nossos objetos suportem e interajam com elementos fundamentais da linguagem, tais como: - -* Coleções - -* Acesso a atributos - -* Iteração (incluindo iteração assíncrona com `+async for+`) - -* Sobrecarga (_overloading_) de operadores - -* Invocação de funções e métodos - -* Representação e formatação de strings - -* Programação assíncrona usando `+await+` - -* Criação e destruição de objetos - -* Contextos gerenciados usando as instruções `with` ou `async with` - - -.Mágica e o "dunder" -[NOTE] -==== -O((("magic methods")))((("dunder methods"))) termo _método mágico_ é uma gíria usada para se referir aos métodos especiais, mas como falamos de um método específico, por exemplo `+__getitem__+`? -Aprendi a dizer "dunder-getitem" com o autor e professor Steve Holden. -"Dunder" é uma contração da frase em inglês "double underscore before and after" (_sublinhado duplo antes e depois_). -Por isso os métodos especiais são também conhecidos como _métodos dunder_. -O capítulo https://docs.python.org/pt-br/3/reference/lexical_analysis.html#reserved-classes-of-identifiers["Análise Léxica"] de _A Referência da Linguagem Python_ adverte que "_Qualquer_ uso de nomes no formato `+__*__+` que não siga explicitamente o uso documentado, em qualquer contexto, está sujeito a quebra sem aviso prévio." -==== - - -=== Novidades nesse capítulo - -Esse((("Python Data Model", "significant changes to"))) capítulo sofreu poucas alterações desde a primeira edição, pois é uma introdução ao Modelo de Dados do Python, que é muito estável. As mudanças mais significativas foram: - -* Métodos especiais que suportam programação assíncrona e outras novas funcionalidades foram acrescentados às tabelas em <>. - -* A <>, mostrando o uso de métodos especiais em <>, incluindo a classe base abstrata `collections.abc.Collection`, introduzida no Python 3.6. - -Além disso, aqui((("f-string syntax", "benefits of"))) e por toda essa segunda edição, adotei a sintaxe _f-string_, introduzida no Python 3.6, -que é mais legível e muitas vezes mais conveniente que as notações de formatação de strings mais antigas: -o((("str.format() method")))((("% (modulo) operator")))((("modulo (%) operator"))) método `str.format()` e o operador `%`. - -[role="man-height-1-125"] -[TIP] -==== -Existe((("my_fmt.format() method"))) ainda uma razão para usar `my_fmt.format()`: quando a definição de `my_fmt` precisa vir de um lugar diferente daquele onde a operação de formatação precisa acontecer no código. Por exemplo, quando `my_fmt` tem múltiplas linhas e é melhor definida em uma constante, ou quando tem de vir de um arquivo de configuração ou de um banco de dados. Essas são necessidades reais, mas não acontecem com frequência. -==== - -//// -PROD: As is often the case, the TIP above may or may not run over the next paragraphs, -depending on some random factor I don't know how to control. -//// - -[[pythonic_card_deck]] -=== Um baralho pythônico - -O <> é((("__getitem__", id="getitem01")))((("__len__", id="len01")))((("Pythonic Card Deck example", id="pycard01")))((("Python Data Model", "__getitem__ and __len__", id="PDMgetitem01", secondary-sortas="getitem")))((("special methods", "__getitem__ and __len__", id="SMgetitem01", secondary-sortas="getitem")))((("card deck example", id="carddeck01"))) simples, mas demonstra as possibilidades que se abrem com a implementação de apenas dois métodos especiais, `+__getitem__+` e `+__len__+`. - -[[ex_pythonic_deck]] -.Um baralho como uma sequência de cartas -==== -[source, python3] ----- -include::code/01-data-model/frenchdeck.py[] ----- -==== - - - -A((("collections.namedtuple"))) primeira coisa a se observar é o uso de `collections.namedtuple` -para construir uma classe simples representando cartas individuais. -Usamos `namedtuple` para criar classes de objetos que são apenas um agrupamento de atributos, sem métodos próprios, como um registro de banco de dados. Neste exemplo, a utilizamos para fornecer uma boa representação para as cartas em um baralho, como mostra a sessão no console: - -[source, pycon] ----- ->>> beer_card = Card('7', 'diamonds') ->>> beer_card -Card(rank='7', suit='diamonds') ----- - -Mas a parte central desse exemplo é a classe `FrenchDeck`. -Ela é curta, mas poderosa. -Primeiro, como qualquer coleção padrão do Python, uma instância de `FrenchDeck` responde à função((("len() function")))((("functions", "len() function"))) `len()`, devolvendo o número de cartas naquele baralho: - -[source, pycon] ----- ->>> deck = FrenchDeck() ->>> len(deck) -52 ----- - -Ler cartas específicas do baralho é fácil, graças ao método `+__getitem__+`. -Por exemplo, a primeira e a última carta: - -[source, pycon] ----- ->>> deck[0] -Card(rank='2', suit='spades') ->>> deck[-1] -Card(rank='A', suit='hearts') ----- - -Deveríamos criar um método para obter uma carta aleatória? Não é necessário. -O Python((("random.choice function"))) já tem uma função que devolve um item aleatório de uma sequência: `random.choice`. -Podemos usá-la em uma instância de `FrenchDeck`: - -[source, pycon] ----- ->>> from random import choice ->>> choice(deck) -Card(rank='3', suit='hearts') ->>> choice(deck) -Card(rank='K', suit='spades') ->>> choice(deck) -Card(rank='2', suit='clubs') ----- - -Acabamos((("special methods", "advantages of using"))) de ver duas vantagens de usar os métodos especiais no contexto do Modelo de Dados do Python. - -* Os usuários de suas classes não precisam memorizar nomes arbitrários de métodos para operações comuns ("Como descobrir o número de itens? Seria `.size()`, `.length()` ou alguma outra coisa?") - -* É mais fácil de aproveitar a rica biblioteca padrão do Python e evitar reinventar a roda, como no caso da função `random.choice`. - -Mas fica melhor. - -Como nosso `+__getitem__+` usa o((("square brackets ([])")))((("[] (square brackets)"))) -operador `[]` de `self._cards`, -nosso baralho suporta fatiamento automaticamente. -Podemos olhar as três primeiras cartas no topo de um novo baralho, -e depois pegar apenas os ases, iniciando com o índice 12 e pulando 13 cartas por vez: - -[source, pycon] ----- ->>> deck[:3] -[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), -Card(rank='4', suit='spades')] ->>> deck[12::13] -[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), -Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')] ----- - -E como já temos o método especial `+__getitem__+`, nosso baralho é um objeto iterável, -ou seja, pode ser percorrido em um laço `for`: - -[source, pycon] ----- ->>> for card in deck: # doctest: +ELLIPSIS -... print(card) -Card(rank='2', suit='spades') -Card(rank='3', suit='spades') -Card(rank='4', suit='spades') -... ----- - -Também podemos iterar sobre o baralho na ordem inversa: - -[source, pycon] ----- ->>> for card in reversed(deck): # doctest: +ELLIPSIS -... print(card) -Card(rank='A', suit='hearts') -Card(rank='K', suit='hearts') -Card(rank='Q', suit='hearts') -... ----- - -.Reticências nos doctests -[NOTE] -==== -Sempre((("doctest package", "ellipsis in")))((("ellipsis (…)")))((("… (ellipsis)"))) que possível, extraí as listagens do console do Python usadas neste livro com o -https://docs.python.org/pt-br/3/library/doctest.html[`doctest`], para garantir a precisão. -Quando a saída era grande demais, a parte omitida está marcada por reticências (`...`), como na última linha do trecho de código anterior. - -Nesse casos, usei((("+ELLIPSIS directive"))) a diretiva `# doctest: +ELLIPSIS` para fazer o doctest funcionar. -Se você estiver tentando rodar esses exemplos no console iterativo, pode simplesmente omitir todos os comentários de doctest. -==== - -A iteração muitas vezes é implícita. -Se uma coleção não((("__contains__"))) fornecer um método `+__contains__+`, o operador `in` realiza uma busca sequencial. -No nosso caso, `in` funciona com nossa classe `FrenchDeck` porque ela é iterável. -Veja a seguir: - -[source, pycon] ----- ->>> Card('Q', 'hearts') in deck -True ->>> Card('7', 'beasts') in deck -False ----- - -E o ordenamento? -Um sistema comum de ordenar cartas é por seu valor numérico (ases sendo os mais altos) e depois por naipe, na ordem espadas (o mais alto), copas, ouros e paus (o mais baixo). -Aqui está uma função que ordena as cartas com essa regra, -devolvendo `0` para o 2 de paus e `51` para o às de espadas. - -[source, python3] ----- -suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0) - -def spades_high(card): - rank_value = FrenchDeck.ranks.index(card.rank) - return rank_value * len(suit_values) + suit_values[card.suit] ----- - -Podemos agora listar nosso baralho em ordem crescente de usando `spades_high` como critério de ordenação: - -[source, pycon] ----- ->>> for card in sorted(deck, key=spades_high): # doctest: +ELLIPSIS -... print(card) -Card(rank='2', suit='clubs') -Card(rank='2', suit='diamonds') -Card(rank='2', suit='hearts') -... (46 cards omitted) -Card(rank='A', suit='diamonds') -Card(rank='A', suit='hearts') -Card(rank='A', suit='spades') ----- - -Apesar da `FrenchDeck` herdar implicitamente da classe `object`, -a maior parte de sua funcionalidade não é herdada, vem do uso do modelo de dados e de composição. -Ao implementar os métodos especiais `+__len__+` e `+__getitem__+`, -nosso `FrenchDeck` se comporta como uma sequência Python padrão, -podendo assim se beneficiar de recursos centrais da linguagem (por exemplo, iteração e fatiamento), -e da biblioteca padrão, como mostramos nos exemplos usando `random.choice`, -pass:[reversed], e `sorted`. -Graças à composição, -as implementações de `+__len__+` e `+__getitem__+` podem delegar todo o trabalho para um objeto `list`, especificamente `self._cards`.((("", startref="PDMgetitem01")))((("", startref="getitem01")))((("", startref="len01")))((("", startref="pycard01")))((("", startref="SMgetitem01")))((("", startref="carddeck01"))) - -.E como embaralhar as cartas? -[NOTE] -===================================================================== -Como foi implementado até aqui, um `FrenchDeck` não pode ser embaralhado, -porque as cartas e suas posições não podem ser alteradas, -exceto violando o encapsulamento e manipulando o atributo `_cards` diretamente. -Em <> vamos corrigir isso acrescentando um método `+__setitem__+` -de uma linha. Você consegue imaginar como ele seria implementado? -===================================================================== - - -[[how_special_used]] -=== Como os métodos especiais são utilizados - -A((("Python Data Model", "using special methods", id="PDMspecmeth01")))((("special methods", "calling"))) primeira coisa para se saber sobre os métodos especiais é que eles foram feitos para serem chamados pelo interpretador Python, e não por você. -Você não escreve `+my_object.__len__()+`. -Escreve `len(my_object)` e, se `my_object` é uma instância de de uma classe definida pelo usuário, então o Python chama o método `+__len__+` que você implementou. - -Mas o interpretador pega um atalho quando está lidando com um tipo embutido como `list`, `str`, `bytearray`, ou extensões como os arrays do NumPy. -As coleções de tamanho variável do Python escritas em C incluem uma structfootnote:[Uma struct do C é um tipo de registro com campos nomeados.] -chamada `PyVarObject`, com um campo `ob_size` que mantém o número de itens na coleção. Então, se `my_object` é uma instância de algum daqueles tipos embutidos, `len(my_object)` lê o valor do campo `ob_size`, e isso é muito mais rápido que chamar um método. - -Na maior parte das vezes, a chamada a um método especial é implícita. -Por exemplo, o comando `for i in x:` na verdade gera uma invocação de `iter(x)`, -que por sua vez pode chamar `+x.__iter__()+` se esse método estiver disponível, -ou usar `+x.__getitem__()+`, como no exemplo do `FrenchDeck`. - -Em condições normais, seu código não deveria conter muitas chamadas diretas a métodos especiais. A menos que você esteja fazendo muita metaprogramação, implementar métodos especiais deve ser muito mais frequente que invocá-los explicitamente. O((("__init__"))) único método especial que é chamado frequentemente pelo seu código é `+__init__+`, -para invocar o método de inicialização da superclasse na implementação do seu próprio `+__init__+`. - -Geralmente, se você precisa invocar um método especial, é melhor chamar a função embutida relacionada (por exemplo, `len`, `iter`, `str`, etc.). -Essas funções chamam o método especial correspondente, -mas também fornecem outros serviços e—para tipos embutidos—são mais rápidas que chamadas a métodos. -Veja, por exemplo, <> no <>. - -Na próxima seção veremos alguns dos usos mais importantes dos métodos especiais: - -* Emular tipos numéricos -* Representar objetos na forma de strings -* Determinar o valor booleano de um objeto -* Implementar de coleções - - -[[data_model_emulating_sec]] -==== Emulando tipos numéricos - -Vários((("special methods", "emulating numeric types", id="SMnumeric01")))((("numeric types", "emulating using special methods", id="NTemul01"))) métodos especiais permitem que objetos criados pelo usuário respondam a operadores como `+`. -Vamos tratar disso com mais detalhes no capítulo <>. -Aqui nosso objetivo é continuar ilustrando o uso dos métodos especiais, através de outro exemplo simples. - -Vamos((("vectors", "representing two-dimensional", id="Vtwo01"))) implementar uma classe para representar vetores bi-dimensionais—isto é, vetores euclidianos como aqueles usados em matemática e física (veja a <>). - -[TIP] -====== -O tipo embutido `complex` pode ser usado para representar vetores bi-dimensionais, -mas nossa classe pode ser estendida para representar vetores __n__-dimensionais. -Faremos isso em <>. -====== - -[[vectors_fig]] -.Exemplo de adição de vetores bi-dimensionais; Vector(2, 4) + Vector(2, 1) resulta em Vector(4, 5). -image::images/flpy_0101.png[vetores 2D] - -Vamos começar a projetar a API para essa classe escrevendo em uma sessão de console simulada, que depois podemos usar como um doctest. -O trecho a seguir testa a adição de vetores ilustrada na <>: - -[source, pycon] ----- ->>> v1 = Vector(2, 4) ->>> v2 = Vector(2, 1) ->>> v1 + v2 -Vector(4, 5) ----- - -Observe((("+ operator"))) como o operador `+` produz um novo objeto `Vector(4, 5)`. - -A((("abs built-in function")))((("functions", "abs built-in function"))) função embutida `abs` devolve o valor absoluto de números inteiros e de ponto flutuante, e a magnitude de números `complex`. Então, por consistência, nossa API também usa `abs` para calcular a magnitude de um vetor: - -[source, pycon] ----- ->>> v = Vector(3, 4) ->>> abs(v) -5.0 ----- - -Podemos((("* (star) operator")))((("multiplication, scalar")))((("star (*) operator"))) também implementar o operador `*`, para realizar multiplicação escalar (isto é, multiplicar um vetor por um número para obter um novo vetor de mesma direção e magnitude multiplicada): - -[source, pycon] ----- ->>> v * 3 -Vector(9, 12) ->>> abs(v * 3) -15.0 ----- - -O <> é uma classe `Vector` que implementa as operações descritas acima, usando os métodos especiais((("__repr__", id="repr01")))((("__mul__")))((("__add__")))((("__abs__"))) `+__repr__+`, `+__abs__+`, `+__add__+`, e `+__mul__+`. - -[[ex_vector2d]] -.A simple two-dimensional vector class -==== -[source, python3] ----- -include::code/01-data-model/vector2d.py[] ----- -==== - -Implementamos cinco métodos especiais, além do costumeiro `+__init__+`. -Veja que nenhum deles é chamado diretamente dentro da classe ou durante seu uso normal, ilustrado pelos doctests. -Como mencionado antes, o interpretador Python é o único usuário frequente da maioria dos métodos especiais. - -O <> implementa dois operadores: `+` e `*`, -para demonstrar o uso básico de `+__add__+` e `+__mul__+`. -No dois casos, os métodos criam e devolvem uma nova instância de `Vector`, -e não modificam nenhum dos operandos: `+self+` e `other` são apenas lidos. -Esse é o comportamento esperado de operadores infixos: criar novos objetos e não tocar em seus operandos. -Vou falar muito mais sobre esse tópico no capítulo <>. - -[WARNING] -==== -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. -Vamos consertar isso com o método especial `+__rmul__+` no capítulo <>. -==== - -Nas seções seguintes vamos discutir os outros métodos especiais em `Vector`.((("", startref="SMnumeric01")))((("", startref="NTemul01")))((("", startref="Vtwo01"))) - - -[[repr_intro]] -==== Representação de strings - -O((("special methods", "string representation")))((("strings", "representation using special methods"))) método especial `+__repr__+` é chamado pelo `repr` embutido para obter a representação do objeto como string, para inspeção. Sem um `+__repr__+` personalizado, o console do Python mostraria uma instância de `Vector` como ``. - -O console iterativo e o depurador chamam `repr` para exibir o resultado das expressões. -O `repr` também é usado: - -* Pelo marcador posicional `%r` na formatação clássica com o operador `%`. Ex.: `'%r' % my_obj` -* Pelo sinalizador de conversão `!r` na nova https://docs.python.org/pt-br/3.10/library/string.html#format-string-syntax[sintaxe de strings de formato] usada nas((("f-string syntax", "string representation using special methods"))) _f-strings_ e no método `str.format`. -Ex: `f'{my_obj!r}'` - -Note que a _f-string_ no nosso `+__repr__+` usa `!r` para obter a representação padrão dos atributos a serem exibidos. -Isso é uma boa prática, pois durante uma seção de depuração podemos ver a diferença entre `Vector(1, 2)` e `Vector('1', '2')`. Este segundo objeto não funcionaria no contexto desse exemplo, porque nosso código espera que os argumentos do construtor sejam números, não `str`. - -A string devolvida por `+__repr__+` não deve ser ambígua e, se possível, deve corresponder ao código-fonte necessário para recriar o objeto representado. É por isso que nossa representação de `Vector` se parece com uma chamada ao construtor da classe, por exemplo `Vector(3, 4)`. - -Por((("__str__")))((("str() function")))((("functions", "str() function"))) outro lado, `+__str__+` é chamado pelo método embutido `str()` e usado implicitamente pela função `print`. -Ele deve devolver uma string apropriada para ser exibida aos usuários finais. - -Algumas vezes a própria string devolvida por `+__repr__+` é adequada para exibir ao usuário, -e você não precisa programar `+__str__+`, porque a implementação herdada da classe `object` chama -`+__repr__+` como alternativa. -O <> é um dos muitos exemplos neste livro com um `+__str__+` personalizado. - -[TIP] -==== -Programadores com experiência anterior em linguagens que contém o método `toString` tendem a implementar `+__str__+` e não `+__repr__+`. -Se você for implementar apenas um desses métodos especiais, escolha `+__repr__+`. - -https://fpy.li/1-5["What is the difference between `+__str__+` and `+__repr__+` in Python?" (_Qual a diferença entre `+__str__+` e `+__repr__+` em Python?_)] (EN) é uma questão no Stack Overflow com excelentes contribuições dos pythonistas Alex Martelli e Martijn Pieters.((("", startref="repr01"))) -==== - - -==== O valor booleano de um tipo personalizado - -Apesar ((("__bool__")))((("special methods", "Boolean values of custom types")))((("Boolean values, custom types and")))((("bool type"))) do Python ter um tipo `bool`, ele aceita qualquer objeto em um contexto booleano, tal como as expressões controlando uma instrução `if` ou `while`, ou como operandos de `and`, `or` e `not`. -Para determinar se um valor `x` é _verdadeiro_ ou _falso_, o Python invoca `bool(x)`, -que devolve `True` ou `False`. - -Por default, instâncias de classes definidas pelo usuário são consideradas verdadeiras, a menos que `+__bool__+` ou `+__len__+` estejam implementadas. -Basicamente, `bool(x)` chama `+x.__bool__()+` e usa o resultado. -Se `+__bool__+` não está implementado, o Python tenta invocar `+x.__len__()+`, e se esse último devolver zero, `bool` devolve `False`. -Caso contrário, `bool` devolve `True`. - -Nossa implementação de `+__bool__+` é conceitualmente simples: -ela devolve `False` se a magnitude do vetor for zero, caso contrário devolve `True`. -Convertemos a magnitude para um valor booleano usando `bool(abs(self))`, porque espera-se que -`+__bool__+` devolva um booleano. -Fora dos métodos `+__bool__+`, raramente é necessário chamar `bool()` explicitamente, -porque qualquer objeto pode ser usado em um contexto booleano. - -Observe que o método especial `+__bool__+` permite que seus objetos sigam as regras de teste do valor verdade definidas no https://docs.python.org/pt-br/3/library/stdtypes.html#truth[capítulo "Tipos Embutidos"] da documentação da _Biblioteca Padrão do Python_. - -[NOTE] -==== -Essa é uma implementação mais rápida de `+Vector.__bool__+`: - -[source, python3] ----- - def __bool__(self): - return bool(self.x or self.y) ----- - -Isso é mais difícil de ler, mas evita a jornada através de `abs`, `+__abs__+`, os quadrados, e a raiz quadrada. -A conversão explícita para `bool` é necessária porque `+__bool__+` deve devolver um booleano, e `or` devolve um dos seus operandos no formato original: `x or y` resulta em `x` se x for verdadeiro, caso contrário resulta em `y`, 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 - -A <> documenta((("special methods", "Collection API", id="SMcollection01")))((("Collection API", id="Cspeical01")))((("ABCs (abstract base classes)", "UML class diagrams", id="abcs01")))((("UML class diagrams", "fundamental collection types"))) as interfaces dos tipos de coleções essenciais na linguagem. Todas as classes no diagrama são ABCs—_classes base abstratas_ (_ABC é a sigla para a mesma expressão em inglês, Abstract Base Classes_). As ABCs e o módulo `collections.abc` são tratados no capítulo <>. -O objetivo dessa pequena seção é dar uma visão panorâmica das interfaces das coleções mais importantes do Python, mostrando como elas são criadas a partir de métodos especiais. - -[role="width-70"] -[[collection_uml]] -.Diagrama de classes UML com os tipos fundamentais de coleções. Métodos como nome em _itálico_ são abstratos, então precisam ser implementados pelas subclasses concretas, tais como `list` e `dict`. O restante dos métodos tem implementações concretas, então as subclasses podem herdá-los. -image::images/flpy_0102.png[Diagram de classes UML com todas as superclasses e algumas subclasses de `abc.Collection`] - -Cada uma das ABCs no topo da hierarquia tem um único método especial. -A ABC `Collection` (introduzida no Python 3.6) unifica as três interfaces essenciais, que toda coleção deveria implementar: - -* `Iterable`, para((("Iterable interface")))((("interfaces", "Iterable interface"))) suportar `for`, https://docs.python.org/pt-br/3/tutorial/controlflow.html#unpacking-argument-lists[desempacotamento], e outras formas de iteração -* `Sized` para((("Sized interface")))((("interfaces", "Sized interface"))) suportar a função embutida `len` -* `Container` para((("Container interface")))((("interfaces", "Container interface"))) suportar o operador `in` - -Na verdade, o Python não exige que classes concretas herdem de qualquer dessas ABCs. -Qualquer classe que implemente `+__len__+` satisfaz a interface `Sized`. - -Três especializações muito importantes de `Collection` são: - -* `Sequence`, formalizando a interface de tipos embutidos como `list` e `str` -* `Mapping`, implementado por `dict`, `collections.defaultdict`, etc. -* `Set`, a interface dos tipos embutidos `set` e `frozenset` - -Apenas `Sequence` é `Reversible`, porque sequências suportam o ordenamento arbitrário de seu conteúdo, ao contrário de mapeamentos(_mappings_) e conjuntos(_sets_). - -[NOTE] -==== -Desde((("keys", "preserving key insertion order"))) o Python 3.7, o tipo `dict` é oficialmente "ordenado", mas isso só que dizer que a ordem de inserção das chaves é preservada. -Você não pode rearranjar as chaves em um `dict` da forma que quiser. -==== - -Todos os métodos especiais na ABC `Set` implementam operadores infixos. -Por exemplo, `a & b` calcula a intersecção entre os conjuntos `a` e `b`, -e é implementada no método especial `+__and__+`. - -Os próximos dois capítulos vão tratar em detalhes das sequências, mapeamentos e conjuntos da biblioteca padrão. - -Agora vamos considerar as duas principais categorias dos métodos especiais definidos no Modelo de Dados do Python.((("", startref="PDMspecmeth01")))((("", startref="SMcollection01")))((("", startref="Cspeical01")))((("", startref="abcs01"))) - -[[overview_special_methods]] -=== Visão geral dos, métodos especiais - -O((("Python Data Model", "special methods overview", id="PDMspmtov01")))((("special methods", "special method names (operators excluded)"))) https://docs.python.org/pt-br/3/reference/datamodel.html[capítulo "Modelo de Dados"] de _A Referência da Linguagem Python_ lista mais de 80 nomes de métodos especiais. -Mais da metade deles implementa operadores aritméticos, bit a bit, ou de comparação. -Para ter uma visão geral do que está disponível, veja tabelas a seguir. - -// Table pass:[#special_names_tbl] -A <> mostra nomes de métodos especiais, excluindo aqueles usados para implementar operadores infixos ou funções matemáticas fundamentais como `abs`. A maioria desses métodos será tratado ao longo do livro, incluindo as adições mais recentes: -métodos especiais assíncronos como `+__anext__+` (acrescentado no Python 3.5), e o método de personalização de classes, `+__init_subclass__+` (do Python 3.6). - -//// -PROD: Please make sure funcion names such as divmod() and __truediv__ are not broken at line breaks in the table cells. Thanks! -I put all the method names in each table cell inside a single code markup `x` because if each name is in a separate ``, then the space between the names looks too small. -If it's too hard to increase the space between the words, then we could put a semicolon between them. In the first edition we used colons but it looked like a tiny defect. -I tried to use keep-together in <> but it was rendering a 0 (zero) instead of the content within the element. -//// - -[[special_names_tbl]] -.Nomes de métodos especiais (excluindo operadores) -[options="header"] -|================================================================================================= -|Categoria|Nomes dos métodos -|Representação de string/bytes|`+__repr__ __str__ __format__ __bytes__ __fspath__+` -|Conversão para número|`+__bool__ __complex__ __int__ __float__ __hash__ __index__+` -|Emulação de coleções|pass:[__len__ __getitem__ __setitem__ __delitem__ __contains__] -|Iteração|`+__iter__ __aiter__ __next__ __anext__ __reversed__+` -|Execução de chamável ou corrotina|`+__call__ __await__+` -|Gerenciamento de contexto|`+__enter__ __exit__ __aexit__ __aenter__+` -|Criação e destruição de instâncias|`+__new__ __init__ __del__+` -|Gerenciamento de atributos|`+__getattr__ __getattribute__ __setattr__ __delattr__ __dir__+` -|Descritores de atributos|`+__get__ __set__ __delete__ __set_name__+` -|Classes base abstratas|`+__instancecheck__ __subclasscheck__+` -|Metaprogramação de classes|`+__prepare__ __init_subclass__ __class_getitem__ __mro_entries__+` -|================================================================================================= - -Operadores infixos e numéricos são suportados pelos métodos especiais listados na -<>. -// pass:[#special_operators_tbl]. -Aqui os nomes mais recentes são `+__matmul__+`, `+__rmatmul__+`, e `+__imatmul__+`, adicionados no Python 3.5 para suportar o uso de `@` como um operador infixo de multiplicação de matrizes, como veremos no capítulo <>.((("special methods", "special method names and symbols for operators"))) - -[[special_operators_tbl]] -.Nomes e símbolos de métodos especiais para operadores -[options="header"] -|===================================================================================================================================================================================== -|Categoria do operador|Símbolos|Nomes de métodos -|Unário numérico| `- + abs()` | `+__neg__ __pos__ __abs__+` -|Comparação rica| `< \<= == != > >=` | `+__lt__ __le__ __eq__ __ne__ __gt__ __ge__+` -|Aritmético| `+ - * / // % @ divmod() round() ** pow()` | `+__add__ __sub__ __mul__+` pass:[ __truediv__ ] `+__floordiv__ __mod__+` pass:[ __matmul__ ] `+__divmod__ __round__ __pow__+` -|Aritmética reversa| operadores aritméticos com operandos invertidos) |`+__radd__ __rsub__ __rmul__ __rtruediv__ __rfloordiv__ __rmod__ __rmatmul__ __rdivmod__ __rpow__+` -|Atribuição aritmética aumentada| `+= -= \*= /= //= %= @= **=` | `+__iadd__ __isub__ __imul__ __itruediv__ __ifloordiv__ __imod__ __imatmul__ __ipow__+` -|Bit a bit | `& \| ^ << >> ~` | `+__and__ __or__ __xor__ __lshift__ __rshift__ __invert__+` -|Bit a bit reversa| (operadores bit a bit com os operandos invertidos) | `+__rand__ __ror__ __rxor__ __rlshift__ __rrshift__+` -|Atribuição bit a bit aumentada| `&= \|= ^= <<= >>=` | `+__iand__ __ior__ __ixor__ __ilshift__ __irshift__+` -|===================================================================================================================================================================================== - -[NOTE] -==== -O Python invoca um método especial de operador reverso no segundo argumento quando o método especial correspondente não pode ser usado no primeiro operando. -Atribuições aumentadas são atalho combinando um operador infixo com uma atribuição de variável, por exemplo `a += b`. - -O capítulo <> explica em detalhes os operadores reversos e a atribuição aumentada.((("", startref="PDMspmtov01"))) -==== - - -=== Porque len não é um método? - -Em 2013, fiz((("Python Data Model", "making len work with custom objects")))((("__len__"))) essa pergunta a Raymond Hettinger, um dos desenvolvedores principais do Python, e o núcleo de sua resposta era uma citação do https://fpy.li/1-8["The Zen of Python" (_O Zen do Python_)] (EN): "a praticidade vence a pureza." -Em <>, descrevi como `len(x)` roda muito rápido quando `x` é uma instância de um tipo embutido. -Nenhum método é chamado para os objetos embutidos do CPython: o tamanho é simplesmente lido de um campo em uma struct C. -Obter o número de itens em uma coleção é uma operação comum, e precisa funcionar de forma eficiente para tipos tão básicos e diferentes como `str`, `list`, `memoryview`, e assim por diante. - -Em outras palavras, `len` não é chamado como um método porque recebe um tratamento especial como parte do Modelo de Dados do Python, da mesma forma que `abs`. -Mas graças ao método especial pass:[__len__], também é possível fazer `len` funcionar com nossos objetos personalizados. -Isso é um compromisso justo entre a necessidade de objetos embutidos eficientes e a consistência da linguagem. -Também de "O Zen do Python": "Casos especiais não são especiais o bastante para quebrar as regras." - - -[NOTE] -==== -Pensar em `abs` e `len` como operadores unários nos deixa mais inclinados a perdoar seus aspectos funcionais, contrários à sintaxe de chamada de método que esperaríamos em uma linguagem orientada a objetos. -De fato, a linguagem ABC—uma ancestral direta do Python, que antecipou muitas das funcionalidades desta última—tinha o operador `#`, que era o equivalente de `len` (se escrevia `#s`). -Quando usado como operador infixo, `x#s` contava as ocorrências de `x` em `s`, que em Python obtemos com `s.count(x)`, para qualquer sequência `s`. -==== - -[role="pagebreak-before less_space"] -=== Resumo do capítulo - -Ao((("Python Data Model", "overview of"))) implementar métodos especiais, seus objetos podem se comportar como tipos embutidos, permitindo o estilo de programação expressivo que a comunidade considera pythônico. - -Uma exigência básica para um objeto Python é fornecer strings representando a si mesmo que possam ser usadas, uma para depuração e registro (_log_), outra para apresentar aos usuários finais. É para isso que os métodos especiais `+__repr__+` e `+__str__+` existem no modelo de dados. - -Emular sequências, como mostrado com o exemplo do `FrenchDeck`, é um dos usos mais comuns dos métodos especiais. -Por exemplo, bibliotecas de banco de dados frequentemente devolvem resultados de consultas na forma de coleções similares a sequências. -Tirar o máximo proveito dos tipos de sequências existentes é o assunto do capítulo <>. -Como implementar suas próprias sequências será visto na seção <>, -onde criaremos uma extensão multidimensional da classe `Vector`. - -Graças à sobrecarga de operadores, o Python oferece uma rica seleção de tipos numéricos, desde os tipos embutidos até `decimal.Decimal` e `fractions.Fraction`, -todos eles suportando operadores aritméticos infixos. -As bibliotecas de ciência de dados _NumPy_ suportam operadores infixos com matrizes e tensores. A implementação de operadores—incluindo operadores reversos e atribuição aumentada—será vista no capítulo <>, usando melhorias do exemplo `Vector`. - -Também veremos o uso e a implementação da maioria dos outros métodos especiais do Modelo de Dados do Python ao longo deste livro. - - -=== Para saber mais - -O((("Python Data Model", "further reading on"))) https://docs.python.org/pt-br/3/reference/datamodel.html[capítulo "Modelo de Dados"] em _A Referência da Linguagem Python_ é a fonte canônica para o assunto desse capítulo e de uma boa parte deste livro. - -pass:[Python in a Nutshell, 3rd ed.] (EN), de Alex Martelli, Anna Ravenscroft, e Steve Holden (O'Reilly) tem uma excelente cobertura do modelo de dados. Sua descrição da mecânica de acesso a atributos é a mais competente que já vi, perdendo apenas para o próprio código-fonte em C do CPython. Martelli também é um contribuidor prolífico do Stack Overflow, com mais de 6200 respostas publicadas. Veja seu perfil de usuário no https://fpy.li/1-9[Stack Overflow]. - -David Beazley tem dois livros tratando do modelo de dados em detalhes, no contexto do Python 3: pass:[Python Essential Reference] (EN), 4th ed. (Addison-Wesley), -e pass:[Python Cookbook, 3rd ed.] (EN) (O'Reilly), com a co-autoria de Brian K. Jones. - -O pass:[The Art of the Metaobject Protocol] (EN) (MIT Press) de Gregor Kiczales, Jim des Rivieres, e Daniel G. Bobrow explica o conceito de um protocolo de metaobjetos, do qual o Modelo de Dados do Python é um exemplo. - - - -.Ponto de Vista -**** - -[role="soapbox-title"] -Modelo de dados ou modelo de objetos? - - -Aquilo((("Soapbox sidebars", "data model versus object model")))((("Python Data Model", "Soapbox discussion"))) que a documentação do Python chama de "Modelo de Dados do Python", a maioria dos autores diria que é o "Modelo de objetos do Python" - -O _Python in a Nutshell_, 3rd ed. de Martelli, Ravenscroft, e Holden, e o _Python Essential Reference_, 4th ed., de David Beazley são os melhores livros sobre o Modelo de Dados do Python, mas se referem a ele como o "modelo de objetos." -Na Wikipedia, a primeira definição de https://fpy.li/1-10["modelo de objetos"] (EN) é: -"as propriedades dos objetos em geral em uma linguagem de programação de computadores específica." -É disso que o Modelo de Dados do Python trata. -Neste livro, usarei "modelo de dados" porque a documentação prefere este termo ao se referir ao modelo de objetos do Python, e porque esse é o título do https://docs.python.org/pt-br/3/reference/datamodel.html[capítulo de _A Referência da Linguagem Python_] mais relevante para nossas discussões. - - -[role="soapbox-title"] -Métodos de "trouxas" - -O https://fpy.li/1-11[_The Original Hacker's Dictionary_ (_Dicionário Hacker Original_)] (EN) define ((("Soapbox sidebars", "magic methods")))((("magic methods"))) _mágica_ como "algo ainda não explicado ou muito complicado para explicar" ou "uma funcionalidade, em geral não divulgada, que permite fazer algo que de outra forma seria impossível." - -A comunidade Ruby chama o equivalente dos métodos especiais naquela linguagem de _métodos mágicos_. -Muitos integrantes da comunidade Python também adotam esse termo. -Eu acredito que os métodos especiais são o contrário de mágica. -O Python e o Ruby oferecem a seus usuários um rico protocolo de metaobjetos integralmente documentado, permitindo que "trouxas" como você e eu possam emular muitas das funcionalidades disponíveis para os desenvolvedores principais que escrevem os interpretadores daquelas linguagens. - -Por outro lado, pense no Go. Alguns objetos naquela linguagem tem funcionalidades que são mágicas, no sentido de não poderem ser emuladas em nossos próprios objetos definidos pelo usuário. -Por exemplo, os arrays, strings e mapas do Go suportam o uso de colchetes para acesso a um item, na forma `a[i]`. -Mas não há como fazer a notação `[]` funcionar com um novo tipo de coleção definida por você. -Pior ainda, o Go não tem o conceito de uma interface iterável ou um objeto iterador ao nível do usuário, daí sua sintaxe para `for/range` estar limitada a suportar cinco tipos "mágicos" embutidos, incluindo arrays, strings e mapas. - -Talvez, no futuro, os projetistas do Go melhorem seu protocolo de metaobjetos. -Em 2021, ele ainda é muito mais limitado do que Python, Ruby, e JavaScript oferecem. - - -[role="soapbox-title"] -Metaobjetos - -_The Art of the Metaobject Protocol (AMOP)_ (_A Arte do protocolo de metaobjetos_) é((("Soapbox sidebars", "metaobjects")))((("metaobjects"))) meu título favorito entre livros de computação. Mas o menciono aqui porque o termo _protocolo de metaobjetos_ é útil para pensar sobre o Modelo de Dados do Python, e sobre recursos similares em outras linguagens. -A parte _metaobjetos_ se refere aos objetos que são os componentes essenciais da própria linguagem. Nesse contexto, _protocolo_ é sinônimo de _interface_. Assim, um _protocolo de metaobjetos_ é um sinônimo chique para modelo de objetos: uma API para os elementos fundamentais da linguagem. - -Um protocolo de metaobjetos rico permite estender a linguagem para suportar novos paradigmas de programação. Gregor Kiczales, o primeiro autor do _AMOP_, mais tarde se tornou um pioneiro da programação orientada a aspecto, e o autor inicial do AspectJ, uma extensão de Java implementando aquele paradigma. -A programação orientada a aspecto é muito mais fácil de implementar em uma linguagem dinâmica como Python, e algumas frameworks fazem exatamente isso. -O exemplo mais importante é a https://fpy.li/1-12[_zope.interface_] (EN), -parte da framework sobre a qual o sistema de gerenciamento de conteúdo https://plone.org.br/[Plone] é construído. -**** - diff --git a/capitulos/cap03.adoc b/capitulos/cap03.adoc deleted file mode 100644 index 0b7fed2c..00000000 --- a/capitulos/cap03.adoc +++ /dev/null @@ -1,1321 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte - -[[dicts-a-to-z]] -== Dicionários e conjuntos - -[quote, Lalo Martins, pioneiro do nomadismo digital e pythonista] -____ -O Python é feito basicamente de dicionários cobertos por muitas camadas de açúcar sintático -____ - -Usamos dicionários em todos os nossos programas Python. Se não diretamente em nosso código, então indiretamente, pois o tipo `dict` é um elemento fundamental da implementação do Python. -Atributos de classes e de instâncias, espaços de nomes de módulos e argumentos nomeados de funções são alguns dos elementos fundamentais do Python representados na memória por dicionários. -O `+__builtins__.__dict__+` armazena todos os tipos, funções e objetos embutidos. - -Por seu papel crucial, os dicts do Python são extremamente otimizados—e continuam recebendo melhorias. As _Tabelas de hash_((("hash tables"))) são o motor por trás do alto desempenho dos dicts do Python. - -Outros tipos embutidos baseados em tabelas de hash são `set` e `frozenset`. Eles oferecem uma API mais completa e operadores mais robustos que os conjuntos que você pode ter encontrado em outras linguagens populares. Em especial, os conjuntos do Python implementam todas as operações fundamentais da teoria dos conjuntos, como união, intersecção, testes de subconjuntos, etc. Com eles, podemos expressar algoritmos de forma mais declarativa, evitando o excesso de loops 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) -* Métodos comuns dos tipos de mapeamentos -* Tratamento especial para chaves ausentes -* Variantes de `dict` na biblioteca padrão -* Os tipos `set` e `frozenset` -* As implicações das tabelas de hash no comportamento de conjuntos e dicionários - - -=== Novidades nesse capítulo - -A((("dictionaries and sets", "significant changes to"))) maior parte das mudanças nessa segunda edição se concentra em novos recursos relacionados a tipos de mapeamento: - -* A seção <> fala da sintaxe aperfeiçoada de desempacotamento e de diferentes maneiras de mesclar mapeamentos—incluindo os operadores `|` e `|=`, suportados pelos `dicts` desde o Python 3.9. -* A seção <> ilustra o manuseio de mapeamentos com `match/case`, recurso que surgiu no Python 3.10. -* A seção <> agora se concentra nas pequenas mas ainda relevantes diferenças entre `dict` e `OrderedDict`—levando em conta que, desde o Python 3.6, `dict` passou a manter a ordem de inserção das chaves. -* Novas seções sobre os objetos view devolvidos por `dict.keys`, `dict.items`, e `dict.values`: a <> e a <>. - -A implementação interna de `dict` e `set` ainda está alicerçada em tabelas de hash, -mas o código de `dict` teve duas otimizações importantes, que economizam memória e preservam o ordem de inserção das chaves. -As seções <> e <> resumem o que você precisa saber sobre isso para usar bem as estruturas efetadas. - -[NOTE] -==== -Após((("dictionaries and sets", "internals of"))) acrescentar mais de 200 páginas a essa segunda edição, transferi a seção opcional -https://fpy.li/hashint["Internals of sets and dicts" (_As entranhas dos sets e dos dicts_)] (EN) -para o pass:[fluentpython.com], o site que complementa o livro. -O https://fpy.li/hashint[post de 18 páginas] (EN) foi atualizado e expandido, e inclui explicações e diagramas sobre: - -* O algoritmo de tabela de hash e as estruturas de dados, começando por seu uso em `set`, que é mais simples de entender. -* A otimização de memória que preserva a ordem de inserção de chaves em instâncias de `dict` (desde o Python 3.6) . -* O layout do compartilhamento de chaves em dicionários que mantêm atributos de -instância—o `+__dict__+` de objetos definidos pelo usuário (otimização implementada no Python 3.3). -==== - - -[[modern_dict_syntax_sec]] -=== A sintaxe moderna dos dicts - -As((("dictionaries and sets", "modern dict syntax", id="DASsyntax03"))) próximas seções descrevem os recursos avançados de sintaxe para criação, desempacotamento e processamento de mapeamentos. Alguns desses recursos não são novos na linguagem, mas podem ser novidade para você. Outros requerem Python 3.9 (como o operador `|`) ou Python 3.10 (como `match/case`). -Vamos começar por um dos melhores e mais antigos desses recursos. - -[[dictcomp_sec]] -==== Compreensões de dict - -Desde((("dictcomps (dict comprehensions)"))) o Python 2.7, a sintaxe das listcomps e genexps foi adaptada para compreensões de `dict` (e também compreensões de `set`, que veremos em breve). Uma _dictcomp_ (compreensão de dict) cria uma instância de `dict`, recebendo pares `key:value` de qualquer iterável. O <> mostra o uso de compreensões de `dict` para criar dois dicionários a partir de uma mesma lista de tuplas. - -[[example3-1]] -.Exemplos de compreensões de `dict` -==== -[source, pycon] ----- ->>> dial_codes = [ # <1> -... (880, 'Bangladesh'), -... (55, 'Brazil'), -... (86, 'China'), -... (91, 'India'), -... (62, 'Indonesia'), -... (81, 'Japan'), -... (234, 'Nigeria'), -... (92, 'Pakistan'), -... (7, 'Russia'), -... (1, 'United States'), -... ] ->>> country_dial = {country: code for code, country in dial_codes} # <2> ->>> country_dial -{'Bangladesh': 880, 'Brazil': 55, 'China': 86, 'India': 91, 'Indonesia': 62, -'Japan': 81, 'Nigeria': 234, 'Pakistan': 92, 'Russia': 7, 'United States': 1} ->>> {code: country.upper() # <3> -... for country, code in sorted(country_dial.items()) -... if code < 70} -{55: 'BRAZIL', 62: 'INDONESIA', 7: 'RUSSIA', 1: 'UNITED STATES'} ----- -==== -<1> Um iterável de pares chave-valor como `dial_codes` pode ser passado diretamente para o construtor de `dict`, mas... -<2> ...aqui permutamos os pares: `country` é a chave, e `code` é o valor. -<3> Ordenando `country_dial` por nome, revertendo novamente os pares, colocando os valores em maiúsculas e filtrando os itens com `code < 70`. - -Se você já está acostumada com as listcomps, as dictcomps são um próximo passo natural. Caso contrário, a propagação da sintaxe de compreensão mostra que agora é mais valioso que nunca se tornar fluente nessa técnica. - - -[[dict_unpacking_sec]] -[role="pagebreak-before less_space"] -==== Desempacotando mapeamentos - -A https://fpy.li/pep448[PEP 448—Additional Unpacking Generalizations (_Generalizações de Desempacotamento Adicionais_)] melhorou o suporte ao desempacotamento de mapeamentos((("unpacking", "mapping unpackings")))((("mappings", "unpacking"))) de duas formas, desde o Python 3.5. - -Primeiro, podemos((("** (double star) operator")))((("double star (**) operator"))) aplicar `**` a mais de um argumento em uma chamada de função. Isso funciona quando todas as chaves são strings e únicas, para todos os argumentos (porque argumentos nomeados duplicados são proibidos): - -[source, pycon] ----- ->>> def dump(**kwargs): -... return kwargs -... ->>> dump(**{'x': 1}, y=2, **{'z': 3}) -{'x': 1, 'y': 2, 'z': 3} ----- - -Em segundo lugar, `**` pode ser usado dentro de um literal `dict`—também múltiplas vezes: - -[source, pycon] ----- ->>> {'a': 0, **{'x': 1}, 'y': 2, **{'z': 3, 'x': 4}} -{'a': 0, 'x': 4, 'y': 2, 'z': 3} ----- - -Nesse caso, chaves duplicadas são permitidas. Cada ocorrência sobrescreve ocorrências anteriores—observe o valor mapeado para `x` no exemplo. - -Essa sintaxe também pode ser usada para mesclar mapas, mas isso pode ser feito de outras formas. Siga comigo. - -==== Fundindo mapeamentos com | - -Desde a versão 3.9, Python((("mappings", "merging")))((("ǀ (pipe) operator")))((("pipe (ǀ) operator")))((("ǀ= (pipe equals) operator")))((("pipe equals (ǀ=) operator"))) suporta o uso de `|` e `|=` para mesclar mapeamentos. -Isso faz todo sentido, já que estes são também os operadores de união de conjuntos. - -O operador `|` cria um novo mapeamento: - -[source, pycon] ----- ->>> d1 = {'a': 1, 'b': 3} ->>> d2 = {'a': 2, 'b': 4, 'c': 6} ->>> d1 | d2 -{'a': 2, 'b': 4, 'c': 6} ----- - -O tipo do novo mapeamento normalmente será o mesmo do operando da esquerda—no exemplo, `d1`—mas ele pode ser do tipo do segundo operando se tipos definidos pelo usuário estiverem envolvidos na operação, dependendo das regras de sobrecarga de operadores, que exploraremos no <>. - -Para atualizar mapeamentos existentes no mesmo lugar, use `|=`. -Retomando o exemplo anterior, ali `d1` não foi modificado. Mas aqui sim: - -[source, pycon] ----- ->>> d1 -{'a': 1, 'b': 3} ->>> d1 |= d2 ->>> d1 -{'a': 2, 'b': 4, 'c': 6} ----- - -[TIP] -==== -Se você precisa manter código rodando no Python 3.8 ou anterior, a seção -https://fpy.li/3-1["Motivation" (_Motivação_)] (EN) da -https://fpy.li/pep584[PEP 584—Add Union Operators To dict (_Acrescentar Operadores de União a dict_)] (EN) inclui um bom resumo das outras formas de mesclar mapeamentos. -==== - -Agora vamos ver como o pattern matching se aplica aos mapeamentos.((("", startref="DASsyntax03"))) - -[[pattern_matching_mappings_sec]] -=== Pattern matching com mapeamentos - -A((("pattern matching", "with mappings", secondary-sortas="mappings", id="PMmap03")))((("mappings", "pattern matching with", id="Mpattern03")))((("match/case statement")))((("dictionaries and sets", "pattern matching with mappings", id="DASmap03"))) instrução `match/case` suporta sujeitos que sejam objetos mapeamento. -Padrões para mapeamentos se parecem com literais `dict`, mas podem casar com instâncias de qualquer subclasse real ou virtual de `collections.abc.Mapping`.footnote:[Uma subclasse virtual é qualquer classe registrada com uma chamada ao método `.register()` de uma ABC, como explicado na seção <>. Um tipo implementado através da API Python/C também é aceitável, se um bit de marcação específico estiver acionado. Veja https://docs.python.org/pt-br/3.10/c-api/typeobj.html#Py_TPFLAGS_MAPPING[`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 processar registros estruturados como sequências e mapeamentos aninhados, que frequentemente precisamos ler de APIs JSON ou bancos de dados com schemas semi-estruturados, como o MongoDB, o EdgeDB, ou o PostgreSQL. -O <> demonstra isso. - -As dicas de tipo simples em `get_creators` tornam claro que ela recebe um `dict` e devolve uma `list`. - -[[dict_match_ex]] -.creator.py: `get_creators()` extrai o nome dos criadores em registros de mídia -==== -[source, py] ----- -include::code/03-dict-set/py3.10/creator.py[tags=DICT_MATCH] ----- -==== -[role="pagebreak-before less_space"] -<1> Casa com qualquer mapeamento na forma `'type': 'book', 'api' :2`, e uma chave `'authors'` mapeada para uma sequência. Devolve os itens da sequência, como uma nova `list`. -<2> Casa com qualquer mapeamento na forma `'type': 'book', 'api' :1`, e uma chave `'author'` mapeada para qualquer objeto. Devolve aquele objeto dentro de uma `list`. -<3> Qualquer outro mapeamento na forma `'type': 'book'` é inválido e gera um `ValueError`. -<4> Casa qualquer mapeamento na forma `'type': 'movie'` e uma chave `'director'` mapeada para um único objeto. Devolve o objeto dentro de uma `list`. -<5> Qualquer outro sujeito é inválido e gera um `ValueError`. - -O <> mostra algumas práticas úteis para lidar com dados semi-estruturados, tais como registros JSON: - -* Incluir um campo descrevendo o tipo de registro (por exemplo, `'type': 'movie'`) -* Incluir um campo identificando a versão do schema (por exemplo, `'api': 2'`), para permitir evoluções futuras das APIs públicas. -* Ter cláusulas `case` para processar registros inválidos de um tipo específico (por exemplo, `'book'`), bem como um `case` final para capturar tudo que tenha passado pelas condições anteriores. - -Agora vamos ver como `get_creators` se comporta com alguns doctests concretos: - -[source, pycon] ----- -include::code/03-dict-set/py3.10/creator.py[tags=DICT_MATCH_TEST] ----- - -Observe que a ordem das chaves nos padrões é irrelevante, mesmo se o sujeito for um `OrderedDict` como `b2`. - -Diferente de patterns de sequência, patterns de mapeamento funcionam com matches parciais. -Nos doctests, os sujeitos `b1` e `b2` incluem uma chave `'title'`, que não aparece em nenhum padrão `'book'`, mas mesmo assim casam. - -Não há necessidade de usar `+**extra+` para casar pares chave-valor adicionais, mas se você quiser capturá-los como um `dict`, pode prefixar uma variável com `**`. -Ela precisa ser a última do padrão, e `+**_+` é proibido, pois seria redundante. -Um exemplo simples: - - -[source, pycon] ----- ->>> food = dict(category='ice cream', flavor='vanilla', cost=199) ->>> match food: -... case {'category': 'ice cream', **details}: -... print(f'Ice cream details: {details}') -... -Ice cream details: {'flavor': 'vanilla', 'cost': 199} ----- - -Na seção <>, 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`. - -[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 `sentinel` default é um marcador com valor especial, que não pode aparecer nos dados do usuário. -==== - -Vistas a sintaxe e a estrutura, vamos estudar a API dos mapeamentos.((("", startref="PMmap03")))((("", startref="Mpattern03")))((("", startref="DASmap03"))) - - -=== A API padrão dos tipos de mapeamentos - -O((("dictionaries and sets", "standard API of mapping types", id="DASapi03")))((("mappings", "standard API of mapping types", id="Mapi03")))((("collections.abc module", "Mapping and MutableMapping ABCs")))((("MutableMapping ABC"))) módulo `collections.abc` contém as ABCs `Mapping` e `MutableMapping`, descrevendo as interfaces de `dict` e de tipos similares. Veja a <>.((("UML class diagrams", "simplified for MutableMapping and superclasses"))) - -A maior utilidade dessas ABCs é documentar e formalizar as interfaces padrão para os mapeamentos, e servir e critério para testes com `isinstance` em código que precise suportar mapeamentos de forma geral: - -[source, pycon] ----- ->>> my_dict = {} ->>> isinstance(my_dict, abc.Mapping) -True ->>> isinstance(my_dict, abc.MutableMapping) -True ----- - -[TIP] -==== -Usar `isinstance` com uma ABC é muitas vezes melhor que verificar se um argumento de função é do tipo concreto `dict`, porque daí tipos alternativos de mapeamentos podem ser usados. Vamos discutir isso em detalhes no <>. -==== - -[[mapping_uml]] -.Diagrama de classe simplificado para `MutableMapping` e suas superclasses de `collections.abc` (as setas de herança apontam das subclasses para as superclasses; nomes em itálico indicam classes e métodos abstratos -image::images/flpy_0301.png[Diagrama de classes UML para `Mapping` e `MutableMapping`] - -Para implementar uma mapeamento personalizado, é mais fácil estender `collections.UserDict`, ou envolver um `dict` por composição, ao invés de criar uma subclasse dessas ABCs. -A classe `collections.UserDict` e todas as classes concretas de mapeamentos da biblioteca padrão encapsulam o `dict` básico em suas implementações, que por sua vez é criado sobre uma tabela de hash. Assim, todas elas compartilham a mesma limitação, as((("keys", "hashability"))) chaves precisam ser _hashable_ (os valores não precisam ser hashable, só as chaves). -Se você precisa de uma recapitulação, a próxima seção explica isso. - -[[what_is_hashable]] -==== O que é hashable? - -Aqui((("hashable, definition of"))) está parte da definição de hashable, adaptado do https://docs.python.org/pt-br/3/glossary.html#term-hashable[_Glossário_ do Python]: - -[quote] -____ -Um objeto é hashable se tem um código de hash que nunca muda durante seu ciclo de vida (precisa ter um método __hash__()) e pode ser comparado com outros objetos (precisa ter um método __eq__()). Objetos hashable que são comparados como iguais devem ter o mesmo código de hash.footnote:[O verbete para "hashable" no https://docs.python.org/pt-br/3/glossary.html#term-hashable[_Glossário_ do Python] usa o termo "valor de hash" em vez de((("hash code, versus hash value"))) _código de hash_. Prefiro _código de hash_ porque "valor" é um conceito frequentemente usado no contexto de mapeamentos, onde itens são compostos de chavas e valores. Então pode ser confuso se referir ao código de hash como um valor. Nesse livro usarei apenas _código de hash_.] -____ - -Tipos numéricos((("numeric types", "hashability of"))) e os tipos planos imutáveis `str` e `bytes` são todos hashable. -Tipos contêineres são hashable se forem imutáveis e se todos os objetos por eles contidos forem também hashable. -Um `frozenset` é sempre hashable, pois todos os elementos que ele contém devem ser, por definição, hashable. -Uma `tuple` é hashable apenas se todos os seus itens também forem. Observe as tuplas `tt`, `tl`, and `tf`: - -[source, pycon] ----- ->>> tt = (1, 2, (30, 40)) ->>> hash(tt) -8027212646858338501 ->>> tl = (1, 2, [30, 40]) ->>> hash(tl) -Traceback (most recent call last): - File "", line 1, in -TypeError: unhashable type: 'list' ->>> tf = (1, 2, frozenset([30, 40])) ->>> hash(tf) --4118419923444501110 ----- - -O código de hash de um objeto pode ser diferente dependendo da versão do Python, da arquitetura da máquina, e pelo((("salts"))) _sal_ acrescentado ao cálculo do hash por razões de segurança.footnote:[Veja a https://fpy.li/pep456[PEP 456—Secure and interchangeable hash algorithm (Algoritmo de hash seguro e intercambiável_)] (EN) para saber mais sobre as implicações de segurança e as soluções adotadas.] O código de hash de um objeto corretamente implementado tem a garantia de ser constante apenas dentro de um processo Python. - -Tipos definidos pelo usuário são hashble por default, pois seu código de hash é seu `id()`, e o método `+__eq__()+` herdado da classe `objetct` apenas compara os IDs dos objetos. Se um objeto implementar seu próprio `+__eq__()+`, que leve em consideração seu estado interno, ele será hashable apenas se seu `+__hash__()+` sempre devolver o mesmo código de hash. Na prática, isso exige que `+__eq__()+` e `+__hash__()+` levem em conta apenas atributos de instância que nunca mudem durante a vida do objeto. - - -Vamos agora revisar a API dos tipos de mapeamento mais comumente usado no Python: `dict`, `defaultdict`, e `OrderedDict`. - - -==== Revisão dos métodos mais comuns dos mapeamentos - -A((("defaultdict")))((("OrderedDict")))((("collections.abc module", "defaultdict and OrderedDict"))) API básica para mapeamentos é muito variada. A <> mostra os métodos implementados por `dict` e por duas variantes populares: `defaultdict` e `OrderedDict`, ambas classes definidas no módulo `collections`. - -[role="pagebreak-before less_space"] -[[mapping_methods_tbl]] -.Métodos do tipos de mapeamento `dict`, `collections.defaultdict`, e `collections.OrderedDict` (métodos comuns de `object` omitidos por concisão); argumentos opcionais então entre `[…]` -[options="header"] -|=========================================================================================================================================================================================================================================================================== -| |dict |defaultdict |OrderedDict |   -| `d.clear()` | ● | ● | ● | Remove todos os itens. -| `+d.__contains__(k)+` | ● | ● | ● | `k in d`. -| `d.copy()` | ● | ● | ● | Cópia rasa. -| `+d.__copy__()+` | | ● | | Suporte a `copy.copy(d)`. -| `d.default_factory` | | ● | | Chamável invocado por `+__missing__+` para definir valores ausentes.footnote:[`default_factory` não é um método, mas um atributo chamável definido pelo usuário quando um `defaultdict` é instanciado.] -| `+d.__delitem__(k)+` | ● | ● | ● | `del d[k]`—remove item com chave `k` -| `d.fromkeys(it, [initial])` | ● | ● | ● | Novo mapeamento com chaves no iterável `it`, com um valor inicial opcional (o default é `None`). -| `d.get(k, [default])` | ● | ● | ● | Obtém item com chave `k`, devolve `default` ou `None` se `k` não existir. -| `+d.__getitem__(k)+` | ● | ● | ● | ++d[k]++—obtém item com chave `k`. -| `d.items()` | ● | ● | ● | Obtém uma _view_ dos itens—pares `(chave, valor)`. -| `+d.__iter__()+` | ● | ● | ● | Obtém iterador das chaves. -| `d.keys()` | ● | ● | ● | Obtém _view_ das chaves. -| `+d.__len__()+` | ● | ● | ● | `len(d)`—número de itens. -| `+d.__missing__(k)+` | | ● | | Chamado quando `+__getitem__+` não consegue encontrar a chave. -| `d.move_to_end(k, [last])` | | | ● | Move `k` para primeira ou última posição (`last` é `True` por default). -| `+d.__or__(other)+` | ● | ● | ● | Suporte a `d1 \| d2` para criar um novo \`dict`, fundindo `d1` e `d2` (Python ≥ 3.9). -| `+d.__ior__(other)+` | ● | ● | ● | Suporte a `d1 \|= d2` para atualizar `d1` com `d2` (Python ≥ 3.9). -| `d.pop(k, [default])` | ● | ● | ● | Remove e devolve valor em `k`, ou `default` ou `None`, se `k` não existir. -| `d.popitem()` | ● | ● | ● | Remove e devolve, na forma `(chave, valor)`, o último item inserido.footnote:[`OrderedDict.popitem(last=False)` remove o primeiro item inserido (FIFO). O argumento nomeado `last` não é suportado por `dict` ou `defaultdict`, pelo menos até o Python 3.10b3.] -| `+d.__reversed__()+` | ● | ● | ● | Suporte a `reverse(d)`—devolve um iterador de chaves, da última para a primeira a serem inseridas. -| `+d.__ror__(other)+` | ● | ● | ● | Suporte a `other \| dd`—operador de união invertido (Python ≥ 3.9)footnote:[Operadores invertidos são tratados no <>.] -|`d.setdefault(k, [default])` | ● | ● | ● | Se `k in d`, devolve `d[k]`; senão, atribui `d[k] = default` e devolve isso. -| `+d.__setitem__(k, v)+` | ● | ● | ● | `d[k] = v`—coloca `v` em `k` -| `d.update(m, [**kwargs])` | ● | ● | ● | Atualiza `d` com itens de um mapeamento ou iterável de pares `(chave, valor)`. -| `d.values()` | ● | ● | ● | Obtém uma _view_ dos valores. -|=========================================================================================================================================================================================================================================================================== - -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, 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)`. -O construtor da maioria dos mapeamentos do Python usa internamente a lógica de `update()`, -o que quer dizer que eles podem ser inicializados por outros mapeamentos ou a partir de qualquer objeto iterável que produza pares `(chave, valor)`. - -Um método sutil dos mapeamentos é `setdefault()`. -Ele evita buscas redundantes de chaves quando precisamos atualizar o valor em um item no mesmo lugar. -A próxima seção mostra como ele pode ser usado. - -==== Inserindo ou atualizando valores mutáveis - -Alinhada((("fail-fast philosophy")))((("defensive programming")))((("mutable values, inserting or updating", id="MVinsert03"))) à filosofia de _falhar rápido_ do Python, a consulta a um `dict` com `d[k]` gera um erro quando `k` não é uma chave existente. Pythonistas sabem que `d.get(k, default)` é uma alternativa a `d[k]` sempre que receber um valor default é mais conveniente que tratar um `KeyError`. Entretanto, se você está buscando um valor mutável e quer atualizá-lo, há um jeito melhor. - -Considere um script para indexar texto, produzindo um mapeamento no qual cada chave é uma palavra, e o valor é uma lista das posições onde aquela palavra ocorre, como mostrado no <>. - -[[index0_output_ex]] -.Saída parcial do <> processando o texto "Zen of Python"; cada linha mostra uma palavra e uma lista de ocorrências na forma de pares (`line_number`, `column_number`) (_número da linha_, _número da coluna) -==== -[source, bash] ----- -$ python3 index0.py zen.txt -a [(19, 48), (20, 53)] -Although [(11, 1), (16, 1), (18, 1)] -ambiguity [(14, 16)] -and [(15, 23)] -are [(21, 12)] -aren [(10, 15)] -at [(16, 38)] -bad [(19, 50)] -be [(15, 14), (16, 27), (20, 50)] -beats [(11, 23)] -Beautiful [(3, 1)] -better [(3, 14), (4, 13), (5, 11), (6, 12), (7, 9), (8, 11), (17, 8), (18, 25)] -... ----- -==== - -O <> é um script aquém do ideal, para mostrar um caso onde `dict.get` não é a melhor maneira de lidar com uma chave ausente. -Ele foi adaptado de um exemplo de Alex Martelli.footnote:[O script original aparece no slide 41 da apresentação de Martelli, https://fpy.li/3-5["Re-learning Python" (_Reaprendendo Python_)] (EN). -O script é, na verdade, uma demonstração de `dict.setdefault`, como visto no nosso <>.] - -[[index0_ex]] -.index0.py usa `dict.get` para obter e atualizar uma lista de ocorrências de palavras de um índice (uma solução melhor é apresentada no <>) -==== -[source, py] ----- -include::code/03-dict-set/index0.py[tags=INDEX0] ----- -==== -<1> Obtém a lista de ocorrências de `word`, ou `[]` se a palavra não for encontrada. -<2> Acrescenta uma nova localização a `occurrences`. -<3> Coloca a `occurrences` modificada no dict `index`; isso exige uma segunda busca em `index`. -<4> Não estou chamando `str.upper` no argumento `key=` de `sorted`, apenas passando uma referência àquele método, para que a função `sorted` possa usá-lo para normalizar as palavras antes de ordená-las.footnote:[Isso é um exemplo do uso de um método como uma função de primeria classe, o assunto do <>.] - -As três linhas tratando de `occurrences` no <> podem ser substituídas por uma única linha usando `dict.setdefault`. O <> fica mais próximo do código apresentado por Alex Martelli. - -[[index_ex]] -.index.py usa `dict.setdefault` para obter e atualizar uma lista de ocorrências de uma palavra em uma única linha de código; compare com o <> -==== -[source, py] ----- -include::code/03-dict-set/index.py[tags=INDEX] ----- -==== -<1> Obtém a lista de ocorrências de `word`, ou a define como `[]`, se não for encontrada; `setdefault` devolve o valor, então ele pode ser atualizado sem uma segunda busca. - -Em outras palavras, o resultado final desta linha... - -[source, python3] ----- -my_dict.setdefault(key, []).append(new_value) ----- - -...é o mesmo que executar... - -[source, python3] ----- -if key not in my_dict: - my_dict[key] = [] -my_dict[key].append(new_value) ----- - -...exceto que este último trecho de código executa pelo menos duas buscas por `key`—três se a chave não for encontrada—enquanto `setdefault` faz tudo isso com uma única busca. - -Uma questão relacionada, o tratamento de chaves ausentes em qualquer busca (e não apenas para inserção de valores), é o assunto da próxima seção.((("", startref="MVinsert03")))((("", startref="Mapi03")))((("", startref="DASapi03"))) - -[[mappings_flexible_sec]] -=== Tratamento automático de chaves ausentes - -Algumas vezes((("dictionaries and sets", "automatic handling of missing keys", id="DASmissing03")))((("keys", "automatic handling of missing", id="Kauto03")))((("mappings", "automatic handling of missing keys", id="Mauto03"))) é conveniente que os mapeamentos devolvam algum valor padronizado quando se busca por uma chave ausente. Há duas abordagem principais para esse fim: uma é usar um `defaultdict` em vez de um `dict` simples. A outra é criar uma subclasse de `dict` ou de qualquer outro tipo de mapeamento e acrescentar um método `+__missing__+`. Vamos ver as duas soluções a seguir. - -[[defaultdict_sec]] -==== defaultdict: outra perspectiva sobre as chaves ausentes - -Uma instância de `collections.defaultdict`((("defaultdict"))) cria itens com um valor default sob demanda, sempre que uma chave ausente é buscada usando a sintaxe `d[k]`. -O <> usa `defaultdict` para fornecer outra solução elegante para o índice de palavras do <>. - -Funciona assim: ao instanciar um `defaultdict`, você fornece um chamável que produz um valor default sempre que `+__getitem__+` recebe uma chave inexistente como argumento. - -Por exemplo, dado um `defaultdict` criado por `dd = defaultdict(list)`, se `'new-key'` não estiver em `dd`, a expressão `dd['new-key']` segue os seguintes passos: - -. Chama +list()+ para criar uma nova lista. -. Insere a lista em `dd` usando `'new-key'` como chave. -. Devolve uma referência para aquela lista. - -O chamável que produz os valores default é mantido em um atributo de instância chamado `default_factory`. - -[[index_default_ex]] -.index_default.py: usando um `defaultdict` em vez do método `setdefault` -==== -[source, py] ----- -include::code/03-dict-set/index_default.py[tags=INDEX_DEFAULT] ----- -==== -<1> Cria um `defaultdict` com o construtor de `list` como `default_factory`. -<2> Se `word` não está inicialmente no `index`, o `default_factory` é chamado para produzir o valor ausente, que neste caso é uma `list` vazia, que então é atribuída a `index[word]` e devolvida, de forma que a operação -`.append(location)` é sempre bem sucedida. - -Se nenhum `default_factory` é fornecido, o `KeyError` usual é gerado para chaves ausente. - -[WARNING] -==== -O `default_factory` de um `defaultdict` só é invocado para fornecer valores default para chamadas a `+__getitem__+`, não para outros métodos. Por exemplo, se `dd` é um `defaultdict` e `k` uma chave ausente, `dd[k]` chamará `default_factory` para criar um valor default, -mas `dd.get(k)` vai devolver `None` e `k in dd` é `False`. -==== - -O mecanismo que faz `defaultdict` funcionar, chamando `default_factory`, é o método especial `+__missing__+`, um recurso discutido a seguir. - - -[[missing_method]] -==== O método +__missing__+ - -Por((("__missing__", id="missing03"))) trás da forma como os mapeamentos lidam com chaves ausentes está o método muito apropriadamente chamado `+__missing__+`.footnote:[NT: "Missing" significa ausente, perdido ou desaparecido]. Esse método não é definido na classe base `dict`, mas `dict` está ciente de sua possibilidade: se você criar uma subclasse de `dict` e incluir um método `+__missing__+`, o `+dict.__getitem__+` padrão vai chamar seu método sempre que uma chave não for encontrada, em vez de gerar um `KeyError`. - -Suponha que você queira um mapeamento onde as chaves são convertidas para `str` quando são procuradas. Um caso de uso concreto seria uma biblioteca para dispositivos IoT (Internet of Things, _Internet das Coisas_)footnote:[Uma biblioteca dessas é a https://fpy.li/3-6[_Pingo.io_], que não está mais em desenvolvimento ativo.], onde uma placa programável com portas genéricas programáveis (por exemplo, uma Raspberry Pi ou uma Arduino) é representada por uma classe "Placa" com um atributo `minha_placa.portas`, que é uma mapeamento dos identificadores das portas físicas para objetos de software portas. O identificador da porta física pode ser um número ou uma string como `"A0"` ou `"P9_12"`. Por consistência, é desejável que todas as chaves em `placa.portas` seja strings, mas também é conveniente buscar uma porta por número, como em `meu-arduino.porta[13]`, para evitar que iniciantes tropecem quando quiserem fazer piscar o LED na porta 13 de seus Arduinos. O <> mostra como tal mapeamento funcionaria. - -[[ex_strkeydict0_tests]] -.Ao buscar por uma chave não-string, `StrKeyDict0` a converte para `str` quando ela não é encontrada -==== -[source, py] ----- -include::code/03-dict-set/strkeydict0.py[tags=STRKEYDICT0_TESTS] ----- -==== - -O <> implementa a classe `StrKeyDict0`, que passa nos doctests acima. - -[TIP] -==== -Uma forma melhor de criar uma mapeamento definido pelo usuário é criar uma subclasse de `collections.UserDict` em vez de `dict` (como faremos no <>). Aqui criamos uma sibclasse de `dict` apenas para mostrar que `+__missing__+` é suportado pelo método embutido `+dict.__getitem__+`. -==== - -[[ex_strkeydict0]] -.`StrKeyDict0` converte chaves não-string para string no momento da consulta (vejas os testes no <>) -==== -[source, py] ----- -include::code/03-dict-set/strkeydict0.py[tags=STRKEYDICT0] ----- -==== -<1> `StrKeyDict0` herda de `dict`. -<2> Verifica se `key` já é uma `str`. Se é, e está ausente, gera um `KeyError`. -<3> Cria uma `str` de `key` e a procura. -<4> O método `get` delega para `+__getitem__+` usando a notação `self[key]`; isso dá oportunidade para nosso `+__missing__+` agir. -<5> Se um `KeyError` foi gerado, `+__missing__+` já falhou, então devolvemos o `default`. -<6> Procura pela chave não-modificada (a instância pode conter chaves não-`str`), depois por uma `str` criada a partir da chave. - -Considere por um momento o motivo do teste `isinstance(key, str)` ser necessário na implementação de `+__missing__+`. - -Sem aquele teste, nosso método `+__missing__+` funcionaria bem com qualquer chave `k`—`str` ou não—sempre que `str(k)` produzisse uma chave existente. Mas se `str(k)` não for uma chave existente, teríamos uma recursão infinita. -Na última linha de `+__missing__+`, `self[str(key)]` chamaria `+__getitem__+`, passando aquela chave `str`, e `+__getitem__+`, por sua vez, chamaria -`+__missing__+` novamente. - -O((("__contains__"))) método `+__contains__+` também é necessário para que o comportamento nesse exemplo seja consistente, pois a operação `k in d` o chama, mas o método -herdado de `dict` não invoca `+__missing__+` com chaves ausentes. Há um detalhe sutil em nossa implementação de `+__contains__+`: não verificamos a existência da chave da forma pythônica normal—`k in d`—porque `str(key) in self` chamaria -`+__contains__+` recursivamente. Evitamos isso procurando a chave explicitamente em `self.keys()`. - -Uma busca como `k in my_dict.keys()` é eficiente em Python 3 mesmo para mapeamentos muito grandes, porque `dict.keys()` devolve uma view, que é similar a um _set_, como veremos na seção <>. -Entretanto, lembre-se que `k in my_dict` faz o mesmo trabalho, e é mais rápido porque evita a busca nos atributos para encontrar o método `.keys`. - -Eu tinha uma razão específica para usar `self.keys()` no método `+__contains__+` do -<>. -A verificação da chave não-modificada `++key in self.keys()++` é necessária por correção, pois `StrKeyDict0` não obriga todas as chaves no dicionário a serem do tipo `str`. -Nosso único objetivo com esse exemplo simples foi fazer a busca "mais amigável", e não forçar tipos. - -[WARNING] -==== -Classes definidas pelo usuário derivadas de mapeamentos da biblioteca padrão podem ou não usar -`+__missing__+` como alternativa em sua implementação de `+__getitem__+`, `get`, ou `+__contains__+`, -como explicado na próxima seção. -==== - -[[inconsistent_missing]] -==== O uso inconsistente de pass:[__missing__] na biblioteca padrão - -Considere os seguintes cenários, e como eles afetam a busca de chaves ausentes: - -subclasse de `dict`:: - Uma subclasse de `dict` que implemente apenas `+__missing__+` e nenhum outro método. - Nesse caso, `+__missing__+` pode ser chamado apenas em `d[k]`, que usará o `+__getitem__+` herdado de `dict`. - -subclasse de `collections.UserDict`:: - Da mesma forma, uma subclasse de `UserDict` que implemente apenas `+__missing__+` e nenhum outro método. - O método `get` herdado de `UserDict` chama `+__getitem__+`. - Isso significa que `+__missing__+` pode ser chamado para tratar de consultas com `d[k]` e com - `d.get(k)`. - -subclasse de `abc.Mapping` com o `+__getitem__+` mais simples possível:: - Uma subclasse mínima de `abc.Mapping`, implementando `+__missing__+` e os métodos abstratos obrigatórios, - incluindo uma implementação de `+__getitem__+` que não chama `+__missing__+`. - O método `+__missing__+` nunca é acionado nessa classe. - -subclasse de `abc.Mapping` com `+__getitem__+` chamando `+__missing__+`:: - Uma subclasse mínima de `abc.Mapping`, implementando `+__missing__+` e os métodos abstratos obrigatórios, - incluindo uma implementação de `+__getitem__+` que chama `+__missing__+`. - O método `+__missing__+` é acionado nessa classe para consultas por chaves ausentes feitas com - `d[k]`, `d.get(k)`, e `k in d`. - -Veja -https://fpy.li/3-7[_missing.py_] -no repositório de exemplos de código para demonstrações dos cenários descritos acima. - -Os quatro cenários que acabo de descrever supõe implementações mínimas. -Se a sua subclasse implementa `+__getitem__+`, `get`, e `+__contains__+`, -então você pode ou não fazer tais métodos usarem `+__missing__+`, dependendo de suas necessidades. -O ponto aqui é mostrar que é preciso ter cuidado ao criar subclasses dos mapeamentos da biblioteca padrão para usar `+__missing__+`, porque as classes base suportam comportamentos default diferentes. -Não se esqueça que o comportamento de `setdefault` e `update` também é afetado pela consulta de chaves. -E por fim, dependendo da lógica de seu `+__missing__+`, -pode ser necessário implementar uma lógica especial em `+__setitem__+`, -para evitar inconsistências ou comportamentos surpreeendentes. -Veremos um exemplo disso na seção <>. - -Até aqui tratamos dos tipos de mapeamentos `dict` e `defaultdict`, mas a biblioteca padrão traz outras implementações de mapeamentos, que discutiremos a seguir.((("", startref="missing03")))((("", startref="Mauto03")))((("", startref="Kauto03")))((("", startref="DASmissing03"))) - -=== Variações de dict - -Nessa((("dictionaries and sets", "variations of dict", id="DASvardict03"))) seção falaremos brevemente sobre os tipos de mapeamentos incluídos na biblioteca padrão diferentes de `defaultdict`, já visto na seção <>. - -[[ordereddict_sec]] -==== collections.OrderedDict - -Agora((("collections.abc module", "defaultdict and OrderedDict")))((("OrderedDict"))) que o `dict` embutido também mantém as chaves ordenadas (desde o Python 3.6), o motivo mais comum para usar `OrderedDict` é escrever código compatível com versões anteriores do Python. -Dito isso, a documentação lista algumas diferenças entre `dict` e `OrderedDict` que ainda persistem e que cito aqui—apenas reordenando os itens conforme sua relevância no uso diário: - -* A operação de igualdade para `OrderedDict` verifica a igualdade da ordenação. -* O método `popitem()` de `OrderedDict` tem uma assinatura diferente, que aceita um argumento opcional especificando qual item será devolvido. -* `OrderedDict` tem um método `move_to_end()`, que reposiciona de um elemento para uma ponta do dicionário de forma eficiente. -* O `dict` comum foi projetado para ser muito bom nas operações de mapeamento. Monitorar a ordem de inserção era uma preocupação secundária. -* `OrderedDict` foi projetado para ser bom em operações de reordenamento. Eficiência espacial, velocidade de iteração e o desempenho de operações de atualização eram preocupações secundárias. -* Em termos do algoritmo, um `OrderedDict` lida melhor que um dict com operações frequentes de reordenamento. Isso o torna adequado para monitorar acessos recentes (em um cache LRUfootnote:[NT: Least Recently Used, _Menos Recentemente Usado_, esquema de cache que descarta o item armazenado que esteja há mais tempo sem requisições], por exemplo). - - -[[chainmap_sec]] -==== collections.ChainMap - -Uma((("collections.abc module", "ChainMap")))((("ChainMap"))) instância de `ChainMap` mantém uma lista de mapeamentos que podem ser consultados como se fossem um mapeamento único. -A busca é realizada em cada mapa incluído, na ordem em que eles aparecem na chamada ao construtor, -e é bem sucedida assim que a chave é encontrada em um daqueles mapeamentos. -Por exemplo: - -[source, pycon] ----- ->>> d1 = dict(a=1, b=3) ->>> d2 = dict(a=2, b=4, c=6) ->>> from collections import ChainMap ->>> chain = ChainMap(d1, d2) ->>> chain['a'] -1 ->>> chain['c'] -6 ----- - -A instância de `ChainMap` não cria cópias dos mapeamentos, mantém referências para eles. -Atualizações ou inserções a um `ChainMap` afetam apenas o primeiro mapeamento passado. -Continuando do exemplo anterior: - -[source, pycon] ----- ->>> chain['c'] = -1 ->>> d1 -{'a': 1, 'b': 3, 'c': -1} ->>> d2 -{'a': 2, 'b': 4, 'c': 6} ----- - -Um `ChainMap` é útil na implementação de linguagens com escopos aninhados, -onde cada mapeamento representa um contexto de escopo, -desde o escopo aninhado mais interno até o mais externo. A seção -https://docs.python.org/pt-br/3/library/collections.html#collections.ChainMap["Objetos ChainMap"], na documentação de `collections`, apresenta vários exemplos do uso de `Chainmap`, -incluindo esse trecho inspirado nas regras básicas de consulta de variáveis no Python: - -[source, python3] ----- -import builtins -pylookup = ChainMap(locals(), globals(), vars(builtins)) ----- - -O <> mostra uma subclasse de `ChainMap` usada para implementar um interpretador parcial da linguagem de programação Scheme. - - -==== collections.Counter - -Um((("collections.abc module", "Counter")))((("Counter"))) mapeamento que mantém uma contagem inteira para cada chave. -Atualizar uma chave existente adiciona à sua contagem. -Isso pode ser usado para contar instâncias de objetos hashable ou como um _multiset_ ("conjunto múltiplo"), discutido adiante nessa seção. -`Counter` implementa os operadores `+` e `-` para combinar contagens, e outros métodos úteis tal como o `most_common([n])`, que devolve uma lista ordenada de tuplas com os _n_ itens mais comuns e suas contagens; veja a https://docs.python.org/pt-br/3/library/collections.html#collections.Counter[documentação]. - -Aqui temos um `Counter` usado para contar as letras em palavras: - -[source, pycon] ----- ->>> ct = collections.Counter('abracadabra') ->>> ct -Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}) ->>> ct.update('aaaaazzz') ->>> ct -Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1}) ->>> ct.most_common(3) -[('a', 10), ('z', 3), ('b', 2)] ----- - -Observe que as chaves `'b'` e `'r'` estão empatadas em terceiro lugar, mas -`ct.most_common(3)` mostra apenas três contagens. - -Para usar `collections.Counter` como um conjunto múltiplo, trate cada chave como um elemento de um conjunto, e a contagem será o número de ocorrências daquele elemento no conjunto. - - -==== shelve.Shelf - -O((("shelve module")))((("pickle module")))((("keys", "persistent storage for mapping"))) módulo `shelve` na biblioteca padrão fornece armazenamento persistente a um mapeamento de chaves em formato string para objetos Python serializados no formato binário `pickle`. -O nome curioso, `shelve`, faz sentido quando você percebe que potes de `pickle` são armazenadas em prateleiras.footnote:[NT: "_to shelve_" é "_colocar na prateleira_", "_pickle_" também significa "_conserva_" e "pickles" é literalmente _picles_. O trocadilho dos desenvolvedores do Python é sobre colocar `pickles` em `shelves`.] - -A função de módulo `shelve.open` devolve uma instância de `shelve.Shelf`—um banco de dados DBM simples de chave-valor, baseado no módulo `dbm`, com as seguintes características: - -* `shelve.Shelf` é uma subclasse de `abc.MutableMapping`, então fornece os métodos essenciais esperados de um tipo mapeamento. -* Além disso, `shelve.Shelf` fornece alguns outros métodos de gerenciamento de E/S, como `sync` e `close`. -* Uma instância de `Shelf` é um gerenciador de contexto, então é possível usar um bloco `with` para garantir que ela seja fechada após o uso. -* Chaves e valores são salvos sempre que um novo valor é atribuído a uma chave. -* As chaves devem ser strings. -* Os valores devem ser objetos que o módulo `pickle` possa serializar. - -A documentação para os módulos -https://docs.python.org/pt-br/3/library/shelve.html[shelve], -https://docs.python.org/pt-br/3/library/dbm.html[dbm] (EN), e -https://docs.python.org/pt-br/3/library/pickle.html[pickle] -traz mais detalhes e também algumas ressalvas. - -[WARNING] -==== -O `pickle` do Python é fácil de usar nos caso mais simples, mas tem vários inconvenientes. -Leia o https://fpy.li/3-13["Pickle’s nine flaws"], de Ned Batchelder, -antes de adotar qualquer solução envolvendo `pickle`. -Em seu post, Ned menciona outros formatos de serialização que podem ser considerados como alternativas. -==== - -As classes `OrderedDict`, `ChainMap`, `Counter`, e `Shelf` podem ser usadas diretamente, mas também podem ser personalizadas por subclasses. -`UserDict`, por outro lado, foi planejada apenas como uma classe base a ser estendida. - - -[[sublcassing_userdict_sec]] -==== Criando subclasses de UserDict em vez de dict - -É ((("collections.abc module", "UserDict", id="coluserdict03")))((("UserDict", id="userdict03"))) -melhor criar um novo tipo de mapeamento estendendo `collections.UserDict` em vez de `dict`. -Percebemos isso quando tentamos estender nosso `StrKeyDict0` do <> para assegurar que qualquer chave adicionada ao mapeamento seja armazenada como `str`. - -A principal razão pela qual é melhor criar uma subclasse de `UserDict` em vez de `dict` é que o tipo embutido tem alguns atalhos de implementação, que acabam nos obrigando a sobrepor métodos que poderíamos apenas herdar de `UserDict` sem maiores problemas.footnote:[O problema exato de se criar subclasses de `dict` e de outros tipos embutidos é tratado na seção <>.] - -Observe que `UserDict` não herda de `dict`, mas usa uma composição: -a classe tem uma instância interna de `dict`, chamada `data`, que mantém os itens propriamente ditos. Isso evita recursão indesejada quando escrevemos métodos especiais, como `+__setitem__+`, e simplifica a programação de `+__contains__+`, quando comparado com o <>. - -Graças((("keys", "converting nonstring keys to str"))) a `UserDict`, o `StrKeyDict` (<>) é mais conciso que o `StrKeyDict0` (<>), mais ainda faz melhor: ele armazena todas as chaves como `str`, evitando surpresas desagradáveis se a instância for criada ou atualizada com dados contendo chaves de outros tipos (que não string). - -[[ex_strkeydict]] -.`StrKeyDict` sempre converte chaves que não sejam strings para `str` na inserção, atualização e busca -==== -[source, py] ----- -include::code/03-dict-set/strkeydict.py[tags=STRKEYDICT] ----- -==== -<1> `StrKeyDict` estende `UserDict`. -<2> `+__missing__+` é exatamente igual ao do <>. -<3> `+__contains__+` é mais simples: podemos assumir que todas as chaves armazenadas são `str`, e podemos operar sobre `self.data` em vez de invocar `self.keys()`, como fizemos em `StrKeyDict0`. -<4> `+__setitem__+` converte qualquer `key` para uma `str`. Esse método é mais fácil de sobrepor quando podemos delegar para o atributo `self.data`. - -Como `UserDict` estende `abc.MutableMapping`, o restante dos métodos que fazem de `StrKeyDict` uma mapeamento completo são herdados de `UserDict`, `MutableMapping`, ou `Mapping`. Estes últimos contém vários métodos concretos úteis, apesar de serem classes base abstratas (ABCs). Os seguinte métodos são dignos de nota: - -`MutableMapping.update`:: Esse método poderoso pode ser chamado diretamente, mas também é usado por -`+__init__+` para criar a instância a partir de outros mapeamentos, de iteráveis de pares `(chave, valor)`, e de argumentos nomeados. Como usa `self[chave] = valor` para adicionar itens, ele termina por invocar nossa implementação de `+__setitem__+`. - -`Mapping.get`:: No `StrKeyDict0` (<>), precisamos codificar nosso próprio `get` para devolver os mesmos resultados de `+__getitem__+`, mas no <> herdamos `Mapping.get`, que é implementado exatamente como `StrKeyDict0.get` -(consulte o https://fpy.li/3-14[código-fonte do Python]). - -[TIP] -==== -Antoine Pitrou escreveu a https://fpy.li/pep455[PEP 455--Adding a key-transforming dictionary to collections (_Acrescentando um dicionário com transformação de chaves a collections_)] (EN) e um patch para aperfeiçoar o módulo `collections` com uma classe `TransformDict`, que é mais genérico que `StrKeyDict` e preserva as chaves como fornecidas antes de aplicar a transformação. A PEP 455 foi rejeitada em maio de 2015—veja a https://fpy.li/3-15[mensagem de rejeição] (EN) de Raymond Hettinger. Para experimentar com a `TransformDict`, extraí o patch de Pitrou do https://fpy.li/3-16[issue18986] (EN) para um módulo independente (https://fpy.li/3-17[_03-dict-set/transformdict.py_] disponível no https://fpy.li/code[repositório de código da segunda edição do _Fluent Python_]). -==== - -Sabemos que existem tipos de sequências imutáveis, mas e mapeamentos imutáveis? Bem, não há um tipo real desses na biblioteca padrão, mas um substituto está disponível. É o que vem a seguir.((("", startref="userdict03")))((("", startref="userdict03")))((("", startref="DASvardict03"))) - - -=== Mapeamentos imutáveis - -Os((("dictionaries and sets", "immutable mappings")))((("mappings", "immutable mappings")))((("immutable mappings"))) tipos de mapeamentos disponíveis na biblioteca padrão são todos mutáveis, mas pode ser necessário impedir que os usuários mudem um mapeamento por acidente. Um caso de uso concreto pode ser encontrado, novamente, em uma biblioteca de programação de hardware como a((("Pingo library"))) _Pingo_, mencionada na seção <>: -o mapeamento `board.pins` representa as portas de GPIO (General Purpose Input/Output, _Entrada/Saída Genérica_) em um dispositivo. Dessa forma, seria útil evitar atualizações descuidadas de `board.pins`, pois o hardware não pode ser modificado via software: qualquer mudança no mapeamento o tornaria inconsistente com a realidade física do dispositivo. - -O módulo `types` oferece uma classe invólucro (_wrapper_) chamada `MappingProxyType` que, dado um mapeamento, devolve uma instância de `mappingproxy`, que é um proxy somente para leitura (mas dinâmico) do mapeamento original. Isso significa que atualizações ao mapeamento original são refletidas no `mappingproxy`, mas nenhuma mudança pode ser feita através desse último. Veja uma breve demonstração no <>. - -[[ex_MappingProxyType]] -.`MappingProxyType` cria uma instância somente de leitura de `mappingproxy` a partir de um `dict` -==== -[source, pycon] ----- ->>> from types import MappingProxyType ->>> d = {1: 'A'} ->>> d_proxy = MappingProxyType(d) ->>> d_proxy -mappingproxy({1: 'A'}) ->>> d_proxy[1] <1> -'A' ->>> d_proxy[2] = 'x' <2> -Traceback (most recent call last): - File "", line 1, in -TypeError: 'mappingproxy' object does not support item assignment ->>> d[2] = 'B' ->>> d_proxy <3> -mappingproxy({1: 'A', 2: 'B'}) ->>> d_proxy[2] -'B' ->>> ----- -==== -<1> Os items em `d` podem ser vistos através de `d_proxy`. -<2> Não é possível fazer modificações através de `d_proxy`. -<3> `d_proxy` é dinâmica: qualquer mudança em `d` é refletida ali. - - -Isso pode ser usado assim na prática, no cenário da programação de hardware: -o construtor em uma subcalsse concreta `Board` preencheria um mapeamento privado com os objetos porta, e o exporia aos clientes da API via um atributo público `.portas`, implementado como um `mappingproxy`. -Dessa forma os clientes não poderiam acrescentar, remover ou modificar as portas por acidente. - -A seguir veremos _views_—que permitem operações de alto desempenho em um `dict`, sem cópias desnecessárias dos dados. - - -[[dictionary_views_sec]] -[role="pagebreak-before less_space"] -=== Views de dicionários - -Os((("dictionaries and sets", "dictionary views", id="DASviews03"))) métodos de instância de `dict` `.keys()`, `.values()`, e `.items()` devolvem instâncias de classes chamadas `dict_keys`, `dict_values`, e `dict_items`, respectivamente. -Essas views de dicionário são projeções somente para leitura de estruturas de dados internas usadas na implemetação de `dict`. -Elas evitam o uso de memória adicional dos métodos equivalentes no Python 2, que devolviam listas, duplicando dados já presentes no `dict` alvo. E também substituem os métodos antigos que devolviam iteradores. - -O <> mostra algumas operações básicas suportadas por todas as views de dicionários. - -[[ex_dict_values]] -.O método `.values()` devolve uma view dos valores em um `dict` -==== -[source, pycon] ----- ->>> d = dict(a=10, b=20, c=30) ->>> values = d.values() ->>> values -dict_values([10, 20, 30]) <1> ->>> len(values) <2> -3 ->>> list(values) <3> -[10, 20, 30] ->>> reversed(values) <4> - ->>> values[0] <5> -Traceback (most recent call last): - File "", line 1, in -TypeError: 'dict_values' object is not subscriptable ----- -==== -<1> O `repr` de um objeto view mostra seu conteúdo. -<2> Podemos consultar a `len` de uma view. -<3> Views são iteráveis, então é fácil criar listas a partir delas. -<4> Views implementam `+__reversed__+`, devolvendo um iterador personalizado. -<5> Não é possível usar `[]` para obter itens individuais de uma view. - -Um objeto view é um proxy dinâmico. -Se o `dict` fonte é atualizado, as mudanças podem ser vistas imediatamente através de uma view existente. -Continuando do <>: - -[source, pycon] ----- ->>> d['z'] = 99 ->>> d -{'a': 10, 'b': 20, 'c': 30, 'z': 99} ->>> values -dict_values([10, 20, 30, 99]) ----- - -As classes `dict_keys`, `dict_values`, e `dict_items` são internas: -elas não estão disponíveis via `+__builtins__+` ou qualquer módulo da biblioteca padrão, -e mesmo que você obtenha uma referência para uma delas, não pode usar essa referência para criar uma view do zero no seu código Python: - -[source, pycon] ----- ->>> values_class = type({}.values()) ->>> v = values_class() -Traceback (most recent call last): - File "", line 1, in -TypeError: cannot create 'dict_values' instances ----- - -A classe `dict_values` é a view de dicionário mais simples—ela implementa apenas os métodos especiais `+__len__+`, `+__iter__+`, e `+__reversed__+`. -Além desses métodos, `dict_keys` e `dict_items` implementam vários métodos dos _sets_, quase tantos quanto a classe `frozenset`. -Após vermos os conjuntos (_sets_), teremos mais a dizer sobre `dict_keys` e `dict_items`, na seção <>. - -Agora vamos ver algumas regras e dicas baseadas na forma como `dict` é implementado debaixo dos panos.((("", startref="DASviews03"))) - -[[consequences_dict_internals]] -=== Consequências práticas da forma como dict funciona - -A((("dictionaries and sets", "consequences of how dict works"))) implementação da tabela de hash do `dict` do Python é muito eficiente, mas é importante entender os efeitos práticos desse design: - -* Chaves((("keys", "practical consequences of using dict"))) devem ser objetos hashable. Eles devem implementar métodos `+__hash__+` e `+__eq__+` apropriados, como descrito na seção <>. -* O acesso aos itens através da chave é muito rápido. Mesmo que um `dict` tenha milhões de chaves, o Python pode localizar uma chave diretamente, computando o código hash da chave e derivando um deslocamento do índice na tabela de hash, com um possível ônus de um pequeno número de tentativas até encontrar a entrada correspondente. -* A ordenação das chaves é preservada, como efeito colateral de um layout de memória mais compacto para `dict` no CPython 3.6, que se tornou um recurso oficial da linguagem no 3.7. -* Apesar de seu novo layout compacto, os dicts apresentam, inevitavelmente, um uso adicional significativo de memória. A estrutura de dados interna mais compacta para um contêiner seria um array de ponteiros para os itens.footnote:[É assim que as tuplas são armazenadas.] Comparado a isso, uma tabela de hash precisa armazenar mais dados para cada entrada e, para manter a eficiência, o Python precisa manter pelo menos um terço das linhas da tabela de hash vazias. -* Para economizar memória, evite criar atributos de instância fora do método `+__init__+`. - -Essa última dica, sobre atributos de instância, é consequência do comportamento default do Python, de armazenar atributos de instância em um atributo `+__dict__+` especial, que é um `dict` vinculado a cada instância.footnote:[A menos que classe tenha umm atributo `+__slots__+`, como explicado na seção <>.] -Desde a implementação da https://fpy.li/pep412[PEP 412—Key-Sharing Dictionary (_Dicionário de Compartilhamento de Chaves_)] (EN), no Python 3.3, -instâncias de uma classe podem compartilhar uma tabela de hash comum, armazenada com a classe. -Essa tabela de hash comum é compartilhada pelo `+__dict__+` de cada nova instância que, quando `+__init__+` retorna, tenha os mesmos nomes de atributos que a primeira instância daquela classe a ser criada. O `+__dict__+` de cada instância então pode manter apenas seus próprios valores de atributos como uma simples array de ponteiros. -Acrescentar um atributo de instância após o `+__init__+` obriga o Python a criar uma nova tabela de hash só para o `+__dict__+` daquela instância (que era o comportamento default antes do Python 3.3). -De acordo com a PEP 412, essa otimização reduz o uso da memória entre 10% e 20% em programas orientados as objetos. -Os detalhes das otimizações do layout compacto e do compartilhamento de chaves são bastante complexos. Para saber mais, por favor leio o texto https://fpy.li/hashint["Internals of sets and dicts"] (EN) em pass:[fluentpython.com]. - -Agora vamos estudar conjuntos(_sets_). - - -=== Teoria dos conjuntos - -Conjuntos((("sets", "set theory", id=Stheory03")))((("dictionaries and sets", "set theory", id="DASset03"))) não são novidade no Python, mais ainda são um tanto subutilizados. O tipo `set` e seu irmão imutável, `frozenset`, surgiram inicialmente como módulos, na biblioteca padrão do Python 2.3, e foram promovidos a tipos embutidos no Python 2.6. - -[NOTE] -==== -Nesse((("frozenset"))) livro, uso a palavra "conjunto" para me referir tanto a `set` quanto a `frozenset`. Quando falo especificamente sobre a classe `set`, uso a fonte de espaçamento constante: `set`. -==== - -Um conjunto é uma coleção de objetos únicos. Um caso de uso básico é a remoção de itens duplicados: - -[source, pycon] ----- ->>> l = ['spam', 'spam', 'eggs', 'spam', 'bacon', 'eggs'] ->>> set(l) -{'eggs', 'spam', 'bacon'} ->>> list(set(l)) -['eggs', 'spam', 'bacon'] ----- - -[TIP] -==== -Para remover elementos duplicados preservando a ordem da primeira ocorrência de cada item, você pode fazer isso com um `dict` simples, assim: -[source, pycon] ----- ->>> dict.fromkeys(l).keys() -dict_keys(['spam', 'eggs', 'bacon']) ->>> list(dict.fromkeys(l).keys()) -['spam', 'eggs', 'bacon'] ----- -==== - -Elementos de um conjunto devem ser hashable. O tipo `set` não é hashable, então não é possível criar um `set` com instâncias aninhadas de `set`. Mas `frozenset` é hashable, então você pode ter elementos `frozenset` dentro de um `set`. - -Além de impor a unicidade de cada elemento, os tipos conjunto implementam muitas operações entre conjuntos como operadores infixos. Assim, dados dois conjuntos `a` e `b`, `a | b` devolve sua união, `a & b` calcula a intersecção, `a - b` a diferença, e `a ^ b` a diferença simétrica. Usadas com sabedoria, 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 e debater—pela remoção de loops e da lógica condicional. - -Por exemplo, imagine que você tem um grande conjunto de endereços de email (o `palheiro`—_haystack_) e um conjunto menor de endereços (as `agulhas`—_needles_), e precisa contar quantas `agulhas` existem no `palheiro`. Graças à interseção de `set` (o operador `&`), é possível programar isso em uma única linha (veja o <>). - -[[ex_set_ops_ex]] -.Conta as ocorrências de agulhas (_needles_) em um palheiro (_haystack_), ambos do tipo set -==== -[source, python3] ----- -found = len(needles & haystack) ----- -==== - -Sem o operador de intersecção, seria necessário escrever o <> para realizar a mesma tarefa executa pelo <>. - -[[ex_set_loop_ex]] -.Conta as ocorrências de agulhas (_needles_) em um palheiro (_haystack_); mesmo resultado final do <> -==== -[source, python3] ----- -found = 0 -for n in needles: - if n in haystack: - found += 1 ----- -==== - -O <> é um pouco mais rápido que o <>. Por outros lado, o <> funciona para quaisquer objetos iteráveis `needles` e `haystack`, enquanto o <> exige que ambos sejam conjuntos. Mas se você não tem conjuntos à mão, pode sempre criá-los na hora, como mostra o <>. - -[[ex_set_ops_ex2]] -.Conta as ocorrências de agulhas (_needles_) em um palheiro (_haystack_); essas linhas funcionam para qualquer tipo iterável -==== -[source, python3] ----- -found = len(set(needles) & set(haystack)) - -# another way: -found = len(set(needles).intersection(haystack)) ----- -==== - -Claro, há o custo extra envolvido na criação dos conjuntos no <>, mas se ou as `needles` ou o `haystack` já forem um `set`, a alternativa no <> pode ser mais barata que o <>. - -Qualquer dos exemplos acima é capaz de buscar 1000 elementos em um `haystack` de 10,000,000 de itens em cerca de 0,3 milisegundos—isso é próximo de 0,3 microsegundos por elemento. - -Além do teste de existência extremamente rápido (graças à tabela de hash subjacente), os tipos embutidos `set` e `frozenset` oferecem uma rica API para criar novos conjuntos ou, no caso de `set`, para modificar conjuntos existentes. Vamos discutir essas operações em breve, após uma observação sobre sintaxe.((("", startref="Stheory03"))) - - -==== Sets literais - -A((("sets", "set literals"))) sintaxe de literais `set`—`{1}`, `{1, 2}`, etc.—parece exatamente igual à notação matemática, mas tem uma importante exceção: não notação literal para o `set` vazio, então precisamos nos lembrar de escrever `set()`. - -.Peculiaridade sintática -[WARNING] -==== -Não esqueça que, para criar um `set` vazio, é preciso usar o construtor sem argumentos: `set()`. Se você escrever `{}`, vai criar um +dict+ vazio—isso não mudou no Python 3. -==== - -No Python 3, a representação padrão dos sets como strings sempre usa a notação `{…}`, exceto para o conjunto vazio: - -[source, pycon] ----- ->>> s = {1} ->>> type(s) - ->>> s -{1} ->>> s.pop() -1 ->>> s -set() ----- - -[role="pagebreak-before less_space"] -A sintaxe do `set` literal, como `{1, 2, 3}`, é mais rápida e mais legível que uma chamada ao construtor (por exemplo, `set([1, 2, 3])`). Essa última forma é mais lenta porque, para avaliá-la, o Python precisa buscar o nome `set` para obter seu construtor, daí criar uma lista e, finalmente, passá-la para o construtor. Por outro lado, para processar um literal como `{1, 2, 3}`, o Python roda um bytecode especializado, `BUILD_SET`.footnote:[Isso pode ser interessante, mas não é super importante. Essa diferença de velocidade vai ocorrer apenas quando um conjunto literal for avaliado, e isso acontece no máximo uma vez por processo Python—quando um módulo é compilado pela primeira vez. Se você estiver curiosa, importe a função `dis` do módulo `dis`, e a use para desmontar os bytecodes para um `set` literal—por exemplo, `dis('{1}')`—e uma chamada a `set`—`+dis('set([1])')+`] - -Não há sintaxe especial para representar literais `frozenset`—eles devem ser criados chamando seu construtor. Sua representação padrão como string no Python 3 se parece com uma chamada ao construtor de `frozenset`. Observe a saída na sessão de console a seguir: - -[source, pycon] ----- ->>> frozenset(range(10)) -frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) ----- - -E por falar em sintaxe, a ideia das listcomps foi adaptada para criar conjuntos também. - - -==== Compreensões de conjuntos - -Compreensões de conjuntos((("sets", "set comprehensions"))) (_setcomps_) apareceram há bastante tempo, no Python 2.7, junto com as dictcomps que vimos na seção <>. O <> mostra procedimento. - -[[ex_setcomp]] -.Cria um conjunto de caracteres Latin-1 que tenham a palavra "SIGN" em seus nomes Unicode -==== -[source, pycon] ----- ->>> from unicodedata import name <1> ->>> {chr(i) for i in range(32, 256) if 'SIGN' in name(chr(i),'')} <2> -{'§', '=', '¢', '#', '¤', '<', '¥', 'µ', '×', '$', '¶', '£', '©', -'°', '+', '÷', '±', '>', '¬', '®', '%'} ----- -==== -<1> Importa a função `name` de `unicodedata` para obter os nomes dos caracteres. -<2> Cria um conjunto de caracteres com códigos entre 32 e 255 que contenham a palavra `'SIGN'` em seus nomes. - -A ordem da saída muda a cada processo Python, devido ao hash "salgado", mencionado na seção <>. - -Questões de sintaxe à parte, vamos considerar agora o comportamento dos conjuntos.((("", startref="DASset03"))) - - -[[consequences_set_sec]] -[role="pagebreak-before less_space"] -=== Consequências práticas da forma de funcionamento dos conjuntos - -Os((("dictionaries and sets", "consequences of how set works")))((("sets", "consequences of how set works"))) tipos `set` e `frozenset` são ambos implementados com um tabela de hash. Isso tem os seguintes efeitos: - -* Elementos de conjuntos tem que ser objetos hashable. Eles precisam implementar métodos `+__hash__+` e `+__eq__+` adequados, como descrido na seção <>. -* O teste de existência de um elemento é muito eficiente. Um conjunto pode ter milhões de elementos, mas um elemento pode ser localizado diretamente, computando o código hash da chave e derivando um deslocamento do índice, com o possível ônus de um pequeno número de tentativas até encontrar a entrada correspondente ou exaurir a busca. -* Conjuntos usam mais memória se comparados aos simples ponteiros de um array para seus elementos—que é uma estrutura mais compacta, mas também muito mais lenta para buscas se seu tamanho cresce além de uns poucos elementos. -* A ordem dos elementos depende da ordem de inserção, mas não de forma útil ou confiável. Se dois elementos são diferentes mas tem o mesmo código hash, sua posição depende de qual elemento foi inserido primeiro. -* Acrescentar elementos a um conjunto muda a ordem dos elementos existentes. -Isso ocorre porque o algoritmo se torna menos eficiente se a tabela de hash estiver com mais de dois terços de ocupação, então o Python pode ter que mover e redimensionar a tabela conforme ela cresce. -Quando isso acontece, os elementos são reinseridos e sua ordem relativa pode mudar. - -Veja o post https://fpy.li/hashint["Internals of sets and dicts"] (EN) -no pass:[fluentpython.com] para maiores detalhes. - -Agora vamos revisar a vasta seleção de operações oferecidas pelos conjuntos. - -[[set_op_section]] -==== Operações de conjuntos - -A <> dá((("dictionaries and sets", "set operations", id="DASset03-ops")))((("sets", "set operations", id="Soper03")))((("UML class diagrams", "simplified for MutableSet and superclasses"))) -uma visão geral dos métodos disponíveis em conjuntos mutáveis e imutáveis. Muitos deles são métodos especiais que sobrecarregam operadores, tais como `&` and `>=`. A <> mostra os operadores matemáticos de conjuntos que tem operadores ou métodos correspondentes no Python. Note que alguns operadores e métodos realizam mudanças no mesmo lugar sobre o conjunto alvo (por exemplo, `&=`, `difference_update`, etc.). Tais operações não fazem sentido no mundo ideal dos conjuntos matemáticos, e também não são implementadas em `frozenset`. - -[role="man-height4"] -[TIP] -==== -Os operadores infixos na <> exigem que os dois operandos sejam conjuntos, mas todos os outros métodos recebem um ou mais argumentos iteráveis. -Por exemplo, para produzir a união de quatro coleções, `a`, `b`, `c`, e `d`, você pode chamar -`a.union(b, c, d)`, onde `a` precisa ser um `set`, mas `b`, `c`, e `d` podem ser iteráveis de qualquer tipo que produza itens hashable. -Para criar um novo conjunto com a união de quatro iteráveis, desde o Python 3.5 você pode escrever `{*a, *b, *c, *d}` ao invés de atualizar um conjunto existente, graças à -https://fpy.li/pep448[PEP 448—Additional Unpacking Generalizations (_Generalizações de Desempacotamento Adicionais_)]. - -==== - -[[set_uml]] -.Diagrama de classes UML simplificado para `MutableSet` e suas superclasses em `collections.abc` (nomes em itálico são classes e métodos abstratos; métodos de operadores reversos foram omitidos por concisão). -image::images/flpy_0302.png[Diagrama de classe UML para `Set` e `MutableSet`] - -[[set_operators_tbl]] -.Operações matemáticas com conjuntos: esses métodos ou produzem um novo conjunto ou atualizam o conjunto alvo no mesmo lugar, se ele for mutável -[options="header"] -|===================================================================================================================================================================================== -|Math symbol|Python operator| Method | Description -| S ∩ Z | `s & z` | `+s.__and__(z)+` | Intersecção de `s` e `z` -| | `z & s` | `+s.__rand__(z)+` | Operador `&` invertido -| | | `s.intersection(it, …)` | Intersecção de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. -| | `s &= z` | `+s.__iand__(z)+` | `s` atualizado com a intersecção de `s` e `z` -| | | `s.intersection_update(it, …)` | `s` atualizado com a intersecção de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. -|||| -| S ∪ Z | `s \| z` | `+s.__or__(z)+` | União de `s` e `z` -| | `z \| s` | `+s.__ror__(z)+` | `\|` invertido -| | | `s.union(it, …)` | União de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. -| | `s \|= z`| `+s.__ior__(z)+` | `s` atualizado com a união de `s` e `z` -| | | `s.update(it, …)` | `s` atualizado com a união de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. -|||| -| S \ Z | `s - z` | `+s.__sub__(z)+` | Complemeto relativo ou diferença entre `s` e `z` -| | `z - s` | `+s.__rsub__(z)+` | Operador `-` invertido -| | | `s.difference(it, …)` | Diferença entre `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. -| | `s -= z` | `+s.__isub__(z)+` | `s` atualizado com a diferença entre `s` e `z` -| | | `s.difference_update(it, …)` | `s` atualizado com a diferença entre `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. -|||| -| S ∆ Z | `s ^ z` | `+s.__xor__(z)+` | Diferença simétrica (o complemento da intersecção `s & z`) -| | `z ^ s` | `+s.__rxor__(z)+` | Operador `^` invertido -| | | `s.symmetric_difference(it)` | Complemento de `s & set(it)` -| | `s ^= z` | `+s.__ixor__(z)+` | `s` atualizado com a diferença simétrica de `s` e `z` -| | |`s.symmetric_difference_update(it, …)` | `s` atualizado com a diferença simétrica de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. -|===================================================================================================================================================================================== - -A <> lista predicados de conjuntos: operadores e métodos que devolvem `True` ou `False`. - -[[set_comparison_tbl]] -.Operadores e métodos de comparação de conjuntos que devolvem um booleano -[options="header"] -|=============================================================================================================== -|Math symbol|Python operator| Method | Description -| S ∩ Z = ∅ | | `s.isdisjoint(z)` | `s` e `z` são disjuntos (não tem elementos em comum) -|||| -| e ∈ S | `e in s` | `+s.__contains__(e)+` | Elemento `e` é membro de `s` -|||| -| S ⊆ Z | `s \<= z` | `+s.__le__(z)+` | `s` é um subconjunto do conjunto `z` -| | | `s.issubset(it)` | `s` é um subconjunto do conjunto criado a partir do iterável `it` -|||| -| S ⊂ Z | `s < z` | `+s.__lt__(z)+` | `s` é um subconjunto própriofootnote:[NT: Na teoria dos conjuntos, A é um _subconjunto próprio_ de B se A é subconjunto de B e A é diferente de B.] do conjunto `z` -|||| -| S ⊇ Z | `s >= z` | `+s.__ge__(z)+` | `s` é um superconjunto do conjunto `z` -| | | `s.issuperset(it)` | `s` é um superconjunto do conjunto criado a partir do iterável `it` -|||| -| S ⊃ Z | `s > z` | `+s.__gt__(z)+` | `s` é um superconjunto próprio do conjunto `z` -|||| -|=============================================================================================================== - - -Além de operadores e métodos derivados da teoria matemática dos conjuntos, os tipos conjunto implementam outros métodos para tornar seu uso prático, resumidos na <>. - -[[set_methods_tbl]] -.Métodos adicionais de conjuntos -[options="header"] -|=================================================================================================================== -| | set | frozenset|   -| `s.add(e)` | ● | | Adiciona elemento `e` a `s` -| `s.clear()` | ● | | Remove todos os elementos de `s` -| `s.copy()` | ● | ● | Cópia rasa de `s` -| `s.discard(e)` | ● | | Remove elemento `e` de `s`, se existir -| `+s.__iter__()+` | ● | ● | Obtém iterador de `s` -| `+s.__len__()+` | ● | ● | `len(s)` -| `s.pop()` | ● | | Remove e devolve um elemento de `s`, gerando um `KeyError` se `s` estiver vazio -| `s.remove(e)` | ● | | Remove elemento `e` de `s`, gerando um `KeyError` se `e` não existir em `s` -|=================================================================================================================== - - -Isso encerra nossa visão geral dos recursos dos conjuntos. Como prometido na seção <>, vamos agora ver como dois dos tipos de views de dicionários se comportam de forma muito similar a um `frozenset`.((("", startref="Soper03")))((("", startref="DASset03-ops"))) - - -[[set_ops_dict_views_sec]] -=== Operações de conjuntos em views de dict - -A <> mostra((("dictionaries and sets", "set operations on dict views")))((("sets", "set operations on dict views")))(((".keys method", primary-sortas="keys method")))(((".items method", primary-sortas="items method"))) como os objetos view devolvidos pelos métodos `.keys()` e `.items()` de dict são notavelmente similares a um `frozenset`. - -[[view_methods_tbl]] -.Métodos implementados por `frozenset`, `dict_keys`, e `dict_items` -[options="header"] -|======================================================================================================================== -| | frozenset | dict_keys | dict_items | Description -| `+s.__and__(z)+` | ● | ● | ● | `s & z` (interseção de `s` e `z`) -| `+s.__rand__(z)+` | ● | ● | ● | operador `&` invertido -| `+s.__contains__()+` | ● | ● | ● | `e in s` -| `s.copy()` | ● | | | Cópia rasa de `s` -| `s.difference(it, …)` | ● | | | Diferença entre `s` e os iteráveis `it`, etc. -| `s.intersection(it, …)` | ● | | | Intersecção de `s` e dos iteráveis `it`, etc. -| `s.isdisjoint(z)` | ● | ● | ● | `s` e `z` são disjuntos (não tem elementos em comum) -| `s.issubset(it)` | ● | | | `s` é um subconjunto do iterável `it` -| `s.issuperset(it)` | ● | | | `s` é um superconjunto do iterável `it` -| `+s.__iter__()+` | ● | ● | ● | obtém iterador para `s` -| `+s.__len__()+` | ● | ● | ● | `len(s)` -| `+s.__or__(z)+` | ● | ● | ● | `s \| z` (união de `s` e `z`) -| `+s.__ror__()+` | ● | ● | ● | Operador `\|` invertido -| `+s.__reversed__()+` | | ● | ● | Obtém iterador para `s` com a ordem invertida -| `+s.__rsub__(z)+` | ● | ● | ● | Operador `-` invertido -| `+s.__sub__(z)+` | ● | ● | ● | `s - z` (diferença entre `s` e `z`) -| `s.symmetric_difference(it)` | ● | | | Complemento de `s & set(it)` -| `s.union(it, …)` | ● | | | União de `s` e dos iteráveis`it`, etc. -| `+s.__xor__()+` | ● | ● | ● | `s ^ z` (diferença simétrica de `s` e `z`) -| `+s.__rxor__()+` | ● | ● | ● | Operador `^` invertido -|======================================================================================================================== - -Especificamente, `dict_keys` e `dict_items` implementam os métodos especiais para suportar as poderosas operações de conjuntos `&` (intersecção), `|` (união), `-` (diferença), and `^` (diferença simétrica). - -Por exemplo, usando `&` é fácil obter as chaves que aparecem em dois dicionários: - -[source, pycon] ----- ->>> d1 = dict(a=1, b=2, c=3, d=4) ->>> d2 = dict(b=20, d=40, e=50) ->>> d1.keys() & d2.keys() -{'b', 'd'} ----- - -Observe que o valor devolvido por `&` é um `set`. -Melhor ainda: os operadores de conjuntos em views de dicionários são compatíveis com instâncias de `set`. -Veja isso: - -[source, pycon] ----- ->>> s = {'a', 'e', 'i'} ->>> d1.keys() & s -{'a'} ->>> d1.keys() | s -{'a', 'c', 'b', 'd', 'i', 'e'} ----- - -[WARNING] -==== -Uma view obtida de `dict_items` só funciona como um conjunto se todos os valores naquele `dict` são hashable. Tentar executar operações de conjuntos sobre uma view devolvida por `dict_items` que contenha valores não-hashable gera um `TypeError: unhashable type 'T'`, sendo `T` o tipo do valor incorreto. - -Por outro lado, uma view devolvida por `dict_keys` sempre pode ser usada como um conjunto, 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 quando seu código precisa inspecionar o conteúdo de dicionários. -Deixe a eficiente implemtação do Python em C trabalhar para você! - -Com isso, encerramos esse capítulo. - - -[role="pagebreak-before less_space"] -=== Resumo do capítulo - -Dicionários((("dictionaries and sets", "overview of"))) são a pedra fundamental do 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`. - -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`. Com a nova implementação de `dict`, o `OrderedDict` não é mais tão útil quanto antes, mas deve permanecer na bibliotece padrão para manter a compatibilidade retroativa—e por suas características específicas ausentes em `dict`, tal como a capacidade de levar em consideração o ordenamento das chaves em uma comparação `==`. Também no módulo `collections` está o `UserDict`, uma classe base fácil de usar na criação de mapeamentos personalizados. - -Dois métodos poderosos disponíveis na maioria dos mapeamentos são `setdefault` e `update`. O método `setdefault` pode atualizar itens que mantenham valores mutáveis—por exemplo, em um `dict` de valores `list`—evitando uma segunda busca pela mesma chave. O método `update` permite inserir ou sobrescrever itens em massa a partir de qualquer outro mapeamento, desde iteráveis que forneçam pares `(chave, valor)` até argumentos nomeados. Os construtores de mapeamentos também usam `update` internamente, permitindo que instâncias sejam inicializadas a partir de outros mapeamentos, de iteráveis e de argumentos nomeados. -Desde o Python 3.9 também podemos usar o operador `|=` para atualizar uma mapeamento e -o operador `|` para criar um novo mapeamento a partir a união de dois mapeamentos. - -Um gancho elegante na API de mapeamento é o método `+__missing__+`, que permite personalizar o que acontece quando uma chave não é encontrada ao se usar a sintaxe `d[k]` syntax, -que invoca `+__getitem__+`. - -O módulo `collections.abc` oferece as classes base abstratas `Mapping` e `MutableMapping` como interfaces padrão, muito úteis para checagem de tipo durante a execução. -O `MappingProxyType`, do módulo `types`, cria uma fachada imutável para um mapeamento que você precise proteger de modificações acidentais. -Existem também ABCs para `Set` e `MutableSet`. - -Views de dicionários foram uma grande novidade no Python 3, eliminando o uso desnecessário de memória dos métodos `.keys()`, `.values()`, e `.items()` do Python 2, que criavam listas duplicando os dados na instância alvo de `dict`. Além disso, as classes `dict_keys` e `dict_items` suportam os operadores e métodos mais úteis de `frozenset`. - - -[[further_reading_dict]] -[role="pagebreak-before less_space"] -=== Leitura complementar - -Na((("dictionaries and sets", "further reading on"))) documentação da Biblioteca Padrão do Python, a seção https://docs.python.org/pt-br/3/library/collections.html["collections—Tipos de dados de contêineres"] inclui exemplos e receitas práticas para vários tipos de mapeamentos. O código-fonte do Python para o módulo, pass:[Lib/collections/__init__.py], é uma excelente referência para qualquer um que deseje criar novos tipos de mapeamentos ou entender a lógica dos tipos existentes. O capítulo 1 do pass:[Python Cookbook, 3rd ed.] (O'Reilly), de David Beazley e Brian K. Jones traz 20 receitas práticas e perpicazes usando estruturas de dados—a maioria mostrando formas inteligentes de usar `dict`. - -Greg Gandenberger defende a continuidade do uso de `collections.OrderedDict`, -com os argumentos de que "explícito é melhor que implícito," compatibilidade retroativa, e o fato de algumas ferramentas e bilbiotecas presumirem que a ordenação das chaves de um `dict` é irrelevante—nesse post: https://fpy.li/3-18["Python Dictionaries Are Now Ordered. Keep Using OrderedDict" (_Os dicionários do Python agora são ordenados. Continue a usar OrderedDict_)] (EN). - -A https://fpy.li/pep3106[PEP 3106--Revamping dict.keys(), .values() and .items() (_Renovando dict.keys(), .values() e .items()_)] (EN) foi onde Guido van Rossum apresentou o recurso de views de dicionário para o Python 3. No resumo, ele afirma que a ideia veio da Java Collections Framework. - -O https://fpy.li/3-19[PyPy] foi o primeiro interpretador Python a implementar a proposta de Raymond Hettinger de dicts compactos, e eles escreverem em seu blog sobre isso, em https://fpy.li/3-20["Faster, more memory efficient and more ordered dictionaries on PyPy" (_Dicionários mais rápidos, mais eficientes em termos de memória e mais ordenados no PyPy_)] (EN), reconhecendo que um layout similar foi adotado no PHP 7, como descrito em https://fpy.li/3-21[PHP's new hashtable implementation (_A nova implementação de tabelas de hash do PHP_)] (EN). É sempre muito bom quando criadores citam trabalhos anteriores de outros. - -Na PyCon 2017, Brandon Rhodes apresentou https://fpy.li/3-22["The Dictionary Even Mightier" (_O dicionário, ainda mais poderoso_)] (EN), uma continuação de sua apresentação animada clássica https://fpy.li/3-23["The Mighty Dictionary" (_O poderoso dicionário_)] (EN)—incluindo colisões de hash animadas! -Outro vídeo atual mas mais aprofundado sobre o funcionamento interno do `dict` do Python é https://fpy.li/3-24["Modern Dictionaries" (_Dicionários modernos_)] (EN) de Raymond Hettinger, onde ele nos diz que após inicialmente fracassar em convencer os desenvolvedores principais do Python sobre os dicts compactos, ele persuadiu a equipe do PyPy, eles os adotaram, a ideia ganhou força, e finalmente foi https://fpy.li/3-25[adicionada] ao CPython 3.6 por INADA Naoki. -Para saber todos os detalhes, dê uma olhada nos extensos comentários no código-fonte do CPython para https://fpy.li/3-26[_Objects/dictobject.c_] (EN) e no documento de design em https://fpy.li/3-27[_Objects/dictnotes.txt_] (EN). - -A justificativa para a adição de conjuntos ao Python está documentada na https://fpy.li/pep218[PEP 218--Adding a Built-In Set Object Type (_Adicionando um objeto embutido de tipo conjunto_)]. Quando a PEP 218 foi aprovada, nenhuma sintaxe literal especial foi adotada para conjuntos. Os literais `set` foram criados para o Python 3 e implementados retroativamente no Python 2.7, assim como as compreensões de `dict` e `set`. -Na PyCon 2019, eu apresentei -https://fpy.li/3-29["Set Practice: learning from Python's set types" (_A Prática dos Conjuntos: aprendendo com os tipos conjunto do Python_)] (EN), -// https://fpy.li/3-28[video], [slides]) -// https://fpy.li/3-28["Set Practice: learning from Python's set types"],footnote:[The slides for this presentation are available at https://fpy.li/3-29[].)] -descrevendo casos de uso de conjuntos em programas reais, falando sobre o design de sua API, e sobre a implementação da https://fpy.li/3-30[`uintset`], uma classe de conjunto para elementos inteiros, usando um vetor de bits ao invés de uma tabela de hash, inspirada por um exemplo do capítulo 6 do excelente http://gopl.io[_The Go Programming Language_ (A Linguagem de Programação Go)] (EN), de Alan Donovan e Brian Kernighan (Addison-Wesley). - -A revista _Spectrum_, do IEEE, tem um artigo sobre Hans Peter Luhn, um prolífico inventor, que patenteou um conjunto de cartões interligados que permitiam selecionar receitas de coquetéis a partir dos ingredientes disponíveis, entre inúmeras outras invenções, incluindo... tabelas de hash! Veja https://fpy.li/3-31["Hans Peter Luhn and the Birth of the Hashing Algorithm" (_Hans Peter Luhn e o Nascimento do Algoritmo de Hash_)]. - -.Ponto de Vista -**** - -[role="soapbox-title"] -Açúcar sintático - -Meu((("dictionaries and sets", "Soapbox discussion")))((("Soapbox sidebars", "syntactic sugar")))((("syntactic sugar"))) amigo Geraldo Cohen certa vez observou que o Python é "simples e correto." - -Puristas de linguagens de programação gostam de desprezar a sintaxe como algo desimportante. - -[quote, Alan Perlis] -____ -Syntactic sugar causes cancer of the semicolon.footnote:[NT: Explicando o trocadilho intraduzível: "colon", em inglês, designa "a parte central do intestino grosso"; "semicolon", por outro lado, é "ponto e vírgula". A frase diz, literalmente, "Açúcar sintático causa câncer no ponto e vírgula", que faz sentido em inglês pela proximidade ortográfica das duas palavras.] -____ - -A sintaxe é a interface de usuário de uma linguagem de programação, então tem muita importância na prática. - -Antes de encontrar o Python, fiz um pouco de programação para a web usando Perl e PHP. A sintaxe para mapeamentos nessas linguagens é muito útil, e eu sinto imensa saudade dela sempre que tenho que usar Java ou C. - -Uma boa sintaxe para mapeamentos literais é muito conveniente para configuração, para implementações guiadas por tabelas, e para conter dados para prototipagem e testes. -Essa foi uma das lições que os projetistas do Go aprenderam com as linguagens dinâmicas. A falta de uma boa forma de expressar dados estruturados no código empurrou a comunidade Java a adotar o prolixo e excessivamente complexo XML como formato de dados. - -JSON foi proposto como https://fpy.li/3-32["The Fat-Free Alternative to XML" (_A alternativa sem gordura ao XML_)] -e se tornou um imenso sucesso, substituindo o XML em vários contextos. -Uma sintaxe concisa para listas e dicionários resulta em um excelente formato para troca de dados. - -O PHP e o Ruby imitaram a sintaxe de hash do Perl, usando `\=>` para ligar chaves a valores. -O JavaScript usa `:` como o Python. Por que usar dois caracteres, quando um já é legível o bastante? - -O JSON veio do JavaScript, mas por acaso também é quase um subconjunto exato da sintaxe do Python. -O JSON é compatível com o Python, exceto pela grafia dos valores `true`, `false`, e `null`. - -Armin Ronacher https://fpy.li/3-33[tuitou] -que gosta de brincar com o espaço de nomes global do Python, para acrescentar apelidos compatíveis com o JSON para o `True`, o `False`,e o `None` do Python, pois daí ele pode colar trechos de JSON diretamente no console. -Sua ideia básica: - -[role="pagebreak-before less_space"] -[source, pycon] ----- ->>> true, false, null = True, False, None ->>> fruit = { -... "type": "banana", -... "avg_weight": 123.2, -... "edible_peel": false, -... "species": ["acuminata", "balbisiana", "paradisiaca"], -... "issues": null, -... } ->>> fruit -{'type': 'banana', 'avg_weight': 123.2, 'edible_peel': False, -'species': ['acuminata', 'balbisiana', 'paradisiaca'], 'issues': None} ----- - -A sintaxe que todo mundo agora usa para trocar dados é a sintaxe de `dict` e `list` do Python. -E então temos uma sintaxe agradável com a conveniência da preservação da ordem de inserção. - -Simples e correto. -**** diff --git a/capitulos/cap04.adoc b/capitulos/cap04.adoc deleted file mode 100644 index 14f71489..00000000 --- a/capitulos/cap04.adoc +++ /dev/null @@ -1,1535 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte - -[[strings_bytes_files]] -== Texto em Unicode versus Bytes - -[quote, Esther Nam e Travis Fischer] -____ -Humanos usam texto. Computadores falam em bytes.footnote:[Slide 12 da palestra "Character Encoding and Unicode in Python" (_Codificação de Caracteres e Unicode no Python_) na PyCon 2014 (https://fpy.li/4-1[slides] (EN), https://fpy.li/4-2[vídeo] (EN)).] -____ - - -O Python 3 introduziu uma forte distinção entre strings de texto humano e sequências de bytes puros. A conversão automática((("implicit conversion"))) de sequências de bytes para texto Unicode ficou para trás no Python 2. Este capítulo trata de strings Unicode, sequências de bytes, e das codificações usadas para converter umas nas outras. - -Dependendo do que você faz com o Python, pode achar que entender o Unicode não é importante. -Isso é improvável, mas mesmo que seja o caso, não há como escapar da separação entre `str` e `bytes`, -que agora exige conversões explícitas. -Como um bônus, você descobrirá que os tipos especializados de sequências binárias `bytes` e `bytearray` -oferecem recursos que a classe `str` "pau para toda obra" do Python 2 não oferecia. - -Nesse((("Unicode text versus bytes", "topics covered"))) capítulo, veremos os seguintes tópicos: - -* Caracteres, pontos de código e representações binárias -* Recursos exclusivos das sequências binárias: `bytes`, `bytearray`, e `memoryview` -* Codificando para o Unicode completo e para conjuntos de caracteres legados -* Evitando e tratando erros de codificação -* Melhores práticas para lidar com arquivos de texto -* A armadilha da codificação default e questões de E/S padrão -* Comparações seguras de texto Unicode com normalização -* Funções utilitárias para normalização, _case folding_ (equiparação maiúsculas/minúsculas) e remoção de sinais diacríticos por força bruta -* Ordenação correta de texto Unicode com `locale` e a biblioteca _pyuca_ -* Metadados de caracteres do banco de dados Unicode -* APIs duais, que processam `str` e `bytes` - - -=== Novidades nesse capítulo - -O suporte((("Unicode text versus bytes", "significant changes to"))) ao Unicode no Python 3 sempre foi muito completo e estável, -então o acréscimo mais notável é a seção <>, -descrevendo um utilitário de linha de comando para busca no banco de dados Unicode—uma forma de encontrar -gatinhos sorridentes ou hieróglifos do Egito antigo. - -Vale a pena mencionar que o suporte a Unicode no Windows ficou melhor e mais simples desde o Python 3.6, -como veremos na seção <>. - -Vamos começar então com os conceitos não-tão-novos mas fundamentais de caracteres, pontos de código e bytes. - -[NOTE] -==== -Para((("struct module")))((("binary records, parsing with struct"))) essa segunda edição, expandi a seção sobre o módulo `struct` e o publiquei online em https://fpy.li/4-3["Parsing binary records with struct" (_Analisando registros binários com struct_)], (EN) -no pass:[fluentpython.com], o website que complementa o livro. - -Lá((("emojis", "building"))) você também vai encontrar o -https://fpy.li/4-4["Building Multi-character Emojis" (_Criando emojis multi-caractere_)] (EN), -descrevendo como combinar caracteres Unicode para criar bandeiras de países, bandeiras de arco-íris, pessoas com tonalidades de pele diferentes e ícones de diferentes tipos de famílias. -==== - - -=== Questões de caracteres - -O((("Unicode text versus bytes", "characters and Unicode standard", id="UTVchar04"))) conceito de "string" é bem simples: uma string é uma sequência de caracteres. -O problema está na definição de "caractere". - -Em 2023, a melhor definição de "caractere" que temos é um caractere Unicode. Consequentemente, os itens que compõe um `str` do Python 3 são caracteres Unicode, como os itens de um objeto `unicode` no Python 2. Em contraste, os itens de uma `str` no Python 2 são bytes, assim como os itens num objeto `bytes` do Python 3. - -O padrão Unicode separa explicitamente a identidade dos caracteres de representações binárias específicas: - -* A identidade de um caractere é chamada de ((("code points")))ponto de código (__code point__). É um número de 0 a 1.114.111 (na base 10), -representado no padrão Unicode na forma de 4 a 6 dígitos hexadecimais precedidos pelo prefixo "U+", de U+0000 a U+10FFFF. -Por exemplo, o ponto de código da letra A é U+0041, o símbolo do Euro é U+20AC, e o símbolo musical da clave de sol corresponde ao ponto de código U+1D11E. -Cerca de 13% dos pontos de código válidos tem caracteres atribuídos a si no Unicode 13.0.0, a versão do padrão usada no Python 3.10. -* Os bytes específicos que representam um caractere dependem da((("encoding", "definition of"))) codificação (_encoding_) usada. -Uma codificação, nesse contexto, é um algoritmo que converte pontos de código para sequências de bytes, e vice-versa. -O ponto de código para a letra A (U+0041) é codificado como um único byte, `\x41`, na codificação UTF-8, -ou como os bytes `\x41\x00` na codificação UTF-16LE. -Em um outro exemplo, o UTF-8 exige três bytes para codificar o símbolo do Euro (U+20AC): `\xe2\x82\xac`. -Mas no UTF-16LE o mesmo ponto de código é U+20AC representado com dois bytes: `\xac\x20`. - -Converter pontos de código para bytes é _codificar_; -converter bytes para pontos de código é((("decoding", "definition of"))) _decodificar_. Veja o <>. - -[[ex_encode_decode]] -.Codificando e decodificando -==== -[source, python3] ----- ->>> s = 'café' ->>> len(s) # <1> -4 ->>> b = s.encode('utf8') # <2> ->>> b -b'caf\xc3\xa9' # <3> ->>> len(b) # <4> -5 ->>> b.decode('utf8') # <5> -'café' ----- -==== -<1> A `str` `'café'` tem quatro caracteres Unicode. -<2> Codifica `str` para `bytes` usando a codificação UTF-8. -<3> `bytes` literais são prefixados com um `b`. -<4> `bytes` `b` tem cinco bytes (o ponto de código para "é" é codificado com dois bytes em UTF-8). -<5> Decodifica `bytes` para `str` usando a codificação UTF-8. - -[TIP] -==== -Um jeito fácil de sempre lembrar a distinção entre `.decode()` e `.encode()` é se convencer -que sequências de bytes podem ser enigmáticos dumps de código de máquina, -ao passo que objetos `str` Unicode são texto "humano". -Daí que faz sentido _decodificar_ `bytes` em `str`, para obter texto legível por seres humanos, e _codificar_ `str` em `bytes`, para armazenamento ou transmissão. -==== - -Apesar do `str` do Python 3 ser quase o tipo `unicode` do Python 2 com um novo nome, -o `bytes` do Python 3 não é meramente o velho `str` renomeado, -e há também o tipo estreitamente relacionado `bytearray`. -Então vale a pena examinar os tipos de sequências binárias antes de avançar para questões de codificação/decodificação.((("", startref="UTVchar04"))) - - -=== Os fundamentos do byte - -Os((("Unicode text versus bytes", "byte essentials", id="UTVbytes04"))) novos tipos de sequências binárias são diferentes do `str` do Python 2 em vários aspectos. -A primeira coisa importante é que existem dois tipos embutidos básicos de((("binary sequences"))) sequências binárias: -o tipo imutável `bytes`, introduzido no Python 3, e o tipo mutável `bytearray`, -introduzido há tempos, no Python 2.6footnote:[O Python 2.6 e o 2.7 também tinham um `bytes`, mas ele era só um apelido (_alias_) para o tipo `str`.]. A documentação do Python algumas vezes usa o termo genérico "byte string" (_string de bytes_, na documentação em português) para se referir a `bytes` e `bytearray`. - -Cada item em `bytes` ou `bytearray` é um inteiro entre 0 e 255, -e não uma string de um caractere, como no `str` do Python 2. -Entretanto, uma fatia de uma sequência binária sempre produz uma sequência binária do mesmo tipo—incluindo fatias de tamanho 1. Veja o <>. - -[[ex_bytes_bytearray]] -.Uma sequência de cinco bytes, como `bytes e como `bytearray` -==== -[source, pycon] ----- ->>> cafe = bytes('café', encoding='utf_8') <1> ->>> cafe -b'caf\xc3\xa9' ->>> cafe[0] <2> -99 ->>> cafe[:1] <3> -b'c' ->>> cafe_arr = bytearray(cafe) ->>> cafe_arr <4> -bytearray(b'caf\xc3\xa9') ->>> cafe_arr[-1:] <5> -bytearray(b'\xa9') ----- -==== -<1> `bytes` pode ser criado a partir de uma `str`, dada uma codificação. -<2> Cada item é um inteiro em `range(256)`. -<3> Fatias de `bytes` também são `bytes`—mesmo fatias de um único byte. -<4> Não há uma sintaxe literal para `bytearray`: elas aparecem como `bytearray()` com um literal `bytes` como argumento. -<5> Uma fatia de `bytearray` também é uma `bytearray`. - -[WARNING] -==== -O fato de `my_bytes[0]` obter um `int` mas `my_bytes[:1]` devolver uma sequência de `bytes` de tamanho 1 só é surpreeendente porque estamos acostumados com o tipo `str` do Python, onde `s[0] == s[:1]`. -Para todos os outros tipos de sequência no Python, um item não é o mesmo que uma fatia de tamanho 1. -==== - -Apesar de sequências binárias serem na verdade sequências de inteiros, -sua notação literal reflete o fato delas frequentemente embutirem texto ASCII. -Assim, quatro formas diferentes de apresentação são utilizadas, dependendo do valor de cada byte: - -* Para bytes com código decimais de 32 a 126—do espaço ao `~` (til)—é usado o próprio caractere ASCII. -* Para os bytes correspondendo ao tab, à quebra de linha, ao carriage return (CR) e à `\`, são usadas as sequências de escape `\t`, `\n`, `\r`, e `\\`. -* Se os dois delimitadores de string, `'` e `"`, aparecem na sequência de bytes, a sequência inteira é delimitada com `'`, e qualquer `'` dentro da sequência é precedida do caractere de escape, assim `\'`.footnote:[Trívia: O caractere ASCII "aspas simples", que por default o Python usa como delimitador de strings, na verdade se chama APOSTROPHE no padrão Unicode. As aspas simples reais são assimétricas: a da esquerda é U+2018 e a da direita U+2019.] -* Para qualquer outro valor do byte, é usada uma sequência de escape hexadecimal (por exemplo, `\x00` é o byte nulo). - -É por isso que no <> vemos `b'caf\xc3\xa9'`: -os primeiros três bytes, `b'caf'`, estão na faixa de impressão do ASCII, ao contrário dos dois últimos. - -Tanto `bytes` quanto `bytearray` suportam todos os métodos de `str`, exceto aqueles relacionados formatação (`format`, `format_map`) -e aqueles que dependem de dados Unicode, incluindo `casefold`, `isdecimal`, `isidentifier`, `isnumeric`, `isprintable`, e `encode`. -Isso significa que você pode usar os métodos conhecidos de string, como `endswith`, `replace`, `strip`, `translate`, `upper` e dezenas de outros, com sequências binárias—mas com argumentos `bytes` em vez de `str`. -Além disso, as funções de expressões regulares no módulo `re` também funcionam com sequências binárias, se a regex for compilada a partir de uma sequência binária ao invés de uma `str`. -Desde o Python 3.5, o operador `%` voltou a funcionar com sequências binárias.footnote:[Ele não funcionava do Python 3.0 ao 3.4, causando muitas dores de cabeça nos desenvolvedores que lidam com dados binários. O retorno está documentado 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 tem um método de classe que `str` não possui, 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: - -[source, pycon] ----- ->>> bytes.fromhex('31 4B CE A9') -b'1K\xce\xa9' ----- - -As outras formas de criar instâncias de `bytes` ou `bytearray` são chamadas a seus construtores com: - -* Uma `str` e um argumento nomeado `encoding` -* Um iterável que forneça itens com valores entre 0 e 255 -* Um objeto que implemente o protocolo de buffer (por exemplo `bytes`, `bytearray`, `memoryview`, `array.array`), que copia os bytes do objeto fonte para a recém-criada sequência binária - -[WARNING] -==== -Até o Python 3.5, era possível chamar `bytes` ou `bytearray` com um único inteiro, para criar uma sequência daquele tamanho inicializada com bytes nulos. -Essa assinatura for descontinuada no Python 3.5 e removida no Python 3.6. -Veja a https://fpy.li/pep467[PEP 467--Minor API improvements for binary sequences (_Pequenas melhorias na API para sequências binárias_) (EN)]. -==== - -Criar uma sequência binária a partir de um objeto tipo buffer é uma operação de baixo nível que pode envolver conversão de tipos. -Veja uma demonstração no <>. - -[[ex_buffer_demo]] -.Inicializando bytes a partir de dados brutos de um array -==== -[source, pycon] ----- ->>> import array ->>> numbers = array.array('h', [-2, -1, 0, 1, 2]) <1> ->>> octets = bytes(numbers) <2> ->>> octets -b'\xfe\xff\xff\xff\x00\x00\x01\x00\x02\x00' <3> ----- -==== -<1> O typecode `'h'` criar um `array` de _short integers_ (inteiros de 16 bits). -<2> `octets` mantém uma cópia dos bytes que compõem `numbers`. -<3> Esses são os 10 bytes que representam os 5 inteiros pequenos. - -Criar um objeto `bytes` ou `bytearray` a partir de qualquer fonte tipo buffer vai sempre copiar os bytes. Já objetos `memoryview` permitem compartilhar memória entre estruturas de dados binários, como vimos na seção <>. - -Após essa exploração básica dos tipos de sequências de bytes do Python, vamos ver como eles são convertidos de e para strings.((("", startref="UTVbytes04"))) - -=== Codificadores/Decodificadores básicos - -A((("Unicode text versus bytes", "basic encoders/decoders", id="UTVbasic04")))((("encoding", "basics of", id="encod04")))((("decoding", "basics of", id="decod04"))) distribuição do Python inclui mais de 100((("codecs"))) _codecs_ (encoders/decoders, _codificadores/decodificadores) para conversão de texto para bytes e vice-versa. -Cada codec tem um nome, como `'utf_8'`, e muitas vezes apelidos, tais como `'utf8'`, `'utf-8'`, e `'U8'`, -que você pode usar como o argumento de codificação em funções como -`open()`, `str.encode()`, `bytes.decode()`, e assim por diante. -O <> mostra o mesmo texto codificado como três sequências de bytes diferentes. - -[[ex_codecs]] -.A string "El Niño" codificada com três codecs, gerando sequências de bytes muito diferentes -==== -[source, pycon] ----- ->>> for codec in ['latin_1', 'utf_8', 'utf_16']: -... print(codec, 'El Niño'.encode(codec), sep='\t') -... -latin_1 b'El Ni\xf1o' -utf_8 b'El Ni\xc3\xb1o' -utf_16 b'\xff\xfeE\x00l\x00 \x00N\x00i\x00\xf1\x00o\x00' ----- -==== - -A <> mostra um conjunto de codecs gerando bytes a partir de caracteres como a letra "A" e o símbolo musical da clave de sol. -Observe que as últimas três codificações tem bytes múltiplos e tamanho variável. - -[role="width-90"] -[[encodings_demo_fig]] -.Doze caracteres, seus pontos de código, e sua representação binária (em hexadecimal) em 7 codificações diferentes (asteriscos indicam que o caractere não pode ser representado naquela codificação). -image::images/flpy_0401.png[Tabela de demonstração de codificações] - -Aqueles asteriscos todos na <> deixam claro que algumas codificações, como o ASCII e mesmo o multi-byte GB2312, não conseguem representar todos os caracteres Unicode. -As codificações UTF, por outro lado, foram projetadas para lidar com todos os pontos de código do Unicode. - -As codificações apresentadas na <> foram escolhidas para montar uma amostra representativa: - -`latin1` a.k.a. `iso8859_1`:: Importante por ser a base de outras codificações,tal como a `cp1252` e o próprio Unicode (observe que os valores binários do `latin1` aparecem nos bytes do `cp1252` e até nos pontos de código). - -`cp1252`:: Um superconjunto útil de `latin1`, criado pela Microsoft, acrescentando símbolos convenientes como as aspas curvas e o € (euro); alguns aplicativos de Windows chamam essa codificação de "ANSI", mas ela nunca foi um padrão ANSI real. - -`cp437`:: O conjunto de caracteres original do IBM PC, com caracteres de desenho de caixas. Incompatível com o `latin1`, que surgiu depois. - -`gb2312`:: Padrão antigo para codificar ideogramas chineses simplificados usados na República da China; uma das várias codificações muito populares para línguas asiáticas. - -`utf-8`:: De longe a codificação de 8 bits mais comum na web. Em julho de 2021, o -pass:["W3Techs: Usage statistics of character encodings for websites"] -afirma que 97% dos sites usam UTF-8, um grande avanço sobre os 81,4% de setembro de 2014, quando escrevi este capítulo na primeira edição. - -`utf-16le`:: Uma forma do esquema de codificação UTF de 16 bits; todas as codificações UTF-16 suportam pontos de código acima de U+FFFF, através de sequências de escape chamadas "pares substitutos". - - -[WARNING] -==== -A UTF-16((("UCS-2 encoding")))((("emojis", "UCS-2 versus UTF-16 encoding"))) sucedeu a codificação de 16 bits original do Unicode 1.0—a UCS-2—há muito tempo, em 1996. -A UCS-2 ainda é usada em muitos sistemas, apesar de ter sido descontinuada ainda no século passado, por suportar apenas ponto de código até U+FFFF. -Em 2021, mas de 57% dos pontos de código alocados estava acima de U+FFFF, -incluindo os importantíssimos emojis. -==== - -Após completar essa revisão das codificações mais comuns, vamos agora tratar das questões relativas a operações de codificação e decodificação.((("", startref="UTVbasic04")))((("", startref="encod04")))((("", startref="decod04"))) - - -=== Entendendo os problemas de codificação/decodificação - -Apesar((("Unicode text versus bytes", "understanding encode/decode problems", id="UTVunder04")))((("encoding", "understanding encode/decode problems", id="Eunderst04")))((("decoding", "understanding encode/decode problems", id="Dunder04"))) de existir uma exceção genérica, `UnicodeError`, o erro relatado pelo Python em geral é mais específico: -ou é um `UnicodeEncodeError` (ao converter uma `str` para sequências binárias) ou é um `UnicodeDecodeError` (ao ler uma sequência binária para uma `str`). -Carregar módulos do Python também pode geram um `SyntaxError`, quando a codificação da fonte for inesperada. -Vamos ver como tratar todos esses erros nas próximas seções. - -[role="man-height3"] -[TIP] -==== -A primeira coisa a observar quando aparece um erro de Unicode é o tipo exato da exceção. -É um `UnicodeEncodeError`, um `UnicodeDecodeError`, ou algum outro erro (por exemplo, `SyntaxError`) -mencionando um problema de codificação? -Para resolver o problema, você primeiro precisa entendê-lo. -==== - - -==== Tratando o UnicodeEncodeError - -A maioria((("UnicodeEncodeError"))) dos codecs não-UTF entendem apenas um pequeno subconjunto dos caracteres Unicode. -Ao converter texto para bytes, um `UnicodeEncodeError` será gerado se um caractere não estiver definido na codificação alvo, a menos que seja fornecido um tratamento especial, passando um argumento `errors` para o método ou função de codificação. -O comportamento para tratamento de erro é apresentado no <>. - -[[ex_encoding]] -.Encoding to bytes: success and error handling -==== -[source, pycon] ----- ->>> city = 'São Paulo' ->>> city.encode('utf_8') <1> -b'S\xc3\xa3o Paulo' ->>> city.encode('utf_16') -b'\xff\xfeS\x00\xe3\x00o\x00 \x00P\x00a\x00u\x00l\x00o\x00' ->>> city.encode('iso8859_1') <2> -b'S\xe3o Paulo' ->>> city.encode('cp437') <3> -Traceback (most recent call last): - File "", line 1, in - File "/.../lib/python3.4/encodings/cp437.py", line 12, in encode - return codecs.charmap_encode(input,errors,encoding_map) -UnicodeEncodeError: 'charmap' codec can't encode character '\xe3' in -position 1: character maps to ->>> city.encode('cp437', errors='ignore') <4> -b'So Paulo' ->>> city.encode('cp437', errors='replace') <5> -b'S?o Paulo' ->>> city.encode('cp437', errors='xmlcharrefreplace') <6> -b'São Paulo' ----- -==== -<1> As codificações UTF lidam com qualquer `str` -<2> `iso8859_1` também funciona com a string `'São Paulo'`. -<3> `cp437` não consegue codificar o `'ã'` ("a" com til). O método default de tratamento de erro, — `'strict'`—gera um `UnicodeEncodeError`. -<4> O método de tratamento `errors='ignore'` pula os caracteres que não podem ser codificados; isso normalmente é uma péssima ideia, levando a perda silenciosa de informação. -<5> Ao codificar, `errors='replace'` substitui os caracteres não-codificáveis por um `'?'`; aqui também há perda de informação, mas os usuários recebem um alerta de que algo está faltando. -<6> `'xmlcharrefreplace'` substitui os caracteres não-codificáveis por uma entidade XML. Se você não pode usar UTF e não pode perder informação, essa é a única opção. - -[NOTE] -==== -O tratamento de erros de `codecs` é extensível. -Você pode registrar novas strings para o argumento `errors` passando um nome e uma função de tratamento de erros para a função `codecs.register_error` function. -Veja https://docs.python.org/pt-br/3/library/codecs.html#codecs.register_error[documentação de `codecs.register_error`] (EN). -==== - -O ASCII é um subconjunto comum a todas as codificações que conheço, então a codificação deveria sempre funcionar se o texto for composto exclusivamente por caracteres ASCII. -O Python 3.7 trouxe um novo método booleano, https://fpy.li/4-7[`str.isascii()`], para verificar se seu texto Unicode é 100% ASCII. -Se for, você deve ser capaz de codificá-lo para bytes em qualquer codificação sem gerar um `UnicodeEncodeError`. - - - -==== Tratando o UnicodeDecodeError - -Nem((("UnicodeDecodeError"))) todo byte contém um caractere ASCII válido, e nem toda sequência de bytes é um texto codificado em UTF-8 ou UTF-16 válidos; assim, se você presumir uma dessas codificações ao converter um sequência binária para texto, pode receber um `UnicodeDecodeError`, se bytes inesperados forem encontrados. - -Por outro lado, várias codificações de 8 bits antigas, como a `'cp1252'`, a `'iso8859_1'` e a `'koi8_r'` são capazes de decodificar qualquer série de bytes, incluindo ruído aleatório, sem reportar qualquer erro. Portanto, se seu programa presumir a codificação de 8 bits errada, ele vai decodificar lixo silenciosamente. - - -[TIP] -==== -Caracteres truncados ou distorcidos são conhecidos como "gremlins" ou "mojibake" (文字化け—"texto modificado" em japonês). -==== - -O <> ilustra a forma como o uso do codec errado pode produzir gremlins ou um `UnicodeDecodeError`. - -[[ex_decoding]] -.Decodificando de `str` para bytes: sucesso e tratamento de erro -==== -[source, pycon] ----- ->>> octets = b'Montr\xe9al' <1> ->>> octets.decode('cp1252') <2> -'Montréal' ->>> octets.decode('iso8859_7') <3> -'Montrιal' ->>> octets.decode('koi8_r') <4> -'MontrИal' ->>> octets.decode('utf_8') <5> -Traceback (most recent call last): - File "", line 1, in -UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 5: -invalid continuation byte ->>> octets.decode('utf_8', errors='replace') <6> -'Montr�al' ----- -==== -<1> A palavra "Montréal" codificada em `latin1`; `'\xe9'` é o byte para "é". -<2> Decodificar com Windows 1252 funciona, pois esse codec é um superconjunto de `latin1`. -<3> ISO-8859-7 foi projetado para a língua grega, então o byte `'\xe9'` é interpretado de forma incorreta, e nenhum erro é gerado. -<4> KOI8-R é foi projetado para o russo. Agora `'\xe9'` significa a letra "И" do alfabeto cirílico. -<5> O codec `'utf_8'` detecta que `octets` não é UTF-8 válido, e gera um `UnicodeDecodeError`. -<6> Usando `'replace'` para tratamento de erro, o `\xe9` é substituído por "�" -(ponto de código #U+FFFD), o caractere oficial do Unicode chamado `REPLACEMENT CHARACTER`, criado exatamente para representar caracteres desconhecidos. - -[[syntax_error_encoding]] -==== O SyntaxError ao carregar módulos com codificação inesperada - -UTF-8((("SyntaxError"))) é a codificação default para fontes no Python 3, da mesma forma que ASCII era o default no Python 2. -Se você carregar um módulo _.py_ contendo dados que não estejam em UTF-8, sem declaração codificação, -receberá uma mensagem como essa: - ----- -include::code/04-text-byte/syntax-msg.txt[] ----- - -Como o UTF-8 está amplamente instalado em sistemas GNU/Linux e macOS, -um cenário onde isso tem mais chance de ocorrer é na abertura de um arquivo _.py_ criado no Windows, com `cp1252`. -Observe que esse erro ocorre mesmo no Python para Windows, pois a codificação default para fontes de Python 3 é UTF-8 em todas as plataformas. - -Para resolver esse problema, acrescente o comentário mágico `coding` no início do arquivo, como no <>. - -[[ex_ola_mundo]] -.'ola.py': um "Hello, World!" em português -==== -[source, python3] ----- -# coding: cp1252 - -print('Olá, Mundo!') ----- -==== - -[TIP] -==== -Agora que o código fonte do Python 3 não está mais limitado ao ASCII, e por default usa a excelente codificação UTF-8, a melhor "solução" para código fonte em codificações antigas como `'cp1252'` é converter tudo para UTF-8 de uma vez, e não se preocupar com os comentários `coding`. -E se seu editor não suporta UTF-8, é hora de trocar de editor. -==== - -Suponha que você tem um arquivo de texto, seja ele código-fonte ou poesia, mas não sabe qual codificação foi usada. -Como detectar a codificação correta? Respostas na próxima seção. - - -[[discover_encoding]] -==== Como descobrir a codificação de uma sequência de bytes - -Como((("byte sequences"))) descobrir a codificação de uma sequência de bytes? Resposta curta: não é possível. Você precisa ser informado. - -Alguns protocolos de comunicação e formatos de arquivo, como o HTTP e o XML, contêm cabeçalhos que nos dizem explicitamente como o conteúdo está codificado. -Você pode ter certeza que algumas sequências de bytes não estão em ASCII, pois elas contêm bytes com valores acima de 127, e o modo como o UTF-8 e o UTF-16 são construídos também limita as sequências de bytes possíveis. - -.O hack do Leo para adivinhar uma decodificação UTF-8 - -**** -(Os próximos parágrafos vieram de uma nota escrita pelo revisor técnico Leonardo Rochael no rascunho desse livro.) - -Pela((("UTF-8 decoding"))) forma como o UTF-8 foi projetado, é quase impossível que uma sequência aleatória de bytes, ou mesmo uma sequência não-aleatória de bytes de uma codificação diferente do UTF-8, seja acidentalmente decodificada como lixo no UTF-8, ao invés de gerar um `UnicodeDecodeError`. - -As razões para isso são que as sequências de escape do UTF-8 nunca usam caracteres ASCII, e tais sequências de escape tem padrões de bits que tornam muito difícil que dados aleatórioas sejam UTF-8 válido por acidente. - -Portanto, se você consegue decodificar alguns bytes contendo códigos > 127 como UTF-8, a maior probabilidade é de sequência estar em UTF-8. - -Trabalhando com os serviços online brasileiros, alguns dos quais alicerçados em back-ends antigos, ocasionalmente precisei implementar uma estratégia de decodificação que tentava decodificar via UTF-8, e tratava um `UnicodeDecodeError` decodificando via `cp1252`. -Uma estratégia feia, mas efetiva. -**** - -Entretanto, considerando que as linguagens humanas também tem suas regras e restrições, uma vez que você supõe que uma série de bytes é um((("plain text"))) _texto humano simples_, -pode ser possível intuir sua codificação usando heurística e estatística. -Por exemplo, se bytes com valor `b'\x00'` bytes forem comuns, é provável que seja uma codificação de 16 ou 32 bits, e não um esquema de 8 bits, pois caracteres nulos em texto simples são erros. -Quando a sequência de bytes \`b'\x20\x00'` aparece com frequência, é mais provável que esse seja o caractere de espaço (U+0020) na codificação UTF-16LE, e não o obscuro caractere U+2000 (`EN QUAD`)—seja lá o que for isso. - -É((("Chardet library"))) assim que o pacote https://fpy.li/4-8["Chardet--The Universal Character Encoding Detector (_Chardet—O Detector Universal de Codificações de Caracteres_)"] trabalha para descobrir cada uma das mais de 30 codificações suportadas. -_Chardet_ é uma biblioteca Python que pode ser usada em seus programas, mas que também inclui um utilitário de comando de linha, `chardetect`. -Aqui está a forma como ele analisa o código fonte desse capítulo: - -[source,bash] ----- -$ chardetect 04-text-byte.asciidoc -04-text-byte.asciidoc: utf-8 with confidence 0.99 ----- - -Apesar de sequências binárias de texto codificado normalmente não trazerem dicas sobre sua codificação, os formatos UTF podem preceder o conteúdo textual por um marcador de ordem dos bytes. Isso é explicado a seguir. - - -==== BOM: um gremlin útil - -No((("BOMs (byte-order marks)"))) <>, você pode ter notado um par de bytes extra no início de uma sequência codificada em UTF-16. -Aqui estão eles novamente: - -[source,pycon] ----- ->>> u16 = 'El Niño'.encode('utf_16') ->>> u16 -b'\xff\xfeE\x00l\x00 \x00N\x00i\x00\xf1\x00o\x00' ----- - -Os bytes são `b'\xff\xfe'`. Isso é um __BOM__—sigla para byte-order mark (marcador de ordem de bytes)—indicando a ordenação de bytes "little-endian" da CPU Intel onde a codificação foi realizada. - -Em uma máquina _little-endian_, para cada ponto de código, o byte menos significativo aparece primeiro: -a letra `'E'`, ponto de código U+0045 (decimal 69), é codificado nas posições 2 e 3 dos bytes como `69` e `0`: - -[source,pycon] ----- ->>> list(u16) -[255, 254, 69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111, 0] ----- - -Em uma CPU _big-endian_, a codificação seria invertida; `'E'` seria codificado como `0` e `69`. - -Para evitar confusão, a codificação UTF-16 precede o texto a ser codificado com o caractere especial invisível `ZERO WIDTH NO-BREAK SPACE` (U+FEFF). -Em um sistema _little-endian_, isso é codificado como `b'\xff\xfe'` (decimais 255, 254). -Como, por design, não existe um caractere U+FFFE em Unicode, a sequência de bytes `b'\xff\xfe'` tem que ser o `ZERO WIDTH NO-BREAK SPACE` em uma codificação _little-endian_, -e então o codec sabe qual ordenação de bytes usar. - -Há uma variante do UTF-16--o UTF-16LE--que é explicitamente _little-endian_, e outra que é explicitamente _big-endian_, o UTF-16BE. -Se você usá-los, um BOM não será gerado: - -[source,pycon] ----- ->>> u16le = 'El Niño'.encode('utf_16le') ->>> list(u16le) -[69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111, 0] ->>> u16be = 'El Niño'.encode('utf_16be') ->>> list(u16be) -[0, 69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111] ----- - -Se o BOM estiver presente, supõe-se que ele será filtrado pelo codec UTF-16, -então recebemos apenas o conteúdo textual efetivo do arquivo, sem o `ZERO WIDTH NO-BREAK SPACE` inicial. - -O padrão Unicode diz que se um arquivo é UTF-16 e não tem um BOM, -deve-se presumir que ele é UTF-16BE (_big-endian_). -Entretanto, a arquitetura x86 da Intel é _little-endian_, -daí que há uma grande quantidade de UTF-16 _little-endian_ e sem BOM no mundo. - -Toda essa questão de ordenação dos bytes (_endianness_) só afeta codificações que usam palavras com mais de um byte, como UTF-16 e UTF-32. -Uma grande vantagem do UTF-8 é produzir a mesma sequência independente da ordenação dos bytes, então um BOM não é necessário. -No entanto, algumas aplicações Windows (em especial o Notepad) mesmo assim acrescentam o BOM a arquivos UTF-8—e o Excel depende do BOM para detectar um arquivo UTF-8, caso contrário ele presume que o conteúdo está codificado com uma página de código do Windows. -Essa codificação UTF-8 com BOM é chamada((("UTF-8-SIG encoding"))) UTF-8-SIG no registro de codecs do Python. -O caractere U+FEFF codificado em UTF-8-SIG é a sequência de três bytes `b'\xef\xbb\xbf'`. -Então, se um arquivo começa com aqueles três bytes, é provavelmente um arquivo UTF-8 com um BOM. - -[role="man-height-2-25"] -.A dica de Caleb sobre o UTF-8-SIG -[TIP] -==== -Caleb Hattingh—um dos revisores técnicos—sugere sempre usar o codec UTF-8-SIG para ler arquivos UTF-8. Isso é inofensivo, pois o UTF-8-SIG lê corretamente arquivos com ou sem um BOM, e não devolve o BOM propriamente dito. -Para escrever arquivos, recomendo usar UTF-8, para interoperabilidade integral. -Por exemplo, scripts Python podem ser tornados executáveis em sistemas Unix, se começarem com o comentário: `#!/usr/bin/env python3`. -Os dois primeiros bytes do arquivo precisam ser `+b'#!'+` para isso funcionar, mas o BOM rompe essa convenção. -Se você tem o requerimento específico de exportar dados para aplicativos que precisam do BOM, use o UTF-8-SIG, mas esteja ciente do que diz a -https://docs.python.org/pt-br/3/library/codecs.html#encodings-and-unicode[documentação sobre codecs] (EN) do Python: -"No UTF-8, o uso do BOM é desencorajado e, em geral, deve ser evitado." -==== - -Vamos agora ver como tratar arquivos de texto no Python 3.((("", startref="UTVunder04")))((("", startref="Eunderst04")))((("", startref="Dunder04"))) - - -=== Processando arquivos de texto - -A((("Unicode text versus bytes", "handling text files", id="UTVtext04")))((("text files, handling", id="Tfile04"))) melhor prática para lidar com E/S de texto é o((("Unicode sandwich"))) "Sanduíche de Unicode" (_Unicode sandwich_) -(<>).footnote:[A primeira vez que vi o termo "Unicode sandwich" (_sanduíche de Unicode_) foi na excelente apresentação de Ned Batchelder, https://fpy.li/4-10["Pragmatic Unicode" (_Unicode pragmático_) (EN)] na US PyCon 2012.] -Isso significa que os `bytes` devem ser decodificados para `str` o mais cedo possível na entrada (por exemplo, ao abrir um arquivo para leitura). -O "recheio" do sanduíche é a lógica do negócio de seu programa, onde o tratamento do texto é realizado exclusivamente sobre objetos `str`. -Você nunca deveria codificar ou decodificar no meio de outro processamento. Na saída, as `str` são codificadas para `bytes` o mais tarde possível. -A maioria das frameworks web funcona assim, e raramente encostamos em `bytes` ao usá-las. -No Django, por exemplo, suas views devem produzir `str` em Unicode; o próprio Django se encarrega de codificar a resposta para `bytes`, usando UTF-8 como default. - -O Python 3 torna mais fácil seguir o conselho do sanduíche de Unicode, pois o embutido `open()` executa a decodificação necessária na leitura e a codificação ao escrever arquivos em modo texto. Dessa forma, tudo que você recebe de `my_file.read()` e passa para `my_file.write(text)` são objetos `str`. - -Assim, usar arquivos de texto é aparentemente simples. -Mas se você confiar nas codificações default, pode acabar levando uma mordida. - -[[unicode_sandwich_fig]] -.O sanduíche de Unicode: melhores práticas atuais para processamento de texto. -image::images/flpy_0402.png[Diagrama do sanduíche de Unicode] - -Observe a sessão de console no <>. Você consegue ver o erro? - -[[ex_cafe_file1]] -.Uma questão de plataforma na codificação (você pode ou não ver o problema se tentar isso na sua máquina) -==== -[source, pycon] ----- ->>> open('cafe.txt', 'w', encoding='utf_8').write('café') -4 ->>> open('cafe.txt').read() -'café' ----- -==== - -O erro: especifiquei a codificação UTF-8 ao escrever o arquivo, mas não fiz isso na leitura, então o Python assumiu a codificação de arquivo default do Windows—página de código 1252—e os bytes finais foram decodificados como os caracteres `'é'` ao invés de `'é'`. - -Executei o <> no Python 3.8.1, 64 bits, no Windows 10 (build 18363). -Os mesmos comandos rodando em um GNU/Linux ou um macOS recentes funcionam perfeitamente, pois a codificação default desses sistemas é UTF-8, dando a falsa impressão que tudo está bem. -Se o argumento de codificação fosse omitido ao abrir o arquivo para escrita, a codificação default do locale seria usada, e poderíamos ler o arquivo corretamente usando a mesma codificação. -Mas aí o script geraria arquivos com conteúdo binário diferente dependendo da plataforma, ou mesmo das configurações do locale na mesma plataforma, criando problemas de compatibilidade. - -[TIP] -==== -Código que precisa rodar em múltiplas máquinas ou múltiplas ocasiões não deveria jamais depender de defaults de codificação. -Sempre passe um argumento `encoding=` explícito ao abrir arquivos de texto, pois o default pode mudar de uma máquina para outra ou de um dia para o outro. -==== - -Um detalhe curioso no <> é que a função `write` na primeira instrução informa que foram escritos quatro caracteres, mas na linha seguinte são lidos cinco caracteres. -O <> é uma versão estendida do <>, e explica esse e outros detalhes. - -[[ex_cafe_file2]] -.Uma inspeção mais atenta do <> rodando no Windows revela o bug e a solução do problema -==== -[source, pycon] ----- ->>> fp = open('cafe.txt', 'w', encoding='utf_8') ->>> fp # <1> -<_io.TextIOWrapper name='cafe.txt' mode='w' encoding='utf_8'> ->>> fp.write('café') # <2> -4 ->>> fp.close() ->>> import os ->>> os.stat('cafe.txt').st_size # <3> -5 ->>> fp2 = open('cafe.txt') ->>> fp2 # <4> -<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='cp1252'> ->>> fp2.encoding # <5> -'cp1252' ->>> fp2.read() # <6> -'café' ->>> fp3 = open('cafe.txt', encoding='utf_8') # <7> ->>> fp3 -<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='utf_8'> ->>> fp3.read() # <8> -'café' ->>> fp4 = open('cafe.txt', 'rb') # <9> ->>> fp4 # <10> -<_io.BufferedReader name='cafe.txt'> ->>> fp4.read() # <11> -b'caf\xc3\xa9' ----- -==== -<1> Por default, `open` usa o modo texto e devolve um objeto `TextIOWrapper` com uma codificação específica. -<2> O método `write` de um `TextIOWrapper` devolve o número de caracteres Unicode escritos. -<3> `os.stat` diz que o arquivo tem 5 bytes; o UTF-8 codifica `'é'` com 2 bytes, 0xc3 e 0xa9. -<4> Abrir um arquivo de texto sem uma codificação explícita devolve um `TextIOWrapper` com a codificação configurada para um default do locale. -<5> Um objeto `TextIOWrapper` tem um atributo de codificação que pode ser inspecionado: neste caso, `cp1252`. -<6> Na codificação `cp1252` do Windows, o byte 0xc3 é um "Ã" (A maiúsculo com til), e 0xa9 é o símbolo de copyright. -<7> Abrindo o mesmo arquivo com a codificação correta. -<8> O resultado esperado: os mesmo quatro caracteres Unicode para `'café'`. -<9> A flag `'rb'` abre um arquivo para leitura em modo binário. -<10> O objeto devolvido é um `BufferedReader`, e não um `TextIOWrapper`. -<11> Ler do arquivo obtém bytes, como esperado. - -[TIP] -==== -Não abra arquivos de texto no modo binário, a menos que seja necessário analisar o conteúdo do arquivo para determinar sua codificação—e mesmo assim, você deveria estar usando o Chardet em vez de reinventar a roda (veja a seção <>). - -Programas comuns só deveriam usar o modo binário para abrir arquivos binários, como arquivos de imagens raster ou bitmaps. -==== - -O problema no <> vem de se confiar numa configuração default ao se abrir um arquivo de texto. -Há várias fontes de tais defaults, como mostra a próxima seção. - -[[encoding_defaults]] -==== Cuidado com os defaults de codificação - -Várias((("encoding", "encoding defaults", id="Edefault04"))) configurações afetam os defaults de codificação para E/S no Python. -Veja o script __default_encodings.py__ script no <>. - -[[ex_default_encodings]] -.Explorando os defaults de codificação -==== -[source, python3] ----- -include::code/04-text-byte/default_encodings.py[] ----- -==== - -A saída do <> no GNU/Linux (Ubuntu 14.04 a 19.10) e no macOS (10.9 a 10.14) é idêntica, mostrando que `UTF-8` é usado em toda parte nesses sistemas: - -[source, bash] ----- -$ python3 default_encodings.py - locale.getpreferredencoding() -> 'UTF-8' - type(my_file) -> - my_file.encoding -> 'UTF-8' - sys.stdout.isatty() -> True - sys.stdout.encoding -> 'utf-8' - sys.stdin.isatty() -> True - sys.stdin.encoding -> 'utf-8' - sys.stderr.isatty() -> True - sys.stderr.encoding -> 'utf-8' - sys.getdefaultencoding() -> 'utf-8' - sys.getfilesystemencoding() -> 'utf-8' ----- - -No Windows, porém, a saída é o <>. - -[[ex_default_encodings_ps]] -.Codificações default, no PowerShell do Windows 10 (a saída é a mesma no cmd.exe) -==== -[source, bash] ----- -> chcp <1> -Active code page: 437 -> python default_encodings.py <2> - locale.getpreferredencoding() -> 'cp1252' <3> - type(my_file) -> - my_file.encoding -> 'cp1252' <4> - sys.stdout.isatty() -> True <5> - sys.stdout.encoding -> 'utf-8' <6> - sys.stdin.isatty() -> True - sys.stdin.encoding -> 'utf-8' - sys.stderr.isatty() -> True - sys.stderr.encoding -> 'utf-8' - sys.getdefaultencoding() -> 'utf-8' - sys.getfilesystemencoding() -> 'utf-8' ----- -==== -<1> `chcp` mostra a página de código ativa para o console: `437`. -<2> Executando __default_encodings.py__, com a saída direcionada para o console. -<3> `locale.getpreferredencoding()` é a configuração mais importante. -<4> Arquivos de texto usam`locale.getpreferredencoding()` por default. -<5> A saída está direcionada para o console, então `sys.stdout.isatty()` é `True`. -<6> Agora, `sys.stdout.encoding` não é a mesma que a página de código informada por `chcp`! - -O suporte a Unicode no próprio Windows e no Python para Windows melhorou desde que escrevi a primeira edição deste livro. -O <> costumava informar quatro codificações diferentes no Python 3.4 rodando no Windows 7. -As codificações para `stdout`, `stdin`, e `stderr` costumavam ser iguais à da página de código ativa informada pelo comando `chcp`, mas agora são todas `utf-8`, graças à https://fpy.li/pep528[PEP 528--Change Windows console encoding to UTF-8 (_Mudar a codificação do console no Windows para UTF-8_)] (EN), implementada no Python 3.6, e ao suporte a Unicode no PowerShell do _cmd.exe_ (desde o Windows 1809, de outubro de 2018).footnote:[Fonte: https://fpy.li/4-11["Windows Command-Line: Unicode and UTF-8 Output Text Buffer" (_A Linha de Comando do Windows: O Buffer de Saída de Texto para Unicode e UTF-8_)].] -É esquisito que o `chcp` e o `sys.stdout.encoding` reportem coisas diferentes quando o `stdout` está escrevendo no console, mas é ótimo podermos agora escrever strings Unicode sem erros de codificação no Windows—a menos que o usuário redirecione a saída para um arquivo, como veremos adiante. -Isso não significa que todos os seus emojis((("emojis", "console font and"))) favoritos vão aparecer: isso também depende da fonte usada pelo console. - -Outra mudança foi a https://fpy.li/pep529[PEP 529--Change Windows filesystem encoding to UTF-8 (_Mudar a codificação do sistema de arquivos do Windows para UTF-8_)], também implementada no Python 3.6, que modificou a codificação do sistema de arquivos (usada para representar nomes de diretórios e de arquivos), da codificação proprietária MBCS da Microsoft para UTF-8. - -Entretanto, se a saída do <> for redirecionada para um arquivo, assim... - -[source, bash] ----- -Z:\>python default_encodings.py > encodings.log ----- - -...aí o valor de `sys.stdout.isatty()` se torna `False`, e `sys.stdout.encoding` é determinado por `locale.getpreferredencoding()`, `'cp1252'` naquela máquina—mas `sys.stdin.encoding` e `sys.stderr.encoding` seguem como `utf-8`. - - -[TIP] -==== -No((("\N{} (Unicode literals escape notation)")))((("Unicode literals escape notation (\N{})"))) <>, usei a expressão de escape`'\N{}'` para literais Unicode, escrevendo o nome oficial do caractere dentro do `\N{}`. -Isso é bastante prolixo, mas explícito e seguro: o Python gera um `SyntaxError` se o nome não existir—bem melhor que escrever um número hexadecimal que pode estar errado, mas isso só será descoberto muito mais tarde. -De qualquer forma, você provavelmente vai querer escrever um comentário explicando os códigos dos caracteres, então a verbosidade do `\N{}` é fácil de aceitar. -==== - -Isso significa que um script como o <> funciona quando está escrevendo no console, mas pode falhar quando a saída é redirecionada para um arquivo. - -[[ex_stdout_check]] -.stdout_check.py -==== -[source, python3] ----- -include::code/04-text-byte/stdout_check.py[] ----- -==== - -O <> mostra o resultado de uma chamada a `sys.stdout.isatty()`, o valor de pass:[sys.​stdout.encoding], e esses três caracteres: - -* `'…'` `HORIZONTAL ELLIPSIS`—existe no CP 1252 mas não no CP 437. -* `'∞'` `INFINITY`—existe no CP 437 mas não no CP 1252. -* `'㊷'` `CIRCLED NUMBER FORTY TWO`—não existe nem no CP 1252 nem no CP 437. - -Quando executo o _stdout_check.py_ no PowerShell ou no _cmd.exe_, funciona como visto na <>. - -[[fig_stdout_check]] -.Executando _stdout_check.py_ no PowerShell. -image::images/flpy_0403.png[Captura de tela do `stdout_check.py` no PowerShell] - -[role="pagebreak-before less_space"] -Apesar de `chcp` informar o código ativo como 437, `sys.stdout.encoding` é UTF-8, então tanto `HORIZONTAL ELLIPSIS` quanto `INFINITY` são escritos corretamente. -O `CIRCLED NUMBER FORTY TWO` é substituído por um retângulo, mas nenhum erro é gerado. -Presume-se que ele seja reconhecido como um caractere válido, mas a fonte do console não tem o glifo para mostrá-lo. - - -Entretanto, quando redireciono a saída de _stdout_check.py_ para um arquivo, o resultado é o da <>. - -[[fig_stdout_check_redir]] -.Executanto _stdout_check.py_ no PowerShell, redirecionando a saída. -image::images/flpy_0404.png["Captura de teal do `stdout_check.py` no PowerShell, redirecionando a saída"] - -O primeiro problema demonstrado pela <> é o `UnicodeEncodeError` mencionando o caractere `'\u221e'`, -porque `sys.stdout.encoding` é `'cp1252'`—uma página de código que não tem o caractere `INFINITY`. - -Lendo _out.txt_ com o comando `type`—ou um editor de Windows como o VS Code ou o Sublime Text—mostra que, ao invés do HORIZONTAL ELLIPSIS, consegui um `'à'` (`LATIN SMALL LETTER A WITH GRAVE`). -Acontece que o valor binário 0x85 no CP 1252 significa `'…'`, mas no CP 437 o mesmo valor binário representa o `'à'`. -Então, pelo visto, a página de código ativa tem alguma importância, não de uma forma razoável ou útil, mas como uma explicação parcial para uma experiência ruim com o Unicode. - - -[NOTE] -==== -Para realizar esses experimentos, usei um laptop configurado para o mercado norte-americano, rodando Windows 10 OEM. Versões de Windows localizadas para outros países podem ter configurações de codificação diferentes. No Brasil, por exemplo, o console do Windows usa a página de código 850 por default--e não a 437. -==== - -Para encerrar esse enlouquecedor tópico de codificações default, vamos dar uma última olhada nas diferentes codificações no <>: - -* Se você omitir o argumento `encoding` ao abrir um arquivo, o default é dado por `locale.getpreferredencoding()` (`'cp1252'` no <>). - -* Antes do Python 3.6, a codificação de `sys.stdout|stdin|stderr` costumava ser determinada pela variável do ambiente https://docs.python.org/pt-br/3/using/cmdline.html#envvar-PYTHONIOENCODING[`PYTHONIOENCODING`]—agora essa variável é ignorada, -a menos que https://docs.python.org/pt-br/3/using/cmdline.html#envvar-PYTHONLEGACYWINDOWSSTDIO[`PYTHONLEGACYWINDOWSSTDIO`] seja definida como uma string não-vazia. -Caso contrário, a codificação da E/S padrão será UTF-8 para E/S interativa, ou definida por -`locale.getpreferredencoding()`, se a entrada e a saída forem redirecionadas para ou de um arquivo. - -* `sys.getdefaultencoding()` é usado internamente pelo Python em conversões implícitas de dados binários de ou para `str`. -Não há suporte para mudar essa configuração. - -* `sys.getfilesystemencoding()` é usado para codificar/decodificar nomes de arquivo (mas não o conteúdo dos arquivos). -Ele é usado quando `open()` recebe um argumento `str` para um nome de arquivo; -se o nome do arquivo é passado como um argumento `bytes`, ele é entregue sem modificação para a API do sistema operacional. - -[NOTE] -==== -Já faz muito anos que, no GNU/Linux e no macOS, todas essas codificações são definidas como UTF-8 por default, -então a E/S entende e exibe todos os caracteres Unicode. -No Windows, não apenas codificações diferentes são usadas no mesmo sistema, -elas também são, normalmente, páginas de código como `'cp850'` ou `'cp1252'`, que suportam só o ASCII -com 127 caracteres adicionais (que por sua vez são diferentes de uma codificação para a outra). -Assim, usuários de Windows tem muito mais chances de cometer erros de codificação, a menos que sejam muito cuidadosos. -==== - -Resumindo, a configuração de codificação mais importante devolvida por `locale.getpreferredencoding()` é a default para abrir arquivos de texto e para `sys.stdout/stdin/stderr`, quando eles são redirecionados para arquivos. -Entretanto, a https://docs.python.org/pt-br/3/library/locale.html#locale.getpreferredencoding[documentação] diz (em parte): - -[quote] -____ -`locale.getpreferredencoding(do_setlocale=True)`:: Retorna a codificação da localidade usada para dados de texto, de acordo com as preferências do usuário. As preferências do usuário são expressas de maneira diferente em sistemas diferentes e podem não estar disponíveis programaticamente em alguns sistemas, portanto, essa função retorna apenas uma estimativa. [...] -____ - -Assim, o melhor conselho sobre defaults de codificação é: não confie neles. - -Você evitará muitas dores de cabeça se seguir o conselho do sanduíche de Unicode, e sempre tratar codificações de forma explícita em seus programas. -Infelizmente, o Unicode é trabalhoso mesmo se você converter seus `bytes` para `str` corretamente. -As duas próximas seções tratam de assuntos que são simples no reino do ASCII, -mas ficam muito complexos no planeta Unicode: normalização de texto (isto é, transformar o texto em uma representação uniforme para comparações) e ordenação.((("", startref="Edefault04")))((("", startref="Tfile04")))((("", startref="UTVtext04"))) - - -[[normalizing_unicode]] -=== Normalizando o Unicode para comparações confiáveis - -Comparações de strings((("Unicode text versus bytes", "normalizing Unicode for reliable comparisons", id="UTVnormal04")))((("strings", "normalizing Unicode for reliable comparisons", id="Snormal04"))) são dificultadas pelo fato do Unicode ter combinações de caracteres: -sinais diacríticos e outras marcações que são anexadas aos caractere anterior, ambos aparecendo juntos como um só caractere quando impressos. - -Por exemplo, a palavra "café" pode ser composta de duas formas, -usando quatro ou cinco pontos de código, mas o resultado parece exatamente o mesmo: - -[source, pycon] ----- ->>> s1 = 'café' ->>> s2 = 'cafe\N{COMBINING ACUTE ACCENT}' ->>> s1, s2 -('café', 'café') ->>> len(s1), len(s2) -(4, 5) ->>> s1 == s2 -False ----- - -Colocar `COMBINING ACUTE ACCENT` (U+0301) após o "e" resulta em "é". -No padrão Unicode, sequências como `'é'` e `'e\u0301'` são chamadas de "equivalentes canônicas", -e se espera que as aplicações as tratem como iguais. Mas o Python vê duas sequências de pontos de código diferentes, e não as considera iguais. - -A solução é a `unicodedata.normalize()`. -O primeiro argumento para essa função é uma dessas quatro strings: `'NFC'`, `'NFD'`, `'NFKC'`, e `'NFKD'`. -Vamos começar pelas duas primeiras. - -A Forma Normal C (NFC)((("Normalization Form C (NFC)"))) combina os ponto de código para produzir a string equivalente mais curta, enquanto a NFD decompõe, expandindo os caracteres compostos em caracteres base e separando caracteres combinados. -Ambas as normalizações fazem as comparações funcionarem da forma esperada, como mostra o próximo exemplo: - -[source, pycon] ----- ->>> from unicodedata import normalize ->>> s1 = 'café' ->>> s2 = 'cafe\N{COMBINING ACUTE ACCENT}' ->>> len(s1), len(s2) -(4, 5) ->>> len(normalize('NFC', s1)), len(normalize('NFC', s2)) -(4, 4) ->>> len(normalize('NFD', s1)), len(normalize('NFD', s2)) -(5, 5) ->>> normalize('NFC', s1) == normalize('NFC', s2) -True ->>> normalize('NFD', s1) == normalize('NFD', s2) -True ----- - -Drivers de teclado normalmente geram caracteres compostos, então o texto digitado pelos usuários estará na NFC por default. Entretanto, por segurança, pode ser melhor normalizar as strings com `normalize('NFC', user_text)` antes de salvá-las. -A NFC também é a forma de normalização recomendada pelo W3C em -https://fpy.li/4-15["Character Model for the World Wide Web: String Matching and Searching" (_Um Modelo de Caracteres para a World Wide Web: Correspondência de Strings e Busca_)] (EN). - -Alguns caracteres singulares são normalizados pela NFC em um outro caractere singular. O símbolo para o ohm (Ω), a unidade de medida de resistência elétrica, é normalizado para a letra grega ômega maiúscula. Eles são visualmente idênticos, mas diferentes quando comparados, então a normalizaçào é essencial para evitar surpresas: - -[source, pycon] ----- ->>> from unicodedata import normalize, name ->>> ohm = '\u2126' ->>> name(ohm) -'OHM SIGN' ->>> ohm_c = normalize('NFC', ohm) ->>> name(ohm_c) -'GREEK CAPITAL LETTER OMEGA' ->>> ohm == ohm_c -False ->>> normalize('NFC', ohm) == normalize('NFC', ohm_c) -True ----- - -As outras duas formas de normalização são a NFKC e a NFKD, a letra K significando "compatibilidade". -Essas são formas mais fortes de normalizaçào, afetando os assim chamados "caracteres de compatibilidade". -Apesar de um dos objetivos do Unicode ser a existência de um único ponto de código "canônico" para cada caractere, alguns caracteres aparecem mais de uma vez, para manter compatibilidade com padrões pré-existentes. -Por exemplo, o `MICRO SIGN`, `µ` (`U+00B5`), foi adicionado para permitir a conversão bi-direcional com o `latin1`, que o inclui, apesar do mesmo caractere ser parte do alfabeto grego com o ponto de código `U+03BC` (`GREEK SMALL LETTER MU`). -Assim, o símbolo de micro é considerado um "caractere de compatibilidade". - -Nas formas NFKC e NFKD, cada caractere de compatibilidade é substituído por uma "decomposição de compatibilidade" de um ou mais caracteres, que é considerada a representação "preferencial", mesmo se ocorrer alguma perda de formatação—idealmente, a formatação deveria ser responsabilidade de alguma marcação externa, não parte do Unicode. Para exemplificar, a decomposição de compatibilidade da fração um meio, `'½'` (`U+00BD`), é a sequência de três caracteres `'1/2'`, e a decomposição de compatibilidade do símbolo de micro, `'µ'` (`U+00B5`), é o mu minúsculo, `'μ'` (`U+03BC`).footnote:[Curiosamente, o símbolo de micro é considerado um "caractere de compatibilidade", mas o símbolo de ohm não. O resultado disso é que a NFC não toca no símbolo de micro, mas muda o símbolo de ohm para ômega maiúsculo, ao passo que a NFKC e a NFKD mudam tanto o ohm quanto o micro para caracteres gregos.] - - -É assim que a NFKC funciona na prática: - -[source, pycon] ----- ->>> from unicodedata import normalize, name ->>> half = '\N{VULGAR FRACTION ONE HALF}' ->>> print(half) -½ ->>> normalize('NFKC', half) -'1⁄2' ->>> for char in normalize('NFKC', half): -... print(char, name(char), sep='\t') -... -1 DIGIT ONE -⁄ FRACTION SLASH -2 DIGIT TWO ->>> four_squared = '4²' ->>> normalize('NFKC', four_squared) -'42' ->>> micro = 'µ' ->>> micro_kc = normalize('NFKC', micro) ->>> micro, micro_kc -('µ', 'μ') ->>> ord(micro), ord(micro_kc) -(181, 956) ->>> name(micro), name(micro_kc) -('MICRO SIGN', 'GREEK SMALL LETTER MU') ----- - -Ainda que `'1⁄2'` seja um substituto razoável para `'½'`, -e o símbolo de micro ser realmente a letra grega mu minúscula, converter `'4²'` para `'42'` muda o sentido. -Uma aplicação poderia armazenar `'4²'` como `'42'`, -mas a função `normalize` não sabe nada sobre formatação. -Assim, NFKC ou NFKD podem perder ou distorcer informações, -mas podem produzir representações intermediárias convenientes para buscas ou indexação. - -Infelizmente, com o Unicode tudo é sempre mais complicado do que parece à primeira vista. -Para o `VULGAR FRACTION ONE HALF`, a normalização NFKC produz 1 e 2 unidos pelo `FRACTION SLASH`, -em vez do `SOLIDUS`, também conhecido como "barra" ("slash" em inglês)—o familiar caractere com código decimal 47 em ASCII. -Portanto, buscar pela sequência ASCII de três caracteres `'1/2'` não encontraria a sequência Unicode normalizada. - -[WARNING] -==== -As normalizações NFKC e NFKD causam perda de dados e devem ser aplicadas apenas em casos especiais, como busca e indexação, e não para armazenamento permanente do texto. -==== - -Ao preparar texto para busca ou indexação, há outra operação útil: _case folding_ footnote:[NT: algo como "dobra" ou "mudança" de caixa.], nosso próximo assunto. - - -==== Case Folding - -_Case folding_((("case folding"))) é essencialmente a conversão de todo o texto para minúsculas, com algumas transformações adicionais. A operação é suportada pelo método `str.casefold()`. - -Para qualquer string `s` contendo apenas caracteres `latin1`, `s.casefold()` produz o mesmo resultado de `s.lower()`, com apenas duas exceções—o símbolo de micro, `'µ'`, é trocado pela letra grega mu minúscula (que é exatamente igual na maioria das fontes) e a letra alemã _Eszett_ (ß), também chamada "s agudo" (_scharfes S_) se torna "ss": - -[source, pycon] ----- ->>> micro = 'µ' ->>> name(micro) -'MICRO SIGN' ->>> micro_cf = micro.casefold() ->>> name(micro_cf) -'GREEK SMALL LETTER MU' ->>> micro, micro_cf -('µ', 'μ') ->>> eszett = 'ß' ->>> name(eszett) -'LATIN SMALL LETTER SHARP S' ->>> eszett_cf = eszett.casefold() ->>> eszett, eszett_cf -('ß', 'ss') ----- - -Há quase 300 pontos de código para os quais `str.casefold()` e `str.lower()` devolvem resultados diferentes. - -Como acontece com qualquer coisa relacionada ao Unicode, _case folding_ é um tópico complexo, com muitos casos linguísticos especiais, mas o grupo central de desenvolvedores do Python fez um grande esforço para apresentar uma solução que, espera-se, funcione para a maioria dos usuários. - -Nas próximas seções vamos colocar nosso conhecimento sobre normalização para trabalhar, desenvolvendo algumas funções utilitárias. - - -==== Funções utilitárias para correspondência de texto normalizado - -Como((("normalized text matching", id="normtext04"))) vimos, é seguro usar a NFC e a NFD, e ambas permitem comparações razoáveis entre strings Unicode. A NFC é a melhor forma normalizada para a maioria das aplicações, e `str.casefold()` é a opção certa para comparações indiferentes a maiúsculas/minúsculas. - -Se você precisa lidar com texto em muitas línguas diferentes, seria muito útil acrescentar às suas ferramentas de trabalho um par de funções como `nfc_equal` e `fold_equal`, do <>. - -[[ex_normeq]] -.normeq.py: normalized Unicode string comparison -==== -[source, python3] ----- -include::code/04-text-byte/normeq.py[] ----- -==== - -Além da normalização e do _case folding_ do Unicode—ambos partes desse padrão—algumas vezes faz sentido aplicar transformações mais profundas, como por exemplo mudar `'café'` para `'cafe'`. Vamos ver quando e como na próxima seção. - - - -==== "Normalização" extrema: removendo sinais diacríticos - -O((("diacritics, normalization and", id="diacritics04"))) tempero secreto da busca do Google inclui muitos truques, mas um deles aparentemente é ignorar sinais diacríticos (acentos e cedilhas, por exemplo), pelo menos em alguns contextos. Remover sinais diacríticos não é uma forma regular de normalização, pois muitas vezes muda o sentido das palavras e pode produzir falsos positivos em uma busca. Mas ajuda a lidar com alguns fatos da vida: as pessoas às vezes são preguiçosas ou desconhecem o uso correto dos sinais diacríticos, e regras de ortografia mudam com o tempo, levando acentos a desaparecerem e reaparecerem nas línguas vivas. - -Além do caso da busca, eliminar os acentos torna as URLs mais legíveis, pelo menos nas línguas latinas. Veja a URL do artigo da Wikipedia sobre a cidade de São Paulo: - ----- -https://en.wikipedia.org/wiki/S%C3%A3o_Paulo ----- - -O trecho `%C3%A3` é a renderização em UTF-8 de uma única letra, o "ã" ("a" com til). A forma a seguir é muito mais fácil de reconhecer, mesmo com a ortografia incorreta: - ----- -https://en.wikipedia.org/wiki/Sao_Paulo ----- - -Para remover todos os sinais diacríticos de uma `str`, você pode usar uma função como a do <>. - -[[ex_shave_marks]] -.simplify.py: função para remover todas as marcações combinadas -==== -[source, py] ----- -include::code/04-text-byte/simplify.py[tags=SHAVE_MARKS] ----- -==== -<1> Decompõe todos os caracteres em caracteres base e marcações combinadas. -<2> Filtra e retira todas as marcações combinadas. -<3> Recompõe todos os caracteres. - - -<> mostra alguns usos para `shave_marks`. - -[[ex_shave_marks_demo]] -.Dois exemplos de uso da `shave_marks` do <> -==== -[source, pycon] ----- ->>> order = '“Herr Voß: • ½ cup of Œtker™ caffè latte • bowl of açaí.”' ->>> shave_marks(order) -'“Herr Voß: • ½ cup of Œtker™ caffe latte • bowl of acai.”' <1> ->>> Greek = 'Ζέφυρος, Zéfiro' ->>> shave_marks(Greek) -'Ζεφυρος, Zefiro' <2> ----- -==== -<1> Apenas as letras "è", "ç", e "í" foram substituídas. -<2> Tanto "έ" quando "é" foram substituídas. - -A função `shave_marks` do <> funciona bem, mas talvez vá longe demais. Frequentemente, a razão para remover os sinais diacríticos é transformar texto de uma língua latina para ASCII puro, mas `shave_marks` também troca caracteres não-latinos--como letras gregas--que nunca se tornarão ASCII apenas pela remoção de seus acentos. Então faz sentido analisar cada caractere base e remover as marcações anexas apenas se o caractere base for uma letra do alfabeto latino. É isso que o <> faz. - -[[ex_shave_marks_latin]] -.Função para remover marcações combinadas de caracteres latinos (comando de importação omitidos, pois isso é parte do módulo simplify.py do <>) -==== -[source, py] ----- -include::code/04-text-byte/simplify.py[tags=SHAVE_MARKS_LATIN] ----- -==== -<1> Decompõe todos os caracteres em caracteres base e marcações combinadas. -<2> Pula as marcações combinadas quando o caractere base é latino. -<3> Caso contrário, mantém o caractere original. -<4> Detecta um novo caractere base e determina se ele é latino. -<5> Recompõe todos os caracteres. - -Um passo ainda mais radical substituiria os símbolos comuns em textos de línguas ocidentais (por exemplo, aspas curvas, travessões, os círculos de _bullet points_, etc) em seus equivalentes `ASCII`. É isso que a função `asciize` faz no <>. - -[[ex_asciize]] -.Transforma alguns símbolos tipográficos ocidentais em ASCII (este trecho também é parte do simplify.py do <>) -==== -[source, py] ----- -include::code/04-text-byte/simplify.py[tags=ASCIIZE] ----- -==== -<1> Cria uma tabela de mapeamento para substituição de caractere para caractere. -<2> Cria uma tabela de mapeamento para substituição de string para caractere. -<3> Funde as tabelas de mapeamento. -<4> `dewinize` não afeta texto em `ASCII` ou `latin1`, apenas os acréscimos da Microsoft ao `latin1` no `cp1252`. -<5> Aplica `dewinize` e remove as marcações de sinais diacríticos. -<6> Substitui o _Eszett_ por "ss" (não estamos usando _case folding_ aqui, pois queremos preservar maiúsculas e minúsculas). -<7> Aplica a normalização NFKC para compor os caracteres com seus pontos de código de compatibilidade. - -O <> mostra a `asciize` em ação. - -[[ex_asciize_demo]] -.Dois exemplos usando `asciize`, do <> -==== -[source, pycon] ----- ->>> order = '“Herr Voß: • ½ cup of Œtker™ caffè latte • bowl of açaí.”' ->>> dewinize(order) -'"Herr Voß: - ½ cup of OEtker(TM) caffè latte - bowl of açaí."' <1> ->>> asciize(order) -'"Herr Voss: - 1⁄2 cup of OEtker(TM) caffe latte - bowl of acai."' <2> ----- -==== -[role="pagebreak-before less_space"] -<1> `dewinize` substitui as aspas curvas, os _bullets_, e o ™ (símbolo de marca registrada). -<2> `asciize` aplica `dewinize`, remove os sinais diacríticos e substitui o `'ß'`. - -[WARNING] -==== -Cada língua tem suas próprias regras para remoção de sinais diacríticos. Por exemplo, os alemães trocam o `'ü'` por `'ue'`. Nossa função `asciize` não é tão refinada, então pode ou não ser adequada para a sua língua. Contudo, ela é aceitável para o português. -==== - -Resumindo, as funções em _simplify.py_ vão bem além da normalização padrão, e realizam um cirurgia profunda no texto, com boas chances de mudar seu sentido. Só você pode decidir se deve ir tão longe, conhecendo a língua alvo, os seus usuários e a forma como o texto transformado será utilizado. - -Isso conclui nossa discussão sobre normalização de texto Unicode. - -Vamos agora ordenar nossos pensamentos sobre ordenação no Unicode.((("", startref="UTVnormal04")))((("", startref="Snormal04")))((("", startref="normtext04")))((("", startref="diacritics04"))) - - -[[sorting_unicode_sec]] -=== Ordenando texto Unicode - -O Python((("Unicode text versus bytes", "sorting Unicode text", id="UTVsort04"))) ordena sequências de qualquer tipo comparando um por um os itens em cada sequência. Para strings, isso significa comparar pontos de código. Infelizmente, isso produz resultados inaceitáveis para qualquer um que use caracteres não-ASCII. - -Considere ordenar uma lista de frutas cultivadas no Brazil: - -[source, pycon] ----- ->>> fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola'] ->>> sorted(fruits) -['acerola', 'atemoia', 'açaí', 'caju', 'cajá'] ----- - -As regras de ordenação variam entre diferentes locales, mas em português e em muitas línguas que usam o alfabeto latino, acentos e cedilhas raramente fazem diferença na ordenação.footnote:[Sinais diacríticos afetam a ordenação apenas nos raros casos em que eles são a única diferennça entre duas palavras—nesse caso, a palavra com o sinal diacrítico é colocada após a palavra sem o sinal na ordenação.] Então "cajá" é lido como "caja," e deve vir antes de "caju." - -A lista `fruits` ordenada deveria ser: - -[source, python3] ----- -['açaí', 'acerola', 'atemoia', 'cajá', 'caju'] ----- - -O modo padrão de ordenar texto não-ASCII em Python é usar a função `locale.strxfrm` que, de acordo com a -https://docs.python.org/pt-br/3/library/locale.html?highlight=strxfrm#locale.strxfrm[documentação do módulo `locale`], "Transforma uma string em uma que pode ser usada em comparações com reconhecimento de localidade." - -Para poder usar `locale.strxfrm`, você deve primeiro definir um locale adequado para sua aplicação, e rezar para que o SO o suporte. A sequência de comando no <> pode funcionar para você. - -[[ex_locale_sort]] -._locale_sort.py_: Usando a função `locale.strxfrm` como chave de ornenamento -==== -[source, python3] ----- -include::code/04-text-byte/locale_sort.py[] ----- -==== - -Executando o <> no GNU/Linux (Ubuntu 19.10) com o locale `pt_BR.UTF-8` instalado, consigo o resultado correto: - -[source, python3] ----- - -'pt_BR.UTF-8' -['açaí', 'acerola', 'atemoia', 'cajá', 'caju'] ----- - -Portanto, você precisa chamar `setlocale(LC_COLLATE, «your_locale»)` antes de usar pass:[locale.strxfrm] como a chave de ordenação. - -Porém, aqui vão algumas ressalvas: - -* Como as configurações de locale são globais, não é recomendado chamar `setlocale` em uma biblioteca. Sua aplicação ou framework deveria definir o locale no início do processo, e não mudá-lo mais depois disso. - -* O locale desejado deve estar instalado no SO, caso contrário `setlocale` gera uma exceção de `locale.Error: unsupported locale setting`. - -* Você tem que saber como escrever corretamente o nome do locale. - -* O locale precisa ser corretamente implementado pelos desenvolvedores do SO. Tive sucesso com o Ubuntu 19.10, mas não no macOS 10.14. No macOS, a chamada `setlocale(LC_COLLATE, 'pt_BR.UTF-8')` devolve a string `'pt_BR.UTF-8'` sem qualquer reclamação. Mas `sorted(fruits, key=locale.strxfrm)` produz o mesmo resultado incorreto de `sorted(fruits)`. Também tentei os locales `fr_FR`, `es_ES`, e `de_DE` no macOS, mas `locale.strxfrm` nunca fez seu trabalho direito.footnote:[De novo, eu não consegui encontrar uma solução, mas encontrei outras pessoas relatando o mesmo problema. Alex Martelli, um dos revisores técnicos, não teve problemas para usar `setlocale` e `locale.strxfrm` em seu Macintosh com o macOS 10.9. Em resumo: cada caso é um caso.] - -Portanto, a solução da biblioteca padrão para ordenação internacionalizada funciona, mas parece ter suporte adequado apenas no GNU/Linux (talvez também no Windows, se você for um especialista). Mesmo assim, ela depende das configurações do locale, criando dores de cabeça na implantação. - -Felizmente, há uma solução mais simples: a((("pyuca library"))) biblioteca _pyuca_, disponível no _PyPI_. - -==== Ordenando com o Algoritmo de Ordenação do Unicode - -James Tauber, contribuidor((("Unicode Collation Algorithm (UCA)"))) muito ativo do Django, deve ter sentido essa nossa mesma dor, e criou a pass:[pyuca], uma implementação integralmente em Python do Algoritmo de Ordenação do Unicode (UCA, sigla em inglês para _Unicode Collation Algorithm_). O <> mostra como ela é fácil de usar. - -[[ex_pyuca_sort]] -.Utilizando o método `pyuca.Collator.sort_key` -==== -[source, pycon] ----- ->>> import pyuca ->>> coll = pyuca.Collator() ->>> fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola'] ->>> sorted_fruits = sorted(fruits, key=coll.sort_key) ->>> sorted_fruits -['açaí', 'acerola', 'atemoia', 'cajá', 'caju'] ----- -==== - -Isso é simples e funciona no GNU/Linux, no macOS, e no Windows, pelo menos com a minha pequena amostra. - -A `pyuca` não leva o locale em consideração. Se você precisar personalizar a ordenação, pode fornecer um caminho para uma tabela própria de ordenação para o construtor `Collator()`. Sem qualquer configuração adicional, a biblioteca usa o https://fpy.li/4-18[_allkeys.txt_], incluído no projeto. Esse arquivo é apenas uma cópia da https://fpy.li/4-19[Default Unicode Collation Element Table (_Tabela Default de Ordenação de Elementos Unicode_)] do _Unicode.org_ . - -.PyICU: A recomendação do Miro para ordenação com Unicode -[TIP] -==== -(O revisor técnico Miroslav Šedivý é um poliglota e um especialista em Unicode. -Eis o que ele escreveu sobre a _pyuca_.) - -A _pyuca_((("PyICU"))) tem um algoritmo de ordenação que não respeita o padrão de ordenação de linguagens individuais. Por exemplo, [a letra] Ä em alemão fica entre o A e o B, enquanto em sueco ela vem depois do Z. -Dê uma olhada na https://fpy.li/4-20[PyICU], -que funciona como locale sem modificar o locale do processo. -Ela também é necessária se você quiser mudar a capitalização de iİ/ıI em turco. -A PyICU inclui uma extensão que precisa ser compilada, então pode ser mais difícil de instalar em alguns sistemas que a _pyuca_, que é toda feita em Python. -==== - -E por sinal, aquela tabela de ordenação é um dos muitos arquivos de dados que formam o banco de dados do Unicode, nosso próximo assunto.((("", startref="UTVsort04"))) - -[[unicodedata_sec]] -=== O banco de dados do Unicode - -O((("Unicode text versus bytes", "Unicode database", id="UTVdatabase04"))) padrão Unicode fornece todo um banco de dados—na forma de vários arquivos de texto estruturados—que inclui não apenas a tabela mapeando pontos de código para nomes de caracteres, mas também metadados sobre os caracteres individuais e como eles se relacionam. Por exemplo, o banco de dados do Unicode registra se um caractere pode ser impresso, se é uma letra, um dígito decimal ou algum outro símbolo numérico. -É assim que os métodos de `str` `isalpha`, `isprintable`, `isdecimal` e `isnumeric` funcionam. -`str.casefold` também usa informação de uma tabela do Unicode. - -[NOTE] -==== -A função `unicodedata.category(char)` devolve uma categoria de `char` com duas letras, do banco de dados do Unicode. -Os métodos de alto nível de `str` são mais fáceis de usar. -Por exemplo, -https://docs.python.org/pt-br/3.10/library/stdtypes.html#str.isalpha[`label.isalpha()`] -devolve `True` se todos os caracteres em `label` -pertencerem a uma das seguintes categorias: `Lm`, `Lt`, `Lu`, `Ll`, or `Lo`. -Para descobrir o que esses códigos significam, veja -https://fpy.li/4-22["General Category"] (EN) no artigo https://fpy.li/4-23["Unicode character property"] (EN) da Wikipedia em inglês. -==== - -[[finding_chars_sec]] -==== Encontrando caracteres por nome - -O((("emojis", "finding characters by name", id="Efind04")))((("characters", "finding Unicode by name", id="Cfinduni04"))) módulo `unicodedata` tem funções para obter os metadados de caracteres, incluindo -`unicodedata.name()`, que devolve o nome oficial do caractere no padrão. -A <> demonstra essa função.footnote:[Aquilo é uma imagem—não uma listagem de código—porque, no momento em que esse capítulo foi escrito, os emojis não tem um bom suporte no sistema de publicação digital da O'Reilly.] - -[[unicodedata_name_fig]] -.Explorando `unicodedata.name()` no console do Python. -image::images/flpy_0405.png[alt="Explorando `unicodedata.name()` no console do Python"] - -Você pode usar a função `name()` para criar aplicações que permitem aos usuários buscarem caracteres por nome. -A <> demonstra o script de comando de linha _cf.py_, que recebe como argumentos uma ou mais palavras, e lista os caracteres que tem aquelas palavras em seus nomes Unicode oficiais. -O código fonte completo de _cf.py_ aparece no <>. - -[[cf_demo_fig]] -.Usando _cf.py_ para encontrar gatos sorridentes. -image::images/flpy_0406.png[alt="Usando _cf.py_ para encontrar gatos sorridentes."] - -[WARNING] -==== -O((("emojis", "varied support for"))) suporte a emojis varia muito entre sistemas operacionais e aplicativos. -Nos últimos anos, o terminal do macOS tem oferecido o melhor suporte para emojis, seguido por terminais gráficos GNU/Linux modernos. -O _cmd.exe_ e o PowerShell do Windows agora suportam saída Unicode, -mas enquanto escrevo essa seção, em janeiro de 2020, eles ainda não mostram emojis—pelo menos não sem configurações adicionais. O revisor técnico Leonardo Rochael me falou sobre um novo https://fpy.li/4-24[terminal para Windows da Microsoft], de código aberto, -que pode ter um suporte melhor a Unicode que os consoles antigos da Microsoft. Não tive tempo de testar. -==== - -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 do Python, não precisamos de um loop `for` aninhado e de outro `if` para implementar essa verificação - -[[ex_cfpy]] -.cf.py: o utilitário de busca de caracteres -==== -[source, python3] ----- -include::code/04-text-byte/charfinder/cf.py[] ----- -==== -<1> Configura os defaults para a faixa de pontos de código da busca. -<2> `find` aceita `query_words` e somente argumentos nomeados (opcionais) para limitar a faixa da busca, facilitando os testes. -<3> Converte `query_words` em um conjunto de strings capitalizadas. -<4> Obtém o caractere Unicode para `code`. -<5> Obtém o nome do caractere, ou `None` se o ponto de código não estiver atribuído a um caractere. -<6> Se há um nome, separa esse nome em uma lista de palavras, então verifica se o conjunto `query` é um subconjunto daquela lista. -<7> Mostra uma linha com o ponto de código no formato `U+9999`, o caractere e seu nome. - - -O módulo `unicodedata` tem outras funções interessantes. A seguir veremos algumas delas, relacionadas a obter informação de caracteres com sentido numérico.((("", startref="Efind04")))((("", startref="Cfinduni04"))) - - -==== O sentido numérico de caracteres - -O((("characters", "numeric meaning of", id="Cnumeric04"))) módulo `unicodedata` inclui funções para determinar se um caractere Unicode representa um número e, se for esse o caso, seu valor numérico em termos humanos—em contraste com o número de seu ponto de código. - -O <> demonstra o uso de `unicodedata.name()` e `unicodedata.numeric()`, -junto com os métodos `.isdecimal()` e `.isnumeric()` de `str`. - -[[ex_numerics_demo]] -.Demo do banco de dados Unicode de metadados de caracteres numéricos (as notas explicativas descrevem cada coluna da saída) -==== -[source, py] ----- -include::code/04-text-byte/numerics_demo.py[tags=NUMERICS_DEMO] ----- -==== -<1> Ponto de código no formato `U+0000`. -<2> O caractere, centralizado em uma `str` de tamanho 6. -<3> Mostra `re_dig` se o caractere casa com a regex `r'\d'`. -<4> Mostra `isdig` se `char.isdigit()` é `True`. -<5> Mostra `isnum` se `char.isnumeric()` é `True`. -<6> Valor numérico formatado com tamanho 5 e duas casa decimais. -<7> O nome Unicode do caractere. - -Executar o <> gera a <>, se a fonte do seu terminal incluir todos aqueles símbolos. - -[[numerics_demo_fig]] -.Terminal do macOS mostrando os caracteres numéricos e metadados correspondentes; `re_dig` significa que o caractere casa com a expressão regular pass:[r'\d']. -image::images/flpy_0407.png[Captura de tela de caracteres numéricos] - -A sexta coluna da <> é o resultado da chamada a `unicodedata.numeric(char)` com o caractere. Ela mostra que o Unicode sabe o valor numérico de símbolos que representam números. Assim, se você quiser criar uma aplicação de planilha que suporta dígitos tamil ou numerais romanos, vá fundo! - -A <> mostra que a expressão regular `r'\d'` casa com o dígito "1" e com o dígito devanágari 3, mas não com alguns outros caracteres considerados dígitos pela função `isdigit`. -O módulo `re` não é tão conhecedor de Unicode quanto deveria ser. -O novo módulo `regex`, disponível no PyPI, foi projetado para um dia substituir o `re`, e fornece um suporte melhor ao Unicode.footnote:[Embora não tenha se saído melhor que o `re` para identificar dígitos nessa amostra em particular.] -Voltaremos ao módulo `re` na próxima seção. - -Ao longo desse capítulo, usamos várias funções de `unicodedata`, mas há muitas outras que não mencionamos. Veja a documentação da biblioteca padrão para o https://docs.python.org/pt-br/3/library/unicodedata.html[módulo `unicodedata`]. - -A seguir vamos dar uma rápida passada pelas APIs de modo dual, com funções que aceitam argumentos `str` ou `bytes` e dão a eles tratamento especial dependendo do tipo.((("", startref="Cnumeric04")))((("", startref="UTVdatabase04"))) - -[[dual_mode_api_sec]] -=== APIs de modo dual para str e bytes - -A biblioteca padrão do Python((("Unicode text versus bytes", "dual-mode str and bytes APIs", id="UTVdual04")))((("strings", "dual-mode str and bytes APIs", id="Sdual04"))) tem funções que aceitam argumentos `str` ou `bytes` e se comportam de forma diferente dependendo do tipo recebido. Alguns exemplos podem ser encontrados nos módulos `re` e `os`. - -==== str versus bytes em expressões regulares - -Se((("regular expressions, str versus bytes in"))) você criar uma expressão regular com `bytes`, padrões tal como `\d` e `\w` vão casar apenas com caracteres ASCII; por outro lado, se esses padrões forem passados como `str`, eles vão casar com dígitos Unicode ou letras além do ASCII. O <> e a <> comparam como letras, dígitos ASCII, superescritos e dígitos tamil casam em padrões `str` e `bytes`. - -[[ex_re_demo]] -.ramanujan.py: compara o comportamento de expressões regulares simples como `str` e como `bytes` -==== -[source, py] ----- -include::code/04-text-byte/ramanujan.py[tags=RE_DEMO] ----- -==== -<1> As duas primeiras expressões regulares são do tipo `str`. -<2> As duas últimas são do tipo `bytes`. -<3> Texto Unicode para ser usado na busca, contendo os dígitos tamil para `1729` (a linha lógica continua até o símbolo de fechamento de parênteses). -<4> Essa string é unida à anterior no momento da compilação (veja https://docs.python.org/pt-br/3/reference/lexical_analysis.html#string-literal-concatenation["2.4.2. String literal concatenation" (_Concatenação de strings literais_)] em _A Referência da Linguagem Python_). -<5> Uma string `bytes` é necessária para a busca com as expressões regulares `bytes`. -<6> O padrão `str` `r'\d+'` casa com os dígitos ASCII e tamil. -<7> O padrão `bytes` `rb'\d+'` casa apenas com os bytes ASCII para dígitos. -<8> O padrão `str` `r'\w+'` casa com letras, superescritos e dígitos tamil e ASCII. -<9> O padrão `bytes` `rb'\w+'` casa apenas com bytes ASCII para letras e dígitos. - -[[fig_re_demo]] -.Captura de tela da execução de ramanujan.py do <>. -image::images/flpy_0408.png[Saída de ramanujan.py] - -O <> é um exemplo trivial para destacar um ponto: você pode usar expressões regulares com `str` ou `bytes`, mas nesse último caso os bytes fora da faixa do ASCII são tratados como caracteres que não representam dígitos nem palavras. - -Para expressões regulares `str`, há uma marcação `re.ASCII`, que faz `\w`, `\W`, `\b`, `\B`, `\d`, `\D`, `\s`, e `\S` executarem um casamento apenas com ASCII. Veja a https://docs.python.org/pt-br/3/library/re.html[documentaçào do módulo `re`] para maiores detalhes. - -Outro módulo importante é o `os`. - -==== str versus bytes nas funções de os - -O((("os functions, str versus bytes in"))) kernel do GNU/Linux não conhece Unicode então, no mundo real, você pode encontrar nomes de arquivo compostos de sequências de bytes que não são válidas em nenhum esquema razoável de codificação, e não podem ser decodificados para `str`. Servidores de arquivo com clientes usando uma variedade de diferentes SOs são particularmente inclinados a apresentar esse cenário. - -Para mitigar esse problema, todas as funções do módulo `os` que aceitam nomes de arquivo ou caminhos podem receber seus argumentos como `str` ou `bytes`. Se uma dessas funções é chamada com um argumento `str`, o argumento será automaticamente convertido usando o codec informado por `sys.getfilesystemencoding()`, e a resposta do SO será decodificada com o mesmo codec. Isso é quase sempre o que se deseja, mantendo a melhor prática do sanduíche de Unicode. - -Mas se você precisa lidar com (e provavelmente corrigir) nomes de arquivo que não podem ser processados daquela forma, você pode passar argumentos `bytes` para as funções de `os`, e receber `bytes` de volta. Esse recurso permite que você processe qualquer nome de arquivo ou caminho, independende de quantos gremlins encontrar. Veja o <>. - -[[ex_listdir1]] -.`listdir` com argumentos `str` e `bytes`, e os resultados -==== -[source, pycon] ----- ->>> os.listdir('.') # <1> -['abc.txt', 'digits-of-π.txt'] ->>> os.listdir(b'.') # <2> -[b'abc.txt', b'digits-of-\xcf\x80.txt'] ----- -==== -<1> O segundo nome de arquivo é "digits-of-π.txt" (com a letra grega pi). -<2> Dado um argumento `byte`, `listdir` devolve nomes de arquivos como bytes: `b'\xcf\x80'` é a codificação UTF-8 para a letra grega pi. - -Para ajudar no processamento manual de sequências `str` ou `bytes` que são nomes de arquivos ou caminhos, -o módulo `os` fornece funções especiais de codificação e decodificação, `os.fsencode(name_or_path)` e `os.fsdecode(name_or_path)`. Ambas as funções aceitam argumentos dos tipos `str`, `bytes` ou, desde o Python 3.6, um objeto que implemente a interface `os.PathLike`. - -O Unicode é um buraco de coelho bem fundo. É hora de encerrar nossa exploração de `str` e `bytes`.((("", startref="UTVdual04")))((("", startref="Sdual04"))) - - -=== Resumo do capítulo - -Começamos((("Unicode text versus bytes", "overview of"))) o capítulo descartando a noção de que `1 caractere == 1 byte`. A medida que o mundo adota o Unicode, precisamos manter o conceito de strings de texto separado das sequências binárias que as representam em arquivos, e o Python 3 aplica essa separação. - -Após uma breve passada pelos tipos de dados sequências binárias—`bytes`, `bytearray`, e `memoryview`—, mergulhamos na codificação e na decodificação, com uma amostragem dos codec importantes, seguida por abordagens para prevenir ou lidar com os abomináveis `UnicodeEncodeError`, `UnicodeDecodeError` e os `SyntaxError` causados pela codificação errada em arquivos de código-fonte do Python. - -A seguir consideramos a teoria e a prática de detecção de codificação na ausência de metadados: em teoria, não pode ser feita, mas na prática o pacote Chardet consegue realizar esse feito para uma grande quantidade de codificações populares. Marcadores de ordem de bytes foram apresentados como a única dica de codificação encontrada em arquivos UTF-16 e UTF-32--algumas vezes também em arquivos UTF-8. - -Na seção seguinte, demonstramos como abrir arquivos de texto, uma tarefa fácil exceto por uma armadilha: o argumento nomeado `encoding=` não é obrigatório quando se abre um arquivo de texto, mas deveria ser. Se você não especificar a codificação, terminará com um programa que consegue produzir "texto puro" que é incompatível entre diferentes plataformas, devido a codificações default conflitantes. Expusemos então as diferentes configurações de codificação usadas pelo Python, e como detectá-las. - -Uma triste realidade para usuários de Windows é o fato dessas configurações muitas vezes terem valores diferentes dentro da mesma máquina, e desses valores serem mutuamente incompatíveis; usuários do GNU/Linux e do macOS, por outro lado, vivem em um lugar mais feliz, onde o UTF-8 é o default por (quase) toda parte. - -O Unicode fornece múltiplas formas de representar alguns caracteres, então a normalização é um pré-requisito para a comparação de textos. Além de explicar a normalização e o _case folding_, apresentamos algumas funções úteis que podem ser adaptadas para as suas necessidades, incluindo transformações drásticas como a remoção de todos os acentos. Vimos como ordenar corretamente texto Unicode, usando o módulo padrão `locale`—com algumas restrições—e uma alternativa que não depende de complexas configurações de locale: a biblioteca((("pyuca library"))) externa _pyuca_. - -Usamos o banco de dados do Unicode para programar um utilitário de comando de linha que busca caracteres por nome--em 28 linha de código, graças ao poder do Python. -Demos uma olhada em outros metadados do Unicode, e vimos rapidamente as APIs de modo dual, onde algumas funções podem ser chamadas com argumentos `str` ou `bytes`, produzindo resultados diferentes. - - -=== Leitura complementar - -A palestra((("Unicode text versus bytes", "further reading on"))) de Ned Batchelder na PyCon US 2012, https://fpy.li/4-28["Pragmatic Unicode, or, How Do I Stop the Pain?" (_Unicode Pragmático, ou, Como Eu Fiz a Dor Sumir?_)] (EN), foi marcante. -Ned é tão profissional que forneceu uma transcrição completa da palestra, além dos slides e do vídeo. - -"Character encoding and Unicode in Python: How to (╯°□°)╯︵ ┻━┻ with dignity" (_Codificação de caracteres e o Unicode no Python: como (╯°□°)╯︵ ┻━┻ com dignidade_) -(https://fpy.li/4-1[slides], https://fpy.li/4-2[vídeo]) (EN) -foi uma excelente palestra de Esther Nam e Travis Fischer na PyCon 2014, -e foi onde encontrei a concisa epígrafe desse capítulo: "Humanos usam texto. Computadores falam em bytes." - -//// -PROD: please make sure the "(╯°□°)╯︵ ┻━┻" part of the talk title is not split at a line break. -//// - -Lennart Regebro--um dos revisores técnicos da primeira edição desse livro--compartilha seu "Useful Mental Model of Unicode (UMMU)" (_Modelo Mental Útil do Unicode_) em um post curto, https://fpy.li/4-31["Unconfusing Unicode: What Is Unicode?" (_Desconfundindo o Unicode: O Que É O Unicode?_)] (EN). -O Unicode é um padrão complexo, então o UMMU de Lennart é realmente um ponto de partida útil. - -O https://docs.python.org/pt-br/3/howto/unicode.html["Unicode HOWTO"] oficial na documentação do Python aborda o assunto por vários ângulos diferentes, de uma boa introdução histórica a detalhes de sintaxe, codecs, expressões regulares, nomes de arquivo, e boas práticas para E/S sensível ao Unicode (isto é, o sanduíche de Unicode), com vários links adicionais de referências em cada seção. - -O https://fpy.li/4-33[Chapter 4, "Strings" (_Capítulo 4, "Strings"_)], do maravilhosos livro https://fpy.li/4-34[__Dive into Python 3__] (EN), de Mark Pilgrim (Apress), também fornece uma ótima introdução ao suporte a Unicode no Python 3. No mesmo livro, o https://fpy.li/4-35[Capítulo 15] descreve como a biblioteca Chardet foi portada do Python 2 para o Python 3, um valioso estudo de caso, dado que a mudança do antigo tipo `str` para o novo `bytes` é a causa da maioria das dores da migração, e esta é uma preocupação central em uma biblioteca projetada para detectar codificações. - -Se você conhece Python 2 mas é novo no Python 3, o artigo https://fpy.li/4-36["What’s New in Python 3.0" (_O quê há de novo no Python 3.0_)] (EN), de Guido van Rossum, tem 15 pontos resumindo as mudanças, com vários links. Guido inicia com uma afirmação brutal: "Tudo o que você achava que sabia sobre dados binários e Unicode mudou". O post de Armin Ronacher em seu blog, https://fpy.li/4-37["The Updated Guide to Unicode on Python" _O Guia Atualizado do Unicode no Python_], é bastante profundo e realça algumas das armadilhas do Unicode no Python (Armin não é um grande fã do Python 3). - -O capítulo 2 ("Strings and Text" _Strings e Texto_) do pass:[Python Cookbook, 3rd ed.] (EN) (O'Reilly), de David Beazley e Brian K. Jones, tem várias receitas tratando de normalização de Unicode, sanitização de texto, e execução de operações orientadas para texto em sequências de bytes. O capítulo 5 trata de arquivos e E/S, e inclui a "Recipe 5.17. Writing Bytes to a Text File" (_Receita 5.17. Escrevendo Bytes em um Arquivo de Texto_), mostrando que sob qualquer arquivo de texto há sempre uma sequência binária que pode ser acessada diretamente quando necessário. Mais tarde no mesmo livro, o módulo `struct` é usado em "Recipe 6.11. Reading and Writing Binary Arrays of Structures" (_Receita 6.11. Lendo e Escrevendo Arrays Binárias de Estruturas_). - -O blog "Python Notes" de Nick Coghlan tem dois posts muito relevantes para esse capítulo: https://fpy.li/4-38["Python 3 and ASCII Compatible Binary Protocols" (_Python 3 e os Protocolos Binários Compatíveis com ASCII_)] (EN) e https://fpy.li/4-39["Processing Text Files in Python 3" (_Processando Arquivos de Texto em Python 3_)] (EN). Fortemente recomendado. - -Uma lista de codificações suportadas pelo Python está disponível em https://docs.python.org/pt-br/3/library/codecs.html#standard-encodings["Standard Encodings"] (EN), na documentação do módulo `codecs`. Se você precisar obter aquela lista de dentro de um programa, pode ver como isso é feito no script https://fpy.li/4-41[__/Tools/unicode/listcodecs.py__], que acompanha o código-fonte do CPython. - -Os livros pass:[Unicode Explained (Unicode Explicado)] (EN), de Jukka K. Korpela (O'Reilly) e https://fpy.li/4-43[_Unicode Demystified_ (Unicode Desmistificado)], de Richard Gillam (Addison-Wesley) não são específicos sobre o Python, nas foram muito úteis para meu estudo dos conceitos do Unicode. https://fpy.li/4-44[_Programming with Unicode_ (Programando com Unicode)], de Victor Stinner, é um livro gratuito e publicado pelo próprio autor (Creative [.keep-together]#Commons# BY-SA) tratando de Unicode em geral, bem como de ferramentas e APIs no contexto dos principais sistemas operacionais e algumas linguagens de programação, incluindo Python. - -As páginas do W3C https://fpy.li/4-45["Case Folding: An Introduction" (_Case Folding: Uma Introdução_)] (EN) e https://fpy.li/4-15["Character Model for the World Wide Web: String Matching" (_O Modelo de Caracteres para a World Wide Web: Correspondência de Strings_)] (EN) tratam de conceitos de normalização, a primeira uma suave introdução e a segunda uma nota de um grupo de trabalho escrita no seco jargão dos padrões—o mesmo tom do https://fpy.li/4-47["Unicode Standard Annex #15--Unicode Normalization Forms" (_Anexo 15 do Padrão Unicode—Formas de Normalização do Unicode_)] (EN). A seção https://fpy.li/4-48["Frequently Asked Questions, Normalization" (_Perguntas Frequentes, Normalização_)] (EN) do pass:[Unicode.org] é mais fácil de ler, bem como o https://fpy.li/4-50["NFC FAQ"] (EN) de Mark Davis--autor de vários algoritmos do Unicode e presidente do Unicode Consortium quando essa seção foi escrita. - -Em 2016, o((("emojis", "in the Museum of Modern Art"))) Museu de Arte Moderna (MoMA) de New York adicionou à sua coleção -https://fpy.li/4-51[o emoji original] (EN), -os 176 emojis desenhados por Shigetaka Kurita em 1999 para a NTT DOCOMO—a provedora de telefonia móvel japonesa. -Indo mais longe no passado, a https://fpy.li/4-52[_Emojipedia_] (EN) publicou o artigo https://fpy.li/4-53["Correcting the Record on the First Emoji Set" (_Corrigindo o Registro [Histórico\] sobre o Primeiro Conjunto de Emojis_)] (EN), atribuindo ao SoftBank do Japão o mais antigo conjunto conhecido de emojis, implantado em telefones celulares em 1997. -O conjunto do SoftBank é a fonte de 90 emojis que hoje fazem parte do Unicode, incluindo o U+1F4A9 (`PILE OF POO`). -O https://fpy.li/4-54[_emojitracker.com_], de Matthew Rothenberg, é um painel ativo mostrando a contagem do uso de emojis no Twitter, atualizado em tempo real. -Quando escrevo isso, `FACE WITH TEARS OF JOY` (U+1F602) é o emoji mais popular no Twitter, com mais de -3.313.667.315 ocorrências registradas. - -.Ponto de vista -**** - -[role="soapbox-title"] -Nomes não-ASCII no código-fonte: você deveria usá-los? - -O Python 3((("Soapbox sidebars", "non-ASCII names in source code")))((("Unicode text versus bytes", "Soapbox discussion"))) permite identificadores não-ASCII no código-fonte: - -[source, python3] ----- ->>> ação = 'PBR' # ação = stock ->>> ε = 10**-6 # ε = epsilon ----- - -Algumas pessoas não gostam dessa ideia. -O argumento mais comum é que se limitar aos caracteres ASCII torna a leitura e a edição so código mais fácil para todo mundo. -Esse argumento erra o alvo: você quer que seu código-fonte seja legível e editável pela audiência pretendida, e isso pode não ser "todo mundo". -Se o código pertence a uma corporação multinacional, ou se é um código aberto e você deseja contribuidores de todo o mundo, os identificadores devem ser em inglês, e então tudo o que você precisa é do ASCII. - -Mas se você é uma professora no Brasil, seus alunos vão achar mais fácil ler código com variáveis e nomes de função em português, e escritos corretamente. -E eles não terão nenhuma dificuldade para digitar as cedilhas e as vogais acentuadas em seus teclados localizados. - -Agora que o Python pode interpretar nomes em Unicode, e que o UTF-8 é a codificação padrão para código-fonte, não vejo motivo para codificar identificadores em português sem acentos, como fazíamos no Python 2, por necessidade—a menos que seu código tenha que rodar também no Python 2. -Se os nomes estão em português, excluir os acentos não vai tornar o código mais legível para ninguém. - -Esse é meu ponto de vista como um brasileiro falante de português, mas acredito que se aplica além de fronteiras e a outras culturas: escolha a linguagem humana que torna o código mais legível para sua equipe, e então use todos os caracteres necessários para a ortografia correta. - -[role="soapbox-title"] -O que é "texto puro"? - -Para((("Soapbox sidebars", "plain text"))) qualquer um que lide diariamente com texto em línguas diferentes do inglês, "texto puro" não significa "ASCII". O https://fpy.li/4-55[Glossário do Unicode] (EN) define((("plain text"))) _texto puro_ dessa forma: - -[quote] -____ -Texto codificado por computador que consiste apenas de uma sequência de pontos de código de um dado padrão, sem qualquer outra informação estrutural ou de formatação. -____ - -Essa definição começa muito bem, mas não concordo com a parte após a vírgula. HTML é um ótimo exemplo de um formato de texto puro que inclui informação estrutural e de formatação. Mas ele ainda é texto puro, porque cada byte em um arquivo desse tipo está lá para representar um caractere de texto, em geral usando UTF-8. Não há bytes com significado não-textual, como você encontra em documentos _.png_ ou _.xls_, onde a maioria dos bytes representa valores binários empacotados, como valores RGB ou números de ponto flutuante. No texto puro, números são representados como sequências de caracteres de dígitos. - -Estou escrevendo esse livro em um formato de texto puro chamado—ironicamente— https://fpy.li/4-56[AsciiDoc], -que é parte do conjunto de ferramentas do excelente https://fpy.li/4-57[Atlas book publishing platform (_paltaforma de publicação de livros Atlas_)] da O'Reilly. Os arquivos fonte de AsciiDoc são texto puro, mas são UTF-8, e não ASCII. Se fosse o contrário, escrever esse capítulo teria sido realmente doloroso. Apesar do nome, o AsciiDoc é muito bom. - -O mundo do Unicode está em constante expansão e, nas margens, as ferramentas de apoio nem sempre existem. Nem todos os caracteres que eu queria exibir estavam disponíveis nas fontes usadas para renderizar o livro. -Por isso tive que usar imagens em vez de listagens em vários exemplos desse capítulo. -Por outro lado, os terminais do Ubuntu e do macOS exibem a maioria do texto Unicode muito bem—incluindo os caracteres japoneses para a palavra "mojibake": 文字化け. - - -[role="soapbox-title"] -Como os ponto de código numa str são representados na RAM? - -A((("Soapbox sidebars", "code points")))((("code points"))) documentação oficial do Python evita falar sobre como os pontos de código de uma `str` são armazenados na memória. -Realmente, é um detalhe de implementação. -Em teoria, não importa: qualquer que seja a representação interna, toda `str` precisa ser codificada para `bytes` na saída. - -Na memória, o Python 3 armazena cada `str` como uma sequência de pontos de código, usando um número fixo de bytes por ponto de código, para permitir um acesso direto eficiente a qualquer caractere ou fatia. - -Desde o Python 3.3, ao criar um novo objeto `str` -o interpretador verifica os caracteres no objeto, e escolhe o layout de memória mais econômico que seja adequado para aquela `str` em particular: -se existirem apenas caracteres na faixa `latin1`, aquela `str` vai usar apenas um byte por ponto de código. -Caso contrário, podem ser usados dois ou quatro bytes por ponto de código, dependendo da `str`. -Isso é uma simplificação; para saber todos os detalhes, dê uma olhada an https://fpy.li/pep393[PEP 393--Flexible String Representation (_Representação Flexível de Strings_)] (EN). - -A representação flexível de strings é similar à forma como o tipo `int` funciona no Python 3: -se um inteiro cabe em uma palavra da máquina, ele será armazenado em uma palavra da máquina. -Caso contrário, o interpretador muda para uma representação de tamanho variável, como aquela do tipo `long` do Python 2. -É bom ver as boas ideias se espalhando. - -Entretanto, sempre podemos contar com Armin Ronacher para encontrar problemas no Python 3. -Ele me explicou porque, na prática, essa não é uma ideia tão boa assim: -basta um único `RAT` (U+1F400) para inflar um texto, que de outra forma seria inteiramente ASCII, e transformá-lo em um array sugadora de memória, usando quatro bytes por caractere, quando um byte seria o suficiente para todos os caracteres exceto o RAT. Além disso, por causa de todas as formas como os caracteres Unicode se combinam, a capacidade de buscar um caractere arbitrário pela posição é superestimada—e extrair fatias arbitrárias de texto Unicode é no mínimo ingênuo, e muitas vezes errado, produzindo mojibake. -Com((("emojis", "increasing issues with"))) os emojis se tornando mais populares, esses problemas vão só piorar. -**** diff --git a/capitulos/cap05.adoc b/capitulos/cap05.adoc deleted file mode 100644 index a43dc11b..00000000 --- a/capitulos/cap05.adoc +++ /dev/null @@ -1,1516 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte - -[[data_class_ch]] -== Fábricas de classes de dados - -// [quote, Martin Fowler and Kent Beck] -// ____ -// Data classes are like children. They are okay as a starting point, but to participate as a grownup object, they need to take some responsibility.footnote:[From _Refactoring, First Edition_, chapter 3, _Bad Smells in Code_, _Data Class_ section, page 87.] -// ____ - -++++ -
-

Classes de dados são como crianças. São boas como um ponto de partida mas, para participarem como um objeto adulto, precisam assumir alguma responsabilidade.

-

Martin Fowler and Kent Beck em Refactoring, primeira edição, Capítulo 3, seção "Bad Smells in Code, Data Class" (Mau cheiro no código, classe de dados), página 87 (Addison-Wesley).

-
-++++ - -O Python oferece algumas formas de criar uma classe simples, apenas uma coleção de campos, com pouca ou nenhuma funcionalidade adicional. -Esse padrão é conhecido como "classe de dados"—e `dataclasses` é um dos pacotes que suporta tal modelo. -Este((("data class builders", "topics covered"))) capítulo trata de três diferentes fábricas de classes que podem ser utilizadas como atalhos para escrever classes de dados: - -`collections.namedtuple`:: A forma mais simples—disponível desde o Python 2.6. -`typing.NamedTuple`:: Uma alternativa que requer dicas de tipo nos campos—desde o Python 3.5, com a sintaxe `class` adicionada no 3.6. -`@dataclasses.dataclass`:: Um decorador de classe que permite mais personalização que as alternativas anteriores, acrescentando várias opções e, potencialmente, mais complexidade—desde o Python 3.7. - -Após falar sobre essas fábricas de classes, vamos discutir o motivo de _classe de dados_ ser também o nome um((("code smells"))) _code smell_: -um padrão de programação que pode ser um sintoma de um design orientado a objetos ruim. - -[NOTE] -==== -A classe `typing.TypedDict` pode((("TypedDict"))) parecer apenas outra fábrica de classes de dados. -Ela usa uma sintaxe similar, e é descrita pouco após `typing.NamedTuple` na https://docs.python.org/pt-br/3/library/typing.html#typing.TypedDict[documentação do módulo `typing`] (EN) do Python 3.11. - -Entretanto, `TypedDict` não cria classes concretas que possam ser instanciadas. -Ela é apenas a sintaxe para escrever dicas de tipo para parâmetros de função e variáveis que aceitarão valores de mapeamentos como registros, enquanto suas chaves serão os nomes dos campos. -Nós veremos mais sobre isso na seção <> do <>. -==== - - -=== Novidades nesse capítulo - -Este((("data class builders", "significant changes to"))) capítulo é novo, aparece nessa segunda edição do _Python Fluente_. -A seção <> era parte do capítulo 2 da primeira edição, mas o restante do capítulo é inteiramente inédito. - -Vamos começar por uma visão geral, por alto, das três fábricas de classes. - -[[data_class_overview_sec]] -=== Visão geral das fábricas de classes de dados - -Considere((("data class builders", "overview of", id="DCBover05"))) uma classe simples, representando um par de coordenadas geográficas, como aquela no <>. - -[[coord_class_ex]] -._class/coordinates.py_ -==== -[source, py3] ----- -include::code/05-data-classes/class/coordinates.py[tags=COORDINATE] ----- -==== - -A tarefa da classe `Coordinate` é manter os atributos latitude e longitude. -Escrever o `+__init__+` padrão fica cansativo muito rápido, -especialmente se sua classe tiver mais que alguns poucos atributos: -cada um deles é mencionado três vezes! -E aquele código repetitivo não nos dá sequer os recursos básicos que esperamos de um objeto Python: - -[source, pycon] ----- ->>> from coordinates import Coordinate ->>> moscow = Coordinate(55.76, 37.62) ->>> moscow - <1> ->>> location = Coordinate(55.76, 37.62) ->>> location == moscow <2> -False ->>> (location.lat, location.lon) == (moscow.lat, moscow.lon) <3> -True ----- -<1> O `+__repr__+` herdado de `object` não é muito útil. -<2> O `==` não faz sentido; o método `+__eq__+` herdado de `object` compara os IDs dos objetos. -<3> Comparar duas coordenadas exige a comparação explícita de cada atributo. - -As fábricas de classes de dados tratadas nesse capítulo fornecem automaticamente os métodos `+__init__+`, `+__repr__+`, e `+__eq__+` necessários, além alguns outros recursos úteis. - -[NOTE] -==== -Nenhuma das fábricas de classes discutidas aqui depende de herança para funcionar. Tanto `collections.namedtuple` quanto `typing.NamedTuple` criam subclasses de `tuple`. -O `@dataclass` é um decorador de classe, não afeta de forma alguma a hierarquia de classes. -Cada um deles utiliza técnicas diferentes de metaprogramação para injetar métodos e atributos de dados na classe em construção. -==== - -Aqui está uma classe `Coordinate` criada com uma `namedtuple`—uma função fábrica que cria uma subclasse de `tuple` com o nome e os campos especificados: - -[source, pycon] ----- ->>> from collections import namedtuple ->>> Coordinate = namedtuple('Coordinate', 'lat lon') ->>> issubclass(Coordinate, tuple) -True ->>> moscow = Coordinate(55.756, 37.617) ->>> moscow -Coordinate(lat=55.756, lon=37.617) <1> ->>> moscow == Coordinate(lat=55.756, lon=37.617) <2> -True ----- -<1> Um `+__repr__+` útil. -<2> Um `+__eq__+` que faz sentido. - -A `typing.NamedTuple`, mais recente, oferece a mesma funcionalidade e acrescenta anotações de tipo a cada campo: - -[source, pycon] ----- ->>> import typing ->>> Coordinate = typing.NamedTuple('Coordinate', -... [('lat', float), ('lon', float)]) ->>> issubclass(Coordinate, tuple) -True ->>> typing.get_type_hints(Coordinate) -{'lat': , 'lon': } ----- - -[TIP] -==== -Uma tupla nomeada e com dicas de tipo pode também ser construída passando os campos como argumentos nomeados, assim: -[source, pycon] ----- -Coordinate = typing.NamedTuple('Coordinate', lat=float, lon=float) ----- - -Além de ser mais legível, essa forma permite fornecer o mapeamento de campos e tipos como `**fields_and_types`. -==== - -Desde o Python 3.6, `typing.NamedTuple` pode também ser usada em uma instrução `class`, -com as anotações de tipo escritas como descrito na https://fpy.li/pep526[PEP 526—Syntax for Variable Annotations (_Sintaxe para Anotações de Variáveis_)] (EN). -É muito mais legível, e torna fácil sobrepor métodos ou acrescentar métodos novos. -O <> é a mesma classe `Coordinate`, com um par de atributos `float` -e um `+__str__+` personalziado, para mostrar a coordenada no formato 55.8°N, 37.6°E. -// as shown in <>. - -[[coord_tuple_ex]] -._typing_namedtuple/coordinates.py_ -==== -[source, py] ----- -include::code/05-data-classes/typing_namedtuple/coordinates.py[tags=COORDINATE] ----- -==== - -[WARNING] -==== -Apesar de `NamedTuple` aparecer na declaração `class` como uma superclasse, não é esse o caso. -`typing.NamedTuple` usa a funcionalidade avançada de uma metaclassefootnote:[As metaclasses são um dos assuntos tratados no pass:[#class_metaprog].] para personalizar a criação da classe do usuário. Veja isso: - -[source, pycon] ----- ->>> issubclass(Coordinate, typing.NamedTuple) -False ->>> issubclass(Coordinate, tuple) -True ----- - -==== - -No método `+__init__+` gerado por `typing.NamedTuple`, os campos aparecem como parâmetros e na mesma ordem em que aparecem na declaração `class`. - -Assim como `typing.NamedTuple`, o decorador `dataclass` suporta a sintaxe da https://fpy.li/pep526[PEP 526] (EN) para declarar atributos de instância. -O decorador lê as anotações das variáveis e gera métodos automaticamente para sua classe. -Como comparação, veja a classe `Coordinate` equivante escrita com a ajuda do decorador `dataclass`, como mostra o <>. - -[[coord_dataclass_ex]] -._dataclass/coordinates.py_ -==== -[source, py] ----- -include::code/05-data-classes/dataclass/coordinates.py[tags=COORDINATE] ----- -==== - -Observe que o corpo das classes no <> e no <> são idênticos—a diferença está na própria declaração `class`. -O decorador `@dataclass` não depende de herança ou de uma metaclasse, então não deve interferir no uso desses mecanismos pelo usuário.footnote:[Decoradores de classe são discutidos no <>, na seção "Metaprogramação de classes", junto com as metaclasses. Ambos são formas de personalizar o comportamento de uma classe além do que seria possível com herança.] -A classe `Coordinate` no <> é uma subclasse de `object`.((("", startref="DCBover05"))) - -[[dc_main_features_sec]] -==== Principais recursos - -As((("data class builders", "main features", id="DCBmain05"))) diferentes fábricas de classes de dados tem muito em comum, como resume a <>. - -[[builders_compared_tbl]] -.Recursos selecionados, comparando as três fábricas de classes de dados; `x` é uma instância de uma classe de dados daquele tipo -[options="header"] -|============================================================================================================================== -| | namedtuple | NamedTuple | dataclass -| instâncias mutáveis | NÃO | NÃO | SIM -| sintaxe de declaração de classe | NÃO | SIM | SIM -| criar um dict | x._asdict() | x._asdict() | dataclasses.asdict(x) -| obter nomes dos campos | x._fields | x._fields | [f.name for f in dataclasses.fields(x)] -| obter defaults | x._field_defaults | x._field_defaults | [f.default for f in dataclasses.fields(x)] -| obter tipos dos campos | N/A | x.__annotations__ | x.__annotations__ -| nova instância com modificações | x._replace(…) | x._replace(…) | dataclasses.replace(x, …) -| nova classe durante a execução | namedtuple(…) | NamedTuple(…) | dataclasses.make_dataclass(…) -|============================================================================================================================== - -[WARNING] -==== -As classes criadas por `typing.NamedTuple` e `@dataclass` tem um atributo `+__annotations__+`, contendo as dicas de tipo para os campos. -Entretanto, ler diretamente de `+__annotations__+` não é recomendado. -Em vez disso, a melhor prática recomendada para obter tal informação é chamar -https://docs.python.org/pt-br/3.10/library/inspect.html#inspect.get_annotations[`inspect.get_annotations(MyClass)`] (a partir do Python 3.10—EN) ou -pass:[typing.​get_​type_​hints(MyClass)] (Python 3.5 a 3.9—EN). -Isso porque tais funções fornecem serviços adicionais, como a resolução de referências futuras nas dicas de tipo. -Voltaremos a isso bem mais tarde neste livro, na seção <>. -==== - -Vamos agora detalhar aqueles recursos principais. - - -===== Instâncias mutáveis - -A diferença fundamental entre essas três fábricas de classes é que `collections.namedtuple` e `typing.NamedTuple` criam subclasses de `tuple`, e portanto as instâncias são imutáveis. Por default, `@dataclass` produz classes mutáveis. Mas o decorador aceita o argumento nomeado `frozen`—que aparece no <>. Quando `frozen=True`, a classe vai gerar uma exceção se você tentar atribuir um valor a um campo após a instância ter sido inicializada. - - -[[class_syntax_feature]] -===== Sintaxe de declaração de classe - -Apenas `typing.NamedTuple` e `dataclass` suportam a sintaxe de declaração de `class` regular, tornando mais fácil acrescentar métodos e docstrings à classe que está sendo criada. - -===== Construir um dict - -As duas variantes de tuplas nomeadas fornecem um método de instância (`._asdict`), para construir um objeto `dict` a partir dos campos de uma instância de classe de dados. -O módulo `dataclasses` fornece uma função para fazer o mesmo: `dataclasses.asdict`. - -===== Obter nomes dos campos e valores default - -Todas as três fábricas de classes permitem que você obtenha os nomes dos campos e os valores default (que podem ser configurados para cada campo). -Nas classes de tuplas nomeadas, aqueles metadados estão nos atributos de classe `._fields` e `._fields_defaults`. -Você pode obter os mesmos metadados em uma classe decorada com `dataclass` usando a função `fields` do módulo `dataclasses`. Ele devolve uma tupla de objetos `Field` com vários atributos, incluindo `name` e `default`. - - -===== Obter os tipos dos campos - -Classes definidas com a ajuda de `typing.NamedTuple` e `@dataclass` contêm um mapeamento dos nomes dos campos para seus tipos, o atributo de classe `+__annotations__+`. -Como já mencionado, use a função `typing.get_type_hints` em vez de ler diretamente de -`+__annotations__+`. - - -===== Nova instância com modificações - -Dada uma instância de tupla nomeada `x`, a chamada `+x._replace(**kwargs)+` devolve uma nova instância com os valores de alguns atributos modificados, de acordo com os argumentos nomeados incluídos na chamada. A função de módulo `dataclasses.replace(x, **kwargs)` faz o mesmo para uma instância de uma classe decorada com `dataclass`. - - -===== Nova classe durante a execução - -Apesar da sintaxe de declaração de classe ser mais legível, ela é fixa no código. Uma framework pode ter a necessidade de criar classes de dados durante a execução. Para tanto, podemos usar a sintaxe default de chamada de função de `collections.namedtuple`, que também é suportada por `typing.NamedTuple`. O módulo `dataclasses` oferece a função `make_dataclass`, com o mesmo propósito. - -Após essa visão geral dos principais recursos das fábricas de classes de dados, vamos examinar cada uma delas mais de perto, começando pela mais simples.((("", startref="DCBmain05"))) - - -[[classic_named_tuples_sec]] -=== Tuplas nomeadas clássicas - -A((("data class builders", "classic named tuples", id="DCBnamedt05")))((("tuples", "classic named tuples", id="Tclassic05")))((("collections.namedtuple", id="colnamedt05")))((("namedtuple", id="namedt05"))) função `collections.namedtuple` é uma fábrica que cria subclasses de `tuple`, acrescidas de nomes de campos, um nome de classe, e um `+__repr__+` informativo. -Classes criadas com `namedtuple` podem ser usadas onde quer que uma tupla seja necessária. Na verdade, muitas funções da biblioteca padrão, que antes devolviam tuplas agora devolvem, por conveniência, tuplas nomeadas, sem afetar de forma alguma o código do usuário. - -[TIP] -==== -Cada instância de uma classe criada por `namedtuple` usa exatamente a mesma quantidade de memória usada por uma tupla, pois os nomes dos campos são armazenados na classe. -==== - -O <> mostra como poderíamos definir uma tupla nomeada para manter informações sobre uma cidade. - -[[ex_named_tuple_1]] -.Definindo e usando um tipo tupla nomeada -==== -[source, pycon] ----- ->>> from collections import namedtuple ->>> City = namedtuple('City', 'name country population coordinates') <1> ->>> tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667)) <2> ->>> tokyo -City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722, -139.691667)) ->>> tokyo.population <3> -36.933 ->>> tokyo.coordinates -(35.689722, 139.691667) ->>> tokyo[1] -'JP' ----- -==== -<1> São necessários dois parâmetros para criar uma tupla nomeada: um nome de classe e uma lista de nomes de campos, que podem ser passados como um iterável de strings ou como uma única string com os nomes delimitados por espaços. -<2> Na inicialização de uma instância, os valores dos campos devem ser passados como argumentos posicionais separados (uma `tuple`, por outro lado, é inicializada com um único iterável) -<3> É possível acessar os campos por nome ou por posição. - -Como uma subclasse de `tuple`, `City` herda métodos úteis, tal como `+__eq__+` e os métodos especiais para operadores de comparação—incluindo `+__lt__+`, que permite ordenar listas de instâncias de `City`. - -Uma tupla nomeada oferece alguns atributos e métodos além daqueles herdados de `tuple`. -O <> demonstra os mais úteis dentre eles: o atributo de classe `_fields`, o método de classe `_make(iterable)`, e o método de instância `_asdict()`. - -[[ex_named_tuple_2]] -.Atributos e métodos das tuplas nomeadas (continuando do exenplo anterior) -==== -[source, pycon] ----- ->>> City._fields <1> -('name', 'country', 'population', 'location') ->>> Coordinate = namedtuple('Coordinate', 'lat lon') ->>> delhi_data = ('Delhi NCR', 'IN', 21.935, Coordinate(28.613889, 77.208889)) ->>> delhi = City._make(delhi_data) <2> ->>> delhi._asdict() <3> -{'name': 'Delhi NCR', 'country': 'IN', 'population': 21.935, -'location': Coordinate(lat=28.613889, lon=77.208889)} ->>> import json ->>> json.dumps(delhi._asdict()) <4> -'{"name": "Delhi NCR", "country": "IN", "population": 21.935, -"location": [28.613889, 77.208889]}' ----- -==== -<1> `._fields` é uma tupla com os nomes dos campos da classe. -<2> `._make()` cria uma `City` a partir de um iterável; `City(*delhi_data)` faria o mesmo. -<3> `._asdict()` devolve um `dict` criado a partir da instância de tupla nomeada. -<4> `._asdict()` é útil para serializar os dados no formato JSON, por exemplo. - -[WARNING] -===== -Até o Python 3.7, o método `_asdict` devolvia um `OrderedDict`. -Desde o Python 3.8, ele devolve um `dict` simples—o que não causa qualquer problema, agora que podemos confiar na ordem de inserção das chaves. -Se você precisar de um `OrderedDict`, a -https://docs.python.org/pt-br/3.8/library/collections.html#collections.somenamedtuple._asdict[documentação do `_asdict`] (EN) -recomenda criar um com o resultado: `OrderedDict(x._asdict())`. -===== - -Desde o Python 3.7, a `namedtuple` aceita o argumento nomeado `defaults`, fornecendo um iterável de N valores default para cada um dos N campos mais à direita na definição da classe. -O <> mostra como definir uma tupla nomeada `Coordinate` com um valor default para o campo `reference`. - -[[ex_coord_tuple_default]] -.Atributos e métodos das tuplas nomeadas, continuando do <> -==== -[source, pycon] ----- ->>> Coordinate = namedtuple('Coordinate', 'lat lon reference', defaults=['WGS84']) ->>> Coordinate(0, 0) -Coordinate(lat=0, lon=0, reference='WGS84') ->>> Coordinate._field_defaults -{'reference': 'WGS84'} ----- -==== - -Na seção <>, mencionei que é mais fácil programar métodos com a sintaxe de classe suportada por `typing.NamedTuple` and `@dataclass`. -Você também pode acrescentar métodos a uma `namedtuple`, mas é um remendo. -Pule a próxima caixinha se você não estiver interessada em gambiarras. - -[[hacking_namedtuple_box]] -.Remendando uma tupla nomeada para injetar um método -**** - -Lembre como criamos a classe `Card` class no <> do <>: - -[source, py3] ----- -Card = collections.namedtuple('Card', ['rank', 'suit']) ----- - -Mas tarde no <>, escrevi uma função `spades_high`, para ordenação. -Seria bom que aquela lógica estivesse encapsulada em um método de `Card`, -mas acrescentar `spades_high` a `Card` sem usar uma declaração `class` exige um remendo rápido: definir a função e então atribuí-la a um atributo de classe. O <> mostra como isso é feito: - - -[[ranked_card_ex]] -.frenchdeck.doctest: Acrescentando um atributo de classe e um método a `Card`, a `namedtuple` da seção <> -==== -[source, pycon] ----- -include::code/05-data-classes/frenchdeck.doctest[tags=SPADES_HIGH] ----- -<1> Acrescenta um atributo de classe com valores para cada naipe. -<2> A função `spades_high` vai se tornar um método; o primeiro argumento não precisa ser chamado de `self`. Como um método, ela de qualquer forma terá acesso à instância que recebe a chamada. -<3> Anexa a função à classe `Card` como um método chamado `overall_rank`. -<4> Funciona! -==== - -Para uma melhor legibilidade e para ajudar na manutenção futura, é muito melhor programar métodos dentro de uma declaração `class`. -Mas é bom saber que essa gambiarra é possível, pois às vezes pode ser útil.footnote:[Se você conhece Ruby, sabe que injetar métodos é uma técnica bastante conhecida, apesar de controversa, entre _rubystas_. Em Python isso não é tão comum, pois não funciona com nenhum dos tipos embutidos—`str`, `list`, etc. Considero essa limitação do Python uma benção.] - -Isso foi apenas um pequeno desvio para demonstrar o poder de uma linguagem dinâmica. -**** - -Agora vamos ver a variante `typing.NamedTuple`.((("", startref="DCBnamedt05")))((("", startref="Tclassic05")))((("", startref="colnamedt05")))((("", startref="namedt05"))) - - -[[typed_named_tuples_sec]] -=== Tuplas nomeadas com tipo - -A((("typing.NamedTuple")))((("tuples", "typing.NamedTuple")))((("data class builders", "typed named tuples"))) classe `Coordinate` com um campo default, do <>, pode ser escrita usando `typing.NamedTuple`, como se vê no <>. - -[[coord_tuple_default_ex]] -._typing_namedtuple/coordinates2.py_ -==== -[source, py] ----- -include::code/05-data-classes/typing_namedtuple/coordinates2.py[tags=COORDINATE] ----- -<1> Todo campo de instância precisa ter uma anotação de tipo. -<2> O campo de instância `reference` é anotado com um tipo e um valor default. -==== - -As classes criadas por `typing.NamedTuple` não tem qualquer método além daqueles que `collections.namedtuple` também gera—e aquele herdados de `tuple`. -A única diferença é a presença do atributo de classe `+__annotations__+`—que o Python ignora completamente durante a execução do programa. - -Dado que o principal recurso de `typing.NamedTuple` são as anotações de tipo, vamos dar uma rápida olhada nisso antes de continuar nossa exploração das fábricas de classes de dados. - - -=== Introdução às dicas de tipo - -Dicas((("data class builders", "type hints", id="DCBhint05")))((("type hints (type annotations)", "basics of", id="typehint05"))) de tipo—também chamadas anotações de tipo—são formas de declarar o tipo esperado dos argumentos, dos valores devolvidos, das variáveis e dos atributos de funções. - -A primeira coisa que você precisa saber sobre dicas de tipo é que elas não são impostas de forma alguma pelo compilador de bytecode ou pelo interpretador do Python. - -[NOTE] -==== -Essa é uma introdução muito breve sobre dicas de tipo, suficiente apenas para que a sintaxe e o propósito das anotações usadas nas declarações de `typing.NamedTuple` e `@dataclass` façam sentido. -Vamos trata de anotações de tipo nas assinaturas de função no <> e de anotações mais avançadas no <>. -Aqui vamos ver principalmente dicas com tipos embutidos simples, tais como `str`, `int`, e `float`, -que são provavelmente os tipos mais comuns usados para anotar campos em classes de dados. -==== - -[[no_runtime_effect_sec]] -==== Nenhum efeito durante a execução - -Pense nas dicas de tipo do Python como -"documentação que pode ser verificada por IDEs e verificadores de tipo". - -Isso porque as dicas de tipo não tem qualquer impacto sobre o comportamento de programas em Python durante a execução. Veja o <>. - -[[no_runtime_check_ex]] -.O Python não exige dicas de tipo durante a execução de um programa -==== -[source, py] ----- ->>> import typing ->>> class Coordinate(typing.NamedTuple): -... lat: float -... lon: float -... ->>> trash = Coordinate('Ni!', None) ->>> print(trash) -Coordinate(lat='Ni!', lon=None) # <1> ----- -==== -<1> Eu avisei: não há verificação de tipo durante a execução! - -Se você incluir o código do <> em um módulo do Python, -ela vai rodar e exibir uma `Coordinate` sem sentido, e sem gerar qualquer erro ou aviso: - -[source, bash] ----- -$ python3 nocheck_demo.py -Coordinate(lat='Ni!', lon=None) ----- - -O objetivo primário das dicas de tipo é ajudar os verificadores de tipo externos, -como o https://fpy.li/mypy[Mypy] ou o verificador de tipo embutido do -https://fpy.li/5-5[PyCharm IDE]. -Essas são ferramentas de análise estática: elas verificam código-fonte Python "parado", -não código em execução. - -Para observar o efeito das dicas de tipo, é necessário executar umas dessas ferramentas sobre seu código—como um linter (_analisador de código_). -Por exemplo, eis o quê o Mypy tem a dizer sobre o exemplo anterior: - -[source, bash] ----- -$ mypy nocheck_demo.py -nocheck_demo.py:8: error: Argument 1 to "Coordinate" has -incompatible type "str"; expected "float" -nocheck_demo.py:8: error: Argument 2 to "Coordinate" has -incompatible type "None"; expected "float" ----- - -Como se vê, dada a definição de `Coordinate`, o Mypy sabe que os dois argumentos para criar um instância devem ser do tipo `float`, -mas atribuição a `trash` usa uma `str` e `None`.footnote:[No contexto das dicas de tipo, `None` não é o singleton `NoneType`, mas um apelido para o próprio `NoneType`. Se pararmos para pensar, isso é estranho, mas agrada nossa intuição e torna as anotações de valores devolvidos por uma função mais fáceis de ler, no caso comum de funções que devolvem `None`.] - -Vamos falar agora sobre a sintaxe e o significado das dicas de tipo. - -[[var_annotation_syntax]] -==== Sintaxe de anotação de variáveis - -Tanto((("variable annotations", "syntax of"))) `typing.NamedTuple` quanto `@dataclass` usam a sintaxe de anotações de variáveis definida na https://fpy.li/pep526[PEP 526] (EN). -Vamos ver aqui uma pequena introdução àquela sintaxe, no contexto da definição de atributos em declarações `class`. - -A sintaxe básica da anotação de variáveis é : - -[source, py3] ----- -var_name: some_type ----- - -A seção https://fpy.li/5-6["Acceptable type hints" (_Dicas de tipo aceitáveis)], na PEP 484, explica o que são tipo aceitáveis. Porém, no contexto da definição de uma classe de dados, os tipos mais úteis geralmente serão os seguintes: - -* Uma classe concreta, por exemplo `str` ou `FrenchDeck`. -* Um tipo de coleção parametrizada, como `list[int]`, `tuple[str, float]`, etc. -* `typing.Optional`, por exemplo `Optional[str]`—para declarar um campo que pode ser uma `str` ou `None`. - -Você também pode inicializar uma variável com um valor. Em uma declaração de `typing.NamedTuple` ou `@dataclass`, aquele valor se tornará o default daquele atributo quando o argumento correspondente for omitido na chamada de inicialização: - -[source, py3] ----- -var_name: some_type = a_value ----- - -==== O significado das anotações de variáveis - -Vimos((("variable annotations", "meaning of", id="VAmean05"))), no tópico <>, que dicas de tipo não tem qualquer efeito durante a execução de um programa. -Mas no momento da importação—quando um módulo é carregado—o Python as lê para construir o dicionário `+__annotations__+`, que `typing.NamedTuple` e `@dataclass` então usam para aprimorar a classe. - -Vamos começar essa exploração no <>, com uma classe simples, -para mais tarde ver que recursos adicionais são acrescentados por `typing.NamedTuple` e `@dataclass`. - -[[ex_demo_plain]] -.meaning/demo_plain.py: uma classe básica com dicas de tipo -==== -[source, python3] ----- -include::code/05-data-classes/meaning/demo_plain.py[] ----- -==== -<1> `a` se torna um registro em `+__annotations__+`, mas é então descartada: nenhum atributo chamado `a` é criado na classe. -<2> `b` é salvo como uma anotação, e também se torna um atributo de classe com o valor `1.1`. -<3> `c` é só um bom e velho atributo de classe básico, sem uma anotação. - -Podemos checar isso no console, primeiro lendo o `+__annotations__+` da `DemoPlainClass`, e daí tentando obter os atributos chamados `a`, `b`, e `c`: - -[source, pycon] ----- ->>> from demo_plain import DemoPlainClass ->>> DemoPlainClass.__annotations__ -{'a': , 'b': } ->>> DemoPlainClass.a -Traceback (most recent call last): - File "", line 1, in -AttributeError: type object 'DemoPlainClass' has no attribute 'a' ->>> DemoPlainClass.b -1.1 ->>> DemoPlainClass.c -'spam' ----- - -Observe que o atributo especial `+__annotations__+` é criado pelo interpretador para registrar dicas de tipo que aparecem no código-fonte—mesmo em uma classe básica. - -O `a` sobrevive apenas como uma anotação, não se torna um atributo da classe, porque nenhum valor é atribuído a ele.footnote:[O conceito de _undefined_, um dos erros mais idiotas no design do Javascript, não existe no Python. Obrigado, Guido!] -O `b` e o `c` são armazenados como atributos de classe porque são vinculados a valores. - -Nenhum desses três atributos estará em uma nova instância de `DemoPlainClass`. -Se você criar um objeto `o = DemoPlainClass()`, `o.a` vai gerar um `AttributeError`, enquanto `o.b` e `o.c` vão obter os atributos de classe com os valores `1.1` e `'spam'`—que é apenas o comportamento normal de um objeto Python. - -===== Inspecionando uma typing.NamedTuple - -Agora vamos examinar uma classe criada com `typing.NamedTuple` (<>), usando os mesmos atributos e anotações da `DemoPlainClass` do <>. - -[[ex_demo_nt]] -.meaning/demo_nt.py: uma classe criada com `typing.NamedTuple` -==== -[source, python3] ----- -include::code/05-data-classes/meaning/demo_nt.py[] ----- -==== -<1> `a` se torna uma anotação e também um atributo de instância. -<2> `b` é outra anotação, mas também se torna um atributo de instância com o valor default `1.1`. -<3> `c` é só um bom e velho atributo de classe comum; não será mencionado em nenhuma anotação. - -Inspecionando a `DemoNTClass`, temos o seguinte: - -[source, pycon] ----- ->>> from demo_nt import DemoNTClass ->>> DemoNTClass.__annotations__ -{'a': , 'b': } ->>> DemoNTClass.a -<_collections._tuplegetter object at 0x101f0f940> ->>> DemoNTClass.b -<_collections._tuplegetter object at 0x101f0f8b0> ->>> DemoNTClass.c -'spam' ----- - -Aqui vemos as mesmas anotações para `a` e `b` que vimos no <>. -Mas `typing.NamedTuple` cria os atributos de classe `a` e `b`. -O atributo c é apenas um atributo de classe simples, com o valor `'spam'`. - -Os atributos de classe `a` e `b` são((("descriptors"))) descritores (_descriptors_)—um recurso avançado tratado no <>. -Por ora, pense neles como similares a um _getter_ de propriedades do objetofootnote:[NT: Um _getter_ é um método que devolve o valor um atributo do objeto. Para propriedades mutáveis, o _getter_ vem geralmente acompanhado por um _setter_, que modifica a mesma propriedade. Os nomes derivam dos verbos em inglês _get_ (obter, receber) e _set_ (definir, estabelecer).]: -métodos que não exigem o operador explícito de chamada `()` para obter um atributo de instância. -Na prática, isso significa que `a` e `b` vão funcionar como atributos de instância somente para leitura—o que faz sentido, se lembrarmos que instâncias de `DemoNTClass` são apenas tuplas chiques, e tuplas são imutáveis. - -A `DemoNTClass` também recebe uma docstring personalizada: - -[source, pycon] ----- ->>> DemoNTClass.__doc__ -'DemoNTClass(a, b)' ----- - -Vamos examinar uma instância de `DemoNTClass`: - -[source, pycon] ----- ->>> nt = DemoNTClass(8) ->>> nt.a -8 ->>> nt.b -1.1 ->>> nt.c -'spam' ----- - -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`; ele não tem um atributo `c`, mas o 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`, vai gerar uma exceção pass:[Attribute​Error], com mensagens de erro sutilmente distintas. Tente fazer isso, e reflita sobre as mensagens. - - -[[inspecting_dataclass_sec]] -===== Inspecionando uma classe decorada com dataclass - -Vamos agora examinar o <>. - -[[ex_demo_dc]] -.meaning/demo_dc.py: uma classe decorada com `@dataclass` -==== -[source, python3] ----- -include::code/05-data-classes/meaning/demo_dc.py[] ----- -==== -<1> `a` se torna uma anotação, e também um atributo de instância controlado por um descritor. -<2> `b` é outra anotação, e também se torna um atributo de instância com um descritor e um valor default de `1.1`. -<3> `c` é apenas um atributo de classe comum; nenhuma anotação se refere a ele. - -Podemos então verificar o `+__annotations__+`, o `+__doc__+`, e os atributos `a`, `b`, `c` no pass:[Demo​DataClass]: - -[source, pycon] ----- ->>> from demo_dc import DemoDataClass ->>> DemoDataClass.__annotations__ -{'a': , 'b': } ->>> DemoDataClass.__doc__ -'DemoDataClass(a: int, b: float = 1.1)' ->>> DemoDataClass.a -Traceback (most recent call last): - File "", line 1, in -AttributeError: type object 'DemoDataClass' has no attribute 'a' ->>> DemoDataClass.b -1.1 ->>> DemoDataClass.c -'spam' ----- - -O `+__annotations__+` e o `+__doc__+` não guardam surpresas. -Entretanto, não há um atributo chamado `a` em `DemoDataClass`—diferente do que ocorre na `DemoNTClass` do <>, -que inclui um descritor para obter `a` das instâncias da classe, como atributos somente para leitura (aquele misterioso `<_collections._tuplegetter_>`). -Isso ocorre porque o atributo `a` só existirá nas instâncias de `DemoDataClass`. -Será um atributo público, que poderemos obter e definir, a menos que a classe seja _frozen_. -Mas `b` e `c` existem como atributos de classe, com `b` contendo o valor default para o atributo de instância `b`, enquanto `c` é apenas um atributo de classe que não será vinculado a instâncias. - -Vejamos como se parece uma instância de `DemoDataClass`: - -[source, pycon] ----- ->>> dc = DemoDataClass(9) ->>> dc.a -9 ->>> dc.b -1.1 ->>> dc.c -'spam' ----- - -Novamente, `a` e `b` são atributos de instância, e `c` é um atributo de classe obtido através da instância. - -Como mencionado, instâncias de `DemoDataClass` são mutáveis—e nenhuma verificação de tipo é realizada durante a execução: - -[source, pycon] ----- ->>> dc.a = 10 ->>> dc.b = 'oops' ----- - -Podemos fazer atribuições ainda mais ridículas: - -[source, pycon] ----- ->>> dc.c = 'whatever' ->>> dc.z = 'secret stash' ----- - -Agora a instância `dc` tem um atributo `c`—mas isso não muda o atributo de classe `c`. -E podemos adicionar um novo atributo `z`. -Isso é o comportamento normal do Python: instâncias regulares podem ter seus próprios atributos, que não aparecem na classe.footnote:[Definir um atributo após o `+__init__+` prejudica a otimização de uso de memória com o compartilhamento das chaves do `+__dict__+`, mencionada na seção <>.]((("", startref="VAmean05")))((("", startref="typehint05")))((("", startref="DCBhint05"))) - -// However, almost always when I see this in real code it's a bad idea. -// I once spent hours chasing a bug that was caused by attributes sneakily stashed in instances, -// like contraband across module borders. -// Also, s - -=== Mais detalhes sobre @dataclass - -Até((("data class builders", "@dataclass", id="DCBatdataclass05")))((("@dataclass", "keyword parameters accepted by"))) agora, só vimos exemplos simples do uso de `@dataclass`. Esse decorador aceita vários argumentos nomeados. Esta é sua assinatura: - -[source, py3] ----- -@dataclass(*, init=True, repr=True, eq=True, order=False, - unsafe_hash=False, frozen=False) ----- - -O `*` na primeira posição significa que os parâmetros restantes são todos parâmetros nomeados. A <> os descreve. - -[[dataclass_options_tbl]] -.Parâmetros nomeados aceitos pelo decorador `@dataclass` -[options="header"] -|==================================================================================================================================================================== -| Option | Meaning | Default | Notes -| `init` | Gera o `+__init__+` | `True` | Ignorado se o -`+__init__+` for implementado pelo usuário. -| `repr` | Gera o `+__repr__+` | `True` | Ignorado se o -`+__repr__+` for implementado pelo usuário. -| `eq` | Gera o `+__eq__+` | `True` | Ignorado se o -`+__eq__+` for implementado pelo usuário. -| `order` | Gera `+__lt__+`, `+__le__+`, `+__gt__+`, `+__ge__+` | `False` | Se `True`, -causa uma exceção se `eq=False`, ou se qualquer dos métodos de comparação que seriam gerados estiver definido ou for herdado. -| `unsafe_hash` | Gera o `+__hash__+` | `False` | Semântica complexa e várias restrições—veja a: https://docs.python.org/pt-br/3/library/dataclasses.html#dataclasses.dataclass[documentação de dataclass]. -| `frozen` | Cria instâncias "imutáveis" | `False` | As instâncias estarão razoavelmente protegidas contra mudanças acidentais, mas não serão realmente imutáveis.footnote:[O `@dataclass` emula a imutabilidade criando um `+__setattr__+` e um `+__delattr__+` -que geram um `dataclass.FrozenInstanceError`—uma subclasse de `AttributeError`—quando o usuário tenta definir ou apagar o valor de um campo.] -|==================================================================================================================================================================== - - -Os((("@dataclass", "default settings"))) defaults são, de fato, as configurações mais úteis para os casos de uso mais comuns. As opções mais prováveis de serem modificadas de seus defaults são: - -`frozen=True`:: Protege as instâncias da classe de modificações acidentais. -`order=True`:: Permite ordenar as instâncias da classe de dados. - -Dada a natureza dinâmica de objetos Python, não é muito difícil para um programador curioso contornar a proteção oferecida por `frozen=True`. Mas os truques necessários são fáceis de perceber em uma revisão do código. - -Se((("@dataclass", "__hash__ method")))((("__hash__"))) tanto o argumento `eq` quanto o `frozen` forem `True`, `@dataclass` produz um método -`+__hash__+` adequado, e daí as instâncias serão _hashable_. -O `+__hash__+` gerado usará dados de todos os campos que não forem individualmente excluídos usando uma opção de campo, que veremos na seção <>. -Se `frozen=False` (o default), `@dataclass` definirá `+__hash__+` como `None`, sinalizando que as instâncias não são hashable, e portanto sobrepondo o `+__hash__+` de qualquer superclasse. - -A https://fpy.li/pep557[PEP 557—Data Classes (_Classe de Dados_)] (EN) diz o seguinte sobre `unsafe_hash`: - -[quote] -____ -Apesar de não ser recomendado, você pode forçar Classes de Dados a criarem um método `+__hash__+` com `unsafe_hash=True`. -Pode ser esse o caso, se sua classe for logicamente imutável e mesmo assim possa ser modificada. -Este é um caso de uso especializado e deve ser considerado com cuidado. -____ - -Deixo o `unsafe_hash` por aqui. -Se você achar que precisa usar essa opção, leia a -https://docs.python.org/pt-br/3/library/dataclasses.html#dataclasses.dataclass[documentação de `dataclasses.dataclass`]. - - -Outras personalizações da classe de dados gerada podem ser feitas no nível dos campos. - -[[field_options_sec]] -==== Opções de campo - -Já((("@dataclass", "field options", id="atdatafield05"))) vimos a opção de campo mais básica: fornecer (ou não) um valor default junto com a dica de tipo. -Os campos de instância declarados se tornarão parâmetros no `+__init__+` gerado. -O Python não permite parâmetros sem um default após parâmetros com defaults. -Então, após declarar um campo com um valor default, cada um dos campos seguintes deve também ter um default. - -Valores default mutáveis são a fonte mais comum de bugs entre desenvolvedores Python iniciantes. -Em definições de função, um valor default mutável é facilmente corrompido, quando uma invocação da função modifica o default, mudando o comportamento nas invocações posteriores—um tópico que vamos explorar na seção <> (no <>). -Atributos de classe são frequentemente usados como valores default de atributos para instâncias, inclusive em classes de dados. -E o `@dataclass` usa os valores default nas dicas de tipo para gerar parâmetros com defaults no -`+__init__+`. -Para prevenir bugs, o `@dataclass` rejeita a definição de classe que aparece no <>. - -[[club_wrong_ex]] -._dataclass/club_wrong.py_: essa classe gera um `ValueError` -==== -[source, py3] ----- -include::code/05-data-classes/dataclass/club_wrong.py[tags=CLUBMEMBER] ----- -==== - -Se você carregar o módulo com aquela classe `ClubMember`, o resultado será esse: - -[source, bash] ----- -$ python3 club_wrong.py -Traceback (most recent call last): - File "club_wrong.py", line 4, in - class ClubMember: - ...several lines omitted... -ValueError: mutable default for field guests is not allowed: -use default_factory ----- - -A mensagem do `ValueError` explica o problema e sugere uma solução: usar a `default_factory`. O <> mostra como corrigir a `ClubMember`. - -[[club_ex]] -._dataclass/club.py_: essa definição de `ClubMember` funciona -==== -[source, py3] ----- -include::code/05-data-classes/dataclass/club.py[] ----- -==== - -No campo `guests` do <>, em vez de uma lista literal, o valor default é definido chamando a função `dataclasses.field` com `default_factory=list`. - -O parâmetro `default_factory` permite que você forneça uma função, classe ou qualquer outro invocável, que será chamado com zero argumentos, para gerar um valor default a cada vez que uma instância da classe de dados for criada. Dessa forma, cada instância de `ClubMember` terá sua própria `list`—ao invés de todas as instâncias compartilharem a mesma `list` da classe, que raramente é o que queremos, e muitas vezes é um bug. - -[WARNING] -==== -É bom que `@dataclass` rejeite definições de classe com uma `list` default em um campo. Entretanto, entenda que isso é uma solução parcial, que se aplica apenas a `list`, `dict` e `set`. -Outros valores mutáveis usados como default não serão apontados por `@dataclass`. -É sua responsabilidade entender o problema e se lembrar de usar uma _factory_ default para definir valores default mutáveis. -==== - -Se você estudar a documentação do módulo https://docs.python.org/pt-br/3/library/dataclasses.html[`dataclasses`], verá um campo `list` definido com uma sintaxe nova, como no <>. - -[[club_generic_ex]] -._dataclass/club_generic.py_: essa definição de `ClubMember` é mais precisa -==== -[source, py3] ----- -include::code/05-data-classes/dataclass/club_generic.py[] ----- -==== -<1> `list[str]` significa "uma lista de str." - -A nova sintaxe `list[str]` é um tipo genérico parametrizado: desde o Python 3.9, -o tipo embutido `list` aceita aquela notação com colchetes para especificar o tipo dos itens da lista. - -[WARNING] -==== -Antes do Python 3.9, as coleções embutidas não suportavam a notação de tipagem genérica. Como uma solução temporária, há tipos correspondentes de coleções no módulo `typing`. Se você precisa de uma dica de tipo para uma `list` parametrizada no Python 3.8 ou anterior, você tem que importar e usar o tipo `List` de `typing`: `List[str]`. -Leia mais sobre isso na caixa <>. -==== - -Vamos tratar dos tipos genéricos no <>. Por ora, observe que o <> e o <> estão ambos corretos, e que o verificador de tipagem Mypy não reclama de nenhuma das duas definições de classe. - -A diferença é que aquele `guests: list` significa que `guests` pode ser uma `list` de objetos de qualquer natureza, enquanto `guests: list[str]` diz que `guests` deve ser uma `list` na qual cada item é uma `str`. -Isso permite que o verificador de tipos encontre (alguns) bugs em código que insira itens inválidos na lista, ou que leia itens dali. - -A `default_factory` é possivelmente a opção mais comum da função `field`, mas há várias outras, listadas na <>. - -[[field_options_tbl]] -.Argumentos nomeados aceitos pela função `field` -[options="header"] -|==================================================================================================================================================================== -| Option | Meaning | Default -| `default` | Valor default para o campo | `_MISSING_TYPE` footnote:[`dataclass._MISSING_TYPE` é um valor sentinela, indicando que a opção não foi fornecida. Ele existe para que se possa definir `None` como um valor default efetivo, um caso de uso comum.] -| `default_factory` | função com 0 parâmetros usada para produzir um valor default | `_MISSING_TYPE` -| `init` | Incluir o campo nos parâmetros de `+__init__+` | `True` -| `repr` | Incluir o campo em `+__repr__+` | `True` -| `compare` | Usar o campo nos métodos de comparação `+__eq__+`, `+__lt__+`, etc. | `True` -| `hash` | Incluir o campo no cálculo de `+__hash__+` | ++None++footnote:[A opção `hash=None` significa que o campo será usado em `+__hash__+` apenas se `compare=True`.] -| `metadata` | Mapeamento com dados definidos pelo usuário; ignorado por `@dataclass` | `None` -|==================================================================================================================================================================== - -A opção `default` existe porque a chamada a `field` toma o lugar do valor default na anotação do campo. Se você quisesse criar um campo `athlete` com o valor default `False`, e também omitir aquele campo do método `+__repr__+`, escreveria o seguinte: - -[source, py3] ----- -@dataclass -class ClubMember: - name: str - guests: list = field(default_factory=list) - athlete: bool = field(default=False, repr=False) ----- - - -==== Processamento pós-inicialização - -O((("@dataclass", "post-init processing", id="atdatapost05")))((("__init__")))((("__post_init__"))) método `+__init__+` gerado por `@dataclass` apenas recebe os argumentos passados e os atribui—ou seus valores default, se o argumento não estiver presente—aos atributos de instância, que são campos da instância. Mas pode ser necessário fazer mais que isso para inicializar a instância. Se for esse o caso, você pode fornecer um método `+__post_init__+`. -Quando esse método existir, `@dataclass` acrescentará código ao `+__init__+` gerado para invocar -`+__post_init__+` como o último passo da inicialização. - -Casos de uso comuns para `+__post_init__+` são validação e o cálculo de valores de campos baseado em outros campos. Vamos estudar um exemplo simples, que usa `+__post_init__+` pelos dois motivos. - -Primeiro, dê uma olhada no comportamento esperado de uma subclasse de `ClubMember`, chamada `HackerClubMember`, como descrito por doctests no <>. - -[[hackerclub_doctests_ex]] -._dataclass/hackerclub.py_: doctests para `HackerClubMember` -==== -[source, py3] ----- -include::code/05-data-classes/dataclass/hackerclub.py[tags=DOCTESTS] ----- -==== - -Observe que precisamos fornecer `handle` como um argumento nomeado, pois `HackerClubMember` herda `name` e `guests` de `ClubMember`, e acrescenta o campo `handle`. A docstring gerada para `HackerClubMember` mostra a ordem dos campos na chamada de inicialização: - -[source, pycon] ----- ->>> HackerClubMember.__doc__ -"HackerClubMember(name: str, guests: list = , handle: str = '')" ----- - -Aqui `` é um caminho mais curto para dizer que algum invocável vai produzir o valor default para `guests` (no nosso caso, a fábrica é a classe `list`). -O ponto é o seguinte: para fornecer um `handle` mas não um `guests`, precisamos passar `handle` como um argumento nomeado. - -A seção https://docs.python.org/pt-br/3/library/dataclasses.html#inheritance["Herança] na documentação do módulo `dataclasses` explica como a ordem dos campos é analisada quando existem vários níveis de herança. - -[NOTE] -==== -No <> vamos falar sobre o uso indevido da herança, especialmente quando as superclasses não são abstratas. -Criar uma hierarquia de classes de dados é, em geral, uma má ideia, mas nos serviu bem aqui para tornar o <> mais curto, e permitir que nos concentrássemos na declaração do campo `handle` e na validação com -`+__post_init__+`. -==== - -O <> mostra a implementação. - -[[hackerclub_ex]] -._dataclass/hackerclub.py_: código para `HackerClubMember` -==== -[source, py3] ----- -include::code/05-data-classes/dataclass/hackerclub.py[tags=HACKERCLUB] ----- -==== -<1> `HackerClubMember` estende `ClubMember`. -<2> `all_handles` é um atributo de classe. -<3> `handle` é um campo de instância do tipo `str`, com uma string vazia como valor default; isso o torna opcional. -<4> Obtém a classe da instância. -<5> Se `self.handle` é a string vazia, a define como a primeira parte de `name`. -<6> Se `self.handle` está em `cls.all_handles`, gera um `ValueError`. -<7> Insere o novo `handle` em `cls.all_handles`. - - -O <> funciona como esperado, mas não é satisfatório pra um verificador estático de tipos. -A seguir veremos a razão disso, e como resolver o problema.((("", startref="atdatapost05"))) - -==== Atributos de classe tipados - -Se((("@dataclass", "typed class attributes"))) verificarmos os tipos de <> com o Mypy, seremos repreendidos: - -[source] ----- -$ mypy hackerclub.py -hackerclub.py:37: error: Need type annotation for "all_handles" -(hint: "all_handles: Set[] = ...") -Found 1 error in 1 file (checked 1 source file) ----- - -Infelizmente, a dica fornecida pelo Mypy (versão 0.910 quando essa seção foi revisada) não é muito útil no contexto do uso de `@dataclass`. -Primeiro, ele sugere usar `Set`, mas desde o Python 3.9 podemos usar `set`—sem a necessidade de importar `Set` de `typing`. -E mais importante, se acrescentarmos uma dica de tipo como `set[…]` a `all_handles`, `@dataclass` vai encontrar essa anotação e transformar `all_handles` em um campo de instância. -Vimos isso acontecer na seção <>. - -A forma de contornar esse problema definida na -https://fpy.li/5-11[PEP 526—Syntax for Variable Annotations (_Sintaxe para Anotações de Variáveis_)] (EN) -é horrível. -Para criar uma variável de classe com uma dica de tipo, precisamos usar um pseudo-tipo chamado `typing.ClassVar`, que aproveita a notação de tipos genéricos (`[]`) para definir o tipo da variável e também para declará-la como um atributo de classe. - -Para fazer felizes tanto o verificador de tipos quando o `@dataclass`, deveríamos declarar o `all_handles` do <> assim: - -[source, py3] ----- - all_handles: ClassVar[set[str]] = set() ----- - -Aquela dica de tipo está dizendo o seguinte: - -[quote] -____ -`all_handles` é um atributo de classe do tipo ++set++-de-++str++, com um `set` vazio como valor default. -____ - -Para escrever aquela anotação precisamos também importar `ClassVar` do módulo `typing`. - -O decorador `@dataclass` não se importa com os tipos nas anotações, exceto em dois casos, e este é um deles: se o tipo for `ClassVar`, um campo de instância não será gerado para aquele atributo. - -O outro caso onde o tipo de um campo é relevante para `@dataclass` é quando declaramos _variáveis apenas de inicialização_, nosso próximo tópico. - - -==== Variáveis de inicialização que não são campos - -Algumas((("@dataclass", "init-only variables")))((("variables", "init-only variables"))) vezes pode ser necessário passar para `+__init__+` argumentos que não são campos de instância. -Tais argumentos são chamados "argumentos apenas de inicialização" (_init-only variables_) pela https://docs.python.org/pt-br/3/library/dataclasses.html#init-only-variables[documentação de `dataclasses`]. -Para declarar um argumento desses, o módulo `dataclasses` oferece o pseudo-tipo `InitVar`, que usa a mesma sintaxe de `typing.ClassVar`. -O exemplo dados na documentação é uma classe de dados com um campo inicializado a partir de um banco de dados, e o objeto banco de dados precisa ser passado para o `+__init__+`. - -O <> mostra o código que ilustra a seção https://docs.python.org/pt-br/3/library/dataclasses.html#init-only-variables["Variáveis de inicialização apenas"]. - -[[initvar_ex]] -.Exemplo da documentação do módulo https://docs.python.org/pt-br/3/library/dataclasses.html#init-only-variables[`dataclasses`] -==== -[source, py3] ----- -@dataclass -class C: - i: int - j: int | None = None - database: InitVar[DatabaseType | None] = None - - def __post_init__(self, database): - if self.j is None and database is not None: - self.j = database.lookup('j') - -c = C(10, database=my_database) ----- -==== - -Veja como o atributo `database` é declarado. `InitVar` vai evitar que `@dataclass` trate `database` como um campo regular. -Ele não será definido como um atributo de instância, e a função `dataclasses.fields` não vai listá-lo. -Entretanto, `database` será um dos argumentos aceitos pelo `+__init__+` gerado, e também será passado para o `+__post_init__+`. Ao escrever aquele método é preciso adicionar o argumento correspondente à sua assinatura, como mostra o <>. - -Esse longo tratamento de `@dataclass` cobriu os recursos mais importantes desse decorador—alguns deles apareceram em seções anteriores, como na <>, onde falamos em paralelo das três fábricas de classes de dados. A https://docs.python.org/pt-br/3/library/dataclasses.html#init-only-variables[documentação de `dataclasses`] e a https://fpy.li/pep526[PEP 526--Syntax for Variable Annotations (_Sintaxe para Anotações de Variáveis_)] (EN) têm todos os detalhes. - -Na próxima seção apresento um exemplo mais completo com o `@dataclass`. - - -[[dc_resource_sec]] -==== Exemplo de @dataclass: o registro de recursos do Dublin Core - -Frequentemente as classes((("@dataclass", "example using", id="atdataexample05")))((("Dublin Core Schema"))) criadas com o `@dataclass` vão ter mais campos que os exemplos muito curtos apresentados até aqui. -O https://fpy.li/5-12[Dublin Core] (EN) oferece a fundação para um exemplo mais típico de `@dataclass`. - -[quote, Dublin Core na Wikipedia] -____ -O Dublin Core é um esquema de metadados que visa descrever objetos digitais, tais como, videos, sons, imagens, textos e sites na web. Aplicações de Dublin Core utilizam XML e o RDF (Resource Description Framework).footnote:[Fonte: O artigo https://pt.wikipedia.org/wiki/Dublin_Core[Dublin Core] na Wikipedia.] -____ - -O padrão define 15 campos opcionais; a classe `Resource`, no <>, usa 8 deles. - -[[resource_ex]] -._dataclass/resource.py_: código de `Resource`, uma classe baseada nos termos do Dublin Core -==== -[source, py3] ----- -include::code/05-data-classes/dataclass/resource.py[tags=DATACLASS] ----- -==== -<1> Esse `Enum` vai fornecer valores de um tipo seguro para o campo `Resource.type`. -<2> `identifier` é o único campo obrigatório. -<3> `title` é o primeiro campo com um default. Isso obriga todos os campos abaixo dele a fornecerem defaults. -<4> O valor de `date` pode ser uma instância de `datetime.date` ou `None`. -<5> O default do campo `type` é `ResourceType.BOOK`. - - -O <> mostra um doctest, para demonstrar como um registro `Resource` aparece no código. - -[[resource_doctest_ex]] -._dataclass/resource.py_: código de `Resource`, uma classe baseada nos termos do Dublin Core -==== -[source, py3] ----- -include::code/05-data-classes/dataclass/resource.py[tags=DOCTEST] ----- -==== - -O `+__repr__+` gerado pelo `@dataclass` é razoável, -mas podemos torná-lo mais legível. -Esse é o formato que queremos para `repr(book)`: - -[source, py3] ----- -include::code/05-data-classes/dataclass/resource_repr.py[tags=DOCTEST] ----- - -O <> é o código para o `+__repr__+`, produzindo o formato que aparece no trecho anterior. -Esse exemplo usa `dataclass.fields` para obter os nomes dos campos da classe de dados. - - -[[resource_repr_ex]] -.`dataclass/resource_repr.py`: código para o método `+__repr__+`, implementado na classe `Resource` do <> -==== -[source, py3] ----- -include::code/05-data-classes/dataclass/resource_repr.py[tags=REPR] ----- -==== -<1> Dá início à lista `res`, para criar a string de saída com o nome da classe e o parênteses abrindo. -<2> Para cada campo `f` na classe... -<3> ...obtém o atributo nomeado da instância. -<4> Anexa uma linha indentada com o nome do campo e `repr(value)`—é isso que o `!r` faz. -<5> Acrescenta um parênteses fechando. -<6> Cria uma string de múltiplas linhas a partir de `res`, e devolve essa string. - -Com esse exemplo, inspirado pelo espírito de Dublin, Ohio, concluímos nosso passeio pelas fábricas de classes de dados do Python. - -Classes de dados são úteis, mas podem estar sendo usadas de forma excessiva em seu projeto. A próxima seção explica isso.((("", startref="atdataexample05")))((("", startref="DCBatdataclass05"))) - -[[dataclass_code_smell_sec]] -=== A classe de dados como _cheiro no código_ - -Independente((("data class builders", "data class as code smell", id="DCBsmell05")))((("code smells", id="codesmells05"))) de você implementar uma classe de dados escrevendo todo o código ou aproveitando as facilidades oferecidas por alguma das fábricas de classes descritas nesse capítulo, fique alerta: isso pode sinalizar um problema em seu design. - -No pass:[Refactoring: Improving the Design of Existing Code (Refatorando: Melhorando o Design de Código Existente), 2nd ed.] (Addison-Wesley), Martin Fowler e Kent Beck apresentam um catálogo de "_cheiros no código_"footnote:[NT: _Code smell_ em geral não é traduzido na bibliografia em português—uma tradução quase literal seria "fedor no código". Uma tradução mais gentil pode ser "cheiro no código", adotado aqui. Mais gentil e menos enviesada: um "cheiro no código" nem sempre é indicação de um problema.]—padrões no código que podem indicar a necessidade de refatoração. O verbete entitulado "Data Class" (_Classe de Dados_) começa assim: - -[quote] -____ -Essas são classes que tem campos, métodos para obter e definir os campos, e nada mais. -Tais classes são recipientes burros de dados, e muitas vezes são manipuladas de forma excessivamente detalhada por outras classes. -____ - -No site pessoal de Fowler, há um post muito esclarecedor chamado https://fpy.li/5-14["Code Smell" (_Cheiro no Código_)] (EN). Esse texto -é muito relevante para nossa discussão, pois o autor usa a _classe de dados_ como um exemplo de _cheiro no código_, e sugere alternativas para lidar com ele. -Abaixo está a tradução integral daquele artigo.footnote:[Eu tenho a felicidade de ter Martin Fowler como colega de trabalho na Thoughtworks, estão precisei de apenas 20 minutos para obter sua permissão.] - - -[[code_smell_essay]] -.Cheiros no Código -**** - -*De Martin Fowler* - -Um cheiro no código é um indicação superficial que frequentemente corresponde a um problema mais profundo no sistema. O termo foi inventado por Kent Beck, enquanto ele me ajudava com meu livro, https://fpy.li/5-15[_Refactoring_]. - -A rápida definição acima contém um par de detalhes sutis. -Primeiro, um cheiro é, por definição, algo rápido de detectar—é "cheiroso", como eu disse recentemente. -Um método longo é um bom exemplo disso—basta olhar o código e ver mais de uma dúzia de linhas de Java para meu nariz se contrair. - -O segundo detalhe é que cheiros nem sempre indicam um problema. Alguns métodos longos são bons. É preciso ir mais fundo para ver se há um problema subjacente ali. Cheiros não são inerentemente ruins por si só—eles frequentemente são o indicador de um problema, não o problema propriamente dito. - -Os melhores cheiros são algo fácil de detectar e que, na maioria das vezes, leva a problemas realmente interessantes. Classes de dados (classes contendo só dados e nenhum comportamento [próprio]) são um bom exemplo. Você olha para elas e se pergunta que comportamento deveria fazer parte daquela classe. Então você começa a refatorar, para incluir ali aquele comportamento. Muitas vezes, algumas perguntas simples e essas refatorações iniciais são um passo vital para transformar um objeto anêmico em alguma coisa que realmente tenha classe. - -Uma coisa boa sobre cheiros é sua facilidade de detecção por pessoas inexperientes, mesmo aquelas pessoas que não conhecem o suficiente para avaliar se há mesmo um problema ou , se existir, para corrigi-lo. Soube de um líder de uma equipe de desenvolvimento que elege um "cheiro da semana", e pede às pessoas que procurem aquele cheiro e o apresentem para colegas mais experientes. Fazer isso com um cheiro por vez é uma ótima maneira de ensinar gradualmente os membros da equipe a serem programadores melhores. - -**** - -A principal ideia da programação orientada a objetos é manter o comportamento e os dados juntos, na mesma unidade de código: uma classe. -Se uma classe é largamente utilizada mas não tem qualquer comportamento próprio significativo, é bem provável que o código que interage com as instâncias dessa classe esteja espalhado (ou mesmo duplicado) em métodos e funções ao longo de todo o sistema—uma receita para dores de cabeça na manutenção. -Por isso, as refatorações de Fowler para lidar com uma classe de dados envolvem trazer responsabilidades de volta para a classe. - -Levando o que foi dito acima em consideração, há alguns cenários comuns onde faz sentido ter um classe de dados com pouco ou nenhum comportamento. - -==== A classe de dados como um esboço - -Nesse cenário, a classe de dados é uma implementação simplista inicial de uma classe, para dar início a um novo projeto ou módulo. Com o tempo, a classe deve ganhar seus próprios métodos, deixando de depender de métodos de outras classes para operar sobre suas instâncias. O esboço é temporário; ao final do processo, sua classe pode se tornar totalmente independente da fábrica usada inicialmente para criá-la. - -O Python também é muito usado para resolução rápida de problemas e para experimentaçào, e nesses casos é aceitável deixar o esboço pronto para uso. - -==== A classe de dados como representação intermediária - -Uma classe de dados pode ser útil para criar registros que serão exportados para o JSON ou algum outro formato de intercomunicação, ou para manter dados que acabaram de ser importados, cruzando alguma fronteira do sistema. Todas as fábricas de classes de dados do Python oferecem um método ou uma função para converter uma instância em um `dict` simples, e você sempre pode invocar o construtor com um `dict`, usado para passar argumentos nomeados expandidos com `**`. Um `dict` desses é muito similar a um registro JSON. - -Nesse cenário, as instâncias da classe de dados devem ser tratadas como objetos imutáveis—mesmo que os campos sejam mutáveis, não deveriam ser modificados nessa forma intermediária. Mudá-los significa perder o principal benefício de manter os dados e o comportamento próximos. Quando o processo de importação/exportação exigir mudança nos valores, você deve implementar seus próprios métodos de fábrica, em vez de usar os métodos "as dict" existentes ou os construtores padrão. - -Vamos agora mudar de assunto e aprender como escrever padrões que "casam" com instâncias de classes arbitrárias, não apenas com as sequências e mapeamentos que vimos nas seções -<> e <>.((("", startref="DCBsmell05")))((("", startref="codesmells05"))) - -[[pattern_instances_sec]] -=== Pattern Matching com instâncias de classes - -Padrões de classe((("data class builders", "pattern matching class instances", id="DCBpattern05")))((("pattern matching", "pattern matching class instances", id="PMpattern05"))) 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. -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. -Vamos estudá-las nessa ordem. - -==== Padrões de classe simples - -Já((("simple class patterns"))) vimos um exemplo de padrões de classe simples usados como sub-padrões na seção <>: - -[source, python3] ----- - case [str(name), _, _, (float(lat), float(lon))]: ----- - -//// - -By the way, there is an interesting corolary here: -if I want to do duck typing, -i.e. pattern match attributes of an instance of an arbitrary class, -I can pattern match with `object`: - - >>> class Foo: pass - >>> foo = Foo() - >>> foo.bar = 'baz' - >>> match foo: - ... case object(bar=b): - ... print(b) - ... - baz - -And that's regardless of `object` not taking keyword arguments: - - >>> object(bar='baz') - Traceback (most recent call last): - File "", line 1, in - object(bar='baz') - TypeError: object() takes no arguments - -//// - -Aquele padrão "casa" com uma sequência de quatro itens, onde o primeiro item deve ser uma instância de `str` e o último item deve ser um tupla de dois elementos, com duas instâncias de `float`. - -A sintaxe dos padrões de classe se parece com a invocação de um construtor. -Abaixo temos um padrão de classe que "casa" com valores `float` sem vincular uma variável (o corpo do `case` pode ser referir a `x` diretamente, se necessário): - -[source, python3] ----- - match x: - case float(): - do_something_with(x) ----- - -Mas isso aqui possivelmente será um bug no seu código: - -[source, python3] ----- - match x: - case float: # DANGER!!! - do_something_with(x) ----- - -No exemplo anterior, `case float:` "casa" com qualquer sujeito, pois o Python entende `float` como uma variável, que é então vinculada ao sujeito. - -A sintaxe `float(x)` do padrão simples é um caso especial que se aplica apenas a onze tipos embutidos "abençoados", listados no final da seção https://fpy.li/5-16["Class Patterns" (_Padrões de Classe_)] (EN) da -pass:[PEP 634—Structural Pattern Matching: Specification ((Pattern Matching Estrutural: Especificação)]: - -[source] ----- -bool bytearray bytes dict float frozenset int list set str tuple ----- - -Nessas classes, a variável que parece um argumento do construtor—por exemplo, o `x` em `float(x)`—é vinculada a toda a instância do sujeito ou à parte do sujeito que "casa" com um sub-padrão, como exemplificado por `str(name)` no padrão de sequência que vimos antes: - -[source, python3] ----- - case [str(name), _, _, (float(lat), float(lon))]: ----- - -Se a classe não de um daqueles onze tipos embutidos "abençoados", então essas variáveis parecidas com argumentos representam padrões a serem testados com atributos de uma instância daquela classe. - - -[[keyword_class_patterns_sec]] -==== Padrões de classe nomeados - -Para((("keyword class patterns"))) entender como usar padrões de classe nomeados, -observe a classe `City` e suas cinco instâncias no <>, abaixo. - -[[ex_cities_match]] -.A classe `City` e algumas instâncias -==== -[source, python3] ----- -include::code/05-data-classes/match_cities.py[tags=CITY] ----- -==== - -Dadas essas definições, a seguinte função devolve uma lista de cidades asiáticas: - -[source, python3] ----- -include::code/05-data-classes/match_cities.py[tags=ASIA] ----- - -O padrão `City(continent='Asia')` encontra qualquer instância de `City` onde o atributo `continent` seja igual a `'Asia'`, independente do valor dos outros atributos. - -Para coletar o valor do atributo `country`, você poderia escrever: - -[source, python3] ----- -include::code/05-data-classes/match_cities.py[tags=ASIA_COUNTRIES] ----- - -O padrão `City(continent='Asia', country=cc)` encontra as mesmas cidades asiáticas, como antes, mas agora a variável `cc` está vinculada ao atributo `country` da instância. -Isso inclusive funciona se a variável do padrão também se chamar `country`: - -[source, python3] ----- - match city: - case City(continent='Asia', country=country): - results.append(country) ----- - -Padrões de classe nomeados são bastante legíveis, -e funcionam com qualquer classe que possua 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. - -[[positional_class_patterns_sec]] -==== Padrões de classe posicionais - -Dadas((("positional class patterns"))) as definições do <>, -a seguinte função devolveria uma lista de cidades asiáticas, -usando um padrão de classe posicional: - -[source, python3] ----- -include::code/05-data-classes/match_cities.py[tags=ASIA_POSITIONAL] ----- - -O padrão `City('Asia')` encontra qualquer instância de `City` na qual o valor do primeiro atributo seja `Asia`, independente do valor dos outros atributos. - -Se você quiser obter o valor do atributo `country`, poderia escrever: - -[source, python3] ----- -include::code/05-data-classes/match_cities.py[tags=ASIA_COUNTRIES_POSITIONAL] ----- - -O padrão `City('Asia', _, country)` encontra as mesmas cidades de antes, mas agora variável `country` está vinculada ao terceiro atributo da instância. - -Eu falei do "primeiro" ou do "terceiro" atributos, mas o quê isso realmente significa? - -`City` (ou qualquer classe) funciona com padrões posicionais graças a um atributo de classe especial chamado `+__match_args__+`, -que as fábricas de classe vistas nesse capítulo criam automaticamente. -Esse é o valor de `+__match_args__+` na classe `City`: - -[source, pycon] ----- ->>> City.__match_args__ -('continent', 'name', 'country') ----- - -Como se vê, `+__match_args__+` declara os nomes dos atributos na ordem em que eles serão usados em padrões posicionais. - -Na seção <> vamos escrever código para definir `+__match_args__+` em uma classe que criaremos sem a ajuda de uma fábrica de classes. - -[TIP] -==== -Você pode combinar argumentos nomeados e posicionais em um padrão. -Alguns, mas não todos, os atributos de instância disponíveis para o _match_ podem estar listados no -`+__match_args__+`. -Dessa forma, algumas vezes pode ser necessário usar argumentos nomeados em um padrão, além dos argumentos posicionais. -==== - -Hora de um resumo de capítulo.((("", startref="DCBpattern05")))((("", startref="PMpattern05"))) - - -=== Resumo do Capítulo - -O((("data class builders", "overview of"))) tópico principal desse capítulo foram as fábricas de classes de dados `collections.namedtuple`, `typing.NamedTuple`, e `dataclasses.dataclass`. -Vimos como cada uma delas gera classes de dados a partir de descrições, fornecidas como argumentos a uma função fábrica ou, no caso das duas últimas, a partir de uma declaração `class` com dicas de tipo. -Especificamente, ambas as variantes de tupla produzem subclasses de `tuple`, acrescentando apenas a capacidade de acessar os campos por nome, e criando também um atributo de classe `_fields`, que lista os nomes dos campos na forma de uma tupla de strings. - -A seguir colocamos lado a lado os principais recursos de cada uma das três fábricas de classes, incluindo como extrair dados da instância como um `dict`, como obter os nomes e valores default dos campos, e como criar uma nova instância a partir de uma instância existente. - -Isso levou ao nosso primeiro contato com dicas de tipo, especialmente aquelas usadas para anotar atributos em uma declaração `class`, usando a notação introduzida no Python 3.6 com a https://fpy.li/pep526[PEP 526—Syntax for Variable Annotations (_Sintaxe para Anotações de Variáveis_)] (EN). -O aspecto provavelmente mais surpreeendente das dicas de tipo em geral é o fato delas não terem qualquer efeito durante a execução. -O Python continua sendo uma linguagem dinâmica. -Ferramentas externas, como o Mypy, são necessárias para aproveitar a informação de tipagem na detecção de erros via análise estática do código-fonte. -Após um resumo básico da sintaxe da PEP 526, estudamos os efeitos das anotações em uma classe simples e em classes criadas por `typing.NamedTuple` e por `@dataclass`. - -A seguir falamos sobre os recursos mais usados dentre os oferecidos por `@dataclass`, e sobre a opção `default_factory` da função `dataclasses.field`. -Também demos uma olhada nas dicas de pseudo-tipo especiais `typing.ClassVar` e -`dataclasses.InitVar`, importantes no contexto das classes de dados. -Esse tópico central foi concluído com um exemplo baseado no schema Dublin Core, ilustrando como usar `dataclasses.fields` para iterar sobre os atributos de uma instância de `Resource` em um -`+__repr__+` personalizado. - -Então alertamos contra os possíveis usos abusivos das classes de dados, 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_ funciona com instâncias de qualquer classe como sujeitos—e não apenas das classes criadas com as fábricas apresentadas nesse capítulo. - -[[further_data_class]] -=== Leitura complementar - -A documentação((("data class builders", "further reading on"))) padrão do Python para as fábricas de classes de dados vistas aqui é muito boa, e inclui muitos pequenos exemplos. - -Em especial para `@dataclass`, a maior parte da https://fpy.li/pep557[PEP 557—Data Classes (_Classes de Dados_)] (EN) foi copiada para a documentação do módulo https://docs.python.org/pt-br/3/library/dataclasses.html[`dataclasses`] . -Entretanto, algumas seções informativas da https://fpy.li/pep557[PEP 557] não foram copiadas, -incluindo https://fpy.li/5-18["Why not just use namedtuple?" (_Por que simplesmente não usar namedtuple?_)], -https://fpy.li/5-19["Why not just use typing.NamedTuple?" (_Por que simplesmente não usar typing.NamedTuple?_)], e a seção https://fpy.li/5-20["Rationale" (_Justificativa_)], que termina com a seguinte _Q&A_: - -[quote, Eric V. Smith, PEP 557 "Justificativa"] -____ -Quando não é apropriado usar Classes de Dados? - -Quando for exigida compatibilidade da API com tuplas de dicts. -Quando for exigida validação de tipo além daquela oferecida pelas PEPs 484 e 526 , ou quando for exigida validação ou conversão de valores. -____ - -Em pass:[RealPython.com], Geir Arne Hjelle escreveu um -https://fpy.li/5-22["Ultimate guide to data classes in Python 3.7" (_O guia definitivo das classes de dados no Python 3.7_)] (EN) muito completo. - -Na PyCon US 2018, Raymond Hettinger apresentou https://fpy.li/5-23["Dataclasses: The code generator to end all code generators" (video) (_Dataclasses: O gerador de código para acabar com todos os geradores de código_)] (EN). - -Para mais recursos e funcionalidade avançada, incluindo validação, o -https://fpy.li/5-24[projeto _attrs_] (EN), -liderado por Hynek Schlawack, surgiu anos antes de `dataclasses` e oferece mais facilidades, com a promessa de "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_)". - -A influência do _attrs_ sobre o `@dataclass` é reconhecida por Eric V. Smith na PEP 557. -Isso provavelmente inclui a mais importante decisão de Smith sobre a API: o uso de um decorador de classe em vez de uma classe base ou de uma metaclasse para realizar a tarefa. - -Glyph—fundador do projeto Twisted—escreveu uma excelente introdução à _attrs_ em -https://fpy.li/5-25["The One Python Library Everyone Needs" (_A Biblioteca Python que Todo Mundo Precisa Ter_)] (EN). -A documentação da _attrs_ inclui uma https://fpy.li/5-26[discussão aobre alternativas]. - -O autor de livros, instrutor e cientista maluco da computação Dave Beazley escreveu o -https://fpy.li/5-27[_cluegen_], um outro gerador de classes de dados. -Se você já assistiu alguma palestra do David, sabe que ele é um mestre na metaprogramação Python a partir de princípios básicos. -Então achei inspirador descobrir, no arquivo _README.md_ do _cluegen_, -o caso de uso concreto que o motivou a criar uma alternativa ao `@dataclass` do Python, -e sua filosofia de apresentar uma abordagem para resolver o problema, -ao invés de fornecer uma ferramenta: a ferramenta pode inicialmente ser mais rápida de usar , -mas a abordagem é mais flexível e pode ir tão longe quanto você deseje. - -Sobre a _classe de dados_ como um cheiro no código, a melhor fonte que encontrei foi livro de Martin Fowler, _Refactoring_ ("Refatorando"), 2ª ed. -A versão mais recente não traz a citação da epígrafe deste capitulo, "Classes de dados são como crianças...", mas apesar disso é a melhor edição do livro mais famoso de Fowler, em especial para pythonistas, pois os exemplos são em JavaScript moderno, que é mais próximo do Python que do Java—a linguagem usada na primeira edição. - -O site https://fpy.li/5-28[_Refactoring Guru_ (Guru da Refatoração)] (EN) também tem uma descrição do -https://fpy.li/5-29[data class code smell (_classe de dados como cheiro no código_)] (EN). - - - -.Ponto de vista -**** - -O((("data class builders", "Soapbox discussion")))((("Soapbox sidebars", "@dataclass"))) verbete para https://fpy.li/5-30["Guido"] no -pass:["The Jargon File"] (EN) é sobre Guido van Rossum. Entre outras coisa, 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 se diz que ele possui por causa da frequência irritante com que pedidos de usuários por novos recursos recebem como resposta "Eu implementei isso noite passada mesmo..." -____ - -Por um longo tempo, uma das peças ausentes da sintaxe do Python foi uma forma rápida e padronizada de declarar atributos de instância em uma classe. -Muitas linguagens orientadas a objetos incluem esse recurso. -Aqui está parte da definição da classe `Point` em Smalltalk: - ----- -Object subclass: #Point - instanceVariableNames: 'x y' - classVariableNames: '' - package: 'Kernel-BasicObjects' ----- - -A segunda linha lista os nomes dos atributos de instância `x` e `y`. -Se existissem atributos de classe, eles estariam na terceira linha. - -O Python sempre teve uma forma fácil de declarar um atributo de classe, se ele tiver um valor inicial. -Mas atributos de instância são muito mais comuns, e os programadores Python tem sido obrigados a olhar dentro do método `+__init__+` para encontrá-los, sempre temerosos que podem existir atributos de instância sendo criados em outro lugar na classe—ou mesmo por funções e métodos de outras classes. - -Agora temos o `@dataclass`, viva! - -Mas ele traz seus próprios problemas - -Primeiro, quando você usa `@dataclass`, dicas de tipo não são opcionais. Pelos últimos sete anos, desde a https://fpy.li/pep484[PEP 484—Type Hints (_Dicas de Tipo_)] (EN), nos prometeram que elas sempre seriam opcionais. -Agora temos um novo recurso importante na linguagem que exige dicas de tipo. -Se você não gosta de toda essa tendência de tipagem estática, pode querer usar a https://fpy.li/5-24[`attrs`] no lugar do `@dataclass`. - -Em segundo lugar, a sintaxe da https://fpy.li/pep526[PEP 526] (EN) para anotar atributos de instância e de classe inverte a convenção consagrada para declarações de classe: -tudo que era declarado no nível superior de um bloco `class` era um atributo de classe (métodos também são atributos de classe). -Com a PEP 526 e o `@dataclass`, -qualquer atributo declarado no nível superior com uma dica de tipo se torna um atributo de instância: - -[source, python3] ----- - @dataclass - class Spam: - repeat: int # instance attribute ----- - -Aqui, `repeat` também é um atributo de instância: - -[source, python3] ----- - @dataclass - class Spam: - repeat: int = 99 # instance attribute ----- - -Mas se não houver dicas de tipo, subitamente estamos de volta os bons velhos tempos quando declarações no nível superior da classe pertencem apenas à classe: - -[source, python3] ----- - @dataclass - class Spam: - repeat = 99 # class attribute! ----- - -Por fim, se você desejar anotar aquele atributo de classe com um tipo, -não pode usar tipos regulares, porque então ele se tornará um atributo de instância. -Você tem que recorrer a aquela anotação usando o pseudo-tipo `ClassVar`: - -[source, python3] ----- - @dataclass - class Spam: - repeat: ClassVar[int] = 99 # aargh! ----- - -Aqui estamos falando sobre uma exceçao da exceção da regra. -Me parece algo muito pouco pythônico. - -Não tomei parte nas discussões que levaram à PEP 526 ou à -https://fpy.li/pep557[PEP 557—Data Classes (_Classes de Dados_)], -mas aqui está uma sintaxe alternativa que eu gostaria de ver: - -[source, python3] ----- -@dataclass -class HackerClubMember: - .name: str # <1> - .guests: list = field(default_factory=list) - .handle: str = '' - - all_handles = set() # <2> ----- -<1> Atributos de instância devem ser declarados com um prefixo `.`. -<2> Qualquer nome de atributo que não tenha um prefixo `.` é um atributo de classe (como sempre foram). - -A gramática da linguagem teria que mudar para acomodar isso. -Mas acho essa forma muito legível, e ela evita o problema da exceção-da-exceção. - -Queria poder pegar a máquina do tempo de Gudo emprestada e voltar a 2017, para convencer os desenvolvedores principais a aceitarem essa ideia. -**** diff --git a/capitulos/cap06.adoc b/capitulos/cap06.adoc deleted file mode 100644 index b515f0f5..00000000 --- a/capitulos/cap06.adoc +++ /dev/null @@ -1,914 +0,0 @@ -[[mutability_and_references]] -== Referências, mutabilidade, e memória -:xrefstyle: short -:example-number: 0 -:figure-number: 0 - -[quote, Adaptado de “Alice Através do Espelho e o que Ela Encontrou Lá”, de Lewis Caroll] -____ -“Você está triste,” disse o Cavaleiro em um tom de voz ansioso: “deixe eu cantar para você uma canção reconfortante. […] O nome da canção se chama ‘OLHOS DE HADOQUE’.” - -“Oh, esse é o nome da canção?,” disse Alice, tentando parecer interessada. - -“Não, você não entendeu,” retorquiu o Cavaleiro, um pouco irritado. “É assim que o nome É CHAMADO. O nome na verdade é ‘O ENVELHECIDO HOMEM VELHO.‘” -____ -Alice e o Cavaleiro((("object references", "distinction between objects and their names"))) dão o tom do que veremos nesse capítulo. O tema é a distinção entre objetos e seus nomes; um nome não é o objeto; o nome é algo diferente. - -Começamos o capítulo apresentando uma metáfora para variáveis em Python: variáveis são rótulos, não caixas. Se variáveis de referência não são novidade para você, a analogia pode ainda ser útil para ilustrar questões de aliasing (“apelidamento”) para alguém. - -Depois discutimos os conceitos de identidade, valor e apelidamento de objetos. Uma característica surpreendente das tuplas é revelada: elas são imutáveis, mas seus valores podem mudar. Isso leva a uma discussão sobre cópias rasas e profundas. Referências e parâmetros de funções são o tema seguinte: o problema com parâmetros mutáveis por default e formas seguras de lidar com argumentos mutáveis passados para nossas funções por clientes. - -As últimas seções do capítulo tratam de coleta de lixo (“garbage collection”), o comando `del` e de algumas peças que o Python prega com objetos imutáveis. - -É um capítulo bastante árido, mas os tópicos tratados podem causar muitos bugs sutis em programas reais em Python. - -=== Novidades nesse capítulo - -Os tópicos tratados aqui são muito estáveis e fundamentais. Não foi introduzida nenhuma mudança digna de nota nesta segunda edição. - -Acrescentei um exemplo usando `is` para testar a existência de um objeto sentinela, e um aviso sobre o mau uso do operador `is` no final de <>. - -Este capítulo estava na Parte IV, mas decidi abordar esses temas mais cedo, pois eles funcionam melhor como o encerramento da Parte II, “Estruturas de Dados”, que como abertura de “Práticas de Orientação a Objetos" - -[NOTE] -==== -A seção sobre “Referências Fracas” da primeira edição deste livro agora é -https://fpy.li/weakref[um post em _fluentpython.com_]. -==== - -Vamos começar desaprendendo que uma variável é como uma caixa onde você guarda dados. - - -=== Variáveis não são caixas - -Em 1997, ((("object references", "variables as labels versus boxes", id="ORvar06")))((("variables", "as labels versus boxes", secondary-sortas="labels versus boxes", id="Vlabel06"))) fiz um curso de verão sobre Java no MIT. A professora, Lynn Stein footnote:[Lynn Andrea Stein é uma aclamada educadora de ciências da computação. Ela -https://fpy.li/6-1[atualmente leciona na Olin College of Engineering (EN)].] -, apontou que a metáfora comum, de “variáveis como caixas”, na verdade, atrapalha o entendimento de variáveis de referência em linguagens orientadas a objetos. As variáveis em Python são como variáveis de referência em Java; uma metáfora melhor é pensar em uma variável como um rótulo (ou etiqueta) que associa um nome a um objeto. O exemplo e a figura a seguir ajudam a entender o motivo disso. - -<> é uma interação simples que não pode ser explicada por “variáveis como caixas”. -A <> ilustra o motivo de metáfora da caixa estar errada em Python, enquanto etiquetas apresentam uma imagem mais útil para entender como variáveis funcionam. - -[[ex_a_b_refs]] -.As variáveis a e b mantém referências para a mesma lista, não cópias da lista. -==== -[source, pycon] ----- ->>> a = [1, 2, 3] <1> ->>> b = a <2> ->>> a.append(4) <3> ->>> b <4> -[1, 2, 3, 4] ----- -==== -<1> Cria uma lista [1, 2, 3] e a vincula à variável `a`. -<2> Vincula a variável `b` ao mesmo valor referenciado por `a`. -<3> Modifica a lista referenciada por `a`, anexando um novo item. -<4> É possível ver o efeito através da variável `b`. Se você pensar em `b` como uma caixa que guardava uma cópia de -`[1, 2, 3]` da caixa `a`, este comportamento não faz sentido. - - -[[var-boxes-x-labels]] -.Se você imaginar variáveis como caixas, não é possível entender a atribuição em Python; por outro lado, pense em variáveis como etiquetas autocolantes e <> é facilmente explicável. -image::images/flpy_0601.png[Boxes and labels diagram] - -Assim, a instrução `b = a` não copia o conteúdo de uma caixa `a` para uma caixa `b`. -Ela cola uma nova etiqueta `b` no objeto que já tem a etiqueta `a`. - -A professora Stein também falava sobre atribuição de uma maneira bastante específica. -Por exemplo, quando discutia sobre um objeto representando uma gangorra em uma simulação, -ela dizia: -“A variável g foi atribuída à gangorra”, mas nunca “A gangorra foi atribuída à variável g”. -Com variáveis de referência, -faz muito mais sentido dizer que a variável é atribuída a um objeto, não o contrário. -Afinal, o objeto é criado antes da atribuição. -<> prova que o lado direito de uma atribuição é processado primeiro. - -Já que o verbo “atribuir” é usado de diferentes maneiras, -uma alternativa útil é “vincular”: -a declaração de atribuição em Python `x = …` vincula o nome `x` -ao objeto criado ou referenciado no lado direito. -E o objeto precisa existir antes que um nome possa ser vinculado a ele, -como demonstra <>. - -[[ex_var_assign_after]] -.Variáveis são vinculadas a objetos somente após os objetos serem criados -==== -[source, pycon] ----- ->>> class Gizmo: -... def __init__(self): -... print(f'Gizmo id: {id(self)}') -... ->>> x = Gizmo() -Gizmo id: 4301489152 <1> ->>> y = Gizmo() * 10 <2> -Gizmo id: 4301489432 <3> -Traceback (most recent call last): - File "", line 1, in -TypeError: unsupported operand type(s) for *: 'Gizmo' and 'int' ->>> ->>> dir() <4> -['Gizmo', '__builtins__', '__doc__', '__loader__', '__name__', -'__package__', '__spec__', 'x'] ----- -==== -<1> A saída `Gizmo id: …` é um efeito colateral da criação de uma instância de `Gizmo`. -<2> Multiplicar uma instância de `Gizmo` levanta uma exceção. -<3> Aqui está a prova que um segundo `Gizmo` foi de fato instanciado antes que a multiplicação fosse tentada. -<4> Mas a variável `y` nunca foi criada, porque a exceção aconteceu enquanto a parte direita da atribuição estava sendo executada. - -[TIP] -==== -Para entender uma atribuição em Python, leia primeiro o lado direito: é ali que o objeto é criado ou recuperado. Depois disso, a variável do lado esquerdo é vinculada ao objeto, como uma etiqueta colada a ele. Esqueça as caixas. -==== - -Como variáveis são apenas meras etiquetas, nada impede que um objeto tenha várias etiquetas vinculadas a si. Quando isso acontece, você tem _apelidos_ (aliases), nosso próximo tópico.((("", startref="ORvar06")))((("", startref="Vlabel06"))) - - -=== Identidade, igualdade e apelidos - -Lewis Carroll((("object references", "aliasing", id="ORalias06")))((("aliasing", id="alias06"))) é o pseudônimo literário do Prof. Charles Lutwidge Dodgson. O Sr. Carroll não é apenas igual ao Prof. Dodgson, eles são exatamente a mesma pessoa. <> expressa essa ideia em Python. - -[[ex_equal_and_same]] -.`charles` e `lewis` se referem ao mesmo objeto -==== -[source, pycon] ----- ->>> charles = {'name': 'Charles L. Dodgson', 'born': 1832} ->>> lewis = charles <1> ->>> lewis is charles -True ->>> id(charles), id(lewis) <2> -(4300473992, 4300473992) ->>> lewis['balance'] = 950 <3> ->>> charles -{'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950} ----- -==== -<1> `lewis` é um apelido para `charles`. -<2> O operador `is` e a função `id` confirmam essa afirmação. -<3> Adicionar um item a `lewis` é o mesmo que adicionar um item a `charles`. - -Entretanto, suponha que um impostor—vamos chamá-lo de Dr. Alexander Pedachenko—diga -que é o verdadeiro Charles L. Dodgson, nascido em 1832. -Suas credenciais podem ser as mesmas, mas o Dr. Pedachenko não é o Prof. Dodgson. <> ilustra esse cenário. - -[[alias_x_copy]] -.`charles` e `lewis` estão vinculados ao mesmo objeto; `alex` está vinculado a um objeto diferente de valor igual. -image::images/flpy_0602.png[Alias x copy diagram] - -<> implementa e testa o objeto +alex+ como apresentado em <>. - -[[ex_equal_not_same]] -.`alex` e `charles` são iguais quando comparados, mas `alex` _não é_ `charles` -==== -[source, pycon] ----- ->>> alex = {'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950} <1> ->>> alex == charles <2> -True ->>> alex is not charles <3> -True ----- -==== -<1> `alex` é uma referência a um objeto que é uma réplica do objeto vinculado a `charles`. -<2> Os objetos são iguais quando comparados devido à implementação de `+__eq__+` na classe `dict`. -<3> Mas são objetos distintos. Essa é a forma pythônica de escrever a negação de uma comparação de identidade: `a is not b`. - -<> é um exemplo de _apelidamento_ (aliasing). Naquele código, `lewis` e `charles` são apelidos: duas variáveis vinculadas ao mesmo objeto. Por outro lado, `alex` não é um apelido para `charles`: essas variáveis estão vinculadas a objetos diferentes. Os ((("== (equality) operator")))((("equality (==) operator")))((("comparison operators"))) objetos vinculados a `alex` e `charles` tem o mesmo __valor__ -- é isso que `==` compara -- mas tem identidades diferentes. - -Na _The Python Language Reference_ (Referência da Linguagem Python), https://docs.python.org/pt-br/3/reference/datamodel.html#objects-values-and-types - está escrito: - -[quote] -____ -A identidade de um objeto nunca muda após ele ter sido criado; você pode pensar nela como o endereço do objeto na memória. O operador `is` compara a identidade de dois objetos; a função ((("functions", "id() function")))((("id() function"))) `id()` retorna um inteiro representando essa identidade. -____ - -O verdadeiro significado do ID de um objeto depende da implementação da linguagem. Em CPython, `id()` retorna o endereço de memória do objeto, mas outro interpretador Python pode retornar algo diferente. O ponto fundamental é que o ID será sempre um valor numérico único, e ele jamais mudará durante a vida do objeto. - -Na prática, nós raramente usamos a função `id()` quando programamos. A verificação de identidade é feita, na maior parte das vezes, com o operador `is`, que compara os IDs dos objetos, então nosso código não precisa chamar `id()` explicitamente. A seguir vamos falar sobre `is` versus `==`. - -[TIP] -==== -Para o revisor técnico Leonardo Rochael, o uso mais frequente de `id()` ocorre durante o processo de debugging, quando o `repr()` de dois objetos são semelhantes, mas você precisa saber se duas referências são apelidos ou apontam para objetos diferentes. Se as referências estão em contextos diferentes -- por exemplo, em stack frames diferentes -- pode não ser viável usar `is`. -==== - - -[[choosing_eq_v_is_sec]] -==== Escolhendo Entre == e is - -O operador ((("is operator"))) `==` compara os valores de objetos (os dados que eles contêm), enquanto `is` compara suas identidades. - -Quando estamos programando, em geral, nos preocupamos mais com os valores que com as identidades dos objetos, então `==` aparece com mais frequência que `is` em programas Python. - -Entretanto, se você estiver comparando uma variável com um singleton (um objeto único) faz mais sentido usar `is`. -O caso mais comum, de longe, é verificar se a variável está vinculada a `None`. -Esta é a forma recomendada de fazer isso: - -[source, python3] ----- -x is None ----- - -E a forma apropriada de escrever sua negação é: - -[source, python3] ----- -x is not None ----- - -`None` é o singleton mais comum que testamos com `is`. -Objetos sentinela são outro exemplo de singletons que testamos com `is`. -Veja um modo de criar e testar um objeto sentinela: - -[source, python3] ----- -END_OF_DATA = object() -# ... many lines -def traverse(...): - # ... more lines - if node is END_OF_DATA: - return - # etc. ----- - -O operador `is` é mais rápido que `==`, pois não pode ser sobrecarregado. Daí o Python não precisa encontrar e invocar métodos especiais para calcular seu resultado, e o processamento é tão simples quanto comparar dois IDs inteiros. Por outro lado, `a == b` é açúcar sintático para `+a.__eq__(b)+`. O método `+__eq__+`, herdado de `object`, compara os IDs dos objetos, então produz o mesmo resultado de `is`. Mas a maioria dos tipos embutidos sobrepõe `+__eq__+` com implementações mais úteis, que levam em consideração os valores dos atributos dos objetos. A determinação da igualdade pode envolver muito processamento--por exemplo, quando se comparam coleções grandes ou estruturas aninhadas com muitos níveis. - -[WARNING] -==== -Normalmente estamos mais interessados na igualdade que na identidade de objetos. Checar se o objeto é `None` é _o único_ caso de uso comum do operador `is`. A maioria dos outros usos que eu vejo quando reviso código estão errados. Se você não estiver seguro, use `==`. Em geral, é o que você quer, e ele também funciona com `None`, ainda que não tão rápido. -==== - -Para concluir essa discussão de identidade versus igualdade, vamos ver como o tipo notoriamente imutável `tuple` não é assim tão invariável quanto você poderia supor. - - -[[tuple-relative-immutable]] -==== A imutabilidade relativa das tuplas - -As tuplas, como((("tuples", "relative immutability of"))) a maioria das coleções em Python -- lists, dicts, sets, etc..— são contêiners: eles armazenam referências para objetos.footnote:[Ao contrário de sequências simples de tipo único, como `str`, `byte` e `array.array`, que não contêm referências e sim seu conteúdo -- caracteres, bytes e números -- armazenado em um espaço contíguo de memória.] - -Se os itens referenciados forem mutáveis, eles poderão mudar, mesmo que tupla em si não mude. Em outras palavras, a imutabilidade das tuplas, na verdade, se refere ao conteúdo físico da estrutura de dados `tupla` (isto é, as referências que ela mantém), e não se estende aos objetos referenciados. - -<> ilustra uma situação em que o valor de uma tupla muda como resultado de mudanças em um objeto mutável ali referenciado. O que não pode nunca mudar em uma tupla é a identidade dos itens que ela contém. - -[[ex_mutable_tuples]] -.`t1` e `t2` inicialmente são iguais, mas a mudança em um item mutável dentro da tupla `t1` as torna diferentes -==== -[source, pycon] ----- ->>> t1 = (1, 2, [30, 40]) <1> ->>> t2 = (1, 2, [30, 40]) <2> ->>> t1 == t2 <3> -True ->>> id(t1[-1]) <4> -4302515784 ->>> t1[-1].append(99) <5> ->>> t1 -(1, 2, [30, 40, 99]) ->>> id(t1[-1]) <6> -4302515784 ->>> t1 == t2 <7> -False ----- -==== -<1> `t1` é imutável, mas `t1[-1]` é mutável. -<2> Cria a tupla `t2`, cujos itens são iguais àqueles de `t1`. -<3> Apesar de serem objetos distintos, quando comparados `t1` e `t2` são iguais, como esperado. -<4> Obtém o ID da lista na posição `t1[-1]`. -<5> Modifica diretamente a lista `t1[-1]`. -<6> O ID de `t1[-1]` não mudou, apenas seu valor. -<7> `t1` e `t2` agora são diferentes - -Essa imutabilidade relativa das tuplas está por trás do enigma <>. Essa também é razão pela qual não é possível gerar o hash de algumas tuplas, como vimos em <>. - -A distinção entre igualdade e identidade tem outras implicações quando você precisa copiar um objeto. Uma cópia é um objeto igual com um ID diferente. Mas se um objeto contém outros objetos, é preciso que a cópia duplique os objetos internos ou eles podem ser compartilhados? Não há uma resposta única. A seguir discutimos esse ponto.((("", startref="ORalias06")))((("", startref="alias06"))) - -=== A princípio, cópias são rasas - -A ((("object references", "shallow copies", id="ORshallow06")))((("shallow copies", id="shallow06")))((("copies", "shallow", id="Cshallow06")))((("lists", "shallow copies of", id="Lshallow06")))forma mais fácil de copiar uma lista (ou a maioria das coleções mutáveis nativas) é usando o construtor padrão do próprio tipo. Por exemplo: - - -[source, pycon] ----- ->>> l1 = [3, [55, 44], (7, 8, 9)] ->>> l2 = list(l1) <1> ->>> l2 -[3, [55, 44], (7, 8, 9)] ->>> l2 == l1 <2> -True ->>> l2 is l1 <3> -False ----- -<1> `list(l1)` cria uma cópia de `l1`. -<2> As cópias são iguais... -<3> ...mas se referem a dois objetos diferentes. - -Para listas e outras sequências mutáveis, o atalho `l2 = l1[:]` também cria uma cópia. - -Contudo, tanto o construtor quanto `[:]` produzem uma _cópia rasa_ (shallow copy). Isto é, o contêiner externo é duplicado, mas a cópia é preenchida com referências para os mesmos itens contidos no contêiner original. Isso economiza memória e não causa qualquer problema se todos os itens forem imutáveis. Mas se existirem itens mutáveis, isso pode gerar surpresas desagradáveis. - -Em <> criamos uma lista contendo outra lista e uma tupla, e então fazemos algumas mudanças para ver como isso afeta os objetos referenciados. - -[TIP] -==== -Se você tem um computador conectado à internet disponível, recomendo fortemente que você assista à animação interativa do <> em https://fpy.li/6-3[Online Python Tutor]. No momento em que escrevo, o link direto para um exemplo pronto no _pythontutor.com_ não estava funcionando de forma estável. -Mas a ferramenta é ótima, então vale a pena gastar seu tempo copiando e colando o código. -==== - - -[[ex_shallow_copy]] -.Criando uma cópia rasa de uma lista contendo outra lista; copie e cole esse código para vê-lo animado no Online Python Tutor -==== -[source, python3] ----- -l1 = [3, [66, 55, 44], (7, 8, 9)] -l2 = list(l1) # <1> -l1.append(100) # <2> -l1[1].remove(55) # <3> -print('l1:', l1) -print('l2:', l2) -l2[1] += [33, 22] # <4> -l2[2] += (10, 11) # <5> -print('l1:', l1) -print('l2:', l2) ----- -==== -<1> `l2` é uma cópia rasa de `l1`. Este estado está representado em <>. -<2> Concatenar `100` a `l1` não tem qualquer efeito sobre `l2`. -<3> Aqui removemos `55` da lista interna `l1[1]`. Isso afeta `l2`, pois `l2[1]` está associado à mesma lista em `l1[1]`. -<4> Para um objeto mutável como a lista referida por `l2[1]`, o operador `+=` altera a lista diretamente. Essa mudança é visível em `l1[1]`, que é um apelido para `l2[1]`. -<5> `+=` em uma tupla cria uma nova tupla e reassocia a variável `l2[2]` a ela. Isso é equivalente a fazer `l2[2] = l2[2] + (10, 11)`. Agora as tuplas na última posição de `l1` e `l2` não são mais o mesmo objeto. Veja <>. - -[[shallow_copy1]] -.Estado do programa imediatamente após a atribuição `l2 = list(l1)` em <>. `l1` e `l2` se referem a listas diferentes, mas as listas compartilham referências para um mesmo objeto interno, a lista `[66, 55, 44]` e para a tupla `(7, 8, 9)`. (Diagrama gerado pelo Online Python Tutor) -image::images/flpy_0603.png[References diagram] - -A saída de <> é <>, e o estado final dos objetos está representado em <>. - -[[ex_shallow_copy_out]] -.Saída de <> -==== -[source, python3] ----- -l1: [3, [66, 44], (7, 8, 9), 100] -l2: [3, [66, 44], (7, 8, 9)] -l1: [3, [66, 44, 33, 22], (7, 8, 9), 100] -l2: [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)] ----- -==== - -[[shallow_copy2]] -.Estado final de `l1` e `l2`: elas ainda compartilham referências para o mesmo objeto lista, que agora contém `[66, 44, 33, 22]`, mas a operação `l2[2] += (10, 11)` criou uma nova tupla com conteúdo `(7, 8, 9, 10, 11)`, sem relação com a tupla `(7, 8, 9)` referenciada por `l1[2]`. (Diagram generated by the Online Python Tutor.) -image::images/flpy_0604.png[References diagram] - -Já deve estar claro que cópias rasas são fáceis de criar, mas podem ou não ser o que você quer. Nosso próximo tópico é a criação de cópias profundas. -((("", startref="ORshallow06")))((("", startref="shallow06")))((("", startref="Cshallow06")))((("", startref="Lshallow06"))) - -[[deep_x_shallow_copies]] -==== Cópias profundas e cópias rasas - -Trabalhar((("object references", "deep copies", id="ORdeep06")))((("copies", "deep", id="Cdeep06")))((("deep copies", id="deepcopy06"))) -com cópias rasas nem sempre é um problema, mas algumas vezes você vai precisar criar cópias profundas -(isto é, cópias que não compartilham referências de objetos internos). -O módulo `copy` oferece as funções `deepcopy` e `copy`, que retornam cópias profundas e rasas de objetos arbitrários. - -Para ilustrar o uso de `copy()` e `deepcopy()`, <> define uma classe simples, -`Bus`, representando um ônibus escolar que é carregado com passageiros, -e então pega ou deixa passageiros ao longo de sua rota. - -[[ex_bus1]] -.Bus pega ou deixa passageiros -==== -[source, py] ----- -include::code/06-obj-ref/bus.py[tags=BUS_CLASS] ----- -==== - -Agora, no <> interativo, vamos criar um objeto +bus+ (+bus1+) e -dois clones—uma cópia rasa (+bus2+) e uma cópia profunda (+bus3+)—para ver o que acontece quando `bus1` deixa um passageiro. - -[[ex_bus1_console]] -.Os efeitos do uso de `copy` versus `deepcopy` -==== -[source, pycon] ----- ->>> import copy ->>> bus1 = Bus(['Alice', 'Bill', 'Claire', 'David']) ->>> bus2 = copy.copy(bus1) ->>> bus3 = copy.deepcopy(bus1) ->>> id(bus1), id(bus2), id(bus3) -(4301498296, 4301499416, 4301499752) <1> ->>> bus1.drop('Bill') ->>> bus2.passengers -['Alice', 'Claire', 'David'] <2> ->>> id(bus1.passengers), id(bus2.passengers), id(bus3.passengers) -(4302658568, 4302658568, 4302657800) <3> ->>> bus3.passengers -['Alice', 'Bill', 'Claire', 'David'] <4> ----- -==== -<1> Usando `copy` e `deepcopy`, criamos três instâncias distintas de `Bus`. -<2> Após `bus1` deixar `'Bill'`, ele também desaparece de `bus2`. -<3> A inspeção do atributo dos `passengers` mostra que `bus1` e `bus2` compartilham o mesmo objeto lista, pois `bus2` é uma cópia rasa de `bus1`. -<4> `bus3` é uma cópia profunda de `bus1`, então seu atributo `passengers` se refere a outra lista. - -Observe que, 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. A função 'deepcopy' lembra dos objetos já copiados, de forma a tratar referências cíclicas de modo elegante. Isso é demonstrado em <>. - -[[ex_cycle1]] -.Referências cíclicas: `b` tem uma referência para `a` e então é concatenado a `a`; ainda assim, `deepcopy` consegue copiar `a`. -==== -[source, pycon] ----- ->>> a = [10, 20] ->>> b = [a, 30] ->>> a.append(b) ->>> a -[10, 20, [[...], 30]] ->>> from copy import deepcopy ->>> c = deepcopy(a) ->>> c -[10, 20, [[...], 30]] ----- -==== - -Além disso, algumas vezes uma cópia profunda pode ser profunda demais. Por exemplo, objetos podem ter referências para recursos externos ou para singletons (objetos únicos) que não devem ser copiados. Você pode controlar o comportamento de `copy` e de `deepcopy` implementando os métodos especiais `+__copy__+` e `+__deepcopy__+`, como descrito em https://docs.python.org/pt-br/3/library/copy.html [documentação do módulo `copy`] - -O compartilhamento de objetos através de apelidos também explica como a passagens de parâmetros funciona em Python, e o problema do uso de tipos mutáveis como parâmetros default. Vamos falar sobre essas questões a seguir.((("", startref="deepcopy06")))((("", startref="Cdeep06")))((("", startref="ORdeep06"))) - - -=== Parâmetros de função como referências - -O((("object references", "function parameters as references", id="ORfparam06")))((("call by sharing")))((("parameters", "parameter passing"))) único modo de passagem de parâmetros em Python é a _chamada por compartilhamento_ (_call by sharing_). É o mesmo modo usado na maioria das linguagens orientadas a objetos, incluindo Javascript, Ruby e Java (em Java isso se aplica aos tipos de referência; tipos primitivos usam a chamada por valor). Chamada por compartilhamento significa que cada parâmetro formal da função recebe uma cópia de cada referência nos argumentos. Em outras palavras, os parâmetros dentro da função se tornam apelidos dos argumentos. - -O resultado desse esquema é que a função pode modificar qualquer objeto mutável passado a ela como parâmetro, mas não pode mudar a identidade daqueles objetos (isto é, ela não pode substituir integralmente um objeto por outro). -<> mostra uma função simples usando `+=` com um de seus parâmetros. Quando passamos números, listas e tuplas para a função, os argumentos originais são afetados de maneiras diferentes. -// O próximo exemplo demonstra: - -[[ex_param_pass]] -.Uma função pode mudar qualquer objeto mutável que receba -==== -[source, pycon] ----- ->>> def f(a, b): -... a += b -... return a -... ->>> x = 1 ->>> y = 2 ->>> f(x, y) -3 ->>> x, y <1> -(1, 2) ->>> a = [1, 2] ->>> b = [3, 4] ->>> f(a, b) -[1, 2, 3, 4] ->>> a, b <2> -([1, 2, 3, 4], [3, 4]) ->>> t = (10, 20) ->>> u = (30, 40) ->>> f(t, u) <3> -(10, 20, 30, 40) ->>> t, u -((10, 20), (30, 40)) ----- -==== -<1> O número `x` não se altera. -<2> A lista `a` é alterada. -<3> A tupla `t` não se altera. - -Outra questão relacionada a parâmetros de função é o uso de valores mutáveis como defaults, discutida a seguir. - - -[[mutable_default_parameter_sec]] -==== Porque evitar tipos mutáveis como default em parâmetros - -Parâmetros opcionais((("mutable parameters", id="muttype06")))((("parameters", "mutable", id="Pmut06"))) com valores default são um ótimo recurso para definição de funções em Python, permitindo que nossas APIs evoluam mantendo a compatibilidade com versões anteriores. -Entretanto, você deve evitar usar objetos mutáveis como valores default em parâmetros. - -Para ilustrar esse ponto, em <>, -modificamos o método `+__init__+` da classe `Bus` de <> para criar `HauntedBus`. -Tentamos ser espertos: em vez do valor default `passengers=None`, -temos `passengers=[]`, para evitar o `if` do `+__init__+` anterior. -Essa "esperteza" causa problemas. - -[[ex_haunted_bus]] -.Uma classe simples ilustrando o perigo de um default mutável -==== -[source, py] ----- -include::code/06-obj-ref/haunted_bus.py[tags=HAUNTED_BUS_CLASS] ----- -==== -<1> Quando o argumento `passengers` não é passado, esse parâmetro é vinculado ao objeto lista default, que inicialmente está vazio. -<2> Essa atribuição torna `self.passengers` um apelido de `passengers`, que por sua vez é um apelido para a lista default, quando um argumento `passengers` não é passado para a função. -<3> Quando os métodos `.remove()` e `.append()` são usados com `self.passengers`, estamos, na verdade, mudando a lista default, que é um atributo do objeto-função. - -<> mostra o comportamento misterioso de `HauntedBus`. - -[[demo_haunted_bus]] -.Ônibus assombrados por passageiros fantasmas -==== -[source, pycon] ----- ->>> bus1 = HauntedBus(['Alice', 'Bill']) <1> ->>> bus1.passengers -['Alice', 'Bill'] ->>> bus1.pick('Charlie') ->>> bus1.drop('Alice') ->>> bus1.passengers <2> -['Bill', 'Charlie'] ->>> bus2 = HauntedBus() <3> ->>> bus2.pick('Carrie') ->>> bus2.passengers -['Carrie'] ->>> bus3 = HauntedBus() <4> ->>> bus3.passengers <5> -['Carrie'] ->>> bus3.pick('Dave') ->>> bus2.passengers <6> -['Carrie', 'Dave'] ->>> bus2.passengers is bus3.passengers <7> -True ->>> bus1.passengers <8> -['Bill', 'Charlie'] ----- -==== -<1> `bus1` começa com uma lista de dois passageiros. -<2> Até aqui, tudo bem: nenhuma surpresa em `bus1`. -<3> `bus2` começa vazio, então a lista vazia default é vinculada a `self.passengers`. -<4> `bus3` também começa vazio, e novamente a lista default é atribuída. -<5> A lista default não está mais vazia! -<6> Agora `Dave`, pego pelo `bus3`, aparece no `bus2`. -<7> O problema: `bus2.passengers` e `bus3.passengers` se referem à mesma lista. -<8> Mas `bus1.passengers` é uma lista diferente. - -O problema é que instâncias de `HauntedBus` que não recebem uma lista de passageiros inicial acabam todas compartilhando a mesma lista de passageiros entre si. - -Este tipo de bug pode ser muito sutil. Como <> demonstra, quando `HauntedBus` recebe uma lista com passageiros como parâmetro, ele funciona como esperado. As coisas estranhas acontecem somente quando `HauntedBus` começa vazio, pois aí `self.passengers` se torna um apelido para o valor default do parâmetro `passengers`. O problema é que cada valor default é processado quando a função é definida -- i.e., normalmente quando o módulo é carregado -- e os valores default se tornam atributos do objeto-função. Assim, se o valor default é um objeto [.keep-together]#mutável# e você o altera, a alteração vai afetar todas as futuras chamadas da [.keep-together]#função#. - -Após executar as linhas do exemplo em <>, você pode inspecionar o objeto -`+HauntedBus.__init__+` e ver os estudantes fantasma assombrando o atributo `+__defaults__+`: - -[source, pycon] ----- ->>> dir(HauntedBus.__init__) # doctest: +ELLIPSIS -['__annotations__', '__call__', ..., '__defaults__', ...] ->>> HauntedBus.__init__.__defaults__ -(['Carrie', 'Dave'],) ----- - -Por fim, podemos verificar que `bus2.passengers` é um apelido vinculado ao primeiro elemento do atributo `+HauntedBus.__init__.__defaults__+`: - -[source, pycon] ----- ->>> HauntedBus.__init__.__defaults__[0] is bus2.passengers -True ----- - -O problema com defaults mutáveis explica porque `None` é normalmente usado como valor default para parâmetros que podem receber valores mutáveis. Em <>, `+__init__+` checa se o argumento `passengers` é `None`. Se for, `self.passengers` é vinculado a uma nova lista vazia. Se `passengers` não for `None`, a implementação correta vincula uma cópia daquele argumento a `self.passengers`. -A próxima seção explica porque copiar o argumento é uma boa prática. - - -[[defensive_argument]] -==== Programação defensiva com argumentos mutáveis - -Ao escrever uma função que recebe um argumento mutável, -você deve considerar com cuidado se o cliente que chama sua função espera que o argumento passado seja modificado. - -Por exemplo, se sua função recebe um `dict` e precisa modificá-lo durante seu processamento, -esse efeito colateral deve ou não ser visível fora da função? -A resposta, na verdade, depende do contexto. -É tudo uma questão de alinhar as expectativas do autor da função com as do cliente da função. - -O último exemplo com ônibus neste capítulo mostra como o `TwilightBus` viola as expectativas -ao compartilhar sua lista de passageiros com seus clientes. -Antes de estudar a implementação, veja como a classe `TwilightBus` funciona pela -perspectiva de um cliente daquela classe, em <>. - -[[demo_twilight_bus]] -.Passageiros desaparecem quando são deixados por um `TwilightBus` -==== -[source, pycon] ----- ->>> basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat'] <1> ->>> bus = TwilightBus(basketball_team) <2> ->>> bus.drop('Tina') <3> ->>> bus.drop('Pat') ->>> basketball_team <4> -['Sue', 'Maya', 'Diana'] ----- -==== -<1> `basketball_team` contém o nome de cinco estudantes. -<2> Um `TwilightBus` é carregado com o time. -<3> O `bus` deixa uma estudante, depois outra. -<4> As passageiras desembarcadas desapareceram do time de basquete! - -`TwilightBus` viola o "Princípio da Menor Surpresa Possível", uma boa prática do design de interfaces.footnote:[Ver https://fpy.li/6-5[_Principle of least astonishment_] (EN).] É certamente espantoso que quando o ônibus deixa uma estudante, seu nome seja removido da escalação do time de basquete. - - -<> é a implementação de `TwilightBus` e uma explicação do [.keep-together]#problema#. - -[[ex_twilight_bus]] -.Uma classe simples mostrando os perigos de mudar argumentos recebidos -==== -[source, py] ----- -include::code/06-obj-ref/twilight_bus.py[tags=TWILIGHT_BUS_CLASS] ----- -==== -[role="pagebreak-before less_space"] -<1> Aqui nós cuidadosamente criamos uma lista vazia quando `passengers` é `None`. -<2> Entretanto, esta atribuição transforma `self.passengers` em um apelido para `passengers`, que por sua vez é um apelido para o argumento efetivamente passado para `+__init__+` (i.e. `basketball_team` em <>). -<3> Quando os métodos `.remove()` e `.append()` são usados com `self.passengers`, estamos, na verdade, modificando a lista original recebida como argumento pelo construtor. - -O problema aqui é que o ônibus está apelidando a lista passada para o construtor. Ao invés disso, ele deveria manter sua própria lista de passageiros. A solução é simples: em `+__init__+`, quando o parâmetro `passengers` é fornecido, `self.passengers` deveria ser inicializado com uma cópia daquela lista, como fizemos, de forma correta, em <>: - -[source, python3] ----- - def __init__(self, passengers=None): - if passengers is None: - self.passengers = [] - else: - self.passengers = list(passengers) <1> ----- -<1> Cria uma cópia da lista `passengers`, ou converte o argumento para `list` se ele não for uma lista. - -Agora nossa manipulação interna da lista de passageiros não afetará o argumento usado para inicializar o ônibus. E com uma vantagem adicional, essa solução é mais flexível: agora o argumento passado no parâmetro `passengers` pode ser uma tupla ou qualquer outro tipo iterável, como `set` ou mesmo resultados de uma consulta a um banco de dados, pois o construtor de `list` aceita qualquer iterável. Ao criar nossa própria lista, estamos também assegurando que ela suporta os métodos necessários, `.remove()` e `.append()`, operações que usamos nos métodos `.pick()` e `.drop()`. - -[TIP] -==== -A menos que um método tenha o objetivo explícito de alterar um objeto recebido como argumento, você deveria pensar bem antes de apelidar tal objeto e simplesmente vinculá-lo a uma variável interna de sua classe. Quando em dúvida, crie uma cópia. Os clientes de sua classe ficarão mais felizes. -Claro, criar uma cópia não é grátis: há um custo de processamento e uso de memória. Entretanto, uma API que causa bugs sutis é, em geral, um problema bem maior que uma que seja um pouco mais lenta ou que use mais recursos. -==== - -Agora vamos conversar sobre um dos comandos mais incompreendidos em Python: `del`.((("", startref="ORfparam06")))((("", startref="muttype06")))((("", startref="Pmut06"))) - -[[del_sec]] -=== del e coleta de lixo - -[quote, “Modelo de Dados” capítulo de A Referência da Linguagem Python] -____ -Os objetos((("object references", "del and garbage collection", id="ORdel06")))((("garbage collection", id="garb06"))) nunca são destruídos explicitamente; no entanto, quando eles se tornam inacessíveis, eles podem ser coletados como lixo. -____ - -A((("del statement", id="del06"))) primeira estranheza sobre `del` é ele não ser uma função, mas um comando. - -Escrevemos `del x` e não `del(x)` -- apesar dessa última forma funcionar também, mas apenas porque as expressões `x` e `(x)` em geral terem o mesmo significado em Python. - -O segundo aspecto surpreendente é que `del` apaga referências, não objetos. A coleta de lixo pode eliminar um objeto da memória como resultado indireto de `del`, se a variável apagada for a última referência ao objeto. Reassociar uma variável também pode reduzir a zero o número de referências a um objeto, causando sua destruição. - -[source, pycon] ----- ->>> a = [1, 2] <1> ->>> b = a <2> ->>> del a <3> ->>> b <4> -[1, 2] ->>> b = [3] <5> ----- -<1> Cria o objeto `[1, 2]` e vincula `a` a ele. -<2> Vincula `b` ao mesmo objeto `[1, 2]`. -<3> Apaga a referência `a`. -<4> `[1, 2]` não é afetado, pois `b` ainda aponta para ele. -<5> Reassociar `b` a um objeto diferente remove a última referência restante a `[1, 2]`. Agora o coletor de lixo pode descartar aquele objeto. - - -[WARNING] -==== -Existe((("__del__"))) um método especial `+__del__+`, -mas ele não causa a remoção de uma instância e não deve ser usado em seu código. -`+__del__+` é invocado pelo interpretador Python quando a instância está prestes a ser destruída, -para dar a ela a chance de liberar recursos externos. -É muito raro ser preciso implementar `+__del__+` em seu código, -mas ainda assim alguns programadores Python perdem tempo codando este método sem necessidade. -O uso correto de `+__del__+` é bastante complexo. -Consulte -https://docs.python.org/pt-br/3/reference/datamodel.html#object.%5C_%5C_del__[`+__del__+`] no capítulo "Modelo de Dados" em _A Referência da Linguagem Python_. -==== - -Em CPython, o algoritmo primário de coleta de lixo é ((("reference counting")))a contagem de referências. Essencialmente, cada objeto mantém uma contagem do número de referências apontando para si. Assim que a contagem chega a zero, o objeto é imediatamente destruído: CPython invoca o método `+__del__+` no objeto (se definido) e daí libera a memória alocada para aquele objeto. Em CPython 2.0, um algoritmo de coleta de lixo geracional foi acrescentado, para detectar grupos de objetos envolvidos em referências cíclicas -- grupos que pode ser inacessíveis mesmo que existam referências restantes, quando todas as referências mútuas estão contidas dentro daquele grupo. Outras implementações de Python tem coletores de lixo mais sofisticados, que não se baseiam na contagem de referências, o que significa que o método `+__del__+` pode não ser chamado imediatamente quando não existem mais referências ao objeto. Veja https://fpy.li/6-7["PyPy, Garbage Collection, and a Deadlock"] (EN) by A. Jesse Jiryu Davis para uma discussão sobre os usos próprios e impróprios de `+__del__+`. - -Para demonstrar o fim da vida de um objeto, <> usa `weakref.finalize` para registrar uma função callback a ser chamada quando o objeto é destruído. - -[[ex_finalize]] -.Assistindo o fim de um objeto quando não resta nenhuma referência apontando para ele - -==== -[source, pycon] ----- ->>> import weakref ->>> s1 = {1, 2, 3} ->>> s2 = s1 <1> ->>> def bye(): <2> -... print('...like tears in the rain.') -... ->>> ender = weakref.finalize(s1, bye) <3> ->>> ender.alive <4> -True ->>> del s1 ->>> ender.alive <5> -True ->>> s2 = 'spam' <6> -...like tears in the rain. ->>> ender.alive -False ----- -==== -<1> `s1` e `s2` são apelidos do mesmo conjunto, `{1, 2, 3}`. -<2> Essa função não deve ser um método associado ao objeto prestes a ser destruído, nem manter uma referência para o objeto. -<3> Registra o callback `bye` no objeto referenciado por `s1`. -<4> O atributo `.alive` é `True` antes do objeto `finalize` ser chamado. -<5> Como vimos, `del` não apaga o objeto, apenas a referência `s1` a ele. -<6> Reassociar a última referência, `s2`, torna `{1, 2, 3}` inacessível. Ele é destruído, o callback `bye` é invocado, e `ender.alive` se torna `False`. - -O ponto principal de <> é mostrar explicitamente que `del` não apaga objetos, mas que objetos podem ser apagados como uma consequência de se tornarem inacessíveis após o uso de `del`. - -Você pode estar se perguntando porque o objeto `{1, 2, 3}` foi destruído em <>. Afinal, a referência `s1` foi passada para a função `finalize`, que precisa tê-la mantido para conseguir monitorar o objeto e invocar o callback. Isso funciona porque `finalize` mantém uma((("weak references"))) _referência fraca_ (_weak reference_) para {1, 2, 3}. Referências fracas não aumentam a contagem de referências de um objeto. Assim, uma referência fraca não evita que o objeto alvo seja destruído pelo coletor de lixo. Referências fracas são úteis em cenários de caching, pois não queremos que os objetos "cacheados" sejam mantidos vivos apenas por terem uma referência no cache.((("", startref="ORdel06")))((("", startref="del06")))((("", startref="garb06"))) - -[NOTE] -==== -Referências fracas são um tópico muito especializado, então decidi retirá-lo dessa segunda edição. Em vez disso, publiquei a nota -https://fpy.li/weakref["Weak References" em _fluentpython.com_]. -==== - -=== Peças que Python prega com imutáveis - -[NOTE] -==== -Esta((("object references", "immutability and"))) seção opcional discute alguns detalhes que, na verdade, não são muito importantes para _usuários_ de Python, e que podem não se aplicar a outras implementações da linguagem ou mesmo a futuras versões de CPython. Entretanto, já vi muita gente tropeçar nesses casos laterais e daí passar a usar o((("is operator"))) operador `is` de forma incorreta, então acho que vale a pena mencionar esses detalhes. -==== - -Eu((("tuples", "immutability and"))) fiquei surpreso em descobrir que, para uma tupla `t`, a chamada `t[:]` não cria uma cópia, mas devolve uma referência para o mesmo objeto. Da mesma forma, `tuple(t)` também retorna uma referência para a mesma tupla.footnote:[Isso está claramente documentado. Digite `help(tuple)` no console do Python e leia: "Se o argumento é uma tupla, o valor de retorno é o mesmo objeto." Pensei que sabia tudo sobre tuplas antes de escrever esse livro.] - -<> demonstra esse fato. - -[[ex_same_tuple]] -.Uma tupla construída a partir de outra é, na verdade, exatamente a mesma tupla. -==== -[source, pycon] ----- ->>> t1 = (1, 2, 3) ->>> t2 = tuple(t1) ->>> t2 is t1 <1> -True ->>> t3 = t1[:] ->>> t3 is t1 <2> -True ----- -==== -<1> `t1` e `t2` estão vinculadas ao mesmo objeto -<2> Assim como `t3`. - -O mesmo comportamento pode ser observado com instâncias de `str`, `bytes` e `frozenset`. Observe que `frozenset` não é uma sequência, então `fs[:]` não funciona se `fs` é um `frozenset`. Mas `fs.copy()` tem o mesmo efeito: ele trapaceia e retorna uma referência ao mesmo objeto, e não uma cópia, como mostra <>.footnote:[Essa mentirinha inofensiva, do método `copy` não copiar nada, é justificável pela compatibilidade da interface: torna `frozenset` mais compatível com `set`. De qualquer forma, não faz nenhuma diferença para o usuário final se dois objetos imutáveis idênticos são o mesmo ou são cópias.] - - -[[ex_same_string]] -.Strings literais podem criar objetos compartilhados. -==== -[source, pycon] ----- ->>> t1 = (1, 2, 3) ->>> t3 = (1, 2, 3) # <1> ->>> t3 is t1 # <2> -False ->>> s1 = 'ABC' ->>> s2 = 'ABC' # <3> ->>> s2 is s1 # <4> -True ----- -==== -<1> Criando uma nova tupla do zero. -<2> `t1` e `t3` são iguais, mas não são o mesmo objeto. -<3> Criando uma segunda `str` do zero. -<4> Surpresa: `a` e `b` se referem à mesma `str`! - -O((("interning"))) compartilhamento de strings literais é uma técnica de otimização chamada _internalização_ (_interning_). O CPython usa uma técnica similar com inteiros pequenos, para evitar a duplicação desnecessária de números que aparecem com muita frequência em programas, como 0, 1, -1, etc. Observe que o CPython não internaliza todas as strings e inteiros, e o critério pelo qual ele faz isso é um detalhe de implementação não documentado. - -[WARNING] -==== -Nunca dependa da internalização de `str` ou `int`! Sempre use `==` em vez de `is` para verificar a igualdade de strings ou inteiros. -A internalização é uma otimização para uso interno do interpretador Python. -==== - -Os truques discutidos nessa seção, incluindo o comportamento de `frozenset.copy()`, são mentiras inofensivas que economizam memória e tornam o interpretador mais rápido. Não se preocupe, elas não trarão nenhum problema, pois se aplicam apenas a tipos imutáveis. Provavelmente, o melhor uso para esse tipo de detalhe é ganhar apostas contra outros Pythonistas.footnote:[Um péssimo uso dessas informações seria perguntar sobre elas quando entrevistando candidatos a emprego ou criando perguntas para exames de "certificação". Há inúmeros fatos mais importantes e úteis para testar conhecimentos de Python.] - -=== Resumo do capítulo - -Todo((("object references", "overview of"))) objeto em Python tem uma identidade, um tipo e um valor. Apenas o valor do objeto pode mudar ao longo do tempo.footnote:[Na verdade, o tipo de um objeto pode ser modificado, bastando para isso atribuir uma classe diferente ao atributo `+__class__+` do objeto. Mas isso é uma perversão, e eu me arrependo de ter escrito essa nota de rodapé.] - -Se duas variáveis se referem a objetos imutáveis de igual valor (`a == b` is `True`), na prática, dificilmente importa se elas se referem a cópias de mesmo valor ou são apelidos do mesmo objeto, porque o valor de objeto imutável não muda, com uma exceção. A exceção são as coleções imutáveis, como as tuplas: se uma coleção imutável contém referências para itens mutáveis, então seu valor pode de fato mudar quando o valor de um item mutável for modificado. Na prática, esse cenário não é tão comum. O que nunca muda numa coleção imutável são as identidades dos objetos mantidos ali. A classe `frozenset` não sofre desse problema, porque ela só pode manter elementos hashable, e o valor de um objeto hashable não pode mudar nunca, por [.keep-together]#definição#. - -O fato de variáveis manterem referências tem muitas consequências práticas para a programação em Python: - -* Uma atribuição simples não cria cópias. -* Uma atribuição composta com `+=` ou `*=` cria novos objetos se a variável à esquerda da atribuição estiver vinculada a um objeto imutável, mas pode modificar um objeto mutável diretamente. -* Atribuir um novo valor a uma variável existente não muda o objeto previamente vinculado à variável. Isso se chama reassociar (rebinding); a variável está agora associada a um objeto diferente. Se aquela variável era a última referência ao objeto anterior, aquele objeto será eliminado pela coleta de lixo. - -[role="pagebreak-before less_space"] -* Parâmetros de função são passados como apelidos, o que significa que a função pode alterar qualquer objeto mutável recebido como argumento. Não há como evitar isso, exceto criando cópias locais ou usando objetos imutáveis (i.e., passando uma tupla em vez de uma lista) -* Usar objetos mutáveis como valores default de parâmetros de função é perigoso, pois se os parâmetros forem modificados pela função, o default muda, afetando todas as chamadas posteriores que usem o default. - -Em CPython, os objetos são descartados assim que o número de referências a eles chega a zero. Eles também podem ser descartados se formarem grupos com referências cíclicas sem nenhuma referência externa ao grupo. - -Em algumas situações, pode ser útil manter uma referência para um objeto que não irá -- por si só -- manter o objeto vivo. Um exemplo é uma classe que queira manter o registro de todas as suas instâncias atuais. Isso pode ser feito com referências fracas, um mecanismo de baixo nível encontrado nas úteis coleções `WeakValueDictionary`, `WeakKeyDictionary`, `WeakSet`, e na função `finalize` do módulo `weakref`. - -Para saber mais, leia https://fpy.li/weakref["Weak References" em _fluentpython.com_]. - - -=== Para saber mais - -O((("object references", "further reading on"))) https://docs.python.org/pt-br/3/reference/datamodel.html[capítulo "Modelo de Dados"] de _A Referência da Linguagem Python_ inicia com uma explicação bastante clara sobre identidades e valores de objetos. - -Wesley Chun, autor da série _Core Python_, apresentou https://fpy.li/6-8[Understanding Python's Memory Model, Mutability, and Methods] (EN) na EuroPython 2011, discutindo não apenas o tema desse capítulo como também o uso de métodos especiais. - -Doug Hellmann escreveu os posts https://fpy.li/6-9["copy – Duplicate Objects"] (EN) e -https://fpy.li/6-10["weakref—Garbage-Collectable References to Objects"] (EN), cobrindo alguns dos tópicos que acabamos de tratar. - -Você pode encontrar mais informações sobre o coletor de lixo geracional do CPython em the https://docs.python.org/pt-br/3/library/gc.html[gc — Interface para o coletor de lixo¶], que começa com a frase "Este módulo fornece uma interface para o opcional garbage collector". O adjetivo "opcional" usado aqui pode ser surpreendente, mas o https://docs.python.org/pt-br/3/reference/datamodel.html[capítulo "Modelo de Dados"] também afirma: - -[quote] -____ -Uma implementação tem permissão para adiar a coleta de lixo ou omiti-la completamente -- é uma questão de detalhe de implementação como a coleta de lixo é implementada, desde que nenhum objeto que ainda esteja acessível seja coletado. -____ - -[role="pagebreak-before less_space"] -Pablo Galindo escreveu um texto mais aprofundado sobre o Coletor de Lixo em Python, em -https://fpy.li/6-12["Design of CPython’s Garbage Collector"] (EN) -no https://fpy.li/6-13[_Python Developer’s Guide_], -voltado para contribuidores novos e experientes da implementação CPython. - -O coletor de lixo do CPython 3.4 aperfeiçoou o tratamento de objetos contendo um método `+__del__+`, -como descrito em https://fpy.li/6-14[PEP 442--Safe object finalization] (EN). - -A Wikipedia tem um artigo sobre https://fpy.li/6-15[string interning] (EN), -que menciona o uso desta técnica em várias linguagens, incluindo Python. - -A Wikipedia também tem um artigo sobre https://fpy.li/6-16["Haddocks' Eyes"], -a canção de Lewis Carroll que mencionei no início deste capítulo. -Os editores da Wikipedia escreveram que a letra é usada em trabalhos de lógica e filosofia -"para elaborar o status simbólico do conceito de 'nome': -um nome como um marcador de identificação pode ser atribuído a qualquer coisa, -incluindo outro nome, introduzindo assim níveis diferentes de simbolização." - -.Ponto de vista -**** - -[role="soapbox-title"] -Tratamento igual para todos os objetos - -Eu((("object references", "Soapbox discussion")))((("Soapbox sidebars", "equality (==) operator")))((("== (equality) operator")))((("equality (==) operator"))) aprendi Java antes de conhecer Python. O operador `==` em Java nunca me pareceu funcionar corretamente. É muito mais comum que programadores estejam preocupados com a igualdade que com a identidade. Mas para objetos (não tipos primitivos), o `==` em Java compara referências, não valores dos objetos. Mesmo para algo tão básico quanto comparar strings, Java obriga você a usar o método `.equals`. E mesmo assim, há outro problema: se você escrever `a.equals(b)` e `a` for `null`, você causa uma null pointer exception (exceção de ponteiro nulo). Os projetistas do Java sentiram necessidade de sobrecarregar `+` para strings; por que não mantiveram essa ideia e sobrecarregaram `==` também? - -Python faz melhor. O operador `==` compara valores de objetos; `is` compara referências. E como Python permite sobrecarregar operadores, `==` funciona de forma sensata com todos os objetos na biblioteca padrão, incluindo `None`, que é um objeto verdadeiro, ao contrário do `Null` de Java. - -E claro, você pode definir `+__eq__+` nas suas próprias classes para controlar o que `==` significa para suas instâncias. Se você não sobrecarregar `+__eq__+`, o método herdado de `object` compara os IDs dos objetos, então a regra básica é que cada instância de uma classe definida pelo usuário é considerada diferente. - -Estas são algumas das coisas que me fizeram mudar de Java para Python assim que terminei de ler _The Python Tutorial_ em uma tarde de setembro de 1998. - -[role="soapbox-title"] -Mutabilidade - -Este((("Soapbox sidebars", "mutability")))((("mutable objects")))((("objects", "mutable"))) capítulo não seria necessário se todos os objetos em Python fossem imutáveis. Quando você está lidando com objetos imutáveis, não faz diferença se as variáveis guardam os objetos em si ou referências para objetos compartilhados. - -Se `a == b` é verdade, e nenhum dos dois objetos pode mudar, eles podem perfeitamente ser o mesmo objeto. Por isso a internalização de strings é segura. A identidade dos objetos ser torna importante apenas quando esses objetos podem mudar. - -Em programação funcional "pura", todos os dados são imutáveis: concatenar algo a uma coleção, na verdade, cria uma nova coleção. -Elixir é uma linguagem funcional prática e fácil de aprender, na qual todos os tipos nativos são imutáveis, incluindo as listas. - -Python, por outro lado, não é uma linguagem funcional, menos uma ainda uma linguagem pura. Instâncias de classes definidas pelo usuário são mutáveis por default em Python -- como na maioria das linguagens orientadas a objetos. Ao criar seus próprios objetos, você tem que tomar o cuidado adicional de torná-los imutáveis, se este for um requisito. Cada atributo do objeto precisa ser também imutável, senão você termina criando algo como uma tupla: imutável quanto ao ID do objeto, mas seu valor pode mudar se a tupla contiver um objeto mutável. - -Objetos mutáveis também são a razão pela qual programar com threads é tão difícil: threads modificando objetos sem uma sincronização apropriada podem corromper dados. Sincronização excessiva, por outro lado, causa deadlocks. -A linguagem e a plataforma Erlang -- que inclui Elixir -- foi projetada para maximizar o tempo de execução em aplicações distribuídas de alta concorrência, tais como aplicações de controle de telecomunicações. Naturalmente, eles escolheram tornar os dados imutáveis por default. - -[role="soapbox-title"] -Destruição de objetos e coleta de lixo - -Não há((("Soapbox sidebars", "object destruction and garbage collection")))((("garbage collection"))) qualquer mecanismo em Python para destruir um objeto diretamente, e essa omissão é, na verdade, uma grande qualidade: se você pudesse destruir um objeto a qualquer momento, o que aconteceria com as referências que apontam para ele? - -A coleta de lixo em CPython é feita principalmente por contagem de referências, que é fácil de implementar, mas vulnerável a vazamentos de memória (_memory leaks_) quando existem referências cíclicas. Assim, com a versão 2.0 (de outubro de 2000), um coletor de lixo geracional foi implementado, e ele consegue dispor de objetos inatingíveis que foram mantidos vivos por ciclos de referências. - -Mas a contagem de referências ainda está lá como mecanismo básico, e ela causa a destruição imediata de objetos com zero referências. Isso significa que, em CPython -- pelo menos por hora -- é seguro escrever: - -[source, python3] ----- -open('test.txt', 'wt', encoding='utf-8').write('1, 2, 3') ----- - -Este código é seguro porque a contagem de referências do objeto file será zero após o método `write` retornar. Entretanto, a mesma linha não é segura em Jython ou IronPython, que usam o coletor de lixo dos runtimes de seus ambientes (a Java VM e a .NET CLR, respectivamente), que são mais sofisticados, mas não se baseiam em contagem de referências, e podem demorar mais para destruir o objeto e fechar o arquivo. Em todos os casos, incluindo em CPython, a melhor prática é fechar o arquivo explicitamente, e a forma mais confiável de fazer isso é usando o comando `with`, que garante o fechamento do arquivo mesmo se acontecerem exceções enquanto ele estiver aberto. Usando `with`, a linha anterior se torna: - -[source, python3] ----- -with open('test.txt', 'wt', encoding='utf-8') as fp: - fp.write('1, 2, 3') ----- - -Se você estiver interessado no assunto de coletores de lixo, você talvez queira ler o artigo de Thomas Perl, https://fpy.li/6-17["Python Garbage Collector Implementations: CPython, PyPy and GaS"] (EN), onde eu aprendi esses detalhes sobre a segurança de `open().write()` em CPython. - -[role="soapbox-title"] -Passagem de parâmetros: chamada por compartilhamento - -Uma maneira popular de explicar como a passagem de parâmetros funciona em Python é a frase: "Parâmetros são passados por valor, mas os valores são referências." Isso não está errado, mas causa confusão porque os modos mais comuns de passagem de parâmetros nas linguagens antigas são _chamada por valor_ (a função recebe uma cópia dos argumentos) e _chamada por referência_ (a função recebe um ponteiro para o argumento). Em Python, a função recebe uma cópia dos argumentos, mas os argumentos são sempre referências. Então o valor dos objetos referenciados podem ser alterados pela função, se eles forem mutáveis, mas sua identidade não. Além disso, como a função recebe uma cópia da referência em um argumento, reassociar essa referência no corpo da função não tem qualquer efeito fora da função. Adotei o termo _chamada por compartilhamento_ depois de ler sobre esse assunto em _Programming Language Pragmatics_, 3rd ed., de Michael L. Scott (Morgan Kaufmann), section "8.3.1: Parameter Modes." - -**** diff --git a/capitulos/cap08.adoc b/capitulos/cap08.adoc deleted file mode 100644 index b6bd0361..00000000 --- a/capitulos/cap08.adoc +++ /dev/null @@ -1,2073 +0,0 @@ -[[type_hints_in_def_ch]] -== Dicas de tipo em funções -:xrefstyle: short -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo:example-number: 0 -:figure-number: 0 - -++++ -
-

É preciso enfatizar que Python continuará sendo uma linguagem de tipagem dinâmica, e os autores não tem qualquer intenção de algum dia tornar dicas de tipo obrigatórias, mesmo que por mera convenção.

-

Guido van Rossum, Jukka Lehtosalo, e Łukasz Langa, PEP 484—Type Hints PEP 484—Type Hints (EN), "Rationale and Goals"; negritos mantidos do original.

-
-++++ -// [quote, Guido van Rossum, Jukka Lehtosalo, and Łukasz Langa, PEP 484—Type Hints] -// ____ -// It should also be emphasized that *Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention*.footnote:[https://fpy.li/8-1[PEP 484—Type Hints], section _Rationale and Goals_; bold emphasis retained from the original.] -// ____ - -Dicas de tipo((("functions, type hints in", "benefits and drawbacks of")))((("type hints (type annotations)", "benefits and drawbacks of"))) foram a maior mudança na história do 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. -Por isso deverão ser sempre opcionais. - -A -https://fpy.li/pep484[PEP 484—Type Hints] introduziu a sintaxe e a semântica para declarações explícitas de tipo em argumentos de funções, valores de retorno e variáveis. O objetivo é ajudar ferramentas de desenvolvimento a encontrarem bugs nas bases de código em Python através de análise estática, isto é, sem precisar efetivamente executar o código através de testes. - -Os maiores beneficiários são engenheiros de software profissionais que usam IDEs (_Ambientes de Desenvolvimento Integrados_) e CI (_Integração Contínua_). -A análise de custo-benefício que torna as dicas de tipo atrativas para esse grupo não se aplica a todos os usuários de Python. - -A base de usuários de Python vai muito além dessa classe de profissionais. Ela inclui cientistas, comerciantes, jornalistas, artistas, inventores, analistas e estudantes de inúmeras áreas -- entre outros. -Para a maioria deles, o custo de aprender dicas de tipo será certamente maior -- a menos que já conheçam uma outra [.keep-together]#linguagem# com tipos estáticos, subtipos e tipos genéricos. -Os benefícios serão menores para muitos desses usuários, dada a forma como que eles interagem com Python, o tamanho menor de suas bases de código e de suas equipes -- muitas vezes "equipes de um". - -A tipagem dinâmica, default do Python, é [.keep-together]#mais simples# e mais expressiva quando estamos escrevendo programas para explorar dados e ideias, como é o caso em ciência de dados, computação criativa e para aprender. - -Este capítulo se concentra nas dicas de tipo de Python nas assinaturas de função. -<> explora as dicas de tipo no contexto de classes e outros recursos do módulo `typing`. - -Os((("functions, type hints in", "topics covered")))((("type hints (type annotations)", "topics covered"))) tópicos mais importantes aqui são: - -* Uma introdução prática à tipagem gradual com Mypy -* As perspectivas complementares da duck typing (_tipagem pato_) e da tipagem nominal -* A revisão para principais categorias de tipos que podem surgir em anotações -- isso representa cerca de 60% do capítulo -* Os parâmetros variádicos das dicas de tipo (`\*args`, `**kwargs`) -* As limitações e desvantagens das dicas de tipo e da tipagem estática. - -=== Novidades nesse capítulo - -Este((("functions, type hints in", "significant changes to")))((("type hints (type annotations)", "significant changes to"))) capítulo é completamente novo. As dicas de tipo apareceram no Python 3.5, após eu ter terminado de escrever a primeira edição de _Python Fluente_. - -Dadas as limitações de um sistema de tipagem estática, a melhor ideia da PEP 484 foi propor um _sistema de tipagem gradual_. Vamos começar definindo o que isso significa. - -=== Sobre tipagem gradual - -A PEP 484((("functions, type hints in", "gr adual typing", id="FTHgrad08")))((("type hints (type annotations)", "gradual typing", id="THgrad0 8")))((("gradual type system", "basics of"))) introduziu no Python um _sistema de tipagem gradual_. -Outras linguagens com sistemas de tipagem gradual são o Typescript da Microsoft, Dart (a linguagem do SDK Flutter, criado pelo Google), e o Hack (um dialeto de PHP criado para uso na máquina virtual HHVM do Facebook). O próprio verificador de tipo MyPy começou como uma linguagem: um dialeto de Python de tipagem gradual com seu próprio interpretador. Guido van Rossum convenceu o criador do MyPy, Jukka Lehtosalo, a transformá-lo em uma ferramenta para checar código Python anotado. - -Eis uma função com anotações de tipos: - -[source, py] ----- -def tokenize(s: str) -> list[str]: - "Convert a string into a list of tokens." - return s.replace('(', ' ( ').replace(')', ' ) ').split() ----- - -A assinatura informa que a função `tokenize` recebe uma `str` -e devolve `list[str]`: uma lista de strings. -A utilidade dessa função será explicada no <>. - -Um sistema de tipagem gradual: - -É opcional:: -Por default, o verificador de tipo não deve emitir avisos para código que não tenha dicas de tipo. Em vez disso, o verificador supõe o tipo `Any` quando não consegue determinar o tipo de um objeto. O tipo `Any` é considerado compatível com todos os outros tipos. -Não captura erros de tipagem durante a execução do código:: -Dicas de tipo são usadas por verificadores de tipo, analisadores de código-fonte (_linters_) e IDEs para emitir avisos. Eles não evitam que valores inconsistentes sejam passados para funções ou atribuídos a variáveis durante a execução. Por exemplo, nada impede que alguém chame -`tokenie(42)`, apesar da anotação de tipo do argumento `s: str`). -A chamada ocorrerá, e teremos um erro de execução no corpo da função. -Não melhora o desempenho:: -Anotações de tipo fornecem dados que poderiam, em tese, permitir otimizações do bytecode gerado. -Mas, até julho de 2021, tais otimizações não ocorrem em nenhum ambiente Python que eu conheça.footnote:[Um compilador JIT ("just-in-time", compiladores que transformam o bytecode gerado pelo interpretador em código da máquina-alvo no momento da execução) como o do PyPy tem informações muito melhores que as dicas de tipo: ele monitora o programa Python durante a execução, detecta os tipos concretos em uso, e gera código de máquina otimizado para aqueles tipos concretos.] - -O melhor aspecto de usabilidade da tipagem gradual é que as anotações são sempre opcionais. - -Nos sistemas de tipagem estáticos, a maioria das restrições de tipo são fáceis de expressar, muitas são desajeitadas, muitas são difíceis e algumas são impossíveis: Por exemplo, em julho de 2021, -tipos recursivos não tinham suporte -- veja as questões https://fpy.li/8-2[#182, Define a JSON type] (EN) sobre o JSON e https://fpy.li/8-3[#731, Support recursive types] (EN) do MyPy. - -É perfeitamente possível que você escreva um ótimo programa Python, que consiga passar por uma boa cobertura de testes, mas ainda assim não consiga acrescentar dicas de tipo que satisfaçam um verificador de tipagem. Não tem problema; esqueça as dicas de tipo problemáticas e entregue o programa! - -Dicas de tipo são opcionais em todos os níveis: você pode criar ou usar pacotes inteiros sem dicas de tipo, pode silenciar o verificador ao importar um daqueles pacotes sem dicas de tipo para um módulo onde você use dicas de tipo, e você também pode adicionar comentários especiais, para fazer o verificador de tipos ignorar linhas específicas do seu código. - -[TIP] -==== -Tentar impor uma cobertura de 100% de dicas de tipo irá provavelmente estimular seu uso de forma impensada, apenas para satisfazer essa métrica. Isso também vai impedir equipes de aproveitarem da melhor forma possível o potencial e a flexibilidade do Python. Código sem dicas de tipo deveria ser aceito sem objeções quando anotações tornassem o uso de uma API menos amigável ou quando complicassem em demasia seu desenvolvimento. -==== - -=== Tipagem gradual na prática - -Vamos ((("gradual type system", "in practice", id="GRSpract08"))) ver como a tipagem gradual funciona na prática, começando com uma função simples e acrescentando gradativamente a ela dicas de tipo, guiados pelo((("Mypy type checker", id="mypy08"))) Mypy. - -[NOTE] -==== -Há muitos((("Python type checkers"))) verificadores de tipo para Python compatíveis com a PEP 484, -incluindo o https://fpy.li/8-4[pytype] do Google, -o https://fpy.li/8-5[Pyright] da Microsoft, -o https://fpy.li/8-6[Pyre] do Facebook — além de verificadores incluídos em IDEs como o PyCharm. -Eu escolhi usar o https://fpy.li/mypy[Mypy] nos exemplos por ele ser o mais conhecido. Entretanto, algum daqueles outros pode ser mais adequado para alguns projetos ou equipes. O Pytype, por exemplo, foi projetado para lidar com bases de código sem nenhuma dica de tipo e ainda assim gerar recomendações úteis. Ele é mais tolerante que o MyPy, e consegue também gerar anotações para o seu código. -==== - -Vamos anotar uma função `show_count`, que retorna uma string com um número e uma palavra no singular ou no plural, dependendo do número: - -[source, pycon] ----- -include::code/08-def-type-hints/messages/no_hints/messages.py[tags=SHOW_COUNT_DOCTEST] ----- - -<> mostra o código-fonte de `show_count`, sem anotações. - -[[msgs_no_hints]] -.`show_count` de _messages.py_ sem dicas de tipo. -==== -[source, py] ----- -include::code/08-def-type-hints/messages/no_hints/messages.py[tags=SHOW_COUNT] ----- -==== - -==== Usando o Mypy - -Para começar a verificação de tipo, rodamos o comando `mypy` passando o módulo _messages.py_ como parâmetro: - -[source] ----- -…/no_hints/ $ pip install mypy -[muitas mensagens omitidas...] -…/no_hints/ $ mypy messages.py -Success: no issues found in 1 source file ----- - -Na configuração default, o Mypy não encontra nenhum problema com o <>. - -[WARNING] -==== -Durante a revisão deste capítulo estou usando Mypy 0.910, a versão mais recente no momento (em julho de 2021). -A https://fpy.li/8-7["Introduction"] (EN) do Mypy adverte que ele "é oficialmente software beta. Mudanças ocasionais irão quebrar a compatibilidade com versões mais antigas." -O Mypy está gerando pelo menos um relatório diferente daquele que recebi quando escrevi o capítulo, em abril de 2020. -E quando você estiver lendo essas linhas, talvez os resultados também sejam diferentes daqueles mostrados aqui. -==== - -Se a assinatura de uma função não tem anotações, Mypy a ignora por default -- a menos que seja configurado de outra forma. - -O <> também inclui testes de unidade do `pytest`. -Este é código de _messages_test.py_. - -[[msgs_test_no_hints]] -._messages_test.py_ sem dicas de tipo. -==== -[source, py] ----- -include::code/08-def-type-hints/messages/no_hints/messages_test.py[] ----- -==== - -Agora vamos acrescentar dicas de tipo, guiados pelo Mypy. - - -==== Tornando o Mypy mais rigoroso - -A opção de linha de comando `--disallow-untyped-defs` faz o Mypy apontar todas as definições de função que não tenham dicas de tipo para todos os argumentos e para o valor de retorno. - -Usando `--disallow-untyped-defs` com o arquivo de teste produz três erros e uma observação: - -[source] ----- -…/no_hints/ $ mypy --disallow-untyped-defs messages_test.py -messages.py:14: error: Function is missing a type annotation -messages_test.py:10: error: Function is missing a type annotation -messages_test.py:15: error: Function is missing a return type annotation -messages_test.py:15: note: Use "-> None" if function does not return a value -Found 3 errors in 2 files (checked 1 source file) ----- - -Nas primeiras etapas da tipagem gradual, prefiro usar outra opção: - -`--disallow-incomplete-defs`. - -Inicialmente o Mypy não me dá nenhuma nova informação: - -[source] ----- -…/no_hints/ $ mypy --disallow-incomplete-defs messages_test.py -Success: no issues found in 1 source file ----- - -Agora vou acrescentar apenas o tipo do retorno a `show_count` em _messages.py_: - -[source] ----- -def show_count(count, word) -> str: ----- - -Isso é suficiente para fazer o Mypy olhar para o código. Usando a mesma linha de comando anterior para verificar _messages_test.py_ fará o Mypy examinar novamente o _messages.py_: - -[source] ----- -…/no_hints/ $ mypy --disallow-incomplete-defs messages_test.py -messages.py:14: error: Function is missing a type annotation -for one or more arguments -Found 1 error in 1 file (checked 1 source file) ----- - -Agora posso gradualmente acrescentar dicas de tipo, função por função, sem receber avisos sobre as funções onde ainda não adicionei anotações -Essa é uma assinatura completamente anotada que satisfaz o Mypy: - -[source, py] ----- -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`#, 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. -Aqui está um _mypy.ini_ simples, para servir de base: - ----- -[mypy] -python_version = 3.9 -warn_unused_configs = True -disallow_incomplete_defs = True ----- -==== - - -==== Um valor default para um argumento - -A função `show_count` no <> só funciona com substantivos regulares. Se o plural não pode ser composto acrescentando um `'s'`, devemos deixar o usuário fornecer a forma plural, assim: - -[source, pycon] ----- ->>> show_count(3, 'mouse', 'mice') -'3 mice' ----- - -Vamos experimentar um pouco de "desenvolvimento orientado a tipos." -Primeiro acrescento um teste usando aquele terceiro argumento. -Não esqueça de adicionar a dica do tipo de retorno à função de teste, -senão o Mypy não vai inspecioná-la. - -[source, py3] ----- -def test_irregular() -> None: - got = show_count(2, 'child', 'children') - assert got == '2 children' ----- - -O Mypy detecta o erro: -[source, py] ----- -…/hints_2/ $ mypy messages_test.py -messages_test.py:22: error: Too many arguments for "show_count" -Found 1 error in 1 file (checked 1 source file) ----- - -Então edito `show_count`, acrescentando o argumento opcional `plural` no <>. - -[[msgs_optional_str_param]] -.`showcount` de _hints_2/messages.py_ com um argumento opcional -==== -[source, py] ----- -include::code/08-def-type-hints/messages/hints_2/messages.py[tags=SHOW_COUNT] ----- -==== - -E agora o Mypy reporta "Success." - -[WARNING] -==== -Aqui está um erro de digitação que o Python não reconhece. Você consegue encontrá-lo? - -[source, py] ----- -def hex2rgb(color=str) -> tuple[int, int, int]: ----- - -O relatório de erros do Mypy não é muito útil: - -[source] ----- -colors.py:24: error: Function is missing a type - annotation for one or more arguments ----- - -A dica de tipo para o argumento `color` deveria ser `color: str`. -Eu escrevi `color=str`, que não é uma anotação: ele determina que o valor default de `color` é `str`. - -Pela minha experiência, esse é um erro comum e fácil de passar desapercebido, especialmente em dicas de tipo complexas. -==== - -Os seguintes detalhes são considerados um bom estilo para dicas de tipo: - -* Sem espaço entre o nome do parâmetro e o `:`; um espaço após o `:` -* Espaços dos dois lados do `=` que precede um valor default de parâmetro - -Por outro lado, a PEP 8 diz que não deve haver espaço em torno de `=` se não há nenhuma dica de tipo para aquele parâmetro específico. - -.Estilo de Código: use flake8 e blue - -**** -Em vez de((("flake8 tool")))((("blue tool"))) decorar essas regrinhas bobas, use ferramentas como -https://fpy.li/8-9[_flake8_] e https://fpy.li/8-10[_blue_]. O -_flake8_ informa sobre o estilo do código e várias outras questões, enquanto o _blue_ reescreve o código-fonte com base na (maioria) das regras prescritas pela ferramenta de formatação de código -https://fpy.li/8-11[_black_]. - -Se o objetivo é impor um estilo de programação "padrão", _blue_ é melhor que _black_, porque segue o estilo próprio do Python, de usar aspas simples por default e aspas duplas como alternativa. - -[source, py] ----- ->>> "I prefer single quotes" -'I prefer single quotes' ----- - -No CPython, a preferência por aspas simples está incorporada no `repr()`, entre outros lugares. O módulo https://fpy.li/doctest[_doctest_] depende do `repr()` usar aspas simples por default. - -Um dos autores do _blue_ é https://fpy.li/8-12[Barry Warsaw], co-autor da PEP 8, core developer do Python desde 1994 e membro do Python's Steering Council desde 2019. Daí estamos em ótima companhia quando escolhemos usar aspas simples. - -Se você precisar mesmo usar o _black_, use a opção `black -S`. Isso deixará suas aspas intocadas. -**** - - -[[dealing_with_none_sec]] -==== Usando None como default -No <>, o parâmetro `plural` está anotado como `str`, e o valor default é `''`. Assim não há conflito de tipo. - -Eu gosto dessa solução, mas em outros contextos `None` é um default melhor. Se o parâmetro opcional requer um tipo mutável, então `None` é o único default sensato, como vimos na <>. - -Com `None` como default para o parâmetro `plural`, a assinatura ficaria assim: - -[source, py] ----- -from typing import Optional - -def show_count(count: int, singular: str, plural: Optional[str] = None) -> str: ----- - -Vamos destrinchar essa linha: - -* `Optional[str]` significa que `plural` pode ser uma `str` ou `None`. -* É obrigatório fornecer explicitamente o valor default `= None`. - -Se você não atribuir um valor default a `plural`, o runtime do Python vai tratar o parâmetro como obrigatório. Lembre-se: durante a execução do programa, as dicas de tipo são ignoradas. - -Veja que é preciso importar `Optional` do módulo `typing`. Quando importamos tipos, é uma boa prática usar a sintaxe `from typing import X`, para reduzir o tamanho das assinaturas das funções. - -[WARNING] -==== -`Optional` não é um bom nome, pois aquela anotação não torna o argumento opcional. O que o torna opcional é a atribuição de um valor default ao parâmetro. `Optional[str]` significa apenas: o tipo desse parâmetro pode ser `str` ou `NoneType`. -Nas linguagens Haskell e Elm, um tipo parecido se chama `Maybe`. -==== - -Agora que tivemos um primeiro contato concreto com a tipagem gradual, vamos examinar o que o conceito de _tipo_ significa na prática.((("", startref="FTHgrad08")))((("", startref="THgrad08")))((("", startref="GRSpract08")))((("", startref="mypy08"))) - -[[types_defined_by_ops_sec]] -=== Tipos são definidos pelas operações possíveis - - -[quote, PEP 483—A Teoria das Dicas de Tipo] -____ - -Há muitas definições do conceito de tipo na literatura. Aqui vamos assumir que tipo é um conjunto de valores e um conjunto de funções que podem ser aplicadas àqueles valores. -____ - -Na((("functions, type hints in", "supported operations and", id="FTHsupport08")))((("type hints (type annotations)", "supported operations and", id="THsup08"))) prática, é mais útil considerar o conjunto de operações possíveis como a caraterística definidora de um tipo.footnote:[ Em Python não há sintaxe para controlar o conjunto de possíveis valores de um tipo, exceto para tipos `Enum`. Por exemplo, não é possível, usando dicas de tipo, definir `Quantity` como um número inteiro entre 1 e 10000, ou `AirportCode` como uma sequência de 3 letras. O NumPy oferece `uint8`, `int16`, e outros tipos numéricos referentes à arquitetura do hardware, mas na biblioteca padrão do Python nós encontramos apenas tipos com conjuntos muitos pequenos de valores (`NoneType`, `bool`) ou conjuntos muito grandes (`float`, `int`, `str`, todas as tuplas possíveis, etc.).] - -Por exemplo, pensando nas operações possíveis, quais são os tipos válidos para `x` na função a seguir? - -[source, python] ----- -def double(x): - return x * 2 ----- - -O tipo do parâmetro `x` pode ser numérico (`int`, `complex`, `Fraction`, `numpy.uint32`, etc.), mas também pode ser uma sequência (`str`, `tuple`, `list`, `array`), uma `numpy.array` N-dimensional, ou qualquer outro tipo que implemente ou herde um método `+__mul__+` que aceite um inteiro como argumento. - -Entretanto, considere a anotação `double` abaixo. Ignore por enquanto a ausência do tipo do retorno, vamos nos concentrar no tipo do parâmetro: - -[source, python] ----- -from collections import abc - -def double(x: abc.Sequence): - return x * 2 ----- - -Um verificador de tipo irá rejeitar esse 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 como `str`, `tuple`, `list`, `array`, etc., bem como com números, pois durante a execução as dicas de tipo são ignoradas. Mas o verificador de tipo se preocupa apenas com o que estiver explicitamente declarado, e `abc.Sequence` não suporta `+__mul__+`. - -Por essa razão o título dessa seção é "Tipos São Definidos pelas Operações Possíveis." O runtime do Python aceita qualquer objeto como argumento `x` nas duas versões da função `double`. O cálculo de `x * 2` pode funcionar, ou pode causar um `TypeError`, se a operação não for suportada por `x`. Por outro lado, Mypy vai marcar `x * 2` como um erro quando analisar o código-fonte anotado de `double`, pois é uma operação não suportada pelo tipo declarado `x: abc.Sequence`. - -Em um sistema de tipagem gradual, acontece uma interação entre duas perspectivas diferentes de tipo: - -Duck typing ("tipagem pato"):: -A ((("duck typing"))) perspectiva adotada pelo Smalltalk — a primeira linguagem orientada a objetos — bem como em Python, JavaScript, e Ruby. -Objetos tem tipo, mas variáveis (incluindo parâmetros) não. Na prática, não importa qual o tipo declarado de um objeto, importam apenas as operações que ele efetivamente suporta. -Se eu posso invocar `birdie.quack()` então, nesse contexto, `birdie` é um pato. -Por definição, duck typing só é aplicada durante a execução, quando se tenta aplicar operações sobre os objetos. Isso é mais flexível que a _tipagem nominal_, ao preço de permitir mais erros durante a execução.footnote:[Duck typing é uma forma implícita de _tipagem estrutural_, que o Python passou a suportar após a versão 3.8, com a introdução de `typing.Protocol`. Vamos falar disso mais adiante nesse capítulo - em <> — e com mais [.keep-together]#detalhes em# <>.] - - -Tipagem nominal:: -É a ((("nominal typing"))) perspectiva adotada em C++, Java, e C#, e suportada em Python anotado. -Objetos e variáveis tem tipos. Mas objetos só existem durante a execução, e o verificador de tipo só se importa com o código-fonte, onde as variáveis (incluindo parâmetros de função) tem anotações com dicas de tipo. -Se `Duck` é uma subclasse de `Bird`, você pode atribuir uma instância de `Duck` a um parâmetro anotado como `birdie: Bird`. -Mas no corpo da função, o verificador considera a chamada `birdie.quack()` ilegal, pois `birdie` é nominalmente um `Bird`, e aquela classe não fornece o método `.quack()`. -Não interessa que o argumento real, durante a execução, é um `Duck`, porque a tipagem nominal é aplicada de forma estática. O verificador de tipo não executa qualquer pedaço do programa, ele apenas lê o código-fonte. -Isso é mais rígido que _duck typing_, com a vantagem de capturar alguns bugs durante o desenvolvimento, ou mesmo em tempo real, enquanto o código está sendo digitado em um IDE. - -O <> é um exemplo bobo que contrapõe duck typing e tipagem nominal, bem como verificação de tipo estática e comportamento durante a execução.footnote:[Muitas vezes a herança é sobreutilizada e difícil de justificar em exemplos que, apesar de realistas, são muito simples. Então por favor aceite esse exemplo com animais como uma rápida ilustração de sub-tipagem.] - -[[birds_module_ex]] -._birds.py_ -==== -[source, py] ----- -include::code/08-def-type-hints/birds/birds.py[] ----- -==== -<1> `Duck` é uma subclasse de `Bird`. -<2> `alert` não tem dicas de tipo, então o verificador a ignora. -<3> `alert_duck` aceita um argumento do tipo `Duck`. -<4> `alert_bird` aceita um argumento do tipo `Bird`. - -Verificando _birds.py_ com Mypy, encontramos um problema: - -[source] ----- -…/birds/ $ mypy birds.py -birds.py:16: error: "Bird" has no attribute "quack" -Found 1 error in 1 file (checked 1 source file) ----- - -Só de analisar o código fonte, Mypy percebe que `alert_bird` é problemático: a dica de tipo declara o parâmetro `birdie` como do tipo `Bird`, mas o corpo da função chama `birdie.quack()` — e a classe `Bird` não tem esse método. - -Agora vamos tentar usar o módulo `birds` em _daffy.py_ no <>. - -[[daffy_module_ex]] -._daffy.py_ -==== -[source, py] ----- -include::code/08-def-type-hints/birds/daffy.py[] ----- -==== -<1> Chamada válida, pois `alert` não tem dicas de tipo. -<2> Chamada válida, pois `alert_duck` recebe um argumento do tipo `Duck` e `daffy` é um `Duck`. -<3> Chamada válida, pois `alert_bird` recebe um argumento do tipo `Bird`, e `daffy` também é um `Bird` — a superclasse de `Duck`. - -Mypy reporta o mesmo erro em _daffy.py_, sobre a chamada a `quack` na função `alert_bird` definida em _birds.py_: - -[source] ----- -…/birds/ $ mypy daffy.py -birds.py:16: error: "Bird" has no attribute "quack" -Found 1 error in 1 file (checked 1 source file) ----- - -Mas o Mypy não vê qualquer problema com _daffy.py_ em si: as três chamadas de função estão OK. - -Agora, rodando _daffy.py_, o resultado é o seguinte: - -[source] ----- -…/birds/ $ python3 daffy.py -Quack! -Quack! -Quack! ----- - -Funciona perfeitamente! Viva o duck typing! - -Durante a execução do programa, o Python não se importa com os tipos declarados. Ele usa apenas duck typing. O Mypy apontou um erro em `alert_bird`, mas a chamada da função com `daffy` funciona corretamente quando executada. -À primeira vista isso pode surpreender muitos pythonistas: um verificador de tipo estático muitas vezes encontra erros em código que sabemos que vai funcionar quanto executado. - -Entretanto, se daqui a alguns meses você for encarregado de estender o exemplo bobo do pássaro, você agradecerá ao Mypy. Observe esse módulo _woody.py_ module, que também usa `birds`, no <>. - -[[woody_module_ex]] -._woody.py_ -==== -[source, py] ----- -include::code/08-def-type-hints/birds/woody.py[] ----- -==== - -O Mypy encontra dois erros ao verificar _woody.py_: - -[source] ----- -…/birds/ $ mypy woody.py -birds.py:16: error: "Bird" has no attribute "quack" -woody.py:5: error: Argument 1 to "alert_duck" has incompatible type "Bird"; -expected "Duck" -Found 2 errors in 2 files (checked 1 source file) ----- - -O primeiro erro é em _birds.py_: a chamada a `birdie.quack()` em `alert_bird`, que já vimos antes. -O segundo erro é em _woody.py_: `woody` é uma instância de `Bird`, então a chamada `alert_duck(woody)` é inválida, pois aquela função exige um `Duck.` -Todo `Duck` é um `Bird`, mas nem todo `Bird` é um `Duck`. - -Durante a execução, nenhuma das duas chamadas em _woody.py_ funcionariam. -A sucessão de falhas é melhor ilustrada em uma sessão no console, através das mensagens de erro, no <>. - -[[birdie_errors_ex]] -.Erros durante a execução e como o Mypy poderia ter ajudado -==== -[source, py] ----- ->>> from birds import * ->>> woody = Bird() ->>> alert(woody) # <1> -Traceback (most recent call last): - ... -AttributeError: 'Bird' object has no attribute 'quack' ->>> ->>> alert_duck(woody) # <2> -Traceback (most recent call last): - ... -AttributeError: 'Bird' object has no attribute 'quack' ->>> ->>> alert_bird(woody) # <3> -Traceback (most recent call last): - ... -AttributeError: 'Bird' object has no attribute 'quack' ----- -==== -<1> O Mypy não tinha como detectar esse erro, pois não há dicas de tipo em `alert`. -<2> O Mypy avisou do problema: `Argument 1 to "alert_duck" has incompatible type "Bird"; expected "Duck"` (_Argumento 1 para `alert_duck` é do tipo incompatível "Bird"; argumento esperado era "Duck"_) -<3> O Mypy está avisando desde o <> que o corpo da função `alert_bird` está errado: `"Bird" has no attribute "quack"` (_Bird não tem um atributo "quack"_) - -Este pequeno experimento mostra que o duck typing é mais fácil para o iniciante e mais flexível, mas permite que operações não suportadas causem erros durante a execução. -A tipagem nominal detecta os erros antes da execução, mas algumas vezes rejeita código que seria executado sem erros - como a chamada a `alert_bird(daffy)` no <>. - -Mesmo que funcione algumas vezes, o nome da função `alert_bird` está incorreto: seu código exige um objeto que suporte o método `.quack()`, que não existe em `Bird`. - -Nesse exemplo bobo, as funções tem uma linha apenas. -Mas na vida real elas poderiam ser mais longas, e poderiam passar o argumento `birdie` para outras funções, e a origem daquele argumento poderia estar a muitas chamadas de função de distância, tornando difícil localizar a causa do erro durante a execução. -O verificador de tipos impede que muitos erros como esse aconteçam durante a execução de um programa. - - -[NOTE] -==== -O valor das dicas de tipo é questionável em exemplos minúsculo que cabem em um livro. Os benefícios crescem conforme o tamanho da base de código afetada. É por essa razão que empresas com milhões de linhas de código em Python - como a Dropbox, o Google e o Facebook - investiram em equipes e ferramentas para promover a adoção global de dicas de tipo internamente, e hoje tem partes significativas e crescentes de sua base de código checadas para tipo em suas linhas (_pipeline_) de integração contínua. -==== - -Nessa seção exploramos as relações de tipos e operações no duck typing e na tipagem nominal, começando com a função simples `double()` — que deixamos sem dicas de tipo. Agora vamos dar uma olhada nos tipos mais importantes ao anotar funções. - -Vamos ver um bom modo de adicionar dicas de tipo a `double()` quando examinarmos <>. Mas antes disso, há tipos mais importantes para conhecer.((("", startref="FTHsupport08")))((("", startref="THsup08"))) - - -=== Tipos próprios para anotações - -Quase((("functions, type hints in", "types usable in annotations", id="FTHusable08")))((("type hints (type annotations)", "types usable in", id="THTusable08"))) todos os tipos em Python podem ser usados em dicas de tipo, mas há restrições e recomendações. Além disso, o((("typing module"))) módulo `typing` introduziu constructos especiais com uma semântica às vezes surpreendente. - -Essa seção trata de todos os principais tipos que você pode usar em anotações: - -* `typing.Any` -* Tipos e classes simples -* `typing.Optional` e `typing.Union` -* Coleções genéricas, incluindo tuplas e mapeamentos -* Classes base abstratas -* Iteradores genéricos -* Genéricos parametrizados e `TypeVar` -* `typing.Protocols` — crucial para _duck typing estático_ -* `typing.Callable` -* `typing.NoReturn` — um bom modo de encerrar essa lista. - -Vamos falar de um de cada vez, começando por um tipo que é estranho, aparentemente inútil, mas de uma importância fundamental. - -==== O tipo Any - -A((("Any type", id="anytype08")))((("dynamic type", id="dynamic08")))((("gradual type system", "Any type", id="GTSany08"))) pedra fundamental de qualquer sistema gradual de tipagem é o tipo `Any`, também conhecido como o _tipo dinâmico_. Quando um verificador de tipo vê um função sem tipo como esta: - -[source, python] ----- -def double(x): - return x * 2 ----- - -ele supõe isto: -[source, python] ----- -def double(x: Any) -> Any: - return x * 2 ----- - -Isso significa que o argumento `x` e o valor de retorno podem ser de qualquer tipo, inclusive de tipos diferentes. Assume-se que `Any` pode suportar qualquer operação possível. - -Compare `Any` com `object`. Considere essa assinatura: -[source, python] ----- -def double(x: object) -> object: ----- - -Essa função também aceita argumentos de todos os tipos, porque todos os tipos são _subtipo-de_ `object`. - -Entretanto, um verificador de tipo vai rejeitar essa função: -[source, python] ----- -def double(x: object) -> object: - return x * 2 ----- -O problema é que `object` não suporta a operação `+__mul__+`. Veja o que diz o Mypy: - -[source] ----- -…/birds/ $ mypy double_object.py -double_object.py:2: error: Unsupported operand types for * ("object" and "int") -Found 1 error in 1 file (checked 1 source file) ----- - -Tipos mais gerais tem interfaces mais restritas, isto é, eles suportam menos operações. A classe `object` implementa menos operações que `abc.Sequence`, -que implementa menos operações que `abc.MutableSequence`, -que por sua vez implementa menos operações que `list`. - -Mas `Any` é um tipo mágico que reside tanto no topo quanto na base da hierarquia de tipos. Ele é simultaneamente o tipo mais geral - então um argumento `n: Any` aceita valores de qualquer tipo - e o tipo mais especializado, suportando assim todas as operações possíveis. -Pelo menos é assim que o verificador de tipo entende `Any`. - -Claro, nenhum tipo consegue suportar qualquer operação possível, então usar `Any` impede o verificador de tipo de cumprir sua missão primária: detectar operações potencialmente ilegais antes que seu programa falhe e levante uma exceção durante sua execução.((("", startref="GTSany08"))) - -[[consistent_with_sec]] -===== Subtipo-de versus consistente-com - -Sistemas((("gradual type system", "subtype-of versus consistent-with relationships", id="GTSsub08")))((("subtype-of relationships"))) tradicionais de tipagem nominal orientados a objetos se baseiam na relação _subtipo-de_. -Dada uma classe `T1` e uma subclasse `T2`, então `T2` é _subtipo-de_ `T1`. - -Observe este código: - -[source, python] ----- -class T1: - ... - -class T2(T1): - ... - -def f1(p: T1) -> None: - ... - -o2 = T2() - -f1(o2) # OK ----- - -A chamada `f1(o2)` é uma aplicação do Princípio de Substituição de Liskov (_Liskov Substitution Principle—LSP_). - -Barbara Liskovfootnote:[Professora do MIT, designer de linguagens de programação e homenageada com o Turing Award em 2008. Wikipedia: https://pt.wikipedia.org/wiki/Barbara_Liskov[Barbara Liskov].] na verdade definiu _é subtipo-de_ em termos das operações suportadas. Se um objeto do tipo `T2` substitui um objeto do tipo `T1` e o programa continua se comportando de forma correta, então `T2` é _subtipo-de_ `T1`. - -Seguindo com o código visto acima, essa parte mostra uma violação do LSP: - - -[source, python] ----- -def f2(p: T2) -> None: - ... - -o1 = T1() - -f2(o1) # type error ----- - -Do ponto de vista das operações suportadas, faz todo sentido: como uma subclasse, `T2` herda e precisa suportar todas as operações suportadas por `T1`. Então uma instância de `T2` pode ser usada em qualquer lugar onde se espera uma instância de `T1`. Mas o contrário não é necessariamente verdadeiro: `T2` pode implementar métodos adicionais, então uma instância de `T1` não pode ser usada onde se espera uma instância de `T2`. Este((("behavioral subtyping"))) foco nas operações suportadas se reflete no nome https://fpy.li/8-15[_behavioral subtyping (subtipagem comportamental)] (EN), também usado para se referir ao LSP. - -Em((("consistent-with relationships"))) um sistema de tipagem gradual há outra relação, _consistente-com_ (_consistent-with_), que se aplica sempre que _subtipo-de_ puder ser aplicado, com disposições especiais para o tipo `Any`. - -As regras para _consistente-com_ são: - -. Dados `T1` e um subtipo `T2`, então `T2` é _consistente-com_ `T1` (substituição de Liskov). -. Todo tipo é _consistente-com_ `Any`: você pode passar objetos de qualquer tipo em um argumento declarado como de tipo `Any. -. `Any` é _consistente-com_ todos os tipos: você sempre pode passar um objeto de tipo `Any` onde um argumento de outro tipo for esperado. - -Considerando as definições anteriores dos objetos `o1` e `o2`, aqui estão alguns exemplos de código válido, ilustrando as regras #2 e #3: - -[source, python] ----- -def f3(p: Any) -> None: - ... - -o0 = object() -o1 = T1() -o2 = T2() - -f3(o0) # -f3(o1) # tudo certo: regra #2 -f3(o2) # - -def f4(): # tipo implícito de retorno: `Any` - ... - -o4 = f4() # tipo inferido: `Any` - -f1(o4) # -f2(o4) # tudo certo: regra #3 -f3(o4) # ----- - -Todo sistema de tipagem gradual precisa de um tipo coringa como `Any` - -[TIP] -==== -O verbo "inferir" é um sinônimo bonito para "adivinhar", quando usado no contexto da análise de tipos. Verificadores de tipo modernos, em Python e outras linguagens, não precisam de anotações de tipo em todo lugar porque conseguem inferir o tipo de muitas expressões. Por exemplo, se eu escrever `x = len(s) * 10`, o verificador não precisa de uma declaração local explícita para saber que `x` é um `int`, desde que consiga encontrar dicas de tipo para `len` em algum lugar. -==== - -Agora podemos explorar o restante dos tipos usados em anotações.((("", startref="GTSsub08")))((("", startref="dynamic08")))((("", startref="anytype08"))) - -==== Tipos simples e classes - -Tipos simples((("gradual type system", "simple types and classes"))) como `int`, `float`, `str`, e `bytes` podem ser usados diretamente em dicas de tipo. -Classes concretas da biblioteca padrão, de pacotes externos ou definidas pelo usuário — `FrenchDeck`, `Vector2d`, e `Duck` - também podem ser usadas em dicas de tipo. - -Classes base abstratas também são úteis aqui. Voltaremos a elas quando formos estudar os tipos coleção, e em <>. - -Para classes, _consistente-com_ é definido como _subtipo_de_: uma subclasse é _consistente-com_ todas as suas superclasses. - -Entretanto, "a praticidade se sobrepõe à pureza", então há uma exceção importante, discutida em seguida. - -[[int_complex_tip]] -.int é Consistente-Com complex -[TIP] -==== -Não há nenhuma relação nominal de subtipo entre os tipo nativos `int`, `float` e `complex`: eles são subclasses diretas de `object`. -Mas a PEP 484 https://fpy.li/cardxvi[declara] -que `int` é _consistente-com_ `float`, e `float` é _consistente-com_ `complex`. -Na prática, faz sentido: -`int` implementa todas as operações que `float` implementa, e `int` implementa operações adicionais também - operações binárias como `&`, `|`, `<<`, etc. -O resultado final é o seguinte: `int` é _consistente-com_ `complex`. -Para `i = 3`, `i.real` é `3` e `i.imag` é `0`. -==== - - -==== Os tipos Optional e Union - -Nós((("gradual type system", "Optional and Union types")))((("Union type")))((("Optional type"))) vimos o tipo especial `Optional` em <>. Ele resolve o problema de ter `None` como default, como no exemplo daquela seção: - -[source, py] ----- -from typing import Optional - -def show_count(count: int, singular: str, plural: Optional[str] = None) -> str: ----- - -A sintaxe `Optional[str]` é na verdade um atalho para `Union[str, None]`, -que significa que o tipo de `plural` pode ser `str` ou `None`. - -.Uma sintaxe melhor para Optional e Union em Python 3.10 - -[TIP] -==== -Desde o Python 3.10 é possível escrever `str | bytes` em vez de `Union[str, bytes]`. -É menos digitação, e não há necessidade de importar `Optional` ou `Union` de `typing`. -Compare a sintaxe antiga com a nova para a dica de tipo do parâmetro `plural` em `show_count`: - -[source, py] ----- -plural: Optional[str] = None # before -plural: str | None = None # after ----- - -O operador `|` também funciona com `isinstance` e `issubclass` para declarar o segundo argumento: `isinstance(x, int | str)`. -Para saber mais, veja https://fpy.li/pep604[PEP 604—Complementary syntax for Union[]] (EN). -==== - -A assinatura da função nativa `ord` é um exemplo simples de `Union` - ela aceita `str` or `bytes`, -e retorna um `int`:footnote:[Para ser mais preciso, `ord` só aceita `str` ou `bytes` com `len(s) == 1`. -Mas no momento o sistema de tipagem não consegue expressar essa restrição.] - -[source, py] ----- -def ord(c: Union[str, bytes]) -> int: ... ----- - -Aqui está um exemplo de uma função que aceita uma `str`, mas pode retornar uma `str` ou um `float`: - -[source, py] ----- -from typing import Union - -def parse_token(token: str) -> Union[str, float]: - try: - return float(token) - except ValueError: - return token ----- - -Se possível, evite criar funções que retornem o tipo `Union`, pois esse tipo exige um esforço extra do usuário: pois para saber o que fazer com o valor recebido da função será necessário verificar o tipo daquele valor durante a execução. -Mas a `parse_token` no código acima é um caso de uso razoável no contexto de interpretador de expressões simples. - -[TIP] -==== -Na <>, vimos funções que aceitam tanto `str` quanto `bytes` como argumento, mas retornam uma `str` se o argumento for `str` ou `bytes`, se o argumento for `bytes`. Nesses casos, o tipo de retorno é determinado pelo tipo da entrada, então `Union` não é uma solução precisa. -Para anotar tais funções corretamente, precisamos usar um tipo variável - apresentado em <> - ou sobrecarga (_overloading_), que veremos na <>. -==== - -`Union[]` exige pelo menos dois tipos. -Tipos `Union` aninhados tem o mesmo efeito que uma `Union` "achatada" . -Então esta dica de tipo: - -[source, py] ----- -Union[A, B, Union[C, D, E]] ----- - -é o mesmo que: - -[source, py] ----- -Union[A, B, C, D, E] ----- - -`Union` é mais útil com tipos que não sejam consistentes entre si. Por exemplo: `Union[int, float]` é redundante, pois `int` é _consistente-com_ `float`. Se você usar apenas `float` para anotar o parâmetro, ele vai também aceitar valores `int`. - - -[[simple_collections_type_sec]] -==== Coleções genéricas - -A maioria((("gradual type system", "generic collections", id="GTSgeneric08")))((("generic collections", "type annotations and", id="generic08"))) das coleções em Python são heterogêneas. - -Por exemplo, você pode inserir qualquer combinação de tipos diferentes em uma `list`. -Entretanto, na prática isso não é muito útil: se você colocar objetos em uma coleção, você certamente vai querer executar alguma operação com eles mais tarde, e normalmente isso significa que eles precisam compartilhar pelo menos um método comum.footnote:[Em ABC - a linguagem que mais influenciou o design inicial do Python - cada lista estava restrita a aceitar valores de um único tipo: o tipo do primeiro item que você colocasse ali.] - -Tipos genéricos podem ser declarados com parâmetros de tipo, para especificar o tipo de item com o qual eles conseguem trabalhar. - -Por exemplo, uma `list` pode ser parametrizada para restringir o tipo de elemento ali contido, como se pode ver no <>. - -[[tokenize_ex]] -.`tokenize` com dicas de tipo para Python ≥ 3.9 -==== -[source, python] ----- -def tokenize(text: str) -> list[str]: - return text.upper().split() ----- -==== - -Em Python ≥ 3.9, isso significa que `tokenize` retorna uma `list` onde todos os elementos são do tipo `str`. - -As anotações `stuff: list` e `stuff: list[Any]` significam a mesma coisa: `stuff` é uma lista de objetos de qualquer tipo. - -[TIP] -==== -Se você estiver usando Python 3.8 ou anterior, o conceito é o mesmo, mas você precisa de mais código para funcionar - como explicado em -<>. -==== - -A https://fpy.li/8-16[PEP 585—Type Hinting Generics In Standard Collections] (EN) lista as coleções da biblioteca padrão que aceitam dicas de tipo genéricas. A lista a seguir mostra apenas as coleções que usam a forma mais simples de dica de tipo genérica, [.keep-together]#`container[item]`:# - -[source] ----- -list collections.deque abc.Sequence abc.MutableSequence -set abc.Container abc.Set abc.MutableSet -frozenset abc.Collection ----- - -Os tipos `tuple` e mapping aceitam dicas de tipo mais complexas, como veremos em suas respectivas seções. - -No Python 3.10, não há uma boa maneira de anotar `array.array`, levando em consideração o argumento `typecode` do construtor, que determina se o array contém inteiros ou floats. Um problema ainda mais complicado é verificar a faixa dos inteiros, para prevenir `OverflowError` durante a execução, ao se adicionar novos elementos. Por exemplo, um `array` com `typecode=B` só pode receber valores `int` de 0 a 255. Até o Python 3.11, o sistema de tipagem estática do Python não consegue lidar com esse desafio.((("", startref="GTSgeneric08")))((("", startref="generic08"))) - -[[legacy_deprecated_typing_box]] -.Suporte a tipos de coleção descontinuados -**** - -(Você pode pular esse box se usa apenas Python 3.9 ou posterior.) - -Em((("deprecated collection types")))((("gradual type system", "legacy support and deprecated collection types"))) Python 3.7 e 3.8, você precisa importar um `+__future__+` para fazer a notação `[]` funcionar com as coleções nativas, tal como `list`, como ilustrado no <>. - -[[tokenize_3_7_ex]] -.`tokenize` com dicas de tipo para Python ≥ 3.7 -==== -[source, python] ----- -from __future__ import annotations - -def tokenize(text: str) -> list[str]: - return text.upper().split() ----- -==== - -O `+__future__+` não funciona com Python 3.6 ou anterior. O -<> mostra como anotar `tokenize` de uma forma que funciona com Python ≥ 3.5. - -[[tokenize_3_5_ex]] -.`tokenize` com dicas de tipo para Python ≥ 3.5 -==== -[source, python] ----- -from typing import List - -def tokenize(text: str) -> List[str]: - return text.upper().split() ----- -==== - -Para fornecer um suporte inicial a dicas de tipo genéricas, -os autores da PEP 484 criaram dúzias de tipos genéricos no módulo `typing`. -A <> mostra alguns deles. -Para a lista completa, consulte a documentação do módulo https://docs.python.org/pt-br/3/library/typing.html[_typing_] . - -[[generic_collections_tbl]] -.Alguns tipos de coleção e seus equivalentes nas dicas de tipo -[options="header"] -|=========================================================== -|Collection |Type hint equivalent -|`list` |`typing.List` -|`set` |`typing.Set` -|`frozenset` |`typing.FrozenSet` -|`collections.deque` |`typing.Deque` -|`collections.abc.MutableSequence` |`typing.MutableSequence` -|`collections.abc.Sequence` |`typing.Sequence` -|`collections.abc.Set` |`typing.AbstractSet` -|`collections.abc.MutableSet` |`typing.MutableSet` -|=========================================================== - -A https://fpy.li/pep585[PEP 585—Type Hinting Generics In Standard Collections] deu início a um processo de vários anos para melhorar a usabilidade das dicas de tipo genéricas. -Podemos resumir esse processo em quatro etapas: - - -. Introduzir `from __future__ import annotations` no Python 3.7 para permitir o uso das classes da biblioteca padrão como genéricos com a notação `list[str]`. - -. Tornar aquele comportamento o default a partir do 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://docs.python.org/pt-br/3/library/typing.html#module-contents["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 verificadores de tipo devem sinalizar os tipos descontinuados quando o programa sendo verificado 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 o Python 3.9. No ritmo atual, esse deverá ser o Python 3.14, também conhecido como Python Pi. -**** - -Agora vamos ver como anotar tuplas genéricas. - -[[tuple_type_sec]] -==== Tipos tuple - -Há ((("gradual type system", "tuple types", id="GTStupple08")))((("tuples", "type hints (type annotations)", id="Thint08"))) três maneiras de anotar os tipos `tuple`. - -* Tuplas como registros (_records_) -* Tuplas como registro com campos nomeados -* Tuplas como sequências imutáveis. - -===== Tuplas como registros - -Se você está usando uma `tuple` como um registro, use o tipo `tuple` nativo e declare os tipos dos campos dentro dos `[]`. - -Por exemplo, a dica de tipo seria `tuple[str, float, str]` para aceitar uma tupla com nome da cidade, população e país: -`('Shanghai', 24.28, 'China')`. - -Observe uma função que recebe um par de coordenadas geográficas e retorna uma https://fpy.li/8-18[Geohash], usada assim: - -[source, pycon] ----- ->>> shanghai = 31.2304, 121.4737 ->>> geohash(shanghai) -'wtw3sjq6q' ----- - -O <> mostra a definição da função `geohash`, usando o pacote `geolib` do PyPI. - -[[geohash_ex_1]] -._coordinates.py_ com a função `geohash` -==== -[source, py] ----- -include::code/08-def-type-hints/coordinates/coordinates.py[tags=GEOHASH] ----- -==== -<1> Esse comentário evita que o Mypy avise que o pacote `geolib` não tem nenhuma dica de tipo. -<2> O parâmetro `lat_lon`, anotado como uma `tuple` com dois campos `float`. - -[TIP] -==== -Com Python < 3.9, importe e use `typing.Tuple` nas dicas de tipo. -Este tipo está descontinuado mas permanecerá na biblioteca padrão pelo menos até 2024. -==== - -===== Tuplas como registros com campos nomeados - -Para a anotar uma tupla com muitos campos, ou tipos específicos de tupla que seu código usa com frequência, recomendo fortemente usar `typing.NamedTuple`, como visto no <>. -O <> mostra uma variante de <> com `NamedTuple`. - -[[geohash_ex_2]] -._coordinates_named.py_ com `NamedTuple`, `Coordinates` e a função `geohash` -==== -[source, py] ----- -include::code/08-def-type-hints/coordinates/coordinates_named.py[tags=GEOHASH] ----- -==== - -Como explicado na <>, `typing.NamedTuple` é uma factory de subclasses de `tuple`, então `Coordinate` é _consistente-com_ `tuple[float, float]`, mas o inverso não é verdadeiro - afinal, `Coordinate` tem métodos extras adicionados por `NamedTuple`, como `._asdict()`, e também poderia ter métodos definidos pelo usuário. - -Na prática, isso significa que é seguro (do ponto de vista do tipo de argumento) passar uma instância de `Coordinate` para a função `display`, definida assim: - -[source, py] ----- -include::code/08-def-type-hints/coordinates/coordinates_named.py[tags=DISPLAY] ----- - - -===== Tuplas como sequências imutáveis - -Para anotar tuplas de tamanho desconhecido, usadas como listas imutáveis, você precisa especificar um único tipo, seguido de uma vírgula e `...` (isto é o símbolo de reticências do Python, formado por três pontos, não o caractere Unicode `U+2026`—`HORIZONTAL ELLIPSIS`). - -Por exemplo, `tuple[int, ...]` é uma tupla com itens `int`. - -As reticências indicam que qualquer número de elementos >= 1 é aceitável. -Não há como especificar campos de tipos diferentes para tuplas de tamanho arbitrário. - -As anotações `stuff: tuple[Any, ...]` e `stuff: tuple` são equivalentes: -`stuff` é uma tupla de tamanho desconhecido contendo objetos de qualquer tipo. - -Aqui temos um função `columnize`, que transforma uma sequência em uma tabela de colunas e células, na forma de uma lista de tuplas de tamanho desconhecido. -É útil para mostrar os itens em colunas, assim: - -[source, pycon] ----- ->>> animals = 'drake fawn heron ibex koala lynx tahr xerus yak zapus'.split() ->>> table = columnize(animals) ->>> table -[('drake', 'koala', 'yak'), ('fawn', 'lynx', 'zapus'), ('heron', 'tahr'), - ('ibex', 'xerus')] ->>> for row in table: -... print(''.join(f'{word:10}' for word in row)) -... -drake koala yak -fawn lynx zapus -heron tahr -ibex xerus ----- - -O <> mostra a implementação de `columnize`. -Observe o tipo((("", startref="Thint08")))((("", startref="GTStupple08"))) do retorno: - -[source, py] ----- -list[tuple[str, ...]] ----- - -[[columnize_ex]] -._columnize.py_ retorna uma lista de tuplas de strings -==== -[source, py] ----- -include::code/08-def-type-hints/columnize.py[tags=COLUMNIZE] ----- -==== - - -[[mapping_type_sec]] -==== Mapeamentos genéricos - -Tipos de mapeamento genéricos((("gradual type system", "generic mappings")))((("generic mapping types"))) são anotados como `MappingType[KeyType, ValueType]`. -O tipo nativo `dict` e os tipos de mapeamento em `collections` e `collections.abc` aceitam essa notação em Python ≥ 3.9. Para versões mais antigas, você deve usar `typing.Dict` e outros tipos de mapeamento no módulo `typing`, como discutimos em <>. - -O <> mostra um uso na prática de uma função que retorna um https://fpy.li/8-19[índice invertido] para permitir a busca de caracteres Unicode pelo nome — uma variação do <> mais adequada para código server-side (também chamado back-end), como veremos no <>. - -Dado o início e o final dos códigos de caractere Unicode, `name_index` retorna um `dict[str, set[str]]`, que é um índice invertido mapeando cada palavra para um conjunto de caracteres que tem aquela palavra em seus nomes. Por exemplo, após indexar os caracteres ASCII de 32 a 64, aqui estão os conjuntos de caracteres mapeados para as palavras `'SIGN'` e `'DIGIT'`, e a forma de encontrar o caractere chamado `'DIGIT EIGHT'`: - -[source, pycon] ----- ->>> index = name_index(32, 65) ->>> index['SIGN'] -{'$', '>', '=', '+', '<', '%', '#'} ->>> index['DIGIT'] -{'8', '5', '6', '2', '3', '0', '1', '4', '7', '9'} ->>> index['DIGIT'] & index['EIGHT'] -{'8'} ----- - -O <> mostra o código fonte de _charindex.py_ com a função `name_index`. -Além de uma dica de tipo `dict[]`, este exemplo tem três outros aspectos que estão aparecendo pela primeira vez no livro. - - -[[charindex_ex]] -._charindex.py_ -==== -[source, py] ----- -include::code/08-def-type-hints/charindex.py[tags=CHARINDEX] ----- -==== -<1> `tokenize` é uma função geradora. <> é sobre geradores. -<2> A variável local `index` está anotada. Sem a dica, o Mypy diz: -`Need type annotation for 'index' (hint: "index: dict[, ] = ...")`. -<3> Eu usei o operador morsa (_walrus operator_) `:=` na condição do `if`. Ele atribui o resultado da chamada a `unicodedata.name()` a `name`, e a expressão inteira é calculada a partir daquele resultado. -Quando o resultado é `''`, isso é falso, e o `index` não é atualizado.footnote:[Eu uso `:=` quando faz sentido em alguns exemplos, mas não trato desse operador no livro. Veja https://fpy.li/pep572[PEP 572—Assignment Expressions] para entender os detalhes dos operadores de atribuição.] - -[NOTE] -==== -Ao usar `dict` como um registro, é comum que todas as chaves sejam do tipo `str`, com valores de tipos diferentes dependendo das chaves. Isso é tratado na <>. -==== - - -[[type_hint_abc_sec]] -==== Classes bases abstratas - -[quote, lei de Postel, ou o Princípio da Robustez] - -____ -Seja conservador no que envia, mas liberal no que aceita. -____ - -A <> apresenta((("gradual type system", "abstract base classes", id="GTSabstract08")))((("ABCs (abstract base classes)", "type hints (type annotations)", id="ABChint08"))) várias classes abstratas de `collections.abc`. -Idealmente, uma função deveria aceitar argumentos desses tipos abstratos--ou seus equivalentes de `typing` antes do Python 3.9--e não tipos concretos. Isso dá mais flexibilidade a quem chama a função. - - -Considere essa assinatura de função: - -[source, py3] ----- -from collections.abc import Mapping - -def name2hex(name: str, color_map: Mapping[str, int]) -> str: ----- - -Usar `abc.Mapping` permite ao usuário da função fornecer uma instância de `dict`, `defaultdict`, `ChainMap`, uma subclasse de `UserDict` subclass, ou qualquer outra classe que seja um _subtipo-de_ `Mapping`. - -Por outro lado, veja essa assinatura: - -[source, py3] ----- -def name2hex(name: str, color_map: dict[str, int]) -> str: ----- - -Agora `color_map` tem que ser um `dict` ou um de seus subtipos, tal como `defaultdict` ou [.keep-together]#`OrderedDict`#. -Especificamente, uma subclasse de `collections.UserDict` não passaria pela verificação de [.keep-together]#tipo# para `color_map`, a despeito de ser a maneira recomendada de criar mapeamentos [.keep-together]#definidos pelo usuário#, como vimos na <>. -O Mypy rejeitaria um `UserDict` ou uma instância de classe derivada dele, porque `UserDict` [.keep-together]#não é# uma subclasse de `dict`; eles são irmãos. Ambos são subclasses de pass:[abc.MutableMapping].footnote:[Na verdade, `dict` é uma subclasse virtual de `abc.MutableMapping`. O conceito de subclasse virtual será explicado em <>. Por hora, basta saber que `issubclass(dict, abc.MutableMapping)` é `True`, apesar de dict ser implementado em C e não herdar nada de `abc.MutableMapping`, apenas de `object`.] - -Assim, em geral é melhor usar `abc.Mapping` ou `abc.MutableMapping` em dicas de tipos de parâmetros, em vez de `dict` (ou `typing.Dict` em código antigo). -Se a função `name2hex` não precisar modificar o `color_map` recebido, a dica de tipo mais precisa para `color_map` é `abc.Mapping`. -Desse jeito, quem chama não precisa fornecer um objeto que implemente métodos como `setdefault`, `pop`, e `update`, que fazem parte da interface de `MutableMapping`, mas não de `Mapping`. -Isso reflete a segunda parte da lei de Postel: - "[seja] liberal no que aceita." - -A lei de Postel também nos diz para sermos conservadores no que enviamos. O valor de retorno de uma função é sempre um objeto concreto, então a dica de tipo do valor de saída deve ser um tipo concreto, como no exemplo em <> — que usa `list[str]`: - -[source, python] ----- -def tokenize(text: str) -> list[str]: - return text.upper().split() ----- - -No verbete de https://docs.python.org/pt-br/3/library/typing.html#typing.List[`typing.List`] (EN - Tradução abaixo não oficial), a documentação do Python diz: - -[quote] -____ -Versão genérica de `list`. Útil para anotar tipos de retorno. Para anotar argumentos é preferível usar um tipo de coleção abstrata , tal como `Sequence` ou `Iterable`. -____ - -Comentários similares aparecem nos verbetes de https://fpy.li/8-21[`typing.Dict`] -e https://fpy.li/8-22[`typing.Set`]. - -Lembre-se que a maioria dos ABCs de `collections.abc` e outras classes concretas de `collections`, bem como as coleções nativas, suportam notação de dica de tipo genérica como `collections.deque[str]` desde o Python 3.9. As coleções correspondentes em `typing` só precisavam suportar código escrito em Python 3.8 ou anterior. A lista completa de classes que se tornaram genéricas aparece em na seção https://fpy.li/8-16["Implementation"] da -https://fpy.li/pep585[PEP 585—Type Hinting Generics In Standard Collections] (EN). - -Para encerrar nossa discussão de ABCs em dicas de tipo, precisamos falar sobre os ABCs `numbers`. - -[[numeric_tower_warning]] -===== A queda da torre numérica - -O((("numbers package")))((("numeric tower"))) pacote https://docs.python.org/pt-br/3/library/numbers.html[`numbers`] define a assim chamada _torre numérica_ (_numeric tower_) descrita na https://fpy.li/pep3141[PEP 3141—A Type Hierarchy for Numbers] (EN). -A torre é uma hierarquia linear de ABCs, com `Number` no topo: - -* `Number` -* `Complex` -* `Real` -* `Rational` -* `Integral` - -Esses ABCs funcionam perfeitamente para checagem de tipo durante a execução, mas eles não são suportados para checagem de tipo estática. A seção https://fpy.li/cardxvi["Numeric Tower"] da PEP 484 rejeita os ABCs `numbers` e [.keep-together]#manda tratar# os tipo nativos `complex`, `float`, e `int` como casos especiais, como explicado em <>. Vamos voltar a essa questão na <>, em <>, que é dedicada a comparar protocolos e ABCs - -Na prática, se você quiser anotar argumentos numéricos para checagem de tipo estática, existem algumas opções: - -. Usar um dos tipo concretos, `int`, `float`, ou `complex` — como recomendado pela PEP 488. -. Declarar um tipo union como `Union[float, Decimal, Fraction]`. -. Se você quiser evitar a codificação explícita de tipos concretos, usar protocolos numéricos como `SupportsFloat`, tratados na <>. - -A seção <> abaixo é um pré-requisito para entender protocolos numéricos. - -Antes disso, vamos examinar um dos ABCs mais úteis para dicas de tipo: `Iterable`.((("", startref="GTSabstract08")))((("", startref="ABChint08"))) - - -==== Iterable - -A((("gradual type system", "Iterable", id="GTSiterable08")))((("Iterable interface", id="iterable08")))((("interfaces", "Iterable interface"))) documentação de https://docs.python.org/pt-br/3/library/typing.html#typing.List[`typing.List`] que eu citei acima recomenda `Sequence` e `Iterable` para dicas de tipo de parâmetros de função. - -Esse é um exemplo de argumento `Iterable`, na função `math.fsum` da biblioteca padrão: - -[source, python] ----- -def fsum(__seq: Iterable[float]) -> float: ----- - -.Arquivos Stub e o Projeto Typeshed -[TIP] -==== -Até((("Typeshed project"))) o Python 3.10, a biblioteca padrão não tem anotações, mas o Mypy, o PyCharm, etc, conseguem encontrar as dicas de tipo necessárias no projeto -https://fpy.li/8-26[Typeshed], na forma de _arquivos stub_: arquivos de código-fonte especiais, com uma extensão _.pyi_, que contém assinaturas anotadas de métodos e funções, sem a implementação - muito parecidos com _headers_ em C. - -A assinatura para `math.fsum` está em https://fpy.li/8-27[_/stdlib/2and3/math.pyi_]. Os sublinhados iniciais em `__seq` são uma convenção estabelecida na PEP 484 para parâmetros apenas posicionais, como explicado em <>. -==== - -O <> é outro exemplo do uso de um parâmetro `Iterable`, que produz itens que são `tuple[str, str]`. A função é usada assim: - -[source, pycon] ----- ->>> l33t = [('a', '4'), ('e', '3'), ('i', '1'), ('o', '0')] ->>> text = 'mad skilled noob powned leet' ->>> from replacer import zip_replace ->>> zip_replace(text, l33t) -'m4d sk1ll3d n00b p0wn3d l33t' ----- - -O <> mostra a implementação. - -[[replacer_ex]] -._replacer.py_ -==== -[source, py] ----- -include::code/08-def-type-hints/replacer.py[tags=ZIP_REPLACE] ----- -==== -<1> `FromTo` é um _apelido de tipo_: eu atribui `tuple[str, str]` a `FromTo`, para tornar a assinatura de `zip_replace` mais legível. -<2> `changes` tem que ser um `Iterable[FromTo]`; é o mesmo que escrever `Iterable[tuple[str, str]]`, mas é mais curto e mais fácil de ler. - -.O TypeAlias Explícito em Python 3.10 -[TIP] -==== -https://fpy.li/pep613[PEP 613—Explicit Type Aliases] introduziu um tipo especial, o `TypeAlias`, para tornar as atribuições que criam apelidos de tipos mais visíveis e mais fáceis para os verificadores de tipo. -A partir do Python 3.10, esta é a forma preferencial de criar um apelidos de tipo. - -[source, py3] ----- -from typing import TypeAlias - -FromTo: TypeAlias = tuple[str, str] ----- -==== - - -===== abc.Iterable versus abc.Sequence - -Tanto((("abc.Iterable")))((("abc.Sequence"))) `math.fsum` quanto `replacer.zip_replace` tem que percorrer todos os argumentos do `Iterable` para produzir um resultado. Dado um iterável sem fim tal como o gerador `itertools.cycle` como entrada, essas funções consumiriam toda a memória e derrubariam o processo Python. Apesar desse perigo potencial, é muito comum no Python moderno se oferecer funções que aceitam um `Iterable` como argumento, mesmo se elas tem que processar a estrutura inteira para obter um resultado. Isso dá a quem chama a função a opção de fornecer um gerador como dado de entrada, em vez de uma sequência pré-construída, com uma grande economia potencial de memória se o número de itens de entrada for grande. - -Por outro lado, a função `columnize` no <> requer uma `Sequence`, não um `Iterable`, pois ela precisa obter a `len()` do argumento para calcular previamente o número de linhas. - -Assim como `Sequence`, o melhor uso de `Iterable` é como tipo de argumento. Ele é muito vago como um tipo de saída. 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 [.keep-together]#iteradores clássicos#.((("", startref="GTSiterable08")))((("", startref="iterable08"))) - -[[param_generics_typevar_sec]] -==== Genéricos parametrizados e TypeVar - -Um((("gradual type system", "parameterized generics and TypeVar", id="GTSparam08")))((("generic collections", "parameterized generics and TypeVar", id="GCtypvar08")))((("TypeVar", id="typevar08"))) genérico parametrizado é um tipo genérico, escrito na forma `list[T]`, onde `T` é um tipo variável que será vinculado a um tipo específico a cada uso. Isso permite que um tipo de parâmetro seja refletido no tipo resultante. - - -O <> define `sample`, uma função que recebe dois argumentos: -uma `Sequence` de elementos de tipo `T` e um `int`. -Ela retorna uma `list` de elementos do mesmo tipo `T`, escolhidos aleatoriamente do primeiro argumento. - -O <> mostra a implementação. - -[[generic_sample_ex]] -._sample.py_ -==== -[source, py] ----- -include::code/08-def-type-hints/sample.py[tags=SAMPLE] ----- -==== - -Aqui estão dois exemplos do motivo de eu usar um tipo variável em `sample`: - -* Se chamada com uma tupla de tipo `tuple[int, ...]` — que é _consistente-com_ `Sequence[int]` - então o tipo parametrizado é `int`, então o tipo de retorno é `list[int]`. -* Se chamada com uma `str` — que é _consistente-com_ `Sequence[str]` — então o tipo parametrizado é `str`, e o tipo do retorno é `list[str]`. - - -[role="man-height-2in"] -.Por que TypeVar é necessário? -[NOTE] -==== -Os autores da PEP 484 queriam introduzir dicas de tipo ao acrescentar o módulo `typing`, sem mudar nada mais na linguagem. -Com uma metaprogramação inteligente, eles poderiam fazer o operador `[]` funcionar para classes como `Sequence[T]`. -Mas o nome da variável `T` dentro dos colchetes precisa ser definido em algum lugar - ou o interpretador Python necessitaria de mudanças mais profundas, para suportar a notação de tipos genéricos como um caso especial de `[]`. -Por isso o construtor `typing.TypeVar` é necessário: para introduzir o nome da variável no _namespace_ (_espaço de nomes_) corrente. -Linguagens como Java, C# e TypeScript não exigem que o nome da variável seja declarado previamente, então eles não tem nenhum equivalente da classe TypeVar do Python. -==== - -//// -PROD: As is often the case, the NOTE above may or may not run over the next paragraphs, -depending on some random factor I don't know how to control. -//// - -Outro exemplo é a função `statistics.mode` da biblioteca padrão, que retorna o ponto de dado mais comum de uma série. - -Aqui é uma exemplo de uso da https://docs.python.org/pt-br/3/library/statistics.html#statistics.mode[documentação]: - -[source, pycon] ----- ->>> mode([1, 1, 2, 3, 3, 3, 3, 4]) -3 ----- - -Sem o uso de `TypeVar`, `mode` poderia ter uma assinatura como a apresentada no <>. - -[[mode_float_ex]] -._mode_float.py_: `mode` que opera com `float` e seus subtipos footnote:[A implementação aqui é mais simples que aquela do módulo https://fpy.li/8-29[`statistics`] na biblioteca padrão do Python] -==== -[source, py] ----- -include::code/08-def-type-hints/mode/mode_float.py[tags=MODE_FLOAT] ----- -==== - -Muitos dos usos de `mode` envolvem valores `int` ou `float`, mas o Python tem outros tipos numéricos, e é desejável que o tipo de retorno siga o tipo dos elementos do `Iterable` recebido. -Podemos melhorar aquela assinatura usando `TypeVar`. Vamos começar com uma assinatura parametrizada simples, mas errada. - -[source, python3] ----- -from collections.abc import Iterable -from typing import TypeVar - -T = TypeVar('T') - -def mode(data: Iterable[T]) -> T: ----- - -Quando aparece pela primeira vez na assinatura, o tipo parametrizado `T` pode ser qualquer tipo. Da segunda vez que aparece, ele vai significar o mesmo tipo que da primeira vez. - -Assim, qualquer iterável é _consistente-com_ `Iterable[T]`, incluindo iterável de tipos _unhashable_ que `collections.Counter` não consegue tratar. -Precisamos restringir os tipos possíveis de se atribuir a `T`. -Vamos ver maneiras diferentes de fazer isso nas duas seções seguintes. - -[[typevar_constraints_sec]] -===== TypeVar restrito - -O `TypeVar` aceita argumentos posicionais adicionais para restringir o tipo parametrizado. -Podemos melhorar a assinatura de `mode` para aceitar um número específico de tipos, assim: - -[source, python3] ----- -from collections.abc import Iterable -from decimal import Decimal -from fractions import Fraction -from typing import TypeVar - -NumberT = TypeVar('NumberT', float, Decimal, Fraction) - -def mode(data: Iterable[NumberT]) -> NumberT: ----- - -Está melhor que antes, e era a assinatura de `mode` em -https://fpy.li/8-30[_statistics.pyi_], o arquivo stub em `typeshed` em 25 de maio de 2020. - -Entretanto, a documentação em https://fpy.li/8-28[`statistics.mode`] inclui esse exemplo: - -[source, pycon] ----- ->>> mode(["red", "blue", "blue", "red", "green", "red", "red"]) -'red' ----- - -Na pressa, poderíamos apenas adicionar `str` à definição de `NumberT`: - -[source, python3] ----- -NumberT = TypeVar('NumberT', float, Decimal, Fraction, str) ----- - -Com certeza funciona, mas `NumberT` estaria muito mal batizado se aceitasse `str`. -Mais importante, não podemos ficar listando tipos para sempre, cada vez que percebermos que `mode` pode lidar com outro deles. -Podemos fazer com melhor com um outro recurso de `TypeVar`, como veremos a seguir. - -[[bounded_typevar_sec]] -===== TypeVar delimitada - -Examinando o corpo de `mode` no <>, vemos que a classe `Counter` é usada para classificação. `Counter` é baseada em `dict`, então o tipo do elemento do iterável `data` precisa ser _hashable_. - -A princípio, essa assinatura pode parecer que funciona: - -[source, python3] ----- -from collections.abc import Iterable, Hashable - -def mode(data: Iterable[Hashable]) -> Hashable: ----- - -Agora o problema é que o tipo do item retornado é `Hashable`: -um ABC que implementa apenas o método `+__hash__+`. -Então o verificador de tipo não vai permitir que façamos nada com o valor retornado, exceto chamar seu método `hash()`. Não é muito útil. - -A solução está em outro parâmetro opcional de `TypeVar`: o parâmetro representado pela palavra-chave `bound`. Ele estabelece um limite superior para os tipos aceitos. -No <>, temos `bound=Hashable`. Isso significa que o tipo do parâmetro pode ser `Hashable` ou qualquer _subtipo-de_ `Hashable`.footnote:[Eu contribui com essa solução para `typeshed`, e em 26 de maio de 2020 `mode` aparecia anotado assim em -https://fpy.li/8-32[_statistics.pyi_].] - -[[mode_hashable_ex]] -._mode_hashable.py_: igual a <>, mas com uma assinatura mais flexível -==== -[source, py] ----- -include::code/08-def-type-hints/mode/mode_hashable.py[tags=MODE_HASHABLE_T] ----- -==== - -Em resumo: - -* Um tipo variável restrito será concretizado em um dos tipos nomeados na declaração do TypeVar. -* Um tipo variável delimitado será concretizado pata o tipo inferido da expressão - desde que o tipo inferido seja _consistente-com_ o limite declarado pelo argumento `bound=` do TypeVar. - -[NOTE] -==== -É um pouco lamentável que a palavra-chave do argumento para declarar um `TypeVar` delimitado tenha sido chamado `bound=`, pois o verbo "to bind" (_ligar ou vincular_) é normalmente usado para indicar o estabelecimento do valor de uma variável, que na semântica de referência do Python é melhor descrita como vincular (_bind_) um nome a um valor. -Teria sido menos confuso se a palavra-chave do argumento tivesse sido chamada `boundary=`. -==== - -O construtor de `typing.TypeVar` tem outros parâmetros opcionais - `covariant` e `contravariant` — que veremos em <>, <>. - -Agora vamos concluir essa introdução a `TypeVar` com `AnyStr`. - - -===== O tipo variável pré-definido AnyStr - -O((("AnyStr"))) módulo `typing` inclui um TypeVar pré-definido chamado `AnyStr`. -Ele está definido assim: - -[source, python3] ----- -AnyStr = TypeVar('AnyStr', bytes, str) ----- - -`AnyStr` é usado em muitas funções que aceitam tanto `bytes` quanto `str`, e retornam valores do tipo recebido. - -Agora vamos ver `typing.Protocol`, um novo recurso do Python 3.8, capaz de permitir um uso de dicas de tipo mais pythônico.((("", startref="GTSparam08")))((("", startref="GCtypvar08")))((("", startref="typevar08"))) - -[[protocols_in_fn]] -==== Protocolos estáticos - -[NOTE] -==== -Em ((("gradual type system", "static protocols", id="GTSstatic08")))((("static protocols", "type hints (type annotations)", id="stprot08"))) programação orientada a objetos, o conceito de um "protocolo" como uma interface informal é tão antigo quando o Smalltalk, e foi uma parte essencial do Python desde o início. Entretanto, no contexto de dicas de tipo, um protocolo é uma subclasse de `typing.Protocol`, definindo uma interface que um verificador de tipo pode analisar. Os dois tipos de protocolo são tratados em <>. Aqui apresento apenas uma rápida introdução no contexto de anotações de função. -==== - -O((("Protocol type", id="proto08"))) tipo `Protocol`, como descrito em -https://fpy.li/pep544[PEP 544—Protocols: Structural subtyping (static duck typing)] (EN), é similar às interfaces em Go: um tipo protocolo é definido especificando um ou mais métodos, e o verificador de tipo analisa se aqueles métodos estão implementados onde um tipo daquele protocolo é usado. - -Em Python, uma definição de protocolo é escrita como uma subclasse de `typing.Protocol`. -Entretanto, classes que _implementam_ um protocolo não precisam herdar, registrar ou declarar qualquer relação com a classe que _define_ o protocolo. É função do verificador de tipo encontrar os tipos de protocolos disponíveis e exigir sua utilização. - -Abaixo temos um problema que pode ser resolvido com a ajuda de `Protocol` e `TypeVar`. -Suponha que você quisesse criar uma função `top(it, n)`, que retorna os `n` maiores elementos do iterável `it`: - -[source, pycon] ----- -include::code/08-def-type-hints/comparable/top.py[tags=TOP_DOCTEST] ----- - -Um genérico parametrizado `top` ficaria parecido com o mostrado no <>. - -[[top_undefined_t_ex]] -.a função `top` function com um parâmetro de tipo `T` indefinido -==== -[source, py] ----- -def top(series: Iterable[T], length: int) -> list[T]: - ordered = sorted(series, reverse=True) - return ordered[:length] ----- -==== - -O problema é, como restringir `T`? -Ele não pode ser `Any` ou `object`, pois `series` precisa funcionar com `sorted`. A `sorted` nativa na verdade aceita `Iterable[Any]`, mas só porque o parâmetro opcional `key` recebe uma função que calcula uma chave de ordenação arbitrária para cada elemento. O que acontece se você passar para `sorted` uma lista de objetos simples, mas não fornecer um argumento `key`? -Vamos tentar: - -[source, pycon] ----- ->>> l = [object() for _ in range(4)] ->>> l -[, , -, ] ->>> sorted(l) -Traceback (most recent call last): - File "", line 1, in -TypeError: '<' not supported between instances of 'object' and 'object' ----- - -A mensagem de erro mostra que `sorted` usa o operador `<` nos elementos do iterável. -É só isso? Vamos tentar outro experimento rápido:footnote:[Não é maravilhoso poder abrir um console iterativo e contar com o duck typing para explorar recursos da linguagem, como acabei de fazer? Eu sinto muita falta deste tipo de exploração quando uso linguagem que não tem esse recurso.] - -[source, pycon] ----- ->>> class Spam: -... def __init__(self, n): self.n = n -... def __lt__(self, other): return self.n < other.n -... def __repr__(self): return f'Spam({self.n})' -... ->>> l = [Spam(n) for n in range(5, 0, -1)] ->>> l -[Spam(5), Spam(4), Spam(3), Spam(2), Spam(1)] ->>> sorted(l) -[Spam(1), Spam(2), Spam(3), Spam(4), Spam(5)] ----- - -Isso confirma a suspeita: eu consigo passar um lista de `Spam` para `sort`, porque `Spam` implementa `+__lt__+` — o método especial subjacente ao operador `<`. - -Então o parâmetro de tipo `T` no <> deveria ser limitado a tipos que implementam `+__lt__+`. -No <>, precisávamos de um parâmetro de tipo que implementava `+__hash__+`, para poder usar `typing.Hashable` como limite superior do parâmetro de tipo. -Mas agora não há um tipo adequado em `typing` ou `abc` para usarmos, então precisamos criar um. - -O <> mostra o novo tipo `SupportsLessThan`, um `Protocol`. - -[[comparable_protocol_ex]] -._comparable.py_: a definição de um tipo `Protocol`, `SupportsLessThan` -==== -[source, py] ----- -include::code/08-def-type-hints/comparable/comparable.py[] ----- -==== -<1> Um protocolo é uma subclasse de `typing.Protocol`. -<2> O corpo do protocolo tem uma ou mais definições de método, com `...` em seus corpos. - -Um tipo `T` é _consistente-com_ um protocolo `P` se `T` implementa todos os métodos definido em `P`, com assinaturas de tipo correspondentes. - -Dado `SupportsLessThan`, nós agora podemos definir essa versão funcional de `top` no <>. - -[[top_protocol_ex]] -._top.py_: definição da função `top` usando uma `TypeVar` com `bound=SupportsLessThan` -==== -[source, py] ----- -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`. -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]] -._top_test.py_: visão parcial da bateria de testes para `top` -==== -[source, py] ----- -include::code/08-def-type-hints/comparable/top_test.py[tags=TOP_IMPORT] - -# muitas linhas omitidas - -include::code/08-def-type-hints/comparable/top_test.py[tags=TOP_TEST] ----- -==== -<1> A constante `typing.TYPE_CHECKING` é sempre `False` durante a execução do programa, mas os verificadores de tipo fingem que ela é `True` quando estão fazendo a verificação. -<2> Declaração de tipo explícita para a variável `series`, para tornar mais fácil a leitura da saída do Mypy.footnote:[Sem essa dica de tipo, o Mypy inferiria o tipo de `series` como `Generator[Tuple[builtins.int, builtins.str*], None, None]`, que é prolixo mas _consistente-com_ `Iterator[tuple[int, str]]`, como veremos na <>.] -<3> Esse `if` evita que as três linhas seguintes sejam executadas durante o teste. -<4> `reveal_type()` não pode ser chamada durante a execução, porque não é uma função regular, mas sim um mecanismo de depuração do Mypy - por isso não há `import` para ela. Mypy vai produzir uma mensagem de depuração para cada chamada à pseudo-função `reveal_type()`, mostrando o tipo inferido do argumento. -<5> Essa linha será marcada pelo Mypy como um erro. - -Os testes anteriores são bem sucedidos - mas eles funcionariam de qualquer forma, com ou sem dicas de tipo em _top.py_. -Mais precisamente, se eu verificar aquele arquivo de teste com o Mypy, verei que o `TypeVar` está funcionando como o esperado. -Veja a saída do comando `mypy` no <>. - -[WARNING] -==== -Desde o Mypy 0.910 (julho de 2021), em alguns casos a saída de `reveal_type` não mostra precisamente os tipos que eu declarei, mas mostra tipos compatíveis. Por exemplo, eu não usei `typing.Iterator` e sim `abc.Iterator`. -Por favor, ignore esse detalhe. O relatório do Mypy ainda é útil. Vou fingir que esse problema do Mypy já foi corrigido quando for discutir os resultados. -==== - -[[top_protocol_mypy_output]] -.Saída do _mypy top_test.py_ (linha quebradas para facilitar a leitura) -==== -[source] ----- -…/comparable/ $ mypy top_test.py -top_test.py:32: note: - Revealed type is "typing.Iterator[Tuple[builtins.int, builtins.str]]" <1> -top_test.py:33: note: - Revealed type is "builtins.list[Tuple[builtins.int, builtins.str]]" -top_test.py:34: note: - Revealed type is "builtins.list[Tuple[builtins.int, builtins.str]]" <2> -top_test.py:41: note: - Revealed type is "builtins.list[builtins.object*]" <3> -top_test.py:43: error: - Value of type variable "LT" of "top" cannot be "object" <4> -Found 1 error in 1 file (checked 1 source file) ----- -==== -<1> Em `test_top_tuples`, `reveal_type(series)` mostra que ele é um `Iterator[tuple[int, str]]`— que eu declarei explicitamente. -<2> `reveal_type(result)` confirma que o tipo produzido pela chamada a `top` é o que eu queria: dado o tipo de `series`, o `result` é `list[tuple[int, str]]`. -<3> Em `test_top_objects_error`, `reveal_type(series)` mostra que ele é uma `list[object*]`. Mypy põe um `*` após qualquer tipo que tenha sido inferido: eu não anotei o tipo de `series` nesse teste. -<4> Mypy marca o erro que esse teste produz intencionalmente: o tipo dos elementos do `Iterable` `series` não pode ser `object` (ele tem que ser do tipo `SupportsLessThan`). - -A principal vantagem de um tipo protocolo sobre os ABCs é que o tipo não precisa de nenhuma declaração especial para ser _consistente-com_ um tipo protocolo. Isso permite que um protocolo seja criado aproveitando tipos pré-existentes, ou tipos implementados em bases de código que não estão sob nosso controle. -Eu não tenho que derivar ou registrar `str`, `tuple`, `float`, `set`, etc. com `SupportsLessThan` para usá-los onde um parâmetro `SupportsLessThan` é esperado. -Eles só precisam implementar `+__lt__+`. -E o verificador de tipo ainda será capaz de realizar seu trabalho, porque `SupportsLessThan` está explicitamente declarado como um `Protocol`— diferente dos protocolos implícitos comuns no duck typing, que são invisíveis para o verificador de tipos. - -A classe especial `Protocol` foi introduzida na -https://fpy.li/pep544[PEP 544—Protocols: Structural subtyping (static duck typing)]. -O <> demonstra((("static duck typing"))) porque esse recurso é conhecido como _duck typing estático_ (_static duck typing_): a solução para anotar o parâmetro `series` de `top` era dizer "O tipo nominal de `series` não importa, desde que ele implemente o método `+__lt__+`." -Em Python, o duck typing sempre permitiu dizer isso de forma implícita, deixando os verificadores de tipo estáticos sem ação. -Um verificador de tipo não consegue ler o código fonte em C do CPython, ou executar experimentos no console para descobrir que `sorted` só requer que seus elementos suportem `<`. - -Agora podemos tornar o duck typing explícito para os verificadores estáticos de tipo. Por isso faz sentido dizer que `typing.Protocol` nos oferece _duck typing estático_.footnote:[Eu não sei quem inventou a expressão _duck tying estático_, mas ela se tornou mais popular com a linguagem Go, que tem uma semântica de interfaces que é mais parecida com os protocolos de Python que com as interfaces nominais de Java.] - -Há mais para falar sobre `typing.Protocol`. Vamos voltar a ele na Parte IV, onde <> compara as abordagens da tipagem estrutural, do duck typing e dos ABCs - outro modo de formalizar protocolos. -Além disso, a <> (no <>) explica como declarar assinaturas de funções de sobrecarga (_overload_) com `@typing.overload`, e inclui um exemplo bastante extenso usando `typing.Protocol` e uma `TypeVar` delimitada. - -[NOTE] -==== -O `typing.Protocol` torna possível anotar a função `double` na <> sem perder funcionalidade. O segredo é definir uma classe de protocolo com o método `+__mul__+`. -Convido o leitor a fazer isso como um exercício. A solução está na <> (<>).((("", startref="GTSstatic08")))((("", startref="stprot08")))((("", startref="proto08"))) -==== - - -==== Callable - -Para((("gradual type system", "Callable type", id="GTScallable08")))((("Callable type", id="callable08"))) anotar parâmetros de callback ou objetos _callable_ retornados por funções de ordem superior, o módulo `collections.abc` oferece o tipo `Callable`, disponível no módulo `typing` para quem ainda não estiver usando Python 3.9. -Um tipo `Callable` é parametrizado assim: - -[source, python3] ----- -Callable[[ParamType1, ParamType2], ReturnType] ----- - -A lista de parâmetros - `[ParamType1, ParamType2]` — pode ter zero ou mais tipos. - -Aqui está um exemplo no contexto de uma função `repl`, parte do interpretador iterativo simples que veremos na <>:footnote:[REPL -significa Read-Eval-Print-Loop (_Ler-Calcular-Imprimir-Recomeçar_), o comportamento básico de interpretadores iterativos.] - -[source, python3] ----- -def repl(input_fn: Callable[[Any], str] = input]) -> None: ----- -Durante a utilização normal, a função `repl` usa a `input` nativa do Python para ler expressões inseridas pelo usuário. -Entretanto, para testagem automatizada ou para integração com outras fontes de input, `repl` aceita um parâmetro `input_fn` opcional: -um `Callable` com o mesmo parâmetro e tipo de retorno de `input`. - -A `input` nativa tem a seguinte assinatura no typeshed: - -[source, python3] ----- -def input(__prompt: Any = ...) -> str: ... ----- - -A assinatura de `input` é _consistente-com_ esta dica de tipo `Callable` - - -[source, python3] ----- -Callable[[Any], str] ----- -Não existe sintaxe para a nomear tipo de argumentos opcionais ou de palavra-chave. A -https://docs.python.org/pt-br/3/library/typing.html#typing.Callable[documentação] de `typing.Callable` diz "tais funções são raramente usadas como tipo de callback." Se você precisar de um dica de tipo para acompanhar uma função com assinatura flexível, substitua o lista de parâmetros inteira por `...` - assim: - -[source, python3] ----- -Callable[..., ReturnType] ----- - -A interação de parâmetros de tipo genéricos com uma hierarquia de tipos introduz um novo conceito: variância. - -[[callable_variance_sec]] -===== Variância em tipos callable - -Imagine um sistema de controle de temperatura com uma função `update` simples, como mostrada no <>.((("variance", "in callable types")))((("covariance", see="variance")))((("contravariance", see="variance"))) -A função `update` chama a função `probe` para obter a temperatura atual, e chama `display` para mostrar a temperatura para o usuário. -`probe` e `display` são ambas passadas como argumentos para `update`, por motivos didáticos. O objetivo do exemplo é contrastar duas anotações de `Callable`: uma com um tipo de retorno e outro com um tipo de parâmetro. - -[[callable_variance_ex]] -.Ilustrando a variância. -==== -[source, py] ----- -include::code/08-def-type-hints/callable/variance.py[] ----- -==== -<1> `update` recebe duas funções callable como argumentos. -<2> `probe` precisa ser uma callable que não recebe nenhuma argumento e retorna um `float` -<3> `display` recebe um argumento `float` e retorna `None`. -<4> `probe_ok` é _consistente-com_ `Callable[[], float]` porque retornar um `int` não quebra código que espera um `float`. -<5> `display_wrong` não é _consistente-com_ `Callable[[float], None]` porque não há garantia que uma função esperando um `int` consiga lidar com um `float`; por exemplo, a função `hex` do Python aceita um `int` mas rejeita um `float`. -<6> O Mypy marca essa linha porque `display_wrong` é incompatível com a dica de tipo no parâmetro `display` em `update`. -<7> `display_ok` é _consistente_com_ `Callable[[float], None]` porque uma função que aceita um `complex` também consegue lidar com um argumento `float`. -<8> Mypy está satisfeito com essa linha. - -Resumindo, -não há problema em fornecer uma função de callback que retorne um `int` quando o código espera uma função callback que retorne um `float`, porque um valor `int` sempre pode ser usado onde um `float` é esperado. - -Formalmente, dizemos que `Callable[[], int]` é _subtipo-de_ `Callable[[], float]`— assim como `int` é _subtipo-de_ `float`. -Isso significa que `Callable` é _covariante_ no que diz respeito aos tipos de retorno, porque a relação _subtipo-de_ dos tipos `int` e `float` aponta na mesma direção que os tipo `Callable` que os usam como tipos de retorno. - -Por outro lado, é um erro de tipo fornecer uma função callback que recebe um argumento `int` quando é necessário um callback que possa processar um `float`. - -Formalmente, `Callable[[int], None]` não é _subtipo-de_ `Callable[[float], None]`. -Apesar de `int` ser _subtipo-de_ `float`, no `Callable` parametrizado a relação é invertida: -`Callable[[float], None]` é _subtipo-de_ `Callable[[int], None]`. -Assim dizemos que aquele `Callable` é _contravariante_ a respeito dos tipos de parâmetros declarados. - -A <> no <> explica variância em mais detalhes e com exemplos de tipos invariantes, covariantes e contravariantes. - -[TIP] -==== -Por hora, saiba que a maioria dos tipos genéricos parametrizados são _invariantes_, portanto mais simples. -Por exemplo, se eu declaro `scores: list[float]`, -isso me diz exatamente o que posso atribuir a `scores`. -Não posso atribuir objetos declarados como `list[int]` ou `list[complex]`: - -* Um objeto `list[int]` não é aceitável porque ele não pode conter valores `float` que meu código pode precisar colocar em `scores`. -* Um objeto `list[complex]` não é aceitável porque meu código pode precisar ordenar `scores` para encontrar a mediana, mas `complex` não fornece o método `+__lt__+`, então `list[complex]` não é ordenável. -==== - -Agora chegamos ou último tipo especial que examinaremos nesse capítulo. - -[[noreturn_sec]] -==== NoReturn - -Esse((("gradual type system", "NoReturn type")))((("NoReturn type"))) é um tipo especial usado apenas para anotar o tipo de retorno de funções que nunca retornam. -Normalmente, elas existem para gerar exceções. -Há dúzias dessas funções na biblioteca padrão. - -Por exemplo, `sys.exit()` levanta `SystemExit` para encerrar o processo Python. - -Sua assinatura no `typeshed` é: - -[source, python3] ----- -def exit(__status: object = ...) -> NoReturn: ... ----- - -O parâmetro `+__status__+` é apenas posicional, e tem um valor default. -Arquivos stub não contém valores default, em vez disso eles usam `...`. -O tipo de `__status` é `object`, o que significa que pode também ser `None`, -assim seria redundante escrever `Optional[object]`. - -Na <>, -o <> usa `NoReturn` em `__flag_unknown_attrs`, um método projetado para produzir uma mensagem de erro completa e amigável, e então levanta um `AttributeError`. - -A última seção desse capítulo épico é sobre parâmetros posicionais e variádicos -((("", startref="THTusable08")))((("", startref="FTHusable08"))) - -[[arbitrary_arguments_sec]] -=== Anotando parâmetros apenas posicionais e variádicos - -Lembra((("functions, type hints in", "annotating positional only and variadic parameters")))((("type hints (type annotations)", "annotating positional only and variadic parameters")))((("parameters", "annotating positional only and variadic parameters")))((("variadic parameters"))) da função `tag` do <>? -Da última vez que vimos sua assinatura foi em <>: - -[source, python] ----- -def tag(name, /, *content, class_=None, **attrs): ----- - -Aqui está `tag`, completamente anotada e ocupando várias linhas - uma convenção comum para assinaturas longas, -com quebras de linha como o formatador https://fpy.li/8-10[_blue_] faria: - -[source, python] ----- -from typing import Optional - -def tag( - name: str, - /, - *content: str, - class_: Optional[str] = None, - **attrs: str, -) -> str: ----- - -Observe a dica de tipo `*content: str`, para parâmetros posicionais arbitrários; -Isso significa que todos aqueles argumentos tem que ser do tipo `str`. -O tipo da variável local `content` no corpo da função será `tuple[str, ...]`. - -A dica de tipo para argumentos de palavra-chave arbitrários é `**attrs: str` neste exemplo, portanto o tipo de `attrs` dentro da função será `dict[str, str]`. -Para uma [.keep-together]#dica de tipo# como `**attrs: float`, -o tipo de `attrs` na função seria [.keep-together]#`dict[str, float]`.#`` - -Se for necessário que o parâmetro `attrs` aceite valores de tipos diferentes, é preciso usar uma `Union[]` ou `Any`: `**attrs: Any`. - -A notação `/` para parâmetros puramente posicionais só está disponível com Python ≥ 3.8. -Em Python 3.7 ou anterior, isso é um erro de sintaxe. -A https://fpy.li/8-36[convenção da PEP 484] é prefixar o nome cada parâmetro puramente posicional com dois sublinhados. -Veja a assinatura de `tag` novamente, agora em duas linhas, usando a convenção da PEP 484: - -[source, python] ----- -from typing import Optional - -def tag(__name: str, *content: str, class_: Optional[str] = None, - **attrs: str) -> str: ----- - -O Mypy entende e aplica as duas formas de declarar parâmetros puramente posicionais. - -Para encerrar esse capítulo, vamos considerar brevemente os limites das dicas de tipo e do sistema de tipagem estática que elas suportam. - -=== Tipos imperfeitos e testes poderosos - -Os mantenedores((("functions, type hints in", "flawed typing and strong testing")))((("type hints (type annotations)", "flawed typing and strong testing")))((("flawed typing")))((("strong testing"))) de grandes bases de código corporativas relatam que muitos bugs são encontrados por verificadores de tipo estáticos, e o custo de resolvê-los é menor que se os mesmos bugs fossem descobertos apenas após o código estar rodando em produção. -Entretanto, é essencial observar que a testagem automatizada era uma prática padrão largamente adotada muito antes da tipagem estática ser introduzida nas empresas que eu conheço. - -Mesmo em contextos onde ela é mais benéfica, a tipagem estática não pode ser elevada a árbitro final da correção. -Não é difícil encontrar: - -Falsos Positivos:: Ferramentas indicam erros de tipagem em código correto. -Falsos Negativos:: Ferramentas não indicam erros em código incorreto. - -Além disso, se formos forçados a checar o tipo de tudo, perdemos um pouco do poder expressivo do Python: - -* Alguns recursos convenientes não podem ser checados de forma estática: por exemplo, o desempacotamento de argumentos como em `config(**settings)`. -* Recursos avançados como propriedades, descritores, metaclasses e metaprogramação em geral, têm suporte muito deficiente ou estão além da compreensão dos verificadores [.keep-together]#de tipo# -* Verificadores de tipo ficam obsoletos e/ou incompatíveis após o lançamento de novas versões do Python, rejeitando ou mesmo quebrando ao analisar código com novos recursos da linguagem - algumas vezes por mais de um ano. - -Restrições comuns de dados não podem ser expressas no sistema de tipo - mesmo restrições simples. -Por exemplo, dicas de tipo são incapazes de assegurar que "quantidade deve ser um inteiro > 0" ou que "label deve ser uma string com 6 a 12 letras em ASCII." -Em geral, dicas de tipo não são úteis para localizar erros na lógica do negócio subjacente ao código. - -Dadas essas ressalvas, dicas de tipo não podem ser o pilar central da qualidade do software, e torná-las obrigatórias sem qualquer exceção só amplificaria os aspectos negativos. - -Considere o verificador de tipo estático como uma das ferramentas na estrutura moderna de integração de código, ao lado de testadores, analisadores de código (_linters_), etc. -O objetivo de uma estrutura de produção de integração de código é reduzir as falhas no software, e testes automatizados podem encontrar muitos bugs que estão fora do alcance de dicas de tipo. Qualquer código que possa ser escrito em Python pode ser testado em Python - com ou sem dicas de tipo. - -[NOTE] -==== -O título e a conclusão dessa seção foram inspirados pelo artigo -https://fpy.li/8-37["Strong Typing vs. Strong Testing"] (EN) de Bruce Eckel, também publicado na antologia https://fpy.li/8-38[_The Best Software Writing I_] (EN), editada por Joel Spolsky (Apress). -Bruce é um fã de Python, e autor de livros sobre C++, Java, Scala, e -Kotlin. Naquele texto, ele conta como foi um defensor da tipagem estática até aprender Python, e conclui: -"Se um programa em Python tem testes de unidade adequados, ele poderá ser tão robusto quanto um programa em C++, Java, ou C# com testes de unidade adequados (mas será mais rápido escrever os testes em Python). -==== - -// [role="pagebreak-before less_space"] -Isso encerra nossa cobertura das dicas de tipo em Python por agora. -Elas serão também o ponto central do <>, que trata de classes genéricas, -variância, assinaturas sobrecarregadas, coerção de tipos (_type casting_), entre outros tópicos. -Até lá, as dicas de tipo aparecerão em várias funções ao longo do livro. - - -=== Resumo do capítulo - -Começamos((("functions, type hints in", "overview of")))((("type hints (type annotations)", "overview of"))) com uma pequena introdução ao conceito de tipagem gradual, depois adotamos uma abordagem prática. É difícil ver como a tipagem gradual funciona sem uma ferramenta que efetivamente leia as dicas de tipo, então desenvolvemos uma função anotada guiados pelos relatórios de erro do Mypy. - -Voltando à ideia de tipagem gradual, vimos como ela é um híbrido do duck typing tradicional de Python e da tipagem nominal mais familiar aos usuários de Java, C++ e de outra linguagens de tipagem estática. - -A maior parte do capítulo foi dedicada a apresentar os principais grupos de tipos usados em anotações. -Muitos dos tipos discutidos estão relacionados a tipos conhecidos de objetos do Python, tais como coleções, tuplas e callables - estendidos para suportar notação genérica do tipo `Sequence[float]`. -Muitos daqueles tipos são substitutos temporários, implementados no módulo `typing` antes que os tipos padrão fossem modificados para suportar genéricos, no Python 3.9. - -Alguns desses tipos são entidade especiais. -`Any`, `Optional`, `Union`, e `NoReturn` não tem qualquer relação com objetos reais na memória, existem apenas no domínio abstrato do sistema de tipos. - -Estudamos genéricos parametrizados e variáveis de tipo, que trazem mais flexibilidade para as dicas de tipo sem sacrificar a segurança da tipagem. - -Genéricos parametrizáveis se tornam ainda mais expressivos com o uso de `Protocol`. -Como só surgiu no Python 3.8, `Protocol` ainda não é muito usado - mas é de uma enorme importância. -`Protocol` permite duck typing estático: -É a ponte fundamental entre o núcleo do Python, coberto pelo duck typing, e a tipagem nominal que permite a verificadores de tipo estáticos encontrarem bugs. - -Ao discutir alguns desses tipos, usamos o Mypy para localizar erros de checagem de tipo e tipos inferidos, com a ajuda da função mágica `reveal_type()` do Mypy. - -A seção final mostrou como anotar parâmetros exclusivamente posicionais e variádicos. - -Dicas de tipo são um tópico complexo e em constante evolução. -Felizmente elas são um recurso opcional. -Vamos manter o Python acessível para a maior base de usuários possível, e parar de defender que todo código Python precisa ter dicas de tipo - como já presenciei em sermões públicos de evangelistas da tipagem. - -Nosso BDFLfootnote:["Benevolent Dictator For Life." - Ditador Benevolente Vitalício. Veja Guido van van Rossum em https://fpy.li/bdfl["Origin of BDFL"].] emérito liderou a movimento de inclusão de dicas de tipo em Python, então é muito justo que esse capítulo comece e termine com palavras dele. - -[quote, Guido van Rossum] -____ -Não gostaria de uma versão de Python na qual eu fosse moralmente obrigado a adicionar dicas de tipo o tempo todo. -Eu realmente acho que dicas de tipo tem seu lugar, mas há muitas ocasiões em que elas não valem a pena, e é maravilhoso que possamos escolher usá-las.footnote:[Do vídeo no Youtube, https://fpy.li/8-39["Type Hints by Guido van Rossum (March 2015)"] (EN). A citação começa em https://fpy.li/8-40[13'40"]. Editei levemente a transcrição para manter a clareza.] -____ - - -=== Para saber mais - -Bernát Gábor escreveu((("functions, type hints in", "further reading on")))((("type hints (type annotations)", "further reading on"))) em seu excelente post, -https://fpy.li/8-41["The state of type hints in Python"] (EN): - -[quote] -____ -Dicas de Tipo deveriam ser usadas sempre que valha à pena escrever testes de unidade . -____ - -Eu sou um grande fã de testes, mas também escrevo muito código exploratório. Quando estou explorando, testes e dicas de tipo não ajudam. São um entrave. - -Esse post do Gábor é uma das melhores introduções a dicas de tipo em Python que eu já encontrei, junto com o texto de Geir Arne Hjelle, -https://fpy.li/8-42["Python Type Checking (Guide)"] (EN). -https://fpy.li/8-43["Hypermodern Python Chapter 4: Typing"] (EN), de Claudio Jolowicz, é uma introdução mas curta que também fala de validação de checagem de tipo durante a execução. - -Para uma abordagem mais aprofundada, a https://fpy.li/8-44[documentação do Mypy] -é a melhor fonte. Ela é útil independente do verificador de tipo que você esteja usando, pois tem páginas de tutorial e de referência sobre tipagem em Python em geral - não apenas sobre o próprio Mypy. - -Lá você também encontrará uma conveniente -https://fpy.li/8-45[página de referência (ou _cheat sheet)] (EN) -e uma página muito útil sobre -https://fpy.li/8-46[problemas comuns e suas soluções] (EN). - -A documentação do módulo https://docs.python.org/pt-br/3/library/typing.html[`typing`] é uma boa referência rápida, mas não entra em muitos detalhes. - -A https://fpy.li/pep483[PEP 483—The Theory of Type Hints] (EN) inclui uma explicação aprofundada sobre variância, usando `Callable` para ilustrar a contravariância. -As referências definitivas são as PEP relacionadas a tipagem. -Já existem mais de 20 delas. -A audiência alvo das PEPs são os core developers (_desenvolvedores principais da linguagem em si_) e o Steering Council do Python, então elas pressupõe uma grande quantidade de conhecimento prévio, e certamente não são uma leitura leve. - -Como já mencionado, o <> cobre outros tópicos sobre tipagem, e a -<> traz referências adicionais, incluindo a -<>, com a lista das PEPs sobre tipagem aprovadas ou em discussão até o final de 2021. - -https://fpy.li/8-47["Awesome Python Typing"] é uma ótima coleção de links para ferramentas e referências. - -[[type_hints_in_def_soapbox]] -.Ponto de vista -**** - -[role="soapbox-title"] -Apenas Pedale - -[quote, Grant Petersen, Just Ride: A Radically Practical Guide to Riding Your Bike (Apenas Pedale: Um Guia Radicalmente Prático sobre o Uso de sua Bicicleta) (Workman Publishing)] -____ -Esqueça((("functions, type hints in", "Soapbox discussion", id="FTHsoap08")))((("type hints (type annotations)", "Soapbox discussion", id="THsoag08"))) as desconfortáveis bicicletas ultraleves, as malhas brilhantes, os sapatos desajeitados que se prendem a pedais minúsculos, o esforço de quilômetros intermináveis. Em vez disso, faça como você fazia quando era criança - suba na sua bicicleta e descubra o puro prazer de pedalar. -____ - - -Se((("Soapbox sidebars", "type hints (type annotations)", id="SStypehints08"))) programar não é sua profissão principal, mas uma ferramenta útil no seu trabalho ou algo que você faz para aprender, experimentar e se divertir, você provavelmente não precisa de dicas de tipo mais que a maioria dos ciclistas precisa de sapatos com solas rígidas e presilhas metálicas. - -Apenas programe. - -[role="soapbox-title"] -O Efeito Cognitivo da Tipagem - -Eu me preocupo com o efeito que as dicas de tipo terão sobre o estilo de programação em Python. - -Concordo que usuários da maioria das APIs se beneficiam de dicas de tipo. Mas o Python me atraiu - entre outras razões - porque proporciona funções tão poderosas que substituem APIs inteiras, e podemos escrever nós mesmos funções poderosas similares. -Considere a função nativa https://fpy.li/8-48[`max()`]. -Ela é poderosa, entretanto fácil de entender. Mas vou mostrar na <> que são necessárias 14 linhas de dicas de tipo para anotar corretamente essa função - sem contar um `typing.Protocol` e algumas definições de `TypeVar` para sustentar aquelas dicas de tipo. - -Me inquieta que a coação estrita de dicas de tipo em bibliotecas desencorajem programadores de sequer considerarem programar funções assim no futuro. - -De acordo com o verbete em inglês na Wikipedia, https://fpy.li/8-49["relatividade linguística"] — ou a hipótese Sapir–Whorf — é um "princípio alegando que a estrutura de uma linguagem afeta a visão de mundo ou a cognição de seus falantes" - -A Wikipedia continua: - -* A versão _forte_ diz que a linguagem _determina_ o pensamento, e que categorias linguísticas limitam e determinam as categorias cognitivas. -* A versão _fraca_ diz que as categorias linguísticas e o uso apenas _influenciam_ o pensamento e as decisões. - -Linguistas em geral concordam que a versão forte é falsa, mas há evidência empírica apoiando a versão fraca. - -Não conheço estudos específicos com linguagens de programação, mas na minha experiência, elas tiveram grande impacto sobre a forma como eu abordo problemas. A primeira linguagem de programação que usei profissionalmente foi o Applesoft BASIC, na era dos computadores de 8 bits. Recursão não era diretamente suportada pelo BASIC - você tinha que produzir sua própria pilha de chamada (_call stack_) para obter recursão. Então eu nunca considerei usar algoritmos ou estruturas de dados recursivos. Eu sabia, em algum nível conceitual, que tais coisas existiam, mas elas não eram parte de meu arsenal de técnicas de resolução de problemas. - -Décadas mais tarde, quando aprendi Elixir, gostei de resolver problemas com recursão e usei essa técnica além da conta - até descobrir que muitas das minhas soluções seriam mais simples se que usasse funções existentes nos módulos `Enum` e `Stream` do Elixir. -Aprendi que código de aplicações em Elixir idiomático raramente contém chamadas recursivas explícitas - em vez disso, usam enums e streams que implementam recursão por trás das cortinas. - -A relatividade linguística pode explicar a ideia recorrente (e também não provada) que aprender linguagens de programação diferentes torna alguém um programador melhor, especialmente quando as linguagens em questão suportam diferentes paradigmas de programação. Praticar com Elixir me tornou mais propenso a aplicar patterns funcionais quando escrevo programas em Python ou Go. - -Agora voltando à Terra. - -O pacote `requests` provavelmente teria uma API muito diferente se Kenneth Reitz estivesse decidido (ou tivesse recebido ordens de seu chefe) a anotar todas as suas funções. Seu objetivo era escrever uma API que fosse fácil de usar, flexível e poderosa. Ele conseguiu, dada a fantástica popularidade de `requests` - em maio de 2020, ela estava em #4 nas https://fpy.li/8-50[PyPI Stats], com 2,6 milhões de downloads diários. A #1 era a `urllib3`, uma dependência de `requests`. - -Em 2017 os mantenedores de `requests` https://fpy.li/8-51[decidiram] não perder seu tempo escrevendo dicas de tipo. Um deles, Cory Benfield, escreveu um email dizendo: - -[quote] -____ -Acho que bibliotecas com APIs 'pythônicas' são as menos propensas a adotar esse sistema de tipagem, pois ele vai adicionar muito pouco valor a elas. -____ - -Naquela mensagem, Benfield incluiu esse exemplo extremo de uma tentativa de definição de tipo para o argumento de palavra-chave `files` em https://fpy.li/8-53[`requests.request()`]: - ----- -Optional[ - Union[ - Mapping[ - basestring, - Union[ - Tuple[basestring, Optional[Union[basestring, file]]], - Tuple[basestring, Optional[Union[basestring, file]], - Optional[basestring]], - Tuple[basestring, Optional[Union[basestring, file]], - Optional[basestring], Optional[Headers]] - ] - ], - Iterable[ - Tuple[ - basestring, - Union[ - Tuple[basestring, Optional[Union[basestring, file]]], - Tuple[basestring, Optional[Union[basestring, file]], - Optional[basestring]], - Tuple[basestring, Optional[Union[basestring, file]], - Optional[basestring], Optional[Headers]] - ] - ] - ] -] ----- - -E isso assume essa definição: - ----- -Headers = Union[ - Mapping[basestring, basestring], - Iterable[Tuple[basestring, basestring]], -] ----- - -Você acha que `requests` seria como é se os mantenedores insistissem em ter uma cobertura de dicas de tipo de 100%? -SQLAlchemy é outro pacote importante que não trabalha muito bem com dicas de tipo. - -O que torna essas bibliotecas fabulosas é incorporarem a natureza dinâmica do Python. - -Apesar das dicas de tipo trazerem benefícios, há também um preço a ser pago. - -Primeiro, há o significativo investimento em entender como o sistema de tipos funciona. -Esse é um custo unitário. - -Mas há também um custo recorrente, eterno. - -Nós perdemos um pouco do poder expressivo do Python se insistimos que tudo precisa estar sob a checagem de tipos. -Recursos maravilhosos como desempacotamento de argumentos — e.g., `config(**settings)`— estão além da capacidade de compreensão dos verificadores de tipo. - -Se você quiser ter uma chamada como `config(**settings)` verificada quanto ao tipo, você precisa explicitar cada argumento. -Isso me traz lembranças de programas em Turbo Pascal, que escrevi 35 anos atrás. - -Bibliotecas que usam metaprogramação são difíceis ou impossíveis de anotar. -Claro que a metaprogramação pode ser mal usada, mas isso também é algo que torna muitos pacotes do Python divertidos [.keep-together]#de usar#. - -Se dicas de tipo se tornarem obrigatórias sem exceções, por uma decisão superior em grande empresas, aposto que logo veremos pessoas usando geração de código para reduzir linhas de código padrão em programas Python - uma prática comum com linguagens menos dinâmicas. - -Para alguns projetos e contextos, dicas de tipo simplesmente não fazem sentido. -Mesmo em contextos onde elas fazer muito sentido, não fazem sentido o tempo todo. -Qualquer política razoável sobre o uso de dicas de tipo precisa conter exceções. - -Alan Kay, o recipiente do Turing Award que foi um dos pioneiros da programação orientada a objetos, certa vez disse: - -[quote] -____ -Algumas pessoas são completamente religiosas no que diz respeito a sistemas de tipo, e como um matemático eu adoro a ideia de sistemas de tipos, mas ninguém até agora inventou um que tenha alcance o suficiente..footnote:[Fonte: -https://fpy.li/8-54["A Conversation with Alan Kay"].] -____ - -Obrigado, Guido, pela tipagem opcional. -Vamos usá-la como foi pensada, e não tentar anotar tudo em conformidade estrita com um estilo de programação que se parece com Java 1.5.((("", startref="SStypehints08"))) - - -[role="soapbox-title"] -Duck Typing FTW - -Duck typing((("Soapbox sidebars", "duck typing")))((("duck typing"))) encaixa bem no meu cérebro, e duck typing estático é um bom compromisso, permitindo checagem estática de tipo sem perder muito da flexibilidade que alguns sistemas de tipagem nominal só permitem ao custo de muita complexidade - isso quando permitem. - -Antes da PEP 544, toda essa ideia de dicas de tipo me parecia completamente não-pythônica, -Fiquei muito feliz quando vi `typing.Protocol` surgir em Python. -Ele traz equilíbrio para a Força. - -[role="soapbox-title"] -Genéricos ou Específicos? - -De((("Soapbox sidebars", "generic collections")))((("generic collections", "Soapbox discussion"))) uma perspectiva de Python, o uso do termo "genérico" na tipagem é um retrocesso. -Os sentidos comuns do termo "genérico" são "aplicável integralmente a um grupo ou uma classe" ou "sem uma marca distintiva." - -Considere `list` versus `list[str]`. o primeiro é genérico: aceita qualquer objeto. O segundo é específico: só aceita `str`. - -Por outro lado, o termo faz sentido em Java. -Antes do Java 1.5, todas as coleções de Java (exceto a mágica `array`) eram "específicas": só podiam conter referência a `Object`, então era necessário converter os itens que saim de uma coleção antes que eles pudessem ser usados. Com Java 1.5, as coleções ganharam parâmetros de tipo, e se tornaram "genéricas."((("", startref="THsoag08")))((("", startref="FTHsoap08"))) - -**** diff --git a/capitulos/cap09.adoc b/capitulos/cap09.adoc deleted file mode 100644 index 06f21ccc..00000000 --- a/capitulos/cap09.adoc +++ /dev/null @@ -1,1332 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte - -[[closures_and_decorators]] -== Decoradores e Clausuras - -[quote, PEP 318—Decorators for Functions and Methods ("Decoradores para Funções e Métodos"—EN)] -____ - -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"_), de 1985. Seus (quatro) 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. - -Mas para dominar esse recurso é preciso antes entender clausuras (_closures_)—o 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". Alguns autores usam "fechamento". O termo em inglês é pronunciado "clôujure", e o nome da linguagem Clojure brinca com esse fato. Gosto da palavra clausura por uma analogia cultural. Em conventos, a clausura é um espaço fechado onde algumas freiras vivem isoladas. Suas memórias são seu único vínculo com o exterior, mas elas refletem 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 do 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 programar em um estilo funcional quando isso fizer sentido. - -O((("decorators and closures", "topics covered"))) objetivo último 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 o Python analisa a sintaxe de decoradores -* Como o Python decide se uma variável é local -* Porque clausuras existem e como elas funcionam -* Qual problema é resolvido por `nonlocal` - -Após criar essa base, poderemos então enfrentar os outros tópicos relativos aos decoradores: - -* A implementação de um decorador bem comportado -* Os poderosos decoradores na biblioteca padrão: `@cache`, `@lru_cache`, e `@singledispatch` -* A implementação de um decorador parametrizado - - -=== Novidades nesse capítulo - -O((("decorators and closures", "significant changes to"))) decorador de _caching_ `functools.cache`—introduzido no Python 3.9—é mais simples que o tradicional `functools.lru_cache`, então falo primeiro daquele. Este último é tratado na seção <>, incluindo a forma simplificada introduzida no Python 3.8. - -A seção <> foi expandida e agora inclui dicas de tipo, a forma recomendada de usar `functools.singledispatch` desde o Python 3.7. - -A seção <> agora inclui um exemplo baseado em classes, o <>. - -Transferi o pass:[#rethinking_design_patterns] para o final da <>, para melhorar a fluidez do livro. -E a seção <> também aparece agora naquele capítulo, juntamente com outras variantes do padrão de projeto Estratégia usando invocáveis. - -Começamos com uma introdução muito suave aos decoradores, e dali seguiremos para o restante dos tópicos listados no início do capítulo. - - -=== 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 ou a devolve ou a substitui por outra função ou por um 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. Decoradores de classe são tratadas no <>.] - -Em outras palavras, supondo a existência de um decorador chamado `decorate`, esse código: - -[source, python3] ----- -@decorate -def target(): - print('running target()') ----- - -tem o mesmo efeito de: - -[source, python3] ----- -def target(): - print('running target()') - -target = decorate(target) ----- - -O resultado final é o mesmo: após a execução de qualquer dos dois trechos, 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, pycon] ----- ->>> 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. - -[role="pagebreak-before less_space"] -Três fatos essenciais nos dão um bom resumo dos decoradores: - -* Um decorador é uma função ou outro invocável. -* Um decorador pode substituir a função decorada por outra, diferente. -* Decoradores são executados imediatamente quando um módulo é carregado. - -Vamos agora nos concentrar nesse terceiro ponto.((("", startref="DACbasic09"))) - - -=== Quando o 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 _tempo de importação_ (isto é, quando um módulo é carregado pelo Python). Observe _registration.py_ no <>. - -[[registration_ex]] -.O módulo registration.py -==== -[source, py] ----- -include::code/09-closure-deco/registration.py[tags=REGISTRATION] ----- -==== -<1> `registry` vai manter 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 fins de demonstração. -<4> Insere `func` em `registry`. -<5> Devolve `func`: precisamos 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` mostra `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_ se parece com isso: - ----- -$ 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`. Essa 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, pycon] ----- ->>> import registration -running register() -running register() ----- - -Nesse momento, se você inspecionar `registry`, verá isso: - -[source, pycon] ----- ->>> 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 que pythonistas chamam de _tempo de importação_ e _tempo de execução_. - -[[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 duas razões: - -* A função do decorador é definida no mesmo módulo das funções decoradas. Em geral, um decorador real é definido em um módulo e aplicado a funções de outros módulos. -* 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, aquela técnica não é inútil. Decoradores parecidos são usados por muitas 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 seção <> (do <>). - -A maioria dos decoradores modificam a função decorada. -Eles normalmente fazem isso definindo e devolvendo uma função interna para substituir a função decorada. -E 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, pycon] ----- ->>> 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, pycon] ----- ->>> b = 6 ->>> f1(3) -3 -6 ----- - -Agora vamos ver um exemplo que pode ser surpreendente. - -Dê uma olhada na 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, pycon] ----- ->>> 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 o 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, pensava que o `6` deveria ser exibido, pois há uma variável global `b`, e a atribuição para a `b` local ocorre após `print(b)`. - -Mas o fato é que, quando o 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, e tentará obter `b` no escopo local. Mais tarde, quando a chamada `f2(3)` é realizada, 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: o Python não exige que você declare variáveis, mas assume que uma variável atribuída no corpo de uma função é local. Isso é muito melhor que o comportamento do 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 sem nem saber. - -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, pycon] ----- ->>> 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 de 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 de onde variáveis podem vir, chamado _nonlocal_, e ele é fundamental para clausuras; vamos tratar disso em breve. - -Após ver mais de perto como o escopo de variáveis funciona no Python, podemos enfrentar as clausuras na próxima seção, <>. Se você 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` module oferece uma forma fácil de descompilar o bytecode de funções do Python. 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, py] ----- -include::code/09-closure-deco/global_x_local.rst[tags=F1_DIS] ----- -==== -<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, py] ----- -include::code/09-closure-deco/global_x_local.rst[tags=F2_DIS] ----- -==== -<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 _stack_, então as operações `LOAD` e `POP` se referem ao _stack_. A descrição mais detalhada dos opcodes do Python está além da finalidade desse livro, mas eles estão documentados junto com o módulo, em https://docs.python.org/pt-br/3/library/dis.html["dis—Disassembler de bytecode do Python"]. - -**** - -[[closures_sec]] -=== Clausuras - -Na((("decorators and closures", "closure basics", id="DACclos09")))((("anonymous functions"))) blogosfera, as clausuras são algumas vezes confundidas com funções anônimas. -Muita gente confunde os dois conceitos por causa da história paralela dos dois recursos: -definir funções dentro de outras funções não é tão comum ou conveniente, até existirem funções anônimas. -E clausuras só importam 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 referenciadas no corpo de `f` que não são nem variáveis globais nem variáveis locais de `f`. Tais variáveis devem vir do escopo local de uma função externa que englobe `f`. - -Não interessa aqui 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 de fechamento de uma commodity através 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é ali. - -Começando do zero, `avg` poderia ser usada assim: - -[source, pycon] ----- ->>> avg(10) -10.0 ->>> avg(11) -10.5 ->>> avg(12) -11.0 ----- - -Da onde vem `avg`, e onde ela mantém 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 contínua -==== -[source, python3] ----- -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, pycon] ----- ->>> 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 clacular uma média contínua -==== -[source, python3] ----- -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, pycon] ----- ->>> 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` mantém o histórico: no atributo de instância `self.series`. Mas onde a função `avg` no segundo exemplo 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 há muito deixou de existir. - -Dentro de `averager`, `series` é uma((("free variables")))((("variables", "free"))) _variável livre_. Esse é um termo técnico para designar uma variável que não está vinculada no escopo local. Veja a <>. - -[[closure_fig]] -.A clausura para `averager` estende o escopo daquela função para incluir a vinculação da variável livre `series`. -image::images/flpy_0901.png[Diagrama de uma clausura] - -Inspecionar o objeto `averager` devolvido mostra como o Python mantém os nomes de variáveis locais e 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, pycon] ----- ->>> avg.__code__.co_varnames -('new_value', 'total') ->>> avg.__code__.co_freevars -('series',) ----- -==== - -O valor de `series` é mantido no atributo `+__closure__+` da função devolvida, `avg`. Cada item em `+avg.__closure__+` corresponde a um nome em `+__code__+`. Esses itens são `cells`, e tem um atributo chamado `cell_contents`, onde o valor real pode ser encontrado. O <> mostra esses atributos. - -[[ex_average_demo3]] -.Continuando do <> -==== -[source, pycon] ----- ->>> avg.__code__.co_freevars -('series',) ->>> 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 estiver mais disponível. - -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 declaração nonlocal - -Nossa((("decorators and closures", "nonlocal declarations", id="DACnonlocal09")))((("nonlocal keyword", id="nonlocal09")))((("keywords", "nonlocal keyword", id="Knon09"))) implementação anterior de `make_averager` não era eficiente. 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 número de itens até aquele momento, e calcularia a média com esses dois números. - -O <> é uma implementação errada, apenas para ilustrar um ponto. Você consegue ver onde o código quebra? - -[[ex_average_broken]] -.Um função de ordem superior incorreta para calcular um média contínua sem manter todo o histórico -==== -[source, python3] ----- -def make_averager(): - count = 0 - total = 0 - - def averager(new_value): - count += 1 - total += new_value - return total / count - - return averager ----- -==== - -Se você testar o <>, eis o resultado: - -[source, pycon] ----- ->>> 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 efetivamente 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ê tentar revinculá-las, como em `count = count + 1`, estará criando implicitamente uma variável local `count`. Ela não será mais uma variável livre, e assim não será armazenada na clausura. - -A palavra reservada `nonlocal` foi introduzida no Python 3 para contornar esse problema. Ela permite declarar uma variável como variável livre, mesmo quando ela for atribuída dentro da função. Se um novo valor é atribuído a uma variável `nonlocal`, o vínculo armazenado na clausura é modificado. Uma implemetação correta da nossa última versão de `make_averager` se pareceria com o <>. - -[[ex_average_fixed]] -.Calcula uma média contínua sem manter todo o histórico (corrigida com o uso de `nonlocal`) -==== -[source, python3] ----- -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 da consulta de variáveis - -Quando((("variables", "lookup logic"))) uma função é definida, o compilador de bytecode do 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`, `x` vem de e é atribuída à variável global `x` do módulo.footnote:[O Python não tem um escopo global de programa, apenas escopos globais de módulos.] -* Se há uma declaração `nonlocal x`, `x` vem de e atribuída à 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 _nonlocal_). -** 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 do Python, podemos agora de fato 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 simples que mostra o tempo de execução de funções -==== -[source, python3] ----- -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 para `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, python3] ----- -include::code/09-closure-deco/clock/clockdeco_demo.py[] ----- -==== - -O resultado da execução do <> é o seguinte: - -[source, bash] ----- -$ 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 que esse código: - -[source, python3] ----- -@clock -def factorial(n): - return 1 if n < 2 else n*factorial(n-1) ----- - -na verdade faz isso: - -[source, python3] ----- -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, pycon] ----- ->>> import clockdeco_demo ->>> clockdeco_demo.factorial.__name__ -'clocked' ->>> ----- - -Então `factorial` agora mantém uma referência para a função `clocked`. -Daqui por diante, cada vez que `factorial(n)` for chamada, `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 com: "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 do Python guardam pouca semelhança com o decorador clássico descrito no _Padrões de Projetos_ original. O <> fala um pouco mais sobre esse assunto. -==== - -O decorador `clock` implementado no <> tem alguns defeitos: ele não suporta 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`. -E nessa nova versão os argumentos nomeados também são tratados corretamente. - -[[ex_clockdeco2]] -._clockdeco.py_: um decorador `clock` melhora -==== -[source, python3] ----- -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 impressionante oferecido por `functools`: `cache`.((("", startref="DACdecimp09"))) - - -=== Decoradores na biblioteca padrão - -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 seção <> e os outros na seção <>. - -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 funciona salvando os resultados de invocações anteriores de uma função dispendiosa, evitando repetir o processamento para argumentos previamente utilizados. - -[TIP] -==== -O `functools.cache` foi introduzido no Python 3.9. -Se você precisar rodar esses exemplo no Python 3.8, substitua `@cache` por `@lru_cache`. -Em versões anteriores é preciso invocar o decorador, escrevendo `@lru_cache()`, como explicado na seção <>. -==== - -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]] -.O modo recursivo e extremamente dispendioso de calcular o _enésimo_ número na série de Fibonacci -==== -[source, python3] ----- -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, python3] ----- -include::code/09-closure-deco/fibo_demo_cache.py[] ----- -==== -<1> Essa linha funciona com Python 3.9 ou posterior. Veja a seção <> para uma alternativa que suporta versões anteriores do Python. -<2> Esse é um exemplo de decoradores empilhados: `@cache` é aplicado à função devolvida por `@clock`. - -[[stacked_decorators_tip]] -.Decoradore empilhados -[TIP] -==== -Para((("stacked decorators"))) entender os decoradores empilhados, lembre-se que a `@` é açúcar sintático para indicar a aplicação da função decoradora à função abaixo dela. -Se houver mais de um decorador, eles se comportam como chamadas a funções aninhadas. Isso: - -[source, python] ----- -@alpha -@beta -def my_fn(): - ... ----- - -é o mesmo que isso: - -[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, para um total de 2.692.537 chamadas. - -Todos os argumentos recebidos pela função decorada devem ser _hashable_, pois o `lru_cache` subjacente usa um `dict` para armazenar os resultados, e as chaves são criadas a partir dos argumentos posicionais e nomeados usados nas chamados. - -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 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 o Python 3.8 e outras versões anteriores. - -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 conservador de 128—significando que o cache pode manter no máximo 128 registros simultâneos. - -LRU((("Least Recently Used (LRU)"))) é a sigla de _Least Recently Used_ (literalmente "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. -Abaixo vemos o modo mais simples em uso: - -[source, python3] ----- -@lru_cache -def costly_function(a, b): - ... ----- - -A outra forma—disponível desde o Python 3.2—é invocá-lo como uma função, -com `()`: - -[source, python3] ----- -@lru_cache() -def costly_function(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, python3] ----- -@lru_cache(maxsize=2**20, typed=True) -def costly_function(a, b): - ... ----- - -Vamos agora examinar outro decorador poderoso: `functools.singledispatch`. - -[[generic_functions]] -==== 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, python3] ----- -import html - -def htmlize(obj): - content = html.escape(repr(obj)) - return f'
{content}
' ----- - -Isso funcionará para qualquer tipo do Python, 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 `

` 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, pycon]
-----
-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 equivalemte aproximada.
-
-===== Despacho único de funções
-
-Como não temos no Python a sobrecarga de métodos ao estilo do 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 despachante `htmlize` de tornaria grande demais, e o acoplamento entre ela e as funções especializadas seria excessivamente sólido.
-
-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 um 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.
-É isso que signifca o termo _despacho único_. Se mais argumentos fossem usados para selecionar a função específica, teríamos um _despacho múltiplo_.
-O <> mostra como funciona.
-
-
-[WARNING]
-====
-`functools.singledispatch` existe desde o python 3.4, mas só passou a suportar dicas de tipo no Python 3.7.
-As últimas duas funções no <> ilustram a sintaxe que funciona em todas as versões do Python desde a 3.4.
-====
-
-
-[[singledispatch_ex]]
-.`@singledispatch` cria uma `@htmlize.register` personalizada, para empacotar várias funções em uma função genérica
-====
-[source, py]
-----
-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 <>, 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, o 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á.]
-
-////
-++++
-
-
-
@singledispatch creates a custom @htmlize.register to bundle several functions into a generic function
- -
from functools import singledispatch
-from collections import abc
-import fractions
-import decimal
-import html
-import numbers
-
-@singledispatch  1
-def htmlize(obj: object) -> str:
-    content = html.escape(repr(obj))
-    return f'<pre>{content}</pre>'
-
-@htmlize.register  2
-def _(text: str) -> str:  3
-    content = html.escape(text).replace('\n', '<br/>\n')
-    return f'<p>{content}</p>'
-
-@htmlize.register  4
-def _(seq: abc.Sequence) -> str:
-    inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
-    return '<ul>\n<li>' + inner + '</li>\n</ul>'
-
-@htmlize.register  5
-def _(n: numbers.Integral) -> str:
-    return f'<pre>{n} (0x{n:x})</pre>'
-
-@htmlize.register  6
-def _(n: bool) -> str:
-    return f'<pre>{n}</pre>'
-
-@htmlize.register(fractions.Fraction)  7
-def _(x) -> str:
-    frac = fractions.Fraction(x)
-    return f'<pre>{frac.numerator}/{frac.denominator}</pre>'
-
-@htmlize.register(decimal.Decimal)  8
-@htmlize.register(float)
-def _(x) -> str:
-    frac = fractions.Fraction(x).limit_denominator()
-    return f'<pre>{x} ({frac.numerator}/{frac.denominator})</pre>'
-
-
1
-

@singledispatch marks the base function that handles the object type.

-
2
-

Each specialized function is decorated with @«base».register.

-
3
-

The type of the first argument given at runtime determines when this particular function definition will be used. The name of the specialized functions is irrelevant; _ is a good choice to make this -clear.Unfortunately, Mypy 0.770 complains when it sees multiple functions with the same name.

-
4
-

For each additional type to get special treatment, -register a new function with a matching type hint in the first parameter.

-
5
-

The numbers ABCs are useful for use with singledispatch.Despite the warning in #numeric_tower_warning, the number ABCs are not deprecated and you find them in Python 3 code.

-
6
-

bool is a subtype-of numbers.Integral, but the singledispatch logic seeks the implementation with the most specific matching type, regardless of the order they appear in the code.

-
7
-

If you don’t want to, or cannot, add type hints to the decorated function, you can pass a type to the @«base».register decorator. This syntax works in Python 3.4 or later.

-
8
-

The @«base».register decorator returns the undecorated function, so it’s possible to stack them to register two or more types on the same implementation.Maybe one day you'll also be able to express this with single unparameterized @htmlize.register and type hint using Union, but when I tried, Python raised a TypeError with a message saying that Union is not a class. So, although PEP 484 syntax is supported by @singledispatch, the semantics are not there yet.

-
-++++ -//// - - -Sempre que possível, registre as funções especializadas para tratar ABCs (classes abstratas), tais 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 do 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. E é possível também escrever funções personalizadas 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 muitos outros recursos que não me cabe descrever aqui. Uma boa referência é a https://fpy.li/pep443[PEP 443--Single-dispatch generic functions] (EN) mas ela não menciona o uso de dicas de tipo, acrescentado posteriormente. A documentação do módulo `functools` foi aperfeiçoada e oferece um tratamento mais atualizado, com vários exemplos na seção referente ao https://docs.python.org/pt-br/3/library/functools.html#functools.singledispatch[`singledispatch`] (EN). - -[NOTE] -==== -O `@singledispatch` não foi criado para trazer para o Python a sobrecarga de métodos no estilo do Java. Uma classe única com muitas variações sobrecarregadas de um método é melhor que uma única função com uma longa sequênca de blocos `if/elif/elif/elif`. Mas as duas soluções são incorretas, pois concentram responsabilidade excessiva 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 alguns decoradores recebendo argumentos, por exemplo `@lru_cache()` e o -`htmlize.register(float)` criado por `@singledispatch` no <>. -A próxima seção mostra como criar decoradores que aceitam 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, o 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. Confuso? Com certeza. 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, py] ----- -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, py] ----- -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 `not active` e `func in registry`, remove a função. -<6> Como `decorate` é um decorador, ele deve 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 chamado como uma função—`@register()`—isto é, para 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, pycon] ----- ->>> 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, pycon] ----- ->>> 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 - -Nessa((("clock decorators", "parameterized", id="CDparam09"))) seção vamos revisitar o decorador `clock`, acrescentando um recurso: os usuários podem passar uma string de formatação, para controlar a saída do relatório sobre função cronometrada. Veja o <>. - -[NOTE] -==== -Para simplificar, o <> está baseado na implementação inicial de `clock` no <>, e não na versão aperfeiçoada do <> que usa `@functools.wraps`, acrescentando assim mais uma camada de função. -==== - -[[clockdeco_param_ex]] -.Módulo clockdeco_param.py: o decorador `clock` parametrizado -==== -[source, py] ----- -include::code/09-closure-deco/clock/clockdeco_param.py[tags=CLOCKDECO_PARAM] ----- -==== -<1> `clock` é a nossa fábrica de decoradores parametrizados. -<2> `decorate` é o verdadeiro decorador. -<3> `clocked` envolve a função decorada. -<4> `_result` é o resultado efetivo da função decorada. -<5> `_args` mantém os verdadeiros argumentos de `clocked`, enquanto `args` é a `str` usada para exibição. -<6> `result` é a `str` que representa `_result`, para exibição. -<7> 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 verificação desencorajam o uso dos recursos dinâmicos do Python que primeiro me atraíram (e a incontáveis outros programadores) na linguagem. Para fazer o _linter_ feliz, eu poderia escrever cada variável local duas vezes na chamada: pass:[fmt.format(elapsed=​elapsed, name=name, args=args, result=result)]. Prefiro não fazer isso. Se você usa ferramentas estáticas de verificação, é importante saber quando ignorá-las.] -<8> `clocked` vai substituir a função decorada, então ela deve devolver o mesmo que aquela função devolve. -<9> `decorate` devolve `clocked`. -<10> `clock` devolve `decorate`. -<11> 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, bash] ----- -$ 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, vamos dar uma olhada em dois outros módulos que usam o `clockdeco_param`, o <<#ex_clockdecoparam_demo1>> e o <<#ex_clockdecoparam_demo2>>, e nas saídas que eles geram. - -[[ex_clockdecoparam_demo1]] -.clockdeco_param_demo1.py -==== -[source, python3] ----- -include::code/09-closure-deco/clock/clockdeco_param_demo1.py[] ----- -==== - -Saída do <>: - -[source, bash] ----- -$ python3 clockdeco_param_demo1.py -snooze: 0.12414693832397461s -snooze: 0.1241159439086914s -snooze: 0.12412118911743164s ----- - -[[ex_clockdecoparam_demo2]] -.clockdeco_param_demo2.py -==== -[source, python3] ----- -include::code/09-closure-deco/clock/clockdeco_param_demo2.py[] ----- -==== - -Saída do <>: - -[source, bash] ----- -$ 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 seria melhor programar decoradores como classes implementando `+__call__+`, e não como funções (caso dos exemplos nesse 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 técnicas robustas de criação de decoradores, veja as referências na <>, especialmente o blog de Graham Dumpleton e o módulo `wrapt`, . -==== - -A próxima seção traz 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, py] ----- -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. A nomeei com um `c` minúsculo, para deixar claro que essa implementação é uma substituta direta para aquela 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 <>.((("", startref="DACparam09")))((("", startref="paramdec09"))) - - -=== Resumo do capítulo - -Percorremos((("decorators and closures", "overview of"))) um terreno acidentado nesse capítulo. Tentei tornar a jornada tão suave quanto possível, mas entramos definitivamente nos domínios da meta-programação. - -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, tem aplicações reais nas 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 _tempo de importação_ e _tempo 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]] -=== Leitura complementar - -O((("decorators and closures", "further reading on"))) item #26 do livro https://fpy.li/effectpy[_Effective Python_, 2nd ed.] (EN) (Addison-Wesley), de Brett Slatkin, trata das melhores práticas para decoradores de função, e recomenda sempre usar `functools.wraps`—que vimos no <>.footnote:[Como 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] (EN) 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 profundos sobre o assunto também estão muito bem demonstrados no módulo https://fpy.li/9-7[`wrapt`], que Dumpleton 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 <> na <> é sobre descritores. - -https://fpy.li/9-8["Metaprogramming" (_Metaprogramação_)] (EN), 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" (_Receita 9.6. Definindo um Decorador Que Recebe um Argumento Opcional_) desse livro de receitas. - -Michele Simionato criou um pacote com objetivo de "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 a documentação. Ele está disponível no PyPI, em https://fpy.li/9-9[decorator [.keep-together]#package# (_pacote decorador_)] (EN). - -Criada quando os decoradores ainda eram um recurso novo no Python, a página wiki https://fpy.li/9-10[Python Decorator Library] (EN) 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" (_Clausuras em Python_)] (EN) é um post de blog 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_)] (EN) descreve a introdução da declaração pass:[nonlocal], para permitir a re-vinculação de nomes que não são nem locais nem globais. 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 o Python. - -Em um nível mais teórico, a https://fpy.li/9-13[PEP 227--Statically Nested Scopes (_Escopos Estaticamente Aninhados_)] (EN) 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] (EN) traz a justificativa e uma descrição detalhada do mecanismo de funções genéricas de despacho único. Um antigo (março de 2005) post de blog de Guido van Rossum, https://fpy.li/9-15["Five-Minute Multimethods in Python" (_Multi-métodos em Python em Cinco Minutos_)] (EN), mostra os passos para uma implementação de funcões genéricas (também chamadas multi-métodos) usando decoradores. O código de multi-métodos de Guido é interessante, mas é um exemplo didático. Para ver uma implementação moderna e pronta para ser usada em produção de funções genéricas de despacho múltiplo, veja a https://fpy.li/9-16[Reg] de Martijn Faassen–autor da https://fpy.li/9-17[Morepath], uma framework web guiada por modelos e compatível com REST. - -[[closures_soapbox]] -[role="pagebreak-before less_space"] -.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. -A pergunta é: como avaliar as variáveis livres? -A resposta inicial e mais simples é "escopo dinâmico". -Isso significa que variáveis livres são avaliadas olhando para dentro do ambiente onde a funcão é invocada. - -Se o Python tivesse escopo dinâmico e não tivesse clausuras, poderíamos improvisar `avg`—similar ao <>—assim: - -[source, pycon] ----- ->>> ### this is not a real Python console session! ### ->>> 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 se tratar duas médias continuas independentes ao mesmo tempo. - -Funções deveriam ser 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 ser capaz de configurar 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, pass:["Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I" (Funções Recursivas de Expressões Simbólicas e Sua Computação via Máquina)]. O artigo de McCarthy é uma obra prima no nível da Nona Sinfonia de Beethoven. Paul Graham o traduziu para o resto de nós, da matemática para o inglês e o código executável. - -O comentário de Paul Graham explica como o escopo dinâmico é complexo. Citando o "_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. -____ - -Hoje em dia 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` do Python não ofereceu 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 tem uma memória muito boa. Desde então, `lambda` é embaraçoso apenas por sua sintaxe limitada.((("", startref="Scynamic09")))((("", startref="SSdynamic09"))) - -[role="soapbox-title"] -O decoradores do Python e o padrão de projeto Decorador - -Os decoradores de função((("Soapbox sidebars", "Python decorators and decorator design pattern"))) do 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 do 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 funcã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). Pegando emprestado da citação anterior, podemos adaptar a última frase para dizer que "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. Apesar disso ser possível em situações específicas, em geral o padrão decorador é melhor implementado com classes representando o decorador e os componentes que ela vai envolver.((("", startref="DACsoap09"))) -**** diff --git a/capitulos/cap10.adoc b/capitulos/cap10.adoc deleted file mode 100644 index e428467d..00000000 --- a/capitulos/cap10.adoc +++ /dev/null @@ -1,437 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte - -[[rethinking_design_patterns]] -== Padrões de projetos com funções de primeira classe - -[quote, Ralph Johnson, co-autor do clássico "Padrões de Projetos"] -____ -Conformidade a padrões não é uma 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://pt.wikipedia.org/wiki/Padr%C3%A3o_de_projeto_de_software[_padrão de projeto_] é uma receita genérica para solucionar um problema de design frequente. -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_). -O livro é um catálogo de 23 padrões, cada um deles composto por arranjos de classes e exemplificados com código em C++, mas assumidos como ú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 <> vai mostrar que não faz sentido emular a receita do padrão https://fpy.li/10-2[Iterator (_Iterador_)] (EN) no 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 clássica. - -Os autores de _Padrões de Projetos_ reconhecem, na introdução, 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 do Smalltalk e do C++—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_)] (EN), Peter Norvig afirma que 16 dos 23 padrões no _Padrões de Projeto_ original se tornam "invisíveis ou mais simples" em uma linguagem dinâmica (slide 9). -Ele está falando das linguagens Lisp e Dylan, mas muitos dos recursos dinâmicos relevantes também estão presentes no Python. Em especial, no contexto de linguagens com funções de primeira classe, Norvig sugere repensar os padrões clássicos conhecidos como Estratégia (_Strategy_), Comando (_Command_), Método Template (_Template Method_) e Visitante (_Visitor_). - -O objetivo desse capítulo é mostrar como—em alguns casos—as funções podem realizar o mesmo trabalho das classes, com um código mais legível e mais conciso. 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 nesse 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 seção <>, e também usar dicas de tipo nos exemplos. -A maior parte das dicas de tipo usadas nesse capítulo não são complicadas, 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 clássico, pode pular direto para <>, onde refatoramos o código usando funções, reduzindo significativamente o número de 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 o processamento de descontos em um pedido, implementado com o padrão de projeto Estratégia. -image::images/flpy_1001.png[Cálculos de desconto de um pedido como estratégias] - -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 pelo menos 10 itens diferentes recebem 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. No exemplo de ecommerce, o contexto é uma classe `Order`, configurada para aplicar um desconto promocional de acordo com um de vários algoritmos. - -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 o 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, py] ----- -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]] -.Amostra de uso da classe `Order` com a aplicação de diferentes promoções -==== -[source, pycon] ----- -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`. - -//// -PROD: I don't know why the console session in <> is not rendering with syntax coloring. -//// - -O <> funciona perfeitamente bem, mas a mesma funcionalidade pode ser implementada com menos linhas de código em Python, se usarmos funções como objetos. Veremos como fazer isso na próxima seção.((("", 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 único método, `discount`. -Além disso, as instâncias de estratégia não tem nenhum 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 <>, -substituindo as estratégias concretas por funções simples e removendo a classe abstrata `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 verificar 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://github.com/python/mypy/issues/12629[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 de descontos implementadas como funções -==== -[source, py] ----- -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, chama o invocável `self.promotion`, passando `self` como um 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 seção <> vai explicar o mecanismo que vincula automaticamente métodos a instâncias. Mas isso não se aplica a `promotion`, pois ela 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, pycon] ----- -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`, basta passar a função de promoção como argumento. -<3> Uma função de promoção diferente é usada aqui e no teste seguinte. - -//// -PROD: Again, I don't know why the console session in <> is not rendering with syntax coloring. -//// - -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 disponíveis para serem usadas. - -É 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_.] -Uma definição do padrão _Peso Mosca_ em outra parte daquele texto afirma: -"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`. -Então, para contornar uma desvantagem do padrão Estratégia—seu custo durante a execução—os autores recomendam a aplicação de mais outro padrão. -Enquanto isso, o número de linhas e custo de manutenção de seu código vão se acumulando. - -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 tem 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 simples 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: uma 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 funcão `best_promo` aplica todos os descontos e devolve o maior -==== -[source, py] ----- -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> Encerrando a compra com um carrinho simples, `best_promo` deu à cliente fiel `ann` o desconto da `fidelity_promo`. - -A implementação de `best_promo` é muito simples. Veja o <>. - -[[ex_strategy_best]] -.`best_promo` encontra o desconto máximo iterando sobre uma lista de funções -==== -[source, py] ----- -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. Depois que você se acostuma à ideia de funções como objetos de primeira classe, o próximo passo é notar que construir estruturas de dados contendo funções muitas vezes faz todo sentido. - -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 do Python: - -`globals()`:: Devolve um dicionário representando a tabela de símbolos globais atual. Isso é sempre o dicionário do módulo atual (dentro de uma função ou método, esse é o módulo onde a função ou método foram definidos, não o módulo de onde são chamados). - -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, py] ----- -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 entender a natureza dinâmica do Python. Se seguirmos todos os conselhos dessas ferramentas, logo estaremos escrevendo programas austeros e prolixos similares aos do Java, mas com a sintaxe do 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 `inspect`, que fornece funções de introspecção de alto nível. - - -[[ex_strategy_best3]] -.A lista `promos` é construída a partir da introspecção de um novo módulo, `promotions` -==== -[source, py] ----- -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 do módulo. - -O <> funciona independente dos nomes dados às funções; tudo 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]] -=== Padrão Estratégia aperfeiçoado com um decorador - -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 silenciosamente ignorar a nova estratégia, introduzindo no sistema um bug sutil. O <> resolve esse problema com a técnica vista na seção <>. - -[[ex_strategy_best31]] -.A lista `promos` é preenchida pelo decorador `promotion` -==== -[source, py] ----- -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 destinatário (_receiver_) diferente: o objeto que implementa a ação. Para `PasteCommand`, o destinatário é Document. Para `OpenCommand`, o destinatário á a aplicação. -image::images/flpy_1002.png[Aplicação do padrão Comando a um editor de texto] - -O objetivo de Comando é desacoplar um objeto que invoca uma operação (o _invoker_ ou remetente) do objeto fornecedor que implementa aquela operação (o _receiver_ ou destinatário). No exemplo em _Padrões de Projetos_, cada remetente é um item de menu em uma aplicação gráfica, e os destinatários 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 destinatário para executar a operação desejada. Assim, o remetente não precisa conhecer a interface do destinatário, e destinatários diferentes podem ser adaptados com diferentes subclasses de `Command`. O remetente é 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 remetente uma instância de `Command`, podemos simplesmente dar a ele uma função. Em vez de chamar `command.execute()`, o remetente pode apenas chamar `command()`. O `MacroCommand` pode ser programado como uma classe que implementa `+__call__+`. Instâncias de `MacroCommand` seriam invocáveis, cada uma mantendo uma lista de funções para invocação futura, como implementado no <>. - - -[[ex_macro_command]] -.Cada instância de `MacroCommand` tem uma lista interna de comandos -==== -[source, python3] ----- -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, o 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 manter o estado interno de 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 as instâncias de uma classe participante que implementava uma interface de método único por invocáveis. -Afinal, todo invocável do 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 tem implementações qualitativamente mais simples em Lisp ou Dylan que em C++, pelo menos para alguns usos de cada padrão" (slide 9 da apresentação de Norvig, https://fpy.li/10-4["Design Patterns in Dynamic Languages" presentation] (_Padrões de Projetos em Linguagens Dinâmicas_)). O Python compartilha alguns dos recursos dinâmicos das linguagens Lisp e Dylan, especialmente funções de primeira classe, nosso foco nesse 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 por Gamma, Helm, Johnson, e Vlissides 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]] -=== Leitura complementar - -A "Receita 8.21. Implementando o Padrão Visitante" (_Receipt 8.21. Implementing the Visitor Pattern_) no((("functions, design patterns with first-class", "further reading on"))) pass:[Python Cookbook, 3ª ed.] (EN), 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. - -_Learning Python Design Patterns_ ("Aprendendo os Padrões de Projeto do Python"), de Gennadiy Zlobin (Packt), é o único livro inteiramente dedicado a padrões em Python que encontrei. Mas o trabalho de Zlobin é muito breve (100 páginas) e trata de apenas 8 dos 23 padrões de projeto originais. - -_Expert Python Programming_ ("Programação Avançada em Python"), de Tarek Ziadé (Packt), é um dos melhores livros de Python de nível intermediário, e seu capítulo final, "Useful Design Patterns" (_Padrões de Projetos Úteis_), apresenta vários dos padrões clássicos de uma perspectiva pythônica. - -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] (EN) e um https://fpy.li/10-6[conjunto de slides em seu site pessoal] (EN). Ao longo dos anos, encontrei diferentes jogos de slides e vídeos de diferentes tamanhos, então vale a pena tentar uma busca mais ampla com o nome dele e as palavras "Python Design Patterns". Um editor me contou que Martelli está trabalhando em um livro sobre esse assunto. Eu certamente comprarei meu exemplar assim que estiver disponível. - -Há muitos livros sobre padrões de projetos no contexto do Java mas, dentre todos eles, meu preferido é _Head First Design Patterns_ ("Mergulhando de Cabeça nos Padrões de Projetos"), 2ª ed., de Eric Freeman e Elisabeth Robson (O'Reilly). Esse volume explica 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. Ele é centrado no Java, mas a segunda edição foi atualizada para refletir a introdução de funções de primeira classe naquela linguagem, tornando alguns dos exemplos mais próximos de código que escreveríamos em Python. - -Para um olhar moderno sobre padrões, do ponto de vista de uma linguagem dinâmica com _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 o Python e o Ruby estão mais próximos entre si que do Java ou do C++. - - -Em https://fpy.li/norvigdp["Design Patterns in Dynamic Languages" (_Padrões de Projetos em Linguagens Dinâmicas_)] (slides), 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 citados, como "Programe para uma interface, não para uma implementação" e "Prefira a composição de objetos à herança de classe", vem ambos daquela introdução. - -A aplicação de padrões a 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 aplicável à engenharia de software. - -.Ponto de vista -**** -O 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 (no slide 10 de https://fpy.li/norvigdp["Design Patterns in Dynamic Languages" (_Padrões de Projetos em Linguagens Dinâmicas_)]). No <>, vimos que o Python também tem funções genéricas (na seção <>), 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 C++—no livro, alguns deles tem 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). - -A bibliografia do Python sobre padrões de projetos é muito pequena, se comparada à existente para Java, C++ ou Ruby. Na seção <>, mencionei _Learning Python Design Patterns_ ("Aprendendo Padrões de Projeto do Python"), de Gennadiy Zlobin, que foi publicado apenas em novembro de 2013. Para se ter uma ideia, _Design Patterns in Ruby_ ("Padrões de Projetos em Ruby"), de Russ Olsen, foi publicado em 2007 e tem 384 páginas—284 a mais que a obra de Zlobin. - -Agora que o Python está se tornando cada vez mais popular no ambiente acadêmico, podemos esperar que novos livros sobre padrões de projetos sejam escritos no contexto de nossa 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, nosso entendimento sobre a forma de aplicação dos padrões de projetos clássicos deve também evoluir. - -[role="soapbox-title"] -O __call__ selvagem - -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 tem 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 dele tem alguma utilidade, mas eis um fato engraçado: - -[source, pycon] ----- ->>> 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' ->>> turtle.__call__.__call__.__call__.__call__.__call__.__call__.__call__() -'eggs' ----- - -https://fpy.li/10-8[Turtles all the way down ]footnote:[NT: Literalmente "_Tartarugas até embaixo_" ou algo como "_Tartarugas até onde a vista alcança_" ou "_Uma torre infinita de tartarugas_". Curiosamente, um livro com esse nome foi publicado no Brasil com o título "Mil vezes adeus", na tradição brasileira (especialmente para filmes) de traduzir nomes de obras de forma preguiçosa ou aleatória.] - - -**** - diff --git a/capitulos/cap11.adoc b/capitulos/cap11.adoc deleted file mode 100644 index 31729156..00000000 --- a/capitulos/cap11.adoc +++ /dev/null @@ -1,1015 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte - -[[pythonic_objects]] -== Um objeto pythônico - - -[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 uma programadora 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 do 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 reais. -As classes na sua aplicação provavelmente não precisam nem devem implementar tantos métodos especiais quanto os exemplos nesse capítulo. -Mas se você estiver escrevendo uma biblioteca ou uma 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 <> 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 _sets_ 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 será interrompida para discutirmos dois tópicos conceituais: - -* Como e quando usar os decoradores `@classmethod` e `@staticmethod` -* Atributos privados e protegidos no Python: uso, convenções e limitações - -=== Novidades nesse 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 discutido apenas no final do livro. - -A seção <> foi atualizada 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 as ideias centrais apareceram no 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 tem pelo menos uma forma padrão de se obter uma representação de qualquer objeto como uma string. O 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 do Python ou um depurador mostram um objeto. -`str()`:: Devolve((("str() function")))((("functions", "str() function"))) uma string representando o objeto como o usuário quer vê-lo. É 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 <>. - -Existem((("__bytes__")))((("__format__"))) dois métodos especiais adicionais para suportar 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__(format_spec)` para obter versões de exibição de objetos usando 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 do 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 <>. -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, py] ----- -include::code/11-pythonic-obj/vector2d_v0.py[tags=VECTOR2D_V0_DEMO] ----- -==== -[role="pagebreak-before less_space"] -<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` emula 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 aqui apenas para mostrar uma característica de `repr`; para clonar uma instância, a função `copy.copy` é mais segura e rápida.] -<5> `Vector2d` suporta a comparação com `==`; isso é ú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`. - -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 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, py] ----- -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 inadequados. -<3> `+__iter__+` torna um `Vector2d` iterável; é isso que faz o desempacotamento funcionar (por exemplo, `x, y = my_vector`). Vamos implementá-lo aqui usando uma expressão geradora para produzir os componentes, um após outro.footnote:[Essa linha também poderia ser escrita assim: `yield self.x; yield.self.y`. Terei muito mais a dizer sobre o método especial `+__iter__+`, sobre expressões geradoras e sobre a palavra reservada `yield` no <>.] -<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> Dado um iterável `Vector2d`, é 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` criada iterando sobre a instância. -<8> Para comparar rapidamente todos os componentes, cria 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 de 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 seção <>. 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, py] ----- -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 seção <>.] -<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 do Python, nem tampouco o `staticmethod`. Qualquer um que tenha aprendido OO com Java pode se perguntar porque o Python tem esses dois decoradores, e não apenas um deles. - -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` de fato usa 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 qualquer primeiro argumento especial. 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, pycon] ----- ->>> 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 muito raros. -Talvez a função, mesmo sem nunca tocar na classe, seja intimamente relacionada a essa última. Daí você pode querer que ela fique próxima no seu código. -E 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_)] (EN), de Julien Danjou. O post de Danjou é muito bom; recomendo sua leitura. Mas ele não foi suficiente para mudar meu ponto de vista sobre `staticmethod`. Você terá que decidir por si mesmo.] -==== - -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 formatação efetiva para cada tipo, chamando seu método `+.__format__(format_spec)+`. O `format_spec` especifica a formatação desejada, e é: - -* O segundo argumento em `format(my_obj, format_spec)`, ou -* O que quer que apareça após os dois pontos (`:`) em um campo de substituição delimitado por `{}` dentro de uma f-string ou o `fmt` em `fmt.str.format()` - -[role="pagebreak-before less_space"] -Por exemplo: - -[source, pycon] ----- ->>> 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 formatação especificada é `'0.4f'`. -<2> A formatação especificada é `'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 no 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 textos explicativos apontam um fato importante: uma string de formatação tal como`'{0.mass:5.3e}'` na verdade 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 do formato é chamada https://docs.python.org/pt-br/3/library/string.html#formatspec[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://docs.python.org/pt-br/3/library/string.html#formatspec[Mini-Linguagem de Especificação de Formato]. -Após pegar o jeito desta última, leia -https://docs.python.org/pt-br/3/reference/lexical_analysis.html#f-strings["Literais de string formatados"] -e -https://docs.python.org/pt-br/3/library/string.html#format-string-syntax["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 `str.format()` obsoleto: na maioria dos casos f-strings resolvem o problema, mas algumas vezes é melhor especificar a string de formatação em outro lugar (e não onde ela será renderizada). -==== - -Alguns tipos embutidos tem 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, pycon] ----- ->>> 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 `format_spec` como quiser. Por exemplo, as classes no módulo `datetime` usam os mesmos códigos de formatação nas funções `strftime()` e em seus métodos `+__format__+`. Veja abaixo alguns exemplos de uso da função `format()` e do método `str.format()`: - -[source, pycon] ----- ->>> 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 possuir um `+__format__+`, o método herdado de `object` devolve `str(my_object)`. Como `Vector2d` tem um `+__str__+`, isso funciona: - -[source, pycon] ----- ->>> v1 = Vector2d(3, 4) ->>> format(v1) -'(3.0, 4.0)' ----- - -Entretanto, se você passar um especificador de formato, `+object.__format__+` gera um `TypeError`: - -[source, pycon] ----- ->>> 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, pycon] ----- ->>> 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, python3] ----- - # 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 na fórmula `'(x, y)'`. - -Agora vamos acrescentar um código de formatação personalizado à 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 personalizado de formato, evitei sobrepor códigos usados por outros tipos. Na https://docs.python.org/pt-br/3/library/string.html#formatspec[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 personalizado 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, python3] ----- - # 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, py] ----- -include::code/11-pythonic-obj/vector2d_v2_fmt_snippet.py[tags=VECTOR2D_V2_FORMAT] ----- -==== -<1> O formato termina com `'p'`: usa 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 chaves de ângulo. -<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, pycon] ----- ->>> 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 criar conjuntos de vetores ou usá-los como chaves em um `dict`.((("", startref="dispform11")))((("", startref="POformat11"))) - - -[[hashable_vector2d]] -=== 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, pycon] ----- ->>> 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, mas já temos esse método). Além disso, precisamos tornar imutáveis as instâncias do vetor, como vimos na seção <>. - -[role="pagebreak-before less_space"] -Nesse momento, qualquer um pode fazer `v1.x = 7`, e não há nada no código sugerindo que é proibido modificar um `Vector2d`. O comportamento que queremos é o seguinte: - -[source, pycon] ----- ->>> 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: apenas as mudanças necessárias para tornar `Vector2d` imutável são exibidas aqui; a listagem completa está no <> -==== -[source, py] ----- -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 seção <>, mais adiante.] -<2> O decorador `@property` marca o método _getter_ de uma propriedade. -<3> O método _getter_ é nomeado de acordo com o 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 permanecer como estavam, lendo as propriedades públicas através de `self.x` e `self.y` em vez de usar os atributos privados. Então essa listagem omite o restante do código da classe. - -[NOTE] -==== -`Vector.x` e `Vector.y` são exemplos de propriedades apenas para leitura. Propriedades para leitura/escrita serão tratadas no <>, 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 pass:[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, python3] ----- - # 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, pycon] ----- ->>> 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, supostamente, o valor de um objeto _hashable_ nunca deveria mudar, então isso me dá uma boa desculpa para falar sobre propriedades apenas para leitura. -==== - -Se você estiver criando um tipo com um valor numérico escalar que faz sentido, você pode também implementar os métodos `+__int__+` e `+__float__+`, invocados pelos construtores `int()` e `float()`, que são usados, em alguns contextos, para coerçã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 pattern matching posicional - -Até aqui, instâncias de `Vector2d`((("Pythonic objects", "supporting positional patterns")))((("positional patterns"))) são compatíveis com o _pattern matching_ com instâncias de classe—vistos na <>. - -No <>, todos aqueles padrões nomeados funcionam como esperado. - -[[vector_match_keyword_ex]] -.Padrões nomeados para sujeitos `Vector2d`—requer Python 3.10 -==== -[source, py] ----- -include::code/11-pythonic-obj/patterns.py[tags=KEYWORD_PATTERNS] ----- -==== - -Entretanto, se tentamos usar um padrão posicional, como esse: - -[source, py] ----- - case Vector2d(_, 0): - print(f'{v!r} is horizontal') ----- - -o resultado é esse: - -[source] ----- -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. - - -[source, py] ----- -class Vector2d: - __match_args__ = ('x', 'y') - - # etc... ----- - -Agora podemos economizar alguma digitação ao escrever padrões para usar contra sujeitos -`Vector2d`, como se vê no <>. - -[[vector_match_positional_ex]] -.Padrões posicionais para sujeitos `Vector2d`—requer Python 3.10 -==== -[source, py] ----- -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. - -Vamos dar um passo atrás e 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 pacote completo -==== -[source, python3] ----- -include::code/11-pythonic-obj/vector2d_v3.py[] ----- -==== - -Recordando, nessa seção e nas anteriores vimos alguns dos métodos especiais essenciais que você pode querer implementar para obter um objeto completo. - -[NOTE] -==== -Você deve implementar esses métodos especiais apenas se sua aplicação precisar deles. Os usuários finais não se importam se os objetos que compõem uma 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 eles estarão esperando ver esses comportamentos pythônicos que descrevemos aqui. -==== - -Como programado no <>, -`Vector2d` é um exemplo didático com uma lista extensiva de métodos especiais relacionados à representação de objetos, não um modelo para qualquer classe definida pelo usuário. - -Na próxima seção, deixamos o `Vector2d` de lado por um tempo para discutir o design e as desvantagens 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` no 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), o 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+`. - -Esse recurso da linguagem é conhecido pela encantadora -alcunha de((("name mangling"))) _desfiguração de nome_ ("name mangling"). - -O <> mostra o resultado na classe `Vector2d` do <>. - -[[name_mangling_ex]] -.Nomes de atributos privados são "desfigurados", prefixando-os com o `_` e o nome da classe -==== -[source, pycon] ----- ->>> v1 = Vector2d(3, 4) ->>> v1.__dict__ -{'_Vector2d__y': 4.0, '_Vector2d__x': 3.0} ->>> v1._Vector2d__x -3.0 ----- -==== - -A desfiguração do nome é sobre alguma proteção, não sobre segurança: ela foi projetada para evitar acesso acidental, não ataques maliciosos. A <> ilustra outro dispositivo 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 <>—isso na verdade é útil para depuração e serialização. Isso 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 com 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 da forma convencional -(por exemplo, `self._x`). Críticos da desfiguração automática com o sublinhado duplo sugerem que preocupações com modificações acidentais a atributos deveriam ser tratadas através de convenções de nomenclatura. Ian Bicking—criador do pip, do virtualenv e de outros projetos—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 onde o sublinhado duplo é obscuro.footnote:[Do https://fpy.li/11-8["Paste Style Guide" (_Guia de Estilo do Paste_)].] -____ - -[[safety_fig]] -.Uma cobertura sobre um interruptor é um dispositivo de proteção, não de segurança: ele previne acidentes, não sabotagem -image::images/flpy_1101.png[interruptores com coberturas de proteção] - -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://docs.python.org/pt-br/3/tutorial/modules.html#more-on-modules[_Tutorial do 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 do Python.footnote:[Um exemplo é a https://docs.python.org/pt-br/3/library/gettext.html#gettext.NullTranslations[documentação do módulo 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 o Python fosse mais parecido com o Java nesse aspecto, nem leia minha discussão sobre a força relativa do modificador `private` do Java no <>.] - -Vamos agora voltar à nossa classe `Vector2d`. Na próxima seção trataremos de um atributo (e não um método) especial que afeta o armazenamento interno de um objeto, com um imenso impacto potencial sobre seu uso de memória, mas pouco efeito sobre sua interface pública: `+__slots__+`.((("", startref="Aprivate11")))((("", startref="POprivate11"))) - - -[[slots_section]] -=== Economizando memória com pass:[__slots__] - -Por((("Pythonic objects", "saving memory with __slots__", id="POslot11")))((("__slots__", id="slots11")))((("memory, saving with __slots__", id="memsave11"))) default, o Python armazena os atributos de cada instância em um `dict` chamado `+__dict__+`. -Como vimos em <>, 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__+`, que mantém uma sequência de nomes de atributos, o 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, pycon] ----- -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 qualquer 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`, 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, pycon] ----- -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, pycon] ----- -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 no habitual -`+__dict__+`. -Isso é necessário para usar o decorador `@cached_property` (tratado na seção <>). - -Naturalmente, incluir `+__dict__+` em `+__slots__+` pode desviar completamente do objetivo deste último, -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: adiciona complexidade sem colher qualquer benefício. - -Outro atributo de instância especial que você pode querer manter é `+__weakref__+`, necessário para que objetos suportem referências fracas (mencionadas brevemente na seção <>). -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 da adição de `+__slots__+` a `Vector2d`. - -==== Uma medida simples da economia gerada por pass:[__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, py] ----- -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. -<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, bash] ----- -$ 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 do NumPy (veja a seção <>), 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 ao mesmo tempo. 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]] -==== 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 da sobreposição de um atributo de classe em instâncias e subclasses.((("", startref="POslot11")))((("", startref="slots11")))((("", startref="memsave11"))) - -[[overriding_class_attributes]] -=== Sobrepondo atributos de classe - -Um((("Pythonic objects", "overriding class attributes", id="POoverride11")))((("attributes", "overriding class attributes", id="Aover11"))) recurso característico do 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`. Ele é usado duas vezes no método `+__bytes__+`, mas é lido intencionalmente como `self.typecode`. As instâncias de `Vector2d` são criadas sem um atributo `typecode` próprio, então `self.typecode` vai, por default, se referir ao atributo de classe `Vector2d.typecode`. - -Mas se incluirmos um atributo de instância que não existe, estamos criando um novo atributo de instância—por exemplo, um atributo de instância `typecode`—e o atributo de classe com o mesmo nome permanece intocado. Entretanto, daí em diante, sempre que algum código referente àquela instância contiver `self.typecode`, o `typecode` da instância será usado, na prática escondendo o atributo de classe de mesmo nome. Isso abre a possibilidade de personalizar uma instância individual com um `typecode` diferente. - -O `Vector2d.typecode` default é `'d'`: isso significa que cada componente do vetor será representado como um número de ponto flutuante de dupla precisão 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 do acréscimo de um atributo de instância, assim o <> usa a implementação de `Vector2d` sem `+__slots__+`, como aparece no <>. -==== - -[[typecode_instance_demo]] -.Personalizando uma instância pela definição do atributo `typecode`, que antes era herdado da classe -==== -[source, pycon] ----- ->>> 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 diferentes formatos de exportação. - -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, pycon] ----- ->>> 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 personalizar 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 sobrepõe o `typecode` default -==== -[source, pycon] ----- ->>> 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 sobrepor 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 escrevi explicitamente o `class_name` em pass:[Vector2d.​__repr__], optando por obtê-lo de `+type(self).__name__+`, assim: - -[source, python3] ----- - # 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 de ser herdado. - -Aqui termina nossa conversa sobre a criação de uma classe simples, que se vale do modelo de dados para se adaptar bem ao restante do Python: oferecendo diferentes representações do objeto, fornecendo um código de formatação personalizado, 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 do Python. Mas decidir qual das duas implementações de `Vector2d` é mais adequada, a primeira ou a última, depende do contexto onde a classe será usada. O "Zen of Python" (_Zen do Python_), de Tim Peter, diz: - -[quote] -____ -Simples é melhor que complexo. -____ - -Um objeto deve ser tão simples quanto seus requerimentos exigem—e não um desfile de recursos da linguagem. Se o código for parte de uma aplicação, ele deveria se concentrar naquilo que for necessário para suportar 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 implementar métodos especiais que suportam comportamentos esperados por pythonistas. -Por exemplo, `+__eq__+` pode não ser necessário para suportar um requisito do negócio, mas torna a classe mais fácil de testar. - -Minha meta, ao expandir o código do `Vector2d`, foi criar um contexto para a discussão dos métodos especiais e das convenções de programação em Python. -Os exemplos neste capítulo demonstraram vários dos métodos especiais vistos antes na <> (do <>): - -* 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 suportar testes e _hashing_ (juntamente com `+__hash__+`) - -Quando suportamos a conversão para `bytes`, também implementamos um construtor alternativo, `Vector2d.frombytes()`, que nos deu um contexto 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://docs.python.org/pt-br/3/library/string.html#formatspec[Mini-Linguagem de Especificação de Formato] é extensível, ao implementarmos um método `+__format__+` que analisa uma `format_spec` fornecida à função embutida `format(obj, format_spec)` ou dentro de campos de substituição `'{:«format_spec»}'` em f-strings ou ainda strings usadas com o método `str.format()`. - -Para preparar a transformação de instâncias de `Vector2d` em _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 apenas para leitura. Nós então implementamos -`+__hash__+` usando a técnica recomendada, aplicar 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 sobreposição 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 do 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 objetos reais do Python. -____ - -[[pythonic_further_reading]] -=== Leitura complementar - -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 <>, onde tivemos uma ideia geral do mesmo tópico. Por conveniência, vou repetir aquelas quatro recomendações anteriores aqui, e acrescentar algumas outras: - -O https://docs.python.org/pt-br/3/reference/datamodel.html[capítulo "Modelo de Dados"] em _A Referência da Linguagem Python_:: A maioria dos métodos usados nesse capítulo estão documentados em https://docs.python.org/pt-br/3/reference/datamodel.html#basic-customization["3.3.1. Personalização básica"]. - -pass:[Python in a Nutshell, 3ª ed.,] de Alex Martelli, Anna Ravenscroft, e Steve Holden:: Trata com profundidade dos métodos especiais . - -pass:[Python Cookbook, 3ª ed.], de David Beazley e Brian K. Jones:: Práticas modernas do Python demonstradas através de receitas. Especialmente o Capítulo 8, "Classes and Objects" (_Classes e Objetos_), que contém várias receitas relacionadas às discussões deste capítulo. - -_Python Essential Reference_, 4ª ed., de David Beazley:: Trata do modelo de dados em detalhes, apesar de falar apenas do 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 o Python 2.2, quando os tipos embutidos e as classes definidas pelo usuário foram unificados. - -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 <>. -As fábricas de classes de dados do <>, assim como `attrs`, equipam suas classes automaticamente 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—quando necessário—sobrescrever os métodos que eles geram. - -Vimos neste capítulo todos os métodos especiais relacionados à representação de objetos, exceto -`+__index__+` e `+__fspath__+`. -Discutiremos `+__index__+` no <>, na seção <>. -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_)] (EN). - -Uma percepção precoce da necessidade de strings de representação diferentes para objetos apareceu no Smalltalk. O artigo de 1996 https://fpy.li/11-13["How to Display an Object as a String: printString and displayString" (_Como Mostrar um Objeto como uma String: printString and displayString_)] (EN), de Bobby Woolf, discute a implementação dos métodos `printString` e `displayString` naquela linguagem. Foi desse artigo que peguei emprestado as expressivas descrições "como o desenvolvedor quer vê-lo" e "como o usuário quer vê-lo" para definir `repr()` e `str()`, na seção <>. - - -[role="pagebreak-before less_space"] -[[pythonic_soapbox]] -.Ponto de Vista -**** - - -[role="soapbox-title"] -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 sentimos a necessidade de evitar modificações acidentais dos atributos `x` e `y`, implementamos propriedades, mas nada mudou 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` and `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) nós mais tarde precisarmos impor mais controle, com _getters_ e _setters_, estes métodos 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 (`x` e `y`, por 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 -pass:[Python in a Nutshell, 3rd ed.], digitar chamadas a _getters_ e _setters_ por toda parte é patético. Você é obrigado a escrever coisas como: - -[source, python3] ----- ->>> my_object.set_foo(my_object.get_foo() + 1) ----- - -Apenas para fazer isso: - -[source, python3] ----- ->>> 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 é uma distração 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 . - - -[role="soapbox-title"] -Proteção versus segurança em atributos privados - -[quote, Larry Wall, criador do 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. -____ - -O Python((("Soapbox sidebars", "safety versus security in private attributes")))((("attributes", "safety versus security in private"))) e o 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, descobri que muitos deles tem uma fé excessiva nas garantias de privacidade oferecidas pelo Java. -E na verdade, os modificadores `private` e `protected` do 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] (EN) do Java, e isso raramente acontece na prática, mesmo em configurações corporativas atentas à segurança. - -Para provar meu argumento, gostaria de mostrar essa classe Java (o <>). - -[[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, só para deixar óbvio que qualquer coisa que esteja naquele campo estará escrito inteiramente em maiúsculas. - -A verdadeira demonstração consiste em rodar _expose.py_ com Jython. Aquele script usa introspecção ("reflexão"—_reflection_—no jargão do Java) para obter 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, python3] ----- -include::code/11-pythonic-obj/private/expose.py[] ----- -==== - -Executando o <>, o resultado é esse: - -[source, bash] ----- -$ 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 do 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 pass:[repositório de código do Python Fluente]). - -A chamada `.setAccessible(True)` só falhará se o script Jython ou o programa principal em Java (por exemplo, a `Expose.class`) estiverem rodando sob a supervisão de um https://fpy.li/11-15[`SecurityManager`] (EN). -Mas, no mundo real, aplicações Java raramente são implantadas com um `SecurityManager`—com a exceção das _applets_ Java, quando elas ainda era suportadas pelos navegadores. - -Meu ponto: também em Java, -os modificadores de controle de acesso são principalmente sobre proteção e não segurança, -pelo menos na prática. -Então relaxe e aprecie o poder dado a você pelo Python. -E use esse poder com responsabilidade.((("", startref="POsoap11"))) - -**** diff --git a/capitulos/cap12.adoc b/capitulos/cap12.adoc deleted file mode 100644 index 7a89ae71..00000000 --- a/capitulos/cap12.adoc +++ /dev/null @@ -1,914 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte - -[[user_defined_sequences]] -== Métodos especiais para sequências - -[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 exato de comportamentos de pato você precisa para usar em seus jogos de linguagem. (`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 simples sequência imutável padrão do 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 segura de instâncias com muitos itens -* Suporte adequado a fatiamento, produzindo novas instâncias de `Vector` -* _Hashing_ agregado, levando em consideração o valor de cada elemento contido na sequência -* Um extensão personalizada 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 típico de tipos sequência. - -Nossa apresentação voltada para o código será interrompida por uma discussão conceitual sobre a ideia de protocolos como uma interface informal. Vamos discutir a relação entre protocolos e _duck typing_, e as implicações práticas disso na criação de seus próprios tipos - -=== Novidades nesse capítulo - -Não((("sequences, special methods for", "significant changes to"))) ocorreu qualquer grande modificação neste capítulo. Há uma breve discussão nova sobre o `typing.Protocol` em um quadro de dicas, no final da seção <>. - -Na seção <>, 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: Um 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/12-1[Modelo de Espaço Vetorial] (EN). Nesse modelo, a métrica fundamental de relevância é a __similaridade de cosseno__—o cosseno do ângulo entre o vetor representando a consulta e o vetor representando o documento. Conforme o ângulo diminui, o valor do cosseno aumenta, indicando a relevância do documento para aquela consulta: cosseno próximo de 0 significa pouca relevância; próximo de 1 indica alta relevância. - -Dito isto, a classe `Vector` nesse capítulo é um exemplo didático. O objetivo é apenas demonstrar alguns métodos especiais do 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 pass:[Vector.__init__] e pass:[Vector.__repr__] -==== -[source, pycon] ----- ->>> 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, me assegurei que todos os testes realizados com `Vector2d` (por exemplo, `Vector2d(3, 4)`) são bem sucedidos 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 <> lista a implementação de nossa primeira versão de `Vector` (esse exemplo usa como base o código mostrado no pass:[#ex_vector2d_v0] e no pass:[#ex_vector2d_v1] do <>). - -[[ex_vector_v1]] -.vector_v1.py: derived from vector2d_v1.py -==== -[source, py] ----- -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++.footnote:[A função `iter()` é tratada no <>, 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 modo como usei `reprlib.repr` pede alguma elaboração. Essa função produz representações seguras de estruturas grandes ou recursivas, limitando a tamanho da string devolvida e marcando o corte 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__+`, poderia ter produzido uma versão para exibição simplificada de `components` com essa expressão: `reprlib.repr(list(self._components))`. Isso, entretanto, geraria algum desperdício, pois eu estaria copiando cada item de `self._components` para uma `list` apenas 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` (um `*` foi removido na última linha). Isso é um dos benefícios de termos tornado o `Vector2d` original iterável. - -Aliás, poderíamos ter criado `Vector` como uma subclasse de `Vector2d`, mas escolhi não fazer isso por duas razões. Em primeiro lugar, os construtores incompatíveis de fato tornam a relação de super/subclasse desaconselhável. Eu até poderia contornar isso como um tratamento engenhoso dos parâmetros em -`+__init__+`, mas a segunda razão é mais importante: 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_section]] -=== Protocolos e o _duck typing_ - -Já((("Vector class, multidimensional", "protocols and duck typing")))((("sequences, special methods for", "protocols and duck typing")))((("protocols", "duck typing and")))((("duck typing"))) no <>, vimos que não é necessário herdar de qualquer classe em especial 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ões, pode ser usada em qualquer lugar onde uma sequência for 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 <>. - -[[ex_pythonic_deck_rep]] -.Código do <>, reproduzido aqui por conveniência -==== -[source, python3] ----- -include::code/01-data-model/frenchdeck.py[] ----- -==== - -A classe `FrenchDeck`, no <>, pode tirar proveito de muitas facilidades do 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, e é isso que importa. - -Isso ficou conhecido 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 sabemos o contexto específico em que a classe será utilizada. Por exemplo, apenas `+__getitem__+` basta para suportar iteração; não há necessidade de fornecer um `+__len__+`. - -[role="man-height4"] -[TIP] -==== -Com a https://fpy.li/pep544[PEP 544—Protocols: Structural subtyping (static duck typing) (_Protocolos:sub-tipagem estrutural (duck typing estático)_)] (EN), -o Python((("protocol classes")))((("protocols", "static protocols"))) 3.8 suporta _classes protocolo_: subclasses de `typing.Protocol`, -que estudamos na seção <>. -Esse novo uso da palavra protocolo no Python tem um significado relacionado, mas diferente. 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 é que implementações de um protocolo estático precisam oferecer todos os métodos definidos na classe protocolo. -A seção <> no <> traz maiores detalhes. -==== - -//// -PROD: The preceding box sometimes overlaps with the next paragraph. -//// - -Vamos agora implementar o protocolo sequência em `Vector`, primeiro sem suporte adequado ao fatiamento, que acrescentaremos mais tarde. - - -[[sliceable_sequence]] -=== Vector versão #2: Uma 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, python3] ----- -class Vector: - # many lines omitted - # ... - - def __len__(self): - return len(self._components) - - def __getitem__(self, index): - return self._components[index] ----- - -Após tais acréscimos, agora todas as seguintes operações funcionam: - -[source, pycon] ----- ->>> 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 antiga classe `FrenchDeck` tem um problema similar: quando ela é fatiada, o resultado é uma `list`. No caso de `Vector`, muito da funcionalidade é perdida quando o fatiamento produz arrays simples. - -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 algum 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 o Python transforma a sintaxe `my_seq[1:3]` em argumentos para `+my_seq.__getitem__(...)+`. - - -[[how_slicing_works]] -==== Como funciona o fatiamento - -Uma demonstração vale mais que mil palavras, então dê uma olhada no <>. - -[[ex_slice0]] -.Examinando o comportamento de `+__getitem__+` e fatias -==== -[source, pycon] ----- ->>> 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, pycon] ----- ->>> 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 seção <>). -<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)`:: - 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, `indices` expõe a lógica complexa implementada nas sequências embutidas, para lidar graciosamente com índices inexistentes ou negativos e com 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, considerando uma sequência de `len == 5`, por exemplo `'ABCDE'`: - -[source, pycon] ----- ->>> 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 ajuda evita a necessidade 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, py] ----- -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_)] (EN), 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 principal entre `operator.index()` e `int()` é que o primeiro foi projetado para esse propósito específico. Por exemplo, `int(3.14)` devolve `3`, mas `operator.index(3.14)` gera um `TypeError`, porque um `float` não deve ser usado como índice. - - -[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 verificar se esse argumento era um inteiro. O uso de `operator.index` evita esse teste, e gera um pass:[Type​Error] 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, pycon] ----- -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: acesso dinâmico a atributos - -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, algo como `x`, `y`, `z` em vez de `v[0]`, `v[1]`, and `v[2]`. - -Aqui está a sintaxe alternativa que queremos oferecer para a leitura dos quatro primeiros componentes de um vetor: - -[source, pycon] ----- ->>> 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 <>). Poderíamos incluir quatro propriedades no `Vector`, mas isso seria tedioso. O método especial `+__getattr__+` nos fornece uma opção melhor. - -O método `+__getattr__+` é invocado pelo interpretador quando a busca por um atributo falha. -Simplificando, dada a expressão `my_obj.x`, o 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 detalhes macabros desse processo no <>. Por ora, essa 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'`). - -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, py] ----- -include::code/12-seq-hacking/vector_v3.py[tags=VECTOR_V3_GETATTR] ----- -==== -<1> Define `+__match_args__+` para permitir _pattern matching_ posicional sobre os atributos dinâmicos suportados por `+__getattr__+`.footnote:[Apesar de `+__match_args__+` existir para suportar _pattern matching_ desde Python 3.10, definir este atributo em versões anteriores da linguagem é inofensivo. 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 manter 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, pycon] ----- ->>> 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 `v.x` devolve `10` na segunda consulta (<3>), se aquele 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 pouco sutil, mas é um alicerce fundamental para entender grande parte do que veremos mais tarde no livro. - -Após pensar um pouco sobre essa questão, siga em frente e leia a explicação para o que aconteceu. - -A inconsistência no <> ocorre devido à forma como `+__getattr__+` funciona: o 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__+` não leva em consideração qualquer atributo de instância diferente de -`self._components`, de onde ele obtém os valores dos "atributos virtuais" listados em `+__match_args__+`. - -Para evitar essa inconsistência, precisamos modificar 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, só 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, py] ----- -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 mensagens de erro específicas. -<3> Se `name` é uma letra minúscula, configura a mensagem de erro sobre todos os nomes de uma única 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 obter 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 o 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 seção <>. -==== - -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 [re]-definir 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. - -Observe que não estamos proibindo a modificação de todos os atributos, apenas daqueles com nomes compostos por uma única letra minúscula, para evitar conflitos com os atributos apenas para leitura suportados, `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 seção <>, 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]] -=== 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 <>) computava o _hash_ de uma `tuple` construída com os dois componentes, `self.x` and `self.y`. -Nós 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 seção <>.] 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`. - -//// -PROD: unexpected indent in first line after page break -//// - -[[reduce_fig]] -.Funções de redução—`reduce`, `sum`, `any`, `all`—produzem um único resultado agregado a partir de uma sequência ou de qualquer objeto iterável finito. -image::images/flpy_1201.png[Diagrama de reduce] - -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. - -Aqui está como `reduce` poderia ser usada para computar `5!` (o fatorial de 5): - -[source, pycon] ----- ->>> 2 * 3 * 4 * 5 # the result we want: 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 loop `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, pycon] ----- ->>> 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 loop `for` e uma variável de acumulação. -<2> `functools.reduce` usando uma função anônima. -<3> `functools.reduce` substituindo a `lambda` personalizada por `operator.xor`. - -Das alternativas apresentadas no <>, a última é minha favorita, e o loop `for` vem a seguir. Qual sua preferida? - -Como visto na seção <>, `operator` oferece a funcionalidade de todos os operadores infixos do 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`. <> apresenta as modificações 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, python3] ----- -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... ----- -==== -[role="pagebreak-before less_space"] -<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 próximo aviso). - -[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` ("_TypeError: reduce() de uma sequência vazia sem valor inicial_"— uma mensagem excelente: 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, e portanto deve ser o elemento neutro da operação. Assim, o `initializer` para `+`, `|`, `^` 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 (<>). - -[[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). -image::images/flpy_1202.png[Diagrama de map-reduce] - -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 `map` em vez de uma _genexp_, a etapa de mapeamento fica ainda mais visível: - -[source, python3] ----- - def __hash__(self): - hashes = map(hash, self._components) - return functools.reduce(operator.xor, hashes) ----- - -[TIP] -==== -A solução com `map` seria menos eficiente no Python 2, onde a função `map` cria 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 <>, temos esta implementação bastante concisa de `+__eq__+`: - -[source, python3] ----- - 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ãofootnote:[Vamos considerar seriamente o caso de `++Vector([1, 2]) == (1, 2)++` na seção <>.]. -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 loop `for`, para uma comparação mais eficiente -==== -[source, python3] ----- - 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> Se as `len` dos objetos são diferentes, eles não são iguais. -<2> `zip` produz um gerador de tuplas criadas a partir dos itens em cada argumento iterável. Veja a caixa <>, se `zip` for novidade para você. Em pass:[1], a comparação com `len` é necessária porque `zip` para de produzir valores sem qualquer aviso quando uma das fontes de entrada se exaure. -<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 esse objeto físico funciona engatando pares de dentes tomados dos dois lados do zíper, uma boa analogia visual para o que faz `zip(left, right)`. Nenhuma relação com arquivos comprimidos. -==== - -O <> é eficiente, mas a função `all` pode produzir a mesma computação de um agregado do loop `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`. - -[[ex_eq_all]] -.A implementação de `+Vector.__eq__+` usando `zip` e `all`: mesma lógica do <> -==== -[source, python3] ----- - def __eq__(self, other): - return len(self) == len(other) and all(a == b for a, b in zip(self, other)) ----- -==== - -Observe que primeiro comparamos o `len()` dos operandos porque -se os tamanhos são diferentes é desnecessário comparar os itens. - -O <> é a implementação que escolhemos para `+__eq__+` em _vector_v4.py_.((("", startref="eq12")))((("", startref="hash12")))((("", startref="SSMhasheq12")))((("", startref="VCMhasheq12"))) - - -[[zip_box]] -.O fantástico zip -**** -Ter um loop `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, pycon] ----- ->>> 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; nós 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 nova opção de zip() no Python 3.10 -[NOTE] -==== -Escrevi na primeira edição deste livro que `zip` encerrar silenciosamente ao final do iterável mais curto era surpreendente—e não era uma boa característica em uma API. -Ignorar parte dos dados de entrada sem qualquer alerta pode levar a bugs sutis. -Em vez disso, `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—alinhado à política de _falhar rápido_((("fail-fast philosophy"))) do Python. -A https://fpy.li/pep618[PEP 618—Add Optional Length-Checking To zip] acrescentou um argumento opcional `strict` à função `zip`, para fazê-la de comportar dessa forma. -Isso foi implementado no Python 3.10. -==== - -A função `zip` pode também ser usada para transpor uma matriz, representada como iteráveis aninhados. -Por exemplo: - -[source, pycon] ----- ->>> 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 descobrindo 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 estiver familiarizado com `enumerate` deveria estudar a seção dedicada a ela na https://docs.python.org/pt-br/3/library/functions.html#enumerate[documentação das "Funções embutidas"]. As funções embutidas `zip` e `enumerate`, bem como várias outras funções geradores na biblioteca padrão, são tratadas na seção <>. -**** - -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 personalizada em coordenadas polares, `Vector` usará coordenadas esféricas—também conhecidas como coordendas "hiperesféricas", pois agora suportamos _n_ dimensões, e as esferas são "hiperesferas", em 4D e alémfootnote:[O website Wolfram Mathworld tem um artigo sobre https://fpy.li/12-4[hypersphere (_hiperesfera_)] (EN); na Wikipedia, "hypersphere" redireciona para https://fpy.li/nsphere[a página “_n_-sphere” ] (EN)footnote:[NT: A Wikipedia tem uma página em português, https://pt.wikipedia.org/wiki/N-esfera["N-esfera"]. Entretanto, enquanto a versão em inglês traz uma extensa explicação matemática, dividida em 12 seções e inúmeras subseções, a versão em português se resume a um parágrafo curto. Preferimos então manter o link para a versão mais completa.]. Como consequência, mudaremos também o sufixo do formato personalizado de `'p'` para `'h'`. - -//// -PROD: unexpected indent in first line after page break -//// - -[TIP] -==== -Como vimos na seção <>, ao estender a https://docs.python.org/pt-br/3/library/string.html#formatspec[Minilinguagem de especificação de formato] é melhor evitar a reutilização dos códigos de formato usados por tipos embutidos. Especialmente, nossa minilinguagens estendida também usa os códigos de formato dos números de ponto flutuante (`'eEfFgGn%'`), em seus significados originais, então devemos certamente evitar qualquer um daqueles. 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 ``, onde `r` é a magnitude (`abs(v)`), e o restante dos números são os componentes angulares Φ₁, Φ₂, Φ₃. - -Aqui estão algumas amostras do formato de coordenadas esféricas em 4D, retiradas dos doctests de _vector_v5.py_ (veja o <>): - -[source, pycon] ----- ->>> 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, Φ₁), 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”] (EN: ver Nota 6) da Wikipedia contém 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 seção <>, e acrescentando a formatação personalizada - -[[ex_vector_v5]] -.vector_v5.py: doctests e todo o código da versão final da classe `Vector`; as notas explicativas enfatizam os acréscimos necessários para suportar `+__format__+` -==== -[source, py] ----- -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, usando fórmulas adaptadas do artigo https://fpy.li/nsphere[_n_-sphere] (EN: ver Nota 6) 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, com os delimitadores de ângulo (`<` e `>`). -<6> Configura uma coordenda cartesiana para exibição, com parênteses. -<7> Cria uma expressão geradoras para formatar sob demanda cada item de coordenada. -<8> Insere componentes formatados, separados por vírgulas, dentro de delimitadores ou parênteses. - -[NOTE] -==== -Estamos fazendo uso intensivo de 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 <>, vamos usar parte do código de `Vector` nos exemplos, e lá os recursos dos geradores serão explicados em detalhes. -==== - -Isso conclui nossa missão nesse capítulo. A classe `Vector` será aperfeiçoada 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 de coleções.((("", 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 _duck typing_. - -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__+`. Fazer isso abriu a possibilidade de incentivar o usuário a atribuir àqueles componentes especiais, 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 cujos nomes tenham apenas uma letra. É comum, após escrever um `+__getattr__+`, ser necessário adicionar também `+__setattr__+`, para evitar comportamento inconsistente. - -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 referente a todo o `Vector`. Após aplicar `reduce` em `+__hash__+`, usamos a função embutida de redução `all`, para criar um método `+__eq__+` mais eficiente. - -O último aperfeiçoamento a `Vector` foi reimplementar o método `+__format__+` de `Vector2d`, para suportar coordenadas esféricas como alternativa às coordenadas cartesianas default. Usamos bastante matemática e vários geradores para programar `+__format__+` e suas funções auxiliares, mas esses são detalhes de implementação—e voltaremos aos geradores no <>. O objetivo daquela última seção foi suportar um formato personalizado, cumprindo assim a promessa de um `Vector` capaz de fazer tudo que um `Vector2d` faz e algo mais. - -Como fizemos no <>, muitas vezes aqui olhamos como os objetos padrão do Python se comportam, para emulá-los e dar a `Vector` uma aparência "pythônica". - -No <> vamos implemenar vários operadores infixos em `Vector`. A matemática será muito mais simples que aquela no método `angle()` daqui, 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, vamos parar um pouco de trabalhar com uma única classe e olhar para a organização de múltiplas classes com interfaces e herança, os assuntos dos capítulos pass:[#ifaces_prot_abc] e pass:[#inheritance]. - - -=== Leitura complementar - -A((("Vector class, multidimensional", "further reading on")))((("sequences, special methods for", "further reading on"))) maioria dos métodos especiais tratados no exemplo de `Vector` também apareceram no exemplo do `Vector2d`, no <>, então as referências na seção <> ali são todas 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 https://fpy.li/12-5[artigo "Fold (higher-order function)" (_"Dobrar (função de ordem superior)"_)] (EN), que apresenta aplicações daquela função de ordem superior, 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://docs.python.org/2.5/whatsnew/pep-357.html["What's New in Python 2.5" (_Novidades no Python 2.5_)] (EN) há uma pequena explicação sobre `+__index__+`, projetado para suportar métodos `+__getitem__+`, como vimos na seção <>. -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 de um implementador de uma extensão em C—Travis Oliphant, o principal criador da NumPy. As muitas contribuições de Oliphant tornaram o Python uma importante linguagem para computação científica, que por sua vez posicionou a linguagem como a escolha preferencial para 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 do Python. Os criadores do 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 isso era meramente um artefato de documentação e navegação, e não era imposto 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 (e impostas pelo compilador). - -Protocolos bem estabelecidos ou consagrados evoluem naturalmente em qualquer linguagem que usa tipagem dinâmica (isto é, quando a verificação de tipo 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 do Python, muitas vezes podemos perceber que um protocolo está sendo discutido pelo uso de linguagem como "um objeto similar a um arquivo". Isso é uma forma abreviada de dizer "algo que se comporta como um arquivo, implementando as partes da interface de arquivo relevantes ao 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://docs.python.org/pt-br/3/reference/datamodel.html#special-method-names[Seção 3.3] do capítulo "Modelo de Dados" na documentação do Python sugere que: - -[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 para manter o compilador feliz, fica mais fácil seguir o https://pt.wikipedia.org/wiki/Princ%C3%ADpio_KISS[princípio KISS]. - -Por outro lado, se você quiser usar um verificador de tipo para checar suas implementações de protocolos, então uma definição mais estrita de "protocolo" é necessária. É isso que `typing.Protocol` nos fornece. - -Terei mais a dizer sobre protocolos e interfaces no <>, onde esses conceitos são o assunto principal. - - -[role="soapbox-title"] -As origens do _duck typing_ - -Creio((("Soapbox sidebars", "duck typing")))((("duck typing"))) que a comunidade Ruby, mais que qualquer outra, ajudou a popularizar o termo "duck typing" (_tipagem pato_), ao pregar para as massas de usuários de Java. Mas a expressão já era usada nas discussões do Python muito antes do Ruby ou do 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] (EN), 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://pt.wikipedia.org/wiki/Duck_typing["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 considerado inconveniente, então seria legal 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 colocar `...` em seu lugar. 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 ignorante do problema de exibição de vetores muito grandes não será acidentalmente penalizado. Mas se a limitação default se tornar incômoda, a presença das `...` iria incentivar o usuário a consultar a documentação e descobrir o código de formatação `*` . - - -[role="soapbox-title"] -A busca por uma soma pythônica - -Não há((("Soapbox sidebars", "Pythonic sums", id="SSpysum12")))((("Pythonic sums", id="pysum12"))) uma resposta única para a "O que é pythônico?", da mesma forma que não há uma resposta única para "O que é belo?". Dizer, como eu mesmo muitas vezes faço, que significa usar "Python idiomático" não é 100% satisfatório, porque talvez o que é "idiomático" para você não seja para mim. Sei de uma coisa: "idiomático" não significa o uso dos recursos mais obscuros da linguagem. - -Na https://fpy.li/12-11[Python-list] (EN), 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 os "n" elementos de uma lista_)]. Ela é relevante para nossa discussão de `reduce` acima nesse capítulo. - -O autor original, Guy Middleton, pediu melhorias para essa solução, afirmando não gostar de usar `lambda`:footnote:[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).] - -[source, pycon] ----- ->>> my_list = [[1, 2, 3], [40, 50, 60], [9, 8, 7]] ->>> import functools ->>> functools.reduce(lambda a, b: a+b, [sub[1] for sub in my_list]) -60 ----- - -Esse código usa muitos idiomas: `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—praticamente os dois lados de uma disputa. - -Se você vai usar `lambda`, provavelmente não há razão para usar uma compreensão de lista—exceto para filtragem, que não é o caso aqui. - -Aqui está uma solução minha que agradará os amantes de `lambda`: - -[source, pycon] ----- ->>> functools.reduce(lambda a, b: a + b[1], my_list, 0) -60 ----- - -Não tomei parte na discussão original, e não usaria o trecho acima em código real, -pois eu também não gosto muito de `lambda`. -Mas eu queria mostrar um exemplo sem uma compreensão de lista. - -A primeira resposta veio de Fernando Perez, criador do IPython, e realça como o NumPy suporta arrays _n_-dimensionais e fatiamento _n_-dimensional: - -[source, pycon] ----- ->>> import numpy as np ->>> my_array = np.array(my_list) ->>> np.sum(my_array[:, 1]) -60 ----- - -Acho a solução de Perez boa, mas Guy Middleton elegiou essa próxima solução, de Paul Rubin e Skip Montanaro: - -[source, pycon] ----- ->>> import operator ->>> functools.reduce(operator.add, [sub[1] for sub in my_list], 0) -60 ----- - -Então Evan Simpson perguntou, "Há algo errado com isso?": - -[source, pycon] ----- ->>> total = 0 ->>> for sub in my_list: -... total += sub[1] -... ->>> total -60 ----- - -Muitos concordaram que esse código era bastante pythônico. Alex Martelli chegou a dizer que provavelmente seria assim que Guido escreveria a solução. - -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 isso para se parecer com "a soma de uma lista de itens", não para se parecer com "faça um loop sobre esses itens, mantenha uma outra variável t, execute uma sequência de adições". Por que outra razão 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 implementá-las? -____ - -[role="pagebreak-before less_space"] -E daí Alex Martelli voltou para sugerir: - -[quote] -____ -A soma é necessária com tanta frequência que eu não me importaria de forma alguma se o 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 está se referindo à linguagem https://pt.wikipedia.org/wiki/APL_(linguagem_de_programa%C3%A7%C3%A3o[APL]] e um apreciador da FPfootnote:[NT:E aqui à linguagem https://pt.wikipedia.org/wiki/FP_(linguagem_de_programa%C3%A7%C3%A3o[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 o 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, pycon] ----- ->>> sum([sub[1] for sub in my_list]) -60 ----- - -No final do ano seguinte (novembro de 2004), o 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, pycon] ----- ->>> 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` do 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/capitulos/cap13.adoc b/capitulos/cap13.adoc deleted file mode 100644 index 479532d9..00000000 --- a/capitulos/cap13.adoc +++ /dev/null @@ -1,1992 +0,0 @@ -[[ifaces_prot_abc]] -== Interfaces, protocolos, e ABCs -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:xrefstyle: short -:example-number: 0 -:figure-number: 0 - -++++ -
-

Programe mirando uma interface, não uma implementação.

-

Gamma, Helm, Johnson, Vlissides, First Principle of Object-Oriented Design Design Patterns: Elements of Reusable Object-Oriented Software, "Introduction," p. 18.

-
-++++ -// [quote, Gamma, Helm, Johnson, Vlissides, First Principle of Object-Oriented Design] -// ____ -// Program to an interface, not an implementation.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 abordagem para entender um tipo em Python é conhecer os métodos que -aquele tipo oferece—sua interface—como discutimos na -<> do (<>). - -Dependendo ((("interfaces", "ways of defining and using"))) da linguagem de programação, -temos uma ou mais maneiras de definir e usar interfaces. -Desde o Python 3.8, temos quatro maneiras. -Elas estão ilustradas no _Mapa de Sistemas de Tipagem_ (<>). -Podemos resumi-las assim: - -Duck typing (_tipagem pato_):: - A((("duck typing"))) abordagem default do Python para tipagem desde o início. - Estamos estudando duck typing desde <>. -Goose typing (_tipagem ganso_):: - A((("goose typing", "definition of term"))) abordagem suportada pelas classes base abstratas - (ABCs, _sigla em inglês para Abstract Base Classes_) desde o Python 2.6, - que depende de verificações dos objetos como as ABCs durante a execução. - A _tipagem ganso_ é um dos principais temas desse 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 verificadores de tipo externos compatíveis com a - https://fpy.li/pep484[PEP 484—Type Hints]. - Este não é o foco desse capítulo. - A maior parte do <> e do <> - mais adiante são sobre tipagem estática. -Duck typing estática:: - 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 verificadores de tipo externos. - Tratamos desse tema pela primeira vez em <> (<>), - e continuamos nesse capítulo. - - -=== O mapa de tipagem - -As((("interfaces", "typing map")))((("typing map"))) quatro abordagens retratadas na -<> são complementares: elas tem diferentes prós e contras. -Não faz sentido descartar qualquer uma delas. - -[[type_systems_described]] -.A metade superior descreve abordagens de checagem de tipo durante a execução usando apenas o interpretador Python; a metade inferior requer um verificador de tipo estático externo, como o Mypy ou um IDE como o PyCharm. Os quadrantes da esquerda se referem a tipagem baseada na estrutura do objeto - isto é, dos métodos oferecidos pelo objeto, independente do nome de sua classe ou superclasses; os quadrantes da direita dependem dos objetos terem tipos explicitamente nomeados: o nome da classe do objeto, ou o nome de suas superclasses. -image::images/flpy_1301.png[Quatro abordagens para verificação de tipo] - -Cada uma dessas quatro abordagens dependem de interfaces para funcionarem, -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 duck typing, goose typing (_tipagem ganso_), e duck typing 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. (<>): - -* <> compara duas formas de tipagem estrutural com protocolos - isto é, o lado esquerdo do Mapa. - -* <> se aprofunda no já familiar duck typing do Python, incluindo como fazê-lo mais seguro e ao mesmo tempo preservar sua melhor qualidade: a flexibilidade. -* <> explica o uso de ABCs para um 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 duck typing, duck typing estático e tipagem estática em outras partes do livro. -* <> cobre o uso, a implementação e o design de subclasses de `typing.Protocol` — úteis para checagem de tipo estática e durante a execução. - - -=== Novidades nesse capítulo - -Este((("interfaces", "significant changes to")))((("protocols", "significant changes to"))) capítulo foi bastante modificado, e é 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. -* <> explica as semelhanças e diferenças entre protocolos dinâmicos e estáticos. -* <> praticamente reproduz o conteúdo da primeira edição, mas foi atualizada e agora tem um título de seção que enfatiza sua importância. -* <> é toda nova. Ela se apoia na apresentação inicial em <> (<>). -* Os diagramas de classe de `collections.abc` nas Figuras pass:[#sequence_uml_repeat], pass:[#mutablesequence_uml], and pass:[#collections_uml] foram atualizados para incluir a `Collection` ABC, do Python 3.6. - -A primeira edição de _Python Fluente_ tinha uma seção encorajando o uso das ABCs `numbers` para goose typing. -Na <> eu explico porque, em vez disso, você deve usar protocolos numéricos estáticos do módulo `typing` se você planeja usar verificadores de tipo estáticos, ou checagem durante a execução no estilo da goose typing. - - -[[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, tais como `GET`, `PUT` e `HEAD`. - -Vimos na <> que um objeto protocolo especifica métodos que um objeto precisa oferecer para cumprir um papel. - -O exemplo `FrenchDeck` no <> demonstra um objeto 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, pycon] ----- ->>> 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 especial `+__getitem__+` é de fato o ponto central do protocolo de sequência. - -Veja essa parte do pass:[Manual de referência da API Python/C], https://docs.python.org/pt-br/3/c-api/sequence.html["Seção Protocolo de Sequência"]: - -`int PySequence_Check(PyObject *o)`:: - Retorna `1` se o objeto oferecer o protocolo de sequência, caso contrário retorna `0`. - Observe que ela 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 que 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 do 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)] (EN) no Python 3.8, a palavra "protocolo" ganhou um novo sentido em Python - um sentido próximo, mas diferente. -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 verificador de tipo estático. - -Quando precisar ser específico, vou adotar os seguintes termos: - -Protocolo dinâmico:: - Os((("dynamic protocols"))) protocolos informais que o 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 do Python são mantidos pelo próprio interpretador, e documentados no - https://docs.python.org/pt-br/3/reference/datamodel.html[capítulo "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: Structural subtyping (static duck typing)], a partir do Python 3.8. - Um protocolo estático tem um definição explícita: 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 se seu programa não precise de todos eles. -* Protocolos estáticos podem ser inspecionados por verificadores de tipo estáticos, protocolos dinâmicos não. - -Os dois tipos de protocolo compartilham um característica essencial, uma classe nunca precisa declarar que suporta um protocolo pelo nome, isto é, por herança. - -Além de protocolos estáticos, o Python também oferece 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]] -==== O Python curte sequências - -A filosofia do Modelo de Dados do Python é cooperar o máximo possível com os protocolos dinâmicos essenciais. -Quando se trata de sequências, o Python faz de tudo para lidar mesmo com as mais simples implementações. - -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 do Python 3.6, não existia uma ABC `Collection` - `Sequence` era uma subclasse direta de `Container`, `Iterable` e `Sized`. -image::images/flpy_1302.png[UML class diagram for `Sequence`] - -[TIP] -==== -A maior parte das ABCs no módulo `collections.abc` existem para formalizar interfaces que são implementadas por objetos nativos e são implicitamente suportadas pelo interpretador - objetos e suporte que existem desde antes das próprias ABCs. -As ABCs são úteis como pontos de partida para novas classes, e para permitir checagem de tipo explícita durante a execução (também conhecida como _goose typing_), bem como para servirem de dicas de tipo para verificadores de tipo estáticos. -==== - -Estudando a <>, vemos que uma subclasse correta 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__+`. - -Não há um método `+__iter__+`, mas as instâncias de `Vowels` são iteráveis porque - como alternativa - se o Python encontra um método `+__getitem__+`, tenta iterar sobre o object chamando aquele método com índices inteiros começando de `0`. -Da mesma forma que o 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 de estruturas como a sequência, o 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 de <> 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 <>) -==== -[source, python3] ----- -include::code/01-data-model/frenchdeck.py[] ----- -==== - -Muitos dos exemplos no <> funcionam por causa do tratamento especial que o Python dá a qualquer estrutura vagamente semelhante a uma sequência. -O protocolo iterável em Python representa uma forma extrema de duck typing: -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 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 outro exemplo que enfatiza a natureza dinâmica dos protocolos - e mostra porque verificadores de tipo estáticos não tem como lidar com eles.((("", startref="Pseqit13")))((("", startref="seqpro13")))((("", startref="itpro13"))) - - -==== Monkey patching: Implementando um Protocolo durante a Execução - -_Monkey patching_((("protocols", "implementing at runtime", id="Prun13")))((("monkey-patching", id="monkey13"))) é a ação de modificar dinamicamente um módulo, uma classe ou uma função durante a execução do código, para acrescentar funcionalidade ou corrigir bugs. -Por exemplo, a biblioteca de rede gevent faz um "monkey patch" em partes da biblioteca padrão do Python, para permitir concorrência com baixo impacto, sem threads ou `async`/`await`.footnote:[O artigo https://fpy.li/13-4["Monkey patch"] (EN) na Wikipedia tem um exemplo engraçado em Python.] -// Because it does not change the source code like a regular patch, -// a monkey patch only affects the currently running instance of the program. -// The https://fpy.li/13-5[_gevent_] networking library -// monkey patches parts of Python's standard library to allow lightweight concurrency without threads or `async/await`. -// Be aware that monkey patches depend on implementation details of the patched code, -// so they can easily break when libraries are updated. - -A classe `FrenchDeck` do <> não tem uma funcionalidade essencial: 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` age como uma sequência, então ele não precisa de seu próprio método `shuffle`, pois já existe um `random.shuffle`, -https://docs.python.org/pt-br/3/library/random.html#random.shuffle[documentado] como "Embaralha a sequência x internamente." - -A função `random.shuffle` padrão é usada assim: - -[source, pycon] ----- ->>> from random import shuffle ->>> l = list(range(10)) ->>> shuffle(l) ->>> l -[5, 2, 9, 7, 8, 3, 1, 4, 0, 6] ----- - -[TIP] -==== -Quando você segue protocolos estabelecidos, -você melhora suas chances de aproveitar o código já existente na -biblioteca padrão e em bibliotecas de terceiros, graças ao duck typing. -==== - -Entretanto, se tentamos usar shuffle com uma instância de `FrenchDeck` -ocorre uma exceção, como visto no <>. - -[[ex_unshuffable]] -.`random.shuffle` cannot handle `FrenchDeck` -==== -[source, pycon] ----- ->>> 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, e `FrenchDeck` só implementa o protocolo de sequência _imutável_. -Sequências mutáveis precisam também oferecer um método `+__setitem__+`. - -Como o 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, pycon] ----- ->>> 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 _A Referência da Linguagem Python_ em https://docs.python.org/pt-br/3/reference/datamodel.html#emulating-container-types["3.3.6. Emulando de 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 [.keep-together]#nomear# o primeiro argumento `self` é só uma convenção. -Isso está bom para uma sessão no console, mas em um arquivo de código-fonte de Python é muito melhor usar `self`, `key`, e `value`, seguindo a [.keep-together]#documentação#. - -O truque é que `set_card` sabe que o `deck` tem um atributo chamado `_cards`, -e `_cards` tem que ser uma sequência mutável. -A função `set_cards` é então anexada à classe `FrenchDeck` class 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 finte. -O "monkey patching" é poderoso, mas o código que efetivamente executa a modificação está muito intimamente ligado 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 no duck typing dinâmico: -`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. - -O duck typing não precisa ser loucamente inseguro 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 verificações explícitas.((("", startref="monkey13")))((("", startref="Prun13"))) - -[[defensive_duck_prog_sec]] -==== Programação defensiva e "falhe rápido" - -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 quando defrontando programadores (ou motoristas) negligentes. - -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 rápido" é um conselho excelente para gerar programas mais seguros e mais fáceis de manter. -Falhar rápido significa provocar erros de tempo de execução o mais cedo possível. Por exemplo, rejeitando argumentos inválidos no início do corpo de uma função. - -Aqui está um exemplo: -quando você escreve código que aceita uma sequência de itens para processar internamente como uma `list`, não imponha um argumento `list` através de 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 <>, -visto mais à frente nesse capítulo: - -[source, python3] ----- - def __init__(self, iterable): - self._balls = list(iterable) ----- - -Dessa 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 rapidamente com uma exceção de `TypeError` bastante clara, no exato momento em que o objeto for inicializado. -Se você quiser ser mais explícito, pode envelopar a chamada a `list()` em um `try/except`, para adequar a mensagem de erro - mas eu usaria aquele código extra apenas em uma API externa, pois o problema ficaria mais visível para os mantenedores da 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 primeira do problema 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, por projeto, espera que os itens sejam modificados internamente, como no caso de `random.shuffle`. -Neste caso, uma verificação durante a execução como `isinstance(x, abc.MutableSequence)` seria a melhor opção, - -Se você estiver com receio de produzir 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 gerará imediatamente um erro. - -Por outro lado, se um 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á rapidamente com um 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` é _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 verificador de tipo se torna inútil. -Além disso, dicas de tipo não são aplicadas durante a execução. -Falhar rápido é a última linha de defesa. - -Código defensivo usando tipos "duck" também podem incluir lógica para lidar com tipos diferentes sem usar testes com `isinstance()` e `hasattr()`. - -Um exemplo é como poderíamos emular o modo como https://fpy.li/13-8[`collections.namedtuple`] lida com o argumento `field_names`: -`field_names` aceita um única string, com identificadores separados por espaços ou vírgulas, ou uma sequência de identificadores. -O <> mostra como eu faria isso usando duck typing. - -[[ex_duck_typing_str_list]] -.Duck typing para lidar com uma string ou um iterável de strings -==== -[source, python3] ----- - 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 (MFPP - mais fácil pedir perdão que permissão). -<2> Converte vírgulas em espaços e divide o resultado em uma lista de nomes. -<3> Desculpe, `field_names` não grasna como uma `str`: não tem `.replace`, ou retorna algo que não conseguimos passar para `.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. -<6> Usamos `str.isidentifier` para se assegurar que todos os nomes são válidos. - -O <> mostra uma situação onde o duck typing é mais expressivo que dicas de tipo estáticas. -Não há como escrever uma dica de tipo que diga "`field_names` deve ser uma string de identificadores separados por espaços ou vírgulas." -Essa é a parte relevante da assinatura de `namedtuple` no typeshed -(veja o código-fonte completo em pass:[stdlib/3/collections/__init__.pyi]): - -[source, python3] ----- - def namedtuple( - typename: str, - field_names: Union[str, Iterable[str]], - *, - # rest of signature omitted ----- - -Como se vê, `field_names` está anotado como `Union[str, Iterable[str]]`, que serve para seus propósitos, mas não é suficiente para evitar todos os problemas possíveis. - -Após revisar protocolos dinâmicos, passamos para uma forma mais explícita de checagem de tipo durante a execução: goose typing.((("", startref="Pdefens13")))((("", startref="failfast13")))((("", startref="defprog13"))) - -[[goose_typing_sec]] -=== Goose typing - -++++ -
-

Uma classe abstrata representa uma interface.

-

Bjarne Stroustrup, criador do C++. Bjarne Stroustrup, The Design and Evolution of C++, p. 278 (Addison-Wesley).

-
-++++ -// [quote, Bjarne Stroustrup, creator of C++] -// ____ -// An abstract class represents an interface.footnote:[Bjarne Stroustrup, _The Design and Evolution of C++_ (Addison-Wesley, 1994), p. 278.] -// ____ - -O 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 passíveis de checagem explícita de tipo durante a execução - também suportado por verificadores de tipo estáticos. - - -O verbete para https://docs.python.org/pt-br/3/glossary.html#term-abstract-base-class[classe base abstrata] no Glossário da Documentação do Python tem uma boa explicação do valor dessas estruturas para linguagens que usam duck typing: - -[quote] -____ -Classes bases 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). CBAs 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`.footnote:[Consultada em 3 de março de 2023.] -____ - -A goose typing é uma abordagem à checagem de tipo durante a execução que se apoia nas ABCs. Vou deixar que Alex Martelli explique, no <>. - -[role="man-height-1-125"] -[NOTE] -==== -Eu sou muito agradecido a meus amigos Alex MArtekli e Anna Ravenscroft. -Mostrei a eles o primeiro rescunho do _Python Fluente_ na OSCON 2013, e eles me encorajaram a submeter à O'Reilly para publicação. -Mais tarde os dois contribuíram com revisões técnicas minuciosas. -Alex já era a pessoa mais citada nesse livro, e então se ofereceu para escrever esse ensaio. Segue daí, Alex! -==== - -[[waterfowl_essay]] -.Pássaros aquáticos e as ABCs -**** - -*By Alex Martelli* - -Eu recebi https://fpy.li/13-11[créditos na Wikipedia] por ter ajudado a popularizar o útil meme e a frase de efeito "_duck typing_" (isto é, ignorar o tipo efetivo 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 verificar o tipo do objeto (sem nem mencionar a abordagem ainda pior de verificar, 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 _duck typing_ continua muito útil em inúmeros contextos - mas em muitos outros, um nova abordagem muitas vezes preferível evoluiu ao longo do tempo. E aqui começa nossa história... - -Em gerações recentes, a taxinomia 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 focalizada nas similaridades de morfologia e comportamento... principalmente traços _observáveis_. A analogia com o "duck typing" era patente. - -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 o [seguinte] exemplo clássico de programação orientada a objetos: - -[source, python3] ----- -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`, chamado sem argumentos, [.keep-together]#está longe# de ser suficiente para garantir que dois objetos `x` e `y`, da forma como `x.draw()` e `y.draw()` podem ser chamados, são de qualquer forma intercambiáveis ou abstratamente pass:[equivalentes — nada] sobre a similaridade da semântica resultante de tais chamadas pode ser inferido. Na verdade, é necessário um programador inteligente para, de alguma forma, _assegurar_ positivamente 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 taxinô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 [.keep-together]#na subfamília# Tadornidae (sugerindo que eles são mais próximos entre si 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 [.keep-together]#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 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 muito mais relevantes que a cladística. Mas para outros problemas, tal como a suscetibilidade a diferentes patógenos (se você estiver tentando criar aves aquáticas em cativeiro, ou preservá-las na natureza), a proximidade do DNA por ser muito mais crucial. - -Então, a partir dessa analogia bem frouxa 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á) a boa e velha _duck typing_ com... a _goose typing_ (tipagem ganso)! - -A _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 do 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 eu os desencorajaria de definir suas próprias metaclasses personalizadas... 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 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 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., Scott Meyer’s “toda classe não-final ("não-folha") deveria ser abstrata”; veja o https://fpy.li/13-12[Item 33] de seu livro, _More_ [.keep-together]#_Effective_# pass:[C++], Addison-Wesley), as ABCs do Python acrescentam uma grande vantagem prática: o método de classe `register`, que permite ao código do usuário final "declarar" que determinada classe é uma subclasse "virtual" de uma ABC (para este propósito, a classe registrada precisa cumprir os requerimentos 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 muito mais cautela que aquela tipicamente praticada pela maioria do programadores orientados a .[.keep-together]#objetos#. - -Em algumas ocasiões você sequer precisa registrar uma classe para que uma ABC a reconheça como uma [.keep-together]#subclasse#! - -Esse é o caso para as ABCs cuja essência se resume em alguns métodos especiais. Por exemplo: - -[source, pycon] ----- ->>> 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 muitos maiores que esses). - -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 outra framework que estiver usando, se assegure (caso necessário) de ser uma subclasse ou de registrar sua classe com a ABC correspondente. No início de seu programa usando uma biblioteca ou framework que definam classes que omitiram esse passo, registre você mesmo as classes. Daí, quando precisar verificar se (tipicamente) um argumento é, por exemplo, "uma sequência", verifique se: -[source, python3] ----- -isinstance(the_arg, collections.abc.Sequence) ----- - -E _não_ defina ABCs personalizadas (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 muito mais felizes se limitando a código simples e direto, e evitando((("", startref="GTabcs13")))((("", startref="ABCgoose13"))) tais profundezas. _Valē!_ - -**** - -[role="pagebreak-before less_space"] -Em((("goose typing", "overview of"))) resumo, _goose typing_ 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 em <>, mais adiante nesse mesmo capítulo. -Por hora, aqui está um pequeno exemplo: dada a classe `FrenchDeck`, se eu quiser que ela passe em uma verificação como `(FrenchDeck, Sequence)`, posso torná-la uma _subclasse virtual_ da ABC `Sequence` com as seguintes linhas: - -[source, python3] ----- -from collections.abc import Sequence -Sequence.register(FrenchDeck) ----- -==== - -O uso de `isinstance` e `issubclass` se torna mais aceitável se você está verificando ABCs em vez de classes concretas. -Se usadas com classes concretas, verificações 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 verificações de tipo explícitas. - -Entretanto, mesmo com ABCs, você deve se precaver contra o uso excessivo de verificações com `isinstance`, pois isso poder ser um ((("code smells"))) __code smell__— sintoma de um design ruim. - -Normalmente _não_ é bom ter uma série de `if/elif/elif` com verificações de `isinstance` executando ações diferentes, dependendo do tipo de objeto: nesse caso você deveria estar usando polimorfismo - isto é, projetando suas classes para permitir ao interpretador enviar chamadas para os métodos corretos, em vez de codificar diretamente a lógica de envio em blocos `if/elif/elif`. - -Por outro lado, não há problema em executar uma verificação com `isinstance` contra uma ABC se você quer garantir um contrato de API: -"Cara, você tem que implementar isso se quiser me chamar," como costuma dizer o revisor técnico Lennart Regebro. -Isso é especialmente útil em sistemas com arquitetura plug-in. -Fora das frameworks, duck typing é muitas vezes mais simples e flexível que verificações de tipo. - -Por fim, em seu ensaio Alex reforça mais de uma vez a necessidade de coibir 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 email: - -[quote] -____ -ABCs servem para encapsular conceitos muito genéricos, abstrações, introduzidos por uma 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 goose typing 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, python3] ----- -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]. - -O 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 nós tentamos de fato instanciar `FrenchDeck2`. -Ali, se deixamos de implementar qualquer [.keep-together]#dos# métodos abstratos, -recebemos uma exceção de `TypeError` com uma mensagem como -pass:["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, 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[Diagrama de classe UML para `Sequence` e `MutableSequence`] - -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, é possível sobrepor os métodos 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"] (EN) -em pass:[fluentpython.com] para conhecer mais sobre esse método. -==== -// (see <>). - -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"))) o Python 2.6, a biblioteca padrão oferece várias ABCs. A maioria está definida no módulo `collections.abc`, mas há outras. -Você pode encontrar ABCs nos pacotes `io` e `numbers`, por exemplo. -Mas a maioria das mais usadas estão em `collections.abc`. - -[TIP] -==== -Há dois módulos chamados `abc` na biblioteca padrão. Aqui nós 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. -==== - -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`. -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 <> à 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.] - -[[collections_uml]] -.Diagrama de classes UML para as ABCs em `collections.abc`. -image::images/flpy_1304.png[UML for collections.abc] - -Vamos revisar os grupos em <>: - -`Iterable`, `Container`, `Sized`:: -Toda coleção deveria ou herdar dessas ABCs ou implementar protocolos compatíveis. -`Iterable` oferece iteração com `+__iter__+`, -`Container` oferece o operador `in` com `+__contains__+`, -e `Sized` oferece `len()` with `+__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 em <>; -para `MutableMapping` e `MutableSet`, veja as Figuras pass:[#mapping_uml] e -pass:[#set_uml] em <>. - -`MappingView`:: -No Python 3, os objetos retornados 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`. Discutimos melhor isso adiante, em <>. - -`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 [.keep-together]#serem# incluídas. -Elas suportam a verificação de tipo de objetos que precisam ser "chamáveis" ou [.keep-together]#hashable#. - -Para a detecção de 'callable', a função nativa `callable(obj)` é muito 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. -Isso é explicado no box seguinte. - -[[isinstance_mislead_box]] -.isinstance com Hashable e Iterable pode enganar você -**** -É 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 ou herda `+__hash__+`. -Mas se `obj` é uma tupla contendo itens _unhashable_, então `obj` não é _hashable_, apesar do resultado positivo da verificação com `isinstance`. -O revisor técnico Jürgen Gmach esclareceu que o _duck typing_ fornece a forma mais precisa 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 <> e na seção <>. -A documentação de -https://docs.python.org/pt-br/3/library/collections.abc.html#collections.abc.Iterable[`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 goose typing 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"))) - -[[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_: - -[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 tem 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 requerimentos é 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. - -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. - -A ABC vai ser chamada `Tombola`, seguindo o nome italiano do bingo (e do recipiente balançante 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. - -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). - -A <> mostra a ABC `Tombola` e três implementações concretas. - -[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 é usada para implementações de interface - as estou usando aqui para mostrar que `TomboList` implementa não apenas a interface `Tombola`, mas também está registrada como uma _subclasse virtual_ de `Tombola` - como veremos mais tarde nesse capítulo.pass:[«registrada» and «subclasse virtual» não são termos da UML padrão. Estão sendo usados para representar uma relação de classe específica do Python.] -image::images/flpy_1305.png[UML for Tombola] - -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. -==== -[source, py] ----- -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 sobreposto 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 na verdade pode ter uma implementação. -Mas mesmo que [.keep-together]#tenha#, as subclasses ainda são obrigadas a sobrepô-lo, mas poderão invocar o método abstrato com `super()`, -acrescentando funcionalidade em vez de implementar do zero. -Veja a https://docs.python.org/pt-br/3/library/abc.html[documentação do módulo `abc`] para os detalhes do uso de `@abstractmethod`. -==== - -O código para 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 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 sobrepor `.inspect()` com uma implementação mais adequada, 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 <>). - -Eu 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 personalizada de `LookupError` para atender à interface. -Veja a <>. - -[role="width-40"] -[[exc_tree_part]] -.Parte da hierarquia da classe `Exception`.footnote:[A árvore completa está na seção "5.4. Exception hierarchy" da documentação da _Biblioteca Padrão do Python.] -image::images/flpy_1306.png["Árvore de ponta cabeça, com BaseException no topo, e quatro ramos principais, incluindo Exception."] - - -➊ `LookupError` é a exceção que tratamos em `Tombola.inspect`. - -➋ `IndexError` é a subclasse de `LookupError` gerada quando tentamos acessar um item em uma sequência usando um índice além da última posição. - -➌ `KeyError` ocorre quando usamos uma chave inexistente para acessar um item em um mapeamento (`dict` etc.). - -//// -++++ -
-
1
-

LookupError é a exceção que tratamos em Tombola.inspect.

-
2
-

IndexError é a subclasse de LookupError levantada quando tentamos recuperar um item de uma sequência com um índice além da última posição.

-
3
-

KeyError acontece quando usamos uma chave inexistente para recuperar um item de um mapeamento.

-
-++++ -//// - -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, pycon] ----- ->>> 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`, and `@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, python3] ----- -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://docs.python.org/pt-br/dev/library/abc.html#abc.abstractmethod[`@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 o comando `def`. -==== - -Agora que abordamos essas questões de sintaxe das ABCs, vamos colocar `Tombola` em uso, implementando dois descendentes concretos dessa classe. - -==== Criando uma subclasse de ABC - -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. - -A classe `BingoCage` no <> é uma variação da <> 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, py] ----- -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://docs.python.org/pt-br/3/library/os.html#os.urandom[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. - -`BingoCage` herda o custoso método `loaded` e o tolo `inspect` de `Tombola`. -Ambos poderiam ser sobrepostos com métodos de uma linha muito 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. - -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.. - -[[ex_lotto]] -.lotto.py: `LottoBlower` é uma subclasse concreta que sobrecarrega os métodos `inspect` e `loaded` de `Tombola` -==== -[source, py] ----- -include::code/13-protocol-abc/lotto.py[tags=LOTTERY_BLOWER] ----- -==== -[role="pagebreak-before less_space"] -<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> Sobrepõe `loaded` para evitar a chamada a `inspect` (como `Tombola.loaded` faz no <>). Podemos fazer isso mais rápido rápida trabalhando diretamente com `self._balls` -- não há necessidade de criar toda uma nova `tuple`. -<5> Sobrepõe `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:[<> em <> foi dedicado à questão de apelidamento que acabamos de evitar aqui.] - -Chegamos agora à característica dinâmica crucial da goose typing: -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 goose typing - 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 o 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. - -[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 verificadores de tipo 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 <>), -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 <>. - -[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[UML for TomboList] - -[[ex_tombolist]] -.tombolist.py: a classe `TomboList` é uma subclasse virtual de `Tombola` -==== -[source, python3] ----- -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. -<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://docs.python.org/pt-br/3/library/stdtypes.html#truth["4.1. Teste do Valor Verdade"] no capítulo "Tipos Embutidos" da documentação do 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`: - -[source, pycon] ----- ->>> 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__+`—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 o 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`: - -[source, pycon] ----- ->>> 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 do 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: - -[source, python3] ----- -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 verificar 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 verificações 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 `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`. - -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 <>.] -O duck typing estático e o dinâmico 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`: - -[source, pycon] ----- ->>> 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 <>. - -[[sized_source_code]] -.Definição de `Sized` no código-fonte de https://fpy.li/13-25[Lib/_collections_abc.py] -==== -[source, python3] ----- -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> ...retorna `True`, sinalizando que `C` é uma subclasse virtual de `Sized`. -<3> Caso contrário retorna `NotImplemented`, para permitir que a verificação de subclasse continue. - -[NOTE] -==== -Se você tiver interesse nos detalhes da verificação 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. -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 `isinstance` verificar com a ABC, -e ainda ter um classe sem qualquer relação passando uma verificação 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 eu vi no código-fonte do 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"))) - - -[[static_protocols_sec]] -=== Protocolos estáticos - -[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 o duck typing é uma parte essencial do Python, -e verificação de tipo estática sem protocolos não consegue lidar muito bem com as 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 verificar 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: - -[source, pycon] ----- ->>> 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:[Certo, double()` não é muito útil, exceto como um exemplo. Mas a biblioteca padrão do 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. Eu 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`.] - - -Graças ao duck typing, `double` funciona mesmo com tipos do futuro, tal como a classe `Vector` aprimorada que veremos no <> (<>): - -[source, pycon] ----- ->>> 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 duck typing 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, py] ----- -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 `repeat_count` está limitado nesse protocolo a `int`. -<3> A variável de tipo `RT` é vinculada pelo protocolo `Repeatable`: -o verificador de tipo vai exigir que o tipo efetivo implemente `Repeatable`. -<4> Agora o verificador de tipo pode verificar 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 (duck typing estático))." -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"))) - - -[[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 verificação 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 verificações 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`]: - -`class typing.SupportsComplex`:: - An ABC with one abstract method, `+__complex__+`. - ("Uma 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__+`.") - -Esse((("numeric types", "checking for convertibility"))) protocolos foram projetados para verificar 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()`#. - -<> mostra o -https://fpy.li/13-31[código-fonte] -do protocolo `typing.SupportsComplex`. - -[[supportscomplex_ex]] -.código-fonte do protocolo `typing.SupportsComplex` -==== -[source, python3] ----- -@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 verificações com `isinstance` no <>. - -[[repeatable_protocol_demo_ex]] -.Usando `SupportsComplex` durante a execução -==== -[source, pycon] ----- ->>> 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 do 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`. - -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: - -[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` do NumPy são todos registrados como subclasses virtuais de `numbers.Complex`, então isso aqui 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 verificadores de tipo 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. - -[TIP] -==== -Se você estiver usando um verificador de tipo externo, há uma vantagem nas verificações explícitas com `isinstance`: -quando você escreve um comando `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`. -==== - -[[duck_typing_friend_box]] -.O Duck Typing É Seu Amigo -**** -Durante((("duck typing"))) a execução, muitas vezes o duck typing é a melhor abordagem para verificação de tipo: -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. Aqui está um exemplo concreto: - -Continuando a discussão anterior: dado um objeto `o` que eu 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 -else: - raise TypeError('o must be convertible to complex') ----- - -A abordagem da goose typing seria usar a ABC `numbers.Complex`: - -[source, python] ----- -if isinstance(o, numbers.Complex): - # do something with `o`, an instance of `Complex` -else: - raise TypeError('o must be an instance of Complex') ----- - -Eu, entretanto, prefiro aproveitar o duck typing e fazer isso usando o princípio do MFDP - mais fácil pedir desculpas que permissão: - -[source, python] ----- -try: - c = complex(o) -except TypeError as exc: - raise TypeError('o 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: - -[source, python] ----- -c = complex(o) ----- - -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: - -[source] ----- -TypeError: complex() first argument must be a string or a number, not 'tuple' ("O primeiro argumento de complex() deve ser uma string ou um número, não 'tuple'") ----- - -Acho a abordagem duck typing muito melhor nesse 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 verificações 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 verificações com `isinstance` or `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`. - -Veja essa sessão no console: - -[source, pycon] ----- ->>> 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 <>. - -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_. - -Continuando da sessão anterior de Python 3.9: - -[source, pycon] ----- ->>> from typing import SupportsFloat ->>> c = 3+4j ->>> isinstance(c, SupportsFloat) -True ->>> issubclass(complex, SupportsFloat) -True ----- - -Então temos resultados enganosos: as verificações 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: -Verificações 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 verificação de tipo 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 verificar 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 verificação de tipo, mesmo em scripts Python curtos.] -==== - -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`. - -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`. - -[[ex_vector2d_complex_v4]] -._vector2d_v4.py_: métodos para conversão de e para `complex` -==== -[source, py] ----- -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: - -[source, pycon] ----- ->>> 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 verificação de tipo 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, py] ----- -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 verifica 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) do Python Steering Council no 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 goose typing, 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 o duck typing estático 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 <>. - -[[ex_randompick_protocol]] -._randompick.py_: definition of `RandomPicker` -==== -[source, python3] ----- -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`. -==== - -[[ex_randompick_protocol_demo]] -._randompick_test.py_: `RandomPicker` em uso -==== -[source, python3] ----- -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 é o duck typing estático 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 nós só conseguimos chamá-la de dentro de blocos `if` protegidos por `typing.TYPE_CHECKING`, que só é `True` para os olhos de um verificador de tipo estático, mas é `False` durante a execução. - -Os dois testes em <> passam. -O Mypy também não vê nenhum 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 duck typing estático em Go, está claro que protocolos estreitos são mais úteis - muitas vezes tais protocolos tem 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://pt.wikipedia.org/wiki/Princ%C3%ADpio_da_segrega%C3%A7%C3%A3o_de_interface[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 - -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. -Muitas das interfaces Go mais usadas tem 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. - -[[ex_randompickload_protocol]] -._randompickload.py_: estendendo `RandomPicker` -==== -[source, python3] ----- -include::code/13-protocol-abc/typing/randompickload.py[] ----- -==== -<1> Se você quer que o protocolo derivado possa ser verificado 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. - -Para encerrar o capítulo, 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 novod 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 verificação de tipo durante a execução. - -Se você precisa verificar 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, o 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`. - -[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). -==== - -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`. - -Se as ABCs de `numbers` não tem suporte, quais as opções? - -Um bom lugar para procurar soluções de tipagem é no projeto _typeshed_. -Como parte da biblioteca padrão do 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: - -[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`, -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 do NumPy implementam métodos `+__float__+` e `+__int__+` que funcionam, -emitindo apenas um aviso quando cada um deles é usado pela primeira vez: - -[source, pycon] ----- ->>> 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`, 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 o NumPy oferece.] -Os tipo complexos do 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, pycon] ----- ->>> 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 verificações 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. - -Por outro lado, o Mypy aceita argumentos de todos esses seis tipos em uma chamada à função [.keep-together]#`to_complex()`#, definida assim: - -[source, python3] ----- -def to_complex(n: SupportsComplex) -> complex: - return complex(n) ----- - -No momento em que escrevo isso, o NumPy não tem dicas de tipo, então seus tipos numéricos são todos `Any`.footnote:[Os tipos numéricos do 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 do 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 verificação de tipo 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 verificadores de tipo 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__+`. - -[TIP] -==== -Eu só encontrei resultados inesperados usando verificações 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 verificação de tipo 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 verificação de tipo 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"))) - - -=== 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 duck typing e duck typing estático, 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 duck typing terminou com sugestões de programação defensiva, -incluindo a detecção de tipos estruturais sem verificações explícitas com `isinstance` ou `hasattr`, usando `try/except` e falhando rápido. - -Após Alex Martelli introduzir o goose typing em <>, -vimos como criar subclasses de ABCs existentes, -examinamos algumas ABCs importantes da biblioteca padrão, -e criamos uma ABC do zero, -que nós 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 do duck typing estático, 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 verificadores de tipo 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, o Python ganhou sua enorme popularidade enquanto suportava apenas duck typing. -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 do duck typing. - -[[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 verificadas 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"] (EN).("Eu Quero Um Novo Pato: `typing.Protocol` e o futuro do duck typing`"). -Eu 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 na 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".] - -Ótimos livros sobre Python tem - quase que por definição - uma ótima cobertura de duck typing. -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. -Ele também escreveu -https://fpy.li/13-33["Role Interface"] (EN) ("Interface Papel"), -que mencionei na <>. -Apesar de não ser sobre duck typing, -aquele post é altamente relevante para o projeto de protocolos em Python, -pois ele contrasta as estreitas interfaces papel 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 coisa relacionada a tipagem estática em Python, incluindo duck typing estático, tratado no capítulo -https://fpy.li/13-50["Protocols and structural subtyping"] (EN) ("Protocolos e subtipagem estrutural"). - -As referências restantes são todas sobre goose typing. - -Beazley and Jones's pass:[Python Cookbook], 3rd ed. (O'Reilly) -tem uma seção sobre como definir uma ABC (Recipe 8.12). -O livro foi escrito antes do 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 nós 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, no excelente site do Doug https://fpy.li/13-51[_PyMOTW_—Python Module of the Week] (EN). -Hellmann também usa a declaração de ABC no estilo antigo:`PluginBase(metaclass=abc.ABCMeta)` em vez do mais simples `PluginBase(abc.ABC)`, disponível 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`, e `Set`— estendem `Collection`, que por sua vez estende múltiplas ABCs -(veja <>). Assim, <> é um importante tópico complementar a esse. - -A https://fpy.li/13-52[PEP 3119--Introducing Abstract Base Classes] (EN) -apresenta a justificativa para as ABCs. A https://fpy.li/13-53[PEP 3141--A Type Hierarchy for Numbers] (EN) -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 verificação 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 o goose typing compatíveis, como eles 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 PMV da tipagem estática em Python - -Eu((("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"))) trabalho para a Thoughtworks, uma líder global em desenvolvimento de software ágil. -Na Thoughtworks, muitas vezes recomendamos a nossos clientes que procurem criar e implanta PMVs: produtos mínimos viáveis, "uma versão simples de um produto, que é disponibilizada para os usuários com o objetivo de validar hipóteses centrais do negócio," como definido or meu colega Paulo Caroli in https://fpy.li/13-58["Lean Inception"], -um post no https://fpy.li/13-59[Martin Fowler's collective blog]. - -Guido van Rossum e os outros core developers que projetaram e implementaram a tipagem estática tem seguido a estratégia do PMV 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. -Isso foi feito para explicitamente permitir experimentação e receber feedback - os principais benefícios de um PMV. - -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 verificação estática efetiva 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 PMV de tipagem foi bem sucedida 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 do 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], 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 este autor - ficaram desapontados com o suporte à tipagem. Após aprender Go, a ausência de duck typing estático em Python era incompreensível, em uma linguagem onde o duck typing havia sempre sido uma força central. - -Mas essa é a natureza dos PMVs: 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 o 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 o Python mais atrativo par 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 os nomes de algumas linguagem populares que suportam cada um dos modos de tipagem. - -[[type_systems_languages]] -.Quatro abordagens para verificação de tipo e algumas linguagens que as usam. -image::images/flpy_1308.png[Quatro abordagens para verificação de tipo e algumas linguagens que as usam.] - -TypeScript e o Python ≥ 3.8 são as únicas linguagem em minha pequena e arbitrária amostra que suportam todas as quatro abordagens. - -Go é claramente uma linguagem de tipo estáticos na tradição do Pascal, -mas ela foi a pioneira do duck typing estático - pelo menos entre as linguagens mais usadas hoje. -Eu também coloquei Go no quadrante do goose typing por causa de suas declarações (_assertions_) de tipo, que permitem a verificação e adaptação a diferentes tipos durante a execução. - -Se eu tivesse que desenhar um diagrama similar no ano 2000, apenas os quadrantes do duck typing e da tipagem estática teriam linguagens. -Não conheço nenhuma linguagem que suportava duck typing estático ou goose typing 20 anos atrás. -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, o Python não permite modificações de tipos embutidos durante a execução. Eu na verdade considero isso uma vantagem, pois dá a certeza que um objeto `str` vai sempre ter os mesmos métodos. -Essa limitação reduz a chance de bibliotecas externas aplicarem correções conflitantes. - -[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): - -[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. - -_About Face_ é, de longe, o melhor livro sobre design de UI que eu 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"))) - -**** diff --git a/capitulos/cap14.adoc b/capitulos/cap14.adoc deleted file mode 100644 index 3a4b6734..00000000 --- a/capitulos/cap14.adoc +++ /dev/null @@ -1,982 +0,0 @@ -:xrefstyle: short -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo - -[[inheritance]] -== Herança: para o bem ou para o mal - -[quote, Alan Kay, Os Primórdios do 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 tais como: especialização, generalização, especiação, etc.).footnote:[Alan Kay, "The Early History of Smalltalk" (_Os Primórdios do 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.] -____ - -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 do Python_], ou por experiências com outra linguagem orientada a objetos popular, tal como Java, C# ou C++. -Aqui vamos nos concentrar em quatro características do Python: - -* A função `super()` -* As 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 C++ 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 do Java, após seu aparente abuso nas primeiras bases de código C++. - -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. - -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. - -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. - -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. - -=== Novidades nesse 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. - -Escrevi uma nova seção de abertura, tratando especificamente da função embutida `super()`, e mudei os exemplos na seção <>, para explorar mais profundamente a forma como `super()` suporta a herança múltipla _cooperativa_. - -A seção <> também é nova. A seção <> 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. - -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()`. - - -=== 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. - -Quando uma subclasse sobrepõe 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`.] - -[source, py] ----- -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` sobrepõe `+__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__+` sobreposto é 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. O Python não faz isso. Se acostume a escrever o seguinte código padrão: - -[source, py] ----- - def __init__(self, a, b) : - super().__init__(a, b) - ... # more initialization code ----- -==== - -Você pode já ter visto código que não usa `super()`, e em vez disso chama o método na superclasse diretamente, assim: - -[source, py] ----- -class NotRecommended(OrderedDict): - """Isto é um contra-exemplo!""" - - def __setitem__(self, key, value): - OrderedDict.__setitem__(self, key, value) - self.move_to_end(key) ----- - -Essa alternativa até funciona nesse caso em particular, mas não é recomendado 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 seção <>. -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: - -[source, py] ----- -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 argumento de `super` são agora opcionais. -O compilador de bytecode do Python 3 obtém e fornece ambos examinando o contexto circundante, quando `super()` é invocado dentro de um método. -Os argumentos 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. - -`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"))) - - -[[subclass_builtin_woes]] -=== É complicado criar 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á 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_)]: - -[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. -____ - -O <> ilustra o problema. - -[[ex_doppeldict]] -.Nossa sobreposição de `+__setitem__+` é ignorado pelos métodos `+__init__+` e `+__update__+` to tipo embutido `dict` -==== -[source, pycon] ----- ->>> 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 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 do 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 -C++ diferencia métodos virtuais e não-virtuais. Métodos virtuais tem 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 seção <>. - -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). - -[[ex_other_subclass]] -.O `+__getitem__+` de `AnswerDict` é ignorado por `dict.update` -==== -[source, pycon] ----- ->>> 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` 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. -==== - -Se você criar uma subclasse 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, pycon] ----- ->>> 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 <>, 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 -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).] - -Vamos agora examinar uma questão que aparece na herança múltipla: -se uma classe tem duas superclasses, como o Python decide qual atributo usar quando invocamos `super().attr`, mas ambas as superclasses tem um atributo com esse nome?((("", startref="IASsubbuilt14"))) - -[[mro_section]] -=== 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 <>. - -[[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] - -[[ex_diamond]] -.diamond.py: classes `Leaf`, `A`, `B`, `Root` formam o grafo na <> -==== -[source, python3] ----- -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()`. - -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, python3] ----- -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: - -* 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: - -[source, pycon] ----- -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 sobrepõe 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. - -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. - -[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()`. -==== - -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. - -O 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, python3] ----- -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, pycon] ----- -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'`. - -Entretanto, o método `U.ping` não é inteiramente sem solução. Veja isso: - -[source, pycon] ----- -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)`, 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()`. - -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 seção <>. - -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 do 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, pycon] ----- ->>> 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 é 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 personalizando 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 C++. -O 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. -==== - -Vamos ver um exemplo simples mas conveniente 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, python3] ----- -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. -O <> apresenta dois exemplos. - -[[ex_upperdict]] -.uppermixin.py: duas classes que usam `UpperCaseMixin` -==== -[source, python3] ----- -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`. -<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. -//// - -Aqui estão alguns doctests de -https://fpy.li/14-11[_uppermixin.py_], para `UpperDict`: - -[source, pycon] ----- -include::code/14-inheritance/uppermixin.py[tags=UPPERDICT_DEMO] ----- - -E uma rápida demonstração de `UpperCounter`: - -[source, pycon] ----- -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. - -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 seção <>. -É 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"))) - -[[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 C++, 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. - -==== ABCs também são mixins - -Na((("collections.abc module", "multiple inheritance in")))((("mixin methods"))) biblioteca padrão do 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 do Java.] - -A documentação oficial do 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`. - -==== 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: - -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. - -Este é 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, python3] ----- -class ThreadingMixIn: - """Mixin class to handle each request in a new thread.""" - - # 8 lines omitted in book listing - - def process_request_thread(self, request, client_address): # <1> - ... # 6 lines omitted in book listing - - def process_request(self, request, client_address): # <2> - ... # 8 lines omitted in book listing - - 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 sobrepõe 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]. - - - -[[django_cbv_sec]] -==== Mixins de views genéricas no Django - -[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. - -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`.# - -[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`.] - -[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. - -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`. - -[[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"))) - - -==== 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 do 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`]). - -[[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. -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. - -➋ `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, pycon] ----- ->>> 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 é muito profunda. Poucas partes da bilbioteca padrão do Python tem mais que três ou quatro níveis de classes concretas, e o mesmo pode ser dito da biblioteca de classes do Java. -Entretanto, é interessante observar que algumas das hierarquias mais profundas da biblioteca de classes do 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 do 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. -* `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"))) - - -[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. - -É 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. - -[[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. - -==== 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. 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.] -____ - -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, python3] ----- -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 verdade 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. - -Observe que classes agregadas não precisam ser inteiramente vazias (mas frequentemente são). - - -==== Só crie subclasses de classes criadas 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 sobrepor métodos de qualquer classe complexa é um processo muito suscetível a erros, porque os métodos da superclasse podem ignorar as sobreposições da subclasse de formas inesperadas. Sempre que possível, evite sobrepor métodos, ou pelo menos se limite 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://docs.python.org/pt-br/3/library/socketserver.html[`socketserver`] (EN) do Python é descrito como "um framework para servidores de rede". -Sua classe https://docs.python.org/pt-br/3/library/socketserver.html#socketserver.BaseServer[`BaseServer`] (EN) 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] (EN) no código-fonte da classe informa explicitamente quais de seus métodos foram criados para serem sobrepostos 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_)] (EN). -A PEP introduz um decorador https://docs.python.org/pt-br/3/library/typing.html#typing.final[`@final`], que pode ser aplicado a classes ou a métodos individuais, de forma que IDEs ou verificadores de tipo podem identificar tentativas equivocadas de criar subclasses daquelas classes ou de sobrepor aqueles métodos.footnote:[A PEP 591 também introduz uma anotação https://docs.python.org/pt-br/3/library/typing.html#typing.Final[`Final`] para variáveis e atributos que não devem ser reatribuídos ou sobrepostos.] - - -[role="pagebreak-before less_space"] -==== 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 tem um estado interno, que pode ser facilmente corrompido se sobrepusermos métodos que dependem daquele estado. -Mesmo se nossos métodos cooperarem chamando `super()`, e o estado interno seja mantido através da sintaxe `__x`, restarão ainda inúmeras formas pelas quais a sobreposição de um método pode introduzir bugs. - -No <>, Alex Martelli cita _More Effective C++_, 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 seção <>. - -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 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. - -[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. -==== - -Aqui termina nossa viagem através do labirinto da herança.((("", startref="IAScop14"))) - -[[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` personalizados, é 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 do 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 verificadas 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: as frameworks que usamos nos impõe suas escolhas de design. - -[[inheritance_further_reading]] -=== Leitura complementar - -[quote, Hynek Schlawack, "Subclassing in Python Redux"] -____ -No que diz respeito à legibilidade, composição feita de forma adequada é superior a herança. Como é muito 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. - -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. -O 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() do 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 do Python é bacana, mas você não deve usá-lo (Antes: Super do 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 do 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. -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): -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 do Python_)] (EN), -https://fpy.li/14-47[part 2] (EN), e https://fpy.li/14-48[part 3] (EN). -Os posts mais antigos usam a sintaxe de `super` do 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 o Python. - -.Soapbox -**** - -[role="soapbox-title"] -Pense nas classes realmente necessárias - -[quote, Alan Kay, The Early History of Smalltalk ("Os Primórdios do 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 do 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: - -* 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 nós 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 o 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 C++, e esse recurso foi abusado o suficiente para que o Java—criado para ser um substituto do C++—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 C++ e em Python. -Depois do Java, a linguagem da JVM mais usada é provavelmente o Scala, que implementa _traits_. - -Outras linguagens que suportam _traits_ são a última versão estável do PHP e do Groovy, -bem como o Rust e o 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. - -O 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 seção <>.((("", startref="IASsoap14"))) - -**** diff --git a/capitulos/cap15.adoc b/capitulos/cap15.adoc deleted file mode 100644 index 8beceaa7..00000000 --- a/capitulos/cap15.adoc +++ /dev/null @@ -1,1595 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte -[[more_types_ch]] -== Mais dicas de tipo - -[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).] -____ - - -Esse((("gradual type system", "topics covered"))) capítulo é uma continuação do <>, e fala mais sobre o sistema de tipagem gradual do Python. -Os tópicos principais são: - -* Assinaturas de funções sobrepostas -* `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 nesse 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. - -[[overload_sec]] -=== Assinaturas sobrepostas - -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. - -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. - -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"]: - -[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 - - 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. ----- - -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_]: - -[source, python3] ----- -@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 sobreposições. -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`. -Assim os arquivos _.pyi_ são arquivos Python válidos. - -Como mencionado na seção <>, os dois sublinhados prefixando `+__iterable+` são a convenção da PEP 484 para argumentos apenas posicionais, que é verificada pelo Mypy. -Isso significa que você pode invocar `sum(my_list)`, mas não `sum(__iterable = my_list)`. - -O verificador de tipo 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. - -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. - -[[sum_overload_ex]] -._mysum.py_: definição da função `sum` com assinaturaas sobrepostas -==== -[source, python3] ----- -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. - -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`. - -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 do Python tem 186 sobreposições—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. -==== - -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`. - - -[[max_overload_sec]] -==== Sobreposição 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 do 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: - ----- -TypeError: '>' not supported between instances of 'int' and 'NoneType' - -[NT: TypeError: '>' 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 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`. - -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 <>. - -[[mymax_ex]] -._mymax.py_: Versão da funcão `max` em Python -==== -[source, python3] ----- -# 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 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. - -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`. - -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.] - -[[mymax_types_ex]] -._mymax.py_: início do módulo, com importações, definições e sobreposições -==== -[source, python3] ----- -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 verificação usando `isinstance`, e fornece a mesma verificação 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. - -===== Argumentos implementando SupportsLessThan, mas key e default não são fornecidos - -[source, python3] ----- -@overload -def max(__arg1: LT, __arg2: LT, *_args: LT, key: None = ...) -> LT: - ... -# ... lines omitted ... -@overload -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 seção <>. - -Amostras de chamadas que casam com essas sobreposições: - -[source, python3] ----- -max(1, 2, -3) # returns 2 -max(['Go', 'Python', 'Rust']) # returns 'Rust' ----- - -===== Argumento key fornecido, mas default não - -[source, python3] ----- -@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 sobreposições: - -[source, python3] ----- -max(1, 2, -3, key=abs) # returns -3 -max(['Go', 'Python', 'Rust'], key=len) # returns 'Python' ----- - -===== Argumento default fornecido, key não - -[source, python3] ----- -@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 sobreposições: - -[source, python3] ----- -max([1, 2, -3], default=0) # returns 2 -max([], default=None) # returns None ----- - - -===== Argumentos key e default fornecidos - -[source, python3] ----- -@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, python3] ----- -max([1, 2, -3], key=abs, default=None) # returns -3 -max([], key=abs, default=None) # returns None ----- - - - -==== Lições da sobreposição 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, ter de escrever tantas linhas para suportar o verificador de tipo 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. - -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 à do 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, tais 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 do JSON precisa acontecer durante a execução, e não com verificação estática de tipo. -Para verificar 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 do 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 do Python 3.8, não havia uma boa maneira de anotar um registro como esse, pois os tipos de mapeamento que vimos na seção <> 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, 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]`. - -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. - -[[bookdict_ex]] -._books.py_: a definição de `BookDict` -==== -[source, py] ----- -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 verificadores de tipo, 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 verificador de tipo para 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çao 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. - -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, pycon] ----- ->>> 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> Oops...Esqueci que `authors` deve ser uma lista. Mas tipagem gradual significa que não há checagem de tipo 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 verificador de tipo, `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 <>, por outro lado, -são úteis mesmo se você não usar um verificador de tipo, -porque durante a execução elas geram uma classe personalizada que pode ser instanciada. -Elas também fornecem vários métodos ou funções úteis, -listadas na <> do <>. - -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, py] ----- -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 verificados. 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 do 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 <>. - -[[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) ----- -==== -[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]`. -Verificadores 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`. -<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 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.] - -[[to_xml_ex]] -._books.py_: a função `to_xml` -==== -[source, py] ----- -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. - -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, py] ----- -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`! - -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, py] ----- -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 verificador de tipo 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 verificação de tipo 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, py] ----- -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? - -Agora verificamos _demo_not_book.py_ com o Mypy (no <>). - -[[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 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. - -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 verificação 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 <> 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: - -* `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_] (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, 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). - -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"))) - - -[[type_casting_sec]] -=== Coerção de Tipo - -Nenhum((("gradual type system", "type casting", id="GTStypecast15")))((("type casting", id="typecast15"))) sistema de tipos é perfeito, nem tampouco os verificadores estáticos de tipo, as dicas de tipo no projeto _typeshed_ ou as dicas de tipo em pacotes de terceiros que as oferecem. - -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: - -[quote] -____ -Coerções são usadas para silenciar avisos espúrios do verificador de tipos, e dão uma ajuda ao verificador quando ele não consegue entender direito o que está acontecendo. -____ - -Durante a execução, `typing.cast` não faz absolutamente nada. Essa é 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 PEP 484 exige que os verificadores de tipo "acreditem cegamente" em `cast`. -A https://fpy.li/15-16[seção "Casts" (_Coerções_) da PEP 484] mostra um exemplo onde o verificador 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. - -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 verificador de tipo. 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 do Python. -No <>, criei um objeto _asyncio_ , `Server`, e queria obter o endereço que o servidor estava ouvindo. -Escrevi essa 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 o 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] ----- - -//// -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. - -O leitor atento pode ser 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])`. - -[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. -==== - -Apesar de suas desvantagens, há usos válidos para `cast`. -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 email 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: - -* `# 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 verificador de tipo 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. - -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, 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 -https://fpy.li/15-23[_clip_annot.py_].] - - -[[ex_clip_annot]] -.clipannot.py: a assinatura anotada da função `clip` -==== -[source, py] ----- -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, pycon] ----- ->>> 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 `->` 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`, -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 usadas muitas dicas de tipo. -* Referências a tipos ainda não definidos exigem o uso de strings em vez do 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__+`. -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é o 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, py] ----- -class Rectangle: - # ... lines omitted ... - def stretch(self, factor: float) -> '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 verificadores de tipo 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 -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: - -`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`. [...] - -[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 o 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. -==== - -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): - -[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 do Python 3.7, é assim que anotações são tratadas em qualquer módulo que comece com a seguinte instrução `import`: - -[source, py] ----- -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, pycon] ----- ->>> 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, pycon] ----- ->>> 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 resis—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. - -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. - -Na discussão que se seguiu na lista de email 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.] -____ - -O Steering Council do Python decidiu adiar a elevação da PEP 563 a comportamento padrão até o 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. - -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 o 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 muito 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. -==== - -==== 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 personalizada 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 <>: - -[source, py] ----- -class Checked: - @classmethod - def _fields(cls) -> dict[str, type]: - return get_type_hints(cls) - # ... more lines ... ----- - -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 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 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_)] (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). -==== - -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"))) - - -[[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. -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, py] ----- -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, py] ----- -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, py] ----- -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. -<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. -==== - -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.] - -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, 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. -==== - -Já vimos o conceito de _variância_ na seção <>, 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. - -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!] - - -==== 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, py] ----- -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 é legal: - - -[source, py] ----- -include::code/15-more-types/cafeteria/invariant.py[tags=INSTALL_JUICE_DISPENSER] ----- - -Entretanto, isso não é legal: - -[source, py] ----- -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 é ilegal: - -[source, py] ----- -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 do 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_: type definitions and `install` function -==== -[source, py] ----- -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, py] ----- -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, py] ----- -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 que é 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, py] ----- -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, py] ----- -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, py] ----- -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. - -Como mencionado, as coleções mutáveis do Python são invariantes por default. -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, py] ----- -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 argumentos 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://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`: - -[source, py] ----- -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 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. - -[[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 desse 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 seção <>. -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 seção <>; -`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 seção <>. - -Dessas 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 é mais aberta 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. - -Nossa discussão sobre variância continua na seção <>. - -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 do 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, python3] ----- -@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, python3] ----- -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, python3] ----- -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 verificações 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 na seção <>, 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, python3] ----- -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. - -[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"))) - - -=== Resumo do capítulo - -Começamos((("gradual type system", "overview of"))) com um exemplo simples de uso de `@overload`, seguido por um exemplo muito 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 verificações 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. - -Então falamos sobre `typing.cast`, uma função projetada para nos permitir guiar o trabalho do verificador de tipos. É importante considerar cuidadosamente quando usar `cast`, porque seu uso excessivo atrapalha o verificador 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 desenvolvedores principais do 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 do 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 do 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. -==== - - -[[more_type_hints_further_sec]] -=== Leitura complementar - -O((("gradual type system", "further reading on"))) sistema de tipagem estática do 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://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 do Python. Todos os textos das PEPs estão em inglês. Dados coletados em maio 2021. -[options="header"] -|================================================================================================================================= -|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 -|================================================================================================================================= - -A documentação oficial do 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 do 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_)]. - -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 você estiver interessado na história do 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 do 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 verificador 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.] - - -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). - -Por fim, me surpreeendi ao encontrar https://fpy.li/15-51["Generics Considered Harmful" (_Genéricos Considerados Nocivos_)], de Ken Arnold, -um desenvolvedor principal do 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 do Java. - -Infelizmente, as críticas de Arnold também se aplicam ao sistema de tipagem estática do 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 C++: -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..." -____ - -Felizmente, o Python tem uma vantagem crítica sobre o Java e o C++: -um sistema de tipagem opcional. -Podemos silenciar os verificadores de tipo e omitir as dicas de tipo quando se tornam muito incômodos. - -[[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 verificador de tipo, 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 seção <>. -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. - - -Caí em uma toca de coelho similar quando acrescentei dicas de tipo ao -<>, 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. - -[role="soapbox-title"] -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 do Python não é tão boa quanto poderia ser. -Essa 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.] -____ - -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 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. - -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. - -[role="pagebreak-before less_space"] -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), -segue que `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: - -[source, python3] ----- -T_out = TypeVar('T_out', covariant=True) -T_in = TypeVar('T_in', contravariant=True) ----- - -Aí poderíamos definir as classes assim: - -[source, python3] ----- -class BeverageDispenser(Generic[T_out]): - ... - -class TrashCan(Generic[T_in]): - ... ----- - -Será que é tarde demais para modificar a convenção de nomenclatura definida na PEP 484? - -**** diff --git a/capitulos/cap16.adoc b/capitulos/cap16.adoc deleted file mode 100644 index 936a8a4e..00000000 --- a/capitulos/cap16.adoc +++ /dev/null @@ -1,1017 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte - -[[operator_overloading]] -== Sobrecarga de operadores - -[quote, James Gosling, Criador do 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 C++.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).] -____ - -Em((("operator overloading", "infix operators")))((("infix operators"))) Python, podemos calcular juros compostos usando uma fórmula escrita assim: - -[source, python3] ----- -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` do Python—e a fórmula vai funcionar como está escrita, produzindo 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`: - -[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 estendidos, tal como os arrays do 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 do Python na ciência de dados, incluindo as aplicações financeiras e científicas. - -Na seção <> (do <>) 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: - -* 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 - - -=== Novidades nesse capítulo - -O _goose typing_((("operator overloading", "significant changes to"))) é uma parte fundamental do 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 verificação explícita usando `isinstance` contra `numbers.Real`.footnote:[O restante das ABCs na biblioteca padrão do Python ainda são valiosas para o _goose typing_ e a tipagem estática. O problema com as ABCs `numbers` é explicado na seção <>.] - -Na primeira edição do _Python Fluente_, tratei do operador de multiplicação de matrizes `@` como uma mudança futura, quando o Python 3.5 ainda estava em sua versão alfa. Agora o `@` está integrado ao fluxo do capítulo na seção <>. -Aproveitei o _goose typing_ para tornar a implementação de `+__matmul__+` aqui mais segura que a da primeira edição, sem comprometer sua flexibilidade. - -A seção <> 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`. - - -[[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 tais 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 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 utilizado, ele gera APIs agradáveis de usar e código legível. O 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 binários `&`, `|`, 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 `*`. - -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`. -`+`, 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ção <>, 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 destinatário (`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 destinatário 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. - -[[ex_vector_v6_unary]] -.vector_v6.py: unary operators - and + added to <> -==== -[source, py] ----- -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. - -Não vamos implementar `+__invert__+`. Se um usuário tentar escrever `~v` para uma instância de `Vector`, o 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 . - -[[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`. - -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 <>. - -[[ex_unary_plus_decimal]] -.Uma mudança na precisão do contexto aritmético pode fazer `x` se tornar diferente de `+x` -==== -[source, py] ----- -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 para aritmética com `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. - -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 <>. - -[[ex_unary_plus_counter]] -.O + unário produz um novo `Counter`sem as contagens negativas ou zero -==== -[source, pycon] ----- ->>> 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 se vê, `+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://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 mas mais significativo 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, pycon] ----- ->>> v_concatenated = Vector(list(v1) + list(v2)) ->>> v_repeated = 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: - -[source, pycon] ----- ->>> 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, pycon] ----- ->>> v1 = Vector([3, 4, 5, 6]) ->>> v3 = Vector([1, 2]) ->>> v1 + v3 -Vector([4.0, 6.0, 5.0, 6.0]) ----- - -Dados esses requerimentos básicos, podemos implementar `+__add__+` como no <>. - -[[ex_vector_add_t1]] -.Método `+Vector.__add__+`, versão #1 -==== -[source, python3] ----- - # inside the Vector class - - 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`. - -[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 seção <>. -==== - -O <> permite somar um `Vector` a um `Vector2d`, e `Vector` a uma tupla ou qualquer iterável que produza números, como prova o <>. - -[[ex_vector_add_demo_mixed_ok]] -.Nossa versão #1 de `+Vector.__add__+` também aceita objetos diferentes de ++Vector++ -==== -[source, pycon] ----- ->>> 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 `+` no <> funcionam porque `+__add__+` usa -`zip_longest(…)`, capaz de consumir qualquer iterável, e a expressão geradora que cria um novo `Vector` simplemente 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á. - -Entretanto, se trocarmos a ordem dos operandos (no <>), a soma de tipos diferentes falha. - -[[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 -==== -[source, pycon] ----- ->>> 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, o 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 <>): - -. 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_). - -[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 do 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.] -==== - -[[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 o 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. -==== - -A implementação viável mais simples de `+__radd__+` aparece no <>. - -[[ex_vector_add_t2]] -.Os métodos `+__add__+` e `+__radd__+` de `Vector` -==== -[source, python3] ----- - # inside the Vector class - - 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 <>; 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. - -Se `+__radd__+` apenas invoca `+__add__+`, aqui está outra forma de obter o mesmo efeito: - -[source, python3] ----- - 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 `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 <>. - -[[ex_vector_error_iter]] -.O método `+Vector.__add__+` precisa de operandos iteráveis -==== -[source, pycon] ----- ->>> 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__+` precisa de um iterável com itens numéricos -==== -[source, pycon] ----- ->>> 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 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 o 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 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`_) - -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 <>) -==== -[source, py] ----- -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 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. -==== - -Agora que já sobrecarregamos o operador `+` 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 - -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_): - -[source, pycon] ----- ->>> 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 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 seção <>. -==== - -De volta a nosso produto escalar, começamos novamente com os métodos `+__mul__+` e `+__rmul__+` mais simples possíveis que possam funcionar: - -[source, python3] ----- - # inside the Vector class - - def __mul__(self, scalar): - return Vector(n * scalar for n in self) - - def __rmul__(self, scalar): - 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 verificação de tipo 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_. - -[[ex_vector_v7]] -.vector_v7.py: métodos do operador `*` adicionados -==== -[source, python3] ----- -class Vector: - typecode = 'd' - - 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 - - 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, pycon] ----- ->>> 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 _goose typing_ no <>: verificava 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 verificados de forma estática me parece uma má ideia. - -Outra alternativa seria verificar com o protocolo `typing.SupportsFloat`, que vimos na seção <>. Escolhi usar _duck typing_ naquele exemplo por achar 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 _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 o 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 do NumPy. - -O operador `@` é suportado pelos métodos especiais `+__matmul__+`, `+__rmatmul__+` -e `+__imatmul__+`, cujos nomes derivam de "matrix multiplication" (_multiplicação de matrizes_). -Até o 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 do NumPy--e o resto de nós--podemos implementar o operador `@` em nossas classes. -O analisador sintático do 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`: - -[source, pycon] ----- ->>> 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, pycon] ----- ->>> 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: operator `@` methods -==== -[source, python3] ----- -class Vector: - # many methods omitted in book listing - - 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]] -.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 tem tamanhos diferentes. O default é `False`. -Esse novo comportamento estrito se alinha à filosofia de https://fpy.li/16-8[_falhar rápido_] do 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 seção <>. -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. - -Vamos revisar os operadores aritméticos suportados pelo Python antes de mergulhar na categoria especial dos <>.((("", 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 "no mesmo lugar" serão tratados em <>). - -[[infix_operator_names_tbl]] -.Nomes dos métodos de operadores infixos (os operadores "no mesmo lugar" são usados para atribuição aumentada; operadores de comparação estão na <>) -[options="header"] -|================================================================================================= -| Operador | Direto | Reverso | No mesmo lugar | Descrição -| `+` | `+__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 -| `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 cheia usam um conjunto diferente de regras.((("", startref="infixop16"))) - - -[[rich_comp_op_sec]] -=== Operadores de comparação cheia - -O((("operator overloading", "rich comparison operators", id="OOrich16")))((("rich comparison operators", id="richcomp16")))((("comparison operators", id="comop16")))((("== (equality) operator")))((("!= (not equal to) operator")))((("< (less than) operator")))((("<= (less than or equal to) operator")))((("> (greater than) operator")))(((">= (greater than or equal to) operator")))((("equality (==) operator")))((("greater than (>) operator")))((("greater than or equal to (>=) operator")))((("less than (<) operator")))((("less than or equal to (<=) operator")))((("not equal to (!=) operator"))) tratamento dos operadores de comparação cheia `==`, `!=`, `>`, `<`, `>=` 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`, o Python vai comparar os IDs dos objetos em vez de gerar um `TypeError`. - -[[reversed_rich_comp_op_tbl]] -.Operadores de comparação cheia: métodos reversos invocados quando a chamada inicial ao método devolve `NotImplemented` -[options="header"] -|================================================================================================= -| 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)` -| | `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` -|================================================================================================= - -Dadas essas regras, vamos revisar e aperfeiçoar o comportamento do método `+Vector.__eq__+`, que foi escrito assim no __vector_v5.py__ (<>): - -[source, python3] ----- -class Vector: - # many lines omitted - - def __eq__(self, other): - return (len(self) == len(other) and - all(a == b for a, b in zip(self, other))) ----- - -Eaae método produz os resultados do <>. - -[[eq_initial_demo]] -.Comparando um `Vector` a um `Vector`, a um `Vector2d`, e a uma `tuple` -==== -[source, pycon] ----- ->>> 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 `tuple` ou qualquer iterável com itens numéricos 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: - -[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, vamos ser conservadores e executar alguma verificação 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 o Python cuidar do caso. Veja o <>. - -[[ex_vector_v8_eq]] -.vector_v8.py: `+__eq__+` aperfeiçoado na classe `Vector` -==== -[source, py] ----- -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 que aparecem no <>. - -[[eq_demo_new_eq]] -.Mesmas comparações do <>: o último resultado mudou -==== -[source, pycon] ----- ->>> 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 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... - -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`, o Python invoca `Vector.__eq__(vc, v2d)`. -. `+Vector.__eq__(vc, v2d)+` verifica que `v2d` não é um `Vector` e devolve `NotImplemented`. -. O 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: - -. Para avaliar `va == t3`, o Python invoca `+Vector.__eq__(va, t3)+`. -. `+Vector.__eq__(va, t3)+` verifica que `t3` não é um `Vector` e devolve `NotImplemented`. -. O Python recebe o resultado `NotImplemented`, e então tenta `+tuple.__eq__(t3, va)+`. -[role="pagebreak-before less_space"] -. `+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`, o 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. - -Em outras palavras, dados os mesmos objetos que usamos no <>, os resultados para `!=` são consistentes: - -[source, python3] ----- ->>> va != vb -False ->>> vc != v2d -False ->>> va != (1, 2, 3) -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.] - -[source, python3] ----- - 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.Vamos agora voltar nossa atenção para uma classe diferente de operador: 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. - -O <> os mostra em ação. - -[[eq_demo_augm_assign_immutable]] -.Usando `+=` e `*=` com instâncias de `Vector` -==== -[source, pycon] ----- ->>> 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 operadores "no mesmo lugar" 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. - -Entretanto, se você implementar um operador "no mesmo lugar" tal como -`+__iadd__+`, aquele método será chamado para computar o resultado de `a += b`. -Como indica seu nome, se espera que esses operadores modifiquem o operando à esquerda do operador no mesmo lugarfootnote:[NT: O "i" nos nomes desses operadores se refere a "_in-place_"], e não criem um novo objeto como resultado. - -[WARNING] -==== -Os métodos especiais de atualização no mesmo lugar não devem nunca ser implementados para tipos imutáveis como nossa classe `Vector`. Isso é bastante óbvio, mas vale a pena enfatizar. -==== - -Para mostrar o código de um operador de atualização no mesmo lugar, vamos estender a classe `BingoCage` do <> para implementar -`+__add__+` e `+__iadd__+`. - -Vamos chamar a subclasse de `AddableBingoCage`. O <> mostra o comportamento esperado para o operador `+`. - -[[demo_addable_bingo_add]] -.O operador `+` cria uma nova instância de `AddableBingoCage` -==== -[source, py] ----- -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 do 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 <>) -==== -[source, py] ----- -include::code/16-op-overloading/bingoaddable.py[tags=ADDABLE_BINGO_IADD_DEMO] ----- -==== -<1> Cria um alias para podermos verificar 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 no mesmo lugar, 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. -==== - -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 <>. - -[[ex_addable_bingo]] -.bingoaddable.py: `AddableBingoCage` estende `BingoCage` para suportar `+` e `+=` -==== -[source, py] ----- -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 no mesmo lugar comparando as instruções `return` que produzem os resultados em `+__add__+` e em `+__iadd__+` no <>: - -`+__add__+`:: O resultado é produzido chamando o construtor `AddableBingoCage` para criar uma nova instância. - -`+__iadd__+`:: O resultado é produzido devolvendo `self`, após ele ter sido modificado. - -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 o 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 o 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. -==== - -Isso conclui 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 verificação 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 do Python pode produzir mensagens de erro enganosas ou resultados inesperados quando combinado com o _duck typing_. -Por essa razão, a disciplina da verificação de tipo 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 seção <>. -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 verificações 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 o Python oferece uma implementação conveniente de `!=` no `+__ne__+` herdado da classe base `object`. A forma como o 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 o Python compara os IDs dos objetos como último recurso. - -Na última seção, nos concentramos nos operadores de atribuição aumentada. Vimos que o 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 no mesmo lugar, 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. - -[[further_reading_op_sec]] -=== Leitura complementar - -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 do 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ção <>. -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 do 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 do 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: - -[source, pycon] ----- ->>> 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 `/` 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 do Smalltalk), e https://fpy.li/16-18["Arithmetic and Double Dispatching in Smalltalk-80" (_Aritmética e Despacho Duplo no 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 o Smalltalk, o Python e o Ruby. O Python não tem despacho duplo para tratar operadores, como descrito naqueles artigos. O algoritmo do 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 _C++ 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. - -[[operator_soapbox]] -.Ponto de Vista -**** - -[role="soapbox-title"] -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: - -[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. -____ - -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 personalizados, e permitindo ao analisador sintático do Python permanecer simples. O 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. - -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. -____ - -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]. - -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 - -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, pycon] ----- ->>> 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`, o 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 <> 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. - -**** diff --git a/capitulos/cap17.adoc b/capitulos/cap17.adoc deleted file mode 100644 index 0f7f8ae2..00000000 --- a/capitulos/cap17.adoc +++ /dev/null @@ -1,2331 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte - -[[iterables2generators]] -== Iteradores, geradores e corrotinas clássicas - -//++++ -//
-//

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! - -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. - -Todas as coleções padrão do Python são _iteráveis_. -Um _iterável_ é um objeto que fornece um _iterador_, que o Python usa para suportar operações como: - -* loops `for` -* Compreensões de lista, dict e set -* Desempacotamento para atribuições -* 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: - -* Como o 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 - - -=== Novidades nesse capítulo - -A seção <> 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. - -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 <>). - -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. - -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 -==== -[source, py] ----- -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 seção <>.] - - -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` -==== -[source, pycon] ----- ->>> s = Sentence('"The time has come," the Walrus said,') # <1> ->>> s -Sentence('"The time ha... Walrus said,') # <2> ->>> for word in s: # <3> -... print(word) -The -time -has -come -the -Walrus -said ->>> list(s) # <4> -['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: - -[source, pycon] ----- ->>> s[0] -'The' ->>> s[5] -'Walrus' ->>> s[-1] -'said' ----- - -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 - -Sempre((("functions", "iter() function")))((("iterators", "iter() function", id="Iinterfun17")))((("iter() function", id="iterfunc17"))) que o Python precisa iterar sobre um objeto `x`, ele automaticamente invoca `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, o Python gera um `TypeError`, normalmente dizendo `'C' object is not iterable` (_objeto 'C' não é iterável_), onde `C` é a classe do objeto alvo. - -Por isso todas as sequências do 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. - -Como mencionado na seção <>, 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: - -[source, pycon] ----- ->>> class Spam: -... def __getitem__(self, i): -... print('->', i) -... raise IndexError() -... ->>> spam_can = Spam() ->>> iter(spam_can) - ->>> list(spam_can) --> 0 -[] ->>> from collections import abc ->>> isinstance(spam_can, abc.Iterable) -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 do 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`. - -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 seção <>. Eis uma demonstração: - -[source, pycon] ----- ->>> class GooseSpam: -... def __iter__(self): -... pass -... ->>> from collections import abc ->>> issubclass(GooseSpam, abc.Iterable) -True ->>> goose_spam_can = GooseSpam() ->>> isinstance(goose_spam_can, abc.Iterable) -True ----- - -[TIP] -==== -Desde o Python 3.10, a forma mais precisa de verificar 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 verificação. -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 verificação explícita. -A verificação 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. - -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]] -==== 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. - -O exemplo a seguir mostra como usar `iter` para rolar um dado de seis faces até que o valor `1` seja sorteado: - -[source, pycon] ----- ->>> def d6(): -... return randint(1, 6) -... ->>> d6_iter = iter(d6, 1) ->>> d6_iter - ->>> for roll in d6_iter: -... print(roll) -... -4 -3 -6 -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()`. - -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: - -[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: -____ - -[source, python3] ----- -from functools import partial - -with open('mydata.db', 'rb') as f: - read64 = partial(f.read, 64) - for block in iter(read64, 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. - -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 seção <> 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. - -É importante deixar clara a relação entre iteráveis e iteradores: o Python obtém iteradores de iteráveis. - -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: - -[source, pycon] ----- ->>> s = 'ABC' ->>> for char in s: -... print(char) -... -A -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: - -[source, pycon] ----- ->>> s = 'ABC' ->>> it = iter(s) # <1> ->>> while True: -... try: -... print(next(it)) # <2> -... except StopIteration: # <3> -... del it # <4> -... break # <5> -... -A -B -C ----- -<1> Cria um iterador `it` a partir de um iterável. -<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 obleto iterador é descartado. -<5> Sai do loop. - -`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. - -A interface padrão do Python para um iterador tem dois métodos: - -`+__next__+`:: Devolve o próximo item em uma série, gerando `StopIteration` se não há mais nenhum. - -`+__iter__+`:: Devolve `self`; isso permite que iteradores sejam usado quando um iterável é esperado. Por exemplo, em um loop `for` loop. - -Essa 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] - -O código-fonte de `collections.abc.Iterator` aparece no <>. - -[[abc_iterator_src]] -.Classe `abc.Iterator`; extraído de https://fpy.li/17-5[__Lib/_collections_abc.py__] -==== -[source, python3] ----- -class Iterator(Iterable): - - __slots__ = () - - @abstractmethod - def __next__(self): - 'Return the next item from the iterator. When exhausted, raise StopIteration' - raise StopIteration - - def __iter__(self): - return self - - @classmethod - def __subclasshook__(cls, C): # <1> - if cls is Iterator: - return _check_methods(C, '__iter__', '__next__') # <2> - return NotImplemented ----- -==== -<1> `+__subclasshook__+` suporta a verificação de tipo estrutural com `isinstance` e `issubclass`. Vimos isso na seção <>. -<2> `_check_methods` percorre o parâmetro `+__mro__+` da classe, para verificar 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 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 -# verificar [a existência] de ambos os atributos "__iter__" e "__next__". ----- - -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 verificar 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 do Python é possivel ver claramente como o iterador é criado por `iter()` e consumido por `next()`: - -[source, pycon] ----- ->>> s3 = Sentence('Life of Brian') # <1> ->>> it = iter(s3) # <2> ->>> it # doctest: +ELLIPSIS - ->>> next(it) # <3> -'Life' ->>> next(it) -'of' ->>> next(it) -'Brian' ->>> next(it) # <4> -Traceback (most recent call last): - ... -StopIteration ->>> list(it) # <5> -[] ->>> list(iter(s3)) # <6> -['Life', 'of', 'Brian'] ----- -<1> Cria uma sentença `s3` com três palavras. -<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.. -<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 verificar 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. - -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.] - -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"))) - - -=== Classes Sentence com pass:[__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. - - -==== Sentence versão #2: um iterador clássico - -A próxima implementação de `Sentence` segue a forma do padrão de projeto _Iterator_ clássico, do livro _Padrões de Projeto_. -Observe que isso não é Python idiomático, como as refatorações seguintes deixarão claro. -Mas é útil para mostrar a distinção entre uma coleção iterável e um iterador que trabalha com ela. - -A classe `Sentence` no <> é iterável por implementar o método especial `+__iter__+`, -que cria e devolve um `SentenceIterator`. É assim que um iterável e um iterador se relacionam. - -[[ex_sentence1]] -.sentence_iter.py: `Sentence` implementada usando o padrão _Iterator_ -==== -[source, py] ----- -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__+`. -<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`. -<7> Incrementa `self.index`. -<8> Devolve a palavra. -<9> Implementa `+self.__iter__+`. - -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__+`. - -É 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. - -[[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 tem 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 -`+__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. - -A seção "Aplicabilidade" do padrão de projeto _Iterator_ no livro _Padrões de Projeto_ diz: - -[quote] -____ -Use o padrão Iterator - -* para acessar o conteúdo de um objeto agregado sem expor sua representação interna. - -* para suportar travessias múltiplas de objetos agregados. - -* 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. - -Agora((("yield keyword", id="yielda17")))((("keywords", "yield keyword", id=Kyielda17"))) que demonstramos de forma apropriada o padrão _Iterator_ clássico, vamos em frente. -O 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 funcã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 <>. - -[[ex_sentence2]] -.sentence_gen.py: `Sentence` implementada usando uma geradora -==== -[source, py] ----- -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.] -<4> Não há necessidade de uma classe iteradora separada! - -Novamente temos aqui uma implementação diferente 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. - -Segue abaixo uma explicação completa das geradoras. - - -==== Como funciona uma geradora - -Qualquer((("generators", "yield keyword"))) função do 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 <> mostra o comportamento de uma função geradora simples.footnote:[Agradeço a David Kwast por sugerir esse exemplo.] - -[[gen-func-ex-three-yield]] -[source, pycon] -.Uma função geradora que produz três números((("generators", "examples of", id="genex17"))) -==== ----- ->>> def gen_123(): -... yield 1 # <1> -... yield 2 -... yield 3 -... ->>> gen_123 # doctest: +ELLIPSIS - # <2> ->>> gen_123() # doctest: +ELLIPSIS - # <3> ->>> for i in gen_123(): # <4> -... print(i) -1 -2 -3 ->>> g = gen_123() # <5> ->>> next(g) # <6> -1 ->>> next(g) -2 ->>> next(g) -3 ->>> next(g) # <7> -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. -<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`. - -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`. - -[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 seção <>. -==== - -O <> torna a iteração entre um loop `for` e o corpo da função mais explícita. - -[[ex_gen_ab]] -.Uma função geradora que exibe mensagens quando roda -==== -[source, pycon] ----- ->>> def gen_AB(): -... print('start') -... yield 'A' # <1> -... print('continue') -... yield 'B' # <2> -... print('end.') # <3> -... ->>> for c in gen_AB(): # <4> -... print('-->', c) # <5> -... -start <6> ---> A <7> -continue <8> ---> B <9> -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. - -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. - -Vamos criar a seguir 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`. - - -==== Sentence versão #4: uma geradora preguiçosa - -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. -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. -O código está no <>. - -[[ex_sentence3]] -.sentence_gen2.py: `Sentence` implementada usando uma função geradora que invoca a função geradora `re.finditer` -==== -[source, py] ----- -include::code/17-it-generator/sentence_gen2.py[tags=SENTENCE_GEN2] ----- -==== -<1> Não é necessário manter uma lista `words`. -<2> `finditer` cria um iterador sobre os termos encontrados com `RE_WORD` em `self.text`, produzindo instâncias de `MatchObject`. -<3> `match.group()` extraí o texto da instância de `MatchObject`. - -Geradores são um ótimo atalho, mas o código pode ser ainda mais conciso com uma expressão geradora. - - -==== Sentence versão #5: Expressão geradora preguiçosa - -Podemos substituir funções geradoras simples como aquela na última classe `Sentence (no <>) por uma expressão geradora. -Assim como uma compreensão de lista cria listas, uma expressão geradora cria objetos geradores. -O <> compara o comportamento nos dois casos. - -[[ex_gen_ab_genexp]] -.A função geradora `gen_AB` é usada primeiro por uma compreensão de lista, depois por uma expressão geradora -==== -[source, pycon] ----- ->>> def gen_AB(): # <1> -... print('start') -... yield 'A' -... print('continue') -... yield 'B' -... print('end.') -... ->>> res1 = [x*3 for x in gen_AB()] # <2> -start -continue -end. ->>> for i in res1: # <3> -... print('-->', i) -... ---> AAA ---> BBB ->>> res2 = (x*3 for x in gen_AB()) # <4> ->>> res2 - at 0x10063c240> ->>> for i in res2: # <5> -... print('-->', i) -... -start # <6> ---> AAA -continue ---> BBB -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. -<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`. - -Podemos usar uma expressão geradora para reduzir ainda mais o código na classe `Sentence`. Veja o <>. - -[[ex_sentence4]] -.sentence_genexp.py: `Sentence` implementada usando uma expressão geradora -==== -[source, py] ----- -include::code/17-it-generator/sentence_genexp.py[tags=SENTENCE_GENEXP] ----- -==== - -A única diferença com o <> é o método `+__iter__+`, que aqui não é uma função geradora (ela não contém uma instrução `yield`) mas usa uma expressão geradora para criar um gerador e devolvê-lo. O resultado final é o mesmo: quem invoca `+__iter__+` recebe um objeto gerador. - -Expressões geradoras são "açúcar sintático": elas pode sempre ser substituídas por funções geradoras, mas algumas vezes são mais convenientes. A próxima seção trata do uso de expressões geradoras.((("", startref="Ilazy17")))((("", startref="Glazy17")))((("", startref="lazysen17"))) - - -=== 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__+`. -// 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. - -No <>, vimos que uma expressão geradora é um atalho sintático para criar um gerador sem definir e invocar uma função. -Por outro lado, funções geradoras são mais flexíveis: podemos programar uma lógica complexa, com múltiplos comandos, e podemos até usá-las como _corrotinas_, como veremos na seção <>. - -Nos casos mais simples, uma expressão geradora é mais fácil de ler de relance, como mostra o exemplo de `Vector`. - -Minha regra básica para escolher qual sintaxe usar é simples: se a expressão geradora exige mais que um par de linhas, prefiro escrever uma função geradora, em nome da legibilidade. - -[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: - -[source, python3] ----- -def __mul__(self, scalar): - if isinstance(scalar, numbers.Real): - return Vector(n * scalar for n in self) - else: - return NotImplemented ----- - -Entretanto, se existirem mais argumentos para a função após a expressão geradora, é preciso cercar a expressão com parênteses para evitar um `SyntaxError`. - -==== - -Os exemplos de `Sentence` vistos até aqui mostram geradores fazendo o papel do padrão _Iterator_ clássico: obter itens de uma coleção. -Mas podemos também usar geradores para produzir valores independente de uma fonte de dados. -A próxima seção mostra um exemplo. - -Mas antes, um pequena discussão sonre os conceitos sobrepostos de _iterador_ e _gerador_. - -.Comparando iteradores e geradores -**** - -Na((("iterators", "versus generators", secondary-sortas="generators")))((("generators", "versus iterators", secondary-sortas="iterators"))) documentação e na base de código oficiais do Python, a terminologia em torno de iteradores e geradores é inconsistente e está em evolução. -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. - 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__+`. - 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. - 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 do Python_] -introduziu recentemente o termo -https://docs.python.org/pt-br/3/glossary.html#term-generator-iterator[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". - - -Mas, de acordo com o interpretador Python, -os objetos devolvidos em ambos os casos são objetos geradores: - -[source, pycon] ----- ->>> def g(): -... yield 0 -... ->>> g() - ->>> ge = (c for c in 'XYZ') ->>> ge - at 0x10e936ce0> ->>> type(g()), type(ge) -(, ) ----- - -**** - -=== 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 <> 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. - -[[ap_class_demo]] -.Demonstração de uma classe `ArithmeticProgression` -==== -[source, py] ----- -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 do 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]] -.A classe `ArithmeticProgression` -==== -[source, py] ----- -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. -<6> O `result` atual é produzido. -<7> O próximo resultado em potencial é calculado. Ele pode nunca ser produzido, se o loop `while` terminar. - -Na última linha do <>, -em vez de somar `self.step` ao `result` anterior a cada passagem do loop, -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: - -[source, pycon] ----- ->>> 100 * 1.1 -110.00000000000001 ->>> sum(1.1 for _ in range(100)) -109.99999999999982 ->>> 1000 * 1.1 -1100.0 ->>> sum(1.1 for _ in range(1000)) -1100.0000000000086 ----- - -A classe `ArithmeticProgression` do <> funciona como esperado, é outro exemplo do uso de uma função geradora para implementar o método especial `+__iter__+`. -Entretanto, se o único objetivo de uma classe é criar um gerador pela implementação de `+__iter__+`, -podemos substituir a classe por uma função geradora. Pois afinal, uma função geradora é uma fábrica de geradores. - -O <> mostra uma função geradora chamada `aritprog_gen`, que realiza a mesma tarefa da `ArithmeticProgression`, mas com menos código. Se, em vez de chamar `ArithmeticProgression`, você chamar `aritprog_gen`, os testes no <> são todos bem sucedidos.footnote:[O diretório __17-it-generator/__ no https://fpy.li/code[repositório de código do _Python Fluente_] inclui doctests e um script, __aritprog_runner.py__, que roda os testes contra todas as variações dos scripts __aritprog*.py__.] - - -[[ex_ap_genfunc1]] -.a função geradora `aritprog_gen` -==== -[source, py] ----- -include::code/17-it-generator/aritprog_v2.py[tags=ARITPROG_GENFUNC] ----- -==== - -O <> é elegante, mas lembre-se sempre: há muitos geradores prontos para uso na biblioteca padrão, e a próxima seção vai mostrar uma implementação mais curta, usando o módulo `itertools`. - - -[[ap_itertools_sec]] -==== Progressão aritmética com itertools - -O((("itertools module"))) módulo `itertools` no Python 3.10 contém 20 funções geradoras, que podem ser combinadas de várias maneiras interessantes. - -Por exemplo, a função `itertools.count` devolve um gerador que produz números. Sem argumentos, ele produz uma série de inteiros começando de `0`. Mas você pode fornecer os valores opcionais `start` e `step`, para obter um resultado similar ao das nossas funções `aritprog_gen`: - -[source, pycon] ----- ->>> import itertools ->>> gen = itertools.count(1, .5) ->>> next(gen) -1 ->>> next(gen) -1.5 ->>> next(gen) -2.0 ->>> next(gen) -2.5 ----- - - -[WARNING] -==== -`itertools.count` nunca para, então se você chamar `list(count())`, o Python vai tentar criar uma `list` que preencheria todos os chips de memória já fabricados. -Na prática, sua máquina vai ficar muito mal-humorada bem antes da chamada fracassar. -==== - -Por outro lado, temos também a função `itertools.takewhile`: ela devolve um gerador que consome outro gerador e para quando um dado predicado é avaliado como `False`. Então podemos combinar os dois e escrever o seguinte: - -[source, pycon] ----- ->>> gen = itertools.takewhile(lambda n: n < 3, itertools.count(1, .5)) ->>> list(gen) -[1, 1.5, 2.0, 2.5] ----- - -Se valendo de `takewhile` e `count`, o <> é ainda mais conciso. - -[[ex_almost_aritprog]] -.aritprog_v3.py: funciona como as funções `aritprog_gen` anteriores -==== -[source, py] ----- -include::code/17-it-generator/aritprog_v3.py[tags=ARITPROG_ITERTOOLS] ----- -==== - -Observe que `aritprog_gen` no <> não é uma função geradora: não há um `yield` em seu corpo. -Mas ela devolve um gerador, exatamente como faz uma função geradora. - -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. -Por isso a próxima seção trata de várias funções geradoras prontas para usar.((("", startref="Garith17"))) - - -[[stdlib_generators]] -=== Funções geradoras na biblioteca padrão - -// [NOTE] -// ==== -// Perhaps you know all the functions mentioned in this section, but some of them are underused, so a quick overview may be good to recall what's already available. -// ==== - -A((("generators", "generator functions in Python standard library", id="Ggenfunc17"))) biblioteca padrão oferece muitas geradoras, desde objetos de arquivo de texto forncendo iteração linha por linha até a incrível função pass:[os.walk], que produz nomes de arquivos enquanto cruza uma árvore de diretórios, tornando buscas recursivas no sistema de arquivos tão simples quanto um loop `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. - -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"))) - -[[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). -|============================================================================================================================================================================================================================================================= - -//// -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! -//// - - -A seção de console no <> demonstra o uso de todas as funções na <>. - -[[demo_filter_genfunc]] -.Exemplos de funções geradoras de filtragem -==== -[source, pycon] ----- ->>> def vowel(c): -... return c.lower() in 'aeiou' -... ->>> list(filter(vowel, 'Aardvark')) -['A', 'a', 'a'] ->>> import itertools ->>> list(itertools.filterfalse(vowel, 'Aardvark')) -['r', 'd', 'v', 'r', 'k'] ->>> list(itertools.dropwhile(vowel, 'Aardvark')) -['r', 'd', 'v', 'a', 'r', 'k'] ->>> list(itertools.takewhile(vowel, 'Aardvark')) -['A', 'a'] ->>> list(itertools.compress('Aardvark', (1, 0, 1, 1, 0, 1))) -['A', 'r', 'd', 'a'] ->>> list(itertools.islice('Aardvark', 4)) -['A', 'a', 'r', 'd'] ->>> list(itertools.islice('Aardvark', 4, 7)) -['v', 'a', 'r'] ->>> list(itertools.islice('Aardvark', 1, 7, 2)) -['a', 'd', 'a'] ----- -==== - -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. - -[[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! -//// - - -O <> demonstra alguns usos de `itertools.accumulate`. - -[[demo_accumulate_genfunc]] -.Exemplos das funções geradoras de `itertools.accumulate` -==== -[source, pycon] ----- ->>> sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1] ->>> import itertools ->>> list(itertools.accumulate(sample)) # <1> -[5, 9, 11, 19, 26, 32, 35, 35, 44, 45] ->>> list(itertools.accumulate(sample, min)) # <2> -[5, 4, 2, 2, 2, 2, 2, 0, 0, 0] ->>> list(itertools.accumulate(sample, max)) # <3> -[5, 5, 5, 8, 8, 8, 8, 8, 9, 9] ->>> import operator ->>> list(itertools.accumulate(sample, operator.mul)) # <4> -[5, 20, 40, 320, 2240, 13440, 40320, 0, 0, 0] ->>> list(itertools.accumulate(range(1, 11), operator.mul)) -[1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800] # <5> ----- -==== -<1> Soma acumulada. -<2> Mínimo corrente. -<3> Máximo corrente. -<4> Produto acumulado. -<5> Fatoriais de `1!` a `10!`. - -As funções restantes da <> são demonstradas no <>. - -[[demo_mapping_genfunc]] -.Exemplos de funções geradoras de mapeamento -==== -[source, pycon] ----- ->>> list(enumerate('albatroz', 1)) # <1> -[(1, 'a'), (2, 'l'), (3, 'b'), (4, 'a'), (5, 't'), (6, 'r'), (7, 'o'), (8, 'z')] ->>> import operator ->>> list(map(operator.mul, range(11), range(11))) # <2> -[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100] ->>> list(map(operator.mul, range(11), [2, 4, 8])) # <3> -[0, 4, 16] ->>> list(map(lambda a, b: (a, b), range(11), [2, 4, 8])) # <4> -[(0, 2), (1, 4), (2, 8)] ->>> import itertools ->>> list(itertools.starmap(operator.mul, enumerate('albatroz', 1))) # <5> -['a', 'll', 'bbb', 'aaaa', 'ttttt', 'rrrrrr', 'ooooooo', 'zzzzzzzz'] ->>> sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1] ->>> list(itertools.starmap(lambda a, b: b / a, -... enumerate(itertools.accumulate(sample), 1))) # <6> -[5.0, 4.5, 3.6666666666666665, 4.75, 5.2, 5.333333333333333, -5.0, 4.375, 4.888888888888889, 4.5] ----- -==== -<1> Número de letras na palavra, começando por `1`. -<2> Os quadrados dos inteiros de `0` a `10`. -<3> Multiplicando os números de dois iteráveis em paralelo; os resultados cessam quando o iterável menor termina. -<4> Isso é o que faz a função embutida `zip`. -<5> 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 <>. - -[[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 <> 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]] -.Exemplos de funções geradoras de fusão -==== -[source, pycon] ----- ->>> list(itertools.chain('ABC', range(2))) # <1> -['A', 'B', 'C', 0, 1] ->>> list(itertools.chain(enumerate('ABC'))) # <2> -[(0, 'A'), (1, 'B'), (2, 'C')] ->>> list(itertools.chain.from_iterable(enumerate('ABC'))) # <3> -[0, 'A', 1, 'B', 2, 'C'] ->>> list(zip('ABC', range(5), [10, 20, 30, 40])) # <4> -[('A', 0, 10), ('B', 1, 20), ('C', 2, 30)] ->>> list(itertools.zip_longest('ABC', range(5))) # <5> -[('A', 0), ('B', 1), ('C', 2), (None, 3), (None, 4)] ->>> list(itertools.zip_longest('ABC', range(5), fillvalue='?')) # <6> -[('A', 0), ('B', 1), ('C', 2), ('?', 3), ('?', 4)] ----- -==== -[role="pagebreak-before less_space"] -<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. -<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 personalizado. - -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 seção <>. 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`.# - -[[demo_product_genfunc]] -.Exemplo da função geradora `itertools.product` -==== -[source, pycon] ----- ->>> list(itertools.product('ABC', range(2))) # <1> -[('A', 0), ('A', 1), ('B', 0), ('B', 1), ('C', 0), ('C', 1)] ->>> suits = 'spades hearts diamonds clubs'.split() ->>> list(itertools.product('AK', suits)) # <2> -[('A', 'spades'), ('A', 'hearts'), ('A', 'diamonds'), ('A', 'clubs'), -('K', 'spades'), ('K', 'hearts'), ('K', 'diamonds'), ('K', 'clubs')] ->>> list(itertools.product('ABC')) # <3> -[('A',), ('B',), ('C',)] ->>> list(itertools.product('ABC', repeat=2)) # <4> -[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'B'), -('B', 'C'), ('C', 'A'), ('C', 'B'), ('C', 'C')] ->>> list(itertools.product(range(2), repeat=3)) -[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), -(1, 0, 1), (1, 1, 0), (1, 1, 1)] ->>> rows = itertools.product('AB', range(2), repeat=2) ->>> for row in rows: print(row) -... -('A', 0, 'A', 0) -('A', 0, 'A', 1) -('A', 0, 'B', 0) -('A', 0, 'B', 1) -('A', 1, 'A', 0) -('A', 1, 'A', 1) -('A', 1, 'B', 0) -('A', 1, 'B', 1) -('B', 0, 'A', 0) -('B', 0, 'A', 1) -('B', 0, 'B', 0) -('B', 0, 'B', 1) -('B', 1, 'A', 0) -('B', 1, 'A', 1) -('B', 1, 'B', 0) -('B', 1, 'B', 1) ----- -==== -<1> O produto cartesiano de uma `str` com três caracteres e um `range` com dois inteiros produz seis tuplas (porque `3 * 2` é `6`). -<2> O produto de duas cartas altas (`'AK'`) e quatro naipes é uma série de oito tuplas. -<3> Dado um único iterável, `product` produz uma série de tuplas de um elemento—muito pouco útil. -<4> O argumento nomeado `repeat=N` diz à função para consumir cada iterável de entrada `N` vezes. - -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 -|============================================================================================================================================================================================================================================================= - -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 seção <>. -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`. - -[[demo_count_repeat_genfunc]] -.`count`, `cycle`, `pairwise`, e `repeat` -==== -[source, pycon] ----- ->>> ct = itertools.count() # <1> ->>> next(ct) # <2> -0 ->>> next(ct), next(ct), next(ct) # <3> -(1, 2, 3) ->>> list(itertools.islice(itertools.count(1, .3), 3)) # <4> -[1, 1.3, 1.6] ->>> cy = itertools.cycle('ABC') # <5> ->>> next(cy) -'A' ->>> list(itertools.islice(cy, 7)) # <6> -['B', 'C', 'A', 'B', 'C', 'A', 'B'] ->>> list(itertools.pairwise(range(7))) # <7> -[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)] ->>> rp = itertools.repeat(7) # <8> ->>> next(rp), next(rp) -(7, 7) ->>> list(itertools.repeat(8, 4)) # <9> -[8, 8, 8, 8] ->>> list(map(operator.mul, range(11), itertools.repeat(5))) # <10> -[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50] ----- -==== -<1> Cria `ct`, uma geradora `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'`. -<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`. - -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`]. -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]] -.Funções geradoras combinatórias produzem múltiplos valores para cada item de entrada -==== -[source, pycon] ----- ->>> list(itertools.combinations('ABC', 2)) # <1> -[('A', 'B'), ('A', 'C'), ('B', 'C')] ->>> list(itertools.combinations_with_replacement('ABC', 2)) # <2> -[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'B'), ('B', 'C'), ('C', 'C')] ->>> list(itertools.permutations('ABC', 2)) # <3> -[('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')] ->>> list(itertools.product('ABC', repeat=2)) # <4> -[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'B'), ('B', 'C'), -('C', 'A'), ('C', 'B'), ('C', 'C')] ----- -==== -<1> Todas as combinações com `len()==2` a partir dos itens em `'ABC'`; a ordem dos itens nas tuplas geradas é irrelevante (elas poderiam ser conjuntos). -<2> Todas as combinação com `len()==2` a partir dos itens em `'ABC'`, incluindo combinações com itens repetidos. -<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. - -[[expanding_genfunc_tbl2]] -.Funções geradoras de rearranjo -[options="header"] -|============================================================================================================================================================================================================================================================= -|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! -//// - -O <> demonstra o uso de `itertools.groupby` e da função embutida `reversed`. Observe que `itertools.groupby` assume que o iterável de entrada está ordenado pelo critério de agrupamento, ou que pelo menos os itens estejam agrupados por aquele critério—mesmo que não estejam completamente ordenados. -O revisor técnico Miroslav Šedivý sugeriu esse caso de uso: -você pode ordenar objetos `datetime` em ordem cronológica, e então `groupby` por dia da semana, para obter o grupo com os dados de segunda-feira, seguidos pelos dados de terça, etc., e então da segunda (da semana seguinte) novamente, e assim por diante. - -[[demo_groupby_reversed_genfunc]] -.`itertools.groupby` -==== -[source, pycon] ----- ->>> list(itertools.groupby('LLLLAAGGG')) # <1> -[('L', ), -('A', ), -('G', )] ->>> for char, group in itertools.groupby('LLLLAAAGG'): # <2> -... print(char, '->', list(group)) -... -L -> ['L', 'L', 'L', 'L'] -A -> ['A', 'A',] -G -> ['G', 'G', 'G'] ->>> animals = ['duck', 'eagle', 'rat', 'giraffe', 'bear', -... 'bat', 'dolphin', 'shark', 'lion'] ->>> animals.sort(key=len) # <3> ->>> animals -['rat', 'bat', 'duck', 'bear', 'lion', 'eagle', 'shark', -'giraffe', 'dolphin'] ->>> for length, group in itertools.groupby(animals, len): # <4> -... print(length, '->', list(group)) -... -3 -> ['rat', 'bat'] -4 -> ['duck', 'bear', 'lion'] -5 -> ['eagle', 'shark'] -7 -> ['giraffe', 'dolphin'] ->>> for length, group in itertools.groupby(reversed(animals), len): # <5> -... print(length, '->', list(group)) -... -7 -> ['dolphin', 'giraffe'] -5 -> ['shark', 'eagle'] -4 -> ['lion', 'bear', 'duck'] -3 -> ['bat', 'rat'] ->>> ----- -==== -<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. -<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. - -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 <>. - -[[demo_tee_genfunc]] -.`itertools.tee` produz múltiplos geradores, cada um produzindo todos os itens do gerador de entrada -==== -[source, pycon] ----- ->>> list(itertools.tee('ABC')) -[, ] ->>> g1, g2 = itertools.tee('ABC') ->>> next(g1) -'A' ->>> next(g2) -'A' ->>> next(g2) -'B' ->>> list(g1) -['B', 'C'] ->>> list(g2) -['C'] ->>> list(zip(*itertools.tee('ABC'))) -[('A', 'A'), ('B', 'B'), ('C', 'C')] ----- -==== - -Observe que vários exemplos nesta seção usam combinações de funções geradoras. Essa é uma excelente característica dessas funções: como recebem como argumentos e devolvem geradores, elas podem ser combinadas de muitas formas diferentes. - -Vamos agora revisar outro grupo de funções da biblioteca padrão que lidam com iteráveis.((("", startref="Ggenfunc17"))) - -[[iterable_reducing_sec]] -=== Funções de redução de iteráveis - -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 seção <>. - -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. -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 -`+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(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`) -|============================================================================================================================================================================================================================================================= - -O <> exemplifica a operação de `all` e de `any`. - -[[all_any_demo]] -.Resultados de `all` e `any` para algumas sequências -==== -[source, pycon] ----- ->>> all([1, 2, 3]) -True ->>> all([1, 0, 3]) -False ->>> all([]) -True ->>> any([1, 2, 3]) -True ->>> any([1, 0, 3]) -True ->>> any([0, 0.0]) -False ->>> any([]) -False ->>> g = (n for n in [0, 0.0, 7, 8]) ->>> any(g) # <1> -True ->>> next(g) # <2> -8 ----- -==== -<1> `any` iterou sobre `g` até `g` produzir `7`; neste momento `any` parou e devolveu `True`. -<2> É por isso que `8` ainda restava. - -Outra função embutida que recebe um iterável e devolve outra coisa é `sorted`. -Diferente de `reversed`, que é uma função geradora, `sorted` cria e devolve uma nova `list`. -Afinal, cada um dos itens no iterável de entrada precisa ser lido para que todos possam ser ordenados, e a ordenação acontece em uma `list`; `sorted` então apenas devolve aquela `list` após terminar seu processamento. -Menciono `sorted` aqui porque ela consome um iterável arbitrário. - -Claro, `sorted` e as funções de redução só funcionam com iteráveis que terminam em algum momento. -Caso contrário, eles seguirão coletando itens e nunca devolverão um resultado. - -[NOTE] -==== -Se você chegou até aqui, já viu o conteúdo mais importante e útil deste capítulo. -As seções restantes tratam de recursos avançados de geradores, que a maioria de nós não vê ou precisa com muita frequência, tal como a instrução `yield from` e as corrotinas clássicas. - -Há também seções sobre dicas de tipo para iteráveis, iteradores e corrotinas clássicas. -==== - -A sintaxe `yield from` fornece uma nova forma de combinar geradores. É nosso próximo assunto.((("", startref="Greduc17")))((("", startref="Ireduc17")))((("", startref="redfunc17"))) - - -[[yield_from_sec0]] -=== Subgeradoras com yield from - -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: - -[source, pycon] ----- ->>> def sub_gen(): -... yield 1.1 -... yield 1.2 -... ->>> def gen(): -... yield 1 -... for i in sub_gen(): -... yield i -... yield 2 -... ->>> for x in gen(): -... print(x) -... -1 -1.1 -1.2 -2 ----- - -Podemos obter o mesmo resultado usando `yield from`, como se vê no <>. - -[[ex_simple_yield_from]] -.Experimentando `yield from` -==== -[source, pycon] ----- ->>> def sub_gen(): -... yield 1.1 -... yield 1.2 -... ->>> def gen(): -... yield 1 -... yield from sub_gen() -... yield 2 -... ->>> for x in gen(): -... print(x) -... -1 -1.1 -1.2 -2 ----- -==== - -No <>, o((("delegating generators")))((("subgenerators")))((("client codes"))) loop `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. -Enquanto isso, `gen` está suspenso e não pode ver os valores que passam por ele. -`gen` continua apenas quando `sub_gen` termina. - -Quando o subgerador contém uma instrução `return` com um valor, aquele valor pode ser capturado pelo gerador delegante, com o uso de `yield from` como parte de uma expressão. -Veja a demonstração no <>. - -[[ex_simple_yield_from_return]] -.`yield from` recebe o valor devolvido pelo subgerador -==== -[source, pycon] ----- ->>> def sub_gen(): -... yield 1.1 -... yield 1.2 -... return 'Done!' -... ->>> def gen(): -... yield 1 -... result = yield from sub_gen() -... print('<--', result) -... yield 2 -... ->>> for x in gen(): -... print(x) -... -1 -1.1 -1.2 -<-- Done! -2 ----- -==== - -Agora que já vimos o básico sobre `yield from`, vamos estudar alguns exemplos simples mas práticos de sua utilização. - -[[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, -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.] - -[source, pycon] ----- ->>> def chain(*iterables): -... for it in iterables: -... for i in it: -... yield i -... ->>> s = 'ABC' ->>> r = range(3) ->>> list(chain(s, r)) -['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: - -[source, pycon] ----- ->>> def chain(*iterables): -... for i in iterables: -... yield from i -... ->>> list(chain(s, t)) -['A', 'B', 'C', 0, 1, 2] ----- - -O uso de `yield from` neste exemplo está correto, e o código é mais legível, mas parece açúcar sintático, com pouco ganho real. -Vamos então desenvolver um exemplo mais interessante. - - -[[traversing_tree_sec]] -==== Percorrendo uma árvore - -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] do 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. - -[[ex_tree_step0]] -.tree/step0/tree.py: produz o nome da classe raiz e para -==== -[source, py] ----- -include::code/17-it-generator/tree/step0/tree.py[] ----- -==== - -A saída do <> tem apenas uma linha: - -[source, bash] ----- -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. -Os nomes das subclasses são indentados para explicitar a hierarquia. -Esta é a saída que queremos: - -[source, bash] ----- -$ python3 tree.py -BaseException - Exception - GeneratorExit - SystemExit - KeyboardInterrupt ----- - -O <> produz a saída acima. - -[[ex_tree_step1]] -.tree/step1/tree.py: produz o nome da classe raiz e das subclasses diretas -==== -[source, py] ----- -include::code/17-it-generator/tree/step1/tree.py[] ----- -==== -<1> Para suportar a saída indentada, produz o nome da classe e seu nível na hierarquia. -<2> Usa o método especial `+__subclasses__+` para obter uma lista de subclasses. -<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. - -[[ex_tree_step2]] -.tree/step2/tree.py: `tree` produz o nome da classe raiz, e entao delega para `sub_tree` -==== -[source, py] ----- -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` ... -<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 <>. - -[[ex_tree_step3]] -.tree/step3/tree.py: `sub_tree` percorre os níveis 1 e 2, primeiro em profundidade -==== -[source, py] ----- -include::code/17-it-generator/tree/step3/tree.py[] ----- -==== - -Este é o resultado da execução de _step3/tree.py_, do <>: - -[source, bash] ----- -$ python3 tree.py -BaseException - Exception - TypeError - StopAsyncIteration - StopIteration - ImportError - OSError - EOFError - RuntimeError - NameError - AttributeError - SyntaxError - LookupError - ValueError - AssertionError - ArithmeticError - SystemError - ReferenceError - MemoryError - BufferError - Warning - GeneratorExit - SystemExit - KeyboardInterrupt ----- - -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`. - -[[ex_tree_step4]] -.A geradora `sub_tree` de _tree/step4/tree.py_ -==== -[source, py] ----- -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. - -Na seção <>, vimos como é possível substituir um loop `for` aninhado controlando uma geradora com `yield from` sobre a mesma geradora. -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 <>. - -[[ex_tree_step5]] -.tree/step5/tree.py: a `sub_tree` recursiva vai tão longe quanto a memória permitir -==== -[source, py] ----- -include::code/17-it-generator/tree/step5/tree.py[] ----- -==== - -O <> pode percorrer árvores de qualquer profundidade, -limitado apenas pelo limite de recursão do Python. -O limite default permite 1.000 funções pendentes. - -Qualquer bom tutorial sobre recursão enfatizará a importância de ter um caso base, -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, -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. - -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` -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. -O <> é o último passo deste exemplo. - -[[ex_tree_step6]] -.tree/step6/tree.py: chamadas recursivas de `tree` passam um argumento `level` incrementado -==== -[source, py] ----- -include::code/17-it-generator/tree/step6/tree.py[] ----- -==== - -No início da seção <>, 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 seção <>. - -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"))) - -[[generic_iterable_types_sec]] -=== Tipos iteráveis genéricos - -A((("generators", "generic iterable types")))((("iterators", "generic iterable types"))) bilbioteca padrão do Python contém muitas funções que aceitam argumentos iteráveis. -Em seu código, tais funções podem ser anotadas como a função `zip_replace`, vista no <>, -usando `collections.abc.Iterable` (ou `typing.Iterable`, se você precisa suporta o Python 3.8 ou anterior, como explicado no <>). Veja o <>. - -[[replacer_iterable_ex]] -.replacer.py devolve um iterador de tuplas de strings -==== -[source, py] ----- -include::code/08-def-type-hints/replacer.py[tags=ZIP_REPLACE] ----- -==== -<1> Define um apelido (_alias_) de tipo; isso não é obrigatório, mas torna a próxima dica de tipo mais legível. -Desde o Python 3.10, `FromTo` deve ter uma dica de tipo de `typing.TypeAlias`, para esclarecer a razão para essa linha: `FromTo: TypeAlias = tuple[str, str]`. -<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. - -[[fibo_gen_annot_ex]] -._fibo_gen.py_: `fibonacci` devolve um gerador de inteiros -==== -[source, py] ----- -include::code/17-it-generator/fibo_gen.py[] ----- -==== - -Observe que o tipo `Iterator` é usado para geradoras 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, -que não recebem valores via `.send()`. - -O <>, quando verificado com o Mypy, revela que o tipo `Iterator` é, na verdade, um caso especial simplificado do tipo `Generator`. - -[[iter_gen_type_ex]] -.itergentype.py: duas formas de anotar iteradores -==== -[source, py] ----- -include::code/17-it-generator/iter_gen_type.py[] ----- -==== -<1> Uma expressão geradora que produz palavras reservadas do Python com menos de `5` caracteres. -<2> O Mypy infere: `typing.Generator[builtins.str*, None, None]`.footnote:[Na versão 0.910, a versão mais recente disponível quando escrevi este capítulo), o Mypy ainda utiliza os tipos descontinuados de `typing`.] -<3> Isso também produz strings, mas acrescentei uma dica de tipo explícita. -<4> Tipo revelado: `typing.Iterator[builtins.str]`. - -`abc.Iterator[str]` é _consistente-com_ `abc.Generator[str, None, None]`, -assim o Mypy não reporta erros na verificação de tipos 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." -Geradoras capazes de consumir e devolver valores são corrotinas, nosso próximo tópico. - - -[[classic_coroutines_sec]] -=== Corrotinas clássicas - -[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 do 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". - -Após o lançamento do 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. -Vamos então dar um passo atrás e examinar outro recurso do Python que pode ser usado de duas maneiras. - -Vimos na seção <> que é possível usar instâncias de `tuple` como registros ou como sequências imutáveis. -Quando usadas como um registro, se espera que uma tupla tenha um número específico de itens, e cada item pode ter um tipo diferente. -Quando usadas como listas imutáveis, uma tupla pode ter qualquer tamanho, e se espera que todos os itens sejam do mesmo tipo. -Por essa razão, há duas formas de anotar tuplas com dicas de tipo: - -[source, py] ----- -# Um registro de cidade, como nome, país e população: -city: tuple[str, str, int] - -# Uma sequência imutável de nomes de domínios: -domains: tuple[str, ...] ----- - -Algo similar ocorre com geradoras. -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: - -[source, py] ----- -# A variável `readings` pode ser delimitada a um iterador -# ou a um objeto gerador que produz itens `float`: -readings: Iterator[float] - -# A variável `sim_taxi` pode ser delimitada a uma corrotina -# representando um táxi em uma simulação de eventos discretos. -# Ela produz eventos, recebe um `float` de data/hora, e devolve -# o número de viagens realizadas durante a simulação: -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. - -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`: - -[source, py] ----- -Generator[YieldType, SendType, ReturnType] ----- - -O `SendType` só é relevante quando a geradora é 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. -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 -outras formas de iteração. O `YieldType` é o tipo do valor -devolvido em uma chamada a `next(it)`. - -O tipo `Generator` tem os mesmo parâmetros de tipo de https://fpy.li/typecoro[`typing.Coroutine`]: - -[source, py] ----- -Coroutine[YieldType, SendType, ReturnType] ----- - -A -https://fpy.li/typecoro[documentação de `typing.Coroutine`] -diz literalmente: -"A variância e a ordem das variáveis de tipo correspondem às de `Generator`." -Mas `typing.Coroutine` (descontinuada) -e `collections.abc.Coroutine` (genérica a partir do Python 3.9) -foram projetadas para anotar apenas corrotinas nativas, e não corrotinas clássicas. -Se você quiser usar dicas de tipo com corrotinas clássicas, -vai sofrer com a confusão advinda de anotá-las como -`Generator[YieldType, SendType, ReturnType]`. - -David Beazley criou algumas das melhores palestras e algumas das oficinas mais abrangentes sobre corrotinas clássicas. -No https://fpy.li/17-18[material de seu curso na PyCon 2009] há um slide chamado "Keeping It Straight" (_Cada Coisa em Seu Lugar_), onde se lê: - -____ -* Geradoras produzem dados para iteração -* Corrotinas são consumidoras de dados -* Para evitar que seu cérebro exploda, não misture os dois conceitos -* Corrotinas não tem relação com iteração -* Nota: Há uma forma de fazer `yield` produzir um valor em uma corrotina, mas isso não está ligado à iteração.footnote:[Slide 33, "Keeping It Straight" (_Cada Coisa em seu Lugar_) em https://fpy.li/17-18["A Curious Course on Coroutines and Concurrency" (_Um Curioso Curso sobre Corrotinas e Concorrência_)].] -____ - - -Vamos ver agora como as corrotinas clássicas funcionam. - - -==== Exemplo: Corrotina para computar uma média móvel - -Quando((("coroutines", "computing running averages", id="CRavg17")))((("running averages, computing", id="runavg17")))((("averages, computing", id="avg17"))) discutimos clausuras no <>, -estudamos objetos para computar uma média móvel. -O <> mostra uma classe e o <> apresenta uma função de ordem superior devolvendo uma função que mantem as variáveis `total` e `count` entre invocações, em uma clausura. -O <> mostra como fazer o mesmo com uma corrotina.footnote:[Este exemplo foi inspirado por um trecho enviado por Jacob Holm à lista Python-ideas, em uma mensagem intitulada https://fpy.li/17-20["Yield-From: Finalization guarantees" (_Yield-From: Garantias de finalização_)] (EN). Algumas variantes aparecem mais tarde na mesma thread, e Holm dá mais explicações sobre suas ideia em https://fpy.li/17-21[message 003912] (EN).] - -[[ex_coroaverager]] -.coroaverager.py: corrotina para computar uma média móvel -==== -[source, py] ----- -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. - -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 -enquanto a corrotina está suspensa, esperando pelo próximo `.send()`. -Por isso as corrotinas são substitutas atraentes para _callbacks_ em programação assíncrona—elas mantêm o estado local entre ativações. - -O <> executa doctests mostrando a corrotina `averager` em operação. - -[[ex_coroaverager_test]] -.coroaverager.py: doctest para a corrotina de média móvel do <> -==== -[source, py] ----- -include::code/17-it-generator/coroaverager.py[tags=CORO_AVERAGER_TEST] ----- -==== -<1> Cria o objeto corrotina. -<2> Inicializa a corrotina. Isso produz o valor inicial de `average`: 0.0. -<3> Agora estamos conversando: cada chamada a `.send()` produz a média atual. - -No <>, a chamada `next(coro_avg)` faz a corrotina avançar até o `yield`, produzindo o valor inicial de `average`. -Também é possível inicializar a corrotina chamando `coro_avg.send(None)`—na verdade é isso que a função embutida `next()` faz. -Mas você não pode enviar qualquer valor diferente de `None`, -pois a corrotina só pode aceitar um valor enviado quando está suspensa, em uma linha de `yield`. -Invocar `next()` ou `.send(None)` para avançar até o primeiro `yield` é conhecido como "preparar (priming) a corrotina". - -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 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. -Se for necessário encerrá-la explicitamente, use o método `.close()`, como mostra o <>. - -[[ex_coroaverager_test_cont]] -.coroaverager.py: continuando de <> -==== -[source, py] ----- -include::code/17-it-generator/coroaverager.py[tags=CORO_AVERAGER_TEST_CONT] ----- -==== -<1> `coro_avg` é a instância criada no <>. -<2> O método `.close()` gera uma exceção `GeneratorExit` na expressão `yield` suspensa. -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`. - -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. -A próxima seção mostra como fazer isso.((("", startref="avg17")))((("", startref="runavg17")))((("", startref="CRavg17"))) - -[[coro_return_sec]] -==== Devolvendo um valor a partir de uma corrotina - -Vamos((("coroutines", "returning values from", id="Cvalue17"))) agora estudar outra corrotina para computar uma média. -Essa versão não vai produzir resultados parciais. -Em vez disso, ela devolve uma tupla com o número de termos e a média. -Dividi a listagem em duas partes, no <> e no <>. - -[[ex_returning_averager_top]] -.coroaverager2.py: a primeira parte do arquivo -==== -[source, py] ----- -include::code/17-it-generator/coroaverager2.py[tags=RETURNING_AVERAGER_TOP] ----- -==== -<1> A corrotina `averager2` no <> vai devolver uma instância de `Result`. -<2> `Result` é, na verdade, uma subclasse de `tuple`, que tem um método `.count()`, que não preciso aqui. O comentário `# type: ignore` evita que o Mypy reclame sobre a existência do campo `count`.footnote:[Considerei renomear o campo, mas `count` é o melhor nome para a variável local na corrotina, e é o nome que usei para essa variável em exemplos similares ao longo do livro, então faz sentido usar o mesmo nome no campo de `Result`. Não hesitei em usar `# type: ignore` para evitar as limitações e os aborrecimentos de verificadores de tipo estáticos, quando se submeteer à ferramenta tornaria o código pior ou desnecessariamente complicado.] -<3> Uma classe para criar um valor sentinela com um `+__repr__+` legível. -<4> O valor sentinela que vou usar para fazer a corrotina parar de coletar dados e devolver uma resultado. -<5> Vou usar esse apelido de tipo para o segundo parâmetro de tipo devolvido pela corrotina `Generator`, o parâmetro `SendType`. - -A definição de `SendType` também funciona no Python 3.10 mas, se não for necessário suportar versões mais antigas, é melhor escrever a anotação assim, após importar `TypeAlias` de `typing`: - -[source, py] ----- -SendType: TypeAlias = float | Sentinel ----- - -Usar `|` em vez de `typing.Union` é tão conciso e legível que eu provavelmente não criaria aquele apelido de tipo. Em vez disso, escreveria a assinatura de `averager2` assim: - -[source, py] ----- -def averager2(verbose: bool=False) -> Generator[None, float | Sentinel, Result]: ----- - -Vamos agora estudar o código da corrotina em si (no <>). - -[[ex_returning_averager_coro]] -.coroaverager2.py: uma corrotina que devolve um valor resultante -==== -[source, py] ----- -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 verificação 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. - -Vamos ver agora como podemos usar essa corrotina, começando por um exemplo simples, que sequer produz um resultado (no <>). - -[[ex_coro_averager2_demo_1]] -.coroaverager2.py: doctest mostrando `.cancel()` -==== -[source, py] ----- -include::code/17-it-generator/coroaverager2.py[tags=RETURNING_AVERAGER_DEMO_1] ----- -==== -<1> Lembre-se que `averager2` não produz resultados parciais. Ela produz `None`, que o console do Python omite. -<2> Invocar `.close()` nessa corrotina a faz parar, mas não devolve um resultado, pois a exceção `GeneratorExit` é gerada na linha `yield` da corrotina, então a instrução `return` nunca é alcançada. - -Vamos então fazê-la funcionar, no <>. - -[[ex_coro_averager2_demo_2]] -.coroaverager2.py: doctest mostrando `StopIteration` com um `Result` -==== -[source, py] ----- -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`. -<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"] -do capítulo 6 de -https://fpy.li/17-24[_A Referência da Linguagem Python_]. - - -Uma geradora delegante pode obter o valor devolvido por uma corrotina diretamente, usando a sintaxe -`yield from`, como demonstrado no <>. - -[[ex_coro_averager2_demo_3]] -.coroaverager2.py: doctest mostrando `StopIteration` com um `Result` -==== -[source, py] ----- -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. -<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. -<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, -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. -Acrescente o tratamento de exceções e o método de corrotina `.throw()`, e os exemplos ficam ainda mais complicados. -Não vou tratar de `.throw()` nesse livro porque—como `.send()`—ele só é útil para controlar corrotinas "manualmente", e não recomendo fazer isso, a menos que você esteja criando uma nova framework baseada em corrotinas do zero . - -[NOTE] -==== -Se você estiver interessado 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 uma framework de programação assíncrona. -==== - -Na prática, realizar trabalho produtivo com corrotinas exige o suporte de uma 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 desenvolvedores principais do 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ó. -Internamente, as corrotinas nativas usam `await` em vez de `yield from` para delegar a outras corrotinas. -O <> é todo sobre esse assunto. - -Vamos agora encerrar o capítulo com uma seção alucinante sobre co-variância e contra-variância em dicas de tipo para corrotinas.((("", startref="Cvalue17"))) - - -[[generic_classic_coroutine_types_sec]] -==== Dicas de tipo genéricas para corrotinas clássicas - -Anteriomente((("coroutines", "generic type hints for")))((("type hints (type annotations)", "generic type hints for coroutines"))), na seção <>, -mencionei `typing.Generator` como um dos poucos tipos da biblioteca padrão com um parâmetro de tipo contra-variante. -Agora que estudamos as corrotinas clássicas, estamos prontos para entender esse tipo genérico. - -É assim que `typing.Generator` era https://fpy.li/17-25[declarado] -no módulo _typing.py_ do Python 3.6:footnote:[Desde o Python 3.7, -`typing.Generator` e outros tipos que correspondem a ABCs em`collections.abc` foram refatorados e encapsulads em torno da ABC correspondente, então seus parâmetros genéricos não são visíveis no código-fonte de _typing.py_ . Por isso estou fazendo referência ao código-fonte do Python 3.6 aqui.] - -[source, python3] ----- -T_co = TypeVar('T_co', covariant=True) -V_co = TypeVar('V_co', covariant=True) -T_contra = TypeVar('T_contra', contravariant=True) - -# muitas linhas omitidas - -class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co], - extra=_G_base): ----- - -Essa declaração de tipo genérico significa que uma dica de tipo de `Generator` requer aqueles três parâmetros de tipo que vimos antes: - -[source, python3] ----- -my_coro : Generator[YieldType, SendType, ReturnType] ----- - -Pelas variáveis de tipo nos parâmetros formais, vemos que `YieldType` e `ReturnType` são covariantes, mas `SendType` é contra-variante. -Para entender a razão disso, considere que `YieldType` e `ReturnType` são tipos de "saída". -Ambos descrevem dados que saem do objeto corrotina—isto é, o objeto gerador quando usado como um objeto corrotina.. - -Faz sentido que esses parâmetros sejam covariantes, pois qualquer código esperando uma corrotina que produz números de ponto flutuante pode usar uma corrotina que produz inteiros. -Por isso `Generator` é covariante em seu parâmetro `YieldType`. -O mesmo raciocínio se aplica ao parâmetro `ReturnType`—também covariante. - -Usando a notação introduzida na seção <>, a covariância do primeiro e do terceiro parâmetros pode ser expressa pelos símbolos `:>` apontando para a mesma direção: - -[source] ----- - float :> int -Generator[float, Any, float] :> Generator[int, Any, int] ----- - -`YieldType` e `ReturnType` são exemplos da primeira regra apresentada na seção <>: - -[quote] -____ -. Se um parâmetro de tipo formal define um tipo para dados que saem de um objeto, ele pode ser covariante. -____ - -Por outro lado, `SendType` é um parâmetro de "entrada": -ele é o tipo do argumento `value` para o método `.send(value)` do objeto corrotina. -Código cliente que precise enviar números de ponto flutuante para uma corrotina não consegue usar uma corrotina que receba `int` como o `SendType`, porque `float` não é um subtipo de `int`. -Em outras palavras, `float` não é _consistente-com_ `int`. -Mas o cliente pode usar uma corrotina que tenha `complex` como `SendType`, -pois `float` é um subtipo de `complex`, -e portanto `float` é _consistente-com_ `complex`. - -A notação `:>` torna visível a contra-variância do segundo parâmetro: - -[source] ----- - float :> int -Generator[Any, float, Any] <: Generator[Any, int, Any] ----- - -Este é um exemplo da segunda regra geral da variância: - -[quote] -____ -[start=2] -. Se um parâmetro de tipo formal define um tipo para dados que entram em um objeto após sua construção inicial, ele pode ser contra-variante. -____ - -Essa alegre discussão sobre variância encerra o capítulo mais longo do livro. - - -=== Resumo do capítulo - -A iteração((("iterators", "overview of")))((("generators", "overview of")))((("coroutines", "overview of"))) está integrada tão profundamente à linguagem que eu gosto de dizer que o Python _groks_ iteradoresfootnote:[De acordo com o https://fpy.li/17-26[Jargon file] (EN), _to grok_ não é meramente aprender algo, mas absorver de uma forma que "aquilo se torna parte de você, parte de sua identidade".] -A integração do padrão _Iterator_ na semântica do Python é um exemplo perfeito de como padrões de projeto não são aplicáveis a todas as linguagens de programação. -No Python, um _Iterator_ clássico, implementado "à mão", como no <>, não tem qualquer função prática, exceto como exemplo didático. - -Neste capítulo, criamos algumas versões de uma classe para iterar sobre palavras individuais em arquivos de texto (que podem ser muito grandes). -Vimos como o Python usa a função embutida `iter()` para criar iteradores a partir de objetos similares a sequências. -Criamos um iterador clássico como uma classe com `+__next__()+`, e então usamos geradoras, -para tornar cada refatoração sucessiva da classe `Sentence` mais concisa e legível. - -Daí criamos uma geradora de progressões aritméticas, e mostramos como usar o módulo `itertools` para torná-la mais simples. -A isso se seguiu uma revisão da maioria das funções geradoras de uso geral na biblioteca padrão. - -A seguir estudamos expressões `yield from` no contexto de geradoras simples, com os exemplos `chain` e `tree`. - -A última seção de nota foi sobre corrotinas clássicas, um tópico de importância decrescente após a introducão das corrotinas nativas, no Python 3.5. -Apesar de difíceis de usar na prática, corrotinas clássicas são os alicerces das corrotinas nativas, e a expressão `yield from` é uma precursora direta de `await`. - -Dicas de tipo para os tipos `Iterable`, `Iterator`, e `Generator` também foram abordadas—com esse último oferecendo um raro exemplo concreto de um parâmetro de tipo contra-variante. - - -=== Leitura complementar - -Uma((("iterators", "further reading on")))((("generators", "further reading on")))((("coroutines", "further reading on"))) explicação técnica detalhada sobre geradoras aparece na _A Referência da Linguagem Python_, em https://docs.python.org/pt-br/3/reference/expressions.html#yieldexpr["6.2.9. Expressões yield"]. -A PEP onde as funções geradoras foram definidas é a https://fpy.li/pep255[PEP 255--Simple Generators (_Geradoras Simples_)]. - -A pass:[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. - -Além da bilbioteca padrão do 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. - -"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`. - -Sebastian Rittau—atualmente um dos principais colaboradores do _typeshed_—explica porque iteradores devem ser iteráveis. Ele observou, em 2006, que https://fpy.li/17-31["Java: Iterators are not Iterable" (_Java:Iteradores não são Iteráveis_)]. - -A sintaxe de `yield from` é explicada, com exemplos, na seção "What’s New in Python 3.3" (_Novidades no Python 3.3_) da https://fpy.li/17-32[PEP 380--Syntax for Delegating to a Subgenerator (_Sintaxe para Delegar para um Subgerador_)]. Meu artigo -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., -(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: -https://fpy.li/17-33["Generator Tricks for Systems Programmers" (_Truques com Geradoras para Programadores de Sistemas_)] (EN). -A PyCon US 2009 assisitiu ao lendário -https://fpy.li/17-34["A Curious Course on Coroutines and Concurrency" (_Um Curioso Curso sobre Corrotinas e Concorrência_)] (EN) -(links de vídeo difíceis de encontrar para todas as três partes: -https://fpy.li/17-35[parte 1], -https://fpy.li/17-36[parte 2], e -https://fpy.li/17-37[parte 3]). -Seu tutorial na PyCon 2014 em Montreal foi -https://fpy.li/17-38["Generators: The Final Frontier" (_Geradoras: A Fronteira Final_)], onde ele apresenta mais exemplos de concorrência—então é, na verdade, mais relacionado aos tópicos do <>. -Dave não consegue deixar de explodir cérebros em suas aulas, então, na última parte de "A Fronteira Final", corrotinas substituem o padrão clássico _Visitor_ em um analisador de expressões aritméticas. - -Corrotinas permitem organizar o código de novas maneiras e, assim como a recursão e o polimorfismo (despacho dinâmico), demora um certo tempo para se acostumar com suas possibilidades. -Um exemplo interessante de um algoritmo clássico reescrito com corrotinas aparece no post https://fpy.li/17-39["Greedy algorithm with coroutines" (_O Algoritmo guloso com corrotinas_)], de James Powell. - -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. -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]. - -.Ponto de Vista -**** - -[role="soapbox-title"] -A interface Iterador minimalista do Python - -Na((("iterators", "Soapbox discussion")))((("generators", "Soapbox discussion")))((("coroutines", "Soapbox discussion")))((("Soapbox sidebars", "minimalistic iterator interface"))) seção "Implementação" do padrão _Iterator_,footnote:[Gamma et. al., _Design Patterns: Elements of Reusable Object-Oriented Software_, p. 261.], a Guange dos Quatro escreveu: - -[quote] -____ -A interface mínima de Iterator consiste das operações First, Next, IsDone, e CurrentItem. -____ - -Entretanto, essa mesma frase tem uma nota de rodapé, onde se lê: - -[quote] -____ -Podemos tornar essa interface ainda menor, fundindo Next, IsDone, e CurrentItem em uma única operação que avança para o próximo objeto e o devolve. Se a travessia estiver encerrada, essa operação daí devolve um valor especial (0, por exemplo), que marca o final da iteração. -____ - -Isso é próximo do que temos em Python: um único método `+__next__+`, faz o serviço. Mas em vez de uma sentinela, que poderia passar desapercebida por enganoo ou distração, a exceção `StopIteration` sinaliza o final da iteração. Simples e correto: esse é o jeito do Python. - -[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. - -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. - -Desde o final dos anos 1980, o sistema de banco de dados usado para gerenciar o LILACS é o CDS/ISIS, um banco de dados não-relacional de documentos, criado pela UNESCO. -Uma de minhas tarefas era pesquisar alternativas para uma possível migração do LILACS--e depois do SciELO, muito maior--para um banco de dados de documentos moderno e de código aberto, tal como o CouchDB ou o MongoDB. -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_. - -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. - -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 o Python 3.] - -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_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 maiores 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. - -`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. - -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. -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"))) - -**** diff --git a/capitulos/cap18.adoc b/capitulos/cap18.adoc deleted file mode 100644 index 2d0f5d7a..00000000 --- a/capitulos/cap18.adoc +++ /dev/null @@ -1,1360 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo - - -[[with_match_ch]] -== Instruções with, match, e blocos else - -[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` é muito mais que isso.footnote:[Palestra de abertura da PyCon US 2013: https://fpy.li/18-1["What Makes Python Awesome" ("_O que torna o Python incrível_")]; a parte sobre `with` começa em 23:00 e termina em 26:15.] (EN) -____ - - -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 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`. - -=== Novidades nesse capítulo - -A seção <> é nova. - -Também((("with, match, and else blocks", "significant changes to"))) atualizei a seção <> 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. - -Vamos começar com a poderosa instrução `with`. - -[[context_managers_sec]] -=== Gerenciadores de contexto e a instrução with - -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`. - -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 personalizados 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). - -A((("context managers", "methods included in interface")))((("__enter__")))((("__exit__"))) interface gerenciador de contexto consiste dos métodos `+__enter__+` and `+__exit__+`. -No topo do `with`, o Python chama o método `+__enter__+` do objeto gerenciador de contexto. Quando o bloco `with` encerra ou termina por qualquer razão, 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 -==== -[source, pycon] ----- ->>> with open('mirror.py') as fp: # <1> -... src = fp.read(60) # <2> -... ->>> len(src) -60 ->>> fp # <3> -<_io.TextIOWrapper name='mirror.py' mode='r' encoding='UTF-8'> ->>> fp.closed, fp.encoding # <4> -(True, 'UTF-8') ->>> fp.read(60) # <5> -Traceback (most recent call last): - File "", line 1, in -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`. -<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. - -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. - -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 chamar seus métodos. Mas alguns gerenciadores de contexto devolvem `None`, pois não tem nenhum 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` -==== -[source, py] ----- -include::code/18-with-match/mirror.py[tags=MIRROR_DEMO_1] ----- -==== -<1> O gerenciador de contexto é uma instância de `LookingGlass`; o 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'`. -<4> A saída do programa não está mais invertida. - - -O <> mostra a implementação de `LookingGlass`. - -[[looking_glass_ex]] -.mirror.py: código da classe gerenciadora de contexto `LookingGlass` -==== -[source, py] ----- -include::code/18-with-match/mirror.py[tags=MIRROR_EX] ----- -==== -<1> O 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, o 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. -<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. - -[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`. -==== - -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`. - -`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.] - -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__+`. - -[[looking_glass_demo_2]] -.Exercitando o `LookingGlass` sem um bloco `with` -==== -[source, py] ----- -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"))) - - -.Gerenciadores de contexto entre parênteses -[TIP] -==== -O 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)]. -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: - -[source, py] ----- -with ( - CtxManager1() as example1, - CtxManager2() as example2, - CtxManager3() as example3, -): - ... ----- - -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. - -[[context_utilities_sec]] -==== Utilitários do 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 do 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`. - -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__+`. - -`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. - -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 seção <>. - -`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. - -`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 o 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 <>. - -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`. - -[[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 do 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. - -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. - -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 -==== -[source, py] ----- -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). -<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. - -O <> mostra a função `looking_glass` em operação. - -[[looking_glass_gen_demo]] -.Testando a função gerenciadora de contexto `looking_glass` -==== -[source, py] ----- -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`. - -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__ do 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`. - -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`. - -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 <> 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 <> -==== -[source, py] ----- -include::code/18-with-match/mirror_gen_exc.py[tags=MIRROR_GEN_EXC] ----- -==== -<1> Cria uma variável para uma possível mensagem de erro; essa é a primeira mudança em relação a <>. -<2> Trata `ZeroDivisionError`, fixando uma mensagem de erro. -<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. - -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!] -==== - -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`. - -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. -==== -[source, py] ----- -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. - -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. - -[[inplace_ex]] -.Um gerenciador de contexto para reescrever arquivos no lugar -==== -[source, python3] ----- -import csv - -with inplace(csvfilename, 'r', newline='') as (infh, outfh): - reader = csv.reader(infh) - writer = csv.writer(outfh) - - for row in reader: - row += ['new', 'columns'] - writer.writerow(row) ----- -==== - -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). - -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. - -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"))) seção <>, vimos exemplos de sequências de padrões extraídos da funcão `evaluate` do interpretador _lis.py_ de Peter Norvig, portado para o 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: - -. 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 o 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. - - -==== 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] - -O <> mostra um exemplo simples em Scheme. - -[[ex_gcd_scheme]] -.Maior divisor comum em Scheme -==== -[source, scheme] ----- -(define (mod m n) - (- m (* n (quotient m n)))) - -(define (gcd m n) - (if (= n 0) - m - (gcd n (mod m n)))) - -(display (gcd 18 45)) ----- -==== - -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_]). - -[[ex_gcd_python]] -.Igual ao <>, mas escrito em Python -==== -[source, py] ----- -def mod(m, n): - return m - (m // n * n) - -def gcd(m, n): - if n == 0: - return m - else: - return gcd(n, mod(m, n)) - -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. - -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 <>.] - -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 -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"))) - - -==== 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 o Python 3.10. - -[[lis_top_ex]] -.lis.py: início do arquivo -==== -[source, py] ----- -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_—é muito 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. - -`Expression`:: Os componentes básicos de programas Scheme são expressões feitas de átomos e listas, possivelmente aninhados. - -==== 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 do 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 funcões do analisador -==== -[source, py] ----- -include::code/18-with-match/lispy/py3.10/lis.py[lines=27..36] - # mais código do analisador omitido na listagem do livro ----- -==== - -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. - -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. - -Aqui estão alguns doctests estraídos do https://fpy.li/18-18[_lispy/py3.10/examples_test.py_]: - -[source, pycon] ----- -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: - -. 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. - -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"] - -==== 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 <>. - - -[[environment_class_ex]] -._lis.py_: a classe `Environment` -==== -[source, py] ----- -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.] -Tentar mudar uma chave não encontrada causa um `KeyError`. - -Esse doctest mostra como `Environment` funciona: - -[source, pycon] ----- -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__+` do Python, que está sempre disponível (<>). - -[[lis_std_env_ex]] -.lis.py: `standard_env()` constrói e devolve o ambiente global -==== -[source, py] ----- -include::code/18-with-match/lispy/py3.10/lis.py[lines=77..85] - # omitted here: more operator definitions -include::code/18-with-match/lispy/py3.10/lis.py[lines=92..97] - # omitted here: more function definitions -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` do Python -* Operadores selecionados do módulo `op` do Python -* Funções simples porém poderosas construídas com o `lambda` do Python -* Estruturas e entidades embutidas do Python, ou 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 do 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 do Python.] - -[role="pagebreak-before less_space"] -[[ex_lispy_repl]] -.As funções do REPL -==== -[source, python3] ----- -include::code/18-with-match/lispy/py3.10/lis.py[tags=REPL] ----- -==== - -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, - 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`. - -`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)'`. - -==== 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`. - -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 -==== -[source, python3] ----- -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 do Python. -Os doctests extraídos de -https://fpy.li/18-21[_examples_test.py_] demonstram cada `case`. - - -[[eval_atom_sec]] -===== avaliando números - -[source, python3] ----- - case int(x) | float(x): - return x ----- - -Padrão::: - Instância de `int` ou `float`. - -Ação::: - Devolve o próprio valor. - -Exemplo::: -+ -[source, pycon] ----- -include::code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_NUMBER] ----- - -===== avaliando símbolos - -[source, python3] ----- - case Symbol(var): - return env[var] ----- - -Padrão::: - Instância de `Symbol`, isto é, uma `str` usada como identificador. - -Ação::: - Consulta `var` em `env` e devolve seu valor. - -Exemplos::: -+ -[source, pycon] ----- -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. - -[source, python3] ----- - # (quote (99 bottles of beer)) - case ['quote', x]: - return x ----- - -Padrão::: - Lista começando com o símbolo `'quote'`, seguido de uma expressão `x`. - -Ação::: - Devolve `x` sem avaliá-la. - -Exemplos::: -+ -[source, pycon] ----- -include::code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_QUOTE] ----- - -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 -* `(/ 10 0)` geraria um `ZeroDivisionError` - -.Por que linguagens tem 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. - -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 -* Para gerenciar o ambiente, como em `define` e `set` - -Por isso também o 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. -**** - - -===== (if …) - -[source, python3] ----- - # (if (< x 0) 0 x) - case ['if', test, consequence, alternative]: - if evaluate(test, env): - return evaluate(consequence, env) - else: - return evaluate(alternative, env) ----- - -Padrão::: - Lista começando com `'if'` seguida de três expressões: `test`, `consequence`, e `alternative`. - -Ação::: - Avalia `test`: - * Se verdadeira, avalia `consequence` e devolve seu valor. - * Caso contrário, avalia `alternative` e devolve seu valor. - -Exemplos::: -+ -[source, pycon] ----- -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 <>. - -===== (lambda …) - -A forma `lambda` do Scheme define funções anônimas. -Ela não sofre das limitações da `lambda` do Python: -qualquer função que pode ser escrita em Scheme pode ser escrita usando a sintaxe `(lambda …)`. - -[source, python3] ----- - # (lambda (a b) (/ (+ a b) 2)) - case ['lambda' [*parms], *body] if body: - return Procedure(parms, body, env) ----- - -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). - -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. - -Exemplo::: -+ -[source, pycon] ----- -include::code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_LAMBDA] ----- - -A classe `Procedure` implementa o conceito de uma closure (_clausura_): -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. -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 mais simples é: - -[source, python3] ----- - # (define half (/ 1 2)) - case ['define', Symbol(name), value_exp]: - env[name] = evaluate(value_exp, env) ----- - -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. - -Exemplo::: -+ -[source, pycon] ----- -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`. - -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. - -[source, python3] ----- - - # (define (average a b) (/ (+ a b) 2)) - case ['define', [Symbol(name), *parms], *body] if body: - env[name] = Procedure(parms, body, env) ----- - -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) - -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. - * 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. - -[[test_case_defun]] -.Definindo uma função chamada `%`, que calcula uma porcentagem -==== -[source, pycon] ----- -include::code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_DEFUN] ----- -==== - -// O doctet novamente cria um `global_env`. - -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. - - - -===== (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.] - -[source, python3] ----- - # (set! n (+ n 1)) - case ['set!', Symbol(name), value_exp]: - env.change(name, evaluate(value_exp, env)) ----- - -Padrão::: - Lista começando com `'set!'`, seguida de um `Symbol` e de uma expressão. - -Ação::: - Atualiza o valor de `name` em `env` com o resultado da avaliação da expressão. - -O método `Environment.change` atravessa os ambientes encadeados de local para global, -e atualiza a primeira ocorrência de `name` com o novo valor. -Se não estivéssemos implementando a palavra reservada `'set!'`, -esse interpretador poderia usar apenas o `ChainMap` do Python para implementar `env`, -sem precisar da nossa classe `Environment`. - -[role="pagebreak-before less_space"] -.O nonlocal do 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 seção <>. - -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. - -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_: - -[source, scheme] ----- -(define (make-averager) - (define count 0) - (define total 0) - (lambda (new-value) - (set! count (+ count 1)) - (set! total (+ total new-value)) - (/ total count) - ) -) -(define avg (make-averager)) # <1> -(avg 10) # <2> -(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`. -<2> Devolve 10.0. -<3> Devolve 10.5. -<4> Devolve 12.0. - -O código acima é um dos testes em https://fpy.li/18-18[_lispy/py3.10/examples_test.py_]. - -**** - -Agora chegamos a uma chamada de função. - -[[function_call_sec]] -===== Chamada de função - -[source, python3] ----- - # (gcd (* 2 105) 84) - case [func_exp, *args] if func_exp not in KEYWORDS: - proc = evaluate(func_exp, env) - values = [evaluate(arg, env) for arg in args] - return proc(*values) ----- - -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 <>. - -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. --- - -Ação::: - * Avaliar `func_exp` para obter uma `proc` da 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. - -Exemplo::: -+ -[source, pycon] ----- -include::code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_CALL] ----- - -Esse doctest continua do <>: -ele assume que `global_env` contém uma função chamada `%`. -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. -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. - - -===== Capturar erros de sintaxe - -Se o sujeito `exp` não casa com nenhum dos `case` anteriores, -o `case` "pega tudo" gera um `SyntaxError`: - -[source, python3] ----- - case _: - raise SyntaxError(lispstr(exp)) ----- - -Aqui está um exemplo de um `(lambda …)` malformado, identificado como um `SyntaxError`: - -[source, pycon] ----- -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"))) - - -==== 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 funcã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 seção <>. - -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`. -<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]`. -<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. -<6> Itera sobre cada expressão em `self.body`, avaliando-as no `env` combinado. -<7> Devolve o resultado da última expressão avaliada. - -Há um par de funções simples após `evaluate` em https://fpy.li/18-24[_lis.py_]: -`run` lê um programa Scheme completo e o executa, -e `main` chama `run` ou `repl`, dependendo da linha de comando—parecido com o modo como o Python faz. -Não vou descrever essas funções, pois não há nada novo ali. -Meus objetivos aqui eram compartilhar com vocês a beleza do pequeno interpretador de Norvig, -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"))) - -==== 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: - -[source, python3] ----- - case int(x) | float(x): - 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. - -[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. -==== - -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: - -[source, python3] ----- - # (λ (a b) (/ (+ a b) 2) ) - case ['lambda' | 'λ', [*parms], *body] if body: - return Procedure(parms, body, env) ----- - -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"))) - -=== Faça isso, então aquilo: os 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`. - -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. - -Aqui estão as regras: - -`for`:: O bloco `else` vai 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`). - -`while`:: O bloco `else` vai 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`) - -`try`:: O bloco else vai 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."" - -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. - -[NOTE] -==== -Acho `else` uma escolha muito pobre de palavra reservada em todos os casos, exceto o `if`. -Ela implica em uma alternativa excludente, como em "Execute esse loop, caso contrário faça aquilo." -Mas a semântica para o `else` em loops é o oposto: "Execute esse loop, daí faça aquilo." -Isso sugere `then` como uma escolha melhor—que também faria sentido no contexto de um `try`: -"Tente isso, então faça aquilo." -Entretanto, acrescentar uma palavra reservada é uma ruptura séria em uma linguagem—uma decisão muito difícil. -==== - -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. - -O uso de `else` em loops em geral segue o padrão desse trecho: - -[source, python3] ----- -for item in my_list: - if item.flavor == 'banana': - break -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? - -[source, python3] ----- -try: - dangerous_call() - after_call() -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: - -[source, python3] ----- -try: - dangerous_call() -except OSError: - log('OSError...') -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`. - -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 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. -____ - -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. - -____ - -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`?] -==== - -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 personalizado: 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" ("_O 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`. - -Nós 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 muito 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. - - -[[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`, como 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 do 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' do 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 do 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. - -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 o 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! -//// - - -[[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 o 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: - -[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. -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. -____ - -Hettinger admite que está tentando muito vender a 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. - -Se você precisa convencer alguém que o 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 - - -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`. - -Por outro lado, essa `factorial` não faz uma chamada de cauda: - -[source, py] ----- -def factorial(n): - if n < 2: - return 1 - 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. - -Aqui está uma alternativa que usa uma chamada de cauda, -e é portanto _recursiva de cauda_: - -[source, py] ----- -def factorial_tc(n, product=1): - if n < 1: - return product - return factorial_tc(n - 1, product * n) ----- - -O 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 o 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 -https://fpy.li/18-16[_lispy.py_], implementa. - -[role="soapbox-title"] -Os 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"_)] -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. -____ - -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). -O WebKit é usado pelo Safari. -Os interpretadores JS em todos os outros navegadores populares não tem 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). - -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. - -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] -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, -por exemplo, um árvore de parsing típica, -mas não o bastante para um loop 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 do 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_ - -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: -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` -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 -https://fpy.li/18-54[_mylis_]. -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"))) - -**** diff --git a/capitulos/cap19.adoc b/capitulos/cap19.adoc deleted file mode 100644 index f96f1005..00000000 --- a/capitulos/cap19.adoc +++ /dev/null @@ -1,1507 +0,0 @@ -[[concurrency_models_ch]] -== Modelos de concorrência em Python -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:xrefstyle: short -:example-number: 0 -:figure-number: 0 - -[quote, Rob Pike, Co-criador da linguagem Go] -____ -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. + -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 o 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. -Todos sistema paralelo é concorrente, mas nem todo sistema concorrente é paralelo. -No início dos anos 2000, usávamos máquinas GNU Linux de um único núcleo, que rodavam 100 processos ao mesmo tempo. -Um laptop moderno com quatro núcleos de CPU rotineiramente está executando mais de 200 processos a qualquer momento, sob uso normal, casual. -Para executar 200 tarefas em paralelo, você precisaria de 200 núcleos. -Assim, na prática, a maior parte da computação é concorrente e não paralela. -O SO administra centenas de processos, assegurando que cada um tenha a oportunidade de progredir, -mesmo se a CPU em si não possa fazer mais que quatro coisas ao mesmo tempo. - -Este((("concurrency models", "topics covered"))) capítulo não assume que você tenha qualquer 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. -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_, -porque a aptidão do 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 "O Python não escala." - -=== Novidades nesse 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_. -Aqui eles foram revisados, e apresentam uma primeira ilustração das três abordagens do 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_. - -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 do Python. - -=== 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 C++.] - -Quando você chama uma função, o código que origina a chamada fica bloqueado 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. - -Essas 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. -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. - -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. -Mas corrotinas muitas vezes são iniciadas pela framework assíncrona, -e isso pode torná-las tão difíceis de monitorar quanto threads ou processos. - -Por fim, as corrotinas e threads do Python não são adequadas para tarefas de uso intensivo da CPU, como veremos. - -É por isso tudo que programação concorrente exige aprender novos conceitos e novos modelos de programação. -Então vamos primeiro garantir que estamos na mesma página em relação a alguns conceitos centrais. - -=== 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: - -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_). - -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)). - -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. - O Python suporta de forma nativa três tipos de unidade 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 puros. - 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. - -Thread:: - Uma((("threads", "definition of term"))) unidade de execução dentro de um único 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). - Isso facilita o compartilhamento de informações entre threads, mas pode também levar a corrupção de dados, - se mais de uma thread atualizar concorrentemente o mesmo objeto. - Como os processos, as threads também possibilitam a _multitarefa preemptiva_ sob a supervisão do agendador do SO. - Uma thread consome menos recursos que um processo para realizar a mesma tarefa. - - -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 do 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 a _asyncio_, a _Curio_, ou a _Trio_ fornecem um loop 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. - -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".] - Filas permitem que unidades de execução separadas troquem dados da aplicação e mensagens de controle, tais como códigos de erro e sinais de término. - A implementação de uma fila varia de acordo com o modelo de concorrência subjacente: o pacote `queue` na biblioteca padrão do Python fornece classes de fila para suportar threads, já os pacotes `multiprocessing` e `asyncio` implementam suas próprias classes de fila. Os pacotes `queue` e `asyncio` também incluem filas não FIFO: `LifoQueue` e `PriorityQueue`. - -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. - 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. - -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. - 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"))) - -==== Processos, threads, e a infame GIL do Python - -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_ do 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_. - -. O acesso à contagem de referências a objetos e outros estados internos do interpretador é controlado por uma trava, -a((("Global Interpreter Lock (GIL)"))) Global Interpreter Lock (GIL) ou _Trava Global do Interpretador_. -A qualquer dado momento, apenas uma thread do 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 do Python segure a GIL indefinidamente, o interpretador de bytecode do 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. -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. - -. Toda função na biblioteca padrão do 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.] - -. 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_. - -. 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 do 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)].] - -. 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. - -. Para rodar código Python de uso intensivo da CPU em múltiplos núcleos, você tem que 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).] - -[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 - `concurrent.futures.ProcessPoolExecutor`. - -Entretanto, threads ainda são o modelo adequado se você deseja rodar múltiplas tarefas ligadas a E/S simultaneamente. -____ - -O parágrafo anterior começa com "Detalhe de implementação do CPython" porque a GIL não é parte da definição da linguagem Python. As implementações Jython e o IronPython não tem uma GIL. Infelizmente, ambas estão ficando para trás, ainda compatíveis apenas com Python 2.7 e 3.4, respectivamente. O interpretador de alto desempenho https://fpy.li/19-9[PyPy] também tem uma GIL em suas versões 2.7, 3.8 e 3.9 (a mais recente em março de 2021). - -[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 uma framework assíncrona. 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. -Isso será explicado na <>. -==== - -Mas chega de conceitos por agora. Vamos ver algum código.((("", startref="CMconcepts19"))) - -[[concurrent_hello_world]] -=== Um "Olá mundo" concorrente - -Durante((("concurrency models", "Hello World example", id="CMhello19"))) uma discussão sobre threads e sobre como evitar a GIL, o contribuidor do Python Michele Simionato https://fpy.li/19-10[postou um exemplo] que é praticamente um "Olá Mundo" concorrente: -o programa mais simples possível mostrando como o Python pode "mascar chiclete e subir a escada ao mesmo tempo". - -O programa de Simionato usa `multiprocessing`, -mas eu o adaptei para apresentar também `threading` e `asyncio`. -Vamos começar com a versão `threading`, que pode parecer familiar se você já estudou threads em Java ou C. - - -==== Caracteres animados com threads - -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`. - -<> 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. - -[[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. -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 <> -lista as duas primeiras funções no script, e o <> mostra o restante. - -[[spinner_thread_top_ex]] -.spinner_thread.py: as funções `spin` e `slow` -==== -[source, py] ----- -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. -<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. - -[TIP] -==== -O primeiro detalhe importante deste exemplo é que `time.sleep()` bloqueia a thread que a chama, mas libera a GIL, permitindo que outras threads Python rodem. -==== - -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 do 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`. -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. - - - -[[spinner_thread_rest_ex]] -.spinner_thread.py: as funções `supervisor` e `main` -==== -[source, py] ----- -include::code/19-concurrency/spinner_thread.py[tags=SPINNER_THREAD_REST] ----- -==== -<1> `supervisor` irá 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`. -<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`. -<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. - -Agora vamos ver um exemplo similar usando o pacote `multiprocessing`.((("", startref="Tspin19")))((("", startref="Sthread19"))) - - -==== Animação com processos - -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. - -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_ (<>). - -[[spinner_proc_ex]] -.spinner_proc.py: apenas as partes modificadas são mostradas; todo o resto é idêntico a spinner_thread.py -==== -[source, py] ----- -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 - -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`... -<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_.# - -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. -Isso significa que objetos cruzando fronteiras entre processos tem que 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. O Python fornece diferentes classes de semáforos para uso com threads, processos e corrotinas. Veremos o `asyncio.Semaphore` na <> (no <>).] - -[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. -Veja a documentação de -https://docs.python.org/pt-br/3/library/multiprocessing.shared_memory.html#multiprocessing.shared_memory.ShareableList[`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]] -==== Animação com corrotinas - -[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. -==== - -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. - -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. - -[[spinner_async_start_ex]] -.spinner_async.py: a função `main` e a corrotina `supervisor` -==== -[source, py] ----- -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. -A função `main` ficará bloqueada até que `supervisor` retorne. -O valor de retorno de `supervisor` será o valor de retorno de `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`. - -<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. -`asyncio.create_task(coro())`:: - É chamado 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. -`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. - -[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. -==== - -Vamos estudar agora as corrotinas `spin` e `slow` no <>. - -[[spinner_async_top_ex]] -.spinner_async.py: as corrotinas `spin` e `slow` -==== -[source, py] ----- -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. -<4> A corrotina `slow` também usa `await asyncio.sleep` em vez de `time.sleep`. - -===== Experimento: Estragando a animação para sublinhar um ponto - -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 <>. - -[[spinner_async_time_sleep_ex]] -.spinner_async.py: substituindo `await asyncio.sleep(3)` por `time.sleep(3)` -==== -[source, py] ----- -async def slow() -> int: - time.sleep(3) - return 42 ----- -==== - -Assistir o comportamento é mais memorável que ler sobre ele. -Vai lá, eu espero. - -Quando você roda o experimento, você vê isso: - -. O objeto `spinner` aparece: `>`. -. 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, -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. -No <>, vamos nos concentrar no que ocorre nas corrotinas `supervisor` e `slow` durante o experimento proposto. - -[[spinner_async_experiment_ex]] -.spinner_async_experiment.py: as corrotinas `supervisor` e `slow` -==== -[source, py] ----- -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_). -<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`. - -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. -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"))) -==== - -[[gevent_box]] -.Greenlet e gevent -**** -Ao((("greenlet package"))) discutir concorrência com corrotinas, -é importante 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`, -e assim são mais fáceis de integrar a bases de código sequencial existentes. -O https://fpy.li/19-15[SQL Alchemy 1.4 ORM] usa greenlets -internamente para implementar sua nova -https://fpy.li/19-16[API assíncrona] compatível com _asyncio_. - -A((("gevent library"))) biblioteca de programação de redes -https://fpy.li/19-17[_gevent_] modifica, através de _monkey patches_, o módulo `socket` padrão do Python, tornando-o não-bloqueante, substituindo parte do código daquele módulo por greenlets. -Na maior parte dos casos, _gevent_ é transparente para o código em seu entorno, tornando mais fácil adaptar aplicações e bibliotecas sequenciais—tal como drivers de bancos de dados—para executar E/S de rede de forma concorrente. -https://fpy.li/19-18[Inúmeros projetos open source] -usam _gevent_, incluindo o muito usado -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 <>. - -[[thread_supervisor_ex]] -.spinner_thread.py: a função `supervisor` com threads -==== -[source, python3] ----- -def supervisor() -> int: - done = Event() - spinner = Thread(target=spin, - args=('thinking!', done)) - print('spinner object:', spinner) - spinner.start() - result = slow() - done.set() - spinner.join() - return result ----- -==== - -Para comparar, o <> mostra a corrotina `supervisor` do <>. - -[[asyncio_supervisor_ex]] -.spinner_async.py: a corrotina assíncrona `supervisor` -==== -[source, python3] ----- -async def supervisor() -> int: - spinner = asyncio.create_task(spin('thinking!')) - print('spinner object:', spinner) - result = await slow() - spinner.cancel() - return result ----- -==== - -Aqui está um resumo das diferenças e semelhanças notáveis entre as duas implementações de `supervisor`: - -* Uma `asyncio.Task` é aproximadamente equivalente a `threading.Thread`. -* 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`. - -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. - -Um último ponto relativo a threads versus corrotinas: -quem já escreveu qualquer programa não-trivial com threads -sabe quão desafiador é estruturar o programa, porque o agendador pode interromper uma thread a qualquer momento. -É preciso lembrar de manter travas para proteger seções críticas do programa, para evitar ser interrompido no meio de uma operação de muitas etapas—algo que poderia deixar dados em um estado inválido. - -Com corrotinas, seu código está protegido de interrupções arbitrárias. -É preciso chamar `await` explicitamente para deixar o resto do programa rodar. -Em vez de manter travas para sincronizar as operações de múltiplas threads, -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. -Enquanto isso, a animação seguirá girando. - -Com código de uso intensivo da CPU, a história é outra. -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 verificação 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 do Python] -==== -[source, py] ----- -include::code/19-concurrency/primes/primes.py[tags=IS_PRIME] ----- -==== -A chamada `is_prime(5_000_111_000_222_021)` leva cerca de 3.3s no laptop da empresa que estou usando agora.footnote:[É um MacBook Pro 15” de 2018, com uma CPU Intel Core i7 2.2 GHz de 6 núcleos.] - -==== Teste Rápido - -Dado o que vimos até aqui, -pare um instante para pensar sobre a seguinte questão, de três partes. -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: - -. 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)`? -. Em _spinner_async.py_, substitua `await asyncio.sleep(3)` com uma chamada a `is_prime(n)`? -____ - -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#. - -Agora as respostas, da mais fácil para a mais difícil. - -===== 1. Resposta para multiprocessamento - -A animação é controlada por um processo filho, então continua girando enquanto o teste de números primos é computado no processo raiz.footnote:[Isso é verdade hoje porque você provavelmente está usando um SO moderno, com _multitarefa preemptiva_. O Windows antes da era NT e o MacOS antes da era OSX não eram "preemptivos", então qualquer processo podia tomar 100% da CPU e paralisar o sistema inteiro. Não estamos inteiramente livres desse tipo de problema hoje, mas confie na minha barba branca: esse tipo de coisa assombrava todos os usuários nos anos 1990, e a única cura era um reset de hardware.] - -===== 2. Resposta para versão com threads - -A((("threads", "Global Interpreter Lock impact"))) animação é controlada por uma thread secundária, então continua girando enquanto o teste de número primo é computado na thread principal. - -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 o 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. -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. -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. - -===== 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 <>, -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`. -Quando `is_prime` retornar, `slow` vai retornar também, e `supervisor` retomará a execução, cancelando a tarefa `spinner` antes dela ser executada sequer uma vez. -O programa parecerá congelado por aproximadamente 3s, e então mostrará a resposta.((("", startref="CMimpact19")))((("", startref="gil19")))((("", startref="SPgil19"))) - -.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 <>. - -[[example-19-11]] -.spinner_async_nap.py: `is_prime` agora é uma corrotina -==== -[source, py] ----- -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)`. - -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`). - -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 <>. -**** - -Até aqui experimentamos com uma única chamada para uma função de uso intensivo de CPU. A próxima seção apresenta a execução concorrente de múltiplas chamadas de uso intensivo da CPU. - -[[naive_multiprocessing_sec]] -=== Um pool de processos caseiro - -[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, -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 verificar 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. - -O((("sequential.py program"))) programa _sequential.py_ fornece a linha base de desempenho. -Aqui está o resultado de uma execução de teste: - -[source] ----- -$ python3 sequential.py - 2 P 0.000001s - 142702110479723 P 0.568328s - 299593572317531 P 0.796773s -3333333333333301 P 2.648625s -3333333333333333 0.000007s -3333335652092209 2.672323s -4444444444444423 P 3.052667s -4444444444444444 0.000001s -4444444488888889 3.061083s -5555553133149889 3.451833s -5555555555555503 P 3.556867s -5555555555555555 0.000007s -6666666666666666 0.000001s -6666666666666719 P 3.781064s -6666667141414921 3.778166s -7777777536340681 4.120069s -7777777777777753 P 4.141530s -7777777777777777 0.000007s -9999999999999917 P 4.678164s -9999999999999999 0.000007s -Total time: 40.31 ----- - -Os resultados aparecem em três colunas: - -* O número a ser verificado. -* `P` se é um número primo, caso contrária, vazia. -* Tempo decorrido para verificar se aquele número específico é primo. - -Neste exemplo, o tempo total é aproximadamente a soma do tempo de cada verificação, -mas está computado separadamente, como se vê no <>. - -[[primes_sequential_ex]] -.sequential.py: verificação de números primos em um pequeno conjunto de dados -==== -[source, py] ----- -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. -<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. - -[[proc_based_solution]] -==== Solução baseada em processos - -O((("process pools", "process-based solution"))) próximo exemplo, _procs.py_, mostra o uso de múltiplos processos para distribuir a verificação de números primos por muitos núcleos da CPU. -Esses são os tempos obtidos com _procs.py_: - -[source] ----- -$ python3 procs.py -Checking 20 numbers with 12 processes: - 2 P 0.000002s -3333333333333333 0.000021s -4444444444444444 0.000002s -5555555555555555 0.000018s -6666666666666666 0.000002s - 142702110479723 P 1.350982s -7777777777777777 0.000009s - 299593572317531 P 1.981411s -9999999999999999 0.000008s -3333333333333301 P 6.328173s -3333335652092209 6.419249s -4444444488888889 7.051267s -4444444444444423 P 7.122004s -5555553133149889 7.412735s -5555555555555503 P 7.603327s -6666666666666719 P 7.934670s -6666667141414921 8.017599s -7777777536340681 8.339623s -7777777777777753 P 8.388859s -9999999999999917 P 8.117313s -20 checks in 9.58s ----- - -A última linha dos resultados mostra que _procs.py_ foi 4,2 vezes mais rápido que _sequential.py_. - - -==== Entendendo os tempos decorridos - -Observe((("process pools", "understanding elapsed times"))) que o tempo decorrido na primeira coluna é o tempo para verificar aquele número específico. Por exemplo, `is_prime(7777777777777753)` demorou quase 8,4s para retornar `True`. Enquanto isso, outros processos estavam verificando outros números em paralelo. - -Há 20 números para serem verificados. Escrevi _procs.py_ para iniciar um número de processos de trabalho igual ao número de núcleos na CPU, como determinado por `multiprocessing.cpu_count()`. - -O tempo total neste caso é muito menor que a soma dos tempos decorridos para cada verificação individual. Há algum tempo gasto em iniciar processos e na comunicação entre processos, então o resultado final é que a versão multiprocessos é apenas cerca de 4,2 vezes mais rápida que a sequencial. Isso é bom, mas um pouco desapontador, considerando que o código inicia 12 processos, para usar todos os núcleos desse laptop. - -[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 verificação simples de números primos. -==== - -[[code_for_multicore_prime_sec]] -==== Código para o verificador de números primos com 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. - -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 do Python, -pode substituir `SimpleQueue` por `Queue` no <>. -==== - -[[primes_procs_top_ex]] -.procs.py: verificação de primos com múltiplos processos; importações, tipos, e funções -==== -[source, py] ----- -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. -<2> `multiprocessing.queues` contém a classe `SimpleQueue` que precisamos para dicas de tipo. -<3> `PrimeResult` inclui o número verificado. 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 verificação. -<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 verificados, 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`.] -<9> Invoca a verificação 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. -<11> `procs` é o número de processos que executarão a verificação de números primos em paralelo. -<12> Coloca na fila `jobs` os números a serem verificados. -<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`. -<14> Inicia cada processo filho. -<15> Coloca um `0` na fila de cada processo, para encerrá-los. - -[[good_poison_pill_tip]] -.Loops, 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. -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 do Python usa -https://fpy.li/19-22[muitos valores diferentes] (EN) como sentinelas. -A https://fpy.li/pep661[PEP 661—Sentinel Values] (EN) -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 <>. - -[[primes_procs_main_ex]] -.procs.py: verificação de números primos com múltiplos processos; função `main` -==== -[source, py] ----- -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]. -<5> Mostra quantos números foram verificados 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`]. -<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 verificados) 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. - -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. -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. -Se "A" acontecer antes de "B", tudo segue normal; -mas se "B" acontecer antes, surge um erro. -Essa é a corrida. - -Se você estiver curiosa, esse 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"))) -==== - - -==== 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... - - -[source, bash] ----- -$ 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#. - -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 <>. - -[[procs_x_time_fig]] -.Mediana dos tempos de execução para cada número de subprocessos de 1 a 20. O maior tempo mediano foi 40,81s, com 1 processo. O tempo mediano mais baixo foi 10,39s, com 6 processos, indicado pela linha pontilhada. -image::images/flpy_1902.png[Mediana dos tempos de execução para cada número de processos, de 1 a 20.] - -Neste laptop de 6 núcleos, o menor tempo mediano ocorreu com 6 processos:10.39s—marcado pela linha pontilhada na <>. -Seria de se esperar que o tempo de execução aumentasse após 6 processos, devido à disputa pela CPU, -e ele atingiu um máximo local de 12.51s, com 10 processes. -Eu não esperava e não sei explicar porque o desempenho melhorou com 11 processos e permaneceu praticamente igual com 13 a 20 processos, -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.] - -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 (<>). - -As((("", startref="CMprocess19"))) demais seções nesse capítulo procuram responder à questão: - -[quote] -____ -Dadas as limitações discutidas até aqui, como é possível que o Python seja tão bem-sucedido em um mundo de CPUs com múltiplos núcleos? -____ - - -[[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]: - -[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]. -____ - -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: -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 do 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 do 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. -==== - -Apesar da GIL, o Python está cada vez mais popular entre aplicações que exigem execução concorrente ou paralela, -graças a bibliotecas e arquiteturas de software que contornam as limitações do CPython. - -Agora vamos discutir como o Python é usado em administração de sistemas, ciência de dados, -e desenvolvimento de aplicações para servidores no mundo do processamento distribuído e dos multi-núcleos de 2023. - -==== 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. -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. - -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: -"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 do Python 2." - - -==== Ciência de dados - -A ciência de dados—incluindo((("multicore processing", "data science")))((("data science"))) a inteligência artificial—e a computação científica estão muito bem servidas pelo Python. - -Aplicações nesses campos são de processamento intensivo, mas os usuários de Python se beneficiam de um vasto ecossistema de bibliotecas de computação numérica, escritas em -C, [.keep-together]#C++#, Fortran, Cython, etc.—muitas das quais capazes de aproveitar os benefícios de máquinas multi-núcleo, GPUs, e/ou computação paralela distribuída em clusters heterogêneos. - -Em 2021, o ecossistema de ciência de dados de Python já incluía algumas ferramentas impressionantes: - -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, vem de 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]:: - Estas((("TensorFlow"))) são as duas 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) - medido pelo uso de seus recursos de aprendizagem durante 2020. - Os dois projetos são escritos em C++, e conseguem se beneficiar de múltiplos núcleos, GPUs e clusters. - Eles também suportam outras linguagens, mas o 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. - -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 o 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. - -Estes são apenas alguns exemplos para ilustrar como a comunidade de ciência de dados está criando soluções que extraem o melhor do 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 - -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 o Python fornece "de fábrica". -Antes de discutir as ferramentas necessárias para usar o Python larga escala, preciso citar uma advertência da _Technology Radar_ da Thoughtworks: - -[quote] -____ -*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 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. - -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). - -Observe a <>, o primeiro de muitos diagramas de arquitetura no 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 documentos: _Apache CouchDB_, _MongoDB_ -* Full-text indexes (_índices de texto integral_): _Elasticsearch_, _Apache Solr_ -* Enfileiradores 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).] -image::images/flpy_1903.png[Arquitetura para um sistema de dados combinando diversos componentes] - -Há outros produtos de código aberto extremamente robustos em cada uma dessas categorias. -Os grandes fornecedores de serviços na nuvem também oferecem suas próprias alternativas proprietárias - -O diagrama de Kleppmann é genérico e independente da linguagem—como seu livro. -Para aplicações de servidor em Python, dois componentes específicos são comumente utilizados: - -* Um servidor de aplicação, para distribuir a carga entre várias instâncias da aplicação Python. O servidor de aplicação apareceria perto do topo na <>, processando as requisições dos clientes antes delas chegaram ao código da aplicação. - -* Uma fila de tarefas construída em torno da fila de mensagens no lado direito da <>, oferecendo uma API de alto nível e mais fácil de usar, para distribuir tarefas para processos rodando em outras máquinas. - -As duas próximas seções exploram esses componentes, recomendados pelas boas práticas de implementações de aplicações Python de servidor. - - -[[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 <> 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 <>. -==== - -Os servidores de aplicação mais conhecidos em projeto 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/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. - -[[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. -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_ é muito 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_.] - -Lançado em 2018, o _NGINX Unit_ é um novo produto dos desenvolvedores do conhecido servidor HTTP e proxy reverso _NGINX_. - -O _mod_wsgi_ e o _Gunicorn_ só suportam apps web Python, -enquanto o _uWSGI_ e o _NGINX Unit_ funcionam também com outras linguagens. -Para saber mais, consulte a documentação de cada um deles. - -O ponto principal: todos esses servidores de aplicação podem, potencialmente, utilizar todos os núcleos de CPU no servidor, criando múltiplos processos Python para executar apps web tradicionais escritas no bom e velho código sequencial em _Django_, _Flask_, _Pyramid_, etc. -Isso explica porque tem sido possível ganhar a vida como desenvolvedor Python sem nunca ter estudado os módulos `threading`, `multiprocessing`, ou `asyncio`: -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, -projetada para frameworks Python assíncronas para programação web, tais como _aiohttp_, _Sanic_, _FastAPI_, etc., -bem como _Django_ e _Flask_, que estão gradativamente acrescentando funcionalidade assíncrona. -==== - -Agora vamos examinar outra forma de evitar a GIL para obter um melhor desempenho em aplicações Python de servidor. - -[[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 email 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 o 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. - -[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. -==== - -Citando diretamente o https://fpy.li/19-49[FAQ] do 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. -* 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. - -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. - -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"))) - - - -=== 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 do Python: - -* Threads, com o pacote `threading` -* Processo, com `multiprocessing` -* Corrotinas assíncronas com `asyncio` - -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. -A versão com threads do experimento funcionou—apesar da GIL—porque o 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 do 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 <>. -Por exemplo, a GIL não afeta muitos dos casos de uso de Python em administração de sistemas. -Por outro lado, as comunidades de ciência de dados e de desenvolvimento para servidores evitaram os problemas com a GIL usando soluções robustas, criadas sob medida para suas necessidades específicas. -As últimas duas seções mencionaram os dois elementos comuns que sustentam o uso de Python em aplicações de servidor escaláveis: -servidores de aplicação WSGI e filas de tarefas distribuídas. - -[[concurrency_further_reading_sec]] -=== Para saber mais - -Este((("concurrency models", "further reading on", id="CMfurther19"))) capítulo tem uma extensa lista de referências, então a dividi em subseções. - -[[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`. -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_) -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`. -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 do 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 verificação 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: -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 do Python bem como o _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 tem relação com o Python ou a GIL. -Citando literalmente a página 14 de _Using Asyncio in Python_: - -[quote] -____ -Esses temas se repetem com frequência: - -* Programação com threads torna o código difícil de analisar. - -* Programação com threads é um modelo ineficiente para concorrência em larga escala (milhares de tarefas concorrentes). -____ - -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. - - -==== 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 do 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?_). -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 do 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_): -um mergulho profundo no suporte nativo do Python a threads e processos, -incluindo o gerenciamento da GIL por extensões usando a API C/Python. - -Por fim, David Beazley apresentou uma exploração detalhada em https://fpy.li/19-65["Understanding the Python GIL"] -(_Entendendo a GIL do Python_).footnote:[Agradeço a Lucas Brunialti por me enviar um link para essa palestra.] -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-68[Python issue #7946]. - - -==== Concorrência além da biblioteca padrão - -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 do 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 do 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 -(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 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. - -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). - -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. -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. -Ambos os pacotes são baseados na biblioteca _multiprocessing_. - -Eric Snow, um dos desenvolvedores oficiais 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 do Python a execução em paralelo. -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 o 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 do 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. - -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-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_], -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 da 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. - -==== Concorrência e escalabilidade para além do Python - -https://fpy.li/19-92[_RabbitMQ in Action_], de Alvaro Videla and Jason J. W. Williams (Manning), é uma introdução muito bem escrita ao _RabbitMQ_ e ao padrão AMQP (_Advanced Message Queuing Protocol_, Protocolo Avançado de Enfileiramento de Mensagens), com exemplos em Python, PHP, e Ruby. -Independente do resto de seu stack tecnológico, e mesmo se você planeja usar _Celery_ com _RabbitMQ_ debaixo dos panos, -recomendo esse livro por sua abordagem dos conceitos, da motivação e dos modelos das filas de mensagem distribuídas, bem como a operação e configuração do _RabbitMQ_ em larga escala. - -Aprendi muito lendo https://fpy.li/19-93[_Seven Concurrency Models in Seven Weeks_], -de Paul Butcher (Pragmatic Bookshelf), que traz o eloquente subtítulo _When Threads Unravel_.footnote:[NT: Trocadilho intraduzível com thread no sentido de "fio" ou "linha", algo como "Quando as linhas desfiam."] -O capítulo 1 do livro apresenta os conceitos centrais e os desafios da programação com threads e travas em Java.footnote:[As APIs Python `threading` e `concurrent.futures` foram fortemente influenciadas pela biblioteca padrão do Java.] -Os outros seis capítulos do livro são dedicados ao que o autor considera as melhores alternativas para programação concorrente e paralela, e como funcionam com diferentes linguagens, ferramentas e bibliotecas. -Os exemplos usam Java, Clojure, Elixir, e -C (no capítulo sobre programação paralela com a framework https://fpy.li/19-94[OpenCL]). -O modelo CSP é exemplificado com código Clojure, apesar da linguagem Go merecer os créditos pela popularização daquela abordagem. -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 a 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. - -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_) -(https://fpy.li/19-99[video] na O'Reilly Learning Platform). -Apesar do título, aos 9:35 no video, Cesarini explica: - -[quote] -____ -Muito pouco do que vou dizer será específico de Erlang […]. -Resta o fato de que o Erlang remove muitas dificuldades acidentais no desenvolvimento de sistemas resilientes que nunca falham, além serem escalonáveis. -Então será mais fácil se vocês usarem Erlang ou uma linguagem rodando na máquina virtual Erlang. -____ - -Aquele workshop foi baseado nos últimos quatro capítulos do -https://fpy.li/19-100[_Designing for Scalability with Erlang/OTP_] -de Francesco Cesarini e Steve Vinoski (O'Reilly). - -Desenvolver((("KISS principle"))) sistemas distribuídos é desafiador e empolgante, mas cuidado com a -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?"], -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: - -[quote] -____ -Meu script Perl é mais rápido que seu cluster Hadoop. -____ - - -[[concurrency_models_soapbox]] -.Ponto de vista -**** - -[role="soapbox-title"] -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. - -É 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 muito 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. - -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 bençãos. -Elas fazem com que pensar sobre o código se torne muito mais simples. - -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_. -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. -____ - -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.] - -* 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. - -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 -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"))) - -**** diff --git a/capitulos/cap20.adoc b/capitulos/cap20.adoc deleted file mode 100644 index d6433a01..00000000 --- a/capitulos/cap20.adoc +++ /dev/null @@ -1,879 +0,0 @@ -[[futures_ch]] -== Executores concorrentes -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:xrefstyle: short -:example-number: 0 -:figure-number: 0 - -++++ -
-

Quem fala mal de threads são tipicamente programadoras de sistemas, -que tem em mente casos de uso que o típico programador de aplicações nunca vai encontrar na vida.[...] -Em 99% dos casos de uso que o programador de aplicações vai encontrar, -o modelo simples de gerar um monte de threads e coletar os resultados em uma fila é tudo que se precisa saber.

-

Michele Simionato, profundo pensador do Python. -Do post de Michele Simionato, -"Threads, processes and concurrency in Python: some thoughts" -(_Threads, processos e concorrência em Python: algumas reflexões_), -resumido assim: "Removendo exageros sobre a (não-)revolução dos múltiplos núcleos -e alguns comentários sensatos (oxalá) sobre threads e outras formas de concorrência."

-
- -++++ - - -Este((("concurrent executors", "purpose of"))) capítulo se concentra nas classes do `concurrent.futures.Executor`, -que encapsulam o modelo de "gerar um monte de threads independentes e coletar os resultados em uma fila" descrito por Michele Simionato. -Executores concorrentes tornam o uso desse modelo quase trivial, -não apenas com threads mas também com processos—úteis para tarefas de processamento intensivo em CPU. - -Também((("futures", "definition of term"))) introduzo aqui o conceito de -_futures_—objetos que representam a execução assíncrona de uma operação, similares às _promises_ do Javascript. -Essa ideia básica é a fundação de `concurrent.futures` bem como do pacote `asyncio`, assunto do <>. - - -=== Novidades nesse capítulo -Renomeei((("concurrent executors", "significant changes to"))) este capítulo de "Concorrência com futures" para "Executores concorrentes", porque os executores são o recurso de alto nível mais importante tratado aqui. -_Futures_ são objetos de baixo nível, tratados na seção <>, -mas quase invisíveis no resto do capítulo. - -Todos os exemplos de clientes HTTP agora usam a nova biblioteca https://fpy.li/httpx[_HTTPX_], que oferece APIs síncronas e assíncronas. - -A configuração para os experimentos na seção <> ficou mais simples, -graças ao servidor de múltiplas threads adicionado ao pacote https://fpy.li/20-2[`http.server`] no Python 3.7. -Antes, a biblioteca padrão oferecia apenas o `BaseHttpServer` de thread única, -que não era adequado para experiências com clientes concorrentes, -então na primeira edição desse livro precisei usar um servidor externo. - -A seção <> agora demonstra como um executor simplifica o código que vimos na <>. - -Por fim, movi a maior parte da teoria para o novo <>. - -[[ex_web_downloads_sec]] -=== Downloads concorrentes da web - -A((("network I/O", "essential role of concurrency in", id="IOconcur20")))((("concurrent executors", "concurrent web downloads", id="CEwebdown20"))) concorrência é essencial para uma comunicação eficiente via rede: em vez de esperar de braços cruzados por respostas de máquinas remotas, a aplicação deveria fazer alguma outra coisa até a resposta chegar.footnote:[Especialmente se o seu provedor de serviços na nuvem aluga máquinas por tempo de uso, independente de quão ocupada esteja a CPU.] - -Para demonstrar com código, escrevi três programas simples que baixam da web imagens de 20 bandeiras de países. -O primeiro, _flags.py_, roda sequencialmente: -ele só requisita a imagem seguinte quando a anterior foi baixada e salva localmente. -Os outros dois scripts fazem downloads concorrentes: -eles requisitam várias imagens quase ao mesmo tempo, e as salvam conforme chegam. -O script _flags_threadpool.py_ usa o pacote `concurrent.futures`, -enquanto _flags_asyncio.py_ usa `asyncio`. - -O <> mostra o resultado da execução dos três scripts, três vezes cada um. - -Os scripts baixam imagens de https://fluentpython.com[_fluentpython.com_], que usa uma CDN -(_Content Delivery Network_, _Rede de Fornecimento de Conteúdo_), -então você pode notar os resultados mais lentos nas primeiras passagens. -Os resultados no <> foram obtidos após várias execuções, -então o cache da CDN estava carregado. - -[[ex_flags_sample_runs]] -.Três execuções típicas dos scripts flags.py, flags_threadpool.py, e flags_asyncio.py -==== -[source, text] ----- -$ python3 flags.py -BD BR CD CN DE EG ET FR ID IN IR JP MX NG PH PK RU TR US VN <1> -20 flags downloaded in 7.26s <2> -$ python3 flags.py -BD BR CD CN DE EG ET FR ID IN IR JP MX NG PH PK RU TR US VN -20 flags downloaded in 7.20s -$ python3 flags.py -BD BR CD CN DE EG ET FR ID IN IR JP MX NG PH PK RU TR US VN -20 flags downloaded in 7.09s -$ python3 flags_threadpool.py -DE BD CN JP ID EG NG BR RU CD IR MX US PH FR PK VN IN ET TR -20 flags downloaded in 1.37s <3> -$ python3 flags_threadpool.py -EG BR FR IN BD JP DE RU PK PH CD MX ID US NG TR CN VN ET IR -20 flags downloaded in 1.60s -$ python3 flags_threadpool.py -BD DE EG CN ID RU IN VN ET MX FR CD NG US JP TR PK BR IR PH -20 flags downloaded in 1.22s -$ python3 flags_asyncio.py <4> -BD BR IN ID TR DE CN US IR PK PH FR RU NG VN ET MX EG JP CD -20 flags downloaded in 1.36s -$ python3 flags_asyncio.py -RU CN BR IN FR BD TR EG VN IR PH CD ET ID NG DE JP PK MX US -20 flags downloaded in 1.27s -$ python3 flags_asyncio.py -RU IN ID DE BR VN PK MX US IR ET EG NG BD FR CN JP PH CD TR <5> -20 flags downloaded in 1.42s ----- -==== -<1> A saída de cada execução começa com os códigos dos países de cada bandeira a medida que as imagens são baixadas, e termina com uma mensagem mostrando o tempo decorrido. -<2> _flags.py_ precisou em média de 7,18s para baixar 20 imagens. -<3> A média para _flags_threadpool.py_ foi 1,40s. -<4> Já _flags_asyncio.py_, obteve um tempo médio de 1,35s. -<5> Observe a ordem do códigos de país: nos scripts concorrentes, as imagens foram baixadas em um ordem diferente a cada vez. - -A diferença de desempenho entre os scripts concorrentes não é significativa, mas ambos são mais de cinco vezes mais rápidos que o script sequencial—e isto apenas para a pequena tarefa de baixar 20 arquivos, cada um com uns poucos kilobytes. -Se você escalar a tarefa para centenas de downloads, os scripts concorrentes podem superar o código sequencial por um fator de 20 ou mais. - -[WARNING] -==== -Ao testar clientes HTTP concorrentes usando servidores web públicos, você pode inadvertidamente lançar um ataque de negação de serviço (DoS, _Denial of Service attack_), ou se tornar suspeito de estar tentando um ataque. -No caso do <> não há problema, pois aqueles scripts estão codificados para realizar apenas 20 requisições. -Mais adiante nesse capítulo usaremos o pacote `http.server` do Python para executar nossos testes localmente. -==== - -Vamos agora estudar as implementações de dois dos scripts testados no <>: _flags.py_ e _flags_threadpool.py_. Vou deixar o terceiro, _flags_asyncio.py_, para o <>, mas queria demonstrar os três juntos para fazer duas observações: - -. Independente dos elementos de concorrência que você use—threads ou corrotinas—haverá um ganho enorme de desempenho sobre código sequencial em operações de E/S de rede, se o script for escrito corretamente. -. Para clientes HTTP que podem controlar quantas requisições eles fazem, não há diferenças significativas de desempenho entre threads e corrotinas.footnote:[Para servidores que podem ser acessados por muitos clientes, há uma diferença: as corrotinas escalam melhor, pois usam menos memória que as threads, e também reduzem o custo das mudanças de contexto, que mencionei na seção <>.] - -Vamos ver o código.((("", startref="IOconcur20"))) - - -==== Um script de download sequencial - -O <> contém((("network I/O", "sequential download script", id="IOsequen20"))) a implementação de _flags.py_, o primeiro script que rodamos no <>. -Não é muito interessante, mas vamos reutilizar a maior parte do código e das configurações para implementar os scripts concorrentes, então ele merece alguma atenção. - -[NOTE] -==== -Por clareza, não há qualquer tipo de tratamento de erro no <>. Vamos lidar come exceções mais tarde, mas aqui quero me concentrar na estrutura básica do código, para facilitar a comparação deste script com os scripts que usam concorrência. -==== - -[[flags_module_ex]] -.flags.py: script de download sequencial; algumas funções serão reutilizadas pelos outros scripts -==== -[source, py] ----- -include::code/20-executors/getflags/flags.py[tags=FLAGS_PY] ----- -==== -<1> Importa a biblioteca `httpx`. Ela não é parte da biblioteca padrão. Assim, por convenção, a importação aparece após os módulos da biblioteca padrão e uma linha em branco. -<2> Lista do código de país ISO 3166 para os 20 países mais populosos, em ordem decrescente de população. -<3> O diretório com as imagens das bandeiras.footnote:[As imagens são originalmente do https://fpy.li/20-4[CIA World Factbook], uma publicação de domínio público do governo norte-americano. Copiei as imagens para o meu site, para evitar o risco de lançar um ataque de DoS contra _cia.gov_.] -<4> Diretório local onde as imagens são salvas. -<5> Salva os bytes de `img` para `filename` no `DEST_DIR`. -<6> Dado um código de país, constrói a URL e baixa a imagem, retornando o conteúdo binário da resposta. -<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#. -<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. -O argumento `flush=True` é necessário porque, por default, a saída do Python usa um buffer de linha, o que significa que o Python só mostraria os caracteres enviados após uma quebra de linha. -<13> `main` precisa ser chamada com a função que fará os downloads; dessa forma podemos usar `main` como uma função de biblioteca com outras implementações de `download_many` nos exemplos de `threadpool` e `ascyncio`. -<14> Cria o `DEST_DIR` se necessário; não acusa erro se o diretório existir. -<15> Mede e apresenta o tempo decorrido após rodar a função `downloader`. -<16> Chama `main` com a função `download_many`. - -[TIP] -==== -A biblioteca https://fpy.li/httpx[_HTTPX_] é inspirada no pacote pythônico -https://fpy.li/20-5[_requests_], -mas foi desenvolvida sobre bases mais modernas. -Especialmente, _HTTPX_ tem APIs síncronas e assíncronas, -então podemos usá-la em todos os exemplos de clientes HTTP nesse capítulo e no próximo. -A biblioteca padrão do Python contém o módulo `urllib.request`, -mas sua API é exclusivamente síncrona, e não é muito amigável. -==== - -Não há mesmo nada de novo em _flags.py_. -Ele serve de base para comparação com outros scripts, e o usei como uma biblioteca, para evitar código redundante ao implementar aqueles scripts. -Vamos ver agora uma reimplementação usando `concurrent.futures`.((("", startref="IOsequen20"))) - -[[downloading_with_futures_sec]] -==== Download com concurrent.futures - -Os((("network I/O", "downloading with concurrent.futures", id="IOfutures20")))((("concurrent.futures", "downloading with", id="confut20"))) principais recursos do pacote `concurrent.futures` são as classes `ThreadPoolExecutor` e `ProcessPoolExecutor`, que implementam uma API para submissão de _callables_ (_"chamáveis"_) para execução em diferentes threads ou processos, respectivamente. -As classes gerenciam de forma transparente um grupo de threads ou processos de trabalho, e filas para distribuição de tarefas e coleta de resultados. -Mas a interface é de um nível muito alto, e não precisamos saber nada sobre qualquer desses detalhes para um caso de uso simples como nossos downloads de bandeiras. - -O <> mostra a forma mais fácil de implementar os downloads de forma concorrente, usando o método `ThreadPoolExecutor.map`. - -[[flags_threadpool_ex]] -.flags_threadpool.py: script de download com threads, usando `futures.ThreadPoolExecutor` -==== -[source, py] ----- -include::code/20-executors/getflags/flags_threadpool.py[tags=FLAGS_THREADPOOL] ----- -==== -<1> Reutiliza algumas funções do módulo `flags` (<>). -<2> Função para baixar uma única imagem; isso é o que cada thread de trabalho vai executar. -<3> Instancia o `ThreadPoolExecutor` como um gerenciador de contexto; o método pass:[executor​.__exit__] vai chamar `executor.shutdown(wait=True)`, que vai bloquear até que todas as threads terminem de rodar. -<4> O método `map` é similar ao `map` embutido, exceto que a função `download_one` será chamada de forma concorrente por múltiplas threads; ele retorna um gerador que você pode iterar para recuperar o valor retornado por cada chamada da função—nesse caso, cada chamada a `download_one` vai retornar um código de país. -<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. - -[TIP] -==== -O <> é muito curto porque pude reutilizar a maior parte das funções do script sequencial _flags.py_. -Uma das melhores características do `concurrent.futures` é tornar simples a execução concorrente de código sequencial legado. -==== - -O construtor de `ThreadPoolExecutor` recebe muitos argumentos além dos mostrados aqui, mas o primeiro e mais importante é `max_workers`, definindo o número máximo de threads de trabalho a serem executadas. -Quando `max_workers` é `None` (o default), -pass:[ThreadPool​Executor] decide seu valor usando, desde o Python 3.8, a seguinte expressão: - -[source, py] ----- -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`]: - -[quote] -____ -Esse valor default conserva pelo menos 5 threads de trabalho para tarefas de E/S. Ele utiliza no máximo 32 núcleos da CPU para tarefas de processamento, o quê libera a GIL. E ele evita usar recursos muitos grandes implicitamente em máquinas com muitos núcleos. - -`ThreadPoolExecutor` agora também reutiliza threads de trabalho inativas antes iniciar [novas] threads de trabalho de `max_workers`. -____ - -Concluindo: o valor default calculado de `max_workers` é razoável, e `ThreadPoolExecutor` evita iniciar novas threads de trabalho desnecessariamente. -Entender a lógica por trás de `max_workers` pode ajudar a decidir quando e como estabelecer o valor em seu código. - -A biblioteca se chama _concurrency.futures_, mas não há qualquer _future_ à vista no <>, então você pode estar se perguntando onde estão eles. A próxima seção explica isso.((("", startref="confut20")))((("", startref="IOfutures20"))) - -[[where_futures_sec]] -==== Onde estão os futures? - -Os((("network I/O", "role of futures", id="IOfuturesrole20")))((("futures", "basics of", id="Fbasic20"))) _futures_ (literalmente "futuros") são componentes centrais de `concurrent.futures` e de `asyncio`, mas como usuários dessas bibliotecas, raramente os vemos. -O <> depende de _futures_ por trás do palco, -mas o código apresentado não lida diretamente com objetos dessa classe. -Essa seção apresenta uma visão geral dos _futures_, com um exemplo mostrando-os em ação. - -Desde o Python 3.4, há duas classes chamadas `Future` na biblioteca padrão: -`concurrent.futures.Future` e `asyncio.Future`. -Elas tem o mesmo propósito: -uma instância de qualquer das classes `Future` representa um processamento adiado, -que pode ou não ter sido completado. -Isso é algo similar à classe `Deferred` no Twisted, -a classe `Future` no Tornado, e objetos `Promise` no Javascript moderno. - -Os _futures_ encapsulam operações pendentes de forma que possamos colocá-los em filas, -verificar se terminaram, e recuperar resultados (ou exceções) quando eles ficam #disponíveis#. - -Uma coisa importante de saber sobre _futures_ é eu e você, não devemos criá-los: -eles são feitos para serem instanciados exclusivamente pela framework de concorrência, -seja ela a `concurrent.futures` ou a `asyncio`. -O motivo é que um `Future` representa algo que vai ser executado em algum momento, -portanto precisa ser agendado para rodar, e quem agenda tarefas é a framework. - -Especificamente, instâncias `concurrent.futures.Future` são criadas apenas como resultado da submissão de um objeto invocável (_callable_) -para execução a uma subclasse de `concurrent.futures.Executor`. -Por exemplo, o método `Executor.submit()` recebe um invocável, agenda sua execução e retorna um `Future`. - -O código da aplicação não deve mudar o estado de um _future_: -a framework de concorrência muda o estado de um _future_ quando o processamento que ele representa termina, e não temos como controlar quando isso acontece. - -Ambos os tipos de `Future` tem um método `.done()` não-bloqueante, que retorna um Boolean informando se o invocável encapsulado por aquele `future` foi ou não executado. -Entretanto, em vez de perguntar repetidamente se um _future_ terminou, o código cliente em geral pede para ser notificado. -Por isso as duas classes `Future` tem um método `.add_done_callback()`: -você passa a ele um invocável e aquele invocável será invocado com o _future_ como único argumento, quando o _future_ tiver terminado. -Observe que aquele invocável de callback será invocado na mesma thread ou processo de trabalho que rodou a função encapsulada no _future_. - -Há também um método `.result()`, que funciona igual nas duas classes quando a execução do _future_ termina: -ele retorna o resultado do invocável, ou relança qualquer exceção que possa ter aparecido quando o invocável foi executado. -Entretanto, quando o _future_ não terminou, o comportamento do método `result` é bem diferente entre os dois sabores de `Future`. -Em uma instância de `concurrency.futures.Future`, -invocar `f.result()` vai bloquear a thread que chamou até o resultado ficar pronto. -Um argumento `timeout` opcional pode ser passado, e se o _future_ não tiver terminado após aquele tempo, o método `result` gera um `TimeoutError`. -O método `asyncio.Future.result` não suporta um timeout, e `await` é a forma preferencial de obter o resultado de _futures_ no `asyncio`—mas `await` não funciona com instâncias de `concurrency.futures.Future`. - -Várias funções em ambas as bibliotecas retornam _futures_; outras os usam em sua implementação de uma forma transparente para o usuário. -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 -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`: -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`. -O código de `download_many` aumentou de 5 para 17 linhas, -mas agora podemos inspecionar os misteriosos _futures_. -As outras funções são idênticas as do <>. - -[[flags_threadpool_futures_ex]] -.flags_threadpool_futures.py: substitui `executor.map` por `executor.submit` e `futures.as_completed` na função `download_many` -==== -[source, py] ----- -include::code/20-executors/getflags/flags_threadpool_futures.py[tags=FLAGS_THREADPOOL_AS_COMPLETED] ----- -==== -[role="pagebreak-before less_space"] -<1> Para essa demonstração, usa apenas os cinco países mais populosos. -<2> Configura `max_workers` para `3`, para podermos ver os _futures_ pendentes na saída. -<3> Itera pelos códigos de país em ordem alfabética, para deixar claro que os resultados vão aparecer fora de ordem. -<4> `executor.submit` agenda o invocável a ser executado, e retorna um `future` representando essa operação pendente. -<5> Armazena cada `future`, para podermos recuperá-los mais tarde com `as_completed`. -<6> Mostra uma mensagem com o código do país e seu respectivo `future`. -<7> `as_completed` entrega _futures_ conforme eles terminam. -<8> Recupera o resultado desse `future`. -<9> Mostra o `future` e seu resultado. - -Observe que a chamada a `future.result()` nunca bloqueará a thread nesse exemplo, pois `future` está vindo de `as_completed`. O - <> mostra a saída de uma execução do <>. - -[[flags_threadpool_futures_run]] -.Saída de flags_threadpool_futures.py -==== -[source, text] ----- -$ python3 flags_threadpool_futures.py -Scheduled for BR: <1> -Scheduled for CN: -Scheduled for ID: -Scheduled for IN: <2> -Scheduled for US: -CN result: 'CN' <3> -BR ID result: 'BR' <4> - result: 'ID' -IN result: 'IN' -US result: 'US' - -5 downloads in 0.70s ----- -==== -<1> Os _futures_ são agendados em ordem alfabética; o `repr()` de um `future` mostra seu estado: os três primeiros estão `running`, pois há três threads de trabalho. -<2> Os dois últimos `futures` estão `pending`; esperando pelas threads de trabalho. -<3> O primeiro `CN` aqui é a saída de `download_one` em uma thread de trabalho; o resto da linha é a saída de `download_many`. -<4> Aqui, duas threads retornam códigos antes que `download_many` na thread principal possa mostrar o resultado da primeira thread. - -[role="man-height-1-125"] -[TIP] -==== -Recomendo experimentar com _flags_threadpool_futures.py_. -Se você o rodar várias vezes, vai ver a ordem dos resultados variar. -Aumentar `max_workers` para `5` vai aumentar a variação na ordem dos resultados. -Diminuindo aquele valor para `1` fará o script rodar de forma sequencial, e a ordem dos resultados será sempre a ordem das chamadas a `submit`. -==== - -Vimos duas variantes do script de download usando `concurrent.futures`: uma no <> com `ThreadPoolExecutor.map` e uma no <> com `futures.as_completed`. -Se você está curioso sobre o código de _flags_asyncio.py_, pode espiar o <> no <>, onde ele é explicado. - -Agora vamos dar uma olhada rápida em um modo simples de desviar da GIL para tarefas de uso intensivo de CPU, usando `concurrent.futures`.((("", startref="Fbasic20")))((("", startref="IOfuturesrole20")))((("", startref="CEwebdown20"))) - -[[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 -// `ProcessPoolExecutor` class. -pass:[ProcessPool​Executor]. - -Ambas as classes, `ProcessPoolExecutor` e `ThreadPoolExecutor` implementam a interface https://fpy.li/20-9[`Executor`], então é fácil mudar de uma solução baseada em threads para uma baseada em processos usando `concurrent.futures`. - -Não há nenhuma vantagem em usar um `ProcessPoolExecutor` no exemplo de download de bandeiras ou em qualquer tarefa concentrada em E/S. -É fácil comprovar isso; apenas modifique as seguintes linhas no <>: - -[source, python3] ----- -def download_many(cc_list: list[str]) -> int: - with futures.ThreadPoolExecutor() as executor: ----- - -para: - -[source, python3] ----- -def download_many(cc_list: list[str]) -> int: - with futures.ProcessPoolExecutor() as executor: ----- - -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 verificação de números primos de<>, e reescrevê-lo com [.keep-together]#`concurrent.futures`.# - -[[multicore_prime_redux_sec]] -==== Verificador de primos multinúcleo redux - -Na seção <>, estudamos _procs.py_, um script que verificava se alguns números grandes eram primos usando `multiprocessing`. -No <> resolvemos o mesmo problema com o programa _proc_pool.py_, usando um pass:[ProcessPool​Executor]. Do primeiro `import` até a chamada a `main()` no final, _procs.py_ tem 43 linhas de código não-vazias, e _proc_pool.py_ tem 31—28% mais curto. - -[[proc_pool_py]] -.proc_pool.py: _procs.py_ reescrito com `ProcessPoolExecutor` -==== -[source, py] ----- -include::code/20-executors/primes/proc_pool.py[tags=PRIMES_POOL] ----- -==== -<1> Não há necessidade de importar `multiprocessing`, `SimpleQueue` etc.; `concurrent.futures` esconde tudo isso. -<2> A tupla `PrimeResult` e a função `check` são as mesmas que vimos em _procs.py_, mas não precisamos mais das filas nem da função `worker`. -<3> Em vez de decidirmos por nós mesmos quantos processos de trabalho serão usados se um argumento não for passado na linha de comando, atribuímos `None` a `workers` e deixamos o `ProcessPoolExecutor` decidir. -<4> Aqui criei o `ProcessPoolExecutor` antes do bloco `with` em ➐, para poder mostrar o número real de processos na próxima linha. -<5> `_max_workers_` é um atributo de instância não-documentado de um `ProcessPoolExecutor`. Decidi usá-lo para mostrar o número de processos de trabalho criados quando a variável `workers` é `None`. O _Mypy_ corretamente reclama quando eu acesso esse atributo, então coloquei o comentário `type: ignore` para silenciar a reclamação. -<6> Ordena os números a serem verificados em ordem descendente. Isso vai mostrar a diferença no comportamento de _proc_pool.py_ quando comparado a _procs.py_. Veja a explicação após esse exemplo. -<7> Usa o `executor` como um gerenciador de contexto. -<8> A chamada a `executor.map` retorna as instâncias de `PrimeResult` retornadas por `check` na mesma ordem dos argumentos `numbers`. - -Se você rodar o <>, verá os resultados aparecente em ordem rigorosamente descendente, como mostrado no <>. -Por outro lado, a ordem da saída de _procs.py_ (mostrado em <>) é severamente influenciado pela dificuldade em verificar se cada número é ou não primo. -Por exemplo, _procs.py_ mostra o resultado para 7777777777777777 próximo ao topo, pois ele tem um divisor pequeno, 7, então `is_prime` rapidamente determina que ele não é primo. - -Já o de 7777777536340681 is 88191709^2^, então `is_prime` vai demorar muito mais para determinar que esse é um número composto, -e ainda mais para descobrir que 7777777777777753 é primo—assim, ambos esses números aparecem próximo do final da saída de _procs.py_. - -Ao rodar _proc_pool.py_, podemos observar não apenas a ordem descendente dos resultados, mas também que o programa parece emperrar após mostrar o resultado para 9999999999999999. - -[[proc_pool_py_output]] -.Saída de proc_pool.py -==== -[source, text] ----- -$ ./proc_pool.py -Checking 20 numbers with 12 processes: -9999999999999999 0.000024s # <1> -9999999999999917 P 9.500677s # <2> -7777777777777777 0.000022s # <3> -7777777777777753 P 8.976933s -7777777536340681 8.896149s -6666667141414921 8.537621s -6666666666666719 P 8.548641s -6666666666666666 0.000002s -5555555555555555 0.000017s -5555555555555503 P 8.214086s -5555553133149889 8.067247s -4444444488888889 7.546234s -4444444444444444 0.000002s -4444444444444423 P 7.622370s -3333335652092209 6.724649s -3333333333333333 0.000018s -3333333333333301 P 6.655039s - 299593572317531 P 2.072723s - 142702110479723 P 1.461840s - 2 P 0.000001s -Total time: 9.65s ----- -==== -<1> Essa linha aparece muito rápido. -<2> Essa linha demora mais de 9,5s para aparecer. -<3> Todas as linhas restantes aparecem quase imediatamente. - -[role="pagebreak-before less_space"] -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. -* 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 verificar. -* Enquanto isso, os 11 processos restantes estarão verificando outros números, que são ou primos ou compostos com fatores grandes ou compostos com fatores muito pequenos. -* Quando o processo de trabalho encarregado de 9999999999999917 finalmente determina que ele é primo, todos os outros processos já completaram suas últimas tarefas, então os resultados aparecem logo depois. - -[NOTE] -==== -Apesar do progresso de _proc_pool.py_ não ser tão visível quanto o de _procs.py_, o tempo total de execução, para o mesmo número de processo de trabalho e de núcleos de CPU, é praticamente idêntico, como retratado em <>. -==== - -Entender como programas concorrentes se comportam não é um processo direto, então aqui está um segundo experimento que pode ajudar a visualizar o funcionamento de -`Executor.map`.((("", startref="CFlaunch20")))((("", startref="CElaunch20")))((("", startref="Plaunch20"))) - -=== Experimentando com Executor.map - - -Vamos((("concurrent executors", "Executor.map", id="CEexecutormap20")))((("Executor.map", id="execumap20"))) investigar `Executor.map`, agora usando um `ThreadPoolExecutor` com três threads de trabalho rodando cinco chamáveis que retornam mensagens marcadas com data/hora. O código está no <>, o resultado no <>. - -[[demo_executor_map_ex]] -.demo_executor_map.py: Uma demonstração simples do método map de `ThreadPoolExecutor` -==== -[source, py] ----- -include::code/20-executors/demo_executor_map.py[tags=EXECUTOR_MAP] ----- -==== -<1> Essa função exibe o momento da execução no formato `[HH:MM:SS]` e os argumentos recebidos. -<2> `loiter` não faz nada além mostrar uma mensagem quanto inicia, dormir por `n` segundos, e mostrar uma mensagem quando termina; são usadas tabulações para indentar as mensagens de acordo com o valor de `n`. -<3> `loiter` retorna `n * 10`, então podemos ver como coletar resultados. -<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. - -[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. - -O <> mostra uma execução típica do <>. - -[[demo_executor_map_run]] -.Amostra da execução de demo_executor_map.py, do <> -==== -[source, text] ----- -$ python3 demo_executor_map.py -[15:56:50] Script starting. <1> -[15:56:50] loiter(0): doing nothing for 0s... <2> -[15:56:50] loiter(0): done. -[15:56:50] loiter(1): doing nothing for 1s... <3> -[15:56:50] loiter(2): doing nothing for 2s... -[15:56:50] results: <4> -[15:56:50] loiter(3): doing nothing for 3s... <5> -[15:56:50] Waiting for individual results: -[15:56:50] result 0: 0 <6> -[15:56:51] loiter(1): done. <7> -[15:56:51] loiter(4): doing nothing for 4s... -[15:56:51] result 1: 10 <8> -[15:56:52] loiter(2): done. <9> -[15:56:52] result 2: 20 -[15:56:53] loiter(3): done. -[15:56:53] result 3: 30 -[15:56:55] loiter(4): done. <10> -[15:56:55] result 4: 40 ----- -==== -<1> Essa execução começou em 15:56:50. -<2> A primeira thread executa `loiter(0)`, então vai dormir por 0s e retornar antes mesmo da segunda thread ter chance de começar, mas YMMV.footnote:[Acrônimo de _your mileage may vary_, algo como _sua quilometragem pode variar_, querendo dizer "seu caso pode ser diferente". Com threads, você nunca sabe a sequência exata de eventos que deveriam acontecer quase ao mesmo tempo; é possível que, em outra máquina, se veja `loiter(1)` começar antes de `loiter(0)` terminar, especialmente porque `sleep` sempre libera a GIL, então o Python pode mudar para outra thread mesmo se você dormir por 0s.] -<3> `loiter(1)` e `loiter(2)` começam imediatamente (como o pool de threads tem 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)`. -<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. - -A função `Executor.map` é fácil de usar, -mas muitas vezes é preferível obter os resultados assim que estejam prontos, independente da ordem em que foram submetidos. -Para fazer isso, precisamos de uma combinação do método `Executor.submit` e da função `futures.as_completed` como vimos no <>. Vamos voltar a essa técnica na seção <>. - -[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`.# -==== - -Na próxima seção vamos retomar os exemplos de download de bandeiras com novos requerimentos que vão nos obrigar a iterar sobre os resultados de `futures.as_completed` em vez de usar `executor.map`.((("", startref="execumap20")))((("", startref="CEexecutormap20"))) - - -[[flags2_sec]] -=== Download com exibição do progresso e tratamento de erro - -Como((("concurrent executors", "downloads with progress display and error handling", id="CEdown20")))((("network I/O", "downloads with progress display and error handling", id="IOprog20")))((("progress displays", id="progdisp20")))((("error handling, in network I/O", secondary-sortas="network I/O", id="EHnetwork20"))) mencionado, os scripts em <> não tem tratamento de erros, para torná-los mais fáceis de ler e para comparar a estrutura das três abordagens: sequencial, com threads e assíncrona. - -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 -pass:[fluentpython/example-code-2e] repositório: -https://fpy.li/20-10[_20-executors/getflags/flags2_common.py_]. - -flags2_sequential.py:: Um cliente HTTP sequencial com tratamento de erro correto e a exibição de uma barra de progresso. Sua função `download_one` também é usada por `flags2_threadpool.py`. - -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 seção <>, [.keep-together]#no capítulo <>.# - -[[careful_testing_clients]] -[WARNING] -.Tenha cuidado ao testar clientes concorrentes -==== -Ao testar clientes HTTP concorrentes em servidores web públicos, você pode gerar muitas requisições por segundo, e é assim que ataques de negação de serviço (DoS, _denial-of-service_) são feitos. -Controle cuidadosamente seus clientes quando for usar servidores públicos. -Para testar, configure um servidor HTTP local. Veja o <> para instruções. -==== - -A característica mais visível dos exemplos `flags2` é sua barra de progresso animada em modo texto, implementada com o https://fpy.li/20-11[pacote _tqdm_]. Publiquei um https://fpy.li/20-12[vídeo de 108s no YouTube] mostrando a barra de progresso e comparando a velocidade dos três scripts `flags2`. No vídeo, começo com o download sequencial, mas interrompo a execução após 32s. O script demoraria mais de 5 minutos para acessar 676 URLs e baixar 194 bandeiras. Então rodo o script usando threads e o que usa `asyncio`, três vezes cada um, e todas as vezes eles completam a tarefa em 6s ou menos (isto é mais de 60 vezes mais rápido). A <> mostra duas capturas de tela: durante e após a execução de _flags2_threadpool.py_. - -[[flags2_progress_fig]] -.Acima, à esquerda: flags2_threadpool.py rodando com a barra de progresso em tempo real gerada pelo tqdm; Abaixo, à direita: mesma janela do terminal após o script terminar de rodar. -image::images/flpy_2001.png[flags2_threadpool.py running with progress bar] - -O exemplo de uso mais simples do _tqdm_ aparece em um _.gif_ animado, no https://fpy.li/20-13[_README.md_] do projeto. -Se você digitar o código abaixo no console do Python após instalar o pacote _tqdm_, -uma barra de progresso animada aparecerá no lugar onde está o comentário: - -[source, pycon] ----- ->>> import time ->>> from tqdm import tqdm ->>> for i in tqdm(range(1000)): -... time.sleep(.01) -... ->>> # -> progress bar will appear here <- ----- - -Além do efeito elegante, o `tqdm` também é conceitualmente interessante: -ele consome qualquer iterável, e produz um iterador que, enquanto é consumido, mostra a barra de progresso e estima o tempo restante para completar todas as iterações. Para calcular aquela estimativa, o `tqdm` precisa receber um iterável que tenha um `len`, ou receber adicionalmente o argumento `total=` com o número esperado de itens. Integrar o `tqdm` com nossos exemplos `flags2` proporciona um oportunidade de observar mais profundamente o funcionamento real dos scripts concorrentes, pois nos obriga a usar as funções https://fpy.li/20-7[`futures.as_completed`] e https://fpy.li/20-15[`asyncio.as_completed`], para permitir que o `tqdm` mostre o progresso conforme cada `future` é termina sua execução. - -A outra característica dos exemplos `flags2` é a interface de linha de comando. -Todos os três scripts aceitam as mesmas opções, -e você pode vê-las rodando qualquer um deles com a opção `-h`. -O <> mostra o texto de ajuda. - -[[flags2_help_demo]] -.Tela de ajuda dos scripts da série flags2 -==== -[source, text] ----- -$ python3 flags2_threadpool.py -h -usage: flags2_threadpool.py [-h] [-a] [-e] [-l N] [-m CONCURRENT] [-s LABEL] - [-v] - [CC [CC ...]] - -Download flags for country codes. Default: top 20 countries by population. - -positional arguments: - CC country code or 1st letter (eg. B for BA...BZ) - -optional arguments: - -h, --help show this help message and exit - -a, --all get all available flags (AD to ZW) - -e, --every get flags for every possible code (AA...ZZ) - -l N, --limit N limit to N first codes - -m CONCURRENT, --max_req CONCURRENT - maximum concurrent requests (default=30) - -s LABEL, --server LABEL - Server to hit; one of DELAY, ERROR, LOCAL, REMOTE - (default=LOCAL) - -v, --verbose output detailed progress info - ----- -==== - -Todos os argumentos são opcionais. Mas o `-s/--server` é essencial para os testes: -ele permite escolher qual servidor HTTP e qual porta serão usados no teste. -Passe um desses parâmetros (insensíveis a maiúsculas/minúsculas) para determinar onde o script vai buscar as bandeiras: - -`LOCAL`:: Usa `http://localhost:8000/flags`; esse é o default. Você deve configurar um servidor HTTP local, respondendo na porta 8000. Veja as instruções na nota a seguir. - -`REMOTE`:: Usa `http://fluentpython.com/data/flags`; este é meu site público, hospedado em um servidor compartilhado. Por favor, não o martele com requisições concorrentes excessivas. O domínio _fluentpython.com_ é gerenciado pela CDN (Content Delivery Network, _Rede de Fornecimento de Conteúdo_) da https://fpy.li/20-16[Cloudflare], então você pode notar que os primeiros downloads são mais lentos, mas ficam mais rápidos conforme o cache da CDN é carregado. - -`DELAY`:: Usa `http://localhost:8001/flags`; um servidor atrasando as respostas HTTP deve responder na porta 8001. Escrevi o _slow_server.py_ para facilitar o experimento. Ele está no diretório _20-futures/getflags/_ do https://fpy.li/code[repositório de código do _Python Fluente_]. Veja as instruções na nota a seguir. - -`ERROR`:: Usa `http://localhost:8002/flags`; um servidor devolvendo alguns erros HTTP deve responder na porta 8002. Instruções a seguir. - -.Configurando os servidores de teste -[[setting_up_servers_box]] -[NOTE] -==== -Se((("test servers")))((("servers", "test servers"))) você não tem um servidor HTTP local para testes, escrevi instruções de configuração usando apenas Python ≥ 3.9 (nenhuma biblioteca externa) em -https://fpy.li/20-17[_20-executors/getflags/README.adoc_] -no https://fpy.li/code[_fluentpython/example-code-2e_] repositório. -Em resumo, o _README.adoc_ descreve como usar: - -`python3 -m http.server`:: O servidor `LOCAL` na porta 8000 -`python3 slow_server.py`:: O servidor `DELAY` na porta 8001, que acrescenta um atraso aleatório de 0,5s a 5s antes de cada resposta -`python3 slow_server.py 8002 --error-rate .25`:: O servidor `ERROR` na porta 8002, que além do atraso aleatório tem uma chance de 25% de retornar um erro https://fpy.li/20-18["418 I'm a teapot"] como resposta -==== - -Por default, cada script _flags2*.py_ irá baixar as bandeiras dos 20 países mais populosos do servidor `LOCAL` (`http://localhost:8000/flags`), usando um número default de conexões concorrentes, que varia de script para script. -O <> mostra uma execução padrão do script _flags2_sequential.py_ usando as configurações default. -Para rodá-lo, você precisa de um servidor local, como explicado em <>. - -[[flags2_sequential_run]] -.Rodando flags2_sequential.py com todos os defaults: `site LOCAL`, as 20 bandeiras dos países mais populosos, 1 conexão concorrente -==== -[source] ----- -$ python3 flags2_sequential.py -LOCAL site: http://localhost:8000/flags -Searching for 20 flags: from BD to VN -1 concurrent connection will be used. --------------------- -20 flags downloaded. -Elapsed time: 0.10s ----- -==== - -Você pode selecionar as bandeiras a serem baixadas de várias formas. -O <> mostra como baixar todas as bandeiras com códigos de país começando pelas letras A, B ou C. - -[[flags2_threadpool_run]] -.Roda flags2_threadpool.py para obter do servidor `DELAY` todas as bandeiras com prefixos de códigos de país A, B ou C -==== -[source] ----- -$ python3 flags2_threadpool.py -s DELAY a b c -DELAY site: http://localhost:8001/flags -Searching for 78 flags: from AA to CZ -30 concurrent connections will be used. --------------------- -43 flags downloaded. -35 not found. -Elapsed time: 1.72s ----- -==== - -Independente de como os códigos de país são selecionados, o número de bandeiras a serem obtidas pode ser limitado com a opção `-l/--limit`. O <> demonstra como fazer exatamente 100 requisições, combinando a opção `-a` para obter todas as bandeiras com `-l 100`. - -[[flags2_asyncio_run]] -.Roda flags2_asyncio.py para baixar 100 bandeiras (`-al 100`) do servidor `ERROR`, usando 100 requisições concorrentes (`-m 100`) -==== -[source] ----- -$ python3 flags2_asyncio.py -s ERROR -al 100 -m 100 -ERROR site: http://localhost:8002/flags -Searching for 100 flags: from AD to LK -100 concurrent connections will be used. --------------------- -73 flags downloaded. -27 errors. -Elapsed time: 0.64s ----- -==== - - -Essa é a interface de usuário dos exemplos `flags2`. Vamos ver como eles estão implementados. - - -==== Tratamento de erros nos exemplos flags2 - -A estratégia comum em todos os três exemplos para lidar com erros HTTP é que erros 404 (not found) são tratados pela função encarregada de baixar um único arquivo (`download_one`). Qualquer outra exceção propaga para ser tratada pela função `download_many` ou pela corrotina `supervisor`—no exemplo de `asyncio`. - -Vamos novamente começar estudando o código sequencial, que é mais fácil de compreender—e muito reutilizado pelo script com um pool de threads. O <> mostra as funções que efetivamente fazer os downloads nos scripts _flags2_sequential.py_ e _flags2_threadpool.py_. - -[[flags2_basic_http_ex]] -.flags2_sequential.py: funções básicas encarregadas dos downloads; ambas são reutilizadas no flags2_threadpool.py -==== -[source, py] ----- -include::code/20-executors/getflags/flags2_sequential.py[tags=FLAGS2_BASIC_HTTP_FUNCTIONS] ----- -==== -<1> Importa a biblioteca de exibição de barra de progresso `tqdm`, e diz ao Mypy para não checá-la.footnote:[Em setembro de 2021 não havia dicas de tipo na versão (então) atual do `tqdm`. Tudo bem. O mundo não vai acabar por causa disso. Obrigado, Guido, pela tipagem opcional!] -<2> Importa algumas funções e um `Enum` do módulo `flags2_common`. -<3> Dispara um `HTTPStatusError` se o código de status do HTTP não está em `range(200, 300)`. -<4> `download_one` trata o `HTTPStatusError`, especificamente para tratar o código HTTP 404... -<5> ...mudando seu `status` local para `DownloadStatus.NOT_FOUND`; `DownloadStatus` é um `Enum` importado de _flags2_common.py_. -<6> Qualquer outra exceção de `HTTPStatusError` é re-emitida e propagada para quem chamou a função. -<7> Se a opção de linha de comando `-v/--verbose` está vigente, o código do país e a mensagem de status são exibidos; é assim que você verá o progresso no modo `verbose`. - -O <> lista a versão sequencial da função `download_many`. O código é simples, mas vale a pena estudar para compará-lo com as versões concorrentes que veremos a seguir. Se concentre em como ele informa o progresso, trata erros e conta os downloads. - -[[flags2_download_many_seq]] -.flags2_sequential.py: a implementação sequencial de `download_many` -==== -[source, py] ----- -include::code/20-executors/getflags/flags2_sequential.py[tags=FLAGS2_DOWNLOAD_MANY_SEQUENTIAL] ----- -==== -<1> Este `Counter` vai registrar os diferentes resultados possíveis dos downloads: `DownloadStatus.OK`, `DownloadStatus.NOT_FOUND`, ou `DownloadStatus.ERROR`. -<2> `cc_iter` mantém a lista de códigos de país recebidos como argumentos, em ordem alfabética. -<3> Se não estamos rodando em modo `verbose`, `cc_iter` é passado para o `tqdm`, que retorna um iterador que produz os itens em `cc_iter` enquanto também anima a barra de progresso. -<4> Faz chamadas sucessivas a `download_one`. -<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. -<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`. -<11> Se no modo `verbose`, mostra a mensagem de erro para o código de país atual, se houver. -<12> Retorna `counter` para que `main` possa mostrar os números no relatório final. - - -Agora vamos estudar _flags2_threadpool.py_, o exemplo de pool de threads refatorado. - -[[using_futures_as_completed_sec]] -==== Usando futures.as_completed - -Para((("futures.as_completed", id="futuresas20"))) integrar a barra de progresso do _tqdm_ e tratar os erros a cada requisição, o script _flags2_threadpool.py_ usa o `futures.ThreadPoolExecutor` com a função, já vista anteriormente, pass:[futures.as_completed]. O <> é a listagem completa de _flags2_threadpool.py_. Apenas a função `download_many` é implementada; as outras funções são reutilizadas de _flags2_common.py_ e _flags2_sequential.py_. - -[[flags2_threadpool_full]] -.flags2_threadpool.py: listagem completa -==== -[source, py] ----- -include::code/20-executors/getflags/flags2_threadpool.py[tags=FLAGS2_THREADPOOL] ----- -==== -<1> Reutiliza `download_one` de `flags2_sequential` (<>). -<2> Se a opção de linha de comando `-m/--max_req` não é passada, este será o número máximo de requisições concorrentes, implementado como o tamanho do poll de threads; o número real pode ser menor se o número de bandeiras a serem baixadas for menor. -<3> `MAX_CONCUR_REQ` limita o número máximo de requisições concorrentes independente do número de bandeiras a serem baixadas ou da opção de linha de comando `-m/--max_req`. É uma medida de segurança, para evitar iniciar threads demais, com seu uso significativo de memória. -<4> Cria o `executor` com `max_workers` determinado por `concur_req`, calculado pela função `main` como o menor de: `MAX_CONCUR_REQ`, o tamanho de `cc_list`, ou o valor da opção de linha de comando `-m/--max_req`. Isso evita criar mais threads que o necessário. -<5> Este `dict` vai mapear cada instância de `Future`—representando um download—com o respectivo código de país, para exibição de erros. -<6> Itera sobre a lista de códigos de país em ordem alfabética. A ordem dos resultados vai depender, mais do que de qualquer outra coisa, do tempo das respostas HTTP; mas se o tamanho do pool de threads (dado por `concur_req`) for muito menor que `len(cc_list)`, você poderá ver os downloads aparecendo em ordem alfabética. -<7> Cada chamada a `executor.submit` agenda a execução de uma invocável e retorna uma instância de `Future`. O primeiro argumento é a invocável, o restante são os argumentos que ela receberá. -<8> Armazena o `future` e o código de país no `dict`. -<9> `futures.as_completed` retorna um iterador que produz _futures_ conforme cada tarefa é completada. -<10> Se não estiver no modo `verbose`, passa o resultado de `as_completed` com a função `tqdm`, para mostrar a barra de progresso; como `done_iter` não tem `len`, precisamos informar o `tqdm` qual o número de itens esperado com o argumento `total=`, para que ele possa estimar o trabalho restante. -<11> Itera sobre os _futures_ conforme eles vão terminando. -<12> Chamar o método `result` em um _future_ retorna ou o valor retornado pela invocável ou dispara qualquer exceção que tenha sido capturada quando a invocável foi executada. Esse método pode bloquear quem chama, esperando por uma resolução. Mas não nesse exemplo, porque `as_completed` só retorna _futures_ que terminaram sua execução. -<13> Trata exceções em potencial; o resto dessa função é idêntica à função `download_many` no <>), exceto pela observação a seguir. -<14> Para dar contexto à mensagem de erro, recupera o código de país do `to_do_map`, usando o `future` atual como chave. Isso não era necessário na versão sequencial, pois estávamos iterando sobre a lista de códigos de país, então sabíamos qual era o `cc` atual; aqui estamos iterando sobre _futures_. - - -[TIP] -==== -O <> usa um idioma que é muito útil com `futures.as_completed`: -construir um `dict` mapeando cada _future_ a outros dados que podem ser úteis quando o _future_ terminar de executar. Aqui o `to_do_map` mapeia cada _future_ ao código de país atribuído a ele. Isso torna fácil realizar o pós-processamento com os resultados dos _futures_, apesar deles serem produzidos fora de ordem. -==== - -As threads do Python são bastante adequadas a aplicações de uso intensivo de E/S, e o pacote `concurrent.futures` as torna relativamente simples de implementar em certos casos de uso. Com `ProcessPoolExecutor` você também pode resolver problemas de uso intensivo de CPU em múltiplos núcleos—se o processamento for https://fpy.li/20-19["embaraçosamente paralelo"]. Isso encerra nossa introdução básica a `concurrent.futures`.((("", startref="futuresas20")))((("", startref="EHnetwork20")))((("", startref="progdisp20")))((("", startref="IOprog20")))((("", startref="CEdown20"))) - - -=== Resumo do capítulo - -Nós((("concurrent executors", "overview of"))) começamos o capítulo comparando dois clientes HTTP concorrentes com um sequencial, demonstrando que as soluções concorrentes mostram um ganho significativo de desempenho sobre o script sequencial. - -Após estudar o primeiro exemplo, baseado no `concurrent.futures`, olhamos mais de perto os objetos _future_, instâncias de `concurrent.futures.Future` ou de pass:[asyncio​.Future], enfatizando as semelhanças entre essas classes (suas diferenças serão examinadas no <>). Vimos como criar _futures_ chamando `Executor.submit`, e como iterar sobre _futures_ que terminaram sua execução com `concurrent.futures.as_completed`. - -Então discutimos o uso de múltiplos processos com a classe `concurrent.futures.ProcessPoolExecutor`, para evitar a GIL e usar múltiplos núcleos de CPU, simplificando o verificador de números primos multi-núcleo que vimos antes no <>. - -Na seção seguinte vimos como funciona a `concurrent.futures.ThreadPoolExecutor`, com um exemplo didático, iniciando tarefas que apenas não faziam nada por alguns segundos, exceto exibir seu status e a hora naquele instante. - -Nós então voltamos para os exemplos de download de bandeiras. Melhorar aqueles exemplos com uma barra de progresso e tratamento de erro adequado nos ajudou a explorar melhor a função geradora `future.as_completed` mostrando um modelo comum: armazenar _futures_ em um `dict` para anexar a eles informação adicional quando são submetidos, para podermos usar aquela informação quando o _future_ sai do iterador `as_completed`. - - -=== Para saber mais - -O((("concurrent executors", "further reading on"))) pacote `concurrent.futures` foi uma contribuição de Brian Quinlan, que o apresentou em uma palestra sensacional intitulada https://fpy.li/20-20["The Future Is Soon!"] (EN), na PyCon Australia 2010. A palestra de Quinlan não tinha slides; ele mostra o que a biblioteca faz digitando código diretamente no console do Python. Como exemplo motivador, a apresentação inclui um pequeno vídeo com o cartunista/programador do XKCD, Randall Munroe, executando um ataque de negação de serviço (DoS) não-intencional contra o Google Maps, para criar um mapa colorido de tempos de locomoção pela cidade. A introdução formal à biblioteca é a https://fpy.li/pep3148[PEP 3148 - `futures` - execute computations asynchronously] (_`futures` - executar processamento assíncrono_) (EN). Na PEP, Quinlan escreveu que a biblioteca `concurrent.futures` foi "muito influenciada pelo pacote `java.util.concurrent` do Java." - -Para recursos adicionais falando do `concurrent.futures`, -por favor consulte o <>. -Todas as referências que tratam de `threading` e `multiprocessing` do Python na <> também tratam do `concurrent.futures`. - -.Ponto de vista -**** - -[role="soapbox-title"] -Evitando Threads - -++++ -
-

Concorrência: um dos tópicos mais difíceis na ciência da computação (normalmente é melhor evitá-lo).

-

David Beazley, educador Python e cientista louco—Slide #9 do tutorial "A Curious Course on Coroutines and Concurrency" ("Um Curioso Curso sobre Corrotinas e Concorrência") (EN), apresentado na PyCon 2009.

-
-++++ - -// [quote, David Beazley, Python coach and mad scientist] -// ____ -// Concurrency: one of the most difficult topics in computer science (usually best avoided).footnote:[Slide #9 from https://fpy.li/20-21["A Curious Course on Coroutines and Concurrency,"] tutorial presented at PyCon 2009.] -// ____ - -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]. -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"]. - -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. - -Para problemas de concorrência "não embaraçosos", threads e travas também não são a solução. Ao nível do sistema operacional, as threads nunca vão desaparecer. Mas todas as linguagens de programação que achei empolgantes nos últimos muitos anos fornecem abstrações de alto nível para concorrência, como demonstra o excelente livro de Paul Butcher, https://fpy.li/20-24[_Seven Concurrency Models in Seven Weeks_] (_Sete Modelos de Concorrência em Sete Semanas_) (EN). Go, Elixir, e Clojure estão entre elas. Erlang—a linguagem de implementação do Elixir—é um exemplo claro de uma linguagem projetada desde o início pensando em concorrência. Erlang não me excita por uma razão simples: acho sua sintaxe feia. O Python me acostumou mal. - -José Valim, antes um dos contribuidores centrais do Ruby on Rails, projetou o Elixir com uma sintaxe moderna e agradável. Como Lisp e Clojure, o Elixir implementa macros sintáticas. Isso é uma faca de dois gumes. Macros sintáticas permitem criar DSLs poderosas, mas a proliferação de sub-linguagens pode levar a bases de código incompatíveis e à fragmentação da comunidade. O Lisp se afogou em um mar de macros, cada empresa e grupo de desenvolvedores Lisp usando seu próprio dialeto arcano. A padronização em torno do Common Lisp resultou em uma linguagem inchada. Espero que José Valim inspire a comunidade do Elixir a evitar um destino semelhante. Até agora, o cenário parece bom. O invólucro de bancos de dados e gerador de queries https://fpy.li/20-25[Ecto] é muito agradável de usar: um grande exemplo do uso de macros para criar uma DSL—sigla de Domain-Specific Language, _Linguagem de Domínio Específico_—flexível mas amigável, para interagir com bancos de dados relacionais e não-relacionais. - -Como o Elixir, o Go é uma linguagem moderna com ideias novas. -Mas, em alguns aspectos, é uma linguagem conservadora, comparada ao Elixir. -O Go não tem macros, e sua sintaxe é mais simples que a do Python. -O Go não suporta herança ou sobrecarga de operadores, e oferece menos oportunidades para metaprogramação que o Python. -Essas limitações são consideradas benéficas. -Elas levam a comportamentos e desempenho mais previsíveis. -Isso é uma grande vantagem em ambientes de missão crítica altamente concorrentes, -onde o Go pretende substituir C++, Java e Python. - -Enquanto Elixir e Go são competidores diretos no espaço da alta concorrência, -seus projetos e filosofias atraem públicos diferentes. -Ambos tem boas chances de prosperar. -Mas historicamente, as linguagens mais conservadoras tendem a atrair mais programadores. - -**** diff --git a/capitulos/cap21.adoc b/capitulos/cap21.adoc deleted file mode 100644 index 010fef61..00000000 --- a/capitulos/cap21.adoc +++ /dev/null @@ -1,1818 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo - -[[async_ch]] -== Programação assíncrona - -++++ -
-

O problema com as abordagens usuais da programação assíncrona é que elas são propostas do tipo "tudo ou nada". Ou você reescreve todo o código, de forma que nada nele bloqueie [o processamento] ou você está só perdendo tempo.

-

Alvaro Videla e Jason J. W. Williams, RabbitMQ in Action (RabbitMQ em Ação)Videla & Williams, RabbitMQ in Action (RabbitMQ em Ação) (Manning), Capítulo 4, "Solving Problems with Rabbit: coding and patterns (Resolvendo Problemas com Rabbit: programação e modelos)," p. 61.

-
-++++ - -// [quote, Alvaro Videla & Jason J. W. Williams, RabbitMQ in Action] -// ____ -// The problem with normal approaches to asynchronous programming as that they're all-or-nothing propositions. You rewrite all your code so none of it blocks or you're just wasting your time.footnote:[Videla & Williams, _RabbitMQ in Action (Manning, 2012)_, Chapter 4, _Solving Problems with Rabbit: coding and patterns_, p. 61] -// ____ - -Este((("asynchronous programming", "topics covered"))) capítulo trata de três grandes tópicos intimamente interligados: - -* Os elementos de linguagem `async def`, `await`, `async with`, e `async for` do Python; -* Objetos que suportam tais elementos através de métodos especiais como `+__await__+`, `+__aiter__+` etc., tais como corrotinas nativas e variantes assíncronas de gerenciadores de contexto, iteráveis, geradores e compreensões; -* _asyncio_ e outras bibliotecas assíncronas. - -Este capítulo parte das ideias de iteráveis e geradores (<>, -em particular da <>), gerenciadores de contexto (no <>), -e conceitos gerais de programação concorrente (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. - -Após os exemplos de clientes HTTP, teremos duas aplicações simples de servidor, -uma delas usando a framework cada vez mais popular _FastAPI_. -A seguir tratamos de outros artefatos da linguagem viabilizados pelas palavras-chave `async/await`: -funções geradoras assíncronas, compreensões assíncronas, e expressões geradoras assíncronas. Para realçar o fato daqueles recursos da linguagem não estarem limitados ao _asyncio_, veremos um exemplo reescrito para usar a _Curio_—o elegante e inovador framework inventado por David Beazley. - -Finalizando o capítulo, escrevi uma pequena seção sobre vantagens e -armadilhas da programação assíncrona. - -Há um longo caminho à nossa frente. Teremos espaço apenas para exemplos básicos, -mas eles vão ilustrar as características mais importantes de cada ideia. - - -[TIP] -==== -A https://fpy.li/21-1[documentação do _asyncio_] melhorou((("asyncio package", "documentation"))) muito após Yury Selivanovfootnote:[Selivanov implementou `async/await` no Python, e escreveu as PEPs relacionadas: -https://fpy.li/pep492[492], -https://fpy.li/pep525[525], e -https://fpy.li/pep530[530]. -] reorganizá-la, dando maior destaque às funções úteis para desenvolvedores de aplicações. -A maior parte da API de _asyncio_ consiste em funções e classes voltadas para -criadores de pacotes como frameworks web e drivers de bancos de dados, ou seja, -são necessários para criar bibliotecas assíncronas, mas não aplicações. - -Para mais profundidade sobre _asyncio_, recomendo -pass:[Using Asyncio in Python ("Usando Asyncio em Python")] -de Caleb Hattingh (O'Reilly). -Política de transparência: Caleb é um dos revisores técnicos deste livro. -==== - - -=== Novidades nesse capítulo - -Quando((("asynchronous programming", "significant changes to"))) escrevi a primeira edição de _Python Fluente_, a biblioteca _asyncio_ era provisória e as palavras-chave `async/await` não existiam. -Assim, todos os exemplos desse capítulo precisaram ser atualizados. -Também criei novos exemplos: scripts de sondagem de domínios, um serviço web com _FastAPI_, e experimentos com o novo modo assíncrono do console do Python. - -Novas seções tratam de recursos da linguagem inexistentes naquele momento, como corrotinas nativas, `async with`, `async for`, e os objetos que suportam essas instruções. - -As ideias na seção <> refletem lições importantes tiradas da experiência prática, e a considero uma leitura essencial para qualquer um trabalhando com programação assíncrona. Elas podem ajudar você a evitar muitos problemas—seja no Python, seja no Node.js. - -Por fim, removi vários parágrafos sobre `asyncio.Futures`, que agora considero parte das APIs de baixo nível do _asyncio_. - -[role="pagebreak-before less_space"] -=== Algumas definições. - -No ((("asynchronous programming", "relevant terminology"))) início da seção <>, vimos que, desde o Python 3.5, a linguagem oferece três tipos de corrotinas: - -Corrotina nativa:: - Uma((("native coroutines", "definition of term")))((("coroutines", "types of"))) função corrotina definida com `async def`. Você pode delegar de uma corrotina nativa para outra corrotina nativa, usando a palavra-chave `await`, de forma similar àquela como as corrotinas clássicas usam `yield from`. O comando `async def` sempre define uma corrotina nativa, mesmo se a palavra-chave `await` não seja usada em seu corpo. A palavra-chave `await` não pode ser usada fora de uma corrotina nativa.footnote:[Há uma exceção a essa regra: se você iniciar o Python com a opção `-m asyncio`, pode então usar `await` diretamente no prompt `>>>` para controlar uma corrotina nativa. Isso é explicado na seção <>.] - -Corrotina clássica:: - Uma função geradora que consome dados enviados a ela via chamadas a `my_coro.send(data)`, e que lê aqueles dados usando `yield` em uma expressão. - 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:: - 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`. - -Nesse capítulo vamos nos concentrar nas corrotinas nativas, bem como nas _geradoras assíncronas_: - -Geradora assíncrona:: - Uma((("asynchronous generators"))) função geradora definida com `async def` que usa `yield` em seu corpo. - Ela devolve um objeto gerador assíncrono que oferece um `+__anext__+`, um método corrotina para obter o próximo item. - -.@asyncio.coroutine Não Tem Futurofootnote:[Desculpem, não consegui resistir.] -[WARNING] -==== -O((("@asyncio.coroutine decorator"))) decorador `@asyncio.coroutine` para corrotinas clássicas e corrotinas baseadas em gerador foi descontinuado no Python 3.8, e está previsto para ser removido no Python 3.11, de acordo com o https://fpy.li/21-2[Issue 43216]. -Por outro lado, `@types.coroutine` deve continuar existindo, como se vê aqui: -https://fpy.li/21-3[Issue 36921]. -Esse decorador não é mais suportado pelo _asyncio_, mas é usado em código interno nas frameworks assíncronas _Curio_ e _Trio_. -==== - - -=== Um exemplo de asyncio: sondando domínios - -Imagine((("asyncio package", "example script", id="APexample21")))((("asynchronous programming", "asyncio script example", id="APRscript21"))) -que você esteja prestes a lançar um novo blog sobre Python, -e planeje registrar um domínio usando uma palavra-chave do Python e o sufixo _.DEV_—por exemplo, _AWAIT.DEV._ -O <> é um script usando _asyncio_ que verifica vários domínios de forma concorrente. -Essa é saída produzida pelo script: - -[source, text] ----- -$ python3 blogdom.py - with.dev -+ elif.dev -+ def.dev - from.dev - else.dev - or.dev - if.dev - del.dev -+ as.dev - none.dev - pass.dev - true.dev -+ in.dev -+ for.dev -+ is.dev -+ and.dev -+ try.dev -+ not.dev ----- - -Observe que os domínios aparecem fora de ordem. -Se você rodar o script, os verá sendo exibidos um após o outro, a intervalos variados. -O sinal de `+` indica que sua máquina foi capaz de resolver o domínio via DNS. -Caso contrário, o domínio não foi resolvido e pode estar disponível.footnote:[`true.dev` está disponível por US$ 360,00 ao ano no momento em que escrevi isso. Também notei que `for.dev` está registrado, mas seu DNS não está configurado.] - -No _blogdom.py_, a sondagem de DNS é feita por objetos corrotinas nativas. -Como as operações assíncronas são intercaladas, o tempo necessário para verificar 18 domínios é bem menor que se eles fosse verificados sequencialmente. -Na verdade, o tempo total é quase o igual ao da resposta mais lenta, em vez da soma dos tempos de todas as respostas do DNS. - -O <> mostra o código dp _blogdom.py_. - -[[blogdom_ex]] -.blogdom.py: procura domínios para um blog sobre Python -==== -[source, python3] ----- -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. -<5> `main` tem que ser uma corrotina, para podemros 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__':`. - -[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`. -==== - -==== O truque de Guido para ler código assíncrono - -Há muitos conceitos novos para entender no _asyncio_, mas a lógica básica do <> é fácil de compreender se você usar o truque sugerido pelo próprio Guido van Rossum: -cerre os olhos e finja que as palavras-chave `async` e `await` não estão ali. -Fazendo isso, você vai perceber que as corrotinas podem ser lidas como as boas e velhas funções sequenciais. - -Por exemplo, imagine que o corpo dessa corrotina... - -[source, python3] ----- -async def probe(domain: str) -> tuple[str, bool]: - loop = asyncio.get_running_loop() - try: - await loop.getaddrinfo(domain, None) - except socket.gaierror: - return (domain, False) - return (domain, True) ----- - -...funciona como a função abaixo, exceto que, magicamente, ela nunca bloqueia a execução: - -[source, python3] ----- -def probe(domain: str) -> tuple[str, bool]: # no async - loop = asyncio.get_running_loop() - try: - loop.getaddrinfo(domain, None) # no await - except socket.gaierror: - return (domain, False) - return (domain, True) ----- - -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')`. - -Quando o loop 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. -Mas eles podem lidar com qualquer objeto "esperável". Esse conceito será explicado a seguir.((("", startref="APexample21")))((("", startref="APRscript21"))) - -=== Novo conceito: awaitable ou esperável - -A((("asynchronous programming", "awaitables")))((("await keyword")))((("keywords", "await keyword"))) palavra-chave `for` funciona com _iteráveis_. -A palavra-chave `await` funciona com _esperáveis_ (_awaitable_). - -Como um usuário final do _asyncio_, esses são os esperáveis que você verá diariamente: - -* Um _objeto corrotina nativa_, que você obtém chamando uma _função corrotina nativa_ -* Uma `asyncio.Task`, que você normalmente obtém passando um objeto corrotina para `asyncio.create_task()` - -Entretanto, o código do usuário final nem sempre precisa `await` por uma `Task`. -Usamos `asyncio.create_task(one_coro())` para agendar `one_coro` para execução concorrente, sem esperar que retorne. -Foi o que fizemos com a corrotina `spinner` em _spinner_async.py_ (no <>). -Criar a tarefa é o suficiente para agendar a execução da corrotina. - -[WARNING] -==== -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 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`]. -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] -==== - -Por outro lado, usamos `await other_coro()` para executar `other_coro` agora mesmo -e esperar que ela termine, porque precisamos do resultado para prosseguir. -Em _spinner_async.py_, a corrotina `supervisor` usava `res = await slow()` -para executar `slow` e aguardar seu resultado.. - -Ao implementar bibliotecas assíncronas ou contribuir para o próprio _asyncio_, -você pode também encontrar esse esperáveis de baixo nível: - -* Um objeto com um método `+__await__+` que devolve um iterador; por exemplo, uma instância de `asyncio.Future` (`asyncio.Task` é uma subclasse de `asyncio.Future`) -* Objetos escritos em outras linguagens usando a API Python/C, com uma função `tp_as_async.am_await`, que devolvem um iterador (similar ao método `+__await__+`) - -As bases de código existentes podem também conter um tipo adicional de esperável: _objetos corrotina baseados em geradores_, que estão no processo de serem descontinuados. - -[NOTE] -==== -A PEP 492 https://fpy.li/21-7[afirma] (EN) que a expressão `await` -"usa a [mesma] implementação de `yield from` [mas] com um passo adicional de validação de seu argumento" -e que “`await` só aceita um esperável.” -A PEP não explica aquela implementação em detalhes, mas se refere à https://fpy.li/pep380[PEP 380], que introduziu `yield from`. -Eu postei uma explicação detalhada no texto https://fpy.li/21-8["The Meaning of yield from"] (EN) da seção https://fpy.li/oldcoro["Classic Coroutines"] (EN) -do pass:[fluentpython.com]. -==== - -Agora vamos estudar a versão _asyncio_ de um script que baixa um conjunto fixo de imagens de bandeiras. - -[[flags_asyncio_sec]] -=== Downloads com asyncio e HTTPX - -O((("asyncio package", "downloading with", id="APdown21")))((("network I/O", "downloading with asyncio", id="NIOdownload21")))((("HTTPX library", id="httpx21"))) script _flags_asyncio.py_ baixa um conjunto fixo de 20 bandeiras de _fluentpython.com_. -Nós já o mencionamos na <>, -mas agora vamos examiná-lo em detalhes, aplicando os conceitos que acabamos de ver. - -A partir do Python 3.10, o _asyncio_ só suporta TCP e UDP diretamente, -e não há pacotes de cliente ou servidor HTTP assíncronos na bilbioteca padrão. -Estou usando o https://fpy.li/httpx[_HTTPX_] em todos os exemplos de cliente HTTP. - -Vamos explorar o _flags_asyncio.py_ de baixo para cima, isto é, olhando primeiro as função que configuram a ação no <>. - -[WARNING] -==== -Para deixar o código mais fácil de ler, _flags_asyncio.py_ não tem qualquer tratamento de erro. -Nessa introdução a `async/await` é útil se concentrar inicialmente no "caminho feliz", para entender como funções regulares e corrotinas são dispostas em um programa. -Começando na seção <>, os exemplos incluem tratamento de erros e outros recursos. - -Os exemplos de pass:[flags_.py] aqui e no capítulo <> compartilham código e dados, então os coloquei juntos no diretório -https://fpy.li/21-9[_example-code-2e/20-executors/getflags_]. -==== - - -[[flags_asyncio_start_ex]] -.flags_asyncio.py: funções de inicialização -==== -[source, py] ----- -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. -<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 seção <>). -<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. - -[[flags_asyncio_ex]] -.flags_asyncio.py: imports and download functions -==== -[source, py] ----- -include::code/20-executors/getflags/flags_asyncio.py[tags=FLAGS_ASYNCIO_TOP] ----- -==== -<1> `httpx` precisa ser importado—não faz parte da biblioteca padrão -<2> Reutiliza código de _flags.py_ (<>). -<3> `download_one` tem que 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`. - -[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. -Entretanto, atualmente _asyncio_ não oferece uma API assíncrona de acesso ao sistema de arquivos—como faz o Node.js. - -A seção <> vai mostrar como delegar `save_flag` para uma thread. -==== - -O seu código delega para as corrotinas do `httpx` explicitamente, usando `await`, ou implicitamente, usando os métodos especiais dos gerenciadores de contexto assíncronos, tais como pass:[Async​Client] e `ClientResponse`—como veremos na seção <>. - - -==== O segredo das corrotinas nativas: humildes geradores - -A((("native coroutines", "humble generators and")))((("humble generators")))((("generators", "humble generators"))) diferença fundamental entre os exemplos de corrotinas clássicas vistas nas seção <> e _flags_asyncio.py_ é que não há chamadas a `.send()` ou expressões `yield` visíveis nesse último. -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. -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. -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. -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. - -==== 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 seção <>. - -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."" - -Pela mesma razão, também não pude reutilizar a função `download_one` de _flags_threadpool.py_ -(<>). -O código no <> aciona `get_flag` com `await`, -então `download_one` precisa também ser uma corrotina. -Para cada requisição, um objeto corrotina `download_one` é criado em `supervisor`, e eles são todos acionados pela corrotina `asyncio.gather`. - -Vamos agora estudar o comando `async with`, que apareceu em `supervisor` (<>) e `get_flag` (<>).((("", startref="APdown21")))((("", startref="NIOdownload21")))((("", startref="httpx21"))) - - -[[async_context_manager_sec]] -=== Gerenciadores de contexto assíncronos - -Na((("context managers", "asynchronous", id="CMasync21")))((("asynchronous programming", "asynchronous context managers", id="APRaconman21"))) seção <>, vimos como um objeto pode ser usado para executar código antes e depois do corpo de um bloco `with`, se sua classe oferecer os métodos `+__enter__+` e `+__exit__+`. - -Agora, considere o <>, que usa o driver PostgreSQL https://fpy.li/21-10[_asyncpg_] compatível com o _asyncio_ (https://fpy.li/21-11[documentação do _asyncpg_ sobre transações]). - -[[asyncpg_transaction_no_context_ex]] -.Código exemplo da documentação do driver PostgreSQL _asyncpg_ -==== -[source, python3] ----- -tr = connection.transaction() -await tr.start() -try: - await connection.execute("INSERT INTO mytable VALUES (1, 2, 3)") -except: - await tr.rollback() - raise -else: - await tr.commit() ----- -==== - -Uma transação de banco de dados se presta naturalmente a protocolo do gerenciador de contexto: -a transação precisa ser iniciada, dados são modificados com `connection.execute`, e então um _roolback_ (reversão) ou um _commit_ (confirmação) precisam acontecer, dependendo do resultado das mudanças. - -Em((("asyncpg"))) um driver assíncrono como o _asyncpg_, a configuração e a execução precisam acontecer em corrotinas, para que outras operações possam ocorrer de forma concorrente. -Entretando, a implementação do comando `with` clássico não suporta corrotinas na implementação dos métodos `+__enter__+` ou `+__exit__+`. - -Por essa razão a https://fpy.li/pep492[PEP 492—Coroutines with async and await syntax (_Corrotinas com async e await_)] (EN) introduziu o comando `async with`, que funciona com gerenciadores de contexto assíncronos: -objetos implementando os métodos `+__aenter__+` e `+__aexit__+` como corrotinas. - -Com `async with`, o <> pode ser escrito como esse outro trecho da https://fpy.li/21-11[documentação do _asyncpg_]: - -[source, python3] ----- -async with connection.transaction(): - await connection.execute("INSERT INTO mytable VALUES (1, 2, 3)") ----- - -Na -https://fpy.li/21-13[classe `asyncpg.Transaction`], -o método corrotina `+__aenter__+` executa `await self.start()`, e -a corrotina `+__aexit__+` espera pelos métodos corrotina privados `__rollback` ou `__commit`, -dependendo da ocorrência ou não de uma exceção. -Usar corrotinas para implementar `Transaction` como um gerenciador de contexto assíncrono permite ao _asyncpg_ controlar, de forma concorrente, muitas transações simultâneas. - -.Caleb Hattingh sobre o asyncpg -[TIP] -==== -Outro detalhe fantástico sobre o _asyncpg_ -é que ele também contorna a falta de suporte à alta-concorrência do PostgreSQL -(que usa um processo servidor por conexão) -implementando um pool de conexões para conexões internas ao próprio Postgres. - -Isso significa que você não precisa de ferramentas adicionais (por exemplo o _pgbouncer_), -como explicado na https://fpy.li/21-14[documentação] (EN) do _asyncpg_.footnote:[Essa dica é uma citação literal de um comentário do revisor técnico Caleb Hattingh. Obrigado, Caleb!] -==== - -Voltando ao _flags_asyncio.py_, a classe `AsyncClient` do `httpx` é um gerenciador de contexto assíncrono, então pode usar esperáveis em seus métodos corrotina especiais `+__aenter__+` e `+__aexit__+`. - - -[NOTE] -==== -A seção <> mostra como usar a `contextlib` do Python para criar um gerenciador de contexto assíncrono sem precisar escrever uma classe. Essa explicação aparece mais tarde nesse capítulo por causa de um pré-requsito: a seção <>. -==== - -Agora vamos melhorar o exemplo _asyncio_ de download de bandeiras com uma barra de progresso, que nos levará a explorar um pouco mais da API do _asyncio_.((("", startref="APRaconman21")))((("", startref="CMasync21"))) - - -[[flags2_asyncio_sec]] -=== Melhorando o download de bandeiras asyncio - -Vamos((("asynchronous programming", "enhancing asyncio downloader", id="APRenhanc21")))((("asyncio package", "enhancing asyncio downloader", id="APenhanc21")))((("network I/O", "enhancing asyncio downloader", id="NIOenhance21"))) recordar a seção <>, na qual o conjunto de exemplos `flags2` compartilhava a mesma interface de linha de comando, e todos mostravam uma barra de progresso enquanto os downloads aconteciam. Eles também incluíam tratamento de erros. - -[TIP] -==== -Encorajo você a brincar com os exemplos `flags2`, para desenvolver uma intuição sobre o funcionamento de clientes HTTP concorrentes. -Use a opção `-h` para ver a tela de ajuda no <>. -Use as opções de linha de comando `-a`, `-e`, e `-l` para controlar o número de downloads, -e a opção `-m` para estabelecer o número de downloads concorrentes. -Execute testes com os servidores `LOCAL`, `REMOTE`, `DELAY`, e `ERROR`. -Descubra o número ótimo de downloads concorrentes para maximizar a taxa de transferência de cada servidor. -Varie as opções dos servidores de teste, como descrito no <>. -==== - -Por exemplo, o <> mostra uma tentativa de obter 100 bandeiras (`-al 100`) do servidor `ERROR`, -usando 100 conexões concorrentes (`-m 100`). -Os 48 erros no resultado são ou HTTP 418 ou erros de tempo de espera excedido (_time-out_)—o [mau]comportamento esperado do _slow_server.py_. - -[[flags2_asyncio_run_repeat]] -.Running flags2_asyncio.py -==== -[source] ----- -$ python3 flags2_asyncio.py -s ERROR -al 100 -m 100 -ERROR site: http://localhost:8002/flags -Searching for 100 flags: from AD to LK -100 concurrent connections will be used. -100%|█████████████████████████████████████████| 100/100 [00:03<00:00, 30.48it/s] --------------------- - 52 flags downloaded. - 48 errors. -Elapsed time: 3.31s ----- -==== - -[role="man-height-1-125"] -[WARNING] -.Aja de forma responsável ao testar clientes concorrentes -==== -Mesmo que o tempo total de download não seja muito diferente entre os clientes HTTP na versão com threads e na versão _asyncio_ HTTP , o -_asyncio_ é capaz de enviar requisições mais rápido, então aumenta a probabilidade do servidor suspeitar de um ataque DoS. -Para exercitar esses clientes concorrentes em sua capacidade máxima, por favor use servidores HTTP locais em seus testes, como explicado no <>. -==== - -Agora vejamos como o _flags2_asyncio.py_ é implementado. - -[[using_as_completed_sec]] -==== Usando asyncio.as_completed e uma thread - -No((("threads", "enhancing asyncio downloader", id="Tenhance21"))) <>, passamos várias corrotinas para `asyncio.gather`, que devolve uma lista com os resultados das corrotinas na ordem em que foram submetidas. -Isso significa que `asyncio.gather` só pode retornar quando todos os esperáveis terminarem. -Entretanto, para atualizar uma barra de progresso, precisamos receber cada um dos resultados assim que eles estejam prontos. - -Felizmente existe um equivalente `asyncio` da função geradora `as_completed` -que usamos no exemplo de pool de threads com a barra de progresso, (<>). - -O <> mostra o início do script _flags2_asyncio.py_, onde as corrotinas `get_flag` e `download_one` são definidas. O <> lista o restante do código-fonte, com `supervisor` e `download_many`. -O script é maior que _flags_asyncio.py_ por causa do tratamento de erros. - -[[flags2_asyncio_top]] -.flags2_asyncio.py: parte superior (inicial) do script; o resto do código está no <> -==== -[source, py] ----- -include::code/20-executors/getflags/flags2_asyncio.py[tags=FLAGS2_ASYNCIO_TOP] ----- -==== -<1> `get_flag` é muito similar à versão sequencial no <>. -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. - -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. - -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, -a seção <> mostra como fazer isso, adicionando algumas linhas ao seu programa. -Mas primeiro, vamos terminar nosso estudo do código do cliente HTTP. - -==== Limitando as requisições com um semáforo - -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_] -é 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. -O <> tem mais informações. - -No _flags2_threadpool.py_ (<>), -a limitação era obtida instanciando o `ThreadPoolExecutor` com o argumento obrigatório `max_workers` fixado em `concur_req` na função `download_many`. -Em _flags2_asyncio.py_, um `asyncio.Semaphore` é criado pela função `supervisor` -(mostrada no <>) -e passado como o argumento `semaphore` para `download_one` no <>. - -[[about_semaphores_box]] -.Semáforos no Python -**** -O cientista da computação Edsger W. Dijkstra inventou o https://pt.wikipedia.org/wiki/Sem%C3%A1foro_(computa%C3%A7%C3%A3o)[semáforo] no início dos anos 1960. -É uma ideia simples, mas tão flexível que a maioria dos outros objetos de sincronização—tais como as travas e as barreiras—podem ser construídas a partir de semáforos. -Há três classes `Semaphore` na biblioteca padrão do Python: -uma em `threading`, outra em `multiprocessing`, e uma terceira em `asyncio`. -Essas classes são parecidas, mas têm implementações bem diferentes. -Aqui vamos descrever a versão de `asyncio`. - -Um `asyncio.Semaphore` tem um contador interno que é decrementado toda vez que -usamos `await` no método corrotina `.acquire()`, -e incrementado quando chamamos o método `.release()`—que não é uma corrotina porque nunca bloqueia. O valor inicial do contador é definido quando o `Semaphore` é instanciado: - -[source, python3] ----- - semaphore = asyncio.Semaphore(concur_req) ----- - -Invocar `await` em `.acquire()` não causa qualquer atraso quando o contador interno -é maior que zero. -Se o contador for 0, entretanto, -`.acquire()` suspende a a corrotina que chamou `await` até que alguma outra corrotina chame -`.release()` no mesmo `Semaphore`, incrementando assim o contador. - -Em vez de usar esses métodos diretamente, -é mais seguro usar o `semaphore` como um gerenciador de contexto assíncrono, -como fiz na função `download_one` em <>: - -[source, python3] ----- - async with semaphore: - image = await get_flag(client, base_url, cc) ----- - -O método corrotina `+Semaphore.__aenter__+` espera por `.acquire()` -(usando `await` internamente), -e seu método corrotina `+__aexit__+` chama `.release()`. -Este `async with` garante que não mais que `concur_req` -instâncias de corrotinas `get_flags` estarão ativas a qualquer dado momento. - -Cada uma das classes `Semaphore` na biblioteca padrão tem uma subclasse `BoundedSemaphore`, que impõe uma restrição adicional: o contador interno não pode nunca ficar maior que o valor inicial, quando ocorrerem mais operações `.release()` que `.acquire()`.footnote:[Agradeço a Guto Maia, que notou que conceito de um semáforo não era explicado, quando leu o primeiro rascunho deste capítulo.] - -**** - -Agora vamos olhar o resto do script em <>. - -[[flags2_asyncio_rest]] -.flags2_asyncio.py: continuação de <> -==== -[source, py] ----- -include::code/20-executors/getflags/flags2_asyncio.py[tags=FLAGS2_ASYNCIO_START] ----- -==== -<1> `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. -<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 <>. -<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. - -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`. O 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. -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. -==== - -Isso encerra nossa discussão da funcionalidade de um exemplo usando _asyncio_ similar ao _flags2_threadpool.py_ que vimos antes. - -O próximo exemplo demonstra um modelo simples de execução de uma tarefa assíncrona após outra usando corrotinas. -Isso merece nossa atenção porque qualquer um com experiência prévia em Javascript sabe que rodar um função assíncrona após outra foi a razão para o padrão de codificação aninhado conhecido como -https://fpy.li/21-20[_pyramid of doom_ (_pirâmide da perdição_)] (EN). -A palavra-chave `await` desfaz a maldição. -Por isso `await` agora é parte do Python e do Javascript.((("", startref="throttle21")))((("", startref="semaphores21"))) - -==== Fazendo múltiplas requisições para cada download - -Suponha que você queira salvar cada bandeira com o nome e o código do país, em vez de apenas o código. Agora você precisa fazer duas requisições HTTP por bandeira: uma para obter a imagem da bandeira propriamente dita, a outra para obter o arquivo _metadata.json_, no mesmo diretório da imagem—é nesse arquivo que o nome do país está registrado. - -Coordenar múltiplas requisições na mesma tarefa é fácil no script com threads: basta fazer uma requisição depois a outra, bloqueando a thread duas vezes, e mantendo os dois dados (código e nome do país) em variáveis locais, prontas para serem usadas quando os arquivos forem salvo. -Se você precisasse fazer o mesmo em um script assíncrono com callbacks, você precisaria de funções aninhadas, de forma que o código e o nome do país estivessem disponíveis até o momento em que fosse possível salvar o arquivo, pois cada callback roda em um escopo local diferente. -A palavra-chave `await` fornece um saída para esse problema, permitindo que você acione as requisições assíncronas uma após a outra, compartilhando o escopo local da corrotina que dirige as ações. - - -[TIP] -==== -Se você está trabalhando com programação de aplicações assíncronas no Python moderno e recorre a uma grande quantidade de callbacks, provavelmente está aplicando modelos antigos, que não fazem mais sentido no Python atual. -Isso é justificável se você estiver escrevendo uma biblioteca que se conecta a código legado ou a código de baixo nível, que não suportem corrotinas. De qualquer forma, o Q&A do StackOverflow, -https://fpy.li/21-21["What is the use case for future.add_done_callback()?" (_Qual o caso de uso para future.add_done_callback()?_)] (EN) explica porque callbacks são necessários em código de baixo nível, mas não são muito úteis hoje em dia em código Python a nível de aplicação. -==== - -A terceira variante do script `asyncio` de download de bandeiras traz algumas mudanças: - -`get_country`:: Essa nova corrotina baixa o arquivo _metadata.json_ daquele código de país, e extrai dele o nome do país. -`download_one`:: Essa corrotina agora usa `await` para delegar para `get_flag` e para a nova corrotina `get_country`, usando o resultado dessa última para compor o nome do arquivo a ser salvo. - -Vamos começar com o código de `get_country` (<>). -Observe que ele muito similar ao `get_flag` do <>. - -[[flags3_asyncio_get_country]] -.flags3_asyncio.py: corrotina `get_country` -==== -[source, py] ----- -include::code/20-executors/getflags/flags3_asyncio.py[tags=FLAGS3_ASYNCIO_GET_COUNTRY] ----- -==== -<1> Essa corrotina devolve uma string com o nome do país—se tudo correr bem. -<2> `metadata` vai receber um `dict` Python construído a partir do conteúdo JSON da resposta. -<3> Devolve o nome do país. - -Agora vamos ver o `download_one` modificado do <>, que tem apenas algumas linhas diferentes da corrotina de mesmo nome do <>. - -[[flags3_asyncio]] -.flags3_asyncio.py: corrotina `download_one` -==== -[source, py] ----- -include::code/20-executors/getflags/flags3_asyncio.py[tags=FLAGS3_ASYNCIO_DOWNLOAD_ONE] ----- -==== -<1> Segura o `semaphore` para `await` por `get_flag`... -<2> ...e novamente por `get_country`. -<3> Usa o nome do país para criar um nome de arquivo. Como usuário da linha de comando, não gosto de ver espaços em nomes de arquivo. - -Muito melhor que callbacks aninhados! - -Coloquei as chamadas a `get_flag` e `get_country` em blocos `with` separados, controlados pelo `semaphore` porque é uma boa prática manter semáforos e travas pelo menor tempo possível. - -Eu poderia ter agendado ambos os scripts, `get_flag` e `get_country`, em paralelo, usando `asyncio.gather`, mas se `get_flag` levantar uma exceção não haverá imagem para salvar, então seria inútil rodar `get_country`. Mas há casos onde faz sentido usar `asyncio.gather` para acessar várias APIs simultaneamente, em vez de esperar por uma resposta antes de fazer a próxima requisição - -Em _flags3_asyncio.py_, a sintaxe `await` aparece seis vezes, e `async with` três vezes. -Espero que você esteja pegando o jeito da programação assíncrona em Python. -Um desafio é saber quando você precisa usar `await` e quando você não pode usá-la. -A resposta, em princípio, é fácil: você `await` por corrotinas e outros esperáveis, tais como instâncias de `asyncio.Task`. -Mas algumas APIs são complexas, misturam corrotinas e funções normais de maneiras aparentemente arbitrárias, como a classe `StreamWriter` que usaremos no <>. - -O <> encerra o grupo de exemplos _flags_. -Vamos agora discutir o uso de executores de threads ou processos na programação assíncrona.((("", startref="APRenhanc21")))((("", startref="APenhanc21")))((("", startref="Tenhance21")))((("", startref="NIOenhance21"))) - -[[delegating_to_executors_sec]] -=== 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 o 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 corrotina `download_one` de <>, usei a seguinte linha para salvar a imagem baixada para o disco: - -[source, py] ----- - await asyncio.to_thread(save_flag, image, f'{cc}.gif') ----- - -Como mencionado antes, o `asyncio.to_thread` foi acrescentado no Python 3.9. -Se você precisa suportar 3.7 ou 3.8, -substitua aquela linha pelas linhas em <>. - -[[flags2_asyncio_executor_fragment]] -.Linhas para usar no lugar de `await asyncio.to_thread` -==== -[source, py] ----- -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`. -<3> Você pode passar argumentos posicionais para a função a ser executada, mas se você precisar passar argumentos de palavra-chave, 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`]. - -A função mais recente `asyncio.to_thread` é mais fácil de usar e mais flexível, já que também aceita argumentos de palavra-chave. - -A própria implementação de `asyncio` usa `run_in_executor` debaixo dos panos em alguns pontos. -Por exemplo, a corrotina `loop.getaddrinfo(…)`, que vimos no <> é implementada chamando a função `getaddrinfo` do módulo `socket`—uma função bloqueante que pode levar alguns segundos para retornar, pois depende de resolução de DNS. - -Um padrão comum em APIs assíncronas é encobrir chamadas bloqueantes que sejam detalhes de implementação nas corrotinas usando `run_in_executor` internamente. -Dessa forma, é possível apresentar uma interface consistente de corrotinas a serem acionadas com `await` e esconder as threads que precisam ser usadas por razões pragmáticas. -O driver assíncrono para o MongoDB https://fpy.li/21-23[Motor] tem uma API compatível com `async/await` que na verdade é uma fachada, encobrindo um núcleo de threads que conversa com o servidor de banco de dados. -A. Jesse Jiryu Davis, o principal desenvolvedor do Motor, explica suas razões em -https://fpy.li/21-24[“Response to ‘Asynchronous Python and Databases’” (“_Resposta a ‘O Python Assíncrono e os Bancos de Dados’”)]. -Spoiler: Davis descobriu que um pool de threads tem melhor desempenho no caso de uso específico de um driver de banco de dados—apesar do mito que abordagens assíncronas são sempre mais rápidas que threads para E/S de rede. - -A principal razão para passar um `Executor` explícito para `loop.run_in_executor` é utilizar um `ProcessPoolExecutor`, se a função a ser executada for de uso intensivo da CPU. Dessa forma ela rodará em um processo Python diferente, evitando a disputa pela GIL. Por seu alto custo de inicialização, seria melhor iniciar o `ProcessPoolExecutor` no `supervisor`, e passá-lo para as corrotinas que precisem utilizá-lo. - -Caleb Hattingh—O autor de -pass:[Using Asyncio in Python] -(O' Reilly)—é um dos revisores técnicos desse livro, e sugeriu que eu acrescentasse o seguinte aviso sobre executores e o _asyncio_. - -.O aviso de Caleb sobre run_in_executors -[WARNING] -==== -Usar `run_in_executor` pode produzir problemas difíceis de depurar, já que o cancelamento não funciona da forma que se esperaria. -Corrotinas que usam executores apenas fingem terminar: a thread subjacente (se for um `ThreadPoolExecutor`) não tem um mecanismo de cancelamento. -Por exemplo, uma thread de longa duração criada dentro de uma chamada a `run_in_executor` pode impedir que seu programa _asyncio_ encerre de forma limpa: -`asyncio.run` vai esperar para retornar até o executor terminar inteiramente, e vai esperar para sempre se os serviços iniciados pelo executor não pararem sozinhos de alguma forma. -Minha barba branca me inclina a desejar que aquela função se chamasse `run_in_executor_uncancellable`. -==== - -Agora saímos de scripts cliente para escrever servidores com o `asyncio`.((("", startref="exedel21")))((("", startref="APRdelegat21"))) - - -=== 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`. - -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 seção <>. -A <> mostra uma sessão com o _web_mojifinder.py_, o primeiro servidor que escreveremos. - -[[web_mojifinder_result]] -.Janela de navegador mostrando os resultados da busca por "mountain" no serviço web_mojifinder.py. -image::images/flpy_2102.png[Captura de tela de conexão do Firefox com o web_mojifinder.py] - -A lógica de busca no Unicode nesses exemplos é a classe `InvertedIndex` no módulo _charindex.py_ no https://fpy.li/code[repositório de código do _Python Fluente_]. Não há nada concorrente naquele pequeno módulo, então vou dar apenas um explicação breve sobre ele, no box opcional a seguir. Você pode pular para a implementação do servidor HTTP na seção <>. - -.Conhecendo o índice invertido -**** - -Um((("inverted indexes"))) índice invertido normalmente mapeia palavras a -documentos onde elas ocorrem. -Nos exemplos _mojifinder_, cada "documento" é o nome de um caractere Unicode. -A classe `charindex.InvertedIndex` indexa cada palavra que aparece no nome de -cada caractere no banco de dados Unicode, e cria um índice invertido em um `defaultdict`. -Por exemplo, para indexar o caractere U+0037—DIGIT SEVEN—o construtor -de `InvertedIndex` anexa o caractere `'7'` aos registros sob as chaves `'DIGIT'` e `'SEVEN'`. -Após indexar os dados do Unicode 13.0.0 incluídos no Python 3.10, `'DIGIT'` será mapeado para -868 caracteres que tem essa palavra em seus nomes; -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.] - -[[inverted_index_fig]] -.Explorando o atributo `entries` e o método `search` de `InvertedIndex` no console do Python -image::images/flpy_2103.png[Captura de tela do console do Python] - -O método `InvertedIndex.search` quebra a consulta em palavras separadas, e devolve a intersecção dos registros para cada palavra. -É 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. -**** - -[[fastapi_web_service_sec]] -==== Um serviço web com FastAPI - -Escrevi((("FastAPI framework", id="fastapi21"))) o próximo exemplo—_web_mojifinder.py_—usando a https://fpy.li/21-28[_FastAPI_]: -uma das frameworks ASGI de desenvolvimento Web do Python, mencionada na <>. -A <> é uma captura de tela da interface de usuário. -É uma aplicação muito simples, de uma página só (SPA, Single Page Application): após o download inicial do HTML, a interface é atualizada via Javascript no cliente, em comunicação com o servidor. - -A _FastAPI_ foi projetada para implementar o lado servidor de SPAs and apps móveis, -que consistem principalmente de pontos de acesso de APIs web, devolvendo respostas JSON em vez de HTML renderizado no servidor. A _FastAPI_ se vale de decoradores, dicas de tipo e introspecção de código para eliminar muito do código repetitivo das APIs web, e também publica automaticamente uma documentação no padrão OpenAPI—a.k.a. https://fpy.li/21-29[Swagger]—para a API que criamos. -A <> mostra a página `/docs` para o _web_mojifinder.py_, gerada automaticamente. - -[[web_mojifinder_schema]] -.Schema OpenAPI gerado automaticamente para o ponto de acesso `/search`. -image::images/flpy_2104.png[Captura de tela do Firefox mostrando o schema OpenAPI para o ponto de acesso `/search`] - -O <> é o código do _web_mojifinder.py_, mas aquele é apenas o código do lado servidor. Quando você acessa a URL raiz `/`, o servidor envia o arquivo _form.html_, que contém 81 linhas de código, incluindo 54 linhas de Javascript para comunicação com o servidor e preenchimento de uma tabela com os resultados. Se você estiver interessado em ler Javascript puro sem uso de frameworks, vá olhar o _21-async/mojifinder/static/form.html_ no -https://fpy.li/code[repositório de código do _Python Fluente_]. - -Para rodar o _web_mojifinder.py_, você precisa instalar dois pacotes e suas dependências: _FastAPI_ e _uvicorn_.footnote:[Você pode usar outro servidor ASGI no lugar do _uvicorn_, tais como o _hypercorn_ ou o _Daphne_. Veja na documentação oficial do ASGI a https://fpy.li/21-30[página sobre implementações] (EN) para maiores informações.] - -Este é o comando para executar o <> com _uvicorn_ em modo de desenvolvimento: - -[source, shell] ----- -$ uvicorn web_mojifinder:app --reload ----- - -os parâmetros são: - -`web_mojifinder:app`:: - O nome do pacote, dois pontos, e o nome da aplicação ASGI definida nele—`app` é o nome usado por convenção. - -`--reload`:: - Faz o _uvicorn_ monitorar mudanças no código-fonte da aplicação, e recarregá-la automaticamente. Útil apenas durante o desenvolvimento. - -Vamos agora olhar o código-fonte do _web_mojifinder.py_. - - -[[web_mojifinder_ex]] -.web_mojifinder.py: código-fonte completo -==== -[source, py] ----- -include::code/21-async/mojifinder/web_mojifinder.py[] ----- -==== -<1> Não relacionado ao tema desse capítulo, mas digno de nota: o uso elegante do operador `/` sobrecarregado por `pathlib`.footnote:[Agradeço o revisor técnico Miroslav Šedivý por apontar bons lugares para usar `pathlib` nos exemplo de código.] -<2> Essa linha define a app ASGI. Ela poderia ser tão simples como `app = FastAPI()`. Os parâmetros mostrados são metadata para a documentação auto-gerada. -<3> Um schema _pydantic_ para uma resposta JSON, com campos `char` e `name`.footnote:[Como mencionado no <>, o https://fpy.li/21-31[_pydantic_] aplica dicas de tipo durante a execução, para validação de dados.] -<4> Cria o `index` e carrega o formulário HTML estático, anexando ambos ao `app.state` para uso posterior. -<5> Roda `init` quando esse módulo é carregado pelo servidor ASGI. -<6> Rota para o ponto de acesso `/search`; `response_model` usa aquele modelo `CharName` do _pydantic_ para descrever o formato da resposta. -<7> A _FastAPI_ assume que qualquer parâmetro que apareça na assinatura da função ou da corrotina e que não esteja no caminho da rota será passado na string de consulta HTTP, isto é, `/search?q=cat`. Como `q` não tem default, a _FastAPI_ devolverá um status 422 (Unprocessable Entity, _Entidade Não-Processável_) se `q` não estiver presente na string da consulta. -<8> Devolver um iterável de `dicts` compatível com o schema `response_model` permite ao _FastAPI_ criar uma resposta JSON de acordo com o `response_model` no decorador `@app.get`, -<9> Funções regulares (isto é, não-assíncronas) também podem ser usadas para produzir respostas. -<10> Este módulo não tem uma função principal. É carregado e acionado pelo servidor ASGI—neste exemplo, o _uvicorn_. - -O <> não tem qualquer chamada direta ao `asyncio`. -O _FastAPI_ é construído sobre o tollkit ASGI _Starlette_, que por sua vez usa o `asyncio`. - -Observe também que o corpo de `search` não usa `await`, `async with`, ou `async for`, -e assim poderia ser uma função normal. -Defini `search` como um corrotina apenas para mostrar que o _FastAPI_ sabe como lidar com elas. -Em uma aplicação real, a maioria dos pontos de acesso serão consultas a bancos de dados ou acessos a outros servidores remotos, então é uma vantagem crítica do _FastAPI_—e de frameworks ASGI em geral— -suportarem corrotinas que podem se valer de bibliotecas assíncronas para E/S de rede. - -[TIP] -==== -As funções `init` e `form`, escritas para carregar e entregar o formulário em HTML estático é uma improvisação que escrevi para manter esse exemplo curto e fácil de executar. -A melhor prática recomendada é ter um proxy/balanceador de carga na frente do ASGI, para gerenciar todos os recursos estáticos, e também usar uma CDN (Content Delivery Network, _Rede de Entrega de Conteúdo_) quando possível. -Um proxy/balanceador de carga desse tipo é o https://fpy.li/21-32[_Traefik_] (EN), que se auto-descreve como um "roteador de ponta (_edge router)", que "recebe requisições em nome de seu sistema e descobre quais componentes são responsáveis por lidar com elas." O _FastAPI_ tem scripts de https://fastapi.tiangolo.com/pt/project-generation/[geração de projeto] que preparam seu código para fazer isso. -==== - -Os entusiastas pela tipagem podem ter notado que não há dicas de tipo para os resultados devolvidos por `search` e `form`. -Em vez disto, o _FastAPI_ conta com o argumento de palavra-chave `response_model=` nos decoradores de rota. -A página https://fpy.li/21-34["Modelo de Resposta"] (EN) na documentação do _FastAPI_ explica: - -[quote] -____ -O modelo de resposta é declarado neste parâmetro em vez de como uma anotação de tipo de resultado devolvido por uma função, porque a função de rota pode não devolver aquele modelo de resposta mas sim um `dict`, um objeto banco de dados ou algum outro modelo, e então usar o `response_model` para realizar a limitação de campo e a serialização. -____ - -Por exemplo, em `search`, eu devolvi um gerador de itens `dict` e não uma lista de objetos `CharName`, -mas isso é o suficiente para o _FastAPI_ e o _pydantic_ validarem meus dados e construírem a resposta JSON apropriada, compatível com `response_model=list[CharName]`. - -Agora vamos nos concentrar no script _tcp_mojifinder.py_, que responde às consultas, na <>.((("", startref="fastapi21"))) - - -==== Um servidor TCP asyncio - -O((("TCP servers", id="tcp21")))((("servers", "TCP servers", id="Stcp22"))) programa _tcp_mojifinder.py_ usa TCP puro para se comunicar com um cliente como o Telnet ou o Netcat, então pude escrevê-lo usando `asyncio` sem dependências externas—e sem reinventar o HTTP. O <> mostra a interface de texto do usuário. - - -[[tcp_mojifinder_demo]] -.Sessão de telnet com o servidor tcp_mojifinder.py: consultando "fire." -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 <>. -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]] -.tcp_mojifinder.py: um servidor TCP simples; continua em <> -==== -[source, py] ----- -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`]. -<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 `server_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." -<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`. -<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 <>. - -[[tcp_mojifinder_server_demo]] -.tcp_mojifinder.py: isso é o lado servidor da sessão mostrada na <> -==== -[source, text] ----- -$ python3 tcp_mojifinder.py -Building index. # <1> -Serving on ('127.0.0.1', 2323). Hit Ctrl-C to stop. # <2> - From ('127.0.0.1', 58192): 'cat face' # <3> - To ('127.0.0.1', 58192): 10 results. - From ('127.0.0.1', 58192): 'fire' # <4> - To ('127.0.0.1', 58192): 11 results. - From ('127.0.0.1', 58192): '\x00' # <5> -Close ('127.0.0.1', 58192). # <6> -^C # <7> -Server shut down. # <8> -$ ----- -==== -<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. -<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. -<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. - -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. - -Agora vamos ver o início de _tcp_mojifinder.py_, com a corrotina `finder`. - -[[tcp_mojifinder_top]] -.tcp_mojifinder.py: continuação de <> -==== -[source, py] ----- -include::code/21-async/mojifinder/tcp_mojifinder.py[tags=TCP_MOJIFINDER_TOP] ----- -==== -[role="pagebreak-before less_space"] -<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. -<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`. -<8> Se nenhum byte foi recebido, o cliente fechou a conexão, então sai do loop. -<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. -<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()`]. -<17> Registra o final dessa sessão do cliente no console do servidor. - -O último pedaço desse exemplo é a corrotina `search`, listada no <>. - -[[tcp_mojifinder_search]] -.tcp_mojifinder.py: corrotina `search` -==== -[source, py] ----- -include::code/21-async/mojifinder/tcp_mojifinder.py[tags=TCP_MOJIFINDER_SEARCH] ----- -==== -<1> `search` tem que ser uma corrotina, pois escreve em um `StreamWriter` e precisa usar seu método corrotina `.drain()`. -<2> Consulta o índice invertido. -<3> Essa expressão geradora vai produzir strings de bytes codificadas em UTF-8 com o ponto de código Unicode, o caractere efetivo, seu nome e uma sequência `CRLF` (_Return+Line Feed_), isto é, `b'U+0039\t9\tDIGIT NINE\r\n'`. -<4> Envia a `lines`. Surpreendentemente, `writer.writelines` não é uma corrotina. -<5> Mas `writer.drain()` é uma corrotina. Não esqueça do `await`! -<6> Cria depois envia uma linha de status. - -Observe que toda a E/S de rede em _tcp_mojifinder.py_ é feita em `bytes`; precisamos decodificar os `bytes` recebidos da rede, e codificar strings antes de enviá-las. No Python 3, a codificação default é UTF-8, e foi o que usei implicitamente em todas as chamadas a `encode` e `decode` nesse exemplo. - -[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]. -==== - -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 da framework _Twisted_. Veja a documentação do `asyncio` para maiores 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. - -Nosso próximo tópico é `async for` e os objetos que a fazem funcionar.((("", startref="tcp21")))((("", startref="APRwrit21")))((("", startref="APwrite21")))((("", startref="Stcp22")))((("", startref="Sasyncio21"))) - - -=== Iteração assíncrona e iteráveis assíncronos - -Na((("asynchronous programming", "iteration and iterables", id="APRiteration21")))((("iterables", "asynchronous", id="ITasync21")))((("iterators", "asynchronous", id="ITERasync21"))) seção <> vimos como `async with` funciona com objetos que implementam os métodos `+__aenter__+` and `+__aexit__+`, devolvendo esperáveis—normalmente na forma de objetos corrotina. - -Se forma similar, `async for` funciona com _iteráveis assíncronos_: objetos que implementam `+__aiter__+`. Entretanto, `+__aiter__+` precisa ser um método regular—não um método corrotina—e precisa devolver um _iterador assíncrono_. - -Um iterador assíncrono fornece um método corrotina `+__anext__+` que devolve um esperável—muitas vezes um objeto corrotina. Também se espera que eles implementem `+__aiter__+`, que normalmente devolve `self`. Isso espelha a importante distinção entre iteráveis e iteradores que discutimos na seção <>. - -A https://fpy.li/21-43[documentação] (EN) do driver assíncrono de PostgreSQL _aiopg_ traz um exemplo que ilustra o uso de `async for` para iterar sobre as linhas de cursor de banco de dados. - -[source, python3] ----- -async def go(): - pool = await aiopg.create_pool(dsn) - async with pool.acquire() as conn: - async with conn.cursor() as cur: - await cur.execute("SELECT 1") - ret = [] - async for row in cur: - ret.append(row) - assert ret == [(1,)] ----- - -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. - - -[[async_gen_func_sec]] -==== Funções geradoras assíncronas - -Você((("generators", "asynchronous generator functions", id="Gasync21"))) pode implementar um iterador assíncrono escrevendo uma classe com `+__anext__+` e `+__aiter__+`, mas há um jeito mais simples: escreve uma função declarada com `async def` que use `yield` em seu corpo. -Isso é paralelo à forma como funções geradoras simplificam o modelo clássico do Iterador. - -Vamos estudar um exemplo simples usando `async for` e implementando um gerador assíncrono. -No <> vimos _blogdom.py_, um script que sondava nomes de domínio. -Suponha agora que encontramos outros usos para a corrotina `probe` definida ali, e decidimos colocá-la em um novo módulo—_domainlib.py_—junto com um novo gerador assíncrono `multi_probe`, que recebe uma lista de nomes de domínio e produz resultados conforme eles são sondados. - -Vamos ver a implementação de _domainlib.py_ logo, mas primeiro examinaremos como ele é usado com o novo console assíncrono do Python. - -[[python_async_console_sec]] -===== Experimentando com o console assíncrono do 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 o 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:: - -[source, shell] ----- -$ python -m asyncio ----- - -Você verá o console iniciar, de forma similar a isso: - -[source, shell] ----- -asyncio REPL 3.9.1 (v3.9.1:1e5d33e9b9, Dec 7 2020, 12:10:52) -[Clang 6.0 (clang-600.0.57)] on darwin -Use "await" directly instead of "asyncio.run()". -Type "help", "copyright", "credits" or "license" for more information. ->>> import asyncio ->>> ----- - -Veja como o cabeçalho diz que você pode usar `await` em vez de `asyncio.run()`—para acionar corrotinas e outros esperáveis. -E mais: eu não digitei `import asyncio`. -O módulo `asyncio` é automaticamente importado e aquela linha torna esse fato claro para o usuário. - -[role="pagebreak-before less_space"] -Vamos agora importar _domainlib.py_ e brincar com suas duas corrotinas: `probe` and `multi_probe` (<>). - -[[domainlib_demo_repl]] -.Experimentando com _domainlib.py_ após executar `python3 -m asyncio` -==== -[source, pycon] ----- ->>> await asyncio.sleep(3, 'Rise and shine!') # <1> -'Rise and shine!' ->>> from domainlib import * ->>> await probe('python.org') # <2> -Result(domain='python.org', found=True) # <3> ->>> names = 'python.org rust-lang.org golang.org no-lang.invalid'.split() # <4> ->>> async for result in multi_probe(names): # <5> -... print(*result, sep='\t') -... -golang.org True # <6> -no-lang.invalid False -python.org True -rust-lang.org True ->>> ----- -==== -<1> Tente um simples `await` para ver o console assíncrono em ação. Dica: `asyncio.sleep()` pode receber um segundo argumento opcional que é devolvido quando você usa `await` com ele. -<2> Acione a corrotina `probe`. -<3> A versão `domainlib` de `probe` devolve uma tupla nomeada `Result`. -<4> Faça um lista de domínios. O domínio de nível superior `.invalid` é reservado para testes. Consultas ao DNS por tais domínios sempre recebem uma resposta NXDOMAIN dos servidores DNS, que quer dizer "aquele domínio não existe."footnote:[Veja https://fpy.li/21-45[RFC 6761—Special-Use Domain Names].] -<5> Itera com `async for` sobre o gerador assíncrono `multi_probe` para mostrar os resultados. -<6> Note que os resultados não estão na ordem em que os domínios foram enviados a `multiprobe`. Eles aparecem quando cada resposta do DNS chega. - -O <> mostra que `multi_probe` é um gerador assíncrono, pois ele é compatível com `async for`. Vamos executar mais alguns experimentos, continuando com o <>. - -[[domainlib_more_exp_repl]] -.mais experimentos, continuando de <> -==== -[source, pycon] ----- ->>> probe('python.org') # <1> - ->>> multi_probe(names) # <2> - ->>> for r in multi_probe(names): # <3> -... print(r) -... -Traceback (most recent call last): - ... -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__+`. - -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. - - -===== Implementando um gerador assíncrono - -Vamos agora estudar o código do _domainlib.py_, com o gerador assíncrono `multi_probe` (<>). - -[[domainlib_ex]] -.domainlib.py: funções para sondar domínios -==== -[source, python3] ----- -include::code/21-async/domains/asyncio/domainlib.py[] ----- -==== -<1> A `NamedTuple` torna o resultado de `probe` mais fácil de ler e depurar. -<2> Este apelido de tipo serve para evitar que a linha seguinte fique grande demais em uma listagem impressa em um livro. -<3> `probe` agora recebe um argumento opcional `loop`, para evitar chamadas repetidas a `get_running_loop` quando essa corrotina é acionada por `multi_probe`. -<4> Uma função geradora assíncrona produz um objeto gerador assíncrono, que pode ser anotado como `AsyncIterator[SomeType]`. -<5> Constrói uma lista de objetos corrotina `probe`, cada um com um `domain` diferente. -<6> Isso não é `async for` porque `asyncio.as_completed` é um gerador clássico. -<7> Espera pelo objeto corrotina para obter o resultado. -<8> Produz `result`. Esta linha faz com que `multi_probe` seja um gerador assíncrono. - -[NOTE] -==== -O loop `for` no <> poderia ser mais conciso: - -[source, python3] ----- - for coro in asyncio.as_completed(coros): - yield await coro ----- - -O Python interpreta isso como `yield (await coro)`, então funciona. - -Achei que poderia ser confuso usar esse atalho no primeiro exemplo de gerador assíncrono no livro, então dividi em duas linhas. -==== - -Dado _domainlib.py_, podemos demonstrar o uso do gerador assíncrono `multi_probe` em _domaincheck.py_: um script que recebe um sufixo de domínio e busca por domínios criados a partir de palavras-chave curtas do Python. - -Aqui está uma amostra da saída de _domaincheck.py_: - -[source, text] ----- -$ ./domaincheck.py net -FOUND NOT FOUND -===== ========= -in.net -del.net -true.net -for.net -is.net - none.net -try.net - from.net -and.net -or.net -else.net -with.net -if.net -as.net - elif.net - pass.net - not.net - def.net ----- - -Graças à _domainlib_, o código de _domaincheck.py_ é bastante direto, como se vê no <>. - -[[domaincheck_ex]] -.domaincheck.py: utilitário para sondar domínios usando domainlib -==== -[source, python3] ----- -include::code/21-async/domains/asyncio/domaincheck.py[] ----- -==== -<1> Gera palavras-chave de tamanho até `4`. -<2> Gera nomes de domínio com o sufixo recebido como TLD (_Top Level Domain_, "Domínio de Topo"). -<3> Formata um cabeçalho para a saída tabular. -<4> Itera de forma assíncrona sobre `multi_probe(domains)`. -<5> Define `indent` como zero ou dois tabs, para colocar o resultado na coluna correta. -<6> Roda a corrotina `main` com o argumento de linha de comando passado. - -Geradores tem um uso adicional, não relacionado à iteração: ele podem ser usados como gerenciadores de contexto. Isso também se aplica aos geradores assíncronos. - -[[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. -Ele é muito similar ao decorador `@contextmanager` que estudamos na seção <>. - -Um exemplo interessante da combinação de `@asynccontextmanager` com `loop.run_in_executor` aparece no livro de Caleb Hattingh, -https://fpy.li/hattingh[_Using Asyncio in Python_]. O <> é o código de Caleb—com uma única mudança e o acréscimo das explicações. - -[[asynccontextmanager_ex]] -.Exemplo usando `@asynccontextmanager` e `loop.run_in_executor` -==== -[source, python3] ----- -from contextlib import asynccontextmanager - -@asynccontextmanager -async def web_page(url): # <1> - loop = asyncio.get_running_loop() # <2> - data = await loop.run_in_executor( # <3> - None, download_webpage, url) - yield data # <4> - await loop.run_in_executor(None, update_stats, url) # <5> - -async with web_page('google.com') as data: # <6> - process(data) ----- -==== -<1> A função decorada tem que 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. -<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`. - -Isso é muito similar ao decorador sequencial `@contextmanager`. -Por favor, consulte a seção <> para maiores 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`]. - -Por fim, vamos terminar nossa jornada pelas funções geradoras assíncronas comparado-as com as corrotinas nativas. - -===== Geradores assíncronos versus corrotinas nativas - -Aqui((("native coroutines", "versus asynchronous generators", secondary-sortas="asynchronous generators"))) estão algumas semelhanças e diferenças fundamentais entre uma corrotina nativa e uma função geradora assíncrona: - -* Ambas são declaradas com `async def`. -* Um gerador assíncrono sempre tem uma expressão `yield` em seu corpo—é isso que o torna um gerador. Uma corrotina nativa nunca contém um `yield`. -* Uma corrotina nativa pode `return` (_devolver_) algum valor diferente de `None`. Um gerador assíncrono só pode usar comandos `return` vazios. -* Corrotinas nativas são esperáveis: elas podem ser acionadas por expressões `await` ou passadas para alguma das muitas funções do `asyncio` que aceitam argumentos esperáveis, tal como `create_task`. Geradores assíncronos não são esperáveis. Eles são iteráveis assíncronos, acionados por `async for` ou por compreensões assíncronas. - - -É hora então de falar sobre as compreensões assíncronas.((("", startref="Gasync21"))) - -==== Compreensões assíncronas e expressões geradoras assíncronas - -A https://fpy.li/pep530[PEP 530—Asynchronous Comprehensions] (EN) introduziu((("generator expressions (genexps)", id="genexp21")))((("list comprehensions (listcomps)", "asynchronous", id="LCasync21"))) o uso de `async for` e `await` na sintaxe de compreensões e expressões geradoras, a partir do Python 3.6. - -A única sintaxe definida na PEP 530 que pode aparecer fora do corpo -de uma `async def` é uma expressão geradora assíncrona. - -===== Definindo e usando uma expressão geradora assíncrona - -Dado o gerador assíncrono `multi_probe` do <>, -poderíamos escrever outro gerador assíncrono que devolvesse apenas os nomes de domínios encontrados. -Aqui está uma forma de fazer isso—novamente usando o console assíncrono iniciado com `-m asyncio`: - -[source, pycon] ----- ->>> from domainlib import multi_probe ->>> names = 'python.org rust-lang.org golang.org no-lang.invalid'.split() ->>> gen_found = (name async for name, found in multi_probe(names) if found) # <1> ->>> gen_found - at 0x10a8f9700> # <2> ->>> async for name in gen_found: # <3> -... print(name) -... -golang.org -python.org -rust-lang.org ----- -<1> O uso de `async for` torna isso uma expressão geradora assíncrona. Ela pode ser definida em qualquer lugar de um módulo Python. -<2> A expressão geradora assíncrona cria um objeto `async_generator`—exatamente o mesmo tipo de objeto devolvido por uma função geradora assíncrona como `multi_probe`. -<3> O objeto gerador assíncrono é acionado pelo comando `async for`, que por sua vez só pode aparecer dentro do corpo de uma `async def` ou no console assíncrono mágico que eu usei nesse exemplo. - -Resumindo: uma expressão geradora assíncrona pode ser definida em qualquer ponto do seu programa, mas só pode ser consumida dentro de uma corrotina nativa ou de uma função geradora assíncrona. - -As demais construções sintáticas introduzidos pela PEP 530 só podem ser definidos e usados dentro de corrotinas nativas ou de funções geradoras assíncronas. - -===== Compreeensões assíncronas - -Yury Selivanov—autor da PEP 530—justifica a necessidade de compreensões assíncronas com três trechos curtos de código, reproduzidos a seguir. - -Podemos todos concordar que deveria ser possível reescrever esse código: - -[source, py3] ----- -result = [] -async for i in aiter(): - if i % 2: - result.append(i) ----- - -assim: - -[source, py3] ----- -result = [i async for i in aiter() if i % 2] ----- - -Além disso, dada uma corrotina nativa `fun`, deveria ser possível escrever isso: - -[source, py3] ----- -result = [await fun() for fun in funcs] ----- - -[TIP] -==== -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`] -para mais informações. -==== - -Voltemos ao console assíncrono mágico: - -[source, pycon] ----- ->>> names = 'python.org rust-lang.org golang.org no-lang.invalid'.split() ->>> names = sorted(names) ->>> coros = [probe(name) for name in names] ->>> await asyncio.gather(*coros) -[Result(domain='golang.org', found=True), -Result(domain='no-lang.invalid', found=False), -Result(domain='python.org', found=True), -Result(domain='rust-lang.org', found=True)] ->>> [await probe(name) for name in names] -[Result(domain='golang.org', found=True), -Result(domain='no-lang.invalid', found=False), -Result(domain='python.org', found=True), -Result(domain='rust-lang.org', found=True)] ->>> ----- - -Observe que eu ordenei a lista de nomes, para mostrar que os resultados chegam na ordem em que foram enviados, nos dois casos. - -A PEP 530 permite o uso de `async for` e `await` em compreensões de lista, bem como em compreensões de `dict` e de `set`. Por exemplo, aqui está uma compreensão de `dict` para armazenar os resultados de `multi_probe` no console assíncrono: - -[source, pycon] ----- ->>> {name: found async for name, found in multi_probe(names)} -{'golang.org': True, 'python.org': True, 'no-lang.invalid': False, -'rust-lang.org': True} ----- - -Podemos usar a palavra-chave `await` na expressão antes de cláusulas `for` ou de `async for`, e também na expressão após a cláusula `if`. -Aqui está uma compreensão de `set` no console assíncrono, coletando apenas os domínios encontrados. - -[source, pycon] ----- ->>> {name for name in names if (await probe(name)).found} -{'rust-lang.org', 'python.org', 'golang.org'} ----- - -Precisei colocar parênteses extras ao redor da expressão `await` devido à precedência mais alta do operador `.` (ponto) de `+__getattr__+`. - -Repetindo, todas essas compreensões só podem aparecer no corpo de uma `async def` ou no console assíncrono encantado. - -Agora vamos discutir um recurso muito importante dos comandos `async`, das expressões `async`, e dos objetos que eles criam. -Esses artefatos são muitas vezes usados com o _asyncio_ mas, na verdade, eles são independentes da biblioteca.((("", startref="LCasync21")))((("", startref="genexp21")))((("", startref="ITERasync21")))((("", startref="ITasync21")))((("", startref="APRiteration21"))) - - -=== 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` do 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.] -Graças à API extensível fornecida por métodos especiais, qualquer um suficientemente motivado pode escrever seu ambiente de runtime e sua 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_]. -Ele estava interessado em repensar como esses recursos da linguagem poderiam ser usados em uma framework desenvolvida do zero. Lembre-se que o `asyncio` foi lançado no Python 3.4, e usava `yield from` em vez de `await`, então sua API não conseguia aproveitar gerenciadores de contexto assíncronos, iteradores assíncronos e tudo o mais que as palavras-chave `async/await` tornaram possível. O resultado é que o _Curio_ tem uma API mais elegante e uma implementação mais simples quando comparado ao `asyncio`. - -O <> mostra o script _blogdom.py_ (<>) reescrito para usar o _Curio_. - -[[blogdom_curio_ex]] -.blogdom.py: <>, agora usando o _Curio_ -==== -[source, python3] ----- -include::code/21-async/domains/curio/blogdom.py[] ----- -==== -<1> `probe` não precisa obter o loop 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`. -<5> Iterar com `async for` sobre um `TaskGroup` produz instâncias de `Task` a medida que cada uma termina. Isso corresponde à linha em <> que usa `for … as_completed(…):`. -<6> O _Curio_ foi pioneiro no uso dessa maneira sensata de iniciar um programa assíncrono em Python. - -Para expandir esse último ponto: se você olhar nos exemplo de código de `asyncio` na primeira edição do _Python Fluente_, verá linhas como as seguintes, repetidas várias vezes: - -[source, python3] ----- - loop = asyncio.get_event_loop() - loop.run_until_complete(main()) - loop.close() ----- - -Um `TaskGroup` do _Curio_ é um gerenciador de contexto assíncrono que substitui várias APIs e padrões de codificação ad hoc do `asyncio`. -Acabamos de ver como iterar sobre um `TaskGroup` torna a função `asyncio.as_completed(…)` desnecessária. - -Outro exemplo: em vez da função especial `gather`, este trecho da -https://fpy.li/21-50[documentação de "Task Groups"] (EN) -coleta os resultados de todas as tarefas no grupo: - -[source, python3] ----- -async with TaskGroup(wait=all) as g: - await g.spawn(coro1) - await g.spawn(coro2) - await g.spawn(coro3) -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. -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] -==== -A concorrência estruturada vai provavelmente ser adotada pelo `asyncio` em versões futuras do Python. Uma indicação forte disso aparece na https://fpy.li/pep654[PEP 654–Exception Groups and except*] (EN), que foi https://fpy.li/21-52[aprovada para o Python 3.11] (EN). -A https://fpy.li/21-53[seção "Motivation"](EN) menciona as "creches" (_nurseries) do _Trio_, o nome que els dão para grupos de tarefas: -"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. -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`. -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_], -mas não sei de nenhuma biblioteca assíncrona de banco de dados que o suporte nesse momento. -No repositório do _Curio_ há um conjunto impressionante de https://fpy.li/21-56[exemplos de programação para rede], incluindo um que utiliza _WebSocket_, e outro implementando o algoritmo concorrente https://fpy.li/21-57[RFC 8305—Happy Eyeballs], para conexão com pontos de acesso IPv6 com rápido recuo para IPv4 se necessário. - -O design do _Curio_ tem tido grande influência. -A 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 do 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 do 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. - -Anotações de tipo para tipos assíncronos é o nosso próximo tópico.((("", startref="APRcurio21")))((("", startref="curio21"))) - -=== Dicas de tipo para objetos assíncronos - -O((("asynchronous programming", "type hinting asynchronous objects")))((("type hints (type annotations)", "for asynchronous objects", secondary-sortas="asynchronous objects"))) tipo devolvido por uma corrotina nativa é o tipo do objeto que você obtém quando usa `await` naquela corrotina, que é o tipo do objeto que aparece nos comandos `return` no corpo da função corrotina nativa.footnote:[Isso difere das anotações de corrotinas clássicas, discutidas na seção <>.] - -Nesse capítulo mostro muitos exemplos de corrotinas nativas anotadas, incluindo a `probe` do <>: - -[source, python] ----- -async def probe(domain: str) -> tuple[str, bool]: - try: - await socket.getaddrinfo(domain, None) - except socket.gaierror: - return (domain, False) - return (domain, True) ----- - -Se você precisar anotar um parâmetro que recebe um objeto corrotina, então o tipo genérico é: - -[source, python] ----- -class typing.Coroutine(Awaitable[V_co], Generic[T_co, T_contra, V_co]): - ... ----- - -Aquele tipo e os tipos seguintes foram introduzidos no Python 3.5 e 3.6 para anotar objetos assíncronos: - -[source, python] ----- -class typing.AsyncContextManager(Generic[T_co]): - ... -class typing.AsyncIterable(Generic[T_co]): - ... -class typing.AsyncIterator(AsyncIterable[T_co]): - ... -class typing.AsyncGenerator(AsyncIterator[T_co], Generic[T_co, T_contra]): - ... -class typing.Awaitable(Generic[T_co]): - ... ----- - -Com o Python ≥ 3.9, use os equivalentes deles em `collections.abc`. - -Quero destacar três aspectos desses tipos genéricos. - -Primeiro: eles são todos covariantes do primeiro parâmetro de tipo, que é o tipo dos itens produzidos a partir desses objetos. -Lembre-se da regra #1 da <>: - -[quote] -____ -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". -Assim, pode ser contra-variante, pelo Regra de Variância #2 - -[quote] -____ -Se um parâmetro de tipo formal define um tipo para um dado que entra no objeto após sua construção inicial, ele pode ser contra-variante. -____ - -Terceiro: `AsyncGenerator` não tem tipo de devolução, ao contrário de `typing.Generator`, -que vimos na seção <>. -Devolver um valor levantando `StopIteration(value)` era uma das gambiarras que permitia a geradores operarem como corrotinas e suportar `yield from`, como vimos na seção <>. -Não há tal sobreposição entre os objetos assíncronos: -objetos `AsyncGenerator` não devolvem valores e são completamente separados de objetos corrotina, que são anotados com `typing.Coroutine`. - -Por fim, vamos discutir rapidamente as vantagens e desafios da programaçào assíncrona. - - -[[how_async_works_and_does_not_sec]] -=== Como a programação assíncrona funciona e como não funciona - -As seções finais deste capítulo discutem as ideias de alto nível em torno da programação assíncrona, independente da linguagem ou da biblioteca usadas. - -Vamos começar explicando a razão número 1 pela qual a programação assíncrona é atrativa, seguido por um mito popular e como lidar com ele. - - -[[around_blocking_calls_sec]] -==== Correndo em círculos em torno de chamadas bloqueantes - -Ryan Dahl, o((("asynchronous programming", "benefits of"))) inventor do Node.js, introduz a filosofia por trás de seu projeto dizendo "Estamos fazendo E/S de forma totalmente errada."footnote:[Vídeo: https://fpy.li/21-59["Introduction to Node.js" (_Introdução ao Node.js_)], em 4:55.] (EN). Ele define uma _função bloqueante_ como uma função que faz E/S de arquivo ou rede, e argumenta que elas não podem ser tratadas da mesma forma que tratamos funções não-bloqueantes. Para explicar a razão disso, ele apresenta os números na segunda coluna da <>. - -[role="pagebreak-before less_space"] -[[latency_tbl]] -.Latência de computadores modernos para ler dados em diferentes dispositivos. A terceira coluna mostra os tempos proporcionais em uma escala fácil de entender para nós, humanos vagarosos. -[options="header"] -|======================================================= -|Dispositivo |Ciclos de CPU |Escala proporcional "humana" -|L1 cache |3 |3 segundos -|L2 cache |14 |14 segundos -|RAM |250 |250 segundos -|disk |41.000.000 |1,3 anos -|network |240.000.000 |7,6 anos -|======================================================= - -//// - -[options="header", cols="<,>,<"] - -PROD: the column widths in the rendered PDF don't reflect the actual contents or headers. -For example, the first column is the widest, for no apparent reason. - -Also, it would be nice to align the numbers in the "CPU cycles" column to the right, -but I don't know how to do that. - -Thanks! - -//// - - -Para a <> fazer sentido, tenha em mente que as CPUs modernas, com relógios funcionando em frequências na casa dos GHz, rodam bilhões de ciclos por segundo. -Vamos dizer que uma CPU rode exatamente 1 bilhão de ciclos por segundo. -Aquela CPU pode realizar mais de 333 milhões de leituras do cache L1 em 1 segundo, ou 4 (quatro!) leituras da rede no mesmo tempo. -A terceira coluna da <> coloca os números em perspectiva, multiplicando a segunda coluna por um fator constante. -Então, em um universo alternativo, se uma leitura do cache L1 demorasse 3 segundos, uma leitura da rede demoraria 7,6 anos! - -A <> explica porque uma abordagem disciplinada da programação assíncrona pode levar a servidores de alto desempenho. -O desafio é conseguir essa disciplina. -O primeiro passo é reconhecer que um sistema limitado apenas por E/S é uma fantasia. - -[[myth_iobound_sec]] -==== O mito dos sistemas limitados por E/S - -Um((("asynchronous programming", "myth of I/O-bound systems")))((("network I/O", "myth of I/O-bound systems"))) -meme exaustivamente repetido é que programação assíncrona é boa para -"sistemas limitados por E/S"—_I/O bound systems_, ou seja, -sistemas onde o gargalo é E/S, e não processamento de dados na CPU. -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, -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, -com severos impactos no desempenho do sistema como um todo. - -Dado que qualquer sistema não-trivial terá funções limitadas pela CPU, -lidar com elas é a chave do sucesso na programação assíncrona. - -[[avoid_cpu_trap_sec]] -==== Evitando as armadilhas do uso da CPU - -Se((("asynchronous programming", "avoiding CPU-bound traps")))((("CPU-bound systems"))) você está usando Python em larga escala, deve ter testes automatizados projetados especificamente para detectar regressões de desempenho assim que elas acontecem. -Isso é de importância crítica com código assíncrono, mas é relevante também para código Python baseado em threads—por causa da GIL. Se você esperar até a lentidão começar a incomodar a equipe de desenvolvimento, será tarde demais. O conserto provavelmente vai exigir algumas mudanças drásticas. - -Aqui estão algumas opções para quando você identifica gargalos de uso da CPU: - -* Delegar a tarefa para um pool de processos Python. -* Delegar a tarefa para um fila de tarefas externa. -* Reescrever o código relevante em Cython, C, Rust ou alguma outra linguagem que compile para código de máquina e faça interface com a API Python/C, de preferência liberando a GIL. -* Decidir que você pode tolerar a perda de desempenho e deixar como está—mas registre essa decisão, para torná-la mais fácil de reverter no futuro. - -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]. - -Programação concorrente é um tópico fascinante, e eu gostaria de escrever muito 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. - - -=== Resumo do capítulo - -[quote, Alvaro Videla e Jason J. W. Williams, RabbitMQ in Action] -____ -O problema com as abordagens usuais da programação assíncrona é que elas são propostas do tipo "tudo ou nada". -Ou você reescreve todo o código, de forma que nada nele bloqueie [o processamento] ou você está só perdendo tempo. -____ - -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 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. - -Após os exemplos simples com o _spinner_ no capítulo <>, -aqui nosso maior foco foi a programação assíncrona com corrotinas nativas, começando com o exemplo de sondagem de DNS _blogdom.py_, seguido pelo conceito de _esperáveis_. -Lendo o código-fonte de _flags_asyncio.py_, descobrimos o primeiro exemplo de um _gerenciador de contexto assíncrono_. - -As variantes mais avançadas do programa de download de bandeiras introduziram duas funções poderosas: o gerador `asyncio.as_completed` generator e a corrotina `loop.run_in_executor`. Nós também vimos o conceito e a aplicação de um semáforo, para limitar o número de downloads concorrentes—como esperado de clientes HTTP bem comportados. - -A programaçãp assíncrona para servidores foi apresentada com os exemplos _mojifinder_: -um serviço web usando a _FastAPI_ e o _tcp_mojifinder.py_—este último utilizando apenas o `asyncio` e o protocolo TCP.. - -A seguir, iteração assíncrona e iteráveis assíncronos foram o principal tópico, com seções sobre `async for`, o console assíncrono do Python, geradores assíncronos expressões geradoras assíncronas e compreensões assíncronas. - -O último exemplo do capítulo foi o _blogdom.py_ reescrito com a framework _Curio_, demonstrando como os recursos de programação assíncrona do Python não estão presos ao pacote `asyncio`. -O _Curio_ também demonstra o conceito de _concorrência estruturada_, que pode vir a ter um grande impacto em toda a indústria de tecnologia, trazendo mais clareza para o código concorrente. - -Por fim, as seções sob o título <> -discutiram o principal atrativo da programação assíncrona, -a falácia dos "sistemas limitados por E/S" -e como lidar com as inevitáveis partes de uso intensivo de CPU em seu programa. - - -=== Para saber mais - -A((("asynchronous programming", "further reading on"))) palestra de abertura da PyOhio 2016, de David Beazley, -https://fpy.li/21-61["Fear and Awaiting in Async"] (EN) é uma fantástica introdução, com "código ao vivo", ao potencial dos recursos da linguagem tornados possíveis pela contribuição de Yury Selivanov ao Python 3.5, as palavras-chave `async/await`. -Em certo momento, Beazley reclama que `await` não pode ser usada em compreensões de lista, mas isso foi resolvido por Selivanov na https://fpy.li/pep530[PEP 530—Asynchronous Comprehensions] (EN), implementada mais tarde naquele mesmo ano, no Python 3.6. - -Fora isso, todo o resto da palestra de Beazley é atemporal, pois ele demonstra como os objetos assíncronos vistos nesse capítulo funcionam, sem ajuda de qualquer framework—com uma simples função `run` usando `.send(None)` para acionar corrotinas. -Apenas no final Beazley mostra o https://fpy.li/21-62[_Curio_], que ele havia começado a programar naquele ano, como um experimento, para ver o quão longe era possível levar a programação assíncrona sem se basear em callbacks ou _futures_, usando apenas corrotinas. -Como se viu, dá para ir muito longe—como demonstra a evolução do _Curio_ e a criação posterior do https://fpy.li/21-58[_Trio_] por Nathaniel J. Smith. -A documentação do _Curio's_ contém https://fpy.li/21-64[links] para outras palestras de Beazley sobre o assunto. - -Além criar o _Trio_, -Nathaniel J. Smith escreveu dois post de blog muito profundos, que gostaria de recomendar: -https://fpy.li/21-65["Some thoughts on asynchronous API design in a post-async/await world" (_Algumas reflexões sobre o design de APIs assíncronas em um mundo pós-async/await_)] (EN), comparando os designs do _Curio_ e do _asyncio_, e -https://fpy.li/21-66["Notes on structured concurrency, or: Go statement considered harmful" (_Notas sobre concorrência estruturada, ou: o comando Go considerado nocivo_)] (EN), sobre concorrência estruturada. Smith também deu uma longa e informativa resposta à questão: -https://fpy.li/21-67["What is the core difference between asyncio and trio?" (_Qual é a principal diferença entre o asyncio e o trio?_)] (EN) -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, -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. - -Para uma introdução muito acessível, de 30 minutos, à programação assíncrona em geral e também ao _asyncio_, -assista a palestra -https://fpy.li/21-71["Asynchronous Python for the Complete Beginner" (_Python Assíncrono para o Iniciante Completo_)] (EN), de Miguel Grinberg, -apresentada na PyCon 2017. -Outra ótima introdução é -https://fpy.li/21-72["Demystifying Python's Async and Await Keywords" (_Desmistificando as Palavras-Chave Async e Await do Python_)] (EN), -apresentada por Michael Kennedy, onde entre outras coisas aprendi sobre a bilblioteca -https://fpy.li/21-73[_unsync_], que oferece um decorador para delegar a execução de corrotinas, funções dedicadas a E/S e funções de uso intensivo de CPU para `asyncio`, `threading`, ou `multiprocessing`, conforme a necessidade. - -Na EuroPython 2019, Lynn Root—uma líder global da https://fpy.li/21-74[_PyLadies_]—apresentou a excelente -https://fpy.li/21-75["Advanced asyncio: Solving Real-world Production Problems" (_Asyncio Avançado: Resolvendo Problemas de Produção do Mundo Real_)] (EN), -baseada na sua experiência usando Python como um engenheira do Spotify. - -Em 2020, Łukasz Langa gravou um grande série de vídeos sobre o _asyncio_, começando com -https://fpy.li/21-76["Learn Python's AsyncIO #1--The Async Ecosystem" (_Aprenda o AsyncIO do Python—O Ecossistema Async_)] (EN). -Langa também fez um vídeo muito bacana, -https://fpy.li/21-77["AsyncIO + Music"] (EN), -para a PyCon 2020, que não apenas mostra o _asyncio_ aplicado a um domínio orientado a eventos muito concreto, como também explica essa aplicação do início ao fim. - -Outra área dominada por programaçao orientada a eventos são os sistemas embarcados. -Por isso Damien George adicionou o suporte a `async/await` em seu interpretador https://fpy.li/21-78[_MicroPython_] (EN) para microcontroladores -Na PyCon Australia 2018, Matt Trentini demonstrou a biblioteca -https://fpy.li/21-79[_uasyncio_] (EN), -um subconjunto do _asyncio_ que é parte da biblioteca padrão do MicroPython. - -Para uma visão de mais alto nível sobre a programação assíncrona em Python, leia o post de blog -https://fpy.li/21-80["Python async frameworks—Beyond developer tribalism" (_Frameworks assíncronas do Python—Para além do tribalismo dos desenvolvedores_)] (EN), de Tom Christie. - -Por fim, recomendo https://fpy.li/21-81["What Color Is Your Function?" (_Qual a Cor da Sua Função?_)] de Bob Nystrom, -discutindo os modelos de execução incompatíveis de funções normais versus funções assíncronas—também conhecidas como corrotinas—em Javascript, Python, C# e outras linguagens. -Alerta de spoiler: A conclusão de Nystrom é que a linguagem que acertou nessa área foi Go, -onde todas as funções tem a mesma cor. Eu gosto disso no Go. Mas também acho que Nathaniel J. Smith tem razão quando escreveu -https://fpy.li/21-66["Go statement considered harmful" (_Comando Go considerado nocivo_)] (EN). -Nada é perfeito, e programação concorrente é sempre difícil. - - -[[async_soapbox]] -.Ponto de vista -**** - -[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 outra framework assíncrona do 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 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`: - -[quote] -____ -Entretanto, o gargalo de desempenho no _aiohttp_ estava em seu parser de HTTP, que era tão lento que pouco importava a velocidade da biblioteca de E/S subjacente. Para tornar as coisas mais interessantes, criamos uma biblioteca para Python usar a _http-parser_ (a biblioteca em C do parser do Node.js, originalmente desenvolvida para o _NGINX_). A biblioteca é chamada _httptools_, e está disponível no Github e no PyPI. -____ - -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. - - -[role="soapbox-title"] -Morte por mil cortes - -Em((("Soapbox sidebars", "Twisted library")))((("Twisted library"))) vez de um simples servidor eco, -imagine um sistema Python complexo e em evolução, com milhares de linhas de código assíncrono, -e conectado a muitas bibliotexas externas. -Anos atrás me pediram para ajudar a diagnosticar problemas de desempenho em um sistema assim. -Ele era escrito em Python 2.7, com a framework https://fpy.li/21-88[_Twisted_]—uma biblioteca sólida e, de muitas maneiras, uma precursora do próprio _asyncio_. - -Python era usado para construir uma fachada para a interface Web, -integrando funcionalidades fornecidas por biliotecas pré-existentes e -ferramentas de linha de comando escritas em outras -linguagens—mas não projetadas para execução concorrente. - -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_. -O papel do 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: -o problema estava espalhado por incontáveis pequenas funções criadas ao longo de -meses de desenvolvimento. -O conserto exigiria repensar a arquitetura do sistema, reescrever muito código, -provavelmente usar um fila de tarefas, e talvez usar microsserviços ou bibliotecas personalizadas, -escritas em linguagens mais adequadas a processamento concorrente intensivo em CPU. -Os apoiadores internos não estavam preparados para fazer aquele investimento adicional, -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. -Essa conversa com Glyph foi a inspiração para a seção <>. -**** diff --git a/capitulos/cap22.adoc b/capitulos/cap22.adoc deleted file mode 100644 index b3add4d9..00000000 --- a/capitulos/cap22.adoc +++ /dev/null @@ -1,1181 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte -[[dynamic_attributes]] -== Atributos dinâmicos e propriedades - - -[quote, Martelli, Ravenscroft & Holden, Why properties are important (Porque propriedades são importantes)] -____ -A importância crucial das propriedades é que sua existência torna perfeitamente seguro, e de fato aconselhável, expor atributos públicos de dados como parte da interface pública de sua classe.footnote:[Alex Martelli, Anna Ravenscroft & Steve Holden, https://fpy.li/pynut3[Python in a Nutshell, Third Edition] (EN) (O'Reilly), p. 123.] -____ - -No Python((("dynamic attributes and properties", "dynamic versus virtual attributes"))), atributos de dados e métodos são conhecidos conjuntamente como _atributos_ . -Um método é um atributo _invocável_. -_Atributos dinâmicos_ apresentam a mesma interface que os atributos de dados—isto é, `obj.attr`—mas são computados sob demanda. -Isso atende ao _Princípio de Acesso Uniforme_ de Bertrand Meyer: - -[quote, Bertrand Meyer, Object-Oriented Software Construction (Construção de Software Orientada a Objetos)] -____ -Todos os serviços oferecidos por um módulo deveriam estar disponíveis através de uma notação uniforme, que não revele se eles são implementados por armazenamento ou por computação.footnote:[Bertrand Meyer, Object-Oriented Software Construction, 2nd ed. (Pearson), p. 57. (EN)] -____ - -Há muitas formas de implementar atributos dinâmicos em Python. Este capítulo trata das mais simples delas: o decorador `@property` e o método especial `+__getattr__+`. - -Uma((("virtual attributes")))((("attributes", "virtual attributes"))) classe definida pelo usuário que implemente `+__getattr__+` pode implementar uma variação dos atributos dinâmicos que chamo de _atributos virtuais_: -atributos que não são declarados explicitamente em lugar algum no código-fonte da classe, e que não estão presentes no `+__dict__+` das instâncias, mas que podem ser obtidos de algum outro lugar ou calculados em tempo real sempre que um usuário tenta ler um atributo inexistente tal como `obj.no_such_attr`. - -Programar atributos dinâmicos e virtuais é o tipo de metaprogramação que autores de frameworks fazem. Entretanto, como as técnicas básicas no Python são simples, podemos usá-las nas tarefas cotidianas de processamento de dados. -É por aí que iniciaremos esse capítulo. - - -=== Novidades nesse capítulo - -A maioria das((("dynamic attributes and properties", "significant changes to"))) atualizações deste capítulo foram motivadas pela discussão relativa a `@functools.cached_property` (introduzido no Python 3.8), bem como pelo uso combinado de `@property` e `@functools.cache` (novo no 3.9). -Isso afetou o código das classes `Record` e `Event`, que aparecem na seção <>. -Também acrescentei uma refatoração para aproveitar a otimização da https://fpy.li/pep412[PEP 412—Key-Sharing Dictionary (_Dicionário de Compartilhamento de Chaves_)]. - -Para enfatizar as características mais relevantes, e ao mesmo tempo manter os exemplos legíveis, removi algum código não-essencial—fundindo a antiga classe `DbRecord` com `Record`, substituindo `shelve.Shelve` por um `dict` e suprimindo a lógica para baixar o conjunto de dados da OSCON—que os exemplos agora leem de um arquivo local, disponível no https://fpy.li/code[repositório de código do _Python Fluente_]. - -=== Processamento de dados com atributos dinâmicos - -Nos((("dynamic attributes and properties", "data wrangling with dynamic attributes", id="DAPwrangl22")))((("data wrangling", "with dynamic attributes", secondary-sortas="dynamic attributes", id="DWdyatt22"))) próximos exemplos, vamos nos valer dos atributos dinâmicos para trabalhar com um conjunto de dados JSON publicado pela O'Reilly, para a conferência OSCON 2014. O <> mostra quatro registros daquele conjunto de dados.footnote:[A OSCON—O'Reilly Open Source Conference (_Conferência O'Reilly de Código Aberto_)—foi uma vítima da pandemia de COVID-19. O arquivo JSON original de 744 KB, que usei para esses exemplos, não está mais disponível online hoje (10 de janeiro de 2021). Você pode obter uma cópia do https://fpy.li/22-1[_osconfeed.json_] no repositório de exemplos do livro.] - -[[ex_osconfeed_json]] -.Amostra de registros do osconfeed.json; o conteúdo de alguns campos foi abreviado -==== -[source, json] ----- -include::code/22-dyn-attr-prop/oscon/osconfeed-sample.json[] ----- -==== - -O <> mostra 4 dos 895 registros no arquivo JSON. O conjunto dados total é um único objeto JSON, com a chave `"Schedule"` (_Agenda_), e seu valor é outro mapeamento com quatro chaves: `"conferences"` (_conferências_), `"events"` (_eventos_), `"speakers"` (_palestrantes_), e `"venues"` (_locais_). -Cada uma dessas quatro últimas chaves aponta para uma lista de registros. -No conjunto de dados completo, as listas de `"events"`, `"speakers"` e `"venues"`contêm dezenas ou centenas de registros, ao passo que `"conferences"` contém apenas aquele único registro exibido no <>. -Cada registro inclui um campo `"serial"`, que é um identificador único do registro dentro da lista. - -Usei o console do Python para explorar o conjuntos de dados, como mostra o <>. - -[[ex_osconfeed_explore]] -.Exploração interativa do osconfeed.json -==== -[source, pycon] ----- -include::code/22-dyn-attr-prop/oscon/osconfeed_explore.rst[] ----- -==== -<1> `feed` é um `dict` contendo dicts e listas aninhados, com valores string e inteiros. -<2> Lista as quatro coleções de registros dentro de `"Schedule"`. -<3> Exibe a contagem de registros para cada coleção. -<4> Navega pelos dicts e listas aninhados para obter o nome da última palestrante (`speaker`). -<5> Obtém o número de série para aquela mesma palestrante. -<6> Cada evento tem uma lista `'speakers'`, com o número de série de zero ou mais palestrantes.((("", startref="DWdyatt22"))) - - -==== Explorando dados JSON e similares com atributos dinâmicos - -O <> é((("data wrangling", "JSON-like data", id="DWjsaon22")))((("JSON-like data", id="jsonlike22"))) bastanre simples, mas a sintaxe `feed['Schedule']['events'][40]['name']` é desajeitada. Em JavaScript, é possível obter o mesmo valor escrevendo `feed.Schedule.events[40].name`. É fácil de implementar uma classe parecida com um `dict` para fazer o mesmo em Python--há inúmeras implementações na web.footnote:[Dois exemplos são https://fpy.li/22-2[`AttrDict`] e https://fpy.li/22-3[`addict`].] Escrevi `FrozenJSON`, que é mais simples que a maioria das receitas, pois suporta apenas leitura: ela serve apenas para explorar os dados. `FrozenJSON` é também recursivo, lidando automaticamente com mapeamentos e listas aninhados. - -O <> é uma demonstração da `FrozenJSON`, e o código-fonte aparece no <>. - -[[ex_explore0_demo]] -.`FrozenJSON`, do <>, permite ler atributos como `name`, e invocar métodos como `++.keys()++` e `++.items()++` -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/explore0.py[tags=EXPLORE0_DEMO] ----- -==== -<1> Cria uma instância de `FrozenJSON` a partir de `raw_feed`, feito de dicts e listas aninhados. -<2> `FrozenJSON` permite navegar dicts aninhados usando a notação de atributos; aqui exibimos o tamanho da lista de palestrantes. -<3> Métodos dos dicts subjacentes também podem ser acessados; por exemplo,`.keys()`, para recuperar os nomes das coleções de registros. -<4> Usando `items()`, podemos buscar os nomes das coleções de registros e seus conteúdos, para exibir o `len()` de cada um deles. -<5> Uma `list`, tal como `feed.Schedule.speakers`, permanece uma lista, mas os itens dentro dela, se forem mapeamentos, são convertidos em um `FrozenJSON`. -<6> O item 40 na lista `events` era um objeto JSON; agora ele é uma instância de `FrozenJSON`. -<7> Registros de eventos tem uma lista de `speakers` com os números de séries de palestrantes. -<8> Tentar ler um atributo inexistente gera uma exceção `KeyError`, em vez da `AttributeError` usual. - -A pedra angular da classe `FrozenJSON` é o metodo `+__getattr__+`, que já usamos no exemplo `Vector` da seção <>, para recuperar componentes de `Vector` por letra: `v.x`, `v.y`, `v.z`, etc. É essencial lembrar que o método especial `+__getattr__+` só é invocado pelo interpretador quando o processo habitual falha em recuperar um atributo (isto é, quando o atributo nomeado não é encontrado na instância, nem na classe ou em suas superclasses). - -A última linha do <> expõe um pequeno problema em meu código: tentar ler um atributo ausente deveria produzir uma exceção `AttributeError`, e não a `KeyError` gerada. -Quando implementei o tratamento de erro para fazer isso, o método -`+__getattr__+` se tornou duas vezes mais longo, distraindo o leitor da lógica mais importante que eu queria apresentar. -Dado que os usuários saberiam que uma `FrozenJSON` é criada a partir de mapeamentos e listas, acho que `KeyError` não é tão confuso assim. - - -[[ex_explore0]] -.explore0.py: transforma um conjunto de dados JSON em um `FrozenJSON` contendo objetos `FrozenJSON` aninhados, listas e tipos simples -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/explore0.py[tags=EXPLORE0] ----- -==== -<1> Cria um `dict` a partir do argumento `mapping`. Isso garante que teremos um mapeamento ou algo que poderá ser convertido para isso. O prefixo de duplo sublinhado em `+__data+` o torna um _atributo privado_. -<2> `+__getattr__+` é invocado apenas quando não existe um atributo com aquele `name`. -<3> Se `name` corresponde a um atributo da instância de `dict` `++__data++`, devolve aquele atributo. -É assim que chamadas como `feed.keys()` são tratadas: -o método `keys` é um atributo do `dict` `__data`. -<4> Caso contrário, obtém o item com a chave `name` de `+self.__data+`, e devolve o resultado da chamada `FrozenJSON.build()` com aquele argumento.footnote:[A expressão `+self.__data[name]+` é onde a exceção `KeyError` pode acontecer. Idealmente, ela deveria ser tratada, e uma `AttributeError` deveria ser gerada em seu lugar, pois é isso que se espera de `+__getattr__+`. O leitor mais diligente está convidado a programar o tratamento de erro, como um exercício.] -<5> Implementar `+__dir__+` suporta a função embutida `dir()`, que por sua vez suporta o preenchimento automático (_auto-complete_) no console padrão do Python, bem como no IPython, no Jupyter Notebook, etc. Esse código simples vai permitir preenchimento automático recursivo baseado nas chaves em `+self.__data+`, porque `+__getattr__+` cria instâncias de `FrozenJSON` em tempo real—um recurso útil para a exploração interativa dos dados. -<6> Este é um construtor alternativo, um uso comum para o decorador -<7> Se `obj` é um mapeamento, cria um `FrozenJSON` com ele. Esse é um exmeplo de _goose typing_—veja a seção <> caso precise de uma revisão desse tópico. -<8> Se for uma `MutableSequence`, tem que ser uma listafootnote:[A fonte dos dados é JSON e os únicos tipos de coleção em dados JSON são `dict` e `list`.], então criamos uma `list`, passando recursivamente cada item em `obj` para `.build()`. -<9> Se não for um `dict` ou uma `list`, devolve o item com está. - -Uma instância de `FrozenJSON` contém um atributo de instância privado `+__data+`, armazenado sob o nome `++_FrozenJSON__data++`, como explicado na seção <>. -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`. - -Observe que `FrozenJSON` não transforma ou armazena o conjunto de dados original. -Conforme navegamos pelos dados, `+__getattr__+` cria continuamente instâncias de `FrozenJSON`. -Isso é aceitável para um conjunto de dados deste tamanho, e para um script que só será usado para explorar ou converter os dados. - -Qualquer script que gera ou emula nomes de atributos dinâmicos a partir de fontes arbitrárias precisa lidar com uma questão: as chaves nos dados originais podem não ser nomes adequados de atributos. A próxima seção fala disso.((("", startref="jsonlike22")))((("", startref="DWjsaon22"))) - - -[[dynamic_names_sec]] -==== O problema do nome de atributo inválido - -O((("data wrangling", "invalid attribute name problem")))((("invalid attribute name problem"))) código de `FrozenJSON` não aceita com nomes de atributos que sejam palavras reservadas do Python. Por exemplo, se você criar um objeto como esse - -[source, pycon] ----- ->>> student = FrozenJSON({'name': 'Jim Bo', 'class': 1982}) ----- - -não será possível ler `student.class`, porque `class` é uma palavra reservada no Python: - -[source, pycon] ----- ->>> student.class - File "", line 1 - student.class - ^ -SyntaxError: invalid syntax ----- - -Claro, sempre é possível fazer assim: - -[source, pycon] ----- ->>> getattr(student, 'class') -1982 ----- - -Mas a ideia de `FrozenJSON` é oferecer acesso conveniente aos dados, então uma solução melhor é verificar se uma chave no mapamento passado para `+FrozenJSON.__init__+` é uma palavra reservada e, em caso positivo, anexar um `_` a ela, de forma que o atributo possa ser acessado assim: - -[source, pycon] ----- ->>> student.class_ -1982 ----- - -Isso pode ser feito substituindo o `+__init__+` de uma linha do <> pela versão no <>. - -[[ex_explore1]] -.explore1.py: anexa um `_` a nomes de atributo que sejam palavraas reservadas do Python -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/explore1.py[tags=EXPLORE1] ----- -==== -<1> A função `keyword.iskeyword(…)` é exatamente o que precisamos; para usá-la, o módulo `keyword` precisa ser importado; isso não aparece nesse trecho. - -Um problema similar pode surgir se uma chave em um registro JSON não for um identificador válido em Python: - - -[source, pycon] ----- ->>> x = FrozenJSON({'2be':'or not'}) ->>> x.2be - File "", line 1 - x.2be - ^ -SyntaxError: invalid syntax ----- - -Essas chaves problemáticas são fáceis de detectar no Python 3, porque a classe `str` oferece o método `s.isidentifier()`, que informa se `s` é um identificador Python válido, de acordo com a gramática da linguagem. Mas transformar uma chave que não seja um identificador válido em um nome de atributo válido não é trivial. Uma solução seria implementar `+__getitem__+` para permitir acesso a atributos usando uma notação como `x['2be']`. Em nome da simplicidade, não vou me preocupar com esse problema. - - -Após essa pequena conversa sobre os nomes de atributos dinâmicos, vamos examinar outra característica essencial de `FrozenJSON`: a lógica do método de classe `build`. -`Frozen.JSON.build` é usado por `+__getattr__+` para devolver um tipo diferente de objeto, dependendo do valor do atributo que está sendo acessado: estruturas aninhadas são convertidas para instâncias de `FrozenJSON` ou listas de instâncias de `FrozenJSON`. - -Em vez de usar um método de classe, a mesma lógica poderia ser implementada com o método especial -`+__new__+`, como veremos a seguir. - - -[[flexible_new_sec]] -==== Criação flexível de objetos com __new__ - -Muitas((("data wrangling", "flexible object creation", id="DWflex22")))((("*_new*_", id="new22")))((("objects", "flexible object creation", id="Oflex22"))) vezes nos referimos ao `+__init__+` como o método construtor, mas isso é porque adotamos o jargão de outras linguagens. -No Python, `+__init__+` recebe `self` como primeiro argumentos, portanto o objeto já existe quando `+__init__+` é invocado pelo interpretador. -Além disso, `+__init__+` não pode devolver nada. -Então, na verdade, esse método é um inicializador, não um construtor. - -Quando uma classe é chamada para criar uma instância, o método especial chamado pelo Python naquela classe para construir a instância é `+__new__+`. É um método de classe, mas recebe tratamento especial, então o decorador `@classmethod` não é aplicado a ele. -O Python recebe a instância devolvida por `+__new__+`, e daí a passa como o primeiro argumento (`self`) para `+__init__+`. Raramente precisamos escrever um `+__new__+`, pois a implementação herdada de `object` é suficiente na vasta maioria dos casos. - -Se necessário, o método `+__new__+` pode também devolver uma instância de uma classe diferente. Quando isso acontece, o interpretador não invoca `+__init__+`. -Em outras palavras, a lógica do Python para criar um objeto é similar a esse pseudo-código: - -[source, python3] ----- -include::code/22-dyn-attr-prop/pseudo_construction.py[] ----- - -O <> mostra uma variante de `FrozenJSON` onde a lógica da antiga classe `build` foi transferida para o método `+__new__+`. - -[[ex_explore2]] -.explore2.py: usando `+__new__+` em vez de `build` para criar novos objetos, que podem ou não ser instâncias de `FrozenJSON` -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/explore2.py[tags=EXPLORE2] ----- -==== -<1> Como se trata de um método de classe, o primeiro argumento recebido por `+__new__+` é a própria classe, e os argumentos restantes são os mesmos recebido por `+__init__+`, exceto por `self`. -<2> O comportamento default é delegar para o `+__new__+` de uma superclasse. Nesse caso, estamos invocando o `+__new__+` da classe base `object`, passando `FrozenJSON` como único argumento. -<3> As linhas restantes de `+__new__+` são exatamente as do antigo método `build`. -<4> Era daqui que `FrozenJSON.build` era chamado antes; agora chamamos apenas a classe `FrozenJSON`, e o Python trata essa chamada invocando `+FrozenJSON.__new__+`. - -O método `+__new__+` recebe uma classe como primeiro argumento porque, normalmente, o objeto criado será uma instância daquela classe. -Então, em `+FrozenJSON.__new__+`, quando a expressão `+super().__new__(cls)+` efetivamente chama -`+object.__new__(FrozenJSON)+`, a instância criada pela classe `object` é, na verdade, uma instância de `FrozenJSON`. -O atributo `+__class__+` da nova instância vai manter uma referência para ++FrozenJSON++, apesar da construção concreta ser realizada por `+object.__new__+`, implementado em C, nas entranhas do interpretador. - -O conjunto de dados da OSCON está estruturado de uma forma pouco amigável à exploração interativa. -Por exemplo, o evento no índice `40`, chamado `'There *Will* Be Bugs'` (_Haverá Bugs_) tem dois palestrantes, `3471` e `5199`. -Encontrar os nomes dos palestrantes é confuso, pois esses são números de série e a lista `Schedule.speakers` não está indexada por eles. -Para obter cada palestrante, precisamos iterar sobre a lista até encontrar um registro com o número de série correspondente. -Nossa próxima tarefa é reestruturar os dados para preparar a recuperação automática de registros relacionados.((("", startref="Oflex22")))((("", startref="new22")))((("", startref="DWflex22")))((("", startref="DAPwrangl22"))) - -[[computed_props_sec]] -=== Propriedades computadas - -Vimos inicialmente o decorador `@property` no <>, na seção <>. No <>, 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'`. -Por exemplo, esse é o registro de uma palestra (com a descrição parcial terminando em reticências): - -[source, json] ----- -include::code/22-dyn-attr-prop/oscon/osconfeed-talk.json[] ----- - -Vamos implementar uma classe `Event` com propriedades `venue` e `speakers`, para devolver automaticamente os dados relacionados--em outras palavras, "derreferenciar" o número de série. -Dada uma instância de `Event`, o <> mostra o comportamento desejado. - -[[ex22-7-added-uuid]] -.Ler `venue` e `speakers` devolve objetos `Record` -==== -[source, pycon] ----- -include::code/22-dyn-attr-prop/oscon/schedule_v4.py[tags=SCHEDULE4_DEMO] ----- -==== -<1> Dada uma instância de `Event`,... -<2> ...ler `event.venue` devolve um objeto `Record` em vez de um número de série. -<3> Agora é fácil obter o nome do `venue`. -<4> A propriedade `event.speakers` devolve uma lista de instâncias de `Record`. - -Como sempre, vamos criar o código passo a passo, começando com a classe `Record` e uma função para ler dados JSON e devolver um `dict` com instâncias de `Record`. - -==== Passo 1: criação de atributos baseados em dados - -O <> mostra((("computed properties", "data-driven attribute creation", id="CPdatadriven22"))) o doctest para orientar esse primeiro passo. - -[[ex_schedule_v1_demo]] -.Testando schedule_v1.py (do <>) -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/schedule_v1.py[tags=SCHEDULE1_DEMO] ----- -==== -<1> `load` um `dict` com os dados JSON. -<2> As chaves em `records` são strings criadas a partir do tipo de registro e do número de série. -<3> `speaker` é uma instância da classe `Record`, definida no <>. -<4> Campos do JSON original podem ser acessados como atributos de instância de `Record`. - -O código de _schedule_v1.py_ está no <>. - -[[ex_schedule_v1]] -.schedule_v1.py: reorganizando os dados de agendamento da OSCON -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/schedule_v1.py[tags=SCHEDULE1] ----- -==== -<1> Isso é um atalho comum para construir uma instância com atributos criados a partir de argumentos nomeados (a explicação detalhada está abaixo) . -<2> Usa o campo `serial` para criar a representação personalizada de `Record` exibida no <>. -<3> `load` vai por fim devolver um `dict` de instâncias de `Record`. -<4> Analisa o JSON, devolvendo objetos Python nativos: listas, dicts, strings, números, etc. -<5> Itera sobre as quatro listas principais, chamadas `'conferences'`, `'events'`, `'speakers'`, e `'venues'`. -<6> `record_type` é o nome da lista sem o último caractere, então `speakers` se torna `speaker`. No Python ≥ 3.9, podemos fazer isso de forma mais explícita com `collection.removesuffix('s')`—veja a -https://fpy.li/pep616[PEP 616—String methods to remove prefixes and suffixes (Métodos de string para remover prefixos e sufixos_)]. -<7> Cria a `key` no formato `'speaker.3471'`. -<8> Cria uma instância de `Record` e a armazena em `records` com a chave `key`. - - -O método `+Record.__init__+` ilustra um antigo truque do Python. Lembre-se que o `+__dict__+` de um objeto é onde são mantidos seus atributos--a menos que `+__slots__+` seja declarado na classe, como vimos na seção <>. -Daí, atualizar o `+__dict__+` de uma instância é uma maneira fácil de criar um punhado de atributos naquela instância.footnote:[`Bunch` ou "punhado" é o nome da classe usada por Alex Martelli para compartilhar essa dica em uma receita de 2001 intitulada https://fpy.li/22-4["The simple but handy ‘collector of a bunch of named stuff’ class" (_Uma classe simples mas prática 'coletora de um punhado de coisas nomeadas'_)].] - -[NOTE] -==== -Dependendo da aplicação, a classe `Record` pode ter que lidar com chaves que não sejam nomes de atributo válidos, como vimos na seção <>. Tratar essa questão nos distrairia da ideia principal desse exemplo, e não é um problema no conjunto de dados que estamos usando. -==== - -A definição de `Record` no <> é tão simples que você pode estar se perguntando porque não a usei antes, em vez do mais complicado `FrozenJSON`. São duas razões. Primeiro, `FrozenJSON` funciona convertendo recursivamente os mapeamentos aninhados e listas; `Record` não precisa fazer isso, pois nosso conjunto de dados convertido não contém mapeamentos aninhados ou listas. Os registros contêm apenas strings, inteiros, listas de strings e listas de inteiros. A segunda razão: `FrozenJSON` oferece acesso aos atributos no `dict` embutido `+__data+`—que usamos para invocar métodos como `.keys()`—e agora também não precisamos mais dessa funcionalidade. - -[NOTE] -==== -A biblioteca padrão do 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), -and -https://docs.python.org/pt-br/3/library/multiprocessing.html#multiprocessing.managers.Namespace[`multiprocessing.managers.Namespace`] (EN). -Escrevi a classe `Record`, mais simples, para destacar a ideia essencial: `+__init__+` atualizando o -`+__dict__+` da instância. -==== - -Após reorganizar o conjunto de dados de agendamento, podemos aprimorar a classe `Record` para obter automaticamente registros de `venue` e `speaker` referenciados em um registro `event`. Vamos utilizar propriedades para fazer exatamente isso nos próximos exemplos.((("", startref="CPdatadriven22"))) - - -[[oscon_schedule_v2_sec]] -==== Passo 2: Propriedades para recuperar um registro relacionado - -O((("computed properties", "property to retrieve linked records", id="CPlinked22"))) objetivo da próxima versão é: dado um registro `event`, ler sua propriedade `venue` vai devolver um `Record`. -Isso é similar ao que o ORM (_Object Relational Mapping_, Mapeamento Relacional de Objetos) do Django faz quando acessamos um campo `ForeignKey`: em vez da chave, recebemos o modelo de objeto relacionado. - -Vamos começar pela propriedade `venue`. Veja a interação parcial no <>. - -[[ex_schedule_v2_demo]] -.Extratos dos doctests de schedule_v2.py -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/schedule_v2.py[tags=SCHEDULE2_DEMO] ----- -==== -<1> O método estático `Record.fetch` obtém um `Record` ou um `Event` do conjunto de dados. -<2> Observe que `event` é uma instância da classe `Event`. -<3> Acessar `event.venue` devolve uma instância de `Record`. -<4> Agora é fácil encontrar o nome de um `event.venue`. -<5> A instância de `Event` também tem um atributo `venue_serial`, vindo dos dados JSON. - -`Event` é uma subclasse de `Record`, acrescentando um `venue` para obter os registros relacionados, e um método `+__repr__+` especializado. - -O código dessa seção está no módulo https://fpy.li/22-8[_schedule_v2.py_], no -https://fpy.li/code[repositório de código do _Python Fluente_]. -O exemplo tem aproximadamente 50 linhas, então vou apresentá-lo em partes, começando pela classe `Record` aperfeiçoada. - -[[ex_schedule_v2_record]] -.schedule_v2.py: a classe `Record` com um novo método `fetch` -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/schedule_v2.py[tags=SCHEDULE2_RECORD] ----- -==== -<1> `inspect` será usado em `load`, lista do no <>. -<2> No final, o atributo de classe privado `+__index+` manterá a referência ao `dict` devolvido por `load`. -<3> `fetch` é um `staticmethod`, para deixar explícito que seu efeito não é influenciado pela classe ou pela instância de onde ele é invocado. -<4> Preenche o `+Record.__index+`, se necessário. -<5> E o utiliza para obter um registro com uma dada `key`. - -[TIP] -==== -Esse é um exemplo onde o uso de `staticmethod` faz sentido. -O método `fetch` sempre age sobre o atributo de classe `Record.__index`, mesmo quando invocado desde uma subclasse, como `Event.fetch()`—que exploraremos a seguir. -Seria equivocado programá-lo como um método de classe, pois o primeiro argumento, `cls`, nunca é usado. -==== - -Agora podemos usar a propriedade na classe `Event`, listada no <>. - -[[ex_schedule_v2_event]] -.schedule_v2.py: a classe `Event` -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/schedule_v2.py[tags=SCHEDULE2_EVENT] ----- -==== -[role="pagebreak-before less_space"] -<1> `Event` estende `Record`. -<2> Se a instância tem um atributo `name`, esse atributo será usado para produzir uma representação personalizada. -Caso contrário, delega para o `+__repr__+` de `Record`. -<3> A propriedade `venue` cria uma `key` a partir do atributo `venue_serial`, e a passa para o método de classe `fetch`, herdado de `Record` (a razão para usar `+self.__class__+` logo ficará clara). - -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`. -Esse é um bug sutil, e poderia facilmente escapar aos testes, pois depende do conjunto de dados. - - - -[WARNING] -==== -Ao criar nomes de atributos de instância a partir de dados, sempre existe o risco de bugs causados pelo ocultamento de atributos de classe—tais como métodos—ou pela perda de dados por sobrescrita acidental de atributos de instância existentes. Esses problemas talvez expliquem, mais que qualquer outra coisa, porque os dicts do Python não são como objetos Javascript. -==== - -Se a classe `Record` se comportasse mais como um mapeamento, implementando um `+__getitem__+` dinâmico em vez de um `+__getattr__+` dinâmico, não haveria risco de bugs por ocultamento ou sobrescrita. Um mapeamento personalizado seria provavelmente a forma pythônica de implementar `Record`. Mas se eu tivesse seguido por aquele caminho, não estaríamos estudando os truques e as armadilhas da programação dinâmica de atributos. - -A parte final deste exemplo é a função `load` revisada, no <>. - -[[ex_schedule_v2_load]] -.schedule_v2.py: a função `load` -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/schedule_v2.py[tags=SCHEDULE2_LOAD] ----- -==== -<1> Até aqui, nenhuma mudança em relação ao `load` em _schedule_v1.py_ (do <>). -<2> Muda a primeira letra de `record_type` para maiúscula, para obter um possível nome de classe; por exemplo, `'event'` se torna `'Event'`. -<3> Obtém um objeto com aquele nome do escopo global do módulo; se aquele objeto não existir, obtém a classe `Record`. -<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... -<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 personalizada é `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`. - -Vamos agora aplicar a mesma ideia à nova propriedade `speakers`, na classe `Events`.((("", startref="CPlinked22"))) - -[[property_overriding_sec]] -==== Passo 3: Uma propriedade sobrepondo um atributo existente - -O((("computed properties", "property overriding existing attributes"))) nome da propriedade `venue` no <> não corresponde a um nome de campo nos registros da coleção `"events"`. -Seus dados vem de um nome de campo `venue_serial`. -Por outro lado, cada registro na coleção `events` tem um campo `speakers`, contendo uma lista de números de série. -Queremos expor essa informação na forma de uma propriedade `speakers` em instâncias de `Event`, que devolve um lista de instâncias de `Record`. -Essa colisão de nomes exige uma atenção especial, como revela o <>. - - -[[ex_schedule_v3_speakers]] -.schedule_v3.py: a propriedade `speakers` -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/schedule_v3.py[tags=SCHEDULE3_SPEAKERS] ----- -==== -<1> Os dados que precisamos estão em um atributo `speakers`, mas precisamos obtê-los diretamente -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 do 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. - -[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 seção <>, e o <> vai revelar que uma propriedade é implementada como um descritor—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. -Entretanto, criar um _cache_ de uma propriedade é uma necessidade comum—e há ressalvas. -Vamos ver então, nos próximos exemplos, como fazer isso. - - -[[cached_property_sec]] -==== Passo 4: Um _cache_ de propriedades sob medida - -Fazer((("computed properties", "property caching", id="CPpcach22"))) _caching_ de propriedades é uma necessidade comum, pois há a expectativa de que uma expressão como `event.venue` deveria ser pouco dispendiosa.footnote:[Isso é, na verdade, uma desvantagem do Princípio de Acesso Uniforme de Meyer, mencionada no início deste capítulo. Quem tiver interesse nessa discussão pode ler o <> opcional.] -Alguma forma de _caching_ poderia se tornar necessário caso o método `Record.fetch`, subjacente às propriedades de `Event`, precise consultar um banco de dados ou uma API web. - -Na primeira edição de _Python Fluente_, programei a lógica personalizada de _caching_ para o método `speakers`, como mostra o <>. - -[[ex_schedule_v4_hasattr]] -.A lógica de _caching_ personalizada usando `hasattr` desabilita a otimização de compartilhamento de chaves -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/schedule_v4_hasattr.py[tags=SCHEDULE4_HASATTR_CACHE] ----- -==== -<1> Se a instância não tem um atributo chamado `__speaker_objs`, obtém os objetos `speaker` e os armazena ali.. -<2> Devolve `self.__speaker_objs`. - -O _caching_ caseiro no <> é bastante direto, mas criar atributos após a inicialização da instância frustra a otimização da -https://fpy.li/pep412[PEP 412—Key-Sharing Dictionary (_Dicionário de Compartilhamento de Chaves_)], como explicado na seção <>. -Dependendo do tamanho da massa de dados, a diferença de uso de memória pode ser importante. - -Uma solução manual similar, que funciona bem com a otimização de compartilhamento de chaves, exige escrever um `+__init__+` para a classe `Event`, para criar o necessário `+__speaker_objs+` inicializado para `None`, e então usá-lo no método `speakers`. Veja o <>. - -[[ex_schedule_v4]] -.Armazenamento definido em `+__init__+` para manter a otimização de compartilhamento de chaves -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/schedule_v4.py[tags=SCHEDULE4_INIT] -# 15 lines omitted... -include::code/22-dyn-attr-prop/oscon/schedule_v4.py[tags=SCHEDULE4_CACHE] ----- -==== - -O <> e o <> ilustram técnicas simples de _caching_ bastante comuns em bases de código Python legadas. -Entretanto, em programas com múltiplas threads, _caches_ manuais como aqueles introduzem condições de concorrência (ou de corrida) que podem levar à corrupção de dados. -Se duas threads estão lendo uma propriedade que não foi armazenada no _cache_ anteriormente, a primeira thread precisará computar os dados para o atributo de _cache_ (`__speaker_objs` nos exemplos) e a segunda thread corre o risco de ler um valor incompleto do _cache_. - -Felizmente, o Python 3.8 introduziu o decorador `@functools.cached_property`, que é seguro para uso com threads. -Infelizmente, ele vem com algumas ressalvas, discutidas a seguir.((("", startref="CPpcach22"))) - -[[caching_properties_sec]] -==== Passo 5: _Caching_ de propriedades com functools - -O((("computed properties", "caching properties with functools", id="CPfunctool22")))((("functools module", "caching properties with", id="functools22"))) módulo `functools` oferece três decoradores para _caching_. -Vimos `@cache` e `@lru_cache` na seção <> (no <>). O Python 3.8 introduziu `@cached_property`. - -O decorador `functools.cached_property` faz _cache_ do resultado de um método em uma variável de instância com o mesmo nome. - -Por exemplo, no <>, o valor computado pelo método `venue` é armazenado em um atributo `venue`, em `self`. -Após isso, quando código cliente tenta ler `venue`, o recém-criado atributo de instância `venue` é usado, em vez do método. - -[[ex_schedule_v5_cached_property]] -.Uso simples de uma `@cached_property` -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/schedule_v5.py[tags=SCHEDULE5_CACHED_PROPERTY] ----- -==== - -Na seção <>, vimos que uma propriedade oculta um atributo de instância de mesmo nome. -Se isso é verdade, como `@cached_property` pode funcionar? -Se a propriedade se sobrepõe ao atributo de instância, o atributo `venue` será ignorado e o método `venue` será sempre chamado, -computando a `key` e rodando `fetch` todas as vezes! - -A((("attribute descriptors", "overriding versus nonoverriding")))((("nonoverriding descriptors")))((("overriding descriptors"))) resposta é um tanto triste: `cached_property` é um nome enganador. -O decorador `@cached_property` não cria uma propriedade completa, ele cria um _descritor não dominante_. Um descritor é um objeto que gerencia o acesso a um atributo em outra classe. -Vamos mergulhar nos descritores no <>. -O decorador `property` é uma API de alto nível para criar um _descritor dominante_. -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 do Python]: - -[quote] -____ -A mecânica de `cached_property()` é um tanto diferente da de `property()`. -Uma propriedade regular bloqueia a escrita em atributos, a menos que um _setter_ seja definido. Uma `cached_property`, por outro lado, permite a escrita. - -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 do 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. - -[WARNING] -==== -`@cached_property` tem algumas importantes limitações: - -* Ele não pode ser usado como um substituto direto de `@property` -se o método decorado já depender de um atributo de instância de mesmo nome. -* Ele não pode ser usado em uma classe que defina `+__slots__+`. -* 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_)]. - - -A -https://docs.python.org/pt-br/3.10/library/functools.html#functools.cached_property[documentação] de `@cached_property` -recomenda uma solução altenativa que podemos usar com `speakers`: -Empilhar decoradores `@property` e `@cache`, como exibido no <>. - -[[ex_schedule_v5_property_over_cache]] -.Stacking `@property` sobre `@cache` -==== -[source, py] ----- -include::code/22-dyn-attr-prop/oscon/schedule_v5.py[tags=SCHEDULE5_PROPERTY_OVER_CACHE] ----- -==== -<1> A ordem é importante: `@property` vai acima... -<2> ...de `@cache`. - -Lembre-se do significado dessa sintaxe, comentada em <>. -A três primeiras linhas do <> são similares a : - -[source, py] ----- -speakers = property(cache(speakers)) ----- - -O `@cache` é aplicado a `speakers`, devolvendo uma nova função. -Essa função é então decorada por `@property`, -que a substitui por uma propriedade recém-criada. - -Isso encerra nossa discussão de propriedades somente para leitura e decoradores de _caching_, explorando o conjunto de dados da OSCON. - -Na próxima seção iniciamos uma nova série de exemplos, criando propriedades de leitura e escrita.((("", startref="DAPcomputp22")))((("", startref="CPfunctool22")))((("", startref="functools22"))) - -[[prop_validation_sec]] -=== Usando uma propriedade para validação de atributos - -Além((("attributes", "using properties for attribute validation", id="Aval22")))((("dynamic attributes and properties", "using properties for attribute validation", id="DAPval22"))) de computar valores de atributos, as propriedades também são usadas para impor regras de negócio, transformando um atributo público em um atributo protegido por um _getter_ e um _setter_, sem afetar o código cliente. Vamos explorar um exemplo estendido. - - -==== LineItem Versão #1: Um classe para um item em um pedido - -Imagine uma aplicação para uma loja que vende comida orgânica a granel, onde os fregueses podem encomendar nozes, frutas secas e cereais por peso. Nesse sistema, cada pedido mantém uma sequência de produtos, e cada produto pode ser representado por uma instância de uma classe, como no <>. - - -[[lineitem_class_v1]] -.bulkfood_v1.py: a classe `LineItem` mais simples -==== -[source, py] ----- -include::code/22-dyn-attr-prop/bulkfood/bulkfood_v1.py[tags=LINEITEM_V1] ----- -==== - -Esse código é simples e agradável. Talvez simples demais. <> mostra um problema. - -[[lineitem_problem_v1]] -.Um peso negativo resulta em um subtotal negativo -==== -[source, py] ----- -include::code/22-dyn-attr-prop/bulkfood/bulkfood_v1.py[tags=LINEITEM_PROBLEM_V1] ----- -==== - -Apesar desse ser um exemplo inventado, não é tão fantasioso quanto se poderia imaginar. Aqui está uma história do início da Amazon.com: - -[quote, Jeff Bezos, fundador e CEO da Amazon.com] -____ -Descobrimos que os clientes podiam encomendar uma quantidade negativa de livros! E nós creditaríamos seus cartões de crédito com o preço e, suponho, esperaríamos que eles nos enviassem os livros.footnote:[Citação direta de Jeff Bezos no artigo do _Wall Street Journal_ https://fpy.li/22-16["Birth of a Salesman" (_O Nascimento de um Vendedor_)] (EN) (15 de outubro de 2011). Pelo menos até 2023, é necessario ser assinante para ler o artigo.] -____ - -Como consertar isso? Poderíamos mudar a interface de `LineItem` para usar um _getter_ e um _setter_ para o atributo `weight`. Esse seria o caminho do Java, e não está errado. -Por outro lado, é natural poder determinar o `weight` (_peso_) de um item apenas atribuindo um valor a ele; e talvez o sistema esteja em produção, com outras partes já acessando `item.weight` diretamente. Nesse caso, o caminho do Python seria substituir o atributo de dados por uma propriedade. - - -==== LineItem versão #2: Uma propriedade de validação - -Implementar uma propriedade nos permitirá usar um _getter_ e um _setter_, mas a interface de `LineItem` não mudará (isto é, definir o `weight` de um `LineItem` ainda será escrito no formato `raisins.weight = 12`). - -O <> lista o código para uma propriedade de leitura e escrita de `weight`. - -[[lineitem_class_v2]] -.bulkfood_v2.py: um `LineItem` com uma propriedade `weight` -==== -[source, py] ----- -include::code/22-dyn-attr-prop/bulkfood/bulkfood_v2.py[tags=LINEITEM_V2] ----- -==== -<1> Aqui o _setter_ da propriedade já está em uso, assegurando que nenhuma instância com peso negativo possa ser criada. -<2> `@property` decora o método _getter_. -<3> Todos os métodos que implementam a propriedade compartilham o mesmo nome, do atributo público: `weight`. -<4> O valor efetivo é armazenado em um atributo privado `+__weight+`. -<5> O _getter_ decorado tem um atributo `.setter`, que também é um decorador; isso conecta o _getter_ e o _setter_. -<6> Se o valor for maior que zero, definimos o `+__weight+` privado. -<7> Caso contrário, uma `ValueError` é gerada. - -Observe como agora não é possível criar uma `LineItem` com peso inválido: - -[source, pycon] ----- ->>> walnuts = LineItem('walnuts', 0, 10.00) -Traceback (most recent call last): - ... -ValueError: value must be > 0 ----- - -Agora protegemos `weight` impedindo que usuários forneçam valores negativos. Apesar de compradores normalmente não poderem definir o preço de um produto, um erro administrativo ou um bug poderiam criar um `LineItem` com um `price` negativo. Para evitar isso, poderíamos também transformar `price` em uma propriedade, mas isso levaria a alguma repetição no nosso código. - -Lembre-se da citação de Paul Graham no <>: "Quando vejo padrões em meus programas, considero isso um mau sinal." A cura para a repetição é a abstração. Há duas maneiras de abstrair definições de propriedades: usar uma fábrica de propriedades ou uma classe descritora. A abordagem via classe descritora é mais flexível, e dedicaremos o <> a uma discussão completa desse recurso. Na verdade, propriedades são, elas mesmas, implementadas como classes descritoras. Mas aqui vamos seguir com nossa exploração das propriedades, implementando uma fábrica de propriedades em forma de função. - -Mas antes de podermos implementar uma fábrica de propriedades, precisamos entender melhor as propriedades em si.((("", startref="Aval22")))((("", startref="DAPval22"))) - - -=== Considerando as propriedades de forma adequada - -Apesar((("dynamic attributes and properties", "property class", id="DAPpclass22")))((("property class", id="proclass22"))) de ser frequentemente usada como um decorador, `property` é na verdade uma classe embutida. No Python, funções e classes são muitas vezes intercambiáveis, pois ambas são invocáveis e não há um operador `new` para instanciação de objeto, então invocar um construtor não é diferente de invocar uma função fábrica. E ambas podem ser usadas como decoradores, desde que elas devolvam um novo invocável, que seja um substituto adequado do invocável decorado. - -Essa é a assinatura completa do construtor de `property`: - -[source, python3] ----- -property(fget=None, fset=None, fdel=None, doc=None) ----- - -Todos os argumentos são opcionais, e se uma função não for fornecida para algum deles, a operação correspondente não será permitida pelo objeto propriedade resultante. - -O tipo `property` foi introduzido no Python 2.2, mas a sintaxe `@` do decorador só surgiu no Python 2.4. Então, por alguns anos, propriedades eram definidas passando as funções de acesso nos dois primeiros argumentos. - -A sintaxe "clássica" para definir propriedades sem decoradores é ilustrada pelo <>. - -[[lineitem_class_v2b]] -.bulkfood_v2b.py: igual ao <>, mas sem usar decoradores -==== -[source, py] ----- -include::code/22-dyn-attr-prop/bulkfood/bulkfood_v2b.py[tags=LINEITEM_V2B] ----- -==== -<1> Um _getter_ simples. -<2> Um _setter_ simples. -<3> Cria a `property` e a vincula a um atributo de classe simples. - -Em algumas situações, a forma clássica é melhor que a sintaxe do decorador; o código da fábrica de propriedade, que discutiremos em breve, é um exemplo. Por outro lado, no corpo de uma classe com muitos métodos, os decoradores tornam explícito quais são os _getters_ e os _setters_, sem depender da convenção do uso dos prefixos `get` e `set` em seus nomes. - -A presença de uma propriedade em uma classe afeta como os atributos nas instâncias daquela classe podem ser encontrados, de uma forma que à primeira vista pode ser surpreendente. A próxima seção explica isso. - - -[[prop_override_instance]] -==== Propriedades sobrepõe atributos de instância - -Propriedades são sempre atributos de classe, mas elas na verdade gerenciam o acesso a atributos nas instâncias da classe. - -Na seção <>, vimos que quando uma instância e sua classe tem um atributo de dados com o mesmo nome, o atributo de instância sobrepõe, ou oculta, o atributo da classe—ao menos quando lidos através daquela instância. O <> ilustra esse ponto. - -[[attr_override_demo1]] -.Atributo de instância oculta o atributo de classe `data` -==== -[source, pycon] ----- ->>> class Class: # <1> -... data = 'the class data attr' -... @property -... def prop(self): -... return 'the prop value' -... ->>> obj = Class() ->>> vars(obj) # <2> -{} ->>> obj.data # <3> -'the class data attr' ->>> obj.data = 'bar' # <4> ->>> vars(obj) # <5> -{'data': 'bar'} ->>> obj.data # <6> -'bar' ->>> Class.data # <7> -'the class data attr' ----- -==== -<1> Define `Class` com dois atributos de classe: o atributo `data` e a propriedade `prop`. -<2> `vars` devolve o `+__dict__+` de `obj`, mostrando que ele não tem atributos de instância. -<3> Ler de `obj.data` obtém o valor de `Class.data`. -<4> Escrever em `obj.data` cria um atributo de instância. -<5> Inspeciona a instância, para ver o atributo de instância. -<6> Ler agora de `obj.data` obtém o valor do atributo da instância. -Quanto lido a partir da instância `obj`, o `data` da instância oculta o `data` da classe. -<7> O atributo `Class.data` está intacto. - - -Agora vamos tentar sobrepor o atributo `prop` na instância `obj`. Continuando a sessão de console anterior, temos o <>. - -[[attr_override_demo2]] -.Um atributo de instância não oculta uma propriedade da classe (continuando do <>) -==== -[source, pycon] ----- ->>> Class.prop # <1> - ->>> obj.prop # <2> -'the prop value' ->>> obj.prop = 'foo' # <3> -Traceback (most recent call last): - ... -AttributeError: can't set attribute ->>> obj.__dict__['prop'] = 'foo' # <4> ->>> vars(obj) # <5> -{'data': 'bar', 'prop': 'foo'} ->>> obj.prop # <6> -'the prop value' ->>> Class.prop = 'baz' # <7> ->>> obj.prop # <8> -'foo' ----- -==== -<1> Ler `prop` diretamente de `Class` obtém o próprio objeto propriedade, sem executar seu método _getter_. -<2> Ler `obj.prop` executa o _getter_ da propriedade. -<3> Tentar definir um atributo `prop` na instância falha. -<4> Inserir `'prop'` diretamente em `+obj.__dict__+` funciona. -<5> Podemos ver que agora `obj` tem dois atributos de instância: `data` e `prop`. -<6> Entretanto, ler `obj.prop` ainda executa o _getter_ da propriedade. A propriedade não é ocultada pelo atributo de instância. -<7> Sobrescrever `Class.prop` destrói o objeto propriedade. -<8> Agora `obj.prop` obtém o atributo de instância. `Class.prop` não é mais uma propriedade, então ela não mais sobrepõe `obj.prop`. - -Como uma demonstração final, vamos adicionar uma propriedade a `Class`, e vê-la sobrepor um atributo de instância. O <> retoma a sessão onde <> parou. - -[[attr_override_demo3]] -.Uma nova propriedade de classe oculta o atributo de instância existente (continuando do <>) -==== -[source, pycon] ----- ->>> obj.data # <1> -'bar' ->>> Class.data # <2> -'the class data attr' ->>> Class.data = property(lambda self: 'the "data" prop value') # <3> ->>> obj.data # <4> -'the "data" prop value' ->>> del Class.data # <5> ->>> obj.data # <6> -'bar' ----- -==== -<1> `obj.data` obtém o atributo de instância `data`. -<2> `Class.data` obtém o atributo de classe `data`. -<3> Sobrescreve `Class.data` com uma nova propriedade. -<4> `obj.data` está agora ocultado pela propriedade `Class.data`. -<5> Apaga a propriedade . -<6> `obj.data` agora lê novamente o atributo de instância `data`. - -O ponto principal desta seção é que uma expressão como `obj.data` não começa a busca por `data` em `obj`. A busca na verdade começa em `+obj.__class__+`, e o Python só olha para a instância `obj` se não houver uma propriedade chamada `data` na classe. Isso se aplica a((("overriding descriptors"))) _descritores dominantes_ em geral, dos quais as propriedades são apenas um exemplo. -Mas um tratamento mais profundo de descritores vai ter que aguardar pelo <>. - -Voltemos às propriedades. Toda unidade de código do Python—módulos, funções, classes, métodos—pode conter uma docstring. O próximo tópico mostra como anexar documentação às propriedades. - - -==== Documentação de propriedades - -Quando ferramentas como a função `help()` do console ou IDEs precisam mostrar a documentação de uma propriedade, elas extraem a informação do atributo `+__doc__+` da propriedade. - -Se usada com a sintaxe clássica de invocação, `property` pode receber a string de documentação no argumento `doc`: - -[source, python3] ----- - weight = property(get_weight, set_weight, doc='weight in kilograms') ----- - -A docstring do método _getter_—aquele que recebe o decorador `@property`—é usado como documentação da propriedade toda. O <> mostra telas de ajuda geradas a partir do código no <>. - - -[[help_foo_screens]] -.Capturas de tela do console do Python para os comandos `help(Foo.bar)` e `help(Foo)`. O código-fonte está no <>. -image::images/flpy_2201.png[Screenshots of the Python console] - -[[ex_foo_property_doc]] -.Documentação para uma propriedade -==== -[source, py] ----- -include::code/22-dyn-attr-prop/doc_property.py[tags=DOC_PROPERTY] ----- -==== - -Agora que cobrimos o essencial sobre as propriedades, vamos voltar para a questão de proteger os atributos `weight` e `price` de `LineItem`, para que eles só aceitem valores maiores que zero—mas sem implementar manualmente dois pares de _getters/setters_ praticamente idênticos.((("", startref="DAPpclass22")))((("", startref="proclass22"))) - -[[coding_prop_factory_sec]] -=== Criando uma fábrica de propriedades - -Vamos((("dynamic attributes and properties", "coding property factories", id="DAPfactory22")))((("quantity properties", id="quantprop22"))) programar uma fábrica para criar propriedades `quantity` (_quantidade_)--assim chamadas porque os atributos gerenciados representam quantidades que não podem ser negativas ou zero na aplicação. O <> mostra a aparência cristalina da classe `LineItem` usando duas instâncias de propriedades `quantity`: uma para gerenciar o atributo `weight`, a outra para o `price`. - -[[lineitem_class_v2prop_class]] -.bulkfood_v2prop.py: a fábrica de propriedades `quantity` em ação -==== -[source, py] ----- -include::code/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py[tags=LINEITEM_V2_PROP_CLASS] ----- -==== -<1> Usa a fábrica para definir a primeira propriedade personalizada, `weight`, como um atributo de classe. -<2> Essa segunda chamada cria outra propriedade personalizada, `price`. -<3> Aqui a propriedade já está ativa, assegurando que um peso negativo ou `0` seja rejeitado. -<4> As propriedades também são usadas aqui, para recuperar os valores armazenados na instância. - -Recorde que propriedades são atributos de classe. Ao criar cada propriedade `quantity`, precisamos passar o nome do atributo de `LineItem` que será gerenciado por aquela propriedade específica. Ter que digitar a palavra `weight` duas vezes na linha abaixo é lamentável: - -[source, python3] ----- - weight = quantity('weight') ----- - -Mas evitar tal repetição é complicado, pois a propriedade não tem como saber qual nome de atributo será vinculado a ela. Lembre-se: o lado direito de uma atribuição é avaliado primeiro, então quando `quantity()` é invocada, o atributo de classe `weight` sequer existe. - -[NOTE] -==== -Aperfeiçoar a propriedade `quantity` para que o usuário não precise redigitar o nome do atributo é uma problema não-trivial de metaprogramação. -Um problema que resolveremos no <>. -==== - -O <> apresenta a implementação da fábrica de propriedades `quantity`.footnote:[Esse código foi adaptado da "Recipe 9.21. Avoiding Repetitive Property Methods" (_Receita 9.21. Evitando Métodos Repetitivos de Propriedades_) do https://fpy.li/pycook3[Python Cookbook] (EN), 3ª ed., de David Beazley e Brian K. Jones (O’Reilly).] - -[[lineitem_class_v2prop]] -.bulkfood_v2prop.py: a fábrica de propriedades `quantity` -==== -[source, py] ----- -include::code/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py[tags=LINEITEM_V2_PROP_FACTORY_FUNCTION] ----- -==== -<1> O argumento `storage_name`, onde os dados de cada propriedade são armazenados; para `weight`, o nome do armazenamento será `'weight'`. -<2> O primeiro argumento do `qty_getter` poderia se chamar `self`, mas soaria estranho, pois isso não é o corpo de uma classe; `instance` se refere à instância de `LineItem` onde o atributo será armazenado. -<3> `qty_getter` se refere a `storage_name`, então ele será preservado na clausura desta função; o valor é obtido diretamente de `+instance.__dict__+`, para contornar a propriedade e evitar uma recursão infinita. -<4> `qty_setter` é definido, e também recebe `instance` como primeiro argumento. -<5> O `value` é armazenado diretamente no `+instance.__dict__+`, novamente contornando a propriedade. -<6> Cria e devolve um objeto propriedade personalizado. - -As partes do <> que merecem um estudo mais cuidadoso giram em torno da variável `storage_name`. - -Quando programamos um propriedade da maneira tradicional, o nome do atributo onde um valor será armazenado está definido explicitamente nos métodos _getter_ e _setter_. -Mas aqui as funções `qty_getter` e `qty_setter` são genéricas, e dependem da variável `storage_name` para saber onde ler/escrever o atributo gerenciado no `+__dict__+` da instância. -Cada vez que a fábrica `quantity` é chamada para criar uma propriedade, `storage_name` precisa ser definida com um valor único. - -As funções `qty_getter` e `qty_setter` serão encapsuladas pelo objeto `property`, criado na última linha da função fábrica. Mais tarde, quando forem chamadas para cumprir seus papéis, essas funções lerão a `storage_name` de suas clausuras para determinar de onde ler ou onde escrever os valores dos atributos gerenciados. - -No <>, criei e inspecionei uma instância de `LineItem`, expondo os atributos armazenados. - -[[lineitem_class_v2prop_demo]] -.bulkfood_v2prop.py: explorando propriedades e atributos de armazenamento -==== -[source, pycon] ----- -include::code/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py[tags=LINEITEM_V2_PROP_DEMO] ----- -==== -<1> Lendo o `weight` e o `price` através das propriedades que ocultam os atributos de instância de mesmo nome. -<2> Usando `vars` para inspecionar a instância `nutmeg`: aqui vemos os reais atributos de instância usados para armazenar os valores. - -Observe como as propriedades criadas por nossa fábrica se valem do comportamento descrito na seção <>: a propriedade `weight` se sobrepõe ao atributo de instância `weight`, de forma que qualquer referência a `self.weight` ou `nutmeg.weight` é tratada pelas funções da propriedade, e a única maneira de contornar a lógica da propriedade é acessando diretamente o `+__dict__+`da instância. - -O código no <> pode ser um pouco complicado, mas é conciso: seu tamanho é idêntico ao do par _getter/setter_ decorado que define apenas a propriedade `weight` no <>. A definição de `LineItem` no <> parece muito melhor sem o ruído de _getters_ e _setters_. - -Em um sistema real, o mesmo tipo de validação pode aparecer em muitos campos espalhados por várias classes, e a fábrica `quantity` estaria em um módulo utilitário, para ser usada continuamente. Por fim, aquela fábrica simples poderia ser refatorada em um classe descritora mais extensível, com subclasses especializadas realizando diferentes validações. Faremos isso no <>. - -Vamos agora encerrar a discussão das propriedades com a questão da exclusão de atributos.((("", startref="DAPfactory22")))((("", startref="quantprop22"))) - - -[[attribute_deletion_sec]] -=== Tratando a exclusão de atributos - -Podemos((("dynamic attributes and properties", "handling attribute deletion")))((("attributes", "handling attribute deletion")))((("del statement"))) usar a instrução `del` para excluir não apenas variáveis, mas também atributos: - -[source, pycon] ----- ->>> class Demo: -... pass -... ->>> d = Demo() ->>> d.color = 'green' ->>> d.color -'green' ->>> del d.color ->>> d.color -Traceback (most recent call last): - File "", line 1, in -AttributeError: 'Demo' object has no attribute 'color' ----- - -Na prática, a exclusão de atributos não é algo que se faça todo dia no Python, e a necessidade de lidar com isso no caso de uma propriedade é ainda mais rara. Mas tal operação é suportada, e consigo pensar em um exemplo bobo para demonstrá-la. - -Em uma definição de propriedade, o decorador `@my_property.deleter` encapsula o método responsável por excluir o atributo gerenciado pela propriedade. -Como prometido, o tolo <> foi inspirado pela cena com o Cavaleiro Negro, do filme _Monty Python e o Cálice Sagrado_.footnote:[Aquela cena sangrenta está https://fpy.li/22-17[disponível no Youtube] (EN) quando reviso essa seção, em outubro de 2021.] - -[[ex_black_knight]] -.blackknight.py -==== -[source, py] ----- -include::code/22-dyn-attr-prop/blackknight.py[tags=BLACK_KNIGHT] ----- -==== - -Os doctests em _blackknight.py_ estão no <>. - -[[demo_black_knight]] -.blackknight.py: doctests para <> (o Cavaleiro Negro nunca reconhece a derrota) -==== -[source, pycon] ----- -include::code/22-dyn-attr-prop/blackknight.py[tags=BLACK_KNIGHT_DEMO] ----- -==== - -Usando a sintaxe clássica de invocação em vez de decoradores, o argumento `fdel` configura a função de exclusão. -Por exemplo, a propriedade `member` seria escrita assim no corpo da classe `BlackKnight`: - -[source, python3] ----- - member = property(member_getter, fdel=member_deleter) ----- - -Se você não estiver usando uma propriedade, a exclusão de atributos pode ser tratada implementando o método especial de nível mais baixo `+__delattr__+`, apresentado na seção <>. Programar um classe tola com `+__delattr__+` fica como exercício para a leitora que queira procrastinar. - -Propriedades são recursos poderosos, mas algumas vezes alternativas mais simples ou de nível mais baixo são preferíveis. -Na seção final deste capítulo, vamos revisar algumas das APIs essenciais oferecidas pelo Python para programação de atributos dinâmicos. - - -=== Atributos e funções essenciais para tratamento de atributos - -Por((("dynamic attributes and properties", "essential attributes and functions for attribute handling", id="DAPessential22"))) todo este capítulo, e mesmo antes no livro, usamos algumas das funções embutidas e alguns dos métodos especiais oferecidos pelo Python para lidar com atributos dinâmicos. Esta seção os reúne em um único lugar para uma visão geral, pois sua documentação está espalhada na documentação oficial. - - -==== Atributos especiais que afetam o tratamento de atributos - -O comportamento de muitas das funções e dos métodos especiais elencados nas próximas seções dependem de três atributos especiais: - -`+__class__+`:: Uma((("__class__"))) referência à classe do objeto (isto -é, `+obj.__class__+` é o mesmo que `type(obj)`). O Python procura por métodos especiais tal como -`+__getattr__+` apenas na classe do objeto, e não nas instâncias em si. - -`+__dict__+`:: Um((("__dict__")))((("__slots__"))) mapeamento que armazena os atributos passíveis de escrita de um objeto ou de uma classe. Um objeto que tenha um `+__dict__+` pode ter novos atributos arbitrários definidos a qualquer tempo. Se uma classe tem um atributo `+__slots__+`, então suas instâncias não podem ter um `+__dict__+`. Veja `+__slots__+` (abaixo). - -`+__slots__+`:: Um atributo que pode ser definido em uma classe para economizar memória. -`+__slots__+` é uma `tuple` de strings, nomeando os atributos permitidosfootnote:[Alex Martelli assinala que, apesar de `+__slots__+` poder ser definido como uma `list`, é melhor ser explícito e sempre usar uma `tuple`, pois modificar a lista em `+__slots__+` após o processamento do corpo da classe não tem qualquer efeito. Assim, seria equivocado usar uma sequência mutável ali.]. Se o -nome `+'__dict__'+` não estiver em `+__slots__+`, as instâncias daquela classe então não terão um `+__dict__+` próprio, e apenas os atributos listados em `+__slots__+` serão permitidos naquelas instâncias. -Revise a seção <> para recordar esse tópico. - -[[bif_attribute_handling]] -==== Funções embutidas para tratamento de atributos - -Essas cinco funções embutidas executam leitura, escrita e introspecção de atributos de objetos: - -`dir([object])`:: Lista((("dir([object]) function")))((("functions", "dir([object]) function"))) a maioria dis atributos de um objeto. A https://docs.python.org/pt-br/3/library/functions.html#dir[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. Vários atributos especiais de classes, tais como -`+__mro__+`, `+__bases__+` e `+__name__+`, também não são exibidos por `dir`. Você pode personalziar a saída de `dir` implementando o método especial `+__dir__+`, como vimos no <>. Se o argumento opcional `object` não for passado, `dir` lista os nomes no escopo corrente. - -`getattr(object, name[, default])`:: Devolve((("functions", "getattr(object, name[, default]) function")))((("getattr(object, name[, default]) function"))) o atributo do `object` identificado pela string `name`. O principal caso de uso é obter atributos (ou métodos) cujos nomes não sabemos de antemão. Essa função pode recuperar um atributo da classe do objeto ou de uma superclasse. Se tal atributo não existir, `getattr` gera uma `AttributeError` ou devolve o valor `default`, se ele for passado. -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." - -`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. - -`vars([object])`:: Devolve((("vars([object]) function")))((("functions", "setattr(object, name, value) function"))) o `+__dict__+` de `object`; `vars` não funciona com instâncias de classes que definem `+__slots__+` e não têm um `+__dict__+` (compare com `dir`, que aceita essas instâncias). Sem argumentos, `vars()` faz o mesmo que `locals()`: devolve um `dict` representando o escopo local. - -[[special_methods_for_attr_sec]] -==== Métodos especiais para tratamento de atributos - -Quando implementados em uma classe definida pelo usuário, os métodos especiais listados abaixo controlam a recuperação, a atualização, a exclusão e a listagem de atributos. - -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: - -[quote] -____ -Para classes personalizadas, as invocações implícitas de métodos especiais só têm garantia de funcionar corretamente se definidas em um tipo de objeto, não no dicionário de instância do objeto. -____ - -Em outras palavras, assuma que os métodos especiais serão acessados na própria classe, mesmo quando o alvo da ação é uma instância. Por essa razão, métodos especiais não são ocultados por atributos de instância de mesmo nome. - -Nos exemplos a seguir, assuma que há uma classe chamada `Class`, que `obj` é uma instância de `Class`, e que `attr` é um atributo de `obj`. - -Para cada um destes métodos especiais, não importa se o acesso ao atributo é feito usando a notação de ponto ou uma das funções embutidas listadas acima, em <>. Por exemplo, tanto `obj.attr` quanto `getattr(obj, 'attr', 42)` disparam `+Class.__getattribute__(obj, 'attr')+`. - -`+__delattr__(self, name)+`:: É((("__delattr__"))) sempre invocado quando ocorre uma tentativa de excluir um atributo usando a instrução `del`; por exemplo, `del obj.attr` dispara `+Class.__delattr__(obj, 'attr')+`. -Se `attr` for uma propriedade, seu método de exclusão nunca será invocado se a classe implementar -`+__delattr__+`. - -`+__dir__(self)+`:: Chamado((("__dir__"))) quando `dir` é invocado sobre um objeto, para fornecer uma lista de atributos; por exemplo, `dir(obj)` dispara -`+Class.__dir__(obj)+`. Também usado pelo recurso de auto-completar em todos os consoles modernos do Python. - -`+__getattr__(self, name)+`:: Chamado((("__getattr__"))) apenas quando uma tentativa de obter o atributo nomeado falha, após `obj`, `Class` e suas superclasses serem pesquisadas. As expressões `+obj.no_such_attr+`, `getattr(obj, 'no_such_attr')` e `hasattr(obj, 'no_such_attr')` podem disparar `+Class.__getattr__(obj, 'no_such_attr')+`, mas apenas se um atributo com aquele nome não for encontrado em `obj` ou em `Class` e suas superclasses. - -`+__getattribute__(self, name)+`:: Sempre((("__getattribute__"))) chamado quando há uma tentativa de obter o atributo nomeado diretamente a partir de código Python (o interpretador pode ignorar isso em alguns casos, por exemplo para obter o método `+__repr__+`). A notação de ponto e as funções embutidas `getattr` e `hasattr` disparam esse método. `+__getattr__+` só é invocado após `+__getattribute__+`, e apenas quando `+__getattribute__+` gera uma `AttributeError`. Para acessar atributos da instância `obj` sem entrar em uma recursão infinita, implementações de `+__getattribute__+` devem usar `+super().__getattribute__(obj, name)+`. - -`+__setattr__(self, name, value)+`:: Sempre((("__setattr__"))) chamado quando há uma tentativa de atribuir um valor ao atributo nomeado. A notação de ponto e a função embutida `setattr` disparam esse método; por exemplo, tanto `obj.attr = 42` quanto pass:[setattr(obj, 'attr', 42)] disparam -`+Class.__setattr__(obj, 'attr', 42)+`. - -[WARNING] -==== -Na prática, como são chamados incondicionalmene e afetam praticamente todos os acessos a atributos, os métodos especiais `+__getattribute__+` e `+__setattr__+` são mais difíceis de usar corretamente que `+__getattr__+`, que só lida com nome de atributos não-existentes. Usar propriedades ou descritores tende a causar menos erros que definir esses métodos especiais. -==== - -Isso conclui nosso mergulho nas propriedades, nos métodos especiais e nas outras técnicas de programação de atributos dinâmicos.((("", startref="DAPessential22"))) - - -=== Resumo do capítulo - -Começamos((("dynamic attributes and properties", "overview of"))) nossa discussão dos atributos dinâmicos mostrando exemplos práticos de classes simples, que tornavam mais fácil processar um conjunto de dados JSON. O primeiro exemplo foi a classe `FrozenJSON`, que converte listas e dicts aninhados em instâncias aninhadas de `FrozenJSON`, e em listas de instâncias da mesma classe. O código de `FrozenJSON` demonstrou o uso do método especial `+__getattr__+` para converter estruturas de dados em tempo real, sempre que seus atributos eram lidos. A última versão de `FrozenJSON` mostrou o uso do método construtor `+__new__+` para transformar uma classe em uma fábrica flexível de objetos, não restrita a instâncias de si mesma. - -Convertemos então o conjunto de dados JSON em um `dict` que armazena instâncias da classe `Record`. -A primeira versão de `Record` tinha apenas algumas linhas e introduziu o dialeto do "punhado" ("bunch"): usar `+self.__dict__.update(**kwargs)+` para criar atributos arbitrários a partir de argumentos nomeados passados para `+__init__+`. -A segunda passagem acrescentou a classe `Event`, implementando a recuperação automática de registros relacionados através de propriedades. -Valores calculados de propriedades algumas vezes exigem _caching_, e falamos de algumas formas de fazer isso. -Após descobrir que `@functools.cached_property` não é sempre aplicável, aprendemos sobre uma alternativa: a combinação de `@property` acima de `@functools.cache`, nessa ordem. - -A discussão sobre propriedades continuou com a classe `LineItem`, onde uma propriedade foi criada para proteger um atributo `weight` de receber valores negativos ou zero, que não fazem sentido em termos do negócio. Após um aprofundamento da sintaxe e da semântica das propriedades, criamos uma fábrica de propriedades para aplicar a mesma validação a `weight` e a `price`, sem precisar escrever múltiplos _getters_ e _setters_. A fábrica de propriedades se apoiou em conceitos sutis—tais como clausuras e a sobreposição de atributos de instância por propriedades--para fornecer um solução genérica elegante, usando para isso o mesmo número de linhas que usamos antes para escrever manualmente a definição de uma única propriedade. - -Por fim, demos uma rápida passada pelo tratamento da exclusão de atributos com propriedades, seguida por um resumo dos principais atributos especiais, funções embutidas e métodos especiais que suportam a metaprogramação de atributos no núcleo da linguagem Python. - - -=== 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 do 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 do Python_, "Tipos embutidos", https://docs.python.org/pt-br/3/library/stdtypes.html#special-attributes["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 sobrepor 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 seção <> 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 do 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, C++, 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. - - -[role="pagebreak-before less_space"] -[[properties_soapbox]] -.Ponto de Vista -**** - -O Princípio de Acesso Uniforme de Meyer((("Soapbox sidebars", "Uniform Access Principle", id="Suaccess22")))((("Uniform Access Principle", id="uaccess22")))((("Meyer's Uniform Access Principle", id="meyerua22")))((("dynamic attributes and properties", "Soapbox discussion", id="DAPsoap22"))) é esteticamente atraente. Como um programador usando uma API, eu não deveria ter de me preocupar se `product.price` simplesmente lê um atributo de dados ou executa uma computação. Como um consumidor e um cidadão, eu me importo: no comércio online atual, o valor de `product.price` muitas vezes depende de quem está perguntando, então ele certamente não é um mero atributo de dados. Na verdade, é uma prática comum apresentar um preço mais baixo se a consulta vem de fora da loja—por exemplo, de um mecanismo de comparação de preços. Isso efetivamente pune os fregueses fiéis, que gostam de navegar dentro de uma loja específica. Mas estou divagando. - -A digressão anterior toca um ponto relevante para programação: apesar do Princípio de Acesso Uniforme fazer todo sentido em um mundo ideal, na realidade os usuários de uma API podem precisar saber se ler `product.price` é potencialmente dispendioso ou demorado demais. Isso é um problema com abstrações de programaçõa em geral: elas tornam difícil raciocinar sobre o custo da avaliação de uma expressão durante a execução. Por outro lado, abstrações permitem aos usuários fazerem mais com menos código. É uma negociação. Como de hábito em questões de engenharia de software, o https://fpy.li/22-26[wiki original] (EN) de Ward Cunningham contém argumentos perspicazes sobre os méritos do https://fpy.li/22-27[Princípio de Acesso Uniforme] (EN). - -Em linguagens de programação orientadas a objetos, a aplicação ou violação do Princípio de Acesso Uniforme muitas vezes gira em torno da sintaxe de leitura de atributos de dados públicos versus a invocação de métodos _getter/setter_. - -Smalltalk e Ruby resolvem essa questão de uma forma simples e elegante: elas não suportam nenhuma forma de atributos de dados públicos. Todo atributo de instância nessas linguagens é privado, então qualquer acesso a eles deve passar por métodos. Mas sua sintaxe torna isso indolor: em Ruby, `product.price` invoca o _getter_ de `price`; em Smalltalk, ele é simplesmente `product price`. - -Na outra ponta do espectro, a linguagem Java permite ao programador escolher entre quatro modificadores de nível de acesso—incluindo o default sem nome que o https://fpy.li/22-28[Tutorial do Java] (EN) chama de "_package-private_". - -A prática geral, entretanto, não concorda com a sintaxe definida pelos projetistas do Java. Todos no campo do Java concordam que atributos devem ser `private`, e é necessário escrever isso explicitamente todas as vezes, porque não é o default. Quando todos os atributos são privados, todo acesso a eles de fora da classe precisa passar por métodos de acesso. Os IDEs de Java incluem atalhos para gerar métodos de acesso automaticamente. Infelizmente, o IDE não ajuda quando você precisa ler aquele código seis meses depois. É problema seu navegar por um oceano de métodos de acesso que não fazem nada, para encontrar aqueles que adicionam valor, implementando alguma lógica do negócio. - -Alex Martelli fala pela maioria da comunidade Python quando chama métodos de acesso de "idiomas patéticos", e então apresenta esse exemplos que parecem muito diferentes, mas fazem a mesma coisa: footnote:[Alex Martelli, _Python in a Nutshell_, 2ª ed. (O'Reilly), p. 101.] - -[source, python3] ----- -someInstance.widgetCounter += 1 -# rather than... -someInstance.setWidgetCounter(someInstance.getWidgetCounter() + 1) ----- - -Algumas vezes, ao projetar uma API, me pergunto se todo método que não recebe qualquer argumento (além de `self`), devolve um valor (diferente de `None`) e é uma função pura (isto é, não tem efeitos colaterais) não deveria ser substituído por uma propriedade somente de leitura. Nesse capítulo, o método `LineItem.subtotal` (no <>) seria um bom candidato a se tornar uma propriedade somente para leitura. Claro, isso exclui métodos projetados para modificar o objeto, tal como `my_list.clear()`. Seria uma péssima ideia transformar isso em uma propriedade, de tal forma que o mero acesso a `my_list.clear` apagaria o conteúdo da lista! - -Na biblioteca GPIO https://fpy.li/22-29[_Pingo_] (mencionada na seção <>), da qual sou co-autor, grande parte da API do usuário está baseada em propriedades. Por exemplo, para ler o valor atual de uma porta analógica, o usuário escreve `pin.value`, e definir o modo de uma porta digital é escrito `pin.mode = OUT`. Por trás da cortina, ler o valor de uma porta analógica ou definir o modo de uma porta digital pode implicar em bastante código, dependendo do driver específico da placa. Decidimos usar propriedades no Pingo porque queríamos que a API fosse confortável de usar até mesmo em ambientes interativos como um Jupyter Notebook, e achamos que `pin.mode = OUT` é mais fácil para os olhos e para os dedos que `pin.set_mode(OUT)`. - -Apesar de achar a solução do Smalltalk e do Ruby mais limpa, acho que a abordagem do Python faz mais sentido que a do Java. Podemos começar simples, programando elementos de dados como atributos públicos, pois sabemos que eles sempre podem ser encapsulados por propriedades (ou descritores, dos quais falaremos no próximo capítulo).((("", startref="meyerua22")))((("", startref="uaccess22")))((("", startref="Suaccess22"))) - -[role="soapbox-title"] -pass:[+__new__+] é melhor que new - -Outro exemplo((("function-class duality")))((("Soapbox sidebars", "function-class duality"))) do Princípio de Acesso Uniforme (ou uma variante dele) é o fato de chamadas de função e instanciação de objetos usarem a mesma sintaxe no Python: `my_obj = foo()`, onde `foo` pode ser uma classe ou qualquer outro invocável. - -Outras linguagens, influenciadas pela sintaxe do C++, tem um operador `new` que faz a instanciação parecer diferente de uma invocação. Na maior parte do tempo, o usuário de uma API não se importa se `foo` é uma função ou uma classe. Por anos tive a impressão que `property` era uma função. Para o uso normal, não faz diferença. - -Há muitas boas razões para substituir construtores por fábricas. footnote:[As razões que menciono foram apresentadas no artigo intitulado https://fpy.li/22-30["Java's new Considered Harmful" (_new do Java considerado nocivo_)], de Jonathan Amsterdam, publicado na Dr. Dobbs Journal, e no "Consider static factory methods instead of constructors" (_Considere substituir construtores por métodos estáticos de fábrica_), que é o Item 1 do premiado livro _Effective Java_, 3ª ed., de Joshua Bloch (Addison-Wesley).] Um motivo popular é limitar o número de instâncias, devolvendo objetos construídps anterioremente (como no padrão de projeto Singleton). Um uso relacionado é fazer _caching_ de uma construção de objeto dispendiosa. Além disso, às vezes é conveniente devolver objetos de tipos diferentes, dependendo dos argumentos passados. - -Programar um construtor é simples; fornecer uma fábrica aumenta a flexibilidade às custas de mais código. Em linguagens com um operador `new`, o projetista de uma API precisa decidir a priori se vai se ater a um construtor simples ou investir em uma fábrica. Se a escolha inicial estiver errada, a correção pode ser cara—tudo porque `new` é um operador. - -Algumas vezes pode ser conveniente pegar o caminho inverso, e substituir uma função simples por uma classe. - -Em Python, classes e funções são muitas vezes intercambiáveis. Não apenas pela ausência de um operador `new`, mas também porque existe o método especial `+__new__+`, que pode transformar uma classe em uma fábrica que produz tipos diferentes de objetos (como vimos na seção <>) ou devolver instâncias pré-fabricadas em vez de criar uma nova todas as vezes. - -Essa dualidade função-classe seria mais fácil de aproveitar se a https://fpy.li/22-31[PEP 8 -- Style Guide for Python Code (_Guia de Estilo para Código Python_)] não recomendasse `CamelCase` para nomes de classe. Por outro lado, dezenas de classes na biblioteca padrão tem nomes apenas em minúsculas (por exemplo, `property`, `str`, `defaultdict`, etc.). Daí que talvez o uso de nomes de classe apenas com minúsculas seja um recurso, e não um bug. Mas independente de como olhemos, essa inconsistência no uso de maiúsculas e minúsculas nos nomes de classes na biblioteca padrão do Python nos coloca um problema de usabilidade. - -Apesar da invocação de uma função não ser diferente da invocação de uma classe, é bom saber qual é qual, por causa de outra coisa que podemos fazer com uma classe: criar uma subclasse. Então eu, pessoalmente, uso `CamelCase` em todas as classes que escrevo, e gostaria que todas as classea na biblioteca padrão do Python seguissem a mesma convenção. Estou olhando para vocês, `collections.OrderedDict` e `collections.defaultdict`.((("", startref="DAPsoap22"))) -**** diff --git a/capitulos/cap23.adoc b/capitulos/cap23.adoc deleted file mode 100644 index 4c9ad05c..00000000 --- a/capitulos/cap23.adoc +++ /dev/null @@ -1,667 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte - -[[attribute_descriptors]] -== Descritores de Atributos - -[quote, Raymond Hettinger, guru do Python e um de seus desenvolvedores principais] -____ -Aprender sobre descritores não apenas dá acesso a um conjunto maior de ferramentas, cria também uma maior compreensão sobre o funcionamento do 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"].] -____ - -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_), tais 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 do Python, e vice-versa. - -Um((("__get__")))((("__set__")))((("__delete__"))) descritor é uma classe que implementa um protocolo dinâmico, composto pelos métodos `+__get__+`, `+__set__+`, e `+__delete__+`. A classe `property` implementa o protocolo descritor completo. Como habitual em protocolos dinâmicos, implementações parciais são aceitáveis. E, na verdade, a maioria dos descritores que vemos em código real implementam apenas -`+__get__+` e `+__set__+`, e muitos implementam apenas um destes métodos. - -Descritores são um recurso característico do Python, presentes não apenas no nível das aplicações mas também na infraestrutura da linguagem. -Funções definidas pelo usuário são descritores. Veremos como o protocolo descritor permite que métodos operem como métodos vinculados ou desvinculados, dependendo de como são invocados. - -Entender os descritores é crucial para dominar o Python. Esse capítulo é sobre isso. - -Nas próximas páginas((("attribute descriptors", "topics covered"))) vamos refatorar o exemplo da loja de comida orgânica a granel, visto na seção <>, substituindo propriedades por descritores. -Isso tornará mais fácil reutilizar a lógica de validação de atributos em diferentes classes. - -Vamos estudar os conceitos de descritores dominantes e não dominantes, e entender que as funções do Python são descritores. -Para finalizar, veremos algumas dicas para a implementação de descritores. - - -[[whats_new_descriptor_sec]] -=== Novidades nesse capítulo - -O((("attribute descriptors", "significant changes to"))) exemplo do descritor `Quantity`, na seção <>, foi dramaticamente simplificado, graças ao método especial `+__set_name__+`, adicionado ao protocolo descritor no Python 3.6. Nessa mesma seção, removi o exemplo da fábrica de propriedades, pois ele se tornou irrelevante: o ponto ali era mostrar uma solução alternativa para o problema de `Quantity`, mas com `+__set_name__+` a solução com o descritor se tornou muito mais simples. - -A classe `AutoStorage`, que aparecia na seção <>, também foi removida, pois o mesmo `+__set_name__+` a tornou obsoleta. - -[[validating_descriptor_sec]] -=== Exemplo de descritor: validação de atributos - -Como((("attribute descriptors", "attribute validation", id="ADvalidation23")))((("attributes", "using attribute descriptors for validation", id="ATvalidation23"))) vimos na seção <>, uma fábrica de propriedades é uma maneira de evitar código repetitivo de _getters_ e _setters_, aplicando padrões de programação funcional. -Um fábrica de propriedades é uma função de ordem superior que cria um conjunto de funções de acesso parametrizadas e constrói uma instância de propriedade personalizada, com clausuras para manter configurações como `storage_name`. -A forma orientada a objetos de resolver o mesmo problema é uma classe descritora. - -Vamos seguir com a série de exemplos `LineItem` de onde paramos, na seção <>, refatorando a fábrica de propriedades `quantity` em uma classe descritora `Quantity`. -Isso vai torná-la mais fácil de usar. - -==== LineItem versão #3: Um descritor simples - -Como dito na introdução, uma classe que implemente um método `+__get__+`, um `+__set__+` ou um -`+__delete__+` é um descritor. Podemos usar um descritor declarando instâncias dele como atributos de classe em outra classe. - - -Vamos criar um descritor `Quantity`, e a classe `LineItem` vai usar duas instâncias de `Quantity`: uma para gerenciar o atributo `weight`, a outra para `price`. Um((("UML class diagrams", "managed and descriptor classes"))) diagrama ajuda: dê uma olhada na <>. - -[[lineitem3_uml]] -.Diagrama de classe UML para `LineItem` usando uma classe descritora chamada `Quantity`. Atributos sublinhados no UML são atributos de classe. Observe que `weight` e `price` são instâncias de `Quantity` na classe `LineItem`, mas instâncias de `LineItem` também têm seus próprios atributos `weight` e `price`, onde esses valores são armazenados. -image::images/flpy_2301.png[Diagrama de classes UML para `Quantity` e `LineItem`] - -Note que a palavra `weight` aparece duas vezes na <>, pois na verdade há dois atributos diferentes chamados `weight`: um é um atributo de classe de `LineItem`, o outro é um atributo de instância que existirá em cada objeto `LineItem`. O mesmo se aplica a `price`. - -===== Termos para entender descritores - -Implementar((("attribute descriptors", "relevant terminology"))) e usar descritores envolve vários componentes, então é útil ser preciso ao nomeá-los. -Vou utilizar termos e definições abaixo nas descrições dos exemplos desse capítulo. -Vai ser mais fácil entendê-los após ver o código, mas quis colocar todas as definições no início, para você poder voltar a elas quando necessário. - -Classe descritora:: Uma((("descriptor classes"))) classe que implementa o protocolo descritor. Por exemplo, `Quantity` na <>. - -Classe gerenciada:: A((("managed classes"))) classe onde as instâncias do descritor são declaradas, como atributos de classe. Na <>, `LineItem` é a classe gerenciada. - -Instância do descritor:: Cada((("descriptor instances"))) instância de uma classe descritora, declarada como um atributo de classe da classe gerenciada. Na <>, cada instância do descritor está representada pela seta de composição com um nome sublinhado (na UML, o sublinhado indica um atributo de classe). Os diamantes pretos tocam a classe `LineItem`, que contém as instâncias do descritor. - -Instância gerenciada:: Uma((("managed instances"))) instância da classe gerenciada. Nesse exemplo, instâncias de `LineItem` são as instâncias gerenciadas (elas não aparecem no diagrama de classe). - -Atributo de armazenamento:: Um((("storage attributes"))) atributo da instância gerenciada que mantém o valor de um atributo gerenciado para aquela instância específica. Na <>, os atributos de instância `weight` e `price` de `LineItem` são atributos de armazenamento. Eles são diferentes das instâncias do descritor, que são sempre atributos de classe. - -Atributos gerenciados:: Um((("managed attributes"))) atributo público na classe gerenciada que é controlado por uma instância do descritor, com os valores mantidos em atributos de armazenamento. Em outras palavras, uma instância do descritor e um atributo de armazenamento fornecem a infraestrutura para um atributo gerenciado. - -É importante entender que instâncias de `Quantity` são atributos de classe de `LineItem`. Este((("UML class diagrams", "annotated with MGN"))) ponto fundamental é realçado pelas "engenhocas" (_mills_) e bugigangas (_gizmos_) na <>. - -[[lineitem3_uml_mgn]] -.Diagrama de classe UML anotado com MGN (Mills & Gizmos Notation - Notação de Engenhocas e Bugigangas): classes são engenhocas que produzem bugigangas—as instâncias. A engenhoca `Quantity` produz duas bugigangas de cabeça redonda, que são anexadas à engenhoca `LineItem`: `weight` e `price`. A engenhoca `LineItem` produz bugigangas retangulares que tem seus próprios atributos `weight` e `price`, onde aqueles valores são armazenados. -image::images/flpy_2302.png[Diagrama de classe UML+MGN para `Quantity` e `LineItem`] - -[role="pagebreak-before less_space"] -[[mgn_box1]] -.Introduzindo a notação Engenhocas & Bugigangas (_Mills & Gizmos_) -**** -Após((("Mills & Gizmos Notation (MGN)"))) explicar descritores várias vezes, percebi que a UML não é muito boa para mostrar as relações entre classes e instâncias, tal como a relação entre uma classe gerenciada e as instâncias do descritor.footnote:[Classes e instâncias são representadas por retângulos em diagramas de classe UML. Há diferenças visuais, mas instâncias raramente aparecem em diagramas de classe, entao desenvolvedores podem não reconhecê-las como tal.] Daí inventei minha própria "linguagem", a Notação Engenhocas e Bugigangas (MGN), que uso para anotar diagramas UML. - -A MGN é projetada para tornar bastante clara a diferença entre classes e instâncias. Veja a <>. Na MGN, uma classe aparece como uma "engenhoca", uma máquina complexa que produz bugigangas. Classes/engenhocas são sempre máquina com alavancas e mostradores. As bugigangas são as instâncias, e elas têm uma aparência bem mais simples. Quando este livro é gerado em cores, as bugigangas tem a mesma cor da engenhoca que as produziu. - -[[mgn_diagram_demo]] -.Esboço MGN mostrando a classe `LineItem` produzindo três instâncias, e `Quantity` produzindo duas. Uma instância de `Quantity` está recuperando um valor armazenado em uma instância de `LineItem`. -image::images/flpy_2303.png[Esboço MGN para `LineItem` e `Quantity`] - -Para este exemplo, desenhei instâncias de `LineItem` como linhas em uma fatura tabular, com três células representando os três atributos (`description`, `weight` e `price`). Como as instâncias de `Quantity` são descritores, eles tem uma lente de aumento para `+__get__+` (_obter_) os valores, e uma garra para `+__set__+` (_definir_) os valores. Quando chegarmos às metaclasses, você me agradecerá por esses desenhos. -**** - -Mas chega de rabiscos por enquanto. Aqui está o código: o <> mostra a classe descritora `Quantity`, e o <> lista a nova classe `LineItem` usando duas instâncias de `Quantity`. - -[[quantity_v3]] -.bulkfood_v3.py: o descritor `Quantity` não aceita valores negativos -==== -[source, py] ----- -include::code/23-descriptor/bulkfood/bulkfood_v3.py[tags=LINEITEM_QUANTITY_V3] ----- -==== -<1> O descritor é um recurso baseado em protocolo: não é necessário criar uma subclasse para implementá-lo. -<2> Cada instância de `Quantity` terá um atributo `storage_name`: é o nome do atributo de armazenamento que vai manter o valar nas instâncias gerenciadas. -<3> O `+__set__+` é chamado quando ocorre uma tentativa de atribuir um valor a um atributo gerenciado. Aqui, `self` é a instância do descritor (isto é, `LineItem.weight` ou `LineItem.price`), `instance` é a instância gerenciada (uma instância de `LineItem`) e `value` é o valor que está sendo atribuído. -<4> Precisamos armazenar o valor do atributo diretamente no `+__dict__+`; chamar pass:[set​attr​(instance, self.storage_name)] dispararia novamente o método `+__set__+`, levando a uma recursão infinita. -<5> Precisamos implementar `+__get__+`, pois o nome do atributo gerenciado pode não ser igual ao `storage_name`. O argumento `owner` será explicado a seguir. - -Implementar `+__get__+` é necessário porque um usuário poderia escrever algo assim: - -[source, py] ----- -class House: - rooms = Quantity('number_of_rooms') ----- - -Na classe `House`, o atributo gerenciado é `rooms`, mas o atributo de armazenamento é `number_of_rooms`. -Dada uma instância de `House` chamada `chaos_manor`, acessar e modificar `chaos_manor.rooms` passa pela instância do descritor `Quantity` ligada a `rooms`, mas acessar e modificar -`chaos_manor.number_of_rooms` escapa ao descritor. - -Observe que `+__get__+` recebe três argumentos: `self`, `instance` e `owner`. O argumento `owner` é uma referência à classe gerenciada (por exemplo, `LineItem`), e é útil se você quiser que o descritor suporte o acesso a um atributo de classe—talvez para emular o comportamento default do Python, de procurar um atributo de classe quando o nome não é encontrado na instância. - -Se um atributo gerenciado, tal como `weight`, é acessado através da classe como pass:[Line​Item.weight], o método `+__get__+` do descritor recebe `None` como valor do argumento `instance`. - -Para suportar introspecção e outras técnicas de metaprogramação pelo usuário, é uma boa prática fazer `+__get__+` devolver a instância do descritor quando o atributo gerenciado é acessado através da classe. Para fazer isso, escreveríamos `+__get__+` assim: - -[source, py] ----- - def __get__(self, instance, owner): - if instance is None: - return self - else: - return instance.__dict__[self.storage_name] ----- - -O <> demonstra o uso de `Quantity` em `LineItem`. - -[[lineitem_class_v3]] -.bulkfood_v3.py: descritores `Quantity` gerenciam atributos em `LineItem` -==== -[source, py] ----- -include::code/23-descriptor/bulkfood/bulkfood_v3.py[tags=LINEITEM_V3] ----- -==== -<1> A primeira instância do descritor vai gerenciar o atributo `weight`. -<2> A segunda instância do descritor vai gerenciar o atributo `price`. -<3> O restante do corpo da classe é tão simples e limpo como o código orginal em _bulkfood_v1.py_ (no <>). - -O código no <> funciona como esperado, evitando a venda de trufas por $0:footnote:[O quilo de trufas brancas custa milhares de reais. Impedir a venda de trufas por $0,01 fica como exercício para a leitora com espírito de aventura. Conheço um caso real, de uma pessoa que comprou uma enciclopédia de estatísticas de 1.800 dólares por 18 dólares, devido a um erro em uma loja online(neste caso não foi na _Amazon.com_).] - -[source, pycon] ----- ->>> truffle = LineItem('White truffle', 100, 0) -Traceback (most recent call last): - ... -ValueError: value must be > 0 ----- - -[WARNING] -==== -Ao programar os métodos `+__get__+` e `+__set__+` de um descritor, tenha em mente o significado dos argumentos `self` e `instance`: `self` é a instância do descritor, `instance` é a instância gerenciada. Descritores que gerenciam atributos de instância devem armazenar os valores nas instâncias gerenciadas. É por isso que o Python fornece o argumento `instance` aos métodos do descritor. -==== - -Pode ser tentador, mas é um erro, armazenar o valor de cada atributo gerenciado na própria instância do descritor. Em outras palavras, em vez de escrever o método `+__set__+` assim: - -[source, python3] ----- - instance.__dict__[self.storage_name] = value ----- - -escrever a alternativa tentadora mas ruim, assim: - -[source, python3] ----- - self.__dict__[self.storage_name] = value ----- - -Para entender porque isso está errado, pense no significado dos dois primeiros argumentos passados a `+__set__+`: `self` e `instance`. Aqui, `self` é a instância do descritor, que na verdade é um atributo de classe da classe gerenciada. Você pode ter milhares de instâncias de `LineItem` na memória em um dado momento, mas terá apenas duas instâncias dos descritores: os atributos de classe `LineItem.weight` e `LineItem.price`. Então, qualquer coisa armazenada nas próprias instâncias do descritor é na verdade parte de um atributo de classe de `LineItem`, e portanto é compartilhada por todas as instâncias de `LineItem`. - -Um inconveniente do <> é a necessidade de repetir os nomes dos atributos quando os descritores são instanciados no corpo da classe gerenciada. Seria bom se a classe `LineItem` pudesse ser declarada assim: - -[source, python3] ----- -class LineItem: - weight = Quantity() - price = Quantity() - - # o restante dos métodos permanece igual ----- - -Da forma como está escrito, o <> exige nomear explicitamente cada `Quantity`, algo não apenas inconveniente, mas também perigoso. Se um programador, ao copiar e colar código, se esquecer de editar os dois nomes, e terminar com uma linha como `price = Quantity('weight')`, o programa vai se comportar de forma muito errática, sobrescrevendo o valor de `weight` sempre que `price` for definido. - -O problema é que—como vimos no <>—o lado direito de uma atribuição é executado antes da variável existir. A expressão `Quantity()` é avaliada para criar uma instância do descritor, e não há como o código na classe `Quantity` adivinhar o nome da variável à qual o descritor será vinculado (por exemplo, `weight` ou `price`). - -Felizmente, o protocolo descritor agora suporta o muito bem batizado método `+__set_name__+`. Veremos a seguir como usá-lo. - -[NOTE] -==== -Nomear automaticamente o atributo de armazenamendo de um descritor contumava ser uma tarefa espinhosa. Na primeira edição do _Python Fluente_, dediquei várias páginas e muitas linhas de código neste capítulo e no seguinte para apresentar diferentes soluções, incluindo o uso de um decorador de classe e depois metaclasses (no <>). -Tudo isso ficou muito mais simples no Python 3.6. -==== - -[[auto_storage_sec]] -==== LineItem versão #4: Nomeando atributos de armazenamento automaticamente - -Para evitar a redigitação do nome do atributo em instâncias do descritor, vamos implementar -`+__set_name__+`, para definir o `storage_name` de cada instância de `Quantity`. O método especial -`+__set_name__+` foi acrescentado ao protocolo descritor no Python 3.6. - -O interpretador invoca `+__set_name__+` em cada descritor encontrado no corpo de uma `class`—se o descritor implementar esse método.footnote:[Mais precisamente, `+__set_name__+` é invocado por `+type.__new__+`—o construtor de objetos que representam classes. A classe embutida `type` é na verdade uma metaclasse, a classe default de classes definidas pelo usuário. Isso é um pouco difícil de entender de início, mas fique tranquila: o <> é dedicado à configuração dinâmica de classes, incluindo o conceito de metaclasses.] - -No <>, a classe descritora `Quantity` não precisa de um `+__init__+`. -Em vez disso, `+__set_item__+` armazena o nome do atributo de armazenamento. - -[[lineitem_class_v4]] -.bulkfood_v4.py: `+__set_name__+` define o nome para cada instância do descritor `Quantity` -==== -[source, py] ----- -include::code/23-descriptor/bulkfood/bulkfood_v4.py[tags=LINEITEM_V4] ----- -==== -<1> `self` é a instância do descritor (não a instância gerenciada), `owner` é a classe gerenciada e `name` é o nome do atributo de `owner` ao qual essa instância do descritor foi atrbuída no corpo da classe de `owner`. -<2> Isso é o que o `+__init__+` fazia no <>. -<3> O método `+__set__+` aqui é exatamente igual ao do <>. -<4> Não é necessário implementar `+__get__+`, porque o nome do atributo de armazenamento é igual ao nome do atributo gerenciado. A expressão `product.price` obtém o atributo `price` diretamente da instância de `LineItem`. -<5> Não é necessário passar o nome do atributo gerenciado para o construtor de `Quantity`. Esse era o objetivo dessa versão. - -Olhando para o <>, pode parecer muito código apenas para gerenciar um par de atributos, mas é importante perceber que a lógica do descritor foi agora abstraida em uma unidade de código diferente: a classe `Quantity`. -Nós normalmente sequer definimos um descritor no mesmo módulo em que ele é usado, mas em um módulo utilitário separado, projetado para ser usado por toda a aplicação—ou mesmo por muitas aplicações, se estivermos desenvolvendo uma bliblioteca ou uma framework. - -Tendo isso em mente, o <> representa melhor o uso típico de um descritor. - -[[lineitem_class_v4c]] -.bulkfood_v4c.py: uma definição mais limpa de `LineItem`; a classe descritora `Quantity` agora reside no módulo importado `model_v4c` -==== -[source, py] ----- -include::code/23-descriptor/bulkfood/bulkfood_v4c.py[tags=LINEITEM_V4C] ----- -==== -<1> Importa o módulo `model_v4c`, onde `Quantity` é implementada. -<2> Coloca `model.Quantity` em uso. - -Usuários do Django vão perceber que o <> se parece muito com uma definição de modelo. Isso não é uma coincidência: os campos de modelos Django são descritores. - -Já que descritores são implementado como classes, podemos aproveitar a herança para reutilizar parte do código que já temos em novos descritores. É o que faremos na próxima seção. - -[[new_descr_type_sec]] -==== LineItem versão #5: um novo tipo descritor - -A loja imaginária de comida orgânica encontra um obstáculo: de alguma forma, uma instância de um produto foi criada com uma descrição vazia, e o pedido não pode ser processado. Para prevenir isso, criaremos um novo descritor: `NonBlank`. Ao projetar `NonBlank`, percebemos que ele será muito parecido com o descritor `Quantity`, exceto pela lógica de validação. - -Isso leva a uma refatoração, resultando em `Validated`, uma classe abstrata que sobrepõe um método -`+__set__+`, invocando o método `validate`, que precisa ser implementado por subclasses. - -Vamos então reescrever `Quantity` e implementar `NonBlank`, herdando de `Validated` e programando apenas os métodos `validate`. - -A relação entre `Validated`, `Quantity` e `NonBlank` é uma aplicação do _método modelo_ ("template method"), como descrito no clássico _Design Patterns_: - -[quote] -____ -Um método modelo define um algoritimo em termos de operações abstratas que subclasses sobrepõe para fornecer o comportamento concreto.footnote:[Gamma et al., _Design Patterns: Elements of Reusable Object-Oriented Software_, p. 326. (_Padrões de Projetos: Soluções Reutilizáveis de Software Orientados a Objetos_)] -____ - -No <>, `+Validated.__set__+` é um método modelo e `self.validate` é a operação abstrata. - -[[model_v5_abc]] -.model_v5.py: the `Validated` ABC -==== -[source, py] ----- -include::code/23-descriptor/bulkfood/model_v5.py[tags=MODEL_V5_VALIDATED_ABC] ----- -==== -<1> `+__set__+` delega a validação para o método `validate`... -<2> ...e então usa o `value` devolvido para atualizar o valor armazenado. -<3> `validate` é um método abstrato; este é o método modelo. - -Alex Martelli prefere chamar este padrão de projeto _Auto-Delegação_ ("_Self-Delegation_"), -e concordo que é um nome mais descritivo: a primeira linha de `+__set__+` auto-delega para -`validate`.footnote:[Slide #50 da palestra https://fpy.li/23-1["Python Design Patterns" (_Padrões de Projeto do Python_)] (EN), de Alex Martelli. Altamente recomendada.] - -As subclasses concretas de `Validated` neste exemplo são `Quantity` e `NonBlank`, apresentadas no <>. - -[[model_v5_sub]] -.model_v5.py: `Quantity` e `NonBlank`, subclasses concretas de `Validated` -==== -[source, py] ----- -include::code/23-descriptor/bulkfood/model_v5.py[tags=MODEL_V5_VALIDATED_SUB] ----- -==== -<1> Implementação do método modelo exigida pelo método abstrado `Validated.validate`. -<2> Se não sobrar nada após a remoção os espaços em branco antes e depois do valor, este é rejeitado. -<3> Exigir que os métodos `validate` concretos devolvam o valor validado dá a eles a oportunidade de limpar, converter ou normalizar os dados recebidos. Neste caso, `value` é devolvido sem espaços iniciais ou finais. - -Usuários de _model_v5.py_ não precisam saber todos esses detalhes. O que importa é poder usar `Quantity` e `NonBlank` para automatizar a validação de atributos de instância. Veja a última classe `LineItem` no <>. - -[[lineitem_class_v5]] -.bulkfood_v5.py: `LineItem` usando os descritores `Quantity` e `NonBlank` -==== -[source, py] ----- -include::code/23-descriptor/bulkfood/bulkfood_v5.py[tags=LINEITEM_V5] ----- -==== -<1> Importa o módulo `model_v5`, dando a ele um nome amigável. -<2> Usa `model.NonBlank`. O restante do código não foi modificado. - -Os exemplos de `LineItem` que vimos neste capítulo demonstram um uso típico de descritores, para gerenciar atributos de dados. -Descritores como `Quantity` são chamado descritores dominantes, pois seu método `+__set__+` sobrepõe (isto é, intercepta e anula) a definição de um atributo de instância com o mesmo nome na instância gerenciada. Entretanto, há também descritores não dominantes. Vamos explorar essa diferença detalhadamente na próxima seção.((("", startref="ADvalidation23")))((("", startref="ATvalidation23"))) - - -=== Descritores dominantes versus descritores não dominantes - -Recordando((("attribute descriptors", "overriding versus nonoverriding", id="ADovervnon23")))((("nonoverriding descriptors", id="nonover23")))((("overriding descriptors", id="overrid23"))), há uma importante assimetria na forma como o Python lida com atributos. Ler um atributo através de uma instância normalmente devolve o atributo definido na instância. Mas se tal atributo não existir na instância, um atributo de classe será obtido. Por outro lado, uma atribuição a um atributo em uma instância normalmente cria o atributo na instância, sem afetar a classe de forma alguma. - -Essa assimetria também afeta descritores, criando efetivamente duas grandes categorias de descritores, dependendo do método `+__set__+` estar ou não implementado. -Se `+__set__+` estiver presente, a classe é um descritor dominante; caso contrário, ela é um descritor não dominante. -Esses termos farão sentido quando examinarmos os comportamentos de descritores, nos próximos exemplos. - -Observar as categorias diferentes de descritores exige algumas classes, então vamos usar o código no <> como nossa bancada de testes para as próximas seções. - -[TIP] -==== -Todos os métodos `+__get__+` e `+__set__+` no <> chamam `print_args`. Assim, suas invocações são apresentadas de uma forma legível. Entender `print_args` e suas funções auxiliares, `cls_name` e `display`, não é importante, então não se deixe distrair por elas. -==== - -[[descriptorkinds_ex]] -.descriptorkinds.py: classes simples para estudar os comportamentos dominantes de descritores -==== -[source, py] ----- -include::code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS] ----- -==== -<1> Uma classe descritora dominante com `+__get__+` e `+__set__+`. -<2> A função `print_args` é chamada por todos os métodos do descritor neste exemplo. -<3> Um descritor dominante sem um método `+__get__+`. -<4> Nenhum método `+__set__+` aqui, estão este é um descritor não dominante. -<5> A classe gerenciada, usando uma instância de cada uma das classes descritoras. -<6> O método `spam` está aqui para efeito de comparação, pois métodos também são descritores. - -Nas próximas seções, examinaremos o comportamento de leitura e escrita de atributos na classe `Managed` e em uma de suas instâncias, passando por cada um dos diferentes descritores definidos. - -[[overriding_descriptor_sec]] -==== Descritores dominantes - -Um descritor que implementa o método `+__set__+` é um _descritor dominante_ pois, apesar de ser um atributo de classe, um descritor que implementa `+__set__+` irá sobrepor tentativas de atribuição a atributos de instância. É assim que o <> foi implementado. Propriedades também são descritores dominantes: se você não fornecer uma função _setter_, o `+__set__+` default da classe `property` vai gerar um `AttributeError`, para sinalizar que o atributo é somente para leitura. - -[WARNING] -==== -Contribuidores e autores da comunidade Python usam termos diferentes ao discutir esses conceitos. Adotei "descritor dominante" (_overriding descriptor_), do livro _Python in a Nutshell_. -A documentação oficial do Python usa "descritor de dados" (_data descriptor_) mas "descritor dominante" destaca o comportamento especial. -Descritores dominantes também são chamados "descritores forçados" (_enforced descriptors_). -Sinônimos para descritores não dominantes incluem "descritores sem dados" (_nondata descriptors_, na documentação oficial em português) ou "descritores ocultáveis" (_shadowable descriptors_). -==== - -Dado o código no <>, alguns experimentos com um descritor dominante podem ser vistos no <>. - -[[descriptorkinds_demo1]] -.O comportamento de um descritor dominante -==== -[source, py] ----- -include::code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO1] ----- -==== -<1> Cria o objeto `Managed`, para testes. -<2> `obj.over` aciona o método `+__get__+` do descritor, passando a instância gerenciada `obj` como segundo argumento. -<3> `Managed.over` aciona o método `+__get__+` do descritor, passando `None` como segundo argumento (`instance`). -<4> Atribuir a `obj.over` aciona o método `+__set__+` do descritor, passando o valor `7` como último argumento. -<5> Ler `obj.over` ainda invoca o método `+__get__+` do descritor. -<6> Contorna o descritor, definindo um valor diretamente no `+obj.__dict__+`. -<7> Verifica se aquele valor está no `+obj.__dict__+`, sob a chave `over`. -<8> Entretanto, mesmo com um atributo de instância chamado `over`, o descritor `Managed.over` continua interceptando tentativas de ler `obj.over`. - -==== Descritor dominante sem __get__ - -Propriedades e outros descritores dominantes, tal como os campos de modelo do Django, implementam tanto `+__set__+` quanto `+__get__+`. Mas também é possível implementar apenas `+__set__+`, como vimos no <>. Neste caso, apenas a escrita é controlada pelo descritor. Ler o descritor através de uma instância irá devolver o próprio objeto descritor, pois não há um -`+__get__+` para tratar daquele acesso. Se um atributo de instância de mesmo nome for criado com um novo valor, através de acesso direto ao `+__dict__+` da instância, o método `+__set__+` continuará interceptando tentativas posteriores de definir aquele atributo, mas a leitura do atributo vai simplesmente devolver o novo valor na instância, em vez de devolver o objeto descritor. Em outras palavras, o atributo de instância vai ocultar o descritor, mas apenas para leitura. Veja o <>. - -[[descriptorkinds_demo2]] -.Descritor dominante sem `+__get__+` -==== -[source, py] ----- -include::code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO2] ----- -==== -<1> Este descritor dominante não tem um método `+__get__+`, então ler `obj.over_no_get` obtém a instância do descritor a partir da classe. -<2> A mesma coisa acontece se obtivermos a instância do descritor diretamente da classe gerenciada. -<3> Tentar definir um valor para `obj.over_no_get` invoca o método `+__set__+` do descritor. -<4> Como nosso `+__set__+` não faz modificações, ler `obj.over_no_get` novamente obtém a instância do descritor na classe gerenciada. -<5> Percorrendo o `+__dict__+` da instância para definir um atributo de instância chamado `over_no_get`. -<6> Agora aquele atributo de instância `over_no_get` oculta o descritor, mas apenas para leitura. -<7> Tentar atribuir um valor a `obj.over_no_get` continua passando pelo _set_ do descritor. -<8> Mas, para leitura, aquele descritor é ocultado enquanto existir um atributo de instância de mesmo nome. - - -==== Descritor não dominante - -Um descritor que não implementa `+__set__+` é um descritor não dominante. Definir um atributo de instância com o mesmo nome vai ocultar o descritor, tornando-o incapaz de tratar aquele atributo naquela instância específica. Métodos e a `@functools.cached_property` são implementados como descritores não dominantes. O <> mostra a operação de um descritor não dominante. - -[[descriptorkinds_demo3]] -.Comportamento de um descritor não dominante -==== -[source, py] ----- -include::code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO3] ----- -==== -<1> `obj.non_over` aciona o método `+__get__+` do descritor, passando `obj` como segundo argumento. -<2> `Managed.non_over` é um descritor não dominante, então não há um `+__set__+` para interferir com essa atribuição. -<3> O `obj` agora tem um atributo de instância chamado `non_over`, que oculta o atributo do descritor de mesmo nome na classe `Managed`. -<4> O descritor `Managed.non_over` ainda está lá, e intercepta esse acesso através da classe. -<5> Se o atributo de instância `non_over` for excluído... -<6> ...então ler `obj.non_over` encontra o método `+__get__+` do descritor; mas observe que o segundo argumento é a instância gerenciada. - -Nos exemplos anteriores, vimos várias atribuições a um atributo de instância com nome igual ao do descritor, com resultados diferentes dependendo da presença ou não de um método `+__set__+` no descritor. - -A definição de atributos na classe não pode ser controlada por descritores ligados à mesma classe. -Em especial, isso significa que os próprios atributos do descritor podem ser danificados por atribuições à classe, como explicado na próxima seção. - - -==== Sobrescrevendo um descritor em uma classe - -Independente do descritor ser ou não dominante, ele pode ser sobrescrito por uma atribuição à classe. Isso é uma((("monkey-patching"))) técnica de _monkey-patching_ mas, no <>, os descritores são substituídos por números inteiros, algo que certamente quebraria a lógica de qualquer classe que dependesse dos descritores para seu funcionamento correto. - -[[descriptorkinds_demo4]] -.Qualquer descritor pode ser sobrescrito na própria classe -==== -[source, py] ----- -include::code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO4] ----- -==== -<1> Cria uma nova instância para testes posteriores. -<2> Sobrescreve os atributos dos descritores na classe. -<3> Os descritores realmente desapareceram. - -O <> expõe outra assimetria entre a leitura e a escrita de atributos: apesar da leitura de um atributo de classe poder ser controlada por um `+__get__+` de um descritor ligado à classe gerenciada, a escrita em um atributo de classe não pode ser tratado por um `+__set__+` de um descritor ligado à mesma classe. - -[TIP] -==== -Para controlar a escrita a atributos em uma classe, é preciso associar descritores à classe da classe—em outras palavras, à metaclasse. Por default, a metaclasse de classes definidas pelo usuário é `type`, e não podemos acrescentar atributos a `type`. Mas, no <>, vamos criar nossas próprias metaclasses. -==== - -Vamos ver agora como descritores são usados para implementar métodos no Python.((("", startref="ADovervnon23")))((("", startref="nonover23")))((("", startref="overrid23"))) - -[[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 <>. - - -[[descriptorkinds_demo5]] -.Um método é um descritor não dominante -==== -[source, py] ----- -include::code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO5] ----- -==== -<1> Ler de `obj.spam` obtém um objeto método vinculado. -<2> Mas ler de `Managed.spam` obtém uma função. -<3> Atribuir um valor a `obj.spam` oculta o atributo de classe, tornando o método `spam` inacessível a partir da instância `obj`. - -Funções não implementam `+__set__+`, portanto são descritores não dominantes, como mostra a última linha do <>. - -A outra lição fundamental do <> é que `obj.spam` e `Managed.spam` devolvem objetos diferentes. Como de hábito com descritores, o `+__get__+` de uma função devolve uma referência para a própria função quando o acesso ocorre através da classe gerenciada. Mas quando o acesso vem através da instância, o `+__get__+` da função devolve um objeto método vinculado: um invocável que envolve a função e vincula a instância gerenciada (no exemplo, `obj`) ao primeiro argumento da função (isto é, `self`), como faz a função `functools.partial` (que vimos na seção <>). -Para um entendimento mais profundo desse mecanismo, dê uma olhada no <>. - -[[func_descriptor_ex]] -.method_is_descriptor.py: uma classe `Text`, derivada de `UserString` -==== -[source, py] ----- -include::code/23-descriptor/method_is_descriptor.py[tags=FUNC_DESCRIPTOR_EX] ----- -==== - -Vamos então investigar o método `Text.reverse`. Veja o <>. - -[[func_descriptor_demo]] -.Experimentos com um método -==== -[source, py] ----- -include::code/23-descriptor/method_is_descriptor.py[tags=FUNC_DESCRIPTOR_DEMO] ----- -==== -<1> O `repr` de uma instância de `Text` se parece com uma chamada ao construtor de `Text` que criaria uma instância idêntica. -<2> O método `reverse` devolve o texto escrito de trás para frente. -<3> Um método invocado na classe funciona como uma função. -<4> Observe os tipos diferentes: uma `function` e um `method`. -<5> `Text.reverse` opera como uma função, mesmo ao trabalhar com objetos que não são instâncias de `Text`. -<6> Toda função é um descritor não dominante. Invocar seu `+__get__+` com uma instância obtém um método vinculado a aquela instância. -<7> Invocar o `+__get__+` da função com `None` como argumento `instance` obtém a própria função. -<8> A expressão `word.reverse` na verdade invoca `+Text.reverse.__get__(word)+`, devolvendo o método vinculado. -<9> O objeto método vinculado tem um atributo `+__self__+`, contendo uma referência à instância na qual o método foi invocado. -<10> O atributo `+__func__+` do método vinculado é uma referência à função original, ligada à classe gerenciada. - -O objeto método vinculado contém um método `+__call__+`, que trata a invocação em si. Este método chama a função original, referenciada em `+__func__+`, passando o atributo `+__self__+` do método como primeiro argumento. É assim que funciona a vinculação implícita do argumento `self` convencional. - -O modo como funções são transformadas em métodos vinculados é um exemplo perfeito de como descritores são usados como infraestrutura da linguagem. - -Após este mergulho profundo no funcionamento de descritores e métodos, vamos repassar alguns conselhos práticos sobre seu uso.((("", startref="ADmethodsas23"))) - -[[descriptor_usage_sec]] -=== Dicas para o uso de descritores - -A((("attribute descriptors", "descriptor usage tips", id="ADtips23"))) lista a seguir trata de algumas consequências práticas das características dos descritores descritas acima: - -Use `property` para manter as coisas simples:: A classe embutida `property` cria descritores dominantes, implementando `+__set__+` e `+__get__+`, mesmo se um método _setter_ não for definido.footnote:[Um método `+__delete__+` também é fornecido pelo decorador `property`, mesmo se você não definir um método _deleter_ (de exclusão).] -O `+__set__+` default de uma propriedade gera um `AttributeError: can't set attribute` (_AttributeError: não é permitido definir o atributo_), então uma propriedade é a forma mais fácil de criar um atributo somente para leitura, evitando o problema descrito a seguir. - -Descritores somente para leitura exigem um `+__set__+`:: Se você usar uma classe descritora para implementar um atributo somente para leitura, precisa lembrar de programar tanto `+__get__+` quanto -`+__set__+`. Caso contrário, definir um atributo com o mesmo nome em uma instância vai ocultar o descritor. O método `+__set__+` de um atributo somente para leitura deve apenas gerar um `AttributeError` com uma mensagem adequada.footnote:[O Python não é consistente nessas mensagens. Tentar modificar o atributo `c.real` de um número `complex` resulta em um `AttributeError: readonly attribute` (_AttributeError: atributo somente para leitura_), mas uma tentativa de mudar -`c.conjugate` (um método de `complex`) gera um `AttributeError: 'complex' object attribute 'conjugate' is read-only` (_AttributeError: o atributo 'conjugate' do objeto 'complex' é somente para leitura_). Até "read-only" está escrito de maneira diferente (na mensagem original em inglês).] - -Descritores de validação podem funcionar apenas com `+__set__+`:: Em um descritor projetado apenas para validação, o método `+__set__+` deve verificar o argumento `value` recebido e, se ele for válido, atualizar o `+__dict__+` da instância diretamente, usando o nome da instância do descritor como chave. Dessa forma, ler o atributo de mesmo nome a partir da instância será tão rápido quanto possível, pois não vai precisar de um `+__get__+`. Veja o código no <>. - -_Caching_ pode ser feito de forma eficiente apenas com `+__get__+`:: Se você escrever apenas o método `+__get__+`, cria um descritor não dominante. -Eles são úteis para executar alguma computação custosa e então armazenar o resultado, definindo um atributo com o mesmo nome na instânciafootnote:[Entretanto, lembre-se que criar atributos de instância após o método `+__init__+` frustra a otimização de memória através de compartilhamento de chaves, como discutido na seção <>.]. -O atributo de mesmo nome na instância vai ocultar o descritor, daí acessos subsequentes a aquele atributo vão buscá-lo diretamente no `+__dict__+` da instância, sem acionar mais o `+__get__+` do descritor. -O decorador `@functools.cached_property` na verdade produz um descritor não dominante. - -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. - -[role="man-height"] -[NOTE] -==== -A classe `FrozenJSON` no <> está a salvo de atributos de instância ocultando métodos, pois seus únicos métodos são métodos especiais e o método de classe `build`. Métodos de classe são seguros desde que sejam sempre acessados através da classe, como fiz com `FrozenJSON.build` no <>—mais tarde substituído por `+__new__+` no <>. As classes `Record` e `Event`, apresentadas na seção <>, também estão a salvo: elas implementam apenas métodos especiais, métodos estáticos e propriedades. Propriedades são descritores dominantes, então não são ocultados por atributos de instância. -==== - -Para encerrar esse capítulo, vamos falar de dois recursos que vimos com as propriedades, mas não no contexto dos descritores: documentação e o tratamento de tentativas de excluir um atributo gerenciado.((("", startref="ADtips23"))) - -[[descriptor_doc_del_sec]] -=== Docstrings de descritores e a sobreposição de exclusão - -A((("attribute descriptors", "descriptor docstring and overriding deletion"))) docstring de uma classe descritora é usada para documentar todas as instâncias do descritor na classe gerenciada. -O <> mostra as telas de ajuda para a classe `LineItem` com os descritores `Quantity` e `NonBlank`, do -<> e do <>. - -Isso é um tanto insatisfatório. No caso de `LineItem`, seria bom acrescentar, por exemplo, a informação de que `weight` deve ser expresso em quilogramas. Isso seria trivial com propriedades, pois cada propriedade controla um atributo gerenciado específico. Mas com descritores, a mesma classe descritora `Quantity` é usada para `weight` e `price`.footnote:[Personalizar o texto de ajuda para cada instância do descritor é supreendentemente difícil. Uma solução exige criar dinamicamente uma classe invólucro (_wrapper_) para cada instância do descritor.] - -O segundo detalhe que discutimos com propriedades, mas não com descritores, é o tratamento de tentativas de apagar um atributo gerenciado. -Isso pode ser feito pela implementação de um método `+__delete__+` juntamente com (ou em vez de) os habituais `+__get__+` e/ou `+__set__+` na classe descritora. -Omiti deliberadamente falar de `+__delete__+`, porque acredito que seu uso no mundo real é raro. -Se você precisar disso, por favor consulte a seção https://fpy.li/23-2["Implementando descritores"] na documentação do https://fpy.li/dtmodel[Modelo de dados do Python]. -Escrever um classe descritora boba com `+__delete__+` fica como exercício para a leitora ociosa. - -[[descriptor_help_screens]] -.Capturas de tela do console do Python após os comandos `help(LineItem.weight)` e `help(LineItem)`. -image::images/flpy_2304.png[Capturas de tela do console do Python com a ajuda de descritores.] - -=== Resumo do capítulo - -O((("attribute descriptors", "overview of"))) primeiro exemplo deste capítulo foi uma continuação dos exemplos `LineItem` do <>. No <>, substituímos propriedades por descritores. Vimos que um descritor é uma classe que fornece instâncias, que são instaladas como atributos na classe gerenciada. Discutir esse mecanismo exigiu uma terminologia especial, apresentando termos tais como _instância gerenciada_ e _atributo de armazenamento_. - -Na seção <>, removemos a exigência de descritores `Quantity` serem declarados com um `storage_name` explícito, um requisito redundante e propenso a erros. A solução foi implementar o método especial `+__set_name__+` em `Quantity`, para armazenar o nome da propriedade gerenciada como `self.storage_name`. - -A seção <> mostrou como criar uma subclasse de uma classe descritora abstrata, para compartilhar código ao programar descritores especializados com alguma funcionalidade em comum. - -[role="pagebreak-before less_space"] -Examinamos então os comportamentos diferentes de descritores, fornecendo ou omitindo o método -`+__set__+`, criando uma distinção fundamental entre descritores dominantes e não dominantes, também conhecidos como descritores de dados e sem dados. Por meio de testes detalhados, revelamos quando os descritores estão no controle, e quando são ocultados, contornados ou sobrescritos. - -Em seguida, estudamos uma categoria específica de descritores não dominantes: métodos. Experimentos no console revelaram como uma função associada ao uma classe se torna um método ao ser acessada através de uma instância, se valendo do protocolo descritor. - -Para concluir o capítulo, a seção <> trouxe dicas práticas, e a seção <> forneceu um rápido olhar sobre como documentar descritores. - -[NOTE] -==== -Como observado na seção <>, vários exemplos deste capítulo se tornaram muito mais simples graças ao método especial `+__set_name__+` do protocolo descritor, adicionado no Python 3.6. Isso é evolução da linguagem! -==== - -=== Leitura complementar - -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 do Python. - -Como sempre, em se tratando de assuntos relativos ao modelo de objetos do 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 do 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)). - -[WARNING] -==== -Cuidado, qualquer tratamento de descritores escrito ou gravado antes da PEP 487 ser adotada, em 2016, corre o risco de conter exemplos desnecessariamente complicados hoje, pois `+__set_name__+` não era suportado nas versões do Python anteriores a 3.6. -==== - -Para mais exemplos práticos, o _Python Cookbook_, 3ª ed., de David Beazley e Brian K. Jones (O’Reilly), traz muitas receitas ilustrando descritores, dentre as quais quero destacar "6.12. Reading Nested and Variable-Sized Binary Structures" (_Lendo Estruturas Binárias Aninhadas e de Tamanho Variável_), "8.10. Using Lazily Computed Properties" (_Usando Propriedades Computadas de Forma Preguiçosa_), "8.13. Implementing a Data Model or Type System" (_Implementando um Modelo de Dados ou um Sistema de Tipos_) e "9.9. Defining Decorators As Classes" (_Definindo Decoradores como Classes_). Essa última receita trata das questões profundas envolvidas na interação entre decoradores de função, descritores e métodos, e de como um decorador de função implementado como uma classe, com `+__call__+`, também precisa implementar `+__get__+` se quiser funcionar com métodos de decoração e também com funções. - -A https://fpy.li/pep487[PEP 487—Simpler customization of class creation (_PEP 487—Uma personalização mais simples da criação de classes_)] (EN) -introduziu o método especial `+__set_name__+` e inclui um exemplo de um -https://fpy.li/23-7[validating descriptor (_descritor de validação_)] (EN). - -//[role="pagebreak-before"] -.Ponto de vista -**** - -[role="soapbox-title"] -O projeto de self - -A((("attribute descriptors", "Soapbox discussion")))((("Soapbox sidebars", "explicit self argument")))((("explicit self argument"))) exigência de declarar `self` explicitamente como o primeiro argumento em métodos foi uma decisão de projeto controversa no Python. -Após 23 anos usando a linguagem, já estou acostumado com isso. -Acho que essa decisão é um exemplo de "pior é melhor" (_worse is better_): -a filosofia de projeto descrita pelo cientista da computação Richard P. Gabriel em -https://fpy.li/23-8["The Rise of Worse is Better" (_A Ascenção do Pior é Melhor_)] (EN). -A primeira prioridade dessa filosofia é "simplicidade", que Gabriel apresenta assim: - -[quote] -____ -O projeto deve ser simples, tanto na implementação quanto na interface. -É mais importante que a implementação seja simples, do que a interface. -A simplicidade é a consideração mais importante em um projeto. -____ - -O `self` explícito do Python incorpora essa filosofia de projeto. -A implementação é simples—até mesmo elegante—à custas da interface do usuário: uma assinatura de método como `def zfill(self, width):` não corresponde, visualmente, à invocação `label.zfill(8)`. - -O Modula-3 introduziu essa convenção com o mesmo identificador, `self`. -Mas há uma diferença crucial: no Modula-3, interfaces são declaradas separadamente de sua implementação, e na declaração da interface o argumento `self` é omitido. Então, da perspectiva do usuário, um método aparece em uma declaração de interface com os mesmos parâmetros explícitos usados para invocá-lo. - -Ao longo do tempo, as mensagens de erro do Python relacionadas a argumentos de métodos se tornaram mais claras. -Em um método definido pelo usuário com um argumento além de `self`, se o usuário invocasse -`obj.meth()`, o Python 2.7 gerava: - -[source] ----- -TypeError: meth() takes exactly 2 arguments (1 given) -("TypeError: meth() recebe exatamente 2 argumentos (1 passado)"") ----- - -No Python 3, a confusa contagem de argumentos não é mencionada, e o argumento ausente é nomeado: - -[source] ----- -TypeError: meth() missing 1 required positional argument: 'x' -("TypeError: 1 argumento posicional obrigatório faltando em meth(): 'x'") ----- - -Além do uso de `self` como um argumento explícito, a exigência de qualificar cada acesso a atributos de instância com `self` também é criticada. Veja, por exemplo, o famoso post "Python Warts" (_As verrugas do Python_) de A. M. Kuchling (em https://fpy.li/23-9[archived] (EN)); o próprio Kuchling não se incomoda muito com o qualificador `self`, mas ele o menciona—provavelmente ecoando opiniões do grupo comp.lang.python. -Pessoalmente não me importo em digitar o qualificador `self`: é bom para distinguir variáveis locais de atributos. Minha questão é com o uso de `self` em comandos `def`. - -Quem estiver triste com o `self` explícito do Python pode se sentir bem melhor após considerar a -https://fpy.li/23-10[semântica desconcertante] (EN) do `this` implícito em JavaScript. Guido tinha algumas boas razões para fazer `self` funcionar como funciona, e ele escreveu sobre elas em -https://fpy.li/23-11["Adding Support for User-Defined Classes" (_Adicionando Suporte a Classes Definidas pelo Usuário_)], um post em seu blog, _The History of Python_ ("A História do Python"). -**** - diff --git a/capitulos/cap24.adoc b/capitulos/cap24.adoc deleted file mode 100644 index f59619b0..00000000 --- a/capitulos/cap24.adoc +++ /dev/null @@ -1,1463 +0,0 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte - -[[class_metaprog]] -== Metaprogramação de classes - -[quote, Brian W. Kernighan and P. J. Plauger, The Elements of Programming Style] -____ -Todo mundo sabe que depurar um programa é duas vezes mais difícil que escrever o mesmo programa. -Mas daí, se você der tudo de si ao escrever o programa, como vai conseguir depurá-lo?footnote:[Citação extraída do capítulo 2, _Expression_ ("Expressão"), página 10, de _The Elements of Programming Style, Second Edition (NT: "Elementos de Estilo de Programação"; não encontramos edição traduzida deste livro.)] -____ - -Metaprogramação((("class metaprogramming", "benefits and drawbacks of"))) de classes é a arte de criar ou personalizar classes durante a execução do programa. -Em Python, classes são objetos de primeira classe, então uma função pode ser usada para criar uma nova classe a qualquer momento, sem usar a palavra-chave `class`. - -Decoradores de classes também são funções, mas são projetados para inspecionar, modificar ou mesmo substituir a classe decorada por outra classe. -Por fim, metaclasses são a ferramenta mais avançada para metaprogramação de classes: elas permitem a criação de categorias de classes inteiramente novas, com características especiais, tais como as classes base abstratas, que já vimos anteriormente. - -Metaclasses são poderosas, mas difíceis de justificar na prática, e ainda mais difíceis de entender direito. Decoradores de classe resolvem muitos dos mesmos problemas, e são mais fáceis de compreender. -Mais ainda, o Python 3.6 implementou a https://fpy.li/pep487[PEP 487—Simpler customization of class creation (_PEP 487—Uma personalização mais simples da criação de classes_)], fornecendo métodos especiais para tarefas que antes exigiam metaclasses ou decoradores de classe.footnote:[Isso não quer dizer que a PEP 487 quebrou código que usava aqueles recursos, mas apenas que parte do código que utilizava decoradores de classe ou metaclasses antes do Python 3.6 pode agora ser refatorado para usar classes comuns, resultando em um código mais simples e possivelmente mais eficiente.] - -Este capítulo apresenta as técnicas de metaprogramação de classes em ordem ascendente de complexidade. - - -[WARNING] -==== -Esse é um tópico empolgante, e é fácil se deixar levar pelo entusiasmo. -Então preciso deixar aqui esse conselho. - -Em nome da legibilidade e facilidade de manutenção, você provavelmente deveria evitar as técnicas descritas neste capítulo em aplicações. - -Por outro lado, caso você queira escrever o próximo framework formidável do Python, essas são suas ferramentas de trabalho. -==== - -=== Novidades nesse capítulo - -Todo((("class metaprogramming", "significant changes to"))) o código do capítulo "Metaprogramação de Classes" da primeira edição do _Python Fluente_ ainda funciona corretamente. -Entretanto, alguns dos exemplos antigos não representam mais as soluções mais simples, tendo em vista os novos recursos surgidos desde o Python 3.6. - -Substituí aqueles exemplos por outros, enfatizando os novos recursos de metaprogramação ou acrescentando novos requisitos para justificar o uso de técnicas mais avançadas. -Alguns destes novos exemplos se valem de dicas de tipo para fornecer fábricas de classes similares ao decorador `@dataclass` e a `typing.NamedTuple`. - -A seção <> é nova, trazendo algumas considerações de alto nível sobre a aplicabilidade das metaclasses. - -[TIP] -==== -Algumas das melhores refatorações envolvem a remoção de código tornado redundante por formas novas e e mais simples de resolver o mesmo problema. -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 do Python para todas as classes. - -[[anatomy_of_classes]] -=== Classes como objetos - -Como((("class metaprogramming", "classes as objects"))) acontece com a maioria das entidades programáticas do Python, classes também são objetos. -Toda classe tem alguns atributos definidos no Modelo de Dados do 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 do 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: - -`+cls.__bases__+`:: A((("cls.__bases__"))) tupla de classes base da classe. - -`+cls.__qualname__+`:: O((("cls.__qualname__"))) nome qualificado de uma classe ou função, que é um caminho pontuado, desde o escopo global do módulo até a definição da classe. Isso é relevante quando a classe é definida dentro de outra classe. -Por exemplo, em um modelo de classe Django, tal como https://fpy.li/24-2[`Ox`] (EN), há uma classe interna chamada `Meta`. O `+__qualname__+` de `Meta` é `Ox.Meta`, mas seu `+__name__+` é apenas `Meta`. -A especificação para este atributo está na -https://fpy.li/24-3[PEP 3155--Qualified name for classes and functions (_PEP 3155—Nome qualificado para classes e funções_)] (EN). - -`+cls.__subclasses__()+`:: Este((("cls.__subclasses__()"))) método devolve uma lista das subclasses imediatas da classe. -A implementação usa referências fracas, para evitar referências circulares entre a superclasse e suas subclasses—que mantêm uma referência forte para a superclasse em seus atributos `+__bases__+`. -O método lista as subclasses na memória naquele momento. Subclasses em módulos ainda não importados não aparecerão no resultado. - -`cls.mro()`:: O((("cls.mro()"))) interpretador invoca este método quando está criando uma classe, para obter a tupla de superclasses armazenada no atributo `+__mro__+` da classe. -Uma metaclasse pode sobrepor este método, para personalziar a ordem de resolução de métodos da classe em construção. - -[TIP] -==== -Nenhum dos atributos mencionados nesta seção aparecem na lista devolvida pela função `dir(…)`. -==== - -Agora, se classe é um objeto, o que é a classe de uma classe? - - -=== type: a fábrica de classes embutida - -Nós((("class metaprogramming", "built-in class factory"))) normalmente pensamos em `type` como uma função que devolve a classe de um objeto, porque é isso que `type(my_object)` faz: devolve `my_object.__class__`. - -Entretanto, `type` é uma classe que cria uma nova classe quando invocada com três argumentos. - -Considere essa classe simples: - -[source, python3] ----- -class MyClass(MySuperClass, MyMixin): - x = 42 - - def x2(self): - return self.x * 2 ----- - -Usando o construtor `type`, podemos criar `MyClass` durante a execução, com o seguinte código: - -[source, python3] ----- -MyClass = type('MyClass', - (MySuperClass, MyMixin), - {'x': 42, 'x2': lambda self: self.x * 2}, - ) ----- - -Aquela chamada a `type` é funcionalmente equivalente ao bloco sob a instrução `class MyClass…` anterior. - -Quando o Python lê uma instrução `class`, invoca `type` para construir um objeto classe com os parâmetros abaixo: - -`name`:: - O identificador que aparece após a palavra-chave `class`, por exemplo, `MyClass`. -`bases`:: - A tupla de superclasses passadas entre parênteses após o identificador da classe, ou - `(object,)`, caso nenhuma superclasse seja mencionada na instrução `class`. -`dict`:: - Um mapeamento entre nomes de atributo e valores. Invocáveis se tornam métodos, como vimos na seção <>. Outros valores se tornam atributos de classe. - -[NOTE] -==== -O construtor `type` aceita argumentos nomeados opcionais, que são ignorados por `type`, mas que são passados como recebidos para `+__init_subclass__+`, que deve consumi-los. -Vamos estudar esse método especial na seção <>, mas não vou tratar do uso de argumentos nomeados. Para saber mais sobre isso, por favor leia a https://fpy.li/pep487[PEP 487—Simpler customization of class creation (_PEP 487—Uma personalização mais simples da criação de classes_)] (EN). -==== - -A classe `type` é uma((("metaclasses", "definition of term"))) _metaclasse_: uma classe que cria classes. -Em outras palavras, instâncias da classe `type` são classes. -A biblioteca padrão contém algumas outras metaclasses, mas `type` é a default: - -[source, pycon] ----- ->>> type(7) - ->>> type(int) - ->>> type(OSError) - ->>> class Whatever: -... pass -... ->>> type(Whatever) - ----- - -Vamos criar metaclasses personalizadas na seção <>. - -Agora, vamos usar a classe embutida `type` para criar uma função que constrói classes. - - -=== Uma função fábrica de classes - -A((("class metaprogramming", "class factory function", id="CMfacfun24"))) biblioteca padrão contém uma função fábrica de classes que já apareceu várias vezes aqui: `collections.namedtuple`. -No <> também vimos `typing.NamedTuple` e `@dataclass`. -Todas essas fábricas de classe usam técnicas que veremos neste capítulo. - -Vamos começar com uma fábrica muito simples, para classes de objetos mutáveis—a substituta mais simples possível de `@dataclass`. - -Suponha que eu esteja escrevendo uma aplicação para uma _pet shop_, e queira armazenar dados sobre cães como registros simples. Mas não quero escrever código padronizado como esse: - -[source, python3] ----- -class Dog: - def __init__(self, name, weight, owner): - self.name = name - self.weight = weight - self.owner = owner ----- - -Chato... cada nome de campo aparece três vezes, e essa repetição sequer nos garante um bom `repr`: - -[source, pycon] ----- ->>> rex = Dog('Rex', 30, 'Bob') ->>> rex -<__main__.Dog object at 0x2865bac> ----- - -Inspirados por `collections.namedtuple`, vamos criar uma `record_factory`, que cria classes simples como `Dog` em tempo real. O <> mostra como ela deve funcionar. - -[[record_factory_demo]] -.Testando `record_factory`, uma fábrica de classes simples -==== -[source, py] ----- -include::code/24-class-metaprog/factories.py[tags=RECORD_FACTORY_DEMO] ----- -==== -<1> A fábrica pode ser chamada como `namedtuple`: nome da classe, seguido dos nomes dos atributos separados por espaços, em um única string. -<2> Um `repr` agradável. -<3> Instâncias são iteráveis, então elas podem ser convenientemente desempacotadas em uma atribuição... -<4> ...ou quando são passadas para funções como `format`. -<5> Uma instância do registro é mutável. -<6> A classe recém-criada herda de `object`—não tem qualquer relação com nossa fábrica. - -O código para `record_factory` está no <>.footnote:[Agradeço a meu amigo J. S. O. Bueno por ter contribuído com esse exemplo.] - -[[record_factory_ex]] -.record_factory.py: uma classe fábrica simples -==== -[source, py] ----- -include::code/24-class-metaprog/factories.py[tags=RECORD_FACTORY] ----- -==== -<1> O usuário pode fornecer os nomes dos campos como uma string única ou como um iterável de strings. -<2> Aceita argumentos como os dois primeiros de `collections.namedtuple`; devolve `type`—isto é, uma classe que se comporta como uma `tuple`. -<3> Cria uma tupla de nomes de atributos; esse será o atributo `+__slots__+` da nova classe. -<4> Essa função se tornará o método `+__init__+` na nova classe. Ela aceita argumentos posicionais e/ou nomeados.footnote:[Não acrescentei dicas de tipo aos argumentos porque os tipos reais são `Any`. Escrevi a dica to tipo devolvido porque em caso contrário, o Mypy não verificaria o código dentro do método.] -<5> Produz os valores dos campos na ordem dada por `+__slots__+`. -<6> Produz um `repr` agradável, iterando sobre `+__slots__+` e `self`. -<7> Monta um dicionário de atributos de classe. -<8> Cria e devolve a nova classe, invocando o construtor de `type`. -<9> Converte `names` separados por espaços ou vírgulas em uma lista de `str`. - -O <> é a primeira vez que vemos `type` em uma dica de tipo. -Se a anotação fosse apenas `-> type`, significaria que `record_factory` devolve uma classe—e isso estaria correto. -Mas a anotação `-> type[tuple]` é mais precisa: indica que a classe devolvida será uma subclasse de `tuple`. - -A última linha de `record_factory` no <> cria uma classe cujo nome é o valor de `cls_name`, com `object` como sua única classe base imediata, e um espaço de nomes carregado com -`+__slots__+`, `+__init__+`, `+__iter__+`, e `+__repr__+`, sendo os útimos três métodos de instância. - -Poderíamos ter dado qualquer outro nome ao atributo de classe `+__slots__+`, mas então teríamos que implementar `+__setattr__+` para validar os nomes dos atributos em uma atribuição, porque em nossas classes similares a registros queremos que o conjunto de atributos seja sempre o mesmo e na mesma ordem. Entretanto, lembre-se que a principal característica de `+__slots__+` é economizar memória quando estamos lidando com milhões de instâncias, e que usar `+__slots__+` traz algumas desvantagens, discutidas na seção <>. - -[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". -==== - -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"))) - - -[[enhancing_with_init_subclass]] -=== Apresentando pass:[__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 personalização mais simples da criação de classes_)]. -Falamos pela primeira vez do método especial para descritores `+__set_name__+` na seção <>. -Agora vamos estudar `+__init_subclass__+`. - -No <>, vimos como `typing.NamedTuple` e `@dataclass` permitem a programadores usarem a instrução `class` para especificar atributos para uma nova classe, que então é aperfeiçoada pela fábrica de classes com a adição automática de métodos essenciais, tais como `+__init__+`, `+__repr__+`, `+__eq__+`, etc. - -Ambas as fábricas de classes leem as dicas de tipo na instrução `class` do usuário para aperfeiçoar a classe. Essas dicas de tipo também permitem que verificadores de tipo estáticos validem código que define ou lê aqueles atributos. -Entretanto, `NamedTuple` e `@dataclass` não se valem das dicas de tipo para validação de atributos durante a execução. A classe `Checked`, no próximo exemplo, faz isso. - -[NOTE] -==== -Não é possível suportar toda dica de tipo estática concebível para verificação de tipo durante a execução, e possivelmente essa é a razão para `typing.NamedTuple` e `@dataclass` sequer tentarem. -Entretanto, algums tipos que são também classes concretas podem ser usados com `Checked`. -Isso inclui tipos simples, usados com frequência para o conteúdo de campos, tais como `str`, `int`, `float`, e `bool`, bem como listas destes tipos. -==== - -O <> mostra como usar `Checked` para criar uma classe `Movie`. - -[[checked_demo1_ex]] -.initsub/checkedlib.py: doctest para a criação de uma subclasse `Movie` de `Checked` -==== -[source, python] ----- -include::code/24-class-metaprog/checked/initsub/checkedlib.py[tags=MOVIE_DEFINITION] ----- -==== -<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. - -Os construtores usados como dicas de tipo podem ser qualquer invocável que receba zero ou um argumento, e devolva um valor adequado ao tipo do campo pretendido ou rejeite o argumento, gerando um `TypeError` ou um `ValueError`. - -Usar tipos embutidos para as anotações no <> significa que os valores devem aceitáveis pelo construtor do tipo. -Para `int`, isso significa qualquer `x` tal que `int(x)` devolva um `int`. -Para `str`, qualquer coisa serve durante a execução, pois `str(x)` funciona com qualquer `x` no -Python.footnote:[Isso é verdade para qualquer objeto, exceto quando sua classe sobrepõe os métodos `+__str__+` ou `+__repr__+`, herdados de `object`, por uma implementação que não funcione.] - -Quando chamado sem argumentos, o construtor deve devolver um valor default de seu tipo.footnote:[Essa solução evita usar `None` como default. Evitar valores nulos é uma https://fpy.li/24-5[boa ideia]. Em geral, eles são difíceis de evitar, mas em alguns casos isso é fácil. Tanto no Python quanto no SQL, prefiro representar dados ausentes em um campo de texto como um string vazia em vez de `None` ou `NULL`. Aprender Go reforçou essa ideia: em Go, variáveis e campos struct de tipos primitivos são inicializados por default com um "valor zero" (_zero value_). Se você estiver curiosa, veja a página https://fpy.li/24-6["Zero values" ("Valores zero")] (EN) no _Tour of Go_ ("Tour do Go") online] - -Esse é o comportamento padrão de construtores embutidos no Python: - -[source, python] ----- ->>> int(), float(), bool(), str(), list(), dict(), set() -(0, 0.0, False, '', [], {}, set()) ----- - -Em uma subclasse de `Checked` como `Movie`, parâmetros ausentes criam instâncias com os valores default devolvidos pelos construtores dos campos. Por exemplo: - -[source, python] ----- -include::code/24-class-metaprog/checked/initsub/checkedlib.py[tags=MOVIE_DEFAULTS] ----- - -Os construtores são usados para validação durante a instanciação, e quando um atributo é definido diretamente em uma instância: - -[source, python] ----- -include::code/24-class-metaprog/checked/initsub/checkedlib.py[tags=MOVIE_TYPE_VALIDATION] ----- - -.Subclasses de `Checked` e a verificação estática de tipos -[WARNING] -==== -Em um arquivo de código fonte _.py_ contendo uma instância `movie` da classe `Movie`, como definida no <>, o Mypy marca essa atribuição como um erro de tipo: - -[source, python] ----- -movie.year = 'MCMLXXII' ----- - -Entretanto, o Mypy não consegue detectar erros de tipo nessa chamada ao construtor: - -[source, python] ----- -blockbuster = Movie(title='Avatar', year='MMIX') ----- - -Isso porque `Movie` herda `+Checked.__init__+`, -e a assinatura daquele método deve aceitar qualquer argumento nomeado, para suportar classes arbitrárias definidas pelo usuário. - -Por outro lado, se você declarar um campo de uma subclasse de `Checked` com a dica de tipo -`list[float]`, o Mypy pode sinalizar atribuições de listas com tipos incompatíveis, mas `Checked` vai ignorar o parâmetro de tipo e tratá-lo como igual a `list`. -==== - -Vamos ver agora a implementação de _checkedlib.py_. -A primeira classe é o descritor `Field`, como mostra o <>. - -[[checked_field_ex]] -.initsub/checkedlib.py: a classe descritora `Field` -==== -[source, py] ----- -include::code/24-class-metaprog/checked/initsub/checkedlib.py[tags=CHECKED_FIELD] ----- -==== -<1> Lembre-se, desde o Python 3.9, o tipo `Callable` para anotações é a ABC em -`collections.abc`, e não o descontinuado `typing.Callable`. -<2> Essa é a dica de tipo `Callable` mínima; o parâmetro de tipo e o tipo devolvido para `constructor` são ambos implicitamente `Any`. -<3> Para verificação durante a execução, usamos o embutido `callable`.footnote:[Na minha opinião, `callable` deveria se tornar adequado para dicas de tipo. Em 6 de maio de 2021, quando essa nota foi escrita, essa ainda era uma https://fpy.li/24-7[questão aberta] (EN).] O teste contra `type(None)` é necessário porque o Python entende `None` em um tipo como `NoneType`, a classe de `None` (e portanto invocável), mas esse é um construtor inútil, que apenas devolve `None`. -<4> Se `+Checked.__init__+` definir `value` como `...` (o objeto embutido `Ellipsis`), invocamos o construtor sem argumentos. -<5> Caso contrário, invocamos o `constructor` com o `value` dado. -<6> Se `constructor` gerar qualquer dessas exceções, geramos um `TypeError` com uma mensagem útil, incluindo os nomes do campo e do construtor; por exemplo, `'MMIX' não é compatível com year:int`. -<7> Se nenhuma exceção for gerada, o `value` é armazenado no `+instance.__dict__+`. - -Em `+__set__+`, precisamos capturar `TypeError` e `ValueError`, pois os construtores embutidos podem gerar qualquer dos dois, dependendo do argumento. -Por exemplo, `float(None)` gera um `TypeError`, mas `float('A')` gera um `ValueError`. -Por outro lado, `float('8')` não causa qualquer erro, e devolve `8.0`. -E assim eu aqui declaro que, nesse exemplo simples, este um recurso, não um bug. - -[TIP] -==== -Na seção <>, 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 <>. -==== - -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]] -.initsub/checkedlib.py: os métodos mais importante da classe `Checked` -==== -[source, py] ----- -include::code/24-class-metaprog/checked/initsub/checkedlib.py[tags=CHECKED_TOP] ----- -==== -<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 do Python ≥ 3.10, invocaria `inspect.get_annotations` em vez disso. Reveja a seção <> 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 seção <>. -<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... -<7> ...obtém o `value` correspondente de `kwargs` e o remove de `kwargs`. Usar `...` (o objeto `Ellipsis`) como default nos permite distinguir entre argumentos com valor `None` de argumentos ausentes.footnote:[Como mencionado em <>, o objeto `Ellipsis` é um valor sentinela conveniente e seguro. Ele existe no Python há muito tempo, mas recentemente mais usos tem sido encontrados para ele, como vemos nas dicas de tipo e no NumPy.] -<8> Essa chamada a `setattr` aciona `+Checked.__setattr__+`, apresentado no <>. -<9> Se houver itens remanescentes em `kwargs`, seus nomes não correspondem a qualquer dos campos declarados, e `+__init__+` vai falhar. -<10> Esse erro é informado por `+__flag_unknown_attrs+`, listado no <>. Ele recebe um argumento `*names` com os nomes de atributos desconhecidos. Usei um único asterisco em `*kwargs`, para passar suas chaves como uma sequência de argumentos. - - -[[init_subclass_not_typical_box]] -.pass:[__init_subclass__] não é um método de classe típico -**** -O decorador `@classmethod` nunca é usado com `+__init_subclass__+`, mas isso não quer dizer muita coisa, pois o método especial `+__new__+` se comporta como um método de classe mesmo sem `@classmethod`. -O primeiro argumento que o 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.]. -**** - -Vamos examinar os métodos restantes da classe `Checked`, -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]] -.initsub/checkedlib.py: métodos restantes da classe `Checked` -==== -[source, py] ----- -include::code/24-class-metaprog/checked/initsub/checkedlib.py[tags=CHECKED_BOTTOM] ----- -==== -<1> Intercepta qualquer tentativa de definir um atributo de instância. Isso é necessário para evitar a definição de um atributo desconhecido. -<2> Se o `name` do atributo é conhecido, busca o `descriptor` correspondente. -<3> Normalmente não é preciso invocar o `+__set__+` do descritor explicitamente. Nesse caso isso foi necessário porque `+__setattr__+` intercepta todas as tentativas de definir um atributo em uma instância, mesmo na presença de um descritor dominante, tal como `Field`.footnote:[O sutil conceito de descritor dominante foi explicado na seção <>.] -<4> Caso contrário, o atributo `name` é desconhecido, e uma exceção será gerada por -`+__flag_unknown_attrs+`. -<5> Cria uma mensagem de erro útil, listando todos os argumentos inesperados, e gera um `AttributeError`. -Este é um raro exemplo do tipo especial `NoReturn`, tratado na seção <>. -<6> Cria um `dict` a partir dos atributos de um objeto `Movie`. Eu chamaria este método de -`+_as_dict+`, mas segui a convenção iniciada com o método `+_asdict+` em `collections.namedtuple`. -<7> Implementar um `+__repr__+` agradável é a principal razão para ter `_asdict` neste exemplo. - -O exemplo `Checked` mostra como tratar descritores dominantes ao implementar `+__setattr__+` para bloquear a definição arbitrária de atributos após a instanciação. -É possível debater se vale a pena implementar `+__setattr__+` neste exemplo. -Sem ele, definir `movie.director = 'Greta Gerwig'` funcionaria, mas o atributo `director` não seria verificado de forma alguma, não apareceria no `+__repr__+` nem seria incluído no `dict` devolvido por `+_asdict+`—ambos definidos no <>. - -Em _record_factory.py_ (no <>), solucionei essa questão usando o atributo de classe `+__slots__+`. -Entretanto, essa solução mais simples não é viável aqui, como explicado a seguir. - -[[why_cannot_config_slots_sec]] -==== Por que pass:[__init_subclass__] não pode configurar pass:[__slots__]? - -O((("__slots__"))) atributo `+__slots__+` só é efetivo se for um dos elementos do espaço de nomes da classe passado para `+type.__new__+`. -Acrescentar `+__slots__+` a uma classe existente não tem qualquer efeito. -O Python invoca `+__init_subclass__+` apenas após a classe ser criada—neste ponto, é tarde demais para configurar `+__slots__+`. -Um decorador de classes também não pode configurar `+__slots__+`, pois ele é aplicado ainda mais tarde que `+__init_subclass__+`. -Vamos explorar essas questões de sincronia na seção <>. - -Para configurar `+__slots__+` durante a execução, nosso próprio código precisa criar o espaço de nomes da classe a ser passado como último argumento de `+type.__new__+`. -Para fazer isso, podemos escrever uma função fábrica de classes, como _record_factory.py_, ou optar pelo caminho bombástico, e implementar uma metaclasse. -Veremos como configurar `+__slots__+` dinamicamente na seção <>. - -Antes da https://fpy.li/pep487[PEP 487] (EN) simplificar a personalização da criação de classes com -`+__init_subclass__+`, no Python 3.7, uma funcionalidade similar só poderia ser implementada usando um decorador de classe. -É o tópico de nossa próxima seção.((("", startref="initsub24")))((("", startref="CMinitsub24"))) - - -=== Melhorando classes com um decorador de classes - -Um((("class metaprogramming", "enhancing classes with class decorators", id="CMcdecorator24")))((("decorators and closures", "enhancing classes with class decorators", id="DACcdeco24"))) decorador de classes é um invocável que se comporta de forma similar a um decorador de funções: -recebe uma classe decorada como argumento, e deve devolver um classe para substituir a classe decorada. Decoradores de classe frequentemente devolvem a própria classe decorada, após injetar nela mais métodos pela definição de atributos. -Provavelmente, a razão mais comum para escolher um decorador de classes, em vez do mais simples -`+__init_subclass__+`, é evitar interferência com outros recursos da classe, tais como herança e metaclasses.footnote:[Essa justificativa aparece no resumo da https://fpy.li/24-9[PEP 557–Data Classes (_Classes de Dados_)] (EN), para explicar porque ela foi implementada como um decorador de classes.] - -Nessa seção vamos estudar _checkeddeco.py_, que oferece a mesma funcionalidade de _checkedlib.py_, mas usando um decorador de classe. -Como sempre, começamos examinando um exemplo de uso, extraído dos doctests em _checkeddeco.py_ (no <>). - -[[checkeddeco_demo1_ex]] -.checkeddeco.py: criando uma classe `Movie` decorada com `@checked` -==== -[source, python] ----- -include::code/24-class-metaprog/checked/decorator/checkeddeco.py[tags=MOVIE_DEFINITION] ----- -==== - -A única diferença entre o <> e o <> é a forma como a classe `Movie` é declarada: ela é decorada com `@checked` em vez de ser uma subclasse de `Checked`. -Fora isso, o comportamento externo é o mesmo, incluindo a validação de tipo e a atribuição de valores default, apresentados após -o <>, na seção <>. - -Vamos olhar agora para a implementação de _checkeddeco.py_. -As importações e a classe `Field` são as mesmas de _checkedlib.py_, listadas no <>. -Em _checkeddeco.py_ não há qualquer outra classe, apenas funções. - -A lógica antes implementada em `+__init_subclass__+` agora é parte da função `checked`—o decorador de classes listado no <>. - -[[checkeddeco_decorators_ex]] -.checkeddeco.py: o decorador de classes -==== -[source, python] ----- -include::code/24-class-metaprog/checked/decorator/checkeddeco.py[tags=CHECKED_DECORATOR] ----- -==== -<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... -<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`. -<7> Devolve a `cls` decorada, cumprindo o contrato básico de um decorador de classes. - -[role="pagebreak-before less_space"] -Todas as funções no primeiro nível de _checkeddeco.py_ estão prefixadas com um sublinhado, exceto o decorador `checked`. -Essa convenção para a nomenclatura faz sentido por duas razões: - -* `checked` é parte da interface pública do módulo _checkeddeco.py_, as outras funções não. -* As funções no <> serão injetadas na classe decorada, e o `+_+` inicial reduz as chances de um conflito de nomes com atributos e métodos definidos pelo usuário na classe decorada. - -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 <>. - -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. - -[[checkeddeco_methods_ex]] -.checkeddeco.py: os métodos que serão injetados na classe decorada -==== -[source, python] ----- -include::code/24-class-metaprog/checked/decorator/checkeddeco.py[tags=CHECKED_METHODS] ----- -==== - -O módulo _checkeddeco.py_ implementa um decorador de classes simples mas usável. -O `@dataclass` do Python faz muito mais. -Ele suporta várias opções de configuração, acrescenta mais métodos à classe decorada, trata ou avisa sobre conflitos com métodos definidos pelo usuário na classe decorada, e até percorre o `+__mro__+` para coletar atributos definidos pelo usuário declarados em superclasses da classe decorada. -O https://fpy.li/24-10[código-fonte] do pacote `dataclasses` no Python 3.9 tem mais de 1200 linhas. - -Para fazer metaprogramação de classes, precisamos saber quando o interpretador Python avalia cada bloco de código durante a criação de uma classe. -É disso que falaremos a seguir.((("", startref="DACcdeco24")))((("", startref="CMcdecorator24"))) - -[[import_v_runtime_sec]] -=== O que acontece quando: importação versus execução - -Programadores Python((("class metaprogramming", "import time versus runtime", id="CMimport24")))((("import time versus runtime"))) falam de "importação" (_import time_) versus "execução" (_runtime_), mas estes termos não tem definições precisas e há uma zona cinzenta entre eles. - -Na importação, o interpretador: - -. Analisa o código-fonte de módulo _.py_ em uma passagem, de cima até embaixo. É aqui que um `SyntaxError` pode ocorrer. -. Compila o _bytecode_ a ser executado. -. Executa o código no nível superior do módulo compilado. - -Se existir um arquivo _.pyc_ atualizado no `+__pycache__+` local, a análise e a compilação são omitidas, pois o _bytecode_ está pronto para ser executado. - -Apesar da análise e a compilação serem definitivamente atividades de "importação", outras coisas podem acontecer durante o processo, pois quase todos os comandos ou instruções no Python são executáveis, no sentido de poderem potencialmente rodar código do usuário e modificar o estado do programa do usuário. - -Em especial, a instrução `import` não é meramente uma declaraçãofootnote:[Compare com a instrução `import` em Java, que é apenas uma declaração para informar o compilador que determinados pacotes são necessários.], pois na verdade ela executa todo o código no nível superior de um módulo, quando este é importado para o processo pela primeira vez. Importações posteriores do mesmo módulo usarão um _cache_, e então o único efeito será a vinculação dos objetos importados a nomes no módulo cliente. Aquele código no primeiro nível pode fazer qualquer coisa, incluindo ações típicas da "execução", tais como escrever em um arquivo de log ou conectar-se a um banco de dados.footnote:[Não estou dizendo que é uma boa ideia abrir uma conexão com um banco de dados só porque o módulo foi importado, apenas apontando que isso pode ser feito.] -Por isso a fronteira entre a "importação" e a "execução" é difusa: `import` pode acionar todo tipo de comportamento de "execução", porque a instrução `import` e a função embutida -`+__import__()+` podem ser usadas dentro de qualquer função regular. - -Tudo isso é bastante abstrato e sútil, então vamos realizar alguns experimentos para ver o que acontece, e quando. - - -[[evaldemo_sec]] -==== Experimentos com a fase de avaliação (_evaluation time_) - -Considere um script _evaldemo.py_, que usa um decorador de classes, um descritor e uma fábrica de classes baseada em `+__init_subclass__+`, todos definidos em um módulo _builderlib.py_. -Os módulos usados tem várias chamadas a `print`, para revelar o que acontece por baixo dos panos. Fora isso, eles não fazem nada de útil. O objetivo destes experimentos é observar a ordem na qual essas chamadas a `print` acontecem. - -[WARNING] -==== -Aplicar um decorador de classes e uma fábrica de classes com `+__init_subclass__+` juntos, em uma única classe, é provavelmente um sinal de excesso de engenharia ou de desespero. Essa combinação incomum é útil nesses experimentos, para mostrar a ordem temporal das mudanças que um decorador de classes e `+__init_subclass__+` podem aplicar a uma classe. -==== - -Vamos começar examinando _builderlib.py_, dividido em duas partes: o <> e o <>. - -[[builderlib_top_ex]] -.builderlib.py: primeira parte do módulo -==== -[source, python3] ----- -include::code/24-class-metaprog/evaltime/builderlib.py[tags=BUILDERLIB_TOP] ----- -==== -<1> Essa é uma fábrica de classes para implementar... -<2> ...um método `+__init_subclass__+`. -<3> Define uma função para ser adicionada à subclasse na atribuição abaixo. -<4> Um decorador de classes. -<5> Função a ser adicionada à classe decorada. -<6> Devolve a classe recebida como argumento. - -Continuando _builderlib.py_ no <>... - -[[builderlib_bottom_ex]] -.builderlib.py: a parte final do módulo -==== -[source, python3] ----- -include::code/24-class-metaprog/evaltime/builderlib.py[tags=BUILDERLIB_BOTTOM] ----- -==== -<1> Uma classe descritora para demonstrar quando... -<2> ...uma instância do descritor é criada, e quando... -<3> ...`+__set_name__+` será invocado durante a criação da classe `owner`. -<4> Como os outros métodos, este `+__set__+` não faz nada, exceto exibir seus argumentos. - -Se importarmos _builderlib.py_ no console do Python, veremos o seguinte: - -[source, pycon] ----- ->>> import builderlib -@ builderlib module start -@ Builder body -@ Descriptor body -@ builderlib module end ----- - -Observe que as linhas exibidas por _builderlib.py_ tem um `@` como prefixo. - -Vamos agora voltar a atenção para _evaldemo.py_, que vai acionar método especiais em -_builderlib.py_ (no <>). - -[[evaldemo_ex]] -.evaldemo.py: script para experimentar com _builderlib.py_ -==== -[source, python3] ----- -include::code/24-class-metaprog/evaltime/evaldemo.py[] ----- -==== -<1> Aplica um decorador. -<2> Cria uma subclasse de `Builder` para acionar seu `+__init_subclass__+`. -<3> Instancia o descritor. -<4> Isso só será chamado se o módulo for executado como o programa pincipal. - -As chamadas a `print` em _evaldemo.py_ tem um `#` como prefixo. -Se você abrir o console novamente e importar _evaldemo.py_, a saída aparece no <>. - -[[evaldemo_console_ex]] -.Experimentos de console com _evaldemo.py_ -==== -[source, pycon] ----- ->>> import evaldemo -@ builderlib module start <1> -@ Builder body -@ Descriptor body -@ builderlib module end -# evaldemo module start -# Klass body <2> -@ Descriptor.__init__() <3> -@ Descriptor.__set_name__(, - , 'attr') <4> -@ Builder.__init_subclass__() <5> -@ deco() <6> -# evaldemo module end ----- -==== -<1> As primeiras quatro linhas são o resultado de `from builderlib import…`. Elas não vão aparecer se você não fechar o console após o experimento anterior, pois _builderlib.py_ já estará carregado. -<2> Isso sinaliza que o Python começou a ler o corpo de `Klass`. Neste momento o objeto classe ainda não existe. -<3> A instância do descritor é criada e vinculada a `attr`, no espaço de nomes que o Python passará para o construtor default do objeto classe: `+type.__new__+`. -<4> Neste ponto, a função embutida do Python `+type.__new__+` já criou o objeto `Klass` e invoca -`+__set_name__+` em cada instância das classes do descritor que oferecem aquele método, passando `Klass` como argumento `owner`. -<5> `+type.__new__+` então chama `+__init_subclass__+` na superclasse de `Klass`, passando `Klass` como único argumento. -<6> Quando `+type.__new__+` devolve o objeto classe, o Python aplica o decorador. Neste exemplo, a classe devolvida por `deco` está vinculada a `Klass` no espaço de nomes do módulo - -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 do 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": -importar `builderlib` e definir `Klass`. - -Se você executar _evaldemo.py_ como um script, vai ver a mesma saída do <>, com linhas extras logo antes do final. -As linhas adicionais são o resultado da execução de `main()` (veja o <>). - -[[evaldemo_script_ex]] -.Executando _evaldemo.py_ como um programa -==== -[source] ----- -$ ./evaldemo.py -[... 9 linhas omitidas ...] -@ deco() <1> -@ Builder.__init__() <2> -# Klass.__init__() -@ SuperA.__init_subclass__:inner_0() <3> -@ deco:inner_1() <4> -@ Descriptor.__set__(, , 999) <5> -# evaldemo module end ----- -==== -<1> As 10 primeiras linhas—incluindo essa—são as mesma que aparecem no <>. -<2> Acionado por `+super().__init__()+` em `+Klass.__init__+`. -<3> Acionado por `obj.method_a()` em `main`; o `method_a` foi injetado por -`+SuperA.__init_subclass__+`. -<4> Acionado por `obj.method_b()` em `main`; `method_b` foi injetado por `deco`. -<5> Acionado por `obj.attr = 999` em `main`. - -Uma classe base com `+__init_subclass__+` ou um decorador de classes são ferramentas poderosas, mas elas estão limitadas a trabalhar sobre uma classe já criada por `+type.__new__+` por baixo dos panos. -Nas raras ocasiões em que for preciso ajustar os argumentos passados a `+type.__new__+`, uma metaclasse é necessária. -Esse é o destino final desse capítulo—e desse livro.((("", startref="CMimport24"))) - - -[[metclass101_sec]] -=== Introdução às metaclasses - -[quote, Tim Peters, inventor do algoritmo timsort e um produtivo colaborador do Python] -____ -[Metaclasses] são uma mágica tão profunda que 99% dos usuários jamais deveria se preocupar com elas. Quem se pergunta se precisa delas, não precisa (quem realmente precisa de metaclasses sabe disso com certeza, e não precisa que lhe expliquem a razão).footnote:[Mensagem a comp.lang.python, assunto: https://fpy.li/24-12["Acrimony in c.l.p." (_animosidade no c.l.p._)]. -Essa é outra parte da mesma mensagem de 23 de dezembro de 2002, citada na <>. O TimBot estava inspirado naquele dia.] -____ - -Uma((("metaclasses", "basics of", id="MCbasics24")))((("class metaprogramming", "metaclass basics", id="CMmetabasic24"))) metaclasse é uma fábrica de classes. -Diferente de `record_factory`, do <>, -uma metaclasse é escrita como uma classe. -Em outras palavras, uma metaclasse é uma classe cujas instâncias são classes. -A <> usa a Notação Engenhocas e Bugigangas para representar uma metaclasse: uma engenhoca que produz outra engenhoca. - -[[meta_class_and_class_mgn]] -.Uma metaclasse é uma classe que cria classes. -image::images/flpy_2401.png[Diagrama MGN com metaclasse e classe.] - -Pense no modelo de objetos do Python: classes são objetos, portanto cada classe deve ser uma instância de alguma outra classe. -Por default, as classes do Python são instâncias de `type`. -Em outras palavras, `type` é a metaclasse da maioria das classes, sejam elas embutidas ou definidas pelo usuário: - -[source, pycon] ----- ->>> str.__class__ - ->>> from bulkfood_v5 import LineItem ->>> LineItem.__class__ - ->>> type.__class__ - ----- - -Para evitar regressões infinitas, a classe de `type` é `type`, como mostra a última linha. - -Observe que não estou dizendo que `str` ou `LineItem` são subclasses de `type`. Estou dizendo que `str` e `LineItem` são instâncias de `type`. -Elas são todas subclasses de `object`. A <> pode ajudar você a contemplar essa estranha realidade. - -[[class_hier_2tops_uml]] -.Os dois diagramas são verdadeiros. O da esquerda enfatiza que `str`, `type`, e `LineItem` são subclasses de `object`. O da direita deixa claro que `str`, `object`, e `LineItem` são instâncias de `type`, pois todas são classes. -image::images/flpy_2402.png[Diagrama de classes UML com as relações de `object` e `type`.] - -[NOTE] -==== -As classes `object` e `type` tem uma relação singular: `object` é uma instância de `type`, e `type` é uma subclasse de `object`. Essa relação é "mágica": ela não pode ser expressa em Python, porque cada uma das classes teria que existir antes da outra poder ser definida. O fato de `type` ser uma instância de si mesma também é mágico. -==== - -O próximo trecho mostra que a classe de `collections.Iterable` é `abc.ABCMeta`. -Observe que `Iterable` é uma classe abstrata, mas `ABCMeta` é uma classe concreta--afinal, `Iterable` é uma instância de `ABCMeta`: - -[source, pycon] ----- ->>> from collections.abc import Iterable ->>> Iterable.__class__ - ->>> import abc ->>> from abc import ABCMeta ->>> ABCMeta.__class__ - ----- - -Por fim, a classe de `ABCMeta` também é `type`. -Toda classe é uma instância de `type`, direta ou indiretamente, mas apenas metaclasses são também subclasses de `type`. -Essa é a mais importante relação para entender as metaclasses: -uma metaclasse, tal como `ABCMeta`, herda de `type` o poder de criar classes. -A <> ilustra essa relação fundamental. - -[role="width-60"] -[[metaclass_abcmeta_uml]] -.`Iterable` é uma subclasse de `object` e uma instância de `ABCMeta`. Tanto `object` quanto `ABCMeta` são instâncias de `type`, mas a relação crucial aqui é que `ABCMeta` também é uma subclasse de `type`, porque `ABCMeta` é uma metaclasse. Neste diagrama, `Iterable` é a única classe abstrata. -image::images/flpy_2403.png[Diagramas de classe UML com as relações de `Iterable` e `ABCMeta`.] - -A lição importante aqui é que metaclasses são subclasses de `type`, e é isso que permite a elas funcionarem como fábricas de classes. -Uma metaclasse pode personalizar suas instâncias implementando métodos especiais, como demosntram as próximas seções.((("", startref="MCbasics24"))) - -[[how_metaclass_customizes]] -==== Como uma metaclasse personaliza uma classe - -Para((("metaclasses", "customizing classes"))) usar uma metaclasse, é crucial entender como -`+__new__+` funciona em qualquer classe. -Isso foi discutido na seção <>. - -A mesma mecânica se repete no nível "meta", quando uma metaclasse está prestes a criar uma nova instância, que é uma classe. -Considere a declaração abaixo: - -[source, python] ----- -class Klass(SuperKlass, metaclass=MetaKlass): - x = 42 - def __init__(self, y): - self.y = y ----- - -Para processar essa instrução `class`, o Python invoca `+MetaKlass.__new__+` com os seguintes argumentos: - -`meta_cls`:: A própria metaclasse(`MetaKlass`), porque `+__new__+` funciona como um método de classe. - -`cls_name`:: A string `Klass`. - -`bases`:: A tupla com um único elemento `(SuperKlass,)` (ou com mais elementos, em caso de herança múltipla). - -`cls_dict`:: -+ --- -Um mapeamento como esse: - -[source] ----- -{x: 42, `+__init__+`: } ----- --- - -Ao implementar `+MetaKlass.__new__+`, podemos inspecionar e modificar aqueles argumentos antes de passá-los para `+super().__new__+`, que por fim invocará `+type.__new__+` para criar o novo objeto classe. - -Após `+super().__new__+` retornar, -podemos também aplicar processamento adicional à classe recém-criada, antes de devolvê-la para o Python. O Python então invoca `+SuperKlass.__init_subclass__+`, passando a classe que criamos, e então aplicando um decorador de classe, se algum estiver presente. -Finalmente, o Python vincula o objeto classe a seu nome no espaço de nomes circundante—normalmente o espaço de nomes global do módulo, se a instrução `class` foi uma instrução no primeiro nível. - -O processamento mais comum realizado no `+__new__+` de uma metaclasse é adicionar ou substituir itens no `cls_dict`—o mapeamento que representa o espaço de nomes da classe em construção. Por exemplo, antes de chamar `+super().__new__+`, podemos injetar métodos na classe em construção adicionando funções a `cls_dict`. -Entretanto, observe que adicionar métodos pode também ser feito após a classe ser criada, e é por essa razão que podemos fazer isso usando `+__init_subclass__+` ou um decorador de classe. - -Um atributo que precisa ser adicionado a `cls_dict` antes de se executar `+type.__new__+` é -`+__slots__+`, como discutido na seção <>. -O método `+__new__+` de uma metaclasse é o lugar ideal para configurar `+__slots__+`. -A próxima seção mostra como fazer isso. - - -[[nice_metaclass_sec]] -==== Um belo exemplo de metaclasse - -A((("metaclasses", "example metaclass", id="MCexample24"))) metaclasse `MetaBunch`, apresentada aqui, é uma variação do último exemplo no Capítulo 4 do pass:[Python in a Nutshell, 3ª ed.], de Alex Martelli, Anna Ravenscroft, e Steve Holden, escrito para rodar sob Python 2.7 e 3.5.footnote:[Os autores gentilmente me deram permissão para usar seu exemplo. `MetaBunch` apareceu pela primeira vez em uma mensagem enviada por Martelli para o grupo comp.lang.python, em 7 de julho de 2002, com o assunto -https://fpy.li/24-13["a nice metaclass example (was Re: structs in python)" (_um belo exmeplo de metaclasse (era Re: structs no python)_)], na sequência de uma discussão sobre estruturas de dados similares a registros no Python. O código original de Martelli, para Python 2.2, ainda roda após uma única modificação: para usar uma metaclasse no Python 3, é necessário usar o argumento nomeado `metaclass` na declaração da classe (por exemplo, `Bunch(metaclass=MetaBunch)`), em vez da convenção antiga, que era adicionar um atributo `+__metaclass__+` no corpo da classe.] -Assumindo o uso do Python 3.6 ou mais recente, pude simplificar ainda mais o código. - -Mas primeiro vamos ver o que a classe base `Bunch` oferece: - -[source, pycon] ----- -include::code/24-class-metaprog/metabunch/from3.6/bunch.py[tags=BUNCH_POINT_DEMO_1] ----- - -Lembre-se que `Checked` atribui nomes aos descritores `Field` em subclasses, baseada em dicas de tipo de variáveis de classe, que não se tornam atributos na classe, já que não tem valores. - -Subclasses de `Bunch`, por outro lado, usam atributos de classe reais com valores, que então se tornam os valores default dos atributos de instância. -O `+__repr__+` gerado omite os argumentos para atributos iguais aos defaults. - -`MetaBunch`—a metaclasse de `Bunch`—gera `+__slots__+` para a nova classe a partir de atributos de classe declarados na classe do usuário. -Isso bloqueia a instanciação e posterior atribuição a atributos não declarados: - -[source, pycon] ----- -include::code/24-class-metaprog/metabunch/from3.6/bunch.py[tags=BUNCH_POINT_DEMO_2] ----- - -Vamos agora mergulhar no elegante código de `MetaBunch`, no <>. - -[[metabunch_ex]] -.metabunch/from3.6/bunch.py: a metaclasse `MetaBunch` e a classe `Bunch` -==== -[source, python3] ----- -include::code/24-class-metaprog/metabunch/from3.6/bunch.py[tags=METABUNCH] ----- -==== -<1> Para criar uma nova metaclasse, herdamos de `type`. -<2> `+__new__+` funciona como um método de classe, mas a classe é uma metaclasse, então gosto de nomear o primeiro argumento `meta_cls` (`mcs` é uma alternativa comum). Os três argumentos restantes são os mesmos da assinatura de três argumentos de `type()`, quando chamada diretamente para criar uma classe. -<3> `defaults` vai manter um mapeamento de nomes de atributos e seus valores default. -<4> Isso irá ser injetado na nova classe. -<5> Lê `defaults` e define o atributo de instância correspondente, com o valor extraído de `kwargs`, ou um valor default. -<6> Se ainda houver itens em `kwargs`, isso significa que não há posição restante onde possamos colocá-los. -Acreditamos em _falhar rápido_ como melhor prática, então não queremos ignorar silenciosamente os itens em excesso. Uma solução rápida e eficiente é extrair um item de `kwargs` e tentar defini-lo na instância, gerando propositalmente um `AttributeError`. -<7> `+__repr__+` devolve uma string que se parece com uma chamada ao construtor—por exemplo, `Point(x=3)`, omitindo os argumentos nomeados com valores default. -<8> Inicializa o espaço de nomes para a nova classe. -<9> Itera sobre o espaço de nomes da classe do usuário. -<10> Se um `name` _dunder_ (com sublinhados como prefixo e sufixo) é encontrado, copia o item para o espaço de nomes da nova classe, a menos que ele já esteja lá. Isso evita que usuários sobrescrevam -`+__init__+`, `+__repr__+` e outros atributos definidos pelo Python, tais como `+__qualname__+` e -`+__module__+`. -<11> Se `name` não for um _dunder_, acrescenta `name` a `+__slots__+` e armazena seu `value` em `defaults`. -<12> Cria e devolve a nova classe. -<13> Fornece uma classe base, assim os usuários não precisam ver `MetaBunch`. - -`MetaBunch` funciona por ser capaz de configurar `+__slots__+` antes de invocar `+super().__new__+` para criar a classe final. -Como sempre em metaprogramação, o fundamental é entender a sequência de ações. -Vamos fazer outro experimento sobre a fase de avaliação, agora com uma metaclasse.((("", startref="MCexample24"))) - - -==== Experimento com a fase de avaliação de metaclasses - -Essa((("metaclasses", "metaclass evaluation time experiment", id="MCtime24"))) é uma variação do <>, acrescentando uma metaclasse à mistura. -O módulo _builderlib.py_ é o mesmo de antes, mas o script principal é agora _evaldemo_meta.py_, listado no <>. - -[[evaldemo_meta_ex]] -.evaldemo_meta.py: experimentando com uma metaclasse -==== -[source, python3] ----- -include::code/24-class-metaprog/evaltime/evaldemo_meta.py[] ----- -==== -<1> Importa `MetaKlass` de _metalib.py_, que veremos no <>. -<2> Declara `Klass` como uma subclasse de `Builder` e uma instância de `MetaKlass`. -<3> Este método é injetado por `+MetaKlass.__new__+`, como veremos adiante. - - -[WARNING] -==== -Em nome da ciência, o <> desafia qualquer racionalidade e aplica três técnicas diferentes de metaprogramação juntas a `Klass`: -um decorador, uma classe base usando `+__init_subclass__+`, e uma metaclasse personalizada. -Se você fizer isso com código em produção, por favor não me culpe. -Repito, o objetivo é observar a ordem na qual as três técnicas interferem no processo de criação de uma classe. -==== - -Como no experimento anterior com a fase de avaliação, este exemplo não faz nada, apenas exibe mensagens revelando o fluxo de execução. -O <> mostra a primeira parte do código de _metalib.py_—o restante está no <>. - -[[metalib_top_ex]] -.metalib.py: a classe `NosyDict` -==== -[source, python3] ----- -include::code/24-class-metaprog/evaltime/metalib.py[tags=METALIB_TOP] ----- -==== - -Escrevi a classe `NosyDict` para sobrepor `+__setitem__+` e exibir cada `key` e cada `value` conforme eles são definidos. -A metaclasse vai usar uma instância de `NosyDict` para manter o espaço de nomes da classe em construção, revelando um pouco mais sobre o funcionamento interno do Python. - -A principal atração de _metalib.py_ é a metaclasse no <>. -Ela implementa o método especial `+__prepare__+`, um método de classe que o Python só invoca em metaclasses. -O método `+__prepare__+` oferece a primeira oportunidade para influenciar o processo de criação de uma nova classe. - -[TIP] -==== -Ao programar uma metaclasse, acho útil adotar a seguinte convenção de nomenclatura para argumentos de métodos especiais: - -* Usar `cls` em vez de `self` para métodos de instância, pois a instância é uma classe. - -* Usar `meta_cls` em vez de `cls` para métodos de classe, pois a classe é uma metaclasse. Lembre-se que `+__new__+` se comporta como um método de classe mesmo sem o decorador `@classmethod`. -==== - -[[metalib_bottom_ex]] -.metalib.py: a `MetaKlass` -==== -[source, python3] ----- -include::code/24-class-metaprog/evaltime/metalib.py[tags=METALIB_BOTTOM] ----- -==== -<1> `+__prepare__+` deve ser declarado como um método de classe. -Ele não é um método de instância, pois a classe em construção ainda não existe quando o Python invoca `+__prepare__+`. -<2> O Python invoca `+__prepare__+` em uma metaclasse para obter um mapeamento, onde vai manter o espaço de nomes da classe em construção. -<3> Devolve uma instância de `NosyDict` para ser usado como o espaço de nomes. -<4> `cls_dict` é uma instância de `NosyDict` devolvida por `+__prepare__+`. -<5> `+type.__new__+` exige um `dict` real como último argumento, então passamos a ele o atributo `data` de `NosyDict`, herdado de `UserDict`. -<6> Injeta um método na classe recém-criada. -<7> Como sempre, `+__new__+` precisa devolver o objeto que acaba de ser criado—neste caso, a nova classe. -<8> Definir `+__repr__+` em uma metaclasse permite personalizar o `repr()` de objetos classe. - -O principal caso de uso para `+__prepare__+` antes do Python 3.6 era oferecer um -`OrderedDict` para manter os atributos de uma classe em construção, para que o `+__new__+` da metaclasse pudesse processar aqueles atributos na ordem em que aparecem no código-fonte da definição de classe do usuário. -Agora que `dict` preserva a ordem de inserção, `+__prepare__+` raramente é necessário. -Veremos um uso criativo para ele no <>. - -Importar _metalib.py_ no console do Python não é muito empolgante. -Observe o uso de `%` para prefixar as linhas geradas por esse módulo: - -[source, pycon] ----- ->>> import metalib -% metalib module start -% MetaKlass body -% metalib module end ----- - -Muitas coisas acontecem quando importamos _evaldemo_meta.py_, como visto no <>. - -[[evaldemo_meta_console_ex]] -.Experimento com _evaldemo_meta.py_ no console -==== -[source, pycon] ----- ->>> import evaldemo_meta -@ builderlib module start -@ Builder body -@ Descriptor body -@ builderlib module end -% metalib module start -% MetaKlass body -% metalib module end -# evaldemo_meta module start <1> -% MetaKlass.__prepare__(, 'Klass', <2> - (,)) -% NosyDict.__setitem__(, '__module__', 'evaldemo_meta') <3> -% NosyDict.__setitem__(, '__qualname__', 'Klass') -# Klass body -@ Descriptor.__init__() <4> -% NosyDict.__setitem__(, 'attr', ) <5> -% NosyDict.__setitem__(, '__init__', - ) <6> -% NosyDict.__setitem__(, '__repr__', - ) -% NosyDict.__setitem__(, '__classcell__', ) -% MetaKlass.__new__(, 'Klass', - (,), ) <7> -@ Descriptor.__set_name__(, - , 'attr') <8> -@ Builder.__init_subclass__() -@ deco() -# evaldemo_meta module end ----- -==== -<1> As linhas antes disso são resultado da importação de _builderlib.py_ e _metalib.py_. -<2> O Python invoca `+__prepare__+` para iniciar o processamento de uma instrução `class`. -<3> Antes de analisar o corpo da classe, o Python acrescenta `+__module__+` e `+__qualname__+` ao espaço de nomes de uma classe em construção. -<4> A instância do descritor é criada... -<5> ...e vinculada a `attr` no espaço de nomes da classe. -<6> Os métodos `+__init__+` e `+__repr__+` são definidos e adicionados ao espaço de nomes. -<7> Após terminar o processamento do corpo da classe, o Python chama `+MetaKlass.__new__+`. -<8> `+__set_name__+`, `+__init_subclass__+` e o decorador são invocados nessa ordem, após o método -`+__new__+` da metaclasse devolver a classe recém-criada. - -Se executarmos _evaldemo_meta.py_ como um script, `main()` é chamado, e algumas outras coisas acontecem (veja o <>). - -[[evaldemo_meta_script_ex]] -.Rodando _evaldemo_meta.py_ como um programa -==== -[source] ----- -$ ./evaldemo_meta.py -[... 20 linhas omitidas ...] -@ deco() <1> -@ Builder.__init__() -# Klass.__init__() -@ SuperA.__init_subclass__:inner_0() -@ deco:inner_1() -% MetaKlass.__new__:inner_2() <2> -@ Descriptor.__set__(, , 999) -# evaldemo_meta module end ----- -==== -<1> As primeiras 21 linhas—incluindo esta—são as mesmas que aparecem no <>. -<2> Acionado por `obj.method_c()` em `main`; `method_c` foi injetado por `+MetaKlass.__new__+`. - -Vamos agora voltar à ideia da classe `Checked`, com descritores `Field` implementando validação de tipo durante a execução, e ver como aquilo pode ser feito com uma metaclasse.((("", startref="CMmetabasic24")))((("", startref="MCtime24"))) - - -=== Uma solução para Checked usando uma metaclasse - -Não((("class metaprogramming", "metaclass solution for checkedlib.py", id="CMcheck24")))((("metaclasses", "metaclass solution for checkedlib.py", id="MCchecked24"))) quero encorajar a otimização prematura nem excessos de engenharia, então aqui temos um cenário de faz de conta para justificar a reescrever _checkedlib.py_ com `+__slots__+`, exigindo a aplicação de uma metaclasse. -Sinta-se a vontade para pular a historinha. - -.Uma contação de história -**** -Nosso _checkedlib.py_ usando `+__init_subclass__+` é um sucesso na empresa, e em qualquer dado momento nossos servidores de produção guardam milhões de instâncias de subclasses de `Checked` em suas memórias. - -Analisando o perfil de uma prova de conceito, descobrimos que usar `+__slots__+` pode reduzi os custos de hospedagem, por duas razões: - -* Menos uso de memória, já que as instâncias de `Checked` não precisarão manter seus próprios -`+__dict__+` -* Melhor desempenho, pela remoção de `+__setattr__+`, que foi criado só para bloquear atributos inesperados, mas é acionado na instanciação e para todas as definições de atributos antes de -`+Field.__set__+` ser chamado para realizar seu trabalho - -**** - - -O módulo _metaclass/checkedlib.py_, que estudaremos a seguir, é um substituto instantâneo para _initsub/checkedlib.py_. -Os doctests embutidos nos dois módulos são idênticos, bem como os arquivos _checkedlib_test.py_ para o _pytest_. - -A complexidade de _checkedlib.py_ é ocultada do usuário. -Aqui está o código-fonte de um script que usa o pacote: - -[source, python3] ----- -include::code/24-class-metaprog/checked/metaclass/checked_demo.py[tags=MOVIE_DEMO] ----- - -Essa definição concisa da classe `Movie` se vale de três instâncias do descritor de validação `Field`, uma configuração de `+__slots__+`, cinco métodos herdados de `Checked` e uma metaclasse para juntar tudo isso. -A única parte visível de `checkedlib` é a classe base `Checked`. - -Observe a <>. -A Notação Engenhocas e Bugigangas((("UML class diagrams", "annotated with MGN"))) complementa o diagrama de classes UML, tornando mais visível a relação entre classes e instâncias. - -Por exemplo, uma classe `Movie` usando a nova _checkedlib.py_ é uma instância de `CheckedMeta` e uma subclasse de `Checked`. -Adicionalmente, os atributos de classe `title`, `year` e `box_office` de `Movie` são três instâncias diferentes de `Field`. -Cada instância de `Movie` tem seus próprios atributos `_title`, `_year` e `_box_office`, para armazenar os valores dos campos correspondentes. - -Vamos agora estudar o código, começando pela classe `Field`, exibida no <>. - -A classe descritora `Field` agora está um pouco diferente. Nos exemplos anteriores, cada instância do descritor `Field` armazenava seu valor na instância gerenciada, usando um atributo de mesmo nome. Por exemplo, na classe `Movie`, o descritor `title` armazenava o valor do campo em um atributo `title` na instância gerenciada. -Isso tornava desnecessário que `Field` implementasse um método `+__get__+`. - -Entretanto, quando uma classe como `Movie` usa `+__slots__+`, ela não pode ter atributos de classe e atributos de instância com o mesmo nome. Cada instância do descritor é um atributo de classe, e agora precisamos de atributos de armazenamento separados em cada instância. O código usa o nome do descritor prefixado por um único `_`. -Portanto, instâncias de `Field` têm atributos `name` e `storage_name` distintos, e implementamos -`+Field.__get__+`. - -[[checkedlib_uml_mgn]] -.Diagrama de classes UML com MGN: a meta-engenhoca `CheckedMeta` cria a engenhoca `Movie`. A engenhoca `Field` cria os descritores `title`, `year`, e `box_office`, que são atributos de classe de `Movie`. Os dados de cada instância para os campos são armazenados nos atributos de instância `+_title+`, `+_year+` e `+_box_office+` de `Movie`. Observe a fronteira do pacote `checkedlib`. O desenvolvedor de `Movie` não precisa entender todo o maquinário dentro de _checkedlib.py_. -image::images/flpy_2404.png[Diagrama de classes UML+MGN para `CheckedMeta`, `Movie` etc.] - -O <> mostra o código-fonte de `Field`, com os textos explicativos descrevendo apenas as mudanças nessa versão. - -[[checked_field_meta_ex]] -.metaclass/checkedlib.py: o descritor `Field` com `storage_name` e `+__get__+` -==== -[source, py] ----- -include::code/24-class-metaprog/checked/metaclass/checkedlib.py[tags=CHECKED_FIELD] ----- -==== -<1> Determina `storage_name` a partir do argumento `name`. -<2> Se `+__get__+` recebe `None` como argumento `instance`, o descritor está sendo lido desde a própria classe gerenciada, não de uma instância gerenciada. Neste caso devolvemos o descritor. -<3> Caso contrário, devolve o valor armazenado no atributo chamado `storage_name`. -<4> `+__set__+` agora usa `setattr` para definir ou atualizar o atributo gerenciado. - -O <> mostra o código para a metaclasse que controla este exemplo. - -[[checked_metaclass_ex]] -.metaclass/checkedlib.py: tha metaclasse `CheckedMeta` -==== -[source, py] ----- -include::code/24-class-metaprog/checked/metaclass/checkedlib.py[tags=CHECKED_META] ----- -==== -<1> `+__new__+` é o único método implementado em `CheckedMeta`. -<2> Só melhora a classe se seu `cls_dict` não incluir `+__slots__+`. Se `+__slots__+` já está presente, assume que essa é a classe base `Checked` e não uma subclasse definida pelo usuário, e cria a classe sem modificações. -<3> Nos exemplos anteriores usamos `typing.get_type_hints` para obter as dicas de tipo, mas aquilo exige um classe existente como primeiro argumento. Neste ponto, a classe que estamos configurando ainda não existe, então precisamos recuperar `+__annotations__+` diretamente do `cls_dict`—o espaço de nomes da classe em construção, que o Python passa como último argumento para o `+__new__+` da metaclasse. -<4> Itera sobre `type_hints` para... -<5> ...criar um `Field` para cada atributo anotado... -<6> ...sobrescreve o item correspondente em `cls_dict` com a instância de `Field`... -<7> ...e acrescenta o `storage_name` do campo à lista que usaremos para... -<8> ...preencher o `+__slots__+` no `cls_dict`—o espaço de nomes da classe em construção. -<9> Por fim, invocamos `+super().__new__+`. - -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: - -. 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__+`. -. A remoção de `+__setattr__+`, que se tornou redundante: o acréscimo de `+__slots__+` à classe definida pelo usuário impede a definição de atributos não declarados. - -O <> é a listagem completa da versão final de `Checked`. - -[[checked_baseclass_ex]] -.metaclass/checkedlib.py: a classe base `Checked` -==== -[source, py] ----- -include::code/24-class-metaprog/checked/metaclass/checkedlib.py[tags=CHECKED_CLASS] ----- -==== - -Isso conclui nossa terceira versão de uma fábrica de classes com descritores validados. - -A próxima seção trata de algumas questões gerais relacionadas a metaclasses.((("", startref="MCchecked24")))((("", startref="CMcheck24"))) - -[[metaclases_real_world_sec]] -=== Metaclasses no mundo real - -Metaclasses((("metaclasses", "considerations for use", id="MCconsider24")))((("class metaprogramming", "metaclass issues", id="CMissue24"))) são poderosas mas complexas. Antes de se decidir a implementar uma metaclasse, considere os pontos a seguir. - - -[[metaclass_modern_features_sec]] -==== Recursos modernos simplificam ou substituem as metaclasses - -Ao longo do tempo, vários casos de uso comum de metaclasses se tornaram redundantes devido a novos recursos da linguagem: - -Decoradores de classes:: Mais simples de entender que metaclasses, e com menor probabilidade de causar conflitos com classes base e metaclasses. - -`+__set_name__+`:: Elimina a necessidade de uma metaclasse com lógica personalizada para definir automaticamente o nome de um descritor.footnote:[Na primeira edição de _Python Fluente_, as versões mais avançadas da classe `LineItem` usavam uma metaclasse apenas para definir o nome do armazenamento dos atributos. Veja o código nas metaclasses do https://fpy.li/24-14[exemplo da comida a granel], no repositório de código da primeira edição.] - -`+__init_subclass__+`:: Fornece uma forma de personalizar a criação de classes que é transparente para o usuário final e ainda mais simples que um decorador—mas pode introduzir conflitos em uma hierarquia de classes complexa. - -O `dict` embutido preservando a ordem de inserção de chaves:: Eliminou((("keys", "preserving key insertion order"))) a principal razão para usar `+__prepare__+`: -fornecer um `OrderedDict` para armazenar o espaço de nomes de uma classe em construção. -O Python só invoca `+__prepare__+` em metaclasses e então, se fosse necessário processar o espaço de nomes da classe na ordem em que eles aparecem o código-fonte, antes do Python 3.6 era preciso usar uma metaclasse. - -Em 2021, todas as versões sob manutenção ativa do CPython suportam todos os recursos listados acima. - -Sigo defendendo esses recursos porque vejo muita complexidade desnecessária em nossa profissão, e as metaclasses são uma porta de entrada para a complexidade. - - -==== Metaclasses são um recurso estável da linguagem - -As metaclasses foram introduzidas no Python em 2002, junto com as assim chamadas "classes com novo estilo", descritores e propriedades. -together with so-called "new-style classes," descriptors, and properties. - -É impressionante que o exemplo do `MetaBunch`, postado pela primeira vez por Alex Martelli em julho de 2002, ainda funcione no Python 3.9—a única modificação sendo a forma de especificar a metaclasse a ser usada, algo que no Python 3 é feito com a sintaxe `class Bunch(metaclass=MetaBunch):`. - -Nenhum dos acréscimos que mencionei na seção <> quebrou código existente que usava metaclasses. Mas código legado com metaclasses frequentemente pode ser simplificado através do uso daqueles recursos, especialmente se for possível ignorar versões do Python anteriores à 3.6—versões que não são mais mantidas. - - -==== Uma classe só pode ter uma metaclasse - -Se sua declaração de classe envolver duas ou mais metaclasses, você verá essa intrigante mensagem de erro: - -[source] ----- -TypeError: metaclass conflict: the metaclass of a derived class -must be a (non-strict) subclass of the metaclasses of all its bases -(_TypeError: conflito de metaclasses: a metaclasse de uma classe derivada deve ser uma subclasse (não-estrita) das metaclasses de todas as suas bases) ----- - -Isso pode acontecer mesmo sem herança múltipla. -Por exemplo, a declaração abaixo pode gerar aquele `TypeError`: - -[source, python] ----- -class Record(abc.ABC, metaclass=PersistentMeta): - pass ----- - -Vimos que `abc.ABC` é uma instância da metaclasse `abc.ABCMeta`. -Se aquela metaclasse `Persistent` não for uma subclasse de `abc.ABCMeta`, -você tem um conflito de metaclasses. - -Há duas maneiras de lidar com esse erro: - -* Encontre outra forma de fazer o que precisa ser feito, evitando o uso de pelo menos uma das metaclasse envolvidas. -* Escreva a sua própria metaclasse `PersistentABCMeta` como uma subclasse tanto de `abc.ABCMeta` quanto de `PersistentMeta`, usando herança múltipla, e faça dela a única metaclasse de `Record`.footnote:[Se você sentiu vertigem ao ponderar sobre as implicações de herança múltipla com metaclasses, bom para você. Eu também passaria longe dessa solução.] - - -[TIP] -==== -Posso aceitar a solução de uma metaclasse com duas metaclasses base, implementada para atender um prazo. -Na minha experiência, a programação de metaclasses sempre leva mais tempo que o esperado, tornando essa abordagem arriscada ante um prazo inflexível. -Se você fizer isso e cumprir o prazo previsto, seu código pode conter bugs sutis. -E mesmo na ausência de bugs conhecidos, essa abordagem deveria ser considerada uma dívida técnica, pelo simples fato de ser difícil de entender e manter. -==== - - -==== Metaclasses devem ser detalhes de implementação - -Além de `type`, existem apenas outras seis metaclasses em toda a bilbioteca padrão do Python 3.9. -As metaclasses mais conhecidas provavelmnete são `abc.ABCMeta`, `typing.NamedTupleMeta` e -`enum.EnumMeta`. -Nenhuma delas foi projetada com a intenção de aparecer explicitamente no código do usuário. -Podemos considerá-las detalhes de implementação. - -Apesar de ser possível fazer metaprogramação bem maluca com metaclasses, é melhor se ater ao -https://fpy.li/24-15[princípio do menor espanto], de forma que a maioria dos usuários possa de fato considerar metaclasses detalhes de implementação.footnote:[Eu ganhei a vida por alguns anos escrevendo código para Django, antes de resolver estudar como os campos dos modelos Django eram implementados. Só então aprendi sobre descritores e metaclasses.] - -Nos últimos anos, algumas metaclasses na biblioteca padrão do Python foram substituídas por outros mecanismos, sem afetar a API pública de seus pacotes. A forma mais simples de resguardar essas APIs para o futuro é oferecer uma classe regular, da qual usuários podem então criar subclasses para acessar a funcionalidade fornecida pela metaclasse. como fizemos em nossos exemplos. - -Para encerrar nossa conversa sobre metaprogramação de classes, vou compartilhar com vocês o pequeno exemplo de metaclasse mais sofisticado que encontrei durante minha pesquisa para esse capítulo.((("", startref="CMissue24")))((("", startref="MCconsider24"))) - - -[[metahack_sec]] -=== Um _hack_ de metaclasse com pass:[__prepare__] - -Quando((("class metaprogramming", "__prepare__ method", id="CMjprepare24", secondary-sortas="prepare")))((("__prepare__", id="prepare24"))) atualizei esse capítulo para a segunda edição, precisava encontrar exemplos simples mas reveladores, para substituir o código de `LineItem` no exemplo da loja de comida a granel, que não precisava mais de metaclasses desde o Python 3.6. - -A ideia de metaclasse mais interessante e mais simples me foi dada por -João S. O. Bueno—mais conhecido como JS na comunidade Python brasileira. -Uma aplicação de sua ideia é criar uma classe que gera constantes numéricas automaticamente: - -[source, py] ----- -include::code/24-class-metaprog/autoconst/autoconst_demo.py[tags=AUTOCONST] ----- - -Sim, esse código funciona como exibido! Aquilo acima é um doctest em _autoconst_demo.py_. - -Aqui está a classe base fácil de usar `AutoConst` , e a metaclasse por trás dela, implementadas em _autoconst.py_: - -[source, py] ----- -include::code/24-class-metaprog/autoconst/autoconst.py[tags=AUTOCONST] ----- - -É só isso. - -Claramente, o truque está em `WilyDict`. - -Quando o Python processa o espaço de nomes da classe do usuário e lê `banana`, ele procura aquele nome no mapeamento fornecido por `+__prepare__+`: uma instância de `WilyDict`. -`WilyDict` implementa `+__missing__+`, tratado na seção <>. -A instância de `WilyDict` inicialmente não contém uma chave `'banana'`, então o método -`+__missing__+` é acionado. -Ele cria um item em tempo real, com a chave `'banana'` e o valor `0`, e devolve esse valor. -O Python se contenta com isso, e daí tenta recuperar `'coconut'`. -`WilyDict` imediatamente adiciona aquele item com o valor `1`, e o devolve. -O mesmo acontece com `'vanilla'`, que é então mapeado para `2`. - -[role="pagebreak-before less_space"] -Já vimos `+__prepare__+` e `+__missing__+` antes. -A verdadeira inovação é a forma como JS as juntou. - -Aqui está o código-fonte de `WilyDict`, também de _autoconst.py_: - -[source, py] ----- -include::code/24-class-metaprog/autoconst/autoconst.py[tags=WilyDict] ----- - -Enquanto experimentava, descobri que o Python procurava `+__name__+` no espaço de nomes da classe em construção, fazendo com que `WilyDict` acrescentasse um item `+__name__+` e incrementasse `+__next_value+`. -Eu então inseri uma instrução `if` em `+__missing__+`, para gerar um `KeyError` para chaves que se parecem com atributos _dunder_. - -O pacote _autoconst.py_ tanto exige quanto ilustra o mecanismo de criação dinâmica de classes do Python. - -Me diverti muito adicionando mais funcionalidades a `AutoConstMeta` e `AutoConst`, mas em vez de compartilhar meus experimentos, vou deixar vocês se divertirem, brincando com o _hack_ genial de JS. - -Aqui estão algumas ideias: - -* Torne possivel obter o nome da constante a partir do valor. Por exemplo, `Flavor[2]` devolveria `'vanilla'`. Você pode fazer isso implementando `+__getitem__+` em `AutoConstMeta`. Desde o -Python 3.9, épossível implementar -pass:[__class_getitem__] -na própria `AutoConst`. - -* Suporte a iteração sobre a classe, implementando `+__iter__+` na metaclasse. -Eu faria `+__iter__+` produzir as constantes na forma de pares `(name, value)`. - -* Implemente uma nova variante de `Enum`. Isso seria um empreeendimento complexo, pois o pacote `enum` está cheio de armadilhas, incluindo a metaclasse `EnumMeta`, com centenas de linhas de código e um método `+__prepare__+` nem um pouco trivial. - -Divirta-se! - -[NOTE] -==== -O método especial `+__class_getitem__+` foi introduzido no Python 3.9 para suportar tipos genéricos, como parte da https://fpy.li/pep585[PEP 585—Type Hinting Generics In Standard Collections (_Dicas de Tipos Genéricas em Coleções Padrão_)] (EN). -Graças a `+__class_getitem__+`, os desenvolvedores principais do Python não precisaram escrever uma nova metaclasse para que os tipos embutidos implementassem `+__getitem__+`, de modo que fosse possível escrever dicas de tipo genéricas, tal como `list[int]`. -Esse é um recurso restrito, mas representativo, de um caso de uso mais amplo para metaclasses: implementar operadores e outros métodos especiais para funcionarem a nível de classes, tal como fazer a própria classe iterável, como as subclasses de `Enum`.((("", startref="prepare24")))((("", startref="CMjprepare24"))) -==== - -=== Para encerrar - -Metaclasses, bem((("class metaprogramming", "useful applications of metaclasses")))((("metaclasses", "useful applications of"))) como decoradores de classes e `+__init_subclass__+`, são úteis para: - -- Registro de subclasses -- Validação estrutural de subclasses -- Aplicar decoradores a muitos métodos ao mesmo tempo -- Serialização de objetos -- 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] - -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 <>: - -[quote] -____ -E _não_ defina ABCs personalizadas (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 muito mais felizes se limitando a código simples e direto, e evitando tais profundezas. -____ - -Acredito que o conselho de Martelli se aplica não apenas a ABCs e metaclasses, -mas também a hierarquias de classe, sobrecarga de operadores, decoradores de funções, descritores, decoradores de classes e fábricas de classes usando `+__init_subclass__+`. - -Em princípio, essas poderosas ferramentas existem para suportar o desenvolvimento de bibliotecas e frameworks. -Naturalmente, as aplicações devem _usar_ tais ferramentas, na forma oferecida pela biblioteca padrão do Python ou por pacotes externos. -Mas _implementá-las_ em código de aplicações é frequentemente resultado de uma abstração prematura. - -[quote, David Heinemeier Hansson, criador do Ruby on Rails] -____ -Boas frameworks são extraídas, não inventadas.footnote:[Essa frase é muito citada. Encontrei uma citação direta antiga em um https://fpy.li/24-19[post] de 2005 no blog de DHH.] -____ - - -=== Resumo do capítulo - -Este((("class metaprogramming", "overview of"))) capítulo começou com uma revisão dos atributos encontrados em objetos classe, tais como `+__qualname__+` e o método `+__subclasses__()+`. -A seguir, vimos como a classe embutida `type` pode ser usada para criar classes durante a execução. - -O método especial `+__init_subclass__+` foi introduzido, com a primeira versão de uma classe base `Checked`, projetada para substituir dicas de tipo de atributos em subclasses definidas pelo usuário por instâncias de `Field`, que usam construtores para impor o tipo daqueles atributos durante a execução. - -A mesma ideia foi implementada com um decorador de classes `@checked`, que acrescenta recursos a classes definidas pelo usuário, de forma similar ao que pode ser feito com `+__init_subclass__+`. -Vimos que nem `+__init_subclass__+` nem um decorador de classes podem configurar `+__slots__+` dinamicamente, pois operam apenas após a criação da classe. - -Os conceitos de "[momento/tempo de] importação" e "[momento/tempo de] execução" foram esclarecidos com experimentos mostrando a ordem na qual o código Python é executado quando módulos, descritores, decoradores de classe e `+__init_subclass__+` estão envolvidos. - -Nossa exploração de metaclasses começou com um explicação geral de `type` como uma metaclasse, -e sobre como metaclasses definidas pelo usuário podem implementar `+__new__+`, para personalziar as classes que criam. -Vimos então nossa primeira metaclasse personalizada, o clássico exemplo `MetaBunch`, usando -`+__slots__+`. -A seguir, outro experimento com o tempo de avaliação demonstrou como os métodos `+__prepare__+` e -`+__new__+` de uma metaclasse são invocados mais cedo que `+__init_subclass__+` e decoradores de classe, oferecendo oportunidades para uma personalização de classes mais profunda. - -A terceira versão de uma fábrica de classes `Checked`, com descritores `Field` e uma configuração personalizada de `+__slots__+` foi apresentada, seguida de considerações gerais sobre o uso de metaclasses na prática. - -Por fim, vimos o hack `AutoConst`, inventado por João S. O. Bueno, baseado na brilhante ideia de uma metaclasse com `+__prepare__+` devolvendo um mapeamento que implementa `+__missing__+`. -Em menos de 20 linhas de código, _autoconst.py_ demonstra o poder da combinação de técnicas de metaprogramação no Python. - -Nunca encontrei outra linguagem como o Python, fácil para iniciantes, prática para profissionais e empolgante para hackers. -Obrigado, Guido van Rossum e todos que a fazem ser assim. - - -[[ch21-furtherreading]] -=== Leitura complementar - -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 do 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 do Python_ também são leituras fundamentais. - -Na _Biblioteca Padrão do 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`. - -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 implemetaçã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 muito mais simples—de decorador de classes na bilbioteca padrão do 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. - -Para metaclasses, a principal referência na documentação do Python é a -https://fpy.li/pep3115[PEP 3115—Metaclasses in Python 3000 (_Metaclasses no Python 3000_)], -onde o método especial `+__prepare__+` foi introduzido. - -O pass:[Python in a Nutshell], 3ª ed., de Alex Martelli, Anna Ravenscroft, e Steve Holden, é uma referência, mas foi escrito antes da https://fpy.li/pep487[PEP 487—Simpler customization of class creation (_PEP 487—Uma personalização mais simples da criação de classes_)] ser publicada. O principal exemplo de metaclasse no livro—`MetaBunch`—ainda é válido, pois não pode ser escrito com mecanismos mais simples. -O pass:[Effective Python], 2ª ed. (Addison-Wesley), de Brett Slatkin, traz vários exemplos atualizados de técnicas de criação de classes, incluindo metaclasses. - -[role="pagebreak-before less_space"] -Para aprender sobre as origens da metaprogramação de classes no Python, recomento o artigo de Guido van Rossum de 2003, -https://fpy.li/24-28["Unifying types and classes in Python 2.2" (_Unificando tipos e classes no Python 2.2_)] (EN). -O texto se aplica também ao Python moderno, pois cobre o quê era então chamado de "novo estilo" de semântica de classes—a semântica default no Python 3—incluindo descritores e metaclasses. -Uma das referências citadas por Guido é -_Putting Metaclasses to Work: a New Dimension in Object-Oriented Programming_, -de Ira R. Forman e Scott H. Danforth (Addison-Wesley), -livro para o qual ele deu cinco estrelas na _Amazon.com_, acrescentando o seguinte comentário: - - -[quote] -____ -*Este livro contribuiu para o projeto das metaclasses no Python 2.2* - -Pena que esteja fora de catálogo; sempre me refiro a ele como o melhor tutorial que conheço para o difícil tópico da herança múltipla cooperativa, suportada pelo Python através da função `super()`.footnote:[Comprei um exemplar usado, e achei uma leitura muito desafiadora.] -____ - -Se você gosta de metaprogramação, talvez gostaria que o Python suportasse o recurso definitivo de metaprogramação: macros sintáticas, como as oferecidas pela família de linguagens Lisp e—mais recentemente—pelo Elixir e pelo Rust. -Macros sintáticas são mais poderosas e menos sujeitas a erros que as macros primitivas de substituição de código da linguagem C. -Elas são funções especiais que reescrevem código-fonte para código padronizado, usando uma sintaxe personalizada, antes da etapa de compilação, -permitindo a desenvolvedores introduzir novas estruturas na linguagem sem modificar o compilador. -Como a sobrecarga de operadores, macros sintáticas podem ser mal usadas. -Mas, desde que a comunidade entenda e gerencie as desvantagens, elas suportam abstrações poderosas e amigáveis, como as DSLs (Domain-Specific Languages—_Linguagens de Domínio Específico_). -Em setembro de 2020, Marc Shannon, um dos desenvolvedores principais do Python, publicou a -https://fpy.li/pep638[PEP 638—Syntactic Macros (_Macros Sintáticas_)] (EN), defendendo exatamente isso. -Um ano após sua publicação inicial (quando escrevo essas linhas), a PEP 638 ainda era um rascunho e não havia discussões contínuas sobre ela. -Claramente não é uma prioridade muito alta entre os desenvolvedores principais do Python. -Eu gostaria de ver a PEP 638 sendo melhor discutida e, por fim, aprovada. -Macros sintáticas permitiriam à comunidade Python experimentar com novos recursos controversos, tal como o "operador morsa" (_operador walrus_) (https://fpy.li/pep572[PEP 572] (EN)), -correspondência/casamento de padrões (https://fpy.li/pep634[PEP 634] (EN)) e regras alternativas para avaliação de dicas de tipo -(PEPs https://fpy.li/pep563[563] (EN) e -https://fpy.li/pep649[649] (EN)), -antes que se fizessem modificações permanentes no núcleo da linguagem. -Nesse meio tempo, podemos sentir o gosto das macros sintáticas com o pacote -https://fpy.li/24-29[MacroPy]. - - -[role="pagebreak-before less_space"] -.Ponto de vista -**** - -Vou((("class metaprogramming", "Soapbox discussion")))((("Soapbox sidebars", "programming language design"))) iniciar o último ponto de vista no livro com uma longa citação de Brian Harvey e Matthew Wright, dois professores de ciência da computação da Universidade da California (Berkeley e Santa Barbara). Em seu livro, _Simply Scheme: Introducing Computer Science_ ("Simplesmente Scheme: Introduzindo a Ciência da Computação") (MIT Press), Harvey e Wright escreveram: - - -[quote, Brian Harvey and Matthew Wright, no prefácio de Simply Scheme] -____ -Há duas escolas de pensamento sobre o ensino de ciência da computação. Podemos representar as duas visões de uma forma caricatual, assim: - -. *A visão conservadora*: Programas de computador se tornaram muito grandes e complexos para serem apreendidos pela mente humana. Portanto, a tarefa da educação na ciência da computação é ensinar os estudantes como se disciplinarem, de tal forma que 500 programadores medíocres possam se juntar e produzir um programa que atende suas especificações. -. *A visão radical*: Programas de computador se tornaram muito grandes e complexos para serem apreendidos pela mente humana. Portanto, a tarefa da educação na ciência da computação é ensinar os estudantes como expandir suas mentes até que os programas caibam ali, aprendendo a pensar com um vocabulário de ideias maiores, mais poderosas e mais flexíveis que aquelas óbvias. Cada unidade de pensamento programático deve gerar uma grande recompensa para as capacidades do programa.footnote:[Brian Harvey e Matthew Wright, _Simply Scheme_ (MIT Press, 1999), p. xvii. O texto completo está disponível em https://fpy.li/24-30[Berkeley.edu] (EN).] -____ - -As descrições exageradas de Harvey e Wright versam sobre o ensino de ciência da computação, mas também se aplicam ao projeto de linguagens de programação. Nesse ponto você já deve ter adivinhado que eu concordo com a visão "radical", e acredito que o Python foi projetado naquele espírito. - -A ideia de propriedade é um grande passo adiante, comparado com a abordagem "métodos de acesso desde o início", praticamente exigida em Java e suportada pela geração de _getters/setters_ através de atalhos do teclado por IDEs Java. A principal vantagem das propriedades é nos permitir começar a criar nossos programas simplesmente expondo atributos publicamente—no espírito do _KISS_—, sabendo que um atributo público pode se tornar uma propriedade a qualquer momento sem muita dificuldade. Mas a ideia de descritor vai muito além disso, fornecendo uma framework para abstrair lógica de acesso repetitiva. Essa framework é tão eficiente que mecanismos essenciais do Python a usam por baixo dos panos. - -Outra ideia poderosa são as funções como objetos de primeira classe, pavimentando o caminho para funções de ordem superior. E acontece que a combinação de descritores e funções de ordem superior permite a unificação de funções e métodos. O `+__get__+` de uma função produz um objeto método em tempo real, vinculando a instância ao argumento `self`. Isso é elegante.footnote:[__Machine Beauty: Elegance and the Heart of Technology__ ("Beleza de Máquina: A Elegância e o Coração da Tecnologia"), de David Gelernter (Basic Books), começa com uma discussão intrigante sobre elegância e estética em obras de engenharia, de pontes a software. Os capítulos posteriores não são tão bons, mas o início vale o preço.] - -Por fim, temos((("data structures", see="also data class builders; dictionaries and sets; object references; sequences; Unicode text versus bytes")))((("functions, as first-class objects", see="also decorators and closures")))((("control flow", see="also asynchronous programming; concurrent executors; concurrency models; iterators; generators; with, match, and else blocks")))((("metaprogramming", see="also attribute descriptors; class metaprogramming; dynamic attributes and properties")))((("sets", see="also dictionaries and sets")))((("frozenset", see="also dictionaries and sets")))((("byte sequences", see="also Unicode text versus bytes")))((("text files, handling", see="also Unicode text versus bytes")))((("encoding", see="also Unicode text versus bytes")))((("decoding", see="also Unicode text versus bytes")))((("NFC", see="Normalization Form C")))((("UCA", see="Unicode Collation Algorithm")))((("mutable objects", see="also object references")))((("first-class functions", see="functions, as first-class objects")))((("closures", see="decorators and closures")))((("function decorators", see="decorators and closures")))((("design patterns", see="functions, design patterns with first-class")))((("Pythonic objects", see="also objects")))((("patterns", see="functions, design patterns with first-class; pattern matching")))((("special methods", see="also sequences, special methods for")))((("protocols", see="also interfaces")))((("typing map", see="also type hints (type annotations)")))((("interfaces", see="also goose typing; protocols")))((("subclassing", see="inheritance and subclassing")))((("MRO", see="method resolution order")))((("multiple inheritance", see="also inheritance and subclassing")))((("gradual type system", see="also type hints (type annotations)")))((("type hints (type annotations)", "gradual typing", see="also gradual type system")))((("match blocks", see="with, match, and else blocks")))((("spinners (loading indicators)", see="also network I/O")))((("executors, delegating tasks to", see="also concurrent executors")))((("attributes", see="also attribute descriptors; dynamic attributes and properties")))((("attribute descriptors", see="also attributes; dynamic attributes and properties")))((("properties", see="computed properties; dynamic attributes and properties")))((("descriptors", see="also attribute descriptors")))((("PUG", see="Python Users Group")))((("PSF", see="Python Software Foundation")))((("I/O (input/output)", see="network I/O")))((("LRU", see="Least Recently Used")))((("classes", see="also protocols")))((("binary sequences", see="also Unicode text versus bytes")))((("data model", see="Python Data Model")))((("data attributes", see="attributes")))((("data descriptors", see="overriding descriptors")))((("recycling", see="garbage collection")))((("methods, as callable objects", see="also sequences, special methods for; special methods"))) a ideia de classes como objetos de primeira classe. -É uma façanha marcante do projeto, que uma linguagem acessível para um iniciante forneça abstrações poderosas, tais como fábricas de classe, decoradores de classe, e metaclasses completas e definidas pelo usuário. -Melhor ainda, os recursos avançados estão integrados de forma a não afetar a adequação do Python para programação casual (eles na verdade ajudam nisso, por trás da cortina). -A conveniência e o sucesso de frameworks como o Django e o SQLAlchemy devem muito às metaclasses. -Ao longo dos anos, a metaprogramação de classes em Python está se tornando cada vez mais simples, pelo menos para os casos de uso comuns. -Os melhores recursos da linguagem são aqueles que beneficiam a todos, mesmo que alguns usuários do Python não os conheçam. Mas esses usuários sempre podem aprender, e criar a próxima grande biblioteca. - -Espero notícias sobre suas contribuições ao ecossistema e à comunidade do Python! -**** diff --git a/capitulos/code/01-data-model/README.md b/code/01-data-model/README.md similarity index 100% rename from capitulos/code/01-data-model/README.md rename to code/01-data-model/README.md diff --git a/capitulos/code/01-data-model/data-model.ipynb b/code/01-data-model/data-model.ipynb similarity index 100% rename from capitulos/code/01-data-model/data-model.ipynb rename to code/01-data-model/data-model.ipynb diff --git a/capitulos/code/01-data-model/frenchdeck.doctest b/code/01-data-model/frenchdeck.doctest similarity index 100% rename from capitulos/code/01-data-model/frenchdeck.doctest rename to code/01-data-model/frenchdeck.doctest diff --git a/capitulos/code/01-data-model/frenchdeck.py b/code/01-data-model/frenchdeck.py similarity index 100% rename from capitulos/code/01-data-model/frenchdeck.py rename to code/01-data-model/frenchdeck.py diff --git a/capitulos/code/01-data-model/test.sh b/code/01-data-model/test.sh similarity index 100% rename from capitulos/code/01-data-model/test.sh rename to code/01-data-model/test.sh diff --git a/capitulos/code/01-data-model/vector2d.doctest b/code/01-data-model/vector2d.doctest similarity index 100% rename from capitulos/code/01-data-model/vector2d.doctest rename to code/01-data-model/vector2d.doctest diff --git a/capitulos/code/01-data-model/vector2d.py b/code/01-data-model/vector2d.py similarity index 100% rename from capitulos/code/01-data-model/vector2d.py rename to code/01-data-model/vector2d.py diff --git a/code/01-data-model/vector2d_pt.py b/code/01-data-model/vector2d_pt.py new file mode 100644 index 00000000..4780e45d --- /dev/null +++ b/code/01-data-model/vector2d_pt.py @@ -0,0 +1,55 @@ +""" +vector2d.py: classe simples usando métodos especiais + +Classe simplificada por motivos didáticos. +Falta tratamento de erro nos métodos `__add__` e `__mul__`. + +Em outras partes do livro faremos esse código ficar +mais robusto e flexível. + +Soma:: + + >>> v1 = Vector(2, 4) + >>> v2 = Vector(2, 1) + >>> v1 + v2 + Vector(4, 5) + +Valor absoluto ou módulo:: + + >>> v = Vector(3, 4) + >>> abs(v) + 5.0 + +Multiplicação escalar:: + + >>> v * 3 + Vector(9, 12) + >>> abs(v * 3) + 15.0 + +""" + +import math + +class Vector: + + def __init__(self, x=0, y=0): + self.x = x + self.y = y + + def __repr__(self): + return f'Vector({self.x!r}, {self.y!r})' + + def __abs__(self): + return math.hypot(self.x, self.y) + + def __bool__(self): + return bool(abs(self)) + + def __add__(self, other): + x = self.x + other.x + y = self.y + other.y + return Vector(x, y) + + def __mul__(self, scalar): + return Vector(self.x * scalar, self.y * scalar) diff --git a/capitulos/code/02-array-seq/README.rst b/code/02-array-seq/README.rst similarity index 100% rename from capitulos/code/02-array-seq/README.rst rename to code/02-array-seq/README.rst diff --git a/capitulos/code/02-array-seq/array-seq.ipynb b/code/02-array-seq/array-seq.ipynb similarity index 100% rename from capitulos/code/02-array-seq/array-seq.ipynb rename to code/02-array-seq/array-seq.ipynb diff --git a/capitulos/code/02-array-seq/bisect_demo.py b/code/02-array-seq/bisect_demo.py similarity index 100% rename from capitulos/code/02-array-seq/bisect_demo.py rename to code/02-array-seq/bisect_demo.py diff --git a/capitulos/code/02-array-seq/bisect_insort.py b/code/02-array-seq/bisect_insort.py similarity index 100% rename from capitulos/code/02-array-seq/bisect_insort.py rename to code/02-array-seq/bisect_insort.py diff --git a/capitulos/code/02-array-seq/lispy/py3.10/examples_test.py b/code/02-array-seq/lispy/py3.10/examples_test.py similarity index 100% rename from capitulos/code/02-array-seq/lispy/py3.10/examples_test.py rename to code/02-array-seq/lispy/py3.10/examples_test.py diff --git a/capitulos/code/02-array-seq/lispy/py3.10/lis.py b/code/02-array-seq/lispy/py3.10/lis.py similarity index 100% rename from capitulos/code/02-array-seq/lispy/py3.10/lis.py rename to code/02-array-seq/lispy/py3.10/lis.py diff --git a/capitulos/code/02-array-seq/lispy/py3.10/lis_test.py b/code/02-array-seq/lispy/py3.10/lis_test.py similarity index 100% rename from capitulos/code/02-array-seq/lispy/py3.10/lis_test.py rename to code/02-array-seq/lispy/py3.10/lis_test.py diff --git a/capitulos/code/02-array-seq/lispy/py3.10/quicksort.scm b/code/02-array-seq/lispy/py3.10/quicksort.scm similarity index 100% rename from capitulos/code/02-array-seq/lispy/py3.10/quicksort.scm rename to code/02-array-seq/lispy/py3.10/quicksort.scm diff --git a/capitulos/code/02-array-seq/lispy/py3.9/README.md b/code/02-array-seq/lispy/py3.9/README.md similarity index 100% rename from capitulos/code/02-array-seq/lispy/py3.9/README.md rename to code/02-array-seq/lispy/py3.9/README.md diff --git a/capitulos/code/02-array-seq/lispy/py3.9/examples_test.py b/code/02-array-seq/lispy/py3.9/examples_test.py similarity index 100% rename from capitulos/code/02-array-seq/lispy/py3.9/examples_test.py rename to code/02-array-seq/lispy/py3.9/examples_test.py diff --git a/capitulos/code/02-array-seq/lispy/py3.9/lis.py b/code/02-array-seq/lispy/py3.9/lis.py similarity index 100% rename from capitulos/code/02-array-seq/lispy/py3.9/lis.py rename to code/02-array-seq/lispy/py3.9/lis.py diff --git a/capitulos/code/02-array-seq/lispy/py3.9/lis_test.py b/code/02-array-seq/lispy/py3.9/lis_test.py similarity index 100% rename from capitulos/code/02-array-seq/lispy/py3.9/lis_test.py rename to code/02-array-seq/lispy/py3.9/lis_test.py diff --git a/capitulos/code/02-array-seq/listcomp_speed.py b/code/02-array-seq/listcomp_speed.py similarity index 100% rename from capitulos/code/02-array-seq/listcomp_speed.py rename to code/02-array-seq/listcomp_speed.py diff --git a/capitulos/code/02-array-seq/match_lat_lon.py b/code/02-array-seq/match_lat_lon.py similarity index 100% rename from capitulos/code/02-array-seq/match_lat_lon.py rename to code/02-array-seq/match_lat_lon.py diff --git a/capitulos/code/02-array-seq/memoryviews.ipynb b/code/02-array-seq/memoryviews.ipynb similarity index 100% rename from capitulos/code/02-array-seq/memoryviews.ipynb rename to code/02-array-seq/memoryviews.ipynb diff --git a/capitulos/code/02-array-seq/metro_lat_lon.py b/code/02-array-seq/metro_lat_lon.py similarity index 100% rename from capitulos/code/02-array-seq/metro_lat_lon.py rename to code/02-array-seq/metro_lat_lon.py diff --git a/capitulos/code/02-array-seq/test.sh b/code/02-array-seq/test.sh similarity index 100% rename from capitulos/code/02-array-seq/test.sh rename to code/02-array-seq/test.sh diff --git a/capitulos/code/03-dict-set/03-dict-set.ipynb b/code/03-dict-set/03-dict-set.ipynb similarity index 100% rename from capitulos/code/03-dict-set/03-dict-set.ipynb rename to code/03-dict-set/03-dict-set.ipynb diff --git a/capitulos/code/03-dict-set/README.md b/code/03-dict-set/README.md similarity index 100% rename from capitulos/code/03-dict-set/README.md rename to code/03-dict-set/README.md diff --git a/capitulos/code/03-dict-set/dialcodes.py b/code/03-dict-set/dialcodes.py similarity index 100% rename from capitulos/code/03-dict-set/dialcodes.py rename to code/03-dict-set/dialcodes.py diff --git a/capitulos/code/03-dict-set/index.py b/code/03-dict-set/index.py similarity index 100% rename from capitulos/code/03-dict-set/index.py rename to code/03-dict-set/index.py diff --git a/capitulos/code/03-dict-set/index0.py b/code/03-dict-set/index0.py similarity index 100% rename from capitulos/code/03-dict-set/index0.py rename to code/03-dict-set/index0.py diff --git a/capitulos/code/03-dict-set/index_default.py b/code/03-dict-set/index_default.py similarity index 100% rename from capitulos/code/03-dict-set/index_default.py rename to code/03-dict-set/index_default.py diff --git a/capitulos/code/03-dict-set/missing.py b/code/03-dict-set/missing.py similarity index 100% rename from capitulos/code/03-dict-set/missing.py rename to code/03-dict-set/missing.py diff --git a/capitulos/code/03-dict-set/py3.10/creator.py b/code/03-dict-set/py3.10/creator.py similarity index 74% rename from capitulos/code/03-dict-set/py3.10/creator.py rename to code/03-dict-set/py3.10/creator.py index 97d6bdef..9680adcf 100644 --- a/capitulos/code/03-dict-set/py3.10/creator.py +++ b/code/03-dict-set/py3.10/creator.py @@ -27,14 +27,14 @@ # tag::DICT_MATCH[] def get_creators(record: dict) -> list: match record: - case {'type': 'book', 'api': 2, 'authors': [*names]}: # <1> + case {'type': 'book', 'api': 2, 'authors': [*names]}: # <1> return names - case {'type': 'book', 'api': 1, 'author': name}: # <2> + case {'type': 'book', 'api': 1, 'author': name}: # <2> return [name] - case {'type': 'book'}: # <3> + case {'type': 'book'}: # <3> raise ValueError(f"Invalid 'book' record: {record!r}") - case {'type': 'movie', 'director': name}: # <4> + case {'type': 'movie', 'director': name}: # <4> return [name] - case _: # <5> + case _: # <5> raise ValueError(f'Invalid record: {record!r}') # end::DICT_MATCH[] diff --git a/capitulos/code/03-dict-set/strkeydict.py b/code/03-dict-set/strkeydict.py similarity index 86% rename from capitulos/code/03-dict-set/strkeydict.py rename to code/03-dict-set/strkeydict.py index ba821331..5b868470 100644 --- a/capitulos/code/03-dict-set/strkeydict.py +++ b/code/03-dict-set/strkeydict.py @@ -42,15 +42,15 @@ Tests for update using a `dict` or a sequence of pairs:: >>> d.update({6:'six', '8':'eight'}) - >>> sorted(d.keys()) - ['0', '2', '4', '6', '8'] + >>> list(d.keys()) + ['2', '4', '0', '6', '8'] >>> d.update([(10, 'ten'), ('12', 'twelve')]) - >>> sorted(d.keys()) - ['0', '10', '12', '2', '4', '6', '8'] - >>> d.update([1, 3, 5]) + >>> list(d.keys()) + ['2', '4', '0', '6', '8', '10', '12'] + >>> d.update([1, 3, 5]) # doctest: +ELLIPSIS Traceback (most recent call last): ... - TypeError: 'int' object is not iterable + TypeError: ...iterable... """ # tag::STRKEYDICT[] @@ -70,5 +70,4 @@ def __contains__(self, key): def __setitem__(self, key, item): self.data[str(key)] = item # <4> - # end::STRKEYDICT[] diff --git a/capitulos/code/03-dict-set/strkeydict0.py b/code/03-dict-set/strkeydict0.py similarity index 87% rename from capitulos/code/03-dict-set/strkeydict0.py rename to code/03-dict-set/strkeydict0.py index 7062b5ea..90c368d4 100644 --- a/capitulos/code/03-dict-set/strkeydict0.py +++ b/code/03-dict-set/strkeydict0.py @@ -2,7 +2,7 @@ # tag::STRKEYDICT0_TESTS[] -Tests for item retrieval using `d[key]` notation:: +Acesso a item com a notação `d[key]`: >>> d = StrKeyDict0([('2', 'two'), ('4', 'four')]) >>> d['2'] @@ -14,7 +14,7 @@ ... KeyError: '1' -Tests for item retrieval using `d.get(key)` notation:: +Acesso a item com a notação `d.get(key)`: >>> d.get('2') 'two' @@ -24,7 +24,7 @@ 'N/A' -Tests for the `in` operator:: +Operador `in`: >>> 2 in d True diff --git a/capitulos/code/03-dict-set/support/container_perftest.py b/code/03-dict-set/support/container_perftest.py similarity index 100% rename from capitulos/code/03-dict-set/support/container_perftest.py rename to code/03-dict-set/support/container_perftest.py diff --git a/capitulos/code/03-dict-set/support/container_perftest_datagen.py b/code/03-dict-set/support/container_perftest_datagen.py similarity index 100% rename from capitulos/code/03-dict-set/support/container_perftest_datagen.py rename to code/03-dict-set/support/container_perftest_datagen.py diff --git a/capitulos/code/03-dict-set/support/hashdiff.py b/code/03-dict-set/support/hashdiff.py similarity index 100% rename from capitulos/code/03-dict-set/support/hashdiff.py rename to code/03-dict-set/support/hashdiff.py diff --git a/capitulos/code/03-dict-set/transformdict.py b/code/03-dict-set/transformdict.py similarity index 100% rename from capitulos/code/03-dict-set/transformdict.py rename to code/03-dict-set/transformdict.py diff --git a/capitulos/code/03-dict-set/zen.txt b/code/03-dict-set/zen.txt similarity index 100% rename from capitulos/code/03-dict-set/zen.txt rename to code/03-dict-set/zen.txt diff --git a/capitulos/code/04-text-byte/.gitignore b/code/04-text-byte/.gitignore similarity index 100% rename from capitulos/code/04-text-byte/.gitignore rename to code/04-text-byte/.gitignore diff --git a/capitulos/code/04-text-byte/README.rst b/code/04-text-byte/README.rst similarity index 100% rename from capitulos/code/04-text-byte/README.rst rename to code/04-text-byte/README.rst diff --git a/capitulos/code/04-text-byte/categories.py b/code/04-text-byte/categories.py similarity index 100% rename from capitulos/code/04-text-byte/categories.py rename to code/04-text-byte/categories.py diff --git a/capitulos/code/04-text-byte/charfinder/README.rst b/code/04-text-byte/charfinder/README.rst similarity index 100% rename from capitulos/code/04-text-byte/charfinder/README.rst rename to code/04-text-byte/charfinder/README.rst diff --git a/capitulos/code/04-text-byte/charfinder/cf.py b/code/04-text-byte/charfinder/cf.py similarity index 100% rename from capitulos/code/04-text-byte/charfinder/cf.py rename to code/04-text-byte/charfinder/cf.py diff --git a/capitulos/code/04-text-byte/charfinder/test.sh b/code/04-text-byte/charfinder/test.sh similarity index 100% rename from capitulos/code/04-text-byte/charfinder/test.sh rename to code/04-text-byte/charfinder/test.sh diff --git a/capitulos/code/04-text-byte/default_encodings.py b/code/04-text-byte/default_encodings.py similarity index 100% rename from capitulos/code/04-text-byte/default_encodings.py rename to code/04-text-byte/default_encodings.py diff --git a/capitulos/code/04-text-byte/encodings-win10.txt b/code/04-text-byte/encodings-win10.txt similarity index 100% rename from capitulos/code/04-text-byte/encodings-win10.txt rename to code/04-text-byte/encodings-win10.txt diff --git a/capitulos/code/04-text-byte/locale_sort.py b/code/04-text-byte/locale_sort.py similarity index 100% rename from capitulos/code/04-text-byte/locale_sort.py rename to code/04-text-byte/locale_sort.py diff --git a/capitulos/code/04-text-byte/normeq.py b/code/04-text-byte/normeq.py similarity index 100% rename from capitulos/code/04-text-byte/normeq.py rename to code/04-text-byte/normeq.py diff --git a/capitulos/code/04-text-byte/numerics_demo.py b/code/04-text-byte/numerics_demo.py similarity index 100% rename from capitulos/code/04-text-byte/numerics_demo.py rename to code/04-text-byte/numerics_demo.py diff --git a/capitulos/code/04-text-byte/ola.py b/code/04-text-byte/ola.py similarity index 100% rename from capitulos/code/04-text-byte/ola.py rename to code/04-text-byte/ola.py diff --git a/capitulos/code/04-text-byte/ramanujan.py b/code/04-text-byte/ramanujan.py similarity index 100% rename from capitulos/code/04-text-byte/ramanujan.py rename to code/04-text-byte/ramanujan.py diff --git a/capitulos/code/04-text-byte/simplify.py b/code/04-text-byte/simplify.py similarity index 100% rename from capitulos/code/04-text-byte/simplify.py rename to code/04-text-byte/simplify.py diff --git a/capitulos/code/04-text-byte/skin.py b/code/04-text-byte/skin.py similarity index 100% rename from capitulos/code/04-text-byte/skin.py rename to code/04-text-byte/skin.py diff --git a/capitulos/code/04-text-byte/stdout_check.py b/code/04-text-byte/stdout_check.py similarity index 100% rename from capitulos/code/04-text-byte/stdout_check.py rename to code/04-text-byte/stdout_check.py diff --git a/capitulos/code/04-text-byte/syntax-msg.txt b/code/04-text-byte/syntax-msg.txt similarity index 100% rename from capitulos/code/04-text-byte/syntax-msg.txt rename to code/04-text-byte/syntax-msg.txt diff --git a/capitulos/code/04-text-byte/two_flags.py b/code/04-text-byte/two_flags.py similarity index 100% rename from capitulos/code/04-text-byte/two_flags.py rename to code/04-text-byte/two_flags.py diff --git a/capitulos/code/04-text-byte/zwj_sample.ipynb b/code/04-text-byte/zwj_sample.ipynb similarity index 100% rename from capitulos/code/04-text-byte/zwj_sample.ipynb rename to code/04-text-byte/zwj_sample.ipynb diff --git a/capitulos/code/04-text-byte/zwj_sample.png b/code/04-text-byte/zwj_sample.png similarity index 100% rename from capitulos/code/04-text-byte/zwj_sample.png rename to code/04-text-byte/zwj_sample.png diff --git a/capitulos/code/04-text-byte/zwj_sample.py b/code/04-text-byte/zwj_sample.py similarity index 100% rename from capitulos/code/04-text-byte/zwj_sample.py rename to code/04-text-byte/zwj_sample.py diff --git a/capitulos/code/05-data-classes/README.asciidoc b/code/05-data-classes/README.asciidoc similarity index 100% rename from capitulos/code/05-data-classes/README.asciidoc rename to code/05-data-classes/README.asciidoc diff --git a/capitulos/code/05-data-classes/cards.doctest b/code/05-data-classes/cards.doctest similarity index 100% rename from capitulos/code/05-data-classes/cards.doctest rename to code/05-data-classes/cards.doctest diff --git a/capitulos/code/05-data-classes/cards.py b/code/05-data-classes/cards.py similarity index 100% rename from capitulos/code/05-data-classes/cards.py rename to code/05-data-classes/cards.py diff --git a/capitulos/code/05-data-classes/cards_enum.py b/code/05-data-classes/cards_enum.py similarity index 100% rename from capitulos/code/05-data-classes/cards_enum.py rename to code/05-data-classes/cards_enum.py diff --git a/capitulos/code/05-data-classes/class/coordinates.py b/code/05-data-classes/class/coordinates.py similarity index 100% rename from capitulos/code/05-data-classes/class/coordinates.py rename to code/05-data-classes/class/coordinates.py diff --git a/capitulos/code/05-data-classes/dataclass/club.py b/code/05-data-classes/dataclass/club.py similarity index 100% rename from capitulos/code/05-data-classes/dataclass/club.py rename to code/05-data-classes/dataclass/club.py diff --git a/capitulos/code/05-data-classes/dataclass/club_generic.py b/code/05-data-classes/dataclass/club_generic.py similarity index 100% rename from capitulos/code/05-data-classes/dataclass/club_generic.py rename to code/05-data-classes/dataclass/club_generic.py diff --git a/capitulos/code/05-data-classes/dataclass/club_wrong.py b/code/05-data-classes/dataclass/club_wrong.py similarity index 100% rename from capitulos/code/05-data-classes/dataclass/club_wrong.py rename to code/05-data-classes/dataclass/club_wrong.py diff --git a/capitulos/code/05-data-classes/dataclass/coordinates.py b/code/05-data-classes/dataclass/coordinates.py similarity index 100% rename from capitulos/code/05-data-classes/dataclass/coordinates.py rename to code/05-data-classes/dataclass/coordinates.py diff --git a/capitulos/code/05-data-classes/dataclass/hackerclub.py b/code/05-data-classes/dataclass/hackerclub.py similarity index 90% rename from capitulos/code/05-data-classes/dataclass/hackerclub.py rename to code/05-data-classes/dataclass/hackerclub.py index 60f8234f..e2173211 100644 --- a/capitulos/code/05-data-classes/dataclass/hackerclub.py +++ b/code/05-data-classes/dataclass/hackerclub.py @@ -2,9 +2,9 @@ """ ``HackerClubMember`` objects accept an optional ``handle`` argument:: - >>> anna = HackerClubMember('Anna Ravenscroft', handle='AnnaRaven') + >>> anna = HackerClubMember('Anna Ravenscroft', handle='raven') >>> anna - HackerClubMember(name='Anna Ravenscroft', guests=[], handle='AnnaRaven') + HackerClubMember(name='Anna Ravenscroft', guests=[], handle='raven') If ``handle`` is omitted, it's set to the first part of the member's name:: @@ -12,7 +12,7 @@ >>> leo HackerClubMember(name='Leo Rochael', guests=[], handle='Leo') -Members must have a unique handle. The following ``leo2`` will not be created, +Handles must be unique. The following ``leo2`` will not be created, because its ``handle`` would be 'Leo', which was taken by ``leo``:: >>> leo2 = HackerClubMember('Leo DaVinci') diff --git a/capitulos/code/05-data-classes/dataclass/hackerclub_annotated.py b/code/05-data-classes/dataclass/hackerclub_annotated.py similarity index 100% rename from capitulos/code/05-data-classes/dataclass/hackerclub_annotated.py rename to code/05-data-classes/dataclass/hackerclub_annotated.py diff --git a/capitulos/code/05-data-classes/dataclass/resource.py b/code/05-data-classes/dataclass/resource.py similarity index 88% rename from capitulos/code/05-data-classes/dataclass/resource.py rename to code/05-data-classes/dataclass/resource.py index f332a11b..389f988d 100644 --- a/capitulos/code/05-data-classes/dataclass/resource.py +++ b/code/05-data-classes/dataclass/resource.py @@ -12,15 +12,13 @@ # tag::DOCTEST[] >>> description = 'Improving the design of existing code' - >>> book = Resource('978-0-13-475759-9', 'Refactoring, 2nd Edition', + >>> book = Resource('978-0-13-475759-9', + ... 'Refactoring, 2nd Edition', ... ['Martin Fowler', 'Kent Beck'], date(2018, 11, 19), ... ResourceType.BOOK, description, 'EN', ... ['computer programming', 'OOP']) >>> book # doctest: +NORMALIZE_WHITESPACE - Resource(identifier='978-0-13-475759-9', title='Refactoring, 2nd Edition', - creators=['Martin Fowler', 'Kent Beck'], date=datetime.date(2018, 11, 19), - type=, description='Improving the design of existing code', - language='EN', subjects=['computer programming', 'OOP']) + Resource(identifier='978-0-13-475759-9', title='Refactoring, 2nd Edition', creators=['Martin Fowler', 'Kent Beck'], date=datetime.date(2018, 11, 19), type=, description='Improving the design of existing code', language='EN', subjects=['computer programming', 'OOP']) # end::DOCTEST[] """ @@ -31,13 +29,11 @@ from enum import Enum, auto from datetime import date - class ResourceType(Enum): # <1> BOOK = auto() EBOOK = auto() VIDEO = auto() - @dataclass class Resource: """Media resource description.""" diff --git a/capitulos/code/05-data-classes/dataclass/resource_repr.py b/code/05-data-classes/dataclass/resource_repr.py similarity index 100% rename from capitulos/code/05-data-classes/dataclass/resource_repr.py rename to code/05-data-classes/dataclass/resource_repr.py diff --git a/capitulos/code/05-data-classes/frenchdeck.doctest b/code/05-data-classes/frenchdeck.doctest similarity index 93% rename from capitulos/code/05-data-classes/frenchdeck.doctest rename to code/05-data-classes/frenchdeck.doctest index 0257a7fc..66717f1e 100644 --- a/capitulos/code/05-data-classes/frenchdeck.doctest +++ b/code/05-data-classes/frenchdeck.doctest @@ -35,16 +35,17 @@ Card(rank='Q', suit='hearts') Sort with *spades high* overall ranking # tag::SPADES_HIGH[] ->>> Card.suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0) # <1> ->>> def spades_high(card): # <2> +>>> Card.suit_values = dict(spades=3, hearts=2, +... diamonds=1, clubs=0) # <1> +>>> def spades_high(card): # <2> ... rank_value = FrenchDeck.ranks.index(card.rank) ... suit_value = card.suit_values[card.suit] ... return rank_value * len(card.suit_values) + suit_value ... ->>> Card.overall_rank = spades_high # <3> +>>> Card.overall_rank = spades_high # <3> >>> lowest_card = Card('2', 'clubs') >>> highest_card = Card('A', 'spades') ->>> lowest_card.overall_rank() # <4> +>>> lowest_card.overall_rank() # <4> 0 >>> highest_card.overall_rank() 51 diff --git a/capitulos/code/05-data-classes/frenchdeck.py b/code/05-data-classes/frenchdeck.py similarity index 100% rename from capitulos/code/05-data-classes/frenchdeck.py rename to code/05-data-classes/frenchdeck.py diff --git a/capitulos/code/05-data-classes/match_cities.py b/code/05-data-classes/match_cities.py similarity index 100% rename from capitulos/code/05-data-classes/match_cities.py rename to code/05-data-classes/match_cities.py diff --git a/capitulos/code/05-data-classes/meaning/demo_dc.py b/code/05-data-classes/meaning/demo_dc.py similarity index 100% rename from capitulos/code/05-data-classes/meaning/demo_dc.py rename to code/05-data-classes/meaning/demo_dc.py diff --git a/capitulos/code/05-data-classes/meaning/demo_nt.py b/code/05-data-classes/meaning/demo_nt.py similarity index 100% rename from capitulos/code/05-data-classes/meaning/demo_nt.py rename to code/05-data-classes/meaning/demo_nt.py diff --git a/capitulos/code/05-data-classes/meaning/demo_plain.py b/code/05-data-classes/meaning/demo_plain.py similarity index 100% rename from capitulos/code/05-data-classes/meaning/demo_plain.py rename to code/05-data-classes/meaning/demo_plain.py diff --git a/capitulos/code/05-data-classes/typing_namedtuple/coordinates.py b/code/05-data-classes/typing_namedtuple/coordinates.py similarity index 100% rename from capitulos/code/05-data-classes/typing_namedtuple/coordinates.py rename to code/05-data-classes/typing_namedtuple/coordinates.py diff --git a/capitulos/code/05-data-classes/typing_namedtuple/coordinates2.py b/code/05-data-classes/typing_namedtuple/coordinates2.py similarity index 100% rename from capitulos/code/05-data-classes/typing_namedtuple/coordinates2.py rename to code/05-data-classes/typing_namedtuple/coordinates2.py diff --git a/capitulos/code/05-data-classes/typing_namedtuple/nocheck_demo.py b/code/05-data-classes/typing_namedtuple/nocheck_demo.py similarity index 100% rename from capitulos/code/05-data-classes/typing_namedtuple/nocheck_demo.py rename to code/05-data-classes/typing_namedtuple/nocheck_demo.py diff --git a/capitulos/code/06-obj-ref/bus.py b/code/06-obj-ref/bus.py similarity index 100% rename from capitulos/code/06-obj-ref/bus.py rename to code/06-obj-ref/bus.py diff --git a/capitulos/code/06-obj-ref/haunted_bus.py b/code/06-obj-ref/haunted_bus.py similarity index 100% rename from capitulos/code/06-obj-ref/haunted_bus.py rename to code/06-obj-ref/haunted_bus.py diff --git a/capitulos/code/06-obj-ref/twilight_bus.py b/code/06-obj-ref/twilight_bus.py similarity index 100% rename from capitulos/code/06-obj-ref/twilight_bus.py rename to code/06-obj-ref/twilight_bus.py diff --git a/capitulos/code/07-1class-func/README.rst b/code/07-1class-func/README.rst similarity index 100% rename from capitulos/code/07-1class-func/README.rst rename to code/07-1class-func/README.rst diff --git a/capitulos/code/07-1class-func/bingocall.py b/code/07-1class-func/bingocall.py similarity index 100% rename from capitulos/code/07-1class-func/bingocall.py rename to code/07-1class-func/bingocall.py diff --git a/capitulos/code/07-1class-func/tagger.py b/code/07-1class-func/tagger.py similarity index 100% rename from capitulos/code/07-1class-func/tagger.py rename to code/07-1class-func/tagger.py diff --git a/capitulos/code/08-def-type-hints/README.asciidoc b/code/08-def-type-hints/README.asciidoc similarity index 100% rename from capitulos/code/08-def-type-hints/README.asciidoc rename to code/08-def-type-hints/README.asciidoc diff --git a/capitulos/code/08-def-type-hints/arg_lab.py b/code/08-def-type-hints/arg_lab.py similarity index 100% rename from capitulos/code/08-def-type-hints/arg_lab.py rename to code/08-def-type-hints/arg_lab.py diff --git a/capitulos/code/08-def-type-hints/birds/birds.py b/code/08-def-type-hints/birds/birds.py similarity index 100% rename from capitulos/code/08-def-type-hints/birds/birds.py rename to code/08-def-type-hints/birds/birds.py diff --git a/capitulos/code/08-def-type-hints/birds/daffy.py b/code/08-def-type-hints/birds/daffy.py similarity index 100% rename from capitulos/code/08-def-type-hints/birds/daffy.py rename to code/08-def-type-hints/birds/daffy.py diff --git a/capitulos/code/08-def-type-hints/birds/protocol/lake.py b/code/08-def-type-hints/birds/protocol/lake.py similarity index 100% rename from capitulos/code/08-def-type-hints/birds/protocol/lake.py rename to code/08-def-type-hints/birds/protocol/lake.py diff --git a/capitulos/code/08-def-type-hints/birds/protocol/parrot.py b/code/08-def-type-hints/birds/protocol/parrot.py similarity index 100% rename from capitulos/code/08-def-type-hints/birds/protocol/parrot.py rename to code/08-def-type-hints/birds/protocol/parrot.py diff --git a/capitulos/code/08-def-type-hints/birds/protocol/swan.py b/code/08-def-type-hints/birds/protocol/swan.py similarity index 100% rename from capitulos/code/08-def-type-hints/birds/protocol/swan.py rename to code/08-def-type-hints/birds/protocol/swan.py diff --git a/capitulos/code/08-def-type-hints/birds/woody.py b/code/08-def-type-hints/birds/woody.py similarity index 100% rename from capitulos/code/08-def-type-hints/birds/woody.py rename to code/08-def-type-hints/birds/woody.py diff --git a/capitulos/code/08-def-type-hints/bus.py b/code/08-def-type-hints/bus.py similarity index 100% rename from capitulos/code/08-def-type-hints/bus.py rename to code/08-def-type-hints/bus.py diff --git a/capitulos/code/08-def-type-hints/callable/variance.py b/code/08-def-type-hints/callable/variance.py similarity index 100% rename from capitulos/code/08-def-type-hints/callable/variance.py rename to code/08-def-type-hints/callable/variance.py diff --git a/capitulos/code/08-def-type-hints/charindex.py b/code/08-def-type-hints/charindex.py similarity index 92% rename from capitulos/code/08-def-type-hints/charindex.py rename to code/08-def-type-hints/charindex.py index c368c378..059dd58b 100644 --- a/capitulos/code/08-def-type-hints/charindex.py +++ b/code/08-def-type-hints/charindex.py @@ -25,11 +25,14 @@ def tokenize(text: str) -> Iterator[str]: # <1> for match in RE_WORD.finditer(text): yield match.group().upper() -def name_index(start: int = 32, end: int = STOP_CODE) -> dict[str, set[str]]: +def name_index( + start: int = 32, end: int = STOP_CODE +) -> dict[str, set[str]]: index: dict[str, set[str]] = {} # <2> for char in (chr(i) for i in range(start, end)): if name := unicodedata.name(char, ''): # <3> for word in tokenize(name): index.setdefault(word, set()).add(char) return index + # end::CHARINDEX[] diff --git a/capitulos/code/08-def-type-hints/colors.py b/code/08-def-type-hints/colors.py similarity index 100% rename from capitulos/code/08-def-type-hints/colors.py rename to code/08-def-type-hints/colors.py diff --git a/capitulos/code/08-def-type-hints/columnize.py b/code/08-def-type-hints/columnize.py similarity index 100% rename from capitulos/code/08-def-type-hints/columnize.py rename to code/08-def-type-hints/columnize.py diff --git a/capitulos/code/08-def-type-hints/columnize_test.py b/code/08-def-type-hints/columnize_test.py similarity index 100% rename from capitulos/code/08-def-type-hints/columnize_test.py rename to code/08-def-type-hints/columnize_test.py diff --git a/capitulos/code/08-def-type-hints/comparable/comparable.py b/code/08-def-type-hints/comparable/comparable.py similarity index 100% rename from capitulos/code/08-def-type-hints/comparable/comparable.py rename to code/08-def-type-hints/comparable/comparable.py diff --git a/capitulos/code/08-def-type-hints/comparable/top.py b/code/08-def-type-hints/comparable/top.py similarity index 100% rename from capitulos/code/08-def-type-hints/comparable/top.py rename to code/08-def-type-hints/comparable/top.py diff --git a/capitulos/code/08-def-type-hints/comparable/top_test.py b/code/08-def-type-hints/comparable/top_test.py similarity index 100% rename from capitulos/code/08-def-type-hints/comparable/top_test.py rename to code/08-def-type-hints/comparable/top_test.py diff --git a/capitulos/code/08-def-type-hints/coordinates/coordinates.py b/code/08-def-type-hints/coordinates/coordinates.py similarity index 100% rename from capitulos/code/08-def-type-hints/coordinates/coordinates.py rename to code/08-def-type-hints/coordinates/coordinates.py diff --git a/capitulos/code/08-def-type-hints/coordinates/coordinates_named.py b/code/08-def-type-hints/coordinates/coordinates_named.py similarity index 100% rename from capitulos/code/08-def-type-hints/coordinates/coordinates_named.py rename to code/08-def-type-hints/coordinates/coordinates_named.py diff --git a/capitulos/code/08-def-type-hints/coordinates/coordinates_named_test.py b/code/08-def-type-hints/coordinates/coordinates_named_test.py similarity index 100% rename from capitulos/code/08-def-type-hints/coordinates/coordinates_named_test.py rename to code/08-def-type-hints/coordinates/coordinates_named_test.py diff --git a/capitulos/code/08-def-type-hints/coordinates/coordinates_test.py b/code/08-def-type-hints/coordinates/coordinates_test.py similarity index 100% rename from capitulos/code/08-def-type-hints/coordinates/coordinates_test.py rename to code/08-def-type-hints/coordinates/coordinates_test.py diff --git a/capitulos/code/08-def-type-hints/coordinates/requirements.txt b/code/08-def-type-hints/coordinates/requirements.txt similarity index 100% rename from capitulos/code/08-def-type-hints/coordinates/requirements.txt rename to code/08-def-type-hints/coordinates/requirements.txt diff --git a/capitulos/code/08-def-type-hints/ctime.py b/code/08-def-type-hints/ctime.py similarity index 100% rename from capitulos/code/08-def-type-hints/ctime.py rename to code/08-def-type-hints/ctime.py diff --git a/capitulos/code/08-def-type-hints/double/double_object.py b/code/08-def-type-hints/double/double_object.py similarity index 100% rename from capitulos/code/08-def-type-hints/double/double_object.py rename to code/08-def-type-hints/double/double_object.py diff --git a/capitulos/code/08-def-type-hints/double/double_protocol.py b/code/08-def-type-hints/double/double_protocol.py similarity index 100% rename from capitulos/code/08-def-type-hints/double/double_protocol.py rename to code/08-def-type-hints/double/double_protocol.py diff --git a/capitulos/code/08-def-type-hints/double/double_sequence.py b/code/08-def-type-hints/double/double_sequence.py similarity index 100% rename from capitulos/code/08-def-type-hints/double/double_sequence.py rename to code/08-def-type-hints/double/double_sequence.py diff --git a/capitulos/code/08-def-type-hints/double/double_test.py b/code/08-def-type-hints/double/double_test.py similarity index 100% rename from capitulos/code/08-def-type-hints/double/double_test.py rename to code/08-def-type-hints/double/double_test.py diff --git a/capitulos/code/08-def-type-hints/messages/hints_1/messages.py b/code/08-def-type-hints/messages/hints_1/messages.py similarity index 100% rename from capitulos/code/08-def-type-hints/messages/hints_1/messages.py rename to code/08-def-type-hints/messages/hints_1/messages.py diff --git a/capitulos/code/08-def-type-hints/messages/hints_1/messages_test.py b/code/08-def-type-hints/messages/hints_1/messages_test.py similarity index 100% rename from capitulos/code/08-def-type-hints/messages/hints_1/messages_test.py rename to code/08-def-type-hints/messages/hints_1/messages_test.py diff --git a/capitulos/code/08-def-type-hints/messages/hints_2/messages.py b/code/08-def-type-hints/messages/hints_2/messages.py similarity index 100% rename from capitulos/code/08-def-type-hints/messages/hints_2/messages.py rename to code/08-def-type-hints/messages/hints_2/messages.py diff --git a/capitulos/code/08-def-type-hints/messages/hints_2/messages_test.py b/code/08-def-type-hints/messages/hints_2/messages_test.py similarity index 100% rename from capitulos/code/08-def-type-hints/messages/hints_2/messages_test.py rename to code/08-def-type-hints/messages/hints_2/messages_test.py diff --git a/capitulos/code/08-def-type-hints/messages/no_hints/messages.py b/code/08-def-type-hints/messages/no_hints/messages.py similarity index 100% rename from capitulos/code/08-def-type-hints/messages/no_hints/messages.py rename to code/08-def-type-hints/messages/no_hints/messages.py diff --git a/capitulos/code/08-def-type-hints/messages/no_hints/messages_test.py b/code/08-def-type-hints/messages/no_hints/messages_test.py similarity index 100% rename from capitulos/code/08-def-type-hints/messages/no_hints/messages_test.py rename to code/08-def-type-hints/messages/no_hints/messages_test.py diff --git a/capitulos/code/08-def-type-hints/mode/mode_float.py b/code/08-def-type-hints/mode/mode_float.py similarity index 100% rename from capitulos/code/08-def-type-hints/mode/mode_float.py rename to code/08-def-type-hints/mode/mode_float.py diff --git a/capitulos/code/08-def-type-hints/mode/mode_hashable.py b/code/08-def-type-hints/mode/mode_hashable.py similarity index 100% rename from capitulos/code/08-def-type-hints/mode/mode_hashable.py rename to code/08-def-type-hints/mode/mode_hashable.py diff --git a/capitulos/code/08-def-type-hints/mypy.ini b/code/08-def-type-hints/mypy.ini similarity index 100% rename from capitulos/code/08-def-type-hints/mypy.ini rename to code/08-def-type-hints/mypy.ini diff --git a/capitulos/code/08-def-type-hints/replacer.py b/code/08-def-type-hints/replacer.py similarity index 100% rename from capitulos/code/08-def-type-hints/replacer.py rename to code/08-def-type-hints/replacer.py diff --git a/capitulos/code/08-def-type-hints/romans.py b/code/08-def-type-hints/romans.py similarity index 100% rename from capitulos/code/08-def-type-hints/romans.py rename to code/08-def-type-hints/romans.py diff --git a/capitulos/code/08-def-type-hints/romans_test.py b/code/08-def-type-hints/romans_test.py similarity index 100% rename from capitulos/code/08-def-type-hints/romans_test.py rename to code/08-def-type-hints/romans_test.py diff --git a/capitulos/code/08-def-type-hints/sample.py b/code/08-def-type-hints/sample.py similarity index 100% rename from capitulos/code/08-def-type-hints/sample.py rename to code/08-def-type-hints/sample.py diff --git a/capitulos/code/08-def-type-hints/typevar_bounded.py b/code/08-def-type-hints/typevar_bounded.py similarity index 100% rename from capitulos/code/08-def-type-hints/typevar_bounded.py rename to code/08-def-type-hints/typevar_bounded.py diff --git a/capitulos/code/08-def-type-hints/typevars_constrained.py b/code/08-def-type-hints/typevars_constrained.py similarity index 100% rename from capitulos/code/08-def-type-hints/typevars_constrained.py rename to code/08-def-type-hints/typevars_constrained.py diff --git a/capitulos/code/09-closure-deco/README.rst b/code/09-closure-deco/README.rst similarity index 100% rename from capitulos/code/09-closure-deco/README.rst rename to code/09-closure-deco/README.rst diff --git a/capitulos/code/09-closure-deco/average.py b/code/09-closure-deco/average.py similarity index 100% rename from capitulos/code/09-closure-deco/average.py rename to code/09-closure-deco/average.py diff --git a/capitulos/code/09-closure-deco/average_oo.py b/code/09-closure-deco/average_oo.py similarity index 100% rename from capitulos/code/09-closure-deco/average_oo.py rename to code/09-closure-deco/average_oo.py diff --git a/capitulos/code/09-closure-deco/clock/clockdeco.py b/code/09-closure-deco/clock/clockdeco.py similarity index 100% rename from capitulos/code/09-closure-deco/clock/clockdeco.py rename to code/09-closure-deco/clock/clockdeco.py diff --git a/capitulos/code/09-closure-deco/clock/clockdeco0.py b/code/09-closure-deco/clock/clockdeco0.py similarity index 100% rename from capitulos/code/09-closure-deco/clock/clockdeco0.py rename to code/09-closure-deco/clock/clockdeco0.py diff --git a/capitulos/code/09-closure-deco/clock/clockdeco_cls.py b/code/09-closure-deco/clock/clockdeco_cls.py similarity index 100% rename from capitulos/code/09-closure-deco/clock/clockdeco_cls.py rename to code/09-closure-deco/clock/clockdeco_cls.py diff --git a/capitulos/code/09-closure-deco/clock/clockdeco_demo.py b/code/09-closure-deco/clock/clockdeco_demo.py similarity index 100% rename from capitulos/code/09-closure-deco/clock/clockdeco_demo.py rename to code/09-closure-deco/clock/clockdeco_demo.py diff --git a/capitulos/code/09-closure-deco/clock/clockdeco_param.py b/code/09-closure-deco/clock/clockdeco_param.py similarity index 54% rename from capitulos/code/09-closure-deco/clock/clockdeco_param.py rename to code/09-closure-deco/clock/clockdeco_param.py index 2dbb571c..1a9ca139 100644 --- a/capitulos/code/09-closure-deco/clock/clockdeco_param.py +++ b/code/09-closure-deco/clock/clockdeco_param.py @@ -12,25 +12,25 @@ # tag::CLOCKDECO_PARAM[] import time -DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}' +DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}' # <1> -def clock(fmt=DEFAULT_FMT): # <1> - def decorate(func): # <2> - def clocked(*_args): # <3> +def clock(fmt=DEFAULT_FMT): # <2> + def decorate(func): # <3> + def clocked(*_args): # <4> t0 = time.perf_counter() - _result = func(*_args) # <4> + _result = func(*_args) # <5> elapsed = time.perf_counter() - t0 name = func.__name__ - args = ', '.join(repr(arg) for arg in _args) # <5> - result = repr(_result) # <6> - print(fmt.format(**locals())) # <7> - return _result # <8> - return clocked # <9> - return decorate # <10> + args = ', '.join(repr(arg) for arg in _args) # <6> + result = repr(_result) # <7> + print(fmt.format(**locals())) # <8> + return _result # <9> + return clocked # <10> + return decorate # <11> if __name__ == '__main__': - @clock() # <11> + @clock() # <12> def snooze(seconds): time.sleep(seconds) diff --git a/capitulos/code/09-closure-deco/clock/clockdeco_param_demo1.py b/code/09-closure-deco/clock/clockdeco_param_demo1.py similarity index 100% rename from capitulos/code/09-closure-deco/clock/clockdeco_param_demo1.py rename to code/09-closure-deco/clock/clockdeco_param_demo1.py diff --git a/capitulos/code/09-closure-deco/clock/clockdeco_param_demo2.py b/code/09-closure-deco/clock/clockdeco_param_demo2.py similarity index 100% rename from capitulos/code/09-closure-deco/clock/clockdeco_param_demo2.py rename to code/09-closure-deco/clock/clockdeco_param_demo2.py diff --git a/capitulos/code/09-closure-deco/fibo_compare.py b/code/09-closure-deco/fibo_compare.py similarity index 100% rename from capitulos/code/09-closure-deco/fibo_compare.py rename to code/09-closure-deco/fibo_compare.py diff --git a/capitulos/code/09-closure-deco/fibo_demo.py b/code/09-closure-deco/fibo_demo.py similarity index 100% rename from capitulos/code/09-closure-deco/fibo_demo.py rename to code/09-closure-deco/fibo_demo.py diff --git a/capitulos/code/09-closure-deco/fibo_demo_cache.py b/code/09-closure-deco/fibo_demo_cache.py similarity index 100% rename from capitulos/code/09-closure-deco/fibo_demo_cache.py rename to code/09-closure-deco/fibo_demo_cache.py diff --git a/capitulos/code/09-closure-deco/global_x_local.rst b/code/09-closure-deco/global_x_local.rst similarity index 100% rename from capitulos/code/09-closure-deco/global_x_local.rst rename to code/09-closure-deco/global_x_local.rst diff --git a/capitulos/code/09-closure-deco/htmlizer.py b/code/09-closure-deco/htmlizer.py similarity index 100% rename from capitulos/code/09-closure-deco/htmlizer.py rename to code/09-closure-deco/htmlizer.py diff --git a/capitulos/code/09-closure-deco/registration.py b/code/09-closure-deco/registration.py similarity index 100% rename from capitulos/code/09-closure-deco/registration.py rename to code/09-closure-deco/registration.py diff --git a/capitulos/code/09-closure-deco/registration_abridged.py b/code/09-closure-deco/registration_abridged.py similarity index 100% rename from capitulos/code/09-closure-deco/registration_abridged.py rename to code/09-closure-deco/registration_abridged.py diff --git a/capitulos/code/09-closure-deco/registration_param.py b/code/09-closure-deco/registration_param.py similarity index 100% rename from capitulos/code/09-closure-deco/registration_param.py rename to code/09-closure-deco/registration_param.py diff --git a/capitulos/code/09-closure-deco/stacked.py b/code/09-closure-deco/stacked.py similarity index 100% rename from capitulos/code/09-closure-deco/stacked.py rename to code/09-closure-deco/stacked.py diff --git a/capitulos/code/10-dp-1class-func/README.rst b/code/10-dp-1class-func/README.rst similarity index 100% rename from capitulos/code/10-dp-1class-func/README.rst rename to code/10-dp-1class-func/README.rst diff --git a/capitulos/code/10-dp-1class-func/classic_strategy.py b/code/10-dp-1class-func/classic_strategy.py similarity index 100% rename from capitulos/code/10-dp-1class-func/classic_strategy.py rename to code/10-dp-1class-func/classic_strategy.py diff --git a/capitulos/code/10-dp-1class-func/classic_strategy_test.py b/code/10-dp-1class-func/classic_strategy_test.py similarity index 100% rename from capitulos/code/10-dp-1class-func/classic_strategy_test.py rename to code/10-dp-1class-func/classic_strategy_test.py diff --git a/capitulos/code/10-dp-1class-func/monkeytype/classic_strategy.py b/code/10-dp-1class-func/monkeytype/classic_strategy.py similarity index 100% rename from capitulos/code/10-dp-1class-func/monkeytype/classic_strategy.py rename to code/10-dp-1class-func/monkeytype/classic_strategy.py diff --git a/capitulos/code/10-dp-1class-func/monkeytype/classic_strategy.pyi b/code/10-dp-1class-func/monkeytype/classic_strategy.pyi similarity index 100% rename from capitulos/code/10-dp-1class-func/monkeytype/classic_strategy.pyi rename to code/10-dp-1class-func/monkeytype/classic_strategy.pyi diff --git a/capitulos/code/10-dp-1class-func/monkeytype/classic_strategy_test.py b/code/10-dp-1class-func/monkeytype/classic_strategy_test.py similarity index 100% rename from capitulos/code/10-dp-1class-func/monkeytype/classic_strategy_test.py rename to code/10-dp-1class-func/monkeytype/classic_strategy_test.py diff --git a/capitulos/code/10-dp-1class-func/monkeytype/run.py b/code/10-dp-1class-func/monkeytype/run.py similarity index 100% rename from capitulos/code/10-dp-1class-func/monkeytype/run.py rename to code/10-dp-1class-func/monkeytype/run.py diff --git a/capitulos/code/10-dp-1class-func/promotions.py b/code/10-dp-1class-func/promotions.py similarity index 100% rename from capitulos/code/10-dp-1class-func/promotions.py rename to code/10-dp-1class-func/promotions.py diff --git a/capitulos/code/10-dp-1class-func/pytypes/classic_strategy.py b/code/10-dp-1class-func/pytypes/classic_strategy.py similarity index 100% rename from capitulos/code/10-dp-1class-func/pytypes/classic_strategy.py rename to code/10-dp-1class-func/pytypes/classic_strategy.py diff --git a/capitulos/code/10-dp-1class-func/pytypes/classic_strategy_test.py b/code/10-dp-1class-func/pytypes/classic_strategy_test.py similarity index 100% rename from capitulos/code/10-dp-1class-func/pytypes/classic_strategy_test.py rename to code/10-dp-1class-func/pytypes/classic_strategy_test.py diff --git a/capitulos/code/10-dp-1class-func/pytypes/typelogger_output/classic_strategy.pyi b/code/10-dp-1class-func/pytypes/typelogger_output/classic_strategy.pyi similarity index 100% rename from capitulos/code/10-dp-1class-func/pytypes/typelogger_output/classic_strategy.pyi rename to code/10-dp-1class-func/pytypes/typelogger_output/classic_strategy.pyi diff --git a/capitulos/code/10-dp-1class-func/requirements.txt b/code/10-dp-1class-func/requirements.txt similarity index 100% rename from capitulos/code/10-dp-1class-func/requirements.txt rename to code/10-dp-1class-func/requirements.txt diff --git a/capitulos/code/10-dp-1class-func/strategy.py b/code/10-dp-1class-func/strategy.py similarity index 99% rename from capitulos/code/10-dp-1class-func/strategy.py rename to code/10-dp-1class-func/strategy.py index 89a93baf..139ef40b 100644 --- a/capitulos/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/capitulos/code/10-dp-1class-func/strategy_best.py b/code/10-dp-1class-func/strategy_best.py similarity index 100% rename from capitulos/code/10-dp-1class-func/strategy_best.py rename to code/10-dp-1class-func/strategy_best.py diff --git a/capitulos/code/10-dp-1class-func/strategy_best2.py b/code/10-dp-1class-func/strategy_best2.py similarity index 100% rename from capitulos/code/10-dp-1class-func/strategy_best2.py rename to code/10-dp-1class-func/strategy_best2.py diff --git a/capitulos/code/10-dp-1class-func/strategy_best3.py b/code/10-dp-1class-func/strategy_best3.py similarity index 93% rename from capitulos/code/10-dp-1class-func/strategy_best3.py rename to code/10-dp-1class-func/strategy_best3.py index 8b16ceb9..506e5e94 100644 --- a/capitulos/code/10-dp-1class-func/strategy_best3.py +++ b/code/10-dp-1class-func/strategy_best3.py @@ -41,13 +41,13 @@ # tag::STRATEGY_BEST3[] from decimal import Decimal -import inspect +from inspect import getmembers, isfunction from strategy import Order import promotions -promos = [func for _, func in inspect.getmembers(promotions, inspect.isfunction)] +promos = [func for _, func in getmembers(promotions, isfunction)] def best_promo(order: Order) -> Decimal: diff --git a/capitulos/code/10-dp-1class-func/strategy_best4.py b/code/10-dp-1class-func/strategy_best4.py similarity index 99% rename from capitulos/code/10-dp-1class-func/strategy_best4.py rename to code/10-dp-1class-func/strategy_best4.py index 8e124bb7..72a9d3c9 100644 --- a/capitulos/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/capitulos/code/10-dp-1class-func/strategy_param.py b/code/10-dp-1class-func/strategy_param.py similarity index 100% rename from capitulos/code/10-dp-1class-func/strategy_param.py rename to code/10-dp-1class-func/strategy_param.py diff --git a/capitulos/code/10-dp-1class-func/strategy_param_test.py b/code/10-dp-1class-func/strategy_param_test.py similarity index 100% rename from capitulos/code/10-dp-1class-func/strategy_param_test.py rename to code/10-dp-1class-func/strategy_param_test.py diff --git a/capitulos/code/10-dp-1class-func/strategy_test.py b/code/10-dp-1class-func/strategy_test.py similarity index 100% rename from capitulos/code/10-dp-1class-func/strategy_test.py rename to code/10-dp-1class-func/strategy_test.py diff --git a/capitulos/code/10-dp-1class-func/untyped/classic_strategy.py b/code/10-dp-1class-func/untyped/classic_strategy.py similarity index 100% rename from capitulos/code/10-dp-1class-func/untyped/classic_strategy.py rename to code/10-dp-1class-func/untyped/classic_strategy.py diff --git a/capitulos/code/10-dp-1class-func/untyped/promotions.py b/code/10-dp-1class-func/untyped/promotions.py similarity index 100% rename from capitulos/code/10-dp-1class-func/untyped/promotions.py rename to code/10-dp-1class-func/untyped/promotions.py diff --git a/capitulos/code/10-dp-1class-func/untyped/strategy.py b/code/10-dp-1class-func/untyped/strategy.py similarity index 100% rename from capitulos/code/10-dp-1class-func/untyped/strategy.py rename to code/10-dp-1class-func/untyped/strategy.py diff --git a/capitulos/code/10-dp-1class-func/untyped/strategy_best.py b/code/10-dp-1class-func/untyped/strategy_best.py similarity index 100% rename from capitulos/code/10-dp-1class-func/untyped/strategy_best.py rename to code/10-dp-1class-func/untyped/strategy_best.py diff --git a/capitulos/code/10-dp-1class-func/untyped/strategy_best2.py b/code/10-dp-1class-func/untyped/strategy_best2.py similarity index 100% rename from capitulos/code/10-dp-1class-func/untyped/strategy_best2.py rename to code/10-dp-1class-func/untyped/strategy_best2.py diff --git a/capitulos/code/10-dp-1class-func/untyped/strategy_best3.py b/code/10-dp-1class-func/untyped/strategy_best3.py similarity index 100% rename from capitulos/code/10-dp-1class-func/untyped/strategy_best3.py rename to code/10-dp-1class-func/untyped/strategy_best3.py diff --git a/capitulos/code/10-dp-1class-func/untyped/strategy_best4.py b/code/10-dp-1class-func/untyped/strategy_best4.py similarity index 100% rename from capitulos/code/10-dp-1class-func/untyped/strategy_best4.py rename to code/10-dp-1class-func/untyped/strategy_best4.py diff --git a/capitulos/code/10-dp-1class-func/untyped/strategy_param.py b/code/10-dp-1class-func/untyped/strategy_param.py similarity index 100% rename from capitulos/code/10-dp-1class-func/untyped/strategy_param.py rename to code/10-dp-1class-func/untyped/strategy_param.py diff --git a/capitulos/code/10-dp-1class-func/untyped/strategy_param2.py b/code/10-dp-1class-func/untyped/strategy_param2.py similarity index 100% rename from capitulos/code/10-dp-1class-func/untyped/strategy_param2.py rename to code/10-dp-1class-func/untyped/strategy_param2.py diff --git a/capitulos/code/11-pythonic-obj/README.md b/code/11-pythonic-obj/README.md similarity index 100% rename from capitulos/code/11-pythonic-obj/README.md rename to code/11-pythonic-obj/README.md diff --git a/capitulos/code/11-pythonic-obj/mem_test.py b/code/11-pythonic-obj/mem_test.py similarity index 100% rename from capitulos/code/11-pythonic-obj/mem_test.py rename to code/11-pythonic-obj/mem_test.py diff --git a/capitulos/code/11-pythonic-obj/patterns.py b/code/11-pythonic-obj/patterns.py similarity index 100% rename from capitulos/code/11-pythonic-obj/patterns.py rename to code/11-pythonic-obj/patterns.py diff --git a/capitulos/code/11-pythonic-obj/private/.gitignore b/code/11-pythonic-obj/private/.gitignore similarity index 100% rename from capitulos/code/11-pythonic-obj/private/.gitignore rename to code/11-pythonic-obj/private/.gitignore diff --git a/capitulos/code/11-pythonic-obj/private/Confidential.java b/code/11-pythonic-obj/private/Confidential.java similarity index 100% rename from capitulos/code/11-pythonic-obj/private/Confidential.java rename to code/11-pythonic-obj/private/Confidential.java diff --git a/capitulos/code/11-pythonic-obj/private/Expose.java b/code/11-pythonic-obj/private/Expose.java similarity index 100% rename from capitulos/code/11-pythonic-obj/private/Expose.java rename to code/11-pythonic-obj/private/Expose.java diff --git a/capitulos/code/11-pythonic-obj/private/expose.py b/code/11-pythonic-obj/private/expose.py similarity index 100% rename from capitulos/code/11-pythonic-obj/private/expose.py rename to code/11-pythonic-obj/private/expose.py diff --git a/capitulos/code/11-pythonic-obj/private/leakprivate.py b/code/11-pythonic-obj/private/leakprivate.py similarity index 100% rename from capitulos/code/11-pythonic-obj/private/leakprivate.py rename to code/11-pythonic-obj/private/leakprivate.py diff --git a/capitulos/code/11-pythonic-obj/private/no_respect.py b/code/11-pythonic-obj/private/no_respect.py similarity index 100% rename from capitulos/code/11-pythonic-obj/private/no_respect.py rename to code/11-pythonic-obj/private/no_respect.py diff --git a/capitulos/code/11-pythonic-obj/slots.rst b/code/11-pythonic-obj/slots.rst similarity index 100% rename from capitulos/code/11-pythonic-obj/slots.rst rename to code/11-pythonic-obj/slots.rst diff --git a/capitulos/code/11-pythonic-obj/vector2d_v0.py b/code/11-pythonic-obj/vector2d_v0.py similarity index 96% rename from capitulos/code/11-pythonic-obj/vector2d_v0.py rename to code/11-pythonic-obj/vector2d_v0.py index 453edc59..718a8af8 100644 --- a/capitulos/code/11-pythonic-obj/vector2d_v0.py +++ b/code/11-pythonic-obj/vector2d_v0.py @@ -50,7 +50,7 @@ def __str__(self): return str(tuple(self)) # <5> def __bytes__(self): - return (bytes([ord(self.typecode)]) + # <6> + return (self.typecode.encode('ascii') + # <6> bytes(array(self.typecode, self))) # <7> def __eq__(self, other): diff --git a/capitulos/code/11-pythonic-obj/vector2d_v1.py b/code/11-pythonic-obj/vector2d_v1.py similarity index 94% rename from capitulos/code/11-pythonic-obj/vector2d_v1.py rename to code/11-pythonic-obj/vector2d_v1.py index 25a85b43..993cb6e2 100644 --- a/capitulos/code/11-pythonic-obj/vector2d_v1.py +++ b/code/11-pythonic-obj/vector2d_v1.py @@ -23,7 +23,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector2d.frombytes(bytes(v1)) >>> v1_clone @@ -55,7 +55,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(array(self.typecode, self))) def __eq__(self, other): diff --git a/capitulos/code/11-pythonic-obj/vector2d_v2.py b/code/11-pythonic-obj/vector2d_v2.py similarity index 92% rename from capitulos/code/11-pythonic-obj/vector2d_v2.py rename to code/11-pythonic-obj/vector2d_v2.py index 2a9dcf50..66dbe055 100644 --- a/capitulos/code/11-pythonic-obj/vector2d_v2.py +++ b/code/11-pythonic-obj/vector2d_v2.py @@ -23,7 +23,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector2d.frombytes(bytes(v1)) >>> v1_clone @@ -32,7 +32,7 @@ True -Tests of ``format()`` with Cartesian coordinates: +Tests of `format()` with Cartesian coordinates: >>> format(v1) '(3.0, 4.0)' @@ -42,7 +42,7 @@ '(3.000e+00, 4.000e+00)' -Tests of the ``angle`` method:: +Tests of the `.angle()` method:: >>> Vector2d(0, 0).angle() 0.0 @@ -55,7 +55,7 @@ True -Tests of ``format()`` with polar coordinates: +Tests of `format()` with polar coordinates: >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' @@ -89,7 +89,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(array(self.typecode, self))) def __eq__(self, other): diff --git a/capitulos/code/11-pythonic-obj/vector2d_v2_fmt_snippet.py b/code/11-pythonic-obj/vector2d_v2_fmt_snippet.py similarity index 93% rename from capitulos/code/11-pythonic-obj/vector2d_v2_fmt_snippet.py rename to code/11-pythonic-obj/vector2d_v2_fmt_snippet.py index 77e76dd7..68edfbfd 100644 --- a/capitulos/code/11-pythonic-obj/vector2d_v2_fmt_snippet.py +++ b/code/11-pythonic-obj/vector2d_v2_fmt_snippet.py @@ -21,7 +21,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector2d.frombytes(bytes(v1)) >>> v1_clone @@ -30,7 +30,7 @@ True -Tests of ``format()`` with Cartesian coordinates: +Tests of `format()` with Cartesian coordinates: >>> format(v1) '(3.0, 4.0)' @@ -40,7 +40,7 @@ '(3.000e+00, 4.000e+00)' -Tests of the ``angle`` method:: +Tests of the `.angle()` method:: >>> Vector2d(0, 0).angle() 0.0 @@ -53,7 +53,7 @@ True -Tests of ``format()`` with polar coordinates: +Tests of `format()` with polar coordinates: >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' diff --git a/capitulos/code/11-pythonic-obj/vector2d_v3.py b/code/11-pythonic-obj/vector2d_v3.py similarity index 91% rename from capitulos/code/11-pythonic-obj/vector2d_v3.py rename to code/11-pythonic-obj/vector2d_v3.py index 0edffb74..fc18145a 100644 --- a/capitulos/code/11-pythonic-obj/vector2d_v3.py +++ b/code/11-pythonic-obj/vector2d_v3.py @@ -23,7 +23,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector2d.frombytes(bytes(v1)) >>> v1_clone @@ -32,7 +32,7 @@ True -Tests of ``format()`` with Cartesian coordinates: +Tests of `format()` with Cartesian coordinates: >>> format(v1) '(3.0, 4.0)' @@ -42,7 +42,7 @@ '(3.000e+00, 4.000e+00)' -Tests of the ``angle`` method:: +Tests of the `.angle()` method:: >>> Vector2d(0, 0).angle() 0.0 @@ -55,7 +55,7 @@ True -Tests of ``format()`` with polar coordinates: +Tests of `format()` with polar coordinates: >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' @@ -72,7 +72,7 @@ >>> v1.x = 123 Traceback (most recent call last): ... - AttributeError: can't set attribute 'x' + AttributeError: property 'x' of 'Vector2d' object has no setter Tests of hashing: @@ -115,7 +115,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(array(self.typecode, self))) def __eq__(self, other): diff --git a/capitulos/code/11-pythonic-obj/vector2d_v3_prophash.py b/code/11-pythonic-obj/vector2d_v3_prophash.py similarity index 92% rename from capitulos/code/11-pythonic-obj/vector2d_v3_prophash.py rename to code/11-pythonic-obj/vector2d_v3_prophash.py index 1652cd40..ec1bae00 100644 --- a/capitulos/code/11-pythonic-obj/vector2d_v3_prophash.py +++ b/code/11-pythonic-obj/vector2d_v3_prophash.py @@ -23,7 +23,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector2d.frombytes(bytes(v1)) >>> v1_clone @@ -32,7 +32,7 @@ True -Tests of ``format()`` with Cartesian coordinates: +Tests of `format()` with Cartesian coordinates: >>> format(v1) '(3.0, 4.0)' @@ -42,7 +42,7 @@ '(3.000e+00, 4.000e+00)' -Tests of the ``angle`` method:: +Tests of the `.angle()` method:: >>> Vector2d(0, 0).angle() 0.0 @@ -55,7 +55,7 @@ True -Tests of ``format()`` with polar coordinates: +Tests of `format()` with polar coordinates: >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' @@ -72,7 +72,7 @@ >>> v1.x = 123 Traceback (most recent call last): ... - AttributeError: can't set attribute 'x' + AttributeError: property 'x' of 'Vector2d' object has no setter # end::VECTOR2D_V3_HASH_DEMO[] @@ -121,7 +121,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(array(self.typecode, self))) def __eq__(self, other): diff --git a/capitulos/code/11-pythonic-obj/vector2d_v3_slots.py b/code/11-pythonic-obj/vector2d_v3_slots.py similarity index 91% rename from capitulos/code/11-pythonic-obj/vector2d_v3_slots.py rename to code/11-pythonic-obj/vector2d_v3_slots.py index 89da9731..387655a8 100644 --- a/capitulos/code/11-pythonic-obj/vector2d_v3_slots.py +++ b/code/11-pythonic-obj/vector2d_v3_slots.py @@ -23,7 +23,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector2d.frombytes(bytes(v1)) >>> v1_clone @@ -32,7 +32,7 @@ True -Tests of ``format()`` with Cartesian coordinates: +Tests of `format()` with Cartesian coordinates: >>> format(v1) '(3.0, 4.0)' @@ -42,7 +42,7 @@ '(3.000e+00, 4.000e+00)' -Tests of the ``angle`` method:: +Tests of the `.angle()` method:: >>> Vector2d(0, 0).angle() 0.0 @@ -55,7 +55,7 @@ True -Tests of ``format()`` with polar coordinates: +Tests of `format()` with polar coordinates: >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' @@ -72,7 +72,7 @@ >>> v1.x = 123 Traceback (most recent call last): ... - AttributeError: can't set attribute 'x' + AttributeError: property 'x' of 'Vector2d' object has no setter Tests of hashing: @@ -117,7 +117,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(array(self.typecode, self))) def __eq__(self, other): diff --git a/code/12-seq-hacking/soma-pythonica.ipynb b/code/12-seq-hacking/soma-pythonica.ipynb new file mode 100644 index 00000000..621179a5 --- /dev/null +++ b/code/12-seq-hacking/soma-pythonica.ipynb @@ -0,0 +1,480 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6f7e6659-f6f5-4170-b62d-9de8b1829e8c", + "metadata": {}, + "source": [ + "# Em busca da soma pythônica" + ] + }, + { + "cell_type": "markdown", + "id": "6d03afc1-37dd-466d-baf7-806e7d6470dc", + "metadata": {}, + "source": [ + "Na [Python-list](https://fpy.li/12-11), há um fio de abril de 2003 com\n", + "o título\n", + "[_Pythonic Way to Sum n-th List Element?_](https://fpy.li/12-12)\n", + "(A forma pythônica de somar o n-ésimo elemento em listas).\n", + "\n", + "Não há uma resposta única para a \"O que é pythônico?\", da mesma\n", + "forma que não há uma resposta única para \"O que é belo?\".\n", + "\n", + "Mas talvez esta discussão dê alguma luz.\n", + "\n", + "O autor original do fio, Guy Middleton, pediu melhorias para a solução abaixo,\n", + "afirmando não gostar de usar `lambda`.\n", + "\n", + "No caso específico, ele quer somar o segundo item de cada lista de uma série de listas." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "58006700-7d19-4352-83db-eeb39d52751d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from functools import reduce\n", + "my_list = [[2, 3, 4], [22, 33, 44], [7, 6, 5]]\n", + "reduce(lambda ac, n: ac+n, [sub[1] for sub in my_list])" + ] + }, + { + "cell_type": "markdown", + "id": "55d9e82e-8d55-4e83-bd63-6053aaac8382", + "metadata": {}, + "source": [ + "Este código usa várias peculiaridades de Python: `lambda`, `reduce` e uma compreensão de lista.\n", + "\n", + "## Sobre o reduce\n", + "\n", + "Esta é assinatura de `reduce`:\n", + "\n", + "```python\n", + "def reduce(function, iterable, /, initial=initial_missing): ...\n", + "```\n", + "\n", + "`function` tem que ser uma função que aceita dois argumentos: um acumulador, e um item.\n", + "O acumulador é inicializado com `initial`, ou com o primeiro item do `iterable`.\n", + "A função será invocada uma vez para cada item do iterável,\n", + "recebendo o acumulador e o próximo item. O resultado serve para atualizar o acumulador\n", + "antes da próxima chamada.\n", + "\n", + "Uma versão simplificada de `reduce`, lidando só com sequências, já serve para ilustrar o algoritmo:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8a3c112c-f27a-44b2-ae54-7fc4f39b9536", + "metadata": {}, + "outputs": [], + "source": [ + "def reduzir(função, sequência):\n", + " acumulador = sequência[0]\n", + " for item in sequência[1:]:\n", + " acumulador = função(acumulador, item)\n", + " return acumulador" + ] + }, + { + "cell_type": "markdown", + "id": "ff011542-ce9f-40d7-9525-c00f58b657f9", + "metadata": {}, + "source": [ + "Por exemplo, o fatorial de 5 pode ser calculado assim, sem usar `lambda`:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "954018a5-d8a5-423d-98fc-67721c444c61", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "120" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def mult(ac, n):\n", + " return ac * n\n", + " \n", + "reduzir(mult, range(2, 6))" + ] + }, + { + "cell_type": "markdown", + "id": "bff4b269-02b9-481c-b9dd-6c139b40e342", + "metadata": {}, + "source": [ + "(Implementar `reduzir` com suporte a iteráveis e o argumento `inicial` é um exercício legal.)" + ] + }, + { + "cell_type": "markdown", + "id": "7cb6aa3a-6956-4b2e-98ad-790bdc7a0f6f", + "metadata": {}, + "source": [ + "## Voltando ao problema de somar o n-ésimo item\n", + "\n", + "A solução que Guy Middleton apresentou\n", + "provavelmente ficaria em último lugar em um concurso de popularidade, pois\n", + "ofende quem odeia `lambda` e também aqueles que desprezam as compreensões de\n", + "lista.\n", + "\n", + "Nem o próprio Guy curtiu o código que ele apresentou:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e003233b-e688-4f01-a9d1-4ade0c1dcff9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from functools import reduce\n", + "my_list = [[2, 3, 4], [22, 33, 44], [7, 6, 5]]\n", + "reduce(lambda ac, n: ac+n, [sub[1] for sub in my_list])" + ] + }, + { + "cell_type": "markdown", + "id": "211954fd-1505-479b-9973-b415404f71ad", + "metadata": {}, + "source": [ + "Se você vai usar `lambda`, provavelmente não há razão para usar uma compreensão\n", + "de lista—exceto para filtrar com `if`, que não é o caso aqui.\n", + "\n", + "Aqui está uma solução minha que ofenderá todo mundo, exceto os fanáticos por `lambda`:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e638e00c-3b54-4a2d-b4bd-16ce003264ee", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "reduce(lambda ac, sub: ac + sub[1], my_list, 0)" + ] + }, + { + "cell_type": "markdown", + "id": "9bdfbaed-0e43-4922-9c76-01bc8a288cd3", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "source": [ + "Não participei da discussão original,\n", + "e não usaria este código porque também não gosto muito de `lambda`,\n", + "principalmente em casos obscuros como este.\n", + "\n", + "Apenas quis mostrar aqui um exemplo sem uma compreensão de lista.\n", + "\n", + "A primeira resposta veio de Fernando Perez, criador do IPython e do Jupyter,\n", + "mostrando como a NumPy suporta arrays _n_-dimensionais e fatiamento _n_-dimensional:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e4fe9a09-e807-4ec0-ae9e-3e600e8e558b", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(42)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "my_array = np.array(my_list)\n", + "np.sum(my_array[:, 1])" + ] + }, + { + "cell_type": "markdown", + "id": "e6ecf3f3-fd99-4aa4-8526-94ad25f9ef10", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "source": [ + "Acho a solução de Perez boa, mas Guy Middleton elogiou essa próxima solução,\n", + "de Paul Rubin e Skip Montanaro:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e5432cf4-2923-4a51-9b3e-60926185aaaf", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import operator\n", + "reduce(operator.add, [sub[1] for sub in my_list], 0)" + ] + }, + { + "cell_type": "markdown", + "id": "30d8acba-2007-402f-a1c8-1cdfcdb42430", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "source": [ + "Então Evan Simpson perguntou, \"O que há de errado em fazer assim?\":" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a5241876-2216-4e01-b3f0-b57e42ed099b", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ac = 0\n", + "for sub in my_list:\n", + " ac += sub[1]\n", + "\n", + "ac" + ] + }, + { + "cell_type": "markdown", + "id": "a4c16e31-2556-4cfa-9aab-eb9914f6eb91", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "source": [ + "Muitos concordaram que esse código era bastante pythônico.\n", + "Alex Martelli chegou a escrever que Guido provavelmente \n", + "resolveria o problema desta maneira.\n", + "\n", + "Gosto do código de Evan Simpson, mas também gosto do comentário\n", + "de David Eppstein sobre ele:\n", + "\n", + "> Se você quer a soma de uma lista de itens, deveria escrever algo como\n", + "\"a soma de uma lista de itens\", não como \"faça um loop sobre\n", + "esses itens, mantenha uma variável `ac`, execute uma série de somas\".\n", + "Por que temos linguagens de alto nível, senão para expressar nossas\n", + "intenções em um nível mais alto e deixar a linguagem se preocupar\n", + "com as operações de baixo nível necessárias para executá-las?" + ] + }, + { + "cell_type": "markdown", + "id": "49afe1be-c2cf-4d36-9130-e5dc36aeaa30", + "metadata": {}, + "source": [ + "E daí Alex Martelli voltou para sugerir:\n", + "\n", + "> Fazemos somas com tanta frequência que eu não me importaria de forma\n", + "alguma se Python a tornasse uma função embutida. Mas `reduce(operator.add,\n", + "...)` não é uma boa forma de expressar isso, na minha opinião (e vejam\n", + "que, como um antigo *APLer* e *FP-liker* eu _deveria_ gostar daquilo, mas não gosto).\n", + "\n", + "(Aqui o Martelli faz referência às linguagens funcionas\n", + "[APL](https://pt.wikipedia.org/wiki/APL_(linguagem_de_programa%C3%A7%C3%A3o)) e\n", + "[FP](https://pt.wikipedia.org/wiki/FP_(linguagem_de_programa%C3%A7%C3%A3o)))\n", + "\n", + "## A soma pythônica\n", + "\n", + "Martelli então sugere uma função `sum()`, que ele mesmo programa e submete para\n", + "o Python.\n", + "\n", + "Ela se torna uma função embutida no Python 2.3, lançado apenas três\n", + "meses após aquela conversa na lista.\n", + "\n", + "A sintaxe preferida de Martelli se torna a regra:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "58c6a9f7-c901-432c-8252-dd67cee5bef2", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum([sub[1] for sub in my_list])" + ] + }, + { + "cell_type": "markdown", + "id": "bd0b62f0-440d-4169-a0a1-4b22ad15e9c7", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "source": [ + "No final do ano seguinte (novembro de 2004), Python 2.4 foi lançado e incluía\n", + "expressões geradoras, fornecendo o que agora é, na minha opinião, a resposta\n", + "mais pythônica para a pergunta original de Guy Middleton:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7d734682-ce4c-4b88-80e4-f8689aa16347", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum(sub[1] for sub in my_list)" + ] + }, + { + "cell_type": "markdown", + "id": "970934c6-22f8-430e-960b-cd7e8f6b49ab", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "source": [ + "Isso não só é mais legível que `reduce`,\n", + "também trata o problema da sequência\n", + "vazia, porque `sum([])` é `0`.\n", + "\n", + "Na mesma conversa, Alex Martelli sugeriu que a função embutida `reduce` de\n", + "Python 2 trazia mais problemas que soluções, porque encorajava idiomas de\n", + "programação difíceis de explicar. Ele foi bastante convincente: a função foi\n", + "rebaixada para o módulo `functools` no Python 3.\n", + "\n", + "Ainda assim, `functools.reduce` tem seus usos.\n", + "Mas quando pensar em `reduce`,\n", + "veja se `sum`, `all` e `any` resolvem seu problema\n", + "mais facilmente.\n", + "\n", + "----\n", + "\n", + "Este notebook foi adaptado da seção **Ponto de Vista** do\n", + "[Capítulo 12](https://pythonfluente.com/2/#_para_saber_mais_5)\n", + "do **Python Fluente, 2ª ed.**" + ] + }, + { + "cell_type": "markdown", + "id": "16c61e61-b827-4bfe-afb3-b11750e6dd8a", + "metadata": {}, + "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.0rc1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/capitulos/code/12-seq-hacking/vector_v1.py b/code/12-seq-hacking/vector_v1.py similarity index 91% rename from capitulos/code/12-seq-hacking/vector_v1.py rename to code/12-seq-hacking/vector_v1.py index 9d734b6e..46a06bd5 100644 --- a/capitulos/code/12-seq-hacking/vector_v1.py +++ b/code/12-seq-hacking/vector_v1.py @@ -1,7 +1,7 @@ """ -A multi-dimensional ``Vector`` class, take 1 +A multi-dimensional `Vector` class, take 1 -A ``Vector`` is built from an iterable of numbers:: +A `Vector` is built from an iterable of numbers:: >>> Vector([3.1, 4.2]) Vector([3.1, 4.2]) @@ -33,7 +33,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone @@ -70,7 +70,7 @@ 9.53939201... -Test of ``.__bytes__`` and ``.frombytes()`` methods:: +Test of `.__bytes__` and `.frombytes()` methods:: >>> v1 = Vector([3, 4, 5]) >>> v1_clone = Vector.frombytes(bytes(v1)) @@ -87,7 +87,6 @@ import reprlib import math - class Vector: typecode = 'd' @@ -106,7 +105,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(self._components)) # <5> def __eq__(self, other): diff --git a/capitulos/code/12-seq-hacking/vector_v2.py b/code/12-seq-hacking/vector_v2.py similarity index 93% rename from capitulos/code/12-seq-hacking/vector_v2.py rename to code/12-seq-hacking/vector_v2.py index f25c2439..98ea9db3 100644 --- a/capitulos/code/12-seq-hacking/vector_v2.py +++ b/code/12-seq-hacking/vector_v2.py @@ -1,7 +1,7 @@ """ -A multi-dimensional ``Vector`` class, take 2 +A multi-dimensional `Vector` class, take 2 -A ``Vector`` is built from an iterable of numbers:: +A `Vector` is built from an iterable of numbers:: >>> Vector([3.1, 4.2]) Vector([3.1, 4.2]) @@ -33,7 +33,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone @@ -70,7 +70,7 @@ 9.53939201... -Test of ``.__bytes__`` and ``.frombytes()`` methods:: +Test of `.__bytes__` and `.frombytes()` methods:: >>> v1 = Vector([3, 4, 5]) >>> v1_clone = Vector.frombytes(bytes(v1)) @@ -133,7 +133,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(self._components)) def __eq__(self, other): @@ -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/capitulos/code/12-seq-hacking/vector_v3.py b/code/12-seq-hacking/vector_v3.py similarity index 95% rename from capitulos/code/12-seq-hacking/vector_v3.py rename to code/12-seq-hacking/vector_v3.py index c1f7859e..59c043ff 100644 --- a/capitulos/code/12-seq-hacking/vector_v3.py +++ b/code/12-seq-hacking/vector_v3.py @@ -1,7 +1,7 @@ """ -A multi-dimensional ``Vector`` class, take 3 +A multi-dimensional `Vector` class, take 3 -A ``Vector`` is built from an iterable of numbers:: +A `Vector` is built from an iterable of numbers:: >>> Vector([3.1, 4.2]) Vector([3.1, 4.2]) @@ -33,7 +33,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone @@ -70,7 +70,7 @@ 9.53939201... -Test of ``.__bytes__`` and ``.frombytes()`` methods:: +Test of `.__bytes__` and `.frombytes()` methods:: >>> v1 = Vector([3, 4, 5]) >>> v1_clone = Vector.frombytes(bytes(v1)) @@ -176,7 +176,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(self._components)) def __eq__(self, other): diff --git a/capitulos/code/12-seq-hacking/vector_v4.py b/code/12-seq-hacking/vector_v4.py similarity index 95% rename from capitulos/code/12-seq-hacking/vector_v4.py rename to code/12-seq-hacking/vector_v4.py index 856f338d..68ec614f 100644 --- a/capitulos/code/12-seq-hacking/vector_v4.py +++ b/code/12-seq-hacking/vector_v4.py @@ -1,7 +1,7 @@ """ -A multi-dimensional ``Vector`` class, take 4 +A multi-dimensional `Vector` class, take 4 -A ``Vector`` is built from an iterable of numbers:: +A `Vector` is built from an iterable of numbers:: >>> Vector([3.1, 4.2]) Vector([3.1, 4.2]) @@ -33,7 +33,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone @@ -70,7 +70,7 @@ 9.53939201... -Test of ``.__bytes__`` and ``.frombytes()`` methods:: +Test of `.__bytes__` and `.frombytes()` methods:: >>> v1 = Vector([3, 4, 5]) >>> v1_clone = Vector.frombytes(bytes(v1)) @@ -172,7 +172,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(self._components)) def __eq__(self, other): diff --git a/capitulos/code/12-seq-hacking/vector_v5.py b/code/12-seq-hacking/vector_v5.py similarity index 93% rename from capitulos/code/12-seq-hacking/vector_v5.py rename to code/12-seq-hacking/vector_v5.py index 09b3044d..2b1bcdf1 100644 --- a/capitulos/code/12-seq-hacking/vector_v5.py +++ b/code/12-seq-hacking/vector_v5.py @@ -1,8 +1,8 @@ # tag::VECTOR_V5[] """ -A multidimensional ``Vector`` class, take 5 +A multidimensional `Vector` class, take 5 -A ``Vector`` is built from an iterable of numbers:: +A `Vector` is built from an iterable of numbers:: >>> Vector([3.1, 4.2]) Vector([3.1, 4.2]) @@ -34,7 +34,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone @@ -71,7 +71,7 @@ 9.53939201... -Test of ``.__bytes__`` and ``.frombytes()`` methods:: +Test of `.__bytes__` and `.frombytes()` methods:: >>> v1 = Vector([3, 4, 5]) >>> v1_clone = Vector.frombytes(bytes(v1)) @@ -147,7 +147,7 @@ True -Tests of ``format()`` with Cartesian coordinates in 2D:: +Tests of `format()` with Cartesian coordinates in 2D:: >>> v1 = Vector([3, 4]) >>> format(v1) @@ -158,7 +158,7 @@ '(3.000e+00, 4.000e+00)' -Tests of ``format()`` with Cartesian coordinates in 3D and 7D:: +Tests of `format()` with Cartesian coordinates in 3D and 7D:: >>> v3 = Vector([3, 4, 5]) >>> format(v3) @@ -167,7 +167,7 @@ '(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)' -Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D:: +Tests of `format()` with spherical coordinates in 2D, 3D and 4D:: >>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' @@ -215,7 +215,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(self._components)) def __eq__(self, other): diff --git a/capitulos/code/13-protocol-abc/README.rst b/code/13-protocol-abc/README.rst similarity index 100% rename from capitulos/code/13-protocol-abc/README.rst rename to code/13-protocol-abc/README.rst diff --git a/capitulos/code/13-protocol-abc/bingo.py b/code/13-protocol-abc/bingo.py similarity index 100% rename from capitulos/code/13-protocol-abc/bingo.py rename to code/13-protocol-abc/bingo.py diff --git a/capitulos/code/13-protocol-abc/double/double_object.py b/code/13-protocol-abc/double/double_object.py similarity index 100% rename from capitulos/code/13-protocol-abc/double/double_object.py rename to code/13-protocol-abc/double/double_object.py diff --git a/capitulos/code/13-protocol-abc/double/double_protocol.py b/code/13-protocol-abc/double/double_protocol.py similarity index 100% rename from capitulos/code/13-protocol-abc/double/double_protocol.py rename to code/13-protocol-abc/double/double_protocol.py diff --git a/capitulos/code/13-protocol-abc/double/double_sequence.py b/code/13-protocol-abc/double/double_sequence.py similarity index 100% rename from capitulos/code/13-protocol-abc/double/double_sequence.py rename to code/13-protocol-abc/double/double_sequence.py diff --git a/capitulos/code/13-protocol-abc/double/double_test.py b/code/13-protocol-abc/double/double_test.py similarity index 100% rename from capitulos/code/13-protocol-abc/double/double_test.py rename to code/13-protocol-abc/double/double_test.py diff --git a/capitulos/code/13-protocol-abc/drum.py b/code/13-protocol-abc/drum.py similarity index 100% rename from capitulos/code/13-protocol-abc/drum.py rename to code/13-protocol-abc/drum.py diff --git a/capitulos/code/13-protocol-abc/frenchdeck2.py b/code/13-protocol-abc/frenchdeck2.py similarity index 100% rename from capitulos/code/13-protocol-abc/frenchdeck2.py rename to code/13-protocol-abc/frenchdeck2.py diff --git a/capitulos/code/13-protocol-abc/lotto.py b/code/13-protocol-abc/lotto.py similarity index 100% rename from capitulos/code/13-protocol-abc/lotto.py rename to code/13-protocol-abc/lotto.py diff --git a/capitulos/code/15-more-types/lotto/tombola.py b/code/13-protocol-abc/tombola.py similarity index 82% rename from capitulos/code/15-more-types/lotto/tombola.py rename to code/13-protocol-abc/tombola.py index 50f19fce..ed62706a 100644 --- a/capitulos/code/15-more-types/lotto/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/capitulos/code/13-protocol-abc/tombola_runner.py b/code/13-protocol-abc/tombola_runner.py similarity index 100% rename from capitulos/code/13-protocol-abc/tombola_runner.py rename to code/13-protocol-abc/tombola_runner.py diff --git a/capitulos/code/13-protocol-abc/tombola_subhook.py b/code/13-protocol-abc/tombola_subhook.py similarity index 100% rename from capitulos/code/13-protocol-abc/tombola_subhook.py rename to code/13-protocol-abc/tombola_subhook.py diff --git a/capitulos/code/13-protocol-abc/tombola_tests.rst b/code/13-protocol-abc/tombola_tests.rst similarity index 100% rename from capitulos/code/13-protocol-abc/tombola_tests.rst rename to code/13-protocol-abc/tombola_tests.rst diff --git a/capitulos/code/13-protocol-abc/tombolist.py b/code/13-protocol-abc/tombolist.py similarity index 100% rename from capitulos/code/13-protocol-abc/tombolist.py rename to code/13-protocol-abc/tombolist.py diff --git a/capitulos/code/13-protocol-abc/typing/randompick.py b/code/13-protocol-abc/typing/randompick.py similarity index 100% rename from capitulos/code/13-protocol-abc/typing/randompick.py rename to code/13-protocol-abc/typing/randompick.py diff --git a/capitulos/code/13-protocol-abc/typing/randompick_test.py b/code/13-protocol-abc/typing/randompick_test.py similarity index 99% rename from capitulos/code/13-protocol-abc/typing/randompick_test.py rename to code/13-protocol-abc/typing/randompick_test.py index 115c4d3e..be7a6de8 100644 --- a/capitulos/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/capitulos/code/13-protocol-abc/typing/randompickload.py b/code/13-protocol-abc/typing/randompickload.py similarity index 100% rename from capitulos/code/13-protocol-abc/typing/randompickload.py rename to code/13-protocol-abc/typing/randompickload.py diff --git a/capitulos/code/13-protocol-abc/typing/randompickload_test.py b/code/13-protocol-abc/typing/randompickload_test.py similarity index 100% rename from capitulos/code/13-protocol-abc/typing/randompickload_test.py rename to code/13-protocol-abc/typing/randompickload_test.py diff --git a/capitulos/code/13-protocol-abc/typing/vector2d_v4.py b/code/13-protocol-abc/typing/vector2d_v4.py similarity index 90% rename from capitulos/code/13-protocol-abc/typing/vector2d_v4.py rename to code/13-protocol-abc/typing/vector2d_v4.py index deaa8244..15de067e 100644 --- a/capitulos/code/13-protocol-abc/typing/vector2d_v4.py +++ b/code/13-protocol-abc/typing/vector2d_v4.py @@ -23,7 +23,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector2d.frombytes(bytes(v1)) >>> v1_clone @@ -32,7 +32,7 @@ True -Tests of ``format()`` with Cartesian coordinates: +Tests of `format()` with Cartesian coordinates: >>> format(v1) '(3.0, 4.0)' @@ -42,7 +42,7 @@ '(3.000e+00, 4.000e+00)' -Tests of the ``angle`` method:: +Tests of the `.angle()` method:: >>> Vector2d(0, 0).angle() 0.0 @@ -55,7 +55,7 @@ True -Tests of ``format()`` with polar coordinates: +Tests of `format()` with polar coordinates: >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' @@ -72,7 +72,7 @@ >>> v1.x = 123 Traceback (most recent call last): ... - AttributeError: can't set attribute + AttributeError: property 'x' of 'Vector2d' object has no setter Tests of hashing: @@ -127,7 +127,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(array(self.typecode, self))) def __eq__(self, other): @@ -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/capitulos/code/13-protocol-abc/typing/vector2d_v4_test.py b/code/13-protocol-abc/typing/vector2d_v4_test.py similarity index 100% rename from capitulos/code/13-protocol-abc/typing/vector2d_v4_test.py rename to code/13-protocol-abc/typing/vector2d_v4_test.py diff --git a/capitulos/code/13-protocol-abc/typing/vector2d_v5.py b/code/13-protocol-abc/typing/vector2d_v5.py similarity index 92% rename from capitulos/code/13-protocol-abc/typing/vector2d_v5.py rename to code/13-protocol-abc/typing/vector2d_v5.py index 378b8265..f9dbeaee 100644 --- a/capitulos/code/13-protocol-abc/typing/vector2d_v5.py +++ b/code/13-protocol-abc/typing/vector2d_v5.py @@ -25,7 +25,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector2d.frombytes(bytes(v1)) >>> v1_clone @@ -34,7 +34,7 @@ True -Tests of ``format()`` with Cartesian coordinates: +Tests of `format()` with Cartesian coordinates: >>> format(v1) '(3.0, 4.0)' @@ -44,7 +44,7 @@ '(3.000e+00, 4.000e+00)' -Tests of the ``angle`` method:: +Tests of the `.angle()` method:: >>> Vector2d(0, 0).angle() 0.0 @@ -57,7 +57,7 @@ True -Tests of ``format()`` with polar coordinates: +Tests of `format()` with polar coordinates: >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' @@ -128,7 +128,7 @@ def __str__(self) -> str: return str(tuple(self)) def __bytes__(self) -> bytes: - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(array(self.typecode, self))) def __eq__(self, other) -> bool: @@ -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/capitulos/code/13-protocol-abc/typing/vector2d_v5_test.py b/code/13-protocol-abc/typing/vector2d_v5_test.py similarity index 100% rename from capitulos/code/13-protocol-abc/typing/vector2d_v5_test.py rename to code/13-protocol-abc/typing/vector2d_v5_test.py diff --git a/capitulos/code/14-inheritance/README.rst b/code/14-inheritance/README.rst similarity index 100% rename from capitulos/code/14-inheritance/README.rst rename to code/14-inheritance/README.rst diff --git a/capitulos/code/14-inheritance/diamond.py b/code/14-inheritance/diamond.py similarity index 100% rename from capitulos/code/14-inheritance/diamond.py rename to code/14-inheritance/diamond.py diff --git a/capitulos/code/14-inheritance/diamond2.py b/code/14-inheritance/diamond2.py similarity index 100% rename from capitulos/code/14-inheritance/diamond2.py rename to code/14-inheritance/diamond2.py diff --git a/capitulos/code/14-inheritance/strkeydict_dictsub.py b/code/14-inheritance/strkeydict_dictsub.py similarity index 100% rename from capitulos/code/14-inheritance/strkeydict_dictsub.py rename to code/14-inheritance/strkeydict_dictsub.py diff --git a/capitulos/code/14-inheritance/uppermixin.py b/code/14-inheritance/uppermixin.py similarity index 100% rename from capitulos/code/14-inheritance/uppermixin.py rename to code/14-inheritance/uppermixin.py diff --git a/capitulos/code/15-more-types/cafeteria/cafeteria.py b/code/15-more-types/cafeteria/cafeteria.py similarity index 100% rename from capitulos/code/15-more-types/cafeteria/cafeteria.py rename to code/15-more-types/cafeteria/cafeteria.py diff --git a/capitulos/code/15-more-types/cafeteria/contravariant.py b/code/15-more-types/cafeteria/contravariant.py similarity index 100% rename from capitulos/code/15-more-types/cafeteria/contravariant.py rename to code/15-more-types/cafeteria/contravariant.py diff --git a/capitulos/code/15-more-types/cafeteria/covariant.py b/code/15-more-types/cafeteria/covariant.py similarity index 99% rename from capitulos/code/15-more-types/cafeteria/covariant.py rename to code/15-more-types/cafeteria/covariant.py index 768fe2da..c4ca6f33 100644 --- a/capitulos/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/capitulos/code/15-more-types/cafeteria/invariant.py b/code/15-more-types/cafeteria/invariant.py similarity index 100% rename from capitulos/code/15-more-types/cafeteria/invariant.py rename to code/15-more-types/cafeteria/invariant.py diff --git a/capitulos/code/15-more-types/cast/empty.py b/code/15-more-types/cast/empty.py similarity index 100% rename from capitulos/code/15-more-types/cast/empty.py rename to code/15-more-types/cast/empty.py diff --git a/capitulos/code/15-more-types/cast/find.py b/code/15-more-types/cast/find.py similarity index 100% rename from capitulos/code/15-more-types/cast/find.py rename to code/15-more-types/cast/find.py diff --git a/capitulos/code/15-more-types/cast/tcp_echo.py b/code/15-more-types/cast/tcp_echo.py similarity index 100% rename from capitulos/code/15-more-types/cast/tcp_echo.py rename to code/15-more-types/cast/tcp_echo.py diff --git a/capitulos/code/15-more-types/cast/tcp_echo_no_cast.py b/code/15-more-types/cast/tcp_echo_no_cast.py similarity index 100% rename from capitulos/code/15-more-types/cast/tcp_echo_no_cast.py rename to code/15-more-types/cast/tcp_echo_no_cast.py diff --git a/capitulos/code/15-more-types/clip_annot.py b/code/15-more-types/clip_annot.py similarity index 100% rename from capitulos/code/15-more-types/clip_annot.py rename to code/15-more-types/clip_annot.py diff --git a/capitulos/code/15-more-types/clip_annot_demo.py b/code/15-more-types/clip_annot_demo.py similarity index 100% rename from capitulos/code/15-more-types/clip_annot_demo.py rename to code/15-more-types/clip_annot_demo.py diff --git a/capitulos/code/15-more-types/clip_annot_post.py b/code/15-more-types/clip_annot_post.py similarity index 100% rename from capitulos/code/15-more-types/clip_annot_post.py rename to code/15-more-types/clip_annot_post.py diff --git a/capitulos/code/15-more-types/collections_variance.py b/code/15-more-types/collections_variance.py similarity index 100% rename from capitulos/code/15-more-types/collections_variance.py rename to code/15-more-types/collections_variance.py diff --git a/capitulos/code/15-more-types/gen_contra.py b/code/15-more-types/gen_contra.py similarity index 100% rename from capitulos/code/15-more-types/gen_contra.py rename to code/15-more-types/gen_contra.py diff --git a/capitulos/code/15-more-types/lotto/generic_lotto.py b/code/15-more-types/lotto/generic_lotto.py similarity index 100% rename from capitulos/code/15-more-types/lotto/generic_lotto.py rename to code/15-more-types/lotto/generic_lotto.py diff --git a/capitulos/code/15-more-types/lotto/generic_lotto_demo.py b/code/15-more-types/lotto/generic_lotto_demo.py similarity index 100% rename from capitulos/code/15-more-types/lotto/generic_lotto_demo.py rename to code/15-more-types/lotto/generic_lotto_demo.py diff --git a/capitulos/code/15-more-types/lotto/generic_lotto_errors.py b/code/15-more-types/lotto/generic_lotto_errors.py similarity index 100% rename from capitulos/code/15-more-types/lotto/generic_lotto_errors.py rename to code/15-more-types/lotto/generic_lotto_errors.py diff --git a/capitulos/code/13-protocol-abc/tombola.py b/code/15-more-types/lotto/tombola.py similarity index 100% rename from capitulos/code/13-protocol-abc/tombola.py rename to code/15-more-types/lotto/tombola.py diff --git a/capitulos/code/15-more-types/mysum.py b/code/15-more-types/mysum.py similarity index 100% rename from capitulos/code/15-more-types/mysum.py rename to code/15-more-types/mysum.py diff --git a/capitulos/code/15-more-types/petbox/petbox.py b/code/15-more-types/petbox/petbox.py similarity index 100% rename from capitulos/code/15-more-types/petbox/petbox.py rename to code/15-more-types/petbox/petbox.py diff --git a/capitulos/code/15-more-types/petbox/petbox_demo.py b/code/15-more-types/petbox/petbox_demo.py similarity index 100% rename from capitulos/code/15-more-types/petbox/petbox_demo.py rename to code/15-more-types/petbox/petbox_demo.py diff --git a/capitulos/code/15-more-types/protocol/abs_demo.py b/code/15-more-types/protocol/abs_demo.py similarity index 100% rename from capitulos/code/15-more-types/protocol/abs_demo.py rename to code/15-more-types/protocol/abs_demo.py diff --git a/capitulos/code/15-more-types/protocol/mymax/mymax.py b/code/15-more-types/protocol/mymax/mymax.py similarity index 100% rename from capitulos/code/15-more-types/protocol/mymax/mymax.py rename to code/15-more-types/protocol/mymax/mymax.py diff --git a/capitulos/code/15-more-types/protocol/mymax/mymax_demo.py b/code/15-more-types/protocol/mymax/mymax_demo.py similarity index 100% rename from capitulos/code/15-more-types/protocol/mymax/mymax_demo.py rename to code/15-more-types/protocol/mymax/mymax_demo.py diff --git a/capitulos/code/15-more-types/protocol/mymax/mymax_test.py b/code/15-more-types/protocol/mymax/mymax_test.py similarity index 100% rename from capitulos/code/15-more-types/protocol/mymax/mymax_test.py rename to code/15-more-types/protocol/mymax/mymax_test.py diff --git a/capitulos/code/15-more-types/protocol/random/erp.py b/code/15-more-types/protocol/random/erp.py similarity index 100% rename from capitulos/code/15-more-types/protocol/random/erp.py rename to code/15-more-types/protocol/random/erp.py diff --git a/capitulos/code/15-more-types/protocol/random/erp_test.py b/code/15-more-types/protocol/random/erp_test.py similarity index 100% rename from capitulos/code/15-more-types/protocol/random/erp_test.py rename to code/15-more-types/protocol/random/erp_test.py diff --git a/capitulos/code/15-more-types/protocol/random/generic_randompick.py b/code/15-more-types/protocol/random/generic_randompick.py similarity index 100% rename from capitulos/code/15-more-types/protocol/random/generic_randompick.py rename to code/15-more-types/protocol/random/generic_randompick.py diff --git a/capitulos/code/15-more-types/protocol/random/generic_randompick_test.py b/code/15-more-types/protocol/random/generic_randompick_test.py similarity index 100% rename from capitulos/code/15-more-types/protocol/random/generic_randompick_test.py rename to code/15-more-types/protocol/random/generic_randompick_test.py diff --git a/capitulos/code/15-more-types/protocol/random/randompop.py b/code/15-more-types/protocol/random/randompop.py similarity index 100% rename from capitulos/code/15-more-types/protocol/random/randompop.py rename to code/15-more-types/protocol/random/randompop.py diff --git a/capitulos/code/15-more-types/protocol/random/randompop_test.py b/code/15-more-types/protocol/random/randompop_test.py similarity index 100% rename from capitulos/code/15-more-types/protocol/random/randompop_test.py rename to code/15-more-types/protocol/random/randompop_test.py diff --git a/capitulos/code/15-more-types/typeddict/books.py b/code/15-more-types/typeddict/books.py similarity index 100% rename from capitulos/code/15-more-types/typeddict/books.py rename to code/15-more-types/typeddict/books.py diff --git a/capitulos/code/15-more-types/typeddict/books_any.py b/code/15-more-types/typeddict/books_any.py similarity index 100% rename from capitulos/code/15-more-types/typeddict/books_any.py rename to code/15-more-types/typeddict/books_any.py diff --git a/capitulos/code/15-more-types/typeddict/demo_books.py b/code/15-more-types/typeddict/demo_books.py similarity index 100% rename from capitulos/code/15-more-types/typeddict/demo_books.py rename to code/15-more-types/typeddict/demo_books.py diff --git a/capitulos/code/15-more-types/typeddict/demo_not_book.py b/code/15-more-types/typeddict/demo_not_book.py similarity index 100% rename from capitulos/code/15-more-types/typeddict/demo_not_book.py rename to code/15-more-types/typeddict/demo_not_book.py diff --git a/capitulos/code/15-more-types/typeddict/test_books.py b/code/15-more-types/typeddict/test_books.py similarity index 100% rename from capitulos/code/15-more-types/typeddict/test_books.py rename to code/15-more-types/typeddict/test_books.py diff --git a/capitulos/code/15-more-types/typeddict/test_books_check_fails.py b/code/15-more-types/typeddict/test_books_check_fails.py similarity index 100% rename from capitulos/code/15-more-types/typeddict/test_books_check_fails.py rename to code/15-more-types/typeddict/test_books_check_fails.py diff --git a/capitulos/code/16-op-overloading/README.rst b/code/16-op-overloading/README.rst similarity index 100% rename from capitulos/code/16-op-overloading/README.rst rename to code/16-op-overloading/README.rst diff --git a/capitulos/code/16-op-overloading/Untitled.ipynb b/code/16-op-overloading/Untitled.ipynb similarity index 100% rename from capitulos/code/16-op-overloading/Untitled.ipynb rename to code/16-op-overloading/Untitled.ipynb diff --git a/capitulos/code/16-op-overloading/bingo.py b/code/16-op-overloading/bingo.py similarity index 100% rename from capitulos/code/16-op-overloading/bingo.py rename to code/16-op-overloading/bingo.py diff --git a/capitulos/code/16-op-overloading/bingoaddable.py b/code/16-op-overloading/bingoaddable.py similarity index 100% rename from capitulos/code/16-op-overloading/bingoaddable.py rename to code/16-op-overloading/bingoaddable.py diff --git a/capitulos/code/16-op-overloading/tombola.py b/code/16-op-overloading/tombola.py similarity index 100% rename from capitulos/code/16-op-overloading/tombola.py rename to code/16-op-overloading/tombola.py diff --git a/capitulos/code/16-op-overloading/unary_plus_decimal.py b/code/16-op-overloading/unary_plus_decimal.py similarity index 100% rename from capitulos/code/16-op-overloading/unary_plus_decimal.py rename to code/16-op-overloading/unary_plus_decimal.py diff --git a/capitulos/code/16-op-overloading/vector2d_v3.py b/code/16-op-overloading/vector2d_v3.py similarity index 91% rename from capitulos/code/16-op-overloading/vector2d_v3.py rename to code/16-op-overloading/vector2d_v3.py index 9ee716ca..27fa1cb7 100644 --- a/capitulos/code/16-op-overloading/vector2d_v3.py +++ b/code/16-op-overloading/vector2d_v3.py @@ -23,7 +23,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector2d.frombytes(bytes(v1)) >>> v1_clone @@ -32,7 +32,7 @@ True -Tests of ``format()`` with Cartesian coordinates: +Tests of `format()` with Cartesian coordinates: >>> format(v1) '(3.0, 4.0)' @@ -42,7 +42,7 @@ '(3.000e+00, 4.000e+00)' -Tests of the ``angle`` method:: +Tests of the `.angle()` method:: >>> Vector2d(0, 0).angle() 0.0 @@ -55,7 +55,7 @@ True -Tests of ``format()`` with polar coordinates: +Tests of `format()` with polar coordinates: >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' @@ -72,7 +72,7 @@ >>> v1.x = 123 Traceback (most recent call last): ... - AttributeError: can't set attribute 'x' + AttributeError: property 'x' of 'Vector2d' object has no setter Tests of hashing: @@ -117,7 +117,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(array(self.typecode, self))) def __eq__(self, other): diff --git a/capitulos/code/16-op-overloading/vector_v6.py b/code/16-op-overloading/vector_v6.py similarity index 94% rename from capitulos/code/16-op-overloading/vector_v6.py rename to code/16-op-overloading/vector_v6.py index 6ae4dcf2..6f3115e1 100644 --- a/capitulos/code/16-op-overloading/vector_v6.py +++ b/code/16-op-overloading/vector_v6.py @@ -1,7 +1,7 @@ """ -A multi-dimensional ``Vector`` class, take 6: operator ``+`` +A multi-dimensional `Vector` class, take 6: operator ``+`` -A ``Vector`` is built from an iterable of numbers:: +A `Vector` is built from an iterable of numbers:: >>> Vector([3.1, 4.2]) Vector([3.1, 4.2]) @@ -33,7 +33,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone @@ -70,7 +70,7 @@ 9.53939201... -Test of ``.__bytes__`` and ``.frombytes()`` methods:: +Test of `.__bytes__` and `.frombytes()` methods:: >>> v1 = Vector([3, 4, 5]) >>> v1_clone = Vector.frombytes(bytes(v1)) @@ -146,7 +146,7 @@ True -Tests of ``format()`` with Cartesian coordinates in 2D:: +Tests of `format()` with Cartesian coordinates in 2D:: >>> v1 = Vector([3, 4]) >>> format(v1) @@ -157,7 +157,7 @@ '(3.000e+00, 4.000e+00)' -Tests of ``format()`` with Cartesian coordinates in 3D and 7D:: +Tests of `format()` with Cartesian coordinates in 3D and 7D:: >>> v3 = Vector([3, 4, 5]) >>> format(v3) @@ -166,7 +166,7 @@ '(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)' -Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D:: +Tests of `format()` with spherical coordinates in 2D, 3D and 4D:: >>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' @@ -270,7 +270,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(self._components)) def __eq__(self, other): diff --git a/capitulos/code/16-op-overloading/vector_v7.py b/code/16-op-overloading/vector_v7.py similarity index 95% rename from capitulos/code/16-op-overloading/vector_v7.py rename to code/16-op-overloading/vector_v7.py index 953622e8..885249a1 100644 --- a/capitulos/code/16-op-overloading/vector_v7.py +++ b/code/16-op-overloading/vector_v7.py @@ -1,7 +1,7 @@ """ -A multi-dimensional ``Vector`` class, take 7: operator ``*`` +A multi-dimensional `Vector` class, take 7: operator ``*`` -A ``Vector`` is built from an iterable of numbers:: +A `Vector` is built from an iterable of numbers:: >>> Vector([3.1, 4.2]) Vector([3.1, 4.2]) @@ -33,7 +33,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone @@ -70,7 +70,7 @@ 9.53939201... -Test of ``.__bytes__`` and ``.frombytes()`` methods:: +Test of `.__bytes__` and `.frombytes()` methods:: >>> v1 = Vector([3, 4, 5]) >>> v1_clone = Vector.frombytes(bytes(v1)) @@ -146,7 +146,7 @@ True -Tests of ``format()`` with Cartesian coordinates in 2D:: +Tests of `format()` with Cartesian coordinates in 2D:: >>> v1 = Vector([3, 4]) >>> format(v1) @@ -157,7 +157,7 @@ '(3.000e+00, 4.000e+00)' -Tests of ``format()`` with Cartesian coordinates in 3D and 7D:: +Tests of `format()` with Cartesian coordinates in 3D and 7D:: >>> v3 = Vector([3, 4, 5]) >>> format(v3) @@ -166,7 +166,7 @@ '(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)' -Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D:: +Tests of `format()` with spherical coordinates in 2D, 3D and 4D:: >>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' @@ -322,7 +322,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(self._components)) def __eq__(self, other): diff --git a/capitulos/code/16-op-overloading/vector_v8.py b/code/16-op-overloading/vector_v8.py similarity index 95% rename from capitulos/code/16-op-overloading/vector_v8.py rename to code/16-op-overloading/vector_v8.py index 44c05fd8..90dff396 100644 --- a/capitulos/code/16-op-overloading/vector_v8.py +++ b/code/16-op-overloading/vector_v8.py @@ -1,7 +1,7 @@ """ -A multi-dimensional ``Vector`` class, take 8: operator ``==`` +A multi-dimensional `Vector` class, take 8: operator ``==`` -A ``Vector`` is built from an iterable of numbers:: +A `Vector` is built from an iterable of numbers:: >>> Vector([3.1, 4.2]) Vector([3.1, 4.2]) @@ -33,7 +33,7 @@ (True, False) -Test of ``.frombytes()`` class method: +Test of `.frombytes()` class method: >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone @@ -70,7 +70,7 @@ 9.53939201... -Test of ``.__bytes__`` and ``.frombytes()`` methods:: +Test of `.__bytes__` and `.frombytes()` methods:: >>> v1 = Vector([3, 4, 5]) >>> v1_clone = Vector.frombytes(bytes(v1)) @@ -146,7 +146,7 @@ True -Tests of ``format()`` with Cartesian coordinates in 2D:: +Tests of `format()` with Cartesian coordinates in 2D:: >>> v1 = Vector([3, 4]) >>> format(v1) @@ -157,7 +157,7 @@ '(3.000e+00, 4.000e+00)' -Tests of ``format()`` with Cartesian coordinates in 3D and 7D:: +Tests of `format()` with Cartesian coordinates in 3D and 7D:: >>> v3 = Vector([3, 4, 5]) >>> format(v3) @@ -166,7 +166,7 @@ '(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)' -Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D:: +Tests of `format()` with spherical coordinates in 2D, 3D and 4D:: >>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' @@ -323,7 +323,7 @@ def __str__(self): return str(tuple(self)) def __bytes__(self): - return (bytes([ord(self.typecode)]) + + return (self.typecode.encode('ascii') + bytes(self._components)) # tag::VECTOR_V8_EQ[] diff --git a/capitulos/code/17-it-generator/README.rst b/code/17-it-generator/README.rst similarity index 100% rename from capitulos/code/17-it-generator/README.rst rename to code/17-it-generator/README.rst diff --git a/capitulos/code/17-it-generator/aritprog.rst b/code/17-it-generator/aritprog.rst similarity index 100% rename from capitulos/code/17-it-generator/aritprog.rst rename to code/17-it-generator/aritprog.rst diff --git a/capitulos/code/17-it-generator/aritprog_float_error.py b/code/17-it-generator/aritprog_float_error.py similarity index 100% rename from capitulos/code/17-it-generator/aritprog_float_error.py rename to code/17-it-generator/aritprog_float_error.py diff --git a/capitulos/code/17-it-generator/aritprog_runner.py b/code/17-it-generator/aritprog_runner.py similarity index 100% rename from capitulos/code/17-it-generator/aritprog_runner.py rename to code/17-it-generator/aritprog_runner.py diff --git a/capitulos/code/17-it-generator/aritprog_v0.py b/code/17-it-generator/aritprog_v0.py similarity index 100% rename from capitulos/code/17-it-generator/aritprog_v0.py rename to code/17-it-generator/aritprog_v0.py diff --git a/capitulos/code/17-it-generator/aritprog_v1.py b/code/17-it-generator/aritprog_v1.py similarity index 100% rename from capitulos/code/17-it-generator/aritprog_v1.py rename to code/17-it-generator/aritprog_v1.py diff --git a/capitulos/code/17-it-generator/aritprog_v2.py b/code/17-it-generator/aritprog_v2.py similarity index 100% rename from capitulos/code/17-it-generator/aritprog_v2.py rename to code/17-it-generator/aritprog_v2.py diff --git a/capitulos/code/17-it-generator/aritprog_v3.py b/code/17-it-generator/aritprog_v3.py similarity index 100% rename from capitulos/code/17-it-generator/aritprog_v3.py rename to code/17-it-generator/aritprog_v3.py diff --git a/capitulos/code/17-it-generator/columnize_iter.py b/code/17-it-generator/columnize_iter.py similarity index 100% rename from capitulos/code/17-it-generator/columnize_iter.py rename to code/17-it-generator/columnize_iter.py diff --git a/capitulos/code/17-it-generator/coroaverager.py b/code/17-it-generator/coroaverager.py similarity index 100% rename from capitulos/code/17-it-generator/coroaverager.py rename to code/17-it-generator/coroaverager.py diff --git a/capitulos/code/17-it-generator/coroaverager2.py b/code/17-it-generator/coroaverager2.py similarity index 92% rename from capitulos/code/17-it-generator/coroaverager2.py rename to code/17-it-generator/coroaverager2.py index 3a15f5e2..c8d81b30 100644 --- a/capitulos/code/17-it-generator/coroaverager2.py +++ b/code/17-it-generator/coroaverager2.py @@ -25,7 +25,7 @@ >>> coro_avg.send(30) >>> coro_avg.send(6.5) >>> try: - ... coro_avg.send(STOP) # <1> + ... coro_avg.send(Sentinel()) # <1> ... except StopIteration as exc: ... result = exc.value # <2> ... @@ -45,7 +45,7 @@ ... return res # <3> ... >>> comp = compute() # <4> - >>> for v in [None, 10, 20, 30, STOP]: # <5> + >>> for v in [None, 10, 20, 30, Sentinel()]: # <5> ... try: ... comp.send(v) # <6> ... except StopIteration as exc: # <7> @@ -78,7 +78,7 @@ def __repr__(self): SendType = Union[float, Sentinel] # <5> # end::RETURNING_AVERAGER_TOP[] # tag::RETURNING_AVERAGER[] -def averager2(verbose: bool = False) -> Generator[None, SendType, Result]: # <1> +def averager2(verbose:bool=False) -> Generator[None, SendType, Result]: # <1> total = 0.0 count = 0 average = 0.0 diff --git a/capitulos/code/17-it-generator/fibo_by_hand.py b/code/17-it-generator/fibo_by_hand.py similarity index 100% rename from capitulos/code/17-it-generator/fibo_by_hand.py rename to code/17-it-generator/fibo_by_hand.py diff --git a/capitulos/code/17-it-generator/fibo_gen.py b/code/17-it-generator/fibo_gen.py similarity index 100% rename from capitulos/code/17-it-generator/fibo_gen.py rename to code/17-it-generator/fibo_gen.py diff --git a/capitulos/code/17-it-generator/isis2json/README.rst b/code/17-it-generator/isis2json/README.rst similarity index 100% rename from capitulos/code/17-it-generator/isis2json/README.rst rename to code/17-it-generator/isis2json/README.rst diff --git a/capitulos/code/17-it-generator/isis2json/isis2json.py b/code/17-it-generator/isis2json/isis2json.py similarity index 100% rename from capitulos/code/17-it-generator/isis2json/isis2json.py rename to code/17-it-generator/isis2json/isis2json.py diff --git a/capitulos/code/17-it-generator/isis2json/iso2709.py b/code/17-it-generator/isis2json/iso2709.py similarity index 100% rename from capitulos/code/17-it-generator/isis2json/iso2709.py rename to code/17-it-generator/isis2json/iso2709.py diff --git a/capitulos/code/17-it-generator/isis2json/subfield.py b/code/17-it-generator/isis2json/subfield.py similarity index 100% rename from capitulos/code/17-it-generator/isis2json/subfield.py rename to code/17-it-generator/isis2json/subfield.py diff --git a/capitulos/code/17-it-generator/iter_gen_type.py b/code/17-it-generator/iter_gen_type.py similarity index 100% rename from capitulos/code/17-it-generator/iter_gen_type.py rename to code/17-it-generator/iter_gen_type.py diff --git a/capitulos/code/17-it-generator/sentence.py b/code/17-it-generator/sentence.py similarity index 100% rename from capitulos/code/17-it-generator/sentence.py rename to code/17-it-generator/sentence.py diff --git a/capitulos/code/17-it-generator/sentence.rst b/code/17-it-generator/sentence.rst similarity index 100% rename from capitulos/code/17-it-generator/sentence.rst rename to code/17-it-generator/sentence.rst diff --git a/capitulos/code/17-it-generator/sentence_gen.py b/code/17-it-generator/sentence_gen.py similarity index 90% rename from capitulos/code/17-it-generator/sentence_gen.py rename to code/17-it-generator/sentence_gen.py index 1f7ebebf..c5b58404 100644 --- a/capitulos/code/17-it-generator/sentence_gen.py +++ b/code/17-it-generator/sentence_gen.py @@ -8,7 +8,6 @@ RE_WORD = re.compile(r'\w+') - class Sentence: def __init__(self, text): @@ -20,9 +19,7 @@ def __repr__(self): def __iter__(self): for word in self.words: # <1> - yield word # <2> - # <3> + yield word # <2> <3> # done! <4> - # end::SENTENCE_GEN[] diff --git a/capitulos/code/17-it-generator/sentence_gen2.py b/code/17-it-generator/sentence_gen2.py similarity index 100% rename from capitulos/code/17-it-generator/sentence_gen2.py rename to code/17-it-generator/sentence_gen2.py diff --git a/capitulos/code/17-it-generator/sentence_genexp.py b/code/17-it-generator/sentence_genexp.py similarity index 100% rename from capitulos/code/17-it-generator/sentence_genexp.py rename to code/17-it-generator/sentence_genexp.py diff --git a/capitulos/code/17-it-generator/sentence_iter.py b/code/17-it-generator/sentence_iter.py similarity index 100% rename from capitulos/code/17-it-generator/sentence_iter.py rename to code/17-it-generator/sentence_iter.py diff --git a/capitulos/code/17-it-generator/sentence_iter2.py b/code/17-it-generator/sentence_iter2.py similarity index 100% rename from capitulos/code/17-it-generator/sentence_iter2.py rename to code/17-it-generator/sentence_iter2.py diff --git a/capitulos/code/17-it-generator/sentence_runner.py b/code/17-it-generator/sentence_runner.py similarity index 100% rename from capitulos/code/17-it-generator/sentence_runner.py rename to code/17-it-generator/sentence_runner.py diff --git a/capitulos/code/17-it-generator/tree/4steps/tree_step0.py b/code/17-it-generator/tree/4steps/tree_step0.py similarity index 100% rename from capitulos/code/17-it-generator/tree/4steps/tree_step0.py rename to code/17-it-generator/tree/4steps/tree_step0.py diff --git a/capitulos/code/17-it-generator/tree/4steps/tree_step1.py b/code/17-it-generator/tree/4steps/tree_step1.py similarity index 100% rename from capitulos/code/17-it-generator/tree/4steps/tree_step1.py rename to code/17-it-generator/tree/4steps/tree_step1.py diff --git a/capitulos/code/17-it-generator/tree/4steps/tree_step2.py b/code/17-it-generator/tree/4steps/tree_step2.py similarity index 100% rename from capitulos/code/17-it-generator/tree/4steps/tree_step2.py rename to code/17-it-generator/tree/4steps/tree_step2.py diff --git a/capitulos/code/17-it-generator/tree/4steps/tree_step3.py b/code/17-it-generator/tree/4steps/tree_step3.py similarity index 100% rename from capitulos/code/17-it-generator/tree/4steps/tree_step3.py rename to code/17-it-generator/tree/4steps/tree_step3.py diff --git a/capitulos/code/17-it-generator/tree/classtree/classtree.py b/code/17-it-generator/tree/classtree/classtree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/classtree/classtree.py rename to code/17-it-generator/tree/classtree/classtree.py diff --git a/capitulos/code/17-it-generator/tree/classtree/classtree_test.py b/code/17-it-generator/tree/classtree/classtree_test.py similarity index 100% rename from capitulos/code/17-it-generator/tree/classtree/classtree_test.py rename to code/17-it-generator/tree/classtree/classtree_test.py diff --git a/capitulos/code/17-it-generator/tree/extra/drawtree.py b/code/17-it-generator/tree/extra/drawtree.py similarity index 96% rename from capitulos/code/17-it-generator/tree/extra/drawtree.py rename to code/17-it-generator/tree/extra/drawtree.py index b74cd331..04b73376 100644 --- a/capitulos/code/17-it-generator/tree/extra/drawtree.py +++ b/code/17-it-generator/tree/extra/drawtree.py @@ -41,4 +41,5 @@ def draw(cls): if __name__ == '__main__': - draw(BaseException) + import numbers + draw(numbers.Number) diff --git a/capitulos/code/17-it-generator/tree/extra/test_drawtree.py b/code/17-it-generator/tree/extra/test_drawtree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/extra/test_drawtree.py rename to code/17-it-generator/tree/extra/test_drawtree.py diff --git a/capitulos/code/17-it-generator/tree/extra/test_tree.py b/code/17-it-generator/tree/extra/test_tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/extra/test_tree.py rename to code/17-it-generator/tree/extra/test_tree.py diff --git a/capitulos/code/17-it-generator/tree/extra/tree.py b/code/17-it-generator/tree/extra/tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/extra/tree.py rename to code/17-it-generator/tree/extra/tree.py diff --git a/capitulos/code/17-it-generator/tree/step0/test_tree.py b/code/17-it-generator/tree/step0/test_tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/step0/test_tree.py rename to code/17-it-generator/tree/step0/test_tree.py diff --git a/capitulos/code/17-it-generator/tree/step0/tree.py b/code/17-it-generator/tree/step0/tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/step0/tree.py rename to code/17-it-generator/tree/step0/tree.py diff --git a/capitulos/code/17-it-generator/tree/step1/test_tree.py b/code/17-it-generator/tree/step1/test_tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/step1/test_tree.py rename to code/17-it-generator/tree/step1/test_tree.py diff --git a/capitulos/code/17-it-generator/tree/step1/tree.py b/code/17-it-generator/tree/step1/tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/step1/tree.py rename to code/17-it-generator/tree/step1/tree.py diff --git a/capitulos/code/17-it-generator/tree/step2/test_tree.py b/code/17-it-generator/tree/step2/test_tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/step2/test_tree.py rename to code/17-it-generator/tree/step2/test_tree.py diff --git a/capitulos/code/17-it-generator/tree/step2/tree.py b/code/17-it-generator/tree/step2/tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/step2/tree.py rename to code/17-it-generator/tree/step2/tree.py diff --git a/capitulos/code/17-it-generator/tree/step3/test_tree.py b/code/17-it-generator/tree/step3/test_tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/step3/test_tree.py rename to code/17-it-generator/tree/step3/test_tree.py diff --git a/capitulos/code/17-it-generator/tree/step3/tree.py b/code/17-it-generator/tree/step3/tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/step3/tree.py rename to code/17-it-generator/tree/step3/tree.py diff --git a/capitulos/code/17-it-generator/tree/step4/test_tree.py b/code/17-it-generator/tree/step4/test_tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/step4/test_tree.py rename to code/17-it-generator/tree/step4/test_tree.py diff --git a/capitulos/code/17-it-generator/tree/step4/tree.py b/code/17-it-generator/tree/step4/tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/step4/tree.py rename to code/17-it-generator/tree/step4/tree.py diff --git a/capitulos/code/17-it-generator/tree/step5/test_tree.py b/code/17-it-generator/tree/step5/test_tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/step5/test_tree.py rename to code/17-it-generator/tree/step5/test_tree.py diff --git a/capitulos/code/17-it-generator/tree/step5/tree.py b/code/17-it-generator/tree/step5/tree.py similarity index 99% rename from capitulos/code/17-it-generator/tree/step5/tree.py rename to code/17-it-generator/tree/step5/tree.py index df5f5af0..6f28bbd8 100644 --- a/capitulos/code/17-it-generator/tree/step5/tree.py +++ b/code/17-it-generator/tree/step5/tree.py @@ -2,18 +2,15 @@ def tree(cls): yield cls.__name__, 0 yield from sub_tree(cls, 1) - def sub_tree(cls, level): for sub_cls in cls.__subclasses__(): yield sub_cls.__name__, level yield from sub_tree(sub_cls, level+1) - def display(cls): for cls_name, level in tree(cls): indent = ' ' * 4 * level print(f'{indent}{cls_name}') - if __name__ == '__main__': display(BaseException) diff --git a/capitulos/code/17-it-generator/tree/step6/test_tree.py b/code/17-it-generator/tree/step6/test_tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/step6/test_tree.py rename to code/17-it-generator/tree/step6/test_tree.py diff --git a/capitulos/code/17-it-generator/tree/step6/tree.py b/code/17-it-generator/tree/step6/tree.py similarity index 100% rename from capitulos/code/17-it-generator/tree/step6/tree.py rename to code/17-it-generator/tree/step6/tree.py diff --git a/capitulos/code/17-it-generator/yield_delegate_fail.py b/code/17-it-generator/yield_delegate_fail.py similarity index 100% rename from capitulos/code/17-it-generator/yield_delegate_fail.py rename to code/17-it-generator/yield_delegate_fail.py diff --git a/capitulos/code/17-it-generator/yield_delegate_fix.py b/code/17-it-generator/yield_delegate_fix.py similarity index 100% rename from capitulos/code/17-it-generator/yield_delegate_fix.py rename to code/17-it-generator/yield_delegate_fix.py diff --git a/capitulos/code/18-with-match/README.rst b/code/18-with-match/README.rst similarity index 100% rename from capitulos/code/18-with-match/README.rst rename to code/18-with-match/README.rst diff --git a/capitulos/code/18-with-match/lispy/LICENSE b/code/18-with-match/lispy/LICENSE similarity index 100% rename from capitulos/code/18-with-match/lispy/LICENSE rename to code/18-with-match/lispy/LICENSE diff --git a/capitulos/code/18-with-match/lispy/README.md b/code/18-with-match/lispy/README.md similarity index 100% rename from capitulos/code/18-with-match/lispy/README.md rename to code/18-with-match/lispy/README.md diff --git a/capitulos/code/18-with-match/lispy/original/LICENSE b/code/18-with-match/lispy/original/LICENSE similarity index 100% rename from capitulos/code/18-with-match/lispy/original/LICENSE rename to code/18-with-match/lispy/original/LICENSE diff --git a/capitulos/code/18-with-match/lispy/original/README.md b/code/18-with-match/lispy/original/README.md similarity index 100% rename from capitulos/code/18-with-match/lispy/original/README.md rename to code/18-with-match/lispy/original/README.md diff --git a/capitulos/code/18-with-match/lispy/original/lis.py b/code/18-with-match/lispy/original/lis.py similarity index 100% rename from capitulos/code/18-with-match/lispy/original/lis.py rename to code/18-with-match/lispy/original/lis.py diff --git a/capitulos/code/18-with-match/lispy/original/lispy.py b/code/18-with-match/lispy/original/lispy.py similarity index 100% rename from capitulos/code/18-with-match/lispy/original/lispy.py rename to code/18-with-match/lispy/original/lispy.py diff --git a/capitulos/code/18-with-match/lispy/original/lispytest.py b/code/18-with-match/lispy/original/lispytest.py similarity index 100% rename from capitulos/code/18-with-match/lispy/original/lispytest.py rename to code/18-with-match/lispy/original/lispytest.py diff --git a/capitulos/code/18-with-match/lispy/py3.10/examples_test.py b/code/18-with-match/lispy/py3.10/examples_test.py similarity index 100% rename from capitulos/code/18-with-match/lispy/py3.10/examples_test.py rename to code/18-with-match/lispy/py3.10/examples_test.py diff --git a/capitulos/code/18-with-match/lispy/py3.10/lis.py b/code/18-with-match/lispy/py3.10/lis.py similarity index 98% rename from capitulos/code/18-with-match/lispy/py3.10/lis.py rename to code/18-with-match/lispy/py3.10/lis.py index f2d982c7..dd731084 100755 --- a/capitulos/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/capitulos/code/18-with-match/lispy/py3.10/lis_test.py b/code/18-with-match/lispy/py3.10/lis_test.py similarity index 100% rename from capitulos/code/18-with-match/lispy/py3.10/lis_test.py rename to code/18-with-match/lispy/py3.10/lis_test.py diff --git a/capitulos/code/18-with-match/lispy/py3.10/quicksort.scm b/code/18-with-match/lispy/py3.10/quicksort.scm similarity index 100% rename from capitulos/code/18-with-match/lispy/py3.10/quicksort.scm rename to code/18-with-match/lispy/py3.10/quicksort.scm diff --git a/capitulos/code/18-with-match/lispy/py3.9/README.md b/code/18-with-match/lispy/py3.9/README.md similarity index 100% rename from capitulos/code/18-with-match/lispy/py3.9/README.md rename to code/18-with-match/lispy/py3.9/README.md diff --git a/capitulos/code/18-with-match/lispy/py3.9/examples_test.py b/code/18-with-match/lispy/py3.9/examples_test.py similarity index 100% rename from capitulos/code/18-with-match/lispy/py3.9/examples_test.py rename to code/18-with-match/lispy/py3.9/examples_test.py diff --git a/capitulos/code/18-with-match/lispy/py3.9/lis.py b/code/18-with-match/lispy/py3.9/lis.py similarity index 100% rename from capitulos/code/18-with-match/lispy/py3.9/lis.py rename to code/18-with-match/lispy/py3.9/lis.py diff --git a/capitulos/code/18-with-match/lispy/py3.9/lis_test.py b/code/18-with-match/lispy/py3.9/lis_test.py similarity index 100% rename from capitulos/code/18-with-match/lispy/py3.9/lis_test.py rename to code/18-with-match/lispy/py3.9/lis_test.py diff --git a/capitulos/code/18-with-match/mirror.py b/code/18-with-match/mirror.py similarity index 100% rename from capitulos/code/18-with-match/mirror.py rename to code/18-with-match/mirror.py diff --git a/capitulos/code/18-with-match/mirror_gen.py b/code/18-with-match/mirror_gen.py similarity index 100% rename from capitulos/code/18-with-match/mirror_gen.py rename to code/18-with-match/mirror_gen.py diff --git a/capitulos/code/18-with-match/mirror_gen_exc.py b/code/18-with-match/mirror_gen_exc.py similarity index 100% rename from capitulos/code/18-with-match/mirror_gen_exc.py rename to code/18-with-match/mirror_gen_exc.py diff --git a/capitulos/code/19-concurrency/primes/README.md b/code/19-concurrency/primes/README.md similarity index 100% rename from capitulos/code/19-concurrency/primes/README.md rename to code/19-concurrency/primes/README.md diff --git a/capitulos/code/19-concurrency/primes/log-procs.txt b/code/19-concurrency/primes/log-procs.txt similarity index 100% rename from capitulos/code/19-concurrency/primes/log-procs.txt rename to code/19-concurrency/primes/log-procs.txt diff --git a/capitulos/code/19-concurrency/primes/primes.py b/code/19-concurrency/primes/primes.py similarity index 99% rename from capitulos/code/19-concurrency/primes/primes.py rename to code/19-concurrency/primes/primes.py index bb917717..582b2b3c 100755 --- a/capitulos/code/19-concurrency/primes/primes.py +++ b/code/19-concurrency/primes/primes.py @@ -35,7 +35,6 @@ def is_prime(n: int) -> bool: return True if n % 2 == 0: return False - root = math.isqrt(n) for i in range(3, root + 1, 2): if n % i == 0: diff --git a/capitulos/code/19-concurrency/primes/procs.py b/code/19-concurrency/primes/procs.py similarity index 100% rename from capitulos/code/19-concurrency/primes/procs.py rename to code/19-concurrency/primes/procs.py diff --git a/capitulos/code/19-concurrency/primes/procs_race_condition.py b/code/19-concurrency/primes/procs_race_condition.py similarity index 100% rename from capitulos/code/19-concurrency/primes/procs_race_condition.py rename to code/19-concurrency/primes/procs_race_condition.py diff --git a/capitulos/code/19-concurrency/primes/py36/primes.py b/code/19-concurrency/primes/py36/primes.py similarity index 100% rename from capitulos/code/19-concurrency/primes/py36/primes.py rename to code/19-concurrency/primes/py36/primes.py diff --git a/capitulos/code/19-concurrency/primes/py36/procs.py b/code/19-concurrency/primes/py36/procs.py similarity index 100% rename from capitulos/code/19-concurrency/primes/py36/procs.py rename to code/19-concurrency/primes/py36/procs.py diff --git a/capitulos/code/19-concurrency/primes/run_procs.sh b/code/19-concurrency/primes/run_procs.sh similarity index 100% rename from capitulos/code/19-concurrency/primes/run_procs.sh rename to code/19-concurrency/primes/run_procs.sh diff --git a/capitulos/code/19-concurrency/primes/sequential.py b/code/19-concurrency/primes/sequential.py similarity index 100% rename from capitulos/code/19-concurrency/primes/sequential.py rename to code/19-concurrency/primes/sequential.py diff --git a/capitulos/code/19-concurrency/primes/spinner_prime_async_broken.py b/code/19-concurrency/primes/spinner_prime_async_broken.py similarity index 100% rename from capitulos/code/19-concurrency/primes/spinner_prime_async_broken.py rename to code/19-concurrency/primes/spinner_prime_async_broken.py diff --git a/capitulos/code/19-concurrency/primes/spinner_prime_async_nap.py b/code/19-concurrency/primes/spinner_prime_async_nap.py similarity index 100% rename from capitulos/code/19-concurrency/primes/spinner_prime_async_nap.py rename to code/19-concurrency/primes/spinner_prime_async_nap.py diff --git a/capitulos/code/19-concurrency/primes/spinner_prime_proc.py b/code/19-concurrency/primes/spinner_prime_proc.py similarity index 100% rename from capitulos/code/19-concurrency/primes/spinner_prime_proc.py rename to code/19-concurrency/primes/spinner_prime_proc.py diff --git a/capitulos/code/19-concurrency/primes/spinner_prime_thread.py b/code/19-concurrency/primes/spinner_prime_thread.py similarity index 100% rename from capitulos/code/19-concurrency/primes/spinner_prime_thread.py rename to code/19-concurrency/primes/spinner_prime_thread.py diff --git a/capitulos/code/19-concurrency/primes/stats-procs.ipynb b/code/19-concurrency/primes/stats-procs.ipynb similarity index 100% rename from capitulos/code/19-concurrency/primes/stats-procs.ipynb rename to code/19-concurrency/primes/stats-procs.ipynb diff --git a/capitulos/code/19-concurrency/primes/threads.py b/code/19-concurrency/primes/threads.py similarity index 100% rename from capitulos/code/19-concurrency/primes/threads.py rename to code/19-concurrency/primes/threads.py diff --git a/capitulos/code/19-concurrency/spinner_async.py b/code/19-concurrency/spinner_async.py similarity index 100% rename from capitulos/code/19-concurrency/spinner_async.py rename to code/19-concurrency/spinner_async.py diff --git a/capitulos/code/19-concurrency/spinner_async_experiment.py b/code/19-concurrency/spinner_async_experiment.py similarity index 100% rename from capitulos/code/19-concurrency/spinner_async_experiment.py rename to code/19-concurrency/spinner_async_experiment.py diff --git a/capitulos/code/19-concurrency/spinner_proc.py b/code/19-concurrency/spinner_proc.py similarity index 100% rename from capitulos/code/19-concurrency/spinner_proc.py rename to code/19-concurrency/spinner_proc.py diff --git a/capitulos/code/19-concurrency/spinner_thread.py b/code/19-concurrency/spinner_thread.py similarity index 100% rename from capitulos/code/19-concurrency/spinner_thread.py rename to code/19-concurrency/spinner_thread.py diff --git a/capitulos/code/20-executors/demo_executor_map.py b/code/20-executors/demo_executor_map.py similarity index 90% rename from capitulos/code/20-executors/demo_executor_map.py rename to code/20-executors/demo_executor_map.py index 5ceee29a..52d37b02 100644 --- a/capitulos/code/20-executors/demo_executor_map.py +++ b/code/20-executors/demo_executor_map.py @@ -10,12 +10,12 @@ def display(*args): # <1> print(*args) def loiter(n): # <2> - msg = '{}loiter({}): doing nothing for {}s...' + msg = '{}loiter({}): napping for {}s...' display(msg.format('\t'*n, n, n)) sleep(n) msg = '{}loiter({}): done.' display(msg.format('\t'*n, n)) - return n * 10 # <3> + return n + 10 # <3> def main(): display('Script starting.') diff --git a/capitulos/code/20-executors/getflags/.gitignore b/code/20-executors/getflags/.gitignore similarity index 100% rename from capitulos/code/20-executors/getflags/.gitignore rename to code/20-executors/getflags/.gitignore diff --git a/capitulos/code/20-executors/getflags/README.adoc b/code/20-executors/getflags/README.adoc similarity index 100% rename from capitulos/code/20-executors/getflags/README.adoc rename to code/20-executors/getflags/README.adoc diff --git a/capitulos/code/20-executors/getflags/country_codes.txt b/code/20-executors/getflags/country_codes.txt similarity index 100% rename from capitulos/code/20-executors/getflags/country_codes.txt rename to code/20-executors/getflags/country_codes.txt diff --git a/capitulos/code/20-executors/getflags/flags.py b/code/20-executors/getflags/flags.py similarity index 100% rename from capitulos/code/20-executors/getflags/flags.py rename to code/20-executors/getflags/flags.py diff --git a/capitulos/code/20-executors/getflags/flags.zip b/code/20-executors/getflags/flags.zip similarity index 100% rename from capitulos/code/20-executors/getflags/flags.zip rename to code/20-executors/getflags/flags.zip diff --git a/capitulos/code/20-executors/getflags/flags2_asyncio.py b/code/20-executors/getflags/flags2_asyncio.py similarity index 96% rename from capitulos/code/20-executors/getflags/flags2_asyncio.py rename to code/20-executors/getflags/flags2_asyncio.py index 4f1849a3..19d7dd14 100755 --- a/capitulos/code/20-executors/getflags/flags2_asyncio.py +++ b/code/20-executors/getflags/flags2_asyncio.py @@ -71,7 +71,8 @@ async def supervisor(cc_list: list[str], try: status = await coro # <8> except httpx.HTTPStatusError as exc: - error_msg = 'HTTP error {resp.status_code} - {resp.reason_phrase}' + error_msg = ('HTTP error {resp.status_code}' + + ' - {resp.reason_phrase}') error_msg = error_msg.format(resp=exc.response) error = exc # <9> except httpx.RequestError as exc: diff --git a/capitulos/code/20-executors/getflags/flags2_asyncio_executor.py b/code/20-executors/getflags/flags2_asyncio_executor.py similarity index 100% rename from capitulos/code/20-executors/getflags/flags2_asyncio_executor.py rename to code/20-executors/getflags/flags2_asyncio_executor.py diff --git a/capitulos/code/20-executors/getflags/flags2_common.py b/code/20-executors/getflags/flags2_common.py similarity index 100% rename from capitulos/code/20-executors/getflags/flags2_common.py rename to code/20-executors/getflags/flags2_common.py diff --git a/capitulos/code/20-executors/getflags/flags2_sequential.py b/code/20-executors/getflags/flags2_sequential.py similarity index 92% rename from capitulos/code/20-executors/getflags/flags2_sequential.py rename to code/20-executors/getflags/flags2_sequential.py index e4358a3a..7b851dfe 100755 --- a/capitulos/code/20-executors/getflags/flags2_sequential.py +++ b/code/20-executors/getflags/flags2_sequential.py @@ -35,7 +35,10 @@ def get_flag(base_url: str, cc: str) -> bytes: resp.raise_for_status() # <3> return resp.content -def download_one(cc: str, base_url: str, verbose: bool = False) -> DownloadStatus: +def download_one( + cc: str, + base_url: str, + verbose: bool = False) -> DownloadStatus: try: image = get_flag(base_url, cc) except httpx.HTTPStatusError as exc: # <4> @@ -69,7 +72,8 @@ def download_many(cc_list: list[str], try: status = download_one(cc, base_url, verbose) # <4> except httpx.HTTPStatusError as exc: # <5> - error_msg = 'HTTP error {resp.status_code} - {resp.reason_phrase}' + error_msg = ('HTTP error {resp.status_code}' + + ' - {resp.reason_phrase}') error_msg = error_msg.format(resp=exc.response) except httpx.RequestError as exc: # <6> error_msg = f'{exc} {type(exc)}'.strip() diff --git a/capitulos/code/20-executors/getflags/flags2_threadpool.py b/code/20-executors/getflags/flags2_threadpool.py similarity index 94% rename from capitulos/code/20-executors/getflags/flags2_threadpool.py rename to code/20-executors/getflags/flags2_threadpool.py index 62f6c3b1..dabb4228 100755 --- a/capitulos/code/20-executors/getflags/flags2_threadpool.py +++ b/code/20-executors/getflags/flags2_threadpool.py @@ -50,7 +50,8 @@ def download_many(cc_list: list[str], try: status = future.result() # <12> except httpx.HTTPStatusError as exc: # <13> - error_msg = 'HTTP error {resp.status_code} - {resp.reason_phrase}' + error_msg = ('HTTP error {resp.status_code}' + + ' - {resp.reason_phrase}') error_msg = error_msg.format(resp=exc.response) except httpx.RequestError as exc: error_msg = f'{exc} {type(exc)}'.strip() diff --git a/capitulos/code/20-executors/getflags/flags3_asyncio.py b/code/20-executors/getflags/flags3_asyncio.py similarity index 100% rename from capitulos/code/20-executors/getflags/flags3_asyncio.py rename to code/20-executors/getflags/flags3_asyncio.py diff --git a/capitulos/code/20-executors/getflags/flags_asyncio.py b/code/20-executors/getflags/flags_asyncio.py similarity index 96% rename from capitulos/code/20-executors/getflags/flags_asyncio.py rename to code/20-executors/getflags/flags_asyncio.py index 588d1665..40ad8f65 100755 --- a/capitulos/code/20-executors/getflags/flags_asyncio.py +++ b/code/20-executors/getflags/flags_asyncio.py @@ -43,5 +43,5 @@ async def supervisor(cc_list: list[str]) -> int: return len(res) # <6> if __name__ == '__main__': - main(download_many) + main(download_many) # <7> # end::FLAGS_ASYNCIO_START[] diff --git a/capitulos/code/20-executors/getflags/flags_threadpool.py b/code/20-executors/getflags/flags_threadpool.py similarity index 100% rename from capitulos/code/20-executors/getflags/flags_threadpool.py rename to code/20-executors/getflags/flags_threadpool.py diff --git a/capitulos/code/20-executors/getflags/flags_threadpool_futures.py b/code/20-executors/getflags/flags_threadpool_futures.py similarity index 98% rename from capitulos/code/20-executors/getflags/flags_threadpool_futures.py rename to code/20-executors/getflags/flags_threadpool_futures.py index b17a698c..2c81ef13 100755 --- a/capitulos/code/20-executors/getflags/flags_threadpool_futures.py +++ b/code/20-executors/getflags/flags_threadpool_futures.py @@ -20,7 +20,7 @@ def download_many(cc_list: list[str]) -> int: to_do.append(future) # <5> print(f'Scheduled for {cc}: {future}') # <6> - for count, future in enumerate(futures.as_completed(to_do), 1): # <7> + for count, future in enumerate(futures.as_completed(to_do), 1):# <7> res: str = future.result() # <8> print(f'{future} result: {res!r}') # <9> diff --git a/capitulos/code/20-executors/getflags/httpx-error-tree/drawtree.py b/code/20-executors/getflags/httpx-error-tree/drawtree.py similarity index 100% rename from capitulos/code/20-executors/getflags/httpx-error-tree/drawtree.py rename to code/20-executors/getflags/httpx-error-tree/drawtree.py diff --git a/capitulos/code/20-executors/getflags/httpx-error-tree/tree.py b/code/20-executors/getflags/httpx-error-tree/tree.py similarity index 100% rename from capitulos/code/20-executors/getflags/httpx-error-tree/tree.py rename to code/20-executors/getflags/httpx-error-tree/tree.py diff --git a/capitulos/code/20-executors/getflags/requirements.txt b/code/20-executors/getflags/requirements.txt similarity index 100% rename from capitulos/code/20-executors/getflags/requirements.txt rename to code/20-executors/getflags/requirements.txt diff --git a/capitulos/code/20-executors/getflags/slow_server.py b/code/20-executors/getflags/slow_server.py similarity index 100% rename from capitulos/code/20-executors/getflags/slow_server.py rename to code/20-executors/getflags/slow_server.py diff --git a/capitulos/code/20-executors/primes/primes.py b/code/20-executors/primes/primes.py similarity index 100% rename from capitulos/code/20-executors/primes/primes.py rename to code/20-executors/primes/primes.py diff --git a/capitulos/code/20-executors/primes/proc_pool.py b/code/20-executors/primes/proc_pool.py similarity index 92% rename from capitulos/code/20-executors/primes/proc_pool.py rename to code/20-executors/primes/proc_pool.py index b184ede8..04ed7728 100755 --- a/capitulos/code/20-executors/primes/proc_pool.py +++ b/code/20-executors/primes/proc_pool.py @@ -32,7 +32,8 @@ def main() -> None: executor = futures.ProcessPoolExecutor(workers) # <4> actual_workers = executor._max_workers # type: ignore # <5> - print(f'Checking {len(NUMBERS)} numbers with {actual_workers} processes:') + print(f'Checking {len(NUMBERS)} numbers', + f'with {actual_workers} processes:') t0 = perf_counter() diff --git a/capitulos/code/21-async/README.rst b/code/21-async/README.rst similarity index 100% rename from capitulos/code/21-async/README.rst rename to code/21-async/README.rst diff --git a/capitulos/code/21-async/domains/README.rst b/code/21-async/domains/README.rst similarity index 100% rename from capitulos/code/21-async/domains/README.rst rename to code/21-async/domains/README.rst diff --git a/capitulos/code/21-async/domains/asyncio/blogdom.py b/code/21-async/domains/asyncio/blogdom.py similarity index 100% rename from capitulos/code/21-async/domains/asyncio/blogdom.py rename to code/21-async/domains/asyncio/blogdom.py diff --git a/capitulos/code/21-async/domains/asyncio/domaincheck.py b/code/21-async/domains/asyncio/domaincheck.py similarity index 99% rename from capitulos/code/21-async/domains/asyncio/domaincheck.py rename to code/21-async/domains/asyncio/domaincheck.py index cf18995d..a03b6360 100755 --- a/capitulos/code/21-async/domains/asyncio/domaincheck.py +++ b/code/21-async/domains/asyncio/domaincheck.py @@ -5,7 +5,6 @@ from domainlib import multi_probe - async def main(tld: str) -> None: tld = tld.strip('.') names = (kw for kw in kwlist if len(kw) <= 4) # <1> @@ -16,7 +15,6 @@ async def main(tld: str) -> None: indent = '' if found else '\t\t' # <5> print(f'{indent}{domain}') - if __name__ == '__main__': if len(sys.argv) == 2: asyncio.run(main(sys.argv[1])) # <6> diff --git a/capitulos/code/21-async/domains/asyncio/domainlib.py b/code/21-async/domains/asyncio/domainlib.py similarity index 89% rename from capitulos/code/21-async/domains/asyncio/domainlib.py rename to code/21-async/domains/asyncio/domainlib.py index 29c231af..d78d83d4 100644 --- a/capitulos/code/21-async/domains/asyncio/domainlib.py +++ b/code/21-async/domains/asyncio/domainlib.py @@ -22,7 +22,8 @@ async def probe(domain: str, loop: OptionalLoop = None) -> Result: # <3> return Result(domain, True) -async def multi_probe(domains: Iterable[str]) -> AsyncIterator[Result]: # <4> +async def multi_probe( + domains: Iterable[str]) -> AsyncIterator[Result]: # <4> loop = asyncio.get_running_loop() coros = [probe(domain, loop) for domain in domains] # <5> for coro in asyncio.as_completed(coros): # <6> diff --git a/capitulos/code/21-async/domains/curio/blogdom.py b/code/21-async/domains/curio/blogdom.py similarity index 74% rename from capitulos/code/21-async/domains/curio/blogdom.py rename to code/21-async/domains/curio/blogdom.py index f3dd598f..fbd5d2ed 100755 --- a/capitulos/code/21-async/domains/curio/blogdom.py +++ b/code/21-async/domains/curio/blogdom.py @@ -1,22 +1,20 @@ #!/usr/bin/env python3 -from curio import run, TaskGroup -import curio.socket as socket from keyword import kwlist +import curio MAX_KEYWORD_LEN = 4 - async def probe(domain: str) -> tuple[str, bool]: # <1> try: - await socket.getaddrinfo(domain, None) # <2> - except socket.gaierror: + await curio.socket.getaddrinfo(domain, None) # <2> + except curio.socket.gaierror: return (domain, False) return (domain, True) async def main() -> None: names = (kw for kw in kwlist if len(kw) <= MAX_KEYWORD_LEN) domains = (f'{name}.dev'.lower() for name in names) - async with TaskGroup() as group: # <3> + async with curio.TaskGroup() as group: # <3> for domain in domains: await group.spawn(probe, domain) # <4> async for task in group: # <5> @@ -25,4 +23,4 @@ async def main() -> None: print(f'{mark} {domain}') if __name__ == '__main__': - run(main()) # <6> + curio.run(main()) # <6> diff --git a/capitulos/code/21-async/domains/curio/domaincheck.py b/code/21-async/domains/curio/domaincheck.py similarity index 100% rename from capitulos/code/21-async/domains/curio/domaincheck.py rename to code/21-async/domains/curio/domaincheck.py diff --git a/capitulos/code/21-async/domains/curio/domainlib.py b/code/21-async/domains/curio/domainlib.py similarity index 100% rename from capitulos/code/21-async/domains/curio/domainlib.py rename to code/21-async/domains/curio/domainlib.py diff --git a/capitulos/code/21-async/domains/curio/requirements.txt b/code/21-async/domains/curio/requirements.txt similarity index 100% rename from capitulos/code/21-async/domains/curio/requirements.txt rename to code/21-async/domains/curio/requirements.txt diff --git a/capitulos/code/21-async/mojifinder/README.md b/code/21-async/mojifinder/README.md similarity index 100% rename from capitulos/code/21-async/mojifinder/README.md rename to code/21-async/mojifinder/README.md diff --git a/capitulos/code/21-async/mojifinder/bottle.py b/code/21-async/mojifinder/bottle.py similarity index 100% rename from capitulos/code/21-async/mojifinder/bottle.py rename to code/21-async/mojifinder/bottle.py diff --git a/capitulos/code/21-async/mojifinder/charindex.py b/code/21-async/mojifinder/charindex.py similarity index 100% rename from capitulos/code/21-async/mojifinder/charindex.py rename to code/21-async/mojifinder/charindex.py diff --git a/capitulos/code/21-async/mojifinder/requirements.txt b/code/21-async/mojifinder/requirements.txt similarity index 100% rename from capitulos/code/21-async/mojifinder/requirements.txt rename to code/21-async/mojifinder/requirements.txt diff --git a/code/21-async/mojifinder/static/form.html b/code/21-async/mojifinder/static/form.html new file mode 100644 index 00000000..91e6911f --- /dev/null +++ b/code/21-async/mojifinder/static/form.html @@ -0,0 +1,81 @@ + + + + + Mojifinder + + + + + +
+ + +
+ + +
+ + diff --git a/capitulos/code/21-async/mojifinder/tcp_mojifinder.py b/code/21-async/mojifinder/tcp_mojifinder.py similarity index 100% rename from capitulos/code/21-async/mojifinder/tcp_mojifinder.py rename to code/21-async/mojifinder/tcp_mojifinder.py diff --git a/capitulos/code/21-async/mojifinder/web_mojifinder.py b/code/21-async/mojifinder/web_mojifinder.py similarity index 100% rename from capitulos/code/21-async/mojifinder/web_mojifinder.py rename to code/21-async/mojifinder/web_mojifinder.py diff --git a/capitulos/code/21-async/mojifinder/web_mojifinder_bottle.py b/code/21-async/mojifinder/web_mojifinder_bottle.py similarity index 100% rename from capitulos/code/21-async/mojifinder/web_mojifinder_bottle.py rename to code/21-async/mojifinder/web_mojifinder_bottle.py diff --git a/capitulos/code/22-dyn-attr-prop/blackknight.py b/code/22-dyn-attr-prop/blackknight.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/blackknight.py rename to code/22-dyn-attr-prop/blackknight.py diff --git a/capitulos/code/22-dyn-attr-prop/bulkfood/bulkfood_v1.py b/code/22-dyn-attr-prop/bulkfood/bulkfood_v1.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/bulkfood/bulkfood_v1.py rename to code/22-dyn-attr-prop/bulkfood/bulkfood_v1.py diff --git a/capitulos/code/22-dyn-attr-prop/bulkfood/bulkfood_v2.py b/code/22-dyn-attr-prop/bulkfood/bulkfood_v2.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/bulkfood/bulkfood_v2.py rename to code/22-dyn-attr-prop/bulkfood/bulkfood_v2.py diff --git a/capitulos/code/22-dyn-attr-prop/bulkfood/bulkfood_v2b.py b/code/22-dyn-attr-prop/bulkfood/bulkfood_v2b.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/bulkfood/bulkfood_v2b.py rename to code/22-dyn-attr-prop/bulkfood/bulkfood_v2b.py diff --git a/capitulos/code/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py b/code/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py rename to code/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py diff --git a/capitulos/code/22-dyn-attr-prop/doc_property.py b/code/22-dyn-attr-prop/doc_property.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/doc_property.py rename to code/22-dyn-attr-prop/doc_property.py diff --git a/capitulos/code/22-dyn-attr-prop/oscon/data/osconfeed.json b/code/22-dyn-attr-prop/oscon/data/osconfeed.json similarity index 94% rename from capitulos/code/22-dyn-attr-prop/oscon/data/osconfeed.json rename to code/22-dyn-attr-prop/oscon/data/osconfeed.json index 7bea08d8..a0397b1b 100644 --- a/capitulos/code/22-dyn-attr-prop/oscon/data/osconfeed.json +++ b/code/22-dyn-attr-prop/oscon/data/osconfeed.json @@ -16,7 +16,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1458, "description": "The web development platform is massive. With tons of libraries, frameworks and concepts out there, it might be daunting for the 'legacy' developer to jump into it.\r\n\r\nIn this presentation we will introduce Google Dart & Polymer. Two hot technologies that work in harmony to create powerful web applications using concepts familiar to OOP developers.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33451", + "website_url": "http://oscon.com/2014/sched/33451", "speakers": [149868], "categories": [ @@ -34,7 +34,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1449, "description": "Refactoring code (altering code to make it cleaner, simpler, and often faster, while not sacrificing functionality) We hate to do it, so learn how to do it better. Covers: When to refactor. How to refactor. Why refactor. How refactor can help us write better code. Common methodology for refactoring.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33457", + "website_url": "http://oscon.com/2014/sched/33457", "speakers": [169862], "categories": [ @@ -52,7 +52,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1459, "description": "Until iOS and Android came along, the opportunities for open source to flourish in the mobile space were limited, because platforms were totally proprietary. Now you can find countless FL/OSS projects that help mobile developers get their job done. So what's on the horizon, and what are the best open source tools today to deliver the next great app?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33463", + "website_url": "http://oscon.com/2014/sched/33463", "speakers": [169870,2216,96208,150073], "categories": [ @@ -70,7 +70,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1451, "description": "Everyday things are becoming smarter. The problem? The things are becoming smarter, but they\u2019re also becoming selfish and you\u2019ve ended up as a mechanical turk inside your own software. How can we fix the Internet of Things? The things have to become not just smarter, but more co-operative, and the Internet of things needs to become anticipatory rather than reactive.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33464", + "website_url": "http://oscon.com/2014/sched/33464", "speakers": [2216], "categories": [ @@ -88,7 +88,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1458, "description": "PHP is used by the likes of Facebook, Yahoo, Zynga, Tumblr, Etsy, and Wikipedia. How do the largest internet companies scale PHP to meet their demand? Join this session and find out how to use the latest tools in PHP for developing high performance applications. We\u2019ll take a look at common techniques for scaling PHP applications and best practices for profiling and optimizing performance.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33476", + "website_url": "http://oscon.com/2014/sched/33476", "speakers": [54107], "categories": [ @@ -106,7 +106,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1456, "description": "At Netflix Engineering's Partner Product Innovation group, we underwent a revamp of the tech stack to make it API-driven. This was to not only help with the expanding list of API consumers, but also to address the evolving streaming business. With Scala, Scalatra, and Swagger, we achieved one of the best architecture for the scale, agility and robustness needed.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33481", + "website_url": "http://oscon.com/2014/sched/33481", "speakers": [113667], "categories": [ @@ -124,7 +124,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1458, "description": "You might know about XSS and usual SQL injection, but time has changed and we have to keep up-to-date with the latest attack scenarios.\r\nDo you also know what a clickjacking is? If not I'll show you how to protect against it.\r\nI'll also present techniques like Perfect Pixel Timing and a combination of xss/time-based-sql-injection to access intranet sites, which are not even compromised.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33485", + "website_url": "http://oscon.com/2014/sched/33485", "speakers": [169932], "categories": [ @@ -142,7 +142,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1475, "description": "Do you use Hadoop for large scale data analysis? Do your data scientists love R? This presentation will discuss the challenges of scaling R to multi-terabyte data sets and how RHadoop can be used to solve them.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33503", + "website_url": "http://oscon.com/2014/sched/33503", "speakers": [126882], "categories": [ @@ -160,7 +160,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1466, "description": "There are a number of interrelated concepts which make the understanding and implementation of HA complex. The potential for not implementing HA correctly would be disastrous. This session will use demos to reinforce the concepts and how to connect the dots using OpenStack infrastructure as an example although the lessons learned can be used for implementing HA in general.\r\n\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33520", + "website_url": "http://oscon.com/2014/sched/33520", "speakers": [131499], "categories": [ @@ -178,7 +178,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1454, "description": "This presentation will provide insight into the security mechanisms being used by the IZON IP camera, some of the weaknesses found during research, and a few recommendations for them (or anyone else developing these sorts of cameras) to benefit from. Attention will be paid to topics such as network protocols, iOS app security, APIs, and other aspects of the camera's attack surface.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33549", + "website_url": "http://oscon.com/2014/sched/33549", "speakers": [170134], "categories": [ @@ -196,7 +196,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1475, "description": "Many expert programmers who write complex SQL without a second thought still struggle with database design. Unfortunately, many introductory topics cause eyes to glaze over when we read 'transitive dependencies' and 'Boyce-Codd normal form'. When you're done with this talk, you'll understand the basics of creating a database that won't make a DBA yell at you. We won't even use (many) big words.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33557", + "website_url": "http://oscon.com/2014/sched/33557", "speakers": [170158], "categories": [ @@ -214,7 +214,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1465, "description": "Perl is known for its testing culture. Unfortunately it's often focused on quantity over quality. Perl's Test::Class::Moose project started out as an experiment but morphed into a way of having higher quality testing. With this module, you can get fine-grained control over your test suite, better understand your *real* code coverage and get an quick boost to test suite performance.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33564", + "website_url": "http://oscon.com/2014/sched/33564", "speakers": [170158], "categories": [ @@ -232,7 +232,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1475, "description": "Elasticsearch is about more than just search. It\u2019s currently being used in production for everything from traditional text search, to big data analytics, to distributed document storage. This talk will introduce you to Elasticsearch\u2019s REST API, and discuss the basics of full text search and analytics with Elasticsearch.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33571", + "website_url": "http://oscon.com/2014/sched/33571", "speakers": [170054], "categories": [ @@ -250,7 +250,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1459, "description": "In this talk, we'll go into excruciating technical detail about building a greenfield, massively scalable cloud service. Along the path to constructing a scalable cloud service, there are many options and critical decisions to take, and we'll share our choices that brought both success and frustrations.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33581", + "website_url": "http://oscon.com/2014/sched/33581", "speakers": [116050], "categories": [ @@ -268,7 +268,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1457, "description": "Developing freemium which involves OSS is not a trivial task. In this talk we\u2019ll showcase Artifactory, which successfully combines open-source and Pro versions. We will talk about developing, building, testing, and releasing hybrid freemium application and will review the existing approaches, discussing pros and cons of each of them.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33585", + "website_url": "http://oscon.com/2014/sched/33585", "speakers": [114822], "categories": [ @@ -286,7 +286,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1456, "description": "An introduction to building Reactive Applications and what tools you can use to do so.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33590", + "website_url": "http://oscon.com/2014/sched/33590", "speakers": [170293], "categories": [ @@ -304,7 +304,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1452, "description": "So you want to create a platform for your product? Creating a fantastic open API (or even a closed one) is not the same as creating other products. I'll talk about how what you need to know to design, plan and execute a successful, engaging API and how to avoid common pitfalls.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33596", + "website_url": "http://oscon.com/2014/sched/33596", "speakers": [4265], "categories": [ @@ -322,7 +322,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1475, "description": "The Quantified Self movement is all about keeping measurements about your life in order to track progress in various ways. As geeks we all enjoy playing with new toys, and there are a variety of devices and applications out there to help measure steps, activity and fitness. Combining the data from these devices can help you build tools to track your fitness in a way that makes sense for you.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33597", + "website_url": "http://oscon.com/2014/sched/33597", "speakers": [4265,182808], "categories": [ @@ -340,7 +340,7 @@ "time_stop": "2014-07-20 17:00:00", "venue_serial": 1450, "description": "You've heard the hype about Docker and container virtualization now see it in action. This tutorial will introduce you to Docker and take you through installing it, running it and integrating it into your development and operational workflow.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33627", + "website_url": "http://oscon.com/2014/sched/33627", "speakers": [5060], "categories": [ @@ -360,7 +360,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1449, "description": "Architecting and developing user interfaces used to be relatively easy, pick a server side framework, define a standard monitor resolution and spend your days dealing with browser quirks. But today, the landscape presents us with a plethora of screen sizes and resolutions. How does a team embrace this brave new world knowing that the future will introduce even more volatility to the client space?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33631", + "website_url": "http://oscon.com/2014/sched/33631", "speakers": [108125], "categories": [ @@ -378,7 +378,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1462, "description": "Devoxx4Kids is a worldwide initiative that introduces programming, robotics, and engineering to kids by organizing events and workshops. This session will share how Devoxx4Kids is giving Scratch, Greenfoot, Minecraft, Raspberry Pi, Arduino, NAO, Tynker workshops. The session will show a path that can be followed by parents to keep their kids engaged and build, instead of just play games. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33648", + "website_url": "http://oscon.com/2014/sched/33648", "speakers": [143377], "categories": [ @@ -396,7 +396,7 @@ "time_stop": "2014-07-20 12:30:00", "venue_serial": 1451, "description": "The purpose of this tutorial is to train web developers working on a Linux/UNIX ENV on production, development ENVs, or both.\r\nOften, these developers, while proficient in say, PHP, lack UNIX system knowledge and therefore come across a brick wall when debugging production issues.\r\nOften times, because the development ENV is different than production.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33687", + "website_url": "http://oscon.com/2014/sched/33687", "speakers": [171078], "categories": [ @@ -416,7 +416,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1456, "description": "The Java EE 7 platform has 4 new components (WebSocket, JSON-P, batch, and concurrency), 3 that are significantly updated (JAX-RS, JMS, and EL), and several others that bring significant changes to the platform. This session explains each feature with a code snippet and provides details on where and how you can use it in your applications.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33689", + "website_url": "http://oscon.com/2014/sched/33689", "speakers": [143377], "categories": [ @@ -434,7 +434,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1456, "description": "Recommendation engines are the mainstay of e-commerce sites. What if you could build one with only a few lines of code using open source tools. Come to this talk to find out how as we build one using the data from StackOverflow!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33704", + "website_url": "http://oscon.com/2014/sched/33704", "speakers": [171147], "categories": [ @@ -452,7 +452,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1452, "description": "In this session, we will take a look at how we parallelize Deep Belief Networks in Deep Learning on the next\u200b-generation YARN framework Iterative Reduce and the parallel machine learning library Metronome. We\u2019ll also take a look at some real world applications of Deep Learning on Hadoop such as image classification and NLP.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33709", + "website_url": "http://oscon.com/2014/sched/33709", "speakers": [171197,171201], "categories": [ @@ -470,7 +470,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1451, "description": "I was diagnosed with depression and anxiety when I was thirteen, and I've been struggling with it my whole life. In this talk, I'll discuss how it has impacted my work as a developer, husband, and father. By speaking openly about my experiences, I hope those struggling with mental illness will know the are not alone, and others can better understand how to be helpful and supportive.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33727", + "website_url": "http://oscon.com/2014/sched/33727", "speakers": [1639], "categories": [ @@ -488,7 +488,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1475, "description": "After using PostgreSQL for a while, you realize that there are missing features that would make it significantly easier to use in large production environments. Thankfully, it's extremely easy to make add-ons to enable some of those features right now, even without knowing C! This talk will discuss projects I've worked on and show how easy it is to make an impact in the PostgreSQL community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33733", + "website_url": "http://oscon.com/2014/sched/33733", "speakers": [152242], "categories": [ @@ -506,7 +506,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1475, "description": "In this session, SolidFire's John Griffith will review some of the key features included within OpenStack Block Storage to help achieve the enterprise storage functionality they require to host production applications in their cloud infrastructure. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33741", + "website_url": "http://oscon.com/2014/sched/33741", "speakers": [171381], "categories": [ @@ -524,7 +524,7 @@ "time_stop": "2014-07-21 12:30:00", "venue_serial": 1456, "description": "This tutorial will demonstrate the use of Codename One to develop a cross-platform mobile application in Java. In it you will build a non-trivial application and deploy it to your mobile device.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33800", + "website_url": "http://oscon.com/2014/sched/33800", "speakers": [171418], "categories": [ @@ -544,7 +544,7 @@ "time_stop": "2014-07-20 17:00:00", "venue_serial": 1475, "description": "This tutorial explores a set of simple and practical techniques for giving better, more effective, more entertaining technical presentations. Discover how to capture an audience, hold their interest, convey your message to them clearly\u2026and maybe even inspire them.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33834", + "website_url": "http://oscon.com/2014/sched/33834", "speakers": [4710], "categories": [ @@ -564,7 +564,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1465, "description": "Perl 6's many advanced features (junctions, multiple dispatch, generics, grammars, lazy evaluation, coroutines, etc.) may well offer awesome cosmic power, but for most of us the real and immediate benefits of switching to Perl 6 are the numerous minor Perl annoyances it fixes. This talk offers a dozen practical reasons why Perl 6 might now be a better choice as your everyday go-to problem-solver.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33839", + "website_url": "http://oscon.com/2014/sched/33839", "speakers": [4710], "categories": [ @@ -582,7 +582,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1465, "description": "Join Damian for his annual kaleidoscopic tour of the strange and wonderful new Perl modules he's been developing over the past twelve months.\r\n ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33841", + "website_url": "http://oscon.com/2014/sched/33841", "speakers": [4710], "categories": [ @@ -600,7 +600,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1452, "description": "How do you build and maintain a stable API while rapidly iterating and innovating in your business? Change can never be eliminated, but its impact can be minimized. GitHub takes a pragmatic approach to Hypermedia that emphasizes workflows over data retrieval and employs open source to ensure a consistent experience for API consumers.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33863", + "website_url": "http://oscon.com/2014/sched/33863", "speakers": [109297], "categories": [ @@ -618,7 +618,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1456, "description": "Today's Java developer is a rare bird: SQL and JPA on the backend, or MongoDB or Hadoop? HTTP, REST and websockets on the web? What about security? JavaScript, HTML, CSS, (not to mention LESS, SASS, and CoffeeScript!) on the client? Today's Java developer is a _full stack_ developer. Join Josh Long and Phillip Webb for a look at how Spring Boot simplifies full-stack development for everyone.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33875", + "website_url": "http://oscon.com/2014/sched/33875", "speakers": [171564,171565], "categories": [ @@ -638,7 +638,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1448, "description": "Instagram.com renders almost all of its UI in JavaScript. I'll talk about how our packaging and push systems work in great detail, which are clever combinations of existing open-source tools. Anyone building a large site using lots of JavaScript would find what we've learned interesting!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33880", + "website_url": "http://oscon.com/2014/sched/33880", "speakers": [164756], "categories": [ @@ -656,7 +656,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1457, "description": "There's been a lot of talk about patent trolls, but how can the free and open source software community address the more complicated (and potentially more damaging) problem of anti-competitive litigation? ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33913", + "website_url": "http://oscon.com/2014/sched/33913", "speakers": [130731], "categories": [ @@ -676,7 +676,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1457, "description": "PayPal has recently moved their web application stack from a proprietary framework, resulting in weeks of training per developer and large maintenance costs, to an open source-based stack that allows our engineers to come in the door coding. This is the story of how we changed our enterprise culture and started giving back to the open source community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33937", + "website_url": "http://oscon.com/2014/sched/33937", "speakers": [183835,183908], "categories": [ @@ -694,7 +694,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1456, "description": "Software development is easy. You tell a computer to do something. It does it. Someone sends you a packet. The OS receives it. Things don't happen unless you ask them to. Simple.\r\n\r\nBut what if that wasn't true? What if your computer is full of hidden magic? What if your hardware makes assumptions about your software? Vendors wouldn't do that, would they?\r\n\r\n(Spoiler: Yes, they would)", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33943", + "website_url": "http://oscon.com/2014/sched/33943", "speakers": [6852], "categories": [ @@ -712,7 +712,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1450, "description": "You have seen the stuff that FourSquare has done with spatial and you want some of that hotness for your app. We will load some data into MongoDB, show you how to handle spatial and finally plug in in some Node.JS JavaScript code to build simple REST services to query your data. Finally we will show how to use the REST service with OpenStreetMap and Leaflet for a fully interactive map.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33945", + "website_url": "http://oscon.com/2014/sched/33945", "speakers": [142320], "categories": [ @@ -730,7 +730,7 @@ "time_stop": "2014-07-20 12:30:00", "venue_serial": 1456, "description": "Clojure: it's a Lisp that runs on the JVM and it's gotten a lot of buzz in the last few years. What is it actually good for? In this tutorial, you'll learn about Clojure's radically simple approach to data and state and how it can help you build real-world projects from web applications to servers to mobile apps.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33947", + "website_url": "http://oscon.com/2014/sched/33947", "speakers": [137149,171822], "categories": [ @@ -750,7 +750,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1449, "description": "If you're pushing the envelope of programming (or of your own skills)... and even when you\u2019re not... there *will* be bugs in your code. Don't panic! We cover the attitudes and skills (not taught in most schools) to minimize your bugs, track them, find them, fix them, ensure they never recur, and deploy fixes to your users.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33950", + "website_url": "http://oscon.com/2014/sched/33950", "speakers": [3471,5199], "categories": [ @@ -768,7 +768,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1449, "description": "A review of the past six years of Apache Cordova development, starting from its origins as PhoneGap, to its donation to the Apache Software Foundation, told from the point of view of its longest running contributor. This will include a simple introduction to cross-platform hybrid applications on iOS and Android, and their evolution.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33973", + "website_url": "http://oscon.com/2014/sched/33973", "speakers": [96208], "categories": [ @@ -786,7 +786,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1466, "description": "Have you tried some recursion in your SQL? In this session, we will go over the concept of Common Table Expressions (CTE), also known as WITH queries. We will explore syntax, features, and use cases for this powerful SQL construct.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33978", + "website_url": "http://oscon.com/2014/sched/33978", "speakers": [25862], "categories": [ @@ -804,7 +804,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1448, "description": "Open source has always been a huge part of Facebook's culture. But in 2013, we rebooted our portfolio and launched a unique suite of internal tools & instrumentation to support hundreds of repos, thousands of engineers, and tens of thousands of contributors. The result? Better-than-ever community adoption - and an open & responsible stewardship, attuned to our ethos of hacking & moving fast.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33994", + "website_url": "http://oscon.com/2014/sched/33994", "speakers": [118233], "categories": [ @@ -822,7 +822,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1459, "description": "As part of a large-scale adoption of cloud computing to support the increasing computing needs of the Large Hadron Collider processing over 35 PB/year, the infrastructure of CERN IT is undergoing major changes in both technology and culture. This session will describe the steps taken, the challenges encountered and our outlook for the future.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33997", + "website_url": "http://oscon.com/2014/sched/33997", "speakers": [170052], "categories": [ @@ -840,7 +840,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1464, "description": "How can you encourage involvement and participation in Open Source? They key is through empowerment. We'll discuss how to empower and encourage more people to participate in your open source project by enabling Heroism. This talk will also discuss issues of diversity and inclusion. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34005", + "website_url": "http://oscon.com/2014/sched/34005", "speakers": [144736], "categories": [ @@ -858,7 +858,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1450, "description": "Still building presentations in an office suite? That's so 2013! Today, you can build awesome, engaging presentations that run in your browser or on your phone, using nothing but HTML5 and a few clever JavaScript libraries. And it's super simple! This talk shows you how.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34012", + "website_url": "http://oscon.com/2014/sched/34012", "speakers": [131884], "categories": [ @@ -876,7 +876,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1465, "description": "Ruth Suehle, one of the authors of Raspberry Pi Hacks (O\u2019Reilly, December 2013) will offer technical tips for hardware and software hackers who want to build around the Raspberry Pi computer. She\u2019ll start with some tips like how to add a power switch and go on to share fun projects, from costume builds to radios and light displays.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34018", + "website_url": "http://oscon.com/2014/sched/34018", "speakers": [108840], "categories": [ @@ -894,7 +894,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1449, "description": "Open Source is ubiquitous in Cloud compute. Just as we became familiar with Cloud computing, a new model has emerged, an extension of the cloud to the edge of the network, some call it Fog computing, some call it the Internet of Things. This talk describes how the compute model is changing as the new generation of devices stretched what we previously knew as Cloud compute.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34019", + "website_url": "http://oscon.com/2014/sched/34019", "speakers": [172370], "categories": [ @@ -912,7 +912,7 @@ "time_stop": "2014-07-21 17:00:00", "venue_serial": 1475, "description": "So, you\u2019ve inherited a PostgreSQL server. Congratulations? This tutorial will cover the essential care and feeding of a Postgres server so that you can get back to your real job.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34026", + "website_url": "http://oscon.com/2014/sched/34026", "speakers": [3397], "categories": [ @@ -932,7 +932,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1451, "description": "This talk will take you on a journey from making an LED blink through to building your own Elixir-powered robot using a RaspberryPi and Android.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34037", + "website_url": "http://oscon.com/2014/sched/34037", "speakers": [172532,172534], "categories": [ @@ -950,7 +950,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1458, "description": "Asynchronous frameworks like Tornado, Twisted, and Node are increasingly important for writing high-performance web applications. Even if you\u2019re an experienced web programmer, you may lack a rigorous understanding of how these frameworks work and when to use them. See how Tornado's event loop works, and learn how to efficiently handle very large numbers of concurrent connections.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34040", + "website_url": "http://oscon.com/2014/sched/34040", "speakers": [172536], "categories": [ @@ -968,7 +968,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1465, "description": "Object Oriented programming has dominated software engineering for the last two decades. Although Go is not OO in the strict sense, we can continue to leverage the skills we've honed as OO engineers. This talk will cover how to use our OO programming fundamentals in go, common mistakes made by those coming to go from other OO languages (Ruby, Python, JS, etc.), and principles of good design in go.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34047", + "website_url": "http://oscon.com/2014/sched/34047", "speakers": [142995], "categories": [ @@ -986,7 +986,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1448, "description": "No one size fits all formula can be applied to build a business around open source, and attempting to do so may end in humiliation and disaster.\r\n\r\nThere is no doubt that 'Open Source' has impacted the dynamics of all manner of business, but building a business on 'Open Source' is not a solved problem.\r\n\r\nA guided tour of open source business models, real and imaginary.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34056", + "website_url": "http://oscon.com/2014/sched/34056", "speakers": [24052], "categories": [ @@ -1004,7 +1004,7 @@ "time_stop": "2014-07-21 17:00:00", "venue_serial": 1452, "description": "In this tutorial, we\u2019ll explore three unique technologies, and accompanying use cases, for Node.js development. We\u2019ll divide the tutorial into three one-hour segments, in which you will develop three different Node.js-powered applications.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34063", + "website_url": "http://oscon.com/2014/sched/34063", "speakers": [141235,147649], "categories": [ @@ -1024,7 +1024,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1450, "description": "In this session, I\u2019ll presenting high-quality Node.js design patterns. I\u2019ll bring to the table design patterns I\u2019ve stumbled across in my own Node projects, as well as patterns observed from experts in the Node.js community.\r\n\r\nTopics include: Mastering Modules, Object Inheritance in Node.js, Patterns to avoid callback hell, Batch and Queuing patterns for massively concurrent asynchronous I/O", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34064", + "website_url": "http://oscon.com/2014/sched/34064", "speakers": [141235], "categories": [ @@ -1042,7 +1042,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1475, "description": "This talk will focus on the motivation, design, and architecture of Druid (druid.io), an open-source, real-time analytical data store. Druid is used in production at several organizations to facilitate rapid exploration of high dimensional spaces. Druid can maintain a 95% query latency under 1 second on data sets with >50 billion rows and 2 trillion impressions in tables with 30+ dimensions.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34075", + "website_url": "http://oscon.com/2014/sched/34075", "speakers": [172607], "categories": [ @@ -1060,7 +1060,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1475, "description": "The maturation and development of open source technologies has made it easier than ever for companies to derive insights from vast quantities of data. In this session, we will cover how to build a real-time analytics stack using Kafka, Storm, and Druid. This combination of technologies can power a robust data pipeline that supports real-time ingestion and flexible, low-latency queries.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34076", + "website_url": "http://oscon.com/2014/sched/34076", "speakers": [153565,153566], "categories": [ @@ -1078,7 +1078,7 @@ "time_stop": "2014-07-21 12:30:00", "venue_serial": 1457, "description": "Moose continues to emerge as the new standard for writing OO libraries in Perl. It provides a powerful, consistent API for building classes with a minimum of code. It can be customized with reusable components, making it easier to refactor your code as you go. This tutorial will explain what Moose is, how its parts work together, and how to start using Moose today to get more done with less.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34078", + "website_url": "http://oscon.com/2014/sched/34078", "speakers": [3189], "categories": [ @@ -1096,7 +1096,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1449, "description": "Show how tools including cURL, Wireshark and Charles can be used to inspect and change HTTP traffic when debugging applications which consume APIs and other remote sources.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34081", + "website_url": "http://oscon.com/2014/sched/34081", "speakers": [45968], "categories": [ @@ -1114,7 +1114,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1458, "description": "How should you organise your models in a PHP MVC application? What is a service class, a mapper or an entity? This talk will look at the components of the model layer and the options you have when creating your models. We\u2019ll look at the different schools of thought in this area and compare and contrast their strengths and weaknesses with an eye to flexibility and testability.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34083", + "website_url": "http://oscon.com/2014/sched/34083", "speakers": [46440], "categories": [ @@ -1132,7 +1132,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1451, "description": "It doesn't matter if your passion boating, fashion, kids, carpentry, architecture, race cars or rockets. Building OS hardware can be incorporated into all of these things. We will learn how to bring what you learn in your software day job into your weekend fun time. The result will be better at what you love and making work more fun. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34087", + "website_url": "http://oscon.com/2014/sched/34087", "speakers": [133377], "categories": [ @@ -1150,7 +1150,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1459, "description": "Tsuru is an open source, component oriented PaaS. It allows developers to focus on writing, testing and deploying applications in an easier way, without worrying how they get deployed in a server. Its key features include easy extensibility, a fully open source stack and graceful handling of failures. This talk aims to introduce Tsuru and its components, showing how they work together.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34093", + "website_url": "http://oscon.com/2014/sched/34093", "speakers": [151991], "categories": [ @@ -1168,7 +1168,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1456, "description": "This talk describes the implementation and use of a full stack, low overhead tracing and profiling tool based on the Linux kernel profiler (perf) and extensions to the OpenJDK Hotspot JVM, that we've built at Twitter to help understand the behavior of the kernel, native and managed applications in production.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34094", + "website_url": "http://oscon.com/2014/sched/34094", "speakers": [172240], "categories": [ @@ -1186,7 +1186,7 @@ "time_stop": "2014-07-20 17:00:00", "venue_serial": 1449, "description": "Scala powers some of the biggest companies in the world, including Twitter, Intel, and LinkedIn. Come learn what led them to choose this powerful JVM language and try it out yourself. You\u2019ll get a hands-on intro to Scala and functional programming concepts by building your own performant REST API. No FP experience needed--if you can build apps in Java, Python or Ruby you\u2019ll do great in this class.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34095", + "website_url": "http://oscon.com/2014/sched/34095", "speakers": [171226,150440], "categories": [ @@ -1206,7 +1206,7 @@ "time_stop": "2014-07-21 12:30:00", "venue_serial": 1471, "description": "In this tutorial you will build a Reactive application with Play Framework, Scala, WebSockets, and AngularJS. We will get started with a template app in Typesafe Activator. Then we will add a Reactive RESTful JSON service and a WebSocket in Scala. We will then build the UI with AngularJS.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34103", + "website_url": "http://oscon.com/2014/sched/34103", "speakers": [1158], "categories": [ @@ -1226,7 +1226,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1456, "description": "Play Framework is the High Velocity Web Framework For Java and Scala. It is lightweight, stateless, RESTful, and developer friendly. This is an introduction to building web applications with Play. You will learn about: routing, Scala controllers & templates, database access, asset compilation for LESS & CoffeeScript, and JSON services.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34104", + "website_url": "http://oscon.com/2014/sched/34104", "speakers": [1158], "categories": [ @@ -1244,7 +1244,7 @@ "time_stop": "2014-07-21 17:00:00", "venue_serial": 1451, "description": "Metaprogramming in Python is fun and profitable thanks to its rich Data Model \u2013 APIs that let you handle functions, modules and even classes as objects that you can create, inspect and modify at runtime. The Data Model also enables your own objects to support infix operators, become iterable and emulate collections. This workshop shows how, through a diverse selection of examples and exercises.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34107", + "website_url": "http://oscon.com/2014/sched/34107", "speakers": [150170], "categories": [ @@ -1262,7 +1262,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1465, "description": "The key to writing Pythonic classes, APIs and frameworks is leveraging the Python Data Model: a set of interfaces which, when implemented in your classes, enables them to leverage fundamental language features such as iteration, context managers, infix operators, attribute access control etc. This talk shows how, through a diverse selection of examples.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34108", + "website_url": "http://oscon.com/2014/sched/34108", "speakers": [150170], "categories": [ @@ -1280,7 +1280,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1454, "description": "The underpinnings of open government are transparency and citizen participation. In re-imagining a new Data.gov (the open data, open government initiative for the White House), this was taken to heart. This system was created using open source and with comments, issues, and commits worked with the public all along the way.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34109", + "website_url": "http://oscon.com/2014/sched/34109", "speakers": [62631], "categories": [ @@ -1298,7 +1298,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1465, "description": "Python has no private fields, but the property decorator lets you replace public attributes with getters and setters without breaking client code. And the descriptor mechanism, used in Django for model field declarations, enables wide reuse of getter/setter logic via composition instead of inheritance. This talk explains how properties and descriptors work by refactoring a practical example.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34112", + "website_url": "http://oscon.com/2014/sched/34112", "speakers": [150170], "categories": [ @@ -1316,7 +1316,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1450, "description": "With Web performance and scalability becoming more and more important,\r\nchoosing advanced HTTP intermediaries is a vital skill. This presentation\r\nwill give the audience a thorough walkthrough of the most popular and\r\nadvanced solutions available today. The audience will gain a solid\r\nbackground to be able to make the right choices when it comes to HTTP\r\nintermediaries and proxy caches.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34116", + "website_url": "http://oscon.com/2014/sched/34116", "speakers": [63576], "categories": [ @@ -1334,7 +1334,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1452, "description": "You are a clever and talented person. You have architected a system that even my cat could use; your spreadsheet-fu is legendary. Your peers adore you. Your clients love you. But, until now, you haven\u2019t *&^#^! been able to make Git work. It makes you angry inside that you have to ask for help, again, to figure out that *&^#^! command to upload your work. It's not you. It's Git. Promise.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34117", + "website_url": "http://oscon.com/2014/sched/34117", "speakers": [4146], "categories": [ @@ -1352,7 +1352,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1454, "description": "The Facebook Android app is large and developed by hundreds of software engineers. This talk will cover how OSS helps us build Facebook for Android - and how we are good OSS citizens - by looking at the full life cycle of a release, from how we organize our git repo, do code reviews in Phabricator, through building using Buck, to how we've improved the quality of our releases using Selendroid.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34125", + "website_url": "http://oscon.com/2014/sched/34125", "speakers": [172656], "categories": [ @@ -1370,7 +1370,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1462, "description": "It's time to design products to capture life and physiologic signs invisibly\u2026 usually through non-invasive sensors that don't require a single drop of blood, but just whiffs and sniffs. And when it is visible, it must be designed to feel wonderful.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34135", + "website_url": "http://oscon.com/2014/sched/34135", "speakers": [44753], "categories": [ @@ -1388,7 +1388,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1449, "description": "Since its first release in early 2013, Docker has been deployed successfully to implement continuous integration and testing environments, where the very fast lifecycle of containers gives them an edge over virtual machines. We will see how to extend the workflow from development to testing and all the way to production. We'll also address challenges like reliability and scaling with Docker.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34136", + "website_url": "http://oscon.com/2014/sched/34136", "speakers": [151611], "categories": [ @@ -1406,7 +1406,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1475, "description": "Linux Containers (or LXC) is now a popular choice for development and testing environments. As more and more people use them in production deployments, they face a common question: are Linux Containers secure enough? It is often claimed that containers have weaker isolation than virtual machines. We will explore whether this is true, if it matters, and what can be done about it.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34137", + "website_url": "http://oscon.com/2014/sched/34137", "speakers": [151611], "categories": [ @@ -1424,7 +1424,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1462, "description": "University students rarely get a chance to fully embrace the Devops or FOSS development culture while in school. This year, we\u2019ve started a program called Devops Bootcamp, which is a hands-on, informal workshop open to any student at OSU. Devops Bootcamp immerses college students in the basics of Linux, Linux system administration and FOSS development practices. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34145", + "website_url": "http://oscon.com/2014/sched/34145", "speakers": [29558,123516], "categories": [ @@ -1442,7 +1442,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1465, "description": "A large part of the internet of things will be made up of small constrained devices. The IETF is standardising protocols which are memory, energy and network efficient. Come and get an overview of these and of some Open Source implementations. See devices including Arduinos and Raspberry Pis with several sensors talking with one another and be inspired to build/connect your own devices.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34148", + "website_url": "http://oscon.com/2014/sched/34148", "speakers": [172751], "categories": [ @@ -1460,7 +1460,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1462, "description": "Have you ever written or used an API wrapper for a webservice? REST is a client-server architecture model and building the server is only half of the challenge. This talk will walk through some of the challenges of building a REST client, describe some best practices and some patterns to avoid, and discuss how we can all work to build better APIs for an open web.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34156", + "website_url": "http://oscon.com/2014/sched/34156", "speakers": [151665], "categories": [ @@ -1478,7 +1478,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1452, "description": "This session presents the data platform used at Netflix for event collection, aggregation, and analysis. The platform helps Netflix process and analyze billions of events every day. Attendees will learn how to assemble their own large-scale data pipeline/analytics platform using open source software from NetflixOSS and others, such as Kafka, ElasticSearch, Druid from Metamarkets, and Hive. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34159", + "website_url": "http://oscon.com/2014/sched/34159", "speakers": [172661,171450], "categories": [ @@ -1496,7 +1496,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1466, "description": "This session will show how devops can use Heat to orchestrate the deployment &scaling of complex applications on top of OpenStack. Starting with a walk-thru of OpenStack example deployment Heat Templates for OpenShift Origin (available in openstack github repository) and enhance them to provide additional functions such as positioning alarms, responding to alarms, adding instances, &autoscaling. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34162", + "website_url": "http://oscon.com/2014/sched/34162", "speakers": [33987], "categories": [ @@ -1514,7 +1514,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1452, "description": "In this talk we'll explore the Fourier transform and FIR filters in an intuitive way to make it accessible. You'll come out with the ability to look at your time-series data in a new way and explore new uses for otherwise useless data.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34164", + "website_url": "http://oscon.com/2014/sched/34164", "speakers": [172201], "categories": [ @@ -1532,7 +1532,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1466, "description": "Exploring how the functional language features of Java 8 and Scala combine with Vaadin to allow you to write clearer UI code.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34176", + "website_url": "http://oscon.com/2014/sched/34176", "speakers": [120866], "categories": [ @@ -1550,7 +1550,7 @@ "time_stop": "2014-07-21 17:00:00", "venue_serial": 1450, "description": "Getting software released to users is often a painful, risky, and time-consuming process. This tutorial sets out the principles and technical practices that enable rapid, incremental delivery of high quality and valuable new functionality to users.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34187", + "website_url": "http://oscon.com/2014/sched/34187", "speakers": [2650], "categories": [ @@ -1570,7 +1570,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1464, "description": "In this talk we will look at some of the basic dynamics playing out in open source communities and introduce some mental models explaining them. We will look at the Open Source Flywheel (inspired by Walton's Productivity Loop and the Bezos Flywheel) and the Open Source Community Funnel (inspired by Sales Funnels) to explain them.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34188", + "website_url": "http://oscon.com/2014/sched/34188", "speakers": [62981], "categories": [ @@ -1590,7 +1590,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1452, "description": "Learning the syntax of a new language is easy, but learning to think under a different paradigm is hard. This session helps you transition from a Java writing imperative programmer to a functional programmer, using Java, Clojure and Scala for examples.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34192", + "website_url": "http://oscon.com/2014/sched/34192", "speakers": [2650], "categories": [ @@ -1608,7 +1608,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1456, "description": "Clojure is the most interesting new language on the horizon, but many developers suffer from the Blub Paradox when they see the Lisp syntax. This talk introduces Clojure to developers who haven\u2019t been exposed to it yet, focusing on the things that truly set it apart from other languages.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34194", + "website_url": "http://oscon.com/2014/sched/34194", "speakers": [2650], "categories": [ @@ -1626,7 +1626,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1450, "description": "'Callback hell' has very little to do with callbacks. Are promises delivering on the promise of better async flow control, or muddying the waters? Generating general generators, WAT? Let's wade through the world of async in JS to find order in the chaos.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34198", + "website_url": "http://oscon.com/2014/sched/34198", "speakers": [74368], "categories": [ @@ -1644,7 +1644,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1452, "description": "Higher-order functions such as map(), flatmap(), filter() and reduce() have their origins in mathematics and ancient functional programming languages such as Lisp. But today they have become mainstream and are available in languages such as JavaScript, Scala and Java 8. Learn how to they can be used to simplify code in a variety of domains including collection processing, concurrency and big data.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34201", + "website_url": "http://oscon.com/2014/sched/34201", "speakers": [11886], "categories": [ @@ -1662,7 +1662,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1458, "description": "Web APIs are increasingly important but their creation is still more an art than a science. This talk will demonstrate how Web APIs consumable by generic clients can be implemented in considerably less time. It will also give a brief introduction to JSON-LD and Hydra.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34203", + "website_url": "http://oscon.com/2014/sched/34203", "speakers": [172864], "categories": [ @@ -1680,7 +1680,7 @@ "time_stop": "2014-07-21 12:30:00", "venue_serial": 1452, "description": "You've dabbled a little in version control using Git. You can follow along with the various tutorials you've found online. But now you've been asked to implement a work flow strategy and you're not really sure how (or where) to start. You have a lot of choices, we'll help you pick the right one for your project.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34208", + "website_url": "http://oscon.com/2014/sched/34208", "speakers": [4146], "categories": [ @@ -1700,7 +1700,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1465, "description": "As technologists, sometimes it\u2019s as important to be able to share information with others as to be able to actually build something. IPython notebook is a powerful tool to both experiment with code (and data) and share the results with others, technical and non-technical alike. This session introduces the notebook and gives examples and techniques for using it effectively.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34212", + "website_url": "http://oscon.com/2014/sched/34212", "speakers": [59574], "categories": [ @@ -1718,7 +1718,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1462, "description": "Did you hear about the double arm amputee who was refused service at a bank because he could not provide a thumbprint? Or the online petition to increase services for blind folks, that they couldn\u2019t sign because of CAPTCHA? These are examples of security practices that cause barriers to people with disabilities. Security can create barriers, but it doesn\u2019t have to reduce accessibility!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34224", + "website_url": "http://oscon.com/2014/sched/34224", "speakers": [172899], "categories": [ @@ -1738,7 +1738,7 @@ "time_stop": "2014-07-20 17:00:00", "venue_serial": 1452, "description": "AngularJS is relatively new, meteorically popular, and functionally powerful. However, a lot of AngularJS\u2019s workings are very opaque and confusing. In this tutorial, my goal is to walk you through building a basic app, and introduce you to concepts, patterns, and ways of thinking that will allow you to comfortably dive further into using AngularJS for future projects.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34232", + "website_url": "http://oscon.com/2014/sched/34232", "speakers": [171372], "categories": [ @@ -1758,7 +1758,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1451, "description": "In this session we'll be exploring how to build rapid hardware prototypes using wifi and bluetooth low energy enabled Arduino boards, all controlled through JavaScript and API data, to allow for innovative, web enabled, software to hardware development techniques.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34236", + "website_url": "http://oscon.com/2014/sched/34236", "speakers": [74565], "categories": [ @@ -1776,7 +1776,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1457, "description": "Learn how someone writes code by writing code with them. Using katas and pair programming on actual code allows you to get an honest look at the candidate's thought process and capabilities, while exposing them to your team's culture and key players. Help your evaluators be focused on what kind of people you actually want on your project by creating key prompts for them to check.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34238", + "website_url": "http://oscon.com/2014/sched/34238", "speakers": [155107], "categories": [ @@ -1794,7 +1794,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1466, "description": "Elasticsearch is an open-source document store known for enabling search and real-time analytics on large data sets. In this presentation we will walk through the development of an application that monitors the Parrot AR.Drone. This application will collect metrics from the drone and then transform them to JSON for storage and real-time analysis in Elasticsearch.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34243", + "website_url": "http://oscon.com/2014/sched/34243", "speakers": [172929,142336], "categories": [ @@ -1812,7 +1812,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1464, "description": "Events are an attractive way to grow a community, but how do you choose which types of events work best for your users? We'll cover a variety of community event types, as well as building and scaling user groups remotely, simultaneous in-person and online participation, and getting other departments at your company invested in what you're planning.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34244", + "website_url": "http://oscon.com/2014/sched/34244", "speakers": [142767], "categories": [ @@ -1830,7 +1830,7 @@ "time_stop": "2014-07-21 17:00:00", "venue_serial": 1454, "description": "This full day of community management training is delivered by Jono Bacon, author of The Art of Community, and covers a wide range of topics for community managers and leaders to build fun, productive, and rewarding communities.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34247", + "website_url": "http://oscon.com/2014/sched/34247", "speakers": [108813], "categories": [ @@ -1848,7 +1848,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1464, "description": "In this new presentation from Jono Bacon, author of The Art of Community, founder of the Community Leadership Summit, and Ubuntu Community Manager, he discusses how to process, interpret, and manage rude, disrespectful, and non-constructive feedback in communities so the constructive criticism gets through but the hate doesn't.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34248", + "website_url": "http://oscon.com/2014/sched/34248", "speakers": [108813], "categories": [ @@ -1866,7 +1866,7 @@ "time_stop": "2014-07-21 12:30:00", "venue_serial": 1449, "description": "We want you to leave OSCON with a working cloud account, including supporting infrastructure that Amazon DOESN\u2019T provide but that will make your cloud life way more manageable! Once your account is bootstrapped with Asgard and Aminator, we\u2019ll be baking some of the myriad of @NetflixOSS apps. This tutorial will be meaningful for anyone getting started with or currently using AWS. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34252", + "website_url": "http://oscon.com/2014/sched/34252", "speakers": [172610], "categories": [ @@ -1886,7 +1886,7 @@ "time_stop": "2014-07-21 17:00:00", "venue_serial": 1449, "description": "Python is quickly becoming the go-to language for data analysis. However, it can be difficult to figure out which tools are good to use. In this workshop, we\u2019ll work through in-depth examples of tools for data wrangling, machine learning, and data visualization. I\u2019ll show you how to work through a data analysis workflow, and how to deal with different kinds of data. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34254", + "website_url": "http://oscon.com/2014/sched/34254", "speakers": [170237], "categories": [ @@ -1906,7 +1906,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1459, "description": "Python is quickly becoming the go-to language for data analysis, but it can be difficult to figure out which tools to use. In this presentation, I\u2019ll give a bird\u2019s eye overview of some of the best tools for data analysis and how you can apply them to your own workflow. I\u2019ll introduce you to how you can use Pandas, Scikit-Learn, NLTK, MRJob, and matplotlib for data analysis.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34255", + "website_url": "http://oscon.com/2014/sched/34255", "speakers": [170237], "categories": [ @@ -1924,7 +1924,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1452, "description": "'Programmer' and 'Manager' are two different titles for a reason: they're two different jobs and skill sets. If you have managerial aspirations (or have had them foisted upon you), come to this session to learn some of the tricks of the managerial trade.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34258", + "website_url": "http://oscon.com/2014/sched/34258", "speakers": [131404], "categories": [ @@ -1944,7 +1944,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1448, "description": "Hiring remote workers is great for filling those holes on the team...but if you don't have the correct infrastructure in place you're just setting yourself--and your team members--up for a world of hurt. This session will detail how our engineering department went remote and thrived because of it.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34260", + "website_url": "http://oscon.com/2014/sched/34260", "speakers": [131404], "categories": [ @@ -1962,7 +1962,7 @@ "time_stop": "2014-07-21 17:00:00", "venue_serial": 1457, "description": "This tutorial provides an introduction to Go with a focus on using it for everyday sysadmins tooling. A example of working from iostat is used to show a practical approach to learning the language.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34267", + "website_url": "http://oscon.com/2014/sched/34267", "speakers": [172994], "categories": [ @@ -1982,7 +1982,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1454, "description": "Savvy functional programmers are discovering logic programming, and SWI-Prolog's vast niftiness. Come watch Annie run her debugger in reverse, directly execute syntax specifications, and lets the computer figure out it's own darn execution strategy. Be amazed as Annie constrains variables and shrinks her APIs. Ooh and Aah at the many libraries, nifty web framework and clean environment.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34273", + "website_url": "http://oscon.com/2014/sched/34273", "speakers": [172986], "categories": [ @@ -2000,7 +2000,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1448, "description": "Furby's are back and more annoying than ever. Forget about a traffic light flashing or an email. When that Furby starts jabbering, you'll do ANYTHING to fix that build quickly. This talk will connect an Arduino board with Jenkins continuous integration framework and out to the Furby to let it annoy your development team, rather than you!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34274", + "website_url": "http://oscon.com/2014/sched/34274", "speakers": [99280], "categories": [ @@ -2020,7 +2020,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1458, "description": "Phass is a ZF2-based framework (implemented as a Module) designed to make building Google GlassWare applications in PHP as easy as possible. In this talk we\u2019ll show you how Phass works, complete with a live Google Glass demo of an application in action! \r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34275", + "website_url": "http://oscon.com/2014/sched/34275", "speakers": [6894], "categories": [ @@ -2040,7 +2040,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1449, "description": "This talk shows how to design mobile apps whose complex internal logic runs on many mobile operating systems, but with native UI on those platforms. This ensures that the best possible user experience on each platform.\r\n\r\nThis talk focuses on design patterns for structuring your app for dealing with a mix of cross\u2013platform code and platform-specific UI code.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34280", + "website_url": "http://oscon.com/2014/sched/34280", "speakers": [109468], "categories": [ @@ -2058,7 +2058,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1456, "description": "Erlang is a concurrent programming language with a small, active community and many high-uptime, critical deployments. It's syntax is a bit odd, being inspired by Prolog. Other languages--Elixir, notably--have begun to reap the benefits of Erlang's VM, BEAM, modifying syntax and semantics. This talk will provide a view of the BEAM languages, their history, motivations and benefits. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34281", + "website_url": "http://oscon.com/2014/sched/34281", "speakers": [172990], "categories": [ @@ -2076,7 +2076,7 @@ "time_stop": "2014-07-21 12:30:00", "venue_serial": 1450, "description": "Learn Test-Driven-Development and how it applies to web applications by building a simple web app from scratch using Python and Django. We'll cover unit testing, Django models, views and templates, as well as using Selenium to open up a real web browser for functional tests.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34283", + "website_url": "http://oscon.com/2014/sched/34283", "speakers": [180056], "categories": [ @@ -2098,7 +2098,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1454, "description": "This talk will introduce developers to the Elixir programming language and the Erlang VM and show how they introduce a completely new vocabulary which shapes how developers design and build distributed, fault-tolerant applications. This talk also discusses Elixir goals and what it brings to the Erlang VM.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34285", + "website_url": "http://oscon.com/2014/sched/34285", "speakers": [76735], "categories": [ @@ -2116,7 +2116,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1462, "description": "Women make up only 11% of open source developers. As Girl Develop It leaders in Philadelphia, we\u2019ve learned about what works to get women involved in open source projects at the grassroots level. We\u2019ll share our experience encouraging women to make open source contributions, using concrete methods that can be replicated in your own communities.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34289", + "website_url": "http://oscon.com/2014/sched/34289", "speakers": [169992,173025], "categories": [ @@ -2136,7 +2136,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1458, "description": "While Node.js and other asynchronous technologies have been receiving quite a bit of attention, in this session, we'll discuss a technology stack we've written which permits PHP developers to perform complex database, cache, and API requests asynchronously, in parallel, resulting in excellent response times. This can be done natively in PHP with NO gearman and NO custom PECL extensions.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34293", + "website_url": "http://oscon.com/2014/sched/34293", "speakers": [86090], "categories": [ @@ -2154,7 +2154,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1475, "description": "Find out why some people claim Go and MongoDB are a 'pair made in heaven' and 'the best database driver they've ever used' in this talk by Gustavo Niemeyer, the author of the mgo driver, and Steve Francia, the drivers team lead at MongoDB Inc.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34299", + "website_url": "http://oscon.com/2014/sched/34299", "speakers": [142995], "categories": [ @@ -2172,7 +2172,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1458, "description": "With plenty of live code and demos, this talk will show you how incredibly easy it is to write Java micro-services with modern Spring. We will walk though the process of creating a simple REST service, discuss deployment options and talk about how self-contained, stand-alone applications work in production.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34314", + "website_url": "http://oscon.com/2014/sched/34314", "speakers": [171565], "categories": [ @@ -2192,7 +2192,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1450, "description": "This talk will discuss why LineRate, a high-performance Layer 7 app proxy for developers, chose to embed Node.js as the programming language for the data path. The talk will focus on the challenges of building an embeddable\r\nNode.js and conclude with how the open source Node.js codebase could evolve to better support embeddable use cases.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34327", + "website_url": "http://oscon.com/2014/sched/34327", "speakers": [173056], "categories": [ @@ -2210,7 +2210,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1450, "description": "A combination of open standards, open source projects, and evolving browser technologies have made static web apps an increasingly appealing target even for complex applications. Learn how you can \u201cgo static\u201d and why you might want to do so.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34329", + "website_url": "http://oscon.com/2014/sched/34329", "speakers": [2593], "categories": [ @@ -2228,7 +2228,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1459, "description": "Learn about the 'nyt\u2a0da\u0431rik' platform which sits behind The New York Times website. Learn how it scales across many continents and AWS availability zones using RabbitMQ as the backbone of communication for exchanging messages in near real-time. nyt\u2a0da\u0431rik is built on open-source and most of it will be open sourced.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34332", + "website_url": "http://oscon.com/2014/sched/34332", "speakers": [112672,172658], "categories": [ @@ -2246,7 +2246,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1464, "description": "Working in open source is living the dream, right? What happens when that dream clashes with the real world deliveries associated with those paying you to work in the open. Speaking from 3 years of experience working on a new tools project and through interviews with others in the industry, this talk should help show you through the ups and downs.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34336", + "website_url": "http://oscon.com/2014/sched/34336", "speakers": [39928], "categories": [ @@ -2264,7 +2264,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1457, "description": "One of the tenets of Open Source is \u201cFree as in beer\u201d but there is still a viable commercial model. There are many successful open source companies that, despite the fact they give away their product, still manage to make money. A lot of money. How can this be possible? Let\u2019s explore some of the time tested strategies without compromising your core values.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34356", + "website_url": "http://oscon.com/2014/sched/34356", "speakers": [152376], "categories": [ @@ -2282,7 +2282,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1464, "description": "Not all projects benefit from a deep-pocketed corporate sponsor to fund their community activities. There are bills that need paying for server hosting, download bandwidth and the like, and maybe for trademark registration and other legal costs for larger projects. What's the best way to fund your project? These speakers know!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34362", + "website_url": "http://oscon.com/2014/sched/34362", "speakers": [29591,28902,173340], "categories": [ @@ -2300,7 +2300,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1461, "description": "The Go programming language has emerged as a favorite tool of DevOps and cloud practitioners alike. In many ways, Go is more famous for what it doesn't include than what it does, and co-author Rob Pike has said that Go represents a 'less is more' approach to language design. This talk will introduce Go and its distinctives to Java developers looking to add Go to their toolkits.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34371", + "website_url": "http://oscon.com/2014/sched/34371", "speakers": [173088], "categories": [ @@ -2320,7 +2320,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1450, "description": "Unless you're working full time as a front-end engineer, odds are that CSS frustrates you from time to time. This session offers advice on how to see past the obtuse corners of CSS, backed by fifteen years' hands-on experience.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34374", + "website_url": "http://oscon.com/2014/sched/34374", "speakers": [173093], "categories": [ @@ -2338,7 +2338,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1459, "description": "The Jet Propulsion Laboratory has been busy lately open sourcing its software, such as mobile apps for viewing the latest Mars images, communicating between robots, and sharing scientific analysis software in using app containers and cloud computing. Come and listen to stories and anecdotes about working on NASA projects and our journey into open source.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34377", + "website_url": "http://oscon.com/2014/sched/34377", "speakers": [173111], "categories": [ @@ -2356,7 +2356,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1451, "description": "WeIO is an innovative Open Source hardware and software platform for Internet of Things that allows the creation of wirelessly connected objects using popular web languages such as HTML5 or Python.\r\nAll further details can be found on project's web-site: http://we-io.net.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34378", + "website_url": "http://oscon.com/2014/sched/34378", "speakers": [173105], "categories": [ @@ -2374,7 +2374,7 @@ "time_stop": "2014-07-21 17:00:00", "venue_serial": 1471, "description": "Scalding is an open source framework developed at Twitter that provides a high level abstraction over Hadoop MapReduce, letting you concisely specify complex data analysis pipelines using simple Scala operations like map, filter, join, group, and sum. This introductory tutorial does not require experience with either Hadoop or Scala.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34383", + "website_url": "http://oscon.com/2014/sched/34383", "speakers": [90628], "categories": [ @@ -2394,7 +2394,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1465, "description": "We perl programmers aren't known as fans of formal types. Types are for straitjacketed languages like Java.\r\n\r\nBut... the Moose revolution's changing all that. Types are a great way of encapsulating the messy business of data conversion and parameter validation, and can help you think more clearly about what's going on in complex code.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34394", + "website_url": "http://oscon.com/2014/sched/34394", "speakers": [75349], "categories": [ @@ -2412,7 +2412,7 @@ "time_stop": "2014-07-21 12:30:00", "venue_serial": 1475, "description": "This tutorial will give developers an introduction and practical experience in building applications with the go language. Go expert Steve Francia will lead the class to build a working go web and cli application together teaching fundamentals, key features and best practices along the way. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34395", + "website_url": "http://oscon.com/2014/sched/34395", "speakers": [142995], "categories": [ @@ -2430,7 +2430,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1457, "description": "Do you know how long could it take to your team start producing value in the Big Data and Machine Learning area? This talk shows a real team experience starting from scratch to a functional Big Data and Machine Learning platform using several open source tools such as Apache Hadoop, Apache Hive and Python frameworks SciPy/Numpy/scikit-learn", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34407", + "website_url": "http://oscon.com/2014/sched/34407", "speakers": [173146,94695], "categories": [ @@ -2448,7 +2448,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1451, "description": "Can computers tell if trains run on time? Using microphones, IP cameras, Arduino and Raspberry Pi we set up sensors to detect commuter trains as they passed by. Together with signal processing in Python, streaming data aggregation with Flume and storing in Hadoop, we\u2019ll show you how you can do this too.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34414", + "website_url": "http://oscon.com/2014/sched/34414", "speakers": [171621,133624], "categories": [ @@ -2466,7 +2466,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1466, "description": "Apache Mesos is a cluster manager that provides efficient resource isolation and sharing across distributed applications. Mesos is not only a resource scheduler but also a library for rapidly developing scalable and fault-tolerant distributed systems. This talk will take the audience through the key aspects contributing to the growing adoption of Mesos in companies with large-scale data centers.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34422", + "website_url": "http://oscon.com/2014/sched/34422", "speakers": [172898,171598], "categories": [ @@ -2486,7 +2486,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1457, "description": "Open source, open data, and open access, that's what the City of Raleigh is all about. But how did Raleigh go from open government resolution to an open data portal and a preference for open source software for IT procurement? Come to this session to learn how city government and citizens are working together to create an open source city. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34430", + "website_url": "http://oscon.com/2014/sched/34430", "speakers": [156534,173173], "categories": [ @@ -2506,7 +2506,7 @@ "time_stop": "2014-07-21 17:00:00", "venue_serial": 1470, "description": "This training offers the first step in building a good knowledge of graph databases, and covers the core functionality of the open source Neo4j graph database. With a mixture of theory and hands-on practice sessions, you will quickly learn how easy it is to work with a powerful graph database using Cypher as the query language. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34431", + "website_url": "http://oscon.com/2014/sched/34431", "speakers": [159719], "categories": [ @@ -2524,7 +2524,7 @@ "time_stop": "2014-07-20 17:00:00", "venue_serial": 1456, "description": "The shift to the cloud is old news. Unfortunately, the pain of developing distributed architectures is not. Apache Mesos handles the hard parts of building distributed systems and lets developers focus on what makes their application special. In this workshop, we will illustrate how to write applications on Mesos by walking through the implementation of an example framework.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34432", + "website_url": "http://oscon.com/2014/sched/34432", "speakers": [172973,171598,172898], "categories": [ @@ -2544,7 +2544,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1451, "description": "It's a great time to be a hardware hacker. What started with the Arduino has now evolved to the Raspberry Pi, the BeagleBone Black, the Spark Core, the new Arduino Yun, and a host of other boards. How do you know which one is right for your project? This talk will compare the mainstream boards, how they are applied and help you decide which one best fits your needs. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34434", + "website_url": "http://oscon.com/2014/sched/34434", "speakers": [77350], "categories": [ @@ -2562,7 +2562,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1451, "description": "This year brings the release of Perl 5.20.0, and the 20th anniversary of the Perl 5 programming language. In this session, Ricardo Signes, the Perl 5 project manager, covers the latest developments in the language, the development process, and changes we're hoping for in the near future.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34442", + "website_url": "http://oscon.com/2014/sched/34442", "speakers": [3189], "categories": [ @@ -2580,7 +2580,7 @@ "time_stop": "2014-07-20 12:30:00", "venue_serial": 1449, "description": "In the fourth edition of this popular tutorial, we will focus on data visualization. Finding, parsing, drawing, and animating interesting data sets to promote understanding.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34447", + "website_url": "http://oscon.com/2014/sched/34447", "speakers": [6931,183609], "categories": [ @@ -2598,7 +2598,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1451, "description": "The last year has been great for Bluetooth LE. Supported on all smartphone OSes, hackable with Arduino and Raspberry PI, and ready for wearable computing. Review the year of BLE, and build your own smart watch.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34450", + "website_url": "http://oscon.com/2014/sched/34450", "speakers": [6931], "categories": [ @@ -2616,7 +2616,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1449, "description": "Operating a massive-scale system, such as the Netflix API, is no trivial task. It supports over 44M members in 40+ countries and sees billions of requests a day. Along the way, there have been many mistakes, yet it is still at the center of the Netflix streaming ecosystem. In this session, I will go into detail on the top ten lessons learned in operating this complex and critical system.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34451", + "website_url": "http://oscon.com/2014/sched/34451", "speakers": [173189], "categories": [ @@ -2634,7 +2634,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1462, "description": "Most Saturday mornings, Greg Bulmash brings together 70-80 boys and girls, dozens of parents and volunteers, and they teach the kids to code at a free club called CoderDojo. Come learn how to start a CoderDojo in your city and join the hundreds of cities around the world where kids are learning everything from 'hello world' to NodeCopters to building apps.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34458", + "website_url": "http://oscon.com/2014/sched/34458", "speakers": [171495], "categories": [ @@ -2652,7 +2652,7 @@ "time_stop": "2014-07-21 12:30:00", "venue_serial": 1451, "description": "In this tutorial you'll learn why you can't consider UX + design an optional extra when creating mobile apps, and how to tell an awesome app from a bad app. This highly interactive platform-agnostic design-heavy workshop is for programmers of any background. Learn how mobile apps work from a UI perspective, and how + why to build wireframes, and how to evaluate your designs for future improvement.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34459", + "website_url": "http://oscon.com/2014/sched/34459", "speakers": [108884,118998,109468], "categories": [ @@ -2672,7 +2672,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1450, "description": "Understanding games means understanding user engagement and interaction. In this session, you'll learn a fresh perspective on user experience design by understanding how users engage with the fastest-growing form of entertainment in the world.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34461", + "website_url": "http://oscon.com/2014/sched/34461", "speakers": [108884,118998], "categories": [ @@ -2690,7 +2690,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1462, "description": "We're building ever larger and more complex systems. Coupled with changing requirements and demands for scaling concurrency and parallelism, taming this complexity is no small order.\r\n\r\nAllow me to share my excitement with you! I'll show you how Haskell helps tame this complexity, allows you to overcome the challenges of modern software, and make predictions about what the near future holds.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34462", + "website_url": "http://oscon.com/2014/sched/34462", "speakers": [155881], "categories": [ @@ -2708,7 +2708,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1454, "description": "GitGot is a Perl-based tool for batch management of collections of git repos. It has a number of interesting features and acts as a force multiplier when dealing with a large varied collection of repositories. My talk will cover why you would want to use GitGot as well as how to use it effectively. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34463", + "website_url": "http://oscon.com/2014/sched/34463", "speakers": [173201], "categories": [ @@ -2726,7 +2726,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1464, "description": "I am going to expand on the experiences of setting up a worldwide community around our product, the Neo4j graph database. I will be presenting through the prism of being a woman in the technology world and how that has affected the way i had to work.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34471", + "website_url": "http://oscon.com/2014/sched/34471", "speakers": [161517], "categories": [ @@ -2744,7 +2744,7 @@ "time_stop": "2014-07-20 12:30:00", "venue_serial": 1470, "description": "Have you always wanted to create hardware devices to interact with the real world? Heard about the Arduino electronics prototyping platform but not sure how to get started? When you attend this workshop you will: set up an Arduino board & software; learn how the Arduino fits into the field of physical computing; and make your Arduino respond to button presses and blink lights. Hardware is fun!\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34473", + "website_url": "http://oscon.com/2014/sched/34473", "speakers": [77469], "categories": [ @@ -2764,7 +2764,7 @@ "time_stop": "2014-07-21 12:30:00", "venue_serial": 1470, "description": "The MySQL world is full of tradeoffs and choosing a High Availability (HA) solution is no exception. We demystify all the alternatives in an unbiased nature. Preference is of course only given to opensource solutions. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34498", + "website_url": "http://oscon.com/2014/sched/34498", "speakers": [147], "categories": [ @@ -2784,7 +2784,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1462, "description": "Aside from the fact that high school programming curricula often require proprietary IDEs, they also don't involve examining any source code from Open Source software projects. What changes would be required in programming curricula to incorporate Open Source? And is that a desirable objective?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34505", + "website_url": "http://oscon.com/2014/sched/34505", "speakers": [157509], "categories": [ @@ -2802,7 +2802,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1458, "description": "Sane and safe continuous deployment (and testing) can be achieved without much effort using a set of freely-available open-source tools, such as a good source control system, Phing, PHPUnit, some security tools, phpDocumentor and others.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34506", + "website_url": "http://oscon.com/2014/sched/34506", "speakers": [173235], "categories": [ @@ -2820,7 +2820,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1458, "description": "One of the most important tools created to help people learn Go is the Go tour (http://tour.golang.org)\r\n\r\nIt allows the user to learn the basics of Go and put them in practice directly on their browsers, running code without installing any compiler.\r\n\r\nImplementing this in a safe way is not an easy task! In this talk I present some techniques used to make sure everything goes as expected.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34509", + "website_url": "http://oscon.com/2014/sched/34509", "speakers": [155088], "categories": [ @@ -2838,7 +2838,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1465, "description": "As the internet grows, there are more and more interesting devices to connect to it - some of which are mobile, sensor platforms, or healthcare devices. This is all part of the 'Internet of Things' that has been an emerging area of excitement for the last few years. MQTT is a lightweight, messaging system for connected devices, the Industrial Internet, mobile, and the IoT.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34522", + "website_url": "http://oscon.com/2014/sched/34522", "speakers": [141661], "categories": [ @@ -2856,7 +2856,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1452, "description": "The actor model has received much attention because of its scalable and intuitive approach to concurrency. But the notion of concurrency is as fundamental to certain languages as object-orientation is to Java. In this talk, we will describe the evolution of concurrent thinking in Erlang, providing valuable lessons for Go, Rust, Elixir and AKKA developers who will undertake a similar journey.. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34530", + "website_url": "http://oscon.com/2014/sched/34530", "speakers": [10595], "categories": [ @@ -2874,7 +2874,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1458, "description": "Cheap LCD TV + Raspberry Pi = instant data dashboard. Learn how to use NodeJS and Amino for full screen GPU accelerated graphics (no X) to quickly build data dashboards. Show feeds, chart tweets, or visualize your build server with a particle fountain. Unleash gratuitous graphics for all to see.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34535", + "website_url": "http://oscon.com/2014/sched/34535", "speakers": [6931], "categories": [ @@ -2892,7 +2892,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1450, "description": "Learn about going beyond simple cookies and busting the 5MB limit imposed by Web Storage. We'll dive into the IndexedDB API and open your world to reading and writing not just strings from within browser storage, but also blobs, Arrays and Objects too.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34536", + "website_url": "http://oscon.com/2014/sched/34536", "speakers": [2397], "categories": [ @@ -2910,7 +2910,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1475, "description": "There has been an explosion in datastore technologies. There are five main types of datastores: Relational, Column Family, Graph, Key-Value and Document. Polyglot Persistence, or the ability to have many different types of datastores interacting with one application, is becoming more prominent and beginning to take center stage.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34542", + "website_url": "http://oscon.com/2014/sched/34542", "speakers": [159586], "categories": [ @@ -2928,7 +2928,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1454, "description": "Today's tech job descriptions want 'superstars', but most companies \u2013 and employees! \u2013 still treat employee talent as a replaceable commodity. How can you market yourself and your talents, to benefit your own career as well as the company or project you work for? This talk will provide practical ideas and real-life case studies, based on years of experience helping geeks communicate what they do.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34551", + "website_url": "http://oscon.com/2014/sched/34551", "speakers": [122293], "categories": [ @@ -2946,7 +2946,7 @@ "time_stop": "2014-07-21 17:00:00", "venue_serial": 1456, "description": "Getting everyone in your company or development team on the same page can be a challenge. This on-your-feet workshop will teach fast, fun improv techniques for helping your group to bond, generate quality ideas and make quick decisions. Learn the secrets of applied improv from two professionals who have decades of experience working in open source, Internet startups and corporate training.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34552", + "website_url": "http://oscon.com/2014/sched/34552", "speakers": [4378,106355,123894], "categories": [ @@ -2966,7 +2966,7 @@ "time_stop": "2014-07-20 17:00:00", "venue_serial": 1471, "description": "In this tutorial, we will develop a working Android application using open source libraries for key platform components: HTTP client, JSON parsing, Async image download and caching.\r\n\r\nYou will learn how to manage dependencies using Gradle and best practices for building Android apps using open source libraries.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34555", + "website_url": "http://oscon.com/2014/sched/34555", "speakers": [173281,182019], "categories": [ @@ -2984,7 +2984,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1449, "description": "React is a JavaScript library for building user interfaces developed by Facebook and Instagram. It has a novel rendering architecture that we're going to explore in this talk.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34568", + "website_url": "http://oscon.com/2014/sched/34568", "speakers": [133198], "categories": [ @@ -3002,7 +3002,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1457, "description": "Often business and developer needs are at odds when developing public facing websites that need to be indexed. Business is concerned with factors such as SEO, visitor retention and bounce rates, while engineering is concerned with developer ergonomics, re-usage, separation of concerns, and maintenance. This talk will describe a solution that satisfies both business and engineering requirements.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34570", + "website_url": "http://oscon.com/2014/sched/34570", "speakers": [135908], "categories": [ @@ -3022,7 +3022,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1466, "description": "Denial of Service (DoS) attacks have been making the news lately -- can your site hold up? In this talk, we'll look at a number of open-source tools for testing your site and walk through ways to guard yourself against web attackers.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34575", + "website_url": "http://oscon.com/2014/sched/34575", "speakers": [173285], "categories": [ @@ -3040,7 +3040,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1475, "description": "There is an adage that given enough data, a data scientist can answer the world's questions. The untold truth is that the majority of work happens during the ETL and data preprocessing phase. In this talk I discuss Origins, an open source Python library for extracting and mapping structural metadata across heterogenous data stores.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34578", + "website_url": "http://oscon.com/2014/sched/34578", "speakers": [152026], "categories": [ @@ -3058,7 +3058,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1449, "description": "Computing is spreading outwards: clusters of 1000s of nodes serve a single database, and hundreds of machines analyze the same KPIs.\r\n\r\nHow do we monitor a cluster with many nodes?\r\n\r\nThis talk presents how to effectively monitor a multi-node Cassandra cluster using Riemann and other graphing solutions.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34587", + "website_url": "http://oscon.com/2014/sched/34587", "speakers": [156989], "categories": [ @@ -3076,7 +3076,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1466, "description": "Firefox OS is a new mobile operating system, developed by Mozilla, which lets users install and run open web applications created using HTML, CSS, and JavaScript.\r\nThe session will introduce people to Firefox OS, the overview, branding and distribution and will explain the governance behind it. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34588", + "website_url": "http://oscon.com/2014/sched/34588", "speakers": [120025,173308], "categories": [ @@ -3094,7 +3094,7 @@ "time_stop": "2014-07-20 12:30:00", "venue_serial": 1457, "description": "How accessible are your development projects? This session puts development to the ultimate accessibility test. The presenters will guide you through an experience of accessibility for people who are blind and then go on to cover best practices, testing, and pitfalls in implementing accessible web and program design. You will walk away with actionable tips to use in your development projects.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34589", + "website_url": "http://oscon.com/2014/sched/34589", "speakers": [2699,173303], "categories": [ @@ -3114,7 +3114,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1464, "description": "This talk shares the success story of how a small open hardware project used an Arduino/Python archival digitization robot to spark an international collaboration spanning cultures and continents. The talk focuses on how the collaboration came to be, how the teams used tools like 3D printing and video to work together across 5000+ miles, and how other OS projects can create similar partnerships.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34595", + "website_url": "http://oscon.com/2014/sched/34595", "speakers": [165643], "categories": [ @@ -3132,7 +3132,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1466, "description": "The need for secure DNS is more pressing than ever but the current standard API for using the DNS can't take advantage of modern DNS features. We will give an application developers view of DNSSEC and describe the independently written getDNS API specification. We will showcase the open source implementation of the specification built by our team of developers from NLNet Labs and Verisign.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34610", + "website_url": "http://oscon.com/2014/sched/34610", "speakers": [173324,173326,173325,172895], "categories": [ @@ -3150,7 +3150,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1449, "description": "LinkedIn runs a node.js server to power its phone clients. Because the server makes HTTP requests to other services, network latencies make for slow, and potentially unreliable end-to-end tests. This presentation walks through how LinkedIn built an open-source tool, sepia, to address the challenge of scaling a complex test infrastructure in order to release high quality code with high confidence.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34612", + "website_url": "http://oscon.com/2014/sched/34612", "speakers": [172988], "categories": [ @@ -3168,7 +3168,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1459, "description": "Taking a complex API and wrapping it to create a coherent SDK for a programming language is a huge undertaking, and even harder when it has to be done by a single developer. I created pyrax, the Python SDK for OpenStack, at the request of my company. It has been a success, but it didn't come easy. In this talk I'll share some of the many lessons learned, both technical and political.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34627", + "website_url": "http://oscon.com/2014/sched/34627", "speakers": [152106], "categories": [ @@ -3186,7 +3186,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1464, "description": "In this session we'll look at how design effects an open source project and how to encourage designers to contribute. We'll also cover the fundamentals of design, in case a developer finds themselves in the role of designer.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34630", + "website_url": "http://oscon.com/2014/sched/34630", "speakers": [173248], "categories": [ @@ -3206,7 +3206,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1459, "description": "Mark McLoughlin and Monty Taylor - both members of the OpenStack Technical Committee and Foundation Board - gives their perspectives on how OpenStack caters to two distinct audiences with its time-based release process and its support for continuous deployment. They will also talk to this a case study for how DevOps is influencing the way open-source projects are managed and consumed.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34632", + "website_url": "http://oscon.com/2014/sched/34632", "speakers": [172824,109289], "categories": [ @@ -3224,7 +3224,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1449, "description": "This talk will explore the 'move fast' side of Facebook\u2019s software engineering culture: development process, organizational structure, and the vast amounts of tooling we create and use to make sure we don\u2019t screw up. We\u2019ll also dig into how we 'ship things': release process, A/B testing, gate keepers, test infrastructure, and more.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34635", + "website_url": "http://oscon.com/2014/sched/34635", "speakers": [109270], "categories": [ @@ -3244,7 +3244,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1459, "description": "The open source configuration management and automation framework Chef is used to configure, deploy and manage many large public and private installations of OpenStack and supports a wide variety of integration opportunities. OpenStack is a large and complex ecosystem, this session will highlight the Chef resources available for developers and operators.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34637", + "website_url": "http://oscon.com/2014/sched/34637", "speakers": [141169], "categories": [ @@ -3262,7 +3262,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1458, "description": "Did you know that one of the biggest PHP sites on the internet isn't running PHP? Did you know that HHVM clocks in at anywhere between 2x and 10x faster than standard PHP with an Opcode Cache? Come take a look at \u201cThe other PHP engine\u201d, how to get a server up and running, what pitfalls to watch out for in migrating over, and what exciting extras are waiting. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34640", + "website_url": "http://oscon.com/2014/sched/34640", "speakers": [173262,173336], "categories": [ @@ -3280,7 +3280,7 @@ "time_stop": "2014-07-21 12:30:00", "venue_serial": 1458, "description": "Most organisations have strategy documents full of implementation, purchasing, tactical and operational choices. Remove this and you're often left with a vague 'why' which normally boils down to copying everyone else. In this tutorial I'll demonstrate how a large number of companies are playing a game of chess in which they can't see the board and how you can exploit this.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34642", + "website_url": "http://oscon.com/2014/sched/34642", "speakers": [6219], "categories": [ @@ -3298,7 +3298,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1454, "description": "The Global Database of Events, Language, and Tone (GDELT) is an initiative to construct a catalog of human societal-scale behavior and beliefs across all countries of the world. Analysis of this data set requires addressing typical data quality and data skew issues. \r\n\r\nUse a combined Hadoop + SQL on Hadoop stack to cleanse the data and deliver insights into the state of the world. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34646", + "website_url": "http://oscon.com/2014/sched/34646", "speakers": [53442], "categories": [ @@ -3316,7 +3316,7 @@ "time_stop": "2014-07-20 17:00:00", "venue_serial": 1470, "description": "The new Arduino Yun contains both an Arduino Leonardo and a full Linux system on a chip with built-in Ethernet and Wifi. This intermediate level hands-on tutorial will teach you how to use the Yun to communicate between Yun and Yun, Yun and laptop, and Yun and internet services, such Gmail, Twitter, and other services with APIs", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34650", + "website_url": "http://oscon.com/2014/sched/34650", "speakers": [141561], "categories": [ @@ -3336,7 +3336,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1462, "description": "Red October is an open source encryption server with a twist -- it can encrypt secrets, requiring more than one person to decrypt them. This talk will describe what goes into building an open source security product and using it in the real world. From motivation, design decisions, pitfalls of using a young programming language like Go, through deployment and opening the work up to the community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34654", + "website_url": "http://oscon.com/2014/sched/34654", "speakers": [164229], "categories": [ @@ -3354,7 +3354,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1450, "description": "OpenUI5 is a comprehensive enterprise-grade HTML5 UI library (developed by SAP) which has been open-sourced recently. Explore its power through concrete code examples and demos for key features like declarative UIs, data binding, and responsiveness: write ONE app and it will adapt to any device, from desktop screen to smartphones.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34668", + "website_url": "http://oscon.com/2014/sched/34668", "speakers": [170822,173233], "categories": [ @@ -3372,7 +3372,7 @@ "time_stop": "2014-07-20 12:30:00", "venue_serial": 1452, "description": "Elasticsearch provides a powerful combination of clustered full-text search, synonyms, faceting, and geographic math, but there's a big gap between its documentation and real life. We'll tell hard-won war stories, work through hands-on examples, and show what happens behind the scenes, leaving you equipped to get the best use out of Elasticseach in your projects.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34677", + "website_url": "http://oscon.com/2014/sched/34677", "speakers": [173247,150], "categories": [ @@ -3392,7 +3392,7 @@ "time_stop": "2014-07-20 17:00:00", "venue_serial": 1451, "description": "Are you a software person? An artsy type? Never thought you would like hardware? Or perhaps you love hardware? No matter what your skill level, this workshop is for you. Get in on the open hardware movement and join ChickTech to create your own \u201csoft circuit\u201d using conductive thread, fabric, inputs/outputs, and a microcontroller! ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34678", + "website_url": "http://oscon.com/2014/sched/34678", "speakers": [131890,124700], "categories": [ @@ -3410,7 +3410,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1456, "description": "Karen Sandler, Executive Director of the GNOME Foundation, will discuss the peculiar tension in the intersection of free and open source software and corporate interest. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34687", + "website_url": "http://oscon.com/2014/sched/34687", "speakers": [173364], "categories": [ @@ -3430,7 +3430,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1448, "description": "Hacking Healthcare author David Uhlman will show you how to 3D print your body parts, order your own lab work, build a DNA analyzer, tour an array of personal monitoring devices for fitness, health and open biology projects, stop eating altogether by switching to soylent. Also tips on what insurance to get, navigating hospitals and finding the right doctor.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34688", + "website_url": "http://oscon.com/2014/sched/34688", "speakers": [86111], "categories": [ @@ -3448,7 +3448,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1448, "description": "Every open source project is a unique snowflake of technology choices, coding style, and communication channels. Learning not only how, but the 'correct' way to contribute to each new project can be a blocker for would-be contributors. This talk will give practical examples of how you can reduce the learning curve for new contributors and improve the quality of first-commits.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34690", + "website_url": "http://oscon.com/2014/sched/34690", "speakers": [104522], "categories": [ @@ -3466,7 +3466,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1462, "description": "Open edX is an open-source platform for delivering online courses. It's in use by the 31 member universities of edx.org (Harvard, MIT, Berkeley, etc), as well as Stanford, Google, and many other colleges and universities. This talk will describe the platform and show you ways to participate, as a course author, tool developer, or offering institution.\r\n ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34695", + "website_url": "http://oscon.com/2014/sched/34695", "speakers": [41059,179963], "categories": [ @@ -3484,7 +3484,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1458, "description": "PHP 5.6 is out, and comes with useful new features and internal cleanups, as the last few 5.x releases have. In this talk, I'll discuss those features, but also where PHP is going: will there be a PHP 6 or 7 in the near future? What might it contain? How can we learn from Python 3 and Perl 6?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34700", + "website_url": "http://oscon.com/2014/sched/34700", "speakers": [152118], "categories": [ @@ -3502,7 +3502,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1450, "description": "This talk gives a close look at second wave HTML5 features around video delivery \u2014 specifically, mediaSource API / adaptive streaming, encrypted media extension and WebRTC. We look at open tools and techniques for transcending platform limitations and delivery these experiences across increasingly diverse set of devices with real world examples from Kaltura, Wikimedia and others.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34702", + "website_url": "http://oscon.com/2014/sched/34702", "speakers": [108736], "categories": [ @@ -3520,7 +3520,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1457, "description": "What is the future of CentOS Linux? Hear the true story from the project leaders behind the surprise announcement that the CentOS Project and Red Hat are joining forces.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34705", + "website_url": "http://oscon.com/2014/sched/34705", "speakers": [46737,173380,173381], "categories": [ @@ -3538,7 +3538,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1475, "description": "Development challenges us to code for users\u2019 personal world. Users give push-back to ill-fitted assumptions about their own name, gender, sexual orientation, important relationships, & other attributes that are individually meaningful. We'll explore how to develop software that brings real world into focus & that allows individuals to authentically reflect their personhood & physical world.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34711", + "website_url": "http://oscon.com/2014/sched/34711", "speakers": [141590], "categories": [ @@ -3556,7 +3556,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1451, "description": "Mobile's here to stay! This talk will showcase how Open Source tools can power your test automation for mobile apps. It entirely relies on Open Source components such as Appium, Cordova/PhoneGap an Topcoat.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34713", + "website_url": "http://oscon.com/2014/sched/34713", "speakers": [173378], "categories": [ @@ -3574,7 +3574,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1450, "description": "Creating high performance sites and apps is crucial for every developer. In this session, we will explore the best practices and performance tricks, to make your apps running faster and fluid. Come learn the tips, tricks, and tools for maximizing the performance of your sites and apps with JavaScript and HTML5.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34717", + "website_url": "http://oscon.com/2014/sched/34717", "speakers": [133360], "categories": [ @@ -3592,7 +3592,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1466, "description": "This session is an overview of the Android Developer Tools (ADT and Android Studio), including many useful techniques, tips and tricks for getting the most out of them.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34718", + "website_url": "http://oscon.com/2014/sched/34718", "speakers": [150073], "categories": [ @@ -3610,7 +3610,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1459, "description": "\r\nThe Netflix OSS Cloud stack is clearly a great set of components for building a cloud infrastructure and platform\u2014if you are Netflix. But how does that architecture work for other businesses? Learn how at Riot we leveraged Netflix OSS cloud tools and platform components to create an infrastructure for our global game platform\u2014maybe it can work for you too.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34731", + "website_url": "http://oscon.com/2014/sched/34731", "speakers": [161577], "categories": [ @@ -3628,7 +3628,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1465, "description": "The Consumer Financial Protection Bureau (http://cfpb.gov) has\r\ndeveloped an open source web-based tool to make regulations easy to\r\nread, access and understand. We talk about the unique parsing and\r\nother challenges we encountered working with these legal documents,\r\nand how we used Python, pyParsing, Django and other open source tools\r\nto solve them.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34737", + "website_url": "http://oscon.com/2014/sched/34737", "speakers": [157931], "categories": [ @@ -3646,7 +3646,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1466, "description": "In this presentation we'll cover five important machine learning techniques that can be used in a wide range of applications. It will be a wide and shallow introduction, for Rubyists, not mathematicians - we'll have plenty of simple code examples. \r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34744", + "website_url": "http://oscon.com/2014/sched/34744", "speakers": [173396], "categories": [ @@ -3664,7 +3664,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1462, "description": "Secure software development is something absolutely critical to helping create safer more trusted computing experiences for everyone.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34745", + "website_url": "http://oscon.com/2014/sched/34745", "speakers": [173399,173403], "categories": [ @@ -3682,7 +3682,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1450, "description": "Like maps and open data? Koop has created a new way of accessing open data and making cool maps with wide variety of data \r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34746", + "website_url": "http://oscon.com/2014/sched/34746", "speakers": [108520,173406], "categories": [ @@ -3700,7 +3700,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1464, "description": "Japan has a thriving open source technology community. It\u2019s also the third largest IT market in the world, with more engineers per capita than the US, and a history of game-changing open source projects like Ruby and Jenkins. Hear a first hand account of managing and cultivating open source communities in Japan, the US and other countries and discuss international community building.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34753", + "website_url": "http://oscon.com/2014/sched/34753", "speakers": [153857], "categories": [ @@ -3718,7 +3718,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1452, "description": "A brief and friendly tour of the basics of graph theory, including a description and classification of the kinds of graphs and some interesting problems they can be employed to solve.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34756", + "website_url": "http://oscon.com/2014/sched/34756", "speakers": [137697], "categories": [ @@ -3736,7 +3736,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1448, "description": "A fun and approachable tour of some otherwise intimidating data structures. Learn how to solve difficult problems efficiently through the clever organization and linking of data.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34757", + "website_url": "http://oscon.com/2014/sched/34757", "speakers": [137697], "categories": [ @@ -3754,7 +3754,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1456, "description": "With the diversity and innovation in the Ruby ecosystem and the popularity of polyglot programming on the robust and efficient JVM, Ruby and the JVM make a great fit. We\u2019ll cover numerous ways to invoke Ruby from Java and other JVM languages and how to package and deploy this style of application. We\u2019ll study examples from AsciidoctorJ, a Java API to the Ruby-based text processor, Asciidoctor.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34769", + "website_url": "http://oscon.com/2014/sched/34769", "speakers": [117513], "categories": [ @@ -3772,7 +3772,7 @@ "time_stop": "2014-07-20 17:00:00", "venue_serial": 1458, "description": "This Introduction to Ceph tutorial will include a mix of lecture and instructor-led demonstrations that will introduce students to the Ceph distributed storage system, the challenges it addresses, its architecture, and solutions it offers.\r\n\r\nStudents will leave understanding how Ceph works, how it can be integrated with your services and applications, and how it works alongside OpenStack.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34772", + "website_url": "http://oscon.com/2014/sched/34772", "speakers": [183246], "categories": [ @@ -3792,7 +3792,7 @@ "time_stop": "2014-07-20 12:30:00", "venue_serial": 1458, "description": "Building on last year\u2019s critically acclaimed \u2018Demystifying SELinux: WTF is it saying?\u2019 talk Demystifying \u2018SELinux Part II: Who\u2019s policy is it anyway?\u2019 is an extended tutorial which has attendees work through real life examples of SELinux configuration and policy construction.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34773", + "website_url": "http://oscon.com/2014/sched/34773", "speakers": [151833], "categories": [ @@ -3812,7 +3812,7 @@ "time_stop": "2014-07-20 17:00:00", "venue_serial": 1457, "description": "This tutorial is a quick introduction to the Elixir programming language. We\u2019ll explore the basics of the language, meta programming, and explore why you want to use Elixir to write concurrent, scalable, and robust programs.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34788", + "website_url": "http://oscon.com/2014/sched/34788", "speakers": [173421], "categories": [ @@ -3832,7 +3832,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1454, "description": "The story of an open-source project that brings mobile accessibility APIs together with native webviews to make mobile app development more responsive to users with disabilities.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34797", + "website_url": "http://oscon.com/2014/sched/34797", "speakers": [17378], "categories": [ @@ -3850,7 +3850,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1448, "description": "So you know Java, Scala, Python, and Perl, but do you know the correct usage of a semicolon when it comes to the English language? Writers and engineers alike often fall victim to grammatical blunders that can obscure their intended message. Fortunately, there are some simple ways of spotting and correcting these errors. Once learned, your writing will improve and your readers will thank you. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34808", + "website_url": "http://oscon.com/2014/sched/34808", "speakers": [173393], "categories": [ @@ -3868,7 +3868,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1452, "description": "Visitors to an online store rarely make their intention explicit. A valuable goal in digital marketing is to infer this intention so to influence the visitor's behavior in-situ. We describe a data-driven approach to identifying and predicting online user behavior. The talk focuses on the construction of real-time machine learning tools for inference to sites with thousands of concurrent visitors.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34809", + "website_url": "http://oscon.com/2014/sched/34809", "speakers": [173414,173431], "categories": [ @@ -3886,7 +3886,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1448, "description": "You check out the schedule, and you note with excitement that there's a presentation called DIGITAL DANCING. You grab your stuff and head for that conference room.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34811", + "website_url": "http://oscon.com/2014/sched/34811", "speakers": [161486,182741], "categories": [ @@ -3904,7 +3904,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1466, "description": "Web combined with functional programming gives pure awesomeness. Come and learn about WebSharper, an open source web development framework for F#, and how it makes programmers happier and more productive.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34814", + "website_url": "http://oscon.com/2014/sched/34814", "speakers": [132323], "categories": [ @@ -3922,7 +3922,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1465, "description": "Python has a complex past with crypto. There are half a dozen frameworks built on at least three separate C implementations, each with their own strengths and weaknesses and in various states of maintenance. This presentation will review the current state of the art and discuss the future of crypto in Python including a new library aimed at fixing modern crypto support in Python.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34820", + "website_url": "http://oscon.com/2014/sched/34820", "speakers": [173432,173435], "categories": [ @@ -3940,7 +3940,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1462, "description": "Technology moves too quickly for us to ever really stop learning - but how can we establish and maintain a culture of continuous learning in our business teams? And how can we ensure that continuous learning is effective?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34823", + "website_url": "http://oscon.com/2014/sched/34823", "speakers": [152299], "categories": [ @@ -3960,7 +3960,7 @@ "time_stop": "2014-07-20 12:30:00", "venue_serial": 1475, "description": "Curious about OpenStack, but don't know where to start? In this hands on tutorial we will walk you through the basics of OpenStack, the OpenSource cloud computing platform that is used to build private and public clouds. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34824", + "website_url": "http://oscon.com/2014/sched/34824", "speakers": [169647,169673], "categories": [ @@ -3978,7 +3978,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1452, "description": "Erlang is famous for building systems that are incredibly reliable, having virtually no down time! What are the principles that Erlang uses? Can we apply them in other languages? In this presentation, you'll learn how Erlang's design enables reliability and how you can use similar patterns to improve your own software and software systems.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34829", + "website_url": "http://oscon.com/2014/sched/34829", "speakers": [131729], "categories": [ @@ -3996,7 +3996,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1458, "description": "Want to integrate some body, face, voice recognition into your 3D application? You can do this fairly easily using the Kinect for Windows sensor along with the Kinect Common Bridge, an open source library that makes it simple to integrate Kinect experiences into your C++ code/library. OpenFrameworks, Cinder and other creative development communities have adopted it already! Cool creative demos!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34831", + "website_url": "http://oscon.com/2014/sched/34831", "speakers": [143232,23017], "categories": [ @@ -4014,7 +4014,7 @@ "time_stop": "2014-07-23 20:30:00", "venue_serial": 1450, "description": "Join us for the ever popular Perl Lightning Talks.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34836", + "website_url": "http://oscon.com/2014/sched/34836", "speakers": [4429], "categories": [ @@ -4034,7 +4034,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1456, "description": "This session will explore how Java development has been brought into the open over the past several years. Several Java developer community efforts have brought open source development processes and new levels of transparency and participation into their communities. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34839", + "website_url": "http://oscon.com/2014/sched/34839", "speakers": [65329,116276], "categories": [ @@ -4052,7 +4052,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1466, "description": "Does every open source project need an open infrastructure? Should root be potentially available to any community member? If you think, 'Maybe, yes,' come learn how-to and why-to with lessons-learned from Fedora, oVirt, CentOS Project, and other projects.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34840", + "website_url": "http://oscon.com/2014/sched/34840", "speakers": [46737], "categories": [ @@ -4070,7 +4070,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1454, "description": "Learn to use Puppet like a Pro! We will take you through several examples of how to bring your Puppet deployment to the next level. We will cover Hiera, deploying puppet code, code architecture best practices, and integrating external tools.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34844", + "website_url": "http://oscon.com/2014/sched/34844", "speakers": [125113,99817], "categories": [ @@ -4090,7 +4090,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1451, "description": "At OSCON 2011 we introduced the Transit Appliance, a project to use open hardware, open source software and open APIs to create a low-cost display for transit arrivals. Three years later we have two dozen displays deployed in the community, have seen the retirement of the Chumby, the rise of the Raspberry Pi and many new web services enriching the display. Progress and lessons learned.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34845", + "website_url": "http://oscon.com/2014/sched/34845", "speakers": [109116], "categories": [ @@ -4108,7 +4108,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1466, "description": "Many developers, system/network admins, and designers spend good portions of their careers avoiding any interaction with their systems' command line interface(s) (CLI's). Unfortunately, the CLI is viewed as an archaic and inefficient means of being productive. In tmux, a powerful terminal multiplexer, developers and admins have a tool for more fully exploiting the power of the CLI.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34849", + "website_url": "http://oscon.com/2014/sched/34849", "speakers": [138530], "categories": [ @@ -4126,7 +4126,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1448, "description": "Online services like 'If This Then That' (IFTTT) are great for automating your life. However they provide limited ways for the end-user to add their own services, and often require credentials that one may normally wish to keep secret.\r\n\r\nThe 'exobrain' project allows for service integration and extension on a machine *you* control.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34853", + "website_url": "http://oscon.com/2014/sched/34853", "speakers": [6631], "categories": [ @@ -4144,7 +4144,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1466, "description": "This talk will introduce a new open source platform for citizen science data. It allows anyone anywhere to create online data sets by uploading data from their own environmental sensors, mobile devices, do-it-yourself science equipment, and other measurement tools. The talk will describe the design and use of the platform, covering multiple applications and alternatives.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34855", + "website_url": "http://oscon.com/2014/sched/34855", "speakers": [169557], "categories": [ @@ -4162,7 +4162,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1464, "description": "The United States needs more tech talent. Period.\r\n\r\nAnd yet there is a solution \u2014 Grow Developers. My talk will cover all the many ways this community can actively solve the lack of talent problem, and at the same time give solutions for also growing the female and minority tech populations.\r\n\r\nPeople will walk away with a 3-tiered approach for growing developers and growing diversity. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34856", + "website_url": "http://oscon.com/2014/sched/34856", "speakers": [173388], "categories": [ @@ -4180,7 +4180,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1457, "description": "This talk is going to provide insight into what\u2019s it\u2019s like to view Software Development as an outsider, who happens to be an experienced successful professional. I will also tackle the issues that are implicit with imposter syndrome, such as how to grow developers, how to grow diversity, fixing broken hiring practices, and what it\u2019s like to be afraid of open source. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34860", + "website_url": "http://oscon.com/2014/sched/34860", "speakers": [173388,180122], "categories": [ @@ -4200,7 +4200,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1464, "description": "What happens to an open source community full of hobbyists when the project cleans up its pile of spaghetti and chooses to adopt widely held programming paradigms and systems?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34863", + "website_url": "http://oscon.com/2014/sched/34863", "speakers": [173433], "categories": [ @@ -4218,7 +4218,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1459, "description": "OpenStack is an open source implementation of cloud computing, potentially at very large scale. However, it has many moving parts and is complex to operate. \r\nSaltStack appears to provide scalable and secure orchestration for OpenStack.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34865", + "website_url": "http://oscon.com/2014/sched/34865", "speakers": [143135], "categories": [ @@ -4236,7 +4236,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1464, "description": "Asia is a huge untapped market for open source expansion, but it is very unlike North America or Europe. Learn differences within Asia, see open source proliferation in all markets, and most importantly get into thinking Asia expansion is prime.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34870", + "website_url": "http://oscon.com/2014/sched/34870", "speakers": [147], "categories": [ @@ -4256,7 +4256,7 @@ "time_stop": "2014-07-20 12:30:00", "venue_serial": 1471, "description": "Advanced math for business people: \u201cjust enough math\u201d to take advantage of new classes of open source frameworks. Many take college math up to calculus, but never learn how to approach sparse matrices, complex graphs, or supply chain optimizations. This tutorial ties these pieces together into a conceptual whole, with use cases and simple Python code, as a new approach to computational thinking.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34873", + "website_url": "http://oscon.com/2014/sched/34873", "speakers": [146540], "categories": [ @@ -4276,7 +4276,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1457, "description": "Money Machines are small scale highly technical or craft based entrepreneurial excursions. The purpose of the Money Machine is to empower individuals with tools which will allow her/him/them to follow their geeky and nerdy passion of passions while enabling them to address the various financial necessities of life. Money Machines allow people to work in the manner of their choosing.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34875", + "website_url": "http://oscon.com/2014/sched/34875", "speakers": [138530], "categories": [ @@ -4294,7 +4294,7 @@ "time_stop": "2014-07-21 17:00:00", "venue_serial": 1458, "description": "Learn the fundamentals of Erlang - a high productivity, functional programming language used to build scalable, highly concurrent systems. In this tutorial, we'll introduce Erlang by way of a fun problem: building an HTTP server! You'll learn the basic of networking programming in Erlang along with key techniques for performance and scalability.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34881", + "website_url": "http://oscon.com/2014/sched/34881", "speakers": [131729], "categories": [ @@ -4314,7 +4314,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1465, "description": "Functional programming is everywhere, hiding between imperative procedures. Stateless code with no side-effects may seem academic, but practical application of functional techniques leads to fewer bugs and cleaner code. Functional thinking is useful whether you're wrestling with a mess of copy-pasta or doing test-first development on some new object library.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34882", + "website_url": "http://oscon.com/2014/sched/34882", "speakers": [6574], "categories": [ @@ -4332,7 +4332,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1459, "description": "With the rise of cloud-based services and Web APIs, it may be time to re-visit Raymond's 19 'lessons' from his book 'The Cathedral and the Bazaar' to see how they can be applied (and/or modified) to fit a world where much of the software we use is no longer installed locally and is often kept out of reach from most developers and users. \r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34888", + "website_url": "http://oscon.com/2014/sched/34888", "speakers": [108272], "categories": [ @@ -4350,7 +4350,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1449, "description": "Tritium is a new open source language from the creator of the popular Sass and HAML languages that brings a modern approach to web development with transforms. In this talk, we'll introduce the Tritium language and the power of transform based approaches for separating content from presentation in the building of multi-device websites for desktops, smartphones, tablets, TVs, wearables and beyond.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34894", + "website_url": "http://oscon.com/2014/sched/34894", "speakers": [122599], "categories": [ @@ -4368,7 +4368,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1454, "description": "These days, moving away doesn\u2019t put too much of a dampener on staying in touch with your friends. Unfortunately, it has had a severe effect on my regular board games day.\r\nSo, I thought, why not solve this problem with telepresence board gaming? Can\u2019t be too hard! This session will cover what's been done, and what problems are still out there I have no idea about how to solve?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34897", + "website_url": "http://oscon.com/2014/sched/34897", "speakers": [173452], "categories": [ @@ -4386,7 +4386,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1456, "description": "If your application doesn't have APIs, it's probably written in Cold Fusion. Every application has APIs, and APIs need authentication. See how OAuth2 is robust enough to satisfy the demands of the enterprise, while still serving the smallest of side projects. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34905", + "website_url": "http://oscon.com/2014/sched/34905", "speakers": [173314], "categories": [ @@ -4404,7 +4404,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1457, "description": "Mapbox is leading the way in open mapping. Large companies are switching to OpenStreetMap and open source software for mapping. Learn how Mapbox is running a business like you would run an open source project and how it is succeeding in a field dominated by large, well-funded players by being open. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34906", + "website_url": "http://oscon.com/2014/sched/34906", "speakers": [104652], "categories": [ @@ -4422,7 +4422,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1452, "description": "Several frameworks have emerged for handling data workflows. Meanwhile, business use of Machine Learning is less about algorithms and more about leveraging workflows. This talk compares/contrasts different workflow approaches with focus on use cases, plus how some leverage the PMML open standard. Summary points build a scorecard for evaluating frameworks based on your use case needs.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34913", + "website_url": "http://oscon.com/2014/sched/34913", "speakers": [146540], "categories": [ @@ -4440,7 +4440,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1457, "description": "AngularJS is one of the most widely adopted open source Javascript frameworks in recent times. We use it for a not-so-typical use case: web apps to deliver financial services to the poor. In this case-study session, we analyze the pros/cons of AngularJS, establish why it was right for us, and go over our experiences using this powerful lightweight framework which adds value to our community daily.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34920", + "website_url": "http://oscon.com/2014/sched/34920", "speakers": [173455], "categories": [ @@ -4460,7 +4460,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1465, "description": "We'll explore how to use an $8 dollar DVB-T TV dongle to monitor and capture various radio frequencies. The RTL-SDR library turns a cheap Realtek DVB-T into a very powerful Software Defined Radio receiver which can be used to inspect and hack various wireless protocols. All of the code is Free and Open Source so it runs on all platforms.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34921", + "website_url": "http://oscon.com/2014/sched/34921", "speakers": [77757], "categories": [ @@ -4478,7 +4478,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1459, "description": "Sometimes your API is meant for a small group and will live for only a short time. Other times, your aim is to create an interface that will have wide appeal and should last years into the future. \r\n\r\nThis talk shows you how to create and maintain an API that it can be both stable and vital well into the future.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34922", + "website_url": "http://oscon.com/2014/sched/34922", "speakers": [108272], "categories": [ @@ -4496,7 +4496,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1454, "description": "Parse is a popular mobile Backend-as-a-Service allowing mobile developers to use backend APIs in conjunction with mobile apps. LoopBack is an open source mBaaS implementation that offers all the same functionality, is written in Node.js, and can be extended with Node.js' community of over 50,000 modules.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34941", + "website_url": "http://oscon.com/2014/sched/34941", "speakers": [173465], "categories": [ @@ -4514,7 +4514,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1464, "description": "Culture shift is a huge challenge in the public sector. I will walk through how the Consumer Financial Protection Bureau is able to successfully open source data, platforms, and standards.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34942", + "website_url": "http://oscon.com/2014/sched/34942", "speakers": [156591], "categories": [ @@ -4532,7 +4532,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1449, "description": "Developers, increasingly, need to work in several different development languages. It is hard enough to remember all the bits and pieces of the languages themselves, do you really need to know all the unique toolchains to make them work?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34944", + "website_url": "http://oscon.com/2014/sched/34944", "speakers": [141524], "categories": [ @@ -4550,7 +4550,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1462, "description": "Documentation is paramount to increasing an open source project's adoption and growth. But writing good documentation is hard. Using examples from new and mature projects, we'll explore detailed tactics for selecting, prioritizing, outlining, and writing documentation targeted at multiple audiences.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34952", + "website_url": "http://oscon.com/2014/sched/34952", "speakers": [142111], "categories": [ @@ -4568,7 +4568,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1454, "description": "The prospects and promise of webRTC--direct browser-to-browser multimedia communications--have led to an explosion of tools, both proprietary and Open Source. In this session we present an overview of a variety of tools vying for attention, along with a demonstration of the sipML Javascript toolkit, using webRTC-enabled browsers and the latest version of Asterisk.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34953", + "website_url": "http://oscon.com/2014/sched/34953", "speakers": [6921,173464,173466,173467,173468], "categories": [ @@ -4586,7 +4586,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1475, "description": "Broad introduction to Bash features for users who want to go beyond simple command execution. Covered topics include builtins, keywords, functions, parameters (arguments, variables, arrays, special parameters), parameter expansion and manipulation, compound commands (loops, groups, conditionals), and brace expansion.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34954", + "website_url": "http://oscon.com/2014/sched/34954", "speakers": [173223], "categories": [ @@ -4604,7 +4604,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1451, "description": "I was chosen, out of eighteen successful applicants, to be one of four Linux kernel interns through the Gnome Outreach Program for Women. This is the story of my journey from a frustrated retail worker, dreaming of writing code for a living, to a full fledged kernel developer. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34955", + "website_url": "http://oscon.com/2014/sched/34955", "speakers": [140811], "categories": [ @@ -4622,7 +4622,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1459, "description": "If you want to run your own Internet node, it requires gluing together an awful lot of software, and maintaining it. We'll show you a fresh approach: use the Mirage operating system to easily compile the protocols you need (DNS, HTTP, XMPP and IMAP) for your Internet presence into a type-safe unikernel, and deploy the whole thing using just Travis CI and Git directly on the cloud or on ARM.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35024", + "website_url": "http://oscon.com/2014/sched/35024", "speakers": [109140,159772], "categories": [ @@ -4640,7 +4640,7 @@ "time_stop": "2014-07-20 12:30:00", "venue_serial": 1450, "description": "Learn everything you need to know from Git and GitHub to be the most effective member of your team, save yourself from any jam, and work with the rest of your team flawlessly.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35027", + "website_url": "http://oscon.com/2014/sched/35027", "speakers": [152215], "categories": [ @@ -4658,7 +4658,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1475, "description": "Discover why Electronic Arts goes Erlang and hear about a powerful, reactive server architecture that supports a highly concurrent, analyzable and secure simulation stack for gaming. Learn how to easily script composable entities using a server environment purpose-built for event-driven programming, which is scalable under load, resilient and enables evaluation of huge data sets in real-time.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35038", + "website_url": "http://oscon.com/2014/sched/35038", "speakers": [174072,174073], "categories": [ @@ -4676,7 +4676,7 @@ "time_stop": "2014-07-24 11:40:00", "venue_serial": 1456, "description": "This presentation covers all aspects of configuring Apache HTTP Server for https/TLS, including ECC, RSA and DH keys and key strength, cipher suites, SSL session caches vs. session tickets, OCSP stapling and TLS virtual hostnames. These elements are integrated to provide perfect forward secrecy and meet modern best practices for both client and proxied connections.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35367", + "website_url": "http://oscon.com/2014/sched/35367", "speakers": [124630], "categories": [ @@ -4694,7 +4694,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1451, "description": "The Nest learning thermostat has won the hearts and minds of consumers everywhere by completely re-thinking how a thermostat works. In this session, we'll explore the Internet of Things by discussing how to build an amazing connected device using open source technology, with Spark's open source thermostat project acting as a case study.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35390", + "website_url": "http://oscon.com/2014/sched/35390", "speakers": [175040], "categories": [ @@ -4712,7 +4712,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1451, "description": "Managing CPAN dependencies can be a major frustration for Perl developers. In this session, you'll discover how to easily manage those dependencies by creating a private CPAN repository with Pinto.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35406", + "website_url": "http://oscon.com/2014/sched/35406", "speakers": [173472], "categories": [ @@ -4730,7 +4730,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1457, "description": "How can businesses take the best ideas from the open source community to improve their end product and the happiness of their developers? In this fireside-chat-styled session, Derek Sorkin from GitHub will talk with Tim Tyler about his experiences setting up a community inside Qualcomm that mimics an open source project. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35437", + "website_url": "http://oscon.com/2014/sched/35437", "speakers": [175353,175354], "categories": [ @@ -4748,7 +4748,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1448, "description": "It seems to have been a common theme amongst startups to create the MVP\r\n(Minimum Viable Product) in a language that facilitates rapid\r\nprototyping (for example Ruby), and then migrate to the JVM when the\r\napplication has proved itself and requires more in terms of stability\r\nand performance.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35444", + "website_url": "http://oscon.com/2014/sched/35444", "speakers": [132564], "categories": [ @@ -4766,7 +4766,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1457, "description": "Multiple indexes into data structures add complexity and slow down processing. A single multi-keyed AVL tree can allow complex searches to be constructed more easily and performed quickly, with a single O(lg N) lookup.\r\n\r\nIn this talk we will discuss how these trees work and how to implement them. Examples will be shown using python version 3, with C++ libraries for optimization of key routines.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35455", + "website_url": "http://oscon.com/2014/sched/35455", "speakers": [31044], "categories": [ @@ -4784,7 +4784,7 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1452, "description": "Uber is one of the fastest growing companies in the world and the real-time engineering team are responsible for their mission critical Node.js-powered systems. Learn how they are adapting their services to be autonomous, loosely-coupled and highly-available by applying the principles of event-driven architecture.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35464", + "website_url": "http://oscon.com/2014/sched/35464", "speakers": [175481], "categories": [ @@ -4802,7 +4802,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1454, "description": "For the uninitiated, a conversation with functional programmers can feel like ground zero of a jargon explosion. In this talk Lambda Ladies Co-Founder Katie Miller will help you to defend against the blah-blah blast by demystifying several terms commonly used by FP fans with bite-sized Haskell examples and friendly pictures. Expect appearances by Curry, Lens, and the infamous M-word, among others.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35493", + "website_url": "http://oscon.com/2014/sched/35493", "speakers": [175488], "categories": [ @@ -4820,7 +4820,7 @@ "time_stop": "2014-07-23 10:00:00", "venue_serial": 1525, "description": "The purpose of this talk is to reexamine the topic through the lens of concrete things individuals can do to check their privilege \u2013 and to put it to work serving themselves and others.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35511", + "website_url": "http://oscon.com/2014/sched/35511", "speakers": [8837], "categories": [ @@ -4838,7 +4838,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1448, "description": "A lot has changed at Microsoft. Azure has 1000 Linux VMs to choose from, there's RESTful APIs abound, and more OSS than ever before. What are Microsoft's web folks thinking and how are they developing software today? Is is a good thing?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35544", + "website_url": "http://oscon.com/2014/sched/35544", "speakers": [132865], "categories": [ @@ -4856,7 +4856,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1448, "description": "Design is a process, not a product. What processes do successful data designers follow, and how can we all benefit by open-sourcing our processes (to make better products)?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35684", + "website_url": "http://oscon.com/2014/sched/35684", "speakers": [147840], "categories": [ @@ -4874,7 +4874,7 @@ "time_stop": "2014-07-23 09:45:00", "venue_serial": 1525, "description": "In this keynote, Simon will present the general principles of industry change and describe what can and cannot be predicted. He will then examine how companies can better understand the environment around them and by anticipating the nature of change then manipulate the market in their favor through open techniques.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35743", + "website_url": "http://oscon.com/2014/sched/35743", "speakers": [6219], "categories": [ @@ -4892,7 +4892,7 @@ "time_stop": "2014-07-23 09:20:00", "venue_serial": 1525, "description": "What do you care about most in the worlds of software, the Net, and Life Online? Are you worried about it? Now is the time for sensible, reasonable, extreme paranoia.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35744", + "website_url": "http://oscon.com/2014/sched/35744", "speakers": [24978], "categories": [ @@ -4910,7 +4910,7 @@ "time_stop": "2014-07-20 17:00:00", "venue_serial": 1587, "description": "If you have a school aged children interested in learning more about computer programming, bring them to OSCON. We'll be hosting an entire day of workshops for kids about Java, Python, Scratch, Minecraft Modding, Arduino and more. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35847", + "website_url": "http://oscon.com/2014/sched/35847", "speakers": [], "categories": [ @@ -4928,7 +4928,7 @@ "time_stop": "2014-07-20 19:00:00", "venue_serial": 1525, "description": "If you had five minutes on stage what would you say? What if you only got 20 slides and they rotated automatically after 15 seconds? Would you pitch a project? Launch a web site? Teach a hack? We\u2019ll find out at our annual Ignite event at OSCON.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35848", + "website_url": "http://oscon.com/2014/sched/35848", "speakers": [], "categories": [ @@ -4946,7 +4946,7 @@ "time_stop": "2014-07-20 22:00:00", "venue_serial": 1606, "description": "Don't forget to pack your running shoes and your glow-in-the-dark gear, because the OSCON 5K fun run is back. Whether you are an avid runner or just starting out, you are invited to join other OSCON attendees Sunday evening for a run/jog/walk through some of the most scenic and emblematic sites of Portland. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35853", + "website_url": "http://oscon.com/2014/sched/35853", "speakers": [], "categories": [ @@ -4964,7 +4964,7 @@ "time_stop": "2014-07-21 18:00:00", "venue_serial": 1474, "description": "Grab a drink and kick off OSCON by meeting and mingling with exhibitors and fellow attendees.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35854", + "website_url": "http://oscon.com/2014/sched/35854", "speakers": [], "categories": [ @@ -4982,7 +4982,7 @@ "time_stop": "2014-07-21 20:00:00", "venue_serial": 1585, "description": "This year's attendee party focuses on the four classical elements--fire, earth, air, and water. Wait till you see how each of these essential ideas transforms Hall B into new areas to explore and savor. Trust us, this is one party you don't want to miss! ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35855", + "website_url": "http://oscon.com/2014/sched/35855", "speakers": [], "categories": [ @@ -5000,7 +5000,7 @@ "time_stop": "2014-07-21 22:00:00", "venue_serial": 1626, "description": "Join Puppet Labs for our OSCON \u201cOpen\u201d House Party! We are excited to open our doors to all our OSCON and Puppet Labs Friends.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35856", + "website_url": "http://oscon.com/2014/sched/35856", "speakers": [], "categories": [ @@ -5018,7 +5018,7 @@ "time_stop": "2014-07-22 19:00:00", "venue_serial": 1474, "description": "Quench your thirst with vendor-hosted libations and snacks while you check out all the cool stuff in the expo hall. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35857", + "website_url": "http://oscon.com/2014/sched/35857", "speakers": [], "categories": [ @@ -5036,7 +5036,7 @@ "time_stop": "2014-07-22 23:30:00", "venue_serial": 1473, "description": "*8:30pm - 12:00am*\r\n\r\nCitrix is sponsoring a night of poker, cocktails and hors d'oeuvres. For one night only, OSCON\u2019s Foyer will be transformed into Portland\u2019s only poker room complete with professional dealers. You'll be playing poker above the city lights with a perfect view of the city.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35858", + "website_url": "http://oscon.com/2014/sched/35858", "speakers": [], "categories": [ @@ -5054,7 +5054,7 @@ "time_stop": "2014-07-24 14:00:00", "venue_serial": 1473, "description": "Take the opportunity to network one last time and exchange contact information with one another. Drinks and snacks provided. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35859", + "website_url": "http://oscon.com/2014/sched/35859", "speakers": [], "categories": [ @@ -5072,7 +5072,7 @@ "time_stop": "2014-07-23 09:10:00", "venue_serial": 1525, "description": "Keynote by Piers Cawley, Perl programmer, singer and balloon modeller.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35864", + "website_url": "http://oscon.com/2014/sched/35864", "speakers": [75349], "categories": [ @@ -5090,7 +5090,7 @@ "time_stop": "2014-07-24 13:10:00", "venue_serial": 1525, "description": "Keynote by Paul Fenwick, managing director of Perl Training Australia.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35883", + "website_url": "http://oscon.com/2014/sched/35883", "speakers": [6631], "categories": [ @@ -5108,7 +5108,7 @@ "time_stop": "2014-07-22 09:45:00", "venue_serial": 1525, "description": "Keynote by Wendy Chisholm, Senior Accessibility Strategist and Universal Design Evangelist, Microsoft.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35913", + "website_url": "http://oscon.com/2014/sched/35913", "speakers": [17417], "categories": [ @@ -5126,7 +5126,7 @@ "time_stop": "2014-07-22 09:05:00", "venue_serial": 1525, "description": "Opening remarks by OSCON program chairs, Matthew McCullough, Sarah Novotny and Simon St. Laurent. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35915", + "website_url": "http://oscon.com/2014/sched/35915", "speakers": [74852,76338,3476], "categories": [ @@ -5142,7 +5142,7 @@ "time_stop": "2014-07-23 09:05:00", "venue_serial": 1525, "description": "Wednesday announcements and remarks by OSCON program chairs, Matthew McCullough, Sarah Novotny and Simon St. Laurent. We'll be announcing more keynote speakers here soon.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35950", + "website_url": "http://oscon.com/2014/sched/35950", "speakers": [76338,74852,3476], "categories": [ @@ -5158,7 +5158,7 @@ "time_stop": "2014-07-24 09:05:00", "venue_serial": 1525, "description": "Thursday announcements and remarks by OSCON program chairs, Matthew McCullough, Sarah Novotny and Simon St. Laurent. We'll be announcing more keynote speakers here soon.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35951", + "website_url": "http://oscon.com/2014/sched/35951", "speakers": [74852,76338,3476], "categories": [ @@ -5174,7 +5174,7 @@ "time_stop": "2014-07-22 09:20:00", "venue_serial": 1525, "description": "Kids can start to learn to program at any age; I started at six. All I needed was tools, guidance, and encouragement. Once I got hooked, a whole new world of possibilities opened up for me. I could create my own video games instead of being restricted by the rules of games made by others. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35956", + "website_url": "http://oscon.com/2014/sched/35956", "speakers": [177245], "categories": [ @@ -5192,7 +5192,7 @@ "time_stop": "2014-07-21 12:30:00", "venue_serial": 1462, "description": "OpenUI5 is a powerful web UI library from SAP that has recently entered the open source world. With OpenUI5 you can easily develop enterprise-grade responsive web applications that run on multiple platforms. It is based on many open source libraries. Start from scratch and learn how to build OpenUI5 applications in this tutorial.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35988", + "website_url": "http://oscon.com/2014/sched/35988", "speakers": [173233,104828,170822], "categories": [ @@ -5210,7 +5210,7 @@ "time_stop": "2014-07-22 21:00:00", "venue_serial": 1464, "description": "Open discussion to talk about the methods, tricks and different tools available for copying and replicating data from traditional RDBMS into Hadoop, covering best practices and customer stories.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36005", + "website_url": "http://oscon.com/2014/sched/36005", "speakers": [], "categories": [ @@ -5226,7 +5226,7 @@ "time_stop": "2014-07-22 20:00:00", "venue_serial": 1464, "description": "Do you Hadoop? Or at least interested in stories from people who do?\r\nWe will meet to share our experience and trade tips about working with\r\nthe open source data storage and processing framework - Hadoop.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36014", + "website_url": "http://oscon.com/2014/sched/36014", "speakers": [], "categories": [ @@ -5242,7 +5242,7 @@ "time_stop": "2014-07-20 20:00:00", "venue_serial": 1607, "description": "Come join some of the team from Docker and get your questions answered and your problems resolved!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36065", + "website_url": "http://oscon.com/2014/sched/36065", "speakers": [], "categories": [ @@ -5258,7 +5258,7 @@ "time_stop": "2014-07-22 21:00:00", "venue_serial": 1454, "description": "Caching at scale might be troublesome and sometimes require adapting your usage patterns to really take advantage of the employed solutions.\r\nMoving the business logic to the caching subsystem and allowing horizontal scaling might be a key factor to minimise the impact of introducing a caching layer in your infrastructure.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36174", + "website_url": "http://oscon.com/2014/sched/36174", "speakers": [], "categories": [ @@ -5274,7 +5274,7 @@ "time_stop": "2014-07-21 17:00:00", "venue_serial": 1462, "description": "Building a cloud is one part of the equation. To get work done with a cloud you need a solid ecosystem that goes with it. In this hands-on tutorial we go through some of the tools in the CloudStack ecosystem: Cloudmonkey, Libcloud, Vagrant, Ansible, and Ec2stack. Come ready to learn and use a cloud.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36180", + "website_url": "http://oscon.com/2014/sched/36180", "speakers": [152123], "categories": [ @@ -5292,7 +5292,7 @@ "time_stop": "2014-07-22 10:10:00", "venue_serial": 1525, "description": "Keynote by Will Marshall, CEO of Planet Labs. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36202", + "website_url": "http://oscon.com/2014/sched/36202", "speakers": [164144], "categories": [ @@ -5310,7 +5310,7 @@ "time_stop": "2014-07-20 20:00:00", "venue_serial": 1456, "description": "Meetup for students, mentors, and org admins who have participated or are participating in Google Summer of Code. Also come to learn more about Google Summer of Code if you think you might be interested in participating in a future year!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36203", + "website_url": "http://oscon.com/2014/sched/36203", "speakers": [], "categories": [ @@ -5326,7 +5326,7 @@ "time_stop": "2014-07-24 09:30:00", "venue_serial": 1525, "description": "As more and more atypical devices are internet enabled, operating system providers need to look at the longer term impacts and plan accordingly. How can CE manufactures keep devices up to date and secure over the lifetime of the device. What does it look like when we fail to plan to do so? How can the open source way solve some of these problems.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36257", + "website_url": "http://oscon.com/2014/sched/36257", "speakers": [178139], "categories": [ @@ -5344,7 +5344,7 @@ "time_stop": "2014-07-23 20:00:00", "venue_serial": 1460, "description": "If you use and love the ELK stack (that's Elasticsearch for search & analytics, Logstash for centralized logging & Kibana for beautiful visualizations), join us for an evening of discussion about these three open source tools and how they make developers and sysadmins lives way better. And your business humans, too. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36404", + "website_url": "http://oscon.com/2014/sched/36404", "speakers": [], "categories": [ @@ -5360,7 +5360,7 @@ "time_stop": "2014-07-24 12:45:00", "venue_serial": 1525, "description": "The 10th Annual O\u2019Reilly Open Source Award winners will be announced. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36494", + "website_url": "http://oscon.com/2014/sched/36494", "speakers": [], "categories": [ @@ -5378,7 +5378,7 @@ "time_stop": "2014-07-24 09:20:00", "venue_serial": 1525, "description": "In this presentation Andrew will be live-coding the generative algorithms that will be producing the music that the audience will be listening too. As Andrew is typing he will also attempt to narrate the journey, discussing the various computational and musical choices made along the way. A must see for anyone interested in creative computing.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36818", + "website_url": "http://oscon.com/2014/sched/36818", "speakers": [178738], "categories": [ @@ -5396,7 +5396,7 @@ "time_stop": "2014-07-24 10:40:00", "venue_serial": 1448, "description": "In this talk, Andrew will delve deeper into the technical and philosophical underpinnings of live-coding as an artistic performance practice. Using his own Extempore language as a reference, Andrew will demonstrate, in a very hands on way, how live-coding works in practice, from both an end-user perspective, as well as a systems design perspective. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36819", + "website_url": "http://oscon.com/2014/sched/36819", "speakers": [178738], "categories": [ @@ -5414,7 +5414,7 @@ "time_stop": "2014-07-20 20:00:00", "venue_serial": 1457, "description": "Webmaker is a collection of innovative tools and curricula for a global community that is teaching the web. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36848", + "website_url": "http://oscon.com/2014/sched/36848", "speakers": [], "categories": [ @@ -5430,7 +5430,7 @@ "time_stop": "2014-07-22 20:00:00", "venue_serial": 1461, "description": "Designing for Participation was created in order to get Mozilla community members to think about how they can structure the work Mozilla does to better enable contributions from anywhere. This BOF will lead discussion through a workshop designed by Mozilla to help project leaders Design projects for participation", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36849", + "website_url": "http://oscon.com/2014/sched/36849", "speakers": [], "categories": [ @@ -5446,7 +5446,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1460, "description": "Apache CloudStack enables cloud operators to quickly create scalable clouds with support for multiple hypervisors. Choice is wonderful, but also requires an understanding of how hypervisor features integrate with CloudStack. In this session we'll look at the options and provide a template for deployment success.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36890", + "website_url": "http://oscon.com/2014/sched/36890", "speakers": [141574], "categories": [ @@ -5464,7 +5464,7 @@ "time_stop": "2014-07-24 12:30:00", "venue_serial": 1464, "description": "OSCON belongs to its attendees, and we want to hear what you think of this year\u2019s show. Join the organizers to talk about what you loved and hated about OSCON, and what you\u2019d like to see next year.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36923", + "website_url": "http://oscon.com/2014/sched/36923", "speakers": [10,74852,76338,3476], "categories": [ @@ -5482,7 +5482,7 @@ "time_stop": "2014-07-21 17:00:00", "venue_serial": 1459, "description": "Open Cloud Day at OSCON will cover the latest innovations in public and private clouds, IaaS, and PaaS platforms. You'll learn from industry practitioners from a variety of platforms, who will share their expertise, and provide you with a vision of where open source in the cloud is heading. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36934", + "website_url": "http://oscon.com/2014/sched/36934", "speakers": [], "categories": [ @@ -5502,7 +5502,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1461, "description": "The open source mantra is to release early and release often. That means software velocity can be difficult to keep up with. This discussion will expand on the latest open source software used to deliver and manage cloud computing infrastructure. Topics covered include virtualization (KVM, Xen Project, LXC), orchestration (OpenStack, CloudStack, Eucalyptus), and other complimentary technology.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37002", + "website_url": "http://oscon.com/2014/sched/37002", "speakers": [6653], "categories": [ @@ -5520,7 +5520,7 @@ "time_stop": "2014-07-21 12:00:00", "venue_serial": 1584, "description": "Discuss how OpenStack works towards an interoperable open IaaS using a process we call DefCore. We'll review how we're creating a process that is open, collaborative, technically relevant and principles driven.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37014", + "website_url": "http://oscon.com/2014/sched/37014", "speakers": [122917], "categories": [ @@ -5538,7 +5538,7 @@ "time_stop": "2014-07-21 16:15:00", "venue_serial": 1584, "description": "The talk will present a quantitative analysis of the projects producing the main free, open source software cloud platforms: OpenStack, Apache CloudStack, OpenNebula and Eucalyptus. The analysis will focus on the communities behind those projects, their main development parameters, and the trends that can be observed.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37016", + "website_url": "http://oscon.com/2014/sched/37016", "speakers": [173116], "categories": [ @@ -5556,7 +5556,7 @@ "time_stop": "2014-07-21 16:45:00", "venue_serial": 1584, "description": "When designing and building a private cloud often times the network is not treated as a first class citizen and even sometimes dealt with as an after thought. This presentation will look at the process of building a cloud from the network view. We will look at some best practices for configuring servers and switches in a rack as the basis for the cloud deployment and on going management. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37020", + "website_url": "http://oscon.com/2014/sched/37020", "speakers": [122424], "categories": [ @@ -5574,7 +5574,7 @@ "time_stop": "2014-07-21 14:30:00", "venue_serial": 1584, "description": "The last OpenStack release was the result of the combined work of more than 1200 contributors. How can all those people be coordinated and deliver results on schedule, with no classic management structure ?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37021", + "website_url": "http://oscon.com/2014/sched/37021", "speakers": [109289], "categories": [ @@ -5592,7 +5592,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1461, "description": "This session is aimed at providing an understanding of why, where, and how SAP is engaged in adopting and leading Open Source projects in the enterprise software space. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37030", + "website_url": "http://oscon.com/2014/sched/37030", "speakers": [180019], "categories": [ @@ -5610,7 +5610,7 @@ "time_stop": "2014-07-23 16:00:00", "venue_serial": 1546, "description": "Gwen\u2019s got answers when it comes to Hadoop, R, analytics and more. She\u2019s happy to talk to you about getting started with Hadoop; R, Python and data analysis with Hadoop; architecture, design, and implementation of Hadoop applications; and anything else you\u2019d like to bring up.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37039", + "website_url": "http://oscon.com/2014/sched/37039", "speakers": [126882], "categories": [ @@ -5628,7 +5628,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1546, "description": "If you have questions about community management and leadership, hiring community managers, or managing community relationships, come by and talk to Jono. He\u2019ll discuss building collaborative workflow and tooling, conflict resolution and managing complex personalities, and building buzz and excitement around your community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37042", + "website_url": "http://oscon.com/2014/sched/37042", "speakers": [108813], "categories": [ @@ -5646,7 +5646,7 @@ "time_stop": "2014-07-23 16:00:00", "venue_serial": 1547, "description": "Come by and talk to Stephen about anything related to big data, Hadoop, and creating scalable, high-availability, data and applications solutions\u2014including data pipelines, data platforms, and Fourier analysis. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37043", + "website_url": "http://oscon.com/2014/sched/37043", "speakers": [133624], "categories": [ @@ -5664,7 +5664,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1546, "description": "Garth has found that the topic of open source is a bit controversial when people start talk about applying it to the visual and experience design process. He\u2019d like to talk with open source contributors about how the design process could be worked into specific projects, and the proposal (as well its rebuttals) that design should be done in the open.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37044", + "website_url": "http://oscon.com/2014/sched/37044", "speakers": [173248], "categories": [ @@ -5682,7 +5682,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1546, "description": "Constantine will be more than happy to discuss Node.js or anything else related to programming, software architecture, web apps, and other topics. Let\u2019s have some fun! Dive into Node.js programming and design patterns, event loops and high concurrency apps, reactive apps, and the Meteor framework.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37045", + "website_url": "http://oscon.com/2014/sched/37045", "speakers": [141235], "categories": [ @@ -5700,7 +5700,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1546, "description": "Can computers tell if trains run on time? Harrison and Stephen are ready to discuss this topic and other matters related to the Internet of Things and data\u2014including data pipelines, data platforms, and Fourier analysis.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37046", + "website_url": "http://oscon.com/2014/sched/37046", "speakers": [171621,133624], "categories": [ @@ -5718,7 +5718,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1546, "description": "Jerome has worked with Docker since its inception, and before that, built the dotCloud PAAS. He\u2019ll be glad to share his experiences in those domains, and discuss everything about Docker and containers, including how to make containers secure; service discovery, network integration, scaling, and failover; and containers for desktop applications.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37047", + "website_url": "http://oscon.com/2014/sched/37047", "speakers": [151611], "categories": [ @@ -5736,7 +5736,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1546, "description": "The Adopt-a-JSR and Adopt OpenJDK programs have gained worldwide community participation in the past two years. Come discuss the details with Heather and find out how you can contribute to better, more practical standards. She\u2019ll talk about upcoming changes aimed at broadening participation in the standards process. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37048", + "website_url": "http://oscon.com/2014/sched/37048", "speakers": [65329], "categories": [ @@ -5754,7 +5754,7 @@ "time_stop": "2014-07-22 16:00:00", "venue_serial": 1546, "description": "Kara is always eager to swap user group tips with you. Come by and pick her brain about ways to remove common obstacles that user groups encounter, and how to craft welcoming, friendly meetings and events. Find out how to plan the right events for your community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37049", + "website_url": "http://oscon.com/2014/sched/37049", "speakers": [142767], "categories": [ @@ -5772,7 +5772,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1546, "description": "Come by and talk to Michael about single-page web applications, static site generators, and static content hosting. He\u2019s also willing to tackle other issues such as CORS web services, browser technology, and web components.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37051", + "website_url": "http://oscon.com/2014/sched/37051", "speakers": [2593], "categories": [ @@ -5790,7 +5790,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1547, "description": "Graph Databases take a different approach than relational and aggregate-oriented NoSQL databases. They make connections cheap and fast by making them an explicit part and first-level citizen of your database. Michael will discuss graph-enabled applications, answer questions about their pros and cons, and help you model your domain and use-cases in an interactive way. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37052", + "website_url": "http://oscon.com/2014/sched/37052", "speakers": [159719], "categories": [ @@ -5808,7 +5808,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1548, "description": "If you\u2019re ready to learn about Docker for building, shipping, and running distributed applications, James is ready to talk Docker with you. You\u2019ll not only find out how to get started, but also learn about Docker use cases, the Docker roadmap, and how to integrate Docker with Puppet, Chef, SaltStack, or Ansible.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37053", + "website_url": "http://oscon.com/2014/sched/37053", "speakers": [5060], "categories": [ @@ -5826,7 +5826,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1546, "description": "You want to get more women involved in open source projects and communities. And Catherine and Corinne want to help. Come by find out how you can organize communities that are welcoming to women, and how you can be an ally to underrepresented minorities in tech. Start or help a Girl Develop It chapter, and learn about other nonprofits you can and should partner with both nationally and locally.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37054", + "website_url": "http://oscon.com/2014/sched/37054", "speakers": [169992,173025], "categories": [ @@ -5844,7 +5844,7 @@ "time_stop": "2014-07-21 15:45:00", "venue_serial": 1584, "description": "In the range of API and protocol development from free-form to totally static, there is room in open clouds for patterns that allow both for dynamic discovery and predictable reuse. This talk highlights open cloud standards-based methods that have stood up to testing in the National Science Foundation's Cloud and Autonomic Computing Center Standards Testing Lab under real-world conditions.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37055", + "website_url": "http://oscon.com/2014/sched/37055", "speakers": [180050], "categories": [ @@ -5862,7 +5862,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1579, "description": "Shadaj is a 14 year old student who loves to program. He\u2019s happy to chat about anything from Scala programming to technology in schools today. He\u2019ll answer your questions about kids programming, including how to get started and what tools are available. And he\u2019ll engage you on topics such bioinformatics algorithms, Android, web development, and game programming.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37056", + "website_url": "http://oscon.com/2014/sched/37056", "speakers": [177245], "categories": [ @@ -5880,7 +5880,7 @@ "time_stop": "2014-07-21 14:00:00", "venue_serial": 1584, "description": "'The Cloud' was built on Open Source. In no way does anything resembling IaaS 'cloud' exist without the foundation of freely available open source operating systems and virtualization, but what does 'Open' mean to the user of a service? Does it matter if the code behind a service is open? How would one even know? Do other aspects and definitions of openness become more important than source code?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37058", + "website_url": "http://oscon.com/2014/sched/37058", "speakers": [24052], "categories": [ @@ -5898,7 +5898,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1547, "description": "Whether the testing goat is steering you right or wrong, you\u2019ll want to join Harry for an informal discussion of testing and TDD. Find out how to get started with testing and how to get the most value from it. Learn the relative merits of pure isolated unit tests, integrated tests, and functional tests. And explore how to adapt your approach for different project types.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37059", + "website_url": "http://oscon.com/2014/sched/37059", "speakers": [180056], "categories": [ @@ -5916,7 +5916,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1546, "description": "Tap Florian\u2019s brain about all things OpenStack, and take advantage of his broad experience in real-life production OpenStack deployments. Whether it\u2019s OpenStack strategy, organizational concerns, or getting down into the nitty-gritty aspects of OpenStack technology, Florian is happy to talk to you about it. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37061", + "website_url": "http://oscon.com/2014/sched/37061", "speakers": [131884], "categories": [ @@ -5934,7 +5934,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1547, "description": "Come by and have an informal chat with Jesse about MongoDB and Python-related topics, such as Python\u2019s async frameworks, using MongoDB with Python, and MongoDB in general.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37063", + "website_url": "http://oscon.com/2014/sched/37063", "speakers": [172536], "categories": [ @@ -5952,7 +5952,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1546, "description": "Why would a Java developer want to learn the Go programming language? Matt is ready to have an informal chat with you about it. Find out how the idiomatic use of Go interfaces encourage you to structure your programs differently (as opposed to Java\u2019s object-oriented constructs), and how Go\u2019s built-in concurrency features make writing concurrent software accessible to mere mortals. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37066", + "website_url": "http://oscon.com/2014/sched/37066", "speakers": [173088], "categories": [ @@ -5970,7 +5970,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1547, "description": "Ann is ready to talk SWI-Prolog with you\u2014and help you get it installed. She\u2019ll chat with you about your planned uses for Prolog, talk you out of the \u201cwrap my rules engine in a real language\u201d mentality, and help you get through the \u201chey, 2+2 = 4 fails, wtf?\u201d stage. And she\u2019ll laugh evilly when you mention Tomcat.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37067", + "website_url": "http://oscon.com/2014/sched/37067", "speakers": [172986], "categories": [ @@ -5988,7 +5988,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1548, "description": "Chef questions? Bring any and all directly to Matt. He\u2019s ready to discuss how Chef is used to manage the deployment of OpenStack as well as the infrastructure on top of OpenStack, and how to get involved with the Chef and OpenStack community. Beyond that, the sky\u2019s the limit.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37068", + "website_url": "http://oscon.com/2014/sched/37068", "speakers": [141169], "categories": [ @@ -6006,7 +6006,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1547, "description": "If there\u2019s anything you want to know about Spring Boot or the Spring Framework in general, Phil is ready to help. He\u2019ll talk to you about an array of issues, such as developing micro-services with Spring, how Pivotal developed the new \u201cspring.io\u201d website, and how to become a full-stack Java developer.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37069", + "website_url": "http://oscon.com/2014/sched/37069", "speakers": [171565], "categories": [ @@ -6024,7 +6024,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1547, "description": "Reactive applications are taking over the enterprise world. Join Jamie to chat about various Reactive topics. He\u2019ll answer questions including: How well do various technologies support the core Reactive tenets of event-driven, scalable, fault tolerant and responsive? How do you use open source technologies to deploy an elastically Reactive application? What is the role of big data in Reactive?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37071", + "website_url": "http://oscon.com/2014/sched/37071", "speakers": [170293], "categories": [ @@ -6042,7 +6042,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1580, "description": "Scott\u2019s here to answer your questions about data and how to successfully conveying meaning through well-designed graphics. He\u2019ll chat with you about the data visualization design process; related technologies, including d3.js and Processing; and how we can all benefit by open-sourcing our processes. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37073", + "website_url": "http://oscon.com/2014/sched/37073", "speakers": [147840], "categories": [ @@ -6060,7 +6060,7 @@ "time_stop": "2014-07-23 16:00:00", "venue_serial": 1548, "description": "If you\u2019ve got questions about Go, you\u2019re in luck. Francesc will answer questions about Go basics, programming, organizing your code, and more. Find out about Go concurrency and concurrency primitives, and how Go interfaces simplify your code and break fictitious dependencies.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37075", + "website_url": "http://oscon.com/2014/sched/37075", "speakers": [155088], "categories": [ @@ -6078,7 +6078,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1547, "description": "If you want your APIs to amaze and delight your developer partners, bring Kristen all of your API design questions. She\u2019ll chat with you about the API design process (how to plan and create an API that will be successful), developer engagement and support (aka the care and feeding of your developer partners), and how you can help your customers learn and troubleshoot your API.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37077", + "website_url": "http://oscon.com/2014/sched/37077", "speakers": [4265], "categories": [ @@ -6096,7 +6096,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1548, "description": "If you\u2019ve ever toyed with the idea of contributing to Firefox, Benjamin would love to talk to you about ways to make it happen. Find out why Firefox OS matters, how Mozilla builds community, and how you can contribute to Firefox.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37078", + "website_url": "http://oscon.com/2014/sched/37078", "speakers": [120025], "categories": [ @@ -6114,7 +6114,7 @@ "time_stop": "2014-07-22 08:15:00", "venue_serial": 1574, "description": "Programmers do a lot of sitting, so come refresh your body, mind, and spirit before you head into the day\u2019s sessions. This will be an easy beginner\u2019s yoga session \u2013 so don\u2019t be shy about coming out even if this will be your first yoga experience. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37085", + "website_url": "http://oscon.com/2014/sched/37085", "speakers": [], "categories": [ @@ -6132,7 +6132,7 @@ "time_stop": "2014-07-23 08:15:00", "venue_serial": 1574, "description": "Programmers do a lot of sitting, so come refresh your body, mind, and spirit before you head into the day\u2019s sessions. This will be an easy beginner\u2019s yoga session \u2013 so don\u2019t be shy about coming out even if this will be your first yoga experience. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37086", + "website_url": "http://oscon.com/2014/sched/37086", "speakers": [], "categories": [ @@ -6150,7 +6150,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1548, "description": "Git can be frustrating and opaque. Don\u2019t worry, it\u2019s not you. It\u2019s Git. Come ask questions about making Git work for you instead of feeling stuck in a detached HEAD state. Emma Jane will talk about upgrading from a centralized system to a distributed workflow and improving team efficiencies with the right branching strategy. She\u2019ll also provide tips for teaching anyone how to use Git.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37087", + "website_url": "http://oscon.com/2014/sched/37087", "speakers": [4146], "categories": [ @@ -6168,7 +6168,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1548, "description": "Need some advice on your next great open source project? Ken can steer you in the right direction. Talk to him about the benefits of the Eclipse Foundation as a home for your project, and discuss what prevents you from trying out cloud IDEs. Ken will also point out which open source components from Orion\u2019s JavaScript libraries you can leverage in your own project. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37090", + "website_url": "http://oscon.com/2014/sched/37090", "speakers": [39928], "categories": [ @@ -6186,7 +6186,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1579, "description": "If you\u2019re looking for answers about OpenStack, John is ready to field your questions. He\u2019ll chat with you about block storage in OpenStack with Cinder, core OpenStack features and functionality, and general queries about OpenStack in general.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37091", + "website_url": "http://oscon.com/2014/sched/37091", "speakers": [171381], "categories": [ @@ -6204,7 +6204,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1548, "description": "The professor is in. Perl\u2019s own Dr. Evil emerges from his secret lair to talk about Perl 6, Perl 5, Vim, and presentation skills. Damian knows a lot about all of the above as the widely sought-after speaker who runs Thoughtstream, an international provider of programmer training. He devotes much of his spare time to Larry Wall and the design of Perl 6.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37095", + "website_url": "http://oscon.com/2014/sched/37095", "speakers": [4710], "categories": [ @@ -6222,7 +6222,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1579, "description": "Organizer of the Sunshine PHP Developer Conference, Adam is ready to hold an open discussion on efficient code refactoring. Find out when to refactor, the key indicators for triggering a refactor, how to get started with code refactoring, and anything else you think is related.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37097", + "website_url": "http://oscon.com/2014/sched/37097", "speakers": [169862], "categories": [ @@ -6240,7 +6240,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1547, "description": ".", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37098", + "website_url": "http://oscon.com/2014/sched/37098", "speakers": [146540], "categories": [ @@ -6258,7 +6258,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1548, "description": "Luciano is busy at OSCON with two sessions and a tutorial, but he\u2019s happy to answer any questions you may have about those talks: \u201cIntroduction to Python Metaprogramming\u201d, \u201cIdiomatic APIs with the Python Data Model\u201d, and \u201cPython: Encapsulation with Descriptors\u201d. The common theme for all three is how to use the special methods defined in the Python Data Model. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37099", + "website_url": "http://oscon.com/2014/sched/37099", "speakers": [150170], "categories": [ @@ -6276,7 +6276,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1579, "description": "Join Jess to talk about useful debugging tools, PHP-based apps, and some of her other favorite open source topics. She\u2019ll hold forth on managing open source projects as a commercial company, deploying and maintaining large-scale video applications in the cloud, and open source development in general.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37101", + "website_url": "http://oscon.com/2014/sched/37101", "speakers": [171078], "categories": [ @@ -6294,7 +6294,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1547, "description": ".", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37105", + "website_url": "http://oscon.com/2014/sched/37105", "speakers": [171147], "categories": [ @@ -6312,7 +6312,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1579, "description": "If you have questions about using applied improvisation for better communication, idea generation, and decision making in your company or team, come to Wade and Andrew. They\u2019ll talk to you about growing a distributed engineering team, and building and maintaining culture in a distributed organization. They\u2019ll also share tools and tips for improving communication.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37108", + "website_url": "http://oscon.com/2014/sched/37108", "speakers": [4378,106355], "categories": [ @@ -6330,7 +6330,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1579, "description": "Do you have questions on Apache Cassandra? Bring any and all of them to Tim during Office Hours. He\u2019s also ready to chat about technical training, the technologies formerly known as Big Data, and any other topics of mutual interest.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37114", + "website_url": "http://oscon.com/2014/sched/37114", "speakers": [137697], "categories": [ @@ -6348,7 +6348,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1548, "description": "Joshua is available to talk to you about any of his favorite topics, such as HTML Canvas, Bluetooth Low Energy, wearable computing, and the Internet of Things. And he\u2019s happy just to rant about Java.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37122", + "website_url": "http://oscon.com/2014/sched/37122", "speakers": [6931], "categories": [ @@ -6366,7 +6366,7 @@ "time_stop": "2014-07-22 16:00:00", "venue_serial": 1547, "description": "Now that it is possible for startups to build products as powerful and game changing as Nest\u2014without millions of dollars\u2014how can you get in on the action? Zach Supalla from the Spark Labs startup accelerator will share details and answer questions about developing an IoT product, building a business on open source hardware, and growing from a Kickstarter campaign to a business.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37129", + "website_url": "http://oscon.com/2014/sched/37129", "speakers": [175040], "categories": [ @@ -6384,7 +6384,7 @@ "time_stop": "2014-07-20 20:00:00", "venue_serial": 1458, "description": "Explore thoughts and ideas around public health and open data.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37134", + "website_url": "http://oscon.com/2014/sched/37134", "speakers": [], "categories": [ @@ -6400,7 +6400,7 @@ "time_stop": "2014-07-22 16:00:00", "venue_serial": 1548, "description": "Josh Berkus is happy to chat with you about PostgreSQL high-availability and scale-out techniques, especially on cloud hosting. He\u2019ll discuss things like connection pooling and load-balancing tools, automated failover, RDS vs. roll-your-own on AWS, and scaling out multi-tenant apps.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37144", + "website_url": "http://oscon.com/2014/sched/37144", "speakers": [3397], "categories": [ @@ -6418,7 +6418,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1450, "description": "Scott Chacon, co-founder of GitHub, and Jay Borenstein, CS professor at Stanford and founder of Facebook's Open Academy, a program designed to match university students with open source projects for academic credit, will discuss how to bring the best of the open source community's learning frameworks into formal computer science education. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37148", + "website_url": "http://oscon.com/2014/sched/37148", "speakers": [837,180268], "categories": [ @@ -6436,7 +6436,7 @@ "time_stop": "2014-07-23 22:00:00", "venue_serial": 1578, "description": "Join Mandrill for an OSCON Party at the Jupiter Hotel!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37153", + "website_url": "http://oscon.com/2014/sched/37153", "speakers": [], "categories": [ @@ -6454,7 +6454,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1547, "description": "Andrei will be happy to answer questions about the \u201cmove fast\u201d side of Facebook, including large-scale design, the D programming language, and Facebook\u2019s software engineering culture.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37173", + "website_url": "http://oscon.com/2014/sched/37173", "speakers": [109270], "categories": [ @@ -6472,7 +6472,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1579, "description": "In his talk (\u201cErlang, LFE, Joxa and Elixir: Established and Emerging Languages in the Erlang Ecosystem\u201d), Brian talks about the differences between these BEAM languages, and how each one is applicable to a distinct engineering task. During Office Hours, he\u2019ll tell you how to get started with each of these languages, and talk about the domains in which they succeed and fail.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37201", + "website_url": "http://oscon.com/2014/sched/37201", "speakers": [172990], "categories": [ @@ -6490,7 +6490,7 @@ "time_stop": "2014-07-22 20:00:00", "venue_serial": 1456, "description": "Deploying workloads in the cloud can be challenging, in this BoF we'll discuss how to use Ubuntu and Juju to get up and running in the cloud quickly ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37253", + "website_url": "http://oscon.com/2014/sched/37253", "speakers": [], "categories": [ @@ -6506,7 +6506,7 @@ "time_stop": "2014-07-23 16:00:00", "venue_serial": 1579, "description": "Now that you\u2019re familiar with cloud computing, along comes the next new thing: Fog computing. Michael will tell you what exactly Fog computing is, how it evolved from cloud compute, and where it\u2019s going. He\u2019ll also talk about network challenges in cloud computing with the Internet of Things, as well as IoT open source projects.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37256", + "website_url": "http://oscon.com/2014/sched/37256", "speakers": [172370], "categories": [ @@ -6524,7 +6524,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1461, "description": "Software Collections is a new way to run newer packages on Enterprise Linux (RHEL/CentOS) such as Python, Ruby, PHP, MySQL/MariaDB, and others. Learn how this enables us to use Perl 5.16 and PostgreSQL 9.2 alongside distribution-provided versions (on EL6, Perl 5.10, and PostgreSQL 8.4.). Also learn how we've extended the Perl 5.16 collection with additional packages.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37259", + "website_url": "http://oscon.com/2014/sched/37259", "speakers": [180437], "categories": [ @@ -6542,7 +6542,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1580, "description": "Wondering how patents and copyright and trademark laws work with free and open source software projects? Deb is ready to share her knowledge. Find out if legislation and recent court decisions about patent trolls will keep you from being sued, and how the landscape likely to change. None of this is legal advice, just good solid background! ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37292", + "website_url": "http://oscon.com/2014/sched/37292", "speakers": [130731], "categories": [ @@ -6560,7 +6560,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1548, "description": "Gian and Fangjin will talk to you about real-time analytics with open source technologies, including the motivation for the Kafka, Storm, Hadoop, and Druid real-time analytics stack. They\u2019ll discuss implementation details (if you\u2019re interested in trying the stack out at home) and show you how to scale the stack and make it highly available. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37306", + "website_url": "http://oscon.com/2014/sched/37306", "speakers": [153566,153565], "categories": [ @@ -6578,7 +6578,7 @@ "time_stop": "2014-07-22 20:00:00", "venue_serial": 1465, "description": "In this session, you will discover a new Open Source Personal Social Media Aggregation system called acilos. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37310", + "website_url": "http://oscon.com/2014/sched/37310", "speakers": [], "categories": [ @@ -6594,7 +6594,7 @@ "time_stop": "2014-07-22 20:00:00", "venue_serial": 1463, "description": "There is a debate on the relevance of Open Standards when faced with Open Source efforts. Open Source is an industry force, yet government bodies and others still rely on and give preference to ANSI and ISO standards. Standards representatives from across the industry will be on hand to discuss possible paths forward that are complementary rather than competitive.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37311", + "website_url": "http://oscon.com/2014/sched/37311", "speakers": [], "categories": [ @@ -6610,7 +6610,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1579, "description": "Spencer and William are ready to talk about all things Puppet related. They\u2019ll answer questions about module best practices and testing, using Hiera to manage data, and interfacing with the community. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37316", + "website_url": "http://oscon.com/2014/sched/37316", "speakers": [125113,99817], "categories": [ @@ -6628,7 +6628,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1579, "description": "If you want to explore that thin line where hardware meets software, stop by and chat with Drasko. He\u2019ll talk to you about electronic prototyping of connected objects for the Internet of Things from a designer\u2019s point of view; discuss open source HW schematic modifications, component sourcing, and production; and weigh in on monitoring, data harvesting, remote control, sensors, and actuators.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37338", + "website_url": "http://oscon.com/2014/sched/37338", "speakers": [173105], "categories": [ @@ -6646,7 +6646,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1580, "description": "When it comes to hacking micro-controller and nano-computing projects, Rob is your man. He\u2019s ready to discuss all your hacker questions, such as the latest micro-controller trends, the hacker cross-pollination with artists, scientists, engineers, students, and suits, and\u2014of course\u2014how to get started hacking and making.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37345", + "website_url": "http://oscon.com/2014/sched/37345", "speakers": [77350], "categories": [ @@ -6664,7 +6664,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1580, "description": "Talk to Yoav and Baruch about all things CI/CD, such as choosing the right build tools, and automating your build, deployment, and distribution process. They\u2019ll discuss the issues of moving to a cloud-based development stack, how to speed up development by dealing with bottlenecks in your CI/CD flow, and distributing software releases all the way to end users.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37363", + "website_url": "http://oscon.com/2014/sched/37363", "speakers": [116050,114822], "categories": [ @@ -6682,7 +6682,7 @@ "time_stop": "2014-07-23 16:00:00", "venue_serial": 1580, "description": "Learn more about getdns for accessing DNS security and other powerful features. Targeted to application developers, getdns provides both basic and advanced DNS capabilities, while requiring little DNS expertise. Glen, Neel, Willem, and Allison will explain how to leverage DNS to create authenticated services with getdns modes. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37401", + "website_url": "http://oscon.com/2014/sched/37401", "speakers": [173324,172895,173325,173326], "categories": [ @@ -6700,7 +6700,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1580, "description": "Doris is ready to answer any questions you have about developing high performance websites and apps with JavaScript and HTML5. She\u2019ll share tips and tricks for tackling real-world web platform performance problems, including network requests, speed and responsiveness, and optimizing media usage.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37405", + "website_url": "http://oscon.com/2014/sched/37405", "speakers": [133360], "categories": [ @@ -6718,7 +6718,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1579, "description": "Want to learn the intricacies of the money machine and where and when you can hack it? Boyd can help. Find out why staying in total control of your money machine/cash contraption can rival accepting venture capital and angel funding when creating a business. And learn how the Lean Business Model can open up unique opportunities for money machines. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37408", + "website_url": "http://oscon.com/2014/sched/37408", "speakers": [138530], "categories": [ @@ -6736,7 +6736,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1580, "description": "If you want to find out more about machine learning, Benjamin is available to discuss several techniques, especially those available as Ruby gems. He\u2019ll talk to you about machine learning with Ruby, scaling Rails with PostgreSQL, and other questions you have on your mind. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37409", + "website_url": "http://oscon.com/2014/sched/37409", "speakers": [173396], "categories": [ @@ -6754,7 +6754,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1580, "description": "In his ongoing campaign to demystify SELinux, David is ready to tackle any questions you have\u2014including what it was like to work on the SELinux team at the NSA. He\u2019ll gladly talk to you about SELinux Internals and anything he covered (or didn\u2019t cover) in his tutorial, \u201cDemystifying SELinux Part II: Who\u2019s Policy Is It Anyway?\u201d", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37410", + "website_url": "http://oscon.com/2014/sched/37410", "speakers": [151833], "categories": [ @@ -6772,7 +6772,7 @@ "time_stop": "2014-07-22 16:00:00", "venue_serial": 1579, "description": "Niklas, Connor, and Adam are ready to talk to you about the Apache Mesos ecosystem for combining your datacenter servers and cloud instances into one shared pool. Find out how to take control of the datacenter with multi-tenancy and fault-tolerance, how to deploy and manage distributed applications in many languages, and how to migrate existing applications to Mesos using Marathon and Chronos.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37411", + "website_url": "http://oscon.com/2014/sched/37411", "speakers": [171598,172973,172898], "categories": [ @@ -6790,7 +6790,7 @@ "time_stop": "2014-07-23 15:10:00", "venue_serial": 1548, "description": "Learn how Kelley evolved from frustrated retail worker to Linux kernel developer through the Gnome Outreach Program for Women. She\u2019ll talk about the personal ways that outreach and mentoring affect new contributors\u2014and how important introspection and self-knowledge are in the process. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37412", + "website_url": "http://oscon.com/2014/sched/37412", "speakers": [140811], "categories": [ @@ -6808,7 +6808,7 @@ "time_stop": "2014-07-22 20:30:00", "venue_serial": 1583, "description": "Celebrate OpenStack's Birthday\r\n\r\n ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37476", + "website_url": "http://oscon.com/2014/sched/37476", "speakers": [], "categories": [ @@ -6826,7 +6826,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1460, "description": "With current best practices for mobile development, you can create great enterprise applications faster, iterate more often, and future-proof against a fast-changing mobile OS and hardware landscape. This session will look at key considerations for developers building enterprise apps for any device and OS.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37488", + "website_url": "http://oscon.com/2014/sched/37488", "speakers": [182198], "categories": [ @@ -6844,7 +6844,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1580, "description": "Looking to get started with Scala? Jason is on hand to answer questions about this object-functional programming language. Find out how Netflix is using Scala for rapid API development, and why Scala may be useful to you. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37566", + "website_url": "http://oscon.com/2014/sched/37566", "speakers": [171226], "categories": [ @@ -6862,7 +6862,7 @@ "time_stop": "2014-07-22 16:00:00", "venue_serial": 1580, "description": "Stop by and chat with Brian about using the Aerospike database in real-time big data-driven applications. He\u2019ll talk to you about subjects including low latency, high throughput applications and cacheless architectures, what does and what doesn\u2019t work when using flash as a storage option, and big data management with Hadoop, Storm, Spark, and NoSQL.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37567", + "website_url": "http://oscon.com/2014/sched/37567", "speakers": [148534], "categories": [ @@ -6880,7 +6880,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1580, "description": "Think you have the right use case to adopt Erlang or Elixir? Concerned about selling this new technology to management and operations? The Erlang Solutions crew will explain the advantages of the Erlang stack\u2014including when and when not to use it. Find out how to run an Erlang project, support and maintain Erlang clusters, and learn how to introduce Erlang to your organization.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37578", + "website_url": "http://oscon.com/2014/sched/37578", "speakers": [10595,174073,173421], "categories": [ @@ -6898,7 +6898,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1463, "description": "New Relic believes in the principals of open source. So much so, that we built the New Relic Platform according those principals: by making the SDKs and our plugins open source, we ignited a rapidly growing and rich community of plugin authors. Come hear about our open source strategy, efforts in building community, and see how easy it is to participate.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37581", + "website_url": "http://oscon.com/2014/sched/37581", "speakers": [77939], "categories": [ @@ -6916,7 +6916,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1460, "description": "CGI::Ex::App is a lightweight, high performance framework that has been quietly driving million-dollar websites since 2004. Come see why this application framework might be the perfect fit for you.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37583", + "website_url": "http://oscon.com/2014/sched/37583", "speakers": [137347], "categories": [ @@ -6934,7 +6934,7 @@ "time_stop": "2014-07-21 09:15:00", "venue_serial": 1584, "description": "Sarah Novotny, OSCON Program Chair, technical evangelist and community manager for NGINX will kick off Open Cloud Day. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37584", + "website_url": "http://oscon.com/2014/sched/37584", "speakers": [76338], "categories": [ @@ -6952,7 +6952,7 @@ "time_stop": "2014-07-21 09:45:00", "venue_serial": 1584, "description": "Whether you have one or a million visitors accessing your web app, they are all going to demand a great user experience regardless of what it takes for you to deliver it. This invariably means quick page loads and fast response times every single time.I am about different ways to start scaling your application with the new 'Cloud' technology.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37585", + "website_url": "http://oscon.com/2014/sched/37585", "speakers": [142320], "categories": [ @@ -6970,7 +6970,7 @@ "time_stop": "2014-07-21 11:30:00", "venue_serial": 1584, "description": "The success of development and deployment of anything is often unrelated to the technical superiority of a cloud platform but a product of what the platform enables you to do. We need robust but more importantly enjoyable to use APIs at every stage in an application's lifecycle. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37586", + "website_url": "http://oscon.com/2014/sched/37586", "speakers": [161475], "categories": [ @@ -6988,7 +6988,7 @@ "time_stop": "2014-07-23 20:00:00", "venue_serial": 1454, "description": "Full stack is becoming a popular developer specialization for web and mobile application development. We'll be talking about the role of the full stack developer using open source platforms for building recommendation-based apps. We'll walk through full stack development on node.js, AngularJS, and Neo4j graph database.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37588", + "website_url": "http://oscon.com/2014/sched/37588", "speakers": [], "categories": [ @@ -7004,7 +7004,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1580, "description": "Ready to write your components in the language of your choice, and put those components wherever you want on your network? Then come talk to Steve and Katie about the event-driven application framework Vert.X. They\u2019ll show you what the framework looks like and how you can get started with it.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37589", + "website_url": "http://oscon.com/2014/sched/37589", "speakers": [142320,175488], "categories": [ @@ -7022,7 +7022,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1461, "description": "Atom is an open-source desktop text editor built with web technology. This talk will be a deep, technical exploration of how Atom manipulates and renders text. Nathan will share lessons we\u2019ve learned about efficiently rendering text via the DOM, and then explore the key components involved in Atom\u2019s text editing system and how the concepts they model are surfaced in the API.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37593", + "website_url": "http://oscon.com/2014/sched/37593", "speakers": [2732], "categories": [ @@ -7040,7 +7040,7 @@ "time_stop": "2014-07-20 20:00:00", "venue_serial": 1471, "description": "Cloud Foundry is an open source Platform as a Service (PaaS) that features a range of components which provide a faster and easier way to build, test, deploy and scale applications. We will go through the most important elements and capabilities that make Cloud Foundry a first class citizen PaaS.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37603", + "website_url": "http://oscon.com/2014/sched/37603", "speakers": [], "categories": [ @@ -7056,7 +7056,7 @@ "time_stop": "2014-07-21 11:00:00", "venue_serial": 1584, "description": "Located outside of F150 in the Lobby Foyer.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37604", + "website_url": "http://oscon.com/2014/sched/37604", "speakers": [], "categories": [ @@ -7072,7 +7072,7 @@ "time_stop": "2014-07-21 15:15:00", "venue_serial": 1584, "description": "Located outside of F150 in the Lobby Foyer.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37605", + "website_url": "http://oscon.com/2014/sched/37605", "speakers": [], "categories": [ @@ -7088,7 +7088,7 @@ "time_stop": "2014-07-21 13:30:00", "venue_serial": 1584, "description": "Located in Exhibit Hall E. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37606", + "website_url": "http://oscon.com/2014/sched/37606", "speakers": [], "categories": [ @@ -7104,7 +7104,7 @@ "time_stop": "2014-07-21 10:15:00", "venue_serial": 1584, "description": "Clouds - they started as nice tools, became a favorite of\r\nstartups and Web 2.0 companies. But the enterprise, why hasn't cloud\r\ncomputing dominated in the enterprise already? Well, it's complicated...", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37622", + "website_url": "http://oscon.com/2014/sched/37622", "speakers": [141881], "categories": [ @@ -7122,7 +7122,7 @@ "time_stop": "2014-07-22 21:00:00", "venue_serial": 1457, "description": "We will discuss the advantages and disadvantages of using SQL over NoSQL datastores, optimal solutions to the Blob problems, what \u201creal-time\u201d really means, dealing with sharding, spatial data types and the expected throughputs of such systems and more. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37623", + "website_url": "http://oscon.com/2014/sched/37623", "speakers": [], "categories": [ @@ -7138,7 +7138,7 @@ "time_stop": "2014-07-20 20:00:00", "venue_serial": 1453, "description": "Lets discuss how to iterate on APIs with our users, using their feedback to help us get the API right from the start. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37624", + "website_url": "http://oscon.com/2014/sched/37624", "speakers": [], "categories": [ @@ -7154,7 +7154,7 @@ "time_stop": "2014-07-21 15:00:00", "venue_serial": 1584, "description": "Moving applications from a traditional data center to a cloud\r\nprovides immediate and real advantages in the way services are delivered\r\nto customers. What if you could also easily move from one cloud to\r\nanother? ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37625", + "website_url": "http://oscon.com/2014/sched/37625", "speakers": [181484], "categories": [ @@ -7172,7 +7172,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1460, "description": "Tizen is aimed at various profiles, not only mobile. The UI must be scalable and themeable to support these diverse profiles. This presentation will share the technology behind the scalable and themeable Tizen UI which is called EFL (Enlightenment Foundation Libraries). This will reduce development time tremendously to support multiple products and applications.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37626", + "website_url": "http://oscon.com/2014/sched/37626", "speakers": [181502], "categories": [ @@ -7190,7 +7190,7 @@ "time_stop": "2014-07-22 11:20:00", "venue_serial": 1460, "description": "This talk includes an introduction to IoT and background, lessons learned from standard specification and its limitation, reason for open source in IoT, Samsung's efforts on IoT open source, and the future of IoT.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37627", + "website_url": "http://oscon.com/2014/sched/37627", "speakers": [172630], "categories": [ @@ -7208,7 +7208,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1460, "description": "The industry needs cloud solutions built on an open, extensible architecture that delivers consistent access to infrastructure, runtimes, and application resources. As customers continue to adopt cloud service-based solutions, they need to avoid vendor lock-in, simplify building of complex cloud environments, and quickly develop cloud-ready applications that drive massively scalable cloud models.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37629", + "website_url": "http://oscon.com/2014/sched/37629", "speakers": [181598], "categories": [ @@ -7226,7 +7226,7 @@ "time_stop": "2014-07-23 12:10:00", "venue_serial": 1463, "description": "Enterprise developers want flexible, open architectures to develop cloud-native applications and bring new ideas to market faster. Yet enterprise IT needs to quickly deliver services, applications, and infrastructures in a consistent, secure, repeatable manner. How do you get the agility and flexibility while maintaining control?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37631", + "website_url": "http://oscon.com/2014/sched/37631", "speakers": [26426], "categories": [ @@ -7244,7 +7244,7 @@ "time_stop": "2014-07-24 09:50:00", "venue_serial": 1525, "description": "You may not feel like you\u2019re a \u201ccreative person,\u201d but never underestimate where your code could turn up or what stories it might tell. The most unassuming repo can be remixed into something magnificent. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37646", + "website_url": "http://oscon.com/2014/sched/37646", "speakers": [143674], "categories": [ @@ -7262,7 +7262,7 @@ "time_stop": "2014-07-22 21:00:00", "venue_serial": 1460, "description": "Meet to Learn, Define and Deliver Winning Products that Shape Tomorrow\r\n\r\nBillion of genealogical records available to read and write through open restful APIs. Learn what's available, how to use them, and get profiles of many application opportunities.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37647", + "website_url": "http://oscon.com/2014/sched/37647", "speakers": [], "categories": [ @@ -7278,7 +7278,7 @@ "time_stop": "2014-07-23 20:00:00", "venue_serial": 1456, "description": "RootsDev has created a full function Javascript API that can be used to authenticate and read historical person data with in 30 minutes. Learn how and why this SDK was built and what open source apps are already talking to it.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37648", + "website_url": "http://oscon.com/2014/sched/37648", "speakers": [], "categories": [ @@ -7294,7 +7294,7 @@ "time_stop": "2014-07-22 16:30:00", "venue_serial": 1597, "description": "Author book signings will be held in the O'Reilly Authors' booth on Tuesday and Wednesday. This is a great opportunity for you to meet O'Reilly authors and to get a free copy of their book. Complimentary copies will be provided for the first 25 attendees. Limit one free book per attendee.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37649", + "website_url": "http://oscon.com/2014/sched/37649", "speakers": [], "categories": [ @@ -7310,7 +7310,7 @@ "time_stop": "2014-07-23 17:00:00", "venue_serial": 1597, "description": "Author book signings will be held in the O'Reilly Authors' booth on Tuesday and Wednesday. This is a great opportunity for you to meet O'Reilly authors and to get a free copy of their book. Complimentary copies will be provided for the first 25 attendees. Limit one free book per attendee.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37650", + "website_url": "http://oscon.com/2014/sched/37650", "speakers": [], "categories": [ @@ -7326,7 +7326,7 @@ "time_stop": "2014-07-22 16:30:00", "venue_serial": 1596, "description": "Office Hours are your chance to meet face-to-face with OSCON presenters in a small-group setting. Drop in to discuss their sessions, ask questions, or make suggestions.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37651", + "website_url": "http://oscon.com/2014/sched/37651", "speakers": [], "categories": [ @@ -7342,7 +7342,7 @@ "time_stop": "2014-07-23 17:00:00", "venue_serial": 1596, "description": "Office Hours are your chance to meet face-to-face with OSCON presenters in a small-group setting. Drop in to discuss their sessions, ask questions, or make suggestions.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37652", + "website_url": "http://oscon.com/2014/sched/37652", "speakers": [], "categories": [ @@ -7358,7 +7358,7 @@ "time_stop": "2014-07-22 21:00:00", "venue_serial": 1461, "description": "The Ubuntu Server BoF - come to tell the Ubuntu Server Product and Project Managers what would you like to see next - and hear from us what's new for Cloud users and a comprehensive tour of our security features. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37657", + "website_url": "http://oscon.com/2014/sched/37657", "speakers": [], "categories": [ @@ -7374,7 +7374,7 @@ "time_stop": "2014-07-23 20:00:00", "venue_serial": 1457, "description": "RIT recently announced the first Academic Minor in Free/Open Source Software and Free Culture in the United States (http://boingboing.net/2014/03/06/get-a-wee-degree-in-free-from.html) This session will detail the engagement strategies, metrics of success and failure, and educational resources that made it possible. Patches welcome. Forks encouraged. Teacher, Learner, and Hacker friendly session.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37658", + "website_url": "http://oscon.com/2014/sched/37658", "speakers": [], "categories": [ @@ -7390,7 +7390,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1460, "description": "The Apache CouchDB project and world of open source development has seen a lot of change since 2005. In this talk, Joan Touzet will discuss the change journey the CouchDB community has taken as it has matured, covering advocacy efforts, Bylaws and Code of Conduct, GitHub, marketing efforts, mailing lists, growth of the committer base, and operating within the larger ASF community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37659", + "website_url": "http://oscon.com/2014/sched/37659", "speakers": [181972], "categories": [ @@ -7408,7 +7408,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1461, "description": "Join Rackspace and CoreOS as they discuss/examine that developers are beginning to see a new set of disruptive technologies come into play - beyond virtual machine, beyond just configuration management.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37661", + "website_url": "http://oscon.com/2014/sched/37661", "speakers": [173503,30412], "categories": [ @@ -7426,7 +7426,7 @@ "time_stop": "2014-07-22 15:40:00", "venue_serial": 1598, "description": "Paris, Jon, and Tim will be at the O'Reilly Authors booth #719, signing copies of their book, Learning Cocoa with Objective-C, 4th Edition.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37662", + "website_url": "http://oscon.com/2014/sched/37662", "speakers": [], "categories": [ @@ -7444,7 +7444,7 @@ "time_stop": "2014-07-22 19:00:00", "venue_serial": 1549, "description": "Paco will be at the O'Reilly Authors booth #719, signing copies of his book, Enterprise Data Workflows with Cascading.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37663", + "website_url": "http://oscon.com/2014/sched/37663", "speakers": [], "categories": [ @@ -7462,7 +7462,7 @@ "time_stop": "2014-07-22 10:40:00", "venue_serial": 1549, "description": "Harry will be at the O'Reilly Authors booth #719, signing copies of his book, Test-Driven Development with Python.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37664", + "website_url": "http://oscon.com/2014/sched/37664", "speakers": [], "categories": [ @@ -7480,7 +7480,7 @@ "time_stop": "2014-07-22 16:10:00", "venue_serial": 1598, "description": "Mike will be at the O'Reilly Authors booth #719, signing copies of his book, RESTful Web APIs.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37665", + "website_url": "http://oscon.com/2014/sched/37665", "speakers": [], "categories": [ @@ -7498,7 +7498,7 @@ "time_stop": "2014-07-22 15:40:00", "venue_serial": 1549, "description": "Arun will be at the O'Reilly Authors booth #719, signing copies of his book, Java EE 7 Essentials.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37666", + "website_url": "http://oscon.com/2014/sched/37666", "speakers": [], "categories": [ @@ -7516,7 +7516,7 @@ "time_stop": "2014-07-22 10:40:00", "venue_serial": 1550, "description": "Anil will be at the O'Reilly Authors booth #719, signing copies of his book, Real World OCaml.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37667", + "website_url": "http://oscon.com/2014/sched/37667", "speakers": [], "categories": [ @@ -7534,7 +7534,7 @@ "time_stop": "2014-07-23 10:40:00", "venue_serial": 1549, "description": "Lorna will be at the O'Reilly Authors booth #719, signing copies of her book, PHP Web Services.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37668", + "website_url": "http://oscon.com/2014/sched/37668", "speakers": [], "categories": [ @@ -7552,7 +7552,7 @@ "time_stop": "2014-07-23 15:40:00", "venue_serial": 1549, "description": "Kyle will be at the O'Reilly Authors booth #719, signing copies of his book, You Don't Know JS: Scope & Closures.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37669", + "website_url": "http://oscon.com/2014/sched/37669", "speakers": [], "categories": [ @@ -7570,7 +7570,7 @@ "time_stop": "2014-07-22 16:10:00", "venue_serial": 1549, "description": "Ruth will be at the O'Reilly Authors booth #719, signing copies of her book, Raspberry Pi Hacks.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37670", + "website_url": "http://oscon.com/2014/sched/37670", "speakers": [], "categories": [ @@ -7588,7 +7588,7 @@ "time_stop": "2014-07-22 18:00:00", "venue_serial": 1549, "description": "Mike will be at the O'Reilly Authors booth #719, signing copies of his book, Programming the Android Developer Tools Essentials.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37671", + "website_url": "http://oscon.com/2014/sched/37671", "speakers": [], "categories": [ @@ -7606,7 +7606,7 @@ "time_stop": "2014-07-22 15:40:00", "venue_serial": 1550, "description": "Neal will be at the O'Reilly Authors booth #719, signing copies of his book, Functional Thinking.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37672", + "website_url": "http://oscon.com/2014/sched/37672", "speakers": [], "categories": [ @@ -7624,7 +7624,7 @@ "time_stop": "2014-07-22 18:00:00", "venue_serial": 1550, "description": "Francesco will be at the O'Reilly Authors booth #719, signing copies of his book, Designing for Scalability with Erlang/OTP.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37673", + "website_url": "http://oscon.com/2014/sched/37673", "speakers": [], "categories": [ @@ -7642,7 +7642,7 @@ "time_stop": "2014-07-23 20:00:00", "venue_serial": 1458, "description": "Join us for lighting talks and discussion of how to approach the hard problems of building scalable, fault tolerant systems in the real world using Erlang.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37674", + "website_url": "http://oscon.com/2014/sched/37674", "speakers": [], "categories": [ @@ -7658,7 +7658,7 @@ "time_stop": "2014-07-22 18:30:00", "venue_serial": 1549, "description": "Sebastien will be at the O'Reilly Authors booth #719, signing copies of his book, 60 Recipes for Apache CloudStack.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37675", + "website_url": "http://oscon.com/2014/sched/37675", "speakers": [], "categories": [ @@ -7676,7 +7676,7 @@ "time_stop": "2014-07-23 15:40:00", "venue_serial": 1550, "description": "Dan will be at the O'Reilly Authors booth #719, signing copies of his books, Programming Google App Engine with Java and Programming Google App Engine with Python.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37676", + "website_url": "http://oscon.com/2014/sched/37676", "speakers": [], "categories": [ @@ -7694,7 +7694,7 @@ "time_stop": "2014-07-22 16:10:00", "venue_serial": 1550, "description": "Alasdair will be at the O'Reilly Authors booth #719, signing copies of his book, Distributed Network Data.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37677", + "website_url": "http://oscon.com/2014/sched/37677", "speakers": [], "categories": [ @@ -7712,7 +7712,7 @@ "time_stop": "2014-07-23 11:20:00", "venue_serial": 1463, "description": "Legend tells of a legendary piece of software whose packet processing speed is the stuff of legend. Find out all about this software, how it's made, where you can get it, and how it can help getting network packets into your native or virtualized application.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37678", + "website_url": "http://oscon.com/2014/sched/37678", "speakers": [182043], "categories": [ @@ -7730,7 +7730,7 @@ "time_stop": "2014-07-22 18:30:00", "venue_serial": 1550, "description": "Steven and Katie will be at the O'Reilly Authors booth #719, signing copies of their book, Getting Started with OpenShift.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37679", + "website_url": "http://oscon.com/2014/sched/37679", "speakers": [], "categories": [ @@ -7748,7 +7748,7 @@ "time_stop": "2014-07-23 15:40:00", "venue_serial": 1598, "description": "Scott will be at the O'Reilly Authors booth #719, signing copies of his book, Interactive Data Visualization for the Web.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37680", + "website_url": "http://oscon.com/2014/sched/37680", "speakers": [], "categories": [ @@ -7766,7 +7766,7 @@ "time_stop": "2014-07-23 16:10:00", "venue_serial": 1549, "description": "Jason will be at the O'Reilly Authors booth #719, signing copies of his book, Web Components.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37681", + "website_url": "http://oscon.com/2014/sched/37681", "speakers": [], "categories": [ @@ -7784,7 +7784,7 @@ "time_stop": "2014-07-23 14:00:00", "venue_serial": 1549, "description": "Jamie will be at the O'Reilly Authors booth #719, signing copies of his book, Effective Akka.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37682", + "website_url": "http://oscon.com/2014/sched/37682", "speakers": [], "categories": [ @@ -7802,7 +7802,7 @@ "time_stop": "2014-07-23 10:40:00", "venue_serial": 1550, "description": "Ethan will be at the O'Reilly Authors booth #719, signing copies of his book, Web Development with Node and Express.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37683", + "website_url": "http://oscon.com/2014/sched/37683", "speakers": [], "categories": [ @@ -7820,7 +7820,7 @@ "time_stop": "2014-07-23 16:10:00", "venue_serial": 1598, "description": "Tom and Everett will be at the O'Reilly Authors booth #719, signing copies of their book, OpenStack Operations Guide.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37684", + "website_url": "http://oscon.com/2014/sched/37684", "speakers": [], "categories": [ @@ -7838,7 +7838,7 @@ "time_stop": "2014-07-23 10:40:00", "venue_serial": 1598, "description": "Jeff and Narayan will be at the O'Reilly Authors booth #719, signing copies of their book, High Performance Drupal.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37685", + "website_url": "http://oscon.com/2014/sched/37685", "speakers": [], "categories": [ @@ -7856,7 +7856,7 @@ "time_stop": "2014-07-21 17:00:00", "venue_serial": 1584, "description": "For this presentation, we'll take a look at the current state of Docker\r\ncontainers, what they're useful for, and where it's going.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37688", + "website_url": "http://oscon.com/2014/sched/37688", "speakers": [6845], "categories": [ @@ -7874,7 +7874,7 @@ "time_stop": "2014-07-21 12:30:00", "venue_serial": 1584, "description": "This talk will cover the interactions and influences that people and\r\ncommunities have over open source software and with one another. Special\r\nattention will be given to many of the hot technologies being used in\r\nand for Open Cloud technologies.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37689", + "website_url": "http://oscon.com/2014/sched/37689", "speakers": [182080], "categories": [ @@ -7892,7 +7892,7 @@ "time_stop": "2014-07-21 10:45:00", "venue_serial": 1584, "description": "Working to help reboot the delivery and consumption of\r\ntechnology services within the Walt Disney Company, we've built a cloud\r\nservice offering and integration platform on open source technologies like\r\nOpenStack with the goal of providing ways to host any application -\r\nwhether modern and cloud ready or on software development hospice care.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37690", + "website_url": "http://oscon.com/2014/sched/37690", "speakers": [182081], "categories": [ @@ -7910,7 +7910,7 @@ "time_stop": "2014-07-23 20:00:00", "venue_serial": 1461, "description": "This session will cover the strategies, tips and techniques we have used at PayPal with our design teams to move towards a goal of delivering products with sufficient color contrast for all user experiences to assure that as many people as possible can see and use them. Designers and developers are welcome to attend. \r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37698", + "website_url": "http://oscon.com/2014/sched/37698", "speakers": [], "categories": [ @@ -7926,7 +7926,7 @@ "time_stop": "2014-07-22 16:50:00", "venue_serial": 1454, "description": "Peek into the future of Git with Android, Google and GitHub. Learn\r\nabout the 450x server performance improvement developed by Google and\r\nGitHub, and get a glimpse of the scaling roadmap.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37699", + "website_url": "http://oscon.com/2014/sched/37699", "speakers": [64512], "categories": [ @@ -7944,7 +7944,7 @@ "time_stop": "2014-07-20 20:00:00", "venue_serial": 1470, "description": "Alchemy.js is a new open source visualization library from GraphAlchemist. Come join the team behind the new tool and learn about the benefits of using Alchemy.js.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37703", + "website_url": "http://oscon.com/2014/sched/37703", "speakers": [], "categories": [ @@ -7960,7 +7960,7 @@ "time_stop": "2014-07-23 20:00:00", "venue_serial": 1463, "description": "Acquiring users, getting them signed up, and protecting both your and their interests are all hard things. What are the hard parts, what are the available tools, and what are the OSS angles?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37704", + "website_url": "http://oscon.com/2014/sched/37704", "speakers": [], "categories": [ @@ -7976,7 +7976,7 @@ "time_stop": "2014-07-22 20:00:00", "venue_serial": 1460, "description": "ManageIQ is a newly open sourced project that lets operations and developers manage the lifecycle of their virtualization and cloud infrastructures. Place virtual workloads according to your policies and automate them, prioritizing for cost, performance, security, and/or reliability. In this BoF, we will demonstrate how to use ManageIQ as a single API and gateway for cloud workloads.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37708", + "website_url": "http://oscon.com/2014/sched/37708", "speakers": [], "categories": [ @@ -7992,7 +7992,7 @@ "time_stop": "2014-07-22 12:10:00", "venue_serial": 1463, "description": "The world of cloud and application development is not just for the hardened developer these days. In their session, Phil Jackson, Development Community Advocate for SoftLayer, and Harold Hannon, Sr. Software Architect at SoftLayer, will pull back the curtain of the architecture of a fun demo application purpose-built for the cloud.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37709", + "website_url": "http://oscon.com/2014/sched/37709", "speakers": [140339,122564], "categories": [ @@ -8010,7 +8010,7 @@ "time_stop": "2014-07-22 20:00:00", "venue_serial": 1458, "description": "Get up to speed with the Gluster Community - we released 3.5, planned 3.6, created lots of language bindings for GFAPI, and are currently planning the Gluster Software Distribution. Come hear about the latest developments in this BoF and how you can participate and benefit.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37712", + "website_url": "http://oscon.com/2014/sched/37712", "speakers": [], "categories": [ @@ -8026,7 +8026,7 @@ "time_stop": "2014-07-21 12:30:00", "venue_serial": 1607, "description": "Data scientists need to have a grab bag of tools available to accomplish the task of value-driven data analytics. Many of those tools are open source. Come see how Pivotal is leveraging and contributing to open source with data science. Special focus on: R, Python, MADlib, Open Chorus, Apache Tomcat, Apache Hadoop, Redis, Rabbit MQ, Cloud Foundry and other open source toolkits. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37713", + "website_url": "http://oscon.com/2014/sched/37713", "speakers": [182463], "categories": [ @@ -8044,7 +8044,7 @@ "time_stop": "2014-07-22 10:40:00", "venue_serial": 1598, "description": "Miguel will be at the O'Reilly Authors booth #719, signing copies of his book, Flask Web Development.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37714", + "website_url": "http://oscon.com/2014/sched/37714", "speakers": [], "categories": [ @@ -8062,7 +8062,7 @@ "time_stop": "2014-07-22 20:00:00", "venue_serial": 1466, "description": "Leaders in technology convene to discuss the 'state of the movement' for issues around privacy, censorship, and surveillance. What are our successes and failures? What are the current threats and opportunities to affect meaningful change? ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37715", + "website_url": "http://oscon.com/2014/sched/37715", "speakers": [], "categories": [ @@ -8078,7 +8078,7 @@ "time_stop": "2014-07-23 16:50:00", "venue_serial": 1462, "description": "Open Source licenses are mostly grounded in US Copyright Law, which requires 51% representation to claim standing in any copyright-related action (including defense against infringement claims as well as re-licensing). Yet, they are also a barrier to participation, since you often must have one in place before you make a substantial (or in some cases any) contribution.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37717", + "website_url": "http://oscon.com/2014/sched/37717", "speakers": [179595,7969,46421,6380], "categories": [ @@ -8096,7 +8096,7 @@ "time_stop": "2014-07-22 09:30:00", "venue_serial": 1525, "description": "Non-profit entities help impact the lives for millions of people, and make the world a better place. Bluehost is working with Grassroots.org to help make it simpler for non-profits to get online and share their messages with the world. Join us and learn how you can help make a difference.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37719", + "website_url": "http://oscon.com/2014/sched/37719", "speakers": [3476,181009,182521], "categories": [ @@ -8114,7 +8114,7 @@ "time_stop": "2014-07-22 09:55:00", "venue_serial": 1525, "description": "A (very) fast overview of the results of the program: lines of code, #'s of participants, and so on. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37721", + "website_url": "http://oscon.com/2014/sched/37721", "speakers": [81759], "categories": [ @@ -8132,7 +8132,7 @@ "time_stop": "2014-07-22 09:50:00", "venue_serial": 1525, "description": "More and more Enterprises are evaluating and adopting OpenStack as an option for deployments in the cloud. HP Helion OpenStack is\r\nthe latest addition to the OpenStack distribution, find out how your Enterprise can leverage it to deliver a scalable, secure and stable cloud environment for complex workloads.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37723", + "website_url": "http://oscon.com/2014/sched/37723", "speakers": [177325], "categories": [ @@ -8150,7 +8150,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1460, "description": "This session will dig into the nuts and bolts of Canvas and explore techniques for implementing beautiful, smooth, and efficient visualizations.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37724", + "website_url": "http://oscon.com/2014/sched/37724", "speakers": [160033], "categories": [ @@ -8168,7 +8168,7 @@ "time_stop": "2014-07-23 20:00:00", "venue_serial": 1465, "description": "Learn how to draw comics or practice drawing with fellow cartoonists at this fun session. We're all friends, regardless of skill level!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37733", + "website_url": "http://oscon.com/2014/sched/37733", "speakers": [], "categories": [ @@ -8184,7 +8184,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1463, "description": "The Big Boss(tm) has just OKed the first Hadoop cluster in the company. You are the guy in charge of analyzing petabytes of your company's valuable data using a combination of custom MapReduce jobs and SQL-on-Hadoop solutions. All of a sudden the web is full of articles telling you that Hadoop is dead, Spark has won and you should quit while you're still ahead. But should you? ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37736", + "website_url": "http://oscon.com/2014/sched/37736", "speakers": [151691], "categories": [ @@ -8202,7 +8202,7 @@ "time_stop": "2014-07-24 09:40:00", "venue_serial": 1525, "description": "Open source design has been a recent trend in hardware, but it tends to be limited to open libraries of 3D-printable parts. These are geared at makers, artists, hobbyists, and whoever else really wants to print their own figurines, not necessarily the engineering community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37738", + "website_url": "http://oscon.com/2014/sched/37738", "speakers": [182587], "categories": [ @@ -8220,7 +8220,7 @@ "time_stop": "2014-07-23 20:00:00", "venue_serial": 1466, "description": "Learn how to build your own cell network and invent cell network applications and mobile services using OpenBTS APIs in a session led by Harvind Samra, co-founder of the OpenBTS project, and Michael Iedema, Senior Engineer at Range Networks.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37742", + "website_url": "http://oscon.com/2014/sched/37742", "speakers": [], "categories": [ @@ -8236,7 +8236,7 @@ "time_stop": "2014-07-22 13:30:00", "venue_serial": 1546, "description": "Got an Open Source project you\u2019re thinking of turning into a startup? Got a startup you\u2019re planning to raise funding for? Want help or feedback on your business plan? O\u2019Reilly AlphaTech Ventures is at OSCON and here to help. Come by and say hi!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37744", + "website_url": "http://oscon.com/2014/sched/37744", "speakers": [122516,1402,171704], "categories": [ @@ -8254,7 +8254,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1463, "description": "Cassandra 2.0 introduced lightweight transactions (LWT), making it the first database to allow mixing linearizable transactions into a fully distributed, highly availability system ('AP' in CAP terminology).\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37755", + "website_url": "http://oscon.com/2014/sched/37755", "speakers": [140062], "categories": [ @@ -8272,7 +8272,7 @@ "time_stop": "2014-07-23 21:00:00", "venue_serial": 1464, "description": "Much has been written about the connections between mathematics and music. Come meet and collaborate with your fellow attendees as we engage in another OSCON tradition: Geek Choir! (Yes, there will be singing.)", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37759", + "website_url": "http://oscon.com/2014/sched/37759", "speakers": [], "categories": [ @@ -8288,7 +8288,7 @@ "time_stop": "2014-07-22 20:00:00", "venue_serial": 1451, "description": "Get together with the local users group + Pg folks in town for OSCON.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37767", + "website_url": "http://oscon.com/2014/sched/37767", "speakers": [], "categories": [ @@ -8304,7 +8304,7 @@ "time_stop": "2014-07-22 13:15:00", "venue_serial": 1547, "description": "Have you wondered about \u201cbig idea marketing\u201d? That is, how to align what you do with big movements many people care about? Tim has advice on this and other topics, such as company culture, startup and project pitches, how programming is changing in the era of cloud computing and DevOps, and why it\u2019s important for coders to help improve government services (like the healthcare.gov rescue effort).", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37768", + "website_url": "http://oscon.com/2014/sched/37768", "speakers": [251], "categories": [ @@ -8322,7 +8322,7 @@ "time_stop": "2014-07-22 18:15:00", "venue_serial": 1546, "description": "Are you worried about software, the Net, and life online? According to Tim, now is the time for sensible, reasonable, extreme paranoia. Come chat with him about privacy policy and technology, public-key encryption, identity federation, and related topics. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37769", + "website_url": "http://oscon.com/2014/sched/37769", "speakers": [24978], "categories": [ @@ -8340,7 +8340,7 @@ "time_stop": "2014-07-23 10:10:00", "venue_serial": 1525, "description": "Keynote by Tim O'Reilly, founder and CEO of O'Reilly Media.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37771", + "website_url": "http://oscon.com/2014/sched/37771", "speakers": [251], "categories": [ @@ -8358,7 +8358,7 @@ "time_stop": "2014-07-23 20:00:00", "venue_serial": 1451, "description": "An open discussion on how to start a local user group and grow it into a successful community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37773", + "website_url": "http://oscon.com/2014/sched/37773", "speakers": [], "categories": [ @@ -8374,7 +8374,7 @@ "time_stop": "2014-07-23 21:00:00", "venue_serial": 1454, "description": "Come meet other Go developers and hear why Go is becoming the new language of the cloud.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37775", + "website_url": "http://oscon.com/2014/sched/37775", "speakers": [], "categories": [ @@ -8390,7 +8390,7 @@ "time_stop": "2014-07-22 21:00:00", "venue_serial": 1456, "description": "brooklyn.io lets you create blueprints to deploy and manage distributed applications, building on best-practice blueprints and policies maintained by the community. Recently accepted into the Apache Incubator, Brooklyn is a multi-cloud autonomic control plane based on the OASIS CAMP YAML standard for composing deployment plans.\r\n\r\nThis BoF is for anyone wanting to share experiences or learn more.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37776", + "website_url": "http://oscon.com/2014/sched/37776", "speakers": [], "categories": [ @@ -8406,7 +8406,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1460, "description": "Containers provide an opportunity for more direct management of\r\napplications. However, loosely coupled, distributed, elastic\r\nmicro-services require more than individual containers and hosts.\r\nKubernetes is a new open source project inspired by Google\u2019s internal\r\nworkload management systems that establishes robust primitives for\r\nmanaging applications comprised of multiple containers.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37777", + "website_url": "http://oscon.com/2014/sched/37777", "speakers": [183169], "categories": [ @@ -8424,7 +8424,7 @@ "time_stop": "2014-07-22 17:40:00", "venue_serial": 1463, "description": "This talk will introduce GNOME's Outreach Program for Women, outline\r\nhow the program works and update you on what's happened recently.\r\nTired of people talking about how there isn't diversity in free\r\nsoftware? Come to this talk and find out how you can help actually\r\nchange things.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37778", + "website_url": "http://oscon.com/2014/sched/37778", "speakers": [173364], "categories": [ @@ -8442,7 +8442,7 @@ "time_stop": "2014-07-22 14:20:00", "venue_serial": 1463, "description": "There\u2019s only one way to do it here at PayPal \u2013 with a framework. Everything you'll ever need is done if you stay inside the lines. Imagine my reaction when I joined PayPal with Scala and high hopes. We have many reasons to avoid the monolithic framework, so we were going against the grain from day 1.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37779", + "website_url": "http://oscon.com/2014/sched/37779", "speakers": [128980], "categories": [ @@ -8460,7 +8460,7 @@ "time_stop": "2014-07-23 21:00:00", "venue_serial": 1458, "description": "DreamFactory is an open source REST API platform that makes it easy to develop mobile and IoT applications without rolling your own user management, security, and REST APIs on the server. Come learn what DreamFactory is all about!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37780", + "website_url": "http://oscon.com/2014/sched/37780", "speakers": [], "categories": [ @@ -8476,7 +8476,7 @@ "time_stop": "2014-07-22 21:00:00", "venue_serial": 1463, "description": "We will discuss the current state of containers and what can/should be improved.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37794", + "website_url": "http://oscon.com/2014/sched/37794", "speakers": [], "categories": [ @@ -8492,7 +8492,7 @@ "time_stop": "2014-07-22 15:10:00", "venue_serial": 1461, "description": "Go is an open source language first released in 2009. Go is popular\r\nbecause it makes coding super-fun again. Josh will illustrate the deep role tools\r\nlike gofmt, godoc, 'go vet', 'go test' and others play in the Go\r\nexperience, show you how to roll your own, and talk about some unexplored\r\npossibilities.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37795", + "website_url": "http://oscon.com/2014/sched/37795", "speakers": [179435], "categories": [ @@ -8510,7 +8510,7 @@ "time_stop": "2014-07-23 09:30:00", "venue_serial": 1525, "description": "In February of this year, PayPal announced it had hired Danese Cooper as their first Head of Open Source. PayPal? And Open Source? In fact, Open Source is playing a key role in reinventing PayPal engineering as a place where innovation at scale is easy and fun - especially if you like to work in Open Source.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37801", + "website_url": "http://oscon.com/2014/sched/37801", "speakers": [76338,179599,179595,179435], "categories": [ @@ -8528,7 +8528,7 @@ "time_stop": "2014-07-23 14:20:00", "venue_serial": 1461, "description": "Meet OpenUI5--a powerful web UI library for developing responsive web apps that run on and adapt to any current browser and device. \r\n\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37808", + "website_url": "http://oscon.com/2014/sched/37808", "speakers": [173233,104828,170822], "categories": [ @@ -8546,7 +8546,7 @@ "time_stop": "2014-07-23 19:30:00", "venue_serial": 1450, "description": "Some talks carefully guide the listeners through the entirety of a topic,\r\n starting with the basics and ending with the fine details.\r\n\r\nThat's\u2026 not the plan for this talk.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37810", + "website_url": "http://oscon.com/2014/sched/37810", "speakers": [3189], "categories": [ @@ -10610,7 +10610,7 @@ "position": "President", "affiliation": "Software Freedom Conservancy", "twitter": "bkuhn_ebb_org", - "bio": "

Bradley M. Kuhn is a Director of FSF, President of the Software Freedom Conservancy, and won the O’Reilly Open Source Award at OSCON 2012. Kuhn began his work in the Free, Libre and Open Source Software (FLOSS) Movement as a volunteer in 1992, when he became an early adopter of the GNU /Linux operating system, and began contributing to various FLOSS projects. He worked during the 1990s as a system administrator and software developer for various companies, and taught AP Computer Science at Walnut Hills High School in Cincinnati. Kuhn’s non-profit career began in 2000, when he was hired by the Free Software Foundation. As FSF’s Executive Director from 2001-2005, Kuhn led FSF’s GPL enforcement, launched its Associate Member program, and invented the Affero GPL. From 2005-2010, Kuhn worked as the Policy Analyst and Technology Director of the Software Freedom Law Center. Kuhn holds a summa cum laude B.S. in Computer Science from Loyola University in Maryland, and an M.S. in Computer Science from the University of Cincinnati. His Master’s thesis (an excerpt from which won the Damien Conway Award for Best Technical Paper at this conference in 2000) discussed methods for dynamic interoperability of FLOSS languages. Kuhn has a regular blog and a microblog (@bkuhn on identi.ca).

" + "bio": "

Bradley M. Kuhn is a Director of FSF, President of the Software Freedom Conservancy, and won the O’Reilly Open Source Award at OSCON 2012. Kuhn began his work in the Free, Libre and Open Source Software (FLOSS) Movement as a volunteer in 1992, when he became an early adopter of the GNU /Linux operating system, and began contributing to various FLOSS projects. He worked during the 1990s as a system administrator and software developer for various companies, and taught AP Computer Science at Walnut Hills High School in Cincinnati. Kuhn’s non-profit career began in 2000, when he was hired by the Free Software Foundation. As FSF’s Executive Director from 2001-2005, Kuhn led FSF’s GPL enforcement, launched its Associate Member program, and invented the Affero GPL. From 2005-2010, Kuhn worked as the Policy Analyst and Technology Director of the Software Freedom Law Center. Kuhn holds a summa cum laude B.S. in Computer Science from Loyola University in Maryland, and an M.S. in Computer Science from the University of Cincinnati. His Master’s thesis (an excerpt from which won the Damien Conway Award for Best Technical Paper at this conference in 2000) discussed methods for dynamic interoperability of FLOSS languages. Kuhn has a regular blog and a microblog (@bkuhn on identi.ca).

" }, { diff --git a/capitulos/code/22-dyn-attr-prop/oscon/explore0.py b/code/22-dyn-attr-prop/oscon/explore0.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/explore0.py rename to code/22-dyn-attr-prop/oscon/explore0.py diff --git a/capitulos/code/22-dyn-attr-prop/oscon/explore1.py b/code/22-dyn-attr-prop/oscon/explore1.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/explore1.py rename to code/22-dyn-attr-prop/oscon/explore1.py diff --git a/capitulos/code/22-dyn-attr-prop/oscon/explore2.py b/code/22-dyn-attr-prop/oscon/explore2.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/explore2.py rename to code/22-dyn-attr-prop/oscon/explore2.py diff --git a/capitulos/code/22-dyn-attr-prop/oscon/osconfeed-sample.json b/code/22-dyn-attr-prop/oscon/osconfeed-sample.json similarity index 88% rename from capitulos/code/22-dyn-attr-prop/oscon/osconfeed-sample.json rename to code/22-dyn-attr-prop/oscon/osconfeed-sample.json index dbbd7c90..f4700cff 100644 --- a/capitulos/code/22-dyn-attr-prop/oscon/osconfeed-sample.json +++ b/code/22-dyn-attr-prop/oscon/osconfeed-sample.json @@ -7,8 +7,8 @@ "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1462, - "description": "Aside from the fact that high school programming...", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34505", + "description": "Aside from the fact that high school programming…", + "website_url": "http://oscon.com/2014/sched/34505", "speakers": [157509], "categories": ["Education"] } ], @@ -20,7 +20,7 @@ "position": "CTO", "affiliation": "Sharewave", "twitter": "sharewaveteam", - "bio": "Robert ´r0ml´ Lefkowitz is the CTO at Sharewave, a startup..." } + "bio": "Robert ´r0ml´ Lefkowitz is the CTO at Sharewave…" } ], "venues": [ { "serial": 1462, diff --git a/capitulos/code/22-dyn-attr-prop/oscon/osconfeed-talk.json b/code/22-dyn-attr-prop/oscon/osconfeed-talk.json similarity index 64% rename from capitulos/code/22-dyn-attr-prop/oscon/osconfeed-talk.json rename to code/22-dyn-attr-prop/oscon/osconfeed-talk.json index 4913bfd0..37979c90 100644 --- a/capitulos/code/22-dyn-attr-prop/oscon/osconfeed-talk.json +++ b/code/22-dyn-attr-prop/oscon/osconfeed-talk.json @@ -4,8 +4,8 @@ "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1449, - "description": "If you're pushing the envelope of programming...", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33950", + "description": "If you're pushing the envelope of programming…", + "website_url": "http://oscon.com/2014/sched/33950", "speakers": [3471, 5199], "categories": ["Python"] } \ No newline at end of file diff --git a/capitulos/code/22-dyn-attr-prop/oscon/osconfeed_explore.rst b/code/22-dyn-attr-prop/oscon/osconfeed_explore.rst similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/osconfeed_explore.rst rename to code/22-dyn-attr-prop/oscon/osconfeed_explore.rst diff --git a/capitulos/code/22-dyn-attr-prop/oscon/runtests.sh b/code/22-dyn-attr-prop/oscon/runtests.sh similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/runtests.sh rename to code/22-dyn-attr-prop/oscon/runtests.sh diff --git a/capitulos/code/22-dyn-attr-prop/oscon/schedule_v1.py b/code/22-dyn-attr-prop/oscon/schedule_v1.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/schedule_v1.py rename to code/22-dyn-attr-prop/oscon/schedule_v1.py diff --git a/capitulos/code/22-dyn-attr-prop/oscon/schedule_v2.py b/code/22-dyn-attr-prop/oscon/schedule_v2.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/schedule_v2.py rename to code/22-dyn-attr-prop/oscon/schedule_v2.py diff --git a/capitulos/code/22-dyn-attr-prop/oscon/schedule_v3.py b/code/22-dyn-attr-prop/oscon/schedule_v3.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/schedule_v3.py rename to code/22-dyn-attr-prop/oscon/schedule_v3.py diff --git a/capitulos/code/22-dyn-attr-prop/oscon/schedule_v4.py b/code/22-dyn-attr-prop/oscon/schedule_v4.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/schedule_v4.py rename to code/22-dyn-attr-prop/oscon/schedule_v4.py diff --git a/capitulos/code/22-dyn-attr-prop/oscon/schedule_v4_hasattr.py b/code/22-dyn-attr-prop/oscon/schedule_v4_hasattr.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/schedule_v4_hasattr.py rename to code/22-dyn-attr-prop/oscon/schedule_v4_hasattr.py diff --git a/capitulos/code/22-dyn-attr-prop/oscon/schedule_v5.py b/code/22-dyn-attr-prop/oscon/schedule_v5.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/schedule_v5.py rename to code/22-dyn-attr-prop/oscon/schedule_v5.py diff --git a/capitulos/code/22-dyn-attr-prop/oscon/test_schedule_v1.py b/code/22-dyn-attr-prop/oscon/test_schedule_v1.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/test_schedule_v1.py rename to code/22-dyn-attr-prop/oscon/test_schedule_v1.py diff --git a/capitulos/code/22-dyn-attr-prop/oscon/test_schedule_v2.py b/code/22-dyn-attr-prop/oscon/test_schedule_v2.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/test_schedule_v2.py rename to code/22-dyn-attr-prop/oscon/test_schedule_v2.py diff --git a/capitulos/code/22-dyn-attr-prop/oscon/test_schedule_v3.py b/code/22-dyn-attr-prop/oscon/test_schedule_v3.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/test_schedule_v3.py rename to code/22-dyn-attr-prop/oscon/test_schedule_v3.py diff --git a/capitulos/code/22-dyn-attr-prop/oscon/test_schedule_v4.py b/code/22-dyn-attr-prop/oscon/test_schedule_v4.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/test_schedule_v4.py rename to code/22-dyn-attr-prop/oscon/test_schedule_v4.py diff --git a/capitulos/code/22-dyn-attr-prop/oscon/test_schedule_v5.py b/code/22-dyn-attr-prop/oscon/test_schedule_v5.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/oscon/test_schedule_v5.py rename to code/22-dyn-attr-prop/oscon/test_schedule_v5.py diff --git a/capitulos/code/22-dyn-attr-prop/pseudo_construction.py b/code/22-dyn-attr-prop/pseudo_construction.py similarity index 100% rename from capitulos/code/22-dyn-attr-prop/pseudo_construction.py rename to code/22-dyn-attr-prop/pseudo_construction.py diff --git a/capitulos/code/23-descriptor/README.rst b/code/23-descriptor/README.rst similarity index 100% rename from capitulos/code/23-descriptor/README.rst rename to code/23-descriptor/README.rst diff --git a/capitulos/code/23-descriptor/bulkfood/bulkfood_v3.py b/code/23-descriptor/bulkfood/bulkfood_v3.py similarity index 100% rename from capitulos/code/23-descriptor/bulkfood/bulkfood_v3.py rename to code/23-descriptor/bulkfood/bulkfood_v3.py diff --git a/capitulos/code/23-descriptor/bulkfood/bulkfood_v4.py b/code/23-descriptor/bulkfood/bulkfood_v4.py similarity index 100% rename from capitulos/code/23-descriptor/bulkfood/bulkfood_v4.py rename to code/23-descriptor/bulkfood/bulkfood_v4.py diff --git a/capitulos/code/23-descriptor/bulkfood/bulkfood_v4c.py b/code/23-descriptor/bulkfood/bulkfood_v4c.py similarity index 100% rename from capitulos/code/23-descriptor/bulkfood/bulkfood_v4c.py rename to code/23-descriptor/bulkfood/bulkfood_v4c.py diff --git a/capitulos/code/23-descriptor/bulkfood/bulkfood_v5.py b/code/23-descriptor/bulkfood/bulkfood_v5.py similarity index 100% rename from capitulos/code/23-descriptor/bulkfood/bulkfood_v5.py rename to code/23-descriptor/bulkfood/bulkfood_v5.py diff --git a/capitulos/code/23-descriptor/bulkfood/model_v4c.py b/code/23-descriptor/bulkfood/model_v4c.py similarity index 100% rename from capitulos/code/23-descriptor/bulkfood/model_v4c.py rename to code/23-descriptor/bulkfood/model_v4c.py diff --git a/capitulos/code/23-descriptor/bulkfood/model_v5.py b/code/23-descriptor/bulkfood/model_v5.py similarity index 100% rename from capitulos/code/23-descriptor/bulkfood/model_v5.py rename to code/23-descriptor/bulkfood/model_v5.py diff --git a/capitulos/code/23-descriptor/descriptorkinds.py b/code/23-descriptor/descriptorkinds.py similarity index 98% rename from capitulos/code/23-descriptor/descriptorkinds.py rename to code/23-descriptor/descriptorkinds.py index 6849f5ab..d05ee253 100644 --- a/capitulos/code/23-descriptor/descriptorkinds.py +++ b/code/23-descriptor/descriptorkinds.py @@ -138,9 +138,7 @@ """ -# tag::DESCR_KINDS[] - -### auxiliary functions for display only ### +# tag::DESCR_KINDS_AUX[] def cls_name(obj_or_cls): cls = type(obj_or_cls) @@ -161,8 +159,9 @@ def print_args(name, *args): pseudo_args = ', '.join(display(x) for x in args) print(f'-> {cls_name(args[0])}.__{name}__({pseudo_args})') +# end::DESCR_KINDS_AUX[] -### essential classes for this example ### +# tag::DESCR_KINDS[] class Overriding: # <1> """a.k.a. data descriptor or enforced descriptor""" diff --git a/capitulos/code/23-descriptor/descriptorkinds_dump.py b/code/23-descriptor/descriptorkinds_dump.py similarity index 100% rename from capitulos/code/23-descriptor/descriptorkinds_dump.py rename to code/23-descriptor/descriptorkinds_dump.py diff --git a/capitulos/code/23-descriptor/method_is_descriptor.py b/code/23-descriptor/method_is_descriptor.py similarity index 80% rename from capitulos/code/23-descriptor/method_is_descriptor.py rename to code/23-descriptor/method_is_descriptor.py index de5b8a2f..612a8f3b 100644 --- a/capitulos/code/23-descriptor/method_is_descriptor.py +++ b/code/23-descriptor/method_is_descriptor.py @@ -10,12 +10,12 @@ Text('drawkcab') >>> type(Text.reverse), type(word.reverse) # <4> (, ) - >>> list(map(Text.reverse, ['repaid', (10, 20, 30), Text('stressed')])) # <5> - ['diaper', (30, 20, 10), Text('desserts')] + >>> [Text.reverse(x) for x in ['abc', (1, 2), Text('stressed')]] # <5> + ['cba', (2, 1), Text('desserts')] >>> Text.reverse.__get__(word) # <6> >>> Text.reverse.__get__(None, Text) # <7> - + >>> word.reverse # <8> >>> word.reverse.__self__ # <9> @@ -33,7 +33,7 @@ class Text(collections.UserString): def __repr__(self): - return 'Text({!r})'.format(self.data) + return f'Text({self.data!r})' def reverse(self): return self[::-1] diff --git a/capitulos/code/24-class-metaprog/autoconst/autoconst.py b/code/24-class-metaprog/autoconst/autoconst.py similarity index 100% rename from capitulos/code/24-class-metaprog/autoconst/autoconst.py rename to code/24-class-metaprog/autoconst/autoconst.py diff --git a/capitulos/code/24-class-metaprog/autoconst/autoconst_demo.py b/code/24-class-metaprog/autoconst/autoconst_demo.py similarity index 100% rename from capitulos/code/24-class-metaprog/autoconst/autoconst_demo.py rename to code/24-class-metaprog/autoconst/autoconst_demo.py diff --git a/capitulos/code/24-class-metaprog/bulkfood/README.md b/code/24-class-metaprog/bulkfood/README.md similarity index 100% rename from capitulos/code/24-class-metaprog/bulkfood/README.md rename to code/24-class-metaprog/bulkfood/README.md diff --git a/capitulos/code/24-class-metaprog/bulkfood/bulkfood_v6.py b/code/24-class-metaprog/bulkfood/bulkfood_v6.py similarity index 100% rename from capitulos/code/24-class-metaprog/bulkfood/bulkfood_v6.py rename to code/24-class-metaprog/bulkfood/bulkfood_v6.py diff --git a/capitulos/code/24-class-metaprog/bulkfood/bulkfood_v7.py b/code/24-class-metaprog/bulkfood/bulkfood_v7.py similarity index 100% rename from capitulos/code/24-class-metaprog/bulkfood/bulkfood_v7.py rename to code/24-class-metaprog/bulkfood/bulkfood_v7.py diff --git a/capitulos/code/24-class-metaprog/bulkfood/bulkfood_v8.py b/code/24-class-metaprog/bulkfood/bulkfood_v8.py similarity index 100% rename from capitulos/code/24-class-metaprog/bulkfood/bulkfood_v8.py rename to code/24-class-metaprog/bulkfood/bulkfood_v8.py diff --git a/capitulos/code/24-class-metaprog/bulkfood/model_v6.py b/code/24-class-metaprog/bulkfood/model_v6.py similarity index 100% rename from capitulos/code/24-class-metaprog/bulkfood/model_v6.py rename to code/24-class-metaprog/bulkfood/model_v6.py diff --git a/capitulos/code/24-class-metaprog/bulkfood/model_v7.py b/code/24-class-metaprog/bulkfood/model_v7.py similarity index 100% rename from capitulos/code/24-class-metaprog/bulkfood/model_v7.py rename to code/24-class-metaprog/bulkfood/model_v7.py diff --git a/capitulos/code/24-class-metaprog/bulkfood/model_v8.py b/code/24-class-metaprog/bulkfood/model_v8.py similarity index 100% rename from capitulos/code/24-class-metaprog/bulkfood/model_v8.py rename to code/24-class-metaprog/bulkfood/model_v8.py diff --git a/capitulos/code/24-class-metaprog/checked/decorator/checkeddeco.py b/code/24-class-metaprog/checked/decorator/checkeddeco.py similarity index 100% rename from capitulos/code/24-class-metaprog/checked/decorator/checkeddeco.py rename to code/24-class-metaprog/checked/decorator/checkeddeco.py diff --git a/capitulos/code/24-class-metaprog/checked/decorator/checkeddeco_demo.py b/code/24-class-metaprog/checked/decorator/checkeddeco_demo.py similarity index 100% rename from capitulos/code/24-class-metaprog/checked/decorator/checkeddeco_demo.py rename to code/24-class-metaprog/checked/decorator/checkeddeco_demo.py diff --git a/capitulos/code/24-class-metaprog/checked/decorator/checkeddeco_test.py b/code/24-class-metaprog/checked/decorator/checkeddeco_test.py similarity index 100% rename from capitulos/code/24-class-metaprog/checked/decorator/checkeddeco_test.py rename to code/24-class-metaprog/checked/decorator/checkeddeco_test.py diff --git a/capitulos/code/24-class-metaprog/checked/initsub/checked_demo.py b/code/24-class-metaprog/checked/initsub/checked_demo.py similarity index 100% rename from capitulos/code/24-class-metaprog/checked/initsub/checked_demo.py rename to code/24-class-metaprog/checked/initsub/checked_demo.py diff --git a/capitulos/code/24-class-metaprog/checked/initsub/checkedlib.py b/code/24-class-metaprog/checked/initsub/checkedlib.py similarity index 91% rename from capitulos/code/24-class-metaprog/checked/initsub/checkedlib.py rename to code/24-class-metaprog/checked/initsub/checkedlib.py index 9b6bf7f1..e94585a8 100644 --- a/capitulos/code/24-class-metaprog/checked/initsub/checkedlib.py +++ b/code/24-class-metaprog/checked/initsub/checkedlib.py @@ -22,7 +22,8 @@ # tag::MOVIE_TYPE_VALIDATION[] - >>> blockbuster = Movie(title='Avatar', year=2009, box_office='billions') + >>> megahit = Movie(title='Avatar', year=2009, + ... box_office='billions') Traceback (most recent call last): ... TypeError: 'billions' is not compatible with box_office:float @@ -45,7 +46,7 @@ Providing extra arguments to the constructor is not allowed:: - >>> blockbuster = Movie(title='Avatar', year=2009, box_office=2000, + >>> megahit = Movie(title='Avatar', year=2009, box_office=2000, ... director='James Cameron') Traceback (most recent call last): ... @@ -77,6 +78,7 @@ def __init__(self, name: str, constructor: Callable) -> None: # <2> raise TypeError(f'{name!r} type hint must be callable') self.name = name self.constructor = constructor + def __set__(self, instance: Any, value: Any) -> None: if value is ...: # <4> @@ -86,7 +88,8 @@ def __set__(self, instance: Any, value: Any) -> None: value = self.constructor(value) # <5> except (TypeError, ValueError) as e: # <6> type_name = self.constructor.__name__ - msg = f'{value!r} is not compatible with {self.name}:{type_name}' + msg = (f'{value!r} is not compatible with ' + + f'{self.name}:{type_name}') raise TypeError(msg) from e instance.__dict__[self.name] = value # <7> # end::CHECKED_FIELD[] @@ -124,7 +127,8 @@ def __flag_unknown_attrs(self, *names: str) -> NoReturn: # <5> plural = 's' if len(names) > 1 else '' extra = ', '.join(f'{name!r}' for name in names) cls_name = repr(self.__class__.__name__) - raise AttributeError(f'{cls_name} object has no attribute{plural} {extra}') + raise AttributeError(f'{cls_name} object has ' + + f'no attribute{plural} {extra}') def _asdict(self) -> dict[str, Any]: # <6> return { diff --git a/capitulos/code/24-class-metaprog/checked/initsub/checkedlib_test.py b/code/24-class-metaprog/checked/initsub/checkedlib_test.py similarity index 100% rename from capitulos/code/24-class-metaprog/checked/initsub/checkedlib_test.py rename to code/24-class-metaprog/checked/initsub/checkedlib_test.py diff --git a/capitulos/code/24-class-metaprog/checked/metaclass/checked_demo.py b/code/24-class-metaprog/checked/metaclass/checked_demo.py similarity index 100% rename from capitulos/code/24-class-metaprog/checked/metaclass/checked_demo.py rename to code/24-class-metaprog/checked/metaclass/checked_demo.py diff --git a/capitulos/code/24-class-metaprog/checked/metaclass/checkedlib.py b/code/24-class-metaprog/checked/metaclass/checkedlib.py similarity index 95% rename from capitulos/code/24-class-metaprog/checked/metaclass/checkedlib.py rename to code/24-class-metaprog/checked/metaclass/checkedlib.py index 768a0f68..921a05c2 100644 --- a/capitulos/code/24-class-metaprog/checked/metaclass/checkedlib.py +++ b/code/24-class-metaprog/checked/metaclass/checkedlib.py @@ -91,7 +91,8 @@ def __set__(self, instance: Any, value: Any) -> None: value = self.constructor(value) except (TypeError, ValueError) as e: type_name = self.constructor.__name__ - msg = f'{value!r} is not compatible with {self.name}:{type_name}' + msg = (f'{value!r} is not compatible ' + + f'with {self.name}:{type_name}') raise TypeError(msg) from e setattr(instance, self.storage_name, value) # <4> # end::CHECKED_FIELD[] @@ -133,7 +134,8 @@ def __flag_unknown_attrs(self, *names: str) -> NoReturn: plural = 's' if len(names) > 1 else '' extra = ', '.join(f'{name!r}' for name in names) cls_name = repr(self.__class__.__name__) - raise AttributeError(f'{cls_name} object has no attribute{plural} {extra}') + raise AttributeError( + f'{cls_name} object has no attribute{plural} {extra}') def _asdict(self) -> dict[str, Any]: return { diff --git a/capitulos/code/24-class-metaprog/checked/metaclass/checkedlib_test.py b/code/24-class-metaprog/checked/metaclass/checkedlib_test.py similarity index 100% rename from capitulos/code/24-class-metaprog/checked/metaclass/checkedlib_test.py rename to code/24-class-metaprog/checked/metaclass/checkedlib_test.py diff --git a/capitulos/code/24-class-metaprog/evaltime/builderlib.py b/code/24-class-metaprog/evaltime/builderlib.py similarity index 100% rename from capitulos/code/24-class-metaprog/evaltime/builderlib.py rename to code/24-class-metaprog/evaltime/builderlib.py index 5f19ba28..cb3de4ef 100644 --- a/capitulos/code/24-class-metaprog/evaltime/builderlib.py +++ b/code/24-class-metaprog/evaltime/builderlib.py @@ -1,6 +1,7 @@ # tag::BUILDERLIB_TOP[] print('@ builderlib module start') + class Builder: # <1> print('@ Builder body') @@ -45,6 +46,5 @@ def __set__(self, instance, value): # <4> def __repr__(self): return '' - print('@ builderlib module end') # end::BUILDERLIB_BOTTOM[] diff --git a/capitulos/code/24-class-metaprog/evaltime/evaldemo.py b/code/24-class-metaprog/evaltime/evaldemo.py similarity index 99% rename from capitulos/code/24-class-metaprog/evaltime/evaldemo.py rename to code/24-class-metaprog/evaltime/evaldemo.py index 7be9ba1a..731b5aae 100755 --- a/capitulos/code/24-class-metaprog/evaltime/evaldemo.py +++ b/code/24-class-metaprog/evaltime/evaldemo.py @@ -17,7 +17,6 @@ def __init__(self): def __repr__(self): return '' - def main(): # <4> obj = Klass() obj.method_a() diff --git a/capitulos/code/24-class-metaprog/evaltime/evaldemo_meta.py b/code/24-class-metaprog/evaltime/evaldemo_meta.py similarity index 100% rename from capitulos/code/24-class-metaprog/evaltime/evaldemo_meta.py rename to code/24-class-metaprog/evaltime/evaldemo_meta.py diff --git a/capitulos/code/24-class-metaprog/evaltime/metalib.py b/code/24-class-metaprog/evaltime/metalib.py similarity index 92% rename from capitulos/code/24-class-metaprog/evaltime/metalib.py rename to code/24-class-metaprog/evaltime/metalib.py index 82f4ebfb..b790dba3 100644 --- a/capitulos/code/24-class-metaprog/evaltime/metalib.py +++ b/code/24-class-metaprog/evaltime/metalib.py @@ -29,10 +29,9 @@ def __new__(meta_cls, cls_name, bases, cls_dict): # <4> def inner_2(self): print(f'% MetaKlass.__new__:inner_2({self!r})') - cls = super().__new__(meta_cls, cls_name, bases, cls_dict.data) # <5> - + cls = super().__new__( + meta_cls, cls_name, bases, cls_dict.data) # <5> cls.method_c = inner_2 # <6> - return cls # <7> def __repr__(cls): # <8> diff --git a/capitulos/code/24-class-metaprog/factories.py b/code/24-class-metaprog/factories.py similarity index 93% rename from capitulos/code/24-class-metaprog/factories.py rename to code/24-class-metaprog/factories.py index cf61d89a..d2c28e89 100644 --- a/capitulos/code/24-class-metaprog/factories.py +++ b/code/24-class-metaprog/factories.py @@ -29,12 +29,11 @@ # tag::RECORD_FACTORY[] -from typing import Union, Any from collections.abc import Iterable, Iterator -FieldNames = Union[str, Iterable[str]] # <1> +FieldNames = str | Iterable[str] # <1> -def record_factory(cls_name: str, field_names: FieldNames) -> type[tuple]: # <2> +def record_factory(cls_name: str, field_names: FieldNames) -> type: # <2> slots = parse_identifiers(field_names) # <3> @@ -44,7 +43,7 @@ def __init__(self, *args, **kwargs) -> None: # <4> for name, value in attrs.items(): setattr(self, name, value) - def __iter__(self) -> Iterator[Any]: # <5> + def __iter__(self) -> Iterator: # <5> for name in self.__slots__: yield getattr(self, name) diff --git a/capitulos/code/24-class-metaprog/factories_ducktyped.py b/code/24-class-metaprog/factories_ducktyped.py similarity index 100% rename from capitulos/code/24-class-metaprog/factories_ducktyped.py rename to code/24-class-metaprog/factories_ducktyped.py diff --git a/capitulos/code/24-class-metaprog/hours/hours.py b/code/24-class-metaprog/hours/hours.py similarity index 100% rename from capitulos/code/24-class-metaprog/hours/hours.py rename to code/24-class-metaprog/hours/hours.py diff --git a/capitulos/code/24-class-metaprog/hours/hours_test.py b/code/24-class-metaprog/hours/hours_test.py similarity index 100% rename from capitulos/code/24-class-metaprog/hours/hours_test.py rename to code/24-class-metaprog/hours/hours_test.py diff --git a/capitulos/code/24-class-metaprog/metabunch/README.md b/code/24-class-metaprog/metabunch/README.md similarity index 100% rename from capitulos/code/24-class-metaprog/metabunch/README.md rename to code/24-class-metaprog/metabunch/README.md diff --git a/capitulos/code/24-class-metaprog/metabunch/from3.6/bunch.py b/code/24-class-metaprog/metabunch/from3.6/bunch.py similarity index 86% rename from capitulos/code/24-class-metaprog/metabunch/from3.6/bunch.py rename to code/24-class-metaprog/metabunch/from3.6/bunch.py index aa0a65ec..d3280ad4 100644 --- a/capitulos/code/24-class-metaprog/metabunch/from3.6/bunch.py +++ b/code/24-class-metaprog/metabunch/from3.6/bunch.py @@ -36,7 +36,7 @@ >>> p.flavor = 'banana' Traceback (most recent call last): ... - AttributeError: 'Point' object has no attribute 'flavor' + AttributeError: 'Point' object has no attribute 'flavor' and no __dict__ for setting new attributes # end::BUNCH_POINT_DEMO_2[] """ @@ -60,12 +60,16 @@ def __repr__(self): # <7> if (value := getattr(self, name)) != default) return f'{cls_name}({rep})' - new_dict = dict(__slots__=[], __init__=__init__, __repr__=__repr__) # <8> + new_dict = dict(__slots__=[], + __init__=__init__, + __repr__=__repr__) # <8> + for name, value in cls_dict.items(): # <9> if name.startswith('__') and name.endswith('__'): # <10> if name in new_dict: - raise AttributeError(f"Can't set {name!r} in {cls_name!r}") + msg = f"Can't set {name!r} in {cls_name!r}" + raise AttributeError(msg) new_dict[name] = value else: # <11> new_dict['__slots__'].append(name) diff --git a/capitulos/code/24-class-metaprog/metabunch/from3.6/bunch_test.py b/code/24-class-metaprog/metabunch/from3.6/bunch_test.py similarity index 100% rename from capitulos/code/24-class-metaprog/metabunch/from3.6/bunch_test.py rename to code/24-class-metaprog/metabunch/from3.6/bunch_test.py diff --git a/capitulos/code/24-class-metaprog/metabunch/nutshell3e/bunch.py b/code/24-class-metaprog/metabunch/nutshell3e/bunch.py similarity index 100% rename from capitulos/code/24-class-metaprog/metabunch/nutshell3e/bunch.py rename to code/24-class-metaprog/metabunch/nutshell3e/bunch.py diff --git a/capitulos/code/24-class-metaprog/metabunch/nutshell3e/bunch_test.py b/code/24-class-metaprog/metabunch/nutshell3e/bunch_test.py similarity index 100% rename from capitulos/code/24-class-metaprog/metabunch/nutshell3e/bunch_test.py rename to code/24-class-metaprog/metabunch/nutshell3e/bunch_test.py diff --git a/capitulos/code/24-class-metaprog/metabunch/original/bunch.py b/code/24-class-metaprog/metabunch/original/bunch.py similarity index 100% rename from capitulos/code/24-class-metaprog/metabunch/original/bunch.py rename to code/24-class-metaprog/metabunch/original/bunch.py diff --git a/capitulos/code/24-class-metaprog/metabunch/original/bunch_test.py b/code/24-class-metaprog/metabunch/original/bunch_test.py similarity index 100% rename from capitulos/code/24-class-metaprog/metabunch/original/bunch_test.py rename to code/24-class-metaprog/metabunch/original/bunch_test.py diff --git a/capitulos/code/24-class-metaprog/metabunch/pre3.6/bunch.py b/code/24-class-metaprog/metabunch/pre3.6/bunch.py similarity index 100% rename from capitulos/code/24-class-metaprog/metabunch/pre3.6/bunch.py rename to code/24-class-metaprog/metabunch/pre3.6/bunch.py diff --git a/capitulos/code/24-class-metaprog/metabunch/pre3.6/bunch_test.py b/code/24-class-metaprog/metabunch/pre3.6/bunch_test.py similarity index 100% rename from capitulos/code/24-class-metaprog/metabunch/pre3.6/bunch_test.py rename to code/24-class-metaprog/metabunch/pre3.6/bunch_test.py diff --git a/capitulos/code/24-class-metaprog/persistent/.gitignore b/code/24-class-metaprog/persistent/.gitignore similarity index 100% rename from capitulos/code/24-class-metaprog/persistent/.gitignore rename to code/24-class-metaprog/persistent/.gitignore diff --git a/capitulos/code/24-class-metaprog/persistent/dblib.py b/code/24-class-metaprog/persistent/dblib.py similarity index 100% rename from capitulos/code/24-class-metaprog/persistent/dblib.py rename to code/24-class-metaprog/persistent/dblib.py diff --git a/capitulos/code/24-class-metaprog/persistent/dblib_test.py b/code/24-class-metaprog/persistent/dblib_test.py similarity index 100% rename from capitulos/code/24-class-metaprog/persistent/dblib_test.py rename to code/24-class-metaprog/persistent/dblib_test.py diff --git a/capitulos/code/24-class-metaprog/persistent/persistlib.py b/code/24-class-metaprog/persistent/persistlib.py similarity index 100% rename from capitulos/code/24-class-metaprog/persistent/persistlib.py rename to code/24-class-metaprog/persistent/persistlib.py diff --git a/capitulos/code/24-class-metaprog/persistent/persistlib_test.py b/code/24-class-metaprog/persistent/persistlib_test.py similarity index 100% rename from capitulos/code/24-class-metaprog/persistent/persistlib_test.py rename to code/24-class-metaprog/persistent/persistlib_test.py diff --git a/capitulos/code/24-class-metaprog/qualname/fakedjango.py b/code/24-class-metaprog/qualname/fakedjango.py similarity index 100% rename from capitulos/code/24-class-metaprog/qualname/fakedjango.py rename to code/24-class-metaprog/qualname/fakedjango.py diff --git a/capitulos/code/24-class-metaprog/qualname/models.py b/code/24-class-metaprog/qualname/models.py similarity index 100% rename from capitulos/code/24-class-metaprog/qualname/models.py rename to code/24-class-metaprog/qualname/models.py diff --git a/capitulos/code/24-class-metaprog/sentinel/sentinel.py b/code/24-class-metaprog/sentinel/sentinel.py similarity index 100% rename from capitulos/code/24-class-metaprog/sentinel/sentinel.py rename to code/24-class-metaprog/sentinel/sentinel.py diff --git a/capitulos/code/24-class-metaprog/sentinel/sentinel_test.py b/code/24-class-metaprog/sentinel/sentinel_test.py similarity index 100% rename from capitulos/code/24-class-metaprog/sentinel/sentinel_test.py rename to code/24-class-metaprog/sentinel/sentinel_test.py diff --git a/capitulos/code/24-class-metaprog/setattr/example_from_leo.py b/code/24-class-metaprog/setattr/example_from_leo.py similarity index 100% rename from capitulos/code/24-class-metaprog/setattr/example_from_leo.py rename to code/24-class-metaprog/setattr/example_from_leo.py diff --git a/capitulos/code/24-class-metaprog/slots/slots_timing.py b/code/24-class-metaprog/slots/slots_timing.py similarity index 100% rename from capitulos/code/24-class-metaprog/slots/slots_timing.py rename to code/24-class-metaprog/slots/slots_timing.py diff --git a/capitulos/code/24-class-metaprog/timeslice.py b/code/24-class-metaprog/timeslice.py similarity index 100% rename from capitulos/code/24-class-metaprog/timeslice.py rename to code/24-class-metaprog/timeslice.py diff --git a/capitulos/code/24-class-metaprog/tinyenums/microenum.py b/code/24-class-metaprog/tinyenums/microenum.py similarity index 100% rename from capitulos/code/24-class-metaprog/tinyenums/microenum.py rename to code/24-class-metaprog/tinyenums/microenum.py diff --git a/capitulos/code/24-class-metaprog/tinyenums/microenum_demo.py b/code/24-class-metaprog/tinyenums/microenum_demo.py similarity index 100% rename from capitulos/code/24-class-metaprog/tinyenums/microenum_demo.py rename to code/24-class-metaprog/tinyenums/microenum_demo.py diff --git a/capitulos/code/24-class-metaprog/tinyenums/nanoenum.py b/code/24-class-metaprog/tinyenums/nanoenum.py similarity index 100% rename from capitulos/code/24-class-metaprog/tinyenums/nanoenum.py rename to code/24-class-metaprog/tinyenums/nanoenum.py diff --git a/capitulos/code/24-class-metaprog/tinyenums/nanoenum_demo.py b/code/24-class-metaprog/tinyenums/nanoenum_demo.py similarity index 100% rename from capitulos/code/24-class-metaprog/tinyenums/nanoenum_demo.py rename to code/24-class-metaprog/tinyenums/nanoenum_demo.py diff --git a/code/README.md b/code/README.md new file mode 100644 index 00000000..02407028 --- /dev/null +++ b/code/README.md @@ -0,0 +1,14 @@ +# Do not reformat the code in these directories! + +Although I tried to follow the best practices in Python code formatting, +a printed book imposes different constraints. + +Sometimes I broke long lines to fit on the page. + +Other times I added or removed blank lines between code blocks +to avoid page breaks in awkward positions. + +I made these changes while composing the print version of +_Python Fluente, 2ª edição_, in early 2026. + +A different book layout would require different changes. diff --git a/como-gerar-o-livro.md b/como-gerar-o-livro.md new file mode 100644 index 00000000..5e1e0c85 --- /dev/null +++ b/como-gerar-o-livro.md @@ -0,0 +1,50 @@ +# Gerando o livro apartir do fonte + +## Instale as dependências necessárias. + +### Ruby + +#### Linux + +##### ubuntu + +``` +sudo apt-get install -y ruby +``` + +##### arch + +``` +sudo pacman -S ruby` +``` + +#### Mac OS + +Com o [Homebrew](https://brew.sh/) instalado use o comando abaixo: + +``` +brew install ruby +``` + +#### Windows + + +Com o [Chocolatey](https://chocolatey.org/) instalado use o comando abaixo: + +``` +choco install ruby +``` + +### Asciidoctor-epub3 + +``` +gem install asciidoctor-epub3 +``` + +### Gerando livro no formato epub. + +Na raiz do projeto rode o comando: + +``` +asciidoctor-epub3 livro.adoc -o 'Python Fluente - Luciano Ramalho, Segunda Edição (2023).epub' +``` diff --git a/cover.jpg b/cover.jpg new file mode 100644 index 00000000..46106239 Binary files /dev/null and b/cover.jpg differ diff --git a/deploy.sh b/deploy.sh deleted file mode 100755 index b8093738..00000000 --- a/deploy.sh +++ /dev/null @@ -1,9 +0,0 @@ - -#!/bin/bash -set -e # exit when any command fails -asciidoctor Livro.adoc -o index.html - -#scp index.html dh_kqh7yy@pendleton.dreamhost.com:/home/dh_kqh7yy/pythonfluente.com/index.html -rsync -avz --delete index.html dh_kqh7yy@pythonfluente.com:~/pythonfluente.com/ - -open https://pythonfluente.com diff --git a/ferramentas/anchors.ini.zip b/ferramentas/anchors.ini.zip new file mode 100644 index 00000000..bcba4355 Binary files /dev/null and b/ferramentas/anchors.ini.zip differ diff --git a/ferramentas/fix_dunder.py b/ferramentas/fix_dunder.py index 7008fc8b..ddb3a10c 100755 --- a/ferramentas/fix_dunder.py +++ b/ferramentas/fix_dunder.py @@ -23,7 +23,7 @@ def backup(filename: str) -> None: print(msg, file=sys.stderr) sys.exit(1) -def replace(filename: str) -> str: +def replace(filename: str) -> None: with open(filename) as fp: adoc = fp.read() diff --git a/ferramentas/go_check.go b/ferramentas/go_check.go new file mode 100644 index 00000000..0edb2de8 --- /dev/null +++ b/ferramentas/go_check.go @@ -0,0 +1,94 @@ +package main + +import ( + "bufio" + "fmt" + "net/http" + "net/url" + "os" + "strings" + "sync" +) + +const MaxRedirects = 10 + +func CheckURL(rawURL string, taskId int, wg *sync.WaitGroup) { + defer wg.Done() + + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + parsedURL, err := url.Parse(rawURL) // parse URL to keep #fragment-id + if err != nil { + fmt.Fprintf(os.Stderr, "Error parsing URL %s: %v\n", rawURL, err) + return + } + currentURL := rawURL + redirectCount := 0 + location := "" // preserve Location header to assert end of redirects + for redirectCount = range MaxRedirects { + resp, err := client.Get(currentURL) + if err != nil { + fmt.Printf("[%4d] *ERROR* %v\n", taskId, err) + break + } + indent := strings.Repeat(" ", redirectCount*3) + if resp.StatusCode != http.StatusOK || redirectCount > 0 { // report only errors or redirects + fmt.Printf("[%4d] %s%d %s\n", taskId, indent, resp.StatusCode, currentURL) + } + location = resp.Header.Get("Location") + if resp.StatusCode >= 300 && resp.StatusCode < 400 { // got a redirect + if location == "" { // empty location header, stop following + break + } + currentURL = location + if parsedURL.Fragment != "" { + currentURL += "#" + parsedURL.Fragment + } + } else { // not a redirect, we're done + break + } + } + if location != "" { // location should be empty at end of redirect chain + fmt.Printf("[%4d] Location still set after %d redirects: %s\n", taskId, redirectCount, location) + } +} + +func main() { + if len(os.Args) < 2 { + fmt.Fprintf(os.Stderr, "Usage: %s \n", os.Args[0]) + os.Exit(1) + } + + filename := os.Args[1] + file, err := os.Open(filename) + if err != nil { + fmt.Fprintf(os.Stderr, "Error opening file: %v\n", err) + os.Exit(1) + } + defer file.Close() + + var wg sync.WaitGroup + scanner := bufio.NewScanner(file) + + taskId := 1 + for scanner.Scan() { + url := strings.TrimSpace(scanner.Text()) + if url == "" { + continue + } + + wg.Add(1) + go CheckURL(url, taskId, &wg) + taskId++ + } + + wg.Wait() + + if err := scanner.Err(); err != nil { + fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err) + } +} diff --git a/ferramentas/references_finder.py b/ferramentas/references_finder.py new file mode 100755 index 00000000..a9ecfccb --- /dev/null +++ b/ferramentas/references_finder.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 + +import re +import configparser +from pathlib import Path + + +book_dir = Path('../online') +FILES = [(book_dir / f'cap{i:02d}.adoc') for i in range(1,25)] + +print(book_dir) +for file in FILES: + print(repr(file)) + +def find_anchors(file): + """Encontra todas as âncoras em um capítulo""" + brack_re = re.compile(r'\[(\[[\w-]+\])\]\n+(?:\[[\w=" -]+\]\n)?=* ?(.*)') + list_brack = re.findall(brack_re,file) + return list_brack + + +def find_refs(file): + """Encontra todas as referências em um capítulo""" + refs_re = re.compile(r'<<([\w_]+)>>') + list_refs = re.findall(refs_re,file) + return list_refs + + +def find_all_anchors(): + """Procura e grava todas as âncoras dos capítulos no arquivo anchors.ini""" + anchors_ini = "# Aqruivo conténdo as ancoras de cada capítulo" + anchors = [] + for file_name in FILES: + with open(file_name,"r",encoding="utf-8") as fp: + file = fp.read() + anchors = find_anchors(file) #Lista de Tuplas + anchors_ini = anchors_ini+"\n"+anchors[0][0]+"\n" # Insere o identificador do capítulo + anchors = anchors[1:] # Remove a tupla do capítulo + for anchor in anchors: + ident, text = anchor + ident = ident[1:-1] + anchors_ini += f'{ident} = {text}\n' + with open("anchors.ini","w",encoding="utf-8") as fp: + fp.write(anchors_ini) + +def find_all_xrefs(repeticao = "False"): + """Procura as referências cruzadas e gera o arquivo xrefs_xparts.ini""" + xrefs_ini = '''# Referências cruzadas para outras partes + # Aqui só aparecem xrefs <> para outras partes do livro''' + anchors = configparser.ConfigParser() + anchors.read('anchors.ini') + + chapters = anchors.sections() # Lista dos capítulos + chap_part = [(i, 'Parte 1') for i in chapters[0:6]] + chap_part += [(i, 'Parte 2') for i in chapters[6:10]] + chap_part += [(i, 'Parte 3') for i in chapters[10:16]] + chap_part += [(i, 'Parte 4') for i in chapters[16:21]] + chap_part += [(i, 'Parte 5') for i in chapters[21:]] + parte = dict(chap_part) + + for file in FILES: + with open(file,"r",encoding="utf-8") as fp: + f = fp.read() + match = re.search(r'\[\[[\w-]+\]\]', f) + if match is None: + continue + current_chap = match.group(0)[2:-2] + xrefs_ini += f'\n[{current_chap}]\n' # Escreve o nome do capítulo no arquivo + xrefs_chap = dict() # {xref, parte} + refs = find_refs(f) + for ref in refs: + if ref in xrefs_chap and repeticao: + continue # Pula caso a referência encontrada já esteja listada + elif ref in anchors[current_chap]: + continue # Pula caso a referência encontrada esteja no próprio capítulo + elif ref in chapters and parte[ref] != parte[current_chap]: + # A referência encontrada é um capítulo + xrefs_chap[ref] = f'{parte[ref]}' + else: + for chapter in chapters: # Procura em cada cap pela âncora + if ref in anchors[chapter]: + # A referência encontrada é uma âncora dentro de um capítulo + if parte[chapter] != parte[current_chap]: + xrefs_chap[ref] = f'do capítulo [[{chapter}]] da {parte[chapter]}' + break + else: + xrefs_ini += f'{ref} = NÃO ENCONTRADA\n' + for xref, xpart in xrefs_chap.items(): + xrefs_ini += f'{xref} = {xpart}\n' + + with open("xrefs_xparts.ini","w",encoding="utf-8") as fp: + fp.write(xrefs_ini) + + + +if __name__ == "__main__": + find_all_anchors() + find_all_xrefs() + print("Done!") + \ No newline at end of file diff --git a/ferramentas/sembreak.py b/ferramentas/sembreak.py new file mode 100755 index 00000000..0e01e65f --- /dev/null +++ b/ferramentas/sembreak.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +import fileinput +import re +import shutil +import sys +from time import strftime + + +RE_PERIOD = re.compile(r'(\w\w\w)\. +([A-Z])') + + +def sembreak(text): + return RE_PERIOD.sub(r'\1.\n\2', text) + + +def main(adoc_name): + bkp_name = adoc_name + strftime('-%H-%M-%S') + '.bkp' + shutil.copy2(adoc_name, bkp_name) + + with open(adoc_name) as fp: + lines = fp.readlines() + + with open(adoc_name, 'wt') as fp: + for line in lines: + if line[0].isalpha(): + line = sembreak(line) + fp.write(line) + + +if __name__ == '__main__': + main(sys.argv[1]) \ No newline at end of file diff --git a/ferramentas/stats-estilo.ipynb b/ferramentas/stats-estilo.ipynb new file mode 100644 index 00000000..b781a738 --- /dev/null +++ b/ferramentas/stats-estilo.ipynb @@ -0,0 +1,150 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7336557a-43a5-48d7-96c9-668e8ae0fe88", + "metadata": {}, + "source": [ + "# Estatísticas para guia de estilo\n", + "\n", + "O guia de estilo do **Python Fluente Segunda Edição** está aqui:
\n", + "https://github.com/pythonfluente/pythonfluente2e/blob/main/guia-de-estilo.adoc\n", + "\n", + "Este notebook coleta algumas estatísticas para ajudar a decidir e verificar as regras do guia de estilo.\n" + ] + }, + { + "cell_type": "markdown", + "id": "d38e5166-6de7-422a-a05c-11fc06e74b63", + "metadata": {}, + "source": [ + "## Pontuação em legendas\n", + "\n", + "Assumindo que toda legenda é uma linha física com um `.` na primeira posição." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "151b5275-b0b8-4ca4-b380-84471861ffca", + "metadata": {}, + "outputs": [], + "source": [ + "def pontos_em_legendas(*paths):\n", + " legendas = 0\n", + " pontos_finais = 0\n", + " for path in paths:\n", + " with open(path) as arq:\n", + " for n, linha in enumerate(arq, 1):\n", + " linha = linha.rstrip()\n", + " if linha == '.':\n", + " print(path, n, repr(linha))\n", + " continue\n", + " if linha.startswith('.') and linha[1] not in ' .': # ignora itens de listas e ..\n", + " legendas += 1\n", + " if linha.endswith('.'):\n", + " pontos_finais += 1\n", + " print(linha)\n", + " else:\n", + " pass\n", + " #print(linha)\n", + " return(pontos_finais, legendas)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "9659dd5d-de95-4aac-91ed-12ede27c8684", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ".Saída parcial do <> processando o texto \"Zen of Python\"; cada linha mostra uma palavra e uma lista de ocorrências na forma de pares `(line_number, column_number)` (número da linha, número da coluna).\n", + ".Diagrama de classes UML simplificado para `MutableSet` e suas superclasses em `collections.abc` (nomes em itálico são classes e métodos abstratos; métodos de operadores reversos foram omitidos por concisão).\n", + ".Diagramas de memória simplificados mostrando uma `tuple` e um `array`, cada uma com três itens. As células em cinza representam o cabeçalho de cada objeto Python na memória. A `tuple` tem um array de ponteiros para seus itens. Cada item é um objeto Python separado, possivelmente contendo também referências aninhadas a outros objetos Python, como aquela lista de dois itens. Por outro lado, um `array` Python é um único objeto, contendo um array da linguagem C com três números de ponto flutuante no formato nativo da CPU.\n", + ".Diagrama de classe UML simplificado para algumas classes de collections.abc (as superclasses estão à esquerda; as setas de herança apontam das subclasses para as superclasses; nomes em itálico indicam classes e métodos abstratos).\n", + ".O produto cartesiano de 3 valores de cartas e 4 naipes é uma sequência de 12 itens.\n", + ".O conteúdo em si da tupla é imutável, mas isso significa apenas que as referências mantidas pela tupla vão sempre apontar para os mesmos objetos. Entretanto, se um dos objetos referenciados for mutável—uma lista, por exemplo—seu conteúdo pode mudar.\n", + ".Estados inicial e final do enigma da atribuição de tuplas (diagrama gerado pelo Online Python Tutor).\n", + ".Criando, armazenando e carregando uma grande array de números de ponto flutuante.\n", + ".Doze caracteres, seus pontos de código, e sua representação binária (em hexadecimal) em 7 codificações diferentes (asteriscos indicam que o caractere não pode ser representado naquela codificação).\n", + ".O sanduíche de Unicode: a melhor prática para processamento de texto.\n", + ".Executando _stdout_check.py_ no PowerShell.\n", + ".Executanto _stdout_check.py_ no PowerShell, redirecionando a saída.\n", + ".Explorando `unicodedata.name()` no console de Python.\n", + ".Usando _cf.py_ para encontrar gatos sorridentes.\n", + ".Terminal do macOS mostrando os caracteres numéricos e metadados correspondentes; `re_dig` significa que o caractere casa com a expressão regular `r'\\d'`.\n", + ".Captura de tela da execução de ramanujan.py do <>.\n", + ".`show_count` de _messages.py_ sem dicas de tipo.\n", + "._messages_test.py_ sem dicas de tipo.\n", + ".Ilustrando a variância.\n", + ".Tela de ajuda para `factorial`; o texto é criado a partir do atributo `+__doc__+` da função.\n", + ".As variáveis `a` e `b` mantém referências para a mesma lista, não cópias da lista.\n", + ".Se você imaginar variáveis como caixas, não é possível entender a atribuição em Python; por outro lado, imagine variáveis como etiquetas autocolantes e o <> é facilmente explicável.\n", + ".`charles` e `lewis` estão vinculados ao mesmo objeto; `alex` está vinculado a um objeto diferente de valor igual.\n", + ".Referências cíclicas: `b` tem uma referência para `a` e então é concatenado a `a`; ainda assim, `deepcopy` consegue copiar `a`.\n", + ".Uma tupla construída a partir de outra é, na verdade, exatamente a mesma tupla.\n", + ".Strings literais podem criar objetos compartilhados.\n", + ".Soma de vetores bi-dimensionais; `Vector(2, 4) + Vector(2, 1)` devolve `Vector(4, 5)`.\n", + ".Uma classe simples para representar um vetor 2D.\n", + ".Diagrama de classes UML com os tipos fundamentais de coleções. Métodos com nome em _itálico_ são abstratos, então precisam ser implementados pelas subclasses concretas, como `list` e `dict`. O restante dos métodos têm implementações concretas, então as subclasses podem herdá-los.\n" + ] + }, + { + "data": { + "text/plain": [ + "(29, 210)" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from glob import glob\n", + "pontos_em_legendas(*glob('../vol1/cap??.adoc'))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d675e73d-0cab-4617-888d-d446575c521f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eafe2c3d-4172-4907-8d62-2ed262e51f50", + "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.0rc1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/ferramentas/test_sembreak.py b/ferramentas/test_sembreak.py new file mode 100644 index 00000000..afc634b4 --- /dev/null +++ b/ferramentas/test_sembreak.py @@ -0,0 +1,14 @@ +import pytest + +from sembreak import sembreak + +@pytest.mark.parametrize("given,expected", [ + ('aaa bbb.', 'aaa bbb.'), + ('aaa bbb. Ccc', 'aaa bbb.\nCcc'), + ('Mr. A. Turing', 'Mr. A. Turing'), + ('aaa bbb. Ccc', 'aaa bbb.\nCcc'), + ('aaa bbb. ccc', 'aaa bbb. ccc'), +]) +def test_sembreak(given:str, expected:str): + res = sembreak(given) + assert res == expected diff --git a/ferramentas/toc-pt-br.txt b/ferramentas/toc-pt-br.txt new file mode 100644 index 00000000..7f719ae3 --- /dev/null +++ b/ferramentas/toc-pt-br.txt @@ -0,0 +1,287 @@ +Parte I: Estruturas de dados + + 1. O modelo de dados de Python + 1.1. Novidades nesse capítulo + 1.2. Um baralho pythônico + 1.3. Como os métodos especiais são utilizados + 1.4. Visão geral dos métodos especiais + 1.5. Por que len não é um método? + 1.6. Resumo do capítulo + 1.7. Para saber mais + 2. Uma coleção de sequências + 2.1. Novidades neste capítulo + 2.2. Uma visão geral das sequências embutidas + 2.3. Compreensões de listas e expressões geradoras + 2.4. Tuplas não são apenas listas imutáveis + 2.5. Desempacotando sequências e iteráveis + 2.6. Pattern matching com sequências + 2.7. Fatiamento + 2.8. Usando + e * com sequências + 2.9. list.sort versus a função embutida sorted + 2.10. Quando uma lista não é a resposta + 2.11. Resumo do capítulo + 2.12. Leitura complementar + 3. Dicionários e conjuntos + 3.1. Novidades nesse capítulo + 3.2. A sintaxe moderna dos dicts + 3.3. Pattern matching com mapeamentos + 3.4. A API padrão dos tipos de mapeamentos + 3.5. Tratamento automático de chaves ausentes + 3.6. Variações de dict + 3.7. Mapeamentos imutáveis + 3.8. Views de dicionários + 3.9. Consequências práticas da forma como dict funciona + 3.10. Teoria dos conjuntos + 3.11. Consequências práticas da forma de funcionamento dos conjuntos + 3.12. Operações de conjuntos em views de dict + 3.13. Resumo do capítulo + 3.14. Leitura complementar + 4. Texto em Unicode versus Bytes + 4.1. Novidades nesse capítulo + 4.2. Questões de caracteres + 4.3. Os fundamentos do byte + 4.4. Codificadores/Decodificadores básicos + 4.5. Entendendo os problemas de codificação/decodificação + 4.6. Processando arquivos de texto + 4.7. Normalizando o Unicode para comparações confiáveis + 4.8. Ordenando texto Unicode + 4.9. O banco de dados do Unicode + 4.10. APIs de modo dual para str e bytes + 4.11. Resumo do capítulo + 4.12. Leitura complementar + 5. Fábricas de classes de dados + 5.1. Novidades nesse capítulo + 5.2. Visão geral das fábricas de classes de dados + 5.3. Tuplas nomeadas clássicas + 5.4. Tuplas nomeadas com tipo + 5.5. Introdução às dicas de tipo + 5.6. Mais detalhes sobre @dataclass + 5.7. A classe de dados como cheiro no código + 5.8. Pattern Matching com instâncias de classes + 5.9. Resumo do Capítulo + 5.10. Leitura complementar + 6. Referências, mutabilidade, e memória + 6.1. Novidades nesse capítulo + 6.2. Variáveis não são caixas + 6.3. Identidade, igualdade e apelidos + 6.4. A princípio, cópias são rasas + 6.5. Parâmetros de função como referências + 6.6. del e coleta de lixo + 6.7. Peças que Python prega com imutáveis + 6.8. Resumo do capítulo + 6.9. Para saber mais + +Parte II: Funções como objetos + + 7. Funções como objetos de primeira classe + 7.1. Novidades nesse capítulo + 7.2. Tratando uma função como um objeto + 7.3. Funções de ordem superior + 7.4. Funções anônimas + 7.5. Os nove sabores de objetos invocáveis + 7.6. Tipos invocáveis definidos pelo usuário + 7.7. De parâmetros posicionais a parâmetros somente nomeados + 7.8. Pacotes para programação funcional + 7.9. Resumo do capítulo + 7.10. Leitura complementar + 8. Dicas de tipo em funções + 8.1. Novidades nesse capítulo + 8.2. Sobre tipagem gradual + 8.3. Tipagem gradual na prática + 8.4. Tipos são definidos pelas operações possíveis + 8.5. Tipos próprios para anotações + 8.6. Anotando parâmetros apenas posicionais e variádicos + 8.7. Tipos imperfeitos e testes poderosos + 8.8. Resumo do capítulo + 8.9. Para saber mais + 9. Decoradores e Clausuras + 9.1. Novidades nesse capítulo + 9.2. Introdução aos decoradores + 9.3. Quando Python executa decoradores + 9.4. Decoradores de registro + 9.5. Regras de escopo de variáveis + 9.6. Clausuras + 9.7. A declaração nonlocal + 9.8. Implementando um decorador simples + 9.9. Decoradores na biblioteca padrão + 9.10. Decoradores parametrizados + 9.11. Resumo do capítulo + 9.12. Leitura complementar + 10. Padrões de projetos com funções de primeira classe + 10.1. Novidades nesse capítulo + 10.2. Estudo de caso: refatorando Estratégia + 10.3. Padrão Estratégia aperfeiçoado com um decorador + 10.4. O padrão Comando + 10.5. Resumo do Capítulo + 10.6. Leitura complementar + +Parte III: Classes e protocolos + + 11. Um objeto pythônico + 11.1. Novidades nesse capítulo + 11.2. Representações de objetos + 11.3. A volta da classe Vector + 11.4. Um construtor alternativo + 11.5. classmethod versus staticmethod + 11.6. Exibição formatada + 11.7. Um Vector2d hashable + 11.8. Suportando o pattern matching posicional + 11.9. Listagem completa Vector2d, versão 3 + 11.10. Atributos privados e "protegidos" no Python + 11.11. Economizando memória com __slots__ + 11.12. Sobrepondo atributos de classe + 11.13. Resumo do capítulo + 11.14. Leitura complementar + 12. Métodos especiais para sequências + 12.1. Novidades nesse capítulo + 12.2. Vector: Um tipo sequência definido pelo usuário + 12.3. Vector versão #1: compatível com Vector2d + 12.4. Protocolos e o duck typing + 12.5. Vector versão #2: Uma sequência fatiável + 12.6. Vector versão #3: acesso dinâmico a atributos + 12.7. Vector versão #4: o hash e um == mais rápido + 12.8. Vector versão #5: Formatando + 12.9. Resumo do capítulo + 12.10. Leitura complementar + 13. Interfaces, protocolos, e ABCs + 13.1. O mapa de tipagem + 13.2. Novidades nesse capítulo + 13.3. Dois tipos de protocolos + 13.4. Programando patos + 13.5. Goose typing + 13.6. Protocolos estáticos + 13.7. Resumo do capítulo + 13.8. Para saber mais + 14. Herança: para o bem ou para o mal + 14.1. Novidades nesse capítulo + 14.2. A função super() + 14.3. É complicado criar subclasses de tipos embutidos + 14.4. Herança múltipla e a Ordem de Resolução de Métodos + 14.5. Classes mixin + 14.6. Herança múltipla no mundo real + 14.7. Lidando com a herança + 14.8. Resumo do capítulo + 14.9. Leitura complementar + 15. Mais dicas de tipo + 15.1. Novidades nesse capítulo + 15.2. Assinaturas sobrepostas + 15.3. TypedDict + 15.4. Coerção de Tipo + 15.5. Lendo dicas de tipo durante a execução + 15.6. Implementando uma classe genérica + 15.7. Variância + 15.8. Implementando um protocolo estático genérico + 15.9. Resumo do capítulo + 15.10. Leitura complementar + 16. Sobrecarga de operadores + 16.1. Novidades nesse capítulo + 16.2. Introdução à sobrecarga de operadores + 16.3. Operadores unários + 16.4. Sobrecarregando + para adição de Vector + 16.5. Sobrecarregando * para multiplicação escalar + 16.6. Usando @ como operador infixo + 16.7. Resumindo os operadores aritméticos + 16.8. Operadores de comparação cheia + 16.9. Operadores de atribuição aumentada + 16.10. Resumo do capítulo + 16.11. Leitura complementar + +Parte IV: Controle de fluxo + + 17. Iteradores, geradores e corrotinas clássicas + 17.1. Novidades nesse capítulo + 17.2. Uma sequência de palavras + 17.3. Porque sequências são iteráveis: a função iter + 17.4. Iteráveis versus iteradores + 17.5. Classes Sentence com __iter__ + 17.6. Sentenças preguiçosas + 17.7. Quando usar expressões geradoras + 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. Subgeradores com yield from + 17.12. Tipos iteráveis genéricos + 17.13. Corrotinas clássicas + 17.14. Resumo do capítulo + 17.15. Leitura complementar + 18. Instruções with, match, e blocos else + 18.1. Novidades nesse capítulo + 18.2. Gerenciadores de contexto e a instrução with + 18.3. Pattern matching no lis.py: um estudo de caso + 18.4. Faça isso, então aquilo: os blocos else além do if + 18.5. Resumo do capítulo + 18.6. Para saber mais + 19. Modelos de concorrência em Python + 19.1. Novidades nesse capítulo + 19.2. A visão geral + 19.3. Um pouco de jargão + 19.4. Um "Olá mundo" concorrente + 19.5. O real impacto da GIL + 19.6. Um pool de processos caseiro + 19.7. Python no mundo multi-núcleo. + 19.8. Resumo do capítulo + 19.9. Para saber mais + 20. Executores concorrentes + 20.1. Novidades nesse capítulo + 20.2. Downloads concorrentes da web + 20.3. Iniciando processos com concurrent.futures + 20.4. Experimentando com Executor.map + 20.5. Download com exibição do progresso e tratamento de erro + 20.6. Resumo do capítulo + 20.7. Para saber mais + 21. Programação assíncrona + 21.1. Novidades nesse capítulo + 21.2. Algumas definições. + 21.3. Um exemplo de asyncio: sondando domínios + 21.4. Novo conceito: awaitable ou esperável + 21.5. Downloads com asyncio e HTTPX + 21.6. Gerenciadores de contexto assíncronos + 21.7. Melhorando o download de bandeiras asyncio + 21.8. Delegando tarefas a executores + 21.9. Programando servidores asyncio + 21.10. Iteração assíncrona e iteráveis assíncronos + 21.11. Programação assíncrona além do asyncio: Curio + 21.12. Dicas de tipo para objetos assíncronos + 21.13. Como a programação assíncrona funciona e como não funciona + 21.14. Resumo do capítulo + 21.15. Para saber mais + +Parte V: Metaprogramação + + 22. Atributos dinâmicos e propriedades + 22.1. Novidades nesse capítulo + 22.2. Processamento de dados com atributos dinâmicos + 22.3. Propriedades computadas + 22.4. Usando uma propriedade para validação de atributos + 22.5. Considerando as propriedades de forma adequada + 22.6. Criando uma fábrica de propriedades + 22.7. Tratando a exclusão de atributos + 22.8. Atributos e funções essenciais para tratamento de atributos + 22.9. Resumo do capítulo + 22.10. Leitura Complementar + 23. Descritores de Atributos + 23.1. Novidades nesse capítulo + 23.2. Exemplo de descritor: validação de atributos + 23.3. Descritores dominantes versus descritores não dominantes + 23.4. Métodos são descritores + 23.5. Dicas para o uso de descritores + 23.6. Docstrings de descritores e a sobreposição de exclusão + 23.7. Resumo do capítulo + 23.8. Leitura complementar + 24. Metaprogramação de classes + 24.1. Novidades nesse capítulo + 24.2. Classes como objetos + 24.3. type: a fábrica de classes embutida + 24.4. Uma função fábrica de classes + 24.5. Apresentando __init_subclass__ + 24.6. Melhorando classes com um decorador de classes + 24.7. O que acontece quando: importação versus execução + 24.8. Introdução às metaclasses + 24.9. Uma solução para Checked usando uma metaclasse + 24.10. Metaclasses no mundo real + 24.11. Um hack de metaclasse com __prepare__ + 24.12. Para encerrar + 24.13. Resumo do capítulo + 24.14. Leitura complementar + Posfácio + diff --git a/ferramentas/unicode-chars-used.ipynb b/ferramentas/unicode-chars-used.ipynb new file mode 100644 index 00000000..e47aa5e1 --- /dev/null +++ b/ferramentas/unicode-chars-used.ipynb @@ -0,0 +1,165 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "65717f00-e968-4ff6-a107-c6d13d338019", + "metadata": {}, + "source": [ + "# Unicode chars used in Python Fluente 2ª edição" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e2884a42-8e61-4db3-96c8-b1f064bc49e7", + "metadata": {}, + "outputs": [], + "source": [ + "from collections import Counter\n", + "from unicodedata import name\n", + "\n", + "def non_ascii_counter(file_paths):\n", + " non_ascii = Counter()\n", + "\n", + " for file_path in file_paths:\n", + " for line in open(file_path):\n", + " if line.isascii():\n", + " continue\n", + " for char in line:\n", + " if not char.isascii():\n", + " non_ascii[char] += 1\n", + " return non_ascii" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "496107ff-d975-448d-92e3-a353b53aa012", + "metadata": {}, + "outputs": [], + "source": [ + "html = non_ascii_counter(['../online/index.html'])" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "60267f00-ea73-47d4-a8f6-ff1a5acc85fa", + "metadata": {}, + "outputs": [], + "source": [ + "from glob import glob\n", + "\n", + "adoc = non_ascii_counter(glob('../online/*.adoc'))" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "b69d39e1-4506-4536-84ff-feb3eb0d92df", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(adoc.keys() - html.keys())" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e8bfba70-e3ce-41cb-91b1-3a37470d5241", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "19" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(html.keys() - adoc.keys())" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "826e2cc4-f381-4010-9561-39201871ae57", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2500\t─\tBOX DRAWINGS LIGHT HORIZONTAL\n", + "201a\t‚\tSINGLE LOW-9 QUOTATION MARK\n", + "2020\t†\tDAGGER\n", + "2712\t✒\tBLACK NIB\n", + "02c6\tˆ\tMODIFIER LETTER CIRCUMFLEX ACCENT\n", + "02dc\t˜\tSMALL TILDE\n", + "1f449\t👉\tWHITE RIGHT POINTING BACKHAND INDEX\n", + "00f6\tö\tLATIN SMALL LETTER O WITH DIAERESIS\n", + "201e\t„\tDOUBLE LOW-9 QUOTATION MARK\n", + "0153\tœ\tLATIN SMALL LIGATURE OE\n", + "26a0\t⚠\tWARNING SIGN\n", + "00b4\t´\tACUTE ACCENT\n", + "203a\t›\tSINGLE RIGHT-POINTING ANGLE QUOTATION MARK\n", + "2021\t‡\tDOUBLE DAGGER\n", + "2030\t‰\tPER MILLE SIGN\n", + "0192\tƒ\tLATIN SMALL LETTER F WITH HOOK\n", + "2039\t‹\tSINGLE LEFT-POINTING ANGLE QUOTATION MARK\n", + "fe0f\t️\tVARIATION SELECTOR-16\n", + "00e6\tæ\tLATIN SMALL LETTER AE\n" + ] + } + ], + "source": [ + "for char in html.keys() - adoc.keys():\n", + " print(f'{ord(char):04x}\\t{char}\\t{name(char)}')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f283c8ea-8508-4603-ba02-e378e525a145", + "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.0rc1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/ferramentas/xrefs_xparts.ini b/ferramentas/xrefs_xparts.ini new file mode 100644 index 00000000..eae29d6a --- /dev/null +++ b/ferramentas/xrefs_xparts.ini @@ -0,0 +1,111 @@ +# Referências cruzadas para outras partes + # Aqui só aparecem xrefs <> para outras partes do livro +[ch_data_model] +ch_ifaces_prot_abc = Parte 3 +ch_generators = Parte 4 +ch_op_overload = Parte 3 +ch_seq_methods = Parte 3 + +[ch_sequences] +ch_seq_methods = Parte 3 +ch_ifaces_prot_abc = Parte 3 +ch_func_objects = Parte 2 +ch_generators = Parte 4 +ch_op_overload = Parte 3 + +[ch_dicts_sets] +ch_op_overload = Parte 3 +ch_ifaces_prot_abc = Parte 3 +ch_func_objects = Parte 2 + +[ch_str_bytes] + +[ch_dataclass] +ch_more_types = Parte 3 +ch_class_metaprog = Parte 5 +ch_type_hints_def = Parte 2 +ch_descriptors = Parte 5 +ch_inheritance = Parte 3 + +[ch_refs_mut_mem] + +[ch_func_objects] +ch_async = Parte 4 +ch_generators = Parte 4 + +[ch_type_hints_def] +ch_more_types = Parte 3 +ch_ifaces_prot_abc = Parte 3 +ch_dataclass = Parte 1 +ch_async = Parte 4 +ch_generators = Parte 4 +ch_class_metaprog = Parte 5 + +[ch_closure_decorator] +ch_class_metaprog = Parte 5 +ch_ifaces_prot_abc = Parte 3 +ch_descriptors = Parte 5 + +[ch_design_patterns] +ch_generators = Parte 4 + +[ch_pythonic_obj] +ch_data_model = Parte 1 +ex_vector2d = do capítulo [[ch_data_model]] da Parte 1 +ch_generators = Parte 4 +ch_dynamic_attrs = Parte 5 +special_names_tbl = do capítulo [[ch_data_model]] da Parte 1 +ch_dataclass = Parte 1 + +[ch_seq_methods] +ch_generators = Parte 4 +ch_data_model = Parte 1 +ex_pythonic_deck = do capítulo [[ch_data_model]] da Parte 1 + +[ch_ifaces_prot_abc] +ch_type_hints_def = Parte 2 +ch_data_model = Parte 1 +ex_pythonic_deck = do capítulo [[ch_data_model]] da Parte 1 +ch_dicts_sets = Parte 1 +ch_generators = Parte 4 +ch_refs_mut_mem = Parte 1 +ch_class_metaprog = Parte 5 + +[ch_inheritance] +ch_descriptors = Parte 5 + +[ch_more_types] +ch_type_hints_def = Parte 2 +ch_dataclass = Parte 1 +ch_class_metaprog = Parte 5 +ch_async = Parte 4 + +[ch_op_overload] +data_model_emulating_sec = do capítulo [[ch_data_model]] da Parte 1 +ch_data_model = Parte 1 +ex_vector2d = do capítulo [[ch_data_model]] da Parte 1 +ch_generators = Parte 4 + +[ch_generators] +ch_data_model = Parte 1 +ch_closure_decorator = Parte 2 + +[ch_with_match] + +[ch_concurrency_models] + +[ch_executors] + +[ch_async] +ch_type_hints_def = Parte 2 + +[ch_dynamic_attrs] +ch_pythonic_obj = Parte 3 +ch_closure_decorator = Parte 2 +ch_generators = Parte 4 + +[ch_descriptors] +ch_refs_mut_mem = Parte 1 + +[ch_class_metaprog] +ch_dataclass = Parte 1 diff --git a/gerar-epub.md b/gerar-epub.md new file mode 100644 index 00000000..0d6744bc --- /dev/null +++ b/gerar-epub.md @@ -0,0 +1,52 @@ +# Como gerar o `epub` a partir dos arquivos fonte + +## Pré-requisitos + +Para gerar o `.epub` do livro é necessário ter o [Docker](https://docker.com) instalado em sua máquina. + +### Docker Ruby Image + +Precisamos baixar a [imagem Ruby oficial de Docker](https://hub.docker.com/_/ruby). Podemos fazê-lo com o seguinte comando: + +```bash +docker pull ruby +``` + +Verificando se a imagem Ruby foi baixada. + +``` bash +docker images +``` + +E espera-se o seguinte resultado: + +``` +REPOSITORY TAG IMAGE ID CREATED SIZE +ruby latest 1a74e25729c7 12 days ago 990MB +``` + +### Clone do repositório + +Realize o clone do repositório na sua máquina local. + +```bash +git clone https://github.com/pythonfluente/pythonfluente2e.git +cd pythonfluente2e +``` + +## Executando o build do `epub` + +Na raiz do repositório recém clonado, iremos executar um container que irá instalar as dependências para gerar o livro, e gerar o `.epub` na mesma raiz. Basta executar o seguinte comando: + +```bash +docker run -it --rm -v .:/book ruby sh -c "gem install asciidoctor-epub3 && asciidoctor-epub3 /book/Livro.adoc -o '/book/Python Fluente, Segunda Edição (2023).epub' 2> /dev/null" +``` + +Neste comando: + +- `-it`: Permite entrar no modo iterativo. +- `--rm`: Remove o container após a saída. +- `-v .:/book`: Monta o volume com o caminho da pasta raiz no container na em /book. +- `sh -c "gem install asciidoctor-epub3 && asciidoctor-epub3 /book/Livro.adoc -o '/book/Python Fluente, Segunda Edição (2023).epub' 2> /dev/null"`: Executa o comando especificado dentro do container. O comando faz a instalação do asciidoctor-epub3 dentro do container e realiza o build do livro. + +Após isso o container irá executar e salvar automaticamente o livro `.epub` em sua máquina. Basta agora enviar o arquivo para o seu leitor de e-books. diff --git a/glifos/README.md b/glifos/README.md new file mode 100644 index 00000000..12ac5486 --- /dev/null +++ b/glifos/README.md @@ -0,0 +1,54 @@ +# Tofu, ou glifos fantando + +Glifos são as figuras dos caracteres. Na falta de um glifo, +o software usado para exibir o texto mostra um caractere alternativo, +geralmente um retângulo branco apelidado de "tofu". + +As fontes padrão do `asciidoctor-pdf` não têm os glifos de +todos os caracteres que usei no *Python Fluente Segunda Edição*. +Por isso inicialmente o PDF exibia dúzias de tofu. +O PDF é o miolo do livro que vai para gráfica. +Tem tudo menos as capas. + +> O problema não acontece nos navegadores exibindo o livro em HTML: +> https://pythonfluente.com. + +## Diagnosticar o problema + +Para diagnosticar o problema, escrevi `list_symbols.py` que lê +`stdin` ou arquivos de uma lista de argumentos na linha de comando, +e gera no `stdout` um arquivo `.adoc` com cada um dos caracteres +não-ASCII que aparecem nos arquivos de entrada, bem como sua contagem. + +## Solução + +### Plano A + +O ideal seria configurar o `asciidoctor-pdf` para usar o conceito +de *fallback font*: uma ou mais fonte alternativas onde encontrar +os glifos faltando. +Em tese, todos os glifos necessários existem na +[coleção de fontes Noto](https://github.com/notofonts/notofonts.github.io). + +Mas a fonte Noto distribuída com o `asciidoctor-pdf` não tem todos os glifos, +e minhas tentativas de usar fallback fracassaram. + +### Plano B + +Eu poderia editar a fonte Noto que vem com o `asciidoctor-pdf` para +incluir algumas dúzias de glifos necessários para o *Python Fluente*, +mas a documentação do Asciidoctor alerta que há vários truques necessários +para que uma fonte moderna como a Noto funcionem com a biblioteca +`prawn` em Ruby, usada pelo `asciidoctor-pdf`, que depende de informações +consideradas obsoletas, que as fontes modernas não têm. + +Como não sou especialista em fontes, esse plano ficou inviável. + +### Plano C + +Descobri que a maioria dos tofus estão no capítulo 4 cujo tema é Unicode. +Alguns símbolos de operações de conjunto aparecem no capítulo 3, sobre +dicts e sets. + +Com o prazo acabando, removi os caracteres que apareciam como tofu +no capítulo 2, e coloquei notas explicando o problema no capítulo 4. diff --git a/glifos/cp1252_additions.py b/glifos/cp1252_additions.py new file mode 100755 index 00000000..4aca0974 --- /dev/null +++ b/glifos/cp1252_additions.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 + +from unicodedata import name + +RC = '\N{REPLACEMENT CHARACTER}' + +def cp1252(): + start = 128 + span = 32 + print('## Characters in CP1252 but not in Latin-1') + for i in range(start, start+span): + try: + char = bytes([i]).decode('cp1252') + except UnicodeDecodeError: + char = ' ' + code = ' ' * 6 + char_name = '_unused_' + else: + code = f'U+{ord(char):04X}' + char_name = name(char) + print(f'{i:X} {code} {char} {char_name}') + +cp1252() diff --git a/glifos/cp437_versus_cp1252.ipynb b/glifos/cp437_versus_cp1252.ipynb new file mode 100644 index 00000000..495d5743 --- /dev/null +++ b/glifos/cp437_versus_cp1252.ipynb @@ -0,0 +1,486 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "53813547-dfaf-463c-a679-e522a45c10ff", + "metadata": {}, + "outputs": [], + "source": [ + "def gen_sample(encoding: str) -> list[str]:\n", + " res = []\n", + " for i in range(128, 256):\n", + " c = bytes([i]).decode(encoding, errors='replace')\n", + " res.append(c)\n", + " return res" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "efa28897-d9af-4101-b2dc-ad689d5ecd67", + "metadata": {}, + "outputs": [], + "source": [ + "ibm = set(gen_sample('cp437'))\n", + "ms = set(gen_sample('cp1252'))" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d944f183-161a-461f-a6d9-729122be27ed", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "  ¡ ¢ £ ¥ ª « ¬ ° ± ² µ · º » ¼ ½ ¿ Ä Å Æ Ç É Ñ Ö Ü ß à á â ä å æ ç è é ê ë ì í î ï ñ ò ó ô ö ÷ ù ú û ü ÿ ƒ " + ] + } + ], + "source": [ + "for c in sorted(ibm & ms):\n", + " print(c, end=' ')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "6a01c6d8-549d-4656-9c2d-513f0d9664a5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Γ Θ Σ Φ Ω α δ ε π σ τ φ ⁿ ₧ ∙ √ ∞ ∩ ≈ ≡ ≤ ≥ ⌐ ⌠ ⌡ ─ │ ┌ ┐ └ ┘ ├ ┤ ┬ ┴ ┼ ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟ ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ▀ ▄ █ ▌ ▐ ░ ▒ ▓ ■ " + ] + } + ], + "source": [ + "for c in sorted(ibm - ms):\n", + " print(c, end=' ')" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "326676e3-da2c-4156-9cd1-540f5f736b6d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "¤ ¦ § ¨ © ­ ® ¯ ³ ´ ¶ ¸ ¹ ¾ À Á Â Ã È Ê Ë Ì Í Î Ï Ð Ò Ó Ô Õ × Ø Ù Ú Û Ý Þ ã ð õ ø ý þ Œ œ Š š Ÿ Ž ž ˆ ˜ – — ‘ ’ ‚ “ ” „ † ‡ • … ‰ ‹ › € ™ � " + ] + } + ], + "source": [ + "for c in sorted(ms - ibm):\n", + " print(c, end=' ')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cac6988d-46e8-4a41-b1db-e5b3ed06b988", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'EURO SIGN'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from unicodedata import name, category\n", + "name('€')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "7d54e321-7e86-497b-b81e-9709f8f2debc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "●\n" + ] + } + ], + "source": [ + "b = '\\N{BLACK CIRCLE}'\n", + "print(b)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1f914534-251d-4452-983d-196c041f0f12", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "both = ibm | ms\n", + "b not in both" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "704ba453-af77-4850-b600-802d05b9b51b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "϶ GREEK REVERSED LUNATE EPSILON SYMBOL\n", + "؆ ARABIC-INDIC CUBE ROOT\n", + "؇ ARABIC-INDIC FOURTH ROOT\n", + "؈ ARABIC RAY\n", + "⁄ FRACTION SLASH\n", + "⁒ COMMERCIAL MINUS SIGN\n", + "⁺ SUPERSCRIPT PLUS SIGN\n", + "⁻ SUPERSCRIPT MINUS\n", + "⁼ SUPERSCRIPT EQUALS SIGN\n", + "₊ SUBSCRIPT PLUS SIGN\n", + "₋ SUBSCRIPT MINUS\n", + "₌ SUBSCRIPT EQUALS SIGN\n", + "℘ SCRIPT CAPITAL P\n", + "⅀ DOUBLE-STRUCK N-ARY SUMMATION\n", + "⅁ TURNED SANS-SERIF CAPITAL G\n", + "⅂ TURNED SANS-SERIF CAPITAL L\n", + "⅃ REVERSED SANS-SERIF CAPITAL L\n", + "⅄ TURNED SANS-SERIF CAPITAL Y\n", + "⅋ TURNED AMPERSAND\n", + "← LEFTWARDS ARROW\n", + "↑ UPWARDS ARROW\n", + "→ RIGHTWARDS ARROW\n", + "↓ DOWNWARDS ARROW\n", + "↔ LEFT RIGHT ARROW\n", + "↚ LEFTWARDS ARROW WITH STROKE\n", + "↛ RIGHTWARDS ARROW WITH STROKE\n", + "↠ RIGHTWARDS TWO HEADED ARROW\n", + "↣ RIGHTWARDS ARROW WITH TAIL\n", + "↦ RIGHTWARDS ARROW FROM BAR\n", + "↮ LEFT RIGHT ARROW WITH STROKE\n", + "⇎ LEFT RIGHT DOUBLE ARROW WITH STROKE\n", + "⇏ RIGHTWARDS DOUBLE ARROW WITH STROKE\n", + "⇒ RIGHTWARDS DOUBLE ARROW\n", + "⇔ LEFT RIGHT DOUBLE ARROW\n", + "⇴ RIGHT ARROW WITH SMALL CIRCLE\n", + "⇵ DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW\n", + "⇶ THREE RIGHTWARDS ARROWS\n", + "⇷ LEFTWARDS ARROW WITH VERTICAL STROKE\n", + "⇸ RIGHTWARDS ARROW WITH VERTICAL STROKE\n", + "⇹ LEFT RIGHT ARROW WITH VERTICAL STROKE\n", + "⇺ LEFTWARDS ARROW WITH DOUBLE VERTICAL STROKE\n", + "⇻ RIGHTWARDS ARROW WITH DOUBLE VERTICAL STROKE\n", + "⇼ LEFT RIGHT ARROW WITH DOUBLE VERTICAL STROKE\n", + "⇽ LEFTWARDS OPEN-HEADED ARROW\n", + "⇾ RIGHTWARDS OPEN-HEADED ARROW\n", + "⇿ LEFT RIGHT OPEN-HEADED ARROW\n", + "∀ FOR ALL\n", + "∁ COMPLEMENT\n", + "∂ PARTIAL DIFFERENTIAL\n", + "∃ THERE EXISTS\n", + "∄ THERE DOES NOT EXIST\n", + "∅ EMPTY SET\n", + "∆ INCREMENT\n", + "∇ NABLA\n", + "∈ ELEMENT OF\n", + "∉ NOT AN ELEMENT OF\n", + "∊ SMALL ELEMENT OF\n", + "∋ CONTAINS AS MEMBER\n", + "∌ DOES NOT CONTAIN AS MEMBER\n", + "∍ SMALL CONTAINS AS MEMBER\n", + "∎ END OF PROOF\n", + "∏ N-ARY PRODUCT\n", + "∐ N-ARY COPRODUCT\n", + "∑ N-ARY SUMMATION\n", + "− MINUS SIGN\n", + "∓ MINUS-OR-PLUS SIGN\n", + "∔ DOT PLUS\n", + "∕ DIVISION SLASH\n", + "∖ SET MINUS\n", + "∗ ASTERISK OPERATOR\n", + "∘ RING OPERATOR\n", + "∛ CUBE ROOT\n", + "∜ FOURTH ROOT\n", + "∝ PROPORTIONAL TO\n", + "∟ RIGHT ANGLE\n", + "∠ ANGLE\n", + "∡ MEASURED ANGLE\n", + "∢ SPHERICAL ANGLE\n", + "∣ DIVIDES\n", + "∤ DOES NOT DIVIDE\n", + "∥ PARALLEL TO\n", + "∦ NOT PARALLEL TO\n", + "∧ LOGICAL AND\n", + "∨ LOGICAL OR\n", + "∪ UNION\n", + "∫ INTEGRAL\n", + "∬ DOUBLE INTEGRAL\n", + "∭ TRIPLE INTEGRAL\n", + "∮ CONTOUR INTEGRAL\n", + "∯ SURFACE INTEGRAL\n", + "∰ VOLUME INTEGRAL\n", + "∱ CLOCKWISE INTEGRAL\n", + "∲ CLOCKWISE CONTOUR INTEGRAL\n", + "∳ ANTICLOCKWISE CONTOUR INTEGRAL\n", + "∴ THEREFORE\n", + "∵ BECAUSE\n", + "∶ RATIO\n", + "∷ PROPORTION\n", + "∸ DOT MINUS\n", + "∹ EXCESS\n", + "∺ GEOMETRIC PROPORTION\n", + "∻ HOMOTHETIC\n", + "∼ TILDE OPERATOR\n", + "∽ REVERSED TILDE\n", + "∾ INVERTED LAZY S\n", + "∿ SINE WAVE\n", + "≀ WREATH PRODUCT\n", + "≁ NOT TILDE\n", + "≂ MINUS TILDE\n", + "≃ ASYMPTOTICALLY EQUAL TO\n", + "≄ NOT ASYMPTOTICALLY EQUAL TO\n", + "≅ APPROXIMATELY EQUAL TO\n", + "≆ APPROXIMATELY BUT NOT ACTUALLY EQUAL TO\n", + "≇ NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO\n", + "≉ NOT ALMOST EQUAL TO\n", + "≊ ALMOST EQUAL OR EQUAL TO\n", + "≋ TRIPLE TILDE\n", + "≌ ALL EQUAL TO\n", + "≍ EQUIVALENT TO\n", + "≎ GEOMETRICALLY EQUIVALENT TO\n", + "≏ DIFFERENCE BETWEEN\n", + "≐ APPROACHES THE LIMIT\n", + "≑ GEOMETRICALLY EQUAL TO\n", + "≒ APPROXIMATELY EQUAL TO OR THE IMAGE OF\n", + "≓ IMAGE OF OR APPROXIMATELY EQUAL TO\n", + "≔ COLON EQUALS\n", + "≕ EQUALS COLON\n", + "≖ RING IN EQUAL TO\n", + "≗ RING EQUAL TO\n", + "≘ CORRESPONDS TO\n", + "≙ ESTIMATES\n", + "≚ EQUIANGULAR TO\n", + "≛ STAR EQUALS\n", + "≜ DELTA EQUAL TO\n", + "≝ EQUAL TO BY DEFINITION\n", + "≞ MEASURED BY\n", + "≟ QUESTIONED EQUAL TO\n", + "≠ NOT EQUAL TO\n", + "≢ NOT IDENTICAL TO\n", + "≣ STRICTLY EQUIVALENT TO\n", + "≦ LESS-THAN OVER EQUAL TO\n", + "≧ GREATER-THAN OVER EQUAL TO\n", + "≨ LESS-THAN BUT NOT EQUAL TO\n", + "≩ GREATER-THAN BUT NOT EQUAL TO\n", + "≪ MUCH LESS-THAN\n", + "≫ MUCH GREATER-THAN\n", + "≬ BETWEEN\n", + "≭ NOT EQUIVALENT TO\n", + "≮ NOT LESS-THAN\n", + "≯ NOT GREATER-THAN\n", + "≰ NEITHER LESS-THAN NOR EQUAL TO\n", + "≱ NEITHER GREATER-THAN NOR EQUAL TO\n", + "≲ LESS-THAN OR EQUIVALENT TO\n", + "≳ GREATER-THAN OR EQUIVALENT TO\n", + "≴ NEITHER LESS-THAN NOR EQUIVALENT TO\n", + "≵ NEITHER GREATER-THAN NOR EQUIVALENT TO\n", + "≶ LESS-THAN OR GREATER-THAN\n", + "≷ GREATER-THAN OR LESS-THAN\n", + "≸ NEITHER LESS-THAN NOR GREATER-THAN\n", + "≹ NEITHER GREATER-THAN NOR LESS-THAN\n", + "≺ PRECEDES\n", + "≻ SUCCEEDS\n", + "≼ PRECEDES OR EQUAL TO\n", + "≽ SUCCEEDS OR EQUAL TO\n", + "≾ PRECEDES OR EQUIVALENT TO\n", + "≿ SUCCEEDS OR EQUIVALENT TO\n", + "⊀ DOES NOT PRECEDE\n", + "⊁ DOES NOT SUCCEED\n", + "⊂ SUBSET OF\n", + "⊃ SUPERSET OF\n", + "⊄ NOT A SUBSET OF\n", + "⊅ NOT A SUPERSET OF\n", + "⊆ SUBSET OF OR EQUAL TO\n", + "⊇ SUPERSET OF OR EQUAL TO\n", + "⊈ NEITHER A SUBSET OF NOR EQUAL TO\n", + "⊉ NEITHER A SUPERSET OF NOR EQUAL TO\n", + "⊊ SUBSET OF WITH NOT EQUAL TO\n", + "⊋ SUPERSET OF WITH NOT EQUAL TO\n", + "⊌ MULTISET\n", + "⊍ MULTISET MULTIPLICATION\n", + "⊎ MULTISET UNION\n", + "⊏ SQUARE IMAGE OF\n", + "⊐ SQUARE ORIGINAL OF\n", + "⊑ SQUARE IMAGE OF OR EQUAL TO\n", + "⊒ SQUARE ORIGINAL OF OR EQUAL TO\n", + "⊓ SQUARE CAP\n", + "⊔ SQUARE CUP\n", + "⊕ CIRCLED PLUS\n", + "⊖ CIRCLED MINUS\n", + "⊗ CIRCLED TIMES\n", + "⊘ CIRCLED DIVISION SLASH\n", + "⊙ CIRCLED DOT OPERATOR\n", + "⊚ CIRCLED RING OPERATOR\n", + "⊛ CIRCLED ASTERISK OPERATOR\n", + "⊜ CIRCLED EQUALS\n", + "⊝ CIRCLED DASH\n", + "⊞ SQUARED PLUS\n", + "⊟ SQUARED MINUS\n", + "⊠ SQUARED TIMES\n", + "⊡ SQUARED DOT OPERATOR\n", + "⊢ RIGHT TACK\n", + "⊣ LEFT TACK\n", + "⊤ DOWN TACK\n", + "⊥ UP TACK\n", + "⊦ ASSERTION\n", + "⊧ MODELS\n", + "⊨ TRUE\n", + "⊩ FORCES\n", + "⊪ TRIPLE VERTICAL BAR RIGHT TURNSTILE\n", + "⊫ DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE\n", + "⊬ DOES NOT PROVE\n", + "⊭ NOT TRUE\n", + "⊮ DOES NOT FORCE\n", + "⊯ NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE\n", + "⊰ PRECEDES UNDER RELATION\n", + "⊱ SUCCEEDS UNDER RELATION\n", + "⊲ NORMAL SUBGROUP OF\n", + "⊳ CONTAINS AS NORMAL SUBGROUP\n", + "⊴ NORMAL SUBGROUP OF OR EQUAL TO\n", + "⊵ CONTAINS AS NORMAL SUBGROUP OR EQUAL TO\n", + "⊶ ORIGINAL OF\n", + "⊷ IMAGE OF\n", + "⊸ MULTIMAP\n", + "⊹ HERMITIAN CONJUGATE MATRIX\n", + "⊺ INTERCALATE\n", + "⊻ XOR\n", + "⊼ NAND\n", + "⊽ NOR\n", + "⊾ RIGHT ANGLE WITH ARC\n", + "⊿ RIGHT TRIANGLE\n", + "⋀ N-ARY LOGICAL AND\n", + "⋁ N-ARY LOGICAL OR\n", + "⋂ N-ARY INTERSECTION\n", + "⋃ N-ARY UNION\n", + "⋄ DIAMOND OPERATOR\n", + "⋅ DOT OPERATOR\n", + "⋆ STAR OPERATOR\n", + "⋇ DIVISION TIMES\n", + "⋈ BOWTIE\n", + "⋉ LEFT NORMAL FACTOR SEMIDIRECT PRODUCT\n", + "⋊ RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT\n", + "⋋ LEFT SEMIDIRECT PRODUCT\n", + "⋌ RIGHT SEMIDIRECT PRODUCT\n", + "⋍ REVERSED TILDE EQUALS\n", + "⋎ CURLY LOGICAL OR\n", + "⋏ CURLY LOGICAL AND\n", + "⋐ DOUBLE SUBSET\n", + "⋑ DOUBLE SUPERSET\n", + "⋒ DOUBLE INTERSECTION\n", + "⋓ DOUBLE UNION\n", + "⋔ PITCHFORK\n", + "⋕ EQUAL AND PARALLEL TO\n", + "⋖ LESS-THAN WITH DOT\n", + "⋗ GREATER-THAN WITH DOT\n", + "⋘ VERY MUCH LESS-THAN\n", + "⋙ VERY MUCH GREATER-THAN\n", + "⋚ LESS-THAN EQUAL TO OR GREATER-THAN\n", + "⋛ GREATER-THAN EQUAL TO OR LESS-THAN\n", + "⋜ EQUAL TO OR LESS-THAN\n", + "⋝ EQUAL TO OR GREATER-THAN\n", + "⋞ EQUAL TO OR PRECEDES\n", + "⋟ EQUAL TO OR SUCCEEDS\n", + "⋠ DOES NOT PRECEDE OR EQUAL\n", + "⋡ DOES NOT SUCCEED OR EQUAL\n", + "⋢ NOT SQUARE IMAGE OF OR EQUAL TO\n", + "⋣ NOT SQUARE ORIGINAL OF OR EQUAL TO\n", + "⋤ SQUARE IMAGE OF OR NOT EQUAL TO\n", + "⋥ SQUARE ORIGINAL OF OR NOT EQUAL TO\n", + "⋦ LESS-THAN BUT NOT EQUIVALENT TO\n", + "⋧ GREATER-THAN BUT NOT EQUIVALENT TO\n", + "⋨ PRECEDES BUT NOT EQUIVALENT TO\n", + "⋩ SUCCEEDS BUT NOT EQUIVALENT TO\n", + "⋪ NOT NORMAL SUBGROUP OF\n", + "⋫ DOES NOT CONTAIN AS NORMAL SUBGROUP\n", + "⋬ NOT NORMAL SUBGROUP OF OR EQUAL TO\n", + "⋭ DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL\n", + "⋮ VERTICAL ELLIPSIS\n", + "⋯ MIDLINE HORIZONTAL ELLIPSIS\n", + "⋰ UP RIGHT DIAGONAL ELLIPSIS\n", + "⋱ DOWN RIGHT DIAGONAL ELLIPSIS\n", + "⋲ ELEMENT OF WITH LONG HORIZONTAL STROKE\n", + "⋳ ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE\n", + "⋴ SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE\n", + "⋵ ELEMENT OF WITH DOT ABOVE\n", + "⋶ ELEMENT OF WITH OVERBAR\n", + "⋷ SMALL ELEMENT OF WITH OVERBAR\n", + "⋸ ELEMENT OF WITH UNDERBAR\n", + "⋹ ELEMENT OF WITH TWO HORIZONTAL STROKES\n", + "⋺ CONTAINS WITH LONG HORIZONTAL STROKE\n", + "⋻ CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE\n", + "⋼ SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE\n", + "⋽ CONTAINS WITH OVERBAR\n", + "⋾ SMALL CONTAINS WITH OVERBAR\n", + "⋿ Z NOTATION BAG MEMBERSHIP\n" + ] + } + ], + "source": [ + "for i in range(256, 0x2300):\n", + " c = chr(i)\n", + " if c not in both:\n", + " if category(c) == 'Sm':\n", + " print(c, name(c))" + ] + } + ], + "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.0rc1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/glifos/find-tofus.ipynb b/glifos/find-tofus.ipynb new file mode 100644 index 00000000..788e388f --- /dev/null +++ b/glifos/find-tofus.ipynb @@ -0,0 +1,73 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 3, + "id": "9fd118ac-e880-43ab-98c8-89ca369530e1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\"Character encoding and Unicode in Python: How to (╯°□°)╯︵ ┻━┻ with dignity\"\n", + "\n", + "(_Codificação de caracteres e o Unicode no Python: como (╯°□°)╯︵ ┻━┻ com dignidade_)\n", + "\n" + ] + } + ], + "source": [ + "tofus = [\n", + " '\\N{PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS}',\n", + " '\\N{BOX DRAWINGS HEAVY HORIZONTAL}',\n", + " '\\N{BOX DRAWINGS LIGHT ARC UP AND LEFT}',\n", + " '\\N{BOX DRAWINGS HEAVY UP AND HORIZONTAL}',\n", + "]\n", + "\n", + "def find_tofus():\n", + " with open('../vol1/cap04.adoc') as fp:\n", + " lines = fp.readlines()\n", + "\n", + " found = set()\n", + " for line in lines:\n", + " for tofu in tofus:\n", + " if tofu in line and line not in found:\n", + " print(line)\n", + " found.add(line)\n", + "\n", + "find_tofus()\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9083fc4f-711f-4f80-91c4-75602dcb7e68", + "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.0rc1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/glifos/list_symbols.py b/glifos/list_symbols.py new file mode 100755 index 00000000..74d3a3af --- /dev/null +++ b/glifos/list_symbols.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 + +""" +This CLI script reads stdin or a list of filenames and +prints to stdout an asciidoc document to +visually find missing glyphs after rendering to +HTML or PDF. + +Example:: + + $ ./list_symbols ../online/index.html > symbols.adoc +""" + +import fileinput +from collections import Counter, namedtuple +import unicodedata +from operator import attrgetter +from time import strftime + +UniChar = namedtuple('UniChar', 'char name categ count') + +def count_non_ascii(lines, counter=None) -> Counter[str]: + if counter is None: + non_ascii = Counter() + else: + non_ascii = counter + for line in lines: + if line.isascii(): + continue + for char in line: + if not char.isascii(): + non_ascii[char] += 1 + return non_ascii + + +def arrange_sample(sample, row_len, filler='x'): + ''' + Break `sample` iterable into rows of `row_len` items, + filling the empty places in the last row if needed. + >>> arrange_sample(range(6), 3) + [[0, 1, 2], [3, 4, 5]] + >>> arrange_sample(range(5), 3) + [[0, 1, 2], [3, 4, None]] + ''' + source = list(sample) + rows = [] + for start in range(0, len(source), row_len): + chars = source[start:start+row_len] + chars = [(c if c != '\N{REPLACEMENT CHARACTER}' else '?') for c in chars] + row = [f'&#x{ord(c):x};' for c in chars] + if len(row) < row_len: + row.extend([filler]*(row_len-len(row))) + rows.append(row) + return rows + + +def compact_display(characters, row_width): + sample = arrange_sample(characters, row_width, ' ') + for row in sample: + for cell in row: + print('|'+cell, end='') + print() + +def detail_display(counter, sortkey=None): + uchars: list[UniChar] = [] + + for char, count in ((c, n) for c, n in counter.items() + if ord(c) >= 256): + name = unicodedata.name(char) + categ = unicodedata.category(char) + uchars.append(UniChar(char, name, categ, count)) + + if sortkey is not None: + uchars.sort(key=sortkey) + + print('[cols=">2,^1,11,1,>1"]') + print('|====') + for char, name, categ, count in uchars: + print(f'|`U+{ord(char):04x}`|{char}|{name}|{categ}|{count}') + print('|====') + +def main(): + print('Generated', strftime('%H:%M:%S')) + non_ascii = count_non_ascii(fileinput.input()) + num_cols = 32 + + print('\n## Latin 1\n') + latin1 = [c for c in non_ascii if ord(c) < 256] + print('|====') + compact_display(latin1, num_cols) + print('|====') + + print('\n## CP1252\n') + octets = bytes(i for i in range(128, 160)) + cp1252 = octets.decode('cp1252', errors='replace') + used = {char:count for char, count in non_ascii.items() if char in cp1252} + # print('|====') + # compact_display(used.keys(), num_cols) + # print('|====') + + detail_display(used, sortkey=lambda uc: -uc.count) + + print('\n## Other') + + detail_display({c : n for c, n in non_ascii.items() + if (ord(c) >= 256) and c not in cp1252}, + sortkey=attrgetter('categ', 'count')) + + +if __name__ == '__main__': + main() diff --git a/glifos/math-symbols-default-for-print-fonts.pdf b/glifos/math-symbols-default-for-print-fonts.pdf new file mode 100644 index 00000000..3a9cc7af Binary files /dev/null and b/glifos/math-symbols-default-for-print-fonts.pdf differ diff --git a/glifos/math-symbols-noto-math-fail.pdf b/glifos/math-symbols-noto-math-fail.pdf new file mode 100644 index 00000000..111103c7 Binary files /dev/null and b/glifos/math-symbols-noto-math-fail.pdf differ diff --git a/glifos/math-symbols.adoc b/glifos/math-symbols.adoc new file mode 100644 index 00000000..9a7cea17 --- /dev/null +++ b/glifos/math-symbols.adoc @@ -0,0 +1,19 @@ + +## Math symbols used in Fluent Python + +[cols=">2,^1,11,1"] +|==== +|`U+2264`|≤|LESS-THAN OR EQUAL TO|Sm +|`U+222a`|∪|UNION|Sm +|`U+2206`|∆|INCREMENT|Sm +|`U+2205`|∅|EMPTY SET|Sm +|`U+2208`|∈|ELEMENT OF|Sm +|`U+2286`|⊆|SUBSET OF OR EQUAL TO|Sm +|`U+2282`|⊂|SUBSET OF|Sm +|`U+2287`|⊇|SUPERSET OF OR EQUAL TO|Sm +|`U+2283`|⊃|SUPERSET OF|Sm +|`U+221e`|∞|INFINITY|Sm +|`U+2229`|∩|INTERSECTION|Sm +|`U+2044`|⁄|FRACTION SLASH|Sm +|`U+2265`|≥|GREATER-THAN OR EQUAL TO|Sm +|==== diff --git a/glifos/non-ascii-LR-font-mix.pdf b/glifos/non-ascii-LR-font-mix.pdf new file mode 100644 index 00000000..26843443 Binary files /dev/null and b/glifos/non-ascii-LR-font-mix.pdf differ diff --git a/glifos/non-ascii-default-for-print-fonts.pdf b/glifos/non-ascii-default-for-print-fonts.pdf new file mode 100644 index 00000000..2bf35177 Binary files /dev/null and b/glifos/non-ascii-default-for-print-fonts.pdf differ diff --git a/glifos/non-ascii-google-noto.pdf b/glifos/non-ascii-google-noto.pdf new file mode 100644 index 00000000..cf358918 Binary files /dev/null and b/glifos/non-ascii-google-noto.pdf differ diff --git a/glifos/non-ascii.adoc b/glifos/non-ascii.adoc new file mode 100644 index 00000000..1f48a9bf --- /dev/null +++ b/glifos/non-ascii.adoc @@ -0,0 +1,106 @@ +Generated 20:46:00 + +## Latin 1 + +|==== +|ç|ã|á|é|ú|õ|ó|í|ô|ê|à|â|É|ª|ü|ý|Á|¢|£|¥|¤|²|×|ö|§|µ|¶|©|°|÷|±|¬ +|®|ñ|³|Ã|½|ß|è|Æ|æ|«|»|Ä|Ú|Ô|À|ä|º|Í|Ó|´| | | | | | | | | | | | +|==== + +## CP1252 + +[cols=">2,^1,11,1,>1"] +|==== +|`U+2014`|—|EM DASH|Pd|941 +|`U+2026`|…|HORIZONTAL ELLIPSIS|Po|107 +|`U+201d`|”|RIGHT DOUBLE QUOTATION MARK|Pf|38 +|`U+201c`|“|LEFT DOUBLE QUOTATION MARK|Pi|36 +|`U+2019`|’|RIGHT SINGLE QUOTATION MARK|Pf|24 +|`U+2018`|‘|LEFT SINGLE QUOTATION MARK|Pi|9 +|`U+2013`|–|EN DASH|Pd|7 +|`U+2022`|•|BULLET|Po|7 +|`U+0160`|Š|LATIN CAPITAL LETTER S WITH CARON|Lu|6 +|`U+20ac`|€|EURO SIGN|Sc|6 +|`U+2122`|™|TRADE MARK SIGN|So|5 +|`U+0152`|Œ|LATIN CAPITAL LIGATURE OE|Lu|4 +|`U+fffd`|�|REPLACEMENT CHARACTER|So|2 +|`U+201a`|‚|SINGLE LOW-9 QUOTATION MARK|Ps|1 +|`U+0192`|ƒ|LATIN SMALL LETTER F WITH HOOK|Ll|1 +|`U+201e`|„|DOUBLE LOW-9 QUOTATION MARK|Ps|1 +|`U+02c6`|ˆ|MODIFIER LETTER CIRCUMFLEX ACCENT|Lm|1 +|`U+2039`|‹|SINGLE LEFT-POINTING ANGLE QUOTATION MARK|Pi|1 +|`U+02dc`|˜|SMALL TILDE|Sk|1 +|`U+203a`|›|SINGLE RIGHT-POINTING ANGLE QUOTATION MARK|Pf|1 +|`U+0153`|œ|LATIN SMALL LIGATURE OE|Ll|1 +|`U+2030`|‰|PER MILLE SIGN|Po|1 +|`U+2020`|†|DAGGER|Po|1 +|`U+2021`|‡|DOUBLE DAGGER|Po|1 +|==== + +## Other +[cols=">2,^1,11,1,>1"] +|==== +|`U+03b9`|ι|GREEK SMALL LETTER IOTA|Ll|1 +|`U+0131`|ı|LATIN SMALL LETTER DOTLESS I|Ll|1 +|`U+016f`|ů|LATIN SMALL LETTER U WITH RING ABOVE|Ll|1 +|`U+0159`|ř|LATIN SMALL LETTER R WITH CARON|Ll|1 +|`U+0113`|ē|LATIN SMALL LETTER E WITH MACRON|Ll|1 +|`U+03ad`|έ|GREEK SMALL LETTER EPSILON WITH TONOS|Ll|2 +|`U+03c6`|φ|GREEK SMALL LETTER PHI|Ll|2 +|`U+03c5`|υ|GREEK SMALL LETTER UPSILON|Ll|2 +|`U+03c1`|ρ|GREEK SMALL LETTER RHO|Ll|2 +|`U+03bf`|ο|GREEK SMALL LETTER OMICRON|Ll|2 +|`U+03c2`|ς|GREEK SMALL LETTER FINAL SIGMA|Ll|2 +|`U+03c0`|π|GREEK SMALL LETTER PI|Ll|2 +|`U+03b8`|θ|GREEK SMALL LETTER THETA|Ll|2 +|`U+03bc`|μ|GREEK SMALL LETTER MU|Ll|3 +|`U+03b5`|ε|GREEK SMALL LETTER EPSILON|Ll|3 +|`U+03bb`|λ|GREEK SMALL LETTER LAMDA|Ll|4 +|`U+6587`|文|CJK UNIFIED IDEOGRAPH-6587|Lo|2 +|`U+5b57`|字|CJK UNIFIED IDEOGRAPH-5B57|Lo|2 +|`U+5316`|化|CJK UNIFIED IDEOGRAPH-5316|Lo|2 +|`U+3051`|け|HIRAGANA LETTER KE|Lo|2 +|`U+03a9`|Ω|GREEK CAPITAL LETTER OMEGA|Lu|1 +|`U+0130`|İ|LATIN CAPITAL LETTER I WITH DOT ABOVE|Lu|1 +|`U+0158`|Ř|LATIN CAPITAL LETTER R WITH CARON|Lu|1 +|`U+0418`|И|CYRILLIC CAPITAL LETTER I|Lu|2 +|`U+0396`|Ζ|GREEK CAPITAL LETTER ZETA|Lu|2 +|`U+0141`|Ł|LATIN CAPITAL LETTER L WITH STROKE|Lu|3 +|`U+03a6`|Φ|GREEK CAPITAL LETTER PHI|Lu|7 +|`U+fe0f`|️|VARIATION SELECTOR-16|Mn|175 +|`U+32b7`|㊷|CIRCLED NUMBER FORTY TWO|No|1 +|`U+278d`|➍|DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FOUR|No|1 +|`U+278e`|➎|DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FIVE|No|1 +|`U+2790`|➐|DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SEVEN|No|1 +|`U+2082`|₂|SUBSCRIPT TWO|No|2 +|`U+2083`|₃|SUBSCRIPT THREE|No|2 +|`U+278a`|➊|DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ONE|No|2 +|`U+278b`|➋|DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT TWO|No|2 +|`U+278c`|➌|DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT THREE|No|2 +|`U+2081`|₁|SUBSCRIPT ONE|No|3 +|`U+fe35`|︵|PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS|Ps|2 +|`U+2264`|≤|LESS-THAN OR EQUAL TO|Sm|1 +|`U+222a`|∪|UNION|Sm|1 +|`U+2206`|∆|INCREMENT|Sm|1 +|`U+2205`|∅|EMPTY SET|Sm|1 +|`U+2208`|∈|ELEMENT OF|Sm|1 +|`U+2286`|⊆|SUBSET OF OR EQUAL TO|Sm|1 +|`U+2282`|⊂|SUBSET OF|Sm|1 +|`U+2287`|⊇|SUPERSET OF OR EQUAL TO|Sm|1 +|`U+2283`|⊃|SUPERSET OF|Sm|1 +|`U+221e`|∞|INFINITY|Sm|1 +|`U+2229`|∩|INTERSECTION|Sm|2 +|`U+2044`|⁄|FRACTION SLASH|Sm|4 +|`U+2265`|≥|GREATER-THAN OR EQUAL TO|Sm|22 +|`U+1f64f`|🙏|PERSON WITH FOLDED HANDS|So|1 +|`U+2500`|─|BOX DRAWINGS LIGHT HORIZONTAL|So|1 +|`U+25a1`|□|WHITE SQUARE|So|2 +|`U+2501`|━|BOX DRAWINGS HEAVY HORIZONTAL|So|2 +|`U+256f`|╯|BOX DRAWINGS LIGHT ARC UP AND LEFT|So|4 +|`U+253b`|┻|BOX DRAWINGS HEAVY UP AND HORIZONTAL|So|4 +|`U+2588`|█|FULL BLOCK|So|41 +|`U+26a0`|⚠|WARNING SIGN|So|76 +|`U+2712`|✒|BLACK NIB|So|99 +|`U+1f449`|👉|WHITE RIGHT POINTING BACKHAND INDEX|So|114 +|`U+25cf`|●|BLACK CIRCLE|So|249 +|==== diff --git a/glifos/tofu-cap04.adoc b/glifos/tofu-cap04.adoc new file mode 100644 index 00000000..36a9b566 --- /dev/null +++ b/glifos/tofu-cap04.adoc @@ -0,0 +1,25 @@ +Generated 19:03:44 + +## CP1252 + +[cols=">2,^1,11,1,>1"] +|==== +|`U+fffd`|�|REPLACEMENT CHARACTER|So|2 +|==== + +## Other +[cols=">2,^1,11,1,>1"] +|==== +|`U+1F006`|🀆|MAHJONG TILE WHITE DRAGON|??|0 +|`U+6587`|文|CJK UNIFIED IDEOGRAPH-6587|Lo|2 +|`U+5b57`|字|CJK UNIFIED IDEOGRAPH-5B57|Lo|2 +|`U+5316`|化|CJK UNIFIED IDEOGRAPH-5316|Lo|2 +|`U+3051`|け|HIRAGANA LETTER KE|Lo|2 +|`U+32b7`|㊷|CIRCLED NUMBER FORTY TWO|No|1 +|`U+2265`|≥|GREATER-THAN OR EQUAL TO|Sm|13 +|`U+fe35`|︵|PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS|Ps|2 +|`U+2044`|⁄|FRACTION SLASH|Sm|4 +|`U+2501`|━|BOX DRAWINGS HEAVY HORIZONTAL|So|2 +|`U+256f`|╯|BOX DRAWINGS LIGHT ARC UP AND LEFT|So|4 +|`U+253b`|┻|BOX DRAWINGS HEAVY UP AND HORIZONTAL|So|4 +|==== diff --git a/glifos/tofu-cap04.pdf b/glifos/tofu-cap04.pdf new file mode 100644 index 00000000..398a5243 Binary files /dev/null and b/glifos/tofu-cap04.pdf differ diff --git a/glifos/vol1-cap01-08-SEM-cap04.adoc b/glifos/vol1-cap01-08-SEM-cap04.adoc new file mode 100644 index 00000000..1d5c7756 --- /dev/null +++ b/glifos/vol1-cap01-08-SEM-cap04.adoc @@ -0,0 +1,32 @@ +Generated 19:06:44 + +## Latin 1 + +|==== +|é|í|ó|«|»|á|ê|ô|É|ç|ã|õ|à|ú|â|Á|¢|£|¥|¤|²|×|§|µ|¶|©|°|÷|±|¬|®|à +|ª|Ô|ý|ü|À|Ú| | | | | | | | | | | | | | | | | | | | | | | | | | +|==== + +## CP1252 + +[cols=">2,^1,11,1,>1"] +|==== +|`U+2014`|—|EM DASH|Pd|273 +|`U+2026`|…|HORIZONTAL ELLIPSIS|Po|44 +|`U+201c`|“|LEFT DOUBLE QUOTATION MARK|Pi|17 +|`U+201d`|”|RIGHT DOUBLE QUOTATION MARK|Pf|16 +|`U+20ac`|€|EURO SIGN|Sc|4 +|`U+2019`|’|RIGHT SINGLE QUOTATION MARK|Pf|4 +|`U+2018`|‘|LEFT SINGLE QUOTATION MARK|Pi|3 +|`U+2013`|–|EN DASH|Pd|2 +|`U+0160`|Š|LATIN CAPITAL LETTER S WITH CARON|Lu|1 +|==== + +## Other +[cols=">2,^1,11,1,>1"] +|==== +|`U+0141`|Ł|LATIN CAPITAL LETTER L WITH STROKE|Lu|1 +|`U+2264`|≤|LESS-THAN OR EQUAL TO|Sm|1 +|`U+2265`|≥|GREATER-THAN OR EQUAL TO|Sm|13 +|`U+25cf`|●|BLACK CIRCLE|So|249 +|==== diff --git a/glifos/vol1-cap01-08-SEM-cap04.pdf b/glifos/vol1-cap01-08-SEM-cap04.pdf new file mode 100644 index 00000000..b491cdbb Binary files /dev/null and b/glifos/vol1-cap01-08-SEM-cap04.pdf differ diff --git a/glifos/vol1-cap01-08.adoc b/glifos/vol1-cap01-08.adoc new file mode 100644 index 00000000..07b7b198 --- /dev/null +++ b/glifos/vol1-cap01-08.adoc @@ -0,0 +1,63 @@ +Generated 19:03:44 + +## Latin 1 + +|==== +|é|í|ó|«|»|á|ê|ô|É|ç|ã|õ|à|ú|â|Á|¢|£|¥|¤|²|×|§|µ|¶|©|°|÷|±|¬|®|ñ +|³|Ã|½|ß|è|ü|ý|Ä|Ú|ª|Ô|À| | | | | | | | | | | | | | | | | | | | +|==== + +## CP1252 + +[cols=">2,^1,11,1,>1"] +|==== +|`U+2014`|—|EM DASH|Pd|324 +|`U+2026`|…|HORIZONTAL ELLIPSIS|Po|47 +|`U+201c`|“|LEFT DOUBLE QUOTATION MARK|Pi|20 +|`U+201d`|”|RIGHT DOUBLE QUOTATION MARK|Pf|19 +|`U+2022`|•|BULLET|Po|6 +|`U+20ac`|€|EURO SIGN|Sc|5 +|`U+2019`|’|RIGHT SINGLE QUOTATION MARK|Pf|5 +|`U+2122`|™|TRADE MARK SIGN|So|4 +|`U+0152`|Œ|LATIN CAPITAL LIGATURE OE|Lu|3 +|`U+2018`|‘|LEFT SINGLE QUOTATION MARK|Pi|3 +|`U+2013`|–|EN DASH|Pd|2 +|`U+fffd`|�|REPLACEMENT CHARACTER|So|2 +|`U+0160`|Š|LATIN CAPITAL LETTER S WITH CARON|Lu|2 +|==== + +## Other +[cols=">2,^1,11,1,>1"] +|==== +|`U+03b9`|ι|GREEK SMALL LETTER IOTA|Ll|1 +|`U+0131`|ı|LATIN SMALL LETTER DOTLESS I|Ll|1 +|`U+03ad`|έ|GREEK SMALL LETTER EPSILON WITH TONOS|Ll|2 +|`U+03c6`|φ|GREEK SMALL LETTER PHI|Ll|2 +|`U+03c5`|υ|GREEK SMALL LETTER UPSILON|Ll|2 +|`U+03c1`|ρ|GREEK SMALL LETTER RHO|Ll|2 +|`U+03bf`|ο|GREEK SMALL LETTER OMICRON|Ll|2 +|`U+03c2`|ς|GREEK SMALL LETTER FINAL SIGMA|Ll|2 +|`U+03c0`|π|GREEK SMALL LETTER PI|Ll|2 +|`U+03bc`|μ|GREEK SMALL LETTER MU|Ll|3 +|`U+03b5`|ε|GREEK SMALL LETTER EPSILON|Ll|3 +|`U+6587`|文|CJK UNIFIED IDEOGRAPH-6587|Lo|2 +|`U+5b57`|字|CJK UNIFIED IDEOGRAPH-5B57|Lo|2 +|`U+5316`|化|CJK UNIFIED IDEOGRAPH-5316|Lo|2 +|`U+3051`|け|HIRAGANA LETTER KE|Lo|2 +|`U+03a9`|Ω|GREEK CAPITAL LETTER OMEGA|Lu|1 +|`U+0130`|İ|LATIN CAPITAL LETTER I WITH DOT ABOVE|Lu|1 +|`U+0141`|Ł|LATIN CAPITAL LETTER L WITH STROKE|Lu|1 +|`U+0418`|И|CYRILLIC CAPITAL LETTER I|Lu|2 +|`U+0396`|Ζ|GREEK CAPITAL LETTER ZETA|Lu|2 +|`U+32b7`|㊷|CIRCLED NUMBER FORTY TWO|No|1 +|`U+fe35`|︵|PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS|Ps|2 +|`U+2264`|≤|LESS-THAN OR EQUAL TO|Sm|1 +|`U+221e`|∞|INFINITY|Sm|1 +|`U+2044`|⁄|FRACTION SLASH|Sm|4 +|`U+2265`|≥|GREATER-THAN OR EQUAL TO|Sm|13 +|`U+25a1`|□|WHITE SQUARE|So|2 +|`U+2501`|━|BOX DRAWINGS HEAVY HORIZONTAL|So|2 +|`U+256f`|╯|BOX DRAWINGS LIGHT ARC UP AND LEFT|So|4 +|`U+253b`|┻|BOX DRAWINGS HEAVY UP AND HORIZONTAL|So|4 +|`U+25cf`|●|BLACK CIRCLE|So|249 +|==== diff --git a/glifos/vol1-cap01-08.pdf b/glifos/vol1-cap01-08.pdf new file mode 100644 index 00000000..17f19669 Binary files /dev/null and b/glifos/vol1-cap01-08.pdf differ diff --git a/glifos/vol1-cap04.adoc b/glifos/vol1-cap04.adoc new file mode 100644 index 00000000..7a46f164 --- /dev/null +++ b/glifos/vol1-cap04.adoc @@ -0,0 +1,56 @@ +Generated 19:21:30 + +## Latin 1 + +|==== +|ç|ã|«|»|í|ê|á|õ|é|ô|ó|ú|É|â|ñ|³|Ã|©|à|µ|½|²|ß|è|ü|ý|Ä|°|Ú| | | +|==== + +## CP1252 + +[cols=">2,^1,11,1,>1"] +|==== +|`U+2014`|—|EM DASH|Pd|51 +|`U+2022`|•|BULLET|Po|6 +|`U+2122`|™|TRADE MARK SIGN|So|4 +|`U+2026`|…|HORIZONTAL ELLIPSIS|Po|3 +|`U+201c`|“|LEFT DOUBLE QUOTATION MARK|Pi|3 +|`U+0152`|Œ|LATIN CAPITAL LIGATURE OE|Lu|3 +|`U+201d`|”|RIGHT DOUBLE QUOTATION MARK|Pf|3 +|`U+fffd`|�|REPLACEMENT CHARACTER|So|2 +|`U+20ac`|€|EURO SIGN|Sc|1 +|`U+0160`|Š|LATIN CAPITAL LETTER S WITH CARON|Lu|1 +|`U+2019`|’|RIGHT SINGLE QUOTATION MARK|Pf|1 +|==== + +## Other +[cols=">2,^1,11,1,>1"] +|==== +|`U+03b9`|ι|GREEK SMALL LETTER IOTA|Ll|1 +|`U+0131`|ı|LATIN SMALL LETTER DOTLESS I|Ll|1 +|`U+03ad`|έ|GREEK SMALL LETTER EPSILON WITH TONOS|Ll|2 +|`U+03c6`|φ|GREEK SMALL LETTER PHI|Ll|2 +|`U+03c5`|υ|GREEK SMALL LETTER UPSILON|Ll|2 +|`U+03c1`|ρ|GREEK SMALL LETTER RHO|Ll|2 +|`U+03bf`|ο|GREEK SMALL LETTER OMICRON|Ll|2 +|`U+03c2`|ς|GREEK SMALL LETTER FINAL SIGMA|Ll|2 +|`U+03c0`|π|GREEK SMALL LETTER PI|Ll|2 +|`U+03bc`|μ|GREEK SMALL LETTER MU|Ll|3 +|`U+03b5`|ε|GREEK SMALL LETTER EPSILON|Ll|3 +|`U+6587`|文|CJK UNIFIED IDEOGRAPH-6587|Lo|2 +|`U+5b57`|字|CJK UNIFIED IDEOGRAPH-5B57|Lo|2 +|`U+5316`|化|CJK UNIFIED IDEOGRAPH-5316|Lo|2 +|`U+3051`|け|HIRAGANA LETTER KE|Lo|2 +|`U+03a9`|Ω|GREEK CAPITAL LETTER OMEGA|Lu|1 +|`U+0130`|İ|LATIN CAPITAL LETTER I WITH DOT ABOVE|Lu|1 +|`U+0418`|И|CYRILLIC CAPITAL LETTER I|Lu|2 +|`U+0396`|Ζ|GREEK CAPITAL LETTER ZETA|Lu|2 +|`U+32b7`|㊷|CIRCLED NUMBER FORTY TWO|No|1 +|`U+fe35`|︵|PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS|Ps|2 +|`U+221e`|∞|INFINITY|Sm|1 +|`U+2044`|⁄|FRACTION SLASH|Sm|4 +|`U+25a1`|□|WHITE SQUARE|So|2 +|`U+2501`|━|BOX DRAWINGS HEAVY HORIZONTAL|So|2 +|`U+256f`|╯|BOX DRAWINGS LIGHT ARC UP AND LEFT|So|4 +|`U+253b`|┻|BOX DRAWINGS HEAVY UP AND HORIZONTAL|So|4 +|==== diff --git a/glifos/vol1-cap04.pdf b/glifos/vol1-cap04.pdf new file mode 100644 index 00000000..815525ac Binary files /dev/null and b/glifos/vol1-cap04.pdf differ diff --git a/glifos/vol1-sem-cap4.adoc b/glifos/vol1-sem-cap4.adoc new file mode 100644 index 00000000..1bde9836 --- /dev/null +++ b/glifos/vol1-sem-cap4.adoc @@ -0,0 +1,46 @@ +Generated 13:44:32 + +## Latin 1 + +|==== +|ç|ã|á|é|ú|õ|ó|í|ô|ê|à|â|É|ª|ü|ý|Á|¢|£|¥|¤|²|×|ö|§|µ|¶|©|°|÷|±|¬ +|®|Ã|Ô|À|Ú| | | | | | | | | | | | | | | | | | | | | | | | | | | +|==== + +## CP1252 + +[cols=">2,^1,11,1,>1"] +|==== +|`U+2014`|—|EM DASH|Pd|288 +|`U+2026`|…|HORIZONTAL ELLIPSIS|Po|46 +|`U+201c`|“|LEFT DOUBLE QUOTATION MARK|Pi|18 +|`U+201d`|”|RIGHT DOUBLE QUOTATION MARK|Pf|17 +|`U+2019`|’|RIGHT SINGLE QUOTATION MARK|Pf|8 +|`U+20ac`|€|EURO SIGN|Sc|4 +|`U+2018`|‘|LEFT SINGLE QUOTATION MARK|Pi|3 +|`U+0160`|Š|LATIN CAPITAL LETTER S WITH CARON|Lu|2 +|`U+2013`|–|EN DASH|Pd|2 +|==== + +## Other +[cols=">2,^1,11,1,>1"] +|==== +|`U+0141`|Ł|LATIN CAPITAL LETTER L WITH STROKE|Lu|1 +|`U+fe0f`|️|VARIATION SELECTOR-16|Mn|60 +|`U+2264`|≤|LESS-THAN OR EQUAL TO|Sm|1 +|`U+222a`|∪|UNION|Sm|1 +|`U+2206`|∆|INCREMENT|Sm|1 +|`U+2205`|∅|EMPTY SET|Sm|1 +|`U+2208`|∈|ELEMENT OF|Sm|1 +|`U+2286`|⊆|SUBSET OF OR EQUAL TO|Sm|1 +|`U+2282`|⊂|SUBSET OF|Sm|1 +|`U+2287`|⊇|SUPERSET OF OR EQUAL TO|Sm|1 +|`U+2283`|⊃|SUPERSET OF|Sm|1 +|`U+2229`|∩|INTERSECTION|Sm|2 +|`U+2265`|≥|GREATER-THAN OR EQUAL TO|Sm|13 +|`U+1f64f`|🙏|PERSON WITH FOLDED HANDS|So|1 +|`U+26a0`|⚠|WARNING SIGN|So|23 +|`U+2712`|✒|BLACK NIB|So|37 +|`U+1f449`|👉|WHITE RIGHT POINTING BACKHAND INDEX|So|40 +|`U+25cf`|●|BLACK CIRCLE|So|249 +|==== diff --git a/glifos/vol1-sem-cap4.pdf b/glifos/vol1-sem-cap4.pdf new file mode 100644 index 00000000..8e2aed43 Binary files /dev/null and b/glifos/vol1-sem-cap4.pdf differ diff --git a/guia-de-estilo.adoc b/guia-de-estilo.adoc index c6b12c8e..7a024299 100644 --- a/guia-de-estilo.adoc +++ b/guia-de-estilo.adoc @@ -5,6 +5,86 @@ Padrões gráficos e de linguagem adotados nesta tradução. +## Formatação + +### Uso de maiúsculas em títulos + +Somente a primeira letra do título das partes, capítulos e seções deve estar em maiúsculas +(além de nomes próprios ;-). + +Padrão: + +*Aventuras aquáticas ao longo do Amazonas* + +Fora do padrão: + +*Aventuras Aquáticas ao Longo do Amazonas* + + +### Uso de pontuação + +#### Legendas de exemplos + + + +### Código dentro do texto corrido + +`Identificadores` de Python devem ser marcados no Asciidoc como `pass:[`monospace`]` (delimitado por crases, `chr(96)`, Unicode GRAVE ACCENT). + +Nos identificadores `+__dunder__+`, é preciso colocar um par de `{plus}` dentro das crases, assim: `pass:[`+__dunder__+`]`. +Isso se aplica também a expressões compostas como `+complex.__float__+` que precisa ser escrita como `pass:[`+complex.__float__+`]` + +Ver https://docs.asciidoctor.org/asciidoc/latest/text/literal-monospace/[literal monospace] na documentação do Asciidoctor. + + +### Usar atributos pré-definidos para caracteres especiais + +O Asciidoctor tem uma série de atributos pré-definidos que são úteis para evitar conflitos de marcação. +Por exemplo, o caractere {plus} serve para marcar quebra de linha, e {cpp} +pode ser ainda mais complicado, então é melhor usar os atributos `pass:[{plus}]` e `pass:[{cpp}]`. + +Lista completa: https://docs.asciidoctor.org/asciidoc/latest/attributes/character-replacement-ref/[Character Replacement Attributes Reference] + + +### Reiniciar numeração de exemplos e figuras por capítulo + +Logo abaixo do título do capítulo, zere os atribuitos `:example-number:` e `:figure-number:`. + +Exemplo de `cap06.adoc`: + +++++ +
+[[ch_refs_mut_mem]]
+== Referências, Mutabilidade, e Memória
+:example-number: 0
+:figure-number: 0
+
+++++ + +### 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 Os termos abaixo geram dúvidas sobre tradução: @@ -33,7 +113,7 @@ Termo em português adotado:: embutido _(built-in)_ Termo em inglês adotado:: - _o cluster_ (agrupamento) + _cluster_ (agrupamento) **Legenda** @@ -42,21 +122,24 @@ Termo em inglês adotado:: |✅| termo adotado |❗| termo não adotado |❓| não conhecemos uma boa tradução -|🔎| ocorrências a corrigir +|🔎| possíveis ocorrências para buscar e corrigir |=== [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 +|❗ _bitwise_ |✅ bit-a-bit | 🔎 bit a bit |❗ _built-in_ |✅ embutido | |✅ _cache_ (s.m.) |❓ | |✅ _caching_ (s.m.) |❓ | -|❗ _callable_ |✅ invocável (s.m.) | 🔎 chamável +|❗ _call_ (verb) |✅ invocar (executar uma função) | ex: "a função é invocada pelo pelo interpretador", 🔎 chamar +|❗ _call_ (verb) |✅ chamar (dar nome) | ex: "a função chamada `bla`" +|❗ _callable_ |✅ invocável | 🔎 chamável |✅ _callback_ (s.f.) |❗ função que trata o evento | 🔎 função de retorno |❗ _closure_ |✅ clausura | preserva variáveis livres no momento da definição da função |✅ _cluster_ (s.m.) |❓ | 🔎 agrupamento @@ -65,11 +148,13 @@ Termo em inglês adotado:: |❗ _core_ |✅ núcleo | "CPU de 8 núcleos" |❗ _coroutine_ |✅ corrotina | |❗ _CPU bound_ |✅ limitado pela CPU | +|❗ _custom_ |✅ customizado | 🔎 personalizado |✅ _deadlock_ (s.m.) |❗ impasse | |❗ _duck typing_ |✅ tipagem pato | -|❗ _evaluate_ |✅ avaliar | contraste com "analisar" (_parse_) +|❗ _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.) |❓ | @@ -77,11 +162,17 @@ Termo em inglês adotado:: |✅ _hashable_ (adj.) |❓ | |✅ _hook_ (s.m.) |❗gancho | |❗ _I/O bound_ | ✅ limitado por E/S | +|❗ _import time_ | ✅ momento da importação | 🔎 tempo de importação +|❗ _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 | 🔎 sobrepor |❗ _overloading_ | ✅ sobrecarga | |❗ _overloaded signatures_ |✅ assinaturas sobrecarregadas| |❗ _parse_ |✅ analise (ou análise sintática) | contraste com "avaliar" (_evaluate_) @@ -91,16 +182,21 @@ Termo em inglês adotado:: |❗ _performance_ |✅ desempenho | |❗ _query_ |✅ consulta | |❗ _queue_ |✅ fila | -|❗ _receiver_ |✅ receptor | 🔎 recebido, recipiente +|❗ _receiver_ |✅ receptor | 🔎 recebido, recipiente, destinatário |❗ _return_ (flow control) |✅ retorna | "A função retorna após 10s." |❗ _return_ (value) |✅ devolve | "A função devolve a lista de estudantes." +|❗ _reversed_ (operador) |✅ reverso (ex.: `+__rmul__+`) | 🔎 inverso, invertido +|❗ _reversed_ (ordem) |✅ invertido | "reversed(x) devolve uma lista invertida" |❗ _S-expression_ |✅ expressão-S | +|❗ _source code_ |✅ código-fonte | 🔎 código fonte |❗ _stack_ |✅ pilha | |❗ _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) |❗ _type hint_ |✅ dica de tipo | |❗ _type variable_ |✅ variável de tipo| |❗ _type-driven development_|✅ desenvolvimento orientado a tipos| @@ -108,39 +204,3 @@ Termo em inglês adotado:: |=== -## Dicas de formatação - -### Uso de maiúsculas em títulos - -Somente a primeira letra do título das partes, capítulos e seções deve estar em maiúsculas -(além de nomes próprios ;-). - -Padrão: + -*Aventuras aquáticas ao longo do Amazonas* - -Fora do padrão: + -*Aventuras Aquáticas ao Longo do Amazonas* - -### Código dentro do texto corrido - -`Identificadores` de Python devem ser marcados no Asciidoc como `pass:[`monospace`]` (delimitado por crases, `chr(96)`, Unicode GRAVE ACCENT). - -Nos identificadores `+__dunder__+`, é preciso colocar um par de `pass:[+]` dentro das crases, assim: `pass:[`+__dunder__+`]`. -Isso se aplica também a expressões compostas como `+complex.__float__+` que precisa ser escrita como `pass:[`+complex.__float__+`]` - -Ver https://docs.asciidoctor.org/asciidoc/latest/text/literal-monospace/[literal monospace] na documentação do Asciidoctor. - -### Reiniciar numeração de exemplos e figuras por capítulo - -Logo abaixo do título do capítulo, zere o atribuitos `:example-number:` e `:figure-number:`. - -Exemplo de `cap06.adoc`: - -++++ -
-[[mutability_and_references]]
-== Referências, Mutabilidade, e Memória
-:example-number: 0
-:figure-number: 0
-
-++++ diff --git a/images/abc-uml.graffle b/images/abc-uml.graffle new file mode 100644 index 00000000..f682b1c2 Binary files /dev/null and b/images/abc-uml.graffle differ diff --git a/images/diag-filter.png b/images/diag-filter.png new file mode 100644 index 00000000..b904bb02 Binary files /dev/null and b/images/diag-filter.png differ diff --git a/images/diag-filter.xcf b/images/diag-filter.xcf new file mode 100644 index 00000000..7cb95ad7 Binary files /dev/null and b/images/diag-filter.xcf differ diff --git a/images/diag-groupby.png b/images/diag-groupby.png new file mode 100644 index 00000000..fc4c9d0e Binary files /dev/null and b/images/diag-groupby.png differ diff --git a/images/diag-groupby.xcf b/images/diag-groupby.xcf new file mode 100644 index 00000000..c7d33451 Binary files /dev/null and b/images/diag-groupby.xcf differ diff --git a/images/diag-map-reduce.png b/images/diag-map-reduce.png new file mode 100644 index 00000000..53cbe1e8 Binary files /dev/null and b/images/diag-map-reduce.png differ diff --git a/images/diag-map.png b/images/diag-map.png new file mode 100644 index 00000000..e30568bd Binary files /dev/null and b/images/diag-map.png differ diff --git a/images/diag-map.xcf b/images/diag-map.xcf new file mode 100644 index 00000000..560584f4 Binary files /dev/null and b/images/diag-map.xcf differ diff --git a/images/diag-reduce.png b/images/diag-reduce.png new file mode 100644 index 00000000..34389f70 Binary files /dev/null and b/images/diag-reduce.png differ diff --git a/images/diag-reduce.xcf b/images/diag-reduce.xcf new file mode 100644 index 00000000..2b941c60 Binary files /dev/null and b/images/diag-reduce.xcf differ diff --git a/images/diagrama9-1.odg b/images/diagrama9-1.odg new file mode 100644 index 00000000..1b563447 Binary files /dev/null and b/images/diagrama9-1.odg differ diff --git a/images/diagrama9-1.png b/images/diagrama9-1.png new file mode 100644 index 00000000..cc983ccc Binary files /dev/null and b/images/diagrama9-1.png differ diff --git a/images/flpy_0201.png b/images/flpy_0201.png index 4edf4c9f..d187ca89 100644 Binary files a/images/flpy_0201.png and b/images/flpy_0201.png differ diff --git a/images/flpy_0402-EN.png b/images/flpy_0402-EN.png new file mode 100644 index 00000000..f2b5ff2a Binary files /dev/null and b/images/flpy_0402-EN.png differ diff --git a/images/flpy_0402.png b/images/flpy_0402.png index f2b5ff2a..09c8174f 100644 Binary files a/images/flpy_0402.png and b/images/flpy_0402.png differ diff --git a/images/flpy_0405.png b/images/flpy_0405.png index beb73736..30d51c99 100644 Binary files a/images/flpy_0405.png and b/images/flpy_0405.png differ diff --git a/images/flpy_0406.png b/images/flpy_0406.png index be93b00d..ea110c97 100644 Binary files a/images/flpy_0406.png and b/images/flpy_0406.png differ diff --git a/images/flpy_0407.png b/images/flpy_0407.png index 45336913..ffc1a30c 100644 Binary files a/images/flpy_0407.png and b/images/flpy_0407.png differ diff --git a/images/flpy_0408.png b/images/flpy_0408.png index cd14c229..96bf13c0 100644 Binary files a/images/flpy_0408.png and b/images/flpy_0408.png differ diff --git a/images/flpy_0901-EN.png b/images/flpy_0901-EN.png new file mode 100644 index 00000000..78862bf1 Binary files /dev/null and b/images/flpy_0901-EN.png differ diff --git a/images/flpy_1001-EN.png b/images/flpy_1001-EN.png new file mode 100644 index 00000000..e547b7e4 Binary files /dev/null and b/images/flpy_1001-EN.png differ diff --git a/images/flpy_1001.png b/images/flpy_1001.png index e547b7e4..5b4e713d 100644 Binary files a/images/flpy_1001.png and b/images/flpy_1001.png differ diff --git a/images/flpy_1002-EN.png b/images/flpy_1002-EN.png new file mode 100644 index 00000000..026fc2bd Binary files /dev/null and b/images/flpy_1002-EN.png differ diff --git a/images/flpy_1002.odg b/images/flpy_1002.odg new file mode 100644 index 00000000..3b841df2 Binary files /dev/null and b/images/flpy_1002.odg differ diff --git a/images/flpy_1002.png b/images/flpy_1002.png index 026fc2bd..aec01dfa 100644 Binary files a/images/flpy_1002.png and b/images/flpy_1002.png differ diff --git a/images/flpy_1301.png b/images/flpy_1301.png index 01e18555..af5594ae 100644 Binary files a/images/flpy_1301.png and b/images/flpy_1301.png differ diff --git a/images/flpy_1308.png b/images/flpy_1308.png index 3eebe329..5f9a8659 100644 Binary files a/images/flpy_1308.png and b/images/flpy_1308.png differ diff --git a/images/flpy_2101-EN.png b/images/flpy_2101-EN.png new file mode 100644 index 00000000..5f6bc014 Binary files /dev/null and b/images/flpy_2101-EN.png differ diff --git a/images/flpy_2101.png b/images/flpy_2101.png index 5f6bc014..82e46420 100644 Binary files a/images/flpy_2101.png and b/images/flpy_2101.png differ diff --git a/images/flpy_2101.xcf b/images/flpy_2101.xcf new file mode 100644 index 00000000..14d8b704 Binary files /dev/null and b/images/flpy_2101.xcf differ diff --git a/images/flpy_2303.png b/images/flpy_2303.png index 4dc249ed..f0ec0654 100644 Binary files a/images/flpy_2303.png and b/images/flpy_2303.png differ diff --git a/images/flpy_2403.png b/images/flpy_2403.png index adf45729..6af4d257 100644 Binary files a/images/flpy_2403.png and b/images/flpy_2403.png differ diff --git a/images/mapa-da-tipagem-linguagens.odg b/images/mapa-da-tipagem-linguagens.odg new file mode 100644 index 00000000..8b31ea60 Binary files /dev/null and b/images/mapa-da-tipagem-linguagens.odg differ diff --git a/images/mapa-da-tipagem-linguagens.png b/images/mapa-da-tipagem-linguagens.png new file mode 100644 index 00000000..65770fdb Binary files /dev/null and b/images/mapa-da-tipagem-linguagens.png differ diff --git a/images/mapa-da-tipagem.odg b/images/mapa-da-tipagem.odg new file mode 100644 index 00000000..2cea1905 Binary files /dev/null and b/images/mapa-da-tipagem.odg differ diff --git a/images/mapa-da-tipagem.png b/images/mapa-da-tipagem.png new file mode 100644 index 00000000..0adea5f9 Binary files /dev/null and b/images/mapa-da-tipagem.png differ diff --git a/images/mojibake-cinza.png b/images/mojibake-cinza.png new file mode 100644 index 00000000..fe836a9c Binary files /dev/null and b/images/mojibake-cinza.png differ diff --git a/images/mojibake.png b/images/mojibake.png new file mode 100644 index 00000000..989f8289 Binary files /dev/null and b/images/mojibake.png differ diff --git a/images/numbers-uml.graffle b/images/numbers-uml.graffle new file mode 100644 index 00000000..41214f3b Binary files /dev/null and b/images/numbers-uml.graffle differ diff --git a/links/FPY.LI.custom.htaccess b/links/FPY.LI.custom.htaccess new file mode 100644 index 00000000..cc779db7 --- /dev/null +++ b/links/FPY.LI.custom.htaccess @@ -0,0 +1,1072 @@ +ErrorDocument 404 /404.html + +# main resources +RedirectTemp /book https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /code https://github.com/fluentpython/example-code-2e +RedirectTemp /home https://www.fluentpython.com/ + +# URLs mentioned at least three times +RedirectTemp /bisect https://www.fluentpython.com/extra/ordered-sequences-with-bisect/ +RedirectTemp /cardxvi https://www.python.org/dev/peps/pep-0484/#the-numeric-tower +RedirectTemp /collec https://docs.python.org/3/library/collections.html +RedirectTemp /dask https://dask.org/ +RedirectTemp /dtmodel https://docs.python.org/3/reference/datamodel.html +RedirectTemp /descr101 https://www.python.org/download/releases/2.2.3/descrintro/ +RedirectTemp /descrhow https://docs.python.org/3/howto/descriptor.html +RedirectTemp /doctest https://docs.python.org/3/library/doctest.html +RedirectTemp /effectpy https://effectivepython.com/ +RedirectTemp /fmtspec https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /gunicorn https://gunicorn.org/ +RedirectTemp /hashint https://www.fluentpython.com/extra/internals-of-sets-and-dicts/ +RedirectTemp /hattingh https://www.oreilly.com/library/view/using-asyncio-in/9781492075325/ +RedirectTemp /httpx https://www.python-httpx.org/ +RedirectTemp /initvar https://docs.python.org/3/library/dataclasses.html#init-only-variables +RedirectTemp /mypy https://mypy.readthedocs.io/en/stable/ +RedirectTemp /norvigdp http://norvig.com/design-patterns/ +RedirectTemp /nsphere https://en.wikipedia.org/wiki/N-sphere +RedirectTemp /oldcoro https://www.fluentpython.com/extra/classic-coroutines/ +RedirectTemp /pandas https://pandas.pydata.org/ +RedirectTemp /pycook3 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ +RedirectTemp /pynut3 https://www.oreilly.com/library/view/python-in-a/9781491913833/ +RedirectTemp /pypydif https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /shed4051 https://github.com/python/typeshed/issues/4051 +RedirectTemp /specattr https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine +RedirectTemp /typing https://docs.python.org/3/library/typing.html +RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ + +# URL added during QA of the Second Edition +RedirectTemp /bdfl https://www.artima.com/weblogs/viewpost.jsp?thread=235725 + +# Python Enhancement Proposals +RedirectTemp /pep218 https://www.python.org/dev/peps/pep-0218/ +RedirectTemp /pep227 https://www.python.org/dev/peps/pep-0227/ +RedirectTemp /pep255 https://www.python.org/dev/peps/pep-0255/ +RedirectTemp /pep342 https://www.python.org/dev/peps/pep-0342/ +RedirectTemp /pep343 https://www.python.org/dev/peps/pep-0343/ +RedirectTemp /pep357 https://www.python.org/dev/peps/pep-0357/ +RedirectTemp /pep362 https://www.python.org/dev/peps/pep-0362/ +RedirectTemp /pep371 https://www.python.org/dev/peps/pep-0371/ +RedirectTemp /pep380 https://www.python.org/dev/peps/pep-0380/ +RedirectTemp /pep393 https://www.python.org/dev/peps/pep-0393/ +RedirectTemp /pep412 https://www.python.org/dev/peps/pep-0412/ +RedirectTemp /pep442 https://www.python.org/dev/peps/pep-0442/ +RedirectTemp /pep443 https://www.python.org/dev/peps/pep-0443/ +RedirectTemp /pep448 https://www.python.org/dev/peps/pep-0448/ +RedirectTemp /pep455 https://www.python.org/dev/peps/pep-0455/ +RedirectTemp /pep456 https://www.python.org/dev/peps/pep-0456/ +RedirectTemp /pep461 https://www.python.org/dev/peps/pep-0461/ +RedirectTemp /pep465 https://www.python.org/dev/peps/pep-0465/ +RedirectTemp /pep467 https://www.python.org/dev/peps/pep-0467/ +RedirectTemp /pep482 https://www.python.org/dev/peps/pep-0482/ +RedirectTemp /pep483 https://www.python.org/dev/peps/pep-0483/ +RedirectTemp /pep484 https://www.python.org/dev/peps/pep-0484/ +RedirectTemp /pep487 https://www.python.org/dev/peps/pep-0487/ +RedirectTemp /pep492 https://www.python.org/dev/peps/pep-0492/ +RedirectTemp /pep519 https://www.python.org/dev/peps/pep-0519/ +RedirectTemp /pep525 https://www.python.org/dev/peps/pep-0525/ +RedirectTemp /pep526 https://www.python.org/dev/peps/pep-0526/ +RedirectTemp /pep528 https://www.python.org/dev/peps/pep-0528/ +RedirectTemp /pep529 https://www.python.org/dev/peps/pep-0529/ +RedirectTemp /pep530 https://www.python.org/dev/peps/pep-0530/ +RedirectTemp /pep544 https://www.python.org/dev/peps/pep-0544/ +RedirectTemp /pep554 https://www.python.org/dev/peps/pep-0554/ +RedirectTemp /pep557 https://www.python.org/dev/peps/pep-0557/ +RedirectTemp /pep560 https://www.python.org/dev/peps/pep-0560/ +RedirectTemp /pep561 https://www.python.org/dev/peps/pep-0561/ +RedirectTemp /pep563 https://www.python.org/dev/peps/pep-0563/ +RedirectTemp /pep570 https://www.python.org/dev/peps/pep-0570/ +RedirectTemp /pep572 https://www.python.org/dev/peps/pep-0572/ +RedirectTemp /pep584 https://www.python.org/dev/peps/pep-0584/ +RedirectTemp /pep585 https://www.python.org/dev/peps/pep-0585/ +RedirectTemp /pep586 https://www.python.org/dev/peps/pep-0586/ +RedirectTemp /pep589 https://www.python.org/dev/peps/pep-0589/ +RedirectTemp /pep591 https://www.python.org/dev/peps/pep-0591/ +RedirectTemp /pep593 https://www.python.org/dev/peps/pep-0593/ +RedirectTemp /pep604 https://www.python.org/dev/peps/pep-0604/ +RedirectTemp /pep612 https://www.python.org/dev/peps/pep-0612/ +RedirectTemp /pep613 https://www.python.org/dev/peps/pep-0613/ +RedirectTemp /pep616 https://www.python.org/dev/peps/pep-0616/ +RedirectTemp /pep617 https://www.python.org/dev/peps/pep-0617/ +RedirectTemp /pep618 https://www.python.org/dev/peps/pep-0618/ +RedirectTemp /pep634 https://www.python.org/dev/peps/pep-0634/ +RedirectTemp /pep635 https://www.python.org/dev/peps/pep-0635/ +RedirectTemp /pep636 https://www.python.org/dev/peps/pep-0636/ +RedirectTemp /pep638 https://www.python.org/dev/peps/pep-0638/ +RedirectTemp /pep645 https://www.python.org/dev/peps/pep-0645/ +RedirectTemp /pep646 https://www.python.org/dev/peps/pep-0646/ +RedirectTemp /pep647 https://www.python.org/dev/peps/pep-0647/ +RedirectTemp /pep649 https://www.python.org/dev/peps/pep-0649/ +RedirectTemp /pep654 https://www.python.org/dev/peps/pep-0654/ +RedirectTemp /pep655 https://www.python.org/dev/peps/pep-0655/ +RedirectTemp /pep661 https://www.python.org/dev/peps/pep-0661/ +RedirectTemp /pep3099 https://www.python.org/dev/peps/pep-3099/ +RedirectTemp /pep3102 https://www.python.org/dev/peps/pep-3102/ +RedirectTemp /pep3104 https://www.python.org/dev/peps/pep-3104/ +RedirectTemp /pep3106 https://www.python.org/dev/peps/pep-3106/ +RedirectTemp /pep3107 https://www.python.org/dev/peps/pep-3107/ +RedirectTemp /pep3115 https://www.python.org/dev/peps/pep-3115/ +RedirectTemp /pep3118 https://www.python.org/dev/peps/pep-3118/ +RedirectTemp /pep3119 https://www.python.org/dev/peps/pep-3119/ +RedirectTemp /pep3129 https://www.python.org/dev/peps/pep-3129/ +RedirectTemp /pep3132 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /pep3141 https://www.python.org/dev/peps/pep-3141/ +RedirectTemp /pep3148 https://www.python.org/dev/peps/pep-3148/ +RedirectTemp /pep3155 https://www.python.org/dev/peps/pep-3155/ +RedirectTemp /pep3333 https://www.python.org/dev/peps/pep-3333/ + +# Remaining URLs by chapter + +############################################################ p +RedirectTemp /p-1 https://mail.python.org/pipermail/python-list/2002-December/134521.html +RedirectTemp /p-2 https://docs.python.org/3.10/tutorial/ +RedirectTemp /p-3 https://docs.python.org/3/tutorial/ +RedirectTemp /p-4 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /p-5 https://www.oreilly.com/online-learning/try-now.html +RedirectTemp /p-6 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /p-7 https://stackoverflow.com/users/95810/alex-martelli +RedirectTemp /p-8 https://pythonpro.com.br +RedirectTemp /p-9 https://groups.google.com/g/python-brasil +RedirectTemp /p-10 https://www.coffeelab.com.br/ +RedirectTemp /p-11 https://garoa.net.br/wiki/P%C3%A1gina_principal +############################################################ a +RedirectTemp /a-1 https://groups.google.com/forum/#!topic/python-tulip/Y4bhLNbKs74 +RedirectTemp /a-2 https://docs.python.org/3/library/asyncio-eventloop.html#executor +RedirectTemp /a-3 https://www.youtube.com/watch?v=x-kB2o8sd5c +RedirectTemp /a-4 https://www.youtube.com/watch?v=OSGv2VnC0go +RedirectTemp /a-5 https://mail.python.org/pipermail/python-ideas/2015-March/032557.html +RedirectTemp /a-6 https://pypi.org/project/pep8/ +RedirectTemp /a-7 https://pypi.org/project/flake8/ +RedirectTemp /a-8 https://pypi.org/project/pyflakes/ +RedirectTemp /a-9 https://pypi.org/project/mccabe/ +RedirectTemp /a-10 https://google.github.io/styleguide/pyguide.html +RedirectTemp /a-11 https://flask.palletsprojects.com/en/1.1.x/styleguide/ +RedirectTemp /a-12 https://docs.python-guide.org/ +RedirectTemp /a-13 https://david.goodger.org/projects/pycon/2007/idiomatic/handout.html +RedirectTemp /a-14 https://docs.mongodb.com/manual/about/#about-the-documentation-process +RedirectTemp /a-15 https://blog.startifact.com/posts/older/what-is-pythonic.html +RedirectTemp /a-16 https://mail.python.org/pipermail/tutor/2003-October/thread.html#25930 +RedirectTemp /a-17 https://mail.python.org/pipermail/python-list/2003-April/192027.html +RedirectTemp /a-18 https://www.python.org/doc/essays/ +############################################################ 01 +RedirectTemp /1-1 http://hugunin.net/story_of_jython.html +RedirectTemp /1-2 https://www.oreilly.com/library/view/jython-essentials/9781449397364/ +RedirectTemp /1-3 https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers +RedirectTemp /1-4 https://docs.python.org/3.10/library/string.html#format-string-syntax +RedirectTemp /1-5 https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr +RedirectTemp /1-6 https://docs.python.org/3/library/stdtypes.html#truth +RedirectTemp /1-7 https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists +RedirectTemp /1-8 https://www.python.org/doc/humor/#the-zen-of-python +RedirectTemp /1-9 https://stackoverflow.com/users/95810/alex-martelli +RedirectTemp /1-10 https://en.wikipedia.org/wiki/Object_model +RedirectTemp /1-11 https://www.dourish.com/goodies/jargon.html +RedirectTemp /1-12 https://zopeinterface.readthedocs.io/en/latest/ +RedirectTemp /1-13 https://plone.org/ +############################################################ 02 +RedirectTemp /2-1 https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/listcomp_speed.py +RedirectTemp /2-2 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /2-3 https://stackoverflow.com/questions/68630/are-tuples-more-efficient-than-lists-in-python/22140115#22140115 +RedirectTemp /2-4 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2-5 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2-6 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2-7 https://docs.python.org/3.10/whatsnew/3.10.html +RedirectTemp /2-8 https://en.wikipedia.org/wiki/Switch_statement#Fallthrough +RedirectTemp /2-9 https://en.wikipedia.org/wiki/Dangling_else +RedirectTemp /2-10 https://github.com/gvanrossum/patma/blob/3ece6444ef70122876fd9f0099eb9490a2d630df/EXAMPLES.md#case-6-a-very-deep-iterable-and-type-match-with-extraction +RedirectTemp /2-11 https://github.com/fluentpython/lispy/blob/main/original/norvig/lis.py +RedirectTemp /2-12 https://norvig.com/lispy.html +RedirectTemp /2-13 https://numpy.org/doc/stable/user/quickstart.html#indexing-slicing-and-iterating +RedirectTemp /2-14 https://pythontutor.com/ +RedirectTemp /2-15 https://en.wikipedia.org/wiki/Fluent_interface +RedirectTemp /2-16 https://docs.python.org/3/library/bisect.html#bisect.insort +RedirectTemp /2-17 https://stackoverflow.com/questions/4845418/when-should-a-memoryview-be-used/ +RedirectTemp /2-18 https://www.fluentpython.com/extra/parsing-binary-struct/ +RedirectTemp /2-19 http://www.netlib.org +RedirectTemp /2-20 https://pandas.pydata.org/ +RedirectTemp /2-21 https://scikit-learn.org/stable/ +RedirectTemp /2-22 https://docs.python.org/3/howto/sorting.html +RedirectTemp /2-23 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /2-24 https://bugs.python.org/issue2292 +RedirectTemp /2-25 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2-26 https://docs.python.org/3.10/whatsnew/3.10.html +RedirectTemp /2-27 https://www.python.org/dev/peps/pep-0636/#appendix-a-quick-intro +RedirectTemp /2-28 https://eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol-and-memoryviews/ +RedirectTemp /2-29 https://jakevdp.github.io/PythonDataScienceHandbook/ +RedirectTemp /2-30 https://www.oreilly.com/library/view/python-for-data/9781491957653/ +RedirectTemp /2-31 https://www.labri.fr/perso/nrougier/from-python-to-numpy/ +RedirectTemp /2-32 https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html +RedirectTemp /2-33 http://www.fonts101.com/fonts/view/Uncategorized/34398/Dijkstra +RedirectTemp /2-34 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /2-35 https://en.wikipedia.org/wiki/Timsort +RedirectTemp /2-36 http://www.groklaw.net/pdf3/OraGoogle-1202.pdf +RedirectTemp /2-37 https://www.python.org/doc/humor/#id9 +############################################################ 03 +RedirectTemp /3-1 https://www.python.org/dev/peps/pep-0584/#motivation +RedirectTemp /3-2 https://docs.python.org/3.10/c-api/typeobj.html#Py_TPFLAGS_MAPPING +RedirectTemp /3-3 https://docs.python.org/3/glossary.html#term-hashable +RedirectTemp /3-4 https://docs.python.org/3/glossary.html#term-hashable +RedirectTemp /3-5 http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf +RedirectTemp /3-6 https://github.com/pingo-io/pingo-py +RedirectTemp /3-7 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/missing.py +RedirectTemp /3-8 https://docs.python.org/3/library/collections.html#collections.ChainMap +RedirectTemp /3-9 https://docs.python.org/3/library/collections.html#collections.Counter +RedirectTemp /3-10 https://docs.python.org/3/library/shelve.html +RedirectTemp /3-11 https://docs.python.org/3/library/dbm.html +RedirectTemp /3-12 https://docs.python.org/3/library/pickle.html +RedirectTemp /3-13 https://nedbatchelder.com/blog/202006/pickles_nine_flaws.html +RedirectTemp /3-14 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py#L813 +RedirectTemp /3-15 https://mail.python.org/pipermail/python-dev/2015-May/140003.html +RedirectTemp /3-16 https://bugs.python.org/issue18986 +RedirectTemp /3-17 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/transformdict.py +RedirectTemp /3-18 http://gandenberger.org/2018/03/10/ordered-dicts-vs-ordereddict/ +RedirectTemp /3-19 https://www.pypy.org/ +RedirectTemp /3-20 https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html +RedirectTemp /3-21 https://www.npopov.com/2014/12/22/PHPs-new-hashtable-implementation.html +RedirectTemp /3-22 https://www.youtube.com/watch?v=66P5FMkWoVU +RedirectTemp /3-23 https://pyvideo.org/video/276/the-mighty-dictionary-55/ +RedirectTemp /3-24 https://www.youtube.com/watch?v=p33CVV29OG8 +RedirectTemp /3-25 https://docs.python.org/3/whatsnew/3.6.html#new-dict-implementation +RedirectTemp /3-26 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictobject.c +RedirectTemp /3-27 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictnotes.txt +RedirectTemp /3-28 https://www.youtube.com/watch?v=tGAngdU_8D8 +RedirectTemp /3-29 https://speakerdeck.com/ramalho/python-set-practice-at-pycon +RedirectTemp /3-30 https://github.com/standupdev/uintset +RedirectTemp /3-31 https://spectrum.ieee.org/hans-peter-luhn-and-the-birth-of-the-hashing-algorithm +RedirectTemp /3-32 http://www.json.org/fatfree.html +RedirectTemp /3-33 https://twitter.com/mitsuhiko/status/1229385843585974272 +############################################################ 04 +RedirectTemp /4-1 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 +RedirectTemp /4-2 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ +RedirectTemp /4-3 https://www.fluentpython.com/extra/parsing-binary-struct/ +RedirectTemp /4-4 https://www.fluentpython.com/extra/multi-character-emojis/ +RedirectTemp /4-5 https://w3techs.com/technologies/overview/character_encoding +RedirectTemp /4-6 https://docs.python.org/3/library/codecs.html#codecs.register_error +RedirectTemp /4-7 https://docs.python.org/3/library/stdtypes.html#str.isascii +RedirectTemp /4-8 https://pypi.org/project/chardet/ +RedirectTemp /4-9 https://docs.python.org/3/library/codecs.html#encodings-and-unicode +RedirectTemp /4-10 https://nedbatchelder.com/text/unipain/unipain.html +RedirectTemp /4-11 https://devblogs.microsoft.com/commandline/windows-command-line-unicode-and-utf-8-output-text-buffer/ +RedirectTemp /4-12 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONIOENCODING +RedirectTemp /4-13 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONLEGACYWINDOWSSTDIO +RedirectTemp /4-14 https://docs.python.org/3/library/locale.html#locale.getpreferredencoding +RedirectTemp /4-15 http://www.w3.org/TR/charmod-norm/ +RedirectTemp /4-16 https://docs.python.org/3/library/locale.html?highlight=strxfrm#locale.strxfrm +RedirectTemp /4-17 https://pypi.org/project/pyuca/ +RedirectTemp /4-18 https://github.com/jtauber/pyuca +RedirectTemp /4-19 http://www.unicode.org/Public/UCA/6.3.0/allkeys.txt +RedirectTemp /4-20 https://pypi.org/project/PyICU/ +RedirectTemp /4-21 https://docs.python.org/3.10/library/stdtypes.html#str.isalpha +RedirectTemp /4-22 https://en.wikipedia.org/wiki/Unicode_character_property#General_Category +RedirectTemp /4-23 https://en.wikipedia.org/wiki/Unicode_character_property +RedirectTemp /4-24 https://github.com/microsoft/terminal +RedirectTemp /4-25 https://docs.python.org/3/library/unicodedata.html +RedirectTemp /4-26 https://docs.python.org/3/reference/lexical_analysis.html#string-literal-concatenation +RedirectTemp /4-27 https://docs.python.org/3/library/re.html +RedirectTemp /4-28 https://nedbatchelder.com/text/unipain.html +RedirectTemp /4-29 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 +RedirectTemp /4-30 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ +RedirectTemp /4-31 https://regebro.wordpress.com/2011/03/23/unconfusing-unicode-what-is-unicode/ +RedirectTemp /4-32 https://docs.python.org/3/howto/unicode.html +RedirectTemp /4-33 https://diveintopython3.net/strings.html +RedirectTemp /4-34 https://diveintopython3.net/ +RedirectTemp /4-35 https://finderiko.com/python-book +RedirectTemp /4-36 https://docs.python.org/3.0/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit +RedirectTemp /4-37 https://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/ +RedirectTemp /4-38 http://python-notes.curiousefficiency.org/en/latest/python3/binary_protocols.html +RedirectTemp /4-39 http://python-notes.curiousefficiency.org/en/latest/python3/text_file_processing.html +RedirectTemp /4-40 https://docs.python.org/3/library/codecs.html#standard-encodings +RedirectTemp /4-41 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Tools/unicode/listcodecs.py +RedirectTemp /4-42 https://www.oreilly.com/library/view/unicode-explained/059610121X/ +RedirectTemp /4-43 https://www.informit.com/store/unicode-demystified-a-practical-programmers-guide-to-9780201700527 +RedirectTemp /4-44 https://unicodebook.readthedocs.io/index.html +RedirectTemp /4-45 https://www.w3.org/International/wiki/Case_folding +RedirectTemp /4-46 http://www.w3.org/TR/charmod-norm/ +RedirectTemp /4-47 http://unicode.org/reports/tr15/ +RedirectTemp /4-48 http://www.unicode.org/faq/normalization.html +RedirectTemp /4-49 http://www.unicode.org/ +RedirectTemp /4-50 http://www.macchiato.com/unicode/nfc-faq +RedirectTemp /4-51 https://stories.moma.org/the-original-emoji-set-has-been-added-to-the-museum-of-modern-arts-collection-c6060e141f61?gi=c403f5a840a5 +RedirectTemp /4-52 https://emojipedia.org/ +RedirectTemp /4-53 https://blog.emojipedia.org/correcting-the-record-on-the-first-emoji-set/ +RedirectTemp /4-54 http://emojitracker.com/ +RedirectTemp /4-55 http://www.unicode.org/glossary/#plain_text +RedirectTemp /4-56 http://www.methods.co.nz/asciidoc/ +RedirectTemp /4-57 https://atlas.oreilly.com/ +############################################################ 05 +RedirectTemp /5-1 https://docs.python.org/3/library/typing.html#typing.TypedDict +RedirectTemp /5-2 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations +RedirectTemp /5-3 https://docs.python.org/3/library/typing.html#typing.get_type_hints +RedirectTemp /5-4 https://docs.python.org/3.8/library/collections.html#collections.somenamedtuple._asdict +RedirectTemp /5-5 https://www.jetbrains.com/pycharm/ +RedirectTemp /5-6 https://www.python.org/dev/peps/pep-0484/#acceptable-type-hints +RedirectTemp /5-7 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /5-8 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /5-9 https://docs.python.org/3/library/dataclasses.html +RedirectTemp /5-10 https://docs.python.org/3/library/dataclasses.html#inheritance +RedirectTemp /5-11 https://www.python.org/dev/peps/pep-0526/#class-and-instance-variable-annotations +RedirectTemp /5-12 https://dublincore.org/specifications/dublin-core/ +RedirectTemp /5-13 https://en.wikipedia.org/wiki/Dublin_Core +RedirectTemp /5-14 https://martinfowler.com/bliki/CodeSmell.html +RedirectTemp /5-15 https://martinfowler.com/books/refactoring.html +RedirectTemp /5-16 https://www.python.org/dev/peps/pep-0634/#class-patterns +RedirectTemp /5-17 https://docs.python.org/3/library/dataclasses.html +RedirectTemp /5-18 https://www.python.org/dev/peps/pep-0557/#id47 +RedirectTemp /5-19 https://www.python.org/dev/peps/pep-0557/#id48 +RedirectTemp /5-20 https://www.python.org/dev/peps/pep-0557/#id33 +RedirectTemp /5-21 https://realpython.com +RedirectTemp /5-22 https://realpython.com/python-data-classes/ +RedirectTemp /5-23 https://www.youtube.com/watch?v=T-TwcmT6Rcw +RedirectTemp /5-24 https://www.attrs.org/en/stable/ +RedirectTemp /5-25 https://glyph.twistedmatrix.com/2016/08/attrs.html +RedirectTemp /5-26 https://www.attrs.org/en/stable/why.html +RedirectTemp /5-27 https://github.com/dabeaz/cluegen +RedirectTemp /5-28 https://refactoring.guru/ +RedirectTemp /5-29 https://refactoring.guru/smells/data-class +RedirectTemp /5-30 https://web.archive.org/web/20190204130328/http://catb.org/esr/jargon/html/G/Guido.html +RedirectTemp /5-31 https://web.archive.org/web/20190211161610/http://catb.org/esr/jargon/html/index.html +RedirectTemp /5-32 https://www.attrs.org/en/stable/ +############################################################ 06 +RedirectTemp /6-1 https://www.olin.edu/faculty/profile/lynn-andrea-stein/ +RedirectTemp /6-2 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /6-3 https://pythontutor.com/ +RedirectTemp /6-4 https://docs.python.org/3/library/copy.html +RedirectTemp /6-5 https://en.wikipedia.org/wiki/Principle_of_least_astonishment +RedirectTemp /6-6 https://docs.python.org/3/reference/datamodel.html#object.%5C_%5C_del__ +RedirectTemp /6-7 https://emptysqua.re/blog/pypy-garbage-collection-and-a-deadlock/ +RedirectTemp /6-8 https://www.youtube.com/watch?v=HHFCFJSPWrI&feature=youtu.be +RedirectTemp /6-9 http://pymotw.com/3/copy/ +RedirectTemp /6-10 http://pymotw.com/3/weakref/ +RedirectTemp /6-11 https://docs.python.org/3/library/gc.html +RedirectTemp /6-12 https://devguide.python.org/garbage_collector/ +RedirectTemp /6-13 https://devguide.python.org/ +RedirectTemp /6-14 https://www.python.org/dev/peps/pep-0442/ +RedirectTemp /6-15 https://en.wikipedia.org/wiki/String_interning +RedirectTemp /6-16 https://en.wikipedia.org/wiki/Haddocks%27_Eyes +RedirectTemp /6-17 https://thp.io/2012/python-gc/python_gc_final_2012-01-22.pdf +############################################################ 07 +RedirectTemp /7-1 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html +RedirectTemp /7-2 https://www.fluentpython.com/extra/function-introspection/ +RedirectTemp /7-3 https://docs.python.org/3/library/functions.html#map +RedirectTemp /7-4 https://en.wikipedia.org/wiki/Functional_programming +RedirectTemp /7-5 https://docs.python.org/3/howto/functional.html +RedirectTemp /7-6 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /7-7 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters +RedirectTemp /7-8 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters +RedirectTemp /7-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/functools.py#L341 +RedirectTemp /7-10 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /7-11 https://docs.python.org/3/howto/functional.html +RedirectTemp /7-12 https://stackoverflow.com/questions/3252228/python-why-is-functools-partial-necessary +RedirectTemp /7-13 https://speakerdeck.com/ramalho/beyond-paradigms-berlin-edition +RedirectTemp /7-14 https://www.youtube.com/watch?v=bF3a2VYXxa0 +RedirectTemp /7-15 http://cs.brown.edu/~sk/Publications/Papers/Published/sk-teach-pl-post-linnaean/ +RedirectTemp /7-16 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html +RedirectTemp /7-17 https://raw.githubusercontent.com/python/cpython/main/Misc/HISTORY +RedirectTemp /7-18 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html +############################################################ 08 +RedirectTemp /8-1 https://www.python.org/dev/peps/pep-0484/#non-goals +RedirectTemp /8-2 https://github.com/python/typing/issues/182 +RedirectTemp /8-3 https://github.com/python/mypy/issues/731 +RedirectTemp /8-4 https://github.com/google/pytype +RedirectTemp /8-5 https://github.com/Microsoft/pyright +RedirectTemp /8-6 https://pyre-check.org/ +RedirectTemp /8-7 https://mypy.readthedocs.io/en/stable/introduction.html +RedirectTemp /8-8 https://mypy.readthedocs.io/en/stable/config_file.html +RedirectTemp /8-9 https://pypi.org/project/flake8/ +RedirectTemp /8-10 https://pypi.org/project/blue/ +RedirectTemp /8-11 https://pypi.org/project/black/ +RedirectTemp /8-12 https://wefearchange.org/2020/11/steeringcouncil.rst.html +RedirectTemp /8-13 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes +RedirectTemp /8-14 https://en.wikipedia.org/wiki/Barbara_Liskov +RedirectTemp /8-15 https://en.wikipedia.org/wiki/Behavioral_subtyping +RedirectTemp /8-16 https://www.python.org/dev/peps/pep-0585/#implementation +RedirectTemp /8-17 https://docs.python.org/3/library/typing.html#module-contents +RedirectTemp /8-18 https://en.wikipedia.org/wiki/Geohash +RedirectTemp /8-19 https://en.wikipedia.org/wiki/Inverted_index +RedirectTemp /8-20 https://docs.python.org/3/library/typing.html#typing.List +RedirectTemp /8-21 https://docs.python.org/3/library/typing.html#typing.Dict +RedirectTemp /8-22 https://docs.python.org/3/library/typing.html#typing.Set +RedirectTemp /8-23 https://www.python.org/dev/peps/pep-0585/#implementation +RedirectTemp /8-24 https://docs.python.org/3/library/numbers.html +RedirectTemp /8-25 https://docs.python.org/3/library/typing.html#typing.List +RedirectTemp /8-26 https://github.com/python/typeshed +RedirectTemp /8-27 https://github.com/python/typeshed/blob/66cd36268a6a667714efaa27198a41d0d7f89477/stdlib/2and3/math.pyi#L45 +RedirectTemp /8-28 https://docs.python.org/3/library/statistics.html#statistics.mode +RedirectTemp /8-29 https://github.com/python/cpython/blob/822efa5695b5ba6c2316c1400e4e9ec2546f7ea5/Lib/statistics.py#L534 +RedirectTemp /8-30 https://github.com/python/typeshed/blob/e1e99245bb46223928eba68d4fc74962240ba5b4/stdlib/3/statistics.pyi +RedirectTemp /8-31 https://docs.python.org/3/library/statistics.html#statistics.mode +RedirectTemp /8-32 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/3/statistics.pyi#L32 +RedirectTemp /8-33 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ +RedirectTemp /8-34 https://docs.python.org/3/library/typing.html#typing.Callable +RedirectTemp /8-35 https://pypi.org/project/blue/ +RedirectTemp /8-36 https://www.python.org/dev/peps/pep-0484/#id38 +RedirectTemp /8-37 https://docs.google.com/document/d/1aXs1tpwzPjW9MdsG5dI7clNFyYayFBkcXwRDo-qvbIk/preview +RedirectTemp /8-38 https://www.oreilly.com/library/view/the-best-software/9781590595008/ +RedirectTemp /8-39 https://www.youtube.com/watch?v=YFexUDjHO6w +RedirectTemp /8-40 https://www.youtube.com/watch?v=YFexUDjHO6w&t=13m40s +RedirectTemp /8-41 https://bernat.tech/posts/the-state-of-type-hints-in-python/ +RedirectTemp /8-42 https://realpython.com/python-type-checking/ +RedirectTemp /8-43 https://cjolowicz.github.io/posts/hypermodern-python-04-typing/ +RedirectTemp /8-44 https://mypy.readthedocs.io/en/stable/index.html +RedirectTemp /8-45 https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html +RedirectTemp /8-46 https://mypy.readthedocs.io/en/stable/common_issues.html +RedirectTemp /8-47 https://github.com/typeddjango/awesome-python-typing +RedirectTemp /8-48 https://docs.python.org/3/library/functions.html#max +RedirectTemp /8-49 https://en.wikipedia.org/wiki/Linguistic_relativity +RedirectTemp /8-50 https://pypistats.org/top +RedirectTemp /8-51 https://github.com/psf/requests/issues/3855 +RedirectTemp /8-52 https://lwn.net/Articles/643399/ +RedirectTemp /8-53 https://docs.python-requests.org/en/master/api/#requests.request +RedirectTemp /8-54 https://queue.acm.org/detail.cfm?id=1039523 +############################################################ 09 +RedirectTemp /9-1 https://docs.python.org/3/library/dis.html +RedirectTemp /9-2 https://en.wikipedia.org/wiki/Memoization +RedirectTemp /9-3 https://numpy.org/doc/stable/user/basics.types.html +RedirectTemp /9-4 https://docs.python.org/3/library/functools.html#functools.singledispatch +RedirectTemp /9-5 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/README.md +RedirectTemp /9-6 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/01-how-you-implemented-your-python-decorator-is-wrong.md +RedirectTemp /9-7 https://wrapt.readthedocs.io/en/latest/ +RedirectTemp /9-8 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch09.html +RedirectTemp /9-9 https://pypi.org/project/decorator/ +RedirectTemp /9-10 https://wiki.python.org/moin/PythonDecoratorLibrary +RedirectTemp /9-11 http://web.archive.org/web/20201109032203/http://effbot.org/zone/closure.htm +RedirectTemp /9-12 https://www.python.org/dev/peps/pep-3104/ +RedirectTemp /9-13 https://www.python.org/dev/peps/pep-0227/ +RedirectTemp /9-14 https://www.python.org/dev/peps/pep-0443/ +RedirectTemp /9-15 https://www.artima.com/weblogs/viewpost.jsp?thread=101605 +RedirectTemp /9-16 https://reg.readthedocs.io/en/latest/ +RedirectTemp /9-17 https://morepath.readthedocs.io/en/latest/ +RedirectTemp /9-18 https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Binding.html +RedirectTemp /9-19 http://www.paulgraham.com/rootsoflisp.html +RedirectTemp /9-20 http://www-formal.stanford.edu/jmc/recursive/recursive.html +RedirectTemp /9-21 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this +############################################################ 10 +RedirectTemp /10-1 https://en.wikipedia.org/wiki/Software_design_pattern +RedirectTemp /10-2 https://en.wikipedia.org/wiki/Iterator_pattern +RedirectTemp /10-3 https://github.com/python/mypy/issues/9397 +RedirectTemp /10-4 http://www.norvig.com/design-patterns/index.htm +RedirectTemp /10-5 https://pyvideo.org/video/1110/python-design-patterns/ +RedirectTemp /10-6 http://www.aleax.it/gdd_pydp.pdf +RedirectTemp /10-7 https://perl.plover.com/yak/design/ +RedirectTemp /10-8 https://en.wikipedia.org/wiki/Turtles_all_the_way_down +############################################################ 11 +RedirectTemp /11-1 https://blog.startifact.com/posts/older/what-is-pythonic.html +RedirectTemp /11-2 https://julien.danjou.info/guide-python-static-class-abstract-methods/ +RedirectTemp /11-3 https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /11-4 https://docs.python.org/3/reference/lexical_analysis.html#f-strings +RedirectTemp /11-5 https://docs.python.org/3/library/string.html#format-string-syntax +RedirectTemp /11-6 https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /11-7 https://docs.python.org/3/reference/datamodel.html#object.__hash__ +RedirectTemp /11-8 https://web.archive.org/web/20161025185040/http://pythonpaste.org/StyleGuide.html +RedirectTemp /11-9 https://docs.python.org/3/tutorial/modules.html#more-on-modules +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-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 +RedirectTemp /11-17 https://docs.oracle.com/javase/tutorial/essential/environment/security.html +############################################################ 12 +RedirectTemp /12-1 https://en.wikipedia.org/wiki/Vector_space_model +RedirectTemp /12-2 https://pypi.org/project/gensim/ +RedirectTemp /12-3 https://docs.python.org/3/library/functions.html#enumerate +RedirectTemp /12-4 https://mathworld.wolfram.com/Hypersphere.html +RedirectTemp /12-5 https://en.wikipedia.org/wiki/Fold_(higher-order_function) +RedirectTemp /12-6 https://docs.python.org/2.5/whatsnew/pep-357.html +RedirectTemp /12-7 https://docs.python.org/3/reference/datamodel.html#special-method-names +RedirectTemp /12-8 https://en.wikipedia.org/wiki/KISS_principle +RedirectTemp /12-9 https://mail.python.org/pipermail/python-list/2000-July/046184.html +RedirectTemp /12-10 https://en.wikipedia.org/wiki/Duck_typing +RedirectTemp /12-11 https://mail.python.org/mailman/listinfo/python-list +RedirectTemp /12-12 https://mail.python.org/pipermail/python-list/2003-April/218568.html +############################################################ 13 +RedirectTemp /13-1 https://docs.python.org/3/c-api/index.html +RedirectTemp /13-2 https://docs.python.org/3/c-api/sequence.html +RedirectTemp /13-3 https://github.com/python/cpython/blob/31ceccb2c77854893f3a754aca04bedd74bedb10/Lib/_collections_abc.py#L870 +RedirectTemp /13-4 https://en.wikipedia.org/wiki/Monkey_patch +RedirectTemp /13-5 https://www.gevent.org/api/gevent.monkey.html +RedirectTemp /13-6 https://docs.python.org/3/library/random.html#random.shuffle +RedirectTemp /13-7 https://docs.python.org/3/reference/datamodel.html#emulating-container-types +RedirectTemp /13-8 https://docs.python.org/3/library/collections.html#collections.namedtuple +RedirectTemp /13-9 https://github.com/python/typeshed/blob/24afb531ffd07083d6a74be917342195062f7277/stdlib/collections/__init__.pyi +RedirectTemp /13-10 https://docs.python.org/3/glossary.html#term-abstract-base-class +RedirectTemp /13-11 https://en.wikipedia.org/wiki/Duck_typing#History +RedirectTemp /13-12 http://ptgmedia.pearsoncmg.com/images/020163371x/items/item33.html +RedirectTemp /13-13 https://docs.python.org/3/library/bisect.html#bisect.bisect +RedirectTemp /13-14 https://github.com/python/cpython/blob/main/Lib/_collections_abc.py +RedirectTemp /13-15 https://github.com/python/cpython/blob/main/Lib/abc.py +RedirectTemp /13-16 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes +RedirectTemp /13-17 https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable +RedirectTemp /13-18 https://docs.python.org/3/library/abc.html +RedirectTemp /13-19 https://docs.python.org/dev/library/abc.html#abc.abstractmethod +RedirectTemp /13-20 https://docs.python.org/dev/library/abc.html +RedirectTemp /13-21 https://docs.python.org/3/library/os.html#os.urandom +RedirectTemp /13-22 https://github.com/python/mypy/issues/2922 +RedirectTemp /13-23 https://docs.python.org/3/library/stdtypes.html#truth +RedirectTemp /13-24 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py +RedirectTemp /13-25 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L369 +RedirectTemp /13-26 https://github.com/python/cpython/blob/c0a9afe2ac1820409e6173bd1893ebee2cf50270/Lib/abc.py#L196 +RedirectTemp /13-27 https://bugs.python.org/issue31333 +RedirectTemp /13-28 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Modules/_abc.c#L605 +RedirectTemp /13-29 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L881 +RedirectTemp /13-30 https://docs.python.org/3/library/typing.html#protocols +RedirectTemp /13-31 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Lib/typing.py#L1751 +RedirectTemp /13-32 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ +RedirectTemp /13-33 https://martinfowler.com/bliki/RoleInterface.html +RedirectTemp /13-34 https://en.wikipedia.org/wiki/Interface_segregation_principle +RedirectTemp /13-35 https://github.com/python/typeshed/blob/master/CONTRIBUTING.md +RedirectTemp /13-36 https://gist.github.com/asukakenji/ac8a05644a2e98f1d5ea8c299541fce9 +RedirectTemp /13-37 https://www.python.org/dev/peps/pep-0544/#runtime-checkable-decorator-and-narrowing-types-by-isinstance +RedirectTemp /13-38 https://www.python.org/dev/peps/pep-0544/#merging-and-extending-protocols +RedirectTemp /13-39 https://numpy.org/devdocs/user/basics.types.html +RedirectTemp /13-40 https://github.com/python/typeshed/blob/master/stdlib/statistics.pyi +RedirectTemp /13-41 https://bugs.python.org/issue41974 +RedirectTemp /13-42 https://glyph.twistedmatrix.com/2020/07/new-duck.html +RedirectTemp /13-43 https://glyph.twistedmatrix.com/2021/03/interfaces-and-protocols.html +RedirectTemp /13-44 https://plone.org/ +RedirectTemp /13-45 https://trypyramid.com/ +RedirectTemp /13-46 https://twistedmatrix.com/trac/ +RedirectTemp /13-47 https://www.artima.com/articles/contracts-in-python +RedirectTemp /13-48 https://martinfowler.com/bliki/DynamicTyping.html +RedirectTemp /13-49 https://martinfowler.com/bliki/RoleInterface.html +RedirectTemp /13-50 https://mypy.readthedocs.io/en/stable/protocols.html +RedirectTemp /13-51 https://pymotw.com/3/abc/index.html +RedirectTemp /13-52 https://www.python.org/dev/peps/pep-3119/ +RedirectTemp /13-53 https://www.python.org/dev/peps/pep-3141/ +RedirectTemp /13-54 https://docs.python.org/3/library/numbers.html +RedirectTemp /13-55 https://github.com/python/mypy/issues/3186 +RedirectTemp /13-56 https://stackoverflow.com/questions/69334475/how-to-hint-at-number-types-i-e-subclasses-of-number-not-numbers-themselv/69383462#69383462 +RedirectTemp /13-57 https://github.com/python/mypy/issues/3186 +RedirectTemp /13-58 https://martinfowler.com/articles/lean-inception/ +RedirectTemp /13-59 https://martinfowler.com +RedirectTemp /13-60 https://www.jetbrains.com/pycharm/ +RedirectTemp /13-61 https://wingware.com/ +RedirectTemp /13-62 https://code.visualstudio.com/ +############################################################ 14 +RedirectTemp /14-1 http://worrydream.com/EarlyHistoryOfSmalltalk/ +RedirectTemp /14-2 https://docs.python.org/3/tutorial/classes.html +RedirectTemp /14-3 https://docs.python.org/3/library/collections.html#ordereddict-examples-and-recipes +RedirectTemp /14-4 https://discuss.python.org/t/is-it-time-to-deprecate-unbound-super-methods/1833 +RedirectTemp /14-5 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /14-6 https://docs.python.org/3/library/collections.html +RedirectTemp /14-7 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/strkeydict_dictsub.py +RedirectTemp /14-8 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /14-9 https://en.wikipedia.org/wiki/Breadth-first_search +RedirectTemp /14-10 https://www.python.org/download/releases/2.3/mro/ +RedirectTemp /14-11 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/uppermixin.py +RedirectTemp /14-12 https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html +RedirectTemp /14-13 https://docs.python.org/3/library/collections.abc.html +RedirectTemp /14-14 https://github.com/python/cpython/blob/8ece98a7e418c3c68a4c61bc47a2d0931b59a889/Lib/collections/__init__.py#L1084 +RedirectTemp /14-15 https://docs.python.org/3/library/http.server.html +RedirectTemp /14-16 https://github.com/python/cpython/blob/17c23167942498296f0bdfffe52e72d53d66d693/Lib/http/server.py#L144 +RedirectTemp /14-17 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L664 +RedirectTemp /14-18 https://docs.python.org/3/library/socketserver.html#socketserver.ForkingMixIn +RedirectTemp /14-19 https://docs.python.org/3/library/os.html#os.fork +RedirectTemp /14-20 https://en.wikipedia.org/wiki/POSIX +RedirectTemp /14-21 http://ccbv.co.uk/ +RedirectTemp /14-22 https://github.com/django/django/tree/main/django/views/generic +RedirectTemp /14-23 https://en.wikipedia.org/wiki/Template_method_pattern +RedirectTemp /14-24 https://docs.python.org/3/library/tkinter.html +RedirectTemp /14-25 https://docs.python.org/3/library/tkinter.ttk.html +RedirectTemp /14-26 https://docs.oracle.com/javase/10/docs/api/java/awt/package-tree.html +RedirectTemp /14-27 https://docs.oracle.com/javase/10/docs/api/javax/swing/package-tree.html +RedirectTemp /14-28 https://squeak.org/ +RedirectTemp /14-29 https://github.com/django/django/blob/b64db05b9cedd96905d637a2d824cbbf428e40e7/django/views/generic/list.py#L194 +RedirectTemp /14-30 https://github.com/python/cpython/blob/8ed183391241f0c73e7ba7f42b1d49fc02985f7b/Lib/tkinter/__init__.py#L2618 +RedirectTemp /14-31 https://docs.python.org/3/library/socketserver.html +RedirectTemp /14-32 https://docs.python.org/3/library/socketserver.html#socketserver.BaseServer +RedirectTemp /14-33 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L153 +RedirectTemp /14-34 https://docs.python.org/3/library/typing.html#typing.final +RedirectTemp /14-35 https://docs.python.org/3/library/typing.html#typing.Final +RedirectTemp /14-36 https://docs.python.org/3/library/collections.abc.html +RedirectTemp /14-37 https://hynek.me/articles/python-subclassing-redux/ +RedirectTemp /14-38 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch08.html#super +RedirectTemp /14-39 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ +RedirectTemp /14-40 https://fuhm.net/super-harmful/ +RedirectTemp /14-41 https://stackoverflow.com/questions/30190185/how-to-use-super-with-one-argument/30190341#30190341 +RedirectTemp /14-42 https://www.artima.com/weblogs/viewpost.jsp?thread=246488 +RedirectTemp /14-43 https://www.artima.com/weblogs/viewpost.jsp?thread=281127 +RedirectTemp /14-44 https://www.artima.com/weblogs/viewpost.jsp?thread=246341 +RedirectTemp /14-45 https://www.artima.com/weblogs/viewpost.jsp?thread=246483 +RedirectTemp /14-46 https://www.artima.com/weblogs/viewpost.jsp?thread=236275 +RedirectTemp /14-47 https://www.artima.com/weblogs/viewpost.jsp?thread=236278 +RedirectTemp /14-48 https://www.artima.com/weblogs/viewpost.jsp?thread=237121 +RedirectTemp /14-49 https://python-patterns.guide/gang-of-four/composition-over-inheritance/ +RedirectTemp /14-50 https://python-patterns.guide/ +RedirectTemp /14-51 https://www.youtube.com/watch?v=3MNVP9-hglc +RedirectTemp /14-52 http://worrydream.com/EarlyHistoryOfSmalltalk/ +RedirectTemp /14-53 https://en.wikipedia.org/wiki/Polymorphism_(computer_science) +############################################################ 15 +RedirectTemp /15-1 https://www.youtube.com/watch?v=csL8DLXGNlU&t=92m5s +RedirectTemp /15-2 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi#L1434 +RedirectTemp /15-3 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi +RedirectTemp /15-4 https://twitter.com/gwidion/status/1265384692464967680 +RedirectTemp /15-5 https://pypi.org/project/pydantic/ +RedirectTemp /15-6 https://google.github.io/pytype/faq.html +RedirectTemp /15-7 https://google.github.io/pytype/faq.html +RedirectTemp /15-8 https://lxml.de/ +RedirectTemp /15-9 https://docs.python.org/3/library/xml.etree.elementtree.html +RedirectTemp /15-10 https://mypy.readthedocs.io/en/stable/common_issues.html +RedirectTemp /15-11 https://mypy.readthedocs.io/en/stable/common_issues.html#types-of-empty-collections +RedirectTemp /15-12 https://github.com/python/typing/issues/182 +RedirectTemp /15-13 https://pypi.org/project/pydantic/ +RedirectTemp /15-14 https://mypy.readthedocs.io/en/stable/type_narrowing.html#casts +RedirectTemp /15-15 https://github.com/python/cpython/blob/bee66d3cb98e740f9d8057eb7f503122052ca5d8/Lib/typing.py#L1340 +RedirectTemp /15-16 https://www.python.org/dev/peps/pep-0484/#casts +RedirectTemp /15-17 https://github.com/python/typeshed/issues/5535 +RedirectTemp /15-18 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /15-19 https://github.com/python/cpython/blob/b798ab06937f8bb24b444a49dd42e11fff15e654/Lib/test/test_asyncio/test_server.py#L55 +RedirectTemp /15-20 https://en.wikipedia.org/wiki/Code_smell +RedirectTemp /15-21 https://mail.python.org/archives/list/typing-sig@python.org/message/5LCWMN2UY2UQNLC5Z47GHBZKSPZW4I63/ +RedirectTemp /15-22 https://mypy.readthedocs.io/en/stable/error_codes.html#error-codes +RedirectTemp /15-23 https://github.com/fluentpython/example-code-2e/blob/master/15-more-types/clip_annot.py +RedirectTemp /15-24 https://docs.python.org/3/library/typing.html#introspection-helpers +RedirectTemp /15-25 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations +RedirectTemp /15-26 https://www.python.org/dev/peps/pep-0563/#abstract +RedirectTemp /15-27 https://mail.python.org/archives/list/python-dev@python.org/message/ZBJ7MD6CSGM6LZAOTET7GXAVBZB7O77O/ +RedirectTemp /15-28 https://docs.python.org/3.10/howto/annotations.html +RedirectTemp /15-29 https://docs.python.org/3/library/typing.html#user-defined-generic-types +RedirectTemp /15-30 https://github.com/python/typeshed/blob/bfc83c365a0b26ab16586beac77ff16729d0e473/stdlib/builtins.pyi#L743 +RedirectTemp /15-31 https://docs.python.org/3.10/library/typing.html#typing.FrozenSet +RedirectTemp /15-32 https://docs.python.org/3.10/library/typing.html#typing.Generator +RedirectTemp /15-33 https://docs.python.org/3.10/library/typing.html#typing.AsyncGenerator +RedirectTemp /15-34 https://github.com/python/cpython/blob/46b16d0bdbb1722daed10389e27226a2370f1635/Lib/typing.py#L1786 +RedirectTemp /15-35 https://github.com/python/typeshed/blob/2a9f081abbf01134e4e04ced6a750107db904d70/stdlib/builtins.pyi#L239 +RedirectTemp /15-36 https://www.oreilly.com/library/view/robust-python/9781098100650/ +RedirectTemp /15-37 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance +RedirectTemp /15-38 https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generic-types +RedirectTemp /15-39 https://mypy.readthedocs.io/en/stable/common_issues.html#variance +RedirectTemp /15-40 https://www.artima.com/weblogs/viewpost.jsp?thread=85551 +RedirectTemp /15-41 https://dl.acm.org/action/cookieAbsent +RedirectTemp /15-42 http://bracha.org/pluggableTypesPosition.pdf +RedirectTemp /15-43 https://www.researchgate.net/publication/213886116_Static_Typing_Where_Possible_Dynamic_Typing_When_Needed_The_End_of_the_Cold_War_Between_Programming_Languages +RedirectTemp /15-44 https://www.atomickotlin.com/atomickotlin/ +RedirectTemp /15-45 https://www.informit.com/store/effective-java-9780134685991 +RedirectTemp /15-46 https://www.manning.com/books/programming-with-types +RedirectTemp /15-47 https://www.oreilly.com/library/view/programming-typescript/9781492037644/ +RedirectTemp /15-48 https://www.informit.com/store/dart-programming-language-9780321927705 +RedirectTemp /15-49 https://www.yodaiken.com/2017/09/15/bad-ideas-in-type-theory/ +RedirectTemp /15-50 https://www.yodaiken.com/2017/11/30/types-considered-harmful-ii/ +RedirectTemp /15-51 https://web.archive.org/web/20071010002142/http://weblogs.java.net/blog/arnold/archive/2005/06/generics_consid_1.html +RedirectTemp /15-52 https://github.com/python/cpython/blob/3e7ee02327db13e4337374597cdc4458ecb9e3ad/Lib/asyncio/trsock.py#L5 +RedirectTemp /15-53 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance +############################################################ 16 +RedirectTemp /16-1 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-2 https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations +RedirectTemp /16-3 https://docs.python.org/3/reference/datamodel.html#object.__neg__ +RedirectTemp /16-4 https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#boolean-indexing +RedirectTemp /16-5 https://docs.python.org/3/library/collections.html#collections.Counter +RedirectTemp /16-6 https://docs.python.org/3/reference/datamodel.html#emulating-container-types +RedirectTemp /16-7 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations +RedirectTemp /16-8 https://www.fluentpython.com/lingo/#fail-fast +RedirectTemp /16-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Objects/typeobject.c#L4598 +RedirectTemp /16-10 https://neopythonic.blogspot.com/2019/03/why-operators-are-useful.html +RedirectTemp /16-11 https://treyhunner.com/2019/03/python-deep-comparisons-and-code-readability/ +RedirectTemp /16-12 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations +RedirectTemp /16-13 https://docs.python.org/3/library/pathlib.html +RedirectTemp /16-14 https://pypi.org/project/scapy/ +RedirectTemp /16-15 https://scapy.readthedocs.io/en/latest/usage.html#stacking-layers +RedirectTemp /16-16 https://docs.python.org/3/library/functools.html#functools.total_ordering +RedirectTemp /16-17 https://wiki.illinois.edu//wiki/download/attachments/273416327/ingalls.pdf +RedirectTemp /16-18 https://wiki.illinois.edu//wiki/download/attachments/273416327/double-dispatch.pdf +RedirectTemp /16-19 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-20 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-21 https://doc.rust-lang.org/std/ops/index.html +RedirectTemp /16-22 https://www.fluentpython.com/lingo/#lazy +############################################################ 17 +RedirectTemp /17-1 http://www.paulgraham.com/icad.html +RedirectTemp /17-2 https://en.wikipedia.org/wiki/Sentinel_value +RedirectTemp /17-3 https://docs.python.org/3.10/library/functions.html#iter +RedirectTemp /17-4 https://docs.python.org/3.10/library/functions.html#iter +RedirectTemp /17-5 https://github.com/python/cpython/blob/b1930bf75f276cd7ca08c4455298128d89adf7d1/Lib/_collections_abc.py#L271 +RedirectTemp /17-6 https://github.com/python/cpython/blob/main/Lib/types.py#L6 +RedirectTemp /17-7 https://en.wikipedia.org/wiki/CLU_(programming_language) +RedirectTemp /17-8 https://docs.python.org/3/glossary.html +RedirectTemp /17-9 https://docs.python.org/3/glossary.html#term-generator-iterator +RedirectTemp /17-10 https://docs.python.org/3/glossary.html#term-generator-expression +RedirectTemp /17-11 https://marc.info/?l=python-list&m=141826925106951&w=2 +RedirectTemp /17-12 https://docs.python.org/3/library/os.html#os.walk +RedirectTemp /17-13 https://docs.python.org/3/library/itertools.html +RedirectTemp /17-14 https://docs.python.org/3/library/exceptions.html#exception-hierarchy +RedirectTemp /17-15 https://en.wikipedia.org/wiki/Depth-first_search +RedirectTemp /17-16 https://docs.python.org/3.10/library/typing.html#typing.TypeAlias +RedirectTemp /17-17 https://docs.python.org/3/library/typing.html#typing.Generator +RedirectTemp /17-18 http://www.dabeaz.com/coroutines/Coroutines.pdf +RedirectTemp /17-19 http://www.dabeaz.com/coroutines/Coroutines.pdf +RedirectTemp /17-20 https://mail.python.org/pipermail/python-ideas/2009-April/003841.html +RedirectTemp /17-21 https://mail.python.org/pipermail/python-ideas/2009-April/003912.html +RedirectTemp /17-22 https://docs.python.org/3/library/exceptions.html#StopIteration +RedirectTemp /17-23 https://docs.python.org/3/reference/expressions.html#yield-expressions +RedirectTemp /17-24 https://docs.python.org/3/reference/index.html +RedirectTemp /17-25 https://github.com/python/cpython/blob/6f743e7a4da904f61dfa84cc7d7385e4dcc79ac5/Lib/typing.py#L2060 +RedirectTemp /17-26 http://catb.org/~esr/jargon/html/G/grok.html +RedirectTemp /17-27 https://docs.python.org/3/reference/expressions.html#yieldexpr +RedirectTemp /17-28 https://docs.python.org/3/library/itertools.html +RedirectTemp /17-29 https://docs.python.org/3/library/itertools.html#itertools-recipes +RedirectTemp /17-30 https://more-itertools.readthedocs.io/en/stable/index.html +RedirectTemp /17-31 https://rittau.org/2006/11/java-iterators-are-not-iterable/ +RedirectTemp /17-32 https://docs.python.org/3/whatsnew/3.3.html#pep-380-syntax-for-delegating-to-a-subgenerator +RedirectTemp /17-33 http://www.dabeaz.com/generators/ +RedirectTemp /17-34 http://www.dabeaz.com/coroutines/ +RedirectTemp /17-35 https://archive.org/details/pyvideo_213___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-1-of-3 +RedirectTemp /17-36 https://archive.org/details/pyvideo_215___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-2-of-3 +RedirectTemp /17-37 https://archive.org/details/pyvideo_214___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-3-of-3 +RedirectTemp /17-38 http://www.dabeaz.com/finalgenerator/ +RedirectTemp /17-39 https://web.archive.org/web/20200218150637/http://seriously.dontusethiscode.com/2013/05/01/greedy-coroutine.html +RedirectTemp /17-40 https://effectivepython.com/ +RedirectTemp /17-41 https://effectivepython.com/2015/03/10/consider-coroutines-to-run-many-functions-concurrently +RedirectTemp /17-42 https://en.wikipedia.org/wiki/Conway's_Game_of_Life +RedirectTemp /17-43 https://gist.github.com/ramalho/da5590bc38c973408839 +RedirectTemp /17-44 https://gist.github.com/ramalho/da5590bc38c973408839 +RedirectTemp /17-45 https://journal.code4lib.org/articles/4893 +RedirectTemp /17-46 https://github.com/fluentpython/isis2json +RedirectTemp /17-47 https://github.com/fluentpython/isis2json/blob/master/README.rst +############################################################ 18 +RedirectTemp /18-1 https://pyvideo.org/video/1669/keynote-3/ +RedirectTemp /18-2 https://docs.python.org/3/library/sqlite3.html#using-the-connection-as-a-context-manager +RedirectTemp /18-3 https://docs.python.org/3/library/threading.html#using-locks-conditions-and-semaphores-in-the-with-statement +RedirectTemp /18-4 https://docs.python.org/3/library/decimal.html#decimal.localcontext +RedirectTemp /18-5 https://docs.python.org/3/library/unittest.mock.html#patch +RedirectTemp /18-6 https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout +RedirectTemp /18-7 https://docs.python.org/3/library/sys.html#sys.exc_info +RedirectTemp /18-8 https://en.wikipedia.org/wiki/LL_parser +RedirectTemp /18-9 https://docs.python.org/3/library/contextlib.html +RedirectTemp /18-10 https://github.com/python/cpython/blob/8afab2ebbc1b343cd88d058914cf622fe687a2be/Lib/contextlib.py#L123 +RedirectTemp /18-11 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ +RedirectTemp /18-12 https://docs.python.org/3/library/fileinput.html#fileinput.input +RedirectTemp /18-13 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ +RedirectTemp /18-14 https://en.wikipedia.org/wiki/Euclidean_algorithm +RedirectTemp /18-15 https://github.com/fluentpython/example-code-2e/tree/master/18-with-match/lispy/py3.10/ +RedirectTemp /18-16 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lispy.py +RedirectTemp /18-17 https://github.com/fluentpython/example-code-2e/blob/6527037ae7319ba370a1ee2d9fe79214d0ed9452/18-with-match/lispy/py3.10/lis.py#L35 +RedirectTemp /18-18 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py +RedirectTemp /18-19 https://github.com/python/typeshed/issues/6042 +RedirectTemp /18-20 https://github.com/fluentpython/lispy/tree/main/mylis +RedirectTemp /18-21 https://github.com/fluentpython/example-code-2e/blob/00e4741926e1b771ee7c753148b1415c0bd12e39/02-array-seq/lispy/py3.10/examples_test.py +RedirectTemp /18-22 https://mitpress.mit.edu/sites/default/files/sicp/index.html +RedirectTemp /18-23 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py +RedirectTemp /18-24 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/lis.py +RedirectTemp /18-25 https://www.python.org/dev/peps/pep-0634/#or-patterns +RedirectTemp /18-26 https://en.wikipedia.org/wiki/Lambda#Character_encodings +RedirectTemp /18-27 https://docs.python.org/3/reference/compound_stmts.html +RedirectTemp /18-28 https://docs.python.org/3/glossary.html#term-eafp +RedirectTemp /18-29 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 +RedirectTemp /18-30 https://docs.python.org/3/reference/compound_stmts.html +RedirectTemp /18-31 https://stackoverflow.com/questions/16138232/is-it-a-good-practice-to-use-try-except-else-in-python +RedirectTemp /18-32 https://docs.python.org/3/library/stdtypes.html#typecontextmanager +RedirectTemp /18-33 https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers +RedirectTemp /18-34 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 +RedirectTemp /18-35 https://speakerdeck.com/pyconslides/transforming-code-into-beautiful-idiomatic-python-by-raymond-hettinger-1?slide=34 +RedirectTemp /18-36 https://preshing.com/20110920/the-python-with-statement-by-example/ +RedirectTemp /18-37 https://www.rath.org/on-the-beauty-of-pythons-exitstack.html +RedirectTemp /18-38 https://norvig.com/lispy.html +RedirectTemp /18-39 https://norvig.com/lispy2.html +RedirectTemp /18-40 https://github.com/norvig/pytudes +RedirectTemp /18-41 https://github.com/fluentpython/lispy +RedirectTemp /18-42 https://racket-lang.org/ +RedirectTemp /18-43 https://pyvideo.org/video/1669/keynote-3/ +RedirectTemp /18-44 https://en.wikipedia.org/wiki/Tail_call +RedirectTemp /18-45 https://2ality.com/2015/06/tail-call-optimization.html +RedirectTemp /18-46 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lis.py +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-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 +RedirectTemp /18-54 https://github.com/fluentpython/lispy/tree/main/mylis +############################################################ 19 +RedirectTemp /19-1 https://go.dev/blog/waza-talk +RedirectTemp /19-2 https://en.wikipedia.org/wiki/Graphics_processing_unit +RedirectTemp /19-3 https://docs.python.org/3/library/sys.html#sys.getswitchinterval +RedirectTemp /19-4 https://docs.python.org/3/library/sys.html#sys.setswitchinterval +RedirectTemp /19-5 https://en.wikipedia.org/wiki/System_call +RedirectTemp /19-6 https://mail.python.org/pipermail/python-dev/2009-October/093356.html +RedirectTemp /19-7 http://www.dabeaz.com/finalgenerator/ +RedirectTemp /19-8 https://docs.python.org/3/library/threading.html#thread-objects +RedirectTemp /19-9 https://www.pypy.org/ +RedirectTemp /19-10 https://mail.python.org/pipermail/python-list/2009-February/675659.html +RedirectTemp /19-11 https://en.wikipedia.org/wiki/Braille_Patterns +RedirectTemp /19-12 https://docs.python.org/3/library/multiprocessing.shared_memory.html +RedirectTemp /19-13 https://docs.python.org/3/library/multiprocessing.shared_memory.html#multiprocessing.shared_memory.ShareableList +RedirectTemp /19-14 https://greenlet.readthedocs.io/en/latest/ +RedirectTemp /19-15 https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#asynchronous-io-support-for-core-and-orm +RedirectTemp /19-16 https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html +RedirectTemp /19-17 http://www.gevent.org/ +RedirectTemp /19-18 https://github.com/gevent/gevent/wiki/Projects +RedirectTemp /19-19 https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor-example +RedirectTemp /19-20 https://github.com/python/asyncio/issues/284 +RedirectTemp /19-21 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /19-22 https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/ +RedirectTemp /19-23 https://docs.python.org/3/library/queue.html#queue.SimpleQueue.get +RedirectTemp /19-24 https://en.wikipedia.org/wiki/Race_condition +RedirectTemp /19-25 https://github.com/fluentpython/example-code-2e/commit/2c1230579db99738a5e5e6802063bda585f6476d +RedirectTemp /19-26 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/README.md +RedirectTemp /19-27 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/threads.py +RedirectTemp /19-28 https://en.wikipedia.org/wiki/Context_switch +RedirectTemp /19-29 http://www.gotw.ca/publications/concurrency-ddj.htm +RedirectTemp /19-30 https://www.ansible.com/ +RedirectTemp /19-31 https://saltproject.io/ +RedirectTemp /19-32 https://www.fabfile.org/ +RedirectTemp /19-33 https://engineering.fb.com/2016/05/27/production-engineering/python-in-production-engineering/ +RedirectTemp /19-34 https://jupyter.org/ +RedirectTemp /19-35 https://docs.bokeh.org/en/latest/index.html +RedirectTemp /19-36 https://www.tensorflow.org/ +RedirectTemp /19-37 https://pytorch.org/ +RedirectTemp /19-38 https://www.oreilly.com/radar/where-programming-ops-ai-and-the-cloud-are-headed-in-2021/ +RedirectTemp /19-39 https://www.youtube.com/watch?v=ods97a5Pzw0 +RedirectTemp /19-40 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy +RedirectTemp /19-41 https://modwsgi.readthedocs.io/en/master/ +RedirectTemp /19-42 https://uwsgi-docs.readthedocs.io/en/latest/ +RedirectTemp /19-43 https://unit.nginx.org/ +RedirectTemp /19-44 https://www.techatbloomberg.com/blog/configuring-uwsgi-production-deployment/ +RedirectTemp /19-45 https://www.youtube.com/watch?v=p6R1h2Nn468 +RedirectTemp /19-46 https://asgi.readthedocs.io/en/latest/index.html +RedirectTemp /19-47 https://docs.celeryproject.org/en/stable/getting-started/introduction.html +RedirectTemp /19-48 https://python-rq.org/ +RedirectTemp /19-49 https://docs.celeryproject.org/en/stable/faq.html#what-kinds-of-things-should-i-use-celery-for +RedirectTemp /19-50 https://redis.io/ +RedirectTemp /19-51 https://realpython.com/intro-to-python-threading/ +RedirectTemp /19-52 https://pymotw.com/3/concurrency.html +RedirectTemp /19-53 https://www.pearson.com/us/higher-education/program/Hellmann-Python-3-Standard-Library-by-Example-The/PGM328871.html +RedirectTemp /19-54 https://docs.python.org/3/library/multiprocessing.html#programming-guidelines +RedirectTemp /19-55 https://docs.python.org/3/library/multiprocessing.html +RedirectTemp /19-56 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ +RedirectTemp /19-57 https://link.springer.com/book/10.1007/978-1-4842-5793-7?error=cookies_not_supported&code=2ed5d61d-ae9f-4f3d-94ac-0f68cf45ea4f +RedirectTemp /19-58 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 +RedirectTemp /19-59 https://greenteapress.com/wp/semaphores/ +RedirectTemp /19-60 https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock +RedirectTemp /19-61 https://docs.python.org/3/faq/library.html#can-t-we-get-rid-of-the-global-interpreter-lock +RedirectTemp /19-62 https://www.artima.com/weblogs/viewpost.jsp?thread=214235 +RedirectTemp /19-63 http://jessenoller.com/blog/2009/02/01/python-threads-and-the-global-interpreter-lock +RedirectTemp /19-64 https://realpython.com/products/cpython-internals-book/ +RedirectTemp /19-65 http://www.dabeaz.com/GIL/ +RedirectTemp /19-66 http://www.dabeaz.com/python/UnderstandingGIL.pdf +RedirectTemp /19-67 https://bugs.python.org/issue7946#msg223110 +RedirectTemp /19-68 https://bugs.python.org/issue7946 +RedirectTemp /19-69 https://www.fullstackpython.com/ +RedirectTemp /19-70 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ +RedirectTemp /19-71 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 +RedirectTemp /19-72 https://www.packtpub.com/product/distributed-computing-with-python/9781785889691 +RedirectTemp /19-73 https://towardsdatascience.com/python-performance-and-gpus-1be860ffd58d?gi=a7d537cc2fb4 +RedirectTemp /19-74 https://instagram-engineering.com/web-service-efficiency-at-instagram-with-python-4976d078e366?gi=12a441991c88 +RedirectTemp /19-75 https://www.oreilly.com/library/view/architecture-patterns-with/9781492052197/ +RedirectTemp /19-76 https://www.cosmicpython.com/ +RedirectTemp /19-77 https://pypi.org/project/lelo/ +RedirectTemp /19-78 https://github.com/npryce/python-parallelize +RedirectTemp /19-79 https://github.com/ericsnowcurrently/multi-core-python/wiki +RedirectTemp /19-80 https://gist.github.com/markshannon/79cace3656b40e21b7021504daee950c +RedirectTemp /19-81 https://mail.python.org/archives/list/python-dev@python.org/message/YOOQZCFOKEPQ24YHWWLQSJ3RCXFMS7D7/ +RedirectTemp /19-82 https://en.wikipedia.org/wiki/Communicating_sequential_processes +RedirectTemp /19-83 https://github.com/stackless-dev/stackless/wiki +RedirectTemp /19-84 https://www.eveonline.com +RedirectTemp /19-85 https://www.ccpgames.com/ +RedirectTemp /19-86 https://stackless.readthedocs.io/en/3.6-slp/stackless-python.html#history +RedirectTemp /19-87 https://doc.pypy.org/en/latest/stackless.html +RedirectTemp /19-88 https://greenlet.readthedocs.io/en/latest/ +RedirectTemp /19-89 http://www.gevent.org/ +RedirectTemp /19-90 http://thespianpy.com/doc/ +RedirectTemp /19-91 https://pykka.readthedocs.io/en/latest/ +RedirectTemp /19-92 https://www.manning.com/books/rabbitmq-in-action +RedirectTemp /19-93 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ +RedirectTemp /19-94 https://en.wikipedia.org/wiki/OpenCL +RedirectTemp /19-95 https://media.pragprog.com/titles/pb7con/Bonus_Chapter.pdf +RedirectTemp /19-96 https://martinfowler.com/ +RedirectTemp /19-97 https://martinfowler.com/articles/patterns-of-distributed-systems/ +RedirectTemp /19-98 https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/ +RedirectTemp /19-99 https://www.oreilly.com/library/view/oscon-2016-video/9781491965153/video247021.html +RedirectTemp /19-100 https://www.oreilly.com/library/view/designing-for-scalability/9781449361556/ +RedirectTemp /19-101 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy +RedirectTemp /19-102 https://en.wikipedia.org/wiki/KISS_principle +RedirectTemp /19-103 https://www.usenix.org/conference/hotos15/workshop-program/presentation/mcsherry +############################################################ 20 +RedirectTemp /20-1 https://www.artima.com/weblogs/viewpost.jsp?thread=299551 +RedirectTemp /20-2 https://docs.python.org/3/library/http.server.html +RedirectTemp /20-3 https://www.youtube.com/watch?v=A9e9Cy1UkME +RedirectTemp /20-4 https://www.cia.gov/the-world-factbook/ +RedirectTemp /20-5 https://docs.python-requests.org/en/latest/ +RedirectTemp /20-6 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor +RedirectTemp /20-7 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /20-8 https://docs.python.org/3/library/concurrent.futures.html +RedirectTemp /20-9 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.Executor +RedirectTemp /20-10 https://github.com/fluentpython/example-code-2e/blob/master/20-executors/getflags/flags2_common.py +RedirectTemp /20-11 https://github.com/noamraph/tqdm +RedirectTemp /20-12 https://www.youtube.com/watch?v=M8Z65tAl5l4 +RedirectTemp /20-13 https://github.com/noamraph/tqdm/blob/master/README.md +RedirectTemp /20-14 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /20-15 https://docs.python.org/3/library/asyncio-task.html#asyncio.as_completed +RedirectTemp /20-16 https://www.cloudflare.com/ +RedirectTemp /20-17 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags +RedirectTemp /20-18 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 +RedirectTemp /20-19 https://en.wikipedia.org/wiki/Embarrassingly_parallel +RedirectTemp /20-20 https://pyvideo.org/video/480/pyconau-2010--the-future-is-soon/ +RedirectTemp /20-21 http://www.dabeaz.com/coroutines/ +RedirectTemp /20-22 https://en.wikipedia.org/wiki/POSIX_Threads +RedirectTemp /20-23 https://en.wikipedia.org/wiki/C_dynamic_memory_allocation +RedirectTemp /20-24 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ +RedirectTemp /20-25 https://hexdocs.pm/ecto/getting-started.html +############################################################ 21 +RedirectTemp /21-1 https://docs.python.org/3/library/asyncio.html +RedirectTemp /21-2 https://bugs.python.org/issue43216 +RedirectTemp /21-3 https://bugs.python.org/issue36921 +RedirectTemp /21-4 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.getaddrinfo +RedirectTemp /21-5 https://docs.python.org/3/library/socket.html#socket.getaddrinfo +RedirectTemp /21-6 https://docs.python.org/3.10/library/asyncio-eventloop.html#asyncio.get_event_loop +RedirectTemp /21-7 https://www.python.org/dev/peps/pep-0492/#await-expression +RedirectTemp /21-8 https://www.fluentpython.com/extra/classic-coroutines/#yield_from_meaning_sec +RedirectTemp /21-9 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags +RedirectTemp /21-10 https://magicstack.github.io/asyncpg/current/ +RedirectTemp /21-11 https://magicstack.github.io/asyncpg/current/api/index.html#transactions +RedirectTemp /21-12 https://magicstack.github.io/asyncpg/current/api/index.html#transactions +RedirectTemp /21-13 https://github.com/MagicStack/asyncpg/blob/4d39a05268ce4cc01b00458223a767542da048b8/asyncpg/transaction.py#L57 +RedirectTemp /21-14 https://magicstack.github.io/asyncpg/current/usage.html#connection-pools +RedirectTemp /21-15 https://gist.github.com/jboner/2841832 +RedirectTemp /21-16 https://en.wikipedia.org/wiki/Network-attached_storage +RedirectTemp /21-17 https://en.wikipedia.org/wiki/Semaphore_(programming) +RedirectTemp /21-18 https://en.wikipedia.org/wiki/Semaphore_(programming) +RedirectTemp /21-19 https://groups.google.com/forum/#!msg/python-tulip/PdAEtwpaJHs/7fqb-Qj2zJoJ +RedirectTemp /21-20 https://web.archive.org/web/20151209151711/http://tritarget.org/blog/2012/11/28/the-pyramid-of-doom-a-javascript-style-trap +RedirectTemp /21-21 https://stackoverflow.com/questions/53701841/what-is-the-use-case-for-future-add-done-callback/53710563 +RedirectTemp /21-22 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /21-23 https://motor.readthedocs.io/en/stable/ +RedirectTemp /21-24 https://emptysqua.re/blog/response-to-asynchronous-python-and-databases/ +RedirectTemp /21-25 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /21-26 https://en.wikipedia.org/wiki/Phaistos_Disc +RedirectTemp /21-27 https://en.wikipedia.org/wiki/Inverted_index +RedirectTemp /21-28 https://fastapi.tiangolo.com/ +RedirectTemp /21-29 https://swagger.io/specification/ +RedirectTemp /21-30 https://asgi.readthedocs.io/en/latest/implementations.html +RedirectTemp /21-31 https://pydantic-docs.helpmanual.io/ +RedirectTemp /21-32 https://doc.traefik.io/traefik/ +RedirectTemp /21-33 https://fastapi.tiangolo.com/project-generation/ +RedirectTemp /21-34 https://fastapi.tiangolo.com/tutorial/response-model/ +RedirectTemp /21-35 https://docs.python.org/3/library/asyncio-stream.html#asyncio.start_server +RedirectTemp /21-36 https://github.com/python/typeshed/issues/5535 +RedirectTemp /21-37 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.Server.serve_forever +RedirectTemp /21-38 https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter.close +RedirectTemp /21-39 https://docs.python.org/3/library/asyncio-stream.html#streamwriter +RedirectTemp /21-40 https://docs.python.org/3/library/asyncio-stream.html +RedirectTemp /21-41 https://docs.python.org/3/library/asyncio-protocol.html +RedirectTemp /21-42 https://docs.python.org/3/library/asyncio-protocol.html#tcp-echo-server +RedirectTemp /21-43 https://github.com/aio-libs/aiopg +RedirectTemp /21-44 https://docs.python.org/3/whatsnew/3.8.html#asyncio +RedirectTemp /21-45 https://datatracker.ietf.org/doc/html/rfc6761 +RedirectTemp /21-46 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /21-47 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /21-48 https://docs.python.org/3/library/asyncio-task.html#asyncio.gather +RedirectTemp /21-49 https://curio.readthedocs.io/en/latest/index.html +RedirectTemp /21-50 https://curio.readthedocs.io/en/latest/reference.html#task-groups +RedirectTemp /21-51 https://en.wikipedia.org/wiki/Structured_concurrency +RedirectTemp /21-52 https://mail.python.org/archives/list/python-dev@python.org/thread/2ORDAW74LGE3ZI2QETPJRT2ZL7MCCPG2/ +RedirectTemp /21-53 https://www.python.org/dev/peps/pep-0654/#motivation +RedirectTemp /21-54 https://curio.readthedocs.io/en/latest/reference.html#AWAIT +RedirectTemp /21-55 https://www.python-httpx.org/async/#curio +RedirectTemp /21-56 https://github.com/dabeaz/curio/tree/78bca8a6ad677ef51e1568ac7b3e51441ab49c42/examples +RedirectTemp /21-57 https://datatracker.ietf.org/doc/html/rfc8305 +RedirectTemp /21-58 https://trio.readthedocs.io/en/stable/ +RedirectTemp /21-59 https://www.youtube.com/watch?v=M-sc73Y-zQA +RedirectTemp /21-60 https://en.wikipedia.org/wiki/Technical_debt +RedirectTemp /21-61 https://www.youtube.com/watch?v=E-1Y4kSsAFc +RedirectTemp /21-62 https://github.com/dabeaz/curio +RedirectTemp /21-63 https://trio.readthedocs.io/en/stable/ +RedirectTemp /21-64 https://curio.readthedocs.io/en/latest/#curio-university +RedirectTemp /21-65 https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/ +RedirectTemp /21-66 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ +RedirectTemp /21-67 https://stackoverflow.com/questions/49482969/what-is-the-core-difference-between-asyncio-and-trio +RedirectTemp /21-68 https://docs.python.org/3/library/asyncio.html +RedirectTemp /21-69 https://bugs.python.org/issue33649 +RedirectTemp /21-70 https://docs.python.org/3/library/asyncio-dev.html +RedirectTemp /21-71 https://www.youtube.com/watch?v=iG6fr81xHKA +RedirectTemp /21-72 https://www.youtube.com/watch?v=F19R_M4Nay4 +RedirectTemp /21-73 https://asherman.io/projects/unsync.html +RedirectTemp /21-74 https://pyladies.com/ +RedirectTemp /21-75 https://www.youtube.com/watch?v=sW76-pRkZk8 +RedirectTemp /21-76 https://www.youtube.com/watch?v=Xbl7XjFYsN4 +RedirectTemp /21-77 https://www.youtube.com/watch?v=02CLD-42VdI +RedirectTemp /21-78 https://micropython.org/ +RedirectTemp /21-79 https://docs.micropython.org/en/latest/library/uasyncio.html +RedirectTemp /21-80 https://www.encode.io/articles/python-async-frameworks-beyond-developer-tribalism +RedirectTemp /21-81 https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ +RedirectTemp /21-82 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ +RedirectTemp /21-83 https://github.com/MagicStack/uvloop +RedirectTemp /21-84 http://magic.io/blog/uvloop-blazing-fast-python-networking/ +RedirectTemp /21-85 https://github.com/MagicStack/httptools +RedirectTemp /21-86 https://docs.aiohttp.org/en/stable/ +RedirectTemp /21-87 https://github.com/wg/wrk +RedirectTemp /21-88 https://twistedmatrix.com/trac/ +############################################################ 22 +RedirectTemp /22-1 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/data/osconfeed.json +RedirectTemp /22-2 https://pypi.org/project/attrdict/ +RedirectTemp /22-3 https://pypi.org/project/addict/ +RedirectTemp /22-4 https://github.com/ActiveState/code/tree/master/recipes/Python/52308_simple_but_handy_collector_bunch_named_stuff +RedirectTemp /22-5 https://docs.python.org/3/library/types.html#types.SimpleNamespace +RedirectTemp /22-6 https://docs.python.org/3/library/argparse.html#argparse.Namespace +RedirectTemp /22-7 https://docs.python.org/3/library/multiprocessing.html#multiprocessing.managers.Namespace +RedirectTemp /22-8 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/schedule_v2.py +RedirectTemp /22-9 https://docs.python.org/3/library/functools.html#functools.cached_property +RedirectTemp /22-10 https://docs.python.org/3/library/functools.html#functools.cached_property +RedirectTemp /22-11 https://bugs.python.org/issue42781 +RedirectTemp /22-12 https://docs.python.org/3/howto/descriptor.html +RedirectTemp /22-13 https://github.com/python/cpython/blob/e6d0107e13ed957109e79b796984d3d026a8660d/Lib/functools.py#L926 +RedirectTemp /22-14 https://docs.python.org/3/library/threading.html#rlock-objects +RedirectTemp /22-15 https://docs.python.org/3.10/library/functools.html#functools.cached_property +RedirectTemp /22-16 https://www.wsj.com/articles/SB10001424052970203914304576627102996831200 +RedirectTemp /22-17 https://www.youtube.com/watch?v=s35rVw1zskA&feature=youtu.be +RedirectTemp /22-18 https://docs.python.org/3/library/functions.html#dir +RedirectTemp /22-19 https://github.com/python/cpython/blob/19903085c3ad7a17c8047e1556c700f2eb109931/Lib/cmd.py#L214 +RedirectTemp /22-20 https://docs.python.org/3/library/functions.html#hasattr +RedirectTemp /22-21 https://docs.python.org/3.10/reference/datamodel.html#special-method-lookup +RedirectTemp /22-22 https://docs.python.org/3/library/functions.html +RedirectTemp /22-23 https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access +RedirectTemp /22-24 https://docs.python.org/3/reference/datamodel.html#special-method-lookup +RedirectTemp /22-25 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /22-26 http://wiki.c2.com/?WelcomeVisitors +RedirectTemp /22-27 http://wiki.c2.com/?UniformAccessPrinciple +RedirectTemp /22-28 https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html +RedirectTemp /22-29 http://www.pingo.io/docs/ +RedirectTemp /22-30 https://www.drdobbs.com/javas-new-considered-harmful/184405016 +RedirectTemp /22-31 https://www.python.org/dev/peps/pep-0008/#class-names +############################################################ 23 +RedirectTemp /23-1 http://www.aleax.it/goo_pydp.pdf +RedirectTemp /23-2 https://docs.python.org/3.10/reference/datamodel.html#implementing-descriptors +RedirectTemp /23-3 https://docs.python.org/3/howto/descriptor.html +RedirectTemp /23-4 https://docs.python.org/3/howto/ +RedirectTemp /23-5 http://www.aleax.it/Python/nylug05_om.pdf +RedirectTemp /23-6 https://www.youtube.com/watch?v=VOzvpHoYQoo +RedirectTemp /23-7 https://www.python.org/dev/peps/pep-0487/#trait-descriptors +RedirectTemp /23-8 https://dreamsongs.com/RiseOfWorseIsBetter.html +RedirectTemp /23-9 http://web.archive.org/web/20031002184114/www.amk.ca/python/writing/warts.html +RedirectTemp /23-10 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this +RedirectTemp /23-11 http://python-history.blogspot.com/2009/02/adding-support-for-user-defined-classes.html +############################################################ 24 +RedirectTemp /24-1 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /24-2 https://docs.djangoproject.com/en/3.2/topics/db/models/#meta-options +RedirectTemp /24-3 https://www.python.org/dev/peps/pep-3155/ +RedirectTemp /24-4 https://github.com/python/cpython/blob/3.9/Lib/collections/__init__.py +RedirectTemp /24-5 https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions +RedirectTemp /24-6 https://go.dev/tour/basics/12 +RedirectTemp /24-7 https://bugs.python.org/issue42102 +RedirectTemp /24-8 https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__ +RedirectTemp /24-9 https://www.python.org/dev/peps/pep-0557/#abstract +RedirectTemp /24-10 https://github.com/python/cpython/blob/3.9/Lib/dataclasses.py +RedirectTemp /24-11 https://docs.python.org/3/reference/datamodel.html#creating-the-class-object +RedirectTemp /24-12 https://mail.python.org/pipermail/python-list/2002-December/134521.html +RedirectTemp /24-13 https://mail.python.org/pipermail/python-list/2002-July/162558.html +RedirectTemp /24-14 https://github.com/fluentpython/example-code/tree/master/21-class-metaprog/bulkfood +RedirectTemp /24-15 https://en.wikipedia.org/wiki/Principle_of_least_astonishment +RedirectTemp /24-16 https://docs.python.org/3/reference/datamodel.html#object.__class_getitem__ +RedirectTemp /24-17 https://en.wikipedia.org/wiki/Trait_(computer_programming) +RedirectTemp /24-18 https://en.wikipedia.org/wiki/Aspect-oriented_programming +RedirectTemp /24-19 https://dhh.dk/arc/000416.html +RedirectTemp /24-20 https://github.com/cjrh/autoslot +RedirectTemp /24-21 https://docs.python.org/3/reference/datamodel.html#customizing-class-creation +RedirectTemp /24-22 https://docs.python.org/3/library/functions.html#type +RedirectTemp /24-23 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /24-24 https://docs.python.org/3/library/types.html +RedirectTemp /24-25 https://www.python.org/dev/peps/pep-3129/ +RedirectTemp /24-26 https://www.youtube.com/watch?v=cAGliEJV9_o +RedirectTemp /24-27 https://docs.python.org/3/library/functools.html#functools.total_ordering +RedirectTemp /24-28 https://www.python.org/download/releases/2.2.3/descrintro/ +RedirectTemp /24-29 https://github.com/lihaoyi/macropy +RedirectTemp /24-30 https://people.eecs.berkeley.edu/~bh/ss-toc2.html diff --git a/links/FPY.LI.htaccess b/links/FPY.LI.htaccess new file mode 100644 index 00000000..c352dec6 --- /dev/null +++ b/links/FPY.LI.htaccess @@ -0,0 +1,1509 @@ +ErrorDocument 404 /404.html + +# main resources +RedirectTemp /book https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /code https://github.com/fluentpython/example-code-2e +RedirectTemp /home https://www.fluentpython.com/ + +# QR code on back cover of Python Fluente 2ª edição +RedirectTemp /pf2q https://pythonfluente.com + +# URLs mentioned at least three times +RedirectTemp /bisect https://www.fluentpython.com/extra/ordered-sequences-with-bisect/ +RedirectTemp /cardxvi https://www.python.org/dev/peps/pep-0484/#the-numeric-tower +RedirectTemp /collec https://docs.python.org/3/library/collections.html +RedirectTemp /dask https://dask.org/ +RedirectTemp /dtmodel https://docs.python.org/3/reference/datamodel.html +RedirectTemp /descr101 https://www.python.org/download/releases/2.2.3/descrintro/ +RedirectTemp /descrhow https://docs.python.org/3/howto/descriptor.html +RedirectTemp /doctest https://docs.python.org/3/library/doctest.html +RedirectTemp /effectpy https://effectivepython.com/ +RedirectTemp /fmtspec https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /gunicorn https://gunicorn.org/ +RedirectTemp /hashint https://www.fluentpython.com/extra/internals-of-sets-and-dicts/ +RedirectTemp /hattingh https://www.oreilly.com/library/view/using-asyncio-in/9781492075325/ +RedirectTemp /httpx https://www.python-httpx.org/ +RedirectTemp /initvar https://docs.python.org/3/library/dataclasses.html#init-only-variables +RedirectTemp /mypy https://mypy.readthedocs.io/en/stable/ +RedirectTemp /norvigdp http://norvig.com/design-patterns/ +RedirectTemp /nsphere https://en.wikipedia.org/wiki/N-sphere +RedirectTemp /oldcoro https://www.fluentpython.com/extra/classic-coroutines/ +RedirectTemp /pandas https://pandas.pydata.org/ +RedirectTemp /pycook3 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ +RedirectTemp /pynut3 https://www.oreilly.com/library/view/python-in-a/9781491913833/ +RedirectTemp /pypydif https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /shed4051 https://github.com/python/typeshed/issues/4051 +RedirectTemp /specattr https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine +RedirectTemp /typing https://docs.python.org/3/library/typing.html +RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ + +# URL added during QA of the Second Edition +RedirectTemp /bdfl https://www.artima.com/weblogs/viewpost.jsp?thread=235725 + +# URL added during production of the Python Fluente 2e +RedirectTemp /ccby https://creativecommons.org/licenses/by-nc-nd/4.0/deed.pt_BR + +# Python Enhancement Proposals +RedirectTemp /pep218 https://www.python.org/dev/peps/pep-0218/ +RedirectTemp /pep227 https://www.python.org/dev/peps/pep-0227/ +RedirectTemp /pep255 https://www.python.org/dev/peps/pep-0255/ +RedirectTemp /pep342 https://www.python.org/dev/peps/pep-0342/ +RedirectTemp /pep343 https://www.python.org/dev/peps/pep-0343/ +RedirectTemp /pep357 https://www.python.org/dev/peps/pep-0357/ +RedirectTemp /pep362 https://www.python.org/dev/peps/pep-0362/ +RedirectTemp /pep371 https://www.python.org/dev/peps/pep-0371/ +RedirectTemp /pep380 https://www.python.org/dev/peps/pep-0380/ +RedirectTemp /pep393 https://www.python.org/dev/peps/pep-0393/ +RedirectTemp /pep412 https://www.python.org/dev/peps/pep-0412/ +RedirectTemp /pep442 https://www.python.org/dev/peps/pep-0442/ +RedirectTemp /pep443 https://www.python.org/dev/peps/pep-0443/ +RedirectTemp /pep448 https://www.python.org/dev/peps/pep-0448/ +RedirectTemp /pep455 https://www.python.org/dev/peps/pep-0455/ +RedirectTemp /pep456 https://www.python.org/dev/peps/pep-0456/ +RedirectTemp /pep461 https://www.python.org/dev/peps/pep-0461/ +RedirectTemp /pep465 https://www.python.org/dev/peps/pep-0465/ +RedirectTemp /pep467 https://www.python.org/dev/peps/pep-0467/ +RedirectTemp /pep482 https://www.python.org/dev/peps/pep-0482/ +RedirectTemp /pep483 https://www.python.org/dev/peps/pep-0483/ +RedirectTemp /pep484 https://www.python.org/dev/peps/pep-0484/ +RedirectTemp /pep487 https://www.python.org/dev/peps/pep-0487/ +RedirectTemp /pep492 https://www.python.org/dev/peps/pep-0492/ +RedirectTemp /pep519 https://www.python.org/dev/peps/pep-0519/ +RedirectTemp /pep525 https://www.python.org/dev/peps/pep-0525/ +RedirectTemp /pep526 https://www.python.org/dev/peps/pep-0526/ +RedirectTemp /pep528 https://www.python.org/dev/peps/pep-0528/ +RedirectTemp /pep529 https://www.python.org/dev/peps/pep-0529/ +RedirectTemp /pep530 https://www.python.org/dev/peps/pep-0530/ +RedirectTemp /pep544 https://www.python.org/dev/peps/pep-0544/ +RedirectTemp /pep554 https://www.python.org/dev/peps/pep-0554/ +RedirectTemp /pep557 https://www.python.org/dev/peps/pep-0557/ +RedirectTemp /pep560 https://www.python.org/dev/peps/pep-0560/ +RedirectTemp /pep561 https://www.python.org/dev/peps/pep-0561/ +RedirectTemp /pep563 https://www.python.org/dev/peps/pep-0563/ +RedirectTemp /pep570 https://www.python.org/dev/peps/pep-0570/ +RedirectTemp /pep572 https://www.python.org/dev/peps/pep-0572/ +RedirectTemp /pep584 https://www.python.org/dev/peps/pep-0584/ +RedirectTemp /pep585 https://www.python.org/dev/peps/pep-0585/ +RedirectTemp /pep586 https://www.python.org/dev/peps/pep-0586/ +RedirectTemp /pep589 https://www.python.org/dev/peps/pep-0589/ +RedirectTemp /pep591 https://www.python.org/dev/peps/pep-0591/ +RedirectTemp /pep593 https://www.python.org/dev/peps/pep-0593/ +RedirectTemp /pep604 https://www.python.org/dev/peps/pep-0604/ +RedirectTemp /pep612 https://www.python.org/dev/peps/pep-0612/ +RedirectTemp /pep613 https://www.python.org/dev/peps/pep-0613/ +RedirectTemp /pep616 https://www.python.org/dev/peps/pep-0616/ +RedirectTemp /pep617 https://www.python.org/dev/peps/pep-0617/ +RedirectTemp /pep618 https://www.python.org/dev/peps/pep-0618/ +RedirectTemp /pep634 https://www.python.org/dev/peps/pep-0634/ +RedirectTemp /pep635 https://www.python.org/dev/peps/pep-0635/ +RedirectTemp /pep636 https://www.python.org/dev/peps/pep-0636/ +RedirectTemp /pep638 https://www.python.org/dev/peps/pep-0638/ +RedirectTemp /pep645 https://www.python.org/dev/peps/pep-0645/ +RedirectTemp /pep646 https://www.python.org/dev/peps/pep-0646/ +RedirectTemp /pep647 https://www.python.org/dev/peps/pep-0647/ +RedirectTemp /pep649 https://www.python.org/dev/peps/pep-0649/ +RedirectTemp /pep654 https://www.python.org/dev/peps/pep-0654/ +RedirectTemp /pep655 https://www.python.org/dev/peps/pep-0655/ +RedirectTemp /pep661 https://www.python.org/dev/peps/pep-0661/ +RedirectTemp /pep3099 https://www.python.org/dev/peps/pep-3099/ +RedirectTemp /pep3102 https://www.python.org/dev/peps/pep-3102/ +RedirectTemp /pep3104 https://www.python.org/dev/peps/pep-3104/ +RedirectTemp /pep3106 https://www.python.org/dev/peps/pep-3106/ +RedirectTemp /pep3107 https://www.python.org/dev/peps/pep-3107/ +RedirectTemp /pep3115 https://www.python.org/dev/peps/pep-3115/ +RedirectTemp /pep3118 https://www.python.org/dev/peps/pep-3118/ +RedirectTemp /pep3119 https://www.python.org/dev/peps/pep-3119/ +RedirectTemp /pep3129 https://www.python.org/dev/peps/pep-3129/ +RedirectTemp /pep3132 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /pep3141 https://www.python.org/dev/peps/pep-3141/ +RedirectTemp /pep3148 https://www.python.org/dev/peps/pep-3148/ +RedirectTemp /pep3155 https://www.python.org/dev/peps/pep-3155/ +RedirectTemp /pep3333 https://www.python.org/dev/peps/pep-3333/ + +# Fluent Python 2e URLs by chapter + +############################################################ p +RedirectTemp /p-1 https://mail.python.org/pipermail/python-list/2002-December/134521.html +RedirectTemp /p-2 https://docs.python.org/3.10/tutorial/ +RedirectTemp /p-3 https://docs.python.org/3/tutorial/ +RedirectTemp /p-4 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /p-5 https://www.oreilly.com/online-learning/try-now.html +RedirectTemp /p-6 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /p-7 https://stackoverflow.com/users/95810/alex-martelli +RedirectTemp /p-8 https://pythonpro.com.br +RedirectTemp /p-9 https://groups.google.com/g/python-brasil +RedirectTemp /p-10 https://www.coffeelab.com.br/ +RedirectTemp /p-11 https://garoa.net.br/wiki/P%C3%A1gina_principal +############################################################ a +RedirectTemp /a-1 https://groups.google.com/forum/#!topic/python-tulip/Y4bhLNbKs74 +RedirectTemp /a-2 https://docs.python.org/3/library/asyncio-eventloop.html#executor +RedirectTemp /a-3 https://www.youtube.com/watch?v=x-kB2o8sd5c +RedirectTemp /a-4 https://www.youtube.com/watch?v=OSGv2VnC0go +RedirectTemp /a-5 https://mail.python.org/pipermail/python-ideas/2015-March/032557.html +RedirectTemp /a-6 https://pypi.org/project/pep8/ +RedirectTemp /a-7 https://pypi.org/project/flake8/ +RedirectTemp /a-8 https://pypi.org/project/pyflakes/ +RedirectTemp /a-9 https://pypi.org/project/mccabe/ +RedirectTemp /a-10 https://google.github.io/styleguide/pyguide.html +RedirectTemp /a-11 https://flask.palletsprojects.com/en/1.1.x/styleguide/ +RedirectTemp /a-12 https://docs.python-guide.org/ +RedirectTemp /a-13 https://david.goodger.org/projects/pycon/2007/idiomatic/handout.html +RedirectTemp /a-14 https://docs.mongodb.com/manual/about/#about-the-documentation-process +RedirectTemp /a-15 https://blog.startifact.com/posts/older/what-is-pythonic.html +RedirectTemp /a-16 https://mail.python.org/pipermail/tutor/2003-October/thread.html#25930 +RedirectTemp /a-17 https://mail.python.org/pipermail/python-list/2003-April/192027.html +RedirectTemp /a-18 https://www.python.org/doc/essays/ +############################################################ 01 +RedirectTemp /1-1 http://hugunin.net/story_of_jython.html +RedirectTemp /1-2 https://www.oreilly.com/library/view/jython-essentials/9781449397364/ +RedirectTemp /1-3 https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers +RedirectTemp /1-4 https://docs.python.org/3.10/library/string.html#format-string-syntax +RedirectTemp /1-5 https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr +RedirectTemp /1-6 https://docs.python.org/3/library/stdtypes.html#truth +RedirectTemp /1-7 https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists +RedirectTemp /1-8 https://www.python.org/doc/humor/#the-zen-of-python +RedirectTemp /1-9 https://stackoverflow.com/users/95810/alex-martelli +RedirectTemp /1-10 https://en.wikipedia.org/wiki/Object_model +RedirectTemp /1-11 https://www.dourish.com/goodies/jargon.html +RedirectTemp /1-12 https://zopeinterface.readthedocs.io/en/latest/ +RedirectTemp /1-13 https://plone.org/ +############################################################ 02 +RedirectTemp /2-1 https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/listcomp_speed.py +RedirectTemp /2-2 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /2-3 https://stackoverflow.com/questions/68630/are-tuples-more-efficient-than-lists-in-python/22140115#22140115 +RedirectTemp /2-4 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2-5 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2-6 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2-7 https://docs.python.org/3.10/whatsnew/3.10.html +RedirectTemp /2-8 https://en.wikipedia.org/wiki/Switch_statement#Fallthrough +RedirectTemp /2-9 https://en.wikipedia.org/wiki/Dangling_else +RedirectTemp /2-10 https://github.com/gvanrossum/patma/blob/3ece6444ef70122876fd9f0099eb9490a2d630df/EXAMPLES.md#case-6-a-very-deep-iterable-and-type-match-with-extraction +RedirectTemp /2-11 https://github.com/fluentpython/lispy/blob/main/original/norvig/lis.py +RedirectTemp /2-12 https://norvig.com/lispy.html +RedirectTemp /2-13 https://numpy.org/doc/stable/user/quickstart.html#indexing-slicing-and-iterating +RedirectTemp /2-14 https://pythontutor.com/ +RedirectTemp /2-15 https://en.wikipedia.org/wiki/Fluent_interface +RedirectTemp /2-16 https://docs.python.org/3/library/bisect.html#bisect.insort +RedirectTemp /2-17 https://stackoverflow.com/questions/4845418/when-should-a-memoryview-be-used/ +RedirectTemp /2-18 https://www.fluentpython.com/extra/parsing-binary-struct/ +RedirectTemp /2-19 http://www.netlib.org +RedirectTemp /2-20 https://pandas.pydata.org/ +RedirectTemp /2-21 https://scikit-learn.org/stable/ +RedirectTemp /2-22 https://docs.python.org/3/howto/sorting.html +RedirectTemp /2-23 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /2-24 https://bugs.python.org/issue2292 +RedirectTemp /2-25 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2-26 https://docs.python.org/3.10/whatsnew/3.10.html +RedirectTemp /2-27 https://www.python.org/dev/peps/pep-0636/#appendix-a-quick-intro +RedirectTemp /2-28 https://eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol-and-memoryviews/ +RedirectTemp /2-29 https://jakevdp.github.io/PythonDataScienceHandbook/ +RedirectTemp /2-30 https://www.oreilly.com/library/view/python-for-data/9781491957653/ +RedirectTemp /2-31 https://www.labri.fr/perso/nrougier/from-python-to-numpy/ +RedirectTemp /2-32 https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html +RedirectTemp /2-33 http://www.fonts101.com/fonts/view/Uncategorized/34398/Dijkstra +RedirectTemp /2-34 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /2-35 https://en.wikipedia.org/wiki/Timsort +RedirectTemp /2-36 http://www.groklaw.net/pdf3/OraGoogle-1202.pdf +RedirectTemp /2-37 https://www.python.org/doc/humor/#id9 +############################################################ 03 +RedirectTemp /3-1 https://www.python.org/dev/peps/pep-0584/#motivation +RedirectTemp /3-2 https://docs.python.org/3.10/c-api/typeobj.html#Py_TPFLAGS_MAPPING +RedirectTemp /3-3 https://docs.python.org/3/glossary.html#term-hashable +RedirectTemp /3-4 https://docs.python.org/3/glossary.html#term-hashable +RedirectTemp /3-5 http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf +RedirectTemp /3-6 https://github.com/pingo-io/pingo-py +RedirectTemp /3-7 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/missing.py +RedirectTemp /3-8 https://docs.python.org/3/library/collections.html#collections.ChainMap +RedirectTemp /3-9 https://docs.python.org/3/library/collections.html#collections.Counter +RedirectTemp /3-10 https://docs.python.org/3/library/shelve.html +RedirectTemp /3-11 https://docs.python.org/3/library/dbm.html +RedirectTemp /3-12 https://docs.python.org/3/library/pickle.html +RedirectTemp /3-13 https://nedbatchelder.com/blog/202006/pickles_nine_flaws.html +RedirectTemp /3-14 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py#L813 +RedirectTemp /3-15 https://mail.python.org/pipermail/python-dev/2015-May/140003.html +RedirectTemp /3-16 https://bugs.python.org/issue18986 +RedirectTemp /3-17 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/transformdict.py +RedirectTemp /3-18 https://gandenberger.org/posts/2018-03-10_ordered-dicts/ordered_dicts.html +RedirectTemp /3-19 https://www.pypy.org/ +RedirectTemp /3-20 https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html +RedirectTemp /3-21 https://www.npopov.com/2014/12/22/PHPs-new-hashtable-implementation.html +RedirectTemp /3-22 https://www.youtube.com/watch?v=66P5FMkWoVU +RedirectTemp /3-23 https://pyvideo.org/video/276/the-mighty-dictionary-55/ +RedirectTemp /3-24 https://www.youtube.com/watch?v=p33CVV29OG8 +RedirectTemp /3-25 https://docs.python.org/3/whatsnew/3.6.html#new-dict-implementation +RedirectTemp /3-26 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictobject.c +RedirectTemp /3-27 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictnotes.txt +RedirectTemp /3-28 https://www.youtube.com/watch?v=tGAngdU_8D8 +RedirectTemp /3-29 https://speakerdeck.com/ramalho/python-set-practice-at-pycon +RedirectTemp /3-30 https://github.com/standupdev/uintset +RedirectTemp /3-31 https://spectrum.ieee.org/hans-peter-luhn-and-the-birth-of-the-hashing-algorithm +RedirectTemp /3-32 http://www.json.org/fatfree.html +RedirectTemp /3-33 https://twitter.com/mitsuhiko/status/1229385843585974272 +############################################################ 04 +RedirectTemp /4-1 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 +RedirectTemp /4-2 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ +RedirectTemp /4-3 https://www.fluentpython.com/extra/parsing-binary-struct/ +RedirectTemp /4-4 https://www.fluentpython.com/extra/multi-character-emojis/ +RedirectTemp /4-5 https://w3techs.com/technologies/overview/character_encoding +RedirectTemp /4-6 https://docs.python.org/3/library/codecs.html#codecs.register_error +RedirectTemp /4-7 https://docs.python.org/3/library/stdtypes.html#str.isascii +RedirectTemp /4-8 https://pypi.org/project/chardet/ +RedirectTemp /4-9 https://docs.python.org/3/library/codecs.html#encodings-and-unicode +RedirectTemp /4-10 https://nedbatchelder.com/text/unipain/unipain.html +RedirectTemp /4-11 https://devblogs.microsoft.com/commandline/windows-command-line-unicode-and-utf-8-output-text-buffer/ +RedirectTemp /4-12 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONIOENCODING +RedirectTemp /4-13 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONLEGACYWINDOWSSTDIO +RedirectTemp /4-14 https://docs.python.org/3/library/locale.html#locale.getpreferredencoding +RedirectTemp /4-15 http://www.w3.org/TR/charmod-norm/ +RedirectTemp /4-16 https://docs.python.org/3/library/locale.html?highlight=strxfrm#locale.strxfrm +RedirectTemp /4-17 https://pypi.org/project/pyuca/ +RedirectTemp /4-18 https://github.com/jtauber/pyuca +RedirectTemp /4-19 http://www.unicode.org/Public/UCA/6.3.0/allkeys.txt +RedirectTemp /4-20 https://pypi.org/project/PyICU/ +RedirectTemp /4-21 https://docs.python.org/3.10/library/stdtypes.html#str.isalpha +RedirectTemp /4-22 https://en.wikipedia.org/wiki/Unicode_character_property#General_Category +RedirectTemp /4-23 https://en.wikipedia.org/wiki/Unicode_character_property +RedirectTemp /4-24 https://github.com/microsoft/terminal +RedirectTemp /4-25 https://docs.python.org/3/library/unicodedata.html +RedirectTemp /4-26 https://docs.python.org/3/reference/lexical_analysis.html#string-literal-concatenation +RedirectTemp /4-27 https://docs.python.org/3/library/re.html +RedirectTemp /4-28 https://nedbatchelder.com/text/unipain.html +RedirectTemp /4-29 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 +RedirectTemp /4-30 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ +RedirectTemp /4-31 https://regebro.wordpress.com/2011/03/23/unconfusing-unicode-what-is-unicode/ +RedirectTemp /4-32 https://docs.python.org/3/howto/unicode.html +RedirectTemp /4-33 https://diveintopython3.net/strings.html +RedirectTemp /4-34 https://diveintopython3.net/ +RedirectTemp /4-35 https://finderiko.com/python-book +RedirectTemp /4-36 https://docs.python.org/3.0/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit +RedirectTemp /4-37 https://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/ +RedirectTemp /4-38 http://python-notes.curiousefficiency.org/en/latest/python3/binary_protocols.html +RedirectTemp /4-39 http://python-notes.curiousefficiency.org/en/latest/python3/text_file_processing.html +RedirectTemp /4-40 https://docs.python.org/3/library/codecs.html#standard-encodings +RedirectTemp /4-41 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Tools/unicode/listcodecs.py +RedirectTemp /4-42 https://www.oreilly.com/library/view/unicode-explained/059610121X/ +RedirectTemp /4-43 https://www.informit.com/store/unicode-demystified-a-practical-programmers-guide-to-9780201700527 +RedirectTemp /4-44 https://unicodebook.readthedocs.io/index.html +RedirectTemp /4-45 https://www.w3.org/International/wiki/Case_folding +RedirectTemp /4-46 http://www.w3.org/TR/charmod-norm/ +RedirectTemp /4-47 http://unicode.org/reports/tr15/ +RedirectTemp /4-48 http://www.unicode.org/faq/normalization.html +RedirectTemp /4-49 http://www.unicode.org/ +RedirectTemp /4-50 http://www.macchiato.com/unicode/nfc-faq +RedirectTemp /4-51 https://stories.moma.org/the-original-emoji-set-has-been-added-to-the-museum-of-modern-arts-collection-c6060e141f61?gi=c403f5a840a5 +RedirectTemp /4-52 https://emojipedia.org/ +RedirectTemp /4-53 https://blog.emojipedia.org/correcting-the-record-on-the-first-emoji-set/ +RedirectTemp /4-54 http://emojitracker.com/ +RedirectTemp /4-55 http://www.unicode.org/glossary/#plain_text +RedirectTemp /4-56 http://www.methods.co.nz/asciidoc/ +RedirectTemp /4-57 https://atlas.oreilly.com/ +############################################################ 05 +RedirectTemp /5-1 https://docs.python.org/3/library/typing.html#typing.TypedDict +RedirectTemp /5-2 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations +RedirectTemp /5-3 https://docs.python.org/3/library/typing.html#typing.get_type_hints +RedirectTemp /5-4 https://docs.python.org/3.8/library/collections.html#collections.somenamedtuple._asdict +RedirectTemp /5-5 https://www.jetbrains.com/pycharm/ +RedirectTemp /5-6 https://www.python.org/dev/peps/pep-0484/#acceptable-type-hints +RedirectTemp /5-7 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /5-8 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /5-9 https://docs.python.org/3/library/dataclasses.html +RedirectTemp /5-10 https://docs.python.org/3/library/dataclasses.html#inheritance +RedirectTemp /5-11 https://www.python.org/dev/peps/pep-0526/#class-and-instance-variable-annotations +RedirectTemp /5-12 https://dublincore.org/specifications/dublin-core/ +RedirectTemp /5-13 https://en.wikipedia.org/wiki/Dublin_Core +RedirectTemp /5-14 https://martinfowler.com/bliki/CodeSmell.html +RedirectTemp /5-15 https://martinfowler.com/books/refactoring.html +RedirectTemp /5-16 https://www.python.org/dev/peps/pep-0634/#class-patterns +RedirectTemp /5-17 https://docs.python.org/3/library/dataclasses.html +RedirectTemp /5-18 https://www.python.org/dev/peps/pep-0557/#id47 +RedirectTemp /5-19 https://www.python.org/dev/peps/pep-0557/#id48 +RedirectTemp /5-20 https://www.python.org/dev/peps/pep-0557/#id33 +RedirectTemp /5-21 https://realpython.com +RedirectTemp /5-22 https://realpython.com/python-data-classes/ +RedirectTemp /5-23 https://www.youtube.com/watch?v=T-TwcmT6Rcw +RedirectTemp /5-24 https://www.attrs.org/en/stable/ +RedirectTemp /5-25 https://glyph.twistedmatrix.com/2016/08/attrs.html +RedirectTemp /5-26 https://www.attrs.org/en/stable/why.html +RedirectTemp /5-27 https://github.com/dabeaz/cluegen +RedirectTemp /5-28 https://refactoring.guru/ +RedirectTemp /5-29 https://refactoring.guru/smells/data-class +RedirectTemp /5-30 https://web.archive.org/web/20190204130328/http://catb.org/esr/jargon/html/G/Guido.html +RedirectTemp /5-31 https://web.archive.org/web/20190211161610/http://catb.org/esr/jargon/html/index.html +RedirectTemp /5-32 https://www.attrs.org/en/stable/ +############################################################ 06 +RedirectTemp /6-1 https://www.olin.edu/faculty/profile/lynn-andrea-stein/ +RedirectTemp /6-2 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /6-3 https://pythontutor.com/ +RedirectTemp /6-4 https://docs.python.org/3/library/copy.html +RedirectTemp /6-5 https://en.wikipedia.org/wiki/Principle_of_least_astonishment +RedirectTemp /6-6 https://docs.python.org/3/reference/datamodel.html#object.%5C_%5C_del__ +RedirectTemp /6-7 https://emptysqua.re/blog/pypy-garbage-collection-and-a-deadlock/ +RedirectTemp /6-8 https://www.youtube.com/watch?v=HHFCFJSPWrI&feature=youtu.be +RedirectTemp /6-9 http://pymotw.com/3/copy/ +RedirectTemp /6-10 http://pymotw.com/3/weakref/ +RedirectTemp /6-11 https://docs.python.org/3/library/gc.html +RedirectTemp /6-12 https://devguide.python.org/garbage_collector/ +RedirectTemp /6-13 https://devguide.python.org/ +RedirectTemp /6-14 https://www.python.org/dev/peps/pep-0442/ +RedirectTemp /6-15 https://en.wikipedia.org/wiki/String_interning +RedirectTemp /6-16 https://en.wikipedia.org/wiki/Haddocks%27_Eyes +RedirectTemp /6-17 https://thp.io/2012/python-gc/python_gc_final_2012-01-22.pdf +############################################################ 07 +RedirectTemp /7-1 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html +RedirectTemp /7-2 https://www.fluentpython.com/extra/function-introspection/ +RedirectTemp /7-3 https://docs.python.org/3/library/functions.html#map +RedirectTemp /7-4 https://en.wikipedia.org/wiki/Functional_programming +RedirectTemp /7-5 https://docs.python.org/3/howto/functional.html +RedirectTemp /7-6 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /7-7 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters +RedirectTemp /7-8 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters +RedirectTemp /7-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/functools.py#L341 +RedirectTemp /7-10 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /7-11 https://docs.python.org/3/howto/functional.html +RedirectTemp /7-12 https://stackoverflow.com/questions/3252228/python-why-is-functools-partial-necessary +RedirectTemp /7-13 https://speakerdeck.com/ramalho/beyond-paradigms-berlin-edition +RedirectTemp /7-14 https://www.youtube.com/watch?v=bF3a2VYXxa0 +RedirectTemp /7-15 http://cs.brown.edu/~sk/Publications/Papers/Published/sk-teach-pl-post-linnaean/ +RedirectTemp /7-16 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html +RedirectTemp /7-17 https://raw.githubusercontent.com/python/cpython/main/Misc/HISTORY +RedirectTemp /7-18 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html +############################################################ 08 +RedirectTemp /8-1 https://www.python.org/dev/peps/pep-0484/#non-goals +RedirectTemp /8-2 https://github.com/python/typing/issues/182 +RedirectTemp /8-3 https://github.com/python/mypy/issues/731 +RedirectTemp /8-4 https://github.com/google/pytype +RedirectTemp /8-5 https://github.com/Microsoft/pyright +RedirectTemp /8-6 https://pyre-check.org/ +RedirectTemp /8-7 https://mypy.readthedocs.io/en/stable/introduction.html +RedirectTemp /8-8 https://mypy.readthedocs.io/en/stable/config_file.html +RedirectTemp /8-9 https://pypi.org/project/flake8/ +RedirectTemp /8-10 https://pypi.org/project/blue/ +RedirectTemp /8-11 https://pypi.org/project/black/ +RedirectTemp /8-12 https://wefearchange.org/2020/11/steeringcouncil.rst.html +RedirectTemp /8-13 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes +RedirectTemp /8-14 https://en.wikipedia.org/wiki/Barbara_Liskov +RedirectTemp /8-15 https://en.wikipedia.org/wiki/Behavioral_subtyping +RedirectTemp /8-16 https://www.python.org/dev/peps/pep-0585/#implementation +RedirectTemp /8-17 https://docs.python.org/3/library/typing.html#module-contents +RedirectTemp /8-18 https://en.wikipedia.org/wiki/Geohash +RedirectTemp /8-19 https://en.wikipedia.org/wiki/Inverted_index +RedirectTemp /8-20 https://docs.python.org/3/library/typing.html#typing.List +RedirectTemp /8-21 https://docs.python.org/3/library/typing.html#typing.Dict +RedirectTemp /8-22 https://docs.python.org/3/library/typing.html#typing.Set +RedirectTemp /8-23 https://www.python.org/dev/peps/pep-0585/#implementation +RedirectTemp /8-24 https://docs.python.org/3/library/numbers.html +RedirectTemp /8-25 https://docs.python.org/3/library/typing.html#typing.List +RedirectTemp /8-26 https://github.com/python/typeshed +RedirectTemp /8-27 https://github.com/python/typeshed/blob/66cd36268a6a667714efaa27198a41d0d7f89477/stdlib/2and3/math.pyi#L45 +RedirectTemp /8-28 https://docs.python.org/3/library/statistics.html#statistics.mode +RedirectTemp /8-29 https://github.com/python/cpython/blob/822efa5695b5ba6c2316c1400e4e9ec2546f7ea5/Lib/statistics.py#L534 +RedirectTemp /8-30 https://github.com/python/typeshed/blob/e1e99245bb46223928eba68d4fc74962240ba5b4/stdlib/3/statistics.pyi +RedirectTemp /8-31 https://docs.python.org/3/library/statistics.html#statistics.mode +RedirectTemp /8-32 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/3/statistics.pyi#L32 +RedirectTemp /8-33 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ +RedirectTemp /8-34 https://docs.python.org/3/library/typing.html#typing.Callable +RedirectTemp /8-35 https://pypi.org/project/blue/ +RedirectTemp /8-36 https://www.python.org/dev/peps/pep-0484/#id38 +RedirectTemp /8-37 https://docs.google.com/document/d/1aXs1tpwzPjW9MdsG5dI7clNFyYayFBkcXwRDo-qvbIk/preview +RedirectTemp /8-38 https://www.oreilly.com/library/view/the-best-software/9781590595008/ +RedirectTemp /8-39 https://www.youtube.com/watch?v=YFexUDjHO6w +RedirectTemp /8-40 https://www.youtube.com/watch?v=YFexUDjHO6w&t=13m40s +RedirectTemp /8-41 https://bernat.tech/posts/the-state-of-type-hints-in-python/ +RedirectTemp /8-42 https://realpython.com/python-type-checking/ +RedirectTemp /8-43 https://cjolowicz.github.io/posts/hypermodern-python-04-typing/ +RedirectTemp /8-44 https://mypy.readthedocs.io/en/stable/index.html +RedirectTemp /8-45 https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html +RedirectTemp /8-46 https://mypy.readthedocs.io/en/stable/common_issues.html +RedirectTemp /8-47 https://github.com/typeddjango/awesome-python-typing +RedirectTemp /8-48 https://docs.python.org/3/library/functions.html#max +RedirectTemp /8-49 https://en.wikipedia.org/wiki/Linguistic_relativity +RedirectTemp /8-50 https://pypistats.org/top +RedirectTemp /8-51 https://github.com/psf/requests/issues/3855 +RedirectTemp /8-52 https://lwn.net/Articles/643399/ +RedirectTemp /8-53 https://docs.python-requests.org/en/master/api/#requests.request +RedirectTemp /8-54 https://queue.acm.org/detail.cfm?id=1039523 +############################################################ 09 +RedirectTemp /9-1 https://docs.python.org/3/library/dis.html +RedirectTemp /9-2 https://en.wikipedia.org/wiki/Memoization +RedirectTemp /9-3 https://numpy.org/doc/stable/user/basics.types.html +RedirectTemp /9-4 https://docs.python.org/3/library/functools.html#functools.singledispatch +RedirectTemp /9-5 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/README.md +RedirectTemp /9-6 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/01-how-you-implemented-your-python-decorator-is-wrong.md +RedirectTemp /9-7 https://wrapt.readthedocs.io/en/latest/ +RedirectTemp /9-8 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch09.html +RedirectTemp /9-9 https://pypi.org/project/decorator/ +RedirectTemp /9-10 https://wiki.python.org/moin/PythonDecoratorLibrary +RedirectTemp /9-11 http://web.archive.org/web/20201109032203/http://effbot.org/zone/closure.htm +RedirectTemp /9-12 https://www.python.org/dev/peps/pep-3104/ +RedirectTemp /9-13 https://www.python.org/dev/peps/pep-0227/ +RedirectTemp /9-14 https://www.python.org/dev/peps/pep-0443/ +RedirectTemp /9-15 https://www.artima.com/weblogs/viewpost.jsp?thread=101605 +RedirectTemp /9-16 https://reg.readthedocs.io/en/latest/ +RedirectTemp /9-17 https://morepath.readthedocs.io/en/latest/ +RedirectTemp /9-18 https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Binding.html +RedirectTemp /9-19 http://www.paulgraham.com/rootsoflisp.html +RedirectTemp /9-20 http://www-formal.stanford.edu/jmc/recursive/recursive.html +RedirectTemp /9-21 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this +############################################################ 10 +RedirectTemp /10-1 https://en.wikipedia.org/wiki/Software_design_pattern +RedirectTemp /10-2 https://en.wikipedia.org/wiki/Iterator_pattern +RedirectTemp /10-3 https://github.com/python/mypy/issues/9397 +RedirectTemp /10-4 http://www.norvig.com/design-patterns/index.htm +RedirectTemp /10-5 https://pyvideo.org/video/1110/python-design-patterns/ +RedirectTemp /10-6 http://www.aleax.it/gdd_pydp.pdf +RedirectTemp /10-7 https://perl.plover.com/yak/design/ +RedirectTemp /10-8 https://en.wikipedia.org/wiki/Turtles_all_the_way_down +############################################################ 11 +RedirectTemp /11-1 https://blog.startifact.com/posts/older/what-is-pythonic.html +RedirectTemp /11-2 https://julien.danjou.info/guide-python-static-class-abstract-methods/ +RedirectTemp /11-3 https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /11-4 https://docs.python.org/3/reference/lexical_analysis.html#f-strings +RedirectTemp /11-5 https://docs.python.org/3/library/string.html#format-string-syntax +RedirectTemp /11-6 https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /11-7 https://docs.python.org/3/reference/datamodel.html#object.__hash__ +RedirectTemp /11-8 https://web.archive.org/web/20161025185040/http://pythonpaste.org/StyleGuide.html +RedirectTemp /11-9 https://docs.python.org/3/tutorial/modules.html#more-on-modules +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 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 +RedirectTemp /11-17 https://docs.oracle.com/javase/tutorial/essential/environment/security.html +############################################################ 12 +RedirectTemp /12-1 https://en.wikipedia.org/wiki/Vector_space_model +RedirectTemp /12-2 https://pypi.org/project/gensim/ +RedirectTemp /12-3 https://docs.python.org/3/library/functions.html#enumerate +RedirectTemp /12-4 https://mathworld.wolfram.com/Hypersphere.html +RedirectTemp /12-5 https://en.wikipedia.org/wiki/Fold_(higher-order_function) +RedirectTemp /12-6 https://docs.python.org/2.5/whatsnew/pep-357.html +RedirectTemp /12-7 https://docs.python.org/3/reference/datamodel.html#special-method-names +RedirectTemp /12-8 https://en.wikipedia.org/wiki/KISS_principle +RedirectTemp /12-9 https://mail.python.org/pipermail/python-list/2000-July/046184.html +RedirectTemp /12-10 https://en.wikipedia.org/wiki/Duck_typing +RedirectTemp /12-11 https://mail.python.org/mailman/listinfo/python-list +RedirectTemp /12-12 https://mail.python.org/pipermail/python-list/2003-April/218568.html +############################################################ 13 +RedirectTemp /13-1 https://docs.python.org/3/c-api/index.html +RedirectTemp /13-2 https://docs.python.org/3/c-api/sequence.html +RedirectTemp /13-3 https://github.com/python/cpython/blob/31ceccb2c77854893f3a754aca04bedd74bedb10/Lib/_collections_abc.py#L870 +RedirectTemp /13-4 https://en.wikipedia.org/wiki/Monkey_patch +RedirectTemp /13-5 https://www.gevent.org/api/gevent.monkey.html +RedirectTemp /13-6 https://docs.python.org/3/library/random.html#random.shuffle +RedirectTemp /13-7 https://docs.python.org/3/reference/datamodel.html#emulating-container-types +RedirectTemp /13-8 https://docs.python.org/3/library/collections.html#collections.namedtuple +RedirectTemp /13-9 https://github.com/python/typeshed/blob/24afb531ffd07083d6a74be917342195062f7277/stdlib/collections/__init__.pyi +RedirectTemp /13-10 https://docs.python.org/3/glossary.html#term-abstract-base-class +RedirectTemp /13-11 https://en.wikipedia.org/wiki/Duck_typing#History +RedirectTemp /13-12 http://ptgmedia.pearsoncmg.com/images/020163371x/items/item33.html +RedirectTemp /13-13 https://docs.python.org/3/library/bisect.html#bisect.bisect +RedirectTemp /13-14 https://github.com/python/cpython/blob/main/Lib/_collections_abc.py +RedirectTemp /13-15 https://github.com/python/cpython/blob/main/Lib/abc.py +RedirectTemp /13-16 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes +RedirectTemp /13-17 https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable +RedirectTemp /13-18 https://docs.python.org/3/library/abc.html +RedirectTemp /13-19 https://docs.python.org/dev/library/abc.html#abc.abstractmethod +RedirectTemp /13-20 https://docs.python.org/dev/library/abc.html +RedirectTemp /13-21 https://docs.python.org/3/library/os.html#os.urandom +RedirectTemp /13-22 https://github.com/python/mypy/issues/2922 +RedirectTemp /13-23 https://docs.python.org/3/library/stdtypes.html#truth +RedirectTemp /13-24 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py +RedirectTemp /13-25 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L369 +RedirectTemp /13-26 https://github.com/python/cpython/blob/c0a9afe2ac1820409e6173bd1893ebee2cf50270/Lib/abc.py#L196 +RedirectTemp /13-27 https://bugs.python.org/issue31333 +RedirectTemp /13-28 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Modules/_abc.c#L605 +RedirectTemp /13-29 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L881 +RedirectTemp /13-30 https://docs.python.org/3/library/typing.html#protocols +RedirectTemp /13-31 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Lib/typing.py#L1751 +RedirectTemp /13-32 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ +RedirectTemp /13-33 https://martinfowler.com/bliki/RoleInterface.html +RedirectTemp /13-34 https://en.wikipedia.org/wiki/Interface_segregation_principle +RedirectTemp /13-35 https://github.com/python/typeshed/blob/master/CONTRIBUTING.md +RedirectTemp /13-36 https://gist.github.com/asukakenji/ac8a05644a2e98f1d5ea8c299541fce9 +RedirectTemp /13-37 https://www.python.org/dev/peps/pep-0544/#runtime-checkable-decorator-and-narrowing-types-by-isinstance +RedirectTemp /13-38 https://www.python.org/dev/peps/pep-0544/#merging-and-extending-protocols +RedirectTemp /13-39 https://numpy.org/devdocs/user/basics.types.html +RedirectTemp /13-40 https://github.com/python/typeshed/blob/master/stdlib/statistics.pyi +RedirectTemp /13-41 https://bugs.python.org/issue41974 +RedirectTemp /13-42 https://glyph.twistedmatrix.com/2020/07/new-duck.html +RedirectTemp /13-43 https://glyph.twistedmatrix.com/2021/03/interfaces-and-protocols.html +RedirectTemp /13-44 https://plone.org/ +RedirectTemp /13-45 https://trypyramid.com/ +RedirectTemp /13-46 https://twistedmatrix.com/trac/ +RedirectTemp /13-47 https://www.artima.com/articles/contracts-in-python +RedirectTemp /13-48 https://martinfowler.com/bliki/DynamicTyping.html +RedirectTemp /13-49 https://martinfowler.com/bliki/RoleInterface.html +RedirectTemp /13-50 https://mypy.readthedocs.io/en/stable/protocols.html +RedirectTemp /13-51 https://pymotw.com/3/abc/index.html +RedirectTemp /13-52 https://www.python.org/dev/peps/pep-3119/ +RedirectTemp /13-53 https://www.python.org/dev/peps/pep-3141/ +RedirectTemp /13-54 https://docs.python.org/3/library/numbers.html +RedirectTemp /13-55 https://github.com/python/mypy/issues/3186 +RedirectTemp /13-56 https://stackoverflow.com/questions/69334475/how-to-hint-at-number-types-i-e-subclasses-of-number-not-numbers-themselv/69383462#69383462 +RedirectTemp /13-57 https://github.com/python/mypy/issues/3186 +RedirectTemp /13-58 https://martinfowler.com/articles/lean-inception/ +RedirectTemp /13-59 https://martinfowler.com +RedirectTemp /13-60 https://www.jetbrains.com/pycharm/ +RedirectTemp /13-61 https://wingware.com/ +RedirectTemp /13-62 https://code.visualstudio.com/ +############################################################ 14 +RedirectTemp /14-1 http://worrydream.com/EarlyHistoryOfSmalltalk/ +RedirectTemp /14-2 https://docs.python.org/3/tutorial/classes.html +RedirectTemp /14-3 https://docs.python.org/3/library/collections.html#ordereddict-examples-and-recipes +RedirectTemp /14-4 https://discuss.python.org/t/is-it-time-to-deprecate-unbound-super-methods/1833 +RedirectTemp /14-5 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /14-6 https://docs.python.org/3/library/collections.html +RedirectTemp /14-7 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/strkeydict_dictsub.py +RedirectTemp /14-8 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /14-9 https://en.wikipedia.org/wiki/Breadth-first_search +RedirectTemp /14-10 https://www.python.org/download/releases/2.3/mro/ +RedirectTemp /14-11 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/uppermixin.py +RedirectTemp /14-12 https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html +RedirectTemp /14-13 https://docs.python.org/3/library/collections.abc.html +RedirectTemp /14-14 https://github.com/python/cpython/blob/8ece98a7e418c3c68a4c61bc47a2d0931b59a889/Lib/collections/__init__.py#L1084 +RedirectTemp /14-15 https://docs.python.org/3/library/http.server.html +RedirectTemp /14-16 https://github.com/python/cpython/blob/17c23167942498296f0bdfffe52e72d53d66d693/Lib/http/server.py#L144 +RedirectTemp /14-17 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L664 +RedirectTemp /14-18 https://docs.python.org/3/library/socketserver.html#socketserver.ForkingMixIn +RedirectTemp /14-19 https://docs.python.org/3/library/os.html#os.fork +RedirectTemp /14-20 https://en.wikipedia.org/wiki/POSIX +RedirectTemp /14-21 http://ccbv.co.uk/ +RedirectTemp /14-22 https://github.com/django/django/tree/main/django/views/generic +RedirectTemp /14-23 https://en.wikipedia.org/wiki/Template_method_pattern +RedirectTemp /14-24 https://docs.python.org/3/library/tkinter.html +RedirectTemp /14-25 https://docs.python.org/3/library/tkinter.ttk.html +RedirectTemp /14-26 https://docs.oracle.com/javase/10/docs/api/java/awt/package-tree.html +RedirectTemp /14-27 https://docs.oracle.com/javase/10/docs/api/javax/swing/package-tree.html +RedirectTemp /14-28 https://squeak.org/ +RedirectTemp /14-29 https://github.com/django/django/blob/b64db05b9cedd96905d637a2d824cbbf428e40e7/django/views/generic/list.py#L194 +RedirectTemp /14-30 https://github.com/python/cpython/blob/8ed183391241f0c73e7ba7f42b1d49fc02985f7b/Lib/tkinter/__init__.py#L2618 +RedirectTemp /14-31 https://docs.python.org/3/library/socketserver.html +RedirectTemp /14-32 https://docs.python.org/3/library/socketserver.html#socketserver.BaseServer +RedirectTemp /14-33 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L153 +RedirectTemp /14-34 https://docs.python.org/3/library/typing.html#typing.final +RedirectTemp /14-35 https://docs.python.org/3/library/typing.html#typing.Final +RedirectTemp /14-36 https://docs.python.org/3/library/collections.abc.html +RedirectTemp /14-37 https://hynek.me/articles/python-subclassing-redux/ +RedirectTemp /14-38 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch08.html#super +RedirectTemp /14-39 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ +RedirectTemp /14-40 https://fuhm.net/super-harmful/ +RedirectTemp /14-41 https://stackoverflow.com/questions/30190185/how-to-use-super-with-one-argument/30190341#30190341 +RedirectTemp /14-42 https://www.artima.com/weblogs/viewpost.jsp?thread=246488 +RedirectTemp /14-43 https://www.artima.com/weblogs/viewpost.jsp?thread=281127 +RedirectTemp /14-44 https://www.artima.com/weblogs/viewpost.jsp?thread=246341 +RedirectTemp /14-45 https://www.artima.com/weblogs/viewpost.jsp?thread=246483 +RedirectTemp /14-46 https://www.artima.com/weblogs/viewpost.jsp?thread=236275 +RedirectTemp /14-47 https://www.artima.com/weblogs/viewpost.jsp?thread=236278 +RedirectTemp /14-48 https://www.artima.com/weblogs/viewpost.jsp?thread=237121 +RedirectTemp /14-49 https://python-patterns.guide/gang-of-four/composition-over-inheritance/ +RedirectTemp /14-50 https://python-patterns.guide/ +RedirectTemp /14-51 https://www.youtube.com/watch?v=3MNVP9-hglc +RedirectTemp /14-52 http://worrydream.com/EarlyHistoryOfSmalltalk/ +RedirectTemp /14-53 https://en.wikipedia.org/wiki/Polymorphism_(computer_science) +############################################################ 15 +RedirectTemp /15-1 https://www.youtube.com/watch?v=csL8DLXGNlU&t=92m5s +RedirectTemp /15-2 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi#L1434 +RedirectTemp /15-3 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi +RedirectTemp /15-4 https://twitter.com/gwidion/status/1265384692464967680 +RedirectTemp /15-5 https://pypi.org/project/pydantic/ +RedirectTemp /15-6 https://google.github.io/pytype/faq.html +RedirectTemp /15-7 https://google.github.io/pytype/faq.html +RedirectTemp /15-8 https://lxml.de/ +RedirectTemp /15-9 https://docs.python.org/3/library/xml.etree.elementtree.html +RedirectTemp /15-10 https://mypy.readthedocs.io/en/stable/common_issues.html +RedirectTemp /15-11 https://mypy.readthedocs.io/en/stable/common_issues.html#types-of-empty-collections +RedirectTemp /15-12 https://github.com/python/typing/issues/182 +RedirectTemp /15-13 https://pypi.org/project/pydantic/ +RedirectTemp /15-14 https://mypy.readthedocs.io/en/stable/type_narrowing.html#casts +RedirectTemp /15-15 https://github.com/python/cpython/blob/bee66d3cb98e740f9d8057eb7f503122052ca5d8/Lib/typing.py#L1340 +RedirectTemp /15-16 https://www.python.org/dev/peps/pep-0484/#casts +RedirectTemp /15-17 https://github.com/python/typeshed/issues/5535 +RedirectTemp /15-18 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /15-19 https://github.com/python/cpython/blob/b798ab06937f8bb24b444a49dd42e11fff15e654/Lib/test/test_asyncio/test_server.py#L55 +RedirectTemp /15-20 https://en.wikipedia.org/wiki/Code_smell +RedirectTemp /15-21 https://mail.python.org/archives/list/typing-sig@python.org/message/5LCWMN2UY2UQNLC5Z47GHBZKSPZW4I63/ +RedirectTemp /15-22 https://mypy.readthedocs.io/en/stable/error_codes.html#error-codes +RedirectTemp /15-23 https://github.com/fluentpython/example-code-2e/blob/master/15-more-types/clip_annot.py +RedirectTemp /15-24 https://docs.python.org/3/library/typing.html#introspection-helpers +RedirectTemp /15-25 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations +RedirectTemp /15-26 https://www.python.org/dev/peps/pep-0563/#abstract +RedirectTemp /15-27 https://mail.python.org/archives/list/python-dev@python.org/message/ZBJ7MD6CSGM6LZAOTET7GXAVBZB7O77O/ +RedirectTemp /15-28 https://docs.python.org/3.10/howto/annotations.html +RedirectTemp /15-29 https://docs.python.org/3/library/typing.html#user-defined-generic-types +RedirectTemp /15-30 https://github.com/python/typeshed/blob/bfc83c365a0b26ab16586beac77ff16729d0e473/stdlib/builtins.pyi#L743 +RedirectTemp /15-31 https://docs.python.org/3.10/library/typing.html#typing.FrozenSet +RedirectTemp /15-32 https://docs.python.org/3.10/library/typing.html#typing.Generator +RedirectTemp /15-33 https://docs.python.org/3.10/library/typing.html#typing.AsyncGenerator +RedirectTemp /15-34 https://github.com/python/cpython/blob/46b16d0bdbb1722daed10389e27226a2370f1635/Lib/typing.py#L1786 +RedirectTemp /15-35 https://github.com/python/typeshed/blob/2a9f081abbf01134e4e04ced6a750107db904d70/stdlib/builtins.pyi#L239 +RedirectTemp /15-36 https://www.oreilly.com/library/view/robust-python/9781098100650/ +RedirectTemp /15-37 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance +RedirectTemp /15-38 https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generic-types +RedirectTemp /15-39 https://mypy.readthedocs.io/en/stable/common_issues.html#variance +RedirectTemp /15-40 https://www.artima.com/weblogs/viewpost.jsp?thread=85551 +RedirectTemp /15-41 https://dl.acm.org/action/cookieAbsent +RedirectTemp /15-42 http://bracha.org/pluggableTypesPosition.pdf +RedirectTemp /15-43 https://www.researchgate.net/publication/213886116_Static_Typing_Where_Possible_Dynamic_Typing_When_Needed_The_End_of_the_Cold_War_Between_Programming_Languages +RedirectTemp /15-44 https://www.atomickotlin.com/atomickotlin/ +RedirectTemp /15-45 https://www.informit.com/store/effective-java-9780134685991 +RedirectTemp /15-46 https://www.manning.com/books/programming-with-types +RedirectTemp /15-47 https://www.oreilly.com/library/view/programming-typescript/9781492037644/ +RedirectTemp /15-48 https://www.informit.com/store/dart-programming-language-9780321927705 +RedirectTemp /15-49 https://www.yodaiken.com/2017/09/15/bad-ideas-in-type-theory/ +RedirectTemp /15-50 https://www.yodaiken.com/2017/11/30/types-considered-harmful-ii/ +RedirectTemp /15-51 https://web.archive.org/web/20071010002142/http://weblogs.java.net/blog/arnold/archive/2005/06/generics_consid_1.html +RedirectTemp /15-52 https://github.com/python/cpython/blob/3e7ee02327db13e4337374597cdc4458ecb9e3ad/Lib/asyncio/trsock.py#L5 +RedirectTemp /15-53 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance +############################################################ 16 +RedirectTemp /16-1 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-2 https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations +RedirectTemp /16-3 https://docs.python.org/3/reference/datamodel.html#object.__neg__ +RedirectTemp /16-4 https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#boolean-indexing +RedirectTemp /16-5 https://docs.python.org/3/library/collections.html#collections.Counter +RedirectTemp /16-6 https://docs.python.org/3/reference/datamodel.html#emulating-container-types +RedirectTemp /16-7 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations +RedirectTemp /16-8 https://www.fluentpython.com/lingo/#fail-fast +RedirectTemp /16-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Objects/typeobject.c#L4598 +RedirectTemp /16-10 https://neopythonic.blogspot.com/2019/03/why-operators-are-useful.html +RedirectTemp /16-11 https://treyhunner.com/2019/03/python-deep-comparisons-and-code-readability/ +RedirectTemp /16-12 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations +RedirectTemp /16-13 https://docs.python.org/3/library/pathlib.html +RedirectTemp /16-14 https://pypi.org/project/scapy/ +RedirectTemp /16-15 https://scapy.readthedocs.io/en/latest/usage.html#stacking-layers +RedirectTemp /16-16 https://docs.python.org/3/library/functools.html#functools.total_ordering +RedirectTemp /16-17 https://wiki.illinois.edu//wiki/download/attachments/273416327/ingalls.pdf +RedirectTemp /16-18 https://wiki.illinois.edu//wiki/download/attachments/273416327/double-dispatch.pdf +RedirectTemp /16-19 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-20 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-21 https://doc.rust-lang.org/std/ops/index.html +RedirectTemp /16-22 https://www.fluentpython.com/lingo/#lazy +############################################################ 17 +RedirectTemp /17-1 http://www.paulgraham.com/icad.html +RedirectTemp /17-2 https://en.wikipedia.org/wiki/Sentinel_value +RedirectTemp /17-3 https://docs.python.org/3.10/library/functions.html#iter +RedirectTemp /17-4 https://docs.python.org/3.10/library/functions.html#iter +RedirectTemp /17-5 https://github.com/python/cpython/blob/b1930bf75f276cd7ca08c4455298128d89adf7d1/Lib/_collections_abc.py#L271 +RedirectTemp /17-6 https://github.com/python/cpython/blob/main/Lib/types.py#L6 +RedirectTemp /17-7 https://en.wikipedia.org/wiki/CLU_(programming_language) +RedirectTemp /17-8 https://docs.python.org/3/glossary.html +RedirectTemp /17-9 https://docs.python.org/3/glossary.html#term-generator-iterator +RedirectTemp /17-10 https://docs.python.org/3/glossary.html#term-generator-expression +RedirectTemp /17-11 https://marc.info/?l=python-list&m=141826925106951&w=2 +RedirectTemp /17-12 https://docs.python.org/3/library/os.html#os.walk +RedirectTemp /17-13 https://docs.python.org/3/library/itertools.html +RedirectTemp /17-14 https://docs.python.org/3/library/exceptions.html#exception-hierarchy +RedirectTemp /17-15 https://en.wikipedia.org/wiki/Depth-first_search +RedirectTemp /17-16 https://docs.python.org/3.10/library/typing.html#typing.TypeAlias +RedirectTemp /17-17 https://docs.python.org/3/library/typing.html#typing.Generator +RedirectTemp /17-18 http://www.dabeaz.com/coroutines/Coroutines.pdf +RedirectTemp /17-19 http://www.dabeaz.com/coroutines/Coroutines.pdf +RedirectTemp /17-20 https://mail.python.org/pipermail/python-ideas/2009-April/003841.html +RedirectTemp /17-21 https://mail.python.org/pipermail/python-ideas/2009-April/003912.html +RedirectTemp /17-22 https://docs.python.org/3/library/exceptions.html#StopIteration +RedirectTemp /17-23 https://docs.python.org/3/reference/expressions.html#yield-expressions +RedirectTemp /17-24 https://docs.python.org/3/reference/index.html +RedirectTemp /17-25 https://github.com/python/cpython/blob/6f743e7a4da904f61dfa84cc7d7385e4dcc79ac5/Lib/typing.py#L2060 +RedirectTemp /17-26 http://catb.org/~esr/jargon/html/G/grok.html +RedirectTemp /17-27 https://docs.python.org/3/reference/expressions.html#yieldexpr +RedirectTemp /17-28 https://docs.python.org/3/library/itertools.html +RedirectTemp /17-29 https://docs.python.org/3/library/itertools.html#itertools-recipes +RedirectTemp /17-30 https://more-itertools.readthedocs.io/en/stable/index.html +RedirectTemp /17-31 https://rittau.org/2006/11/java-iterators-are-not-iterable/ +RedirectTemp /17-32 https://docs.python.org/3/whatsnew/3.3.html#pep-380-syntax-for-delegating-to-a-subgenerator +RedirectTemp /17-33 http://www.dabeaz.com/generators/ +RedirectTemp /17-34 http://www.dabeaz.com/coroutines/ +RedirectTemp /17-35 https://archive.org/details/pyvideo_213___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-1-of-3 +RedirectTemp /17-36 https://archive.org/details/pyvideo_215___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-2-of-3 +RedirectTemp /17-37 https://archive.org/details/pyvideo_214___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-3-of-3 +RedirectTemp /17-38 http://www.dabeaz.com/finalgenerator/ +RedirectTemp /17-39 https://web.archive.org/web/20200218150637/http://seriously.dontusethiscode.com/2013/05/01/greedy-coroutine.html +RedirectTemp /17-40 https://effectivepython.com/ +RedirectTemp /17-41 https://effectivepython.com/2015/03/10/consider-coroutines-to-run-many-functions-concurrently +RedirectTemp /17-42 https://en.wikipedia.org/wiki/Conway's_Game_of_Life +RedirectTemp /17-43 https://gist.github.com/ramalho/da5590bc38c973408839 +RedirectTemp /17-44 https://gist.github.com/ramalho/da5590bc38c973408839 +RedirectTemp /17-45 https://journal.code4lib.org/articles/4893 +RedirectTemp /17-46 https://github.com/fluentpython/isis2json +RedirectTemp /17-47 https://github.com/fluentpython/isis2json/blob/master/README.rst +############################################################ 18 +RedirectTemp /18-1 https://pyvideo.org/video/1669/keynote-3/ +RedirectTemp /18-2 https://docs.python.org/3/library/sqlite3.html#using-the-connection-as-a-context-manager +RedirectTemp /18-3 https://docs.python.org/3/library/threading.html#using-locks-conditions-and-semaphores-in-the-with-statement +RedirectTemp /18-4 https://docs.python.org/3/library/decimal.html#decimal.localcontext +RedirectTemp /18-5 https://docs.python.org/3/library/unittest.mock.html#patch +RedirectTemp /18-6 https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout +RedirectTemp /18-7 https://docs.python.org/3/library/sys.html#sys.exc_info +RedirectTemp /18-8 https://en.wikipedia.org/wiki/LL_parser +RedirectTemp /18-9 https://docs.python.org/3/library/contextlib.html +RedirectTemp /18-10 https://github.com/python/cpython/blob/8afab2ebbc1b343cd88d058914cf622fe687a2be/Lib/contextlib.py#L123 +RedirectTemp /18-11 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ +RedirectTemp /18-12 https://docs.python.org/3/library/fileinput.html#fileinput.input +RedirectTemp /18-13 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ +RedirectTemp /18-14 https://en.wikipedia.org/wiki/Euclidean_algorithm +RedirectTemp /18-15 https://github.com/fluentpython/example-code-2e/tree/master/18-with-match/lispy/py3.10/ +RedirectTemp /18-16 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lispy.py +RedirectTemp /18-17 https://github.com/fluentpython/example-code-2e/blob/6527037ae7319ba370a1ee2d9fe79214d0ed9452/18-with-match/lispy/py3.10/lis.py#L35 +RedirectTemp /18-18 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py +RedirectTemp /18-19 https://github.com/python/typeshed/issues/6042 +RedirectTemp /18-20 https://github.com/fluentpython/lispy/tree/main/mylis +RedirectTemp /18-21 https://github.com/fluentpython/example-code-2e/blob/00e4741926e1b771ee7c753148b1415c0bd12e39/02-array-seq/lispy/py3.10/examples_test.py +RedirectTemp /18-22 https://mitpress.mit.edu/sites/default/files/sicp/index.html +RedirectTemp /18-23 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py +RedirectTemp /18-24 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/lis.py +RedirectTemp /18-25 https://www.python.org/dev/peps/pep-0634/#or-patterns +RedirectTemp /18-26 https://en.wikipedia.org/wiki/Lambda#Character_encodings +RedirectTemp /18-27 https://docs.python.org/3/reference/compound_stmts.html +RedirectTemp /18-28 https://docs.python.org/3/glossary.html#term-eafp +RedirectTemp /18-29 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 +RedirectTemp /18-30 https://docs.python.org/3/reference/compound_stmts.html +RedirectTemp /18-31 https://stackoverflow.com/questions/16138232/is-it-a-good-practice-to-use-try-except-else-in-python +RedirectTemp /18-32 https://docs.python.org/3/library/stdtypes.html#typecontextmanager +RedirectTemp /18-33 https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers +RedirectTemp /18-34 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 +RedirectTemp /18-35 https://speakerdeck.com/pyconslides/transforming-code-into-beautiful-idiomatic-python-by-raymond-hettinger-1?slide=34 +RedirectTemp /18-36 https://preshing.com/20110920/the-python-with-statement-by-example/ +RedirectTemp /18-37 https://www.rath.org/on-the-beauty-of-pythons-exitstack.html +RedirectTemp /18-38 https://norvig.com/lispy.html +RedirectTemp /18-39 https://norvig.com/lispy2.html +RedirectTemp /18-40 https://github.com/norvig/pytudes +RedirectTemp /18-41 https://github.com/fluentpython/lispy +RedirectTemp /18-42 https://racket-lang.org/ +RedirectTemp /18-43 https://pyvideo.org/video/1669/keynote-3/ +RedirectTemp /18-44 https://en.wikipedia.org/wiki/Tail_call +RedirectTemp /18-45 https://2ality.com/2015/06/tail-call-optimization.html +RedirectTemp /18-46 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lis.py +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 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 +RedirectTemp /18-54 https://github.com/fluentpython/lispy/tree/main/mylis +############################################################ 19 +RedirectTemp /19-1 https://go.dev/blog/waza-talk +RedirectTemp /19-2 https://en.wikipedia.org/wiki/Graphics_processing_unit +RedirectTemp /19-3 https://docs.python.org/3/library/sys.html#sys.getswitchinterval +RedirectTemp /19-4 https://docs.python.org/3/library/sys.html#sys.setswitchinterval +RedirectTemp /19-5 https://en.wikipedia.org/wiki/System_call +RedirectTemp /19-6 https://mail.python.org/pipermail/python-dev/2009-October/093356.html +RedirectTemp /19-7 http://www.dabeaz.com/finalgenerator/ +RedirectTemp /19-8 https://docs.python.org/3/library/threading.html#thread-objects +RedirectTemp /19-9 https://www.pypy.org/ +RedirectTemp /19-10 https://mail.python.org/pipermail/python-list/2009-February/675659.html +RedirectTemp /19-11 https://en.wikipedia.org/wiki/Braille_Patterns +RedirectTemp /19-12 https://docs.python.org/3/library/multiprocessing.shared_memory.html +RedirectTemp /19-13 https://docs.python.org/3/library/multiprocessing.shared_memory.html#multiprocessing.shared_memory.ShareableList +RedirectTemp /19-14 https://greenlet.readthedocs.io/en/latest/ +RedirectTemp /19-15 https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#asynchronous-io-support-for-core-and-orm +RedirectTemp /19-16 https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html +RedirectTemp /19-17 http://www.gevent.org/ +RedirectTemp /19-18 https://github.com/gevent/gevent/wiki/Projects +RedirectTemp /19-19 https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor-example +RedirectTemp /19-20 https://github.com/python/asyncio/issues/284 +RedirectTemp /19-21 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /19-22 https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/ +RedirectTemp /19-23 https://docs.python.org/3/library/queue.html#queue.SimpleQueue.get +RedirectTemp /19-24 https://en.wikipedia.org/wiki/Race_condition +RedirectTemp /19-25 https://github.com/fluentpython/example-code-2e/commit/2c1230579db99738a5e5e6802063bda585f6476d +RedirectTemp /19-26 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/README.md +RedirectTemp /19-27 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/threads.py +RedirectTemp /19-28 https://en.wikipedia.org/wiki/Context_switch +RedirectTemp /19-29 http://www.gotw.ca/publications/concurrency-ddj.htm +RedirectTemp /19-30 https://www.ansible.com/ +RedirectTemp /19-31 https://saltproject.io/ +RedirectTemp /19-32 https://www.fabfile.org/ +RedirectTemp /19-33 https://engineering.fb.com/2016/05/27/production-engineering/python-in-production-engineering/ +RedirectTemp /19-34 https://jupyter.org/ +RedirectTemp /19-35 https://docs.bokeh.org/en/latest/index.html +RedirectTemp /19-36 https://www.tensorflow.org/ +RedirectTemp /19-37 https://pytorch.org/ +RedirectTemp /19-38 https://www.oreilly.com/radar/where-programming-ops-ai-and-the-cloud-are-headed-in-2021/ +RedirectTemp /19-39 https://www.youtube.com/watch?v=ods97a5Pzw0 +RedirectTemp /19-40 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy +RedirectTemp /19-41 https://modwsgi.readthedocs.io/en/master/ +RedirectTemp /19-42 https://uwsgi-docs.readthedocs.io/en/latest/ +RedirectTemp /19-43 https://unit.nginx.org/ +RedirectTemp /19-44 https://www.techatbloomberg.com/blog/configuring-uwsgi-production-deployment/ +RedirectTemp /19-45 https://www.youtube.com/watch?v=p6R1h2Nn468 +RedirectTemp /19-46 https://asgi.readthedocs.io/en/latest/index.html +RedirectTemp /19-47 https://docs.celeryproject.org/en/stable/getting-started/introduction.html +RedirectTemp /19-48 https://python-rq.org/ +RedirectTemp /19-49 https://docs.celeryproject.org/en/stable/faq.html#what-kinds-of-things-should-i-use-celery-for +RedirectTemp /19-50 https://redis.io/ +RedirectTemp /19-51 https://realpython.com/intro-to-python-threading/ +RedirectTemp /19-52 https://pymotw.com/3/concurrency.html +RedirectTemp /19-53 https://www.pearson.com/us/higher-education/program/Hellmann-Python-3-Standard-Library-by-Example-The/PGM328871.html +RedirectTemp /19-54 https://docs.python.org/3/library/multiprocessing.html#programming-guidelines +RedirectTemp /19-55 https://docs.python.org/3/library/multiprocessing.html +RedirectTemp /19-56 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ +RedirectTemp /19-57 https://link.springer.com/book/10.1007/978-1-4842-5793-7?error=cookies_not_supported&code=2ed5d61d-ae9f-4f3d-94ac-0f68cf45ea4f +RedirectTemp /19-58 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 +RedirectTemp /19-59 https://greenteapress.com/wp/semaphores/ +RedirectTemp /19-60 https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock +RedirectTemp /19-61 https://docs.python.org/3/faq/library.html#can-t-we-get-rid-of-the-global-interpreter-lock +RedirectTemp /19-62 https://www.artima.com/weblogs/viewpost.jsp?thread=214235 +RedirectTemp /19-63 http://jessenoller.com/blog/2009/02/01/python-threads-and-the-global-interpreter-lock +RedirectTemp /19-64 https://realpython.com/products/cpython-internals-book/ +RedirectTemp /19-65 http://www.dabeaz.com/GIL/ +RedirectTemp /19-66 http://www.dabeaz.com/python/UnderstandingGIL.pdf +RedirectTemp /19-67 https://bugs.python.org/issue7946#msg223110 +RedirectTemp /19-68 https://bugs.python.org/issue7946 +RedirectTemp /19-69 https://www.fullstackpython.com/ +RedirectTemp /19-70 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ +RedirectTemp /19-71 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 +RedirectTemp /19-72 https://www.packtpub.com/product/distributed-computing-with-python/9781785889691 +RedirectTemp /19-73 https://towardsdatascience.com/python-performance-and-gpus-1be860ffd58d?gi=a7d537cc2fb4 +RedirectTemp /19-74 https://instagram-engineering.com/web-service-efficiency-at-instagram-with-python-4976d078e366?gi=12a441991c88 +RedirectTemp /19-75 https://www.oreilly.com/library/view/architecture-patterns-with/9781492052197/ +RedirectTemp /19-76 https://www.cosmicpython.com/ +RedirectTemp /19-77 https://pypi.org/project/lelo/ +RedirectTemp /19-78 https://github.com/npryce/python-parallelize +RedirectTemp /19-79 https://github.com/ericsnowcurrently/multi-core-python/wiki +RedirectTemp /19-80 https://gist.github.com/markshannon/79cace3656b40e21b7021504daee950c +RedirectTemp /19-81 https://mail.python.org/archives/list/python-dev@python.org/message/YOOQZCFOKEPQ24YHWWLQSJ3RCXFMS7D7/ +RedirectTemp /19-82 https://en.wikipedia.org/wiki/Communicating_sequential_processes +RedirectTemp /19-83 https://github.com/stackless-dev/stackless/wiki +RedirectTemp /19-84 https://www.eveonline.com +RedirectTemp /19-85 https://www.ccpgames.com/ +RedirectTemp /19-86 https://stackless.readthedocs.io/en/3.6-slp/stackless-python.html#history +RedirectTemp /19-87 https://doc.pypy.org/en/latest/stackless.html +RedirectTemp /19-88 https://greenlet.readthedocs.io/en/latest/ +RedirectTemp /19-89 http://www.gevent.org/ +RedirectTemp /19-90 http://thespianpy.com/doc/ +RedirectTemp /19-91 https://pykka.readthedocs.io/en/latest/ +RedirectTemp /19-92 https://www.manning.com/books/rabbitmq-in-action +RedirectTemp /19-93 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ +RedirectTemp /19-94 https://en.wikipedia.org/wiki/OpenCL +RedirectTemp /19-95 https://media.pragprog.com/titles/pb7con/Bonus_Chapter.pdf +RedirectTemp /19-96 https://martinfowler.com/ +RedirectTemp /19-97 https://martinfowler.com/articles/patterns-of-distributed-systems/ +RedirectTemp /19-98 https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/ +RedirectTemp /19-99 https://www.oreilly.com/library/view/oscon-2016-video/9781491965153/video247021.html +RedirectTemp /19-100 https://www.oreilly.com/library/view/designing-for-scalability/9781449361556/ +RedirectTemp /19-101 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy +RedirectTemp /19-102 https://en.wikipedia.org/wiki/KISS_principle +RedirectTemp /19-103 https://www.usenix.org/conference/hotos15/workshop-program/presentation/mcsherry +############################################################ 20 +RedirectTemp /20-1 https://www.artima.com/weblogs/viewpost.jsp?thread=299551 +RedirectTemp /20-2 https://docs.python.org/3/library/http.server.html +RedirectTemp /20-3 https://www.youtube.com/watch?v=A9e9Cy1UkME +RedirectTemp /20-4 https://www.cia.gov/the-world-factbook/ +RedirectTemp /20-5 https://docs.python-requests.org/en/latest/ +RedirectTemp /20-6 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor +RedirectTemp /20-7 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /20-8 https://docs.python.org/3/library/concurrent.futures.html +RedirectTemp /20-9 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.Executor +RedirectTemp /20-10 https://github.com/fluentpython/example-code-2e/blob/master/20-executors/getflags/flags2_common.py +RedirectTemp /20-11 https://github.com/noamraph/tqdm +RedirectTemp /20-12 https://www.youtube.com/watch?v=M8Z65tAl5l4 +RedirectTemp /20-13 https://github.com/noamraph/tqdm/blob/master/README.md +RedirectTemp /20-14 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /20-15 https://docs.python.org/3/library/asyncio-task.html#asyncio.as_completed +RedirectTemp /20-16 https://www.cloudflare.com/ +RedirectTemp /20-17 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags +RedirectTemp /20-18 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 +RedirectTemp /20-19 https://en.wikipedia.org/wiki/Embarrassingly_parallel +RedirectTemp /20-20 https://pyvideo.org/video/480/pyconau-2010--the-future-is-soon/ +RedirectTemp /20-21 http://www.dabeaz.com/coroutines/ +RedirectTemp /20-22 https://en.wikipedia.org/wiki/POSIX_Threads +RedirectTemp /20-23 https://en.wikipedia.org/wiki/C_dynamic_memory_allocation +RedirectTemp /20-24 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ +RedirectTemp /20-25 https://hexdocs.pm/ecto/getting-started.html +############################################################ 21 +RedirectTemp /21-1 https://docs.python.org/3/library/asyncio.html +RedirectTemp /21-2 https://bugs.python.org/issue43216 +RedirectTemp /21-3 https://bugs.python.org/issue36921 +RedirectTemp /21-4 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.getaddrinfo +RedirectTemp /21-5 https://docs.python.org/3/library/socket.html#socket.getaddrinfo +RedirectTemp /21-6 https://docs.python.org/3.10/library/asyncio-eventloop.html#asyncio.get_event_loop +RedirectTemp /21-7 https://www.python.org/dev/peps/pep-0492/#await-expression +RedirectTemp /21-8 https://www.fluentpython.com/extra/classic-coroutines/#yield_from_meaning_sec +RedirectTemp /21-9 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags +RedirectTemp /21-10 https://magicstack.github.io/asyncpg/current/ +RedirectTemp /21-11 https://magicstack.github.io/asyncpg/current/api/index.html#transactions +RedirectTemp /21-12 https://magicstack.github.io/asyncpg/current/api/index.html#transactions +RedirectTemp /21-13 https://github.com/MagicStack/asyncpg/blob/4d39a05268ce4cc01b00458223a767542da048b8/asyncpg/transaction.py#L57 +RedirectTemp /21-14 https://magicstack.github.io/asyncpg/current/usage.html#connection-pools +RedirectTemp /21-15 https://gist.github.com/jboner/2841832 +RedirectTemp /21-16 https://en.wikipedia.org/wiki/Network-attached_storage +RedirectTemp /21-17 https://en.wikipedia.org/wiki/Semaphore_(programming) +RedirectTemp /21-18 https://en.wikipedia.org/wiki/Semaphore_(programming) +RedirectTemp /21-19 https://groups.google.com/forum/#!msg/python-tulip/PdAEtwpaJHs/7fqb-Qj2zJoJ +RedirectTemp /21-20 https://web.archive.org/web/20151209151711/http://tritarget.org/blog/2012/11/28/the-pyramid-of-doom-a-javascript-style-trap +RedirectTemp /21-21 https://stackoverflow.com/questions/53701841/what-is-the-use-case-for-future-add-done-callback/53710563 +RedirectTemp /21-22 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /21-23 https://motor.readthedocs.io/en/stable/ +RedirectTemp /21-24 https://emptysqua.re/blog/response-to-asynchronous-python-and-databases/ +RedirectTemp /21-25 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /21-26 https://en.wikipedia.org/wiki/Phaistos_Disc +RedirectTemp /21-27 https://en.wikipedia.org/wiki/Inverted_index +RedirectTemp /21-28 https://fastapi.tiangolo.com/ +RedirectTemp /21-29 https://swagger.io/specification/ +RedirectTemp /21-30 https://asgi.readthedocs.io/en/latest/implementations.html +RedirectTemp /21-31 https://pydantic-docs.helpmanual.io/ +RedirectTemp /21-32 https://doc.traefik.io/traefik/ +RedirectTemp /21-33 https://fastapi.tiangolo.com/project-generation/ +RedirectTemp /21-34 https://fastapi.tiangolo.com/tutorial/response-model/ +RedirectTemp /21-35 https://docs.python.org/3/library/asyncio-stream.html#asyncio.start_server +RedirectTemp /21-36 https://github.com/python/typeshed/issues/5535 +RedirectTemp /21-37 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.Server.serve_forever +RedirectTemp /21-38 https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter.close +RedirectTemp /21-39 https://docs.python.org/3/library/asyncio-stream.html#streamwriter +RedirectTemp /21-40 https://docs.python.org/3/library/asyncio-stream.html +RedirectTemp /21-41 https://docs.python.org/3/library/asyncio-protocol.html +RedirectTemp /21-42 https://docs.python.org/3/library/asyncio-protocol.html#tcp-echo-server +RedirectTemp /21-43 https://github.com/aio-libs/aiopg +RedirectTemp /21-44 https://docs.python.org/3/whatsnew/3.8.html#asyncio +RedirectTemp /21-45 https://datatracker.ietf.org/doc/html/rfc6761 +RedirectTemp /21-46 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /21-47 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /21-48 https://docs.python.org/3/library/asyncio-task.html#asyncio.gather +RedirectTemp /21-49 https://curio.readthedocs.io/en/latest/index.html +RedirectTemp /21-50 https://curio.readthedocs.io/en/latest/reference.html#task-groups +RedirectTemp /21-51 https://en.wikipedia.org/wiki/Structured_concurrency +RedirectTemp /21-52 https://mail.python.org/archives/list/python-dev@python.org/thread/2ORDAW74LGE3ZI2QETPJRT2ZL7MCCPG2/ +RedirectTemp /21-53 https://www.python.org/dev/peps/pep-0654/#motivation +RedirectTemp /21-54 https://curio.readthedocs.io/en/latest/reference.html#AWAIT +RedirectTemp /21-55 https://www.python-httpx.org/async/#curio +RedirectTemp /21-56 https://github.com/dabeaz/curio/tree/78bca8a6ad677ef51e1568ac7b3e51441ab49c42/examples +RedirectTemp /21-57 https://datatracker.ietf.org/doc/html/rfc8305 +RedirectTemp /21-58 https://trio.readthedocs.io/en/stable/ +RedirectTemp /21-59 https://www.youtube.com/watch?v=M-sc73Y-zQA +RedirectTemp /21-60 https://en.wikipedia.org/wiki/Technical_debt +RedirectTemp /21-61 https://www.youtube.com/watch?v=E-1Y4kSsAFc +RedirectTemp /21-62 https://github.com/dabeaz/curio +RedirectTemp /21-63 https://trio.readthedocs.io/en/stable/ +RedirectTemp /21-64 https://curio.readthedocs.io/en/latest/#curio-university +RedirectTemp /21-65 https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/ +RedirectTemp /21-66 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ +RedirectTemp /21-67 https://stackoverflow.com/questions/49482969/what-is-the-core-difference-between-asyncio-and-trio +RedirectTemp /21-68 https://docs.python.org/3/library/asyncio.html +RedirectTemp /21-69 https://bugs.python.org/issue33649 +RedirectTemp /21-70 https://docs.python.org/3/library/asyncio-dev.html +RedirectTemp /21-71 https://www.youtube.com/watch?v=iG6fr81xHKA +RedirectTemp /21-72 https://www.youtube.com/watch?v=F19R_M4Nay4 +RedirectTemp /21-73 https://asherman.io/projects/unsync.html +RedirectTemp /21-74 https://pyladies.com/ +RedirectTemp /21-75 https://www.youtube.com/watch?v=sW76-pRkZk8 +RedirectTemp /21-76 https://www.youtube.com/watch?v=Xbl7XjFYsN4 +RedirectTemp /21-77 https://www.youtube.com/watch?v=02CLD-42VdI +RedirectTemp /21-78 https://micropython.org/ +RedirectTemp /21-79 https://docs.micropython.org/en/latest/library/uasyncio.html +RedirectTemp /21-80 https://www.encode.io/articles/python-async-frameworks-beyond-developer-tribalism +RedirectTemp /21-81 https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ +RedirectTemp /21-82 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ +RedirectTemp /21-83 https://github.com/MagicStack/uvloop +RedirectTemp /21-84 http://magic.io/blog/uvloop-blazing-fast-python-networking/ +RedirectTemp /21-85 https://github.com/MagicStack/httptools +RedirectTemp /21-86 https://docs.aiohttp.org/en/stable/ +RedirectTemp /21-87 https://github.com/wg/wrk +RedirectTemp /21-88 https://twistedmatrix.com/trac/ +############################################################ 22 +RedirectTemp /22-1 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/data/osconfeed.json +RedirectTemp /22-2 https://pypi.org/project/attrdict/ +RedirectTemp /22-3 https://pypi.org/project/addict/ +RedirectTemp /22-4 https://github.com/ActiveState/code/tree/master/recipes/Python/52308_simple_but_handy_collector_bunch_named_stuff +RedirectTemp /22-5 https://docs.python.org/3/library/types.html#types.SimpleNamespace +RedirectTemp /22-6 https://docs.python.org/3/library/argparse.html#argparse.Namespace +RedirectTemp /22-7 https://docs.python.org/3/library/multiprocessing.html#multiprocessing.managers.Namespace +RedirectTemp /22-8 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/schedule_v2.py +RedirectTemp /22-9 https://docs.python.org/3/library/functools.html#functools.cached_property +RedirectTemp /22-10 https://docs.python.org/3/library/functools.html#functools.cached_property +RedirectTemp /22-11 https://bugs.python.org/issue42781 +RedirectTemp /22-12 https://docs.python.org/3/howto/descriptor.html +RedirectTemp /22-13 https://github.com/python/cpython/blob/e6d0107e13ed957109e79b796984d3d026a8660d/Lib/functools.py#L926 +RedirectTemp /22-14 https://docs.python.org/3/library/threading.html#rlock-objects +RedirectTemp /22-15 https://docs.python.org/3.10/library/functools.html#functools.cached_property +RedirectTemp /22-16 https://www.wsj.com/articles/SB10001424052970203914304576627102996831200 +RedirectTemp /22-17 https://www.youtube.com/watch?v=s35rVw1zskA&feature=youtu.be +RedirectTemp /22-18 https://docs.python.org/3/library/functions.html#dir +RedirectTemp /22-19 https://github.com/python/cpython/blob/19903085c3ad7a17c8047e1556c700f2eb109931/Lib/cmd.py#L214 +RedirectTemp /22-20 https://docs.python.org/3/library/functions.html#hasattr +RedirectTemp /22-21 https://docs.python.org/3.10/reference/datamodel.html#special-method-lookup +RedirectTemp /22-22 https://docs.python.org/3/library/functions.html +RedirectTemp /22-23 https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access +RedirectTemp /22-24 https://docs.python.org/3/reference/datamodel.html#special-method-lookup +RedirectTemp /22-25 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /22-26 http://wiki.c2.com/?WelcomeVisitors +RedirectTemp /22-27 http://wiki.c2.com/?UniformAccessPrinciple +RedirectTemp /22-28 https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html +RedirectTemp /22-29 http://www.pingo.io/docs/ +RedirectTemp /22-30 https://www.drdobbs.com/javas-new-considered-harmful/184405016 +RedirectTemp /22-31 https://www.python.org/dev/peps/pep-0008/#class-names +############################################################ 23 +RedirectTemp /23-1 http://www.aleax.it/goo_pydp.pdf +RedirectTemp /23-2 https://docs.python.org/3.10/reference/datamodel.html#implementing-descriptors +RedirectTemp /23-3 https://docs.python.org/3/howto/descriptor.html +RedirectTemp /23-4 https://docs.python.org/3/howto/ +RedirectTemp /23-5 http://www.aleax.it/Python/nylug05_om.pdf +RedirectTemp /23-6 https://www.youtube.com/watch?v=VOzvpHoYQoo +RedirectTemp /23-7 https://www.python.org/dev/peps/pep-0487/#trait-descriptors +RedirectTemp /23-8 https://dreamsongs.com/RiseOfWorseIsBetter.html +RedirectTemp /23-9 http://web.archive.org/web/20031002184114/www.amk.ca/python/writing/warts.html +RedirectTemp /23-10 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this +RedirectTemp /23-11 http://python-history.blogspot.com/2009/02/adding-support-for-user-defined-classes.html +############################################################ 24 +RedirectTemp /24-1 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /24-2 https://docs.djangoproject.com/en/3.2/topics/db/models/#meta-options +RedirectTemp /24-3 https://www.python.org/dev/peps/pep-3155/ +RedirectTemp /24-4 https://github.com/python/cpython/blob/3.9/Lib/collections/__init__.py +RedirectTemp /24-5 https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions +RedirectTemp /24-6 https://go.dev/tour/basics/12 +RedirectTemp /24-7 https://bugs.python.org/issue42102 +RedirectTemp /24-8 https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__ +RedirectTemp /24-9 https://www.python.org/dev/peps/pep-0557/#abstract +RedirectTemp /24-10 https://github.com/python/cpython/blob/3.9/Lib/dataclasses.py +RedirectTemp /24-11 https://docs.python.org/3/reference/datamodel.html#creating-the-class-object +RedirectTemp /24-12 https://mail.python.org/pipermail/python-list/2002-December/134521.html +RedirectTemp /24-13 https://mail.python.org/pipermail/python-list/2002-July/162558.html +RedirectTemp /24-14 https://github.com/fluentpython/example-code/tree/master/21-class-metaprog/bulkfood +RedirectTemp /24-15 https://en.wikipedia.org/wiki/Principle_of_least_astonishment +RedirectTemp /24-16 https://docs.python.org/3/reference/datamodel.html#object.__class_getitem__ +RedirectTemp /24-17 https://en.wikipedia.org/wiki/Trait_(computer_programming) +RedirectTemp /24-18 https://en.wikipedia.org/wiki/Aspect-oriented_programming +RedirectTemp /24-19 https://dhh.dk/arc/000416.html +RedirectTemp /24-20 https://github.com/cjrh/autoslot +RedirectTemp /24-21 https://docs.python.org/3/reference/datamodel.html#customizing-class-creation +RedirectTemp /24-22 https://docs.python.org/3/library/functions.html#type +RedirectTemp /24-23 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /24-24 https://docs.python.org/3/library/types.html +RedirectTemp /24-25 https://www.python.org/dev/peps/pep-3129/ +RedirectTemp /24-26 https://www.youtube.com/watch?v=cAGliEJV9_o +RedirectTemp /24-27 https://docs.python.org/3/library/functools.html#functools.total_ordering +RedirectTemp /24-28 https://www.python.org/download/releases/2.2.3/descrintro/ +RedirectTemp /24-29 https://github.com/lihaoyi/macropy +RedirectTemp /24-30 https://people.eecs.berkeley.edu/~bh/ss-toc2.html + +# chapters +RedirectTemp /1 https://pythonfluente.com/2/#ch_data_model +RedirectTemp /2 https://pythonfluente.com/2/#ch_sequences +RedirectTemp /3 https://pythonfluente.com/2/#ch_dicts_sets +RedirectTemp /4 https://pythonfluente.com/2/#ch_str_bytes +RedirectTemp /5 https://pythonfluente.com/2/#ch_dataclass +RedirectTemp /6 https://pythonfluente.com/2/#ch_refs_mut_mem +RedirectTemp /7 https://pythonfluente.com/2/#ch_func_objects +RedirectTemp /8 https://pythonfluente.com/2/#ch_type_hints_def +RedirectTemp /9 https://pythonfluente.com/2/#ch_closure_decorator +RedirectTemp /10 https://pythonfluente.com/2/#ch_design_patterns +RedirectTemp /11 https://pythonfluente.com/2/#ch_pythonic_obj +RedirectTemp /12 https://pythonfluente.com/2/#ch_seq_methods +RedirectTemp /13 https://pythonfluente.com/2/#ch_ifaces_prot_abc +RedirectTemp /14 https://pythonfluente.com/2/#ch_inheritance +RedirectTemp /15 https://pythonfluente.com/2/#ch_more_types +RedirectTemp /16 https://pythonfluente.com/2/#ch_op_overload +RedirectTemp /17 https://pythonfluente.com/2/#ch_generators +RedirectTemp /18 https://pythonfluente.com/2/#ch_with_match +RedirectTemp /19 https://pythonfluente.com/2/#ch_concurrency_models +RedirectTemp /20 https://pythonfluente.com/2/#ch_executors +RedirectTemp /21 https://pythonfluente.com/2/#ch_async +RedirectTemp /22 https://pythonfluente.com/2/#ch_dynamic_attrs +RedirectTemp /23 https://pythonfluente.com/2/#ch_descriptors +RedirectTemp /24 https://pythonfluente.com/2/#ch_class_metaprog + +# appended 2025-05-23 15:12:13 +# next 3 configs changed to accomodate short chapter links (above) +# RedirectTemp /22 https://pythonfluente.com/2/#pattern_matching_case_study_sec +# RedirectTemp /23 https://pythonfluente.com/2/#how_slicing_works +# RedirectTemp /24 https://pythonfluente.com/2/#sliceable_sequence +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_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 +RedirectTemp /2c https://pythonfluente.com/2/#positional_pattern_implement_sec + +# cap01: appended 2025-06-17 19:49:42 +RedirectTemp /2d https://docs.python.org/pt-br/3/reference/lexical_analysis.html#reserved-classes-of-identifiers +RedirectTemp /2e https://docs.python.org/pt-br/3/library/doctest.html +RedirectTemp /2f https://docs.python.org/pt-br/3.10/library/string.html#format-string-syntax +RedirectTemp /2g https://docs.python.org/pt-br/3/library/stdtypes.html#truth +RedirectTemp /2h https://docs.python.org/pt-br/3/tutorial/controlflow.html#unpacking-argument-lists +RedirectTemp /2j https://docs.python.org/pt-br/3/reference/datamodel.html +RedirectTemp /2k https://dabeaz.com/per.html +RedirectTemp /2m https://mitpress.mit.edu/books/art-metaobject-protocol +RedirectTemp /2n https://plone.org.br/ + +# cap02: appended 2025-06-18 21:30:08 +RedirectTemp /2p http://fluentpython.com +RedirectTemp /2q https://docs.python.org/pt-br/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2r https://docs.python.org/pt-br/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2s https://docs.python.org/pt-br/3.10/whatsnew/3.10.html +RedirectTemp /2t https://docs.python.org/pt-br/3/library/bisect.html#bisect.insort +RedirectTemp /2v https://docs.python.org/pt-br/3/howto/sorting.html +RedirectTemp /2w https://docs.python.org/pt-br/3/library/collections.html +RedirectTemp /2x https://docs.python.org/pt-br/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /2y https://pt.wikipedia.org/wiki/Timsort + +# cap03: appended 2025-06-23 16:24:59 +RedirectTemp /2z https://docs.python.org/pt-br/3.10/c-api/typeobj.html#Py_TPFLAGS_MAPPING +RedirectTemp /32 https://docs.python.org/pt-br/3/glossary.html#term-hashable +RedirectTemp /33 https://docs.python.org/pt-br/3/library/collections.html#collections.ChainMap +RedirectTemp /34 https://docs.python.org/pt-br/3/library/collections.html#collections.Counter +RedirectTemp /35 https://docs.python.org/pt-br/3/library/shelve.html +RedirectTemp /36 https://docs.python.org/pt-br/3/library/dbm.html +RedirectTemp /37 https://docs.python.org/pt-br/3/library/pickle.html +RedirectTemp /38 http://gopl.io + +# cap04: appended 2025-06-23 16:33:12 +RedirectTemp /39 https://docs.python.org/pt-br/3/library/codecs.html#codecs.register_error +RedirectTemp /3a https://docs.python.org/pt-br/3/library/codecs.html#encodings-and-unicode +RedirectTemp /3b https://docs.python.org/pt-br/3/using/cmdline.html#envvar-PYTHONIOENCODING +RedirectTemp /3c https://docs.python.org/pt-br/3/using/cmdline.html#envvar-PYTHONLEGACYWINDOWSSTDIO +RedirectTemp /3d https://docs.python.org/pt-br/3/library/locale.html#locale.getpreferredencoding +RedirectTemp /3e https://docs.python.org/pt-br/3/library/locale.html?highlight=strxfrm#locale.strxfrm +RedirectTemp /3f https://docs.python.org/pt-br/3.10/library/stdtypes.html#str.isalpha +RedirectTemp /3g https://docs.python.org/pt-br/3/library/unicodedata.html +RedirectTemp /3h https://docs.python.org/pt-br/3/reference/lexical_analysis.html#string-literal-concatenation +RedirectTemp /3j https://docs.python.org/pt-br/3/library/re.html +RedirectTemp /3k https://docs.python.org/pt-br/3/howto/unicode.html +RedirectTemp /3m https://docs.python.org/pt-br/3/library/codecs.html#standard-encodings + +# cap05: appended 2025-06-23 16:34:53 +RedirectTemp /3n https://docs.python.org/pt-br/3/library/typing.html#typing.TypedDict +RedirectTemp /3p https://docs.python.org/pt-br/3.10/library/inspect.html#inspect.get_annotations +RedirectTemp /3q https://docs.python.org/pt-br/3.8/library/collections.html#collections.somenamedtuple._asdict +RedirectTemp /3r https://docs.python.org/pt-br/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /3s https://docs.python.org/pt-br/3/library/dataclasses.html +RedirectTemp /3t https://docs.python.org/pt-br/3/library/dataclasses.html#inheritance +RedirectTemp /3v https://docs.python.org/pt-br/3/library/dataclasses.html#init-only-variables +RedirectTemp /3w https://pt.wikipedia.org/wiki/Dublin_Core +RedirectTemp /3z https://docs.python.org/pt-br/3/library/typing.html#typing.get_type_hints +RedirectTemp /42 https://martinfowler.com/books/refactoring.html + +# cap06: appended 2025-06-23 16:35:43 +RedirectTemp /3x https://docs.python.org/pt-br/3/reference/datamodel.html#object.%5C_%5C_del__ +RedirectTemp /3y https://docs.python.org/pt-br/3/library/gc.html +RedirectTemp /43 https://docs.python.org/pt-br/3/library/copy.html + +# cap07: appended 2025-08-13 20:10:26 +RedirectTemp /44 https://docs.python.org/pt-br/3/library/functions.html#map +RedirectTemp /45 https://pt.wikipedia.org/wiki/Programa%C3%A7%C3%A3o_funcional +RedirectTemp /46 https://docs.python.org/pt-br/3/howto/functional.html +RedirectTemp /47 https://docs.python.org/pt-br/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /48 https://docs.python.org/pt-br/3/whatsnew/3.8.html#positional-only-parameters + +# cap08: appended 2025-08-13 20:11:07 +RedirectTemp /49 https://pt.wikipedia.org/wiki/Barbara_Liskov +RedirectTemp /4a https://docs.python.org/pt-br/3/library/typing.html +RedirectTemp /4b https://docs.python.org/pt-br/3/library/typing.html#module-contents +RedirectTemp /4c https://docs.python.org/pt-br/3/library/typing.html#typing.List +RedirectTemp /4d https://docs.python.org/pt-br/3/library/numbers.html +RedirectTemp /4e https://docs.python.org/pt-br/3/library/statistics.html#statistics.mode +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/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 +RedirectTemp /4m https://github.com/pythonfluente/pythonfluente2e/issues +RedirectTemp /4n https://www.thoughtworks.com/pt-br +RedirectTemp /4p https://asciidoctor.org/ + +# appended 2025-08-31 22:14:23 +RedirectTemp /4q https://pythonfluente.com/2/#ch_ifaces_prot_abc +RedirectTemp /4r https://pythonfluente.com/2/#ch_op_overload +RedirectTemp /4s https://pythonfluente.com/2/#ch_generators +RedirectTemp /4t https://pythonfluente.com/2/#ch_seq_methods +RedirectTemp /4v https://pythonfluente.com/2/#classes_protocols_part +RedirectTemp /4w https://pythonfluente.com/2/#how_slicing_works_sec +RedirectTemp /4x https://pythonfluente.com/2/#sliceable_sequence_sec +RedirectTemp /4y https://pythonfluente.com/2/#lispy_environ_sec +RedirectTemp /4z https://pythonfluente.com/2/#subclass_builtin_woes_sec +RedirectTemp /52 https://pythonfluente.com/2/#slots_sec +RedirectTemp /53 https://pythonfluente.com/2/#ch_class_metaprog +RedirectTemp /54 https://pythonfluente.com/2/#ch_more_types +RedirectTemp /55 https://pythonfluente.com/2/#ch_descriptors +RedirectTemp /56 https://pythonfluente.com/2/#ch_inheritance +RedirectTemp /57 https://pythonfluente.com/2/#ch_async +RedirectTemp /58 https://pythonfluente.com/2/#runtime_annot_sec +RedirectTemp /59 https://pythonfluente.com/2/#multi_hashing_sec +RedirectTemp /5a https://pythonfluente.com/2/#iterable_reducing_sec +RedirectTemp /5b https://pythonfluente.com/2/#flexible_new_sec +RedirectTemp /5c https://pythonfluente.com/2/#ch_closure_decorator +RedirectTemp /5d https://pythonfluente.com/2/#ch_design_patterns +RedirectTemp /5e https://pythonfluente.com/2/#lispy_parser_sec +RedirectTemp /5f https://pythonfluente.com/2/#overload_sec +RedirectTemp /5g https://pythonfluente.com/2/#numbers_abc_proto_sec +RedirectTemp /5h https://pythonfluente.com/2/#runtime_checkable_proto_sec +RedirectTemp /5j https://pythonfluente.com/2/#variance_sec +RedirectTemp /5k https://pythonfluente.com/2/#generic_iterable_types_sec +RedirectTemp /5m https://pythonfluente.com/2/#typed_double_sec +RedirectTemp /5n https://pythonfluente.com/2/#enhancing_with_init_subclass_sec +RedirectTemp /5p https://pythonfluente.com/2/#more_type_hints_further_sec +RedirectTemp /5q https://pythonfluente.com/2/#max_overload_sec + +# appended 2025-09-15 19:46:00 +RedirectTemp /5r https://pythonfluente.com/2/#decode_error_sec +RedirectTemp /5s https://en.wikipedia.org/wiki/Noto_fonts +RedirectTemp /5t https://pythonfluente.com/2/#ex_stdout_check + +# appended 2025-09-17 22:49:00 +RedirectTemp /5v https://pythonfluente.com/2/#ex_shave_marks_demo +RedirectTemp /5w https://pythonfluente.com/2/#ex_asciize + +# cap09: appended 2025-09-28 14:32:48 +RedirectTemp /5x https://docs.python.org/pt-br/3/library/dis.html +RedirectTemp /5y https://docs.python.org/pt-br/3/library/functools.html#functools.singledispatch + +# cap10: appended 2025-09-28 18:08:20 +RedirectTemp /5z https://pt.wikipedia.org/wiki/Padr%C3%A3o_de_projeto_de_software +RedirectTemp /62 https://github.com/python/mypy/issues/12629 + +# cap10: appended 2025-09-28 18:29:35 +RedirectTemp /63 https://docs.python.org/pt-br/3/library/string.html#formatspec +RedirectTemp /64 https://docs.python.org/pt-br/3/reference/lexical_analysis.html#f-strings +RedirectTemp /65 https://docs.python.org/pt-br/3/library/string.html#format-string-syntax +RedirectTemp /66 https://docs.python.org/pt-br/3/reference/datamodel.html#object.__hash__ +RedirectTemp /67 https://docs.python.org/pt-br/3/tutorial/modules.html#more-on-modules +RedirectTemp /68 https://docs.python.org/pt-br/3/library/gettext.html#gettext.NullTranslations +RedirectTemp /69 https://docs.python.org/pt-br/3/reference/datamodel.html#basic-customization + +# changed to introduce short URLs for chapters +# was /22, now /6a +RedirectTemp /6a https://pythonfluente.com/2/#pattern_matching_case_study_sec + +# cap12: appended 2025-10-20 17:10:58 +RedirectTemp /6b https://pt.wikipedia.org/wiki/Modelo_vetorial_em_sistemas_de_recupera%C3%A7%C3%A3o_da_informa%C3%A7%C3%A3o +RedirectTemp /6c https://pt.wikipedia.org/wiki/Princ%C3%ADpio_da_substitui%C3%A7%C3%A3o_de_Liskov +RedirectTemp /6d https://docs.python.org/pt-br/3/library/functions.html#enumerate +RedirectTemp /6e https://docs.python.org/pt-br/3/reference/datamodel.html#special-method-names +RedirectTemp /6f https://pt.wikipedia.org/wiki/Princ%C3%ADpio_KISS +RedirectTemp /6g https://pt.wikipedia.org/wiki/Duck_typing +RedirectTemp /6h https://pt.wikipedia.org/wiki/APL_(linguagem_de_programa%C3%A7%C3%A3o) +RedirectTemp /6j https://pt.wikipedia.org/wiki/FP_(linguagem_de_programa%C3%A7%C3%A3o) + +# cap13: appended 2025-10-20 19:55:16 +RedirectTemp /6k https://docs.python.org/pt-br/3/c-api/sequence.html +RedirectTemp /6m https://docs.python.org/pt-br/3/library/random.html#random.shuffle +RedirectTemp /6n https://docs.python.org/pt-br/3/reference/datamodel.html#emulating-container-types +RedirectTemp /6p https://docs.python.org/pt-br/3/glossary.html#term-abstract-base-class +RedirectTemp /6q https://docs.python.org/pt-br/3/library/collections.abc.html#collections.abc.Iterable +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 + +# cap21: appended 2026-02-06 +RedirectTemp /c5 https://fastapi.tiangolo.com/pt/project-generation/ +RedirectTemp /c6 https://pypi.org/project/curio-compat/ + +# cap22: appended 2026-02-11 +RedirectTemp /c7 https://docs.python.org/pt-br/3.10/reference/datamodel.html#implementing-descriptors + +# vol3 xref links: appended 2026-02-25 +RedirectTemp /c8 https://pythonfluente.com/2/#closures_sec +RedirectTemp /c9 https://pythonfluente.com/2/#hashable_vector2d_sec +RedirectTemp /ca https://pythonfluente.com/2/#memoization_sec + +# vol3 xref links: appended 2026-02-27 18:32:42 +RedirectTemp /cb https://pythonfluente.com/2/#vector_take1_sec +RedirectTemp /cc https://pythonfluente.com/2/#python_digs_seq_sec +RedirectTemp /cd https://pythonfluente.com/2/#subclasshook_sec +RedirectTemp /ce https://pythonfluente.com/2/#cartesian_product_sec +RedirectTemp /cf https://pythonfluente.com/2/#tuples_more_than_lists_sec +RedirectTemp /cg https://pythonfluente.com/2/#contravariant_types_sec +RedirectTemp /ch https://pythonfluente.com/2/#covariant_types_sec +RedirectTemp /cj https://pythonfluente.com/2/#variance_rules_sec +RedirectTemp /ck https://pythonfluente.com/2/#pattern_matching_seq_interp_sec +RedirectTemp /cm https://pythonfluente.com/2/#nonlocal_sec +RedirectTemp /cn https://pythonfluente.com/2/#unicodedata_sec +RedirectTemp /cp https://pythonfluente.com/2/#vector_dynamic_attrs_sec +RedirectTemp /cq https://pythonfluente.com/2/#goose_typing_sec +RedirectTemp /cr https://pythonfluente.com/2/#private_protected_sec +RedirectTemp /cs https://pythonfluente.com/2/#overriding_class_attributes_sec +RedirectTemp /ct https://pythonfluente.com/2/#missing_method_sec +RedirectTemp /cv https://pythonfluente.com/2/#functools_partial_sec +RedirectTemp /cw https://pythonfluente.com/2/#mult_inherit_mro_sec + diff --git a/links/FPY.LI.htaccess.BACKUP b/links/FPY.LI.htaccess.BACKUP new file mode 100644 index 00000000..eb766966 --- /dev/null +++ b/links/FPY.LI.htaccess.BACKUP @@ -0,0 +1,1086 @@ +ErrorDocument 404 /404.html + +# main resources +RedirectTemp /book https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /code https://github.com/fluentpython/example-code-2e +RedirectTemp /home https://www.fluentpython.com/ + +# URLs mentioned at least three times +RedirectTemp /bisect https://www.fluentpython.com/extra/ordered-sequences-with-bisect/ +RedirectTemp /cardxvi https://www.python.org/dev/peps/pep-0484/#the-numeric-tower +RedirectTemp /collec https://docs.python.org/3/library/collections.html +RedirectTemp /dask https://dask.org/ +RedirectTemp /dtmodel https://docs.python.org/3/reference/datamodel.html +RedirectTemp /descr101 https://www.python.org/download/releases/2.2.3/descrintro/ +RedirectTemp /descrhow https://docs.python.org/3/howto/descriptor.html +RedirectTemp /doctest https://docs.python.org/3/library/doctest.html +RedirectTemp /effectpy https://effectivepython.com/ +RedirectTemp /fmtspec https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /gunicorn https://gunicorn.org/ +RedirectTemp /hashint https://www.fluentpython.com/extra/internals-of-sets-and-dicts/ +RedirectTemp /hattingh https://www.oreilly.com/library/view/using-asyncio-in/9781492075325/ +RedirectTemp /httpx https://www.python-httpx.org/ +RedirectTemp /initvar https://docs.python.org/3/library/dataclasses.html#init-only-variables +RedirectTemp /mypy https://mypy.readthedocs.io/en/stable/ +RedirectTemp /norvigdp http://norvig.com/design-patterns/ +RedirectTemp /nsphere https://en.wikipedia.org/wiki/N-sphere +RedirectTemp /oldcoro https://www.fluentpython.com/extra/classic-coroutines/ +RedirectTemp /pandas https://pandas.pydata.org/ +RedirectTemp /pycook3 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ +RedirectTemp /pynut3 https://www.oreilly.com/library/view/python-in-a/9781491913833/ +RedirectTemp /pypydif https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /shed4051 https://github.com/python/typeshed/issues/4051 +RedirectTemp /specattr https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine +RedirectTemp /typing https://docs.python.org/3/library/typing.html +RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ + +# URL added during QA of the Second Edition +RedirectTemp /bdfl https://www.artima.com/weblogs/viewpost.jsp?thread=235725 + +# Python Enhancement Proposals +RedirectTemp /pep218 https://www.python.org/dev/peps/pep-0218/ +RedirectTemp /pep227 https://www.python.org/dev/peps/pep-0227/ +RedirectTemp /pep255 https://www.python.org/dev/peps/pep-0255/ +RedirectTemp /pep342 https://www.python.org/dev/peps/pep-0342/ +RedirectTemp /pep343 https://www.python.org/dev/peps/pep-0343/ +RedirectTemp /pep357 https://www.python.org/dev/peps/pep-0357/ +RedirectTemp /pep362 https://www.python.org/dev/peps/pep-0362/ +RedirectTemp /pep371 https://www.python.org/dev/peps/pep-0371/ +RedirectTemp /pep380 https://www.python.org/dev/peps/pep-0380/ +RedirectTemp /pep393 https://www.python.org/dev/peps/pep-0393/ +RedirectTemp /pep412 https://www.python.org/dev/peps/pep-0412/ +RedirectTemp /pep442 https://www.python.org/dev/peps/pep-0442/ +RedirectTemp /pep443 https://www.python.org/dev/peps/pep-0443/ +RedirectTemp /pep448 https://www.python.org/dev/peps/pep-0448/ +RedirectTemp /pep455 https://www.python.org/dev/peps/pep-0455/ +RedirectTemp /pep456 https://www.python.org/dev/peps/pep-0456/ +RedirectTemp /pep461 https://www.python.org/dev/peps/pep-0461/ +RedirectTemp /pep465 https://www.python.org/dev/peps/pep-0465/ +RedirectTemp /pep467 https://www.python.org/dev/peps/pep-0467/ +RedirectTemp /pep482 https://www.python.org/dev/peps/pep-0482/ +RedirectTemp /pep483 https://www.python.org/dev/peps/pep-0483/ +RedirectTemp /pep484 https://www.python.org/dev/peps/pep-0484/ +RedirectTemp /pep487 https://www.python.org/dev/peps/pep-0487/ +RedirectTemp /pep492 https://www.python.org/dev/peps/pep-0492/ +RedirectTemp /pep519 https://www.python.org/dev/peps/pep-0519/ +RedirectTemp /pep525 https://www.python.org/dev/peps/pep-0525/ +RedirectTemp /pep526 https://www.python.org/dev/peps/pep-0526/ +RedirectTemp /pep528 https://www.python.org/dev/peps/pep-0528/ +RedirectTemp /pep529 https://www.python.org/dev/peps/pep-0529/ +RedirectTemp /pep530 https://www.python.org/dev/peps/pep-0530/ +RedirectTemp /pep544 https://www.python.org/dev/peps/pep-0544/ +RedirectTemp /pep554 https://www.python.org/dev/peps/pep-0554/ +RedirectTemp /pep557 https://www.python.org/dev/peps/pep-0557/ +RedirectTemp /pep560 https://www.python.org/dev/peps/pep-0560/ +RedirectTemp /pep561 https://www.python.org/dev/peps/pep-0561/ +RedirectTemp /pep563 https://www.python.org/dev/peps/pep-0563/ +RedirectTemp /pep570 https://www.python.org/dev/peps/pep-0570/ +RedirectTemp /pep572 https://www.python.org/dev/peps/pep-0572/ +RedirectTemp /pep584 https://www.python.org/dev/peps/pep-0584/ +RedirectTemp /pep585 https://www.python.org/dev/peps/pep-0585/ +RedirectTemp /pep586 https://www.python.org/dev/peps/pep-0586/ +RedirectTemp /pep589 https://www.python.org/dev/peps/pep-0589/ +RedirectTemp /pep591 https://www.python.org/dev/peps/pep-0591/ +RedirectTemp /pep593 https://www.python.org/dev/peps/pep-0593/ +RedirectTemp /pep604 https://www.python.org/dev/peps/pep-0604/ +RedirectTemp /pep612 https://www.python.org/dev/peps/pep-0612/ +RedirectTemp /pep613 https://www.python.org/dev/peps/pep-0613/ +RedirectTemp /pep616 https://www.python.org/dev/peps/pep-0616/ +RedirectTemp /pep617 https://www.python.org/dev/peps/pep-0617/ +RedirectTemp /pep618 https://www.python.org/dev/peps/pep-0618/ +RedirectTemp /pep634 https://www.python.org/dev/peps/pep-0634/ +RedirectTemp /pep635 https://www.python.org/dev/peps/pep-0635/ +RedirectTemp /pep636 https://www.python.org/dev/peps/pep-0636/ +RedirectTemp /pep638 https://www.python.org/dev/peps/pep-0638/ +RedirectTemp /pep645 https://www.python.org/dev/peps/pep-0645/ +RedirectTemp /pep646 https://www.python.org/dev/peps/pep-0646/ +RedirectTemp /pep647 https://www.python.org/dev/peps/pep-0647/ +RedirectTemp /pep649 https://www.python.org/dev/peps/pep-0649/ +RedirectTemp /pep654 https://www.python.org/dev/peps/pep-0654/ +RedirectTemp /pep655 https://www.python.org/dev/peps/pep-0655/ +RedirectTemp /pep661 https://www.python.org/dev/peps/pep-0661/ +RedirectTemp /pep3099 https://www.python.org/dev/peps/pep-3099/ +RedirectTemp /pep3102 https://www.python.org/dev/peps/pep-3102/ +RedirectTemp /pep3104 https://www.python.org/dev/peps/pep-3104/ +RedirectTemp /pep3106 https://www.python.org/dev/peps/pep-3106/ +RedirectTemp /pep3107 https://www.python.org/dev/peps/pep-3107/ +RedirectTemp /pep3115 https://www.python.org/dev/peps/pep-3115/ +RedirectTemp /pep3118 https://www.python.org/dev/peps/pep-3118/ +RedirectTemp /pep3119 https://www.python.org/dev/peps/pep-3119/ +RedirectTemp /pep3129 https://www.python.org/dev/peps/pep-3129/ +RedirectTemp /pep3132 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /pep3141 https://www.python.org/dev/peps/pep-3141/ +RedirectTemp /pep3148 https://www.python.org/dev/peps/pep-3148/ +RedirectTemp /pep3155 https://www.python.org/dev/peps/pep-3155/ +RedirectTemp /pep3333 https://www.python.org/dev/peps/pep-3333/ + +# Remaining URLs by chapter + +############################################################ p +RedirectTemp /p-1 https://mail.python.org/pipermail/python-list/2002-December/134521.html +RedirectTemp /p-2 https://docs.python.org/3.10/tutorial/ +RedirectTemp /p-3 https://docs.python.org/3/tutorial/ +RedirectTemp /p-4 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /p-5 https://www.oreilly.com/online-learning/try-now.html +RedirectTemp /p-6 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /p-7 https://stackoverflow.com/users/95810/alex-martelli +RedirectTemp /p-8 https://pythonpro.com.br +RedirectTemp /p-9 https://groups.google.com/g/python-brasil +RedirectTemp /p-10 https://www.coffeelab.com.br/ +RedirectTemp /p-11 https://garoa.net.br/wiki/P%C3%A1gina_principal +############################################################ a +RedirectTemp /a-1 https://groups.google.com/forum/#!topic/python-tulip/Y4bhLNbKs74 +RedirectTemp /a-2 https://docs.python.org/3/library/asyncio-eventloop.html#executor +RedirectTemp /a-3 https://www.youtube.com/watch?v=x-kB2o8sd5c +RedirectTemp /a-4 https://www.youtube.com/watch?v=OSGv2VnC0go +RedirectTemp /a-5 https://mail.python.org/pipermail/python-ideas/2015-March/032557.html +RedirectTemp /a-6 https://pypi.org/project/pep8/ +RedirectTemp /a-7 https://pypi.org/project/flake8/ +RedirectTemp /a-8 https://pypi.org/project/pyflakes/ +RedirectTemp /a-9 https://pypi.org/project/mccabe/ +RedirectTemp /a-10 https://google.github.io/styleguide/pyguide.html +RedirectTemp /a-11 https://flask.palletsprojects.com/en/1.1.x/styleguide/ +RedirectTemp /a-12 https://docs.python-guide.org/ +RedirectTemp /a-13 https://david.goodger.org/projects/pycon/2007/idiomatic/handout.html +RedirectTemp /a-14 https://docs.mongodb.com/manual/about/#about-the-documentation-process +RedirectTemp /a-15 https://blog.startifact.com/posts/older/what-is-pythonic.html +RedirectTemp /a-16 https://mail.python.org/pipermail/tutor/2003-October/thread.html#25930 +RedirectTemp /a-17 https://mail.python.org/pipermail/python-list/2003-April/192027.html +RedirectTemp /a-18 https://www.python.org/doc/essays/ +############################################################ 01 +RedirectTemp /1-1 http://hugunin.net/story_of_jython.html +RedirectTemp /1-2 https://www.oreilly.com/library/view/jython-essentials/9781449397364/ +RedirectTemp /1-3 https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers +RedirectTemp /1-4 https://docs.python.org/3.10/library/string.html#format-string-syntax +RedirectTemp /1-5 https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr +RedirectTemp /1-6 https://docs.python.org/3/library/stdtypes.html#truth +RedirectTemp /1-7 https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists +RedirectTemp /1-8 https://www.python.org/doc/humor/#the-zen-of-python +RedirectTemp /1-9 https://stackoverflow.com/users/95810/alex-martelli +RedirectTemp /1-10 https://en.wikipedia.org/wiki/Object_model +RedirectTemp /1-11 https://www.dourish.com/goodies/jargon.html +RedirectTemp /1-12 https://zopeinterface.readthedocs.io/en/latest/ +RedirectTemp /1-13 https://plone.org/ +############################################################ 02 +RedirectTemp /2-1 https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/listcomp_speed.py +RedirectTemp /2-2 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /2-3 https://stackoverflow.com/questions/68630/are-tuples-more-efficient-than-lists-in-python/22140115#22140115 +RedirectTemp /2-4 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2-5 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2-6 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2-7 https://docs.python.org/3.10/whatsnew/3.10.html +RedirectTemp /2-8 https://en.wikipedia.org/wiki/Switch_statement#Fallthrough +RedirectTemp /2-9 https://en.wikipedia.org/wiki/Dangling_else +RedirectTemp /2-10 https://github.com/gvanrossum/patma/blob/3ece6444ef70122876fd9f0099eb9490a2d630df/EXAMPLES.md#case-6-a-very-deep-iterable-and-type-match-with-extraction +RedirectTemp /2-11 https://github.com/fluentpython/lispy/blob/main/original/norvig/lis.py +RedirectTemp /2-12 https://norvig.com/lispy.html +RedirectTemp /2-13 https://numpy.org/doc/stable/user/quickstart.html#indexing-slicing-and-iterating +RedirectTemp /2-14 https://pythontutor.com/ +RedirectTemp /2-15 https://en.wikipedia.org/wiki/Fluent_interface +RedirectTemp /2-16 https://docs.python.org/3/library/bisect.html#bisect.insort +RedirectTemp /2-17 https://stackoverflow.com/questions/4845418/when-should-a-memoryview-be-used/ +RedirectTemp /2-18 https://www.fluentpython.com/extra/parsing-binary-struct/ +RedirectTemp /2-19 http://www.netlib.org +RedirectTemp /2-20 https://pandas.pydata.org/ +RedirectTemp /2-21 https://scikit-learn.org/stable/ +RedirectTemp /2-22 https://docs.python.org/3/howto/sorting.html +RedirectTemp /2-23 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /2-24 https://bugs.python.org/issue2292 +RedirectTemp /2-25 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2-26 https://docs.python.org/3.10/whatsnew/3.10.html +RedirectTemp /2-27 https://www.python.org/dev/peps/pep-0636/#appendix-a-quick-intro +RedirectTemp /2-28 https://eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol-and-memoryviews/ +RedirectTemp /2-29 https://jakevdp.github.io/PythonDataScienceHandbook/ +RedirectTemp /2-30 https://www.oreilly.com/library/view/python-for-data/9781491957653/ +RedirectTemp /2-31 https://www.labri.fr/perso/nrougier/from-python-to-numpy/ +RedirectTemp /2-32 https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html +RedirectTemp /2-33 http://www.fonts101.com/fonts/view/Uncategorized/34398/Dijkstra +RedirectTemp /2-34 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /2-35 https://en.wikipedia.org/wiki/Timsort +RedirectTemp /2-36 http://www.groklaw.net/pdf3/OraGoogle-1202.pdf +RedirectTemp /2-37 https://www.python.org/doc/humor/#id9 +############################################################ 03 +RedirectTemp /3-1 https://www.python.org/dev/peps/pep-0584/#motivation +RedirectTemp /3-2 https://docs.python.org/3.10/c-api/typeobj.html#Py_TPFLAGS_MAPPING +RedirectTemp /3-3 https://docs.python.org/3/glossary.html#term-hashable +RedirectTemp /3-4 https://docs.python.org/3/glossary.html#term-hashable +RedirectTemp /3-5 http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf +RedirectTemp /3-6 https://github.com/pingo-io/pingo-py +RedirectTemp /3-7 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/missing.py +RedirectTemp /3-8 https://docs.python.org/3/library/collections.html#collections.ChainMap +RedirectTemp /3-9 https://docs.python.org/3/library/collections.html#collections.Counter +RedirectTemp /3-10 https://docs.python.org/3/library/shelve.html +RedirectTemp /3-11 https://docs.python.org/3/library/dbm.html +RedirectTemp /3-12 https://docs.python.org/3/library/pickle.html +RedirectTemp /3-13 https://nedbatchelder.com/blog/202006/pickles_nine_flaws.html +RedirectTemp /3-14 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py#L813 +RedirectTemp /3-15 https://mail.python.org/pipermail/python-dev/2015-May/140003.html +RedirectTemp /3-16 https://bugs.python.org/issue18986 +RedirectTemp /3-17 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/transformdict.py +RedirectTemp /3-18 http://gandenberger.org/2018/03/10/ordered-dicts-vs-ordereddict/ +RedirectTemp /3-19 https://www.pypy.org/ +RedirectTemp /3-20 https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html +RedirectTemp /3-21 https://www.npopov.com/2014/12/22/PHPs-new-hashtable-implementation.html +RedirectTemp /3-22 https://www.youtube.com/watch?v=66P5FMkWoVU +RedirectTemp /3-23 https://pyvideo.org/video/276/the-mighty-dictionary-55/ +RedirectTemp /3-24 https://www.youtube.com/watch?v=p33CVV29OG8 +RedirectTemp /3-25 https://docs.python.org/3/whatsnew/3.6.html#new-dict-implementation +RedirectTemp /3-26 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictobject.c +RedirectTemp /3-27 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictnotes.txt +RedirectTemp /3-28 https://www.youtube.com/watch?v=tGAngdU_8D8 +RedirectTemp /3-29 https://speakerdeck.com/ramalho/python-set-practice-at-pycon +RedirectTemp /3-30 https://github.com/standupdev/uintset +RedirectTemp /3-31 https://spectrum.ieee.org/hans-peter-luhn-and-the-birth-of-the-hashing-algorithm +RedirectTemp /3-32 http://www.json.org/fatfree.html +RedirectTemp /3-33 https://twitter.com/mitsuhiko/status/1229385843585974272 +############################################################ 04 +RedirectTemp /4-1 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 +RedirectTemp /4-2 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ +RedirectTemp /4-3 https://www.fluentpython.com/extra/parsing-binary-struct/ +RedirectTemp /4-4 https://www.fluentpython.com/extra/multi-character-emojis/ +RedirectTemp /4-5 https://w3techs.com/technologies/overview/character_encoding +RedirectTemp /4-6 https://docs.python.org/3/library/codecs.html#codecs.register_error +RedirectTemp /4-7 https://docs.python.org/3/library/stdtypes.html#str.isascii +RedirectTemp /4-8 https://pypi.org/project/chardet/ +RedirectTemp /4-9 https://docs.python.org/3/library/codecs.html#encodings-and-unicode +RedirectTemp /4-10 https://nedbatchelder.com/text/unipain/unipain.html +RedirectTemp /4-11 https://devblogs.microsoft.com/commandline/windows-command-line-unicode-and-utf-8-output-text-buffer/ +RedirectTemp /4-12 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONIOENCODING +RedirectTemp /4-13 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONLEGACYWINDOWSSTDIO +RedirectTemp /4-14 https://docs.python.org/3/library/locale.html#locale.getpreferredencoding +RedirectTemp /4-15 http://www.w3.org/TR/charmod-norm/ +RedirectTemp /4-16 https://docs.python.org/3/library/locale.html?highlight=strxfrm#locale.strxfrm +RedirectTemp /4-17 https://pypi.org/project/pyuca/ +RedirectTemp /4-18 https://github.com/jtauber/pyuca +RedirectTemp /4-19 http://www.unicode.org/Public/UCA/6.3.0/allkeys.txt +RedirectTemp /4-20 https://pypi.org/project/PyICU/ +RedirectTemp /4-21 https://docs.python.org/3.10/library/stdtypes.html#str.isalpha +RedirectTemp /4-22 https://en.wikipedia.org/wiki/Unicode_character_property#General_Category +RedirectTemp /4-23 https://en.wikipedia.org/wiki/Unicode_character_property +RedirectTemp /4-24 https://github.com/microsoft/terminal +RedirectTemp /4-25 https://docs.python.org/3/library/unicodedata.html +RedirectTemp /4-26 https://docs.python.org/3/reference/lexical_analysis.html#string-literal-concatenation +RedirectTemp /4-27 https://docs.python.org/3/library/re.html +RedirectTemp /4-28 https://nedbatchelder.com/text/unipain.html +RedirectTemp /4-29 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 +RedirectTemp /4-30 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ +RedirectTemp /4-31 https://regebro.wordpress.com/2011/03/23/unconfusing-unicode-what-is-unicode/ +RedirectTemp /4-32 https://docs.python.org/3/howto/unicode.html +RedirectTemp /4-33 https://diveintopython3.net/strings.html +RedirectTemp /4-34 https://diveintopython3.net/ +RedirectTemp /4-35 https://finderiko.com/python-book +RedirectTemp /4-36 https://docs.python.org/3.0/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit +RedirectTemp /4-37 https://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/ +RedirectTemp /4-38 http://python-notes.curiousefficiency.org/en/latest/python3/binary_protocols.html +RedirectTemp /4-39 http://python-notes.curiousefficiency.org/en/latest/python3/text_file_processing.html +RedirectTemp /4-40 https://docs.python.org/3/library/codecs.html#standard-encodings +RedirectTemp /4-41 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Tools/unicode/listcodecs.py +RedirectTemp /4-42 https://www.oreilly.com/library/view/unicode-explained/059610121X/ +RedirectTemp /4-43 https://www.informit.com/store/unicode-demystified-a-practical-programmers-guide-to-9780201700527 +RedirectTemp /4-44 https://unicodebook.readthedocs.io/index.html +RedirectTemp /4-45 https://www.w3.org/International/wiki/Case_folding +RedirectTemp /4-46 http://www.w3.org/TR/charmod-norm/ +RedirectTemp /4-47 http://unicode.org/reports/tr15/ +RedirectTemp /4-48 http://www.unicode.org/faq/normalization.html +RedirectTemp /4-49 http://www.unicode.org/ +RedirectTemp /4-50 http://www.macchiato.com/unicode/nfc-faq +RedirectTemp /4-51 https://stories.moma.org/the-original-emoji-set-has-been-added-to-the-museum-of-modern-arts-collection-c6060e141f61?gi=c403f5a840a5 +RedirectTemp /4-52 https://emojipedia.org/ +RedirectTemp /4-53 https://blog.emojipedia.org/correcting-the-record-on-the-first-emoji-set/ +RedirectTemp /4-54 http://emojitracker.com/ +RedirectTemp /4-55 http://www.unicode.org/glossary/#plain_text +RedirectTemp /4-56 http://www.methods.co.nz/asciidoc/ +RedirectTemp /4-57 https://atlas.oreilly.com/ +############################################################ 05 +RedirectTemp /5-1 https://docs.python.org/3/library/typing.html#typing.TypedDict +RedirectTemp /5-2 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations +RedirectTemp /5-3 https://docs.python.org/3/library/typing.html#typing.get_type_hints +RedirectTemp /5-4 https://docs.python.org/3.8/library/collections.html#collections.somenamedtuple._asdict +RedirectTemp /5-5 https://www.jetbrains.com/pycharm/ +RedirectTemp /5-6 https://www.python.org/dev/peps/pep-0484/#acceptable-type-hints +RedirectTemp /5-7 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /5-8 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /5-9 https://docs.python.org/3/library/dataclasses.html +RedirectTemp /5-10 https://docs.python.org/3/library/dataclasses.html#inheritance +RedirectTemp /5-11 https://www.python.org/dev/peps/pep-0526/#class-and-instance-variable-annotations +RedirectTemp /5-12 https://dublincore.org/specifications/dublin-core/ +RedirectTemp /5-13 https://en.wikipedia.org/wiki/Dublin_Core +RedirectTemp /5-14 https://martinfowler.com/bliki/CodeSmell.html +RedirectTemp /5-15 https://martinfowler.com/books/refactoring.html +RedirectTemp /5-16 https://www.python.org/dev/peps/pep-0634/#class-patterns +RedirectTemp /5-17 https://docs.python.org/3/library/dataclasses.html +RedirectTemp /5-18 https://www.python.org/dev/peps/pep-0557/#id47 +RedirectTemp /5-19 https://www.python.org/dev/peps/pep-0557/#id48 +RedirectTemp /5-20 https://www.python.org/dev/peps/pep-0557/#id33 +RedirectTemp /5-21 https://realpython.com +RedirectTemp /5-22 https://realpython.com/python-data-classes/ +RedirectTemp /5-23 https://www.youtube.com/watch?v=T-TwcmT6Rcw +RedirectTemp /5-24 https://www.attrs.org/en/stable/ +RedirectTemp /5-25 https://glyph.twistedmatrix.com/2016/08/attrs.html +RedirectTemp /5-26 https://www.attrs.org/en/stable/why.html +RedirectTemp /5-27 https://github.com/dabeaz/cluegen +RedirectTemp /5-28 https://refactoring.guru/ +RedirectTemp /5-29 https://refactoring.guru/smells/data-class +RedirectTemp /5-30 https://web.archive.org/web/20190204130328/http://catb.org/esr/jargon/html/G/Guido.html +RedirectTemp /5-31 https://web.archive.org/web/20190211161610/http://catb.org/esr/jargon/html/index.html +RedirectTemp /5-32 https://www.attrs.org/en/stable/ +############################################################ 06 +RedirectTemp /6-1 https://www.olin.edu/faculty/profile/lynn-andrea-stein/ +RedirectTemp /6-2 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /6-3 https://pythontutor.com/ +RedirectTemp /6-4 https://docs.python.org/3/library/copy.html +RedirectTemp /6-5 https://en.wikipedia.org/wiki/Principle_of_least_astonishment +RedirectTemp /6-6 https://docs.python.org/3/reference/datamodel.html#object.%5C_%5C_del__ +RedirectTemp /6-7 https://emptysqua.re/blog/pypy-garbage-collection-and-a-deadlock/ +RedirectTemp /6-8 https://www.youtube.com/watch?v=HHFCFJSPWrI&feature=youtu.be +RedirectTemp /6-9 http://pymotw.com/3/copy/ +RedirectTemp /6-10 http://pymotw.com/3/weakref/ +RedirectTemp /6-11 https://docs.python.org/3/library/gc.html +RedirectTemp /6-12 https://devguide.python.org/garbage_collector/ +RedirectTemp /6-13 https://devguide.python.org/ +RedirectTemp /6-14 https://www.python.org/dev/peps/pep-0442/ +RedirectTemp /6-15 https://en.wikipedia.org/wiki/String_interning +RedirectTemp /6-16 https://en.wikipedia.org/wiki/Haddocks%27_Eyes +RedirectTemp /6-17 https://thp.io/2012/python-gc/python_gc_final_2012-01-22.pdf +############################################################ 07 +RedirectTemp /7-1 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html +RedirectTemp /7-2 https://www.fluentpython.com/extra/function-introspection/ +RedirectTemp /7-3 https://docs.python.org/3/library/functions.html#map +RedirectTemp /7-4 https://en.wikipedia.org/wiki/Functional_programming +RedirectTemp /7-5 https://docs.python.org/3/howto/functional.html +RedirectTemp /7-6 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /7-7 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters +RedirectTemp /7-8 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters +RedirectTemp /7-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/functools.py#L341 +RedirectTemp /7-10 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /7-11 https://docs.python.org/3/howto/functional.html +RedirectTemp /7-12 https://stackoverflow.com/questions/3252228/python-why-is-functools-partial-necessary +RedirectTemp /7-13 https://speakerdeck.com/ramalho/beyond-paradigms-berlin-edition +RedirectTemp /7-14 https://www.youtube.com/watch?v=bF3a2VYXxa0 +RedirectTemp /7-15 http://cs.brown.edu/~sk/Publications/Papers/Published/sk-teach-pl-post-linnaean/ +RedirectTemp /7-16 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html +RedirectTemp /7-17 https://raw.githubusercontent.com/python/cpython/main/Misc/HISTORY +RedirectTemp /7-18 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html +############################################################ 08 +RedirectTemp /8-1 https://www.python.org/dev/peps/pep-0484/#non-goals +RedirectTemp /8-2 https://github.com/python/typing/issues/182 +RedirectTemp /8-3 https://github.com/python/mypy/issues/731 +RedirectTemp /8-4 https://github.com/google/pytype +RedirectTemp /8-5 https://github.com/Microsoft/pyright +RedirectTemp /8-6 https://pyre-check.org/ +RedirectTemp /8-7 https://mypy.readthedocs.io/en/stable/introduction.html +RedirectTemp /8-8 https://mypy.readthedocs.io/en/stable/config_file.html +RedirectTemp /8-9 https://pypi.org/project/flake8/ +RedirectTemp /8-10 https://pypi.org/project/blue/ +RedirectTemp /8-11 https://pypi.org/project/black/ +RedirectTemp /8-12 https://wefearchange.org/2020/11/steeringcouncil.rst.html +RedirectTemp /8-13 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes +RedirectTemp /8-14 https://en.wikipedia.org/wiki/Barbara_Liskov +RedirectTemp /8-15 https://en.wikipedia.org/wiki/Behavioral_subtyping +RedirectTemp /8-16 https://www.python.org/dev/peps/pep-0585/#implementation +RedirectTemp /8-17 https://docs.python.org/3/library/typing.html#module-contents +RedirectTemp /8-18 https://en.wikipedia.org/wiki/Geohash +RedirectTemp /8-19 https://en.wikipedia.org/wiki/Inverted_index +RedirectTemp /8-20 https://docs.python.org/3/library/typing.html#typing.List +RedirectTemp /8-21 https://docs.python.org/3/library/typing.html#typing.Dict +RedirectTemp /8-22 https://docs.python.org/3/library/typing.html#typing.Set +RedirectTemp /8-23 https://www.python.org/dev/peps/pep-0585/#implementation +RedirectTemp /8-24 https://docs.python.org/3/library/numbers.html +RedirectTemp /8-25 https://docs.python.org/3/library/typing.html#typing.List +RedirectTemp /8-26 https://github.com/python/typeshed +RedirectTemp /8-27 https://github.com/python/typeshed/blob/66cd36268a6a667714efaa27198a41d0d7f89477/stdlib/2and3/math.pyi#L45 +RedirectTemp /8-28 https://docs.python.org/3/library/statistics.html#statistics.mode +RedirectTemp /8-29 https://github.com/python/cpython/blob/822efa5695b5ba6c2316c1400e4e9ec2546f7ea5/Lib/statistics.py#L534 +RedirectTemp /8-30 https://github.com/python/typeshed/blob/e1e99245bb46223928eba68d4fc74962240ba5b4/stdlib/3/statistics.pyi +RedirectTemp /8-31 https://docs.python.org/3/library/statistics.html#statistics.mode +RedirectTemp /8-32 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/3/statistics.pyi#L32 +RedirectTemp /8-33 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ +RedirectTemp /8-34 https://docs.python.org/3/library/typing.html#typing.Callable +RedirectTemp /8-35 https://pypi.org/project/blue/ +RedirectTemp /8-36 https://www.python.org/dev/peps/pep-0484/#id38 +RedirectTemp /8-37 https://docs.google.com/document/d/1aXs1tpwzPjW9MdsG5dI7clNFyYayFBkcXwRDo-qvbIk/preview +RedirectTemp /8-38 https://www.oreilly.com/library/view/the-best-software/9781590595008/ +RedirectTemp /8-39 https://www.youtube.com/watch?v=YFexUDjHO6w +RedirectTemp /8-40 https://www.youtube.com/watch?v=YFexUDjHO6w&t=13m40s +RedirectTemp /8-41 https://bernat.tech/posts/the-state-of-type-hints-in-python/ +RedirectTemp /8-42 https://realpython.com/python-type-checking/ +RedirectTemp /8-43 https://cjolowicz.github.io/posts/hypermodern-python-04-typing/ +RedirectTemp /8-44 https://mypy.readthedocs.io/en/stable/index.html +RedirectTemp /8-45 https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html +RedirectTemp /8-46 https://mypy.readthedocs.io/en/stable/common_issues.html +RedirectTemp /8-47 https://github.com/typeddjango/awesome-python-typing +RedirectTemp /8-48 https://docs.python.org/3/library/functions.html#max +RedirectTemp /8-49 https://en.wikipedia.org/wiki/Linguistic_relativity +RedirectTemp /8-50 https://pypistats.org/top +RedirectTemp /8-51 https://github.com/psf/requests/issues/3855 +RedirectTemp /8-52 https://lwn.net/Articles/643399/ +RedirectTemp /8-53 https://docs.python-requests.org/en/master/api/#requests.request +RedirectTemp /8-54 https://queue.acm.org/detail.cfm?id=1039523 +############################################################ 09 +RedirectTemp /9-1 https://docs.python.org/3/library/dis.html +RedirectTemp /9-2 https://en.wikipedia.org/wiki/Memoization +RedirectTemp /9-3 https://numpy.org/doc/stable/user/basics.types.html +RedirectTemp /9-4 https://docs.python.org/3/library/functools.html#functools.singledispatch +RedirectTemp /9-5 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/README.md +RedirectTemp /9-6 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/01-how-you-implemented-your-python-decorator-is-wrong.md +RedirectTemp /9-7 https://wrapt.readthedocs.io/en/latest/ +RedirectTemp /9-8 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch09.html +RedirectTemp /9-9 https://pypi.org/project/decorator/ +RedirectTemp /9-10 https://wiki.python.org/moin/PythonDecoratorLibrary +RedirectTemp /9-11 http://web.archive.org/web/20201109032203/http://effbot.org/zone/closure.htm +RedirectTemp /9-12 https://www.python.org/dev/peps/pep-3104/ +RedirectTemp /9-13 https://www.python.org/dev/peps/pep-0227/ +RedirectTemp /9-14 https://www.python.org/dev/peps/pep-0443/ +RedirectTemp /9-15 https://www.artima.com/weblogs/viewpost.jsp?thread=101605 +RedirectTemp /9-16 https://reg.readthedocs.io/en/latest/ +RedirectTemp /9-17 https://morepath.readthedocs.io/en/latest/ +RedirectTemp /9-18 https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Binding.html +RedirectTemp /9-19 http://www.paulgraham.com/rootsoflisp.html +RedirectTemp /9-20 http://www-formal.stanford.edu/jmc/recursive/recursive.html +RedirectTemp /9-21 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this +############################################################ 10 +RedirectTemp /10-1 https://en.wikipedia.org/wiki/Software_design_pattern +RedirectTemp /10-2 https://en.wikipedia.org/wiki/Iterator_pattern +RedirectTemp /10-3 https://github.com/python/mypy/issues/9397 +RedirectTemp /10-4 http://www.norvig.com/design-patterns/index.htm +RedirectTemp /10-5 https://pyvideo.org/video/1110/python-design-patterns/ +RedirectTemp /10-6 http://www.aleax.it/gdd_pydp.pdf +RedirectTemp /10-7 https://perl.plover.com/yak/design/ +RedirectTemp /10-8 https://en.wikipedia.org/wiki/Turtles_all_the_way_down +############################################################ 11 +RedirectTemp /11-1 https://blog.startifact.com/posts/older/what-is-pythonic.html +RedirectTemp /11-2 https://julien.danjou.info/guide-python-static-class-abstract-methods/ +RedirectTemp /11-3 https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /11-4 https://docs.python.org/3/reference/lexical_analysis.html#f-strings +RedirectTemp /11-5 https://docs.python.org/3/library/string.html#format-string-syntax +RedirectTemp /11-6 https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /11-7 https://docs.python.org/3/reference/datamodel.html#object.__hash__ +RedirectTemp /11-8 https://web.archive.org/web/20161025185040/http://pythonpaste.org/StyleGuide.html +RedirectTemp /11-9 https://docs.python.org/3/tutorial/modules.html#more-on-modules +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-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 +RedirectTemp /11-17 https://docs.oracle.com/javase/tutorial/essential/environment/security.html +############################################################ 12 +RedirectTemp /12-1 https://en.wikipedia.org/wiki/Vector_space_model +RedirectTemp /12-2 https://pypi.org/project/gensim/ +RedirectTemp /12-3 https://docs.python.org/3/library/functions.html#enumerate +RedirectTemp /12-4 https://mathworld.wolfram.com/Hypersphere.html +RedirectTemp /12-5 https://en.wikipedia.org/wiki/Fold_(higher-order_function) +RedirectTemp /12-6 https://docs.python.org/2.5/whatsnew/pep-357.html +RedirectTemp /12-7 https://docs.python.org/3/reference/datamodel.html#special-method-names +RedirectTemp /12-8 https://en.wikipedia.org/wiki/KISS_principle +RedirectTemp /12-9 https://mail.python.org/pipermail/python-list/2000-July/046184.html +RedirectTemp /12-10 https://en.wikipedia.org/wiki/Duck_typing +RedirectTemp /12-11 https://mail.python.org/mailman/listinfo/python-list +RedirectTemp /12-12 https://mail.python.org/pipermail/python-list/2003-April/218568.html +############################################################ 13 +RedirectTemp /13-1 https://docs.python.org/3/c-api/index.html +RedirectTemp /13-2 https://docs.python.org/3/c-api/sequence.html +RedirectTemp /13-3 https://github.com/python/cpython/blob/31ceccb2c77854893f3a754aca04bedd74bedb10/Lib/_collections_abc.py#L870 +RedirectTemp /13-4 https://en.wikipedia.org/wiki/Monkey_patch +RedirectTemp /13-5 https://www.gevent.org/api/gevent.monkey.html +RedirectTemp /13-6 https://docs.python.org/3/library/random.html#random.shuffle +RedirectTemp /13-7 https://docs.python.org/3/reference/datamodel.html#emulating-container-types +RedirectTemp /13-8 https://docs.python.org/3/library/collections.html#collections.namedtuple +RedirectTemp /13-9 https://github.com/python/typeshed/blob/24afb531ffd07083d6a74be917342195062f7277/stdlib/collections/__init__.pyi +RedirectTemp /13-10 https://docs.python.org/3/glossary.html#term-abstract-base-class +RedirectTemp /13-11 https://en.wikipedia.org/wiki/Duck_typing#History +RedirectTemp /13-12 http://ptgmedia.pearsoncmg.com/images/020163371x/items/item33.html +RedirectTemp /13-13 https://docs.python.org/3/library/bisect.html#bisect.bisect +RedirectTemp /13-14 https://github.com/python/cpython/blob/main/Lib/_collections_abc.py +RedirectTemp /13-15 https://github.com/python/cpython/blob/main/Lib/abc.py +RedirectTemp /13-16 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes +RedirectTemp /13-17 https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable +RedirectTemp /13-18 https://docs.python.org/3/library/abc.html +RedirectTemp /13-19 https://docs.python.org/dev/library/abc.html#abc.abstractmethod +RedirectTemp /13-20 https://docs.python.org/dev/library/abc.html +RedirectTemp /13-21 https://docs.python.org/3/library/os.html#os.urandom +RedirectTemp /13-22 https://github.com/python/mypy/issues/2922 +RedirectTemp /13-23 https://docs.python.org/3/library/stdtypes.html#truth +RedirectTemp /13-24 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py +RedirectTemp /13-25 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L369 +RedirectTemp /13-26 https://github.com/python/cpython/blob/c0a9afe2ac1820409e6173bd1893ebee2cf50270/Lib/abc.py#L196 +RedirectTemp /13-27 https://bugs.python.org/issue31333 +RedirectTemp /13-28 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Modules/_abc.c#L605 +RedirectTemp /13-29 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L881 +RedirectTemp /13-30 https://docs.python.org/3/library/typing.html#protocols +RedirectTemp /13-31 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Lib/typing.py#L1751 +RedirectTemp /13-32 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ +RedirectTemp /13-33 https://martinfowler.com/bliki/RoleInterface.html +RedirectTemp /13-34 https://en.wikipedia.org/wiki/Interface_segregation_principle +RedirectTemp /13-35 https://github.com/python/typeshed/blob/master/CONTRIBUTING.md +RedirectTemp /13-36 https://gist.github.com/asukakenji/ac8a05644a2e98f1d5ea8c299541fce9 +RedirectTemp /13-37 https://www.python.org/dev/peps/pep-0544/#runtime-checkable-decorator-and-narrowing-types-by-isinstance +RedirectTemp /13-38 https://www.python.org/dev/peps/pep-0544/#merging-and-extending-protocols +RedirectTemp /13-39 https://numpy.org/devdocs/user/basics.types.html +RedirectTemp /13-40 https://github.com/python/typeshed/blob/master/stdlib/statistics.pyi +RedirectTemp /13-41 https://bugs.python.org/issue41974 +RedirectTemp /13-42 https://glyph.twistedmatrix.com/2020/07/new-duck.html +RedirectTemp /13-43 https://glyph.twistedmatrix.com/2021/03/interfaces-and-protocols.html +RedirectTemp /13-44 https://plone.org/ +RedirectTemp /13-45 https://trypyramid.com/ +RedirectTemp /13-46 https://twistedmatrix.com/trac/ +RedirectTemp /13-47 https://www.artima.com/articles/contracts-in-python +RedirectTemp /13-48 https://martinfowler.com/bliki/DynamicTyping.html +RedirectTemp /13-49 https://martinfowler.com/bliki/RoleInterface.html +RedirectTemp /13-50 https://mypy.readthedocs.io/en/stable/protocols.html +RedirectTemp /13-51 https://pymotw.com/3/abc/index.html +RedirectTemp /13-52 https://www.python.org/dev/peps/pep-3119/ +RedirectTemp /13-53 https://www.python.org/dev/peps/pep-3141/ +RedirectTemp /13-54 https://docs.python.org/3/library/numbers.html +RedirectTemp /13-55 https://github.com/python/mypy/issues/3186 +RedirectTemp /13-56 https://stackoverflow.com/questions/69334475/how-to-hint-at-number-types-i-e-subclasses-of-number-not-numbers-themselv/69383462#69383462 +RedirectTemp /13-57 https://github.com/python/mypy/issues/3186 +RedirectTemp /13-58 https://martinfowler.com/articles/lean-inception/ +RedirectTemp /13-59 https://martinfowler.com +RedirectTemp /13-60 https://www.jetbrains.com/pycharm/ +RedirectTemp /13-61 https://wingware.com/ +RedirectTemp /13-62 https://code.visualstudio.com/ +############################################################ 14 +RedirectTemp /14-1 http://worrydream.com/EarlyHistoryOfSmalltalk/ +RedirectTemp /14-2 https://docs.python.org/3/tutorial/classes.html +RedirectTemp /14-3 https://docs.python.org/3/library/collections.html#ordereddict-examples-and-recipes +RedirectTemp /14-4 https://discuss.python.org/t/is-it-time-to-deprecate-unbound-super-methods/1833 +RedirectTemp /14-5 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /14-6 https://docs.python.org/3/library/collections.html +RedirectTemp /14-7 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/strkeydict_dictsub.py +RedirectTemp /14-8 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /14-9 https://en.wikipedia.org/wiki/Breadth-first_search +RedirectTemp /14-10 https://www.python.org/download/releases/2.3/mro/ +RedirectTemp /14-11 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/uppermixin.py +RedirectTemp /14-12 https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html +RedirectTemp /14-13 https://docs.python.org/3/library/collections.abc.html +RedirectTemp /14-14 https://github.com/python/cpython/blob/8ece98a7e418c3c68a4c61bc47a2d0931b59a889/Lib/collections/__init__.py#L1084 +RedirectTemp /14-15 https://docs.python.org/3/library/http.server.html +RedirectTemp /14-16 https://github.com/python/cpython/blob/17c23167942498296f0bdfffe52e72d53d66d693/Lib/http/server.py#L144 +RedirectTemp /14-17 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L664 +RedirectTemp /14-18 https://docs.python.org/3/library/socketserver.html#socketserver.ForkingMixIn +RedirectTemp /14-19 https://docs.python.org/3/library/os.html#os.fork +RedirectTemp /14-20 https://en.wikipedia.org/wiki/POSIX +RedirectTemp /14-21 http://ccbv.co.uk/ +RedirectTemp /14-22 https://github.com/django/django/tree/main/django/views/generic +RedirectTemp /14-23 https://en.wikipedia.org/wiki/Template_method_pattern +RedirectTemp /14-24 https://docs.python.org/3/library/tkinter.html +RedirectTemp /14-25 https://docs.python.org/3/library/tkinter.ttk.html +RedirectTemp /14-26 https://docs.oracle.com/javase/10/docs/api/java/awt/package-tree.html +RedirectTemp /14-27 https://docs.oracle.com/javase/10/docs/api/javax/swing/package-tree.html +RedirectTemp /14-28 https://squeak.org/ +RedirectTemp /14-29 https://github.com/django/django/blob/b64db05b9cedd96905d637a2d824cbbf428e40e7/django/views/generic/list.py#L194 +RedirectTemp /14-30 https://github.com/python/cpython/blob/8ed183391241f0c73e7ba7f42b1d49fc02985f7b/Lib/tkinter/__init__.py#L2618 +RedirectTemp /14-31 https://docs.python.org/3/library/socketserver.html +RedirectTemp /14-32 https://docs.python.org/3/library/socketserver.html#socketserver.BaseServer +RedirectTemp /14-33 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L153 +RedirectTemp /14-34 https://docs.python.org/3/library/typing.html#typing.final +RedirectTemp /14-35 https://docs.python.org/3/library/typing.html#typing.Final +RedirectTemp /14-36 https://docs.python.org/3/library/collections.abc.html +RedirectTemp /14-37 https://hynek.me/articles/python-subclassing-redux/ +RedirectTemp /14-38 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch08.html#super +RedirectTemp /14-39 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ +RedirectTemp /14-40 https://fuhm.net/super-harmful/ +RedirectTemp /14-41 https://stackoverflow.com/questions/30190185/how-to-use-super-with-one-argument/30190341#30190341 +RedirectTemp /14-42 https://www.artima.com/weblogs/viewpost.jsp?thread=246488 +RedirectTemp /14-43 https://www.artima.com/weblogs/viewpost.jsp?thread=281127 +RedirectTemp /14-44 https://www.artima.com/weblogs/viewpost.jsp?thread=246341 +RedirectTemp /14-45 https://www.artima.com/weblogs/viewpost.jsp?thread=246483 +RedirectTemp /14-46 https://www.artima.com/weblogs/viewpost.jsp?thread=236275 +RedirectTemp /14-47 https://www.artima.com/weblogs/viewpost.jsp?thread=236278 +RedirectTemp /14-48 https://www.artima.com/weblogs/viewpost.jsp?thread=237121 +RedirectTemp /14-49 https://python-patterns.guide/gang-of-four/composition-over-inheritance/ +RedirectTemp /14-50 https://python-patterns.guide/ +RedirectTemp /14-51 https://www.youtube.com/watch?v=3MNVP9-hglc +RedirectTemp /14-52 http://worrydream.com/EarlyHistoryOfSmalltalk/ +RedirectTemp /14-53 https://en.wikipedia.org/wiki/Polymorphism_(computer_science) +############################################################ 15 +RedirectTemp /15-1 https://www.youtube.com/watch?v=csL8DLXGNlU&t=92m5s +RedirectTemp /15-2 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi#L1434 +RedirectTemp /15-3 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi +RedirectTemp /15-4 https://twitter.com/gwidion/status/1265384692464967680 +RedirectTemp /15-5 https://pypi.org/project/pydantic/ +RedirectTemp /15-6 https://google.github.io/pytype/faq.html +RedirectTemp /15-7 https://google.github.io/pytype/faq.html +RedirectTemp /15-8 https://lxml.de/ +RedirectTemp /15-9 https://docs.python.org/3/library/xml.etree.elementtree.html +RedirectTemp /15-10 https://mypy.readthedocs.io/en/stable/common_issues.html +RedirectTemp /15-11 https://mypy.readthedocs.io/en/stable/common_issues.html#types-of-empty-collections +RedirectTemp /15-12 https://github.com/python/typing/issues/182 +RedirectTemp /15-13 https://pypi.org/project/pydantic/ +RedirectTemp /15-14 https://mypy.readthedocs.io/en/stable/type_narrowing.html#casts +RedirectTemp /15-15 https://github.com/python/cpython/blob/bee66d3cb98e740f9d8057eb7f503122052ca5d8/Lib/typing.py#L1340 +RedirectTemp /15-16 https://www.python.org/dev/peps/pep-0484/#casts +RedirectTemp /15-17 https://github.com/python/typeshed/issues/5535 +RedirectTemp /15-18 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /15-19 https://github.com/python/cpython/blob/b798ab06937f8bb24b444a49dd42e11fff15e654/Lib/test/test_asyncio/test_server.py#L55 +RedirectTemp /15-20 https://en.wikipedia.org/wiki/Code_smell +RedirectTemp /15-21 https://mail.python.org/archives/list/typing-sig@python.org/message/5LCWMN2UY2UQNLC5Z47GHBZKSPZW4I63/ +RedirectTemp /15-22 https://mypy.readthedocs.io/en/stable/error_codes.html#error-codes +RedirectTemp /15-23 https://github.com/fluentpython/example-code-2e/blob/master/15-more-types/clip_annot.py +RedirectTemp /15-24 https://docs.python.org/3/library/typing.html#introspection-helpers +RedirectTemp /15-25 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations +RedirectTemp /15-26 https://www.python.org/dev/peps/pep-0563/#abstract +RedirectTemp /15-27 https://mail.python.org/archives/list/python-dev@python.org/message/ZBJ7MD6CSGM6LZAOTET7GXAVBZB7O77O/ +RedirectTemp /15-28 https://docs.python.org/3.10/howto/annotations.html +RedirectTemp /15-29 https://docs.python.org/3/library/typing.html#user-defined-generic-types +RedirectTemp /15-30 https://github.com/python/typeshed/blob/bfc83c365a0b26ab16586beac77ff16729d0e473/stdlib/builtins.pyi#L743 +RedirectTemp /15-31 https://docs.python.org/3.10/library/typing.html#typing.FrozenSet +RedirectTemp /15-32 https://docs.python.org/3.10/library/typing.html#typing.Generator +RedirectTemp /15-33 https://docs.python.org/3.10/library/typing.html#typing.AsyncGenerator +RedirectTemp /15-34 https://github.com/python/cpython/blob/46b16d0bdbb1722daed10389e27226a2370f1635/Lib/typing.py#L1786 +RedirectTemp /15-35 https://github.com/python/typeshed/blob/2a9f081abbf01134e4e04ced6a750107db904d70/stdlib/builtins.pyi#L239 +RedirectTemp /15-36 https://www.oreilly.com/library/view/robust-python/9781098100650/ +RedirectTemp /15-37 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance +RedirectTemp /15-38 https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generic-types +RedirectTemp /15-39 https://mypy.readthedocs.io/en/stable/common_issues.html#variance +RedirectTemp /15-40 https://www.artima.com/weblogs/viewpost.jsp?thread=85551 +RedirectTemp /15-41 https://dl.acm.org/action/cookieAbsent +RedirectTemp /15-42 http://bracha.org/pluggableTypesPosition.pdf +RedirectTemp /15-43 https://www.researchgate.net/publication/213886116_Static_Typing_Where_Possible_Dynamic_Typing_When_Needed_The_End_of_the_Cold_War_Between_Programming_Languages +RedirectTemp /15-44 https://www.atomickotlin.com/atomickotlin/ +RedirectTemp /15-45 https://www.informit.com/store/effective-java-9780134685991 +RedirectTemp /15-46 https://www.manning.com/books/programming-with-types +RedirectTemp /15-47 https://www.oreilly.com/library/view/programming-typescript/9781492037644/ +RedirectTemp /15-48 https://www.informit.com/store/dart-programming-language-9780321927705 +RedirectTemp /15-49 https://www.yodaiken.com/2017/09/15/bad-ideas-in-type-theory/ +RedirectTemp /15-50 https://www.yodaiken.com/2017/11/30/types-considered-harmful-ii/ +RedirectTemp /15-51 https://web.archive.org/web/20071010002142/http://weblogs.java.net/blog/arnold/archive/2005/06/generics_consid_1.html +RedirectTemp /15-52 https://github.com/python/cpython/blob/3e7ee02327db13e4337374597cdc4458ecb9e3ad/Lib/asyncio/trsock.py#L5 +RedirectTemp /15-53 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance +############################################################ 16 +RedirectTemp /16-1 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-2 https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations +RedirectTemp /16-3 https://docs.python.org/3/reference/datamodel.html#object.__neg__ +RedirectTemp /16-4 https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#boolean-indexing +RedirectTemp /16-5 https://docs.python.org/3/library/collections.html#collections.Counter +RedirectTemp /16-6 https://docs.python.org/3/reference/datamodel.html#emulating-container-types +RedirectTemp /16-7 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations +RedirectTemp /16-8 https://www.fluentpython.com/lingo/#fail-fast +RedirectTemp /16-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Objects/typeobject.c#L4598 +RedirectTemp /16-10 https://neopythonic.blogspot.com/2019/03/why-operators-are-useful.html +RedirectTemp /16-11 https://treyhunner.com/2019/03/python-deep-comparisons-and-code-readability/ +RedirectTemp /16-12 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations +RedirectTemp /16-13 https://docs.python.org/3/library/pathlib.html +RedirectTemp /16-14 https://pypi.org/project/scapy/ +RedirectTemp /16-15 https://scapy.readthedocs.io/en/latest/usage.html#stacking-layers +RedirectTemp /16-16 https://docs.python.org/3/library/functools.html#functools.total_ordering +RedirectTemp /16-17 https://wiki.illinois.edu//wiki/download/attachments/273416327/ingalls.pdf +RedirectTemp /16-18 https://wiki.illinois.edu//wiki/download/attachments/273416327/double-dispatch.pdf +RedirectTemp /16-19 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-20 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-21 https://doc.rust-lang.org/std/ops/index.html +RedirectTemp /16-22 https://www.fluentpython.com/lingo/#lazy +############################################################ 17 +RedirectTemp /17-1 http://www.paulgraham.com/icad.html +RedirectTemp /17-2 https://en.wikipedia.org/wiki/Sentinel_value +RedirectTemp /17-3 https://docs.python.org/3.10/library/functions.html#iter +RedirectTemp /17-4 https://docs.python.org/3.10/library/functions.html#iter +RedirectTemp /17-5 https://github.com/python/cpython/blob/b1930bf75f276cd7ca08c4455298128d89adf7d1/Lib/_collections_abc.py#L271 +RedirectTemp /17-6 https://github.com/python/cpython/blob/main/Lib/types.py#L6 +RedirectTemp /17-7 https://en.wikipedia.org/wiki/CLU_(programming_language) +RedirectTemp /17-8 https://docs.python.org/3/glossary.html +RedirectTemp /17-9 https://docs.python.org/3/glossary.html#term-generator-iterator +RedirectTemp /17-10 https://docs.python.org/3/glossary.html#term-generator-expression +RedirectTemp /17-11 https://marc.info/?l=python-list&m=141826925106951&w=2 +RedirectTemp /17-12 https://docs.python.org/3/library/os.html#os.walk +RedirectTemp /17-13 https://docs.python.org/3/library/itertools.html +RedirectTemp /17-14 https://docs.python.org/3/library/exceptions.html#exception-hierarchy +RedirectTemp /17-15 https://en.wikipedia.org/wiki/Depth-first_search +RedirectTemp /17-16 https://docs.python.org/3.10/library/typing.html#typing.TypeAlias +RedirectTemp /17-17 https://docs.python.org/3/library/typing.html#typing.Generator +RedirectTemp /17-18 http://www.dabeaz.com/coroutines/Coroutines.pdf +RedirectTemp /17-19 http://www.dabeaz.com/coroutines/Coroutines.pdf +RedirectTemp /17-20 https://mail.python.org/pipermail/python-ideas/2009-April/003841.html +RedirectTemp /17-21 https://mail.python.org/pipermail/python-ideas/2009-April/003912.html +RedirectTemp /17-22 https://docs.python.org/3/library/exceptions.html#StopIteration +RedirectTemp /17-23 https://docs.python.org/3/reference/expressions.html#yield-expressions +RedirectTemp /17-24 https://docs.python.org/3/reference/index.html +RedirectTemp /17-25 https://github.com/python/cpython/blob/6f743e7a4da904f61dfa84cc7d7385e4dcc79ac5/Lib/typing.py#L2060 +RedirectTemp /17-26 http://catb.org/~esr/jargon/html/G/grok.html +RedirectTemp /17-27 https://docs.python.org/3/reference/expressions.html#yieldexpr +RedirectTemp /17-28 https://docs.python.org/3/library/itertools.html +RedirectTemp /17-29 https://docs.python.org/3/library/itertools.html#itertools-recipes +RedirectTemp /17-30 https://more-itertools.readthedocs.io/en/stable/index.html +RedirectTemp /17-31 https://rittau.org/2006/11/java-iterators-are-not-iterable/ +RedirectTemp /17-32 https://docs.python.org/3/whatsnew/3.3.html#pep-380-syntax-for-delegating-to-a-subgenerator +RedirectTemp /17-33 http://www.dabeaz.com/generators/ +RedirectTemp /17-34 http://www.dabeaz.com/coroutines/ +RedirectTemp /17-35 https://archive.org/details/pyvideo_213___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-1-of-3 +RedirectTemp /17-36 https://archive.org/details/pyvideo_215___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-2-of-3 +RedirectTemp /17-37 https://archive.org/details/pyvideo_214___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-3-of-3 +RedirectTemp /17-38 http://www.dabeaz.com/finalgenerator/ +RedirectTemp /17-39 https://web.archive.org/web/20200218150637/http://seriously.dontusethiscode.com/2013/05/01/greedy-coroutine.html +RedirectTemp /17-40 https://effectivepython.com/ +RedirectTemp /17-41 https://effectivepython.com/2015/03/10/consider-coroutines-to-run-many-functions-concurrently +RedirectTemp /17-42 https://en.wikipedia.org/wiki/Conway's_Game_of_Life +RedirectTemp /17-43 https://gist.github.com/ramalho/da5590bc38c973408839 +RedirectTemp /17-44 https://gist.github.com/ramalho/da5590bc38c973408839 +RedirectTemp /17-45 https://journal.code4lib.org/articles/4893 +RedirectTemp /17-46 https://github.com/fluentpython/isis2json +RedirectTemp /17-47 https://github.com/fluentpython/isis2json/blob/master/README.rst +############################################################ 18 +RedirectTemp /18-1 https://pyvideo.org/video/1669/keynote-3/ +RedirectTemp /18-2 https://docs.python.org/3/library/sqlite3.html#using-the-connection-as-a-context-manager +RedirectTemp /18-3 https://docs.python.org/3/library/threading.html#using-locks-conditions-and-semaphores-in-the-with-statement +RedirectTemp /18-4 https://docs.python.org/3/library/decimal.html#decimal.localcontext +RedirectTemp /18-5 https://docs.python.org/3/library/unittest.mock.html#patch +RedirectTemp /18-6 https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout +RedirectTemp /18-7 https://docs.python.org/3/library/sys.html#sys.exc_info +RedirectTemp /18-8 https://en.wikipedia.org/wiki/LL_parser +RedirectTemp /18-9 https://docs.python.org/3/library/contextlib.html +RedirectTemp /18-10 https://github.com/python/cpython/blob/8afab2ebbc1b343cd88d058914cf622fe687a2be/Lib/contextlib.py#L123 +RedirectTemp /18-11 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ +RedirectTemp /18-12 https://docs.python.org/3/library/fileinput.html#fileinput.input +RedirectTemp /18-13 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ +RedirectTemp /18-14 https://en.wikipedia.org/wiki/Euclidean_algorithm +RedirectTemp /18-15 https://github.com/fluentpython/example-code-2e/tree/master/18-with-match/lispy/py3.10/ +RedirectTemp /18-16 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lispy.py +RedirectTemp /18-17 https://github.com/fluentpython/example-code-2e/blob/6527037ae7319ba370a1ee2d9fe79214d0ed9452/18-with-match/lispy/py3.10/lis.py#L35 +RedirectTemp /18-18 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py +RedirectTemp /18-19 https://github.com/python/typeshed/issues/6042 +RedirectTemp /18-20 https://github.com/fluentpython/lispy/tree/main/mylis +RedirectTemp /18-21 https://github.com/fluentpython/example-code-2e/blob/00e4741926e1b771ee7c753148b1415c0bd12e39/02-array-seq/lispy/py3.10/examples_test.py +RedirectTemp /18-22 https://mitpress.mit.edu/sites/default/files/sicp/index.html +RedirectTemp /18-23 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py +RedirectTemp /18-24 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/lis.py +RedirectTemp /18-25 https://www.python.org/dev/peps/pep-0634/#or-patterns +RedirectTemp /18-26 https://en.wikipedia.org/wiki/Lambda#Character_encodings +RedirectTemp /18-27 https://docs.python.org/3/reference/compound_stmts.html +RedirectTemp /18-28 https://docs.python.org/3/glossary.html#term-eafp +RedirectTemp /18-29 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 +RedirectTemp /18-30 https://docs.python.org/3/reference/compound_stmts.html +RedirectTemp /18-31 https://stackoverflow.com/questions/16138232/is-it-a-good-practice-to-use-try-except-else-in-python +RedirectTemp /18-32 https://docs.python.org/3/library/stdtypes.html#typecontextmanager +RedirectTemp /18-33 https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers +RedirectTemp /18-34 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 +RedirectTemp /18-35 https://speakerdeck.com/pyconslides/transforming-code-into-beautiful-idiomatic-python-by-raymond-hettinger-1?slide=34 +RedirectTemp /18-36 https://preshing.com/20110920/the-python-with-statement-by-example/ +RedirectTemp /18-37 https://www.rath.org/on-the-beauty-of-pythons-exitstack.html +RedirectTemp /18-38 https://norvig.com/lispy.html +RedirectTemp /18-39 https://norvig.com/lispy2.html +RedirectTemp /18-40 https://github.com/norvig/pytudes +RedirectTemp /18-41 https://github.com/fluentpython/lispy +RedirectTemp /18-42 https://racket-lang.org/ +RedirectTemp /18-43 https://pyvideo.org/video/1669/keynote-3/ +RedirectTemp /18-44 https://en.wikipedia.org/wiki/Tail_call +RedirectTemp /18-45 https://2ality.com/2015/06/tail-call-optimization.html +RedirectTemp /18-46 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lis.py +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-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 +RedirectTemp /18-54 https://github.com/fluentpython/lispy/tree/main/mylis +############################################################ 19 +RedirectTemp /19-1 https://go.dev/blog/waza-talk +RedirectTemp /19-2 https://en.wikipedia.org/wiki/Graphics_processing_unit +RedirectTemp /19-3 https://docs.python.org/3/library/sys.html#sys.getswitchinterval +RedirectTemp /19-4 https://docs.python.org/3/library/sys.html#sys.setswitchinterval +RedirectTemp /19-5 https://en.wikipedia.org/wiki/System_call +RedirectTemp /19-6 https://mail.python.org/pipermail/python-dev/2009-October/093356.html +RedirectTemp /19-7 http://www.dabeaz.com/finalgenerator/ +RedirectTemp /19-8 https://docs.python.org/3/library/threading.html#thread-objects +RedirectTemp /19-9 https://www.pypy.org/ +RedirectTemp /19-10 https://mail.python.org/pipermail/python-list/2009-February/675659.html +RedirectTemp /19-11 https://en.wikipedia.org/wiki/Braille_Patterns +RedirectTemp /19-12 https://docs.python.org/3/library/multiprocessing.shared_memory.html +RedirectTemp /19-13 https://docs.python.org/3/library/multiprocessing.shared_memory.html#multiprocessing.shared_memory.ShareableList +RedirectTemp /19-14 https://greenlet.readthedocs.io/en/latest/ +RedirectTemp /19-15 https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#asynchronous-io-support-for-core-and-orm +RedirectTemp /19-16 https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html +RedirectTemp /19-17 http://www.gevent.org/ +RedirectTemp /19-18 https://github.com/gevent/gevent/wiki/Projects +RedirectTemp /19-19 https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor-example +RedirectTemp /19-20 https://github.com/python/asyncio/issues/284 +RedirectTemp /19-21 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /19-22 https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/ +RedirectTemp /19-23 https://docs.python.org/3/library/queue.html#queue.SimpleQueue.get +RedirectTemp /19-24 https://en.wikipedia.org/wiki/Race_condition +RedirectTemp /19-25 https://github.com/fluentpython/example-code-2e/commit/2c1230579db99738a5e5e6802063bda585f6476d +RedirectTemp /19-26 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/README.md +RedirectTemp /19-27 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/threads.py +RedirectTemp /19-28 https://en.wikipedia.org/wiki/Context_switch +RedirectTemp /19-29 http://www.gotw.ca/publications/concurrency-ddj.htm +RedirectTemp /19-30 https://www.ansible.com/ +RedirectTemp /19-31 https://saltproject.io/ +RedirectTemp /19-32 https://www.fabfile.org/ +RedirectTemp /19-33 https://engineering.fb.com/2016/05/27/production-engineering/python-in-production-engineering/ +RedirectTemp /19-34 https://jupyter.org/ +RedirectTemp /19-35 https://docs.bokeh.org/en/latest/index.html +RedirectTemp /19-36 https://www.tensorflow.org/ +RedirectTemp /19-37 https://pytorch.org/ +RedirectTemp /19-38 https://www.oreilly.com/radar/where-programming-ops-ai-and-the-cloud-are-headed-in-2021/ +RedirectTemp /19-39 https://www.youtube.com/watch?v=ods97a5Pzw0 +RedirectTemp /19-40 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy +RedirectTemp /19-41 https://modwsgi.readthedocs.io/en/master/ +RedirectTemp /19-42 https://uwsgi-docs.readthedocs.io/en/latest/ +RedirectTemp /19-43 https://unit.nginx.org/ +RedirectTemp /19-44 https://www.techatbloomberg.com/blog/configuring-uwsgi-production-deployment/ +RedirectTemp /19-45 https://www.youtube.com/watch?v=p6R1h2Nn468 +RedirectTemp /19-46 https://asgi.readthedocs.io/en/latest/index.html +RedirectTemp /19-47 https://docs.celeryproject.org/en/stable/getting-started/introduction.html +RedirectTemp /19-48 https://python-rq.org/ +RedirectTemp /19-49 https://docs.celeryproject.org/en/stable/faq.html#what-kinds-of-things-should-i-use-celery-for +RedirectTemp /19-50 https://redis.io/ +RedirectTemp /19-51 https://realpython.com/intro-to-python-threading/ +RedirectTemp /19-52 https://pymotw.com/3/concurrency.html +RedirectTemp /19-53 https://www.pearson.com/us/higher-education/program/Hellmann-Python-3-Standard-Library-by-Example-The/PGM328871.html +RedirectTemp /19-54 https://docs.python.org/3/library/multiprocessing.html#programming-guidelines +RedirectTemp /19-55 https://docs.python.org/3/library/multiprocessing.html +RedirectTemp /19-56 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ +RedirectTemp /19-57 https://link.springer.com/book/10.1007/978-1-4842-5793-7?error=cookies_not_supported&code=2ed5d61d-ae9f-4f3d-94ac-0f68cf45ea4f +RedirectTemp /19-58 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 +RedirectTemp /19-59 https://greenteapress.com/wp/semaphores/ +RedirectTemp /19-60 https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock +RedirectTemp /19-61 https://docs.python.org/3/faq/library.html#can-t-we-get-rid-of-the-global-interpreter-lock +RedirectTemp /19-62 https://www.artima.com/weblogs/viewpost.jsp?thread=214235 +RedirectTemp /19-63 http://jessenoller.com/blog/2009/02/01/python-threads-and-the-global-interpreter-lock +RedirectTemp /19-64 https://realpython.com/products/cpython-internals-book/ +RedirectTemp /19-65 http://www.dabeaz.com/GIL/ +RedirectTemp /19-66 http://www.dabeaz.com/python/UnderstandingGIL.pdf +RedirectTemp /19-67 https://bugs.python.org/issue7946#msg223110 +RedirectTemp /19-68 https://bugs.python.org/issue7946 +RedirectTemp /19-69 https://www.fullstackpython.com/ +RedirectTemp /19-70 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ +RedirectTemp /19-71 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 +RedirectTemp /19-72 https://www.packtpub.com/product/distributed-computing-with-python/9781785889691 +RedirectTemp /19-73 https://towardsdatascience.com/python-performance-and-gpus-1be860ffd58d?gi=a7d537cc2fb4 +RedirectTemp /19-74 https://instagram-engineering.com/web-service-efficiency-at-instagram-with-python-4976d078e366?gi=12a441991c88 +RedirectTemp /19-75 https://www.oreilly.com/library/view/architecture-patterns-with/9781492052197/ +RedirectTemp /19-76 https://www.cosmicpython.com/ +RedirectTemp /19-77 https://pypi.org/project/lelo/ +RedirectTemp /19-78 https://github.com/npryce/python-parallelize +RedirectTemp /19-79 https://github.com/ericsnowcurrently/multi-core-python/wiki +RedirectTemp /19-80 https://gist.github.com/markshannon/79cace3656b40e21b7021504daee950c +RedirectTemp /19-81 https://mail.python.org/archives/list/python-dev@python.org/message/YOOQZCFOKEPQ24YHWWLQSJ3RCXFMS7D7/ +RedirectTemp /19-82 https://en.wikipedia.org/wiki/Communicating_sequential_processes +RedirectTemp /19-83 https://github.com/stackless-dev/stackless/wiki +RedirectTemp /19-84 https://www.eveonline.com +RedirectTemp /19-85 https://www.ccpgames.com/ +RedirectTemp /19-86 https://stackless.readthedocs.io/en/3.6-slp/stackless-python.html#history +RedirectTemp /19-87 https://doc.pypy.org/en/latest/stackless.html +RedirectTemp /19-88 https://greenlet.readthedocs.io/en/latest/ +RedirectTemp /19-89 http://www.gevent.org/ +RedirectTemp /19-90 http://thespianpy.com/doc/ +RedirectTemp /19-91 https://pykka.readthedocs.io/en/latest/ +RedirectTemp /19-92 https://www.manning.com/books/rabbitmq-in-action +RedirectTemp /19-93 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ +RedirectTemp /19-94 https://en.wikipedia.org/wiki/OpenCL +RedirectTemp /19-95 https://media.pragprog.com/titles/pb7con/Bonus_Chapter.pdf +RedirectTemp /19-96 https://martinfowler.com/ +RedirectTemp /19-97 https://martinfowler.com/articles/patterns-of-distributed-systems/ +RedirectTemp /19-98 https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/ +RedirectTemp /19-99 https://www.oreilly.com/library/view/oscon-2016-video/9781491965153/video247021.html +RedirectTemp /19-100 https://www.oreilly.com/library/view/designing-for-scalability/9781449361556/ +RedirectTemp /19-101 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy +RedirectTemp /19-102 https://en.wikipedia.org/wiki/KISS_principle +RedirectTemp /19-103 https://www.usenix.org/conference/hotos15/workshop-program/presentation/mcsherry +############################################################ 20 +RedirectTemp /20-1 https://www.artima.com/weblogs/viewpost.jsp?thread=299551 +RedirectTemp /20-2 https://docs.python.org/3/library/http.server.html +RedirectTemp /20-3 https://www.youtube.com/watch?v=A9e9Cy1UkME +RedirectTemp /20-4 https://www.cia.gov/the-world-factbook/ +RedirectTemp /20-5 https://docs.python-requests.org/en/latest/ +RedirectTemp /20-6 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor +RedirectTemp /20-7 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /20-8 https://docs.python.org/3/library/concurrent.futures.html +RedirectTemp /20-9 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.Executor +RedirectTemp /20-10 https://github.com/fluentpython/example-code-2e/blob/master/20-executors/getflags/flags2_common.py +RedirectTemp /20-11 https://github.com/noamraph/tqdm +RedirectTemp /20-12 https://www.youtube.com/watch?v=M8Z65tAl5l4 +RedirectTemp /20-13 https://github.com/noamraph/tqdm/blob/master/README.md +RedirectTemp /20-14 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /20-15 https://docs.python.org/3/library/asyncio-task.html#asyncio.as_completed +RedirectTemp /20-16 https://www.cloudflare.com/ +RedirectTemp /20-17 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags +RedirectTemp /20-18 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 +RedirectTemp /20-19 https://en.wikipedia.org/wiki/Embarrassingly_parallel +RedirectTemp /20-20 https://pyvideo.org/video/480/pyconau-2010--the-future-is-soon/ +RedirectTemp /20-21 http://www.dabeaz.com/coroutines/ +RedirectTemp /20-22 https://en.wikipedia.org/wiki/POSIX_Threads +RedirectTemp /20-23 https://en.wikipedia.org/wiki/C_dynamic_memory_allocation +RedirectTemp /20-24 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ +RedirectTemp /20-25 https://hexdocs.pm/ecto/getting-started.html +############################################################ 21 +RedirectTemp /21-1 https://docs.python.org/3/library/asyncio.html +RedirectTemp /21-2 https://bugs.python.org/issue43216 +RedirectTemp /21-3 https://bugs.python.org/issue36921 +RedirectTemp /21-4 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.getaddrinfo +RedirectTemp /21-5 https://docs.python.org/3/library/socket.html#socket.getaddrinfo +RedirectTemp /21-6 https://docs.python.org/3.10/library/asyncio-eventloop.html#asyncio.get_event_loop +RedirectTemp /21-7 https://www.python.org/dev/peps/pep-0492/#await-expression +RedirectTemp /21-8 https://www.fluentpython.com/extra/classic-coroutines/#yield_from_meaning_sec +RedirectTemp /21-9 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags +RedirectTemp /21-10 https://magicstack.github.io/asyncpg/current/ +RedirectTemp /21-11 https://magicstack.github.io/asyncpg/current/api/index.html#transactions +RedirectTemp /21-12 https://magicstack.github.io/asyncpg/current/api/index.html#transactions +RedirectTemp /21-13 https://github.com/MagicStack/asyncpg/blob/4d39a05268ce4cc01b00458223a767542da048b8/asyncpg/transaction.py#L57 +RedirectTemp /21-14 https://magicstack.github.io/asyncpg/current/usage.html#connection-pools +RedirectTemp /21-15 https://gist.github.com/jboner/2841832 +RedirectTemp /21-16 https://en.wikipedia.org/wiki/Network-attached_storage +RedirectTemp /21-17 https://en.wikipedia.org/wiki/Semaphore_(programming) +RedirectTemp /21-18 https://en.wikipedia.org/wiki/Semaphore_(programming) +RedirectTemp /21-19 https://groups.google.com/forum/#!msg/python-tulip/PdAEtwpaJHs/7fqb-Qj2zJoJ +RedirectTemp /21-20 https://web.archive.org/web/20151209151711/http://tritarget.org/blog/2012/11/28/the-pyramid-of-doom-a-javascript-style-trap +RedirectTemp /21-21 https://stackoverflow.com/questions/53701841/what-is-the-use-case-for-future-add-done-callback/53710563 +RedirectTemp /21-22 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /21-23 https://motor.readthedocs.io/en/stable/ +RedirectTemp /21-24 https://emptysqua.re/blog/response-to-asynchronous-python-and-databases/ +RedirectTemp /21-25 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /21-26 https://en.wikipedia.org/wiki/Phaistos_Disc +RedirectTemp /21-27 https://en.wikipedia.org/wiki/Inverted_index +RedirectTemp /21-28 https://fastapi.tiangolo.com/ +RedirectTemp /21-29 https://swagger.io/specification/ +RedirectTemp /21-30 https://asgi.readthedocs.io/en/latest/implementations.html +RedirectTemp /21-31 https://pydantic-docs.helpmanual.io/ +RedirectTemp /21-32 https://doc.traefik.io/traefik/ +RedirectTemp /21-33 https://fastapi.tiangolo.com/project-generation/ +RedirectTemp /21-34 https://fastapi.tiangolo.com/tutorial/response-model/ +RedirectTemp /21-35 https://docs.python.org/3/library/asyncio-stream.html#asyncio.start_server +RedirectTemp /21-36 https://github.com/python/typeshed/issues/5535 +RedirectTemp /21-37 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.Server.serve_forever +RedirectTemp /21-38 https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter.close +RedirectTemp /21-39 https://docs.python.org/3/library/asyncio-stream.html#streamwriter +RedirectTemp /21-40 https://docs.python.org/3/library/asyncio-stream.html +RedirectTemp /21-41 https://docs.python.org/3/library/asyncio-protocol.html +RedirectTemp /21-42 https://docs.python.org/3/library/asyncio-protocol.html#tcp-echo-server +RedirectTemp /21-43 https://github.com/aio-libs/aiopg +RedirectTemp /21-44 https://docs.python.org/3/whatsnew/3.8.html#asyncio +RedirectTemp /21-45 https://datatracker.ietf.org/doc/html/rfc6761 +RedirectTemp /21-46 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /21-47 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /21-48 https://docs.python.org/3/library/asyncio-task.html#asyncio.gather +RedirectTemp /21-49 https://curio.readthedocs.io/en/latest/index.html +RedirectTemp /21-50 https://curio.readthedocs.io/en/latest/reference.html#task-groups +RedirectTemp /21-51 https://en.wikipedia.org/wiki/Structured_concurrency +RedirectTemp /21-52 https://mail.python.org/archives/list/python-dev@python.org/thread/2ORDAW74LGE3ZI2QETPJRT2ZL7MCCPG2/ +RedirectTemp /21-53 https://www.python.org/dev/peps/pep-0654/#motivation +RedirectTemp /21-54 https://curio.readthedocs.io/en/latest/reference.html#AWAIT +RedirectTemp /21-55 https://www.python-httpx.org/async/#curio +RedirectTemp /21-56 https://github.com/dabeaz/curio/tree/78bca8a6ad677ef51e1568ac7b3e51441ab49c42/examples +RedirectTemp /21-57 https://datatracker.ietf.org/doc/html/rfc8305 +RedirectTemp /21-58 https://trio.readthedocs.io/en/stable/ +RedirectTemp /21-59 https://www.youtube.com/watch?v=M-sc73Y-zQA +RedirectTemp /21-60 https://en.wikipedia.org/wiki/Technical_debt +RedirectTemp /21-61 https://www.youtube.com/watch?v=E-1Y4kSsAFc +RedirectTemp /21-62 https://github.com/dabeaz/curio +RedirectTemp /21-63 https://trio.readthedocs.io/en/stable/ +RedirectTemp /21-64 https://curio.readthedocs.io/en/latest/#curio-university +RedirectTemp /21-65 https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/ +RedirectTemp /21-66 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ +RedirectTemp /21-67 https://stackoverflow.com/questions/49482969/what-is-the-core-difference-between-asyncio-and-trio +RedirectTemp /21-68 https://docs.python.org/3/library/asyncio.html +RedirectTemp /21-69 https://bugs.python.org/issue33649 +RedirectTemp /21-70 https://docs.python.org/3/library/asyncio-dev.html +RedirectTemp /21-71 https://www.youtube.com/watch?v=iG6fr81xHKA +RedirectTemp /21-72 https://www.youtube.com/watch?v=F19R_M4Nay4 +RedirectTemp /21-73 https://asherman.io/projects/unsync.html +RedirectTemp /21-74 https://pyladies.com/ +RedirectTemp /21-75 https://www.youtube.com/watch?v=sW76-pRkZk8 +RedirectTemp /21-76 https://www.youtube.com/watch?v=Xbl7XjFYsN4 +RedirectTemp /21-77 https://www.youtube.com/watch?v=02CLD-42VdI +RedirectTemp /21-78 https://micropython.org/ +RedirectTemp /21-79 https://docs.micropython.org/en/latest/library/uasyncio.html +RedirectTemp /21-80 https://www.encode.io/articles/python-async-frameworks-beyond-developer-tribalism +RedirectTemp /21-81 https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ +RedirectTemp /21-82 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ +RedirectTemp /21-83 https://github.com/MagicStack/uvloop +RedirectTemp /21-84 http://magic.io/blog/uvloop-blazing-fast-python-networking/ +RedirectTemp /21-85 https://github.com/MagicStack/httptools +RedirectTemp /21-86 https://docs.aiohttp.org/en/stable/ +RedirectTemp /21-87 https://github.com/wg/wrk +RedirectTemp /21-88 https://twistedmatrix.com/trac/ +############################################################ 22 +RedirectTemp /22-1 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/data/osconfeed.json +RedirectTemp /22-2 https://pypi.org/project/attrdict/ +RedirectTemp /22-3 https://pypi.org/project/addict/ +RedirectTemp /22-4 https://github.com/ActiveState/code/tree/master/recipes/Python/52308_simple_but_handy_collector_bunch_named_stuff +RedirectTemp /22-5 https://docs.python.org/3/library/types.html#types.SimpleNamespace +RedirectTemp /22-6 https://docs.python.org/3/library/argparse.html#argparse.Namespace +RedirectTemp /22-7 https://docs.python.org/3/library/multiprocessing.html#multiprocessing.managers.Namespace +RedirectTemp /22-8 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/schedule_v2.py +RedirectTemp /22-9 https://docs.python.org/3/library/functools.html#functools.cached_property +RedirectTemp /22-10 https://docs.python.org/3/library/functools.html#functools.cached_property +RedirectTemp /22-11 https://bugs.python.org/issue42781 +RedirectTemp /22-12 https://docs.python.org/3/howto/descriptor.html +RedirectTemp /22-13 https://github.com/python/cpython/blob/e6d0107e13ed957109e79b796984d3d026a8660d/Lib/functools.py#L926 +RedirectTemp /22-14 https://docs.python.org/3/library/threading.html#rlock-objects +RedirectTemp /22-15 https://docs.python.org/3.10/library/functools.html#functools.cached_property +RedirectTemp /22-16 https://www.wsj.com/articles/SB10001424052970203914304576627102996831200 +RedirectTemp /22-17 https://www.youtube.com/watch?v=s35rVw1zskA&feature=youtu.be +RedirectTemp /22-18 https://docs.python.org/3/library/functions.html#dir +RedirectTemp /22-19 https://github.com/python/cpython/blob/19903085c3ad7a17c8047e1556c700f2eb109931/Lib/cmd.py#L214 +RedirectTemp /22-20 https://docs.python.org/3/library/functions.html#hasattr +RedirectTemp /22-21 https://docs.python.org/3.10/reference/datamodel.html#special-method-lookup +RedirectTemp /22-22 https://docs.python.org/3/library/functions.html +RedirectTemp /22-23 https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access +RedirectTemp /22-24 https://docs.python.org/3/reference/datamodel.html#special-method-lookup +RedirectTemp /22-25 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /22-26 http://wiki.c2.com/?WelcomeVisitors +RedirectTemp /22-27 http://wiki.c2.com/?UniformAccessPrinciple +RedirectTemp /22-28 https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html +RedirectTemp /22-29 http://www.pingo.io/docs/ +RedirectTemp /22-30 https://www.drdobbs.com/javas-new-considered-harmful/184405016 +RedirectTemp /22-31 https://www.python.org/dev/peps/pep-0008/#class-names +############################################################ 23 +RedirectTemp /23-1 http://www.aleax.it/goo_pydp.pdf +RedirectTemp /23-2 https://docs.python.org/3.10/reference/datamodel.html#implementing-descriptors +RedirectTemp /23-3 https://docs.python.org/3/howto/descriptor.html +RedirectTemp /23-4 https://docs.python.org/3/howto/ +RedirectTemp /23-5 http://www.aleax.it/Python/nylug05_om.pdf +RedirectTemp /23-6 https://www.youtube.com/watch?v=VOzvpHoYQoo +RedirectTemp /23-7 https://www.python.org/dev/peps/pep-0487/#trait-descriptors +RedirectTemp /23-8 https://dreamsongs.com/RiseOfWorseIsBetter.html +RedirectTemp /23-9 http://web.archive.org/web/20031002184114/www.amk.ca/python/writing/warts.html +RedirectTemp /23-10 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this +RedirectTemp /23-11 http://python-history.blogspot.com/2009/02/adding-support-for-user-defined-classes.html +############################################################ 24 +RedirectTemp /24-1 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /24-2 https://docs.djangoproject.com/en/3.2/topics/db/models/#meta-options +RedirectTemp /24-3 https://www.python.org/dev/peps/pep-3155/ +RedirectTemp /24-4 https://github.com/python/cpython/blob/3.9/Lib/collections/__init__.py +RedirectTemp /24-5 https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions +RedirectTemp /24-6 https://go.dev/tour/basics/12 +RedirectTemp /24-7 https://bugs.python.org/issue42102 +RedirectTemp /24-8 https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__ +RedirectTemp /24-9 https://www.python.org/dev/peps/pep-0557/#abstract +RedirectTemp /24-10 https://github.com/python/cpython/blob/3.9/Lib/dataclasses.py +RedirectTemp /24-11 https://docs.python.org/3/reference/datamodel.html#creating-the-class-object +RedirectTemp /24-12 https://mail.python.org/pipermail/python-list/2002-December/134521.html +RedirectTemp /24-13 https://mail.python.org/pipermail/python-list/2002-July/162558.html +RedirectTemp /24-14 https://github.com/fluentpython/example-code/tree/master/21-class-metaprog/bulkfood +RedirectTemp /24-15 https://en.wikipedia.org/wiki/Principle_of_least_astonishment +RedirectTemp /24-16 https://docs.python.org/3/reference/datamodel.html#object.__class_getitem__ +RedirectTemp /24-17 https://en.wikipedia.org/wiki/Trait_(computer_programming) +RedirectTemp /24-18 https://en.wikipedia.org/wiki/Aspect-oriented_programming +RedirectTemp /24-19 https://dhh.dk/arc/000416.html +RedirectTemp /24-20 https://github.com/cjrh/autoslot +RedirectTemp /24-21 https://docs.python.org/3/reference/datamodel.html#customizing-class-creation +RedirectTemp /24-22 https://docs.python.org/3/library/functions.html#type +RedirectTemp /24-23 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /24-24 https://docs.python.org/3/library/types.html +RedirectTemp /24-25 https://www.python.org/dev/peps/pep-3129/ +RedirectTemp /24-26 https://www.youtube.com/watch?v=cAGliEJV9_o +RedirectTemp /24-27 https://docs.python.org/3/library/functools.html#functools.total_ordering +RedirectTemp /24-28 https://www.python.org/download/releases/2.2.3/descrintro/ +RedirectTemp /24-29 https://github.com/lihaoyi/macropy +RedirectTemp /24-30 https://people.eecs.berkeley.edu/~bh/ss-toc2.html +# content of short.htaccess file created and managed by short.py + +# appended: 2025-05-23 15:12:13 +RedirectTemp /22 https://pythonfluente.com/2/#pattern_matching_case_study_sec +RedirectTemp /23 https://pythonfluente.com/2/#how_slicing_works +RedirectTemp /24 https://pythonfluente.com/2/#sliceable_sequence +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 /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 +RedirectTemp /2c https://pythonfluente.com/2/#positional_pattern_implement_sec diff --git a/links/FPY.LI.short.htaccess b/links/FPY.LI.short.htaccess new file mode 100644 index 00000000..504eb505 --- /dev/null +++ b/links/FPY.LI.short.htaccess @@ -0,0 +1,20 @@ +###################################################################################### +# content of FPY.LI.short.htaccess file created and managed by shorten.py + +# appended: 2025-05-23 15:12:13 +RedirectTemp /22 https://pythonfluente.com/2/#pattern_matching_case_study_sec +RedirectTemp /23 https://pythonfluente.com/2/#how_slicing_works +RedirectTemp /24 https://pythonfluente.com/2/#sliceable_sequence +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 /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 +RedirectTemp /2c https://pythonfluente.com/2/#positional_pattern_implement_sec + +# appended: 2025-05-26 16:01:24 +RedirectTemp /2d https://mitpress.mit.edu/9780262111584/the-art-of-the-metaobject-protocol/ +RedirectTemp /2e https://dabeaz.com/per.html +RedirectTemp /2f https://pythonfluente.com/2/#iter_closer_look diff --git a/links/README.md b/links/README.md new file mode 100644 index 00000000..e2f62bc0 --- /dev/null +++ b/links/README.md @@ -0,0 +1,42 @@ +# Short links for URLs in the book + +## Problem: link rot + +_Fluent Python, Second Edition_ has more than 1000 links to external resources. +Inevitably, some of those links will rot as time passes. +But I can't change the URLs in the print book... + +## Solution: indirection + +I replaced almost all URLs in the book with shortened versions that go through the `fpy.li` site which I control. +The site has an `.htaccess` file with *temporary* redirects. + +When I find out a link is stale, I can thange the redirect in `.htaccess` to a new target, +which may be a link to copy in the Internet Archive's +[Wayback Machine](https://archive.org/web/) +o the link in the book is back in service through the updated redirect. + + +## Help wanted + +Please report broken links as bugs in the [`FPY.LI.htaccess`](FPY.LI.htaccess) file. +Also, feel free to send pull requests with fixes to that file. +When I accept a PR, I will redeploy it to `fpy.li/.htaccess`. + + +## Details + +Almost all URLs in the book are replaced with shortened versions like +[`http://fpy.li/1-3`](http://fpy.li/1-3)—for chapter 1, link #3. + +There are also custom short URLs like +[`https://fpy.li/code`](https://fpy.li/code) which redirects to the example code repository. +I used custom short URLs for URLs with 3 or more mentions, or links to PEPs. + +Exceptions: + +- URLs with `oreilly` in them are unchanged; +- `fluentpython.com` URL (with no path) is unchanged; + +`FPY.LI.htaccess` is deployed at the root folder in `http://fpy.li`, +renamed to `.htaccess`. diff --git a/links/amostra-links.adoc b/links/amostra-links.adoc new file mode 100644 index 00000000..15c571ac --- /dev/null +++ b/links/amostra-links.adoc @@ -0,0 +1,55 @@ += Python Fluente: Dados e Funções +:doctype: book +:media: prepress +:hide-uri-scheme: +:pdf-page-size: [17cm, 24cm] +:source-highlighter: rouge +:author: Luciano Ramalho +:lang: pt_BR +:language: asciidoctor +:xrefstyle: short +:sectnums: +:sectnumlevels: 4 +:sectlinks: +:data-uri: +:toc: left +:toclevels: 2 +:!chapter-signifier: + +O sinalizador da nova «sintaxe de strings de formato» [fpy.li/2f] +usada nas _f-strings_ e... + +O sinalizador da nova https://fpy.li/2f[sintaxe de strings de formato] +usada nas _f-strings_ e... + +O sinalizador da nova «sintaxe de strings de formato» [.small]#[fpy.li/2f]# +usada nas _f-strings_ e... + +O sinalizador da nova «sintaxe de strings de formato» [.small]##[fpy.li/2f]## +usada nas _f-strings_ e... + + +---- +U+0022 " QUOTATION MARK +U+00AB « LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +U+00BB » RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +U+2018 ‘ LEFT SINGLE QUOTATION MARK +U+2019 ’ RIGHT SINGLE QUOTATION MARK +U+201A ‚ SINGLE LOW-9 QUOTATION MARK +U+201B ‛ SINGLE HIGH-REVERSED-9 QUOTATION MARK +U+201C “ LEFT DOUBLE QUOTATION MARK +U+201D ” RIGHT DOUBLE QUOTATION MARK +U+201E „ DOUBLE LOW-9 QUOTATION MARK +U+201F ‟ DOUBLE HIGH-REVERSED-9 QUOTATION MARK +U+2039 ‹ SINGLE LEFT-POINTING ANGLE QUOTATION MARK +U+203A › SINGLE RIGHT-POINTING ANGLE QUOTATION MARK +U+275B ❛ HEAVY SINGLE TURNED COMMA QUOTATION MARK ORNAMENT +U+275C ❜ HEAVY SINGLE COMMA QUOTATION MARK ORNAMENT +U+275D ❝ HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT +U+275E ❞ HEAVY DOUBLE COMMA QUOTATION MARK ORNAMENT +U+275F ❟ HEAVY LOW SINGLE COMMA QUOTATION MARK ORNAMENT +U+2760 ❠ HEAVY LOW DOUBLE COMMA QUOTATION MARK ORNAMENT +U+276E ❮ HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT +U+276F ❯ HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT + +---- \ No newline at end of file diff --git a/links/cap01-replacements.txt b/links/cap01-replacements.txt new file mode 100644 index 00000000..8ff8d26a --- /dev/null +++ b/links/cap01-replacements.txt @@ -0,0 +1,11 @@ + /2d https://docs.python.org/pt-br/3/reference/lexical_analysis.html#reserved-classes-of-identifiers + /2e https://docs.python.org/pt-br/3/library/doctest.html + /2f https://docs.python.org/pt-br/3.10/library/string.html#format-string-syntax + /2g https://docs.python.org/pt-br/3/library/stdtypes.html#truth + /2h https://docs.python.org/pt-br/3/tutorial/controlflow.html#unpacking-argument-lists + /2j https://docs.python.org/pt-br/3/reference/datamodel.html + /2j https://docs.python.org/pt-br/3/reference/datamodel.html + /2k https://dabeaz.com/per.html + /2m https://mitpress.mit.edu/books/art-metaobject-protocol + /2j https://docs.python.org/pt-br/3/reference/datamodel.html + /2n https://plone.org.br/ diff --git a/links/cap01-urls.txt b/links/cap01-urls.txt new file mode 100644 index 00000000..47a78d44 --- /dev/null +++ b/links/cap01-urls.txt @@ -0,0 +1,11 @@ +https://docs.python.org/pt-br/3/reference/lexical_analysis.html#reserved-classes-of-identifiers +https://docs.python.org/pt-br/3/library/doctest.html +https://docs.python.org/pt-br/3.10/library/string.html#format-string-syntax +https://docs.python.org/pt-br/3/library/stdtypes.html#truth +https://docs.python.org/pt-br/3/tutorial/controlflow.html#unpacking-argument-lists +https://docs.python.org/pt-br/3/reference/datamodel.html +https://docs.python.org/pt-br/3/reference/datamodel.html +https://dabeaz.com/per.html +https://mitpress.mit.edu/books/art-metaobject-protocol +https://docs.python.org/pt-br/3/reference/datamodel.html +https://plone.org.br/ diff --git a/links/cap02-replacements.txt b/links/cap02-replacements.txt new file mode 100644 index 00000000..6e1fe4e0 --- /dev/null +++ b/links/cap02-replacements.txt @@ -0,0 +1,14 @@ += /2p http://fluentpython.com += /2q https://docs.python.org/pt-br/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations += /2q https://docs.python.org/pt-br/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations += /2r https://docs.python.org/pt-br/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching += /2s https://docs.python.org/pt-br/3.10/whatsnew/3.10.html += /2p http://fluentpython.com += /2t https://docs.python.org/pt-br/3/library/bisect.html#bisect.insort += /2p http://fluentpython.com += /2v https://docs.python.org/pt-br/3/howto/sorting.html += /2r https://docs.python.org/pt-br/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching += /2s https://docs.python.org/pt-br/3.10/whatsnew/3.10.html += /2w https://docs.python.org/pt-br/3/library/collections.html += /2x https://docs.python.org/pt-br/3/reference/datamodel.html#objects-values-and-types += /2y https://pt.wikipedia.org/wiki/Timsort diff --git a/links/cap02-urls.txt b/links/cap02-urls.txt new file mode 100644 index 00000000..2c649ada --- /dev/null +++ b/links/cap02-urls.txt @@ -0,0 +1,14 @@ +http://fluentpython.com +https://docs.python.org/pt-br/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +https://docs.python.org/pt-br/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +https://docs.python.org/pt-br/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +https://docs.python.org/pt-br/3.10/whatsnew/3.10.html +http://fluentpython.com +https://docs.python.org/pt-br/3/library/bisect.html#bisect.insort +http://fluentpython.com +https://docs.python.org/pt-br/3/howto/sorting.html +https://docs.python.org/pt-br/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +https://docs.python.org/pt-br/3.10/whatsnew/3.10.html +https://docs.python.org/pt-br/3/library/collections.html +https://docs.python.org/pt-br/3/reference/datamodel.html#objects-values-and-types +https://pt.wikipedia.org/wiki/Timsort diff --git a/links/data/README.txt b/links/data/README.txt new file mode 100644 index 00000000..204025c1 --- /dev/null +++ b/links/data/README.txt @@ -0,0 +1 @@ +data files for testing, used by pytest-datadir diff --git a/links/data/sample.htaccess b/links/data/sample.htaccess new file mode 100644 index 00000000..195e07d7 --- /dev/null +++ b/links/data/sample.htaccess @@ -0,0 +1,22 @@ +ErrorDocument 404 /404.html + +# main resources +RedirectTemp /book https://www.oreilly.com/.../9781492056348/ +RedirectTemp /home https://www.fluentpython.com/ # extra content site + +# duplicate targets +RedirectTemp /1-20 https://www.fluentpython.com/ +RedirectTemp /ora https://www.oreilly.com/.../9781492056348/ +RedirectTemp /2-10 http://example.com/ +RedirectTemp /10-2 http://example.com/ + +# appended 2025-06-17 19:05:20 +RedirectTemp /22 https://docs.python.org/pt-br/3/reference/lexical_analysis.html#reserved-classes-of-identifiers +RedirectTemp /23 https://docs.python.org/pt-br/3/library/doctest.html +RedirectTemp /24 https://docs.python.org/pt-br/3.10/library/string.html#format-string-syntax +RedirectTemp /25 https://docs.python.org/pt-br/3/library/stdtypes.html#truth +RedirectTemp /26 https://docs.python.org/pt-br/3/tutorial/controlflow.html#unpacking-argument-lists +RedirectTemp /27 https://docs.python.org/pt-br/3/reference/datamodel.html +RedirectTemp /28 https://dabeaz.com/per.html +RedirectTemp /29 https://mitpress.mit.edu/books/art-metaobject-protocol +RedirectTemp /2a https://plone.org.br/ diff --git a/links/deploy-fpy.sh b/links/deploy-fpy.sh new file mode 100755 index 00000000..ced9bd9e --- /dev/null +++ b/links/deploy-fpy.sh @@ -0,0 +1,2 @@ +#!/bin/bash +scp FPY.LI.htaccess dh_i4p2ka@fpy.li:~/fpy.li/.htaccess diff --git a/links/encurtar.ipynb b/links/encurtar.ipynb new file mode 100644 index 00000000..0e3de937 --- /dev/null +++ b/links/encurtar.ipynb @@ -0,0 +1,149 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "63ff70d0-34e4-426b-a455-4486922b0a75", + "metadata": {}, + "source": [ + "# Processo para encurtar URLS" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "565ac98b-4f9e-4fd7-b23d-7bddcf2dd60a", + "metadata": {}, + "outputs": [], + "source": [ + "from list_urls import find_urls" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0f2ce1b8-1c45-41a0-9f38-675e6912d805", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "0 directives appended to FPY.LI.htaccess\n" + ] + } + ], + "source": [ + "CAP = 17\n", + "\n", + "adoc_path = f'../vol3/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": 5, + "id": "37c13b84-c4a4-4295-8987-205c8da7a2e5", + "metadata": {}, + "outputs": [], + "source": [ + "for line in redirects:\n", + " print(line)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "25b20f6b-471f-4a14-bdd0-22eb21627962", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'pairs_path' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[4]\u001b[39m\u001b[32m, line 6\u001b[39m\n\u001b[32m 3\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mreplace_urls\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m main\n\u001b[32m 5\u001b[39m pairs_file = io.StringIO(\u001b[33m'\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33m'\u001b[39m.join(redirects))\n\u001b[32m----> \u001b[39m\u001b[32m6\u001b[39m \u001b[43mmain\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpairs_file\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43madoc_path\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/flupy/pythonfluente2e/links/replace_urls.py:13\u001b[39m, in \u001b[36mmain\u001b[39m\u001b[34m(pairs_file, adoc_path)\u001b[39m\n\u001b[32m 10\u001b[39m \u001b[38;5;28;01massert\u001b[39;00m pair[\u001b[32m0\u001b[39m].startswith(\u001b[33m'\u001b[39m\u001b[33m/\u001b[39m\u001b[33m'\u001b[39m), \u001b[33mf\u001b[39m\u001b[33m'\u001b[39m\u001b[33mno path: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mline\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m\n\u001b[32m 11\u001b[39m pairs.append(pair)\n\u001b[32m---> \u001b[39m\u001b[32m13\u001b[39m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(pairs) > \u001b[32m0\u001b[39m, \u001b[33mf\u001b[39m\u001b[33m'\u001b[39m\u001b[33mno pairs found in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[43mpairs_path\u001b[49m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m\n\u001b[32m 15\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mopen\u001b[39m(adoc_path) \u001b[38;5;28;01mas\u001b[39;00m fp:\n\u001b[32m 16\u001b[39m adoc = fp.read()\n", + "\u001b[31mNameError\u001b[39m: name 'pairs_path' is not defined" + ] + } + ], + "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": null, + "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 new file mode 100755 index 00000000..d3989525 --- /dev/null +++ b/links/list_urls.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +import fileinput +import re + +URL_RE = re.compile(r"""(https?://[^\s[<>"']+)\[""") + + +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 + 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__': + multi_find_urls(fpy=False) diff --git a/links/replace_urls.py b/links/replace_urls.py new file mode 100755 index 00000000..3baedb0e --- /dev/null +++ b/links/replace_urls.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +import sys + +def main(pairs_file, adoc_path): + pairs = [] + 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_path}' + + with open(adoc_path) as fp: + adoc = fp.read() + + initial_adoc = adoc + + replaced = set() + + for path, url in pairs: + if url in replaced: + continue + assert url in adoc, f'{url} not found in {adoc_path}' + print(path, url) + # 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_path}: {len(initial_adoc)=} {len(adoc)=}' + + with open(adoc_path, 'w') as fp: + fp.write(adoc) + + +if __name__ == '__main__': + 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/sample-urls.txt b/links/sample-urls.txt new file mode 100644 index 00000000..9eac47c0 --- /dev/null +++ b/links/sample-urls.txt @@ -0,0 +1,47 @@ +https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +https://dask.org/ +http://example.com/1572039572038573208 +http://www.unicode.org/ +https://www.techcrunch.com/2024/startup-funding-trends +https://blog.medium.com/writing-tips-for-beginners +https://github.com/microsoft/typescript +https://stackoverflow.com/questions/javascript-async-await +https://www.reddit.com/r/programming/hot +https://docs.google.com/spreadsheets/create +https://www.youtube.com/watch?v=dQw4w9WgXcQ +https://www.amazon.com/dp/B08N5WRWNW +https://support.apple.com/iphone-setup-guide +https://www.wikipedia.org/wiki/Machine_Learning +https://www.linkedin.com/in/johndoe123 +https://www.instagram.com/p/CxYz123AbC/ +https://twitter.com/elonmusk/status/1234567890 +https://www.facebook.com/events/987654321 +https://drive.google.com/file/d/1AbCdEfGhIjKlMnOp/view +https://www.dropbox.com/s/qwerty123/document.pdf +https://zoom.us/j/1234567890?pwd=abcdef +https://calendly.com/janedoe/30min-meeting +https://www.shopify.com/admin/products/new +https://stripe.com/docs/api/charges/create +https://www.paypal.com/invoice/create +https://mailchimp.com/campaigns/dashboard +https://analytics.google.com/analytics/web/ +https://console.aws.amazon.com/s3/buckets +https://portal.azure.com/dashboard +https://www.figma.com/file/AbCdEf123456/design-system +https://www.notion.so/workspace/project-notes +https://trello.com/b/AbCdEfGh/marketing-board +https://slack.com/app_redirect?channel=general +https://discord.gg/AbCdEfGh123 +https://www.twitch.tv/streamername/videos +https://www.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M +https://www.netflix.com/browse/genre/83 +https://www.hulu.com/series/breaking-bad-2008 +https://www.airbnb.com/rooms/12345678 +https://www.booking.com/hotel/us/grand-plaza.html +https://www.expedia.com/flights/search?trip=roundtrip +https://www.uber.com/ride/request +https://www.doordash.com/store/pizza-palace-123 +https://www.grubhub.com/restaurant/tacos-el-rey-456 +https://www.zillow.com/homes/for_sale/San-Francisco-CA +https://www.craigslist.org/about/sites +https://www.python.org/dev/peps/pep-0484/ \ No newline at end of file diff --git a/links/shorten.py b/links/shorten.py new file mode 100644 index 00000000..4e4e49c6 --- /dev/null +++ b/links/shorten.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 + +""" +short.py generates unique short URLs. + +This script reads lines from stdin or files named as arguments, then: + +1. retrieves or creates new short URLs, taking into account existing RedirectTemp + directives in custom.htaccess or short.htaccess; +2. appends RedirectTemp directives for newly created short URLs to short.htaccess; +3. outputs the list of (short, long) URLs retrieved or created. + +""" + +import fileinput +import itertools +from collections.abc import Iterator +from time import strftime +from typing import NamedTuple + +HTACCESS_MAIN = 'FPY.LI.htaccess' +HTACCESS_SHORT = 'FPY.LI.short.htaccess' +HTACCESS_FILES = (HTACCESS_MAIN, HTACCESS_SHORT) +BASE_DOMAIN = 'fpy.li' + +type ShortCode = bytes +type Url = str +type RedirMap = dict[ShortCode, Url] +type TargetMap = dict[Url, ShortCode] + + +class ShortPair(NamedTuple): + code: ShortCode + url: Url + + +def load_redirects() -> tuple[RedirMap, TargetMap]: + redirects: RedirMap = {} + targets: TargetMap = {} + for filename in HTACCESS_FILES: + with open(filename) as fp: + for line in fp: + if line.startswith('RedirectTemp'): + _, field1, long, *_ = line.split() + short = field1.encode('ascii')[1:] # Remove leading slash + assert short not in redirects, f'{filename}: duplicate redirect from {short}' + # htaccess.custom is live since 2022, I can't change it to remove duplicate targets + # if filename != HTACCESS_MAIN: + # assert long not in targets, f'{filename}: duplicate redirect to {long}' + if long in targets: + print(f'{filename}: duplicate redirect to {long}') + redirects[short] = long + targets[long] = short + + return redirects, targets + + +SDIGITS = b'23456789abcdefghjkmnpqrstvwxyz' + + +def gen_short(start_len=2) -> Iterator[ShortCode]: + """Generate every possible sequence of SDIGITS, starting with start_len""" + length = start_len + while True: + for short in itertools.product(SDIGITS, repeat=length): + yield bytes(short) + length += 1 + + +def gen_unused_short(redirects: dict, start_len=2) -> Iterator[ShortCode]: + """Generate next available short URL of len >= start_len.""" + for short in gen_short(start_len): + if short not in redirects: + yield short + + +def shorten(urls: list[str]) -> list[ShortPair]: + """Return (short, long) pairs, appending directives to HTACCESS_SHORT as needed.""" + redirects, targets = load_redirects() + iter_short = gen_unused_short(redirects) + pairs = [] + timestamp = strftime('%Y-%m-%d %H:%M:%S') + with open(HTACCESS_SHORT, 'a') as fp: + for long in urls: + assert BASE_DOMAIN not in long, f'{long} is a {BASE_DOMAIN} URL' + if long in targets: + short = targets[long] + else: + short = next(iter_short) + redirects[short] = long + targets[long] = short + if timestamp: + fp.write(f'\n# appended: {timestamp}\n') + timestamp = None + fp.write(f'RedirectTemp /{short.decode("ascii")} {long}\n') + pairs.append((short, long)) + + return pairs + + +def main() -> None: + """read URLS from filename arguments or stdin""" + urls = [line.strip() for line in fileinput.input(encoding='utf-8')] + for pair in shorten(urls): + short = pair.code.decode('ascii') + print(f'{BASE_DOMAIN}/{short}\t{pair.url}') + + +if __name__ == '__main__': + # main() + load_redirects() diff --git a/links/shortener.py b/links/shortener.py new file mode 100755 index 00000000..ee11f80d --- /dev/null +++ b/links/shortener.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 + +""" +# URL shortener for .htaccess redirects + +This script reads a `.htaccess` file and a plain text file with +URLs (the target URLs). + +It outputs a list of target URLs and corresponding paths +for short URLs in the FPY.LI domain like `/2d`, `/2e`, etc. +This list is used to replace the target URLs with short URLs +in the `.adoc` files where the target URLs are used. + +If a target URL is not in the `.htaccess` file, +the script generates a new short URL +and adds a new `RedirectTemp` directive to the `.htaccess` file, +appending it in place with a timestamp. + +## Redirects in memory + +The `redirects` dict maps short paths to target URLs. +It's loaded from data in the `.htaccess` file. + +## Targets in memory + +The `targets` dict maps target URLs to short paths. +It's also computed from data in the `.htaccess` file, +but the algorithm is more complicated. + +The same target URL can be mapped to multiple short paths +in `.htaccess` if the same target URL was added more +than once with different short paths by mistake. +We cannot fix these mistakes because the redundant +short paths are printed in Fluent Python Second Edition. + +When loading the `.htaccess` file, +if a target URL is already in the `targets` dict, +we compare the existing short path with the new one +and save the shorter one in the `targets` dict. + +That way, we ensure that the shortest path is used for each target URL +in the list of replacements to apply to the `.adoc` files. + + +## Shortening URLs + +The `targets` dict maps target URLs to short paths. + +To shorten a target URL, find it in the `targets` dict. +If the target URL is found: + use the existing short path. +If the target URL is not found: + generate a new short path; + store target and path in both `targets` and `redirects` dicts; + collect new short path and target URL in a `new_redirects` list + to be appended to the `.htaccess` file at the end of the process. + +""" + +import itertools +import sys +from collections.abc import Iterable, Iterator +from typing import NamedTuple, TextIO +from datetime import datetime + +DO_NOT_SHORTEN = { + 'http://pythonfluente.com', + 'http://fluentpython.com', + 'http://oreilly.com', +} + + +class PathURL(NamedTuple): + path: str + url: str + new: bool = False + + +def parse_htaccess(text: str) -> Iterator[PathURL]: + for line in text.splitlines(): + fields = line.split() + if len(fields) >= 3 and fields[0] == 'RedirectTemp': + path = fields[1] + assert path[0] == '/', f'Missing /: {path!r}' + path = path[1:] # Remove leading slash + assert len(path) > 0, f'Root path in line {line!r}' + yield PathURL(path, fields[2]) + + +def choose(a: str, b: str) -> str: + def key(k: str) -> tuple[int, bool, list[str]]: + parts = k.split('-') + if len(parts) > 1: + parts = [(f'z{p:>08}' if p.isnumeric() else p) for p in parts] + return len(k), '-' in k, parts + + return min(a, b, key=key) + + +def load_redirects(pairs: Iterable[PathURL]) -> tuple[dict, dict]: + redirects = {} + targets = {} + for path, url, _new in pairs: + url = redirects.setdefault(path, url) + existing_path = targets.get(url) + if existing_path is None: + targets[url] = path + else: + targets[url] = choose(path, existing_path) + + return redirects, targets + + +NO_PATH = '' + + +def shorten_one(target: str, path_gen: Iterator[str], redirects: dict, targets: dict) -> PathURL: + if path := targets.get(target, NO_PATH): + return PathURL(path, target, False) + path = next(path_gen) + redirects[path] = target + targets[target] = path + return PathURL(path, target, True) + + +def timestamp(): + return datetime.now().isoformat(sep=' ', timespec='seconds') + + +def update_htaccess(f: TextIO, directives: list[PathURL]) -> int: + """append new redirects, returns count of new redirects""" + directives = [d for d in directives if d.new] + if directives: + f.write(f'\n# appended {timestamp()}\n') + for path, url, _new in directives: + f.write(f'RedirectTemp /{path}\t{url}\n') + return len(directives) + + +SDIGITS = '23456789abcdefghjkmnpqrstvwxyz' + + +def gen_short(start_len=1) -> Iterator[str]: + """Generate every possible sequence of SDIGITS, starting with start_len""" + length = start_len + while True: + for digits in itertools.product(SDIGITS, repeat=length): + yield ''.join(digits) + length += 1 + + +def gen_unused_short(redirects: dict) -> Iterator[str]: + """Generate next available short URL of len >= 2.""" + for short in gen_short(2): + if short not in redirects: + yield short + + +def main(htaccess_path, urls): + with open(htaccess_path) as f: + hta = f.read() + assert 'RedirectTemp' in hta, 'No RedirecTemp in {htaccess_path}' + + 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: + continue + path_url = shorten_one(url, path_gen, redirects, targets) + path_urls.append(path_url) + path, url, new = path_url + flag = '+' if new else '=' + 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__': + 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/links/test_shortener.py b/links/test_shortener.py new file mode 100644 index 00000000..4f6dea05 --- /dev/null +++ b/links/test_shortener.py @@ -0,0 +1,158 @@ +import io +from unittest import mock + +from pytest import mark + +import shortener # for mocking timestamp() +from shortener import parse_htaccess, choose, load_redirects +from shortener import gen_short, gen_unused_short, shorten_one, PathURL +from shortener import update_htaccess + + +SAMPLE_HTACCESS = """ +ErrorDocument 404 /404.html + +# main resources +RedirectTemp /book https://www.oreilly.com/.../9781492056348/ +RedirectTemp /home https://www.fluentpython.com/ # extra content site + +# duplicate targets +RedirectTemp /1-20 https://www.fluentpython.com/ +RedirectTemp /ora https://www.oreilly.com/.../9781492056348/ +RedirectTemp /2-10 http://example.com/ +RedirectTemp /10-2 http://example.com/ + +# shortened +RedirectTemp /22 http://firstshortened.co +""" + +FROZEN_TIME = '2025-06-07 01:02:03' + +UPDATED_SAMPLE_HTACCESS = ( + SAMPLE_HTACCESS + + f""" +# appended {FROZEN_TIME} +RedirectTemp /23 https://new.site/ +RedirectTemp /24 https://other.new.site/ +""" +) + + +PARSED_SAMPLE_HTACCESS = [ + PathURL('book', 'https://www.oreilly.com/.../9781492056348/'), + PathURL('home', 'https://www.fluentpython.com/'), + PathURL('1-20', 'https://www.fluentpython.com/'), + PathURL('ora', 'https://www.oreilly.com/.../9781492056348/'), + PathURL('2-10', 'http://example.com/'), + PathURL('10-2', 'http://example.com/'), + PathURL('22', 'http://firstshortened.co'), +] + +# straightforward mapping of .htaccess; some targets may be duplicated. +SAMPLE_REDIRECTS = { + 'home': 'https://www.fluentpython.com/', + '1-20': 'https://www.fluentpython.com/', + '2-10': 'http://example.com/', + '10-2': 'http://example.com/', + 'book': 'https://www.oreilly.com/.../9781492056348/', + 'ora': 'https://www.oreilly.com/.../9781492056348/', + '22': 'http://firstshortened.co', +} + +# the value must be shortest path for that target in the .htaccess +SAMPLE_TARGETS = { + 'https://www.fluentpython.com/': 'home', + 'https://www.oreilly.com/.../9781492056348/': 'ora', + 'http://example.com/': '2-10', + 'http://firstshortened.co': '22', +} + + +def test_parse_htaccess(): + res = list(parse_htaccess(SAMPLE_HTACCESS)) + assert res == PARSED_SAMPLE_HTACCESS + + +@mark.parametrize( + 'a,b,expected', + [ + ('a', 'b', 'a'), + ('b', 'a', 'a'), + ('aa', 'a', 'a'), + ('a-a', 'aaa', 'aaa'), + ('2-10', '10-2', '2-10'), + ('p-1', '1-1', 'p-1'), + ], +) +def test_choose(a, b, expected): + res = choose(a, b) + assert res == expected + + +def test_load_redirects(): + redirects, _ = load_redirects(PARSED_SAMPLE_HTACCESS) + assert redirects == SAMPLE_REDIRECTS + + +def test_load_redirect_targets(): + _, targets = load_redirects(PARSED_SAMPLE_HTACCESS) + assert targets == SAMPLE_TARGETS + + +@mark.parametrize( + 'target,path,new', + [ + ('https://www.fluentpython.com/', 'home', False), + ('https://new.site/', '23', True), + ], +) +def test_shorten_one(target, path, new): + expected = PathURL(path, target, new) + redirects = dict(SAMPLE_REDIRECTS) + targets = dict(SAMPLE_TARGETS) + result = shorten_one(target, gen_unused_short(redirects), redirects, targets) + assert result == expected + updated = redirects.keys() - SAMPLE_REDIRECTS.keys() + if new: + assert len(updated) == 1 + new_path = updated.pop() + assert new_path == path + assert redirects == SAMPLE_REDIRECTS | {new_path: target} + assert targets == SAMPLE_TARGETS | {target: new_path} + else: + assert len(updated) == 0 + assert redirects == SAMPLE_REDIRECTS + assert targets == SAMPLE_TARGETS + + +def test_timestamp(): + with mock.patch('shortener.timestamp', return_value=FROZEN_TIME): + assert shortener.timestamp() == FROZEN_TIME + + +def test_update_htaccess(): + directives = [ + PathURL('home', 'https://www.fluentpython.com/', False), + PathURL('23', 'https://new.site/', True), + PathURL('24', 'https://other.new.site/', True) + ] + htac = io.StringIO(SAMPLE_HTACCESS) + htac.seek(0, io.SEEK_END) # emulate append mode 'a+' + with mock.patch('shortener.timestamp', return_value=FROZEN_TIME): + res = update_htaccess(htac, directives) + assert res == 2 + assert htac.getvalue() == UPDATED_SAMPLE_HTACCESS + + +def test_gen_short(): + expected = '222 223 224 225 226 227 228 229 22a 22b'.split() + gen = gen_short(3) + res = [next(gen) for _ in range(10)] + assert res == expected + + +def test_gen_unused_short(): + redirects = {'22': 'u1', '23': 'u2', '25': 'u4'} + gen = gen_unused_short(redirects) + assert next(gen) == '24' + assert next(gen) == '26' diff --git a/online/.gitignore b/online/.gitignore new file mode 100644 index 00000000..b867a8fe --- /dev/null +++ b/online/.gitignore @@ -0,0 +1,2 @@ +# Páginas renderizadas +*.html \ No newline at end of file diff --git a/online/Livro-vol1-sem-cap04.adoc b/online/Livro-vol1-sem-cap04.adoc new file mode 100644 index 00000000..955aa3b6 --- /dev/null +++ b/online/Livro-vol1-sem-cap04.adoc @@ -0,0 +1,45 @@ += Python Fluente, 2ª Edição (2023) +:doctype: book +:author: Luciano Ramalho +:lang: pt_BR +:language: asciidoctor +:source-highlighter: rouge +include::../atributos-pt_BR.adoc[] +:xrefstyle: short +:sectnumlevels: 4 +:sectlinks: +:data-uri: +:toc: left +:toclevels: 2 +:!chapter-signifier: +:front-cover-image: image:cover.jpg[fit=cover] +// Substituições +:dunder: __ + +:sectnums!: +include::Prefacio.adoc[] + +[[data_structures_part]] += Parte I: Estruturas de dados +:sectnums: +:example-number: 0 +:figure-number: 0 + +include::cap01.adoc[] + +include::cap02.adoc[] + +include::cap03.adoc[] + +// include::cap04.adoc[] + +include::cap05.adoc[] + +include::cap06.adoc[] + +[[function_objects_part]] += Parte II: Funções como objetos + +include::cap07.adoc[] + +include::cap08.adoc[] diff --git a/online/Livro.adoc b/online/Livro.adoc new file mode 100644 index 00000000..05f3980f --- /dev/null +++ b/online/Livro.adoc @@ -0,0 +1,88 @@ += Python Fluente, 2ª Edição (2023) +:doctype: book +:author: Luciano Ramalho +:lang: pt_BR +:language: asciidoctor +:source-highlighter: rouge +include::../atributos-pt_BR.adoc[] +:xrefstyle: short +:sectnumlevels: 4 +:sectlinks: +:data-uri: +:toc: left +:toclevels: 2 +:!chapter-signifier: +:icons: font +// Substituições +:dunder: __ + +:sectnums!: +include::Prefacio.adoc[] + +[[data_structures_part]] += Parte I: Estruturas de dados +:sectnums: +:example-number: 0 +:figure-number: 0 + +include::cap01.adoc[] + +include::cap02.adoc[] + +include::cap03.adoc[] + +include::cap04.adoc[] + +include::cap05.adoc[] + +include::cap06.adoc[] + +[[function_objects_part]] += Parte II: Funções como objetos + +include::cap07.adoc[] + +include::cap08.adoc[] + +include::cap09.adoc[] + +include::cap10.adoc[] + +[[classes_protocols_part]] += Parte III: Classes e Protocolos + +include::cap11.adoc[] + +include::cap12.adoc[] + +include::cap13.adoc[] + +include::cap14.adoc[] + +include::cap15.adoc[] + +include::cap16.adoc[] + +[[control_flow_part]] += Parte IV: Controle de fluxo + +include::cap17.adoc[] + +include::cap18.adoc[] + +include::cap19.adoc[] + +include::cap20.adoc[] + +include::cap21.adoc[] + +[[metaprog_part]] += Parte V: Metaprogramação + +include::cap22.adoc[] + +include::cap23.adoc[] + +include::cap24.adoc[] + +include::Posfacio.adoc[] diff --git a/Posfacio.adoc b/online/Posfacio.adoc similarity index 89% rename from Posfacio.adoc rename to online/Posfacio.adoc index 387edd62..10fcea1c 100644 --- a/Posfacio.adoc +++ b/online/Posfacio.adoc @@ -5,13 +5,13 @@ [quote, Alan Runyan, co-fundador do Plone] ____ -O Python é uma linguagem para programação consensual entre adultos. +Python é uma linguagem para programação consensual entre adultos. ____ -A((("Python", "community support for"))) definição sagaz do Alan expressa uma das melhores qualidades do Python: ele sai da frente e deixa você fazer o que for preciso. Isso também significa que a linguagem não dá ferramentas para restringir o que outros podem fazer com seu código e com os objetos que ele cria. +A((("Python", "community support for"))) definição sagaz do Alan expressa uma das melhores qualidades de Python: ele sai da frente e deixa você fazer o que for preciso. Isso também significa que a linguagem não dá ferramentas para restringir o que outros podem fazer com seu código e com os objetos que ele cria. -Aos 30, o Python segue ganhando popularidade. -Mas, claro, não é perfeito. +Aos 30, Python segue ganhando popularidade. +Mas, claro, não é perfeito. Entre minhas maiores irritações está o uso inconsistente do `CamelCase` footnote:[NT: termos com maiúsculas no início de cada palavra de uma expressão composta.], do `snake_case` footnote:[NT: termos com hífens separando palavras de uma expressão composta], e `joinedwords` footnote:[NT: composição direta de palavras] na biblioteca padrão. @@ -25,7 +25,7 @@ Isso não se passou em um clube exclusivo. Qualquer pessoa pode se juntar à lis Outro exemplo de abertura: a((("Python Software Foundation (PSF)"))) Python Software Foundation (PSF) tem trabalhado para aumentar a diversidade na comunidade Python. Alguns resultados encorajadores já apareceram. A diretoria para 2013–2014 da PSF viu as primeiras diretoras eleitas: Jessica McKellar e Lynn Root. Em 2015, Diana Clarke presidiu a PyCon North America em Montreal, onde cerca de um terço dos palestrantes foram mulheres. A PyLadies((("PyLadies"))) se tornou um movimento verdadeiramente global, e me orgulha que tenhamos tantas seções da PyLadies no Brasil. -Se você é um pythonista mas ainda não se envolveu com a comunidade, encorajo você a fazê-lo. Procure a PyLadies ou((("Python Users Group (PUG)"))) um Grupo de Usuários Python na sua vizinhança. Se nenhum existir, crie um. O Python está em todo lugar, então você não ficará sozinho. Viaje para eventos, se puder. Junte-se também a eventos online. Durante a pandemia de Covid-19, aprendi muito nos "encontros no hall" das conferências online. +Se você é um pythonista mas ainda não se envolveu com a comunidade, encorajo você a fazê-lo. Procure a PyLadies ou((("Python Users Group (PUG)"))) um Grupo de Usuários Python na sua vizinhança. Se nenhum existir, crie um. Python está em todo lugar, então você não ficará sozinho. Viaje para eventos, se puder. Junte-se também a eventos online. Durante a pandemia de Covid-19, aprendi muito nos "encontros no hall" das conferências online. Venha a uma conferência da PythonBrasil--temos a presença de palestrantes estrangeiros há anos. Encontrar outros pythonistas traz benefícios reais além do compartilhamento de conhecimento. Como empregos reais e amizades reais. @@ -47,13 +47,13 @@ Brandon Rhodes é um fantático professor de Python, e sua palestra https://fpy. A thread https://fpy.li/a-5["Evolution of Style Guides" (_A Evolução dos Guias de Estilo_)] (EN), iniciada por Ian Lee no Python-ideas, vale a leitura. Lee é o mantenedor do pacote https://fpy.li/a-6[`pep8`], que verifica código Python quanto à aderência ao PEP 8. Para verificar o código deste livro usei o https://fpy.li/a-7[`flake8`], que inclui o `pep8`, https://fpy.li/a-8[`pyflakes`], e o https://fpy.li/a-9[McCabe complexity plug-in (_plug-in de complexidade McCabe_ ou _complexidade ciclomática_)], de Ned Batchelder. -Além do PEP 8, outros influentes guias de estilo são o https://fpy.li/a-10[_Google Python Style Guide_ ("Guia de Estilo Python do Google")] e o +Além do PEP 8, outros influentes guias de estilo são o https://fpy.li/a-10[_Google Python Style Guide_ ("Guia de Estilo Python do Google")] e o https://fpy.li/a-11[_Pocoo Styleguide_ ("Guia de Estilo Pocoo")], do mesmo grupo que nos deu o Flake, o Sphinx, a Jinja 2 e outras ótimas bibliotecas Python. -https://fpy.li/a-12[_The Hitchhiker’s Guide to Python!_ ("O Guia do Mochileiro Python")] é um esforço coletivo sobre a escrita de código pythônico. Seu contribuidor mais prolífico é Kenneth Reitz, um herói da comunidade devido a seu maravilhoso e pythônico pacote `requests`. David Goodger apresentou um tutorial na PyCon US 2008 intitulado https://fpy.li/a-13["Code Like a Pythonista: Idiomatic Python" (_Programe como um Pythonista: Python Idiomático_)]. Se impressas, as notas do tutorial tem 30 páginas. Goodger criou tanto reStructuredText quanto ++docutils++—as bases do Sphinx, o excelente sistema de documentação do Python (que, por sinal, também é o https://fpy.li/a-14[sistema oficial de documentação] do MongoDB e de muitos outros projetos). +https://fpy.li/a-12[_The Hitchhiker’s Guide to Python!_ ("O Guia do Mochileiro Python")] é um esforço coletivo sobre a escrita de código pythônico. Seu contribuidor mais prolífico é Kenneth Reitz, um herói da comunidade devido a seu maravilhoso e pythônico pacote `requests`. David Goodger apresentou um tutorial na PyCon US 2008 intitulado https://fpy.li/a-13["Code Like a Pythonista: Idiomatic Python" (_Programe como um Pythonista: Python Idiomático_)]. Se impressas, as notas do tutorial tem 30 páginas. Goodger criou tanto reStructuredText quanto ++docutils++—as bases do Sphinx, o excelente sistema de documentação de Python (que, por sinal, também é o https://fpy.li/a-14[sistema oficial de documentação] do MongoDB e de muitos outros projetos). Martijn Faassen enfrenta a questão diretamente em https://fpy.li/a-15["What is Pythonic?" (_O que é pythônico?_)] Na python-list há uma thread com https://fpy.li/a-16[o mesmo título]. O post de Martijn é de 2005, e a thread de 2003, mas o ideal pythônico não mudou muito—e aliás, nem a própria linguagem mudou tanto. Uma ótima thread com "pythônico" no título é https://fpy.li/a-17["Pythonic way to sum n-th list element?" (_A forma pythônica de somar os "n" elementos de uma lista_)], que citei extensivamente no <>. -A https://fpy.li/pep3099[PEP 3099 -- Things that will Not Change in Python 3000 (_PEP 3099 -- Coisas que não vão mudar no Python 3000_)] explica porque muitas coisas são como são, mesmo após uma revisão profunda como foi o Python 3. Por muito tempo, o Python 3 foi apelidado Python 3000, mas acabou chegando alguns séculos adiantado--para o desespero de alguns. A PEP 3099 foi escrita por Georg Brandl, compilando muitas opiniões expressas pelo _BDFL_ Guido van Rossum. A página https://fpy.li/a-18["Python Essays" (_Ensaios sobre Python_)] lista vários textos do próprio Guido. +A https://fpy.li/pep3099[PEP 3099 -- Things that will Not Change in Python 3000 (_PEP 3099 -- Coisas que não vão mudar no Python 3000_)] explica porque muitas coisas são como são, mesmo após uma revisão profunda como foi Python 3. Por muito tempo, Python 3 foi apelidado Python 3000, mas acabou chegando alguns séculos adiantado--para o desespero de alguns. A PEP 3099 foi escrita por Georg Brandl, compilando muitas opiniões expressas pelo _BDFL_ Guido van Rossum. A página https://fpy.li/a-18["Python Essays" (_Ensaios sobre Python_)] lista vários textos do próprio Guido. diff --git a/Prefacio.adoc b/online/Prefacio.adoc similarity index 74% rename from Prefacio.adoc rename to online/Prefacio.adoc index 4c3dc2ed..67ed8b05 100644 --- a/Prefacio.adoc +++ b/online/Prefacio.adoc @@ -1,43 +1,33 @@ -:xrefstyle: short -:example-number: 0 -:figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte - -[dedication] +[[dedication]] __Para Marta, com todo o meu amor.__ -[preface] +[[preface]] == Prefácio -[quote, Tim Peters, lendário colaborador do CPython e autor do Zen do Python] +[quote, Tim Peters, lendário colaborador do CPython e autor do Zen de Python] ____ -Eis um plano: se uma pessa usar um recurso que você não entende, mate-a. +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 do Python 0.9.6 .footnote:[Mensagem para o grupo da Usenet comp.lang.python em 23 de dezembro de 2002: https://fpy.li/p-1["Acrimony in c.l.p"] (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: https://fpy.li/p-1["Acrimony in c.l.p"] (EN).] ____ -"Python é uma linguagem fácil de aprender e poderosa." Essas((("Python", "appreciating language-specific features"))) são as primeiras palavras do https://fpy.li/p-2[tutorial oficial do Python 3.10]. -Isso é verdade, mas há uma pegadinha: como a linguagem é fácil de entender e de começar a usar, muitos programadores praticantes do Python se contentam apenas com uma fração de seus poderosos recursos. +"Python é uma linguagem fácil de aprender e poderosa." Essas((("Python", "appreciating language-specific features"))) são as primeiras palavras do https://fpy.li/p-2[tutorial oficial de Python 3.10]. +Isso é verdade, mas há uma pegadinha: como a linguagem é fácil de entender e de começar a usar, muitos programadores praticantes de Python se contentam apenas com uma fração de seus poderosos recursos. Uma programadora experiente pode começar a escrever código Python útil em questão de horas. Conforme as primeiras horas produtivas se tornam semanas e meses, muitos desenvolvedores continuam escrevendo código Python com um forte sotaque das linguagens que aprenderam antes. -Mesmo se o Python for sua primeira linguagem, muitas vezes ela é apresentada nas universidades e +Mesmo se Python for sua primeira linguagem, muitas vezes ela é apresentada nas universidades e em livros introdutórios evitando deliberadamente os recursos específicos da linguagem. Como professor, ensinando Python para programadores experientes em outras linguagens, vejo outro problema: só sentimos falta daquilo que conhecemos. -Vindo de outra linguagem, qualquer um é capaz de imaginar que o Python suporta expressões regulares, +Vindo de outra linguagem, qualquer um é capaz de imaginar que Python suporta expressões regulares, e procurar esse tema na documentação. Mas se você nunca viu desempacotamento de tuplas ou descritores de atributos, talvez nunca procure por eles, e pode acabar não usando esses recursos, só por que são novos para você. -Este livro não é uma referência exaustiva do Python de A a Z. -A ênfase está em recursos da linguagem característicos do Python +Este livro não é uma referência exaustiva de Python de A a Z. +A ênfase está em recursos da linguagem característicos de Python ou incomuns em outras linguagens populares. Vamos nos concentrar principalmente nos aspectos centrais da linguagem e pacotes essenciais da biblioteca padrão. Apenas alguns exemplos mostram o uso de pacotes externos como FastAPI, httpx, e Curio. @@ -45,15 +35,15 @@ Apenas alguns exemplos mostram o uso de pacotes externos como FastAPI, httpx, e === Para quem é esse livro -Escrevi este ((("Python", "versions featured"))) livro para programadores que já usam Python e +Escrevi este ((("Python", "versions featured"))) livro para programadores que já usam Python e desejem se tornar fluentes em Python 3 moderno. Testei os exemplos em Python 3.10—e a maioria também em Python 3.9 e 3.8. Os exemplos que exigem especificamente Python 3.10 estão indicados. Caso((("Python", "prerequisites to learning"))) não tenha certeza se conhece Python o suficiente para acompanhar o livro, -revise o -https://docs.python.org/pt-br/3.10/tutorial/[tutorial oficial do Python]. +revise o +https://fpy.li/4g[tutorial oficial de Python]. Tópicos tratados no tutorial não serão explicados aqui, exceto por alguns recursos mais novos. @@ -67,95 +57,95 @@ de métodos especiais e truques de metaprogramação. Abstração prematura é tão ruim quanto otimização prematura. Para quem está aprendendo a programar, recomendo o livro -https://penseallen.github.io/PensePython2e/[Pense em Python] de Allen Downey, disponível na Web. +https://fpy.li/4h[Pense em Python] de Allen Downey, disponível na Web. Se já sabe programar e está aprendendo Python, o -https://docs.python.org/pt-br/3.10/tutorial/[tutorial oficial do Python] foi traduzido +https://fpy.li/4g[tutorial oficial de Python] foi traduzido pela comunidade Python brasileira. === Como ler este livro -Recomendo((("Python", "approach to learning", id="Papproach00"))) que todos leiam o <>. -Após a leitura do capítulo "O modelo de dados do Python", +Recomendo((("Python", "approach to learning", id="Papproach00"))) que todos leiam o <>. +Após a leitura do capítulo "O modelo de dados de Python", o público principal deste livro não terá problema em pular diretamente para qualquer outra parte, -mas muitas vezes assumo que você leu os capítulos precendentes de cada parte específica. +mas muitas vezes assumo que você leu os capítulos precedentes de cada parte específica. Pense nas partes <> até a <> como cinco livros dentro do livro. Tentei enfatizar o uso de classes e módulos que já existem antes de discutir como criar seus próprios. -Por exemplo, na Parte <>, -o <> trata dos tipos de sequências que estão prontas para serem usadas, +Por exemplo, na <>, +o <> trata dos tipos de sequências que estão prontas para serem usadas, incluindo algumas que não recebem muita atenção, como `collections.deque`. Criar sequências definidas pelo usuário só é discutido na <>, onde também vemos como usar as classes base abstratas (ABCs) de `collections.abc`. Criar suas próprias ABCs é discutido ainda mais tarde, na <>, pois acredito na importância de estar confortável usando uma ABC antes de escrever uma. Essa abordagem tem algumas vantagens. -Primeiro, saber o que está disponivel para uso imediato pode evitar que você reinvente a roda. Usamos as classes de coleções existentes com mais frequência que implementamos nossas próprias coleções, e podemos prestar mais atenção ao uso avançado de ferramentas prontas adiando a discussão sobre a criação de novas ferramentas. -Também é mais provável herdamos de ABCs existentes que criar uma nova ABC do zero. +Primeiro, saber o que está disponivel para uso imediato pode evitar que você reinvente a roda. Usamos as classes de coleções existentes com mais frequência que implementamos nossas próprias coleções, e podemos prestar mais atenção ao uso avançado de ferramentas prontas, adiando a discussão sobre a criação de novas ferramentas. +Também é mais provável herdar de ABCs existentes que criar uma nova ABC do zero. E, finalmente, acredito ser mais fácil entender as abstrações após vê-las em ação. A desvantagem dessa estratégia são as referências a pontos futuros espalhadas pelo livro. -Acredito que isso é mais fácil de tolerar agora que você sabe porque escolhi esse caminho. +Espero que isso seja mais fácil de tolerar agora que você sabe porque escolhi esse caminho. -==== Cinco livros em um +==== Cinco livros em um Aqui estão os principais tópicos de cada parte do livro: -<>:: -O <> introduz o Modelo de Dados do Python e explica porque os métodos especiais (por exemplo, `+__repr__+`) são a chave do comportamento consistente de objetos de todos os tipos. Os métodos especiais são tratatos em maiores detalhes ao longo do livro. Os((("data structures"))) capítulos restantes dessa parte cobrem o uso de tipos coleção: sequências, mapeamentos e conjuntos, bem como a separação de `str` e `bytes`--causa de muitas celebrações entre usuários do Python 3, e de muita dor para usuários de Python 2 obrigados a migrar suas bases de código. Também são abordadas as fábricas de classe de alto nível na biblioteca padrão: fábricas de tuplas nomeadas e o decorador `@dataclass`. _Pattern matching_ ("Correspondência de padrões")—novidade no Python 3.10—é tratada em seções do <>, -do <> e -do <>, +<>:: +O <> introduz o Modelo de Dados de Python e explica porque os métodos especiais (por exemplo, `+__repr__+`) são a chave do comportamento consistente de objetos de todos os tipos. Os métodos especiais são tratados em mais detalhes ao longo do livro. Os((("data structures"))) capítulos restantes dessa parte cobrem o uso de tipos coleção: sequências, mapeamentos e conjuntos, bem como a separação de `str` e `bytes`--causa de muitas celebrações entre usuários de Python 3, e de muita dor para usuários de Python 2 obrigados a migrar suas bases de código. Também são abordadas as fábricas de classe de alto nível na biblioteca padrão: fábricas de tuplas nomeadas e o decorador `@dataclass`. _Pattern matching_ ("casamento de padrões")—novidade no Python 3.10—é tratada em seções do <>, +do <> e +do <>, que discutem padrões para sequências, padrões para mapeamentos e padrões para instâncias de classes. O último capítulo na <> versa sobre o ciclo de vida dos objetos: referências, mutabilidade e coleta de lixo (_garbage collection_). -<>:: Aqui((("functions, as first-class objects", "topics covered"))) falamos sobre funções como objetos de primeira classe na linguagem: o significado disso, como isso afeta alguns padrões de projetos populares e como aproveitar as clausuras para implementar decoradores de função. Também são vistos aqui o conceito geral de invocáveis no Python, atributos de função, introspecção, anotação de parâmetros e a nova declaração `nonlocal` no Python 3. O <> introduz um novo tópico importante, dicas de tipo em assinaturas de função. +<>:: Aqui((("functions, as first-class objects", "topics covered"))) falamos sobre funções como objetos de primeira classe na linguagem: o significado disso, como isso afeta alguns padrões de projetos populares e como aproveitar as clausuras para implementar decoradores de função. Também são vistos aqui o conceito geral de invocáveis no Python, atributos de função, introspecção, anotação de parâmetros e a nova declaração `nonlocal` no Python 3. O <> introduz um novo tópico importante, dicas de tipo em assinaturas de função. -<>:: Agora((("classes", "topics covered"))) o foco se volta para a criação "manual" de classes—em contraste com o uso de fábricas de classe vistas no <>. -Como qualquer linguagem orientada a objetos, Python tem seu conjunto particular de recursos que podem ou não estar presentes na linguagem na qual você ou eu aprendemos programação baseada em classes. Os capítulos explicam como criar suas próprias coleções, classes base abstratas (ABCs) e protocolos, bem como as formas de lidar com herança múltipla e como implementar a sobrecarga de operadores, quando fizer sentido.O <> continua a conversa sobre dicas de tipo. +<>:: Agora((("classes", "topics covered"))) o foco se volta para a criação "manual" de classes—em contraste com o uso de fábricas de classe vistas no <>. +Como qualquer linguagem orientada a objetos, Python tem seu conjunto particular de recursos que podem ou não estar presentes na linguagem na qual você ou eu aprendemos programação baseada em classes. Os capítulos explicam como criar suas próprias coleções, classes base abstratas (ABCs) e protocolos, bem como as formas de lidar com herança múltipla e como implementar a sobrecarga de operadores, quando fizer sentido. O <> continua a conversa sobre dicas de tipo. -<>:: Nesta((("control flow"))) parte são tratados os mecanismos da linguagem e as bibliotecas que vão além do controle de fluxo tradicional, com condicionais, laços e sub-rotinas. Começamos com os geradores, visitamos a seguir os gerenciadores de contexto e as corrotinas, incluindo a desafiadora mas poderosa sintaxe do `yield from`. O <> inclui um exemplo significativo, usando _pattern matching_ em um interpretador de linguagem simples mas funcional. O <> é novo, apresentando uma visão geral das alternativas para processamento concorrente e paralelo no Python, suas limitações, e como a arquitetura de software permite ao Python operar na escala da Web. Reescrevi o capítulo sobre _programação assíncrona_, para enfatizar os recursos centrais da linguagem—por exemplo, `await`, `async dev`, `async for` e `async with`, e mostrar como eles são usados com _asyncio_ e outras frameworks. +<>:: Nesta((("control flow"))) parte são tratados os mecanismos da linguagem e as bibliotecas que vão além do controle de fluxo tradicional, com condicionais, laços e sub-rotinas. Começamos com os geradores, visitamos a seguir os gerenciadores de contexto e as corrotinas, incluindo a desafiadora mas poderosa sintaxe do `yield from`. O <> inclui um exemplo significativo, usando _pattern matching_ em um interpretador de linguagem simples mas funcional. O <> é novo, apresentando uma visão geral das alternativas para processamento concorrente e paralelo no Python, suas limitações, e como a arquitetura de software permite ao Python operar na escala da Web. Reescrevi o capítulo sobre _programação assíncrona_, para enfatizar os recursos centrais da linguagem—por exemplo, `await`, `async def`, `async for` e `async with`, e mostrar como eles são usados com _asyncio_ e outros frameworks. <>:: Essa((("metaprogramming"))) parte começa com uma revisão de técnicas para criação de classes com atributos criados dinamicamente para lidar com dados semi-estruturados, tal como conjuntos de dados JSON. A seguir tratamos do mecanismo familiar das propriedades, antes de mergulhar no funcionamento do acesso a atributos de objetos no Python em um nível mais baixo, usando descritores. A relação entre funções, métodos e descritores é explicada. Por toda a <>, a implementação passo a passo de uma biblioteca de validação de campos revela questões sutis, levando às ferramentas avançadas do capítulo final: decoradores de classes e metaclasses. === Abordagem "mão na massa" -Frequentemente usaremos o console interativo do Python para explorar a linguagem e as bibliotecas. +Frequentemente usaremos o console interativo de Python para explorar a linguagem e as bibliotecas. Acho isso importante para enfatizar o poder dessa ferramenta de aprendizagem, especialmente para quem teve mais experiência com linguagens estáticas compiladas, que não oferecem um REPL.footnote:[_Read-Eval-Print Loop_, o nome acadêmico de um console interativo que funciona como um laço lendo código, avaliando, e exibindo resultados.] -Um dos pacotes padrão de testagem do Python, o https://fpy.li/doctest[`doctest`], funciona simulando sessões de console e verificando se as expressões resultam nas resposta exibidas. Usei `doctest` para verificar a maior parte do código desse livro, incluindo as listagens do console. +Um dos pacotes padrão de testagem de Python, o https://fpy.li/doctest[`doctest`], funciona simulando sessões de console e verificando se as expressões resultam nas resposta exibidas. Usei `doctest` para verificar a maior parte do código desse livro, incluindo as listagens do console. Não é necessário usar ou sequer saber da existência do `doctest` para acompanhar o texto: a principal característica dos _doctests_ é que eles imitam transcrições de sessões -interativas no console do Python, assim qualquer pessoa pode reproduzir as demonstrações facilmente. +interativas no console de Python, assim qualquer pessoa pode reproduzir as demonstrações facilmente. Algumas vezes vou explicar o que queremos realizar mostrando um _doctest_ antes do código que implementa a solução. Estabelecer precisamente o quê deve ser feito, antes de pensar sobre como fazer, ajuda a focalizar nosso esforço de codificação. Escrever os testes previamente é a base de desenvolvimento dirigido por testes (TDD, _test-driven development_), e também acho essa técnica útil para ensinar. -Também((("pytest package")))((("unittest module"))) escrevi testes de unidade para alguns dos exemplos maiores usando _pytest_—que acho mais fácil de usar e mais poderoso que o módulo _unittest_ da bibliotexa padrão. +Também((("pytest package")))((("unittest module"))) escrevi testes unitários para alguns dos exemplos maiores usando _pytest_—que acho mais fácil de usar e mais poderoso que o módulo _unittest_ da biblioteca padrão. Você vai descobrir que pode verificar a maior parte do código do livro digitando `python3 -m doctest example_script.py` ou `pytest` no console de seu sistema operacional. A configuração do _pytest.ini_, na raiz do https://fpy.li/code[repositório do código de exemplo], assegura que _doctests_ são coletados e executados pelo comando `pytest`.((("", startref="Papproach00"))) === Ponto de vista: minha perspectiva pessoal -Venho usando, ensinando e debatendo Python desde 1998, e gosto de estudar e comparar linguagens de programação, seus projetos e a teoria por trás delas. Ao final de alguns capítulos acrescentei uma seção "Ponto de vista", apresentando minha perspectiva sobre o Python e outras linguagens. Você pode pular essas partes, se não tiver interesse em tais discussões. Seu conteúdo é inteiramente opcional. +Venho usando, ensinando e debatendo Python desde 1998, e gosto de estudar e comparar linguagens de programação, seus projetos e a teoria por trás delas. Ao final de alguns capítulos acrescentei uma seção "Ponto de vista", apresentando minha perspectiva sobre Python e outras linguagens. Você pode pular essas partes, se não tiver interesse em tais discussões. Seu conteúdo é inteiramente opcional. -=== Conteúdo na na Web +=== Conteúdo na Web Criei dois sites para este livro: https://pythonfluente.com:: -O texto integral em português traduzido por Paulo Candido de Oliveira filho. É que você está lendo agora. +O texto integral em português traduzido por Paulo Candido de Oliveira Filho. É que você está lendo agora. https://fluentpython.com:: -Contém textos em inglês para ambas edições do livro, além de um glossário. -É um material que eu cortei para não ultrapassar o limite de 1.000 páginas. +Contém textos em inglês complementando as duas edições do livro, além de um glossário. +Tive que colocar esse conteúdo online para não ultrapassar o limite de 1.000 páginas. O repositório de exemplos de código está no https://fpy.li/code[GitHub]. @@ -163,16 +153,16 @@ O repositório de exemplos de código está no https://fpy.li/code[GitHub]. As seguintes convenções tipográficas são usadas neste livro: -_Itálico_:: Indica novos termos, URLs, endereços de email, nomes e extensões de arquivos footnote:[NT: Nesta edição em português +_Itálico_:: Indica novos termos, URLs, endereços de e-mail, nomes e extensões de arquivos footnote:[NT: Nesta edição em português também usamos _itálico_ em alguns termos mantidos em inglês ou traduções de termos cuja versão em português não é familiar]. -+Espaçamento constante+:: Usado para listagens de programas, bem como dentro de parágrafos para indicar elementos programáticos tais como nomes de variáveis ou funções, bancos de dados, tipos de dados, variáveis do ambiente, instruções e palavras-chave. +`Espaçamento constante`:: Usado para listagens de programas, bem como dentro de parágrafos para indicar elementos programáticos tais como nomes de variáveis ou funções, bancos de dados, tipos de dados, variáveis do ambiente, instruções e palavras-chave. + Observe que quando uma quebra de linha cai dentro de um termo de pass:[espaçamento constante], o hífen não é utilizado--pois ele poderia ser erroneamente entendido como parte do termo. -**`Espaçamento constante em negrito`**:: Mostra comandos oi outro texto que devem ser digitados literalmente pelo usuário. +**`Espaçamento constante em negrito`**:: Mostra comandos ou outro texto que devem ser digitados literalmente pelo usuário. -_++Espaçamento constante em itálico++_:: Mostra texto que deve ser substituído por valores fornecidos pelo usuário ou por valores determinados pelo contexto. +`_Espaçamento constante em itálico_`:: Mostra texto que deve ser substituído por valores fornecidos pelo usuário ou por valores determinados pelo contexto. [role="pagebreak-before less_space"] @@ -193,56 +183,46 @@ Este elemento é um aviso ou alerta. === Usando os exemplos de código -Todos((("code examples, obtaining and using"))) os scripts e a maior parte dos trechos de código que aparecem no livro estão disponíveis no repositório de código do Python Fluente, https://fpy.li/code[no GitHub]. +Todos((("code examples, obtaining and using"))) os scripts e a maior parte dos trechos de código que aparecem no livro estão disponíveis no repositório de código de Python Fluente, https://fpy.li/code[no GitHub]. -Se você tiver uma questão técnica ou algum problema para usar o código, por favor mande um email para pass:[]. +Se você tiver uma questão técnica ou algum problema para usar o código, por favor mande um e-mail para pass:[bookquestions@oreilly.com]. Esse livro existe para ajudar você a fazer seu trabalho. Em geral, se o código exemplo está no livro, você pode usá-lo em seus programas e na sua documentação. Não é necessário nos contactar para pedir permissão, a menos que você queira reproduzir uma parte significativa do código. Por exemplo, escrever um programa usando vários pedaços de código deste livro não exige permissão. Vender ou distribuir exemplos de livros da O’Reilly exige permissão. Responder uma pergunta citando este livro e código exemplo daqui não exige permissão. Incorporar uma parte significativa do código exemplo do livro na documentação de seu produto exige permissão. Gostamos, mas em geral não exigimos, atribuição da fonte. Isto normalmente inclui o título, o autor, a editora e o ISBN. Por exemplo, “_Python Fluente_, 2ª ed., de Luciano Ramalho. Copyright 2022 Luciano Ramalho, 978-1-492-05635-5.” -Se você achar que seu uso dos exemplo de código está fora daquilo previsto na lei ou das permissões dadas acima, por favor entre em contato com pass:[]. +Se você achar que seu uso dos exemplo de código está fora daquilo previsto na lei ou das permissões dadas acima, por favor entre em contato com pass:[permissions@oreilly.com]. === O'Reilly Online Learning [role = "ormenabled"] [NOTE] ==== -Por mais de 40 anos, pass:[O’Reilly Media] tem oferecido treinamento, conhecimento e ideias sobre tecnologia e negócios, ajudando empresas serem bem sucedidas. +Por mais de 40 anos, http://oreilly.com[_O’Reilly Media_] tem oferecido treinamento, conhecimento e ideias sobre tecnologia e negócios, ajudando empresas serem bem sucedidas. ==== -Nossa rede sem igual de especialistas e inovadores compartilha conhecimento e sabedoria através de livros, artigos e de nossa plataforma online de aprendizagem. A plataforma de aprendizagem online da O’Reilly’s oferece acesso sob demanda a treinamentos ao vivo, trilhas de aprendizagem profunda, ambientes interativos de programação e uma imensa coleção de textos e vídeos da O'Reilly e de mais de 200 outras editoras. Para maiores informações, visite pass:[http://oreilly.com]. +Nossa rede sem igual de especialistas e inovadores compartilha conhecimento e sabedoria através de livros, artigos e de nossa plataforma online de aprendizagem. A plataforma de aprendizagem online da O’Reilly oferece acesso sob demanda a treinamentos ao vivo, trilhas de aprendizagem profunda, ambientes interativos de programação e uma imensa coleção de textos e vídeos da O'Reilly e de mais de 200 outras editoras. Para mais informações, visite _http://oreilly.com_. === Como entrar em contato -Por favor((("comments and questions")))((("questions and comments"))), envie comentários e perguntas sobre esse livro para o editor: +Por gentileza((("comments and questions")))((("questions and comments"))), envie comentários e perguntas sobre esse livro para o editor: -++++ -
    -
  • O’Reilly Media, Inc.
  • -
  • 1005 Gravenstein Highway North
  • -
  • Sebastopol, CA 95472
  • -
  • 800-998-9938 (in the United States or Canada)
  • -
  • 707-829-0515 (international or local)
  • -
  • 707-829-0104 (fax)
  • -
-++++ +---- +O’Reilly Media, Inc. +1005 Gravenstein Highway North +Sebastopol, CA 95472 +800-998-9938 (in the United States or Canada) +707-829-0515 (international or local) +707-829-0104 (fax) +---- -Há uma página online para este livro, com erratas, exemplos e informação adicional, que pode ser acessada aqui: https://fpy.li/p-4[]. +Há uma página online para o original em inglês deste livro, com erratas e informação adicional, +que pode ser acessada aqui: https://fpy.li/p-4. -++++ - -++++ +Envie e-mail para _bookquestions@oreilly.com_, com comentários ou dúvidas técnicas sobre o livro. -Envie email para pass:[], com comentários ou dúvidas técnicas sobre o livro. +Novidades e informações sobre nossos livros e cursos podem ser encontradas em _http://oreilly.com_. -Novidades e informações sobre nossos livros e cursos podem ser encontradas em link:$$http://oreilly.com$$[]. - -No Facebook: link:$$http://facebook.com/oreilly$$[]. - -No Twitter: link:$$https://twitter.com/oreillymedia$$[]. - -No YouTube: link:$$http://www.youtube.com/oreillymedia$$[]. === Agradecimentos @@ -263,9 +243,9 @@ Alexander Hagerman, Chen Hanxiao, Sam Hyeong, Simon Ilincev, Parag Kalra, Tim Ki Tina Lapine, Wanpeng Li, Guto Maia, Scott Martindale, Mark Meyer, Andy McFarland, Chad McIntire, Diego Rabatone Oliveira, Francesco Piccoli, Meredith Rawls, Michael Robinson, Federico Tula Rovaletti, Tushar Sadhwani, Arthur Constantino Scardua, Randal L. Schwartz, Avichai Sefati, Guannan Shen, William Simpson, -Vivek Vashist, Jerry Zhang, Paul Zuradzki—e outros que pediram para não ter seus nomes mencionados, enviaram correções após a entrega da versão inicial ou foram omitidos porque eu não registri seus nomes—mil desculpas. +Vivek Vashist, Jerry Zhang, Paul Zuradzki—e outros que pediram para não ter seus nomes mencionados, enviaram correções após a entrega da versão inicial ou foram omitidos porque eu não registrei seus nomes—mil desculpas. -Durante minha pesquisa, aprendi sobre tipagem, concorrência, _pattern matching_ e metaprogramação interagindo com +Durante minha pesquisa, aprendi sobre tipagem, concorrência, _pattern matching_ e metaprogramação interagindo com Michael Albert, Pablo Aguilar, Kaleb Barrett, David Beazley, J. S. O. Bueno, Bruce Eckel, Martin Fowler, Ivan Levkivskyi, Alex Martelli, Peter Norvig, Sebastian Rittau, Guido van Rossum, Carol Willing e Jelle Zijlstra. @@ -277,7 +257,7 @@ Inevitavelmente, vão restar erros de minha própria criação no produto final. Por fim gostaria de estender meus sinceros agradecimento a meus colegas na Thoughtworks Brasil—e especialmente a meu mentor, Alexey Bôas—que apoiou este projeto de muitas formas até o fim. -Claro, todos os que me ajudaram a entender o Python e a escrever a primeira edição merecem agora agradecimentos em dobro. +Claro, todos os que me ajudaram a entender Python e a escrever a primeira edição merecem agora agradecimentos em dobro. Não haveria segunda edição sem o sucesso da primeira. [role="pagebreak-before less_space"] @@ -288,10 +268,10 @@ Guido van Rossum, filho de um arquiteto e irmão de projetista de fonte magistra Adoro ensinar Python porque ele é belo, simples e claro. Alex Martelli e Anna Ravenscroft foram os primeiros a verem o esquema desse livro, e me encorajaram a submetê-lo à O'Reilly para publicação. -Seus livros me ensinaram o Python idiomático e são modelos de clareza, precisão e profundidade em escrita técnica. +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). @@ -299,13 +279,13 @@ Mario Domenech Goulart deu sugestões numerosas e detalhadas, desde a primeira v Ao longo dos anos, muitas pessoas me encorajaram a me tornar um autor, mas os mais persuasivos foram Rubens Prates, Aurelio Jargas, Rudá Moura e Rubens Altimari. Mauricio Bussab me abriu muitas portas, incluindo minha primeira experiência real na escrita de um livro. Renzo Nuccitelli apoiou este projeto de escrita o tempo todo, mesmo quando significou iniciar mais lentamente nossa parceria no pass:[python.pro.br]. -A maravilhosa comunidade brasileira de Python é inteligente, generosa e divertida. O https://fpy.li/p-9[The Python Brasil group] tem milhares de membros, e nossas conferências nacionais reúnem centenas de pessoas. Mas os mais influemtes em minha jornada como pythonista foram Leonardo Rochael, Adriano Petrich, Daniel Vainsencher, Rodrigo RBP Pimentel, Bruno Gola, Leonardo Santagada, Jean Ferri, Rodrigo Senra, J. S. Bueno, David Kwast, Luiz Irber, Osvaldo Santana, Fernando Masanori, Henrique Bastos, Gustavo Niemayer, Pedro Werneck, Gustavo Barbieri, Lalo Martins, Danilo Bellini, e Pedro Kroger. +A maravilhosa comunidade brasileira de Python é inteligente, generosa e divertida. O https://fpy.li/p-9[The Python Brasil group] tem milhares de membros, e nossas conferências nacionais reúnem centenas de pessoas. Mas os mais influentes em minha jornada como pythonista foram Leonardo Rochael, Adriano Petrich, Daniel Vainsencher, Rodrigo RBP Pimentel, Bruno Gola, Leonardo Santagada, Jean Ferri, Rodrigo Senra, J. S. Bueno, David Kwast, Luiz Irber, Osvaldo Santana, Fernando Masanori, Henrique Bastos, Gustavo Niemayer, Pedro Werneck, Gustavo Barbieri, Lalo Martins, Danilo Bellini, e Pedro Kroger. Dorneles Tremea foi um grande amigo, (e incrivelmente generoso com seu tempo e seu conhecimento), um hacker fantástico e o mais inspirador líder da Associação Python Brasil. Ele nos deixou cedo demais. Meus estudantes, ao longo desses anos, me ensinaram muito através de suas perguntas, ideias, feedbacks e soluções criativas para problemas. Érico Andrei e a Simples Consultoria tornaram possível que eu me concentrasse em ser um professor de Python pela primeira vez. -Martijn Faassen foi meu mentor de Grok e compartilhou ideias valiosas sobre o Python e os neandertais. Seu trabalho e o de Paul Everitt, Chris McDonough, Tres Seaver, Jim Fulton, Shane Hathaway, Lennart Regebro, Alan Runyan, Alexander Limi, Martijn Pieters, Godefroid Chapelle e outros, dos planetas Zope, Plone e Pyramid, foram decisivos para minha carreira. Graças ao Zope e a surfar na primeira onda da web, pude começar a ganhar a vida com Python em 1998. José Octavio Castro Neves foi meu sócio na primeira software house baseada em Python do Brasil. +Martijn Faassen foi meu mentor de Grok e compartilhou ideias valiosas sobre Python e os neandertais. Seu trabalho e o de Paul Everitt, Chris McDonough, Tres Seaver, Jim Fulton, Shane Hathaway, Lennart Regebro, Alan Runyan, Alexander Limi, Martijn Pieters, Godefroid Chapelle e outros, dos planetas Zope, Plone e Pyramid, foram decisivos para minha carreira. Graças ao Zope e a surfar na primeira onda da web, pude começar a ganhar a vida com Python em 1998. José Octavio Castro Neves foi meu sócio na primeira software house baseada em Python do Brasil. Tenho gurus demais na comunidade Python como um todo para listar todos aqui, mas além daqueles já mencionados, eu tenho uma dívida com Steve Holden, Raymond Hettinger, A.M. Kuchling, David Beazley, Fredrik Lundh, Doug Hellmann, Nick Coghlan, Mark Pilgrim, Martijn Pieters, Bruce Eckel, Michele Simionato, Wesley Chun, Brandon Craig Rhodes, Philip Guo, Daniel Greenfeld, Audrey Roy e Brett Slatkin, por me ensinarem novas e melhores formas de ensinar Python. @@ -322,29 +302,25 @@ Agradeço a todos vocês, por tudo. === Sobre esta tradução -_Python Fluente, Segunda Edição_ +_Python Fluente, 2ª Edição_ é uma tradução direta de _Fluent Python, Second Edition_ (O'Reilly, 2022). Não é uma obra derivada de _Python Fluente_ (Novatec, 2015). A presente tradução foi autorizada pela O'Reilly Media para distribuição nos termos da licença -https://creativecommons.org/licenses/by-nc-nd/4.0/deed.pt_BR[CC BY-NC-ND]. +https://fpy.li/4j[CC BY-NC-ND]. Os arquivos-fonte em formato _Asciidoc_ estão no repositório público https://github.com/pythonfluente/pythonfluente2e. Enquanto publicávamos a tradução ao longo de 2023, muitas correções foram enviadas por leitores como __issues__ (defeitos) ou __pull requests__ (correções) -no https://github.com/pythonfluente/pythonfluente2e[repositório]. Agradeceço a todas as pessoas que colaboraram! +no https://fpy.li/4k[repositório]. Agradeceço a todas as pessoas que colaboraram! [NOTE] ==== -Se um link aparece entre colchetes <>, -ele não funciona porque é uma referência para uma seção não identificada. -Precisamos corrigir. - Correções e sugestões de melhorias são bem vindas! Para contribuir, veja os -https://github.com/pythonfluente/pythonfluente2e/issues[__issues__] +https://fpy.li/4m[__issues__] no repositório https://github.com/pythonfluente/pythonfluente2e. Contamos com sua colaboração. 🙏 @@ -377,7 +353,7 @@ no Brasil pela Editora Novatec em 2015, sob licença da O'Reilly. Entre 2020 e 2022, atualizei e expandi bastante o livro para a segunda edição. Sou muito grato à liderança da -https://www.thoughtworks.com/pt-br[Thoughtworks Brasil] +https://fpy.li/4n[Thoughtworks Brasil] por terem me apoiado enquanto passei a maior parte de 2020 e 2021 pesquisando, escrevendo, e revisando esta edição. @@ -387,7 +363,7 @@ segunda edição em PT-BR com uma licença livre, como uma contribuição para comunidade Python lusófona. A O'Reilly autorizou que essa tradução fosse publicada sob a licença CC BY-NC-ND: -https://creativecommons.org/licenses/by-nc-nd/4.0/deed.pt_BR[Creative Commons — Atribuição-NãoComercial-SemDerivações 4.0 Internacional]. +https://fpy.li/4j[Creative Commons — Atribuição-NãoComercial-SemDerivações 4.0 Internacional]. Com essa mudança contratual, a Editora Novatec não teve interesse em traduzir e publicar a segunda edição. @@ -398,7 +374,7 @@ Hoje ele presta serviços editoriais, inclusive faz traduções com a excelente qualidade desta aqui. Contratei PC para traduzir. Estou fazendo a revisão técnica, -gerando os arquivos HTML com https://asciidoctor.org/[Asciidoctor] +gerando os arquivos HTML com https://fpy.li/4p[Asciidoctor] e publicando em https://PythonFluente.com. Estamos trabalhando diretamente a partir do _Fluent Python, Second Edition_ da O'Reilly, sem aproveitar a tradução da primeira edição, cujo copyright @@ -406,4 +382,4 @@ pertence à Novatec. O copyright desta tradução pertence a mim. -_Luciano Ramalho, São Paulo, 13 de março de 2023_ \ No newline at end of file +_Luciano Ramalho, São Paulo, 13 de março de 2023_ diff --git a/online/README.md b/online/README.md new file mode 100644 index 00000000..3d6ff326 --- /dev/null +++ b/online/README.md @@ -0,0 +1,40 @@ +# Procedimentos para preparar .adoc para revisão + +## Operações + +### Nos arquivos da edição online: + +1. Eliminar duplicação de atributos entre cabeçalhos de `capN.adoc`, `atributos-pt_BR.adoc`, `Livro.adoc`, `Volume1.adoc`... +2. Encurtar links já presentes +3. Quebrar linhas longas (semantic linebreaks) +4. Retirar inline pass macros[^1] +5. Gerar PDF para impressão +6. Revisar PDF, aplicar correções no `.adoc` + +[^1]: https://docs.asciidoctor.org/asciidoc/latest/pass/pass-macro/ + +## Cuidados ao automatizar mudanças + +### Encurtar somente URLs em links Asciidoc + +A sintaxe do link é `http://d.t[Texto]` portanto é bom usar +o sinal `[` logo após a URL como parte da expressão regular. + +### Não reformatar blocos + +Não reformatar ou encurtar URLs em: + +* blocos de código +* tabelas + +### Não quebrar legendas de figuras + +Uma legenda de figura começa com `.` e não pode ser quebrada em várias linhas. + +Exemplo: + +``` +[[vectors_fig]] +.Soma de vetores bi-dimensionais: `Vector(2, 4) + Vector(2, 1)` devolve `Vector(4, 5)`. +image::../images/flpy_0101.png[vetores 2D] +``` \ No newline at end of file diff --git a/online/build.sh b/online/build.sh new file mode 100755 index 00000000..348cf155 --- /dev/null +++ b/online/build.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# $1 is the root .adoc file +set -e # exit when any command fails +bundle exec asciidoctor -v $1 -o index.html +open index.html diff --git a/online/cap01.adoc b/online/cap01.adoc new file mode 100644 index 00000000..7632acf2 --- /dev/null +++ b/online/cap01.adoc @@ -0,0 +1,849 @@ +[[ch_data_model]] +== O modelo de dados de Python +:example-number: 0 +:figure-number: 0 + +[quote, Jim Hugunin, criador do Jython, co-criador do AspectJ, e arquiteto do .Net DLR—Dynamic Language Runtime] +____ +O senso estético de Guido para o design de linguagens é incrível. +Conheci muitos projetistas capazes de criar linguagens teoricamente lindas, +que ninguém jamais usaria. +Mas Guido é uma daquelas raras pessoas capazes de criar uma linguagem só um pouco menos teoricamente linda que, +por isso mesmo, é uma delícia para programar. +footnote:[Traduzido de +https://fpy.li/1-1[Story of Jython] (EN), +prefácio de +https://fpy.li/1-2[Jython Essentials] +(EN), de Samuele Pedroni e Noel Rappin (O'Reilly).] +____ + +Uma das melhores qualidades de Python é sua consistência. +Após trabalhar com Python por algum tempo, é possível intuir, de modo informado e correto, +o funcionamento de recursos que você acabou de conhecer. + +Entretanto, se você aprendeu outra linguagem orientada a objetos antes de Python, +pode achar estranho usar `len(collection)` em vez de `collection.len()`. +Essa pecularidade é a ponta de um iceberg que, quando bem compreendido, +é a chave para tudo aquilo que chamamos de _pythônico_. +O iceberg se chama o Modelo de Dados de Python, +e é a API que usamos para fazer nossos objetos lidarem bem com +os recursos mais poderosos e característicos da linguagem. + +É((("Python Data Model", "overview of"))) possível pensar no modelo de dados como +uma descrição de Python na forma de um framework. +Ele formaliza as interfaces dos elementos constituintes da própria linguagem, +como sequências, funções, iteradores, corrotinas, classes, gerenciadores de contexto e assim por diante. + +Quando usamos um framework, +passamos um bom tempo programando métodos que são chamados pelo framework, +e não pelas nossas classes. +O mesmo acontece quando nos valemos do Modelo de Dados de Python para criar novas classes. +O interpretador de Python invoca((("special methods", "purpose of"))) +métodos especiais para realizar operações básicas sobre os objetos, +muitas vezes acionadas por uma sintaxe especial. +Os((("special methods", "naming conventions")))((("__ +(double underscore)")))((("double underscore (__)"))) +nomes dos métodos especiais são sempre precedidos e seguidos de dois sublinhados. +Por exemplo, a sintaxe `obj[key]` está amparada no método especial `+__getitem__+`. +Para resolver `my_collection[key]`, o interpretador chama `+my_collection.__getitem__(key)+`. + +Implementamos métodos especiais quando queremos que nossos objetos suportem e interajam com +elementos fundamentais da linguagem, como: + +* Coleções + +* Acesso a atributos + +* Iteração (incluindo iteração assíncrona com `+async for+`) + +* Sobrecarga (_overloading_) de operadores + +* Invocação de funções e métodos + +* Representação e formatação de strings + +* Programação assíncrona usando `+await+` + +* Criação e destruição de objetos + +* Contextos gerenciados usando as instruções `with` ou `async with` + + +.Mágica e o "dunder" +[NOTE] +==== +O((("magic methods")))((("dunder methods"))) +termo _método mágico_ é uma gíria usada para se referir aos métodos especiais, +mas como falamos de um método específico, por exemplo `+__getitem__+`? +Aprendi a dizer "dunder-getitem" com o autor e professor Steve Holden. +"Dunder" é uma contração da frase em inglês "double underscore before and after" +(_sublinhado duplo antes e depois_). +Por isso os métodos especiais são também conhecidos como _métodos dunder_. +O capítulo +https://fpy.li/2d["Análise Léxica"] +de _A Referência da Linguagem Python_ adverte: +"_Qualquer_ uso de nomes no formato `+__*__+` que não siga explicitamente o uso documentado, +em qualquer contexto, está sujeito a quebra sem aviso prévio." +==== + + +=== Novidades neste capítulo + +Esse((("Python Data Model", "significant changes to"))) capítulo sofreu poucas alterações desde a primeira edição, +pois é uma introdução ao Modelo de Dados de Python, que é muito estável. +As mudanças mais significativas foram: + +* Métodos especiais que suportam programação assíncrona e outras novas funcionalidades +foram acrescentados às tabelas na <>. + +* A <>, mostrando o uso de métodos especiais na <>, +incluindo a classe base abstrata `collections.abc.Collection`, introduzida no Python 3.6. + +Além disso, aqui((("f-string syntax", "benefits of"))) e por toda essa segunda edição, +adotei a sintaxe _f-string_, introduzida no Python 3.6, +que é mais legível e muitas vezes mais conveniente que as notações de formatação de strings mais antigas: +o((("str.format() method")))((("% (modulo) operator")))((("modulo (%) operator"))) +método `str.format()` e o operador `%`. + +[role="man-height-1-125"] +[TIP] +==== +Existe((("my_fmt.format() method"))) ainda uma razão para usar `my_fmt.format()`: +quando a definição de `my_fmt` precisa vir de um lugar diferente daquele onde +a operação de formatação precisa acontecer no código. +Por exemplo, quando `my_fmt` tem múltiplas linhas e é melhor definida em uma constante, +ou quando tem de vir de um arquivo de configuração ou de um banco de dados. +Essas são necessidades reais, mas não acontecem com frequência. +==== + +//// +PROD: As is often the case, the TIP above may or may not run over the next paragraphs, +depending on some random factor I don't know how to control. +//// + +[[pythonic_card_deck]] +=== Um baralho pythônico + +O <> é((("__getitem__", +id="getitem01")))((("__len__", id="len01")))((("Pythonic Card Deck example", +id="pycard01")))((("Python Data Model", "__getitem__ and __len__", +id="PDMgetitem01", secondary-sortas="getitem")))((("special methods", +"__getitem__ and __len__", +id="SMgetitem01", secondary-sortas="getitem")))((("card deck example", id="carddeck01"))) +simples, mas demonstra as possibilidades que se abrem com a implementação de apenas dois métodos especiais, +`+__getitem__+` e `+__len__+`. + +[[ex_pythonic_deck]] +.Um baralho como uma sequência de cartas +==== +[source, python] +---- +include::../code/01-data-model/frenchdeck.py[] +---- +==== + +A((("collections.namedtuple"))) primeira coisa a notar é o uso de `collections.namedtuple` +para construir uma classe simples representando cartas individuais. +Usamos `namedtuple` para criar classes de objetos que são apenas um agrupamento de atributos, +sem métodos próprios, como um registro de banco de dados. +Neste exemplo, a utilizamos para fornecer uma boa representação textual para as cartas em um baralho, +como mostra a sessão no console: + +[source, python] +---- +>>> beer_card = Card('7', 'diamonds') +>>> beer_card +Card(rank='7', suit='diamonds') +---- + +Mas a parte central desse exemplo é a classe `FrenchDeck`. +Ela é curta, mas poderosa. +Primeiro, como qualquer coleção padrão de Python, +uma instância de `FrenchDeck` responde à +função((("len() function")))((("functions", "len() function"))) `len()`, +devolvendo o número de cartas naquele baralho: + +[source, python] +---- +>>> deck = FrenchDeck() +>>> len(deck) +52 +---- + +Ler cartas específicas do baralho é fácil, graças ao método `+__getitem__+`. +Por exemplo, a primeira e a última carta: + +[source, python] +---- +>>> deck[0] +Card(rank='2', suit='spades') +>>> deck[-1] +Card(rank='A', suit='hearts') +---- + +Deveríamos criar um método para obter uma carta aleatória? Não é necessário. +Python((("random.choice function"))) já tem uma função que devolve um item aleatório de uma sequência: `random.choice`. +Podemos usá-la em uma instância de `FrenchDeck`: + +[source, python] +---- +>>> from random import choice +>>> choice(deck) +Card(rank='3', suit='hearts') +>>> choice(deck) +Card(rank='K', suit='spades') +>>> choice(deck) +Card(rank='2', suit='clubs') +---- + +Acabamos((("special methods", "advantages of using"))) de ver duas vantagens de usar +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 itens? É `.size()`, `.length()` ou outra coisa?") + +* É mais fácil de aproveitar a rica biblioteca padrão de Python e evitar reinventar a roda, +como no caso da função `random.choice`. + +Mas não é só isso. + +Como nosso `+__getitem__+` usa o((("square brackets ([])")))((("[] (square brackets)"))) +operador `[]` de `self._cards`, +nosso baralho suporta fatiamento automaticamente. +Podemos olhar as três primeiras cartas no topo de um baralho, +e depois pegar só os ases, iniciando com o índice 12 e pulando 13 cartas por vez: + +[source, python] +---- +>>> deck[:3] +[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), +Card(rank='4', suit='spades')] +>>> deck[12::13] +[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), +Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')] +---- + +E como já temos o método especial `+__getitem__+`, nosso baralho é um objeto iterável, +ou seja, pode ser percorrido em um laço `for`: + +[source, python] +---- +>>> for card in deck: # doctest: +ELLIPSIS +... print(card) +Card(rank='2', suit='spades') +Card(rank='3', suit='spades') +Card(rank='4', suit='spades') +... +---- + +Também podemos iterar sobre o baralho na ordem inversa: + +[source, python] +---- +>>> for card in reversed(deck): # doctest: +ELLIPSIS +... print(card) +Card(rank='A', suit='hearts') +Card(rank='K', suit='hearts') +Card(rank='Q', suit='hearts') +... +---- + +.Reticências nos doctests +[NOTE] +==== +Sempre((("doctest package", "ellipsis in")))((("ellipsis (…)")))((("… (ellipsis)"))) que possível, +extraí as listagens do console de Python usadas neste livro com o +https://fpy.li/2e[`doctest`], para garantir a precisão. +Quando a saída era longa demais, a parte omitida está marcada por reticências (`\...`), +como na última linha do trecho de código anterior. + +Nesse casos, usei((("+ELLIPSIS directive"))) a diretiva `# doctest: +ELLIPSIS` +para fazer o doctest funcionar. +Ao experimentar esses exemplos no console iterativo, +pode omitir todos os comentários de doctest. +==== + +A iteração muitas vezes é implícita. +Python invoca o método `+__contains__+` da coleção para tratar o operador `in`: `student in team`. +Mas se a coleção não((("__contains__"))) fornece um método `+__contains__+`, +o operador `in` realiza uma busca sequencial. +No nosso caso, `in` funciona com nossa classe `FrenchDeck` porque ela é iterável. +Veja a seguir: + +[source, python] +---- +>>> Card('Q', 'hearts') in deck +True +>>> Card('7', 'beasts') in deck +False +---- + +E o ordenamento? +Um sistema comum de ordenar cartas é por seu valor numérico (ases sendo os mais altos) e depois por naipe, +na ordem espadas (o mais alto), copas, ouros e paus (o mais baixo). +Aqui está uma função que ordena as cartas com essa regra, +devolvendo `0` para o 2 de paus e `51` para o Ás de espadas. + +[source, python] +---- +suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0) + +def spades_high(card): + rank_value = FrenchDeck.ranks.index(card.rank) + return rank_value * len(suit_values) + suit_values[card.suit] +---- + +Podemos agora ordenar nosso baralho usando `spades_high` como critério de ordenação: + +[source, python] +---- +>>> for card in sorted(deck, key=spades_high): # doctest: +ELLIPSIS +... print(card) +Card(rank='2', suit='clubs') +Card(rank='2', suit='diamonds') +Card(rank='2', suit='hearts') +... (46 cards omitted) +Card(rank='A', suit='diamonds') +Card(rank='A', suit='hearts') +Card(rank='A', suit='spades') +---- + +Apesar da `FrenchDeck` herdar implicitamente da classe `object`, +a maior parte de sua funcionalidade não é herdada, vem do modelo de dados e de composição. +Ao implementar os métodos especiais `+__len__+` e `+__getitem__+`, +nosso `FrenchDeck` se comporta como uma sequência Python padrão, +podendo assim se beneficiar de recursos centrais da linguagem (por exemplo, iteração e fatiamento), +e da biblioteca padrão, como mostramos nos exemplos usando `random.choice`, +`reversed`, e `sorted`. +Graças à composição, +as implementações de `+__len__+` e `+__getitem__+` podem delegar todo o trabalho para um objeto `list`, +especificamente `self._cards`.((("", startref="PDMgetitem01")))((("", startref="getitem01")))((("", +startref="len01")))((("", startref="pycard01")))((("", startref="SMgetitem01")))((("", startref="carddeck01"))) + +.Como embaralhar as cartas? +[NOTE] +===================================================================== +Como foi implementado até aqui, um `FrenchDeck` não pode ser embaralhado, +porque as cartas e suas posições não podem ser alteradas, +exceto violando o encapsulamento e manipulando o atributo `_cards` diretamente. +No <> vamos corrigir isso acrescentando um método `+__setitem__+` +de uma linha. +Você consegue imaginar como ele seria implementado? +===================================================================== + + +[[how_special_used]] +=== Como os métodos especiais são utilizados + +A((("Python Data Model", "using special methods", id="PDMspecmeth01")))((("special methods", "calling"))) +primeira coisa a saber sobre os métodos especiais é que eles são feitos para +serem chamados pelo interpretador Python, e não por você. +Você não escreve `+my_object.__len__()+`. +Escreve `len(my_object)` e, se `my_object` é uma instância de uma classe definida por você, +então Python chama o método `+__len__+` que você implementou. + +Mas o interpretador pega um atalho quando está lidando com um tipo embutido como `list`, `str`, `bytearray`, +ou extensões compiladas como os arrays da NumPy. +As coleções de tamanho variável de Python escritas em C incluem uma +structfootnote:[Uma struct do C é um tipo de registro com campos nomeados.] +chamada `PyVarObject`, com um campo `ob_size` que registra a quantidade de itens na coleção. +Então, se `my_object` é uma instância de algum daqueles tipos embutidos, +`len(my_object)` devolve diretamente valor do campo `ob_size`, +e isso é mais rápido que chamar um método. + +Na maior parte das vezes, a chamada a um método especial é implícita. +Por exemplo, o comando `for i in x:` na verdade gera uma invocação de `iter(x)`, +que por sua vez pode chamar `+x.__iter__()+` se esse método estiver disponível, +ou usar `+x.__getitem__()+`, como no exemplo do `FrenchDeck`. + +Em condições normais, seu código não deveria conter muitas chamadas diretas a métodos especiais. +A menos que você esteja fazendo muita metaprogramação, +implementar métodos especiais deve ser mais frequente que invocá-los explicitamente. +O((("__init__"))) +único método especial que é chamado frequentemente pelo seu código é `+__init__+`, +para invocar a inicialização da superclasse na implementação do seu próprio `+__init__+`. + +Geralmente, se você precisa invocar um método especial, +é melhor chamar a função embutida relacionada (por exemplo, `len`, `iter`, `str`, etc.). +Essas funções chamam o método especial correspondente, +mas também fornecem outros serviços e—para tipos embutidos—são mais rápidas que chamadas a métodos. + +Na próxima seção veremos alguns dos usos mais importantes dos métodos especiais: + +* Emular tipos numéricos +* Representar objetos na forma de strings +* Determinar o valor booleano de um objeto +* Implementar coleções + + +[[data_model_emulating_sec]] +==== Emulando tipos numéricos + +Vários((("special methods", "emulating numeric types", id="SMnumeric01")))((("numeric types", +"emulating using special methods", id="NTemul01"))) +métodos especiais permitem que objetos criados pelo usuário respondam a operadores como `+`. +Vamos tratar disso com mais detalhes no <>. +Aqui nosso objetivo é continuar ilustrando o uso dos métodos especiais, através de outro exemplo simples. + +Vamos((("vectors", "representing two-dimensional", id="Vtwo01"))) implementar uma classe para representar +vetores bi-dimensionais—isto é, vetores euclidianos como aqueles usados em matemática e física +(veja a <>). + +[TIP] +====== +O tipo embutido `complex` pode ser usado para representar vetores bi-dimensionais, +mas nossa classe pode ser estendida para representar vetores N-dimensionais. +Faremos isso no <>. +====== + +[[vectors_fig]] +.Soma de vetores bi-dimensionais; `Vector(2, 4) + Vector(2, 1)` devolve `Vector(4, 5)`. +image::../images/flpy_0101.png[vetores 2D] + +Vamos começar a projetar a API para essa classe escrevendo uma sessão de console simulada, +que depois podemos usar como um doctest. +O trecho a seguir testa a adição de vetores ilustrada na <>: + +[source, python] +---- +>>> v1 = Vector(2, 4) +>>> v2 = Vector(2, 1) +>>> v1 + v2 +Vector(4, 5) +---- + +Observe((("+ operator"))) como o operador `+` produz um novo objeto `Vector(4, 5)`. + +A((("abs built-in function")))((("functions", "abs built-in function"))) função embutida `abs` +devolve o valor absoluto de números inteiros e de ponto flutuante, e a magnitude de números `complex`. +Então, por consistência, nossa API também usa `abs` para calcular a magnitude de um vetor: + +[source, python] +---- +>>> v = Vector(3, 4) +>>> abs(v) +5.0 +---- + +Podemos((("* (star) operator")))((("multiplication, scalar")))((("star (*) operator"))) +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] +---- +>>> v * 3 +Vector(9, 12) +>>> abs(v * 3) +15.0 +---- + +O <> é uma classe `Vector` que implementa as operações descritas acima, +usando os métodos especiais((("__repr__", +id="repr01")))((("__mul__")))((("__add__")))((("__abs__"))) +`+__repr__+`, `+__abs__+`, `+__add__+`, e `+__mul__+`. + +[[ex_vector2d]] +.Uma classe simples para representar um vetor 2D. +==== +[source, python] +---- +include::../code/01-data-model/vector2d.py[] +---- +==== + +Implementamos cinco métodos especiais, além do costumeiro `+__init__+`. +Veja que nenhum deles é chamado diretamente dentro da classe ou durante seu uso normal, +ilustrado pelos doctests. +Como mencionado antes, o interpretador Python é o único usuário frequente da maioria dos métodos especiais. + +O <> implementa dois operadores: `{plus}` e `*`, +para demonstrar o uso básico de `+__add__+` e `+__mul__+`. +No dois casos, os métodos criam e devolvem uma nova instância de `Vector`, +e não modificam nenhum dos operandos: `self` e `other` são apenas lidos. +Esse é o comportamento esperado de operadores infixos: criar novos objetos e não tocar em seus operandos. +Vou falar mais sobre esse tópico no <>. + +[WARNING] +==== +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 por escalar. +Vamos consertar isso com o método especial `+__rmul__+` no <>. +==== + +Nas seções seguintes vamos discutir os outros métodos especiais em +`Vector`.((("", startref="SMnumeric01")))((("", startref="NTemul01")))((("", startref="Vtwo01"))) + + +[[repr_intro]] +==== Representação como string + +O((("special methods", "string representation")))((("strings", "representation using special methods"))) +método especial `+__repr__+` é chamado pela função embutida `repr` +para obter a representação do objeto como uma string, para depuração. +Sem um `+__repr__+` customizado, o console de Python mostraria uma instância de `Vector` como +``. + +O console iterativo e o depurador chamam `repr` para exibir o resultado das expressões. +O `repr` também é usado: + +* Pelo marcador posicional `%r` na formatação clássica com o operador `%`. Ex.: `'%r' % my_obj` +* Pelo sinalizador de conversão `!r` na nova +https://fpy.li/2f[sintaxe de strings de formato] +usada nas((("f-string syntax", "string representation using special methods"))) +_f-strings_ e no método `str.format`. Ex: `f'{my_obj!r}'` + +Note que a _f-string_ no nosso `+__repr__+` usa `!r` para obter a representação padrão dos atributos a serem exibidos. +Isso é uma boa prática, pois durante uma seção de depuração podemos ver a diferença +entre `Vector(1, 2)` e `Vector('1', '2')`. +Este segundo objeto não funcionaria no contexto desse exemplo, +porque o construtor espera que os argumentos sejam números, não `str`. + +A string devolvida por `+__repr__+` não deve ser ambígua e, se possível, +deve corresponder ao código-fonte necessário para recriar o objeto representado. +É por isso que nossa representação de `Vector` se parece com uma chamada ao construtor da classe, +por exemplo `Vector(3, 4)`. + +Por((("__str__")))((("str() function")))((("functions", "str() function"))) +outro lado, `+__str__+` é chamado pela função embutida `str()` e usado automaticamente pela função `print`. +Ele deve devolver uma string apropriada para ser exibida aos usuários finais da aplicação. + +Algumas vezes a própria string devolvida por `+__repr__+` é adequada para exibir ao usuário, +e você não precisa programar `+__str__+`, +porque a implementação de `+__str__+` herdada da classe `object` já invoca `+__repr__+`. +O <> é um dos muitos exemplos neste livro com um `+__str__+` customizado. + +[TIP] +==== +Programadores com experiência anterior em linguagens que contém o método `toString` +tendem a implementar `+__str__+` e não `+__repr__+`. +Se você for implementar apenas um desses métodos especiais, escolha `+__repr__+`. + +https://fpy.li/1-5["What is the difference between `+__str__+` and `+__repr__+` in Python?" +(_Qual a diferença entre `+__str__+` e `+__repr__+` em Python?_)] (EN) +é uma questão no Stack Overflow com excelentes contribuições dos pythonistas +Alex Martelli e Martijn Pieters.((("", startref="repr01"))) +==== + + +==== O valor booleano de um tipo customizado + +Apesar ((("__bool__")))((("special methods", +"Boolean values of custom types")))((("Boolean values, custom types and")))((("bool type"))) +de Python ter um tipo `bool`, a linguagem aceita qualquer objeto em um contexto booleano, +tal como as expressões controlando instruções `if` ou `while`, ou como operandos de `and`, `or` e `not`. +Para determinar se um valor `x` é _verdadeiro_ ou _falso_, Python invoca `bool(x)`, +que devolve somente `True` ou `False`. + +Por padrão, instâncias de classes definidas pelo usuário são consideradas verdadeiras, +a menos que `+__bool__+` ou `+__len__+` sejam implementadas. +Basicamente, `bool(x)` chama `+x.__bool__()+` e usa o resultado. +Se `+__bool__+` não está implementado, Python tenta invocar `+x.__len__()+`, +e se esse último devolver zero, `bool` devolve `False`. +Caso contrário, `bool` devolve `True`. + +Nossa implementação de `+__bool__+` é simples: +ela devolve `False` se a magnitude do vetor for zero, caso contrário devolve `True`. +Convertemos a magnitude para um valor booleano usando `bool(abs(self))`, +porque espera-se que `+__bool__+` devolva um booleano. +Fora dos métodos `+__bool__+`, raramente é necessário chamar `bool()` explicitamente, +porque qualquer objeto pode ser usado em um contexto booleano. + +Observe que o método especial `+__bool__+` permite que seus objetos sigam as +regras de teste do valor verdade definidas no +https://fpy.li/2g[capítulo "Tipos Embutidos"] +da documentação da _Biblioteca Padrão de Python_. + +[NOTE] +==== +Essa é uma implementação mais rápida de `+Vector.__bool__+`: + +[source, python] +---- + def __bool__(self): + return bool(self.x or self.y) +---- + +Isso é mais difícil de ler, mas evita a jornada através de `abs`, `+__abs__+`, os quadrados, e a raiz quadrada. +A conversão explícita para `bool` é necessária porque `+__bool__+` deve devolver um booleano, +e `or` devolve um dos seus operandos no formato original: +`x or y` resulta em `x` se x for verdadeiro, caso contrário resulta em `y`, +qualquer que seja o valor deste último. +==== + +[[collection_api]] +==== A API de Collection + +A <> documenta((("special methods", "Collection API", +id="SMcollection01")))((("Collection API", id="Cspeical01")))((("ABCs (abstract base classes)", +"UML class diagrams", id="abcs01")))((("UML class diagrams", "fundamental collection types"))) +as interfaces dos tipos de coleções essenciais na linguagem. +Todas as classes no diagrama são ABCs—_classes base abstratas_ +(_ABC_ é sigla para _Abstract Base Class_). +As ABCs e o módulo `collections.abc` são tratados no <>. +O objetivo dessa pequena seção é dar uma visão panorâmica das interfaces das coleções mais importantes de Python, +mostrando como elas são criadas a partir de métodos especiais. + +[role="width-70"] +[[collection_uml]] +.Diagrama de classes UML com os tipos fundamentais de coleções. Métodos com nome em _itálico_ são abstratos, então precisam ser implementados pelas subclasses concretas, como `list` e `dict`. O restante dos métodos têm implementações concretas, então as subclasses podem herdá-los. +image::../images/flpy_0102.png[Diagram de classes UML com todas as superclasses e algumas subclasses de `abc.Collection`] + +Cada uma das ABCs no topo da hierarquia tem um único método especial. +A ABC `Collection` (introduzida no Python 3.6) unifica as três interfaces essenciais +que toda coleção deveria implementar: + +* `Iterable`, para((("Iterable interface")))((("interfaces", "Iterable interface"))) suportar `for`, +https://fpy.li/2h[desempacotamento], +e outras formas de iteração +* `Sized` para((("Sized interface")))((("interfaces", "Sized interface"))) suportar a função embutida `len` +* `Container` para((("Container interface")))((("interfaces", "Container interface"))) suportar o operador `in` + +Na verdade, Python não exige que classes concretas herdem de qualquer dessas ABCs. +Qualquer classe que implemente `+__len__+` satisfaz a interface `Sized`. + +Três especializações muito importantes de `Collection` são: + +* `Sequence`, formalizando a interface de tipos embutidos como `list` e `str` +* `Mapping`, implementado por `dict`, `collections.defaultdict`, etc. +* `Set`, a interface dos tipos embutidos `set` e `frozenset` + +Apenas `Sequence` é `Reversible`, porque sequências suportam o ordenamento arbitrário de seu conteúdo, +ao contrário de mapeamentos(_mappings_) e conjuntos(_sets_). + +[NOTE] +==== +Desde((("keys", "preserving key insertion order"))) Python 3.7, o tipo `dict` é oficialmente "ordenado", +mas isso só quer dizer que a ordem de inserção das chaves é preservada. +Você não pode rearranjar as chaves em um `dict` da forma que quiser. +==== + +Todos os métodos especiais na ABC `Set` implementam operadores infixos. +Por exemplo, `a & b` calcula a interseção entre os conjuntos `a` e `b`, +e é implementada no método especial `+__and__+`. + +Os próximos dois capítulos vão tratar em detalhes das sequências, mapeamentos e conjuntos da biblioteca padrão. + +Agora vamos considerar as duas principais categorias dos métodos especiais definidos no Modelo de Dados de +Python.((("", startref="PDMspecmeth01")))((("", startref="SMcollection01")))((("", startref="Cspeical01")))((("", startref="abcs01"))) + +[[overview_special_methods]] +=== Visão geral dos métodos especiais + +O((("Python Data Model", "special methods overview", id="PDMspmtov01")))((("special methods", +"special method names (operators excluded)"))) +https://fpy.li/2j[capítulo "Modelo de Dados"] +de _A Referência da Linguagem Python_ lista mais de 80 nomes de métodos especiais. +Mais da metade deles implementa operadores aritméticos, de comparação, ou bit-a-bit. +Para ter uma visão geral do que está disponível, veja tabelas a seguir. + +A <> mostra nomes de métodos especiais, excluindo aqueles usados para implementar +operadores infixos ou funções matemáticas fundamentais como `abs`. +A maioria desses métodos será tratado ao longo do livro, incluindo as adições mais recentes: +métodos especiais assíncronos como `+__anext__+` (acrescentado no Python 3.5), +e o método de configuração de classes, `+__init_subclass__+` (do Python 3.6). + + +[[special_names_tbl]] +.Nomes de métodos especiais (excluindo operadores) +[options="header"] +[cols="2,3"] +|================================================================================================= +|Categoria|Nomes dos métodos +|Representação de string/bytes|`+__repr__ __str__ __format__ __bytes__ __fspath__+` +|Conversão para número|`+__bool__ __complex__ __int__ __float__ __hash__ __index__+` +|Emulação de coleções|`+__len__ __getitem__ __setitem__ __delitem__ __contains__+` +|Iteração|`+__iter__ __aiter__ __next__ __anext__ __reversed__+` +|Execução de invocávelfootnote:[NT: invocável é um objeto que contém código que pode ser executado com a sintaxe `o()`. Isso inclui funções, métodos, classes e outros objetos, como veremos em detalhes no <>. A documentação do Python usa o termo "chamável". Adotamos "invocável" para evitar confusão entre dois sentidos do verbo "chamar": executar (uma função) ou nomear (a função chama-se...).] ou corrotina|`+__call__ __await__+` +|Gerenciamento de contexto|`+__enter__ __exit__ __aexit__ __aenter__+` +|Criação e destruição de instâncias|`+__new__ __init__ __del__+` +|Gerenciamento de atributos|`+__getattr__ __getattribute__ __setattr__ __delattr__ __dir__+` +|Descritores de atributos|`+__get__ __set__ __delete__ __set_name__+` +|Classes base abstratas|`+__instancecheck__ __subclasscheck__+` +|Metaprogramação de classes|`+__prepare__ __init_subclass__ __class_getitem__ __mro_entries__+` +|================================================================================================= + +Operadores infixos e numéricos são suportados pelos métodos especiais listados na +<>. +Aqui os nomes mais recentes são `+__matmul__+`, `+__rmatmul__+`, e `+__imatmul__+`, +adicionados no Python 3.5 para suportar o uso de `@` como operador de multiplicação de matrizes, +como veremos no <>.((("special methods", "special method names and symbols for operators"))) + +[[special_operators_tbl]] +.Nomes e símbolos de métodos especiais para operadores +[options="header"] +[cols="2,3,5"] +|===================================================================================================================================================================================== +|Categoria do operador|Símbolos|Nomes de métodos +|Unário numérico| `- + abs()` | `+__neg__ __pos__ __abs__+` +|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 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__+` +|===================================================================================================================================================================================== + +[NOTE] +==== +Python invoca um método especial de operador reverso no segundo argumento +quando o método especial correspondente não pode ser usado no primeiro operando. +Atribuições aumentadas são atalho combinando um operador infixo com uma atribuição de variável, por exemplo `a += b`. + +O <> explica em detalhes os operadores reversos e a atribuição aumentada.((("", startref="PDMspmtov01"))) +==== + + +=== Por que len não é um método? + +Em 2013, fiz((("Python Data Model", "making len work with custom objects")))((("__len__"))) +essa pergunta a Raymond Hettinger, um dos mantenedores do Python, +e sua resposta foi basicamente uma citação do https://fpy.li/1-8["The Zen of Python" (_O Zen do Python_)] (EN): "a praticidade vence a pureza." +Na <>, descrevi como `len(x)` roda muito rápido quando `x` é uma instância de um tipo embutido. +Nenhum método é chamado para os objetos embutidos do CPython: o tamanho é simplesmente lido de um campo em uma struct C. +Obter o número de itens em uma coleção é uma operação comum, e precisa funcionar de forma eficiente para tipos tão básicos e diferentes como +`str`, `list`, `memoryview`, e assim por diante. + +Em outras palavras, `len` não é chamado como um método porque recebe um tratamento especial como parte do Modelo de Dados de Python, +da mesma forma que `abs`. +Mas graças ao método especial `+__len__+`, também é possível fazer `len` funcionar com nossas classes. +Isso é um compromisso justo entre a necessidade de objetos embutidos eficientes e a consistência da linguagem. +Também de "O Zen de Python": "Casos especiais não são especiais o bastante para quebrar as regras." + + +[NOTE] +==== +Pensar em `abs` e `len` como operadores unários nos deixa mais inclinados a perdoar seus aspectos funcionais, +contrários à sintaxe de chamada de método que esperaríamos em uma linguagem orientada a objetos. +De fato, Python herdou muito de sua sintaxe e estruturas de dados da linguagem ABC, +onde existe o operador `#`, +que equivale ao `len`: em ABC, `len(s)` escreve-se `#s`. +Quando usado como operador infixo, +`x#s` conta as ocorrências de `x` em `s`, +que em Python obtemos com `s.count(x)`, +para qualquer sequência `s`. +==== + +[role="pagebreak-before less_space"] +=== Resumo do capítulo + +Ao((("Python Data Model", "overview of"))) implementar métodos especiais, +as classes que você cria podem se comportar como os tipos embutidos, +permitindo o estilo de programação expressivo que a comunidade considera _pythônico_. + +Uma exigência básica para um objeto em Python é fornecer strings representando a si mesmo que possam ser usadas, +uma para depuração e registro (_log_), outra para apresentar aos usuários finais. +É para isso que os métodos especiais `+__repr__+` e `+__str__+` existem no modelo de dados. + +Emular sequências, como mostrado com o exemplo do `FrenchDeck`, é um dos usos mais comuns dos métodos especiais. +Por exemplo, bibliotecas de banco de dados frequentemente devolvem resultados de consultas na forma de coleções similares a sequências. +Tirar o máximo proveito dos tipos de sequências existentes é o assunto do <>. +Como implementar suas próprias sequências será visto no <>, +onde criaremos uma extensão multidimensional da classe `Vector`. + +Graças à sobrecarga de operadores, Python oferece uma rica seleção de tipos numéricos, +desde os tipos embutidos até `decimal.Decimal` e `fractions.Fraction`, +todos eles suportando operadores aritméticos infixos. +As bibliotecas de ciência de dados _NumPy_ suportam operadores infixos com matrizes e tensores. +A implementação de operadores—incluindo operadores reversos e atribuição aumentada—será vista no <>, +usando melhorias do exemplo `Vector`. + +Também veremos o uso e a implementação da maioria dos outros métodos especiais do Modelo de Dados de Python ao longo deste livro. + + +=== Para saber mais + +O((("Python Data Model", "further reading on"))) +https://fpy.li/2j[capítulo "Modelo de Dados"] +em _A Referência da Linguagem Python_ é a fonte canônica para o assunto desse capítulo e de uma boa parte deste livro. + +https://fpy.li/pynut3[Python in a Nutshell, 3rd ed.] +(EN), de Alex Martelli, Anna Ravenscroft, e Steve Holden (O'Reilly) +tem uma excelente cobertura do modelo de dados. +Sua descrição da mecânica de acesso a atributos é a mais competente que já vi, +perdendo apenas para o próprio código-fonte em C do CPython. +Martelli também é um contribuidor prolífico do Stack Overflow, +com mais de 6200 respostas publicadas. +Veja seu perfil de usuário no +https://fpy.li/1-9[Stack Overflow]. + +David Beazley tem dois livros tratando do modelo de dados em detalhes, no contexto de Python 3: +https://fpy.li/2k[Python Essential Reference] +(EN), 4th ed. (Addison-Wesley), e +https://fpy.li/pycook3[Python Cookbook, 3rd ed] +(EN) (O'Reilly), colaborando com Brian K. Jones. + +https://fpy.li/2m[The Art of the Metaobject Protocol] +(EN) (MIT Press) de Gregor Kiczales, Jim des Rivieres, e Daniel G. Bobrow +explica o conceito de um protocolo de metaobjetos, do qual o Modelo de Dados de Python é um exemplo. + + +.Ponto de Vista +**** + +[role="soapbox-title"] +**Modelo de dados ou modelo de objetos?** + + +Aquilo((("Soapbox sidebars", "data model versus object model")))((("Python Data Model", "Soapbox discussion"))) +que a documentação de Python chama de "Modelo de Dados de Python", a maioria dos autores diria que é o "Modelo de objetos de Python" + +O _Python in a Nutshell_, 3rd ed. de Martelli, Ravenscroft, e Holden, e o _Python Essential Reference_, 4th ed., +de David Beazley são os melhores livros sobre o Modelo de Dados de Python, mas se referem a ele como o "modelo de objetos." +Na Wikipedia, a primeira definição de +https://fpy.li/1-10["modelo de objetos"] +(EN) é: "as propriedades dos objetos em geral em uma linguagem de programação de computadores específica." +É disso que o Modelo de Dados de Python trata. +Neste livro, usarei "modelo de dados" porque esse é o título do +https://fpy.li/2j[capítulo de _A Referência da Linguagem Python_] +mais relevante para nossas discussões. + +[role="soapbox-title"] +**Métodos de "trouxas"** + +https://fpy.li/1-11[_The Original Hacker's Dictionary_ (_Dicionário Hacker Original_)] +(EN) define ((("Soapbox sidebars", "magic methods")))((("magic methods"))) _mágica_ como +"algo ainda não explicado ou muito complicado para explicar" ou "uma funcionalidade, +em geral não divulgada, que permite fazer algo que de outra forma seria impossível." + +Ruby tem o equivalente aos métodos especiais, chamados de _métodos mágicos_ naquela comunidade. +Alguns na comunidade Python também adotam esse termo. +Acredito que os métodos especiais são o contrário de mágica. +Python e Ruby oferecem a seus usuários um rico protocolo de metaobjetos integralmente documentado, +permitindo que "trouxas" como você e eu possam emular muitas das funcionalidades disponíveis para os +mantenedores que escrevem os interpretadores daquelas linguagens. + +Por outro lado, pense em Go. +Alguns objetos naquela linguagem tem funcionalidades que são mágicas, +no sentido de não poderem ser emuladas em nossos próprios objetos definidos pelo usuário. +Por exemplo, os arrays, strings e mapas de Go suportam o uso de colchetes para acesso a um item, na forma `a[i]`. +Mas não há como fazer a notação `[]` funcionar com um novo tipo de coleção definida por você. + +Talvez, no futuro, os projetistas de Go melhorem seu protocolo de metaobjetos. +Em 2021, ele ainda é mais limitado do que Python, Ruby, e JavaScript oferecem. + + +[role="soapbox-title"] +**Metaobjetos** + +_The Art of the Metaobject Protocol (AMOP)_ (_A Arte do protocolo de metaobjetos_) +é((("Soapbox sidebars", "metaobjects")))((("metaobjects"))) meu título favorito entre livros de computação. +Mas o menciono aqui porque o termo _protocolo de metaobjetos_ é útil para pensar sobre o Modelo de Dados de Python, +e sobre recursos similares em outras linguagens. +A parte _metaobjetos_ se refere aos objetos que são os componentes essenciais da própria linguagem. +Nesse contexto, _protocolo_ é sinônimo de _interface_. +Assim, um _protocolo de metaobjetos_ é um sinônimo chique para modelo de objetos: +uma API para os elementos fundamentais da linguagem. + +Um protocolo de metaobjetos rico permite estender a linguagem para suportar novos paradigmas de programação. +Gregor Kiczales, o primeiro autor do _AMOP_, mais tarde se tornou um pioneiro da programação orientada a aspecto, +e o autor inicial do AspectJ, uma extensão de Java implementando aquele paradigma. +A programação orientada a aspecto é mais fácil de implementar em uma linguagem dinâmica como Python, +e alguns frameworks fazem exatamente isso. +Um exemplo importante é a +https://fpy.li/1-12[_zope.interface_] +(EN), parte do framework Zope sobre o qual o sistema de gerenciamento de conteúdo +https://fpy.li/2n[Plone] é construído. +**** + diff --git a/capitulos/cap02.adoc b/online/cap02.adoc similarity index 50% rename from capitulos/cap02.adoc rename to online/cap02.adoc index 2425f3f4..e3c22ab7 100644 --- a/capitulos/cap02.adoc +++ b/online/cap02.adoc @@ -1,58 +1,54 @@ -:xrefstyle: short +[[ch_sequences]] +== Uma coleção de sequências :example-number: 0 :figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte - -[[sequences]] -== Uma coleção de sequências -// [quote, Leo Geurts, Lambert Meertens, and Steven Pembertonm, ABC Programmer's Handbook (Bosko Books)] -// ____ -// As you may have noticed, several of the operations mentioned work equally for texts, lists and tables. -// Texts, lists and tables together are called 'trains'. [...] The +FOR+ command also works generically on trains. -// ____ -// footnote:[Leo Geurts, Lambert Meertens, and Steven Pemberton, _ABC Programmer's Handbook_, p. 8. (Bosko Books)] +[quote, Leo Geurts, Lambert Meertens, e Steven Pembertonm, ABC Programmer's Handbook (Bosko Books) p.8] +____ +Como podem ter notado, várias das operações mencionadas funcionam da mesma forma com textos, listas e tabelas. +Coletivamente, textos, listas e tabelas são chamados de 'trens' (_trains_). [...] +A instrução `FOR` também funciona, de forma genérica, com trens. +____ -++++ -
-

Como vocês podem ter notado, várias das operações mencionadas funcionam da mesma forma com textos, listas e tabelas. Coletivamente, textos, listas e tabelas são chamados de 'trens' (trains). [...] O comando `FOR` também funciona, de forma geral, em trens.

-

Leo Geurts, Lambert Meertens, e Steven Pembertonm, ABC Programmer's Handbook, p. 8. (Bosko Books)

-
-++++ -Antes de criar o Python, Guido foi um dos desenvolvedores da linguagem ABC—um projeto de pesquisa de 10 anos para criar um ambiente de programação para iniciantes. -A ABC introduziu várias ideias que hoje consideramos "pithônicas": operações genéricas com diferentes tipos de sequências, tipos tupla e mapeamento embutidos, estrutura [do código] por indentação, tipagem forte sem declaração de variáveis, entre outras. -O Python não é assim tão amigável por acidente. +Antes de criar Python, Guido foi um dos desenvolvedores da linguagem +ABC—um projeto de pesquisa de 10 anos para criar um ambiente de programação para iniciantes. +A ABC introduziu várias ideias que hoje consideramos "pythônicas": +operações genéricas com diferentes tipos de sequências, tipos tupla e mapeamento embutidos, +estrutura de blocos por indentação, tipagem forte sem declaração de variáveis, entre outras. +A usabilidade de Python não é acidental. -O Python herdou da ABC o tratamento uniforme de sequências. Strings, listas, sequências de bytes, arrays, elementos XML e resultados vindos de bancos de dados compartilham um rico conjunto de operações comuns, incluindo iteração, fatiamento, ordenação e concatenação. +Python herdou da ABC o tratamento uniforme de sequências. +Strings, listas, sequências de bytes, arrays, elementos XML e resultados vindos de bancos de dados +compartilham um rico conjunto de operações comuns, incluindo iteração, fatiamento, ordenação e concatenação. -Entender a variedade de sequências disponíveis no Python evita que reinventemos a roda, e sua interface comum nos inspira a criar APIs que suportem e se aproveitem de forma apropriada dos tipos de sequências existentes e futuras. +Entender a variedade de sequências disponíveis no Python evita reinventar a roda, +e sua interface comum nos inspira a criar APIs que +suportem e aproveitem bem os tipos de sequências existentes e futuros. -A maior parte da discussão deste capítulo se aplica às sequências em geral, desde a conhecida `list` até os tipos `str` e `bytes`, adicionados no Python 3. Tópicos específicos sobre listas, tuplas, arrays e filas também foram incluídos, mas os detalhes sobre strings Unicode e sequências de bytes são tratados no <>. +A maior parte deste capítulo trata das sequências em geral, +desde a conhecida `list` até os tipos `str` e `bytes`, adicionados no Python 3. +Tópicos específicos sobre listas, tuplas, arrays e filas também foram incluídos, +mas os detalhes sobre strings Unicode e sequências de bytes são tratados no <>. Além disso, a ideia aqui é falar sobre os tipos de sequências prontas para usar. -A criação de novos tipos de sequência é o tema do <>. +A criação de novos tipos de sequência é o tema do <>. Os((("sequences", "topics covered"))) principais tópicos cobertos neste capítulo são: -* Compreensão de listas e os fundamentos das expressões geradoras. -* O uso de tuplas como registros versus o uso de tuplas como listas imutáveis -* Desempacotamento de sequências e padrões de sequências. -* Lendo de fatias e escrevendo em fatias -* Tipos especializados de sequências, tais como arrays e filas +* Compreensão de listas e os fundamentos das expressões geradoras +* Usar tuplas como registros ou como listas imutáveis +* Desempacotamento de sequências e padrões de sequências +* Fatiamento de sequências para leitura e escrita +* Tipos especializados de sequências, como arrays e filas === Novidades neste capítulo A((("sequences", "significant changes to"))) atualização mais importante desse capítulo é -a seção <>, +a <>, primeira abordagem das instruções `match/case` introduzidas no Python 3.10. -As outras mudanças não são atualizações e sim aperfeiçoamentos da primeira edição: +As outras mudanças são aperfeiçoamentos da primeira edição: * Um novo diagrama e uma nova descrição do funcionamento interno das sequências, contrastando contêineres e sequências planas. @@ -61,7 +57,7 @@ sequências, contrastando contêineres e sequências planas. * Ressalvas sobre tuplas com elementos mutáveis, e como detectá-los se necessário. -Movi a discussão sobre tuplas nomeadas para a seção <> no <>, +Movi a discussão sobre tuplas nomeadas para a <> (<>), onde elas são comparadas com `typing.NamedTuple` e `@dataclass`. [NOTE] @@ -69,38 +65,46 @@ onde elas são comparadas com `typing.NamedTuple` e `@dataclass`. Para abrir espaço para conteúdo novo mantendo o número de páginas dentro do razoável, a seção "Managing Ordered Sequences with Bisect" ("_Gerenciando sequências ordenadas com bisect_") da primeira edição agora é um https://fpy.li/bisect[artigo] (EN) -no site que complementa o livro, -pass:[fluentpython.com]. +no site que complementa o livro. ==== === Uma visão geral das sequências embutidas -A((("sequences", "overview of built-in", id="Soverv02"))) biblioteca padrão oferece uma boa seleção de tipos de sequências, -implementadas em C: +A((("sequences", "overview of built-in", id="Soverv02"))) biblioteca padrão oferece +uma boa seleção de tipos de sequências, implementadas em C: Sequências contêiner:: - Podem((("container sequences"))) armazenar itens de tipos diferentes, incluindo contêineres aninhados e objetos de qualquer tipo. Alguns exemplos: `list`, `tuple`, e `collections.deque`. + Podem((("container sequences"))) armazenar itens de tipos diferentes, incluindo contêineres aninhados e objetos de qualquer tipo. + Alguns exemplos: `list`, `tuple`, e `collections.deque`. Sequências planas:: - Armazenam((("flat sequences"))) itens de algum tipo simples, mas não outras coleções ou referências a objetos. Alguns exemplos: `str`, `bytes`, e `array.array`. + Armazenam((("flat sequences"))) itens de algum tipo simples, mas não outras coleções ou referências a objetos. + Alguns exemplos: `str`, `bytes`, e `array.array`. -Uma((("tuples", "simplified memory diagram for")))((("arrays"))) _sequência contêiner_ mantém referências para os objetos que contém, que podem ser de qualquer tipo, enquanto uma _sequência plana_ armazena o valor de seu conteúdo em seu próprio espaço de memória, e não como objetos Python distintos. Veja a <>. +Uma((("tuples", "simplified memory diagram for")))((("arrays"))) _sequência contêiner_ mantém +referências para os objetos que contém, que podem ser de qualquer tipo, +enquanto uma _sequência plana_ armazena o valor de seu conteúdo em seu próprio espaço de memória, +e não como objetos Python distintos. +Veja a <>. [[container_v_flat_img]] -.Diagramas de memória simplificados mostrando uma `tuple` e um `array`, cada uma com três itens. As células em cinza representam o cabeçalho de cada objeto Python na memória. A `tuple` tem um array de referências para seus itens. Cada item é um objeto Python separado, possivelmente contendo também referências aninhadas a outros objetos Python, como aquela lista de dois itens. Por outro lado, um `array` Python é um único objeto, contendo um array da linguagem C com três números de ponto flutuante`. -image::images/flpy_0201.png[Diagrama de memória simplificado de um `array` e de uma `tuple`] +.Diagramas de memória simplificados mostrando uma `tuple` e um `array`, cada uma com três itens. As células em cinza representam o cabeçalho de cada objeto Python na memória. A `tuple` tem um array de ponteiros para seus itens. Cada item é um objeto Python separado, possivelmente contendo também referências aninhadas a outros objetos Python, como aquela lista de dois itens. Por outro lado, um `array` Python é um único objeto, contendo um array da linguagem C com três números de ponto flutuante no formato nativo da CPU. +image::../images/flpy_0201.png[Diagrama de memória simplificado de um `array` e de uma `tuple`] -Dessa forma, sequências planas são mais compactas, mas estão limitadas a manter valores primitivos como bytes e números inteiros e de ponto flutuante. +Dessa forma, sequências planas são mais compactas, mas só podem armazenar valores primitivos como +bytes e números inteiros e de ponto flutuante. [NOTE] ==== -Todo objeto Python na memória tem um cabeçalho com metadados. O objeto Python mais simples, um `float`, tem um campo de valor e dois campos de metadados: +Todo objeto Python na memória tem um cabeçalho com metadados. +O objeto Python mais simples, um `float`, tem um campo de valor e dois campos de metadados: * `ob_refcnt`: a contagem de referências ao objeto * `ob_type`: um ponteiro para o tipo do objeto * `ob_fval`: um `double` de C mantendo o valor do `float` No Python 64-bits, cada um desses campos ocupa 8 bytes. -Por isso um array de números de ponto flutuante é muito mais compacto que uma tupla de números de ponto flutuante: o array é um único objeto contendo apenas o valor dos números, +Por isso um array de números de ponto flutuante é mais compacto que uma tupla de números de ponto flutuante: +o array é um único objeto contendo apenas o valor dos números, enquanto a tupla consiste de vários objetos—a própria tupla e cada objeto `float` que ela contém. ==== @@ -112,10 +116,14 @@ Sequências mutáveis:: Sequências imutáveis:: Por((("immutable sequences"))) exemplo, `tuple`, `str`, e `bytes`. -A <> ajuda a visualizar como as sequências mutáveis herdam todos os métodos das sequências imutáveis e implementam vários métodos adicionais. -Os tipos embutidos concretos de sequências na verdade não são subclasses das classes base abstratas (ABCs) `Sequence` e `MutableSequence`, mas sim _subclasses virtuais_ registradas com aquelas ABCs—como veremos no <>. Por serem subclasses virtuais, `tuple` e `list` passam nesses testes: +A <> ajuda a visualizar como as sequências mutáveis herdam todos os métodos +das sequências imutáveis e implementam vários métodos adicionais. +Os tipos embutidos de sequências na verdade não são subclasses das classes base abstratas (ABCs) +`Sequence` e `MutableSequence`, mas sim _subclasses virtuais_ registradas com aquelas +ABCs—como veremos no <>. +Por serem subclasses virtuais, `tuple` e `list` passam nesses testes: -[source, pycon] +[source, python] ---- >>> from collections import abc >>> issubclass(tuple, abc.Sequence) @@ -126,38 +134,49 @@ True [[sequence_uml]] .Diagrama de classe UML simplificado para algumas classes de collections.abc (as superclasses estão à esquerda; as setas de herança apontam das subclasses para as superclasses; nomes em itálico indicam classes e métodos abstratos). -image::images/flpy_0202.png[Diagrama de classe UML para `Sequence` e `MutableSequence`] +image::../images/flpy_0202.png[Diagrama de classe UML para `Sequence` e `MutableSequence`] -Lembre-se((("UML class diagrams", "simplified for collections.abc"))) dessas características básicas: mutável versus imutável; contêiner versus plana. +Lembre-se((("UML class diagrams", "simplified for collections.abc"))) dessas características básicas: +mutável versus imutável; contêiner versus plana. Elas ajudam a extrapolar o que se sabe sobre um tipo de sequência para outros tipos. O tipo mais fundamental de sequência é a lista: um contêiner mutável. -Espero que você já esteja muito familiarizada com listas, então vamos passar diretamente para a compreensão de listas, uma forma potente de criar listas que algumas vezes é subutilizada por sua sintaxe parecer, a princípio, estranha. -Dominar as compreensões de listas abre as portas para expressões geradoras que—entre outros usos—podem produzir elementos para preencher sequências de qualquer tipo. +Espero que você já esteja muito familiarizada com listas, +então vamos passar diretamente para a compreensão de listas, +uma forma potente de criar listas que algumas vezes é subutilizada por sua sintaxe parecer, a princípio, estranha. +Dominar as compreensões de listas abre as portas para expressões geradoras que—entre outros usos—podem produzir +elementos para preencher sequências de qualquer tipo. Ambas são temas da próxima seção.((("", startref="Soverv02"))) === Compreensões de listas e expressões geradoras -Um((("sequences", "list comprehensions and generator expressions", id="Slist02")))((("list comprehensions (listcomps)", "building sequences with"))) jeito rápido de criar uma sequência é usando uma compreensão de lista (se o alvo é uma `list`) ou uma expressão geradora (para outros tipos de sequências). -Se você não usa essas formas sintáticas diariamente, aposto que está perdendo oportunidades de escrever código mais legível e, muitas vezes, mais rápido também. +Um((("sequences", "list comprehensions and generator expressions", id="Slist02")))((("list comprehensions (listcomps)", +"building sequences with"))) +jeito rápido de criar uma sequência é usando uma compreensão de lista (se o alvo é uma `list`) ou +uma expressão geradora (para outros tipos de sequências). +Se você não usa essas formas sintáticas diariamente, +aposto que está perdendo oportunidades de escrever código mais legível e, muitas vezes, mais rápido também. -Se você duvida de minha alegação, sobre essas formas serem "mais legíveis", continue lendo. Vou tentar convencer você. +Se você duvida de minha alegação, sobre essas formas serem "mais legíveis", continue lendo. +Vou tentar convencer você. [TIP] ==== -Por comodidade, muitos programadores Python se referem a compreensões de listas como _listcomps_, e a expressões geradoras como _genexps_. +Por comodidade, muitos programadores Python se referem a compreensões de listas como _listcomps_, +e a expressões geradoras como _genexps_. Usarei também esses dois termos. ==== ==== Compreensões de lista e legibilidade -Aqui((("list comprehensions (listcomps)", "readability and"))) está um teste: qual dos dois você acha mais fácil de ler, o <> ou o <>? +Em sua opinião((("list comprehensions (listcomps)", "readability and"))) +qual desses exemplos é mais fácil de ler, o <> ou o <>? [[ex_build_list]] -.Cria uma lista de pontos de código Unicode a partir de uma string +.Cria uma lista de códigos Unicode a partir de uma string ==== -[source, pycon] +[source, python] ---- >>> symbols = '$¢£¥€¤' >>> codes = [] @@ -170,9 +189,9 @@ Aqui((("list comprehensions (listcomps)", "readability and"))) está um teste: q ==== [[ex_listcomp0]] -.Cria uma lista de pontos de código Unicode a partir de uma string, usando uma listcomp +.Cria uma lista de códigos Unicode a partir de uma string, usando uma listcomp ==== -[source, pycon] +[source, python] ---- >>> symbols = '$¢£¥€¤' >>> codes = [ord(symbol) for symbol in symbols] @@ -182,31 +201,56 @@ Aqui((("list comprehensions (listcomps)", "readability and"))) está um teste: q ==== 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. +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: percorrer uma sequência para contar ou encontrar itens, computar valores agregados (somas, médias), ou inúmeras outras tarefas. +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. -Uma listcomp, por outro lado, é mais clara. Seu objetivo é sempre criar uma nova lista. +Uma listcomp, por outro lado, é mais clara. +Seu objetivo é sempre criar uma nova lista. -Naturalmente, é possível abusar das compreensões de lista para escrever código verdadeiramente incompreensível. Já vi código Python usando listcomps apenas para repetir um bloco de código por seus efeitos colaterais. 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`. Avalie qual o melhor caminho: em Python, como em português, não existem regras absolutas para se escrever bem. +Naturalmente, é possível abusar das compreensões de lista para escrever código verdadeiramente incompreensível. +Já vi código Python usando listcomps apenas para repetir um bloco de código por seus efeitos colaterais. +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 laço `for`. +Avalie qual o melhor caminho: em Python, como em português, não existem regras absolutas para se escrever bem. .Dica de sintaxe [TIP] ==== -No((("list comprehensions (listcomps)", "syntax tip")))((("\ line continuation escape")))((("backslash (\)")))((("\ (backslash)")))((("line breaks")))((("multiline lists")))((("lists", "multiline")))((("square brackets ([])")))((("[] (square brackets)")))((("{} (curly brackets)")))((("curly brackets ({})"))) código Python, quebras de linha são ignoradas dentro de pares de `[]`, `{}`, ou `()`. -Então você pode usar múltiplas linhas para criar listas, listcomps, tuplas, dicionários, etc., sem necessidade de usar o marcador de continuação de linha `\`, que não funciona se após o `\` você acidentalmente digitar um espaço. -Outro detalhe, quando aqueles pares de delimitadores são usados para definir um literal com uma série de itens separados por vírgulas, uma vírgula solta no final será ignorada. -Daí, por exemplo, quando se codifica uma lista a partir de um literal com múltiplas linhas, é de bom tom deixar uma vírgula após o último item. Isso torna um pouco mais fácil ao próximo programador acrescentar mais um item àquela lista, e reduz o ruído quando se lê os diffs. +Em((("list comprehensions (listcomps)", "syntax tip")))((("\ line continuation escape")))((("backslash (\)")))((("\ (backslash)")))((("line breaks")))((("multiline lists")))((("lists", "multiline")))((("square brackets ([])")))((("[] (square brackets)")))((("{} (curly brackets)")))((("curly brackets ({})"))) +código Python, quebras de linha são ignoradas dentro de pares de `[]`, `{}`, ou `()`. +Então você pode usar múltiplas linhas para criar listas, listcomps, tuplas, dicionários, etc., +sem necessidade de usar o marcador de continuação de linha `\`, +que não funciona se após o `\` você acidentalmente digitar um espaço. +Outro detalhe, quando aqueles pares de delimitadores são usados para definir um literal com uma série de itens +separados por vírgulas, uma vírgula solta no final será ignorada. +Daí, por exemplo, quando se codifica uma lista a partir de um literal com múltiplas linhas, +é uma gentileza deixar uma vírgula após o último item. +Isso torna um pouco mais fácil ao próximo programador acrescentar mais um item àquela lista, +e reduz o ruído quando se lê os diffs. ==== .Escopo local dentro de compreensões e expressões geradoras **** -No((("scope", "within comprehensions and generator expressions")))((("generator expressions (genexps)")))((("list comprehensions (listcomps)", "local scope within"))) Python 3, compreensões de lista, expressões geradoras, e suas irmãs, as compreensões de `set` e de `dict`, tem um escopo local para manter as variáveis criadas na condição `for`. -Entretanto, variáveis atribuídas com o((("assignment expression (:=)")))((("Walrus operator (:=)")))(((":= (Walrus operator)"))) "operador morsa" (_"Walrus operator"_), `:=`, continuam acessíveis após aquelas compreensões ou expressões retornarem—diferente das variáveis locais em uma função. -A https://fpy.li/pep572[PEP 572—Assignment Expressions] (EN) define o escopo do alvo de um `:=` como a função à qual ele pertence, exceto se houver uma declaração `global` ou `nonlocal` para aquele alvo.footnote:[Agradeço à leitora Tina Lapine por apontar essa informação.] +No((("scope", "within comprehensions and generator expressions")))((("generator expressions (genexps)")))((("list comprehensions (listcomps)", "local scope within"))) +Python 3, compreensões de lista, expressões geradoras, e suas irmãs, as compreensões de `set` e de `dict`, +tem um escopo local para manter as variáveis criadas na condição `for`. +Entretanto, variáveis atribuídas com +o((("assignment expression (:=)")))((("Walrus operator (:=)")))(((":= (Walrus operator)"))) +"operador morsa" (_"Walrus operator"_), `:=`, continuam acessíveis após +aquelas compreensões ou expressões retornarem—diferente +das variáveis locais em uma função. +A https://fpy.li/pep572[PEP 572—Assignment Expressions] (EN) define o escopo do alvo de um `:=` +como a função à qual ele pertence, exceto se houver uma declaração `global` ou `nonlocal` para aquele +identificador.footnote:[Agradeço à leitora Tina Lapine por apontar essa informação.] -[source, pycon] +[source, python] ---- >>> x = 'ABC' >>> codes = [ord(x) for x in x] @@ -228,50 +272,60 @@ NameError: name 'c' is not defined **** -Compreensões de lista criam listas a partir de sequências ou de qualquer outro tipo iterável, filtrando e transformando os itens. -As funções embutidas `filter` e `map` podem fazer o mesmo, mas perde-se alguma legibilidade, como veremos a seguir. +Compreensões de lista criam listas a partir de sequências ou de qualquer outro tipo iterável, +filtrando e transformando os itens. +As funções embutidas `filter` e `map` podem fazer o mesmo, +mas perdemos legibilidade, como veremos a seguir. ==== Listcomps versus map e filter -Listcomps((("map function")))((("functions", "map function")))((("filter function")))((("functions", "filter, map, and reduce functions")))((("list comprehensions (listcomps)", "versus map and filter functions"))) fazem tudo que as funções `map` e `filter` fazem, sem os malabarismos exigidos pela funcionalidade limitada do `lambda` do Python. +Listcomps((("map function")))((("functions", "map function")))((("filter function")))((("functions", +"filter, map, and reduce functions")))((("list comprehensions (listcomps)", "versus map and filter functions"))) +fazem tudo que as funções `map` e `filter` fazem, +sem os malabarismos exigidos pela funcionalidade limitada do `lambda` de Python. Considere o <>. [[ex_listcomp_x_filter_map]] .A mesma lista, criada por uma listcomp e por uma composição de map/filter ==== -[source, pycon] +[source, python] ---- >>> symbols = '$¢£¥€¤' >>> beyond_ascii = [ord(s) for s in symbols if ord(s) > 127] >>> beyond_ascii [162, 163, 165, 8364, 164] ->>> beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols))) +>>> beyond_ascii = list(filter(lambda c: c > 127, +... map(ord, symbols))) >>> beyond_ascii [162, 163, 165, 8364, 164] ---- ==== -Eu acreditava que `map` e `filter` eram mais rápidas que as listcomps equivalentes, mas Alex Martelli +Eu imaginava que `map` e `filter` fossem mais rápidas que as listcomps equivalentes, mas Alex Martelli assinalou que não é o caso—pelo menos não nos exemplos acima. O script https://fpy.li/2-1[listcomp_speed.py] no -https://fpy.li/code[repositório de código do Python Fluente] é um teste de velocidade simples, comparando listcomp com `filter/map`. +https://fpy.li/code[repositório de código de Python Fluente] +é um teste de desempenho simples, comparando listcomp com `filter/map`. -Vou falar mais sobre `map` e `filter` no <>. -Vamos agora ver o uso de listcomps para computar produtos cartesianos: uma lista contendo tuplas criadas a partir de todos os itens de duas ou mais listas. +Vou falar mais sobre `map` e `filter` no <>. +Vamos agora ver o uso de listcomps para computar produtos cartesianos: +uma lista contendo tuplas criadas a partir de todos os itens de duas ou mais listas. [[cartesian_product_sec]] ==== Produtos cartesianos -Listcomps((("list comprehensions (listcomps)", "building lists from cartesian products")))((("Cartesian products", id="cartprod02"))) podem criar listas a partir do produto cartesiano de dois ou mais iteráveis. +Listcomps((("list comprehensions (listcomps)", "building lists from cartesian products")))((("Cartesian products", +id="cartprod02"))) +podem criar listas a partir do produto cartesiano de dois ou mais iteráveis. Os itens resultantes de um produto cartesiano são tuplas criadas com os itens de cada iterável na entrada, e -a lista resultante tem o tamanho igual ao produto da multiplicação dos tamanhos dos iteráveis usados. +a lista resultante tem o tamanho igual ao produto dos tamanhos dos iteráveis usados. Veja a <>. [[cartesian_product_fig]] -.O produto cartesiano de 3 valores de cartas e 4 naipes é uma sequência de 12 parâmetros. -image::images/flpy_0203.png[Diagrama do produto cartesiano] +.O produto cartesiano de 3 valores de cartas e 4 naipes é uma sequência de 12 itens. +image::../images/flpy_0203.png[Diagrama do produto cartesiano] Por exemplo, imagine que você precisa produzir uma lista de camisetas disponíveis em duas cores e três tamanhos. O <> mostra como produzir tal lista usando uma listcomp. @@ -280,11 +334,12 @@ O resultado tem seis itens. [[ex_listcomp_cartesian]] .Produto cartesiano usando uma compreensão de lista ==== -[source, pycon] +[source, python] ---- >>> colors = ['black', 'white'] >>> sizes = ['S', 'M', 'L'] ->>> tshirts = [(color, size) for color in colors for size in sizes] <1> +>>> tshirts = [(color, size) for color in colors +... for size in sizes] <1> >>> tshirts [('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), ('white', 'M'), ('white', 'L')] @@ -306,26 +361,37 @@ 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` 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`; adicionar uma quebra de linha listcomp torna mais fácil ver como o resultado será ordenado. +<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 <> (em <>), 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: +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: -[source, python3] +[source, python] ---- self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks] ---- -Listcomps são mágicos de um só truque: elas criam listas. +Listcomps são mágicas de um truque só: elas criam listas. Para gerar dados para outros tipos de sequências, uma genexp é o caminho. -A próxima seção é uma pequena incursão às genexps, no contexto de criação de sequências que não são listas.((("", startref="cartprod02"))) +A próxima seção é uma pequena incursão às genexps, no contexto de criação de sequências que não são +listas.((("", startref="cartprod02"))) ==== Expressões geradoras -Para((("generator expressions (genexps)")))((("list comprehensions (listcomps)", "versus generator expressions", secondary-sortas="generator expressions"))) inicializar tuplas, arrays e outros tipos de sequências, você também poderia começar de uma listcomp, mas uma genexp (expressão geradora) economiza memória, pois ela produz itens um de cada vez usando o protocolo iterador, em vez de criar uma lista inteira apenas para alimentar outro construtor. +Para((("generator expressions (genexps)")))((("list comprehensions (listcomps)", "versus generator expressions", +secondary-sortas="generator expressions"))) +inicializar tuplas, arrays e outros tipos de sequências, você também pode usar uma listcomp, +mas uma genexp (expressão geradora) economiza memória, +pois ela produz itens um de cada vez usando o protocolo iterador, +em vez de criar uma lista inteira apenas para alimentar outro construtor. As genexps usam a mesma sintaxe das listcomps, mas são delimitadas por parênteses em vez de colchetes. @@ -334,7 +400,7 @@ O <> demonstra o uso básico de genexps para criar uma tupla e u [[ex_genexp_load]] .Inicializando uma tupla e um array a partir de uma expressão geradora ==== -[source, pycon] +[source, python] ---- >>> symbols = '$¢£¥€¤' >>> tuple(ord(symbol) for symbol in symbols) <1> @@ -344,23 +410,26 @@ O <> demonstra o uso básico de genexps para criar uma tupla e u array('I', [36, 162, 163, 165, 8364, 164]) ---- ==== -<1> Se a expressão geradora é o único argumento em uma chamada de função, não há necessidade de duplicar os parênteses circundantes. -<2> O construtor de `array` espera dois argumentos, então os parênteses em torno da expressão geradora são obrigatórios. -O primeiro argumento do construtor de `array` define o tipo de armazenamento usado para os números no array, como veremos na seção <>. +<1> Se a expressão geradora é o único argumento em uma chamada de função, +não há necessidade de duplicar os parênteses circundantes. +<2> O construtor de `array` espera dois argumentos, +então os parênteses em torno da expressão geradora são obrigatórios. +O primeiro argumento do construtor de `array` define o tipo de armazenamento usado para os números no array, +como veremos na <>. 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 ==== -[source, pycon] +[source, python] ---- >>> colors = ['black', 'white'] >>> sizes = ['S', 'M', 'L'] @@ -375,40 +444,50 @@ white M white L ---- ==== -<1> A expressão geradora produz um item por vez; uma lista com todas as seis variações de camisetas nunca aparece neste exemplo. +<1> A expressão geradora produz um item por vez; uma lista com todas as seis variações de camisetas +nunca é criada neste exemplo. [NOTE] ==== -O <> explica em detalhes o funcionamento de geradoras. -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. +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. ==== -Vamos agora estudar outra sequência fundamental do Python: a tupla. +Vamos agora estudar outra sequência fundamental de Python: a tupla. [[tuples_more_than_lists_sec]] === Tuplas não são apenas listas imutáveis -Alguns((("sequences", "tuples", id="Stup02"))) textos introdutórios de Python apresentam as tuplas como "listas imutáveis", mas isso é subestimá-las. Tuplas tem duas funções: elas podem ser usada como listas imutáveis e também como registros sem nomes de campos. +Alguns((("sequences", "tuples", id="Stup02"))) textos introdutórios de Python apresentam as tuplas como +"listas imutáveis", mas isso é subestimá-las. +Tuplas têm dois usos: como listas imutáveis ou como registros com campos sem nome. Esse uso algumas vezes é negligenciado, então vamos começar por ele.((("", startref="Slist02"))) ==== Tuplas como registros Tuplas podem conter registros: -cada((("tuples", "as records", secondary-sortas="records", id="tuple02"))) item na tupla contém os dados de um campo, e a posição do item indica seu significado. +cada((("tuples", "as records", secondary-sortas="records", id="tuple02"))) +item na tupla contém os dados de um campo, e a posição do item indica seu significado. -Se você pensar em uma tupla apenas como uma lista imutável, a quantidade e a ordem dos elementos pode ou não ter alguma importância, dependendo do contexto. Mas quando usamos uma tupla como uma coleção de campos, o número de itens em geral é fixo e sua ordem é sempre importante. +Se você pensar em uma tupla apenas como uma lista imutável, +a quantidade e a ordem dos elementos pode ser importante ou não, dependendo do contexto. +Mas quando usamos uma tupla como uma coleção de campos, +o número de itens em geral é fixo e sua ordem é sempre importante. O <> mostras tuplas usadas como registros. -Observe que, em todas as expressões, ordenar a tupla destruiria a informação, pois o significado de cada campo é dado por sua posição na tupla. +Observe que, em todas as expressões, ordenar a tupla destruiria a informação, +pois o significado de cada campo é dado por sua posição na tupla. [[ex_tuples_as_records]] .Tuplas usadas como registros ==== -[source, pycon] +[source, python] ---- >>> lax_coordinates = (33.9425, -118.408056) <1> ->>> city, year, pop, chg, area = ('Tokyo', 2003, 32_450, 0.66, 8014) <2> +>>> city, year, pop, chg, area = ( +... 'Tokyo', 2003, 32_450, 0.66, 8014) <2> >>> traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), <3> ... ('ESP', 'XDA205856')] >>> for passport in sorted(traveler_ids): <4> @@ -430,26 +509,29 @@ ESP <3> Uma lista de tuplas no formato +(código_de_país, número_do_passaporte)+. <4> Iterando sobre a lista, `passport` é vinculado a cada tupla. <5> O operador de formatação `%` entende as tuplas e trata cada item como um campo separado. -<6> O loop `for` sabe como recuperar separadamente os itens de uma tupla—isso é chamado "desempacotamento" ("_unpacking_"). +<6> A instrução `for` sabe como recuperar separadamente os itens de uma tupla—isso é chamado "desempacotamento" (_unpacking_). Aqui não estamos interessados no segundo item, então o atribuímos a `_`, -uma variável descartável, usada apenas para coletar valores que não serão usados. +uma variável descartável, apenas para coletar valores que não usaremos. [TIP] ==== 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. Veja a seção <>. -E no console do Python, o resultado do comando anterior é atribuído a `+_+`—a menos que o resultado seja `None`. +Entretanto, em uma instrução `match/case`, o `+_+` é um coringa que corresponde a qualquer 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`. ==== Muitas vezes pensamos em registros como estruturas de dados com campos nomeados. -O <> apresenta duas formas de criar tuplas com campos nomeados. +O <> apresenta duas formas de criar tuplas com campos nomeados. Mas muitas vezes não é preciso se dar ao trabalho de criar uma classe apenas para nomear os campos, especialmente se você aproveitar o desempacotamento e evitar o uso de índices para acessar os campos. No <>, atribuímos -`('Tokyo', 2003, 32_450, 0.66, 8014)` a `city, year, pop, chg, area` em um único comando. -E daí o operador `%` atribuiu cada item da tupla `passport` para a posição correspondente da string de formato no argumento `print`. +`('Tokyo', 2003, 32_450, 0.66, 8014)` a `city, year, pop, chg, area` em uma única instrução. +E daí o operador `%` atribuiu cada item da tupla `passport` para a posição correspondente +da string de formato passada a `print`. Esses foram dois exemplos de _desempacotamento de tuplas_. @@ -460,36 +542,37 @@ O((("tuples", "tuple unpacking"))) termo "desempacotamento de tuplas" (_tuple un e está ganhando popularidade, como no título da https://fpy.li/2-2[PEP 3132 -- Extended Iterable Unpacking (_Desempacotamento Estendido de Iteráveis_)]. -A seção <> fala muito mais sobre desempacotamento, +A <> fala mais sobre desempacotamento, não apenas de tuplas, mas também de sequências e iteráveis em geral. ==== -//// -PROD: The note above is one more example that runs over the next paragraph in the rendered PDF. -//// - -Agora vamos considerar o uso da classe `tuple` como uma variante imutável da classe `list`.((("", startref="tuple02"))) +Agora vamos considerar o uso da classe `tuple` como uma variante imutável da classe +`list`.((("", startref="tuple02"))) ==== Tuplas como listas imutáveis -O((("tuples", "as immutable lists", secondary-sortas="immutable lists", id="Timlist02")))((("lists", "using tuples as immutable", id="Ltupple02"))) interpretador Python e a biblioteca padrão fazem uso extensivo das tuplas como listas imutáveis, e você deveria seguir o exemplo. Isso traz dois benefícios importantes: +O((("tuples", "as immutable lists", secondary-sortas="immutable lists", id="Timlist02")))((("lists", "using tuples as immutable", +id="Ltupple02"))) +interpretador Python e a biblioteca padrão fazem uso extensivo das tuplas como listas imutáveis, +e você deve seguir o exemplo. +Isso traz dois benefícios importantes: Clareza:: Quando você vê uma `tuple` no código, sabe que seu tamanho nunca mudará. Desempenho:: Uma `tuple` usa menos memória que uma `list` de mesmo tamanho, e permite ao Python realizar algumas otimizações. -Entretanto, lembre-se que a imutabilidade de uma `tuple` só se aplica às referências ali contidas. -Referências em um tupla não podem ser apagadas ou substituídas. +Entretanto, lembre-se de que a imutabilidade de uma `tuple` só se aplica às referências ali contidas. +Referências em uma tupla não podem ser apagadas ou substituídas. Mas se uma daquelas referências apontar para um objeto mutável, e aquele objeto mudar, então o valor da `tuple` muda. O próximo trecho de código ilustra esse fato criando duas tuplas—`a` e `b`— que inicialmente são iguais. A <> representa a disposição inicial da tupla `b` na memória. [[tuple_mutable]] .O conteúdo em si da tupla é imutável, mas isso significa apenas que as referências mantidas pela tupla vão sempre apontar para os mesmos objetos. Entretanto, se um dos objetos referenciados for mutável—uma lista, por exemplo—seu conteúdo pode mudar. -image::images/flpy_0204.png[Diagram de referências para uma tupla com três itens] +image::../images/flpy_0204.png[Diagram de referências para uma tupla com três itens] -Quando o último item em `b` muda, `b` e `a` se tornam diferentes: +Quando o último item em `b` muda, `a` e `b` se tornam diferentes: -[source, pycon] +[source, python] ---- >>> a = (10, 'alpha', [1, 2]) >>> b = (10, 'alpha', [1, 2]) @@ -503,12 +586,14 @@ False ---- Tuplas com itens mutáveis podem ser uma fonte de bugs. -Se uma tupla contém qualquer item mutável, ela não pode ser usada como chave em um `dict` ou como elemento em um `set`. -O motivo será explicado em <>. +Se uma tupla contém qualquer item mutável, +ela não pode ser usada como chave em um `dict` ou como elemento em um `set`. +O motivo será explicado na <>. -Se você quiser determinar explicitamente se uma tupla (ou qualquer outro objeto) tem um valor fixo, pode usar a função embutida `hash` para criar uma função `fixed`, assim: +Se você quiser determinar explicitamente se uma tupla (ou qualquer outro objeto) tem um valor fixo, +pode usar a função embutida `hash` para criar uma função `fixed`, assim: -[source, pycon] +[source, python] ---- >>> def fixed(o): ... try: @@ -525,53 +610,65 @@ True False ---- -Vamos aprofundar essa questão em <>. +Vamos aprofundar essa questão na <>. Apesar dessa ressalva, as tuplas são frequentemente usadas como listas imutáveis. -Elas oferecem algumas vantagens de desempenho, explicadas por uma dos desenvolvedores principais do Python, +Elas oferecem algumas vantagens de desempenho, explicadas por um dos mantenedores de Python, Raymond Hettinger, em uma resposta à questão -https://fpy.li/2-3["Are tuples more efficient than lists in Python?" (_As tuplas são mais eficientes que as listas no Python?_)] no StackOverflow. Em resumo, Hettinger escreveu: - -* Para avaliar uma tupla literal, o compilador Python gera bytecode para uma constante tupla em uma operação; mas para um literal lista, o bytecode gerado insere cada elemento como uma constante separada no stack de dados, e então cria a lista. -* Dada a tupla `t`, `tuple(t)` simplesmente devolve uma referência para a mesma `t`. Não há necessidade de cópia. Por outro lado, dada uma lista `l`, o construtor `list(l)` precisa criar uma nova cópia de `l`. -* Devido a seu tamanho fixo, uma instância de `tuple` tem alocado para si o espaço exato de memória que precisa. Em contrapartida, instâncias de `list` tem alocadas para si memória adicional, para amortizar o custo de acréscimos futuros. -* As referências para os itens em uma tupla são armazenadas em um array na struct da tupla, enquanto uma lista mantém um ponteiro para um array de referências armazenada em outro lugar. Essa indireção é necessária porque, quando a lista cresce além do espaço alocado naquele momento, o Python precisa realocar o array de referências para criar espaço. A indireção adicional torna o cache da CPU menos eficiente.((("", startref="Timlist02")))((("", startref="Ltupple02"))) +https://fpy.li/2-3["Are tuples more efficient than lists in Python?" (_As tuplas são mais eficientes que as listas no Python?_)] +no StackOverflow. +Em resumo, Hettinger escreveu: + +* Para avaliar uma tupla literal como `(1, 2, 3)`, o compilador Python gera bytecode para uma constante tupla em uma operação; +mas para um literal lista, `[1, 2, 3]`, o bytecode gerado insere cada elemento como uma constante separada na pilha, +e então cria a lista. +* Dada a tupla `t`, `tuple(t)` simplesmente devolve uma referência para a mesma `t`. Não há necessidade de cópia. +Por outro lado, dada uma lista `l`, o construtor `list(l)` precisa criar uma nova cópia de `l`. +* Devido a seu tamanho fixo, uma instância de `tuple` tem alocado para si o espaço exato de memória que precisa. +Em contrapartida, instâncias de `list` reservam memória adicional, para amortizar o custo de acréscimos futuros. +* As referências para os itens em uma tupla são armazenadas em um array na struct da tupla, +enquanto uma lista mantém um ponteiro para um array de referências armazenada em outro lugar. +Essa indireção é necessária porque, quando a lista cresce além do espaço alocado naquele momento, +Python precisa realocar o array de referências para criar espaço. +A indireção adicional torna o cache da CPU menos eficiente.((("", startref="Timlist02")))((("", startref="Ltupple02"))) ==== Comparando os métodos de tuplas e listas -Quando((("tuples", "versus lists", secondary-sortas="lists")))((("lists", "versus tuples", secondary-sortas="tuples"))) usamos uma tupla como uma variante imutável de `list`, é bom saber o quão similares são suas APIs. +Quando((("tuples", "versus lists", secondary-sortas="lists")))((("lists", "versus tuples", secondary-sortas="tuples"))) +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__+`. -Entretanto, isso é só uma otimização; `reversed(my_tuple)` funciona sem esse método. +`tuple` suporta todos os métodos de `list` que não envolvem adicionar ou remover itens, +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]] .Métodos e atributos encontrados em `list` ou `tuple` (os métodos implementados por `object` foram omitidos para economizar espaço) [options="header"] |================================================================================================================================================ -| | `list` | `tuple` |   -| `+s.__add__(s2)+` | ● | ● | ++s + s2++—concatenação -| `+s.__iadd__(s2)+` | ● | | ++s += s2++—concatenação no mesmo lugar -| `s.append(e)` | ● | | Acrescenta um elemento após o último -| `s.clear()` | ● | | Apaga todos os itens +| | `list` | `tuple` |   +| `+s.__add__(s2)+` | ● | ● | `s + s2`—concatenação +| `+s.__iadd__(s2)+` | ● | | `+s += s2+`—concatenação interna +| `s.append(e)` | ● | | Acrescenta um elemento após o último +| `s.clear()` | ● | | Apaga todos os itens | `+s.__contains__(e)+` | ● | ● | `e in s` -| `s.copy()` | ● | | Cópia rasa da lista -| `s.count(e)` | ● | ● | Conta as ocorrências de um elemento +| `s.copy()` | ● | | Cópia rasa da lista +| `s.count(e)` | ● | ● | Conta as ocorrências de um elemento | `+s.__delitem__(p)+` | ● | | Remove o item na posição `p` -| `s.extend(it)` | ● | | Acrescenta itens do iterável `it` -| `+s.__getitem__(p)+` | ● | ● | ++s[p]++—obtém o item na posição `p` +| `s.extend(it)` | ● | | Acrescenta itens do iterável `it` +| `+s.__getitem__(p)+` | ● | ● | `+s[p]+`—obtém o item na posição `p` | `+s.__getnewargs__()+` | | ● | Suporte a serialização otimizada com `pickle` -| `s.index(e)` | ● | ● | Encontra a posição da primeira ocorrência de `e` -| `s.insert(p, e)` | ● | | Insere elemento `e` antes do item na posição `p` -| `+s.__iter__()+` | ● | ● | Obtém o iterador -| `+s.__len__()+` | ● | ● | ++len(s)++—número de itens -| `+s.__mul__(n)+` | ● | ● | ++s * n++—concatenação repetida -| `+s.__imul__(n)+` | ● | | ++s *= n++—concatenação repetida no mesmo lugar -| `+s.__rmul__(n)+` | ● | ● | ++n * s++—concatenação repetida inversafootnote:[Operadores invertidos são explicados no capítulo <>.] -| `s.pop([p])` | ● | | Remove e devolve o último item ou o item na posição opcional `p` -| `s.remove(e)` | ● | | Remove a primeira ocorrência do elemento `e`, por valor -| `s.reverse()` | ● | | Reverte, no lugar, a ordem dos itens -| `+s.__reversed__()+` | ● | | Obtém iterador para examinar itens, do último para o primeiro -| `+s.__setitem__(p, e)+` | ● | | ++s[p] = e++—coloca `e` na posição `p`, sobrescrevendo o item existentefootnote:[Também usado para sobrescrever uma sub-sequência. Veja a seção <>.] +| `s.index(e)` | ● | ● | Encontra a posição da primeira ocorrência de `e` +| `s.insert(p, e)` | ● | | Insere elemento `e` antes do item na posição `p` +| `+s.__iter__()+` | ● | ● | Obtém um iterador +| `+s.__len__()+` | ● | ● | `+len(s)+`—número de itens +| `+s.__mul__(n)+` | ● | ● | `+s * n+`—concatenação repetida +| `+s.__imul__(n)+` | ● | | `+s *= n+`—concatenação repetida interna +| `+s.__rmul__(n)+` | ● | ● | `+n * s+`—concatenação repetida inversafootnote:[Operadores reversos são explicados no <>.] +| `s.pop([p])` | ● | | Remove e devolve o último item ou o item na posição opcional `p` +| `s.remove(e)` | ● | | Remove o primeiro elemento de valor igual a `e` +| `s.reverse()` | ● | | Reverte, no lugar, a ordem dos itens +| `+s.__reversed__()+` | ● | | Obtém iterador para percorrer itens do último para o primeiro +| `+s.__setitem__(p, e)+` | ● | | `+s[p] = e+`—coloca `e` na posição `p`, sobrescrevendo o item existentefootnote:[Também usado para sobrescrever uma sub-sequência. Veja a <>.] |`s.sort([key], [reverse])` | ● | | Ordena os itens no lugar, com os argumentos nomeados opcionais `key` e `reverse` |================================================================================================================================================ @@ -582,13 +679,19 @@ tuplas, listas e desempacotamento iterável.((("", startref="Stup02"))) [[iterable_unpacking_sec]] === Desempacotando sequências e iteráveis -O desempacotamento((("sequences", "unpacking sequences and iterables", id="Sunpack02")))((("iterables", "unpacking", id="iterun02")))((("unpacking", "sequences and iterables"))) é importante porque evita o uso de índices para extrair elementos de sequências, um processo desnecessário e vulnerável a erros. -Além disso, o desempacotamento funciona tendo qualquer objeto iterável como fonte de dados—incluindo iteradores, que não suportam((("square brackets ([])")))((("[] (square brackets)"))) a notação de índice (`[]`). -O único requisito é que o iterável produza exatamente um item por variável na ponta de recebimento, a menos que você use um asterisco (`*`) para capturar os itens em excesso, como explicado na seção <>. +O desempacotamento((("sequences", "unpacking sequences and iterables", id="Sunpack02")))((("iterables", "unpacking", +id="iterun02")))((("unpacking", "sequences and iterables"))) +é importante porque evita o uso de índices para acessar itens de sequências, +o que causa muitos bugs. +Além disso, o desempacotamento funciona tendo qualquer objeto iterável como fonte de dados—incluindo iteradores, +que não suportam((("square brackets ([])")))((("[] (square brackets)"))) a notação de índice (`[]`). +O único requisito é que o iterável produza exatamente um item por variável do lado esquerdo da atribuição, +a menos que você use um asterisco (`*`) para capturar os itens em excesso, como explicado na <>. -A forma mais visível de desempacotamento é a _atribuição paralela_; isto é, atribuir itens de um iterável a uma tupla de variáveis, como vemos nesse exemplo: +A forma mais visível de desempacotamento é a _atribuição paralela_; +isto é, atribuir itens de um iterável a uma tupla de variáveis, como vemos nesse exemplo: -[source, pycon] +[source, python] ---- >>> lax_coordinates = (33.9425, -118.408056) >>> latitude, longitude = lax_coordinates # unpacking @@ -600,14 +703,14 @@ A forma mais visível de desempacotamento é a _atribuição paralela_; isto é, Uma aplicação elegante de desempacotamento é permutar os valores de variáveis sem usar uma variável temporária: -[source, pycon] +[source, python] ---- >>> b, a = a, b ---- Outro exemplo de desempacotamento é prefixar um argumento com `*` ao chamar uma função: -[source, pycon] +[source, python] ---- >>> divmod(20, 8) (2, 4) @@ -621,9 +724,10 @@ Outro exemplo de desempacotamento é prefixar um argumento com `*` ao chamar uma O código acima mostra outro uso do desempacotamento: permitir que funções devolvam múltiplos valores de forma conveniente para quem as chama. -Em ainda outro exemplo, a função `os.path.split()` cria uma tupla `(path, last_part)` a partir de um caminho do sistema de arquivos: +Em ainda outro exemplo, a função `os.path.split()` cria uma tupla `(path, last_part)` +a partir de um caminho do sistema de arquivos: -[source, pycon] +[source, python] ---- >>> import os >>> _, filename = os.path.split('/home/luciano/.ssh/id_rsa.pub') @@ -636,11 +740,14 @@ Outra forma de usar apenas alguns itens quando desempacotando é com a sintaxe ` [[tuple_star]] ==== Usando * para recolher itens em excesso -Definir((("unpacking", "using * to grab excess items")))((("star (*) operator", id="star02")))((("* (star) operator", id="star02a"))) parâmetros de função com `*args` para capturar argumentos arbitrários em excesso é um recurso clássico do Python. +Definir((("unpacking", "using * to grab excess items")))((("star (*) operator", +id="star02")))((("* (star) operator", id="star02a"))) +parâmetros de função com `*args` para capturar argumentos +em excesso é um recurso clássico de Python. No Python 3, essa ideia foi estendida para se aplicar também à atribuição paralela: -[source, pycon] +[source, python] ---- >>> a, b, *rest = range(5) >>> a, b, rest @@ -653,9 +760,10 @@ No Python 3, essa ideia foi estendida para se aplicar também à atribuição pa (0, 1, []) ---- -No contexto da atribuição paralela, o prefixo `*` pode ser aplicado a exatamente uma variável, mas pode aparecer em qualquer posição: +No contexto da atribuição paralela, o prefixo `*` pode ser aplicado a exatamente uma variável, +mas pode aparecer em qualquer posição: -[source, pycon] +[source, python] ---- >>> a, *body, c, d = range(5) >>> a, body, c, d @@ -668,11 +776,14 @@ No contexto da atribuição paralela, o prefixo `*` pode ser aplicado a exatamen ==== Desempacotando com * em chamadas de função e sequências literais -A https://fpy.li/pep448[PEP 448—Additional Unpacking Generalizations (_Generalizações adicionais de desempacotamento_)] (EN) introduziu((("unpacking", "with * in function calls and sequence literals"))) uma sintaxe mais flexível para desempacotamento iterável, melhor resumida em https://docs.python.org/pt-br/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations["O que há de novo no Python 3.5" ] (EN). +A https://fpy.li/pep448[PEP 448—Additional Unpacking Generalizations (_Generalizações adicionais de desempacotamento_)] +(EN) introduziu((("unpacking", "with * in function calls and sequence literals"))) +uma sintaxe mais flexível para desempacotamento de iterável, melhor resumida em +https://fpy.li/2q["O que há de novo no Python 3.5" ] (EN). Em chamadas de função, podemos usar `*` múltiplas vezes: -[source, pycon] +[source, python] ---- >>> def fun(a, b, c, d, *rest): ... return a, b, c, d, rest @@ -683,10 +794,10 @@ Em chamadas de função, podemos usar `*` múltiplas vezes: O `*` pode também ser usado na definição de literais `list`, `tuple`, ou `set`, como visto nesses exemplos de -https://docs.python.org/pt-br/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations["O que há de novo no Python 3.5"] (EN): +https://fpy.li/2q["O que há de novo no Python 3.5"] (EN): -[source, pycon] +[source, python] ---- >>> *range(4), 4 (0, 1, 2, 3, 4) @@ -696,28 +807,29 @@ https://docs.python.org/pt-br/3/whatsnew/3.5.html#pep-448-additional-unpacking-g {0, 1, 2, 3, 4, 5, 6, 7} ---- -A PEP 448 introduziu uma nova sintaxe similar para `**`, que veremos na seção <>. +A PEP 448 introduziu uma nova sintaxe similar para `**`, que veremos na <>. -Por fim, outro importante aspecto do desempacotamento de tuplas: ele funciona com estruturas aninhadas.((("", startref="star02")))((("", startref="star02a"))) +Por fim, outro importante aspecto do desempacotamento de tuplas: +funciona com estruturas aninhadas.((("", startref="star02")))((("", startref="star02a"))) ==== Desempacotamento aninhado O((("unpacking", "nested"))) alvo de um desempacotamento pode usar aninhamento, por exemplo `(a, b, (c, d))`. -O Python fará a coisa certa se o valor tiver a mesma estrutura aninhada. +Python fará a coisa certa se o valor tiver a mesma estrutura aninhada. O <> mostra o desempacotamento aninhado em ação. [[ex_nested_tuple]] .Desempacotando tuplas aninhadas para acessar a longitude ==== -[source, python3] +[source, python] ---- -include::code/02-array-seq/metro_lat_lon.py[tags=MAIN] +include::../code/02-array-seq/metro_lat_lon.py[tags=MAIN] ---- ==== <1> Cada tupla contém um registro com quatro campos, o último deles um par de coordenadas. <2> Ao atribuir o último campo a uma tupla aninhada, desempacotamos as coordenadas. -<3> O teste `lon <= 0:` seleciona apenas cidades no hemisfério ocidental. +<3> O teste `lon \<= 0:` seleciona apenas cidades no hemisfério ocidental. A saída do <> é: @@ -731,55 +843,71 @@ São Paulo | -23.5478 | -46.6358 ---- O alvo da atribuição de um desempacotamento pode também ser uma lista, mas bons casos de uso aqui são raros. -Aqui está o único que conheço: se você tem uma consulta de banco de dados que devolve um único registro (por exemplo, se o código SQL tem a instrução `LIMIT 1`), daí é possível desempacotar e ao mesmo tempo se assegurar que há apenas um resultado com o seguinte código: +Aqui está o único que conheço: se você tem uma consulta de banco de dados que devolve um único registro +(por exemplo, se o código SQL tem a instrução `LIMIT 1`), +daí é possível desempacotar e ao mesmo tempo se assegurar que há apenas um resultado com o seguinte código: -[source, pycon] +[source, python] ---- >>> [record] = query_returning_single_row() ---- Se o registro contiver apenas um campo, é possível obtê-lo diretamente, assim: -[source, pycon] +[source, python] ---- >>> [[field]] = query_returning_single_row_with_single_field() ---- -Ambos os exemplos acima podem ser escritos com tuplas, mas não esqueça da peculiaridade sintática, tuplas com um único item devem ser escritas com uma vírgula final. +Ambos os exemplos acima podem ser escritos com tuplas, mas não esqueça da peculiaridade sintática, +tuplas com um único item devem ser escritas com uma vírgula final. 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.] +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_, que suporta maneiras ainda mais poderosas para desempacotar sequências.((("", startref="Sunpack02")))((("", startref="iterun02"))) +Agora vamos estudar casamento de padrões, +que suporta maneiras ainda mais poderosas para desempacotar +sequências.((("", startref="Sunpack02")))((("", startref="iterun02"))) [[sequence_patterns_sec]] === 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 do Python 3.10 é o _pattern matching_ (casamento de padrões) 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). +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 +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 do Python, escreveu uma excelente introdução ao _pattern matching_ na seção https://docs.python.org/pt-br/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching["Correspondência de padrão estrutural"]footnote:[NT: A tradução em português da documentação do Python adotou o termo "correspondência de padrões" no lugar de _pattern matching_. Escolhemos manter o termo em inglês, pois é 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.] em -https://docs.python.org/pt-br/3.10/whatsnew/3.10.html["O que há de novo no Python 3.10"]. +Carol Willing, uma das desenvolvedoras principais de Python, +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 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, optei por dividir o tratamento da correspondência de padrões em diferentes capítulos, +Neste livro, dividi o tratamento do casamento de padrões em diferentes capítulos, dependendo dos tipos de padrão: -Na seção <> e na <>. -E há um exemplo mais longo na seção <>. +na <> e na <>. +E há um exemplo mais longo na <>. ==== Vamos ao primeiro exemplo do tratamento de sequências com `match/case`. -Imagine que você está construindo um robô que aceita comandos, enviados como sequências de palavras e números, como `BEEPER 440 3`. +Imagine que você está construindo um robô que aceita comandos, +enviados como sequências de palavras e números, como `BEEPER 440 3`. Após separar o comando em partes e analisar os números, você teria uma mensagem como `['BEEPER', 440, 3]`. Então, você poderia usar um método assim para interpretar mensagens naquele formato: [[ex_robot]] .Método de uma classe `Robot` imaginária ==== -[source, python3] +[source, python] ---- def handle_command(self, message): match message: # <1> @@ -787,38 +915,49 @@ Então, você poderia usar um método assim para interpretar mensagens naquele f self.beep(times, frequency) case ['NECK', angle]: # <3> self.rotate_neck(angle) - case ['LED', ident, intensity]: # <4> - self.leds[ident].set_brightness(ident, intensity) - case ['LED', ident, red, green, blue]: # <5> - self.leds[ident].set_color(ident, red, green, blue) + case ['LED', pin, intensity]: # <4> + self.leds[ident].set_brightness(pin, intensity) + case ['LED', pin, red, green, blue]: # <5> + self.leds[ident].set_color(pin, red, green, blue) case _: # <6> raise InvalidCommand(message) ---- ==== <1> A expressão após a palavra-chave `match` é o sujeito (_subject_). -O sujeito contém os dados que o Python vai comparar aos padrões em cada instrução `case`. -<2> Esse padrão casa com qualquer sujeito que seja uma sequência de três itens. O primeiro item deve ser a string `BEEPER`. +O sujeito contém os dados que Python vai comparar aos padrões em cada instrução `case`. +<2> Este padrão casa com qualquer sujeito que seja uma sequência de três itens. +O primeiro item deve ser a string `BEEPER`. O segundo e o terceiro itens podem ser qualquer coisa, e serão vinculados às variáveis `frequency` e `times`, nessa ordem. <3> Isso casa com qualquer sujeito com dois itens, se o primeiro for `'NECK'`. -<4> Isso vai casar com uma sujeito de três itens começando com `LED`. Se o número de itens não for correspondente, o Python segue para o próximo `case`. +<4> Isso vai casar com um sujeito de três itens começando com `LED`. +Se o número de itens não for correspondente, Python segue para o próximo `case`. <5> Outro padrão de sequência começando com `'LED'`, agora com cinco itens—incluindo a constante `'LED'`. -<6> Esse é o `case` default. Vai casar com qualquer sujeito que não tenha sido capturado por um dos padrões precedentes. A variável `_` é especial, como logo veremos. - -Olhando((("pattern matching", "destructuring"))) superficialmente, `match/case` se parece instrução `switch/case` da linguagem C—mas isso é só uma pequena parte da sua funcionalidade.footnote:[Na minha opinião, uma sucessão `if/elif/elif/.../else` funciona muito bem como no lugar de `switch/case`. E ela não sofre dos problemas de https://fpy.li/2-8[fallthrough (_cascateamento_)] (EN) e de -https://fpy.li/2-9[dangling else (_o `else` errante_)] (EN), que alguns projetistas de linguagens copiaram irracionalmente do C—décadas após sabermos que tais problemas são a causa de inúmeros bugs.] -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 do Python, +<6> Este é o `case` default. +Vai casar com qualquer sujeito que não tenha sido capturado por um dos padrões precedentes. +A variável `_` é especial, como logo veremos. + +Olhando((("pattern matching", "destructuring"))) superficialmente, +`match/case` se parece com a instrução `switch/case` da linguagem C—mas isso é só uma pequena parte da sua +funcionalidade.footnote:[Na minha opinião, uma sucessão `if/elif/elif/.../else` funciona muito bem no lugar de `switch/case`. +E ela não sofre dos problemas de https://fpy.li/2-8[fallthrough (_cascateamento_)] (EN) e de +https://fpy.li/2-9[dangling else (_o `else` errante_)] (EN), +que alguns projetistas de linguagens copiaram irracionalmente do C—décadas +após sabermos que tais problemas causam inúmeros bugs.] +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`. +Como um primeiro exemplo de desestruturação, o <> +mostra parte do <> reescrito com `match/case`. [[ex_nested_tuple_match]] .Desestruturando tuplas aninhadas—requer Python ≥ 3.10 ==== -[source, python3] +[source, python] ---- -include::code/02-array-seq/match_lat_lon.py[tags=MAIN] +include::../code/02-array-seq/match_lat_lon.py[tags=MAIN] ---- ==== <1> O sujeito desse `match` é `record`—isto é, cada uma das tuplas em `metro_areas`. @@ -828,26 +967,31 @@ Em geral, um padrão de sequência casa com o sujeito se estas três condições . O sujeito é uma sequência, _e_ . O sujeito e o padrão tem o mesmo número de itens, _e_ -. Cada item correspondente casa, incluindo os itens aninhados. +. Todos os itens correspondentes casam, incluindo os itens aninhados. Por exemplo, o padrão `[name, _, _, (lat, lon)]` no <> -casa com uma sequência de quatro itens, e o último item tem que ser uma sequência de dois itens. +casa com uma sequência de quatro itens, e o último item precisa ser uma sequência de dois itens. -Padrões de sequência((("pattern matching", "tuples and lists"))) podem ser escritos como tuplas e listas, mas a sintaxe usada não faz diferença: -em um padrão de sequência, colchetes e parênteses tem o mesmo significado. -// in a pattern, tuples and lists match any sequence. -Escrevi o padrão como uma lista com uma tupla aninhada de dois itens para evitar a repetição de colchetes ou parênteses no <>. +Padrões de sequência((("pattern matching", "tuples and lists"))) +podem ser escritos como tuplas e listas, mas a sintaxe usada não faz diferença: +em um padrão de sequência, colchetes e parênteses têm o mesmo significado. +Escrevi o padrão como uma lista com uma tupla aninhada de dois itens para evitar a +repetição de colchetes ou parênteses no <>. -Um padrão de sequência pode casar com instâncias da maioria das subclasses reais ou virtuais de `collections.abc.Sequence`, com a exceção de `str`, `bytes`, e `bytearray`. +Um padrão de sequência pode casar com instâncias da maioria das subclasses reais ou virtuais de `collections.abc.Sequence`, +com a exceção de `str`, `bytes`, e `bytearray`. [WARNING] ==== Instâncias de `str`, `bytes`, e `bytearray` não são tratadas como sequências no contexto de um `match/case`. -Um sujeito de `match` de um desses tipos é tratado como um valor "atômico"—assim como o inteiro 987 é tratado como um único valor, e não como uma sequência de dígitos. +Um sujeito de `match` de um desses tipos é tratado como um valor "atômico"—assim como o inteiro 987 é tratado como um único valor, +e não como uma sequência de dígitos. Tratar aqueles três tipos como sequências poderia causar bugs devido a casamentos não intencionais. -Se você quer usar um objeto daqueles tipos como um sujeito sequência, converta-o na instrução `match`. Por exemplo, veja `tuple(phone)` no trecho abaixo, que poderia ser usado para separar números de telefone por regiões do mundo com base no prefixo DDI: +Se você quer usar um objeto daqueles tipos como um sujeito sequência, converta-o na instrução `match`. +Por exemplo, veja `tuple(phone)` no trecho abaixo, +que poderia ser usado para separar números de telefone por regiões do mundo com base no prefixo DDI: -[source, py] +[source, python] ---- match tuple(phone): case ['1', *rest]: # North America and Caribbean @@ -868,14 +1012,18 @@ list memoryview array.array tuple range collections.deque ---- -Ao contrário do desempacotamento, padrões não desestruturam iteráveis que não sejam sequências (tal como os iteradores). +Ao contrário do desempacotamento, +padrões não desestruturam iteráveis que não sejam sequências (tal como os iteradores). -O((("pattern matching", "_ symbol")))((("_ symbol"))) símbolo `+_+` é especial nos padrões: ele casa com qualquer item naquela posição, mas nunca é vinculado ao valor daquele item. O valor é descartado. +O((("pattern matching", "_ symbol")))((("_ symbol"))) símbolo `+_+` é especial nos padrões: +ele casa com qualquer item naquela posição, mas nunca é vinculado ao valor daquele item. +O valor é descartado. Além disso, o `+_+` é a única variável que pode aparecer mais de uma vez em um padrão. -Você pode vincular qualquer parte de um padrão a uma variável usando a((("keywords", "as keyword")))((("as keyword"))) palavra-chave `as`: +Você pode vincular qualquer parte de um padrão a uma variável usando +a((("keywords", "as keyword")))((("as keyword"))) palavra-chave `as`: -[source, python3] +[source, python] ---- case [name, _, _, (lat, lon) as coord]: ---- @@ -893,11 +1041,13 @@ o padrão anterior vai casar e atribuir valores às seguintes variáveis: | `coord` | `(31.1, 121.3)` |=== -Podemos((("pattern matching", "type information"))) tornar os padrões mais específicos, incluindo informação de tipo. -Por exemplo, o seguinte padrão casa com a mesma estrutura de sequência aninhada do exemplo anterior, mas o primeiro item deve ser uma instância de `str`, +Para((("pattern matching", "type information"))) tornar os padrões mais específicos, +podemos incluir informação de tipo. +Por exemplo, o seguinte padrão casa com a mesma estrutura de sequência aninhada do exemplo anterior, +mas o primeiro item deve ser uma instância de `str`, e ambos os itens da tupla devem ser instâncias de `float`: -[source, python3] +[source, python] ---- case [str(name), _, _, (float(lat), float(lon))]: ---- @@ -907,61 +1057,74 @@ e ambos os itens da tupla devem ser instâncias de `float`: ==== As expressões `str(name)` e `float(lat)` se parecem com chamadas a construtores, que usaríamos para converter `name` e `lat` para `str` e `float`. -Mas no contexto de um padrão, aquela sintaxe faz uma verificação de tipo durante a execução do programa: +Mas no contexto de um padrão, aquela sintaxe faz uma checagem de tipos durante a execução do programa: o padrão acima vai casar com uma sequência de quatro itens, na qual o item 0 deve ser uma `str` e o item 3 deve ser um par de números de ponto flutuante. Além disso, a `str` no item 0 será vinculada à variável `name` e os números no item 3 serão vinculados a `lat` e `lon`, respectivamente. Assim, apesar de imitar a sintaxe de uma chamada de construtor, o significado de `str(name)` é totalmente diferente no contexto de um padrão. -O uso de classes arbitrárias em padrões será tratado na seção <>. +O uso de classes arbitrárias em padrões será tratado na <>. ==== -Por outro lado, se queremos casar qualquer sujeito sequência começando com uma `str` e terminando com uma sequência aninhada com dois números de ponto flutuante, podemos escrever: +Por outro lado, se queremos casar qualquer sujeito sequência começando com uma `str` e +terminando com uma sequência aninhada com dois números de ponto flutuante, podemos escrever: -[source, python3] +[source, python] ---- case [str(name), *_, (float(lat), float(lon))]: ---- -O((("pattern matching", "*_ symbol")))((("*_ symbol"))) `+*_+` casa com qualquer número de itens, sem vinculá-los a uma variável. +O((("pattern matching", "*_ symbol")))((("*_ symbol"))) `+*_+` +casa com qualquer número de itens, sem vinculá-los a uma variável. Usar `*extra` em vez de `+*_+` vincularia os itens a `extra` como uma `list` com 0 ou mais itens. A instrução de guarda opcional começando com `if` só é avaliada se o padrão casar, e pode se referir a variáveis vinculadas no padrão, como no <>: -[source, python3] +[source, python] ---- match record: case [name, _, _, (lat, lon)] if lon <= 0: print(f'{name:15} | {lat:9.4f} | {lon:9.4f}') ---- -O bloco aninhado com o comando `print` só será executado se o padrão casar e a expressão guarda for _verdadeira_. +O bloco aninhado com a instrução `print` só será executado se o padrão casar e a expressão guarda for _verdadeira_. [TIP] ==== -A desestruturação com padrões é tão expressiva que, algumas vezes, um `match` com um único `case` pode tornar o código mais simples. +A desestruturação com padrões é tão expressiva que, algumas vezes, +um `match` com um único `case` pode tornar o código mais simples. Guido van Rossum tem uma coleção de exemplos de `case/match`, incluindo um que ele chamou de https://fpy.li/2-10["A very deep iterable and type match with extraction" (_Um match de iterável e tipo muito profundo, com extração_)] (EN). ==== -O <> não melhora o <>. +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 -Peter Norvig((("lis.py interpreter", "pattern matching in", id="lispypattern02")))((("pattern matching", "in lis.py interpreter", secondary-sortas="lis.py", id="PMinterp02")))((("Scheme language", id="scheme02"))), da Universidade de Stanford, escreveu o +Peter Norvig((("lis.py interpreter", "pattern matching in", id="lispypattern02")))((("pattern matching", +"in lis.py interpreter", +secondary-sortas="lis.py", id="PMinterp02")))((("Scheme language", id="scheme02"))), +da Universidade de Stanford, escreveu o 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 o Python 3.10, para exemplificar o _pattern matching_. -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`. - -As duas funções principais do _lis.py_ são `parse` e `evaluate`.footnote:[A última é chamada `eval` no código original; a renomeei para evitar que fosse confundida com a função embutida `eval` do Python.] -O parser (_analisador sintático_) recebe as expressões entre parênteses do Scheme e devolve listas Python. Aqui estão dois exemplos: +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 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`. + +As duas funções principais do _lis.py_ são `parse` e `evaluate`.footnote:[A última é +chamada `eval` no código original; +a renomeei para evitar confusão com a função embutida `eval` de Python.] +O parser (_analisador sintático_) recebe as expressões entre parênteses do Scheme +e devolve listas Python. +Aqui estão dois exemplos: [source, python] ---- @@ -977,15 +1140,17 @@ O parser (_analisador sintático_) recebe as expressões entre parênteses do Sc O avaliador recebe listas como essas e as executa. O primeiro exemplo está chamando uma função `gcd` com `18` e `45` como argumentos. -Quando executada, ela computa o maior divisor comum (gcd são as iniciais do termo em inglês, _greatest common divisor) dos argumentos (que é 9). +Quando executada, ela computa o maior divisor comum (`gcd` são as iniciais do termo em inglês +_greatest common divisor_) dos argumentos (que é 9). O segundo exemplo está definindo uma função chamada `double` com um parâmetro `n`. O corpo da função é a expressão `(* n 2)`. O resultado da chamada a uma função em Scheme é o valor da última expressão no corpo da função chamada. -Nosso foco aqui é a desestruturação de sequências, então não vou explicar as ações do avaliador. Veja a seção <> para aprender mais sobre o funcionamento do _lis.py_. +Nosso foco aqui é a desestruturação de sequências, então não vou explicar as ações do avaliador. +Veja a <> para aprender mais sobre o funcionamento do _lis.py_. O <> mostra o avaliador de Norvig com algumas pequenas modificações, -e abreviado para mostrar apenas os padrões de sequência. +abreviado para mostrar apenas os padrões de sequência. [[ex_norvigs_eval]] @@ -993,15 +1158,15 @@ e abreviado para mostrar apenas os padrões de sequência. ==== [source, python] ---- -include::code/02-array-seq/lispy/py3.9/lis.py[tags=EVAL_IF_TOP] +include::../code/02-array-seq/lispy/py3.9/lis.py[tags=EVAL_IF_TOP] # ... lines omitted -include::code/02-array-seq/lispy/py3.9/lis.py[tags=EVAL_IF_MIDDLE] +include::../code/02-array-seq/lispy/py3.9/lis.py[tags=EVAL_IF_MIDDLE] # ... more lines omitted ---- ==== 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 <>. @@ -1009,28 +1174,30 @@ Usando `match/case` em Python ≥ 3.10, podemos refatorar `evaluate`, como mostr [[ex_match_eval]] .Pattern matching com `match/case`—requer Python ≥ 3.10 ==== -[source, python3] +[source, python] ---- -include::code/02-array-seq/lispy/py3.10/lis.py[tags=EVAL_MATCH_TOP] +include::../code/02-array-seq/lispy/py3.10/lis.py[tags=EVAL_MATCH_TOP] # ... lines omitted -include::code/02-array-seq/lispy/py3.10/lis.py[tags=EVAL_MATCH_MIDDLE] +include::../code/02-array-seq/lispy/py3.10/lis.py[tags=EVAL_MATCH_MIDDLE] # ... more lines omitted -include::code/02-array-seq/lispy/py3.10/lis.py[tags=EVAL_MATCH_BOTTOM] +include::../code/02-array-seq/lispy/py3.10/lis.py[tags=EVAL_MATCH_BOTTOM] ---- ==== <1> Casa se o sujeito for uma sequência de dois itens começando com `'quote'`. <2> Casa se o sujeito for uma sequência de quatro itens começando com `'if'`. <3> Casa se o sujeito for uma sequência com três ou mais itens começando com `'lambda'`. A guarda assegura que `body` não esteja vazio. -<4> Casa de o sujeito for uma sequência de três itens começando com `'define'`, seguido de uma instância de `Symbol`. -<5> é uma boa prática ter um `case` para capturar todo o resto. +<4> Casa se o sujeito for uma sequência de três itens começando com `'define'`, seguido de uma instância de `Symbol`. +<5> É uma boa prática ter um `case` para capturar qualquer outro sujeito. Neste exemplo, se `exp` não casar com nenhum dos padrões, a expressão está mal-formada, então gera um `SyntaxError`. -Sem o último `case`, para pegar tudo que tiver passado pelos anteriores, todo o bloco `match` não faz nada quando o sujeito não casa com algum `case`—e isso pode ser uma falha silenciosa. +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. +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. O <> é mais curto e mais seguro que o <>. ===== Padrões alternativos para lambda @@ -1046,7 +1213,7 @@ o sufixo `…` significa que o elemento pode aparecer zero ou mais vezes: Um padrão simples para o `case` de `'lambda'` seria esse: -[source, python3] +[source, python] ---- case ['lambda', parms, *body] if body: ---- @@ -1054,7 +1221,7 @@ Um padrão simples para o `case` de `'lambda'` seria esse: Entretanto, isso casa com qualquer valor na posição `parms`, incluindo o primeiro `x` nesse sujeito inválido: -[source, python3] +[source, python] ---- ['lambda', 'x', ['*', 'x', 2]] ---- @@ -1063,11 +1230,11 @@ A lista aninhada após a palavra-chave `lambda` do Scheme contém os nomes do parâmetros formais da função, e deve ser uma lista mesmo que contenha apenas um elemento. Ela pode também ser uma lista vazia, -se função não receber parâmetros—como a `random.random()` do Python. +se função não receber parâmetros—como a `random.random()` de Python. No <>, tornei o padrão de `'lambda'` mais seguro usando um padrão de sequência aninhado: -[source, python3] +[source, python] ---- case ['lambda', [*parms], *body] if body: return Procedure(parms, body, env) @@ -1082,32 +1249,36 @@ e nos deu uma verificação estrutural adicional. ===== Sintaxe abreviada para definição de função -O Scheme tem uma sintaxe alternativa de `define`, para criar uma função nomeada sem usar um `lambda` aninhado. Tal sintaxe funciona assim: +O Scheme tem uma sintaxe alternativa de `define`, para criar uma função nomeada sem usar um `lambda` aninhado. +Tal sintaxe funciona assim: [source, lisp] ---- (define (name parm…) body1 body2…) ---- -A palavra-chave `define` é seguida por uma lista com o `name` da nova função e zero ou mais nomes de parâmetros. Após a lista vem o corpo da função, com uma ou mais expressões. +A palavra-chave `define` é seguida por uma lista com o `name` da nova função e zero ou mais nomes de parâmetros. +Após a lista vem o corpo da função, com uma ou mais expressões. Acrescentar essas duas linhas ao `match` cuida da implementação: -[source, py] +[source, python] ---- - case ['define', [Symbol() as name, *parms], *body] if body: - env[name] = Procedure(parms, body, env) + case ['define', [Symbol() as name, *parms], *body] if body: + env[name] = Procedure(parms, body, env) ---- Eu colocaria esse `case` após o `case` da outra forma de `define` no <>. -A ordem desses _cases_ de `define` é irrelevante nesse exemplo, pois nenhum sujeito pode casar com esses dois padrões: +A ordem desses _cases_ de `define` é irrelevante nesse exemplo, +pois nenhum sujeito pode casar com esses dois padrões: o segundo elemento deve ser um `Symbol` na forma original de `define`, -mas deve ser uma sequência começando com um `Symbol` no atalho de `define` para definição de função. +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 <>. -A instrução `match` faz muito mais que o `switch` das linguagens similares ao C. +Agora pense em quanto trabalho teríamos para adicionar o suporte a essa segunda sintaxe de `define` +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 <>. @@ -1124,44 +1295,49 @@ A forma do código segue a forma dos dados, como ilustra a <>, +Veremos mais do _lis.py_ na <>, quando vamos revisar o exemplo completo de `match/case` em `evaluate`. Se você quiser aprender mais sobre o _lys.py_ de Norvig, leia seu maravilhoso post https://fpy.li/2-12["(How to Write a (Lisp) Interpreter (in Python))" _(Como Escrever um Interpretador (Lisp) em (Python))_]. ==== -Isso conclui nossa primeira passagem por desempacotamento, desestruturação e _pattern matching_ com sequências. +Isso conclui nossa primeira passagem por desempacotamento, desestruturação e casamento de padrões com sequências. Vamos tratar de outros tipos de padrões mais adiante, em outros capítulos. Todo programador Python sabe que sequências podem ser fatiadas usando a sintaxe `s[a:b]`. -Vamos agora examinar alguns fatos menos conhecidos sobre fatiamento.((("", startref="PMinterp02")))((("", startref="Spattern02")))((("", startref="lispypattern02")))((("", startref="scheme02"))) +Vamos agora examinar alguns fatos menos conhecidos sobre fatiamento.((("", +startref="PMinterp02")))((("", startref="Spattern02")))((("", startref="lispypattern02")))((("", startref="scheme02"))) === Fatiamento -Um((("sequences", "slicing", id="Sslice02")))((("zero-based indexing"))) recurso comum a `list`, `tuple`, `str`, e a todos os tipos de sequência em Python, é o suporte a operações de fatiamento, que são mais potentes do que a maioria das pessoas percebe. +Um((("sequences", "slicing", id="Sslice02")))((("zero-based indexing"))) +recurso comum a `list`, `tuple`, `str`, e a todos os tipos de sequência em Python, +é o suporte a operações de fatiamento, que são mais potentes do que a maioria das pessoas percebe. Nesta seção descrevemos o _uso_ dessas formas avançadas de fatiamento. -Sua implementação em uma classe definida pelo usuário será tratada no <>, -mantendo nossa filosofia de tratar de classes prontas para usar nessa parte do livro, e da criação de novas classes na <>. +Sua implementação em uma classe definida pelo usuário será tratada no <>, +mantendo nossa filosofia de tratar de classes prontas para usar nessa parte do livro, +e da criação de novas classes na <>. ==== Por que fatias e faixas excluem o último item? -A((("slicing", "excluding last item in"))) convenção pythônica de excluir o último item em fatias e faixas funciona bem com a indexação iniciada no zero usada no Python, no C e em muitas outras linguagens. -Algumas características convenientes da convenção são: +A((("slicing", "excluding last item in"))) convenção pythônica de excluir o último item em fatias e +faixas funciona bem com a indexação iniciada no zero usada no Python, no C e em muitas outras linguagens. +Algumas vantagens desta convenção: * É fácil ver o tamanho da fatia ou da faixa quando apenas a posição final é dada: tanto `range(3)` quanto `my_list[:3]` produzem três itens. * É fácil calcular o tamanho de uma fatia ou de uma faixa quando o início e o fim são dados: basta subtrair `fim-início`. -* É fácil cortar uma sequência em duas partes em qualquer índice `x`, sem sobreposição: simplesmente escreva `my_list[:x]` e `my_list[x:]`. +* É fácil cortar uma sequência em duas partes em qualquer índice `x`, sem sobreposição: escreva `my_list[:x]` e `my_list[x:]`. Por exemplo: + -[source, pycon] +[source, python] ---- >>> l = [10, 20, 30, 40, 50, 60] >>> l[:2] # split at 2 @@ -1174,19 +1350,18 @@ Por exemplo: [40, 50, 60] ---- -Os melhores argumentos a favor desta convenção foram escritos pelo cientista da computação holandês Edsger W. Dijkstra (veja a última referência na seção <>). - -Agora vamos olhar mais de perto a forma como o Python interpreta a notação de fatiamento. +Agora vamos olhar mais de perto a forma como Python interpreta a notação de fatiamento. -[[slice_objects]] +[[slice_objects_sec]] ==== Objetos fatia Isso((("slicing", "slice objects"))) não é segredo, mas vale a pena repetir, só para ter certeza: -`s[a:b:c]` pode ser usado para especificar um passo ou salto `c`, fazendo com que a fatia resultante pule itens. O passo pode ser também negativo, devolvendo os itens em ordem inversa. -Três exemplos esclarecem a questão: +`s[a:b:c]` pode ser usado para especificar um passo ou salto `c`, fazendo com que a fatia resultante pule itens. +O passo pode ser também negativo, devolvendo os itens em ordem inversa. +Veja três exemplos: -[source, pycon] +[source, python] ---- >>> s = 'bicycle' >>> s[::3] @@ -1197,9 +1372,9 @@ Três exemplos esclarecem a questão: 'eccb' ---- -Vimos outro exemplo no capítulo <>, quando usamos `deck[12::13]` para obter todos os ases de uma baralho não embaralhado: +Vimos outro exemplo no <>, quando usamos `deck[12::13]` para obter todos os ases de uma baralho não embaralhado: -[source, pycon] +[source, python] ---- >>> deck[12::13] [Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), @@ -1208,18 +1383,19 @@ Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')] A notação `a:b:c` só é válida entre `[]` quando usada como operador de indexação ou de subscrição (_subscript_), e produz um objeto fatia (_slice object_): `slice(a, b, c)`. -Como veremos na seção <>, para avaliar a expressão `seq[start:stop:step]`, -o Python chama `+seq.__getitem__(slice(start, stop, step))+`. -Mesmo se você não for implementar seus próprios tipos de sequência, saber dos objetos fatia é útil, porque eles permitem que você atribua nomes às fatias, da mesma forma que planilhas permitem dar nomes a faixas de células. +Para avaliar a expressão `seq[start:stop:step]`, +o Python chama `+seq.__getitem__(slice(start, stop, step))+`, como veremos na <>. +Mesmo se você não for implementar seus próprios tipos de sequência, saber dos objetos fatia é útil, +porque eles permitem que você atribua nomes às fatias, da mesma forma que planilhas permitem dar nomes a faixas de células. -Suponha que você precise analisar um arquivo de dados como a fatura mostrada na <>. +Suponha que você precise analisar um arquivo de dados como a fatura mostrada na <>. 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_invoce]] +[[flat_file_invoice]] .Itens de um arquivo tabular de fatura ==== -[source, pycon] +[source, python] ---- >>> invoice = """ ... 0.....6.................................40........52...55........ @@ -1244,41 +1420,58 @@ Veja como isso torna legível o loop `for` no final do exemplo. ---- ==== -Voltaremos aos objetos +slice+ quando formos discutir a criação de suas próprias coleções, na seção <>. -Enquanto isso, do ponto de vista do usuário, o fatiamento tem recursos adicionais, tais como fatias multidimensionais e a notação de reticências (`\...`). +Voltaremos aos objetos +slice+ quando formos discutir a criação de suas próprias coleções, na <>. +Enquanto isso, do ponto de vista do usuário, o fatiamento tem recursos adicionais, +como fatias multidimensionais e a notação de reticências (`\...`). Siga comigo. ==== Fatiamento multidimensional e reticências -O operador `[]`((("[] (square brackets)")))((("square brackets ([])")))((("slicing", "multidimensional slicing and ellipses"))) pode também receber múltiplos índices ou fatias separadas por vírgulas. -Os((("__getitem__")))((("__setitem__"))) métodos especiais `+__getitem__+` e `+__setitem__+`, que tratam o operador `[]`, apenas recebem os índices em `a[i, j]` como uma tupla. -Em outras palavras, para avaliar `a[i, j]`, o Python chama `+a.__getitem__((i, j))+`. +O operador `[]`((("[] (square brackets)")))((("square brackets ([])")))((("slicing", "multidimensional slicing and ellipses"))) +pode também receber múltiplos índices ou fatias separadas por vírgulas. +Os((("__getitem__")))((("__setitem__"))) +métodos especiais `+__getitem__+` e `+__setitem__+`, que tratam o operador `[]`, +recebem os índices em `a[i, j]` como uma tupla. +Em outras palavras, para avaliar `a[i, j]`, Python chama `+a.__getitem__((i, j))+`. Isso é usado, por exemplo, no pacote externo NumPy, onde itens de uma `numpy.ndarray` bi-dimensional -podem ser recuperados usando a sintaxe `a[i, j]`, e uma fatia bi-dimensional é obtida com uma expressão como `a[m:n, k:l]`. O <>, abaixo nesse mesmo capítulo, mostra o uso dessa notação. - -Exceto por `memoryview`, os tipos embutidos de sequência do Python são uni-dimensionais, então aceitam apenas um índice ou fatia, e não uma tupla de índices ou fatias.footnote:[Na seção <> vamos mostrar que views da memória construídas de forma especial podem ter mais de uma dimensão.] - -As((("ellipsis (…)")))((("… (ellipsis)"))) reticências—escritas como três pontos finais (`\...`) e não como `…` (Unicode U+2026)—são reconhecidas como um símbolo pelo parser do Python. Esse símbolo é um apelido para o objeto `Ellipsis`, a única instância da classe `ellipsis`.footnote:[Não, eu não escrevi ao contrário: o nome da classe `ellipsis` realmente se escreve só com minúsculas, e a instância é um objeto embutido chamado `Ellipsis`, da mesma forma que `bool` é em minúsculas mas suas instâncias são `True` e `False`.] -Dessa forma, ele pode ser passado como argumento para funções e como parte da especificação de uma fatia, como em `f(a, \..., z)` ou `a[i:\...]`. -O NumPy usa `\...` como atalho ao fatiar arrays com muitas dimensões; por exemplo, se `x` é um array com quatro dimensões, `x[i, \...]` é um atalho para `x[i, :, :, :,]`. +podem ser recuperados usando a sintaxe `a[i, j]`, e uma fatia bi-dimensional é obtida com uma expressão como `a[m:n, k:l]`. +O <>, abaixo nesse mesmo capítulo, mostra o uso dessa notação. + +Exceto por `memoryview`, os tipos embutidos de sequência de Python são uni-dimensionais, +então aceitam só um índice ou fatia, e não uma tupla de índices ou +fatias.footnote:[Na <> vamos mostrar que +views da memória construídas de forma especial podem ter mais de uma dimensão.] + +As((("ellipsis (…)")))((("… (ellipsis)"))) reticências—escritas como três pontos finais +(`\...`) e não como `…` (Unicode U+2026)—são reconhecidas como um símbolo pelo parser de Python. +Esse símbolo é um apelido para o objeto `Ellipsis`, a única instância da classe `ellipsis`.footnote:[Não, +eu não escrevi ao contrário: o nome da classe `ellipsis` realmente se escreve só com minúsculas, +e a instância é um objeto embutido chamado `Ellipsis`, +da mesma forma que `bool` é em minúsculas mas suas instâncias são `True` e `False`.] +Dessa forma, ele pode ser passado como argumento para funções e como parte da especificação de uma fatia, +como em `f(a, \..., z)` ou `a[i:\\...]`. +O NumPy usa `\...` como atalho ao fatiar arrays com muitas dimensões; +por exemplo, se `x` é um array com quatro dimensões, `x[i, \\...]` é um atalho para `x[i, :, :, :,]`. Veja https://fpy.li/2-13["NumPy quickstart"] (EN) para saber mais sobre isso. -No momento em que escrevo isso, desconheço usos de `Ellipsis` ou de índices multidimensionais na biblioteca padrão do Python. -Se você souber de algum, me avise. -Esses recursos sintáticos existem para suportar tipos definidos pelo usuário ou extensões como o NumPy. +Desconheço usos de `Ellipsis` ou de índices multidimensionais na biblioteca padrão de Python. +Esses recursos sintáticos existem para suportar tipos definidos pelo usuário ou extensões como a NumPy. -Fatias não são úteis apenas para extrair informações de sequências; elas podem também ser usadas para modificar sequências mutáveis no lugar—isto é, sem precisar reconstruí-las do zero. +Fatias não são úteis apenas para extrair informações de sequências; +elas podem também ser usadas para modificar sequências mutáveis no lugar—isto é, sem precisar reconstruí-las do zero. [[assigning_to_slices]] ==== Atribuindo a fatias -Sequências((("slicing", "assigning to slices"))) mutáveis podem ser transplantadas, extirpadas e, de forma geral, modificadas no lugar com o uso da notação de fatias no lado esquerdo de um comando de atribuição ou como alvo de um comando `del`. +Sequências((("slicing", "assigning to slices"))) mutáveis podem ser enxertadas, +extirpadas e, de várias maneiras modificadas no lugar com o uso da notação de fatias +no lado esquerdo de uma instrução de atribuição ou como alvo de uma instrução `del`. Os próximos exemplos dão uma ideia do poder dessa notação: -[source, pycon] +[source, python] ---- >>> l = list(range(10)) >>> l @@ -1300,7 +1493,8 @@ TypeError: can only assign an iterable >>> l [0, 1, 100, 22, 9] ---- -<1> Quando o alvo de uma atribuição é uma fatia, o lado direito deve ser um objeto iterável, mesmo que tenha apenas um item. +<1> Quando o alvo de uma atribuição é uma fatia, +o lado direito deve ser um objeto iterável, mesmo que tenha apenas um item. Todo programador sabe que a concatenação é uma operação frequente com sequências. Tutoriais introdutórios de Python explicam o uso de `+` e `*` para tal propósito, @@ -1308,12 +1502,17 @@ mas há detalhes sutis em seu funcionamento, como veremos a seguir.((("", startr === Usando + e * com sequências -Programadores((("sequences", "using + and * with", id="Splusast02")))((("+ operator", id="plusop02")))((("star (*) operator", id="starseq02")))((("* (star) operator", id="starseq02a")))((("concatenation", id="concat02"))) Python esperam que sequências suportem `+` e `*`. Em geral, os dois operandos de `+` devem ser sequências do mesmo tipo, e nenhum deles é modificado, uma nova sequência daquele mesmo tipo é criada como resultado da concatenação. +Em((("sequences", "using + and * with", id="Splusast02")))((("+ operator", +id="plusop02")))((("star (*) operator", id="starseq02")))((("* (star) operator", +id="starseq02a")))((("concatenation", id="concat02"))) +Python, podemos assumir que sequências suportem `\+` e `*`. +Em geral, os dois operandos de `+` devem ser sequências do mesmo tipo, +e nenhum deles é modificado: uma nova sequência daquele mesmo tipo é criada como resultado da concatenação. -Para concatenar múltiplas cópias da mesma sequência basta multiplicá-la por um inteiro. -E da mesma forma, uma nova sequência é criada: +Para concatenar múltiplas cópias da mesma sequência, multiplique por um inteiro. +Da mesma forma, uma nova sequência é criada: -[source, pycon] +[source, python] ---- >>> l = [1, 2, 3] >>> l * 5 @@ -1322,24 +1521,30 @@ E da mesma forma, uma nova sequência é criada: 'abcdabcdabcdabcdabcd' ---- -Tanto `+` quanto `*` sempre criam um novo objetos, e nunca modificam seus operandos. +Tanto `+` quanto `*` sempre criam um novo objeto, e nunca modificam seus operandos. [WARNING] ==== -Tenha cuidado com expressões como `a * n` quando `a` é uma sequência contendo itens mutáveis, pois o resultado pode ser surpreendente. Por exemplo, tentar inicializar uma lista de listas como `my_list = [[]] * 3` vai resultar em uma lista com três referências para a mesma lista interna, que provavelmente não é o quê você quer. +Tenha cuidado com expressões como `a * n` quando `a` é uma sequência contendo itens mutáveis, +pois o resultado pode ser surpreendente. +Por exemplo, tentar inicializar uma lista de listas como `my_list = [[]] * 3` +vai resultar em uma lista com três referências para a mesma lista interna, +que provavelmente não é o desejado. ==== A próxima seção fala das armadilhas ao se tentar usar `*` para inicializar uma lista de listas. ==== Criando uma lista de listas -Algumas((("lists", "building lists of lists"))) vezes precisamos inicializar uma lista com um certo número de listas aninhadas—para, por exemplo, distribuir estudantes em uma lista de equipes, ou para representar casas no tabuleiro de um jogo. +Algumas((("lists", "building lists of lists"))) vezes precisamos inicializar +uma lista com um certo número de listas aninhadas—para, por exemplo, +distribuir estudantes em uma lista de equipes, ou para representar casas no tabuleiro de um jogo. A melhor forma de fazer isso é com uma compreensão de lista, como no <>. [[ex_list_of_lists_ok]] .Uma lista com três listas de tamanho 3 pode representar um tabuleiro de jogo da velha ==== -[source, pycon] +[source, python] ---- >>> board = [['_'] * 3 for i in range(3)] <1> >>> board @@ -1358,7 +1563,7 @@ Um atalho tentador mas errado seria fazer algo como o <> [[ex_list_of_lists_wrong]] .Uma lista com três referências para a mesma lista é inútil ==== -[source, pycon] +[source, python] ---- >>> weird_board = [['_'] * 3] * 3 <1> >>> weird_board @@ -1374,7 +1579,7 @@ Enquanto ela não é modificada, tudo parece correr bem. O problema com o <> é que ele se comporta, essencialmente, como o código abaixo: -[source, python3] +[source, python] ---- row = ['_'] * 3 board = [] @@ -1385,7 +1590,7 @@ for i in range(3): Por outro lado, a compreensão de lista no <> equivale ao seguinte código: -[source, python3] +[source, python] ---- >>> board = [] >>> for i in range(3): @@ -1403,40 +1608,55 @@ Por outro lado, a compreensão de lista no <> equivale ao [TIP] ==== -Se o problema ou a solução mostrados nessa seção não estão claros para você, não se preocupe. O <> foi escrito para esclarecer a mecânica e os perigos das referências e dos objetos mutáveis. +Se o problema ou a solução mostrados nessa seção não estão claros para você, não se preocupe. +Escrevi o <> para esclarecer a mecânica e os perigos das referências e dos objetos mutáveis. ==== -Até aqui discutimos o uso dos operadores simples `+` e `+*+` com sequências, -mas existem também os operadores `+=` e `*=`, que produzem resultados muito diferentes, dependendo da mutabilidade da sequência alvo. A próxima seção explica como eles funcionam. +Até aqui discutimos o uso dos operadores simples `\+` e `\*` com sequências, +mas existem também os operadores `+=` e `*=`, que produzem resultados muito diferentes, +dependendo da mutabilidade da sequência alvo. +A próxima seção explica como eles funcionam. [[aug_assign_seqs]] ==== Atribuição aumentada com sequências -Os((("augmented assignment operators", id="augasop02")))((("+= (addition assignment) operator", id="adassign02")))((("*= (star equals) operator", id="stareq02")))((("addition assignment (+=) operator", id="additonassign02"))) operadores de atribuição aumentada `+=` e `++*=++`` se comportam de formas muito diferentes, dependendo do primeiro operando. Para simplificar a discussão, vamos primeiro nos concentrar na adição aumentada (`+=`), mas os conceitos se aplicam a `*=` e a outros operadores de atribuição aumentada. +Os((("augmented assignment operators", id="augasop02")))((("+= (addition assignment) operator", +id="adassign02")))((("*= (star equals) operator", id="stareq02")))((("addition assignment (+=) operator", +id="additonassign02"))) +operadores de atribuição aumentada `\+=` e `\*=` se comportam de formas muito diferentes, +dependendo do primeiro operando. +Para simplificar a discussão, vamos primeiro nos concentrar na adição aumentada (`+=`), +mas os conceitos se aplicam a `*=` e a outros operadores de atribuição aumentada. -O((("__iadd__"))) método especial que faz `+=` funcionar é `+__iadd__+` (significando "in-place addition", _adição no mesmo lugar_). +O método especial que faz `\+=` funcionar +chama-se `++__iadd__++` , que significa _in-place addition_ (adição interna). -Entretanto, se `+__iadd__+` não estiver implementado, o Python chama `+__add__+` como fallback. +Entretanto, se `+__iadd__+` não estiver implementado, Python usa `+__add__+` como antes de fazer a atribuição. Considere essa expressão simples: -[source, pycon] +[source, python] ---- >>> a += b ---- Se `a` implementar `+__iadd__+`, esse método será chamado. -No caso de sequências mutáveis (por exemplo, `list`, `bytearray`, `array.array`), `a` será modificada no lugar (isto é, o efeito ser similar a `a.extend(b)`). -Porém, quando `a` não implementa `+__iadd__+`, a expressão `a += b` tem o mesmo efeito de `a = a + b`: a expressão `a + b` é avaliada antes, produzindo um novo objeto, que então é vinculado a `a`. -Em outras palavras, a identidade do objeto vinculado a `a` pode ou não mudar, dependendo da disponibilidade de `+__iadd__+`. +No caso de sequências mutáveis (por exemplo, `list`, `bytearray`, `array.array`), +o objeto `a` será modificado no lugar (mesmo resultado de `a.extend(b)`). +Porém, quando `a` não implementa `+__iadd__+`, a expressão `+a += b+` tem o mesmo efeito de `a = a + b`: +a expressão `a + b` é avaliada antes, produzindo um novo objeto, que então é vinculado a `a`. +Em outras palavras, a identidade do objeto vinculado à variável `a` pode ou não mudar, +dependendo da existência de `+__iadd__+`. + +Em geral, para sequências mutáveis, é razoável supor que `+__iadd__+` está implementado e +que `+=` acontece _in-place_. +Para sequências imutáveis, obviamente isso não pode acontecer. -Em geral, para sequências mutáveis, é razoável supor que `+__iadd__+` está implementado e que `+=` acontece no mesmo lugar. -Para sequências imutáveis, obviamente não há forma disso acontecer. +O que acabei de escrever sobre `+=` também se aplica a `\*=`, que é implementado via `++__imul__++`. +Os métodos especiais `++__iadd__++` e `++__imul__++` são tratados no <>. -Isso que acabei de escrever sobre `+=` também se aplica a `+*=+`, que é implementado via `+__imul__+`. -Os métodos especiais `+__iadd__+` e `+__imul__+` são tratados no <>. -Aqui está uma demonstração de `+*=+` com uma sequência mutável e depois com uma sequência imutável: +Veja uma demonstração de `*=` com uma sequência mutável e depois com uma sequência imutável: -[source, pycon] +[source, python] ---- >>> l = [1, 2, 3] >>> id(l) @@ -1453,25 +1673,34 @@ Aqui está uma demonstração de `+*=+` com uma sequência mutável e depois com >>> id(t) 4301348296 <4> ---- -<1> O ID da lista inicial. +<1> O `id` da lista inicial. <2> Após a multiplicação, a lista é o mesmo objeto, com novos itens anexados. -<3> O ID da tupla inicial. +<3> O `id` da tupla inicial. <4> Após a multiplicação, uma nova tupla foi criada. -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, 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.] +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 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.] Vimos casos de uso comuns para `+=`. -A próxima seção mostra um caso lateral intrigante, que realça o real significado de "imutável" no contexto das tuplas. +A próxima seção mostra um caso surpreendente, +que demonstra o real significado de "imutável" no contexto das tuplas. [[tuple_puzzler]] ==== Um quebra-cabeça com a atribuição += -Tente responder sem usar o console: qual o resultado da avaliação das duas expressões no <>?footnote:[Agradeço a Leonardo Rochael e Cesar Kawakami por compartilharem esse enigma na Conferência PythonBrasil de 2013.] +Tente responder sem usar o console: qual o resultado da avaliação das duas expressões no +<>?footnote:[Agradeço a Leonardo Rochael e Cesar Kawakami +por compartilharem esse enigma na Conferência PythonBrasil de 2013.] [[ex_aug_item_assign_question]] .Um enigma ==== -[source, pycon] +[source, python] ---- >>> t = (1, 2, [30, 40]) >>> t[2] += [50, 60] @@ -1480,25 +1709,27 @@ Tente responder sem usar o console: qual o resultado da avaliação das duas exp O que acontece a seguir? Escolha a melhor alternativa: -[role="numbered-list"] - A. `t` se torna `(1, 2, [30, 40, 50, 60])`. - B. É gerado um `TypeError` com a mensagem `'tuple' object does not support item assignment` (_o objeto tupla não suporta atribuição de itens_). - C. Nenhuma das alternativas acima.. - D. Ambas as alternativas, A e B. +A):: `t` se torna `(1, 2, [30, 40, 50, 60])`. + +B):: É gerado um `TypeError` com a mensagem `'tuple' object does not support item assignment` (_o objeto tupla não suporta atribuição de itens_). -//// -PROD: a reader reporting this is rendering as a list numbered 1 to 4. We need to present the items labeled A to D, -as in a quiz—which it is. In the next paragraph I refer to the options by letters. -//// +C):: As alternativas A e B estão corretas. -Quando vi isso, tinha certeza que a resposta era B, mas, na verdade é D, "Ambas as alternativas, A e B"! -O <> é a saída real em um console rodando Python 3.10.footnote:[Alguns leitores sugeriram que a operação no exemplo pode ser realizada com `++t[2].extend([50,60])++`, sem erros. Eu sei disso, mas a intenção aqui é mostrar o comportamento estranho do operador `+=` nesse caso.] +D):: Nenhuma das alternativas acima. + + +Quando vi isso, tinha certeza que a resposta era B, mas, na verdade é C: as alternativas A e B estão corretas! + +O <> é a saída real em um console rodando Python +3.10.footnote:[Alguns leitores sugeriram que a operação no exemplo pode ser +realizada com `++t[2].extend([50,60])++`, sem erros. +Eu sei disso, mas a intenção aqui é mostrar o comportamento estranho do operador `+=` nesse caso.] [[ex_aug_item_assign_solution]] .O resultado inesperado: o item t2 é modificado _e_ uma exceção é gerada ==== -[source, pycon] +[source, python] ---- >>> t = (1, 2, [30, 40]) >>> t[2] += [50, 60] @@ -1510,18 +1741,22 @@ TypeError: 'tuple' object does not support item assignment ---- ==== -O https://fpy.li/2-14[Online Python Tutor] (EN) é uma ferramenta online fantástica para visualizar em detalhes o funcionamento do Python. A <> é uma composição de duas capturas de tela, mostrando os estados inicial e final da tupla `t` do <>. +O https://fpy.li/2-14[Online Python Tutor] (EN) é uma ferramenta online fantástica +para visualizar em detalhes o funcionamento de Python. +A <> é uma composição de duas capturas de tela, +mostrando os estados inicial e final da tupla `t` do <>. [[aug_item_assign_tutor]] .Estados inicial e final do enigma da atribuição de tuplas (diagrama gerado pelo Online Python Tutor). -image::images/flpy_0205.png[Diagrama de referência] +image::../images/flpy_0205.png[Diagrama de referência] -Se olharmos o bytecode gerado pelo Python para a expressão `s[a] += b` (<>), fica claro como isso acontece. +Se olharmos o bytecode gerado pelo Python para a expressão `s[a] += b` (<>), +fica claro como isso acontece. [[ex_aug_item_assign_bytecode]] .Bytecode para a expressão `s[a] += b` ==== -[source, pycon] +[source, python] ---- >>> dis.dis('s[a] += b') 1 0 LOAD_NAME 0 (s) @@ -1536,29 +1771,40 @@ Se olharmos o bytecode gerado pelo Python para a expressão `s[a] += b` (< Coloca o valor de `s[a]` no `TOS` (_Top Of Stack_, topo da pilha de execução_). -<2> Executa `TOS += b`. Isso é bem sucedido se `TOS` se refere a um objeto mutável (no <> é uma lista). +<1> Coloca o valor de `s[a]` no `TOS` (_Top Of Stack_, topo da pilha). +<2> Executa `TOS += b`. Isso é bem sucedido se `TOS` se refere a um objeto mutável +(no <> é uma lista). <3> Atribui `s[a] = TOS`. Isso falha se `s` é imutável (a tupla `t` no <>). -Esse exemplo é um caso raro—em meus 20 anos usando Python, nunca vi esse comportamento estranho estragar o dia de alguém. +Esse exemplo é um caso raro—nunca vi esse comportamento bizarro estragar o dia de alguém +em mais de 20 anos trabalhando com Python. Há três lições para tirar daqui: * Evite colocar objetos mutáveis em tuplas. * A atribuição aumentada não é uma operação atômica—acabamos de vê-la gerar uma exceção após executar parte de seu trabalho. -* Inspecionar o bytecode do Python não é muito difícil, e pode ajudar a ver o que está acontecendo por debaixo dos panos. +* Inspecionar o bytecode de Python não é muito difícil, e pode ajudar a ver o que está acontecendo por debaixo dos panos. -Após testemunharmos as sutilezas do uso de `+` e `*` para concatenação, podemos mudar de assunto e tratar de outra operação essencial com sequências: ordenação.((("", startref="Splusast02")))((("", startref="plusop02")))((("", startref="starseq02")))((("", startref="starseq02a")))((("", startref="concat02")))((("", startref="augasop02")))((("", startref="stareq02")))((("", startref="additonassign02")))((("", startref="adassign02"))) +Depois de ver as sutilezas do uso de `+` e `*` para concatenação, +podemos mudar de assunto e tratar de outra operação essencial com sequências: +ordenação.((("", startref="Splusast02")))((("", startref="plusop02")))((("", +startref="starseq02")))((("", startref="starseq02a")))((("", startref="concat02")))((("", +startref="augasop02")))((("", startref="stareq02")))((("", startref="additonassign02")))((("", startref="adassign02"))) [[sort_x_sorted]] === list.sort versus a função embutida sorted -O((("sequences", "list.sort versus sorted built-in", id="Slistsort02")))((("lists", "list.sort versus sorted built-in", id="Llistsort")))((("sorted function", id="sortfun02"))) método `list.sort` ordena uma lista no mesmo lugar—isto é, sem criar uma cópia. Ele devolve `None` para nos lembrar que muda a própria instância e não cria uma nova lista. -Essa é uma convenção importante da API do Python: -funções e métodos que mudam um objeto no mesmo lugar deve devolver `None`, -para deixar claro a quem chamou que o receptorfootnote:[Receptor (_receiver_) é o alvo de uma chamada a um método, o objeto vinculado a `self` no corpo do método.] +O((("sequences", "list.sort versus sorted built-in", id="Slistsort02")))((("lists", +"list.sort versus sorted built-in", id="Llistsort")))((("sorted function", id="sortfun02"))) +método `list.sort` ordena uma lista internamente—isto é, sem criar uma cópia. +Ele devolve `None` para nos lembrar que muda a própria instância e não cria uma nova lista. +Essa é uma convenção importante da API de Python: +funções e métodos que mudam um objeto internamente devem devolver `None`, +para deixar claro a quem chamou que o +receptorfootnote:[Receptor (_receiver_) é o alvo de uma chamada a um método, +o objeto vinculado a `self` no corpo do método.] foi modificado, e que nenhum objeto novo foi criado. Um comportamento similar pode ser observado, por exemplo, na função `random.shuffle(s)`, que devolve `None` após @@ -1568,36 +1814,48 @@ isto é, mudando a posição dos itens dentro da própria sequência. [NOTE] ==== -A convenção de devolver `None` para sinalizar mudanças no mesmo lugar tem uma desvantagem: não podemos cascatear chamadas a esses métodos. Em contraste, métodos que devolvem novos objetos (todos os métodos de `str`, por exemplo) podem ser cascateados no estilo de uma interface fluente. -Veja o artigo https://fpy.li/2-15["Fluent interface"] (EN) da Wikipedia em inglês para uma descrição mais detalhada deste tópico. +A convenção de devolver `None` para sinalizar mudanças internas tem uma desvantagem: +não podemos encadear chamadas a esses métodos. +Em contraste, métodos que devolvem novos objetos (todos os métodos de `str`, por exemplo) +podem ser cascateados no estilo de uma interface fluente. +Veja o artigo https://fpy.li/2-15["Fluent interface"] (EN) +da Wikipedia em inglês para uma descrição mais detalhada deste tópico. ==== A função embutida `sorted`, por outro lado, cria e devolve uma nova lista. -Ela aceita qualquer objeto iterável como um argumento, incluindo sequências imutáveis e geradores (veja o <>). -Independente do tipo do iterável passado a `sorted`, ela sempre cria e devolve uma nova lista. +Ela aceita qualquer objeto iterável como um argumento, incluindo sequências imutáveis e geradores (veja o <>). +independentemente do tipo do iterável passado a `sorted`, ela sempre cria e devolve uma nova lista. -Tanto `list.sort` quanto `sorted` podem receber dois argumentos de palavra-chave opcionais: +Tanto `list.sort` quanto `sorted` podem receber dois argumentos nomeados opcionais: `reverse`:: Se `True`, os itens são devolvidos em ordem decrescente (isto é, invertendo a comparação dos itens). O default é `False`. `key`:: - Uma função com um argumento que será aplicada a cada item, para produzir sua chave de ordenação. - Por exemplo, ao ordenar uma lista de strings, `key=str.lower` pode ser usada para realizar uma ordenação sem levar em conta maiúsculas e minúsculas, e `key=len` irá ordenar as strings pela quantidade de caracteres. + Uma função de um argumento que será aplicada a cada item, para produzir sua chave de ordenação. + Por exemplo, ao ordenar uma lista de strings, `key=str.lower` + pode ser usada para realizar uma ordenação sem levar em conta maiúsculas e minúsculas, + e `key=len` irá ordenar as strings pela quantidade de caracteres. O default é a função identidade (isto é, os itens propriamente ditos são comparados). [TIP] ==== -Também se pode usar o parâmetro de palavra-chave opcional `key` com as funções embutidas `min()` e `max()`, e com outras funções da biblioteca padrão (por exemplo, `itertools.groupby()` e `heapq.nlargest()`). +Também se pode usar o parâmetro de palavra-chave opcional `key` +com as funções embutidas `min()` e `max()`, +e com outras funções da biblioteca padrão (por exemplo, `itertools.groupby()` e `heapq.nlargest()`). ==== -Aqui estão alguns exemplos para esclarecer o uso dessas funções e dos argumentos de palavra-chave. -Os exemplos também demonstram que o algoritmo de ordenação do Python é estável (isto é, ele preserva a ordem relativa de itens que resultam iguais na comparação):footnote:[O principal algoritmo de ordenação do Python se chama Timsort, em homenagem a seu criador, Tim Peters. Para curiosidades sobre o Timsort, veja o <>.] +Aqui estão alguns exemplos para esclarecer o uso dessas funções e dos argumentos nomeados. +Os exemplos também demonstram que o algoritmo de ordenação de Python é estável +(isto é, ele preserva a ordem relativa de itens que resultam iguais na comparação):footnote:[O +principal algoritmo de ordenação de Python se chama Timsort, em homenagem a seu criador, +Tim Peters. +Para curiosidades sobre o Timsort, veja o <>.] -[source, pycon] +[source, python] ---- >>> fruits = ['grape', 'raspberry', 'apple', 'banana'] >>> sorted(fruits) @@ -1616,7 +1874,10 @@ Os exemplos também demonstram que o algoritmo de ordenação do Python é está >>> fruits ['apple', 'banana', 'grape', 'raspberry'] <8> ---- -<1> Isso produz uma lista de strings ordenadas alfabeticamente.footnote:[As palavras nesse exemplo estão ordenadas em ordem alfabética porque são 100% constituídas de caracteres ASCII em letras minúsculas. Veja o aviso após o exemplo.] +<1> Isso produz uma lista de strings ordenadas +alfabeticamente.footnote:[As palavras nesse exemplo estão ordenadas em ordem alfabética +porque são 100% constituídas de caracteres ASCII em letras minúsculas. +Veja o aviso após o exemplo.] <2> Inspecionando a lista original, vemos que ela não mudou. <3> Isso é a ordenação "alfabética" anterior, invertida. <4> Uma nova lista de strings, agora ordenada por tamanho. @@ -1624,64 +1885,81 @@ Como o algoritmo de ordenação é estável, "grape" e "apple," ambas com tamanh <5> Essas são strings ordenadas por tamanho em ordem descendente. Não é o inverso do resultado anterior porque a ordenação é estável e então, novamente, "grape" aparece antes de "apple." <6> Até aqui, a ordenação da lista `fruits` original não mudou. -<7> Isso ordena a lista no mesmo lugar, devolvendo `None` (que o console omite). +<7> Isso ordena a lista internamente, devolvendo `None` (que o console omite). <8> Agora `fruits` está ordenada. [WARNING] ==== -Por((("strings", "default sorting of"))) default, o Python ordena as strings lexicograficamente por código de caractere. -Isso quer dizer que as letras maiúsculas ASCII virão antes das minúsculas, e que os caracteres não-ASCII dificilmente serão ordenados de forma razoável. -A seção <> trata de maneiras corretas de ordenar texto da forma esperada por seres humanos. +Por((("strings", "default sorting of"))) default, +Python ordena as strings lexicograficamente por código de caractere. +Isso quer dizer que as letras maiúsculas ASCII virão antes das minúsculas, +e caracteres não-ASCII dificilmente serão ordenados de forma razoável. +A <> trata de maneiras corretas de ordenar texto da forma esperada por seres humanos. ==== -Uma vez ordenadas, podemos realizar em nossas sequências de forma muito eficiente. -Um((("bisect module"))) algoritmo de busca binária já é fornecido no módulo `bisect` da biblioteca padrão do Python. +Uma vez ordenadas, podemos realizar buscas em nossas sequências de forma muito eficiente. +Um((("bisect module"))) algoritmo de busca binária já é fornecido no módulo `bisect` da biblioteca padrão de Python. Aquele módulo também inclui a função `bisect.insort`, que você pode usar para assegurar que suas sequências ordenadas permaneçam ordenadas. -Há uma introdução ilustrada ao módulo `bisect` no post https://fpy.li/bisect["Managing Ordered Sequences with Bisect" (_Gerenciando Sequências Ordenadas com Bisect_)] (EN) -em pass:[fluentpython.com], o website que complementa este livro. +Há uma introdução ilustrada ao módulo `bisect` no post https://fpy.li/bisect["Managing Ordered Sequences with Bisect" (_Gerenciando Sequências Ordenadas com Bisect_)] +no site que complementa este livro. Muito do que vimos até aqui neste capítulo se aplica a sequências em geral, não apenas a listas ou tuplas. -Programadores Python às vezes usam excessivamente o tipo `list`, por ele ser tão conveniente—eu mesmo já fiz isso. Por exemplo, se você está processando grandes listas de números, deveria considerar usar arrays em vez de listas. O restante do capítulo é dedicado a alternativas a listas e tuplas.((("", startref="Slistsort02")))((("", startref="Llistsort")))((("", startref="sortfun02"))) +Programadores Python às vezes usam excessivamente o tipo `list`, +por ele ser tão conveniente—eu mesmo já fiz isso. +Por exemplo, se você está processando grandes listas de números, +deveria considerar usar arrays em vez de listas. +O restante do capítulo é dedicado a alternativas a listas e tuplas.((("", startref="Slistsort02")))((("", startref="Llistsort")))((("", startref="sortfun02"))) === Quando uma lista não é a resposta -O((("sequences", "alternatives to lists", id="Salt02")))((("lists", "alternatives to", id="Lalt02"))) tipo `list` é flexível e fácil de usar mas, dependendo dos requerimentos específicos, há opções melhores. +O((("sequences", "alternatives to lists", id="Salt02")))((("lists", "alternatives to", id="Lalt02"))) +tipo `list` é flexível e fácil de usar mas, dependendo dos requisitos específicos, há opções melhores. Por exemplo, um `array` economiza muita memória se você precisa manipular milhões de valores de ponto flutuante. -Por outro lado, se você está constantemente acrescentando e removendo itens das pontas opostas de uma lista, é bom saber que um((("queues", "deque (double-ended queue)")))((("deque (double-ended queue)")))((("FIFO (first in, first out)"))) `deque` (uma fila com duas pontas) é uma estrutura de dados FIFOfootnote:[Sigla em inglês para "First in, first out" (_Primeiro a entrar, primeiro a sair_—o comportamento padrão de filas.] mais eficiente. +Por outro lado, se você está constantemente acrescentando e removendo itens das pontas opostas de uma lista, +é bom saber que um((("queues", "deque (double-ended queue)")))((("deque (double-ended queue)")))((("FIFO (first in, first out)"))) +`deque` (uma fila com duas pontas) é uma estrutura de dados +FIFOfootnote:[Sigla em inglês para "First in, first out" +(primeiro a entrar, primeiro a sair), o comportamento padrão de filas.] mais eficiente. [TIP] ==== Se seu código frequentemente verifica se um item está presente em uma coleção (por exemplo, `item in my_collection`), considere usar um `set` para `my_collection`, especialmente se ela contiver um número grande de itens. Um `set` é otimizado para verificação rápida de presença de itens. -Eles também são iteráveis, mas não são coleções, porque a ordenação de itens de sets não é especificada. -Vamos falar deles no <>. +Eles também são iteráveis, mas não são sequências, porque a ordenação de itens de conjuntos não é especificada. +Vamos falar deles no <>. ==== -O restante desse capítulo discute tipos mutáveis de sequências que, em muitos casos, podem substituir as listas. Começamos pelos arrays. +O restante desse capítulo discute tipos mutáveis de sequências que, em muitos casos, podem substituir as listas. +Começamos pelos arrays. [[arrays_sec]] ==== Arrays -Se((("arrays", id="array02"))) uma lista contém apenas números, uma `array.array` é um substituto mais eficiente. -Arrays suportam todas as operações das sequências mutáveis (incluindo `.pop`, `.insert`, e `.extend`), bem como métodos adicionais para carregamento e armazenamento rápidos, tais como +Se((("arrays", id="array02"))) uma lista contém apenas números, um `array.array` é um substituto mais eficiente. +Arrays suportam todas as operações das sequências mutáveis (incluindo `.pop`, `.insert`, e `.extend`), +bem como métodos adicionais para carregamento e armazenamento rápidos, como `.frombytes` e `.tofile`. -Um array do Python quase tão enxuto quanto um array do C. -Como mostrado na <>, um `array` de valores `float` não mantém instâncias completas de `float`, mas apenas pacotes de bytes representando seus valores em código de máquina—de forma similar a um array de `double` na linguagem C. -Ao criar um `array`, você fornece um código de tipo (_typecode_), uma letra que determina o tipo C subjacente usado para armazenar cada item no array. +Um array de Python é quase tão enxuto quanto um array do C. +Como mostrado na <>, um `array` de valores `float` não contém objetos da classe `float`, +mas apenas os bytes representando seus valores em código de máquina—como um array do tipo `double` na linguagem C. +Ao criar um `array`, você fornece um código de tipo (_typecode_), +uma letra que determina o tipo na linguagem C usado para armazenar cada item na memória. Por exemplo, +b+ é o código de tipo para o que o C chama de `signed char`, um inteiro variando de -128 a 127. -Se você criar uma `array('b')`, então cada item será armazenado em um único byte e será interpretado como um inteiro. Para grandes sequências de números, isso economiza muita memória. -E o Python não permite que você insira qualquer número que não corresponda ao tipo do array. +Se você criar uma `array('b')`, então cada item será armazenado em um único byte e será interpretado como um inteiro. +Para grandes sequências de números, isso economiza muita memória. +E Python não permite que você insira qualquer número que não corresponda ao tipo do array. -O <> mostra a criação, o armazenamento e o carregamento de um array de 10 milhões de números de ponto flutuante aleatórios. +O <> mostra a criação, o armazenamento e o carregamento +de um array de 10 milhões de números de ponto flutuante aleatórios. [[ex_array_io]] .Criando, armazenando e carregando uma grande array de números de ponto flutuante. ==== -[source, pycon] +[source, python] ---- >>> from array import array <1> >>> from random import random @@ -1702,7 +1980,8 @@ True ---- ==== <1> Importa o tipo `array`. -<2> Cria um array de números de ponto flutuante de dupla precisão (código de tipo `'d'`) a partir de qualquer objeto iterável—nesse caso, uma expressão geradora. +<2> Cria um array de números de ponto flutuante de dupla precisão (código de tipo `'d'`) +a partir de qualquer objeto iterável—nesse caso, uma expressão geradora. <3> Inspeciona o último número no array. <4> Salva o array em um arquivo binário. <5> Cria um array vazio de números de ponto flutuante de dupla precisão @@ -1712,13 +1991,19 @@ True Como você pode ver, `array.tofile` e `array.fromfile` são fáceis de usar. Se você rodar o exemplo, verá que são também muito rápidos. -Um pequeno experimento mostra que `array.fromfile` demora aproximadamente 0,1 segundos para carregar 10 milhões de números de ponto flutuante de dupla precisão de um arquivo binário criado com `array.tofile`. +Um pequeno experimento mostra que `array.fromfile` demora aproximadamente 0,1 +segundos para carregar 10 milhões de números de ponto flutuante de +dupla precisão de um arquivo binário criado com `array.tofile`. Isso é quase 60 vezes mais rápido que ler os números de um arquivo de texto, algo que também exige passar cada linha para a função embutida `float`. -Salvar o arquivo com `array.tofile` é umas sete vezes mais rápido que escrever um número de ponto flutuante por vez em um arquivo de texto. -Além disso, o tamanho do arquivo binário com 10 milhões de números de dupla precisão é de 80.000.000 bytes (8 bytes por número, zero excesso), enquanto o arquivo de texto ocupa 181.515.739 bytes para os mesmos dados. +Salvar o arquivo com `array.tofile` é umas sete vezes mais rápido que +escrever um número de ponto flutuante por vez em um arquivo de texto. +Além disso, o tamanho do arquivo binário com 10 milhões de números de dupla precisão é de +80.000.000 bytes (8 bytes por número, nenhum byte a mais), +enquanto o arquivo de texto ocupa 181.515.739 bytes para os mesmos dados. -Para o caso específico de arrays numéricas representando dados binários, tal como bitmaps de imagens, o Python tem os tipos `bytes` e `bytearray`, discutidos na seção <>. +Para o caso específico de arrays numéricas representando dados binários, +tal como bitmaps de imagens, Python tem os tipos `bytes` e `bytearray`, discutidos no <>. Vamos encerrar essa seção sobre arrays com a <>, comparando as características de `list` e `array.array`. @@ -1729,7 +2014,7 @@ comparando as características de `list` e `array.array`. |================================================================================================================================================ | | list | array |   | `+s.__add__(s2)+` | ● | ● | `++s + s2++`—concatenação -| `+s.__iadd__(s2)+` | ● | ● | `++s += s2++`—concatenação no mesmo lugar +| `+s.__iadd__(s2)+` | ● | ● | `++s += s2++`—concatenação interna | `s.append(e)` | ● | ● | Acrescenta um elemento após o último | `s.byteswap()` | | ● | Permuta os bytes de todos os itens do array para conversão de _endianness_ (ordem de interpretação bytes) | `s.clear()` | ● | | Apaga todos os itens @@ -1738,10 +2023,10 @@ comparando as características de `list` e `array.array`. | `+s.__copy__()+` | | ● | Suporte a `copy.copy` | `s.count(e)` | ● | ● | Conta as ocorrências de um elemento | `+s.__deepcopy__()+` | | ● | Suporte otimizado a `copy.deepcopy` -| `+s.__delitem__(p)+` | ● | ● | Remove item na posição `p` +| `+s.__delitem__(p)+` | ● | ● | Remove o item na posição `p` | `s.extend(it)` | ● | ● | Acrescenta itens a partir do iterável `it` -| `s.frombytes(b)` | | ● | Acrescenta itens de uma sequência de bytes, interpretada como valores em código de máquina empacotados -| `s.fromfile(f, n)` | | ● | Acrescenta `n` itens de um arquivo binário `f`, interpretado como valores em código de máquina empacotados +| `s.frombytes(b)` | | ● | Acrescenta itens de uma sequência de bytes, interpretada como valores compactos de máquina +| `s.fromfile(f, n)` | | ● | Acrescenta `n` itens de um arquivo binário `f`, interpretado como valores compactos de máquina | `s.fromlist(l)` | | ● | Acrescenta itens de lista; se um deles causar um `TypeError`, nenhum item é acrescentado | `+s.__getitem__(p)+` | ● | ● | `++s[p]++`—obtém o item ou fatia na posição | `s.index(e)` | ● | ● | Encontra a posição da primeira ocorrência de `e` @@ -1750,57 +2035,71 @@ comparando as características de `list` e `array.array`. | `+s.__iter__()+` | ● | ● | Obtém iterador | `+s.__len__()+` | ● | ● | `++len(s)++`—número de itens | `+s.__mul__(n)+` | ● | ● | `++s * n++`—concatenação repetida -| `+s.__imul__(n)+` | ● | ● | `++s *= n++`—concatenação repetida no mesmo lugar -| `+s.__rmul__(n)+` | ● | ● | ++n * s++—concatenação repetida invertidafootnote:[Operadores invertidos são explicados no capítulo <>.] +| `+s.__imul__(n)+` | ● | ● | `++s *= n++`—concatenação repetida interna +| `+s.__rmul__(n)+` | ● | ● | ++n * s++—concatenação repetida invertidafootnote:[Operadores reversos são explicados no <>.] | `s.pop([p])` | ● | ● | Remove e devolve o item na posição `p` (default: o último) -| `s.remove(e)` | ● | ● | Remove a primeira ocorrência do elemento `e` por valor -| `s.reverse()` | ● | ● | Reverte a ordem dos itens no mesmo lugar +| `s.remove(e)` | ● | | Remove o primeiro elemento de valor igual a `e` +| `s.reverse()` | ● | ● | Reverte a ordem dos itens internamente | `+s.__reversed__()+` | ● | | Obtém iterador para percorrer itens do último até o primeiro | `+s.__setitem__(p, e)+` | ● | ● | ++s[p] = e++—coloca `e` na posição `p`, sobrescrevendo item ou fatia existente -|`s.sort([key], [reverse])` | ● | | Ordena itens no mesmo lugar, com os argumentos de palavra-chave opcionais `key` e `reverse` -| `s.tobytes()` | | ● | Devolve itens como pacotes de valores em código de máquina em um objeto `bytes` -| `s.tofile(f)` | | ● | Grava itens como pacotes de valores em código de máquina no arquivo binário `f` +|`s.sort([key], [reverse])` | ● | | Ordena itens internamente, com os argumentos nomeados opcionais `key` e `reverse` +| `s.tobytes()` | | ● | Devolve itens como valores de máquina compactos em um objeto `bytes` +| `s.tofile(f)` | | ● | Grava itens como valores de máquina compactos no arquivo binário `f` | `s.tolist()` | | ● | Devolve os itens como objetos numéricos em uma `list` | `s.typecode` | | ● | String de um caractere identificando o tipo em C dos itens |================================================================================================================================================ [TIP] ==== -Até o Python 3.10, o tipo `array` ainda não tem um método `sort` equivalente a `list.sort()`, que reordena os elementos na própria estrutura de dados, sem copiá-la. Se você precisa ordenar um array, use a função embutida `sorted` para reconstruir o array: +Até Python 3.10, o tipo `array` ainda não tem um método `sort` equivalente a `list.sort()`, +que reordena os elementos na própria estrutura de dados, sem copiá-la. +Se você precisa ordenar um array, use a função embutida `sorted` para reconstruir o array: -[source, python3] +[source, python] ---- a = array.array(a.typecode, sorted(a)) ---- -Para manter a ordem de um array ordenado ao acrescentar novos itens, use a função https://docs.python.org/pt-br/3/library/bisect.html#bisect.insort[`bisect.insort`]. +Para manter a ordem de um array ordenado ao acrescentar novos itens, use a função https://fpy.li/2t[`bisect.insort`]. ==== Se você trabalha muito com arrays e não conhece `memoryview`, -está perdendo oportunidades. Veja o próximo tópico.((("", startref="array02"))) +pode estar desperdiçando memória e CPU. +Veja o próximo tópico.((("", startref="array02"))) [[memoryview_sec]] ==== Views de memória -A((("memoryview class", id="memview02"))) classe embutida `memoryview` é um tipo sequência de memória compartilhada, que permite manipular fatias de arrays sem copiar bytes. Ela foi inspirada pela biblioteca NumPy (que discutiremos brevemente, na seção <>). -Travis Oliphant, autor principal da NumPy, responde assim à questão https://fpy.li/2-17["When should a memoryview be used?" _Quando se deve usar uma memoryview?_]: +A((("memoryview class", id="memview02"))) classe embutida `memoryview` é um tipo sequência de memória compartilhada, +que permite manipular fatias de arrays sem copiar bytes. +Ela foi inspirada pela biblioteca NumPy (que discutiremos brevemente, na <>). +Travis Oliphant, autor principal da NumPy, +responde assim à questão https://fpy.li/2-17["When should a memoryview be used?" _Quando se deve usar uma memoryview?_]: [quote] ____ -Uma memoryview é essencialmente uma estrutura de array Numpy generalizada dentro do próprio Python (sem a matemática). Ela permite compartilhar memória entre estruturas de dados (coisas como imagens PIL, bancos de dados SQLite, arrays da NumPy, etc.) sem copiar antes. -Isso é muito importante para conjuntos grandes de dados. +Uma memoryview é essencialmente uma estrutura de array Numpy generalizada dentro do próprio Python +(sem a matemática). +Ela permite compartilhar memória entre estruturas de dados +(coisas como imagens PIL, bancos de dados SQLite, arrays da NumPy, etc.) sem copiar bytes. +Isso é muito importante ao lidar com grandes conjuntos de dados. ____ -Usando uma notação similar ao módulo `array`, o método `memoryview.cast` permite mudar a forma como múltiplos bytes são lidos ou escritos como unidades, sem a necessidade de mover os bits. `memoryview.cast` devolve ainda outro objeto `memoryview`, sempre compartilhando a mesma memória. +Usando uma notação similar ao módulo `array`, o método `memoryview.cast` permite mudar +a forma como múltiplos bytes são lidos ou escritos como unidades, +sem a necessidade de alterar os bytes. +`memoryview.cast` devolve um novo objeto `memoryview`, +sempre compartilhando a mesma memória. -O <> mostra como criar views alternativas da mesmo array de 6 bytes, para operar com ele como uma matriz de 2x3 ou de 3x2. +O <> mostra como criar views alternativas da mesmo array de 6 bytes, +para operar com ele como uma matriz de 2x3 ou de 3x2. [[ex_memoryview_demo]] .Manipular 6 bytes de memória como views de 1×6, 2×3, e 3×2 ==== -[source, pycon] +[source, python] ---- >>> from array import array >>> octets = array('B', range(6)) # <1> @@ -1833,7 +2132,7 @@ O <> mostra como mudar um único byte de um item em um [[ex_memoryview_evil_demo]] .Mudando o valor de um item em um array de inteiros de 16 bits trocando apenas o valor de um de seus bytes ==== -[source, pycon] +[source, python] ---- >>> numbers = array.array('h', [-2, -1, 0, 1, 2]) >>> memv = memoryview(numbers) <1> @@ -1849,9 +2148,9 @@ O <> mostra como mudar um único byte de um item em um array('h', [-2, -1, 1024, 1, 2]) <6> ---- ==== -<1> Cria uma `memoryview` a partir de um array de 5 inteiros com sinal de 16 bits (código de tipo `'h'`). +<1> Cria uma `memoryview` a partir de um array de 5 inteiros de 16 bits com sinal (tipo `'h'`). <2> `memv` vê os mesmos 5 itens no array. -<3> Cria `memv_oct`, transformando os elementos de `memv` em bytes (código de tipo `'B'`). +<3> Cria `memv_oct`, transformando os elementos de `memv` em bytes (tipo `'B'`). <4> Exporta os elementos de `memv_oct` como uma lista de 10 bytes, para inspeção. <5> Atribui o valor `4` ao byte com offset `5`. <6> Observe a mudança em `numbers`: um `4` no byte mais significativo de um inteiro de 2 bytes sem sinal é `1024`. @@ -1859,32 +2158,43 @@ array('h', [-2, -1, 1024, 1, 2]) <6> [NOTE] ==== Você pode ver um exemplo de inspeção de uma `memoryview` com o pacote `struct` em -pass:[fluentpython.com]: https://fpy.li/2-18["Parsing binary records with struct" _Analisando registros binários com struct_] (EN). ==== -Enquanto isso, se você está fazendo processamento numérico avançado com arrays, deveria estar usando as bibliotecas NumPy. +Enquanto isso, se você está fazendo processamento numérico avançado com arrays, +deveria estar usando as bibliotecas NumPy. Vamos agora fazer um breve passeio por elas.((("", startref="memview02"))) [[numpy_sec]] ==== NumPy -Por((("NumPy", id="numpy02"))) todo esse livro, procuro destacar o que já existe na biblioteca padrão do Python, para que você a aproveite ao máximo. -Mas a NumPy é tão maravilhosa que exige um desvio. - -Por suas operações avançadas de arrays e matrizes, o Numpy é a razão pela qual o Python se tornou uma das principais linguagens para aplicações de computação científica. -A Numpy implementa tipos multidimensionais e homogêneos de arrays e matrizes, que podem conter não apenas números, mas também registros definidos pelo usuário. E fornece operações eficientes ao nível desses elementos. - -A SciPy((("SciPy", id="scipy02"))) é uma biblioteca criada usando a NumPy, e oferece inúmeros algoritmos de computação científica, incluindo álgebra linear, cálculo numérico e estatística. -A SciPy é rápida e confiável porque usa a popular base de código C e Fortran do https://fpy.li/2-19[Repositório Netlib]. -Em outras palavras, a SciPy dá a cientistas o melhor de dois mundos: um prompt iterativo e as APIs de alto nível do Python, junto com funções estáveis e de eficiência comprovada para processamento de números, otimizadas em C e Fortran - -O <>, uma amostra muito rápida da Numpy, demonstra algumas operações básicas com arrays bi-dimensionais. +Nesse((("NumPy", id="numpy02"))) livro eu priorizo o que já existe na biblioteca padrão de Python, +para que você a aproveite ao máximo. +Mas a NumPy é tão importante que exige um desvio. + +Graças a suas operações avançadas com arrays e matrizes, +o Numpy permitiu que Python se tornasse uma das principais linguagens para aplicações de computação científica. +A Numpy implementa tipos multidimensionais e homogêneos de arrays e matrizes, +que podem conter não apenas números, mas também registros definidos pelo usuário. +E fornece operações eficientes ao nível desses elementos. + +A SciPy((("SciPy", id="scipy02"))) é uma biblioteca criada usando a NumPy, +e oferece inúmeros algoritmos de computação científica, incluindo álgebra linear, +cálculo numérico e estatística. +A SciPy é rápida e confiável porque usa a popular base de código C e Fortran do +https://fpy.li/2-19[Repositório Netlib]. +Em outras palavras, a SciPy dá a cientistas o melhor de dois mundos: +um prompt iterativo e as APIs de alto nível de Python, +junto com funções estáveis e comprovadamente eficientes para processamento numérico, +otimizadas em C e Fortran. + +O <>, uma demonstração muito rápida da Numpy, +demonstra algumas operações básicas com arrays bi-dimensionais. [[ex_numpy_array]] .Operações básicas com linhas e colunas em uma `numpy.ndarray` ==== -[source, pycon] +[source, python] ---- >>> import numpy as np <1> >>> a = np.arange(12) <2> @@ -1912,18 +2222,20 @@ array([[ 0, 4, 8], [ 3, 7, 11]]) ---- ==== -<1> Importa a NumPy, que precisa ser instalada previamente (ela não faz parte da biblioteca padrão do Python). Por convenção, `numpy` é importada como `np`. -<2> Cria e inspeciona uma `numpy.ndarray` com inteiros de `0` a `11`. -<3> Inspeciona as dimensões do array: essa é um array com uma dimensão e 12 elementos. +<1> Importa a NumPy, que precisa ser instalada previamente (ela não faz parte da biblioteca padrão de Python). +Por convenção, `numpy` é importada como `np`. +<2> Cria e inspeciona um `numpy.ndarray` com inteiros de `0` a `11`. +<3> Inspeciona as dimensões do array: esse é um array com uma dimensão e 12 elementos. <4> Muda o formato do array, acrescentando uma dimensão e depois inspecionando o resultado. <5> Obtém a linha no índice `2` <6> Obtém elemento na posição `2, 1`. <7> Obtém a coluna no índice `1` <8> Cria um novo array por transposição (permutando as colunas com as linhas) -A NumPy também suporta operações de alto nível para carregar, salvar e operar sobre todos os elementos de uma `numpy.ndarray`: +A NumPy também suporta operações de alto nível para carregar, +salvar e operar sobre todos os elementos de um `numpy.ndarray`: -[source, pycon] +[source, python] ---- >>> import numpy >>> floats = numpy.loadtxt('floats-10M-lines.txt') <1> @@ -1946,35 +2258,56 @@ memmap([ 3016362.69195522, 535281.10514262, 4566560.44373946]) <3> Multiplica cada elemento no array `floats` por `.5` e inspeciona novamente os três últimos elementos. <4> Importa o cronômetro de medida de tempo em alta resolução (disponível desde o Python 3.3). -<5> Divide cada elemento por `3`; o tempo decorrido para dividir os 10 milhões de números de ponto flutuante é menos de 40 milissegundos. +<5> Divide cada elemento por `3`; +o tempo decorrido para dividir os 10 milhões de números de ponto flutuante é menos de 40 milissegundos. <6> Salva o array em um arquivo binário _.npy_. -<7> Carrega os dados como um arquivo mapeado na memória em outro array; isso permite o processamento eficiente de fatias do array, mesmo que ele não caiba inteiro na memória. +<7> Carrega os dados como um arquivo mapeado na memória em outro array; +isso permite o processamento eficiente de fatias do array, mesmo que ele não caiba inteiro na memória. <8> Inspeciona os três últimos elementos após multiplicar cada elemento por `6`. -Mas isso foi apenas um aperitivo. - -A NumPy e a SciPy são bibliotecas formidáveis, e estão na base de outras ferramentas fantásticas, como a https://fpy.li/2-20[Pandas] (EN)—que implementa tipos eficientes de arrays capazes de manter dados não-numéricos, e fornece funções de importação/exportação em vários formatos diferentes, como _.csv_, _.xls_, dumps SQL, HDF5, etc.—e a https://fpy.li/2-21[scikit-learn] (EN), o conjunto de ferramentas para Aprendizagem de Máquina mais usado atualmente. -A maior parte das funções da NumPy e da SciPy são implementadas em C ou C++, e conseguem aproveitar todos os núcleos de CPU disponíveis, pois podem liberar a GIL((("Global Interpreter Lock (GIL)"))) (Global Interpreter Lock, _Trava Global do Interpretador_) do Python. -O projeto https://fpy.li/dask[Dask] suporta a paralelização do processamento da NumPy, da Pandas e da scikit-learn para grupos (_clusters_) de máquinas. -Esses pacotes merecem que livros inteiros sejam escritos sobre eles. -Este não um desses livros. -Mas nenhuma revisão das sequências do Python estaria completa sem pelo menos uma breve passagem pelos arrays da NumPy. - -Tendo olhado as sequências planas—arrays padrão e arrays da NumPy—vamos agora nos voltar para um grupo completamente diferentes de substitutos para a boa e velha `list`: filas (_queues_).((("", startref="numpy02")))((("", startref="scipy02"))) +Isso foi apenas um aperitivo. + +A NumPy e a SciPy são bibliotecas formidáveis, e estão na base de outras ferramentas fantásticas, +como a https://fpy.li/2-20[Pandas] (EN)—que implementa tipos eficientes de arrays capazes de conter dados não-numéricos, +e fornece funções de importação/exportação em vários formatos diferentes, como _.csv_, _.xls_, dumps SQL, HDF5, +etc.—e a https://fpy.li/2-21[scikit-learn] (EN), o conjunto de ferramentas para Aprendizagem de Máquina mais usado atualmente. +A maior parte das funções da NumPy e da SciPy são implementadas em C ou {cpp}, +e conseguem aproveitar todos os núcleos de CPU disponíveis, pois podem liberar a GIL((("Global Interpreter Lock (GIL)"))) +(Global Interpreter Lock, _Trava Global do Interpretador_) de Python. +O projeto https://fpy.li/dask[Dask] suporta a paralelização do processamento da NumPy, +da Pandas e da scikit-learn para grupos (_clusters_) de máquinas. +Esses pacotes merecem livros inteiros. +Este não é um desses livros, +mas nenhuma revisão das sequências de Python estaria completa sem pelo menos uma breve passagem pelos arrays da NumPy. + +Tendo visto as sequências planas—arrays padrão e arrays da NumPy—vamos agora +nos voltar para um grupo completamente diferente de substitutos para a boa e velha `list`: +filas (_queues_).((("", startref="numpy02")))((("", startref="scipy02"))) ==== Deques e outras filas -Os((("queues", "deque (double-ended queue)")))((("deque (double-ended queue)", id="deque02")))(((".append method", primary-sortas="append method")))(((".pop method", primary-sortas="pop method")))((("FIFO (first in, first out)"))) métodos `.append` e `.pop` tornam uma `list` usável como uma pilha (_stack_) ou uma fila (_queue_) (usando `.append` e `.pop(0)`, se obtém um comportamento FIFO). -Mas inserir e remover da cabeça de uma lista (a posição com índice 0) é caro, pois a lista toda precisa ser deslocada na memória. - -A((("collections.deque class", id="coldeq02"))) classe `collections.deque` é uma fila de duas pontas e segura para usar com threads, projetada para inserção e remoção rápida nas duas pontas. É também a estrutura preferencial se você precisa manter uma lista de "últimos itens vistos" ou coisa semelhante, pois um `deque` pode ser delimitado—isto é, criado com um tamanho máximo fixo. Se um `deque` delimitado está cheio, quando se adiciona um novo item, o item na ponta oposta é descartado. +Os((("queues", "deque (double-ended queue)")))((("deque (double-ended queue)", +id="deque02")))(((".append method", primary-sortas="append method")))(((".pop method", +primary-sortas="pop method")))((("FIFO (first in, first out)"))) +métodos `.append` e `.pop` tornam uma `list` usável como uma pilha (_stack_) +ou uma fila (_queue_) (usando `.append` e `.pop(0)`, se obtém o comportamento FIFO de uma fila). +Mas inserir e remover da cabeça de uma lista +(a posição com índice 0) é caro, pois a lista toda precisa ser deslocada na memória. + +A((("collections.deque class", id="coldeq02"))) classe `collections.deque` é +uma fila _thread-safe_ (segura para usar com threads), +otimizada para inserção e remoção rápida nas duas pontas. +É também a estrutura preferencial se você precisa manter uma lista de +"últimos itens vistos" ou coisa semelhante, +pois um `deque` pode ser delimitado—isto é, criado com um tamanho máximo fixo. +Se um `deque` delimitado está cheio, quando se adiciona um novo item, o item na ponta oposta é descartado. O <> mostra algumas das operações típicas com um `deque`. [[ex_deque]] .Usando um `deque` ==== -[source, pycon] +[source, python] ---- >>> from collections import deque >>> dq = deque(range(10), maxlen=10) <1> @@ -1997,169 +2330,246 @@ deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33], maxlen=10) deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8], maxlen=10) ---- ==== -<1> O argumento opcional `maxlen` determina o número máximo de itens permitidos nessa instância de `deque`; isso estabelece o valor de um atributo de instância `maxlen`, somente de leitura. -<2> Rotacionar com `n > 0` retira itens da direita e os recoloca pela esquerda; quando `n < 0`, os itens são retirados pela esquerda e anexados pela direita. -<3> Acrescentar itens a um `deque` cheio (`len(d) == d.maxlen`) elimina itens da ponta oposta. Observe, na linha seguinte, que o `0` foi descartado. +<1> O argumento opcional `maxlen` determina o número máximo de itens permitidos nessa instância de `deque`; +isso estabelece o valor de um atributo de instância `maxlen`, somente de leitura. +<2> Rotacionar com `n > 0` retira itens da direita e os recoloca pela esquerda; +quando `n < 0`, os itens são retirados pela esquerda e anexados pela direita. +<3> Acrescentar itens a um `deque` cheio (`len(d) == d.maxlen`) elimina itens da ponta oposta. +Na linha seguinte, note que o `0` foi descartado. <4> Acrescentar três itens à direita derruba `-1`, `1`, e `2` da extremidade esquerda. -<5> Observe que `extendleft(iter)` acrescenta cada item sucessivo do argumento `iter` do lado esquerdo do `deque`, então a posição final dos itens é invertida. +<5> Observe que `extendleft(iter)` acrescenta cada item sucessivo do argumento `iter` +do lado esquerdo do `deque`, então a posição final dos itens é invertida. -A <> compara os métodos específicos de `list` e `deque` (omitindo aqueles que também aparecem em `object`). +A <> compara os métodos específicos de `list` e `deque` +(omitindo aqueles que também aparecem em `object`). -Veja que `deque` implementa a maioria dos métodos de `list`, acrescentando alguns específicos ao seu modelo, como `popleft` e `rotate`. +Veja que `deque` implementa a maioria dos métodos de `list`, +acrescentando alguns específicos ao seu modelo, como `popleft` e `rotate`. Mas há um custo oculto: remover itens do meio de um `deque` não é rápido. A estrutura é realmente otimizada para acréscimos e remoções pelas pontas. -As operações `append` e `popleft` são atômicas, então `deque` pode ser usado de forma segura como uma fila FIFO em aplicações multithread sem a necessidade de travas. +As operações `append` e `popleft` são atômicas, +então `deque` pode ser usado de forma segura como uma fila FIFO em aplicações multithread sem a necessidade de travas. [[list_x_deque_methods_tbl]] .Métodos implementados em `list` ou `deque` (aqueles também implementados por `object` foram omitidos para preservar espaço) [options="header"] |================================================================================================================================================== -| | list | deque |   -| `+s.__add__(s2)+` | ● | | ++s + s2++—concatenação -| `+s.__iadd__(s2)+` | ● | ● | ++s += s2++—concatenação no mesmo lugar -| `s.append(e)` | ● | ● | Acrescenta um elemento à direita (após o último) -| `s.appendleft(e)` | | ● | Acrescenta um elemento à esquerda (antes do primeiro) -| `s.clear()` | ● | ● | Apaga todos os itens +| | list | deque |   +| `+s.__add__(s2)+` | ● | | `++s + s2++`—concatenação +| `+s.__iadd__(s2)+` | ● | ● | `++s += s2++`—concatenação interna +| `s.append(e)` | ● | ● | Acrescenta um elemento à direita (após o último) +| `s.appendleft(e)` | | ● | Acrescenta um elemento à esquerda (antes do primeiro) +| `s.clear()` | ● | ● | Apaga todos os itens | `+s.__contains__(e)+` | ● | | `e in s` -| `s.copy()` | ● | | Cópia rasa da lista +| `s.copy()` | ● | | Cópia rasa da lista | `+s.__copy__()+` | | ● | Suporte a `copy.copy` (cópia rasa) -| `s.count(e)` | ● | ● | Conta ocorrências de um elemento +| `s.count(e)` | ● | ● | Conta ocorrências de um elemento | `+s.__delitem__(p)+` | ● | ● | Remove item na posição `p` -| `s.extend(i)` | ● | ● | Acrescenta item do iterável `i` pela direita -| `s.extendleft(i)` | | ● | Acrescenta item do iterável `i` pela esquerda -| `+s.__getitem__(p)+` | ● | ● | ++s[p]++—obtém item ou fatia na posição -| `s.index(e)` | ● | | Encontra a primeira ocorrência de `e` -| `s.insert(p, e)` | ● | | Insere elemento `e` antes do item na posição `p` +| `s.extend(i)` | ● | ● | Acrescenta item do iterável `i` pela direita +| `s.extendleft(i)` | | ● | Acrescenta item do iterável `i` pela esquerda +| `+s.__getitem__(p)+` | ● | ● | `++s[p]++`—obtém item ou fatia na posição +| `s.index(e)` | ● | | Encontra a primeira ocorrência de `e` +| `s.insert(p, e)` | ● | | Insere elemento `e` antes do item na posição `p` | `+s.__iter__()+` | ● | ● | Obtém iterador -| `+s.__len__()+` | ● | ● | ++len(s)++—número de itens -| `+s.__mul__(n)+` | ● | | ++s * n++—concatenação repetida -| `+s.__imul__(n)+` | ● | | ++s *= n++—concatenação repetida no mesmo lugar -| `+s.__rmul__(n)+` | ● | | ++n * s++—concatenação repetida invertidafootnote:[Operadores invertidos são explicados no capítulo <>.] -| `s.pop()` | ● | ● | Remove e devolve último itemfootnote:[`a_list.pop(p)` permite remover da posição `p`, mas `deque` não suporta essa opção.] -| `s.popleft()` | | ● | Remove e devolve primeiro item -| `s.remove(e)` | ● | ● | Remove primeira ocorrência do elemento `e` por valor -| `s.reverse()` | ● | ● | Inverte a ordem do itens no mesmo lugar +| `+s.__len__()+` | ● | ● | `++len(s)++`—número de itens +| `+s.__mul__(n)+` | ● | | `++s * n++`—concatenação repetida +| `+s.__imul__(n)+` | ● | | `++s *= n++`—concatenação repetida interna +| `+s.__rmul__(n)+` | ● | | `++n * s`++—concatenação repetida invertidafootnote:[Operadores reversos são explicados no <>.] +| `s.pop()` | ● | ● | Remove e devolve último itemfootnote:[`a_list.pop(p)` permite remover da posição `p`, mas `deque` não suporta essa opção.] +| `s.popleft()` | | ● | Remove e devolve primeiro item +| `s.remove(e)` | ● | | Remove o primeiro elemento de valor igual a `e` +| `s.reverse()` | ● | ● | Inverte a ordem do itens internamente | `+s.__reversed__()+` | ● | ● | Obtém iterador para percorrer itens, do último para o primeiro -| `s.rotate(n)` | | ● | Move `n` itens de um lado para o outro -| `+s.__setitem__(p, e)+` | ● | ● | ++s[p] = e++—coloca `e` na posição `p`, sobrescrevendo item ou fatia existentes -|`s.sort([key], [reverse])` | ● | | Ordena os itens no mesmo lugar, com os argumentos de palavra-chave opcionais `key` e `reverse` +| `s.rotate(n)` | | ● | Move `n` itens de um lado para o outro +| `+s.__setitem__(p, e)+` | ● | ● | `++s[p] = e++`—coloca `e` na posição `p`, sobrescrevendo item ou fatia existentes +| `s.sort([key], [reverse])` | ● | | Ordena os itens internamente, com os argumentos nomeados opcionais `key` e `reverse` |================================================================================================================================================== -Além((("queues", "implementing"))) de `deque`, outros pacotes da biblioteca padrão do Python implementam filas: +Além((("queues", "implementing"))) de `deque`, outros pacotes da biblioteca padrão de Python implementam filas: `queue`:: - Fornece as classes sincronizadas (isto é, seguras para se usar com múltiplas threads) `SimpleQueue`, `Queue`, `LifoQueue`, e `PriorityQueue`. - Essas classes podem ser usadas para comunicação segura entre threads. Todas, exceto `SimpleQueue`, podem ser delimitadas passando um argumento `maxsize` maior que 0 ao construtor. + Fornece as classes sincronizadas (isto é, seguras para se usar com múltiplas threads) + `SimpleQueue`, `Queue`, `LifoQueue`, e `PriorityQueue`. + Essas classes podem ser usadas para comunicação segura entre threads. + Todas, exceto `SimpleQueue`, podem ser delimitadas passando um argumento `maxsize` maior que 0 ao construtor. Entretanto, elas não descartam um item para abrir espaço, como faz `deque`. - Em vez disso, quando a fila está lotada, a inserção de um novo item bloqueia quem tentou inserir—isto é, ela espera até alguma outra thread criar espaço retirando um item da fila, algo útil para limitar o número de threads ativas. + Em vez disso, quando a fila está lotada, a inserção de um novo item bloqueia quem tentou inserir—isto é, + ela espera até alguma outra thread criar espaço retirando um item da fila, algo útil para limitar o número de threads ativas. `multiprocessing`:: - Implementa((("multiprocessing package"))) sua própria `SimpleQueue`, não-delimitada, e `Queue`, delimitada, muito similares àquelas no pacote `queue`, mas projetadas para comunicação entre processos. - Uma fila especializada, `multiprocessing.JoinableQueue`, é disponibilizada para gerenciamento de tarefas. + Implementa((("multiprocessing package"))) sua própria `SimpleQueue`, não-delimitada, + e `Queue`, delimitada, muito similares àquelas no pacote `queue`, mas projetadas para comunicação entre processos. + Uma fila especializada, `multiprocessing.JoinableQueue`, serve para gerenciamento de tarefas. `asyncio`:: Fornece((("asyncio package", "queue implementation by"))) `Queue`, `LifoQueue`, `PriorityQueue`, - e `JoinableQueue` com APIs inspiradas pelas classes nos módulos `queue` e `multiprocessing`, mas adaptadas para gerenciar tarefas em programação assíncrona. + e `JoinableQueue` com APIs inspiradas pelas classes nos módulos `queue` e `multiprocessing`, + mas adaptadas para gerenciar tarefas em programação assíncrona. `heapq`:: - Diferente((("heapq package"))) do últimos três módulos, `heapq` não implementa a classe queue, mas oferece funções como `heappush` e `heappop`, que permitem o uso de uma sequência mutável como - uma fila do tipo heap ou como uma fila de prioridade. + Diferente((("heapq package"))) do últimos três módulos, + `heapq` não implementa a classe queue, mas oferece funções como `heappush` e `heappop`, + que permitem o uso de uma sequência mutável como uma fila do tipo heap ou como uma fila de prioridade. -Aqui termina nossa revisão das alternativas ao tipo `list`, e também nossa exploração dos tipos sequência em geral—exceto pelas especificidades de `str` e das sequências binárias, que tem seu próprio capítulo (<>).((("", startref="Salt02")))((("", startref="Lalt02")))((("", startref="deque02")))((("", startref="coldeq02"))) +Aqui termina nossa revisão das alternativas ao tipo `list`, +e também nossa exploração dos tipos sequência em geral—exceto pelas especificidades de `str` e +das sequências binárias, que tem seu próprio capítulo +(<>).((("", startref="Salt02")))((("", startref="Lalt02")))((("", startref="deque02")))((("", startref="coldeq02"))) === Resumo do capítulo -Dominar((("sequences", "overview of"))) o uso dos tipos sequência da biblioteca padrão é um pré-requisito para escrever código Python conciso, eficiente e idiomático. +Dominar((("sequences", "overview of"))) o uso dos tipos sequência da biblioteca padrão +é um pré-requisito para escrever código Python conciso, eficiente e idiomático. -As sequências do Python são geralmente categorizadas como mutáveis ou imutáveis, mas também é útil considerar um outro eixo: sequências planas e sequências contêiner. As primeiras são mais compactas, mais rápidas e mais fáceis de usar, mas estão limitadas a armazenar dados atômicos como números, caracteres e bytes. -As sequências contêiner são mais flexíveis, mas podem surpreender quando contêm objetos mutáveis. Então, quando armazenando estruturas de dados aninhadas, é preciso ter cuidado para usar tais sequências da forma correta. +As sequências de Python são geralmente categorizadas como mutáveis ou imutáveis, +mas também é útil considerar um outro eixo: sequências planas e sequências contêiner. +As primeiras são mais compactas, mais rápidas e mais fáceis de usar, +mas estão limitadas a armazenar dados atômicos como números, caracteres e bytes. +As sequências contêiner são mais flexíveis, mas podem surpreender quando contêm objetos mutáveis. +Então, quando armazenando estruturas de dados aninhadas, +é preciso ter cuidado para usar tais sequências da forma correta. -Infelizmente o Python não tem um tipo de sequência contêiner imutável infalível: -mesmo as tuplas "imutáveis" podem ter seus valores modificados quando contêm itens mutáveis como listas ou objetos definidos pelo usuário. +Infelizmente Python não tem um tipo de sequência contêiner imutável infalível: +mesmo as tuplas "imutáveis" podem ter seus valores modificados quando contêm itens mutáveis +como listas ou objetos definidos pelo usuário. Compreensões de lista e expressões geradoras são notações poderosas para criar e inicializar sequências. Se você ainda não se sente confortável com essas técnicas, gaste o tempo necessário para aprender seu uso básico. Não é difícil, e você logo vai estar gostando delas. As tuplas no Python tem dois papéis: como registros de campos sem nome e como listas imutáveis. -Ao usar uma tupla como uma lista imutável, lembre-se que só é garantido que o valor de uma tupla será fixo se todos os seus itens também forem imutáveis. -Chamar `hash(t)` com a tupla como argumento é uma forma rápida de se assegurar que seu valor é fixo. Se `t` contiver itens mutáveis, um `TypeError` é gerado. - -Quando uma tupla é usada como registro, o desempacotamento de tuplas é a forma mais segura e legível de extrair seus campos. -Além das tuplas, `*` funciona com listas e iteráveis em vários contextos, e alguns de seus casos de uso apareceram no Python 3.5 com a -https://fpy.li/pep448[PEP 448—Additional Unpacking Generalizations (_Generalizações de Desempacotamento Adicionais_)] (EN). -O Python 3.10 introduziu o _pattern matching_ com `match/case`, +Ao usar uma tupla como uma lista imutável, +lembre-se de que só é garantido que o valor de uma tupla será fixo se todos os seus itens também forem imutáveis. +Chamar `hash(t)` com a tupla como argumento é uma forma rápida de se assegurar que seu valor é fixo. +Se `t` contiver itens mutáveis, um `TypeError` é gerado. + +Quando uma tupla é usada como registro, +o desempacotamento de tuplas é a forma mais segura e legível de extrair seus campos. +Além das tuplas, `*` funciona com listas e iteráveis em vários contextos, +e alguns de seus casos de uso apareceram no Python 3.5 com a +https://fpy.li/pep448[PEP 448—Additional Unpacking Generalizations (_Generalizações de Desempacotamento Adicionais_)] +(EN). +Python 3.10 introduziu o casamento de padrões com `match/case`, suportando um tipo de desempacotamento mais poderoso, conhecido como desestruturação. -Fatiamento de sequências é um dos recursos de sintaxe preferidos do Python, e é ainda mais poderoso do que muita gente pensa. Fatiamento multidimensional e a notação de reticências (`\...`), como usados no NumPy, podem também ser suportados por sequências definidas pelo usuário. +O fatiamento de sequências é um dos recursos de sintaxe preferidos de Python, +e é ainda mais poderoso do que muita gente pensa. +Fatiamento multidimensional e a notação de reticências (`\...`), como usados no NumPy, +podem também ser suportados por sequências definidas pelo usuário. Atribuir a fatias é uma forma muito expressiva de editar sequências mutáveis. -Concatenação repetida, como em `seq * n`, é conveniente e, tomando cuidado, pode ser usada para inicializar listas de listas contendo itens imutáveis. +Concatenação repetida, como em `seq * n`, é conveniente e, tomando cuidado, +pode ser usada para inicializar listas de listas contendo itens imutáveis. Atribuição aumentada com `+=` e `*=` se comporta de forma diferente com sequências mutáveis e imutáveis. No último caso, esses operadores necessariamente criam novas sequências. -Mas se a sequência alvo é mutável, ela em geral é modificada no lugar—mas nem sempre, depende de como a sequência é implementada. +Mas se a sequência alvo é mutável, ela em geral é modificada no lugar—mas nem sempre, +depende de como a sequência é implementada. -O método `sort` e a função embutida `sorted` são fáceis de usar e flexíveis, graças ao argumento opcional `key`: uma função para calcular o critério de ordenação. +O método `sort` e a função embutida `sorted` são fáceis de usar e flexíveis, +graças ao argumento opcional `key`: uma função para calcular o critério de ordenação. E aliás, `key` também pode ser usado com as funções embutidas `min` e `max`. -Além de listas e tuplas, a biblioteca padrão do Python oferece `array.array`. +Além de listas e tuplas, a biblioteca padrão de Python oferece `array.array`. Apesar da NumPy e da SciPy não serem parte da biblioteca padrão, se você faz qualquer tipo de processamento numérico em grandes conjuntos de dados, estudar mesmo uma pequena parte dessas bibliotecas pode levar você muito longe. -Terminamos com uma visita à versátil `collections.deque`, também segura para usar com threads. -Comparamos sua API com a de `list` na <> e mencionamos as outras implementações de filas na biblioteca padrão. +Terminamos com uma visita à versátil `collections.deque`, que é segura para usar com threads. +Comparamos sua API com a de `list` na <> +e mencionamos as outras implementações de filas na biblioteca padrão. [[array_fur_reading_sec]] -=== Leitura complementar - -O((("sequences", "further reading on"))) capítulo 1, "Data Structures" (_Estruturas de Dados_) do pass:[Python Cookbook, 3rd ed.] (EN) (O'Reilly), de David Beazley e Brian K. -Jones, traz muitas receitas usando sequências, incluindo a "Recipe 1.11. -Naming a Slice" (_Receita 1.11. Nomeando uma Fatia_), onde aprendi o truque de atribuir fatias a variáveis para melhorar a legibilidade, como ilustrado no nosso <>. - -A segunda edição do _Python Cookbook_ foi escrita para Python 2.4, mas a maior parte de seu código funciona com Python 3, e muitas das receitas dos capítulos 5 e 6 lidam com sequências. +=== Para saber mais + +O((("sequences", "further reading on"))) capítulo 1, "Data Structures" (_Estruturas de Dados_) +do https://fpy.li/pycook3[Python Cookbook, 3rd ed.] +(EN) (O'Reilly), de David Beazley e Brian K. Jones, traz muitas receitas usando sequências, +incluindo a "Recipe 1.11. +Naming a Slice" (_Receita 1.11. +Nomeando uma Fatia_), +onde aprendi o truque de atribuir fatias a variáveis para melhorar a legibilidade, +como ilustrado no nosso <>. + +A segunda edição do _Python Cookbook_ foi escrita para Python 2.4, +mas a maior parte de seu código funciona com Python 3, +e muitas das receitas dos capítulos 5 e 6 lidam com sequências. O livro foi editado por Alex Martelli, Anna Ravenscroft, e David Ascher, e inclui contribuições de dúzias de pythonistas. -A terceira edição foi reescrita do zero, e se concentra mais na semântica da linguagem—especialmente no que mudou no Python 3—enquanto o volume mais antigo enfatiza a pragmática (isto é, como aplicar a linguagem a problemas da vida real). -Apesar de algumas das soluções da segunda edição não serem mais a melhor abordagem, eu honestamente acho que vale a pena ter à mão as duas edições do _Python Cookbook_. +A terceira edição foi reescrita do zero, e se concentra mais na semântica da linguagem—especialmente +no que mudou no Python 3—enquanto o volume mais antigo enfatiza a pragmática +(isto é, como aplicar a linguagem a problemas da vida real). +Apesar de algumas das soluções da segunda edição não serem mais a melhor abordagem, +honestamente acho que vale a pena ter à mão as duas edições do _Python Cookbook_. -O https://docs.python.org/pt-br/3/howto/sorting.html["HowTo - Ordenação"] oficial do Python tem vários exemplos de técnicas avançadas de uso de `sorted` e `list.sort`. +O https://fpy.li/2v["HowTo - Ordenação"] +oficial de Python tem vários exemplos de técnicas avançadas de uso de `sorted` e `list.sort`. -A https://fpy.li/2-2[PEP 3132--Extended Iterable Unpacking (_Desempacotamento Iterável Estendido_)] (EN) é a fonte canônica para ler sobre o novo uso da sintaxe `*extra` no lado esquerdo de atribuições paralelas. Se você quiser dar uma olhada no processo de evolução do Python, https://fpy.li/2-24["Missing *-unpacking generalizations" (_As generalizações esquecidas de * no desempacotamento_)] (EN) é um tópico do bug tracker propondo melhorias na notação de desempacotamento iterável. -https://fpy.li/pep448[PEP 448--Additional Unpacking Generalizations (_Generalizações de Desempacotamento Adicionais_)] (EN) foi o resultado de discussões ocorridas naquele tópico. +A https://fpy.li/2-2[PEP 3132--Extended Iterable Unpacking (_Desempacotamento Estendido de Iterável_)] +(EN) é a fonte canônica para ler sobre o novo uso da sintaxe `*extra` no lado esquerdo de atribuições paralelas. +Se você quiser dar uma olhada no processo de evolução de Python, +https://fpy.li/2-24["Missing *-unpacking generalizations" (_As generalizações esquecidas de * no desempacotamento_)] +(EN) é um tópico do bug tracker propondo melhorias na notação de desempacotamento iterável. +https://fpy.li/pep448[PEP 448--Additional Unpacking Generalizations (_Generalizações de Desempacotamento Adicionais_)] +(EN) foi o resultado de discussões ocorridas naquele tópico. [role="pagebreak-before less_space"] -Como mencionei na seção <>, o texto introdutório -https://docs.python.org/pt-br/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching["Correspondência de padrão estrutural"], de Carol Willing, no -https://docs.python.org/pt-br/3.10/whatsnew/3.10.html["O que há de novo no Python 3.10"], é uma ótima introdução a esse novo grande recurso, em mais ou menos 1.400 palavras (isso é menos de 5 páginas quando o Firefox converte o HTML em PDF). -A https://fpy.li/pep636[PEP 636—Structural Pattern Matching: Tutorial (_Casamento de Padrões Estrutural: Tutorial_)] (EN) também é boa, mas mais longa. A mesma PEP 636 inclui o +Como mencionei na <>, o texto introdutório +https://fpy.li/2r["Casamento de padrão estrutural"], +de Carol Willing, no +https://fpy.li/2s["O que há de novo no Python 3.10"], +é uma ótima introdução a esse novo grande recurso, em mais ou menos 1.400 palavras +(isso é menos de 5 páginas quando o Firefox converte o HTML em PDF). +https://fpy.li/pep636[PEP 636—Structural Pattern Matching: Tutorial (_Casamento de Padrões Estrutural: Tutorial_)] +(EN) também é bom, porém mais longo. +A mesma PEP 636 inclui o https://fpy.li/2-27["Appendix A—Quick Intro" (_Apêndice A-Introdução Rápida_)] (EN). -Ele é menor que a introdução de Willing, porque omite as considerações gerais sobre os motivos pelos quais o _pattern matching_ é bom para você. -SE você precisar de mais argumentos para se convencer ou convencer outros que o _pattern matching_ é bom para o Python, leia as 22 páginas de -https://fpy.li/pep635[PEP 635—Structural Pattern Matching: Motivation and Rationale (_Casamento de Padrões Estrutural: Motivação e Justificativa)] (EN). +Ele é menor que a introdução de Willing, porque omite as considerações gerais sobre os motivos pelos quais o +casamento de padrões é útil. +Se você precisar de mais argumentos para se convencer ou convencer outros que o casamento de padrões +foi bom para o Python, leia as 22 páginas de +https://fpy.li/pep635[PEP 635—Structural Pattern Matching: Motivation and Rationale (_Casamento de Padrões Estrutural: Motivação e Justificativa_)] +(EN). -O post de Eli Bendersky em seu blog, https://fpy.li/2-28["Less copies in Python with the buffer protocol and memoryviews" (_Menos cópias em Python, com o protocolo de buffer e mamoryviews_)] inclui um pequeno tutorial sobre `memoryview`. +O post de Eli Bendersky em seu blog, +https://fpy.li/2-28["Less copies in Python with the buffer protocol and memoryviews" (_Menos cópias em Python, com o protocolo de buffer e mamoryviews_)] +inclui um pequeno tutorial sobre `memoryview`. Há muitos livros tratando da NumPy no mercado, e muitos não mencionam "NumPy" no título. -Dois exemplos são o https://fpy.li/2-29[_Python Data Science Handbook_], escrito por Jake VanderPlas e de acesso aberto, -e a segunda edição do pass:[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. 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, após a refatoração de uma bela classe pythônica, usando um método gerador, em uma pequena e feroz função que chama um par de funções de vetor da NumPy. +Dois exemplos são o https://fpy.li/2-29[_Python Data Science Handbook_], +escrito por Jake VanderPlas e de acesso aberto, +e a segunda edição do +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 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, +após a refatoração de uma bela classe pythônica, usando um método gerador, +em uma pequena e feroz função que chama um par de funções de vetor da NumPy. Para aprender a usar `deque` (e outras coleções), veja os exemplos e as receitas práticas em -https://docs.python.org/pt-br/3/library/collections.html["Tipos de dados de contêineres"], na documentação do Python. - -A melhor defesa da convenção do Python de excluir o último item em faixas e fatias foi escrita pelo próprio Edsger W. -Dijkstra, em uma nota curta intitulada https://fpy.li/2-32["Why Numbering Should Start at Zero" (_Porque a Numeração Deve Começar em Zero_)]. -O assunto da nota é notação matemática, mas ela é relevante para o Python porque -Dijkstra explica, com humor e rigor, porque uma sequência como 2, 3, ..., 12 deveria sempre ser expressa como 2 ≤ i < 13. -Todas as outras convenções razoáveis são refutadas, bem como a ideia de deixar cada usuário escolher uma convenção. -O título se refere à indexação baseada em zero, mas a nota na verdade é sobre porque é desejável que `'ABCDE'[1:3]` signifique `'BC'` e não `'BCD'`, e porque faz todo sentido escrever `range(2, 13)` para produzir 2, 3, 4, ..., 12. +https://fpy.li/2w["Tipos de dados de contêineres"], +na documentação de Python. + +A melhor defesa da convenção de Python de excluir o último item `range` +e fatias foi escrita pelo grande Edsger W. Dijkstra, em uma nota curta intitulada +https://fpy.li/2-32["Why Numbering Should Start at Zero" (_Porque a Numeração Deve Começar em Zero_)]. +O assunto da nota é notação matemática, mas ela é relevante para Python porque +Dijkstra explica, com humor e rigor, porque uma sequência como 2, 3, ..., 12 +deveria sempre ser expressa como 2 ≤ i < 13. +Todas as outras convenções razoáveis são refutadas, +bem como a ideia de deixar cada usuário escolher uma convenção. +O título se refere à indexação baseada em zero, mas a nota na verdade é sobre porque é desejável que `'ABCDE'[1:3]` +signifique `'BC'` e não `'BCD'`, e porque faz todo sentido escrever +`range(2, 13)` para produzir 2, 3, 4, ..., 12. E, por sinal, a nota foi escrita à mão, mas é linda e totalmente legível. A letra de Dijkstra é tão cristalina que alguém criou uma https://fpy.li/2-33[fonte] a partir de suas anotações. @@ -2168,46 +2578,58 @@ A letra de Dijkstra é tão cristalina que alguém criou uma https://fpy.li/2-33 **** [role="soapbox-title"] -A natureza das tuplas +*A natureza das tuplas* -Em((("Soapbox sidebars", "tuples")))((("tuples", "nature of")))((("sequences", "Soapbox discussion", id="Ssoap02"))) 2012, apresentei um poster sobre a linguagem ABC na PyCon US. -Antes de criar o Python, Guido van Rossum tinha trabalhado no interpretador ABC, então ele veio ver meu pôster. +Em((("Soapbox sidebars", "tuples")))((("tuples", "nature of")))((("sequences", "Soapbox discussion", id="Ssoap02"))) +2012, apresentei um poster sobre a linguagem ABC na PyCon US. +Antes de criar Python, Guido van Rossum tinha trabalhado no interpretador ABC, então ele veio ver meu pôster. Entre outras coisas, falamos sobre como os _compounds_ (compostos) da ABC, -que são claramente os predecessores das tuplas do Python. -Compostos também suportam atribuição paralela e são usados como chaves compostas em dicionários (ou _tabelas_, no jargão da ABC). -Entretanto, compostos não são sequências, -Eles não são iteráveis, e não é possível obter um campo por índice, muitos menos fatiá-los. +predecessores das tuplas de Python. +Compounds também suportam atribuição paralela e são usados como chaves compostas em dicionários +(ou _tabelas_, no jargão da ABC). +Entretanto, compounds não são sequências: +não são iteráveis, e não é possível obter um campo por índice, muitos menos fatiá-los. Ou você manuseia o composto inteiro ou extrai os campos individuais usando atribuição paralela, e é isso. -Disse a Guido que essas limitações tornavam muito claro o principal propósito dos compostos: ele são apenas registros sem campos nomeados. +Disse a Guido que essas limitações tornavam muito claro o principal propósito dos compounds: +ele são apenas registros sem campos nomeados. Sua resposta: "Fazer as tuplas se comportarem como sequências foi uma gambiarra." -Isso ilustra a abordagem pragmática que tornou o Python mais prático e mais bem sucedido que a ABC. Da perspectiva de um implementador de linguagens, fazer as tuplas se comportarem como sequências custa pouco. Como resultado, o principal caso de uso de tuplas como registros não é tão óbvio, -mas ganhamos listas imutáveis—mesmo que seu tipo não seja tão claramente nomeado como o de `frozenlist`. +Isso ilustra a abordagem pragmática que tornou Python mais prático e mais bem sucedido que a ABC. +Da perspectiva de um implementador de linguagens, fazer as tuplas se comportarem como sequências custa pouco. +Como resultado, o principal caso de uso de tuplas como registros não é tão óbvio, +mas ganhamos listas imutáveis—mesmo que seu tipo não seja tão claramente nomeado como seria `frozenlist`. [role="soapbox-title"] -Sequências planas versus sequências contêineres +*Sequências planas versus sequências contêineres* -Para((("Soapbox sidebars", "flat versus container sequences")))((("container sequences")))((("flat sequences"))) realçar os diferentes modelos de memória dos tipos de sequências usei os termos _sequência contêiner_ e _sequência plana_. -A palavra "contêiner" vem da própria https://docs.python.org/pt-br/3/reference/datamodel.html#objects-values-and-types[documentação do "Modelo de Dados"]: +Para((("Soapbox sidebars", "flat versus container sequences")))((("container sequences")))((("flat sequences"))) +realçar os diferentes modelos de memória dos tipos de sequências usei os termos +_sequência contêiner_ e _sequência plana_. +A palavra "contêiner" vem da própria +https://fpy.li/2x[documentação do "Modelo de Dados"]: [quote] ____ Alguns objetos contêm referências a outros objetos; eles são chamados de contêineres. ____ -Usei o termo "sequência contêiner" para ser específico, porque existem contêineres em Python que não são sequências, como `dict` e `set`. +Usei o termo "sequência contêiner" para ser específico, +porque existem contêineres em Python que não são sequências, como `dict` e `set`. Sequências contêineres podem ser aninhadas porque elas podem conter objetos de qualquer tipo, incluindo seu próprio tipo. -Por outro lado, _sequências planas_ são tipos de sequências que não podem ser aninhadas, pois só podem conter valores atômicos como inteiros, números de ponto flutuante ou caracteres. +Por outro lado, _sequências planas_ são tipos de sequências que não podem ser aninhadas, +pois só podem conter valores atômicos como inteiros, números de ponto flutuante ou caracteres. Adotei o termo _sequência plana_ porque precisava de algo para contrastar com "sequência contêiner." -Apesar dos uso anterior da palavra "containers" na documentação oficial, há uma classe abstrata em `collections.abc` chamada `Container`. +Apesar do uso anterior da palavra "containers" na documentação oficial, +há uma classe abstrata em `collections.abc` chamada `Container`. Aquela ABC tem apenas um método, `+__contains__+`—o método especial por trás do operador `in`. -Isso significa que arrays e strings, que não são contêineres no sentido tradicional, são subclasses virtuais de `Container`, porque implementam `+__contains__+`. +Isso significa que arrays e strings, que não são contêineres no sentido tradicional, +são subclasses virtuais de `Container`, porque implementam `+__contains__+`. Isso é só mais um exemplo de humanos usando uma mesma palavra para significar coisas diferentes. Nesse livro, vou escrever "contêiner" com minúscula e em português para "um objeto que contém referências para outros objetos" e @@ -2215,11 +2637,16 @@ Nesse livro, vou escrever "contêiner" com minúscula e em português para [role="soapbox-title"] -Listas bagunçadas +*Listas bagunçadas* -Textos((("Soapbox sidebars", "mixed-bag lists")))((("lists", "mixed-bag"))) introdutórios de Python costumam enfatizar que listas podem conter objetos de diferentes tipos, mas na prática esse recurso não é muito útil: colocamos itens em uma lista para processá-los mais tarde, o que implica o suporte, da parte de todos os itens, a pelo menos alguma operação em comum (isto é, eles devem todos "grasnar", independente de serem ou não 100% patos, geneticamente falando), Por exemplo, não é possível ordenar uma lista em Python 3 a menos que os itens ali contidos sejam comparáveis: +Textos((("Soapbox sidebars", "mixed-bag lists")))((("lists", "mixed-bag"))) +introdutórios de Python costumam enfatizar que listas podem conter objetos de diferentes tipos, +mas na prática esse recurso não é muito útil: colocamos itens em uma lista para processá-los mais tarde, +o que implica o suporte, da parte de todos os itens, a pelo menos alguma operação em comum +(isto é, eles devem todos "grasnar", independente de serem ou não 100% patos, geneticamente falando). +Por exemplo, não é possível ordenar uma lista em Python 3 a menos que os itens ali contidos sejam comparáveis: -[source, pycon] +[source, python] ---- >>> l = [28, 14, '28', 5, '9', '1', 0, 6, '23', 19] >>> sorted(l) @@ -2232,21 +2659,26 @@ Diferente das listas, as tuplas muitas vezes mantêm itens de tipos diferentes. Isso é natural: se cada item em uma tupla é um campo, então cada campo pode ter um tipo diferente. [role="soapbox-title"] -'key' é brilhante +*'key' é brilhante* O((("Soapbox sidebars", "key argument")))((("key argument")))((("arguments", "key argument"))) argumento opcional `key` de `list.sort`, `sorted`, `max`, e `min` é uma grande ideia. -Outras linguagens forçam você a fornecer uma função de comparação com dois argumentos, como a função descontinuada do Python 2 `cmp(a, b)`. +Outras linguagens forçam você a fornecer uma função de comparação com dois argumentos, +como a função descontinuada de Python 2 `cmp(a, b)`. Usar `key` é mais simples e mais eficiente. -É mais simples porque basta definir uma função de um único argumento que recupera ou calcula o critério a ser usado para ordenar seus objetos; isso é mais fácil que escrever uma função de dois argumentos para devolver –1, 0, 1. +É mais simples porque basta definir uma função de um único argumento que recupera ou calcula +o critério a ser usado para ordenar seus objetos; +isso é mais fácil que escrever uma função de dois argumentos para devolver –1, 0, 1. Também é mais eficiente, porque a função `key` é invocada apenas uma vez por item, -enquanto a comparação de dois argumentos é chamada a cada vez que o algoritmo de ordenação precisa comparar dois itens. -Claro, o Python também precisa comparar as chaves ao ordenar, +enquanto a comparação de dois argumentos é chamada a cada vez +que o algoritmo de ordenação precisa comparar dois itens. +Claro, Python também precisa comparar as chaves ao ordenar, mas aquela comparação é feita em código C otimizado, não em uma função Python escrita por você. -Por sinal, usando `key` podemos ordenar uma lista bagunçada de números e strings "parecidas com números". Só precisamos decidir se queremos tratar todos os itens como inteiros ou como strings: +Por sinal, usando `key` podemos ordenar uma lista bagunçada de números e strings "parecidas com números". +Só precisamos decidir se queremos tratar todos os itens como inteiros ou como strings: -[source, pycon] +[source, python] ---- >>> l = [28, 14, '28', 5, '9', '1', 0, 6, '23', 19] >>> sorted(l, key=int) @@ -2257,19 +2689,28 @@ Por sinal, usando `key` podemos ordenar uma lista bagunçada de números e strin [role="soapbox-title"] -A Oracle, o Google, e a Conspiração Timbot +*A Oracle, o Google, e a Conspiração Timbot* -O((("Soapbox sidebars", "Oracle, Google, and the Timbot")))((("Timsort algorithm"))) algoritmo de ordenação usado em `sorted` e `list.sort` é o Timsort, -um algoritmo adaptativo que troca de estratégia de ordenação ( entre _merge sort_ e _insertion sort_), dependendo de quão ordenados os dados já estão. +O((("Soapbox sidebars", "Oracle, Google, and the Timbot")))((("Timsort algorithm"))) +algoritmo de ordenação usado em `sorted` e `list.sort` é o Timsort, +um algoritmo adaptativo que troca de estratégia de ordenação (entre _merge sort_ e _insertion sort_), +dependendo de quão ordenados os dados já estão. Isso é eficiente porque dados reais tendem a ter séries de itens ordenados. -Há um https://pt.wikipedia.org/wiki/Timsort[artigo da Wikipedia] sobre ele. +Há um https://fpy.li/2y[artigo da Wikipedia] sobre ele. O Timsort foi usado no CPython pela primeira vez em 2002. -Desde 2009, o Timsort também é usado para ordenar arrays tanto no Java padrão quanto no Android, um fato que ficou muito conhecido quando a Oracle usou parte do código relacionado ao Timsort como evidência da violação da propriedade intelectual da Sun pelo Google. +Desde 2009, o Timsort também é usado para ordenar arrays tanto em Java padrão quanto no Android, +um fato que ficou muito conhecido quando a Oracle usou parte do código relacionado ao Timsort +como evidência da violação da propriedade intelectual da Sun pelo Google. Por exemplo, veja essa https://fpy.li/2-36[ordem do Juiz William Alsup] (EN) de 2012. -Em 2021, a Suprema Corte dos Estados Unidos decidiu que o uso do código do Java pelo Google é "fair use"footnote:[NT: Conceito da lei de copyright norte-americana que permite, em determinadas circunstâncias, o uso sem custo de partes da propriedade intelectual de outros. Em geral traduzido como "uso razoável" ou "uso aceitável". Essa doutrina não faz parte da lei brasileira.] - -O Timsort foi inventado por Tim Peters, um dos desenvolvedores principais do Python, e tão produtivo que se acredita que ele seja uma inteligência artificial, o Timbot. +Em 2021, a Suprema Corte dos Estados Unidos decidiu que o uso do código de Java pelo Google é +"fair use"footnote:[NT: Conceito da lei de copyright norte-americana que permite, +em determinadas circunstâncias, o uso sem autorização prévia de partes da propriedade intelectual de outros. +Em geral traduzido como "uso razoável" ou "uso aceitável". +Essa doutrina não faz parte da lei brasileira.] + +O Timsort foi inventado por Tim Peters, um dos mantenedores de Python, +e tão produtivo que se acredita que ele seja uma inteligência artificial, o Timbot. Você pode ler mais sobre essa teoria da conspiração em https://fpy.li/2-37["Python Humor"] (EN). Tim também escreveu "The Zen of Python": `import this`.((("", startref="Ssoap02"))) diff --git a/online/cap03.adoc b/online/cap03.adoc new file mode 100644 index 00000000..f0bc8f9b --- /dev/null +++ b/online/cap03.adoc @@ -0,0 +1,1739 @@ +[[ch_dicts_sets]] +== Dicionários e conjuntos +:example-number: 0 +:figure-number: 0 + +[quote, Lalo Martins, pioneiro do nomadismo digital e pythonista] +____ +Python é feito basicamente de dicionários cobertos por muitas camadas de açúcar sintático +____ + +Usamos dicionários em todos os nossos programas Python. +Se não diretamente em nosso código, então indiretamente, +pois o tipo `dict` é um elemento fundamental da implementação de Python. +Atributos de classes e de instâncias, +espaços de nomes de módulos e argumentos nomeados de funções são alguns dos +elementos fundamentais de Python representados na memória por dicionários. +O `+__builtins__.__dict__+` armazena todos os tipos, funções e objetos embutidos. + +Por seu papel crucial, os dicts de Python são extremamente otimizados—e continuam recebendo melhorias. +As _tabelas de hash_((("hash tables"))) são o motor por trás do alto desempenho dos dicts de Python. + +Outros tipos embutidos baseados em tabelas de hash são `set` e `frozenset`. +Eles oferecem uma API mais completa e operadores mais robustos que +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 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 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 +* Os tipos `set` e `frozenset` +* As implicações das tabelas de hash no comportamento de conjuntos e dicionários. + + +=== Novidades neste capítulo + +A((("dictionaries and sets", "significant changes to"))) maior parte das mudanças nessa +segunda edição se concentra em novos recursos relacionados a tipos de mapeamento: + +* A <> fala da sintaxe aperfeiçoada de desempacotamento +e de diferentes maneiras de mesclar mapeamentos—incluindo os operadores `|` e `|=`, suportados pelos `dicts` desde o Python 3.9. +* A <> ilustra o manuseio de mapeamentos com `match/case`, recurso que surgiu no Python 3.10. +* A <> agora se concentra nas pequenas mas ainda relevantes diferenças entre +`dict` e `OrderedDict`—levando em conta que, desde o Python 3.6, `dict` passou a manter a ordem de inserção das chaves. +* Novas seções sobre os objetos view devolvidos por `dict.keys`, `dict.items`, e `dict.values`: +a <> e a <>. + +A implementação interna de `dict` e `set` ainda está alicerçada em tabelas de hash, +mas o código de `dict` teve duas otimizações importantes, +que economizam memória e preservam o ordem de inserção das chaves. +A <> e a <> +resumem o que você precisa saber sobre isso para usar bem essas estruturas. + +[NOTE] +==== +Após((("dictionaries and sets", "internals of"))) acrescentar mais de 200 páginas a essa segunda edição, transferi a seção opcional +https://fpy.li/hashint["Internals of sets and dicts" (_As entranhas dos sets e dos dicts_)] (EN) +para o http://fluentpython.com, o site que complementa o livro. +O https://fpy.li/hashint[post de 18 páginas] (EN) foi atualizado e expandido, e inclui explicações e diagramas sobre: + +* O algoritmo de tabela de hash e as estruturas de dados, começando por seu uso em `set`, que é mais simples de entender. +* A otimização de memória que preserva a ordem de inserção de chaves em instâncias de `dict` (desde o Python 3.6) . +* O layout do compartilhamento de chaves em dicionários que mantêm atributos de +instância—o `+__dict__+` de objetos definidos pelo usuário (otimização implementada no Python 3.3). +==== + + +[[modern_dict_syntax_sec]] +=== A sintaxe moderna dos dicts + +As((("dictionaries and sets", "modern dict syntax", id="DASsyntax03"))) próximas seções descrevem +os recursos avançados de sintaxe para criação, desempacotamento e processamento de mapeamentos. +Alguns desses recursos não são novos na linguagem, mas podem ser novidade para você. +Outros requerem Python 3.9 (como o operador `|`) ou Python 3.10 (como `match/case`). +Vamos começar por um dos melhores e mais antigos desses recursos. + +[[dictcomp_sec]] +==== Compreensões de dict + +Desde((("dictcomps (dict comprehensions)"))) Python 2.7, a sintaxe das listcomps e genexps foi adaptada para compreensões de `dict` +(e também compreensões de `set`, que veremos em breve). +Uma _dictcomp_ (compreensão de dict) cria uma instância de `dict`, recebendo pares `key:value` de qualquer iterável. +O <> mostra o uso de compreensões de `dict` para criar dois dicionários a partir de uma mesma lista de tuplas. + +[[example3-1]] +.Exemplos de compreensões de `dict` +==== +[source, python] +---- +>>> dial_codes = [ # <1> +... (880, 'Bangladesh'), +... (55, 'Brazil'), +... (86, 'China'), +... (91, 'India'), +... (62, 'Indonesia'), +... (81, 'Japan'), +... (234, 'Nigeria'), +... (92, 'Pakistan'), +... (7, 'Russia'), +... (1, 'United States'), +... ] +>>> country_dial = {country: code for code, country in dial_codes} # <2> +>>> country_dial +{'Bangladesh': 880, 'Brazil': 55, 'China': 86, 'India': 91, 'Indonesia': 62, +'Japan': 81, 'Nigeria': 234, 'Pakistan': 92, 'Russia': 7, 'United States': 1} +>>> {code: country.upper() # <3> +... for country, code in sorted(country_dial.items()) +... if code < 70} +{55: 'BRAZIL', 62: 'INDONESIA', 7: 'RUSSIA', 1: 'UNITED STATES'} +---- +==== +<1> Um iterável de pares chave-valor como `dial_codes` pode ser passado diretamente para o construtor de `dict`, mas... +<2> ...aqui permutamos os pares: `country` é a chave, e `code` é o valor. +<3> Ordenando `country_dial` por nome, revertendo novamente os pares, colocando os valores em maiúsculas e filtrando os itens com `code < 70`. + +Se você já usa listcomps, as dictcomps são um próximo passo natural. +Caso contrário, a propagação da sintaxe de compreensão mostra que agora é mais valioso que nunca se tornar fluente nessa técnica. + + +[[dict_unpacking_sec]] +[role="pagebreak-before less_space"] +==== Desempacotando mapeamentos + +A https://fpy.li/pep448[PEP 448—Additional Unpacking Generalizations (_Generalizações de Desempacotamento Adicionais_)] +melhorou o suporte ao desempacotamento de mapeamentos((("unpacking", "mapping unpackings")))((("mappings", "unpacking"))) +de duas formas, desde o Python 3.5. + +Primeiro, podemos((("** (double star) operator")))((("double star (**) operator"))) +aplicar `**` a mais de um argumento em uma chamada de função. +Isso funciona quando todas as chaves são strings e únicas, +para todos os argumentos (porque argumentos nomeados duplicados são proibidos): + +[source, python] +---- +>>> def dump(**kwargs): +... return kwargs +... +>>> dump(**{'x': 1}, y=2, **{'z': 3}) +{'x': 1, 'y': 2, 'z': 3} +---- + +Em segundo lugar, `**` pode ser usado dentro de um literal `dict`—também múltiplas vezes: + +[source, python] +---- +>>> {'a': 0, **{'x': 1}, 'y': 2, **{'z': 3, 'x': 4}} +{'a': 0, 'x': 4, 'y': 2, 'z': 3} +---- + +Nesse caso, chaves duplicadas são permitidas. +Cada ocorrência sobrescreve ocorrências anteriores—observe o valor mapeado para `x` no exemplo. + +Essa sintaxe também pode ser usada para mesclar mapas, mas isso pode ser feito de outras formas. +Siga comigo. + +==== Fundindo mapeamentos com | + +Desde a versão 3.9, Python((("mappings", +"merging")))((("ǀ (pipe) operator")))((("pipe (ǀ) operator")))((("ǀ= (pipe equals) operator")))((("pipe equals (ǀ=) operator"))) +suporta o uso de `|` e `|=` para mesclar mapeamentos. +Isso faz todo sentido, +já que estes são também os operadores de união de conjuntos. + +O operador `|` cria um novo mapeamento: + +[source, python] +---- +>>> d1 = {'a': 1, 'b': 3} +>>> d2 = {'a': 2, 'b': 4, 'c': 6} +>>> d1 | d2 +{'a': 2, 'b': 4, 'c': 6} +---- + +O tipo do novo mapeamento normalmente será o mesmo do operando da esquerda—no exemplo, +`d1`—mas ele pode ser do tipo do segundo operando se tipos definidos pelo usuário estiverem envolvidos na operação, +dependendo das regras de sobrecarga de operadores, que exploraremos no <>. + +Para atualizar mapeamentos existentes internamente, use `|=`. +Retomando o exemplo anterior, ali `d1` não foi modificado. +Mas aqui sim: + +[source, python] +---- +>>> d1 +{'a': 1, 'b': 3} +>>> d1 |= d2 +>>> d1 +{'a': 2, 'b': 4, 'c': 6} +---- + +[TIP] +==== +Se você precisa manter código rodando no Python 3.8 ou anterior, a seção +https://fpy.li/3-1["Motivation" (_Motivação_)] (EN) da +https://fpy.li/pep584[PEP 584—Add Union Operators To dict (_Acrescentar Operadores de União a dict_)] +(EN) inclui um bom resumo das outras formas de mesclar mapeamentos. +==== + +Agora vamos ver como o casamento de padrões se aplica aos mapeamentos.((("", startref="DASsyntax03"))) + +[[pattern_matching_mappings_sec]] +=== Pattern matching com mapeamentos + +A((("pattern matching", "with mappings", secondary-sortas="mappings", +id="PMmap03")))((("mappings", "pattern matching with", +id="Mpattern03")))((("match/case statement")))((("dictionaries and sets", "pattern matching with mappings", +id="DASmap03"))) +instrução `match/case` suporta sujeitos que sejam objetos mapeamento. +Padrões para mapeamentos se parecem com literais `dict`, +mas podem casar com instâncias de qualquer subclasse real ou virtual de `collections.abc.Mapping`.footnote:[Uma +subclasse virtual é +qualquer classe registrada com uma chamada ao método `.register()` de uma ABC, +como explicado na <> +Um tipo implementado através da API Python/C também serve, se tiver bit de marcação específico setado no header. +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 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, +como o MongoDB, o EdgeDB, ou o PostgreSQL. +O <> demonstra isso. + +As dicas de tipo simples em `get_creators` tornam claro que ela recebe um `dict` e devolve uma `list`. + +[[dict_match_ex]] +.creator.py: `get_creators()` extrai o nome dos criadores em registros de mídia +==== +[source, python] +---- +include::../code/03-dict-set/py3.10/creator.py[tags=DICT_MATCH] +---- +==== +[role="pagebreak-before less_space"] +<1> Casa com qualquer mapeamento na forma `'type': 'book', 'api' :2`, e uma chave `'authors'` +mapeada para uma sequência. +Devolve os itens da sequência, como uma nova `list`. +<2> Casa com qualquer mapeamento na forma `'type': 'book', 'api' :1`, e uma chave `'author'` +mapeada para qualquer objeto. +Devolve aquele objeto dentro de uma `list`. +<3> Qualquer outro mapeamento na forma `'type': 'book'` é inválido e gera um `ValueError`. +<4> Casa qualquer mapeamento na forma `'type': 'movie'` e uma chave `'director'` +mapeada para um único objeto. +Devolve o objeto dentro de uma `list`. +<5> Qualquer outro sujeito é inválido e gera um `ValueError`. + +O <> mostra algumas práticas úteis para lidar com dados semi-estruturados, como registros JSON: + +* Incluir um campo descrevendo o tipo de registro (por exemplo, `'type': 'movie'`) +* Incluir um campo identificando a versão do schema (por exemplo, `'api': 2'`), +para permitir evoluções futuras das APIs públicas. +* Ter cláusulas `case` para processar registros inválidos de um tipo específico (por exemplo, `'book'`), +bem como um `case` final para capturar tudo que tenha passado pelas condições anteriores. + +Agora vamos ver como `get_creators` se comporta com alguns doctests concretos: + +[source, python] +---- +include::../code/03-dict-set/py3.10/creator.py[tags=DICT_MATCH_TEST] +---- + +Observe que a ordem das chaves nos padrões é irrelevante, mesmo se o sujeito for um `OrderedDict` como `b2`. + +Diferente de patterns de sequência, patterns de mapeamento funcionam com matches parciais. +Nos doctests, os sujeitos `b1` e `b2` incluem uma chave `'title'`, +que não aparece em nenhum padrão `'book'`, mas mesmo assim casam. + +Não há necessidade de usar `+**extra+` para casar pares chave-valor adicionais, +mas se você quiser capturá-los como um `dict`, pode prefixar uma variável com `**`. +Ela precisa ser a última do padrão, e `+**_+` é proibido, pois seria redundante. +Um exemplo simples: + + +[source, python] +---- +>>> food = dict(category='ice cream', flavor='vanilla', cost=199) +>>> match food: +... case {'category': 'ice cream', **details}: +... print(f'Ice cream details: {details}') +... +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 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 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. +==== + +Vistas a sintaxe e a estrutura, vamos estudar a API dos mapeamentos.((("", +startref="PMmap03")))((("", startref="Mpattern03")))((("", startref="DASmap03"))) + + +=== A API padrão dos tipos de mapeamentos + +O((("dictionaries and sets", "standard API of mapping types", id="DASapi03")))((("mappings", +"standard API of mapping types", id="Mapi03")))((("collections.abc module", +"Mapping and MutableMapping ABCs")))((("MutableMapping ABC"))) +módulo `collections.abc` contém as ABCs `Mapping` e `MutableMapping`, +descrevendo as interfaces de `dict` e de tipos similares. +Veja a <>.((("UML class diagrams", "simplified for MutableMapping and superclasses"))) + +A maior utilidade dessas ABCs é documentar e formalizar as interfaces padrão para os mapeamentos, +e servir e critério para testes com `isinstance` em código que precise suportar mapeamentos de forma geral: + +[source, python] +---- +>>> my_dict = {} +>>> isinstance(my_dict, abc.Mapping) +True +>>> isinstance(my_dict, abc.MutableMapping) +True +---- + +[TIP] +==== +Usar `isinstance` com uma ABC é muitas vezes melhor que checar se um argumento de função +é do tipo concreto `dict`, porque daí tipos alternativos de mapeamentos podem ser usados. +Vamos discutir isso em detalhes no <>. +==== + +[[mapping_uml]] +.Diagrama de classe simplificado para `MutableMapping` e suas superclasses de `collections.abc` (as setas de herança apontam das subclasses para as superclasses; nomes em itálico indicam classes e métodos abstratos +image::../images/flpy_0301.png[Diagrama de classes UML para `Mapping` e `MutableMapping`] + +Para implementar uma mapeamento customizado, é mais fácil estender `collections.UserDict`, +ou envolver um `dict` por composição, ao invés de criar uma subclasse dessas ABCs. +A classe `collections.UserDict` e todas as classes concretas de mapeamentos da biblioteca padrão +encapsulam o `dict` básico em suas implementações, que por sua vez é criado sobre uma tabela de hash. +Assim, todas elas compartilham a mesma limitação: +as((("keys", "hashability"))) chaves precisam ser _hashable_ +(os valores não precisam ser hashable, só as chaves). +Se você precisa de uma recapitulação, a próxima seção explica isso. + +[[what_is_hashable_sec]] +==== O que é hashable? + +Aqui((("hashable, definition of"))) está parte da definição de hashable, adaptado do +https://fpy.li/32[_Glossário_ de Python]: + +[quote] +____ +Um objeto é hashable se tem um código de hash que nunca muda durante seu ciclo de vida +(precisa ter um método `+__hash__+`) e pode ser comparado com outros objetos +(precisa ter um método `+__eq__+`). +Objetos hashable que são comparados como iguais devem ter o mesmo código de hash.footnote:[O +verbete para "hashable" no +https://fpy.li/32[_Glossário_ de Python] +usa o termo "valor de hash" em vez de((("hash code, versus hash value"))) _código de hash_. +Prefiro _código de hash_ porque "valor" é um conceito frequentemente usado no contexto de mapeamentos, +onde itens são compostos de chavas e valores. +Então pode ser confuso se referir ao código de hash como um valor. +Nesse livro usarei apenas _código de hash_.] +____ + +Tipos numéricos((("numeric types", "hashability of"))) e os tipos planos imutáveis `str` e `bytes` são todos hashable. +Tipos contêineres são hashable se forem imutáveis e se todos os objetos por eles contidos forem também hashable. +Um `frozenset` é sempre hashable, pois todos os elementos que ele contém devem ser, por definição, hashable. +Uma `tuple` é hashable apenas se todos os seus itens também forem. +Observe as tuplas `tt`, `tl`, e `tf`: + +[source, python] +---- +>>> tt = (1, 2, (30, 40)) +>>> hash(tt) +8027212646858338501 +>>> tl = (1, 2, [30, 40]) +>>> hash(tl) +Traceback (most recent call last): + File "", line 1, in +TypeError: unhashable type: 'list' +>>> tf = (1, 2, frozenset([30, 40])) +>>> hash(tf) +-4118419923444501110 +---- + +O código de hash de um objeto pode ser diferente dependendo da versão de Python, da arquitetura da máquina, +e pelo((("salt"))) _sal_ acrescentado ao cálculo do hash por razões de segurança.footnote:[Veja a +https://fpy.li/pep456[PEP 456—Secure and interchangeable hash algorithm (Algoritmo de hash seguro e intercambiável_)] (EN) +para saber mais sobre as implicações de segurança e as soluções adotadas.] +O código de hash de um objeto corretamente implementado tem a garantia de ser constante apenas dentro de um processo Python. + +Tipos definidos pelo usuário são hashble por default, pois seu código de hash é seu `id()`, e o método `+__eq__()+` +herdado da classe `objetct` apenas compara os IDs dos objetos. +Se um objeto implementar seu próprio `+__eq__()+`, que leve em consideração seu estado interno, +ele será hashable apenas se seu `+__hash__()+` sempre devolver o mesmo código de hash. +Na prática, isso exige que `+__eq__()+` e `+__hash__()+` levem em conta apenas +atributos de instância que nunca mudem durante a vida do objeto. + + +Vamos agora revisar a API dos tipos de mapeamento mais comumente usado no Python: `dict`, `defaultdict`, e `OrderedDict`. + + +==== Revisão dos métodos mais comuns dos mapeamentos + +A((("defaultdict")))((("OrderedDict")))((("collections.abc module", "defaultdict and OrderedDict"))) +API básica para mapeamentos é bem completa. +A <> mostra os métodos implementados por `dict` e pelas variantes mais usadas: +`defaultdict` e `OrderedDict`, ambas classes definidas no módulo `collections`. + +[role="pagebreak-before less_space"] +[[mapping_methods_tbl]] +.Métodos do tipos de mapeamento `dict`, `collections.defaultdict`, e `collections.OrderedDict` (métodos comuns de `object` omitidos por concisão); argumentos opcionais então entre `[…]` +[options="header"] +|=========================================================================================================================================================================================================================================================================== +| |dict |defaultdict |OrderedDict |   +| `d.clear()` | ● | ● | ● | Remove todos os itens. +| `+d.__contains__(k)+` | ● | ● | ● | `k in d` +| `d.copy()` | ● | ● | ● | Cópia rasa +| `+d.__copy__()+` | | ● | | Suporte a `copy.copy(d)`. +| `d.default_factory` | | ● | | Invocável que `+__missing__+` utiliza para criar valores ausentesfootnote:[`default_factory` não é um método, mas um atributo invocável definido pelo usuário quando um `defaultdict` é instanciado.] +| `+d.__delitem__(k)+` | ● | ● | ● | `del d[k]`—remove item com chave `k` +| `d.fromkeys(it, [initial])` | ● | ● | ● | Novo mapeamento com chaves no iterável `it`, com um valor inicial opcional (o default é `None`) +| `d.get(k, [default])` | ● | ● | ● | Obtém item com chave `k`, devolve `default` ou `None` se `k` não existir +| `+d.__getitem__(k)+` | ● | ● | ● | ++d[k]++—obtém item com chave `k` +| `d.items()` | ● | ● | ● | Obtém uma _view_ dos itens—pares `(chave, valor)` +| `+d.__iter__()+` | ● | ● | ● | Obtém iterador das chaves +| `d.keys()` | ● | ● | ● | Obtém _view_ das chaves +| `+d.__len__()+` | ● | ● | ● | `len(d)`—número de itens +| `+d.__missing__(k)+` | | ● | | Chamado quando `+__getitem__+` não consegue encontrar a chave +| `d.move_to_end(k, [last])` | | | ● | Move `k` para primeira ou última posição (`last` é `True` por default). +| `+d.__or__(other)+` | ● | ● | ● | Suporte a `d1 \| d2` para criar um novo `dict`, fundindo `d1` e `d2` (Python ≥ 3.9) +| `+d.__ior__(other)+` | ● | ● | ● | Suporte a `d1 \|= d2` para atualizar `d1` com `d2` (Python ≥ 3.9) +| `d.pop(k, [default])` | ● | ● | ● | Remove e devolve valor em `k`, ou `default` ou `None`, se `k` não existir +| `d.popitem()` | ● | ● | ● | Remove e devolve, na forma `(chave, valor)`, o último item inseridofootnote:[`OrderedDict.popitem(last=False)` remove o primeiro item inserido (FIFO). O argumento nomeado `last` não é suportado por `dict` ou `defaultdict`, pelo menos até Python 3.10b3.] +| `+d.__reversed__()+` | ● | ● | ● | Suporte a `reverse(d)`—devolve um iterador de chaves, da última para a primeira a serem inseridas +| `+d.__ror__(other)+` | ● | ● | ● | Suporte a `other \| dd`—operador de união invertido (Python ≥ 3.9)footnote:[Operadores reversos são tratados no <>.] +|`d.setdefault(k, [default])` | ● | ● | ● | Se `k in d`, devolve `d[k]`; senão, atribui `d[k] = default` e devolve isso +| `+d.__setitem__(k, v)+` | ● | ● | ● | `d[k] = v`—coloca `v` em `k` +| `d.update(m, [**kwargs])` | ● | ● | ● | Atualiza `d` com itens de um mapeamento ou iterável de pares `(chave, valor)` +| `d.values()` | ● | ● | ● | Obtém uma _view_ dos valores +|=========================================================================================================================================================================================================================================================================== + +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` 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)`. +O construtor da maioria dos mapeamentos de Python usa internamente a lógica de `update()`, +o que quer dizer que eles podem ser inicializados por outros mapeamentos +ou a partir de qualquer objeto iterável que produza pares `(chave, valor)`. + +Um método sutil dos mapeamentos é `setdefault()`. +Ele evita buscas redundantes de chaves quando precisamos atualizar o valor em um item no mesmo lugar. +A próxima seção mostra como ele pode ser usado. + +==== Inserindo ou atualizando valores mutáveis + +Alinhada((("fail-fast philosophy")))((("defensive programming")))((("mutable values, inserting or updating", id="MVinsert03"))) +à filosofia de _falhar rápido_ de Python, a consulta a um `dict` com `d[k]` gera um erro quando `k` não é uma chave existente. +Pythonistas sabem que `d.get(k, default)` é uma alternativa a `d[k]` quando receber um valor default é +mais conveniente que tratar um `KeyError`. +Entretanto, se você está buscando um valor mutável e quer atualizá-lo, há um jeito melhor. + +Considere um script para indexar texto, produzindo um mapeamento no qual cada chave é uma palavra, +e o valor é uma lista das posições onde aquela palavra ocorre, como mostrado no <>. + +[[index0_output_ex]] +.Saída parcial do <> processando o texto "Zen of Python"; cada linha mostra uma palavra e uma lista de ocorrências na forma de pares `(line_number, column_number)` (número da linha, número da coluna). +==== +[source] +---- +$ python3 index0.py zen.txt +a [(19, 48), (20, 53)] +Although [(11, 1), (16, 1), (18, 1)] +ambiguity [(14, 16)] +and [(15, 23)] +are [(21, 12)] +aren [(10, 15)] +at [(16, 38)] +bad [(19, 50)] +be [(15, 14), (16, 27), (20, 50)] +beats [(11, 23)] +Beautiful [(3, 1)] +better [(3, 14), (4, 13), (5, 11), (6, 12), (7, 9), (8, 11), (17, 8), (18, 25)] +... +---- +==== + +O <> é um script aquém do ideal, para mostrar um caso onde `dict.get` +não é a melhor maneira de lidar com uma chave ausente. +Ele foi adaptado de um exemplo de Alex Martelli.footnote:[O script original aparece no slide 41 da +apresentação de Martelli, https://fpy.li/3-5["Re-learning Python" (_Reaprendendo Python_)] (EN). +O script é, na verdade, uma demonstração de `dict.setdefault`, como visto no nosso <>.] + +[[index0_ex]] +.index0.py usa `dict.get` para obter e atualizar uma lista de ocorrências de palavras de um índice (uma solução melhor é apresentada no <>) +==== +[source, python] +---- +include::../code/03-dict-set/index0.py[tags=INDEX0] +---- +==== +<1> Obtém a lista de ocorrências de `word`, ou `[]` se a palavra não for encontrada. +<2> Acrescenta uma nova localização a `occurrences`. +<3> Coloca a `occurrences` modificada no dict `index`; isso exige uma segunda busca em `index`. +<4> Não estou chamando `str.upper` no argumento `key=` de `sorted`, apenas passando uma referência àquele método, +para que a função `sorted` possa usá-lo para normalizar as palavras antes de ordená-las.footnote:[Isso +é um exemplo do uso de um método como uma função de primeira classe, o assunto do <>.] + +As três linhas tratando de `occurrences` no <> podem ser substituídas por uma única linha +usando `dict.setdefault`. O <> fica mais próximo do código apresentado por Alex Martelli. + +[[index_ex]] +.index.py usa `dict.setdefault` para obter e atualizar uma lista de ocorrências de uma palavra em uma única linha de código; compare com o <> +==== +[source, python] +---- +include::../code/03-dict-set/index.py[tags=INDEX] +---- +==== +<1> Obtém a lista de ocorrências de `word`, ou a define como `[]`, se não for encontrada; +`setdefault` devolve o valor, então ele pode ser atualizado sem uma segunda busca. + +Em outras palavras, o resultado final desta linha... + +[source, python] +---- +my_dict.setdefault(key, []).append(new_value) +---- + +...é o mesmo que executar... + +[source, python] +---- +if key not in my_dict: + my_dict[key] = [] +my_dict[key].append(new_value) +---- + +...exceto que este último trecho de código executa pelo menos duas buscas por `key`—três se +a chave não for encontrada—enquanto `setdefault` faz tudo isso com uma única busca. + +Uma questão relacionada, o tratamento de chaves ausentes em qualquer busca (e não apenas para inserção de valores), +é o assunto da próxima seção.((("", startref="MVinsert03")))((("", startref="Mapi03")))((("", startref="DASapi03"))) + +[[mappings_flexible_sec]] +=== Tratamento automático de chaves ausentes + +Algumas vezes((("dictionaries and sets", "automatic handling of missing keys", id="DASmissing03")))((("keys", +"automatic handling of missing", id="Kauto03")))((("mappings", "automatic handling of missing keys", id="Mauto03"))) +é conveniente que os mapeamentos devolvam algum valor padronizado quando se busca por uma chave ausente. +Há duas abordagem principais para esse fim: uma é usar um `defaultdict` em vez de um `dict` simples. +A outra é criar uma subclasse de `dict` ou de qualquer outro tipo de mapeamento e acrescentar um método `+__missing__+`. +Vamos ver as duas soluções a seguir. + +[[defaultdict_sec]] +==== defaultdict: outra perspectiva sobre as chaves ausentes + +Uma instância de `collections.defaultdict`((("defaultdict"))) cria itens com um valor default sob demanda, +sempre que uma chave ausente é buscada usando a sintaxe `d[k]`. +O <> usa `defaultdict` para fornecer outra solução elegante para o índice de palavras do <>. + +Funciona assim: ao instanciar um `defaultdict`, +você fornece um invocável que produz um valor default sempre que `+__getitem__+` recebe uma chave inexistente como argumento. + +Por exemplo, dado um `defaultdict` criado por `dd = defaultdict(list)`, +se `'new-key'` não estiver em `dd`, a expressão `dd['new-key']` segue os seguintes passos: + +. Chama `list()`` para criar uma nova lista. +. Insere a lista em `dd` usando `'new-key'` como chave. +. Devolve uma referência para aquela lista. + +O invocável que produz os valores default é mantido em um atributo de instância chamado `default_factory`. + +[[index_default_ex]] +.index_default.py: usando um `defaultdict` em vez do método `setdefault` +==== +[source, python] +---- +include::../code/03-dict-set/index_default.py[tags=INDEX_DEFAULT] +---- +==== +<1> Cria um `defaultdict` com o construtor de `list` como `default_factory`. +<2> Se `word` não está inicialmente no `index`, o `default_factory` é chamado para +produzir o valor ausente, que neste caso é uma `list` vazia, +que então é atribuída a `index[word]` e devolvida, de forma que a operação +`.append(location)` é sempre bem sucedida. + +Se nenhum `default_factory` é fornecido, o `KeyError` usual é gerado para chaves ausente. + +[WARNING] +==== +O `default_factory` de um `defaultdict` só é invocado para fornecer valores default para chamadas a `+__getitem__+`, +não para outros métodos. +Por exemplo, se `dd` é um `defaultdict` e `k` uma chave ausente, `dd[k]` chamará `default_factory` +para criar um valor default, +mas `dd.get(k)` vai devolver `None`, e `k in dd` resulta `False`. +==== + +O mecanismo que faz `defaultdict` funcionar, chamando `default_factory`, +é o método especial `+__missing__+`, apresentado a seguir. + + +[[missing_method_sec]] +==== O método +__missing__+ + +Por((("__missing__", id="missing03"))) trás da forma como os mapeamentos +lidam com chaves ausentes está o método muito apropriadamente chamado `+__missing__+`.footnote:[NT: +"Missing" significa ausente, perdido ou desaparecido] +Esse método não é definido na classe base `dict`, mas `dict` está ciente de sua possibilidade: +se você criar uma subclasse de `dict` e incluir um método `+__missing__+`, o `+dict.__getitem__+` +padrão vai chamar seu método sempre que uma chave não for encontrada, em vez de gerar um `KeyError`. + +Suponha que você queira um mapeamento onde as chaves são convertidas para `str` quando são procuradas. +Um caso de uso concreto seria uma biblioteca para dispositivos IoT +(Internet of Things, _Internet das Coisas_)footnote:[Uma biblioteca dessas é a +https://fpy.li/3-6[_Pingo.io_], que não está mais em desenvolvimento ativo.], +onde uma placa programável com portas genéricas programáveis (por exemplo, uma Raspberry Pi ou uma Arduino) +é representada por uma classe "Placa" com um atributo `minha_placa.portas`, +que é uma mapeamento dos identificadores das portas físicas para objetos de software portas. +O identificador da porta física pode ser um número ou uma string como `"A0"` ou `"P9_12"`. +Por consistência, é desejável que todas as chaves em `placa.portas` seja strings, +mas também é conveniente buscar uma porta por número, como em `meu-arduino.porta[13]`, +para evitar que iniciantes tropecem quando quiserem fazer piscar o LED na porta 13 de seus Arduinos. +O <> mostra como tal mapeamento funcionaria. + +[[ex_strkeydict0_tests]] +.Ao buscar por uma chave não-string, `StrKeyDict0` a converte para `str` quando ela não é encontrada +==== +[source, python] +---- +include::../code/03-dict-set/strkeydict0.py[tags=STRKEYDICT0_TESTS] +---- +==== + +O <> implementa a classe `StrKeyDict0`, que passa nos doctests acima. + +[TIP] +==== +Uma forma melhor de criar uma mapeamento definido pelo usuário é criar uma subclasse de +`collections.UserDict` em vez de `dict` (como faremos no <>). +Aqui criamos uma subclasse de `dict` apenas para mostrar que `+__missing__+` +é suportado pelo método embutido `+dict.__getitem__+`. +==== + +[[ex_strkeydict0]] +.`StrKeyDict0` converte chaves não-string para string no momento da consulta (vejas os testes no <>) +==== +[source, python] +---- +include::../code/03-dict-set/strkeydict0.py[tags=STRKEYDICT0] +---- +==== +<1> `StrKeyDict0` herda de `dict`. +<2> Verifica se `key` já é uma `str`. Se é, e está ausente, gera um `KeyError`. +<3> Cria uma `str` de `key` e a procura. +<4> O método `get` delega para `+__getitem__+` usando a notação `self[key]`; +isso dá oportunidade para nosso `+__missing__+` agir. +<5> Se um `KeyError` foi gerado, `+__missing__+` já falhou, então devolvemos o `default`. +<6> Procura pela chave não-modificada (a instância pode conter chaves não-`str`), +depois por uma `str` criada a partir da chave. + +Considere por um momento o motivo do teste `isinstance(key, str)` ser necessário na implementação de `+__missing__+`. + +Sem aquele teste, nosso método `+__missing__+` funcionaria bem com qualquer chave `k`—`str` ou não—sempre que `str(k)` +produzisse uma chave existente. +Mas se `str(k)` não for uma chave existente, teríamos uma recursão infinita. +Na última linha de `+__missing__+`, `self[str(key)]` chamaria `+__getitem__+`, +passando aquela chave `str`, e `+__getitem__+`, por sua vez, chamaria +`+__missing__+` novamente. + +O((("__contains__"))) método `+__contains__+` também é necessário para que +o comportamento nesse exemplo seja consistente, pois a operação `k in d` o chama, +mas o método herdado de `dict` não invoca `+__missing__+` com chaves ausentes. +Há um detalhe sutil em nossa implementação de `+__contains__+`: +não verificamos a existência da chave da forma pythônica normal—`k in d`—porque `str(key) in self` chamaria +`+__contains__+` recursivamente. +Evitamos isso procurando a chave explicitamente em `self.keys()`. + +Uma busca como `k in my_dict.keys()` é eficiente em Python 3 mesmo para mapeamentos muito grandes, +porque `dict.keys()` devolve uma view, que é similar a um _set_, como veremos na <>. +Entretanto, lembre-se de que `k in my_dict` faz o mesmo trabalho, +e é mais rápido porque evita a busca nos atributos para encontrar o método `.keys`. + +Eu tinha uma razão específica para usar `self.keys()` no método `+__contains__+` do +<>. +A verificação da chave não-modificada `++key in self.keys()++` é necessária por correção, +pois `StrKeyDict0` não obriga todas as chaves no dicionário a serem do tipo `str`. +Nosso único objetivo com esse exemplo simples foi fazer a busca "mais amigável", +e não forçar tipos. + +[WARNING] +==== +Classes definidas pelo usuário derivadas de mapeamentos da biblioteca padrão podem ou não usar +`+__missing__+` como alternativa em sua implementação de `+__getitem__+`, `get`, ou `+__contains__+`, +como explicado na próxima seção. +==== + +[[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: + +subclasse de `dict`:: + Uma subclasse de `dict` que implemente apenas `+__missing__+` e nenhum outro método. + Nesse caso, `+__missing__+` pode ser chamado apenas em `d[k]`, que usará o `+__getitem__+` herdado de `dict`. + +subclasse de `collections.UserDict`:: + Da mesma forma, uma subclasse de `UserDict` que implemente apenas `+__missing__+` e nenhum outro método. + O método `get` herdado de `UserDict` chama `+__getitem__+`. + Isso significa que `+__missing__+` pode ser chamado para tratar de consultas com `d[k]` e com + `d.get(k)`. + +subclasse de `abc.Mapping` com o `+__getitem__+` mais simples possível:: + Uma subclasse mínima de `abc.Mapping`, implementando `+__missing__+` e os métodos abstratos obrigatórios, + incluindo uma implementação de `+__getitem__+` que não chama `+__missing__+`. + O método `+__missing__+` nunca é acionado nessa classe. + +subclasse de `abc.Mapping` com `+__getitem__+` chamando `+__missing__+`:: + Uma subclasse mínima de `abc.Mapping`, implementando `+__missing__+` e os métodos abstratos obrigatórios, + incluindo uma implementação de `+__getitem__+` que chama `+__missing__+`. + O método `+__missing__+` é acionado nessa classe para consultas por chaves ausentes feitas com + `d[k]`, `d.get(k)`, e `k in d`. + +Veja +https://fpy.li/3-7[_missing.py_] +no repositório de exemplos de código para demonstrações dos cenários descritos acima. + +Os quatro cenários que acabo de descrever supõem implementações mínimas. +Se a sua subclasse implementa `+__getitem__+`, `get`, e `+__contains__+`, +então você pode ou não fazer tais métodos usarem `+__missing__+`, dependendo de suas necessidades. +O ponto aqui é mostrar que é preciso ter cuidado ao criar subclasses dos mapeamentos da biblioteca padrão +para usar `+__missing__+`, porque as classes base suportam comportamentos default diferentes. +Não se esqueça que o comportamento de `setdefault` e `update` também é afetado pela consulta de chaves. +E por fim, dependendo da lógica de seu `+__missing__+`, +pode ser necessário implementar uma lógica especial em `+__setitem__+`, +para evitar inconsistências ou comportamentos surpreendentes. +Veremos um exemplo disso na <>. + +Até aqui tratamos dos tipos de mapeamentos `dict` e `defaultdict`, +mas a biblioteca padrão traz outras implementações de mapeamentos, +que discutiremos a seguir.((("", startref="missing03")))((("", startref="Mauto03")))((("", +startref="Kauto03")))((("", startref="DASmissing03"))) + +=== Variações de dict + +Nessa((("dictionaries and sets", "variations of dict", id="DASvardict03"))) seção falaremos brevemente sobre +os tipos de mapeamentos incluídos na biblioteca padrão diferentes de `defaultdict`, já visto na <>. + +[[ordereddict_sec]] +==== collections.OrderedDict + +Agora((("collections.abc module", "defaultdict and OrderedDict")))((("OrderedDict"))) que o `dict` embutido +também mantém as chaves ordenadas (desde o Python 3.6), +o motivo mais comum para usar `OrderedDict` é escrever código compatível com versões anteriores de Python. +Dito isso, a documentação lista algumas diferenças entre `dict` e `OrderedDict` que +ainda persistem e que cito aqui: + +* A operação de igualdade para `OrderedDict` verifica a igualdade da ordenação. +* O método `popitem()` de `OrderedDict` tem uma assinatura diferente, +que aceita um argumento opcional especificando qual item será devolvido. +* `OrderedDict` tem um método `move_to_end()`, que reposiciona de um elemento para uma ponta do dicionário de forma eficiente. +* O `dict` comum foi projetado para ser muito bom nas operações de mapeamento. +Preservar a ordem de inserção era uma preocupação secundária. +* `OrderedDict` foi projetado para ser bom em operações de reordenamento. +Eficiência espacial, velocidade de iteração e o desempenho de operações de atualização eram preocupações secundárias. +* Em termos do algoritmo, um `OrderedDict` lida melhor que um dict com operações frequentes de reordenamento. +Isso o torna adequado para monitorar acessos recentes (em um cache LRUfootnote:[NT: Least Recently Used, _Menos Recentemente Usado_, +esquema de cache que descarta o item armazenado que esteja há mais tempo sem requisições], por exemplo). + + +[[chainmap_sec]] +==== collections.ChainMap + +Uma((("collections.abc module", "ChainMap")))((("ChainMap"))) instância de `ChainMap` mantém +uma lista de mapeamentos que podem ser consultados como se fossem um mapeamento único. +A busca é realizada em cada mapa incluído, na ordem em que eles aparecem na chamada ao construtor, +e é bem sucedida assim que a chave é encontrada em um daqueles mapeamentos. +Por exemplo: + +[source, python] +---- +>>> d1 = dict(a=1, b=3) +>>> d2 = dict(a=2, b=4, c=6) +>>> from collections import ChainMap +>>> chain = ChainMap(d1, d2) +>>> chain['a'] +1 +>>> chain['c'] +6 +---- + +A instância de `ChainMap` não cria cópias dos mapeamentos, mantém referências para eles. +Atualizações ou inserções a um `ChainMap` afetam apenas o primeiro mapeamento passado. +Continuando do exemplo anterior: + +[source, python] +---- +>>> chain['c'] = -1 +>>> d1 +{'a': 1, 'b': 3, 'c': -1} +>>> d2 +{'a': 2, 'b': 4, 'c': 6} +---- + +Um `ChainMap` é útil na implementação de linguagens com escopos aninhados, +onde cada mapeamento representa um contexto de escopo, +desde o escopo aninhado mais interno até o mais externo. +A seção +https://fpy.li/33["Objetos ChainMap"], +na documentação de `collections`, apresenta vários exemplos do uso de `Chainmap`, +incluindo esse trecho inspirado nas regras básicas de consulta de variáveis no Python: + +[source, python] +---- +import builtins +pylookup = ChainMap(locals(), globals(), vars(builtins)) +---- + +A <> explica uma subclasse de `ChainMap` usada para implementar +um interpretador parcial da linguagem de programação Scheme. + + +==== collections.Counter + +Um((("collections.abc module", "Counter")))((("Counter"))) mapeamento que mantém uma contagem para cada chave. +Atualizar uma chave existente adiciona à sua contagem. +Isso pode ser usado para contar instâncias de objetos hashable ou como um _multiset_ (conjunto múltiplo), +discutido adiante nessa seção. +`Counter` implementa os operadores `+` e `-` para combinar contagens, +e outros métodos úteis tal como o `most_common([n])`, +que devolve uma lista ordenada de tuplas com os _n_ itens mais comuns e suas contagens; veja a +https://fpy.li/34[documentação]. + +Aqui temos um `Counter` usado para contar as letras em palavras: + +[source, python] +---- +>>> ct = collections.Counter('abracadabra') +>>> ct +Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}) +>>> ct.update('aaaaazzz') +>>> ct +Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1}) +>>> ct.most_common(3) +[('a', 10), ('z', 3), ('b', 2)] +---- + +Observe que as chaves `'b'` e `'r'` estão empatadas em terceiro lugar, mas +`ct.most_common(3)` mostra apenas três contagens. + +Para usar `collections.Counter` como um conjunto múltiplo, trate cada chave como um elemento de um conjunto, +e a contagem será o número de ocorrências daquele elemento no conjunto. + + +==== shelve.Shelf + +O((("shelve module")))((("pickle module")))((("keys", "persistent storage for mapping"))) módulo `shelve` +na biblioteca padrão fornece armazenamento persistente a um mapeamento de chaves +em formato string para objetos Python serializados no formato binário `pickle`. +O nome curioso, `shelve`, faz sentido quando você percebe que potes de `pickle` são armazenadas em +prateleiras.footnote:[NT: "_to shelve_" é "_colocar na prateleira_", "_pickle_" também significa "_conserva_" +e "pickles" é literalmente _picles_. +O trocadilho dos desenvolvedores de Python é sobre colocar `pickles` em `shelves`.] + +A função de módulo `shelve.open` devolve uma instância de `shelve.Shelf`—um banco de dados DBM simples de chave-valor, +baseado no módulo `dbm`, com as seguintes características: + +* `shelve.Shelf` é uma subclasse de `abc.MutableMapping`, +então fornece os métodos essenciais esperados de um tipo mapeamento. +* Além disso, `shelve.Shelf` fornece alguns outros métodos de gerenciamento de E/S, como `sync` e `close`. +* Uma instância de `Shelf` é um gerenciador de contexto, +então é possível usar um bloco `with` para garantir que ela seja fechada após o uso. +* Chaves e valores são salvos sempre que um novo valor é atribuído a uma chave. +* As chaves devem ser strings. +* Os valores devem ser objetos que o módulo `pickle` possa serializar. + +A documentação para os módulos +https://fpy.li/35[shelve], +https://fpy.li/36[dbm] (EN), e +https://fpy.li/37[pickle] +traz mais detalhes e também algumas ressalvas. + +[WARNING] +==== +O `pickle` de Python é fácil de usar nos caso mais simples, mas há vários inconvenientes. +Leia o https://fpy.li/3-13["Pickle’s nine flaws"], de Ned Batchelder, +antes de adotar qualquer solução envolvendo `pickle`. +Em seu post, Ned menciona outros formatos de serialização que podem ser considerados como alternativas. +==== + +As classes `OrderedDict`, `ChainMap`, `Counter`, e `Shelf` podem ser usadas diretamente, +mas também podem ser customizadas por subclasses. +`UserDict`, por outro lado, foi planejada apenas como uma classe base a ser estendida. + + +[[sublcassing_userdict_sec]] +==== Criando subclasses de UserDict em vez de dict + +É ((("collections.abc module", "UserDict", id="coluserdict03")))((("UserDict", id="userdict03"))) +melhor criar um novo tipo de mapeamento estendendo `collections.UserDict` em vez de `dict`. +Percebemos isso quando tentamos estender nosso `StrKeyDict0` do <> +para assegurar que qualquer chave adicionada ao mapeamento seja armazenada como `str`. + +A principal razão pela qual é melhor criar uma subclasse de `UserDict` em vez de `dict` é +que o tipo embutido tem alguns atalhos de implementação, +que acabam nos obrigando a sobrescrever métodos que poderíamos apenas herdar de `UserDict` +sem maiores problemas.footnote:[O problema exato de se criar subclasses de `dict` e de outros tipos embutidos +é tratado na <>.] + +Observe que `UserDict` não herda de `dict`, mas usa uma composição: +a classe tem uma instância interna de `dict`, chamada `data`, que mantém os itens propriamente ditos. +Isso evita recursão indesejada quando escrevemos métodos especiais, como `+__setitem__+`, +e simplifica a programação de `+__contains__+`, quando comparado com o <>. + +Graças((("keys", "converting nonstring keys to str"))) a `UserDict`, o `StrKeyDict` (<>) +é mais conciso que o `StrKeyDict0` (<>), mais ainda faz melhor: +ele armazena todas as chaves como `str`, +evitando surpresas desagradáveis se a instância for criada ou atualizada com +dados contendo chaves de outros tipos (que não string). + +[[ex_strkeydict]] +.`StrKeyDict` sempre converte chaves que não sejam strings para `str` na inserção, atualização e busca +==== +[source, python] +---- +include::../code/03-dict-set/strkeydict.py[tags=STRKEYDICT] +---- +==== +<1> `StrKeyDict` estende `UserDict`. +<2> `+__missing__+` é exatamente igual ao do <>. +<3> `+__contains__+` é mais simples: podemos assumir que todas as chaves armazenadas são `str`, +e podemos operar sobre `self.data` em vez de invocar `self.keys()`, como fizemos em `StrKeyDict0`. +<4> `+__setitem__+` converte qualquer `key` para uma `str`. +Esse método é mais fácil de sobrescrever quando podemos delegar para o atributo `self.data`. + +Como `UserDict` estende `abc.MutableMapping`, o restante dos métodos que fazem de `StrKeyDict` +um mapeamento completo são herdados de `UserDict`, `MutableMapping`, ou `Mapping`. +Estes últimos contém vários métodos concretos úteis, apesar de serem classes base abstratas (ABCs). +Os seguinte métodos são dignos de nota: + +`MutableMapping.update`:: Esse método poderoso pode ser chamado diretamente, mas também é usado por +`+__init__+` para criar a instância a partir de outros mapeamentos, de iteráveis de pares `(chave, valor)`, +e de argumentos nomeados. +Como usa `self[chave] = valor` para adicionar itens, +ele termina por invocar nossa implementação de `+__setitem__+`. + +`Mapping.get`:: No `StrKeyDict0` (<>), +precisamos codificar nosso próprio `get` para devolver os mesmos resultados de `+__getitem__+`, +mas no <> herdamos `Mapping.get`, que é implementado exatamente como `StrKeyDict0.get` +(consulte o https://fpy.li/3-14[código-fonte de Python]). + +[TIP] +==== +Antoine Pitrou escreveu a +https://fpy.li/pep455[PEP 455--Adding a key-transforming dictionary to collections +(_Acrescentando um dicionário com transformação de chaves a collections_)] +(EN) e um patch para aperfeiçoar o módulo `collections` com uma classe `TransformDict`, +que é mais genérico que `StrKeyDict` e preserva as chaves como fornecidas antes de aplicar a transformação. +A PEP 455 foi rejeitada em maio de 2015—veja a https://fpy.li/3-15[mensagem de rejeição] (EN) +de Raymond Hettinger. +Para experimentar com a `TransformDict`, +extraí o patch de Pitrou do https://fpy.li/3-16[issue18986] (EN) +para um módulo independente (https://fpy.li/3-17[_03-dict-set/transformdict.py_] +disponível no https://fpy.li/code[repositório de código da segunda edição do _Fluent Python_]). +==== + +Sabemos que existem tipos de sequências imutáveis, mas e mapeamentos imutáveis? +Não existe um tipo real desses na biblioteca padrão, mas um substituto está disponível. +É o que vem a seguir.((("", startref="userdict03")))((("", startref="userdict03")))((("", startref="DASvardict03"))) + + +=== Mapeamentos imutáveis + +Os((("dictionaries and sets", "immutable mappings")))((("mappings", "immutable mappings")))((("immutable mappings"))) +tipos de mapeamentos disponíveis na biblioteca padrão são todos mutáveis, +mas pode ser desejável impedir que os usuários mudem um mapeamento por acidente. +Um caso de uso concreto pode ser encontrado, novamente, em uma biblioteca de programação de hardware como +a((("Pingo library"))) _Pingo_, mencionada na <>: +o mapeamento `board.pins` representa as portas de GPIO (General Purpose Input/Output, _Entrada/Saída Genérica_) +em um dispositivo. +Dessa forma, seria útil evitar atualizações descuidadas de `board.pins`, +pois o hardware não pode ser modificado via software: +qualquer mudança no mapeamento o tornaria inconsistente com a realidade física do dispositivo. + +O módulo `types` oferece uma classe invólucro (_wrapper_) chamada `MappingProxyType` que, +dado um mapeamento, devolve uma instância de `mappingproxy`, +que é um proxy somente para leitura (mas dinâmico) do mapeamento original. +Isso significa que atualizações ao mapeamento original são refletidas no `mappingproxy`, +mas nenhuma mudança pode ser feita através desse último. +Veja uma breve demonstração no <>. + +[[ex_MappingProxyType]] +.`MappingProxyType` cria uma instância somente de leitura de `mappingproxy` a partir de um `dict` +==== +[source, python] +---- +>>> from types import MappingProxyType +>>> d = {1: 'A'} +>>> d_proxy = MappingProxyType(d) +>>> d_proxy +mappingproxy({1: 'A'}) +>>> d_proxy[1] <1> +'A' +>>> d_proxy[2] = 'x' <2> +Traceback (most recent call last): + File "", line 1, in +TypeError: 'mappingproxy' object does not support item assignment +>>> d[2] = 'B' +>>> d_proxy <3> +mappingproxy({1: 'A', 2: 'B'}) +>>> d_proxy[2] +'B' +>>> +---- +==== +<1> Os items em `d` podem ser vistos através de `d_proxy`. +<2> Não é possível fazer modificações através de `d_proxy`. +<3> `d_proxy` é dinâmica: qualquer mudança em `d` é refletida ali. + + +Isso pode ser usado assim na prática, no cenário da programação de hardware: +o construtor em uma subclasse concreta `Board` preencheria um mapeamento privado com os objetos porta, +e o exporia aos clientes da API via um atributo público `.portas`, implementado como um `mappingproxy`. +Dessa forma os clientes não poderiam acrescentar, remover ou modificar as portas por acidente. + +A seguir estudaremos _views_—que permitem operações de alto desempenho em um `dict`, sem cópias desnecessárias dos dados. + + +[[dictionary_views_sec]] +[role="pagebreak-before less_space"] +=== Views de dicionários + +Objetos `dict`((("dictionaries and sets", "dictionary views", id="DASviews03"))) implementam os métodos +`.keys()`, `.values()`, e `.items()`, que devolvem instâncias de classes chamadas +`dict_keys`, `dict_values`, e `dict_items`, respectivamente. +Essas views de dicionário são projeções somente para leitura de estruturas de dados internas +usadas na implementação de `dict`. +Elas evitam o uso de memória adicional dos métodos equivalentes no Python 2, +que construiam listas, duplicando dados já presentes no `dict`. +E também substituem os métodos antigos que devolviam iteradores. + +O <> mostra algumas operações básicas suportadas por todas as views de dicionários. + +[[ex_dict_values]] +.O método `.values()` devolve uma view dos valores em um `dict` +==== +[source, python] +---- +>>> d = dict(a=10, b=20, c=30) +>>> values = d.values() +>>> values +dict_values([10, 20, 30]) <1> +>>> len(values) <2> +3 +>>> list(values) <3> +[10, 20, 30] +>>> reversed(values) <4> + +>>> values[0] <5> +Traceback (most recent call last): + File "", line 1, in +TypeError: 'dict_values' object is not subscriptable +---- +==== +<1> O `repr` de um objeto view mostra seu conteúdo. +<2> Podemos consultar a `len` de uma view. +<3> Views são iteráveis, então é fácil criar listas a partir delas. +<4> Views implementam `+__reversed__+`, devolvendo um iterador customizado. +<5> Não é possível usar `[]` para obter itens individuais de uma view. + +Um objeto view é um proxy dinâmico. +Se o `dict` fonte é atualizado, as mudanças podem ser vistas imediatamente através de uma view existente. +Continuando do <>: + +[source, python] +---- +>>> d['z'] = 99 +>>> d +{'a': 10, 'b': 20, 'c': 30, 'z': 99} +>>> values +dict_values([10, 20, 30, 99]) +---- + +As classes `dict_keys`, `dict_values`, e `dict_items` são internas: +elas não estão disponíveis via `+__builtins__+` ou qualquer módulo da biblioteca padrão, +e mesmo que você obtenha uma referência para uma delas, +não pode usar essa referência para criar uma view do zero no seu código Python: + +[source, python] +---- +>>> values_class = type({}.values()) +>>> v = values_class() +Traceback (most recent call last): + File "", line 1, in +TypeError: cannot create 'dict_values' instances +---- + +A classe `dict_values` é a view de dicionário mais simples—ela implementa apenas os métodos especiais +`+__len__+`, `+__iter__+`, e `+__reversed__+`. +Além desses métodos, `dict_keys` e `dict_items` implementam vários métodos dos _sets_, +quase tantos quanto a classe `frozenset`. +Após vermos os conjuntos, voltaremos a estudar `dict_keys` e `dict_items`, +na <>. + +Agora vamos ver algumas regras e dicas baseadas na forma como `dict` é +implementado debaixo dos panos.((("", startref="DASviews03"))) + +[[conseq_dict_internal_sec]] +=== Consequências práticas da forma como dict funciona + +A((("dictionaries and sets", "consequences of how dict works"))) implementação da tabela de hash do +`dict` de Python é muito eficiente, mas é importante entender os efeitos práticos desse design: + +* Chaves((("keys", "practical consequences of using dict"))) devem ser objetos hashable. +Eles devem implementar métodos `+__hash__+` e `+__eq__+` apropriados, como descrito na <>. +* O acesso aos itens através da chave é muito rápido. +Mesmo que um `dict` tenha milhões de chaves, Python pode localizar uma chave diretamente, +computando o código hash da chave e derivando um deslocamento do índice na tabela de hash, +com um possível ônus de um pequeno número de tentativas até encontrar a entrada correspondente. +* A ordenação das chaves é preservada, +como efeito colateral de um layout de memória mais compacto para `dict` no CPython 3.6, +que se tornou um recurso oficial da linguagem no 3.7. +* Apesar de seu novo layout compacto, os dicts apresentam, inevitavelmente, +um uso adicional significativo de memória. +A estrutura de dados interna mais compacta para um contêiner seria um array de ponteiros para os +itens.footnote:[É assim que as tuplas são armazenadas.] +Comparado a isso, uma tabela de hash precisa armazenar mais dados para cada entrada e, para manter a eficiência, +Python precisa manter pelo menos um terço das linhas da tabela de hash vazias. +* Para economizar memória, evite criar atributos de instância fora do método `+__init__+`. + +Essa última dica, sobre atributos de instância, é consequência do comportamento default de Python, +de armazenar atributos de instância em um atributo `+__dict__+` especial, +que é um `dict` vinculado a cada instância.footnote:[A menos que a classe tenha um atributo `+__slots__+`, +como explicado na <>.] +Desde a implementação da +https://fpy.li/pep412[PEP 412—Key-Sharing Dictionary (_Dicionário de Compartilhamento de Chaves_)] (EN), +no Python 3.3, instâncias de uma classe podem compartilhar uma tabela de hash comum, armazenada com a classe. +Essa tabela de hash comum é compartilhada pelo `+__dict__+` de cada nova instância que, +quando `+__init__+` retorna, tenha os mesmos nomes de atributos que a primeira instância a ser criada naquela classe. +O `+__dict__+` de cada instância então pode manter apenas seus próprios valores de atributos como +um array de ponteiros. +Acrescentar um atributo de instância após o `+__init__+` obriga Python a criar uma nova tabela de hash +só para o `+__dict__+` daquela instância (que era o comportamento default antes de Python 3.3). +De acordo com a PEP 412, essa otimização reduz o uso da memória entre 10% e 20% em programas orientados as objetos. +Os detalhes das otimizações do layout compacto e do compartilhamento de chaves são bastante complexos. +Para saber mais, leia https://fpy.li/hashint["Internals of sets and dicts"] (EN). + +Agora vamos estudar conjuntos. + + +=== Teoria dos conjuntos + +Conjuntos((("sets", "set theory", id=Stheory03")))((("dictionaries and sets", "set theory", +id="DASset03")))((("frozenset"))) +não são novidade no Python, mais ainda são um tanto subutilizados. +O tipo `set` e seu irmão imutável, `frozenset`, +surgiram inicialmente como módulos na biblioteca padrão de Python 2.3, +e foram promovidos a tipos embutidos no Python 2.6. + +[NOTE] +==== +Nesse livro, uso a palavra "conjunto" para me referir tanto a `set` quanto a `frozenset`. +==== + +Um conjunto é uma coleção de objetos únicos. +Uma grande utilidade dos conjuntos é descartar itens duplicados: + +[source, python] +---- +>>> l = ['spam', 'spam', 'eggs', 'spam', 'bacon', 'eggs'] +>>> set(l) +{'eggs', 'spam', 'bacon'} +>>> list(set(l)) +['eggs', 'spam', 'bacon'] +---- + +[TIP] +==== +Para remover elementos duplicados preservando a ordem da primeira ocorrência de cada item, +você pode fazer isso com um `dict` simples, assim: +[source, python] +---- +>>> dict.fromkeys(l).keys() +dict_keys(['spam', 'eggs', 'bacon']) +>>> list(dict.fromkeys(l).keys()) +['spam', 'eggs', 'bacon'] +---- +==== + +Elementos de um conjunto devem ser _hashable_. +O tipo `set` não é hashable, então não é possível criar um `set` com instâncias aninhadas de `set`. +Mas `frozenset` é hashable, então você pode ter instâncias de `frozenset` dentro de um `set`. + +Além de garantir que cada elemento é único, +os tipos conjunto implementam muitas operações entre conjuntos como operadores infixos. +Assim, dados dois conjuntos `a` e `b`, `a | b` devolve sua união, +`a & b` calcula a interseção, `a - b` a diferença, e `a ^ b` a diferença simétrica. +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 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``), +e precisa contar quantas agulhas existem no palheiro. +Graças à interseção de `set` (o operador `&`), +é possível codar isso em uma expressão simples (veja o <>). + +[[ex_set_ops_ex]] +.Conta as ocorrências de agulhas (_needles_) em um palheiro (_haystack_), ambos do tipo set +==== +[source, python] +---- +found = len(needles & haystack) +---- +==== + +Sem o operador de interseção, seria necessário escrever o <> para realizar +a mesma tarefa executa pelo <>. + +[[ex_set_loop_ex]] +.Conta as ocorrências de agulhas (_needles_) em um palheiro (_haystack_); mesmo resultado final do <> +==== +[source, python] +---- +found = 0 +for n in needles: + if n in haystack: + found += 1 +---- +==== + +O <> é um pouco mais rápido que o <>. +Por outro lado, o <> funciona para quaisquer objetos iteráveis `needles` e `haystack`, +enquanto o <> exige que ambos sejam conjuntos. +Mas se você não tem conjuntos à mão, pode sempre criá-los na hora, como mostra o <>. + +[[ex_set_ops_ex2]] +.Conta as ocorrências de agulhas (_needles_) em um palheiro (_haystack_); essas linhas funcionam para qualquer tipo iterável +==== +[source, python] +---- +found = len(set(needles) & set(haystack)) + +# another way: +found = len(set(needles).intersection(haystack)) +---- +==== + +Claro, há o custo extra envolvido na criação dos conjuntos no <>, +mas se ou as `needles` ou o `haystack` já forem um `set`, +a alternativa no <> pode ser mais barata que o <>. + +Qualquer dos exemplos acima é capaz de buscar 1000 elementos em um `haystack` de 10 milhões +de itens em cerca de 0,3 milissegundos—isso é cerca de 0,3 microsegundos por elemento. + +Além do teste de existência extremamente rápido (graças à tabela de hash), +os tipos embutidos `set` e `frozenset` oferecem uma rica API para criar novos conjuntos ou, +no caso de `set`, para modificar conjuntos existentes. +Vamos discutir essas operações em breve, após uma observação sobre sintaxe.((("", startref="Stheory03"))) + + +==== Sets literais + +A((("sets", "set literals"))) sintaxe de literais `set`—`{1}`, `{1, 2}`, etc.—parece +muito com a notação matemática, mas há uma importante exceção: +não há notação literal para o `set` vazio, então precisamos nos lembrar de escrever `set()`. + +.Peculiaridade sintática +[WARNING] +==== +Para criar um `set` vazio, usamos o construtor sem argumentos: `set()`. +Se você escrever `{}`, vai criar um +dict+ vazio—isso não mudou no Python 3. +==== + +No Python 3, a representação padrão dos sets como strings sempre usa a notação `{…}`, +exceto para o conjunto vazio: + +[source, python] +---- +>>> s = {1} +>>> type(s) + +>>> s +{1} +>>> s.pop() +1 +>>> s +set() +---- + +A sintaxe do `set` literal, como `{1, 2, 3}`, é mais rápida e mais legível que uma chamada ao construtor +(por exemplo, `set([1, 2, 3])`). +Essa última forma é mais lenta porque, para avaliá-la, Python precisa buscar o nome `set` para obter seu construtor, +daí criar uma lista e, finalmente, passá-la para o construtor. +Por outro lado, para processar um literal como `{1, 2, 3}`, +o Python roda um bytecode especializado, `BUILD_SET`.footnote:[Isso pode ser interessante, mas não é super importante. +Essa diferença de desempenho vai ocorrer apenas quando um conjunto literal for avaliado, +e isso acontece no máximo uma vez por processo Python—quando um módulo é compilado pela primeira vez. +Se você estiver curiosa, importe a função `dis` do módulo `dis`, +e a use para inspecionar os bytecodes de um `set` literal—por exemplo + `dis('{1}')`—e uma chamada ao construtor `set`—`+dis('set([1])')+`] + +Não há sintaxe especial para representar literais `frozenset`—eles só podem ser criados chamando seu construtor. +Sua representação padrão como string no Python 3 se parece com uma chamada ao construtor +de `frozenset` com um argumento `set`. +Observe a saída no console: + +[source, python] +---- +>>> frozenset(range(10)) +frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) +---- + +E por falar em sintaxe, a ideia das listcomps foi adaptada para criar conjuntos também. + + +==== Compreensões de conjuntos + +Compreensões de conjuntos((("sets", "set comprehensions"))) (_setcomps_) apareceram há bastante tempo, +no Python 2.7, junto com as dictcomps que vimos na <>. O <> mostra procedimento. + +[[ex_setcomp]] +.Cria um conjunto de caracteres Latin-1 que tenham a palavra "SIGN" em seus nomes Unicode +==== +[source, python] +---- +>>> from unicodedata import name <1> +>>> {chr(i) for i in range(32, 256) if 'SIGN' in name(chr(i),'')} <2> +{'§', '=', '¢', '#', '¤', '<', '¥', 'µ', '×', '$', '¶', '£', '©', +'°', '+', '÷', '±', '>', '¬', '®', '%'} +---- +==== +<1> Importa a função `name` de `unicodedata` para obter os nomes dos caracteres. +<2> Cria um conjunto de caracteres com códigos entre 32 e 255 que contenham a palavra `'SIGN'` em seus nomes. + +A ordem da saída muda a cada processo Python, devido ao hash "salgado", mencionado na <>. + +Questões de sintaxe à parte, vamos considerar agora o comportamento dos conjuntos.((("", startref="DASset03"))) + + +[[consequences_set_sec]] +[role="pagebreak-before less_space"] +=== Consequências práticas da forma de funcionamento dos conjuntos + +Os((("dictionaries and sets", "consequences of how set works")))((("sets", "consequences of how set works"))) +tipos `set` e `frozenset` são ambos implementados com um tabela de hash. +Isso tem os seguintes efeitos: + +* Elementos de conjuntos precisam ser objetos hashable. +Eles precisam implementar métodos `+__hash__+` e `+__eq__+` adequados, como descrido na <>. +* O teste de existência de um elemento é muito eficiente. +Um conjunto pode ter milhões de elementos, mas um elemento pode ser localizado diretamente, +computando o código hash da chave e derivando um deslocamento do índice, +com o possível ônus de um pequeno número de tentativas até encontrar a entrada correspondente ou exaurir a busca. +* Conjuntos usam mais memória que um array de ponteiros para seus elementos—que é uma estrutura mais compacta, +porém menos eficientes para buscas quando seu tamanho cresce além de uns poucos elementos. +* A ordem dos elementos depende da ordem de inserção, mas não de forma útil ou confiável. +Se dois elementos são diferentes mas têm o mesmo código hash, +sua posição depende de qual elemento foi inserido primeiro. +* Acrescentar elementos a um conjunto muda a ordem dos elementos existentes. +Isso ocorre porque o algoritmo se torna menos eficiente se a tabela de hash tiver mais de dois terços de ocupação, +então Python pode mover e redimensionar a tabela conforme ela cresce. +Quando isso acontece, os elementos são reinseridos e sua ordem relativa pode mudar. + +Veja o post https://fpy.li/hashint["Internals of sets and dicts"] (EN) +no _http://fluentpython.com_ para mais detalhes. + +Agora vamos revisar a vasta seleção de operações oferecidas pelos conjuntos. + +[[set_ops_sec]] +==== Operações de conjuntos + +A <> dá((("dictionaries and sets", "set operations", id="DASset03-ops")))((("sets", +"set operations", id="Soper03")))((("UML class diagrams", "simplified for MutableSet and superclasses"))) +uma visão geral dos métodos disponíveis em conjuntos mutáveis e imutáveis. +Muitos deles são métodos especiais que sobrecarregam operadores, como `&` e `>=`. +A <> mostra os operadores matemáticos de conjuntos que tem +operadores ou métodos correspondentes no Python. +Note que alguns operadores e métodos realizam mudanças internas sobre o conjunto alvo +(por exemplo, `&=`, `difference_update`, etc.). +Tais operações não fazem sentido no mundo ideal dos conjuntos matemáticos, +e também não são implementadas em `frozenset`. + +[role="man-height4"] +[TIP] +==== +Os operadores infixos na <> exigem que os dois operandos sejam conjuntos, +mas todos os outros métodos recebem um ou mais argumentos iteráveis. +Por exemplo, para produzir a união de quatro coleções, `a`, `b`, `c`, e `d`, você pode chamar +`a.union(b, c, d)`, onde `a` precisa ser um `set`, +mas `b`, `c`, e `d` podem ser iteráveis de qualquer tipo que produza itens hashable. +Para criar um novo conjunto com a união de quatro iteráveis, +desde o Python 3.5 você pode escrever `{*a, *b, *c, *d}` ao invés de atualizar um conjunto existente, graças à +https://fpy.li/pep448[PEP 448—Additional Unpacking Generalizations (_Generalizações de Desempacotamento Adicionais_)]. + +==== + +[[set_uml]] +.Diagrama de classes UML simplificado para `MutableSet` e suas superclasses em `collections.abc` (nomes em itálico são classes e métodos abstratos; métodos de operadores reversos foram omitidos por concisão). +image::../images/flpy_0302.png[Diagrama de classe UML para `Set` e `MutableSet`] + +[[set_operators_tbl]] +.Operações matemáticas com conjuntos: esses métodos produzem um novo conjunto ou atualizam o conjunto alvo internamente, se ele for mutável +[options="header"] +|===================================================================================================================================================================================== +|Math symbol|Python operator| Method | Description +| S ∩ Z | `s & z` | `+s.__and__(z)+` | interseção de `s` e `z` +| | `z & s` | `+s.__rand__(z)+` | Operador `&` invertido +| | | `s.intersection(it, …)` | interseção de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. +| | `s &= z` | `+s.__iand__(z)+` | `s` atualizado com a interseção de `s` e `z` +| | | `s.intersection_update(it, …)` | `s` atualizado com a interseção de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. +| S ∪ Z | `s \| z` | `+s.__or__(z)+` | União de `s` e `z` +| | `z \| s` | `+s.__ror__(z)+` | `\|` invertido +| | | `s.union(it, …)` | União de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. +| | `s \|= z`| `+s.__ior__(z)+` | `s` atualizado com a união de `s` e `z` +| | | `s.update(it, …)` | `s` atualizado com a união de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. +| S \ Z | `s - z` | `+s.__sub__(z)+` | Complemento relativo ou diferença entre `s` e `z` +| | `z - s` | `+s.__rsub__(z)+` | Operador `-` invertido +| | | `s.difference(it, …)` | Diferença entre `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. +| | `s -= z` | `+s.__isub__(z)+` | `s` atualizado com a diferença entre `s` e `z` +| | | `s.difference_update(it, …)` | `s` atualizado com a diferença entre `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. +| S ∆ Z | `s ^ z` | `+s.__xor__(z)+` | Diferença simétrica (o complemento da interseção `s & z`) +| | `z ^ s` | `+s.__rxor__(z)+` | Operador `^` invertido +| | | `s.symmetric_difference(it)` | Complemento de `s & set(it)` +| | `s ^= z` | `+s.__ixor__(z)+` | `s` atualizado com a diferença simétrica de `s` e `z` +| | |`s.symmetric_difference_update(it, …)` | `s` atualizado com a diferença simétrica de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. +|===================================================================================================================================================================================== + +A <> lista predicados de conjuntos: operadores e métodos que devolvem `True` ou `False`. + +[[set_comparison_tbl]] +.Operadores e métodos de comparação de conjuntos que devolvem um booleano +[options="header"] +|=============================================================================================================== +|Math symbol|Python operator| Method | Description +| S ∩ Z = ∅ | | `s.isdisjoint(z)` | `s` e `z` são disjuntos (não tem elementos em comum) +| e ∈ S | `e in s` | `+s.__contains__(e)+` | Elemento `e` é membro de `s` +| S ⊆ Z | `s \<= z` | `+s.__le__(z)+` | `s` é um subconjunto do conjunto `z` +| | | `s.issubset(it)` | `s` é um subconjunto do conjunto criado a partir do iterável `it` +| S ⊂ Z | `s < z` | `+s.__lt__(z)+` | `s` é um subconjunto própriofootnote:[NT: Na teoria dos conjuntos, A é um _subconjunto próprio_ de B se A é subconjunto de B e A é diferente de B.] do conjunto `z` +| S ⊇ Z | `s >= z` | `+s.__ge__(z)+` | `s` é um superconjunto do conjunto `z` +| | | `s.issuperset(it)` | `s` é um superconjunto do conjunto criado a partir do iterável `it` +| S ⊃ Z | `s > z` | `+s.__gt__(z)+` | `s` é um superconjunto próprio do conjunto `z` +|=============================================================================================================== + + +Além de operadores e métodos derivados da teoria matemática dos conjuntos, +os tipos conjunto implementam outros métodos para tornar seu uso prático, resumidos na <>. + +[[set_methods_tbl]] +.Métodos adicionais de conjuntos +[options="header"] +|=================================================================================================================== +| | set | frozenset|   +| `s.add(e)` | ● | | Adiciona elemento `e` a `s` +| `s.clear()` | ● | | Remove todos os elementos de `s` +| `s.copy()` | ● | ● | Cópia rasa de `s` +| `s.discard(e)` | ● | | Remove elemento `e` de `s`, se existir +| `+s.__iter__()+` | ● | ● | Obtém iterador de `s` +| `+s.__len__()+` | ● | ● | `len(s)` +| `s.pop()` | ● | | Remove e devolve um elemento de `s`, gerando um `KeyError` se `s` estiver vazio +| `s.remove(e)` | ● | | Remove elemento `e` de `s`, gerando um `KeyError` se `e` não existir em `s` +|=================================================================================================================== + + +Isso encerra nossa visão geral dos recursos dos conjuntos. +Como prometido na <>, +vamos agora ver como dois dos tipos de views de dicionários se comportam de forma muito similar +a um `frozenset`.((("", startref="Soper03")))((("", startref="DASset03-ops"))) + + +[[set_ops_dict_views_sec]] +=== Operações de conjuntos em views de dict + +A <> mostra((("dictionaries and sets", "set operations on dict views")))((("sets", +"set operations on dict views")))(((".keys method", primary-sortas="keys method")))(((".items method", +primary-sortas="items method"))) +como os objetos view devolvidos pelos métodos `.keys()` e `.items()` +de dict são notavelmente similares a um `frozenset`. + +[[view_methods_tbl]] +.Métodos implementados por `frozenset`, `dict_keys`, e `dict_items` +[options="header"] +|======================================================================================================================== +| | frozenset | dict_keys | dict_items | Description +| `+s.__and__(z)+` | ● | ● | ● | `s & z` (interseção de `s` e `z`) +| `+s.__rand__(z)+` | ● | ● | ● | operador `&` invertido +| `+s.__contains__()+` | ● | ● | ● | `e in s` +| `s.copy()` | ● | | | Cópia rasa de `s` +| `s.difference(it, …)` | ● | | | Diferença entre `s` e os iteráveis `it`, etc. +| `s.intersection(it, …)` | ● | | | interseção de `s` e dos iteráveis `it`, etc. +| `s.isdisjoint(z)` | ● | ● | ● | `s` e `z` são disjuntos (não tem elementos em comum) +| `s.issubset(it)` | ● | | | `s` é um subconjunto do iterável `it` +| `s.issuperset(it)` | ● | | | `s` é um superconjunto do iterável `it` +| `+s.__iter__()+` | ● | ● | ● | obtém iterador para `s` +| `+s.__len__()+` | ● | ● | ● | `len(s)` +| `+s.__or__(z)+` | ● | ● | ● | `s \| z` (união de `s` e `z`) +| `+s.__ror__()+` | ● | ● | ● | Operador `\|` invertido +| `+s.__reversed__()+` | | ● | ● | Obtém iterador para `s` com a ordem invertida +| `+s.__rsub__(z)+` | ● | ● | ● | Operador `-` invertido +| `+s.__sub__(z)+` | ● | ● | ● | `s - z` (diferença entre `s` e `z`) +| `s.symmetric_difference(it)` | ● | | | Complemento de `s & set(it)` +| `s.union(it, …)` | ● | | | União de `s` e dos iteráveis`it`, etc. +| `+s.__xor__()+` | ● | ● | ● | `s ^ z` (diferença simétrica de `s` e `z`) +| `+s.__rxor__()+` | ● | ● | ● | Operador `^` invertido +|======================================================================================================================== + +Especificamente, `dict_keys` e `dict_items` implementam os métodos especiais para suportar as poderosas operações de conjuntos +`&` (interseção), `|` (união), `-` (diferença), e `^` (diferença simétrica). + +Por exemplo, usando `&` é fácil obter as chaves que aparecem em dois dicionários: + +[source, python] +---- +>>> d1 = dict(a=1, b=2, c=3, d=4) +>>> d2 = dict(b=20, d=40, e=50) +>>> d1.keys() & d2.keys() +{'b', 'd'} +---- + +Observe que o valor devolvido por `&` é um `set`. +Melhor ainda: os operadores de conjuntos em views de dicionários são compatíveis com instâncias de `set`. +Veja isso: + +[source, python] +---- +>>> s = {'a', 'e', 'i'} +>>> d1.keys() & s +{'a'} +>>> d1.keys() | s +{'a', 'c', 'b', 'd', 'i', 'e'} +---- + +[WARNING] +==== +Uma view obtida de `dict_items` só funciona como um conjunto se todos os valores naquele `dict` são hashable. +Tentar executar operações de conjuntos sobre uma view devolvida por `dict_items` que +contenha valores não-hashable gera um `TypeError: unhashable type 'T'`, sendo `T` o tipo do valor rejeitado. + +Por outro lado, uma view devolvida por `dict_keys` sempre pode ser usada como um conjunto, +pois todas as chaves são hashable—por definição. +==== + +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ê! + +Com isso, encerramos esse capítulo. + + +[role="pagebreak-before less_space"] +=== Resumo do 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 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`. +Com a nova implementação de `dict`, o `OrderedDict` não é mais tão útil quanto antes, +mas deve permanecer na biblioteca padrão para manter a compatibilidade +retroativa—e por suas características específicas ausentes em `dict`, +tal como a capacidade de levar em consideração o ordenamento das chaves em uma comparação `==`. +Também no módulo `collections` está o `UserDict`, uma classe base fácil de usar na criação de mapeamentos customizados. + +Dois métodos poderosos disponíveis na maioria dos mapeamentos são `setdefault` e `update`. +O método `setdefault` pode atualizar itens que mantenham valores mutáveis—por exemplo, +em um `dict` de valores `list`—evitando uma segunda busca pela mesma chave. +O método `update` permite inserir ou sobrescrever itens em massa a partir de qualquer outro mapeamento, +desde iteráveis que forneçam pares `(chave, valor)` até argumentos nomeados. +Os construtores de mapeamentos também usam `update` internamente, +permitindo que instâncias sejam inicializadas a partir de outros mapeamentos, +de iteráveis e de argumentos nomeados. +Desde Python 3.9 também podemos usar o operador `|=` para atualizar uma mapeamento e +o operador `|` para criar um novo mapeamento a partir a união de dois mapeamentos. + +Um gancho elegante na API de mapeamento é o método `+__missing__+`, +que permite customizar o que acontece quando uma chave não é encontrada ao se usar a sintaxe `d[k]` syntax, +que invoca `+__getitem__+`. + +O módulo `collections.abc` oferece as classes base abstratas `Mapping` e `MutableMapping` como interfaces padrão, +úteis para checagem de tipo durante a execução. +O `MappingProxyType`, do módulo `types`, +cria uma fachada imutável para um mapeamento que você precise proteger de modificações acidentais. +Existem também ABCs para `Set` e `MutableSet`. + +Views de dicionários foram uma grande novidade no Python 3, +eliminando o uso desnecessário de memória dos métodos `.keys()`, `.values()`, e `.items()` de Python 2, +que criavam listas duplicando os dados na instância alvo de `dict`. +Além disso, as classes `dict_keys` e `dict_items` suportam os operadores e métodos mais úteis de `frozenset`. + + +[[further_reading_dict]] +[role="pagebreak-before less_space"] +=== Para saber mais + +Na((("dictionaries and sets", "further reading on"))) documentação da Biblioteca Padrão de Python, +a seção +https://fpy.li/2w["collections—Tipos de dados de contêineres"] +inclui exemplos e receitas práticas para vários tipos de mapeamentos. +O código-fonte do módulo, `+Lib/collections/__init__.py+`, +é uma excelente referência para qualquer um que deseje criar novos tipos de mapeamentos +ou entender a lógica dos tipos existentes. +O capítulo 1 do +https://fpy.li/pycook3[_Python Cookbook, 3rd ed._] (O'Reilly), +de David Beazley e Brian K. Jones traz 20 receitas práticas e +perpicazes usando estruturas de dados—a maioria mostrando formas inteligentes de usar `dict`. + +Greg Gandenberger defende a continuidade do uso de `collections.OrderedDict`, +com os argumentos de que "explícito é melhor que implícito," compatibilidade retroativa, +e o fato de algumas ferramentas e bibliotecas presumirem que a ordenação das chaves de um `dict` +é irrelevante—nesse post: +https://fpy.li/3-18["Python Dictionaries Are Now Ordered. +Keep Using OrderedDict" (_Os dicionários de Python agora são ordenados. +Continue a usar OrderedDict_)] (EN). + +A https://fpy.li/pep3106[PEP 3106--Revamping dict.keys(), .values() and .items() (_Renovando dict.keys(), .values() e .items()_)] +(EN) foi onde Guido van Rossum apresentou o recurso de views de dicionário para Python 3. +No resumo, ele afirma que a ideia veio da Java Collections Framework. + +O https://fpy.li/3-19[PyPy] foi o primeiro interpretador Python a implementar a proposta de Raymond Hettinger para dicts compactos, +e eles escreverem em seu blog sobre isso, em +https://fpy.li/3-20["Faster, more memory efficient and more ordered dictionaries on PyPy" (_Dicionários mais rápidos, mais eficientes em termos de memória e mais ordenados no PyPy_)] +(EN), reconhecendo que um layout similar foi adotado no PHP 7, +como descrito em https://fpy.li/3-21[PHP's new hashtable implementation (_A nova implementação de tabelas de hash de PHP_)] +(EN). É sempre muito bom quando criadores citam trabalhos anteriores de outros. + +Na PyCon 2017, Brandon Rhodes apresentou +https://fpy.li/3-22["The Dictionary Even Mightier" (_O dicionário, ainda mais poderoso_)] +(EN), uma continuação de sua apresentação animada clássica +https://fpy.li/3-23["The Mighty Dictionary" (_O poderoso dicionário_)] +(EN)—incluindo colisões de hash animadas! +Outro vídeo atual mas mais aprofundado sobre o funcionamento interno do `dict` de Python é +https://fpy.li/3-24["Modern Dictionaries" (_Dicionários modernos_)] (EN) de Raymond Hettinger, +onde ele conta que após não conseguir convencer os mantenedores de Python sobre os dicts compactos, +ele persuadiu a equipe do PyPy, eles os adotaram, a ideia ganhou força, e finalmente foi +https://fpy.li/3-25[adicionada] ao CPython 3.6 por INADA Naoki. +Para saber todos os detalhes, +dê uma olhada nos extensos comentários no código-fonte do CPython para +https://fpy.li/3-26[_Objects/dictobject.c_] (EN) e no documento de design em +https://fpy.li/3-27[_Objects/dictnotes.txt_] (EN). + +A justificativa para a adição de conjuntos ao Python está documentada na +https://fpy.li/pep218[PEP 218--Adding a Built-In Set Object Type (_Adicionando um objeto embutido de tipo conjunto_)]. +Quando a PEP 218 foi aprovada, nenhuma sintaxe literal especial foi adotada para conjuntos. +Os literais `set` foram criados para Python 3 e implementados retroativamente no Python 2.7, +assim como as compreensões de `dict` e `set`. +Na PyCon 2019, apresentei +https://fpy.li/3-29["Set Practice: learning from Python's set types" (_A Prática dos Conjuntos: aprendendo com os tipos conjunto de Python_)] (EN), (https://fpy.li/3-28[video]), +descrevendo casos de uso de conjuntos em programas reais, falando sobre o design de sua API, +e sobre a implementação da https://fpy.li/3-30[`uintset`], uma classe de conjunto para elementos inteiros, +usando um vetor de bits ao invés de uma tabela de hash, +inspirada por um exemplo do capítulo 6 do excelente +https://fpy.li/38[_The Go Programming Language_ (A Linguagem de Programação Go)] (EN), +de Alan Donovan e Brian Kernighan (Addison-Wesley). + +A revista _Spectrum_, do IEEE, tem um artigo sobre Hans Peter Luhn, +um prolífico inventor que patenteou um conjunto de cartões interligados que permitiam selecionar +receitas de coquetéis a partir dos ingredientes disponíveis, entre inúmeras outras invenções, incluindo... +tabelas de hash! +Veja https://fpy.li/3-31["Hans Peter Luhn and the Birth of the Hashing Algorithm" (_Hans Peter Luhn e o Nascimento do Algoritmo de Hash_)]. + +.Ponto de Vista +**** + +[role="soapbox-title"] +*Açúcar sintático* + +Meu((("dictionaries and sets", "Soapbox discussion")))((("Soapbox sidebars", "syntactic sugar")))((("syntactic sugar"))) +amigo Geraldo Cohen certa vez observou que Python é "simples e correto." + +Puristas de linguagens de programação gostam de desprezar a sintaxe como algo desimportante. + +[quote, Alan Perlis] +____ +Syntactic sugar causes cancer of the semicolon.footnote:[NT: Explicando o trocadilho intraduzível: +"colon", em inglês, designa "a parte central do intestino grosso"; "semicolon", por outro lado, é "ponto e vírgula". +A frase diz, literalmente, "Açúcar sintático causa câncer no ponto e vírgula", +que faz sentido em inglês pela proximidade sonora das palavras.] +____ + +A sintaxe é a interface de usuário de uma linguagem de programação, então tem muita importância na prática. + +Antes de encontrar Python, fiz um pouco de programação para a Web usando Perl e PHP. +A sintaxe para mapeamentos nessas linguagens é muito útil. +Sinto muita falta dela quando tenho que usar Java ou C. + +Uma boa sintaxe para mapeamentos literais é muito conveniente para configuração, +para implementações guiadas por tabelas, e para conter dados para prototipagem e testes. +Essa foi uma das lições que os projetistas do Go aprenderam com as linguagens dinâmicas. +A falta de uma boa forma de expressar dados estruturados no código empurrou +a comunidade Java a adotar o prolixo e excessivamente complexo XML como formato de dados. + +JSON foi proposto como https://fpy.li/3-32["The Fat-Free Alternative to XML" (_A alternativa sem gordura ao XML_)] +e se tornou um imenso sucesso, substituindo XML em vários contextos. +Uma sintaxe concisa para listas e dicionários resulta em um excelente formato para troca de dados. + +PHP e Ruby imitaram a sintaxe de hash do Perl, usando `\=>` para ligar chaves a valores. +JavaScript usa `:` como Python. +Por que usar dois caracteres, quando um já é legível o bastante? + +O JSON veio de JavaScript, mas por acaso também é quase um subconjunto exato da sintaxe de Python. +O JSON é compatível com Python, exceto por usar `true`, `false`, e `null` em vez de `True`, `False`, e `None`. + +Armin Ronacher https://fpy.li/3-33[tuitou] que gosta de brincar com o espaço de nomes global de Python, +para acrescentar apelidos compatíveis com o JSON para o `True`, o `False`, e o `None` de Python, +pois daí ele pode colar trechos de JSON diretamente no console. +Sua ideia básica: + +[role="pagebreak-before less_space"] +[source, python] +---- +>>> true, false, null = True, False, None +>>> fruit = { +... "type": "banana", +... "avg_weight": 123.2, +... "edible_peel": false, +... "species": ["acuminata", "balbisiana", "paradisiaca"], +... "issues": null, +... } +>>> fruit +{'type': 'banana', 'avg_weight': 123.2, 'edible_peel': False, +'species': ['acuminata', 'balbisiana', 'paradisiaca'], 'issues': None} +---- + +A sintaxe que todo mundo agora usa para trocar dados é a sintaxe de `dict` e `list` de Python. +Agora temos uma sintaxe agradável com a conveniência da preservação da ordem de inserção. + +Simples e correto. +**** diff --git a/online/cap04.adoc b/online/cap04.adoc new file mode 100644 index 00000000..165aca13 --- /dev/null +++ b/online/cap04.adoc @@ -0,0 +1,2061 @@ +[[ch_str_bytes]] +== Texto em Unicode versus Bytes +:example-number: 0 +:figure-number: 0 + +[quote, Esther Nam e Travis Fischer] +____ +Humanos usam texto. +Computadores falam em bytes.footnote:[Slide 12 da palestra "Character Encoding and Unicode in Python" +(_Codificação de Caracteres e Unicode no Python_) na PyCon 2014 (https://fpy.li/4-1[slides] (EN), https://fpy.li/4-2[vídeo] (EN)).] +____ + + +O Python 3 introduziu uma forte distinção entre strings de texto humano e sequências de bytes em estado bruto. +A conversão automática((("implicit conversion"))) de sequências de bytes para +texto Unicode ficou para trás no Python 2. +Este capítulo trata de strings Unicode, sequências de bytes, +e das codificações usadas para converter umas nas outras. + +Dependendo do que você faz com Python, pode achar que entender o Unicode não é importante. +Isso é improvável, mas mesmo que seja o caso, não há como escapar da separação entre `str` e `bytes`, +que agora exige conversões explícitas. +Como um bônus, você descobrirá que os tipos especializados de sequências binárias `bytes` e `bytearray` +oferecem recursos que a classe `str` "pau para toda obra" de Python 2 não oferecia. + +Nesse((("Unicode text versus bytes", "topics covered"))) capítulo, veremos os seguintes tópicos: + +* Caracteres, pontos de código e representações binárias +* Recursos exclusivos das sequências binárias: `bytes`, `bytearray`, e `memoryview` +* Codificando para o Unicode completo e para conjuntos de caracteres legados +* Evitando e tratando erros de codificação +* Melhores práticas para lidar com arquivos de texto +* A armadilha da codificação default e questões de E/S padrão +* Comparações seguras de texto Unicode com normalização +* Funções utilitárias para normalização, _case folding_ (equiparação maiúsculas/minúsculas) +e remoção de sinais diacríticos por força bruta +* Ordenação correta de texto Unicode com `locale` e a biblioteca _pyuca_ +* Metadados de caracteres do banco de dados Unicode +* APIs duais, que processam `str` e `bytes` + + +=== Novidades neste capítulo + +O suporte((("Unicode text versus bytes", "significant changes to"))) ao Unicode no Python 3 sempre foi muito completo e estável, +então o acréscimo mais notável é a <>, +descrevendo um utilitário de linha de comando para busca no banco de dados Unicode—uma forma de encontrar +gatinhos sorridentes ou hieroglifos do Egito antigo. + +Vale a pena mencionar que o suporte a Unicode no Windows ficou melhor e mais simples desde o Python 3.6, +como veremos na <>. + +Vamos começar então com os conceitos não-tão-novos mas fundamentais de caracteres, pontos de código e bytes. + +[NOTE] +==== +Para((("struct module")))((("binary records, parsing with struct"))) essa segunda edição, +expandi a seção sobre o módulo `struct` e o publiquei online em +https://fpy.li/4-3["Parsing binary records with struct" (_Analisando registros binários com struct_)], (EN) +no +http://fluentpython.com[fluentpython.com] (EN) o website que complementa o livro em inglês. + +Lá((("emojis", "building"))) você também vai encontrar o +https://fpy.li/4-4["Building Multi-character Emojis" (_Criando emojis multi-caractere_)] (EN), +descrevendo como combinar caracteres Unicode para criar bandeiras de países, bandeiras de arco-íris, +pessoas com tonalidades de pele diferentes e ícones de diferentes tipos de famílias. +==== + + +=== Questões de caracteres + +O((("Unicode text versus bytes", "characters and Unicode standard", id="UTVchar04"))) +conceito de "string" é simples: uma string é uma sequência de caracteres. +O problema está na definição de "caractere". + +Em 2023, a melhor definição de "caractere" que temos é um caractere Unicode. +Consequentemente, os itens que compõe um `str` de Python 3 são caracteres Unicode, +como os itens de um objeto `unicode` no Python 2. +Em contraste, os itens de uma `str` no Python 2 são bytes, assim como os itens num objeto `bytes` de Python 3. + +O padrão Unicode separa explicitamente a identidade dos caracteres de representações binárias específicas: + +* A identidade de um caractere é chamada de ((("code points")))ponto de código (__code point__). +É um número de 0 a 1.114.111 (na base 10), +formatado no padrão Unicode como 4 a 6 dígitos hexadecimais precedidos pelo prefixo "U+", de U+0000 a U+10FFFF. +Por exemplo, o ponto de código da letra A é U+0041, o símbolo do Euro é U+20AC, +e o símbolo musical da clave de sol corresponde ao ponto de código U+1D11E. +Cerca de 13% dos pontos de código possíveis têm caracteres atribuídos no Unicode 13, +a versão do padrão usada no Python 3.10. +* Os bytes específicos que representam um caractere dependem da((("encoding", "definition of"))) +codificação (_encoding_) usada. +Uma codificação, nesse contexto, é um algoritmo que converte pontos de código para sequências de bytes, e vice-versa. +O ponto de código para a letra A (U+0041) é codificado como um único byte, `\x41`, na codificação UTF-8, +ou como os bytes `\x41\x00` na codificação UTF-16LE. +Em um outro exemplo, o UTF-8 exige três bytes para codificar o símbolo do Euro (U+20AC): `\xe2\x82\xac`. +Mas no UTF-16LE o mesmo ponto de código é representado em dois bytes: `\xac\x20`. + +Converter pontos de código para bytes é _codificar_; +converter bytes para pontos de código é((("decoding", "definition of"))) _decodificar_. +Veja o <>. + +[[ex_encode_decode]] +.Codificando e decodificando +==== +[source, python] +---- +>>> s = 'café' +>>> len(s) # <1> +4 +>>> b = s.encode('utf8') # <2> +>>> b +b'caf\xc3\xa9' # <3> +>>> len(b) # <4> +5 +>>> b.decode('utf8') # <5> +'café' +---- +==== +<1> A `str` `'café'` tem quatro caracteres Unicode. +<2> Codifica `str` para `bytes` usando a codificação UTF-8. +<3> `bytes` literais são prefixados com um `b`. +<4> `bytes` `b` tem cinco bytes (o ponto de código para "é" é codificado com dois bytes em UTF-8). +<5> Decodifica `bytes` para `str` usando a codificação UTF-8. + +[TIP] +==== +Um jeito fácil de memorizar a distinção entre `.decode()` e `.encode()` é se convencer +que sequências de bytes podem ser enigmáticos dumps de código de máquina, +ao passo que objetos `str` Unicode são texto "humano". +Daí que faz sentido _decodificar_ `bytes` em `str`, para obter texto legível por seres humanos, +e _codificar_ `str` em `bytes`, para armazenamento ou transmissão. +==== + +Apesar do `str` de Python 3 ser praticamente igual ao tipo `unicode` de Python 2 com um novo nome, +o `bytes` de Python 3 não é meramente o velho `str` renomeado, +e há também o tipo estreitamente relacionado `bytearray`. +Então vale a pena examinar os tipos de sequências binárias antes de avançar para +questões de codificação/decodificação.((("", startref="UTVchar04"))) + + +=== Os fundamentos do byte + +Os((("Unicode text versus bytes", "byte essentials", id="UTVbytes04"))) +novos tipos de sequências binárias são diferentes do `str` de Python 2 em vários aspectos. +A primeira coisa importante é que existem dois tipos embutidos básicos de((("binary sequences"))) sequências binárias: +o tipo imutável `bytes`, introduzido no Python 3, e o tipo mutável `bytearray`, +introduzido há tempos, no Python 2.6footnote:[Python 2.6 e o 2.7 também tinham um `bytes`, +mas ele era só um apelido (_alias_) para o tipo `str`.]. +A documentação de Python algumas vezes usa o termo genérico "byte string" +(_string de bytes_, na documentação em português) para se referir a `bytes` e `bytearray`. + +Cada item em `bytes` ou `bytearray` é um inteiro entre 0 e 255, +e não uma string de um caractere, como no `str` de Python 2. +Entretanto, uma fatia de uma sequência binária sempre produz +uma sequência binária do mesmo tipo—incluindo fatias de tamanho 1. Veja o <>. + +[[ex_bytes_bytearray]] +.Uma sequência de cinco bytes, como `bytes` e como `bytearray` +==== +[source, python] +---- +>>> cafe = bytes('café', encoding='utf_8') <1> +>>> cafe +b'caf\xc3\xa9' +>>> cafe[0] <2> +99 +>>> cafe[:1] <3> +b'c' +>>> cafe_arr = bytearray(cafe) +>>> cafe_arr <4> +bytearray(b'caf\xc3\xa9') +>>> cafe_arr[-1:] <5> +bytearray(b'\xa9') +---- +==== +<1> `bytes` pode ser criado a partir de uma `str`, dada uma codificação. +<2> Cada item é um inteiro em `range(256)`. +<3> Fatias de `bytes` também são `bytes`—mesmo fatias de um único byte. +<4> A sintaxe literal para `bytearray` é `bytearray(…)` com um literal `bytes` como argumento. +<5> Uma fatia de `bytearray` também é um `bytearray`. + +[WARNING] +==== +O fato de `my_bytes[0]` obter um `int` mas `my_bytes[:1]` devolver uma sequência de `bytes` de tamanho 1 +só é surpreendente porque estamos acostumados com o tipo `str` de Python, onde `s[0] == s[:1]`. +Para todos os outros tipos de sequência no Python, um item não é o mesmo que uma fatia de tamanho 1. +==== + +Apesar de sequências binárias serem na verdade sequências de inteiros, +sua notação literal reflete o fato delas frequentemente embutirem texto ASCII. +Assim, quatro formas diferentes de apresentação são utilizadas, dependendo do valor de cada byte: + +* Para bytes com código decimais de 32 a 126—do espaço ao `~` (til)—é usado o próprio caractere ASCII. +* Para os bytes correspondendo a tab, quebra de linha, carriage return (CR) e `\`, +são usadas as sequências de escape `\t`, `\n`, `\r`, e `\\`. +* Se os dois delimitadores de string, `'` e `"`, aparecem na sequência de bytes, +a sequência inteira é delimitada com `'`, e qualquer `'` dentro da sequência é precedida do caractere de escape, +assim `\'`.footnote:[Trívia: O caractere ASCII "aspas simples", +que por default Python usa como delimitador de strings, +na verdade se chama APOSTROPHE no padrão Unicode. +As aspas simples reais são assimétricas: a da esquerda é U+2018 e a da direita, U+2019.] +* Para qualquer outro valor do byte, é usada uma sequência de escape hexadecimal (por exemplo, `\x00` é o byte nulo). + +É por isso que no <> vemos `b'caf\xc3\xa9'`: +os primeiros três bytes, `b'caf'`, estão na faixa de impressão do ASCII, ao contrário dos dois últimos. + +Tanto `bytes` quanto `bytearray` suportam todos os métodos de `str`, exceto aqueles relacionados a formatação (`format`, `format_map`) +e aqueles que dependem de dados Unicode, incluindo `casefold`, `isdecimal`, `isidentifier`, `isnumeric`, `isprintable`, e `encode`. +Isso significa que você pode usar os métodos conhecidos de string, +como `endswith`, `replace`, `strip`, `translate`, `upper` e dezenas de outros, +com sequências binárias—mas com argumentos `bytes` em vez de `str`. +Além disso, as funções de expressões regulares no módulo `re` também funcionam com sequências binárias, +se a regex for compilada a partir de uma sequência binária ao invés de uma `str`. +Desde o Python 3.5, o operador `%` voltou a funcionar com sequências binárias.footnote:[Ele não funcionava de Python 3.0 ao 3.4, +causando muitas dores de cabeça nos desenvolvedores que lidam com dados binários. +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 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: + +[source, python] +---- +>>> bytes.fromhex('31 4B CE A9') +b'1K\xce\xa9' +---- + +As outras formas de criar instâncias de `bytes` ou `bytearray` são chamadas a seus construtores com: + +* Uma `str` e um argumento nomeado `encoding` +* Um iterável que forneça itens com valores entre 0 e 255 +* Um objeto que implemente o protocolo de buffer (por exemplo `bytes`, `bytearray`, `memoryview`, +`array.array`), que copia os bytes do objeto fonte para a recém-criada sequência binária + +[WARNING] +==== +Até Python 3.5, era possível chamar `bytes` ou `bytearray` com um único inteiro, +para criar uma sequência daquele tamanho inicializada com bytes nulos. +Essa assinatura for descontinuada no Python 3.5 e removida no Python 3.6. +Veja a https://fpy.li/pep467[PEP 467--Minor API improvements for binary sequences +(_Pequenas melhorias na API para sequências binárias_) (EN)]. +==== + +Criar uma sequência binária a partir de um objeto tipo buffer +é uma operação de baixo nível que pode envolver conversão de tipos. +Veja uma demonstração no <>. + +[[ex_buffer_demo]] +.Inicializando bytes a partir de dados brutos de um array +==== +[source, python] +---- +>>> import array +>>> numbers = array.array('h', [-2, -1, 0, 1, 2]) <1> +>>> octets = bytes(numbers) <2> +>>> octets +b'\xfe\xff\xff\xff\x00\x00\x01\x00\x02\x00' <3> +---- +==== +<1> O typecode `'h'` cria um `array` de _short integers_ (inteiros de 16 bits). +<2> `octets` mantém uma cópia dos bytes que compõem `numbers`. +<3> Estes são os 10 bytes que representam os 5 inteiros pequenos. + +Criar um objeto `bytes` ou `bytearray` a partir de qualquer fonte tipo buffer vai sempre copiar os bytes. +Já objetos `memoryview` permitem compartilhar memória entre estruturas de dados binários, como vimos na <>. + +Após essa exploração básica dos tipos de sequências de bytes de Python, +vamos ver como eles são convertidos de e para strings.((("", startref="UTVbytes04"))) + +=== Codificadores/Decodificadores básicos + +A((("Unicode text versus bytes", "basic encoders/decoders", id="UTVbasic04")))((("encoding", "basics of", +id="encod04")))((("decoding", "basics of", id="decod04"))) distribuição de Python inclui mais de 100((("codecs"))) +_codecs_ (encoders/decoders, _codificadores/decodificadores) para conversão de texto para bytes e vice-versa. +Cada codec tem um nome, como `'utf_8'`, e muitas vezes apelidos, como `'utf8'`, `'utf-8'`, e `'U8'`, +que você pode usar como o argumento de codificação em funções como +`open()`, `str.encode()`, `bytes.decode()`, e assim por diante. +O <> mostra o mesmo texto codificado como três sequências de bytes diferentes. + +[[ex_codecs]] +.A string "El Niño" codificada com três codecs, gerando sequências de bytes muito diferentes +==== +[source, python] +---- +>>> for codec in ['latin_1', 'utf_8', 'utf_16']: +... print(codec, 'El Niño'.encode(codec), sep='\t') +... +latin_1 b'El Ni\xf1o' +utf_8 b'El Ni\xc3\xb1o' +utf_16 b'\xff\xfeE\x00l\x00 \x00N\x00i\x00\xf1\x00o\x00' +---- +==== + +A <> mostra um conjunto de codecs gerando bytes a partir de caracteres +como a letra "A" e o símbolo musical da clave de sol. +Observe que as últimas três codificações usam múltiplos bytes e tamanho variável. + +[role="width-90"] +[[encodings_demo_fig]] +.Doze caracteres, seus pontos de código, e sua representação binária (em hexadecimal) em 7 codificações diferentes (asteriscos indicam que o caractere não pode ser representado naquela codificação). +image::../images/flpy_0401.png[Tabela de demonstração de codificações] + +Todos aqueles asteriscos na <> deixam claro que algumas codificações, +como o ASCII e mesmo o multi-byte GB2312, não conseguem representar todos os caracteres Unicode. +As codificações UTF, por outro lado, foram projetadas para lidar com todos os pontos de código possíveis. + +Escolhi as codificações apresentadas na <> como uma amostra representativa: + +`latin1` a.k.a. `iso8859_1`:: Importante por ser a base de outras codificações, tal como a `cp1252` e o próprio Unicode +(observe que os valores binários do `latin1` aparecem nos bytes do `cp1252` e nos pontos de código). + +`cp1252`:: Um superconjunto útil de `latin1`, criado pela Microsoft, +acrescentando símbolos convenientes como as aspas curvas e o € (euro); +alguns aplicativos de Windows chamam essa codificação de "ANSI", mas ela nunca foi um padrão ANSI real. + +`cp437`:: O conjunto de caracteres original do IBM PC, com caracteres de desenho de caixas. +Incompatível com o `latin1`, que surgiu depois. + +`gb2312`:: Padrão legado para codificar ideogramas chineses simplificados usados na República da China; +uma das várias codificações criadas para línguas asiáticas. + +`utf-8`:: A codificação de 8 bits mais comum na Web. Em julho de 2021, o +https://fpy.li/4-5[W³ Techs: Usage statistics of character encodings for websites] +informava que 97% dos sites usam UTF-8, um grande avanço sobre os 81,4% de setembro de 2014, +quando escrevi este capítulo na primeira edição. + +`utf-16le`:: Uma forma do esquema de codificação UTF de 16 bits; todas as codificações UTF-16 +suportam pontos de código acima de U+FFFF, através de sequências de escape chamadas "pares substitutos". + + +[WARNING] +==== +A UTF-16((("UCS-2 encoding")))((("emojis", "UCS-2 versus UTF-16 encoding"))) +sucedeu a codificação de 16 bits original do Unicode 1.0—a UCS-2—há muito tempo, em 1996. +Mas a UCS-2 obsoleta ainda é usada em muitos sistemas, apesar de ter sido descontinuada +por suportar apenas pontos de código até U+FFFF. +Em 2021, mas de 57% dos pontos de código alocados ficam acima de U+FFFF, +incluindo os importantíssimos emojis. +==== + +Após completar essa revisão das codificações mais comuns, +vamos agora tratar das questões relativas a operações de codificação e +decodificação.((("", startref="UTVbasic04")))((("", startref="encod04")))((("", startref="decod04"))) + + +=== Entendendo os problemas de codificação/decodificação + +Apesar((("Unicode text versus bytes", "understanding encode/decode problems", +id="UTVunder04")))((("encoding", "understanding encode/decode problems", +id="Eunderst04")))((("decoding", "understanding encode/decode problems", id="Dunder04"))) +de existir uma exceção genérica, `UnicodeError`, +o erro relatado pelo Python em geral é mais específico: +ou é um `UnicodeEncodeError` (ao converter uma `str` para sequências binárias) +ou é um `UnicodeDecodeError` (ao ler uma sequência binária para uma `str`). +Carregar módulos de Python também pode gerar um `SyntaxError`, +quando a codificação da fonte for inesperada. +Vamos ver como tratar todos esses erros nas próximas seções. + +[role="man-height3"] +[TIP] +==== +A primeira coisa a observar quando aparece um erro de Unicode é o tipo exato da exceção. +É um `UnicodeEncodeError`, um `UnicodeDecodeError`, ou algum outro erro (por exemplo, `SyntaxError`) +mencionando um problema de codificação? +Para resolver o problema, você primeiro precisa entendê-lo. +==== + + +==== Tratando o UnicodeEncodeError + +A maioria((("UnicodeEncodeError"))) dos codecs não-UTF entendem apenas um pequeno subconjunto dos caracteres Unicode. +Ao converter texto para bytes, um `UnicodeEncodeError` será gerado se +um caractere não estiver definido na codificação alvo, +a menos que seja fornecido um tratamento especial, +passando um argumento `errors` para o método ou função de codificação. +O comportamento para tratamento de erro é apresentado no <>. + +[[ex_encoding]] +.Encoding to bytes: success and error handling +==== +[source, python] +---- +>>> city = 'São Paulo' +>>> city.encode('utf_8') <1> +b'S\xc3\xa3o Paulo' +>>> city.encode('utf_16') +b'\xff\xfeS\x00\xe3\x00o\x00 \x00P\x00a\x00u\x00l\x00o\x00' +>>> city.encode('iso8859_1') <2> +b'S\xe3o Paulo' +>>> city.encode('cp437') <3> +Traceback (most recent call last): + File "", line 1, in + File "/.../lib/python3.4/encodings/cp437.py", line 12, in encode + return codecs.charmap_encode(input,errors,encoding_map) +UnicodeEncodeError: 'charmap' codec can't encode character '\xe3' in +position 1: character maps to +>>> city.encode('cp437', errors='ignore') <4> +b'So Paulo' +>>> city.encode('cp437', errors='replace') <5> +b'S?o Paulo' +>>> city.encode('cp437', errors='xmlcharrefreplace') <6> +b'São Paulo' +---- +==== +<1> As codificações UTF lidam com qualquer `str` +<2> `iso8859_1` também funciona com a string `'São Paulo'`. +<3> `cp437` não consegue codificar o `'ã'` ("a" com til). +O método default de tratamento de erro (`'strict'`) gera um `UnicodeEncodeError`. +<4> O método de tratamento `errors='ignore'` pula os caracteres que não podem ser codificados; +isso normalmente é uma péssima ideia, levando a perda silenciosa de informação. +<5> Ao codificar, `errors='replace'` substitui os caracteres não-codificáveis por um `'?'`; +aqui também há perda de informação, mas é mais fácil perceber que algo está faltando. +<6> `'xmlcharrefreplace'` substitui os caracteres não-codificáveis por uma entidade XML. +Se você não pode usar UTF e não pode perder informação, essa é a única opção. + +[NOTE] +==== +O tratamento de erros de `codecs` é extensível. +Você pode registrar novas strings para o argumento `errors` +passando um nome e uma função de tratamento de erros para a função `codecs.register_error`. +Veja https://fpy.li/39[documentação de `codecs.register_error`] (EN). +==== + +O ASCII é um subconjunto comum a todas as codificações que conheço, +então a codificação deveria sempre funcionar se o texto for composto exclusivamente por caracteres ASCII. +Python 3.7 trouxe um novo método booleano, https://fpy.li/4-7[`str.isascii()`], +para verificar se seu texto Unicode é 100% ASCII. +Se for, você deve ser capaz de codificá-lo para bytes em qualquer codificação sem gerar um `UnicodeEncodeError`. + + +[[decode_error_sec]] +==== Tratando o UnicodeDecodeError + +Nem((("UnicodeDecodeError"))) todo byte contém um caractere ASCII válido, +e nem toda sequência de bytes é um texto corretamente codificado em UTF-8 ou UTF-16; +assim, se você presumir uma dessas codificações ao converter um sequência binária para texto, +pode receber um `UnicodeDecodeError`, se bytes inesperados forem encontrados. + +Por outro lado, várias codificações de 8 bits antigas, como a `'cp1252'`, a `'iso8859_1'` e a `'koi8_r'` +são capazes de decodificar qualquer série de bytes, incluindo ruído aleatório, sem reportar qualquer erro. +Portanto, se seu programa presumir a codificação de 8 bits errada, ele vai decodificar lixo silenciosamente. + +.Gremlins, mokibake, e tofu +[TIP] +==== +Caracteres trocados ou distorcidos são conhecidos como "gremlins" ou "mojibake" +(文字化け—"caractere transformado" em japonês). +Outro defeito comum ocorre quando a codificação está certa, +mas a fonte não tem o glifo (desenho) de um caractere. +Neste caso ele aparece como um retângulo branco apelidado de "tofu" +pelos especialistas em fontes Unicode.footnote:[Agradeço ao Felipe Sanches do Garoa Hacker Clube por +me ensinar o termo "tofu", e me contar que a https://fpy.li/5s[coleção de fontes Noto] +é um projeto permanente para juntar todos os glifos necessários para evitar tofu em textos Unicode. +"Noto" refere-se ao ideal "no tofu" (sem tofu).] +==== + +O <> ilustra a forma como o uso do codec errado pode produzir gremlins ou um `UnicodeDecodeError`. + +[[ex_decoding]] +.Decodificando de `str` para bytes: sucesso e tratamento de erro +==== +[source, python] +---- +>>> octets = b'Montr\xe9al' <1> +>>> octets.decode('cp1252') <2> +'Montréal' +>>> octets.decode('iso8859_7') <3> +'Montrιal' +>>> octets.decode('koi8_r') <4> +'MontrИal' +>>> octets.decode('utf_8') <5> +Traceback (most recent call last): + File "", line 1, in +UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 5: +invalid continuation byte +>>> octets.decode('utf_8', errors='replace') <6> +'Montr�al' +---- +==== +<1> A palavra "Montréal" codificada em `latin1`; `'\xe9'` é o byte para "é". +<2> Decodificar com Windows 1252 funciona, pois esse codec é um superconjunto de `latin1`. +<3> ISO-8859-7 foi projetado para a língua grega, então o byte `'\xe9'` é interpretado incorretamente, +mas nenhum erro é gerado. +<4> KOI8-R é foi projetado para o russo. +Agora `'\xe9'` significa a letra "И" do alfabeto cirílico. +<5> O codec `'utf_8'` detecta que `octets` não é UTF-8 válido, e gera um `UnicodeDecodeError`. +<6> Usando `'replace'` para tratamento de erro, o `\xe9` é substituído por "�" +(ponto de código #U+FFFD), o caractere oficial do Unicode chamado `REPLACEMENT CHARACTER`, +criado exatamente para representar caracteres desconhecidos. + +[[syntax_error_encoding]] +==== SyntaxError ao carregar módulos com codificação inesperada + +UTF-8((("SyntaxError"))) é a codificação default para código-fonte no Python 3, +da mesma forma que ASCII era o default no Python 2. +Se você carregar um módulo _.py_ contendo dados que não estejam em UTF-8, +sem declaração codificação, receberá uma mensagem como essa: + +---- +include::../code/04-text-byte/syntax-msg.txt[] +---- + +Como o UTF-8 está amplamente instalado em sistemas GNU/Linux e macOS, +um cenário onde isso tem mais chance de ocorrer é na abertura de um +arquivo _.py_ criado no Windows, com `cp1252`. +Observe que esse erro ocorre mesmo no Python para Windows, +pois a codificação default para fontes de Python 3 é UTF-8 em todas as plataformas. + +Para resolver esse problema, acrescente o comentário mágico `coding` no início do arquivo, como no <>. + +[[ex_ola_mundo]] +.'ola.py': um "Hello, World!" em português +==== +[source, python] +---- +# coding: cp1252 + +print('Olá, Mundo!') +---- +==== + +[TIP] +==== +Agora que o código-fonte de Python 3 não está mais limitado ao ASCII, +e por default usa a codificação UTF-8, a melhor "solução" para +código-fonte em codificações antigas como `'cp1252'` é converter tudo para UTF-8 de uma vez, +e não se preocupar com os comentários `coding`. +Se seu editor não suporta UTF-8, é hora de trocar de editor. +==== + +Suponha que você tem um arquivo de texto, seja ele código-fonte ou poesia, +mas não sabe qual codificação foi usada. +Como detectar a codificação correta? Respostas na próxima seção. + + +[[discover_encoding]] +==== Como descobrir a codificação de uma sequência de bytes + +Como((("byte sequences"))) descobrir a codificação de uma sequência de bytes? +Resposta curta: não é possível. +Você precisa ser informado. + +Alguns protocolos de comunicação e formatos de arquivo, como o HTTP e o XML, +contêm cabeçalhos que nos dizem explicitamente como o conteúdo está codificado. +Você pode ter certeza que algumas sequências de bytes não estão em ASCII, +pois elas contêm bytes com valores acima de 127, +e o modo como o UTF-8 e o UTF-16 são construídos também limita as sequências de bytes possíveis. + +.O hack do Leo para adivinhar uma decodificação UTF-8 +**** +(Os próximos parágrafos vieram de uma nota escrita pelo revisor técnico Leonardo Rochael no rascunho desse livro.) + +Pela((("UTF-8 decoding"))) forma como o UTF-8 foi projetado, +é quase impossível que uma sequência aleatória de bytes, +ou mesmo uma sequência não-aleatória de bytes de uma codificação diferente do UTF-8, +seja acidentalmente decodificada como lixo no UTF-8, ao invés de gerar um `UnicodeDecodeError`. + +As razões para isso são que as sequências de escape do UTF-8 nunca usam caracteres ASCII, +e tais sequências de escape tem padrões de bits que tornam muito difícil que dados aleatórioas sejam UTF-8 válido por acidente. + +Portanto, se você consegue decodificar alguns bytes contendo códigos > 127 como UTF-8, +a maior probabilidade é de sequência estar em UTF-8. + +Trabalhando com os serviços online brasileiros, alguns baseados em back-ends antigos, +ocasionalmente precisei implementar uma estratégia de decodificação que tentava decodificar via UTF-8 + e tratava um `UnicodeDecodeError` decodificando via `cp1252`. +Uma estratégia feia, mas efetiva. +**** + +Entretanto, considerando que as linguagens humanas também tem suas regras e restrições, +uma vez que você supõe que uma série de bytes é um((("plain text"))) _texto humano simples_, +pode ser possível intuir sua codificação usando heurística e estatística. +Por exemplo, se bytes com valor `b'\x00'` bytes forem comuns, +é provável que seja uma codificação de 16 ou 32 bits, e não um esquema de 8 bits, +pois caracteres nulos em texto simples são erros. +Quando a sequência de bytes \`b'\x20\x00'` aparece com frequência, +é mais provável que esse seja o caractere de espaço (U+0020) na codificação UTF-16LE, +e não o obscuro caractere U+2000 (`EN QUAD`)—seja lá o que for isso. + +É((("Chardet library"))) assim que o pacote +https://fpy.li/4-8["Chardet--The Universal Character Encoding Detector (_Chardet—O Detector Universal de Codificações de Caracteres_)"] +trabalha para descobrir cada uma das mais de 30 codificações suportadas. +_Chardet_ é uma biblioteca Python que pode ser usada em seus programas, +mas que também inclui um utilitário de linha de comando, `chardetect`. +Veja como ele analisa o código-fonte desse capítulo: + +[source,bash] +---- +$ chardetect 04-text-byte.asciidoc +04-text-byte.asciidoc: utf-8 with confidence 0.99 +---- + +Apesar de sequências binárias de texto codificado normalmente não trazerem dicas sobre sua codificação, +os formatos UTF podem usar um marcador de ordem dos bytes. +Isso é explicado a seguir. + + +==== BOM: um gremlin útil + +No((("BOMs (byte-order marks)"))) <>, +você pode ter notado um par de bytes extra no início de uma sequência codificada em UTF-16. +Aqui estão eles novamente: + +[source, python] +---- +>>> u16 = 'El Niño'.encode('utf_16') +>>> u16 +b'\xff\xfeE\x00l\x00 \x00N\x00i\x00\xf1\x00o\x00' +---- + +Os bytes são `b'\xff\xfe'`. Isso é um __BOM__—sigla para byte-order mark +(marcador de ordem de bytes)—indicando a ordenação de bytes "little-endian" da CPU Intel onde a codificação foi realizada. + +Em uma máquina _little-endian_, para cada ponto de código, o byte menos significativo aparece primeiro: +a letra `'E'`, ponto de código U+0045 (decimal 69), é codificado nas posições 2 e 3 dos bytes como `69` e `0`: + +[source, python] +---- +>>> list(u16) +[255, 254, 69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111, 0] +---- + +Em uma CPU _big-endian_, a codificação seria invertida; `'E'` seria codificado como `0` e `69`. + +Para evitar confusão, a codificação UTF-16 precede o texto a ser codificado com +o caractere especial invisível `ZERO WIDTH NO-BREAK SPACE` (U+FEFF). +Em um sistema _little-endian_, isso é codificado como `b'\xff\xfe'` (decimais 255, 254). +Como, por design, não existe um caractere U+FFFE em Unicode, +a sequência de bytes `b'\xff\xfe'` tem que ser o `ZERO WIDTH NO-BREAK SPACE` em uma codificação _little-endian_, +e então o codec sabe qual ordenação de bytes usar. + +Há uma variante do UTF-16--o UTF-16LE--que é explicitamente _little-endian_, +e outra que é explicitamente _big-endian_, o UTF-16BE. +Se você usá-los, um BOM não será gerado: + +[source, python] +---- +>>> u16le = 'El Niño'.encode('utf_16le') +>>> list(u16le) +[69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111, 0] +>>> u16be = 'El Niño'.encode('utf_16be') +>>> list(u16be) +[0, 69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111] +---- + +Se o BOM estiver presente, supõe-se que ele será filtrado pelo codec UTF-16, +então recebemos apenas o conteúdo textual efetivo do arquivo, +sem o `ZERO WIDTH NO-BREAK SPACE` inicial. + +O padrão Unicode diz que se um arquivo é UTF-16 e não tem um BOM, +deve-se presumir que ele é UTF-16BE (_big-endian_). +Entretanto, a arquitetura x86 da Intel é _little-endian_, +daí que há uma grande quantidade de UTF-16 _little-endian_ e sem BOM no mundo. + +Toda essa questão de ordenação dos bytes (_endianness_) +só afeta codificações que usam palavras de máquina com mais de um byte, como UTF-16 e UTF-32. +Uma grande vantagem do UTF-8 é produzir a mesma sequência independente da ordenação dos bytes, +então um BOM não é necessário. +No entanto, algumas aplicações Windows (em especial o Notepad) mesmo assim acrescentam o BOM +a arquivos UTF-8—e o Excel depende do BOM para detectar um arquivo UTF-8, +caso contrário ele presume que o conteúdo está codificado com uma página de código do Windows. +Essa codificação UTF-8 com BOM é chamada((("UTF-8-SIG encoding"))) UTF-8-SIG no registro de codecs de Python. +O caractere U+FEFF codificado em UTF-8-SIG é a sequência de três bytes `b'\xef\xbb\xbf'`. +Então, se um arquivo começa com aqueles três bytes, é provavelmente um arquivo UTF-8 com um BOM. + +[role="man-height-2-25"] +.A dica de Caleb sobre o UTF-8-SIG +[TIP] +==== +Caleb Hattingh—um dos revisores técnicos—sugere sempre usar o codec UTF-8-SIG para ler arquivos UTF-8. +Isso é inofensivo, pois o UTF-8-SIG lê corretamente arquivos com ou sem um BOM, e não devolve o BOM propriamente dito. +Para escrever arquivos, recomendo usar UTF-8, para interoperabilidade integral. +Por exemplo, scripts Python podem ser tornados executáveis em sistemas Unix, +se começarem com o comentário: `#!/usr/bin/env python3`. +Os dois primeiros bytes do arquivo precisam ser `+b'#!'+` para isso funcionar, mas o BOM quebra essa convenção. +Se você tem o requisito específico de exportar dados para aplicativos que precisam do BOM, +use o UTF-8-SIG, mas esteja ciente do que diz a +https://fpy.li/3a[documentação sobre codecs] (EN) +de Python: +"No UTF-8, o uso do BOM é desencorajado e, em geral, deve ser evitado." +==== + +Vamos agora ver como tratar arquivos de texto no +Python 3.((("", startref="UTVunder04")))((("", startref="Eunderst04")))((("", startref="Dunder04"))) + + +=== Processando arquivos de texto + +A((("Unicode text versus bytes", "handling text files", id="UTVtext04")))((("text files, handling", id="Tfile04"))) +melhor prática para lidar com E/S de texto é o((("Unicode sandwich"))) "Sanduíche de Unicode" (_Unicode sandwich_) +(<>).footnote:[A primeira vez que vi o termo "Unicode sandwich" (_sanduíche de Unicode_) +foi na excelente apresentação de Ned Batchelder, https://fpy.li/4-10["Pragmatic Unicode" (_Unicode pragmático_) (EN)] na US PyCon 2012.] +Isso significa que os `bytes` devem ser decodificados para `str` o mais cedo possível na entrada +(por exemplo, ao abrir um arquivo para leitura). +O "recheio" do sanduíche é a lógica do negócio de seu programa, +onde o tratamento do texto é feito somente com objetos `str`. +Evite codificar ou decodificar em diferentes estágios do processamento. +Na saída, as `str` são codificadas para `bytes` o mais tarde possível. +A maioria dos frameworks Web funciona assim, e raramente tocamos em `bytes` ao usá-los. +No Django, por exemplo, suas views devem produzir `str` em Unicode; +o próprio Django se encarrega de codificar a resposta para `bytes`, usando UTF-8 como default. + +Python 3 torna mais fácil seguir o conselho do sanduíche de Unicode, +pois a função `open()` decodifica na leitura e +a codifica na escrita, ao lidar com arquivos em modo texto. +Dessa forma, tudo que você recebe de `my_file.read()` e passa para `my_file.write(text)` são objetos `str`. + +Assim, usar arquivos de texto é aparentemente simples. +Mas se você confiar nas codificações default, pode acabar levando uma mordida. + +[[unicode_sandwich_fig]] +.O sanduíche de Unicode: a melhor prática para processamento de texto. +image::../images/flpy_0402.png[Diagrama do sanduíche de Unicode] + +Observe a sessão de console no <>. Você consegue ver o erro? + +[[ex_cafe_file1]] +.Uma questão de plataforma na codificação (você pode ou não ver o problema se tentar isso na sua máquina) +==== +[source, python] +---- +>>> open('cafe.txt', 'w', encoding='utf_8').write('café') +4 +>>> open('cafe.txt').read() +'café' +---- +==== + +O erro: especifiquei a codificação UTF-8 ao escrever o arquivo, mas não fiz isso na leitura, +então Python assumiu a codificação de arquivo default do Windows—página de código 1252—e +os bytes finais foram decodificados como os caracteres `'é'` ao invés de `'é'`. + +Executei o <> no Python 3.8.1, 64 bits, no Windows 10 (build 18363). +Os mesmos comandos rodando em um GNU/Linux ou um macOS recentes funcionam perfeitamente, +pois a codificação default desses sistemas é UTF-8, dando a falsa impressão que tudo está bem. +Se o argumento de codificação fosse omitido ao abrir o arquivo para escrita, +a codificação default do _locale_ seria usada, e poderíamos ler o arquivo corretamente usando a mesma codificação. +Mas aí o script geraria arquivos com bytes diferentes dependendo da plataforma, +ou mesmo das configurações do _locale_ na mesma plataforma, criando problemas de compatibilidade. + +[TIP] +==== +Código que precisa rodar em múltiplas máquinas ou múltiplas ocasiões não deve depender de defaults de codificação. +Sempre passe um argumento `encoding=` explícito ao abrir arquivos de texto, +pois o default pode mudar de uma máquina para outra ou de um dia para o outro. +==== + +Um detalhe curioso no <> é que a função `write` na primeira instrução informa que +foram escritos quatro caracteres, mas na linha seguinte são lidos cinco caracteres. +O <> é uma versão estendida do <>, e explica esse e outros detalhes. + +[[ex_cafe_file2]] +.Uma inspeção mais atenta do <> rodando no Windows revela o bug e a solução do problema +==== +[source, python] +---- +>>> fp = open('cafe.txt', 'w', encoding='utf_8') +>>> fp # <1> +<_io.TextIOWrapper name='cafe.txt' mode='w' encoding='utf_8'> +>>> fp.write('café') # <2> +4 +>>> fp.close() +>>> import os +>>> os.stat('cafe.txt').st_size # <3> +5 +>>> fp2 = open('cafe.txt') +>>> fp2 # <4> +<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='cp1252'> +>>> fp2.encoding # <5> +'cp1252' +>>> fp2.read() # <6> +'café' +>>> fp3 = open('cafe.txt', encoding='utf_8') # <7> +>>> fp3 +<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='utf_8'> +>>> fp3.read() # <8> +'café' +>>> fp4 = open('cafe.txt', 'rb') # <9> +>>> fp4 # <10> +<_io.BufferedReader name='cafe.txt'> +>>> fp4.read() # <11> +b'caf\xc3\xa9' +---- +==== +<1> Por default, `open` usa o modo texto e devolve um objeto `TextIOWrapper` com uma codificação específica. +<2> O método `write` de um `TextIOWrapper` devolve o número de caracteres Unicode escritos. +<3> `os.stat` diz que o arquivo tem 5 bytes; o UTF-8 codifica `'é'` com 2 bytes, 0xc3 e 0xa9. +<4> Abrir um arquivo de texto sem uma codificação explícita devolve um `TextIOWrapper` +com a codificação configurada para um default do locale. +<5> Um objeto `TextIOWrapper` tem um atributo de codificação que pode ser inspecionado: neste caso, `cp1252`. +<6> Na codificação `cp1252` do Windows, o byte 0xc3 é um "Ã" (A maiúsculo com til), +e 0xa9 é o símbolo de copyright. +<7> Abrindo o mesmo arquivo com a codificação correta. +<8> O resultado esperado: os mesmo quatro caracteres Unicode para `'café'`. +<9> A flag `'rb'` abre um arquivo para leitura em modo binário. +<10> O objeto devolvido é um `BufferedReader`, e não um `TextIOWrapper`. +<11> Ler do arquivo obtém bytes, como esperado. + +[TIP] +==== +Não abra arquivos de texto no modo binário, +a menos que seja necessário analisar o conteúdo do arquivo para determinar sua codificação—e mesmo assim, +você deveria estar usando o Chardet em vez de reinventar a roda (veja a <>). + +Programas comuns só deveriam usar o modo binário para abrir arquivos binários, como arquivos de imagens raster ou bitmaps. +==== + +O problema no <> vem de se confiar numa configuração default ao se abrir um arquivo de texto. +Há várias fontes de tais defaults, como mostra a próxima seção. + +[[encoding_defaults]] +==== Cuidado com os defaults de codificação + +Várias((("encoding", "encoding defaults", id="Edefault04"))) +configurações afetam os defaults de codificação para E/S no Python. +Veja o script __default_encodings.py__ script no <>. + +[[ex_default_encodings]] +.Explorando os defaults de codificação +==== +[source, python] +---- +include::../code/04-text-byte/default_encodings.py[] +---- +==== + +A saída do <> no GNU/Linux (Ubuntu 14.04 a 19.10) +e no macOS (10.9 a 10.14) é idêntica, mostrando que `UTF-8` é usado em toda parte nesses sistemas: + +[source] +---- +$ python3 default_encodings.py + locale.getpreferredencoding() -> 'UTF-8' + type(my_file) -> + my_file.encoding -> 'UTF-8' + sys.stdout.isatty() -> True + sys.stdout.encoding -> 'utf-8' + sys.stdin.isatty() -> True + sys.stdin.encoding -> 'utf-8' + sys.stderr.isatty() -> True + sys.stderr.encoding -> 'utf-8' + sys.getdefaultencoding() -> 'utf-8' + sys.getfilesystemencoding() -> 'utf-8' +---- + +No Windows, porém, a saída é o <>. + +[[ex_default_encodings_ps]] +.Codificações default, no PowerShell do Windows 10 (a saída é a mesma no cmd.exe) +==== +[source] +---- +> chcp <1> +Active code page: 437 +> python default_encodings.py <2> + locale.getpreferredencoding() -> 'cp1252' <3> + type(my_file) -> + my_file.encoding -> 'cp1252' <4> + sys.stdout.isatty() -> True <5> + sys.stdout.encoding -> 'utf-8' <6> + sys.stdin.isatty() -> True + sys.stdin.encoding -> 'utf-8' + sys.stderr.isatty() -> True + sys.stderr.encoding -> 'utf-8' + sys.getdefaultencoding() -> 'utf-8' + sys.getfilesystemencoding() -> 'utf-8' +---- +==== +<1> `chcp` mostra a página de código ativa para o console: `437`. +<2> Executando __default_encodings.py__, com a saída direcionada para o console. +<3> `locale.getpreferredencoding()` é a configuração mais importante. +<4> Arquivos de texto usam`locale.getpreferredencoding()` por default. +<5> A saída está direcionada para o console, então `sys.stdout.isatty()` é `True`. +<6> Agora, `sys.stdout.encoding` não é a mesma que a página de código informada por `chcp`! + +O suporte a Unicode no próprio Windows e no Python para Windows melhorou desde que escrevi a primeira edição deste livro. +O <> costumava informar quatro codificações diferentes no Python 3.4 rodando no Windows 7. +As codificações para `stdout`, `stdin`, e `stderr` costumavam ser +iguais à da página de código ativa informada pelo comando `chcp`, +mas agora são todas `utf-8`, graças à +https://fpy.li/pep528[PEP 528--Change Windows console encoding to UTF-8 (_Mudar a codificação do console no Windows para UTF-8_)] +(EN), implementada no Python 3.6, e ao suporte a Unicode no PowerShell do _cmd.exe_ +(desde o Windows 1809, de outubro de 2018).footnote:[Fonte: +https://fpy.li/4-11["Windows Command-Line: Unicode and UTF-8 Output Text Buffer" +(_A Linha de Comando do Windows: O Buffer de Saída de Texto para Unicode e UTF-8_)].] +É esquisito que o `chcp` e o `sys.stdout.encoding` reportem coisas diferentes quando o `stdout` +está escrevendo no console, +mas é ótimo podermos agora escrever strings Unicode sem erros de codificação no Windows—a menos que +o usuário redirecione a saída para um arquivo, como veremos adiante. +Isso não significa que todos os seus emojis((("emojis", "console font and"))) +favoritos vão aparecer: isso também depende da fonte usada pelo console. + +Outra mudança foi a https://fpy.li/pep529[PEP 529--Change Windows filesystem encoding to UTF-8 (_Mudar a codificação do sistema de arquivos do Windows para UTF-8_)], +também implementada no Python 3.6, +que mudou a codificação do sistema de arquivos (usada para representar nomes de diretórios e de arquivos), da codificação proprietária MBCS da Microsoft para UTF-8. + +Entretanto, se a saída do <> for redirecionada para um arquivo, assim... + +[source] +---- +Z:\>python default_encodings.py > encodings.log +---- + +...aí o valor de `sys.stdout.isatty()` se torna `False`, e `sys.stdout.encoding` +é determinado por `locale.getpreferredencoding()`, +`'cp1252'` naquela máquina—mas `sys.stdin.encoding` e `sys.stderr.encoding` seguem como `utf-8`. + + +[TIP] +==== +No((("\N{} (Unicode literals escape notation)")))((("Unicode literals escape notation (\N{})"))) +<>, usei a expressão de escape `'\N{}'` para literais Unicode, +escrevendo o nome oficial do caractere dentro do `\N{}`. +Isso é bastante prolixo, mas explícito e seguro: +Python gera um `SyntaxError` se o nome não existir—bem melhor que escrever um número hexadecimal que pode estar errado, +mas isso só será descoberto mais tarde. +De qualquer forma, você provavelmente vai querer escrever um comentário explicando os códigos numéricos dos caracteres, +então a verbosidade do `\N{}` é fácil de aceitar. +==== + +Isso significa que um script como o <> funciona quando está escrevendo no console, +mas pode falhar quando a saída é redirecionada para um arquivo. + +[[ex_stdout_check]] +.stdout_check.py +==== +[source, python] +---- +include::../code/04-text-byte/stdout_check.py[] +---- +==== + +O <> mostra o resultado de uma chamada a `sys.stdout.isatty()`, +o valor de `sys.stdout.encoding`, e esses três caracteres: + +* `'…'` `HORIZONTAL ELLIPSIS`—existe no CP 1252 mas não no CP 437. +* `'∞'` `INFINITY`—existe no CP 437 mas não no CP 1252. +* `'㊷'` `CIRCLED NUMBER FORTY TWO`—não existe nem no CP 1252 nem no CP 437. + +Quando executo _stdout_check.py_ no PowerShell ou no _cmd.exe_, funciona como visto na <>. + +[[fig_stdout_check]] +.Executando _stdout_check.py_ no PowerShell. +image::../images/flpy_0403.png[Captura de tela do `stdout_check.py` no PowerShell] + +[role="pagebreak-before less_space"] +Apesar de `chcp` informar o código ativo como 437, `sys.stdout.encoding` é UTF-8, +então tanto `HORIZONTAL ELLIPSIS` quanto `INFINITY` são escritos corretamente. +O `CIRCLED NUMBER FORTY TWO` é substituído por um retângulo, mas nenhum erro é gerado. +Presume-se que ele seja reconhecido como um caractere válido, mas a fonte do console não tem o glifo para mostrá-lo. + + +Entretanto, quando redireciono a saída de _stdout_check.py_ para um arquivo, o resultado é o da <>. + +[[fig_stdout_check_redir]] +.Executanto _stdout_check.py_ no PowerShell, redirecionando a saída. +image::../images/flpy_0404.png["Captura de teal do `stdout_check.py` no PowerShell, redirecionando a saída"] + +O primeiro problema demonstrado pela <> é o `UnicodeEncodeError` mencionando o caractere `'\u221e'`, +porque `sys.stdout.encoding` é `'cp1252'`—uma página de código que não tem o caractere `INFINITY`. + +Lendo _out.txt_ com o comando `type`—ou um editor de Windows como o VS Code ou o Sublime Text—mostra que, +ao invés do HORIZONTAL ELLIPSIS, consegui um `'à'` (`LATIN SMALL LETTER A WITH GRAVE`). +Acontece que o valor binário 0x85 no CP 1252 significa `'…'`, mas no CP 437 o mesmo valor binário representa o `'à'`. +Então, pelo visto, a página de código ativa tem alguma importância, não de uma forma razoável ou útil, +mas como uma explicação parcial para uma experiência ruim com o Unicode. + + +[NOTE] +==== +Para realizar esses experimentos, usei um laptop configurado para o mercado norte-americano, +rodando Windows 10 OEM. +Versões de Windows localizadas para outros países podem ter configurações de codificação diferentes. +No Brasil, por exemplo, o console do Windows usa a página de código 850 por default--e não a 437. +==== + +Para encerrar esse enlouquecedor tópico de codificações default, +vamos dar uma última olhada nas diferentes codificações no <>: + +* Se você omitir o argumento `encoding` ao abrir um arquivo, +o default é dado por `locale.getpreferredencoding()` (`'cp1252'` no <>). + +* Antes de Python 3.6, a codificação de `sys.stdout|stdin|stderr` +costumava ser determinada pela variável do ambiente +https://fpy.li/3b[`PYTHONIOENCODING`]—agora +essa variável é ignorada, a menos que +https://fpy.li/3c[`PYTHONLEGACYWINDOWSSTDIO`] +seja definida como uma string não-vazia. +Caso contrário, a codificação da E/S padrão será UTF-8 para E/S interativa, ou definida por +`locale.getpreferredencoding()`, se a entrada e a saída forem redirecionadas para ou de um arquivo. + +* `sys.getdefaultencoding()` é usado internamente pelo Python em conversões implícitas de +dados binários de ou para `str`. Não há suporte para mudar essa configuração. + +* `sys.getfilesystemencoding()` é usado para codificar/decodificar nomes de arquivo +(mas não o conteúdo dos arquivos). +Ele é usado quando `open()` recebe um argumento `str` para um nome de arquivo; +se o nome do arquivo é passado como um argumento `bytes`, +ele é entregue sem modificação para a API do sistema operacional. + +[NOTE] +==== +Já faz muito anos que, no GNU/Linux e no macOS, todas essas codificações são definidas como UTF-8 por default, +então a E/S entende e exibe todos os caracteres Unicode. +No Windows, não apenas codificações diferentes são usadas no mesmo sistema, +elas também são, normalmente, páginas de código como `'cp850'` ou `'cp1252'`, que suportam só o ASCII +com 127 caracteres adicionais (que por sua vez são diferentes de uma codificação para a outra). +Assim, usuários de Windows têm mais chances de encontrar erros de codificação. +==== + +Resumindo, a configuração de codificação mais importante devolvida por `locale.getpreferredencoding()` +é a default para abrir arquivos de texto e para `sys.stdout/stdin/stderr`, +quando eles são redirecionados para arquivos. +Entretanto, a +https://fpy.li/3d[documentação] diz (em parte): + +[quote] +____ +`locale.getpreferredencoding(do_setlocale=True)`:: Retorna a codificação da localidade usada para dados de texto, +de acordo com as preferências do usuário. +As preferências do usuário são expressas de maneira diferente em sistemas diferentes e +podem não estar disponíveis programaticamente em alguns sistemas, +portanto, essa função retorna apenas um palpite. [...] +____ + +Assim, o melhor conselho sobre defaults de codificação é: não confie neles. + +Você evitará muitas dores de cabeça se seguir o conselho do sanduíche de Unicode, +e sempre tratar codificações de forma explícita em seus programas. +Infelizmente, o Unicode é trabalhoso mesmo se você converter seus `bytes` para `str` corretamente. +As duas próximas seções tratam de assuntos que são simples no reino do ASCII, +mas ficam muito complexos no planeta Unicode: normalização de texto +(isto é, transformar o texto em uma representação uniforme para comparações) +e ordenação.((("", startref="Edefault04")))((("", startref="Tfile04")))((("", startref="UTVtext04"))) + + +[[normalizing_unicode]] +=== Normalizando o Unicode para comparações confiáveis + +Comparações de strings((("Unicode text versus bytes", "normalizing Unicode for reliable comparisons", +id="UTVnormal04")))((("strings", "normalizing Unicode for reliable comparisons", id="Snormal04"))) +são complicadas porque o Unicode tem caracteres combinantes (_combining characters_): +sinais diacríticos e outras marcações que são sobrepostas ao caractere anterior, +ambos aparecendo juntos como um só caractere quando impressos. + +Por exemplo, a palavra "café" pode ser composta de duas formas, +usando quatro ou cinco pontos de código, mas o resultado parece exatamente o mesmo: + +[source, python] +---- +>>> s1 = 'café' +>>> s2 = 'cafe\N{COMBINING ACUTE ACCENT}' +>>> s1, s2 +('café', 'café') +>>> len(s1), len(s2) +(4, 5) +>>> s1 == s2 +False +---- + +Colocar `COMBINING ACUTE ACCENT` (U+0301) após o "e" resulta em "é". +No padrão Unicode, sequências como `'é'` e `'e\u0301'` são chamadas de "equivalentes canônicas", +e se espera que as aplicações as tratem como iguais. +Mas Python vê duas sequências de pontos de código diferentes, e não as considera iguais. + +A solução é a `unicodedata.normalize()`. +O primeiro argumento para essa função é uma dessas quatro strings: `'NFC'`, `'NFD'`, `'NFKC'`, e `'NFKD'`. +Vamos começar pelas duas primeiras. + +A Forma Normal C (NFC)((("Normalization Form C (NFC)"))) combina os ponto de código para +produzir a string equivalente mais curta, enquanto a NFD decompõe, +expandindo os caracteres compostos em caracteres base e separando caracteres combinados. +Ambas as normalizações fazem as comparações funcionarem da forma esperada, como mostra o próximo exemplo: + +[source, python] +---- +>>> from unicodedata import normalize +>>> s1 = 'café' +>>> s2 = 'cafe\N{COMBINING ACUTE ACCENT}' +>>> len(s1), len(s2) +(4, 5) +>>> len(normalize('NFC', s1)), len(normalize('NFC', s2)) +(4, 4) +>>> len(normalize('NFD', s1)), len(normalize('NFD', s2)) +(5, 5) +>>> normalize('NFC', s1) == normalize('NFC', s2) +True +>>> normalize('NFD', s1) == normalize('NFD', s2) +True +---- + +Drivers de teclado normalmente geram caracteres compostos, +então o texto digitado pelos usuários estará na NFC por default. +Entretanto, por segurança, +pode ser melhor normalizar as strings com `normalize('NFC', user_text)` antes de salvá-las. +A NFC também é a forma de normalização recomendada pelo W3C em +https://fpy.li/4-15["Character Model for the World Wide Web: String Matching and Searching" +(_Modelo de Caracteres para a World Wide Web: Casamento de Strings e Busca_)] (EN). + +Alguns caracteres singulares são normalizados pela NFC em um outro caractere singular. +O símbolo para o ohm (Ω), a unidade de medida de resistência elétrica, +é normalizado para a letra grega ômega maiúscula. +Eles são visualmente idênticos, mas diferentes quando comparados, +então a normalização pode evitar surpresas: + +[source, python] +---- +>>> from unicodedata import normalize, name +>>> ohm = '\u2126' +>>> name(ohm) +'OHM SIGN' +>>> ohm_c = normalize('NFC', ohm) +>>> name(ohm_c) +'GREEK CAPITAL LETTER OMEGA' +>>> ohm == ohm_c +False +>>> normalize('NFC', ohm) == normalize('NFC', ohm_c) +True +---- + +As outras duas formas de normalização são a NFKC e a NFKD, a letra K significando "compatibilidade". +Essas são formas mais fortes de normalização, afetando os assim chamados "caracteres de compatibilidade". +Apesar de um dos objetivos do Unicode ser a existência de um único ponto de +código "canônico" para cada caractere, +alguns caracteres aparecem mais de uma vez, para manter compatibilidade com padrões pré-existentes. +Por exemplo, o `MICRO SIGN`, `µ` (`U+00B5`), +foi adicionado para permitir a conversão bi-direcional com o `latin1`, que o inclui, +apesar do mesmo caractere ser parte do alfabeto grego com o ponto de código `U+03BC` (`GREEK SMALL LETTER MU`). +Assim, o símbolo de micro é considerado um "caractere de compatibilidade". + +Nas formas NFKC e NFKD, cada caractere de compatibilidade é substituído por uma "decomposição de compatibilidade" +de um ou mais caracteres, que é considerada a representação "preferencial", +mesmo se ocorrer alguma perda de formatação—idealmente, +a formatação deveria ser responsabilidade de alguma marcação externa, não parte do Unicode. +Para exemplificar, a decomposição de compatibilidade da fração um meio, `'½'` (`U+00BD`), +é a sequência de três caracteres `'1/2'`, e a decomposição de compatibilidade do símbolo de micro, +`'µ'` (`U+00B5`), é o mu minúsculo, `'μ'` (`U+03BC`).footnote:[Curiosamente, +o símbolo de micro é considerado um "caractere de compatibilidade", +mas o símbolo de ohm não. +O resultado disso é que a NFC não toca no símbolo de micro, +mas muda o símbolo de ohm para ômega maiúsculo, +ao passo que a NFKC e a NFKD mudam tanto o ohm quanto o micro para caracteres gregos.] + + +É assim que a NFKC funciona na prática: + +[source, python] +---- +>>> from unicodedata import normalize, name +>>> half = '\N{VULGAR FRACTION ONE HALF}' +>>> print(half) +½ +>>> normalize('NFKC', half) +'1⁄2' +>>> for char in normalize('NFKC', half): +... print(char, name(char), sep='\t') +... +1 DIGIT ONE +⁄ FRACTION SLASH +2 DIGIT TWO +>>> four_squared = '4²' +>>> normalize('NFKC', four_squared) +'42' +>>> micro = 'µ' +>>> micro_kc = normalize('NFKC', micro) +>>> micro, micro_kc +('µ', 'μ') +>>> ord(micro), ord(micro_kc) +(181, 956) +>>> name(micro), name(micro_kc) +('MICRO SIGN', 'GREEK SMALL LETTER MU') +---- + +Ainda que `'1⁄2'` seja um substituto razoável para `'½'`, +e o símbolo de micro ser realmente a letra grega mu minúscula, converter `'4²'` para `'42'` muda o sentido. +Uma aplicação poderia armazenar `'4²'` como `'42'`, +mas a função `normalize` não sabe nada sobre formatação. +Assim, NFKC ou NFKD podem perder ou distorcer informações, +mas podem produzir representações intermediárias convenientes para buscas ou indexação. + +Infelizmente, com o Unicode tudo é sempre mais complicado do que parece à primeira vista. +Para o `VULGAR FRACTION ONE HALF`, a normalização NFKC produz 1 e 2 unidos pelo `FRACTION SLASH`, +em vez do `SOLIDUS`, também conhecido como "barra" ("slash" em inglês)—o familiar caractere com código decimal 47 em ASCII. +Portanto, buscar pela sequência ASCII de três caracteres `'1/2'` +não encontraria a sequência Unicode normalizada. + +[WARNING] +==== +As normalizações NFKC e NFKD causam perda de dados e devem ser aplicadas apenas em casos especiais, +como busca e indexação, e não para armazenamento permanente do texto. +==== + +Ao preparar texto para busca ou indexação, há outra operação útil: +_case folding_ footnote:[NT: algo como "dobra" ou "mudança" de caixa.], nosso próximo assunto. + + +==== Case Folding + +_Case folding_((("case folding"))) é essencialmente a conversão de todo o texto para minúsculas, +com algumas transformações adicionais. +A operação é suportada pelo método `str.casefold()`. + +Para qualquer string `s` contendo apenas caracteres `latin1`, `s.casefold()` +produz o mesmo resultado de `s.lower()`, com apenas duas exceções—o símbolo de micro, `'µ'`, +é trocado pela letra grega mu minúscula (que é exatamente igual na maioria das fontes) +e a letra alemã _Eszett_ (ß), também chamada "s agudo" (_scharfes S_) se torna "ss": + +[source, python] +---- +>>> micro = 'µ' +>>> name(micro) +'MICRO SIGN' +>>> micro_cf = micro.casefold() +>>> name(micro_cf) +'GREEK SMALL LETTER MU' +>>> micro, micro_cf +('µ', 'μ') +>>> eszett = 'ß' +>>> name(eszett) +'LATIN SMALL LETTER SHARP S' +>>> eszett_cf = eszett.casefold() +>>> eszett, eszett_cf +('ß', 'ss') +---- + +Há quase 300 pontos de código para os quais `str.casefold()` e `str.lower()` devolvem resultados diferentes. + +Como acontece com qualquer coisa relacionada ao Unicode, _case folding_ é um tópico complexo, +com muitos casos linguísticos especiais, mas os desenvolvedores de Python fizeram +um grande esforço para apresentar uma solução que, espera-se, funcione para a maioria dos usuários. + +Nas próximas seções vamos colocar nosso conhecimento sobre normalização para trabalhar, +desenvolvendo algumas funções utilitárias. + + +==== Funções utilitárias para casamento de texto normalizado + +Como((("normalized text matching", id="normtext04"))) vimos, é seguro usar a NFC e a NFD, +e ambas permitem comparações razoáveis entre strings Unicode. +A NFC é a melhor forma normalizada para a maioria das aplicações, +e `str.casefold()` é a opção certa para comparações indiferentes a maiúsculas/minúsculas. + +Se você precisa lidar com texto em muitas línguas diferentes, +seria muito útil acrescentar às suas ferramentas de trabalho um par de funções como `nfc_equal` e `fold_equal`, +do <>. + +[[ex_normeq]] +.normeq.py: comparação de strings Unicode normalizadas +==== +[source, python] +---- +include::../code/04-text-byte/normeq.py[] +---- +==== + +Além da normalização e do _case folding_ do Unicode—ambos partes desse padrão—algumas +vezes faz sentido aplicar transformações mais profundas, +como por exemplo mudar `'café'` para `'cafe'`. +Vamos ver quando e como na próxima seção. + + +==== "Normalização" extrema: removendo sinais diacríticos + +O((("diacritics, normalization and", id="diacritics04"))) tempero secreto da busca do Google inclui muitos truques, +mas um deles aparentemente é ignorar sinais diacríticos (acentos e cedilhas, por exemplo), +pelo menos em alguns contextos. +Remover sinais diacríticos não é uma forma regular de normalização, +pois muitas vezes muda o sentido das palavras e pode produzir falsos positivos em uma busca. +Mas ajuda a lidar com alguns fatos da vida: +as pessoas às vezes são preguiçosas ou desconhecem o uso correto dos sinais diacríticos, +e regras de ortografia mudam com o tempo, +levando acentos a desaparecerem e reaparecerem nas línguas vivas. + +Além do caso da busca, eliminar os acentos torna as URLs mais legíveis, +pelo menos nas línguas latinas. +Veja a URL do artigo da Wikipedia sobre a cidade de São Paulo: + +---- +https://en.wikipedia.org/wiki/S%C3%A3o_Paulo +---- + +O trecho `%C3%A3` é a renderização em UTF-8 de uma única letra, +o "ã" ("a" com til). A forma a seguir é mais fácil de reconhecer, mesmo com a ortografia incorreta: + +---- +https://en.wikipedia.org/wiki/Sao_Paulo +---- + +Para remover todos os sinais diacríticos de uma `str`, você pode usar uma função como a do <>. + +[[ex_shave_marks]] +.simplify.py: função para remover todas as marcações combinadas +==== +[source, python] +---- +include::../code/04-text-byte/simplify.py[tags=SHAVE_MARKS] +---- +==== +<1> Decompõe todos os caracteres em caracteres base e marcações combinadas. +<2> Filtra e retira todas as marcações combinadas. +<3> Recompõe todos os caracteres. + + +<> mostra alguns usos para `shave_marks`. + +[[ex_shave_marks_demo]] +.Dois exemplos de uso da `shave_marks` do <> +==== +[source, python] +---- +>>> order = '“Herr Voß: • ½ cup of Œtker™ caffè latte • bowl of açaí.”' +>>> shave_marks(order) +'“Herr Voß: • ½ cup of Œtker™ caffe latte • bowl of acai.”' <1> +>>> Greek = 'Ζέφυρος, Zéfiro' +>>> shave_marks(Greek) +'Ζεφυρος, Zefiro' <2> +---- +==== +<1> Apenas as letras "è", "ç", e "í" foram substituídas. +<2> Tanto "έ" quando "é" foram substituídas. + +A função `shave_marks` do <> funciona bem, mas talvez vá longe demais. +Frequentemente, a razão para remover os sinais diacríticos é transformar texto de +uma língua latina para ASCII puro, mas `shave_marks` também troca caracteres +não-latinos--como letras gregas--que nunca se tornarão ASCII apenas pela remoção de acentos. +Então faz sentido analisar cada caractere base e remover as marcações anexas +apenas se o caractere base for uma letra do alfabeto latino. É isso que o <> faz. + +[[ex_shave_marks_latin]] +.Função para remover marcações combinadas de caracteres latinos (comando de importação omitidos, pois isso é parte do módulo simplify.py do <>) +==== +[source, python] +---- +include::../code/04-text-byte/simplify.py[tags=SHAVE_MARKS_LATIN] +---- +==== +<1> Decompõe todos os caracteres em caracteres base e marcações combinadas. +<2> Pula as marcações combinadas quando o caractere base é latino. +<3> Caso contrário, mantém o caractere original. +<4> Detecta um novo caractere base e determina se ele é latino. +<5> Recompõe todos os caracteres. + +Um passo ainda mais radical substituiria os símbolos comuns em textos de línguas ocidentais +(por exemplo, aspas curvas, travessões, os círculos de _bullet points_, etc) +em seus equivalentes `ASCII`. É isso que a função `asciize` faz no <>. + +[[ex_asciize]] +.Transforma alguns símbolos tipográficos ocidentais em ASCII (este trecho também é parte do simplify.py do <>) +==== +[source, python] +---- +include::../code/04-text-byte/simplify.py[tags=ASCIIZE] +---- +==== +<1> Cria tabela de mapeamento para substituição de caracteres. +<2> Tabela de mapeamento para trocar certos caracteres por strings. +<3> Funde as tabelas de mapeamento. +<4> `dewinize` não afeta texto em `ASCII` ou `latin1`, apenas os acréscimos da Microsoft ao `latin1` no `cp1252`. +<5> Aplica `dewinize` e remove as marcações de sinais diacríticos. +<6> Substitui o _Eszett_ por "ss" (não estamos usando _case folding_ aqui, pois queremos preservar maiúsculas e minúsculas). +<7> Aplica a normalização NFKC para compor os caracteres com seus pontos de código de compatibilidade. + +O <> mostra a `asciize` em ação. + +[[ex_asciize_demo]] +.Dois exemplos usando `asciize`, do <> +==== +[source, python] +---- +>>> order = '“Herr Voß: • ½ cup of Œtker™ caffè latte • bowl of açaí.”' +>>> dewinize(order) +'"Herr Voß: - ½ cup of OEtker(TM) caffè latte - bowl of açaí."' <1> +>>> asciize(order) +'"Herr Voss: - 1⁄2 cup of OEtker(TM) caffe latte - bowl of acai."' <2> +---- +==== +[role="pagebreak-before less_space"] +<1> `dewinize` substitui as aspas curvas, os _bullets_, e o ™ (símbolo de marca registrada). +<2> `asciize` aplica `dewinize`, remove os sinais diacríticos e substitui o `'ß'`. + +[WARNING] +==== +Cada idioma tem suas próprias regras para remoção de sinais diacríticos. +Por exemplo, os alemães trocam o `'ü'` por `'ue'`. Nossa função `asciize` não é tão refinada, +então pode ou não ser adequada para seu idioma. +Contudo, ela é aceitável para o português. +==== + +Resumindo, as funções em _simplify.py_ vão bem além da normalização padrão, +e realizam uma cirurgia profunda no texto, com boas chances de mudar seu sentido. +Só você pode decidir se deve ir tão longe, conhecendo o idioma alvo, +os seus usuários e a forma como o texto transformado será utilizado. + +Isso conclui nossa discussão sobre normalização de texto Unicode. + +Vamos agora ordenar nossos pensamentos sobre ordenação no +Unicode.((("", startref="UTVnormal04")))((("", startref="Snormal04")))((("", +startref="normtext04")))((("", startref="diacritics04"))) + + +[[sorting_unicode_sec]] +=== Ordenando texto Unicode + +Python((("Unicode text versus bytes", "sorting Unicode text", id="UTVsort04"))) +ordena sequências de qualquer tipo comparando um por um os itens em cada sequência. +Para strings, isso significa comparar pontos de código. +Infelizmente, isso produz resultados inaceitáveis para qualquer um que use caracteres não-ASCII. + +Considere ordenar uma lista de frutas cultivadas no Brazil: + +[source, python] +---- +>>> fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola'] +>>> sorted(fruits) +['acerola', 'atemoia', 'açaí', 'caju', 'cajá'] +---- + +As regras de ordenação variam entre diferentes locales, +mas em português e em muitas línguas que usam o alfabeto latino, +acentos e cedilhas raramente fazem diferença na ordenação.footnote:[Sinais +diacríticos afetam a ordenação apenas nos raros casos em que eles são +a única diferença entre duas palavras—nesse caso, +a palavra com o sinal diacrítico é colocada após a palavra sem o sinal na ordenação.] +Então "cajá" é lido como "caja," e deve vir antes de "caju." + +A lista `fruits` ordenada deveria ser: + +[source, python] +---- +['açaí', 'acerola', 'atemoia', 'cajá', 'caju'] +---- + +O modo padrão de ordenar texto não-ASCII em Python é usar a função `locale.strxfrm` que, de acordo com a +https://fpy.li/3e[documentação do +módulo `locale`] "transforma uma string em uma que pode ser usada em comparações com reconhecimento de localidade." + +Para poder usar `locale.strxfrm`, você deve primeiro definir um _locale_ adequado para sua aplicação, +e rezar para que o SO o suporte. +A sequência de comando no <> pode funcionar para você. + +[[ex_locale_sort]] +._locale_sort.py_: Usando a função `locale.strxfrm` como chave de ornenamento +==== +[source, python] +---- +include::../code/04-text-byte/locale_sort.py[] +---- +==== + +Executando o <> no GNU/Linux (Ubuntu 19.10) +com o _locale_ `pt_BR.UTF-8` instalado, consigo o resultado correto: + +[source, python] +---- + +'pt_BR.UTF-8' +['açaí', 'acerola', 'atemoia', 'cajá', 'caju'] +---- + +Portanto, você precisa chamar `setlocale(LC_COLLATE, «your_locale»)` +antes de usar `locale.strxfrm` como a chave de ordenação. + +Porém, aqui vão algumas ressalvas: + +* Como as configurações de _locale_ são globais, não é recomendado chamar `setlocale` em uma biblioteca. +Sua aplicação ou framework deveria definir o _locale_ no início do processo, e não mudá-lo mais depois disso. +* O _locale_ desejado deve estar instalado no SO, +caso contrário `setlocale` gera uma exceção de `locale.Error: unsupported locale setting`. +* Você precisa saber como escrever corretamente o nome do locale. +* O _locale_ precisa ser corretamente implementado pelos desenvolvedores do SO. +Tive sucesso com o Ubuntu 19.10, mas não no macOS 10.14. +No macOS, a chamada `setlocale(LC_COLLATE, 'pt_BR.UTF-8')` devolve a string `'pt_BR.UTF-8'` sem qualquer reclamação. +Mas `sorted(fruits, key=locale.strxfrm)` produz o mesmo resultado incorreto de `sorted(fruits)`. +Também tentei os locales `fr_FR`, `es_ES`, e `de_DE` no macOS, +mas `locale.strxfrm` nunca fez seu trabalho direito.footnote:[De novo, +eu não consegui encontrar uma solução, mas encontrei outras pessoas relatando o mesmo problema. +Alex Martelli, um dos revisores técnicos, não teve problemas para usar `setlocale` e `locale.strxfrm` +em seu Mac com o macOS 10.9. Em resumo: cada caso é um caso.] + +Portanto, a solução da biblioteca padrão para ordenação internacionalizada funciona, +mas parece ter suporte adequado apenas no GNU/Linux +(talvez também no Windows, se você for um especialista). +Mesmo assim, ela depende das configurações do _locale_, criando dores de cabeça na implantação. + +Felizmente, há uma solução mais simples: a((("pyuca library"))) biblioteca _pyuca_, disponível no _PyPI_. + +==== Ordenando com o Algoritmo de Ordenação do Unicode + +James Tauber, contribuidor((("Unicode Collation Algorithm (UCA)"))) muito ativo do Django, +deve ter sentido as dores de lidar com _locale_. Ele criou a +https://fpy.li/4-17[pyuca], +uma implementação integralmente em Python do Algoritmo de Ordenação do Unicode +(UCA, sigla em inglês para _Unicode Collation Algorithm_). +O <> mostra como ela é fácil de usar. + +[[ex_pyuca_sort]] +.Utilizando o método `pyuca.Collator.sort_key` +==== +[source, python] +---- +>>> import pyuca +>>> coll = pyuca.Collator() +>>> fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola'] +>>> sorted_fruits = sorted(fruits, key=coll.sort_key) +>>> sorted_fruits +['açaí', 'acerola', 'atemoia', 'cajá', 'caju'] +---- +==== + +Isso é simples e funciona no GNU/Linux, no macOS, e no Windows, pelo menos com a minha pequena amostra. + +A `pyuca` não leva o _locale_ em consideração. +Se você precisar customizar a ordenação, +pode fornecer um caminho para uma tabela própria de ordenação para o construtor `Collator()`. +Sem qualquer configuração adicional, a biblioteca usa o https://fpy.li/4-18[_allkeys.txt_], incluído no projeto. +Esse arquivo é apenas uma cópia da +https://fpy.li/4-19[Default Unicode Collation Element Table (_Tabela Default de Ordenação de Elementos Unicode_)] do _Unicode.org_ . + +.PyICU: A recomendação do Miro para ordenação com Unicode +[TIP] +==== +(O revisor técnico Miroslav Šedivý é um poliglota e um especialista em Unicode. +Eis o que ele escreveu sobre a _pyuca_.) + +A _pyuca_((("PyICU"))) tem um algoritmo de ordenação que não respeita o padrão de ordenação de linguagens individuais. +Por exemplo, [a letra] Ä em alemão fica entre o A e o B, enquanto em sueco ela vem depois do Z. +Dê uma olhada na https://fpy.li/4-20[PyICU], +que funciona sem modificar o locale do processo. +Ela também é necessária se você quiser mudar a capitalização de iİ/ıI em turco. +A PyICU inclui uma extensão que precisa ser compilada, +então pode ser mais difícil de instalar em alguns sistemas que a _pyuca_, +que é toda feita em Python. +==== + +E por sinal, aquela tabela de ordenação é um dos muitos arquivos de dados que formam o banco de dados do Unicode, +nosso próximo assunto.((("", startref="UTVsort04"))) + +[[unicodedata_sec]] +=== O banco de dados do Unicode + +O((("Unicode text versus bytes", "Unicode database", id="UTVdatabase04"))) padrão Unicode +fornece todo um banco de dados—na forma de vários arquivos de texto estruturados—que inclui +não apenas a tabela mapeando pontos de código para nomes de caracteres, +mas também metadados sobre os caracteres individuais e como eles se relacionam. +Por exemplo, o banco de dados do Unicode registra se um caractere pode ser impresso, +se é uma letra, um dígito decimal ou algum outro símbolo numérico. +É assim que os métodos de `str` `isalpha`, `isprintable`, `isdecimal` e `isnumeric` funcionam. +`str.casefold` também usa informação de uma tabela do Unicode. + +[NOTE] +==== +A função `unicodedata.category(char)` devolve uma categoria de `char` com duas letras, +do banco de dados do Unicode. +Os métodos de alto nível de `str` são mais fáceis de usar. +Por exemplo, +https://fpy.li/3f[`label.isalpha()`] +devolve `True` se todos os caracteres em `label` +pertencerem a uma das seguintes categorias: `Lm`, `Lt`, `Lu`, `Ll`, or `Lo`. +Para descobrir o que esses códigos significam, veja +https://fpy.li/4-22["General Category"] (EN) no artigo https://fpy.li/4-23["Unicode character property"] +(EN) da Wikipedia em inglês. +==== + +[[finding_chars_sec]] +==== Encontrando caracteres por nome + +O((("emojis", "finding characters by name", id="Efind04")))((("characters", "finding Unicode by name", +id="Cfinduni04"))) módulo `unicodedata` tem funções para obter os metadados de caracteres, incluindo +`unicodedata.name()`, que devolve o nome oficial do caractere no padrão. +A <> demonstra essa função.footnote:[Aquilo é uma +imagem—não uma listagem de código—porque, no momento em que esse capítulo foi escrito, +os emojis não têm um bom suporte no sistema de publicação digital da O'Reilly.] + +[[unicodedata_name_fig]] +.Explorando `unicodedata.name()` no console de Python. +image::../images/flpy_0405.png[alt="Explorando `unicodedata.name()` no console de Python"] + +Você pode usar a função `name()` para criar aplicações que permitem aos usuários buscarem caracteres por nome. +A <> demonstra o script de linha de comando _cf.py_, +que recebe como argumentos uma ou mais palavras, +e lista os caracteres que tem aquelas palavras em seus nomes Unicode oficiais. +O código-fonte completo de _cf.py_ aparece no <>. + +[[cf_demo_fig]] +.Usando _cf.py_ para encontrar gatos sorridentes. +image::../images/flpy_0406.png[alt="Usando _cf.py_ para encontrar gatos sorridentes."] + +[WARNING] +==== +O((("emojis", "varied support for"))) suporte a emojis varia muito entre sistemas operacionais e aplicativos. +Nos últimos anos, o terminal do macOS tem oferecido o melhor suporte para emojis, +seguido por terminais gráficos GNU/Linux modernos. +O _cmd.exe_ e o PowerShell do Windows agora suportam saída Unicode, +mas enquanto escrevo essa seção, em janeiro de 2020, +eles ainda não mostram emojis—pelo menos não sem configurações adicionais. +O revisor técnico Leonardo Rochael me falou sobre um novo https://fpy.li/4-24[terminal para Windows da Microsoft], de código aberto, +que pode ter um suporte melhor a Unicode que os consoles antigos da Microsoft. +Não tive tempo de testar. +==== + +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 laço `for` aninhado e de outro `if` para implementar essa verificação + +[[ex_cfpy]] +.cf.py: o utilitário de busca de caracteres +==== +[source, python] +---- +include::../code/04-text-byte/charfinder/cf.py[] +---- +==== +<1> Configura os defaults para a faixa de pontos de código da busca. +<2> `find` aceita `query_words` e somente argumentos nomeados (opcionais) para limitar a faixa da busca, facilitando os testes. +<3> Converte `query_words` em um conjunto de strings capitalizadas. +<4> Obtém o caractere Unicode para `code`. +<5> Obtém o nome do caractere, ou `None` se o ponto de código não estiver atribuído a um caractere. +<6> Se há um nome, separa esse nome em uma lista de palavras, então verifica se o conjunto `query` é um subconjunto daquela lista. +<7> Mostra uma linha com o ponto de código no formato `U+9999`, o caractere e seu nome. + + +O módulo `unicodedata` tem outras funções interessantes. +A seguir veremos algumas delas, relacionadas a obter informação de caracteres com +significado numérico.((("", startref="Efind04")))((("", startref="Cfinduni04"))) + + +==== O sentido numérico de caracteres + +O((("characters", "numeric meaning of", id="Cnumeric04"))) módulo `unicodedata` +inclui funções para determinar se um caractere Unicode representa um número e, +se for esse o caso, seu valor numérico em termos humanos—em contraste com o número de seu ponto de código. + +O <> demonstra o uso de `unicodedata.name()` e `unicodedata.numeric()`, +junto com os métodos `.isdecimal()` e `.isnumeric()` de `str`. + +[[ex_numerics_demo]] +.Demo do banco de dados Unicode de metadados de caracteres numéricos +(as notas explicativas descrevem cada coluna da saída) +==== +[source, python] +---- +include::../code/04-text-byte/numerics_demo.py[tags=NUMERICS_DEMO] +---- +==== +<1> Ponto de código no formato `U+0000`. +<2> O caractere, centralizado em uma `str` de tamanho 6. +<3> Mostra `re_dig` se o caractere casa com a regex `r'\d'`. +<4> Mostra `isdig` se `char.isdigit()` é `True`. +<5> Mostra `isnum` se `char.isnumeric()` é `True`. +<6> Valor numérico formatado com tamanho 5 e duas casa decimais. +<7> O nome Unicode do caractere. + +Executar o <> gera a <>, +se a fonte do seu terminal incluir todos aqueles símbolos. + +[[numerics_demo_fig]] +.Terminal do macOS mostrando os caracteres numéricos e metadados correspondentes; `re_dig` significa que o caractere casa com a expressão regular `r'\d'`. +image::../images/flpy_0407.png[Captura de tela de caracteres numéricos] + +A sexta coluna da <> é o resultado da chamada a `unicodedata.numeric(char)` com o caractere. +Ela mostra que o Unicode sabe o valor numérico de símbolos que representam números. +Assim, se você quiser criar uma aplicação de planilha que suporta dígitos tamil ou numerais romanos, vá fundo! + +A <> mostra que a expressão regular `r'\d'` casa com o dígito "1" e com o dígito 3 da escrita devanágari, +mas não com alguns outros caracteres considerados dígitos pela função `isdigit`. +O módulo `re` não é tão conhecedor de Unicode quanto deveria ser. +O novo módulo `regex`, disponível no PyPI, +foi projetado para um dia substituir o `re`, +e fornece um suporte melhor ao Unicode.footnote:[Embora +não tenha se saído melhor que o `re` para identificar dígitos nessa amostra em particular.] +Voltaremos ao módulo `re` na próxima seção. + +Ao longo desse capítulo, usamos várias funções de `unicodedata`, +mas há muitas outras que não mencionamos. +Veja a documentação da biblioteca padrão para o https://fpy.li/3g[módulo `unicodedata`]. + +A seguir vamos dar uma rápida passada pelas APIs de modo dual, +com funções que aceitam argumentos `str` ou `bytes` e dão a eles tratamento especial +dependendo do tipo.((("", startref="Cnumeric04")))((("", startref="UTVdatabase04"))) + +[[dual_mode_api_sec]] +=== APIs de modo dual para str e bytes + +A biblioteca padrão de Python((("Unicode text versus bytes", "dual-mode str and bytes APIs", +id="UTVdual04")))((("strings", "dual-mode str and bytes APIs", id="Sdual04"))) +tem funções que aceitam argumentos `str` ou `bytes` e se comportam de forma diferente dependendo do tipo recebido. +Alguns exemplos podem ser encontrados nos módulos `re` e `os`. + +==== str versus bytes em expressões regulares + +Se((("regular expressions, str versus bytes in"))) você criar uma expressão regular com `bytes`, +padrões tal como `\d` e `\w` vão casar apenas com caracteres ASCII; +por outro lado, se esses padrões forem passados como `str`, +eles vão casar com dígitos Unicode ou letras além do ASCII. +O <> e a <> comparam como letras, +dígitos ASCII, superescritos e dígitos tamil casam em padrões `str` e `bytes`. + +[[ex_re_demo]] +.ramanujan.py: compara o comportamento de expressões regulares simples como `str` e como `bytes` +==== +[source, python] +---- +include::../code/04-text-byte/ramanujan.py[tags=RE_DEMO] +---- +==== +<1> As duas primeiras expressões regulares são do tipo `str`. +<2> As duas últimas são do tipo `bytes`. +<3> Texto Unicode para ser usado na busca, contendo os dígitos tamil para `1729` +(a linha lógica continua até o símbolo de fechamento de parênteses). +<4> Essa string é unida à anterior no momento da compilação +(veja https://fpy.li/3h["2.4.2. String literal concatenation" +(_Concatenação de strings literais_)] em _A Referência da Linguagem Python_). +<5> Uma string `bytes` é necessária para a busca com as expressões regulares `bytes`. +<6> O padrão `str` `r'\d+'` casa com os dígitos ASCII e tamil. +<7> O padrão `bytes` `rb'\d+'` casa apenas com os bytes ASCII para dígitos. +<8> O padrão `str` `r'\w+'` casa com letras, superescritos e dígitos tamil e ASCII. +<9> O padrão `bytes` `rb'\w+'` casa apenas com bytes ASCII para letras e dígitos. + +[[fig_re_demo]] +.Captura de tela da execução de ramanujan.py do <>. +image::../images/flpy_0408.png[Saída de ramanujan.py] + +O <> é um exemplo trivial para destacar um ponto: +você pode usar expressões regulares com `str` ou `bytes`, +mas nesse último caso os bytes fora da faixa do ASCII são tratados como caracteres que não representam dígitos nem palavras. + +Para expressões regulares `str`, há uma marcação `re.ASCII`, +que faz `\w`, `\W`, `\b`, `\B`, `\d`, `\D`, `\s`, e `\S` +casarem apenas com ASCII. +Veja a +https://fpy.li/3j[documentação do módulo `re`] para mais detalhes. + +Outro módulo importante é o `os`. + +==== str versus bytes nas funções de os + +O((("os functions, str versus bytes in"))) kernel do GNU/Linux não conhece Unicode então, no mundo real, +você pode encontrar nomes de arquivo compostos de sequências de bytes que +não são válidas em nenhum esquema razoável de codificação, e não podem ser decodificados para `str`. +Esta é uma situação comum em servidores de arquivo com clientes usando uma variedade de diferentes +SO. + +Para mitigar esse problema, todas as funções do módulo `os` que +aceitam nomes de arquivo ou caminhos podem receber seus argumentos como `str` ou `bytes`. +Se uma dessas funções é chamada com um argumento `str`, +o argumento será automaticamente convertido usando o codec informado por +`sys.getfilesystemencoding()`, +e a resposta do SO será decodificada com o mesmo codec. +Isso é quase sempre o que se deseja, mantendo a melhor prática do sanduíche de Unicode. + +Mas se você precisa lidar com (e provavelmente corrigir) nomes de arquivo que +não podem ser processados daquela forma, +você pode passar argumentos `bytes` para as funções de `os`, e receber `bytes` de volta. +Esse recurso permite que você processe qualquer nome de arquivo ou caminho, +independende de quantos gremlins encontrar. +Veja o <>. + +[[ex_listdir1]] +.`listdir` com argumentos `str` e `bytes`, e os resultados +==== +[source, python] +---- +>>> os.listdir('.') # <1> +['abc.txt', 'digits-of-π.txt'] +>>> os.listdir(b'.') # <2> +[b'abc.txt', b'digits-of-\xcf\x80.txt'] +---- +==== +<1> O segundo nome de arquivo é "digits-of-π.txt" (com a letra grega pi). +<2> Dado um argumento `byte`, `listdir` devolve nomes de arquivos como bytes: +`b'\xcf\x80'` é a codificação UTF-8 para a letra grega pi. + +Para ajudar no processamento manual de sequências `str` ou `bytes` que são nomes de arquivos ou caminhos, +o módulo `os` fornece funções especiais de codificação e decodificação, +`os.fsencode(name_or_path)` e `os.fsdecode(name_or_path)`. +Ambas as funções aceitam argumentos dos tipos `str`, `bytes` ou, +desde o Python 3.6, um objeto que implemente a interface `os.PathLike`. + +O Unicode é uma toca de coelho bem funda. +É hora de encerrar nossa exploração de `str` e `bytes`.((("", startref="UTVdual04")))((("", startref="Sdual04"))) + + +=== Resumo do capítulo + +Começamos((("Unicode text versus bytes", "overview of"))) +o capítulo descartando a noção de que `1 caractere == 1 byte`. +A medida que o mundo adota o Unicode, +precisamos manter o conceito de strings de texto separado das +sequências binárias que as representam em arquivos, e Python 3 reforça essa separação. + +Após uma breve passada pelos tipos de dados sequências +binárias—`bytes`, `bytearray`, e `memoryview`—mergulhamos na codificação e na decodificação, +com uma amostragem dos codecs importantes, +seguida por abordagens para prevenir ou lidar com os abomináveis +`UnicodeEncodeError`, `UnicodeDecodeError` e os `SyntaxError` +causados pela codificação errada em arquivos de código-fonte de Python. + +A seguir consideramos a teoria e a prática de detecção de codificação na ausência de metadados: +em teoria, não pode ser feita, mas na prática o pacote Chardet +consegue realizar esse feito para uma grande quantidade de codificações populares. +Marcadores de ordem de bytes foram apresentados como a única dica de codificação +encontrada em arquivos UTF-16 e UTF-32--algumas vezes também em arquivos UTF-8. + +Na seção seguinte, demonstramos como abrir arquivos de texto, uma tarefa fácil exceto por uma armadilha: +o argumento nomeado `encoding=` não é obrigatório quando se abre um arquivo de texto, mas deveria ser. +Se você não especificar a codificação, terminará com um programa que consegue produzir "texto puro" que +é incompatível entre diferentes plataformas, devido a codificações default conflitantes. +Expusemos então as diferentes configurações de codificação usadas pelo Python, e como detectá-las. + +Uma triste realidade para usuários de Windows é o fato dessas configurações +muitas vezes terem valores diferentes dentro da mesma máquina, +e desses valores serem mutuamente incompatíveis; +usuários do GNU/Linux e do macOS, por outro lado, vivem em um lugar mais feliz, +onde o UTF-8 é o default por (quase) toda parte. + +O Unicode fornece múltiplas formas de representar alguns caracteres, +então a normalização é um pré-requisito para a comparação de textos. +Além de explicar a normalização e o _case folding_, +apresentamos algumas funções úteis que podem ser adaptadas para as suas necessidades, +incluindo transformações drásticas como a remoção de todos os acentos. +Vimos como ordenar corretamente texto Unicode, +usando o módulo padrão `locale`—com algumas restrições—e +uma alternativa que não depende de complexas configurações de locale: +a biblioteca((("pyuca library"))) externa _pyuca_. + +Usamos o banco de dados do Unicode para programar um utilitário de linha de comando que +busca caracteres por nome--em 28 linhas de código, graças ao poder de Python. +Demos uma olhada em outros metadados do Unicode, e vimos rapidamente as APIs de modo dual, +onde algumas funções podem ser chamadas com argumentos `str` ou `bytes`, produzindo resultados diferentes. + + +=== Para saber mais + +A palestra((("Unicode text versus bytes", "further reading on"))) de Ned Batchelder na PyCon US 2012, +https://fpy.li/4-28["Pragmatic Unicode, or, How Do I Stop the Pain?" +(_Unicode Pragmático, ou, Como Eu Fiz a Dor Sumir?_)] (EN), foi marcante. +Ned é tão profissional que forneceu uma transcrição completa da palestra, além dos slides e do vídeo. + +"Character encoding and Unicode in Python: How to (╯°□°)╯︵ ┻━┻ with dignity" +(_Codificação de caracteres e o Unicode no Python: como (╯°□°)╯︵ ┻━┻ com dignidade_) +(https://fpy.li/4-1[slides], https://fpy.li/4-2[vídeo]) (EN) +foi a ótima palestra de Esther Nam e Travis Fischer na PyCon 2014, +e foi onde encontrei a concisa epígrafe desse capítulo: +"Humanos usam texto. +Computadores falam em bytes." + +Lennart Regebro--um dos revisores técnicos da primeira edição desse livro--compartilha seu +"Useful Mental Model of Unicode (UMMU)" (_Modelo Mental Útil do Unicode_) em um post curto, +https://fpy.li/4-31["Unconfusing Unicode: What Is Unicode?" (_Desconfundindo o Unicode: O Que É O Unicode?_)] (EN). +O Unicode é um padrão complexo, então o UMMU de Lennart é realmente um ponto de partida útil. + +O https://fpy.li/3k["Unicode HOWTO"] +oficial na documentação de Python aborda o assunto por vários ângulos diferentes, +de uma boa introdução histórica a detalhes de sintaxe, codecs, expressões regulares, nomes de arquivo, +e boas práticas para E/S sensível ao Unicode (isto é, o sanduíche de Unicode), +com vários links adicionais de referências em cada seção. + +O https://fpy.li/4-33[Chapter 4, "Strings" (_Capítulo 4, "Strings"_)], +do excelente livro https://fpy.li/4-34[_Dive into Python 3_] +de Mark Pilgrim (Apress), também fornece uma ótima introdução ao suporte a Unicode no Python 3. +No mesmo livro, o https://fpy.li/4-35[Capítulo 15] descreve +como a biblioteca Chardet foi portada de Python 2 para Python 3, +um valioso estudo de caso, dado que a mudança do antigo tipo `str` para o novo `bytes` +é a causa da maioria das dores da migração, +e esta é uma preocupação central em uma biblioteca projetada para detectar codificações. + +Se você conhece Python 2 mas é novo no Python 3, o artigo +https://fpy.li/4-36[_What’s New in Python 3.0_ (O que há de novo no Python 3.0)], +de Guido van Rossum, tem 15 pontos resumindo as mudanças, com vários links. +Guido inicia com uma afirmação brutal: +"Tudo o que você achava que sabia sobre dados binários e Unicode mudou." +O post de Armin Ronacher em seu blog, +https://fpy.li/4-37["The Updated Guide to Unicode on Python" +_O Guia Atualizado do Unicode no Python_], +é bastante profundo e realça algumas das armadilhas do Unicode no Python +(Armin não é um grande fã de Python 3). + +O capítulo 2 ("Strings and Text" _Strings e Texto_) do +https://fpy.li/pycook3[Python Cookbook, 3rd ed.] +(EN) (O'Reilly), de David Beazley e Brian K. Jones, tem várias receitas tratando de normalização de Unicode, +sanitização de texto, e execução de operações orientadas para texto em sequências de bytes. +O capítulo 5 trata de arquivos e E/S, e inclui a "Recipe 5.17. +Writing Bytes to a Text File" +(_Receita 5.17. +Escrevendo Bytes em um Arquivo de Texto_), +mostrando que sob qualquer arquivo de texto há sempre uma sequência binária +que pode ser acessada diretamente quando necessário. +Mais tarde no mesmo livro, o módulo `struct` é usado em +"Recipe 6.11. +Reading and Writing Binary Arrays of Structures" (_Receita 6.11. +Lendo e Escrevendo Arrays Binárias de Estruturas_). + +O blog "Python Notes" de Nick Coghlan tem dois posts muito relevantes para esse capítulo: +https://fpy.li/4-38["Python 3 and ASCII Compatible Binary Protocols" +(_Python 3 e os Protocolos Binários Compatíveis com ASCII_)] (EN) e +https://fpy.li/4-39["Processing Text Files in Python 3" (_Processando Arquivos de Texto em Python 3_)] (EN). +Fortemente recomendado. + +Uma lista de codificações suportadas pelo Python está disponível em +https://fpy.li/3m["Standard Encodings"] (EN), +na documentação do módulo `codecs`. +Se você precisar obter aquela lista de dentro de um programa, +pode ver como isso é feito no script https://fpy.li/4-41[__/Tools/unicode/listcodecs.py__], +que acompanha o código-fonte do CPython. + +Os livros +https://fpy.li/4-42[Unicode Explained (Unicode Explicado)] (EN), +de Jukka K. Korpela (O'Reilly) e https://fpy.li/4-43[_Unicode Demystified_ (Unicode Desmistificado)], +de Richard Gillam (Addison-Wesley) não são específicos sobre Python, +mas foram muito úteis para meu estudo dos conceitos do Unicode. +https://fpy.li/4-44[_Programming with Unicode_ (Programando com Unicode)], de Victor Stinner, +é um livro gratuito e publicado pelo próprio autor tratando de Unicode em geral, +bem como de ferramentas e APIs no contexto dos principais sistemas operacionais +e algumas linguagens de programação, incluindo Python. + +As páginas do W3C +https://fpy.li/4-45["Case Folding: An Introduction" (_Case Folding: Uma Introdução_)] (EN) e +https://fpy.li/4-15["Character Model for the World Wide Web: String Matching" +(_O Modelo de Caracteres para a World Wide Web: Casamento de Strings_)] (EN) +tratam de conceitos de normalização, +a primeira uma suave introdução e a segunda uma nota de um grupo de trabalho escrita +no seco jargão dos padrões—o mesmo tom do +https://fpy.li/4-47["Unicode Standard Annex #15--Unicode Normalization Forms" +(_Anexo 15 do Padrão Unicode—Formas de Normalização do Unicode_)] (EN). +A seção +https://fpy.li/4-48["Frequently Asked Questions, Normalization" (_Perguntas Frequentes, Normalização_)] (EN) +do +https://fpy.li/4-49[_Unicode.org_] +é mais fácil de ler, bem como o https://fpy.li/4-50["NFC FAQ"] (EN) de +Mark Davis--autor de vários algoritmos do Unicode e presidente do Unicode Consortium quando essa seção foi escrita. + +Em 2016, o((("emojis", "in the Museum of Modern Art"))) Museu de Arte Moderna (MoMA) de New York adicionou à sua coleção +https://fpy.li/4-51[o emoji original] (EN), +os 176 emojis desenhados por Shigetaka Kurita em 1999 para a NTT DOCOMO—a provedora de telefonia móvel japonesa. +Indo mais longe no passado, a https://fpy.li/4-52[_Emojipedia_] (EN) publicou o artigo +https://fpy.li/4-53["Correcting the Record on the First Emoji Set" (_Corrigindo o Registro [Histórico\] +sobre o Primeiro Conjunto de Emojis_)] (EN), atribuindo à SoftBank do Japão o mais antigo conjunto conhecido de emojis, +implantado em celulares em 1997. +O conjunto da SoftBank é a fonte de 90 emojis que hoje fazem parte do Unicode, +incluindo o U+1F4A9 (`PILE OF POO`). +O https://fpy.li/4-54[_emojitracker.com_], de Matthew Rothenberg, +é um painel ativo mostrando a contagem do uso de emojis no Twitter, atualizado em tempo real. +Quando escrevo isso, `FACE WITH TEARS OF JOY` (U+1F602) é o emoji mais popular no Twitter, com mais de +3.313.667.315 ocorrências registradas. + +.Ponto de vista +**** + +[role="soapbox-title"] +*Nomes não-ASCII no código-fonte: você deveria usá-los?* + +Python 3((("Soapbox sidebars", "non-ASCII names in source code")))((("Unicode text versus bytes", "Soapbox discussion"))) +permite identificadores não-ASCII no código-fonte: + +[source, python] +---- +>>> ação = 'PBR' # ação = stock +>>> ε = 10**-6 # ε = epsilon +---- + +Algumas pessoas não gostam dessa ideia. +O argumento mais comum é que se limitar aos caracteres ASCII torna a leitura e a edição so código mais fácil para todo mundo. +Esse argumento erra o alvo: você quer que seu código-fonte seja legível e editável pela audiência pretendida, +e isso pode não ser "todo mundo". +Se o código pertence a uma corporação multinacional, ou se é um código aberto e você deseja contribuidores de todo o mundo, +os identificadores devem ser em inglês, e então tudo o que você precisa é do ASCII. + +Mas se você é uma professora no Brasil, seus alunos vão achar mais fácil ler código com +variáveis e nomes de função em português, e escritos corretamente. +E eles não terão nenhuma dificuldade para digitar as cedilhas e as vogais acentuadas em seus teclados localizados. + +Agora que Python pode interpretar nomes em Unicode, e que o UTF-8 é a codificação padrão para código-fonte, +não vejo motivo para codificar identificadores em português sem acentos, como fazíamos no Python 2, +por necessidade—a menos que seu código tenha que rodar também no Python 2. +Se os nomes estão em português, excluir os acentos não vai tornar o código mais legível para ninguém. + +Esse é meu ponto de vista como um brasileiro falante de português, +mas acredito que se aplica além de fronteiras e a outras culturas: +escolha a linguagem humana que torna o código mais legível para sua equipe, +e então use todos os caracteres necessários para a ortografia correta. + +[role="soapbox-title"] +*O que é "texto puro"?* + +Para((("Soapbox sidebars", "plain text"))) qualquer um que lide diariamente com texto em línguas diferentes do inglês + "texto puro" não significa "ASCII". O https://fpy.li/4-55[Glossário do Unicode] + (EN) define((("plain text"))) _texto puro_ dessa forma: + +[quote] +____ +Texto codificado por computador que consiste apenas de uma sequência de pontos de código de um dado padrão, +sem qualquer outra informação estrutural ou de formatação. +____ + +Essa definição começa muito bem, mas não concordo com a parte após a vírgula. +HTML é um ótimo exemplo de um formato de texto puro que inclui informação estrutural e de formatação. +Mas ele ainda é texto puro, porque cada byte em um arquivo desse tipo está lá para representar um caractere de texto, +em geral usando UTF-8. +Não há bytes com significado não-textual, +como você encontra em documentos _.png_ ou _.xls_, +onde a maioria dos bytes representa valores binários compactos, +como valores RGB ou números de ponto flutuante. +No texto puro, números são representados como sequências de caracteres de dígitos. + +Estou escrevendo esse livro em um formato de texto puro chamado—ironicamente— https://fpy.li/4-56[AsciiDoc], +que é parte do conjunto de ferramentas da plataforma de publicação de livros +https://fpy.li/4-57[Atlas] +da O'Reilly. +Os arquivos fonte de AsciiDoc são texto puro, mas aceitam UTF-8, e não só ASCII. +Se fosse o contrário, escrever esse capítulo teria sido realmente doloroso. +Apesar do nome equivocado, o AsciiDoc é muito bom. + +O mundo do Unicode está em constante expansão e, nas fronteiras, as ferramentas de apoio nem sempre existem. +Nem todos os caracteres que eu queria exibir estavam disponíveis nas fontes usadas para renderizar o livro. +Por isso tive que usar capturas de tela do terminal em alguns exemplos desse capítulo. +Por outro lado, os terminais do Ubuntu e do macOS exibem a maioria do texto Unicode +muito bem—incluindo os caracteres japoneses para a palavra "mojibake": 文字化け. + + +[role="soapbox-title"] +*Como os ponto de código numa str são representados na memória?* + +A((("Soapbox sidebars", "code points")))((("code points"))) documentação oficial de Python +evita falar sobre como os pontos de código de uma `str` são armazenados na memória. +Realmente, é um detalhe de implementação. +Em teoria, não importa: qualquer que seja a representação interna, +toda `str` precisa ser codificada para `bytes` na saída. + +Na memória, Python 3 armazena cada `str` como uma sequência de pontos de código, +usando um número fixo de bytes por ponto de código, +para permitir o acesso direto eficiente a qualquer caractere ou fatia. + +Desde o Python 3.3, ao criar um novo objeto `str` +o interpretador verifica os caracteres no objeto, +e escolhe o layout de memória mais econômico que seja adequado para aquela `str` em particular: +se existirem apenas caracteres na faixa `latin1`, aquela `str` vai usar apenas um byte por ponto de código. +Caso contrário, podem ser usados dois ou quatro bytes por ponto de código, dependendo da `str`. +Isso é uma simplificação; para saber todos os detalhes, dê uma olhada an +https://fpy.li/pep393[PEP 393--Flexible String Representation (_Representação Flexível de Strings_)] (EN). + +A representação flexível de strings é similar à forma como o tipo `int` funciona no Python 3: +se um inteiro cabe em uma palavra da máquina, ele será armazenado em uma palavra da máquina. +Caso contrário, o interpretador muda para uma representação de tamanho variável, +como aquela do tipo `long` de Python 2. +É bom ver as boas ideias se espalhando. + +Entretanto, sempre podemos contar com Armin Ronacher para encontrar problemas no Python 3. +Ele me explicou porque, na prática, essa não é uma ideia tão boa assim: +basta um único `RAT` (U+1F400) para inflar um texto, +que de outra forma seria inteiramente ASCII, e transformá-lo em um array sugadora de memória, +usando quatro bytes por caractere, +quando um byte seria o suficiente para todos os caracteres exceto o RAT. +Além disso, por causa de todas as formas como os caracteres Unicode se combinam, +a capacidade de buscar um caractere arbitrário pela posição é +superestimada—e extrair fatias arbitrárias de texto Unicode é no mínimo ingênuo, +e muitas vezes errado, produzindo mojibake. +Com((("emojis", "increasing issues with"))) os emojis se tornando mais populares, +esses problemas só vão piorar. +**** diff --git a/online/cap05.adoc b/online/cap05.adoc new file mode 100644 index 00000000..92e60100 --- /dev/null +++ b/online/cap05.adoc @@ -0,0 +1,1904 @@ +[[ch_dataclass]] +== Fábricas de classes de dados +:example-number: 0 +:figure-number: 0 + +[quote, Martin Fowler & Kent Beck] +____ +Classes de dados são como crianças. +São um bom ponto de partida mas, +para participarem como um objeto adulto, precisam assumir alguma responsabilidade.footnote:[Fonte: +_Refactoring, First Edition_, capítulo 3, seção _Bad Smells in Code_, _Data Class_ +(Mau cheiro no código, classe de dados), p. 87.] +____ + +Python oferece algumas formas de criar uma classe simples, +apenas uma coleção de campos, com pouca ou nenhuma funcionalidade adicional. +Esse padrão é conhecido como "classe de dados"—e `dataclasses` é um dos pacotes que suporta tal modelo. +Este((("data class builders", "topics covered"))) +capítulo trata de três diferentes fábricas de classes que +podem ser utilizadas como atalhos para escrever classes de dados: + +`collections.namedtuple`:: A forma mais simples—disponível desde o Python 2.6. +`typing.NamedTuple`:: Uma alternativa que requer dicas de tipo nos campos—desde o Python 3.5, +com a sintaxe `class` adicionada no 3.6. +`@dataclasses.dataclass`:: Um decorador de classe que permite mais customização +que as alternativas anteriores, acrescentando várias opções e, +potencialmente, mais complexidade—desde o Python 3.7. + +Após falar sobre essas fábricas de classes, vamos discutir o motivo de _classe de dados_ +ser também o nome um((("code smells"))) _code smell_ (odor no código): +um padrão de programação que pode ser um sintoma de um mau design orientado a objetos. + +[NOTE] +==== +A classe `typing.TypedDict` pode((("TypedDict"))) +parecer sintaticamente com uma fábrica de classes de dados. +Ela usa uma sintaxe similar, e é descrita pouco após `typing.NamedTuple` na +https://fpy.li/3n[documentação do módulo `typing`] (EN) de Python 3.11. + +Entretanto, `TypedDict` não cria classes concretas que possam ser instanciadas. +Ela é apenas a sintaxe para escrever dicas de tipo para parâmetros de função e +variáveis que aceitarão valores de mapeamentos como registros, +enquanto suas chaves serão os nomes dos campos. +Veremos mais sobre isso na <> (<>). +==== + + +=== Novidades neste capítulo + +Este((("data class builders", "significant changes to"))) capítulo é novo +nessa segunda edição do _Python Fluente_. +A <> era parte do capítulo 2 da primeira edição, +mas o restante do capítulo é inteiramente inédito. + +Vamos começar por uma visão geral, por alto, das três fábricas de classes. + +[[data_class_overview_sec]] +=== Visão geral das fábricas de classes de dados + +Considere((("data class builders", "overview of", id="DCBover05"))) uma classe simples, +representando um par de coordenadas geográficas, como aquela no <>. + +[[coord_class_ex]] +._class/coordinates.py_ +==== +[source, python] +---- +include::../code/05-data-classes/class/coordinates.py[tags=COORDINATE] +---- +==== + +A tarefa da classe `Coordinate` é manter os atributos latitude e longitude. +Escrever o `+__init__+` padrão fica cansativo muito rápido, +especialmente se sua classe tiver mais que alguns poucos atributos: +cada um deles é mencionado três vezes! +E aquele código repetitivo não fornece algumas funcionalidades básicas +que esperamos de um objeto Python: + +[source, python] +---- +>>> from coordinates import Coordinate +>>> moscow = Coordinate(55.76, 37.62) +>>> moscow + <1> +>>> location = Coordinate(55.76, 37.62) +>>> location == moscow <2> +False +>>> (location.lat, location.lon) == (moscow.lat, moscow.lon) <3> +True +---- +<1> O `+__repr__+` herdado de `object` não é muito útil. +<2> O `==` não faz sentido; o método `+__eq__+` herdado de `object` compara os IDs dos objetos. +<3> Comparar duas coordenadas exige a comparação explícita de cada atributo. + +As fábricas de classes de dados tratadas nesse capítulo fornecem automaticamente +os métodos `+__init__+`, `+__repr__+`, e `+__eq__+` necessários, +além de outros recursos úteis. + +[NOTE] +==== +Nenhuma das fábricas de classes discutidas aqui depende de herança para funcionar. +Tanto `collections.namedtuple` quanto `typing.NamedTuple` criam subclasses de `tuple`. +O `@dataclass` é um decorador de classe, não afeta de forma alguma a hierarquia de classes. +Cada um deles utiliza técnicas diferentes de metaprogramação para +injetar métodos e atributos de dados na classe em construção. +==== + +Aqui está uma classe `Coordinate` criada com uma `namedtuple`—uma função +que fabrica uma subclasse de `tuple` com o nome e os campos especificados: + +[source, python] +---- +>>> from collections import namedtuple +>>> Coordinate = namedtuple('Coordinate', 'lat lon') +>>> issubclass(Coordinate, tuple) +True +>>> moscow = Coordinate(55.756, 37.617) +>>> moscow +Coordinate(lat=55.756, lon=37.617) <1> +>>> moscow == Coordinate(lat=55.756, lon=37.617) <2> +True +---- +<1> Um `+__repr__+` útil. +<2> Um `+__eq__+` que faz sentido. + +A `typing.NamedTuple`, mais recente, oferece a mesma funcionalidade e +acrescenta anotações de tipo a cada campo: + +[source, python] +---- +>>> import typing +>>> Coordinate = typing.NamedTuple('Coordinate', +... [('lat', float), ('lon', float)]) +>>> issubclass(Coordinate, tuple) +True +>>> typing.get_type_hints(Coordinate) +{'lat': , 'lon': } +---- + +[TIP] +==== +Uma tupla nomeada e com dicas de tipo pode também ser construída passando +os campos como argumentos nomeados, assim: + +[source, python] +---- +Coordinate = typing.NamedTuple('Coordinate', lat=float, lon=float) +---- + +Além de ser mais legível, essa forma permite fornecer o mapeamento de campos e +tipos como `**fields_and_types`. +==== + +Desde o Python 3.6, `typing.NamedTuple` pode também ser usada em uma instrução `class`, +com as anotações de tipo escritas como descrito na +https://fpy.li/pep526[PEP 526—Syntax for Variable Annotations (_Sintaxe para Anotações de Variáveis_)] (EN). +É mais legível, e torna fácil sobrescrever métodos ou acrescentar métodos novos. +O <> é a mesma classe `Coordinate`, com um par de atributos `float` +e um `+__str__+` customizado, para mostrar a coordenada no formato 55.8°N, 37.6°E. + +[[coord_tuple_ex]] +._typing_namedtuple/coordinates.py_ +==== +[source, python] +---- +include::../code/05-data-classes/typing_namedtuple/coordinates.py[tags=COORDINATE] +---- +==== + +[WARNING] +==== +Apesar de `NamedTuple` aparecer na declaração `class` como uma superclasse, ela não é. +`typing.NamedTuple` usa a funcionalidade avançada de uma +metaclassefootnote:[As metaclasses são um dos assuntos tratados no +<>.] para customizar a criação da classe do usuário. +Confira isso: + +[source, python] +---- +>>> issubclass(Coordinate, typing.NamedTuple) +False +>>> issubclass(Coordinate, tuple) +True +---- + +==== + +No método `+__init__+` gerado por `typing.NamedTuple`, +os campos aparecem como parâmetros e na mesma ordem em que aparecem na declaração `class`. + +Assim como `typing.NamedTuple`, o decorador `dataclass` suporta a sintaxe da +https://fpy.li/pep526[PEP 526] (EN) para declarar atributos de instância. +O decorador lê as anotações das variáveis e gera métodos automaticamente para sua classe. +Como comparação, veja a classe `Coordinate` +equivalente escrita com a ajuda do decorador `dataclass`, como mostra o <>. + +[[coord_dataclass_ex]] +._dataclass/coordinates.py_ +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/coordinates.py[tags=COORDINATE] +---- +==== + +Observe que o corpo das classes no <> e no <> +são idênticos—a diferença está na própria declaração `class`. +O decorador `@dataclass` não depende de herança ou de uma metaclasse, +então não deve interferir no uso desses mecanismos pelo +usuário.footnote:[Decoradores de classe são discutidos no <>, +na seção "Metaprogramação de classes", junto com as metaclasses. +Ambos são formas de customizar o comportamento de uma classe além do que seria possível com herança.] +A classe `Coordinate` no <> é uma subclasse de +`object`.((("", startref="DCBover05"))) + +[[dc_main_features_sec]] +==== Principais recursos + +As((("data class builders", "main features", id="DCBmain05"))) diferentes fábricas de classes de dados +tem muito em comum, como resume a <>. + +[[builders_compared_tbl]] +.Recursos selecionados, comparando as três fábricas de classes de dados; `x` é uma instância de uma classe de dados daquele tipo +[options="header"] +|============================================================================================================================== +| | namedtuple | NamedTuple | dataclass +| instâncias mutáveis | NÃO | NÃO | SIM +| sintaxe de declaração de classe | NÃO | SIM | SIM +| criar um dict | x._asdict() | x._asdict() | dataclasses.asdict(x) +| obter nomes dos campos | x._fields | x._fields | [f.name for f in dataclasses.fields(x)] +| obter defaults | x._field_defaults | x._field_defaults | [f.default for f in dataclasses.fields(x)] +| obter tipos dos campos | N/A | x.__annotations__ | x.__annotations__ +| nova instância com modificações | x._replace(…) | x._replace(…) | dataclasses.replace(x, …) +| nova classe durante a execução | namedtuple(…) | NamedTuple(…) | dataclasses.make_dataclass(…) +|============================================================================================================================== + +[WARNING] +==== +As classes criadas por `typing.NamedTuple` e `@dataclass` tem um atributo `+__annotations__+`, +contendo as dicas de tipo dos campos. +Entretanto, ler `+__annotations__+` diretamente não é recomendado. +Em vez disso, a melhor prática recomendada para obter tal informação é chamar +https://fpy.li/3p[`inspect.get_annotations(MyClass)`] (a partir de Python 3.10—EN) ou +https://fpy.li/3z[`typing.get_type_hints(MyClass)`] +(Python 3.5 a 3.9). +Isso porque tais funções fornecem serviços adicionais, +como a resolução de referências futuras nas dicas de tipo. +Voltaremos a isso bem mais tarde neste livro, na <>. +==== + +Vamos agora detalhar aqueles recursos principais. + + +===== Instâncias mutáveis + +A diferença fundamental entre essas três fábricas de classes é que +`collections.namedtuple` e `typing.NamedTuple` criam subclasses de `tuple`, +e portanto as instâncias são imutáveis. +Por default, `@dataclass` produz classes mutáveis. +Mas o decorador aceita o argumento nomeado `frozen`—que aparece no <>. +Quando `frozen=True`, a classe vai gerar uma exceção +se você tentar atribuir um valor a um campo após a instância ter sido inicializada. + + +[[class_syntax_feature]] +===== Sintaxe de declaração de classe + +Apenas `typing.NamedTuple` e `dataclass` suportam a sintaxe da instrução `class`, +tornando mais fácil acrescentar métodos e docstrings à classe que está sendo criada. + +===== Construir um dict + +As duas variantes de tuplas nomeadas fornecem um método de instância (`._asdict`), +para construir um objeto `dict` a partir dos campos de uma instância de classe de dados. +O módulo `dataclasses` fornece uma função para fazer o mesmo: `dataclasses.asdict`. + +===== Obter nomes dos campos e valores default + +Todas as três fábricas de classes permitem que você obtenha os nomes dos campos e +os valores default (que podem ser configurados para cada campo). +Nas classes de tuplas nomeadas, aqueles metadados estão nos +atributos de classe `._fields` e `._fields_defaults`. +Você pode obter os mesmos metadados em uma classe decorada com `dataclass` usando +a função `fields` do módulo `dataclasses`. +Ela devolve uma tupla de objetos `Field` com vários atributos, incluindo `name` e `default`. + + +===== Obter os tipos dos campos + +Classes definidas com a ajuda de `typing.NamedTuple` e `@dataclass` +contêm um mapeamento dos nomes dos campos para seus tipos, o atributo de classe `+__annotations__+`. +Como já mencionado, use a função `typing.get_type_hints` em vez de ler diretamente de +`+__annotations__+`. + + +===== Nova instância com modificações + +Dada uma instância de tupla nomeada `x`, a chamada `+x._replace(**kwargs)+` +devolve uma nova instância com os valores de alguns atributos modificados, +de acordo com os argumentos nomeados incluídos na chamada. +A função de módulo `dataclasses.replace(x, **kwargs)` +faz o mesmo para uma instância de uma classe decorada com `dataclass`. + + +===== Nova classe durante a execução + +Apesar da sintaxe de declaração de classe ser mais legível, ela é estática, +registrada no código-fonte. +Um framework pode ter a necessidade de criar classes de dados durante a execução. +Para tanto, podemos usar a sintaxe default de chamada de função de `collections.namedtuple`, +que também é suportada por `typing.NamedTuple`. +O módulo `dataclasses` oferece a função `make_dataclass`, com o mesmo propósito. + +Após essa visão geral dos principais recursos das fábricas de classes de dados, +vamos examinar cada uma delas mais de perto, começando pela mais simples.((("", startref="DCBmain05"))) + + +[[classic_named_tuples_sec]] +=== Tuplas nomeadas clássicas + +A((("data class builders", "classic named tuples", id="DCBnamedt05")))((("tuples", +"classic named tuples", id="Tclassic05")))((("collections.namedtuple", +id="colnamedt05")))((("namedtuple", id="namedt05"))) +função `collections.namedtuple` é uma fábrica que cria subclasses de `tuple`, +acrescidas de nomes de campos, um nome de classe, e um `+__repr__+` informativo. +Classes criadas com `namedtuple` podem ser usadas onde quer que uma tupla seja necessária. +Na verdade, muitas funções da biblioteca padrão, que antes devolviam tuplas simples, +agora devolvem tuplas nomeadas, sem afetar de forma alguma o código que as utiliza. + +[TIP] +==== +Cada instância de uma classe criada por `namedtuple` usa exatamente +a mesma quantidade de memória usada por uma tupla, pois os nomes dos campos são armazenados na classe. +==== + +O <> mostra como poderíamos definir +uma tupla nomeada para registrar informações sobre uma cidade. + +[[ex_named_tuple_1]] +.Definindo e usando um tipo tupla nomeada +==== +[source, python] +---- +>>> from collections import namedtuple +>>> City = namedtuple('City', 'name country population coordinates') <1> +>>> tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667)) <2> +>>> tokyo +City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722, +139.691667)) +>>> tokyo.population <3> +36.933 +>>> tokyo.coordinates +(35.689722, 139.691667) +>>> tokyo[1] +'JP' +---- +==== +<1> São necessários dois parâmetros para criar uma tupla nomeada: +um nome de classe e uma lista de nomes de campos, +que podem ser passados como um iterável de strings ou +como uma única string com os nomes delimitados por espaços. +<2> Na inicialização de uma instância, os valores dos campos devem ser passados +como argumentos posicionais separados (uma `tuple`, por outro lado, é inicializada com um único iterável) +<3> É possível acessar os campos por nome ou por posição. + +Como uma subclasse de `tuple`, `City` herda métodos úteis, tal como `+__eq__+` +e os métodos especiais para operadores de comparação—incluindo `+__lt__+`, +que permite ordenar listas de instâncias de `City`. + +Uma tupla nomeada oferece alguns atributos e métodos além daqueles herdados de `tuple`. +O <> demonstra os mais úteis dentre eles: o atributo de classe `_fields`, +o método de classe `_make(iterable)`, e o método de instância `_asdict()`. + +[[ex_named_tuple_2]] +.Atributos e métodos das tuplas nomeadas (continuando do exemplo anterior) +==== +[source, python] +---- +>>> City._fields <1> +('name', 'country', 'population', 'location') +>>> Coordinate = namedtuple('Coordinate', 'lat lon') +>>> delhi_data = ('Delhi NCR', 'IN', 21.935, Coordinate(28.613889, 77.208889)) +>>> delhi = City._make(delhi_data) <2> +>>> delhi._asdict() <3> +{'name': 'Delhi NCR', 'country': 'IN', 'population': 21.935, +'location': Coordinate(lat=28.613889, lon=77.208889)} +>>> import json +>>> json.dumps(delhi._asdict()) <4> +'{"name": "Delhi NCR", "country": "IN", "population": 21.935, +"location": [28.613889, 77.208889]}' +---- +==== +<1> `._fields` é uma tupla com os nomes dos campos da classe. +<2> `._make()` cria uma `City` a partir de um iterável; `City(*delhi_data)` faria o mesmo. +<3> `._asdict()` devolve um `dict` criado a partir da instância de tupla nomeada. +<4> `._asdict()` é útil para serializar os dados no formato JSON, por exemplo. + +[WARNING] +===== +Até Python 3.7, o método `_asdict` devolvia um `OrderedDict`. +Desde o Python 3.8, ele devolve um `dict` simples—o que não é problema, +agora que podemos confiar na ordem de inserção das chaves. +Se você precisar de um `OrderedDict`, a +https://fpy.li/3q[documentação do `_asdict`] (EN) +recomenda criar um com o resultado: `OrderedDict(x._asdict())`. +===== + +Desde o Python 3.7, a `namedtuple` aceita o argumento nomeado `defaults`, +fornecendo um iterável de N valores default para cada um dos N campos mais à direita na definição da classe. +O <> mostra como definir uma tupla nomeada +`Coordinate` com um valor default para o campo `reference`. + +[[ex_coord_tuple_default]] +.Atributos e métodos das tuplas nomeadas, continuando do <> +==== +[source, python] +---- +>>> Coordinate = namedtuple('Coordinate', 'lat lon reference', defaults=['WGS84']) +>>> Coordinate(0, 0) +Coordinate(lat=0, lon=0, reference='WGS84') +>>> Coordinate._field_defaults +{'reference': 'WGS84'} +---- +==== + +Na <>, mencionei que é mais fácil programar métodos com +a sintaxe de classe suportada por `typing.NamedTuple` e `@dataclass`. +Você também pode acrescentar métodos a uma `namedtuple`, mas é um remendo. +Pule a próxima caixinha se você não estiver interessada em gambiarras. + +[[hacking_namedtuple_box]] +.Remendando uma tupla nomeada para injetar um método +**** + +Lembre como criamos a classe `Card` class no <> do <>: + +[source, python] +---- +Card = collections.namedtuple('Card', ['rank', 'suit']) +---- + +Mas tarde no <>, escrevi uma função `spades_high`, para ordenação. +Seria bom que aquela lógica estivesse encapsulada em um método de `Card`, +mas acrescentar `spades_high` a `Card` sem usar uma declaração `class` exige um remendo rápido: +definir a função e então atribuí-la a um atributo de classe. +O <> mostra como isso é feito: + + +[[ranked_card_ex]] +.frenchdeck.doctest: Acrescentando um atributo de classe e um método a `Card`, a `namedtuple` da <> +==== +[source, python] +---- +include::../code/05-data-classes/frenchdeck.doctest[tags=SPADES_HIGH] +---- +<1> Acrescenta um atributo de classe com valores para cada naipe. +<2> A função `spades_high` vai se tornar um método; o primeiro argumento não precisa ser chamado de `self`. +Ao ser invocada como método, ela receberá a instância no primeiro argumento. +<3> Anexa a função à classe `Card` como um método chamado `overall_rank`. +<4> Funciona! +==== + +Para uma melhor legibilidade e para ajudar na manutenção futura, +é muito melhor programar métodos dentro de uma declaração `class`. +Mas é bom saber que essa gambiarra é possível, pois às vezes pode ser +útil.footnote:[Se você conhece Ruby, sabe que injetar métodos é uma técnica bastante conhecida, +apesar de controversa, entre _rubystas_. +Em Python isso não é tão comum, pois não funciona com nenhum dos tipos embutidos—`str`, `list`, etc. +Considero essa limitação de Python uma bêncão.] + +Isso foi apenas um pequeno desvio para demonstrar o poder de uma linguagem dinâmica. +**** + +Agora vamos ver a variante `typing.NamedTuple`.((("", startref="DCBnamedt05")))((("", +startref="Tclassic05")))((("", startref="colnamedt05")))((("", startref="namedt05"))) + + +[[typed_named_tuples_sec]] +=== Tuplas nomeadas com tipo + +A((("typing.NamedTuple")))((("tuples", "typing.NamedTuple")))((("data class builders", "typed named tuples"))) +classe `Coordinate` com um campo default, do <>, +pode ser escrita usando `typing.NamedTuple`, como se vê no <>. + +[[coord_tuple_default_ex]] +._typing_namedtuple/coordinates2.py_ +==== +[source, python] +---- +include::../code/05-data-classes/typing_namedtuple/coordinates2.py[tags=COORDINATE] +---- +<1> Todo campo de instância precisa ter uma anotação de tipo. +<2> O campo de instância `reference` é anotado com um tipo e um valor default. +==== + +As classes criadas por `typing.NamedTuple` não tem qualquer método +além daqueles que `collections.namedtuple` também gera, e aqueles herdados de `tuple`. +A única diferença é a presença do atributo de classe `+__annotations__+`, +que Python ignora durante a execução do programa. + +Dado que o principal recurso de `typing.NamedTuple` são as anotações de tipo, +vamos dar uma rápida olhada nisso antes de continuar nossa exploração das fábricas de classes de dados. + + +=== Introdução às dicas de tipo + +Dicas((("data class builders", "type hints", id="DCBhint05")))((("type hints (type annotations)", +"basics of", id="typehint05"))) de tipo (_type hints_, também chamadas anotações de tipo) são formas de +declarar o tipo esperado dos argumentos de funções, dos valores devolvidos em `return`, +das variáveis e dos atributos de objetos. + +[NOTE] +==== +Essa é uma introdução muito breve sobre dicas de tipo, +suficiente apenas para que a sintaxe e o propósito das anotações usadas +nas declarações de `typing.NamedTuple` e `@dataclass` façam sentido. +Vamos tratar de anotações de tipo nas assinaturas de função no <> +e de anotações mais avançadas no <>. +Aqui vamos ver principalmente dicas com tipos embutidos simples, como `str`, `int`, e `float`, +que são provavelmente os tipos mais comuns usados para anotar campos em classes de dados. +==== + +A primeira coisa que você precisa saber sobre dicas de tipo é que elas não são checadas +pelo compilador de bytecode ou pelo interpretador de Python. + +[[no_runtime_effect_sec]] +==== Nenhum efeito durante a execução + +Pense nas dicas de tipo de Python como +"documentação que pode ser utilizada por IDEs e checadores de tipos". + +Isso porque as dicas de tipo não tem qualquer impacto sobre o comportamento +de programas em Python durante a execução. +Veja o <>. + +[[no_runtime_check_ex]] +.Python não checa dicas de tipo durante a execução de um programa +==== +[source, python] +---- +>>> import typing +>>> class Coordinate(typing.NamedTuple): +... lat: float +... lon: float +... +>>> trash = Coordinate('Ni!', None) +>>> print(trash) +Coordinate(lat='Ni!', lon=None) # <1> +---- +==== +<1> Eu avisei: não há checagem de tipos durante a execução! + +Se você incluir o código do <> em um módulo de Python, +ela vai rodar e exibir uma `Coordinate` sem sentido, +sem gerar erro ou aviso: + +[source] +---- +$ python3 nocheck_demo.py +Coordinate(lat='Ni!', lon=None) +---- + +O objetivo primário das dicas de tipo é ajudar os checadores de tipos externos, +como o https://fpy.li/mypy[Mypy] ou o checador de tipos embutido do +https://fpy.li/5-5[PyCharm IDE]. +Essas são ferramentas de análise estática: elas checam código-fonte Python "parado", +não código em execução. + +Para observar o efeito das dicas de tipo, é necessário executar alguma dessas +ferramentas sobre seu código—como um linter (_analisador de código_). +Por exemplo, eis o quê o Mypy tem a dizer sobre o exemplo anterior: + +[source] +---- +$ mypy nocheck_demo.py +nocheck_demo.py:8: error: Argument 1 to "Coordinate" has +incompatible type "str"; expected "float" +nocheck_demo.py:8: error: Argument 2 to "Coordinate" has +incompatible type "None"; expected "float" +---- + +Como se vê, dada a definição de `Coordinate`, +o Mypy percebe que os dois argumentos para criar um instância devem ser do tipo `float`, +mas atribuição a `trash` usa uma `str` e `None`.footnote:[No contexto das dicas de tipo, +`None` não é o singleton `NoneType`, mas um apelido para o próprio `NoneType`. +Se pararmos para pensar, isso é estranho, +mas agrada nossa intuição e torna as anotações de valores devolvidos por uma função mais fáceis de ler, +no caso comum de funções que devolvem `None`.] + +Vamos falar agora sobre a sintaxe e o significado das dicas de tipo. + +[[var_annotation_syntax]] +==== Sintaxe de anotação de variáveis + +Tanto((("variable annotations", "syntax of"))) `typing.NamedTuple` quanto `@dataclass` +usam a sintaxe de anotações de variáveis definida na https://fpy.li/pep526[PEP 526] (EN). +Vamos ver aqui uma pequena introdução àquela sintaxe, +no contexto da definição de atributos em declarações `class`. + +A sintaxe básica da anotação de variáveis é : + +[source, python] +---- +var_name: some_type +---- + +A seção https://fpy.li/5-6["Acceptable type hints" (_Dicas de tipo aceitáveis_)] na PEP 484 +explica o que são tipos aceitáveis. +Porém, no contexto da definição de uma classe de dados, os tipos mais úteis geralmente serão os seguintes: + +* Uma classe concreta, por exemplo `str` ou `FrenchDeck`. +* Um tipo de coleção parametrizado, como `list[int]`, `tuple[str, float]`, etc. +* `typing.Optional`, por exemplo `Optional[str]`—para declarar um campo que pode ser uma `str` ou `None`, +que também pode ser escrito como `str | None`. + +Você também pode inicializar uma variável com um valor. +Em uma declaração de `typing.NamedTuple` ou `@dataclass`, +aquele valor se tornará o default daquele atributo quando +o argumento correspondente for omitido na chamada de inicialização: + +[source, python] +---- +var_name: some_type = a_value +---- + +==== O significado das anotações de variáveis + +Vimos((("variable annotations", "meaning of", id="VAmean05"))) na <> +que dicas de tipo não tem qualquer efeito durante a execução de um programa. +Mas no momento da importação—quando um módulo é carregado—o Python as lê para construir +o dicionário `+__annotations__+`, que `typing.NamedTuple` e `@dataclass` então usam para aprimorar a classe. + +Vamos começar essa exploração no <>, com uma classe simples, +para mais tarde ver que recursos adicionais são acrescentados por `typing.NamedTuple` e `@dataclass`. + +[[ex_demo_plain]] +.meaning/demo_plain.py: uma classe básica com dicas de tipo +==== +[source, python] +---- +include::../code/05-data-classes/meaning/demo_plain.py[] +---- +==== +<1> `a` se torna um registro em `+__annotations__+`, mas é então descartada: +nenhum atributo chamado `a` é criado na classe. +<2> `b` é salvo como uma anotação, e também se torna um atributo de classe com o valor `1.1`. +<3> `c` é só um bom e velho atributo de classe básico, sem uma anotação. + +Podemos checar isso no console, primeiro lendo o `+__annotations__+` da `DemoPlainClass`, +e daí tentando obter os atributos chamados `a`, `b`, e `c`: + +[source, python] +---- +>>> from demo_plain import DemoPlainClass +>>> DemoPlainClass.__annotations__ +{'a': , 'b': } +>>> DemoPlainClass.a +Traceback (most recent call last): + File "", line 1, in +AttributeError: type object 'DemoPlainClass' has no attribute 'a' +>>> DemoPlainClass.b +1.1 +>>> DemoPlainClass.c +'spam' +---- + +Observe que o atributo especial `+__annotations__+` é criado pelo interpretador +para registrar dicas de tipo que aparecem no código-fonte—mesmo em uma classe básica. + +O `a` sobrevive apenas como uma anotação, não se torna um atributo da classe, +porque nenhum valor é atribuído a +ele.footnote:[O conceito de _undefined_, um dos erros mais tolos no design de JavaScript, +não existe no Python. +Obrigado, Guido!] +O `b` e o `c` são armazenados como atributos de classe porque são vinculados a valores. + +Nenhum desses três atributos estará em uma nova instância de `DemoPlainClass`. +Se você criar um objeto `o = DemoPlainClass()`, `o.a` vai gerar um `AttributeError`, +enquanto `o.b` e `o.c` vão obter os atributos de classe com os valores +`1.1` e `'spam'`—que é apenas o comportamento normal de um objeto Python. + +===== Inspecionando uma typing.NamedTuple + +Agora vamos examinar uma classe criada com `typing.NamedTuple` (<>), +usando os mesmos atributos e anotações da `DemoPlainClass` do <>. + +[[ex_demo_nt]] +.meaning/demo_nt.py: uma classe criada com `typing.NamedTuple` +==== +[source, python] +---- +include::../code/05-data-classes/meaning/demo_nt.py[] +---- +==== +<1> `a` se torna uma anotação e também um atributo de instância. +<2> `b` é outra anotação, mas também se torna um atributo de instância com o valor default `1.1`. +<3> `c` é só um bom e velho atributo de classe comum; não será mencionado em nenhuma anotação. + +Inspecionando a `DemoNTClass`, temos o seguinte: + +[source, python] +---- +>>> from demo_nt import DemoNTClass +>>> DemoNTClass.__annotations__ +{'a': , 'b': } +>>> DemoNTClass.a +<_collections._tuplegetter object at 0x101f0f940> +>>> DemoNTClass.b +<_collections._tuplegetter object at 0x101f0f8b0> +>>> DemoNTClass.c +'spam' +---- + +Aqui vemos as mesmas anotações para `a` e `b` que vimos no <>. +Mas `typing.NamedTuple` cria os atributos de classe `a` e `b`. +O atributo c é apenas um atributo de classe simples, com o valor `'spam'`. + +Os atributos de classe `a` e `b` são((("descriptors"))) descritores +(_descriptors_)—um recurso avançado tratado no <>. +Por ora, pense neles como similares a um _getter_ de propriedades do +objetofootnote:[NT: Um _getter_ é um método que devolve o valor de um atributo do objeto. +Para propriedades mutáveis, o _getter_ vem geralmente acompanhado por um _setter_, +que modifica a mesma propriedade. +Os nomes derivam dos verbos em inglês _get_ (obter, receber) e _set_ (definir, estabelecer).]: +métodos que não exigem o operador explícito de chamada `()` para obter um atributo de instância. +Na prática, isso significa que `a` e `b` vão funcionar como atributos de instância +somente para leitura—o que faz sentido, se lembrarmos que instâncias de `DemoNTClass` +são apenas tuplas enfeitadas, e tuplas são imutáveis. + +A `DemoNTClass` também recebe uma docstring customizada: + +[source, python] +---- +>>> DemoNTClass.__doc__ +'DemoNTClass(a, b)' +---- + +Vamos examinar uma instância de `DemoNTClass`: + +[source, python] +---- +>>> nt = DemoNTClass(8) +>>> nt.a +8 +>>> nt.b +1.1 +>>> nt.c +'spam' +---- + +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` 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`, +vai gerar uma exceção `AttributeError`, com mensagens de erro sutilmente diferentes. +Tente fazer isso, e reflita sobre as mensagens. + + +[[inspecting_dataclass_sec]] +===== Inspecionando uma classe decorada com dataclass + +Vamos agora examinar o <>. + +[[ex_demo_dc]] +.meaning/demo_dc.py: uma classe decorada com `@dataclass` +==== +[source, python] +---- +include::../code/05-data-classes/meaning/demo_dc.py[] +---- +==== +<1> `a` se torna uma anotação, e também um atributo de instância controlado por um descritor. +<2> `b` é outra anotação, e também se torna um atributo de instância +com um descritor e um valor default de `1.1`. +<3> `c` é apenas um atributo de classe comum; nenhuma anotação se refere a ele. + +Podemos então verificar o `+__annotations__+`, o `+__doc__+`, +e os atributos `a`, `b`, `c` em `DemoDataClass`: + +[source, python] +---- +>>> from demo_dc import DemoDataClass +>>> DemoDataClass.__annotations__ +{'a': , 'b': } +>>> DemoDataClass.__doc__ +'DemoDataClass(a: int, b: float = 1.1)' +>>> DemoDataClass.a +Traceback (most recent call last): + File "", line 1, in +AttributeError: type object 'DemoDataClass' has no attribute 'a' +>>> DemoDataClass.b +1.1 +>>> DemoDataClass.c +'spam' +---- + +O `+__annotations__+` e o `+__doc__+` não guardam surpresas. +Entretanto, não há um atributo chamado `a` em `DemoDataClass`—diferente +do que ocorre na `DemoNTClass` do <>, +que inclui um descritor para obter `a` das instâncias da classe, +como atributos somente para leitura (aquele misterioso `<_collections._tuplegetter_>`). +Isso ocorre porque o atributo `a` só existirá nas instâncias de `DemoDataClass`. +Será um atributo público, que poderemos obter e definir, a menos que a classe seja _frozen_. +Mas `b` e `c` existem como atributos de classe, +com `b` contendo o valor default para o atributo de instância `b`, +enquanto `c` é apenas um atributo de classe que não será vinculado a instâncias. + +Vejamos como se parece uma instância de `DemoDataClass`: + +[source, python] +---- +>>> dc = DemoDataClass(9) +>>> dc.a +9 +>>> dc.b +1.1 +>>> dc.c +'spam' +---- + +Novamente, `a` e `b` são atributos de instância, +e `c` é um atributo de classe obtido através da instância. + +Como mencionado, instâncias de `DemoDataClass` são mutáveis—e +nenhuma checagem de tipos é realizada durante a execução: + +[source, python] +---- +>>> dc.a = 10 +>>> dc.b = 'oops' +---- + +Podemos fazer atribuições ainda mais ridículas: + +[source, python] +---- +>>> dc.c = 'whatever' +>>> dc.z = 'secret stash' +---- + +Agora a instância `dc` tem um atributo `c`—mas isso não muda o atributo de classe `c`. +E podemos adicionar um novo atributo `z`. +Isso é o comportamento normal de Python: instâncias podem ter seus próprios atributos, +que não aparecem na classe.footnote:[Definir um atributo após o `+__init__+` +prejudica a otimização de uso de memória com o compartilhamento das chaves do `+__dict__+`, +mencionada na +<>.]((("", startref="VAmean05")))((("", startref="typehint05")))((("", startref="DCBhint05"))) + + +=== Mais detalhes sobre @dataclass + +Até((("data class builders", "@dataclass", id="DCBatdataclass05")))((("@dataclass", +"keyword parameters accepted by"))) agora, só vimos exemplos simples do uso de `@dataclass`. +Esse decorador aceita vários argumentos nomeados. +Esta é sua assinatura: + +[source, python] +---- +@dataclass(*, init=True, repr=True, eq=True, order=False, + unsafe_hash=False, frozen=False) +---- + +O `*` na primeira posição significa que os parâmetros restantes são todos parâmetros nomeados. +A <> os descreve. + +[[dataclass_options_tbl]] +.Parâmetros nomeados aceitos pelo decorador `@dataclass` +[options="header"] +|==================================================================================================================================================================== +| opção | efeito | default | notas +| `init` | Gera o `+__init__+` | `True` | Ignorado se o +`+__init__+` for implementado pelo usuário. +| `repr` | Gera o `+__repr__+` | `True` | Ignorado se o +`+__repr__+` for implementado pelo usuário. +| `eq` | Gera o `+__eq__+` | `True` | Ignorado se o +`+__eq__+` for implementado pelo usuário. +| `order` | Gera `+__lt__+`, `+__le__+`, `+__gt__+`, `+__ge__+` | `False` | Se `True`, +causa uma exceção se `eq=False`, ou se qualquer um dos métodos de comparação que seriam gerados estiver definido ou for herdado. +| `unsafe_hash` | Gera o `+__hash__+` | `False` | Semântica complexa e várias restrições—veja a: https://fpy.li/3r[documentação de dataclass]. +| `frozen` | Cria instâncias "imutáveis" | `False` | As instâncias estarão razoavelmente protegidas contra mudanças acidentais, mas não serão realmente imutáveis.footnote:[O `@dataclass` emula a imutabilidade criando um `+__setattr__+` e um `+__delattr__+` +que geram um `dataclass.FrozenInstanceError`—uma subclasse de `AttributeError`—quando o usuário tenta definir ou apagar o valor de um campo.] +|==================================================================================================================================================================== + + +Os((("@dataclass", "default settings"))) defaults são realmente +as configurações mais úteis para os casos de uso mais comuns. +As opções mais prováveis de serem modificadas de seus defaults são: + +`frozen=True`:: Protege as instâncias da classe de modificações acidentais. +`order=True`:: Permite ordenar as instâncias da classe de dados. + +Dada a natureza dinâmica de objetos Python, +não é muito difícil para um programador curioso contornar a proteção oferecida por `frozen=True`. +Mas os truques necessários são fáceis de perceber em uma revisão do código. + +Se((("@dataclass", "__hash__ method")))((("__hash__"))) +tanto o argumento `eq` quanto o `frozen` forem `True`, `@dataclass` produz um método +`+__hash__+` adequado, e daí as instâncias serão _hashable_. +O `+__hash__+` gerado usará dados de todos os campos que não forem +individualmente excluídos usando uma opção de campo, que veremos na <>. +Se `frozen=False` (o default), `@dataclass` definirá `+__hash__+` como `None`, +sinalizando que as instâncias não são hashable, e portanto sobrescrevendo o `+__hash__+` +de qualquer superclasse. + +A https://fpy.li/pep557[PEP 557—Data Classes (_Classe de Dados_)] (EN) diz o seguinte sobre `unsafe_hash`: + +[quote] +____ +Apesar de não ser recomendado, você pode forçar `@dataclass` a criar um método +`+__hash__+` com `unsafe_hash=True`. +Pode ser esse o caso, se sua classe for logicamente imutável e mesmo assim possa ser modificada. +Este é um caso de uso especializado e deve ser considerado com cuidado. +____ + +Deixo o `unsafe_hash` por aqui. +Se você achar que precisa usar essa opção, leia a +https://fpy.li/3r[documentação de `dataclasses.dataclass`]. + + +Outras customizações da classe de dados gerada podem ser feitas no nível dos campos. + +[[field_options_sec]] +==== Opções de campo + +Já((("@dataclass", "field options", id="atdatafield05"))) vimos a opção de campo mais básica: +fornecer (ou não) um valor default junto com a dica de tipo. +Os campos de instância declarados se tornarão parâmetros no `+__init__+` gerado. +Python não permite parâmetros sem um default após parâmetros com defaults. +Então, após declarar um campo com um valor default, +cada um dos campos seguintes deve também ter um default. + +Valores default mutáveis são a fonte mais comum de bugs entre desenvolvedores Python iniciantes. +Em definições de função, um valor default mutável é facilmente corrompido, +quando uma invocação da função modifica o default, +mudando o comportamento nas invocações posteriores—um tópico que +vamos explorar na <> (<>). +Atributos de classe são frequentemente usados como valores default de atributos para instâncias, +inclusive em classes de dados. +E o `@dataclass` usa os valores default nas dicas de tipo para gerar parâmetros com defaults no +`+__init__+`. +Para prevenir bugs, o `@dataclass` rejeita a definição de classe que aparece no <>. + +[[club_wrong_ex]] +._dataclass/club_wrong.py_: essa classe gera um `ValueError` +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/club_wrong.py[tags=CLUBMEMBER] +---- +==== + +Se você carregar o módulo com aquela classe `ClubMember`, o resultado será esse: + +[source] +---- +$ python3 club_wrong.py +Traceback (most recent call last): + File "club_wrong.py", line 4, in + class ClubMember: + ...several lines omitted... +ValueError: mutable default for field guests is not allowed: +use default_factory +---- + +A mensagem do `ValueError` explica o problema e sugere uma solução: usar a `default_factory`. +O <> mostra como corrigir a `ClubMember`. + +[[club_ex]] +._dataclass/club.py_: essa definição de `ClubMember` funciona +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/club.py[] +---- +==== + +No campo `guests` do <>, em vez de uma lista literal, +o valor default é definido chamando a função `dataclasses.field` com `default_factory=list`. + +O parâmetro `default_factory` permite que você forneça uma função, +classe ou qualquer outro invocável, que será chamado sem argumentos, +para gerar um valor default a cada vez que uma instância da classe de dados for criada. +Dessa forma, cada instância de `ClubMember` terá sua própria `list`—ao invés de +todas as instâncias compartilharem a mesma `list` da classe, +que raramente é o que queremos, e muitas vezes é um bug. + +[WARNING] +==== +É bom que `@dataclass` rejeite definições de classe com uma `list` default em um campo. +Entretanto, entenda que isso é uma solução parcial, que se aplica apenas a `list`, `dict` e `set`. +Outros valores mutáveis usados como default não serão rejeitados por `@dataclass`. +É sua responsabilidade entender o problema e se lembrar de usar uma _factory_ +default para definir valores default mutáveis. +==== + +Se você estudar a documentação do módulo https://fpy.li/3s[`dataclasses`], +verá um campo `list` definido com uma sintaxe nova, como no <>. + +[[club_generic_ex]] +._dataclass/club_generic.py_: essa definição de `ClubMember` é mais precisa +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/club_generic.py[] +---- +==== +<1> `list[str]` significa "uma lista de str." + +A nova sintaxe `list[str]` é um tipo genérico parametrizado: desde o Python 3.9, +o tipo embutido `list` aceita aquela notação com colchetes para especificar o tipo dos itens da lista. + +[WARNING] +==== +Antes de Python 3.9, as coleções embutidas não suportavam a notação de tipagem genérica. +Como uma solução temporária, há tipos correspondentes de coleções no módulo `typing`. +Se você precisa de uma dica de tipo para uma `list` parametrizada no Python 3.8 ou anterior, +você precisa importar e usar o tipo `List` de `typing`: `List[str]`. +Leia mais sobre isso na caixa <>. +==== + +Vamos tratar dos tipos genéricos no <>. +Por ora, observe que o <> e o <> estão ambos corretos, +e que o checador de tipos Mypy não reclama de nenhuma das duas definições de classe. + +A diferença é que aquele `guests: list` significa que `guests` pode ser uma `list` +de objetos de qualquer tipo, enquanto `guests: list[str]` +diz que `guests` deve ser uma `list` na qual cada item é uma `str`. +Isso permite que o checador de tipos encontre (alguns) +bugs em código que insira itens inválidos na lista, ou que leia itens dela. + +A `default_factory` é possivelmente a opção mais comum da função `field`, +mas há várias outras, listadas na <>. + +[[field_options_tbl]] +.Argumentos nomeados aceitos pela função `field` +[options="header"] +|==================================================================================================================================================================== +| Option | Meaning | Default +| `default` | Valor default para o campo | `_MISSING_TYPE` footnote:[`dataclass._MISSING_TYPE` é um valor sentinela, indicando que a opção não foi fornecida. Ele existe para que se possa definir `None` como um valor default efetivo, um caso de uso comum.] +| `default_factory` | função com 0 parâmetros usada para produzir um valor default | `_MISSING_TYPE` +| `init` | Incluir o campo nos parâmetros de `+__init__+` | `True` +| `repr` | Incluir o campo em `+__repr__+` | `True` +| `compare` | Usar o campo nos métodos de comparação `+__eq__+`, `+__lt__+`, etc. | `True` +| `hash` | Incluir o campo no cálculo de `+__hash__+` | ++None++footnote:[A opção `hash=None` significa que o campo será usado em `+__hash__+` apenas se `compare=True`.] +| `metadata` | Mapeamento com dados definidos pelo usuário; ignorado por `@dataclass` | `None` +|==================================================================================================================================================================== + +A opção `default` existe porque a chamada a `field` toma o lugar do valor default na anotação do campo. +Se você quisesse criar um campo `athlete` com o valor default `False`, +e também omitir aquele campo do método `+__repr__+`, escreveria o seguinte: + +[source, python] +---- +@dataclass +class ClubMember: + name: str + guests: list = field(default_factory=list) + athlete: bool = field(default=False, repr=False) +---- + + +==== Processamento pós-inicialização + +O((("@dataclass", "post-init processing", +id="atdatapost05")))((("__init__")))((("__post_init__"))) +método `+__init__+` gerado por `@dataclass` apenas recebe os argumentos passados e +os atribui (ou seus valores default, se o argumento não estiver presente) aos atributos de instância, +que são campos da instância. +Mas pode ser necessário fazer mais que isso para inicializar a instância. +Se for esse o caso, você pode fornecer um método `+__post_init__+`. +Quando esse método existir, `@dataclass` acrescentará código ao `+__init__+` gerado para invocar +`+__post_init__+` como o último passo da inicialização. + +Casos de uso comuns para `+__post_init__+` são validação e +o cálculo de valores de campos baseado em outros campos. +Vamos estudar um exemplo simples, que usa `+__post_init__+` pelos dois motivos. + +Primeiro, dê uma olhada no comportamento esperado de uma subclasse de `ClubMember`, +chamada `HackerClubMember`, +como descrito por doctests no <>. + +[[hackerclub_doctests_ex]] +._dataclass/hackerclub.py_: doctests para `HackerClubMember` +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/hackerclub.py[tags=DOCTESTS] +---- +==== + +Observe que precisamos fornecer `handle` como um argumento nomeado, +pois `HackerClubMember` herda `name` e `guests` de `ClubMember`, +e acrescenta o campo `handle`. A docstring gerada para `HackerClubMember` +mostra a ordem dos campos na chamada de inicialização: + +[source, python] +---- +>>> HackerClubMember.__doc__ +"HackerClubMember(name: str, guests: list = , handle: str = '')" +---- + +Aqui `` apenas indica que um invocável produzirá +o valor default para `guests` (no nosso caso, a fábrica é a classe `list`). +O ponto é o seguinte: para fornecer um `handle` mas não `guests`, +precisamos passar `handle` como um argumento nomeado. + +A seção https://fpy.li/3t["Herança] na documentação do módulo `dataclasses` +explica como a ordem dos campos é analisada quando existem vários níveis de herança. + +[NOTE] +==== +No <> vamos falar sobre o uso indevido da herança, +especialmente quando as superclasses não são abstratas. +Criar uma hierarquia de classes de dados é, em geral, uma má ideia, +mas nos serviu bem aqui para tornar o <> mais curto, +e permitir que nos concentrássemos na declaração do campo `handle` e na validação com +`+__post_init__+`. +==== + +O <> mostra a implementação. + +[[hackerclub_ex]] +._dataclass/hackerclub.py_: código para `HackerClubMember` +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/hackerclub.py[tags=HACKERCLUB] +---- +==== +<1> `HackerClubMember` estende `ClubMember`. +<2> `all_handles` é um atributo de classe. +<3> `handle` é um campo de instância do tipo `str`, +com uma string vazia como valor default; isso o torna opcional. +<4> Obtém a classe da instância. +<5> Se `self.handle` é a string vazia, a define como a primeira parte de `name`. +<6> Se `self.handle` está em `cls.all_handles`, gera um `ValueError`. +<7> Insere o novo `handle` em `cls.all_handles`. + + +O <> funciona como esperado, mas não é satisfatório pra um checador de tipos estático. +A seguir veremos a razão disso, e como resolver o problema.((("", startref="atdatapost05"))) + +==== Atributos de classe tipados + +Se((("@dataclass", "typed class attributes"))) checarmos os tipos de <> com o Mypy, +seremos repreendidos: + +[source] +---- +$ mypy hackerclub.py +hackerclub.py:37: error: Need type annotation for "all_handles" +(hint: "all_handles: Set[] = ...") +Found 1 error in 1 file (checked 1 source file) +---- + +Infelizmente, a dica fornecida pelo Mypy (versão 0.910 quando essa seção foi revisada) +não é muito útil no contexto do uso de `@dataclass`. +Primeiro, ele sugere usar `Set`, mas desde o Python 3.9 podemos usar +`set`—sem a necessidade de importar `Set` de `typing`. +E mais importante, se acrescentarmos uma dica de tipo como `set[…]` +a `all_handles`, `@dataclass` vai encontrar essa anotação e +transformar `all_handles` em um campo de instância. +Vimos isso acontecer na <>. + +A forma de contornar esse problema definida na +https://fpy.li/5-11[PEP 526—Syntax for Variable Annotations (_Sintaxe para Anotações de Variáveis_)] (EN) +é horrível. +Para criar uma variável de classe com uma dica de tipo, precisamos usar um pseudo-tipo +chamado `typing.ClassVar`, +que aproveita a notação de tipos genéricos (`[]`) para definir o tipo da variável +e também para declará-la como um atributo de classe. + +Para fazer felizes tanto o checador de tipos quando o `@dataclass`, +deveríamos declarar o `all_handles` do <> assim: + +[source, python] +---- + all_handles: ClassVar[set[str]] = set() +---- + +Aquela dica de tipo está dizendo o seguinte: + +[quote] +____ +`all_handles` é um atributo de classe do tipo ++set++-de-++str++, com um `set` vazio como valor default. +____ + +Para escrever aquela anotação precisamos também importar `ClassVar` do módulo `typing`. + +O decorador `@dataclass` não se importa com os tipos nas anotações, +exceto em dois casos, e este é um deles: se o tipo for `ClassVar`, +um campo de instância não será gerado para aquele atributo. + +O outro caso onde o tipo de um campo é relevante para `@dataclass` +é quando declaramos _variáveis apenas de inicialização_, nosso próximo tópico. + + +==== Variáveis de inicialização que não são campos + +Algumas((("@dataclass", "init-only variables")))((("variables", "init-only variables"))) +vezes pode ser necessário passar para `+__init__+` argumentos que não são campos de instância. +Tais argumentos são chamados "argumentos apenas de inicialização" (_init-only variables_) pela +https://fpy.li/3v[documentação de `dataclasses`]. +Para declarar um argumento desses, o módulo `dataclasses` oferece o pseudo-tipo `InitVar`, +que usa a mesma sintaxe de `typing.ClassVar`. +O exemplo dado na documentação é uma classe de dados com um campo inicializado +a partir de um banco de dados, e o objeto banco de dados precisa ser passado para o `+__init__+`. + +O <> mostra o código que ilustra a seção https://fpy.li/3v["Variáveis de inicialização apenas"]. + +[[initvar_ex]] +.Exemplo da documentação do módulo https://fpy.li/3v[`dataclasses`] +==== +[source, python] +---- +@dataclass +class C: + i: int + j: int | None = None + database: InitVar[DatabaseType | None] = None + + def __post_init__(self, database): + if self.j is None and database is not None: + self.j = database.lookup('j') + +c = C(10, database=my_database) +---- +==== + +Veja como o atributo `database` é declarado. +`InitVar` vai evitar que `@dataclass` trate `database` como um campo normal. +Ele não será definido como um atributo de instância, e a função `dataclasses.fields` não vai listá-lo. +Entretanto, `database` será um dos argumentos aceitos pelo `+__init__+` gerado, +e também será passado para o `+__post_init__+`. +Ao escrever aquele método é preciso adicionar o argumento correspondente à sua assinatura, +como mostra o <>. + +Esse longo tratamento de `@dataclass` cobriu os recursos mais importantes desse +decorador—alguns deles apareceram em seções anteriores, +como na <>, onde falamos em paralelo das três fábricas de classes de dados. +A https://fpy.li/3v[documentação de `dataclasses`] e a +https://fpy.li/pep526[PEP 526--Syntax for Variable Annotations (_Sintaxe para Anotações de Variáveis_)] +(EN) têm todos os detalhes. + +Na próxima seção apresento um exemplo mais completo com o `@dataclass`. + + +[[dc_resource_sec]] +==== Exemplo de @dataclass: o registro de recursos do Dublin Core + +Frequentemente as classes((("@dataclass", "example using", id="atdataexample05")))((("Dublin Core Schema"))) +criadas com o `@dataclass` vão ter mais campos que os exemplos muito curtos apresentados até aqui. +O https://fpy.li/5-12[Dublin Core] (EN) oferece a fundação para um exemplo mais típico de `@dataclass`. + +[quote, Dublin Core na Wikipedia] +____ +O Dublin Core é um esquema de metadados que visa descrever objetos digitais, +como videos, sons, imagens, textos e sites na Web. +Aplicações de Dublin Core utilizam XML e o RDF +(Resource Description Framework).footnote:[Fonte: O artigo https://fpy.li/3w[Dublin Core] na Wikipedia.] +____ + +O padrão define 15 campos opcionais; a classe `Resource`, no <>, usa 8 deles. + +[[resource_ex]] +._dataclass/resource.py_: código de `Resource`, uma classe baseada nos termos do Dublin Core +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/resource.py[tags=DATACLASS] +---- +==== +<1> Este `Enum` vai fornecer valores de um tipo seguro para o campo `Resource.type`. +<2> `identifier` é o único campo obrigatório. +<3> `title` é o primeiro campo com um default. +Isso obriga todos os campos abaixo dele a fornecerem defaults. +<4> O valor de `date` pode ser uma instância de `datetime.date` ou `None`. +<5> O default do campo `type` é `ResourceType.BOOK`. + + +O <> mostra um doctest, para demonstrar como um registro `Resource` aparece no código. + +[[resource_doctest_ex]] +._dataclass/resource.py_: código de `Resource`, uma classe baseada nos termos do Dublin Core +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/resource.py[tags=DOCTEST] +---- +==== + +O `+__repr__+` gerado pelo `@dataclass` é razoável, +mas podemos torná-lo mais legível. +Esse é o formato que queremos para `repr(book)`: + +[source, python] +---- +include::../code/05-data-classes/dataclass/resource_repr.py[tags=DOCTEST] +---- + +O <> é o código para o `+__repr__+`, produzindo o formato que aparece no trecho anterior. +Esse exemplo usa `dataclass.fields` para obter os nomes dos campos da classe de dados. + + +[[resource_repr_ex]] +.`dataclass/resource_repr.py`: código para o método `+__repr__+`, implementado na classe `Resource` do <> +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/resource_repr.py[tags=REPR] +---- +==== +<1> Inicializa a lista `res`, para criar a string de saída com o nome da classe e o parênteses abrindo. +<2> Para cada campo `f` na classe... +<3> ...obtém o atributo nomeado da instância. +<4> Anexa uma linha indentada com o nome do campo e `repr(value)`—é isso que o `!r` faz. +<5> Acrescenta um parênteses fechando. +<6> Cria uma string de múltiplas linhas a partir de `res`, e devolve essa string. + +Com esse exemplo, inspirado pelo espírito de Dublin, Ohio, +concluímos nosso passeio pelas fábricas de classes de dados de Python. + +Classes de dados são úteis, mas podem estar sendo usadas de forma excessiva em seu projeto. +A próxima seção explica isso.((("", startref="atdataexample05")))((("", startref="DCBatdataclass05"))) + +[[dataclass_code_smell_sec]] +=== A classe de dados como _cheiro no código_ + +Independente((("data class builders", "data class as code smell", id="DCBsmell05")))((("code smells", +id="codesmells05"))) de você implementar uma classe de dados escrevendo todo o código ou +aproveitando as facilidades oferecidas por alguma das fábricas de classes descritas nesse capítulo, +fique alerta: isso pode sinalizar um problema em seu design. + +No livro +https://fpy.li/42[_Refactoring: Improving the Design of Existing Code, 2nd ed._] +(Addison-Wesley), Martin Fowler e Kent Beck apresentam um catálogo de +"_cheiros no código_"footnote:[NT: _Code smell_ em geral não é traduzido na literatura em +português—uma tradução quase literal seria "fedor no código". +Uma tradução mais gentil pode ser "cheiro no código", adotado aqui. +Mais gentil e menos enviesada: um "cheiro no código" nem sempre é indicação de um problema.]—padrões +no código que podem indicar a necessidade de refatoração. +O verbete intitulado "Data Class" +(_Classe de Dados_) começa assim: + +[quote] +____ +Essas são classes que tem campos, métodos para obter e definir os campos, e nada mais. +Tais classes são recipientes burros de dados, +e muitas vezes são manipuladas de forma excessivamente detalhada por outras classes. +____ + +No site pessoal de Fowler, há um post muito esclarecedor chamado +https://fpy.li/5-14["Code Smell" (_Cheiro no Código_)]. +Esse texto é muito relevante para nossa discussão, pois o autor usa a _classe de dados_ +como um exemplo de _cheiro no código_, e sugere alternativas para lidar com ele. +Abaixo está a tradução integral daquele artigo.footnote:[Tenho a felicidade de ter +Martin Fowler como colega de trabalho na Thoughtworks, estão precisei de apenas 20 minutos para obter sua permissão.] + + +[[code_smell_essay]] +.Cheiros no Código +**** + +*De Martin Fowler* + +Um cheiro no código é um indicação superficial que frequentemente corresponde a um problema mais profundo no sistema. +O termo foi inventado por Kent Beck, enquanto ele me ajudava com meu livro, https://fpy.li/5-15[_Refactoring_]. + +A rápida definição acima contém um par de detalhes sutis. +Primeiro, um cheiro é, por definição, algo rápido de detectar—é "cheiroso", como eu disse recentemente. +Um método longo é um bom exemplo disso—basta olhar o código e ver mais de uma dúzia de linhas de Java para meu nariz se contrair. + +O segundo detalhe é que cheiros nem sempre indicam um problema. +Alguns métodos longos são bons. +É preciso ir mais fundo para ver se há um problema subjacente ali. +Cheiros não são inerentemente ruins por si só—eles frequentemente são o indicador de um problema, +não o problema propriamente dito. + +Os melhores cheiros são algo fácil de detectar e que, na maioria das vezes, leva a problemas realmente interessantes. +Classes de dados (classes contendo só dados e nenhum comportamento [próprio]) são um bom exemplo. +Você olha para elas e se pergunta que comportamento deveria fazer parte daquela classe. +Então você começa a refatorar, para incluir ali aquele comportamento. +Muitas vezes, algumas perguntas simples e essas refatorações iniciais são +um passo vital para transformar um objeto anêmico em alguma coisa que realmente tenha classe. + +Uma coisa boa sobre cheiros é sua facilidade de detecção por pessoas inexperientes, +mesmo aquelas pessoas que não conhecem o suficiente para avaliar se há mesmo um problema +ou, se existir, para corrigi-lo. +Soube de um líder de uma equipe de desenvolvimento que elege um "cheiro da semana", +e pede às pessoas que procurem aquele cheiro e o apresentem para colegas mais experientes. +Fazer isso com um cheiro por vez é uma ótima maneira de ensinar gradualmente +os membros da equipe a serem programadores melhores. + +**** + +A principal ideia da programação orientada a objetos é manter o comportamento e os dados juntos, +na mesma unidade de código: uma classe. +Se uma classe é largamente utilizada mas não tem qualquer comportamento próprio significativo, +é bem provável que o código que interage com as instâncias dessa classe esteja espalhado +(ou mesmo duplicado) em métodos e funções ao longo de todo +o sistema—uma receita para dores de cabeça na manutenção. +Por isso, as refatorações de Fowler para lidar com uma classe de dados envolvem +trazer responsabilidades de volta para a classe. + +Levando o que foi dito acima em consideração, +há alguns cenários comuns onde faz sentido ter uma classe de dados com pouco ou nenhum comportamento. + +==== A classe de dados como um esboço + +Nesse cenário, a classe de dados é uma implementação simplista inicial de uma classe, +para dar início a um novo projeto ou módulo. +Com o tempo, a classe deve ganhar seus próprios métodos, +deixando de depender de métodos de outras classes para operar sobre suas instâncias. +O esboço é temporário; ao final do processo, +sua classe pode se tornar totalmente independente da fábrica usada inicialmente para criá-la. + +Python também é muito usado para resolução rápida de problemas e para experimentação, +e nesses casos faz sentido preservar o esboço. + +==== A classe de dados como representação intermediária + +Uma classe de dados pode ser útil para criar registros que serão exportados para o JSON +ou algum outro formato de intercomunicação, ou para manter dados que acabaram de ser importados, +cruzando alguma fronteira do sistema. +Todas as fábricas de classes de dados de Python oferecem um método ou função +para converter uma instância em um `dict` simples, +e você sempre pode invocar o construtor com um `dict`, +usado para passar argumentos nomeados expandidos com `**`. +Um `dict` assim é muito similar a um registro JSON. + +Nesse cenário, as instâncias da classe de dados devem ser tratadas como objetos +imutáveis—mesmo que os campos sejam mutáveis, +não deveriam ser modificados nessa forma intermediária. +Mudá-los significa perder o principal benefício de manter os dados e o comportamento próximos. +Quando o processo de importação/exportação exigir mudança nos valores, +você deve implementar seus próprios métodos de fábrica, +em vez de usar os métodos "as dict" existentes ou os construtores padrão. + +Vamos agora mudar de assunto e aprender como escrever padrões que "casam" +com instâncias de classes arbitrárias, +não apenas com as sequências e mapeamentos que vimos nas seções +<> e +<>.((("", startref="DCBsmell05")))((("", startref="codesmells05"))) + +[[pattern_instances_sec]] +=== Pattern matching com instâncias de classes + +Padrões de classe((("data class builders", "pattern matching class instances", +id="DCBpattern05")))((("pattern matching", "pattern matching class instances", id="PMpattern05"))) +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 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. +Vamos estudá-las nessa ordem. + +==== Padrões de classe simples + +Já((("simple class patterns"))) vimos um exemplo de padrões de +classe simples usados como sub-padrões na <>: + +[source, python] +---- + case [str(name), _, _, (float(lat), float(lon))]: +---- + +//// +Leo wrote: + +By the way, there is an interesting corolary here: +if I want to do duck typing, +i.e. pattern match attributes of an instance of an arbitrary class, +I can pattern match with `object`: + + >>> class Foo: pass + >>> foo = Foo() + >>> foo.bar = 'baz' + >>> match foo: + ... case object(bar=b): + ... print(b) + ... + baz + +And that's regardless of `object` not taking keyword arguments: + + >>> object(bar='baz') + Traceback (most recent call last): + File "", line 1, in + object(bar='baz') + TypeError: object() takes no arguments + +//// + +Aquele padrão casa com uma sequência de quatro itens, +onde o primeiro item deve ser uma instância de `str` e +o último item deve ser uma sequência de duas instâncias de `float`. + +A sintaxe dos padrões de classe se parece com a invocação de um construtor. +Abaixo temos um padrão de classe que "casa" com valores `float` +sem vincular uma variável (o corpo do `case` pode referir-se a `x` diretamente, se necessário): + +[source, python] +---- + match x: + case float(): + do_something_with(x) +---- + +Mas isso aqui possivelmente será um bug no seu código: + +[source, python] +---- + match x: + case float: # DANGER!!! + do_something_with(x) +---- + +No exemplo anterior, `case float:` "casa" com qualquer sujeito, +pois Python entende `float` como uma variável, que é então vinculada ao sujeito. + +A sintaxe `float(x)` do padrão simples é um caso especial que se aplica apenas +a onze tipos embutidos "abençoados", listados no final da seção +https://fpy.li/5-16["Class Patterns" (_Padrões de Classe_)] (EN) da +https://fpy.li/pep634[PEP 634—Structural Pattern Matching: +Specification] (_Pattern Matching Estrutural: Especificação_). + +[source] +---- +bool bytearray bytes dict float frozenset int list set str tuple +---- + +Nessas classes, a variável que parece um argumento do construtor—por exemplo, +o `x` em `float(x)`—é vinculada a toda a instância do sujeito ou à parte do sujeito que "casa" com um sub-padrão, +como exemplificado por `str(name)` no padrão de sequência que vimos antes: + +[source, python] +---- + case [str(name), _, _, (float(lat), float(lon))]: +---- + +Se a classe não é um daqueles onze tipos embutidos abençoados, +então essas variáveis parecidas com argumentos representam padrões +a serem casados com atributos de uma instância daquela classe. + + +[[keyword_class_patterns_sec]] +==== Padrões de classe nomeados + +Para((("keyword class patterns"))) entender como usar padrões de classe nomeados, +observe a classe `City` e suas cinco instâncias no <>, abaixo. + +[[ex_cities_match]] +.A classe `City` e algumas instâncias +==== +[source, python] +---- +include::../code/05-data-classes/match_cities.py[tags=CITY] +---- +==== + +Dadas essas definições, a seguinte função devolve uma lista de cidades asiáticas: + +[source, python] +---- +include::../code/05-data-classes/match_cities.py[tags=ASIA] +---- + +O padrão `City(continent='Asia')` encontra qualquer instância de `City` +onde o atributo `continent` seja igual a `'Asia'`, +independentemente do valor dos outros atributos. + +Para coletar o valor do atributo `country`, você poderia escrever: + +[source, python] +---- +include::../code/05-data-classes/match_cities.py[tags=ASIA_COUNTRIES] +---- + +O padrão `City(continent='Asia', country=cc)` encontra as mesmas cidades asiáticas, +como antes, mas agora a variável `cc` está vinculada ao atributo `country` da instância. +Isso inclusive funciona se a variável do padrão também se chamar `country`: + +[source, python] +---- + match city: + case City(continent='Asia', country=country): + results.append(country) +---- + +Padrões de classe nomeados são bastante legíveis, +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. + +[[positional_class_patterns_sec]] +==== Padrões de classe posicionais + +Dadas((("positional class patterns"))) as definições do <>, +a seguinte função devolveria uma lista de cidades asiáticas, +usando um padrão de classe posicional: + +[source, python] +---- +include::../code/05-data-classes/match_cities.py[tags=ASIA_POSITIONAL] +---- + +O padrão `City('Asia')` encontra qualquer instância de `City` na qual +o valor do primeiro atributo seja `Asia`, independentemente do valor dos outros atributos. + +Se você quiser obter o valor do atributo `country`, poderia escrever: + +[source, python] +---- +include::../code/05-data-classes/match_cities.py[tags=ASIA_COUNTRIES_POSITIONAL] +---- + +O padrão `City('Asia', _, country)` encontra as mesmas cidades de antes, +mas agora variável `country` está vinculada ao terceiro atributo da instância. + +Mencionei o "primeiro" e o "terceiro" atributo, mas de onde vem este ordenamento? + +`City` (ou qualquer classe) funciona com padrões posicionais graças a +um atributo de classe especial chamado `+__match_args__+`, +que as fábricas de classe vistas nesse capítulo criam automaticamente. +Esse é o valor de `+__match_args__+` na classe `City`: + +[source, python] +---- +>>> City.__match_args__ +('continent', 'name', 'country') +---- + +Como se vê, `+__match_args__+` declara os nomes dos atributos na ordem +em que eles serão usados em padrões posicionais. + +Na <> vamos escrever código para definir +`+__match_args__+` em uma classe que criaremos sem a ajuda de uma fábrica de classes. + +[TIP] +==== +Você pode combinar argumentos nomeados e posicionais em um padrão. +Alguns, mas não todos, os atributos de instância disponíveis para o _match_ +podem estar listados no `+__match_args__+`. +Dessa forma, algumas vezes pode ser necessário usar argumentos nomeados em um padrão, +além dos argumentos posicionais. +==== + +Chegou a hora de resumir o capítulo.((("", startref="DCBpattern05")))((("", startref="PMpattern05"))) + + +=== Resumo do capítulo + +O((("data class builders", "overview of"))) tópico principal desse capítulo foram +as fábricas de classes de dados `collections.namedtuple`, `typing.NamedTuple`, +e `dataclasses.dataclass`. +Vimos como cada uma delas gera classes de dados a partir de descrições, +fornecidas como argumentos a uma função fábrica ou, no caso das duas últimas, +a partir de uma declaração `class` com dicas de tipo. +Especificamente, ambas as variantes de tupla produzem subclasses de `tuple`, +acrescentando apenas a capacidade de acessar os campos por nome, +e criando também um atributo de classe `_fields`, +que lista os nomes dos campos na forma de uma tupla de strings. + +A seguir colocamos lado a lado os principais recursos de cada uma das três fábricas de classes, +incluindo como extrair dados da instância como um `dict`, +como obter os nomes e valores default dos campos, +e como criar uma nova instância a partir de uma instância existente. + +Isso levou ao nosso primeiro contato com dicas de tipo, +especialmente aquelas usadas para anotar atributos em uma declaração `class`, +usando a notação introduzida no Python 3.6 com a +https://fpy.li/pep526[PEP 526—Syntax for Variable Annotations (_Sintaxe para Anotações de Variáveis_)] (EN). +O aspecto provavelmente mais surpreendente das dicas de tipo em geral +é o fato delas não terem efeito durante a execução. +Python continua sendo uma linguagem dinâmica. +Ferramentas externas, como o Mypy, +são necessárias para aproveitar a informação de tipagem na +detecção de erros via análise estática do código-fonte. +Após um resumo básico da sintaxe da PEP 526, +estudamos os efeitos das anotações em uma classe simples e +em classes criadas por `typing.NamedTuple` e por `@dataclass`. + +A seguir falamos sobre os recursos mais usados dentre os oferecidos por `@dataclass`, +e sobre a opção `default_factory` da função `dataclasses.field`. +Também demos uma olhada nas dicas de pseudo-tipo especiais `typing.ClassVar` e +`dataclasses.InitVar`, importantes no contexto das classes de dados. +Esse tópico central foi concluído com um exemplo baseado no schema Dublin Core, +ilustrando como usar `dataclasses.fields` para iterar sobre os atributos de uma instância de +`Resource` em um `+__repr__+` customizado. + +Então alertamos contra os possíveis usos abusivos das classes de dados, +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 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. + +[[dataclass_further_sec]] +=== Para saber mais + +A documentação((("data class builders", "further reading on"))) +padrão de Python para as fábricas de classes de dados vistas aqui é muito boa, e inclui muitos pequenos exemplos. + +Em especial para `@dataclass`, a maior parte da +https://fpy.li/pep557[PEP 557—Data Classes (_Classes de Dados_)] (EN) +foi copiada para a documentação do módulo https://fpy.li/3s[`dataclasses`]. +Entretanto, algumas seções informativas da +https://fpy.li/pep557[PEP 557] não foram copiadas, incluindo +https://fpy.li/5-18["Why not just use namedtuple?" (_Por que simplesmente não usar namedtuple?_)], +https://fpy.li/5-19["Why not just use typing.NamedTuple?" (_Por que simplesmente não usar typing.NamedTuple?_)], +e a seção +https://fpy.li/5-20["Rationale" (_Justificativa_)], que termina com a seguinte _Q&A_: + +[quote, Eric V. Smith, PEP 557 "Justificativa"] +____ +Quando não é apropriado usar Classes de Dados? + +Quando for exigida compatibilidade da API com tuplas de dicts. +Quando for exigida validação de tipo além daquela oferecida +pelas PEPs 484 e 526, ou quando for exigida validação ou conversão de valores. +____ + +Em +https://fpy.li/5-21[_RealPython.com_], +Geir Arne Hjelle escreveu +https://fpy.li/5-22[_Ultimate guide to data classes in Python 3.7_)] +(EN) guia bem completo. + +Na PyCon US 2018, Raymond Hettinger apresentou +https://fpy.li/5-23["Dataclasses: The code generator to end all code generators"] +(EN, video). + +Para mais recursos e funcionalidade avançada, incluindo validação, o +https://fpy.li/5-24[projeto _attrs_] (EN), +liderado por Hynek Schlawack, surgiu anos antes de `dataclasses` e oferece mais facilidades, +com a promessa de "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_)". + +A influência do _attrs_ sobre o `@dataclass` é reconhecida por Eric V. Smith na PEP 557. +Isso provavelmente inclui a mais importante decisão de Smith sobre a API: +o uso de um decorador de classe em vez de uma classe base ou de +uma metaclasse para realizar a tarefa. + +Glyph—fundador do projeto Twisted—escreveu uma excelente introdução à _attrs_ em +https://fpy.li/5-25["The One Python Library Everyone Needs" (_A Biblioteca Python que Todo Mundo Precisa Ter_)] (EN). +A documentação da _attrs_ inclui uma https://fpy.li/5-26[discussão sobre alternativas]. + +O autor de livros, instrutor e cientista maluco da computação Dave Beazley escreveu o +https://fpy.li/5-27[_cluegen_], um outro gerador de classes de dados. +Se você já assistiu a alguma palestra do David, +sabe que ele é um mestre na metaprogramação Python a partir de princípios básicos. +Então achei inspirador descobrir, no arquivo _README.md_ do _cluegen_, +o caso de uso concreto que o motivou a criar uma alternativa ao `@dataclass` de Python, +e sua filosofia de apresentar uma abordagem para resolver o problema, +ao invés de fornecer uma ferramenta: a ferramenta pode inicialmente ser mais rápida de usar, +mas a abordagem é mais flexível e pode ir tão longe quanto você queira. + +Sobre a _classe de dados_ como um cheiro no código, +a melhor fonte que encontrei foi o livro de Martin Fowler, _Refactoring_ ("Refatorando"), 2ª ed. +A versão mais recente não traz a citação da epígrafe deste capítulo, +"Classes de dados são como crianças...", mas apesar disso é a melhor edição do livro mais famoso de Fowler, +em especial para pythonistas, pois os exemplos são em JavaScript moderno, +que é mais próximo de Python que de Java—a linguagem usada na primeira edição. + +O site https://fpy.li/5-28[_Refactoring Guru_ (Guru da Refatoração)] também tem uma descrição do +https://fpy.li/5-29[data class code smell (_classe de dados como cheiro no código_)]. + + + +.Ponto de vista +**** + +O((("data class builders", "Soapbox discussion")))((("Soapbox sidebars", "@dataclass"))) +verbete para https://fpy.li/5-30["Guido"] no +https://fpy.li/5-31["The Jargon File"] (EN) +é sobre Guido van Rossum. +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 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..." +____ + +Por um longo tempo, uma das peças ausentes da sintaxe de Python +foi uma forma rápida e padronizada de declarar atributos de instância em uma classe. +Muitas linguagens orientadas a objetos incluem esse recurso. +Aqui está parte da definição da classe `Point` em Smalltalk: + +---- +Object subclass: #Point + instanceVariableNames: 'x y' + classVariableNames: '' + package: 'Kernel-BasicObjects' +---- + +A segunda linha lista os nomes dos atributos de instância `x` e `y`. +Se existissem atributos de classe, eles estariam na terceira linha. + +Python sempre teve uma forma fácil de declarar um atributo de classe, +se ele tiver um valor inicial. +Mas atributos de instância são mais comuns, +e os programadores Python têm sido obrigados a olhar dentro do método `+__init__+` para encontrá-los, +sempre temerosos de que podem existir atributos de instância sendo criados +em outro lugar na classe—ou mesmo por funções e métodos de outras classes. + +Agora temos o `@dataclass`, viva! + +Mas ele traz seus próprios problemas + +Primeiro, quando você usa `@dataclass`, as dicas de tipo não são opcionais. +Pelos últimos sete anos, desde a +https://fpy.li/pep484[PEP 484—Type Hints (_Dicas de Tipo_)] (EN), +prometeram que elas sempre seriam opcionais. +Agora temos um novo recurso importante na linguagem que exige dicas de tipo. +Se você não gosta de toda essa tendência de tipagem estática, +pode querer usar a https://fpy.li/5-24[`attrs`] em vez de `@dataclass`. + +Em segundo lugar, a sintaxe da https://fpy.li/pep526[PEP 526] (EN) +para anotar atributos de instância e de classe inverte a convenção consagrada para declarações de classe: +tudo que era declarado no nível superior de um bloco `class` era um atributo de classe +(métodos também são atributos de classe). +Com a PEP 526 e o `@dataclass`, +qualquer atributo declarado no nível superior com uma dica de tipo se torna um atributo de instância: + +[source, python] +---- + @dataclass + class Spam: + repeat: int # instance attribute +---- + +Aqui, `repeat` também é um atributo de instância: + +[source, python] +---- + @dataclass + class Spam: + repeat: int = 99 # instance attribute +---- + +Mas se não houver dicas de tipo, +subitamente estamos de volta ao cenário em que declarações +no nível superior da classe pertencem apenas à classe: + +[source, python] +---- + @dataclass + class Spam: + repeat = 99 # class attribute! +---- + +Por fim, se você quiser anotar aquele atributo de classe com um tipo, +não pode usar tipos comuns, porque então ele se tornará um atributo de instância. +Você precisa recorrer àquela anotação usando o pseudo-tipo `ClassVar`, +que é uma gambiarra: + +[source, python] +---- + @dataclass + class Spam: + repeat: ClassVar[int] = 99 # aargh! +---- + +Aqui estamos falando sobre uma exceção da exceção da regra. +Me parece algo muito pouco pythônico. + +Não tomei parte nas discussões que levaram à PEP 526 ou à +https://fpy.li/pep557[PEP 557—Data Classes (_Classes de Dados_)], +mas aqui está uma sintaxe alternativa que eu gostaria de ver: + +[source, python] +---- +@dataclass +class HackerClubMember: + .name: str # <1> + .guests: list = field(default_factory=list) + .handle: str = '' + + all_handles = set() # <2> +---- +<1> Atributos de instância devem ser declarados com um prefixo `.` (ponto). +<2> Qualquer nome de atributo que não tenha um prefixo `.` +é um atributo de classe (como sempre foram). + +A gramática da linguagem teria que mudar para acomodar esta sintaxe. +Mas penso que é mais legível, e evita o problema da exceção-da-exceção. + +Queria poder pegar a máquina do tempo de Gudo emprestada e voltar a 2017, +para convencer os mantenedores a aceitarem essa ideia. +**** diff --git a/online/cap06.adoc b/online/cap06.adoc new file mode 100644 index 00000000..d7f02265 --- /dev/null +++ b/online/cap06.adoc @@ -0,0 +1,1346 @@ +[[ch_refs_mut_mem]] +== Referências, mutabilidade, e memória +:example-number: 0 +:figure-number: 0 + +[quote, Adaptado de “Alice Através do Espelho e o que Ela Encontrou Lá”, de Lewis Caroll] +____ +“Você está triste,” disse o Cavaleiro em um tom de voz ansioso: +“deixe eu cantar para você uma canção reconfortante. […] +O nome da canção se chama ‘OLHOS DE HADOQUE’.” + +“Oh, esse é o nome da canção?,” disse Alice, tentando parecer interessada. + +“Não, você não entendeu,” retorquiu o Cavaleiro, um pouco irritado. +“É assim que o nome É CHAMADO. +O nome na verdade é ‘O ENVELHECIDO HOMEM VELHO.‘” +____ + +Alice e o Cavaleiro((("object references", "distinction between objects and their names"))) +dão o tom do que veremos nesse capítulo. +O tema é a distinção entre objetos e seus nomes: +o nome não é o objeto, o nome é outra coisa. + +Começamos o capítulo apresentando uma metáfora para variáveis em Python: +variáveis são rótulos, não caixas. +Mesmo que você já domine variáveis de referência, +a analogia pode ainda ser útil para ilustrar questões de _aliasing_ (“apelidamento”) +para outra pessoa. + +Depois discutimos os conceitos de identidade, valor e apelidamento de objetos. +Uma característica surpreendente das tuplas é revelada: +elas são imutáveis, mas seus valores podem mudar. +Isso leva a uma discussão sobre cópias rasas e profundas. +Referências e parâmetros de funções são o tema seguinte: +o problema do parâmetro com default mutável e +formas seguras de lidar com argumentos mutáveis passados para nossas funções por clientes. + +As últimas seções do capítulo tratam de coleta de lixo (_garbage collection_), +a instrução `del` e de algumas otimizações com com objetos imutáveis em Python. + +É um capítulo bastante árido, +mas os tópicos tratados podem explicar muitos bugs sutis em programas reais em Python, +além de boas práticas para evitá-los. + +=== Novidades neste capítulo + +Os tópicos tratados aqui são muito estáveis e fundamentais. +Não foi introduzida nenhuma mudança digna de nota nesta segunda edição. + +Acrescentei um exemplo usando `is` para testar a existência de um objeto sentinela, +e um aviso sobre o mau uso do operador `is` no final da <>. + +Este capítulo estava na Parte IV, mas decidi abordar esses temas mais cedo, +pois eles funcionam melhor como o encerramento da Parte II, “Estruturas de Dados”, +que como abertura de “Práticas de Orientação a Objetos" + +[NOTE] +==== +A seção sobre “Referências Fracas” da primeira edição deste livro agora é +https://fpy.li/weakref[um post em _https://fluentpython.com_]. +==== + +Vamos começar desaprendendo que uma variável é como uma caixa onde você guarda dados. + + +=== Variáveis não são caixas + +Em 1997, ((("object references", "variables as labels versus boxes", id="ORvar06")))((("variables", +"as labels versus boxes", secondary-sortas="labels versus boxes", id="Vlabel06"))) +fiz um curso de verão sobre Java no MIT. +A professora, Lynn Steinfootnote:[Lynn Andrea Stein é uma aclamada educadora de ciências da computação. +Ela https://fpy.li/6-1[atualmente leciona na Olin College of Engineering (EN)].] +apontou que a metáfora comum, de “variáveis como caixas”, +na verdade atrapalha o entendimento de variáveis de +referência em linguagens orientadas a objetos. +As variáveis em Python são como variáveis de referência em Java; +uma metáfora melhor é pensar em uma variável como uma etiqueta +que dá nome a um objeto. +O exemplo e a figura a seguir ajudam a entender o motivo disso. + +O <> é uma interação simples que não pode ser explicada por “variáveis como caixas”. + +[[ex_a_b_refs]] +.As variáveis `a` e `b` referem-se à mesma lista, não a cópias da lista. +==== +[source, python] +---- +>>> a = [1, 2, 3] <1> +>>> b = a <2> +>>> a.append(4) <3> +>>> b <4> +[1, 2, 3, 4] +---- +==== +<1> Cria uma lista [1, 2, 3] e a vincula à variável `a`. +<2> Vincula a variável `b` ao mesmo valor referenciado por `a`. +<3> Modifica a lista referenciada por `a`, anexando um novo item. +<4> É possível ver o efeito através da variável `b`. +Se você pensar em `b` como uma caixa que guardava uma cópia de +`[1, 2, 3]` da caixa `a`, este comportamento não faz sentido. + +A <> explica por que a metáfora da caixa está errada em Python, +enquanto etiquetas apresentam uma imagem mais útil para entender como variáveis funcionam. + +[[var-boxes-x-labels]] +.Se você imaginar variáveis como caixas, não é possível entender a atribuição em Python; por outro lado, imagine variáveis como etiquetas autocolantes e o <> é facilmente explicável. +image::../images/flpy_0601.png[Boxes and labels diagram] + +Assim, a instrução `b = a` não copia o conteúdo de uma caixa `a` para uma caixa `b`. +Ela cola uma nova etiqueta `b` no objeto que já tem a etiqueta `a`. + +A professora Stein também falava sobre atribuição de uma maneira bastante específica. +Por exemplo, quando discutia sobre um objeto representando uma gangorra em uma simulação, +ela dizia: +“A variável g foi atribuída à gangorra”, mas nunca “A gangorra foi atribuída à variável g”. +Com variáveis de referência, +faz mais sentido dizer que a variável é atribuída a um objeto, não o contrário. +Afinal, o objeto é criado antes da atribuição. +O <> prova que o lado direito de uma atribuição é processado primeiro. + +Já que o verbo “atribuir” é usado de diferentes maneiras, +“vincular” é uma alternativa melhor: +a declaração de atribuição em Python `x = …` vincula o nome `x` +ao objeto criado ou referenciado no lado direito. +E o objeto precisa existir antes que um nome possa ser vinculado a ele, +como demonstra o <>. + +[[ex_var_assign_after]] +.Variáveis são vinculadas a objetos somente após os objetos serem criados +==== +[source, python] +---- +>>> class Gizmo: +... def __init__(self): +... print(f'Gizmo id: {id(self)}') +... +>>> x = Gizmo() +Gizmo id: 4301489152 <1> +>>> y = Gizmo() * 10 <2> +Gizmo id: 4301489432 <3> +Traceback (most recent call last): + File "", line 1, in +TypeError: unsupported operand type(s) for *: 'Gizmo' and 'int' +>>> +>>> dir() <4> +['Gizmo', '__builtins__', '__doc__', '__loader__', '__name__', +'__package__', '__spec__', 'x'] +---- +==== +<1> A saída `Gizmo id: …` é um efeito colateral da criação de uma instância de `Gizmo`. +<2> Multiplicar uma instância de `Gizmo` levanta uma exceção. +<3> Aqui está a prova de que um segundo `Gizmo` foi de fato instanciado +antes que a multiplicação fosse tentada. +<4> Mas a variável `y` nunca foi criada, porque a exceção aconteceu +enquanto a parte direita da atribuição estava sendo executada. + +[TIP] +==== +Para entender uma atribuição em Python, leia primeiro o lado direito: +é ali que o objeto é criado ou recuperado. +Depois disso, a variável do lado esquerdo é vinculada ao objeto, +como uma etiqueta colada a ele. +Esqueça as caixas. +==== + +Como variáveis são apenas meras etiquetas, +nada impede que um objeto tenha várias etiquetas vinculadas a si. +Quando isso acontece, você tem _apelidos_ (aliases), +nosso próximo tópico.((("", startref="ORvar06")))((("", startref="Vlabel06"))) + + +=== Identidade, igualdade e apelidos + +Lewis Carroll((("object references", "aliasing", id="ORalias06")))((("aliasing", id="alias06"))) +é o pseudônimo literário do Prof. +Charles Lutwidge Dodgson. +O Sr. +Carroll não é apenas igual ao Prof. +Dodgson, +eles são exatamente a mesma pessoa. <> +expressa essa ideia em Python. + +[[ex_equal_and_same]] +.`charles` e `lewis` se referem ao mesmo objeto +==== +[source, python] +---- +>>> charles = {'name': 'Charles L. Dodgson', 'born': 1832} +>>> lewis = charles <1> +>>> lewis is charles +True +>>> id(charles), id(lewis) <2> +(4300473992, 4300473992) +>>> lewis['balance'] = 950 <3> +>>> charles +{'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950} +---- +==== +<1> `lewis` é um apelido para `charles`. +<2> O operador `is` e a função `id` confirmam essa afirmação. +<3> Adicionar um item a `lewis` é o mesmo que adicionar um item a `charles`. + +Entretanto, suponha que um impostor—vamos chamá-lo de Dr. +Alexander Pedachenko—diga +que é o verdadeiro Charles L. Dodgson, nascido em 1832. +Suas credenciais podem ser as mesmas, +mas o Dr. +Pedachenko não é o Prof. +Dodgson. <> ilustra esse cenário. + +[[alias_x_copy]] +.`charles` e `lewis` estão vinculados ao mesmo objeto; `alex` está vinculado a um objeto diferente de valor igual. +image::../images/flpy_0602.png[Alias x copy diagram] + +O <> constrói e testa o objeto `alex` como apresentado em <>. + +[[ex_equal_not_same]] +.`alex` e `charles` são iguais quando comparados, mas `alex` _não é_ `charles` +==== +[source, python] +---- +>>> alex = {'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950} <1> +>>> alex == charles <2> +True +>>> alex is not charles <3> +True +---- +==== +<1> `alex` é uma referência a um objeto que é uma réplica do objeto vinculado a `charles`. +<2> Os objetos são iguais quando comparados devido à implementação de `+__eq__+` na classe `dict`. +<3> Mas são objetos distintos. +Essa é a forma pythônica de escrever +a negação de uma comparação de identidade: `a is not b`. + +<> é um exemplo de _apelidamento_ (aliasing). +Naquele código, `lewis` e `charles` são apelidos: duas variáveis vinculadas ao mesmo objeto. +Por outro lado, `alex` não é um apelido para `charles`: +essas variáveis estão vinculadas a objetos diferentes. +Os +((("== (equality) operator")))((("equality (==) operator")))((("comparison operators"))) +objetos vinculados a `alex` e `charles` tem o mesmo +__valor__ -- é isso que `==` compara -- mas têm identidades diferentes. + +Na https://fpy.li/2x[Referência da Linguagem Python], está escrito: + +[quote] +____ +A identidade de um objeto nunca muda após ele ter sido criado; +você pode pensar nela como o endereço do objeto na memória. +O operador `is` compara a identidade de dois objetos; a função +((("functions", "id() function")))((("id() function"))) `id()` +retorna um inteiro representando essa identidade. +____ + +O verdadeiro significado do `id` de um objeto depende da implementação da linguagem. +Em CPython, `id()` retorna o endereço de memória do objeto, +mas outro interpretador Python pode retornar algo diferente. +O ponto fundamental é que o `id` será sempre um valor numérico único, +e ele nunca mudará durante a vida do objeto. + +Na prática, raramente usamos a função `id()` quando programamos. +A verificação de identidade é feita, na maior parte das vezes, com o operador `is`, +que compara os IDs dos objetos, então nosso código não precisa chamar `id()` +explicitamente. +A seguir falamos sobre `is` versus `==`. + +[TIP] +==== +Para o revisor técnico Leonardo Rochael, +o uso mais frequente de `id()` ocorre durante o processo de debugging, +quando o `repr()` de dois objetos são semelhantes, +mas você precisa saber se duas referências são apelidos ou apontam para objetos diferentes. +Se as referências estão em contextos diferentes--por exemplo, em stack frames +diferentes--pode não ser viável usar `is`. +==== + + +[[choosing_eq_v_is_sec]] +==== Escolhendo Entre == e is + +O operador ((("is operator"))) `==` compara os valores de objetos (os dados que eles contêm), +enquanto `is` compara suas identidades. + +Quando estamos programando, em geral, nos preocupamos mais com os valores do +que com as identidades dos objetos, +então `==` aparece com mais frequência que `is` em programas Python. + +Entretanto, se você estiver comparando uma variável com um singleton (um objeto único) +faz mais sentido usar `is`. +O caso mais comum é checar se a variável está vinculada a `None`. +Esta é a forma recomendada de fazer isso: + +[source, python] +---- +x is None +---- + +E a forma apropriada de escrever sua negação é: + +[source, python] +---- +x is not None +---- + +`None` é o singleton mais comum que testamos com `is`. +Objetos sentinela são outro exemplo de singletons que testamos com `is`. +Veja um modo de criar e testar um objeto sentinela: + +[source, python] +---- +END_OF_DATA = object() +# ... many lines +def traverse(...): + # ... more lines + if node is END_OF_DATA: + return + # etc. +---- + +O operador `is` é mais rápido que `==`, pois não pode ser sobrecarregado. +Daí Python não precisa encontrar e invocar métodos especiais para calcular seu resultado +e o processamento é tão simples quanto comparar dois IDs, que são números inteiros. +Por outro lado, `a == b` é açúcar sintático para `+a.__eq__(b)+`. +O método `+__eq__+`, herdado de `object`, compara os IDs dos objetos, +então produz o mesmo resultado de `is`. +Mas a maioria dos tipos embutidos sobrescreve `+__eq__+` com implementações mais úteis, +que levam em consideração os valores dos atributos dos objetos. +A determinação da igualdade pode envolver muito processamento--por exemplo, +quando se comparam coleções grandes ou estruturas aninhadas com muitos níveis. + +[WARNING] +==== +Normalmente estamos mais interessados na igualdade que na identidade de objetos, +por isso o operador `==` é mais utilizado que `is`. +O caso mais comum para uso de `is` é comparar com `None`. +O `is` também é útil para testar valores de classes derivadas de `enum.Enum`. +Se não estiver seguro, use `==`. Em geral, é o que você quer, +e ele também funciona com `None` e valores de `Enum`, +ainda que seja um pouco mais lento. +==== + +Para concluir essa discussão de identidade versus igualdade, +vamos ver como o tipo notoriamente imutável `tuple` não é assim tão invariável quanto você poderia supor. + + +[[tuple_relative_immutable_sec]] +==== A imutabilidade relativa das tuplas + +As tuplas, como((("tuples", "relative immutability of"))) a maioria das coleções em +Python -- lists, dicts, sets, etc..— são contêineres: +armazenam referências para objetos.footnote:[Ao contrário de sequências +planas de tipo único, como `str`, `byte` e `array.array`, +que não contêm referências e sim seu conteúdo -- caracteres, bytes e números -- armazenado +em um espaço contíguo de memória.] + +Se os itens referenciados forem mutáveis, eles podem mudar, mesmo que tupla em si não mude. +Em outras palavras, a imutabilidade das tuplas, refere-se apenas ao conteúdo interno da +estrutura de dados `tuple` (isto é, as referências que ela armazena), +e não se estende aos objetos referenciados. + +O <> ilustra uma situação em que o valor de uma tupla muda +como resultado de mudanças em um objeto mutável ali referenciado. +O que não pode nunca mudar em uma tupla é a identidade dos itens que ela contém. + +[[ex_mutable_tuples]] +.`t1` e `t2` inicialmente são iguais, mas a mudança em um item mutável dentro da tupla `t1` as torna diferentes +==== +[source, python] +---- +>>> t1 = (1, 2, [30, 40]) <1> +>>> t2 = (1, 2, [30, 40]) <2> +>>> t1 == t2 <3> +True +>>> id(t1[-1]) <4> +4302515784 +>>> t1[-1].append(99) <5> +>>> t1 +(1, 2, [30, 40, 99]) +>>> id(t1[-1]) <6> +4302515784 +>>> t1 == t2 <7> +False +---- +==== +<1> `t1` é imutável, mas `t1[-1]` é mutável. +<2> Cria a tupla `t2`, cujos itens são iguais àqueles de `t1`. +<3> Apesar de serem objetos distintos,`t1` e `t2` são iguais quando comparados, como esperado. +<4> Obtém o `id` da lista na posição `t1[-1]`. +<5> Modifica diretamente a lista `t1[-1]`. +<6> O `id` de `t1[-1]` não mudou, apenas seu valor. +<7> `t1` e `t2` agora são diferentes + +Essa imutabilidade relativa das tuplas está por trás do enigma da <>. +Essa também é razão pela qual não é possível gerar o hash de algumas tuplas, +como vimos na <>. + +A distinção entre igualdade e identidade tem outras implicações quando você precisa copiar um objeto. +Uma cópia é um objeto igual com um `id` diferente. +Mas se um objeto contém outros objetos, +é preciso que a cópia duplique os objetos internos ou eles podem ser compartilhados? +Não há uma resposta única. +A seguir discutimos esse ponto.((("", startref="ORalias06")))((("", startref="alias06"))) + +=== A princípio, cópias são rasas + +A ((("object references", "shallow copies", id="ORshallow06")))((("shallow copies", +id="shallow06")))((("copies", "shallow", id="Cshallow06")))((("lists", "shallow copies of", +id="Lshallow06")))forma mais fácil de copiar uma lista +(ou a maioria das coleções mutáveis nativas) é usando o construtor padrão do próprio tipo. +Por exemplo: + + +[source, python] +---- +>>> l1 = [3, [55, 44], (7, 8, 9)] +>>> l2 = list(l1) <1> +>>> l2 +[3, [55, 44], (7, 8, 9)] +>>> l2 == l1 <2> +True +>>> l2 is l1 <3> +False +---- +<1> `list(l1)` cria uma cópia de `l1`. +<2> As cópias são iguais... +<3> ...mas se referem a dois objetos diferentes. + +Para listas e outras sequências mutáveis, o atalho `l2 = l1[:]` também cria uma cópia. + +Contudo, tanto o construtor quanto `[:]` produzem uma _cópia rasa_ (shallow copy). +Isto é, o contêiner externo é duplicado, mas a cópia é preenchida com referências +para os mesmos itens contidos no contêiner original. +Isso economiza memória e não causa qualquer problema se todos os itens forem imutáveis. +Mas se existirem itens mutáveis, isso pode gerar surpresas desagradáveis. + +No <> criamos uma lista contendo outra lista e uma tupla, +e então fazemos algumas mudanças para ver como isso afeta os objetos referenciados. + +[TIP] +==== +Se você tem um computador conectado à internet disponível, +recomendo fortemente que você assista à animação interativa do +<> em https://fpy.li/6-3[Online Python Tutor]. +No momento em que escrevo, o link direto para um exemplo pronto no _pythontutor.com_ +não estava funcionando de forma estável. +Mas a ferramenta é ótima, então vale a pena gastar seu tempo copiando e colando o código. +==== + + +[[ex_shallow_copy]] +.Criando uma cópia rasa de uma lista contendo outra lista; copie e cole esse código para vê-lo animado no Online Python Tutor +==== +[source, python] +---- +l1 = [3, [66, 55, 44], (7, 8, 9)] +l2 = list(l1) # <1> +l1.append(100) # <2> +l1[1].remove(55) # <3> +print('l1:', l1) +print('l2:', l2) +l2[1] += [33, 22] # <4> +l2[2] += (10, 11) # <5> +print('l1:', l1) +print('l2:', l2) +---- +==== +<1> `l2` é uma cópia rasa de `l1`. Este estado está representado em <>. +<2> Concatenar `100` a `l1` não tem qualquer efeito sobre `l2`. +<3> Aqui removemos `55` da lista interna `l1[1]`. Isso afeta `l2`, +pois `l2[1]` está associado à mesma lista em `l1[1]`. +<4> Para um objeto mutável como a lista referida por `l2[1]`, +o operador `+=` altera a lista diretamente. +Essa mudança é visível em `l1[1]`, +que é um apelido para `l2[1]`. +<5> `+=` em uma tupla cria uma nova tupla e reassocia a variável `l2[2]` a ela. +Isso é equivalente a fazer `l2[2] = l2[2] + (10, 11)`. +Agora as tuplas na última posição de `l1` e `l2` não são mais o mesmo objeto. +Veja a <>. + +[[shallow_copy1]] +.Estado do programa imediatamente após a atribuição `l2 = list(l1)` em <>. `l1` e `l2` se referem a listas diferentes, mas as listas compartilham referências para os mesmos objetos internos, a lista `[66, 55, 44]` e para a tupla `(7, 8, 9)`. (Diagrama gerado pelo Online Python Tutor) +image::../images/flpy_0603.png[References diagram] + +A saída de <> é <>, +e o estado final dos objetos está representado em <>. + +[[ex_shallow_copy_out]] +.Saída de <> +==== +[source, python] +---- +l1: [3, [66, 44], (7, 8, 9), 100] +l2: [3, [66, 44], (7, 8, 9)] +l1: [3, [66, 44, 33, 22], (7, 8, 9), 100] +l2: [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)] +---- +==== + +[[shallow_copy2]] +.Estado final de `l1` e `l2`: elas ainda compartilham referências para o mesmo objeto lista, que agora contém `[66, 44, 33, 22]`, mas a operação `l2[2] += (10, 11)` criou uma nova tupla com conteúdo `(7, 8, 9, 10, 11)`, sem relação com a tupla `(7, 8, 9)` referenciada por `l1[2]`. (Diagram generated by the Online Python Tutor.) +image::../images/flpy_0604.png[References diagram] + +Já deve estar claro que cópias rasas são fáceis de criar, +mas podem ou não ser o que você quer. +Nosso próximo tópico é a criação de cópias profundas. +((("", startref="ORshallow06")))((("", startref="shallow06")))((("", +startref="Cshallow06")))((("", startref="Lshallow06"))) + +[[deep_x_shallow_copies]] +==== Cópias profundas e cópias rasas + +Trabalhar((("object references", "deep copies", id="ORdeep06")))((("copies", "deep", +id="Cdeep06")))((("deep copies", id="deepcopy06"))) +com cópias rasas nem sempre é um problema, +mas algumas vezes você vai precisar criar cópias profundas +(isto é, cópias que não compartilham referências de objetos internos). +O módulo `copy` oferece as funções `deepcopy` e `copy`, +que retornam cópias profundas e rasas de objetos arbitrários. + +Para ilustrar o uso de `copy()` e `deepcopy()`, <> define uma classe simples, +`Bus`, representando um ônibus escolar que é carregado com passageiros, +e então pega ou deixa passageiros ao longo de sua rota. + +[[ex_bus1]] +.Bus pega ou deixa passageiros +==== +[source, python] +---- +include::../code/06-obj-ref/bus.py[tags=BUS_CLASS] +---- +==== + +Agora, no <> interativo, vamos criar um objeto `bus1` e +dois clones: uma cópia rasa (`bus2`) e uma cópia profunda +(`bus3`). Então vemos o que acontece quando o `bus1` deixa um passageiro. + +[[ex_bus1_console]] +.Os efeitos do uso de `copy` versus `deepcopy` +==== +[source, python] +---- +>>> import copy +>>> bus1 = Bus(['Alice', 'Bill', 'Claire', 'David']) +>>> bus2 = copy.copy(bus1) +>>> bus3 = copy.deepcopy(bus1) +>>> id(bus1), id(bus2), id(bus3) +(4301498296, 4301499416, 4301499752) <1> +>>> bus1.drop('Bill') +>>> bus2.passengers +['Alice', 'Claire', 'David'] <2> +>>> id(bus1.passengers), id(bus2.passengers), id(bus3.passengers) +(4302658568, 4302658568, 4302657800) <3> +>>> bus3.passengers +['Alice', 'Bill', 'Claire', 'David'] <4> +---- +==== +<1> Usando `copy` e `deepcopy`, criamos três instâncias distintas de `Bus`. +<2> Após `bus1` deixar `'Bill'`, ele também desaparece de `bus2`. +<3> A inspeção do atributo `passengers` mostra que +`bus1` e `bus2` compartilham o mesmo objeto lista, pois `bus2` é uma cópia rasa de `bus1`. +<4> `bus3` é uma cópia profunda de `bus1`, +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 laço infinito. +A função `deepcopy` memoriza os objetos já copiados, +e trata referências cíclicas corretamente. +Isso é demonstrado no <>. + +[[ex_cycle1]] +.Referências cíclicas: `b` tem uma referência para `a` e então é concatenado a `a`; ainda assim, `deepcopy` consegue copiar `a`. +==== +[source, python] +---- +>>> a = [10, 20] +>>> b = [a, 30] +>>> a.append(b) +>>> a +[10, 20, [[...], 30]] +>>> from copy import deepcopy +>>> c = deepcopy(a) +>>> c +[10, 20, [[...], 30]] +---- +==== + +Além disso, algumas vezes uma cópia profunda pode ser profunda demais. +Por exemplo, objetos podem ter referências para recursos externos ou para +_singletons_ (objetos únicos) que não devem ser copiados. +Você pode controlar o comportamento de `copy` e de `deepcopy` +implementando os métodos especiais `+__copy__+` e `+__deepcopy__+`, +como descrito na +https://fpy.li/43[documentação do módulo `copy`] + +O compartilhamento de objetos através de apelidos também explica +como a passagens de parâmetros funciona em Python, +e o problema do uso de tipos mutáveis como parâmetros default. +Vamos falar sobre essas questões a seguir.((("", startref="deepcopy06")))((("", +startref="Cdeep06")))((("", startref="ORdeep06"))) + + +=== Parâmetros de função como referências + +O((("object references", "function parameters as references", +id="ORfparam06")))((("call by sharing")))((("parameters", "parameter passing"))) +único modo de passagem de parâmetros em Python é a _chamada por compartilhamento_ +(_call by sharing_). +É o mesmo modo usado na maioria das linguagens orientadas a objetos, +incluindo JavaScript, Ruby e Java (em Java isso se aplica aos tipos de referência; +tipos primitivos usam a chamada por valor). +Chamada por compartilhamento significa que cada parâmetro formal +da função recebe uma cópia de cada referência nos argumentos. +Em outras palavras, os parâmetros dentro da função se tornam apelidos dos argumentos passados. + +O resultado desse esquema é que a função pode modificar qualquer objeto mutável +passado a ela como parâmetro, mas não pode mudar a identidade daqueles objetos +(isto é, ela não pode substituir integralmente um objeto por outro). +O <> mostra uma função simples usando `+=` com um de seus parâmetros. +Quando passamos números, listas e tuplas para a função, +os argumentos originais são afetados de maneiras diferentes. +Veja só: + +[[ex_param_pass]] +.Uma função pode mudar qualquer objeto mutável que receba +==== +[source, python] +---- +>>> def f(a, b): +... a += b +... return a +... +>>> x = 1 +>>> y = 2 +>>> f(x, y) +3 +>>> x, y <1> +(1, 2) +>>> a = [1, 2] +>>> b = [3, 4] +>>> f(a, b) +[1, 2, 3, 4] +>>> a, b <2> +([1, 2, 3, 4], [3, 4]) +>>> t = (10, 20) +>>> u = (30, 40) +>>> f(t, u) <3> +(10, 20, 30, 40) +>>> t, u +((10, 20), (30, 40)) +---- +==== +<1> O número `x` não se altera. +<2> A lista `a` é alterada. +<3> A tupla `t` não se altera. + +Outra questão relacionada a parâmetros de função é o uso de valores mutáveis como defaults, +discutida a seguir. + + +[[mutable_default_parameter_sec]] +==== Porque evitar tipos mutáveis como default em parâmetros + +Parâmetros opcionais((("mutable parameters", id="muttype06")))((("parameters", "mutable", +id="Pmut06"))) com valores default são um ótimo recurso para definição de funções em Python, +permitindo que nossas APIs evoluam mantendo a compatibilidade com versões anteriores. +Entretanto, evite usar objetos mutáveis como valores default em parâmetros. + +Para ilustrar o motivo, no <> +modificamos o método `+__init__+` da classe `Bus` do <> para criar `HauntedBus`. +Tentamos ser espertos: em vez do valor default `passengers=None`, +temos `passengers=[]`, para evitar o `if` do `+__init__+` anterior. +Essa "esperteza" causa problemas. + +[[ex_haunted_bus]] +.Uma classe simples ilustrando o perigo de um default mutável +==== +[source, python] +---- +include::../code/06-obj-ref/haunted_bus.py[tags=HAUNTED_BUS_CLASS] +---- +==== +<1> Quando não passamos o argumento `passengers`, +esse parâmetro é vinculado ao objeto lista default, que inicialmente está vazia. +<2> Essa atribuição torna `self.passengers` um apelido de `passengers`, +que por sua vez é um apelido para a lista default, +quando um argumento `passengers` não é passado para a função. +<3> Quando os métodos `.remove()` e `.append()` são usados com `self.passengers`, +estamos, na verdade, mudando a lista default, que é um atributo do objeto-função. + +<> mostra o comportamento misterioso de `HauntedBus`. + +[[demo_haunted_bus]] +.Ônibus assombrados por passageiros fantasmas +==== +[source, python] +---- +>>> bus1 = HauntedBus(['Alice', 'Bill']) <1> +>>> bus1.passengers +['Alice', 'Bill'] +>>> bus1.pick('Charlie') +>>> bus1.drop('Alice') +>>> bus1.passengers <2> +['Bill', 'Charlie'] +>>> bus2 = HauntedBus() <3> +>>> bus2.pick('Carrie') +>>> bus2.passengers +['Carrie'] +>>> bus3 = HauntedBus() <4> +>>> bus3.passengers <5> +['Carrie'] +>>> bus3.pick('Dave') +>>> bus2.passengers <6> +['Carrie', 'Dave'] +>>> bus2.passengers is bus3.passengers <7> +True +>>> bus1.passengers <8> +['Bill', 'Charlie'] +---- +==== +<1> `bus1` começa com uma lista de dois passageiros. +<2> Até aqui, tudo bem: nenhuma surpresa em `bus1`. +<3> `bus2` começa vazio, então a lista vazia default é vinculada a `self.passengers`. +<4> `bus3` também começa vazio, e novamente a lista default é atribuída. +<5> A lista default não está mais vazia! +<6> Agora `Dave`, pego pelo `bus3`, aparece no `bus2`. +<7> O problema: `bus2.passengers` e `bus3.passengers` se referem à mesma lista. +<8> Mas `bus1.passengers` é uma lista diferente. + +O problema é que instâncias de `HauntedBus` que não recebem uma lista de passageiros +inicial acabam todas compartilhando a mesma lista de passageiros entre si. + +Este tipo de bug pode ser muito sutil. +Como o <> demonstra, +quando `HauntedBus` recebe uma lista com passageiros como parâmetro, +ele funciona como esperado. +Coisas estranhas acontecem somente quando `HauntedBus` começa vazio, +pois aí `self.passengers` se torna um apelido para o valor default do parâmetro `passengers`. +O problema é que cada valor default é processado quando a função é definida—normalmente +quando o módulo é carregado—e os valores default se tornam atributos do objeto-função. +Assim, se o valor default é um objeto mutável e você o altera, +a alteração vai afetar todas as futuras chamadas da função. + +Após executar as linhas do <>, +você pode inspecionar o objeto `+HauntedBus.__init__+` +e ver os estudantes fantasma assombrando o atributo `+__defaults__+`: + +[source, python] +---- +>>> dir(HauntedBus.__init__) # doctest: +ELLIPSIS +['__annotations__', '__call__', ..., '__defaults__', ...] +>>> HauntedBus.__init__.__defaults__ +(['Carrie', 'Dave'],) +---- + +Por fim, podemos verificar que `bus2.passengers` é um apelido vinculado +ao primeiro elemento do atributo `+HauntedBus.__init__.__defaults__+`: + +[source, python] +---- +>>> HauntedBus.__init__.__defaults__[0] is bus2.passengers +True +---- + +O problema com defaults mutáveis explica porque `None` é normalmente usado como +valor default para parâmetros que podem receber valores mutáveis. +No <>, `+__init__+` checa se o argumento `passengers` é `None`. +Se for, `self.passengers` é vinculado a uma nova lista vazia. +Se `passengers` não for `None`, +a implementação correta vincula uma cópia daquele argumento a `self.passengers`. +A próxima seção explica porque copiar o argumento é uma boa prática. + + +[[defensive_argument_sec]] +==== Programação defensiva com argumentos mutáveis + +Ao escrever uma função que recebe um argumento mutável, +você deve considerar com cuidado se o cliente que +chama sua função espera que o argumento passado seja modificado. + +Por exemplo, se sua função recebe um `dict` e precisa modificá-lo durante seu processamento, +esse efeito colateral deve ou não ser visível fora da função? +A resposta, na verdade, depende do contexto. +É tudo uma questão de alinhar as expectativas do autor da função com as do cliente da função. + +O último exemplo com ônibus neste capítulo mostra como o `TwilightBus` viola as expectativas +ao compartilhar sua lista de passageiros com seus clientes. +Antes de estudar a implementação, veja como a classe `TwilightBus` funciona pela +perspectiva de um cliente daquela classe, em <>. + +[[demo_twilight_bus]] +.Passageiros desaparecem quando são deixados por um `TwilightBus` +==== +[source, python] +---- +>>> basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat'] <1> +>>> bus = TwilightBus(basketball_team) <2> +>>> bus.drop('Tina') <3> +>>> bus.drop('Pat') +>>> basketball_team <4> +['Sue', 'Maya', 'Diana'] +---- +==== +<1> `basketball_team` contém o nome de cinco estudantes. +<2> Um `TwilightBus` é carregado com o time. +<3> O `bus` deixa uma estudante, depois outra. +<4> As passageiras desembarcadas desapareceram do time de basquete! + +`TwilightBus` viola o "Princípio da Menor Surpresa", uma boa prática do design de +interfaces.footnote:[Ver https://fpy.li/6-5[_Principle of least astonishment_] (EN).] +Com certeza, é surpreendente que quando o ônibus deixa uma estudante, +seu nome seja removido da escalação do time de basquete. + + +<> é a implementação de `TwilightBus` e uma explicação do problema. + +[[ex_twilight_bus]] +.Uma classe simples mostrando os perigos de mudar argumentos recebidos +==== +[source, python] +---- +include::../code/06-obj-ref/twilight_bus.py[tags=TWILIGHT_BUS_CLASS] +---- +==== +[role="pagebreak-before less_space"] +<1> Aqui cuidadosamente criamos uma lista vazia quando `passengers` é `None`. +<2> Entretanto, esta atribuição transforma `self.passengers` em um apelido para `passengers`, +que por sua vez é um apelido para o argumento passado para `+__init__+` +(i.e. `basketball_team` em <>). +<3> Quando os métodos `.remove()` e `.append()` são usados com `self.passengers`, +estamos, na verdade, modificando a lista original recebida como argumento pelo construtor. + +O problema aqui é que o ônibus está apelidando a lista passada para o construtor. +Ao invés disso, ele deveria manter sua própria lista de passageiros. +A solução é simples: em `+__init__+`, quando o parâmetro `passengers` é fornecido, +`self.passengers` deveria ser inicializado com uma cópia daquela lista, +como fizemos, de forma correta, em <>: + +[source, python] +---- + def __init__(self, passengers=None): + if passengers is None: + self.passengers = [] + else: + self.passengers = list(passengers) <1> +---- +<1> Cria uma cópia da lista `passengers`, +ou converte o argumento para `list` se ele não for uma lista. + +Agora nossa manipulação interna da lista de passageiros não afetará +o argumento usado para inicializar o ônibus. +E com uma vantagem adicional, essa solução é mais flexível: +agora o argumento passado no parâmetro `passengers` pode ser +uma tupla ou qualquer outro tipo iterável, +como `set` ou mesmo resultados de uma consulta a um banco de dados, +pois o construtor de `list` aceita qualquer iterável. +Ao criar nossa própria lista, estamos também assegurando que ela suporta os métodos necessários, +`.remove()` e `.append()`, operações que usamos nos métodos `.pick()` e `.drop()`. + +[TIP] +==== +A menos que um método tenha o objetivo explícito de alterar um objeto recebido como argumento, +você deveria pensar bem antes de apelidar tal objeto e simplesmente vinculá-lo a +uma variável interna de sua classe. +Quando em dúvida, crie uma cópia. +Os clientes de sua classe ficarão mais felizes. +Claro, criar uma cópia não é grátis: há custos de memória e processamento. +Entretanto, uma API que causa bugs sutis é +um problema bem maior que uma que seja um pouco mais lenta ou que use mais recursos. +==== + +Agora vamos conversar sobre uma das instruções mais incompreendidas em Python: +`del`.((("", startref="ORfparam06")))((("", startref="muttype06")))((("", startref="Pmut06"))) + +[[del_sec]] +=== del e coleta de lixo + +[quote, “Modelo de Dados” capítulo de A Referência da Linguagem Python] +____ +Os objetos((("object references", "del and garbage collection", +id="ORdel06")))((("garbage collection", id="garb06"))) nunca são destruídos explicitamente; +no entanto, quando eles se tornam inacessíveis, eles podem ser coletados como lixo. +____ + +A((("del statement", id="del06"))) primeira surpresa de `del` é não ser uma função, +mas uma instrução (_statement_). + +Escrevemos `del x` e não `del(x)`—apesar dessa última forma funcionar também, +mas apenas porque as expressões `x` e `(x)` em geral tem o mesmo significado em Python. + +O segundo aspecto surpreendente é que `del` apaga referências, não objetos. +A coleta de lixo pode eliminar um objeto da memória como resultado indireto de `del`, +se a variável apagada for a última referência ao objeto. +Reassociar uma variável também pode reduzir a zero o número de referências a um objeto, +causando sua destruição. + +[source, python] +---- +>>> a = [1, 2] <1> +>>> b = a <2> +>>> del a <3> +>>> b <4> +[1, 2] +>>> b = [3] <5> +---- +<1> Cria o objeto `[1, 2]` e vincula `a` a ele. +<2> Vincula `b` ao mesmo objeto `[1, 2]`. +<3> Apaga a referência `a`. +<4> `[1, 2]` não é afetado, pois `b` ainda aponta para ele. +<5> Reassociar `b` a um objeto diferente remove a última referência restante a `[1, 2]`. +Agora o coletor de lixo pode descartar aquele objeto. + + +[WARNING] +==== +Existe((("__del__"))) um método especial `+__del__+`, +mas ele não causa a remoção de uma instância e não deve ser invocado em seu código. +O método `+__del__+` é invocado pelo interpretador Python quando a instância está prestes a ser destruída, +para dar a ela a chance de liberar recursos externos. +É muito raro ser preciso implementar `+__del__+` em seu código, +mas ainda assim alguns programadores Python perdem tempo codando este método sem necessidade. +O uso correto de `+__del__+` é bastante complexo. +Consulte +https://fpy.li/3x[`+__del__+`] no capítulo "Modelo de Dados" em _A Referência da Linguagem Python_. +==== + +No CPython, o algoritmo primário de coleta de lixo é ((("reference counting")))a contagem de referências. +Essencialmente, cada objeto mantém uma contagem do número de referências apontando para si. +Assim que a contagem chega a zero, o objeto é imediatamente destruído: +CPython invoca o método `+__del__+` no objeto (se definido) +e daí libera a memória alocada para aquele objeto. +No CPython 2.0, um algoritmo de coleta de lixo geracional foi acrescentado, +para detectar grupos de objetos envolvidos em referências cíclicas—grupos que +podem ser inacessíveis mesmo que existam referências restantes, +quando todas as referências mútuas estão contidas dentro daquele grupo. +Outras implementações de Python tem coletores de lixo mais sofisticados, +que não se baseiam na contagem de referências, +o que significa que o método `+__del__+` pode não ser chamado +imediatamente quando não existem mais referências ao objeto. +Veja https://fpy.li/6-7["PyPy, Garbage Collection, and a Deadlock"] (EN) +de A. Jesse Jiryu Davis para uma discussão sobre os usos próprios e impróprios de `+__del__+`. + +Para demonstrar o fim da vida de um objeto, <> usa `weakref.finalize` +para registrar uma função callback a ser chamada quando o objeto é destruído. + +[[ex_finalize]] +.Detectando o fim de um objeto quando não resta nenhuma referência apontando para ele + +==== +[source, python] +---- +>>> import weakref +>>> s1 = {1, 2, 3} +>>> s2 = s1 <1> +>>> def bye(): <2> +... print('...like tears in the rain.') +... +>>> ender = weakref.finalize(s1, bye) <3> +>>> ender.alive <4> +True +>>> del s1 +>>> ender.alive <5> +True +>>> s2 = 'spam' <6> +...like tears in the rain. +>>> ender.alive +False +---- +==== +<1> `s1` e `s2` são apelidos do mesmo conjunto, `{1, 2, 3}`. +<2> Para essa demonstração, a função `bye` não deve ser um método vinculado ao objeto prestes a ser destruído, +nem manter uma referência para o objeto. +<3> Registra o callback `bye` no objeto referenciado por `s1`. +<4> O atributo `.alive` é `True` antes do objeto `finalize` ser chamado. +<5> Como vimos, `del` não apaga o objeto, apenas a referência `s1` a ele. +<6> Reassociar a última referência, `s2`, torna `{1, 2, 3}` inacessível. +Ele é destruído, o callback `bye` é invocado, e `ender.alive` se torna `False`. + +O ponto principal de <> é mostrar explicitamente que `del` não apaga objetos, +mas que objetos podem ser apagados como uma consequência de +ficarem inacessíveis após o uso de `del`. + +Você pode estar se perguntando porque o objeto `{1, 2, 3}` foi destruído em <>. +Afinal, a referência `s1` foi passada para a função `finalize`, +que precisa tê-la mantido para conseguir monitorar o objeto e invocar o callback. +Isso funciona porque `finalize` mantém uma((("weak references"))) +_referência fraca_ (_weak reference_) para {1, 2, 3}. +Referências fracas não aumentam a contagem de referências de um objeto. +Assim, uma referência fraca não evita que o objeto alvo seja removido pelo coletor de lixo. +Referências fracas são úteis em cenários de caching, +pois não queremos que os objetos "cacheados" sejam mantidos vivos apenas +por terem uma referência no cache.((("", startref="ORdel06")))((("", startref="del06")))((("", startref="garb06"))) + +[NOTE] +==== +Referências fracas são um tópico muito especializado, +então decidi retirá-lo dessa segunda edição. +Em vez disso, publiquei a nota +https://fpy.li/weakref["Weak References" em _https://fluentpython.com_]. +==== + +=== Peças que Python prega com imutáveis + +[NOTE] +==== +Esta((("object references", "immutability and"))) seção opcional discute alguns detalhes que, +na verdade, não são muito importantes para _usuários_ de Python, +e que podem não se aplicar a outras implementações da linguagem ou mesmo a futuras versões de CPython. +Entretanto, já vi muita gente tropeçar nesses casos laterais e daí passar a usar o((("is operator"))) +operador `is` de forma incorreta, então acho que vale a pena mencionar esses detalhes. +==== + +Fiquei((("tuples", "immutability and"))) surpreso ao descobrir que, dada uma tupla `t`, +a chamada `t[:]` não cria uma cópia, mas devolve uma referência para o mesmo objeto. +Da mesma forma, `tuple(t)` também retorna uma referência para a mesma +tupla.footnote:[Isso está claramente documentado. +Digite `help(tuple)` no console de Python e leia: +"Se o argumento é uma tupla, o valor de retorno é o mesmo objeto." +Pensei que sabia tudo sobre tuplas antes de escrever esse livro.] + +<> demonstra esse fato. + +[[ex_same_tuple]] +.Uma tupla construída a partir de outra é, na verdade, exatamente a mesma tupla. +==== +[source, python] +---- +>>> t1 = (1, 2, 3) +>>> t2 = tuple(t1) +>>> t2 is t1 <1> +True +>>> t3 = t1[:] +>>> t3 is t1 <2> +True +---- +==== +<1> `t1` e `t2` estão vinculadas ao mesmo objeto +<2> Assim como `t3`. + +Podemos observar o mesmo comportamento com instâncias de `str`, `bytes` e `frozenset`. +Note que `frozenset` não é uma sequência, então `fs[:]` +não funciona se `fs` é um `frozenset`. +Mas `fs.copy()` tem o mesmo efeito: ele trapaceia e retorna uma referência ao mesmo objeto, +e não uma cópia.footnote:[Essa mentirinha inofensiva, +do método `copy` não copiar nada, é justificável pela compatibilidade da interface: +torna `frozenset` mais compatível com `set`. +De qualquer forma, não faz diferença para o usuário final +se dois objetos imutáveis idênticos são o mesmo ou são cópias.] + +O <> mostra outra otimização, relacionada aos tipos `str` e `int`: + +[[ex_same_string]] +.Strings e inteiros literais podem criar objetos compartilhados. +==== +[source, python] +---- +>>> s1 = 'ABC' +>>> s2 = 'ABC' # <1> +>>> s2 is s1 # <2> +True +>>> n1 = 10 +>>> n2 = 10 +>>> n1 is n2 # <3> +True +>>> n3 = 1729 +>>> n4 = 1729 +>>> n3 is n4 # <4> +False +---- +==== +<1> Criando duas `str` com o mesmo valor. +<2> Surpresa: `a` e `b` se referem ao mesmo objeto `str`! +<3> Alguns inteiros pequenos são compartilhados. +<4> Outros inteiros não são compartilhados. + + +O((("interning"))) compartilhamento de strings literais é +uma técnica de otimização chamada _internalização_ (_interning_). +O CPython usa uma técnica similar com inteiros pequenos, +para evitar a duplicação desnecessária de números que aparecem com muita frequência em programas, +como 0, 1, -1, 10, etc. +Observe que o CPython não internaliza todas as strings e inteiros, +e o critério pelo qual ele faz isso é um detalhe de implementação não documentado. + +[WARNING] +==== +Nunca dependa da internalização de `str` ou `int`! Sempre use `==` +em vez de `is` para verificar a igualdade de strings ou inteiros. +A internalização é uma otimização para uso interno do interpretador Python. +==== + +Os truques discutidos nessa seção, incluindo o comportamento de `frozenset.copy()`, +são mentiras inofensivas que economizam memória e tornam o interpretador mais rápido. +Não se preocupe, elas não trarão nenhum problema, pois se aplicam apenas a tipos imutáveis. +Provavelmente, o melhor uso para esse tipo de detalhe é ganhar apostas contra outros +Pythonistas.footnote:[Um péssimo uso dessas informações seria perguntar sobre elas +quando entrevistando candidatos a emprego ou criando perguntas para exames de "certificação". +Há inúmeros fatos mais importantes e úteis para testar conhecimentos de Python.] + +=== Resumo do capítulo + +Todo((("object references", "overview of"))) objeto em Python tem uma identidade, +um tipo e um valor. +Apenas o valor do objeto pode mudar ao longo do +tempo.footnote:[Na verdade, o tipo de um objeto pode ser modificado, +bastando para isso atribuir uma classe diferente ao atributo `+__class__+` do objeto. +Mas isso é uma perversão, e eu me arrependo de ter escrito essa nota de rodapé.] + +Se duas variáveis se referem a objetos imutáveis de valor igual (quando `a == b` é `True`), +na prática, dificilmente importa se elas se referem a cópias de mesmo valor +ou são apelidos do mesmo objeto, porque o valor de objeto imutável não muda, +com uma exceção. +A exceção são as tuplas: se ela contém referências para itens mutáveis, +então seu valor mudará se o valor de um item mutável for alterado. +Na prática, esse cenário não é tão comum. +O que nunca muda numa coleção imutável são as identidades dos objetos mantidos ali. +A classe `frozenset` não sofre desse problema, +porque ela só pode conter elementos hashable, +e o valor de um objeto hashable não pode mudar, por definição. + +O fato de variáveis conterem referências tem muitas consequências práticas +para a programação em Python: + +* Uma atribuição simples não cria cópias. +* Uma atribuição composta com `+=` ou `*=` cria novos objetos se +a variável à esquerda da atribuição estiver vinculada a um objeto imutável, +mas pode modificar um objeto mutável diretamente. +* Atribuir um novo valor a uma variável existente não muda +o objeto previamente vinculado à variável. +Isso se chama _rebinding_ (re-vinculação); +a variável passa a se referir a um objeto diferente. +Se aquela variável era a última referência ao objeto anterior, +aquele objeto será eliminado pela coleta de lixo. + +* Parâmetros de função são passados como apelidos, +o que significa que a função pode alterar qualquer objeto mutável recebido como argumento. +Não há como evitar isso, exceto criando cópias locais ou usando objetos imutáveis +(i.e., passando uma tupla em vez de uma lista) +* Usar objetos mutáveis como valores default de parâmetros de função é perigoso, +pois se os parâmetros forem modificados pela função, o default muda, +afetando chamadas posteriores que usem o default. + +Em CPython, um objeto é descartado assim que o número de referências a ele chega a zero. +Objetos também podem ser descartados se formarem grupos com +referências cíclicas sem nenhuma referência externa ao grupo. + +Em algumas situações, pode ser útil manter uma referência para um objeto que +não vai, por si só, manter o objeto vivo. +Um exemplo é uma classe que queira manter o registro de todas as suas instâncias atuais. +Isso pode ser feito com referências fracas, +um mecanismo de baixo nível encontrado nas coleções `WeakValueDictionary`, +`WeakKeyDictionary`, `WeakSet`, e na função `finalize` do módulo `weakref`. + +Leia https://fpy.li/weakref["Weak References" em _https://fluentpython.com_] +para mais detalhes sobre `weakref`. + + +=== Para saber mais + +O((("object references", "further reading on"))) +https://fpy.li/2j[capítulo "Modelo de Dados"] de _A Referência da Linguagem Python_ +inicia com uma explicação bastante clara sobre identidades e valores de objetos. + +Wesley Chun, autor da série _Core Python_, +apresentou https://fpy.li/6-8[Understanding Python's Memory Model, Mutability, and Methods] +(EN) na EuroPython 2011, discutindo não apenas o tema desse capítulo como também o uso de métodos especiais. + +Doug Hellmann escreveu os posts https://fpy.li/6-9["copy — Duplicate Objects"] (EN) e +https://fpy.li/6-10["weakref — Garbage-Collectable References to Objects"] (EN), +cobrindo alguns dos tópicos que acabamos de tratar. + +Você pode encontrar mais informações sobre o coletor de lixo geracional do CPython em +https://fpy.li/3y[gc — Interface para o coletor de lixo], +que começa com a frase "Este módulo fornece uma interface para o coletor de lixo opcional." +O adjetivo "opcional" aqui pode ser surpreendente, +mas o https://fpy.li/2j[capítulo "Modelo de Dados"] também afirma: + +[quote] +____ +Uma implementação tem permissão para adiar a coleta de lixo ou omiti-la completamente -- é +um detalhe de implementação como a coleta de lixo é implementada, +desde que nenhum objeto que ainda esteja acessível seja coletado. +____ + +Pablo Galindo escreveu um texto mais aprofundado sobre o Coletor de Lixo em Python, em +https://fpy.li/6-12["Design of CPython’s Garbage Collector"] (EN) +no https://fpy.li/6-13[_Python Developer’s Guide_], +voltado para contribuidores novos e experientes da implementação CPython. + +O coletor de lixo do CPython 3.4 aperfeiçoou o tratamento de objetos contendo um método `+__del__+`, +como descrito em https://fpy.li/6-14[PEP 442--Safe object finalization] (EN). + +A Wikipedia tem um artigo sobre https://fpy.li/6-15[string interning] (EN), +que menciona o uso desta técnica em várias linguagens, incluindo Python. + +A Wikipedia também tem um artigo sobre https://fpy.li/6-16["Haddocks' Eyes"], +a canção de Lewis Carroll que mencionei no início deste capítulo. +Os editores da Wikipedia escreveram que a letra é usada em trabalhos de lógica e filosofia +"para elaborar o status simbólico do conceito de 'nome': +um nome como um marcador de identificação pode ser atribuído a qualquer coisa, +incluindo outro nome, introduzindo assim níveis diferentes de simbolização." + +.Ponto de vista +**** + +[role="soapbox-title"] +*Tratamento igual para todos os objetos* + +Aprendi((("object references", "Soapbox discussion")))((("Soapbox sidebars", +"equality (==) operator")))((("== (equality) operator")))((("equality (==) operator"))) +Java antes de conhecer Python. +O operador `==` em Java sempre me pareceu equivocado. +É mais comum que programadores estejam preocupados com a igualdade que com a identidade. +Mas para objetos (não tipos primitivos), o `==` em Java compara referências, não valores dos objetos. +Mesmo para algo tão básico quanto comparar strings, +Java obriga você a usar o método `.equals`. +E mesmo assim, há outro problema: se você escrever `a.equals(b)` e `a` for `null`, +você causa uma _null pointer exception_ (exceção de ponteiro nulo). +Os projetistas de Java sentiram necessidade de sobrecarregar `+` para strings; +por que não mantiveram essa ideia e sobrecarregaram `==` também? + +Python faz melhor. +O operador `==` compara valores de objetos; `is` compara referências. +E como Python permite sobrecarregar operadores, +`==` funciona de forma sensata com todos os objetos na biblioteca padrão, +incluindo `None`, que é um objeto de verdade, ao contrário do `null` de Java. + +E claro, você pode definir `+__eq__+` nas suas próprias classes para controlar +o que `==` significa para suas instâncias. +Se você não sobrecarregar `+__eq__+`, o método herdado de `object` compara os IDs dos objetos, +então a regra básica é que cada instância de uma classe definida pelo usuário é considerada diferente. + +Estas são algumas das coisas que me fizeram mudar de Java para Python +assim que terminei de ler _The Python Tutorial_ em uma tarde de setembro de 1998. + +[role="soapbox-title"] +*Mutabilidade* + +Este((("Soapbox sidebars", "mutability")))((("mutable objects")))((("objects", "mutable"))) +capítulo não seria necessário se todos os objetos em Python fossem imutáveis. +Quando você está lidando com objetos imutáveis, +não faz diferença se as variáveis guardam os objetos em si ou referências para objetos compartilhados. + +Se `a == b` é verdade, e nenhum dos dois objetos pode mudar, +eles podem perfeitamente ser o mesmo objeto. +Por isso a internalização de strings é segura. +A identidade dos objetos so é importante quando esses objetos podem mudar. + +Em programação funcional "pura", todos os dados são imutáveis: +concatenar algo a uma coleção, na verdade, cria uma nova coleção. +Elixir é uma linguagem funcional prática e fácil de aprender, +na qual todos os tipos nativos são imutáveis, incluindo as listas. + +Python, por outro lado, não é uma linguagem funcional, +muito menos uma linguagem funcional pura. +Instâncias de classes definidas pelo usuário são mutáveis por padrão em Python—como +na maioria das linguagens orientadas a objetos. +Ao criar seus próprios objetos, você precisa tomar o cuidado adicional de torná-los imutáveis, +se este for um requisito. +Cada atributo do objeto precisa ser também imutável, +senão você termina criando algo como uma tupla: +imutável quanto ao `id` do objeto, +mas seu valor pode mudar se a tupla contiver um objeto mutável. + +Objetos mutáveis também são a razão pela qual programar com threads é tão difícil: +threads modificando objetos sem uma sincronização apropriada podem corromper dados. +Sincronização excessiva, por outro lado, causa deadlocks. +A linguagem e a plataforma Erlang—que inclui Elixir—foi projetada para maximizar +o tempo de execução em aplicações distribuídas de alta concorrência, +como aplicações de controle de telecomunicações. +Naturalmente, eles escolheram tornar os dados imutáveis por default. + +[role="soapbox-title"] +*Destruição de objetos e coleta de lixo* + +Não existe((("Soapbox sidebars", +"object destruction and garbage collection")))((("garbage collection"))) +em Python uma forma de destruir um objeto diretamente. +E essa omissão é uma grande qualidade: +se você pudesse destruir um objeto a qualquer momento, +o que aconteceria com as referências que apontam para ele? + +A coleta de lixo em CPython é feita principalmente por contagem de referências, +que é fácil de implementar, mas vulnerável a vazamentos de memória (_memory leaks_) +quando existem referências cíclicas. +Assim, com a versão 2.0 (de outubro de 2000), +um coletor de lixo geracional foi implementado, +e ele consegue descartar objetos inatingíveis que foram mantidos vivos por ciclos de referências. + +Mas a contagem de referências ainda está lá como mecanismo básico, +e ela causa a destruição imediata de objetos com zero referências. +Isso significa que, em CPython -- pelo menos por hora -- é seguro escrever: + +[source, python] +---- +open('test.txt', 'wt', encoding='utf-8').write('1, 2, 3') +---- + +Este código é seguro porque a contagem de referências do objeto arquivo será zero +após o método `write` retornar, e o arquivo será fechado quando o objeto for descartado. +Entretanto, a mesma linha não é segura em Jython ou IronPython, +que usam o coletor de lixo dos runtimes de seus ambientes +(a Java VM e a .NET CLR, respectivamente), que são mais sofisticados, +mas não se baseiam em contagem de referências, +e podem demorar mais para destruir o objeto e fechar o arquivo. +Em todos os casos, incluindo em CPython, a melhor prática é fechar o arquivo explicitamente, +e a forma mais confiável de fazer isso é usando a instrução `with`, +que garante o fechamento do arquivo mesmo se acontecerem +exceções enquanto ele estiver aberto. +Usando `with`, a linha anterior se torna: + +[source, python] +---- +with open('test.txt', 'wt', encoding='utf-8') as fp: + fp.write('1, 2, 3') +---- + +Se você tiver interesse no assunto de coletores de lixo, +você talvez queira ler o artigo de Thomas Perl, +https://fpy.li/6-17["Python Garbage Collector Implementations: CPython, PyPy and GaS"] +(EN), onde eu aprendi esses detalhes sobre a segurança de `open().write()` em CPython. + +[role="soapbox-title"] +*Passagem de parâmetros: chamada por compartilhamento* + +Uma maneira popular de explicar como a passagem de parâmetros +funciona em Python é a frase: +"Parâmetros são passados por valor, mas os valores são referências." +Isso não está errado, +mas causa confusão porque os modos mais comuns de passagem de parâmetros +em linguagens tradicionais são +_chamada por valor_ (a função recebe uma cópia dos argumentos) e +_chamada por referência_ (a função recebe um ponteiro para o argumento). + +Em Python, a função recebe uma cópia dos argumentos, +mas os argumentos são sempre referências. +Então o valor dos objetos referenciados podem ser alterados pela função, +se eles forem mutáveis, mas sua identidade não. +Além disso, como a função recebe uma cópia da referência em um argumento, +reassociar essa referência no corpo da função não tem qualquer efeito fora da função. +Adotei o termo _chamada por compartilhamento_ depois de +encontrar a definição de _call by sharing_ no livro +_Programming Language Pragmatics_, 3rd ed., de Michael L. Scott +(Morgan Kaufmann), seção "8.3.1: Parameter Modes." + +**** diff --git a/capitulos/cap07.adoc b/online/cap07.adoc similarity index 52% rename from capitulos/cap07.adoc rename to online/cap07.adoc index 18db94ca..12d0384e 100644 --- a/capitulos/cap07.adoc +++ b/online/cap07.adoc @@ -1,23 +1,19 @@ -:xrefstyle: short +[[ch_func_objects]] +== Funções como objetos de primeira classe :example-number: 0 :figure-number: 0 -:figure-caption: Figura -:example-caption: Exemplo -:table-caption: Tabela -:section-caption: Seção -:chapter-caption: Capítulo -:part-caption: Parte - -[[functions_as_objects]] -== Funções como objetos de primeira classe -[quote, Guido van Rossum, BDFL do Python] +[quote, Guido van Rossum, BDFL de Python] ____ -Nunca achei que o Python tenha sido fortemente influenciado por linguagens funcionais, independente do que outros digam ou pensem. -Eu estava muito mais familiarizado com linguagens imperativas, como o C e o Algol e, apesar de ter tornado as funções objetos de primeira classe, não via o Python como uma linguagem funcional.footnote:[https://fpy.li/7-1["Origins of Python's 'Functional' Features" (_As origens dos recursos 'funcionais' do Python_—EN)], do blog The History of Python (A História do Python) do próprio Guido.]footnote:["Benevolent Dictator For Life." - Ditador Benevolente Vitalício. Veja Guido van van Rossum em https://fpy.li/bdfl["Origin of BDFL" (_A Origem do BDFL_)] (EN).] +Nunca achei que Python tenha sido fortemente influenciado por linguagens funcionais, independentemente do que outros digam ou pensem. +Eu estava mais familiarizado com linguagens imperativas, como o C e o Algol e, apesar de ter tornado as funções objetos de primeira classe, não via Python como uma linguagem funcional.footnote:[https://fpy.li/7-1["Origins of Python's 'Functional' Features" (_As origens dos recursos 'funcionais' de Python_—EN)], do blog The History of Python (A História de Python) do próprio Guido.]footnote:["Benevolent Dictator For Life." - Ditador Benevolente Vitalício. +Veja Guido van van Rossum em https://fpy.li/bdfl["Origin of BDFL" (_A Origem do BDFL_)] (EN).] ____ -No Python, funções((("objects", "first-class")))((("first-class objects")))((("functions, as first-class objects", "definition of term"))) são objetos de primeira classe. Estudiosos de linguagens de programação definem um "objeto de primeira classe" como uma entidade programática que pode ser: +No Python, funções((("objects", "first-class")))((("first-class objects")))((("functions, as first-class objects", +"definition of term"))) são objetos de primeira classe. +Estudiosos de linguagens de programação definem um "objeto de primeira classe" +como uma entidade que pode ser: * Criada durante a execução de um programa * Atribuída a uma variável ou a um elemento em uma estrutura de dados @@ -25,49 +21,60 @@ No Python, funções((("objects", "first-class")))((("first-class objects")))((( * Devolvida como o resultado de uma função Inteiros, strings e dicionários são outros exemplos de objetos de primeira classe no Python—nada de incomum aqui. -Tratar funções como objetos de primeira classe é um recurso essencial das linguagens funcionais, tais como Clojure, Elixir e Haskell. -Entretanto, funções de primeira classe são tão úteis que foram adotadas por linguagens muito populares, como o Javascript, o Go e o Java (desde o JDK 8), nenhuma das quais alega ser uma "linguagem funcional". +Tratar funções como objetos de primeira classe é um recurso essencial das linguagens funcionais, como Clojure, Elixir e Haskell. +Entretanto, funções de primeira classe são tão úteis que foram adotadas por linguagens muito populares, +como JavaScript, Go e até Java (desde o JDK 8), nenhuma das quais pretende ser uma "linguagem funcional". Esse capítulo e quase toda a Parte III do livro exploram as aplicações práticas de se tratar funções como objetos. [TIP] ==== -O termo "funções de primeira classe" é largamente usado como uma forma abreviada de "funções como objetos de primeira classe". Ele não é ideal, por sugerir a existência de uma "elite" entre funções. No Python, todas as funções são de primeira classe. +O termo "funções de primeira classe" é largamente usado como uma forma abreviada de +"funções como objetos de primeira classe". +Ele não é ideal, pois sugere a existência de uma "elite" entre as funções. +Em Python, todas as funções são de primeira classe. ==== -=== Novidades nesse capítulo +=== Novidades neste capítulo -A seção((("functions, as first-class objects", "significant changes to"))) <> se chamava "Sete sabores de objetos invocáveis" na primeira edição deste livro. +A seção "Os nove sabores de objetos invocáveis"((("functions, as first-class objects", "significant changes to"))) +(<>) se chamava "Sete sabores de objetos invocáveis" na primeira edição deste livro. Os novos invocáveis são corrotinas nativas e geradores assíncronos, introduzidos no Python 3.5 e 3.6, respectivamente. -Ambos serão estudados no <>, mas são mencionados aqui, ao lado dos outros invocáveis. +Ambos serão estudados no <>, mas são mencionados aqui ao lado dos outros invocáveis. -A seção <> é nova, e fala de um recurso que surgiu no Python 3.8. +A <> é nova, e fala de um recurso que surgiu no Python 3.8: parâmetros somente posicionais. -Transferi a discussão sobre acesso a anotações de funções durante a execução para a seção <>. -Quando escrevi a primeira edição, a https://fpy.li/pep484[PEP 484—Type Hints (_Dicas de Tipo_)] (EN) ainda estava sendo considerada, e as anotações eram usadas de várias formas diferentes. +Transferi a discussão sobre acesso a anotações de funções durante a execução para a <>. +Quando escrevi a primeira edição, a https://fpy.li/pep484[PEP 484—Type Hints (_Dicas de Tipo_)] (EN) +ainda estava sendo considerada, e as anotações eram usadas de várias formas diferentes. Desde o Python 3.5, anotações precisam estar em conformidade com a PEP 484. -Assim, o melhor lugar para falar delas é durante a discussão das dicas de tipo. +Assim, o melhor lugar para falar delas é na discussão sobre as dicas de tipo. [NOTE] ==== -A((("function parameters, introspection of")))((("parameters", "introspection of function parameters"))) primeira edição desse livro continha seções sobre a introspecção de objetos função, que desciam a detalhes de baixo nível e distraiam o leitor do assunto principal do capítulo. -Fundi aquelas seções em um post entitulado -https://fpy.li/7-2["Introspection of Function Parameters" (_Introspecção de Parâmetros de Funções_)], no _fluentpython.com_. +A((("function parameters, introspection of")))((("parameters", "introspection of function parameters"))) +primeira edição desse livro continha seções sobre a introspecção de objetos função, +que desciam a detalhes de baixo nível e desviavam do assunto principal do capítulo. +Reuni aquelas seções em um post intitulado +https://fpy.li/7-2["Introspection of Function Parameters" (_Introspecção de Parâmetros de Funções_)], no _https://fluentpython.com_. ==== -Agora vamos ver porque as funções do Python são objetos completos. +Agora vamos ver porque as funções de Python são objetos completos. === Tratando uma função como um objeto -A((("functions, as first-class objects", "treating functions like objects", id="FAFtreat07")))((("objects", "treating functions like", id="Otreat07"))) sessão de console no <> mostra que funções do Python são objetos. Ali criamos uma função, a chamamos, lemos seu atributo +A((("functions, as first-class objects", "treating functions like objects", +id="FAFtreat07")))((("objects", "treating functions like", id="Otreat07"))) +sessão de console no <> mostra que funções de Python são objetos. +Ali criamos uma função, a chamamos, lemos seu atributo `+__doc__+` e verificamos que o próprio objeto função é uma instância da classe `function`. [[func_object_demo]] -.Cria e testa uma função, e então lê seu pass:[__doc__] e verifica seu tipo +.Cria e testa uma função, e então lê seu `+__doc__+` e verifica seu tipo ==== -[source, pycon] +[source, python] ---- >>> def factorial(n): <1> ... """returns n!""" @@ -85,22 +92,25 @@ A((("functions, as first-class objects", "treating functions like objects", id=" <2> `+__doc__+` é um dos muitos atributos de objetos função. <3> `factorial` é um instância da classe `function`. -O atributo `+__doc__+` é usado para gerar o texto de ajuda de um objeto. No console do Python, o comando `help(factorial)` mostrará uma tela como a da <>. +O atributo `+__doc__+` é usado para gerar o texto de ajuda de um objeto. +No console de Python, a instrução `help(factorial)` mostrará uma tela como a <>. [[factorial_help]] -.Tela de ajuda para `factorial`; o texto é criado a partir do atributo pass:[__doc__] da função. -image::images/flpy_0701.png[Tela de ajuda da função factorial] +.Tela de ajuda para `factorial`; o texto é criado a partir do atributo `+__doc__+` da função. +image::../images/flpy_0701.png[Tela de ajuda da função factorial] O <> mostra a natureza de "primeira classe" de um objeto função. Podemos atribuir tal objeto a uma variável `fact` e invocá-lo por esse nome. -Podemos também passar `factorial` como argumento para a função https://docs.python.org/pt-br/3/library/functions.html#map[`map`]. -Invocar `map(function, iterable)` devolve um iterável no qual cada item é o resultado de uma chamada ao primeiro argumento (uma função) com elementos sucessivos do segundo argumento (um iterável), `range(11)` no exemplo. +Podemos também passar `factorial` como argumento para a função https://fpy.li/44[`map`]. +Invocar `map(function, iterable)` devolve um iterável no qual cada item é +o resultado de uma chamada ao primeiro argumento (uma função) +com elementos sucessivos do segundo argumento (um iterável), `range(11)` no exemplo. [[func_object_demo2]] -.Usa `factorial` usando de um nome diferentes, e passa `factorial` como um argumento +.Invoca `factorial` através da variável `fact`, e passa `factorial` como argumento para `map` ==== -[source, pycon] +[source, python] ---- >>> fact = factorial >>> fact @@ -114,21 +124,25 @@ Invocar `map(function, iterable)` devolve um iterável no qual cada item é o re ---- ==== -Ter funções de primeira classe permite programar em um estilo funcional. Um dos marcos da https://pt.wikipedia.org/wiki/Programa%C3%A7%C3%A3o_funcional[programação funcional] é o uso de funções de ordem superior, nosso próximo tópico.((("", startref="Otreat07")))((("", startref="FAFtreat07"))) +Ter funções de primeira classe permite programar em um estilo funcional. +Um dos marcos da https://fpy.li/45[programação funcional] +é o uso de funções de ordem superior, nosso próximo tópico.((("", startref="Otreat07")))((("", startref="FAFtreat07"))) === Funções de ordem superior -Uma((("functions, as first-class objects", "higher-order functions", id="FAFhigh07")))((("higher-order functions", id="higher07")))((("functions", "higher-order functions", id="Fhigh07"))) função que recebe uma função como argumento ou devolve uma função como resultado +Uma((("functions, as first-class objects", "higher-order functions", +id="FAFhigh07")))((("higher-order functions", id="higher07")))((("functions", "higher-order functions", id="Fhigh07"))) +função que recebe uma função como argumento ou devolve uma função como resultado é uma _função de ordem superior_. Uma dessas funções é `map`, usada no <>. Outra é a função embutida `sorted`: -o argumento opcional `key` permite fornecer uma função, que será então aplicada na ordenação de cada item, como vimos na seção <>. +o argumento opcional `key` permite fornecer uma função, que será então aplicada na ordenação de cada item, como vimos na <>. Por exemplo, para ordenar uma lista de palavras por tamanho, passe a função `len` como `key`, como no <>. [[higher_order_sort]] .Ordenando uma lista de palavras por tamanho ==== -[source, pycon] +[source, python] ---- >>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana'] >>> sorted(fruits, key=len) @@ -137,12 +151,16 @@ Por exemplo, para ordenar uma lista de palavras por tamanho, passe a função `l ---- ==== -Qualquer função com um argumento pode ser usada como chave. Por exemplo, para criar um dicionário de rimas pode ser útil ordenar cada palavra escrita ao contrário. No <>, observe que as palavras na lista não são modificadas de forma alguma; apenas suas versões escritas na ordem inversa são utilizadas como critério de ordenação. Por isso as _berries_ aparecem juntas. +Qualquer função com um argumento pode ser usada como chave. +Por exemplo, para criar um dicionário de rimas pode ser útil ordenar cada palavra escrita ao contrário. +No <>, observe que as palavras na lista não são modificadas de forma alguma; +apenas suas versões escritas na ordem inversa são utilizadas como critério de ordenação. +Por isso as _berries_ aparecem juntas. [[higher_order_sort_reverse]] .Ordenando uma lista de palavras pela ordem inversa de escrita ==== -[source, pycon] +[source, python] ---- >>> def reverse(word): ... return word[::-1] @@ -154,16 +172,23 @@ Qualquer função com um argumento pode ser usada como chave. Por exemplo, para ---- ==== -No((("map function", id="map07")))((("functions", "filter, map, and reduce functions")))((("filter function", id="filter07")))((("reduce function", id="reduce07")))((("apply function", id="apply07"))) paradigma funcional de programação, algumas das funções de ordem superior mais conhecidas são `map`, `filter`, `reduce`, e `apply`. -A função `apply` foi descontinuada no Python 2.3 e removida no Python 3, por não ser mais necessária. Se você precisar chamar uma função com um conjuntos dinâmico de argumentos, pode escrever `fn(*args, **kwargs)` no lugar de `apply(fn, args, kwargs)`. +No((("map function", id="map07")))((("functions", "filter, map, and reduce functions")))((("filter function", +id="filter07")))((("reduce function", id="reduce07")))((("apply function", id="apply07"))) +paradigma funcional de programação, algumas das funções de ordem superior mais conhecidas são `map`, `filter`, `reduce` e `apply`. +A função `apply` foi descontinuada no Python 2.3 e removida no Python 3, por não ser mais necessária. +Se você precisar chamar uma função com um conjuntos dinâmico de argumentos, +pode escrever `fn(*args, **kwargs)` em vez de `apply(fn, args, kwargs)` como fazíamos no século passado. -As funções de ordem superior `map`, `filter`, e `reduce` ainda estão por aí, mas temos alternativas melhores para a maioria de seus casos de uso, como mostra a próxima seção. +As funções de ordem superior `map`, `filter` e `reduce` ainda existem, +mas temos alternativas melhores para a maioria de seus casos de uso, como mostra a próxima seção. -[[map_filter_reduce]] +[[map_filter_reduce_sec]] ==== Substitutos modernos para map, filter, e reduce -Linguagens funcionais((("list comprehensions (listcomps)", "versus map and filter functions")))((("generator expressions (genexps)"))) normalmente oferecem as funções de ordem superior `map`, `filter`, and `reduce` (algumas vezes com nomes diferentes). -As funções `map` e `filter` ainda estão embutidas no Python mas, desde a introdução das compreensões de lista e das expressões geradoras, não são mais tão importantes. +Linguagens funcionais((("list comprehensions (listcomps)", "versus map and filter functions")))((("generator expressions (genexps)"))) +normalmente oferecem as funções de ordem superior `map`, `filter`, e `reduce` (algumas vezes com nomes diferentes). +As funções `map` e `filter` ainda estão embutidas no Python, mas elas não são mais tão importantes +desde a introdução das compreensões de lista e das expressões geradoras. Uma listcomp ou uma genexp fazem o mesmo que `map` e `filter` combinadas, e são mais legíveis. Considere o <>. @@ -171,7 +196,7 @@ Considere o <>. [[reduce_x_sum]] .Listas de fatoriais produzidas com `map` e `filter`, comparadas com alternativas escritas com compreensões de lista ==== -[source, pycon] +[source, python] ---- >>> list(map(factorial, range(6))) <1> [1, 1, 2, 6, 24, 120] @@ -187,16 +212,22 @@ Considere o <>. <1> Cria uma lista de fatoriais de 0! a 5!. <2> Mesma operação, com uma compreensão de lista. <3> Lista de fatoriais de números ímpares até 5!, usando `map` e `filter`. -<4> A compreensão de lista realiza a mesma tarefa, substituindo `map` e `filter`, e tornando `lambda` desnecessário. +<4> A compreensão de lista realiza a mesma tarefa, substituindo `map` e `filter`, +e tornando `lambda` desnecessário. -No Python 3, `map` e `filter` devolvem geradores—uma forma de iterador—então sua substituta direta é agora uma expressão geradora (no Python 2, essas funções devolviam listas, então sua alternativa mais próxima era a compreensão de lista). +No Python 3, `map` e `filter` devolvem geradores—uma forma de iterador—então +sua substituta direta agora é uma expressão geradora +(no Python 2, essas funções devolviam listas, então sua alternativa mais próxima era a compreensão de lista). -A função `reduce` foi rebaixada de função embutida, no Python 2, para o módulo `functools` no Python 3. Seu caso de uso mais comum, a soma, é melhor servido pela função embutida `sum`, disponível desde que o Python 2.3 (lançado em 2003). E isso é uma enorme vitória em termos de legibilidade e desempenho (veja <> abaixo). +A função `reduce` foi rebaixada de função embutida, no Python 2, para o módulo `functools` no Python 3. +Seu caso de uso mais comum, a somatória, é melhor atendido pela função embutida `sum`, +disponível desde o Python 2.3 (lançado em 2003). +A função `sum` é mais legível e mais eficiente: [[reduce_x_sum2]] .Soma de inteiros até 99, realizada com `reduce` e `sum` ==== -[source, pycon] +[source, python] ---- >>> from functools import reduce <1> >>> from operator import add <2> @@ -207,7 +238,7 @@ A função `reduce` foi rebaixada de função embutida, no Python 2, para o mód >>> ---- ==== -<1> A partir do Python 3.0, `reduce` deixou de ser uma função embutida. +<1> A partir de Python 3.0, `reduce` deixou de ser uma função embutida. <2> Importa `add` para evitar a criação de uma função apenas para somar dois números. <3> Soma os inteiros até 99. <4> Mesma operação, com `sum`—não é preciso importar nem chamar `reduce` e `add`. @@ -215,7 +246,8 @@ A função `reduce` foi rebaixada de função embutida, no Python 2, para o mód [NOTE] ===================================================================== A ideia comum de `sum` e `reduce` -é aplicar alguma operação sucessivamente a itens em uma série, acumulando os resultados anteriores, reduzindo assim uma série de valores a um único valor. +é aplicar alguma operação sucessivamente a itens em uma série, +acumulando os resultados anteriores, reduzindo assim uma série de valores a um único valor. ===================================================================== Outras funções de redução embutidas são `all` e `any`: @@ -225,31 +257,38 @@ Outras funções de redução embutidas são `all` e `any`: `any(iterable)`:: Devolve `True` se qualquer elemento do `iterable` for verdadeiro; `any([])` devolve `False`. -Dou um explicação mais completa sobre `reduce` na seção <>, +Dou um explicação mais completa sobre `reduce` na <>, onde um exemplo mais longo, atravessando várias seções, cria um contexto significativo para o uso dessa função. -As funções de redução serão resumidas mais à frente no livro, na seção <>, +As funções de redução serão resumidas mais à frente no livro, na <>, quando estivermos tratando dos iteráveis. -Para usar uma função de ordem superior, às vezes é conveniente criar um pequena -função, que será usada apenas uma vez. As funções anônimas existem para isso. +Para usar uma função de ordem superior, às vezes é conveniente criar uma pequena +função, que será usada apenas uma vez como argumento para outra função. +As funções anônimas existem para isso. Vamos falar delas a seguir.((("", startref="map07")))((("", startref="filter07")))((("", startref="reduce07")))((("", startref="apply07")))((("", startref="higher07")))((("", startref="FAFhigh07")))((("", startref="Fhigh07"))) === Funções anônimas -A((("functions, as first-class objects", "anonymous functions")))((("anonymous functions")))((("lambda keyword")))((("keywords", "lambda keyword"))) palavra reservada `lambda` cria uma função anônima dentro de uma expressão Python. +A((("functions, as first-class objects", "anonymous functions")))((("anonymous functions")))((("lambda keyword")))((("keywords", "lambda keyword"))) +palavra reservada `lambda` cria uma função anônima dentro de uma expressão Python. -Entretanto, a sintaxe simples do Python força os corpos de funções `lambda` a serem expressões puras. Em outras palavras, o corpo não pode conter outras instruções Python como `while`, `try`, etc. A atribuição com `=` também é uma instrução, então não pode ocorrer em um `lambda`. -A nova sintaxe da expressão de atribuição, usando `:=`, pode ser usada. Porém, se você precisar dela, seu `lambda` provavelmente é muito complicado e difícil de ler, e deveria ser refatorado para um função regular usando `def`. +Entretanto, a sintaxe simples de Python obriga que o corpo de uma `lambda` seja uma expressão. +Em outras palavras, o corpo não pode conter instruções como `while`, `try`, etc. +A atribuição com `=` também é uma instrução, então não pode ocorrer em um `lambda`. +A nova sintaxe da expressão de atribuição, usando `:=`, pode ser usada. +Porém, se você precisar dela, sua `lambda` provavelmente é muito complicada e difícil de ler, +e deveria ser refatorado para uma função nomeada usando `def`. O melhor uso das funções anônimas é no contexto de uma lista de argumentos para uma função de ordem superior. -Por exemplo, o <> é o exemplo do dicionário de rimas do <> reescrito com `lambda`, sem definir uma função `reverse`. +Por exemplo, o <> é o exemplo do dicionário de rimas do +<> reescrito com `lambda`, sem definir uma função `reverse`. [[higher_order_sort_reverse_lambda]] .Ordenando uma lista de palavras escritas na ordem inversa usando `lambda` ==== -[source, pycon] +[source, python] ---- >>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana'] >>> sorted(fruits, key=lambda word: word[::-1]) @@ -258,26 +297,30 @@ Por exemplo, o <> é o exemplo do dicionário ---- ==== -Fora do contexto limitado dos argumentos das funções de ordem superior, funções anônimas raramente são úteis no Python. -As restrições sintáticas tendem a tornar ilegíveis ou intratáveis as `lambdas` não-triviais. Se uma `lambda` é difícil de ler, aconselho fortemente seguir o conselho de Fredrik Lundh sobre refatoração. - -// [role="pagebreak-before"] +Fora do contexto limitado dos argumentos das funções de ordem superior, funções anônimas são pouco úteis no Python. +As restrições sintáticas tendem a tornar ilegíveis as `lambdas` não-triviais. +Se uma `lambda` é difícil de ler, sugiro fortemente seguir o conselho de Fredrik Lundh sobre refatoração. .A receita de Fredrik Lundh para refatoração de lambdas **** -Se você encontrar um trecho de código difícil de entender por causa de uma `lambda`, Fredrik Lundh sugere o seguinte procedimento de refatoração: +Se você encontrar um trecho de código difícil de entender por causa de uma `lambda`, +Fredrik Lundh sugere o seguinte procedimento de refatoração: . Escreva um comentário explicando o que diabos aquela `lambda` faz. . Estude o comentário por algum tempo, e pense em um nome que traduza sua essência. . Converta a `lambda` para uma declaração `def`, usando aquele nome. . Remova o comentário. -Esse passos são uma citação do https://docs.python.org/pt-br/3/howto/functional.html["Programação Funcional—COMO FAZER)], uma leitura obrigatória. +Esse passos são uma citação do +https://fpy.li/46[_Programação Funcional—COMO FAZER_], +leitura obrigatória. **** -A sintaxe `lambda` é apenas açúcar sintático: uma expressão `lambda` cria um objeto função, exatamente como a declaração `def`. -Esse é apenas um dos vários tipos de objetos invocáveis no Python. Na próxima seção revisamos todos eles. +A sintaxe `lambda` é apenas açúcar sintático: uma expressão `lambda` cria um objeto função, +exatamente como a declaração `def`. +Esse é apenas um dos vários tipos de objetos invocáveis no Python. +Na próxima seção revisamos todos eles. [[flavors_of_callables]] === Os nove sabores de objetos invocáveis @@ -285,33 +328,37 @@ Esse é apenas um dos vários tipos de objetos invocáveis no Python. Na próxim O((("functions, as first-class objects", "callable objects", id="FAFcall07")))((("callable objects", "nine types of", id="calobj07")))((("objects", "callable objects", id="Ocall07"))) operador de invocação `()` pode ser aplicado a outros objetos além de funções. // beyond user-defined functions and ++lambdas++s. Para determinar se um objeto é invocável, use a função embutida `callable()`. -No Python 3.9, a https://docs.python.org/pt-br/3/reference/datamodel.html#the-standard-type-hierarchy[documentação do modelo de dados] lista nove tipos invocáveis: +No Python 3.10, a https://fpy.li/47[documentação do modelo de dados] lista nove tipos invocáveis: [role="pagebreak-before less_space"] -Funções definidas pelo usuário:: Criadas((("user-defined functions"))) com comandos `def` ou expressões `lambda`. +Funções definidas pelo usuário:: Criadas((("user-defined functions"))) com instruções `def` ou expressões `lambda`. -Funções embutidas:: Uma((("built-in functions"))) funções implementadas em C (no CPython), como `len` ou `time.strftime`. +Funções embutidas:: Funções((("built-in functions"))) implementadas em C (no CPython), como `len` ou `time.strftime`. Métodos embutidos:: Métodos((("methods, as callable objects"))) implementados em C, como `dict.get`. Métodos:: Funções definidas no corpo de uma classe. Classes:: Quando((("classes", "as callable objects"))) invocada, uma classe executa seu método -`+__new__+` para criar uma instância, e a seguir `+__init__+`, para inicializá-la. Então a instância é devolvida ao usuário. Como não existe um operador `new` no Python, invocar uma classe é como invocar uma função.footnote:[Invocar uma classe normalmente cria uma instância daquela mesma classe, mas outros comportamentos são possíveis, sobrepondo o `+__new__+`. Veremos um exemplo disso na seção <>.] +`+__new__+` para criar uma instância, e a seguir `+__init__+`, para inicializá-la. Então a instância é devolvida ao usuário. +Como não existe um operador `new` no Python, invocar uma classe é como invocar uma função.footnote:[Invocar uma classe normalmente cria uma instância daquela mesma classe, mas outros comportamentos são possíveis, sobrescrevendo o `+__new__+`. Veremos um exemplo disso na <>.] Instâncias de classe:: Se uma classe define um método `+__call__+`, suas instâncias podem então ser invocadas como funções—esse é o assunto da próxima seção. -Funções geradoras:: Funções ou métodos que usam a palavra reservada((("yield keyword")))((("keywords", "yield keyword")))((("generators", "generator functions in Python standard library"))) `yield` em seu corpo. Quando chamadas, devolvem um objeto gerador. +Funções geradoras:: Funções ou métodos que usam a palavra reservada((("yield keyword")))((("keywords", "yield keyword")))((("generators", "generator functions in Python standard library"))) `yield` em seu corpo. +Quando chamadas, devolvem um objeto gerador. -Funções de corrotinas nativas:: Funções((("native coroutines", "functions defined with async def"))) ou métodos definidos com `async def`. Quando chamados, devolvem um objeto corrotina. Introduzidas no Python 3.5. +Funções de corrotinas nativas:: Funções((("native coroutines", "functions defined with async def"))) ou métodos definidos com `async def`. Quando chamados, devolvem um objeto corrotina. +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 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 uma framework de programação assíncrona, tal como _asyncio_. -Elas são o assunto do <>. +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`. +Elas são o assunto do <>. [TIP] @@ -332,16 +379,20 @@ Vamos agora criar instâncias de classes que funcionam como objetos invocáveis. [[user_callables]] === Tipos invocáveis definidos pelo usuário -Não((("functions, as first-class objects", "user-defined callable types")))((("callable objects", "user-defined")))((("objects", "user-defined callable objects"))) só as funções Python são objetos reais, também é possível fazer com que objetos Python arbitrários se comportem como funções. Para isso basta implementar o método de instância `+__call__+`. +Além((("functions, as first-class objects", "user-defined callable types")))((("callable objects", "user-defined")))((("objects", "user-defined callable objects"))) das funções serem objetos reais, +também é possível fazer com que objetos arbitrários se comportem como funções. +Para isso, basta implementar o método de instância `+__call__+`. -O <> implementa uma classe `BingoCage`. Uma instância é criada a partir de qualquer iterável, e mantém uma `list` interna de itens, em ordem aleatória. Invocar a instância extrai um item.footnote:[Por que criar uma `BingoCage` quando já temos `random.choice`? A função `choice` pode devolver o mesmo item múltiplas vezes, pois o item escolhido não é removido da coleção usada. Invocações de `BingoCage` nunca devolvem um resultado duplicado—desde que a instância tenha sido preenchida com valores únicos.] +O <> implementa uma classe `BingoCage`. Uma instância é criada a partir de qualquer iterável, e mantém uma `list` interna de itens, em ordem aleatória. +Invocar a instância extrai um item.footnote:[Por que criar uma `BingoCage` quando já temos `random.choice`? A função `choice` pode devolver o mesmo item múltiplas vezes, pois o item escolhido não é removido da coleção usada. +Invocações de `BingoCage` nunca devolvem um resultado duplicado—desde que a instância tenha sido preenchida com valores únicos.] [[ex_bingo_callable]] .bingocall.py: Uma `BingoCage` faz apenas uma coisa: escolhe itens de uma lista embaralhada ==== -[source, py] +[source, python] ---- -include::code/07-1class-func/bingocall.py[tags=BINGO] +include::../code/07-1class-func/bingocall.py[tags=BINGO] ---- ==== <1> `+__init__+` aceita qualquer iterável; criar uma cópia local evita efeitos colaterais inesperados sobre qualquer `list` passada como argumento. @@ -352,30 +403,40 @@ include::code/07-1class-func/bingocall.py[tags=BINGO] Aqui está uma demonstração simples do <>. Observe como uma instância de `bingo` pode ser invocada como uma função, e como a função embutida `callable()` a reconhece como um objeto invocável: -[source, py] +[source, python] ---- -include::code/07-1class-func/bingocall.py[tags=BINGO_DEMO] +include::../code/07-1class-func/bingocall.py[tags=BINGO_DEMO] ---- -Uma classe que implemente `+__call__+` é uma forma fácil de criar objetos similares a funções, com algum estado interno que precisa ser mantido de uma invocação para outra, como os itens restantes na `BingoCage`. -Outro bom caso de uso para `+__call__+` é a implementação de decoradores. Decoradores devem ser invocáveis, e muitas vezes é conveniente "lembrar" algo entre chamadas ao decorador (por exemplo, para _memoization_—a manutenção dos resultados de algum processamento complexo e/ou demorado para uso posterior) ou para separar uma implementação complexa por diferentes métodos. +Uma classe que implementa `+__call__+` é uma forma fácil de criar objetos similares a funções, +com algum estado interno que precisa ser mantido de uma invocação para outra, +como os itens restantes na `BingoCage`. +Outro bom caso de uso para `+__call__+` é a implementação de decoradores. +Decoradores devem ser invocáveis, e muitas vezes é conveniente "lembrar" algo entre chamadas ao decorador +por exemplo, para _memoization_ (a manutenção dos resultados de algum processamento complexo e/ou demorado para uso posterior) +ou para separar uma implementação complexa entre vários métodos. -A abordagem funcional para a criação de funções com estado interno é através do uso de clausuras (_closures_). Clausuras e decoradores são o assunto do <>. +A abordagem funcional para a criação de funções com estado interno é através do uso de clausuras (_closures_). +Clausuras e decoradores são o assunto do <>. Vamos agora explorar a poderosa sintaxe oferecida pelo Python para declarar parâmetros de funções, e para passar argumentos para elas. === De parâmetros posicionais a parâmetros somente nomeados -Um((("functions, as first-class objects", "flexible parameter handling and", id="FAFflex07")))((("positional parameters", id="pospar07")))((("parameters", "positional", id="Pposition07"))) dos melhores recursos das funções Python é seu mecanismo extremamente flexível de tratamento de parâmetros. Intimamente((("unpacking", "iterables and mappings")))((("star (*) operator")))((("* (star) operator")))((("** (double star) operator")))((("double star (**) operator"))) relacionados a isso são os usos de `+*+` e `+**+` para desempacotar iteráveis e mapeamentos em argumentos separados quando chamamos uma função. -Para ver esses recursos em ação, observe o código do <> e os testes mostrando seu uso no <>. +Um((("functions, as first-class objects", "flexible parameter handling and", +id="FAFflex07")))((("positional parameters", id="pospar07")))((("parameters", "positional", id="Pposition07"))) dos melhores recursos das funções Python é +sua sintaxe muito flexível para declaração e tratamento de parâmetros. +Exemplos((("unpacking", "iterables and mappings")))((("star (*) operator")))((("* (star) operator")))((("** (double star) operator")))((("double star (**) operator"))) disso são os usos de `+*+` e `+**+` para +desempacotar e capturar iteráveis e mapeamentos em argumentos separados na chamada de uma função. +Para ver esses recursos em ação, veja o código do <> e os testes mostrando seu uso no <>. [[tagger_ex]] .`tag` gera elementos HTML; um argumento somente nomeado `class_` é usado para passar atributos "class"; o `_` é necessário porque `class` é uma palavra reservada no Python ==== -[source, py] +[source, python] ---- -include::code/07-1class-func/tagger.py[tags=TAG_FUNC] +include::../code/07-1class-func/tagger.py[tags=TAG_FUNC] ---- ==== @@ -384,9 +445,9 @@ A função `tag` pode ser invocada de muitas formas, como demonstra o <> ==== -[source, py] +[source, python] ---- -include::code/07-1class-func/tagger.py[tags=TAG_DEMO] +include::../code/07-1class-func/tagger.py[tags=TAG_DEMO] ---- ==== <1> Um argumento posicional único produz uma `tag` vazia com aquele nome. @@ -394,12 +455,15 @@ include::code/07-1class-func/tagger.py[tags=TAG_DEMO] <3> Argumentos nomeados que não são mencionados explicitamente na assinatura de `tag` são capturados por `**attrs` como um `dict`. <4> O parâmetro `class_` só pode ser passado como um argumento nomeado. <5> O primeiro argumento posicional também pode ser passado como argumento nomeado. -<6> Prefixar o `dict` `my_tag` com `+**+` passa todos os seus itens como argumentos separados, que são então vinculados aos parâmetros nomeados, com o restante sendo capturado por `**attrs`. Nesse caso podemos ter um nome `'class'` no `dict` de argumentos, porque ele é uma string, e não colide com a palavra reservada `class`. +<6> Prefixar o `dict` `my_tag` com `+**+` passa todos os seus itens como argumentos separados, +que são então vinculados aos parâmetros nomeados, com o restante sendo capturado por `**attrs`. +Nesse caso podemos ter uma chave `'class'` no `dict` de argumentos, porque é uma string, e não colide com a palavra reservada `class`. -Argumentos somente nomeados((("keyword-only arguments")))((("parameters", "keyword-only")))((("arguments", "keyword-only arguments"))) são um recurso do Python 3. No <>, o parâmetro `class_` só pode ser passado como um argumento nomeado—ele nunca captura argumentos posicionais não-nomeados. Para especificar argumentos somente nomeados ao definir uma função, eles devem ser nomeados após o argumento prefixado por `+*+`. Se você não quer incluir argumentos posicionais variáveis, mas ainda assim deseja incluir argumentos somente nomeados, coloque um `+*+` sozinho na assinatura, assim: +Argumentos somente nomeados((("keyword-only arguments")))((("parameters", "keyword-only")))((("arguments", "keyword-only arguments"))) são um recurso de Python 3. No <>, o parâmetro `class_` só pode ser passado como um argumento nomeado—ele nunca captura argumentos posicionais não-nomeados. +Para especificar argumentos somente nomeados ao definir uma função, eles devem ser nomeados após o argumento prefixado por `+*+`. Se você não quer incluir argumentos posicionais variáveis, mas ainda assim deseja incluir argumentos somente nomeados, coloque um `+*+` sozinho na assinatura, assim: -[source, pycon] +[source, python] ---- >>> def f(a, *, b): ... return a, b @@ -418,12 +482,13 @@ Observe que argumentos somente nomeados não precisam ter um valor default: eles [[positional_only_params]] ==== Parâmetros somente posicionais -Desde o Python 3.8, assinaturas de funções definidas pelo usuário podem especificar parâmetros somente posicionais. Esse recurso sempre existiu para funções embutidas, tal como `divmod(a, b)`, +Desde o Python 3.8, assinaturas de funções definidas pelo usuário podem especificar parâmetros somente posicionais. +Esse recurso sempre existiu para funções embutidas, tal como `divmod(a, b)`, que só pode ser chamada com parâmetros posicionais, e não na forma `divmod(a=10, b=4)`. Para definir uma função que requer parâmetros somente posicionais, use `/` na lista de parâmetros. -Esse exemplo, de https://docs.python.org/pt-br/3/whatsnew/3.8.html#positional-only-parameters["O que há de novo no Python 3.8"], mostra como emular a função embutida `divmod`: +Esse exemplo, de https://fpy.li/48["O que há de novo no Python 3.8"], mostra como emular a função embutida `divmod`: [source, python] ---- @@ -431,7 +496,8 @@ def divmod(a, b, /): return (a // b, a % b) ---- -Todos os argumentos à esquerda da `/` são somente posicionais. Após a `/`, você pode especificar outros argumentos, que funcionam como da forma usual. +Todos os argumentos à esquerda da `/` são somente posicionais. +Após a `/`, você pode especificar outros argumentos, que funcionam como da forma usual. [WARNING] ==== @@ -447,25 +513,29 @@ def tag(name, /, *content, class_=None, **attrs): ... ---- -Você pode encontrar outros exemplos de parâmetros somente posicionais no já citado https://docs.python.org/pt-br/3/whatsnew/3.8.html#positional-only-parameters["O que há de novo no Python 3.8"] e na https://fpy.li/pep570[PEP 570]. +Você pode encontrar outros exemplos de parâmetros somente posicionais no já citado https://fpy.li/48["O que há de novo no Python 3.8"] e na https://fpy.li/pep570[PEP 570]. Após esse mergulho nos recursos flexíveis de declaração de argumentos no Python, o resto desse capítulo trata dos pacotes da biblioteca padrão mais úteis para programar em um estilo funcional.((("", startref="FAFflex07")))((("", startref="pospar07")))((("", startref="Pposition07"))) === 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 o 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_section]] +[[operator_module_sec]] ==== O módulo operator -Na((("operator module", id="opmod07"))) programação funcional, é muitas vezes conveniente usar um operador aritmético como uma função. Por exemplo, suponha que você queira multiplicar uma sequência de números para calcular fatoriais, mas sem usar recursão. Para calcular a soma, podemos usar `sum`, mas não há uma função equivalente para multiplicação. Você poderia usar ++reduce++—como vimos na seção <>—mas isso exige um função para multiplicar dois itens da sequência. O <> mostra como resolver esse problema usando `lambda`. +Na((("operator module", id="opmod07"))) programação funcional, é muitas vezes conveniente usar um operador aritmético como uma função. +Por exemplo, suponha que você queira multiplicar uma sequência de números para calcular fatoriais, mas sem usar recursão. +Para calcular a soma, podemos usar `sum`, mas não há uma função equivalente para multiplicação. +Você poderia usar ``reduce``—como vimos na <>—mas isso exige um função para multiplicar dois itens da sequência. +O <> mostra como resolver esse problema usando `lambda`. [[fact_reduce_lambda_ex]] -.Fatorial implementado com `reduce`e uma função anônima +.Fatorial implementado com `reduce` e uma função anônima ==== -[source, python3] +[source, python] ---- from functools import reduce @@ -474,13 +544,14 @@ def factorial(n): ---- ==== -O módulo `operator` oferece funções equivalentes a dezenas de operadores, para você não precisar escrever funções triviais como `lambda a, b: a*b`. -Com ele, podemos reescrever o <> como o <>. +O módulo `operator` oferece funções equivalentes a dezenas de operadores, +para você não precisar escrever funções triviais como `lambda a, b: a*b`. +Com ele, podemos reescrever o <> como no <>. [[fact_reduce_operator_ex]] .Fatorial implementado com `reduce` e `operator.mul` ==== -[source, python3] +[source, python] ---- from functools import reduce from operator import mul @@ -491,7 +562,7 @@ def factorial(n): ==== Outro grupo de "++lambda++s de um só truque" que `operator` substitui são funções para extrair itens de sequências ou para ler atributos de objetos: -`itemgetter` e `attrgetter` são fábricas que criam funções personalizadas para fazer exatamente isso. +`itemgetter` e `attrgetter` são fábricas que criam funções customizadas para fazer exatamente isso. O <> mostra um uso frequente de `itemgetter`: ordenar uma lista de tuplas pelo valor de um campo. No exemplo, as cidades são exibidas por ordem de código de país (campo 1). @@ -501,7 +572,7 @@ Isso é mais fácil de escrever e ler que `lambda fields: fields[1]`, que faz a [[itemgetter_demo]] .Demonstração de `itemgetter` para ordenar uma lista de tuplas (mesmos dados do <>) ==== -[source, pycon] +[source, python] ---- >>> metro_data = [ ... ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)), @@ -523,9 +594,9 @@ Isso é mais fácil de escrever e ler que `lambda fields: fields[1]`, que faz a ---- ==== -Se você passar múltiplos argumentos de indice para `itemgetter`, a função criada por ela vai devolver tuplas com os valores extraídos, algo que pode ser útil para ordenar((("keys", "sorting multiple"))) usando chaves múltiplas: +Se você passar vários índices como argumentos para `itemgetter`, a função criada por ela vai devolver tuplas com os valores extraídos, algo que pode ser útil para ordenar((("keys", "sorting multiple"))) usando chaves múltiplas: -[source, pycon] +[source, python] ---- >>> cc_name = itemgetter(1, 0) >>> for city in metro_data: @@ -542,13 +613,18 @@ Se você passar múltiplos argumentos de indice para `itemgetter`, a função cr Como `itemgetter` usa o operador `[]`, ela suporta não apenas sequências, mas também mapeamentos e qualquer classe que implemente `+__getitem__+`. -Uma irmã de `itemgetter` é `attrgetter`, que cria funções para extrair atributos por nome. Se você passar os nomes de vários atributos como argumentos para `attrgetter`, ela vai devolver um tupla de valores. Além disso, se o nome de qualquer argumento contiver um `.` (ponto), `attrgetter` -navegará por objetos aninhados para encontrar o atributo. Esses comportamento são apresentados no <>. Não é exatamente uma sessão de console curta, pois precisamos criar uma estrutura aninhada para demonstrar o tratamento de atributos com `.` por `attrgetter`. +Uma irmã de `itemgetter` é `attrgetter`, para obter atributos por nome. +Se você passar os nomes de vários atributos para `attrgetter`, ela devolve uma tupla de valores. +Além disso, se o nome de qualquer argumento contiver um `.` (ponto), `attrgetter` +navegará por objetos aninhados para encontrar o atributo. +Esses comportamentos são apresentados no <>. +É uma sessão de console um pouco longa, pois precisamos criar uma +estrutura aninhada para demonstrar o tratamento de atributos com `.` por `attrgetter`. [[attrgetter_demo]] .Demonstração de `attrgetter` para processar uma lista previamente definida de `namedtuple` chamada `metro_data` (a mesma lista que aparece no <>) ==== -[source, pycon] +[source, python] ---- >>> from collections import namedtuple >>> LatLon = namedtuple('LatLon', 'lat lon') # <1> @@ -579,12 +655,13 @@ lon=139.691667)) <4> Obtém a latitude de dentro de `metro_areas[0]` . <5> Define um `attrgetter` para obter `name` e o atributo aninhado `coord.lat`. <6> Usa `attrgetter` novamente para ordenar uma lista de cidades pela latitude. -<7> Usa o +attrgetter+ definido em pass:[5] para exibir apenas o nome e a latitude da cidade. +<7> Usa o +attrgetter+ definido antes para exibir apenas o nome e a latitude da cidade. -Abaixo está uma lista parcial das funções definidas em `operator` (nomes iniciando com `_` foram omitidos por serem, em sua maioria, detalhes de implementação): +Abaixo está uma lista parcial das funções definidas em `operator` +(filtrei os nomes iniciando com `_` porque são, em sua maioria, detalhes de implementação): -[source, pycon] +[source, python] ---- >>> [name for name in dir(operator) if not name.startswith('_')] ['abs', 'add', 'and_', 'attrgetter', 'concat', 'contains', @@ -597,14 +674,20 @@ Abaixo está uma lista parcial das funções definidas em `operator` (nomes inic 'pow', 'rshift', 'setitem', 'sub', 'truediv', 'truth', 'xor'] ---- -A maior parte dos 54 nomes listados é auto-evidente. O grupo de nomes formados por um `i` inicial e o nome de outro operador—por exemplo `iadd`, `iand`, etc—correspondem aos operadores de atribuição aumentada—por exemplo, `+=`, `&=`, etc. Essas funções mudam seu primeiro argumento no mesmo lugar, se o argumento for mutável; se não, funcionam como seus pares sem o prefixo `i`: simplemente devolvem o resultado da operação. +A maior parte dos 54 nomes listados é mais ou menos evidente. +O grupo de nomes formados por um `i` inicial e o nome de um +operador—por exemplo `iadd`, `iand`, etc—correspondem aos operadores de atribuição aumentada—por exemplo, `+=`, `&=`, etc. +Essas funções mudam seu primeiro argumento internamente, se o argumento for mutável; +se não, funcionam como seus pares sem o prefixo `i`: simplesmente devolvem o resultado da operação. -Das funções restantes de `operator`, `methodcaller` será a última que veremos. Ela é algo similar a `attrgetter` e `itemgetter`, no sentido de criarem uma função durante a execução. A função criada invoca por nome um método do objeto passado como argumento, como mostra o <>. +Das funções restantes de `operator`, `methodcaller` será a última que veremos. +Ela é algo similar a `attrgetter` e `itemgetter`, no sentido de criarem uma função durante a execução. +A função criada invoca por nome um método do objeto passado como argumento, como mostra o <>. [[methodcaller_demo]] .Demonstração de `methodcaller`: o segundo teste mostra a vinculação de argumentos adicionais ==== -[source, pycon] +[source, python] ---- >>> from operator import methodcaller >>> s = 'The time has come' @@ -619,7 +702,7 @@ Das funções restantes de `operator`, `methodcaller` será a última que veremo O primeiro teste no <> está ali apenas para mostrar o funcionamento de `methodcaller`; se você precisa usar `str.upper` como uma função, basta chamá-lo na classe `str`, passando uma string como argumento, assim: -[source, pycon] +[source, python] ---- >>> str.upper(s) 'THE TIME HAS COME' @@ -630,7 +713,8 @@ O segundo teste do <> mostra que `methodcaller` pode também [[functools_partial_sec]] ==== Fixando argumentos com functools.partial -O((("functools module", "freezing arguments with", id="functools07")))((("arguments", "freezing with functools.partial"))) módulo `functools` oferece várias funções de ordem superior. Já vimos `reduce` na seção <>. +O((("functools module", "freezing arguments with", id="functools07")))((("arguments", "freezing with functools.partial"))) módulo `functools` oferece várias funções de ordem superior. +Já vimos `reduce` na <>. Uma outra é `partial`: dado um invocável, ela produz um novo invocável com alguns dos argumentos do invocável original vinculados a valores pré-determinados. Isso é útil para adaptar uma função que recebe um ou mais argumentos a uma API que requer uma função de callback com menos argumentos. O <> é uma demonstração trivial. @@ -638,7 +722,7 @@ O <> é uma demonstração trivial. [[ex_partial_mul]] .Empregando `partial` para usar uma função com dois argumentos onde é necessário um invocável com apenas um argumento ==== -[source, pycon] +[source, python] ---- >>> from operator import mul >>> from functools import partial @@ -653,12 +737,12 @@ O <> é uma demonstração trivial. <2> Testa a função. <3> Usa `triple` com `map`; `mul` não funcionaria com `map` nesse exemplo. -Um exemplo mais útil envolve a função `unicode.normalize`, que vimos na seção <>. Se você trabalha com texto em muitas línguas diferentes, pode querer aplicar `unicode.normalize('NFC', s)` a qualquer string `s`, antes de compará-la ou armazená-la. Se você precisa disso com frequência, é conveninete ter uma função `nfc` para executar essa tarefa, como no <>. +Um exemplo mais útil envolve a função `unicode.normalize`, que vimos na <>. Se você trabalha com texto em muitas línguas diferentes, pode querer aplicar `unicode.normalize('NFC', s)` a qualquer string `s`, antes de compará-la ou armazená-la. Se você precisa disso com frequência, é conveninete ter uma função `nfc` para executar essa tarefa, como no <>. [[ex_partial_nfc]] .Criando uma função conveniente para normalizar Unicode com `partial` ==== -[source, pycon] +[source, python] ---- >>> import unicodedata, functools >>> nfc = functools.partial(unicodedata.normalize, 'NFC') @@ -680,7 +764,7 @@ O <> mostra o uso de `partial` com a função `tag` (do <> ==== -[source, pycon] +[source, python] ---- >>> from tagger import tag >>> tag @@ -699,52 +783,61 @@ functools.partial(, 'img', class_='pic-frame') <4> {'class_': 'pic-frame'} ---- ==== -<1> Importa `tag` do <> e mostra seu ID. +<1> Importa `tag` do <> e mostra seu `id`. <2> Cria a função `picture` a partir de `tag`, fixando o primeiro argumento posicional em `'img'` e o argumento nomeado `class_` em `'pic-frame'`. <3> `picture` funciona como esperado. -<4> `partial()` devolve um objeto `functools.partial`.footnote:[O https://fpy.li/7-9[código fonte] de _functools.py_ revela que `functools.partial` é implementada em C e é usada por default. Se ela não estiver disponível, uma implementação em Python puro de `partial` está disponível desde o Python 3.4.] +<4> `partial()` devolve um objeto `functools.partial`.footnote:[O https://fpy.li/7-9[código-fonte] de _functools.py_ revela que `functools.partial` é implementada em C e é usada por default. +Se ela não estiver disponível, uma implementação em Python puro de `partial` está disponível desde o Python 3.4.] <5> Um objeto `functools.partial` tem atributos que fornecem acesso à função original e aos argumentos fixados. -A função `functools.partialmethod` faz o mesmo que `partial`, mas foi projetada para trabalhar com métodos. +A função `functools.partialmethod` faz o mesmo que `partial`, mas serve para trabalhar com métodos. -O módulo `functools` também inclui funções de ordem superior para serem usadas como decoradores de função, tais como `cache` e `singledispatch`, entre outras. -Essas funções são tratadas no <>, que também explica como implementar decoradores personalizados.((("", startref="FAFfp07")))((("", startref="fprogpack07")))((("", startref="functools07"))) +O módulo `functools` também inclui funções de ordem superior para serem usadas como decoradores de função, como `cache` e `singledispatch`, entre outras. +Essas funções são tratadas no <>, que também explica como implementar decoradores customizados.((("", startref="FAFfp07")))((("", startref="fprogpack07")))((("", startref="functools07"))) === Resumo do capítulo -O((("functions, as first-class objects", "overview of"))) objetivo deste capítulo foi explorar a natureza das funções como objetos de primeira classe no Python. As principais consequências disso são a possibilidade de atribuir funções a variáveis, passá-las para outras funções, armazená-las em estruturas de dados e acessar os atributos de funções, permitindo que frameworks e ferramentas usem essas informações. +O((("functions, as first-class objects", "overview of"))) objetivo deste capítulo foi explorar a natureza das funções como objetos de primeira classe no Python. +As principais consequências disso são a possibilidade de atribuir funções a variáveis, passá-las para outras funções, armazená-las em estruturas de dados e acessar os atributos de funções, permitindo que frameworks e ferramentas usem essas informações. -Funções de ordem superior, parte importante da programação funcional, são comuns no Python. As funções embutidas `sorted`, `min` e `max`, além de `functools.partial`, são exemplos de funções de ordem superior muito usadas na linguagem. +Funções de ordem superior, parte importante da programação funcional, são comuns no Python. +As funções embutidas `sorted`, `min` e `max`, além de `functools.partial`, são exemplos de funções de ordem superior muito usadas na linguagem. O uso de `map`, `filter` e `reduce` já não é tão frequente como costumava ser, graças às compreensões de lista (e estruturas similares, como as expressões geradoras) e à adição de funções embutidas de redução como `sum`, `all` e `any`. -Desde o Python 3.6, existem nove sabores de invocáveis, de funções simples criadas com `lambda` a instâncias de classes que implementam `+__call__+`. -Geradoras e corrotinas também são invocáveis, mas seu comportamento é muito diferente daquele de outros invocáveis. -Todos os invocáveis podem ser detectados pela função embutida `callable()`. Invocáveis oferecem uma rica sintaxe para declaração de parâmetros formais, incluindo parâmetros nomeados, parâmetros somente posicionais e anotações. +Desde o Python 3.6, existem nove sabores de invocáveis, como funções simples criadas com `lambda` ou instâncias de classes que implementam `+__call__+`. +Geradoras e corrotinas também são invocáveis, mas seu comportamento é muito diferente dos outros invocáveis. +Todos os invocáveis podem ser detectados pela função embutida `callable()`. +Invocáveis oferecem uma rica sintaxe para declaração de parâmetros formais, +incluindo parâmetros nomeados, parâmetros somente posicionais e anotações. -Por fim, vimos algumas funções do módulo `operator` e `functools.partial`, que facilitam a programação funcional, minimizando a necessidade de uso da sintaxe funcionalmente inepta de `lambda`. +Por fim, vimos algumas funções do módulo `operator` e `functools.partial`, +que facilitam a programação funcional, reduzindo a necessidade de lidar com a sintaxe de `lambda`. [[first_cls_fn_further_reading_sec]] -=== Leitura complementar +=== Para saber mais Nos((("functions, as first-class objects", "further reading on"))) próximos capítulos, continuaremos nossa jornada pela programação com objetos função. -O <> é dedicado às dicas de tipo nos parâmetros de função e nos valores devolvidos por elas. -O <> mergulha nos decoradores de função—um tipo especial de função de ordem superior—e no mecanismo de clausura (_closure_) que os faz funcionar. -O <> mostra como as funções de primeira classe podem simplificar alguns padrões clássicos de projetos (_design patterns_) orientados a objetos. +O <> é dedicado às dicas de tipo nos parâmetros de função e nos valores devolvidos por elas. +O <> mergulha nos decoradores de função—um tipo especial de função de ordem superior—e no mecanismo de clausura (_closure_) que os faz funcionar. +O <> mostra como as funções de primeira classe podem simplificar alguns padrões clássicos de projetos (_design patterns_) orientados a objetos. -Em _A Referência da Linguagem Python_, a seção https://docs.python.org/pt-br/3/reference/datamodel.html#the-standard-type-hierarchy["3.2. A hierarquia de tipos padrão"] mostra os noves tipos invocáveis, juntamente com todos os outros tipos embutidos. +Em _A Referência da Linguagem Python_, a seção https://fpy.li/47["3.2. A hierarquia de tipos padrão"] mostra os nove tipos invocáveis, juntamente com todos os outros tipos embutidos. -O capítulo 7 do pass:[Python Cookbook] (EN), 3ª ed. (O'Reilly), de David Beazley e Brian K. Jones, é um excelente complemento a esse capítulo, bem como ao <>, tratando basicamente dos mesmos conceitos, mas com uma abordagem diferente. +O capítulo 7 do +https://fpy.li/pycook3[_Python Cookbook_] (EN), 3ª ed. (O'Reilly), de David Beazley e Brian K. Jones, +é um excelente complemento a esse capítulo, bem como ao <>, +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 você estiver interessada na justificativa e nos casos desse recurso. +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://docs.python.org/pt-br/3/howto/functional.html["Programação Funcional COMO FAZER"], de A. M. Kuchling. -O principal foco daquele texto, entretanto, é o uso de iteradores e geradoras, assunto do <>. +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 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). -Refletindo sobre a pergunta "Seria o Python uma linguagem funcional?", criei uma de minhas palestras favoritas, "Beyond Paradigms" ("Para Além dos Paradigmas"), que apresentei na PyCaribbean, na PyBay e na PyConDE. +Refletindo sobre a pergunta "Seria Python uma linguagem funcional?", criei uma de minhas palestras favoritas, "Beyond Paradigms" ("Para Além dos Paradigmas"), que apresentei na PyCaribbean, na PyBay e na PyConDE. Veja os https://fpy.li/7-13[slides] (EN) e o https://fpy.li/7-14[vídeo] (EN) da apresentação em Berlim—onde conheci Miroslav Šedivý e Jürgen Gmach, dois dos revisores técnicos desse livro. [[soapbox_1st_class_fn]] @@ -752,10 +845,15 @@ Veja os https://fpy.li/7-13[slides] (EN) e o https://fpy.li/7-14[vídeo] (EN) da **** [role="soapbox-title"] -O Python é uma linguagem funcional? - -Em((("functions, as first-class objects", "Soapbox discussion")))((("Soapbox sidebars", "functional programming with Python")))((("Python", "functional programming with")))((("functional programming", "with Python", secondary-sortas="Python"))) algum momento do ano 2000, eu estava participando de uma oficina de Zope na Zope Corporation, nos EUA, quando Guido van Rossum entrou na sala (ele não era o instrutor). -Na seção de perguntas e respostas que se seguiu, alguém perguntou quais recursos do Python ele tinha trazido de outras linguagens. +**Python é uma linguagem funcional?** + +Em((("functions, as first-class objects", +"Soapbox discussion")))((("Soapbox sidebars", "functional programming with Python")))((("Python", +"functional programming with")))((("functional programming", "with Python", +secondary-sortas="Python"))) algum momento do ano 2000, +eu estava participando de uma oficina sobre o framework Zope na Zope Corporation, +nos EUA, quando Guido van Rossum entrou na sala (ele não era o instrutor). +Na seção de perguntas e respostas que se seguiu, alguém perguntou quais recursos de Python ele tinha trazido de outras linguagens. A resposta de Guido: "Tudo que é bom no Python foi roubado de outras linguagens." Shriram Krishnamurthi, professor de Ciência da Computação na Brown University, @@ -763,39 +861,67 @@ inicia seu artigo, https://fpy.li/7-15["Teaching Programming Languages in a Post [quote] ____ -Os "paradigmas" de linguagens de programação são um legado moribundo e tedioso de uma era passada. Os atuais projetistas de linguagens não tem qualquer respeito por eles, então por que nossos cursos aderem servilmente a tais "paradigmas"? +Os "paradigmas" de linguagens de programação são um legado moribundo e tedioso de uma era passada. +Os atuais projetistas de linguagens não têm qualquer respeito por eles, então por que nossos cursos aderem servilmente a tais "paradigmas"? ____ -Nesse artigo, o Python é mencionado nominalmente na seguinte passagem: +Nesse artigo, Python é mencionado nominalmente na seguinte passagem: [quote] ____ E como descrever linguagens como Python, Ruby, ou Perl? -Seus criadores não tem paciência com as sutilezas dessas nomenclaturas de Lineu; +Seus criadores não têm paciência com as sutilezas dessas nomenclaturas de Lineu; eles pegam emprestados todos os recursos que desejam, criando misturas que desafiam totalmente uma caracterização. ____ Krishnamurthi argumenta que, ao invés de tentar classificar as linguagens com alguma taxonomia, seria mais útil olhar para elas como agregados de recursos. -Suas ideias inspiraram minha palestra "Beyond Paradigms" ("Para Além dos Paradigmas"), mencionada no final da seção <>. - -Mesmo se esse não fosse o objetivo de Guido, dotar o Python de funções de primeira classe abriu as portas para a programação funcional. Em seu post, https://fpy.li/7-1["Origins of Python's 'Functional' Features" (_As Origens dos Recursos 'Funcionais' dp Python)] (EN), ele afirma que `map`, `filter`, e `reduce` foram a primeira motivação para a inclusão do `lambda` ao Python. Todos esses recursos foram adicionados juntos ao Python 1.0 em 1994, por Amrit Prem , de acordo com o https://fpy.li/7-17[Misc/HISTORY] (EN) no código-fonte do CPython. - -Funções como `map`, `filter`, e `reduce` surgiram inicialmente no Lisp, a linguagem funcional original. O Lisp, entretanto, não limita o que pode ser feito dentro de uma `lambda`, pois tudo em List é uma expressão. O Python usa uma sintaxe orientada a comandos, na qual as expressões não podem conter comandos, e muitas das estruturas da linguagem são comandos--incluindo`try/catch`, que é o que eu mais sinto falta quando escrevo uma pass:[lambda]. É o preço a pagar pela sintaxe extremamente legível do Python.footnote:[Há também o problema da perda de indentação quando colamos trechos de código em fóruns na Web, mas isso é outro assunto.] O Lisp tem muitas virtudes, mas legibilidade não é uma delas. +Suas ideias inspiraram minha palestra "Beyond Paradigms" ("Para Além dos Paradigmas"), mencionada no final da <>. + +Mesmo se esse não fosse o objetivo de Guido, dotar Python de funções de primeira classe abriu as portas para a programação funcional. +Em seu post, https://fpy.li/7-1["Origins of Python's 'Functional' Features" (_As Origens dos Recursos 'Funcionais' dp Python_)] (EN), +ele conta que `map`, `filter`, e `reduce` foram a primeira motivação para a inclusão do `lambda` ao Python. +Todos esses recursos foram adicionados juntos ao Python 1.0 em 1994, por Amrit Prem, +de acordo com o https://fpy.li/7-17[Misc/HISTORY] (EN) no código-fonte do CPython. + +Funções como `map`, `filter` e `reduce` surgiram inicialmente em Lisp, a primeira linguagem funcional. +Lisp, entretanto, não limita o que pode ser feito dentro de uma `lambda`, pois tudo em Lisp é uma expressão. +Python usa uma sintaxe orientada a instruções (_statement oriented syntax_), +na qual as expressões não podem conter instruções, +e muitas das estruturas da linguagem são instruções--incluindo `try/catch`, +que é o que mais sinto falta quando escrevo uma `lambda`. +É o preço a pagar pela sintaxe extremamente legível de Python.footnote:[Há também o problema da perda de indentação quando colamos trechos de código em fóruns na Web, mas isso é outro assunto.] Lisp tem muitas virtudes, mas legibilidade não é uma delas. Ironicamente, roubar a sintaxe de compreensão de lista de outra linguagem funcional—Haskell—reduziu significativamente a necessidade de usar `map` e `filter`, e também `lambda`. -Além da sintaxe limitada das funções anônimas, o maior obstáculo para uma adoção mais ampla de idiomas de programação funcional no Python é a ausência da eliminação de chamadas de cauda, uma otimização que permite o processamento, de forma eficiente em termos de memória, de uma função que faz uma chamada recursiva na "cauda" de seu corpo. Em outro post de blog, https://fpy.li/7-18["Tail Recursion Elimination" (_Eliminação de Recursão de Cauda_)] (EN), Guido apresenta várias razões pelas quais tal otimização não é adequada ao Python. O post é uma ótima leitura por seus argumentos técnicos, mas mais ainda pelas primeiras três e mais importantes razões dadas serem questões de usabilidade. O Python não é gostoso de usar, aprender e ensinar por acidente. Guido o fez assim. - -Então cá estamos: o Python não é, por projeto, uma linguagem funcional—seja lá o quê isso signifique. O Python só pega emprestadas algumas boas ideias de linguagens funcionais. +Além da sintaxe limitada das funções anônimas, +o maior obstáculo para uma adoção mais ampla de idiomas de programação funcional no Python é +a ausência da eliminação de chamadas de cauda, uma otimização que permite +o processamento eficiente de uma função que faz uma chamada recursiva na "cauda" de seu corpo, +sem aumentar a pilha de execução a cada recursão. +Em outro post, +https://fpy.li/7-18["Tail Recursion Elimination" (_Eliminação de Recursão de Cauda_)] (EN), +Guido apresenta várias razões pelas quais tal otimização não é adequada ao Python. +O post é uma ótima leitura por seus argumentos técnicos, +e mais ainda porque as três primeiras e mais importantes razões dadas são questões de usabilidade. +Python não é um prazer de usar, aprender e ensinar por acidente. +Guido foi intencional. + +Então cá estamos: Python não é, por projeto, uma linguagem funcional—seja lá o quê isso signifique. +Python só pega emprestadas algumas boas ideias de linguagens funcionais. [role="soapbox-title"] -O problema das funções anônimas +**O problema das funções anônimas** -Além((("Soapbox sidebars", "anonymous functions")))((("anonymous functions")))(((""))) das restrições sintáticas específicas do Python, funções anônimas tem uma séria desvantagem em qualquer linguagem: elas não tem nome. +Além((("Soapbox sidebars", "anonymous functions")))((("anonymous functions")))(((""))) +das restrições sintáticas específicas de Python, +funções anônimas têm uma séria desvantagem em qualquer linguagem: elas não têm nome. -Estou brincando, mas não muito. Os _stack traces_ são mais fáceis de ler quando as funções tem nome. Funções anônimas são um atalho conveniente, nos divertimos programando com elas, mas algumas vezes elas são levadas longe demais—especialmente se a linguagem e o ambiente encorajam o aninhamento profundo de funções anônimas, com faz o Javascript combinado com o Node.js. +Estou brincando, mas não muito. +Os _stack traces_ são mais fáceis de ler quando as funções têm nome. +Funções anônimas são um atalho conveniente, nos divertimos programando com elas, mas algumas vezes elas são levadas longe demais—especialmente se a linguagem e o ambiente encorajam o aninhamento profundo de funções anônimas, como faz o JavaScript combinado com o Node.js. Ter muitas funções anônimas aninhadas torna a depuração e o tratamento de erros mais difíceis. A programação assíncrona no Python é mais estruturada, talvez pela sintaxe limitada do `lambda` impedir seu abuso e forçar uma abordagem mais explícita. -Promessas, futuros e diferidos são conceitos usados nas APIs assíncronas modernas. Prometo escrever mais sobre programação assíncrona no futuro, mas esse assunto será diferidofootnote:[NT: Sim, "diferir" também significa "adiar" em português!] até o <>. +Promessas, futuros e adiados (_deferreds_) são conceitos usados nas APIs assíncronas modernas. +Prometo escrever mais sobre programação assíncrona no futuro, mas esse assunto será adiado até o <>. **** diff --git a/online/cap08.adoc b/online/cap08.adoc new file mode 100644 index 00000000..65fd0b91 --- /dev/null +++ b/online/cap08.adoc @@ -0,0 +1,2533 @@ +[[ch_type_hints_def]] +== Dicas de tipo em funções +:example-number: 0 +:figure-number: 0 + +[quote, Guido van Rossum, Jukka Lehtosalo, e Łukasz Langa, PEP 484—Type Hints] +____ +É preciso enfatizar que *Python continuará sendo uma linguagem de tipagem dinâmica, +e os autores não têm qualquer intenção de algum dia tornar dicas de tipo obrigatórias, +mesmo por mera convenção*.footnote:[https://fpy.li/8-1[PEP 484—Type Hints], seção _Rationale and Goals_; negritos mantidos do original.] +____ + +Dicas de tipo((("functions, type hints in", +"benefits and drawbacks of")))((("type hints (type annotations)", "benefits and drawbacks of"))) +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 todas as pessoas que usam Python. +Por isso deverão ser sempre opcionais. + +A +https://fpy.li/pep484[PEP 484—Type Hints] introduziu a sintaxe e a semântica para declarações explícitas de tipo +em argumentos de funções, valores de retorno e variáveis. +O objetivo é ajudar ferramentas de desenvolvimento a encontrarem bugs no código-fonte de programas em Python +através de análise estática, isto é, sem executar o código através de testes. + +Os maiores beneficiários são engenheiros de software profissionais que usam IDEs (_Ambientes de Desenvolvimento Integrados_) +e CI (_Integração Contínua_). +A análise de custo-benefício que torna as dicas de tipo atrativas para esse grupo não se aplica a todos os usuários de Python. + +A base de usuários de Python vai muito além dessa classe de profissionais. +Ela inclui cientistas, comerciantes, jornalistas, artistas, inventores, analistas e estudantes de inúmeras áreas, entre outros. +Para a maioria dessas pessoas, o custo de aprender dicas de tipo será maior--exceto para +a minoria que já conhece outra linguagem com tipos estáticos, subtipos e tipos genéricos. +Os benefícios serão menores para muitas pessoas, dada a forma como que elas interagem com Python, +o tamanho menor de suas bases de código e de suas equipes—muitas vezes "equipes solo". + +A tipagem dinâmica, default de Python, é mais simples e mais expressiva quando +programamos para explorar dados e ideias, como é o caso em ciência de dados, computação criativa e para aprender. + +Este capítulo se concentra nas dicas de tipo de Python nas assinaturas de função. +<> explora as dicas de tipo no contexto de classes e outros recursos do módulo `typing`. + +Os((("functions, type hints in", "topics covered")))((("type hints (type annotations)", "topics covered"))) +tópicos mais importantes aqui são: + +* Uma introdução prática à tipagem gradual com Mypy +* As perspectivas complementares da duck typing (_tipagem pato_) e da tipagem nominal +* A revisão das principais categorias de tipos que podem surgir em anotações (cerca de 60% do capítulo) +* Dicas de tipo em parâmetros variádicos (`\*args`, `**kwargs`) +* Limitações e desvantagens das dicas de tipo e da tipagem estática. + +=== Novidades neste capítulo + +Este((("functions, type hints in", "significant changes to")))((("type hints (type annotations)", "significant changes to"))) +capítulo é completamente novo. +As dicas de tipo apareceram no Python 3.5, após eu ter terminado de escrever a primeira edição de _Python Fluente_. + +Dadas as limitações de um sistema de tipagem estática, +a melhor ideia da PEP 484 foi propor um _sistema de tipagem gradual_. +Vamos começar definindo o que isso significa. + +=== Sobre tipagem gradual + +A PEP 484((("functions, type hints in", "gradual typing", +id="FTHgrad08")))((("type hints (type annotations)", "gradual typing", +id="THgrad0 8")))((("gradual type system", "basics of"))) +introduziu no Python um _sistema de tipagem gradual_. +Outras linguagens com sistemas de tipagem gradual são Typescript da Microsoft, +Dart (a linguagem do SDK Flutter, criado pelo Google), +e Hack (um dialeto de PHP que é compilado para a máquina virtual HHVM do Facebook). +O próprio checador de tipos MyPy começou como uma linguagem: +um dialeto de Python de tipagem gradual com seu próprio interpretador. +Guido van Rossum convenceu o criador do MyPy, Jukka Lehtosalo, +a transformá-lo em uma ferramenta para checar código Python anotado. + +Eis uma função com anotações de tipos: + +[source, python] +---- +def tokenize(s: str) -> list[str]: + "Convert a string into a list of tokens." + return s.replace('(', ' ( ').replace(')', ' ) ').split() +---- + +A assinatura informa que a função `tokenize` recebe uma `str` +e devolve `list[str]`: uma lista de strings. +A utilidade dessa função será explicada na <>. + +Um sistema de tipagem gradual: + +É opcional:: +Por default, o checador de tipos não deve emitir avisos para código que não tenha dicas de tipo. +Em vez disso, o checador supõe o tipo `Any` quando não consegue determinar o tipo de um objeto. +O tipo `Any` é considerado compatível com todos os outros tipos. +Não captura erros de tipagem durante a execução do código:: +Dicas de tipo são usadas por checadores de tipos, analisadores de código-fonte (_linters_) e IDEs para emitir avisos. +Eles não evitam que valores inconsistentes sejam passados para funções ou atribuídos a variáveis durante a execução. +Por exemplo, nada impede que alguém chame +`tokenize(42)`, apesar da anotação de tipo do argumento `s: str`. +A chamada ocorrerá, e teremos um erro de execução no corpo da função. +Não melhora o desempenho:: +Anotações de tipo fornecem dados que poderiam, em tese, permitir otimizações do bytecode gerado. +Mas, até julho de 2021, tais otimizações não ocorrem em nenhum ambiente Python que eu +conheça.footnote:[Um compilador JIT ("just-in-time", compiladores que transformam o bytecode +gerado pelo interpretador em código da máquina-alvo no momento da execução) +como o do PyPy tem informações muito melhores que as dicas de tipo: +ele monitora o programa Python durante a execução, detecta os tipos concretos em uso, +e gera código de máquina otimizado para aqueles tipos concretos.] + +O melhor aspecto de usabilidade da tipagem gradual é que as anotações são sempre opcionais. + +Nos sistemas de tipagem estáticos, a maioria das restrições de tipo são fáceis de expressar, +muitas são desajeitadas, muitas são difíceis e algumas são impossíveis. +Por exemplo, em julho de 2021, +tipos recursivos não tinham suporte—veja as questões +https://fpy.li/8-2[#182, Define a JSON type] (EN) +https://fpy.li/8-3[#731, Support recursive types] (EN) do MyPy. + +É possível que você escreva um ótimo programa em Python, +com uma boa cobertura de testes, todos passando, +mas ainda assim não consiga escrever dicas de tipo que satisfaçam um checador de tipos. +Nesse caso, omita as dicas de tipo problemáticas e entregue o programa! + +Dicas de tipo são opcionais em todos os níveis: +você pode criar ou usar pacotes inteiros sem dicas de tipo, +pode silenciar o checador ao importar algum módulo sem dicas de tipo, +e você também pode colocar comentários especiais, +para que o checador de tipos ignore certas linhas do seu código. + +[TIP] +==== +Tentar impor uma cobertura de 100% de dicas de tipo irá provavelmente estimular seu uso de forma impensada, +apenas para satisfazer essa métrica. +Isso também vai impedir equipes de aproveitarem da melhor forma possível o potencial e a flexibilidade de Python. +Código sem dicas de tipo deve ser aceito sem objeções quando anotações tornam +o uso de uma API menos amigável ou quando complicam demais seu desenvolvimento. +==== + +=== Tipagem gradual na prática + +Vamos ((("gradual type system", "in practice", id="GRSpract08"))) ver como a tipagem gradual funciona na prática, +começando com uma função simples e acrescentando gradativamente a ela dicas de tipo, +guiados pelo((("Mypy type checker", id="mypy08"))) Mypy. + +[NOTE] +==== +Há muitos((("Python type checkers"))) checadores de tipos para Python compatíveis com a PEP 484, +incluindo o https://fpy.li/8-4[pytype] do Google, +o https://fpy.li/8-5[Pyright] da Microsoft, +o https://fpy.li/8-6[Pyre] do Facebook — além de checadores incluídos em IDEs como o PyCharm. +Eu escolhi usar o https://fpy.li/mypy[Mypy] nos exemplos por ele ser o mais conhecido. +Entretanto, algum daqueles outros pode ser mais adequado para alguns projetos ou equipes. +O Pytype, por exemplo, foi projetado para lidar com bases de código sem nenhuma dica de tipo e ainda assim gerar recomendações úteis. +Ele é mais tolerante que o MyPy, e consegue também gerar anotações para o seu código. +==== + +Vamos anotar uma função `show_count`, que retorna uma string com um número e uma palavra no singular ou no plural, dependendo do número: + +[source, python] +---- +include::../code/08-def-type-hints/messages/no_hints/messages.py[tags=SHOW_COUNT_DOCTEST] +---- + +<> mostra o código-fonte de `show_count`, sem anotações. + +[[msgs_no_hints]] +.`show_count` de _messages.py_ sem dicas de tipo. +==== +[source, python] +---- +include::../code/08-def-type-hints/messages/no_hints/messages.py[tags=SHOW_COUNT] +---- +==== + +==== Usando o Mypy + +Para começar a checagem de tipos, rodamos o comando `mypy` passando o módulo _messages.py_ como parâmetro: + +[source] +---- +…/no_hints/ $ pip install mypy +[muitas mensagens omitidas...] +…/no_hints/ $ mypy messages.py +Success: no issues found in 1 source file +---- + +Na configuração default, o Mypy não encontra nenhum problema com o <>. + +[WARNING] +==== +Durante a revisão deste capítulo estou usando Mypy 0.910, a versão mais recente no momento (em julho de 2021). +A https://fpy.li/8-7["Introduction"] (EN) do Mypy adverte que ele "é oficialmente software beta. +Mudanças ocasionais irão quebrar a compatibilidade com versões mais antigas." +O Mypy está gerando pelo menos um relatório diferente daquele que recebi quando escrevi o capítulo, em abril de 2020. +E quando você estiver lendo essas linhas, talvez os resultados também sejam diferentes daqueles mostrados aqui. +==== + +Se a assinatura de uma função não tem anotações, Mypy a ignora por default—a menos que seja configurado de outra forma. + +O <> também inclui testes unitários do `pytest`. +Este é o código de _messages_test.py_: + +[[msgs_test_no_hints]] +._messages_test.py_ sem dicas de tipo. +==== +[source, python] +---- +include::../code/08-def-type-hints/messages/no_hints/messages_test.py[] +---- +==== + +Agora vamos acrescentar dicas de tipo, guiados pelo Mypy. + + +==== Tornando o Mypy mais rigoroso + +A opção de linha de comando `--disallow-untyped-defs` faz o Mypy apontar todas as +definições de funções que não tenham dicas de tipo para todos os argumentos e para o valor de retorno. + +Usando `--disallow-untyped-defs` com o arquivo de teste produz três erros e uma observação: + +[source] +---- +…/no_hints/ $ mypy --disallow-untyped-defs messages_test.py +messages.py:14: error: Function is missing a type annotation +messages_test.py:10: error: Function is missing a type annotation +messages_test.py:15: error: Function is missing a return type annotation +messages_test.py:15: note: Use "-> None" if function does not return a value +Found 3 errors in 2 files (checked 1 source file) +---- + +Nas primeiras etapas da tipagem gradual, prefiro usar outra opção: + +`--disallow-incomplete-defs` + +Com ela, o Mypy não me dá nenhuma nova informação num primeiro momento: + +[source] +---- +…/no_hints/ $ mypy --disallow-incomplete-defs messages_test.py +Success: no issues found in 1 source file +---- + +Agora acrescento apenas o tipo do retorno a `show_count` em _messages.py_: + +[source] +---- +def show_count(count, word) -> str: +---- + +Isso é suficiente para fazer o Mypy olhar para o código. +Usando a mesma linha de comando anterior para checar _messages_test.py_ +fará o Mypy examinar novamente o _messages.py_: + +[source] +---- +…/no_hints/ $ mypy --disallow-incomplete-defs messages_test.py +messages.py:14: error: Function is missing a type annotation +for one or more arguments +Found 1 error in 1 file (checked 1 source file) +---- + +Agora posso gradualmente acrescentar dicas de tipo, função por função, +sem receber avisos sobre as funções onde ainda não adicionei anotações +Essa é uma assinatura completamente anotada que satisfaz o Mypy: + +[source, python] +---- +def show_count(count: int, word: str) -> str: +---- + +[TIP] +==== +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. +Aqui está um _mypy.ini_ simples, para servir de base: + +---- +[mypy] +python_version = 3.9 +warn_unused_configs = True +disallow_incomplete_defs = True +---- +==== + + +==== Um valor default para um argumento + +A função `show_count` no <> só funciona com substantivos regulares. +Se o plural não pode ser composto acrescentando um `'s'`, devemos deixar o usuário fornecer a forma plural, assim: + +[source, python] +---- +>>> show_count(3, 'mouse', 'mice') +'3 mice' +---- + +Vamos experimentar um pouco de "desenvolvimento orientado a tipos." +Primeiro acrescento um teste usando aquele terceiro argumento. +Não esqueça de adicionar a dica do tipo de retorno à função de teste, +senão o Mypy não vai inspecioná-la. + +[source, python] +---- +def test_irregular() -> None: + got = show_count(2, 'child', 'children') + assert got == '2 children' +---- + +O Mypy detecta o erro: +[source, python] +---- +…/hints_2/ $ mypy messages_test.py +messages_test.py:22: error: Too many arguments for "show_count" +Found 1 error in 1 file (checked 1 source file) +---- + +Então edito `show_count`, acrescentando o argumento opcional `plural` no <>. + +[[msgs_optional_str_param]] +.`showcount` de _hints_2/messages.py_ com um argumento opcional +==== +[source, python] +---- +include::../code/08-def-type-hints/messages/hints_2/messages.py[tags=SHOW_COUNT] +---- +==== + +E agora o Mypy reporta "Success." + +[WARNING] +==== +Aqui está um erro de digitação que Python não reconhece. +Você consegue encontrá-lo? + +[source, python] +---- +def hex2rgb(color=str) -> tuple[int, int, int]: +---- + +O relatório de erros do Mypy não é muito útil: + +[source] +---- +colors.py:24: error: Function is missing a type + annotation for one or more arguments +---- + +A dica de tipo para o argumento `color` deveria ser `color: str`. +Eu escrevi `color=str`, que não é uma anotação: ele determina que o valor default de `color` é `str`. + +Pela minha experiência, esse é um erro comum e fácil de passar despercebido, +especialmente em dicas de tipo complexas. +==== + +Os seguintes detalhes são considerados um bom estilo para dicas de tipo: + +* Sem espaço entre o nome do parâmetro e o `:`; um espaço após o `:` +* Espaços dos dois lados do `=` que precede um valor default de parâmetro + +Por outro lado, a PEP 8 diz que não deve haver espaço em torno de `=` +se não há nenhuma dica de tipo para aquele parâmetro específico. + +.Estilo de Código: use flake8 e blue + +**** +Em vez de((("flake8 tool")))((("blue tool"))) decorar essas regrinhas bobas, use ferramentas como +https://fpy.li/8-9[_flake8_] e https://fpy.li/8-10[_blue_]. O +_flake8_ informa sobre o estilo do código e várias outras questões, +enquanto o _blue_ reescreve o código-fonte com base na (maioria) das regras prescritas pela ferramenta de formatação de código +https://fpy.li/8-11[_black_]. + +Se o objetivo é impor um estilo de programação "padrão", _blue_ é melhor que _black_, +porque segue o estilo próprio de Python, de usar aspas simples por default e aspas duplas como alternativa. + +[source, python] +---- +>>> "I prefer single quotes" +'I prefer single quotes' +---- + +No CPython, a preferência por aspas simples está incorporada no `repr()`, entre outros lugares. +O módulo https://fpy.li/doctest[_doctest_] depende do `repr()` usar aspas simples por default. + +Um dos autores do _blue_ é https://fpy.li/8-12[Barry Warsaw], co-autor da PEP 8, +core developer de Python desde 1994 e membro de Python's Steering Council desde 2019. +Daí estamos em ótima companhia quando escolhemos usar aspas simples. + +Se você precisar mesmo usar o _black_, use a opção `black -S`. Isso deixará suas aspas intocadas. +**** + + +[[dealing_with_none_sec]] +==== Usando None como default +No <>, o parâmetro `plural` está anotado como `str`, +e o valor default é `''`. Assim não há conflito de tipo. + +Eu gosto dessa solução, mas em outros contextos `None` é um default melhor. +Se o parâmetro opcional requer um tipo mutável, então `None` é o único default sensato, +como vimos na <>. + +Com `None` como default para o parâmetro `plural`, a assinatura ficaria assim: + +[source, python] +---- +from typing import Optional + +def show_count(count: int, singular: str, plural: Optional[str] = None) -> str: +---- + +Vamos destrinchar essa linha: + +* `Optional[str]` significa que `plural` pode ser uma `str` ou `None`. +* É obrigatório fornecer explicitamente o valor default `= None`. + +Se você não atribuir um valor default a `plural`, o runtime de Python vai tratar o parâmetro como obrigatório. +Lembre-se: durante a execução do programa, as dicas de tipo são ignoradas. + +Veja que é preciso importar `Optional` do módulo `typing`. Quando importamos tipos, +é uma boa prática usar a sintaxe `from typing import X`, para reduzir o tamanho das assinaturas das funções. + +[WARNING] +==== +`Optional` não é um bom nome, pois aquela anotação não torna o argumento opcional. +O que o torna opcional é a atribuição de um valor default ao parâmetro. +`Optional[str]` significa apenas: o tipo desse parâmetro pode ser `str` ou `NoneType`. +Nas linguagens Haskell e Elm, um tipo parecido se chama `Maybe`. +==== + +Agora que tivemos um primeiro contato concreto com a tipagem gradual, +vamos examinar o que o conceito de _tipo_ significa na +prática.((("", startref="FTHgrad08")))((("", startref="THgrad08")))((("", startref="GRSpract08")))((("", startref="mypy08"))) + +[[types_defined_by_ops_sec]] +=== Tipos são definidos pelas operações possíveis + + +[quote, PEP 483—A Teoria das Dicas de Tipo] +____ + +Há muitas definições do conceito de tipo na literatura. +Aqui vamos assumir que tipo é um conjunto de valores e +um conjunto de funções que podem ser aplicadas àqueles valores. +____ + +Na((("functions, type hints in", "supported operations and", id="FTHsupport08")))((("type hints (type annotations)", +"supported operations and", id="THsup08"))) prática, +é mais útil considerar o conjunto de operações possíveis como a característica que define um tipo.footnote:[Em +Python não há sintaxe para controlar o conjunto de possíveis valores de um tipo, +exceto para tipos `Enum`. +Por exemplo, não é possível, usando dicas de tipo, +definir `Quantity` como um número inteiro entre 1 e 10000, +ou `AirportCode` como uma sequência de 3 letras. +O NumPy oferece `uint8`, `int16`, e outros tipos numéricos ligados à arquitetura do hardware, +mas na biblioteca padrão de Python encontramos apenas +tipos com pequenos conjuntos de valores (`NoneType`, `bool`) +ou conjuntos muito grandes (`float`, `int`, `str`, todas as tuplas possíveis, etc.).] + +Por exemplo, pensando nas operações possíveis, +quais são os tipos válidos para `x` na função a seguir? + +[source, python] +---- +def double(x): + return x * 2 +---- + +O tipo do parâmetro `x` pode ser numérico (`int`, `complex`, `Fraction`, `numpy.uint32`, etc.), +mas também pode ser uma sequência (`str`, `tuple`, `list`, `array`), uma `numpy.array` N-dimensional, +ou qualquer outro tipo que implemente ou herde um método `+__mul__+` que aceite um inteiro como argumento. + +Entretanto, considere a anotação `double` abaixo. +Ignore por enquanto a ausência do tipo do retorno, vamos nos concentrar no tipo do parâmetro: + +[source, python] +---- +from collections import abc + +def double(x: abc.Sequence): + return x * 2 +---- + +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 +como `str`, `tuple`, `list`, `array`, etc., bem como com números, +pois durante a execução as dicas de tipo são ignoradas. +Mas o checador de tipos se preocupa apenas com o que +estiver explicitamente declarado, e `abc.Sequence` não suporta `+__mul__+`. + +Por essa razão o título dessa seção é "Tipos São Definidos pelas Operações Possíveis." +O runtime de Python aceita qualquer objeto como argumento `x` nas duas versões da função `double`. +O cálculo de `x * 2` pode funcionar, ou pode causar um `TypeError`, +se a operação não for suportada por `x`. +Por outro lado, Mypy vai marcar `x * 2` como um erro quando analisar o código-fonte anotado de `double`, pois é uma operação não suportada pelo tipo declarado `x: abc.Sequence`. + +Em um sistema de tipagem gradual, acontece uma interação entre duas perspectivas diferentes de tipo: + +Duck typing ("tipagem pato"):: +A ((("duck typing"))) perspectiva adotada pelo Smalltalk—a primeira linguagem orientada a objetos—bem +como em Python, JavaScript, e Ruby. +Objetos têm tipo, mas variáveis (incluindo parâmetros) não tem. +Na prática, não importa qual o tipo declarado de um objeto, importam apenas as operações que ele efetivamente suporta. +Se eu posso invocar `birdie.quack()` então, nesse contexto, `birdie` é um pato. +Por definição, duck typing só é aplicada durante a execução, quando se tenta aplicar operações sobre os objetos. +Isso é mais flexível que a _tipagem nominal_, +ao preço de permitir mais erros durante a execução.footnote:[Duck typing é uma forma implícita de +_tipagem estrutural_, que as anotações de tipo passaram a suportar explicitamente após a versão 3.8, +com a introdução de `typing.Protocol`. +Vamos falar disso mais adiante nesse capítulo, em <>, e com mais detalhes no <>.] + + +Tipagem nominal:: +É a ((("nominal typing"))) perspectiva adotada em {cpp}, Java, e C#, e suportada em Python anotado. +Objetos e variáveis têm tipos. +Mas objetos só existem durante a execução, e o checador de tipos só se importa com o código-fonte, +onde as variáveis (incluindo parâmetros de função) tem anotações com dicas de tipo. +Se `Duck` é uma subclasse de `Bird`, +você pode atribuir uma instância de `Duck` a um parâmetro anotado como `birdie: Bird`. +Mas no corpo da função, o checador considera a chamada `birdie.quack()` ilegal, +pois `birdie` é nominalmente um `Bird`, e aquela classe não fornece o método `.quack()`. +Não interessa que o argumento real, durante a execução, é um `Duck`, +porque a tipagem nominal é checada estaticamente. +O checador de tipos não executa qualquer pedaço do programa, ele apenas analisa o código-fonte. +Isso é mais rígido que _duck typing_, +com a vantagem de capturar alguns bugs durante o desenvolvimento, +ou mesmo em tempo real, enquanto o código está sendo digitado em um IDE. + +O <> é um exemplo bobo que contrapõe duck typing e tipagem nominal, +bem como checagem de tipos estática e comportamento durante a execução.footnote:[Muitas +vezes a herança é sobreutilizada e difícil de justificar em exemplos pequenos. +Então por favor aceite esse exemplo com animais como uma rápida ilustração de sub-tipagem.] + +[[birds_module_ex]] +._birds.py_ +==== +[source, python] +---- +include::../code/08-def-type-hints/birds/birds.py[] +---- +==== +<1> `Duck` é uma subclasse de `Bird`. +<2> `alert` não tem dicas de tipo, então o checador a ignora. +<3> `alert_duck` aceita um argumento do tipo `Duck`. +<4> `alert_bird` aceita um argumento do tipo `Bird`. + +Verificando _birds.py_ com Mypy, encontramos um problema: + +[source] +---- +…/birds/ $ mypy birds.py +birds.py:16: error: "Bird" has no attribute "quack" +Found 1 error in 1 file (checked 1 source file) +---- + +Só de analisar o código-fonte, Mypy percebe que `alert_bird` é problemático: +a dica de tipo declara o parâmetro `birdie` como do tipo `Bird`, +mas o corpo da função chama `birdie.quack()` — e a classe `Bird` não tem esse método. + +Agora vamos tentar usar o módulo `birds` em _daffy.py_ no <>. + +[[daffy_module_ex]] +._daffy.py_ +==== +[source, python] +---- +include::../code/08-def-type-hints/birds/daffy.py[] +---- +==== +<1> Chamada válida, pois `alert` não tem dicas de tipo. +<2> Chamada válida, pois `alert_duck` recebe um argumento do tipo `Duck` e `daffy` é um `Duck`. +<3> Chamada válida, pois `alert_bird` recebe um argumento do tipo `Bird`, +e `daffy` também é um `Bird` — a superclasse de `Duck`. + +Mypy reporta o mesmo erro em _daffy.py_, sobre a chamada a `quack` na função `alert_bird` definida em _birds.py_: + +[source] +---- +…/birds/ $ mypy daffy.py +birds.py:16: error: "Bird" has no attribute "quack" +Found 1 error in 1 file (checked 1 source file) +---- + +Mas o Python não vê qualquer problema com _daffy.py_ em si: as três chamadas de função estão OK. + +Agora, rodando _daffy.py_, o resultado é o seguinte: + +[source] +---- +…/birds/ $ python3 daffy.py +Quack! +Quack! +Quack! +---- + +Funciona perfeitamente! Viva o duck typing! + +Durante a execução do programa, Python não se importa com os tipos declarados. +Ele usa apenas duck typing. +O Mypy apontou um erro em `alert_bird`, mas a chamada da função com `daffy` funciona corretamente quando executada. +À primeira vista isso pode surpreender muitos pythonistas: +um checador de tipos estático muitas vezes encontra erros em código que sabemos que vai funcionar quando executado. + +Entretanto, se daqui a alguns meses você for encarregado de estender o exemplo bobo do pássaro, +você agradecerá ao Mypy. +Observe este módulo _woody.py_, que também usa `birds`, no <>. + +[[woody_module_ex]] +._woody.py_ +==== +[source, python] +---- +include::../code/08-def-type-hints/birds/woody.py[] +---- +==== + +O Mypy encontra dois erros ao checar _woody.py_: + +[source] +---- +…/birds/ $ mypy woody.py +birds.py:16: error: "Bird" has no attribute "quack" +woody.py:5: error: Argument 1 to "alert_duck" has incompatible type "Bird"; +expected "Duck" +Found 2 errors in 2 files (checked 1 source file) +---- + +O primeiro erro é em _birds.py_: a chamada a `birdie.quack()` em `alert_bird`, que já vimos antes. +O segundo erro é em _woody.py_: `woody` é uma instância de `Bird`, +então a chamada `alert_duck(woody)` é inválida, pois aquela função exige um `Duck.` +Todo `Duck` é um `Bird`, mas nem todo `Bird` é um `Duck`. + +Durante a execução, nenhuma das duas chamadas em _woody.py_ funcionariam. +A sucessão de falhas é melhor ilustrada em uma sessão no console, +através das mensagens de erro, no <>. + +[[birdie_errors_ex]] +.Erros durante a execução e como o Mypy poderia ter ajudado +==== +[source, python] +---- +>>> from birds import * +>>> woody = Bird() +>>> alert(woody) # <1> +Traceback (most recent call last): + ... +AttributeError: 'Bird' object has no attribute 'quack' +>>> +>>> alert_duck(woody) # <2> +Traceback (most recent call last): + ... +AttributeError: 'Bird' object has no attribute 'quack' +>>> +>>> alert_bird(woody) # <3> +Traceback (most recent call last): + ... +AttributeError: 'Bird' object has no attribute 'quack' +---- +==== +<1> O Mypy não tinha como detectar esse erro, pois não há dicas de tipo em `alert`. +<2> O Mypy avisou do problema: `Argument 1 to "alert_duck" has incompatible type "Bird"; +expected "Duck"` (_Argumento 1 para `alert_duck` é do tipo incompatível "Bird"; argumento esperado era "Duck"_) +<3> O Mypy está avisando desde o <> que o corpo da função `alert_bird` está errado: +`"Bird" has no attribute "quack"` (_Bird não tem um atributo "quack"_) + +Este pequeno experimento mostra que o duck typing é mais fácil para o iniciante e mais flexível, +porém permite que operações não suportadas causem erros durante a execução. +A tipagem nominal detecta os erros antes da execução, +mas algumas vezes rejeita código que seria executado sem erros—como +a chamada a `alert_bird(daffy)` no <>. + +Mesmo que funcione algumas vezes, o nome da função `alert_bird` está incorreto: +seu código exige um objeto que suporte o método `.quack()`, que não existe em `Bird`. + +Nesse exemplo bobo, as funções têm uma linha apenas. +Mas na vida real elas poderiam ser mais longas, +e poderiam passar o argumento `birdie` para outras funções, +e a origem daquele argumento poderia estar a muitas chamadas de função de distância, +dificultando localizar a causa do erro durante a execução. +O checador de tipos impede que muitos erros como esse aconteçam durante a execução de um programa. + + +[NOTE] +==== +O valor das dicas de tipo é questionável nos pequenos exemplos que cabem em um livro. +Os benefícios crescem com o tamanho da base de código. +É por essa razão que empresas com milhões de linhas de código em +Python—como Dropbox, Google e Facebook—investiram em equipes e ferramentas para +promover a adoção global de dicas de tipo internamente, +e hoje tem partes significativas e crescentes de sua base de código com +anotações de tipo sendo checadas em suas _pipelines_ de integração contínua. +==== + +Nessa seção exploramos as relações de tipos e operações no duck typing e na tipagem nominal, +começando com a função simples `double()` — que deixamos sem dicas de tipo. +Agora vamos dar uma olhada nos tipos mais importantes ao anotar funções. + +Vamos ver um bom modo de adicionar dicas de tipo a `double()` na <>. +Mas antes disso, há tipos mais importantes para conhecer.((("", startref="FTHsupport08")))((("", startref="THsup08"))) + + +=== Tipos próprios para anotações + +Quase((("functions, type hints in", "types usable in annotations", +id="FTHusable08")))((("type hints (type annotations)", "types usable in", id="THTusable08"))) +todos os tipos em Python podem ser usados em dicas de tipo, mas há restrições e recomendações. +Além disso, o((("typing module"))) módulo `typing` introduziu anotações especiais com uma semântica às vezes surpreendente. + +Essa seção trata dos os principais tipos que você pode usar em anotações: + +* `typing.Any` +* Tipos e classes simples +* `typing.Optional` e `typing.Union` +* Coleções genéricas, incluindo tuplas e mapeamentos +* Classes base abstratas +* Iteradores genéricos +* Genéricos parametrizados e `TypeVar` +* `typing.Protocols` — crucial para _duck typing estático_ +* `typing.Callable` +* `typing.NoReturn` — um bom modo de encerrar essa lista. + +Vamos falar de um de cada vez, começando por um tipo que é estranho, +aparentemente inútil, mas de uma importância fundamental. + +==== O tipo Any + +A((("Any type", id="anytype08")))((("dynamic type", id="dynamic08")))((("gradual type system", "Any type", +id="GTSany08"))) pedra fundamental de qualquer sistema de tipagem gradual é o tipo `Any`, +também conhecido como o _tipo dinâmico_. +Quando um checador de tipos vê um função sem tipo como esta: + +[source, python] +---- +def double(x): + return x * 2 +---- + +ele supõe isto: +[source, python] +---- +def double(x: Any) -> Any: + return x * 2 +---- + +Isso significa que o argumento `x` e o valor de retorno podem ser de qualquer tipo, +inclusive de tipos diferentes. +Assume-se que `Any` pode suportar qualquer operação possível. + +Compare `Any` com `object`. Considere essa assinatura: +[source, python] +---- +def double(x: object) -> object: +---- + +Essa função também aceita argumentos de todos os tipos, porque todos os tipos são _subtipo-de_ `object`. + +Entretanto, um checador de tipos vai rejeitar essa função: +[source, python] +---- +def double(x: object) -> object: + return x * 2 +---- +O problema é que `object` não suporta a operação `+__mul__+`. Veja o que diz o Mypy: + +[source] +---- +…/birds/ $ mypy double_object.py +double_object.py:2: error: Unsupported operand types for * ("object" and "int") +Found 1 error in 1 file (checked 1 source file) +---- + +Tipos mais gerais têm interfaces mais restritas, isto é, eles suportam menos operações. +A classe `object` implementa menos operações que `abc.Sequence`, +que implementa menos operações que `abc.MutableSequence`, +que por sua vez implementa menos operações que `list`. + +Mas `Any` é um tipo mágico que reside tanto no topo quanto na base da hierarquia de tipos. +Ele é simultaneamente o tipo mais geral—então um argumento `n: Any` aceita valores de +qualquer tipo—e o tipo mais especializado, suportando assim todas as operações possíveis. +Pelo menos é assim que o checador de tipos entende `Any`. + +Claro, nenhum tipo consegue suportar qualquer operação possível, +então usar `Any` impede o checador de tipos de cumprir sua missão: +detectar operações que podem falhar antes que +seu programa levante uma exceção durante a execução.((("", startref="GTSany08"))) + +[[consistent_with_sec]] +===== Subtipo-de ou consistente-com + +Sistemas((("gradual type system", "subtype-of versus consistent-with relationships", +id="GTSsub08")))((("subtype-of relationships"))) +tradicionais de tipagem nominal orientados a objetos se baseiam na relação _subtipo-de_. +Dada uma classe `T1` e uma subclasse `T2`, então `T2` é _subtipo-de_ `T1`. + +Observe este código: + +[source, python] +---- +class T1: + ... + +class T2(T1): + ... + +def f1(p: T1) -> None: + ... + +o2 = T2() + +f1(o2) # OK +---- + +A chamada `f1(o2)` é uma aplicação do Princípio de Substituição de Liskov +(_Liskov Substitution Principle_, LSP). + +Barbara Liskovfootnote:[Professora do MIT, designer de linguagens de programação e homenageada com o Turing Award em 2008. +Wikipedia: https://fpy.li/49[Barbara Liskov].] +definiu _é subtipo-de_ em termos das operações suportadas. +Se um objeto do tipo `T2` substitui um objeto do tipo `T1` e +o programa continua se comportando de forma correta, então `T2` é _subtipo-de_ `T1`. + +Seguindo com o código visto acima, essa parte mostra uma violação do LSP: + + +[source, python] +---- +def f2(p: T2) -> None: + ... + +o1 = T1() + +f2(o1) # type error +---- + +Do ponto de vista das operações suportadas, faz todo sentido: +como uma subclasse, `T2` herda e precisa suportar todas as operações suportadas por `T1`. +Então uma instância de `T2` pode ser usada em qualquer lugar onde se espera uma instância de `T1`. +Mas o contrário não é necessariamente verdadeiro: +`T2` pode implementar métodos adicionais, então uma instância de `T1` +não pode ser usada onde se espera uma instância de `T2`. +Este((("behavioral subtyping"))) foco nas operações suportadas se reflete no nome +https://fpy.li/8-15[_behavioral subtyping (subtipagem comportamental)] (EN), +também usado para se referir ao LSP. + +Em((("consistent-with relationships"))) um sistema de tipagem gradual há outra relação, +_consistente-com_ (_consistent-with_), +que se aplica sempre que _subtipo-de_ puder ser aplicado, com regras especiais para o tipo `Any`. + +As regras para _consistente-com_ são: + +. Dados `T1` e um subtipo `T2`, então `T2` é _consistente-com_ `T1` (substituição de Liskov). +. Todo tipo é _consistente-com_ `Any`: você pode passar objetos de qualquer tipo em um argumento declarado como `Any`. +. `Any` é _consistente-com_ todos os tipos: você sempre pode passar um objeto de tipo `Any` onde um argumento de outro tipo for esperado. + +Considerando as definições anteriores dos objetos `o1` e `o2`, +aqui estão alguns exemplos de código válido, ilustrando as regras #2 e #3: + +[source, python] +---- +def f3(p: Any) -> None: + ... + +o0 = object() +o1 = T1() +o2 = T2() + +f3(o0) # +f3(o1) # tudo certo: regra #2 +f3(o2) # + +def f4(): # tipo implícito de retorno: `Any` + ... + +o4 = f4() # tipo inferido: `Any` + +f1(o4) # +f2(o4) # tudo certo: regra #3 +f3(o4) # +---- + +Todo sistema de tipagem gradual precisa de um tipo coringa como `Any` + +[TIP] +==== +O verbo "inferir" é um sinônimo bonito para "adivinhar", quando usado no contexto da análise de tipos. +Checadores de tipo modernos, em Python e outras linguagens, +não precisam de anotações de tipo em todo lugar porque conseguem inferir o tipo de muitas expressões. +Por exemplo, se eu escrever `x = len(s) * 10`, +o checador não precisa de uma declaração local explícita para saber que `x` é um `int`, +desde que a ferramenta consiga acessar dicas de tipo para `len` em algum lugar. +==== + +Agora podemos explorar o restante dos tipos usados em anotações. +((("", startref="GTSsub08")))((("", startref="dynamic08")))((("", startref="anytype08"))) + +==== Tipos simples e classes + +Tipos simples((("gradual type system", "simple types and classes"))) +como `int`, `float`, `str`, e `bytes` podem ser usados diretamente em dicas de tipo. +Classes concretas da biblioteca padrão, +de pacotes externos ou definidas pelo usuário (ex. `FrenchDeck`, `Vector2d`, e `Duck`) +também podem ser usadas em dicas de tipo. + +Classes base abstratas também são úteis aqui. +Voltaremos a elas quando formos estudar os tipos coleção, na <>. + +Para classes, _consistente-com_ é definido como _subtipo-de_: +uma subclasse é _consistente-com_ todas as suas superclasses. + +Entretanto, "a praticidade se sobrepõe à pureza", então há uma exceção importante, discutida em seguida. + +[[int_complex_tip]] +.int é consistente-com complex +[TIP] +==== +Não há nenhuma relação nominal de subtipo entre os tipo nativos `int`, `float` e `complex`: eles são subclasses diretas de `object`. +Mas a PEP 484 https://fpy.li/cardxvi[decretou] +que `int` é _consistente-com_ `float`, e `float` é _consistente-com_ `complex`. +Na prática, faz sentido: +`int` implementa todas as operações que `float` implementa, +e `int` implementa operações adicionais para os operadores binários `&`, `|`, `<<`, etc. +O resultado final é o seguinte: `int` é _consistente-com_ `complex`. +Para `i = 3`, `i.real` é `3` e `i.imag` é `0`. +==== + + +==== Os tipos Optional e Union + +Vimos((("gradual type system", "Optional and Union types")))((("Union type")))((("Optional type"))) +o tipo especial `Optional` em <>. +Ele resolve o problema de ter `None` como default, como no exemplo daquela seção: + +[source, python] +---- +from typing import Optional + +def show_count(count: int, singular: str, plural: Optional[str] = None) -> str: +---- + +A sintaxe `Optional[str]` é na verdade um atalho para `Union[str, None]`, +que significa que o tipo de `plural` pode ser `str` ou `None`. + +.Uma sintaxe melhor para Optional e Union em Python 3.10 + +[TIP] +==== +Desde o Python 3.10 é possível escrever `str | bytes` em vez de `Union[str, bytes]`. +É menos digitação, e não precisamos importar `Optional` ou `Union` de `typing`. +Compare a sintaxe antiga com a nova para a dica de tipo do parâmetro `plural` em `show_count`: + +[source, python] +---- +plural: Optional[str] = None # Python < 3.10 +plural: str | None = None # Python >= 3.10 +---- + +O operador `|` também funciona com `isinstance` e `issubclass` para declarar o segundo argumento: `isinstance(x, int | str)`. +Para saber mais, veja https://fpy.li/pep604[PEP 604—Complementary syntax for Union[]] (EN). +==== + +A assinatura da função nativa `ord` é um exemplo simples de `Union`: ela aceita `str` ou `bytes`, +e devolve um `int`.footnote:[Para ser mais preciso, `ord` só aceita `str` ou `bytes` com `len(s) == 1`. +Mas no momento o sistema de tipagem não consegue expressar essa restrição.] + +[source, python] +---- +def ord(c: Union[str, bytes]) -> int: ... +---- + +Aqui está um exemplo de uma função que aceita uma `str`, mas pode retornar uma `str` ou um `float`: + +[source, python] +---- +from typing import Union + +def parse_token(token: str) -> Union[str, float]: + try: + return float(token) + except ValueError: + return token +---- + +Se possível, evite criar funções com o tipo de retorno `Union`, +pois isso exige um esforço extra do usuário: +para saber o que fazer com o valor recebido da função pode ser necessário checar o tipo daquele valor durante a execução. +Mas a `parse_token` no código acima é um caso de uso justificado no contexto de interpretador de expressões simples. + +[TIP] +==== +Na <>, vimos funções que aceitam tanto `str` quanto `bytes` como argumento, +mas retornam uma `str` se o argumento for `str` ou `bytes`, se o argumento for `bytes`. +Nesses casos, o tipo de retorno é determinado pelo tipo da entrada, +então `Union` não é uma solução precisa. +Para anotar tais funções corretamente, +precisamos usar uma variável de tipo—apresentada em +<>—ou sobrecarga (_overloading_), que veremos na <>. +==== + +`Union[]` exige pelo menos dois tipos. +Tipos `Union` aninhados têm o mesmo efeito que uma `Union` "achatada" . +Então esta dica de tipo: + +[source, python] +---- +Union[A, B, Union[C, D, E]] +---- + +Tem o mesmo efeito desta: + +[source, python] +---- +Union[A, B, C, D, E] +---- + +`Union` é mais útil com tipos que não sejam consistentes entre si. +Por exemplo: `Union[int, float]` é redundante, pois `int` é _consistente-com_ `float`. +Se você usar apenas `float` para anotar o parâmetro, ele vai também aceitar valores `int`. + + +[[simple_collections_type_sec]] +==== Coleções genéricas + +A maioria((("gradual type system", "generic collections", id="GTSgeneric08")))((("generic collections", "type annotations and", id="generic08"))) das coleções em Python são heterogêneas. + +Por exemplo, você pode inserir qualquer combinação de tipos diferentes em uma `list`. +Entretanto, na prática isso não é muito útil: se você colocar objetos em uma coleção, +você certamente vai querer executar alguma operação com eles mais tarde, e normalmente isso significa que eles precisam compartilhar pelo menos um método comum.footnote:[Em ABC - a linguagem que mais influenciou o design inicial de Python - cada lista estava restrita a aceitar valores de um único tipo: o tipo do primeiro item que você colocasse ali.] + +Tipos genéricos podem ser declarados com parâmetros de tipo, para especificar o tipo de item com o qual eles conseguem trabalhar. + +Por exemplo, uma `list` pode ser parametrizada para restringir o tipo de elemento ali contido, como se pode ver no <>. + +[[tokenize_ex]] +.`tokenize` com dicas de tipo para Python ≥ 3.9 +==== +[source, python] +---- +def tokenize(text: str) -> list[str]: + return text.upper().split() +---- +==== + +Em Python ≥ 3.9, isso significa que `tokenize` retorna uma `list` onde todos os elementos são do tipo `str`. + +As anotações `stuff: list` e `stuff: list[Any]` significam a mesma coisa: `stuff` é uma lista de objetos de qualquer tipo. + +[TIP] +==== +Se estiver usando Python 3.8 ou anterior, o conceito é o mesmo, +mas você precisa de mais código para funcionar, como explicado em +<>. +==== + +A https://fpy.li/8-16[PEP 585—Type Hinting Generics In Standard Collections] (EN) +lista as coleções da biblioteca padrão que aceitam dicas de tipo genéricas. +A lista a seguir mostra apenas as coleções que usam a forma mais simples de dica de tipo genérica, `container[item]`: + +[source] +---- +list collections.deque abc.Sequence abc.MutableSequence +set abc.Container abc.Set abc.MutableSet +frozenset abc.Collection +---- + +Os tipos `tuple` e mapping aceitam dicas de tipo mais complexas, como veremos em suas respectivas seções. + +No Python 3.10, não há uma boa maneira de anotar `array.array`, levando em consideração o argumento `typecode` do construtor, que determina se o array contém inteiros ou floats. +Um problema ainda mais complicado é checar a faixa dos inteiros, para prevenir `OverflowError` durante a execução, ao se adicionar novos elementos. +Por exemplo, um `array` com `typecode=B` só pode receber valores `int` de 0 a 255. +Até Python 3.11, o sistema de tipagem estática de Python não consegue lidar com esse desafio.((("", startref="GTSgeneric08")))((("", startref="generic08"))) + +[[legacy_deprecated_typing_box]] +.Suporte a tipos de coleção descontinuados +**** + +(Você pode pular esse box se usa apenas Python 3.9 ou posterior.) + +Em((("deprecated collection types")))((("gradual type system", "legacy support and deprecated collection types"))) Python 3.7 e 3.8, você precisa importar um `+__future__+` para fazer a notação `[]` funcionar com as coleções nativas, tal como `list`, como ilustrado no <>. + +[[tokenize_3_7_ex]] +.`tokenize` com dicas de tipo para Python ≥ 3.7 +==== +[source, python] +---- +from __future__ import annotations + +def tokenize(text: str) -> list[str]: + return text.upper().split() +---- +==== + +O `+__future__+` não funciona com Python 3.6 ou anterior. +O +<> mostra como anotar `tokenize` de uma forma que funciona com Python ≥ 3.5. + +[[tokenize_3_5_ex]] +.`tokenize` com dicas de tipo para Python ≥ 3.5 +==== +[source, python] +---- +from typing import List + +def tokenize(text: str) -> List[str]: + return text.upper().split() +---- +==== + +Para fornecer um suporte inicial a dicas de tipo genéricas, +os autores da PEP 484 criaram dúzias de tipos genéricos no módulo `typing`. +A <> mostra alguns deles. +Para a lista completa, consulte a documentação do módulo https://fpy.li/4a[_typing_] . + +[[generic_collections_tbl]] +.Alguns tipos de coleção e seus equivalentes nas dicas de tipo +[options="header"] +|=========================================================== +|Collection |Type hint equivalent +|`list` |`typing.List` +|`set` |`typing.Set` +|`frozenset` |`typing.FrozenSet` +|`collections.deque` |`typing.Deque` +|`collections.abc.MutableSequence` |`typing.MutableSequence` +|`collections.abc.Sequence` |`typing.Sequence` +|`collections.abc.Set` |`typing.AbstractSet` +|`collections.abc.MutableSet` |`typing.MutableSet` +|=========================================================== + +A https://fpy.li/pep585[PEP 585—Type Hinting Generics In Standard Collections] deu início a um processo de vários anos para melhorar a usabilidade das dicas de tipo genéricas. +Podemos resumir esse processo em quatro etapas: + + +. Introduzir `from {dunder}future{dunder} import annotations` no Python 3.7 para permitir o uso das classes da biblioteca padrão como genéricos com a notação `list[str]`. + +. 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 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. +**** + +Agora vamos ver como anotar tuplas genéricas. + +[[tuple_type_sec]] +==== Tipos tuple + +Há ((("gradual type system", "tuple types", id="GTStupple08")))((("tuples", "type hints (type annotations)", id="Thint08"))) +três maneiras de anotar o tipo `tuple`, dependendo de como você usa a tupla: + +* Tupla como registro +* Tupla como registro com campos nomeados +* Tupla como sequência imutável + +===== Tuplas como registros + +Se você está usando uma `tuple` como um registro, use o tipo `tuple` nativo e declare os tipos dos campos dentro dos `[]`. + +Por exemplo, para anotar uma tupla com nome da cidade, população e país: +`('Shanghai', 24.28, 'China')`, a dica de tipo é `tuple[str, float, str]` + +Observe uma função que recebe um par de coordenadas geográficas e retorna uma https://fpy.li/8-18[Geohash], usada assim: + +[source, python] +---- +>>> shanghai = 31.2304, 121.4737 +>>> geohash(shanghai) +'wtw3sjq6q' +---- + +O <> mostra a definição da função `geohash`, usando o pacote `geolib` do PyPI. + +[[geohash_ex_1]] +._coordinates.py_ com a função `geohash` +==== +[source, python] +---- +include::../code/08-def-type-hints/coordinates/coordinates.py[tags=GEOHASH] +---- +==== +<1> Este comentário evita que o Mypy avise que o pacote `geolib` não tem dicas de tipo. +<2> O parâmetro `lat_lon`, anotado como uma `tuple` com dois campos `float`. + +[TIP] +==== +Com Python < 3.9, importe e use `typing.Tuple` nas dicas de tipo. +Este tipo está descontinuado mas permanecerá na biblioteca padrão pelo menos até 2024. +==== + +===== Tuplas como registros com campos nomeados + +Para a anotar uma tupla com muitos campos, +ou tipos específicos de tupla que seu código usa com frequência, +recomendo fortemente usar `typing.NamedTuple`, como visto no <>. +O <> mostra uma variante de <> com `NamedTuple`. + +[[geohash_ex_2]] +._coordinates_named.py_ com `NamedTuple`, `Coordinates` e a função `geohash` +==== +[source, python] +---- +include::../code/08-def-type-hints/coordinates/coordinates_named.py[tags=GEOHASH] +---- +==== + +Como explicado na <>, +`typing.NamedTuple` é uma factory de subclasses de `tuple`, +então `Coordinate` é _consistente-com_ `tuple[float, float]`, +mas o inverso não é verdadeiro—afinal, `Coordinate` tem métodos extras adicionados por `NamedTuple`, +como `._asdict()`, e também poderia ter métodos definidos pelo usuário. + +Na prática, isso significa que é seguro (do ponto de vista do tipo do argumento) +passar uma instância de `Coordinate` para a função `display`, definida assim: + +[source, python] +---- +include::../code/08-def-type-hints/coordinates/coordinates_named.py[tags=DISPLAY] +---- + + +===== Tuplas como sequências imutáveis + +Para anotar tuplas de tamanho desconhecido, usadas como listas imutáveis, você precisa especificar um único tipo, +seguido de uma vírgula e `\...` +(isto é o símbolo de reticências de Python, formado por três caracteres `.`, não o caractere Unicode `U+2026`—`HORIZONTAL ELLIPSIS`). + +Por exemplo, `tuple[int, \...]` é uma tupla com itens `int`. + +As reticências indicam que qualquer número de elementos >= 1 é aceitável. +Não há como especificar campos de tipos diferentes para tuplas de tamanho arbitrário. + +As anotações `stuff: tuple[Any, \...]` e `stuff: tuple` são equivalentes: +`stuff` é uma tupla de tamanho desconhecido contendo objetos de qualquer tipo. + +Aqui temos um função `columnize`, que transforma uma sequência em uma tabela de colunas e células, na forma de uma lista de tuplas de tamanho desconhecido. +É útil para mostrar os itens em colunas, assim: + +[source, python] +---- +>>> animals = 'drake fawn heron ibex koala lynx tahr xerus yak zapus'.split() +>>> table = columnize(animals) +>>> table +[('drake', 'koala', 'yak'), ('fawn', 'lynx', 'zapus'), ('heron', 'tahr'), + ('ibex', 'xerus')] +>>> for row in table: +... print(''.join(f'{word:10}' for word in row)) +... +drake koala yak +fawn lynx zapus +heron tahr +ibex xerus +---- + +O <> mostra a implementação de `columnize`. +Observe o tipo((("", startref="Thint08")))((("", startref="GTStupple08"))) do retorno: + +[source, python] +---- +list[tuple[str, ...]] +---- + +[[columnize_ex]] +._columnize.py_ retorna uma lista de tuplas de strings +==== +[source, python] +---- +include::../code/08-def-type-hints/columnize.py[tags=COLUMNIZE] +---- +==== + + +[[mapping_type_sec]] +==== Mapeamentos genéricos + +Tipos de mapeamento genéricos((("gradual type system", "generic mappings")))((("generic mapping types"))) +são anotados como `MappingType[KeyType, ValueType]`. +O tipo nativo `dict` e os tipos de mapeamento em `collections` e `collections.abc` +aceitam essa notação em Python ≥ 3.9. +Para versões mais antigas, use `typing.Dict` e outros tipos de mapeamento do módulo `typing`, +como discutimos em <>. + +O <> mostra um uso na prática de uma função que retorna um +https://fpy.li/8-19[índice invertido] para permitir a busca de caracteres Unicode pelo nome—uma variação do <> +mais adequada para código server-side, que estudaremos no <>. + +Dado o início e o final dos códigos de caractere Unicode, +`name_index` retorna um `dict[str, set[str]]`, +que é um índice invertido mapeando cada palavra para um conjunto de caracteres que tem aquela palavra em seus nomes. +Por exemplo, após indexar os caracteres ASCII de 32 a 64, +aqui estão os conjuntos de caracteres mapeados para as palavras `'SIGN'` e `'DIGIT'`, +e a forma de encontrar o caractere chamado `'DIGIT EIGHT'`: + +[source, python] +---- +>>> index = name_index(32, 65) +>>> index['SIGN'] +{'$', '>', '=', '+', '<', '%', '#'} +>>> index['DIGIT'] +{'8', '5', '6', '2', '3', '0', '1', '4', '7', '9'} +>>> index['DIGIT'] & index['EIGHT'] +{'8'} +---- + +O <> mostra o código-fonte de _charindex.py_ com a função `name_index`. +Além de uma dica de tipo `dict[]`, este exemplo tem três outros aspectos que estão aparecendo pela primeira vez no livro. + + +[[charindex_ex]] +._charindex.py_ +==== +[source, python] +---- +include::../code/08-def-type-hints/charindex.py[tags=CHARINDEX] +---- +==== +<1> `tokenize` é uma função geradora, assunto do <>. +<2> A variável local `index` está anotada. +Sem a dica, o Mypy diz: +`Need type annotation for 'index' (hint: "index: dict[, ] = ...")`. +<3> Usei o operador morsa (_walrus operator_) `:=` na condição do `if`. Ele atribui o resultado da chamada a `unicodedata.name()` a `name`, e a expressão inteira é calculada a partir daquele resultado. +Quando o resultado é `''`, isso é falso, e o `index` não é atualizado.footnote:[Eu uso `:=` quando faz sentido em alguns exemplos, mas não trato desse operador no livro. +Veja https://fpy.li/pep572[PEP 572—Assignment Expressions] para entender os detalhes dos operadores de atribuição.] + +[NOTE] +==== +Ao usar `dict` como um registro JSON, é comum que todas as chaves sejam do tipo `str`, +com valores de tipos diferentes dependendo das chaves. +Isso é tratado na <>, sobre `typing.TypedDict`. +==== + + +[[type_hint_abc_sec]] +==== Classes base abstratas + +[quote, lei de Postel, ou o Princípio da Robustez] + +____ +Seja conservador no que envia, mas liberal no que aceita. +____ + +A <> apresenta((("gradual type system", "abstract base classes", id="GTSabstract08")))((("ABCs (abstract base classes)", "type hints (type annotations)", id="ABChint08"))) várias classes abstratas de `collections.abc`. +Idealmente, uma função deveria aceitar argumentos desses tipos abstratos--ou seus equivalentes de `typing` antes de Python 3.9--e não tipos concretos. +Isso dá mais flexibilidade a quem chama a função. + + +Considere essa assinatura de função: + +[source, python] +---- +from collections.abc import Mapping + +def name2hex(name: str, color_map: Mapping[str, int]) -> str: +---- + +Usar `abc.Mapping` permite ao usuário da função fornecer uma instância de `dict`, `defaultdict`, `ChainMap`, uma subclasse de `UserDict` subclass, ou qualquer outra classe que seja um _subtipo-de_ `Mapping`. + +Por outro lado, veja essa assinatura: + +[source, python] +---- +def name2hex(name: str, color_map: dict[str, int]) -> str: +---- + +Agora `color_map` precisa ser um `dict` ou um de seus subtipos, tal como `defaultdict` ou `OrderedDict`. +Especificamente, uma subclasse de `collections.UserDict` não passaria pela checagem de tipo para `color_map`, +apesar de ser a maneira recomendada de criar mapeamentos definidos pelo usuário, como vimos na <>. +O Mypy rejeitaria um `UserDict` ou uma instância de classe derivada dele, porque `UserDict` não é uma subclasse de `dict`; elas são irmãs. +Ambas são subclasses `abc.MutableMapping`.footnote:[Na verdade, +`dict` é uma subclasse virtual de `abc.MutableMapping`. +O conceito de subclasse virtual será explicado no <>. +Por hora, basta saber que `issubclass(dict, abc.MutableMapping)` é `True`, +apesar de `dict` ser implementada em C, herdando apenas de `object`.] + +Assim, em geral é melhor usar `abc.Mapping` ou `abc.MutableMapping` em dicas de tipos de parâmetros, em vez de `dict` (ou `typing.Dict` em código antigo). +Se a função `name2hex` não precisar modificar o `color_map` recebido, a dica de tipo mais precisa para `color_map` é `abc.Mapping`. +Desse jeito, quem chama não precisa fornecer um objeto que implemente métodos como `setdefault`, `pop`, e `update`, que fazem parte da interface de `MutableMapping`, mas não de `Mapping`. +Isso reflete a segunda parte da lei de Postel: + "[seja] liberal no que aceita." + +A lei de Postel também nos diz para sermos conservadores no que enviamos. +O valor de retorno de uma função é sempre um objeto concreto, então a dica de tipo do valor de saída deve ser um tipo concreto, como no exemplo em <> — que usa `list[str]`: + +[source, python] +---- +def tokenize(text: str) -> list[str]: + return text.upper().split() +---- + +No verbete de https://fpy.li/4c[`typing.List`], a documentação do Python 3.10 diz +(NT: tradução abaixo não oficial) + +[quote] +____ +Versão genérica de `list`. Útil para anotar tipos de retorno. +Para anotar argumentos é preferível usar um tipo de coleção abstrata, como `Sequence` ou `Iterable`. +____ + +Comentários similares aparecem nos verbetes de https://fpy.li/8-21[`typing.Dict`] +e https://fpy.li/8-22[`typing.Set`]. + +Lembre-se de que a maioria das ABCs de `collections.abc` e outras classes concretas de `collections`, +bem como as coleções nativas, suportam notação de dica de tipo genérica como `collections.deque[str]` desde o Python 3.9. +As coleções correspondentes em `typing` só servem para suportar código escrito em Python 3.8 ou anterior. +A lista completa de classes que se tornaram genéricas aparece na seção https://fpy.li/8-16["Implementation"] da +https://fpy.li/pep585[PEP 585—Type Hinting Generics In Standard Collections] (EN). + +Para encerrar nossa discussão de ABCs em dicas de tipo, precisamos falar sobre as ABCs `numbers`. + +[[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). +A torre é uma hierarquia linear de ABCs, com `Number` no topo: + +* `Number` +* `Complex` +* `Real` +* `Rational` +* `Integral` + +Esses ABCs funcionam perfeitamente para checagem de tipo durante a execução, mas eles não são suportados para checagem de tipo estática. +A seção https://fpy.li/cardxvi["Numeric Tower"] da PEP 484 rejeita as ABCs `numbers` e +manda tratar os tipo nativos `complex`, `float`, e `int` como casos especiais, +como explicado em <>. +Voltaremps a essa questão na <>, que é dedicada a comparar protocolos e ABCs + +Na prática, se você quiser anotar argumentos numéricos para checagem de tipos estática, há algumas opções: + +. Usar um dos tipo concretos, `int`, `float`, ou `complex` — como recomendado pela PEP 488. +. Declarar um tipo union como `Union[float, Decimal, Fraction]`. +. Se você quiser evitar a codificação explícita de tipos concretos, usar protocolos numéricos como `SupportsFloat`, tratados na <>. + +A <> abaixo é um pré-requisito para entender protocolos numéricos. + +Antes disso, vamos examinar uma ABC muito útil em dicas de tipo: `Iterable`.((("", startref="GTSabstract08")))((("", startref="ABChint08"))) + + +==== Iterable + +A((("gradual type system", "Iterable", id="GTSiterable08")))((("Iterable interface", +id="iterable08")))((("interfaces", "Iterable interface"))) +documentação de https://fpy.li/4c[`typing.List`] +que citei acima recomenda `Sequence` e `Iterable` para dicas de tipo de parâmetros de função. + +Esse é um exemplo de argumento `Iterable`, na função `math.fsum` da biblioteca padrão: + +[source, python] +---- +def fsum(__seq: Iterable[float]) -> float: +---- + +.Arquivos Stub e o Projeto Typeshed +[TIP] +==== +Até((("Typeshed project"))) Python 3.10, a biblioteca padrão não tem anotações, +mas Mypy, PyCharm, etc. conseguem encontrar as dicas de tipo necessárias no projeto +https://fpy.li/8-26[Typeshed], +na forma de _arquivos stub_: arquivos de código-fonte especiais, +com a extensão _.pyi_, que contém assinaturas anotadas de métodos e funções, +sem a implementação—semelhante a arquivos _.h_ na linguagem C. + +A assinatura para `math.fsum` está em https://fpy.li/8-27[_/stdlib/2and3/math.pyi_]. +Os sublinhados iniciais em `__seq` são uma convenção estabelecida na PEP 484 para parâmetros apenas posicionais, +como explicado em <>. +==== + +O <> é outro exemplo do uso de um parâmetro `Iterable`, que produz itens que são `tuple[str, str]`. A função é usada assim: + +[source, python] +---- +>>> l33t = [('a', '4'), ('e', '3'), ('i', '1'), ('o', '0')] +>>> text = 'mad skilled noob powned leet' +>>> from replacer import zip_replace +>>> zip_replace(text, l33t) +'m4d sk1ll3d n00b p0wn3d l33t' +---- + +O <> mostra a implementação. + +[[replacer_ex]] +._replacer.py_ +==== +[source, python] +---- +include::../code/08-def-type-hints/replacer.py[tags=ZIP_REPLACE] +---- +==== +<1> `FromTo` é um _apelido de tipo_: atribui `tuple[str, str]` a `FromTo`, +para tornar a assinatura de `zip_replace` mais concisa. +<2> `changes` precisa ser um `Iterable[FromTo]`; +é o mesmo que escrever `Iterable[tuple[str, str]]`, mas é mais curto e mais fácil de ler. + +.O TypeAlias Explícito em Python 3.10 +[TIP] +==== +https://fpy.li/pep613[PEP 613—Explicit Type Aliases] introduziu um tipo especial, +`TypeAlias`, para tornar mais explícita a criação de apelidos de tipos. +A partir do Python 3.10, a sintaxe preferencial é: + +[source, python] +---- +from typing import TypeAlias + +FromTo: TypeAlias = tuple[str, str] +---- +==== + + +===== abc.Iterable versus abc.Sequence + +Tanto((("abc.Iterable")))((("abc.Sequence"))) `math.fsum` quanto `replacer.zip_replace` +tem que percorrer todos os argumentos do `Iterable` para produzir um resultado. +Dado um iterável sem fim tal como o gerador `itertools.cycle` como entrada, +essas funções consumiriam toda a memória e derrubariam o processo Python. +Apesar desse perigo potencial, é muito comum no Python moderno se oferecer funções que aceitam um `Iterable` como argumento, +mesmo se elas têm que processar a estrutura inteira para obter um resultado. +Isso dá a quem chama a função a opção de fornecer um gerador como dado de entrada, +em vez de uma sequência pré-construída, +podendo economizar bastante memória se o número de itens de entrada for grande. + +Por outro lado, a função `columnize` no <> requer uma `Sequence`, +não um `Iterable`, pois ela precisa obter o `len()` do argumento para calcular previamente o número de linhas. + +Assim como `Sequence`, o melhor uso de `Iterable` é como tipo de argumento. +Ele é muito vago como um tipo de saída. +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 no <>, que trata de geradores e +iteradores clássicos.((("", startref="GTSiterable08")))((("", startref="iterable08"))) + +[[param_generics_typevar_sec]] +==== Genéricos parametrizados e TypeVar + +Um((("gradual type system", "parameterized generics and TypeVar", +id="GTSparam08")))((("generic collections", "parameterized generics and TypeVar", +id="GCtypvar08")))((("TypeVar", id="typevar08"))) +genérico parametrizado é um tipo genérico, escrito na forma `list[T]`, +onde `T` é uma variável de tipo que será vinculada a um tipo específico a cada uso. +Isso permite que o tipo de um argumento determine o tipo do resultado da função. + +O <> define `sample`, uma função que recebe dois argumentos: +uma `Sequence` de elementos de tipo `T` e um `int`. +Ela retorna uma `list` de elementos do mesmo tipo `T`, escolhidos aleatoriamente do primeiro argumento. + +O <> mostra a implementação. + +[[generic_sample_ex]] +._sample.py_ +==== +[source, python] +---- +include::../code/08-def-type-hints/sample.py[tags=SAMPLE] +---- +==== + +Aqui estão dois exemplos que ilustram por que usei uma variável de tipo em `sample`: + +* Se chamada com uma tupla de tipo `tuple[int, \...]` (que é _consistente-com_ `Sequence[int]`) +então o tipo parametrizado é `int`, e o tipo de retorno é `list[int]`. +* Se chamada com uma `str` (que é _consistente-com_ `Sequence[str]`) +então o tipo parametrizado é `str`, e o tipo do retorno é `list[str]`. + + +[role="man-height-2in"] +.Por que TypeVar é necessário? +[NOTE] +==== +Os autores da PEP 484 queriam introduzir dicas de tipo ao acrescentar o módulo `typing`, +sem mudar nada mais na linguagem. +Com uma metaprogramação esperta, eles poderiam fazer o operador `[]` funcionar para classes como `Sequence[T]`. +Mas o nome da variável `T` dentro dos colchetes precisa ser definido em algum lugar—do contrário +o interpretador Python necessitaria de mudanças mais profundas, +para suportar a notação de tipos genéricos como um caso especial de `[]`. +Por isso o construtor `typing.TypeVar` é necessário: +para introduzir o nome da variável no _namespace_ (espaço de nomes) atual. +Linguagens como Java, C# e TypeScript não exigem que a variável de tipo seja declarada previamente, +então elas não precisam de algo equivalente à pseudo-classe `TypeVar` de Python. +==== + +//// +PROD: As is often the case, the NOTE above may or may not run over the next paragraphs, +depending on some random factor I don't know how to control. +//// + +Outro exemplo é a função `statistics.mode` da biblioteca padrão, que retorna o ponto de dado mais comum de uma série. + +Eis um exemplo de uso da https://fpy.li/4e[documentação]: + +[source, python] +---- +>>> mode([1, 1, 2, 3, 3, 3, 3, 4]) +3 +---- + +Sem o uso de `TypeVar`, `mode` poderia ter uma assinatura como a apresentada no <>. + +[[mode_float_ex]] +._mode_float.py_: `mode` que opera com `float` e seus subtipos footnote:[A implementação aqui é mais simples que aquela do módulo https://fpy.li/8-29[`statistics`] na biblioteca padrão de Python] +==== +[source, python] +---- +include::../code/08-def-type-hints/mode/mode_float.py[tags=MODE_FLOAT] +---- +==== + +Muitos dos usos de `mode` envolvem valores `int` ou `float`, mas Python tem outros tipos numéricos, e é desejável que o tipo de retorno siga o tipo dos elementos do `Iterable` recebido. +Podemos melhorar aquela assinatura usando `TypeVar`. Vamos começar com uma assinatura parametrizada simples, mas errada. + +[source, python] +---- +from collections.abc import Iterable +from typing import TypeVar + +T = TypeVar('T') + +def mode(data: Iterable[T]) -> T: +---- + +Quando aparece pela primeira vez na assinatura, o parâmetro de tipo `T` aceita qualquer tipo. +Da segunda vez que aparece, só aceitará o mesmo tipo vinculado da primeira vez. + +Assim, qualquer iterável é _consistente-com_ `Iterable[T]`, +incluindo iterável de tipos _unhashable_ que `collections.Counter` não consegue tratar. +Precisamos restringir os tipos possíveis de se atribuir a `T`. +Vamos ver maneiras diferentes de fazer isso nas duas seções seguintes. + +[[typevar_constraints_sec]] +===== TypeVar restrita + +O `TypeVar` aceita argumentos posicionais adicionais para restringir o tipo parametrizado. +Podemos melhorar a assinatura de `mode` para aceitar um número específico de tipos, assim: + +[source, python] +---- +from collections.abc import Iterable +from decimal import Decimal +from fractions import Fraction +from typing import TypeVar + +NumberT = TypeVar('NumberT', float, Decimal, Fraction) + +def mode(data: Iterable[NumberT]) -> NumberT: +---- + +Está melhor que antes, e era a assinatura de `mode` em +https://fpy.li/8-30[_statistics.pyi_], o arquivo stub em `typeshed` em 25 de maio de 2020. + +Entretanto, a documentação em https://fpy.li/8-28[`statistics.mode`] inclui esse exemplo: + +[source, python] +---- +>>> mode(["red", "blue", "blue", "red", "green", "red", "red"]) +'red' +---- + +Na pressa, poderíamos apenas adicionar `str` à definição de `NumberT`: + +[source, python] +---- +NumberT = TypeVar('NumberT', float, Decimal, Fraction, str) +---- + +Com certeza funciona, mas `NumberT` estaria muito mal batizado se aceitasse `str`. +Mais importante, não podemos ficar listando tipos para sempre, cada vez que percebermos que `mode` pode lidar com outro deles. +Podemos fazer com melhor com um outro recurso de `TypeVar`, como veremos a seguir. + +[[bounded_typevar_sec]] +===== TypeVar delimitada + +Examinando o corpo de `mode` no <>, +vemos que a classe `Counter` é usada para ordenação. +`Counter` é baseada em `dict`, então o tipo do elemento do iterável `data` precisa ser _hashable_. + +A princípio, essa assinatura pode parecer que funciona: + +[source, python] +---- +from collections.abc import Iterable, Hashable + +def mode(data: Iterable[Hashable]) -> Hashable: +---- + +Agora o problema é que o tipo do item retornado é `Hashable`: +um ABC que implementa apenas o método `+__hash__+`. +Então o checador de tipos não vai permitir que façamos nada com o valor retornado, +exceto chamar seu método `hash()`. Não é muito útil. + +A solução está em outro parâmetro opcional de `TypeVar`: +o parâmetro representado pela palavra-chave `bound`. +Ele estabelece um limite superior para os tipos aceitos. +No <>, temos `bound=Hashable`. +Isso significa que o tipo do parâmetro pode ser `Hashable` ou +qualquer _subtipo-de_ `Hashable`.footnote:[Eu contribui com essa solução para `typeshed`, +e em 26 de maio de 2020 `mode` aparecia anotado assim em +https://fpy.li/8-32[_statistics.pyi_].] + +[[mode_hashable_ex]] +._mode_hashable.py_: igual a <>, mas com uma assinatura mais flexível +==== +[source, python] +---- +include::../code/08-def-type-hints/mode/mode_hashable.py[tags=MODE_HASHABLE_T] +---- +==== + +Em resumo: + +* Uma variável de tipo restrita será vinculada a um dos tipos nomeados na declaração do `TypeVar`. +* Uma variável de tipo delimitada será vinculada ao tipo inferido da expressão, +desde que o tipo inferido seja _consistente-com_ o limite declarado pelo argumento `bound=` do TypeVar. + +[NOTE] +==== +É uma pena que a palavra-chave do argumento para declarar uma `TypeVar` delimitada se chame `bound=`, +(limite, como substantivo) pois o verbo "bound" (passado do verbo _to bind_, ligar ou vincular) +é muito usado em inglês para descrever a associação de um valor a uma variável. +Seria melhor que a palavra-chave do argumento fosse `boundary=`, +um substantivo mais comum e explícito que também significa _limite_. +==== + +O construtor de `typing.TypeVar` tem outros parâmetros opcionais (`covariant` e `contravariant`) +que envolvem conceitos avançados que vamos deixar para o <>, <>. + +Agora vamos concluir essa introdução a `TypeVar` com `AnyStr`. + + +===== A variável de tipo pré-definida AnyStr + +O((("AnyStr"))) módulo `typing` inclui uma `TypeVar` pré-definida chamada `AnyStr`, +restrita aos tipos `bytes` e `str`. +Ele é definido assim: + +[source, python] +---- +AnyStr = TypeVar('AnyStr', bytes, str) +---- + +O tipo `AnyStr` é usado em funções da biblioteca padrão que aceitam tanto `bytes` quanto `str`, +e retornam valores do tipo recebido. + +Agora vamos ver `typing.Protocol`, um novo recurso de Python 3.8, +que permite um uso mais pythônico de dicas de +tipo.((("", startref="GTSparam08")))((("", startref="GCtypvar08")))((("", startref="typevar08"))) + +[[protocols_in_fn_sec]] +==== Protocolos estáticos + +[NOTE] +==== +Em ((("gradual type system", "static protocols", +id="GTSstatic08")))((("static protocols", "type hints (type annotations)", id="stprot08"))) +programação orientada a objetos, o conceito de um "protocolo" como uma interface informal é tão antigo quanto Smalltalk, +e tem sido parte essencial de Python desde o início. +Entretanto, no contexto de dicas de tipo, um protocolo é uma subclasse de `typing.Protocol`, +definindo uma interface que um checador de tipos pode analisar. +Os dois tipos de protocolo são tratados no <>. +Aqui apresento apenas uma rápida introdução no contexto de anotações de função. +==== + +O((("Protocol type", id="proto08"))) tipo `Protocol`, como descrito em +https://fpy.li/pep544[PEP 544—Protocols: Structural subtyping (static duck typing)] (EN), +é similar às interfaces em Go: um tipo protocolo é definido especificando um ou mais métodos, +e o checador de tipos analisa se aqueles métodos estão implementados onde um tipo daquele protocolo é exigido. + +Em Python, uma definição de protocolo é escrita como uma subclasse de `typing.Protocol`. +Entretanto, classes que _implementam_ um protocolo não precisam herdar, +registrar ou declarar qualquer relação com a classe que _define_ o protocolo. +É função do checador de tipos encontrar os tipos de protocolos disponíveis e checar sua utilização. + +Abaixo temos um problema que pode ser resolvido com a ajuda de `Protocol` e `TypeVar`. +Suponha que você quisesse criar uma função `top(it, n)`, que retorna os `n` maiores elementos do iterável `it`: + +[source, python] +---- +include::../code/08-def-type-hints/comparable/top.py[tags=TOP_DOCTEST] +---- + +A função genérica parametrizada `top` poderia ser implementada como no <>. + +[[top_undefined_t_ex]] +.a função `top` function com um parâmetro de tipo `T` indefinido +==== +[source, python] +---- +def top(series: Iterable[T], length: int) -> list[T]: + ordered = sorted(series, reverse=True) + return ordered[:length] +---- +==== + +O problema é, como restringir `T`? +Ele não pode ser `Any` ou `object`, pois `series` precisa funcionar com `sorted`. +A `sorted` nativa na verdade aceita `Iterable[Any]`, +mas só porque o parâmetro opcional `key` recebe uma função que calcula uma chave de ordenação arbitrária para cada elemento. +O que acontece se você passar para `sorted` uma lista de objetos simples, +mas não fornecer um argumento `key`? +Vamos tentar: + +[source, python] +---- +>>> l = [object() for _ in range(4)] +>>> l +[, , +, ] +>>> sorted(l) +Traceback (most recent call last): + File "", line 1, in +TypeError: '<' not supported between instances of 'object' and 'object' +---- + +A mensagem de erro mostra que `sorted` usa o operador `<` nos elementos do iterável. +É só isso? Vamos tentar outro experimento rápido:footnote:[É +ótimo poder abrir um console iterativo e contar com o duck typing para explorar recursos da linguagem, +como acabei de fazer. +Sinto muita falta deste tipo de exploração quando uso linguagem que não tem esse recurso.] + +[source, python] +---- +>>> class Spam: +... def __init__(self, n): self.n = n +... def __lt__(self, other): return self.n < other.n +... def __repr__(self): return f'Spam({self.n})' +... +>>> l = [Spam(n) for n in range(5, 0, -1)] +>>> l +[Spam(5), Spam(4), Spam(3), Spam(2), Spam(1)] +>>> sorted(l) +[Spam(1), Spam(2), Spam(3), Spam(4), Spam(5)] +---- + +Isso confirma a suspeita: eu consigo passar um lista de `Spam` para `sort`, +porque `Spam` implementa `+__lt__+`, o método especial do operador `<`. + +Então o parâmetro de tipo `T` no <> deveria ser limitado a tipos que implementam `+__lt__+`. +No <>, precisávamos de um parâmetro de tipo que implementava `+__hash__+`, para poder usar `typing.Hashable` como limite superior do parâmetro de tipo. +Mas agora não há um tipo adequado em `typing` ou `abc` para usarmos, então precisamos criar um. + +O <> mostra o novo tipo `SupportsLessThan`, um `Protocol`. + +[[comparable_protocol_ex]] +._comparable.py_: a definição de um tipo `Protocol`, `SupportsLessThan` +==== +[source, python] +---- +include::../code/08-def-type-hints/comparable/comparable.py[] +---- +==== +<1> Um protocolo é uma subclasse de `typing.Protocol`. +<2> O corpo do protocolo tem uma ou mais definições de método, com `\...` no lugar da implementação. + +Um tipo `T` é _consistente-com_ um protocolo `P` se `T` implementa todos os métodos definido em `P`, com assinaturas de tipo correspondentes. + +Dado `SupportsLessThan`, agora podemos definir essa versão funcional de `top` no <>. + +[[top_protocol_ex]] +._top.py_: definição da função `top` usando uma `TypeVar` com `bound=SupportsLessThan` +==== +[source, python] +---- +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`. +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`. + + +[[ex_top_protocol_test]] +._top_test.py_: visão parcial da bateria de testes para `top` +==== +[source, python] +---- +include::../code/08-def-type-hints/comparable/top_test.py[tags=TOP_IMPORT] + +# muitas linhas omitidas + +include::../code/08-def-type-hints/comparable/top_test.py[tags=TOP_TEST] +---- +==== +<1> A constante `typing.TYPE_CHECKING` é sempre `False` durante a execução do programa, mas os checadores de tipos fingem que ela é `True` quando estão fazendo a checagem. +<2> Declaração de tipo explícita para a variável `series`, para tornar mais fácil a leitura da saída do Mypy.footnote:[Sem essa dica de tipo, o Mypy inferiria o tipo de `series` como `Generator[Tuple[builtins.int, builtins.str*], None, None]`, que é prolixo mas _consistente-com_ `Iterator[tuple[int, str]]`, como veremos na <>.] +<3> Este `if` evita que as três linhas seguintes sejam executadas durante o teste. +<4> `reveal_type()` não pode ser chamada durante a execução, porque não é uma função regular, mas sim um mecanismo de depuração do Mypy - por isso não há `import` para ela. +Mypy produzirá uma mensagem de depuração para cada chamada à pseudo-função `reveal_type()`, mostrando o tipo inferido do argumento. +<5> Essa linha será marcada pelo Mypy como um erro. + +Os testes anteriores são bem-sucedidos - mas eles funcionariam de qualquer forma, com ou sem dicas de tipo em _top.py_. +Mais precisamente, se eu checar aquele arquivo de teste com o Mypy, verei que o `TypeVar` está funcionando como o esperado. +Veja a saída do comando `mypy` no <>. + +[WARNING] +==== +Desde o Mypy 0.910 (julho de 2021), em alguns casos a saída de `reveal_type` +não mostra precisamente os tipos que declarei, mas mostra tipos compatíveis. +Por exemplo, não usei `typing.Iterator` e sim `abc.Iterator`. +Pode ignorar este detalhe. +O relatório do Mypy ainda é útil. +Vou fingir que este problema do Mypy já foi corrigido quando for discutir os resultados. +==== + +[[top_protocol_mypy_output]] +.Saída do _mypy top_test.py_ (linha quebradas para facilitar a leitura) +==== +[source] +---- +…/comparable/ $ mypy top_test.py +top_test.py:32: note: + Revealed type is "typing.Iterator[Tuple[builtins.int, builtins.str]]" <1> +top_test.py:33: note: + Revealed type is "builtins.list[Tuple[builtins.int, builtins.str]]" +top_test.py:34: note: + Revealed type is "builtins.list[Tuple[builtins.int, builtins.str]]" <2> +top_test.py:41: note: + Revealed type is "builtins.list[builtins.object*]" <3> +top_test.py:43: error: + Value of type variable "LT" of "top" cannot be "object" <4> +Found 1 error in 1 file (checked 1 source file) +---- +==== +<1> Em `test_top_tuples`, `reveal_type(series)` mostra que ele é um `Iterator[tuple[int, str]]` que declarei explicitamente. +<2> `reveal_type(result)` confirma que o tipo produzido pela chamada a `top` é o que eu queria: +dado o tipo de `series`, o `result` é `list[tuple[int, str]]`. +<3> Em `test_top_objects_error`, `reveal_type(series)` mostra que ele é uma `list[object*]`. +Mypy põe um `*` após qualquer tipo que tenha sido inferido: não anotei o tipo de `series` nesse teste. +<4> Mypy marca o erro que esse teste produz intencionalmente: +o tipo dos elementos do `Iterable` `series` não pode ser `object` (ele precisa ser do tipo `SupportsLessThan`). + +A principal vantagem de um tipo protocolo sobre as ABCs é que +uma classe não precisa de nenhuma declaração especial para ser _consistente-com_ um tipo protocolo. +Isso permite que um protocolo seja criado aproveitando tipos pré-existentes, +ou tipos implementados em bases de código que não estão sob nosso controle. +Eu não tenho que derivar ou registrar `str`, `tuple`, `float`, `set`, etc. +com `SupportsLessThan` para usá-los onde um parâmetro `SupportsLessThan` é esperado. +Eles só precisam implementar `+__lt__+`. +E o checador de tipos ainda será capaz de realizar seu trabalho, +porque `SupportsLessThan` está explicitamente declarado como um +`Protocol`—diferente dos protocolos implícitos comuns no duck typing, +que são invisíveis para o checador de tipos. + +A classe especial `Protocol` foi introduzida na +https://fpy.li/pep544[PEP 544—Protocols: Structural subtyping (static duck typing)]. +O <> demonstra((("static duck typing"))) porque esse recurso +é conhecido como _duck typing estático_ (_static duck typing_): +a solução para anotar o parâmetro `series` de `top` era dizer "O tipo nominal de `series` não importa, +desde que ele implemente o método `+__lt__+`." +Em Python, o duck typing sempre permitiu dizer isso de forma implícita, +deixando os checadores de tipos estáticos sem ação. +Um checador de tipos não consegue ler o código-fonte em C do CPython, +ou executar experimentos no console para descobrir que `sorted` só requer que seus elementos suportem `<`. + +Agora podemos tornar o duck typing explícito para os checadores estáticos de tipo. +Por isso faz sentido dizer que `typing.Protocol` nos oferece _duck typing estático_.footnote:[Não sei quem inventou a expressão _static duck typing_, +mas ela se tornou mais popular com a linguagem Go, +que tem uma semântica de interfaces mais parecida com os protocolos de Python +que com as interfaces nominais de Java.] + +Há mais para falar sobre `typing.Protocol`. +Vamos voltar a ele na Parte IV, onde <> compara as abordagens da tipagem estrutural, +do duck typing e das ABCs—outro modo de formalizar protocolos. +Além disso, a <> explica como +declarar assinaturas de funções de sobrecarga (_overload_) com `@typing.overload`, +e inclui um exemplo bastante extenso usando `typing.Protocol` e uma `TypeVar` delimitada. + +[NOTE] +==== +O `typing.Protocol` torna possível anotar a função `double` na <> sem perder funcionalidade. +O segredo é definir uma classe de protocolo com o método `+__mul__+`. +Convido você a fazer isso como um exercício. +A solução está na <> +(<>).((("", startref="GTSstatic08")))((("", startref="stprot08")))((("", startref="proto08"))) +==== + + +==== Callable + +Para((("gradual type system", "Callable type", id="GTScallable08")))((("Callable type", id="callable08"))) anotar parâmetros de callback ou objetos _callable_ retornados por funções de ordem superior, o módulo `collections.abc` oferece o tipo `Callable`, disponível no módulo `typing` para quem ainda não estiver usando Python 3.9. +Um tipo `Callable` é parametrizado assim: + +[source, python] +---- +Callable[[ParamType1, ParamType2], ReturnType] +---- + +A lista de parâmetros `[ParamType1, ParamType2]` pode ter zero ou mais tipos. + +Aqui está um exemplo no contexto de uma função `repl`, parte do interpretador iterativo simples que veremos na <>:footnote:[REPL +significa Read-Eval-Print-Loop (_Ler-Calcular-Imprimir-Recomeçar_), o comportamento básico de interpretadores iterativos.] + +[source, python] +---- +def repl(input_fn: Callable[[Any], str] = input]) -> None: +---- + +Durante a utilização normal, a função `repl` usa a função `input` nativa de Python para ler expressões inseridas pelo usuário. +Entretanto, para testagem automatizada ou para integração com outras fontes de entrada, +`repl` aceita um parâmetro `input_fn` opcional: +um `Callable` com o mesmo parâmetro e tipo de retorno de `input`. + +A função embutida `input` tem a seguinte assinatura no typeshed: + +[source, python] +---- +def input(__prompt: Any = ...) -> str: ... +---- + +A assinatura de `input` é _consistente-com_ esta dica de tipo `Callable`: + + +[source, python] +---- +Callable[[Any], str] +---- +Não existe sintaxe para a declarar o tipo de argumentos opcionais ou nomeados. +A +https://fpy.li/4f[documentação] +de `typing.Callable` diz "tais funções são raramente usadas como tipo de callback." +Se você precisar de um dica de tipo para acompanhar uma função com assinatura mais flexível, +substitua a lista de parâmetros por `\...` - assim: + +[source, python] +---- +Callable[..., ReturnType] +---- + +A interação de parâmetros de tipo genéricos com uma hierarquia de tipos introduz um novo conceito: variância. + +[[callable_variance_sec]] +===== Variância em tipos callable + +Imagine um sistema de controle de temperatura com uma função `update` simples, como mostrada no <>.((("variance", "in callable types")))((("covariance", see="variance")))((("contravariance", see="variance"))) +A função `update` chama a função `probe` para obter a temperatura atual, e chama `display` para mostrar a temperatura para o usuário. +`probe` e `display` são ambas passadas como argumentos para `update`, por motivos didáticos. +O objetivo do exemplo é contrastar duas anotações de `Callable`: uma como tipo de retorno e outra como tipo de parâmetro. + +[[callable_variance_ex]] +.Ilustrando a variância. +==== +[source, python] +---- +include::../code/08-def-type-hints/callable/variance.py[] +---- +==== +<1> `update` recebe duas funções callable como argumentos. +<2> `probe` precisa ser uma callable que não recebe nenhum argumento e retorna um `float` +<3> `display` recebe um argumento `float` e retorna `None`. +<4> `probe_ok` é _consistente-com_ `Callable[[], float]` porque retornar um `int` não quebra código que espera um `float`. +<5> `display_wrong` não é _consistente-com_ `Callable[[float], None]` porque não há garantia que uma função esperando um `int` consiga lidar com um `float`; por exemplo, a função `hex` de Python aceita um `int` mas rejeita um `float`. +<6> O Mypy marca essa linha porque `display_wrong` é incompatível com a dica de tipo no parâmetro `display` em `update`. +<7> `display_ok` é _consistente_com_ `Callable[[float], None]` porque uma função que aceita um `complex` também consegue lidar com um argumento `float`. +<8> Mypy está satisfeito com essa linha. + +Resumindo, +não há problema em fornecer uma função de callback que retorne um `int` quando o código espera uma função callback que retorne um `float`, porque um valor `int` sempre pode ser usado onde um `float` é esperado. + +Formalmente, dizemos que `Callable[[], int]` é _subtipo-de_ `Callable[[], float]`, +assim como `int` é _subtipo-de_ `float`. +Isso significa que `Callable` é _covariante_ no que diz respeito aos tipos de retorno, +porque a relação _subtipo-de_ dos tipos `int` e `float` aponta na mesma direção +que os tipos `Callable` que os usam como tipos de retorno. + +Por outro lado, é um erro de tipo fornecer uma função callback que recebe um argumento `int` +quando é se espera um callback que possa processar um `float`. + +Formalmente, `Callable[[int], None]` não é _subtipo-de_ `Callable[[float], None]`. +Apesar de `int` ser _subtipo-de_ `float`, no `Callable` parametrizado a relação é invertida: +`Callable[[float], None]` é _subtipo-de_ `Callable[[int], None]`. +Assim dizemos que aquele `Callable` é _contravariante_ a respeito dos tipos de parâmetros declarados. + +A <> (<>) explica variância em mais detalhes e +com exemplos de tipos invariantes, covariantes e contravariantes. + +[TIP] +==== +Por hora, saiba que a maioria dos tipos genéricos parametrizados são _invariantes_, portanto mais simples. +Por exemplo, se eu declaro `scores: list[float]`, +isso me diz exatamente o que posso atribuir a `scores`. +Não posso atribuir objetos declarados como `list[int]` ou `list[complex]`: + +* Um objeto `list[int]` não é aceitável porque ele não pode conter valores `float` que meu código pode precisar colocar em `scores`. +* Um objeto `list[complex]` não é aceitável porque meu código pode precisar ordenar `scores` para encontrar a mediana, mas `complex` não fornece o método `+__lt__+`, então `list[complex]` não é ordenável. +==== + +Agora chegamos ou último tipo especial que examinaremos nesse capítulo. + +[[noreturn_sec]] +==== NoReturn + +Esse((("gradual type system", "NoReturn type")))((("NoReturn type"))) é um tipo especial usado apenas para anotar o tipo de retorno de funções que nunca retornam. +Normalmente, elas existem para gerar exceções. +Há dúzias dessas funções na biblioteca padrão. + +Por exemplo, `sys.exit()` levanta `SystemExit` para encerrar o processo Python. + +Sua assinatura no `typeshed` é: + +[source, python] +---- +def exit(__status: object = ...) -> NoReturn: ... +---- + +O parâmetro `+__status__+` é apenas posicional, e tem um valor default. +Arquivos stub não contém valores default, em vez disso eles usam `\...`. +O tipo de `__status` é `object`, o que significa que pode também ser `None`, +assim seria redundante escrever `Optional[object]`. + +Na <>, +o método `__flag_unknown_attrs` tem o tipo de retorno `NoReturn`, +pois sua função é produzir uma mensagem de erro detalhada e amigável, +e então levantar `AttributeError`. + +A última seção desse capítulo épico é sobre parâmetros posicionais e variádicos +((("", startref="THTusable08")))((("", startref="FTHusable08"))) + +[[arbitrary_arguments_sec]] +=== Anotando parâmetros apenas posicionais e variádicos + +Lembra((("functions, type hints in", "annotating positional only and variadic parameters")))((("type hints (type annotations)", "annotating positional only and variadic parameters")))((("parameters", "annotating positional only and variadic parameters")))((("variadic parameters"))) da função `tag` do <>? +Da última vez que vimos sua assinatura foi em <>: + +[source, python] +---- +def tag(name, /, *content, class_=None, **attrs): +---- + +Aqui está `tag`, completamente anotada e ocupando várias linhas - uma convenção comum para assinaturas longas, +com quebras de linha como o formatador https://fpy.li/8-10[_blue_] faria: + +[source, python] +---- +from typing import Optional + +def tag( + name: str, + /, + *content: str, + class_: Optional[str] = None, + **attrs: str, +) -> str: +---- + +Observe a dica de tipo `*content: str`, para parâmetros posicionais arbitrários; +Isso significa que todos aqueles argumentos precisa ser do tipo `str`. +O tipo da variável local `content` no corpo da função será `tuple[str, \...]`. + +A dica de tipo para argumentos nomeados arbitrários é `+**attrs: str+` neste exemplo, +portanto o tipo de `attrs` dentro da função será `dict[str, str]`. +Para uma dica de tipo como `+**attrs: float+`, +o tipo de `attrs` na função seria `dict[str, float]`. + +Se for necessário que o parâmetro `attrs` aceite valores de tipos diferentes, +é preciso usar uma `Union[]` ou `Any`: `+**attrs: Any+`. + +A notação `/` para parâmetros puramente posicionais só está disponível com Python ≥ 3.8. +Em Python 3.7 ou anterior, isso é um erro de sintaxe. +A https://fpy.li/8-36[convenção da PEP 484] é prefixar o nome cada parâmetro puramente posicional com dois sublinhados. +Veja a assinatura de `tag` novamente, agora em duas linhas, usando a convenção da PEP 484: + +[source, python] +---- +from typing import Optional + +def tag(__name: str, *content: str, class_: Optional[str] = None, + **attrs: str) -> str: +---- + +O Mypy sabe checar as duas formas de declarar parâmetros puramente posicionais. + +Para encerrar esse capítulo, vamos considerar brevemente os limites das dicas de tipo e do sistema de tipagem estática que elas suportam. + +=== Tipos imperfeitos e testes poderosos + +Os mantenedores((("functions, type hints in", +"flawed typing and strong testing")))((("type hints (type annotations)", +"flawed typing and strong testing")))((("flawed typing")))((("strong testing"))) +de grandes bases de código corporativas relatam que muitos bugs são encontrados por checadores de tipos estáticos, +e o custo de resolvê-los é menor que se os mesmos bugs fossem descobertos apenas após o código estar rodando em produção. +Entretanto, é essencial observar que testes automatizados já eram uma boa prática +largamente adotada muito antes da tipagem estática ser introduzida nas empresas que conheço usando Python. + +Mesmo em contextos onde ela é mais benéfica, a tipagem estática não pode ser elevada a árbitro final da correção. +Não é difícil encontrar: + +Falsos Positivos:: Ferramentas indicam erros de tipagem em código correto. +Falsos Negativos:: Ferramentas não indicam erros em código incorreto. + +Além disso, se formos forçados a checar o tipo de tudo, perdemos um pouco do poder expressivo de Python: + +* Alguns recursos convenientes não podem ser checados de forma estática: por exemplo, +o desempacotamento de argumentos como em `config(**settings)`. +* Recursos avançados como propriedades, descritores, metaclasses e metaprogramação em geral, +têm suporte muito deficiente ou estão além da compreensão dos checadores de tipo. +* Checadores de tipo ficam obsoletos e/ou incompatíveis após o lançamento de novas versões de Python, +rejeitando ou mesmo quebrando ao analisar código com novos recursos da linguagem—às vezes com atrasos de um ano ou até mais. + +Restrições comuns de dados não podem ser expressas no sistema de tipo, mesmo restrições simples. +Por exemplo, dicas de tipo são incapazes de assegurar que "quantidade deve ser um inteiro > 0" +ou que "label deve ser uma string com 6 a 12 letras em ASCII." +Em geral, dicas de tipo não são úteis para localizar erros na lógica do negócio subjacente ao código. + +Dadas essas ressalvas, dicas de tipo não podem ser o pilar central da qualidade do software, +e torná-las obrigatórias sem qualquer exceção só amplificaria os aspectos negativos. + +Considere o checador de tipos estático como uma das ferramentas +na infraestrutura moderna de integração de código, ao lado de testadores, +analisadores de código (_linters_), etc. +O objetivo de uma estrutura de produção de integração de código é reduzir as falhas no software, +e testes automatizados podem encontrar muitos bugs que estão fora do alcance de dicas de tipo. +Qualquer código que possa ser escrito em Python pode ser testado em Python—com ou sem dicas de tipo. + +[NOTE] +==== +O título e a conclusão dessa seção foram inspirados pelo artigo +https://fpy.li/8-37["Strong Typing vs. Strong Testing"] (EN) de Bruce Eckel, também publicado na antologia https://fpy.li/8-38[_The Best Software Writing I_] (EN), editada por Joel Spolsky (Apress). +Bruce é um fã de Python, e autor de livros sobre {cpp}, Java, Scala, e +Kotlin. +Naquele texto, ele conta como foi um defensor da tipagem estática até aprender Python, e conclui: +"Se um programa em Python tem testes unitários adequados, ele poderá ser tão robusto quanto um programa em {cpp}, Java, ou C# com testes unitários adequados (mas será mais rápido escrever os testes em Python). +==== + +// [role="pagebreak-before less_space"] +Isso encerra nossa cobertura das dicas de tipo em Python por agora. +Elas serão também o ponto central do <>, que trata de classes genéricas, +variância, assinaturas sobrecarregadas, coerção de tipos (_type casting_), entre outros tópicos. +Até lá, as dicas de tipo aparecerão em várias funções ao longo do livro. + + +=== Resumo do capítulo + +Começamos((("functions, type hints in", "overview of")))((("type hints (type annotations)", "overview of"))) +com uma pequena introdução ao conceito de tipagem gradual, depois adotamos uma abordagem prática. +É difícil ver como a tipagem gradual funciona sem uma ferramenta que efetivamente leia as dicas de tipo, +então desenvolvemos uma função anotada guiados pelos relatórios de erro do Mypy. + +Voltando à ideia de tipagem gradual, vimos como ela é um híbrido do duck typing tradicional +de Python e da tipagem nominal mais familiar aos usuários de Java, {cpp} e de outras linguagens de tipagem estática. + +A maior parte do capítulo foi dedicada a apresentar os principais grupos de tipos usados em anotações. +Muitos dos tipos discutidos estão relacionados a tipos conhecidos de objetos de Python, +como coleções, tuplas e callables - estendidos para suportar notação genérica do tipo `Sequence[float]`. +Muitos daqueles tipos são substitutos temporários, +implementados no módulo `typing` antes que os tipos padrão fossem modificados para suportar genéricos, no Python 3.9. + +Alguns desses tipos são entidades especiais: +`Any`, `Optional`, `Union`, e `NoReturn` não tem qualquer relação com objetos reais na memória, +existem apenas no domínio abstrato do sistema de tipos. + +Estudamos genéricos parametrizados e variáveis de tipo, +que trazem mais flexibilidade para as dicas de tipo sem sacrificar a segurança da tipagem. + +Genéricos parametrizáveis se tornam ainda mais expressivos com o uso de `Protocol`. +Como só surgiu no Python 3.8, `Protocol` ainda não é muito usado—mas é muito importante. +`Protocol` permite duck typing estático: +é a ponte fundamental entre o núcleo de Python, construído sobre duck typing, +e a tipagem nominal que permite a checadores de tipos estáticos encontrarem bugs. + +Ao discutir alguns desses tipos, usamos o Mypy para localizar erros de checagem de tipo e tipos inferidos, +com a ajuda da função mágica `reveal_type()` do Mypy. + +A seção final mostrou como anotar parâmetros exclusivamente posicionais e parâmetros variádicos. + +Dicas de tipo são um tópico complexo e em constante evolução. +Felizmente são um recurso opcional. +Vamos manter Python acessível para a maior base de usuários possível, +e parar de defender que todo código Python precisa ter dicas de tipo—como +já vi em sermões públicos de evangelistas da tipagem estática. + +Nosso BDFLfootnote:["Benevolent Dictator For Life." - Ditador Benevolente Vitalício. +Veja Guido van van Rossum em https://fpy.li/bdfl["Origin of BDFL"].] +emérito liderou o movimento de inclusão de dicas de tipo em Python, +então é muito justo que esse capítulo comece e termine com suas palavras. + +[quote, Guido van Rossum] +____ +Não gostaria de uma versão de Python na qual eu fosse moralmente obrigado a adicionar dicas de tipo o tempo todo. +Realmente acho que dicas de tipo tem seu lugar, mas há muitas ocasiões em que elas não valem a pena, +e é maravilhoso que possamos escolher usá-las.footnote:[Do vídeo no Youtube, +«_Type Hints by Guido van Rossum (March 2015)_» [.small]#[fpy.li/8-39]#. +A citação começa em «13'40"» [.small]#[fpy.li/8-40]#. +Editei levemente a transcrição para facilitar a leitura.] +____ + + +=== Para saber mais + +Bernát Gábor escreveu((("functions, type hints in", "further reading on")))((("type hints (type annotations)", "further reading on"))) em seu excelente post, +https://fpy.li/8-41["The state of type hints in Python"] (EN): + +[quote] +____ +Dicas de Tipo deveriam ser usadas sempre que valha à pena escrever testes unitários . +____ + +Sou um grande fã de testes, mas também escrevo muito código exploratório. +Quando estou explorando, testes e dicas de tipo não ajudam. +São um entrave. + +Esse post do Gábor é uma das melhores introduções a dicas de tipo em Python que já encontrei, junto com o texto de Geir Arne Hjelle, +https://fpy.li/8-42["Python Type Checking (Guide)"] (EN). +https://fpy.li/8-43["Hypermodern Python Chapter 4: Typing"] (EN), de Claudio Jolowicz, é uma introdução mas curta que também fala de validação de checagem de tipo durante a execução. + +Para uma abordagem mais aprofundada, a https://fpy.li/8-44[documentação do Mypy] +é a melhor fonte. +Ela é útil independentemente do checador de tipos que você esteja usando, pois tem páginas de tutorial e de referência sobre tipagem em Python em geral - não apenas sobre o próprio Mypy. + +Lá você também encontrará uma conveniente +https://fpy.li/8-45[página de referência (ou _cheat sheet)] (EN) +e uma página muito útil sobre +https://fpy.li/8-46[problemas comuns e suas soluções] (EN). + +A documentação do módulo https://fpy.li/4a[`typing`] é uma boa referência rápida, mas não entra em muitos detalhes. + +A https://fpy.li/pep483[PEP 483—The Theory of Type Hints] (EN) inclui uma explicação aprofundada sobre variância, usando `Callable` para ilustrar a contravariância. +As referências definitivas são as PEP relacionadas a tipagem. +Já existem mais de 20 delas. +O público alvo das PEPs são os _core developers_ (mantenedores da linguagem em si) e o Steering Council de Python (comitê diretivo), +então elas pressupõe muito conhecimento prévio, e certamente não são uma leitura leve. + +Como já mencionado, o <> cobre outros tópicos sobre tipagem, e a +<> traz referências adicionais, +incluindo uma tabela com a longa lista de PEPs sobre tipagem aprovadas ou em discussão até o final de 2021. + +https://fpy.li/8-47["Awesome Python Typing"] é uma ótima coleção de links para ferramentas e referências. + +[[type_hints_in_def_soapbox]] +.Ponto de vista +**** + +[role="soapbox-title"] +**Apenas Pedale** + +[quote, Grant Petersen, Just Ride: A Radically Practical Guide to Riding Your Bike (Apenas Pedale: Um Guia Radicalmente Prático sobre o Uso de sua Bicicleta) (Workman Publishing)] +____ +Esqueça((("functions, type hints in", "Soapbox discussion", id="FTHsoap08")))((("type hints (type annotations)", "Soapbox discussion", id="THsoag08"))) as desconfortáveis bicicletas ultraleves, as malhas brilhantes, os sapatos desajeitados que se prendem a pedais minúsculos, o esforço de quilômetros intermináveis. +Em vez disso, faça como você fazia quando era criança - suba na sua bicicleta e descubra o puro prazer de pedalar. +____ + + +Se((("Soapbox sidebars", "type hints (type annotations)", id="SStypehints08"))) programar não é sua profissão principal, mas uma ferramenta útil no seu trabalho ou algo que você faz para aprender, experimentar e se divertir, você provavelmente não precisa de dicas de tipo mais que a maioria dos ciclistas precisa de sapatos com solas rígidas e presilhas metálicas. + +Apenas programe. + +[role="soapbox-title"] +**O Efeito Cognitivo da Tipagem** + +Eu me preocupo com o efeito que as dicas de tipo terão sobre o estilo de programação em Python. + +Concordo que usuários da maioria das APIs se beneficiam de dicas de tipo. +Mas Python me atraiu—entre outras razões—porque oferece funções tão poderosas +que substituem APIs inteiras, e podemos escrever nós mesmos funções poderosas similares. +Considere a função nativa https://fpy.li/8-48[`max()`]. +Ela é poderosa, mas é fácil de entender. +Porém, vou mostrar na <> que são necessárias +14 linhas de dicas de tipo para anotar corretamente essa função—sem contar a definição de um `typing.Protocol` +e algumas definições de `TypeVar` para sustentar aquelas dicas de tipo. + +Me inquieta que a adoção estrita de dicas de tipo em bibliotecas desencorajem programadores +de sequer considerarem programar funções assim no futuro. + +De acordo com o verbete em inglês na Wikipedia, https://fpy.li/8-49["relatividade linguística"]—ou a hipótese Sapir–Whorf—é +um "princípio alegando que a estrutura de uma linguagem afeta a visão de mundo ou a cognição de seus falantes." + +A Wikipedia continua: + +* A versão _forte_ diz que a linguagem _determina_ o pensamento, +e que categorias linguísticas limitam e determinam as categorias cognitivas. +* A versão _fraca_ diz que as categorias linguísticas e seu uso +apenas _influenciam_ o pensamento e as decisões. + +Linguistas em geral concordam que a versão forte é falsa, +mas há evidência empírica apoiando a versão fraca. + +Não conheço estudos específicos com linguagens de programação, +mas na minha experiência, elas tiveram grande impacto sobre a forma como eu abordo problemas. +A primeira linguagem de programação que usei profissionalmente foi o Applesoft BASIC, +na era dos computadores de 8 bits. +Recursão não era diretamente suportada pelo BASIC. +Você teria que gerenciar uma pilha na unha para implementar um algoritmo recursivo. +Então eu nunca pensava em usar algoritmos ou estruturas de dados recursivos. +Eu sabia, em algum nível conceitual, que tais coisas existiam, +mas elas não eram parte de meu arsenal de técnicas de resolução de problemas. + +Décadas mais tarde, quando aprendi Elixir, +gostei de resolver problemas com recursão e usei essa técnica além da conta—até +descobrir que muitas das minhas soluções seriam mais simples se que usasse funções existentes nos módulos `Enum` e `Stream` do Elixir. +Aprendi que o código idiomático de aplicações em Elixir raramente contém chamadas recursivas +explícitas—em vez disso, usam enums e streams que implementam recursão por trás dos panos. + +A relatividade linguística pode explicar a ideia recorrente (e também não provada) que aprender linguagens de programação diferentes torna alguém um programador melhor, especialmente quando as linguagens em questão suportam diferentes paradigmas de programação. +Praticar com Elixir me tornou mais propenso a aplicar padrões funcionais quando escrevo programas em Python ou Go. + +Agora voltando à Terra. + +O pacote _requests_ provavelmente teria uma API muito diferente se Kenneth Reitz tivesse decidido +anotar todas as suas funções (ou tivesse recebido ordens de seu chefe para fazê-lo) +Seu objetivo era escrever uma API que fosse fácil de usar, flexível e poderosa. +Ele conseguiu, dada a fantástica popularidade de _requests_ - em maio de 2020, +ela estava em #4 nas https://fpy.li/8-50[PyPI Stats], com 2,6 milhões de downloads diários. +A #1 era a _urllib3_, uma dependência de _requests_. + +Em 2017 os mantenedores de _requests_ https://fpy.li/8-51[decidiram] não perder seu tempo escrevendo dicas de tipo. +Um deles, Cory Benfield, escreveu um e-mail dizendo: + +[quote] +____ +Acho que bibliotecas com APIs 'pythônicas' são as menos propensas a adotar esse sistema de tipagem, pois ele vai adicionar muito pouco valor a elas. +____ + +Naquela mensagem, Benfield incluiu esse exemplo extremo de uma tentativa de definição de tipo para o +argumento nomeado `files` em https://fpy.li/8-53[`requests.request()`]: + +---- +Optional[ + Union[ + Mapping[ + basestring, + Union[ + Tuple[basestring, Optional[Union[basestring, file]]], + Tuple[basestring, Optional[Union[basestring, file]], + Optional[basestring]], + Tuple[basestring, Optional[Union[basestring, file]], + Optional[basestring], Optional[Headers]] + ] + ], + Iterable[ + Tuple[ + basestring, + Union[ + Tuple[basestring, Optional[Union[basestring, file]]], + Tuple[basestring, Optional[Union[basestring, file]], + Optional[basestring]], + Tuple[basestring, Optional[Union[basestring, file]], + Optional[basestring], Optional[Headers]] + ] + ] + ] +] +---- + +E isso assume essa definição: + +---- +Headers = Union[ + Mapping[basestring, basestring], + Iterable[Tuple[basestring, basestring]], +] +---- + +Você acha que _requests_ seria como é se os mantenedores insistissem em ter uma cobertura de dicas de tipo de 100%? +_SQLAlchemy_ é outro pacote importante que não trabalha muito bem com dicas de tipo. + +O que torna essas bibliotecas fabulosas é incorporarem a natureza dinâmica de Python. + +Apesar das dicas de tipo trazerem benefícios, há também um preço a ser pago. + +Primeiro, há o investimento significativo de aprender como o sistema de tipos funciona. +Esse é um preço alto, cobrado uma vez. + +Mas há também um custo recorrente, eterno. + +Perdemos parte do poder expressivo de Python se insistimos que tudo precisa estar sob a checagem de tipos. +Recursos excelentes estão além da capacidade de compreensão dos checadores de tipos, +por exemplo o desempacotamento de argumentos: `config(**settings)`. + +Se quiser ter uma chamada como `config(**settings)` checada quanto ao tipo, +precisa explicitar cada argumento. +Isso me traz lembranças de programas em Turbo Pascal, que escrevi 35 anos atrás. + +Bibliotecas que usam metaprogramação são difíceis ou impossíveis de anotar. +Claro que a metaprogramação pode ser mal usada, +mas isso também é algo que torna muitos pacotes de Python divertidos de usar. + +Se dicas de tipo se tornarem obrigatórias sem exceções, +por uma decisão superior em grande empresas, +aposto que logo veremos pessoas usando geração de código para reduzir a verbosidade em programas Python, +uma prática comum com linguagens menos dinâmicas. + +Para alguns projetos e contextos, dicas de tipo simplesmente não fazem sentido. +Mesmo em contextos onde elas fazer muito sentido, não fazem sentido o tempo todo. +Qualquer política razoável sobre o uso de dicas de tipo precisa conter exceções. + +Alan Kay, que recebeu o prêmio Turing e foi um dos pioneiros da programação orientada a objetos, +certa vez disse: + +[quote] +____ +Algumas pessoas são completamente religiosas no que diz respeito a sistemas de tipo, +e como um matemático eu adoro a ideia de sistemas de tipos, +mas ninguém até agora inventou um que tenha alcance o suficiente.footnote:[Fonte: +https://fpy.li/8-54["A Conversation with Alan Kay"].] +____ + +Obrigado, Guido, pela tipagem opcional. +Vamos usá-la como foi pensada, e não tentar anotar tudo em conformidade estrita com +um estilo de programação que se parece com Java 1.5.((("", startref="SStypehints08"))) + +[role="soapbox-title"] +**Duck Typing FTW** + +Duck typing((("Soapbox sidebars", "duck typing")))((("duck typing"))) +encaixa bem no meu cérebro, e duck typing estático é um bom compromisso, +permitindo checagem estática de tipo sem perder muito da flexibilidade que +alguns sistemas de tipagem nominal só permitem ao custo de muita complexidade—quando permitem. + +Antes da PEP 544, toda essa ideia de dicas de tipo me parecia completamente não-pythônica. +Fiquei muito feliz quando vi `typing.Protocol` surgir em Python. +Ele traz equilíbrio para a Força. + +[role="soapbox-title"] +**Generics ou specifics?** + +De((("Soapbox sidebars", "generic collections")))((("generic collections", +"Soapbox discussion"))) uma perspectiva de Python, +o uso do termo "genérico" na tipagem é invertido. +Os sentidos comuns do termo "genérico" são +"aplicável integralmente a um grupo ou uma classe" ou "sem uma marca distintiva." + +Considere `list` versus `list[str]`. +O primeiro tipo é genérico: a lista aceita qualquer objeto. +O segundo é específico: só aceita itens do tipo `str`. + +Por outro lado, o termo faz sentido em Java. +Antes de Java 1.5, todas as coleções de Java (exceto a mágica `array`) eram "specific": +só podiam conter referências a `Object`, +então era necessário converter os itens que saíam de uma coleção antes que eles pudessem ser usados. +Com Java 1.5, as coleções ganharam parâmetros de tipo, +e se tornaram "generic."((("", startref="THsoag08")))((("", startref="FTHsoap08"))) + +**** diff --git a/online/cap09.adoc b/online/cap09.adoc new file mode 100644 index 00000000..814a07df --- /dev/null +++ b/online/cap09.adoc @@ -0,0 +1,1770 @@ +[[ch_closure_decorator]] +== Decoradores e Clausuras +:example-number: 0 +:figure-number: 0 + +[quote, PEP 318—Decorators for Functions and Methods ("Decoradores para Funções e Métodos"—EN)] +____ +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"_), de 1985. Seus quatro +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 +<>.] + +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, esta técnica 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 de onde variáveis podem vir, chamado _nonlocal_, e ele é +fundamental para clausuras; 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 são algumas vezes confundidas com funções +anônimas. Muita gente confunde por causa da 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 nem +variáveis globais nem variáveis locais de `f`. Tais variáveis devem vir do +escopo local de uma função externa que englobe `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__+`. +Esses itens são `cells`, e têm um atributo chamado `cell_contents`, onde o valor +real pode ser encontrado. O <> mostra esses atributos. + +[[ex_average_demo3]] +.Continuando do <> +==== +[source, python] +---- +>>> avg.__code__.co_freevars +('series',) +>>> 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 declaraçã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 simples que mostra o tempo de execução de funções +==== +[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 <> 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> Este é um exemplo de decoradores empilhados: +`@cache` é aplicado à função devolvida por `@clock`. + +[[stacked_decorators_tip]] +.Decoradores empilhados +[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 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.
+
+===== Despacho único de funções
+
+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 funciona 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 <>,
+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`, acrescentando assim mais uma
+camada de função.
+
+====
+
+[[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`.
+
+====
+
+A próxima seção traz 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 <>.((("", 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
+<>.footnote:[Como 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 <> é sobre descritores).
+
+https://fpy.li/9-8["Metaprogramming" (_Metaprogramação_)] (EN), 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" (_Receita
+9.6. Definindo um Decorador Que Recebe um Argumento Opcional_)  desse 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")))
+
+[role="soapbox-title"]
+**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). Pegando emprestado da citação
+anterior, podemos adaptar a última frase para dizer que "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 possível 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")))
+
+****
diff --git a/online/cap10.adoc b/online/cap10.adoc
new file mode 100644
index 00000000..acaf5202
--- /dev/null
+++ b/online/cap10.adoc
@@ -0,0 +1,735 @@
+[[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
+<> 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 pelo menos 10 itens diferentes recebem 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]]
+.Amostra de uso 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 único método, `discount`. Além disso, as instâncias de estratégia não tem
+nenhum 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 <>, substituindo as estratégias concretas
+por funções simples e removendo a classe abstrata `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 de descontos 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 <> 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 iterando sobre 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
+apenas invoca `command()`. O `MacroCommand` pode ser programado como 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.
+
+_Learning Python Design Patterns_, de Gennadiy Zlobin (Packt),
+é o único livro inteiramente dedicado a
+padrões em Python que encontrei. Mas o trabalho de Zlobin é muito breve (100
+páginas) e trata de apenas 8 dos 23 padrões de projeto originais.
+
+_Expert Python Programming_, de Tarek Ziadé (Packt), é um dos melhores livros
+sobre Python em nível intermediário, e seu capítulo final,
+_Useful Design Patterns_ (Padrões de Projetos Úteis),
+apresenta vários padrões clássicos com uma abordagem pythônica.
+
+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] (EN)
+e um https://fpy.li/10-6[conjunto de slides em seu site pessoal] (EN). Ao longo
+dos anos, encontrei diferentes jogos de slides e vídeos de diferentes tamanhos,
+então vale a pena tentar uma busca mais ampla com o nome dele e as 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 é útil, 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/online/cap11.adoc b/online/cap11.adoc
new file mode 100644
index 00000000..c3abb9f1
--- /dev/null
+++ b/online/cap11.adoc
@@ -0,0 +1,1545 @@
+[[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 <> 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 <>.
+
+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 de 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 <>. 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_, 
+(<>). 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]]
+.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 <>.]
+
+<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 <>.
+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 <>.]
+
+<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
+<>.
+
+Nesse momento, qualquer um pode fazer `v1.x = 7`,
+e não há nada no código sugerindo que é proibido modificar um `Vector2d`.
+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: apenas as mudanças necessárias para tornar `Vector2d` imutável são exibidas 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 permanecer
+como estavam, lendo as propriedades públicas através de `self.x` e `self.y` em
+vez de usar os atributos privados. Então essa listagem omite o restante do
+código da classe.
+
+[NOTE]
+====
+
+`Vector.x` e `Vector.y` são exemplos de propriedades apenas para leitura.
+Propriedades para leitura/escrita serão tratadas no <>, 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 <>.
+
+No <>, todos aqueles 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 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 casamento de padrões posicionais.
+
+
+[source, python]
+----
+class Vector2d:
+    __match_args__ = ('x', 'y')
+
+    # etc...
+----
+
+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+`.
+
+Esse recurso da linguagem é conhecido pela encantadora alcunha de((("name
+mangling"))) "desfiguração de nome" (_name mangling_).
+
+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:
+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
+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 (e não um método) que afeta o armazenamento interno de um
+objeto, reduzindo o uso de memória, mas 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 <>, 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
+<>).
+
+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 <>). 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 da adição de `+__slots__+` a `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 <>), 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_sec]]
+=== 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]]
+.Personalizando uma instância pela definição do atributo `typecode`, que antes era herdado da classe
+====
+[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 <>, onde tivemos uma ideia geral do
+mesmo tópico. Por conveniência, vou repetir aquelas quatro recomendações
+anteriores aqui, e acrescentar algumas outras:
+
+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" (_Classes e Objetos_), que contém 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
+<>. As fábricas de classes de dados do <>,
+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).
+
+Uma percepção precoce da necessidade de strings de representação diferentes para
+objetos apareceu  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 de lá que peguei as descrições
+"como o desenvolvedor quer vê-lo" e "como o usuário quer vê-lo" para definir
+`repr()` e `str()`, na <>.
+
+
+[role="pagebreak-before less_space"]
+[[pythonic_soapbox]]
+.Ponto de Vista
+****
+
+
+[role="soapbox-title"]
+**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 sentimos a necessidade de evitar modificações acidentais dos atributos
+`x` e `y`, implementamos propriedades, mas nada mudou 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` and `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.
+
+
+[role="soapbox-title"]
+**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 deles 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 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")))
+
+****
diff --git a/online/cap12.adoc b/online/cap12.adoc
new file mode 100644
index 00000000..e8b7a4fe
--- /dev/null
+++ b/online/cap12.adoc
@@ -0,0 +1,1431 @@
+[[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 <>,
+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 <> do <>,
+reproduzido no <>.
+
+[[ex_pythonic_deck_rep]]
+.Código do <> do <>, reproduzido aqui por conveniência
+====
+[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 <>).
+<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 pelo interpretador 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 <>. 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'`).
+
+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 pouco 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` e `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` e `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
+<>.] 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 <>, `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 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`.
+
+[[ex_eq_all]]
+.A implementação de `+Vector.__eq__+` usando `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)))
+----
+====
+
+Observe que primeiro comparamos o `len()` dos operandos porque
+não queremos comparar item a item se os vetores têm tamanhos diferentes.
+
+O <> é a implementação que escolhemos para `+__eq__+` em
+_vector_v4.py_.((("", startref="eq12")))((("", startref="hash12")))((("",
+startref="SSMhasheq12")))((("", startref="VCMhasheq12")))
+
+
+[[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 é esgotado.
+<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 esgotado.
+
+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
+<>.
+
+****
+
+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] é melhor evitar a reutilização dos códigos de formato
+usados por tipos embutidos. Em particular, nossa minilinguagens estendida também
+usa os códigos de formato dos números de ponto flutuante (`'eEfFgGn%'`), com seus
+significados originais, então devemos certamente evitar qualquer um daqueles.
+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
+<>, vamos usar parte do código de `Vector` nos exemplos, e lá
+o funcionamento dos geradores será explicado 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 a `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 <>. 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.
+
+[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
+----
+
+Acho a solução de Perez boa, mas Guy Middleton elogiou essa 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 esse 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/online/cap13.adoc b/online/cap13.adoc
new file mode 100644
index 00000000..27ae9d24
--- /dev/null
+++ b/online/cap13.adoc
@@ -0,0 +1,2749 @@
+[[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
+<>.
+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 <> 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 <>,
+    e continuamos nesse 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 <> explica as semelhanças e diferenças entre
+protocolos dinâmicos e estáticos.
+
+* 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 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 <> 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 <>,
+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 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
+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 <> 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 o <> do <>.
+====
+[source, python]
+----
+include::../code/01-data-model/frenchdeck.py[]
+----
+====
+
+Muitos dos exemplos no <> 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://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
+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 <>. 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 (tipicamente) 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 _https://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.
+
+
+[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 <> é((("UML class diagrams", "ABCs in collections.abc"))) um
+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 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.]
+
+[[collections_uml]]
+.Diagrama de classes UML para as ABCs em `collections.abc`.
+image::../images/flpy_1304.png[align="center",pdfwidth=11cm]
+
+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 a
+<> 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 no <>.
+
+`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 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)`.
+____
+
+****
+
+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_:
+
+[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 <>, 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 despercebida
+====
+[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://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 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
+<> 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`
+====
+[source, python]
+----
+include::../code/13-protocol-abc/bingo.py[tags=TOMBOLA_BINGO]
+----
+====
+<1> Essa classe `BingoCage` estende `Tombola` explicitamente.
+
+<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 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 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
+<> 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 <>.] 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` no código-fonte de 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 <>. 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 <>.
+
+[[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_ 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 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 isso aqui
+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 <>.
+
+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 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 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 <> 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 <> 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`:
+
+[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
+<>, 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]
+====
+
+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`.
+
+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 <>. 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 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 recomendado 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 <>). 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 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 deveriam ser.
+
+
+[role="pagebreak-before less_space"]
+[[interfaces_soapbox]]
+.Ponto de vista
+****
+
+**A Jornada MVP da tipagem estática em 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.
+
+[[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 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.
+
+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 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.
+
+____
+
+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
new file mode 100644
index 00000000..8729e497
--- /dev/null
+++ b/online/cap14.adoc
@@ -0,0 +1,1578 @@
+[[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 ao 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 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()`
+* 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.
+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}.
+
+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 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 biblioteca
+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)
+        ...  # more initialization code
+----
+====
+
+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 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 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
+<>.
+
+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` e `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 <>),
+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: 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=11cm]
+
+[[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ção 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]
+----
+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 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.
+
+[[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 <>. É 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 dos 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 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 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://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 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 biblioteca 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 de 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 <>. 
+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 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
new file mode 100644
index 00000000..395dcbc6
--- /dev/null
+++ b/online/cap15.adoc
@@ -0,0 +1,2016 @@
+[[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://https://fpy.li/9k.]
+
+____
+
+
+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 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
+
+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 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]
+----
+>>> 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 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, 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)`.
+
+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 `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 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`.
+
+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=`, 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...
+
+. 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
+<>.
+
+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 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.
+
+____
+
+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 <> 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 <>.
+
+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 <>, 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 <>.
+
+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 <> 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]
+----
+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
+<> 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 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 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.]
+____
+
+É 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 <>.
+
+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`,
+e não as strings `'str'` e `'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
+`inspect.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 <> do <>:
+
+[source, python]
+----
+class Checked:
+    @classmethod
+    def _fields(cls) -> dict[str, type]:
+        return get_type_hints(cls)
+    # ... more lines ...
+----
+
+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 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_)], 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")))
+
+
+[[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 <>, 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 tipos 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.
+
+Como mencionado, as coleções mutáveis de Python são invariantes por default.
+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
+<>. 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 precisos de "enviar" e
+"produzir" são definidos na <>.
+
+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 em 
+<>.
+
+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_]:
+
+[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 <>, 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 provavelmente serã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
+<> 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,
+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**
+
+**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/online/cap16.adoc b/online/cap16.adoc
new file mode 100644
index 00000000..b2ae5236
--- /dev/null
+++ b/online/cap16.adoc
@@ -0,0 +1,1548 @@
+[[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 com esta fórmula:
+
+[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 <>, 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 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 de que instâncias de `Vector` são iteráveis, e o `+Vector.__init__+`
+recebe um argumento iterável, por isso as implementações de `+__neg__+` e
+`+__pos__+` ficaram 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__+` como no
+<>.
+
+[[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__+` 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. 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`, 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++
+====
+[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]]
+.A versão #1 de `+Vector.__add__+` falha se o operador da esquerda não for um `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).
+
+[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
+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.
+
+[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 Vector
+
+    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 <>; 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. 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 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 `{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)
+
+    # vários métodos omitidos no livro; código completo em
+    # https://github.com/fluentpython/example-code-2e
+
+    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]]
+.O novo recurso de zip() no Python 3.10
+[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 <>).
+
+<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.
+
+____
+
+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.
+
+**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 <> 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
new file mode 100644
index 00000000..fb0b2a2f
--- /dev/null
+++ b/online/cap17.adoc
@@ -0,0 +1,2859 @@
+[[ch_generators]]
+== Iteradores, geradores e corrotinas clássicas
+:example-number: 0
+:figure-number: 0
+
+[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 de 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.]
+
+____
+
+
+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:
+
+* O laço `for`
+* Compreensões de lista, dict e set
+* Atribuições com desempacotamento de tuplas
+* Criação de instâncias de coleções
+
+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 é 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 aproveitar 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 usados de formas
+muito diferentes e não devem ser misturadas
+
+
+=== Novidades neste capítulo
+
+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`.
+
+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.
+
+
+=== 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
+por que isso acontece.
+
+O <> mostra uma classe `Sentence` que permite
+ler as palavras de um texto por índice.
+
+[[ex_sentence0]]
+.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` preserva 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 <>.
+
+[[demo_sentence0]]
+.Testando a iteração em uma instância de `Sentence`
+====
+[source, python]
+----
+>>> s = Sentence('"The time has come," the Walrus said,')  # <1>
+>>> s
+Sentence('"The time ha... Walrus said,')  # <2>
+>>> for word in s:  # <3>
+...     print(word)
+The
+time
+has
+come
+the
+Walrus
+said
+>>> list(s)  # <4>
+['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:
+
+[source, python]
+----
+>>> s[0]
+'The'
+>>> s[5]
+'Walrus'
+>>> s[-1]
+'said'
+----
+
+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`
+
+Para((("functions", "iter() function")))((("iterators", "iter() function",
+id="Iinterfun17")))((("iter() function", id="iterfunc17")))
+iterar sobre um objeto `x`, o interpretador Python invoca `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, a partir de `0` (zero).
+
+. 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.
+
+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 este método. A iteração automática via `+__getitem__+` existe para
+manter a compatibilidade retroativa, e pode desaparecer em algum momento—mas
+eu duvido que será removida no futuro.
+
+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]
+----
+>>> class Spam:
+...     def __getitem__(self, i):
+...         print('->', i)
+...         raise IndexError()
+...
+>>> spam_can = Spam()
+>>> iter(spam_can)
+
+>>> list(spam_can)
+-> 0
+[]
+>>> from collections import abc
+>>> isinstance(spam_can, abc.Iterable)
+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.
+
+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 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 registrar como subclasse virtual,
+pois `abc.Iterable`
+implementa o `+__subclasshook__+`, como visto na <>.
+Demonstração:
+
+[source, python]
+----
+>>> class GooseSpam:
+...     def __iter__(self):
+...         pass
+...
+>>> from collections import abc
+>>> issubclass(GooseSpam, abc.Iterable)
+True
+>>> goose_spam_can = GooseSpam()
+>>> isinstance(goose_spam_can, abc.Iterable)
+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.
+
+====
+
+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 é bem explícita:
+`TypeError: 'C' object is not iterable` (o objeto 'C' não é
+iterável). Se você quiser fazer algo 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 erros.
+
+A função embutida `iter()` é usada mais frequentemente pelo Python do que em
+código que nós escrevemos. 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. 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
+enquanto o valor `1` não é sorteado:
+
+[source, python]
+----
+>>> def d6():
+...     return randint(1, 6)
+...
+>>> d6_iter = iter(d6, 1)
+>>> d6_iter
+
+>>> for roll in d6_iter:
+...     print(roll)
+...
+4
+3
+6
+3
+----
+
+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 esgotado. Para recomeçar, é necessário reconstruir o iterador,
+invocando novamente `iter()`.
+
+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()` é construir um
+leitor bloco-a-bloco (_block reader_).
+Por exemplo, ler blocos de 64 bytes de um arquivo binário de banco de dados
+até que o final do arquivo seja atingido:
+
+____
+
+[source, python]
+----
+from functools import partial
+
+with open('mydata.db', 'rb') as f:
+    read_block = partial(f.read, 64)
+    for block in iter(read_block, b''):
+        process_block(block)
+----
+
+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")))
+
+
+=== 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:
+
+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.
+
+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]
+----
+>>> s = 'ABC'
+>>> for char in s:
+...     print(char)
+...
+A
+B
+C
+----
+
+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]
+----
+>>> s = 'ABC'
+>>> it = iter(s)  # <1>
+>>> while True:
+...     try:
+...         char = next(it)  # <2>
+...     except StopIteration:  # <3>
+...         del it  # <4>
+...         break  # <5>
+...     print(char)  # <6>
+A
+B
+C
+----
+<1> Cria um iterador `it` a partir de um iterável.
+<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 laço.
+<6> Exibe `char`. Esta variável continua existindo depois do laço.
+
+`StopIteration` sinaliza que o iterador esgotou.
+Esta exceção é tratada internamente pelo Python, dentro 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.
+
+`+__iter__+`:: Devolve `self`; assim o iterador pode ser
+usado quando um iterável é esperado. Por exemplo, em um laço `for`.
+
+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 <>.
+
+[[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[align="center",pdfwidth=9cm]
+
+O código-fonte de `collections.abc.Iterator` aparece no <>.
+
+[[abc_iterator_src]]
+.Classe `abc.Iterator`; extraído de https://fpy.li/17-5[__Lib/_collections_abc.py__]
+====
+[source, python]
+----
+class Iterator(Iterable):
+
+    __slots__ = ()
+
+    @abstractmethod
+    def __next__(self):
+        """Return the next item from the iterator.
+        When exhausted, raise StopIteration"""
+        raise StopIteration
+
+    def __iter__(self):
+        return self
+
+    @classmethod
+    def __subclasshook__(cls, C):  # <1>
+        if cls is Iterator:
+            return _check_methods(C, '__iter__', '__next__')  # <2>
+        return NotImplemented
+----
+====
+
+<1> `+__subclasshook__+` suporta a checagem de tipos estrutural com `isinstance`
+e `issubclass`. Vimos isso na <>.
+
+<2> `_check_methods` percorre o atributo `+__mro__+` da classe, para checar se
+os métodos estão implementados em sua classe base ou outra superclasse.
+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 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 detectar os atributos "__iter__" e "__next__".
+----
+
+Isto é 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__+`, este teste funciona mesmo quando a
+classe de `x` não é uma subclasse real ou virtual de `Iterator`.
+
+====
+
+Voltando à nossa classe `Sentence` no <>, usando o console de
+Python podemos ver claramente como o iterador é criado por `iter()` e
+consumido por `next()`:
+
+[source, python]
+----
+>>> s3 = Sentence('Life of Brian')  # <1>
+>>> it = iter(s3)  # <2>
+>>> it  # doctest: +ELLIPSIS
+
+>>> next(it)  # <3>
+'Life'
+>>> next(it)
+'of'
+>>> next(it)
+'Brian'
+>>> next(it)  # <4>
+Traceback (most recent call last):
+  ...
+StopIteration
+>>> list(it)  # <5>
+[]
+>>> list(iter(s3))  # <6>
+['Life', 'of', 'Brian']
+----
+<1> Cria uma sentença `s3` com três palavras.
+<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 esgotado, um iterador vai sempre lançar `StopIteration`,
+indicando que não há mais itens.
+<6> Para percorrer a sentença novamente, precisamos 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 precisar
+começar de novo, invoque `iter()` novamente no iterável que criou o
+iterador original. Invocar `iter()` no próprio iterador esgotado não funciona,
+pois—como já mencionado—a implementação de `+Iterator.__iter__+`
+apenas devolve `self`, e isso não reinicia o iterador.
+
+Esta interface minimalista faz sentido 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 este ótimo exemplo.]
+
+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 codar variações de `Sentence` que implementam `+__iter__+` para
+devolver iteradores.((("", startref="IOvie17")))((("", startref="IEvio17")))
+
+
+=== 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.
+
+
+==== Sentence versão #2: um iterador clássico
+
+A próxima implementação de `Sentence` segue a forma do padrão de projeto _Iterator_ clássico, do livro _Padrões de Projeto_.
+Observe que isso não é Python idiomático, como as refatorações seguintes deixarão claro.
+Mas é útil para mostrar a distinção entre uma coleção iterável e um iterador que trabalha com ela.
+
+A classe `Sentence` no <> é iterável por implementar o método especial `+__iter__+`,
+que cria e devolve um `SentenceIterator`. É assim que um iterável e um iterador se relacionam.
+
+[[ex_sentence1]]
+.sentence_iter.py: `Sentence` implementada usando o padrão _Iterator_
+====
+[source, python]
+----
+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`. Esta versão não tem `+__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` preserva 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`, levanta `StopIteration`.
+<7> Incrementa `self.index`.
+<8> Devolve a palavra.
+<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 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__+`.
+
+É 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 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 implementa 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 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:
+
+[quote]
+____
+Use o padrão Iterator para:
+
+* Acessar o conteúdo de um objeto agregado sem expor sua representação interna.
+
+* Suportar travessias múltiplas de objetos agregados.
+
+* 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 a partir de um mesmo objeto iterável, e cada
+iterador deve preservar 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 isto precisamos da classe
+`SentenceIterator` neste exemplo.
+
+Agora((("yield keyword", id="yielda17")))((("keywords", "yield keyword",
+id=Kyielda17"))) que sabemos como funciona o padrão _Iterator_
+clássico, veremos que não precisamos
+"escrever à mão" nenhuma classe de iterador em Python,
+graças à instrução `yield`, que foi inspirada pela linguagem
+https://fpy.li/17-7[_CLU_], criada por um time liderado por Barbara Liskov.
+
+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 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 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. 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 precisamos escrever uma classe iteradora!
+
+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
+<> é um objeto gerador, criado automaticamente quando o
+método `+__iter__+` é invocado, porque neste exemplo `+__iter__+` é uma função geradora.
+
+A seguir: uma explicação bem completa sobre geradores.
+
+
+==== 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, uma função geradora é
+uma fábrica de geradores.
+
+[role="man-height3"]
+[TIP]
+====
+
+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.]
+
+[[gen-func-ex-three-yield]]
+.Uma função geradora que produz três números((("generators", "examples of", id="genex17")))
+====
+[source, python]
+----
+>>> def gen_123():
+...     yield 1  # <1>
+...     yield 2
+...     yield 3
+...
+>>> gen_123  # doctest: +ELLIPSIS
+  # <2>
+>>> gen_123()   # doctest: +ELLIPSIS
+  # <3>
+>>> for i in gen_123():  # <4>
+...     print(i)
+1
+2
+3
+>>> g = gen_123()  # <5>
+>>> next(g)  # <6>
+1
+>>> next(g)
+2
+>>> next(g)
+3
+>>> next(g)  # <7>
+Traceback (most recent call last):
+  ...
+StopIteration
+----
+====
+
+<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 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 termina, 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 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" 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` embrulhado na
+exceção `StopIteration`, mas normalmente isso é feito automaticamente usando a
+sintaxe `yield from`, como veremos na <>.
+
+====
+
+O <> torna mais explícita a interação entre um laço `for` e o corpo da função geradora.
+
+[[ex_gen_ab]]
+.Uma função geradora que exibe mensagens quando roda
+====
+[source, python]
+----
+>>> def gen_AB():
+...     print('start')
+...     yield 'A'          # <1>
+...     print('continue')
+...     yield 'B'          # <2>
+...     print('end.')      # <3>
+...
+>>> for c in gen_AB():     # <4>
+...     print('-->', c)    # <5>
+...
+start     <6>
+--> A     <7>
+continue  <8>
+--> B     <9>
+end.      <10>
+>>>       <11>
+----
+====
+
+<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.
+
+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.
+
+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, valendo-se de uma
+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 avaliação á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 são mais descritivos.]
+
+Até aqui, nossas implementações de `Sentence` não são preguiçosas,
+pois o `+__init__+` cria avidamente 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 deste trabalho será inútil se o usuário iterar apenas sobre as primeiras palavras.
+Se você está se perguntando 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 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.
+O código está no <>.
+
+[[ex_sentence3]]
+.sentence_gen2.py: `Sentence` implementada usando uma função geradora que invoca a função geradora `re.finditer`
+====
+[source, python]
+----
+include::../code/17-it-generator/sentence_gen2.py[tags=SENTENCE_GEN2]
+----
+====
+<1> Não é necessário manter uma lista `words`.
+<2> `finditer` cria um iterador sobre os termos encontrados com `RE_WORD` em `self.text`, produzindo instâncias de `MatchObject`.
+<3> `match.group()` extrai o texto da instância de `MatchObject`.
+
+Geradores são um ótimo atalho, mas o código pode ser ainda mais conciso com uma expressão geradora.
+
+
+==== Sentence versão #5: Expressão geradora preguiçosa
+
+Podemos substituir funções geradoras simples—como aquela na última
+classe `Sentence` (no <>)—por uma expressão geradora.
+Assim como uma compreensão de lista cria listas, uma expressão geradora cria objetos geradores.
+O <> compara o comportamento nos dois casos.
+
+[[ex_gen_ab_genexp]]
+.A função geradora `gen_AB` é usada primeiro por uma compreensão de lista, depois por uma expressão geradora
+====
+[source, python]
+----
+>>> def gen_AB():  # <1>
+...     print('start')
+...     yield 'A'
+...     print('continue')
+...     yield 'B'
+...     print('end.')
+...
+>>> res1 = [x*3 for x in gen_AB()]  # <2>
+start
+continue
+end.
+>>> for i in res1:  # <3>
+...     print('-->', i)
+...
+--> AAA
+--> BBB
+>>> res2 = (x*3 for x in gen_AB())  # <4>
+>>> res2
+ at 0x10063c240>
+>>> for i in res2:  # <5>
+...     print('-->', i)
+...
+start      # <6>
+--> AAA
+continue
+--> BBB
+end.
+----
+====
+<1> Esta é 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> Este 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 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 <>.
+
+[[ex_sentence4]]
+.sentence_genexp.py: `Sentence` implementada usando uma expressão geradora
+====
+[source, python]
+----
+include::../code/17-it-generator/sentence_genexp.py[tags=SENTENCE_GENEXP]
+----
+====
+
+A única diferença com o <> é o método `+__iter__+`, que aqui não é uma função geradora (ela não contém uma instrução `yield`) mas usa uma expressão geradora para criar um gerador e devolvê-lo. O resultado final é o mesmo: quem invoca `+__iter__+` recebe um objeto gerador.
+
+Expressões geradoras são "açúcar sintático": sempre podem ser substituídas por funções geradoras, mas às vezes são mais convenientes. A próxima seção trata do uso de expressões geradoras.((("", startref="Ilazy17")))((("", startref="Glazy17")))((("", startref="lazysen17")))
+
+
+=== Quando usar expressões geradoras
+
+Usei((("generators", "when to use generator expressions")))((("generator
+expressions (genexps)"))) expressões geradoras quando implementamos a
+classe `Vector` no <> do <>. Estes
+métodos contêm expressões geradoras: `+__eq__+`, `+__hash__+`, `+__abs__+`,
+`angle`, `angles`, `format`, `+__add__+`, e `+__mul__+`.
+Em todos eles, uma compreensão de
+lista também funcionaria, usando mais memória para armazenar os
+valores da lista intermediária.
+
+No <>, vimos que uma expressão geradora é um atalho sintático para
+criar um gerador sem definir e invocar uma função. Por outro lado, funções
+geradoras são mais flexíveis: podemos programar uma lógica complexa, com
+várias instruções, e podemos até usá-las como _corrotinas_, como veremos na
+<>.
+
+Nos casos mais simples, uma expressão geradora é mais fácil de ler de relance, como mostra o exemplo de `Vector`.
+
+Minha regra básica para escolher qual sintaxe usar é simples: se a expressão geradora exige mais que um par de linhas, prefiro escrever uma função geradora, em nome da legibilidade.
+
+[TIP]
+.Dica de sintaxe
+====
+
+Quando uma expressão geradora é passada como único argumento a uma função ou construtor,
+não é necessário escrever um par de parênteses para
+invocar função e outro par ao redor da expressão geradora.
+Um único par é
+suficiente, como na invocação do construtor `Vector` no método `+__mul__+` do
+<>, reproduzido abaixo:
+
+[source, python]
+----
+def __mul__(self, scalar):
+    if isinstance(scalar, numbers.Real):
+        return Vector(n * scalar for n in self)
+    else:
+        return NotImplemented
+----
+
+Entretanto, se a invocação exigir mais argumentos após a expressão
+geradora, é preciso cercá-la com parênteses para evitar um
+`SyntaxError`.
+
+====
+
+Os exemplos de `Sentence` vistos até aqui mostram geradores fazendo o papel do padrão _Iterator_ clássico: obter itens de uma coleção.
+Mas podemos também usar geradores para produzir valores sem acessar uma estrutura de dados.
+A próxima seção mostra um exemplo.
+
+Mas antes, uma pequena discussão sobre os conceitos sobrepostos de _iterador_  e _gerador_.
+
+.Comparando iteradores e geradores
+****
+
+Na((("iterators", "versus generators", secondary-sortas="generators")))((("generators", "versus iterators", secondary-sortas="iterators"))) documentação e na base de código oficiais de Python, a terminologia em torno de iteradores e geradores é inconsistente e está em evolução.
+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 laço `for` ou outro mecanismo de iteração, ou chamando `next(it)` explicitamente no iterador—apesar desse uso explícito incomum.
+    Na prática, a maioria dos iteradores que usamos no Python são _geradores_.
+
+gerador::
+    Um iterador criado pelo compilador do Python.
+    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__+`, 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://fpy.li/99[_Glossário de Python_]
+introduziu recentemente o termo
+https://fpy.li/9a[«iterador gerador»]
+para se referir a objetos geradores criados por funções geradoras,
+enquanto o verbete para
+https://fpy.li/9b[«expressão geradora»] diz que ela devolve um "iterador".
+
+Mas, para o interpretador Python,
+os objetos devolvidos em ambos os casos são objetos geradores:
+
+[source, python]
+----
+>>> def g():
+...     yield 0
+...
+>>> g()
+
+>>> ge = (c for c in 'XYZ')
+>>> ge
+ at 0x10e936ce0>
+>>> type(g()), type(ge)
+(, )
+----
+
+****
+
+=== Um gerador de progressão aritmética
+
+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 é útil 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 veremos 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 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 os valores gerados.
+
+[[ap_class_demo]]
+.Demonstração de uma classe `ArithmeticProgression`
+====
+[source, python]
+----
+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`.
+O <> mostra a implementação da classe `ArithmeticProgression`.
+
+[[ex_ap_class]]
+.A classe `ArithmeticProgression`
+====
+[source, python]
+----
+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> Esta 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»].]
+
+<4> Para melhorar a legibilidade, o sinalizador `forever` será `True` se o atributo `self.end` for `None`, resultando em uma série ilimitada.
+
+<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 laço `while` terminar.
+
+Na última linha do <>,
+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 revelam a diferença:
+
+[source, python]
+----
+>>> 100 * 1.1
+110.00000000000001
+>>> sum(1.1 for _ in range(100))
+109.99999999999982
+>>> 1000 * 1.1
+1100.0
+>>> sum(1.1 for _ in range(1000))
+1100.0000000000086
+----
+
+A classe `ArithmeticProgression` do <> 
+é outro exemplo do uso de uma função geradora para implementar o método especial `+__iter__+`.
+Agora, se o único objetivo de uma classe é criar um gerador pela implementação de `+__iter__+`,
+podemos substituir a classe inteira por uma função geradora.
+Afinal, uma função geradora é uma fábrica de geradores.
+
+O <> mostra uma função geradora chamada `aritprog_gen`, que
+realiza a mesma tarefa da `ArithmeticProgression`, mas com menos código. Se, em
+vez de chamar `ArithmeticProgression`, você chamar `aritprog_gen`, os testes no
+<> são todos bem-sucedidos.footnote:[O diretório
+__17-it-generator/__ no https://fpy.li/code[«repositório de código»] do _Python
+Fluente_ inclui doctests e um script, __aritprog_runner.py__, que roda os testes
+contra todas as variações dos scripts __aritprog*.py__.]
+
+
+[[ex_ap_genfunc1]]
+.a função geradora `aritprog_gen`
+====
+[source, python]
+----
+include::../code/17-it-generator/aritprog_v2.py[tags=ARITPROG_GENFUNC]
+----
+====
+
+O <> é elegante, mas lembre-se sempre: há muitos geradores prontos para uso na biblioteca padrão, e a próxima seção vai mostrar uma implementação mais curta, usando o módulo `itertools`.
+
+
+[[ap_itertools_sec]]
+==== Progressão aritmética com itertools
+
+O((("itertools module"))) módulo `itertools` no Python 3.10 contém 20 funções geradoras, que podem ser combinadas de várias maneiras interessantes.
+
+Por exemplo, a função `itertools.count` devolve um gerador que produz números. Sem argumentos, ele produz uma série de inteiros começando de `0`. Mas você pode fornecer os valores opcionais `start` e `step`, para obter um resultado similar ao das nossas funções `aritprog_gen`:
+
+[source, python]
+----
+>>> import itertools
+>>> gen = itertools.count(1, .5)
+>>> next(gen)
+1
+>>> next(gen)
+1.5
+>>> next(gen)
+2.0
+>>> next(gen)
+2.5
+----
+
+
+[WARNING]
+====
+`itertools.count` nunca para, então se você chamar `list(count())`, Python vai tentar criar uma `list` que preencheria todos os chips de memória já fabricados.
+Na prática, sua máquina vai ficar muito mal-humorada bem antes da chamada fracassar.
+====
+
+Por outro lado, temos também a função `itertools.takewhile`: ela devolve um
+gerador que consome outro gerador e para quando um predicado resulta falso.
+Então podemos combinar os dois e escrever o seguinte:
+
+[source, python]
+----
+>>> gen = itertools.takewhile(lambda n: n < 3, itertools.count(1, .5))
+>>> list(gen)
+[1, 1.5, 2.0, 2.5]
+----
+
+Aproveitando `takewhile` e `count`, o <> fica mais conciso.
+
+[[ex_almost_aritprog]]
+.aritprog_v3.py: funciona como as funções `aritprog_gen` anteriores
+====
+[source, python]
+----
+include::../code/17-it-generator/aritprog_v3.py[tags=ARITPROG_ITERTOOLS]
+----
+====
+
+Observe que  `aritprog_gen` no <> não é uma função geradora: não há um `yield` em seu corpo.
+Mas ela devolve um gerador, exatamente como faz uma função geradora.
+
+A lição do <> é:
+ao implementar geradores, veja 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")))
+
+
+[[stdlib_generators_sec]]
+=== Funções geradoras na biblioteca padrão
+
+A((("generators", "generator functions in Python standard library",
+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, agregados ou reordenados.
+
+==== Funções geradoras de seleção
+
+[[filter_fig_2]]
+.Funções geradoras de seleção criam geradores que selecionam itens conforme algum critério.
+image::../images/diag-filter.png[align="center",pdfwidth=8cm]
+
+((("filtering generator functions")))O primeiro grupo são funções geradoras de
+seleção: elas devolvem um gerador que produz um subconjunto dos itens do
+iterável de entrada, sem mudar os itens em si.
+
+O exemplo mais conhecido é a função embutida `filter`:
+
+`filter(predicate, it)`::
+    Aplica `predicate` para cada item de `iterable`, produzindo o item se
+    `predicate(item)` resulta 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 <>.
+
+<<<
+
+[[filter_genfunc_tbl]]
+.`itertools`: funções geradoras de seleção
+[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 dos geradores de seleção.
+
+[[demo_filter_genfunc]]
+.Exemplos de funções geradoras de seleção
+====
+[source, python]
+----
+>>> def vowel(c):
+...     return c.lower() in 'aeiou'
+...
+>>> list(filter(vowel, 'Aardvark'))
+['A', 'a', 'a']
+>>> import itertools
+>>> list(itertools.filterfalse(vowel, 'Aardvark'))
+['r', 'd', 'v', 'r', 'k']
+>>> list(itertools.dropwhile(vowel, 'Aardvark'))
+['r', 'd', 'v', 'a', 'r', 'k']
+>>> list(itertools.takewhile(vowel, 'Aardvark'))
+['A', 'a']
+>>> list(itertools.compress('Aardvark', (1, 0, 1, 1, 0, 1)))
+['A', 'r', 'd', 'a']
+>>> list(itertools.islice('Aardvark', 4))
+['A', 'a', 'r', 'd']
+>>> list(itertools.islice('Aardvark', 4, 7))
+['v', 'a', 'r']
+>>> list(itertools.islice('Aardvark', 1, 7, 2))
+['a', 'd', 'a']
+----
+====
+
+==== Funções geradoras de transformação
+
+[[map_fig_2]]
+.Funções geradoras de transformação criam geradores que aplicam uma função aos itens da entrada, produzindo itens transformados.
+image::../images/diag-map.png[align="center",pdfwidth=8cm]
+
+O((("mappings", "mapping generator functions"))) grupo seguinte contém os
+geradores de transformação. Estes geradores 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 esgotado.
+
+[[mapping_genfunc_tbl]]
+.Funções geradoras de transformação (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 transformação:
+
+[[mapping_genfunc_itertools_tbl]]
+.`itertools`: funções geradoras de transformação
+[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`.
+
+[[demo_accumulate_genfunc]]
+.Exemplos das funções geradoras de `itertools.accumulate`
+====
+[source, python]
+----
+>>> 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>
+[5, 4, 2, 2, 2, 2, 2, 0, 0, 0]
+>>> list(itertools.accumulate(sample, max))  # <3>
+[5, 5, 5, 8, 8, 8, 8, 8, 9, 9]
+>>> import operator
+>>> list(itertools.accumulate(sample, operator.mul))  # <4>
+[5, 20, 40, 320, 2240, 13440, 40320, 0, 0, 0]
+>>> list(itertools.accumulate(range(1, 11), operator.mul))
+[1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]  # <5>
+----
+====
+<1> Soma acumulada.
+<2> Mínimo corrente.
+<3> Máximo corrente.
+<4> Produto acumulado.
+<5> Fatoriais de `1!` a `10!`.
+
+As demais funções de transformação são demonstradas no <>.
+
+[[demo_mapping_genfunc]]
+.Exemplos de funções geradoras de transformação
+====
+[source, python]
+----
+>>> list(enumerate('albatroz', 1))  # <1>
+[(1, 'a'), (2, 'l'), (3, 'b'), (4, 'a'), (5, 't'), (6, 'r'), (7, 'o'), (8, 'z')]
+>>> import operator
+>>> list(map(operator.mul, range(11), range(11)))  # <2>
+[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
+>>> list(map(operator.mul, range(11), [2, 4, 8]))  # <3>
+[0, 4, 16]
+>>> list(map(lambda a, b: (a, b), range(11), [2, 4, 8]))  # <4>
+[(0, 2), (1, 4), (2, 8)]
+>>> import itertools
+>>> list(itertools.starmap(operator.mul, enumerate('albatroz', 1)))  # <5>
+['a', 'll', 'bbb', 'aaaa', 'ttttt', 'rrrrrr', 'ooooooo', 'zzzzzzzz']
+>>> sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
+>>> list(itertools.starmap(lambda a, b: b / a,
+...     enumerate(itertools.accumulate(sample), 1)))  # <6>
+[5.0, 4.5, 3.6666666666666665, 4.75, 5.2, 5.333333333333333,
+5.0, 4.375, 4.888888888888889, 4.5]
+----
+====
+<1> Número de letras na palavra, começando por `1`.
+<2> Os quadrados dos inteiros de `0` a `10`.
+<3> Multiplicando os números de dois iteráveis em paralelo; os resultados cessam quando o iterável menor termina.
+<4> Isso é o que faz a função embutida `zip`.
+<5> Repete cada letra na palavra de acordo com a posição da letra na palavra, começando por `1`.
+<6> Média corrente.
+
+
+==== Funções geradoras de mesclagem
+
+A seguir temos o grupo de geradores de mesclagem (_merge_).
+Elas mesclam os itens de vários iteráveis de entrada.
+
+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 é esgotado, 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`.
+
+[[merging_genfunc_tbl]]
+.`itertools`: funções geradoras que mesclam os iteráveis de entrada
+[options="header", cols="7,13"]
+|===================
+|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 esgotado,  preenchendo os itens ausentes com o `fillvalue`
+|===================
+
+O <> demonstra o uso destas funções geradoras. 
+Lembre-se de que o nome `zip` refere-se ao zíper (ou fecho-éclair) e não tem relação com a compressão de dados.
+Tanto `zip` quanto `itertools.zip_longest` foram apresentadas no <>.
+
+
+[[demo_merging_genfunc]]
+.Exemplos de funções geradoras de fusão
+====
+[source, python]
+----
+>>> list(itertools.chain('ABC', range(2)))  # <1>
+['A', 'B', 'C', 0, 1]
+>>> list(itertools.chain(enumerate('ABC')))  # <2>
+[(0, 'A'), (1, 'B'), (2, 'C')]
+>>> list(itertools.chain.from_iterable(enumerate('ABC')))  # <3>
+[0, 'A', 1, 'B', 2, 'C']
+>>> list(zip('ABC', range(5), [10, 20, 30, 40]))  # <4>
+[('A', 0, 10), ('B', 1, 20), ('C', 2, 30)]
+>>> list(itertools.zip_longest('ABC', range(5)))  # <5>
+[('A', 0), ('B', 1), ('C', 2), (None, 3), (None, 4)]
+>>> list(itertools.zip_longest('ABC', range(5), fillvalue='?'))  # <6>
+[('A', 0), ('B', 1), ('C', 2), ('?', 3), ('?', 4)]
+----
+====
+[role="pagebreak-before less_space"]
+<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 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 até o fim, preenchendo as tuplas de saída com `None` nas posições correspondentes aos iteráveis esgotados antes do iterável mais longo.
+<6> O argumento nomeado `fillvalue` especifica um valor de preenchimento customizado.
+
+O gerador `itertools.product` é uma forma preguiçosa de gerar produtos cartesianos.
+Na <>, criamos produtos cartesianos de modo ávido
+usando compreensões de lista com mais de uma instrução `for`.
+Expressões geradoras com várias 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`
+====
+[source, python]
+----
+>>> list(itertools.product('ABC', range(2)))  # <1>
+[('A', 0), ('A', 1), ('B', 0), ('B', 1), ('C', 0), ('C', 1)]
+>>> suits = 'spades hearts diamonds clubs'.split()
+>>> list(itertools.product('AK', suits))  # <2>
+[('A', 'spades'), ('A', 'hearts'), ('A', 'diamonds'), ('A', 'clubs'),
+('K', 'spades'), ('K', 'hearts'), ('K', 'diamonds'), ('K', 'clubs')]
+>>> list(itertools.product('ABC'))  # <3>
+[('A',), ('B',), ('C',)]
+>>> list(itertools.product('ABC', repeat=2))  # <4>
+[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'B'),
+('B', 'C'), ('C', 'A'), ('C', 'B'), ('C', 'C')]
+>>> list(itertools.product(range(2), repeat=3))
+[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0),
+(1, 0, 1), (1, 1, 0), (1, 1, 1)]
+>>> rows = itertools.product('AB', range(2), repeat=2)
+>>> for row in rows: print(row)
+...
+('A', 0, 'A', 0)
+('A', 0, 'A', 1)
+('A', 0, 'B', 0)
+('A', 0, 'B', 1)
+('A', 1, 'A', 0)
+('A', 1, 'A', 1)
+('A', 1, 'B', 0)
+('A', 1, 'B', 1)
+('B', 0, 'A', 0)
+('B', 0, 'A', 1)
+('B', 0, 'B', 0)
+('B', 0, 'B', 1)
+('B', 1, 'A', 0)
+('B', 1, 'A', 1)
+('B', 1, 'B', 0)
+('B', 1, 'B', 1)
+----
+====
+<1> O produto cartesiano de uma `str` com três caracteres e um `range` com dois inteiros produz seis tuplas (porque `3 * 2` é `6`).
+<2> O produto de duas cartas altas (`'AK'`) e quatro naipes é uma série de oito tuplas.
+<3> Dado um único iterável, `product` produz uma série de tuplas de um elemento—muito pouco útil.
+<4> O argumento nomeado `repeat=N` diz à função para consumir cada iterável de entrada `N` vezes.
+
+Outras((("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]]
+.`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 com `out_len` itens a partir dos itens produzidos por `it`
+|`combinations_with_replacement( +
+it, out_len)`|Produz combinações com `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`, somando `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, indefinidamente
+|`pairwise(it)`|Produz pares sobrepostos sucessivos, obtidos do iterável de entrada (novidade do 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 item repetidamente, indefinidamente, a menos que um número de `times` (vezes) seja especificado
+|====================
+
+As funções `count` e `repeat` de `itertools` devolvem geradores que conjuram itens do nada:
+elas não consomem itens de um iterável de entrada.
+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`.
+
+[[demo_count_repeat_genfunc]]
+.`count`, `cycle`, `pairwise`, e `repeat`
+====
+[source, python]
+----
+>>> ct = itertools.count()  # <1>
+>>> next(ct)  # <2>
+0
+>>> next(ct), next(ct), next(ct)  # <3>
+(1, 2, 3)
+>>> list(itertools.islice(itertools.count(1, .3), 3))  # <4>
+[1, 1.3, 1.6]
+>>> cy = itertools.cycle('ABC')  # <5>
+>>> next(cy)
+'A'
+>>> list(itertools.islice(cy, 7))  # <6>
+['B', 'C', 'A', 'B', 'C', 'A', 'B']
+>>> list(itertools.pairwise(range(7)))  # <7>
+[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
+>>> rp = itertools.repeat(7)  # <8>
+>>> next(rp), next(rp)
+(7, 7)
+>>> list(itertools.repeat(8, 4))  # <9>
+[8, 8, 8, 8]
+>>> list(map(operator.mul, range(11), itertools.repeat(5)))  # <10>
+[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
+----
+====
+<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 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 um gerador `repeat` que 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`.
+
+As funções geradoras `combinations`, `combinations_with_replacement` e
+`permutations`--juntamente com  `product`—são chamadas _geradoras combinatórias_
+na https://fpy.li/9c[«documentação do `itertools`»]. Também há uma
+relação muito próxima entre `itertools.product` e o restante das funções
+_combinatórias_, como mostra o <>.
+
+[[demo_conbinatoric_genfunc]]
+.Funções geradoras combinatórias produzem múltiplos valores para cada item de entrada
+====
+[source, python]
+----
+>>> list(itertools.combinations('ABC', 2))  # <1>
+[('A', 'B'), ('A', 'C'), ('B', 'C')]
+>>> list(itertools.combinations_with_replacement('ABC', 2))  # <2>
+[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'B'), ('B', 'C'), ('C', 'C')]
+>>> list(itertools.permutations('ABC', 2))  # <3>
+[('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
+>>> list(itertools.product('ABC', repeat=2))  # <4>
+[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'B'), ('B', 'C'),
+('C', 'A'), ('C', 'B'), ('C', 'C')]
+----
+====
+<1> Todas as combinações com `len()==2` a partir dos itens em `'ABC'`; a ordem dos itens nas tuplas geradas é irrelevante (elas poderiam ser conjuntos).
+<2> Todas as combinações com `len()==2` a partir dos itens em `'ABC'`, incluindo combinações com itens repetidos.
+<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`).
+
+==== Funções geradoras de rearranjo
+
+As últimas funções geradoras que vamos examinar nessa seção
+produzem 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 único gerador tratado neste capítulo que não
+aceita qualquer iterável como entrada, apenas sequências. Faz sentido: como
+`reversed` 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.
+
+[[expanding_genfunc_tbl2]]
+.Funções geradoras de rearranjo
+[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
+|=====================
+
+O <> demonstra o uso de `itertools.groupby` e da função embutida `reversed`. Observe que `itertools.groupby` assume que o iterável de entrada está ordenado pelo critério de agrupamento, ou que pelo menos os itens estejam agrupados por aquele critério—mesmo que não estejam completamente ordenados.
+
+[[groupby_fig_2]]
+.A função geradora `groupby` produz vários geradores, agrupando os itens da entrada segundo algum critério.
+image::../images/diag-groupby.png[align="center",pdfwidth=8cm]
+
+O revisor técnico Miroslav Šedivý sugeriu esse caso de uso:
+você pode ordenar objetos `datetime` em ordem cronológica, e então `groupby` por dia da semana, para obter o grupo com os dados de segunda-feira, seguidos pelos dados de terça, etc., e então da segunda (da semana seguinte) novamente, e assim por diante.
+
+[[demo_groupby_reversed_genfunc]]
+.`itertools.groupby`
+====
+[source, python]
+----
+>>> list(itertools.groupby('LLLLAAGGG'))  # <1>
+[('L', ),
+('A', ),
+('G', )]
+>>> for char, group in itertools.groupby('LLLLAAAGG'):  # <2>
+...     print(char, '->', list(group))
+...
+L -> ['L', 'L', 'L', 'L']
+A -> ['A', 'A',]
+G -> ['G', 'G', 'G']
+>>> animals = ['duck', 'eagle', 'rat', 'giraffe', 'bear',
+...            'bat', 'dolphin', 'shark', 'lion']
+>>> animals.sort(key=len)  # <3>
+>>> animals
+['rat', 'bat', 'duck', 'bear', 'lion', 'eagle', 'shark',
+'giraffe', 'dolphin']
+>>> for length, group in itertools.groupby(animals, len):  # <4>
+...     print(length, '->', list(group))
+...
+3 -> ['rat', 'bat']
+4 -> ['duck', 'bear', 'lion']
+5 -> ['eagle', 'shark']
+7 -> ['giraffe', 'dolphin']
+>>> for length, group in itertools.groupby(reversed(animals), len): # <5>
+...     print(length, '->', list(group))
+...
+7 -> ['dolphin', 'giraffe']
+5 -> ['shark', 'eagle']
+4 -> ['lion', 'bear', 'duck']
+3 -> ['bat', 'rat']
+>>>
+----
+====
+<1> `groupby` produz tuplas de `(key, group_generator)`.
+<2> Tratar geradores `groupby` envolve iteração aninhada: neste caso, o laço `for` externo e o construtor de `list` interno.
+<3> Ordena `animals` pelo tamanho de cada string.
+<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.
+Estes geradores podem ser consumidos de forma independente, como mostra o
+<>.
+
+[[demo_tee_genfunc]]
+.`itertools.tee` produz múltiplos geradores, cada um produzindo todos os itens do gerador de entrada
+====
+[source, python]
+----
+>>> list(itertools.tee('ABC'))
+[, ]
+>>> g1, g2 = itertools.tee('ABC')
+>>> next(g1)
+'A'
+>>> next(g2)
+'A'
+>>> next(g2)
+'B'
+>>> list(g1)
+['B', 'C']
+>>> list(g2)
+['C']
+>>> list(zip(*itertools.tee('ABC')))
+[('A', 'A'), ('B', 'B'), ('C', 'C')]
+----
+====
+
+Observe que vários exemplos nesta seção usam combinações de funções geradoras.
+Esta é uma característica poderosa destas funções:
+como recebem geradores como argumentos e devolvem geradores como resultado,
+elas podem ser combinadas de muitas formas diferentes.
+
+Vamos agora revisar outro grupo de funções da biblioteca padrão que lidam com iteráveis.((("", startref="Ggenfunc17")))
+
+[[iterable_reducing_sec]]
+=== Funções de redução de iteráveis
+
+[[reduce_fig_2]]
+.A função `reduce` aplica uma função aos sucessivos itens da entrada, gerando um único resultado acumulado.
+image::../images/diag-reduce.png[align="center",pdfwidth=8cm]
+
+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".
+
+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 ao 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 simplificarem os casos de uso mais 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` implementam o retorno por 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", 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
+|`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
+|`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`.
+
+[[all_any_demo]]
+.Resultados de `all` e `any` para algumas sequências
+====
+[source, python]
+----
+>>> all([1, 2, 3])
+True
+>>> all([1, 0, 3])
+False
+>>> all([])
+True
+>>> any([1, 2, 3])
+True
+>>> any([1, 0, 3])
+True
+>>> any([0, 0.0])
+False
+>>> any([])
+False
+>>> g = (n for n in [0, 0.0, 7, 8])
+>>> any(g)  # <1>
+True
+>>> next(g)  # <2>
+8
+----
+====
+<1> `any` iterou sobre `g` até `g` produzir `7`; neste momento `any` parou e devolveu `True`.
+<2> É por isso que `8` ainda restava.
+
+Outra função embutida que recebe um iterável e devolve outra coisa é `sorted`.
+Diferente de `reversed`, que é uma função geradora, `sorted` cria e devolve uma nova `list`.
+Afinal, cada um dos itens no iterável de entrada precisa ser lido para que todos possam ser ordenados, e a ordenação acontece em uma `list`; `sorted` então apenas devolve aquela `list` após terminar seu processamento.
+Menciono `sorted` aqui porque ela consome um iterável arbitrário.
+
+Claro, `sorted` e as funções de redução só funcionam com iteráveis que terminam.
+Caso contrário, eles seguirão consumindo itens e nunca devolverão um resultado.
+
+[NOTE]
+====
+Se você chegou até aqui, já viu o conteúdo mais importante e útil deste capítulo.
+As seções restantes tratam de recursos avançados de geradores,
+que a maioria de nós não vê ou precisa com muita frequência,
+tal como a instrução `yield from` e as corrotinas clássicas.
+
+Há também seções sobre dicas de tipo para iteráveis, iteradores e corrotinas clássicas.
+====
+
+A sintaxe `yield from` fornece uma nova forma de combinar geradores. É nosso próximo assunto.((("", startref="Greduc17")))((("", startref="Ireduc17")))((("", startref="redfunc17")))
+
+
+[[yield_from_sec0]]
+=== Subgeradores com `yield from`
+
+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 laço `for` quando um gerador precisava produzir valores de outro gerador:
+
+[source, python]
+----
+>>> def sub_gen():
+...     yield 1.1
+...     yield 1.2
+...
+>>> def gen():
+...     yield 1
+...     for i in sub_gen():
+...         yield i
+...     yield 2
+...
+>>> for x in gen():
+...     print(x)
+...
+1
+1.1
+1.2
+2
+----
+
+Podemos obter o mesmo resultado usando `yield from`, como se vê no <>.
+
+[[ex_simple_yield_from]]
+.Experimentando `yield from`
+====
+[source, python]
+----
+>>> def sub_gen():
+...     yield 1.1
+...     yield 1.2
+...
+>>> def gen():
+...     yield 1
+...     yield from sub_gen()
+...     yield 2
+...
+>>> for x in gen():
+...     print(x)
+...
+1
+1.1
+1.2
+2
+----
+====
+
+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 esgotar.
+Os valores produzidos por `sub_gen` passam através de `gen` diretamente para o laço `for` do cliente.
+Enquanto isso, `gen` está suspenso e não pode ver os valores que passam por ele.
+`gen` continua apenas quando `sub_gen` termina.
+
+Quando o subgerador contém uma instrução `return` com um valor, aquele valor pode ser capturado pelo gerador delegante, com o uso de `yield from` como parte de uma expressão.
+Veja a demonstração no <>.
+
+[[ex_simple_yield_from_return]]
+.`yield from` recebe o valor devolvido pelo subgerador
+====
+[source, python]
+----
+>>> def sub_gen():
+...     yield 1.1
+...     yield 1.2
+...     return 'Done!'
+...
+>>> def gen():
+...     yield 1
+...     result = yield from sub_gen()
+...     print('<--', result)
+...     yield 2
+...
+>>> for x in gen():
+...     print(x)
+...
+1
+1.1
+1.2
+<-- Done!
+2
+----
+====
+
+Agora que já vimos o básico sobre `yield from`, vamos estudar alguns exemplos simples mas práticos de sua utilização.
+
+[[reinventing_chain_sec]]
+==== Reinventando `chain`
+
+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.
+A maioria das funções de `itertools` são escritas em C, incluindo `chain`.
+Abaixo está uma implementação caseira de `chain`, com laços `for`
+aninhados, em Python:
+
+[source, python]
+----
+>>> def chain(*iterables):
+...     for it in iterables:
+...         for i in it:
+...             yield i
+...
+>>> s = 'ABC'
+>>> r = range(3)
+>>> list(chain(s, r))
+['A', 'B', 'C', 0, 1, 2]
+----
+
+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]
+----
+>>> def chain(*iterables):
+...     for i in iterables:
+...         yield from i
+...
+>>> list(chain(s, t))
+['A', 'B', 'C', 0, 1, 2]
+----
+
+O uso de `yield from` neste exemplo está correto, e o código é mais legível, mas parece açúcar sintático, com pouco ganho real.
+Vamos então desenvolver um exemplo mais interessante.
+
+
+[[traversing_tree_sec]]
+==== Percorrendo uma árvore
+
+Nessa((("tree structures, traversing", id="treetravers17"))) seção, veremos `yield from` em um script para percorrer uma estrutura de árvore.
+Vou desenvolvê-lo passo a passo, em _baby steps_ (passinhos de bebê).
+
+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 passinho será exibir
+o nível zero.
+
+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
+====
+[source, python]
+----
+include::../code/17-it-generator/tree/step0/tree.py[]
+----
+====
+
+A saída do <> tem apenas uma linha:
+
+[source]
+----
+BaseException
+----
+
+O próximo pequeno passo nos leva ao nível 1.
+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:
+
+[source]
+----
+$ python3 tree.py
+BaseException
+    Exception
+    GeneratorExit
+    SystemExit
+    KeyboardInterrupt
+----
+
+O <> produz a saída acima.
+
+[[ex_tree_step1]]
+.tree/step1/tree.py: produz o nome da classe raiz e das subclasses diretas
+====
+[source, python]
+----
+include::../code/17-it-generator/tree/step1/tree.py[]
+----
+====
+<1> Para suportar a saída indentada, produz o nome da classe e seu nível na hierarquia.
+<2> Usa o método especial `+__subclasses__+` para obter uma lista de subclasses.
+<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 classe raiz, processando as subclasses 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 então delega para `sub_tree`
+====
+[source, python]
+----
+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 ao gerador `tree` ...
+<3> ... e são recebidos aqui diretamente.
+
+Seguindo o método de _baby steps_, apresento o código mais simples que consigo imaginar para chegar ao nível 2.
+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
+====
+[source, python]
+----
+include::../code/17-it-generator/tree/step3/tree.py[]
+----
+====
+
+Este é o resultado da execução de _step3/tree.py_, do <>:
+
+[source]
+----
+$ python3 tree.py
+BaseException
+    Exception
+        TypeError
+        StopAsyncIteration
+        StopIteration
+        ImportError
+        OSError
+        EOFError
+        RuntimeError
+        NameError
+        AttributeError
+        SyntaxError
+        LookupError
+        ValueError
+        AssertionError
+        ArithmeticError
+        SystemError
+        ReferenceError
+        MemoryError
+        BufferError
+        Warning
+    GeneratorExit
+    SystemExit
+    KeyboardInterrupt
+----
+
+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 laço `for` aninhado.
+Não há novidades no resto do programa, então o <> mostra apenas o gerador `sub_tree`.
+
+[[ex_tree_step4]]
+.O gerador `sub_tree` de _tree/step4/tree.py_
+====
+[source, python]
+----
+include::../code/17-it-generator/tree/step4/tree.py[tags=SUB_TREE]
+----
+====
+
+Há um padrão claro no <>.
+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 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 <>.
+
+[[ex_tree_step5]]
+.tree/step5/tree.py: a `sub_tree` recursiva vai tão longe quanto a memória permitir
+====
+[source, python]
+----
+include::../code/17-it-generator/tree/step5/tree.py[]
+----
+====
+
+O <> pode percorrer árvores de qualquer profundidade,
+limitado apenas pelo limite de recursão de Python.
+O limite default permite 1.000 funções pendentes.
+
+Qualquer bom tutorial sobre recursão enfatizará a importância de ter um caso base,
+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 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.
+
+O <> funciona como planejado,
+mas podemos fazê-lo mais conciso recordando o padrão que observamos quando
+alcançamos o nível 3 (no <>):
+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 laço aninhado por `yield from`.
+Agora podemos fundir `tree` e `sub_tree` em um único gerador.
+O <> é o último passo deste exemplo.
+
+[[ex_tree_step6]]
+.tree/step6/tree.py: chamadas recursivas de `tree` passam um argumento `level` incrementado
+====
+[source, python]
+----
+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 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")))
+
+[[generic_iterable_types_sec]]
+=== Tipos iteráveis genéricos
+
+A((("generators", "generic iterable types")))((("iterators", "generic iterable types"))) biblioteca padrão de Python contém muitas funções que aceitam argumentos iteráveis.
+Em seu código, tais funções podem ser anotadas como a função `zip_replace`, vista no <>,
+usando `collections.abc.Iterable` (ou `typing.Iterable`, se você precisa suportar Python 3.8 ou anterior, como explicado no <>). Veja o <>.
+
+[[replacer_iterable_ex]]
+.replacer.py devolve um iterador de tuplas de strings
+====
+[source, python]
+----
+include::../code/08-def-type-hints/replacer.py[tags=ZIP_REPLACE]
+----
+====
+<1> Define um apelido (_alias_) de tipo; isso não é obrigatório, mas torna a próxima dica de tipo mais legível.
+Desde o Python 3.10, a variável `FromTo` deve ter uma dica de tipo `typing.TypeAlias`, para esclarecer a razão para esta atribuição: +
+`FromTo: TypeAlias = tuple[str, str]`
+<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 o famoso gerador de Fibonacci, anotado.
+
+[[fibo_gen_annot_ex]]
+._fibo_gen.py_: `fibonacci` devolve um gerador de inteiros
+====
+[source, python]
+----
+include::../code/17-it-generator/fibo_gen.py[]
+----
+====
+
+Observe que o tipo `Iterator` é usado para geradores criados como funções com `yield`,
+bem como para iteradores escritos "à 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 redundante 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`.
+
+[[iter_gen_type_ex]]
+.itergentype.py: duas formas de anotar iteradores
+====
+[source, python]
+----
+include::../code/17-it-generator/iter_gen_type.py[]
+----
+====
+<1> Uma expressão geradora que produz palavras reservadas de Python com menos de `5` caracteres.
+<2> O Mypy infere: `typing.Generator[builtins.str*, None, None]`.footnote:[Na versão 0.910, a versão mais recente disponível quando escrevi este capítulo), o Mypy ainda utiliza os tipos descontinuados de `typing`.]
+<3> Isso também produz strings, mas acrescentei uma dica de tipo explícita.
+<4> Tipo revelado: `typing.Iterator[builtins.str]`.
+
+`abc.Iterator[str]` é _consistente-com_ `abc.Generator[str, None, None]`,
+assim o Mypy não reporta erros na checagem de tipos no <>.
+
+`Iterator[T]` é um atalho para `Generator[T, None, None]`.
+Ambas as anotações significam "um gerador que produz itens do tipo `T`, mas não consome ou devolve valores."
+Geradores capazes de consumir e devolver valores são corrotinas, nosso próximo tópico.
+
+
+[[classic_coroutines_sec]]
+=== Corrotinas clássicas
+
+[NOTE]
+====
+
+A https://fpy.li/pep342[_PEP 342—Coroutines via Enhanced Generators_]
+(Corrotinas via geradores aprimorados) introduziu o método `.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
+use uma terminologia inconsistente para se referir a geradores usados como
+corrotinas, obrigando-me a adotar o qualificador "corrotina clássica", para
+diferenciá-las das novas "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 pela biblioteca `asyncio`.
+
+====
+
+Entender as corrotinas clássicas((("coroutines", "understanding classic"))) no
+Python é mais confuso porque elas são, na verdade, geradores usados 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. Quando usadas como um
+registro, espera-se que uma tupla tenha um número fixo de itens, e cada
+item pode ter um tipo diferente. Quando usadas como listas imutáveis, uma tupla
+pode ter qualquer tamanho, e espera-se que todos os itens sejam do mesmo tipo.
+Por isso, há duas formas de anotar tuplas com dicas de tipo:
+
+[source, python]
+----
+# Um registro de cidade, como nome, país e população:
+city: tuple[str, str, int]
+
+# Uma sequência imutável de nomes de domínios:
+domains: tuple[str, ...]
+----
+
+Algo similar ocorre com geradores.
+Eles são normalmente usadas como iteradores, mas podem também ser usados 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 geradores e corrotinas em Python são tão diferentes que há duas formas de escrever dicas de tipo para elas:
+
+[source, python]
+----
+# A variável `readings` pode ser vinculada a um iterador
+# ou a um objeto gerador que produz itens `float`:
+readings: Iterator[float]
+
+# A variável `sim_taxi` pode ser vinculada a uma corrotina
+# representando um táxi em uma simulação de eventos discretos.
+# Ela produz eventos, recebe um `float` de data/hora, e devolve
+# o número de viagens realizadas durante a simulação:
+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 geradores são mais frequentemente usados como iteradores.
+
+A
+https://fpy.li/9f[«documentação do módulo `typing`»]
+descreve assim os parâmetros de tipo formais de `Generator`:
+
+[source, python]
+----
+Generator[YieldType, SendType, ReturnType]
+----
+
+O `SendType` só é relevante quando o gerador é usado como uma corrotina.
+Aquele parâmetro é o tipo de `x` na chamada `gen.send(x)`.
+É 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 fazem as funções comuns.
+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)`.
+
+O tipo `Generator` tem os mesmos parâmetros de tipo de
+https://fpy.li/typecoro[`typing.Coroutine`]:
+
+[source, python]
+----
+Coroutine[YieldType, SendType, ReturnType]
+----
+
+A
+https://fpy.li/typecoro[documentação de `typing.Coroutine`]
+diz literalmente:
+"A variância e a ordem das variáveis de tipo correspondem às de `Generator`."
+Mas `typing.Coroutine` (descontinuada)
+e `collections.abc.Coroutine` (genérica a partir de Python 3.9)
+foram projetadas para anotar apenas corrotinas nativas, e não corrotinas clássicas.
+Se você quiser usar dicas de tipo em corrotinas clássicas,
+terá que anotá-las como
+`Generator[YieldType, SendType, ReturnType]`.
+
+David Beazley criou algumas das melhores palestras e algumas das oficinas mais abrangentes sobre corrotinas clássicas.
+No https://fpy.li/17-18[material de seu curso na PyCon 2009] há um slide chamado
+_Keeping It Straight_ ("Cada coisa em seu lugar"), onde se lê:
+
+____
+* Geradoras produzem dados para iteração
+* Corrotinas são consumidoras de dados
+* Para não explodir seu cérebro, não misture os dois conceitos
+* Corrotinas não têm relação com iteração
+* Nota: Há uma forma de fazer `yield` produzir um valor em uma corrotina,
+mas isso não está ligado à iteração.footnote:[Slide 33, _Keeping It Straight_ em
+https://fpy.li/17-18[_A Curious Course on Coroutines and Concurrency_]
+(Um Curioso Curso sobre Corrotinas e Concorrência).]
+____
+
+
+Vamos ver agora como as corrotinas clássicas funcionam.
+
+
+==== Exemplo: Corrotina para computar uma média móvel
+
+Quando((("coroutines", "computing running averages", id="CRavg17")))((("running averages, computing",
+id="runavg17")))((("averages, computing", id="avg17")))
+discutimos clausuras no <>, estudamos objetos para
+computar uma média móvel. O <> mostra uma classe e o
+<> apresenta uma função de ordem superior devolvendo uma
+função que preserva as variáveis `total` e `count` entre invocações, em uma
+clausura. O <> mostra como fazer o mesmo com uma
+corrotina.footnote:[Este exemplo foi inspirado por um trecho enviado por Jacob
+Holm à lista Python-ideas, em uma mensagem intitulada
+https://fpy.li/17-20[_Yield-From: Finalization guarantees_] (Yield-From:
+Garantias de finalização). Algumas variantes aparecem mais tarde na mesma
+thread, e Holm dá mais explicações na
+https://fpy.li/17-21[«mensagem 003912»].]
+
+[[ex_coroaverager]]
+.coroaverager.py: corrotina para computar uma média móvel
+====
+[source, python]
+----
+include::../code/17-it-generator/coroaverager.py[tags=CORO_AVERAGER]
+----
+====
+
+<1> Esta função devolve um gerador que produz valores `float`, aceita valores
+`float` via `.send()`, e não devolve um valor útil ao retornar.footnote:[Na
+verdade, ela nunca retorna, a menos que uma exceção interrompa o laço. O Mypy
+0.910 aceita tanto `None` quanto `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 analisar completamente o
+código da corrotina, pelo menos em sua versão 0.910.]
+
+<2> Este laço infinito significa que a corrotina continuará produzindo médias
+enquanto o código cliente enviar valores.
+
+<3> A instrução `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:
+não precisamos usar atributos de uma instância ou uma clausura para preservar o contexto
+enquanto a corrotina está suspensa, esperando pelo próximo `.send()`.
+Por isso as corrotinas são substitutas atraentes para _callbacks_
+em programação assíncrona—elas preservam o estado local entre ativações.
+
+O <> executa doctests mostrando a corrotina `averager` em operação.
+
+[[ex_coroaverager_test]]
+.coroaverager.py: doctest para a corrotina de média móvel do <>
+====
+[source, python]
+----
+include::../code/17-it-generator/coroaverager.py[tags=CORO_AVERAGER_TEST]
+----
+====
+<1> Cria o objeto corrotina.
+<2> Inicializa a corrotina. Isso produz o valor inicial de `average`: 0.0.
+<3> Agora estamos conversando: cada chamada a `.send()` produz a média atual.
+
+No <>, a chamada `next(coro_avg)` faz a corrotina avançar até o `yield`, produzindo o valor inicial de `average`.
+Também é possível inicializar a corrotina chamando `coro_avg.send(None)`—na verdade é isso que a função embutida `next()` faz.
+Mas você não pode enviar qualquer valor diferente de `None`,
+pois a corrotina só pode aceitar um valor enviado quando está suspensa, em uma linha de `yield`.
+Invocar `next()` ou `.send(None)` para avançar até o primeiro `yield` é conhecido como 
+_priming the coroutine_ (preparando a corrotina).
+
+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 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 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]]
+.coroaverager.py: continuando de <>
+====
+[source, python]
+----
+include::../code/17-it-generator/coroaverager.py[tags=CORO_AVERAGER_TEST_CONT]
+----
+====
+<1> `coro_avg` é a instância criada no <>.
+<2> O método `.close()` gera uma exceção `GeneratorExit` na expressão `yield` suspensa.
+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 levanta `StopIteration`.
+
+Além do método `.send()`, a
+https://fpy.li/pep342[_PEP 342—Coroutines via Enhanced Generators_]
+(Corrotinas via geradores aprimorados) também criou 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]]
+==== Devolvendo um valor a partir de uma corrotina
+
+Vamos((("coroutines", "returning values from", id="Cvalue17"))) agora estudar outra corrotina para computar uma média.
+Esta versão não produzirá resultados parciais.
+Em vez disso, ela devolve uma tupla com o número de termos e a média.
+Dividi a listagem em duas partes, no <> e no <>.
+
+[[ex_returning_averager_top]]
+.coroaverager2.py: a primeira parte do arquivo
+====
+[source, python]
+----
+include::../code/17-it-generator/coroaverager2.py[tags=RETURNING_AVERAGER_TOP]
+----
+====
+
+<1> A corrotina `averager2` no <> vai devolver uma
+instância de `Result`.
+
+<2> `Result` é, na verdade, uma subclasse de `tuple`, que tem um método
+`.count()`, que não preciso neste exemplo. O comentário `# type: ignore` evita que o Mypy
+reclame sobre a redeclaração do atributo `count`.footnote:[Considerei renomear o
+atributo, mas `count` é o melhor nome para a variável local na corrotina, e é o
+nome que usei para essa variável em exemplos similares ao longo do livro, então
+faz sentido usar o mesmo nome no atributo de `Result`. Não hesito em usar `# type:
+ignore` para evitar as limitações e os aborrecimentos de checadores de tipos
+estáticos, quando se submeter à ferramenta tornaria o código pior ou
+desnecessariamente complicado.]
+
+<3> Uma classe para criar um valor sentinela com um `+__repr__+` legível.
+
+<4> O valor sentinela que vou usar para fazer a corrotina parar de coletar dados
+e devolver um resultado.
+
+<5> Vou usar esse apelido de tipo para o segundo parâmetro de tipo devolvido
+pela corrotina `Generator`, o parâmetro `SendType`.
+
+A definição de `SendType` também funciona no Python 3.10 mas, se não for
+necessário suportar versões mais antigas, é melhor escrever a anotação assim,
+após importar `TypeAlias` de `typing`:
+
+[source, python]
+----
+SendType: TypeAlias = float | Sentinel
+----
+
+Usar `|` em vez de `typing.Union` é tão conciso e legível que eu provavelmente não criaria aquele apelido de tipo. Em vez disso, escreveria a assinatura de `averager2` assim:
+
+[source, python]
+----
+def averager2(verbose: bool=False) -> Generator[None, float | Sentinel, Result]:
+----
+
+Vamos agora estudar o código da corrotina em si (no <>).
+
+[[ex_returning_averager_coro]]
+.coroaverager2.py: corrotina que devolve um resultado final
+====
+[source, python]
+----
+include::../code/17-it-generator/coroaverager2.py[tags=RETURNING_AVERAGER]
+----
+====
+<1> Para essa corrotina, o tipo produzido ('YieldType') é `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, pois elas consomem dados. Esta linha produz `None`, mas recebe um `term` na próxima invocação de `.send(term)`.
+<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 (eu não poderia somar um `float` a um objeto que pode ser um `float` ou um `Sentinel`).
+<5> Esta linha só será alcançada se um `Sentinel` for enviado para a corrotina.
+
+Vamos ver agora como podemos usar essa corrotina,
+começando por um exemplo simples, que não devolve um resultado (no <>).
+
+[[ex_coro_averager2_demo_1]]
+.coroaverager2.py: doctest mostrando `.cancel()`
+====
+[source, python]
+----
+include::../code/17-it-generator/coroaverager2.py[tags=RETURNING_AVERAGER_DEMO_1]
+----
+====
+<1> Lembre-se de que `averager2` não produz resultados parciais. Ela produz `None`, que o console de Python omite.
+<2> Invocar `.close()` nessa corrotina a faz parar, mas não devolve um resultado, pois a exceção `GeneratorExit` é gerada na linha `yield` da corrotina, então a instrução `return` nunca é alcançada.
+
+Vamos então fazê-la funcionar, no <>.
+
+[[ex_coro_averager2_demo_2]]
+.coroaverager2.py: doctest mostrando `StopIteration` com um `Result`
+====
+[source, python]
+----
+include::../code/17-it-generator/coroaverager2.py[tags=RETURNING_AVERAGER_DEMO_2]
+----
+====
+<1> Enviar um valor `Sentinel()` 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!
+
+Esta ideia de "contrabandear" o valor devolvido para fora de uma corrotina
+dentro de uma exceção `StopIteration` é um _hack_. Entretanto, este _hack_ é parte da
+https://fpy.li/pep342[_PEP 342—Coroutines via Enhanced Generators_],
+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
+_A Referência da Linguagem Python_.
+
+
+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]]
+.coroaverager2.py: doctest mostrando `StopIteration` com um `Result`
+====
+[source, python]
+----
+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 ao tratar a exceção `StopIteration`, que
+sinaliza 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 o gerador for acionado.
+
+<3> Devolve o resultado. Ele também estará encapsulado em `StopIteration`.
+
+<4> Cria o objeto corrotina delegante.
+
+<5> Este 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
+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. Acrescente o tratamento de exceções e o método de corrotina `.throw()`,
+e os exemplos ficam ainda mais complicados. Não vou tratar de `.throw()` nesse
+livro porque—como `.send()`—ele só é útil para controlar corrotinas
+"manualmente", e não recomendo fazer isso, a menos que você esteja criando um
+novo framework baseado em corrotinas do zero .
+
+[NOTE]
+====
+Se tiver interesse em uma discussão mais profunda sobre corrotinas clássicas—incluindo o método `.throw()`—por favor veja
+https://fpy.li/oldcoro[_Classic Coroutines_] no site que acompanha o livro,
+_https://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, qualquer trabalho produtivo com corrotinas exige o apoio de um framework especializado.
+É 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 as corrotinas nativas mais visíveis no código,
+um grande benefício por si só.
+Internamente, as corrotinas nativas usam `await` em vez de `yield from` para delegar a outras corrotinas.
+O <> é todo sobre este assunto.
+
+Vamos agora encerrar o capítulo com uma seção alucinante sobre co-variância e contravariância em dicas de tipo para corrotinas.((("", startref="Cvalue17")))
+
+
+[[generic_classic_coroutine_types_sec]]
+==== Dicas de tipo genéricas para corrotinas clássicas
+
+Na((("coroutines", "generic type hints for")))((("type hints (type annotations)", "generic type hints for coroutines")))
+<>,
+mencionei `typing.Generator` como um dos poucos tipos da biblioteca padrão com um parâmetro de tipo contravariante.
+Agora que estudamos as corrotinas clássicas, estamos prontos para entender este tipo genérico.
+
+É assim que `typing.Generator` era https://fpy.li/17-25[declarado]
+no módulo _typing.py_ de Python 3.6:footnote:[Desde o Python 3.7,
+`typing.Generator` e outros tipos que correspondem a ABCs em `collections.abc` foram refatorados e encapsulados nas ABCs correspondentes, então seus parâmetros genéricos não são visíveis no código-fonte de _typing.py_ . Por isso estou fazendo referência ao código-fonte do Python 3.6 aqui.]
+
+[source, python]
+----
+T_co = TypeVar('T_co', covariant=True)
+V_co = TypeVar('V_co', covariant=True)
+T_contra = TypeVar('T_contra', contravariant=True)
+
+# muitas linhas omitidas
+
+class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co],
+                extra=_G_base):
+----
+
+Esta declaração de tipo genérico significa que uma dica de tipo de `Generator` requer aqueles três parâmetros de tipo que vimos antes:
+
+[source, python]
+----
+my_coro : Generator[YieldType, SendType, ReturnType]
+----
+
+Nas declarações das variáveis de tipo nos parâmetros formais, vemos que `YieldType` e `ReturnType` são covariantes, mas `SendType` é contravariante.
+Para entender a razão disso, considere que `YieldType` e `ReturnType` são tipos de "saída".
+Ambos descrevem dados que saem do objeto corrotina—isto é, o objeto gerador quando usado como um objeto corrotina..
+
+Faz sentido que esses parâmetros sejam covariantes, pois qualquer código esperando uma corrotina que produz números de ponto flutuante pode usar uma corrotina que produz inteiros.
+Por isso `Generator` é covariante em seu parâmetro `YieldType`.
+O mesmo raciocínio se aplica ao parâmetro `ReturnType`—também covariante.
+
+Usando a notação introduzida na <>, a covariância do primeiro e do terceiro parâmetros pode ser expressa pelos símbolos `:>` apontando para a mesma direção:
+
+[source]
+----
+                       float :> int
+Generator[float, Any, float] :> Generator[int, Any, int]
+----
+
+`YieldType` e `ReturnType` são exemplos da primeira regra apresentada na <>:
+
+[quote]
+____
+. Se um parâmetro de tipo formal define um tipo para dados que saem de um objeto, ele pode ser covariante.
+____
+
+Por outro lado, `SendType` é um parâmetro de "entrada":
+ele é o tipo do argumento `value` do método `.send(value)` do objeto corrotina.
+Código cliente que precisa enviar números de ponto flutuante para uma corrotina não pode usar uma corrotina que receba `int` como o `SendType`, porque `float` não é um subtipo de `int`.
+Em outras palavras, `float` não é _consistente-com_ `int`.
+Mas o cliente pode usar uma corrotina que tenha `complex` como `SendType`,
+pois `float` é um subtipo de `complex`,
+e portanto `float` é _consistente-com_ `complex`.
+
+A notação `:>` torna visível a contravariância do segundo parâmetro:
+
+[source]
+----
+                     float :> int
+Generator[Any, float, Any] <: Generator[Any, int, Any]
+----
+
+Este é um exemplo da segunda regra geral da variância:
+
+[quote]
+____
+[start=2]
+. Se um parâmetro de tipo formal define um tipo para dados que entram em um objeto após sua construção inicial, ele pode ser contravariante.
+____
+
+Esta alegre discussão sobre variância encerra o capítulo mais longo do livro.
+
+
+=== Resumo do capítulo
+
+A iteração((("iterators", "overview of")))((("generators", "overview of")))((("coroutines", "overview of"))) está integrada tão profundamente à linguagem que gosto de dizer que Python _groks_ iteráveis.footnote:[De acordo com o https://fpy.li/17-26[_Jargon file_], "to grok" não é meramente entender algo, mas absorver de uma forma que "aquilo se torna parte de você, parte de sua identidade".]
+A integração do padrão _Iterator_ na semântica de Python é um exemplo perfeito de como padrões de projeto não são aplicáveis a todas as linguagens de programação.
+No Python, um _Iterator_ clássico, implementado "à mão", como no <>, não tem qualquer função prática, exceto como exemplo didático.
+
+Neste capítulo, criamos algumas versões de uma classe para iterar sobre palavras individuais em arquivos de texto (que podem ser muito grandes).
+Vimos como Python usa a função embutida `iter()` para criar iteradores a partir de objetos similares a sequências.
+Criamos um iterador clássico como uma classe com `+__next__()+`, e então usamos geradores,
+para tornar a classe `Sentence` mais concisa e legível a cada nova refatoração.
+
+Daí criamos um gerador de progressões aritméticas, e mostramos como usar o módulo `itertools` para simplificar o código.
+A isso se seguiu uma revisão da maioria das funções geradoras de uso geral na biblioteca padrão.
+
+A seguir estudamos expressões `yield from` no contexto de geradores simples, com os exemplos `chain` e `tree`.
+
+A última seção foi sobre corrotinas clássicas, um tópico de importância decrescente após a introdução das corrotinas nativas, no Python 3.5.
+Apesar de difíceis de usar na prática, corrotinas clássicas são os alicerces das corrotinas nativas, e a expressão `yield from` é precursora direta de `await`.
+
+Também abordamos dicas de tipo para os tipos `Iterable`, `Iterator`, e `Generator`—com esse último oferecendo um raro exemplo concreto de um parâmetro de tipo contravariante.
+
+
+=== Para saber mais
+
+Uma((("iterators", "further reading on")))((("generators", "further reading on")))((("coroutines", "further reading on"))) explicação técnica detalhada sobre geradores aparece na _A Referência da Linguagem Python_, em https://fpy.li/9g[_Expressões yield_].
+A PEP onde as funções geradoras foram definidas é a https://fpy.li/pep255[_PEP 255--Simple Generators_].
+
+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 uso 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 biblioteca 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, acompanhados de muitos exemplos e
+receitas úteis.
+
+_Iterators and Generators_, 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`.
+
+Sebastian Rittau—atualmente um dos principais colaboradores do _typeshed_—explica por que iteradores devem ser iteráveis. Ele observou, em 2006, que https://fpy.li/17-31[_Java: Iterators are not Iterable_] (Java: Iteradores não são Iteráveis).
+
+A sintaxe de `yield from` é explicada, com exemplos, na seção _What's New in
+Python 3.3_ (Novidades no Python 3.3) da
+https://fpy.li/17-32[_PEP 380-Syntax for Delegating to a Subgenerator_] (Sintaxe para Delegar para um Subgerador).
+Meu artigo
+https://fpy.li/oldcoro[_Classic Coroutines_]
+_http://fluentpython.com_
+explica `yield from` em
+profundidade, incluindo pseudo-código em Python de sua implementação em C.
+
+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:
+https://fpy.li/17-33[_Generator Tricks for Systems Programmers_] (Truques com Geradoras para Programadores de Sistemas).
+Na PyCon US 2009 aconteceu o lendário
+https://fpy.li/17-34[_A Curious Course on Coroutines and Concurrency_] (Um Curioso Curso sobre Corrotinas e Concorrência)
+(links de vídeo difíceis de encontrar para as três partes:
+https://fpy.li/17-35[parte 1],
+https://fpy.li/17-36[parte 2], e
+https://fpy.li/17-37[parte 3]).
+Seu tutorial na PyCon 2014 em Montreal foi
+https://fpy.li/17-38[_Generators: The Final Frontier_] (Geradores: A Fronteira Final), onde ele apresenta mais exemplos de concorrência—então é, na  verdade, mais relacionado aos tópicos do <>.
+Dave não consegue deixar de explodir cérebros em suas aulas, então, na última parte de _A Fronteira Final_, corrotinas substituem o padrão clássico _Visitor_ em um analisador de expressões aritméticas.
+
+Corrotinas permitem organizar o código de novas maneiras e, assim como a recursão e o polimorfismo (despacho dinâmico),
+leva um tempo para a gente se dar conta de suas possibilidades.
+Um exemplo interessante de um algoritmo clássico reescrito com corrotinas aparece no post
+https://fpy.li/17-39[_Greedy algorithm with coroutines_] (Algoritmo guloso com corrotinas), de James Powell.
+
+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.
+Slatkin apresenta o melhor exemplo que já vi do controle de corrotinas com `yield from`:
+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.
+Publiquei o https://fpy.li/17-43[«exemplo refatorado»] como um https://fpy.li/17-44[_GitHub gist_].
+
+.Ponto de Vista
+****
+
+*A interface Iterador minimalista de Python*
+
+Na((("iterators", "Soapbox discussion")))((("generators",
+"Soapbox discussion")))((("coroutines", "Soapbox discussion")))((("Soapbox sidebars",
+"minimalistic iterator interface"))) seção _Implementação_ do padrão
+_Iterator_,footnote:[Gamma et. al., _Design Patterns: Elements of Reusable
+Object-Oriented Software_, p. 261.], a Gangue dos Quatro escreveu:
+
+[quote]
+____
+A interface mínima de Iterator consiste das operações First, Next, IsDone, e CurrentItem.
+____
+
+Entretanto, esta mesma frase tem uma nota de rodapé, onde se lê:
+
+[quote]
+____
+Podemos tornar esta interface ainda menor, fundindo Next, IsDone, e CurrentItem em uma única operação que avança para o próximo objeto e o devolve. Se a travessia estiver encerrada, esta operação daí devolve um valor especial (0, por exemplo), que marca o final da iteração.
+____
+
+Isto é próximo do que temos em Python: um único método `+__next__+`, faz o serviço.
+Mas em vez de uma sentinela, que poderia passar despercebida por engano ou distração, a exceção `StopIteration` sinaliza o final da iteração. Simples e correto: esse é o jeito de Python.
+
+*Geradoras plugáveis*
+
+Qualquer((("Soapbox sidebars", "pluggable generators", id="SSplgen17"))) um que gerencie grandes conjuntos de dados encontra muitos usos para geradores.
+Esta é 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 a LILACS (Literatura Latino-Americana e do Caribe em Ciências da Saúde) e a SciELO (Scientific Electronic Library Online), duas bases de dados abrangentes, indexando a literatura de pesquisa em ciências da saúde produzida na região.
+
+Desde o final dos anos 1980, o sistema de banco de dados usado para gerenciar a
+LILACS é o CDS/ISIS, um banco de dados não-relacional orientado a documentos,
+criado pela UNESCO. Uma de minhas tarefas era pesquisar alternativas para uma
+possível migração da LILACS--e eventualmente também a SciELO--para um banco de
+dados documental moderno de código aberto, tal como o CouchDB ou o MongoDB.
+Naquela época publiquei um artigo científico 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 CouchDB: Bancos de Dados e Modelos de Dados para Registros Bibliográficos).
+
+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 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 laço de
+escrita JSON já era complicado, pois o script aceitava
+várias opções na linha de comando para reestruturar cada registro de saída.
+Seria ainda mais complicado ler dados usando duas APIs diferentes no mesmo laço `for` onde o JSON era produzido.
+
+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.]
+
+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`:: Função geradora que lê arquivos _.iso_ (que se presume estarem no formato ISO-2709). Ela aceita dois argumentos: 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`:: Função geradora que lê arquivos _.mst_.footnote:[A biblioteca usada para ler o complicado formato binário _.mst_ é escrita em Java, então esta 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_] no repositório. As dependências são importadas dentro das funções geradoras que precisam delas, então o script pode rodar mesmo quando apenas uma das bibliotecas externas está instalada.] 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`:: Função que escreve os 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.
+
+Aproveitando as funções geradoras, consegui isolar completamente a leitura da escrita.
+Claro, a maneira mais simples de isolar 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 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, da Biblioteca do Congresso dos EUA—será fácil acrescentar uma terceira função geradora para implementar a lógica de leitura, sem mudar nada na complicada função `write_json`.
+
+Não foi uma grande inovação, mas é um exemplo real onde os geradores permitiram
+uma 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
new file mode 100644
index 00000000..54befb83
--- /dev/null
+++ b/online/cap18.adoc
@@ -0,0 +1,1854 @@
+[[ch_with_match]]
+== Instruções with, match, e blocos else
+:example-number: 0
+:figure-number: 0
+
+[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.]
+
+____
+
+
+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 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 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 com as instruções `for`, `while` e `try`.
+
+=== Novidades neste capítulo
+
+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
+agrupados entre parênteses, desde o Python 3.10.
+
+Vamos começar com a poderosa instrução `with`.
+
+[[context_managers_sec]]
+=== 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 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((("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://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`»].
+
+* Configurar ambientes customizados para operações
+aritméticas com objetos `Decimal`—veja a
+https://fpy.li/9r[«documentação de `decimal.localcontext`»].
+
+* 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
+é garantir que um 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
+====
+[source, python]
+----
+>>> with open('mirror.py') as fp:  # <1>
+...     src = fp.read(60)  # <2>
+...
+>>> len(src)
+60
+>>> fp  # <3>
+<_io.TextIOWrapper name='mirror.py' mode='r' encoding='UTF-8'>
+>>> fp.closed, fp.encoding  # <4>
+(True, 'UTF-8')
+>>> fp.read(60)  # <5>
+Traceback (most recent call last):
+  File "", line 1, in 
+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`.
+
+<2> Lê `60` caracteres Unicode de `fp`.
+
+<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`.
+
+<4> Podemos acessar atributos do objeto `fp`.
+
+<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 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.
+
+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`
+====
+[source, python]
+----
+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` é 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.
+
+
+O <> mostra a implementação de `LookingGlass`.
+
+[[looking_glass_ex]]
+.mirror.py: código da classe gerenciadora de contexto `LookingGlass`
+====
+[source, python]
+----
+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 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.
+
+[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`.
+
+====
+
+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`.
+
+`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 invocar manualmente seus métodos `+__enter__+` e
+`+__exit__+`.
+
+[[looking_glass_demo_2]]
+.Exercitando o `LookingGlass` sem um bloco `with`
+====
+[source, python]
+----
+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")))
+
+
+.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)].
+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:
+
+[source, python]
+----
+with (
+    CtxManager1() as example1,
+    CtxManager2() as example2,
+    CtxManager3() as example3,
+):
+    ...
+----
+
+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, classes e
+decoradores convenientes para desenvolver, combinar e usar gerenciadores
+de contexto.
+
+[[context_utilities_sec]]
+==== Utilitários da `contextlib`
+
+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`.
+
+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__+`.
+
+`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.
+
+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 uma 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.
+
+`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.
+
+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 <>.
+
+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`.
+
+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__+` é invocado, no final do bloco.
+
+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
+====
+[source, python]
+----
+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 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 é suspenso neste 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.
+
+[[looking_glass_gen_demo]]
+.Testando a função gerenciadora de contexto `looking_glass`
+====
+[source, python]
+----
+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`.
+
+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:
+
+. 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 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 <> 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 <>
+====
+[source, python]
+----
+include::../code/18-with-match/mirror_gen_exc.py[tags=MIRROR_GEN_EXC]
+----
+====
+<1> Cria uma variável para uma possível mensagem de erro; essa é a primeira mudança em relação a <>.
+<2> Trata `ZeroDivisionError`, fixando uma mensagem de erro.
+<3> Desfaz o _monkey-patching_ de `sys.stdout.write`.
+<4> Mostra a mensagem de erro, se ela foi determinada.
+
+Lembre-se de 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.
+
+[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 dos revisores técnicos desse livro. Muito bem colocado, Leo!]
+
+====
+
+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.
+
+[[looking_glass_gen_deco_demo]]
+.O gerenciador de contexto `looking_glass` também funciona como um decorador.
+====
+[source, python]
+----
+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.
+
+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 a
+função `inplace` apresentada naquele artigo.
+
+[[inplace_ex]]
+.Um gerenciador de contexto para reescrever arquivos no lugar
+====
+[source, python]
+----
+import csv
+
+with inplace(csvfilename, 'r', newline='') as (infh, outfh):
+    reader = csv.reader(infh)
+    writer = csv.writer(outfh)
+
+    for row in reader:
+        row += ['new', 'columns']
+        writer.writerow(row)
+----
+====
+
+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. Isto é 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»]), 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")))
+
+
+[[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. Nesta seção quero apresentar uma 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 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.
+
+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á 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 têm comportamentos muito
+diferentes de qualquer função.]
+
+O <> mostra um exemplo simples em Scheme.
+
+[[ex_gcd_scheme]]
+.Maior divisor comum em Scheme
+====
+[source, scheme]
+----
+(define (mod m n)
+    (- m (* n (quotient m n))))
+
+(define (gcd m n)
+    (if (= n 0)
+        m
+        (gcd n (mod m n))))
+
+(display (gcd 18 45))
+----
+====
+
+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 (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
+====
+[source, python]
+----
+def mod(m, n):
+    return m - (m // n * n)
+
+def gcd(m, n):
+    if n == 0:
+        return m
+    else:
+        return gcd(n, mod(m, n))
+
+print(gcd(18, 45))
+----
+====
+
+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 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
+https://fpy.li/18-15[_18-with-match/lispy/py3.10/_],
+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.
+
+[[lis_top_ex]]
+.lis.py: início do arquivo
+====
+[source, python]
+----
+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_), 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 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 aninhadas.
+
+[[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 (<>).
+
+[[lis_parser_ex]]
+.lis.py: as principais funções do analisador
+====
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/lis.py[lines=27..36]
+    # mais código do analisador omitido na listagem do livro
+----
+====
+
+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êntese 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 extraídos do https://fpy.li/18-18[_lispy/py3.10/examples_test.py_]:
+
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=PARSE]
+----
+
+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`.
+
+. 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.
+
+. 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[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` guardam no atributo `self.maps`, que é lista de mapeamentos. O método
+`change` é necessário para suportar a instrução `(set! …)` do Scheme, descrita mais
+tarde.
+
+
+[[environment_class_ex]]
+._lis.py_: a classe `Environment`
+====
+[source, python]
+----
+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.]
+Tentar mudar uma chave não encontrada causa um `KeyError`.
+
+Esse doctest mostra como `Environment` funciona:
+
+[source, python]
+----
+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 (<>).
+
+[[lis_std_env_ex]]
+.lis.py: `standard_env()` constrói e devolve o ambiente global
+====
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/lis.py[lines=77..85]
+            # omitidas: várias definições de funções
+include::../code/18-with-match/lispy/py3.10/lis.py[lines=92..97]
+            # 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, algumas renomeadas, como `callable`
+para `procedure?`, ou mapeadas diretamente, como `round`.
+
+==== O REPL
+
+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 o 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 esquecermos de fechar um parêntese,
+_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 que 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]]
+.As funções do REPL
+====
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/lis.py[tags=REPL]
+----
+====
+
+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 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`.
+
+`lispstr(exp: object) -> str`::
+    A função inversa de `parse`:
+    dado um objeto Python representando a AST de uma expressão,
+    `lispstr` devolve o código-fonte correspondente.
+    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—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.
+
+[[ex_evaluate_match]]
+.`evaluate` recebe uma expressão e calcula seu valor
+====
+[source, python]
+----
+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 casos coloquei comentários mostrando uma expressão-S que
+casaria com o padrão quando transformada em uma lista de Python. Os doctests
+extraídos de https://fpy.li/18-21[_examples_test.py_] demonstram cada `case`.
+
+
+[[eval_atom_sec]]
+===== avaliando números
+
+[source, python]
+----
+    case int(x) | float(x):
+        return x
+----
+
+Padrão:::
+    Instância de `int` ou `float`.
+
+Ação:::
+    Devolve o próprio valor.
+
+Exemplo:::
++
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_NUMBER]
+----
+
+===== avaliando símbolos
+
+[source, python]
+----
+    case Symbol(var):
+        return env[var]
+----
+
+Padrão:::
+    Instância de `Symbol`, isto é, uma `str` usada como identificador.
+
+Ação:::
+    Consulta `var` em `env` e devolve seu valor.
+
+Exemplos:::
++
+[source, python]
+----
+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.
+
+[source, python]
+----
+    # (quote (99 bottles of beer))
+    case ['quote', x]:
+        return x
+----
+
+Padrão:::
+    Lista começando com o símbolo `'quote'`, seguido de uma expressão `x`.
+
+Ação:::
+    Devolve `x` sem avaliá-la.
+
+Exemplos:::
++
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_QUOTE]
+----
+
+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
+* `(/ 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 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 têm 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.
+****
+
+
+===== (if …)
+
+[source, python]
+----
+    # (if (< x 0) 0 x)
+    case ['if', test, consequence, alternative]:
+        if evaluate(test, env):
+            return evaluate(consequence, env)
+        else:
+            return evaluate(alternative, env)
+----
+
+Padrão:::
+    Lista começando com `'if'` seguida de três expressões:
+    `test`, `consequence`, e `alternative`.
+
+Ação:::
+    Avalia `test`:
+    * Se verdadeira, avalia `consequence` e devolve seu valor.
+    * Caso contrário, avalia `alternative` e devolve seu valor.
+
+Exemplos:::
++
+[source, python]
+----
+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 <>.
+
+===== (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 …)`.
+
+[source, python]
+----
+    # (lambda (a b) (/ (+ a b) 2))
+    case ['lambda' [*parms], *body] if body:
+        return Procedure(parms, body, env)
+----
+
+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 `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.
+
+Exemplo:::
++
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_LAMBDA]
+----
+
+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 declarada.
+Vamos estudar o código de `Procedure` daqui a pouco.
+
+
+[role="pagebreak-before less_space"]
+===== (define …)
+
+A palavra reservada `define` é usada em duas formas sintáticas diferentes.
+A mais simples é:
+
+[source, python]
+----
+    # (define half (/ 1 2))
+    case ['define', Symbol(name), value_exp]:
+        env[name] = evaluate(value_exp, env)
+----
+
+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.
+
+Exemplo:::
++
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_DEFINE]
+----
+
+O doctest para este `case` cria um `global_env`, para podermos verificar que
+`evaluate` coloca `answer` dentro daquele `Environment`.
+
+Podemos usar a 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.
+
+[source, python]
+----
+
+    # (define (average a b) (/ (+ a b) 2))
+    case ['define', [Symbol(name), *parms], *body] if body:
+        env[name] = Procedure(parms, body, env)
+----
+
+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)
+
+Ação:::
+    * 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.
+
+[[test_case_defun]]
+.Definindo uma função chamada `%`, que calcula uma porcentagem
+====
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_DEFUN]
+----
+====
+
+Após invocar `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.
+
+
+
+===== (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
+programaçã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]
+----
+    # (set! n (+ n 1))
+    case ['set!', Symbol(name), value_exp]:
+        env.change(name, evaluate(value_exp, env))
+----
+
+Padrão:::
+    Lista começando com `'set!'`, seguida de um `Symbol` e de uma expressão.
+
+Ação:::
+    Atualiza o valor de `name` em `env` com o resultado da avaliação da expressão.
+
+O método `Environment.change` atravessa os ambientes encadeados de local para global,
+e atualiza a primeira ocorrência de `name` com o novo valor.
+Se não estivéssemos implementando a palavra reservada `'set!'`,
+esse interpretador poderia usar apenas o `ChainMap` de Python para implementar `env`,
+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 keyword")))((("keywords", "nonlocal keyword"))) uso da forma
+`set!` tem relação com a 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)` será sempre uma variável local, criada ou atualizada no ambiente
+local.
+
+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, preservando os itens `count` e `total` em uma
+clausura. Aqui está a mesma ideia, escrita no subconjunto de Scheme de _lis.py_:
+
+[source, scheme]
+----
+(define (make-averager)
+    (define count 0)
+    (define total 0)
+    (lambda (new-value)
+        (set! count (+ count 1))
+        (set! total (+ total new-value))
+        (/ total count)
+    )
+)
+(define avg (make-averager))  # <1>
+(avg 10)  # <2>
+(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`, inicializadas com 0; vincula a clausura a `avg`.
+<2> Devolve 10.0.
+<3> Devolve 10.5.
+<4> Devolve 12.0.
+
+O código acima é um dos testes em https://fpy.li/18-18[_lispy/py3.10/examples_test.py_].
+
+****
+
+Agora chegamos a uma chamada de função.
+
+[[function_call_sec]]
+===== Chamada de função
+
+[source, python]
+----
+    # (gcd (* 2 105) 84)
+    case [func_exp, *args] if func_exp not in KEYWORDS:
+        proc = evaluate(func_exp, env)
+        values = [evaluate(arg, env) for arg in args]
+        return proc(*values)
+----
+
+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 <>.
+
+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.
+--
+
+Açã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.
+    * Invocar `proc` com os valores como argumentos separados, devolvendo o resultado.
+
+Exemplo:::
++
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_CALL]
+----
+
+Esse doctest continua do <>:
+ele assume que `global_env` contém uma função chamada `%`.
+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 sujeito que seja uma sequência de um ou mais itens.
+Entretanto, se `func_exp` é uma palavra reservada e o
+sujeito não casou com nenhum dos `case` anteriores,
+então temos 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` "coringa" gera um `SyntaxError`:
+
+[source, python]
+----
+    case _:
+        raise SyntaxError(lispstr(exp))
+----
+
+Aqui está um exemplo de um `(lambda …)` malformado, identificado como um `SyntaxError`:
+
+[source, python]
+----
+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'` 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
+no contexto de um ambiente. A definição de função inclui o nome dos parâmetros e as
+expressões que formam 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_:
+
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/lis.py[tags=PROCEDURE]
+----
+<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> `+__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 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.
+
+Há um par de funções simples após `evaluate` em https://fpy.li/18-24[_lis.py_]:
+`run` lê um programa Scheme completo e o executa,
+e `main` chama `run` ou `repl`, dependendo da linha de comando—parecido com o modo como Python faz.
+Não vou descrever essas funções, pois não há nada novo ali.
+Meus objetivos aqui eram compartilhar com vocês a beleza do pequeno interpretador de Norvig,
+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 casamento de padrões,
+vamos formalizar o conceito de um padrão-OU (_OR-pattern_).((("", startref="lispyproced18")))
+
+==== Usando padrões-OU
+
+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]
+----
+    case int(x) | float(x):
+        return x
+----
+
+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`,
+independentemente 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.
+====
+
+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]
+----
+    # (λ (a b) (/ (+ a b) 2) )
+    case ['lambda' | 'λ', [*parms], *body] if body:
+        return Procedure(parms, body, env)
+----
+
+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")))
+
+=== Faça isso, então aquilo: blocos `else` além do `if`
+
+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` atrapalhou
+meu entendimento destas cláusulas, mas acabei me acostumando.
+
+Aqui estão as regras:
+
+`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 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 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.
+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 destas cláusulas `else`,
+mas do ponto de vista do design da linguagem,
+a palavra `else` foi uma escolha infeliz, porque
+`else` implica em uma alternativa excludente,
+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 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 criar variáveis de controle ou codar instruções
+`if` adicionais.
+
+O uso de `else` em laços em geral segue o padrão desse trecho:
+
+[source, python]
+----
+for item in my_list:
+    if item.flavor == 'banana':
+        break
+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á invocada se a
+`dangerous_call()` não gerar uma exceção, correto?
+
+[source, python]
+----
+try:
+    dangerous_call()
+    after_call()
+except OSError:
+    log('OSError...')
+----
+
+Entretanto, não há um bom motivo para colocar a `after_call()` dentro do bloco `try`.
+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]
+----
+try:
+    dangerous_call()
+except OSError:
+    log('OSError...')
+else:
+    after_call()
+----
+
+Agora fica explícito 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` é bastante 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.
+
+____
+
+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”. 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 é caracterizado 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`.
+
+[NOTE]
+====
+
+Quando a instrução `match` foi proposta, algumas pessoas (inclusive eu)
+acharam que ela também devia ter uma cláusula `else`. Afinal 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
+o significado da instrução `with` e os gerenciadores de contexto, indo
+além do 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
+após 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 funcionamento 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_ foi um instrumento
+para explorarmos mais que casamento de padrões. Ele mostra como diferentes partes
+de um interpretador trabalham juntas, ilustrando conceitos fundamentais do
+próprio Python: por que 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://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 por que 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 e 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 uma 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
+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
+****
+
+
+*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." Eu tive uma reação
+similar, inicialmente. 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:
+
+[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.
+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.
+
+____
+
+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.
+
+Se você precisa convencer alguém que de Python é sensacional, assista à palestra de
+abertura de Hettinger. A parte sobre gerenciadores de contexto fica entre 23:00
+e 26:15. Mas a palestra inteira é excelente.
+
+
+*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 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»].
+
+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]
+----
+def factorial(n):
+    if n < 2:
+       return 1
+    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.
+
+Aqui está uma alternativa que usa uma chamada de cauda,
+e é portanto recursiva de cauda (_tail recursive_):
+
+[source, python]
+----
+def factorial_tc(n, product=1):
+    if n < 1:
+        return product
+    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, 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 de 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 fazer 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.
+
+*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)
+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 rastreando algumas chamadas em um depurador.
+
+____
+
+Em 2015, PTC foram incluídas no padrão ECMAScript 6 para JavaScript.
+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 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:
+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.
+
+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]
+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 percorrer,
+por exemplo, uma árvore de parsing típica,
+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 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")))
+
+*A opinião de Norvig sobre `evaluate()` com casamento de padrões*
+
+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`
+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.
+
+Provavelmente seguirei o conselho do professor Norvig quando
+acrescentar funcionalidades ao
+https://fpy.li/18-54[_mylis_].
+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 casamento de padrões
+e o código é mais enxuto.((("", startref="WMEBsoap18")))
+
+****
diff --git a/online/cap19.adoc b/online/cap19.adoc
new file mode 100644
index 00000000..16689381
--- /dev/null
+++ b/online/cap19.adoc
@@ -0,0 +1,1784 @@
+[[ch_concurrency_models]]
+== Modelos de concorrência em Python
+:example-number: 0
+:figure-number: 0
+
+[quote, Rob Pike, Co-criador da linguagem Go]
+____
+Concorrência é lidar com muitas coisas ao mesmo tempo. +
+Paralelismo é fazer muitas coisas ao mesmo tempo. +
+Não são iguais, mas têm relação. +
+[Concorrência] é sobre estrutura, [paralelismo] é 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 da palestra https://fpy.li/19-1[_Concurrency Is Not Parallelism_] (Concorrência não é paralelismo).]
+
+____
+
+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. 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.
+Um laptop moderno com quatro núcleos de CPU rotineiramente está executando mais de 200 processos a qualquer momento, sob uso normal, casual.
+Para executar 200 tarefas em paralelo, você precisaria de 200 núcleos.
+Portanto, na prática, a maior parte da computação 
+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 paralelo.
+
+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 à 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 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 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."
+
+=== 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 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 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 ecosistema 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ê invoca uma função, o código que faz a chamada aguarda até que a 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 cliente pode cercar aquela chamada com um bloco `try/except` para tratar o erro.
+
+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 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 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 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.
+
+Por fim, as corrotinas e threads de Python não são adequadas para tarefas de uso intensivo da CPU, como veremos.
+
+Programação concorrente envolve conceitos e modelos de programação que podem ser novidade para você.
+Então vamos primeiro garantir que estamos na mesma página em relação a alguns conceitos centrais.
+
+=== Um pouco de jargão
+
+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 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).
+
+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.
+    Python suporta de forma nativa três tipos de unidade de execução:
+    _processos_, _threads_, e _corrotinas_.
+
+Processo::
+    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 rodam rotineiramente 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 outros processos sejam executados.
+    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 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).
+    Isso facilita o compartilhamento de informações entre threads, mas pode também levar à corrupção de dados,
+    se uma thread está lendo um objeto enquanto ele está sendo modificado por outra thread.
+    Como os processos, as threads também possibilitam a _multitarefa preemptiva_ sob a supervisão do agendador do SO.
+    Uma thread consome menos recursos que um processo para realizar a mesma tarefa.
+
+
+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 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 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".]
+    Filas permitem que unidades de execução separadas troquem dados da aplicação e mensagens de controle, como códigos de erro e sinais de término.
+    A implementação de uma fila varia de acordo com o modelo de concorrência subjacente: o pacote `queue` na biblioteca padrão de Python fornece classes de fila para suportar threads, já os pacotes `multiprocessing` e `asyncio` implementam suas próprias classes de fila. Os pacotes `queue` e `asyncio` também incluem filas não FIFO: `LifoQueue` e `PriorityQueue`.
+
+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 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.
+    A variante mais simples de trava é conhecida também como mutex (de _mutual exclusion_, exclusão mútua).
+    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 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")))
+
+==== Processos, threads, e a infame GIL de Python
+
+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 escritos em qualquer linguagem.
+
+. 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_.
+
+. O acesso à contagem de referências a objetos e outros estados internos do interpretador é controlado por uma trava,
+a((("Global Interpreter Lock (GIL)"))) Global Interpreter Lock (GIL) ou _Trava Global do Interpretador_.
+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, mesmo que a CPU tenha vários núcleos.
+
+. 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, liberando a GIL.footnote:[Invoque https://fpy.li/a5[`sys.getswitchinterval()`] para obter o intervalo; ele pode ser modificado com https://fpy.li/ag[`sys.setswitchinterval(s)`].]
+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 demorada.
+
+. 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` e `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 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"].]
+
+. 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, parte da https://fpy.li/a7[documentação do módulo `threading`]:
+
+[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 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.
+____
+
+O parágrafo anterior começa com "Detalhe de implementação do CPython" porque a
+GIL não é parte da definição da linguagem Python. As implementações Jython e o
+IronPython não têm uma GIL. Infelizmente, ambas estão ficando para trás, ainda
+compatíveis apenas com Python 2.7 e 3.4, respectivamente. O interpretador de
+alto desempenho https://fpy.li/19-9[PyPy] também tem uma GIL em suas versões
+2.7, 3.8 e 3.9 (a mais recente em março de 2021).
+
+[NOTE]
+====
+Esta seção não mencionou corrotinas, por que por default elas compartilham a mesma thread Python entre si e com o laço de eventos supervisor fornecido por um framework assíncrono—então não são afetadas pela GIL.
+É 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 <>.
+====
+
+Mas chega de conceitos por agora. Vamos ver algum código.((("", startref="CMconcepts19")))
+
+[[concurrent_hello_world]]
+=== Um "Olá mundo" concorrente
+
+Durante((("concurrency models", "Hello World example", id="CMhello19"))) uma discussão sobre threads e sobre como evitar a GIL,
+o contribuidor do Python Michele Simionato https://fpy.li/19-10[postou um exemplo] que é praticamente um "Olá Mundo" concorrente:
+o programa mais simples possível mostrando como o Python pode "assobiar e chupar cana ao mesmo tempo".
+
+O programa de Simionato usa `multiprocessing`,
+mas eu o adaptei para apresentar também `threading` e `asyncio`.
+Vamos começar com a versão `threading`, que pode parecer familiar se você já estudou threads em Java ou C.
+
+
+==== Caracteres animados com threads
+
+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 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 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.
+image::../images/flpy_1901.png[Captura de tela do console mostrando a saída dos dois exemplos.]
+
+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]]
+.spinner_thread.py: as funções `spin` e `slow`
+====
+[source, python]
+----
+include::../code/19-concurrency/spinner_thread.py[tags=SPINNER_THREAD_TOP]
+----
+====
+<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.
+
+[TIP]
+====
+O primeiro detalhe importante deste exemplo é que `time.sleep()` bloqueia a thread que a chama, mas libera a GIL, permitindo que outras threads Python rodem.
+====
+
+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 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.
+
+
+
+[[spinner_thread_rest_ex]]
+.spinner_thread.py: as funções `supervisor` e `main`
+====
+[source, python]
+----
+include::../code/19-concurrency/spinner_thread.py[tags=SPINNER_THREAD_REST]
+----
+====
+<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 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 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` 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")))
+
+
+==== Animação com processos
+
+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 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`, 
+facilitando a conversão de programas simples de threads para processos, como mostra o _spinner_proc.py_ (<>).
+
+[[spinner_proc_ex]]
+.spinner_proc.py: apenas as partes modificadas são mostradas; todo o resto é idêntico a spinner_thread.py
+====
+[source, python]
+----
+include::../code/19-concurrency/spinner_proc.py[tags=SPINNER_PROC_IMPORTS]
+
+# [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 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 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 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`, 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, 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 de 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://fpy.li/a9[`ShareableList`]
+para mais detalhes.
+====
+
+Agora vamos ver como o mesmo comportamento pode ser obtido com corrotinas em vez de threads ou processos.
+
+[[spinner_async_sec]]
+==== Animação com corrotinas
+
+[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. Esta seção é apenas uma introdução rápida, para contrastar esta 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 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.
+
+[[spinner_async_start_ex]]
+.spinner_async.py: a função `main` e a corrotina `supervisor`
+====
+[source, python]
+----
+include::../code/19-concurrency/spinner_async.py[tags=SPINNER_ASYNC_START]
+----
+====
+<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 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 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 <>.
+
+
+O <> demonstra as três principais formas de rodar uma corrotina:
+
+`asyncio.run(coro())`::
+    É 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` neste exemplo. Esta chamada bloqueia até que `coro` retorne. O resultado de `coro` será o resultado de `run`.
+`asyncio.create_task(coro())`::
+    É invocada dentro de uma corrotina para agendar a execução futura de outra corrotina.
+    Essa chamada não suspende a corrotina atual.
+    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()`::
+    É 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 laço de eventos.
+====
+
+Vamos estudar agora as corrotinas `spin` e `slow` no <>.
+
+[[spinner_async_top_ex]]
+.spinner_async.py: as corrotinas `spin` e `slow`
+====
+[source, python]
+----
+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 laço.
+<4> A corrotina `slow` também usa `await asyncio.sleep` em vez de `time.sleep`.
+
+===== 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 <>.
+
+[[spinner_async_time_sleep_ex]]
+.spinner_async.py: substituindo `await asyncio.sleep(3)` por `time.sleep(3)`
+====
+[source, python]
+----
+async def slow() -> int:
+    time.sleep(3)
+    return 42
+----
+====
+
+Observar o comportamento é mais memorável que ler sobre ele.
+Vai lá, eu espero.
+
+Ao rodar o experimento, você vê o seguinte:
+
+. O objeto `spinner` aparece: `>`.
+. 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 de 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.
+No <>, vamos nos concentrar no que ocorre nas corrotinas `supervisor` e `slow` durante o experimento proposto.
+
+[[spinner_async_experiment_ex]]
+.spinner_async_experiment.py: as corrotinas `supervisor` e `slow`
+====
+[source, python]
+----
+include::../code/19-concurrency/spinner_async_experiment.py[tags=SPINNER_ASYNC_EXPERIMENT]
+----
+====
+<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 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 assíncronas, a menos que você queira pausar o programa inteiro.
+Se uma corrotina precisa passar algum tempo sem fazer nada, use `await asyncio.sleep(DELAY)`.
+Isto devolve o controle para o laço de eventos do `asyncio`, que pode acionar outras corrotinas pendentes.((("", startref="Cspin19")))((("", startref="Scoroutine19")))
+====
+
+[[gevent_box]]
+.Greenlet e gevent
+****
+Ao((("greenlet package"))) discutir concorrência com corrotinas,
+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`,
+e assim são mais fáceis de integrar a bases de código sequencial existentes.
+O https://fpy.li/19-15[SQL Alchemy 1.4 ORM] usa greenlets
+internamente para implementar sua nova
+https://fpy.li/19-16[API assíncrona] compatível com `asyncio`.
+
+A((("gevent library"))) biblioteca de programação de redes
+https://fpy.li/19-17[_gevent_] modifica o módulo `socket` padrão de Python via _monkey patching_, tornando-o não-bloqueante ao substituir parte do código por greenlets.
+Na maior parte dos casos, _gevent_ é transparente para o código em seu entorno,
+tornando mais fácil adaptar aplicações e bibliotecas sequenciais—tal como drivers de bancos de dados—para executar E/S de rede de forma concorrente.
+https://fpy.li/19-18[Inúmeros projetos open source]
+usam _gevent_, incluindo o muito usado
+https://fpy.li/gunicorn[_Gunicorn_]—mencionado na <>.
+****
+
+==== 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 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
+====
+[source, python]
+----
+def supervisor() -> int:
+    done = Event()
+    spinner = Thread(target=spin,
+                     args=('thinking!', done))
+    print('spinner object:', spinner)
+    spinner.start()
+    result = slow()
+    done.set()
+    spinner.join()
+    return result
+----
+====
+
+Para comparar, o <> mostra a corrotina  `supervisor` do <>.
+
+[[asyncio_supervisor_ex]]
+.spinner_async.py: a corrotina assíncrona `supervisor`
+====
+[source, python]
+----
+async def supervisor() -> int:
+    spinner = asyncio.create_task(spin('thinking!'))
+    print('spinner object:', spinner)
+    result = await slow()
+    spinner.cancel()
+    return result
+----
+====
+
+Aqui está um resumo das diferenças e semelhanças notáveis entre as duas implementações de `supervisor`:
+
+* Uma `asyncio.Task` é aproximadamente equivalente  a `threading.Thread`.
+* 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(…)` 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`, 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
+sabe quão desafiador é estruturar o programa, porque o agendador pode interromper uma thread a qualquer momento.
+É preciso lembrar de manter travas para proteger seções críticas do programa, para evitar ser interrompido no meio de uma operação de muitas etapas—algo que poderia deixar dados em um estado inválido.
+
+Com corrotinas, seu código está protegido de interrupções arbitrárias.
+É preciso chamar `await` explicitamente para deixar o resto do programa rodar.
+Em vez de manter travas para sincronizar as operações de múltiplas threads,
+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` 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 uma 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.
+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://fpy.li/aa[`ProcessPoolExecutor`] na documentação de Python]
+====
+[source, python]
+----
+include::../code/19-concurrency/primes/primes.py[tags=IS_PRIME]
+----
+====
+A chamada `is_prime(5_000_111_000_222_021)` leva cerca de 3s no laptop da empresa que estou usando agora.footnote:[É um MacBook Pro 15” de 2018, com uma CPU Intel Core i7 2.2 GHz de 6 núcleos.]
+
+==== Teste Rápido
+
+Dado o que vimos até aqui,
+pare um instante para pensar sobre a seguinte questão, de três partes.
+Uma das partes da resposta é um pouco mais complicada (pelo menos para mim foi).
+
+[quote]
+____
+O que aconteceria à animação após as seguintes modificações,
+presumindo que `n = 5_000_111_000_222_021`—aquele número primo que minha máquina levou 3s para checar:
+
+. Em _spinner_proc.py_, substitua `time.sleep(3)` por uma chamada a `is_prime(n)`?
+. Em _spinner_thread.py_, substitua `time.sleep(3)` por uma chamada a `is_prime(n)`?
+. Em _spinner_async.py_, substitua `await asyncio.sleep(3)` por uma chamada a `is_prime(n)`?
+____
+
+Antes de executar o código ou continuar lendo,
+recomendo dedicar um tempo para considerar as perguntas e formular suas respostas.
+Depois, copie, modifique e rode os exemplos _spinner_ como sugerido.
+
+Agora as respostas, da mais fácil para a mais difícil.
+
+Resposta para multiprocessamento (_spinner_proc.py_)::
+A animação é controlada por um processo filho, então continua girando enquanto o teste de números primos é computado no processo raiz.footnote:[Isso é verdade hoje porque você provavelmente está usando um SO moderno, com _multitarefa preemptiva_. O Windows antes da era NT e o MacOS antes da era OSX não eram "preemptivos", então qualquer processo podia tomar 100% da CPU e paralisar o sistema inteiro. Não estamos inteiramente livres desse tipo de problema hoje, mas confie na minha barba branca: esse tipo de coisa assombrava todos os usuários nos anos 1990, e a única cura era um reset de hardware.]
+
+Resposta para asyncio (_spinner_async.py_)::
+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 é o 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`.
+Quando `is_prime` retornar, `slow` vai retornar também, e `supervisor` retomará a execução, cancelando a tarefa `spinner` antes dela ser executada sequer uma vez.
+O programa parecerá congelado por aproximadamente 3s, e então mostrará a resposta.((("", startref="CMimpact19")))((("", startref="gil19")))((("", startref="SPgil19")))
+
+Resposta para threads (_spinner_thread.py_)::
+A((("threads", "Global Interpreter Lock impact"))) animação é controlada por uma thread secundária, então continua girando enquanto o teste de número primo é computado na thread principal.
+
+Não acertei essa resposta inicialmente:
+eu esperava que a animação congelasse, porque superestimei o impacto da 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 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 mais tempo da CPU, seu programa será mais lento que um programa sequencial.
+
+
+.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 laço de eventos, como no <>.
+
+[[example-19-11]]
+.spinner_async_nap.py: `is_prime` agora é uma corrotina
+====
+[source, python]
+----
+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_] 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 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)` pode ser uma medida paliativa até o código assíncrono ser refatorado para delegar computações de uso intensivo da CPU para outro processo.
+Veremos como 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 <>.
+****
+
+Até aqui experimentamos com uma única chamada para uma função de uso intensivo de CPU. A próxima seção apresenta a execução concorrente de múltiplas chamadas de uso intensivo da CPU.
+
+[[naive_multiprocessing_sec]]
+=== Um pool de processos caseiro
+
+[NOTE]
+====
+
+Não conheço boas traduções para _process pool_.
+Uma alternativa seria "um banco de processos".
+A ideia é que vários processos são iniciados e ficam aguardando tarefas.
+Este padrão amortiza o custo de subir um processo para cada tarefa.
+
+Escrevi((("concurrency models", "process pools", id="CMprocess19")))((("process pools", "example problem")))
+esta seção para mostrar o uso de um banco de processos em cenários de uso intensivo de CPU,
+com filas para distribuir tarefas para os processos, e coletar os resultados.
+O <> apresenta uma forma mais simples de distribuir tarefas para processos:
+o `ProcessPoolExecutor` do pacote `concurrent.futures`, que também usa filas,
+mas elas não são visíveis para o usuário.
+
+====
+
+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:
+
+[source]
+----
+$ python3 sequential.py
+               2  P  0.000001s
+ 142702110479723  P  0.568328s
+ 299593572317531  P  0.796773s
+3333333333333301  P  2.648625s
+3333333333333333     0.000007s
+3333335652092209     2.672323s
+4444444444444423  P  3.052667s
+4444444444444444     0.000001s
+4444444488888889     3.061083s
+5555553133149889     3.451833s
+5555555555555503  P  3.556867s
+5555555555555555     0.000007s
+6666666666666666     0.000001s
+6666666666666719  P  3.781064s
+6666667141414921     3.778166s
+7777777536340681     4.120069s
+7777777777777753  P  4.141530s
+7777777777777777     0.000007s
+9999999999999917  P  4.678164s
+9999999999999999     0.000007s
+Total time: 40.31
+----
+
+Os resultados aparecem em três colunas:
+
+* O número a ser checado.
+* `P` se é um número primo, vazia se é um número composto.
+* Tempo decorrido para checar se aquele número é primo.
+
+Neste exemplo, o tempo total é aproximadamente a soma do tempo de cada checagem,
+mas está computado separadamente, como se vê no <>.
+
+[[primes_sequential_ex]]
+.sequential.py: checagem de números primos em um pequeno conjunto de dados
+====
+[source, python]
+----
+include::../code/19-concurrency/primes/sequential.py[]
+----
+====
+[role="pagebreak-before less_space"]
+<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.
+
+[[proc_based_solution]]
+==== Solução baseada em processos
+
+O((("process pools", "process-based solution"))) próximo exemplo, _procs.py_, mostra o uso de múltiplos processos para distribuir a checagem de números primos por muitos núcleos da CPU.
+Esses são os tempos obtidos com _procs.py_:
+
+[source]
+----
+$ python3 procs.py
+Checking 20 numbers with 12 processes:
+               2  P  0.000002s
+3333333333333333     0.000021s
+4444444444444444     0.000002s
+5555555555555555     0.000018s
+6666666666666666     0.000002s
+ 142702110479723  P  1.350982s
+7777777777777777     0.000009s
+ 299593572317531  P  1.981411s
+9999999999999999     0.000008s
+3333333333333301  P  6.328173s
+3333335652092209     6.419249s
+4444444488888889     7.051267s
+4444444444444423  P  7.122004s
+5555553133149889     7.412735s
+5555555555555503  P  7.603327s
+6666666666666719  P  7.934670s
+6666667141414921     8.017599s
+7777777536340681     8.339623s
+7777777777777753  P  8.388859s
+9999999999999917  P  8.117313s
+20 checks in 9.58s
+----
+
+A última linha dos resultados mostra que _procs.py_ foi 4,2 vezes mais rápido que _sequential.py_.
+
+
+==== Entendendo os tempos decorridos
+
+Observe((("process pools", "understanding elapsed times"))) que o tempo decorrido na primeira coluna é o tempo para checar aquele número específico. Por exemplo, `is_prime(7777777777777753)` demorou quase 8,4s para retornar `True`. Enquanto isso, outros processos estavam checando outros números em paralelo.
+
+Há 20 números para serem checados. Escrevi _procs.py_ para iniciar um número de processos de trabalho igual ao número de núcleos na CPU, como determinado por `multiprocessing.cpu_count()`.
+
+O tempo total neste caso é muito menor que a soma dos tempos decorridos para cada checagem individual. Há algum tempo gasto em iniciar processos e na comunicação entre processos, então o resultado final é que a versão multiprocessos é apenas cerca de 4,2 vezes mais rápida que a sequencial. Isso é bom, mas um pouco desapontador, considerando que o código inicia 12 processos, para usar todos os núcleos desse laptop.
+
+[NOTE]
+====
+
+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 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 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
+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 <>.
+
+[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 <>.
+====
+
+[[ex_primes_procs_top]]
+.procs.py: checagem de primos com múltiplos processos; importações, tipos, e funções
+====
+[source, python]
+----
+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. 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. Preservar `n` 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 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 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`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 para cada processo, para encerrá-los.
+
+[[good_poison_pill_tip]]
+.Laços, sentinelas e pílulas venenosas
+****
+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»] como sentinelas.
+A https://fpy.li/pep661[_PEP 661—Sentinel Values_]
+propõe um tipo sentinela padrão.
+****
+
+Agora vamos estudar a função `main` de _procs.py_ no <>.
+
+[[primes_procs_main_ex]]
+.procs.py: checagem de números primos com múltiplos processos; função `main`
+====
+[source, python]
+----
+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 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 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 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,
+podemos ter _tracebacks_ difíceis de analisar,
+com referências a exceções de `FileNotFoundError` levantadas 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 _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, acontece um erro.
+Esta é a corrida.
+
+Se tiver curiosidade, veja o https://fpy.li/19-25[«diff que mostra o bug e a correção»]
+(mas saiba que depois eu refatorei o exemplo para delegar partes de `main`
+para as funções `start_jobs` e `report`).
+Escrevi um
+https://fpy.li/19-26[_README.md_]
+no mesmo diretório explicando o problema e a solução.((("", startref="PPmulticore19")))
+====
+
+
+==== Experimentando com mais ou menos processos
+
+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]
+----
+$ 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 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 <>.
+
+[[procs_x_time_fig]]
+.Mediana dos tempos de execução para cada número de subprocessos de 1 a 20. O maior tempo mediano foi 40,81s, com 1 processo. O tempo mediano mais baixo foi 10,39s, com 6 processos, indicado pela linha pontilhada.
+image::../images/flpy_1902.png[Mediana dos tempos de execução para cada número de processos, de 1 a 20.]
+
+Neste laptop de 6 núcleos, o menor tempo mediano ocorreu com 6 processos: 10.39s—marcado pela linha pontilhada na <>.
+Seria de se esperar que o tempo de execução aumentasse após 6 processos, devido à disputa pela CPU,
+e ele atingiu um máximo local de 12.51s, com 10 processos.
+Eu não esperava e não sei explicar por que o desempenho melhorou com 11 processos e permaneceu praticamente igual com 13 a 20 processos,
+com tempos medianos apenas ligeiramente maiores que o menor tempo mediano com 6 processos.
+
+[[thread_non_solution_sec]]
+==== 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:[Veja
+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 (<>).
+
+As((("", startref="CMprocess19"))) demais seções nesse capítulo procuram responder à questão:
+
+[quote]
+____
+Dadas as limitações discutidas até aqui, como é possível que Python seja tão bem-sucedido em um mundo de CPUs com múltiplos núcleos?
+____
+
+
+[[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"))) o
+seguinte trecho do artigo
+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 e muito citado desde então:
+
+[quote]
+____
+
+Os fabricantes e arquiteturas de processadores mais importantes, desde Intel e 
+AMD até Sparc e PowerPC, esgotaram o potencial da maioria das abordagens
+tradicionais de aumento do desempenho das CPUs. Ao invés de aumentar a frequência
+do _clock_ [dos processadores] e acelerar o processamento das instruções
+sequenciais, eles estão se voltando em massa para o
+hyper-threading 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 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 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 algoritmo 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,
+graças a bibliotecas e arquiteturas de software que contornam as limitações do CPython.
+
+Agora vamos discutir como Python é usado em administração de sistemas, ciência de dados,
+e desenvolvimento de aplicações para servidores no mundo do processamento distribuído e dos multi-núcleos de 2023.
+
+==== 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 (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 criados pela grande
+comunidade 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 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á muitos projetos populares baseados em Python para gerenciar clusters (_agrupamentos_) de servidores:
+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."
+
+
+==== Ciência de dados
+
+A ciência de dados—incluindo((("multicore processing", "data science")))((("data science"))) a inteligência artificial—e a computação científica estão muito bem servidas pelo Python.
+
+Aplicações nesses campos são de processamento intensivo, mas os usuários de Python se beneficiam de um vasto ecossistema de bibliotecas de computação numérica, escritas em
+C, {cpp}, Fortran, Cython, etc.—muitas delas capazes de aproveitar os benefícios de máquinas multi-núcleo, GPUs, e/ou computação paralela distribuída em clusters heterogêneos.
+
+Em 2021, o ecossistema de ciência de dados de Python já incluía algumas ferramentas impressionantes:
+
+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, que pode ser executado através da rede em máquinas remotas.
+    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 inclui 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»]
+    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 _PyTorch_ pelo Facebook.
+
+https://fpy.li/dask[Dask]::
+    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 recomendo 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 aproveitam o melhor de Python e superam as
+limitações do runtime do CPython.
+
+[[server_side_sec]]
+==== 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
+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*
+
+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 fazem o que precisa ser feito.footnote:[Fonte:
+Thoughtworks Technology Advisory Board, https://fpy.li/19-40[_Technology
+Radar—November 2015_].]
+
+____
+
+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).
+
+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 documentos: _Apache CouchDB_, _MongoDB_
+* Full-text indexes (_índices de texto integral_): _Elasticsearch_, _Apache Solr_
+* Filas de mensagens: _RabbitMQ_, _Redis_
+
+[[one_possible_architecture_fig]]
+.Uma arquitetura possível para um sistema, combinando diversos componentes.footnote:[Diagrama adaptado da Figura 1-1, _Designing Data-Intensive Applications_ de Martin Kleppmann (O'Reilly).]
+image::../images/flpy_1903.png[Arquitetura para um sistema de dados combinando diversos componentes]
+
+Há outros produtos de código aberto extremamente robustos em cada uma dessas categorias.
+Os grandes fornecedores de serviços na nuvem também oferecem suas próprias alternativas proprietárias
+
+O diagrama de Kleppmann é genérico e independente da linguagem—como seu livro.
+Para aplicações de servidor em Python, dois componentes específicos são comumente utilizados:
+
+* Um servidor de aplicação, para distribuir a carga entre várias instâncias da
+aplicação Python. O servidor de aplicação apareceria perto do topo na
+<>, processando as requisições dos clientes antes
+delas chegarem ao código da aplicação.
+
+* Uma fila de tarefas construída em torno da fila de mensagens no lado direito da <>, oferecendo uma API de alto nível e mais fácil de usar, para distribuir tarefas para processos rodando em outras máquinas.
+
+As duas próximas seções exploram esses componentes, recomendados pelas boas práticas de implementações de aplicações Python de servidor.
+
+
+[[wsgi_app_server_sec]]
+==== Servidores de aplicação WSGI
+
+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 <>.
+
+====
+
+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/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 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 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 e 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_.
+
+O _mod_wsgi_ e o _Gunicorn_ só suportam aplicações Web Python,
+enquanto o _uWSGI_ e o _NGINX Unit_ funcionam também com outras linguagens.
+Para saber mais, consulte a documentação de cada um deles.
+
+O ponto principal: todos esses servidores de aplicação podem, potencialmente, utilizar todos os núcleos de CPU no servidor, criando múltiplos processos Python para executar aplicações Web tradicionais escritas no bom e velho código sequencial em _Django_, _Flask_, _Pyramid_, etc.
+Isto explica como é possível ganhar a vida como desenvolvedor Python em aplicações _server side_ sem nunca ter estudado os módulos `threading`, `multiprocessing`, ou `asyncio`:
+o servidor de aplicação lida de forma transparente com a concorrência.
+
+[[asgi_note]]
+.ASGI—Asynchronous Server Gateway Interface
+[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`, 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 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.
+
+[[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 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 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.
+
+[NOTE]
+====
+
+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 Django _produz_ requisições de serviç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] 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.
+* Assegurar-se de que algo seja feito, através de 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 à medida que a demanda cresce.
+Por isso a _Celery_ e a _RQ_ são chamadas de filas de tarefas distribuídas.
+
+Lembre-se de que nosso simples _procs.py_ (<>) usava duas filas:
+uma para requisitar 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 a _RabbitMQ_ ou a _Amazon SQS_, e também alguns bancos de dados para armazenar resultados.
+
+Isto encerra nossa introdução à concorrência em Python.
+Os dois próximos capítulos continuam nesse tema com mais código, demonstrando 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:
+
+* Threads, com o pacote `threading`
+* Processo, com `multiprocessing`
+* Corrotinas assíncronas com `asyncio`
+
+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.
+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 interrompe periodicamente 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 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últiplos núcleos. A GIL de Python
+faz com que as threads sejam mais lentas 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 <>.
+Por exemplo, a GIL não afeta muitos dos casos de uso de Python em administração de sistemas.
+Por outro lado, as comunidades de ciência de dados e de desenvolvimento para servidores evitaram os problemas com a GIL usando soluções robustas, criadas sob medida para suas necessidades específicas.
+As últimas duas seções mencionaram os dois elementos comuns que sustentam o uso de Python em aplicações de servidor escaláveis:
+servidores de aplicação WSGI e filas de tarefas distribuídas.
+
+[[concurrency_further_reading_sec]]
+=== Para saber mais
+
+Este((("concurrency models", "further reading on", id="CMfurther19"))) capítulo tem uma extensa lista de referências, então a dividi em subseções.
+
+[[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 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 a _threading_ em 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).
+
+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://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) têm 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),
+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 a _Celery_.
+
+_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]
+____
+Estes temas se repetem com frequência:
+
+* Programação com threads torna o código difícil de analisar.
+
+* Programação com threads é um modelo ineficiente para concorrência em larga escala (milhares de tarefas concorrentes).
+____
+
+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 até os fáceis são desafiadores.
+
+
+==== A GIL
+
+Se((("Global Interpreter Lock (GIL)"))) você ficou curioso sobre a GIL, lembre-se de 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-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_):
+um mergulho profundo no suporte nativo de Python a threads e processos,
+incluindo o gerenciamento da GIL por extensões usando a API C/Python.
+
+David Beazley apresentou uma exploração detalhada em https://fpy.li/19-65[_Understanding the Python GIL_]
+(_Entendendo a GIL de Python_).footnote:[Agradeço a Lucas Brunialti por me enviar um link para essa palestra.]
+No slide 54 da https://fpy.li/19-66[apresentação],
+Beazley relata um aumento no tempo de processamento de um benchmark específico 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 um novo algoritmo para a GIL) no relatório de bug submetido por Beazley:
+https://fpy.li/19-68[_Python issue #7946_].
+
+
+==== Concorrência além da biblioteca padrão
+
+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 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_] 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_, escrito inteiramente em Python."
+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 publicaram o livro gratuitamente online em https://fpy.li/19-76[_cosmicpython.com_].
+
+Duas bibliotecas elegantes e fáceis de usar para 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 laço `for` por múltiplas CPUs.
+Ambos os pacotes são baseados na biblioteca _multiprocessing_.
+
+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 para execução em paralelo.
+Snow é o autor da https://fpy.li/pep554[_PEP 554-Multiple Interpreters in the Stdlib_]
+(Múltiplos interpretadores na biblioteca padrão).
+A PEP 554 assenta as bases para melhorias futuras,
+que podem um dia permitir que Python use múltiplos núcleos sem pagar os altos custos de inicialização, memória, e comunicação
+do _multiprocessing_.
+Um dos grandes empecilhos é a iteração entre múltiplos subinterpretadores ativos e extensões que assumem a existência de um único interpretador.
+
+Mark Shannon—também um mantenedor do Python—criou uma
+https://fpy.li/19-80[«tabela»] bem ú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
+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 por mensagens
+enviadas por 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-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_],
+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 direta ao Python,
+mas são importantes para aprofundar o tema das arquiteturas escaláveis com
+suporte à concorrência.
+
+==== Concorrência e escalabilidade para além de Python
+
+https://fpy.li/19-92[_RabbitMQ in Action_] (Manning), de Alvaro Videla e Jason J. W. Williams, é uma introdução muito bem escrita ao _RabbitMQ_ e ao padrão AMQP (_Advanced Message Queuing Protocol_, Protocolo Avançado de Enfileiramento de Mensagens), com exemplos em Python, PHP, e Ruby.
+Independente do resto de seu stack tecnológico, e mesmo se você planeja usar _Celery_ com _RabbitMQ_ debaixo dos panos,
+recomendo esse livro por sua abordagem dos conceitos, da motivação e dos modelos das filas de mensagem distribuídas, bem como a operação e configuração do _RabbitMQ_ em larga escala.
+
+Aprendi muito lendo https://fpy.li/19-93[_Seven Concurrency Models in Seven Weeks_],
+de Paul Butcher (Pragmatic Bookshelf), que traz o eloquente subtítulo _When Threads Unravel_.footnote:[NT: Trocadilho intraduzível com thread no sentido de "fio" ou "linha", algo como "Quando as linhas desfiam."]
+O capítulo 1 do livro apresenta os conceitos centrais e os desafios da programação com threads e travas em Java.footnote:[As APIs Python `threading` e `concurrent.futures` foram fortemente influenciadas pela biblioteca padrão de Java.]
+Os outros seis capítulos do livro são dedicados ao que o autor considera as melhores alternativas para programação concorrente e paralela, e como funcionam com diferentes linguagens, ferramentas e bibliotecas.
+Os exemplos usam Java, Clojure, Elixir, e
+C (no capítulo sobre programação paralela com o framework https://fpy.li/19-94[OpenCL]).
+O modelo CSP é exemplificado com código Clojure, e não Go—que popularizou esta abordagem.
+Elixir é a linguagem dos exemplos que ilustram o modelo de atores.
+Um https://fpy.li/19-95[«capítulo bonus»] (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 criou uma série de artigos 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 da série»] é 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_]
+(Projetando aplicações intensivas em dados), 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 assistir ao excelente 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)
+(https://fpy.li/19-99[«video só para assinantes»] na _O'Reilly Learning Platform_).
+Apesar do título, aos 9:35 no vídeo, Cesarini explica:
+
+[quote]
+____
+Muito pouco do que vou dizer será específico de Erlang […].
+Resta o fato de que o Erlang remove muitas dificuldades acidentais no desenvolvimento de sistemas resilientes 
+que nunca falham, além de serem escaláveis.
+Então será mais fácil se vocês usarem Erlang ou uma linguagem rodando na máquina virtual Erlang.
+____
+
+Aquele workshop foi baseado nos últimos quatro capítulos do
+https://fpy.li/19-100[_Designing for Scalability with Erlang/OTP_]
+de Francesco Cesarini e Steve Vinoski (O'Reilly).
+
+Desenvolver((("KISS principle"))) sistemas distribuídos é desafiador e empolgante, mas cuidado com a
+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_: "Deixe Simples, Idiota")
+continua sendo uma recomendação sábia de engenharia.
+
+Veja também o artigo
+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 "têm desempenho pior que uma thread em todas as configurações documentadas."
+
+Estes((("", startref="CMfurther19"))) resultados me lembram uma piada clássica entre _hackers_:
+
+[quote]
+____
+Meu script Perl é mais rápido que seu cluster Hadoop.
+____
+
+
+[[concurrency_models_soapbox]]
+.Ponto de vista
+****
+
+*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 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.
+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 a instrução `RETURN` saltar de volta.
+Subrotinas 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ênçãos.
+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.
+Pode ser fácil para um projetista de linguagens acrescentá-las a uma linguagem,
+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 _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 deste
+capítulo. Então temos aqui os 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 ter um estado interno, mas não pode compartilhar este estado com outros atores.
+* Atores só podem se comunicar enviando e recebendo mensagens.
+* Mensagens contém só 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 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ó acho que lidar com tais mecanismos de baixo nível não é um bom uso do meu tempo
+quando escrevo aplicações—e não módulos do kernel, drivers de hardware, ou servidores de bancos de dados.
+
+Sempre me reservo o direito de mudar de opinião. Mas neste momento, estou
+convencido de 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
new file mode 100644
index 00000000..401de7eb
--- /dev/null
+++ b/online/cap20.adoc
@@ -0,0 +1,1265 @@
+[[ch_executors]]
+== Executores concorrentes
+:example-number: 0
+:figure-number: 0
+
+[quote, Michele Simionato, profundo pensador de Python.]
+____
+Quem fala mal de threads são tipicamente programadoras de sistemas,
+que têm em mente casos de uso que a programadora de aplicações típica nunca encontrará na vida.[...]
+Em 99% dos casos de uso que a programadora de aplicações poderá encontrar,
+o modelo simples de disparar um monte de threads independentes
+e coletar os resultados em uma fila é tudo que se precisa
+saber.footnote:[Trecho do texto 
+https://fpy.li/20-1[_Threads, processes and concurrency in Python: some thoughts_]
+(Threads, processos e concorrência em Python: algumas reflexões), resumido assim pelo autor:
+"Removendo exageros sobre a (não-)revolução dos múltiplos núcleos e alguns comentários
+sensatos (oxalá) sobre threads e outras formas de concorrência."]
+____
+
+
+Este((("concurrent executors", "purpose of"))) capítulo se concentra nas subclasses
+de `concurrent.futures.Executor`, que incorporam o modelo descrito por
+Michele Simionato: "disparar um monte
+de threads independentes e coletar os resultados em uma fila".
+Executores concorrentes implementam internamente este modelo,
+não apenas com threads mas também com processos—que oferecem melhor desempenho
+em tarefas de processamento intensivas no uso de CPUs.
+
+Também((("futures", "definition of term"))) introduzo aqui o conceito de
+_futures_—objetos que representam a execução assíncrona de uma operação,
+similares aos _promises_ de JavaScript.
+Esta ideia é a fundação de `concurrent.futures`
+bem como do pacote `asyncio`, assunto do <>.
+
+
+=== Novidades neste capítulo
+Mudei((("concurrent executors", "significant changes to"))) o título deste capítulo de
+"Concorrência com futures" para "Executores concorrentes",
+porque os executores são o recurso de alto nível mais importante tratado aqui.
+_Futures_ são objetos de baixo nível, tratados na <>,
+mas quase invisíveis no resto do capítulo.
+
+Todos os exemplos de clientes HTTP agora usam a biblioteca
+https://fpy.li/httpx[_HTTPX_], que oferece APIs síncronas e assíncronas.
+
+Simplifiquei a configuração para os experimentos na <>,
+porque desde o Python 3.7 o pacote https://fpy.li/20-2[`http.server`]
+é _multi-thread_. Antes, o `http.server` só usava uma thread,
+então não servia para experimentos com clientes concorrentes,
+o que me obrigou a usar um servidor externo na primeira edição.
+
+A <> agora demonstra como um executor simplifica o
+código que vimos na <>.
+
+Por fim, movi a maior parte da teoria para o <>,
+_Modelos de concorrência em Python_.
+
+[[ex_web_downloads_sec]]
+=== Downloads concorrentes da Web
+
+A((("network I/O", "essential role of concurrency in",
+id="IOconcur20")))((("concurrent executors", "concurrent Web downloads",
+id="CEwebdown20"))) concorrência é essencial para uma comunicação eficiente via
+rede: em vez de esperar de braços cruzados por respostas de máquinas remotas, a
+aplicação pode fazer outra coisa enquanto a resposta não
+chega.
+
+Para demonstrar, escrevi três programas simples que baixam da Web imagens de 20 bandeiras de países.
+O primeiro, _flags.py_, roda sequencialmente:
+ele só requisita a imagem seguinte quando a anterior foi baixada e salva localmente.
+Os outros dois scripts fazem downloads concorrentes:
+eles requisitam várias imagens quase ao mesmo tempo, e as salvam conforme chegam.
+O script _flags_threadpool.py_ usa o pacote `concurrent.futures`,
+enquanto _flags_asyncio.py_ usa `asyncio`.
+
+Os scripts baixam imagens de _https://fluentpython.com_, que usa uma CDN
+(_Content Delivery Network_, Rede de Entrega de Conteúdo),
+então você pode observar resultados mais lentos nos primeiros testes.
+Obtive os resultados no <> após vários testes,
+então o cache da CDN estava "quente" (carregado com os dados).
+
+O <> mostra o resultado da execução dos três scripts, três vezes cada um.
+
+[[ex_flags_sample_runs]]
+.Saida dos scripts flags.py, flags_threadpool.py, e flags_asyncio.py
+====
+[source, text]
+----
+$ python3 flags.py
+BD BR CD CN DE EG ET FR ID IN IR JP MX NG PH PK RU TR US VN  <1>
+20 flags downloaded in 7.26s  <2>
+$ python3 flags.py
+BD BR CD CN DE EG ET FR ID IN IR JP MX NG PH PK RU TR US VN
+20 flags downloaded in 7.20s
+$ python3 flags.py
+BD BR CD CN DE EG ET FR ID IN IR JP MX NG PH PK RU TR US VN
+20 flags downloaded in 7.09s
+$ python3 flags_threadpool.py
+DE BD CN JP ID EG NG BR RU CD IR MX US PH FR PK VN IN ET TR
+20 flags downloaded in 1.37s  <3>
+$ python3 flags_threadpool.py
+EG BR FR IN BD JP DE RU PK PH CD MX ID US NG TR CN VN ET IR
+20 flags downloaded in 1.60s
+$ python3 flags_threadpool.py
+BD DE EG CN ID RU IN VN ET MX FR CD NG US JP TR PK BR IR PH
+20 flags downloaded in 1.22s
+$ python3 flags_asyncio.py  <4>
+BD BR IN ID TR DE CN US IR PK PH FR RU NG VN ET MX EG JP CD
+20 flags downloaded in 1.36s
+$ python3 flags_asyncio.py
+RU CN BR IN FR BD TR EG VN IR PH CD ET ID NG DE JP PK MX US
+20 flags downloaded in 1.27s
+$ python3 flags_asyncio.py
+RU IN ID DE BR VN PK MX US IR ET EG NG BD FR CN JP PH CD TR  <5>
+20 flags downloaded in 1.42s
+----
+====
+
+<1> A saída de cada execução começa com os códigos dos países de cada bandeira a medida que as imagens são baixadas, e termina com uma mensagem mostrando o tempo decorrido. Note que as siglas dos países estão em ordem alfabética, para podermos comparar com a ordem dos resultados nas versões concorrentes.
+
+<2> _flags.py_ precisou em média de 7,18s para baixar 20 imagens.
+
+<3> A média para _flags_threadpool.py_ foi 1,40s.
+
+<4> Já _flags_asyncio.py_, obteve um tempo médio de 1,35s.
+
+<5> Note a ordem dos códigos dos países: nos scripts concorrentes, as imagens foram baixadas em ordem diferente a cada vez.
+
+As versões concorrentes são mais de cinco vezes mais rápidas que o script sequencial,
+mas entre as versões concorrentes não há diferença significativa de desempenho. 
+Nestes exemplos, a tarefa é baixar 20 arquivos, com poucos kilobytes cada um.
+Se você escalar a tarefa para centenas de downloads,
+os scripts concorrentes podem superar o código sequencial por um fator de 20 ou mais.
+
+[WARNING]
+====
+
+Ao testar clientes HTTP concorrentes usando servidores Web públicos, você pode
+acidentalmente lançar um ataque de negação de serviço (DoS, _Denial of Service_,
+negação de serviço),
+ou se tornar suspeito de tentar um ataque. No caso do
+<> não há problema, pois aqueles scripts estão codificados
+para realizar apenas 20 requisições. Mais adiante neste capítulo usaremos o
+pacote `http.server` de Python para executar localmente outros testes com
+centenas de requisições.
+
+====
+
+Vamos agora estudar as implementações de dois dos scripts testados no
+<>: _flags.py_ e _flags_threadpool.py_. Vou deixar o
+terceiro, _flags_asyncio.py_, para o <>, mas queria demonstrar os três
+juntos para fazer duas observações:
+
+. Independente dos elementos de concorrência que você use—threads ou
+corrotinas—haverá um ganho enorme de desempenho sobre código sequencial em
+operações de E/S de rede, se o script for escrito corretamente.
+
+. Para clientes HTTP que podem controlar quantas requisições eles fazem, não há
+diferenças significativas de desempenho entre threads e
+corrotinas.footnote:[Para servidores que podem receber requisições de muitos
+clientes, há uma diferença: as corrotinas escalam melhor, pois usam menos
+memória que as threads, e também reduzem o custo das mudanças de contexto, que
+mencionei na <>.]
+
+Agora vamos ao código.((("", startref="IOconcur20")))
+
+
+==== Um script de download sequencial
+
+O <> contém((("network I/O", "sequential download script",
+id="IOsequen20"))) a implementação de _flags.py_, o primeiro script que rodamos
+no <>. Não é muito interessante, mas vamos reutilizar a
+maior parte do código e das configurações para implementar os scripts
+concorrentes, então ele merece alguma atenção.
+
+[NOTE]
+====
+
+Por uma questão didática, o <> não faz tratamento de erros.
+Vamos tratar exceções em outros exemplos, mas agora vamos focar na
+estrutura básica do código, para facilitar a comparação deste script com os
+scripts que usam concorrência.
+
+====
+
+[[flags_module_ex]]
+.flags.py: script de download sequencial; algumas funções serão reutilizadas pelos outros scripts
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags.py[tags=FLAGS_PY]
+----
+====
+
+<1> Importa a biblioteca `httpx`. Ela não é parte da biblioteca padrão. Assim,
+por convenção, a importação aparece após os módulos da biblioteca padrão e uma
+linha em branco.
+
+<2> Lista do código de país ISO 3166 para os 20 países mais populosos, em ordem
+decrescente de população.
+
+<3> O diretório com as imagens das bandeiras.footnote:[As imagens são
+originalmente do https://fpy.li/20-4[CIA World Factbook], uma publicação de
+domínio público do governo norte-americano. Copiei as imagens para o meu site,
+para evitar o risco de lançar um ataque de DoS contra _cia.gov_.]
+
+<4> Diretório local onde as imagens são salvas.
+
+<5> Salva os bytes de `img` em `filename` no `DEST_DIR`.
+
+<6> Dado um código de país, constrói a URL e baixa a imagem, retornando o
+conteúdo binário da resposta.
+
+<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 neste exemplo, mas eu queria destacar
+essa importante diferença entre _HTTPX_ e _requests_. Além disso, definir
+`follow_redirects=True` no exemplo permite que eu coloque os
+arquivos de imagem em outro lugar no futuro. Considero sensata a configuração default
+do _HTTPX_, `follow_redirects=False`, pois
+redirecionamentos inesperados podem mascarar requisições desnecessárias e
+complicar o diagnóstico de erros.]
+
+<9> Não há tratamento de erros neste script, mas o método `.raise_for_status` 
+lança uma exceção se o status da resposta HTTP não está na faixa 2XX, para evitar
+falhas silenciosas.
+
+<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; devolve 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 quebra de linha no final de
+cada `print` por um espaço, assim todos os códigos de país aparecem
+na mesma linha. O argumento `flush=True` é necessário porque,
+por default, o Python usa um buffer de linha na saída padrão, 
+então só após uma quebra de linha o resultado seria visível no terminal.
+A opção `flush=True` força o esvaziamento do buffer,
+então podemos ver as siglas aparecendo progressivamente.
+
+<13> `main` precisa ser invocada passando a função que fará os downloads; assim
+podemos usar `main` como uma função de biblioteca com outras implementações de
+`download_many` nos exemplos de `threadpool` e `ascyncio`.
+
+<14> Cria o `DEST_DIR` se necessário; não acusa erro se o diretório existir.
+
+<15> Registra e apresenta o tempo decorrido após rodar a função `downloader`.
+
+<16> Invoca `main` com a função `download_many`.
+
+[TIP]
+====
+A biblioteca https://fpy.li/httpx[_HTTPX_] é inspirada no pacote pythônico
+https://fpy.li/20-5[_requests_],
+mas foi desenvolvida sobre bases mais modernas.
+Em particular, _HTTPX_ oferece APIs síncronas e assíncronas,
+então podemos usá-la em todos os exemplos de clientes HTTP neste capítulo e no próximo.
+A biblioteca padrão do Python contém o módulo `urllib.request`,
+mas sua API é somente síncrona, e não é nada amigável.
+====
+
+Não há nada de novo em _flags.py_, mas serve de base para comparação
+com outros scripts, e o usei como uma biblioteca, para evitar código redundante ao implementá-los.
+Vamos ver agora uma reimplementação usando `concurrent.futures`.((("", startref="IOsequen20")))
+
+[[downloading_with_futures_sec]]
+==== Download com `concurrent.futures`
+
+Os((("network I/O", "downloading with concurrent.futures",
+id="IOfutures20")))((("concurrent.futures", "downloading with", id="confut20")))
+principais recursos do pacote `concurrent.futures` são as classes
+`ThreadPoolExecutor` e `ProcessPoolExecutor`, que implementam uma API para
+submeter _callables_ (invocáveis) para execução em um banco de threads ou
+processos (_thread pool_ ou _process pool_).
+As classes gerenciam de forma transparente um banco de threads ou
+processos de trabalho, e filas para distribuição de tarefas e coleta de
+resultados. Mas a interface é de um nível muito alto,
+então não precisamos lidar diretamente com estes
+componentes em um caso de uso simples como nossos downloads de bandeiras.
+
+O <> mostra a forma mais fácil de implementar os downloads de forma concorrente, usando o método `ThreadPoolExecutor.map`.
+
+[[flags_threadpool_ex]]
+.flags_threadpool.py: script de download com threads, usando `futures.ThreadPoolExecutor`
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags_threadpool.py[tags=FLAGS_THREADPOOL]
+----
+====
+<1> Reutiliza algumas funções do módulo `flags` (<>).
+<2> Função para baixar uma única imagem; é ela que cada thread de trabalho vai executar.
+<3> Instancia o `ThreadPoolExecutor` como um gerenciador de contexto;
+o método `executor.__exit__` vai invocar `executor.shutdown(wait=True)`,
+que bloqueia até que todas as threads terminem de rodar.
+<4> O método `map` é similar à função embutida `map`, mas a função `download_one` será chamada de forma concorrente por múltiplas threads; ele devolve um gerador que você pode iterar para recuperar o valor devolvido por cada chamada da função—neste caso, cada chamada a `download_one` vai retornar um código de país.
+<5> Devolve 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 devolvido 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 laço `for` na função `download_many` do <>. Esta é
+uma refatoração comum quando em código concorrente: transformar
+o corpo de um laço `for` sequencial em uma função a ser chamada de modo
+concorrente.
+
+[TIP]
+====
+O <> é muito curto porque pude reutilizar a maior parte das funções do script sequencial _flags.py_.
+Uma das melhores características do `concurrent.futures` é facilitar a execução concorrente de código sequencial.
+====
+
+O construtor de `ThreadPoolExecutor` recebe muitos argumentos além dos mostrados
+aqui, mas o primeiro e mais importante é `max_workers`, definindo o número
+máximo de threads de trabalho a serem executadas. Quando `max_workers` é `None`
+(o default), `ThreadPoolExecutor` decide seu valor
+usando a seguinte expressão, desde o Python 3.8:
+
+[source, python]
+----
+max_workers = min(32, os.cpu_count() + 4)
+----
+
+A justificativa é apresentada na https://fpy.li/aj[documentação de `ThreadPoolExecutor`]:
+
+[quote]
+____
+
+Este valor default reserva pelo menos 5 threads de trabalho para tarefas de E/S.
+Ele utiliza no máximo 32 núcleos da CPU para tarefas de processamento que
+liberam a GIL. E ele evita usar implicitamente recursos demais em
+máquinas com muitos núcleos.
+
+`ThreadPoolExecutor` agora também reutiliza threads de inativas antes
+de iniciar a quantidade de threads de trabalho definida em `max_workers`.
+
+____
+
+Concluindo: o valor default calculado de `max_workers` é razoável, e
+`ThreadPoolExecutor` evita iniciar novas threads de trabalho desnecessariamente.
+Entender a lógica por trás de `max_workers` pode ajudar a decidir quando e como
+definir um valor diferente do default em seu código.
+
+A biblioteca se chama `concurrency.futures`, mas não há qualquer _future_
+à vista no <>,
+então você pode estar se perguntando onde eles estão.
+A próxima seção explica.((("", startref="confut20")))((("", startref="IOfutures20")))
+
+[[where_futures_sec]]
+==== Onde estão os _futures_?
+
+Os((("network I/O", "role of futures", id="IOfuturesrole20")))((("futures",
+"basics of", id="Fbasic20"))) _futures_ (literalmente "futuros") são componentes
+centrais de `concurrent.futures` e de `asyncio`, mas como usuários dessas
+bibliotecas, raramente os vemos. O <> depende de _futures_
+por trás do palco, mas o código apresentado não lida diretamente com objetos
+desta classe. Esta seção apresenta uma visão geral dos _futures_, com um exemplo
+mostrando-os em ação.
+
+Desde o Python 3.4, há duas classes chamadas `Future` na biblioteca padrão:
+`concurrent.futures.Future` e `asyncio.Future`.
+Elas têm o mesmo propósito:
+uma instância de qualquer uma das classes `Future` representa um processamento adiado,
+que pode ou não ter sido completado.
+Isso é algo similar à classe `Deferred` no Twisted,
+a classe `Future` no Tornado, e objetos `Promise` em JavaScript moderno.
+
+Os _futures_ encapsulam operações pendentes de forma que possamos colocá-los em filas,
+verificar se terminaram, e recuperar resultados (ou exceções) quando eles ficam disponíveis.
+
+É importante entender que eu e você não devemos criar _futures_ diretamente:
+eles devem ser instanciados exclusivamente pelo framework de concorrência,
+seja ele `concurrent.futures` ou `asyncio`.
+O motivo é que uma instância de `Future` representa algo que será executado em algum momento,
+portanto precisa ser agendado para rodar, e quem agenda tarefas é o framework.
+
+Especificamente, instâncias de `concurrent.futures.Future` são criadas como resultado de submeter um objeto invocável (_callable_)
+para execução a uma subclasse de `concurrent.futures.Executor`.
+Por exemplo, o método `Executor.submit()` recebe um invocável, agenda sua execução, e devolve um `Future`.
+
+O código da aplicação não deve mudar o estado de um _future_:
+o framework de concorrência muda o estado de um _future_ quando
+o processamento que ele representa termina, e não controlamos quando isso acontece.
+
+Os dois tipos de `Future` têm um método `.done()` não-bloqueante, que devolve um `bool` informando se o invocável encapsulado por aquele `future` foi ou não executado.
+Entretanto, em vez de perguntar repetidamente se um _future_ terminou, o código cliente em geral pede para ser notificado.
+Por isso as duas classes `Future` têm um método `.add_done_callback()`:
+você passa a ele uma função que será invocada com o _future_ como único argumento, quando o _future_ tiver terminado.
+Aquela função de callback será invocada na mesma thread ou processo de trabalho que rodou a função encapsulada no _future_.
+
+Há também um método `.result()`, que funciona igual nas duas classes quando a execução do _future_ termina:
+ele devolve o resultado do invocável, ou relança qualquer exceção que possa ter aparecido quando o invocável foi executado.
+Entretanto, quando o _future_ não terminou, o comportamento do método `result` é bem diferente entre os dois sabores de `Future`.
+Em uma instância de `concurrency.futures.Future`,
+invocar `f.result()` vai bloquear a thread que chamou até o resultado ficar pronto.
+Um argumento `timeout` opcional pode ser passado, e se o _future_ não tiver terminado após aquele tempo, o método `result` gera um `TimeoutError`.
+O método `asyncio.Future.result` não suporta um timeout, e `await` é a forma preferencial de obter o resultado de _futures_ no `asyncio`—mas `await` não funciona com instâncias de `concurrency.futures.Future`.
+
+Várias funções em ambas as bibliotecas devolvem _futures_; outras os usam em sua implementação de uma forma transparente para o usuário.
+Um exemplo deste último caso é o `Executor.map`, que vimos no <>:
+ele devolve 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.
+
+==== Lidando com _futures_
+
+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 devolve 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 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`.
+O código de `download_many` aumentou de 5 para 17 linhas,
+mas agora podemos inspecionar os misteriosos _futures_.
+As outras funções são idênticas às do <>.
+
+[[flags_threadpool_futures_ex]]
+.flags_threadpool_futures.py: substitui `executor.map` por `executor.submit` e `futures.as_completed` na função `download_many`
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags_threadpool_futures.py[tags=FLAGS_THREADPOOL_AS_COMPLETED]
+----
+====
+[role="pagebreak-before less_space"]
+<1> Para esta demonstração, usa apenas os cinco países mais populosos.
+<2> Configura `max_workers` para `3`, para podermos ver os _futures_ pendentes na saída.
+<3> Itera pelos códigos de país em ordem alfabética, para deixar claro que os resultados vão aparecer fora de ordem.
+<4> `executor.submit` agenda o invocável a ser executado, e devolve o `future` representando essa operação pendente.
+<5> Armazena cada `future`, para podermos recuperá-los mais tarde com `as_completed`.
+<6> Mostra uma mensagem com o código do país e seu respectivo `future`.
+<7> `as_completed` produz _futures_ à medida que eles terminam.
+<8> Recupera o resultado deste `future`.
+<9> Exibe o `future` e seu resultado.
+
+A chamada a `future.result()` nunca bloqueará a thread neste
+exemplo, pois `future` está vindo de `as_completed`. O
+<> mostra a saída de uma execução do
+<>.
+
+[[flags_threadpool_futures_run]]
+.Saída de flags_threadpool_futures.py
+====
+[source, text]
+----
+$ python3 flags_threadpool_futures.py
+Scheduled for BR:  <1>
+Scheduled for CN: 
+Scheduled for ID: 
+Scheduled for IN:  <2>
+Scheduled for US: 
+CN  result: 'CN' <3>
+BR ID  result: 'BR' <4>
+ result: 'ID'
+IN  result: 'IN'
+US  result: 'US'
+
+5 downloads in 0.70s
+----
+====
+<1> Os _futures_ são agendados em ordem alfabética; o `repr()` de um `future` mostra seu estado: os três primeiros estão `running`, pois há três threads de trabalho.
+<2> Os dois últimos `futures` estão `pending`; esperando pelas threads de trabalho.
+<3> O primeiro `CN` aqui é a saída de `download_one` em uma thread de trabalho; o resto da linha é a saída de `download_many`.
+<4> Aqui, duas threads exibem códigos antes que `download_many` na thread principal possa mostrar o resultado da primeira thread.
+
+[TIP]
+====
+Recomendo brincar com _flags_threadpool_futures.py_.
+Se você o rodar várias vezes, verá a ordem dos resultados variar.
+Aumentar `max_workers` para `5` aumentará a variação na ordem dos resultados.
+Diminuir aquele valor para `1` fará o script rodar sequencialmente, e a ordem dos resultados será sempre a ordem das chamadas a `submit`.
+====
+
+Vimos duas variantes do script de download usando `concurrent.futures`:
+uma no <> com `ThreadPoolExecutor.map` e
+uma no <> com `futures.as_completed`.
+Se tem curiosidade sobre o código de _flags_asyncio.py_,
+veja o <> do <>, onde ele é explicado.
+
+Agora vamos dar uma olhada rápida em um modo simples de evitar a GIL para
+tarefas que usam a CPU intensivamente, usando 
+`concurrent.futures`.((("", startref="Fbasic20")))((("", startref="IOfuturesrole20")))((("", startref="CEwebdown20")))
+
+[[launching_processes_sec]]
+=== 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://fpy.li/am[página de documentação] de
+`concurrent.futures` tem o 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 vários processos Python usando a classe
+`ProcessPoolExecutor`.
+
+As classes `ProcessPoolExecutor` e `ThreadPoolExecutor` implementam a interface
+https://fpy.li/20-9[`Executor`],
+então é fácil mudar de uma solução baseada em threads para uma baseada em processos usando `concurrent.futures`.
+
+Não há vantagem em usar um `ProcessPoolExecutor` no exemplo de download de bandeiras ou em qualquer tarefa limitada por E/S.
+É fácil comprovar, alterando as seguintes linhas no <>:
+
+[source, python]
+----
+def download_many(cc_list: list[str]) -> int:
+    with futures.ThreadPoolExecutor() as executor:
+----
+
+para:
+
+[source, python]
+----
+def download_many(cc_list: list[str]) -> int:
+    with futures.ProcessPoolExecutor() as executor:
+----
+
+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 of `ProcessPoolExecutor` é em tarefas de uso intensivo da CPU.
+Vamos voltar ao exemplo de teste de checagem de números primos da <>, e reescrevê-lo com `concurrent.futures`.
+
+[[multicore_prime_redux_sec]]
+==== A volta do checador de primos multi-núcleos
+
+Na <>, estudamos _procs.py_, um script que checava se alguns números grandes eram primos usando `multiprocessing`.
+No <> resolvemos o mesmo problema com o programa _proc_pool.py_, usando um `ProcessPool.Executor`.
+Do primeiro `import` até a chamada a `main()` no final, _procs.py_ tem 43 linhas de código não-vazias, e _proc_pool.py_ tem 31—uma redução de 28%.
+
+[[proc_pool_py]]
+.proc_pool.py: _procs.py_ reescrito com `ProcessPoolExecutor`
+====
+[source, python]
+----
+include::../code/20-executors/primes/proc_pool.py[tags=PRIMES_POOL]
+----
+====
+
+<1> Não precisamos importar `multiprocessing`, `SimpleQueue` etc.;
+`concurrent.futures` esconde tudo isso.
+
+<2> A tupla `PrimeResult` e a função `check` são as mesmas que vimos em
+_procs.py_, mas não precisamos mais das filas nem da função `worker`.
+
+<3> Em vez de definir quantos processos de trabalho serão
+usados se um argumento não for passado na linha de comando, atribuímos `None` a
+`workers` e deixamos o `ProcessPoolExecutor` decidir.
+
+<4> Aqui criei o `ProcessPoolExecutor` antes do bloco `with` em `⑦`, para poder
+mostrar o número real de processos na próxima linha.
+
+<5> `_max_workers_` é um atributo de instância não documentado de um
+`ProcessPoolExecutor`. Decidi usá-lo para mostrar o número de processos de
+trabalho criados quando a variável `workers` é `None`. O _Mypy_
+reclama quando eu acesso esse atributo, então coloquei o comentário `type:
+ignore` para silenciar o aviso.
+
+<6> Ordena os números a serem checados em ordem descendente. Isto vai mostrar a
+diferença no comportamento de _proc_pool.py_ quando comparado a _procs.py_. Veja
+a explicação após esse exemplo.
+
+<7> Usa o `executor` como um gerenciador de contexto.
+
+<8> A chamada a `executor.map` devolve as instâncias de `PrimeResult` retornadas
+por `check` na mesma ordem dos argumentos `numbers`.
+
+Se você rodar o <>, verá os resultados aparecendo em ordem rigorosamente descendente, como mostrado no <>.
+Por outro lado, a ordem da saída de _procs.py_ (da <>) é determinada pela dificuldade em checar se cada número é ou não primo.
+Por exemplo, _procs.py_ mostra o resultado para 7777777777777777 próximo ao topo, pois ele tem 7 como divisor, então `is_prime` rapidamente determina que ele não é primo.
+
+O número 7777777536340681 é 88191709^2^. Como este fator é grande, `is_prime` vai demorar mais para determinar que é um número composto,
+e ainda mais para descobrir que 7777777777777753 é primo. Por isso esses números aparecem perto do final da saída do _procs.py_ original.
+
+Ao rodar _proc_pool.py_, podemos observar não apenas a ordem descendente dos resultados, mas também que o programa parece emperrar após mostrar o resultado de 9999999999999999.
+
+[[proc_pool_py_output]]
+.Saída de proc_pool.py
+====
+[source, text]
+----
+$ ./proc_pool.py
+Checking 20 numbers with 12 processes:
+9999999999999999     0.000024s  # <1>
+9999999999999917  P  9.500677s  # <2>
+7777777777777777     0.000022s  # <3>
+7777777777777753  P  8.976933s
+7777777536340681     8.896149s
+6666667141414921     8.537621s
+6666666666666719  P  8.548641s
+6666666666666666     0.000002s
+5555555555555555     0.000017s
+5555555555555503  P  8.214086s
+5555553133149889     8.067247s
+4444444488888889     7.546234s
+4444444444444444     0.000002s
+4444444444444423  P  7.622370s
+3333335652092209     6.724649s
+3333333333333333     0.000018s
+3333333333333301  P  6.655039s
+ 299593572317531  P  2.072723s
+ 142702110479723  P  1.461840s
+               2  P  0.000001s
+Total time: 9.65s
+----
+====
+<1> Esta linha aparece muito rápido.
+<2> Esta linha demora mais de 9,5s para aparecer.
+<3> As linhas restantes aparecem quase imediatamente.
+
+Agora vamos entender o comportamento estranho de _proc_pool.py_:
+
+* Como mencionado antes, `executor.map(check, numbers)` devolve o resultado na mesma ordem em que `numbers` é submetido.
+* 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`. Neste laptop foram 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.
+* Quando o processo de trabalho encarregado de 9999999999999917 finalmente determina que ele é primo, todos os outros processos já completaram suas últimas tarefas, então os resultados aparecem logo depois.
+
+[NOTE]
+====
+
+Apesar do progresso de _proc_pool.py_ não ser tão visível quanto o de
+_procs.py_ na <>, o tempo total de execução é praticamente igual
+ao retratado na <> do <>, 
+para as mesmas quantidades de processos de trabalho e núcleos de CPU.
+
+====
+
+Entender como programas concorrentes se comportam não é fácil,
+então aqui está um segundo experimento que pode ajudar a visualizar o
+funcionamento de `Executor.map`.((("", startref="CFlaunch20")))((("",
+startref="CElaunch20")))((("", startref="Plaunch20")))
+
+=== Experimento com `Executor.map`
+
+
+Vamos((("concurrent executors", "Executor.map",
+id="CEexecutormap20")))((("Executor.map", id="execumap20"))) investigar
+`Executor.map`, agora usando um `ThreadPoolExecutor` com três threads de
+trabalho rodando cinco invocáveis que exibem mensagens com data/hora. O código
+está no <>, o resultado no <>.
+
+[[demo_executor_map_ex]]
+.demo_executor_map.py: Uma demonstração simples do método map de `ThreadPoolExecutor`
+====
+[source, python]
+----
+include::../code/20-executors/demo_executor_map.py[tags=EXECUTOR_MAP]
+----
+====
+
+<1> Esta função exibe o momento da execução no formato `[HH:MM:SS]` e os
+argumentos recebidos.
+
+<2> `loiter` significa "vadiar"; esta função não faz nada além mostrar uma
+mensagem quando inicia, cochilar por `n` segundos, e mostrar uma mensagem quando
+termina; usei tabulações para indentar as mensagens de acordo com o valor de `n` 
+
+<3> `loiter` devolve `n + 10`, para que o valor devolvido seja diferente do
+argumento `n`, tornando mais visível o fluxo de dados.
+
+<4> Cria um `ThreadPoolExecutor` com três threads.
+
+<5> Submete cinco tarefas para o `executor`. Como há três threads,
+apenas três daquelas tarefas vão iniciar imediatamente: a chamadas `loiter(0)`,
+`loiter(1)`, e `loiter(2)`; `executor.map` não bloqueia, retorna imediatamente.
+
+<6> Exibe `results` devolvido por `executor.map`: é um
+gerador, como se vê na saída no <>.
+
+<7> A chamada `enumerate` no laço `for` invocará implicitamente
+`next(results)`, que por sua vez invocará `f.result()` no _future_ (interno)
+`f`, representando a primeira chamada, `loiter(0)`. O método `result`
+bloqueará a thread até que o _future_ termine, portanto cada volta nesse laço vai
+esperar até que o próximo resultado esteja disponível.
+
+
+Sugiro que você rode o <> para ver o resultado sendo exibido incrementalmente.
+
+Para experimentar, altere o argumento `max_workers` do `ThreadPoolExecutor`.
+Mude também a chamada a `range`, que produz os argumentos para invocar `executor.map`—ou
+forneça uma lista de valores escolhidos, para criar intervalos diferentes.
+
+O <> mostra um teste do <>.
+
+[[demo_executor_map_run]]
+.Amostra da execução de demo_executor_map.py, do <>
+====
+[source, text]
+----
+$ python3 demo_executor_map.py
+[15:56:50] Script starting.  <1>
+[15:56:50] loiter(0): napping for 0s...  <2>
+[15:56:50] loiter(0): done.
+[15:56:50]      loiter(1): napping for 1s...  <3>
+[15:56:50]              loiter(2): napping for 2s...
+[15:56:50] results:   <4>
+[15:56:50]                      loiter(3): napping for 3s...  <5>
+[15:56:50] Waiting for individual results:
+[15:56:50] result 0: 10  <6>
+[15:56:51]      loiter(1): done. <7>
+[15:56:51]                              loiter(4): napping for 4s...
+[15:56:51] result 1: 11  <8>
+[15:56:52]              loiter(2): done.  <9>
+[15:56:52] result 2: 12
+[15:56:53]                      loiter(3): done.
+[15:56:53] result 3: 13
+[15:56:55]                              loiter(4): done.  <10>
+[15:56:55] result 4: 14
+----
+====
+
+<1> Este teste começou em 15:56:50.
+
+<2> A primeira thread executa `loiter(0)`, então vai cochilar por 0s e retornar
+antes mesmo da segunda thread ter chance de começar, mas YMMV.footnote:[Acrônimo
+de _your mileage may vary_, algo como "sua quilometragem pode variar", querendo
+dizer "seu caso pode ser diferente". Com threads, você nunca sabe a sequência
+exata de eventos que deveriam acontecer quase ao mesmo tempo; é possível que, em
+outra máquina, se veja `loiter(1)` começar antes de `loiter(0)` terminar,
+especialmente porque `sleep` sempre libera a GIL, então Python pode mudar para
+outra thread mesmo se você dormir por 0s.]
+
+<3> `loiter(1)` e `loiter(2)` começam imediatamente (como o banco de threads tem
+três threads de trabalho, é possível rodar três funções concorrentemente).
+
+<4> Isto mostra que `results` devolvido 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 processar `loiter(3)`.
+
+<6> Aqui é onde 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_ completar. Neste caso, ele não vai
+bloquear porque a chamada a `loiter(0)` terminou antes deste 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 segunda thread está livre para
+iniciar `loiter(4)`.
+
+<8> O resultado de `loiter(1)` é exibido: `11`. 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 cochilou por 4s.
+
+A função `Executor.map` é fácil de usar, mas muitas vezes é preferível obter o resultado
+de cada tarefa assim que esteja pronta, e não necessariamente na ordem em que foram submetidas.
+Para isso, precisamos de uma combinação do método
+`Executor.submit` e da função `futures.as_completed` como vimos no
+<>. Vamos voltar a essa técnica na
+<>.
+
+[TIP]
+====
+
+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 vêm 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")))
+
+
+[[flags2_sec]]
+=== Barra de progresso e tratamento de erros
+
+Como((("concurrent executors", "downloads with progress display and error handling",
+id="CEdown20")))((("network I/O", "downloads with progress display and error handling",
+id="IOprog20")))((("progress displays",
+id="progdisp20")))((("error handling, in network I/O", secondary-sortas="network I/O",
+id="EHnetwork20"))) mencionado, os scripts na <> não
+têm tratamento de erros, para torná-los mais fáceis de ler e para comparar a
+estrutura das três abordagens: sequencial, com threads e assíncrona.
+
+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 da série `flags2`, incluindo a função `main`, que cuida de
+ler os argumentos da linha de comando, medir o tempo e mostrar os
+resultados. Isto é 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
+https://fpy.li/code[«repositório de exemplos»], arquivo
+https://fpy.li/20-10[_20-executors/getflags/flags2_common.py_].
+
+flags2_sequential.py:: Um cliente HTTP sequencial com tratamento de erro correto e a exibição de uma barra de progresso. Sua função `download_one` também é usada por `flags2_threadpool.py`.
+
+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`. Será explicado na <>, no <>.
+
+[[careful_testing_clients]]
+[WARNING]
+.Tenha cuidado ao testar clientes concorrentes
+====
+Ao testar clientes HTTP concorrentes em servidores Web públicos, você pode gerar muitas requisições por segundo, e é assim que ataques de negação de serviço (DoS, _denial-of-service_) são feitos.
+Monitore cuidadosamente seus clientes quando for usar servidores públicos.
+Para testar, configure um servidor HTTP local. Veja instruções após o <>.
+====
+
+A característica mais visível dos exemplos `flags2` é sua barra de progresso
+animada em modo texto, implementada com o https://fpy.li/20-11[pacote _tqdm_].
+Publiquei no YouTube um
+https://fpy.li/20-12[«vídeo de 108s»] mostrando a barra de
+progresso e comparando a velocidade dos três scripts `flags2`. No vídeo, começo
+com o download sequencial, mas interrompo a execução após 32s. O script
+demoraria mais de 5 minutos para acessar 676 URLs e baixar 194 bandeiras. Então
+rodo o script que usa threads e o script que usa `asyncio`, três vezes cada um, e todas
+as vezes eles completam a tarefa em 6s ou menos (isto é mais de 60 vezes mais
+rápido). A <> mostra duas capturas de tela: durante e após
+a execução de _flags2_threadpool.py_.
+
+[[flags2_progress_fig]]
+.Acima: flags2_threadpool.py rodando com a barra de progresso em tempo real gerada pelo tqdm; abaixo: mesma janela do terminal após o script terminar de rodar.
+image::../images/flpy_2001.png[flags2_threadpool.py running with progress bar]
+
+O exemplo de uso mais simples do _tqdm_ aparece em um _.gif_ animado, no https://fpy.li/20-13[_README.md_] do projeto.
+Se você digitar o código abaixo no console de Python após instalar o pacote _tqdm_,
+uma barra de progresso animada aparecerá no lugar onde está o comentário:
+
+[source, python]
+----
+>>> import time
+>>> from tqdm import tqdm
+>>> for i in tqdm(range(1000)):
+...     time.sleep(.01)
+...
+>>> # -> a barra de progresso aparece aqui <-
+----
+
+Além do efeito bonitinho, o gerador `tqdm` também é interessante
+conceitualmente: ele consome qualquer iterável, e produz um iterador que,
+enquanto é consumido, mostra a barra de progresso e estima o tempo restante para
+completar todas as iterações. Para fazer a estimativa, o `tqdm` precisa
+receber um iterável que implemente `len`, ou o argumento
+`total=` com o número esperado de itens. Integrar o `tqdm` com nossos exemplos
+`flags2` permite observar mais profundamente o
+funcionamento real dos scripts concorrentes, obrigando-nos a usar as funções
+https://fpy.li/20-7[`futures.as_completed`] e
+https://fpy.li/20-15[`asyncio.as_completed`], para que o `tqdm` atualize
+a barra de progresso conforme cada `future` termina.
+
+A outra característica dos exemplos `flags2` é a interface de linha de comando.
+Todos os três scripts aceitam várias opções para experimentar,
+que você pode ver passando a opção `-h`.
+O <> mostra o texto de ajuda.
+
+[[flags2_help_demo]]
+.Tela de ajuda dos scripts da série flags2
+====
+[source, text]
+----
+$ python3 flags2_threadpool.py -h
+usage: flags2_threadpool.py [-h] [-a] [-e] [-l N] [-m CONCURRENT] [-s LABEL]
+                            [-v]
+                            [CC [CC ...]]
+
+Download flags for country codes. Default: top 20 countries by population.
+
+positional arguments:
+  CC                    country code or 1st letter (eg. B for BA...BZ)
+
+optional arguments:
+  -h, --help            show this help message and exit
+  -a, --all             get all available flags (AD to ZW)
+  -e, --every           get flags for every possible code (AA...ZZ)
+  -l N, --limit N       limit to N first codes
+  -m CONCURRENT, --max_req CONCURRENT
+                        maximum concurrent requests (default=30)
+  -s LABEL, --server LABEL
+                        Server to hit; one of DELAY, ERROR, LOCAL, REMOTE
+                        (default=LOCAL)
+  -v, --verbose         output detailed progress info
+
+----
+====
+
+Todos os argumentos são opcionais. Mas o `-s/--server` é essencial para os testes:
+ele permite escolher qual servidor HTTP e qual porta serão usados no teste.
+Passe um desses parâmetros (insensíveis a maiúsculas/minúsculas) para determinar onde o script vai buscar as bandeiras:
+
+`LOCAL`:: Usa `http://localhost:8000/flags`; esse é o default. Você deve configurar um servidor HTTP local, respondendo na porta 8000. Veja as instruções na nota a seguir.
+
+`REMOTE`:: Usa `http://fluentpython.com/data/flags`; este é meu site público, hospedado em um servidor compartilhado. Por favor, não o martele com requisições concorrentes excessivas. O domínio _https://fluentpython.com_ é gerenciado pela CDN da https://fpy.li/20-16[_Cloudflare_], então você pode notar que os primeiros downloads são mais lentos, mas ficam mais rápidos conforme o cache da CDN é carregado.
+
+`DELAY`:: Usa `http://localhost:8001/flags`; um servidor atrasando as respostas HTTP deve responder na porta 8001. Escrevi o _slow_server.py_ para facilitar o experimento. Ele está no diretório _20-futures/getflags/_ do https://fpy.li/code[repositório de código do _Python Fluente_]. Veja as instruções na nota a seguir.
+
+`ERROR`:: Usa `http://localhost:8002/flags`; um servidor devolvendo alguns erros HTTP deve responder na porta 8002. Instruções a seguir.
+
+[[setting_up_servers_box]]
+.Configurando os servidores de teste
+[NOTE]
+====
+
+Escrevi((("test servers")))((("servers", "test servers"))) 
+instruções para subir servidores HTTP para testes
+usando apenas Python ≥ 3.9 (nenhuma biblioteca externa) em
+https://fpy.li/20-17[_20-executors/getflags/README.adoc_]
+no https://fpy.li/code[«repositório de exemplos»] .
+Em resumo, o _README.adoc_ descreve como rodar:
+
+`python3 -m http.server`:: O servidor `LOCAL` na porta 8000
+`python3 slow_server.py`:: O servidor `DELAY` na porta 8001, que acrescenta um atraso aleatório de 0,5s a 5s antes de cada resposta
+`python3 slow_server.py 8002 --error-rate .25`:: O servidor `ERROR` na porta 8002, que além do atraso aleatório tem uma chance de 25% de retornar um erro https://fpy.li/20-18["418 I'm a teapot"] como resposta
+
+====
+
+Por default, cada script _flags2*.py_ irá baixar as bandeiras dos 20 países mais populosos do servidor `LOCAL` (`http://localhost:8000/flags`), usando um número default de conexões concorrentes, que varia de script para script.
+O <> mostra uma execução padrão do script  _flags2_sequential.py_ usando as configurações default.
+Para rodá-lo, você precisa de um servidor local, como explicado acima.
+
+[[flags2_sequential_run]]
+.Rodando flags2_sequential.py com todos os defaults: `site LOCAL`, as 20 bandeiras dos países mais populosos, 1 conexão concorrente
+====
+[source]
+----
+$ python3 flags2_sequential.py
+LOCAL site: http://localhost:8000/flags
+Searching for 20 flags: from BD to VN
+1 concurrent connection will be used.
+--------------------
+20 flags downloaded.
+Elapsed time: 0.10s
+----
+====
+
+Você pode selecionar as bandeiras a serem baixadas de várias formas.
+O <> mostra como baixar todas as bandeiras com códigos de país começando pelas letras A, B ou C.
+
+[[flags2_threadpool_run]]
+.Roda flags2_threadpool.py para obter do servidor `DELAY` todas as bandeiras com prefixos de códigos de país A, B ou C
+====
+[source]
+----
+$ python3 flags2_threadpool.py -s DELAY a b c
+DELAY site: http://localhost:8001/flags
+Searching for 78 flags: from AA to CZ
+30 concurrent connections will be used.
+--------------------
+43 flags downloaded.
+35 not found.
+Elapsed time: 1.72s
+----
+====
+
+Independente de como os códigos de país são selecionados, o número de bandeiras a serem obtidas pode ser limitado com a opção `-l/--limit`. O <> demonstra como fazer exatamente 100 requisições, combinando a opção `-a` para obter todas as bandeiras com `-l 100`.
+
+[[flags2_asyncio_run]]
+.Roda flags2_asyncio.py para baixar 100 bandeiras (`-al 100`) do servidor `ERROR`, usando 100 requisições concorrentes (`-m 100`)
+====
+[source]
+----
+$ python3 flags2_asyncio.py -s ERROR -al 100 -m 100
+ERROR site: http://localhost:8002/flags
+Searching for 100 flags: from AD to LK
+100 concurrent connections will be used.
+--------------------
+73 flags downloaded.
+27 errors.
+Elapsed time: 0.64s
+----
+====
+
+Vimos a interface de usuário dos exemplos _flags2_. Agora veremos como eles estão implementados.
+
+
+==== Tratamento de erros nos exemplos _flags2_
+
+A estratégia comum nos três exemplos para lidar com erros HTTP é que
+erros 404 (not found) são tratados pela função encarregada de baixar um único
+arquivo (`download_one`). Outras exceções são tratadas pela
+função `download_many` ou pela corrotina `supervisor`—no exemplo com `asyncio`.
+
+Vamos novamente começar estudando o código sequencial, que é mais fácil de
+compreender, e boa parte dele será reutilizado pelo script com um banco de threads. O
+<> mostra as funções que efetivamente fazem os downloads
+nos scripts _flags2_sequential.py_ e _flags2_threadpool.py_.
+
+[[flags2_basic_http_ex]]
+.flags2_sequential.py: funções básicas encarregadas dos downloads; ambas são reutilizadas no flags2_threadpool.py
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags2_sequential.py[tags=FLAGS2_BASIC_HTTP_FUNCTIONS]
+----
+====
+<1> Importa a biblioteca de exibição de barra de progresso `tqdm`, e diz ao Mypy para não checá-la.footnote:[Em setembro de 2021 não havia dicas de tipo na versão (então) atual do `tqdm`. Tudo bem. O mundo não vai acabar por causa disso. Obrigado, Guido, pela tipagem opcional!]
+<2> Importa algumas funções e um `Enum` do módulo `flags2_common`.
+<3> Dispara um `HTTPStatusError` se o código de status do HTTP não está em `range(200, 300)`.
+<4> `download_one` trata o `HTTPStatusError`, especificamente para tratar o código HTTP 404...
+<5> ...mudando seu `status` local para `DownloadStatus.NOT_FOUND`; `DownloadStatus` é um `Enum` importado de _flags2_common.py_.
+<6> Qualquer outra exceção de `HTTPStatusError` é re-emitida e propagada para quem chamou a função.
+<7> Se a opção de linha de comando `-v/--verbose` está vigente, o código do país e a mensagem de status são exibidos; é assim que você verá o progresso no modo `verbose`.
+
+O <> lista a versão sequencial da função
+`download_many`. O código é simples, mas vale a pena estudar para compará-lo com
+as versões concorrentes que veremos a seguir. Note como ele informa o
+progresso, trata erros, e conta os downloads.
+
+[[flags2_download_many_seq]]
+.flags2_sequential.py: a implementação sequencial de `download_many`
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags2_sequential.py[tags=FLAGS2_DOWNLOAD_MANY_SEQUENTIAL]
+----
+====
+<1> Este `Counter` vai registrar os diferentes resultados possíveis dos downloads: `DownloadStatus.OK`, `DownloadStatus.NOT_FOUND`, ou `DownloadStatus.ERROR`.
+<2> `cc_iter` preserva a lista de códigos de país recebidos como argumentos, em ordem alfabética.
+<3> Se não estamos rodando em modo `verbose`, `cc_iter` é passado para o `tqdm`, que devolve um iterador que produz os itens em `cc_iter` enquanto também anima a barra de progresso.
+<4> Faz chamadas sucessivas a `download_one`.
+<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 um `try/except`.
+<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`.
+<11> No modo `verbose`, mostra a mensagem de erro do código de país atual, se houver.
+<12> Devolve `counter` para que `main` possa mostrar os números no relatório final.
+
+
+Agora vamos estudar _flags2_threadpool.py_, o exemplo com banco de threads aperfeiçoado.
+
+[[using_futures_as_completed_sec]]
+==== Usando `futures.as_completed`
+
+Para((("futures.as_completed", id="futuresas20"))) integrar a barra de progresso
+do _tqdm_ e tratar os erros a cada requisição, o script _flags2_threadpool.py_
+usa o `futures.ThreadPoolExecutor` com a função `futures.as_completed`.
+O <> é a listagem completa de _flags2_threadpool.py_.
+Apenas a função `download_many` é implementada; as outras funções são
+reutilizadas de _flags2_common.py_ e _flags2_sequential.py_.
+
+[[flags2_threadpool_full]]
+.flags2_threadpool.py: listagem completa
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags2_threadpool.py[tags=FLAGS2_THREADPOOL]
+----
+====
+
+<1> Reutiliza `download_one` de `flags2_sequential` (<>).
+
+<2> Se a opção de linha de comando `-m/--max_req` não é passada, este será o
+número máximo de requisições concorrentes, definido como o tamanho do banco de
+threads; o número real pode ser menor se o número de bandeiras a serem baixadas
+for menor.
+
+<3> `MAX_CONCUR_REQ` limita o número máximo de requisições concorrentes
+independente do número de bandeiras a serem baixadas ou da opção de linha de
+comando `-m/--max_req`. É uma medida de segurança, para evitar iniciar threads
+demais, com seu uso significativo de memória.
+
+<4> Cria o `executor` com `max_workers` determinado por `concur_req`, calculado
+pela função `main` como o menor valor entre `MAX_CONCUR_REQ`, o tamanho de
+`cc_list`, ou a opção de linha de comando `-m/--max_req`. Assim evitamos criar
+mais threads que o necessário.
+
+<5> Este `dict` vai mapear cada instância de `Future`—representando um
+download—com o respectivo código de país, para exibição de erros.
+
+<6> Itera sobre a lista de códigos de país em ordem alfabética. A ordem dos
+resultados vai depender, mais do que de qualquer outra coisa, do tempo das
+respostas HTTP; mas se o tamanho do banco de threads (dado por `concur_req`) for
+muito menor que  `len(cc_list)`, você poderá ver os downloads aparecendo em
+ordem alfabética.
+
+<7> Cada chamada a `executor.submit` agenda a execução de um invocável e
+devolve uma instância de `Future`. O primeiro argumento é o invocável, o
+restante são os argumentos que ele receberá.
+
+<8> Armazena o `future` e o código de país no `dict`.
+
+<9> `futures.as_completed` devolve um iterador que produz _futures_ conforme
+cada tarefa é completada.
+
+<10> Se não estiver no modo `verbose`, passa o resultado de `as_completed` para a
+função `tqdm`, para mostrar a barra de progresso; como `done_iter` não tem
+`len`, precisamos informar o gerador `tqdm` qual o número de itens esperado com o
+argumento `total=`, para que ele possa estimar o trabalho restante.
+
+<11> Itera sobre os _futures_ conforme eles vão terminando.
+
+<12> Chamar o método `result` em um _future_ devolve o valor devolvido pela
+invocável ou dispara qualquer exceção que tenha sido capturada quando o
+invocável foi executado. Este método pode bloquear, esperando por uma
+resolução. Mas não nesse exemplo, porque `as_completed` só produz _futures_ que
+terminaram sua execução.
+
+<13> Trata exceções em potencial; o resto desta função é idêntica à função
+`download_many` no <>, exceto pela observação a
+seguir.
+
+<14> Para dar contexto à mensagem de erro, recupera o código de país do
+`to_do_map`, usando o `future` atual como chave. Isso não era necessário na
+versão sequencial, pois estávamos iterando sobre a lista de códigos de país,
+então sabíamos qual era o `cc` atual; aqui estamos iterando sobre _futures_.
+
+
+[TIP]
+====
+
+O <> usa um padrão que é muito útil com
+`futures.as_completed`: construir um `dict` mapeando cada _future_ a outros
+dados que podem ser úteis quando o _future_ terminar de executar. Aqui o
+`to_do_map` mapeia cada _future_ ao código de país atribuído a ele.
+Assim fica mais fácil o pós-processamento dos resultados dos _futures_, apesar deles
+serem produzidos fora de ordem.
+
+====
+
+As threads de Python são bastante adequadas a aplicações de uso intensivo de E/S, e o pacote `concurrent.futures` as torna relativamente simples de implementar em certos casos de uso. Com `ProcessPoolExecutor` você também pode resolver problemas de uso intensivo de CPU em múltiplos núcleos—se o processamento for https://fpy.li/20-19["embaraçosamente paralelo"]. Isso encerra nossa introdução básica a `concurrent.futures`.((("", startref="futuresas20")))((("", startref="EHnetwork20")))((("", startref="progdisp20")))((("", startref="IOprog20")))((("", startref="CEdown20")))
+
+
+=== Resumo do capítulo
+
+Começamos((("concurrent executors", "overview of"))) o capítulo comparando dois
+clientes HTTP concorrentes com um sequencial, demonstrando que as soluções
+concorrentes mostram um ganho significativo de desempenho sobre o script
+sequencial.
+
+Após estudar o primeiro exemplo, baseado no `concurrent.futures`, olhamos mais
+de perto os objetos _future_, instâncias de `concurrent.futures.Future` ou de
+`asyncio.Future`, enfatizando as semelhanças entre
+essas classes (suas diferenças serão examinadas no <>). Vimos como
+criar _futures_ chamando `Executor.submit`, e como iterar sobre _futures_ que
+terminaram sua execução com `concurrent.futures.as_completed`.
+
+Então discutimos o uso de múltiplos processos com a classe
+`concurrent.futures.ProcessPoolExecutor`, para evitar a GIL e usar múltiplos
+núcleos de CPU, simplificando o checador de números primos multi-núcleo que
+vimos antes no <>.
+
+Na seção seguinte, estudamos como funciona a classe `ThreadPoolExecutor`,
+com um exemplo didático, iniciando tarefas que não faziam nada por alguns
+segundos, exceto exibir seu status e a hora naquele instante.
+
+Então voltamos para os exemplos de download de bandeiras. Melhorar aqueles
+exemplos com uma barra de progresso e tratamento de erro adequado nos ajudou a
+explorar melhor a função geradora `future.as_completed` mostrando um modelo
+comum: armazenar _futures_ em um `dict` para vincular a eles informações adicionais
+quando são submetidos, para usá-las quando o _future_
+sai do gerador `as_completed`.
+
+
+=== Para saber mais
+
+O((("concurrent executors", "further reading on"))) pacote `concurrent.futures`
+foi uma contribuição de Brian Quinlan, que o apresentou em uma palestra
+sensacional intitulada
+https://fpy.li/20-20[_The Future Is Soon!_] (O futuro é logo!), na
+PyCon Australia 2010. A palestra de Quinlan não tinha slides; ele mostra o que a
+biblioteca faz digitando código diretamente no console de Python. Como exemplo
+motivador, a apresentação inclui um pequeno vídeo com o cartunista/programador
+do XKCD, Randall Munroe, executando um ataque de negação de serviço (DoS)
+acidental contra o Google Maps, para criar um mapa colorido de tempos de
+locomoção pela cidade. A introdução formal à biblioteca é a
+https://fpy.li/pep3148[PEP 3148—`futures`: execute computations
+asynchronously] (executar processamentos assíncronos). Na PEP,
+Quinlan escreveu que a biblioteca `concurrent.futures` foi "muito influenciada
+pelo pacote `java.util.concurrent` de Java."
+
+Para mais recursos sobre `concurrent.futures`, por favor consulte o
+<>. As referências que tratam de `threading` e
+`multiprocessing` de Python na <> também
+tratam do `concurrent.futures`.
+
+.Ponto de vista
+****
+
+*Evitando Threads*
+
+[quote, David Beazley, instrutor de Python e cientista louco.]
+____
+Concorrência: um dos tópicos mais difíceis na ciência da computação (normalmente é melhor
+evitá-lo).footnote:[Slide #9 do tutorial
+https://fpy.li/20-21[_A Curious Course on Coroutines and Concurrency_]
+(Um Curioso Curso sobre Corrotinas e Concorrência), apresentado na PyCon 2009.]
+____
+
+Concordo((("concurrent executors", "Soapbox discussion")))((("Soapbox sidebars",
+"thread avoidance")))((("threads", "thread avoidance")))
+com as citações aparentemente contraditórias de David Beazley e Michele Simionato no início desse capítulo.
+
+Assisti a um curso de graduação sobre concorrência no IME/USP.
+Tudo o que vimos foi programação de https://fpy.li/an[«threads POSIX»].
+O que aprendi: não quero gerenciar threads e travas na unha,
+pela mesma razão que não quero gerenciar a alocação e desalocação de memória na unha.
+Estas tarefas são para programadores de sistemas, que têm 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://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 chamados problemas "embaraçosamente paralelos"—ao contrário de
+sistemas operacionais ou servidores de banco de dados, como aponta Simionato
+na epígrafe deste capítulo.
+
+Para problemas de concorrência mais complexos, threads e travas também não são a
+solução. Ao nível do sistema operacional, as threads nunca vão desaparecer. Mas
+todas as linguagens de programação que achei interessantes nos últimos 20 anos
+oferecem abstrações de alto nível para concorrência, como demonstra o excelente
+livro de Paul Butcher, https://fpy.li/20-24[_Seven Concurrency Models in Seven
+Weeks_] (Sete Modelos de Concorrência em Sete Semanas). Go, Elixir, e
+Clojure estão entre elas. Erlang—a linguagem de implementação do Elixir—é um
+exemplo claro de uma linguagem projetada desde o início pensando em
+concorrência. Erlang não me empolga só porque acho sua sintaxe deselegante.
+Python me acostumou mal.
+
+José Valim, antigo mantenedor do _Ruby on Rails_, projetou o
+Elixir com uma sintaxe moderna e agradável. Como Lisp e Clojure, o Elixir
+implementa macros sintáticas. Isto é uma faca de dois gumes. Macros sintáticas
+permitem criar DSLs—sigla de _Domain-Specific Language_
+(Linguagem de Domínio Específico), facilitando determinadas tarefas.
+Mas a proliferação de sub-linguagens pode levar a
+bases de código incompatíveis e à fragmentação da comunidade. O Lisp se afogou
+em um mar de macros, cada empresa e grupo de desenvolvedores Lisp usando seu
+próprio dialeto arcano. A padronização que criou o Common Lisp resultou em uma
+linguagem inchada quando muitas macros foram incorporadas ao padrão.
+Espero que José Valim inspire a comunidade do Elixir a evitar
+um destino semelhante. Até agora, o cenário parece bom. A biblioteca
+de banco de dados https://fpy.li/20-25[_Ecto_] é muito agradável de usar:
+um grande exemplo do uso de macros para criar uma DSL flexível e amigável para
+interagir com bases de dados relacionais e não-relacionais.
+
+Como, Elixir, Go é uma linguagem moderna com ideias novas.
+Mas, em alguns aspectos, é uma linguagem conservadora, se comparada ao Elixir.
+Go não tem macros, e sua sintaxe é mais simples que a de Python.
+Go não suporta herança ou sobrecarga de operadores, e oferece menos oportunidades para metaprogramação que Python.
+Estas limitações são consideradas vantagens.
+Elas proporcionam comportamento e desempenho mais previsíveis.
+Isto é uma grande vantagem em ambientes de missão crítica altamente concorrentes,
+onde o Go pretende substituir {cpp}, Java e Python.
+
+Enquanto Elixir e Go são competidores diretos no espaço da alta concorrência,
+seus projetos e filosofias atraem públicos diferentes.
+Ambos têm boas chances de prosperar.
+Mas historicamente, as linguagens mais conservadoras tendem a atrair mais programadores.
+
+****
diff --git a/online/cap21.adoc b/online/cap21.adoc
new file mode 100644
index 00000000..729680ee
--- /dev/null
+++ b/online/cap21.adoc
@@ -0,0 +1,2563 @@
+[[ch_async]]
+== Programação assíncrona
+:example-number: 0
+:figure-number: 0
+
+[quote, Alvaro Videla & Jason J. W. Williams, RabbitMQ in Action]
+____
+O problema com as abordagens usuais da programação assíncrona é
+que são propostas do tipo "tudo ou nada".
+Ou você reescreve todo o código, de forma que nada nele bloqueie, 
+ou está só perdendo tempo.footnote:[Videla & Williams,
+_RabbitMQ in Action (Manning, 2012)_,
+_Solving Problems with Rabbit: coding and patterns_, p. 61]
+____
+
+
+Este((("asynchronous programming", "topics covered"))) capítulo trata de três grandes tópicos interligados:
+
+* As instruções `async def`, `await`, `async with`, e `async for`;
+* Objetos que suportam tais instruções através de métodos especiais como `+__await__+`, `+__aiter__+` etc.,
+tais como corrotinas nativas e variantes assíncronas de gerenciadores de contexto, iteráveis, geradores e compreensões;
+* `asyncio` e outras bibliotecas assíncronas.
+
+Este capítulo parte das ideias de iteráveis e geradores (<>,
+em particular a <>), gerenciadores de contexto (<>),
+e conceitos gerais de programação concorrente (<>).
+
+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 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 _FastAPI_. A seguir estudaremos outros objetos
+suportados pelas palavras-chave `async/await`: funções geradoras assíncronas,
+compreensões assíncronas, e expressões geradoras assíncronas. Para enfatizar
+que estes recursos não estarão limitados ao `asyncio`, veremos
+um exemplo reescrito para usar o _Curio_—o inovador e influente framework
+inventado por David Beazley.
+
+Finalizando o capítulo, escrevi uma pequena seção sobre vantagens e
+armadilhas da programação assíncrona.
+
+Há um longo caminho à nossa frente. Teremos espaço apenas para exemplos básicos,
+mas eles vão ilustrar as características mais importantes de cada ideia.
+
+
+[TIP]
+====
+A https://fpy.li/21-1[documentação do `asyncio`] melhorou((("asyncio package", "documentation"))) muito após Yury Selivanovfootnote:[Selivanov implementou `async/await` no Python, e escreveu as PEPs relacionadas:
+https://fpy.li/pep492[492],
+https://fpy.li/pep525[525], e
+https://fpy.li/pep530[530].
+] reorganizá-la, dando maior destaque às funções úteis para desenvolvedores de aplicações.
+A maior parte da API de `asyncio` consiste em funções e classes voltadas para
+criadores de pacotes como frameworks Web e drivers de bancos de dados, ou seja,
+são necessários para criar bibliotecas assíncronas, mas não aplicações.
+
+Para mais profundidade sobre `asyncio`, recomendo
+https://fpy.li/hattingh[_Using Asyncio in Python_]
+de Caleb Hattingh (O'Reilly).
+Transparência: Caleb é um dos revisores técnicos deste livro.
+====
+
+
+=== Novidades neste capítulo
+
+Quando((("asynchronous programming", "significant changes to"))) escrevi a
+primeira edição de _Python Fluente_, a biblioteca `asyncio` era provisória e as
+palavras-chave `async/await` não existiam. Por isso atualizei todos os exemplos
+deste capítulo. Também criei novos exemplos: scripts de sondagem de domínios, um
+serviço Web com _FastAPI_, e experimentos com o novo modo assíncrono do console
+do Python.
+
+Novas seções tratam de recursos da linguagem inexistentes naquele momento, como
+corrotinas nativas, `async with`, `async for`, e os objetos que suportam essas
+instruções.
+
+As ideias na <> refletem lições importantes
+que aprendi na prática, por isso eu a considero leitura essencial para
+qualquer um trabalhando com programação assíncrona. Elas podem ajudar você a
+evitar muitos problemas—no Python ou mesmo no Node.js.
+
+Por fim, removi vários parágrafos sobre `asyncio.Futures`, que agora considero parte das APIs de baixo nível do `asyncio`.
+
+[role="pagebreak-before less_space"]
+=== Algumas definições.
+
+Na ((("asynchronous programming", "relevant terminology"))) <>,
+vimos que Python oferece três tipos de corrotinas desde a versão 3.5:
+
+Corrotina nativa::
+    Uma((("native coroutines", "definition of term")))((("coroutines", "types
+    of"))) função corrotina definida com `async def`. Uma
+    corrotina nativa pode acionar outra corrotina nativa, usando a instrução
+    `await`, semelhante ao funcionamento de `yield from` em corrotinas
+    clássicas. A instrução `async def` sempre define uma corrotina nativa,
+    mesmo se a instrução `await` não aparecer em seu corpo. A instrução
+    `await` só pode ser usada dentro de uma corrotina nativa.footnote:[Há uma
+    exceção a essa regra: se você iniciar Python com a opção `-m asyncio`, pode
+    então usar `await` diretamente no prompt `>>>` para controlar uma corrotina
+    nativa. Isto é explicado na <>.]
+
+Corrotina clássica::
+    Uma função geradora que consome dados enviados a ela via chamadas a `my_coro.send(data)`, e que lê aqueles dados usando `yield` em uma expressão.
+    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 geradores::
+    Uma((("generators", "generator-based coroutines")))((("coroutines", "generator-based"))) função geradora decorada com `@types.coroutine`—introduzido no Python 3.5.
+    Esse decorador torna o gerador compatível com a nova instrução `await`.
+
+Neste 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.
+    Ela devolve um objeto gerador assíncrono que implementa `+__anext__+`, um método corrotina para obter o próximo item.
+
+.`@asyncio.coroutine` não tem futurofootnote:[Perdoe o jogo de palavras.]
+[WARNING]
+====
+
+O((("@asyncio.coroutine decorator"))) decorador `@asyncio.coroutine` para
+corrotinas clássicas e corrotinas baseadas em gerador foi descontinuado no
+Python 3.8, e está previsto para ser removido no Python 3.11, de acordo com o
+https://fpy.li/21-2[«Issue 43216»]. Por outro lado, `@types.coroutine` deve
+continuar existindo, como se vê aqui: https://fpy.li/21-3[«Issue 36921»]. Este
+decorador não é mais suportado pelo `asyncio`, mas é usado em código interno nos
+frameworks assíncronos _Curio_ e _Trio_.
+
+====
+
+
+=== Sondando domínios com `asyncio`
+
+Imagine((("asyncio package", "example script", id="APexample21")))((("asynchronous programming", "asyncio script example", id="APRscript21")))
+que você esteja prestes a lançar um novo blog sobre Python,
+e planeje registrar um domínio usando uma palavra-chave de Python e o sufixo _.DEV_—por exemplo, _AWAIT.DEV._
+O <> é um script usando `asyncio` que verifica vários domínios de forma concorrente.
+Essa é a saída produzida pelo script:
+
+[source, text]
+----
+$ python3 blogdom.py
+  with.dev
++ elif.dev
++ def.dev
+  from.dev
+  else.dev
+  or.dev
+  if.dev
+  del.dev
++ as.dev
+  none.dev
+  pass.dev
+  true.dev
++ in.dev
++ for.dev
++ is.dev
++ and.dev
++ try.dev
++ not.dev
+----
+
+Observe que os domínios aparecem fora de ordem. Se você rodar o script, os verá
+sendo exibidos um após o outro, em intervalos variados. O sinal de `+` indica
+que sua máquina foi capaz de resolver o domínio via DNS. Caso contrário, o
+domínio não está em uso, e pode estar disponível para
+adquirir.footnote:[`true.dev` está disponível por US$ 360,00 ao ano no momento
+em que escrevo esta nota. Também vi que `for.dev` está registrado, mas seu DNS
+não está configurado.]
+
+No _blogdom.py_, a sondagem de DNS é feita por objetos corrotinas nativas. Como
+as operações assíncronas são intercaladas, o tempo necessário para verificar 18
+domínios é bem menor que se eles fossem verificados sequencialmente. Na verdade,
+o tempo total é quase o igual ao da resposta mais lenta, em vez da soma dos
+tempos de todas as respostas do DNS.
+
+O <> mostra o código dp _blogdom.py_.
+
+[[blogdom_ex]]
+.blogdom.py: procura domínios para um blog sobre Python
+====
+[source, python]
+----
+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 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 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 acioná-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 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 apelido para `asyncio.get_running_loop`.
+====
+
+==== O truque de Guido para ler código assíncrono
+
+Há muitos conceitos novos para entender no `asyncio`,
+mas a lógica básica do <> é fácil de compreender se você usar o truque sugerido pelo próprio Guido van Rossum:
+finja que as palavras-chave `async` e `await` não estão ali.
+Fazendo isso, você vai perceber que a lógica de uma corrotina pode ser lida como uma função sequencial.
+
+Por exemplo, imagine que o corpo desta corrotina...
+
+[source, python]
+----
+async def probe(domain: str) -> tuple[str, bool]:
+    laço = asyncio.get_running_loop()
+    try:
+        await loop.getaddrinfo(domain, None)
+    except socket.gaierror:
+        return (domain, False)
+    return (domain, True)
+----
+
+...funciona como a função abaixo, exceto que, magicamente, ela nunca bloqueia a execução do laço de eventos:
+
+[source, python]
+----
+def probe(domain: str) -> tuple[str, bool]:  # no async
+    laço = asyncio.get_running_loop()
+    try:
+        loop.getaddrinfo(domain, None)  # no await
+    except socket.gaierror:
+        return (domain, False)
+    return (domain, True)
+----
+
+Usar a sintaxe `await loop.getaddrinfo(...)` evita o bloqueio, porque `await` suspende somente 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 laço de eventos,
+não para a corrotina `probe(‘if.dev’)`, que está suspensa.
+Enquanto isso, o laço de eventos pode ativar outros objetos corrotina pendentes,
+tal como `probe('or.dev')`.
+
+Quando o laço de eventos recebe a resposta da consulta `getaddrinfo('if.dev',
+None)`, aquele objeto corrotina específico prossegue sua execução, e devolve o
+controle para `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. Mas eles podem lidar com qualquer objeto "esperável". Esse conceito
+será explicado a seguir.((("", startref="APexample21")))((("",
+startref="APRscript21")))
+
+=== Novo conceito: esperável (_awaitable_)
+
+A((("asynchronous programming", "awaitables")))((("await keyword")))((("keywords",
+"await keyword"))) palavra-chave `for` funciona com
+"iteráveis" (_iterable_). A palavra-chave `await` funciona com "esperáveis" (_awaitable_).
+
+[NOTE]
+====
+Os tradutores da documentação do Python em português traduziram _awaitable_ como
+"aguardável". Adotei "esperável" por ser mais simples.
+E também porque nem todo esperável é agradável ;-)
+====
+
+Como usuário do `asyncio`, estes são os esperáveis que você verá diariamente:
+
+* Um _objeto corrotina nativo_, que você obtém invocando uma _função corrotina nativa_
+* Uma tarefa `asyncio.Task`, criada ao invocar `asyncio.create_task(…)` passando um objeto corrotina nativo.
+
+Entretanto, o código do usuário final nem sempre precisa acionar uma `Task` com `await`.
+Usamos `asyncio.create_task(coro())` para agendar `one_coro` para execução concorrente, sem esperar que retorne.
+Foi o que fizemos com a corrotina `spinner` em _spinner_async.py_
+(<> do <>).
+Criar a tarefa é o suficiente para agendar o acionamento da corrotina pelo laço de eventos.
+
+[WARNING]
+====
+
+Mesmo que você não precise cancelar a tarefa ou esperar pelo resultado,
+é necessário preservar o objeto `Task` devolvido por `create_task`,
+salvando-o em uma variável ou coleção que você controla.footnote:[Agradeço
+ao leitor Samuel Woodward por reportar este erro para a O'Reilly em fevereiro de 2023]
+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 serem acionadas. Por isso você precisa criar referências fortes para
+preservar cada tarefa na memória.
+Veja a documentação de
+https://fpy.li/at[`asyncio.create_task`].
+Sobre referências fracas, escrevi o artigo
+https://fpy.li/weakref[«Weak References»] no _https://fluentpython.com_.
+
+====
+
+Por outro lado, usamos `await other_coro()` para executar `other_coro` agora mesmo
+e esperar que ela termine, porque precisamos do resultado para prosseguir.
+Em _spinner_async.py_, a corrotina `supervisor` usava `res = await slow()`
+para executar `slow` e aguardar seu resultado..
+
+Ao implementar bibliotecas assíncronas ou contribuir para o próprio `asyncio`,
+você pode também encontrar estes esperáveis de baixo nível:
+
+* Um objeto com um método `+__await__+` que devolve um iterador; por exemplo, uma instância de `asyncio.Future` (`asyncio.Task` é uma subclasse de `asyncio.Future`)
+* Objetos escritos em outras linguagens usando a API Python/C, com uma função `tp_as_async.am_await`, que devolve um iterador (similar ao método `+__await__+`)
+
+As bases de código existentes podem também conter um tipo adicional de
+esperável: _objetos corrotina baseados em geradores_, que estão no processo de
+serem descontinuados.
+
+[NOTE]
+====
+
+A PEP 492 https://fpy.li/21-7[«afirma»] que a expressão `await` "usa a
+implementação de `yield from` com um passo adicional de validação
+de seu argumento" e que “`await` só aceita um esperável.” A PEP não explica
+aquela implementação em detalhes, mas cita a
+https://fpy.li/pep380[_PEP 380_], que introduziu `yield from`.
+Escrevi uma explicação detalhada no texto
+https://fpy.li/oldcoro[«Classic Coroutines»], seção
+https://fpy.li/21-8[«The Meaning of `yield from`»] (O significado de `yield from`).
+
+====
+
+Agora vamos estudar a versão `asyncio` do script para baixar figuras da Web.
+
+[[flags_asyncio_sec]]
+=== Downloads com `asyncio` e _HTTPX_
+
+O((("asyncio package", "downloading with", id="APdown21")))((("network I/O",
+"downloading with asyncio", id="NIOdownload21")))((("HTTPX library", id="httpx21")))
+script _flags_asyncio.py_ baixa um conjunto fixo de 20 bandeiras de _https://fluentpython.com_.
+Já mencionamos este script na <>,
+mas agora vamos examiná-lo em detalhes, aplicando os conceitos que acabamos de ver.
+
+No Python 3.10, o `asyncio` só suporta TCP e UDP,
+e não temos pacotes de cliente ou servidor HTTP assíncronos na biblioteca padrão.
+Estou usando o https://fpy.li/httpx[_HTTPX_] nos exemplos de clientes HTTP.
+
+Vamos explorar o _flags_asyncio.py_ a partir do final do módulo, isto é,
+olhando primeiro as funções que iniciam as ações no <>.
+
+[WARNING]
+====
+Para deixar o código mais fácil de ler, _flags_asyncio.py_ não faz tratamento de erros.
+Nesta introdução a `async/await` é bom se concentrar inicialmente no "caminho feliz" (_happy path_),
+para entender como funções comuns e corrotinas são organizadas em um programa.
+A partir da <>, os exemplos incluem tratamento de erros e outros recursos.
+
+Os exemplos __flags*__ aqui e no <> compartilham código e dados, então os coloquei juntos no diretório
+https://fpy.li/21-9[_example-code-2e/20-executors/getflags_].
+====
+
+
+[[flags_asyncio_start_ex]]
+.flags_asyncio.py: funções de inicialização
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags_asyncio.py[tags=FLAGS_ASYNCIO_START]
+----
+====
+
+<1> Esta tem que ser uma função comum—não uma corrotina—para podermos passá-la
+para função `main` do módulo _flags.py_ (<> do <>)
+no passo `⑦`.
+
+<2> Executa o laço de eventos, acionando o objeto corrotina
+`supervisor(cc_list)` até que ele retorne. Esta função ficará bloqueada enquanto o laço de
+eventos roda. O resultado de `supervisor(cc_list)` será devolvido por `asyncio_run`.
+
+<3> Operações assíncronas de cliente HTTP no `httpx` são métodos de
+`AsyncClient`, que também é um gerenciador de contexto assíncrono,
+para uso com `async with`. Trata-se de um gerenciador de contexto com
+métodos corrotina para inicialização e
+encerramento (detalhes logo mais na <>).
+
+<4> Cria uma lista de objetos corrotina, invocando 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 submetidos.
+
+<6> `supervisor` devolve o tamanho da lista vinda de `asyncio.gather`.
+
+<7> Invocamos `main` do `flags.py` (<> do <>)
+
+
+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 e funções de download
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags_asyncio.py[tags=FLAGS_ASYNCIO_TOP]
+----
+====
+
+<1> `httpx` precisa ser instalado; não vem com a biblioteca padrão.
+
+<2> Reutiliza código de _flags.py_ (<> do <>).
+
+<3> `download_one` precisa ser uma corrotina nativa, para acionar
+`get_flag` com `await`. Quando recebe a resposta, exibe o código de país
+bandeira, 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`.
+
+<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]
+====
+
+Idealmente, a invocação de `save_flag` dentro de `get_flag` deveria ser assíncrona,
+evitando bloquear o laço de eventos com uma operação de E/S.
+Entretanto, atualmente `asyncio` não oferece uma API assíncrona para acessar o sistema de arquivos—como faz o Node.js.
+
+A <> vai mostrar como delegar `save_flag` para uma thread.
+====
+
+O seu código delega para as corrotinas do `httpx` explicitamente, usando `await`, ou implicitamente, usando os métodos especiais dos gerenciadores de contexto assíncronos, como `AsyncClient` e `ClientResponse`—como veremos na <>.
+
+
+==== O segredo das corrotinas nativas: humildes geradores
+
+A((("native coroutines", "humble generators and")))((("humble generators")))((("generators", "humble generators"))) diferença fundamental entre os exemplos de corrotinas clássicas vistas na <> e _flags_asyncio.py_ é que não há chamadas a `.send()` ou expressões `yield` visíveis nesse último.
+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 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 direta entre uma biblioteca como a _HTTPX_ e o laço de eventos do framework assíncrono.
+image::../images/flpy_2101.png[Diagrama do canal await]
+
+Debaixo dos panos, o laço de eventos do `asyncio` faz as chamadas a `.send` que
+acionam as nossas corrotinas, e nossas corrotinas acionam outras corrotinas com `await`,
+inclusive corrotinas da biblioteca. Como já mencionado, a maior parte da
+implementação de `await` vem de `yield from`, que internamente invoca `.send`
+para acionar as corrotinas.
+
+O canal `await` termina em um esperável de baixo nível da biblioteca _HTTPX_,
+que devolve um gerador que o laço de eventos pode acionar em resposta a eventos
+tais como E/S de rede ou timers. 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`, podemos ter
+múltiplos canais de `await` ao mesmo tempo, permitindo acionar
+operações de E/S concorrentes em um único laço de eventos, em uma única
+thread.
+
+==== O problema do tudo ou nada
+
+No <>, note que eu reutilizei a função `get_flag` de
+_flags.py_ (<> do <>). 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 acionada com `await` ou `asyncio.create_task`. Assim o controle é
+devolvido ao laço de eventos, enquanto não chega a resposta da operação de envio
+ou recebimento de dados. Se for inviável reescrever a função bloqueante como
+uma corrotina, devemos executá-la em outra thread ou processo, como
+veremos na <>.
+
+Por isso escolhi a epígrafe desse capítulo, que inclui o conselho:
+"Ou você reescreve todo o código, de forma que nada nele bloqueie 
+ou está só perdendo tempo."
+
+Pela mesma razão, também não pude reutilizar a
+função `download_one` de _flags_threadpool.py_
+(<> do <>).
+O código no <> aciona `get_flag` com `await`,
+então `download_one` precisa também ser uma corrotina.
+Para cada requisição, `supervisor` cria um objeto corrotina `download_one`,
+e eles são todos acionados pela corrotina `asyncio.gather`.
+
+Vamos agora estudar a instrução `async with`, que apareceu em `supervisor`
+(<>) e `get_flag` (<>).((("", startref="APdown21")))((("", startref="NIOdownload21")))((("", startref="httpx21")))
+
+
+[[async_context_manager_sec]]
+=== Gerenciadores de contexto assíncronos
+
+Na((("context managers", "asynchronous", id="CMasync21")))((("asynchronous programming", "asynchronous context managers", id="APRaconman21"))) <>, vimos como um objeto pode ser usado para executar código antes e depois do corpo de um bloco `with`, se sua classe oferecer os métodos `+__enter__+` e `+__exit__+`.
+
+Agora, considere o <>, que usa o driver PostgreSQL https://fpy.li/21-10[_asyncpg_] compatível com o `asyncio` (https://fpy.li/21-11[documentação do _asyncpg_ sobre transações]).
+
+[[asyncpg_transaction_no_context_ex]]
+.Código exemplo da documentação do driver PostgreSQL _asyncpg_
+====
+[source, python]
+----
+tr = connection.transaction()
+await tr.start()
+try:
+    await connection.execute("INSERT INTO mytable VALUES (1, 2, 3)")
+except:
+    await tr.rollback()
+    raise
+else:
+    await tr.commit()
+----
+====
+
+Uma transação de banco de dados se presta naturalmente a protocolo de
+gerenciador de contexto: a transação precisa ser iniciada, dados são modificados
+com `connection.execute`, e então temos que fazer um _rollback_ (reversão) ou um
+_commit_ (confirmação), dependendo do resultado das mudanças.
+
+Em((("asyncpg"))) um driver assíncrono como o _asyncpg_, a configuração e a
+execução precisam acontecer em corrotinas, para que outras operações possam
+ocorrer de forma concorrente. Entretanto, a implementação da instrução `with`
+clássica não suporta corrotinas na implementação dos métodos `+__enter__+` ou
+`+__exit__+`.
+
+Por isso a
+https://fpy.li/pep492[_PEP 492—Coroutines with `async` and `await` syntax_] 
+(Corrotinas com a sintaxe `async` e `await`) introduziu a instrução `async with`,
+que funciona com gerenciadores de contexto assíncronos: objetos
+que implementam os métodos `+__aenter__+` e `+__aexit__+` como corrotinas.
+
+Usando `async with`, o <> pode ser escrito como
+esse outro exemplo da https://fpy.li/21-11[«documentação do _asyncpg_»]:
+
+[source, python]
+----
+async with connection.transaction():
+    await connection.execute("INSERT INTO mytable VALUES (1, 2, 3)")
+----
+
+Na
+https://fpy.li/21-13[«classe `asyncpg.Transaction`»],
+o método corrotina `+__aenter__+` executa `await self.start()`, e a corrotina
+`+__aexit__+` aciona um dos métodos corrotina privados `+__rollback+` ou
+`+__commit+`, dependendo da ocorrência ou não de uma exceção. Usar corrotinas para
+implementar `Transaction` como um gerenciador de contexto assíncrono permite ao
+_asyncpg_ controlar muitas transações concorrentemente.
+
+.Caleb Hattingh sobre o asyncpg
+[TIP]
+====
+Outro detalhe impressionante sobre o _asyncpg_
+é que ele também contorna a falta de suporte à alta-concorrência do PostgreSQL
+(que usa um processo servidor por conexão)
+implementando um banco de conexões para conexões internas ao próprio Postgres.
+
+Isto significa que não precisamos de ferramentas adicionais (por exemplo o _pgbouncer_),
+como explicado na https://fpy.li/21-14[«documentação»]
+ do _asyncpg_.
+====
+
+Voltando ao _flags_asyncio.py_, a classe `AsyncClient` do `httpx` é um
+gerenciador de contexto assíncrono, para poder acionar esperáveis em seus métodos
+corrotina especiais `+__aenter__+` e `+__aexit__+`.
+
+
+[NOTE]
+====
+
+A <> mostra como usar a `contextlib` de Python para
+criar um gerenciador de contexto assíncrono sem precisar escrever uma classe.
+Esta explicação aparece mais tarde nesse capítulo por causa de um pré-requisito:
+a <>.
+
+====
+
+Agora vamos melhorar o exemplo `asyncio` de download de bandeiras com uma barra
+de progresso, que nos levará a explorar um pouco mais a API do `asyncio`.((("",
+startref="APRaconman21")))((("", startref="CMasync21")))
+
+
+[[flags2_asyncio_sec]]
+=== Melhorando o download de bandeiras assíncrono
+
+Vamos((("asynchronous programming", "enhancing asyncio downloader", id="APRenhanc21")))((("asyncio package", "enhancing asyncio downloader", id="APenhanc21")))((("network I/O", "enhancing asyncio downloader", id="NIOenhance21"))) recordar a <>, na qual o conjunto de exemplos `flags2` compartilhava a mesma interface de linha de comando, e todos mostravam uma barra de progresso enquanto os downloads aconteciam. Eles também incluíam tratamento de erros.
+
+[TIP]
+====
+Encorajo você a brincar com os exemplos `flags2`, para desenvolver uma intuição sobre o funcionamento de clientes HTTP concorrentes.
+Use a opção `-h` para ver a tela de ajuda no  <>.
+Use as opções de linha de comando `-a`, `-e`, e `-l` para controlar o número de downloads,
+e a opção `-m` para estabelecer o número de downloads concorrentes.
+Execute testes com os servidores `LOCAL`, `REMOTE`, `DELAY`, e `ERROR`.
+Descubra o número ótimo de downloads concorrentes para maximizar a taxa de transferência de cada servidor.
+Varie as opções dos servidores de teste, como descrito na caixa _<>_ da <>.
+====
+
+Por exemplo, o <> mostra uma tentativa de obter 100 bandeiras (`-al 100`) do servidor `ERROR`,
+usando 100 conexões concorrentes (`-m 100`).
+Os 48 erros no resultado são ou HTTP 418 ou erros de tempo de espera excedido (_time-out_)—o [mau]comportamento esperado do _slow_server.py_.
+
+[[flags2_asyncio_run_repeat]]
+.Running flags2_asyncio.py
+====
+[source]
+----
+$ python3 flags2_asyncio.py -s ERROR -al 100 -m 100
+ERROR site: http://localhost:8002/flags
+Searching for 100 flags: from AD to LK
+100 concurrent connections will be used.
+100%|█████████████████████████████████████████| 100/100 [00:03<00:00, 30.48it/s]
+--------------------
+ 52 flags downloaded.
+ 48 errors.
+Elapsed time: 3.31s
+----
+====
+
+[WARNING]
+.Seja responsável ao testar clientes concorrentes
+====
+Mesmo que o tempo total de download não seja muito diferente entre os clientes HTTP na versão com threads e na versão `asyncio` HTTP , o
+_asyncio_ é capaz de enviar requisições mais rápido, então aumenta a probabilidade do servidor suspeitar de um ataque DoS.
+Para testar estes clientes concorrentes em sua capacidade máxima, por favor use servidores HTTP locais em seus testes,
+como explicado na caixa _<>_ da <>.
+====
+
+Agora vejamos como o _flags2_asyncio.py_ é implementado.
+
+[[using_as_completed_sec]]
+==== Usando `asyncio.as_completed` e uma thread
+
+No((("threads", "enhancing asyncio downloader", id="Tenhance21")))
+<>, passamos várias corrotinas para `asyncio.gather`, que
+devolve uma lista com os resultados das corrotinas na ordem em que foram
+submetidas. Isto significa que `asyncio.gather` só pode retornar quando todos os
+esperáveis terminarem. Entretanto, para atualizar a barra de progresso,
+precisamos receber cada resultado assim que ele está pronto.
+
+Felizmente existe uma equivalente assíncrona da função geradora `as_completed`
+que usamos no exemplo de banco de threads com a barra de progresso,
+(<> do <>).
+
+O <> mostra o início do script _flags2_asyncio.py_, onde as
+corrotinas `get_flag` e `download_one` são definidas. O <>
+lista o restante do código-fonte, com `supervisor` e `download_many`. O script é
+maior que _flags_asyncio.py_ por causa do tratamento de erros.
+
+[[flags2_asyncio_top]]
+.flags2_asyncio.py: parte superior (inicial) do script; o resto do código está no <>
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags2_asyncio.py[tags=FLAGS2_ASYNCIO_TOP]
+----
+====
+
+<1> `get_flag` é muito similar à versão sequencial no <>.
+Primeira diferença: ela requer o parâmetro `client`.
+
+<2> Segunda e terceira diferenças: `.get` é um método de `AsyncClient`, e é uma
+corrotina, então precisamos acioná-la com `await`.
+
+<3> Usa o `semaphore` como um gerenciador de contexto assíncrono, assim o
+programa todo não é bloqueado; apenas essa corrotina é suspensa quando o
+contador do semáforo é zero. Veremos mais sobre isso na caixa _<>_,
+<>.
+
+<4> A lógica de tratamento de erro é idêntica à de `download_one`, do
+<> do <>.
+
+<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"—pois
+ler/escrever arquivos é https://fpy.li/21-15[«milhares de vezes mais demorado»]
+que ler/escrever na RAM. Se você estiver usando
+https://fpy.li/av[«armazenamento conectado à rede»], acessar "o disco"
+significa fazer E/S na rede local.
+
+Desde o Python 3.9, a corrotina `asyncio.to_thread` facilitou delegar operações
+de arquivo para um banco de threads fornecido pelo `asyncio`. Se você precisa
+suportar Python 3.7 ou 3.8, a <> mostra como fazer
+isso, adicionando algumas linhas ao seu programa. Mas antes, vamos terminar
+nosso estudo do código do cliente HTTP.
+
+[[limiting_req_with_semaphore_sec]]
+==== Limitando as requisições com um semáforo
+
+Clientes de rede((("throttling", id="throttle21")))((("semaphores",
+id="semaphores21"))) como os que estamos estudando devem ser _throttled_
+(limitados no desempenho) para não martelarem o servidor com um número excessivo de
+requisições concorrentes.
+
+Um https://fpy.li/aw[_semáforo_]
+é uma estrutura primitiva de sincronização, mais flexível que uma trava.
+O mesmo semáforo é usado por várias corrotinas, com um número máximo configurável.
+Assim podemos limitar o número de corrotinas concorrentes ativas usando o semáforo.
+A caixa _<>_ tem mais informações.
+
+No _flags2_threadpool.py_ (<> do <>), a
+limitação de desempenho (_throttling_) foi feita na função `download_many`
+instanciando o `ThreadPoolExecutor` passando um número máximo de threads no
+argumento `max_workers`. Em _flags2_asyncio.py_, um `asyncio.Semaphore` é criado
+pela função `supervisor` (mostrada no <>) e passado como o
+argumento `semaphore` para `download_one` no <>.
+
+[[about_semaphores_box]]
+.Semáforos no Python
+****
+
+O cientista da computação Edsger W. Dijkstra inventou o
+https://fpy.li/aw[«semáforo»] no início dos anos 1960. É uma ideia simples, mas
+tão flexível que a maioria dos outros objetos de sincronização—como as travas e
+as barreiras—podem ser construídas a partir de semáforos. Há três classes
+`Semaphore` na biblioteca padrão de Python: uma em `threading`, outra em
+`multiprocessing`, e uma terceira em `asyncio`. Essas classes têm interfaces
+parecidas, mas suas implementações são bem diferentes. Aqui apresento a versão
+de `asyncio`.
+
+Um `asyncio.Semaphore` tem um contador interno que é decrementado toda vez que
+acionamos o método corrotina `.acquire()` com `await`.
+O contador é incrementado
+quando invocamos o método `.release()`—que não é uma corrotina porque nunca
+bloqueia. O valor inicial do contador é definido quando o `Semaphore` é
+instanciado:
+
+[source, python]
+----
+    semaphore = asyncio.Semaphore(concur_req)
+----
+
+Fazer `await` em `.acquire()` não bloqueia quando o contador
+interno é maior que zero.
+Mas o contador está zerado, `.acquire()` suspende
+a corrotina que chamou `await` até que alguma outra corrotina chame
+`.release()` no mesmo `Semaphore`, incrementando assim o contador.
+
+Em vez de usar estes métodos diretamente,
+é mais seguro usar o `semaphore` como um gerenciador de contexto assíncrono,
+como fiz na função `download_one` em <>:
+
+[source, python]
+----
+        async with semaphore:
+            image = await get_flag(client, base_url, cc)
+----
+
+O método corrotina `+Semaphore.__aenter__+` espera por `.acquire()`
+(usando `await` internamente),
+e seu método corrotina `+__aexit__+` invoca `.release()`.
+Este bloco `async with` garante que no máximo `concur_req`
+instâncias de corrotinas `get_flags` estarão ativas em qualquer momento.
+
+Cada uma das classes `Semaphore` na biblioteca padrão tem uma subclasse
+`BoundedSemaphore`, que impõe uma restrição adicional: o contador interno não
+pode nunca ficar maior que o valor inicial, impedindo mais operações
+`.release()` que `.acquire()`.footnote:[Agradeço a Guto Maia, que notou que o
+conceito de semáforo não era explicado quando leu o primeiro rascunho deste
+capítulo.]
+
+****
+
+Agora vamos olhar o resto do script no <>.
+
+[[flags2_asyncio_rest]]
+.flags2_asyncio.py: continuação de <>
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags2_asyncio.py[tags=FLAGS2_ASYNCIO_START]
+----
+====
+
+<1> `supervisor` recebe os mesmos argumentos que a função `download_many`, mas
+não pode ser invocada diretamente de `main`, pois é uma corrotina e não uma
+função comum 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 definidas 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 laço
+`for` abaixo porque posso precisar envolvê-la com o iterador `tqdm` para a barra
+de progresso, dependendo da opção de verbosidade na linha de comando.
+
+<5> Envolve o iterador `as_completed` com a função geradora `tqdm`, para mostrar
+o progresso.
+
+<6> Declara e inicializa `error` com `None`; esta variável será usada para
+salvar uma exceção além do bloco `try/except`, se alguma for levantada.
+
+<7> Itera pelos objetos corrotina que terminaram a execução; este laço é similar
+ao de `download_many` no <> do <>.
+
+<8> Aciona a corrotina com `await` para obter seu resultado. Isto não bloqueia porque
+`as_completed` só produz corrotinas que já terminaram.
+
+<9> Esta atribuição é necessária porque o escopo da variável `exc` é limitado a
+esta cláusula `except`, mas preciso preservar o valor para uso posterior.
+
+<10> Mesmo que acima.
+
+<11> Se houve um erro, muda o `status`.
+
+<12> 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
+laço de eventos com `asyncio.run`, coletando o contador que `supervisor` devolve
+quando o laço de eventos termina.
+
+No <>, não pudemos usar o mapeamento de `futures` para os
+códigos de país que vimos em <> do <>,
+porque os esperáveis devolvidos por `asyncio.as_completed` não são
+necessariamente os mesmos esperáveis que passamos na invocação de
+`as_completed`. Internamente, a lógica do `asyncio` pode embrulhar os esperáveis
+que fornecemos por outros esperáveis que afinal produzirão os mesmos
+resultados.footnote:[Iniciei uma discussão sobre esta questão 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 e Victor Stinner e fornece detalhes sobre a
+implementação de `as_completed`, bem como 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 país da
+exceção. Para fazer isso, preservei 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: instruções como laços e `try/except` não criam um escopo local nos 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
+dentro daquela cláusula `except` específica.
+
+====
+
+Isto encerra nossa discussão da funcionalidade de um cliente Web
+com tratamento de erros e barra de progresso, implementado com `asyncio`.
+
+O próximo exemplo demonstra um modelo simples de execução de uma tarefa
+assíncrona após outra usando corrotinas.
+
+==== Fazendo múltiplas requisições para cada download
+
+
+Pessoas com experiência em JavaScript sabem que rodar uma função assíncrona após
+outra acabou gerando o padrão de funções aninhadas conhecido como
+https://fpy.li/21-20[_pyramid of doom_] (pirâmide da perdição). A palavra-chave
+`await` desfaz a maldição. Por isso `await` agora é parte de Python e de
+JavaScript.((("", startref="throttle21")))((("", startref="semaphores21")))
+Vamos a um exemplo.
+
+Suponha que você queira salvar cada bandeira com o nome do país e o código, em
+vez de apenas o código. Agora você precisa fazer duas requisições HTTP por
+bandeira: uma para pegar a imagem da bandeira propriamente dita, a outra para
+pegar o arquivo _metadata.json_, no mesmo diretório da imagem—é nesse arquivo
+que o nome do país está registrado.
+
+Coordenar múltiplas requisições na mesma tarefa é fácil no script com threads:
+basta fazer uma requisição depois a outra, bloqueando a thread duas vezes, e
+preservando os dados (código e nome do país) em variáveis locais, prontas para
+serem usadas na hora de salvar o arquivo. Se você precisasse fazer o mesmo em um
+script assíncrono com callbacks, precisaria de funções aninhadas, de forma que o
+código e o nome do país estivessem disponíveis em clausuras até o momento de
+salvar o arquivo, pois cada callback roda em um escopo local diferente. A
+palavra-chave `await` evita este aninhamento, permitindo que
+você acione as requisições assíncronas uma após a outra, compartilhando o escopo
+local da corrotina que aciona as ações.
+
+
+[TIP]
+====
+
+Se você está trabalhando com programação de aplicações assíncronas no Python
+moderno e recorre a uma grande quantidade de callbacks, provavelmente está
+aplicando modelos antigos, que não fazem mais sentido no Python atual. Isso é
+justificável se você estiver escrevendo uma biblioteca que se conecta a código
+legado ou código de baixo nível sem suporte a corrotinas. De qualquer
+forma, a discussão no StackOverflow,
+https://fpy.li/21-21[_What is the use case for future.add_done_callback()?_]
+(Qual o caso de uso para future.add_done_callback()?) explica por que
+callbacks são necessários em código de baixo nível, mas não são muito úteis
+hoje em dia em código Python no nível da aplicação.
+
+====
+
+A terceira variante do script `asyncio` de download de bandeiras traz algumas mudanças:
+
+`get_country`:: Esta nova corrotina baixa o arquivo _metadata.json_ daquele código de país, e extrai dele o nome do país.
+`download_one`:: Esta corrotina agora usa `await` para delegar para `get_flag` e para a nova corrotina `get_country`, usando o resultado dessa última para compor o nome do arquivo a ser salvo.
+
+Vamos começar com o código de `get_country` (<>).
+Note que é muito parecido com o `get_flag` do <>.
+
+[[flags3_asyncio_get_country]]
+.flags3_asyncio.py: corrotina `get_country`
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags3_asyncio.py[tags=FLAGS3_ASYNCIO_GET_COUNTRY]
+----
+====
+<1> Esta corrotina devolve uma string com o nome do país—se tudo correr bem.
+<2> `metadata` vai receber um `dict` Python construído a partir do conteúdo JSON da resposta.
+<3> Devolve o nome do país.
+
+Agora vamos ver o `download_one` modificado do <>, que tem apenas algumas linhas diferentes da corrotina de mesmo nome do <>.
+
+[[flags3_asyncio]]
+.flags3_asyncio.py: corrotina `download_one`
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags3_asyncio.py[tags=FLAGS3_ASYNCIO_DOWNLOAD_ONE]
+----
+====
+<1> Retém o `semaphore` para acionar `get_flag`...
+<2> ...e novamente para acionar `get_country`.
+<3> Usa o nome do país para criar um nome de arquivo.
+Como usuário da linha de comando, não gosto de espaços em nomes de arquivo.
+
+Muito melhor que callbacks aninhados!
+
+Coloquei as chamadas a `get_flag` e `get_country` em blocos `with` separados,
+controlados pelo `semaphore` porque é uma boa prática reter semáforos e travas
+pelo menor tempo possível.
+
+Eu poderia ter agendado as funções `get_flag` e `get_country`, em
+paralelo, usando `asyncio.gather`, mas se `get_flag` levantar uma exceção não
+haverá imagem para salvar, então é inútil rodar `get_country`. Mas há casos
+em que faz sentido usar `asyncio.gather` para acessar várias APIs simultaneamente,
+em vez de esperar por uma resposta antes de fazer a próxima requisição
+
+Em _flags3_asyncio.py_, a sintaxe `await` aparece seis vezes, e `async with` três vezes.
+Espero que você esteja pegando o jeito da programação assíncrona em Python.
+
+Um desafio é saber quando você precisa usar `await` e quando você não pode usá-la.
+A resposta, em princípio, é fácil: use `await` para
+acionar corrotinas e outros esperáveis, como instâncias de `asyncio.Task`.
+Mas algumas APIs são complexas, misturam corrotinas e funções comuns
+de forma aparentemente arbitrária, como a classe `StreamWriter` que usaremos no <>.
+
+O <> encerra o grupo de exemplos _flags_. Vamos agora discutir o
+uso de executores de threads ou processos na programação assíncrona.((("",
+startref="APRenhanc21")))((("", startref="APenhanc21")))((("",
+startref="Tenhance21")))((("", startref="NIOenhance21")))
+
+[[delegating_to_executors_sec]]
+=== 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 o Python para programação assíncrona é a
+biblioteca padrão do Node.js, que inclui APIs assíncronas para todo tipo de
+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
+usar thread principal para ler e escrever no armazenamento bloqueia o laço de
+eventos.
+
+Na corrotina `download_one` de <>, usei a seguinte linha
+para salvar a imagem baixada:
+
+[source, python]
+----
+        await asyncio.to_thread(save_flag, image, f'{cc}.gif')
+----
+
+Como mencionei antes, o `asyncio.to_thread` foi acrescentado no Python 3.9.
+Se você precisa suportar 3.7 ou 3.8,
+substitua aquela linha pelas linhas em  <>.
+
+[[flags2_asyncio_executor_fragment]]
+.Linhas para usar no lugar de `await asyncio.to_thread`
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags2_asyncio_executor.py[tags=FLAGS2_ASYNCIO_EXECUTOR]
+----
+====
+<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, um `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, use
+`functool.partial`, como descrito na
+https://fpy.li/ax[«documentação de `run_in_executor`»].
+
+A função mais nova `asyncio.to_thread` é mais fácil de usar e mais flexível, já que também aceita argumentos nomeados.
+
+A própria implementação de `asyncio` usa `run_in_executor` debaixo dos panos em
+alguns pontos. Por exemplo, a corrotina `loop.getaddrinfo(…)`,  que vimos no
+<> é implementada invocando a função `getaddrinfo` do módulo
+`socket`—uma função bloqueante que pode levar alguns segundos para retornar,
+pois depende de resolução de DNS.
+
+Um padrão comum em APIs assíncronas é encapsular em corrotinas quaisquer
+chamadas bloqueantes que sejam detalhes de implementação, e usar
+`run_in_executor` dentro das corrotinas para executar as chamadas bloqueantes.
+Assim é possível apresentar uma interface consistente de corrotinas a serem
+acionadas com `await`, escondendo as threads que precisam ser usadas por razões
+pragmáticas.
+
+Por exemplo, o driver assíncrono do MongoDB para Python, chamado
+https://fpy.li/21-23[_Motor_], tem uma API compatível com `async/await` que na
+verdade é uma fachada, escondendo um banco de threads que conversa com o
+MongoDB. O criador do _Motor_, A. Jesse Jiryu Davis, explica suas razões em
+https://fpy.li/21-24[_Response to "Asynchronous Python and Databases"_]
+(Resposta a "Python Assíncrono e os Bancos de Dados"). Spoiler: Jiryu Davis
+descobriu que um banco de threads tem melhor desempenho no caso de uso
+específico de um driver de banco de dados—desmascarando o mito de que abordagens
+assíncronas são sempre mais eficientes que threads para E/S de rede.
+
+A principal razão para passar um `Executor` explícito para
+`loop.run_in_executor` é utilizar um `ProcessPoolExecutor`, se a função ocupar
+intensivamente a CPU. Assim ela rodará em um processo Python diferente, evitando
+a disputa pela GIL. Por seu alto custo de inicialização, seria melhor iniciar o
+`ProcessPoolExecutor` no `supervisor`, e passá-lo para as corrotinas que
+precisem utilizá-lo.
+
+O revisor Caleb Hattingh (autor de
+https://fpy.li/hattingh[_Using Asyncio in Python_])
+me passou o seguinte aviso sobre executores e o `asyncio`.
+
+.O aviso de Caleb sobre run_in_executors
+[WARNING]
+====
+
+Usar `run_in_executor` pode produzir problemas difíceis de depurar, já que o
+cancelamento não funciona da forma esperada. Corrotinas que usam
+executores apenas fingem terminar: a thread subjacente (se for um
+`ThreadPoolExecutor`) não tem um mecanismo de cancelamento. Por exemplo, uma
+thread de longa duração criada dentro de uma chamada a `run_in_executor` pode
+impedir que seu programa `asyncio` encerre de forma limpa: `asyncio.run` vai
+esperar para retornar até o executor terminar completamente, e vai esperar para
+sempre se os serviços iniciados pelo executor não pararem sozinhos de alguma
+forma. Minha barba branca sugere que aquela função deveria se chamar
+`run_in_executor_uncancellable`.
+
+====
+
+Agora saímos de scripts clientes para escrever servidores com o `asyncio`.((("",
+startref="exedel21")))((("", startref="APRdelegat21")))
+
+
+=== Programando servidores assíncronos
+
+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://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`.
+
+Estes servidores permitem que os usuários pesquisem caracteres Unicode buscando
+palavras que ocorrem em seus nomes fornecidos pelo módulo `unicodedata` que discutimos na
+<>. A <> mostra uma sessão com o
+_web_mojifinder.py_, o primeiro servidor que escreveremos.
+
+[[web_mojifinder_result]]
+.Janela de navegador mostrando os resultados da busca por "mountain" no serviço web_mojifinder.py.
+image::../images/flpy_2102.png[Captura de tela de conexão do Firefox com o web_mojifinder.py]
+
+A lógica de busca no Unicode nesses exemplos é a classe `InvertedIndex` no
+módulo _charindex.py_ no https://fpy.li/code[«repositório de código do _Python
+Fluente_»]. Não há nada concorrente naquele pequeno módulo, então 
+o box opcional a seguir contém apenas uma breve explicação sobre ele.
+Você pode pular para a implementação do servidor HTTP na <>.
+
+.Conhecendo o índice invertido
+****
+
+Um((("inverted indexes"))) índice invertido normalmente mapeia palavras a
+documentos onde elas ocorrem.
+Nos exemplos _mojifinder_, cada "documento" é o nome de um caractere Unicode.
+A classe `charindex.InvertedIndex` indexa cada palavra que aparece no nome de
+cada caractere no banco de dados Unicode, e cria um índice invertido em um `defaultdict`.
+Por exemplo, para indexar o caractere U+0037—DIGIT SEVEN—o construtor
+de `InvertedIndex` anexa o caractere `'7'` aos registros sob as chaves `'DIGIT'` e `'SEVEN'`.
+Após indexar os dados do Unicode 13 incluídos no Python 3.10, `'DIGIT'` será mapeado para
+868 caracteres que têm esta palavra em seus nomes;
+e `'SEVEN'` para  143, incluindo U+1F556—CLOCK FACE SEVEN OCLOCK e
+U+2790—DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SEVEN.
+
+Veja na <> uma demonstração usando os caracteres indexados para as palavras `'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. Ele vem do 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
+image::../images/flpy_2103.png[Captura de tela do console de Python]
+
+O método `InvertedIndex.search` quebra a consulta em palavras separadas, e devolve a interseção dos registros para cada palavra.
+É por isso que buscar por "face" encontra 171 resultados, "cat" encontra 14, mas "cat face" apenas 10.
+
+Esta é 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://fpy.li/az[«Listas Invertidas»] na Wikipedia para saber mais.
+****
+
+[[fastapi_web_service_sec]]
+==== Um serviço Web com _FastAPI_
+
+Escrevi((("FastAPI framework", id="fastapi21"))) o próximo
+exemplo—_web_mojifinder.py_—usando o https://fpy.li/21-28[_FastAPI_]: um dos
+frameworks ASGI para desenvolvimento Web em Python, mencionado na
+<>. A <> é uma captura de tela da
+interface de usuário. É uma aplicação simples, de uma página só (_SPA_,
+_Single Page Application_): após o download inicial do HTML, a interface é
+atualizada via JavaScript no cliente, em comunicação com o servidor.
+
+O _FastAPI_ foi projetado para implementar o lado servidor de SPAs e aplicativos
+de celular, que consistem principalmente de pontos de acesso de APIs Web, devolvendo
+respostas JSON em vez de HTML renderizado no servidor. O _FastAPI_ se vale de
+decoradores, dicas de tipo e introspecção de código para eliminar muito
+código repetitivo das APIs Web, e também gera uma
+documentação no padrão OpenAPI do https://fpy.li/21-29[_Swagger_].
+A <> mostra a página `/docs` do
+_web_mojifinder.py_, gerada automaticamente.
+
+[[web_mojifinder_schema]]
+.Documentação OpenAPI do ponto de acesso `/search`, gerada automaticamente.
+image::../images/flpy_2104.png[Captura de tela do Firefox mostrando o schema OpenAPI para o ponto de acesso `/search`]
+
+O <> é o código de _web_mojifinder.py_, mas é só
+código do lado servidor. Quando você acessa a URL raiz `/`, o servidor envia o
+arquivo _form.html_, que contém 81 linhas de código, incluindo 54 linhas de
+JavaScript para comunicação com o servidor e preenchimento da tabela com os
+resultados. Se tiver interesse em ler JavaScript puro sem uso de frameworks,
+confira o _21-async/mojifinder/static/form.html_ no
+https://fpy.li/code[«repositório de código»] do _Python Fluente_.
+
+Para rodar o _web_mojifinder.py_, você precisa instalar dois pacotes e suas
+dependências: _FastAPI_ e _uvicorn_.footnote:[Você pode usar outro servidor ASGI
+no lugar do _uvicorn_, como o _hypercorn_ ou o _Daphne_. Veja na documentação
+oficial do ASGI a https://fpy.li/21-30[«página sobre implementações»] para
+mais informações.]
+
+Este é o comando para executar o <> com _uvicorn_ em modo de desenvolvimento:
+
+[source, shell]
+----
+$ uvicorn web_mojifinder:app --reload
+----
+
+os parâmetros são:
+
+`web_mojifinder:app`::
+  O nome do pacote, dois pontos, e o nome da aplicação ASGI definida nele—`app` é o nome usado por convenção.
+   
+`--reload`::
+  Faz o _uvicorn_ monitorar mudanças no código-fonte da aplicação, e recarregá-la automaticamente. Útil apenas durante o desenvolvimento.
+
+Vamos agora olhar o código-fonte do _web_mojifinder.py_.
+
+
+[[web_mojifinder_ex]]
+.web_mojifinder.py: código-fonte completo
+====
+[source, python]
+----
+include::../code/21-async/mojifinder/web_mojifinder.py[]
+----
+====
+
+<1> Não relacionado ao tema desse capítulo, mas digno de nota: o uso elegante do
+operador `/` sobrecarregado por `pathlib`.footnote:[Agradeço ao revisor técnico
+Miroslav Šedivý por apontar bons lugares para usar `pathlib` nos exemplos de
+código.]
+
+<2> Esta linha define a app ASGI. Ela poderia ser apenas `app =
+FastAPI()`. Os parâmetros são metadados para a documentação da API,
+gerada automaticamente.
+
+<3> Um schema _pydantic_ para uma resposta JSON, com campos `char` e
+`name`.footnote:[Como mencionado no <>, o
+https://fpy.li/21-31[_pydantic_] aplica dicas de tipo durante a execução, para
+validação de dados.]
+
+<4> Cria o `index` e carrega o formulário HTML estático, anexando ambos ao
+`app.state` para uso posterior.
+
+<5> Roda `init` quando esse módulo é carregado pelo servidor ASGI.
+
+<6> Rota para o ponto de acesso `/search`; `response_model` usa aquele modelo
+`CharName` do _pydantic_ para descrever o formato da resposta.
+
+<7> O _FastAPI_ assume que qualquer parâmetro que apareça na assinatura da
+função ou da corrotina e que não esteja no caminho da rota será passado na
+string de consulta HTTP, isto é, `/search?q=cat`. Como `q` não tem default, a
+_FastAPI_ devolverá um status 422 (Unprocessable Entity, _Entidade
+Não-Processável_) se `q` não estiver presente na string da consulta.
+
+<8> Devolver um iterável de `dicts` compatível com o schema `response_model`
+permite ao _FastAPI_ criar uma resposta JSON de acordo com o `response_model` no
+decorador `@app.get`,
+
+<9> Funções regulares (isto é, não-assíncronas) também podem ser usadas para
+produzir respostas.
+
+<10> Este módulo não tem uma função principal. É carregado e acionado pelo
+servidor ASGI—neste exemplo, o _uvicorn_.
+
+O <> não faz chamada direta ao `asyncio`.
+O _FastAPI_ é construído sobre o toolkit ASGI _Starlette_, que por sua vez usa o `asyncio`.
+
+Note também que o corpo de `search` não usa `await`, `async with`, ou `async for`,
+então poderia ser uma função comum.
+Defini `search` como uma corrotina apenas para mostrar que o _FastAPI_ sabe como lidar com elas.
+Em uma aplicação real, a maioria dos pontos de acesso serão consultas
+a bancos de dados ou acessos a outros servidores remotos,
+então é uma vantagem importante do _FastAPI_—e dos frameworks ASGI em geral—
+suportarem corrotinas que podem se valer de bibliotecas assíncronas para E/S de rede.
+
+[TIP]
+====
+
+As funções `init` e `form` para carregar e entregar o HTML estático do
+formulário são gambiarras para manter esse exemplo curto e fácil de rodar
+sem mais configurações.
+
+A melhor prática é ter um proxy/balanceador de carga na frente do
+ASGI, servindo todos os recursos estáticos, e também usar uma _CDN_ 
+(Rede de Entrega de Conteúdo) quando possível.
+
+Um proxy/balanceador de carga deste tipo é o https://fpy.li/21-32[_Traefik_],
+descrito como um _edge router_ (roteador de ponta), que "recebe requisições em
+nome de seu sistema e descobre quais componentes são responsáveis por lidar com
+elas." O site do _FastAPI_ apresenta 
+https://fpy.li/c5/[«ferramentas de geração de projeto»]
+que organizam o código para usar o _Trafik_ e outros sofwares auxiliares.
+
+====
+
+Os entusiastas da tipagem estática podem ter notado que não coloquei dicas de
+tipo para os resultados devolvidos por `search` e `form`. Em vez disto, o
+_FastAPI_ aceita o argumento nomeado `response_model=` nos decoradores de rota.
+A página https://fpy.li/21-34[_Response Model - Return Type_] da documentação do _FastAPI_
+explica:
+
+[quote]
+____
+
+O modelo de resposta é declarado neste parâmetro em vez de como uma anotação de
+tipo de resultado devolvido por uma função, porque a função de rota pode não
+devolver aquele modelo de resposta mas sim um `dict`, um objeto do banco de dados
+ou algum outro modelo, e então usar o `response_model` para realizar a validação
+de campos e a serialização.
+____
+
+Por exemplo, em `search`, devolvi um gerador de itens `dict` e não uma lista
+de objetos `CharName`, mas isso basta para o _FastAPI_ e o _pydantic_
+validarem meus dados e construírem a resposta JSON apropriada, compatível com
+`response_model=list[CharName]`.
+
+Agora vamos analisar outro servidor que 
+usa _sockets_ TCP e a biblioteca padrão do Python para responder consultas via
+Telnet no terminal.((("", startref="fastapi21")))
+
+
+==== Um servidor TCP com `asyncio`
+
+O((("TCP servers", id="tcp21")))((("servers", "TCP servers", id="Stcp22")))
+programa _tcp_mojifinder.py_ usa TCP puro para se comunicar com um cliente como
+o Telnet ou o Netcat, então pude escrevê-lo usando `asyncio` sem dependências
+externas, e sem reinventar o HTTP. A <> mostra a interface
+de usuário em modo texto.
+
+
+[[tcp_mojifinder_demo]]
+.Sessão de telnet com o servidor tcp_mojifinder.py: consultando "fire."
+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_ (descontando o HTML e o JavaScript daquele exemplo).
+Por isso dividi a apresentação em três partes:
+<>, <>, e <>.
+O topo do arquivo _tcp_mojifinder.py_, com as instruções `import`, está no <>.
+Mas vou começar descrevendo a corrotina `supervisor` e a função `main` que controlam o programa.
+
+[[ex_tcp_mojifinder_main]]
+.tcp_mojifinder.py: um servidor TCP simples; continua no <>
+====
+[source, python]
+----
+include::../code/21-async/mojifinder/tcp_mojifinder.py[tags=TCP_MOJIFINDER_MAIN]
+----
+====
+
+<1> Este `await` devolve uma instância de `asyncio.Server`, um servidor TCP baseado
+em _sockets_. Por padrão, `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`, uma
+função de _callback_ para ser executada quando a conexão com um novo cliente se inicia.
+Ela pode ser uma função ou uma corrotina, mas precisa aceitar exatamente
+dois argumentos: um `asyncio.StreamReader` e um `asyncio.StreamWriter`.
+Porém, 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
+recebe o leitor (`StreamReader`) e o escritor (`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://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` em maio de 2021.
+Veja https://fpy.li/21-36[«Issue #5535 no _typeshed_»].footnote:[O bug
+#5535 está resolvido desde outubro de 2021, mas o Mypy não lançou uma nova versão
+até o fechamento desta edição, então o erro permanece.]
+
+<5> Exibe 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 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 laço de eventos rodando `supervisor`.
+
+<9> Captura `KeyboardInterrupt` para evitar um traceback ruidoso quando
+encerramos o servidor teclando 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 no
+<>.
+
+[[tcp_mojifinder_server_demo]]
+.tcp_mojifinder.py: isso é o lado servidor da sessão mostrada na <>
+====
+[source, text]
+----
+$ python3 tcp_mojifinder.py
+Building index.  # <1>
+Serving on ('127.0.0.1', 2323). Hit CTRL-C to stop.  # <2>
+ From ('127.0.0.1', 58192): 'cat face'   # <3>
+   To ('127.0.0.1', 58192): 10 results.
+ From ('127.0.0.1', 58192): 'fire'       # <4>
+   To ('127.0.0.1', 58192): 11 results.
+ From ('127.0.0.1', 58192): '\x00'       # <5>
+Close ('127.0.0.1', 58192).              # <6>
+^C  # <7>
+Server shut down.  # <8>
+$
+----
+====
+<1> Saída de `main`. Antes da próxima linha aparecer, notei um intervalo de 0,6s na minha máquina, enquanto o índice era construído.
+<2> Saída de `supervisor`.
+<3> Primeira volta do laço `while` na função `finder` do <>. A pilha TCP/IP atribuiu a porta 58192 a meu cliente Telnet. Se você conectar diversos clientes ao servidor, verá suas diferentes portas aparecerem na saída.
+<4> Segunda iteração do laço `while` em `finder`.
+<5> Teclei CTRL-C no terminal do cliente; o laço `while` em `finder` termina.
+<6> A corrotina `finder` exibe esta mensagem e encerra. Enquanto isso o servidor continua rodando, pronto para receber outros clientes.
+<7> Teclei 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 laço de eventos, a corrotina
+`supervisor` rapidamente exibe a mensagem `Serving on...`,
+e fica suspensa na última linha:
+
+[source, python]
+----
+    await server.serve_forever()
+----
+
+Neste ponto o controle flui para o laço de eventos do `asyncio` 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 a rede para enviar ou
+receber dados.
+
+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. Desta
+forma, milhares de clientes podem ser atendidos concorrentemente por este
+servidor simples. Isto segue até que ocorra um `KeyboardInterrupt` no servidor
+ou que seu processo seja encerrado 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 <>
+====
+[source, python]
+----
+include::../code/21-async/mojifinder/tcp_mojifinder.py[tags=TCP_MOJIFINDER_TOP]
+----
+====
+
+<1> `format_results` é útil para mostrar os resultados 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> Este 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, é uma função
+comum. Esta linha envia o prompt `?>`.
+
+<6> `StreamWriter.drain` esvazia o buffer de `writer`; ela é uma corrotina,
+então precisa ser acionada com `await`.
+
+<7> `StreamWriter.readline` é uma corrotina que devolve `bytes`.
+
+<8> Se nenhum byte foi recebido, o cliente fechou a conexão, então sai do loop.
+
+<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 laço se um caractere de controle ou null foi recebido.
+
+<13> `search` realiza a busca; 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://fpy.li/b4[documentação do método `.close()`].
+
+<17> Registra o final dessa sessão do cliente no console do servidor.
+
+O último trecho deste código é a corrotina `search`, <>.
+
+[[tcp_mojifinder_search]]
+.tcp_mojifinder.py: corrotina `search`
+====
+[source, python]
+----
+include::../code/21-async/mojifinder/tcp_mojifinder.py[tags=TCP_MOJIFINDER_SEARCH]
+----
+====
+<1> `search` precisa ser uma corrotina, pois escreve em um `StreamWriter` e precisa acionar o método corrotina `.drain()`.
+<2> Consulta o índice invertido.
+<3> Esta expressão geradora produzirá strings de bytes codificadas em UTF-8 com o ponto de código Unicode, o caractere, seu nome e uma sequência `CRLF` (_Return+Line Feed_), isto é, `b'U+0039\t9\tDIGIT NINE\r\n'`.
+<4> Envia `lines`. Surpreendentemente, `writer.writelines` não é uma corrotina.
+<5> Mas `writer.drain()` é uma corrotina. Não esqueça do `await`!
+<6> Constrói e envia uma linha de status.
+
+Observe que toda a E/S de rede em _tcp_mojifinder.py_ é feita em `bytes`; precisamos decodificar os `bytes` recebidos da rede, e codificar strings antes de enviá-las. No Python 3, a codificação default é UTF-8, e foi o que usei implicitamente em todas as chamadas a `encode` e `decode` nesse exemplo.
+
+[WARNING]
+====
+
+Note que alguns dos métodos de E/S são corrotinas, e precisam ser acionados com
+`await`, enquanto outros são funções comuns. Por exemplo, `StreamWriter.write`
+é uma função, 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 escrevi a primeira edição desse livro, sugeri uma melhoria na
+https://fpy.li/b5[«documentação da API»] do `asyncio` para indicar com mais
+clareza as corrotinas (antes era preciso ler todo o texto sobre uma corrotina
+para encontrar a informação, porque elas eram formatadas como as funções).
+
+====
+
+O código de _tcp_mojifinder.py_ se vale da 
+https://fpy.li/21-40[«API de streams»] de alto nível do `asyncio`,
+que fornece um servidor pronto para usar,
+então só precisamos codar 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 nas abstrações de 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 a API de baixo nível.
+
+Nosso próximo tópico é a instrução `async for` e os objetos que a fazem
+funcionar.((("", startref="tcp21")))((("", startref="APRwrit21")))((("",
+startref="APwrite21")))((("", startref="Stcp22")))((("", startref="Sasyncio21")))
+
+
+=== Iteráveis assíncronos
+
+Na((("asynchronous programming", "iteration and iterables",
+id="APRiteration21")))((("iterables", "asynchronous",
+id="ITasync21")))((("iterators", "asynchronous", id="ITERasync21")))
+<> vimos como `async with` funciona com objetos que
+implementam os métodos `+__aenter__+` e `+__aexit__+`, devolvendo
+esperáveis—normalmente na forma de objetos corrotina.
+
+De forma análoga, `async for` funciona com _iteráveis assíncronos_: objetos que
+implementam `+__aiter__+`. Entretanto, `+__aiter__+` precisa ser um método
+normal—não um método corrotina—e precisa devolver um _iterador assíncrono_.
+
+Um iterador assíncrono fornece um método corrotina `+__anext__+` que devolve um
+esperável—muitas vezes um objeto corrotina. Também se espera que eles
+implementem `+__aiter__+`, que normalmente devolve `self`. Isso espelha a
+importante distinção entre iteráveis e iteradores que vimos na
+<>.
+
+A https://fpy.li/21-43[«documentação»] do driver assíncrono de PostgreSQL _aiopg_
+traz um exemplo que ilustra o uso de `async for` para iterar sobre as linhas de
+resultados devolvidas pelo objeto cursor, definido no driver do banco de dados.
+
+[source, python]
+----
+async def go():
+    pool = await aiopg.create_pool(dsn)
+    async with pool.acquire() as conn:
+        async with conn.cursor() as cur:
+            await cur.execute("SELECT 1")
+            ret = []
+            async for row in cur:
+                ret.append(row)
+            assert ret == [(1,)]
+----
+
+Neste exemplo, a consulta vai devolver só uma 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ó. Por isso
+é 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 chegarem do PostgreSQL.
+
+
+[[async_gen_func_sec]]
+==== Funções geradoras assíncronas
+
+Você((("generators", "asynchronous generator functions", id="Gasync21"))) pode
+implementar um iterador assíncrono escrevendo uma classe com `+__anext__+` e
+`+__aiter__+`, mas há um jeito mais fácil: escreva uma função declarada com
+`async def` que use `yield` em seu corpo. Isto é semelhante à forma como funções
+geradoras simplificam o implementar o padrão do Iterador clássico.
+
+Vamos estudar um exemplo simples usando `async for` e implementando um gerador
+assíncrono. No <> vimos _blogdom.py_, um script que sondava nomes de
+domínio. Suponha agora que encontramos outros usos para a corrotina `probe`
+definida ali, e decidimos colocá-la em um novo módulo (_domainlib.py_) com
+um novo gerador assíncrono `multi_probe`, que recebe uma lista de nomes de
+domínio e produz resultados conforme eles são sondados.
+
+Vamos ver a implementação de _domainlib.py_ logo, mas primeiro examinaremos como
+ele é usado com o novo console assíncrono de Python.
+
+[[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 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_.
+Então execute:
+
+[source, shell]
+----
+$ python -m asyncio
+----
+
+Você verá o console iniciar, mais ou menos assim:
+
+[source, shell]
+----
+asyncio REPL 3.9.1 (v3.9.1:1e5d33e9b9, Dec  7 2020, 12:10:52)
+[Clang 6.0 (clang-600.0.57)] on darwin
+Use "await" directly instead of "asyncio.run()".
+Type "help", "copyright", "credits" or "license" for more information.
+>>> import asyncio
+>>>
+----
+
+Note como o cabeçalho diz que você pode usar `await` em vez de
+`asyncio.run()` para acionar corrotinas e outros esperáveis.
+E mais: eu não digitei `import asyncio`.
+O módulo `asyncio` é automaticamente importado e a instrução
+`import asyncio` é exibida para deixar isso evidente.
+
+[role="pagebreak-before less_space"]
+Vamos agora importar _domainlib.py_ e brincar com suas duas corrotinas: `probe` e `multi_probe` (<>).
+
+[[domainlib_demo_repl]]
+.Experimentando com _domainlib.py_ após executar `python3 -m asyncio`
+====
+[source, python]
+----
+>>> await asyncio.sleep(3, 'Bom dia!')  # <1>
+'Bom dia!'
+>>> from domainlib import *
+>>> await probe('python.org')  # <2>
+Result(domain='python.org', found=True)  # <3>
+>>> names = 'python.org rust-lang.org golang.org xyz.invalid'.split()  # <4>
+>>> async for result in multi_probe(names):  # <5>
+...      print(*result, sep='\t')
+...
+golang.org      True    # <6>
+xyz.invalid     False
+python.org      True
+rust-lang.org   True
+>>>
+----
+====
+<1> Experimente um simples `await` para ver o console assíncrono em ação. Dica: `asyncio.sleep()` pode receber um segundo argumento opcional que será devolvido através do `await`.
+<2> Acione a corrotina `probe`.
+<3> A versão de `probe` em `domainlib` devolve uma `NamedTuple` chamada `Result`.
+<4> Faça uma lista de domínios. O domínio de nível superior `.invalid` é reservado para testes. Consultas ao DNS por tais domínios sempre recebem uma resposta NXDOMAIN dos servidores DNS, que significa "este domínio não existe."footnote:[Veja https://fpy.li/21-45[_RFC 6761—Special-Use Domain Names_].]
+<5> Itera com `async for` sobre o gerador assíncrono `multi_probe` para exibir os resultados.
+<6> Note que os resultados não estão na ordem em que os domínios foram enviados a `multiprobe`. Eles aparecem quando cada resposta do DNS chega.
+
+O <> mostra que `multi_probe` é um gerador assíncrono, pois é compatível com `async for`. Vamos executar mais alguns experimentos, continuando com o <>.
+
+[[domainlib_more_exp_repl]]
+.Mais experimentos, continuação do <>
+====
+[source, python]
+----
+>>> probe('python.org')  # <1>
+
+>>> multi_probe(names)  # <2>
+
+>>> for r in multi_probe(names):  # <3>
+...    print(r)
+...
+Traceback (most recent call last):
+   ...
+TypeError: 'async_generator' object is not iterable
+----
+====
+<1> Invocar uma corrotina nativa devolve um objeto corrotina.
+<2> Invocar um gerador assíncrono devolve um objeto `async_generator`.
+<3> Não podemos usar um laço `for` comum para percorrer geradores assíncronos,
+porque eles implementam `+__aiter__+` em vez de `+__iter__+`.
+
+Geradores assíncronos são acionados pelas palavras-chave `async for`,
+que pode ser uma instrução de laço (como visto em <>), 
+mas também podem aparecer em compreensões assíncronas, que veremos mais tarde.
+
+
+===== Implementando um gerador assíncrono
+
+Aqui está o módulo _domainlib.py_, com o gerador assíncrono `multi_probe`:
+
+[[domainlib_ex]]
+.domainlib.py: funções para sondar domínios
+====
+[source, python]
+----
+include::../code/21-async/domains/asyncio/domainlib.py[]
+----
+====
+<1> `NamedTuple` torna o resultado de `probe` mais fácil de ler e depurar.
+<2> Este apelido de tipo serve para evitar que a linha seguinte fique grande demais em uma listagem impressa em um livro.
+<3> `probe` agora recebe um argumento opcional `loop`, para evitar chamadas repetidas a `get_running_loop` toda vez que esta corrotina é acionada no laço em `multi_probe`.
+<4> Uma função geradora assíncrona produz um objeto gerador assíncrono, que pode ser anotado como `AsyncIterator[TipoDoItem]`.
+<5> Constrói uma lista de objetos corrotina `probe`, cada um com um `domain` diferente.
+<6> Isto não é `async for` porque `asyncio.as_completed` é um gerador clássico.
+<7> Aciona o objeto corrotina para obter o resultado.
+<8> Produz um `result`. Esta linha faz com que `multi_probe` seja um gerador assíncrono.
+
+[NOTE]
+====
+O corpo do laço `for` no <> poderia ser mais conciso:
+
+[source, python]
+----
+    for coro in asyncio.as_completed(coros):
+        yield await coro
+----
+
+Python interpreta isso como `yield (await coro)`, então funciona.
+
+Achei que poderia ser confuso usar esse atalho no primeiro exemplo
+de gerador assíncrono no livro, então dividi em duas linhas.
+====
+
+Uma vez que temos o _domainlib.py_, podemos demonstrar o uso do
+gerador assíncrono `multi_probe` em _domaincheck.py_:
+um script que recebe um sufixo de domínio e busca por domínios
+criados a partir de palavras-chave curtas de Python.
+
+Aqui está uma amostra da saída de _domaincheck.py_:
+
+[source, text]
+----
+$ ./domaincheck.py net
+FOUND           NOT FOUND
+=====           =========
+in.net
+del.net
+true.net
+for.net
+is.net
+                none.net
+try.net
+                from.net
+and.net
+or.net
+else.net
+with.net
+if.net
+as.net
+                elif.net
+                pass.net
+                not.net
+                def.net
+----
+
+Graças à _domainlib_, o código de _domaincheck.py_ é bem direto:
+
+[[domaincheck_ex]]
+.domaincheck.py: utilitário para sondar domínios usando domainlib
+====
+[source, python]
+----
+include::../code/21-async/domains/asyncio/domaincheck.py[]
+----
+====
+<1> Gera palavras-chave de tamanho até `4`.
+<2> Gera nomes de domínio com o sufixo recebido como TLD (_Top Level Domain_).
+<3> Formata um cabeçalho para a saída tabular.
+<4> Itera de forma assíncrona sobre `multi_probe(domains)`.
+<5> Define `indent` como zero ou dois tabs, para colocar o resultado na coluna apropriada.
+<6> Roda a corrotina `main` com o argumento de linha de comando passado.
+
+Geradores têm uma outra utilidade, não relacionado à iteração:
+eles podem ser usados como gerenciadores de contexto. Isso também se aplica aos geradores assíncronos.
+
+[[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 precisar, considere usar o decorador
+https://fpy.li/b6[`@asynccontextmanager`], incluído no módulo `contextlib` no
+Python 3.7. É análogo 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,
+https://fpy.li/hattingh[_Using Asyncio in Python_]. O <> é o código de Caleb—com uma única mudança e o acréscimo das explicações.
+
+[[asynccontextmanager_ex]]
+.Exemplo usando `@asynccontextmanager` e `loop.run_in_executor`
+====
+[source, python]
+----
+from contextlib import asynccontextmanager
+
+@asynccontextmanager
+async def web_page(url):  # <1>
+    laço = asyncio.get_running_loop()   # <2>
+    data = await loop.run_in_executor(  # <3>
+        None, download_webpage, url)
+    yield data                          # <4>
+    await loop.run_in_executor(None, update_stats, url)  # <5>
+
+async with web_page('google.com') as data:  # <6>
+    process(data)
+----
+====
+<1> A função decorada precisa ser um gerador assíncrono.
+<2> 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 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`.
+
+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://fpy.li/b6[documentação do `contextlib`].
+
+Por fim, vamos terminar nossa jornada pelas funções geradoras assíncronas comparando-as com as corrotinas nativas.
+
+===== Geradores assíncronos versus corrotinas nativas
+
+Aqui((("native coroutines", "versus asynchronous generators", secondary-sortas="asynchronous generators"))) estão algumas semelhanças e diferenças fundamentais entre uma corrotina nativa e uma função geradora assíncrona:
+
+* Ambas são declaradas com `async def`.
+* Um gerador assíncrono sempre tem uma expressão `yield` em seu corpo—é isso que o torna um gerador. Uma corrotina nativa nunca contém um `yield`.
+* Uma corrotina nativa pode devolver (`return`) algum valor diferente de `None`, mas um gerador assíncrono só pode usar instruções `return` vazias.
+* Corrotinas nativas são esperáveis: elas podem ser acionadas por expressões `await` ou passadas para uma das muitas funções do `asyncio` que aceitam argumentos esperáveis, como `create_task` ou `gather`. Em contrapartida, geradores assíncronos não são esperáveis. Eles são iteráveis assíncronos, acionados por `async for` ou por compreensões assíncronas.
+
+Hora de falar sobre as tais compreensões assíncronas.((("", startref="Gasync21")))
+
+==== Compreensões assíncronas e expressões geradoras assíncronas
+
+A https://fpy.li/pep530[_PEP 530—Asynchronous Comprehensions_]
+introduziu((("generator expressions (genexps)",
+id="genexp21")))((("list comprehensions (listcomps)", "asynchronous", id="LCasync21")))
+o uso de `async for` e `await` na sintaxe de compreensões e expressões geradoras, a partir do Python 3.6.
+
+A única sintaxe definida na PEP 530 que pode aparecer fora do corpo
+de uma `async def` é uma expressão geradora assíncrona.
+
+===== Definindo e usando uma expressão geradora assíncrona
+
+Dado o gerador assíncrono `multi_probe` do <>,
+poderíamos escrever outro gerador assíncrono que devolvesse apenas os nomes de domínios encontrados.
+Aqui está uma forma de fazer isso—novamente usando o console assíncrono iniciado com `-m asyncio`:
+
+[source, python]
+----
+>>> from domainlib import multi_probe
+>>> names = 'python.org rust-lang.org golang.org xyz.invalid'.split()
+>>> gen_found = (name async for name, found
+...              in multi_probe(names) if found)  # <1>
+>>> gen_found
+ at 0x10a8f9700>  # <2>
+>>> async for name in gen_found:  # <3>
+...     print(name)
+...
+golang.org
+python.org
+rust-lang.org
+----
+<1> O uso de `async for` define uma expressão geradora assíncrona. Ela pode ser definida em qualquer lugar de um módulo Python.
+<2> A expressão geradora assíncrona cria um objeto `async_generator`—exatamente o mesmo tipo de objeto devolvido por uma função geradora assíncrona como `multi_probe`.
+<3> O objeto gerador assíncrono é acionado pela instrução `async for`,
+que por sua vez só pode aparecer dentro do corpo de uma `async def`
+ou no console assíncrono mágico que usei nesse exemplo.
+
+Resumindo: uma expressão geradora assíncrona pode ser definida
+em qualquer ponto do seu programa, mas só pode ser acionada
+dentro de uma corrotina nativa ou de uma função geradora assíncrona.
+
+Agora veremos as demais construções sintáticas propostas na PEP 530.
+
+===== Compreensões assíncronas
+
+Diferente das expressões geradoras assíncronas,
+as compreensões assíncronas só podem ser definidas e
+usadas dentro de corrotinas nativas ou de funções geradoras assíncronas.
+Isso faz sentido porque as compreensões são ávidas (_eager_): elas
+são executadas imediatamente para construir uma lista.
+Em contraste, as expressões geradoras (assíncronas ou não), são
+preguiçosas (_lazy_). Elas criam um objeto gerador
+que só será executado quando um laço percorrer o gerador.
+
+Yury Selivanov—autor da PEP 530—justificou a necessidade de compreensões
+assíncronas com três trechos curtos de código, reproduzidos a seguir.
+
+Podemos concordar que deveria ser possível reescrever esse código:
+
+[source, python]
+----
+result = []
+async for i in aiter():
+    if i % 2:
+        result.append(i)
+----
+
+assim:
+
+[source, python]
+----
+result = [i async for i in aiter() if i % 2]
+----
+
+Além disso, dada uma corrotina nativa `fun`, deveria ser possível escrever isso:
+
+[source, python]
+----
+result = [await fun() for fun in funcs]
+----
+
+[TIP]
+====
+Usar `await` em uma compreensão de lista é similar a usar `asyncio.gather`.
+Mas `gather` nos dá 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://fpy.li/b7[«documentação de `asyncio.gather`»]
+para mais informações.
+====
+
+Voltemos ao console assíncrono mágico:
+
+[source, python]
+----
+>>> names = 'python.org rust-lang.org golang.org xyz.invalid'.split()
+>>> names = sorted(names)
+>>> coros = [probe(name) for name in names]
+>>> await asyncio.gather(*coros)
+[Result(domain='golang.org', found=True),
+Result(domain='xyz.invalid', found=False),
+Result(domain='python.org', found=True),
+Result(domain='rust-lang.org', found=True)]
+>>> [await probe(name) for name in names]
+[Result(domain='golang.org', found=True),
+Result(domain='xyz.invalid', found=False),
+Result(domain='python.org', found=True),
+Result(domain='rust-lang.org', found=True)]
+>>>
+----
+
+Usei `sorted` para ordenar a lista de nomes e mostrar que os resultados chegam
+na ordem em que foram submetidos, nos dois casos.
+
+A PEP 530 também permite o uso de `async for` e `await` 
+compreensões de `dict` e de `set`. Por exemplo, aqui está uma
+compreensão de `dict` para armazenar os resultados de `multi_probe` no console
+assíncrono:
+
+[source, python]
+----
+>>> {name: found async for name, found in multi_probe(names)}
+{'golang.org': True, 'python.org': True, 'xyz.invalid': False,
+'rust-lang.org': True}
+----
+
+Podemos usar a palavra-chave `await` na expressão antes das cláusulas `for` ou
+`async for`, e também na expressão após a cláusula `if`. Aqui está uma
+compreensão de `set` no console assíncrono, coletando apenas os domínios
+encontrados.
+
+[source, python]
+----
+>>> {name for name in names if (await probe(name)).found}
+{'rust-lang.org', 'python.org', 'golang.org'}
+----
+
+Precisei colocar parênteses adicionais ao redor da expressão `await` devido à
+precedência mais alta do operador `.` (ponto) de `+__getattr__+`.
+
+Relembrando, todas essas compreensões só podem aparecer no corpo de uma `async def` ou no console assíncrono encantado.
+
+Agora vamos discutir uma característica muito importante das instruções e expressões `async` e dos objetos que eles criam:
+Estas construções são muito usadas com o `asyncio` mas, na verdade, 
+são independentes da biblioteca.((("", startref="LCasync21")))((("", startref="genexp21")))((("", startref="ITERasync21")))((("", startref="ITasync21")))((("", startref="APRiteration21")))
+
+
+=== Sondando domínios com _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 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 pessoa
+suficientemente motivada pode escrever seu ambiente de runtime e um framework
+assíncrono para acionar corrotinas nativas, geradores assíncronos, etc.
+
+Foi o que fez David Beazley em seu projeto https://fpy.li/21-49[_Curio_].
+Beazley estava interessado em repensar como estes recursos da linguagem poderiam
+ser usados em um framework desenvolvido do zero, sem carregar uma bagagem do
+passado. Lembre-se de que o `asyncio` foi lançado no Python 3.4, quando não existiam
+as instruções `async`, e em vez de `await` usávamos `yield from`.
+Portanto, a API original do `asyncio` não podia oferecer gerenciadores de
+contexto assíncronos, iteradores assíncronos e tudo o mais que as palavras-chave
+`async/await` tornaram possível. O resultado é que o _Curio_ tem uma API mais
+elegante e uma implementação mais simples quando comparado ao `asyncio`.
+
+WARNING
+====
+O _Curio_ é uma prova de conceito, e David Beazley não está mais evoluindo
+o projeto. Sua influência mais marcante está no framework
+https://fpy.li/21-58[_Trio_], que continua evoluindo e tem mais suporte
+de bibliotecas.
+
+Se você estiver usando Python 3.12 ou superior, precisará instalar
+um _fork_ atualizado publicado no PyPI como https://fpy.li/c6[_curio-compat_].
+====
+
+O <> mostra o script _blogdom.py_ (<>) reescrito
+para usar o _Curio_.
+
+[[blogdom_curio_ex]]
+.blogdom.py: <>, agora usando o _Curio_
+====
+[source, python]
+----
+include::../code/21-async/domains/curio/blogdom.py[]
+----
+====
+
+<1> `probe` não precisa obter o laço de eventos, porque...
+
+<2> ...`getaddrinfo` é uma função de `curio.socket`, não um
+método de um objeto `loop`—como no `asyncio`.
+
+<3> Um `TaskGroup` é um conceito central no _Curio_, para monitorar e controlar
+várias corrotinas, garantindo que elas todas sejam acionadas e encerradas, sem
+deixar alguma para trás.
+
+<4> `TaskGroup.spawn` é como você inicia uma corrotina, gerenciada por uma
+instância específica de `TaskGroup`. A corrotina é embrulhada em uma `Task`.
+
+<5> Iterar com `async for` sobre um `TaskGroup` produz instâncias de `Task` à
+medida que cada uma termina. Isto corresponde à linha em <> que usa +
+`for … in as_completed(…):`
+
+<6> O _Curio_ foi pioneiro no uso dessa maneira simples de iniciar um programa
+assíncrono em Python.
+
+Para ilustrar este último ponto: lendo os exemplos de código de `asyncio` na
+primeira edição do _Python Fluente_, verá linhas como estas repetidas várias
+vezes:
+
+[source, python]
+----
+    laço = asyncio.get_event_loop()
+    loop.run_until_complete(main())
+    loop.close()
+----
+
+==== Concorrência estruturada
+
+Um `TaskGroup` do _Curio_ é um gerenciador de contexto assíncrono que substitui
+várias APIs e padrões de codificação repetitivos do `asyncio`. Acabamos de ver como
+iterar sobre um `TaskGroup` torna a função `asyncio.as_completed(…)`
+desnecessária.
+
+Outro exemplo: em vez da função especial `gather`, este trecho da
+https://fpy.li/21-50[documentação de "Task Groups"]
+coleta os resultados de todas as tarefas no grupo:
+
+[source, python]
+----
+async with TaskGroup(wait=all) as g:
+    await g.spawn(coro1)
+    await g.spawn(coro2)
+    await g.spawn(coro3)
+print('Results:', g.results)
+----
+
+Objetos `TaskGroup` ((("structured concurrency")))((("concurrency models",
+"structured concurrency"))) suportam
+https://fpy.li/21-51[«concorrência estruturada»]:
+uma disciplina de programação concorrente que organiza todas a atividades de um
+grupo de tarefas assíncronas em um bloco de código com apenas um ponto de
+entrada e uma saída. Isto é análogo à programação estruturada, 
+que introduziu instruções de bloco para limitar os pontos de
+entrada e saída de condicionais, laços e sub-rotinas, e eliminou
+a instrução `GOTO` que permitia desviar a execução direto
+para qualquer outra linha do código.
+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 finalizadas ou canceladas e
+qualquer exceção foi levantada.
+
+[NOTE]
+====
+
+A concorrência estruturada está sendo adotada pelo `asyncio`.
+Uma evidência é a
+https://fpy.li/pep654[_PEP 654–Exception Groups and except*_], que foi
+aprovada para o Python 3.11. A seção
+https://fpy.li/21-53[_Motivation_] menciona as _nurseries_ (creches)
+do _Trio_, que correspondem aos `TaskGroup` do _Curio_:
+"Implementar uma API de acionamento de tarefas melhor no `asyncio`,
+inspirada pelas _nurseries_ do Trio, foi a principal motivação desta
+PEP."
+
+====
+
+Outra inovação 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`. Sim,
+o _Curio_ pode ser executado em uma thread ao lado do
+`asyncio` em outra thread, no mesmo processo, comunicando-se através de
+`UniversalQueue` e de `UniversalEvent`. A API destas classes "universais" é
+a mesma dentro e fora de corrotinas, mas em uma corrotina é preciso 
+acionar os métodos 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_],
+mas não sei de nenhuma biblioteca assíncrona de banco de dados que o suporte
+nesse momento. No repositório do _Curio_ há um conjunto impressionante de
+https://fpy.li/21-56[«exemplos de programação para rede»], incluindo um que
+utiliza _WebSocket_, e outro implementando o algoritmo concorrente
+https://fpy.li/21-57[_RFC 8305—Happy Eyeballs_], para conexão com pontos de acesso
+IPv6 revertendo rapidamente para IPv4 quando necessário.
+
+O design do _Curio_ foi muito influente.
+o framework https://fpy.li/21-58[_Trio_], iniciado por Nathaniel J. Smith,
+foi muito inspirado nele.
+O _Curio_ pode também ter estimulado os contribuidores de Python a melhorar a usabilidade da API do `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 métodos de `loop`,
+ou precisavam do laço de eventos como um argumento.
+Em versões mais recentes de Python, acesso direto ao laço não é mais tão necessário e,
+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")))
+
+=== Dicas de tipo para objetos assíncronos
+
+O((("asynchronous programming", "type hinting asynchronous objects")))((("type hints (type annotations)",
+"for asynchronous objects", secondary-sortas="asynchronous objects")))
+tipo devolvido por uma corrotina nativa é o tipo do objeto que você obtém quando
+usa `await` naquela corrotina, que é o tipo do objeto devolvido pela instrução
+`return` no corpo da corrotina nativa. Isto é mais simples que as anotações
+de corrotinas clássicas, discutidas na <>.
+
+Neste capítulo vimos vários exemplos de corrotinas nativas anotadas,
+incluindo a `probe` do <>:
+
+[source, python]
+----
+async def probe(domain: str) -> tuple[str, bool]:
+    try:
+        await socket.getaddrinfo(domain, None)
+    except socket.gaierror:
+        return (domain, False)
+    return (domain, True)
+----
+
+Se você precisar anotar um parâmetro que recebe um objeto corrotina como argumento,
+então o tipo genérico é:
+
+[source, python]
+----
+class typing.Coroutine(Awaitable[V_co], Generic[T_co, T_contra, V_co]):
+    ...
+----
+
+Aquele tipo e os tipos seguintes foram introduzidos no Python 3.5 e 3.6 para
+anotar objetos assíncronos:
+
+[source, python]
+----
+class typing.AsyncContextManager(Generic[T_co]):
+    ...
+class typing.AsyncIterable(Generic[T_co]):
+    ...
+class typing.AsyncIterator(AsyncIterable[T_co]):
+    ...
+class typing.AsyncGenerator(AsyncIterator[T_co],
+                            Generic[T_co, T_contra]):
+    ...
+class typing.Awaitable(Generic[T_co]):
+    ...
+----
+
+Com Python ≥ 3.9, use os equivalentes definidos em `collections.abc`.
+
+Quero destacar três aspectos destes tipos genéricos.
+
+Primeiro: eles são todos covariantes no primeiro parâmetro de tipo, que é o tipo
+dos itens produzidos a partir destes objetos. Lembre-se da regra #1 da
+<>:
+
+[quote]
+____
+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 no penúltimo parâmetro.
+Aquele é o tipo do argumento passado ao método de baixo nível
+`.send()`, que o laço de eventos invoca para acionar geradores assíncronos e
+corrotinas. Desta forma, é um tipo de "entrada", então
+vale a regra #2 da variância:
+
+[quote]
+____
+Se um parâmetro de tipo formal define um tipo para um dado que entra
+no objeto após sua construção inicial, ele pode ser contravariante.
+____
+
+Terceiro: `AsyncGenerator` não tem tipo de retorno, ao contrário de `typing.Generator`,
+usado para anotar corrotinas clássicas, apesar do nome que sugere outra coisa,
+como vimos na <>.
+Devolver um valor levantando `StopIteration(value)` foi a gambiarra que permitiu
+a geradores funcionarem como corrotinas clássicas suportando `yield from`, como
+vimos na <>. Não há tal sobreposição entre os objetos
+assíncronos: objetos `AsyncGenerator` produzem itens mas não devolvem
+um resultado final, e são completamente
+separados de objetos corrotina que devolvem um resultado, mas não usam `yield`.
+
+Por fim, vamos discutir rapidamente as vantagens e desafios da programação assíncrona.
+
+
+[[how_async_works_and_does_not_sec]]
+=== Como a programação assíncrona funciona e como não funciona
+
+As seções finais deste capítulo discutem ideias de alto nível sobre
+programação assíncrona, independente da linguagem ou da biblioteca usadas.
+
+Vamos começar explicando por que a programação assíncrona é útil,
+seguido por um mito popular e como lidar com ele.
+
+
+[[around_blocking_calls_sec]]
+==== Correndo em círculos em torno de chamadas bloqueantes
+
+Ryan Dahl, o((("asynchronous programming", "benefits of"))) inventor do Node.js,
+introduz a filosofia do projeto dizendo "Estamos fazendo E/S de
+forma totalmente errada."footnote:[Vídeo: https://fpy.li/21-59[_Introduction to
+Node.js_], em 4:55.] Ele define uma "função
+bloqueante" como uma função que faz E/S de arquivo ou rede, e argumenta que elas
+não podem ser tratadas da mesma forma que tratamos funções não-bloqueantes.
+Para explicar a razão disso, ele apresenta os números na segunda coluna da
+<>.
+
+[[latency_tbl]]
+.Latência em computadores modernos para ler dados em diferentes dispositivos. A terceira coluna mostra os tempos proporcionais em uma escala fácil de entender para nós, humanos vagarosos.
+[options="header", cols="1,>1,^2"]
+|=======================================================
+|Dispositivo  |Ciclos de CPU |Escala proporcional "humana"
+|cache L1     |3             |3 segundos
+|cache L2     |14            |14 segundos
+|RAM          |250           |250 segundos
+|HD local     |41.000.000    |1,3 anos
+|rede         |240.000.000   |7,6 anos
+|=======================================================
+
+
+Para entender a <>,
+tenha em mente que as CPUs modernas, com seus _clocks_ em frequências na casa dos GHz, rodam bilhões de ciclos por segundo.
+Suponha que uma CPU rode exatamente 1 bilhão de ciclos por segundo.
+Tal CPU pode realizar mais de 333 milhões de leituras do cache L1 em 1 segundo,
+ou 4 (quatro!) leituras da rede no mesmo segundo.
+A terceira coluna da <> coloca os números em perspectiva, multiplicando a segunda coluna por um fator constante.
+Então, em um universo alternativo, se uma leitura da RAM demorasse 250 segundos, uma leitura da rede demoraria 7,6 anos!
+A diferença quantitativa é tão grande que se torna uma diferença qualitativa importante:
+esperar 250 segundos é muito diferente de esperar 7,6 anos!
+
+A <> explica por que uma abordagem disciplinada da programação
+assíncrona pode levar a servidores de alto desempenho. O desafio é alcançar
+esta disciplina. O primeiro passo é reconhecer que um sistema limitado apenas
+por E/S é uma fantasia.
+
+[[myth_iobound_sec]]
+==== O mito dos sistemas limitados por E/S
+
+Um((("asynchronous programming", "myth of I/O-bound systems")))((("network I/O",
+"myth of I/O-bound systems"))) meme exaustivamente repetido é que programação
+assíncrona é boa para _I/O bound systems_ (sistemas limitados por E/S), ou seja,
+sistemas onde o gargalo é a entrada e saída de dados, e não o processamento de
+dados na CPU. 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 CPU e na memória. 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. Até mesmo sistemas triviais revelam isso, sob stress. Na
+caixa _<>_ ao final deste capítulo escrevi sobre dois programas assíncronos sofrendo com
+funções limitadas pela CPU que atrasavam o 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,
+lidar com elas é a chave do sucesso na programação assíncrona.
+
+[[avoid_cpu_trap_sec]]
+==== Evitando as armadilhas do uso da CPU
+
+Se((("asynchronous programming", "avoiding CPU-bound traps")))((("CPU-bound
+systems"))) você está usando Python em larga escala, precisa ter testes
+automatizados especificamente para detectar regressões de desempenho
+assim que elas acontecem. Isso é de importância crítica com código assíncrono,
+mas é relevante também para código Python baseado em threads—por causa da GIL.
+Se você esperar até a lentidão começar a incomodar a equipe de desenvolvimento,
+será tarde demais. A solução poderá exigir mudanças drásticas.
+
+Aqui estão algumas opções para quando você identifica gargalos de uso da CPU:
+
+* Delegar a tarefa para um banco de processos Python.
+* Delegar a tarefa para uma fila de tarefas externa.
+* Reescrever o código relevante em Cython, C, Rust ou alguma outra linguagem que compile para código de máquina e faça interface com a API Python/C, de preferência liberando a GIL.
+* Decidir que pode tolerar a perda de desempenho e deixar como está—mas registre essa decisão, para ficar mais fácil revertê-la no futuro.
+
+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 de deixar como está entra na conta de https://fpy.li/b8[«dívida tecnológica»].
+
+Programação concorrente é um tópico fascinante, e eu gostaria de escrever mais.
+Mas não é o foco principal deste livro,
+e este já é um dos capítulos mais longos, então vamos encerrar por aqui.
+
+
+=== Resumo do capítulo
+
+[quote, Alvaro Videla e Jason J. W. Williams, RabbitMQ in Action]
+____
+O problema com as abordagens usuais da programação assíncrona é
+que elas são propostas do tipo "tudo ou nada".
+Ou você reescreve todo o código, de forma que nada nele bloqueie, 
+ou você está só perdendo tempo.
+____
+
+
+Escolhi((("asynchronous programming", "overview of"))) esta epígrafe para este
+capítulo por duas razões. Em um nível mais alto, ela nos lembra de evitar o
+bloqueio do laço de eventos, delegando tarefas lentas para outra unidade de
+processamento, desde uma thread ou processo local, 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 de repente pode complicar o seu trabalho.
+
+Após os exemplos simples com o _spinner_ no <>, aqui
+nosso maior foco foi a programação assíncrona com corrotinas nativas, começando
+com o exemplo de sondagem de DNS _blogdom.py_, seguido pelo conceito de
+esperável. No código-fonte de _flags_asyncio.py_ encontramos o primeiro
+exemplo de um gerenciador de contexto assíncrono: `httpx.AsyncClient`.
+
+As variantes mais avançadas do programa de download de bandeiras apresentaram
+duas funções poderosas: o gerador `asyncio.as_completed` e a corrotina
+`loop.run_in_executor` para delegar tarefas para threads ou processos.
+Também vimos o conceito e a aplicação de um semáforo,
+para limitar o número de downloads concorrentes—como se espera de um
+cliente HTTP bem comportado.
+
+A programação assíncrona para servidores foi apresentada com os exemplos
+_mojifinder_: um serviço Web usando a _FastAPI_ e o _tcp_mojifinder.py_—este
+último utilizando apenas o protocolo TCP e o `asyncio`.
+
+A seguir, iteração assíncrona e iteráveis assíncronos foram o principal tópico,
+com seções sobre `async for`, o console assíncrono de Python, geradores
+assíncronos, expressões geradoras assíncronas, e compreensões assíncronas.
+
+O último exemplo do capítulo foi o _blogdom.py_ reescrito com o framework
+_Curio_, demonstrando como os recursos de programação assíncrona de Python não
+estão presos ao pacote `asyncio`. O _Curio_ também demonstra o conceito de
+_concorrência estruturada_, que poderá ter um grande impacto muitas
+linguagens de programação, trazendo mais clareza para o código concorrente.
+
+Por fim, a <> apresentou
+o principal atrativo da programação assíncrona: não perder tempo
+esperando por E/S.
+Também vimos que não existem sistemas limitados só por E/S,
+e como lidar com as inevitáveis partes do seu programa assíncrono
+que utilizam intensivamente a CPU.
+
+
+=== Para saber mais
+
+A((("asynchronous programming", "further reading on"))) palestra 
+de David Beazley na abertura da PyOhio 2016,
+https://fpy.li/21-61[_Fear and Awaiting in Async_]
+(Medo e espera em [programação] assíncrona) é uma introdução incrível
+com "código ao vivo" demonstrando os
+recursos da linguagem viabilizados pela contribuição de Yury Selivanov ao
+Python 3.5: as palavras-chave `async/await`. Em certo momento, Beazley reclama
+que `await` não pode ser usada em compreensões de lista, mas isso foi resolvido
+por Selivanov na
+https://fpy.li/pep530[_PEP 530—Asynchronous Comprehensions_],
+implementada mais tarde naquele mesmo ano, no Python 3.6.
+
+Fora isso, todo o resto da palestra de Beazley é atemporal, pois ele revela
+como os objetos assíncronos vistos neste capítulo funcionam, sem ajuda de
+qualquer framework—com uma simples função `run` que invoca `.send(None)` para
+acionar corrotinas. Apenas no final Beazley mostra o
+https://fpy.li/21-62[_Curio_], que ele havia começado a desenvolver naquele ano,
+como uma prova de conceito, para ver o quão longe seria possível levar a programação
+assíncrona usando apenas corrotinas, sem callbacks ou _futures_. 
+Como vimos, dá para ir muito longe—como demonstra o _Curio_ e o desenvolvimento
+posterior do https://fpy.li/21-58[_Trio_] por Nathaniel J. Smith. A
+documentação do _Curio_ contém https://fpy.li/21-64[«links»] para outras
+palestras de Beazley sobre o assunto.
+
+Além de criar o _Trio_,
+Nathaniel J. Smith escreveu dois artigos muito profundos, que eu recomendo:
+https://fpy.li/21-65[_Some thoughts on asynchronous API design in a post-async/await world_]
+(Algumas reflexões sobre o design de APIs assíncronas em um mundo pós-async/await),
+comparando os designs do _Curio_ e do `asyncio`, e
+https://fpy.li/21-66[_Notes on structured concurrency, or: `go` statement considered harmful_]
+(Notas sobre concorrência estruturada, ou: a instrução `go` considerada nociva),
+sobre concorrência estruturada. Smith também deu uma longa e informativa resposta à questão:
+https://fpy.li/21-67[_What is the core difference between `asyncio` and Trio?]
+(Qual é a principal diferença entre `asyncio` e Trio?) no StackOverflow.
+
+Para aprender mais sobre o pacote `asyncio`, já mencionei os melhores recursos
+que conheço no início do capítulo: a 
+https://fpy.li/b9[«documentação oficial»], após a
+https://fpy.li/21-69[«profunda reorganização»] iniciada
+por Yury Selivanov em 2018, e o livro de Caleb Hattingh,
+https://fpy.li/hattingh[_Using Asyncio in Python_] (O'Reilly).
+
+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 de 30 minutos, muito acessível, à programação assíncrona em geral e também ao `asyncio`,
+assista a palestra
+https://fpy.li/21-71[_Asynchronous Python for the Complete Beginner_] (Python Assíncrono para o Iniciante Total),
+de Miguel Grinberg, apresentada na PyCon 2017. Outra ótima introdução é
+https://fpy.li/21-72[_Demystifying Python's Async and Await Keywords_] (Desmistificando as Palavras-Chave Async e Await de Python),
+apresentada por Michael Kennedy, onde aprendi sobre a biblioteca
+https://fpy.li/21-73[_unsync_],
+que fornece um decorador para delegar a execução de corrotinas,
+funções dedicadas a E/S e funções de uso intensivo de CPU para `asyncio`,
+`threading`, ou `multiprocessing`, conforme a necessidade.
+
+Na EuroPython 2019, Lynn Root—uma das líderes mundiais das https://fpy.li/21-74[_PyLadies_]—apresentou a excelente
+https://fpy.li/21-75[_Advanced asyncio: Solving Real-world Production Problems_]
+(Asyncio Avançado: Resolvendo Problemas de Produção do Mundo Real),
+a partir de sua experiência usando Python como engenheira no Spotify.
+
+Em 2020, Łukasz Langa gravou uma ótima série de vídeos sobre o `asyncio`, começando com
+https://fpy.li/21-76[_Learn Python's AsyncIO #1—The Async Ecosystem_]
+(Aprenda o AsyncIO de Python—O Ecossistema Async).
+Langa também fez um vídeo muito bacana,
+https://fpy.li/21-77[_AsyncIO + Music_],
+para a PyCon 2020, que mostra o `asyncio` aplicado a um domínio orientado a eventos muito concreto,
+e também explica esta aplicação do início ao fim.
+
+Outra área dominada por programação orientada a eventos são os sistemas embarcados.
+Por isso Damien George adicionou o suporte a `async/await` em seu interpretador
+https://fpy.li/21-78[_MicroPython_] para microcontroladores.
+Na PyCon Australia 2018, Matt Trentini demonstrou a biblioteca
+https://fpy.li/21-79[_uasyncio_],
+um subconjunto de `asyncio` que é parte da biblioteca padrão do MicroPython.
+
+Para uma visão de mais alto nível sobre a programação assíncrona em Python, leia o post
+https://fpy.li/21-80[_Python async frameworks—Beyond developer tribalism_]
+(Frameworks assíncronos de Python—para além do tribalismo dos desenvolvedores), de Tom Christie.
+
+Por fim, recomendo
+https://fpy.li/21-81[_What Color Is Your Function?_]
+(Qual a Cor da Sua Função?) de Bob Nystrom,
+discutindo os modelos de execução incompatíveis entre funções comuns e
+funções assíncronas—que chamamos de corrotinas—em JavaScript, Python, C# e outras linguagens.
+Alerta de spoiler: a conclusão de Nystrom é que a linguagem que acertou nessa área foi Go,
+onde todas as funções têm a mesma cor. Gosto disso no Go.
+Mas também acho que Nathaniel J. Smith tem razão quando escreveu
+https://fpy.li/21-66[_Go statement considered harmful_]
+(Instrução `go` considerada nociva).
+Nada é perfeito, e programação concorrente é sempre difícil.
+
+
+[[async_soapbox]]
+.Ponto de vista
+****
+
+
+*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 laço de
+eventos embutido do `asyncio`." Os _benchmarks_ (números de desempenho)
+apresentados no https://fpy.li/21-84[«post»] de Selivanov anunciando a
+biblioteca, em 2016, eram muito impressionantes: "ela é pelo menos
+2x mais rápida que o nodejs e gevent, bem como qualquer outro framework
+assíncrono de Python. O desempenho do `asyncio` com o _uvloop_ é próximo ao
+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
+ por múltiplas corrotinas acionadas por um laço de eventos, tudo 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 de que a
+ concorrência não exige múltiplas threads ou múltiplos núcleos de CPU.]
+ 
+ . Que o código Python 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 do _uvloop_
+com a https://fpy.li/21-86[_aiohttp_]—uma das primeiras bibliotecas HTTP
+completas construídas sobre o `asyncio`:
+
+[quote]
+____
+
+Entretanto, o gargalo de desempenho no _aiohttp_ estava em seu parser de HTTP,
+que era tão lento que pouco importava a velocidade da biblioteca de E/S
+subjacente. Para tornar as coisas mais interessantes, criamos uma biblioteca
+para Python usar a _http-parser_ (a biblioteca em C do parser do Node.js,
+originalmente desenvolvida para o _NGINX_). A biblioteca é chamada _httptools_,
+e está disponível no Github e no PyPI.
+
+____
+
+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 caso, a análise de cabeçalhos HTTP é intensiva em CPU, e tinha uma
+implementação lenta, em Python, na biblioteca _aiohttp_ quando Selivanov
+realizou os testes em 2016. 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 que usa intensivamente a CPU, os ganhos de desempenho
+de um laço de eventos mais rápido eram perdidos.
+
+*Morte lenta*
+
+Em((("Soapbox sidebars", "Twisted library")))((("Twisted library"))) vez de um
+simples servidor eco, imagine um sistema Python complexo e em evolução, com
+milhares de linhas de código assíncrono, e conectado a muitas bibliotecas
+externas.
+
+Anos atrás me pediram para ajudar a diagnosticar problemas de desempenho em um
+sistema assim. Ele era escrito em Python 2.7, com o framework
+https://fpy.li/21-88[_Twisted_]—uma biblioteca sólida, de alto desempenho,
+precursora do próprio `asyncio`.
+
+Python era usado para construir uma fachada para a interface Web,
+integrando funcionalidades fornecidas por bibliotecas pré-existentes e
+ferramentas de linha de comando escritas em outras
+linguagens—mas não projetadas para execução concorrente.
+
+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 daquele 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 passar do tempo,
+os desenvolvedores
+notaram que o desempenho do sistema estava piorando, e o time não
+conseguia localizar os principais gargalos.
+
+O que estava acontecendo: cada nova funcionalidade introduzia mais código
+intensivo em CPU, atrasando o laço de eventos do _Twisted_. O papel de Python
+como uma linguagem de integração entre processos externos implicava em
+muita interpretação de dados, serialização, desserialização, e conversões
+entre formatos. Não havia um gargalo único: o problema estava espalhado por
+incontáveis pequenas funções criadas ao longo de meses de desenvolvimento.
+
+A solução seria repensar a arquitetura do sistema, reescrever muito código, usar
+uma fila de tarefas, e talvez criar microsserviços ou bibliotecas customizadas,
+escritas em linguagens mais eficientes no processamento concorrente intensivo em
+CPU (eu sugeri Go para esta finalidade). Os gestores não quiseram
+fazer aquele investimento adicional, e o projeto foi cancelado semanas depois
+deste diagnóstico.
+
+Quando contei essa história para Glyph Lefkowitz—fundador do projeto
+_Twisted_—ele falou que é prioritário decidir quais ferramentas serão usadas para
+executar tarefas intensivas em CPU sem atrapalhar o laço de eventos, logo no
+início de qualquer projeto envolvendo programação assíncrona. Esta conversa com
+Glyph foi a inspiração para a <>.
+
+****
diff --git a/online/cap22.adoc b/online/cap22.adoc
new file mode 100644
index 00000000..7926d224
--- /dev/null
+++ b/online/cap22.adoc
@@ -0,0 +1,1669 @@
+[[ch_dynamic_attrs]]
+== Atributos dinâmicos e propriedades
+:example-number: 0
+:figure-number: 0
+
+[quote, Martelli, Ravenscroft & Holden, Why properties are important (Porque propriedades são importantes)]
+____
+
+As propriedades são muito importantes porque tornam perfeitamente seguro, e até
+aconselhável, expor publicamente atributos de dados como parte da interface pública
+de sua classe.footnote:[Alex Martelli, Anna Ravenscroft & Steve Holden,
+https://fpy.li/pynut3[Python in a Nutshell, Third Edition] (O'Reilly), p. 123.]
+
+____
+
+No Python((("dynamic attributes and properties", "dynamic versus virtual attributes"))),
+atributos de dados (ou campos) e métodos são conhecidos conjuntamente como _atributos_ .
+Um método é um atributo invocável.
+Atributos dinâmicos oferecem a mesma interface que os atributos de dados—isto é, `obj.atrib`—mas são computados sob demanda.
+Isso atende ao _Princípio de Acesso Uniforme_ de Bertrand Meyer:
+
+[quote, Bertrand Meyer, Object-Oriented Software Construction (Construção de Software Orientada a Objetos)]
+____
+
+Todos os serviços oferecidos por um módulo devem estar disponíveis através +
+de uma notação uniforme, que não revele se eles são implementados por
+armazenamento ou
+por computação.footnote:[Bertrand Meyer, Object-Oriented Software Construction,
+2nd ed. (Pearson), p. 57.]
+
+____
+
+Há várias formas de implementar atributos dinâmicos em Python.
+Este capítulo trata das mais simples: o decorador `@property` e o método especial `+__getattr__+`.
+
+Uma((("virtual attributes")))((("attributes", "virtual attributes"))) classe
+definida pelo usuário pode implementar `+__getattr__+` para oferecer uma
+variação de atributos dinâmicos que chamo de _atributos virtuais_: atributos
+que não são declarados explicitamente em lugar algum no código-fonte da classe,
+e que não estão presentes no `+__dict__+` das instâncias, mas que podem ser
+obtidos de algum outro lugar ou calculados sob demanda sempre que um usuário
+tenta ler um atributo inexistente tal como `obj.ausente`.
+
+Programar atributos dinâmicos e virtuais é o tipo de metaprogramação que autores
+de frameworks fazem. Entretanto, como as técnicas básicas no Python são simples,
+podemos usá-las em tarefas cotidianas de processamento de dados. É por aí que
+iniciaremos esse capítulo.
+
+
+=== Novidades neste capítulo
+
+As atualizações ((("dynamic attributes and properties", "significant changes to")))
+deste capítulo foram motivadas pela discussão relativa a
+`@functools.cached_property` (introduzido no Python 3.8), o uso
+combinado de `@property` e `@functools.cache` (novo no 3.9). Isto afetou o
+código das classes `Record` e `Event`, que aparecem na <>.
+Também fiz uma refatoração para aproveitar a otimização da
+https://fpy.li/pep412[_PEP 412—Key-Sharing Dictionary_]
+(Dicionário com chaves compartilhadas).
+
+Para enfatizar as características mais relevantes, e ao mesmo tempo manter os
+exemplos legíveis, removi algum código não-essencial—fundindo a antiga classe
+`DbRecord` com `Record`, substituindo `shelve.Shelve` por um `dict` e suprimindo
+a lógica para baixar o conjunto de dados da OSCON—que os exemplos agora carregam de
+um arquivo local, disponível no https://fpy.li/code[«repositório de código»] do
+_Python Fluente_.
+
+=== Explorando dados com atributos dinâmicos
+
+Nos((("dynamic attributes and properties", "data wrangling with dynamic attributes", id="DAPwrangl22")))((("data wrangling", "with dynamic attributes", secondary-sortas="dynamic attributes", id="DWdyatt22"))) próximos exemplos, vamos nos valer dos atributos dinâmicos para trabalhar com um conjunto de dados JSON publicado pela O'Reilly, para a conferência OSCON 2014. O <> mostra quatro registros daquele conjunto de dados.footnote:[A OSCON—O'Reilly Open Source Conference (_Conferência O'Reilly de Código Aberto_)—foi uma vítima da pandemia de COVID-19. O arquivo JSON original de 744 KB, que usei para esses exemplos, não está mais disponível online hoje (10 de janeiro de 2021). Você pode obter uma cópia do https://fpy.li/22-1[_osconfeed.json_] no repositório de exemplos do livro.]
+
+[[ex_osconfeed_json]]
+.Amostra de registros do osconfeed.json; abreviei o conteúdo de alguns campos
+====
+[source, json]
+----
+include::../code/22-dyn-attr-prop/oscon/osconfeed-sample.json[]
+----
+====
+
+O <> mostra 4 dos 895 registros do arquivo JSON. O conjunto
+completo total é um único objeto JSON, com a chave `"Schedule"` (cronograma), e seu
+valor é outro mapeamento com quatro chaves: `"conferences"` (conferências),
+`"events"` (eventos), `"speakers"` (palestrantes), e `"venues"` (locais).
+Cada uma destas quatro chaves aponta para uma lista de registros. No
+conjunto de dados completo, as listas de `"events"`, `"speakers"` e
+`"venues"` contêm dezenas ou centenas de registros, mas `"conferences"`
+contém apenas aquele único registro exibido na segunda linha do <>. Cada
+registro inclui um campo `"serial"`, que é um identificador único do registro
+dentro da lista onde ele está.
+
+Usei o console de Python para explorar os dados, como mostra o <>.
+
+[[ex_osconfeed_explore]]
+.Exploração interativa do osconfeed.json
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/osconfeed_explore.rst[]
+----
+====
+<1> `feed` é um `dict` contendo dicts e listas aninhados, com valores string e inteiros.
+<2> Lista as quatro coleções de registros dentro de `'Schedule'`. 
+<3> Exibe a contagem de registros para cada coleção.
+<4> Navega pelos dicts e listas aninhados para obter o nome da última palestrante (`speaker`).
+<5> Obtém o número de série daquela palestrante.
+<6> Cada evento tem uma lista `'speakers'`, com o número de série de zero ou mais palestrantes.((("", startref="DWdyatt22")))
+
+
+==== Explorando dados JSON e similares com atributos dinâmicos
+
+O <> é((("data wrangling", "JSON-like data",
+id="DWjsaon22")))((("JSON-like data", id="jsonlike22"))) simples,
+mas esta sintaxe é inconveniente:
+
+[source, python]
+----
+feed['Schedule']['events'][40]['name']
+----
+
+Em JavaScript, é possível obter o mesmo valor escrevendo
+`feed.Schedule.events[40].name`.
+Não é difícil implementar uma classe parecida com
+um `dict` para fazer o mesmo em Python—há inúmeras implementações na
+Web.footnote:[Dois exemplos são https://fpy.li/22-2[`AttrDict`] e
+https://fpy.li/22-3[`addict`].]
+Escrevi `FrozenJSON`, que é mais simples que a maioria das soluções porque
+só permite leitura: ela serve apenas para explorar os dados. `FrozenJSON` é
+recursiva, lidando automaticamente com mapeamentos e listas aninhados.
+
+O <> é uma demonstração da `FrozenJSON`,
+e o código-fonte aparece no <>.
+
+[[ex_explore0_demo]]
+.`FrozenJSON`, do <>, permite ler atributos como `talk.name`, e invocar métodos como `++feed.keys()++` e `++feed.items()++`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/explore0.py[tags=EXPLORE0_DEMO]
+----
+====
+<1> Cria uma instância de `FrozenJSON` a partir de `raw_feed`, feito de dicts e listas aninhados.
+<2> `FrozenJSON` permite percorrer dicts aninhados usando a notação de atributos; aqui exibimos o tamanho da lista de palestrantes.
+<3> Métodos dos dicts subjacentes também podem ser acessados; por exemplo, `.keys()`, para recuperar os nomes das coleções de registros.
+<4> Usando `items()`, podemos buscar os nomes das listas de registros e seus conteúdos, para exibir o `len()` de cada uma.
+<5> Uma `list`, tal como `feed.Schedule.speakers`, permanece uma lista, mas os itens dentro dela, se forem mapeamentos, são convertidos em um `FrozenJSON`.
+<6> O item 40 na lista `events` era um objeto JSON; agora ele é uma instância de `FrozenJSON`.
+<7> Registros de eventos têm uma lista de `speakers` com os números de série dos palestrantes.
+<8> Tentar ler um atributo inexistente gera uma exceção `KeyError`, em vez da `AttributeError` usual.
+
+A pedra angular da classe `FrozenJSON` é o método `+__getattr__+`, que já usamos
+no exemplo `Vector` da <>, para recuperar componentes
+de `Vector` por letra: `v.x`, `v.y`, `v.z`, etc. É importante lembrar que o
+método especial `+__getattr__+` só é invocado pelo interpretador quando o
+processo habitual não consegue recuperar um atributo (isto é, quando o atributo
+acessado não é encontrado na instância, nem na sua classe ou suas superclasses).
+
+O passo `⑧` do <> expõe um pequeno problema em meu código:
+tentar ler um atributo ausente deveria produzir uma exceção `AttributeError`, e
+não a `KeyError` gerada. Quando implementei o tratamento de erro para fazer
+isso, o método `+__getattr__+` se tornou duas vezes mais longo, ofuscando
+a essência da lógica que eu queria apresentar. Dado que os usuários
+devem saber que uma `FrozenJSON` é criada a partir de mapeamentos e listas,
+levantar `KeyError` não é tão confuso assim.
+
+
+[[ex_explore0]]
+.explore0.py: transforma um conjunto de dados JSON em um `FrozenJSON` contendo objetos `FrozenJSON` aninhados, listas e tipos simples
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/explore0.py[tags=EXPLORE0]
+----
+====
+
+<1> Cria um `dict` a partir do argumento `mapping`. Isso garante que teremos um
+mapeamento ou algo que poderá ser convertido para isso. O sublinhado duplo
+no prefixo de `+__data+` o torna um atributo privado.
+
+<2> `+__getattr__+` é invocado só quando não existe um atributo com aquele
+`name`.
+
+<3> Se `name` corresponde a um atributo da instância de `dict` `++__data++`,
+devolve aquele atributo. É assim que chamadas como `feed.keys()` são tratadas: o
+método `keys` é um atributo do `dict` `__data`.
+
+<4> Caso contrário, obtém o item `+self.__data+` com a chave `name`, e
+devolve o resultado da chamada `FrozenJSON.build()` com aquele
+argumento.
+
+<5> Implementar `+__dir__+` suporta a função embutida `dir()`, que por sua vez
+suporta _auto-complete_ no console padrão de
+Python, bem como no IPython, no Jupyter Notebook, etc. Este código simples vai
+permitir _auto-complete_ recursivo baseado nas chaves em
+`+self.__data+`, porque `+__getattr__+` cria instâncias de `FrozenJSON` sob
+demanda, facilitando a exploração interativa dos dados.
+
+<6> Este é um construtor alternativo, um uso comum do decorador `@classmethod`.
+
+<7> Se `obj` é um mapeamento, cria um `FrozenJSON` com ele. Este é um exemplo de
+tipagem ganso—veja a <> caso precise rever este conceito.
+
+<8> Se for uma `MutableSequence`, criamos uma `list`, passando recursivamente
+cada item em `obj` para `.build()`.
+
+<9> Se não for um `dict` ou uma `list`, devolve o item como está.
+
+Cada 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__+`.
+Primeiro, esse método verá se o `dict` vinculado a `+self.__data+` tem um
+atributo (não uma chave!) com aquele nome.
+Assim podemos invocar métodos do `dict`, como `.items()`, pois neste caso
+o `FrozenJSON` vai invocar `+self.__data.items()+`.
+Se `+self.__data+` não tiver um atributo com o `name` dado, `+__getattr__+`
+usa `name` como chave para recuperar um item de
+`+self.__data+`, e passa aquele item para `FrozenJSON.build`. Assim podemos
+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`.
+Isto funciona bem com um conjunto de dados não muito grande,
+em um script que só será usado para explorar ou converter os dados.
+
+Qualquer script que gera dinamicamente nomes de atributos a partir de
+dados arbitrários precisa lidar com uma questão: as chaves nos dados
+podem não ser nomes adequados de atributos. A próxima seção fala disso.((("",
+startref="jsonlike22")))((("", startref="DWjsaon22")))
+
+
+[[dynamic_names_sec]]
+==== O problema do nome de atributo inválido
+
+O((("data wrangling", "invalid attribute name problem")))((("invalid attribute name problem")))
+código de `FrozenJSON` não funciona com nomes de atributos que
+sejam palavras reservadas de Python. Por exemplo, se você criar um objeto assim:
+
+[source, python]
+----
+>>> student = FrozenJSON({'name': 'Jim Bo', 'class': 1982})
+----
+
+não será possível acessar `student.class`, porque `class` é uma palavra reservada no Python:
+
+[source, python]
+----
+>>> student.class
+  File "", line 1
+    student.class
+         ^
+SyntaxError: invalid syntax
+----
+
+Claro, sempre é possível fazer assim:
+
+[source, python]
+----
+>>> getattr(student, 'class')
+1982
+----
+
+Mas a ideia de `FrozenJSON` é oferecer acesso conveniente aos dados, então uma
+solução melhor é verificar se uma chave no mapeamento passado para
+`+FrozenJSON.__init__+` é uma palavra reservada e, em caso positivo, anexar um
+`_` a ela, de forma que o atributo possa ser acessado assim:
+
+[source, python]
+----
+>>> student.class_
+1982
+----
+
+Podemos fazer isto substituindo o `+__init__+` de uma linha do <> pela versão no <>.
+
+[[ex_explore1]]
+.explore1.py: anexa um `_` a nomes de atributo que são palavras reservadas do Python
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/explore1.py[tags=EXPLORE1]
+----
+====
+<1> A função `keyword.iskeyword(…)` é o que precisamos; para usá-la, precisamos importar o módulo `keyword`;
+a importação está antes deste trecho do código.
+
+Um problema similar pode surgir se uma chave em um registro JSON não for um identificador válido em Python:
+
+
+[source, python]
+----
+>>> x = FrozenJSON({'2be':'or not'})
+>>> x.2be
+  File "", line 1
+    x.2be
+      ^
+SyntaxError: invalid syntax
+----
+
+Essas chaves problemáticas são fáceis de detectar no Python 3, porque a classe
+`str` oferece o método `s.isidentifier()`, que informa se `s` é um identificador
+Python válido, de acordo com a gramática da linguagem. Mas transformar uma chave
+que não seja um identificador válido em um nome de atributo válido não é
+trivial. Uma solução seria implementar `+__getitem__+` para permitir acesso a
+atributos usando uma notação como `x['2be']`. Em nome da simplicidade, não vou
+me preocupar com esse problema.
+
+Após essa pequena conversa sobre os nomes de atributos dinâmicos, vamos examinar
+outra característica essencial de `FrozenJSON`: a lógica do método de classe
+`build`. Este método é invocado por `+__getattr__+` para devolver um tipo
+diferente de objeto, dependendo do valor do atributo que está sendo acessado:
+estruturas aninhadas são convertidas para instâncias de `FrozenJSON` ou listas
+de instâncias de `FrozenJSON`.
+
+Em vez de usar um método de classe, podemos implementar esta lógica no método especial
+`+__new__+`, como veremos a seguir.
+
+
+[[flexible_new_sec]]
+==== Criação flexível de objetos com `+__new__+`
+
+Muitas((("data wrangling", "flexible object creation",
+id="DWflex22")))((("*_new*_",
+id="new22")))((("objects", "flexible object creation", id="Oflex22"))) vezes
+dizemos que o `+__init__+` é o "método construtor", mas isso é porque adotamos o
+jargão de outras linguagens.
+No Python, `+__init__+` recebe `self` como primeiro
+argumento, portanto o objeto já foi construído pelo interpretador quando ele invoca
+`+__init__+`. Além disso, `+__init__+` não devolve um valor. Então, na
+verdade, esse método é um inicializador, não propriamente um construtor.footnote:[Em Java acontece
+a mesma coisa: a variável mágica `this` dentro de um "construtor" em Java já aponta
+para um objeto previamente construído e alocado na memória, e o que resta para o seu código é apenas
+inicializá-lo.]
+
+Quando uma classe é invocada para criar uma instância, Python invoca o método especial 
+`+__new__+` da classe para construir a instância.
+
+É um método de classe, mas recebe tratamento especial,
+então o decorador `@classmethod` não é aplicado a ele.
+Python recebe a instância devolvida por `+__new__+`,
+e daí a passa como o primeiro argumento (`self`) para `+__init__+`. 
+Raramente precisamos escrever um `+__new__+`,
+pois a implementação herdada de `object` atende aos casos comuns.
+
+Se necessário, o método `+__new__+` pode devolver uma instância de uma classe diferente.
+Quando isso acontece, o interpretador não invoca `+__init__+`.
+Em outras palavras, a lógica de Python para criar um objeto é similar a esse pseudo-código:
+
+[source, python]
+----
+# pseudocódigo
+def criar(a_classe, algum_arg):
+    novo_objeto = a_classe.__new__(algum_arg)
+    if isinstance(novo_objeto, a_classe):
+        novo_objeto.__init__(algum_arg)
+    return novo_objeto
+
+# as instruções abaixo são praticamente equivalentes
+p = Quitute('pão de queijo')
+p = criar(Quitute, 'pão de queijo')
+----
+
+O <> mostra uma variante de `FrozenJSON` onde refatorei a
+lógica do método `build` para o método `+__new__+`.
+
+[[ex_explore2]]
+.explore2.py: usando `+__new__+` para criar novos objetos, que podem ou não ser instâncias de `FrozenJSON`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/explore2.py[tags=EXPLORE2]
+----
+====
+
+<1> Por ser um método de classe, `+__new__+` recebe como primeiro argumento
+ é a própria classe, e os demais argumentos são os mesmos passados
+para `+__init__+`, exceto o `self`.
+
+<2> O comportamento default é delegar para o `+__new__+` de uma superclasse.
+Neste caso, estamos invocando o `+__new__+` da classe `object`, passando
+`FrozenJSON` como único argumento.
+
+<3> As linhas restantes de `+__new__+` são exatamente as do antigo método
+`build`.
+
+<4> Aqui é onde invocávamos `FrozenJSON.build`; agora invocamos apenas a
+classe, e Python internamente invoca `+FrozenJSON.__new__+`.
+
+O método `+__new__+` recebe uma classe como primeiro argumento porque,
+normalmente, o objeto criado será uma instância daquela classe. Então, em
+`+FrozenJSON.__new__+`, quando a expressão `+super().__new__(cls)+`
+invoca `+object.__new__(FrozenJSON)+`, a instância criada pela classe `object`
+será uma instância de `FrozenJSON`. O atributo `+__class__+` da nova
+instância terá uma referência para `FrozenJSON`, apesar de que a instância
+será construída por `+object.__new__+`,
+implementado em C, nas entranhas do Python.
+
+O conjunto de dados da OSCON está organizado de um jeito pouco amigável à
+exploração interativa. Por exemplo, o evento no índice `40`, intitulado `'There
+*Will* Be Bugs'` (Haverá Bugs) tem dois palestrantes, `3471` e `5199`. Encontrar
+os nomes dos palestrantes é chato, pois esses são números de série e a lista
+`Schedule.speakers` não está indexada por eles. Para obter cada palestrante,
+precisamos iterar sobre a lista até encontrar um registro com o número de série
+correspondente. Nossa próxima tarefa é reestruturar os dados para preparar a
+recuperação automática de registros relacionados.((("",
+startref="Oflex22")))((("", startref="new22")))((("",
+startref="DWflex22")))((("", startref="DAPwrangl22")))
+
+[[computed_props_sec]]
+=== Propriedades computadas
+
+Vimos o decorador `@property` pela primeira vez na <>.
+No <> do <>,
+usei duas propriedades em `Vector2d` apenas para que os atributos `x` e `y` fossem limitados à leitura (_read-only_).
+Aqui veremos 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 da OSCON contêm números de série
+apontando para registros nas listas `'speakers'` e `'venues'`, como se fossem
+chaves estrangeiras em um banco de dados relacional. Por
+exemplo, esse é o registro de uma palestra (com a descrição parcial terminando
+em reticências):
+
+[source, json]
+----
+include::../code/22-dyn-attr-prop/oscon/osconfeed-talk.json[]
+----
+
+Vamos implementar uma classe `Event` com propriedades `venue` e `speakers`, para devolver automaticamente os dados relacionados—em outras palavras, "desreferenciar" o número de série.
+Dada uma instância de `Event`, o <> mostra o comportamento desejado.
+
+[[ex22-7-added-uuid]]
+.Ler `venue` e `speakers` devolve objetos `Record`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v4.py[tags=SCHEDULE4_DEMO]
+----
+====
+<1> Dada uma instância de `Event`...
+<2> ...acessar `event.venue` devolve um objeto `Record` em vez de um número de série.
+<3> Agora é fácil obter o nome do `venue`.
+<4> A propriedade `event.speakers` devolve uma lista de instâncias de `Record`.
+
+Como sempre, vamos criar o código passo a passo,
+começando com a classe `Record` e uma função para
+ler dados JSON e devolver um `dict` com instâncias de `Record`.
+
+==== Passo 1: criação de atributos baseados em dados
+
+O <> mostra((("computed properties",
+"data-driven attribute creation", id="CPdatadriven22")))
+o doctest para orientar este primeiro passo.
+
+[[ex_schedule_v1_demo]]
+.Testando schedule_v1.py (do <>)
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v1.py[tags=SCHEDULE1_DEMO]
+----
+====
+<1> Constrói um `dict` com os dados JSON; a função `load` está no <>.
+<2> As chaves em `records` são strings criadas a partir do tipo de registro e do número de série.
+<3> `speaker` é uma instância da classe `Record`, definida no <>.
+<4> Campos do JSON original podem ser acessados como atributos de instância de `Record`.
+
+O código de _schedule_v1.py_ está no <>.
+
+[[ex_schedule_v1]]
+.schedule_v1.py: reorganizando os dados de agendamento da OSCON
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v1.py[tags=SCHEDULE1]
+----
+====
+
+<1> Isto é um atalho comum para construir uma instância com atributos criados a partir de argumentos nomeados (a explicação detalhada está abaixo).
+
+<2> Usa o campo `serial` para criar a representação customizada de `Record` exibida no <>.
+
+<3> `load` vai devolver um `dict` de instâncias de `Record` no final.
+
+<4> Analisa o JSON, devolvendo objetos Python nativos: listas, dicts, strings, números, etc.
+
+<5> Itera sobre as quatro listas principais, chamadas `'conferences'`, `'events'`, `'speakers'`, e `'venues'`.
+
+<6> `record_type` é o nome da lista sem o último caractere, então `speakers` se
+torna `speaker`. No Python ≥ 3.9, podemos fazer isso de forma mais explícita com
+`collection.removesuffix('s')`—veja a https://fpy.li/pep616[_PEP 616—String
+methods to remove prefixes and suffixes_] (Métodos de string para remover prefixos
+e sufixos).
+
+<7> Cria a `key` no formato `'speaker.3471'`.
+
+<8> Cria uma instância de `Record` e a armazena em `records` com a chave `key`.
+
+
+O método `+Record.__init__+` ilustra um velho truque. Lembre-se de que o
+`+__dict__+` de um objeto é onde são guardados seus atributos—a menos que
+`+__slots__+` seja declarado na classe, como vimos na <>. Daí,
+atualizar o `+__dict__+` de uma instância é uma maneira fácil de criar um
+punhado de atributos naquela instância.footnote:[`Bunch` ou "punhado" é o nome
+da classe usada por Alex Martelli para compartilhar essa dica em uma receita de
+2001 intitulada https://fpy.li/22-4["The simple but handy ‘collector of a bunch
+of named stuff’ class" (_Uma classe simples mas prática 'coletora de um punhado
+de coisas nomeadas'_)].]
+
+[NOTE]
+====
+
+Dependendo da aplicação, a classe `Record` pode ter que lidar com chaves que não
+sejam nomes de atributo válidos, como vimos na <>. Tratar
+essa questão nos desviaria da ideia principal deste exemplo, e o
+problema não ocorre no conjunto de dados que estamos explorando.
+
+====
+
+A definição de `Record` no <> é tão simples que você pode estar
+se perguntando por que não a usei antes, em vez de `FrozenJSON`.
+São duas razões. Primeiro, `FrozenJSON` funciona convertendo recursivamente os
+mapeamentos aninhados e listas, mas `Record` não precisa fazer isso, pois nosso
+conjunto de dados convertido não contém mapeamentos aninhados. Os
+registros contêm apenas strings, inteiros, listas de strings e listas de
+inteiros. A segunda razão: `FrozenJSON` oferece acesso aos atributos no `dict`
+embutido `+__data+`—que usamos para invocar métodos como `.keys()`—e aqui
+não precisamos desta funcionalidade.
+
+[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://fpy.li/bd[`types.SimpleNamespace`],
+https://fpy.li/be[`argparse.Namespace`], e
+https://fpy.li/bf[`multiprocessing.managers.Namespace`].
+Escrevi a classe `Record`, mais simples, para destacar a ideia essencial:
+`+__init__+` preenchendo o `+__dict__+` da instância.
+
+====
+
+Após reorganizar o conjunto de dados de cronograma, podemos aprimorar a classe
+`Record` para obter automaticamente registros de `venue` e `speaker`
+referenciados em um registro `event`. Vamos utilizar propriedades para fazer
+exatamente isso nos próximos exemplos.((("", startref="CPdatadriven22")))
+
+
+[[oscon_schedule_v2_sec]]
+==== Passo 2: Propriedades para recuperar um registro relacionado
+
+O((("computed properties", "property to retrieve linked records",
+id="CPlinked22"))) objetivo da próxima versão é: dado um registro `event`, ler
+sua propriedade `venue` vai devolver um `Record`. Isso é similar ao que o ORM
+(_Object Relational Mapping_, Mapeamento Relacional de Objetos) do Django faz
+quando acessamos um campo `ForeignKey`: em vez da chave, recebemos o
+objeto relacionado.
+
+Vamos começar pela propriedade `venue`. Veja a interação parcial no <>.
+
+[[ex_schedule_v2_demo]]
+.Extratos dos doctests de schedule_v2.py
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v2.py[tags=SCHEDULE2_DEMO]
+----
+====
+<1> O método estático `Record.fetch` obtém um `Record` ou um  `Event` do conjunto de dados.
+<2> Observe que `event` é uma instância da classe `Event`.
+<3> Acessar `event.venue` devolve uma instância de `Record`.
+<4> Agora é fácil encontrar o nome de um `event.venue`.
+<5> A instância de `Event` também tem um atributo `venue_serial`, vindo dos dados JSON.
+
+`Event` é uma subclasse de `Record`, acrescentando um `venue` para obter os registros relacionados, e um método `+__repr__+` especializado.
+
+O código dessa seção está no módulo https://fpy.li/22-8[_schedule_v2.py_], no
+https://fpy.li/code[repositório de código do _Python Fluente_].
+O exemplo tem aproximadamente 50 linhas, então vou apresentá-lo em partes, começando pela classe `Record` aperfeiçoada.
+
+[[ex_schedule_v2_record]]
+.schedule_v2.py: a classe `Record` com um novo método `fetch`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v2.py[tags=SCHEDULE2_RECORD]
+----
+====
+<1> `inspect` será usado em `load`, lista do no <>.
+<2> No final, o atributo de classe privado `+__index+` preservará a referência ao `dict` devolvido por `load`.
+<3> `fetch` é um `staticmethod`, para deixar explícito que seu efeito não é influenciado pela classe ou pela instância de onde ele é invocado.
+<4> Preenche o `+Record.__index+`, se necessário.
+<5> E o utiliza para obter um registro com uma dada `key`.
+
+[TIP]
+====
+Esse é um exemplo onde o uso de `staticmethod` faz sentido.
+O método `fetch` sempre age sobre o atributo de classe `Record.__index`, mesmo quando invocado desde uma subclasse, como `Event.fetch()`—que exploraremos a seguir.
+Seria equivocado programá-lo como um método de classe, pois o primeiro argumento, `cls`, nunca é usado.
+====
+
+Agora podemos usar a propriedade na classe `Event`, listada no <>.
+
+[[ex_schedule_v2_event]]
+.schedule_v2.py: a classe `Event`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v2.py[tags=SCHEDULE2_EVENT]
+----
+====
+[role="pagebreak-before less_space"]
+<1> `Event` estende `Record`.
+<2> Se a instância tem um atributo `name`, esse atributo será usado para produzir uma representação customizada.
+Caso contrário, delega para o `+__repr__+` de `Record`.
+<3> A propriedade `venue` cria uma `key` a partir do atributo `venue_serial`, e a passa para o método de classe `fetch`, herdado de `Record` (a razão para usar `+self.__class__+` logo ficará clara).
+
+A segunda linha do método `venue` no <> devolve
+`+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 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.
+
+
+
+[WARNING]
+====
+Ao criar nomes de atributos de instância a partir de dados, sempre existe o risco de bugs causados pelo ocultamento de atributos da classe—como métodos—ou perda de dados pela sobrescrita acidental de atributos de instância existentes. Estes problemas talvez expliquem por que os dicts de Python não são como objetos JavaScript, e isto é uma vantagem.
+====
+
+Se a classe `Record` se comportasse mais como um mapeamento, implementando um `+__getitem__+` dinâmico em vez de um `+__getattr__+` dinâmico, não haveria risco de bugs por ocultamento ou sobrescrita. Um mapeamento customizado seria provavelmente a forma pythônica de implementar `Record`. Mas se eu tivesse seguido por aquele caminho, não estaríamos estudando os truques e as armadilhas da programação dinâmica de atributos.
+
+A parte final deste exemplo é a função `load` revisada, no <>.
+
+[[ex_schedule_v2_load]]
+.schedule_v2.py: a função `load`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v2.py[tags=SCHEDULE2_LOAD]
+----
+====
+
+<1> Até aqui, nenhuma mudança em relação ao `load` em _schedule_v1.py_ (do
+<>).
+
+<2> Muda a primeira letra de `record_type` para maiúscula, para criar um
+possível nome de classe; por exemplo, `'event'` se torna `'Event'`.
+
+<3> Obtém um objeto com aquele nome do escopo global do módulo; se aquele objeto
+não existir, obtém a classe `Record`.
+
+<4> Se o objeto recém-obtido é uma classe, e é uma subclasse de `Record`...
+
+<5> ...vincula o nome `factory` a ele. Isto significa que `factory` pode ser
+qualquer subclasse de `Record`, dependendo do `record_type`.
+
+<6> Caso contrário, vincula `Record` ao nome `factory`.
+
+<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
+uma instância de `Record` ou de 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 você definir classes chamadas `Speaker` ou `Venue`, `load` usará automaticamente
+aquelas classes ao criar e armazenar registros, em vez da classe `Record`.
+
+Agora vamos aplicar a mesma ideia à nova propriedade `speakers`, na classe `Events`.((("", startref="CPlinked22")))
+
+[[property_overriding_sec]]
+==== Passo 3: Propriedade sobrescrevendo atributo existente
+
+O((("computed properties", "property overriding existing attributes"))) nome da
+propriedade `venue` no <> não corresponde a um nome de
+campo nos registros da coleção `"events"`. Seus dados vêm de um campo chamado
+`venue_serial`. Por outro lado, cada registro na coleção `events` tem um campo
+`speakers`, contendo uma lista de números de série. Queremos expor essa
+informação na forma de uma propriedade `speakers` em instâncias de `Event`, que
+devolverá uma lista de instâncias de `Record`. Esta colisão de nomes exige uma
+atenção especial, como revela o <>.
+
+
+[[ex_schedule_v3_speakers]]
+.schedule_v3.py: a propriedade `speakers`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v3.py[tags=SCHEDULE3_SPEAKERS]
+----
+====
+<1> Os dados que precisamos estão em um atributo `speakers`, mas precisamos obtê-los diretamente
+do `+__dict__+` da instância, para evitar uma chamada recursiva à propriedade `speakers`.
+<2> Devolve uma lista com todos os registros com chaves correspondendo aos números em `spkr_serials`.
+
+Dentro do método `speakers`, uma tentativa de ler `self.speakers` invocará o
+mesmo método, provocando um `RecursionError`. Entretanto,
+acessando via `+self.__dict__['speakers']+`, evitamos o algoritmo de Python para
+busca de atributos, a propriedade não é acessada, 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
+existe uma propriedade com o nome `my_attr`, aquela propriedade oculta um
+atributo de instância com o mesmo nome. Isto será demonstrado com exemplos na
+<>, e o <> revelará que uma
+propriedade é implementada como um _descriptor_ (descritor de atributo), 
+uma abstração mais geral e poderosa.
+
+====
+
+Quando programei 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 nos dados da OSCON contêm poucos palestrantes,
+então programar algo mais complexo seria uma otimização prematura. Entretanto,
+criar um _cache_ de uma propriedade é uma necessidade comum—mas não trivial.
+Veremos então como fazer isso nos próximos exemplos.
+
+
+[[cached_property_sec]]
+==== Passo 4: Um _cache_ de propriedades sob medida
+
+Fazer((("computed properties", "property caching", id="CPpcach22"))) _caching_
+de propriedades é uma necessidade comum, pois há a expectativa de que uma
+expressão como `event.venue` deveria ser pouco dispendiosa.footnote:[Isso é, na
+verdade, uma desvantagem do Princípio de Acesso Uniforme de Meyer, mencionada no
+início deste capítulo. Quem tiver interesse nesta discussão pode ler o
+<> opcional.] Alguma forma de _caching_ poderia se tornar
+necessária caso o método `Record.fetch`, invocado nas propriedades de `Event`,
+precise consultar um banco de dados ou uma API Web.
+
+Na primeira edição de _Python Fluente_,
+programei a lógica customizada de _caching_ para o método `speakers`,
+como mostra o <>.
+
+[[ex_schedule_v4_hasattr]]
+.A lógica de _caching_ customizada usando `hasattr` impede a otimização de compartilhamento de chaves
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v4_hasattr.py[tags=SCHEDULE4_HASATTR_CACHE]
+----
+====
+<1> Se a instância não tem um atributo chamado `__speaker_objs`, obtém os objetos `speaker` e os armazena ali..
+<2> Devolve `self.__speaker_objs`.
+
+O _caching_ caseiro no <> é bastante direto, mas criar atributos após a inicialização da instância frustra a otimização da
+https://fpy.li/pep412[PEP 412—Key-Sharing Dictionary]
+(Dicionário com chaves compartilhadas), como explicado na <>.
+Dependendo do tamanho da massa de dados, a diferença de uso de memória pode ser importante.
+
+Uma solução manual similar, compatível com a otimização de
+compartilhamento de chaves, implica em escrever um `+__init__+` para a classe
+`Event`, para criar o atributo de instância `+__speaker_objs+` inicializado para `None`, e
+então usá-lo no método `speakers`. Veja o <>.
+
+[[ex_schedule_v4]]
+.Armazenamento definido em `+__init__+` para viabilizar a otimização de compartilhamento de chaves
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v4.py[tags=SCHEDULE4_INIT]
+# 15 lines omitted...
+include::../code/22-dyn-attr-prop/oscon/schedule_v4.py[tags=SCHEDULE4_CACHE]
+----
+====
+
+O <> e o <> ilustram técnicas simples de
+_caching_ bastante comuns em bases de código Python legadas. Entretanto, em
+programas com múltiplas threads, _caches_ manuais como aqueles introduzem
+condições de corrida que podem levar à corrupção de dados.
+Se duas threads estão lendo uma propriedade que não foi armazenada no _cache_
+anteriormente, a primeira thread precisará computar os dados para o atributo de
+_cache_ `+__speaker_objs+` e a segunda thread pode ler
+um valor inconsistente do _cache_.
+
+Felizmente, Python 3.8 introduziu o decorador `@functools.cached_property`, que
+é seguro para uso com threads (_thread safe_). Infelizmente, ele vem com algumas ressalvas,
+discutidas a seguir.((("", startref="CPpcach22")))
+
+[[caching_properties_sec]]
+==== Passo 5: _Caching_ de propriedades com `functools`
+
+O((("computed properties", "caching properties with functools", id="CPfunctool22")))((("functools module", "caching properties with", id="functools22"))) módulo `functools` oferece três decoradores para _caching_.
+Vimos `@cache` e `@lru_cache` na <> (<>). Python 3.8 introduziu `@cached_property`.
+
+O decorador `functools.cached_property` faz _cache_ do resultado de um método em uma variável de instância com o mesmo nome.
+
+Por exemplo, no <>, o valor computado pelo método `venue` é armazenado em um atributo `venue`, em `self`.
+Após isso, quando código cliente tenta ler `venue`, o recém-criado atributo de instância `venue` é usado, em vez do método.
+
+[[ex_schedule_v5_cached_property]]
+.Uso simples de uma `@cached_property`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v5.py[tags=SCHEDULE5_CACHED_PROPERTY]
+----
+====
+
+Na <>, vimos que uma propriedade oculta um atributo de instância de mesmo nome.
+Se isso é verdade, como `@cached_property` pode funcionar?
+Se a propriedade sobrescreve o atributo de instância, o atributo `venue` será ignorado e o método `venue` será sempre invocado,
+computando a `key` e rodando `fetch` todas as vezes!
+
+A((("attribute descriptors", "overriding versus nonoverriding")))((("nonoverriding descriptors")))((("overriding descriptors"))) resposta é um pouco triste: `cached_property` é um nome enganador.
+O decorador `@cached_property` não cria uma propriedade completa, ele cria um _descritor não dominante_. Um descritor é um objeto que gerencia o acesso a um atributo em outra classe.
+Vamos mergulhar nos descritores no <>.
+O decorador `property` é uma API de alto nível para criar um _descritor dominante_.
+O <> inclui uma 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://fpy.li/bg[«Documentação do
+Python»]:
+
+[quote]
+____
+
+A mecânica de `cached_property()` é um tanto diferente da de `property()`. Uma
+propriedade normal bloqueia a escrita em atributos, a menos que um _setter_ seja
+definido. Uma `cached_property`, por outro lado, permite a escrita.
+
+O decorador `cached_property` só funciona para consultas e apenas quando um
+atributo de mesmo nome não existe. Quando acionada, `cached_property` escreve no
+atributo de mesmo nome. Leituras e escritas subsequentes daquele atributo têm
+precedência sobre o método decorado com `cached_property` e ele funciona como um
+atributo normal.
+
+O valor "cacheado" (_cached_) pode ser excluído apagando-se o atributo.
+Isto permite que o método `cached_property` rode novamente.footnote:[Fonte:
+documentação de https://fpy.li/bg[@functools.cached_property]. Sei que o autor
+dessa explicação é Raymond Hettinger porque ele a escreveu em resposta a um
+problema que eu 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). Hettinger é um grande colaborador da
+documentação oficial de Python e de pacotes importantes da biblioteca padrão,
+como o sensacional `itertools`.]
+
+____
+
+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.
+
+[WARNING]
+====
+`@cached_property` tem algumas importantes limitações:
+
+* Ele não pode ser usado como um substituto direto de `@property`
+se o método decorado acessa um atributo de instância de mesmo nome.
+* Ele não pode ser usado em uma classe que defina `+__slots__+`.
+* Ele impede a otimização de chaves compartilhadas do `+__dict__+` da instância, pois cria um atributo de instância após o `+__init__+`.
+====
+
+Apesar destas 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://fpy.li/bh[documentação] de `@cached_property`
+recomenda uma solução alternativa que podemos usar com `speakers`:
+empilhar decoradores `@property` e `@cache`, como mostro no <>.
+
+[[ex_schedule_v5_property_over_cache]]
+.Empilhando `@property` sobre `@cache`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v5.py[tags=SCHEDULE5_PROPERTY_OVER_CACHE]
+----
+====
+<1> A ordem é importante: `@property` vai acima...
+<2> ...de `@cache`.
+
+Lembre-se do significado dessa sintaxe, comentada em <>.
+As três primeiras linhas do <> fazem isso:
+
+[source, python]
+----
+speakers = property(cache(speakers))
+----
+
+O `@cache` é aplicado a `speakers`, devolvendo uma nova função.
+Esta função é então decorada por `@property`,
+que a substitui por uma propriedade criada na hora.
+
+Isto encerra nossa discussão de propriedades somente para leitura e decoradores de _caching_, explorando o conjunto de dados da OSCON.
+
+Na próxima seção iniciamos uma nova série de exemplos, criando propriedades de leitura e escrita.((("", startref="DAPcomputp22")))((("", startref="CPfunctool22")))((("", startref="functools22")))
+
+[[prop_validation_sec]]
+=== Propriedades para validação de atributos
+
+Além((("attributes", "using properties for attribute validation", id="Aval22")))((("dynamic attributes and properties", "using properties for attribute validation", id="DAPval22"))) de computar valores de atributos, as propriedades também são usadas para impor regras de negócio, transformando um atributo público em um atributo protegido por um _getter_ e um _setter_, sem afetar o código cliente. Vamos explorar um exemplo mais elaborado.
+
+
+==== LineItem Versão #1: Classe para um item em um pedido
+
+Imagine uma aplicação para uma loja que vende alimentos orgânicos a granel, onde os
+fregueses podem encomendar nozes, frutas secas e cereais por peso. Neste
+sistema, cada pedido contém uma sequência de produtos, e cada produto é
+representado por uma instância de uma classe, como no <>.
+
+
+[[lineitem_class_v1]]
+.bulkfood_v1.py: a classe `LineItem` mais simples
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/bulkfood/bulkfood_v1.py[tags=LINEITEM_V1]
+----
+====
+
+Este código é simples e agradável. Talvez simples demais. <> mostra um problema.
+
+[[lineitem_problem_v1]]
+.Peso negativo resulta em subtotal negativo
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/bulkfood/bulkfood_v1.py[tags=LINEITEM_PROBLEM_V1]
+----
+====
+
+Apesar de ser um exemplo inventado, não é tão fantasioso quanto se poderia imaginar. Aqui está uma história do início da Amazon.com:
+
+[quote, Jeff Bezos, fundador da Amazon.com]
+____
+Descobrimos que os clientes podiam encomendar uma quantidade negativa de livros! E nós creditaríamos seus cartões de crédito com o preço e, suponho, esperaríamos que eles nos enviassem os livros.footnote:[Citação direta de Jeff Bezos no artigo do _Wall Street Journal_ https://fpy.li/22-16[_Birth of a Salesman_] (O Nascimento de um Vendedor) publicado em 15 de outubro de 2011. Pelo menos em 2023, é necessário ser assinante para ler o artigo.]
+____
+
+Como evitar isso?
+Poderíamos mudar a interface de `LineItem` para usar um _getter_ e um _setter_ para o atributo `weight` (peso).
+Este seria o estilo Java, e não está errado, mas podemos fazer melhor.
+É natural definir o `weight` de um item apenas atribuindo um valor a este atributo;
+e talvez o sistema esteja em produção, com outras partes já acessando `item.weight` diretamente.
+Neste caso, o estilo Python seria substituir o atributo de dados por uma propriedade.
+
+
+==== LineItem versão #2: Uma propriedade de validação
+
+Implementar uma propriedade nos permitirá usar um _getter_ e um _setter_,
+sem mudar a interface pública de `LineItem`:
+para definir o `weight` de um `LineItem` ainda poderemos escrever
+`raisins.weight = 12`.
+
+O <> lista o código de uma propriedade de leitura e escrita para `weight`.
+
+[[lineitem_class_v2]]
+.bulkfood_v2.py: um `LineItem` com uma propriedade `weight`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/bulkfood/bulkfood_v2.py[tags=LINEITEM_V2]
+----
+====
+<1> Aqui o _setter_ da propriedade já está em uso, assegurando que nenhuma instância com peso negativo possa ser criada.
+<2> `@property` decora o método _getter_.
+<3> Todos os métodos que implementam a propriedade compartilham o mesmo nome do atributo público: `weight`.
+<4> O valor é armazenado em um atributo privado `+__weight+`.
+<5> O _getter_ decorado ganha um atributo `.setter`, que também é um decorador; isto conecta o _getter_ e o _setter_.
+<6> Se o valor for maior que zero, atualizamos o `+__weight+` privado.
+<7> Caso contrário, um `ValueError` é levantado.
+
+Observe como agora não é possível criar uma `LineItem` com peso inválido:
+
+[source, python]
+----
+>>> walnuts = LineItem('walnuts', 0, 10.00)
+Traceback (most recent call last):
+    ...
+ValueError: value must be > 0
+----
+
+Assim protegemos `weight` impedindo que usuários forneçam valores negativos.
+Fregueses normalmente não podem definir o preço de um produto, mas um
+erro administrativo ou um bug poderiam criar um `LineItem` com um `price`
+negativo. Para evitar isso, poderíamos também transformar `price` em uma
+propriedade, mas isso levaria a alguma repetição no nosso código.
+
+Lembre-se da citação de Paul Graham no <>: "Quando vejo padrões
+em meus programas, considero isso um mau sinal." A cura para a repetição é a
+abstração. Há duas maneiras de abstrair definições de propriedades: usar uma
+fábrica de propriedades ou uma classe descritora. A abordagem via classe
+descritora é mais flexível, e dedicaremos o <> a uma discussão
+completa deste mecanismo. Na verdade, propriedades são, elas mesmas, implementadas
+como classes descritoras. Mas aqui vamos seguir com nossa exploração das
+propriedades, implementando uma fábrica de propriedades em forma de função.
+
+Mas antes de podermos implementar uma fábrica de propriedades, precisamos
+entender melhor as propriedades em si.((("", startref="Aval22")))((("",
+startref="DAPval22")))
+
+
+=== Propriedades em profundidade
+
+Apesar((("dynamic attributes and properties", "property class",
+id="DAPpclass22")))((("property class", id="proclass22"))) de ser frequentemente
+usada como um decorador, `property` é na verdade uma classe embutida. No Python,
+funções e classes são muitas vezes intercambiáveis, pois ambas são invocáveis e
+não há um operador `new` para instanciação de objeto, então invocar um
+construtor não é diferente de invocar uma função fábrica. E ambas podem ser
+usadas como decoradores, desde que elas devolvam um novo invocável, que seja um
+substituto adequado do invocável decorado.
+
+Esta é a assinatura completa do construtor de `property`:
+
+[source, python]
+----
+property(fget=None, fset=None, fdel=None, doc=None)
+----
+
+Todos os argumentos são opcionais, e se uma função não é fornecida para algum deles,
+a operação correspondente não será permitida pela propriedade resultante.
+
+O tipo `property` foi introduzido no Python 2.2, mas a sintaxe `@` do decorador
+só surgiu no Python 2.4. Então, por alguns anos, propriedades eram definidas
+passando as funções de acesso nos dois primeiros argumentos.
+
+A sintaxe "clássica" para definir propriedades sem decoradores é ilustrada pelo <>.
+
+[[lineitem_class_v2b]]
+.bulkfood_v2b.py: igual ao <>, sem usar decoradores
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/bulkfood/bulkfood_v2b.py[tags=LINEITEM_V2B]
+----
+====
+<1> Um _getter_ simples.
+<2> Um _setter_ simples.
+<3> Cria a `property` e a vincula a um atributo da classe.
+
+Em algumas situações, a forma clássica é melhor que a sintaxe do decorador; o código da fábrica de propriedade, que discutiremos em breve, é um exemplo. Por outro lado, no corpo de uma classe com muitos métodos, os decoradores tornam explícito quais são os _getters_ e os _setters_, sem depender da convenção do uso dos prefixos `get` e `set` em seus nomes.
+
+A presença de uma propriedade em uma classe afeta como os atributos nas instâncias daquela classe podem ser encontrados, de uma forma que à primeira vista pode ser surpreendente. A próxima seção explica isso.
+
+
+[[prop_override_instance]]
+==== Propriedades sobrescrevem atributos de instância
+
+Propriedades são sempre atributos de uma classe,
+mas elas controlam o acesso a atributos nas instâncias da classe.
+
+Na <>,
+vimos que quando uma instância e sua classe têm um atributo de dados com o mesmo nome,
+o atributo de instância sobrescreve, ou oculta,
+o atributo da classe—ao menos quando lido através daquela instância.
+O <> ilustra esse ponto.
+
+[[attr_override_demo1]]
+.Atributo de instância oculta o atributo de classe `data`
+====
+[source, python]
+----
+>>> class Class:  # <1>
+...     data = 'the class data attr'
+...     @property
+...     def prop(self):
+...         return 'the prop value'
+...
+>>> obj = Class()
+>>> vars(obj)  # <2>
+{}
+>>> obj.data  # <3>
+'the class data attr'
+>>> obj.data = 'bar' # <4>
+>>> vars(obj)  # <5>
+{'data': 'bar'}
+>>> obj.data  # <6>
+'bar'
+>>> Class.data  # <7>
+'the class data attr'
+----
+====
+<1> Define `Class` com dois atributos de classe: o atributo `data` e a propriedade `prop`.
+<2> `vars` devolve o `+__dict__+` de `obj`, mostrando que ele não tem atributos de instância.
+<3> Ler de `obj.data` obtém o valor de `Class.data`.
+<4> Escrever em `obj.data` cria um atributo de instância.
+<5> Inspeciona a instância, para ver o atributo de instância.
+<6> Ler agora de `obj.data` obtém o valor do atributo da instância.
+Quanto lido a partir da instância `obj`, o `data` da instância oculta o `data` da classe.
+<7> O atributo `Class.data` está intacto.
+
+
+Agora vamos tentar  sobrescrever o atributo `prop` na instância `obj`. Continuando a sessão de console anterior, temos o  <>.
+
+[[attr_override_demo2]]
+.Um atributo de instância não oculta uma propriedade da classe (continuando do <>)
+====
+[source, python]
+----
+>>> Class.prop  # <1>
+
+>>> obj.prop  # <2>
+'the prop value'
+>>> obj.prop = 'foo'  # <3>
+Traceback (most recent call last):
+  ...
+AttributeError: can't set attribute
+>>> obj.__dict__['prop'] = 'foo'  # <4>
+>>> vars(obj)  # <5>
+{'data': 'bar', 'prop': 'foo'}
+>>> obj.prop  # <6>
+'the prop value'
+>>> Class.prop = 'baz'  # <7>
+>>> obj.prop  # <8>
+'foo'
+----
+====
+<1> Ler `prop` diretamente de `Class` obtém o próprio objeto propriedade, sem executar seu método _getter_.
+<2> Ler `obj.prop` executa o _getter_ da propriedade.
+<3> Tentar definir um atributo `prop` na instância falha.
+<4> Inserir `'prop'` diretamente em `+obj.__dict__+` funciona.
+<5> Podemos ver que agora `obj` tem dois atributos de instância: `data` e `prop`.
+<6> Entretanto, ler `obj.prop` ainda executa o _getter_ da propriedade. A propriedade não é ocultada pelo atributo de instância.
+<7> Sobrescrever `Class.prop` destrói o objeto propriedade.
+<8> Agora `obj.prop` obtém o atributo de instância. `Class.prop` não é mais uma propriedade, então  ela não mais sobrescreve `obj.prop`.
+
+Como uma demonstração final, vamos adicionar uma propriedade a `Class`, e vê-la  sobrescrever um atributo de instância. O <> retoma a sessão onde o <> parou.
+
+[[attr_override_demo3]]
+.Uma nova propriedade de classe oculta o atributo de instância existente (continuando do <>)
+====
+[source, python]
+----
+>>> obj.data  # <1>
+'bar'
+>>> Class.data  # <2>
+'the class data attr'
+>>> Class.data = property(lambda self: 'the "data" prop value')  # <3>
+>>> obj.data  # <4>
+'the "data" prop value'
+>>> del Class.data  # <5>
+>>> obj.data  # <6>
+'bar'
+----
+====
+<1> `obj.data` obtém o atributo de instância `data`.
+<2> `Class.data` obtém o atributo de classe `data`.
+<3> Sobrescreve `Class.data` com uma nova propriedade.
+<4> `obj.data` está agora ocultado pela propriedade `Class.data`.
+<5> Apaga a propriedade .
+<6> `obj.data` agora lê novamente o atributo de instância `data`.
+
+O ponto principal desta seção é que uma expressão como `obj.data` não começa a
+busca por `data` em `obj`. A busca na verdade começa em `+obj.__class__+`, e
+Python só olha para a instância `obj` se não houver uma propriedade
+chamada `data` na classe. Isto se aplica a((("overriding descriptors"))) _descritores
+dominantes_ em geral, dos quais as propriedades são apenas um exemplo. 
+Para mais detalhes, aguarde o <>.
+
+Voltemos às propriedades. Toda unidade de código de Python—módulos, funções,
+classes, métodos—pode conter uma docstring. O próximo tópico mostra como anexar
+documentação às propriedades.
+
+
+==== Documentação de propriedades
+
+Quando ferramentas como uma IDE ou a função `help()` do console precisam mostrar a
+documentação de uma propriedade, elas extraem a informação do atributo
+`+__doc__+` da propriedade.
+
+Se usada com a sintaxe clássica de invocação, `property` pode receber a string de documentação no argumento `doc`:
+
+[source, python]
+----
+    weight = property(get_weight, set_weight, doc='weight in kilograms')
+----
+
+Alternativamente, a docstring do método _getter_—aquele que recebe o decorador `@property`—é usada
+como documentação da propriedade toda. A <> mostra telas de
+ajuda geradas a partir do código no <>.
+
+
+[[help_foo_screens]]
+.Capturas de tela do console de Python para os comandos `help(Foo.bar)` e `help(Foo)`. O código-fonte está no <>.
+image::../images/flpy_2201.png[Screenshots of the Python console]
+
+[[ex_foo_property_doc]]
+.Documentação para uma propriedade
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/doc_property.py[tags=DOC_PROPERTY]
+----
+====
+
+Agora que cobrimos o essencial sobre as propriedades, vamos voltar para a
+questão de proteger os atributos `weight` e `price` de `LineItem`, para que eles
+só aceitem valores maiores que zero—mas sem codar na unha dois pares
+de _getters/setters_ praticamente idênticos.((("",
+startref="DAPpclass22")))((("", startref="proclass22")))
+
+[[coding_prop_factory_sec]]
+=== Uma fábrica de propriedades
+
+Vamos((("dynamic attributes and properties", "coding property factories",
+id="DAPfactory22")))((("quantity properties", id="quantprop22"))) programar uma
+fábrica para criar propriedades `quantity` (quantidade)—assim denominada porque
+os atributos gerenciados representam quantidades que não podem ser negativas ou
+zero na aplicação. O <> mostra a aparência
+cristalina da classe `LineItem` usando duas instâncias de propriedades
+`quantity`: uma para gerenciar o atributo `weight`, a outra para o `price`.
+
+[[lineitem_class_v2prop_class]]
+.bulkfood_v2prop.py: a fábrica de propriedades `quantity` em ação
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py[tags=LINEITEM_V2_PROP_CLASS]
+----
+====
+<1> Usa a fábrica para definir a primeira propriedade customizada, `weight`, como um atributo de classe.
+<2> Essa segunda chamada cria outra propriedade customizada, `price`.
+<3> Aqui a propriedade já está ativa, assegurando que um peso negativo ou `0` seja rejeitado.
+<4> As propriedades também são usadas aqui, para recuperar os valores armazenados na instância.
+
+Recorde que propriedades são atributos de classe. Ao criar cada propriedade `quantity`, precisamos passar o nome do atributo de `LineItem` que será gerenciado por aquela propriedade específica. Ter que digitar a palavra `weight` duas vezes na linha abaixo é lamentável:
+
+[source, python]
+----
+    weight = quantity('weight')
+----
+
+Mas evitar tal repetição é complicado, pois a propriedade não tem como saber
+qual nome de atributo será vinculado a ela. Lembre-se: o lado direito de uma
+atribuição é avaliado primeiro, então quando `quantity()` é invocada, o atributo
+`weight` ainda não existe na classe.
+
+[NOTE]
+====
+Aperfeiçoar a propriedade `quantity` para que o usuário
+não precise redigitar o nome do atributo é um problema
+não trivial de metaprogramação, que resolveremos no <>.
+====
+
+O <> apresenta a fábrica de propriedades
+`quantity`.
+
+[[lineitem_class_v2prop]]
+.bulkfood_v2prop.py: a fábrica de propriedades `quantity`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py[tags=LINEITEM_V2_PROP_FACTORY_FUNCTION]
+----
+====
+<1> O argumento `storage_name`, onde os dados de cada propriedade são armazenados; para `weight`, o nome do armazenamento será `'weight'`.
+<2> O primeiro argumento do `qty_getter` poderia se chamar `self`, mas soaria estranho, pois isso não é o corpo de uma classe; `instance` se refere à instância de `LineItem` onde o atributo será armazenado.
+<3> `qty_getter` se refere a `storage_name`, então ele será preservado na clausura desta função; o valor é obtido diretamente de `+instance.__dict__+`, para contornar a propriedade e evitar uma recursão infinita.
+<4> `qty_setter` é definido, e também recebe `instance` como primeiro argumento.
+<5> O `value` é armazenado diretamente no `+instance.__dict__+`, novamente contornando a propriedade.
+<6> Cria e devolve um objeto propriedade customizado.
+
+As partes do <> que merecem um estudo mais cuidadoso giram em torno da variável `storage_name`.
+
+Quando programamos uma propriedade da maneira tradicional, o nome do atributo onde um valor será armazenado está definido explicitamente nos métodos _getter_ e _setter_.
+Mas aqui as funções `qty_getter` e `qty_setter` são genéricas, e dependem da variável `storage_name` para saber onde ler/escrever o atributo gerenciado no `+__dict__+` da instância.
+Cada vez que a fábrica `quantity` é invocada para criar uma propriedade, `storage_name` precisa ser definida com um valor único.
+
+As funções `qty_getter` e `qty_setter` serão encapsuladas pelo objeto `property`, criado na última linha da função fábrica. Mais tarde, quando forem chamadas para cumprir seus papéis, estas funções lerão a `storage_name` de suas clausuras para saber onde os valores dos atributos gerenciados estão armazenados.
+
+No <>, criei e inspecionei uma instância de `LineItem`, expondo os atributos armazenados.
+
+[[lineitem_class_v2prop_demo]]
+.bulkfood_v2prop.py: explorando propriedades e atributos de armazenamento
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py[tags=LINEITEM_V2_PROP_DEMO]
+----
+====
+<1> Lendo o `weight` e o `price` através das propriedades que ocultam os atributos de instância de mesmo nome.
+<2> Usando `vars` para inspecionar a instância `nutmeg`: aqui vemos os reais atributos de instância  usados para armazenar os valores.
+
+Observe como as propriedades criadas por nossa fábrica se valem do comportamento descrito na <>:
+a propriedade `weight` sobrescreve o atributo de instância `weight`, de forma que qualquer referência a `self.weight` ou `nutmeg.weight` é tratada pelas funções da propriedade, e a única maneira de contornar a lógica da propriedade é acessando diretamente o `+__dict__+` da instância.
+
+O código do <> pode ser um pouco complicado, mas é conciso: seu tamanho é idêntico ao do par _getter/setter_ decorado que define apenas a propriedade `weight` no <>. A definição de  `LineItem` no <> é mais legível sem o ruído de _getters_ e _setters_.
+
+Em um sistema real, o mesmo tipo de validação pode aparecer em muitos campos
+espalhados por várias classes, e a fábrica `quantity` estaria em um módulo
+utilitário, pronta para uso em todas as partes do sistema que precisem dela.
+Depois, aquela fábrica simples poderia ser refatorada em uma classe descritora
+mais extensível, com subclasses especializadas realizando diferentes validações.
+Faremos isso no <>.
+
+Vamos agora encerrar a discussão das propriedades com a questão da exclusão de atributos.((("", startref="DAPfactory22")))((("", startref="quantprop22")))
+
+
+[[attribute_deletion_sec]]
+=== Tratando a exclusão de atributos
+
+Podemos((("dynamic attributes and properties", "handling attribute deletion")))((("attributes", "handling attribute deletion")))((("del statement"))) usar a instrução `del` para excluir não apenas variáveis, mas também atributos:
+
+[source, python]
+----
+>>> class Demo:
+...    pass
+...
+>>> d = Demo()
+>>> d.color = 'green'
+>>> d.color
+'green'
+>>> del d.color
+>>> d.color
+Traceback (most recent call last):
+  File "", line 1, in 
+AttributeError: 'Demo' object has no attribute 'color'
+----
+
+Na prática, a exclusão de atributos não é algo que se faça todo dia no Python, e a necessidade de lidar com isso no caso de uma propriedade é ainda mais rara. Mas tal operação é suportada, e consigo pensar em um exemplo bobo para demonstrá-la.
+
+Em uma definição de propriedade, o decorador `@my_property.deleter` encapsula o método responsável por excluir o atributo gerenciado pela propriedade.
+Como prometido, o tolo <> foi inspirado pela cena com o Cavaleiro Negro, do filme _Monty Python e o Cálice Sagrado_.footnote:[Aquela cena sangrenta está https://fpy.li/22-17[disponível no Youtube] quando reviso essa seção, em outubro de 2021.]
+
+[[ex_black_knight]]
+.blackknight.py
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/blackknight.py[tags=BLACK_KNIGHT]
+----
+====
+
+Os doctests em _blackknight.py_ estão no <>.
+
+[[demo_black_knight]]
+.blackknight.py: doctests para <> (o Cavaleiro Negro nunca reconhece a derrota)
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/blackknight.py[tags=BLACK_KNIGHT_DEMO]
+----
+====
+
+Usando a sintaxe clássica de invocação em vez de decoradores, o argumento `fdel` configura a função de exclusão.
+Por exemplo, a propriedade `member` seria escrita assim no corpo da classe `BlackKnight`:
+
+[source, python]
+----
+    member = property(member_getter, fdel=member_deleter)
+----
+
+Se você não estiver usando uma propriedade, a exclusão de atributos pode ser tratada implementando o método especial de nível mais baixo `+__delattr__+`, apresentado na <>. Programar uma classe tola com `+__delattr__+` fica como exercício para a leitora que queira procrastinar.
+
+Propriedades são recursos poderosos, mas algumas vezes alternativas mais simples ou de nível mais baixo são preferíveis.
+Na seção final deste capítulo, vamos revisar algumas das APIs essenciais oferecidas pelo Python para programação de atributos dinâmicos.
+
+
+=== Atributos e funções essenciais para tratamento de atributos
+
+Ao longo((("dynamic attributes and properties", "essential attributes and functions for attribute handling", id="DAPessential22"))) deste capítulo, e mesmo antes no livro, usamos algumas funções embutidas e métodos especiais oferecidos pelo Python para lidar com atributos dinâmicos. Esta seção os reúne em um único lugar para uma visão geral, pois sua documentação está espalhada na documentação oficial.
+
+
+==== Atributos especiais que afetam o tratamento de atributos
+
+O comportamento de muitas das funções e dos métodos especiais elencados nas próximas seções dependem de três atributos especiais:
+
+`+__class__+`:: Uma((("__class__"))) referência à classe do objeto (isto
+é, `+obj.__class__+` é o mesmo que `type(obj)`). Python procura por métodos especiais tal como
+`+__getattr__+` apenas na classe do objeto, e não nas instâncias em si.
+
+`+__dict__+`:: Um((("__dict__")))((("__slots__"))) mapeamento que armazena os atributos passíveis de escrita de um objeto ou de uma classe. Um objeto que tenha um `+__dict__+` pode ter novos atributos arbitrários definidos a qualquer tempo. Se uma classe tem um atributo `+__slots__+`, então suas instâncias não podem ter um `+__dict__+`. Veja `+__slots__+` (abaixo).
+
+`+__slots__+`:: Um atributo que pode ser definido em uma classe para economizar memória.
+`+__slots__+` é uma `tuple` de strings, nomeando os atributos permitidosfootnote:[Alex Martelli assinala que, apesar de `+__slots__+` poder ser definido como uma `list`, é melhor ser explícito e sempre usar uma `tuple`, pois modificar a lista em `+__slots__+` após o processamento do corpo da classe não tem qualquer efeito. Assim, seria equivocado usar uma sequência mutável ali.]. Se o
+nome `+'__dict__'+` não estiver em `+__slots__+`, as instâncias daquela classe então não terão um `+__dict__+` próprio, e apenas os atributos listados em `+__slots__+` serão permitidos naquelas instâncias.
+Revise a <> para recordar esse tópico.
+
+[[bif_attribute_handling]]
+==== Funções embutidas para tratamento de atributos
+
+Essas cinco funções embutidas executam leitura, escrita e introspecção de atributos de objetos:
+
+`dir([object])`:: Lista((("dir([object]) function")))((("functions", "dir([object]) function")))
+a maioria dos atributos de um objeto. A
+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 é listado por `dir`, mas as chaves de `+__dict__+` são listadas.
+Vários atributos especiais de classes, como 
+`+__mro__+`, `+__bases__+` e `+__name__+`, também não são listados por `dir`.
+Você pode customizar o comportamento de `dir` implementando o método especial `+__dir__+`,
+como vimos no <>.
+Se o argumento opcional `object` não for passado, `dir` lista os nomes definidos no escopo atual.
+
+`getattr(object, name[, default])`:: Devolve((("functions", "getattr(object, name[, default]) function")))((("getattr(object, name[, default]) function"))) o atributo identificado pela string `name` no `object`. O principal caso de uso é obter atributos (ou métodos) cujos nomes são conhecidos só durante a execução do programa. Esta função pode recuperar um atributo da classe do objeto ou de uma superclasse. Se tal atributo não existir, `getattr` gera uma `AttributeError` ou devolve o valor `default`, se este argumento for passado.
+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://fpy.li/bk[«documentação»] explica: "Isto é implementado chamando `getattr(object, name)` e vendo se levanta um `AttributeError` ou não."
+
+`setattr(object, name, value)`:: Atribui((("functions", "setattr(object, name, value) function"))) o `value` ao atributo denominado `name` do `object`, se o `object` permitir essa operação. Isto pode criar um novo atributo ou sobrescrever um atributo existente.
+
+`vars([object])`:: Devolve((("vars([object]) function")))((("functions", "setattr(object, name, value) function"))) o `+__dict__+` de `object`; `vars` não funciona com instâncias de classes que definem `+__slots__+` e não têm um `+__dict__+` (compare com `dir`, que aceita essas instâncias). Sem argumentos, `vars()` faz o mesmo que `locals()`: devolve um `dict` representando o escopo local.
+
+[[special_methods_for_attr_sec]]
+==== Métodos especiais para tratamento de atributos
+
+Quando implementados em uma classe definida pelo usuário, os métodos especiais listados abaixo controlam a recuperação, a atualização, a exclusão e a listagem de atributos.
+
+Acessos((("getattr function")))((("setattr function")))((("functions", "hasattr function")))((("hasattr function")))((("functions", "getattr function")))((("functions", "setattr funcion"))) a atributos, usando a notação de ponto ou as funções embutidas `getattr`, `hasattr` e `setattr` disparam os métodos especiais correspondentes, listados aqui. A leitura e escrita de atributos como chaves no
+`+__dict__+` da instância não dispara esses métodos especiais—e esta é a forma habitual de evitá-los quando necessário.
+
+A seção https://fpy.li/bm["3.3.11. Pesquisa de método especial"] do capítulo "Modelo de dados" adverte:
+
+[quote]
+____
+Para classes customizadas, as invocações implícitas de métodos especiais só têm garantia de funcionar corretamente se definidas em um tipo de objeto [a classe], não no dicionário de instância do objeto.
+____
+
+Em outras palavras, assuma que os métodos especiais serão acessados na própria
+classe, mesmo quando o alvo da invocação é uma instância. Por esta razão, métodos
+especiais não são ocultados por atributos de instância de mesmo nome.
+
+Nos exemplos a seguir, assuma que há uma classe chamada `Class`, que `obj` é uma instância de `Class`, e que `atrib` é um atributo de `obj`.
+
+Para cada um destes métodos especiais, não importa se o acesso ao atributo é
+feito usando a notação de ponto ou uma das funções embutidas listadas na
+<>. Por exemplo, tanto `obj.atrib` quanto `getattr(obj,
+'atrib')` disparam `+Class.__getattribute__(obj, 'atrib')+`.
+
+`+__delattr__(self, name)+`:: Sempre((("__delattr__"))) invocado quando ocorre uma tentativa de excluir um atributo usando a instrução `del`; por exemplo, `del obj.atrib` dispara `+Class.__delattr__(obj, 'atrib')+`.
+Se `atrib` for uma propriedade, seu método de exclusão nunca será invocado se a classe implementar
+`+__delattr__+`.
+
+`+__dir__(self)+`:: Invocado((("__dir__"))) quando `dir` é invocado sobre um objeto, para fornecer uma lista de atributos; por exemplo, `dir(obj)` dispara
+`+Class.__dir__(obj)+`. Também é usado pelo recurso de auto-completar em todos os consoles modernos de Python.
+
+`+__getattr__(self, name)+`::
+Invocado((("__getattr__"))) apenas quando uma
+tentativa de obter o atributo nomeado falha, após `obj`, `Class` e suas
+superclasses serem pesquisadas. As expressões `obj.ausente`, `getattr(obj,
+'ausente')` e `hasattr(obj, 'ausente')` podem disparar
+`+Class.__getattr__(obj, 'ausente')+`, mas apenas se um atributo com o
+nome `'ausente'` não for encontrado em `obj` ou em `Class` e suas superclasses.
+
+`+__getattribute__(self, name)+`:: Sempre((("__getattribute__"))) invocado quando há uma tentativa de obter o atributo nomeado diretamente a partir de código Python (o interpretador pode ignorar isso em alguns casos, por exemplo para obter o método `+__repr__+`). A notação de ponto e as funções embutidas `getattr` e `hasattr` disparam este método. O método `+__getattr__+` só é invocado após `+__getattribute__+`, e apenas quando este gera um `AttributeError`. Para acessar atributos da instância `obj` sem entrar em uma recursão infinita, implementações de `+__getattribute__+` devem usar `+super().__getattribute__(obj, name)+`.
+
+`+__setattr__(self, name, value)+`::
+Sempre((("__setattr__"))) invocado quando há uma
+tentativa de atribuir um valor ao atributo nomeado. A notação de ponto e a
+função embutida `setattr` disparam esse método; por exemplo, tanto `obj.atrib =
+42` quanto `setattr(obj, 'atrib', 42)` disparam `+Class.__setattr__(obj, 'atrib', 42)+`.
+
+[WARNING]
+====
+
+Na prática, por serem invocados incondicionalmente, afetando praticamente todos
+os acessos a atributos, os métodos especiais `+__getattribute__+` e
+`+__setattr__+` são mais difíceis de usar corretamente que `+__getattr__+`, que
+só trata acessos a atributos que não existem. Usar propriedades ou descritores
+costuma ser mais fácil que definir `+__getattribute__+` ou
+`+__setattr__+`.
+
+====
+
+Isso conclui nosso mergulho nas propriedades, nos métodos especiais e nas outras técnicas de programação de atributos dinâmicos.((("", startref="DAPessential22")))
+
+
+=== Resumo do capítulo
+
+Começamos((("dynamic attributes and properties", "overview of"))) nossa
+discussão dos atributos dinâmicos mostrando exemplos práticos de classes
+simples para facilitar a exploração de um conjunto de dados em JSON. O primeiro
+exemplo foi a classe `FrozenJSON`, que converte listas e dicts aninhados em
+instâncias aninhadas de `FrozenJSON`, e em listas de instâncias da mesma classe.
+O código de `FrozenJSON` demonstrou o uso do método especial `+__getattr__+`
+para converter estruturas de dados sob demanda, sempre que seus atributos eram
+lidos. A última versão de `FrozenJSON` mostrou o uso do método construtor
+`+__new__+` para transformar uma classe em uma fábrica flexível de objetos, não
+restrita a instâncias de si mesma.
+
+Convertemos então o conjunto de dados JSON em um `dict` que armazena instâncias da classe `Record`.
+A primeira versão de `Record` tinha apenas algumas linhas e apresentou o truque de usar `+self.__dict__.update(**kwargs)+` para criar atributos arbitrários a partir de argumentos nomeados passados para `+__init__+`.
+A segunda passagem acrescentou a classe `Event`, implementando a recuperação automática de registros relacionados através de propriedades.
+
+Propriedades que devolvem valores calculados algumas vezes necessitam de _caching_, e falamos de algumas formas de fazer isso.
+Após descobrir que `@functools.cached_property` não é sempre aplicável, aprendemos sobre uma alternativa: a combinação de `@property` acima de `@functools.cache`, nesta ordem.
+
+A discussão sobre propriedades continuou com a classe `LineItem`, onde criamos
+uma propriedade para evitar que um atributo `weight` receba valores negativos ou
+zero, que não fazem sentido na lógica do negócio. Após um aprofundamento da
+sintaxe e da semântica das propriedades, criamos uma fábrica de propriedades
+para aplicar a mesma validação a `weight` e a `price`, sem precisar escrever
+múltiplos _getters_ e _setters_. A fábrica de propriedades se apoiou em
+conceitos sutis—como clausuras e a sobreposição de atributos de instância por
+propriedades—para fornecer uma solução genérica elegante, usando para isso o
+mesmo número de linhas que usamos antes para escrever manualmente a definição de
+uma única propriedade.
+
+Por fim, demos uma rápida passada pelo tratamento da exclusão de atributos com propriedades, seguida por um resumo dos principais atributos especiais, funções embutidas e métodos especiais que suportam a metaprogramação de atributos no núcleo da linguagem Python.
+
+
+=== 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
+capítulo
+https://fpy.li/bn[«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[«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[«Pesquisa de método especial»]. 
+A seção https://fpy.li/bt[«Atributos especiais»] trata dos atributos `+__class__+` e `+__dict__+`.
+
+O https://fpy.li/pycook3[_Python Cookbook, 3rd. ed._], de David Beazley e Brian
+K. Jones (O’Reilly), tem várias receitas relacionadas aos tópicos deste
+capítulo, mas eu destaco três delas: A
+_Recipe 8.8. Extending a Property in a Subclass_
+(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_
+(Delegando o Acesso a Atributos) implementa uma classe
+de fachada, demonstrando a maioria dos métodos especiais da
+<> deste livro; e a
+_Recipe 9.21. Avoiding Repetitive Property Methods_
+(Evitando Métodos de Propriedade Repetitivos),
+que foi a base da função fábrica de propriedades
+que apresentei no <>.
+
+O https://fpy.li/pynut3[_Python in a Nutshell_, 3rd. ed.], de Alex Martelli,
+Anna Ravenscroft e Steve Holden (O'Reilly), é rigoroso e objetivo. Eles dedicam
+apenas três páginas a propriedades, mas isto foi possível 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  implementadas debaixo
+dos panos. Assim, quando Martelli et al. chegam às 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 _Design by
+Contract_ [Projeto por Contrato], projetou a linguagem Eiffel,
+e escreveu o excelente _Object-Oriented
+Software Construction_, 2ª ed. (Pearson). Os primeiros seis capítulos são
+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. Spoiler: na última página do livro o autor revela que a "notação"
+extremamente legível usada como pseudocódigo ao longo livro é
+na verdade a linguagem Eiffel.
+
+
+[role="pagebreak-before less_space"]
+[[properties_soapbox]]
+.Ponto de Vista
+****
+
+O Princípio de Acesso Uniforme de Meyer((("Soapbox sidebars", "Uniform Access Principle", id="Suaccess22")))((("Uniform Access Principle", id="uaccess22")))((("Meyer's Uniform Access Principle", id="meyerua22")))((("dynamic attributes and properties", "Soapbox discussion", id="DAPsoap22"))) é esteticamente atraente. Como um programador usando uma API, eu não deveria ter de me preocupar se `product.price` simplesmente lê um atributo de dados ou executa uma computação. Como um consumidor e um cidadão, eu me importo: no comércio online atual, o valor de `product.price` muitas vezes depende de quem está perguntando, então ele certamente não é um mero atributo de dados. Na verdade, é uma prática comum apresentar um preço mais baixo se a consulta vem de fora da loja—por exemplo, de um mecanismo de comparação de preços. Isso pune os fregueses fiéis, que gostam de explorar sua loja favorita. Mas estou divagando.
+
+A digressão anterior toca um ponto relevante para a programação: apesar do
+Princípio de Acesso Uniforme fazer todo sentido em um mundo ideal, na realidade
+os usuários de uma API podem precisar saber se ler `product.price` é
+potencialmente dispendioso ou demorado demais. Isto é um problema geral das abstrações
+em programação: elas dificultam raciocinar sobre o custo de computar
+uma expressão durante a execução. Por outro lado, abstrações
+permitem aos usuários da linguagem fazerem mais com menos código. É uma troca. Como de
+hábito em questões de engenharia de software, o
+https://fpy.li/22-26[«wiki original»] de Ward Cunningham contém argumentos inteligentes sobre os prós e contras do
+https://fpy.li/22-27[_Uniform Access Principle_] (Princípio de Acesso Uniforme).
+
+Em linguagens de programação orientadas a objetos, a aplicação ou violação do Princípio de Acesso Uniforme muitas vezes gira em torno da sintaxe de leitura de atributos de dados públicos versus a invocação de métodos _getter/setter_.
+
+Smalltalk e Ruby resolvem essa questão de uma forma simples e elegante: elas não suportam nenhuma forma de atributos de dados públicos. Todo atributo de instância nessas linguagens é privado, então qualquer acesso a eles deve passar por métodos. Mas sua sintaxe torna isso indolor: em Ruby, `product.price` invoca o _getter_ de `price`; em Smalltalk, ele é simplesmente `product price`.
+
+Na outra ponta do espectro, a linguagem Java permite ao programador escolher entre quatro modificadores de nível de acesso—incluindo o default sem nome que o https://fpy.li/22-28[«Java Tutorial»] chama de "_package-private_".
+
+A prática geral, entretanto, não concorda com a sintaxe definida pelos projetistas de Java. Todos no campo de Java concordam que atributos devem ser `private`, e é necessário escrever isso explicitamente todas as vezes, porque não é o default. Quando todos os atributos são privados, todo acesso a eles de fora da classe precisa passar por métodos de acesso. Os IDEs de Java incluem atalhos para gerar métodos de acesso automaticamente. Infelizmente, o IDE não ajuda quando você precisa ler aquele código seis meses depois. É problema seu navegar por um oceano de métodos de acesso que não fazem nada, para encontrar aqueles que adicionam valor, implementando alguma lógica do negócio.
+
+Alex Martelli fala pela maioria da comunidade Python quando chama métodos de acesso de "idiomatismos patéticos", e então apresenta
+exemplos que parecem muito diferentes, mas fazem a mesma coisa:footnote:[Alex Martelli, _Python in a Nutshell_, 2ª ed. (O'Reilly), p. 101.]
+
+[source, python]
+----
+um_objeto.contagem += 1
+# bem melhor que...
+um_objeto.set_contagem(um_objeto.get_contagem() + 1)
+----
+
+Algumas vezes, ao projetar uma API, me pergunto se todo método que não recebe argumentos (além de `self`) é uma função pura (isto é, devolve um valor mas não tem efeitos colaterais) não deveria ser substituído por uma propriedade somente de leitura. Neste capítulo, o método `LineItem.subtotal` (no <>) seria um bom candidato a se tornar uma propriedade somente para leitura. Claro, isso exclui métodos projetados para modificar o objeto, tal como `my_list.clear()`. Seria uma péssima ideia transformar isso em uma propriedade, de tal forma que o mero acesso a `my_list.clear` apagaria o conteúdo da lista!
+
+Na biblioteca GPIO https://fpy.li/22-29[_Pingo_] (mencionada na
+https://fpy.li/ct[«Seção 3.5.2»], vol.1), grande
+parte da API do usuário está baseada em propriedades. Por exemplo, para ler o
+valor atual de uma porta analógica, o usuário escreve `pin.value`, e definir o
+modo de uma porta digital é escrito como 
+`pin.mode = OUT`. Por trás da cortina, ler o
+valor de uma porta analógica ou definir o modo de uma porta digital pode
+implicar em bastante código, dependendo do driver específico da placa. Decidimos
+usar propriedades no Pingo porque queríamos que a API fosse confortável de usar
+até mesmo em ambientes interativos como um Jupyter Notebook, e achamos que
+`pin.mode = OUT` é mais bonito e fácil de digitar que
+`pin.set_mode(OUT)`.
+
+Apesar de achar a solução de Smalltalk e de Ruby mais limpa, acho que a abordagem de Python faz mais sentido que a de Java. Podemos começar simples, programando elementos de dados como atributos públicos, pois sabemos que eles sempre podem ser encapsulados por propriedades (ou descritores, dos quais falaremos no próximo capítulo).((("", startref="meyerua22")))((("", startref="uaccess22")))((("", startref="Suaccess22")))
+
+**`+__new__+` é melhor que `new`**
+
+Em Python((("function-class duality")))((("Soapbox sidebars",
+"function-class duality"))) a instanciação de um objeto usa mesma
+sintaxe que a invocação de uma função: `my_obj = spam()`,
+onde `spam` pode ser uma classe ou qualquer
+outro invocável.
+
+Outras linguagens, influenciadas pela sintaxe do {cpp}, têm um operador `new`
+que faz a instanciação parecer diferente de uma invocação. Na maior parte do
+tempo, o usuário de uma API não se importa se `spam` é uma função ou uma classe.
+Por anos tive a impressão que `property` era uma função. No uso cotidiano, não
+faz diferença.
+
+Há muitas boas razões para substituir construtores por fábricas.footnote:[As razões que menciono foram apresentadas no artigo intitulado https://fpy.li/22-30[_Java's new Considered Harmful_] (new de Java considerado nocivo), de Jonathan Amsterdam, publicado na Dr. Dobbs Journal, e em _Consider static factory methods instead of constructors_ (Considere substituir construtores por métodos fábrica estáticos), que é o Item 1 do premiado livro _Effective Java_, 3ª ed., de Joshua Bloch (Addison-Wesley).] Um motivo comum é limitar o número de instâncias, devolvendo objetos construídos anterioremente (como no padrão de projeto Singleton). Um uso relacionado é fazer _caching_ de uma construção de objeto dispendiosa. Além disso, às vezes é conveniente devolver objetos de tipos diferentes, dependendo dos argumentos passados.
+
+Programar um construtor é simples; fornecer uma fábrica aumenta a flexibilidade
+às custas de mais código. Em linguagens com um operador `new`, o projetista de
+uma API precisa decidir se vai fornecer um construtor simples ou
+investir em uma fábrica. Se a escolha inicial estiver errada, a correção pode
+ser cara—tudo porque `new` é um operador.
+
+Algumas vezes pode ser conveniente pegar o caminho inverso, e substituir uma função simples por uma classe.
+
+Em Python, classes e funções são intercambiáveis em muitas situações.
+Não apenas pela ausência de um operador `new` para construir instâncias,
+mas também porque existe o método especial `+__new__+`,
+que pode transformar uma classe em uma fábrica que produz objetos de diferentes tipos
+(como vimos na <>), ou devolve instâncias pré-fabricadas em vez de criar uma nova instância toda vez.
+
+Esta dualidade função-classe seria mais fácil de aproveitar se a
+https://fpy.li/22-31[PEP 8—Style Guide for Python Code] (Guia de Estilo para Código Python)
+não recomendasse `CamelCase` para nomes de classes.
+Por outro lado, dezenas de classes na biblioteca padrão têm nomes apenas em minúsculas
+(por exemplo, `property`, `str`, `defaultdict`, etc.).
+Daí que talvez o uso de nomes de classe só com minúsculas seja uma vantagem, e não um bug.
+Mas independente do ponto de vista, esta inconsistência
+no uso de maiúsculas e minúsculas nos nomes de classes na biblioteca padrão de Python é desconfortável.
+
+Apesar da invocação de uma função não ser diferente da invocação de uma classe,
+é bom saber qual é qual porque podemos criar uma subclasse de uma classe, mas não de uma função.
+Então eu, pessoalmente, uso `CamelCase` nas classes que escrevo,
+e gostaria que todas as classea na biblioteca padrão de Python seguissem alguma convenção
+para não evitar casos como `collections.OrderedDict` e `collections.defaultdict`.((("", startref="DAPsoap22")))
+****
diff --git a/online/cap23.adoc b/online/cap23.adoc
new file mode 100644
index 00000000..153d30ca
--- /dev/null
+++ b/online/cap23.adoc
@@ -0,0 +1,781 @@
+[[ch_descriptors]]
+== Descritores de Atributos
+:example-number: 0
+:figure-number: 0
+
+[quote, Raymond Hettinger, guru e mantenedor do Python]
+____
+
+Aprender sobre descritores não apenas dá acesso a um conjunto maior de ferramentas,
+também cria uma compreensão melhor sobre o funcionamento do 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, são descritores os campos de registros em um ORM (_Object Relational Mapping_, Mapeamento
+Objeto-Relacional) como o _SQLAlchemy_ ou ORM do _Django_.
+Nestes sistemas, os descritores controlam a conversão
+e validação de dados dos atributos de objetos Python
+para campos nas tabelas do banco de dados.
+
+Um((("__get__")))((("__set__")))((("__delete__"))) descritor é uma classe que implementa um protocolo dinâmico, composto pelos métodos `+__get__+`, `+__set__+`, e `+__delete__+`. A classe `property` implementa o protocolo de descritor completo. Como habitual em protocolos dinâmicos, implementações parciais são aceitáveis. E, na verdade, a maioria dos descritores que vemos em código real implementa apenas
+`+__get__+` e `+__set__+`, e muitos têm só um destes métodos.
+
+Descritores são um recurso característico de Python, presentes não apenas no nível das aplicações, mas também na infraestrutura da linguagem.
+Funções definidas pelo usuário são descritores. Veremos como o protocolo do descritor permite que métodos operem como métodos vinculados ou funções, dependendo de como são acessados.
+
+Entender os descritores é crucial para dominar Python. Este capítulo é sobre isso.
+
+Nas próximas páginas((("attribute descriptors", "topics covered"))) vamos refatorar o exemplo da loja de alimentos orgânicos a granel, visto na <>, substituindo propriedades por descritores.
+Isto tornará mais fácil reutilizar a lógica de validação de atributos em diferentes classes.
+
+Vamos estudar os conceitos de descritores dominantes e não dominantes, e entender que as funções de Python são descritores.
+Para finalizar, veremos algumas dicas para a implementação de descritores.
+
+
+[[whats_new_descriptor_sec]]
+=== Novidades neste capítulo
+
+O((("attribute descriptors", "significant changes to"))) exemplo do descritor
+`Quantity`, na <>, ficou muito mais simples graças ao
+método especial `+__set_name__+`, adicionado ao protocolo de descritor no Python
+3.6. Naquela mesma seção, removi o exemplo da fábrica de propriedades, pois ele se
+tornou irrelevante: o ponto ali era mostrar uma solução alternativa para o
+problema de `Quantity`, mas com `+__set_name__+` o exemplo ficou redundante.
+Removi a classe `AutoStorage`, que aparecia na <> pelo mesmo motivo.
+
+[[validating_descriptor_sec]]
+=== Descritor para validação de atributos
+
+Como((("attribute descriptors", "attribute validation", id="ADvalidation23")))((("attributes", "using attribute descriptors for validation", id="ATvalidation23"))) vimos na <>, uma fábrica de propriedades é uma forma de evitar código repetitivo de _getters_ e _setters_, aplicando padrões de programação funcional.
+Uma fábrica de propriedades é uma função de ordem superior que cria um conjunto de funções de acesso parametrizadas e constrói uma instância de propriedade customizada, com clausuras para preservar configurações como `storage_name`.
+A forma orientada a objetos de resolver o mesmo problema é uma classe descritora.
+
+Vamos seguir com a série de exemplos `LineItem` de onde paramos, na <>, refatorando a fábrica de propriedades `quantity` em uma classe descritora `Quantity`.
+Isso vai torná-la mais fácil de usar.
+
+==== LineItem versão #3: Um descritor simples
+
+Como dito na introdução, uma classe que implemente um método `+__get__+`, um `+__set__+`, ou um
+`+__delete__+` é um descritor. Para usar um descritor, declaramos instâncias dele como atributos em outra classe.
+
+
+Vamos criar um descritor `Quantity`, e a classe `LineItem` vai usar duas instâncias de `Quantity`: uma para gerenciar o atributo `weight`, a outra para `price`. Um((("UML class diagrams", "managed and descriptor classes"))) diagrama ajuda: dê uma olhada na <>.
+
+[[lineitem3_uml]]
+.Diagrama de classe UML para `LineItem` usando uma classe descritora chamada `Quantity`. Atributos sublinhados no UML são atributos de classe. Observe que [.underline]#weight# e [.underline]#price# são instâncias de `Quantity` vinculadas à classe `LineItem` (são atributos da classe), mas cada instância de `LineItem` também tem atributos `weight` e `price`, onde estes valores são armazenados na própria instância.
+image::../images/flpy_2301.png[align="center", pdfwidth=10cm]
+
+Note que a palavra `weight` aparece duas vezes na <>, pois na verdade há dois atributos diferentes chamados `weight`: um é um atributo de classe de `LineItem`, o outro é um atributo de instância que existirá em cada objeto `LineItem`. O mesmo se aplica a `price`.
+
+===== Termos para entender descritores
+
+Implementar((("attribute descriptors", "relevant terminology"))) e usar descritores envolve vários componentes, então é útil ser preciso ao nomeá-los.
+Vou utilizar termos e definições abaixo nas descrições dos exemplos desse capítulo.
+Será mais fácil entendê-los após ver o código, mas quis colocar todas as definições no início, para você poder voltar a elas quando necessário.
+
+Classe descritora (_descriptor class_):: Uma((("descriptor classes"))) classe que implementa o protocolo de descritor. Por exemplo, `Quantity` na <>.
+
+Classe gerenciada (_managed class_):: A((("managed classes"))) classe onde as instâncias do descritor são declaradas, como atributos de classe. Na <>, `LineItem` é a classe gerenciada.
+
+Instância do descritor (_descriptor instance_):: Cada((("descriptor instances"))) instância de uma classe descritora declarada como um atributo de classe na classe gerenciada. Na <>, cada instância do descritor está representada pela seta de composição com um nome sublinhado (na UML, o sublinhado indica um atributo de classe). Os diamantes pretos tocam a classe `LineItem`, que contém as instâncias do descritor.
+
+Instância gerenciada (_managed instance_):: Uma((("managed instances"))) instância da classe gerenciada. Neste exemplo, instâncias de `LineItem` são as instâncias gerenciadas (elas não aparecem no diagrama de classe).
+
+Atributo de armazenamento (_storage attribute_):: Um((("storage attributes"))) atributo da instância gerenciada que guarda o valor de um atributo gerenciado para aquela instância específica. Na <>, os atributos de instância `weight` e `price` de `LineItem` são atributos de armazenamento. Eles são diferentes das instâncias do descritor, que são sempre atributos de classe.
+
+Atributo gerenciado (_managed attribute_):: Um((("managed attributes"))) atributo público na classe gerenciada que é controlado por uma instância de um descritor, com os valores preservados em atributos de armazenamento. Uma instância do descritor e um atributo de armazenamento fornecem a infraestrutura para um atributo gerenciado.
+
+É importante entender que instâncias de `Quantity` são atributos de classe de `LineItem`. Este((("UML class diagrams", "annotated with MGN"))) ponto fundamental é realçado pelas engenhocas (_mills_) e bugigangas (_gizmos_) na <>.
+
+[[lineitem3_uml_mgn]]
+.Diagrama de classe UML anotado com MGN (Mills & Gizmos Notation—Notação de Engenhocas e Bugigangas): classes são engenhocas que produzem bugigangas—as instâncias. A engenhoca `Quantity` produz duas bugigangas de cabeça redonda, que são anexadas à engenhoca `LineItem`: [.underline]#weight# e [.underline]#price#. A engenhoca `LineItem` produz bugigangas retangulares que têm seus próprios atributos `weight` e `price`, onde aqueles valores são armazenados.
+image::../images/flpy_2302.png[align="center", pdfwidth=11cm]
+
+[role="pagebreak-before less_space"]
+[[mgn_box1]]
+.Apresentando a notação Engenhocas & Bugigangas (_Mills & Gizmos_)
+****
+Após((("Mills & Gizmos Notation (MGN)"))) explicar descritores várias vezes, percebi que a UML não é muito boa para mostrar as relações entre classes e instâncias, tal como a relação entre uma classe gerenciada e as instâncias do descritor.footnote:[Classes e instâncias são representadas por retângulos em diagramas de classe UML. Há diferenças visuais, mas instâncias raramente aparecem em diagramas de classe, então desenvolvedores podem não reconhecê-las como tal.] Daí inventei minha própria "linguagem", a Notação Engenhocas e Bugigangas (MGN), que uso para anotar diagramas UML.
+
+Desenhei a MGN para tornar mais evidente a diferença entre classes e instâncias. Veja a <>. Na MGN, uma classe aparece como uma "engenhoca", (_mill_) uma máquina complicada que produz bugigangas (_gizmos_). Classes/engenhocas são máquinas com alavancas e mostradores. As bugigangas são as instâncias, e elas têm uma aparência mais simples. Quando este livro é produzido em cores, as bugigangas têm a mesma cor da engenhoca que as produziu.
+
+[[mgn_diagram_demo]]
+.Esboço MGN mostrando a classe `LineItem` produzindo três instâncias, e `Quantity` produzindo duas. Uma instância de `Quantity` está recuperando um valor armazenado em uma instância de `LineItem`.
+image::../images/flpy_2303.png[Esboço MGN para `LineItem` e `Quantity`]
+
+Para este exemplo, desenhei instâncias de `LineItem` como linhas em uma fatura tabular, com três células representando os três atributos (`description`, `weight` e `price`). Como as instâncias de `Quantity` são descritores, elas têm uma lente de aumento para ler (_get_) os valores, e uma garra para escrever (_set_) os valores. Quando chegarmos às metaclasses, você me agradecerá por esses desenhos.
+****
+
+Chega de rabiscos por enquanto. Aqui está o código: o <> mostra a classe descritora `Quantity`, e o <> lista a nova classe `LineItem` usando duas instâncias de `Quantity`.
+
+[[quantity_v3]]
+.bulkfood_v3.py: o descritor `Quantity` não aceita valores negativos
+====
+[source, python]
+----
+include::../code/23-descriptor/bulkfood/bulkfood_v3.py[tags=LINEITEM_QUANTITY_V3]
+----
+====
+
+<1> O descritor é um recurso baseado em protocolo: não é necessário herdar de uma classe específica, basta implementar `+__get__+` ou `+__set__+`.
+
+<2> Cada instância de `Quantity` terá um atributo `storage_name`: é o nome do atributo de armazenamento que vai preservar o valor nas instâncias gerenciadas.
+
+<3> O `+__set__+` é invocado quando ocorre uma tentativa de atribuir um valor a um atributo gerenciado. Aqui, `self` é a instância do descritor `Quantity`, (isto é, `LineItem.weight` ou `LineItem.price`), `instance` é a instância gerenciada (uma instância de `LineItem`) e `value` é o valor que está sendo atribuído.
+
+<4> Precisamos armazenar o valor do atributo diretamente no `+__dict__+`; invocar `setattr(instance, self.storage_name, value)` dispararia novamente o método `+__set__+`, levando a uma recursão infinita.
+
+<5> Precisamos implementar `+__get__+`, pois o nome do atributo gerenciado pode não ser igual ao `storage_name`. O argumento `owner` será explicado a seguir.
+
+Implementar `+__get__+` é necessário porque um usuário poderia escrever algo assim:
+
+[source, python]
+----
+class Casa:
+    quartos = Quantity('cômodos')
+----
+
+Na classe `Casa`, o atributo gerenciado é `quartos`, mas o atributo de armazenamento é `cômodos`.
+Dada uma instância de `Casa` chamada `meu_lar`, acessar e modificar `meu_lar.quartos` passa pela instância do descritor `Quantity` vinculado a `quartos`, mas acessar e modificar `meu_lar.cômodos` não passa pelo descritor.
+
+Observe que `+__get__+` recebe três argumentos: `self`, `instance` e `owner`. O argumento `owner` é uma referência à classe gerenciada (por exemplo, `LineItem`), e é útil se você quiser que o descritor suporte o acesso a um atributo de classe—talvez para emular o comportamento default de Python, de procurar um atributo de classe quando o nome não é encontrado na instância.
+
+Se um atributo gerenciado como `weight`, é acessado através da classe como
+`LineItem.weight`, o método `+__get__+` do descritor recebe `None` no argumento `instance`.
+
+Para suportar introspecção e outras técnicas de metaprogramação pelo usuário, é uma boa prática fazer `+__get__+` devolver a instância do descritor quando o atributo gerenciado é acessado através da classe. Para fazer isso, escreveríamos `+__get__+` assim:
+
+[source, python]
+----
+    def __get__(self, instance, owner):
+        if instance is None:
+            return self
+        else:
+            return instance.__dict__[self.storage_name]
+----
+
+O <> demonstra o uso de `Quantity` em `LineItem`.
+
+[[lineitem_class_v3]]
+.bulkfood_v3.py: descritores `Quantity` gerenciam atributos em `LineItem`
+====
+[source, python]
+----
+include::../code/23-descriptor/bulkfood/bulkfood_v3.py[tags=LINEITEM_V3]
+----
+====
+<1> A primeira instância do descritor vai gerenciar o atributo `weight`.
+<2> A segunda instância do descritor vai gerenciar o atributo `price`.
+<3> O restante do corpo da classe é tão simples e limpo como o código original em _bulkfood_v1.py_ (no <>).
+
+O código no <> funciona como esperado, evitando a venda de trufas por $0:footnote:[O quilo de trufas brancas custa milhares de reais. Impedir a venda de trufas por $0,01 fica como exercício para a leitora com espírito de aventura. Conheço o caso real de uma pessoa que comprou uma enciclopédia sobre estatística de 1.800 dólares por 18 dólares, devido a um erro em uma loja online (neste caso não foi na _Amazon.com_).]
+
+[source, python]
+----
+>>> truffle = LineItem('White truffle', 100, 0)
+Traceback (most recent call last):
+    ...
+ValueError: value must be > 0
+----
+
+[WARNING]
+====
+Ao programar os métodos `+__get__+` e `+__set__+` de um descritor, tenha em mente o significado dos argumentos `self` e `instance`: `self` é a instância do descritor, `instance` é a instância gerenciada. Descritores que gerenciam atributos de instância devem armazenar os valores nas instâncias gerenciadas. É por isso que Python fornece o argumento `instance` aos métodos do descritor.
+====
+
+Pode ser tentador armazenar o valor de cada atributo gerenciado no próprio do descritor, mas é um erro. 
+
+Em outras palavras, seria errado armazenar o valor no `self.__dict__`:
+
+[source, python]
+----
+    self.__dict__[self.storage_name] = value  # errado
+----
+
+O correto é armazenar o valor no `instance.__dict__`:
+
+[source, python]
+----
+    instance.__dict__[self.storage_name] = value  # certo
+----
+
+Para entender por que isso está errado, pense no significado dos dois primeiros
+argumentos passados a `+__set__+`: `self` e `instance`. Aqui, `self` é a
+instância do descritor, que na verdade é um atributo de classe da classe
+gerenciada. Você pode ter milhares de instâncias de `LineItem` na memória em um
+dado momento, mas terá apenas duas instâncias dos descritores: os atributos de
+classe `LineItem.weight` e `LineItem.price`. Então, qualquer coisa armazenada
+nas próprias instâncias do descritor é na verdade parte de um atributo de classe
+de `LineItem`, e portanto é compartilhada por todas as instâncias de `LineItem`.
+
+
+==== Evitando repetir o nome do atributo gerenciado
+
+Um inconveniente do <> é a necessidade de repetir os nomes dos atributos quando os descritores são instanciados no corpo da classe gerenciada. Seria bom se a classe `LineItem` pudesse ser declarada assim:
+
+[source, python]
+----
+class LineItem:
+    weight = Quantity()
+    price = Quantity()
+
+    # o restante dos métodos permanece igual
+----
+
+Da forma como está escrito, o <> exige nomear explicitamente cada `Quantity`, algo não apenas inconveniente, mas também perigoso. Se um programador, ao copiar e colar código, se esquecer de editar os dois nomes, e terminar com uma linha como `price = Quantity('weight')`, o programa vai se comportar de forma muito errática, sobrescrevendo o valor de `weight` sempre que `price` for definido.
+
+O problema é que—como vimos no <>—o lado direito de uma atribuição é executado antes da variável existir. A expressão `Quantity()` é avaliada para criar uma instância do descritor, e não há como o código na classe `Quantity` adivinhar o nome da variável à qual o descritor será vinculado (por exemplo, `weight` ou `price`).
+
+Felizmente, o protocolo de descritor agora suporta o método `+__set_name__+`. Veremos a seguir como usá-lo.
+
+[NOTE]
+====
+Nomear automaticamente o atributo de armazenamento de um descritor era uma tarefa espinhosa. Na primeira edição do _Python Fluente_, dediquei várias páginas e muitas linhas de código neste capítulo e no seguinte para apresentar diferentes soluções, incluindo o uso de um decorador de classe e depois metaclasses (no <>).
+Tudo isso ficou mais simples no Python 3.6.
+====
+
+[[auto_storage_sec]]
+==== LineItem versão #4: Nomeando atributos de armazenamento automaticamente
+
+Para não ter que repetir o nome do atributo ao criar uma instância de descritor, vamos implementar
+`+__set_name__+`, para definir o `storage_name` de cada instância de `Quantity`. O método especial
+`+__set_name__+` foi acrescentado ao protocolo de descritor no Python 3.6.
+
+O interpretador invoca `+__set_name__+` em cada descritor encontrado no corpo de uma `class`—se o descritor implementar esse método.footnote:[Mais precisamente, `+__set_name__+` é invocado por `+type.__new__+`—o construtor de objetos que representam classes. A classe embutida `type` é na verdade uma metaclasse, a classe default de classes definidas pelo usuário. Isso é um pouco difícil de entender de início, mas fique tranquila: o <> é dedicado à configuração dinâmica de classes, incluindo o conceito de metaclasses.]
+
+No <>, a classe descritora `Quantity` não precisa de um `+__init__+`.
+Em vez disso, `+__set_item__+` armazena o nome do atributo de armazenamento.
+
+[[lineitem_class_v4]]
+.bulkfood_v4.py: `+__set_name__+` define o nome para cada instância do descritor `Quantity`
+====
+[source, python]
+----
+include::../code/23-descriptor/bulkfood/bulkfood_v4.py[tags=LINEITEM_V4]
+----
+====
+<1> `self` é a instância do descritor (não a instância gerenciada), `owner` é a classe gerenciada e `name` é o nome do atributo de `owner` ao qual essa instância do descritor foi atribuída no corpo da classe de `owner`.
+<2> Isso é o que o `+__init__+` fazia no <>.
+<3> O método `+__set__+` aqui é exatamente igual ao do <>.
+<4> Não é necessário implementar `+__get__+`, porque o nome do atributo de armazenamento é igual ao nome do atributo gerenciado. A expressão `product.price` obtém o atributo `price` diretamente da instância de `LineItem`.
+<5> Não é necessário passar o nome do atributo gerenciado para o construtor de `Quantity`. Esse era o objetivo dessa versão.
+
+Olhando para o <>, pode parecer muito código só para gerenciar um par de atributos,
+mas é importante perceber que agora abstraímos a lógica do descritor em uma unidade de código separada e reutilizável: a classe `Quantity`.
+Normalmente não definimos um descritor no mesmo módulo em que ele é usado,
+mas em um módulo utilitário separado,
+para ser usado por toda a aplicação—ou mesmo por muitas aplicações,
+se estivermos desenvolvendo uma biblioteca ou um framework.
+
+Tendo isso em mente, o <> representa melhor o uso típico de um descritor.
+
+[[lineitem_class_v4c]]
+.bulkfood_v4c.py: uma definição mais limpa de `LineItem`; a classe descritora `Quantity` agora reside no módulo importado `model_v4c`
+====
+[source, python]
+----
+include::../code/23-descriptor/bulkfood/bulkfood_v4c.py[tags=LINEITEM_V4C]
+----
+====
+<1> Importa o módulo `model_v4c`, onde `Quantity` é implementada.
+<2> Coloca `model.Quantity` em uso.
+
+Usuários do Django vão perceber que o <> se parece muito com uma definição de modelo. Isso não é uma coincidência: os campos de modelos Django são descritores.
+
+Já que descritores são implementados como classes, podemos aproveitar a herança para reutilizar parte do código que já temos em novos descritores. É o que faremos na próxima seção.
+
+[[new_descr_type_sec]]
+==== LineItem versão #5: um novo tipo descritor
+
+A loja imaginária de comida orgânica encontra um problema: de alguma forma, uma
+instância de um produto foi criada com uma descrição vazia, e o pedido não pode
+ser processado. Para prevenir isso, criaremos um novo descritor: `NonBlank`. Ao
+projetar `NonBlank`, percebemos que ele será muito parecido com o descritor
+`Quantity`, exceto pela lógica de validação.
+
+Isto sugere uma refatoração, resultando em `Validated`, uma classe abstrata que sobrescreve um método
+`+__set__+`, invocando o método `validate`, que precisa ser implementado por subclasses.
+
+Vamos então reescrever `Quantity` e implementar `NonBlank`, herdando de `Validated` e programando apenas os métodos `validate`.
+
+A relação entre `Validated`, `Quantity` e `NonBlank` é uma aplicação do padrão _Template Method_ (Método Gabarito),
+como descrito no clássico _Design Patterns_:
+
+[quote]
+____
+O Método Gabarito define um algoritmo em termos de operações abstratas que subclasses sobrescrevem para fornecer o comportamento concreto.footnote:[Gamma et al., _Design Patterns: Elements of Reusable Object-Oriented Software_, p. 326.]
+____
+
+No <>, `+Validated.__set__+` é um método gabarito e `self.validate` é a operação abstrata.
+
+[[model_v5_abc]]
+.model_v5.py: the `Validated` ABC
+====
+[source, python]
+----
+include::../code/23-descriptor/bulkfood/model_v5.py[tags=MODEL_V5_VALIDATED_ABC]
+----
+====
+<1> `+__set__+` delega a validação para o método `validate`...
+<2> ...e então usa o `value` devolvido para atualizar o valor armazenado.
+<3> `validate` é um método abstrato; este é o método que "preenche" gabarito `+__set__+`, definindo parte do seu comportamento.
+
+Alex Martelli prefere chamar este padrão de projeto _Self-Delegation_ (Auto-Delegação),
+e concordo que é um nome mais descritivo: a primeira linha de `+__set__+` auto-delega para
+`validate`, ou seja, invoca outro método na mesma instância de uma subclasse concreta de `Validated`.footnote:[Slide #50 da palestra https://fpy.li/23-1[_Python Design Patterns_], de Alex Martelli. Altamente recomendada.]
+
+As subclasses concretas de `Validated` neste exemplo são `Quantity` e `NonBlank`, apresentadas no <>.
+
+[[model_v5_sub]]
+.model_v5.py: `Quantity` e `NonBlank`, subclasses concretas de `Validated`
+====
+[source, python]
+----
+include::../code/23-descriptor/bulkfood/model_v5.py[tags=MODEL_V5_VALIDATED_SUB]
+----
+====
+<1> Implementação exigida pelo método abstrato `Validated.validate`.
+<2> Se não sobrar nada após a remoção dos espaços em branco antes e depois do valor, este é rejeitado.
+<3> Exigir que os métodos `validate` concretos devolvam o valor validado dá a eles a oportunidade de limpar, converter ou normalizar os dados recebidos. Neste caso, `value` é devolvido sem espaços iniciais ou finais.
+
+Usuários de _model_v5.py_ não precisam saber todos esses detalhes. O que importa é poder usar `Quantity` e `NonBlank` para automatizar a validação de atributos de instância. Veja a última classe `LineItem` no <>.
+
+[[lineitem_class_v5]]
+.bulkfood_v5.py: `LineItem` usando os descritores `Quantity` e `NonBlank`
+====
+[source, python]
+----
+include::../code/23-descriptor/bulkfood/bulkfood_v5.py[tags=LINEITEM_V5]
+----
+====
+<1> Importa o módulo `model_v5`, dando a ele um nome amigável.
+<2> Usa `model.NonBlank`. O restante do código não foi modificado.
+
+Os exemplos de `LineItem` que vimos neste capítulo demonstram um uso típico de
+descritores para gerenciar atributos de dados. Descritores como `Quantity` são
+chamados _descritores dominantes_, pois seu método `+__set__+` sobrescreve (isto é,
+intercepta e impede) a atribuição de um atributo de instância com o mesmo nome na
+instância gerenciada. Entretanto, há também _descritores não dominantes_. Vamos
+explorar essa diferença detalhadamente na próxima seção.((("",
+startref="ADvalidation23")))((("", startref="ATvalidation23")))
+
+
+=== Descritores dominantes ou não dominantes
+
+Recordando((("attribute descriptors", "overriding versus nonoverriding",
+id="ADovervnon23")))((("nonoverriding descriptors",
+id="nonover23")))((("overriding descriptors", id="overrid23"))), há uma
+importante assimetria na forma como Python lida com atributos.
+Em uma classe sem descritores, ler um atributo
+através de uma instância devolve o atributo definido na instância.
+Mas se tal atributo não existir na instância, um atributo de classe será obtido.
+Por outro lado, uma atribuição a um atributo em uma instância cria o
+atributo na instância, sem afetar a classe de forma alguma.
+
+Esta assimetria também afeta descritores, criando duas grandes categorias de descritores, dependendo do método `+__set__+` estar ou não implementado.
+Se `+__set__+` estiver presente, a classe é um descritor dominante (_overriding descriptor_); caso contrário, ela é um descritor não dominante
+(_non-overriding descriptor_).
+Esses termos farão sentido quando examinarmos os comportamentos de descritores nos próximos exemplos.
+
+Usei algumas funções auxiliares definidas no <> para observar as diferentes categorias de descritores, 
+Sua lógica não é importante, mas elas são usadas nas invocações a `print_args` no <>.
+
+[[descriptorkinds_aux_ex]]
+.descriptorkinds.py: funções auxiliares para formatar e exibir objetos
+====
+[source, python]
+----
+include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_AUX]
+----
+====
+
+Agora vamos criar três classes de descritores, e uma classe `Managed` onde os descritores são instalados.
+
+[[descriptorkinds_ex]]
+.descriptorkinds.py: classes simples para estudar os comportamentos dominantes de descritores
+====
+[source, python]
+----
+include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS]
+----
+====
+<1> Uma classe descritora dominante com `+__get__+` e `+__set__+`.
+<2> A função `print_args` é chamada por todos os métodos do descritor neste exemplo.
+<3> Um descritor dominante sem um método `+__get__+`.
+<4> Nenhum método `+__set__+` aqui, então este é um descritor não dominante.
+<5> A classe gerenciada, usando uma instância de cada uma das classes descritoras.
+<6> O método `spam` está aqui para efeito de comparação, pois métodos também são descritores.
+
+Nas próximas seções, examinaremos o comportamento de leitura e escrita de atributos na classe `Managed` e em uma de suas instâncias, passando por cada um dos diferentes descritores definidos.
+
+[[overriding_descriptor_sec]]
+==== Descritor dominante
+
+Um descritor que implementa o método `+__set__+` é um _descritor dominante_ pois, apesar de ser um atributo de classe, um descritor que implementa `+__set__+` irá  sobrescrever tentativas de atribuição a atributos de instância. É assim que o <> foi implementado. Propriedades também são descritores dominantes: se você não fornecer uma função _setter_, o `+__set__+` default da classe `property` vai gerar um `AttributeError`, para sinalizar que o atributo é somente para leitura.
+
+[WARNING]
+====
+Contribuidores e autores da comunidade Python usam termos diferentes ao discutir esses conceitos. Adotei "descritor dominante" (_overriding descriptor_), do livro _Python in a Nutshell_.
+A documentação oficial de Python usa "descritor de dados" (_data descriptor_) mas "descritor dominante" destaca o comportamento especial.
+Descritores dominantes também são chamados "descritores forçados" (_enforced descriptors_).
+Sinônimos para descritores não dominantes incluem "descritores sem dados" (_nondata descriptors_, na documentação oficial em português) ou "descritores ocultáveis" (_shadowable descriptors_).
+====
+
+Dado o código no <>, alguns experimentos com um descritor dominante podem ser vistos no <>.
+
+[[descriptorkinds_demo1]]
+.O comportamento de um descritor dominante
+====
+[source, python]
+----
+include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO1]
+----
+====
+<1> Cria o objeto `Managed`, para testes.
+<2> `obj.over` aciona o método `+__get__+` do descritor, passando a instância gerenciada `obj` como segundo argumento.
+<3> `Managed.over` aciona o método `+__get__+` do descritor, passando `None` como segundo argumento (`instance`).
+<4> Atribuir a `obj.over` aciona o método `+__set__+` do descritor, passando o valor `7` como último argumento.
+<5> Ler `obj.over` ainda invoca o método `+__get__+` do descritor.
+<6> Contorna o descritor, definindo um valor diretamente no `+obj.__dict__+`.
+<7> Verifica se aquele valor está no `+obj.__dict__+`, sob a chave `over`.
+<8> Entretanto, mesmo com um atributo de instância chamado `over`, o descritor `Managed.over` continua interceptando tentativas de ler `obj.over`.
+
+==== Descritor dominante sem `+__get__+`
+
+Propriedades e outros descritores dominantes, tal como os campos de modelo do Django, implementam tanto `+__set__+` quanto `+__get__+`. Mas também é possível implementar apenas `+__set__+`, como vimos no <>. Neste caso, apenas a escrita é controlada pelo descritor. Ler o descritor através de uma instância irá devolver o próprio objeto descritor, pois não há um
+`+__get__+` para tratar daquele acesso. Se um atributo de instância de mesmo nome for criado com um novo valor, através de acesso direto ao `+__dict__+` da instância, o método `+__set__+` continuará interceptando tentativas posteriores de definir aquele atributo, mas a leitura do atributo vai simplesmente devolver o novo valor na instância, em vez de devolver o objeto descritor. Em outras palavras, o atributo de instância vai ocultar o descritor, mas apenas na leitura. Veja o <>.
+
+[[descriptorkinds_demo2]]
+.Descritor dominante sem `+__get__+`
+====
+[source, python]
+----
+include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO2]
+----
+====
+<1> Este descritor dominante não tem um método `+__get__+`, então ler `obj.over_no_get` obtém a instância do descritor a partir da classe.
+<2> A mesma coisa acontece se obtivermos a instância do descritor diretamente da classe gerenciada.
+<3> Tentar definir um valor para `obj.over_no_get` invoca o método `+__set__+` do descritor.
+<4> Como nosso `+__set__+` não faz modificações, ler `obj.over_no_get` obtém a instância do descritor na classe gerenciada.
+<5> Definindo um atributo de instância chamado `over_no_get` direto no `+__dict__+` da instância.
+<6> Agora aquele atributo de instância `over_no_get` oculta o descritor, mas só na leitura.
+<7> Tentar atribuir um valor a `obj.over_no_get` continua passando pelo `+__set__+` do descritor.
+<8> Mas na leitura, aquele descritor é ocultado enquanto existir um atributo de instância de mesmo nome.
+
+
+==== Descritor não dominante
+
+Um descritor que não implementa `+__set__+` é um descritor não dominante. Definir um atributo de instância com o mesmo nome vai ocultar o descritor, tornando-o incapaz de tratar aquele atributo naquela instância específica. Métodos e a `@functools.cached_property` são implementados como descritores não dominantes. O <> mostra o comportamento de um descritor não dominante.
+
+[[descriptorkinds_demo3]]
+.Comportamento de um descritor não dominante
+====
+[source, python]
+----
+include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO3]
+----
+====
+<1> `obj.non_over` aciona o método `+__get__+` do descritor, passando `obj` como segundo argumento.
+<2> `Managed.non_over` é um descritor não dominante, então não há um `+__set__+` para interferir com essa atribuição.
+<3> O `obj` agora tem um atributo de instância chamado `non_over`, que oculta o atributo do descritor de mesmo nome na classe `Managed`.
+<4> O descritor `Managed.non_over` ainda está lá, e intercepta esse acesso através da classe.
+<5> Se o atributo de instância `non_over` for excluído...
+<6> ...então ler `obj.non_over` encontra o método `+__get__+` do descritor; mas observe que o segundo argumento é a instância gerenciada.
+
+Nos exemplos anteriores, vimos várias atribuições a um atributo de instância com nome igual ao do descritor, com resultados diferentes dependendo da presença ou não de um método `+__set__+` no descritor.
+
+A definição de atributos na classe não pode ser controlada por descritores ligados à mesma classe.
+Em especial, isso significa que os próprios atributos do descritor podem ser danificados por atribuições à classe, como explicado na próxima seção.
+
+
+==== Sobrescrevendo um descritor em uma classe
+
+Independente de o descritor ser dominante ou não, ele pode ser sobrescrito por uma atribuição à classe. Isso é uma((("monkey-patching"))) técnica de _monkey-patching_ mas, no <>, os descritores são substituídos por números inteiros, algo que certamente quebraria a lógica de qualquer classe que dependesse dos descritores para seu funcionamento correto.
+
+[[descriptorkinds_demo4]]
+.Qualquer descritor pode ser sobrescrito na própria classe
+====
+[source, python]
+----
+include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO4]
+----
+====
+<1> Cria uma nova instância para testes posteriores.
+<2> Sobrescreve os atributos dos descritores na classe.
+<3> Os descritores realmente desapareceram.
+
+O <> expõe outra assimetria entre a leitura e a escrita de atributos: apesar da leitura de um atributo de classe poder ser controlada por um `+__get__+` de um descritor ligado à classe gerenciada, a escrita em um atributo de classe não pode ser tratada por um `+__set__+` de um descritor ligado à mesma classe.
+
+[TIP]
+====
+Para controlar a escrita a atributos em uma classe, é preciso associar descritores à classe da classe—em outras palavras, à metaclasse. Por default, a metaclasse de classes definidas pelo usuário é `type`, e não podemos acrescentar atributos a `type`. Mas, no <>, vamos criar nossas próprias metaclasses.
+====
+
+Vamos ver agora como descritores são usados para implementar métodos no Python.((("", startref="ADovervnon23")))((("", startref="nonover23")))((("", startref="overrid23")))
+
+[[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 (_bound method_) 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]]
+.Um método é um descritor não dominante
+====
+[source, python]
+----
+include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO5]
+----
+====
+<1> Ler de `obj.spam` obtém um objeto método vinculado.
+<2> Mas ler de `Managed.spam` obtém uma função.
+<3> Atribuir um valor a `obj.spam` oculta o atributo de classe, tornando o método `spam` inacessível a partir da instância `obj`.
+
+Funções não implementam `+__set__+`, portanto não são descritores dominantes, como mostra a última linha do <>.
+
+A outra lição fundamental do <> é que `obj.spam` e `Managed.spam` devolvem objetos diferentes. Como de hábito com descritores, o `+__get__+` de uma função devolve uma referência para a própria função quando o acesso ocorre através da classe gerenciada. Mas quando o acesso vem através da instância, o `+__get__+` da função devolve um método vinculado: um invocável que embrulha a função e vincula a instância gerenciada (no exemplo, `obj`) ao primeiro argumento da função (isto é, `self`), como faz a função `functools.partial` (que vimos na <>).
+Para um entendimento mais profundo deste mecanismo, dê uma olhada no <>.
+
+[[func_descriptor_ex]]
+.method_is_descriptor.py: uma classe `Text`, derivada de `UserString`
+====
+[source, python]
+----
+include::../code/23-descriptor/method_is_descriptor.py[tags=FUNC_DESCRIPTOR_EX]
+----
+====
+
+Vamos então investigar o método `Text.reverse`. Veja o <>.
+
+[[func_descriptor_demo]]
+.Experimentos com um método
+====
+[source, python]
+----
+include::../code/23-descriptor/method_is_descriptor.py[tags=FUNC_DESCRIPTOR_DEMO]
+----
+====
+
+<1> Seguindo a convenção, o `repr` de uma instância de `Text` parece uma chamada ao construtor
+de `Text` que criaria uma instância igual.
+
+<2> O método `reverse` devolve o texto escrito de trás para frente.
+
+<3> Um método invocado na classe funciona como uma função.
+
+<4> Observe os tipos diferentes: `function` e `method`.
+
+<5> `Text.reverse` opera como uma função, mesmo ao trabalhar com objetos que não
+são instâncias de `Text`.
+
+<6> Toda função é um descritor não dominante. Invocar seu `+__get__+` com uma
+instância obtém um método vinculado àquela instância.
+
+<7> Invocar o `+__get__+` da função com `None` como argumento `instance` obtém a
+própria função.
+
+<8> A expressão `word.reverse` invoca `+Text.reverse.__get__(word)+`,
+devolvendo o método vinculado, mas sem invocá-lo.
+
+<9> O objeto método vinculado tem um atributo `+__self__+`, contendo uma
+referência à instância na qual o método foi invocado.
+
+<10> O atributo `+__func__+` do método vinculado é uma referência à função
+original, implementada na classe gerenciada.
+
+O método vinculado contém um método `+__call__+`, que trata a invocação em si. Este método chama a função original, referenciada em `+__func__+`, passando o atributo `+__self__+` do método como primeiro argumento. É assim que funciona a vinculação implícita do argumento `self` convencional.
+
+A conversão de funções em métodos vinculados é um exemplo perfeito de como descritores são usados na infraestrutura da linguagem.
+
+Após este mergulho profundo no funcionamento de descritores e métodos, vamos repassar alguns conselhos práticos sobre seu uso.((("", startref="ADmethodsas23")))
+
+[[descriptor_usage_sec]]
+=== Dicas para usar descritores
+
+A((("attribute descriptors", "descriptor usage tips", id="ADtips23"))) lista a seguir trata de algumas consequências práticas das características dos descritores descritas acima:
+
+Use `property` para não complicar demais:: A classe embutida `property` cria descritores dominantes, implementando `+__set__+` e `+__get__+`, mesmo se um método _setter_ não for definido.footnote:[Um método `+__delete__+` também é fornecido pelo decorador `property`, mesmo se você não definir um método _deleter_ (de exclusão).]
+O `+__set__+` default de uma propriedade gera um `AttributeError: can't set attribute` (não é permitido setar o atributo), então uma propriedade é a forma mais fácil de criar um atributo somente para leitura, evitando o problema descrito a seguir.
+
+Descritores somente para leitura exigem um `+__set__+`:: Se você usar uma classe descritora para implementar um atributo somente para leitura, precisa lembrar de programar tanto `+__get__+` quanto
+`+__set__+`. Caso contrário, você terá um descritor não-dominante, e setar um atributo com o mesmo nome em uma instância vai ocultar o descritor. O método `+__set__+` de um atributo somente para leitura deve apenas levantar `AttributeError` com uma mensagem adequada.footnote:[Python não é consistente nestas mensagens. Tentar modificar o atributo `c.real` de um número `complex` resulta em um `AttributeError: readonly attribute` (atributo somente para leitura), mas uma tentativa de mudar
+`c.conjugate` (um método de `complex`) levanta um `AttributeError: 'complex' object attribute 'conjugate' is read-only` (o atributo 'conjugate' do objeto 'complex' é somente para leitura). Até "read-only" está escrito de maneira diferente nas mensagens em inglês).]
+
+Descritores de validação podem funcionar apenas com `+__set__+`:: Em um descritor projetado apenas para validação, o método `+__set__+` deve verificar o argumento `value` recebido e, se ele for válido, atualizar o `+__dict__+` da instância diretamente, usando o nome da instância do descritor como chave. Dessa forma, ler o atributo de mesmo nome a partir da instância será tão rápido quanto possível, pois não vai precisar de um `+__get__+`. Veja o código no <>.
+
+_Caching_ pode ser feito eficientemente apenas com `+__get__+`:: Se você escrever apenas o método `+__get__+`, cria um descritor não dominante.
+Eles são úteis para executar alguma computação custosa e então armazenar o resultado, definindo um atributo com o mesmo nome na instânciafootnote:[Entretanto, lembre-se de que criar atributos de instância após o método `+__init__+` frustra a otimização de memória através de compartilhamento de chaves, como discutido na <>.].
+O atributo de mesmo nome na instância vai ocultar o descritor, daí acessos subsequentes àquele atributo vão buscá-lo diretamente no `+__dict__+` da instância, sem acionar mais o `+__get__+` do descritor.
+O decorador `@functools.cached_property` constrói um descritor não dominante.
+
+Métodos não especiais podem 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 `meu_obj.o_método = 7`, significa que acessos posteriores a `o_método` através daquela instância irão obter o número ++7++—sem afetar a classe ou outras instâncias. Isto se aplica aos métodos especiais. O interpretador só acessa 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 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 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.
+
+[NOTE]
+====
+A classe `FrozenJSON` no <> está a salvo de atributos de instância ocultando métodos, pois seus únicos métodos são métodos especiais e o método de classe `build`. Métodos de classe são seguros desde que sejam sempre acessados através da classe, como fiz com `FrozenJSON.build` no <>—mais tarde substituído por `+__new__+` no <>. As classes `Record` e `Event`, apresentadas na <>, também estão a salvo: elas implementam apenas métodos especiais, métodos estáticos e propriedades. Propriedades são descritores dominantes, então não são ocultadas por atributos de instância.
+====
+
+Para encerrar esse capítulo, vamos falar de dois recursos que vimos com as propriedades, mas não no contexto dos descritores: documentação e o tratamento de tentativas de excluir um atributo gerenciado.((("", startref="ADtips23")))
+
+[[descriptor_doc_del_sec]]
+=== Docstrings e exclusão de descritores
+
+A((("attribute descriptors", "descriptor docstring and overriding deletion"))) docstring de uma classe descritora é usada para documentar todas as instâncias do descritor na classe gerenciada.
+A <> mostra as telas de ajuda para a classe `LineItem` com os descritores `Quantity` e `NonBlank`, do
+<> e do <>.
+
+Isso é um tanto insatisfatório. No caso de `LineItem`, seria bom acrescentar, por exemplo, a informação de que `weight` deve ser expresso em quilogramas. Isso seria trivial com propriedades, pois cada propriedade controla um atributo gerenciado específico. Mas com descritores, a mesma classe descritora `Quantity` é usada para `weight` e `price`.footnote:[Customizar o texto de ajuda para cada instância do descritor é surpreendentemente difícil. Uma solução exige criar dinamicamente uma classe invólucro (_wrapper_) para cada instância do descritor.]
+
+O segundo detalhe que discutimos com propriedades, mas não com descritores, é o
+tratamento de tentativas de apagar um atributo gerenciado. Isso pode ser feito
+implementando um método `+__delete__+` na classe descritora. Omiti
+deliberadamente falar de `+__delete__+`, porque seu uso no mundo
+real é raro. Se você precisar disso, por favor consulte a seção
+https://fpy.li/c7[«Implementando descritores»] na documentação do
+_Modelo de dados de Python_. Escrever um exemplo com uma classe descritora
+boba com `+__delete__+` fica como exercício para o leitor com excesso de tempo livre.
+
+[[descriptor_help_screens]]
+.Capturas de tela do console de Python após os comandos `help(LineItem.weight)` e `help(LineItem)`.
+image::../images/flpy_2304.png[Capturas de tela do console de Python com a ajuda de descritores.]
+
+=== Resumo do capítulo
+
+O((("attribute descriptors", "overview of"))) primeiro exemplo deste capítulo foi uma continuação dos exemplos `LineItem` do <>. No <>, substituímos propriedades por descritores. Vimos que um descritor é uma classe que fornece instâncias instaladas como atributos na classe gerenciada. Discutir esse mecanismo exigiu uma terminologia especial, apresentando termos como _instância gerenciada_ e _atributo de armazenamento_.
+
+Na <>, removemos a exigência de descritores `Quantity` serem
+instanciados com um `storage_name` explícito. A solução foi implementar o método especial `+__set_name__+` em
+`Quantity`, para preservar o nome da propriedade gerenciada como
+`self.storage_name`.
+
+A <> mostrou como criar uma subclasse de uma classe descritora abstrata, para compartilhar código ao programar descritores especializados com alguma funcionalidade em comum.
+
+Examinamos então os comportamentos diferentes de descritores, fornecendo ou omitindo o método
+`+__set__+`, criando uma distinção fundamental entre descritores dominantes e não dominantes. Por meio de testes detalhados, revelamos quando os descritores estão no controle, e quando são ocultados, contornados ou sobrescritos.
+
+Em seguida, estudamos uma categoria específica de descritores não dominantes: métodos.
+Experimentos no console revelaram como uma função associada a uma classe se torna um método ao ser acessada através de uma instância, graças ao protocolo de descritores.
+
+Para concluir o capítulo, a <> trouxe dicas práticas, e a <> forneceu um rápido olhar sobre como documentar descritores.
+
+[NOTE]
+====
+Como observado na <>, vários exemplos deste capítulo se tornaram mais simples graças ao método especial `+__set_name__+` do protocolo de descritor, adicionado no Python 3.6. Isso é evolução da linguagem!
+====
+
+=== Para saber mais
+
+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[«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 do Python), tratando com profundidade de propriedades e descritores: https://fpy.li/23-5[«slides»] e https://fpy.li/23-6[«video»].
+
+[WARNING]
+====
+Cuidado, qualquer tratamento de descritores escrito ou gravado antes da PEP 487 ser adotada, em 2016, corre o risco de conter exemplos desnecessariamente complicados hoje, pois `+__set_name__+` não era suportado nas versões de Python anteriores a 3.6.
+====
+
+Para mais exemplos práticos, o _Python Cookbook_, 3ª ed., de David Beazley e
+Brian K. Jones (O’Reilly), traz muitas receitas ilustrando descritores, entre
+as quais destaco
+_6.12. Reading Nested and Variable-Sized Binary Structures_
+(Lendo Estruturas Binárias Aninhadas e de Tamanho Variável),
+_8.10. Using Lazily Computed Properties_
+(Usando Propriedades Computadas de Forma Preguiçosa),
+_8.13. Implementing a Data Model or Type System_
+(Implementando um Modelo de Dados ou um Sistema de Tipos) e
+_9.9. Defining Decorators As Classes_ 
+(Definindo Decoradores como Classes).
+Essa última receita trata das questões profundas envolvidas na interação entre
+decoradores de função, descritores e métodos, e de como um decorador de função
+implementado como uma classe, com `+__call__+`, também precisa implementar
+`+__get__+` se quiser funcionar com métodos.
+
+A https://fpy.li/pep487[_PEP 487—Simpler customization of class creation_]
+(Customização simplificada da criação de classes)
+introduziu o método especial `+__set_name__+` e inclui um exemplo de um
+https://fpy.li/23-7[«descritor de validação»].
+
+//[role="pagebreak-before"]
+.Ponto de vista
+****
+
+
+*O design do parâmetro `self`*
+
+A((("attribute descriptors", "Soapbox discussion")))((("Soapbox sidebars",
+"explicit self argument")))((("explicit self argument"))) exigência de declarar
+`self` explicitamente como o primeiro parâmetro em métodos foi uma decisão de
+design controversa no Python. Eu me acostumei em menos de 20 anos!
+Acho que essa decisão é um exemplo de "pior é melhor"
+(_worse is better_): a filosofia de design descrita pelo cientista da
+computação Richard P. Gabriel em
+https://fpy.li/23-8[_The Rise of Worse is Better_]
+(A Ascensão do Pior é Melhor). A primeira prioridade dessa filosofia
+é "simplicidade", que Gabriel apresenta assim:
+
+[quote]
+____
+O design deve ser simples, tanto na implementação quanto na interface.
+É mais importante simplificar a implementação do que a interface.
+A simplicidade é a consideração mais importante em um design.
+____
+
+O `self` explícito de Python incorpora esta filosofia de design.
+A implementação é simples—até mesmo elegante—em prejuízo da usabilidade:
+uma assinatura de método como `def zfill(self, width):` não corresponde, visualmente, à invocação `label.zfill(8)`.
+
+A linguagem Modula-3, que Guido estudou antes de inventar o Python, introduziu esta convenção com o mesmo identificador, `self`.
+Mas há uma diferença crucial: em Modula-3, interfaces são declaradas separadamente de sua implementação,
+e na declaração da interface o argumento `self` é omitido.
+Então, da perspectiva do usuário, um método aparece em uma declaração de interface com a mesma quantidade de parâmetros necessários para invocá-lo.
+
+Ao longo do tempo, as mensagens de erro de Python relacionadas a argumentos de métodos se tornaram mais claras.
+Em um método definido pelo usuário com um argumento além de `self`, se o usuário invocasse
+`obj.meth()`, Python 2.7 gerava:
+
+[source]
+----
+TypeError: meth() takes exactly 2 arguments (1 given)
+(meth() recebe exatamente 2 argumentos (1 passado))
+----
+
+No Python 3, a confusa contagem de argumentos não é mencionada, mas o argumento ausente é nomeado:
+
+[source]
+----
+TypeError: meth() missing 1 required positional argument: 'x'
+(1 argumento posicional obrigatório faltando em meth(): 'x')
+----
+
+Além do uso de `self` como um argumento explícito, a exigência de qualificar
+cada acesso a atributos de instância com `self` também é criticada. Veja, por
+exemplo, o famoso post _Python Warts_ (Verrugas de Python) de A. M.
+Kuchling (https://fpy.li/23-9[«cópia arquivada»] no _Internet Archive_);
+o próprio Kuchling não se incomoda
+muito com o qualificador `self`, mas ele o menciona—provavelmente ecoando
+opiniões do grupo _comp.lang.python_. Pessoalmente não me importo em digitar o
+qualificador `self`: é bom para distinguir variáveis locais de atributos. Minha
+questão é com o uso de `self` na instrução `def`.
+
+Quem estiver triste com o `self` explícito de Python pode se sentir bem melhor após considerar a
+https://fpy.li/23-10[«semântica desconcertante»] do `this` implícito em JavaScript.
+Guido teve boas razões para fazer `self` funcionar como funciona, e ele escreveu sobre elas em
+https://fpy.li/23-11[_Adding Support for User-Defined Classes_]
+(Adicionando Suporte a Classes Definidas pelo Usuário), 
+em seu blog, _The History of Python_ (A História de Python).
+****
+
diff --git a/online/cap24.adoc b/online/cap24.adoc
new file mode 100644
index 00000000..b7f7778e
--- /dev/null
+++ b/online/cap24.adoc
@@ -0,0 +1,1834 @@
+[[ch_class_metaprog]]
+== Metaprogramação de classes
+:example-number: 0
+:figure-number: 0
+
+[quote, Brian W. Kernighan e P. J. Plauger, The Elements of Programming Style]
+____
+Todo mundo sabe que depurar um programa é duas vezes mais difícil que escrever o mesmo programa.
+Mas daí, se você der tudo de si ao escrever o programa, como vai conseguir depurá-lo?footnote:[Citação extraída do capítulo 2, _Expression_ ("Expressão"), página 10, de _The Elements of Programming Style, Second Edition (NT: "Elementos de Estilo de Programação"; não encontramos edição traduzida deste livro.)]
+____
+
+Metaprogramação((("class metaprogramming", "benefits and drawbacks of"))) de classes é a arte de criar ou customizar classes durante a execução do programa.
+Em Python, classes são objetos de primeira classe, então uma função pode criar uma nova classe a qualquer momento, sem usar a palavra-chave `class`,
+e sem manipular código-fonte ou bytecodes.
+
+Decoradores de classes também são funções, mas servem para inspecionar, modificar ou até substituir a classe decorada por outra classe.
+Por fim, metaclasses são a ferramenta mais avançada para metaprogramação de classes: elas permitem a criação de categorias de classes inteiramente novas, com características especiais, como as classes base abstratas, que já vimos anteriormente.
+
+Metaclasses são poderosas, mas difíceis de justificar na prática, e ainda mais difíceis de entender direito. Decoradores de classe resolvem muitos dos mesmos problemas, e são mais fáceis de compreender. Além disso, Python 3.6 implementou a
+https://fpy.li/pep487[_PEP 487—Simpler customization of class creation_]
+(Customização simplificada da criação de classes), fornecendo métodos especiais para tarefas que antes exigiam metaclasses ou decoradores de classe.footnote:[Isso não quer dizer que a PEP 487 quebrou código que usava aqueles recursos, mas apenas que parte do código que utilizava decoradores de classe ou metaclasses antes de Python 3.6 pode agora ser refatorado para usar classes comuns, resultando em um código mais simples e possivelmente mais eficiente.]
+
+Este capítulo apresenta as técnicas de metaprogramação de classes em ordem ascendente de complexidade.
+
+
+[WARNING]
+====
+Esse é um tópico empolgante, e é fácil se deixar levar pelo entusiasmo.
+Então preciso deixar aqui esse conselho.
+
+Em nome da legibilidade e facilidade de manutenção, você provavelmente deveria evitar as técnicas descritas neste capítulo em aplicações.
+
+Por outro lado, caso queira escrever o próximo grande framework de Python, estas são suas ferramentas de trabalho.
+====
+
+=== Novidades neste capítulo
+
+Todo((("class metaprogramming", "significant changes to"))) o código do capítulo _Metaprogramação de Classes_ da primeira edição do _Python Fluente_ ainda funciona corretamente.
+Entretanto, alguns dos exemplos antigos podem ser bastante simplificados com recursos surgidos desde o Python 3.6.
+
+Substituí aqueles exemplos por outros, enfatizando os novos recursos de metaprogramação ou acrescentando novos requisitos para justificar o uso de técnicas mais avançadas.
+Alguns destes exemplos novos se valem de dicas de tipo para fornecer fábricas de classes similares ao decorador `@dataclass` e a `typing.NamedTuple`.
+
+A <> é nova, trazendo algumas considerações de alto nível sobre a aplicabilidade das metaclasses.
+
+[TIP]
+====
+Algumas das melhores refatorações tratam de remover código tornado redundante por formas modernas e mais simples de resolver o mesmo problema.
+Isso se aplica tanto a código em produção quanto 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_sec]]
+=== Classes como objetos
+
+Como((("class metaprogramming", "classes as objects"))) acontece com a maioria
+dos elementos da linguagem Python, classes também são objetos. Toda classe
+tem alguns atributos definidos no _Modelo de Dados_ de Python, documentados na
+seção https://fpy.li/bt[_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__+`, e `+__mro__+`. Outros atributos de classe padrão são:
+
+`+cls.__bases__+`:: A((("cls.__bases__"))) tupla de classes base da classe.
+
+`+cls.__qualname__+`:: O((("cls.__qualname__"))) nome qualificado de uma classe ou função, que é um caminho pontuado, desde o escopo global do módulo até a definição da classe. Isso é relevante quando a classe é definida dentro de outra classe.
+Por exemplo, na classe https://fpy.li/24-2[`Ox`] que ilustra um modelo na da documentação do Django, há uma classe aninhada chamada `Meta`. O `+__qualname__+` de `Meta` é `Ox.Meta`, mas seu `+__name__+` é apenas `Meta`.
+A especificação para este atributo está na
+https://fpy.li/24-3[_PEP 3155—Qualified name for classes and functions_] (Nome qualificado para classes e funções).
+
+`+cls.__subclasses__()+`:: Este((("cls.__subclasses__()"))) método devolve uma lista das subclasses imediatas da classe.
+A implementação usa referências fracas, para evitar referências circulares entre a superclasse e suas subclasses.
+Cada subclasse tem referências fortes para suas superclasses em seu atributo `+__bases__+`.
+O método lista as subclasses na memória naquele momento. Subclasses em módulos ainda não importados não aparecerão no resultado.
+
+`cls.mro()`:: O((("cls.mro()"))) interpretador invoca este método quando está criando uma classe, para obter a tupla de superclasses armazenada no atributo `+__mro__+` da classe.
+Uma metaclasse pode  sobrescrever este método, para customizar a ordem de resolução de métodos da classe em construção.
+
+[TIP]
+====
+Nenhum dos atributos mencionados nesta seção aparece na lista devolvida pela função `dir(…)`.
+====
+
+Agora, se classe é um objeto, o que é a classe de uma classe?
+
+
+=== `type`: a fábrica de classes embutida
+
+Normalmente((("class metaprogramming", "built-in class factory")))
+pensamos em `type` como uma função que devolve a classe de um objeto,
+porque é isso que `type(my_object)` faz: devolve `+my_object.__class__+`.
+
+Entretanto, `type` é uma classe que cria uma nova classe, quando invocada com três argumentos.
+
+Considere esta classe simples:
+
+[source, python]
+----
+class MyClass(MyMixin, MySuperClass):
+    x = 42
+
+    def x2(self):
+        return self.x * 2
+----
+
+Usando o construtor `type`, podemos criar uma classe idêntica durante a execução, com o seguinte código:
+
+[source, python]
+----
+MyClass = type('MyClass',
+               (MyMixin, MySuperClass),
+               {'x': 42, 'x2': lambda self: self.x * 2},
+          )
+----
+
+Quando Python lê uma instrução `class`, invoca `type` para construir um objeto classe assim:
+
+[source, python]
+----
+new_class = type(name, bases, cls_dict)
+----
+
+Onde os parâmetros são:
+
+`name`::
+    O identificador que aparece após a palavra-chave `class`, por exemplo, `MyClass`.
+`bases`::
+    A tupla de superclasses passada entre parênteses após o identificador da classe, ou
+    `(object,)`, caso nenhuma superclasse seja mencionada na instrução `class`.
+`cls_dict`::
+    Um mapeamento entre nomes de atributos e valores.
+    Invocáveis se tornam métodos, como vimos na <>.
+
+[NOTE]
+====
+
+O construtor `type` aceita argumentos nomeados opcionais, que são ignorados por
+`type`, mas são passados para `+__init_subclass__+`, que deve
+consumí-los. Vamos estudar esse método especial na
+<>, mas tratarei o uso de argumentos
+nomeados. Para saber mais sobre isso, por favor leia a
+https://fpy.li/pep487[_PEP 487_] sobre formas modernas de customizar a criação de classes.
+
+====
+
+A classe `type` é uma((("metaclasses", "definition of term"))) _metaclasse_: uma classe que cria classes.
+Em outras palavras, instâncias da classe `type` são classes.
+A biblioteca padrão contém algumas outras metaclasses, mas `type` é a  default:
+
+[source, python]
+----
+>>> type(7)
+
+>>> type(int)
+
+>>> type(OSError)
+
+>>> class Whatever:
+...     pass
+...
+>>> type(Whatever)
+
+----
+
+Vamos criar metaclasses customizadas na <>.
+
+Agora, vamos usar a classe embutida `type` para criar uma função que constrói classes.
+
+
+=== Uma função fábrica de classes
+
+A((("class metaprogramming", "class factory function", id="CMfacfun24"))) biblioteca padrão contém uma função fábrica de classes que já apareceu várias vezes aqui: `collections.namedtuple`.
+No <> também vimos `typing.NamedTuple` e `@dataclass`.
+Estas fábricas de classe usam técnicas que veremos neste capítulo.
+
+Vamos começar com uma fábrica muito simples, para classes de objetos mutáveis—a substituta mais simples possível de `@dataclass`.
+
+Suponha que eu esteja escrevendo uma aplicação para um _pet shop_,
+e queira armazenar dados sobre cães como registros simples.
+Mas não quero escrever código padronizado como esse:
+
+[source, python]
+----
+class Dog:
+    def __init__(self, name, weight, owner):
+        self.name = name
+        self.weight = weight
+        self.owner = owner
+----
+
+Tédio: `name`, `name`, `name`, `weight`, `weight`, `weight`... E toda essa repetição nem garante um bom `repr`:
+
+[source, python]
+----
+>>> rex = Dog('Rex', 30, 'Bob')
+>>> rex
+<__main__.Dog object at 0x2865bac>
+----
+
+Inspirados por `collections.namedtuple`, vamos criar uma `record_factory`,
+que cria classes simples como `Dog` dinamicamente. O <> mostra como ela deve funcionar.
+
+[[record_factory_demo]]
+.Testando `record_factory`, uma fábrica de classes simples
+====
+[source, python]
+----
+include::../code/24-class-metaprog/factories.py[tags=RECORD_FACTORY_DEMO]
+----
+====
+<1> A fábrica pode ser invocada como `namedtuple`: nome da classe, seguido de uma string contendo os nomes dos atributos separados por espaços.
+<2> Um `repr` agradável.
+<3> Instâncias são iteráveis, então elas podem ser convenientemente desempacotadas em uma atribuição...
+<4> ...ou quando são passadas para funções como `format`.
+<5> As instâncias são mutáveis.
+<6> A classe recém-criada herda de `object`—não tem qualquer relação com nossa fábrica.
+
+O código para `record_factory` está no <>.footnote:[Agradeço ao meu amigo J. S. O. Bueno por ter contribuído com esse exemplo.]
+
+[[record_factory_ex]]
+.record_factory.py: uma classe fábrica simples
+====
+[source, python]
+----
+include::../code/24-class-metaprog/factories.py[tags=RECORD_FACTORY]
+----
+====
+<1> O usuário pode fornecer os nomes dos campos como uma string única ou como um iterável de strings.
+<2> Aceita argumentos como os dois primeiros de `collections.namedtuple`; devolve `type`—isto é, uma classe.
+<3> Cria uma tupla de nomes de atributos; esse será o atributo `+__slots__+` da nova classe.
+<4> Esta função se tornará o método `+__init__+` na nova classe. Ela aceita argumentos posicionais e/ou nomeados.footnote:[Não acrescentei dicas de tipo aos argumentos porque os tipos reais são `Any`. Escrevi a dica do tipo devolvido (`None`) para que o Mypy não deixe de checar o método.]
+<5> Produz os valores dos campos na ordem dada por `+__slots__+`.
+<6> Produz um `repr` agradável, iterando sobre `+__slots__+` e `self`.
+<7> Monta um dicionário de atributos de classe.
+<8> Cria e devolve a nova classe, invocando o construtor de `type`.
+<9> Converte `names` separados por espaços ou vírgulas em uma lista de `str`.
+
+O <> é a primeira vez que vemos `type` em uma dica de tipo:
+indica que `record_factory` devolve uma classe.
+
+A última linha de `record_factory` no <> cria uma classe cujo
+nome é o valor de `cls_name`, com `object` como sua única classe base imediata,
+e um espaço de nomes (_namespace_) carregado com `+__slots__+` e os
+métodos de instância `+__init__+`, `+__iter__+`, e
+`+__repr__+`.
+
+Poderíamos ter dado qualquer outro nome ao atributo de classe `+__slots__+`, mas
+daí teríamos que implementar `+__setattr__+` para validar os nomes dos atributos
+em uma atribuição, porque em nossas classes similares a registros queremos que o
+conjunto de atributos seja sempre o mesmo e na mesma ordem. Entretanto,
+lembre-se de que a principal característica de `+__slots__+` é economizar memória
+quando estamos lidando com milhões de instâncias, e que usar `+__slots__+` traz
+algumas desvantagens, discutidas na <>.
+
+[WARNING]
+====
+
+Instâncias de classes criadas por `record_factory` não são serializáveis—isto
+é, elas não podem ser exportadas em formato binário pela função `dump` do módulo `pickle`.
+Resolver este problema está além do escopo deste exemplo, cujo 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 a instrução `class`, e a enriquece automaticamente com mais funcionalidade.((("", startref="CMfacfun24")))
+
+
+[[enhancing_with_init_subclass_sec]]
+=== 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_].
+
+
+Falamos pela primeira vez do método especial para descritores `+__set_name__+` na <>.
+Agora vamos estudar `+__init_subclass__+`.
+
+No <>, vimos como `typing.NamedTuple` e `@dataclass` usam a sintaxe da instrução `class` para especificar atributos para uma
+nova classe, que então é enriquecida com a criação
+automática de métodos úteis, como `+__init__+`, `+__repr__+`, `+__eq__+`,
+etc.
+
+Ambas as fábricas de classes leem as dicas de tipo na instrução `class` do usuário para enriquecer a classe. Estas dicas de tipo também permitem que checadores de tipos estáticos validem código que define ou lê aqueles atributos.
+Entretanto, `NamedTuple` e `@dataclass` não usam as dicas de tipo para validação de atributos durante a execução.
+A classe `Checked`, no próximo exemplo, faz isso.
+
+[NOTE]
+====
+
+Não é possível suportar toda dica de tipo estática concebível para checagem de
+tipos durante a execução. Entretanto, alguns tipos que são também classes
+concretas podem ser usados com `Checked`. Isto inclui tipos simples, usados com
+frequência para o conteúdo de campos, como  `str`, `int`, `float`, e `bool`, bem
+como listas destes tipos.
+
+====
+
+O <> mostra como usar `Checked` para criar uma classe `Movie`.
+
+[[checked_demo1_ex]]
+.initsub/checkedlib.py: doctest para a criação de uma subclasse `Movie` de `Checked`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=MOVIE_DEFINITION]
+----
+====
+<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.
+
+Os construtores usados como dicas de tipo podem ser qualquer invocável que receba zero ou um argumento, e devolva um valor adequado ao tipo do campo pretendido ou rejeite o argumento, gerando um `TypeError` ou um `ValueError`.
+
+Usar tipos embutidos para as anotações no <> significa que os valores devem ser aceitáveis pelo construtor do tipo.
+Para `int`, isso significa qualquer `x` tal que `int(x)` devolva um `int`.
+Para `str`, qualquer coisa serve durante a execução, pois `str(x)` funciona com qualquer `x` no
+Python.footnote:[Isso é verdade para qualquer objeto, exceto quando sua classe sobrescreve os métodos `+__str__+` ou `+__repr__+`, herdados de `object`, por uma implementação que não funcione.]
+
+Quando chamado sem argumentos, o construtor deve devolver um valor default de seu tipo.footnote:[Essa solução evita usar `None` como default. Evitar valores nulos é uma https://fpy.li/24-5[boa ideia]. Em geral, eles são difíceis de evitar, mas em alguns casos isso é fácil. Tanto no Python quanto no SQL, prefiro representar dados ausentes em um campo de texto como uma string vazia em vez de `None` ou `NULL`. Aprender Go reforçou essa ideia: em Go, variáveis e campos struct de tipos primitivos são inicializados por default com um "valor zero" (_zero value_). Se você estiver curiosa, veja a página https://fpy.li/24-6["Zero values" ("Valores zero")] no _Tour of Go_ ("Tour do Go") online]
+
+Esse é o comportamento padrão de construtores embutidos no Python:
+
+[source, python]
+----
+>>> int(), float(), bool(), str(), list(), dict(), set()
+(0, 0.0, False, '', [], {}, set())
+----
+
+Em uma subclasse de `Checked` como `Movie`, parâmetros ausentes criam instâncias com os valores default devolvidos pelos construtores dos campos. Por exemplo:
+
+[source, python]
+----
+include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=MOVIE_DEFAULTS]
+----
+
+Os construtores são usados para validação durante a instanciação, e quando um atributo é definido diretamente em uma instância:
+
+[source, python]
+----
+include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=MOVIE_TYPE_VALIDATION]
+----
+
+.Subclasses de `Checked` e a verificação estática de tipos
+[WARNING]
+====
+Em um arquivo de código-fonte _.py_ contendo uma instância `movie` da classe `Movie`, como definida no <>, o Mypy marca essa atribuição como um erro de tipo:
+
+[source, python]
+----
+movie.year = 'MCMLXXII'
+----
+
+Entretanto, o Mypy não consegue detectar erros de tipo nesta chamada ao construtor:
+
+[source, python]
+----
+megahit = Movie(title='Avatar', year='MMIX')
+----
+
+Isso porque `Movie` herda `+Checked.__init__+`,
+e a assinatura daquele método deve aceitar qualquer argumento nomeado, para suportar classes arbitrárias definidas pelo usuário.
+
+Por outro lado, se você declarar um campo de uma subclasse de `Checked` com a dica de tipo
+`list[float]`, o Mypy pode sinalizar atribuições de listas com tipos incompatíveis, mas `Checked` vai ignorar o parâmetro de tipo e tratá-lo como igual a `list`.
+====
+
+Vamos ver agora a implementação de _checkedlib.py_.
+A primeira classe é o descritor `Field`, como mostra o <>.
+
+[[checked_field_ex]]
+.initsub/checkedlib.py: a classe descritora `Field`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=CHECKED_FIELD]
+----
+====
+<1> Lembre-se, desde o Python 3.9, o tipo `Callable` para anotações é a ABC em
+`collections.abc`, e não `typing.Callable` que foi descontinuado.
+<2> Essa é a dica de tipo mínima para `Callable` mínima; o parâmetro de tipo e o tipo devolvido são implicitamente `Any`.
+<3> Para checagem durante a execução, usamos o embutido `callable`.footnote:[Na minha opinião, `callable` deveria se tornar adequado para dicas de tipo. Em 6 de maio de 2021, quando essa nota foi escrita, essa ainda era uma https://fpy.li/24-7[questão aberta].] O teste contra `type(None)` é necessário porque Python entende `None` em um tipo como `NoneType`, a classe de `None` (e portanto invocável), mas esse é um construtor inútil, que apenas devolve `None`.
+<4> Se `+Checked.__init__+` definir `value` como `\...` (o objeto embutido `Ellipsis`), invocamos o construtor sem argumentos.
+<5> Caso contrário, invocamos o `constructor` com o `value` fornecido.
+<6> Se `constructor` levantar uma destas exceções, levantamos um `TypeError` com uma mensagem útil, incluindo os nomes do campo e do construtor; por exemplo, `'MMIX' não é compatível com year:int`.
+<7> Se nenhuma exceção for levantada, o `value` é armazenado no `+instance.__dict__+`.
+
+Em `+__set__+`, precisamos capturar `TypeError` e `ValueError`, pois os construtores embutidos podem levantar qualquer uma ou outra, dependendo do argumento.
+Por exemplo, `float(None)` levanta `TypeError`, mas `float('A')` levanta `ValueError`.
+Por outro lado, `float('8')` não causa qualquer erro, e devolve `8.0`.
+Vamos combinar que, neste exemplo simples, este é um recurso, não um bug.
+
+[TIP]
+====
+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 <>.
+====
+
+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 este exemplo.
+O restante dos métodos está no <>.
+
+[[ex_checked_class_top]]
+.initsub/checkedlib.py: os métodos mais importantes da classe `Checked`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=CHECKED_TOP]
+----
+====
+
+<1> Escrevi este método de classe para isolar 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 sempre que 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 a seguir a caixa _<>_.
+
+<3> `+super().__init_subclass__()+` não é estritamente necessário, mas é
+serve para suportar superclasses 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...
+
+<7> ...obtém o `value` correspondente de `kwargs` e o remove de `kwargs`. Usar
+`\...` (o objeto `Ellipsis`) como default nos permite distinguir entre
+argumentos com valor `None` de argumentos ausentes.footnote:[Como mencionado em
+<>, o objeto `Ellipsis` é um valor sentinela conveniente e
+seguro. Ele existe no Python há muito tempo, mas recentemente mais usos têm sido
+encontrados para ele, como vemos nas dicas de tipo e no NumPy.]
+
+<8> Invocar `setattr` aciona `+Checked.__setattr__+`
+(<>).
+
+<9> Se houver itens remanescentes em `kwargs`, seus nomes não correspondem a
+qualquer dos campos declarados, e `+__init__+` vai falhar.
+
+<10> Este erro é informado por `+__flag_unknown_attrs+`, listado no
+<>. Ele recebe um argumento `*names` com os nomes de
+atributos desconhecidos. Usei um único asterisco em `*kwargs`, para passar suas
+chaves como uma sequência de argumentos, sem passar os valores.
+
+
+[[init_subclass_not_typical_box]]
+.`+__init_subclass__+` não é um método de classe típico
+****
+
+O primeiro argumento que Python passa para `+__init_subclass__+` é uma classe.
+Entretanto, esta não é a classe onde `+__init_subclass__+` está definido,
+mas sim uma subclasse que herda daquela classe.
+Este comportamento é diferente de `+__new__+` e de métodos de classe decorados com `@classmethod`,
+onde o primeiro argumento é sempre a própria classe.
+
+Então `+__init_subclass__+` não é um método de classe no sentido usual, e considero enganoso nomear seu primeiro argumento `cls`.
+Prefiro o nome `subcls`. A
+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..."
+
+****
+
+Vamos examinar os métodos restantes da classe `Checked`,
+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]]
+.initsub/checkedlib.py: métodos restantes da classe `Checked`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=CHECKED_BOTTOM]
+----
+====
+<1> Intercepta qualquer tentativa de definir um atributo de instância. Isto é necessário para evitar a definição de um atributo desconhecido.
+<2> Se o `name` do atributo é conhecido, busca o `descriptor` correspondente.
+<3> Normalmente não é preciso invocar o `+__set__+` do descritor explicitamente. Aqui é necessário porque `+__setattr__+` intercepta todas as tentativas de definir um atributo em uma instância, mesmo na presença de um descritor dominante, tal como `Field`.
+<4> Caso contrário, o atributo `name` é desconhecido, e uma exceção será levantada por
+`+__flag_unknown_attrs+`.
+<5> Cria uma mensagem de erro útil, listando todos os argumentos inesperados, e levanta `AttributeError`.
+Este é um raro exemplo do tipo especial `NoReturn`, tratado na <>.
+<6> Cria um `dict` a partir dos atributos de um objeto `Movie`. Eu chamaria este método de
+`+_as_dict+`, mas segui a convenção iniciada com o método `+_asdict+` em `collections.namedtuple`.
+<7> Implementar um `+__repr__+` agradável é a principal razão para ter `_asdict` neste exemplo.
+
+O exemplo `Checked` mostra como tratar descritores dominantes ao implementar `+__setattr__+` para bloquear a definição arbitrária de atributos após a instanciação.
+É possível debater se vale a pena implementar `+__setattr__+` neste exemplo.
+Sem ele, definir `movie.director = 'Greta Gerwig'` funcionaria, mas o atributo `director` não seria verificado de forma alguma, não apareceria no `+__repr__+` nem seria incluído no `dict` devolvido por `+_asdict+`—ambos definidos no <>.
+
+Em _record_factory.py_ (no <>), solucionei essa questão usando o atributo de classe `+__slots__+`.
+Entretanto, essa solução mais simples não é viável aqui, como explicado a seguir.
+
+[[why_cannot_config_slots_sec]]
+==== Por que `+__init_subclass__+` não pode configurar `+__slots__+`?
+
+O((("__slots__"))) atributo `+__slots__+` só tem efeito quando é um dos itens do espaço de nomes da classe passado para `+type.__new__+`.
+Acrescentar `+__slots__+` a uma classe existente não funciona.
+Python invoca `+__init_subclass__+` apenas após a classe ser criada—neste ponto, é tarde demais para configurar `+__slots__+`.
+Um decorador de classes também não pode configurar `+__slots__+`, pois ele é aplicado ainda mais tarde que `+__init_subclass__+`.
+Vamos explorar essas questões de sincronia na <>.
+
+Para configurar `+__slots__+` durante a execução, nosso próprio código precisa criar o espaço de nomes da classe a ser passado como último argumento de `+type.__new__+`.
+Para fazer isso, podemos escrever uma função fábrica de classes, como _record_factory.py_, ou optar pelo caminho radical, e implementar uma metaclasse.
+Veremos como configurar `+__slots__+` dinamicamente na <>.
+
+Antes da https://fpy.li/pep487[_PEP 487_] simplificar a customização da criação de classes com
+`+__init_subclass__+`, no Python 3.7, uma funcionalidade similar só poderia ser implementada usando um decorador de classe.
+Pergunte-me como.((("", startref="initsub24")))((("", startref="CMinitsub24")))
+
+
+=== Um decorador de classes
+
+Um((("class metaprogramming", "enhancing classes with class decorators",
+id="CMcdecorator24")))((("decorators and closures", "enhancing classes with
+class decorators", id="DACcdeco24"))) decorador de classes tem comportamento
+semelhante a um decorador de funções: recebe uma classe decorada
+como argumento, e devolve uma classe para substituir a classe decorada.
+Decoradores de classe normalmente devolvem a própria classe decorada, após
+injetar novos métodos nela. Talvez, a razão
+mais comum para escolher um decorador de classes, em vez do 
+`+__init_subclass__+`, é evitar interferência com outros mecanismos de classes,
+como herança e metaclasses. Esta justificativa aparece no resumo da
+https://fpy.li/24-9[_PEP 557–Data Classes_] para explicar
+por que ela foi implementada como um decorador de classes.
+
+Nesta seção vamos estudar _checkeddeco.py_, que oferece a mesma funcionalidade de _checkedlib.py_, mas usando um decorador de classe.
+Como sempre, começamos examinando um exemplo de uso, extraído dos doctests em _checkeddeco.py_ (no <>).
+
+[[checkeddeco_demo1_ex]]
+.checkeddeco.py: criando uma classe `Movie` decorada com `@checked`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/decorator/checkeddeco.py[tags=MOVIE_DEFINITION]
+----
+====
+
+A única diferença entre o <> e o <> é a forma como a classe `Movie` é declarada: ela é decorada com `@checked` em vez de ser uma subclasse de `Checked`.
+Fora isso, o comportamento externo é o mesmo, incluindo a validação de tipo e a atribuição de valores default, apresentados após
+o <>, na <>.
+
+Vamos olhar agora para a implementação de _checkeddeco.py_.
+As importações e a classe `Field` são as mesmas de _checkedlib.py_, listadas no <>.
+Em _checkeddeco.py_ não há qualquer outra classe, apenas funções.
+
+A lógica antes implementada em `+__init_subclass__+` agora é parte da função `checked`—o decorador de classes listado no <>.
+
+[[checkeddeco_decorators_ex]]
+.checkeddeco.py: o decorador de classes
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/decorator/checkeddeco.py[tags=CHECKED_DECORATOR]
+----
+====
+
+<1> Lembre-se de que classes são instâncias de `type`. Estas dicas de tipo indicam
+que este é um decorador de classes: recebe uma classe e devolve
+uma classe.
+
+<2> `+_fields+` agora é uma função de alto nível definida mais tarde no módulo
+(<>).
+
+<3> Troca 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`.
+
+<7> Devolve a `cls` decorada, cumprindo o contrato básico de um decorador de
+classes.
+
+Todas as funções no primeiro nível de _checkeddeco.py_ estão prefixadas com um sublinhado, exceto o decorador `checked`.
+Essa convenção de nomenclatura faz sentido por duas razões:
+
+* `checked` é parte da interface pública do módulo _checkeddeco.py_, as outras funções não.
+* As funções no <> serão injetadas na classe decorada,
+e o `+_+` inicial reduz as chances de um conflito de nomes com atributos e métodos definidos pelo usuário na classe decorada.
+
+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 <>.
+
+Observe que a função `+_fields+` tem dois papéis em _checkeddeco.py_.
+Ela é usada como uma função normal na primeira linha do decorador `checked` e será também injetada como um método de classe na classe decorada.
+
+[[checkeddeco_methods_ex]]
+.checkeddeco.py: os métodos que serão injetados na classe decorada
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/decorator/checkeddeco.py[tags=CHECKED_METHODS]
+----
+====
+
+O módulo _checkeddeco.py_ implementa um decorador de classes simples, mas usável.
+O `@dataclass` de Python faz mais.
+Ele suporta várias opções de configuração, acrescenta métodos à classe decorada, trata ou avisa sobre conflitos com métodos definidos pelo usuário na classe decorada, e até percorre o `+__mro__+` para coletar atributos definidos pelo usuário declarados em superclasses da classe decorada.
+O https://fpy.li/24-10[«código-fonte»] do pacote `dataclasses` no Python 3.9 tem mais de 1200 linhas.
+
+Para fazer metaprogramação de classes, precisamos saber quando o interpretador Python avalia cada bloco de código durante a criação de uma classe.
+É disso que falaremos a seguir.((("", startref="DACcdeco24")))((("", startref="CMcdecorator24")))
+
+[[import_v_runtime_sec]]
+=== O que acontece quando: importação versus execução
+
+Programadores Python((("class metaprogramming", "import time versus runtime",
+id="CMimport24")))((("import time versus runtime"))) falam de "momento da
+importação" (_import time_) versus "momento de execução" (_run time_), mas estes
+termos não têm definições precisas e há uma zona cinzenta entre eles.
+
+No momento da importação, o interpretador:
+
+. Analisa o código-fonte do módulo _.py_ em uma passagem, da primeira até a última linha. É aqui que um `SyntaxError` pode ocorrer.
+. Compila o _bytecode_ a ser executado.
+. Executa o código no nível superior do módulo compilado.
+
+Se existir um arquivo _.pyc_ atualizado no `+__pycache__+` local,
+a análise e a compilação são omitidas, pois o _bytecode_ está pronto para ser executado.
+
+Apesar de a análise e a compilação serem definitivamente atividades de "importação",
+outras coisas podem acontecer durante este processo,
+pois quase todas as instruções no Python são executáveis,
+pois podem rodar código do usuário
+e modificar o estado do programa.
+
+Em especial, a instrução `import` não é meramente uma
+declaração, como é em Java, onde ela serve para informar
+o compilador sobre os pacotes necessários.
+Em Python, `import` serve para isso, mas também carrega e executa todo o código no
+nível superior de um módulo, quando ele é importado para o processo Python pela
+primeira vez. Importações posteriores do mesmo módulo usarão um _cache_, e então
+o único efeito será a vinculação dos objetos importados a nomes no módulo
+cliente. O código executado em consequência de um `import` pode fazer qualquer
+coisa, incluindo ações típicas da "execução", como escrever em um arquivo de log
+ou conectar-se a um banco de dados.footnote:[Não estou dizendo que é uma boa
+ideia abrir uma conexão com um banco de dados só porque o módulo foi importado,
+apenas apontando que isso pode ser feito.] Por isso a fronteira entre a
+"importação" e a "execução" é difusa: `import` pode acionar todo tipo de
+comportamento de "execução", porque a instrução `import` e a função embutida
+`+__import__()+` podem ser usadas dentro de qualquer função normal.
+
+Tudo isso é bastante abstrato e sutil, então vamos fazer alguns experimentos para ver o que acontece, e quando.
+
+
+[[evaldemo_sec]]
+==== Experimentos com as etapas de avaliação
+
+Considere um script _evaldemo.py_, que usa um decorador de classes, um descritor e uma fábrica de classes baseada em `+__init_subclass__+`, todos definidos em um módulo _builderlib.py_.
+Os módulos usados têm várias chamadas a `print`, para revelar o que acontece por baixo dos panos. Fora isso, eles não fazem nada de útil. O objetivo destes experimentos é observar a ordem na qual essas chamadas a `print` acontecem.
+
+[WARNING]
+====
+
+Aplicar um decorador de classes e uma fábrica de classes com
+`+__init_subclass__+` juntos, em uma única classe, é provavelmente um sinal de
+excesso de engenharia ou de desespero. Esta combinação incomum é útil nestes
+experimentos, para comparar em que momento um decorador de
+classes e `+__init_subclass__+` alteram a classe.
+
+====
+
+Vamos começar examinando _builderlib.py_, dividido em duas partes: o <> e o <>.
+
+[[builderlib_top_ex]]
+.builderlib.py: primeira parte do módulo
+====
+[source, python]
+----
+include::../code/24-class-metaprog/evaltime/builderlib.py[tags=BUILDERLIB_TOP]
+----
+====
+<1> Essa é uma fábrica de classes para implementar...
+<2> ...um método `+__init_subclass__+`.
+<3> Define uma função para ser adicionada à subclasse na atribuição abaixo.
+<4> Um decorador de classes.
+<5> Função a ser adicionada à classe decorada.
+<6> Devolve a classe recebida como argumento.
+
+Continuando _builderlib.py_ no <>...
+
+[[builderlib_bottom_ex]]
+.builderlib.py: a parte final do módulo
+====
+[source, python]
+----
+include::../code/24-class-metaprog/evaltime/builderlib.py[tags=BUILDERLIB_BOTTOM]
+----
+====
+<1> Uma classe descritora para demonstrar quando...
+<2> ...uma instância do descritor é criada, e quando...
+<3> ...`+__set_name__+` será invocado durante a criação da classe `owner`.
+<4> Como os outros métodos, este `+__set__+` não faz nada, exceto exibir seus argumentos.
+
+Se importarmos _builderlib.py_ no console de Python, veremos o seguinte:
+
+[source, python]
+----
+>>> import builderlib
+@ builderlib module start
+@ Builder body
+@ Descriptor body
+@ builderlib module end
+----
+
+Note que as linhas exibidas por _builderlib.py_ têm uma `@` à esquerda.
+
+Agora voltamos a atenção para _evaldemo.py_, que acionará métodos especiais em
+_builderlib.py_ (no <>).
+
+[[evaldemo_ex]]
+.evaldemo.py: script para experimentar com _builderlib.py_
+====
+[source, python]
+----
+include::../code/24-class-metaprog/evaltime/evaldemo.py[]
+----
+====
+<1> Aplica um decorador.
+<2> Cria uma subclasse de `Builder` para acionar seu `+__init_subclass__+`.
+<3> Instancia o descritor.
+<4> Isso só será chamado se o módulo for executado como o programa principal.
+
+As chamadas a `print` em _evaldemo.py_ têm um `#` como prefixo.
+Se você abrir o console novamente e importar _evaldemo.py_, a saída aparece no <>.
+
+[[evaldemo_console_ex]]
+.Experimentos de console com _evaldemo.py_
+====
+[source, python]
+----
+>>> import evaldemo
+@ builderlib module start  <1>
+@ Builder body
+@ Descriptor body
+@ builderlib module end
+# evaldemo module start
+# Klass body  <2>
+@ Descriptor.__init__()  <3>
+@ Descriptor.__set_name__(,
+      , 'attr')                <4>
+@ Builder.__init_subclass__()  <5>
+@ deco()  <6>
+# evaldemo module end
+----
+====
+<1> As primeiras quatro linhas são o resultado de `from builderlib import …`;
+elas aparecerão se você não fechar o console após o experimento anterior, pois _builderlib.py_ já estará carregado.
+<2> Isso sinaliza que Python começou a ler o corpo de `Klass`. Neste momento o objeto classe ainda não existe.
+<3> A instância do descritor é criada e vinculada a `attr`, no espaço de nomes que Python passará para o construtor default do objeto classe: `+type.__new__+`.
+<4> Neste ponto, a função embutida de Python `+type.__new__+` já criou o objeto `Klass` e invoca
+`+__set_name__+` em cada instância das classes do descritor que oferecem aquele método, passando `Klass` como argumento `owner`.
+<5> `+type.__new__+` então chama `+__init_subclass__+` na superclasse de `Klass`, passando `Klass` como único argumento.
+<6> Quando `+type.__new__+` devolve o objeto classe, Python aplica o decorador. Neste exemplo, a classe devolvida por `deco` está vinculada a `Klass` no espaço de nomes do módulo
+
+A implementação de `+type.__new__+` está escrita em C.
+O comportamento que acabei de descrever está documentado na seção
+https://fpy.li/bx[«Criando o objeto classe»], no capítulo
+_Modelo de Dados_ da referência de Python.
+
+Note que a função `main()` de _evaldemo.py_ (<>)
+rodou durante a sessão no console (<>), portanto nenhuma instância de `Klass` foi criada.
+Todas as ações que vimos foram acionadas por operações no momento da importação:
+importar `builderlib` e definir `Klass`.
+
+Se você executar _evaldemo.py_ como um script, verá a mesma saída do <>,
+com mais linhas logo antes do final.
+As linhas adicionais são o resultado da execução de `main()` no <>:
+
+[[evaldemo_script_ex]]
+.Executando _evaldemo.py_ como um programa
+====
+[source]
+----
+$ ./evaldemo.py
+[... 9 linhas omitidas ...]
+@ deco()  <1>
+@ Builder.__init__()  <2>
+# Klass.__init__()
+@ SuperA.__init_subclass__:inner_0()  <3>
+@ deco:inner_1()  <4>
+@ Descriptor.__set__(, , 999)  <5>
+# evaldemo module end
+----
+====
+<1> As 10 primeiras linhas—incluindo essa—são as mesma que aparecem no <>.
+<2> Saída de `+super().__init__()+` em `+Klass.__init__+`.
+<3> Saída de `obj.method_a()` em `main`; o `method_a` foi injetado por
+`+SuperA.__init_subclass__+`.
+<4> Saída de `obj.method_b()` em `main`; `method_b` foi injetado por `deco`.
+<5> Saída de `obj.attr = 999` em `main`.
+
+Uma classe base com `+__init_subclass__+` ou um decorador de classes são ferramentas poderosas, mas elas estão limitadas a trabalhar sobre uma classe já criada por `+type.__new__+` por baixo dos panos.
+Nas raras ocasiões em que for preciso ajustar os argumentos passados a `+type.__new__+`, uma metaclasse é necessária.
+Esse é o destino final deste capítulo—e deste livro!((("", startref="CMimport24")))
+
+
+[[metclass101_sec]]
+=== Introdução às metaclasses
+
+
+[quote, Tim Peters, inventor do algoritmo timsort e prolífico mantenedor de Python]
+____
+
+[Metaclasses] são uma mágica tão profunda que 99% dos usuários jamais
+deveriam se preocupar com elas. Quem se pergunta se precisa delas, não precisa
+(quem realmente precisa de metaclasses sabe com certeza, e não precisa que
+lhe expliquem a razão).footnote:[Mensagem a comp.lang.python, assunto:
+https://fpy.li/24-12[_Acrimony in c.l.p._] (animosidade na c.l.p., o grupo _comp.lang.python_).
+Esta é
+outra parte da mesma mensagem de 23 de dezembro de 2002, que citei no _Prefácio_ de _Python Fluente_.
+O TimBot estava inspirado naquele dia.]
+
+____
+
+
+
+Uma((("metaclasses", "basics of", id="MCbasics24")))((("class metaprogramming", "metaclass basics", id="CMmetabasic24"))) metaclasse é uma fábrica de classes.
+Diferente de `record_factory`, do <>,
+uma metaclasse é escrita como uma classe.
+Em outras palavras, uma metaclasse é uma classe cujas instâncias são classes.
+A <> usa a _Notação de Engenhocas e Bugigangas_ para ilustrar uma metaclasse:
+uma engenhoca que produz outra engenhoca.
+
+[[meta_class_and_class_mgn]]
+.Uma metaclasse é uma classe que cria classes.
+image::../images/flpy_2401.png[align="center", pdfwidth=10cm]
+
+Pense no modelo de objetos de Python: classes são objetos, portanto cada classe deve ser uma instância de alguma outra classe.
+Por default, as classes de Python são instâncias de `type`.
+Em outras palavras, `type` é a metaclasse da maioria das classes, sejam elas embutidas ou definidas pelo usuário:
+
+[source, python]
+----
+>>> str.__class__
+
+>>> from bulkfood_v5 import LineItem
+>>> LineItem.__class__
+
+>>> type.__class__
+
+----
+
+Para evitar regressões infinitas, a classe de `type` é `type`, como mostra a última linha.
+
+Observe que não estou dizendo que `str` ou `LineItem` são subclasses de `type`. Estou dizendo que `str` e `LineItem` são instâncias de `type`.
+Elas são todas subclasses de `object`. A <> pode ajudar você a contemplar essa estranha realidade.
+
+[[class_hier_2tops_uml]]
+.Os dois diagramas são verdadeiros. O da esquerda enfatiza que `str`, `type`, e `LineItem` são subclasses de `object`. O da direita ressalta que `str`, `object`, e `LineItem` são instâncias de `type`, pois todas são classes.
+image::../images/flpy_2402.png[align="center", pdfwidth=10cm]
+
+[NOTE]
+====
+
+As classes `object` e `type` têm uma relação singular: `object` é uma instância
+de `type`, e `type` é uma subclasse de `object`. Esta relação é "mágica": ela
+não pode ser expressa em Python, porque cada uma das classes teria que existir
+antes da outra poder ser definida. O fato de `type` ser uma instância de si
+mesma também é mágico.
+
+====
+
+O próximo trecho mostra que a classe de `collections.Iterable` é `abc.ABCMeta`.
+Observe que `Iterable` é uma classe abstrata, mas `ABCMeta` é uma classe concreta--afinal, `Iterable` é uma instância de `ABCMeta`:
+
+[source, python]
+----
+>>> from collections.abc import Iterable
+>>> Iterable.__class__
+
+>>> import abc
+>>> from abc import ABCMeta
+>>> ABCMeta.__class__
+
+----
+
+Por fim, a classe de `ABCMeta` também é `type`.
+Toda classe é uma instância de `type`, direta ou indiretamente, mas só metaclasses são também subclasses de `type`.
+Esta é a relação mais importante para entender as metaclasses:
+uma metaclasse, tal como `ABCMeta`, herda de `type` o poder de criar classes.
+A <> ilustra essa relação fundamental.
+
+[role="width-60"]
+[[metaclass_abcmeta_uml]]
+.`Iterable` é uma subclasse de `object` e uma instância de `ABCMeta`. Tanto `object` quanto `ABCMeta` são instâncias de `type`, mas a relação crucial aqui é que `ABCMeta` também é uma subclasse de `type`, porque `ABCMeta` é uma metaclasse. Neste diagrama, `Iterable` é a única classe abstrata.
+image::../images/flpy_2403.png[align="center", pdfwidth=6cm]
+
+A lição importante aqui é que metaclasses são subclasses de `type`, e é isso que permite a elas funcionarem como fábricas de classes.
+Uma metaclasse pode customizar suas instâncias implementando métodos especiais, como demonstram as próximas seções.((("", startref="MCbasics24")))
+
+[[how_metaclass_customizes]]
+==== Como uma metaclasse customiza uma classe
+
+Para((("metaclasses", "customizing classes"))) usar uma metaclasse, é crucial entender como
+`+__new__+` funciona em qualquer classe.
+Isto foi discutido na <>.
+
+A mesma mecânica se repete no nível "meta", quando uma metaclasse está prestes a criar uma nova instância, que é uma classe.
+Considere a declaração abaixo:
+
+[source, python]
+----
+class Klass(SuperKlass, metaclass=MetaKlass):
+    x = 42
+    def __init__(self, y):
+        self.y = y
+----
+
+Para processar o bloco `class` acima, o interpretador invoca `+MetaKlass.__new__+`,
+cuja implementação herdada da classe `type` exige os seguintes argumentos:
+
+`meta_cls`:: A própria metaclasse, porque `+__new__+` funciona como um método de classe. +
+Ex: `MetaKlass`
+
+`cls_name`:: O nome da classe a ser criada, como uma string. Ex: `'Klass'`
+
+`bases`:: Uma tupla com as superclasses da classe a ser criada. Ex: `(SuperKlass,)`
+
+`cls_dict`:: Os métodos e outros atributos da classe a ser criada. Ex:
++
+--
+[source, python]
+----
+{'x': 42, '__init__': }
+----
+--
+
+Ao implementar `+MetaKlass.__new__+`, podemos inspecionar e modificar aqueles
+argumentos antes de passá-los para `+super().__new__+`, que por fim invocará
+`+type.__new__+` para criar o novo objeto classe.
+Após `+super().__new__+` retornar, podemos aplicar processamentos
+adicionais à classe recém-criada, antes de devolvê-la para o Python. 
+
+Depois de invocar `+MetaKlass.__new__+` e receber a nova `Klass`,
+Python então invoca `+SuperKlass.__init_subclass__+`,
+passando a classe que criamos. 
+Se existir um decorador de classe acima do bloco `class`, ele é aplicado
+depois que `+__init_subclass__+` retorna.
+Finalmente, Python
+vincula o objeto classe a seu nome no espaço de nomes atual.
+No caso mais comum, a instrução `class` está no primeiro nível do módulo,
+e a `Klass` é inserida no espaço de nomes global do módulo.
+
+O processamento mais comum realizado no `+__new__+` de uma metaclasse é
+adicionar ou substituir itens no `cls_dict`, que representa o espaço
+de nomes da classe em construção.
+Por exemplo, podemos injetar métodos na classe em construção adicionando
+funções a `cls_dict`. Entretanto, observe que adicionar métodos pode também ser
+feito após a classe ser criada, e é por essa razão que podemos fazer isso usando
+`+__init_subclass__+` ou um decorador de classe.
+
+Um atributo que precisa ser adicionado a `cls_dict` antes de se executar `+type.__new__+` é
+`+__slots__+`, como discutido na <>.
+O método `+__new__+` de uma metaclasse é o lugar ideal para configurar `+__slots__+`.
+A próxima seção mostra como fazer isso.
+
+
+[[nice_metaclass_sec]]
+==== Um belo exemplo de metaclasse
+
+A((("metaclasses", "example metaclass", id="MCexample24")))
+metaclasse `MetaBunch` apresentada aqui é uma variação do último exemplo no
+Capítulo 4 do _Python in a Nutshell, 3rd ed._, de Alex
+Martelli, Anna Ravenscroft, e Steve Holden, escrito para rodar sob Python 2.7 e
+3.5.footnote:[Os autores gentilmente me deram permissão para usar seu exemplo.
+`MetaBunch` apareceu pela primeira vez em uma mensagem enviada por Martelli para
+o grupo comp.lang.python, em 7 de julho de 2002, com o assunto
+https://fpy.li/24-13["a nice metaclass example (was Re: structs in python)" (_um
+belo exemplo de metaclasse (era Re: structs no python)_)], na sequência de uma
+discussão sobre estruturas de dados similares a registros no Python. O código
+original de Martelli, para Python 2.2, ainda roda após uma única modificação:
+para usar uma metaclasse no Python 3, é necessário usar o argumento nomeado
+`metaclass` na declaração da classe (por exemplo, `Bunch(metaclass=MetaBunch)`),
+em vez da convenção antiga, que era adicionar um atributo `+__metaclass__+` no
+corpo da classe.] Assumindo o uso de Python 3.6 ou mais recente, pude
+simplificar ainda mais o código.
+
+Mas primeiro vamos ver o que a classe base `Bunch` oferece:
+
+[source, python]
+----
+include::../code/24-class-metaprog/metabunch/from3.6/bunch.py[tags=BUNCH_POINT_DEMO_1]
+----
+
+Subclasses de `Bunch` usam atributos de classe com valores atribuídos, que então se tornam os valores default dos atributos de instância.
+O `+__repr__+` gerado omite os argumentos cujos valores são iguais aos defaults.
+
+`MetaBunch`—a metaclasse de `Bunch`—gera `+__slots__+` para a nova classe a
+partir de atributos de classe declarados na classe do usuário.
+Isto impede que atributos com nomes não declarados sejam
+criados na instanciação ou por atribuição posterior.
+
+[source, python]
+----
+include::../code/24-class-metaprog/metabunch/from3.6/bunch.py[tags=BUNCH_POINT_DEMO_2]
+----
+
+Vamos agora mergulhar no elegante código de `MetaBunch`, no <>.
+
+[[metabunch_ex]]
+.metabunch/from3.6/bunch.py: a metaclasse `MetaBunch` e a classe `Bunch`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/metabunch/from3.6/bunch.py[tags=METABUNCH]
+----
+====
+
+<1> Para criar uma nova metaclasse, herdamos de `type`.
+
+<2> `+__new__+` funciona como um método de classe, mas a classe é uma
+metaclasse, então prefiro nomear o primeiro argumento `meta_cls` (`mcs` é uma
+alternativa comum). Os três argumentos seguintes são os mesmos da assinatura de
+três parâmetros de `type()`, quando invocada diretamente para criar uma classe.
+
+<3> `defaults` vai preservar um mapeamento de nomes de atributos e seus valores
+default.
+
+<4> Isto será injetado na nova classe.
+
+<5> Lê `defaults` e define o atributo de instância correspondente, com o valor
+extraído de `kwargs`, ou um valor default.
+
+<6> Se ainda houver itens em `kwargs`, isso significa que não há posição
+restante onde possamos colocá-los. Adotamos a prática de _falhar rápido_,
+então não queremos ignorar silenciosamente os itens em excesso.
+
+<7> `+__repr__+` devolve uma string que se parece com uma chamada ao
+construtor—por exemplo, `Point(x=3)`, exibindo somente os atributos
+com valores diferentes dos respectivos valores default.
+
+<8> Inicializa o espaço de nomes para a nova classe.
+
+<9> Itera sobre o espaço de nomes da classe fornecida pelo usuário.
+
+<10> Se um `name` _dunder_ (com dois sublinhados no prefixo e sufixo) é encontrado,
+copia o item para o espaço de nomes da nova classe, a menos que ele já esteja
+lá. Isto evita que usuários sobrescrevam `+__init__+`, `+__repr__+` e outros
+atributos definidos pelo Python, como `+__qualname__+` e `+__module__+`.
+
+<11> Se `name` não for um _dunder_, acrescenta `name` a `+__slots__+` e armazena
+seu `value` em `defaults`.
+
+<12> Cria e devolve a nova classe.
+
+<13> Fornece uma classe base, assim os usuários não precisam ver `MetaBunch`.
+
+`MetaBunch` funciona porque tem a oportunidade de configurar `+__slots__+` antes
+de invocar `+super().__new__+` para criar a classe final. Como sempre em
+metaprogramação, o fundamental é entender a sequência de ações. Vamos fazer
+outro experimento sobre as etapas de avaliação, agora com uma metaclasse.((("",
+startref="MCexample24")))
+
+
+==== Experimento com as etapas de avaliação de metaclasses
+
+Esta((("metaclasses", "metaclass evaluation time experiment", id="MCtime24")))
+é uma variação da <>, acrescentando uma metaclasse para ter mais emoção.
+O módulo _builderlib.py_ é o mesmo de antes, mas o script principal agora é _evaldemo_meta.py_,
+listado no <>.
+
+[[evaldemo_meta_ex]]
+.evaldemo_meta.py: experimentando com uma metaclasse
+====
+[source, python]
+----
+include::../code/24-class-metaprog/evaltime/evaldemo_meta.py[]
+----
+====
+<1> Importa `MetaKlass` de _metalib.py_, que veremos no <>.
+<2> Declara `Klass` como uma subclasse de `Builder` e uma instância de `MetaKlass`.
+<3> Este método é injetado por `+MetaKlass.__new__+`, como veremos adiante.
+
+
+[WARNING]
+====
+Em nome da ciência, o <> desafia qualquer razão prática e aplica três técnicas diferentes de metaprogramação em `Klass`:
+um decorador, uma classe base usando `+__init_subclass__+`, e uma metaclasse customizada.
+Se você fizer isto em código de produção, não me culpe.
+Aqui o objetivo é observar a ordem na qual as três técnicas interferem no processo de criação de uma classe.
+====
+
+Como no experimento anterior com as etapas de avaliação, este exemplo não faz nada, apenas exibe mensagens revelando o fluxo de execução.
+O <> mostra a primeira parte do código de _metalib.py_—o restante está no <>.
+
+[[metalib_top_ex]]
+.metalib.py: a classe `NosyDict`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/evaltime/metalib.py[tags=METALIB_TOP]
+----
+====
+
+Escrevi a classe `NosyDict` para  sobrescrever `+__setitem__+` e exibir cada `key` e cada `value` conforme eles são definidos.
+A metaclasse vai usar uma instância de `NosyDict` para guardar o espaço de nomes da classe em construção, revelando um pouco mais sobre o funcionamento interno de Python.
+
+A principal atração de _metalib.py_ é a metaclasse no <>.
+Ela implementa o método especial `+__prepare__+`, um método de classe que Python só invoca em metaclasses.
+O método `+__prepare__+` oferece a primeira oportunidade para influenciar o processo de criação de uma nova classe.
+
+[TIP]
+====
+Ao programar uma metaclasse, acho útil adotar a seguinte convenção de nomenclatura para argumentos de métodos especiais:
+
+* Usar `cls` em vez de `self` para métodos de instância, pois a instância é uma classe.
+
+* Usar `meta_cls` em vez de `cls` para métodos de classe, pois a classe é uma metaclasse.
+Lembre-se de que `+__new__+` se comporta como um método de classe mesmo sem o decorador `@classmethod`.
+====
+
+[[metalib_bottom_ex]]
+.metalib.py: a `MetaKlass`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/evaltime/metalib.py[tags=METALIB_BOTTOM]
+----
+====
+<1> `+__prepare__+` deve ser declarado como um método de classe.
+Ele não é um método de instância, pois a classe em construção ainda não existe quando Python invoca `+__prepare__+`.
+<2> Python invoca `+__prepare__+` em uma metaclasse para obter um mapeamento para guardar o espaço de nomes da classe em construção.
+<3> Devolve uma instância de `NosyDict` para ser usada como o espaço de nomes.
+<4> `cls_dict` é uma instância de `NosyDict` devolvida por `+__prepare__+`.
+<5> `+type.__new__+` exige um `dict` de verdade como último argumento (não um mapeamento qualquer), então fornecemos o atributo `data` de `NosyDict`, herdado de `UserDict`.
+<6> Injeta um método na classe recém-criada.
+<7> Como sempre, `+__new__+` devolve o objeto que acaba de ser criado—neste caso, a nova classe.
+<8> Definir `+__repr__+` em uma metaclasse permite customizar o `repr()` de objetos classe.
+
+O principal caso de uso para `+__prepare__+` antes de Python 3.6 era fornecer um
+`OrderedDict` para preservar os atributos de uma classe em construção,
+para que o `+__new__+` da metaclasse pudesse processar aqueles atributos na ordem
+em que aparecem no código-fonte da definição de classe do usuário.
+Agora que `dict` preserva a ordem de inserção, `+__prepare__+` raramente é necessário.
+Veremos um uso criativo para ele na <>.
+
+Importar _metalib.py_ no console de Python não é muito empolgante.
+Observe o uso de `%` para prefixar as linhas geradas por esse módulo:
+
+[source, python]
+----
+>>> import metalib
+% metalib module start
+% MetaKlass body
+% metalib module end
+----
+
+Muitas coisas acontecem quando importamos _evaldemo_meta.py_, como visto no  <>.
+
+[[evaldemo_meta_console_ex]]
+.Experimento com _evaldemo_meta.py_ no console
+====
+[source, python]
+----
+>>> import evaldemo_meta
+@ builderlib module start
+@ Builder body
+@ Descriptor body
+@ builderlib module end
+% metalib module start
+% MetaKlass body
+% metalib module end
+# evaldemo_meta module start  <1>
+% MetaKlass.__prepare__(, 'Klass',  <2>
+                        (,))
+% NosyDict.__setitem__(, '__module__',
+  'evaldemo_meta')  <3>
+% NosyDict.__setitem__(, '__qualname__', 'Klass')
+# Klass body
+@ Descriptor.__init__()  <4>
+% NosyDict.__setitem__(, 'attr',
+  )  <5>
+% NosyDict.__setitem__(, '__init__',
+                       )  <6>
+% NosyDict.__setitem__(, '__repr__',
+                       )
+% NosyDict.__setitem__(, '__classcell__', )
+% MetaKlass.__new__(, 'Klass',
+                    (,),
+                    )  <7>
+@ Descriptor.__set_name__(,
+                          , 'attr')  <8>
+@ Builder.__init_subclass__()
+@ deco()
+# evaldemo_meta module end
+----
+====
+<1> As linhas acima desta são exibidas durante a importação de _builderlib.py_ e _metalib.py_.
+<2> Python invoca `+__prepare__+` para iniciar o processamento de uma instrução `class`.
+<3> Antes de analisar o corpo da classe, Python acrescenta `+__module__+` e `+__qualname__+` ao espaço de nomes de uma classe em construção.
+<4> A instância do descritor é criada...
+<5> ...e vinculada a `attr` no espaço de nomes da classe.
+<6> Os métodos `+__init__+` e `+__repr__+` são definidos e adicionados ao espaço de nomes.
+<7> Após terminar o processamento do corpo da classe, Python chama `+MetaKlass.__new__+`.
+<8> Após o método `+__new__+` da metaclasse devolver a classe recém-criada,
+os métodos `+__set_name__+`, `+__init_subclass__+` e o decorador `@deco` são invocados nesta ordem, 
+
+Se executarmos _evaldemo_meta.py_ como um script, `main()` é invocado, e mais coisas acontecem. Veja o <>.
+
+[[evaldemo_meta_script_ex]]
+.Rodando _evaldemo_meta.py_ como um programa
+====
+[source]
+----
+$ ./evaldemo_meta.py
+[... 20 linhas omitidas ...]
+@ deco()  <1>
+@ Builder.__init__()
+# Klass.__init__()
+@ SuperA.__init_subclass__:inner_0()
+@ deco:inner_1()
+% MetaKlass.__new__:inner_2()  <2>
+@ Descriptor.__set__(, , 999)
+# evaldemo_meta module end
+----
+====
+<1> As primeiras 21 linhas—incluindo esta—são as mesmas que aparecem no <>.
+<2> Acionado por `obj.method_c()` em `main`; `method_c` foi injetado por `+MetaKlass.__new__+`.
+
+Vamos agora voltar à ideia da classe `Checked`, com descritores `Field`
+implementando validação de tipo durante a execução, e ver como aquilo pode ser
+feito com uma metaclasse.((("", startref="CMmetabasic24")))((("",
+startref="MCtime24")))
+
+
+=== `Checked`, agora com metaclasse
+
+Não((("class metaprogramming", "metaclass solution for checkedlib.py",
+id="CMcheck24")))((("metaclasses", "metaclass solution for checkedlib.py",
+id="MCchecked24"))) quero estimular a otimização prematura nem excessos de engenharia (_over),
+então aqui temos um cenário de ficção para justificar reescrever _checkedlib.py_ com `+__slots__+`,
+exigindo a aplicação de uma metaclasse.
+Fique à vontade para pular a historinha.
+
+.Senta que lá vem história
+****
+Nosso _checkedlib.py_ usando `+__init_subclass__+` é um sucesso na empresa, e em qualquer dado momento nossos servidores de produção guardam milhões de instâncias de subclasses de  `Checked` em suas memórias.
+
+Analisando (_profiling_) o uso de memória durante a execução, constatamos que usar `+__slots__+` pode reduzir os custos de hospedagem, por duas razões:
+
+* Menos uso de memória, já que as instâncias de `Checked` não precisarão ter seus próprios
+`+__dict__+`
+* Melhor desempenho, pela remoção de `+__setattr__+`, que foi criado só para bloquear atributos indesejados,
+mas é acionado na instanciação e para todas as definições de atributos antes de
+`+Field.__set__+` ser invocado para fazer seu trabalho
+
+****
+
+
+O módulo _metaclass/checkedlib.py_, que estudaremos a seguir, é um substituto
+direto para _initsub/checkedlib.py_. Os doctests contidos nos dois módulos são
+idênticos, bem como os arquivos _checkedlib_test.py_ para o _pytest_.
+
+A complexidade de _checkedlib.py_ é ocultada do usuário.
+Aqui está o código-fonte de um script que usa o pacote:
+
+[source, python]
+----
+include::../code/24-class-metaprog/checked/metaclass/checked_demo.py[tags=MOVIE_DEMO]
+----
+
+Esta definição concisa da classe `Movie` se vale de três instâncias do descritor de validação `Field`, uma configuração de `+__slots__+`, cinco métodos herdados de `Checked` e uma metaclasse para juntar tudo isso.
+A única parte visível de `checkedlib` é a classe base `Checked`.
+
+Observe a <>.
+A Notação Engenhocas e Bugigangas((("UML class diagrams", "annotated with MGN"))) complementa o diagrama de classes UML, tornando mais visível a relação entre classes e instâncias.
+
+Por exemplo, uma classe `Movie` usando a nova _checkedlib.py_ é uma instância de `CheckedMeta` e uma subclasse de `Checked`.
+Os atributos de classe `title`, `year` e `box_office` de `Movie` são três instâncias diferentes de `Field`.
+Cada instância de `Movie` tem seus próprios atributos `_title`, `_year` e `_box_office`, para armazenar os valores dos campos correspondentes.
+
+Vamos agora estudar o código, começando pela classe `Field` do <>.
+
+A classe descritora `Field` agora está um pouco diferente. Nos exemplos anteriores, cada instância do descritor `Field` armazenava seu valor na instância gerenciada, usando um atributo de mesmo nome. Por exemplo, na classe `Movie`, o descritor `title` armazenava o valor do campo em um atributo `title` na instância gerenciada.
+Isso tornava desnecessário que `Field` implementasse um método `+__get__+`.
+
+Entretanto, quando uma classe como `Movie` usa `+__slots__+`, ela não pode ter atributos de classe e atributos de instância com o mesmo nome. Cada instância do descritor é um atributo de classe, e agora precisamos de atributos de armazenamento separados em cada instância. O código usa o nome do descritor prefixado por um único `_`.
+Portanto, instâncias de `Field` têm atributos `name` e `storage_name` distintos, e implementamos
+`+Field.__get__+`.
+
+[[checkedlib_uml_mgn]]
+.Diagrama de classes UML com MGN: a meta-engenhoca `CheckedMeta` cria a engenhoca `Movie`. A engenhoca `Field` cria os descritores `title`, `year`, e `box_office`, que são atributos de classe de `Movie`. Os dados dos campos são armazenados nos atributos `+_title+`, `+_year+` e `+_box_office+` em cada instância de `Movie`. Note a fronteira do pacote `checkedlib`. O desenvolvedor de `Movie` não precisa entender todo o maquinário dentro de _checkedlib.py_.
+image::../images/flpy_2404.png[Diagrama de classes UML+MGN para `CheckedMeta`, `Movie` etc.]
+
+O <> mostra o código-fonte de `Field`, com os textos explicativos descrevendo apenas as mudanças nessa versão.
+
+[[checked_field_meta_ex]]
+.metaclass/checkedlib.py: o descritor `Field` com `storage_name` e `+__get__+`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/metaclass/checkedlib.py[tags=CHECKED_FIELD]
+----
+====
+<1> Define `storage_name` a partir do argumento `name`.
+<2> Se `+__get__+` recebe `None` como argumento `instance`, o descritor está sendo lido desde a própria classe gerenciada, não de uma instância gerenciada. Neste caso devolvemos o descritor.
+<3> Caso contrário, devolve o valor armazenado no atributo chamado `storage_name`.
+<4> `+__set__+` agora usa `setattr` para definir ou atualizar o atributo gerenciado.
+
+O <> mostra o código da metaclasse que controla este exemplo.
+
+[[checked_metaclass_ex]]
+.metaclass/checkedlib.py: a metaclasse `CheckedMeta`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/metaclass/checkedlib.py[tags=CHECKED_META]
+----
+====
+<1> `+__new__+` é o único método implementado em `CheckedMeta`.
+<2> Só altera a classe se seu `cls_dict` não incluir `+__slots__+`. Se `+__slots__+` já existe, assumimos que esta é a classe base `Checked` e não uma subclasse definida pelo usuário, e cria a classe sem modificações.
+<3> Nos exemplos anteriores usamos `typing.get_type_hints` para obter as dicas de tipo, mas aquilo exige uma classe existente como primeiro argumento. Neste ponto, a classe que estamos configurando ainda não existe, então precisamos recuperar `+__annotations__+` diretamente do `cls_dict`—o espaço de nomes da classe em construção, que Python passa como último argumento para o `+__new__+` da metaclasse.
+<4> Itera sobre `type_hints` para...
+<5> ...criar um `Field` para cada atributo anotado...
+<6> ...sobrescreve o item correspondente em `cls_dict` com a instância de `Field`...
+<7> ...e acrescenta o `storage_name` do campo à lista que usaremos para...
+<8> ...preencher o `+__slots__+` no `cls_dict`—o espaço de nomes da classe em construção.
+<9> Por fim, invocamos `+super().__new__+`.
+
+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:
+
+. 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__+`.
+. A remoção de `+__setattr__+`, que se tornou redundante: o acréscimo de `+__slots__+` à classe definida pelo usuário impede a definição de atributos não declarados.
+
+O <> é a listagem completa da versão final de `Checked`.
+
+[[checked_baseclass_ex]]
+.metaclass/checkedlib.py: a classe base `Checked`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/metaclass/checkedlib.py[tags=CHECKED_CLASS]
+----
+====
+
+Isto conclui nossa terceira versão de uma fábrica de classes com descritores validados.
+
+A próxima seção trata de algumas questões gerais relacionadas a metaclasses.((("", startref="MCchecked24")))((("", startref="CMcheck24")))
+
+[[metaclases_real_world_sec]]
+=== Metaclasses no mundo real
+
+Metaclasses((("metaclasses", "considerations for use",
+id="MCconsider24")))((("class metaprogramming", "metaclass issues",
+id="CMissue24"))) são poderosas mas complexas. Antes de se decidir a implementar
+uma metaclasse, considere os pontos a seguir.
+
+
+[[metaclass_modern_features_sec]]
+==== Recursos modernos simplificam ou substituem as metaclasses
+
+Ao longo do tempo, vários casos de uso comum de metaclasses se tornaram redundantes devido a novos recursos da linguagem:
+
+Decoradores de classes:: Mais simples de entender que metaclasses, e com menor probabilidade de causar conflitos com classes base e metaclasses.
+
+`+__set_name__+`:: Elimina a necessidade de uma metaclasse com lógica customizada para definir automaticamente o nome de um descritor.footnote:[Na primeira edição de _Python Fluente_, as versões mais avançadas da classe `LineItem` usavam uma metaclasse apenas para definir o nome do armazenamento dos atributos. Veja o código nas metaclasses do https://fpy.li/24-14[exemplo da comida a granel], no repositório de código da primeira edição.]
+
+`+__init_subclass__+`:: Fornece uma forma de customizar a criação de classes que é transparente para o usuário final e ainda mais simples que um decorador—mas pode introduzir conflitos em uma hierarquia de classes complexa.
+
+O `dict` embutido preservando a ordem de inserção de chaves:: Eliminou((("keys", "preserving key insertion order"))) a principal razão para usar `+__prepare__+`, que era
+fornecer um `OrderedDict` para armazenar o espaço de nomes de uma classe em construção.
+Python só invoca `+__prepare__+` em metaclasses e então, se fosse necessário processar o espaço de nomes da classe na ordem em que eles aparecem no código-fonte, antes de Python 3.6 era preciso usar uma metaclasse.
+
+Em 2021, todas as versões sob manutenção ativa do CPython suportam todos os recursos listados acima.
+
+Sigo defendendo esses recursos porque vejo muita complexidade desnecessária em nossa profissão, e as metaclasses são uma porta de entrada para a complexidade.
+
+
+==== Metaclasses são um recurso estável da linguagem
+
+As metaclasses foram introduzidas no Python em 2002, junto com as assim chamadas
+_new-style classes_ (classes com novo estilo), descritores e propriedades.
+
+É impressionante que o exemplo do `MetaBunch`, postado pela primeira vez por
+Alex Martelli em julho de 2002, ainda funcione no Python 3.9—a única modificação
+é a forma de especificar a metaclasse a ser usada, que no Python 3 fazemos com esta sintaxe:
+
+[source, python]
+----
+class Bunch(metaclass=MetaBunch):
+    ...
+----
+
+Nenhum dos acréscimos que mencionei na <> quebrou
+código existente que usava metaclasses. Mas código legado com metaclasses
+frequentemente pode ser simplificado através do uso daqueles recursos,
+especialmente ignorando versões de Python anteriores à 3.6—que hoje são obsoletas.
+
+
+==== Uma classe só pode ter uma metaclasse
+
+Se sua declaração de classe envolver duas ou mais metaclasses, você verá essa intrigante mensagem de erro:
+
+[source]
+----
+TypeError: metaclass conflict: the metaclass of a derived class
+must be a (non-strict) subclass of the metaclasses of all its bases
+(conflito de metaclasses: a metaclasse de uma classe derivada deve
+ser uma subclasse (não-estrita) das metaclasses de todas as suas bases)
+----
+
+Isso pode acontecer mesmo sem herança múltipla.
+Por exemplo, a declaração abaixo pode levantar aquele `TypeError`:
+
+[source, python]
+----
+class Record(abc.ABC, metaclass=PersistentMeta):
+    pass
+----
+
+Vimos que `abc.ABC` é uma instância da metaclasse `abc.ABCMeta`.
+Se aquela metaclasse `Persistent` não for uma subclasse de `abc.ABCMeta`,
+você tem um conflito de metaclasses.
+
+Há duas maneiras de lidar com esse erro:
+
+* Encontre outra forma de fazer o que precisa ser feito, evitando o uso de pelo menos uma das metaclasses envolvidas.
+* Escreva a sua própria metaclasse `PersistentABCMeta` como uma subclasse tanto de `abc.ABCMeta` quanto de `PersistentMeta`, usando herança múltipla, e faça dela a única metaclasse de `Record`.footnote:[Se você sentiu vertigem ao ponderar sobre as implicações de herança múltipla com metaclasses, bom para você. Eu também passaria longe dessa solução.]
+
+
+[TIP]
+====
+
+A solução de uma metaclasse com duas metaclasses base pode ser implementada para atender um prazo em caso de desespero.
+Na minha experiência, a programação de metaclasses sempre leva mais tempo que o esperado,
+tornando esta abordagem arriscada diante de um prazo inflexível.
+Se fizer isso e cumprir o prazo previsto, seu código pode conter bugs sutis.
+E mesmo na ausência de bugs conhecidos, esta abordagem deveria ser considerada uma dívida técnica,
+pelo simples fato de ser difícil de entender e manter.
+
+====
+
+
+==== Metaclasses devem ser detalhes de implementação
+
+Além de `type`, existem apenas outras seis metaclasses em toda a biblioteca padrão de Python 3.9.
+As metaclasses mais utilizadas (indiretamente) provavelmnete são `abc.ABCMeta`, `typing.NamedTupleMeta` e
+`enum.EnumMeta`.
+Nenhuma delas deve aparecer explicitamente no código da aplicação, mas somente em bibliotecas.
+Podemos considerá-las detalhes de implementação.
+
+Apesar de ser possível fazer metaprogramações muito loucas com metaclasses, é melhor se ater ao
+https://fpy.li/24-15[princípio do menor espanto], de forma que a maioria dos usuários possa de fato considerar metaclasses detalhes de implementação.footnote:[Eu ganhei a vida por alguns anos escrevendo código para Django, antes de resolver estudar como os campos dos modelos Django eram implementados. Só então aprendi sobre descritores e metaclasses.]
+
+Nos últimos anos, algumas metaclasses na biblioteca padrão de Python foram
+substituídas por outros mecanismos, sem afetar a API pública de seus pacotes. A
+forma mais simples de resguardar estas APIs para o futuro é oferecer uma classe
+normal, da qual usuários podem então criar subclasses para acessar a
+funcionalidade fornecida pela metaclasse, como fiz em vários exemplos.
+
+Para encerrar nossa conversa sobre metaprogramação de classes, vou mostrar
+o pequeno exemplo de metaclasse mais interessante que encontrei durante
+minha pesquisa para esse capítulo.((("", startref="CMissue24")))((("",
+startref="MCconsider24")))
+
+
+[[metahack_sec]]
+=== Um _hack_ de metaclasse com `+__prepare__+`
+
+Quando((("class metaprogramming", "__prepare__ method", id="CMjprepare24",
+secondary-sortas="prepare")))((("__prepare__",
+id="prepare24"))) atualizei esse capítulo para a segunda edição, procurei
+exemplos simples mas interessantes para substituir o código de
+`LineItem` no exemplo da loja de comida a granel, que não exige mais
+o uso de metaclasses desde o Python 3.6.
+
+João S. O. Bueno—mais conhecido como JS na comunidade Python brasileira,
+me apresentou a ideia de metaclasse mais curiosa que já vi.
+
+Uma aplicação de sua ideia é criar uma classe que gera constantes numéricas automaticamente:
+
+[source, python]
+----
+include::../code/24-class-metaprog/autoconst/autoconst_demo.py[tags=AUTOCONST]
+----
+
+Sim, isto funciona do jeito que está! Este código é um doctest em _autoconst_demo.py_.
+
+Aqui está a classe base fácil de usar `AutoConst` , e a metaclasse por trás dela, implementadas em _autoconst.py_:
+
+[source, python]
+----
+include::../code/24-class-metaprog/autoconst/autoconst.py[tags=AUTOCONST]
+----
+
+É só isso.
+
+Claramente, o truque está em `WilyDict`.
+
+Quando Python processa o espaço de nomes da classe do usuário e lê `banana`, ele
+procura aquele nome no mapeamento fornecido por `+__prepare__+`: uma instância
+de `WilyDict` que implementa o método `+__missing__+`, tratado na
+<>.
+
+A instância de `WilyDict` inicialmente não contém uma chave
+`'banana'`, então o método `+__missing__+` é acionado. Ele cria um item na hora,
+com a chave `'banana'` e o valor `0`, e devolve este valor. Python se
+contenta com isso, e daí tenta acessar `'coconut'`. `WilyDict` imediatamente
+adiciona aquele item com o valor `1`, e o devolve. O mesmo acontece com
+`'vanilla'`, que é então mapeado para `2`.
+
+Já vimos `+__prepare__+` e `+__missing__+` antes.
+A verdadeira inovação é a forma como JS as juntou.
+
+Aqui está o código-fonte de `WilyDict`, também de _autoconst.py_:
+
+[source, python]
+----
+include::../code/24-class-metaprog/autoconst/autoconst.py[tags=WilyDict]
+----
+
+Enquanto experimentava, descobri que Python procurava `+__name__+` no espaço de
+nomes da classe em construção, fazendo com que `WilyDict` acrescentasse um item
+`+__name__+` e incrementasse `+__next_value+`. Eu então inseri uma instrução
+`if` em `+__missing__+`, para levantar um `KeyError` quando uma chave se parece com
+um atributo _dunder_.
+
+Me diverti adicionando mais funcionalidades a `AutoConstMeta` e `AutoConst`, mas em vez de compartilhar meus experimentos, vou deixar vocês se divertirem, brincando com o _hack_ genial de JS.
+
+Aqui estão algumas ideias:
+
+* Torne possível obter o nome da constante a partir do valor. Por exemplo, `Flavor[2]` devolveria `'vanilla'`. Você pode fazer isso implementando `+__getitem__+` em `AutoConstMeta`. Desde o
+Python 3.9, é possível implementar
+`+__class_getitem__+` na própria `AutoConst`.
+
+* Suporte a iteração sobre a classe, implementando `+__iter__+` na metaclasse.
+Eu faria `+__iter__+` produzir as constantes na forma de pares `(name, value)`.
+
+* Implemente uma nova variante de `Enum`. Isso é um empreendimento
+épico, pois o pacote `enum` está cheio de truques, incluindo a metaclasse
+`EnumMeta`, com centenas de linhas de código e um método `+__prepare__+` bem complicado.
+
+Divirta-se!
+
+[NOTE]
+====
+
+O método especial `+__class_getitem__+` foi introduzido no Python 3.9 para
+suportar tipos genéricos, como parte da
+https://fpy.li/pep585[_PEP 585—Type Hinting Generics in Standard Collections_]
+(Dicas de Tipos Genéricas em Coleções Padrão).
+Graças a `+__class_getitem__+`, os mantenedores de Python não
+precisaram escrever uma nova metaclasse para que os tipos embutidos
+implementassem `+__getitem__+`, de modo que fosse possível escrever dicas de
+tipo genéricas, tal como `list[int]`. Esse é um recurso limitado, mas
+representativo, de um caso de uso mais amplo para metaclasses: implementar
+operadores e outros métodos especiais para funcionarem ao nível da classe, tal
+como fazer a própria classe iterável, como as subclasses de `Enum`.((("",
+startref="prepare24")))((("", startref="CMjprepare24")))
+
+====
+
+=== Para encerrar
+
+Metaclasses, bem((("class metaprogramming", "useful applications of metaclasses")))((("metaclasses", "useful applications of"))) como decoradores de classes e `+__init_subclass__+`, são úteis para:
+
+- Registro de subclasses
+- Validação estrutural de subclasses
+- Aplicar decoradores a muitos métodos ao mesmo tempo
+- Serialização de objetos
+- Mapeamento objeto-relacional
+- Persistência automática de objetos
+- Implementar métodos especiais a nível de classe
+- Implementar recursos de classes encontrados em outras linguagens, como
+https://fpy.li/24-17[_traits_] 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 <>
+(<>):
+
+[quote]
+____
+
+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 tais profundezas.
+____
+
+Acredito que o conselho de Martelli se aplica não apenas a ABCs e metaclasses,
+mas também a hierarquias de classe, sobrecarga de operadores, decoradores de funções, descritores, decoradores de classes e fábricas de classes usando `+__init_subclass__+`.
+
+Em princípio, essas ferramentas poderosas existem para suportar o desenvolvimento de bibliotecas e frameworks.
+Naturalmente, as aplicações devem _usar_ tais ferramentas, na forma oferecida pela biblioteca padrão de Python ou por pacotes externos.
+Mas _implementá-las_ em código de aplicações é frequentemente resultado de uma abstração prematura.
+
+[quote, DHH, criador de Ruby on Rails]
+____
+Bons frameworks são extraídos, não inventados.footnote:[Esta frase é muito citada.
+Encontrei uma citação direta antiga no blog de DHH, em https://fpy.li/24-19[post] de 2005.]
+____
+
+
+=== Resumo do capítulo
+
+Este((("class metaprogramming", "overview of"))) capítulo começou com uma revisão dos atributos encontrados em objetos classe, como `+__qualname__+` e o método `+__subclasses__()+`.
+A seguir, vimos como a classe embutida `type` pode ser usada para criar classes durante a execução.
+
+Apresentei o método especial `+__init_subclass__+` na primeira versão de uma
+classe base `Checked`, projetada para substituir dicas de tipo de atributos em
+subclasses definidas pelo usuário por instâncias do descritor `Field`, que usam
+construtores para validar o tipo dos atributos durante a execução.
+
+Implementei a mesma ideia com um decorador de classes `@checked`, que acrescenta
+recursos a classes definidas pelo usuário, de forma similar ao que pode ser
+feito com `+__init_subclass__+`. Vimos que `+__init_subclass__+` ou um
+decorador de classes não conseguem configurar `+__slots__+` dinamicamente, pois atuam
+apenas após a criação da classe, e `+__slots__+` tem que ser definido antes.
+
+Desvendamos os conceitos de "momento de importação" (_import time_)
+e "momento de execução" (_run time_)
+com experimentos mostrando a ordem na qual o código Python é executado quando
+módulos, descritores, decoradores de classe e `+__init_subclass__+` são acionados.
+
+Nossa exploração de metaclasses começou com uma explicação geral de `type` como uma metaclasse,
+e sobre como metaclasses definidas pelo usuário podem implementar `+__new__+`, para customizar as classes que criam.
+Vimos então nossa primeira metaclasse customizada, o clássico exemplo `MetaBunch`, usando
+`+__slots__+`.
+A seguir, outro experimento com etapas de avaliação demonstrou como os métodos `+__prepare__+` e
+`+__new__+` de uma metaclasse são invocados antes de `+__init_subclass__+`
+e decoradores de classe, oferecendo mais oportunidades para customização de classes.
+
+Estudamos uma terceira versão de uma fábrica de classes `Checked`, com descritores `Field` e
+uma configuração customizada de `+__slots__+`, seguida de
+considerações gerais sobre o uso de metaclasses na prática.
+
+Por fim, vimos o hack `AutoConst`, inventado por João S. O. Bueno, baseado na
+brilhante ideia de uma metaclasse com `+__prepare__+` devolvendo um mapeamento
+que implementa `+__missing__+`. Em menos de 20 linhas de código, _autoconst.py_
+demonstra o poder da combinação de técnicas de metaprogramação no Python.
+
+Nunca encontrei outra linguagem como Python, fácil para iniciantes, prática para
+profissionais e empolgante para hackers. Obrigado, Guido van Rossum e todos que
+a fazem ser assim.
+
+
+[[ch21-furtherreading]]
+=== Para saber mais
+
+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://fpy.li/by[«Personalizando a criação de classe»] no capítulo
+_Modelo 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[«Atributos especiais»] do capítulo _Tipos embutidos_ da _Biblioteca Padrão de Python_
+também são leituras fundamentais.
+
+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` e `types.prepare_class`.
+
+Decoradores de classes foram formalizados na
+https://fpy.li/24-25[_PEP 3129—Class Decorators_]
+(Decoradores de Classes), escrita por Collin Winter, com a
+implementação de referência desenvolvida por Jack Diederich.
+A palestra 
+https://fpy.li/24-26[_Class Decorators: Radically Simple_]
+(Decoradores de Classes: Radicalmente Simples) na PyCon 2009,
+também de Jack Diederich, é
+uma rápida introdução a este recurso. Além de `@dataclass`, um exemplo
+interessante—e mais simples—de decorador de classes na biblioteca padrão de
+Python é https://fpy.li/7q[`functools.total_ordering`], 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), onde o método especial `+__prepare__+` foi proposto.
+
+O _Python in a Nutshel_ 3rd. ed., de Alex Martelli, Anna Ravenscroft, e Steve Holden,
+é uma referência, mas foi escrito antes da https://fpy.li/pep487[_PEP 487_]
+simplificar o processo de customizar classes na sua criação. O principal exemplo
+de metaclasse no livro—`MetaBunch`—ainda é válido, pois não pode ser escrito com
+mecanismos mais simples.
+O _Effective Python_ 2nd. ed. (Addison-Wesley), de
+Brett Slatkin, traz vários exemplos atualizados de técnicas de criação de
+classes, incluindo metaclasses.
+
+Para aprender sobre as origens da
+metaprogramação de classes no Python, recomendo o artigo de Guido van Rossum de
+2003,
+https://fpy.li/24-28[_Unifying types and classes in Python 2.2_]
+(Unificando tipos e classes no Python 2.2). O texto se aplica também ao
+Python moderno, pois cobre as chamadas "classes novo estilo"—o comportamento padrão no Python 3—incluindo descritores e
+metaclasses. Uma das referências citadas por Guido é _Putting Metaclasses to
+Work: a New Dimension in Object-Oriented Programming_, de Ira R. Forman e Scott
+H. Danforth (Addison-Wesley), livro para o qual ele deu cinco estrelas na
+_Amazon.com_, escrevendo o seguinte comentário:
+
+[quote]
+____
+
+*Este livro contribuiu para o projeto das metaclasses no Python 2.2*
+
+Pena que esteja fora de catálogo; sempre me refiro a ele como o melhor tutorial
+que conheço para o difícil tópico da herança múltipla cooperativa, suportada
+pelo Python através da função `super()`.footnote:[Comprei um exemplar usado, e
+achei uma leitura muito difícil. Não cheguei ao final.]
+
+____
+
+Se você curte metaprogramação, talvez gostaria que Python suportasse
+o recurso definitivo de metaprogramação: macros sintáticas,
+como as oferecidas pela família de linguagens Lisp e—mais recentemente—pelo Elixir e pelo Rust.
+Macros sintáticas são mais poderosas e menos sujeitas a erros que as macros primitivas de substituição de código da linguagem C.
+Elas são funções especiais que reescrevem código-fonte para código padronizado, usando uma sintaxe customizada, antes da etapa de compilação,
+permitindo a desenvolvedores introduzir novas estruturas na linguagem sem modificar o compilador.
+Como a sobrecarga de operadores, macros sintáticas podem ser mal usadas.
+Mas, desde que a comunidade entenda e controle as desvantagens, elas suportam abstrações poderosas e amigáveis,
+como as DSLs (_Domain-Specific Languages_, Linguagens de Domínio Específico).
+
+Em setembro de 2020, Marc Shannon, um dos mantenedores de Python, publicou a
+https://fpy.li/pep638[_PEP 638—Syntactic Macros_], uma proposta de macros sintáticas.
+Um ano após sua publicação inicial (quando escrevo essas linhas), a PEP 638 ainda era um rascunho e não havia discussões contínuas sobre ela.
+Claramente não é uma prioridade muito alta entre os mantenedores de Python.
+Eu gostaria de ver a PEP 638 sendo melhor discutida e, por fim, aprovada.
+Macros sintáticas permitiriam à comunidade Python experimentar com novos recursos controversos,
+tal como o "operador morsa" (_operador walrus_) (https://fpy.li/pep572[PEP 572]),
+casamento de padrões (https://fpy.li/pep634[PEP 634]) e regras alternativas para avaliação de dicas de tipo
+(PEPs https://fpy.li/pep563[563] e
+https://fpy.li/pep649[649]),
+antes que se fizessem modificações permanentes no núcleo da linguagem.
+Nesse meio tempo, podemos sentir o gosto das macros sintáticas com o pacote
+https://fpy.li/24-29[MacroPy].
+
+
+[role="pagebreak-before less_space"]
+.Ponto de vista
+****
+
+Vou((("class metaprogramming", "Soapbox discussion")))((("Soapbox sidebars", "programming language design"))) iniciar o último ponto de vista no livro com uma longa citação de Brian Harvey e Matthew Wright, dois professores de ciência da computação da Universidade da California (Berkeley e Santa Barbara). Em seu livro, _Simply Scheme: Introducing Computer Science_ ("Simplesmente Scheme: Introduzindo a Ciência da Computação") (MIT Press), Harvey e Wright escreveram:
+
+
+[quote, Brian Harvey e Matthew Wright, no prefácio de Simply Scheme]
+____
+Há duas escolas de pensamento sobre o ensino de ciência da computação. Podemos representar as duas visões de uma forma caricatural, assim:
+
+
+. *A visão conservadora*: Programas de computador se tornaram muito grandes e
+complexos para serem apreendidos pela mente humana. Portanto, a tarefa da
+educação na ciência da computação é ensinar os estudantes como se disciplinarem,
+de tal forma que 500 programadores medíocres possam se juntar e produzir um
+programa que atende às especificações.
+
+. *A visão radical*: Programas de computador se tornaram muito grandes e
+complexos para serem apreendidos pela mente humana. Portanto, a tarefa da
+educação na ciência da computação é ensinar os estudantes como expandir suas
+mentes até abarcar os programas, aprendendo a pensar com um vocabulário
+de ideias maiores, mais poderosas e mais flexíveis que as óbvias. Cada
+unidade de pensamento programático deve gerar uma grande recompensa para as
+capacidades do programa.footnote:[Brian Harvey e Matthew Wright, _Simply Scheme_
+(MIT Press, 1999), p. xvii. O livro completo está disponível em
+https://fpy.li/24-30[Berkeley.edu].]
+
+____
+
+As descrições exageradas de Harvey e Wright versam sobre o ensino de ciência da computação,
+mas também se aplicam ao projeto de linguagens de programação.
+Neste ponto você já deve ter adivinhado que concordo com a visão "radical",
+e acredito que Python foi projetado nesse espírito.
+
+A ideia de propriedade é um grande passo adiante, comparado com a abordagem "métodos de acesso desde o início",
+praticamente exigida em Java e suportada pela geração de _getters/setters_ por atalhos de teclado nas IDEs Java.
+A principal vantagem das propriedades é nos permitir começar a criar nossos programas
+simplesmente expondo atributos publicamente—no espírito do _KISS_—sabendo
+que um atributo público pode se tornar uma propriedade a qualquer momento sem quebrar código existente.
+Mas a ideia de descritor vai muito além disso,
+fornecendo um framework para abstrair lógica repetitiva para acessar atributos.
+Este framework é tão eficaz que mecanismos essenciais de Python o utilizam por baixo dos panos.
+
+Outra ideia poderosa são as funções como objetos de primeira classe,
+abrindo caminho para funções de ordem superior. E acontece que a
+combinação de descritores e funções de ordem superior permite a unificação de
+funções e métodos. O `+__get__+` de uma função cria um objeto método sob demanda,
+vinculando a instância ao argumento `self`. Isto é
+elegante.footnote:[_Machine Beauty: Elegance and the Heart of Technology_
+(Beleza de Máquina: A Elegância e o Coração da Tecnologia), de David Gelernter
+(Basic Books), começa com uma discussão intrigante sobre elegância e estética em
+obras de engenharia, de pontes a software. Os capítulos posteriores não são tão
+bons, mas o início vale o preço.]
+
+Por fim, temos a ideia de classes como objetos de primeira
+classe. É uma façanha impressionante de design que uma linguagem acessível para
+iniciantes forneça abstrações poderosas, como fábricas de classes, decoradores de
+classes, e metaclasses completas definidas pelo usuário. Melhor ainda, os
+recursos avançados estão integrados de forma que não atrapalham a programação
+casual (eles na verdade ajudam, por trás dos panos). A conveniência e o
+sucesso de frameworks como o Django e o SQLAlchemy devem muito às metaclasses.
+Ao longo dos anos, a metaprogramação de classes em Python está se tornando cada
+vez mais simples, pelo menos para os casos de uso comuns. Os melhores recursos
+da linguagem são aqueles que beneficiam a todos, mesmo que alguns usuários de
+Python não os conheçam. Mas esses usuários sempre podem aprender, e criar a
+próxima grande biblioteca.
+
+Mande notícias sobre suas contribuições ao ecossistema e à comunidade de Python!
+****
diff --git a/online/deploy.sh b/online/deploy.sh
new file mode 100755
index 00000000..ada705b2
--- /dev/null
+++ b/online/deploy.sh
@@ -0,0 +1,9 @@
+
+#!/bin/bash
+set -e  # exit when any command fails
+asciidoctor Livro.adoc -o index.html
+
+#scp index.html dh_kqh7yy@pdx1-shared-a1-19.dreamhost.com:/home/dh_kqh7yy/pythonfluente.com/index.html
+rsync -avz --delete index.html dh_kqh7yy@pythonfluente.com:~/pythonfluente.com/2/
+
+open https://pythonfluente.com/2/
diff --git a/online/xrefs-exemplos.ipynb b/online/xrefs-exemplos.ipynb
new file mode 100644
index 00000000..3b590191
--- /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/pdf/cor/pyfl2-vol1-cor-2026-01-16.pdf b/pdf/cor/pyfl2-vol1-cor-2026-01-16.pdf
new file mode 100644
index 00000000..26e6b008
Binary files /dev/null and b/pdf/cor/pyfl2-vol1-cor-2026-01-16.pdf differ
diff --git a/pdf/cor/pyfl2-vol2-cor-2026-03-02.pdf b/pdf/cor/pyfl2-vol2-cor-2026-03-02.pdf
new file mode 100644
index 00000000..904a9556
Binary files /dev/null and b/pdf/cor/pyfl2-vol2-cor-2026-03-02.pdf differ
diff --git a/pdf/cor/pyfl2-vol3-cor-2026-03-03.pdf b/pdf/cor/pyfl2-vol3-cor-2026-03-03.pdf
new file mode 100644
index 00000000..71a03440
Binary files /dev/null and b/pdf/cor/pyfl2-vol3-cor-2026-03-03.pdf differ
diff --git a/pdf/pb/pyfl2-vol1-pb-2026-01-16.pdf b/pdf/pb/pyfl2-vol1-pb-2026-01-16.pdf
new file mode 100644
index 00000000..ca82b727
Binary files /dev/null and b/pdf/pb/pyfl2-vol1-pb-2026-01-16.pdf differ
diff --git a/pdf/pb/pyfl2-vol2-pb-2026-03-02.pdf b/pdf/pb/pyfl2-vol2-pb-2026-03-02.pdf
new file mode 100644
index 00000000..f7ccd1d5
Binary files /dev/null and b/pdf/pb/pyfl2-vol2-pb-2026-03-02.pdf differ
diff --git a/pdf/pb/pyfl2-vol3-pb-2026-03-03.pdf b/pdf/pb/pyfl2-vol3-pb-2026-03-03.pdf
new file mode 100644
index 00000000..2afee0a1
Binary files /dev/null and b/pdf/pb/pyfl2-vol3-pb-2026-03-03.pdf differ
diff --git a/print/.gitignore b/print/.gitignore
new file mode 100644
index 00000000..29296252
--- /dev/null
+++ b/print/.gitignore
@@ -0,0 +1 @@
+go_check
diff --git a/print/README.md b/print/README.md
new file mode 100644
index 00000000..92356a4b
--- /dev/null
+++ b/print/README.md
@@ -0,0 +1,71 @@
+# Procedimentos para preparar volumes para impressão
+
+## Rever ISBN na página de copyright e na contra-capa
+
+Python Fluente, 2ª edição...
+
+|volume| páginas | variante  | ISBN              |
+|------|---------|-----------|-------------------|
+|    1 |     403 | standard  | 978-65-988962-1-8 |
+|      |         | dunder    | 978-65-988962-0-1 |
+|    2 |     364 | standard  | 978-65-989778-3-2 |
+|      |         | dunder    | 978-65-989778-2-5 |
+|    3 |     459 | 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.
+Aqui eles estão escritos em Markdown, mas o livro é Asciidoc,
+então a marcação pode ser diferente.
+
+### Links para a web
+
+Leia o _Tutorial do Python_ [fpy.li/3e]
+
+
+### Links para outro volume
+
+Como veremos na _Seção 11.3_ [vol.2, fpy.li/3f]
+
+
+## Operações
+
+### Nos arquivos da edição online:
+
+1. Eliminar duplicação de atributos entre cabeçalhos de `capN.adoc`, `atributos-pt_BR.adoc`, `Livro.adoc`, `vol1.adoc`...
+2. Encurtar links já presentes
+3. Quebrar linhas longas (semantic linebreaks se possível, hard-wrap como plano B)
+4. Retirar inline pass macros[^1]
+
+5. Gerar PDF para impressão
+6. Revisar PDF, aplicar correções no `.adoc`
+
+[^1]: https://docs.asciidoctor.org/asciidoc/latest/pass/pass-macro/
+
+### Nos arquivos separados por volume
+
+1. Trocar xrefs para outros volumes: _Título do alvo (v2:c8 fpy.li/xy)_
+2. Gerar PDF para impressão
+3. Revisar PDF, aplicar correções no `.adoc`
+
+## Para gerar o PDF
+
+Na raiz do repositório:
+
+```
+./print/pdf_export.sh vol1/vol1.adoc 
+```
+
+## Semantic line breaks
+
+Atenção: legendas de figuras tem que ter apenas uma linha lógica.
+
+A legenda começa com . e não pode ser quebrada em várias linhas. Exemplo:
+
+```
+[[vectors_fig]]
+.Soma de vetores bi-dimensionais: `Vector(2, 4) + Vector(2, 1)` devolve `Vector(4, 5)`.
+image::../images/flpy_0101.png[vetores 2D]
+```
\ No newline at end of file
diff --git a/print/append-pdfs.py b/print/append-pdfs.py
new file mode 100755
index 00000000..a08753bd
--- /dev/null
+++ b/print/append-pdfs.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+from pypdf import PdfReader, PdfWriter
+
+
+def append_pdfs(input_pdf1, input_pdf2, output_pdf):
+    """
+    Append two PDF files together.
+
+    Args:
+        input_pdf1: Path to the first PDF file
+        input_pdf2: Path to the second PDF file (will be appended)
+        output_pdf: Path for the output merged PDF file
+    """
+    # Create a PDF writer object
+    writer = PdfWriter()
+
+    # Read and add pages from the first PDF
+    reader1 = PdfReader(input_pdf1)
+    for page in reader1.pages:
+        writer.add_page(page)
+
+    # Read and add pages from the second PDF
+    reader2 = PdfReader(input_pdf2)
+    for page in reader2.pages:
+        writer.add_page(page)
+
+    # Write the merged PDF to output file
+    with open(output_pdf, 'wb') as output_file:
+        writer.write(output_file)
+
+    print(f'{input_pdf1} + {input_pdf2} --> {output_pdf}')
+
+
+if __name__ == '__main__':
+    import sys
+
+    first_pdf, second_pdf, output_file = sys.argv[1:]
+
+    try:
+        append_pdfs(first_pdf, second_pdf, output_file)
+    except FileNotFoundError as e:
+        print(f'Error: Could not find PDF file - {e}')
+    except Exception as e:
+        print(f'Error: {e}')
diff --git a/print/append_colophon.sh b/print/append_colophon.sh
new file mode 100755
index 00000000..ec06c4af
--- /dev/null
+++ b/print/append_colophon.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+set -e  # exit when any command fails
+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
new file mode 100644
index 00000000..ba092547
--- /dev/null
+++ b/print/attrib-print-pt-br.adoc
@@ -0,0 +1,34 @@
+// Tradução para português brasileiro adaptada de 
+// .../asciidoctor/data/locale/attributes-pt_BR.adoc
+// Arquivo original criado por Rafael Pestano 
+// com atualizações de Andrew Rodrigues 
+// Admonitions
+:icons: font
+:note-caption: Nota
+:tip-caption: Dica
+:warning-caption: Aviso
+// Não usados no FluPy: important, caution
+:important-caption: Importante  
+:caution-caption: Cuidado
+// Book parts
+:part-signifier: Parte
+:part-refsig: {part-signifier}
+:chapter-signifier: Capítulo
+:chapter-refsig: {chapter-signifier}
+:section-refsig: Seção
+:appendix-caption: Apêndice
+:appendix-refsig: {appendix-caption}
+:toc-title: Sumário
+:preface-title: Prefácio
+:example-caption: Exemplo
+:figure-caption: Figura
+:listing-caption: Listagem
+:table-caption: Tabela
+:untitled-label: Sem título
+:last-update-label: HTML gerado em
+:version-label: Versão
+// Substituições
+:dunder: __
+:rt-arrow: ->
+:lte: <=
+:iadd: +=
diff --git a/print/checklist.md b/print/checklist.md
new file mode 100644
index 00000000..3ed2c792
--- /dev/null
+++ b/print/checklist.md
@@ -0,0 +1,21 @@
+# Checklist do PDF para impressão vol.1 (miolo)
+
+[ ] Total de páginas no PDF: 402 páginas
+[ ] Páginas ímpares à direita (anverso)
+[ ] Numeros das página nos cantos externos
+    (esquerda no verso, direita no anverso)
+
+## Primeiras páginas:
+
+[ ] i. anverso: página de título (página sem número)
+[ ] ii. verso: copyright
+[ ] iii. anverso: dedicatória "Para Marta"
+[ ] iv. verso: dedicatória em branco
+[ ] v. anverso: Sumário (primeito item = Prefácio, p. 1)
+...
+[ ] 1. anverso: Prefácio
+
+## Últimas páginas:
+
+[ ] 393. anverso: notas do último capítulo
+[ ] 394. colofão (página sem número)
diff --git a/print/colofao.pdf b/print/colofao.pdf
new file mode 100644
index 00000000..0fc3ed58
Binary files /dev/null and b/print/colofao.pdf differ
diff --git a/print/colofao.txt b/print/colofao.txt
new file mode 100644
index 00000000..ff487d95
--- /dev/null
+++ b/print/colofao.txt
@@ -0,0 +1,3 @@
+Este livro foi composto com Asciidoctor
+usando as fontes Noto Serif e M PLUS 1mn,
+e impresso por demanda na gráfica Meta.
diff --git a/print/contra-capa.adoc b/print/contra-capa.adoc
new file mode 100644
index 00000000..54f48193
--- /dev/null
+++ b/print/contra-capa.adoc
@@ -0,0 +1,84 @@
+# Texto para a contra-capa
+
+## Python Fluente, 2ª edição
+
+Com Python você faz mais escrevendo menos código.
+Quanto mais entender seus padrões,
+interfaces, e melhores práticas,
+maior sua produtividade.
+
+Publicado em nove idiomas, _Fluent Python_
+é um sucesso internacional porque
+não tem "hello, world".
+É para quem já usa Python
+debulhar esta linguagem prática e poderosa.
+
+DOMINE as estruturas de dados nativas, para não reinventar a roda.
+
+EXPLORE ao máximo as funções, antes de criar classes.
+
+ENTENDA técnicas de concorrência e metapogramação.
+
+Essas e outras “manhas” você encontra em
+_Python Fluente_, o livro definitivo sobre Python.
+
+image::fpy.li-pf2q.png[]
+
+ 
+# Texto para orelha da contra-capa (espiral)
+
+## Python Fluente, 2ª edição
+
+### Volume 1: dados + funções
+
+#### 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
+5. Fábricas de classes de dados
+6. Referências, mutabilidade, e memória
+
+#### 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
+
+[start=9]
+9. Decoradores e Clausuras
+10. Padrões de projeto com funções
+
+#### Parte III. Classes e Protocolos
+
+[start=11]
+11. Um objeto pythônico
+12. Métodos especiais para sequências
+13. Interfaces, protocolos, e ABCs
+14. Herança: para o bem ou para o mal
+15. Mais dicas de tipo
+16. Sobrecarga de operadores
+
+### Volume 3: controle + metaprogramação
+
+#### Parte IV. Controle de fluxo
+
+[start=17]
+17. Iteradores, geradores e corrotinas clássicas
+18. Instruções with, match, e blocos else
+19. Modelos de concorrência em Python
+20. Executores concorrentes
+21. Programação assíncrona
+
+#### Parte V. Metaprogramação
+
+[start=22]
+22. Atributos dinâmicos e propriedades
+23. Descritores de atributos
+24. Metaprogramação de classes
+
diff --git a/print/deploy.sh b/print/deploy.sh
new file mode 100755
index 00000000..47f83be7
--- /dev/null
+++ b/print/deploy.sh
@@ -0,0 +1,9 @@
+
+#!/bin/bash
+set -e  # exit when any command fails
+# asciidoctor Livro.adoc -o index.html
+
+scp $1 dh_kqh7yy@pdx1-shared-a1-19.dreamhost.com:/home/dh_kqh7yy/pythonfluente.com/
+# rsync -avz --delete ../vol1/pyfl-vol1-pre1.pdf dh_kqh7yy@pythonfluente.com:~/pythonfluente.com/2/
+
+# open https://pythonfluente.com/2/
diff --git a/print/experimentos/FPY_LI_errors.txt b/print/experimentos/FPY_LI_errors.txt
new file mode 100644
index 00000000..133ffe8d
--- /dev/null
+++ b/print/experimentos/FPY_LI_errors.txt
@@ -0,0 +1,253 @@
+[   5] 302 https://www.python.org/dev/peps/pep-0484/#the-numeric-tower
+[   5]    200 https://peps.python.org/pep-0484/#the-numeric-tower
+[   7] 301 https://dask.org/
+[   7]    200 https://www.dask.org/
+[  33] 302 https://www.python.org/dev/peps/pep-0218/
+[  33]    200 https://peps.python.org/pep-0218/
+[  34] 302 https://www.python.org/dev/peps/pep-0227/
+[  34]    200 https://peps.python.org/pep-0227/
+[  35] 302 https://www.python.org/dev/peps/pep-0255/
+[  35]    200 https://peps.python.org/pep-0255/
+[  36] 302 https://www.python.org/dev/peps/pep-0342/
+[  36]    200 https://peps.python.org/pep-0342/
+[  37] 302 https://www.python.org/dev/peps/pep-0343/
+[  37]    200 https://peps.python.org/pep-0343/
+[  38] 302 https://www.python.org/dev/peps/pep-0357/
+[  38]    200 https://peps.python.org/pep-0357/
+[  39] 302 https://www.python.org/dev/peps/pep-0362/
+[  39]    200 https://peps.python.org/pep-0362/
+[  40] 302 https://www.python.org/dev/peps/pep-0371/
+[  40]    200 https://peps.python.org/pep-0371/
+[  41] 302 https://www.python.org/dev/peps/pep-0380/
+[  41]    200 https://peps.python.org/pep-0380/
+[  42] 302 https://www.python.org/dev/peps/pep-0393/
+[  42]    200 https://peps.python.org/pep-0393/
+[  43] 302 https://www.python.org/dev/peps/pep-0412/
+[  43]    200 https://peps.python.org/pep-0412/
+[  44] 302 https://www.python.org/dev/peps/pep-0442/
+[  44]    200 https://peps.python.org/pep-0442/
+[  45] 302 https://www.python.org/dev/peps/pep-0443/
+[  45]    200 https://peps.python.org/pep-0443/
+[  46] 302 https://www.python.org/dev/peps/pep-0448/
+[  46]    200 https://peps.python.org/pep-0448/
+[  47] 302 https://www.python.org/dev/peps/pep-0455/
+[  47]    200 https://peps.python.org/pep-0455/
+[  48] 302 https://www.python.org/dev/peps/pep-0456/
+[  48]    200 https://peps.python.org/pep-0456/
+[  49] 302 https://www.python.org/dev/peps/pep-0461/
+[  49]    200 https://peps.python.org/pep-0461/
+[  50] 302 https://www.python.org/dev/peps/pep-0465/
+[  50]    200 https://peps.python.org/pep-0465/
+[  51] 302 https://www.python.org/dev/peps/pep-0467/
+[  51]    200 https://peps.python.org/pep-0467/
+[  52] 302 https://www.python.org/dev/peps/pep-0482/
+[  52]    200 https://peps.python.org/pep-0482/
+[  53] 302 https://www.python.org/dev/peps/pep-0483/
+[  53]    200 https://peps.python.org/pep-0483/
+[  54] 302 https://www.python.org/dev/peps/pep-0484/
+[  54]    200 https://peps.python.org/pep-0484/
+[  55] 302 https://www.python.org/dev/peps/pep-0487/
+[  55]    200 https://peps.python.org/pep-0487/
+[  56] 302 https://www.python.org/dev/peps/pep-0492/
+[  56]    200 https://peps.python.org/pep-0492/
+[  57] 302 https://www.python.org/dev/peps/pep-0519/
+[  57]    200 https://peps.python.org/pep-0519/
+[  58] 302 https://www.python.org/dev/peps/pep-0525/
+[  58]    200 https://peps.python.org/pep-0525/
+[  59] 302 https://www.python.org/dev/peps/pep-0526/
+[  59]    200 https://peps.python.org/pep-0526/
+[  60] 302 https://www.python.org/dev/peps/pep-0528/
+[  60]    200 https://peps.python.org/pep-0528/
+[  61] 302 https://www.python.org/dev/peps/pep-0529/
+[  61]    200 https://peps.python.org/pep-0529/
+[  62] 302 https://www.python.org/dev/peps/pep-0530/
+[  62]    200 https://peps.python.org/pep-0530/
+[  63] 302 https://www.python.org/dev/peps/pep-0544/
+[  63]    200 https://peps.python.org/pep-0544/
+[  64] 302 https://www.python.org/dev/peps/pep-0554/
+[  64]    200 https://peps.python.org/pep-0554/
+[  65] 302 https://www.python.org/dev/peps/pep-0557/
+[  65]    200 https://peps.python.org/pep-0557/
+[  66] 302 https://www.python.org/dev/peps/pep-0560/
+[  66]    200 https://peps.python.org/pep-0560/
+[  67] 302 https://www.python.org/dev/peps/pep-0561/
+[  67]    200 https://peps.python.org/pep-0561/
+[  68] 302 https://www.python.org/dev/peps/pep-0563/
+[  68]    200 https://peps.python.org/pep-0563/
+[  69] 302 https://www.python.org/dev/peps/pep-0570/
+[  69]    200 https://peps.python.org/pep-0570/
+[  70] 302 https://www.python.org/dev/peps/pep-0572/
+[  70]    200 https://peps.python.org/pep-0572/
+[  71] 302 https://www.python.org/dev/peps/pep-0584/
+[  71]    200 https://peps.python.org/pep-0584/
+[  72] 302 https://www.python.org/dev/peps/pep-0585/
+[  72]    200 https://peps.python.org/pep-0585/
+[  73] 302 https://www.python.org/dev/peps/pep-0586/
+[  73]    200 https://peps.python.org/pep-0586/
+[  74] 302 https://www.python.org/dev/peps/pep-0589/
+[  74]    200 https://peps.python.org/pep-0589/
+[  75] 302 https://www.python.org/dev/peps/pep-0591/
+[  75]    200 https://peps.python.org/pep-0591/
+[  76] 302 https://www.python.org/dev/peps/pep-0593/
+[  76]    200 https://peps.python.org/pep-0593/
+[  77] 302 https://www.python.org/dev/peps/pep-0604/
+[  77]    200 https://peps.python.org/pep-0604/
+[  78] 302 https://www.python.org/dev/peps/pep-0612/
+[  78]    200 https://peps.python.org/pep-0612/
+[  79] 302 https://www.python.org/dev/peps/pep-0613/
+[  79]    200 https://peps.python.org/pep-0613/
+[  80] 302 https://www.python.org/dev/peps/pep-0616/
+[  80]    200 https://peps.python.org/pep-0616/
+[  81] 302 https://www.python.org/dev/peps/pep-0617/
+[  81]    200 https://peps.python.org/pep-0617/
+[  82] 302 https://www.python.org/dev/peps/pep-0618/
+[  82]    200 https://peps.python.org/pep-0618/
+[  83] 302 https://www.python.org/dev/peps/pep-0634/
+[  83]    200 https://peps.python.org/pep-0634/
+[  84] 302 https://www.python.org/dev/peps/pep-0635/
+[  84]    200 https://peps.python.org/pep-0635/
+[  85] 302 https://www.python.org/dev/peps/pep-0636/
+[  85]    200 https://peps.python.org/pep-0636/
+[  86] 302 https://www.python.org/dev/peps/pep-0638/
+[  86]    200 https://peps.python.org/pep-0638/
+[  87] 302 https://www.python.org/dev/peps/pep-0645/
+[  87]    200 https://peps.python.org/pep-0645/
+[  88] 302 https://www.python.org/dev/peps/pep-0646/
+[  88]    200 https://peps.python.org/pep-0646/
+[  89] 302 https://www.python.org/dev/peps/pep-0647/
+[  89]    200 https://peps.python.org/pep-0647/
+[  90] 302 https://www.python.org/dev/peps/pep-0649/
+[  90]    200 https://peps.python.org/pep-0649/
+[  91] 302 https://www.python.org/dev/peps/pep-0654/
+[  91]    200 https://peps.python.org/pep-0654/
+[  92] 302 https://www.python.org/dev/peps/pep-0655/
+[  92]    200 https://peps.python.org/pep-0655/
+[  93] 302 https://www.python.org/dev/peps/pep-0661/
+[  93]    200 https://peps.python.org/pep-0661/
+[  94] 302 https://www.python.org/dev/peps/pep-3099/
+[  94]    200 https://peps.python.org/pep-3099/
+[  95] 302 https://www.python.org/dev/peps/pep-3102/
+[  95]    200 https://peps.python.org/pep-3102/
+[  96] 302 https://www.python.org/dev/peps/pep-3104/
+[  96]    200 https://peps.python.org/pep-3104/
+[  97] 302 https://www.python.org/dev/peps/pep-3106/
+[  97]    200 https://peps.python.org/pep-3106/
+[  98] 302 https://www.python.org/dev/peps/pep-3107/
+[  98]    200 https://peps.python.org/pep-3107/
+[  99] 302 https://www.python.org/dev/peps/pep-3115/
+[  99]    200 https://peps.python.org/pep-3115/
+[ 100] 302 https://www.python.org/dev/peps/pep-3118/
+[ 100]    200 https://peps.python.org/pep-3118/
+[ 101] 302 https://www.python.org/dev/peps/pep-3119/
+[ 101]    200 https://peps.python.org/pep-3119/
+[ 102] 302 https://www.python.org/dev/peps/pep-3129/
+[ 102]    200 https://peps.python.org/pep-3129/
+[ 103] 302 https://www.python.org/dev/peps/pep-3132/
+[ 103]    200 https://peps.python.org/pep-3132/
+[ 104] 302 https://www.python.org/dev/peps/pep-3141/
+[ 104]    200 https://peps.python.org/pep-3141/
+[ 105] 302 https://www.python.org/dev/peps/pep-3148/
+[ 105]    200 https://peps.python.org/pep-3148/
+[ 106] 302 https://www.python.org/dev/peps/pep-3155/
+[ 106]    200 https://peps.python.org/pep-3155/
+[ 107] 302 https://www.python.org/dev/peps/pep-3333/
+[ 107]    200 https://peps.python.org/pep-3333/
+[ 141] 403 https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr
+[ 151] 302 https://www.python.org/dev/peps/pep-3132/
+[ 151]    200 https://peps.python.org/pep-3132/
+[ 152] 403 https://stackoverflow.com/questions/68630/are-tuples-more-efficient-than-lists-in-python/22140115#22140115
+[ 166] 403 https://stackoverflow.com/questions/4845418/when-should-a-memoryview-be-used/
+[ 172] 302 https://www.python.org/dev/peps/pep-3132/
+[ 172]    200 https://peps.python.org/pep-3132/
+[ 176] 302 https://www.python.org/dev/peps/pep-0636/#appendix-a-quick-intro
+[ 176]    200 https://peps.python.org/pep-0636/#appendix-a-quick-intro
+[ 185] 301 http://www.groklaw.net/pdf3/OraGoogle-1202.pdf
+[ 185]    404 https://www.groklaw.net/pdf3/OraGoogle-1202.pdf
+[ 187] 302 https://www.python.org/dev/peps/pep-0584/#motivation
+[ 187]    200 https://peps.python.org/pep-0584/#motivation
+[ 204] 404 http://gandenberger.org/2018/03/10/ordered-dicts-vs-ordereddict/
+[ 205] 301 https://www.pypy.org/
+[ 216] 301 https://github.com/standupdev/uintset
+[ 216]    200 https://github.com/ramalho/uintset
+[ 219] 403 https://twitter.com/mitsuhiko/status/1229385843585974272
+[ 239] 301 https://pypi.org/project/PyICU/
+[ 239]    200 https://pypi.org/project/pyicu/
+[ 250] 410 https://regebro.wordpress.com/2011/03/23/unconfusing-unicode-what-is-unicode/
+[ 282] 302 https://www.python.org/dev/peps/pep-0484/#acceptable-type-hints
+[ 282]    200 https://peps.python.org/pep-0484/#acceptable-type-hints
+[ 287] 302 https://www.python.org/dev/peps/pep-0526/#class-and-instance-variable-annotations
+[ 287]    200 https://peps.python.org/pep-0526/#class-and-instance-variable-annotations
+[ 292] 302 https://www.python.org/dev/peps/pep-0634/#class-patterns
+[ 292]    200 https://peps.python.org/pep-0634/#class-patterns
+[ 294] 302 https://www.python.org/dev/peps/pep-0557/#id47
+[ 294]    200 https://peps.python.org/pep-0557/#id47
+[ 295] 302 https://www.python.org/dev/peps/pep-0557/#id48
+[ 295]    200 https://peps.python.org/pep-0557/#id48
+[ 296] 302 https://www.python.org/dev/peps/pep-0557/#id33
+[ 296]    200 https://peps.python.org/pep-0557/#id33
+[ 322] 302 https://www.python.org/dev/peps/pep-0442/
+[ 322]    200 https://peps.python.org/pep-0442/
+[ 337] 403 https://stackoverflow.com/questions/3252228/python-why-is-functools-partial-necessary
+[ 344] 302 https://www.python.org/dev/peps/pep-0484/#non-goals
+[ 344]    200 https://peps.python.org/pep-0484/#non-goals
+[ 350] 404 https://mypy.readthedocs.io/en/stable/introduction.html
+[ 355] 308 https://wefearchange.org/2020/11/steeringcouncil.rst.html
+[ 355] *ERROR* Head "/2020/11/steeringcouncil.rst": unsupported protocol scheme ""
+[ 359] 302 https://www.python.org/dev/peps/pep-0585/#implementation
+[ 359]    200 https://peps.python.org/pep-0585/#implementation
+[ 366] 302 https://www.python.org/dev/peps/pep-0585/#implementation
+[ 366]    200 https://peps.python.org/pep-0585/#implementation
+[ 379] 302 https://www.python.org/dev/peps/pep-0484/#id38
+[ 379]    200 https://peps.python.org/pep-0484/#id38
+[ 396] 302 https://docs.python-requests.org/en/master/api/#requests.request
+[ 409] 302 https://www.python.org/dev/peps/pep-3104/
+[ 409]    200 https://peps.python.org/pep-3104/
+[ 410] 302 https://www.python.org/dev/peps/pep-0227/
+[ 410]    200 https://peps.python.org/pep-0227/
+[ 411] 302 https://www.python.org/dev/peps/pep-0443/
+[ 411]    200 https://peps.python.org/pep-0443/
+[ 454] 301 https://mail.python.org/mailman/listinfo/python-list
+[ 454]    200 https://mail.python.org/mailman3/lists/python-list.python.org/
+[ 490] 302 https://github.com/python/typeshed/blob/master/CONTRIBUTING.md
+[ 490]    200 https://github.com/python/typeshed/blob/main/CONTRIBUTING.md
+[ 492] 302 https://www.python.org/dev/peps/pep-0544/#runtime-checkable-decorator-and-narrowing-types-by-isinstance
+[ 492]    200 https://peps.python.org/pep-0544/#runtime-checkable-decorator-and-narrowing-types-by-isinstance
+[ 493] 302 https://www.python.org/dev/peps/pep-0544/#merging-and-extending-protocols
+[ 493]    200 https://peps.python.org/pep-0544/#merging-and-extending-protocols
+[ 495] 302 https://github.com/python/typeshed/blob/master/stdlib/statistics.pyi
+[ 495]    200 https://github.com/python/typeshed/blob/main/stdlib/statistics.pyi
+[ 507] 302 https://www.python.org/dev/peps/pep-3119/
+[ 507]    200 https://peps.python.org/pep-3119/
+[ 508] 302 https://www.python.org/dev/peps/pep-3141/
+[ 508]    200 https://peps.python.org/pep-3141/
+[ 511] 403 https://stackoverflow.com/questions/69334475/how-to-hint-at-number-types-i-e-subclasses-of-number-not-numbers-themselv/69383462#69383462
+[ 558] 403 https://stackoverflow.com/questions/30190185/how-to-use-super-with-one-argument/30190341#30190341
+[ 574] 403 https://twitter.com/gwidion/status/1265384692464967680
+[ 586] 302 https://www.python.org/dev/peps/pep-0484/#casts
+[ 586]    200 https://peps.python.org/pep-0484/#casts
+[ 596] 302 https://www.python.org/dev/peps/pep-0563/#abstract
+[ 596]    200 https://peps.python.org/pep-0563/#abstract
+[ 607] 302 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance
+[ 607]    200 https://peps.python.org/pep-0484/#covariance-and-contravariance
+[ 623] 302 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance
+[ 623]    200 https://peps.python.org/pep-0484/#covariance-and-contravariance
+[ 717] 302 https://www.python.org/dev/peps/pep-0634/#or-patterns
+[ 717]    200 https://peps.python.org/pep-0634/#or-patterns
+[ 723] 403 https://stackoverflow.com/questions/16138232/is-it-a-good-practice-to-use-try-except-else-in-python
+[ 755] 301 https://www.pypy.org/
+[ 881] 302 https://www.python.org/dev/peps/pep-0492/#await-expression
+[ 881]    200 https://peps.python.org/pep-0492/#await-expression
+[ 895] 403 https://stackoverflow.com/questions/53701841/what-is-the-use-case-for-future-add-done-callback/53710563
+[ 927] 302 https://www.python.org/dev/peps/pep-0654/#motivation
+[ 927]    200 https://peps.python.org/pep-0654/#motivation
+[ 941] 403 https://stackoverflow.com/questions/49482969/what-is-the-core-difference-between-asyncio-and-trio
+[ 993] 302 https://www.python.org/dev/peps/pep-0008/#class-names
+[ 993]    200 https://peps.python.org/pep-0008/#class-names
+[1000] 302 https://www.python.org/dev/peps/pep-0487/#trait-descriptors
+[1000]    200 https://peps.python.org/pep-0487/#trait-descriptors
+[1007] 302 https://www.python.org/dev/peps/pep-3155/
+[1007]    200 https://peps.python.org/pep-3155/
+[1013] 302 https://www.python.org/dev/peps/pep-0557/#abstract
+[1013]    200 https://peps.python.org/pep-0557/#abstract
+[1029] 302 https://www.python.org/dev/peps/pep-3129/
+[1029]    200 https://peps.python.org/pep-3129/
diff --git a/print/experimentos/all_urls.py b/print/experimentos/all_urls.py
new file mode 100644
index 00000000..70f51884
--- /dev/null
+++ b/print/experimentos/all_urls.py
@@ -0,0 +1,43 @@
+from glob import glob
+from pathlib import Path
+import re
+
+EXCLUDE = [re.compile(regex) for regex in (
+    r'http://fluentpython.com',
+    r'http://fluentpython.com/data/flags',
+    r'http://localhost:8000/flags',
+    r'http://localhost:8001/flags',
+    r'http://localhost:8002/flags',
+    r'http://oreilly.com'
+)]
+
+def wanted(url):
+    for regex in EXCLUDE:
+        if regex.match(url):
+            return False
+    if 'fpy.li/' in url:
+        return False
+    if '$$' in url:
+        return False
+    return True
+
+
+______ = re.compile(r'(https?://[^[]{1,80}?)\${0,2}\[(.*?)\]')
+URL_RE = re.compile(r'(https?://[^\["`$\n]+)([\["])')
+
+start = 27
+
+seen = set()
+
+for path in sorted(glob('1/*.adoc')): # [start:start+3]:
+    text = Path(path).read_text()
+    matches = URL_RE.findall(text)
+    if not matches: continue
+    # print('_' * 60 + path)
+    for url, delim in matches:
+        assert '\n' not in url
+        # if wanted(url):
+        #     print(f'{url}\t{delim}')
+        if (url not in seen) and wanted(url):
+            print(url)
+            seen.add(url)
diff --git a/print/experimentos/check_urls.py b/print/experimentos/check_urls.py
new file mode 100755
index 00000000..d18436a7
--- /dev/null
+++ b/print/experimentos/check_urls.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+import fileinput
+import asyncio
+import httpx
+import sys
+
+from typing import NamedTuple
+
+
+class StatusURL(NamedTuple):
+    status: int
+    url: str
+    exception: Exception | None = None
+
+
+async def check_url(client: httpx.AsyncClient, start_url: str) -> list[StatusURL]:
+    req = client.build_request("GET", start_url)
+    results = []
+    while req is not None:
+        try:
+            http_resp = await client.send(req)
+            result = StatusURL(http_resp.status_code, req.url)
+        except httpx.HTTPError as e:
+            result = StatusURL(0, req.url, e)
+        else:
+            req = http_resp.next_request
+        results.append(result)
+
+    assert len(results) > 0, 'NO results: ' + start_url
+    return results
+
+
+async def main() -> None:
+    urls = sorted({u for l in fileinput.input() if (u := l.strip())})
+    async with httpx.AsyncClient() as client:
+        tasks = [check_url(client, url) for url in urls]
+        async for task in asyncio.as_completed(tasks):
+            results = await task
+            for n, result in enumerate(results):
+                url = str(result.url)[:70]
+                print(f'{" "*4*n}{result.status} {url}')
+
+if __name__ == "__main__":
+    asyncio.run(main())
\ No newline at end of file
diff --git a/print/experimentos/fix_pythonfluente_urls.py b/print/experimentos/fix_pythonfluente_urls.py
new file mode 100755
index 00000000..e686910a
--- /dev/null
+++ b/print/experimentos/fix_pythonfluente_urls.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+
+"""
+In the volume 1 *.adoc files, find links in this form:
+
+```
+fpy.li/2p?{ref}[{text}] (vol. {vol}, cap. {ch_num})
+```
+
+Replace them with:
+
+```
+_{text} (vol.{vol}, cap.{ch_num}, fpy.li/{su})_
+```
+
+(vol.2, cap.13, fpy.li/q7)
+
+Where {su} is the short URL for a URL to `pythonfluente.com`, such as:
+
+```
+https://pythonfluente.com/2/#{ref}
+```
+
+# Steps
+
+1. Edited chapters with those links to isolate the few relevant links into
+their own lines.
+
+2. Used `grep fpy.li/2p 1/*.adoc > linkspyfl-web.txt` to follect those links.
+
+3. Wrote list_pyfl_links(), use it to write `pyfl_refs.txt` 
+
+```
+./fix_pythonfluente_urls.py > pyfl_refs.txt
+```
+
+4. Sent `pyfl_refs.txt` to `short.py` from the
+[Fluent Python example code repo](https://github.com/fluentpython/example-code-2e/tree/cf996500070e0d712a3b088d29428f74ddc9fa1f/links) 
+
+"""
+
+import re
+
+
+ADOC_LINK_RE = re.compile(r'(.*?)\[(.*)]')
+
+PYFL_BASE = 'https://pythonfluente.com/2/'
+
+def list_pyfl_links():
+    """Make list of links to PythonFluente.com"""
+    with open('links-pyfl-web.txt') as fp:
+        lines = fp.readlines()
+    
+    for line in lines:
+        chapter_path, link = line.split(':', maxsplit=1)
+        res = ADOC_LINK_RE.match(link)
+        if res is None:
+            print('NO MATCH:', line)
+            continue
+        else:
+            url, text = res.groups()
+            _, ref = url.split('?')
+            #print(ref, text, sep='\t')
+            print(PYFL_BASE + '#' + ref)
+
+if __name__ == '__main__':
+    list_pyfl_links()
diff --git a/print/experimentos/fix_xrefs_for_print.py b/print/experimentos/fix_xrefs_for_print.py
new file mode 100755
index 00000000..d18fcbd6
--- /dev/null
+++ b/print/experimentos/fix_xrefs_for_print.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+
+import configparser
+import os
+import re
+import shutil
+import subprocess
+import sys
+from glob import glob
+from pathlib import Path
+
+ANCHORS_PATH = Path('../ferramentas/anchors.ini')
+ANCHOR_RE = re.compile(r'\[(\[[\w-]+\])\]\n+(?:\[[\w=" -]+\]\n)?=* ?(.*)')
+
+VOLUME_MAP = {
+    1 : {
+        1 : 'ch_data_model',
+        2 : 'ch_sequences',
+        3 : 'ch_dicts_sets',
+        4 : 'ch_str_bytes',
+        5 : 'ch_dataclass',
+        6 : 'ch_refs_mut_mem',
+    },
+    2 : {
+        7 : 'ch_func_objects',
+        8 : 'ch_type_hints_def',
+        9 : 'ch_closure_decorator',
+        10 : 'ch_design_patterns',
+        11 : 'ch_pythonic_obj',
+        12 : 'ch_seq_methods',
+        13 : 'ch_ifaces_prot_abc',
+        14 : 'ch_inheritance',
+        15 : 'ch_more_types',
+        16 : 'ch_op_overload',
+    },
+    3 : {
+        17 : 'ch_generators',
+        18 : 'ch_with_match',
+        19 : 'ch_concurrency_models',
+        20 : 'ch_executors',
+        21 : 'ch_async',
+        22 : 'ch_dynamic_attrs',
+        23 : 'ch_descriptors',
+        24 : 'ch_class_metaprog',
+    }
+}
+
+CHAPTERS_BY_LABEL = {VOLUME_MAP[vo][ch] : (vo, ch) for vo in VOLUME_MAP for ch in VOLUME_MAP[vo]}
+CHAPTERS_BY_NUMBER = {CHAPTERS_BY_LABEL[label][1] : (CHAPTERS_BY_LABEL[label][0], label) for label in CHAPTERS_BY_LABEL}
+
+def list_invalid_refs():
+    command = 'asciidoctor -v vol1.adoc'
+    output = subprocess.run(command, shell=True, stderr=subprocess.PIPE, text=True)
+    assert output.returncode == 0
+    res = set()
+    for line in output.stderr.splitlines():
+        if 'invalid reference' in line:
+            res.add(line.split()[-1])
+        else:
+            print('*** Unexpected output:', line, file=sys.stderr)
+            sys.exit(1)
+    return sorted(res)
+
+
+def find_anchors(path):
+    """Encontra todas as âncoras nos AsciiDoc de um path"""
+    with open(path) as fp:
+        text = fp.read()
+    return ANCHOR_RE.findall(text)
+
+
+def load_anchors():
+    config = configparser.ConfigParser()
+    config.read(ANCHORS_PATH, encoding='utf-8')
+    ch_count = len(config.sections())
+    assert ch_count == 24, f'Expected 24 sections (chapters), found {ch_count}'
+    result = {}
+    for n, ch_id in enumerate(config.sections(), 1):
+        section = config[ch_id]
+        for key, value in section.items():
+            #print(f"  {key} = {value}")
+            assert key not in result, f"Duplicate key {key!r}: {value!r}"
+            result[key] = (n, value)
+    return result
+
+
+def backup(filename: str) -> None:
+    msg = ''
+    copy_name = filename + '.bkp'
+    try:
+        shutil.copy2(filename, copy_name)
+    except FileNotFoundError:
+        msg = f'File {filename!r} not found.'
+    if not msg and not os.path.exists(copy_name):
+        msg = f'Unable to create {copy_name!r}.'
+    if msg:
+        print(msg, file=sys.stderr)
+        sys.exit(1)
+
+
+def replace(subs, filename: str) -> None:
+    with open(filename) as fp:
+        adoc = fp.read()
+
+    for ref, new_text in subs.items():
+        adoc = adoc.replace(ref, new_text)
+
+    with open(filename, 'w') as fp:
+        fp.write(adoc)
+
+
+def expand_anchor(ref, anchors):
+    assert ref in anchors, f'Anchor {ref!r} not found in anchors dict'
+    ch_num, text = anchors[ref]
+    vol, _ = CHAPTERS_BY_NUMBER[ch_num]
+    return f'fpy.li/2p?{ref}[{text}] (vol. {vol}, cap. {ch_num})'
+
+
+def build_substitutions(refs, anchors):
+    subs = {}
+    for ref in refs:
+        key = f'<<{ref}>>'
+        if ref.startswith('ch_') or ref.startswith('vo_'):
+            value = '_{' + ref + '}_'
+        else:
+            value = expand_anchor(ref, anchors)
+        subs[key] = value
+
+    return subs
+
+
+def main():
+    irefs = list_invalid_refs()
+    anchors = load_anchors()
+    subs = build_substitutions(irefs, anchors)
+    for filename in glob('1/*.adoc'):
+        print('processing', filename)
+        backup(filename)
+        replace(subs, filename)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/print/experimentos/icml_export.sh b/print/experimentos/icml_export.sh
new file mode 100755
index 00000000..474cf255
--- /dev/null
+++ b/print/experimentos/icml_export.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+asciidoctor -b docbook $1.adoc
+pandoc -s -f docbook -t icml -o $1.icml $1.xml
diff --git a/print/experimentos/links-pyfl-web.txt b/print/experimentos/links-pyfl-web.txt
new file mode 100644
index 00000000..4a9760f2
--- /dev/null
+++ b/print/experimentos/links-pyfl-web.txt
@@ -0,0 +1,13 @@
+1/cap02.adoc:fpy.li/2p?pattern_matching_case_study_sec[Pattern matching no lis.py: um estudo de caso] (vol. 4, cap. 18).
+1/cap02.adoc:fpy.li/2p?pattern_matching_case_study_sec[Pattern matching no lis.py: um estudo de caso] (vol. 4, cap. 18)
+1/cap02.adoc:fpy.li/2p?pattern_matching_case_study_sec[Pattern matching no lis.py: um estudo de caso] (vol. 4, cap. 18),
+1/cap02.adoc:fpy.li/2p?how_slicing_works[Como funciona o fatiamento] (vol. 3, cap. 12), 
+1/cap02.adoc:fpy.li/2p?sliceable_sequence[Vector versão #2: Uma sequência fatiável] (vol. 3, cap. 12).
+1/cap03.adoc:fpy.li/2p?virtual_subclass_sec[Uma subclasse virtual de uma ABC] (vol. 3, cap. 13).
+1/cap03.adoc:fpy.li/2p?environment_class_ex[_lis.py_: a classe `Environment`] (vol. 4, cap. 18)
+1/cap03.adoc:fpy.li/2p?subclass_builtin_woes[É complicado criar subclasses de tipos embutidos] (vol. 3, cap. 14).]
+1/cap03.adoc:fpy.li/2p?slots_section[Economizando memória com pass:[__slots__]] (vol. 3, cap. 11).]
+1/cap05.adoc:fpy.li/2p?typeddict_sec[TypedDict] (vol. 3, cap. 15).
+1/cap05.adoc:fpy.li/2p?problems_annot_runtime_sec[Problemas com anotações durante a execução] (vol. 3, cap. 15).
+1/cap05.adoc:fpy.li/2p?legacy_deprecated_typing_box[Suporte a tipos de coleção descontinuados] (vol. 2, cap. 8).
+1/cap05.adoc:fpy.li/2p?positional_pattern_implement_sec[Suportando o pattern matching posicional] (vol. 3, cap. 11)
diff --git a/print/experimentos/notas.txt b/print/experimentos/notas.txt
new file mode 100644
index 00000000..7ef74125
--- /dev/null
+++ b/print/experimentos/notas.txt
@@ -0,0 +1,24 @@
+# Notas para publicar em volumes
+
+## Remover todos os passthroughs
+
+Perigo: pass:[]!!!
+
+## Referências para capítulos ou partes de outros volumes
+
+Converter <> para _{ch_data_model}_
+
+## Referências para seções, exemplos ou figuras de outros volumes
+
+Converter (note "na" -> "em"):
+
+na <>
+
+(renderizada como "Seção 12.5.1")
+
+para isso:
+
+em _Como funciona o fatiamento_ (fpy.li/2bhwsl)
+
+onde fpy.li/2bhwsl é esta URL encurtada, direto para a seção:
+(https://pythonfluente.com/2e/#how_slicing_works)
\ No newline at end of file
diff --git a/print/experimentos/prep_print.py b/print/experimentos/prep_print.py
new file mode 100755
index 00000000..92727a5c
--- /dev/null
+++ b/print/experimentos/prep_print.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+
+import fileinput
+import re
+
+from collections.abc import Iterable, Iterator
+
+ADOC_LINK_RE = re.compile(r'https?:[^\[]+[^\]]+')
+ADOC_PASS_RE = re.compile(r'pass:\[([^\]]+)')
+
+
+def block_parser(iter_lines: Iterable[str]) -> Iterator[tuple[bool, str]]:
+    in_block = False
+    delim = ''
+    for line in iter_lines:
+        line = line.rstrip()
+        if line.startswith('[source'):
+            assert not in_block, f'[source... in_block block {line!r}'
+            in_block = True
+        elif in_block and delim == '':
+            assert len(line) == 4 and line.startswith(line[0] * 4), f'expected block delimiter, found {line!r}'
+            delim = line
+        elif in_block and line == delim:
+            in_block = False
+            delim = ''
+        yield in_block, line
+
+def prepare():
+    for in_block, line in block_parser(fileinput.input()):
+        if not in_block and len(line) > 80:
+            if found := ADOC_PASS_RE.search(line):
+                print(found.group(1))
+                print()
+
+
+
+if __name__ == '__main__':
+    prepare()
diff --git a/print/experimentos/pyfl_refs.txt b/print/experimentos/pyfl_refs.txt
new file mode 100644
index 00000000..0b3ad882
--- /dev/null
+++ b/print/experimentos/pyfl_refs.txt
@@ -0,0 +1,13 @@
+https://pythonfluente.com/2/#pattern_matching_case_study_sec
+https://pythonfluente.com/2/#pattern_matching_case_study_sec
+https://pythonfluente.com/2/#pattern_matching_case_study_sec
+https://pythonfluente.com/2/#how_slicing_works
+https://pythonfluente.com/2/#sliceable_sequence
+https://pythonfluente.com/2/#virtual_subclass_sec
+https://pythonfluente.com/2/#environment_class_ex
+https://pythonfluente.com/2/#subclass_builtin_woes
+https://pythonfluente.com/2/#slots_section
+https://pythonfluente.com/2/#typeddict_sec
+https://pythonfluente.com/2/#problems_annot_runtime_sec
+https://pythonfluente.com/2/#legacy_deprecated_typing_box
+https://pythonfluente.com/2/#positional_pattern_implement_sec
diff --git a/print/experimentos/sample-urls.txt b/print/experimentos/sample-urls.txt
new file mode 100644
index 00000000..37c442c6
--- /dev/null
+++ b/print/experimentos/sample-urls.txt
@@ -0,0 +1,46 @@
+https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/
+https://dask.org/
+http://www.unicode.org/
+https://www.techcrunch.com/2024/startup-funding-trends
+https://blog.medium.com/writing-tips-for-beginners
+https://github.com/microsoft/typescript
+https://stackoverflow.com/questions/javascript-async-await
+https://www.reddit.com/r/programming/hot
+https://docs.google.com/spreadsheets/create
+https://www.youtube.com/watch?v=dQw4w9WgXcQ
+https://www.amazon.com/dp/B08N5WRWNW
+https://support.apple.com/iphone-setup-guide
+https://www.wikipedia.org/wiki/Machine_Learning
+https://www.linkedin.com/in/johndoe123
+https://www.instagram.com/p/CxYz123AbC/
+https://twitter.com/elonmusk/status/1234567890
+https://www.facebook.com/events/987654321
+https://drive.google.com/file/d/1AbCdEfGhIjKlMnOp/view
+https://www.dropbox.com/s/qwerty123/document.pdf
+https://zoom.us/j/1234567890?pwd=abcdef
+https://calendly.com/janedoe/30min-meeting
+https://www.shopify.com/admin/products/new
+https://stripe.com/docs/api/charges/create
+https://www.paypal.com/invoice/create
+https://mailchimp.com/campaigns/dashboard
+https://analytics.google.com/analytics/web/
+https://console.aws.amazon.com/s3/buckets
+https://portal.azure.com/dashboard
+https://www.figma.com/file/AbCdEf123456/design-system
+https://www.notion.so/workspace/project-notes
+https://trello.com/b/AbCdEfGh/marketing-board
+https://slack.com/app_redirect?channel=general
+https://discord.gg/AbCdEfGh123
+https://www.twitch.tv/streamername/videos
+https://www.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M
+https://www.netflix.com/browse/genre/83
+https://www.hulu.com/series/breaking-bad-2008
+https://www.airbnb.com/rooms/12345678
+https://www.booking.com/hotel/us/grand-plaza.html
+https://www.expedia.com/flights/search?trip=roundtrip
+https://www.uber.com/ride/request
+https://www.doordash.com/store/pizza-palace-123
+https://www.grubhub.com/restaurant/tacos-el-rey-456
+https://www.zillow.com/homes/for_sale/San-Francisco-CA
+https://www.craigslist.org/about/sites
+https://www.python.org/dev/peps/pep-0484/
\ No newline at end of file
diff --git a/print/experimentos/sort_check_output.py b/print/experimentos/sort_check_output.py
new file mode 100755
index 00000000..18968010
--- /dev/null
+++ b/print/experimentos/sort_check_output.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python3
+
+import fileinput
+import re
+
+TASK_RE = re.compile(r'\[\s*(\d+)\]')
+
+def task_id(line):
+    found = TASK_RE.match(line)
+    return int(match.group(1)) if found else 0
+
+for line in sorted(fileinput.input(), key=task_id):
+    print(line, end='')
diff --git a/print/experimentos/vol1-results-orca.txt b/print/experimentos/vol1-results-orca.txt
new file mode 100644
index 00000000..ef9234e2
--- /dev/null
+++ b/print/experimentos/vol1-results-orca.txt
@@ -0,0 +1,6 @@
+[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028)
+URL: https://dabeaz.com/per.html
+-1 https://dabeaz.com/per.html
+302 http://gopl.io
+302 https://pythonfluente.com/#iter_closer_look
+301 https://mitpress.mit.edu/books/art-metaobject-protocol
diff --git a/print/experimentos/vol1-url-checked.txt b/print/experimentos/vol1-url-checked.txt
new file mode 100644
index 00000000..1e32fdfe
--- /dev/null
+++ b/print/experimentos/vol1-url-checked.txt
@@ -0,0 +1,10 @@
+[   3] 302 https://pythonfluente.com/#iter_closer_look
+[   3]    200 https://pythonfluente.com/2/
+[   8] Error connecting to https://dabeaz.com/per.html: Get "https://dabeaz.com/per.html": tls: failed to verify certificate: x509: certificate signed by unknown authority
+[   9] 301 https://mitpress.mit.edu/books/art-metaobject-protocol
+[   9]    301 https://mitpress.mit.edu/9780262111584
+[   9]       301 https://mitpress.mit.edu/9780262111584/
+[   9]          200 https://mitpress.mit.edu/9780262111584/the-art-of-the-metaobject-protocol/
+[  26] 302 http://gopl.io
+[  26]    302 https://gopl.io/
+[  26]       200 http://www.gopl.io/
diff --git a/print/experimentos/vol1-urls-checked.txt b/print/experimentos/vol1-urls-checked.txt
new file mode 100644
index 00000000..214a7ff4
--- /dev/null
+++ b/print/experimentos/vol1-urls-checked.txt
@@ -0,0 +1,10 @@
+[   3] 302 https://pythonfluente.com/#iter_closer_look
+[   3]    200 https://pythonfluente.com/2/#iter_closer_look
+[   8] *ERROR* Get "https://dabeaz.com/per.html": tls: failed to verify certificate: x509: certificate signed by unknown authority
+[   9] 301 https://mitpress.mit.edu/books/art-metaobject-protocol
+[   9]    301 https://mitpress.mit.edu/9780262111584
+[   9]       301 https://mitpress.mit.edu/9780262111584/
+[   9]          200 https://mitpress.mit.edu/9780262111584/the-art-of-the-metaobject-protocol/
+[  26] 302 http://gopl.io
+[  26]    302 https://gopl.io/
+[  26]       200 http://www.gopl.io/
diff --git a/print/experimentos/vol1-urls.txt b/print/experimentos/vol1-urls.txt
new file mode 100644
index 00000000..9ff17b01
--- /dev/null
+++ b/print/experimentos/vol1-urls.txt
@@ -0,0 +1,51 @@
+https://docs.python.org/pt-br/3/reference/lexical_analysis.html#reserved-classes-of-identifiers
+https://docs.python.org/pt-br/3/library/doctest.html
+https://pythonfluente.com/#iter_closer_look
+https://docs.python.org/pt-br/3.10/library/string.html#format-string-syntax
+https://docs.python.org/pt-br/3/library/stdtypes.html#truth
+https://docs.python.org/pt-br/3/tutorial/controlflow.html#unpacking-argument-lists
+https://docs.python.org/pt-br/3/reference/datamodel.html
+https://dabeaz.com/per.html
+https://mitpress.mit.edu/books/art-metaobject-protocol
+https://plone.org.br/
+https://docs.python.org/pt-br/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations
+https://docs.python.org/pt-br/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching
+https://docs.python.org/pt-br/3.10/whatsnew/3.10.html
+https://docs.python.org/pt-br/3/library/bisect.html#bisect.insort
+https://docs.python.org/pt-br/3/howto/sorting.html
+https://docs.python.org/pt-br/3/library/collections.html
+https://docs.python.org/pt-br/3/reference/datamodel.html#objects-values-and-types
+https://pt.wikipedia.org/wiki/Timsort
+https://docs.python.org/pt-br/3.10/c-api/typeobj.html#Py_TPFLAGS_MAPPING
+https://docs.python.org/pt-br/3/glossary.html#term-hashable
+https://docs.python.org/pt-br/3/library/collections.html#collections.ChainMap
+https://docs.python.org/pt-br/3/library/collections.html#collections.Counter
+https://docs.python.org/pt-br/3/library/shelve.html
+https://docs.python.org/pt-br/3/library/dbm.html
+https://docs.python.org/pt-br/3/library/pickle.html
+http://gopl.io
+https://docs.python.org/pt-br/3/library/codecs.html#codecs.register_error
+https://docs.python.org/pt-br/3/library/codecs.html#encodings-and-unicode
+https://docs.python.org/pt-br/3/using/cmdline.html#envvar-PYTHONIOENCODING
+https://docs.python.org/pt-br/3/using/cmdline.html#envvar-PYTHONLEGACYWINDOWSSTDIO
+https://docs.python.org/pt-br/3/library/locale.html#locale.getpreferredencoding
+https://docs.python.org/pt-br/3/library/locale.html?highlight=strxfrm#locale.strxfrm
+https://docs.python.org/pt-br/3.10/library/stdtypes.html#str.isalpha
+https://docs.python.org/pt-br/3/library/unicodedata.html
+https://docs.python.org/pt-br/3/reference/lexical_analysis.html#string-literal-concatenation
+https://docs.python.org/pt-br/3/library/re.html
+https://docs.python.org/pt-br/3/howto/unicode.html
+https://docs.python.org/pt-br/3/library/codecs.html#standard-encodings
+https://docs.python.org/pt-br/3/library/typing.html#typing.TypedDict
+https://docs.python.org/pt-br/3.10/library/inspect.html#inspect.get_annotations
+https://docs.python.org/pt-br/3/library/typing.html#typing.get_type_hints
+https://docs.python.org/pt-br/3.8/library/collections.html#collections.somenamedtuple._asdict
+https://docs.python.org/pt-br/3/library/dataclasses.html#dataclasses.dataclass
+https://docs.python.org/pt-br/3/library/dataclasses.html
+https://docs.python.org/pt-br/3/library/dataclasses.html#inheritance
+https://docs.python.org/pt-br/3/library/dataclasses.html#init-only-variables
+https://pt.wikipedia.org/wiki/Dublin_Core
+https://martinfowler.com/books/refactoring.html
+https://docs.python.org/pt-br/3/library/copy.html
+https://docs.python.org/pt-br/3/reference/datamodel.html#object.%5C_%5C_del__
+https://docs.python.org/pt-br/3/library/gc.html
diff --git a/print/fonts/NotoSansMath-Regular.ttf b/print/fonts/NotoSansMath-Regular.ttf
new file mode 100644
index 00000000..268f9ec6
Binary files /dev/null and b/print/fonts/NotoSansMath-Regular.ttf differ
diff --git a/print/fonts/NotoSerif-Bold.ttf b/print/fonts/NotoSerif-Bold.ttf
new file mode 100644
index 00000000..519d3135
Binary files /dev/null and b/print/fonts/NotoSerif-Bold.ttf differ
diff --git a/print/fonts/NotoSerif-BoldItalic.ttf b/print/fonts/NotoSerif-BoldItalic.ttf
new file mode 100644
index 00000000..23ea70aa
Binary files /dev/null and b/print/fonts/NotoSerif-BoldItalic.ttf differ
diff --git a/print/fonts/NotoSerif-CondensedBold.ttf b/print/fonts/NotoSerif-CondensedBold.ttf
new file mode 100644
index 00000000..c302ac98
Binary files /dev/null and b/print/fonts/NotoSerif-CondensedBold.ttf differ
diff --git a/print/fonts/NotoSerif-CondensedBoldItalic.ttf b/print/fonts/NotoSerif-CondensedBoldItalic.ttf
new file mode 100644
index 00000000..f6f723b7
Binary files /dev/null and b/print/fonts/NotoSerif-CondensedBoldItalic.ttf differ
diff --git a/print/fonts/NotoSerif-Italic.ttf b/print/fonts/NotoSerif-Italic.ttf
new file mode 100644
index 00000000..c3ccb5fe
Binary files /dev/null and b/print/fonts/NotoSerif-Italic.ttf differ
diff --git a/print/fonts/NotoSerif-Regular.ttf b/print/fonts/NotoSerif-Regular.ttf
new file mode 100644
index 00000000..1b13cd72
Binary files /dev/null and b/print/fonts/NotoSerif-Regular.ttf differ
diff --git a/print/fonts/mplus1mn-bold-subset.ttf b/print/fonts/mplus1mn-bold-subset.ttf
new file mode 100644
index 00000000..816e62f4
Binary files /dev/null and b/print/fonts/mplus1mn-bold-subset.ttf differ
diff --git a/print/fonts/mplus1mn-bold_italic-subset.ttf b/print/fonts/mplus1mn-bold_italic-subset.ttf
new file mode 100644
index 00000000..b8f40256
Binary files /dev/null and b/print/fonts/mplus1mn-bold_italic-subset.ttf differ
diff --git a/print/fonts/mplus1mn-italic-subset.ttf b/print/fonts/mplus1mn-italic-subset.ttf
new file mode 100644
index 00000000..63d3d3ac
Binary files /dev/null and b/print/fonts/mplus1mn-italic-subset.ttf differ
diff --git a/print/fonts/mplus1mn-regular-subset.ttf b/print/fonts/mplus1mn-regular-subset.ttf
new file mode 100644
index 00000000..64dd8841
Binary files /dev/null and b/print/fonts/mplus1mn-regular-subset.ttf differ
diff --git a/print/fonts/mplus1p-regular-fallback.ttf b/print/fonts/mplus1p-regular-fallback.ttf
new file mode 100644
index 00000000..6746815e
Binary files /dev/null and b/print/fonts/mplus1p-regular-fallback.ttf differ
diff --git a/print/fonts/notoserif-bold-subset.ttf b/print/fonts/notoserif-bold-subset.ttf
new file mode 100644
index 00000000..9e50e12e
Binary files /dev/null and b/print/fonts/notoserif-bold-subset.ttf differ
diff --git a/print/fonts/notoserif-bold_italic-subset.ttf b/print/fonts/notoserif-bold_italic-subset.ttf
new file mode 100644
index 00000000..bedb61ff
Binary files /dev/null and b/print/fonts/notoserif-bold_italic-subset.ttf differ
diff --git a/print/fonts/notoserif-italic-subset.ttf b/print/fonts/notoserif-italic-subset.ttf
new file mode 100644
index 00000000..0e377259
Binary files /dev/null and b/print/fonts/notoserif-italic-subset.ttf differ
diff --git a/print/fonts/notoserif-regular-subset.ttf b/print/fonts/notoserif-regular-subset.ttf
new file mode 100644
index 00000000..8c0ee1ce
Binary files /dev/null and b/print/fonts/notoserif-regular-subset.ttf differ
diff --git a/print/fpy.li-pf2q.png b/print/fpy.li-pf2q.png
new file mode 100644
index 00000000..45428944
Binary files /dev/null and b/print/fpy.li-pf2q.png differ
diff --git a/print/hide-uri-scheme/README.md b/print/hide-uri-scheme/README.md
new file mode 100644
index 00000000..a37ebd62
--- /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 00000000..3872f1ca
--- /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 00000000..bd03f2ed
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 00000000..718de7fa
--- /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/pdf_export.sh b/print/pdf_export.sh
new file mode 100755
index 00000000..30162ed0
--- /dev/null
+++ b/print/pdf_export.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -e  # exit when any command fails
+name="${1%.*}-miolo.pdf"
+bundle exec asciidoctor-pdf -v --theme pyfl-fontmix-theme.yml -a pdf-fontsdir=./fonts/ $1
+# pdfunite "${1%.*}.pdf" colofao.pdf $name
+# open $name
+echo "confira https://hexapdf.gettalong.org/"
+echo $name
+
diff --git a/print/pyfl-fontmix-theme.yml b/print/pyfl-fontmix-theme.yml
new file mode 100644
index 00000000..8bb05632
--- /dev/null
+++ b/print/pyfl-fontmix-theme.yml
@@ -0,0 +1,19 @@
+extends: pyfl
+
+font:
+  catalog:
+    merge: true
+    Noto Serif:
+      normal: notoserif-regular-subset.ttf
+      bold: NotoSerif-CondensedBold.ttf
+      italic: notoserif-italic-subset.ttf
+      bold_italic: NotoSerif-CondensedBoldItalic.ttf
+    Noto Math: NotoSansMath-Regular.ttf
+    M+ 1mn:
+      normal: mplus1mn-regular-subset.ttf
+      bold: mplus1mn-bold-subset.ttf
+      italic: mplus1mn-italic-subset.ttf
+      bold_italic: mplus1mn-bold_italic-subset.ttf
+    M+ 1p Fallback: mplus1p-regular-fallback.ttf
+    fallbacks: [Noto Math, M+ 1p Fallback]
+
diff --git a/print/pyfl-theme.yml b/print/pyfl-theme.yml
new file mode 100644
index 00000000..117a16f8
--- /dev/null
+++ b/print/pyfl-theme.yml
@@ -0,0 +1,36 @@
+extends: default-for-print
+
+page:
+  margin: [18mm, 17mm, 18mm, 16mm]
+  margin-inner: 17mm
+  margin-outer: 16mm
+  numbering:
+    start-at: after-toc
+
+base:
+  text_align: left
+  font_family: Noto Serif
+  font_size: 10pt
+
+code:
+  font_size: 10pt
+  padding: $code_font_size
+
+example:
+  padding: 0
+
+quote:
+  font_size: $base_font_size
+  font_style: italic
+
+footer:
+  recto:
+    left:
+      content: '{section-or-chapter-title}'
+    right:
+      content: '{page-number}'
+  verso:
+    left:
+      content: $footer_recto_right_content
+    right:
+      content: '{chapter-title}'
diff --git a/print/qr-fpy.li_pf2q.png b/print/qr-fpy.li_pf2q.png
new file mode 100644
index 00000000..631be3e8
Binary files /dev/null and b/print/qr-fpy.li_pf2q.png differ
diff --git a/print/xrefs/experiments/filter_xrefs.py b/print/xrefs/experiments/filter_xrefs.py
new file mode 100755
index 00000000..8e23e10d
--- /dev/null
+++ b/print/xrefs/experiments/filter_xrefs.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+
+"""
+This script takes the output of this:
+
+```
+./pdf_export.sh ../vol1/vol1.adoc 2> invalid-xrefs.txt
+```
+
+and lists the xrefs, once each, in the order they appear.
+
+"""
+
+import sys
+
+
+
+def good_id(xref):
+    return any([
+        xref.startswith('ch_'),
+        xref.endswith('_ex'),
+        xref.endswith('_sec'),
+        xref.endswith('_part'),
+        xref.endswith('_tbl'),
+    ])
+
+
+PREFIX = 'asciidoctor: INFO: possible invalid reference: '
+
+
+def main():
+    # see this module's docstring for input file
+    adoc_msgs = sys.argv[1]
+    with open(adoc_msgs) as fp:
+        msgs = fp.readlines()
+
+    seen = set()
+    xrefs = []
+    for line in (m for m in msgs if m.startswith(PREFIX)):
+        xref = line[len(PREFIX):].strip()
+        if xref in seen:
+            continue
+        xrefs.append(xref)
+        seen.add(xref)
+
+    for xref in xrefs:
+        if not good_id(xref):
+            print('???', end='')
+        print(xref)
+
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file
diff --git a/print/xrefs/experiments/formatos.adoc b/print/xrefs/experiments/formatos.adoc
new file mode 100644
index 00000000..8e34d35e
--- /dev/null
+++ b/print/xrefs/experiments/formatos.adoc
@@ -0,0 +1,112 @@
+== Formato de link externo
+
+Enquanto o defeito 
+https://github.com/asciidoctor/asciidoctor/issues/4696
+não é resolvido...
+
+[source, asciidoctor]
+----
+O sinalizador da nova https://fpy.li/2f["sintaxe de strings de formato"]
+usada nas _f-strings_ e...
+----
+
+Saída:
+
+O sinalizador da nova «sintaxe de strings de formato» [fpy.li/2f]
+usada nas _f-strings_ e...
+
+Regex:
+
+----
+  de: https://(fpy.li/.*?)\[("?)(.+?)\2\]
+para: «$3» [.small]#[$1]#
+----
+
+== Formatos de xref
+
+=== Xrefs dentro do volume
+
+==== Alvo: capítulo
+
+Ex. do cap01:
+
+[source, asciidoctor]
+----
+<>
+----
+
+Saída:
+
+Capítulo 2
+
+==== Alvo: seção
+
+Ex. do cap02:
+
+[source, asciidoctor]
+----
+<>
+----
+
+Saída:
+
+Seção 2.6
+
+=== Xrefs outro volume
+
+==== Alvo: capítulo
+
+Ex. do cap01, l.745:
+
+[source, asciidoctor]
+----
+<>
+----
+
+Saída:
+
+Capítulo 12 [vol.2, fpy.li/xyz]
+
+Onde fpy.li/xyz direciona para:
+
+https://pythonfluente.com/2/#ch_seq_methods
+
+
+==== Alvo: seção
+
+Ex. do cap05, l.42
+
+[source, asciidoctor]
+----
+Veremos mais sobre isso na <>.
+----
+
+[source, html]
+----
+

15.3. TypedDict

+---- + +Saída: + +Veremos mais sobre isso na Seção 15.3 [vol.2, fpy.li/xyz]. + +Onde fpy.li/xyz direciona para: + +https://pythonfluente.com/2/#typeddict_sec + + +=== Alvo: exemplo + +Ex. do cap03, l.825 + +[source, asciidoctor] +---- +O <> mostra uma subclasse +---- + +Decisão: no volume 1 havia só 3 referências para exemplos em outros volumes, +a solução é complexa, e a numeração de exemplos não inclui o número capítulo, +então editei tais ocorrências para apontar para a seção que +contém o exemplo. + + diff --git a/print/xrefs/experiments/list_targets.py b/print/xrefs/experiments/list_targets.py new file mode 100755 index 00000000..794fb053 --- /dev/null +++ b/print/xrefs/experiments/list_targets.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 + +import sys +import os + + +def vol_of_chapter(ch: int) -> int: + return (ch-1) // 8 + 1 + + +def main(adoc_names): + for adoc_name in sorted(adoc_names): + ch = int(os.path.basename(adoc_name)[3:5]) + with open(adoc_name) as adoc: + lines = adoc.readlines() + for line in lines: + if line.startswith('[['): + ch_id = line.strip()[2:-2] + print(f' {ch} : {ch_id!r},') + break + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/print/xrefs/experiments/xref-other-vols.ipynb b/print/xrefs/experiments/xref-other-vols.ipynb new file mode 100644 index 00000000..847bb162 --- /dev/null +++ b/print/xrefs/experiments/xref-other-vols.ipynb @@ -0,0 +1,322 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 28, + "id": "e4c0fab3-df06-4c73-bab5-bc28336ee579", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PosixPath('/Users/luciano/flupy/pythonfluente2e')" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from pathlib import Path\n", + "import os\n", + "import subprocess\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", + "GIT_ROOT" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "9e0ff8c8-2e08-4144-bb1c-31ae032ad693", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/luciano/flupy/pythonfluente2e/vol3/cap17.adoc\n" + ] + } + ], + "source": [ + "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 GIT_ROOT / f'vol{vol}' / cap_adoc(cap)\n", + "\n", + "print(path_adoc(17))" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "7c21a3dd-ed66-4587-aa51-a443365d346d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'ch_data_model': (1, 1),\n", + " 'ch_sequences': (1, 2),\n", + " 'ch_dicts_sets': (1, 3),\n", + " 'ch_str_bytes': (1, 4),\n", + " 'ch_dataclass': (1, 5),\n", + " 'ch_refs_mut_mem': (1, 6),\n", + " 'ch_func_objects': (1, 7),\n", + " 'ch_type_hints_def': (1, 8),\n", + " 'ch_closure_decorator': (2, 9),\n", + " 'ch_design_patterns': (2, 10),\n", + " 'ch_pythonic_obj': (2, 11),\n", + " 'ch_seq_methods': (2, 12),\n", + " 'ch_ifaces_prot_abc': (2, 13),\n", + " 'ch_inheritance': (2, 14),\n", + " 'ch_more_types': (2, 15),\n", + " 'ch_op_overload': (2, 16)}" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "other_vols = {}\n", + "\n", + "for cap_n in range(1, 17):\n", + " with open(path_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": 31, + "id": "546fd140-6977-469a-8e32-c7497f09dc27", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/luciano/flupy/pythonfluente2e/vol3/cap17.adoc: replaced 0 xrefs\n", + "/Users/luciano/flupy/pythonfluente2e/vol3/cap18.adoc: replaced 0 xrefs\n", + "/Users/luciano/flupy/pythonfluente2e/vol3/cap19.adoc: replaced 0 xrefs\n", + "/Users/luciano/flupy/pythonfluente2e/vol3/cap20.adoc: replaced 0 xrefs\n", + "/Users/luciano/flupy/pythonfluente2e/vol3/cap21.adoc: replaced 0 xrefs\n", + "/Users/luciano/flupy/pythonfluente2e/vol3/cap22.adoc: replaced 0 xrefs\n", + "/Users/luciano/flupy/pythonfluente2e/vol3/cap23.adoc: replaced 0 xrefs\n", + "/Users/luciano/flupy/pythonfluente2e/vol3/cap24.adoc: replaced 0 xrefs\n" + ] + } + ], + "source": [ + "# duas xrefs:\n", + "# A maior parte do <> e do <>\n", + "\n", + "for cap_n in range(17, 25):\n", + " with open(path_adoc(cap_n)) as fp:\n", + " adoc = fp.read()\n", + " print(path_adoc(cap_n), end=': ')\n", + " replaced = 0\n", + " for ident, (vol, cap) in other_vols.items():\n", + " xref = f'<<{ident}>>'\n", + " if xref in adoc:\n", + " adoc = adoc.replace(xref, f'https://fpy.li/{cap}[«Capítulo {cap}»] (vol.{vol})')\n", + " replaced += 1\n", + " if replaced:\n", + " with open(path_adoc(cap_n), 'wt', encoding='utf8') as fp:\n", + " fp.write(adoc)\n", + " print(f'replaced {replaced} xrefs')\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "87de0cb0-4e90-43af-9eb0-f72874f2cc2a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['vector_take1_sec',\n", + " 'python_digs_seq_sec',\n", + " 'subclasshook_sec',\n", + " 'cartesian_product_sec',\n", + " 'multi_hashing_sec',\n", + " 'tuples_more_than_lists_sec',\n", + " 'contravariant_types_sec',\n", + " 'covariant_types_sec',\n", + " 'variance_rules_sec',\n", + " 'pattern_matching_seq_interp_sec',\n", + " 'nonlocal_sec',\n", + " 'closures_sec',\n", + " 'unicodedata_sec',\n", + " 'vector_dynamic_attrs_sec',\n", + " 'goose_typing_sec',\n", + " 'private_protected_sec',\n", + " 'slots_sec',\n", + " 'conseq_dict_internal_sec',\n", + " 'overriding_class_attributes_sec',\n", + " 'missing_method_sec',\n", + " 'functools_partial_sec',\n", + " 'problems_annot_runtime_sec',\n", + " 'mult_inherit_mro_sec',\n", + " 'noreturn_sec']" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from xvol_xrefs import replace_xrefs_to_vols, list_invalid_xrefs\n", + "\n", + "list_invalid_xrefs()" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "552c2fa0-ff72-404b-a7b1-868273c38cb9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<>\t https://pythonfluente.com/2/#vector_take1_sec[«Seção 12.3»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#python_digs_seq_sec[«Seção 13.4.1»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#subclasshook_sec[«Seção 13.5.8»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#cartesian_product_sec[«Seção 2.3.3»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#multi_hashing_sec[«Seção 12.7»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#tuples_more_than_lists_sec[«Seção 2.4»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#contravariant_types_sec[«Seção 15.7.4.3»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#covariant_types_sec[«Seção 15.7.4.2»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#variance_rules_sec[«Seção 15.7.4.4»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#pattern_matching_seq_interp_sec[«Seção 2.6.1»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#nonlocal_sec[«Seção 9.7»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#closures_sec[«Seção 9.6»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#unicodedata_sec[«Seção 4.9»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#vector_dynamic_attrs_sec[«Seção 12.6»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#goose_typing_sec[«Seção 13.5»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#private_protected_sec[«Seção 11.10»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#slots_sec[«Seção 11.11»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#conseq_dict_internal_sec[«Seção 3.9»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#overriding_class_attributes_sec[«Seção 11.12»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#missing_method_sec[«Seção 3.5.2»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#functools_partial_sec[«Seção 7.8.2»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#problems_annot_runtime_sec[«Seção 15.5.1»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#mult_inherit_mro_sec[«Seção 14.4»] (vol.2)\n", + "<>\t https://pythonfluente.com/2/#noreturn_sec[«Seção 8.5.12»] (vol.1)\n" + ] + } + ], + "source": [ + "repls = replace_xrefs_to_vols()" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "bd8f090f-e95d-4005-96ce-160387d3db63", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " https://pythonfluente.com/2/#vector_take1_sec\n", + " https://pythonfluente.com/2/#python_digs_seq_sec\n", + " https://pythonfluente.com/2/#subclasshook_sec\n", + " https://pythonfluente.com/2/#cartesian_product_sec\n", + " https://pythonfluente.com/2/#multi_hashing_sec\n", + " https://pythonfluente.com/2/#tuples_more_than_lists_sec\n", + " https://pythonfluente.com/2/#contravariant_types_sec\n", + " https://pythonfluente.com/2/#covariant_types_sec\n", + " https://pythonfluente.com/2/#variance_rules_sec\n", + " https://pythonfluente.com/2/#pattern_matching_seq_interp_sec\n", + " https://pythonfluente.com/2/#nonlocal_sec\n", + " https://pythonfluente.com/2/#closures_sec\n", + " https://pythonfluente.com/2/#unicodedata_sec\n", + " https://pythonfluente.com/2/#vector_dynamic_attrs_sec\n", + " https://pythonfluente.com/2/#goose_typing_sec\n", + " https://pythonfluente.com/2/#private_protected_sec\n", + " https://pythonfluente.com/2/#slots_sec\n", + " https://pythonfluente.com/2/#conseq_dict_internal_sec\n", + " https://pythonfluente.com/2/#overriding_class_attributes_sec\n", + " https://pythonfluente.com/2/#missing_method_sec\n", + " https://pythonfluente.com/2/#functools_partial_sec\n", + " https://pythonfluente.com/2/#problems_annot_runtime_sec\n", + " https://pythonfluente.com/2/#mult_inherit_mro_sec\n", + " https://pythonfluente.com/2/#noreturn_sec\n" + ] + } + ], + "source": [ + "for repl in repls:\n", + " xref, rest = repl.split('\\t')\n", + " url = rest.split('[')[0]\n", + " print(url)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd9a3167-fa6c-4f18-99ac-1fcf3bf19a3a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c04025a5-c86e-4897-86dd-83f924125ae2", + "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 00000000..98e7cf49 --- /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 00000000..df46b1c4 --- /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/experiments/xvol_xrefs.py b/print/xrefs/experiments/xvol_xrefs.py new file mode 100755 index 00000000..dae693f0 --- /dev/null +++ b/print/xrefs/experiments/xvol_xrefs.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python + +import subprocess +from bs4 import BeautifulSoup +from pathlib import Path + +CHAPTER_ID = { + 1 : 'ch_data_model', + 2 : 'ch_sequences', + 3 : 'ch_dicts_sets', + 4 : 'ch_str_bytes', + 5 : 'ch_dataclass', + 6 : 'ch_refs_mut_mem', + 7 : 'ch_func_objects', + 8 : 'ch_type_hints_def', + 9 : 'ch_closure_decorator', + 10 : 'ch_design_patterns', + 11 : 'ch_pythonic_obj', + 12 : 'ch_seq_methods', + 13 : 'ch_ifaces_prot_abc', + 14 : 'ch_inheritance', + 15 : 'ch_more_types', + 16 : 'ch_op_overload', + 17 : 'ch_generators', + 18 : 'ch_with_match', + 19 : 'ch_concurrency_models', + 20 : 'ch_executors', + 21 : 'ch_async', + 22 : 'ch_dynamic_attrs', + 23 : 'ch_descriptors', + 24 : 'ch_class_metaprog', +} + +CHAPTER_NUMBER = {i:n for (n, i) in CHAPTER_ID.items()} + + +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') + + +INVALID_MSG = 'asciidoctor: INFO: possible invalid reference: ' + + +def list_invalid_xrefs() -> list[str]: + adoc = find_git_root() / 'vol3/vol3-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 get_section_title(ident: str, root: BeautifulSoup) -> str: + element = root.find(id=ident) + if element: + return element.get_text(strip=True) + raise LookupError(f'element {ident!r} not found') + + +def section_numbers(sec_title: str): + parts = sec_title.split('.') + numbers = [] + for part in parts: + try: + numbers.append(int(part)) + except ValueError: + break + assert len(numbers) > 0, 'no number prefix: ' + repr(sec_title) + return numbers + +BASE_URL = 'https://pythonfluente.com/2/' + +PART_VOL = { + 'data_structures_part': 1, + # 'function_objects_part': 2, # may be vol 2 or 3 + 'classes_protocols_part': 2, + 'control_flow_part': 3, + 'metaprog_part': 3, +} + +PART_TITLE = { + 'data_structures_part': 'I—Estruturas de dados', + 'function_objects_part': 'II—Funções como objetos', + 'classes_protocols_part': 'III—Classes e protocolos', + 'control_flow_part': 'IV—Controle de fluxo', + 'metaprog_part': 'V—Metaprogramação', +} + +SHORTENER_OUTPUT = ''' ++ /4q https://pythonfluente.com/2/#ch_ifaces_prot_abc ++ /4r https://pythonfluente.com/2/#ch_op_overload ++ /4s https://pythonfluente.com/2/#ch_generators ++ /4t https://pythonfluente.com/2/#ch_seq_methods += /22 https://pythonfluente.com/2/#pattern_matching_case_study_sec ++ /4v https://pythonfluente.com/2/#classes_protocols_part ++ /4w https://pythonfluente.com/2/#how_slicing_works_sec ++ /4x https://pythonfluente.com/2/#sliceable_sequence_sec += /25 https://pythonfluente.com/2/#virtual_subclass_sec ++ /4y https://pythonfluente.com/2/#lispy_environ_sec ++ /4z https://pythonfluente.com/2/#subclass_builtin_woes_sec ++ /52 https://pythonfluente.com/2/#slots_sec += /29 https://pythonfluente.com/2/#typeddict_sec ++ /53 https://pythonfluente.com/2/#ch_class_metaprog += /2a https://pythonfluente.com/2/#problems_annot_runtime_sec ++ /54 https://pythonfluente.com/2/#ch_more_types ++ /55 https://pythonfluente.com/2/#ch_descriptors ++ /56 https://pythonfluente.com/2/#ch_inheritance += /2c https://pythonfluente.com/2/#positional_pattern_implement_sec ++ /57 https://pythonfluente.com/2/#ch_async ++ /58 https://pythonfluente.com/2/#runtime_annot_sec ++ /59 https://pythonfluente.com/2/#multi_hashing_sec ++ /5a https://pythonfluente.com/2/#iterable_reducing_sec ++ /5b https://pythonfluente.com/2/#flexible_new_sec ++ /5c https://pythonfluente.com/2/#ch_closure_decorator ++ /5d https://pythonfluente.com/2/#ch_design_patterns ++ /5e https://pythonfluente.com/2/#lispy_parser_sec ++ /5f https://pythonfluente.com/2/#overload_sec ++ /5g https://pythonfluente.com/2/#numbers_abc_proto_sec ++ /5h https://pythonfluente.com/2/#runtime_checkable_proto_sec ++ /5j https://pythonfluente.com/2/#variance_sec ++ /5k https://pythonfluente.com/2/#generic_iterable_types_sec ++ /5m https://pythonfluente.com/2/#typed_double_sec ++ /5n https://pythonfluente.com/2/#enhancing_with_init_subclass_sec ++ /5p https://pythonfluente.com/2/#more_type_hints_further_sec ++ /5q https://pythonfluente.com/2/#max_overload_sec +''' + +SHORT_URLS = {line.split()[2]:line.split()[1] for line + in SHORTENER_OUTPUT.strip().split('\n')} + + +def replace_xrefs_to_vols(): + html_path = find_git_root() / 'online/index.html' + 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] + text = f'Capítulo {chapter}' + volume = (chapter - 1) // 8 + 1 + elif xref.endswith('_part'): + volume = PART_VOL[xref] + text = PART_TITLE[xref] + elif xref.endswith('_sec'): + title = get_section_title(xref, root) + numbers = section_numbers(title) + number_str = '.'.join(str(n) for n in numbers) + chapter = numbers[0] + volume = (chapter - 1) // 8 + 1 + text = f'Seção {number_str}' + + #else: + # raise ValueError(f'unexpected xref: {xref!r}') + link = BASE_URL + '#' + xref + #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__': + replace_xrefs_to_vols() diff --git a/print/xrefs/vol2-xrefs.txt b/print/xrefs/vol2-xrefs.txt new file mode 100644 index 00000000..bcfdd4f7 --- /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 00000000..7714fa6e --- /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(3)] + + 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/print/xrefs/xvol_replacer.py b/print/xrefs/xvol_replacer.py new file mode 100755 index 00000000..72c7a835 --- /dev/null +++ b/print/xrefs/xvol_replacer.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +import sys +import os +import tempfile +import shutil + +def load_replacements(mapping_file): + replacements = [] + with open(mapping_file, 'r') as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + old, new = line.split(maxsplit=1) + replacements.append((old, new)) + return replacements + +def replace_in_file(file_path, replacements): + with tempfile.NamedTemporaryFile(mode='w', delete=False) as temp_file: + temp_filename = temp_file.name + with open(file_path, 'r') as original_file: + content = original_file.read() + for old_text, new_text in replacements: + content = content.replace(old_text, new_text) + temp_file.write(content) + shutil.move(temp_filename, file_path) + +def main(): + if len(sys.argv) < 3: + print("xvol_replacer.py mapping_file.tsv file1.txt file2.txt ...") + sys.exit(1) + + mapping_file = sys.argv[1] + target_files = sys.argv[2:] + + replacements = load_replacements(mapping_file) + + for file_path in target_files: + if os.path.exists(file_path) and not os.path.isdir(file_path): + replace_in_file(file_path, replacements) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/replacer.ipynb b/replacer.ipynb new file mode 100644 index 00000000..adf88e4d --- /dev/null +++ b/replacer.ipynb @@ -0,0 +1,159 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 15, + "id": "eb57d79f-c9cd-45a6-bc29-8a4cbd9dc87e", + "metadata": {}, + "outputs": [], + "source": [ + "def to_int(s):\n", + " try:\n", + " n = int(s)\n", + " except ValueError:\n", + " n = int(s[0]) * 10 + ord(s[1])\n", + " return n\n", + "\n", + "def get_id(s):\n", + " return s[s.index('#ch_')+1:]" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "2ec7ea76-776f-48f7-b84b-d172d41cb004", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/4q /13 ch_ifaces_prot_abc\n", + "/4r /16 ch_op_overload\n", + "/4s /17 ch_generators\n", + "/4t /12 ch_seq_methods\n", + "/53 /24 ch_class_metaprog\n", + "/54 /15 ch_more_types\n", + "/55 /23 ch_descriptors\n", + "/56 /14 ch_inheritance\n", + "/57 /21 ch_async\n", + "/5c /9 ch_closure_decorator\n", + "/5d /10 ch_design_patterns\n", + "{'/4q': '/13',\n", + " '/4r': '/16',\n", + " '/4s': '/17',\n", + " '/4t': '/12',\n", + " '/53': '/24',\n", + " '/54': '/15',\n", + " '/55': '/23',\n", + " '/56': '/14',\n", + " '/57': '/21',\n", + " '/5c': '/9',\n", + " '/5d': '/10'}\n" + ] + } + ], + "source": [ + "with open('links/FPY.LI.htaccess') as fp:\n", + " lines = fp.readlines()\n", + "\n", + "PRE = 'https://pythonfluente.com/2/#ch_'\n", + "\n", + "id2n = {}\n", + "subs = {}\n", + "\n", + "for line in lines:\n", + " try:\n", + " _, short, long = line.strip().split()\n", + " except ValueError:\n", + " continue\n", + " if not long.startswith(PRE):\n", + " continue\n", + " n = to_int(short[1:])\n", + " id = get_id(long)\n", + " if n in range(1, 25):\n", + " id2n[id] = n\n", + " else:\n", + " print(f'{short} /{id2n[id]} {id}')\n", + " subs[short] = f'/{id2n[id]}'\n", + "\n", + "from pprint import pprint\n", + "pprint(subs)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "d4efc774-3fae-4dd2-91c9-284985f3bddc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "vol1/cap03.adoc\n", + "vol1/Copyright-pb.adoc\n", + "vol1/cap02.adoc\n", + "vol1/cap05.adoc\n", + "vol1/cap04.adoc\n", + "vol1/cap08.adoc\n", + "vol1/Copyright-cor.adoc\n", + "vol1/cap07.adoc\n", + "vol1/cap06.adoc\n", + "vol1/titulos-vol1.adoc\n", + "vol1/vol1-pb.adoc\n", + "vol1/vol1-cor.adoc\n", + "vol1/cap01.adoc\n", + "vol1/Prefacio.adoc\n" + ] + } + ], + "source": [ + "from glob import glob\n", + "\n", + "link = 'fpy.li{}&'\n", + "\n", + "for name in glob('vol1/*.adoc'):\n", + " print(name)\n", + " with open(name) as fp:\n", + " adoc = fp.read()\n", + " for old, new in subs.items():\n", + " old, new = link.format(old), link.format(new)\n", + " adoc = adoc.replace(old, new)\n", + " with open(name, 'w', encoding='utf8') as fp:\n", + " fp.write(adoc)\n", + "\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e801a363-bb8c-4db9-ae1b-216fc1dbb27c", + "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.0rc1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..a99e06fc --- /dev/null +++ b/requirements.txt @@ -0,0 +1,14 @@ +anyio==4.9.0 +certifi==2025.4.26 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +idna==3.10 +iniconfig==2.1.0 +packaging==25.0 +pluggy==1.6.0 +Pygments==2.19.1 +pytest==8.4.0 +pytest-datadir==1.7.2 +ruff==0.11.11 +sniffio==1.3.1 diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000..1c4b3cc3 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,4 @@ +line-length = 73 +[format] +# Use single quotes for strings, like Python's repr() +quote-style = "single" diff --git a/vol1/.gitignore b/vol1/.gitignore new file mode 100644 index 00000000..133695b3 --- /dev/null +++ b/vol1/.gitignore @@ -0,0 +1 @@ +vol1.pdf \ No newline at end of file diff --git a/vol1/Copyright-cor.adoc b/vol1/Copyright-cor.adoc new file mode 100644 index 00000000..63c10a15 --- /dev/null +++ b/vol1/Copyright-cor.adoc @@ -0,0 +1,49 @@ +[colophon%discrete%notitle%nonfacing,toclevels=0] += Copyright +:volume: 1-cor +:isbn-pb: 978-65-988962-1-8 +:isbn-cor: 978-65-988962-0-1 + +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 + +https://fpy.li/ccby[_Atribuição-NãoComercial-SemDerivações 4.0 Internacional_] + +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 + +1ª edição: 2015 + +2ª edição: 2022 + +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") + +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 1: Dados e Funções / + Luciano Ramalho - São Paulo, SP: Z.Edições, 2025. + 403 p.; il.; cor; 17 cm × 24 cm + + ISBN: 978-65-988962-0-1 + 1.Informática. 2.Linguagem de Programação. 3.Python. + 4.Metaprogramação. + + 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 new file mode 100644 index 00000000..31fb115b --- /dev/null +++ b/vol1/Copyright-pb.adoc @@ -0,0 +1,49 @@ +[colophon%discrete%notitle%nonfacing,toclevels=0] += Copyright +:volume: 1-pb +:isbn-pb: 978-65-988962-1-8 +:isbn-cor: 978-65-988962-0-1 + +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 + +https://fpy.li/ccby[_Atribuição-NãoComercial-SemDerivações 4.0 Internacional_] + +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 + +1ª edição: 2015 + +2ª edição: 2022 + +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") + +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 1: Dados e Funções / + Luciano Ramalho - São Paulo, SP: Z.Edições, 2025. + 403 p.; il.; 17 cm × 24 cm + + ISBN: 978-65-988962-1-8 + 1.Informática. 2.Linguagem de Programação. 3.Python. + 4.Metaprogramação. + + 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 new file mode 100644 index 00000000..372392f0 --- /dev/null +++ b/vol1/Prefacio.adoc @@ -0,0 +1,484 @@ +:xrefstyle: short +:example-number: 0 +:figure-number: 0 +:figure-caption: Figura +:example-caption: Exemplo +:table-caption: Tabela +:section-caption: Seção +:chapter-caption: Capítulo +:part-caption: Parte +:sectnums!: + +[preface,toclevels=1] +== Prefácio + +[quote, Tim Peters, lendário colaborador do CPython e autor do Zen de Python] +____ +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]#.] +____ + +"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]#. +Isso é verdade, mas há uma pegadinha: como a linguagem é fácil de entender e de começar a usar, muitos programadores praticantes de Python se contentam apenas com uma fração de seus poderosos recursos. + +Uma programadora experiente pode começar a escrever código Python útil em questão de horas. Conforme as primeiras horas produtivas se tornam semanas e meses, muitos desenvolvedores continuam escrevendo código Python com um forte sotaque das linguagens que aprenderam antes. +Mesmo se Python for sua primeira linguagem, muitas vezes ela é apresentada nas universidades e +em livros introdutórios evitando deliberadamente os recursos específicos da linguagem. + +Como professor, ensinando Python para programadores experientes em outras linguagens, vejo outro problema: +só sentimos falta daquilo que conhecemos. +Vindo de outra linguagem, qualquer um é capaz de imaginar que Python suporta expressões regulares, +e procurar esse tema na documentação. +Mas se você nunca viu desempacotamento de tuplas ou descritores de atributos, +talvez nunca procure por eles, e pode acabar não usando esses recursos, +só porque são novos para você. + +Este livro não é uma referência exaustiva de Python de A a Z. +A ênfase está em recursos da linguagem característicos de Python +ou incomuns em outras linguagens populares. +Vamos nos concentrar principalmente nos aspectos centrais da linguagem e pacotes essenciais da biblioteca padrão. +Apenas alguns exemplos mostram o uso de pacotes externos como FastAPI, httpx, e Curio. + + +=== Para quem é esse livro + +Escrevi este ((("Python", "versions featured"))) +livro para programadores que já usam Python e +desejam se tornar fluentes em Python 3 moderno. +Testei os exemplos em Python 3.10—e a maioria também em Python 3.9 e 3.8. +Os exemplos que exigem especificamente Python 3.10 estão indicados. + +Caso((("Python", "prerequisites to learning"))) +não tenha certeza se conhece Python o suficiente para acompanhar o livro, +revise o +«_tutorial oficial de Python_» [.small]#[fpy.li/4g]#. +Tópicos tratados no tutorial não serão explicados aqui, exceto por alguns recursos mais novos. + + +=== Para quem esse livro não é + +Se((("Python", "target audience"))) está começando a estudar Python, +poderá achar difícil acompanhar este livro. +Mais ainda, se você o ler muito cedo em sua jornada pela linguagem, +pode ficar com a impressão de que todo script Python precisa se valer +de métodos especiais e truques de metaprogramação. +Abstração prematura é tão ruim quanto otimização prematura. + +Para quem está aprendendo a programar, recomendo o livro +«_Pense em Python_» [.small]#[fpy.li/4h]# de Allen Downey, disponível na Web. + +Se já sabe programar e está aprendendo Python, o +«_tutorial oficial de Python_» [.small]#[fpy.li/4g]# foi traduzido +pela comunidade Python brasileira. + + +=== Como ler este livro + +Esta edição impressa de _Python Fluente, 2ª Edição_ está +dividida em três volumes, contendo as cinco partes do original +_Fluent Python, Second Edition_ (O'Reilly, 2022), +que foi publicado em um volume de 1012 páginas. +O texto completo desta edição em português está em +https://pythonfluente.com, em uma única página HTML para facilitar +buscas e a leitura offline. + +Recomendo((("Python", "approach to learning", id="Papproach00"))) que todos leiam o <>. +Após a leitura do capítulo "O modelo de dados de Python", +o público principal deste livro não terá problema em +pular diretamente para qualquer outra parte ou volume, +mas muitas vezes assumo que você leu os capítulos precedentes de cada parte específica. + +Tentei enfatizar o uso de classes e módulos que já existem antes de discutir como criar seus próprios. +Por exemplo, no _Volume 1_, +o <> trata dos tipos de sequências que estão prontas para serem usadas, +incluindo algumas que não recebem muita atenção, como `collections.deque`. +Criar sequências definidas pelo usuário só é discutido no _Volume 2, Parte III: Classes e Protocolos_, +onde também vemos como usar as classes base abstratas (ABCs) de `collections.abc`. +Criar suas próprias ABCs é discutido ainda mais tarde naquele volume, +pois acredito na importância de estar confortável usando uma ABC antes de escrever uma. + +Essa abordagem tem algumas vantagens. +Primeiro, saber o que está pronto para uso imediato pode evitar que você reinvente a roda. +Usamos as classes de coleções existentes com mais frequência do que implementamos nossas próprias coleções, +e podemos prestar mais atenção ao uso avançado de ferramentas prontas, adiando a discussão sobre a criação de novas ferramentas. +Também é mais comum herdar de ABCs existentes do que criar uma nova ABC do zero. +E, finalmente, acredito que é mais fácil entender as abstrações após vê-las em ação. + +A desvantagem dessa estratégia são as referências a pontos futuros espalhadas pelo livro. +Espero que isso seja mais fácil de tolerar agora que você sabe por que escolhi esse caminho. + + +==== Os três volumes e as 5 partes + +O _Fluent Python (Second Edition)_ original é dividido em 5 partes e foi publicado em um volume em inglês. +Nesta edição em português brasileiro, dividimos as 5 partes em 3 volumes. + +===== {vo_data_str} + +{pa_data} (capítulos 1-6):: +O <> introduz o Modelo de Dados de Python e explica por que os +métodos especiais (por exemplo, `+__repr__+`) são a chave do comportamento +consistente de objetos de todos os tipos. Os métodos especiais são tratados em +mais detalhes ao longo do livro. Os((("data structures"))) capítulos restantes +dessa parte cobrem o uso dos tipos de coleção mais importantes: +sequências, mapeamentos e conjuntos, +bem como a separação de `str` e `bytes`--causa de muitas celebrações entre +usuários de Python 3, e de muita dor para usuários de Python 2 obrigados a +migrar suas bases de código. Também são abordadas as fábricas de classe de alto +nível na biblioteca padrão: fábricas de tuplas nomeadas e o decorador +`@dataclass`. _Pattern matching_ ("casamento de padrões")—novidade no Python +3.10—é tratada em seções do <>, do <> e do +<>, que discutem padrões para sequências, padrões para mapeamentos +e padrões para instâncias de classes. O último capítulo deste volume é sobre o +ciclo de vida dos objetos: criação, referências, mutabilidade e coleta de lixo +(_garbage collection_). + +{pa_func_obj_a} (capítulos 7-8):: Aqui explicamos o que significa ter funções +como objetos de primeira classe na linguagem. Também são vistos aqui o conceito +geral de invocáveis no Python e anotação de parâmetros com _type hints_ no +{ch_type_hints_def}. + +===== {vo_func_cls} + +{pa_func_obj_b} (capítulos 9-10):: Lá desvendamos o conceito de _closure_, a instrução `nonlocal`, e as aplicações destes conceitos na construção de decoradores. Outro tema é o uso de funções de primeira classe para simplificar muito a implementação de alguns padrões de projeto conhecidos. + +{pa_cls_proto} (capítulos 11-16):: Agora o foco se volta para a criação "manual" de classes—em contraste com o uso de fábricas de classe vistas no <>. +Como qualquer linguagem orientada a objetos, Python tem seu conjunto particular de recursos que podem ou não estar presentes na linguagem na qual você aprendeu programação baseada em classes. Os capítulos explicam como criar suas próprias coleções, classes base abstratas (ABCs) e protocolos, bem como as formas de lidar com herança múltipla e como implementar a sobrecarga de operadores, quando fizer sentido. O _{ch_more_types}_ continua a conversa sobre dicas de tipo. + +===== {vo_ctrl_meta} + +{pa_ctrl_flow} (capítulos 17-21):: Nesta parte estudamos as instruções da linguagem e as bibliotecas que vão além do controle de fluxo tradicional +(condicionais, laços e sub-rotinas). Começamos com os geradores, visitamos a seguir os gerenciadores de contexto e as corrotinas, +incluindo a desafiadora e poderosa sintaxe do `yield from`. O _{ch_with_match}_ inclui um exemplo significativo, usando _pattern matching_ em um interpretador de linguagem simples mas funcional. O _{ch_concurrency_models}_ é novo, apresentando uma visão geral das alternativas para processamento concorrente e paralelo no Python, suas limitações, e como a arquitetura de software permite ao Python operar na escala da Web. Reescrevi o capítulo sobre _programação assíncrona_, para enfatizar os recursos centrais da linguagem—por exemplo, `await`, `async def`, `async for` e `async with`, e mostrar como eles são usados com _asyncio_ e outros frameworks. + +{pa_metaprog} (capítulos 22-24):: Essa parte começa com uma revisão de técnicas para criação de classes com atributos criados dinamicamente para lidar com dados semi-estruturados, tal como conjuntos de dados JSON. A seguir, tratamos do mecanismo familiar das propriedades, antes de mergulhar no funcionamento do acesso a atributos de objetos no Python em um nível mais baixo, usando descritores. A relação entre funções, métodos e descritores é explicada. Ao longo daquele volume, a implementação passo a passo de uma biblioteca de validação de campos revela questões sutis, levando às ferramentas avançadas do capítulo final: decoradores de classes e metaclasses. + + +=== Abordagem "mão na massa" + +Frequentemente usaremos o console interativo de Python para explorar a linguagem e as bibliotecas. +Acho isso importante para enfatizar o poder dessa ferramenta de aprendizagem, +especialmente para quem teve mais experiência com linguagens estáticas compiladas, +que não oferecem um REPL.footnote:[_Read-Eval-Print Loop_, console interativo +que lê código, executa, e exibe resultados, repetidamente.] + +Um dos pacotes padrão de testagem de Python, o «_doctest_» [.small]#[fpy.li/doctest]#, funciona simulando sessões de console e verificando se as expressões resultam nas respostas exibidas. Usei `doctest` para verificar a maior parte do código desse livro, incluindo as listagens do console. +Não é necessário usar ou sequer saber da existência do `doctest` para acompanhar o texto: +a principal característica dos _doctests_ é que eles imitam transcrições de sessões +interativas no console de Python, assim qualquer pessoa pode reproduzir as demonstrações facilmente. + +Algumas vezes explico o que queremos fazer mostrando um _doctest_ antes do código que implementa a solução. +Definir precisamente o que deve ser feito, antes de pensar sobre como fazer, ajuda a focalizar nosso esforço de codificação. +Escrever previamente os testes é a base do desenvolvimento dirigido por testes (TDD, _test-driven development_), e uma técnica útil também para ensinar. + +Também((("pytest package")))((("unittest module"))) escrevi testes unitários para alguns dos exemplos maiores usando _pytest_—que acho mais fácil de usar e mais poderoso que o módulo _unittest_ da biblioteca padrão. +Você vai descobrir que pode verificar a maior parte do código do livro digitando `python3 -m doctest example_script.py` ou `pytest` no console de seu sistema operacional. +A configuração do _pytest.ini_, na raiz do «repositório do código de exemplo» [.small]#[fpy.li/code]#, assegura que _doctests_ são coletados e executados pelo comando `pytest`.((("", startref="Papproach00"))) + + +=== Ponto de vista: minha perspectiva pessoal + +Venho usando, ensinando e debatendo Python desde 1998, e gosto de estudar e comparar linguagens de programação, +o design e a teoria por trás delas. +Ao final de cada capítulo, escrevi uma seção "Ponto de vista", +apresentando minha perspectiva sobre Python e outras linguagens. +Você pode pular essas partes, se não tiver interesse em tais discussões. +Seu conteúdo é inteiramente opcional. + +=== Conteúdo na Web + +Criei dois sites para este livro: + +https://pythonfluente.com:: +O texto integral em português +publicado em um único HTML com todas as dependências embutidas (estilos, imagens, etc.). +O HTML tem apenas diferenças cosméticas em relação a este livro impresso. + +https://fluentpython.com:: +Textos em inglês que complementam as edições do livro, além de um glossário. +É um material que cortei para não passar de 1.000 páginas. + +O repositório de exemplos de código está no «GitHub» [.small]#[fpy.li/code]#. + +=== Convenções usadas no livro + +As seguintes convenções tipográficas são usadas neste livro: + +_Itálico_:: Indica novos termos, URLs, endereços de e-mail, nomes e extensões de arquivos. Nesta edição em português +também usamos _itálico_ em alguns termos mantidos em inglês, principalmente na primeira ocorrência. + +`Espaçamento constante`:: Usado em listagens de programas, +bem como dentro de parágrafos para indicar elementos programáticos como identificadores e palavras-chave. + +[role="pagebreak-before less_space"] +[TIP] +==== +Esse elemento é uma dica ou sugestão. +==== + +[NOTE] +==== +Este elemento é uma nota ou observação. +==== + +[WARNING] +==== +Este elemento é um aviso ou alerta. +==== + +=== Usando os exemplos de código + +Todos((("code examples, obtaining and using"))) os scripts e a maior parte dos trechos de código que aparecem no livro estão disponíveis no repositório de código de Python Fluente, «no GitHub» [.small]#[fpy.li/code]#. + +Se você tiver uma questão técnica ou algum problema para usar o código, por favor mande um e-mail para pass:[bookquestions@oreilly.com]. + +Esse livro existe para ajudar você a fazer seu trabalho. Em geral, se um exemplo está no livro, você pode usá-lo em seus programas e na sua documentação. Não é necessário nos contatar para pedir permissão, a menos que você queira reproduzir uma parte significativa do código. Por exemplo, escrever um programa usando vários trechos de código deste livro não exige permissão. Vender ou distribuir exemplos de livros da O’Reilly exige permissão. Responder uma pergunta citando este livro e código exemplo daqui não exige permissão. Incorporar uma parte significativa dos exemplos do livro na documentação de seu produto exige permissão. + +Gostamos, mas em geral não exigimos, atribuição da fonte. Isto normalmente inclui o título, o autor, a editora e o ISBN. Por exemplo, “_Fluent Python_, 2ª ed., de Luciano Ramalho. Copyright 2022 Luciano Ramalho, 978-1-492-05635-5.” + +Se você achar que seu uso dos exemplos de código está fora daquilo previsto na lei ou das permissões dadas acima, por favor entre em contato com pass:[permissions@oreilly.com]. + +=== O'Reilly Online Learning + +[role = "ormenabled"] +[NOTE] +==== +Por mais de 40 anos, O’Reilly Media tem oferecido treinamento, conhecimento e ideias sobre tecnologia e negócios, ajudando empresas serem bem sucedidas. +==== + +Nossa rede sem igual de especialistas e inovadores compartilha conhecimento e sabedoria através de livros, artigos e de nossa plataforma online de aprendizagem. A plataforma de aprendizagem online da O'Reilly oferece acesso sob demanda a treinamentos ao vivo, trilhas de aprendizagem profunda, ambientes interativos de programação e uma imensa coleção de textos e vídeos da O'Reilly e de mais de 200 outras editoras. Para mais informações, visite http://oreilly.com[]. + +=== Como entrar em contato + +Por gentileza((("comments and questions")))((("questions and comments"))), envie comentários e perguntas sobre esse livro para o editor: + +---- +O'Reilly Media, Inc. +1005 Gravenstein Highway North +Sebastopol, CA 95472 +800-998-9938 (in the United States or Canada) +707-829-0515 (international or local) +707-829-0104 (fax) +---- + +<<< +Há uma página online para o original em inglês deste livro, com erratas e informação adicional, +que pode ser acessada em https://fpy.li/p-4[]. +Envie e-mail para _bookquestions@oreilly.com_, +com comentários ou dúvidas técnicas sobre o livro. +Novidades e informações sobre nossos livros e cursos podem ser encontradas em http://oreilly.com[]. + +=== Agradecimentos da segunda edição brasileira + +Meu grande amigo Paulo Candido de Oliveira Filho traduziu as 1000 páginas do +_Fluent Python Second Edition_ (O'Reilly, 2022) +sem poder consultar a outra tradução brasileira, +para não violar os direitos da editora que publicou aquela primeira edição de 2015. +Felizmente, PC e eu somos amigos de infância, trabalhamos juntos como +programadores, então eu pude confiar tranquilamente que ele não ia fazer plágio nem usar IA. +E a tradução ficou excelente: "Engenhocas & Bugigangas" é demais! + +Três amigos do Garoa Hacker Clube me ajudaram diretamente nesta edição. +Felipe "Juca" Sanches me orientou sobre tecnologia de fontes, +Gabriel Almeida de Souza fez scripts para resolver as referências entre volumes, +e Hugo Borges me mostrou como configurar um ambiente Ruby flexível +para rodar a ferramenta Asciidoctor com bibliotecas adicionais +para gerar PDF com listagens de código coloridas. +Agradeço a toda a comunidade do Garoa por manter vivo aquele espaço +de criação, aprendizagem e troca de ideias. + +Heinar Maracy e Zander Catta Preta da Z•Edições foram muito parceiros em +todas as etapas da produção do livro impresso, desde a diagramação até a campanha no +https://Catarse.me[], a produção gráfica, e a comercialização na +https://zstores.shop[] e outras livrarias online. + +Muitas pessoas enviaram correções desde que coloquei o https://PythonFluente.com no ar em 2023, +até a reta final da produção do PDF para a gráfica. + +Adorilson Bezerra é o primeiro que eu quero citar (não só pela ordem alfabética), +porque é um dos principais voluntários que traduzem +a documentação oficial de Python em português brasileiro, +um trabalho sem fim, mas de valor inestimável para a comunidade Python de língua portuguesa. +Adorilson me mandou sugestões valiosas citando a terminologia adotada na +documentação oficial. + +<<< +Outras pessoas que enviaram correções e sugestões, grandes ou pequenas: +Alexandre de Siqueira, Ana Paula Sales, André Angeluci, Arturo Fonseca de Souza, +Bruna Menani Pereira Lima, Bruno de Oliveira Pinheiro Júnior, Caio Phillipe Mizerkowski, +Carlos Seabra, Dickson Souza, Diego Mariano, Diego Rabatone Oliveira, +Eduardo Würch, Eric Gonçalves Lemos, Erick R. Ribeiro, +Fabrício Soares, Franklin Sousa, Giovanni Salvatore de Almeida Curcuruto, +Guilherme Henrique Gimenes de Deus, Gustavo de Carvalho Bertoli, +Helder Geovane Gomes de Lima, Inácio Gomes Medeiros, Ismael de Laet Abashi, +Jair Henrique, João Paulo Albuquerque, Juliano Fischer Naves, +Luis Gustavo Mota, Luiz Eduardo Amaral, +Manaia Junior, Mariana Jó, Osvaldo Makoto Yasuda, +Paulo Barbosa de Siqueira Bueno Bruno, +Ramon Gomes da Silva, Raphael Vieira Rossi, Rodolfo De Nadai, Ruan Cardoso Comelli, +Sandra Bastos, Thales Carl Lavoratti, Thiago Brasil, Thiago Gonçalves Mota, Thiago Jack de Oliveira, +Vitor Buxbaum Orlandi, Wellyson de Freitas Santos, e William Ferreira. + +Muito grato, pessoal. Foi um prazer colaborar com vocês! + + +=== Agradecimentos da segunda edição + +Eu não esperava que atualizar um livro sobre Python +cinco anos depois fosse um empreendimento de tal magnitude. +Mas foi. +Marta Mello, minha amada esposa, sempre esteve ao meu lado quando precisei. +Meu querido amigo Leonardo Rochael me ajudou desde os primeiros rascunhos até a revisão técnica final, +incluindo consolidar e revisar as sugestões dos outros revisores técnicos, de leitores e de editores. +Honestamente, não sei se teria conseguido sem seu apoio, Marta e Leo. Muito, muito grato! + +Jürgen Gmach, Caleb Hattingh, Jess Males, Leonardo Rochael e Miroslav Šedivý formaram a fantástica equipe de revisores técnicos da segunda edição. Eles revisaram o livro inteiro. +Bill Behrman, Bruce Eckel, Renato Oliveira e Rodrigo Bernardo Pimentel revisaram capítulos específicos. +Suas inúmeras sugestões, vindas de diferentes perspectivas, tornaram o livro muito melhor. + +Muitos leitores me enviaram correções ou fizeram outras contribuições durante o pré-lançamento, incluindo: +Guilherme Alves, Christiano Anderson, Konstantin Baikov, K. Alex Birch, Michael Boesl, Lucas Brunialti, +Sergio Cortez, Gino Crecco, Chukwuerika Dike, Juan Esteras, Federico Fissore, Will Frey, Tim Gates, +Alexander Hagerman, Chen Hanxiao, Sam Hyeong, Simon Ilincev, Parag Kalra, Tim King, David Kwast, +Tina Lapine, Wanpeng Li, Guto Maia, Scott Martindale, Mark Meyer, Andy McFarland, Chad McIntire, Diego Rabatone Oliveira, +Francesco Piccoli, Meredith Rawls, Michael Robinson, Federico Tula Rovaletti, +Tushar Sadhwani, Arthur Constantino Scardua, Randal L. Schwartz, Avichai Sefati, Guannan Shen, William Simpson, +Vivek Vashist, Jerry Zhang, Paul Zuradzki—e outros que pediram para não ter seus nomes mencionados, enviaram correções após a entrega da versão inicial ou foram omitidos porque eu não registrei seus nomes—mil desculpas. + +Durante minha pesquisa, aprendi sobre tipagem, concorrência, _pattern matching_ e metaprogramação interagindo com +Michael Albert, Pablo Aguilar, Kaleb Barrett, David Beazley, J. S. O. Bueno, Bruce Eckel, Martin Fowler, +Ivan Levkivskyi, Alex Martelli, Peter Norvig, Sebastian Rittau, Guido van Rossum, Carol Willing e Jelle Zijlstra. + +Os editores da O'Reilly Jeff Bleiel, Jill Leonard e Amelia Blevins fizeram sugestões que melhoraram o fluxo do texto em muitas partes. +Jeff Bleiel e o editor de produção Danny Elfanbaum me apoiaram durante essa longa maratona. + +As ideias e sugestões de cada um deles tornaram o livro melhor e mais preciso. +Inevitavelmente, vão restar erros de minha própria criação no produto final. Peço perdão antecipadamente. + +Por fim, gostaria de estender meus sinceros agradecimentos a meus colegas na Thoughtworks Brasil—e especialmente a meu mentor, Alexey Bôas, que apoiou este projeto de várias formas até eu terminar a segunda edição em inglês. + +Claro, todos os que me ajudaram a entender Python e a escrever a primeira edição merecem agora agradecimentos em dobro. +Não haveria segunda edição sem o sucesso da primeira. + +[role="pagebreak-before less_space"] +=== Agradecimentos da primeira edição + +O jogo de xadrez criado por Josef Hartwig na Bauhaus, é um exemplo de design excelente: belo, simples e claro. +Guido van Rossum, filho de um arquiteto e irmão de um projetista de fonte magistral, criou uma obra prima do design de linguagens. +Adoro ensinar Python porque é belo, simples e claro. + +<<< + +Alex Martelli e Anna Ravenscroft foram os primeiros a ver o esquema desse livro, e me encorajaram a submetê-lo à O'Reilly para publicação. +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. + +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). + +Mario Domenech Goulart deu sugestões numerosas e detalhadas, desde a primeira versão do livro. Também recebi muitas sugestões e comentários de Dave Pawson, Elias Dorneles, Leonardo Alexandre Ferreira Leite, Bruce Eckel, J. S. Bueno, Rafael Gonçalves, Alex Chiaranda, Guto Maia, Lucas Vido e Lucas Brunialti. + +Ao longo dos anos, muitas pessoas me encorajaram a me tornar um autor, mas os mais persuasivos foram Rubens Prates, Aurelio Jargas, Rudá Moura e Rubens Altimari. Mauricio Bussab me abriu muitas portas, incluindo minha primeira experiência real na escrita de um livro. Renzo Nuccitelli apoiou este projeto de escrita, +mesmo quando significou iniciar mais lentamente nossa parceria. + +<<< +A comunidade brasileira de Python é inteligente, generosa e divertida. O «grupo Python Brasil» [.small]#[fpy.li/p-9]# tem milhares de membros, e nossas conferências nacionais e regionais reúnem centenas de pessoas. Mas os mais influentes em minha jornada como pythonista foram Leonardo Rochael, Adriano Petrich, Daniel Vainsencher, Rodrigo RBP Pimentel, Bruno Gola, Leonardo Santagada, Jean Ferri, Rodrigo Senra, J. S. Bueno, David Kwast, Luiz Irber, Osvaldo Santana, Fernando Masanori, Henrique Bastos, Gustavo Niemayer, Pedro Werneck, Gustavo Barbieri, Lalo Martins, Danilo Bellini, e Pedro Kroger. + +Dorneles Tremea foi um grande amigo, (e incrivelmente generoso com seu tempo e seu conhecimento), um hacker fantástico e o mais inspirador líder da Associação Python Brasil. Ele nos deixou cedo demais. + +Meus alunos, ao longo desses anos, me ensinaram muito através de suas perguntas, ideias, feedbacks e soluções criativas para problemas. Érico Andrei e a Simples Consultoria me deram a oportunidade de ser um professor de Python em tempo integral pela primeira vez. + +Martijn Faassen foi meu mentor de Grok e compartilhou ideias valiosas sobre Python e os neandertais. Seu trabalho e o de Paul Everitt, Chris McDonough, Tres Seaver, Jim Fulton, Shane Hathaway, Lennart Regebro, Alan Runyan, Alexander Limi, Martijn Pieters, Godefroid Chapelle e outros, dos planetas Zope, Plone e Pyramid, foram decisivos para minha carreira. Graças ao Zope e a surfar na primeira onda da web, pude começar a ganhar a vida com Python em 1998. José Octavio Castro Neves foi meu sócio na primeira software house baseada em Python do Brasil. + +Tenho tantos gurus na comunidade Python que seria impossível citar todos aqui, mas além dos já mencionados, agradeço especialmente Steve Holden, Raymond Hettinger, A.M. Kuchling, David Beazley, Fredrik Lundh, Doug Hellmann, Nick Coghlan, Mark Pilgrim, Martijn Pieters, Bruce Eckel, Michele Simionato, Wesley Chun, Brandon Craig Rhodes, Philip Guo, Daniel Greenfeld, Audrey Roy e Brett Slatkin, por me ensinarem formas melhores de ensinar Python. + +A maior parte dessas páginas foi escrita no meu _home office_ e em dois laboratórios: o CoffeeLab e o Garoa Hacker Clube. O «CoffeeLab» [.small]#[fpy.li/p-10]# é o quartel-general dos geeks cafeinados na Vila Madalena, em São Paulo, Brasil. O «Garoa Hacker Clube» [.small]#[fpy.li/p-11]# é um espaço hacker aberto a todos: um laboratório comunitário onde qualquer pessoa pode experimentar novas ideias. + +<<< +A comunidade Garoa me forneceu inspiração, infraestrutura e distração. Acho que o Aleph curtiria este livro. + +Minha mãe, Maria Lucia, e meu pai, Jairo, sempre me apoiaram de todas as formas. Gostaria que ele estivesse aqui para ver esse livro; e fico feliz de poder compartilhá-lo com ela. + +Minha esposa, Marta Mello, esteve ao meu lado durante os 15 meses em que trabalhei neste livro, +sempre me apoiando e guiando através dos momentos mais críticos, +quando temi que poderia abandonar a maratona. + +Agradeço a todos vocês por tudo. + + +=== Sobre esta tradução + +_Python Fluente, 2ª Edição_ +é uma tradução direta de _Fluent Python, Second Edition_ (O'Reilly, 2022). +Não é uma obra derivada de _Python Fluente_ (Novatec, 2015). + +A presente tradução foi autorizada pela O'Reilly Media para distribuição nos termos da licença +«CC BY-NC-ND» [.small]#[fpy.li/4j]#. +Os arquivos-fonte em formato _Asciidoc_ estão no repositório público +https://github.com/pythonfluente/pythonfluente2e[]. + + +[NOTE] +==== +Correções e sugestões de melhorias são bem vindas! +Para contribuir, veja os +«__issues__» [.small]#[fpy.li/4m]# +no repositório: + +https://github.com/pythonfluente/pythonfluente2e[]. +==== + +=== Histórico das traduções + +Escrevi a primeira e a segunda edições deste livro originalmente em inglês, +para serem mais facilmente distribuídas no mercado internacional. + +Cedi os direitos exclusivos para a O'Reilly Media, +nos termos usuais de contratos com editoras de grande porte: +elas ficam com a maior parte do lucro, o direito de publicar, e +o direito de vender licenças para traduções. + +<<< + +Até 2022, a primeira edição foi publicada nestes idiomas: + +. inglês, +. português brasileiro, +. chinês simplificado (China), +. chinês tradicional (Taiwan), +. japonês, +. coreano, +. russo, +. francês, +. polonês. + +A ótima tradução PT-BR foi produzida e publicada +no Brasil pela Editora Novatec em 2015, sob licença da O'Reilly. + +Entre 2020 e 2022, atualizei e expandi bastante o livro para a segunda edição. +Sou muito grato aos dirigentes da +«Thoughtworks Brasil» [.small]#[fpy.li/4n]# +por terem me apoiado enquanto passei a maior parte de 2020 e 2021 +pesquisando, escrevendo, e revisando esta edição. + +Quando entreguei o manuscrito para a O'Reilly, +negociei um adendo contratual para liberar a tradução da +segunda edição em PT-BR com uma licença livre, +como uma contribuição para a comunidade Python lusófona. +A O'Reilly autorizou que essa tradução fosse publicada sob a licença CC BY-NC-ND: +«Creative Commons — Atribuição-NãoComercial-SemDerivações 4.0 Internacional» +[.small]#[fpy.li/4j]#. +Com essa mudança contratual, +a Editora Novatec não teve interesse em traduzir e publicar a segunda edição. + +Contratei Paulo Candido de Oliveira Filho +para traduzir. Fiz a revisão técnica com ajuda da comunidade, +gerei os arquivos HTML com «Asciidoctor» [.small]#[fpy.li/4p]# +e publiquei em https://PythonFluente.com. + +E agora, finalmente, temos a segunda edição em português impressa! + +_Luciano Ramalho, São Paulo, 24 de setembro de 2025_ + +<<< diff --git a/vol1/blurb-contra-capa.md b/vol1/blurb-contra-capa.md new file mode 100644 index 00000000..6f1044b1 --- /dev/null +++ b/vol1/blurb-contra-capa.md @@ -0,0 +1,9 @@ +PYTHON FLUENTE • 2ª Edição +Faça mais com menos código em Python. +Quanto mais entender seus padrões, interfaces e melhores práticas, maior será sua produtividade. +Publicado em nove idiomas, Fluent Python é um sucesso internacional porque não tem "hello, world". +É para quem já usa Python debulhar esta linguagem prática e poderosa. +DOMINE as estruturas de dados nativas, para não reinventar a roda. +EXPLORE ao máximo as funções, antes de criar classes. +ENTENDA práticas de concorrência e metaprogramação. +Essas e outras técnicas você encontra em Python Fluente, o livro definitivo sobre Python. \ No newline at end of file diff --git a/vol1/build-1.sh b/vol1/build-1.sh new file mode 100755 index 00000000..3ec2ee7d --- /dev/null +++ b/vol1/build-1.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e # exit when any command fails +asciidoctor -v vol1.adoc -o vol1.html +open vol1.html diff --git a/vol1/cap01.adoc b/vol1/cap01.adoc new file mode 100644 index 00000000..828fab48 --- /dev/null +++ b/vol1/cap01.adoc @@ -0,0 +1,841 @@ +[[ch_data_model]] +== O modelo de dados +:example-number: 0 +:figure-number: 0 + +[quote, Jim Hugunin, criador do Jython, co-criador do AspectJ, e arquiteto do .Net DLR (Dynamic Language Runtime)] +____ +O senso estético de Guido para o design de linguagens é incrível. +Conheci muitos projetistas capazes de criar linguagens teoricamente lindas, +que ninguém jamais usaria. +Mas Guido é uma daquelas raras pessoas capazes de criar uma linguagem só um pouco menos teoricamente linda que, +por isso mesmo, é uma delícia para programar. +footnote:[Traduzido de +«Story of Jython» [.small]#[fpy.li/1-1]#, +prefácio de +«Jython Essentials» [.small]#[fpy.li/1-2]#, +de Samuele Pedroni e Noel Rappin (O'Reilly).] +____ + +Uma das melhores qualidades de Python é sua consistência. +Após trabalhar com Python por algum tempo, é possível intuir, de modo informado e correto, +o funcionamento de recursos que você acabou de conhecer. + +Entretanto, se você aprendeu outra linguagem orientada a objetos antes de Python, +pode achar estranho usar `len(collection)` em vez de `collection.len()`. +Essa peculiaridade é a ponta de um iceberg que, quando bem compreendido, +é a chave para tudo aquilo que chamamos de _pythônico_. +O iceberg se chama Modelo de Dados de Python, +e é a API que usamos para fazer nossos objetos lidarem bem com +os recursos mais poderosos e característicos da linguagem. + +É((("Python Data Model", "overview of"))) possível pensar no modelo de dados como +uma descrição de Python na forma de um framework. +Ele formaliza as interfaces dos elementos constituintes da própria linguagem, +como sequências, funções, iteradores, corrotinas, classes, gerenciadores de contexto e assim por diante. + +Quando usamos um framework, +passamos um bom tempo programando métodos que são invocados pelo framework, +e não pelas nossas classes. +O mesmo acontece quando nos valemos do Modelo de Dados de Python para criar novas classes. +O interpretador de Python invoca((("special methods", "purpose of"))) +métodos especiais para realizar operações básicas sobre os objetos, +muitas vezes acionadas por uma sintaxe especial. +Os((("special methods", "naming conventions")))((("__ +(double underscore)")))((("double underscore (__)"))) +nomes dos métodos especiais são sempre precedidos e seguidos de dois sublinhados. +Por exemplo, a sintaxe `obj[key]` está amparada no método especial `+__getitem__+`. +Para resolver `my_collection[key]`, o interpretador chama `+my_collection.__getitem__(key)+`. + +<<< +Implementamos métodos especiais para que os objetos suportem: + +* Coleções + +* Acesso a atributos + +* Iteração (incluindo iteração assíncrona com `+async for+`) + +* Sobrecarga (_overloading_) de operadores + +* Invocação de funções e métodos + +* Representação e formatação de strings + +* Programação assíncrona usando `+await+` + +* Criação e destruição de objetos + +* Contextos gerenciados usando as instruções `with` ou `async with` + + +.Mágica e o "dunder" +[NOTE] +==== +O((("magic methods")))((("dunder methods"))) +termo _método mágico_ é gíria para "método especial". +Para falar de um método específico, como `+__getitem__+`, +pythonistas como o autor Steve Holden dizem "dunder-getitem". +"Dunder" é uma contração de _double underscore before and after_ +(sublinhado duplo antes e depois). +O capítulo «Análise Léxica» [.small]#[fpy.li/2d]# +de _A Referência da Linguagem Python_ adverte: +"_Qualquer_ uso de nomes no formato `+__*__+` que não siga explicitamente o uso documentado, +em qualquer contexto, está sujeito a quebra sem aviso prévio." +==== + +=== Novidades neste capítulo + +Esse((("Python Data Model", "significant changes to"))) capítulo sofreu poucas alterações desde a primeira edição, +pois é o Modelo de Dados de Python é muito estável. +As mudanças mais significativas foram: + +* Métodos especiais que suportam programação assíncrona e outras novas funcionalidades +foram acrescentados às tabelas na <>. + +* A <>, mostrando o uso de métodos especiais na <>, +incluindo a classe base abstrata `collections.abc.Collection`, introduzida no Python 3.6. + +<<< +Além disso, aqui((("f-string syntax", "benefits of"))) e por toda essa segunda edição, +adotei a sintaxe _f-string_, introduzida no Python 3.6, +que é mais legível e muitas vezes mais conveniente que as notações de formatação de strings mais antigas: +o((("str.format() method")))((("% (modulo) operator")))((("modulo (%) operator"))) +método `str.format()` e o operador `%`. + +[role="man-height-1-125"] +[TIP] +==== +Ainda existe((("my_fmt.format() method"))) uma razão para usar `my_fmt.format()`: +quando a definição de `my_fmt` precisa vir de um lugar diferente daquele onde +a operação de formatação precisa acontecer no código. +Por exemplo, quando `my_fmt` tem múltiplas linhas e é melhor definida em uma constante, +ou quando tem de vir de um arquivo de configuração ou de um banco de dados. +Essas são necessidades reais, mas não acontecem com frequência. +==== + +[[pythonic_card_deck]] +=== Um baralho pythônico + +O <> é((("__getitem__", +id="getitem01")))((("__len__", id="len01")))((("Pythonic Card Deck example", +id="pycard01")))((("Python Data Model", "__getitem__ and __len__", +id="PDMgetitem01", secondary-sortas="getitem")))((("special methods", +"__getitem__ and __len__", +id="SMgetitem01", secondary-sortas="getitem")))((("card deck example", id="carddeck01"))) +simples, mas demonstra as possibilidades que se abrem com a implementação de apenas dois métodos especiais, +`+__getitem__+` e `+__len__+`. + +[[ex_pythonic_deck]] +.Um baralho como uma sequência de cartas +==== +[source, python] +---- +include::../code/01-data-model/frenchdeck.py[] +---- +==== + +<<< +A((("collections.namedtuple"))) primeira coisa a notar é o uso de `collections.namedtuple` +para construir uma classe simples representando cartas individuais. +Usamos `namedtuple` para criar classes de objetos que são apenas um agrupamento de atributos, +sem métodos próprios, como um registro de banco de dados. +Neste exemplo, a utilizamos para fornecer uma boa representação textual para as cartas em um baralho, +como mostra a sessão no console: + +[source, python] +---- +>>> beer_card = Card('7', 'diamonds') +>>> beer_card +Card(rank='7', suit='diamonds') +---- + +Mas a parte central desse exemplo é a classe `FrenchDeck`. +Ela é curta, mas poderosa. +Primeiro, como qualquer coleção padrão de Python, +uma instância de `FrenchDeck` responde à +função((("len() function")))((("functions", "len() function"))) `len()`, +devolvendo o número de cartas naquele baralho: + +[source, python] +---- +>>> deck = FrenchDeck() +>>> len(deck) +52 +---- + +Ler cartas específicas do baralho é fácil, graças ao método `+__getitem__+`. +Por exemplo, a primeira e a última carta: + +[source, python] +---- +>>> deck[0] +Card(rank='2', suit='spades') +>>> deck[-1] +Card(rank='A', suit='hearts') +---- + +Deveríamos criar um método para obter uma carta aleatória? Não é necessário. +Python((("random.choice function"))) já tem uma função que devolve um item aleatório de uma sequência: `random.choice`. +Podemos usá-la em uma instância de `FrenchDeck`: + +[source, python] +---- +>>> from random import choice +>>> choice(deck) +Card(rank='3', suit='hearts') +>>> choice(deck) +Card(rank='K', suit='spades') +>>> choice(deck) +Card(rank='2', suit='clubs') +---- + +Acabamos((("special methods", "advantages of using"))) de ver duas vantagens de usar +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 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`. + +Mas não é só isso. + +Como nosso `+__getitem__+` usa o((("square brackets ([])")))((("[] (square brackets)"))) +operador `[]` de `self._cards`, +nosso baralho suporta fatiamento automaticamente. +Podemos olhar as três primeiras cartas no topo de um baralho, +e depois pegar só os ases, iniciando com o índice 12 e pulando 13 cartas por vez: + +[source, python] +---- +>>> deck[:3] +[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), +Card(rank='4', suit='spades')] +>>> deck[12::13] +[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), +Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')] +---- + +E como já temos o método especial `+__getitem__+`, nosso baralho é um objeto iterável, +ou seja, pode ser percorrido em um laço `for`: + +[source, python] +---- +>>> for card in deck: # doctest: +ELLIPSIS +... print(card) +Card(rank='2', suit='spades') +Card(rank='3', suit='spades') +Card(rank='4', suit='spades') +... +---- + +<<< +Também podemos iterar sobre o baralho na ordem inversa: + +[source, python] +---- +>>> for card in reversed(deck): # doctest: +ELLIPSIS +... print(card) +Card(rank='A', suit='hearts') +Card(rank='K', suit='hearts') +Card(rank='Q', suit='hearts') +... +---- + +.Reticências nos doctests +[NOTE] +==== +Sempre((("doctest package", "ellipsis in")))((("ellipsis (…)")))((("… (ellipsis)"))) que possível, +extraí as listagens do console de Python usadas neste livro com o +«`doctest`» [.small]#[fpy.li/2e]#, para garantir a precisão. +Quando a saída era longa demais, a parte omitida está marcada por reticências (`\...`), +como na última linha do trecho de código anterior. + +Nestes casos, usei((("+ELLIPSIS directive"))) a diretiva `# doctest: +ELLIPSIS` +para fazer o doctest funcionar. +Ao experimentar esses exemplos no console iterativo, +pode omitir todos os comentários de doctest. +==== + +A iteração muitas vezes é implícita. +Python invoca o método `+__contains__+` da coleção para tratar o operador `in`: `student in team`. +Mas se a coleção não((("__contains__"))) fornece um método `+__contains__+`, +o operador `in` realiza uma busca sequencial. +No nosso caso, `in` funciona com nossa classe `FrenchDeck` porque ela é iterável. +Veja a seguir: + +[source, python] +---- +>>> Card('Q', 'hearts') in deck +True +>>> Card('7', 'beasts') in deck +False +---- + +E o ordenamento? +Um sistema comum de ordenar cartas é por seu valor numérico (ases sendo os mais altos) e depois por naipe, +na ordem espadas (o mais alto), copas, ouros e paus (o mais baixo). +Aqui está uma função que ordena as cartas com essa regra, +devolvendo `0` para o 2 de paus e `51` para o Ás de espadas. + +[source, python] +---- +suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0) + +def spades_high(card): + rank_value = FrenchDeck.ranks.index(card.rank) + return rank_value * len(suit_values) + suit_values[card.suit] +---- + +Agora podemos ordenar nosso baralho usando `spades_high` como critério de ordenação: + +[source, python] +---- +>>> for card in sorted(deck, key=spades_high): # doctest: +ELLIPSIS +... print(card) +Card(rank='2', suit='clubs') +Card(rank='2', suit='diamonds') +Card(rank='2', suit='hearts') +... (46 cards omitted) +Card(rank='A', suit='diamonds') +Card(rank='A', suit='hearts') +Card(rank='A', suit='spades') +---- + +Apesar do `FrenchDeck` herdar implicitamente da classe `object`, +a maior parte de sua funcionalidade não é herdada, vem do modelo de dados e de composição. +Ao implementar os métodos especiais `+__len__+` e `+__getitem__+`, +nosso `FrenchDeck` se comporta como uma sequência Python padrão, +podendo assim se beneficiar de recursos centrais da linguagem (por exemplo, iteração e fatiamento), +e da biblioteca padrão, como mostramos nos exemplos usando `random.choice`, +`reversed`, e `sorted`. +Graças à composição, +as implementações de `+__len__+` e `+__getitem__+` podem delegar todo o trabalho para um objeto `list`, +especificamente `self._cards`.((("", startref="PDMgetitem01")))((("", startref="getitem01")))((("", +startref="len01")))((("", startref="pycard01")))((("", startref="SMgetitem01")))((("", startref="carddeck01"))) + +.Como embaralhar as cartas? +[NOTE] +===================================================================== +Como foi implementado até aqui, um `FrenchDeck` não pode ser embaralhado, +porque as cartas e suas posições não podem ser alteradas, +exceto violando o encapsulamento e manipulando o atributo `_cards` diretamente. +No «Capítulo 13» [.small]#[vol.2, fpy.li/13]# +vamos corrigir isso acrescentando um método `+__setitem__+` de uma linha. +Você consegue imaginar como ele seria implementado? +===================================================================== + + +[[how_special_used]] +=== Como os métodos especiais são utilizados + +A((("Python Data Model", "using special methods", id="PDMspecmeth01")))((("special methods", "calling"))) +primeira coisa a saber sobre os métodos especiais é que eles são feitos para +serem invocados pelo interpretador Python, e não por você. +Você não escreve `+my_object.__len__()+`. +Escreve `len(my_object)` e, se `my_object` é uma instância de uma classe definida por você, +então Python chama o método `+__len__+` que você implementou. + +Mas o interpretador pega um atalho quando está lidando com um tipo embutido como `list`, `str`, `bytearray`, +ou extensões compiladas como os arrays da NumPy. +As coleções de tamanho variável de Python escritas em C incluem uma +structfootnote:[Uma struct do C é um tipo de registro com campos nomeados.] +chamada `PyVarObject`, com um campo `ob_size` que registra a quantidade de itens na coleção. +Então, se `my_object` é uma instância de algum daqueles tipos embutidos, +`len(my_object)` devolve diretamente o valor do campo `ob_size`, +e isso é mais rápido que chamar um método. + +Na maior parte das vezes, a chamada a um método especial é implícita. +Por exemplo, o comando `for i in x:` na verdade gera uma invocação de `iter(x)`, +que por sua vez pode chamar `+x.__iter__()+` se esse método estiver disponível, +ou usar `+x.__getitem__()+`, como no exemplo do `FrenchDeck`. + +Em condições normais, seu código não deveria conter muitas chamadas diretas a métodos especiais. +A menos que você esteja fazendo muita metaprogramação, +implementar métodos especiais deve ser mais frequente que invocá-los explicitamente. +O((("__init__"))) +único método especial invocado normalmente pelo nosso código é `+__init__+`, +para invocar a inicialização da superclasse na implementação do seu próprio `+__init__+`. +Mas mesmo este método é invocado com muito mais frequência pelo Python, +e não por nós. + +Geralmente, se você precisa invocar um método especial, +é melhor chamar a função embutida relacionada (por exemplo, `len`, `iter`, `str`, etc.). +Essas funções invocam o método especial correspondente, +mas também fornecem outros serviços e—para tipos embutidos—são mais rápidas que chamadas a métodos. + +Na próxima seção, veremos alguns dos usos mais importantes dos métodos especiais: +emular tipos numéricos, representar objetos na forma de strings, +determinar o valor booleano de um objeto, e implementar coleções. + + +[[data_model_emulating_sec]] +==== Emulando tipos numéricos + +Vários((("special methods", "emulating numeric types", id="SMnumeric01")))((("numeric types", +"emulating using special methods", id="NTemul01"))) +métodos especiais permitem que objetos criados pelo usuário respondam a operadores como `+`. +Vamos tratar disso com mais detalhes no «Capítulo 16» [.small]#[vol.2, fpy.li/16]#. +Aqui, nosso objetivo é continuar ilustrando o uso dos métodos especiais, através de outro exemplo simples. + +Vamos((("vectors", "representing two-dimensional", id="Vtwo01"))) implementar uma classe para representar +vetores bidimensionais—isto é, vetores euclidianos como aqueles usados em matemática e física +(<>). + +[TIP] +====== +O tipo embutido `complex` pode ser usado para representar vetores bidimensionais, +mas nossa classe pode ser estendida para representar vetores N-dimensionais. +Faremos isso no «Capítulo 17» [.small]#[vol.3, fpy.li/17]#. +====== + +[[vectors_fig]] +.Soma de vetores: `Vector(2, 4) + Vector(2, 1)` devolve `Vector(4, 5)`. +image::../images/flpy_0101.png[vetores 2D,align="center",pdfwidth=7cm] + +Vamos começar a projetar a API para essa classe escrevendo uma sessão de console simulada, +que depois podemos usar como um doctest. +O trecho a seguir testa a adição de vetores ilustrada na <>: + +[source, python] +---- +>>> v1 = Vector(2, 4) +>>> v2 = Vector(2, 1) +>>> v1 + v2 +Vector(4, 5) +---- + +Observe((("+ operator"))) como o operador `+` produz um novo objeto `Vector(4, 5)`. + +A((("abs built-in function")))((("functions", "abs built-in function"))) função embutida `abs` +devolve o valor absoluto de números inteiros e de ponto flutuante, e a magnitude de números `complex`. +Então, por consistência, nossa API também usa `abs` para calcular a magnitude de um vetor: + +[source, python] +---- +>>> v = Vector(3, 4) +>>> abs(v) +5.0 +---- + +Podemos((("* (star) operator")))((("multiplication, scalar")))((("star (*) operator"))) +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] +---- +>>> v * 3 +Vector(9, 12) +>>> abs(v * 3) +15.0 +---- + +O <> é uma classe `Vector` que implementa as operações descritas acima, +usando os métodos especiais((("__repr__", +id="repr01")))((("__mul__")))((("__add__")))((("__abs__"))) +`+__repr__+`, `+__abs__+`, `+__add__+`, e `+__mul__+`. + +[[ex_vector2d]] +.Uma classe simples para representar um vetor 2D. +==== +[source, python] +---- +include::../code/01-data-model/vector2d_pt.py[] +---- +==== + +<<< +Implementamos cinco métodos especiais, além do costumeiro `+__init__+`. +Veja que nenhum deles é chamado diretamente dentro da classe ou durante seu uso normal, +ilustrado pelos doctests. +Como mencionado antes, o interpretador Python é o único usuário frequente da maioria dos métodos especiais. + +O <> implementa dois operadores: `{plus}` e `*`, +para demonstrar o uso básico de `+__add__+` e `+__mul__+`. +Nos dois casos, os métodos criam e devolvem uma nova instância de `Vector`, +e não modificam nenhum dos operandos: `self` e `other` são apenas lidos. +Esse é o comportamento esperado de operadores infixos: criar novos objetos e não tocar em seus operandos. +Vou falar mais sobre esse tópico no «Capítulo 16» [.small]#[vol.2, fpy.li/16]#. + +[WARNING] +==== +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 por escalar. +Vamos consertar isso com o método especial `+__rmul__+` no «Capítulo 16» [.small]#[vol.2, fpy.li/16]#. +==== + +Nas seções seguintes, vamos discutir os outros métodos especiais de +`Vector`.((("", startref="SMnumeric01")))((("", startref="NTemul01")))((("", startref="Vtwo01"))) + + +[[repr_intro]] +==== Representação como string + +O((("special methods", "string representation")))((("strings", "representation using special methods"))) +método especial `+__repr__+` é chamado pela função embutida `repr` +para obter a representação do objeto como uma string, para depuração. +Sem um `+__repr__+` customizado, o console de Python mostraria uma instância de `Vector` como +``. + +O console iterativo e o depurador de Python (`pdb`) invocam `repr` para exibir o resultado das expressões. +O `repr` também é usado: + +* Pelo marcador posicional `%r` na formatação clássica com o operador `%`. + +Ex.: `'%r' % my_obj` +* Pelo sinalizador de conversão `!r` na nova +«sintaxe de strings de formato» [.small]#[fpy.li/2f]# +usada nas((("f-string syntax", "string representation using special methods"))) +_f-strings_ e no método `str.format`. Ex: `f'{my_obj!r}'` + +Note que a _f-string_ no nosso `+__repr__+` usa `!r` para obter a representação padrão dos atributos a serem exibidos. +Isso é uma boa prática, pois durante uma sessão de depuração podemos ver a diferença +entre `Vector(1, 2)` e `Vector('1', '2')`. +Este segundo objeto não funcionaria no contexto desse exemplo, +porque o construtor espera que os argumentos sejam números, não `str`. + +A string devolvida por `+__repr__+` não deve ser ambígua e, se possível, +deve corresponder ao código-fonte necessário para recriar o objeto representado. +É por isso que nossa representação de `Vector` se parece com uma chamada ao construtor da classe, +por exemplo `Vector(3, 4)`. + +Por((("__str__")))((("str() function")))((("functions", "str() function"))) +outro lado, `+__str__+` é chamado pela função embutida `str()` e usado automaticamente pela função `print`. +Ele deve devolver uma string apropriada para ser exibida aos usuários finais da aplicação. + +Algumas vezes, a própria string devolvida por `+__repr__+` é adequada para exibir ao usuário, +e você não precisa programar `+__str__+`, +porque a implementação de `+__str__+` herdada da classe `object` já invoca `+__repr__+`. +O <> é um dos muitos exemplos neste livro com um `+__str__+` customizado. + +[TIP] +==== +Programadores com experiência anterior em linguagens que usam um método `toString` +tendem a implementar `+__str__+` e não `+__repr__+`. +Se você for implementar apenas um desses métodos especiais, escolha `+__repr__+`. + +«_What is the difference between `+__str__+` and `+__repr__+` in Python?_» [.small]#[fpy.li/1-5]# +(Qual a diferença entre `+__str__+` e `+__repr__+` em Python?) +é uma questão no Stack Overflow com ótimas contribuições dos pythonistas +Alex Martelli e Martijn Pieters.((("", startref="repr01"))) +==== + + +==== O valor booleano de um tipo customizado + +Apesar ((("__bool__")))((("special methods", +"Boolean values of custom types")))((("Boolean values, custom types and")))((("bool type"))) +de Python ter um tipo `bool`, a linguagem aceita qualquer objeto em um contexto booleano, +tal como as expressões controlando instruções `if` ou `while`, ou como operandos de `and`, `or` e `not`. +Para determinar se um valor `x` é _verdadeiro_ ou _falso_, Python invoca `bool(x)`, +que devolve somente `True` ou `False`. + +Por padrão, instâncias de classes definidas pelo usuário são consideradas verdadeiras, +a menos que `+__bool__+` ou `+__len__+` sejam implementadas. +Basicamente, `bool(x)` chama `+x.__bool__()+` e usa o resultado. +Se `+__bool__+` não está implementado, Python tenta invocar `+x.__len__()+`, +e se esse último devolver zero, `bool` devolve `False`. +Caso contrário, `bool` devolve `True`. + +Nossa implementação de `+__bool__+` é simples: +ela devolve `False` se a magnitude do vetor for zero, caso contrário devolve `True`. +Convertemos a magnitude para um valor booleano usando `bool(abs(self))`, +porque espera-se que `+__bool__+` devolva um booleano. +Fora dos métodos `+__bool__+`, raramente é necessário chamar `bool()` explicitamente, +porque qualquer objeto pode ser usado em um contexto booleano. + +Observe que o método especial `+__bool__+` permite que seus objetos sigam as +regras de teste do valor verdade definidas no +capítulo «Tipos Embutidos» [.small]#[fpy.li/2g]# +da documentação da _Biblioteca Padrão de Python_. + +[NOTE] +==== +Essa é uma implementação mais rápida de `+Vector.__bool__+`: + +[source, python] +---- + def __bool__(self): + return bool(self.x or self.y) +---- + +Isso é mais difícil de ler, mas evita a jornada através de `abs`, `+__abs__+`, os quadrados, e a raiz quadrada. +A conversão explícita para `bool` é necessária porque `+__bool__+` deve devolver um booleano, +e `or` devolve um dos seus operandos no formato original: +`x or y` resulta em `x` se `x` for verdadeiro, caso contrário resulta em `y`, +qualquer que seja o valor deste último. +==== + + +[[collection_api]] +==== A API de Collection + +A <> documenta((("special methods", "Collection API", +id="SMcollection01")))((("Collection API", id="Cspeical01")))((("ABCs (abstract base classes)", +"UML class diagrams", id="abcs01")))((("UML class diagrams", "fundamental collection types"))) +as interfaces dos tipos de coleções essenciais na linguagem. +Todas as classes no diagrama são ABCs—_classes base abstratas_ +(_ABC_ é sigla para _Abstract Base Class_). +As ABCs e o módulo `collections.abc` são tratados no «Capítulo 13» [.small]#[vol.2, fpy.li/13]#. +O objetivo dessa pequena seção é dar uma visão panorâmica das interfaces das coleções mais importantes de Python, +mostrando como elas são criadas a partir de métodos especiais. + +[[collection_uml]] +.Diagrama de classes UML com os tipos fundamentais de coleções. Métodos com nome em _itálico_ são abstratos, então precisam ser implementados pelas subclasses concretas, como `list` e `dict`. Os demais métodos têm implementações concretas, então as subclasses podem herdá-los. +image::../images/flpy_0102.png[align="center",pdfwidth=11cm] + +Cada uma das ABCs no topo da hierarquia tem um único método especial. +A ABC `Collection` (introduzida no Python 3.6) unifica as três interfaces essenciais +que toda coleção deveria implementar: + +* `Iterable`, para((("Iterable interface")))((("interfaces", "Iterable interface"))) suportar a instrução `for`, +e outras formas de iteração +* `Sized` para((("Sized interface")))((("interfaces", "Sized interface"))) suportar a função embutida `len` +* `Container` para((("Container interface")))((("interfaces", "Container interface"))) suportar o operador `in` + +Na verdade, Python não exige que classes concretas herdem de qualquer uma dessas ABCs. +Qualquer classe que implemente `+__len__+` satisfaz a interface `Sized`. + +Três especializações muito importantes de `Collection` são: + +* `Sequence`, formalizando a interface de tipos embutidos como `list` e `str` +* `Mapping`, implementado por `dict`, `collections.defaultdict`, etc. +* `Set`, a interface dos tipos embutidos `set` e `frozenset` + +Apenas `Sequence` é `Reversible`, porque sequências suportam o ordenamento arbitrário de seu conteúdo, +ao contrário de mapeamentos (_mappings_) e conjuntos (_sets_). + +[NOTE] +==== +Desde((("keys", "preserving key insertion order"))) Python 3.7, o tipo `dict` é oficialmente "ordenado", +mas isso só quer dizer que a ordem de inserção das chaves é preservada. +Você não pode rearranjar as chaves em um `dict` da forma que quiser. +==== + +Todos os métodos especiais na ABC `Set` implementam operadores infixos. +Por exemplo, `a & b` calcula a interseção entre os conjuntos `a` e `b`, +e é implementada no método especial `+__and__+`. + +Os próximos dois capítulos vão tratar em detalhes das sequências, mapeamentos e conjuntos da biblioteca padrão. + +Agora vamos considerar as duas principais categorias dos métodos especiais definidos no Modelo de Dados de +Python.((("", startref="PDMspecmeth01")))((("", startref="SMcollection01")))((("", startref="Cspeical01")))((("", startref="abcs01"))) + +[[overview_special_methods]] +=== Visão geral dos métodos especiais + +O((("Python Data Model", "special methods overview", id="PDMspmtov01")))((("special methods", +"special method names (operators excluded)"))) +capítulo «Modelo de Dados» [.small]#[fpy.li/2j]# +de _A Referência da Linguagem Python_ lista mais de 80 nomes de métodos especiais. +Mais da metade deles implementa operadores aritméticos, de comparação, ou bit-a-bit. +Para ter uma visão geral do que está disponível, veja as tabelas a seguir. + +A <> mostra nomes de métodos especiais, excluindo aqueles usados para implementar +operadores infixos ou funções matemáticas fundamentais como `abs`. +A maioria desses métodos será tratada ao longo do livro, incluindo as adições mais recentes: +métodos especiais assíncronos como `+__anext__+` (acrescentado no Python 3.5), +e o método de configuração de classes, `+__init_subclass__+` (do Python 3.6). + +<<< +[[special_names_tbl]] +.Nomes de métodos especiais (excluindo operadores) +[options="header", cols="2,3"] +|================================================================================================= +|Categoria|Nomes dos métodos +|Representação de string/bytes|`+__repr__ __str__ __format__ __bytes__ __fspath__+` +|Conversão para número|`+__bool__ __complex__ __int__ __float__ __hash__ __index__+` +|Emulação de coleções|`+__len__ __getitem__ __setitem__ __delitem__ __contains__+` +|Iteração|`+__iter__ __aiter__ __next__ __anext__ __reversed__+` +|Execução de invocávelfootnote:[NT: invocável é um objeto que contém código que pode ser executado com a sintaxe `o()`. Isso inclui funções, métodos, classes e outros objetos, como veremos em detalhes no <>. A documentação do Python usa o termo "chamável". Adotamos "invocável" para evitar confusão entre dois sentidos do verbo "chamar": executar (uma função) ou nomear (a função chama-se...).] ou corrotina|`+__call__ __await__+`|Criação e destruição de instâncias|`+__new__ __init__ __del__+` +|Gerenciamento de atributos|`+__getattr__ __getattribute__ __setattr__ __delattr__ __dir__+` +|Descritores de atributos|`+__get__ __set__ __delete__ __set_name__+` +|Classes base abstratas|`+__instancecheck__ __subclasscheck__+` +|Metaprogramação de classes|`+__prepare__ __init_subclass__ __class_getitem__ __mro_entries__+` +|================================================================================================= + +Operadores infixos e numéricos são suportados pelos métodos especiais listados na +<>. +Aqui os nomes mais recentes são `+__matmul__+`, `+__rmatmul__+`, e `+__imatmul__+`, +adicionados no Python 3.5 para suportar o uso de `@` como operador de multiplicação de matrizes, +como veremos no «Capítulo 16» [.small]#[vol.2, fpy.li/16]#.((("special methods", "special method names and symbols for operators"))) + +[[special_operators_tbl]] +.Nomes e símbolos de métodos especiais para operadores +[options="header"] +|===================================================================================================================================================================================== +|Categoria do operador|Símbolos|Nomes de métodos +|Unário numérico| `- + abs()` | `+__neg__ __pos__ __abs__+` +|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| (operadores aritméticos com operandos trocados) |`+__radd__ __rsub__ __rmul__ __rtruediv__ __rfloordiv__ __rmod__ __rmatmul__ __rdivmod__ __rpow__+` +|Atribuição aritmética aumentada| `+= -= \*= /= //= %= @= **=` | `+__iadd__ __isub__ __imul__ __itruediv__ __ifloordiv__ __imod__ __imatmul__ __ipow__+` +|Bit a bit | `& \| ^ << >> ~` | `+__and__ __or__ __xor__ __lshift__ __rshift__ __invert__+` +|Bit a bit reversa| (operadores bit a bit com operandos trocados) | `+__rand__ __ror__ __rxor__ __rlshift__ __rrshift__+` +|Atribuição bit a bit aumentada| `&= \|= ^= <<= >>=` | `+__iand__ __ior__ __ixor__ __ilshift__ __irshift__+` +|===================================================================================================================================================================================== + +[NOTE] +==== +Python invoca um método especial de operador reverso no segundo argumento +quando o método especial correspondente não pode ser usado no primeiro operando. +Atribuições aumentadas são um atalho combinando um operador infixo com uma atribuição de variável, por exemplo `a += b`. + +O «Capítulo 16» [.small]#[vol.2, fpy.li/16]# +explica em detalhes os operadores reversos e a atribuição aumentada.((("", startref="PDMspmtov01"))) +==== + + +=== Por que len não é um método? + +Em 2013, fiz((("Python Data Model", "making len work with custom objects")))((("__len__"))) +essa pergunta a Raymond Hettinger, um dos mantenedores do Python, +e sua resposta foi basicamente uma citação do +«"The Zen of Python" (_O Zen do Python_)» [.small]#[fpy.li/1-8]#: "a praticidade vence a pureza." +Na <>, descrevi como `len(x)` roda muito rápido quando `x` é uma instância de um tipo embutido. +Nenhum método é chamado para os objetos embutidos do CPython: o tamanho é simplesmente lido de um campo em uma struct C. +Obter o número de itens em uma coleção é uma operação comum, e precisa funcionar de forma eficiente para tipos tão básicos e diferentes como +`str`, `list`, `memoryview`, e assim por diante. + +Em outras palavras, `len` não é chamado como um método porque recebe um tratamento especial como parte do Modelo de Dados de Python, +da mesma forma que `abs`. +Graças ao método especial `+__len__+`, também é possível fazer `len` funcionar com objetos das classes que nós criamos. +Isso é um compromisso justo entre a necessidade de objetos embutidos eficientes e a consistência da linguagem. +Também diz "O Zen de Python": "Casos especiais não são especiais o bastante para quebrar as regras." + + +[NOTE] +==== +Pensar em `abs` e `len` como operadores unários nos deixa mais inclinados a perdoar seus aspectos funcionais, +contrários à sintaxe de chamada de método que esperaríamos em uma linguagem orientada a objetos. +De fato, Python herdou muito de sua sintaxe e estruturas de dados da linguagem ABC, +onde existe o operador `#`, +que equivale ao `len`: em ABC, `len(s)` escreve-se `#s`. +Quando usado como operador infixo, +`x#s` conta as ocorrências de `x` em `s`, +que em Python obtemos com `s.count(x)`, +para qualquer sequência `s`. +==== + +[role="pagebreak-before less_space"] +=== Resumo do capítulo + +Ao((("Python Data Model", "overview of"))) implementar métodos especiais, +as classes que você cria podem se comportar como os tipos embutidos, +permitindo o estilo de programação expressivo que a comunidade considera _pythônico_. + +Uma exigência básica para um objeto em Python é fornecer strings representando a si mesmo que possam ser usadas, +uma para depuração e registro (_log_), outra para apresentar aos usuários finais. +É para isso que os métodos especiais `+__repr__+` e `+__str__+` existem no modelo de dados. + +Emular sequências, como mostrado com o exemplo do `FrenchDeck`, é um dos usos mais comuns dos métodos especiais. +Por exemplo, bibliotecas de banco de dados frequentemente devolvem resultados de consultas na forma de coleções similares a sequências. +Tirar o máximo proveito dos tipos de sequências existentes é o assunto do <>. +Como implementar suas próprias sequências será visto no «Capítulo 12» [.small]#[vol.2, fpy.li/12]#, +onde criaremos uma extensão multidimensional da classe `Vector`. + +Graças à sobrecarga de operadores, Python oferece uma rica seleção de tipos numéricos, +desde os tipos embutidos até `decimal.Decimal` e `fractions.Fraction`, +todos eles suportando operadores aritméticos infixos. +A biblioteca de ciência de dados _NumPy_ suporta operadores infixos com matrizes e tensores. +A implementação de operadores—incluindo operadores reversos e atribuição aumentada—será vista no +«Capítulo 16» [.small]#[vol.2, fpy.li/16]#, +usando melhorias do exemplo `Vector`. + +Também veremos o uso e a implementação da maioria dos outros métodos especiais do Modelo de Dados de Python ao longo deste livro. + +=== Para saber mais + +O((("Python Data Model", "further reading on"))) +capítulo «Modelo de Dados» [.small]#[fpy.li/2j]# +em _A Referência da Linguagem Python_ é a fonte canônica +para o assunto desse capítulo e de uma boa parte deste livro. + +«Python in a Nutshell, 3rd ed.» [.small]#[fpy.li/pynut3]#, +de Alex Martelli, Anna Ravenscroft, e Steve Holden (O'Reilly) +tem uma excelente cobertura do modelo de dados. +Sua descrição da mecânica de acesso a atributos é a mais competente que já vi, +perdendo apenas para o próprio código-fonte em C do CPython. +Martelli também é um contribuidor prolífico do Stack Overflow, +com mais de 6200 respostas publicadas. +Veja «seu perfil de usuário no _Stack Overflow_» [.small]#[fpy.li/1-9]#. + +David Beazley tem dois livros tratando do modelo de dados em detalhes, no contexto de Python 3: +«Python Essential Reference» [.small]#[fpy.li/2k]#, 4th ed. (Addison-Wesley), e +«Python Cookbook, 3rd ed» [.small]#[fpy.li/pycook3]# (O'Reilly), colaborando com Brian K. Jones. + +«The Art of the Metaobject Protocol» [.small]#[fpy.li/2m]# (MIT Press) +de Gregor Kiczales, Jim des Rivieres, e Daniel G. Bobrow +explica o conceito de um protocolo de metaobjetos, +do qual o Modelo de Dados de Python é um exemplo. + +<<< +.Ponto de Vista +**** + +[role="soapbox-title"] +**Modelo de dados ou modelo de objetos?** + + +Aquilo((("Soapbox sidebars", "data model versus object model")))((("Python Data Model", "Soapbox discussion"))) +que a documentação de Python chama de "Modelo de Dados de Python", a maioria dos autores diria que é o "Modelo de objetos de Python" + +O _Python in a Nutshell_, 3rd ed. de Martelli, Ravenscroft, e Holden, e o _Python Essential Reference_, 4th ed., +de David Beazley são os melhores livros sobre o Modelo de Dados de Python, mas se referem a ele como o "modelo de objetos." +Na Wikipedia, a primeira definição de +«modelo de objetos» [.small]#[fpy.li/1-10]# +é: "as propriedades dos objetos em geral em uma linguagem de programação de computadores específica." +É disso que o Modelo de Dados de Python trata. +Neste livro, usarei "modelo de dados" porque esse é o título do +«capítulo de A Referência da Linguagem Python» [.small]#[fpy.li/2j]# +mais relevante para nossas discussões. + +[role="soapbox-title"] +**Métodos de "trouxas"** + +«_The Original Hacker's Dictionary_ (Dicionário Hacker Original)» [.small]#[fpy.li/1-11]# +define ((("Soapbox sidebars", "magic methods")))((("magic methods"))) _mágica_ como +"algo ainda não explicado ou muito complicado para explicar" ou "uma funcionalidade, +em geral não divulgada, que permite fazer algo que de outra forma seria impossível." + +Ruby tem o equivalente aos métodos especiais, conhecidos como _métodos mágicos_ naquela comunidade. +Alguns na comunidade Python também adotam esse termo. +Acredito que os métodos especiais são o contrário de mágica. +Python e Ruby oferecem a seus usuários um rico protocolo de metaobjetos integralmente documentado, +permitindo que "trouxas" como você e eu possam emular muitas das funcionalidades disponíveis para os +mantenedores que escrevem os interpretadores daquelas linguagens. + +Por outro lado, pense em Go. +Alguns objetos naquela linguagem têm funcionalidades que são mágicas, +no sentido de não poderem ser emuladas em nossos próprios objetos definidos pelo usuário. +Por exemplo, os arrays, strings e mapas de Go suportam o uso de colchetes para acesso a um item, na forma `a[i]`. +Mas não há como fazer a notação `[]` funcionar com um novo tipo de coleção definido por você. + +Talvez, no futuro, os projetistas de Go melhorem seu protocolo de metaobjetos. +Em 2021, ele ainda é mais limitado do que Python, Ruby, e JavaScript oferecem. + + +[role="soapbox-title"] +**Metaobjetos** + +_The Art of the Metaobject Protocol (AMOP)_ (_A Arte do protocolo de metaobjetos_) +é((("Soapbox sidebars", "metaobjects")))((("metaobjects"))) meu título favorito entre livros de computação. +Mas o menciono aqui porque o termo _protocolo de metaobjetos_ é útil para pensar sobre o Modelo de Dados de Python, +e sobre recursos similares em outras linguagens. +A parte _metaobjetos_ se refere aos objetos que são os componentes essenciais da própria linguagem. +Nesse contexto, _protocolo_ é sinônimo de _interface_. +Assim, um _protocolo de metaobjetos_ é um sinônimo chique para modelo de objetos: +uma API para os elementos fundamentais da linguagem. + +Um protocolo de metaobjetos rico permite estender a linguagem para suportar novos paradigmas de programação. +Gregor Kiczales, o primeiro autor do _AMOP_, mais tarde se tornou um pioneiro da programação orientada a aspecto, +e o autor inicial do AspectJ, uma extensão de Java implementando aquele paradigma. +A programação orientada a aspecto é mais fácil de implementar em uma linguagem dinâmica como Python, +e alguns frameworks fazem exatamente isso. +Um exemplo importante é a +«_zope.interface_» [.small]#[fpy.li/1-12]#, +parte do framework Zope sobre o qual o sistema de gerenciamento de conteúdo +«_Plone_» [.small]#[fpy.li/2n]# é construído. +**** + diff --git a/vol1/cap02.adoc b/vol1/cap02.adoc new file mode 100644 index 00000000..3510c9d5 --- /dev/null +++ b/vol1/cap02.adoc @@ -0,0 +1,2735 @@ +[[ch_sequences]] +== Uma coleção de sequências +:example-number: 0 +:figure-number: 0 + +[quote, Leo Geurts, Lambert Meertens, e Steven Pemberton, ABC Programmer's Handbook, p.8] +____ +Como podem ter notado, várias das operações mencionadas funcionam da mesma forma com textos, listas e tabelas. +Coletivamente, textos, listas e tabelas são chamados de 'trens' (_trains_). [...] +A instrução `FOR` também funciona, de forma genérica, com trens. +____ + + +Antes de criar Python, Guido foi um dos desenvolvedores da linguagem +ABC—um projeto de pesquisa de 10 anos para criar um ambiente de programação para iniciantes. +A ABC introduziu várias ideias que hoje consideramos "pythônicas": +operações genéricas com diferentes tipos de sequências, tipos tupla e mapeamento embutidos, +estrutura de blocos por indentação, tipagem forte sem declaração de variáveis, entre outras. +A usabilidade de Python não é acidental. + +Python herdou da ABC o tratamento uniforme de sequências. +Strings, listas, sequências de bytes, arrays, elementos XML e resultados vindos de bancos de dados +compartilham um rico conjunto de operações comuns, incluindo iteração, fatiamento, ordenação e concatenação. + +Entender a variedade de sequências disponíveis no Python evita reinventar a roda, +e sua interface comum nos inspira a criar APIs que +suportem e aproveitem bem os tipos de sequências existentes e futuros. + +A maior parte deste capítulo trata das sequências em geral, +desde a conhecida `list` até os tipos `str` e `bytes`, adicionados no Python 3. +Tópicos específicos sobre listas, tuplas, arrays e filas também foram incluídos, +mas os detalhes sobre strings Unicode e sequências de bytes são tratados no <>. +Além disso, a ideia aqui é falar sobre os tipos de sequências prontas para usar. +A criação de novos tipos de sequência é o tema do +«Capítulo 12» [.small]#[vol.2, fpy.li/12]#. + +Os((("sequences", "topics covered"))) principais tópicos cobertos neste capítulo são: + +* Compreensão de listas e os fundamentos das expressões geradoras +* Usar tuplas como registros ou como listas imutáveis +* Desempacotamento de sequências e padrões de sequências +* Fatiamento de sequências para leitura e escrita +* Tipos especializados de sequências, como arrays e filas + +=== Novidades neste capítulo + +A((("sequences", "significant changes to"))) atualização mais importante desse capítulo é +a <>, +primeira abordagem das instruções `match/case` +introduzidas no Python 3.10. + +As outras mudanças são aperfeiçoamentos da primeira edição: + +* Um novo diagrama e uma nova descrição do funcionamento interno das +sequências, contrastando contêineres e sequências planas. + +* Comparação entre `list` e `tuple` quanto a desempenho e memória. + +* Ressalvas sobre tuplas com elementos mutáveis, e como detectá-las. + +Movi a discussão sobre tuplas nomeadas para a <>, +onde elas são comparadas com `typing.NamedTuple` e `@dataclass`. + +[NOTE] +==== +Para abrir espaço para conteúdo novo mantendo o número de páginas dentro do razoável, a seção +_Managing Ordered Sequences with Bisect_ (Gerenciando sequências ordenadas com bisect) da primeira edição +agora é um «artigo» [.small]#[fpy.li/bisect]# +no site que complementa o livro. +==== + +=== Visão geral das sequências embutidas + +A((("sequences", "overview of built-in", id="Soverv02"))) biblioteca padrão oferece +uma boa seleção de tipos de sequências, implementadas em C: + +Sequências contêiner:: + Podem((("container sequences"))) armazenar itens de tipos diferentes, incluindo contêineres aninhados e objetos de qualquer tipo. + Alguns exemplos: `list`, `tuple`, e `collections.deque`. +Sequências planas:: + Armazenam((("flat sequences"))) itens de algum tipo simples, mas não outras coleções ou referências a objetos. + Alguns exemplos: `str`, `bytes`, e `array.array`. + +Uma((("tuples", "simplified memory diagram for")))((("arrays"))) _sequência contêiner_ mantém +referências para os objetos que contém, que podem ser de qualquer tipo, +enquanto uma _sequência plana_ armazena o valor de seu conteúdo em seu próprio espaço de memória, +e não como objetos Python distintos. +Veja a <>. + +[[container_v_flat_img]] +.Diagramas de memória simplificados mostrando uma `tuple` e um `array`, cada uma com três itens. As células em cinza representam o cabeçalho de cada objeto Python na memória. A `tuple` tem um array de ponteiros para seus itens. Cada item é um objeto Python separado, possivelmente contendo também referências aninhadas a outros objetos Python, como aquela lista de dois itens. Por outro lado, um `array` Python é um único objeto, contendo um array da linguagem C com três números de ponto flutuante no formato nativo da CPU. +image::../images/flpy_0201.png[align="center",pdfwidth=11cm] + +Dessa forma, sequências planas são mais compactas, mas só podem armazenar valores primitivos como +bytes e números inteiros e de ponto flutuante. + +[NOTE] +==== +Todo objeto Python na memória tem um cabeçalho com metadados. +O objeto Python mais simples, um `float`, tem um campo de valor e dois campos de metadados: + +* `ob_refcnt`: a contagem de referências ao objeto +* `ob_type`: um ponteiro para o tipo do objeto +* `ob_fval`: um `double` de C mantendo o valor do `float` + +No Python 64-bits, cada um desses campos ocupa 8 bytes. +Por isso, um array de números de ponto flutuante é mais compacto que uma tupla de números de ponto flutuante: +o array é um único objeto contendo apenas o valor dos números, +enquanto a tupla é formada por vários objetos—a própria tupla e cada objeto `float` que ela contém. +==== + +[role="pagebreak-before less_space"] +Outra forma de agrupar as sequências é por mutabilidade: + +Sequências mutáveis:: + Podem((("mutable sequences"))) ter seu conteúdo modificado, reduzido ou ampliado. + Por exemplo, `list`, `bytearray`, `array.array` e `collections.deque`. +Sequências imutáveis:: + Uma((("immutable sequences"))) vez criadas, não podem ter seus itens removidos, trocados ou acrescentados. + Por((("immutable sequences"))) exemplo, `tuple`, `str`, e `bytes`.footnote:[A tupla é imutável mas pode conter itens mutáveis, o que introduz complicações que veremos na <>.] + +A <> ajuda a visualizar como as sequências mutáveis herdam todos os métodos +das sequências imutáveis e implementam vários métodos adicionais. +Os tipos embutidos de sequências na verdade não são subclasses das classes base abstratas (ABCs) +`Sequence` e `MutableSequence`, mas sim _subclasses virtuais_ registradas com aquelas +ABCs—como veremos no «Capítulo 13» [.small]#[vol.2, fpy.li/13]#. +Por serem subclasses virtuais, `tuple` e `list` passam nesses testes: + +[source, python] +---- +>>> from collections import abc +>>> issubclass(tuple, abc.Sequence) +True +>>> issubclass(list, abc.MutableSequence) +True +---- + +[[sequence_uml]] +.Diagrama de classe UML simplificado para algumas classes de collections.abc (as superclasses estão à esquerda; as setas de herança apontam das subclasses para as superclasses; nomes em itálico indicam classes e métodos abstratos). +image::../images/flpy_0202.png[align="center",pdfwidth=11cm] + +Lembre-se((("UML class diagrams", "simplified for collections.abc"))) dessas características básicas: +mutável versus imutável; contêiner versus plana. +Elas ajudam a extrapolar o que se sabe sobre um tipo de sequência para outros tipos. + +O tipo mais fundamental de sequência é a lista: um contêiner mutável. +Espero que você já esteja muito familiarizada com listas, +então vamos passar diretamente para a compreensão de listas, +uma forma potente de criar listas que algumas vezes é subutilizada por sua sintaxe parecer, a princípio, estranha. +Dominar as compreensões de listas abre as portas para expressões geradoras que—entre outros usos—podem produzir +elementos para preencher sequências de qualquer tipo. +Ambas são temas da próxima seção.((("", startref="Soverv02"))) + + +=== Compreensões e genexps + +[TIP] +==== +Compreensões de listas às vezes são chamadas de _listcomps_ +ou __compreensões__ +e expressões geradoras, de _genexps_. +==== + +Um((("sequences", "list comprehensions and generator expressions", id="Slist02")))((("list comprehensions (listcomps)", +"building sequences with"))) +jeito rápido de criar uma sequência é usando uma compreensão de lista (se o alvo é uma `list`) ou +uma expressão geradora (para outros tipos de sequências). +Se você não usa essas formas sintáticas diariamente, +aposto que está perdendo oportunidades de escrever código mais legível e, muitas vezes, mais rápido também. +Se duvida que essas formas são "mais legíveis", continue lendo. +Tentarei te convencer. + + + +==== Compreensões de lista e legibilidade + +Em((("list comprehensions (listcomps)", "readability and"))) sua opinião, +qual destes exemplos é mais fácil de ler, o <> ou o <>? + +[[ex_build_list]] +.Cria uma lista de códigos Unicode a partir de uma string +==== +[source, python] +---- +>>> symbols = '$¢£¥€¤' +>>> codes = [] +>>> for symbol in symbols: +... codes.append(ord(symbol)) +... +>>> codes +[36, 162, 163, 165, 8364, 164] +---- +==== + +[[ex_listcomp0]] +.Mesma operação, usando uma listcomp +==== +[source, python] +---- +>>> symbols = '$¢£¥€¤' +>>> codes = [ord(symbol) for symbol in symbols] +>>> codes +[36, 162, 163, 165, 8364, 164] +---- +==== + +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 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. +Uma listcomp, por outro lado, é mais clara. +Seu objetivo é sempre criar uma nova lista. + +Naturalmente, é possível abusar das compreensões de lista para escrever código praticamente incompreensível. +Já vi código Python usando listcomps apenas para repetir um bloco de código por seus efeitos colaterais. +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 laço `for`. +Use o bom senso: em Python, como em português, não existem regras absolutas para escrever bem. + + +.Dica de sintaxe +[TIP] +==== +Em((("list comprehensions (listcomps)", "syntax tip")))((("\ line continuation escape")))((("backslash (\)")))((("\ (backslash)")))((("line breaks")))((("multiline lists")))((("lists", "multiline")))((("square brackets ([])")))((("[] (square brackets)")))((("{} (curly brackets)")))((("curly brackets ({})"))) +código Python, quebras de linha são ignoradas dentro de pares de `[]`, `{}`, ou `()`. +Então você pode usar múltiplas linhas para criar listas, listcomps, tuplas, dicionários, etc., +sem necessidade de usar o marcador de continuação de linha `\`, +que não funciona se após o `\` você acidentalmente digitar um espaço. +Outro detalhe, quando aqueles pares de delimitadores são usados para definir um literal com uma série de itens +separados por vírgulas, uma vírgula solta no final será ignorada. +Daí, por exemplo, quando se codifica uma lista a partir de um literal com múltiplas linhas, +é uma gentileza deixar uma vírgula após o último item. +Isso torna um pouco mais fácil ao próximo programador acrescentar um item àquela lista, +e reduz o ruído quando se lê os diffs. +==== + +.Escopo local em listcomps e genexps +**** +No((("scope", "within comprehensions and generator expressions")))((("generator expressions (genexps)")))((("list comprehensions (listcomps)", "local scope within"))) +Python 3, compreensões de lista, expressões geradoras, e suas irmãs, as compreensões de `set` e de `dict`, +têm um escopo local para manter as variáveis criadas na condição `for`. +Entretanto, variáveis atribuídas com +o((("assignment expression (:=)")))((("Walrus operator (:=)")))(((":= (Walrus operator)"))) +"operador morsa" (_"Walrus operator"_), `:=`, continuam acessíveis após +aquelas compreensões ou expressões retornarem—diferente +das variáveis locais em uma função. +A «_PEP 572—Assignment Expressions_ (Expressões de atribuição)» [.small]#[fpy.li/pep572]# +define o escopo do alvo de um `:=` +como a função à qual ele pertence, exceto se houver uma declaração `global` ou `nonlocal` para aquele +identificador.footnote:[Agradeço à leitora Tina Lapine por apontar essa informação.] + +[source, python] +---- +>>> x = 'ABC' +>>> codes = [ord(x) for x in x] +>>> x <1> +'ABC' +>>> codes +[65, 66, 67] +>>> codes = [last := ord(c) for c in x] +>>> last <2> +67 +>>> c <3> +Traceback (most recent call last): + File "", line 1, in +NameError: name 'c' is not defined +---- +<1> `x` não foi sobrescrito: continua vinculado a `'ABC'`. +<2> `last` permanece. +<3> `c` desapareceu; ele só existiu dentro da listcomp. + +**** + +<<< +Compreensões de lista criam listas a partir de sequências ou de qualquer outro tipo iterável, +filtrando e transformando os itens. +As funções embutidas `filter` e `map` podem fazer o mesmo, +mas perdemos legibilidade, como veremos a seguir. + +==== Listcomps versus map e filter + +Listcomps((("map function")))((("functions", "map function")))((("filter function")))((("functions", +"filter, map, and reduce functions")))((("list comprehensions (listcomps)", "versus map and filter functions"))) +fazem tudo que as funções `map` e `filter` fazem, +sem os malabarismos exigidos pela funcionalidade limitada do `lambda` de Python. + +Considere o <>. + +[[ex_listcomp_x_filter_map]] +.A mesma lista, criada por uma listcomp e por uma composição de map/filter +==== +[source, python] +---- +>>> symbols = '$¢£¥€¤' +>>> beyond_ascii = [ord(s) for s in symbols if ord(s) > 127] +>>> beyond_ascii +[162, 163, 165, 8364, 164] +>>> beyond_ascii = list(filter(lambda c: c > 127, +... map(ord, symbols))) +>>> beyond_ascii +[162, 163, 165, 8364, 164] +---- +==== + +Eu imaginava que `map` e `filter` fossem mais rápidas que as listcomps equivalentes, mas Alex Martelli +assinalou que não é o caso—pelo menos não nos exemplos acima. +O script «`listcomp_speed.py`» [.small]#[fpy.li/2-1]# no +«repositório de código do _Fluent Python_» [.small]#[fpy.li/code]# +é um teste de desempenho simples, comparando listcomp com `filter/map`. + +Vou falar mais sobre `map` e `filter` no <>. +Vamos agora ver o uso de listcomps para computar produtos cartesianos: +uma lista contendo tuplas criadas a partir de todos os itens de duas ou mais listas. + +[[cartesian_product_sec]] +==== Produtos cartesianos + +Listcomps((("list comprehensions (listcomps)", "building lists from cartesian products")))((("Cartesian products", +id="cartprod02"))) +podem criar listas a partir do produto cartesiano de dois ou mais iteráveis. +Os itens resultantes de um produto cartesiano são tuplas criadas com os itens de cada iterável na entrada, e +a lista resultante tem o tamanho igual ao produto dos tamanhos dos iteráveis usados. +Veja a <>. + +[[cartesian_product_fig]] +.O produto cartesiano de 3 valores de cartas e 4 naipes é uma sequência de 12 itens. +image::../images/flpy_0203.png[align="center",pdfwidth=9cm] + +Por exemplo, imagine que você precisa produzir uma lista de camisetas disponíveis em duas cores e três tamanhos. +O <> mostra como produzir tal lista usando uma listcomp. +O resultado tem seis itens. + +[[ex_listcomp_cartesian]] +.Produto cartesiano usando uma compreensão de lista +==== +[source, python] +---- +>>> colors = ['black', 'white'] +>>> sizes = ['S', 'M', 'L'] +>>> tshirts = [(color, size) for color in colors +... for size in sizes] <1> +>>> tshirts +[('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), + ('white', 'M'), ('white', 'L')] +>>> for color in colors: <2> +... for size in sizes: +... print((color, size)) +... +('black', 'S') +('black', 'M') +('black', 'L') +('white', 'S') +('white', 'M') +('white', 'L') +>>> tshirts = [(color, size) for size in sizes <3> +... for color in colors] +>>> tshirts +[('black', 'S'), ('white', 'S'), ('black', 'M'), ('white', 'M'), + ('black', 'L'), ('white', 'L')] +---- +==== +<1> Isso gera uma lista de tuplas ordenadas por cor, depois por tamanho. +<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 <> 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: + +[source, python] +---- + self._cards = [Card(rank, suit) for suit in self.suits + for rank in self.ranks] +---- + +Listcomps fazem só uma coisa: criam listas. +Para gerar dados para outros tipos de sequências, uma genexp é o caminho. +A próxima seção é uma pequena incursão às genexps, no contexto de criação de sequências que não são +listas.((("", startref="cartprod02"))) + +==== Expressões geradoras + +Para((("generator expressions (genexps)")))((("list comprehensions (listcomps)", "versus generator expressions", +secondary-sortas="generator expressions"))) +inicializar tuplas, arrays e outros tipos de sequências, você também pode usar uma listcomp, +mas uma genexp (expressão geradora) economiza memória, +pois ela produz itens um de cada vez usando o protocolo iterador, +em vez de criar uma lista inteira apenas para alimentar outro construtor. +As genexps usam a mesma sintaxe das listcomps, mas são delimitadas por parênteses em vez de colchetes. + +O <> demonstra o uso básico de genexps para criar uma tupla e um array. + +[[ex_genexp_load]] +.Inicializando uma tupla e um array a partir de uma expressão geradora +==== +[source, python] +---- +>>> symbols = '$¢£¥€¤' +>>> tuple(ord(symbol) for symbol in symbols) <1> +(36, 162, 163, 165, 8364, 164) +>>> import array +>>> array.array('I', (ord(symbol) for symbol in symbols)) <2> +array('I', [36, 162, 163, 165, 8364, 164]) +---- +==== +<1> Se a expressão geradora é o único argumento em uma chamada de função, +não há necessidade de duplicar os parênteses circundantes. +<2> O construtor de `array` espera dois argumentos, +então os parênteses em torno da expressão geradora são obrigatórios. +O primeiro argumento do construtor de `array` define o tipo de armazenamento usado para os números no array, +como veremos na <>. + +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 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 laço `for`. + +[[ex_genexp_cartesian]] +.Produto cartesiano em uma expressão geradora +==== +[source, python] +---- +>>> colors = ['black', 'white'] +>>> sizes = ['S', 'M', 'L'] +>>> for tshirt in (f'{c} {s}' for c in colors for s in sizes): <1> +... print(tshirt) +... +black S +black M +black L +white S +white M +white L +---- +==== +<1> A expressão geradora produz um item por vez; uma lista com todas as seis variações de camisetas +nunca é criada neste exemplo. + +[NOTE] +==== +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. +==== + +Vamos agora estudar outra sequência fundamental de Python: a tupla. + +[[tuples_more_than_lists_sec]] +=== Tuplas são mais que listas imutáveis + +Alguns((("sequences", "tuples", id="Stup02"))) textos introdutórios de Python apresentam as tuplas como +"listas imutáveis", mas isso é subestimá-las. +Tuplas têm dois usos: como listas imutáveis ou como registros com campos sem nome. +Esse uso algumas vezes é negligenciado, então vamos começar por ele.((("", startref="Slist02"))) + + +==== Tuplas como registros + +Tuplas podem conter registros: +cada((("tuples", "as records", secondary-sortas="records", id="tuple02"))) +item na tupla contém os dados de um campo, e a posição do item indica seu significado. +Se você pensar em uma tupla apenas como uma lista imutável, +a quantidade e a ordem dos elementos podem ser importantes ou não, dependendo do contexto. +Mas quando usamos uma tupla como uma coleção de campos, +a quantidade itens em geral é fixa, e sua ordem é sempre importante. + +O <> mostra tuplas usadas como registros. +Observe que, em todas as expressões, ordenar a tupla destruiria a informação, +pois o significado de cada campo é dado por sua posição na tupla. + +[[ex_tuples_as_records]] +.Tuplas usadas como registros +==== +[source, python] +---- +>>> lax_coordinates = (33.9425, -118.408056) <1> +>>> city, year, pop, chg, area = ( +... 'Tokyo', 2003, 32_450, 0.66, 8014) <2> +>>> traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), <3> +... ('ESP', 'XDA205856')] +>>> for passport in sorted(traveler_ids): <4> +... print('%s/%s' % passport) <5> +... +BRA/CE342567 +ESP/XDA205856 +USA/31195855 +>>> for country, _ in traveler_ids: <6> +... print(country) +... +USA +BRA +ESP +---- +==== +<1> Latitude e longitude do Aeroporto Internacional de Los Angeles. +<2> Dados sobre Tóquio: nome, ano, população (em milhares), crescimento populacional (%) e área (km²). +<3> Uma lista de tuplas no formato +(código_de_país, número_do_passaporte)+. +<4> Iterando sobre a lista, `passport` é vinculado a cada tupla. +<5> O operador de formatação `%` entende as tuplas e trata cada item como um campo separado. +<6> A instrução `for` sabe como recuperar separadamente os itens de uma tupla—isso é chamado "desempacotamento" (_unpacking_). +Aqui não estamos interessados no segundo item, então o atribuímos a `_`, +uma variável descartável, apenas para coletar valores que não usaremos. + +[TIP] +==== +Em geral, usar `+_+` como variável descartável (_dummy variable_) é só uma convenção. +É um identificador estranho, mas válido. +Entretanto, em uma instrução `match/case`, o `+_+` é um coringa que corresponde a qualquer valor. +Veja a <>. +No console de Python, o resultado da instrução anterior é atribuído a `+_+`, exceto quando o valor é `None`. +==== + +É comum pensar em registros como estruturas de dados com campos nomeados. +O <> mostra como criar tuplas com campos nomeados. + +Mas nem sempre vale a pena criar uma classe apenas para nomear os campos, +especialmente se você aproveitar o desempacotamento e evitar o uso de índices para acessar os campos. +No <>, atribuímos +`('Tokyo', 2003, 32_450, 0.66, 8014)` a `city, year, pop, chg, area` em uma única instrução. +E o operador `%` colocou cada item da tupla `passport` em uma posição +da string de formato`. +Esses foram dois exemplos de _desempacotamento de tuplas_. + + +[NOTE] +==== +O((("tuples", "tuple unpacking"))) termo "desempacotamento de tuplas" (_tuple unpacking_) +é muito usado entre os pythonistas, mas _desempacotamento de iteráveis_ é mais preciso +e está ganhando popularidade, como no título da +«_PEP 3132—Extended Iterable Unpacking_ (Desempacotamento de iteráveis estendido)» [.small]#[fpy.li/2-2]#. +A <> fala mais sobre desempacotamento, +não apenas de tuplas, mas também de sequências e iteráveis em geral. +==== + +Agora vamos considerar o uso da classe `tuple` como uma variante imutável da classe +`list`.((("", startref="tuple02"))) + +[[tuple_as_immutable_list_sec]] +==== Tuplas como listas imutáveis + +O((("tuples", "as immutable lists", secondary-sortas="immutable lists", id="Timlist02")))((("lists", "using tuples as immutable", +id="Ltupple02"))) +interpretador Python e a biblioteca padrão fazem uso extensivo das tuplas como listas imutáveis, +e você deve seguir o exemplo. +Isso traz dois benefícios importantes: + +Clareza:: Quando você vê uma `tuple` no código, sabe que seu tamanho nunca mudará. +Desempenho:: Uma `tuple` usa menos memória que uma `list` de mesmo tamanho, e permite ao Python realizar algumas otimizações. + +Entretanto, lembre-se de que a imutabilidade de uma `tuple` só se aplica às referências ali contidas. +Referências em uma tupla não podem ser apagadas ou substituídas. +Mas se uma daquelas referências apontar para um objeto mutável, e aquele objeto mudar, então o valor da `tuple` muda. +O próximo trecho de código ilustra esse fato criando duas tuplas—`a` e `b`— que inicialmente são iguais. + +Quando o último item em `b` muda, `a` e `b` se tornam diferentes: + +[source, python] +---- +>>> a = (10, 'alpha', [1, 2]) +>>> b = (10, 'alpha', [1, 2]) +>>> a == b +True +>>> b[-1].append(99) +>>> a == b +False +>>> b +(10, 'alpha', [1, 2, 99]) +---- + +A <> representa a disposição inicial da tupla `b` na memória. + +[[tuple_mutable]] +.O conteúdo em si da tupla é imutável, mas isso significa apenas que as referências mantidas pela tupla vão sempre apontar para os mesmos objetos. Entretanto, se um dos objetos referenciados for mutável—uma lista, por exemplo—seu conteúdo pode mudar. +image::../images/flpy_0204.png[align="center",pdfwidth=9cm] + +Tuplas com itens mutáveis podem ser uma fonte de bugs. +Se uma tupla contém qualquer item mutável, +ela não pode ser usada como chave em um `dict` ou como elemento em um `set`. +O motivo será explicado na <>. + +Se você quiser determinar explicitamente se uma tupla (ou qualquer outro objeto) tem um valor fixo, +pode usar a função embutida `hash` para criar uma função `fixed`, assim: + +[source, python] +---- +>>> def fixed(o): +... try: +... hash(o) +... except TypeError: +... return False +... return True +... +>>> tf = (10, 'alpha', (1, 2)) +>>> tm = (10, 'alpha', [1, 2]) +>>> fixed(tf) +True +>>> fixed(tm) +False +---- + +Vamos aprofundar essa questão na <>. + +<<< +Apesar desta ressalva, as tuplas são frequentemente usadas como listas imutáveis. +Elas oferecem algumas vantagens de desempenho, explicadas por um dos mantenedores de Python, +Raymond Hettinger, em uma resposta à questão +«_Are tuples more efficient than lists in Python?_» [.small]#[fpy.li/2-3]# +(As tuplas são mais eficientes que as listas no Python?) no StackOverflow. +Em resumo, Hettinger escreveu: + +* Para avaliar uma tupla literal como `(1, 2, 3)`, o compilador Python gera bytecode para uma constante tupla em uma operação; +mas para um literal lista, `[1, 2, 3]`, o bytecode gerado insere cada elemento como uma constante separada na pilha, +e então cria a lista. +* Dada a tupla `t`, `tuple(t)` simplesmente devolve uma referência para a mesma `t`. Não há necessidade de cópia. +Por outro lado, dada uma lista `l`, o construtor `list(l)` precisa criar uma nova cópia de `l`. +* Devido ao seu tamanho fixo, uma instância de `tuple` ocupa somente a memória que precisa. +Em contrapartida, instâncias de `list` reservam memória adicional, para amortizar o custo de acréscimos futuros. +* As referências para os itens em uma tupla são armazenadas em um array na struct da tupla, +enquanto uma lista mantém um ponteiro para um array de referências armazenado em outro lugar. +Essa indireção é necessária porque, quando a lista cresce além do espaço alocado naquele momento, +Python precisa realocar o array de referências para criar espaço. +A indireção adicional torna o cache da CPU menos eficiente.((("", startref="Timlist02")))((("", startref="Ltupple02"))) + +==== Comparando os métodos de tuplas e listas + +Quando((("tuples", "versus lists", secondary-sortas="lists")))((("lists", "versus tuples", secondary-sortas="tuples"))) +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 tem o método `+__reversed__+`. +Entretanto, `reversed(my_tuple)` funciona sem esse método; ele serve apenas para otimizar. + +<<< +[[list_x_tuple_attrs_tbl]] +.Métodos e atributos encontrados em `list` ou `tuple` (os métodos implementados por `object` foram omitidos para economizar espaço) +[options="header", cols="9,^3,^3,18"] +|================================================================================================================================================ +| | list | tuple |   +| `+s.__add__(s2)+` | ● | ● | `s + s2`—concatenação +| `+s.__iadd__(s2)+` | ● | | `+s += s2+`—concatenação interna +| `s.append(e)` | ● | | Acrescenta um elemento após o último +| `s.clear()` | ● | | Apaga todos os itens +| `+s.__contains__(e)+` | ● | ● | `e in s` +| `s.copy()` | ● | | Cópia rasa da lista +| `s.count(e)` | ● | ● | Conta as ocorrências de um elemento +| `+s.__delitem__(p)+` | ● | | Remove o item na posição `p` +| `s.extend(it)` | ● | | Acrescenta itens do iterável `it` +| `+s.__getitem__(p)+` | ● | ● | `+s[p]+`—obtém o item na posição `p` +| `+s.__getnewargs__()+` | | ● | Suporte à serialização otimizada com `pickle` +| `s.index(e)` | ● | ● | Posição da primeira ocorrência de `e` +| `s.insert(p, e)` | ● | | Insere `e` antes do item na posição `p` +| `+s.__iter__()+` | ● | ● | Obtém um iterador +| `+s.__len__()+` | ● | ● | `+len(s)+`—quantidade de itens +| `+s.__mul__(n)+` | ● | ● | `+s * n+`—concatenação repetida +| `+s.__imul__(n)+` | ● | | `+s *= n+`—concatenação repetida interna +| `+s.__rmul__(n)+` | ● | ● | `+n * s+`—concatenação repetida reversafootnote:[Operadores reversos são explicados no «Capítulo 16» [.small]#[vol.2, fpy.li/16].]#] +| `s.pop([p])` | ● | | Remove e devolve o último item ou o item na posição opcional `p` +| `s.remove(e)` | ● | | Remove o primeiro item de valor igual a `e` +| `s.reverse()` | ● | | Inverte a ordem dos itens internamente +| `+s.__reversed__()+` | ● | | Obtém iterador para percorrer itens do último para o primeiro +|`+s.__setitem__(p, e)+` | ● | ● | `++s[p] = e++` (sobrescreve a posição ou fatia `p`) +|`s.sort([key], [reverse])` | ● | | Ordena os itens internamente, com os argumentos nomeados opcionais `key` e `reverse` +|================================================================================================================================================ + +Vamos agora examinar um tópico importante para a programação Python idiomática: +tuplas, listas e desempacotamento iterável.((("", startref="Stup02"))) + + +[[iterable_unpacking_sec]] +=== Desempacotando sequências e iteráveis + +O desempacotamento((("sequences", "unpacking sequences and iterables", id="Sunpack02")))((("iterables", "unpacking", +id="iterun02")))((("unpacking", "sequences and iterables"))) +é importante porque evita o uso de índices para acessar itens de sequências, +o que causa muitos bugs. +Além disso, o desempacotamento funciona tendo qualquer objeto iterável como fonte de dados—incluindo iteradores, +que não suportam((("square brackets ([])")))((("[] (square brackets)"))) a notação de índice (`[]`). +O único requisito é que o iterável produza exatamente um item por variável do lado esquerdo da atribuição, +a menos que você use um asterisco (`*`) para capturar os itens em excesso, como explicado na <>. + +A forma mais visível de desempacotamento é a _atribuição paralela_; +isto é, atribuir itens de um iterável a uma tupla de variáveis, como vemos nesse exemplo: + +[source, python] +---- +>>> lax_coordinates = (33.9425, -118.408056) +>>> latitude, longitude = lax_coordinates # unpacking +>>> latitude +33.9425 +>>> longitude +-118.408056 +---- + +Uma aplicação elegante de desempacotamento é permutar os valores de variáveis sem usar uma variável temporária: + +[source, python] +---- +>>> b, a = a, b +---- + +Outro exemplo de desempacotamento é prefixar um argumento com `*` ao chamar uma função: + +[source, python] +---- +>>> divmod(20, 8) +(2, 4) +>>> t = (20, 8) +>>> divmod(*t) +(2, 4) +>>> quotient, remainder = divmod(*t) +>>> quotient, remainder +(2, 4) +---- + +O código acima mostra outro uso do desempacotamento: +permitir que funções devolvam múltiplos valores de forma conveniente para quem as chama. +Em ainda outro exemplo, a função `os.path.split()` cria uma tupla `(path, last_part)` +a partir de um caminho do sistema de arquivos: + +[source, python] +---- +>>> import os +>>> _, filename = os.path.split('/home/luciano/.ssh/id_rsa.pub') +>>> filename +'id_rsa.pub' +---- + +Outra forma de usar apenas alguns itens quando desempacotando é com a sintaxe `*`, que veremos a seguir. + +[[tuple_star]] +==== Usando * para recolher itens em excesso + +Definir((("unpacking", "using * to grab excess items")))((("star (*) operator", +id="star02")))((("* (star) operator", id="star02a"))) +parâmetros de função com `*args` para capturar argumentos +em excesso é um recurso clássico de Python. +No Python 3, essa ideia foi estendida para se aplicar também à atribuição paralela: + +[source, python] +---- +>>> a, b, *rest = range(5) +>>> a, b, rest +(0, 1, [2, 3, 4]) +>>> a, b, *rest = range(3) +>>> a, b, rest +(0, 1, [2]) +>>> a, b, *rest = range(2) +>>> a, b, rest +(0, 1, []) +---- + +No contexto da atribuição paralela, o prefixo `*` pode ser aplicado a exatamente uma variável, +mas pode aparecer em qualquer posição: + +[source, python] +---- +>>> a, *body, c, d = range(5) +>>> a, body, c, d +(0, [1, 2], 3, 4) +>>> *head, b, c, d = range(5) +>>> head, b, c, d +([0, 1], 2, 3, 4) +---- + + +==== Uso de * em chamadas de função e sequências literais + +A «_PEP 448—Additional Unpacking Generalizations_ (Generalizações de desempacotamento adicionais)» [.small]#[fpy.li/pep448]# +introduziu((("unpacking", "with * in function calls and sequence literals"))) +uma sintaxe mais flexível para desempacotamento de iterável, melhor resumida em +«O que há de novo no Python 3.5» [.small]#[fpy.li/2q]#. +Em chamadas de função, podemos usar `*` múltiplas vezes: + +[source, python] +---- +>>> def fun(a, b, c, d, *rest): +... return a, b, c, d, rest +... +>>> fun(*[1, 2], 3, *range(4, 7)) +(1, 2, 3, 4, (5, 6)) +---- + +O `*` pode também ser usado na definição de literais `list`, `tuple`, ou `set`, como +visto nesses exemplos de +«O que há de novo no Python 3.5» [.small]#[fpy.li/2q]#: + + +[source, python] +---- +>>> *range(4), 4 +(0, 1, 2, 3, 4) +>>> [*range(4), 4] +[0, 1, 2, 3, 4] +>>> {*range(4), 4, *(5, 6, 7)} +{0, 1, 2, 3, 4, 5, 6, 7} +---- + +A PEP 448 introduziu uma nova sintaxe similar para `**`, que veremos na <>. +Outro importante aspecto do desempacotamento de tuplas: +funciona com estruturas aninhadas.((("", startref="star02")))((("", startref="star02a"))) + + +==== Desempacotamento aninhado + +O((("unpacking", "nested"))) alvo de um desempacotamento pode usar aninhamento, +por exemplo `(a, b, (c, d))`. +Python fará a coisa certa se o valor tiver a mesma estrutura aninhada. +O <> mostra o desempacotamento aninhado em ação. +[[ex_nested_tuple]] +.Desempacotando tuplas aninhadas para acessar a longitude +==== +[source, python] +---- +include::../code/02-array-seq/metro_lat_lon.py[tags=MAIN] +---- +==== +<1> Cada tupla tem quatro campos; o último é um par de coordenadas. +<2> Ao atribuir o último campo a uma tupla aninhada, desempacotamos as coordenadas. +<3> O teste `lon \<= 0:` seleciona apenas cidades no hemisfério ocidental. + + +A saída do <> é: + +[source, text] +---- + | latitude | longitude +Mexico City | 19.4333 | -99.1333 +New York-Newark | 40.8086 | -74.0204 +São Paulo | -23.5478 | -46.6358 +---- + +O alvo da atribuição de um desempacotamento pode também ser uma lista, mas bons casos de uso aqui são raros. +Aqui está o único que conheço: se você tem uma consulta de banco de dados que devolve um único registro +(por exemplo, se o código SQL tem a instrução `LIMIT 1`), +daí é possível desempacotar e ao mesmo tempo se assegurar que há apenas um resultado com o seguinte código: + +[source, python] +---- +>>> [record] = query_returning_single_row() +---- + +Se o registro contiver apenas um campo, é possível obtê-lo diretamente, assim: + +[source, python] +---- +>>> [[field]] = query_returning_single_row_with_single_field() +---- + +Os dois exemplos acima podem ser escritos com tuplas, mas não esqueça da peculiaridade sintática: +tuplas com um único item devem ser escritas com uma vírgula final. +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 casamento de padrões, +que suporta maneiras ainda mais poderosas para desempacotar +sequências.((("", startref="Sunpack02")))((("", startref="iterun02"))) + + +[[sequence_patterns_sec]] +=== 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 +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 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 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, +dependendo dos tipos de padrão: +na <> e na <>. +E há um exemplo mais longo na «Seção 18.3» [.small]#[vol.3, fpy.li/6a]#. +==== + +Vamos ao primeiro exemplo do tratamento de sequências com `match/case`. + +Imagine que você está construindo um robô que aceita comandos, +enviados como sequências de palavras e números, como `BEEPER 440 3`. +Após separar o comando em partes e analisar os números, você teria uma mensagem como `['BEEPER', 440, 3]`. +Então, você poderia usar um método assim para interpretar mensagens naquele formato: + +[[ex_robot]] +.Método de uma classe `Robot` imaginária +==== +[source, python] +---- + def handle_command(self, message): + match message: # <1> + case ['BEEPER', frequency, times]: # <2> + self.beep(times, frequency) + case ['NECK', angle]: # <3> + self.rotate_neck(angle) + case ['LED', pin, intensity]: # <4> + self.leds[ident].set_brightness(pin, intensity) + case ['LED', pin, red, green, blue]: # <5> + self.leds[ident].set_color(pin, red, green, blue) + case _: # <6> + raise InvalidCommand(message) +---- +==== +<1> A expressão após a palavra-chave `match` é o sujeito (_subject_). +O sujeito contém os dados que Python vai comparar aos padrões em cada instrução `case`. +<2> Este padrão casa se o sujeito é uma sequência de três itens, +e o primeiro item é a string `'BEEPER'`. +O segundo e o terceiro itens podem ser qualquer coisa, e serão vinculados às variáveis `frequency` e `times`, nessa ordem. +<3> Isso casa com qualquer sujeito com dois itens, se o primeiro for `'NECK'`. +<4> Isso vai casar com um sujeito de três itens começando com `LED`. +Se o número de itens não for correspondente, Python segue para o próximo `case`. +<5> Outro padrão de sequência começando com `'LED'`, agora com cinco itens—incluindo a constante `'LED'`. +<6> Este é o `case` default. +Vai casar com qualquer sujeito que não tenha sido capturado por um dos padrões precedentes. +A variável `_` é especial, como logo veremos. + +Olhando((("pattern matching", "destructuring"))) superficialmente, +`match/case` se parece com a instrução `switch/case` da linguagem C—mas isso é só uma pequena parte da sua +funcionalidade.footnote:[Na minha opinião, uma sucessão `if/elif/elif/.../else` funciona muito bem no lugar de `switch/case`. +E ela não sofre dos problemas de «_fallthrough_ (cascateamento)» [.small]#[fpy.li/2-8]# e de +«_dangling else_ (_o `else` pendurado_)» [.small]#[fpy.li/2-9]#, +que alguns projetistas de linguagens copiaram irracionalmente do C—décadas +após sabermos que tais problemas causam inúmeros bugs.] +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 casamento de padrões—como Scala e Elixir. + +Como um primeiro exemplo de desestruturação, o <> +mostra parte do <> reescrito com `match/case`. + +[[ex_nested_tuple_match]] +.Desestruturando tuplas aninhadas—requer Python ≥ 3.10 +==== +[source, python] +---- +include::../code/02-array-seq/match_lat_lon.py[tags=MAIN] +---- +==== +<1> O sujeito desse `match` é `record`—isto é, cada uma das tuplas em `metro_areas`. +<2> Uma instrução `case` tem duas partes: um padrão e uma guarda opcional, com a palavra-chave `if`. + +Em geral, um padrão de sequência casa com o sujeito se estas três condições forem verdadeiras: + +. O sujeito é uma sequência, _e_ +. O sujeito e o padrão têm o mesmo número de itens, _e_ +. Todos os itens correspondentes casam, incluindo os itens aninhados. + +Por exemplo, o padrão `[name, _, _, (lat, lon)]` no <> +casa com uma sequência de quatro itens, e o último item precisa ser uma sequência de dois itens. + +Padrões de sequência((("pattern matching", "tuples and lists"))) +podem ser escritos como tuplas e listas, mas a sintaxe usada não faz diferença: +em um padrão de sequência, colchetes e parênteses têm o mesmo significado. +Escrevi o padrão como uma lista com uma tupla aninhada de dois itens para evitar a +repetição de colchetes ou parênteses no <>. + +Um padrão de sequência pode casar com instâncias da maioria das subclasses reais ou virtuais de `collections.abc.Sequence`, +com a exceção de `str`, `bytes`, e `bytearray`. + +[WARNING] +==== +Instâncias de `str`, `bytes`, e `bytearray` não são tratadas como sequências no contexto de um `match/case`. +Um sujeito de `match` de um desses tipos é tratado como um valor "atômico"—assim como o inteiro 987 é tratado como um único valor, +e não como uma sequência de dígitos. +Tratar aqueles três tipos como sequências poderia causar bugs devido a casamentos não intencionais. +Se você quer usar um objeto daqueles tipos como um sujeito sequência, converta-o na instrução `match`. +Por exemplo, veja `tuple(phone)` no trecho abaixo, +que poderia ser usado para separar números de telefone por regiões do mundo com base no prefixo DDI: + +[source, python] +---- +match tuple(phone): + case ['1', *rest]: # US, Canada, Caribbean + ... + case ['2', *rest]: # Africa, some territories + ... + case ['3' | '4', *rest]: # Europe + ... +---- + +==== + +Na biblioteca padrão, os seguintes tipos são compatíveis com padrões de sequência: + +[source] +---- +list memoryview array.array +tuple range collections.deque +---- + +Ao contrário do desempacotamento, +padrões não desestruturam iteráveis que não sejam sequências (tal como os iteradores). + +O((("pattern matching", "_ symbol")))((("_ symbol"))) símbolo `+_+` é especial nos padrões: +ele casa com qualquer item naquela posição, mas nunca é vinculado ao valor daquele item. +O valor é descartado. +Além disso, o `+_+` é a única variável que pode aparecer mais de uma vez em um padrão. + +Você pode vincular qualquer parte de um padrão a uma variável usando +a((("keywords", "as keyword")))((("as keyword"))) palavra-chave `as`: + +[source, python] +---- + case [name, _, _, (lat, lon) as coord]: +---- + +Dado o sujeito `['Shanghai', 'CN', 24.9, (31.1, 121.3)]`, +o padrão anterior vai casar e atribuir valores às seguintes variáveis: + +// .Pattern matching +[options="header",width=50%, cols=">1,1"] +|=== +| Variável | Valor atribuído +| `name` | `'Shanghai'` +| `lat` | `31.1` +| `lon` | `121.3` +| `coord` | `(31.1, 121.3)` +|=== + +Para((("pattern matching", "type information"))) tornar os padrões mais específicos, +podemos incluir informação de tipo. +Por exemplo, o seguinte padrão casa com a mesma estrutura de sequência aninhada do exemplo anterior, +mas o primeiro item deve ser uma instância de `str`, +e os dois itens da tupla devem ser instâncias de `float`: + +[source, python] +---- + case [str(name), _, _, (float(lat), float(lon))]: +---- + +// but in the context of a pattern, that syntax works as a runtime type check: +[TIP] +==== +As expressões `str(name)` e `float(lat)` se parecem com chamadas a construtores, +que usaríamos para converter `name` e `lat` para `str` e `float`. +Mas no contexto de um padrão, aquela sintaxe faz uma checagem de tipos durante a execução do programa: +o padrão acima vai casar com uma sequência de quatro itens, +na qual o item 0 deve ser uma `str` e o item 3 deve ser um par de números de ponto flutuante. +Além disso, a `str` no item 0 será vinculada à variável `name` +e os números no item 3 serão vinculados a `lat` e `lon`, respectivamente. +Assim, apesar de imitar a sintaxe de uma chamada de construtor, +o significado de `str(name)` é totalmente diferente no contexto de um padrão. +O uso de classes arbitrárias em padrões será tratado na <>. +==== + +Por outro lado, se queremos casar qualquer sujeito sequência começando com uma `str` e +terminando com uma sequência aninhada com dois números de ponto flutuante, podemos escrever: + +[source, python] +---- + case [str(name), *_, (float(lat), float(lon))]: +---- + +O((("pattern matching", "*_ symbol")))((("*_ symbol"))) `+*_+` +casa com qualquer número de itens, sem vinculá-los a uma variável. +Usar `*extra` em vez de `+*_+` vincularia os itens a `extra` como uma `list` com 0 ou mais itens. + +A instrução de guarda opcional começando com `if` só é avaliada se o padrão casar, +e pode se referir a variáveis vinculadas no padrão, como no <>: + +[source, python] +---- + match record: + case [name, _, _, (lat, lon)] if lon <= 0: + print(f'{name:15} | {lat:9.4f} | {lon:9.4f}') +---- + +O bloco aninhado com a instrução `print` só será executado se o padrão casar e a expressão guarda for _verdadeira_. + +[TIP] +==== +A desestruturação com padrões é tão expressiva que, algumas vezes, +um `match` com um único `case` pode tornar o código mais simples. +Guido van Rossum tem uma coleção de exemplos de `case/match`, +incluindo um que ele chamou de +«_A very deep iterable and type match with extraction_ +(Um match de iterável e tipo muito profundo, com extração)» [.small]#[fpy.li/2-10]#. +==== + +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 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 + +Peter Norvig((("lis.py interpreter", "pattern matching in", id="lispypattern02")))((("pattern matching", +"in lis.py interpreter", +secondary-sortas="lis.py", id="PMinterp02")))((("Scheme language", id="scheme02"))), +da Universidade de Stanford, escreveu o +«`lis.py`» [.small]#[fpy.li/2-11]#: +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 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`. + +As duas funções principais do _lis.py_ são `parse` e `evaluate`.footnote:[A última é +chamada `eval` no código original; +a renomeei para evitar confusão com a função embutida `eval` de Python.] +O parser (_analisador sintático_) recebe as expressões entre parênteses do Scheme +e devolve listas Python. +Aqui estão dois exemplos: + +[source, python] +---- +>>> parse('(gcd 18 45)') +['gcd', 18, 45] +>>> parse(''' +... (define double +... (lambda (n) +... (* n 2))) +... ''') +['define', 'double', ['lambda', ['n'], ['*', 'n', 2]]] +---- + +O avaliador recebe listas como essas e as executa. +O primeiro exemplo está chamando uma função `gcd` com `18` e `45` como argumentos. +Quando executada, ela computa o maior divisor comum (`gcd` são as iniciais do termo em inglês +_greatest common divisor_) dos argumentos (que é 9). +O segundo exemplo está definindo uma função chamada `double` com um parâmetro `n`. +O corpo da função é a expressão `(* n 2)`. +O resultado da chamada a uma função em Scheme é o valor da última expressão no corpo da função chamada. + +Nosso foco aqui é a desestruturação de sequências, então não vou explicar as ações do avaliador. +Veja a «Seção 18.3» [.small]#[vol.3, fpy.li/6a]# para aprender mais sobre o funcionamento do _lis.py_. + +O <> mostra o avaliador de Norvig com algumas pequenas modificações, +abreviado para mostrar apenas os padrões de sequência. + +<<< +[[ex_norvigs_eval]] +.Casando padrões sem `match/case` +==== +[source, python] +---- +include::../code/02-array-seq/lispy/py3.9/lis.py[tags=EVAL_IF_TOP] + # ... lines omitted +include::../code/02-array-seq/lispy/py3.9/lis.py[tags=EVAL_IF_MIDDLE] + # ... more lines omitted +---- +==== + +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 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 <>. + +<<< +[[ex_match_eval]] +.Pattern matching com `match/case`—requer Python ≥ 3.10 +==== +[source, python] +---- +include::../code/02-array-seq/lispy/py3.10/lis.py[tags=EVAL_MATCH_TOP] + # ... lines omitted +include::../code/02-array-seq/lispy/py3.10/lis.py[tags=EVAL_MATCH_MIDDLE] + # ... more lines omitted +include::../code/02-array-seq/lispy/py3.10/lis.py[tags=EVAL_MATCH_BOTTOM] +---- +==== +<1> Casa se o sujeito for uma sequência de dois itens começando com `'quote'`. +<2> Casa se o sujeito for uma sequência de quatro itens começando com `'if'`. +<3> Casa se o sujeito for uma sequência com três ou mais itens começando com `'lambda'`. A guarda assegura que `body` não esteja vazio. +<4> Casa se o sujeito for uma sequência de três itens começando com `'define'`, seguido de uma instância de `Symbol`. +<5> É uma boa prática ter um `case` para capturar qualquer outro sujeito. +Neste exemplo, se `exp` não casar com nenhum dos padrões, a expressão está mal-formada, então gera um `SyntaxError`. + +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 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. +O <> é mais curto e mais seguro que o <>. + +===== Padrões alternativos para lambda + +Essa é a sintaxe de `lambda` no Scheme, +usando a convenção sintática onde +o sufixo `…` significa que o elemento pode aparecer zero ou mais vezes: + +[source, lisp] +---- +(lambda (parms…) body1 body2…) +---- + +Um padrão simples para o `case` de `'lambda'` seria esse: + +[source, python] +---- + case ['lambda', parms, *body] if body: +---- + +Entretanto, isso casa com qualquer valor na posição `parms`, +incluindo o primeiro `x` nesse sujeito inválido: + +[source, python] +---- +['lambda', 'x', ['*', 'x', 2]] +---- + +A lista aninhada após a palavra-chave `lambda` do Scheme +contém os nomes dos parâmetros formais da função, +e deve ser uma lista mesmo que contenha apenas um elemento. +Ela pode também ser uma lista vazia, +se a função não tiver parâmetros—como `random.random()` de Python. + +No <>, tornei o padrão de `'lambda'` mais seguro usando um padrão de sequência aninhado: + +[source, python] +---- + case ['lambda', [*parms], *body] if body: + return Procedure(parms, body, env) +---- + +Em um padrão de sequência, o `*` pode aparecer apenas uma vez por sequência. +Aqui temos duas sequências: a externa e a interna. +Colocando colchetes em `[*parms]`, este padrão ficou mais parecido +com a sintaxe do Scheme que ele trata, +e fornece mais uma verificação estrutural. + +===== Sintaxe abreviada para definição de função + +O Scheme tem uma sintaxe alternativa de `define`, para criar uma função nomeada sem usar um `lambda` aninhado. +Tal sintaxe funciona assim: + +[source, lisp] +---- +(define (name parm…) body1 body2…) +---- + +A palavra-chave `define` é seguida por uma lista com o `name` da nova função e zero ou mais nomes de parâmetros. +Após a lista vem o corpo da função, com uma ou mais expressões. + +Acrescentar essas duas linhas ao `match` cuida da implementação: + +[source, python] +---- + case ['define', [Symbol() as name, *parms], *body] if body: + env[name] = Procedure(parms, body, env) +---- + +Eu colocaria esse `case` após o `case` da outra forma de `define` no <>. +A ordem desses _cases_ de `define` é irrelevante nesse exemplo, +pois nenhum sujeito pode casar com esses dois padrões: +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 casamento de padrões no <>. +A instrução `match` faz mais que o `switch` das linguagens similares ao C. + +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 <>. + +<<< +[[syntax_and_pattern_tbl]] +.Algumas formas sintáticas do Scheme e os padrões de `case` para tratá-las +[options="header"] +|============================================================================ +| Sintaxe do Scheme | Padrão de sequência +| `(quote exp)` | `['quote', exp]` +| `(if test conseq alt)` | `['if', test, conseq, alt]` +| `(lambda (parms…) body1 body2…)` | `['lambda', [*parms], *body] if body` +| `(define name exp)` | `['define', Symbol() as name, exp]` +| `(define (name parms…) body1 body2…)` | `['define', [Symbol() as name, *parms], *body] if body` +|============================================================================ + +Espero que a refatoração do `evaluate` de Norvig com casamento de padrões +tenha convencido você de que `match/case` pode tornar seu código mais legível e mais seguro. + +[NOTE] +==== +Veremos mais do _lis.py_ na «Seção 18.3» [.small]#[vol.3, fpy.li/6a]#, +quando vamos revisar o exemplo completo de `match/case` em `evaluate`. +Se você quiser aprender mais sobre o _lys.py_ de Norvig, leia seu maravilhoso texto +«_(How to Write a (Lisp) Interpreter (in Python))_ ((Como Escrever um Interpretador (Lisp) em (Python)))» [.small]#[fpy.li/2-12]#. +==== + +Isso conclui nossa primeira passagem por desempacotamento, desestruturação e casamento de padrões com sequências. +Vamos tratar de outros tipos de padrões mais adiante, em outros capítulos. + +Todo programador Python sabe que sequências podem ser fatiadas usando a sintaxe `s[a:b]`. +Vamos agora examinar alguns fatos menos conhecidos sobre fatiamento.((("", +startref="PMinterp02")))((("", startref="Spattern02")))((("", startref="lispypattern02")))((("", startref="scheme02"))) + +<<< +=== Fatiamento + +Um((("sequences", "slicing", id="Sslice02")))((("zero-based indexing"))) +recurso comum a `list`, `tuple`, `str`, e a todos os tipos de sequência em Python, +é o suporte a operações de fatiamento, que são mais potentes do que a maioria das pessoas percebe. + +Nesta seção descrevemos o _uso_ dessas formas avançadas de fatiamento. +Sua implementação em uma classe definida pelo usuário será tratada no «Capítulo 12» [.small]#[vol.2, fpy.li/12]#, +mantendo nossa filosofia de tratar de classes prontas para usar nessa parte do livro, +e da criação de novas classes na +«Parte III—Classes e Protocolos» [.small]#[vol.2, fpy.li/4v]#. + + +==== Por que fatias e faixas excluem o último item? + +A((("slicing", "excluding last item in"))) convenção pythônica de excluir o último item em fatias e +faixas funciona bem com a indexação iniciada no zero usada no Python, no C e em muitas outras linguagens. +Algumas vantagens desta convenção: + +* É fácil ver o tamanho da fatia ou da faixa quando apenas a posição final é dada: tanto `range(3)` quanto `my_list[:3]` produzem três itens. +* É fácil calcular o tamanho de uma fatia ou de uma faixa quando o início e o fim são dados: basta subtrair `fim-início`. +* É fácil cortar uma sequência em duas partes em qualquer índice `x`, sem sobreposição: escreva `my_list[:x]` e `my_list[x:]`. +Por exemplo: ++ +[source, python] +---- +>>> l = [10, 20, 30, 40, 50, 60] +>>> l[:2] # split at 2 +[10, 20] +>>> l[2:] +[30, 40, 50, 60] +>>> l[:3] # split at 3 +[10, 20, 30] +>>> l[3:] +[40, 50, 60] +---- + +Agora vamos olhar mais de perto a forma como Python interpreta a notação de fatiamento. + +[[slice_objects_sec]] +==== Objetos slice + +Isso((("slicing", "slice objects"))) não é segredo, mas vale a pena repetir, só para ter certeza: +`s[a:b:c]` pode ser usado para especificar um passo ou salto `c`, fazendo com que a fatia resultante pule itens. +O passo pode ser também negativo, devolvendo os itens em ordem inversa. +Veja três exemplos: + +[source, python] +---- +>>> s = 'bicycle' +>>> s[::3] +'bye' +>>> s[::-1] +'elcycib' +>>> s[::-2] +'eccb' +---- + +Vimos outro exemplo no <>, quando usamos `deck[12::13]` para obter todos os ases de um baralho ordenado por naipes: + +[source, python] +---- +>>> deck[12::13] +[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), +Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')] +---- + +A notação `a:b:c` só é válida entre `[]` quando usada como operador de indexação ou de subscrição (_subscript_), +e produz um objeto fatia (_slice object_): `slice(a, b, c)`. +Para avaliar a expressão `seq[start:stop:step]`, +o Python chama `+seq.__getitem__(slice(start, stop, step))+`, como veremos na «Seção 12.5.1» [.small]#[vol.2, fpy.li/4w]#. +Mesmo se você não for implementar seus próprios tipos de sequência, saber dos objetos fatia é útil, +porque eles permitem que você atribua nomes às fatias, da mesma forma que planilhas permitem dar nomes a faixas de células. + +Suponha que você precise analisar um arquivo de dados como a fatura mostrada no <>. +Em vez de encher seu código de fatias explícitas fixas, você pode nomeá-las. +Veja como isso torna legível o laço `for` no final do exemplo. + +<<< +[[flat_file_invoice]] +.Itens de um arquivo tabular de fatura +==== +[source, python] +---- +>>> invoice = """ +... 0.....6.................................40........52...55........ +... 1909 Pimoroni PiBrella $17.50 3 $52.50 +... 1489 6mm Tactile Switch x20 $4.95 2 $9.90 +... 1510 Panavise Jr. - PV-201 $28.00 1 $28.00 +... 1601 PiTFT Mini Kit 320x240 $34.95 1 $34.95 +... """ +>>> SKU = slice(0, 6) +>>> DESCRIPTION = slice(6, 40) +>>> UNIT_PRICE = slice(40, 52) +>>> QUANTITY = slice(52, 55) +>>> ITEM_TOTAL = slice(55, None) +>>> line_items = invoice.split('\n')[2:] +>>> for item in line_items: +... print(item[UNIT_PRICE], item[DESCRIPTION]) +... + $17.50 Pimoroni PiBrella + $4.95 6mm Tactile Switch x20 + $28.00 Panavise Jr. - PV-201 + $34.95 PiTFT Mini Kit 320x240 +---- +==== + +Voltaremos aos objetos +slice+ quando formos discutir a criação de suas próprias coleções, +na «Seção 12.5» [.small]#[vol.2, fpy.li/4x]#. +Enquanto isso, do ponto de vista do usuário, o fatiamento tem recursos adicionais, +como fatias multidimensionais e a notação de reticências (`\...`). +Siga comigo. + + +==== Fatiamento multidimensional e reticências + +O operador `[]`((("[] (square brackets)")))((("square brackets ([])")))((("slicing", "multidimensional slicing and ellipses"))) +pode também receber múltiplos índices ou fatias separadas por vírgulas. +Os((("__getitem__")))((("__setitem__"))) +métodos especiais `+__getitem__+` e `+__setitem__+`, que tratam o operador `[]`, +recebem os índices em `a[i, j]` como uma tupla. +Em outras palavras, para avaliar `a[i, j]`, Python chama `+a.__getitem__((i, j))+`. + +Isso é usado, por exemplo, no pacote externo NumPy, onde itens de uma `numpy.ndarray` bi-dimensional +podem ser recuperados usando a sintaxe `a[i, j]`, e uma fatia bi-dimensional é obtida com uma expressão como `a[m:n, k:l]`. +O <>, abaixo nesse mesmo capítulo, mostra o uso dessa notação. + +Exceto por `memoryview`, os tipos embutidos de sequência de Python são unidimensionais, +então aceitam só um índice ou fatia, e não uma tupla de índices ou +fatias.footnote:[Na <> vamos mostrar que +views da memória construídas de forma especial podem ter mais de uma dimensão.] + +As((("ellipsis (…)")))((("… (ellipsis)"))) reticências—escritas como três pontos finais +(`\...`) e não como `…` (Unicode U+2026)—são reconhecidas como um símbolo pelo parser de Python. +Esse símbolo é um apelido para o objeto `Ellipsis`, a única instância da classe `ellipsis`.footnote:[Não, +eu não escrevi ao contrário: o nome da classe `ellipsis` realmente se escreve só com minúsculas, +e a instância é um objeto embutido chamado `Ellipsis`, +da mesma forma que `bool` é em minúsculas mas suas instâncias são `True` e `False`.] +Dessa forma, ele pode ser passado como argumento para funções e como parte da especificação de uma fatia, +como em `f(a, \..., z)` ou `a[i:\\...]`. +O NumPy usa `\...` como atalho ao fatiar arrays com muitas dimensões; +por exemplo, se `x` é um array com quatro dimensões, `x[i, \\...]` é um atalho para `x[i, :, :, :,]`. +Veja «_NumPy quickstart_» [.small]#[fpy.li/2-13]# +para saber mais sobre isso. + +Desconheço usos de `Ellipsis` ou de índices multidimensionais na biblioteca padrão de Python. +Esses recursos sintáticos existem para suportar tipos definidos pelo usuário ou extensões como a NumPy. + +Fatias não são úteis apenas para extrair informações de sequências; +elas podem também ser usadas para modificar sequências mutáveis internamente—isto é, sem precisar reconstruí-las do zero. + +[[assigning_to_slices]] +==== Atribuindo a fatias + +Sequências((("slicing", "assigning to slices"))) mutáveis podem ser enxertadas, +extirpadas e, modificadas internamente de várias maneiras através da notação de fatias +do lado esquerdo de uma instrução de atribuição ou como alvo de uma instrução `del`. +Os próximos exemplos dão uma ideia do poder desta notação: + +[source, python] +---- +>>> l = list(range(10)) +>>> l +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +>>> l[2:5] = [20, 30] +>>> l +[0, 1, 20, 30, 5, 6, 7, 8, 9] +>>> del l[5:7] +>>> l +[0, 1, 20, 30, 5, 8, 9] +>>> l[3::2] = [11, 22] +>>> l +[0, 1, 20, 11, 5, 22, 9] +>>> l[2:5] = 100 <1> +Traceback (most recent call last): + File "", line 1, in +TypeError: can only assign an iterable +>>> l[2:5] = [100] +>>> l +[0, 1, 100, 22, 9] +---- +<1> Quando o alvo de uma atribuição é uma fatia, +o lado direito deve ser um objeto iterável, mesmo que tenha apenas um item. + +Todo programador sabe que a concatenação é uma operação frequente com sequências. +Tutoriais introdutórios de Python explicam o uso de `+` e `*` para tal propósito, +mas há detalhes sutis em seu funcionamento, como veremos a seguir.((("", startref="Sslice02"))) + + +=== Usando + e * com sequências +Em((("sequences", "using + and * with", id="Splusast02")))((("+ operator", +id="plusop02")))((("star (*) operator", id="starseq02")))((("* (star) operator", +id="starseq02a")))((("concatenation", id="concat02"))) +Python, podemos assumir que sequências suportam `\+` e `*`. +Em geral, os dois operandos de `+` devem ser sequências do mesmo tipo, +e nenhum deles é modificado: uma nova sequência daquele mesmo tipo é criada como resultado da concatenação. + +Para concatenar múltiplas cópias da mesma sequência, multiplique por um inteiro. +Da mesma forma, uma nova sequência é criada: + +[source, python] +---- +>>> l = [1, 2, 3] +>>> l * 5 +[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3] +>>> 5 * 'abcd' +'abcdabcdabcdabcdabcd' +---- + +Tanto `+` quanto `*` sempre criam um novo objeto, e nunca modificam seus operandos. + +<<< +[WARNING] +==== +Tenha cuidado com expressões como `a * n` quando `a` é uma sequência contendo itens mutáveis, +pois o resultado pode ser surpreendente. +Por exemplo, tentar inicializar uma lista de listas como `my_list = [[]] * 3` +vai resultar em uma lista com três referências para a mesma lista interna, +que provavelmente não é o desejado. +==== + +A próxima seção fala das armadilhas ao se tentar usar `*` para inicializar uma lista de listas. + +==== Criando uma lista de listas + +Algumas((("lists", "building lists of lists"))) vezes precisamos inicializar +uma lista com um certo número de listas aninhadas—para, por exemplo, +distribuir estudantes em uma lista de equipes, ou para representar casas no tabuleiro de um jogo. +A melhor forma de fazer isso é com uma compreensão de lista, como no <>. + +[[ex_list_of_lists_ok]] +.Uma lista com três listas de tamanho 3 pode representar um tabuleiro de jogo da velha +==== +[source, python] +---- +>>> board = [['_'] * 3 for i in range(3)] <1> +>>> board +[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']] +>>> board[1][2] = 'X' <2> +>>> board +[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']] +---- +==== +<1> Cria uma lista de três listas, cada uma com três itens. +Inspeciona a estrutura criada. +<2> Coloca um "X" na linha 1, coluna 2, e verifica o resultado. + +Um atalho tentador, mas errado, seria fazer algo como o <>. + +<<< +[[ex_list_of_lists_wrong]] +.Uma lista com três referências para a mesma lista é inútil +==== +[source, python] +---- +>>> weird_board = [['_'] * 3] * 3 <1> +>>> weird_board +[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']] +>>> weird_board[1][2] = 'O' <2> +>>> weird_board +[['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']] +---- +==== +<1> A lista externa é feita de três referências para a mesma lista interna. +Enquanto ela não é modificada, tudo parece correr bem. +<2> Colocar um "O" na linha 1, coluna 2, revela que todas as linhas são apelidos do mesmo objeto. + +O problema com o <> é que ele faz o mesmo que isto: + +[source, python] +---- +row = ['_'] * 3 +board = [] +for i in range(3): + board.append(row) <1> +---- +<1> A mesma `row` é anexada três vezes ao `board`. + +Por outro lado, a compreensão de lista no <> faz isto: + +[source, python] +---- +>>> board = [] +>>> for i in range(3): +... row = ['_'] * 3 # <1> +... board.append(row) +... +>>> board +[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']] +>>> board[2][0] = 'X' +>>> board # <2> +[['_', '_', '_'], ['_', '_', '_'], ['X', '_', '_']] +---- +<1> Cada iteração cria uma nova `row` e a acrescenta ao `board`. +<2> Como esperado, apenas a linha 2 é modificada. + +[TIP] +==== +Se o problema ou a solução mostrados nessa seção não estão claros para você, não se preocupe. +Escrevi o <> para esclarecer a mecânica e os perigos das referências e dos objetos mutáveis. +==== + +Até aqui discutimos o uso dos operadores simples `\+` e `\*` com sequências, +mas existem também os operadores `+=` e `*=`, que produzem resultados muito diferentes, +dependendo da mutabilidade da sequência alvo. +A próxima seção explica como eles funcionam. + +[[aug_assign_seqs]] +==== Atribuição aumentada com sequências + +Os((("augmented assignment operators", id="augasop02")))((("+= (addition assignment) operator", +id="adassign02")))((("*= (star equals) operator", id="stareq02")))((("addition assignment (+=) operator", +id="additonassign02"))) +operadores de atribuição aumentada `\+=` e `\*=` se comportam de formas muito diferentes, +dependendo do primeiro operando. +Para simplificar a discussão, vamos primeiro nos concentrar na adição aumentada (`+=`), +mas os conceitos se aplicam a `*=` e a outros operadores de atribuição aumentada. + +O método especial que faz `\+=` funcionar +chama-se `++__iadd__++` , que significa _in-place addition_ (adição interna). + +Entretanto, se `+__iadd__+` não estiver implementado, Python usa `+__add__+` como antes de fazer a atribuição. +Considere essa expressão simples: + +[source, python] +---- +>>> a += b +---- + +Se `a` implementar `+__iadd__+`, esse método será chamado. +No caso de sequências mutáveis (por exemplo, `list`, `bytearray`, `array.array`), +o objeto `a` será modificado internamente (mesmo resultado de `a.extend(b)`). +Porém, quando `a` não implementa `+__iadd__+`, a expressão `+a += b+` tem o mesmo efeito de `a = a + b`: +a expressão `a + b` é avaliada antes, produzindo um novo objeto, que então é vinculado a `a`. +Em outras palavras, a identidade do objeto vinculado à variável `a` pode ou não mudar, +dependendo da existência de `+__iadd__+`. + +Em geral, para sequências mutáveis, é razoável supor que `+__iadd__+` está implementado e +que `+=` acontece _in-place_. +Para sequências imutáveis, obviamente isso não pode acontecer. + +<<< +O que acabei de escrever sobre `+=` também se aplica a `\*=`, que é implementado via `++__imul__++`. +Os métodos especiais `++__iadd__++` e `++__imul__++` são tratados no +«Capítulo 16» [.small]#[vol.2, fpy.li/16]#. + +Veja uma demonstração de `*=` com uma sequência mutável e depois com uma sequência imutável: + +[source, python] +---- +>>> l = [1, 2, 3] +>>> id(l) +4311953800 <1> +>>> l *= 2 +>>> l +[1, 2, 3, 1, 2, 3] +>>> id(l) +4311953800 <2> +>>> t = (1, 2, 3) +>>> id(t) +4312681568 <3> +>>> t *= 2 +>>> id(t) +4301348296 <4> +---- +<1> O `id` da lista inicial. +<2> Após a multiplicação, a lista é o mesmo objeto, com novos itens anexados. +<3> O `id` da tupla inicial. +<4> Após a multiplicação, uma nova tupla foi criada. + +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 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.] + +Vimos casos de uso comuns para `+=`. +A próxima seção mostra um caso surpreendente, +que demonstra o real significado de "imutável" no contexto das tuplas. + +<<< +[[tuple_puzzler]] +==== Um quebra-cabeça com a atribuição += + +Tente responder sem usar o console: qual o resultado da avaliação das duas expressões no +<>?footnote:[Agradeço a Leonardo Rochael e Cesar Kawakami +por compartilharem este enigma na Conferência PythonBrasil de 2013.] + +[[ex_aug_item_assign_question]] +.Um enigma +==== +[source, python] +---- +>>> t = (1, 2, [30, 40]) +>>> t[2] += [50, 60] +---- +==== + +O que acontece a seguir? Escolha a melhor alternativa: + +**A)** `t` se torna `(1, 2, [30, 40, 50, 60])`. + +**B)** É gerado um `TypeError` com a mensagem `'tuple' object does not support item assignment` (_o objeto tupla não suporta atribuição de itens_). + +**C)** As alternativas **A** e **B** estão corretas. + +**D)** Nenhuma das alternativas acima. + + +Quando vi isso, tinha certeza de que a resposta era **B**, mas, na verdade é **C**: +o conteúdo da tupla é alterado, e acontece aquele `TypeError`! + +O <> é a saída real em um console rodando Python +3.10.footnote:[Alguns leitores sugeriram que a operação no exemplo pode ser +realizada com `++t[2].extend([50,60])++`, sem erros. +Eu sei disso, mas a intenção aqui é mostrar o comportamento estranho do operador `+=` nesse caso.] + +[[ex_aug_item_assign_solution]] +.O resultado inesperado: o item t2 é modificado _e_ uma exceção é gerada + +==== +[source, python] +---- +>>> t = (1, 2, [30, 40]) +>>> t[2] += [50, 60] +Traceback (most recent call last): + File "", line 1, in +TypeError: 'tuple' object does not support item assignment +>>> t +(1, 2, [30, 40, 50, 60]) +---- +==== + +O «_Online Python Tutor_» [.small]#[fpy.li/2-14]# é uma ferramenta online fantástica +para visualizar em detalhes o funcionamento do Python. +A <> é uma composição de duas capturas de tela, +mostrando os estados inicial e final da tupla `t` do <>. + +[[aug_item_assign_tutor]] +.Estados inicial e final do enigma da atribuição de tuplas (diagrama gerado pelo Online Python Tutor). +image::../images/flpy_0205.png[align="center",pdfwidth=11cm] + +Se olharmos o bytecode gerado pelo Python para a expressão `s[a] += b` (<>), +fica claro como isso acontece. + +[[ex_aug_item_assign_bytecode]] +.Bytecode para a expressão `s[a] += b` +==== +[source, python] +---- +>>> dis.dis('s[a] += b') + 1 0 LOAD_NAME 0 (s) + 3 LOAD_NAME 1 (a) + 6 DUP_TOP_TWO + 7 BINARY_SUBSCR <1> + 8 LOAD_NAME 2 (b) + 11 INPLACE_ADD <2> + 12 ROT_THREE + 13 STORE_SUBSCR <3> + 14 LOAD_CONST 0 (None) + 17 RETURN_VALUE +---- +==== +<1> Coloca o valor de `s[a]` no `TOS` (_Top Of Stack_, topo da pilha). +<2> Executa `TOS += b`. Isso é bem sucedido se `TOS` se refere a um objeto mutável +(no <> é uma lista). +<3> Atribui `s[a] = TOS`. +Isso falha se `s` é imutável (a tupla `t` no <>). + +Esse exemplo é um caso raro—nunca vi esse comportamento bizarro estragar o dia de alguém +em mais de 20 anos trabalhando com Python. + +<<< +Há três lições para tirar deste experimento: + +. Evite colocar objetos mutáveis em tuplas. +. A atribuição aumentada não é uma operação atômica—acabamos de vê-la gerar uma exceção após executar parte de seu trabalho. +. Inspecionar o bytecode de Python não é muito difícil, e pode ajudar a ver o que está acontecendo por debaixo dos panos. + +Depois de ver as sutilezas do uso de `+` e `*` para concatenação, +podemos mudar de assunto e tratar de outra operação essencial com sequências: +ordenação.((("", startref="Splusast02")))((("", startref="plusop02")))((("", +startref="starseq02")))((("", startref="starseq02a")))((("", startref="concat02")))((("", +startref="augasop02")))((("", startref="stareq02")))((("", startref="additonassign02")))((("", startref="adassign02"))) + + +[[sort_x_sorted]] +=== list.sort versus a função embutida sorted + +O((("sequences", "list.sort versus sorted built-in", id="Slistsort02")))((("lists", +"list.sort versus sorted built-in", id="Llistsort")))((("sorted function", id="sortfun02"))) +método `list.sort` ordena uma lista internamente (_in-place_)—isto é, sem criar uma cópia. +Ele devolve `None` para nos lembrar que muda a própria instância e não cria uma nova lista. +Essa é uma convenção importante da API de Python: +funções e métodos que mudam um objeto internamente devem devolver `None`, +para deixar claro a quem chamou que o +receptorfootnote:[Receptor (_receiver_) é o alvo de uma chamada a um método, +o objeto vinculado a `self` no corpo do método.] +foi modificado, e que nenhum objeto novo foi criado. +Um comportamento similar pode ser observado +na função `random.shuffle(s)`, que devolve `None` após +embaralhar os itens de uma sequência mutável internamente, +mudando a posição dos itens dentro da própria sequência. + + +[NOTE] +==== +A convenção de devolver `None` para sinalizar mudanças internas tem uma desvantagem: +não podemos encadear chamadas a esses métodos. +Em contraste, métodos que devolvem novos objetos (todos os métodos de `str`, por exemplo) +podem ser cascateados no estilo de uma interface fluente. +Veja o artigo «_Fluent interface_» [.small]#[fpy.li/2-15]# +da Wikipedia em inglês para uma descrição mais detalhada deste tópico. +==== + +A função embutida `sorted`, por outro lado, cria e devolve uma nova lista. +Ela aceita qualquer objeto iterável como um argumento, incluindo sequências imutáveis e geradores +(veja o «Capítulo 17» [.small]#[vol.3, fpy.li/17]#). +independentemente do tipo do iterável passado a `sorted`, ela sempre cria e devolve uma nova lista. + +<<< +Tanto `list.sort` quanto `sorted` podem receber dois argumentos nomeados opcionais: + +`reverse`:: + Se `True`, os itens são devolvidos em ordem decrescente (isto é, invertendo a comparação dos itens). +O default é `False`. + +`key`:: + Uma função de um argumento que será aplicada a cada item, para produzir sua chave de ordenação. + Por exemplo, ao ordenar uma lista de strings, `key=str.lower` + pode ser usada para realizar uma ordenação sem levar em conta maiúsculas e minúsculas, + e `key=len` irá ordenar as strings pela quantidade de caracteres. +O default é a função identidade (isto é, os itens propriamente ditos são comparados). + + +[TIP] +==== +Também se pode usar o parâmetro de palavra-chave opcional `key` +com as funções embutidas `min()` e `max()`, +e com outras funções da biblioteca padrão (por exemplo, `itertools.groupby()` e `heapq.nlargest()`). +==== + +Aqui estão alguns exemplos para esclarecer o uso dessas funções e dos argumentos nomeados. +Os exemplos também demonstram que o algoritmo de ordenação de Python é estável +(isto é, ele preserva a ordem relativa de itens que resultam iguais na comparação):footnote:[O +principal algoritmo de ordenação de Python se chama Timsort, em homenagem a seu criador, +Tim Peters. +Para curiosidades sobre o Timsort, veja o <>.] + + +[source, python] +---- +>>> fruits = ['grape', 'raspberry', 'apple', 'banana'] +>>> sorted(fruits) +['apple', 'banana', 'grape', 'raspberry'] <1> +>>> fruits +['grape', 'raspberry', 'apple', 'banana'] <2> +>>> sorted(fruits, reverse=True) +['raspberry', 'grape', 'banana', 'apple'] <3> +>>> sorted(fruits, key=len) +['grape', 'apple', 'banana', 'raspberry'] <4> +>>> sorted(fruits, key=len, reverse=True) +['raspberry', 'banana', 'grape', 'apple'] <5> +>>> fruits +['grape', 'raspberry', 'apple', 'banana'] <6> +>>> fruits.sort() <7> +>>> fruits +['apple', 'banana', 'grape', 'raspberry'] <8> +---- +<1> Isso produz uma lista de strings ordenadas +alfabeticamente.footnote:[As palavras neste exemplo estão ordenadas em ordem alfabética +porque são 100% constituídas de caracteres ASCII em letras minúsculas. +Veja o aviso após o exemplo.] +<2> Inspecionando a lista original, vemos que ela não mudou. +<3> Isso é a ordenação "alfabética" anterior, invertida. +<4> Uma nova lista de strings, agora ordenada por tamanho. +Como o algoritmo de ordenação é estável, "grape" e "apple," ambas com tamanho 5, estão em sua ordem original. +<5> Essas são strings ordenadas por tamanho em ordem descendente. +Não é o inverso do resultado anterior porque a ordenação é estável e então, novamente, "grape" aparece antes de "apple." +<6> Até aqui, a ordenação da lista `fruits` original não mudou. +<7> Isso ordena a lista internamente, devolvendo `None` (que o console omite). +<8> Agora `fruits` está ordenada. + +[WARNING] +==== +Por((("strings", "default sorting of"))) default, +Python ordena as strings lexicograficamente por código de caractere. +Isso quer dizer que as letras maiúsculas ASCII virão antes das minúsculas, +e caracteres não-ASCII dificilmente serão ordenados de forma razoável. +A <> explica como ordenar alfabeticamente. +==== + +Uma vez ordenadas, podemos realizar buscas em nossas sequências de forma muito eficiente. +Um((("bisect module"))) algoritmo de busca binária já é fornecido no módulo `bisect` da biblioteca padrão de Python. +Aquele módulo também inclui a função `bisect.insort`, +que você pode usar para assegurar que suas sequências ordenadas permaneçam ordenadas. +Há uma introdução ilustrada ao módulo `bisect` no post +«_Managing Ordered Sequences with Bisect_ (Gerenciando Sequências Ordenadas com Bisect)» [.small]#[fpy.li/bisect]# +no site que complementa a edição deste livro em inglês. + +Muito do que vimos até aqui neste capítulo se aplica a sequências em geral, não apenas a listas ou tuplas. +Programadores Python às vezes usam excessivamente o tipo `list`, +por ele ser tão conveniente—eu já fiz isso. +Por exemplo, se você está processando grandes listas de números, +deveria considerar usar arrays em vez de listas. +O restante do capítulo é dedicado a alternativas a listas e tuplas.((("", startref="Slistsort02")))((("", startref="Llistsort")))((("", startref="sortfun02"))) + + +=== Quando uma lista não é a resposta + +O((("sequences", "alternatives to lists", id="Salt02")))((("lists", "alternatives to", id="Lalt02"))) +tipo `list` é flexível e fácil de usar, mas há opções melhores dependendo do caso. +Por exemplo, um `array` economiza muita memória se você precisa manipular milhões de valores de ponto flutuante. +Por outro lado, se você está constantemente acrescentando e removendo itens das pontas opostas de uma lista, +é bom saber que um((("queues", "deque (double-ended queue)")))((("deque (double-ended queue)")))((("FIFO (first in, first out)"))) +`deque` (uma fila com duas pontas) é uma estrutura de dados +FIFOfootnote:[Sigla em inglês para "First in, first out" +(primeiro a entrar, primeiro a sair), o comportamento padrão de filas.] mais eficiente. + +[TIP] +==== +Se seu código frequentemente verifica se um item está presente em uma coleção (ex. `item in my_collection`), +considere usar um `set` para `my_collection`, especialmente se ela contiver um número grande de itens. +Um `set` é otimizado para verificação rápida de presença de itens. +Eles também são iteráveis, mas não são sequências, porque a ordenação de itens de conjuntos não é estável. +Vamos falar deles no <>. +==== + +O restante desse capítulo discute tipos mutáveis de sequências que, em muitos casos, podem substituir as listas. +Começamos pelos arrays. + +[[arrays_sec]] +==== Arrays + +Se((("arrays", id="array02"))) uma lista contém apenas números, um `array.array` é um substituto mais eficiente. +Arrays suportam todas as operações das sequências mutáveis (incluindo `.pop`, `.insert`, e `.extend`), +bem como métodos adicionais para carregamento e armazenamento rápidos, como +`.frombytes` e `.tofile`. + +Um array de Python é quase tão enxuto quanto um array do C. +Como mostrado na <>, um `array` de valores `float` não contém objetos da classe `float`, +mas apenas os bytes representando seus valores em código de máquina—como um array do tipo `double` na linguagem C. +Ao criar um `array`, você fornece um código de tipo (_typecode_), +uma letra que determina o tipo na linguagem C usado para armazenar cada item na memória. +Por exemplo, +b+ é o código de tipo para o que o C chama de `signed char`, um inteiro variando de -128 a 127. +Se você criar um `array('b')`, então cada item será armazenado em um único byte e será lido como um inteiro. +Para grandes sequências de números, isso economiza muita memória. +E Python não permite que você insira números incompatíveis com o tipo do array. + +O <> mostra a criação, o armazenamento e o carregamento +de um array de 10 milhões de números de ponto flutuante aleatórios. + +[[ex_array_io]] +.Criando, armazenando e carregando uma grande array de números de ponto flutuante. +==== +[source, python] +---- +>>> from array import array <1> +>>> from random import random +>>> floats = array('d', (random() for i in range(10**7))) <2> +>>> floats[-1] <3> +0.07802343889111107 +>>> fp = open('floats.bin', 'wb') +>>> floats.tofile(fp) <4> +>>> fp.close() +>>> floats2 = array('d') <5> +>>> fp = open('floats.bin', 'rb') +>>> floats2.fromfile(fp, 10**7) <6> +>>> fp.close() +>>> floats2[-1] <7> +0.07802343889111107 +>>> floats2 == floats <8> +True +---- +==== +<1> Importa o tipo `array`. +<2> Cria um array de números de ponto flutuante de dupla precisão (código de tipo `'d'`) +a partir de qualquer objeto iterável—nesse caso, uma expressão geradora. +<3> Inspeciona o último número no array. +<4> Salva o array em um arquivo binário. +<5> Cria um array vazio de números de ponto flutuante de dupla precisão +<6> Lê 10 milhões de números do arquivo binário. +<7> Inspeciona o último número no array. +<8> Verifica a igualdade do conteúdo dos arrays + +Como você pode ver, `array.tofile` e `array.fromfile` são fáceis de usar. +Se você rodar o exemplo, verá que são também muito rápidos. +Um pequeno experimento mostra que `array.fromfile` demora aproximadamente 0,1 +segundos para carregar 10 milhões de números de ponto flutuante de +dupla precisão de um arquivo binário criado com `array.tofile`. +Isso é quase 60 vezes mais rápido que ler os números de um arquivo de texto, +algo que também exige passar cada linha para a função embutida `float`. +Salvar o arquivo com `array.tofile` é umas sete vezes mais rápido que +escrever um número de ponto flutuante por vez em um arquivo de texto. +Além disso, o tamanho do arquivo binário com 10 milhões de números de dupla precisão é de +80.000.000 bytes (8 bytes por número, nenhum byte a mais), +enquanto o arquivo de texto ocupa 181.515.739 bytes para os mesmos dados. + +Para o caso específico de arrays numéricas representando dados binários, +tal como bitmaps de imagens, Python tem os tipos `bytes` e `bytearray`, +discutidos no <>. + +Vamos encerrar essa seção sobre arrays com a <>, +comparando as características de `list` e `array.array`. + +[[list_x_array_attrs_tbl]] +.Métodos e atributos encontrados em `list` ou `array` (os métodos descontinuados de array e aqueles implementados em object foram omitidos para preservar espaço) +[options="header", cols="7,^2,^2,15"] +|================================================================================================================================================ +| | list | array |   +| `+s.__add__(s2)+` | ● | ● | `++s + s2++`—concatenação +| `+s.__iadd__(s2)+` | ● | ● | `++s += s2++`—concatenação interna +| `s.append(e)` | ● | ● | Acrescenta um elemento após o último +| `s.byteswap()` | | ● | Permuta os bytes de todos os itens do array para conversão de _endianness_ (ordem dos bytes na memória) +| `s.clear()` | ● | | Apaga todos os itens +| `+s.__contains__(e)+` | ● | ● | `e in s` +| `s.copy()` | ● | | Cópia rasa da lista +| `+s.__copy__()+` | | ● | Suporte a `copy.copy` +| `s.count(e)` | ● | ● | Conta as ocorrências de um elemento +| `+s.__deepcopy__()+` | | ● | Suporte otimizado a `copy.deepcopy` +| `+s.__delitem__(p)+` | ● | ● | Remove o item na posição `p` +| `s.extend(it)` | ● | ● | Acrescenta itens a partir do iterável `it` +| `s.frombytes(b)` | | ● | Acrescenta itens de uma sequência de bytes, interpretada como valores compactos de máquina +| `s.fromfile(f, n)` | | ● | Acrescenta `n` itens de um arquivo binário `f`, interpretado como valores compactos de máquina +| `s.fromlist(l)` | | ● | Acrescenta itens de lista; se um deles causar um `TypeError`, nenhum item é acrescentado +| `+s.__getitem__(p)+` | ● | ● | `++s[p]++`—obtém o item ou fatia na posição +| `s.index(e)` | ● | ● | Posição da primeira ocorrência de `e` +| `s.insert(p, e)` | ● | ● | Insere elemento `e` antes do item na posição `p` +| `s.itemsize` | | ● | Tamanho em bytes de cada item do array +| `+s.__iter__()+` | ● | ● | Obtém iterador +| `+s.__len__()+` | ● | ● | `++len(s)++`—quantidade de itens +| `+s.__mul__(n)+` | ● | ● | `++s * n++`—concatenação repetida +| `+s.__imul__(n)+` | ● | ● | `++s *= n++`—concatenação repetida interna +| `+s.__rmul__(n)+` | ● | ● | ++n * s++—concatenação repetida reversa +| `s.pop([p])` | ● | ● | Remove e devolve o item na posição `p` (default: o último) +| `s.remove(e)` | ● | | Remove o primeiro item de valor igual a `e` +| `s.reverse()` | ● | ● | Inverte a ordem dos itens internamente +| `+s.__reversed__()+` | ● | | Obtém iterador para percorrer itens do último até o primeiro +|`+s.__setitem__(p, e)+` | ● | ● | `++s[p] = e++` (sobrescreve a posição ou fatia `p`) +|`s.sort([key], [reverse])` | ● | | Ordena itens internamente, com os argumentos nomeados opcionais `key` e `reverse` +| `s.tobytes()` | | ● | Devolve itens como valores de máquina compactos em um objeto `bytes` +| `s.tofile(f)` | | ● | Grava itens como valores de máquina compactos no arquivo binário `f` +| `s.tolist()` | | ● | Devolve os itens como objetos numéricos em uma `list` +| `s.typecode` | | ● | String de um caractere identificando o tipo em C dos itens +|================================================================================================================================================ + +[TIP] +==== +Até Python 3.10, o tipo `array` ainda não tem um método `sort` equivalente a `list.sort()`, +que reordena os elementos na própria estrutura de dados, sem copiá-la. +Se você precisa ordenar um array, use a função embutida `sorted` para reconstruir o array: + +[source, python] +---- +a = array.array(a.typecode, sorted(a)) +---- + +Para manter a ordem de um array ordenado ao acrescentar novos itens, use a função +«`bisect.insort`» [.small]#[fpy.li/2t]#. +==== + +Se você trabalha muito com arrays e não conhece `memoryview`, +pode estar desperdiçando memória e CPU. +Veja o próximo tópico.((("", startref="array02"))) + + +[[memoryview_sec]] +==== Memoryviews + +A((("memoryview class", id="memview02"))) classe embutida `memoryview` é um tipo de sequência +para acesso direto a memória compartilhada, +que permite manipular fatias de arrays sem copiar bytes. +Ela foi inspirada pela biblioteca NumPy (que discutiremos brevemente, na <>). +Travis Oliphant, autor principal da NumPy, +responde assim à questão +«_When should a memoryview be used?_» [.small]#[fpy.li/2-17]# +(Quando se deve usar uma memoryview?): + +[quote] +____ +Uma memoryview é essencialmente uma estrutura de array Numpy generalizada dentro do próprio Python +(sem a matemática). +Ela permite compartilhar memória entre estruturas de dados +(coisas como imagens PIL, bancos de dados SQLite, arrays da NumPy, etc.) sem copiar bytes. +Isso é muito importante ao lidar com grandes conjuntos de dados. +____ + +Usando uma notação similar ao módulo `array`, o método `memoryview.cast` permite mudar +a forma como múltiplos bytes são lidos ou escritos como unidades, +sem a necessidade de alterar os bytes. +`memoryview.cast` devolve um novo objeto `memoryview`, +sempre compartilhando a mesma memória. +O <> mostra como criar visões alternativas do mesmo array de 6 bytes, +para acessá-lo como uma matriz de 2×3 ou de 3×2. + + +[[ex_memoryview_demo]] +.Manipular 6 bytes de memória como views de 1×6, 2×3, e 3×2 +==== +[source, python] +---- +>>> from array import array +>>> octets = array('B', range(6)) # <1> +>>> m1 = memoryview(octets) # <2> +>>> m1.tolist() +[0, 1, 2, 3, 4, 5] +>>> m2 = m1.cast('B', [2, 3]) # <3> +>>> m2.tolist() +[[0, 1, 2], [3, 4, 5]] +>>> m3 = m1.cast('B', [3, 2]) # <4> +>>> m3.tolist() +[[0, 1], [2, 3], [4, 5]] +>>> m2[1,1] = 22 # <5> +>>> m3[1,1] = 33 # <6> +>>> octets # <7> +array('B', [0, 1, 2, 33, 22, 5]) +---- +==== +<1> Cria um array de 6 bytes (código de tipo `'B'`). +<2> Cria uma `memoryview` a partir daquele array, e a exporta como uma lista. +<3> Cria uma nova `memoryview` a partir da anterior, mas com `2` linhas e `3` colunas. +<4> Ainda outra `memoryview`, agora com `3` linhas e `2` colunas. +<5> Sobrescreve o byte em `m2`, na linha `1`, coluna `1` com `22`. +<6> Sobrescreve o byte em `m3`, na linha `1`, coluna `1` com `33`. +<7> Mostra o array original, provando que a memória era compartilhada entre `octets`, `m1`, `m2`, e `m3`. + +O fantástico poder de `memoryview` também pode ser usado para o mal. +O <> mostra como mudar um único byte de um item em um array de inteiros de 16 bits. + +[[ex_memoryview_evil_demo]] +.Mudando o valor de um item em um array de inteiros de 16 bits, trocando apenas o valor de um de seus bytes +==== +[source, python] +---- +>>> numbers = array.array('h', [-2, -1, 0, 1, 2]) +>>> memv = memoryview(numbers) <1> +>>> len(memv) +5 +>>> memv[0] <2> +-2 +>>> memv_oct = memv.cast('B') <3> +>>> memv_oct.tolist() <4> +[254, 255, 255, 255, 0, 0, 1, 0, 2, 0] +>>> memv_oct[5] = 4 <5> +>>> numbers +array('h', [-2, -1, 1024, 1, 2]) <6> +---- +==== +<1> Cria uma `memoryview` a partir de um array de 5 inteiros de 16 bits com sinal (tipo `'h'`). +<2> `memv` vê os mesmos 5 itens no array. +<3> Cria `memv_oct`, transformando os elementos de `memv` em bytes (tipo `'B'`). +<4> Exporta os elementos de `memv_oct` como uma lista de 10 bytes, para inspeção. +<5> Atribui o valor `4` ao byte com offset `5`. +<6> Observe a mudança em `numbers`: um `4` no byte mais significativo de um inteiro de 2 bytes sem sinal é `1024`. + +[NOTE] +==== +Você pode ver um exemplo de inspeção de uma `memoryview` com o pacote `struct` em +«_Parsing binary records with struct_ (Analisando registros binários com struct)» +[.small]#[fpy.li/2-18]#. +==== + +Enquanto isso, se você está fazendo processamento numérico avançado com arrays, +deveria estar usando as bibliotecas NumPy. +Vamos agora fazer um breve passeio por elas.((("", startref="memview02"))) + +[[numpy_sec]] +==== NumPy + +Neste((("NumPy", id="numpy02"))) livro eu priorizo o que já existe na biblioteca padrão de Python, +para que você a aproveite ao máximo. +Mas a NumPy é tão importante que exige um desvio. + +Graças às suas operações avançadas com arrays e matrizes, +o Numpy permitiu que Python se tornasse uma das principais linguagens para aplicações de computação científica. +A Numpy implementa tipos multidimensionais e homogêneos de arrays e matrizes, +que podem conter não apenas números, mas também registros definidos pelo usuário. +E fornece operações eficientes ao nível desses elementos. + +A SciPy((("SciPy", id="scipy02"))) é uma biblioteca criada usando a NumPy, +e oferece inúmeros algoritmos de computação científica, incluindo álgebra linear, +cálculo numérico e estatística. +A SciPy é rápida e confiável porque usa a popular base de código C e Fortran do +«Repositório Netlib» [.small]#[fpy.li/2-19]#. +Em outras palavras, a SciPy oferece aos cientistas o melhor de dois mundos: +um prompt iterativo e as APIs de alto nível de Python, +e também funções estáveis e altamente otimizadas para processamento numérico, +escritas em C, C++, e Fortran. + +O <>, uma demonstração muito rápida da Numpy, +demonstra algumas operações básicas com arrays bi-dimensionais. + +[[ex_numpy_array]] +.Operações básicas com linhas e colunas em uma `numpy.ndarray` +==== +[source, python] +---- +>>> import numpy as np <1> +>>> a = np.arange(12) <2> +>>> a +array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) +>>> type(a) + +>>> a.shape <3> +(12,) +>>> a.shape = 3, 4 <4> +>>> a +array([[ 0, 1, 2, 3], + [ 4, 5, 6, 7], + [ 8, 9, 10, 11]]) +>>> a[2] <5> +array([ 8, 9, 10, 11]) +>>> a[2, 1] <6> +9 +>>> a[:, 1] <7> +array([1, 5, 9]) +>>> a.transpose() <8> +array([[ 0, 4, 8], + [ 1, 5, 9], + [ 2, 6, 10], + [ 3, 7, 11]]) +---- +==== +<1> Importa a NumPy, que precisa ser instalada previamente (ela não faz parte da biblioteca padrão de Python). +Por convenção, `numpy` é importada como `np`. +<2> Cria e inspeciona um `numpy.ndarray` com inteiros de `0` a `11`. +<3> Inspeciona as dimensões do array: esse é um array com uma dimensão e 12 elementos. +<4> Muda o formato do array, acrescentando uma dimensão e depois inspecionando o resultado. +<5> Obtém a linha no índice `2` +<6> Obtém elemento na posição `2, 1`. +<7> Obtém a coluna no índice `1` +<8> Cria um novo array por transposição (permutando as colunas com as linhas) + +A NumPy também suporta operações de alto nível para carregar, +salvar e operar sobre todos os elementos de um `numpy.ndarray`: + +[source, python] +---- +>>> import numpy +>>> floats = numpy.loadtxt('floats-10M-lines.txt') <1> +>>> floats[-3:] <2> +array([ 3016362.69195522, 535281.10514262, 4566560.44373946]) +>>> floats *= .5 <3> +>>> floats[-3:] +array([ 1508181.34597761, 267640.55257131, 2283280.22186973]) +>>> from time import perf_counter as pc <4> +>>> t0 = pc(); floats /= 3; pc() - t0 <5> +0.03690556302899495 +>>> numpy.save('floats-10M', floats) <6> +>>> floats2 = numpy.load('floats-10M.npy', 'r+') <7> +>>> floats2 *= 6 +>>> floats2[-3:] <8> +memmap([ 3016362.69195522, 535281.10514262, 4566560.44373946]) +---- +<1> Carrega 10 milhões de números de ponto flutuante de um arquivo de texto. +<2> Usa a notação de fatiamento de sequência para inspecionar os três últimos números. +<3> Multiplica cada elemento no array `floats` por `.5` e inspeciona novamente os três últimos elementos. +<4> Importa o cronômetro de medida de tempo em alta resolução (disponível desde o Python 3.3). + +<5> Divide cada elemento por `3`; +o tempo decorrido para dividir os 10 milhões de números de ponto flutuante é menos de 40 milissegundos. +<6> Salva o array em um arquivo binário _.npy_. +<7> Carrega os dados como um arquivo mapeado na memória em outro array; +isso permite o processamento eficiente de fatias do array, mesmo que ele não caiba inteiro na memória. +<8> Inspeciona os três últimos elementos após multiplicar cada elemento por `6`. + +Isso foi apenas um aperitivo. A NumPy e a SciPy são bibliotecas formidáveis, e estão na base de outras ferramentas fantásticas, +como a «Pandas» [.small]#[fpy.li/2-20]#—que implementa tipos eficientes de arrays capazes de conter dados não-numéricos, +e fornece funções de importação/exportação em vários formatos diferentes, como _.csv_, _.xls_, dumps SQL, HDF5, +etc.—e a «scikit-learn» [.small]#[fpy.li/2-21]#, o conjunto de ferramentas para Aprendizagem de Máquina mais usado atualmente. +A maior parte das funções da NumPy e da SciPy são implementadas em C ou {cpp}, +e conseguem aproveitar todos os núcleos de CPU disponíveis, pois podem liberar a GIL((("Global Interpreter Lock (GIL)"))) +(Global Interpreter Lock, _Trava Global do Interpretador_) de Python. +O projeto «Dask» [.small]#[fpy.li/dask]# suporta a paralelização do processamento da NumPy, +da Pandas e da scikit-learn para grupos (_clusters_) de máquinas. +Esses pacotes merecem livros inteiros. +Este não é um desses livros, +mas nenhuma revisão das sequências de Python estaria completa sem pelo menos uma breve passagem pelos arrays da NumPy. + +Tendo visto as sequências planas—arrays padrão e arrays da NumPy—vamos agora +nos voltar para um grupo completamente diferente de substitutos para a boa e velha `list`: +filas (_queues_).((("", startref="numpy02")))((("", startref="scipy02"))) + +==== Deques e outras filas + +Os((("queues", "deque (double-ended queue)")))((("deque (double-ended queue)", +id="deque02")))(((".append method", primary-sortas="append method")))(((".pop method", +primary-sortas="pop method")))((("FIFO (first in, first out)"))) +métodos `.append` e `.pop` tornam uma `list` usável como uma pilha (_stack_) +ou uma fila (_queue_) (usando `.append` e `.pop(0)`, se obtém o comportamento FIFO de uma fila). +Mas inserir e remover da cabeça de uma lista +(a posição com índice 0) é caro, pois a lista toda precisa ser deslocada na memória. + +A((("collections.deque class", id="coldeq02"))) classe `collections.deque` é +uma fila _thread-safe_ (segura para usar com threads), +otimizada para inserção e remoção rápida nas duas pontas. +É também a estrutura preferencial se você precisa manter uma lista de +"últimos itens vistos" ou coisa semelhante, +pois um `deque` pode ser delimitado—isto é, criado com um tamanho máximo fixo. +Se um `deque` delimitado está cheio, quando se adiciona um novo item, o item na ponta oposta é descartado. +O <> mostra algumas das operações típicas com um `deque`. + + +[[ex_deque]] +.Usando um `deque` +==== +[source, python] +---- +>>> from collections import deque +>>> dq = deque(range(10), maxlen=10) <1> +>>> dq +deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10) +>>> dq.rotate(3) <2> +>>> dq +deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10) +>>> dq.rotate(-4) +>>> dq +deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10) +>>> dq.appendleft(-1) <3> +>>> dq +deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10) +>>> dq.extend([11, 22, 33]) <4> +>>> dq +deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33], maxlen=10) +>>> dq.extendleft([10, 20, 30, 40]) <5> +>>> dq +deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8], maxlen=10) +---- +==== +<1> O argumento opcional `maxlen` determina o número máximo de itens permitidos nessa instância de `deque`; +isso estabelece o valor de um atributo de instância `maxlen`, somente de leitura. +<2> Rotacionar com `n > 0` retira itens da direita e os recoloca pela esquerda; +quando `n < 0`, os itens são retirados pela esquerda e anexados pela direita. +<3> Acrescentar itens a um `deque` cheio (`len(d) == d.maxlen`) elimina itens da ponta oposta. +Na linha seguinte, note que o `0` foi descartado. +<4> Acrescentar três itens à direita derruba `-1`, `1`, e `2` da extremidade esquerda. +<5> Observe que `extendleft(iter)` acrescenta cada item sucessivo do argumento `iter` +do lado esquerdo do `deque`, então a posição final dos itens é invertida. + +A <> compara os métodos específicos de `list` e `deque` +(omitindo aqueles que também aparecem em `object`). + +Veja que `deque` implementa a maioria dos métodos de `list`, +acrescentando alguns específicos ao seu modelo, como `popleft` e `rotate`. +Mas há um custo oculto: remover itens do meio de um `deque` não é rápido. +A estrutura é realmente otimizada para acréscimos e remoções pelas pontas. + +As operações `append` e `popleft` são atômicas, +então `deque` pode ser usado de forma segura como uma fila FIFO em aplicações multithread sem a necessidade de travas. + +[[list_x_deque_methods_tbl]] +.Métodos implementados em `list` ou `deque` (aqueles também implementados por `object` foram omitidos para preservar espaço) +[options="header", cols="8,^3,^3,17"]] +|================================================================================================================================================== +| | list | deque |   +| `+s.__add__(s2)+` | ● | | `++s + s2++`—concatenação +| `+s.__iadd__(s2)+` | ● | ● | `++s += s2++`—concatenação interna +| `s.append(e)` | ● | ● | Acrescenta um elemento à direita (após o último) +| `s.appendleft(e)` | | ● | Acrescenta um elemento à esquerda (antes do primeiro) +| `s.clear()` | ● | ● | Apaga todos os itens +| `+s.__contains__(e)+` | ● | | `e in s` +| `s.copy()` | ● | | Cópia rasa da lista +| `+s.__copy__()+` | | ● | Suporte a `copy.copy` (cópia rasa) +| `s.count(e)` | ● | ● | Conta ocorrências de um elemento +| `+s.__delitem__(p)+` | ● | ● | Remove item na posição `p` +| `s.extend(i)` | ● | ● | Acrescenta item do iterável `i` pela direita +| `s.extendleft(i)` | | ● | Acrescenta item do iterável `i` pela esquerda +| `+s.__getitem__(p)+` | ● | ● | `++s[p]++`—obtém item ou fatia na posição +| `s.index(e)` | ● | | Encontra a primeira ocorrência de `e` +| `s.insert(p, e)` | ● | | Insere `e` antes do item na posição `p` +| `+s.__iter__()+` | ● | ● | Obtém iterador +| `+s.__len__()+` | ● | ● | `++len(s)++` (quantidade de itens) +| `+s.__mul__(n)+` | ● | | `++s * n++` (concatenação repetida) +| `+s.__imul__(n)+` | ● | | `++s *= n++` (concatenação repetida interna) +| `+s.__rmul__(n)+` | ● | | `++n * s++` (concatenação repetida reversa) +| `s.pop()` | ● | ● | Remove e devolve último itemfootnote:[`my_list.pop(p)` permite remover da posição `p`, mas `deque` não suporta este parâmetro.] +| `s.popleft()` | | ● | Remove e devolve primeiro item +| `s.remove(e)` | ● | | Remove o primeiro item de valor igual a `e` +| `s.reverse()` | ● | ● | Inverte a ordem dos itens internamente +| `+s.__reversed__()+` | ● | ● | Obtém iterador para percorrer itens, do último para o primeiro +| `s.rotate(n)` | | ● | Move `n` itens do fim para o início da fila +|`+s.__setitem__(p, e)+` | ● | ● | `++s[p] = e++` (sobrescreve a posição ou fatia `p`) +| `s.sort([key], [reverse])` | ● | | Ordena os itens internamente, com os argumentos nomeados opcionais `key` e `reverse` +|================================================================================================================================================== + +Além((("queues", "implementing"))) de `deque`, outros pacotes da biblioteca padrão de Python implementam filas: + +`queue`:: + Fornece as classes sincronizadas (isto é, seguras para se usar com múltiplas threads) + `SimpleQueue`, `Queue`, `LifoQueue`, e `PriorityQueue`. + Essas classes podem ser usadas para comunicação segura entre threads. + Todas, exceto `SimpleQueue`, podem ser delimitadas passando um argumento `maxsize` maior que 0 ao construtor. + Entretanto, elas não descartam um item para abrir espaço, como faz `deque`. + Em vez disso, quando a fila está lotada, a inserção de um novo item bloqueia quem tentou inserir—isto é, + ela espera até alguma outra thread criar espaço retirando um item da fila, útil para limitar a quantidade de threads ativas. + +`multiprocessing`:: + Implementa((("multiprocessing package"))) sua própria `SimpleQueue`, não-delimitada, + e `Queue`, delimitada, muito similares àquelas no pacote `queue`, mas projetadas para comunicação entre processos. + Uma fila especializada, `multiprocessing.JoinableQueue`, serve para gerenciamento de tarefas. + +`asyncio`:: + Fornece((("asyncio package", "queue implementation by"))) `Queue`, `LifoQueue`, `PriorityQueue`, + e `JoinableQueue` com APIs inspiradas pelas classes nos módulos `queue` e `multiprocessing`, + mas adaptadas para gerenciar tarefas em programação assíncrona. + +`heapq`:: + Diferente((("heapq package"))) dos três módulos citados, + `heapq` não implementa uma classe de fila, mas oferece funções como `heappush` e `heappop`, + que permitem o uso de uma sequência mutável como uma fila de prioridade ou _heap queue_. + + +Aqui termina nossa revisão das alternativas ao tipo `list`, +e também nossa exploração dos tipos sequência em geral—exceto pelas especificidades de `str` e +das sequências binárias, que têm um capítulo dedicado +(<>).((("", startref="Salt02")))((("", startref="Lalt02")))((("", startref="deque02")))((("", startref="coldeq02"))) + + +=== Resumo do capítulo + +Dominar((("sequences", "overview of"))) o uso dos tipos sequência da biblioteca padrão +é um pré-requisito para escrever código Python conciso, eficiente e idiomático. + +As sequências de Python são geralmente categorizadas como mutáveis ou imutáveis, +mas também é útil considerar um outro eixo: sequências planas e sequências contêiner. +As primeiras são mais compactas, mais rápidas e mais fáceis de usar, +mas estão limitadas a armazenar dados atômicos como números, caracteres e bytes. +As sequências contêiner são mais flexíveis, mas podem surpreender quando contêm objetos mutáveis. +Então, quando armazenando estruturas de dados aninhadas, +é preciso ter cuidado para usar tais sequências da forma correta. +Infelizmente, Python não tem um tipo de sequência contêiner verdadeiramente imutável: +mesmo as tuplas "imutáveis" podem ter seus valores modificados quando contêm itens mutáveis, +como listas ou objetos definidos pelo usuário. + +Compreensões de lista e expressões geradoras são notações poderosas para criar e inicializar sequências. +Se você ainda não se sente confortável com essas técnicas, gaste o tempo necessário para aprender seu uso básico. +Não é difícil, e você logo gostará delas. + +As tuplas no Python servem como registros de campos sem nome e como listas imutáveis. +Ao usar uma tupla como uma lista imutável, +lembre-se de que seu valor só será fixo se todos os seus itens também forem imutáveis. +Chamar `hash(t)` com a tupla como argumento é uma forma rápida de se assegurar de que seu valor é fixo. +Se `t` contiver itens mutáveis, um `TypeError` é gerado. + +Quando uma tupla é usada como registro, +o desempacotamento de tuplas é a forma mais segura e legível de extrair seus campos. +Além das tuplas, `*` funciona com listas e iteráveis em vários contextos, +e alguns de seus casos de uso apareceram no Python 3.5 com a +«_PEP 448—Additional Unpacking Generalizations_ (Generalizações de desempacotamento adicionais)» +[.small]#[fpy.li/pep448]#. +Python 3.10 introduziu o casamento de padrões com `match/case`, +suportando um tipo de desempacotamento mais poderoso, conhecido como desestruturação. + +O fatiamento de sequências é um dos recursos de sintaxe preferidos de Python, +e é ainda mais poderoso do que muita gente pensa. +Fatiamento multidimensional e a notação de reticências (`\...`), como usados no NumPy, +podem também ser suportados por sequências definidas pelo usuário. +Atribuir a fatias é uma forma muito expressiva de editar sequências mutáveis. + +Concatenação repetida, como em `seq * n`, é conveniente e, tomando cuidado, +pode ser usada para inicializar listas de listas contendo itens imutáveis. +Atribuição aumentada com `+=` e `*=` se comporta de forma diferente com sequências mutáveis e imutáveis. +No último caso, esses operadores necessariamente criam novas sequências. +Mas se a sequência alvo é mutável, ela em geral é modificada internamente—mas nem sempre, +depende de como a sequência é implementada. + +O método `sort` e a função embutida `sorted` são fáceis de usar e flexíveis, +graças ao argumento opcional `key`: uma função para calcular o critério de ordenação. +E aliás, `key` também pode ser usado com as funções embutidas `min` e `max`. + +Além de listas e tuplas, a biblioteca padrão de Python oferece `array.array`. +Apesar da NumPy e da SciPy não serem parte da biblioteca padrão, +se você faz qualquer tipo de processamento numérico em grandes conjuntos de dados, +estudar mesmo uma pequena parte dessas bibliotecas pode levar você muito longe. + +Terminamos com uma visita à versátil `collections.deque`, que é segura para usar com threads. +Comparamos sua API com a de `list` na <> +e mencionamos as outras implementações de filas na biblioteca padrão. + + +[[array_fur_reading_sec]] +=== Para saber mais + +O((("sequences", "further reading on"))) capítulo 1, "Data Structures" (_Estruturas de Dados_) +do «_Python Cookbook, 3rd ed._» [.small]#[fpy.li/pycook3]# +(O'Reilly), de David Beazley e Brian K. Jones, traz muitas receitas usando sequências, +incluindo a _Recipe 1.11. Naming a Slice_ (Receita 1.11. Nomeando uma Fatia), +onde aprendi o truque de atribuir fatias a variáveis para melhorar a legibilidade, +como ilustrado no <>. + +A segunda edição do _Python Cookbook_ foi escrita para Python 2.4, +mas a maior parte de seu código funciona com Python 3, +e muitas das receitas dos capítulos 5 e 6 lidam com sequências. +O livro foi editado por Alex Martelli, Anna Ravenscroft, e David Ascher, +e inclui contribuições de dúzias de pythonistas. +A terceira edição foi reescrita do zero, e se concentra mais na semântica da linguagem—especialmente +no que mudou no Python 3—enquanto o volume mais antigo enfatiza a pragmática +(isto é, como aplicar a linguagem a problemas da vida real). +Apesar de algumas das soluções da segunda edição não serem mais a melhor abordagem, +honestamente acho que vale a pena ter à mão as duas edições do _Python Cookbook_. + +O «HowTo—Ordenação» [.small]#[fpy.li/2v]# +oficial de Python tem vários exemplos de técnicas avançadas de uso de `sorted` e `list.sort`. + +A «_PEP 3132—Extended Iterable Unpacking_ (Desempacotamento de iteráveis estendido)» [.small]#[fpy.li/2-2]# +é a fonte canônica para ler sobre o novo uso da sintaxe `*extra` no lado esquerdo de atribuições paralelas. +Se você quiser dar uma olhada no processo de evolução de Python, +«_Missing *-unpacking generalizations_ (Generalizações esquecidas de * no desempacotamento)» [.small]#[fpy.li/2-24]# +é um tópico do bug tracker propondo melhorias na notação de desempacotamento iterável. +«_PEP 448—Additional Unpacking Generalizations_ (Generalizações de desempacotamento adicionais)» [.small]#[fpy.li/pep448]# +foi o resultado de discussões ocorridas naquele tópico. + +Como mencionei na <>, o texto introdutório +«Casamento de padrão estrutural» [.small]#[fpy.li/2r]#, +de Carol Willing, no +«O que há de novo no Python 3.10» [.small]#[fpy.li/2s]#, +é uma ótima introdução a esse novo grande recurso, em mais ou menos 1.400 palavras +(isso é menos de 5 páginas quando o Firefox converte o HTML em PDF). +«_PEP 636—Structural Pattern Matching: Tutorial_ (Casamento de Padrão Estrutural: Tutorial)» [.small]#[fpy.li/pep636]# +também é bom, porém mais longo. +A mesma PEP 636 inclui o +«_Appendix A—Quick Intro_ (Apêndice A-Introdução Rápida)» [.small]#[fpy.li/2-27]#. +Ele é menor que a introdução de Willing, porque omite as considerações gerais sobre os motivos pelos quais o +casamento de padrões é útil. +Se você precisar de mais argumentos para se convencer ou convencer outros de que o casamento de padrões +foi bom para o Python, leia as 22 páginas de +«_PEP 635—Structural Pattern Matching: Motivation and Rationale_ (Casamento de Padrão Estrutural: Motivação e Justificativa)» +[.small]#[fpy.li/pep635]#. + +O post de Eli Bendersky em seu blog, +«_Less copies in Python with the buffer protocol and memoryviews_ +(Menos cópias em Python, com o protocolo de buffer e mamoryviews)» [.small]#[fpy.li/2-28]# +inclui um pequeno tutorial sobre `memoryview`. + +Há muitos livros tratando da NumPy no mercado, e muitos não mencionam "NumPy" no título. +Dois exemplos são o «_Python Data Science Handbook_» [.small]#[fpy.li/2-29]#, +escrito por Jake VanderPlas e de acesso aberto, +e o «_Python for Data Analysis_» [.small]#[fpy.li/2-30]#, de Wes McKinney. + +"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 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, +após a refatoração de uma bela classe pythônica, usando um método gerador, +em uma pequena e feroz função que chama um par de funções de vetor da NumPy. + +Para aprender a usar `deque` (e outras coleções), veja os exemplos e as receitas práticas em +«Tipos de dados de contêineres» [.small]#[fpy.li/2w]#, +na documentação de Python. + +A melhor defesa da convenção de Python de excluir o último item na função `range` +e fatias foi escrita por Edsger W. Dijkstra, em uma nota curta intitulada +«_Why Numbering Should Start at Zero_ (Porque a Numeração Deve Começar em Zero)» [.small]#[fpy.li/2-32]#. +A nota é sobre notação matemática, mas é relevante para Python porque +Dijkstra explica, com humor e rigor, por que uma sequência como 2, 3, ..., 12 +deveria sempre ser expressa como 2 ≤ i < 13. +Todas as outras convenções razoáveis são refutadas, +bem como a ideia de deixar cada usuário escolher uma convenção. +O título se refere à indexação baseada em zero, mas a nota argumenta +por que é desejável que `'ABCDE'[1:3]` +signifique `'BC'` e não `'BCD'`, e por que faz todo sentido escrever +`range(2, 13)` para produzir 2, 3, 4, ..., 12. +E, por sinal, a nota foi escrita à mão, mas é linda e totalmente legível. +A letra de Dijkstra é tão cristalina que alguém criou uma «fonte» [.small]#[fpy.li/2-33]# +a partir de suas anotações. + +[[sequences_soapbox]] +.Ponto de Vista +**** + +[role="soapbox-title"] +*A natureza das tuplas* + +Em((("Soapbox sidebars", "tuples")))((("tuples", "nature of")))((("sequences", "Soapbox discussion", id="Ssoap02"))) +2012, apresentei um pôster sobre a linguagem ABC na PyCon US. +Antes de criar Python, Guido van Rossum tinha trabalhado no interpretador ABC, então ele veio ver meu pôster. +Entre outras coisas, falamos sobre como os _compounds_ (compostos) da ABC, +predecessores das tuplas de Python. +Compounds também suportam atribuição paralela e são usados como chaves compostas em dicionários +(ou "tabelas", no jargão da ABC). +Entretanto, compounds não são sequências: +não são iteráveis, e não é possível obter um campo por índice, muito menos fatiá-los. +Ou você manuseia o composto inteiro ou extrai os campos individuais usando atribuição paralela, e é isso. + +Falei para o Guido que essas limitações tornavam muito claro o principal propósito dos compounds: +são apenas registros sem campos nomeados. +Sua resposta: "Fazer as tuplas se comportarem como sequências foi uma gambiarra." +(_“Making tuples behave as sequences was a hack.”_) + +Isso ilustra a abordagem pragmática que tornou Python mais prático e mais bem sucedido que a ABC. +Da perspectiva de um implementador de linguagens, fazer as tuplas se comportarem como sequências custa pouco. +Como resultado, o principal caso de uso de tuplas como registros não é tão óbvio, +mas ganhamos listas imutáveis—mesmo que seu tipo não seja tão claramente nomeado como seria `frozenlist`. + + +[role="soapbox-title"] +*Sequências planas versus sequências contêineres* + +Usei((("Soapbox sidebars", "flat versus container sequences")))((("container sequences")))((("flat sequences"))) +os termos _sequência contêiner_ e _sequência plana_ para +realçar os diferentes modelos de memória em sequências. +A palavra "contêiner" vem da própria documentação do +«Modelo de Dados» [.small]#[fpy.li/2x]#: + +[quote] +____ +Alguns objetos contêm referências a outros objetos; eles são chamados de contêineres. +____ + +Usei o termo "sequência contêiner" para ser específico, +porque existem contêineres em Python que não são sequências, como `dict` e `set`. +Sequências contêineres podem ser aninhadas porque elas podem conter objetos de qualquer tipo, +incluindo seu próprio tipo. + +Por outro lado, _sequências planas_ são tipos de sequências que não podem ser aninhadas, +pois só podem conter valores atômicos como inteiros, números de ponto flutuante ou caracteres. + +Adotei o termo _sequência plana_ porque precisava de algo para contrastar com "sequência contêiner." + +Apesar do uso anterior da palavra "containers" na documentação oficial, +há uma classe abstrata em `collections.abc` chamada `Container`. +Aquela ABC tem apenas um método, `+__contains__+`—o método especial por trás do operador `in`. +Isso significa que arrays e strings, que não são contêineres no sentido tradicional, +são subclasses virtuais de `Container`, porque implementam `+__contains__+`. +Isso é só mais um exemplo de humanos usando uma mesma palavra para significar coisas diferentes. +Nesse livro, vou escrever "contêiner" com minúscula e em português para +"um objeto que contém referências para outros objetos" e +`Container` com a inicial maiúscula em fonte mono espaçada para me referir a `collections.abc.Container`. + + +[role="soapbox-title"] +*Listas bagunçadas* + +Textos((("Soapbox sidebars", "mixed-bag lists")))((("lists", "mixed-bag"))) +introdutórios de Python costumam enfatizar que listas podem conter objetos de diferentes tipos, +mas na prática esse recurso não é muito útil: colocamos itens em uma lista para processá-los mais tarde, +o que implica o suporte, da parte de todos os itens, a pelo menos alguma operação em comum +(isto é, eles devem todos "grasnar", independentemente de serem ou não 100% patos, geneticamente falando). +Por exemplo, não é possível ordenar uma lista em Python 3 a menos que os itens ali contidos sejam comparáveis: + +[source, python] +---- +>>> l = [28, 14, '28', 5, '9', '1', 0, 6, '23', 19] +>>> sorted(l) +Traceback (most recent call last): + File "", line 1, in +TypeError: unorderable types: str() < int() +---- + +Diferente das listas, as tuplas muitas vezes mantêm itens de tipos diferentes. +Isso é natural: se cada item em uma tupla é um campo, então cada campo pode ter um tipo diferente. + +[role="soapbox-title"] +*'key' é brilhante* + +O((("Soapbox sidebars", "key argument")))((("key argument")))((("arguments", "key argument"))) +argumento opcional `key` de `list.sort`, `sorted`, `max`, e `min` é uma grande ideia. +Outras linguagens forçam você a fornecer uma função de comparação com dois argumentos, +como a função descontinuada de Python 2 `cmp(a, b)`. +Usar `key` é mais simples e mais eficiente. +É mais simples porque basta definir uma função de um único argumento que recupera ou calcula +o critério a ser usado para ordenar seus objetos; +isso é mais fácil que escrever uma função de dois argumentos para devolver –1, 0, 1. +Também é mais eficiente, porque a função `key` é invocada apenas uma vez por item, +enquanto a comparação de dois argumentos é chamada a cada vez +que o algoritmo de ordenação precisa comparar dois itens. +Claro, Python também precisa comparar as chaves ao ordenar, +mas aquela comparação é feita em código C otimizado, não em uma função Python escrita por você. + +<<< +Por sinal, usando `key` podemos ordenar uma lista bagunçada de números e strings "parecidas com números". +Só precisamos decidir se queremos tratar todos os itens como inteiros ou como strings: + +[source, python] +---- +>>> l = [28, 14, '28', 5, '9', '1', 0, 6, '23', 19] +>>> sorted(l, key=int) +[0, '1', 5, 6, '9', 14, 19, '23', 28, '28'] +>>> sorted(l, key=str) +[0, '1', 14, 19, '23', 28, '28', 5, 6, '9'] +---- + + +[role="soapbox-title"] +*A Oracle, o Google, e a Conspiração Timbot* + +O((("Soapbox sidebars", "Oracle, Google, and the Timbot")))((("Timsort algorithm"))) +algoritmo de ordenação usado em `sorted` e `list.sort` é o Timsort, +um algoritmo adaptativo que troca de estratégia de ordenação (entre _merge sort_ e _insertion sort_), +dependendo de quão ordenados os dados já estão. +Isso é eficiente porque dados reais tendem a ter séries de itens ordenados. +Há um «artigo da Wikipedia» [.small]#[fpy.li/2y]# sobre ele. + +O Timsort foi usado no CPython pela primeira vez em 2002. +Desde 2009, o Timsort também é usado para ordenar arrays tanto em Java padrão quanto no Android, +um fato que ficou muito conhecido quando a Oracle usou parte do código relacionado ao Timsort +como evidência da violação da propriedade intelectual da Sun pelo Google. +Por exemplo, veja essa «ordem do Juiz William Alsup» [.small]#[fpy.li/2-36]# de 2012. +Em 2021, a Suprema Corte dos Estados Unidos decidiu que o uso do código de Java pelo Google é +"fair use"footnote:[NT: Conceito da lei de copyright norte-americana que permite, +em determinadas circunstâncias, o uso sem autorização prévia de partes da propriedade intelectual de outros. +Em geral traduzido como "uso razoável" ou "uso aceitável". +Esta doutrina não faz parte da lei brasileira.] + +O Timsort foi inventado por Tim Peters, um dos mantenedores de Python, +tão produtivo que se acredita que ele seja uma inteligência artificial, o Timbot. +Você pode ler mais sobre essa teoria da conspiração em «_Python Humor_» [.small]#[fpy.li/2-37]#. +Tim também escreveu "The Zen of Python": `import this`.((("", startref="Ssoap02"))) + +**** + +<<< \ No newline at end of file diff --git a/vol1/cap03.adoc b/vol1/cap03.adoc new file mode 100644 index 00000000..f1c69780 --- /dev/null +++ b/vol1/cap03.adoc @@ -0,0 +1,1749 @@ +[[ch_dicts_sets]] +== Dicionários e conjuntos +:example-number: 0 +:figure-number: 0 + +[quote, Lalo Martins, pioneiro do nomadismo digital e pythonista] +____ +Python é feito basicamente de dicionários cobertos por muitas camadas de açúcar sintático +____ + +Usamos dicionários em todos os nossos programas Python. +Se não diretamente em nosso código, então indiretamente, +pois o tipo `dict` é um elemento fundamental da implementação de Python. +Atributos de classes e de instâncias, +espaços de nomes de módulos e argumentos nomeados de funções são alguns dos +elementos fundamentais de Python representados na memória por dicionários. +O `+__builtins__.__dict__+` armazena todos os tipos, funções e objetos embutidos. + +Por seu papel crucial, os dicts de Python são extremamente otimizados—e continuam recebendo melhorias. +As _tabelas de hash_((("hash tables"))) são o motor por trás do alto desempenho dos dicts de Python. + +Outros tipos embutidos baseados em tabelas de hash são `set` e `frozenset`. +Eles oferecem uma API mais completa e operadores mais robustos que +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 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 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 +* Os tipos `set` e `frozenset` +* As implicações das tabelas de hash no comportamento de conjuntos e dicionários. + + +=== Novidades neste capítulo + +A((("dictionaries and sets", "significant changes to"))) maior parte das mudanças nessa +segunda edição se concentra em novos recursos relacionados a tipos de mapeamento: + +* A <> fala da sintaxe aperfeiçoada de desempacotamento +e de diferentes maneiras de mesclar mapeamentos—incluindo os operadores `|` e `|=`, suportados pelos `dicts` desde o Python 3.9. +* A <> ilustra o manuseio de mapeamentos com `match/case`, recurso que surgiu no Python 3.10. +* A <> agora se concentra nas pequenas, mas ainda relevantes, diferenças entre +`dict` e `OrderedDict`—levando em conta que, desde o Python 3.6, `dict` passou a manter a ordem de inserção das chaves. +* Novas seções sobre os objetos view devolvidos por `dict.keys`, `dict.items`, e `dict.values`: +a <> e a <>. + +A implementação interna de `dict` e `set` ainda está alicerçada em tabelas de hash, +mas o código de `dict` teve duas otimizações importantes, +que economizam memória e preservam a ordem de inserção das chaves. +A <> e a <> +resumem o que você precisa saber sobre isso para usar bem essas estruturas. + +[NOTE] +==== +Após((("dictionaries and sets", "internals of"))) acrescentar mais de 200 páginas a essa segunda edição, transferi a seção opcional +"Internals of sets and dicts" (_As entranhas dos sets e dos dicts_) +para o http://fluentpython.com, o site que complementa o livro. +Atualizei e expandi «um texto de 18 páginas» [.small]#[fpy.li/hashint]# com diagramas e explicações sobre: + +* O algoritmo de tabela de hash, começando por seu uso em `set`, que é mais simples de entender. +* A otimização de memória que preserva a ordem de inserção de chaves em objetos `dict` (desde o Python 3.6) . +* O layout do compartilhamento de chaves em dicionários que mantêm atributos de +instância, o `+__dict__+` de objetos definidos pelo usuário (otimização implementada no Python 3.3). +==== + + +[[modern_dict_syntax_sec]] +=== A sintaxe moderna dos dicts + +As((("dictionaries and sets", "modern dict syntax", id="DASsyntax03"))) próximas seções descrevem +os recursos avançados de sintaxe para criação, desempacotamento e processamento de mapeamentos. +Alguns desses recursos não são novos na linguagem, mas podem ser novidade para você. +Outros requerem Python 3.9 (como o operador `|`) ou Python 3.10 (como `match/case`). +Vamos começar por um dos melhores e mais antigos desses recursos. + +[[dictcomp_sec]] +==== Compreensões de dict + +Desde((("dictcomps (dict comprehensions)"))) Python 2.7, a sintaxe das listcomps e genexps foi adaptada para compreensões de `dict` +(e também compreensões de `set`, que veremos em breve). +Uma _dictcomp_ (compreensão de dict) cria uma instância de `dict`, recebendo pares `key:value` de qualquer iterável. +O <> mostra o uso de compreensões de `dict` para criar dois dicionários a partir de uma mesma lista de tuplas. + +[[example3-1]] +.Exemplos de compreensões de `dict` +==== +[source, python] +---- +>>> dial_codes = [ # <1> +... (880, 'Bangladesh'), +... (55, 'Brazil'), +... (86, 'China'), +... (91, 'India'), +... (62, 'Indonesia'), +... (81, 'Japan'), +... (234, 'Nigeria'), +... (92, 'Pakistan'), +... (7, 'Russia'), +... (1, 'United States'), +... ] +>>> country_dial = {country: code for code, country in dial_codes} # <2> +>>> country_dial +{'Bangladesh': 880, 'Brazil': 55, 'China': 86, 'India': 91, + 'Indonesia': 62, 'Japan': 81, 'Nigeria': 234, 'Pakistan': 92, + 'Russia': 7, 'United States': 1} +>>> {code: country.upper() for country, code # <3> +... in sorted(country_dial.items()) +... if code < 70} +{55: 'BRAZIL', 62: 'INDONESIA', 7: 'RUSSIA', 1: 'UNITED STATES'} +---- +==== +<1> Um iterável de pares chave-valor como `dial_codes` pode ser passado diretamente para o construtor de `dict`, mas... +<2> ...aqui permutamos os pares: `country` é a chave, e `code` é o valor. +<3> Ordenando `country_dial` por nome, revertendo novamente os pares, colocando os valores em maiúsculas e filtrando os itens com `code < 70`. + +Se você já usa listcomps, as dictcomps são um próximo passo natural. +Caso contrário, a propagação da sintaxe de compreensão mostra que agora é mais valioso que nunca se tornar fluente nessa técnica. + + +[[dict_unpacking_sec]] +==== Desempacotando mapeamentos + +A «_PEP 448—Additional Unpacking Generalizations_ (Generalizações de desempacotamento adicionais)» [.small]#[fpy.li/pep448]# +melhorou o suporte ao desempacotamento de mapeamentos((("unpacking", "mapping unpackings")))((("mappings", "unpacking"))) +de duas formas, desde o Python 3.5. + +Primeiro, podemos((("** (double star) operator")))((("double star (**) operator"))) +aplicar `**` a mais de um argumento em uma chamada de função. +Isso funciona quando todas as chaves são strings e únicas, +para todos os argumentos (porque argumentos nomeados duplicados são proibidos): + +[source, python] +---- +>>> def dump(**kwargs): +... return kwargs +... +>>> dump(**{'x': 1}, y=2, **{'z': 3}) +{'x': 1, 'y': 2, 'z': 3} +---- + +Em segundo lugar, `**` pode ser usado dentro de um literal `dict`—também múltiplas vezes: + +[source, python] +---- +>>> {'a': 0, **{'x': 1}, 'y': 2, **{'z': 3, 'x': 4}} +{'a': 0, 'x': 4, 'y': 2, 'z': 3} +---- + +Nesse caso, chaves duplicadas são permitidas. +Cada ocorrência sobrescreve ocorrências anteriores—observe o valor mapeado para `x` no exemplo. + +Essa sintaxe também pode ser usada para mesclar mapas, mas isso pode ser feito de outras formas. +Siga comigo. + +==== Fundindo mapeamentos com | + +Desde a versão 3.9, Python((("mappings", +"merging")))((("ǀ (pipe) operator")))((("pipe (ǀ) operator")))((("ǀ= (pipe equals) operator")))((("pipe equals (ǀ=) operator"))) +suporta o uso de `|` e `|=` para mesclar mapeamentos. +Isso faz todo sentido, +já que estes são também os operadores de união de conjuntos. + +O operador `|` cria um novo mapeamento: + +[source, python] +---- +>>> d1 = {'a': 1, 'b': 3} +>>> d2 = {'a': 2, 'b': 4, 'c': 6} +>>> d1 | d2 +{'a': 2, 'b': 4, 'c': 6} +---- + +Normalmente, o tipo do novo mapeamento será o mesmo do operando da esquerda—no exemplo, +`d1`—mas ele pode ser do tipo do segundo operando se tipos definidos pelo usuário estiverem envolvidos na operação, +dependendo das regras de sobrecarga de operadores, +que veremos no «Capítulo 16» [.small]#[vol.2, fpy.li/16]#. + +Para atualizar mapeamentos existentes internamente, use `|=`. +Retomando o exemplo anterior, ali `d1` não foi modificado. +Mas aqui sim: + +[source, python] +---- +>>> d1 +{'a': 1, 'b': 3} +>>> d1 |= d2 +>>> d1 +{'a': 2, 'b': 4, 'c': 6} +---- + +[TIP] +==== +Se você precisa manter código rodando no Python 3.8 ou anterior, a seção +«_Motivation_» [.small]#[fpy.li/3-1]# da +«_PEP 584—Add Union Operators To dict_ (Acrescentar operadores de união a dict)» [.small]#[fpy.li/pep584]# +tem um bom resumo das outras formas de mesclar mapeamentos. +==== + +Agora vamos ver como o casamento de padrões se aplica aos mapeamentos.((("", startref="DASsyntax03"))) + +[[pattern_matching_mappings_sec]] +=== Pattern matching com mapeamentos + +A((("pattern matching", "with mappings", secondary-sortas="mappings", +id="PMmap03")))((("mappings", "pattern matching with", +id="Mpattern03")))((("match/case statement")))((("dictionaries and sets", "pattern matching with mappings", +id="DASmap03"))) +instrução `match/case` suporta sujeitos que sejam objetos de mapeamento. +Padrões para mapeamentos se parecem com literais `dict`, +mas podem casar com instâncias de qualquer subclasse real ou virtual de `collections.abc.Mapping`.footnote:[Uma +subclasse virtual é +qualquer classe registrada com uma chamada ao método `.register()` de uma ABC, +como explicado na «Seção 13.5.6» [vol.2, fpy.li/25]. +Um tipo implementado através da API Python/C também serve, se tiver um bit de marcação específico setado no header. +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 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, +como o MongoDB, o EdgeDB, ou o PostgreSQL. +O <> demonstra isso. + +As dicas de tipo simples em `get_creators` tornam claro que ela recebe um `dict` e devolve uma `list`. + +[[dict_match_ex]] +.creator.py: `get_creators()` extrai o nome dos criadores em registros de mídia +==== +[source, python] +---- +include::../code/03-dict-set/py3.10/creator.py[tags=DICT_MATCH] +---- +==== +[role="pagebreak-before less_space"] +<1> Casa com qualquer mapeamento na forma `'type': 'book', 'api' :2`, e uma chave `'authors'` +mapeada para uma sequência. +Devolve os itens da sequência, como uma nova `list`. +<2> Casa com qualquer mapeamento na forma `'type': 'book', 'api' :1`, e uma chave `'author'` +mapeada para qualquer objeto. +Devolve aquele objeto dentro de uma `list`. +<3> Qualquer outro mapeamento na forma `'type': 'book'` é inválido e gera um `ValueError`. +<4> Casa qualquer mapeamento na forma `'type': 'movie'` e uma chave `'director'` +mapeada para um único objeto. +Devolve o objeto dentro de uma `list`. +<5> Qualquer outro sujeito é inválido e gera um `ValueError`. + +O <> mostra algumas práticas úteis para lidar com dados semi-estruturados, como registros JSON: + +* Incluir um campo descrevendo o tipo de registro (por exemplo, `'type': 'movie'`) +* Incluir um campo identificando a versão do schema (por exemplo, `'api': 2'`), +para permitir evoluções futuras das APIs públicas. +* Ter cláusulas `case` para processar registros inválidos de um tipo específico (por exemplo, `'book'`), +bem como um `case` final para capturar tudo que tenha passado pelas condições anteriores. + +Agora vamos ver como `get_creators` se comporta com alguns doctests concretos: + +[source, python] +---- +include::../code/03-dict-set/py3.10/creator.py[tags=DICT_MATCH_TEST] +---- + +Observe que a ordem das chaves nos padrões é irrelevante, mesmo se o sujeito for um `OrderedDict` como `b2`. + +Diferente de patterns de sequência, patterns de mapeamento funcionam com matches parciais. +Nos doctests, os sujeitos `b1` e `b2` incluem uma chave `'title'`, +que não aparece em nenhum padrão `'book'`, mas mesmo assim casam. + +Não há necessidade de usar `+**extra+` para casar pares chave-valor adicionais, +mas se você quiser capturá-los como um `dict`, pode prefixar uma variável com `**`. +Ela precisa ser a última do padrão, e `+**_+` é proibido, pois seria redundante. +Um exemplo simples: + + +[source, python] +---- +>>> food = dict(category='ice cream', flavor='vanilla', cost=199) +>>> match food: +... case {'category': 'ice cream', **details}: +... print(f'Ice cream details: {details}') +... +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 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 +`sentinel` default é um marcador com valor especial, que não pode aparecer nos dados do usuário. +==== + +Vistas a sintaxe e a estrutura, vamos estudar a API dos mapeamentos.((("", +startref="PMmap03")))((("", startref="Mpattern03")))((("", startref="DASmap03"))) + + +=== A API padrão dos tipos de mapeamentos + +O((("dictionaries and sets", "standard API of mapping types", id="DASapi03")))((("mappings", +"standard API of mapping types", id="Mapi03")))((("collections.abc module", +"Mapping and MutableMapping ABCs")))((("MutableMapping ABC"))) +módulo `collections.abc` contém as ABCs `Mapping` e `MutableMapping`, +descrevendo as interfaces de `dict` e de tipos similares. +Veja a <>.((("UML class diagrams", "simplified for MutableMapping and superclasses"))) +A maior utilidade dessas ABCs é documentar e formalizar as interfaces padrão para os mapeamentos, +e servir de critério para testes com `isinstance` em código que precise suportar mapeamentos de forma geral: + +[source, python] +---- +>>> my_dict = {} +>>> isinstance(my_dict, abc.Mapping) +True +>>> isinstance(my_dict, abc.MutableMapping) +True +---- + +[TIP] +==== +Usar `isinstance` com uma ABC é muitas vezes melhor que checar se um argumento de função +é do tipo concreto `dict`, porque daí tipos alternativos de mapeamentos podem ser usados. +Vamos discutir isso em detalhes no «Capítulo 13» [.small]#[vol.2, fpy.li/13]#. +==== + +[[mapping_uml]] +.Diagrama de classe simplificado para `MutableMapping` e suas superclasses de `collections.abc` (as setas de herança apontam das subclasses para as superclasses; nomes em itálico indicam classes e métodos abstratos +image::../images/flpy_0301.png[Diagrama de classes UML para `Mapping` e `MutableMapping`] + +Para implementar um mapeamento customizado, é mais fácil estender `collections.UserDict`, +ou envolver um `dict` por composição, ao invés de criar uma subclasse dessas ABCs. +A classe `collections.UserDict` e todas as classes concretas de mapeamentos da biblioteca padrão +encapsulam o `dict` básico em suas implementações, que por sua vez é criado sobre uma tabela de hash. +Assim, todas elas compartilham a mesma limitação: +as((("keys", "hashability"))) chaves precisam ser _hashable_ +(os valores não precisam ser _hashable_, só as chaves). +Se você precisa de uma recapitulação, a próxima seção explica isso. + +<<< + +[[what_is_hashable_sec]] +==== O que é hashable? + +Aqui((("hashable, definition of"))) está parte da definição da palavra _hashable_, +adaptada do «Glossário de Python» [.small]#[fpy.li/32]#: + +[quote] +____ +Um objeto é _hashable_ se tem um código de hash que nunca muda durante seu ciclo de vida +(precisa ter um método `+__hash__+`) e pode ser comparado com outros objetos +(precisa ter um método `+__eq__+`). +Objetos _hashable_ que são comparados como iguais devem ter o mesmo código de hash.footnote:[O +verbete para "hashable" no +«_Glossário_ de Python» [.small]#[fpy.li/32]# +usa o termo "valor de hash" em vez de((("hash code, versus hash value"))) _código de hash_. +Prefiro _código de hash_ porque "valor" é um conceito frequentemente usado no contexto de mapeamentos, +onde itens são compostos de chaves e valores. +Então pode ser confuso se referir ao código de hash como um valor. +Nesse livro usarei apenas _código de hash_.] +____ + +Tipos numéricos((("numeric types", "hashability of"))) e os tipos planos imutáveis `str` e `bytes` são todos _hashable_. +Tipos contêineres são _hashable_ se forem imutáveis e se todos os objetos por eles contidos forem também _hashable_. +Um `frozenset` é sempre _hashable_, pois todos os elementos que ele contém são _hashable_, por definição. +Uma `tuple` é _hashable_ apenas se todos os seus itens também forem. +Observe as tuplas `tt`, `tl`, e `tf`: + +[source, python] +---- +>>> tt = (1, 2, (30, 40)) +>>> hash(tt) +8027212646858338501 +>>> tl = (1, 2, [30, 40]) +>>> hash(tl) +Traceback (most recent call last): + File "", line 1, in +TypeError: unhashable type: 'list' +>>> tf = (1, 2, frozenset([30, 40])) +>>> hash(tf) +-4118419923444501110 +---- + +O código de hash de um objeto pode ser diferente dependendo da versão de Python, da arquitetura da máquina, +e pelo((("salt"))) _sal_ acrescentado ao cálculo do hash por razões de segurança.footnote:[Veja a +«_PEP 456—Secure and interchangeable hash algorithm_ (Algoritmo de hash seguro e intercambiável) [fpy.li/pep456] +para saber mais sobre as implicações de segurança e as soluções adotadas.] +O código de hash de um objeto corretamente implementado tem a garantia de ser constante apenas dentro de um processo Python. + +Tipos definidos pelo usuário são _hashable_ por default, pois seu código de hash é seu `id()`, e o método `+__eq__()+` +herdado da classe `object` apenas compara os IDs dos objetos. +Se um objeto implementar seu próprio `+__eq__()+`, que leve em consideração seu estado interno, +ele será _hashable_ apenas se seu `+__hash__()+` sempre devolver o mesmo código de hash. +Na prática, isso exige que `+__eq__()+` e `+__hash__()+` levem em conta apenas +atributos de instância que nunca mudem durante a vida do objeto. + +Vamos agora revisar a API dos tipos de mapeamento mais comumente usados no Python: `dict`, `defaultdict`, e `OrderedDict`. + + +==== Revisão dos métodos mais comuns dos mapeamentos + +A((("defaultdict")))((("OrderedDict")))((("collections.abc module", "defaultdict and OrderedDict"))) +API básica para mapeamentos é bem completa. +A <> mostra os métodos implementados por `dict` e pelas variantes mais usadas: +`defaultdict` e `OrderedDict`, ambas classes definidas no módulo `collections`. + +[role="pagebreak-before less_space"] +[[mapping_methods_tbl]] +.Métodos de `dict`, `collections.defaultdict`, e `collections.OrderedDict` (métodos comuns de `object` omitidos por concisão); argumentos opcionais então entre `[…]` +[options="header", cols="8,^2,^3,^4,14"] +|=========================================================================================================================================================================================================================================================================== +| |dict |default dict |Ordered Dict |   +| `d.clear()` | ● | ● | ● | Remove todos os itens. +| `+d.__contains__(k)+` | ● | ● | ● | `k in d` +| `d.copy()` | ● | ● | ● | Cópia rasa +| `+d.__copy__()+` | | ● | | Suporte a `copy.copy(d)`. +| `d.default_factory` | | ● | | Invocável que `+__missing__+` utiliza para criar valores ausentesfootnote:[`default_factory` não é um método, mas um atributo invocável definido pelo usuário quando um `defaultdict` é instanciado.] +| `+d.__delitem__(k)+` | ● | ● | ● | `del d[k]`—remove item com chave `k` +| `d.fromkeys(it, [initial])` | ● | ● | ● | Novo mapeamento com chaves no iterável `it`, com um valor inicial opcional (o default é `None`) +| `d.get(k, [default])` | ● | ● | ● | Obtém item com chave `k`, devolve `default` ou `None` se `k` não existir +| `+d.__getitem__(k)+` | ● | ● | ● | ++d[k]++—obtém item com chave `k` +| `d.items()` | ● | ● | ● | Obtém uma _view_ dos itens—pares `(chave, valor)` +| `+d.__iter__()+` | ● | ● | ● | Obtém iterador das chaves +| `d.keys()` | ● | ● | ● | Obtém _view_ das chaves +| `+d.__len__()+` | ● | ● | ● | `len(d)`—quantidade de itens +| `+d.__missing__(k)+` | | ● | | Chamado quando `+__getitem__+` não consegue encontrar a chave +| `d.move_to_end(k, [last])` | | | ● | Move `k` para a primeira ou última posição (`last` é `True` por default). +| `+d.__or__(other)+` | ● | ● | ● | Suporte a `d1 \| d2` para criar um novo `dict`, fundindo `d1` e `d2` (Python ≥ 3.9) +| `+d.__ior__(other)+` | ● | ● | ● | Suporte a `d1 \|= d2` para atualizar `d1` com `d2` (Python ≥ 3.9) +| `d.pop(k, [default])` | ● | ● | ● | Remove e devolve valor em `k`, ou `default` ou `None`, se `k` não existir +| `d.popitem()` | ● | ● | ● | Remove e devolve, na forma `(chave, valor)`, o último item inseridofootnote:[`OrderedDict.popitem(last=False)` remove o primeiro item inserido (FIFO). O argumento nomeado `last` não é suportado por `dict` ou `defaultdict`, pelo menos até Python 3.10b3.] +| `+d.__reversed__()+` | ● | ● | ● | Suporte a `reverse(d)`—devolve um iterador de chaves, da última para a primeira a serem inseridas +| `+d.__ror__(other)+` | ● | ● | ● | Suporte a `other \| dd`—operador de união reverso (Python ≥ 3.9)footnote:[Operadores reversos são tratados no «Capítulo 16» [.small]#[vol.2, fpy.li/16].]#] +|`d.setdefault(k, [default])` | ● | ● | ● | Se `k in d`, devolve `d[k]`; senão, atribui `d[k] = default` e devolve isso +| `+d.__setitem__(k, v)+` | ● | ● | ● | `d[k] = v`—coloca `v` em `k` +| `d.update(m, [**kwargs])` | ● | ● | ● | Atualiza `d` com itens de um mapeamento ou iterável de pares `(chave, valor)` +| `d.values()` | ● | ● | ● | Obtém uma _view_ dos valores +|=========================================================================================================================================================================================================================================================================== + +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` 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)`. +O construtor da maioria dos mapeamentos de Python usa internamente a lógica de `update()`, +o que quer dizer que eles podem ser inicializados por outros mapeamentos +ou a partir de qualquer objeto iterável que produza pares `(chave, valor)`. + +Um método sutil dos mapeamentos é `setdefault()`. +Ele evita buscas redundantes de chaves quando precisamos atualizar o valor em um item no mesmo lugar. +A próxima seção mostra como ele pode ser usado. + +==== Inserindo ou atualizando valores mutáveis + +Alinhada((("fail-fast philosophy")))((("defensive programming")))((("mutable values, inserting or updating", id="MVinsert03"))) +à filosofia de _falhar rápido_ de Python, a consulta a um `dict` com `d[k]` gera um erro quando `k` não é uma chave existente. +Pythonistas sabem que `d.get(k, default)` é uma alternativa a `d[k]` quando receber um valor default é +mais conveniente que tratar um `KeyError`. +Entretanto, se você está buscando um valor mutável e quer atualizá-lo, há um jeito melhor. + +Considere um script para indexar texto, produzindo um mapeamento no qual cada chave é uma palavra, +e o valor é uma lista das posições onde aquela palavra ocorre, como mostrado no <>. + +[[index0_output_ex]] +.Saída parcial do <> processando o texto "Zen of Python"; cada linha mostra uma palavra e uma lista de ocorrências na forma de pares `(line_number, column_number)` (número da linha, número da coluna). +==== +[source] +---- +$ python3 index0.py zen.txt +a [(19, 48), (20, 53)] +Although [(11, 1), (16, 1), (18, 1)] +ambiguity [(14, 16)] +and [(15, 23)] +are [(21, 12)] +aren [(10, 15)] +at [(16, 38)] +bad [(19, 50)] +be [(15, 14), (16, 27), (20, 50)] +beats [(11, 23)] +Beautiful [(3, 1)] +better [(3, 14), (4, 13), (5, 11), (6, 12), (7, 9), (8, 11), (17, 8), (18, 25)] +---- +==== + +O <> é um script aquém do ideal, para mostrar um caso em que `dict.get` +não é a melhor maneira de lidar com uma chave ausente. +Ele foi adaptado de um exemplo de Alex Martelli.footnote:[O script original aparece no slide 41 da +apresentação de Martelli, «"Re-learning Python" (_Reaprendendo Python_)» [.small]#[fpy.li/3-5]#. +O script é, na verdade, uma demonstração de `dict.setdefault`, como visto no nosso <>.] + +[[index0_ex]] +.index0.py usa `dict.get` para obter e atualizar uma lista de ocorrências de palavras de um índice (uma solução melhor é apresentada no <>) +==== +[source, python] +---- +include::../code/03-dict-set/index0.py[tags=INDEX0] +---- +==== +<1> Obtém a lista de ocorrências de `word`, ou `[]` se a palavra não for encontrada. +<2> Acrescenta uma nova localização a `occurrences`. +<3> Coloca a `occurrences` modificada no dict `index`; isso exige uma segunda busca em `index`. +<4> Não estou chamando `str.upper` no argumento `key=` de `sorted`, apenas passando uma referência àquele método, +para que a função `sorted` possa usá-lo para normalizar as palavras antes de ordená-las.footnote:[Isso +é um exemplo do uso de um método como uma função de primeira classe, o assunto do <>.] + +As três linhas tratando de `occurrences` no <> podem ser substituídas por uma linha +usando `dict.setdefault`. O <> fica mais próximo do código apresentado por Alex Martelli. + +[[index_ex]] +.index.py usa `dict.setdefault` para obter e atualizar uma lista de ocorrências de uma palavra em uma linha de código; compare com o <> +==== +[source, python] +---- +include::../code/03-dict-set/index.py[tags=INDEX] +---- +==== +<1> Obtém a lista de ocorrências de `word`, ou a define como `[]`, se não for encontrada; +`setdefault` devolve o valor, então ele pode ser atualizado sem uma segunda busca. + +Em outras palavras, o resultado final desta linha... + +[source, python] +---- +my_dict.setdefault(key, []).append(new_value) +---- + +...é o mesmo que executar... + +[source, python] +---- +if key not in my_dict: + my_dict[key] = [] +my_dict[key].append(new_value) +---- + +...exceto que este último trecho de código executa pelo menos duas buscas por `key`—três se +a chave não for encontrada—enquanto `setdefault` faz tudo isso com uma única busca. + +Uma questão relacionada, o tratamento de chaves ausentes em qualquer busca (e não apenas para inserção de valores), +é o assunto da próxima seção.((("", startref="MVinsert03")))((("", startref="Mapi03")))((("", startref="DASapi03"))) + +[[mappings_flexible_sec]] +=== Tratamento automático de chaves ausentes + +Algumas vezes((("dictionaries and sets", "automatic handling of missing keys", id="DASmissing03")))((("keys", +"automatic handling of missing", id="Kauto03")))((("mappings", "automatic handling of missing keys", id="Mauto03"))) +é conveniente que os mapeamentos devolvam algum valor padronizado quando se busca por uma chave ausente. +Há duas abordagens principais para esse fim: uma é usar um `defaultdict` em vez de um `dict` simples. +A outra é criar uma subclasse de `dict` ou de qualquer outro tipo de mapeamento e acrescentar um método `+__missing__+`. +Vamos ver as duas soluções a seguir. + +[[defaultdict_sec]] +==== defaultdict: outra perspectiva sobre as chaves ausentes + +Uma instância de `collections.defaultdict`((("defaultdict"))) cria itens com um valor default sob demanda, +sempre que uma chave ausente é buscada usando a sintaxe `d[k]`. +O <> usa `defaultdict` para fornecer outra solução elegante para o índice de palavras do <>. + +Funciona assim: ao instanciar um `defaultdict`, +você fornece um invocável que produz um valor default sempre que `+__getitem__+` recebe uma chave inexistente como argumento. + +Por exemplo, dado um `defaultdict` criado por `dd = defaultdict(list)`, +se `'new-key'` não estiver em `dd`, a expressão `dd['new-key']` segue os seguintes passos: + +. Chama `list()` para criar uma nova lista. +. Insere a lista em `dd` usando `'new-key'` como chave. +. Devolve uma referência para aquela lista. + +O invocável que produz os valores default é mantido em um atributo de instância chamado `default_factory`. + +[[index_default_ex]] +.index_default.py: usando um `defaultdict` em vez do método `setdefault` +==== +[source, python] +---- +include::../code/03-dict-set/index_default.py[tags=INDEX_DEFAULT] +---- +==== +<1> Cria um `defaultdict` com o construtor de `list` como `default_factory`. +<2> Se `word` não está inicialmente no `index`, o `default_factory` é chamado para +produzir o valor ausente, que neste caso é uma `list` vazia, +que então é atribuída a `index[word]` e devolvida, de forma que a operação +`.append(location)` é sempre bem sucedida. + +Se nenhum `default_factory` é fornecido, chaves ausentes geram `KeyError`, como esperado. + +<<< +[WARNING] +==== +O `default_factory` de um `defaultdict` só é invocado para fornecer valores default para chamadas a `+__getitem__+`, +não para outros métodos. +Por exemplo, se `dd` é um `defaultdict` e `k` uma chave ausente, `dd[k]` chamará `default_factory` +para criar um valor default, +mas `dd.get(k)` vai devolver `None`, e `k in dd` resulta `False`. +==== + +O mecanismo que faz `defaultdict` funcionar, chamando `default_factory`, +é o método especial `+__missing__+`, apresentado a seguir. + + +[[missing_method_sec]] +==== O método `+__missing__+` + +Por((("__missing__", id="missing03"))) trás da forma como os mapeamentos +lidam com chaves ausentes está o método muito apropriadamente chamado `+__missing__+`.footnote:[NT: +"Missing" significa ausente, faltando.] +Esse método não é definido na classe base `dict`, mas `dict` está ciente de sua possibilidade: +se você criar uma subclasse de `dict` e incluir um método `+__missing__+`, o `+dict.__getitem__+` +padrão vai chamar seu método sempre que uma chave não for encontrada, em vez de gerar um `KeyError`. + +Suponha que você queira um mapeamento onde as chaves são convertidas para `str` quando são procuradas. +Um caso de uso concreto seria uma biblioteca para dispositivos IoT +(Internet of Things, _Internet das Coisas_)footnote:[Uma biblioteca dessas é a +«_Pingo.io_» [.small]#[fpy.li/3-6]#, que não está mais em desenvolvimento ativo.], +onde uma placa programável com portas genéricas programáveis (por exemplo, uma Raspberry Pi ou uma Arduino) +é representada por uma classe "Placa" com um atributo `minha_placa.portas`, +que é um mapeamento dos identificadores das portas físicas para objetos de software portas. +O identificador da porta física pode ser um número ou uma string como `"A0"` ou `"P9_12"`. +Por consistência, é desejável que todas as chaves em `placa.portas` sejam strings, +mas também é conveniente buscar uma porta por número, como em `meu-arduino.porta[13]`, +para evitar que iniciantes tropecem quando quiserem fazer piscar o LED na porta 13 de seus Arduinos. +O <> mostra como tal mapeamento funcionaria. + +<<< +[[ex_strkeydict0_tests]] +.Ao buscar por uma chave não-string, `StrKeyDict0` a converte para `str` quando ela não é encontrada +==== +[source, python] +---- +include::../code/03-dict-set/strkeydict0.py[tags=STRKEYDICT0_TESTS] +---- +==== + +O <> implementa a classe `StrKeyDict0`, que passa nos doctests acima. + +[TIP] +==== +Uma forma melhor de criar um mapeamento definido pelo usuário é criar uma subclasse de +`collections.UserDict` em vez de `dict` (como faremos no <>). +Aqui criamos uma subclasse de `dict` apenas para mostrar que `+__missing__+` +é suportado pelo método embutido `+dict.__getitem__+`. +==== + +[[ex_strkeydict0]] +.`StrKeyDict0` converte chaves não-string para string no momento da consulta (veja os testes no <>) +==== +[source, python] +---- +include::../code/03-dict-set/strkeydict0.py[tags=STRKEYDICT0] +---- +==== +<1> `StrKeyDict0` herda de `dict`. +<2> Verifica se `key` já é uma `str`. Se é, e está ausente, gera um `KeyError`. +<3> Cria uma `str` de `key` e a procura. +<4> O método `get` delega para `+__getitem__+` usando a notação `self[key]`; +isso dá oportunidade para nosso `+__missing__+` agir. +<5> Se um `KeyError` foi gerado, `+__missing__+` já falhou, então devolvemos o `default`. +<6> Procura pela chave não-modificada (a instância pode conter chaves não-`str`), +depois por uma `str` criada a partir da chave. + +Considere por um momento o motivo do teste `isinstance(key, str)` ser necessário na implementação de `+__missing__+`. + +Sem aquele teste, nosso método `+__missing__+` funcionaria bem com qualquer chave `k`—`str` ou não—sempre que `str(k)` +produzisse uma chave existente. +Mas se `str(k)` não for uma chave existente, teríamos uma recursão infinita. +Na última linha de `+__missing__+`, `self[str(key)]` chamaria `+__getitem__+`, +passando aquela chave `str`, e `+__getitem__+`, por sua vez, chamaria +`+__missing__+` novamente. + +<<< +O((("__contains__"))) método `+__contains__+` também é necessário para que +o comportamento nesse exemplo seja consistente, pois a operação `k in d` o chama, +mas o método herdado de `dict` não invoca `+__missing__+` com chaves ausentes. +Há um detalhe sutil em nossa implementação de `+__contains__+`: +não verificamos a existência da chave da forma pythônica normal—`k in d`—porque `str(key) in self` chamaria +`+__contains__+` recursivamente. +Evitamos isso procurando a chave explicitamente em `self.keys()`. + +Uma busca como `k in my_dict.keys()` é eficiente em Python 3 mesmo para mapeamentos muito grandes, +porque `dict.keys()` devolve uma view, que é similar a um _set_, como veremos na <>. +Entretanto, lembre-se de que `k in my_dict` faz o mesmo trabalho, +e é mais rápido porque evita a busca nos atributos para encontrar o método `.keys`. + +Eu tinha uma razão específica para usar `self.keys()` no método `+__contains__+` do +<>. +A verificação da chave não-modificada `++key in self.keys()++` é necessária por correção, +pois `StrKeyDict0` não obriga todas as chaves no dicionário a serem do tipo `str`. +Nosso único objetivo com esse exemplo simples foi fazer a busca "mais amigável", +e não forçar tipos. + +[WARNING] +==== +Classes definidas pelo usuário derivadas de mapeamentos da biblioteca padrão podem ou não usar +`+__missing__+` como alternativa em sua implementação de `+__getitem__+`, `get`, ou `+__contains__+`, +como explicado na próxima seção. +==== + +[[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: + +subclasse de `dict`:: + Uma subclasse de `dict` que implemente apenas `+__missing__+` e nenhum outro método. + Nesse caso, `+__missing__+` pode ser chamado apenas em `d[k]`, que usará o `+__getitem__+` herdado de `dict`. + +<<< + +subclasse de `collections.UserDict`:: + Da mesma forma, uma subclasse de `UserDict` que implemente apenas `+__missing__+` e nenhum outro método. + O método `get` herdado de `UserDict` chama `+__getitem__+`. + Isso significa que `+__missing__+` pode ser chamado para tratar de consultas com `d[k]` e com + `d.get(k)`. + +subclasse de `abc.Mapping` com o `+__getitem__+` mais simples possível:: + Uma subclasse mínima de `abc.Mapping`, implementando `+__missing__+` e os métodos abstratos obrigatórios, + incluindo uma implementação de `+__getitem__+` que não chama `+__missing__+`. + O método `+__missing__+` nunca é acionado nessa classe. + +subclasse de `abc.Mapping` com `+__getitem__+` chamando `+__missing__+`:: + Uma subclasse mínima de `abc.Mapping`, implementando `+__missing__+` e os métodos abstratos obrigatórios, + incluindo uma implementação de `+__getitem__+` que chama `+__missing__+`. + O método `+__missing__+` é acionado nessa classe para consultas por chaves ausentes feitas com + `d[k]`, `d.get(k)`, e `k in d`. + +Veja +«_missing.py_» [.small]#[fpy.li/3-7]# +no repositório de exemplos de código para demonstrações dos cenários descritos acima. +Estes cenários supõem implementações mínimas. +Se a sua subclasse implementa `+__getitem__+`, `get`, e `+__contains__+`, +então você pode ou não fazer tais métodos usarem `+__missing__+`, dependendo de suas necessidades. +O ponto aqui é mostrar que é preciso ter cuidado ao criar subclasses dos mapeamentos da biblioteca padrão +para usar `+__missing__+`, porque as classes base suportam comportamentos default diferentes. +Não se esqueça de que o comportamento de `setdefault` e `update` também é afetado pela consulta de chaves. +E por fim, dependendo da lógica de seu `+__missing__+`, +pode ser necessário implementar uma lógica especial em `+__setitem__+`, +para evitar inconsistências ou comportamentos surpreendentes. +Veremos um exemplo disso na <>. + +Até aqui tratamos dos tipos de mapeamentos `dict` e `defaultdict`, +mas a biblioteca padrão traz outras implementações de mapeamentos, +que discutiremos a seguir.((("", startref="missing03")))((("", startref="Mauto03")))((("", +startref="Kauto03")))((("", startref="DASmissing03"))) + +=== Variações de dict + +Nessa((("dictionaries and sets", "variations of dict", id="DASvardict03"))) seção, falaremos brevemente sobre +os tipos de mapeamentos incluídos na biblioteca padrão diferentes de `defaultdict`, já visto na <>. + +[[ordereddict_sec]] +==== collections.OrderedDict + +Agora((("collections.abc module", "defaultdict and OrderedDict")))((("OrderedDict"))) que o `dict` embutido +também mantém as chaves ordenadas (desde o Python 3.6), +o motivo mais comum para usar `OrderedDict` é escrever código compatível com versões anteriores de Python. +Dito isso, a documentação lista algumas diferenças entre `dict` e `OrderedDict` que +ainda persistem e que cito aqui: + +* A operação de igualdade para `OrderedDict` verifica a igualdade da ordenação. +* O método `popitem()` de `OrderedDict` tem uma assinatura diferente, +que aceita um argumento opcional especificando qual item será devolvido. +* `OrderedDict` tem um método `move_to_end()`, que reposiciona um elemento para uma ponta do dicionário de forma eficiente. +* `OrderedDict` foi projetado suportar bem as operações de reordenamento. +Economia de memória e desempenho de iteração ou de operações de atualização foram preocupações secundárias. +* Um `OrderedDict` lida melhor que um `dict` com operações frequentes de reordenamento. +Isso o torna adequado para monitorar acessos recentes (em um cache LRUfootnote:[NT: Least Recently Used, _Menos Recentemente Usado_, +esquema de cache que descarta o item armazenado que esteja há mais tempo sem ser acessado.], por exemplo). + + +[[chainmap_sec]] +==== collections.ChainMap + +Uma((("collections.abc module", "ChainMap")))((("ChainMap"))) instância de `ChainMap` mantém +uma lista de mapeamentos que podem ser consultados como se fossem um mapeamento único. +A busca é realizada em cada mapa incluído, na ordem em que eles aparecem na chamada ao construtor, +e é bem sucedida assim que a chave é encontrada em um daqueles mapeamentos. +Por exemplo: + +[source, python] +---- +>>> d1 = dict(a=1, b=3) +>>> d2 = dict(a=2, b=4, c=6) +>>> from collections import ChainMap +>>> chain = ChainMap(d1, d2) +>>> chain['a'] +1 +>>> chain['c'] +6 +---- + +<<< + +A instância de `ChainMap` não cria cópias dos mapeamentos, mantém referências para eles. +Atualizações ou inserções a um `ChainMap` afetam apenas o primeiro mapeamento passado. +Continuando do exemplo anterior: + +[source, python] +---- +>>> chain['c'] = -1 +>>> d1 +{'a': 1, 'b': 3, 'c': -1} +>>> d2 +{'a': 2, 'b': 4, 'c': 6} +---- + +Um `ChainMap` é útil na implementação de linguagens com escopos aninhados, +onde cada mapeamento representa um contexto de escopo, +desde o escopo aninhado mais interno até o mais externo. +A seção +«Objetos ChainMap» [.small]#[fpy.li/33]#, +na documentação de `collections`, apresenta vários exemplos +incluindo esse trecho inspirado nas regras básicas de consulta de variáveis em Python: + +[source, python] +---- +import builtins +pylookup = ChainMap(locals(), globals(), vars(builtins)) +---- + +A «Seção 18.3.4» [.small]#[vol.3, fpy.li/4y]# explica uma subclasse de `ChainMap` usada para implementar +um interpretador parcial da linguagem de programação Scheme. + + +==== collections.Counter + +Um((("collections.abc module", "Counter")))((("Counter"))) mapeamento que mantém uma contagem para cada chave. +Atualizar uma chave existente adiciona à sua contagem. +Isso pode ser usado para contar instâncias de objetos _hashable_ ou como um _multiset_ (conjunto múltiplo), +discutido adiante nessa seção. +`Counter` implementa os operadores `+` e `-` para combinar contagens, +e outros métodos úteis, como `most_common([n])`, +que devolve uma lista ordenada de tuplas com os _n_ itens mais comuns e suas contagens; veja a +«documentação» [.small]#[fpy.li/34]#. + +Aqui temos um `Counter` usado para contar as letras em palavras: + +[source, python] +---- +>>> ct = collections.Counter('abracadabra') +>>> ct +Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}) +>>> ct.update('aaaaazzz') +>>> ct +Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1}) +>>> ct.most_common(3) +[('a', 10), ('z', 3), ('b', 2)] +---- + +Observe que as chaves `'b'` e `'r'` estão empatadas em terceiro lugar, mas +`ct.most_common(3)` mostra apenas três contagens. + +Para usar `collections.Counter` como um conjunto múltiplo, trate cada chave como um elemento de um conjunto, +e a contagem será o número de ocorrências daquele elemento no conjunto. + + +==== shelve.Shelf + +O((("shelve module")))((("pickle module")))((("keys", "persistent storage for mapping"))) módulo `shelve` +na biblioteca padrão fornece armazenamento persistente a um mapeamento de chaves +em formato string para objetos Python serializados no formato binário `pickle`. +O nome curioso, _shelve_ (prateleira), faz sentido quando lembramos que potes de picles são armazenados em +prateleiras. + +A função de módulo `shelve.open` devolve uma instância de `shelve.Shelf`—um banco de dados DBM simples de chave-valor, +baseado no módulo `dbm`, com as seguintes características: + +* `shelve.Shelf` é uma subclasse de `abc.MutableMapping`, +então fornece os métodos essenciais esperados de um mapeamento. +* Além disso, `shelve.Shelf` fornece alguns outros métodos de gerenciamento de E/S, como `sync` e `close`. +* Uma instância de `Shelf` é um gerenciador de contexto, +então é possível usar um bloco `with` para garantir que ela seja fechada após o uso. +* Chaves e valores são salvos sempre que um novo valor é atribuído a uma chave. +* As chaves devem ser strings. +* Os valores devem ser objetos que o módulo `pickle` consiga serializar. + +A documentação para os módulos +«shelve» [.small]#[fpy.li/35]#, +«dbm» [.small]#[fpy.li/36]#, e +«pickle» [.small]#[fpy.li/37]# +traz mais detalhes e também algumas ressalvas. + +[WARNING] +==== +O `pickle` de Python é fácil de usar nos casos mais simples, mas tem vários inconvenientes. +Leia «_Pickle’s nine flaws_ (Os nove defeitos de pickle)» [.small]#[fpy.li/3-13]#, de Ned Batchelder, +antes de adotar qualquer solução envolvendo `pickle`. +Em seu post, Ned menciona outros formatos de serialização que podem ser alternativas melhores. +==== + +As classes `OrderedDict`, `ChainMap`, `Counter`, e `Shelf` podem ser usadas diretamente, +mas também podem ser customizadas por subclasses. +`UserDict`, por outro lado, foi planejada apenas como uma classe base a ser estendida. + + +[[sublcassing_userdict_sec]] +==== Criando subclasses de UserDict em vez de dict + +É ((("collections.abc module", "UserDict", id="coluserdict03")))((("UserDict", id="userdict03"))) +melhor criar um novo tipo de mapeamento estendendo `collections.UserDict` em vez de `dict`. +Percebemos isso quando tentamos estender nosso `StrKeyDict0` do <> +para assegurar que qualquer chave adicionada ao mapeamento seja armazenada como `str`. + +A principal razão pela qual é melhor criar uma subclasse de `UserDict` em vez de `dict` é +que o tipo embutido tem alguns atalhos de implementação, +que acabam nos obrigando a sobrescrever métodos que poderíamos apenas herdar de `UserDict` +sem maiores problemas.footnote:[O problema exato de se criar subclasses de `dict` e de outros tipos embutidos +é tratado na «Seção 14.3» [.small]#[vol.2, fpy.li/4z].]#.] + +Observe que `UserDict` não herda de `dict`, mas usa uma composição: +a classe tem uma instância interna de `dict`, chamada `data`, que mantém os itens propriamente ditos. +Isso evita recursão indesejada quando escrevemos métodos especiais, como `+__setitem__+`, +e simplifica a programação de `+__contains__+`, quando comparado com o <>. + +Graças((("keys", "converting nonstring keys to str"))) a `UserDict`, o `StrKeyDict` (<>) +é mais conciso que o `StrKeyDict0` (<>), mas ainda faz melhor: +ele armazena todas as chaves como `str`, +evitando surpresas desagradáveis se a instância for criada ou atualizada com +dados contendo chaves de outros tipos (que não string). + +<<< + +[[ex_strkeydict]] +.`StrKeyDict` sempre converte chaves que não sejam strings para `str` na inserção, atualização e busca +==== +[source, python] +---- +include::../code/03-dict-set/strkeydict.py[tags=STRKEYDICT] +---- +==== +<1> `StrKeyDict` estende `UserDict`. +<2> `+__missing__+` é exatamente igual ao do <>. +<3> `+__contains__+` é mais simples: podemos assumir que todas as chaves armazenadas são `str`, +e podemos operar sobre `self.data` em vez de invocar `self.keys()`, como fizemos em `StrKeyDict0`. +<4> `+__setitem__+` converte qualquer `key` para uma `str`. +Este método é mais fácil de sobrescrever quando podemos delegar para o atributo `self.data`. + +Como `UserDict` estende `abc.MutableMapping`, o restante dos métodos que fazem de `StrKeyDict` +um mapeamento completo são herdados de `UserDict`, `MutableMapping`, ou `Mapping`. +Estes últimos contêm vários métodos concretos úteis, apesar de serem classes base abstratas (ABCs). +Os seguintes métodos são dignos de nota: + +`MutableMapping.update`:: Esse método poderoso pode ser chamado diretamente, mas também é usado por +`+__init__+` para criar a instância a partir de outros mapeamentos, de iteráveis de pares `(chave, valor)`, +e de argumentos nomeados. +Como usa `self[chave] = valor` para adicionar itens, +ele invoca nossa implementação de `+__setitem__+`. + +`Mapping.get`:: No `StrKeyDict0` (<>), +precisamos codar nosso próprio `get` para devolver os mesmos resultados de `+__getitem__+`, +mas no <> herdamos `Mapping.get`, que é implementado exatamente como fizemos +no `StrKeyDict0.get` +(confira o «código-fonte de Python» [.small]#[fpy.li/3-14]#). + +[TIP] +==== +Antoine Pitrou escreveu a +«_PEP 455—Adding a key-transforming dictionary to collections_ +(Acrescentando um dicionário com transformação de chaves a collections)» +[.small]#[fpy.li/pep455]# +e um patch para aperfeiçoar o módulo `collections` com uma classe `TransformDict`, +que é mais genérico que `StrKeyDict` e preserva as chaves como fornecidas antes de aplicar a transformação. +A PEP 455 foi rejeitada em maio de 2015—veja a «mensagem de rejeição» [.small]#[fpy.li/3-15]# +de Raymond Hettinger. +Para experimentar com a `TransformDict`, +extraí o patch de Pitrou do «issue18986» [.small]#[fpy.li/3-16]# +para um módulo independente («`03-dict-set/transformdict.py`» [.small]#[fpy.li/3-17]# +disponível no «repositório de código da segunda edição do _Fluent Python_» [.small]#[fpy.li/code]#). +==== + +Sabemos que existem tipos de sequências imutáveis, mas e mapeamentos imutáveis? +Não existe um tipo real desses na biblioteca padrão, mas um substituto está disponível. +É o que vem a seguir.((("", startref="userdict03")))((("", startref="userdict03")))((("", startref="DASvardict03"))) + + +=== Mapeamentos imutáveis + +Os((("dictionaries and sets", "immutable mappings")))((("mappings", "immutable mappings")))((("immutable mappings"))) +tipos de mapeamentos disponíveis na biblioteca padrão são todos mutáveis, +mas pode ser desejável impedir que os usuários mudem um mapeamento por acidente. +Um caso de uso concreto pode ser encontrado, novamente, em uma biblioteca de programação de hardware como +a((("Pingo library"))) _Pingo_, mencionada na <>: +o mapeamento `board.pins` representa as portas de GPIO (General Purpose Input/Output, _Entrada/Saída Genérica_) +em um dispositivo. +Dessa forma, seria útil evitar atualizações descuidadas de `board.pins`, +pois o hardware não pode ser modificado via software: +qualquer mudança no mapeamento o tornaria inconsistente com a realidade física do dispositivo. + +O módulo `types` oferece uma classe invólucro (_wrapper_) chamada `MappingProxyType` que, +dado um mapeamento, devolve uma instância de `mappingproxy`, +que é um proxy somente para leitura (mas dinâmico) do mapeamento original. +Isso significa que atualizações ao mapeamento original são refletidas no `mappingproxy`, +mas nenhuma mudança pode ser feita através desse último. +Veja uma breve demonstração no <>. + +[[ex_MappingProxyType]] +.`MappingProxyType` cria uma instância somente de leitura de `mappingproxy` a partir de um `dict` +==== +[source, python] +---- +>>> from types import MappingProxyType +>>> d = {1: 'A'} +>>> d_proxy = MappingProxyType(d) +>>> d_proxy +mappingproxy({1: 'A'}) +>>> d_proxy[1] <1> +'A' +>>> d_proxy[2] = 'x' <2> +Traceback (most recent call last): + File "", line 1, in +TypeError: 'mappingproxy' object does not support item assignment +>>> d[2] = 'B' +>>> d_proxy <3> +mappingproxy({1: 'A', 2: 'B'}) +>>> d_proxy[2] +'B' +>>> +---- +==== +<1> Os itens em `d` podem ser vistos através de `d_proxy`. +<2> Não é possível fazer modificações através de `d_proxy`. +<3> `d_proxy` é dinâmica: qualquer mudança em `d` é refletida ali. + + +Isso pode ser usado assim na prática, no cenário da programação de hardware: +o construtor em uma subclasse concreta `Board` preencheria um mapeamento privado com os objetos porta, +e o exporia aos clientes da API via um atributo público `.portas`, implementado como um `mappingproxy`. +Desta forma, os clientes não poderiam acrescentar, remover ou modificar as portas por acidente. + +A seguir estudaremos _views_—que permitem operações de alto desempenho em um `dict`, sem cópias desnecessárias dos dados. + + +[[dictionary_views_sec]] +[role="pagebreak-before less_space"] +=== Views de dicionários + +Objetos `dict`((("dictionaries and sets", "dictionary views", id="DASviews03"))) implementam os métodos +`.keys()`, `.values()`, e `.items()`, que devolvem instâncias de classes chamadas +`dict_keys`, `dict_values`, e `dict_items`, respectivamente. +Essas views de dicionário são projeções somente para leitura de estruturas de dados internas +usadas na implementação de `dict`. +Elas evitam o uso de memória adicional dos métodos equivalentes no Python 2, +que construíam listas, duplicando dados já presentes no `dict`. +E também substituem os métodos antigos que devolviam iteradores. + +O <> mostra algumas operações básicas suportadas por todas as views de dicionários. + +[[ex_dict_values]] +.O método `.values()` devolve uma view dos valores em um `dict` +==== +[source, python] +---- +>>> d = dict(a=10, b=20, c=30) +>>> values = d.values() +>>> values +dict_values([10, 20, 30]) <1> +>>> len(values) <2> +3 +>>> list(values) <3> +[10, 20, 30] +>>> reversed(values) <4> + +>>> values[0] <5> +Traceback (most recent call last): + File "", line 1, in +TypeError: 'dict_values' object is not subscriptable +---- +==== +<1> O `repr` de um objeto view mostra seu conteúdo. +<2> Podemos consultar a `len` de uma view. +<3> Views são iteráveis, então é fácil criar listas a partir delas. +<4> Views implementam `+__reversed__+`, devolvendo um iterador customizado. +<5> Não é possível usar `[]` para obter itens individuais de uma view. + +Um objeto view é um proxy dinâmico. +Se o `dict` fonte é atualizado, as mudanças podem ser vistas imediatamente através de uma view existente. +Continuando do <>: + +[source, python] +---- +>>> d['z'] = 99 +>>> d +{'a': 10, 'b': 20, 'c': 30, 'z': 99} +>>> values +dict_values([10, 20, 30, 99]) +---- + +As classes `dict_keys`, `dict_values`, e `dict_items` são internas: +elas não estão disponíveis via `+__builtins__+` ou qualquer módulo da biblioteca padrão, +e mesmo que você obtenha uma referência para uma delas, +não pode usar essa referência para criar uma view do zero no seu código Python: + +[source, python] +---- +>>> values_class = type({}.values()) +>>> v = values_class() +Traceback (most recent call last): + File "", line 1, in +TypeError: cannot create 'dict_values' instances +---- + +A classe `dict_values` é a view de dicionário mais simples—ela implementa apenas os métodos especiais +`+__len__+`, `+__iter__+`, e `+__reversed__+`. +Além desses métodos, `dict_keys` e `dict_items` implementam vários métodos dos _sets_, +quase tantos quanto a classe `frozenset`. +Após vermos os conjuntos, voltaremos a estudar `dict_keys` e `dict_items`, +na <>. + +Agora vamos ver algumas regras e dicas baseadas na forma como `dict` é +implementado debaixo dos panos.((("", startref="DASviews03"))) + +[[conseq_dict_internal_sec]] +=== Consequências da implementação de dicts + +A((("dictionaries and sets", "consequences of how dict works"))) implementação da tabela de hash do +`dict` de Python é muito eficiente, mas é importante entender os efeitos práticos desse design: + +* Chaves((("keys", "practical consequences of using dict"))) devem ser objetos _hashable_. +Eles devem implementar métodos `+__hash__+` e `+__eq__+` apropriados, como descrito na <>. +* O acesso aos itens através da chave é muito rápido. +Mesmo que um `dict` tenha milhões de chaves, Python pode localizar uma chave diretamente, +computando o código hash da chave e derivando um deslocamento do índice na tabela de hash, +com um possível ônus de um pequeno número de tentativas até encontrar a entrada correspondente. +* A ordenação das chaves é preservada, +como efeito colateral de um layout de memória mais compacto para `dict` no CPython 3.6, +que se tornou um recurso oficial da linguagem no 3.7. +* Apesar de seu novo layout compacto, os dicts apresentam, inevitavelmente, +um uso adicional significativo de memória. +A estrutura de dados interna mais compacta para um contêiner seria um array de ponteiros para os +itens.footnote:[É assim que as tuplas são armazenadas.] +Comparado a isso, uma tabela de hash precisa armazenar mais dados para cada entrada e, para manter a eficiência, +Python precisa manter pelo menos um terço das linhas da tabela de hash vazias. +* Para economizar memória, evite criar atributos de instância fora do método `+__init__+`. + +Essa última dica, sobre atributos de instância, é consequência do comportamento default de Python, +de armazenar atributos de instância em um atributo `+__dict__+` especial, +que é um `dict` vinculado a cada instância.footnote:[A menos que a classe tenha um atributo `+__slots__+`, +como explicado na «Seção 11.11» [vol.2, fpy.li/52].] +Desde a implementação da +«_PEP 412—Key-Sharing Dictionary_ (Dicionário com chaves compartilhadas)» [.small]#[fpy.li/pep412]#, +no Python 3.3, instâncias de uma classe podem compartilhar uma tabela de hash comum, armazenada com a classe. +Essa tabela de hash comum é compartilhada pelo `+__dict__+` de cada nova instância que, +quando `+__init__+` retorna, tenha os mesmos nomes de atributos que a primeira instância a ser criada naquela classe. +O `+__dict__+` de cada instância então pode armazenar só seus próprios valores de atributos como +um array de ponteiros, sem as chaves. +Acrescentar um atributo de instância após o `+__init__+` obriga Python a criar uma nova tabela de hash +só para o `+__dict__+` daquela instância (este era o comportamento antes do Python 3.3). +De acordo com a PEP 412, essa otimização reduz o uso da memória entre 10% e 20% em programas orientados a objetos. +Os detalhes das otimizações do layout compacto e do compartilhamento de chaves são bastante complexos. +Para saber mais, leia «_Internals of sets and dicts_» [.small]#[fpy.li/hashint]#. + +Agora vamos estudar conjuntos. + + +=== Teoria dos conjuntos + +Conjuntos((("sets", "set theory", id=Stheory03")))((("dictionaries and sets", "set theory", +id="DASset03")))((("frozenset"))) +não são novidade no Python, mas ainda são um tanto subutilizados. +O tipo `set` e seu irmão imutável, `frozenset`, +surgiram inicialmente como módulos na biblioteca padrão de Python 2.3, +e foram promovidos a tipos embutidos no Python 2.6. + +[NOTE] +==== +Nesse livro, uso a palavra "conjunto" para me referir tanto a `set` quanto a `frozenset`. +==== + +Um conjunto é uma coleção de objetos únicos. +Uma grande utilidade dos conjuntos é descartar itens duplicados: + +[source, python] +---- +>>> l = ['spam', 'spam', 'eggs', 'spam', 'bacon', 'eggs'] +>>> set(l) +{'eggs', 'spam', 'bacon'} +>>> list(set(l)) +['eggs', 'spam', 'bacon'] +---- + +[TIP] +==== +Para remover elementos duplicados preservando a ordem da primeira ocorrência de cada item, +você pode usar um `dict` simples, assim: +[source, python] +---- +>>> dict.fromkeys(l).keys() +dict_keys(['spam', 'eggs', 'bacon']) +>>> list(dict.fromkeys(l).keys()) +['spam', 'eggs', 'bacon'] +---- +==== + +Elementos de um conjunto devem ser _hashable_. +O tipo `set` não é _hashable_, então não é possível criar um `set` com instâncias aninhadas de `set`. +Mas `frozenset` é _hashable_, então você pode ter instâncias de `frozenset` dentro de um `set`. + +Além de garantir que cada elemento é único, +os tipos conjunto implementam muitas operações entre conjuntos como operadores infixos. +Assim, dados dois conjuntos `a` e `b`, `a | b` devolve sua união, +`a & b` calcula a interseção, `a - b` a diferença, e `a ^ b` a diferença simétrica. +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 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`), +e precisa contar quantas agulhas existem no palheiro. +Graças à interseção de `set` (o operador `&`), +é possível codar isso em uma expressão simples (veja o <>). + +[[ex_set_ops_ex]] +.Conta as ocorrências de agulhas (_needles_) em um palheiro (_haystack_), ambos do tipo set +==== +[source, python] +---- +found = len(needles & haystack) +---- +==== + +Sem o operador de interseção, seria necessário escrever o <> para realizar +a mesma tarefa executada pelo <>. + +[[ex_set_loop_ex]] +.Conta as ocorrências de agulhas (_needles_) em um palheiro (_haystack_); mesmo resultado final do <> +==== +[source, python] +---- +found = 0 +for n in needles: + if n in haystack: + found += 1 +---- +==== + +O <> é um pouco mais rápido que o <>. +Por outro lado, o <> funciona para quaisquer objetos iteráveis `needles` e `haystack`, +enquanto o <> exige que ambos sejam conjuntos. +Mas se você não tem conjuntos à mão, pode sempre criá-los na hora, como mostra o <>. + +[[ex_set_ops_ex2]] +.Conta as ocorrências de agulhas (_needles_) em um palheiro (_haystack_); essas linhas funcionam com qualquer tipo iterável +==== +[source, python] +---- +found = len(set(needles) & set(haystack)) + +# another way: +found = len(set(needles).intersection(haystack)) +---- +==== + +Claro, há o custo extra envolvido na criação dos conjuntos no <>, +mas se ou as `needles` ou o `haystack` já forem um `set`, +a alternativa no <> pode ser mais barata que o <>. + +Qualquer dos exemplos acima é capaz de buscar 1000 elementos em um `haystack` de 10 milhões +de itens em cerca de 0,3 milissegundos—isto é, cerca de 0,3 microsegundos por elemento. + +Além do teste de existência extremamente rápido (graças à tabela de hash), +os tipos embutidos `set` e `frozenset` oferecem uma rica API para criar novos conjuntos ou, +no caso de `set`, para modificar conjuntos existentes. +Vamos discutir essas operações em breve, após uma observação sobre sintaxe.((("", startref="Stheory03"))) + +==== Sets literais + +A((("sets", "set literals"))) sintaxe de literais `set`—`{1}`, `{1, 2}`, etc.—parece +muito com a notação matemática, mas há uma importante exceção: +não há notação literal para o `set` vazio, então precisamos nos lembrar de escrever `set()`. + +[WARNING] +==== +A expressão `{}` cria um +dict+ vazio, como sempre fez em Python. +Por isso é necessário usar `set()` para criar um `set` vazio. +==== + +No Python 3, a representação padrão dos sets como strings sempre usa a notação `{…}`, +exceto para o conjunto vazio: + +[source, python] +---- +>>> s = {1} +>>> type(s) + +>>> s +{1} +>>> s.pop() +1 +>>> s +set() +---- + +A sintaxe do `set` literal, como `{1, 2, 3}`, é mais rápida e mais legível que uma chamada ao construtor +(por exemplo, `set([1, 2, 3])`). +Essa última forma é mais lenta porque, para avaliá-la, Python precisa buscar o nome `set` para obter seu construtor, +daí criar uma lista e, finalmente, passá-la para o construtor. +Por outro lado, para processar um literal como `{1, 2, 3}`, +o Python roda um bytecode especializado, `BUILD_SET`.footnote:[Isso pode ser interessante, mas não é super importante. +Essa diferença de desempenho vai ocorrer apenas quando um conjunto literal for avaliado, +e isso acontece no máximo uma vez por processo Python—quando um módulo é compilado pela primeira vez. +Se tiver curiosidade, importe a função `dis` do módulo `dis`, +e use-a para inspecionar os bytecodes de um `set` literal—por exemplo + `dis('{1}')`—e uma chamada ao construtor `set`—`+dis('set([1])')+`] + +Não há sintaxe especial para representar literais `frozenset`—eles só podem ser criados chamando seu construtor. +Sua representação padrão como string no Python 3 se parece com uma chamada ao construtor +de `frozenset` com um argumento `set`. +Observe a saída no console: + +[source, python] +---- +>>> frozenset(range(10)) +frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) +---- + +==== Compreensões de conjuntos + +Compreensões de conjuntos((("sets", "set comprehensions"))) (_setcomps_) apareceram há bastante tempo, +no Python 2.7, junto com as dictcomps que vimos na <>. O <> mostra procedimento. + +[[ex_setcomp]] +.Cria um conjunto de caracteres Latin-1 que têm a palavra "SIGN" em seus nomes Unicode +==== +[source, python] +---- +>>> from unicodedata import name <1> +>>> {chr(i) for i in range(32, 256) if 'SIGN' in name(chr(i),'')} <2> +{'§', '=', '¢', '#', '¤', '<', '¥', 'µ', '×', '$', '¶', '£', '©', +'°', '+', '÷', '±', '>', '¬', '®', '%'} +---- +==== +<1> Importa a função `name` de `unicodedata` para obter os nomes dos caracteres. +<2> Cria um conjunto de caracteres com códigos entre 32 e 255 que contenham a palavra `'SIGN'` em seus nomes. + +A ordem da saída muda a cada processo Python, devido ao hash "salgado", mencionado na <>. + +Questões de sintaxe à parte, vamos considerar agora o comportamento dos conjuntos.((("", startref="DASset03"))) + + +[[consequences_set_sec]] +[role="pagebreak-before less_space"] +=== Consequências práticas da forma de funcionamento dos conjuntos + +Os((("dictionaries and sets", "consequences of how set works")))((("sets", "consequences of how set works"))) +tipos `set` e `frozenset` são ambos implementados com uma tabela de hash. +Isso tem os seguintes efeitos: + +* Elementos de conjuntos precisam ser objetos _hashable_. +Eles precisam implementar métodos `+__hash__+` e `+__eq__+` adequados, como descrito na <>. +* O teste de existência de um elemento é muito eficiente. +Um conjunto pode ter milhões de elementos, mas um elemento pode ser localizado diretamente, +computando o código hash da chave e derivando um deslocamento do índice, +com o possível ônus de um pequeno número de tentativas até encontrar a entrada correspondente ou exaurir a busca. +* Conjuntos usam mais memória que um array de ponteiros para seus elementos—que é uma estrutura mais compacta, +porém menos eficiente para buscas quando seu tamanho cresce além de uns poucos elementos. +* A ordem dos elementos depende da ordem de inserção, mas não de forma útil ou confiável. +Se dois elementos são diferentes, mas têm o mesmo código hash, +sua posição depende de qual elemento foi inserido primeiro. +* Acrescentar elementos a um conjunto muda a ordem dos elementos existentes. +Isso ocorre porque o algoritmo se torna menos eficiente se a tabela de hash tiver mais de dois terços de ocupação, +então Python pode mover e redimensionar a tabela conforme ela cresce. +Quando isso acontece, os elementos são reinseridos e sua ordem relativa pode mudar. + +Veja o post «_Internals of sets and dicts_» [.small]#[fpy.li/hashint]# +no _http://fluentpython.com_ para mais detalhes. + +Agora vamos revisar a vasta seleção de operações oferecidas pelos conjuntos. + +<<< + +[[set_ops_sec]] +==== Operações de conjuntos + +[[set_uml]] +.Diagrama de classes UML simplificado para `MutableSet` e suas superclasses em `collections.abc` (nomes em itálico são classes e métodos abstratos; métodos de operadores reversos foram omitidos por concisão). +image::../images/flpy_0302.png[align="center",pdfwidth=11cm] + +A <> dá((("dictionaries and sets", "set operations", id="DASset03-ops")))((("sets", +"set operations", id="Soper03")))((("UML class diagrams", "simplified for MutableSet and superclasses"))) +uma visão geral dos métodos disponíveis em conjuntos mutáveis e imutáveis. +Muitos deles são métodos especiais que sobrecarregam operadores, como `&` e `>=`. +A <> mostra os operadores matemáticos de conjuntos que têm +operadores ou métodos correspondentes no Python. +Note que alguns operadores e métodos realizam mudanças internas sobre o conjunto alvo +(por exemplo, `&=`, `difference_update`, etc.). +Tais operações não fazem sentido no mundo ideal dos conjuntos matemáticos, +e também não são implementadas em `frozenset`. + +[TIP] +==== +Os operadores infixos na <> exigem que os dois operandos sejam conjuntos, +mas todos os outros métodos recebem um ou mais argumentos iteráveis. +Por exemplo, para produzir a união de quatro coleções, `a`, `b`, `c`, e `d`, você pode chamar +`a.union(b, c, d)`, onde `a` precisa ser um `set`, +mas `b`, `c`, e `d` podem ser iteráveis de qualquer tipo que produza itens _hashable_. +Para criar um novo conjunto com a união de quatro iteráveis, +desde o Python 3.5 você pode escrever `{*a, *b, *c, *d}` ao invés de atualizar um conjunto existente, graças à +«_PEP 448—Additional Unpacking Generalizations_ (Generalizações de Desempacotamento Adicionais)» [.small]#[fpy.li/pep448]#. + +==== + +[[set_operators_tbl]] +.Operações matemáticas com conjuntos: esses métodos produzem um novo conjunto ou atualizam o conjunto alvo internamente, se ele for mutável +[options="header", cols="^4,11,16"] +|==== +|operador| método | +| `s & z` | `+s.__and__(z)+` | Interseção de `s` e `z` +| `z & s` | `+s.__rand__(z)+` | Operador `&` reverso +| | `s.intersection(it, …)` | Interseção de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. +| `s &= z` | `+s.__iand__(z)+` | `s` atualizado com a interseção de `s` e `z` +| | `s.intersection_update(it, …)` | `s` atualizado com a interseção de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. +| `s \| z` | `+s.__or__(z)+` | União de `s` e `z` +| `z \| s` | `+s.__ror__(z)+` | `\|` reverso +| | `s.union(it, …)` | União de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. +| `s \|= z`| `+s.__ior__(z)+` | `s` atualizado com a união de `s` e `z` +| | `s.update(it, …)` | `s` atualizado com a união de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. +| `s - z` | `+s.__sub__(z)+` | Complemento relativo ou diferença entre `s` e `z` +| `z - s` | `+s.__rsub__(z)+` | Operador `-` reverso +| | `s.difference(it, …)` | Diferença entre `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. +| `s -= z` | `+s.__isub__(z)+` | `s` atualizado com a diferença entre `s` e `z` +| | `s.difference_update(it, …)` | `s` atualizado com a diferença entre `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. +| `s ^ z` | `+s.__xor__(z)+` | Diferença simétrica (o complemento da interseção `s & z`) +| `z ^ s` | `+s.__rxor__(z)+` | Operador `^` reverso +| | `s.symmetric_difference(it)` | Complemento de `s & set(it)` +| `s ^= z` | `+s.__ixor__(z)+` | `s` atualizado com a diferença simétrica de `s` e `z` +| |`s.symmetric_difference_update(it, …)` | `s` atualizado com a diferença simétrica de `s` e todos os conjuntos construídos a partir de iteráveis `it`, etc. +|==== + +A <> lista predicados de conjuntos: operadores e métodos que devolvem `True` ou `False`. + +[[set_comparison_tbl]] +.Operadores e métodos de comparação de conjuntos +[options="header",cols="^4,7,16"] +|==== +|operador| método | +| `e in s` | `+s.__contains__(e)+` | Elemento `e` é membro de `s` +| `s \<= z` | `+s.__le__(z)+` | `s` é um subconjunto do conjunto `z` +| | `s.issubset(it)` | `s` é um subconjunto do conjunto criado a partir do iterável `it` +| `s < z` | `+s.__lt__(z)+` | `s` é um subconjunto própriofootnote:[NT: Na teoria dos conjuntos, A é um _subconjunto próprio_ de B se A é subconjunto de B e A é diferente de B.] do conjunto `z` +| `s >= z` | `+s.__ge__(z)+` | `s` é um superconjunto do conjunto `z` +| | `s.issuperset(it)` | `s` é um superconjunto do conjunto criado a partir do iterável `it` +| `s > z` | `+s.__gt__(z)+` | `s` é um superconjunto próprio do conjunto `z` +| | `s.isdisjoint(z)` | `s` e `z` são disjuntos (não têm elementos em comum) +|==== + +Além de operadores e métodos derivados da teoria matemática dos conjuntos, +os tipos conjunto implementam outros métodos para tornar seu uso prático, resumidos na <>. + +[[set_methods_tbl]] +.Métodos adicionais de conjuntos +[options="header",cols="5,^2,^4,15"] +|==== +| | set | frozenset|   +| `s.add(e)` | ● | | Adiciona elemento `e` a `s` +| `s.clear()` | ● | | Remove todos os elementos de `s` +| `s.copy()` | ● | ● | Cópia rasa de `s` +| `s.discard(e)` | ● | | Remove elemento `e` de `s`, se existir +| `+s.__iter__()+` | ● | ● | Obtém iterador de `s` +| `+s.__len__()+` | ● | ● | `len(s)` +| `s.pop()` | ● | | Remove e devolve um elemento de `s`, gerando um `KeyError` se `s` estiver vazio +| `s.remove(e)` | ● | | Remove elemento `e` de `s`, gerando um `KeyError` se `e` não existir em `s` +|==== + + +Isso encerra nossa visão geral dos recursos dos conjuntos. +Como prometido na <>, +vamos agora ver como dois dos tipos de views de dicionários se comportam de forma muito similar +a um `frozenset`.((("", startref="Soper03")))((("", startref="DASset03-ops"))) + + +[[set_ops_dict_views_sec]] +=== Operações de conjuntos em views de dict + +A <> mostra((("dictionaries and sets", "set operations on dict views")))((("sets", +"set operations on dict views")))(((".keys method", primary-sortas="keys method")))(((".items method", +primary-sortas="items method"))) +como os objetos view devolvidos pelos métodos `.keys()` e `.items()` +de dict são notavelmente similares a um `frozenset`. + +[[view_methods_tbl]] +.Métodos implementados por `frozenset`, `dict_keys`, e `dict_items` +[options="header",cols="10,^5,^5,^6,15"] +|======================================================================================================================== +| | frozenset | dict_keys | dict_items | +| `+s.__and__(z)+` | ● | ● | ● | `s & z` (interseção de `s` e `z`) +| `+s.__rand__(z)+` | ● | ● | ● | operador `&` reverso +| `+s.__contains__()+` | ● | ● | ● | `e in s` +| `s.copy()` | ● | | | Cópia rasa de `s` +| `s.difference(it, …)` | ● | | | Diferença entre `s` e os iteráveis `it`, etc. +| `s.intersection(it, …)` | ● | | | Interseção de `s` e dos iteráveis `it`, etc. +| `s.isdisjoint(z)` | ● | ● | ● | `s` e `z` são disjuntos (não têm elementos em comum) +| `s.issubset(it)` | ● | | | `s` é um subconjunto do iterável `it` +| `s.issuperset(it)` | ● | | | `s` é um superconjunto do iterável `it` +| `+s.__iter__()+` | ● | ● | ● | Obtém iterador para `s` +| `+s.__len__()+` | ● | ● | ● | `len(s)` +| `+s.__or__(z)+` | ● | ● | ● | `s \| z` (união de `s` e `z`) +| `+s.__ror__()+` | ● | ● | ● | Operador `\|` reverso +| `+s.__reversed__()+` | | ● | ● | Obtém iterador para `s` com a ordem invertida +| `+s.__rsub__(z)+` | ● | ● | ● | Operador `-` reverso +| `+s.__sub__(z)+` | ● | ● | ● | `s - z` (diferença entre `s` e `z`) +| `s.symmetric_difference(it)` | ● | | | Complemento de `s & set(it)` +| `s.union(it, …)` | ● | | | União de `s` com iteráveis `it`, etc. +| `+s.__xor__()+` | ● | ● | ● | `s ^ z` (diferença simétrica de `s` e `z`) +| `+s.__rxor__()+` | ● | ● | ● | Operador `^` reverso +|======================================================================================================================== + +Especificamente, `dict_keys` e `dict_items` implementam os métodos especiais para suportar as poderosas operações de conjuntos +`&` (interseção), `|` (união), `-` (diferença), e `^` (diferença simétrica). + +Por exemplo, usando `&` é fácil obter as chaves que aparecem em dois dicionários: + +[source, python] +---- +>>> d1 = dict(a=1, b=2, c=3, d=4) +>>> d2 = dict(b=20, d=40, e=50) +>>> d1.keys() & d2.keys() +{'b', 'd'} +---- + +<<< + +Observe que o valor devolvido por `&` é um `set`. +Melhor ainda: os operadores de conjuntos das views de dicionários funcionam com instâncias de `set`. + +[source, python] +---- +>>> s = {'a', 'e', 'i'} +>>> d1.keys() & s +{'a'} +>>> d1.keys() | s +{'a', 'c', 'b', 'd', 'i', 'e'} +---- + +[WARNING] +==== +Uma view obtida de `dict_items` só funciona como um conjunto se todos os valores naquele `dict` são _hashable_. +Tentar executar operações de conjuntos sobre uma view devolvida por `dict_items` que +contenha valores não-hashable gera um `TypeError: unhashable type 'T'`, sendo `T` o tipo do valor rejeitado. + +Por outro lado, uma view devolvida por `dict_keys` sempre pode ser usada como um conjunto, +pois todas as chaves são _hashable_—por definição. +==== + +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ê! + +Com isso, encerramos esse capítulo. + + +[role="pagebreak-before less_space"] +=== Resumo do 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 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`. +Com a nova implementação de `dict`, o `OrderedDict` não é mais tão útil quanto antes, +mas deve permanecer na biblioteca padrão para manter a compatibilidade +retroativa—e por suas características específicas ausentes em `dict`, +tal como a capacidade de levar em consideração o ordenamento das chaves em uma comparação `==`. +Também no módulo `collections` está o `UserDict`, uma classe base fácil de usar na criação de mapeamentos customizados. + +Dois métodos poderosos disponíveis na maioria dos mapeamentos são `setdefault` e `update`. +O método `setdefault` pode atualizar itens que mantenham valores mutáveis—por exemplo, +em um `dict` de valores `list`—evitando uma segunda busca pela mesma chave. +O método `update` permite inserir ou sobrescrever itens em massa a partir de qualquer outro mapeamento, +desde iteráveis que forneçam pares `(chave, valor)` até argumentos nomeados. +Os construtores de mapeamentos também usam `update` internamente, +permitindo que instâncias sejam inicializadas a partir de outros mapeamentos, +de iteráveis e de argumentos nomeados. +Desde o Python 3.9 também podemos usar o operador `|=` para atualizar um mapeamento e +o operador `|` para criar um novo mapeamento pela união de dois mapeamentos. + +Um gancho elegante na API de mapeamento é o método `+__missing__+`, +que permite customizar o que acontece quando uma chave não é encontrada ao se usar a sintaxe `d[k]`, +que invoca `+__getitem__+`. + +O módulo `collections.abc` oferece as classes base abstratas `Mapping` e `MutableMapping` como interfaces padrão, +úteis para checagem de tipo durante a execução. +O `MappingProxyType`, do módulo `types`, +cria uma fachada imutável para um mapeamento que você precise proteger de modificações acidentais. +Existem também ABCs para `Set` e `MutableSet`. + +Views de dicionários foram uma grande novidade no Python 3, +eliminando o uso desnecessário de memória dos métodos `.keys()`, `.values()`, e `.items()` de Python 2, +que criavam listas duplicando os dados na instância alvo de `dict`. +Além disso, as classes `dict_keys` e `dict_items` suportam os operadores e métodos mais úteis de `frozenset`. + + +[[further_reading_dict]] +[role="pagebreak-before less_space"] +=== Para saber mais + +Na((("dictionaries and sets", "further reading on"))) documentação da Biblioteca Padrão de Python, +a seção +«collections—Tipos de dados de contêineres» [.small]#[fpy.li/2w]# +inclui exemplos e receitas práticas para vários tipos de mapeamentos. +O código-fonte do módulo, `+Lib/collections/__init__.py+`, +é uma excelente referência para qualquer um que deseje criar novos tipos de mapeamentos +ou entender a lógica dos tipos existentes. +O capítulo 1 do +«_Python Cookbook, 3rd ed._» [.small]#[fpy.li/pycook3]# (O'Reilly), +de David Beazley e Brian K. Jones traz 20 receitas práticas e +espertas usando estruturas de dados—a maioria mostrando formas inteligentes de usar `dict`. + +Greg Gandenberger defende a continuidade do uso de `collections.OrderedDict`, +com os argumentos de que "explícito é melhor que implícito," compatibilidade retroativa, +e o fato de algumas ferramentas e bibliotecas presumirem que a ordenação das chaves de um `dict` +é irrelevante: «_Python Dictionaries Are Now Ordered. Keep Using OrderedDict_ +(Os dicionários de Python agora são ordenados. Continue usando OrderedDict)» +[.small]#[fpy.li/3-18]#. + +A «_PEP 3106—Revamping dict.keys(), .values() and .items()_ (Renovando dict.keys(), .values() e .items())» [.small]#[fpy.li/pep3106]# +foi onde Guido van Rossum apresentou o recurso de views de dicionário para Python 3. +No resumo, ele afirma que a ideia veio da Java Collections Framework. + +O «_PyPy_» [.small]#[fpy.li/3-19]# foi o primeiro interpretador Python +a implementar a proposta de Raymond Hettinger para dicts compactos, +fato registrado em +«_Faster, more memory efficient and more ordered dictionaries on PyPy_ +(Dicionários mais rápidos, mais eficientes em termos de memória e mais ordenados no PyPy)» [.small]#[fpy.li/3-20]#, +reconhecendo que um layout similar foi adotado no PHP 7, como descrito em +«_PHP's new hashtable implementation_ (A nova implementação de tabelas de hash de PHP)» [.small]#[fpy.li/3-21]#. +É sempre muito bom quando criadores citam trabalhos anteriores de outros. + +Na PyCon 2017, Brandon Rhodes apresentou +«_The Dictionary Even Mightier_ (O dicionário, ainda mais poderoso)» [.small]#[fpy.li/3-22]#, +uma continuação de sua apresentação animada clássica +«_The Mighty Dictionary_ (O poderoso dicionário)» [.small]#[fpy.li/3-23]#, +incluindo animações de colisões de hash! +Outro vídeo atual, mas mais aprofundado, sobre o funcionamento interno do `dict` de Python é +«_Modern Dictionaries_ (Dicionários modernos)» [.small]#[fpy.li/3-24]# de Raymond Hettinger, +onde ele conta que, após não conseguir convencer os desenvolvedores de Python sobre os dicts compactos, +ele persuadiu a equipe do PyPy, que os adotou. +A ideia ganhou força, e finalmente foi +«adicionada» [.small]#[fpy.li/3-25]# ao CPython 3.6 por INADA Naoki. +Para saber todos os detalhes, +dê uma olhada nos extensos comentários no código-fonte do CPython em +«`Objects/dictobject.c`» [.small]#[fpy.li/3-26]# e no documento de design em +«`Objects/dictnotes.txt`» [.small]#[fpy.li/3-27]#. + +A justificativa para a adição de conjuntos ao Python está documentada na +«_PEP 218--Adding a Built-In Set Object Type_ (Adicionando um objeto embutido de tipo conjunto)» [.small]#[fpy.li/pep218]#. +Quando a PEP 218 foi aprovada, nenhuma sintaxe literal especial foi adotada para conjuntos. +Os literais `set` foram criados no Python 3 e implementados retroativamente no Python 2.7, +assim como as compreensões de `dict` e `set`. +Na PyCon 2019, apresentei +«_Set Practice: learning from Python's set types_ +(Prática de Conjuntos: aprendendo com os tipos conjunto de Python)» [.small]#[fpy.li/3-29]#, +(«video» [.small]#[fpy.li/3-28]#), +descrevendo casos de uso de conjuntos em programas reais, falando sobre o design de sua API, +e sobre a implementação da «`UintSet`» [.small]#[fpy.li/3-30]#, uma classe de conjunto para elementos inteiros, +usando um vetor de bits ao invés de uma tabela de hash, +inspirada por um exemplo do capítulo 6 do excelente livro +«A Linguagem de Programação Go» (Novatec), +de Alan Donovan e Brian Kernighan. + +A revista _Spectrum_, do IEEE, tem um artigo sobre Hans Peter Luhn, +um prolífico inventor que patenteou um conjunto de cartões interligados que permitiam selecionar +receitas de coquetéis a partir dos ingredientes disponíveis, entre inúmeras outras invenções, incluindo... +tabelas de hash! +Veja «_Hans Peter Luhn and the Birth of the Hashing Algorithm_ +(Hans Peter Luhn e o Nascimento do Algoritmo de Hash)» [.small]#[fpy.li/3-31]#. + +<<< + +.Ponto de Vista +**** + +[role="soapbox-title"] +*Açúcar sintático* + +Meu((("dictionaries and sets", "Soapbox discussion")))((("Soapbox sidebars", "syntactic sugar")))((("syntactic sugar"))) +amigo Geraldo Cohen certa vez observou que Python é "simples e correto." + +Puristas de linguagens de programação gostam de desprezar a sintaxe como algo desimportante. + +[quote, Alan Perlis] +____ +Syntactic sugar causes cancer of the semicolon.footnote:[NT: Explicando o trocadilho intraduzível: +"colon", em inglês, designa "a parte central do intestino grosso"; "semicolon", por outro lado, é "ponto e vírgula". +A frase diz, literalmente, "Açúcar sintático causa câncer no ponto e vírgula", +que faz sentido em inglês pela proximidade sonora das palavras.] +____ + +A sintaxe é a interface de usuário de uma linguagem de programação, então tem muita importância na prática. +Antes de encontrar Python, fiz um pouco de programação para a Web usando Perl e PHP. +A sintaxe nativa de mapeamentos nestas linguagens é muito útil. +Sinto muita falta dela quando tenho que usar Java ou C. + +Uma boa sintaxe para mapeamentos literais é muito conveniente para configuração, +para implementações guiadas por tabelas, e para conter dados para prototipagem e testes. +Essa foi uma das lições que os projetistas do Go aprenderam com as linguagens dinâmicas. +A falta de uma boa forma de expressar dados estruturados no código empurrou +a comunidade Java a adotar o verboso e complicado XML como formato de dados. + +JSON foi proposto como «_The Fat-Free Alternative to XML_ (A alternativa sem gordura ao XML)» [.small]#[fpy.li/3-32]# +e se tornou um grande sucesso, substituindo XML em vários contextos. +Uma sintaxe concisa para listas e dicionários resulta em um excelente formato para troca de dados. + +PHP e Ruby imitaram a sintaxe de hash do Perl, usando `\=>` para ligar chaves a valores. +JavaScript usa `:` como Python. +Por que usar dois caracteres, quando um já é bem legível? + +O JSON veio de JavaScript, mas por acaso também é quase um subconjunto exato da sintaxe de Python. +O JSON é compatível com Python, exceto por usar `true`, `false`, e `null` em vez de `True`, `False`, e `None`. + +Armin Ronacher «tuitou» [.small]#[fpy.li/3-33]# que gosta de brincar com o espaço de nomes global de Python, +para acrescentar apelidos compatíveis com o JSON para o `True`, o `False`, e o `None` de Python, +pois daí ele pode colar trechos de JSON diretamente no console. +Sua ideia básica: + +[role="pagebreak-before less_space"] +[source, python] +---- +>>> true, false, null = True, False, None +>>> fruit = { +... "type": "banana", +... "avg_weight": 123.2, +... "edible_peel": false, +... "species": ["acuminata", "balbisiana", "paradisiaca"], +... "issues": null, +... } +>>> fruit +{'type': 'banana', 'avg_weight': 123.2, 'edible_peel': False, +'species': ['acuminata', 'balbisiana', 'paradisiaca'], 'issues': None} +---- + +A sintaxe que todo mundo agora usa para trocar dados é a sintaxe de `dict` e `list` de Python. +Agora temos uma sintaxe agradável com a conveniência da preservação da ordem de inserção. + +Simples e correto. +**** + +<<< diff --git a/vol1/cap04.adoc b/vol1/cap04.adoc new file mode 100644 index 00000000..9c37b830 --- /dev/null +++ b/vol1/cap04.adoc @@ -0,0 +1,2107 @@ +[[ch_str_bytes]] +== Unicode versus Bytes +:example-number: 0 +:figure-number: 0 + +[quote, Esther Nam e Travis Fischer] +____ +Humanos usam texto. +Computadores falam em bytes.footnote:[Slide 12 da palestra "Character Encoding and Unicode in Python" +(_Codificação de Caracteres e Unicode no Python_) na PyCon 2014 («slides» [.small]#[fpy.li/4-1]# , «vídeo» [.small]#[fpy.li/4-2]# ).] +____ + + +O Python 3 introduziu uma forte distinção entre strings de texto humano e sequências de bytes em estado bruto. +A conversão automática((("implicit conversion"))) de sequências de bytes para +texto Unicode ficou para trás no Python 2. +Este capítulo trata de strings Unicode, sequências de bytes, +e das codificações usadas para converter umas nas outras. + +Dependendo do que você faz com Python, pode achar que entender o Unicode não é importante. +Isso é improvável, mas mesmo que seja o caso, não há como escapar da separação entre `str` e `bytes`, +que agora exige conversões explícitas. +Como um bônus, você descobrirá que os tipos especializados de sequências binárias `bytes` e `bytearray` +oferecem recursos que a classe `str` "pau para toda obra" de Python 2 não oferecia. + +Nesse((("Unicode text versus bytes", "topics covered"))) capítulo, veremos os seguintes tópicos: + +* Caracteres, pontos de código e representações binárias +* Recursos exclusivos das sequências binárias: `bytes`, `bytearray`, e `memoryview` +* Codificando para o Unicode completo e para conjuntos de caracteres legados +* Evitando e tratando erros de codificação +* Melhores práticas para lidar com arquivos de texto +* A armadilha da codificação default e questões de E/S padrão +* Comparações seguras de texto Unicode com normalização +* Funções utilitárias para normalização, _case folding_ (equiparação maiúsculas/minúsculas) +e remoção de sinais diacríticos por força bruta +* Ordenação correta de texto Unicode com `locale` e a biblioteca _pyuca_ +* Metadados de caracteres do banco de dados Unicode +* APIs duais, que processam `str` e `bytes` + + +=== Novidades neste capítulo + +O suporte((("Unicode text versus bytes", "significant changes to"))) ao Unicode no Python 3 sempre foi muito completo e estável, +então o acréscimo mais notável é a <>, +descrevendo um utilitário de linha de comando para busca no banco de dados Unicode—uma forma de encontrar +gatinhos sorridentes ou hieroglifos do Egito antigo. +Vale a pena mencionar que o suporte a Unicode no Windows ficou melhor e mais simples desde o Python 3.6, +como veremos na <>. + +Vamos começar então com os conceitos não-tão-novos mas fundamentais de caracteres, pontos de código e bytes. + +[NOTE] +==== +Para((("struct module")))((("binary records, parsing with struct"))) essa segunda edição, +expandi a seção sobre o módulo `struct` e o publiquei online em +«_Parsing binary records with struct_ (Analisando registros binários com struct)» [.small]#[fpy.li/4-3]#, +no +http://fluentpython.com[fluentpython.com] o website que complementa o livro em inglês. + +Lá((("emojis", "building"))) você também vai encontrar o +«_Building Multi-character Emojis_ (Criando emojis multi-caractere)» [.small]#[fpy.li/4-4]# , +descrevendo como combinar caracteres Unicode para criar bandeiras de países, bandeiras de arco-íris, +pessoas com tonalidades de pele diferentes e ícones de diferentes tipos de famílias. +==== + + +=== Questões de caracteres + +O((("Unicode text versus bytes", "characters and Unicode standard", id="UTVchar04"))) +conceito de "string" é simples: uma string é uma sequência de caracteres. +O problema está na definição de "caractere". + +Em 2023, a melhor definição de "caractere" que temos é um caractere Unicode. +Consequentemente, os itens que compõem uma `str` de Python 3 são caracteres Unicode, +como os itens de um objeto `unicode` no Python 2. +Em contraste, os itens de uma `str` no Python 2 são bytes, assim como os itens num objeto `bytes` de Python 3. + +O padrão Unicode separa explicitamente a identidade dos caracteres de representações binárias específicas: + +* A identidade de um caractere é chamada de ((("code points")))ponto de código (__code point__). +É um número de 0 a 1.114.111 (na base 10), +formatado no padrão Unicode como 4 a 6 dígitos hexadecimais precedidos pelo prefixo "U+", de U+0000 a U+10FFFF. +Por exemplo, o ponto de código da letra A é U+0041, o símbolo do Euro é U+20AC, +e o símbolo musical da clave de sol corresponde ao ponto de código U+1D11E. +Cerca de 13% dos pontos de código possíveis têm caracteres atribuídos no Unicode 13, +a versão do padrão usada no Python 3.10. +* Os bytes específicos que representam um caractere dependem da((("encoding", "definition of"))) +codificação (_encoding_) usada. +Uma codificação, nesse contexto, é um algoritmo que converte pontos de código para sequências de bytes, e vice-versa. +O ponto de código para a letra A (U+0041) é codificado como um único byte, `\x41`, na codificação UTF-8, +ou como os bytes `\x41\x00` na codificação UTF-16LE. +Em um outro exemplo, o UTF-8 exige três bytes para codificar o símbolo do Euro (U+20AC): `\xe2\x82\xac`. +Mas no UTF-16LE o mesmo ponto de código é representado em dois bytes: `\xac\x20`. + +Converter pontos de código para bytes é _codificar_; +converter bytes para pontos de código é((("decoding", "definition of"))) _decodificar_. +Veja o <>. + +[[ex_encode_decode]] +.Codificando e decodificando +==== +[source, python] +---- +>>> s = 'café' +>>> len(s) # <1> +4 +>>> b = s.encode('utf8') # <2> +>>> b +b'caf\xc3\xa9' # <3> +>>> len(b) # <4> +5 +>>> b.decode('utf8') # <5> +'café' +---- +==== +<1> A `str` `'café'` tem quatro caracteres Unicode. +<2> Codifica `str` para `bytes` usando a codificação UTF-8. +<3> `bytes` literais são prefixados com um `b`. +<4> `bytes` `b` tem cinco bytes (a letra acentuada "é" tem dois bytes em UTF-8). +<5> Decodifica `bytes` para `str` usando a codificação UTF-8. + +[TIP] +==== +Um jeito fácil de memorizar a distinção entre `.decode()` e `.encode()` é +reconhecer que sequências de bytes podem ser dumps de código de máquina ilegíveis, +ao passo que objetos `str` Unicode são texto "humano" legível. +Daí que faz sentido _decodificar_ `bytes` para `str`, para obter texto legível por seres humanos, +e _codificar_ `str` em `bytes`, para armazenamento ou transmissão por máquinas. +==== + +Apesar do `str` de Python 3 ser praticamente igual ao tipo `unicode` de Python 2 com um novo nome, +o `bytes` de Python 3 não é meramente o velho `str` renomeado, +e há também o tipo estreitamente relacionado `bytearray`. +Então vale a pena examinar os tipos de sequências binárias antes de avançar para +questões de codificação/decodificação.((("", startref="UTVchar04"))) + + +=== Os fundamentos do byte + +Os((("Unicode text versus bytes", "byte essentials", id="UTVbytes04"))) +novos tipos de sequências binárias são diferentes do `str` de Python 2 em vários aspectos. +A primeira coisa importante é que existem dois tipos embutidos básicos de((("binary sequences"))) sequências binárias: +o tipo imutável `bytes`, introduzido no Python 3, e o tipo mutável `bytearray`, +introduzido há tempos, no Python 2.6footnote:[Python 2.6 e o 2.7 também tinham um `bytes`, +mas ele era só um apelido (_alias_) para o tipo `str`.]. +A documentação de Python algumas vezes usa o termo genérico "byte string" +(_string de bytes_, na documentação em português) para se referir a `bytes` e `bytearray`. + +Cada item em `bytes` ou `bytearray` é um inteiro entre 0 e 255, +e não uma string de um caractere, como no `str` de Python 2. +Entretanto, uma fatia de uma sequência binária sempre produz +uma sequência binária do mesmo tipo—incluindo fatias de tamanho 1. Veja o <>. + +[[ex_bytes_bytearray]] +.Uma sequência de cinco bytes, como `bytes` e como `bytearray` +==== +[source, python] +---- +>>> cafe = bytes('café', encoding='utf_8') <1> +>>> cafe +b'caf\xc3\xa9' +>>> cafe[0] <2> +99 +>>> cafe[:1] <3> +b'c' +>>> cafe_arr = bytearray(cafe) +>>> cafe_arr <4> +bytearray(b'caf\xc3\xa9') +>>> cafe_arr[-1:] <5> +bytearray(b'\xa9') +---- +==== +<1> Podemos criar `bytes` a partir de uma `str`, dada uma codificação. +<2> Cada item é um inteiro em `range(256)`. +<3> Fatias de `bytes` também são `bytes`—mesmo fatias de um único byte. +<4> A sintaxe literal para `bytearray` é `bytearray(…)` com um literal `bytes` como argumento. +<5> Uma fatia de `bytearray` também é um `bytearray`. + +[WARNING] +==== +O fato de `my_bytes[0]` obter um `int` mas `my_bytes[:1]` devolver uma sequência de `bytes` de tamanho 1 +só é surpreendente porque estamos acostumados com o tipo `str` de Python, onde `s[0] == s[:1]`. +Para todos os outros tipos de sequência no Python, um item não é o mesmo que uma fatia de tamanho 1. +==== + +Apesar de sequências binárias serem, na verdade, sequências de inteiros, +sua notação literal reflete o fato de que elas muitas vezes contêm texto ASCII. +Assim, quatro formas diferentes de apresentação são utilizadas, +dependendo do valor de cada byte: + +* Para bytes com códigos decimais de 32 a 126—do espaço ao `~` (til)—é usado o próprio caractere ASCII. +* Para os bytes correspondendo a tab, quebra de linha, carriage return (CR) e `\`, +são usadas as sequências de escape `\t`, `\n`, `\r`, e `\\`. +* Se os dois delimitadores de string, `'` e `"`, aparecem na sequência de bytes, +a sequência inteira é delimitada com `'`, e qualquer `'` dentro da sequência é precedida do caractere de escape, +assim `\'`.footnote:[Trívia: O caractere ASCII "aspas simples", +que o Python usa como delimitador de strings, +é denominado APOSTROPHE no padrão Unicode. +As verdadeiras aspas simples são assimétricas: a da esquerda é U+2018 e a da direita, U+2019.] +* Para qualquer outro valor do byte, é usada uma sequência de escape hexadecimal (por exemplo, `\x00` é o byte nulo). + +É por isso que no <> vemos `b'caf\xc3\xa9'`: +os primeiros três bytes, `b'caf'`, estão na faixa de impressão do ASCII, ao contrário dos dois últimos. + +Tanto `bytes` quanto `bytearray` suportam todos os métodos de `str`, exceto aqueles relacionados a formatação (`format`, `format_map`) +e aqueles que dependem de dados Unicode, incluindo `casefold`, `isdecimal`, `isidentifier`, `isnumeric`, `isprintable`, e `encode`. +Isso significa que você pode usar os métodos conhecidos de string, +como `endswith`, `replace`, `strip`, `translate`, `upper` e dezenas de outros, +com sequências binárias—mas com argumentos `bytes` em vez de `str`. +Além disso, as funções de expressões regulares no módulo `re` também funcionam com sequências binárias, +se a regex for compilada a partir de uma sequência binária ao invés de uma `str`. +Desde o Python 3.5, o operador `%` voltou a funcionar com sequências binárias.footnote:[Ele não funcionava de Python 3.0 ao 3.4, +causando muitas dores de cabeça nos desenvolvedores que lidam com dados binários. +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 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: + +[source, python] +---- +>>> bytes.fromhex('31 4B CE A9') +b'1K\xce\xa9' +---- + +As outras formas de criar instâncias de `bytes` ou `bytearray` são chamadas a seus construtores com: + +* Uma `str` e um argumento nomeado `encoding` +* Um iterável que forneça itens com valores entre 0 e 255 +* Um objeto que implemente o protocolo de buffer (por exemplo, `bytes`, `bytearray`, `memoryview`, +`array.array`), que copia os bytes do objeto fonte para a recém-criada sequência binária + +[WARNING] +==== +Até Python 3.5, era possível chamar `bytes` ou `bytearray` com um único inteiro, +para criar uma sequência daquele tamanho inicializada com bytes nulos. +Essa assinatura foi descontinuada no Python 3.5 e removida no Python 3.6. +Veja a https://fpy.li/pep467[PEP 467–Minor API improvements for binary sequences +(_Pequenas melhorias na API para sequências binárias_) ]. +==== + +<<< +Criar uma sequência binária a partir de um objeto tipo buffer +é uma operação de baixo nível que pode envolver conversão de tipos. +Veja uma demonstração no <>. + +[[ex_buffer_demo]] +.Inicializando bytes a partir de dados brutos de um array +==== +[source, python] +---- +>>> import array +>>> numbers = array.array('h', [-2, -1, 0, 1, 2]) <1> +>>> octets = bytes(numbers) <2> +>>> octets +b'\xfe\xff\xff\xff\x00\x00\x01\x00\x02\x00' <3> +---- +==== +<1> O typecode `'h'` cria um `array` de _short integers_ (inteiros de 16 bits). +<2> `octets` mantém uma cópia dos bytes que compõem `numbers`. +<3> Estes são os 10 bytes que representam os 5 inteiros pequenos. + +Criar um objeto `bytes` ou `bytearray` a partir de qualquer fonte tipo buffer vai sempre copiar os bytes. +Já objetos `memoryview` permitem compartilhar memória entre estruturas de dados binários, como vimos na <>. + +Após essa exploração básica dos tipos de sequências de bytes de Python, +vamos ver como eles são convertidos de e para strings.((("", startref="UTVbytes04"))) + +=== Codificadores/decodificadores básicos + +A((("Unicode text versus bytes", "basic encoders/decoders", id="UTVbasic04")))((("encoding", "basics of", +id="encod04")))((("decoding", "basics of", id="decod04"))) distribuição de Python inclui mais de 100((("codecs"))) +_codecs_ (_encoders/decoders_, codificadores/decodificadores) para conversão de texto para bytes e vice-versa. +Cada codec tem um nome, como `'utf_8'`, e às vezes apelidos, como `'utf8'`, `'utf-8'`, e `'U8'`, +que você pode usar como o argumento `encoding` em funções como +`open()`, `str.encode()`, `bytes.decode()`, e assim por diante. +O <> mostra o mesmo texto codificado como três sequências de bytes diferentes. + +<<< +[[ex_codecs]] +.A string "El Niño" codificada com três codecs, gerando sequências de bytes muito diferentes +==== +[source, python] +---- +>>> for codec in ['latin_1', 'utf_8', 'utf_16']: +... print(codec, 'El Niño'.encode(codec), sep='\t') +... +latin_1 b'El Ni\xf1o' +utf_8 b'El Ni\xc3\xb1o' +utf_16 b'\xff\xfeE\x00l\x00 \x00N\x00i\x00\xf1\x00o\x00' +---- +==== + +A <> mostra um conjunto de codecs gerando bytes a partir de caracteres +como a letra "A" e o símbolo musical da clave de sol. +Observe que as últimas três codificações usam múltiplos bytes e tamanho variável. + +[role="width-90"] +[[encodings_demo_fig]] +.Doze caracteres, seus pontos de código, e sua representação binária (em hexadecimal) em 7 codificações diferentes (asteriscos indicam que o caractere não pode ser representado naquela codificação). +image::../images/flpy_0401.png[Tabela de demonstração de codificações] + +Todos aqueles asteriscos na <> deixam claro que algumas codificações, +como o ASCII e mesmo o multi-byte GB2312, não conseguem representar todos os caracteres Unicode. +As codificações UTF, por outro lado, foram projetadas para lidar com todos os pontos de código possíveis. + +Escolhi as codificações apresentadas na <> como uma amostra representativa: + +`latin1` a.k.a. `iso8859_1`:: Importante por ser a base de outras codificações, tal como a `cp1252` e o próprio Unicode +(observe que os valores binários do `latin1` aparecem nos bytes do `cp1252` e nos pontos de código). + +`cp1252`:: Um superconjunto útil de `latin1`, criado pela Microsoft, +acrescentando símbolos convenientes como as aspas curvas e o € (euro); +alguns aplicativos de Windows chamam essa codificação de "ANSI", mas ela nunca foi um padrão ANSI real. + +`cp437`:: O conjunto de caracteres original do IBM PC, com caracteres de desenho de caixas. +Incompatível com o `latin1`, que surgiu depois. + +`gb2312`:: Padrão legado para codificar ideogramas chineses simplificados usados na República da China; +uma das várias codificações criadas para línguas asiáticas. + +`utf-8`:: A codificação de 8 bits mais comum na Web. Em julho de 2021, o +«_W³ Techs: Usage statistics of character encodings for websites_» [.small]#[fpy.li/4-5]# +informava que 97% dos sites usam UTF-8, um grande avanço sobre os 81,4% de setembro de 2014, +quando escrevi este capítulo na primeira edição. + +`utf-16le`:: Uma forma do esquema de codificação UTF de 16 bits; todas as codificações UTF-16 +suportam pontos de código acima de U+FFFF, através de sequências de escape chamadas "pares substitutos". + + +[WARNING] +==== +A UTF-16((("UCS-2 encoding")))((("emojis", "UCS-2 versus UTF-16 encoding"))) +sucedeu a codificação de 16 bits original do Unicode 1.0—a UCS-2—há muito tempo, em 1996. +Mas a UCS-2 obsoleta ainda é usada em muitos sistemas, apesar de ter sido descontinuada +por suportar apenas pontos de código até U+FFFF. +Em 2021, mais de 57% dos pontos de código alocados ficam acima de U+FFFF, +incluindo os emojis. +==== + +Após completar essa revisão das codificações mais comuns, +vamos agora tratar das questões relativas a operações de codificação e +decodificação.((("", startref="UTVbasic04")))((("", startref="encod04")))((("", startref="decod04"))) + + +=== Entendendo os problemas de codificação/decodificação + +Apesar((("Unicode text versus bytes", "understanding encode/decode problems", +id="UTVunder04")))((("encoding", "understanding encode/decode problems", +id="Eunderst04")))((("decoding", "understanding encode/decode problems", id="Dunder04"))) +de existir uma exceção genérica, `UnicodeError`, +o erro relatado pelo Python em geral é mais específico: +ou é um `UnicodeEncodeError` (ao converter uma `str` para sequências binárias) +ou é um `UnicodeDecodeError` (ao ler uma sequência binária para uma `str`). +Carregar módulos de Python também pode gerar um `SyntaxError`, +quando a codificação da fonte for inesperada. +Vamos ver como tratar todos esses erros nas próximas seções. + +[role="man-height3"] +[TIP] +==== +A primeira coisa a observar quando aparece um erro de Unicode é o tipo exato da exceção. +É um `UnicodeEncodeError`, um `UnicodeDecodeError`, ou algum outro erro (por exemplo, `SyntaxError`) +mencionando um problema de codificação? +Para resolver o problema, você primeiro precisa entendê-lo. +==== + + +==== Tratando o UnicodeEncodeError + +A maioria((("UnicodeEncodeError"))) dos codecs não-UTF compreendem +apenas um pequeno subconjunto dos caracteres Unicode. +Ao converter texto para bytes, um `UnicodeEncodeError` será gerado +se um caractere não estiver definido na codificação alvo, +a menos que seja fornecido um tratamento especial, +passando um argumento `errors` para o método ou função de codificação. +O comportamento para tratamento de erro é apresentado no <>. + +[[ex_encoding]] +.Codificando para bytes, e tratamento de erros +==== +[source, python] +---- +>>> city = 'São Paulo' +>>> city.encode('utf_8') <1> +b'S\xc3\xa3o Paulo' +>>> city.encode('utf_16') +b'\xff\xfeS\x00\xe3\x00o\x00 \x00P\x00a\x00u\x00l\x00o\x00' +>>> city.encode('iso8859_1') <2> +b'S\xe3o Paulo' +>>> city.encode('cp437') <3> +Traceback (most recent call last): + File "", line 1, in + File "/.../lib/python3.4/encodings/cp437.py", line 12, in encode + return codecs.charmap_encode(input,errors,encoding_map) +UnicodeEncodeError: 'charmap' codec can't encode character '\xe3' in +position 1: character maps to +>>> city.encode('cp437', errors='ignore') <4> +b'So Paulo' +>>> city.encode('cp437', errors='replace') <5> +b'S?o Paulo' +>>> city.encode('cp437', errors='xmlcharrefreplace') <6> +b'São Paulo' +---- +==== +<1> As codificações UTF lidam com qualquer `str` +<2> `iso8859_1` também funciona com a string `'São Paulo'`. +<3> `cp437` não consegue codificar o `'ã'` ("a" com til). +O método default de tratamento de erro (`'strict'`) gera um `UnicodeEncodeError`. +<4> O método de tratamento `errors='ignore'` pula os caracteres que não podem ser codificados; +isso normalmente é uma péssima ideia, levando à perda silenciosa de informação. +<5> Ao codificar, `errors='replace'` substitui os caracteres não-codificáveis por um `'?'`; +há perda de informação, mas é mais fácil notar que algo está errado. +<6> `'xmlcharrefreplace'` substitui os caracteres não-codificáveis por uma entidade XML. +Se não pode usar UTF e não pode perder informação, essa é a opção. + +[NOTE] +==== +O tratamento de erros de `codecs` é extensível. +Você pode registrar novas strings para o argumento `errors` +passando um nome e uma função de tratamento de erros para a função `codecs.register_error`. +Veja «documentação de `codecs.register_error`» [.small]#[fpy.li/39]#. +==== + +O ASCII é um subconjunto comum a todas as codificações que conheço, +então a codificação deveria sempre funcionar se o texto for composto exclusivamente por caracteres ASCII. +Python 3.7 trouxe um novo método booleano, «`str.isascii()`» [.small]#[fpy.li/4-7]#, +para verificar se seu texto Unicode é 100% ASCII. +Se for, você deve ser capaz de codificá-lo para bytes em qualquer codificação sem gerar um `UnicodeEncodeError`. + + +[[decode_error_sec]] +==== Tratando o UnicodeDecodeError + +Nem((("UnicodeDecodeError"))) todo byte contém um caractere ASCII válido, +e nem toda sequência de bytes é um texto corretamente codificado em UTF-8 ou UTF-16; +assim, se você presumir uma dessas codificações ao converter uma sequência binária para texto, +pode receber um `UnicodeDecodeError`, se bytes inesperados forem encontrados. + +Por outro lado, várias codificações de 8 bits antigas, como a `'cp1252'`, a `'iso8859_1'` e a `'koi8_r'` +são capazes de decodificar qualquer série de bytes, incluindo ruído aleatório, sem reportar qualquer erro. +Portanto, se seu programa presumir a codificação de 8 bits errada, ele vai decodificar lixo silenciosamente. + +.Gremlins, mojibake, e tofu +[TIP] +==== +Caracteres trocados ou distorcidos são conhecidos como "gremlins" ou "mojibake" +("caractere transformado", em japonês). +Outro defeito comum ocorre quando a codificação está certa, +mas a fonte não tem o glifo (desenho) de um caractere. +Neste caso ele aparece como um retângulo branco apelidado de "tofu" +entre os especialistas em fontes.footnote:[Agradeço ao Felipe Sanches do Garoa Hacker Clube por +me ensinar o termo "tofu", e me contar que a «coleção de fontes Noto» [.small]#[fpy.li/5s]# +é um projeto permanente para juntar todos os glifos necessários para evitar tofu em textos Unicode. +"Noto" refere-se ao ideal "no tofu" (sem tofu).] +Por exemplo, a palavra mojibake image:../images/mojibake.png[fit=line] pode aparecer como quatro tofus: 文字化け. +Na frase anterior, os caracteres kanji corretos são uma imagem PNG. +Infelizmente o programa `asciidoctor-pdf` que usei para gerar o livro impresso +usa um subconjunto da fonte Noto que não tem glifos kanji. +Lamento essa falha técnica. Pesquisei muito, mas não consegui resolver a tempo. +Pelo menos a versão HTML está sem tofu, nos principais navegadores modernos. +Confira «esta mesma seção lá no PythonFluente.com» [.small]#[fpy.li/5r]#. +==== + +O <> ilustra como o uso do codec errado pode produzir gremlins ou um `UnicodeDecodeError`. + +[[ex_decoding]] +.Decodificando de `str` para bytes: sucesso e tratamento de erro +==== +[source, python] +---- +>>> octets = b'Montr\xe9al' <1> +>>> octets.decode('cp1252') <2> +'Montréal' +>>> octets.decode('iso8859_7') <3> +'Montrιal' +>>> octets.decode('koi8_r') <4> +'MontrИal' +>>> octets.decode('utf_8') <5> +Traceback (most recent call last): + File "", line 1, in +UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 5: +invalid continuation byte +>>> octets.decode('utf_8', errors='replace') <6> +'Montr�al' +---- +==== +<1> A palavra "Montréal" codificada em `latin1`; `'\xe9'` é o byte para "é". +<2> Decodificar com Windows 1252 funciona, pois esse codec é um superconjunto de `latin1`. +<3> ISO-8859-7 foi projetado para a língua grega, então o byte `'\xe9'` é interpretado incorretamente, +mas nenhum erro é gerado. +<4> KOI8-R foi projetado para o russo. +Agora `'\xe9'` significa a letra "И" do alfabeto cirílico. +<5> O codec `'utf_8'` detecta que `octets` não é UTF-8 válido, e gera um `UnicodeDecodeError`. +<6> Usando `'replace'` para tratamento de erro, o `\xe9` é substituído por "�" +(ponto de código #U+FFFD), o caractere oficial do Unicode chamado `REPLACEMENT CHARACTER`, +criado exatamente para representar caracteres desconhecidos. + +[[syntax_error_encoding]] +==== SyntaxError ao carregar módulos com codificação inesperada + +UTF-8((("SyntaxError"))) é a codificação default para código-fonte no Python 3, +da mesma forma que ASCII era o default no Python 2. +Se você carregar um módulo _.py_ contendo dados que não estejam em UTF-8, +sem declaração de codificação, receberá uma mensagem como essa: + +---- +include::../code/04-text-byte/syntax-msg.txt[] +---- + +<<< +Como o UTF-8 está amplamente instalado em sistemas GNU/Linux e macOS, +um cenário onde isso tem mais chance de ocorrer é na abertura de um +arquivo _.py_ criado no Windows, com `cp1252`. +Observe que esse erro ocorre mesmo no Python para Windows, +pois a codificação default para fontes de Python 3 é UTF-8 em todas as plataformas. + +Para resolver esse problema, acrescente o comentário mágico `coding` no início do arquivo, como no <>. + +[[ex_ola_mundo]] +.'ola.py': um "Hello, World!" em português +==== +[source, python] +---- +# coding: cp1252 + +print('Olá, Mundo!') +---- +==== + +[TIP] +==== +Agora que o código-fonte de Python 3 não está mais limitado ao ASCII, +e por padrão usa a codificação UTF-8, +a melhor "solução" para código-fonte em codificações antigas +como `'cp1252'` é converter tudo para UTF-8 de uma vez, +e não se preocupar com os comentários `coding`. +Se seu editor não suporta UTF-8, é hora de trocar de editor. +==== + +Suponha que você tenha um arquivo de texto, seja ele código-fonte ou poesia, +mas não saiba qual codificação foi usada. +Como detectar a codificação correta? Respostas na próxima seção. + + +[[discover_encoding]] +==== Como descobrir a codificação de uma sequência de bytes + +Como((("byte sequences"))) descobrir a codificação de uma sequência de bytes? +Resposta curta: não é possível. +Você precisa ser informado. + +Alguns protocolos de comunicação e formatos de arquivo, como o HTTP e o XML, +contêm cabeçalhos que nos dizem explicitamente como o conteúdo está codificado. +Você pode ter certeza de que algumas sequências de bytes não representam ASCII, +pois contêm bytes com valores acima de 127, +e o modo como o UTF-8 e o UTF-16 são construídos também limita as sequências de bytes possíveis. + +<<< +.O hack do Leo para detectar codificação UTF-8 +**** +(Os próximos parágrafos vieram de uma nota escrita pelo revisor técnico Leonardo Rochael no rascunho deste livro.) + +Pela((("UTF-8 decoding"))) forma como o UTF-8 foi projetado, +é quase impossível que uma sequência aleatória de bytes, +ou mesmo uma sequência não-aleatória de bytes de uma codificação diferente do UTF-8, +seja acidentalmente decodificada como lixo no UTF-8, ao invés de gerar um `UnicodeDecodeError`. + +As razões para isso são que as sequências de escape do UTF-8 nunca usam caracteres ASCII, +e tais sequências de escape têm padrões de bits que tornam muito difícil que dados aleatórios sejam UTF-8 válido por acidente. +Portanto, se você consegue decodificar como UTF-8 alguns bytes com valor maior que 127, +é altamente provável que o texto esteja em UTF-8. + +Trabalhando com os serviços online brasileiros, alguns baseados em back-ends antigos, +ocasionalmente precisei implementar uma estratégia de decodificação que tentava decodificar via UTF-8 +e tratava um `UnicodeDecodeError` decodificando via `cp1252`. +É feio, mas funciona. +**** + +Entretanto, considerando que as línguas humanas também têm suas regras e restrições, +uma vez que você supõe que uma série de bytes é "texto puro" (_plain text_), +pode ser viável determinar sua codificação usando heurística e estatística. +Por exemplo, se bytes com valor `b'\x00'` forem comuns, +é provável que seja uma codificação de 16 ou 32 bits, +e não UTF-8 ou qualquer codificação de 8 bits, +pois caracteres nulos não aparecem em texto puro. +Quando a sequência de bytes \`b'\x20\x00'` aparece com frequência, +é mais provável que esse seja o caractere de espaço (U+0020) na codificação UTF-16LE, +e não o obscuro caractere U+2000 (`EN QUAD`)—seja lá o que for isso. + +É((("Chardet library"))) assim que o pacote +«_Chardet–The Universal Character Encoding Detector_ (Chardet—O Detector Universal de Codificações de Caracteres)» +[.small]#[fpy.li/4-8]# +faz para descobrir cada uma das mais de 30 codificações suportadas. +_Chardet_ é uma biblioteca Python que pode ser usada em seus programas, +mas que também inclui um utilitário de linha de comando, `chardetect`. +Veja como ele analisa o código-fonte desse capítulo: + +[source,bash] +---- +$ chardetect cap04.adoc +cap04.adoc: utf-8 with confidence 0.99 +---- + +Apesar de sequências binárias de texto codificado normalmente não trazerem dicas sobre sua codificação, +os formatos UTF podem usar um marcador de ordem dos bytes. +Isso é explicado a seguir. + + +==== BOM: um gremlin útil + +No((("BOMs (byte-order marks)"))) <>, +você pode ter notado um par de bytes extra no início de uma sequência codificada em UTF-16. +Aqui estão eles novamente: + +[source, python] +---- +>>> u16 = 'El Niño'.encode('utf_16') +>>> u16 +b'\xff\xfeE\x00l\x00 \x00N\x00i\x00\xf1\x00o\x00' +---- + +Os bytes são `b'\xff\xfe'`. Isso é um __BOM__—sigla para byte-order mark +(marcador de ordem de bytes)—indicando a ordenação de bytes "little-endian" da CPU Intel onde a codificação foi realizada. + +Em uma máquina _little-endian_, para cada ponto de código, o byte menos significativo aparece primeiro: +a letra `'E'`, ponto de código U+0045 (decimal 69), é codificado nas posições 2 e 3 dos bytes como `69` e `0`: + +[source, python] +---- +>>> list(u16) +[255, 254, 69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111, 0] +---- + +Em uma CPU _big-endian_, os bytes são trocados: `'E'` é codificado como `0` e `69`. + +Para evitar confusão, a codificação UTF-16 precede o texto a ser codificado com +o caractere especial invisível `ZERO WIDTH NO-BREAK SPACE` (U+FEFF). +Em um sistema _little-endian_, isso é codificado como `b'\xff\xfe'` (decimais 255, 254). +Como, por design, não existe um caractere U+FFFE em Unicode, +a sequência de bytes `b'\xff\xfe'` tem que ser o `ZERO WIDTH NO-BREAK SPACE` em uma codificação _little-endian_, +e então o codec sabe qual ordenação de bytes usar. + +Há uma variante do UTF-16–o UTF-16LE–que é explicitamente _little-endian_, +e outra que é explicitamente _big-endian_, o UTF-16BE. +Se você os usar, um BOM não será gerado: + +[source, python] +---- +>>> u16le = 'El Niño'.encode('utf_16le') +>>> list(u16le) +[69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111, 0] +>>> u16be = 'El Niño'.encode('utf_16be') +>>> list(u16be) +[0, 69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111] +---- + +Se o BOM estiver presente, supõe-se que ele será filtrado pelo codec UTF-16, +então recebemos apenas o conteúdo textual efetivo do arquivo, +sem o `ZERO WIDTH NO-BREAK SPACE` inicial. + +Segundo a norma Unicode, quando um arquivo é UTF-16 e não tem um BOM, +deve-se presumir que ele é UTF-16BE (_big-endian_). +Entretanto, a arquitetura x86 da Intel é _little-endian_, +por isso há muito texto UTF-16 _little-endian_ sem BOM por aí. + +Toda essa questão de ordenação dos bytes (_endianness_) +só afeta codificações que usam palavras de máquina com mais de um byte, como UTF-16 e UTF-32. +Uma grande vantagem do UTF-8 é produzir a mesma sequência independente da ordenação dos bytes, +então um BOM não é necessário. +No entanto, algumas aplicações Windows (em especial o Notepad) mesmo assim acrescentam o BOM +a arquivos UTF-8—e o Excel depende do BOM para detectar um arquivo UTF-8, +caso contrário ele presume que o conteúdo está codificado com uma página de código do Windows. +Essa codificação UTF-8 com BOM é chamada((("UTF-8-SIG encoding"))) UTF-8-SIG no registro de codecs de Python. +O caractere U+FEFF codificado em UTF-8-SIG é a sequência de três bytes `b'\xef\xbb\xbf'`. +Então, se um arquivo começa com aqueles três bytes, é provavelmente um arquivo UTF-8 com um BOM. + +[role="man-height-2-25"] +.A dica de Caleb sobre o UTF-8-SIG +[TIP] +==== +Caleb Hattingh—um dos revisores técnicos—sugere sempre usar o codec UTF-8-SIG para ler arquivos UTF-8. +Isso é inofensivo, pois o UTF-8-SIG lê corretamente arquivos com ou sem um BOM, e não devolve o BOM propriamente dito. +Para escrever arquivos, recomendo usar UTF-8, para interoperabilidade integral. +Por exemplo, scripts Python podem ser tornados executáveis em sistemas Unix, +se começarem com o comentário: `\#!/usr/bin/env python3`. +Os dois primeiros bytes do arquivo precisam ser `+b'#!'+` para isso funcionar, mas o BOM quebra essa convenção. +Se você tem o requisito específico de exportar dados para aplicativos que precisam do BOM, +use o UTF-8-SIG, mas esteja ciente do que diz a +«documentação sobre codecs» [.small]#[fpy.li/3a]# +de Python: +"No UTF-8, o uso do BOM é desencorajado e, em geral, deve ser evitado." +==== + +Vamos agora ver como tratar arquivos de texto no +Python 3.((("", startref="UTVunder04")))((("", startref="Eunderst04")))((("", startref="Dunder04"))) + + +=== Processando arquivos de texto + +[[unicode_sandwich_fig]] +.O sanduíche de Unicode: a melhor prática para processamento de texto. +image::../images/flpy_0402.png[align="center",pdfwidth=11cm] + +A((("Unicode text versus bytes", "handling text files", id="UTVtext04")))((("text files, handling", id="Tfile04"))) +melhor prática para lidar com E/S de texto é o((("Unicode sandwich"))) "Sanduíche de Unicode" (_Unicode sandwich_) +(<>).footnote:[A primeira vez que vi o termo "Unicode sandwich" (_sanduíche de Unicode_) +foi na excelente apresentação de Ned Batchelder, «_Pragmatic Unicode_» [.small]#[fpy.li/4-10]# na US PyCon 2012.] +Isso significa que os `bytes` devem ser decodificados para `str` o mais cedo possível na entrada +(por exemplo, ao abrir um arquivo para leitura). +O "recheio" do sanduíche é a lógica do negócio de seu programa, +onde o tratamento do texto é feito somente com objetos `str`. +Evite codificar ou decodificar em diferentes estágios do processamento. +Na saída, as `str` são codificadas para `bytes` o mais tarde possível. +A maioria dos frameworks Web funciona assim, e raramente tocamos em `bytes` ao usá-los. +No Django, por exemplo, suas views devem produzir `str` em Unicode; +o próprio Django se encarrega de codificar a resposta para `bytes`, usando UTF-8 como default. + +O Python 3 facilita seguir o conselho do sanduíche de Unicode, +pois a função `open()` por padrão abre arquivos em modo texto. +Neste caso, tudo que você recebe de `my_file.read()` e +passa para `my_file.write(text)` são objetos `str`. +A decodificação e a decodificação são automáticas. + +Assim, usar arquivos de texto é aparentemente simples. +Mas se você confiar nas codificações default, pode acabar levando uma mordida. + +Observe a sessão de console no <>. Você consegue ver o erro? + +[[ex_cafe_file1]] +.Uma questão de plataforma na codificação (você pode ou não ver o problema se tentar isso na sua máquina) +==== +[source, python] +---- +>>> open('cafe.txt', 'w', encoding='utf_8').write('café') +4 +>>> open('cafe.txt').read() +'café' +---- +==== + +O erro: especifiquei a codificação UTF-8 ao escrever o arquivo, mas não fiz isso na leitura, +então Python assumiu a codificação de arquivo default do Windows—página de código 1252—e +os bytes finais foram decodificados como os caracteres `'é'` ao invés de `'é'`. + +Executei o <> no Python 3.8.1, 64 bits, no Windows 10 (build 18363). +Os mesmos comandos rodando em um GNU/Linux ou um macOS recentes funcionam perfeitamente, +pois a codificação default desses sistemas é UTF-8, dando a falsa impressão de que está tudo bem. +Se o argumento de codificação fosse omitido ao abrir o arquivo para escrita, +a codificação default do _locale_ seria usada, e poderíamos ler o arquivo corretamente usando a mesma codificação. +Mas aí o script geraria arquivos com bytes diferentes dependendo da plataforma, +ou mesmo das configurações do _locale_ na mesma plataforma, criando problemas de compatibilidade. + +[TIP] +==== +Código que precisa rodar em múltiplas máquinas ou múltiplas ocasiões não deve depender de defaults de codificação. +Sempre passe um argumento `encoding=` explícito ao abrir arquivos de texto, +pois o default pode mudar de uma máquina para outra ou de um dia para o outro. +==== + +Um detalhe curioso no <> é que a função `write` na primeira instrução informa que +foram escritos quatro caracteres, mas na linha seguinte são lidos cinco caracteres. +O <> é uma versão estendida do <>, e explica esse e outros detalhes. + +[[ex_cafe_file2]] +.Uma inspeção mais atenta do <> rodando no Windows revela o bug e a solução do problema +==== +[source, python] +---- +>>> fp = open('cafe.txt', 'w', encoding='utf_8') +>>> fp # <1> +<_io.TextIOWrapper name='cafe.txt' mode='w' encoding='utf_8'> +>>> fp.write('café') # <2> +4 +>>> fp.close() +>>> import os +>>> os.stat('cafe.txt').st_size # <3> +5 +>>> fp2 = open('cafe.txt') +>>> fp2 # <4> +<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='cp1252'> +>>> fp2.encoding # <5> +'cp1252' +>>> fp2.read() # <6> +'café' +>>> fp3 = open('cafe.txt', encoding='utf_8') # <7> +>>> fp3 +<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='utf_8'> +>>> fp3.read() # <8> +'café' +>>> fp4 = open('cafe.txt', 'rb') # <9> +>>> fp4 # <10> +<_io.BufferedReader name='cafe.txt'> +>>> fp4.read() # <11> +b'caf\xc3\xa9' +---- +==== +<1> Por padrão, `open` usa o modo texto e devolve um objeto `TextIOWrapper` com uma codificação específica. +<2> O método `write` de um `TextIOWrapper` devolve o número de caracteres Unicode escritos. +<3> `os.stat` diz que o arquivo tem 5 bytes; o UTF-8 codifica `'é'` com 2 bytes, 0xc3 e 0xa9. +<4> Abrir um arquivo de texto sem uma codificação explícita devolve um `TextIOWrapper` +com a codificação configurada para um default do locale. +<5> Um objeto `TextIOWrapper` tem um atributo de codificação que pode ser inspecionado: neste caso, `cp1252`. +<6> Na codificação `cp1252` do Windows, o byte 0xc3 é um "Ã" (A maiúsculo com til), +e 0xa9 é o símbolo de copyright. +<7> Abrindo o mesmo arquivo com a codificação correta. +<8> O resultado esperado: os mesmos quatro caracteres Unicode para `'café'`. +<9> A flag `'rb'` abre um arquivo para leitura em modo binário. +<10> O objeto devolvido é um `BufferedReader`, e não um `TextIOWrapper`. +<11> Ler do arquivo obtém bytes, como esperado. + +[TIP] +==== +Não abra arquivos de texto no modo binário, +a menos que seja necessário analisar o conteúdo do arquivo para determinar sua codificação—e mesmo assim, +você deveria estar usando o Chardet em vez de reinventar a roda (veja a <>). + +Programas comuns só deveriam usar o modo binário para abrir arquivos binários, como arquivos de imagens raster ou bitmaps. +==== + +O problema no <> vem de se confiar numa configuração default ao se abrir um arquivo de texto. +Há várias fontes de tais defaults, como mostra a próxima seção. + +[[encoding_defaults]] +==== Cuidado com os defaults de codificação + +Várias((("encoding", "encoding defaults", id="Edefault04"))) +configurações afetam os defaults de codificação para E/S no Python. +Veja o script __default_encodings.py__ no <>. + +<<< +[[ex_default_encodings]] +.Explorando os defaults de codificação +==== +[source, python] +---- +include::../code/04-text-byte/default_encodings.py[] +---- +==== + +A saída do <> no GNU/Linux (Ubuntu 14.04 a 19.10) +e no macOS (10.9 a 10.14) é idêntica, mostrando que `UTF-8` é usado em toda parte nesses sistemas: + +[source] +---- +$ python3 default_encodings.py + locale.getpreferredencoding() -> 'UTF-8' + type(my_file) -> + my_file.encoding -> 'UTF-8' + sys.stdout.isatty() -> True + sys.stdout.encoding -> 'utf-8' + sys.stdin.isatty() -> True + sys.stdin.encoding -> 'utf-8' + sys.stderr.isatty() -> True + sys.stderr.encoding -> 'utf-8' + sys.getdefaultencoding() -> 'utf-8' + sys.getfilesystemencoding() -> 'utf-8' +---- + +No Windows, porém, a saída é o <>. + +[[ex_default_encodings_ps]] +.Codificações default, no PowerShell do Windows 10 (a saída é a mesma no cmd.exe) +==== +[source] +---- +> chcp <1> +Active code page: 437 +> python default_encodings.py <2> + locale.getpreferredencoding() -> 'cp1252' <3> + type(my_file) -> + my_file.encoding -> 'cp1252' <4> + sys.stdout.isatty() -> True <5> + sys.stdout.encoding -> 'utf-8' <6> + sys.stdin.isatty() -> True + sys.stdin.encoding -> 'utf-8' + sys.stderr.isatty() -> True + sys.stderr.encoding -> 'utf-8' + sys.getdefaultencoding() -> 'utf-8' + sys.getfilesystemencoding() -> 'utf-8' +---- +==== +<1> `chcp` mostra a página de código ativa para o console: `437`. +<2> Executando __default_encodings.py__, com a saída direcionada para o console. +<3> `locale.getpreferredencoding()` é a configuração mais importante. +<4> Arquivos de texto usam `locale.getpreferredencoding()` como default. +<5> A saída está direcionada para o console, então `sys.stdout.isatty()` é `True`. +<6> Agora, `sys.stdout.encoding` não é a mesma que a página de código informada por `chcp`! + +O suporte a Unicode no próprio Windows e no Python para Windows melhorou desde que escrevi a primeira edição deste livro. +O <> costumava informar quatro codificações diferentes no Python 3.4 rodando no Windows 7. +As codificações para `stdout`, `stdin`, e `stderr` costumavam ser +iguais à da página de código ativa informada pelo comando `chcp`, +mas agora são todas `utf-8`, graças à +«_PEP 528–Change Windows console encoding to UTF-8_ (Mudar a codificação do console no Windows para UTF-8)» +[.small]#[fpy.li/pep528]#, implementada no Python 3.6, e ao suporte a Unicode no PowerShell do _cmd.exe_ +(desde o Windows 1809, de outubro de 2018).footnote:[Fonte: +https://fpy.li/4-11["Windows Command-Line: Unicode and UTF-8 Output Text Buffer" +(_A Linha de Comando do Windows: O Buffer de Saída de Texto para Unicode e UTF-8_)].] +É esquisito que o `chcp` e o `sys.stdout.encoding` reportem coisas diferentes quando o `stdout` +está escrevendo no console, +mas é ótimo podermos agora escrever strings Unicode sem erros de codificação no Windows—a menos que +o usuário redirecione a saída para um arquivo, como veremos adiante. +Isso não significa que todos os seus emojis((("emojis", "console font and"))) +favoritos vão aparecer: isso também depende da fonte usada pelo console. + +Outra mudança foi a +«_PEP 529–Change Windows filesystem encoding to UTF-8_ (Mudar a codificação do sistema de arquivos do Windows para UTF-8)» +[.small]#[fpy.li/pep529]#, +também implementada no Python 3.6, +que mudou a codificação do sistema de arquivos (usada para representar nomes de diretórios e de arquivos), +da codificação proprietária MBCS da Microsoft para UTF-8. + +Entretanto, se a saída do <> for redirecionada para um arquivo, assim... + +[source] +---- +Z:\>python default_encodings.py > encodings.log +---- + +...aí o valor de `sys.stdout.isatty()` se torna `False`, e `sys.stdout.encoding` +é determinado por `locale.getpreferredencoding()`, +`'cp1252'` naquela máquina—mas `sys.stdin.encoding` e `sys.stderr.encoding` seguem como `utf-8`. + + +[TIP] +==== +No((("\N{} (Unicode literals escape notation)")))((("Unicode literals escape notation (\N{})"))) +<>, usei a expressão de escape `'\N{}'` para literais Unicode, +escrevendo o nome oficial do caractere dentro do `\N{}`. +Isso é bastante prolixo, mas explícito e seguro: +Python gera um `SyntaxError` se o nome não existir—bem melhor que escrever um número hexadecimal que pode estar errado, +mas isso só será descoberto mais tarde. +De qualquer forma, você provavelmente vai querer escrever um comentário explicando os códigos numéricos dos caracteres, +então a verbosidade do `\N{}` é fácil de aceitar. +==== + +Isso significa que um script como o <> funciona quando está escrevendo no console, +mas pode falhar quando a saída é redirecionada para um arquivo. + +<<< +[[ex_stdout_check]] +.stdout_check.py +==== +[source, python] +---- +include::../code/04-text-byte/stdout_check.py[] +---- +==== + +O <> mostra o resultado de uma chamada a `sys.stdout.isatty()`, +o valor de `sys.stdout.encoding`, e esses três caracteres: + +* `'…'` `HORIZONTAL ELLIPSIS`: existe no CP 1252, mas não no CP 437. +* `'∞'` `INFINITY`: existe no CP 437, mas não no CP 1252 (perdão pelo tofu). +* `'㊷'` `CIRCLED NUMBER FORTY TWO`: não existe nem no CP 1252 nem no CP 437 (tofu). + +[WARNING] +==== +Este livro foi escrito em UTF-8, portanto em tese posso usar qualquer caractere Unicode. +Porém, a fonte padrão do Asciidoc para gerar o PDF não tem todos os caracteres Unicode. +Não achei alguns caracteres em outra fonte compatível, +então no livro impresso aparecem como tofu. +Mas no HTML, está tudo certo. +Veja este mesmo exemplo no «PythonFluente.com» +[.small]#[fpy.li/5t]#. +==== + +Quando executo _stdout_check.py_ no PowerShell ou no _cmd.exe_, funciona como visto na <>. + +[[fig_stdout_check]] +.Executando _stdout_check.py_ no PowerShell. +image::../images/flpy_0403.png[Captura de tela do `stdout_check.py` no PowerShell] + +[role="pagebreak-before less_space"] +Apesar de `chcp` informar o código ativo como 437, `sys.stdout.encoding` é UTF-8, +então tanto `HORIZONTAL ELLIPSIS` quanto `INFINITY` são escritos corretamente. +O `CIRCLED NUMBER FORTY TWO` é substituído por um retângulo, mas nenhum erro é gerado. +Presume-se que ele seja reconhecido como um caractere válido, mas a fonte do console não tem o glifo para mostrá-lo. + +Entretanto, quando redireciono a saída de _stdout_check.py_ para um arquivo, o resultado é o da <>. + +[[fig_stdout_check_redir]] +.Executanto _stdout_check.py_ no PowerShell, redirecionando a saída. +image::../images/flpy_0404.png["Captura de tela do `stdout_check.py` no PowerShell, redirecionando a saída"] + +O primeiro problema demonstrado pela <> é o `UnicodeEncodeError` mencionando o caractere `'\u221e'`, +porque `sys.stdout.encoding` é `'cp1252'`—uma página de código que não tem o caractere `INFINITY`. + +Lendo _out.txt_ com o comando `type`—ou um editor de Windows como o VS Code ou o Sublime Text—mostra que, +ao invés do HORIZONTAL ELLIPSIS, consegui um `'à'` (`LATIN SMALL LETTER A WITH GRAVE`). +Acontece que o valor binário 0x85 no CP 1252 significa `'…'`, mas no CP 437 o mesmo valor binário representa o `'à'`. +Então, pelo visto, a página de código ativa tem alguma importância, não de uma forma razoável ou útil, +mas como uma explicação parcial para uma experiência ruim com o Unicode. + + +[NOTE] +==== +Para realizar esses experimentos, usei um laptop configurado para o mercado norte-americano, +rodando Windows 10 OEM. +Versões de Windows localizadas para outros países podem ter configurações de codificação diferentes. +No Brasil, por exemplo, o console do Windows usa a página de código 850 por padrão—e não a 437. +==== + +Para encerrar esse enlouquecedor tópico de codificações default, +vamos dar uma última olhada nas diferentes codificações no <>: + +* Se você omitir o argumento `encoding` ao abrir um arquivo, +o default é dado por `locale.getpreferredencoding()` (`'cp1252'` no <>). + +* Antes de Python 3.6, a codificação de `sys.stdout|stdin|stderr` +costumava ser determinada pela variável do ambiente +«`PYTHONIOENCODING`» [.small]#[fpy.li/3b]#—agora +essa variável é ignorada, a menos que +«`PYTHONLEGACYWINDOWSSTDIO`» [.small]#[fpy.li/3c]# +seja definida como uma string não-vazia. +Caso contrário, a codificação da E/S padrão será UTF-8 para E/S interativa, ou definida por +`locale.getpreferredencoding()`, se a entrada e a saída forem redirecionadas para ou de um arquivo. + +* `sys.getdefaultencoding()` é usado internamente pelo Python em conversões implícitas de +dados binários de ou para `str`. Não há suporte para mudar essa configuração. + +* `sys.getfilesystemencoding()` é usado para codificar/decodificar nomes de arquivo +(mas não o conteúdo dos arquivos). +Ele é usado quando `open()` recebe um argumento `str` para um nome de arquivo; +se o nome do arquivo é passado como um argumento `bytes`, +ele é entregue sem modificação para a API do sistema operacional. + +<<< +[NOTE] +==== +Há muitos anos, no GNU/Linux e no macOS, todas essas codificações são definidas como UTF-8 por padrão, +então a E/S entende e exibe todos os caracteres Unicode. +No Windows, não apenas codificações diferentes são usadas no mesmo sistema, +elas também são, normalmente, páginas de código como `'cp850'` ou `'cp1252'`, que suportam só o ASCII +com 127 caracteres adicionais (que por sua vez são diferentes de uma codificação para a outra). +Assim, usuários de Windows têm mais chances de encontrar erros de codificação. +==== + +Resumindo, a configuração de codificação mais importante devolvida por `locale.getpreferredencoding()` +é a default para abrir arquivos de texto e para `sys.stdout/stdin/stderr`, +quando eles são redirecionados para arquivos. +Entretanto, a +«documentação» [.small]#[fpy.li/3d]# diz (em parte): + +[quote] +____ +`locale.getpreferredencoding(do_setlocale=True)`:: Retorna a codificação da localidade usada para dados de texto, +de acordo com as preferências do usuário. +As preferências do usuário são expressas de maneira diferente em sistemas diferentes e +podem não estar disponíveis programaticamente em alguns sistemas, +portanto, essa função retorna apenas um palpite. [...] +____ + +Assim, o melhor conselho sobre defaults de codificação é: não confie neles. + +Você evitará muitas dores de cabeça se seguir o conselho do sanduíche de Unicode, +e sempre tratar codificações de forma explícita em seus programas. +Infelizmente, o Unicode é trabalhoso mesmo se você converter seus `bytes` para `str` corretamente. +As duas próximas seções tratam de assuntos simples no reino do ASCII, +mas complicados no planeta Unicode: normalização de texto +(isto é, transformar o texto em uma representação uniforme para comparações) +e ordenação.((("", startref="Edefault04")))((("", startref="Tfile04")))((("", startref="UTVtext04"))) + +<<< +[[normalizing_unicode]] +=== Normalizando o Unicode para comparações + +Comparações de strings((("Unicode text versus bytes", "normalizing Unicode for reliable comparisons", +id="UTVnormal04")))((("strings", "normalizing Unicode for reliable comparisons", id="Snormal04"))) +são complicadas porque o Unicode tem caracteres combinantes (_combining characters_): +acentos e outras marcações que são sobrepostas ao caractere anterior, +ambos aparecendo juntos como um só caractere quando impressos. + +Por exemplo, a palavra "café" pode ser composta de duas formas, +usando quatro ou cinco pontos de código, mas o resultado parece exatamente o mesmo: + +[source, python] +---- +>>> s1 = 'café' +>>> s2 = 'cafe\N{COMBINING ACUTE ACCENT}' +>>> s1, s2 +('café', 'café') +>>> len(s1), len(s2) +(4, 5) +>>> s1 == s2 +False +---- + +Colocar `COMBINING ACUTE ACCENT` (U+0301) após o "e" resulta em "é". +No padrão Unicode, sequências como `'é'` e `'e\u0301'` são chamadas de "equivalentes canônicas", +e se espera que as aplicações as tratem como iguais. +Mas Python vê duas sequências de pontos de código diferentes, e não as considera iguais. + +A solução é a `unicodedata.normalize()`. +O primeiro argumento para essa função é uma dessas quatro strings: `'NFC'`, `'NFD'`, `'NFKC'`, e `'NFKD'`. +Vamos começar pelas duas primeiras. + +A Forma Normal C (NFC)((("Normalization Form C (NFC)"))) combina os pontos de código para +produzir a string equivalente mais curta, enquanto a NFD decompõe, +expandindo os caracteres compostos em caracteres base e separando caracteres combinados. +Ambas as normalizações fazem as comparações funcionarem da forma esperada, como mostra o próximo exemplo: + +[source, python] +---- +>>> from unicodedata import normalize +>>> s1 = 'café' +>>> s2 = 'cafe\N{COMBINING ACUTE ACCENT}' +>>> len(s1), len(s2) +(4, 5) +>>> len(normalize('NFC', s1)), len(normalize('NFC', s2)) +(4, 4) +>>> len(normalize('NFD', s1)), len(normalize('NFD', s2)) +(5, 5) +>>> normalize('NFC', s1) == normalize('NFC', s2) +True +>>> normalize('NFD', s1) == normalize('NFD', s2) +True +---- + +Drivers de teclado normalmente geram caracteres compostos, +então o texto digitado pelos usuários usará NFC. +Entretanto, por segurança, +pode ser melhor normalizar as strings com `normalize('NFC', user_text)` antes de salvá-las. +A NFC também é a forma de normalização recomendada pelo W3C em +«_Character Model for the World Wide Web: String Matching and Searching_ +(Modelo de Caracteres para a World Wide Web: Casamento de Strings e Busca)» [.small]#[fpy.li/4-15]#. + + +Alguns caracteres singulares são normalizados pela NFC em um outro caractere singular. +O símbolo para o ohm (Ω), a unidade de medida de resistência elétrica, +é normalizado para a letra grega ômega maiúscula. +Eles são visualmente idênticos, mas diferentes quando comparados, +então a normalização pode evitar surpresas: + +[source, python] +---- +>>> from unicodedata import normalize, name +>>> ohm = '\u2126' +>>> name(ohm) +'OHM SIGN' +>>> ohm_c = normalize('NFC', ohm) +>>> name(ohm_c) +'GREEK CAPITAL LETTER OMEGA' +>>> ohm == ohm_c +False +>>> normalize('NFC', ohm) == normalize('NFC', ohm_c) +True +---- + +As outras duas formas de normalização são a NFKC e a NFKD, a letra K significando "compatibilidade". +Estas são formas mais fortes de normalização, afetando os "caracteres de compatibilidade". +Um dos objetivos do Unicode é oferecer um único ponto de código "canônico" para cada caractere. +Mas alguns caracteres aparecem mais de uma vez, por compatibilidade com codificações legadas. +Por exemplo, o `µ` (`MICRO SIGN`, `U+00B5`), +foi incluído para facilitar a conversão bi-direcional com o `latin1`, que o inclui, +apesar do mesmo caractere ser parte do alfabeto grego com o ponto de código `U+03BC` (`GREEK SMALL LETTER MU`). +Assim, o símbolo de micro é considerado um "caractere de compatibilidade". + +Nas formas NFKC e NFKD, cada caractere de compatibilidade é substituído por uma "decomposição de compatibilidade" +de um ou mais caracteres, que é considerada a representação "preferencial", +mesmo se ocorrer alguma perda de formatação—idealmente, +a formatação deveria ser responsabilidade de alguma marcação externa, não parte do Unicode. +Para exemplificar, a decomposição de compatibilidade da fração um meio, `'½'` (`U+00BD`), +é a sequência de três caracteres `'1/2'`, e a decomposição de compatibilidade do símbolo de micro, +`'µ'` (`U+00B5`), é o mu minúsculo, `'μ'` (`U+03BC`).footnote:[Curiosamente, +o símbolo de micro é considerado um "caractere de compatibilidade", +mas o símbolo de ohm não. +O resultado disso é que a NFC não toca no símbolo de micro, +mas muda o símbolo de ohm para ômega maiúsculo, +ao passo que a NFKC e a NFKD mudam tanto o ohm quanto o micro para caracteres gregos.] + +É assim que a NFKC funciona na prática: + +[source, python] +---- +>>> from unicodedata import normalize, name +>>> half = '\N{VULGAR FRACTION ONE HALF}' +>>> print(half) +½ +>>> normalize('NFKC', half) +'1/2' +>>> for char in normalize('NFKC', half): +... print(char, name(char), sep='\t') +... +1 DIGIT ONE +/ FRACTION SLASH +2 DIGIT TWO +>>> four_squared = '4²' +>>> normalize('NFKC', four_squared) +'42' +>>> micro = 'µ' +>>> micro_kc = normalize('NFKC', micro) +>>> micro, micro_kc +('µ', 'μ') +>>> ord(micro), ord(micro_kc) +(181, 956) +>>> name(micro), name(micro_kc) +('MICRO SIGN', 'GREEK SMALL LETTER MU') +---- + +Ainda que `'1/2'` seja um substituto razoável para `'½'`, +e o símbolo de micro ser realmente a letra grega mu minúscula, converter `'4²'` para `'42'` muda o sentido. +Uma aplicação poderia armazenar `'4²'` como `'42'`, +mas a função `normalize` não sabe nada sobre formatação. +Assim, NFKC ou NFKD podem perder ou distorcer informações, +mas podem produzir representações intermediárias convenientes para buscas ou indexação. + +Infelizmente, com o Unicode tudo é sempre mais complicado do que parece à primeira vista. +Para o `VULGAR FRACTION ONE HALF`, a normalização NFKC produz 1 e 2 unidos pelo `FRACTION SLASH` +em vez do `SOLIDUS`, também conhecido como "barra" ("slash" em inglês)—o familiar +caractere com código decimal 47 na tabela ASCII. +Portanto, buscar pela sequência ASCII de três caracteres `'1/2'` +não encontraria a sequência Unicode normalizada.footnote:[Neste trecho, apenas no livro impresso, +troquei o caractere `FRACTION SLASH` pelo `SOLIDUS` porque o primeiro estava aparecendo como tofu, +e ambos têm a mesma aparência. O código no HTML está fiel ao que eu queria mostrar.] + +[WARNING] +==== +As normalizações NFKC e NFKD causam perda de dados e devem ser aplicadas apenas em casos especiais, +como busca e indexação, e não para armazenamento permanente do texto. +==== + +Ao preparar texto para indexação, há outra operação útil: +_case folding_ footnote:[NT: algo como "dobra" ou "mudança" de caixa.], nosso próximo assunto. + + +==== Case folding + +_Case folding_((("case folding"))) é essencialmente a conversão de todo o texto para minúsculas, +com algumas transformações adicionais. +A operação é suportada pelo método `str.casefold()`. + +Para qualquer string `s` contendo apenas caracteres `latin1`, `s.casefold()` +produz o mesmo resultado de `s.lower()`, com apenas duas exceções—o símbolo de micro, `'µ'`, +é trocado pela letra grega mu minúscula (que é exatamente igual na maioria das fontes) +e a letra alemã _Eszett_ (ß), também chamada "s agudo" (_scharfes S_), se torna "ss": + +<<< +[source, python] +---- +>>> micro = 'µ' +>>> name(micro) +'MICRO SIGN' +>>> micro_cf = micro.casefold() +>>> name(micro_cf) +'GREEK SMALL LETTER MU' +>>> micro, micro_cf +('µ', 'μ') +>>> eszett = 'ß' +>>> name(eszett) +'LATIN SMALL LETTER SHARP S' +>>> eszett_cf = eszett.casefold() +>>> eszett, eszett_cf +('ß', 'ss') +---- + +Há quase 300 pontos de código para os quais `str.casefold()` e `str.lower()` devolvem resultados diferentes. + +Como acontece com qualquer coisa relacionada ao Unicode, _case folding_ é um tópico complexo, +com muitos casos linguísticos especiais, mas os desenvolvedores de Python fizeram +um grande esforço para apresentar uma solução que, espera-se, funcione para a maioria dos usuários. + +Nas próximas seções vamos colocar nosso conhecimento sobre normalização para trabalhar, +desenvolvendo algumas funções utilitárias. + + +==== Funções utilitárias para casamento de texto normalizado + +Como((("normalized text matching", id="normtext04"))) vimos, é seguro usar a NFC e a NFD, +e ambas permitem comparações razoáveis entre strings Unicode. +A NFC é a melhor forma normalizada para a maioria das aplicações, +e `str.casefold()` é a opção certa para comparações indiferentes a maiúsculas/minúsculas. + +Se você precisa lidar com texto em muitas línguas diferentes, +seria muito útil acrescentar às suas ferramentas de trabalho um par de funções como `nfc_equal` e `fold_equal`, +do <>. + +[[ex_normeq]] +.normeq.py: comparação de strings Unicode normalizadas +==== +[source, python] +---- +include::../code/04-text-byte/normeq.py[] +---- +==== + +Além da normalização e do _case folding_ da norma Unicode, +às vezes pode ser útil aplicar transformações mais profundas, +como, por exemplo, mudar `'café'` para `'cafe'`. +Veremos como na próxima seção. + +==== "Normalização" extrema: removendo sinais diacríticos + +O((("diacritics, normalization and", id="diacritics04"))) tempero secreto da busca do Google inclui muitos truques, +mas um deles aparentemente é ignorar sinais diacríticos (acentos e cedilhas, por exemplo), +pelo menos em alguns contextos. +Remover sinais diacríticos não é uma forma regular de normalização, +pois muitas vezes muda o sentido das palavras e pode produzir falsos positivos em uma busca. +Mas ajuda a lidar com alguns fatos da vida: +as pessoas às vezes são preguiçosas ou desconhecem o uso correto dos sinais diacríticos, +e regras de ortografia mudam com o tempo, +levando acentos a desaparecerem e reaparecerem nas línguas vivas. + +Além do caso da busca, eliminar os acentos torna as URLs mais legíveis, +pelo menos nas línguas latinas. +Veja a URL do artigo da Wikipedia sobre a cidade de São Paulo: + +---- +https://en.wikipedia.org/wiki/S%C3%A3o_Paulo +---- + +O trecho `%C3%A3` é a representação em UTF-8 de uma única letra, +o "ã" ("a" com til). A forma a seguir é mais fácil de reconhecer, mesmo com a ortografia incorreta: + +---- +https://en.wikipedia.org/wiki/Sao_Paulo +---- + +Para remover todos os sinais diacríticos de uma `str`, você pode usar uma função como a do <>. + +<<< +[[ex_shave_marks]] +.simplify.py: função para remover todas as marcações combinadas +==== +[source, python] +---- +include::../code/04-text-byte/simplify.py[tags=SHAVE_MARKS] +---- +==== +<1> Decompõe todos os caracteres em caracteres base e marcações combinadas. +<2> Filtra e retira todas as marcações combinadas. +<3> Recompõe todos os caracteres. + + +<> mostra alguns usos para `shave_marks`. + +[[ex_shave_marks_demo]] +.Dois exemplos de uso da `shave_marks` do <> +==== +[source, python] +---- +>>> order = '“Herr Voß: • ½ cup of Œtker™ caffè latte • bowl of açaí.”' +>>> shave_marks(order) +'“Herr Voß: • ½ cup of Œtker™ caffe latte • bowl of acai.”' <1> +>>> Greek = 'Ζέφυρος, Zéfiro' +>>> shave_marks(Greek) +'Ζεφυρος, Zefiro' <2> +---- +==== +<1> Apenas as letras "è", "ç", e "í" foram substituídas.footnote:[Perdão pelos tofu neste exemplo, +são aspas duplas assimétricas e dois quadradinhos pretos. +O exemplo está correto «PythonFluente.com» [.small]#[fpy.li/5v]#.] +<2> Tanto "έ" quanto "é" foram substituídas. + +A função `shave_marks` do <> funciona bem, mas talvez vá longe demais. +Frequentemente, a razão para remover os sinais diacríticos é transformar texto de +uma língua latina para ASCII puro, mas `shave_marks` também troca caracteres +não-latinos (como letras gregas) que nunca se tornarão ASCII apenas pela remoção de acentos. +Então faz sentido analisar cada caractere base e remover as marcações anexas +apenas se o caractere base for uma letra do alfabeto latino. É isso que o <> faz. + +[[ex_shave_marks_latin]] +.Função para remover marcações combinadas de caracteres latinos (imports omitidos, pois isso é parte do módulo simplify.py do <>) +==== +[source, python] +---- +include::../code/04-text-byte/simplify.py[tags=SHAVE_MARKS_LATIN] +---- +==== +<1> Decompõe todos os caracteres em caracteres base e marcações combinadas. +<2> Pula as marcações combinadas quando o caractere base é latino. +<3> Caso contrário, mantém o caractere original. +<4> Detecta um novo caractere base e determina se ele é latino. +<5> Recompõe todos os caracteres. + +Um passo ainda mais radical substituiria os símbolos comuns em textos de línguas europeias +(por exemplo, aspas curvas, travessões, os círculos de _bullet points_, etc.) +em seus equivalentes `ASCII`. É isso que a função `asciize` faz no <>. + +<<< +[[ex_asciize]] +.Transforma alguns símbolos tipográficos europeus em ASCII (este trecho também é parte do simplify.py do <>) +==== +[source, python] +---- +include::../code/04-text-byte/simplify.py[tags=ASCIIZE] +---- +==== +<1> Cria tabela de mapeamento para substituição de caracteres; veja +este exemplo sem tofu no «PythonFluente.com» [.small]#[fpy.li/5w]#. +<2> Tabela de mapeamento para trocar certos caracteres por strings. +<3> Funde as tabelas de mapeamento. +<4> `dewinize` não afeta texto em `ASCII` ou `latin1`, apenas os acréscimos da Microsoft ao `latin1` no `cp1252`. +<5> Aplica `dewinize` e remove as marcações de sinais diacríticos. +<6> Substitui o _Eszett_ por "ss" (não estamos usando _case folding_ aqui, pois queremos preservar maiúsculas e minúsculas). +<7> Aplica a normalização NFKC para compor os caracteres com seus pontos de código de compatibilidade. + +O <> mostra a `asciize` em ação. + +[[ex_asciize_demo]] +.Dois exemplos usando `asciize`, do <> +==== +[source, python] +---- +>>> order = '“Herr Voß: • ½ cup of Œtker™ caffè latte • bowl of açaí.”' +>>> dewinize(order) +'"Herr Voß: - ½ cup of OEtker(TM) caffè latte - bowl of açaí."' <1> +>>> asciize(order) +'"Herr Voss: - 1/2 cup of OEtker(TM) caffe latte - bowl of acai."' <2> +---- +==== +<1> `dewinize` substitui as aspas curvas, os _bullets_, e o ™ (símbolo de marca registrada). +<2> `asciize` aplica `dewinize`, remove os sinais diacríticos e substitui o `'ß'`. + +[WARNING] +==== +Cada idioma tem suas próprias regras para remoção de sinais diacríticos. +Por exemplo, os alemães trocam o `'ü'` por `'ue'`. Nossa função `asciize` não é tão refinada, +então pode ou não ser adequada para seu idioma. +Mas é aceitável para o português. +==== + +Resumindo, as funções em _simplify.py_ vão bem além da normalização padrão, +e realizam uma cirurgia profunda no texto, com boas chances de mudar seu sentido. +Só você pode decidir se deve ir tão longe, conhecendo o idioma alvo, +os seus usuários e a forma como o texto transformado será utilizado. + +Vamos agora ordenar nosso entendimento sobre ordenação no +Unicode.((("", startref="UTVnormal04")))((("", startref="Snormal04")))((("", +startref="normtext04")))((("", startref="diacritics04"))) + + +[[sorting_unicode_sec]] +=== Ordenando texto Unicode + +Python((("Unicode text versus bytes", "sorting Unicode text", id="UTVsort04"))) +ordena sequências de qualquer tipo comparando um por um os itens em cada sequência. +Para strings, isso significa comparar pontos de código. +Infelizmente, isso produz resultados inaceitáveis para qualquer um que use caracteres não-ASCII. + +Considere ordenar uma lista de frutas cultivadas no Brasil: + +[source, python] +---- +>>> fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola'] +>>> sorted(fruits) +['acerola', 'atemoia', 'açaí', 'caju', 'cajá'] +---- + +As regras de ordenação variam entre diferentes locales, +mas em português e em muitas línguas que usam o alfabeto latino, +acentos e cedilhas raramente fazem diferença na ordenação.footnote:[Sinais +diacríticos afetam a ordenação apenas nos raros casos em que eles são +a única diferença entre duas palavras—nesse caso, +a palavra com o sinal diacrítico é colocada após a palavra sem o sinal na ordenação.] +Então "cajá" é lido como "caja" e deve vir antes de "caju". + +A lista `fruits` ordenada deveria ser: + +[source, python] +---- +['açaí', 'acerola', 'atemoia', 'cajá', 'caju'] +---- + +O modo padrão de ordenar texto não-ASCII em Python é usar a função `locale.strxfrm` que, de acordo com a +https://fpy.li/3e[documentação do +módulo `locale`] "transforma uma string em uma que pode ser usada em comparações com reconhecimento de localidade." + +Para poder usar `locale.strxfrm`, você deve primeiro definir um _locale_ adequado para sua aplicação, +e rezar para que o SO o suporte. +A sequência de instruções no <> pode funcionar para você. + +[[ex_locale_sort]] +._locale_sort.py_: Usando a função `locale.strxfrm` como chave de ordenamento +==== +[source, python] +---- +include::../code/04-text-byte/locale_sort.py[] +---- +==== + +Executando o <> no GNU/Linux (Ubuntu 19.10) +com o _locale_ `pt_BR.UTF-8` instalado, consigo o resultado correto: + +[source, python] +---- + +'pt_BR.UTF-8' +['açaí', 'acerola', 'atemoia', 'cajá', 'caju'] +---- + +Portanto, você precisa chamar `setlocale(LC_COLLATE, locale_desejado)` +antes de usar `locale.strxfrm` como a chave de ordenação. + +Porém, aqui vão algumas ressalvas: + +* Como as configurações de _locale_ são globais, não é recomendado chamar `setlocale` em uma biblioteca. +Sua aplicação ou framework deveria definir o _locale_ no início do processo, e não mudá-lo depois. +* O _locale_ desejado deve estar instalado no SO, +caso contrário `setlocale` gera uma exceção de `locale.Error: unsupported locale setting`. +* Você precisa saber como escrever corretamente o nome do locale. +* O _locale_ precisa ser corretamente implementado pelos desenvolvedores do SO. +Tive sucesso com o Ubuntu 19.10, mas não no macOS 10.14. +No macOS, a chamada `setlocale(LC_COLLATE, 'pt_BR.UTF-8')` devolve a string `'pt_BR.UTF-8'` sem qualquer reclamação. +Mas `sorted(fruits, key=locale.strxfrm)` produz o mesmo resultado incorreto de `sorted(fruits)`. +Também tentei os locales `fr_FR`, `es_ES`, e `de_DE` no macOS, +mas `locale.strxfrm` nunca fez seu trabalho direito.footnote:[De novo, +eu não consegui encontrar uma solução, mas encontrei outras pessoas relatando o mesmo problema. +Alex Martelli, um dos revisores técnicos, não teve problemas para usar `setlocale` e `locale.strxfrm` +em seu Mac com o macOS 10.9. Em resumo: cada caso é um caso.] + +Portanto, a solução da biblioteca padrão para ordenação internacionalizada funciona, +mas parece ter suporte adequado apenas no GNU/Linux +(talvez também no Windows, se você for um especialista). +Mesmo assim, ela depende das configurações do _locale_, criando dores de cabeça na implantação. + +Felizmente, há uma solução mais simples: a((("pyuca library"))) biblioteca _pyuca_, disponível no _PyPI_. + +==== Ordenando com o Algoritmo de Ordenação do Unicode + +James Tauber, contribuidor((("Unicode Collation Algorithm (UCA)"))) muito ativo do Django, +deve ter sentido as dores de lidar com _locale_. Ele criou a +«_pyuca_» [.small]#[fpy.li/4-17]#, +uma implementação integralmente em Python do Algoritmo de Ordenação do Unicode +(UCA, sigla em inglês para _Unicode Collation Algorithm_). +O <> mostra como ela é fácil de usar. + +[[ex_pyuca_sort]] +.Utilizando o método `pyuca.Collator.sort_key` +==== +[source, python] +---- +>>> import pyuca +>>> coll = pyuca.Collator() +>>> fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola'] +>>> sorted_fruits = sorted(fruits, key=coll.sort_key) +>>> sorted_fruits +['açaí', 'acerola', 'atemoia', 'cajá', 'caju'] +---- +==== + +Isso é simples e funciona no GNU/Linux, no macOS, e no Windows, pelo menos com a minha pequena amostra. + +A `pyuca` não leva o _locale_ em consideração. +Se você precisar customizar a ordenação, +pode fornecer um caminho para uma tabela própria de ordenação para o construtor `Collator()`. +Sem qualquer configuração adicional, a biblioteca usa o «_allkeys.txt_» [.small]#[fpy.li/4-18]#, incluído no projeto. +Esse arquivo é apenas uma cópia da +«_Default Unicode Collation Element Table_ (Tabela Default de Ordenação de Elementos Unicode)» +[.small]#[fpy.li/4-19]# do _Unicode.org_. + +.PyICU: A recomendação do Miro para ordenação com Unicode +[TIP] +==== +(O revisor técnico Miroslav Šedivý é um poliglota e um especialista em Unicode. +Eis o que ele escreveu sobre a _pyuca_.) + +A _pyuca_((("PyICU"))) tem um algoritmo de ordenação que não respeita o padrão de ordenação de linguagens individuais. +Por exemplo, [a letra] Ä em alemão fica entre o A e o B, enquanto em sueco ela vem depois do Z. +Dê uma olhada na «_PyICU_» [.small]#[fpy.li/4-20]#, +que funciona sem modificar o locale do processo. +Ela também é necessária se você quiser mudar a capitalização de iİ/ıI em turco. +A PyICU inclui uma extensão que precisa ser compilada, +então pode ser mais difícil de instalar em alguns sistemas que a _pyuca_, +que é toda feita em Python. +==== + +E por sinal, aquela tabela de ordenação é um dos muitos arquivos de dados que formam o banco de dados do Unicode, +nosso próximo assunto.((("", startref="UTVsort04"))) + +[[unicodedata_sec]] +=== O banco de dados do Unicode + +O((("Unicode text versus bytes", "Unicode database", id="UTVdatabase04"))) padrão Unicode +fornece todo um banco de dados—na forma de vários arquivos de texto estruturados—que inclui +não apenas a tabela mapeando pontos de código para nomes de caracteres, +mas também metadados sobre os caracteres individuais e como eles se relacionam. +Por exemplo, o banco de dados do Unicode registra se um caractere pode ser impresso, +se é uma letra, um dígito decimal ou algum outro símbolo numérico. +É assim que os métodos de `str` como `isalpha`, `isprintable`, `isdecimal` e `isnumeric` funcionam. +O método `str.casefold` também usa informação de uma tabela do Unicode. + +[NOTE] +==== +A função `unicodedata.category(char)` devolve uma categoria de `char` com duas letras, +do banco de dados do Unicode. +Os métodos de alto nível de `str` são mais fáceis de usar. +Por exemplo, +«`label.isalpha()`» [.small]#[fpy.li/3f]# +devolve `True` se todos os caracteres em `label` +pertencerem a uma das seguintes categorias: `Lm`, `Lt`, `Lu`, `Ll`, ou `Lo`. +Para descobrir o que esses códigos significam, veja +«_General Category_» [.small]#[fpy.li/4-22]# no artigo «_Unicode character property_» [.small]#[fpy.li/4-23]# +da Wikipedia em inglês. +==== + +[[finding_chars_sec]] +==== Encontrando caracteres por nome + +O((("emojis", "finding characters by name", id="Efind04")))((("characters", "finding Unicode by name", +id="Cfinduni04"))) módulo `unicodedata` tem funções para obter os metadados de caracteres, incluindo +`unicodedata.name()`, que devolve o nome oficial do caractere no padrão. +A <> demonstra essa função.footnote:[Aquilo é uma +imagem—não uma listagem de código—porque, no momento em que esse capítulo foi escrito, +os emojis não têm um bom suporte no sistema de publicação digital da O'Reilly.] + +[[unicodedata_name_fig]] +.Explorando `unicodedata.name()` no console de Python. +image::../images/flpy_0405.png[] + +Você pode usar a função `name()` para criar aplicações que permitem aos usuários buscarem caracteres por nome. +A <> demonstra o script de linha de comando _cf.py_, +que recebe como argumentos uma ou mais palavras, +e lista os caracteres que têm aquelas palavras em seus nomes Unicode oficiais. +O código-fonte completo de _cf.py_ aparece no <>. + +[[cf_demo_fig]] +.Usando _cf.py_ para encontrar gatos sorridentes. +image::../images/flpy_0406.png[] + +[WARNING] +==== +O((("emojis", "varied support for"))) suporte a emojis varia muito entre sistemas operacionais e aplicativos. +Nos últimos anos, o terminal do macOS tem oferecido o melhor suporte para emojis, +seguido por terminais gráficos GNU/Linux modernos. +O _cmd.exe_ e o PowerShell do Windows agora suportam saída Unicode, +mas quando escrevi essa seção, em janeiro de 2020, +eles não mostravam emojis por padrão. +O revisor técnico Leonardo Rochael me falou sobre um novo «terminal para Windows da Microsoft» [.small]#[fpy.li/4-24]#, de código aberto, +que pode ter um suporte melhor a Unicode que os consoles antigos da Microsoft. +Ainda não testei. +==== + +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 laço `for` aninhado e de outro `if` para implementar essa verificação + +[[ex_cfpy]] +.cf.py: o utilitário de busca de caracteres +==== +[source, python] +---- +include::../code/04-text-byte/charfinder/cf.py[] +---- +==== +<1> Configura os defaults para a faixa de pontos de código da busca. +<2> `find` aceita `query_words` e somente argumentos nomeados (opcionais) para limitar a faixa da busca, facilitando os testes. +<3> Converte `query_words` em um conjunto de strings capitalizadas. +<4> Obtém o caractere Unicode para `code`. +<5> Obtém o nome do caractere, ou `None` se o ponto de código não estiver atribuído a um caractere. +<6> Se há um nome, separa esse nome em uma lista de palavras, então verifica se o conjunto `query` é um subconjunto daquela lista. +<7> Mostra uma linha com o ponto de código no formato `U+9999`, o caractere e seu nome. + +O módulo `unicodedata` tem outras funções interessantes. +A seguir veremos algumas delas, relacionadas a obter informação de caracteres com +significado numérico.((("", startref="Efind04")))((("", startref="Cfinduni04"))) + + +==== O sentido numérico de caracteres + +O((("characters", "numeric meaning of", id="Cnumeric04"))) módulo `unicodedata` +inclui funções para determinar se um caractere Unicode representa um número e, +se for esse o caso, seu valor numérico em termos humanos—em contraste com o número de seu ponto de código. + +O <> demonstra o uso de `unicodedata.name()` e `unicodedata.numeric()`, +com os métodos `.isdecimal()` e `.isnumeric()` de `str`. + +[[ex_numerics_demo]] +.Uso de metadados de caracteres numéricos (as notas explicativas descrevem cada coluna da saída) +==== +[source, python] +---- +include::../code/04-text-byte/numerics_demo.py[tags=NUMERICS_DEMO] +---- +==== +<1> Ponto de código no formato `U+0000`. +<2> O caractere, centralizado em uma `str` de tamanho 6. +<3> Mostra `re_dig` se o caractere casa com a regex `r'\d'`. +<4> Mostra `isdig` se `char.isdigit()` é `True`. +<5> Mostra `isnum` se `char.isnumeric()` é `True`. +<6> Valor numérico formatado com tamanho 5 e duas casas decimais. +<7> O nome Unicode do caractere. + +Executar o <> gera a <>, +se a fonte do seu terminal incluir todos aqueles símbolos. + +[[numerics_demo_fig]] +.Saída do terminal do macOS com caracteres numéricos e alguns metadados; `re_dig` significa que o caractere casa com a expressão regular `r'\d'`.iid +image::../images/flpy_0407.png[Captura de tela de caracteres numéricos] + +A sexta coluna da <> é o resultado da chamada a `unicodedata.numeric(char)` com o caractere. +Ela mostra que o Unicode sabe o valor numérico de símbolos que representam números. +Assim, se você quiser criar uma aplicação de planilha que suporta numerais romanos ou dígitos da escrita tamil, vá fundo! + +A <> mostra que a expressão regular `r'\d'` casa com o dígito "1" e com o dígito 3 da escrita devanágari, +mas não com alguns outros caracteres considerados dígitos pela função `isdigit`. +O módulo `re` não é tão conhecedor de Unicode quanto deveria ser. +O novo módulo `regex`, disponível no PyPI, +foi projetado para um dia substituir o `re`, +e fornece um suporte melhor ao Unicode.footnote:[Embora +não tenha se saído melhor que o `re` para identificar dígitos nessa amostra em particular.] +Voltaremos ao módulo `re` na próxima seção. + +Ao longo desse capítulo, usamos várias funções de `unicodedata`, +mas há muitas outras que não mencionamos. +Veja a documentação da biblioteca padrão para o «módulo `unicodedata`» [.small]#[fpy.li/3g]#. + +A seguir daremos uma passada pelas APIs de modo dual, +com funções que aceitam argumentos `str` ou `bytes` e dão a eles tratamento especial +dependendo do tipo.((("", startref="Cnumeric04")))((("", startref="UTVdatabase04"))) + +<<< +[[dual_mode_api_sec]] +=== APIs de modo dual para str e bytes + +A biblioteca padrão de Python((("Unicode text versus bytes", "dual-mode str and bytes APIs", +id="UTVdual04")))((("strings", "dual-mode str and bytes APIs", id="Sdual04"))) +tem funções que aceitam argumentos `str` ou `bytes` e se comportam de forma diferente dependendo do tipo recebido. +Alguns exemplos podem ser encontrados nos módulos `re` e `os`. + +==== str versus bytes em expressões regulares + +Se((("regular expressions, str versus bytes in"))) você criar uma expressão regular com `bytes`, +padrões como `\d` e `\w` vão casar apenas com caracteres ASCII; +por outro lado, se esses padrões forem passados como `str`, +eles vão casar com dígitos Unicode ou letras além do ASCII. +O <> e a <> comparam como letras, +dígitos ASCII, superescritos e dígitos tamil casam em padrões `str` e `bytes`. + +[[ex_re_demo]] +.ramanujan.py: compara o comportamento de expressões regulares simples como `str` e como `bytes` +==== +[source, python] +---- +include::../code/04-text-byte/ramanujan.py[tags=RE_DEMO] +---- +==== +<1> As duas primeiras expressões regulares são do tipo `str`. +<2> As duas últimas são do tipo `bytes`. +<3> Texto Unicode para ser usado na busca, contendo os dígitos tamil para `1729` +(a linha lógica continua até o símbolo de fechamento de parênteses). +<4> Essa string é unida à anterior no momento da compilação +(veja https://fpy.li/3h["2.4.2. String literal concatenation" +(_Concatenação de strings literais_)] em _A Referência da Linguagem Python_). +<5> Precisamos de uma string de `bytes` para testar as expressões regulares de `bytes`. +<6> O padrão `str` `r'\d+'` casa com os dígitos ASCII e tamil. +<7> O padrão `bytes` `rb'\d+'` casa apenas com os bytes ASCII para dígitos. +<8> O padrão `str` `r'\w+'` casa com letras, superescritos e dígitos tamil e ASCII. +<9> O padrão `bytes` `rb'\w+'` casa apenas com bytes ASCII para letras e dígitos. + +[[fig_re_demo]] +.Captura de tela da execução de ramanujan.py do <>. +image::../images/flpy_0408.png[Saída de ramanujan.py] + +O <> é um exemplo trivial para destacar um ponto: +você pode usar expressões regulares com `str` ou `bytes`, +mas nesse último caso os bytes fora da faixa do ASCII são tratados como caracteres que não representam dígitos nem palavras. + +Para expressões regulares `str`, há uma marcação `re.ASCII`, +que faz `\w`, `\W`, `\b`, `\B`, `\d`, `\D`, `\s`, e `\S` +casarem apenas com ASCII. +Veja a +«documentação do módulo `re`» [.small]#[fpy.li/3j]# para mais detalhes. + +Outro módulo importante é o `os`. + +==== str versus bytes nas funções de os + +O((("os functions, str versus bytes in"))) kernel do GNU/Linux não conhece Unicode então, no mundo real, +você pode encontrar nomes de arquivos compostos de sequências de bytes que +não são válidas em nenhum esquema razoável de codificação, e não podem ser decodificados para `str`. +Esta é uma situação comum em servidores de arquivos com clientes que usam uma variedade de diferentes +SO. + +Para mitigar esse problema, todas as funções do módulo `os` que +aceitam nomes de arquivo ou caminhos podem receber seus argumentos como `str` ou `bytes`. +Se uma dessas funções é chamada com um argumento `str`, +o argumento será automaticamente convertido usando o codec informado por +`sys.getfilesystemencoding()`, +e a resposta do SO será decodificada com o mesmo codec. +Isso é quase sempre o que se deseja, mantendo a melhor prática do sanduíche de Unicode. + +Mas se você precisa lidar com (e provavelmente corrigir) nomes de arquivo que +não podem ser processados daquela forma, +você pode passar argumentos `bytes` para as funções de `os`, e receber `bytes` de volta. +Esse recurso permite que você processe qualquer nome de arquivo ou caminho, +independente de quantos gremlins encontrar. +Veja o <>. + +[[ex_listdir1]] +.`listdir` com argumentos `str` e `bytes`, e os resultados +==== +[source, python] +---- +>>> os.listdir('.') # <1> +['abc.txt', 'digits-of-π.txt'] +>>> os.listdir(b'.') # <2> +[b'abc.txt', b'digits-of-\xcf\x80.txt'] +---- +==== +<1> O segundo nome de arquivo é "digits-of-π.txt" (com a letra grega pi). +<2> Dado um argumento `byte`, `listdir` devolve nomes de arquivos como bytes: +`b'\xcf\x80'` é a codificação UTF-8 para a letra grega pi. + +Para ajudar no processamento manual de sequências `str` ou `bytes` que são nomes de arquivos ou caminhos, +o módulo `os` fornece funções especiais de codificação e decodificação, +`os.fsencode(name_or_path)` e `os.fsdecode(name_or_path)`. +Ambas as funções aceitam argumentos dos tipos `str`, `bytes` ou, +desde o Python 3.6, um objeto que implemente a interface `os.PathLike`. + +O Unicode é uma toca de coelho bem funda. +É hora de encerrar nossa exploração de `str` e `bytes`.((("", startref="UTVdual04")))((("", startref="Sdual04"))) + + +=== Resumo do capítulo + +Começamos((("Unicode text versus bytes", "overview of"))) +o capítulo descartando a noção de que `1 caractere == 1 byte`. +À medida que o mundo adota o Unicode, +precisamos manter o conceito de strings de texto separado das +sequências binárias que as representam em arquivos, e Python 3 reforça essa separação. + +Após uma breve passada pelos tipos de dados sequências +binárias—`bytes`, `bytearray`, e `memoryview`—mergulhamos na codificação e na decodificação, +com uma amostragem dos codecs importantes, +seguida por abordagens para prevenir ou lidar com os abomináveis +`UnicodeEncodeError`, `UnicodeDecodeError` e os `SyntaxError` +causados pela codificação errada em arquivos de código-fonte de Python. + +A seguir consideramos a teoria e a prática de detecção de codificação na ausência de metadados: +em teoria, não pode ser feita, mas, na prática, o pacote Chardet +consegue realizar esse feito para uma grande quantidade de codificações populares. +Marcadores de ordem de bytes foram apresentados como a única dica de codificação +encontrada em arquivos UTF-16 e UTF-32–algumas vezes também em arquivos UTF-8. + +Na seção seguinte, demonstramos como abrir arquivos de texto, uma tarefa fácil exceto por uma armadilha: +o argumento nomeado `encoding=` não é obrigatório quando se abre um arquivo de texto, mas deveria ser. +Se você não especificar a codificação, terminará com um programa que consegue produzir "texto puro" que +é incompatível entre diferentes plataformas, devido a codificações default conflitantes. +Expusemos então as diferentes configurações de codificação usadas pelo Python, e como detectá-las. + +Uma triste realidade para usuários de Windows é que tais configurações +muitas vezes têm valores diferentes e incompatíveis na mesma máquina; +usuários do GNU/Linux e do macOS, por outro lado, têm a vantagem de +encontrar o UTF-8 como default por (quase) toda parte. + +O Unicode fornece múltiplas formas de representar alguns caracteres, +então a normalização é um pré-requisito para a comparação de textos. +Além de explicar a normalização e o _case folding_, +apresentamos algumas funções úteis que podem ser adaptadas para as suas necessidades, +incluindo transformações drásticas como a remoção de todos os acentos. +Vimos como ordenar corretamente texto Unicode, +usando o módulo padrão `locale`—com algumas restrições—e +uma alternativa que não depende de complexas configurações de locale: +a biblioteca((("pyuca library"))) externa _pyuca_. + +Usamos o banco de dados do Unicode para programar um utilitário de linha de comando que +busca caracteres por nome–em 28 linhas de código, graças ao poder de Python. +Demos uma olhada em outros metadados do Unicode, e vimos rapidamente as APIs de modo dual, +onde algumas funções podem ser chamadas com argumentos `str` ou `bytes`, produzindo resultados diferentes. + + +=== Para saber mais + +A palestra((("Unicode text versus bytes", "further reading on"))) de Ned Batchelder na PyCon US 2012, +«_Pragmatic Unicode_» [.small]#[fpy.li/4-28]# foi marcante. +Ned é tão profissional que publicou uma transcrição completa da palestra, além de slides e vídeo. + +_Character encoding and Unicode in Python_ (Codificação de caracteres e o Unicode no Python) +(«slides» [.small]#[fpy.li/4-1]#, «vídeo» [.small]#[fpy.li/4-2]#) +foi a ótima palestra de Esther Nam e Travis Fischer na PyCon 2014, +e foi onde encontrei a concisa epígrafe desse capítulo: +"Humanos usam texto. Computadores falam em bytes." + +Lennart Regebro—um dos revisores técnicos da primeira edição deste livro—compartilha seu +_Useful Mental Model of Unicode (UMMU)_ (Modelo Mental Útil do Unicode) em um post curto, +«_Unconfusing Unicode: What Is Unicode?_ (Desconfundindo o Unicode: o que é o Unicode?)» [.small]#[fpy.li/4-31]#. +O Unicode é um padrão complexo, então o UMMU de Lennart é um ponto de partida muito útil. + +O «_Unicode HOWTO_» [.small]#[fpy.li/3k]# +oficial na documentação de Python aborda o assunto por vários ângulos diferentes, +de uma boa introdução histórica a detalhes de sintaxe, codecs, expressões regulares, nomes de arquivo, +e boas práticas para E/S sensível ao Unicode (isto é, o sanduíche de Unicode), +com vários links adicionais de referências em cada seção. + +O «Capítulo 4, "Strings"» [.small]#[fpy.li/4-33]#, +do excelente livro «_Dive into Python 3_» [.small]#[fpy.li/4-34]# +de Mark Pilgrim (Apress), também fornece uma ótima introdução ao suporte a Unicode no Python 3. +No mesmo livro, o «Capítulo 15» [.small]#[fpy.li/4-35]# descreve +como a biblioteca Chardet foi portada de Python 2 para Python 3, +um valioso estudo de caso, dado que a mudança do antigo tipo `str` para o novo `bytes` +é a causa da maioria das dores da migração, +e esta é uma preocupação central em uma biblioteca projetada para detectar codificações. + +Se você conhece Python 2, mas é novo no Python 3, o artigo +«_What's New in Python 3.0_ (O que há de novo no Python 3.0)» [.small]#[fpy.li/4-36]#, +de Guido van Rossum, tem 15 pontos resumindo as mudanças, com vários links. +Guido começa com uma afirmação brutal: +"Tudo que você achava que sabia sobre dados binários e Unicode mudou." +O post de Armin Ronacher em seu blog, +«_The Updated Guide to Unicode on Python_ (O Guia Atualizado do Unicode no Python)» +[.small]#[fpy.li/4-37]# +é bastante profundo e destaca algumas armadilhas do Unicode no Python +(Armin não é um grande fã de Python 3). + +O capítulo 2 (_Strings and Text_, Strings e Texto) do +«_Python Cookbook, 3rd ed._» [.small]#[fpy.li/pycook3]# (O'Reilly), +de David Beazley e Brian K. Jones, tem várias receitas tratando de normalização de Unicode, +sanitização de texto, e execução de operações orientadas para texto em sequências de bytes. +O capítulo 5 trata de arquivos e E/S, e inclui a +_Recipe 5.17. Writing Bytes to a Text File_ +(Escrevendo Bytes em um Arquivo de Texto), +mostrando que qualquer arquivo de texto contêm no fundo uma sequência de bytes +que pode ser acessada diretamente quando necessário. +Mais tarde no mesmo livro, o módulo `struct` é usado na +_Recipe 6.11. Reading and Writing Binary Arrays of Structures_ +(Lendo e Escrevendo Arrays de Registros Binários). + +O blog "Python Notes" de Nick Coghlan tem dois posts muito relevantes para esse capítulo: +«_Python 3 and ASCII Compatible Binary Protocols_ +(Python 3 e Protocolos Binários Compatíveis com ASCII)» [.small]#[fpy.li/4-38]# +e «_Processing Text Files in Python 3_ (Processando Arquivos de Texto em Python 3)» +[.small]#[fpy.li/4-39]#. Recomendo. + +Uma lista de codificações suportadas pelo Python fica em +«_Standard Encodings_ (Codificações Padrão)» [.small]#[fpy.li/3m]#, +na documentação do módulo `codecs`. +Se precisar obter aquela lista de dentro de um programa, +pode ver como isso é feito no script +«`/Tools/unicode/listcodecs.py`» [.small]#[fpy.li/4-41]#, +no código-fonte do CPython. + +Os livros +«_Unicode Explained_ (Unicode Explicado)» [.small]#[fpy.li/4-42]#, +de Jukka K. Korpela (O'Reilly) e +«_Unicode Demystified_ (Unicode Desmistificado)» [.small]#[fpy.li/4-43]#, +de Richard Gillam (Addison-Wesley) não são específicos sobre Python, +mas foram muito úteis para meu estudo dos conceitos do Unicode. +«_Programming with Unicode_ (Programando com Unicode)» [.small]#[fpy.li/4-44]#, de Victor Stinner, +é um livro gratuito, publicado pelo próprio autor, tratando de Unicode em geral, +bem como de ferramentas e APIs no contexto dos principais sistemas operacionais +e algumas linguagens de programação, incluindo Python. + +As páginas do W3C +«_Case Folding: An Introduction_ (Case Folding: Uma Introdução)» [.small]#[fpy.li/4-45]# e +«_Character Model for the World Wide Web: String Matching_ +(O Modelo de Caracteres para a World Wide Web: Casamento de Strings)» [.small]#[fpy.li/4-15]# +tratam de conceitos de normalização, +a primeira uma suave introdução e a segunda uma nota de um grupo de trabalho escrita +no seco jargão dos padrões—o mesmo estilo literário do +«_Unicode Standard Annex 15—Unicode Normalization Forms_ +(Anexo 15 do Padrão Unicode—Formas de Normalização do Unicode)» [.small]#[fpy.li/4-47]#. +A seção +«_Frequently Asked Questions, Normalization_ (Perguntas Frequentes, Normalização)» +[.small]#[fpy.li/4-48]# +do +«_Unicode.org_» [.small]#[fpy.li/4-49]# +é mais fácil de ler, bem como o «_NFC FAQ_» [.small]#[fpy.li/4-50]# de +Mark Davis—autor de vários algoritmos do Unicode e presidente do Unicode Consortium quando essa seção foi escrita. + +Em 2016, o((("emojis", "in the Museum of Modern Art"))) Museu de Arte Moderna (MoMA) de New York incluiu ao seu acervo permanente +«_The original emoji_» [.small]#[fpy.li/4-51]# , +painéis iluminados mostrando os 176 emojis desenhados por Shigetaka Kurita em 1999 para a NTT DOCOMO—uma provedora de telefonia celular japonesa. +Indo mais longe no passado, a «_Emojipedia_» [.small]#[fpy.li/4-52]# publicou o artigo +«_Correcting the Record on the First Emoji Set_ +(Corrigindo o Registro [Histórico] sobre o Primeiro Conjunto de Emojis)» [.small]#[fpy.li/4-53]# , +atribuindo à SoftBank do Japão o mais antigo conjunto conhecido de emojis, +distribuídos com celulares a partir de 1997. +O conjunto da SoftBank é a fonte de 90 emojis que hoje estão no Unicode, +incluindo o cocô sorridente (`PILE OF POO`, `U+1F4A9`). +O «_emojitracker.com_» [.small]#[fpy.li/4-54]#, de Matthew Rothenberg, +é um painel online mostrando a contagem do uso de emojis no Twitter, atualizado em tempo real. +Quando escrevi isto, o rosto com lágrimas de felicidade (`FACE WITH TEARS OF JOY`, `U+1F602`) +era o emoji mais popular no Twitter, com mais de 3.313.667.315 de ocorrências registradas. + +<<< +.Ponto de vista +**** + +[role="soapbox-title"] +*Nomes não-ASCII no código-fonte: você deveria usá-los?* + +Python 3((("Soapbox sidebars", "non-ASCII names in source code")))((("Unicode text versus bytes", "Soapbox discussion"))) +permite identificadores não-ASCII no código-fonte: + +[source, python] +---- +>>> ação = 'PBR' # ação = stock +>>> ε = 10**-6 # ε = epsilon +---- + +Algumas pessoas não gostam dessa ideia. +O argumento mais comum é que se limitar aos caracteres ASCII torna a leitura e a edição do código mais fácil para todo mundo. +Esse argumento erra o alvo: você quer que seu código-fonte seja legível e editável pela audiência pretendida, +e isso pode não ser "todo mundo". +Se o código pertence a uma corporação multinacional, ou se é um código aberto e você deseja contribuidores de todo o mundo, +os identificadores devem ser em inglês, e então tudo o que você precisa é do ASCII. + +Mas se você é uma professora no Brasil, seus alunos vão achar mais fácil ler código com +variáveis e nomes de função em português, e escritos corretamente. +E eles não terão nenhuma dificuldade para digitar as cedilhas e as vogais acentuadas em seus teclados localizados. + +Agora que Python pode interpretar nomes em Unicode, e que o UTF-8 é a codificação padrão para código-fonte, +não vejo motivo para codificar identificadores em português sem acentos, como fazíamos no Python 2, +por necessidade—a menos que seu código tenha que rodar também no Python 2. +Se os nomes estão em português, excluir os acentos não vai tornar o código mais legível para ninguém. + +Esse é meu ponto de vista como um brasileiro falante de português, +mas acredito que se aplica além de fronteiras e a outras culturas: +escolha a linguagem humana que torna o código mais legível para sua equipe, +e então use todos os caracteres necessários para a ortografia correta. +**** + +<<< + +**** +[role="soapbox-title"] +*O que é "texto puro"?* + +"Texto puro"((("Soapbox sidebars", "plain text"))) (_plain text_) não significa "ASCII" +para quem lida diariamente com texto em línguas diferentes do inglês. +O «_Unicode Glossary_» [.small]#[fpy.li/4-55]# +define((("plain text"))) _plain text_ dessa forma (nossa tradução): + +[quote] +____ +Texto codificado por computador que consiste apenas em uma sequência de pontos de código de um dado padrão, +sem qualquer outra informação estrutural ou de formatação. +____ + +Essa definição começa muito bem, mas não concordo com a parte após a vírgula. +HTML é um ótimo exemplo de um formato de texto puro que inclui informação estrutural e de formatação. +Mas ele ainda é texto puro, porque cada byte em um arquivo desse tipo está lá para representar um caractere de texto, +em geral, usando UTF-8. +Não há bytes com significado não-textual, +como você encontra em documentos _.png_ ou _.xls_, +onde a maioria dos bytes representa valores binários compactos, +como valores RGB ou números de ponto flutuante. +No texto puro, números são representados como sequências de caracteres de dígitos. + +Estou escrevendo este livro em um formato de texto puro chamado, ironicamente, «_AsciiDoc_» [.small]#[fpy.li/4-56]#, +que é parte do conjunto de ferramentas da plataforma de publicação de livros +«_Atlas_» [.small]#[fpy.li/4-57]# +da O'Reilly. +Os arquivos fonte de AsciiDoc são texto puro, mas aceitam UTF-8, e não só ASCII. +Do contrário, escrever esse capítulo teria sido ainda mais doloroso. +Apesar do nome equivocado, o formato AsciiDoc é muito bom.footnote:[NT: Esta segunda edição em português foi produzida sem as ferramentas da O'Reilly. +Usamos o «_AsciiDoctor_» [.small]#[fpy.li/4p]#, escrito em Ruby, porque o AsciiDoc original em Python está abandonado. +Em 2022 a O'Reilly usava um fork proprietário do `asciidoc.py`.] + +O mundo do Unicode está em constante expansão e, nas fronteiras, as ferramentas de apoio nem sempre existem. +Nem todos os caracteres que eu queria exibir estavam disponíveis nas fontes usadas para renderizar o livro. +Por isso tive que usar capturas de tela do terminal em alguns exemplos desse capítulo. +Por outro lado, os terminais do Ubuntu e do macOS exibem a maioria do texto Unicode +muito bem—incluindo os caracteres kanji da palavra "mojibake": image:../images/mojibake-cinza.png[fit=line]. +**** + +<<< + +**** +[role="soapbox-title"] +*Como os pontos de código numa str são representados na memória?* + +A((("Soapbox sidebars", "code points")))((("code points"))) documentação oficial de Python +evita falar sobre como os pontos de código de uma `str` são armazenados na memória. +Realmente, é um detalhe de implementação. +Em teoria, não importa: independentemente da representação interna, +toda `str` precisa ser codificada para `bytes` na saída. + +Na memória, Python 3 armazena cada `str` como uma sequência de pontos de código, +usando um número fixo de bytes por ponto de código, +para permitir o acesso direto eficiente a qualquer caractere ou fatia. + +Desde o Python 3.3, ao criar um novo objeto `str` +o interpretador verifica os caracteres no objeto, +e escolhe o layout de memória mais econômico que seja adequado para aquela `str` em particular: +se existirem apenas caracteres na faixa `latin1`, aquela `str` vai usar apenas um byte por ponto de código. +Caso contrário, podem ser usados dois ou quatro bytes por ponto de código, dependendo da `str`. +Isso é uma simplificação; para saber todos os detalhes, dê uma olhada na +«_PEP 393–Flexible String Representation_ (Representação Flexível de Strings)» [.small]#[fpy.li/pep393]#. + +A representação flexível de strings é similar à forma como o tipo `int` funciona no Python 3: +se um inteiro cabe em uma palavra da máquina, ele será armazenado em uma palavra da máquina. +Caso contrário, o interpretador muda para uma representação de tamanho variável, +como aquela do tipo `long` de Python 2. +É bom ver as boas ideias se espalhando. + +Entretanto, sempre podemos contar com o pythonista Armin Ronacher para criticar o Python 3. +Ele me explicou por que, na prática, a PEP 393 é problemática: +basta um ratinho (`RAT`, `U+1F400`) para inflar um texto, +que de outra forma seria inteiramente ASCII, e transformá-lo em um array gigante na memória, +usando quatro bytes por caractere, +quando um byte seria o suficiente para todos os caracteres exceto o `RAT`. +Além disso, por causa de todas as formas como os caracteres Unicode se combinam, +a facilidade de buscar um caractere arbitrário pela posição é +superestimada—e extrair fatias arbitrárias de texto Unicode é no mínimo ingênuo, +e muitas vezes errado, produzindo mojibake. +Com((("emojis", "increasing issues with"))) os emojis se tornando mais populares, +esses problemas só vão piorar. +**** + +<<< diff --git a/vol1/cap05.adoc b/vol1/cap05.adoc new file mode 100644 index 00000000..88a26984 --- /dev/null +++ b/vol1/cap05.adoc @@ -0,0 +1,1875 @@ +[[ch_dataclass]] +== Fábricas de classes de dados +:example-number: 0 +:figure-number: 0 + +[quote, Martin Fowler & Kent Beck] +____ +Classes de dados são como crianças. +São um bom ponto de partida, mas para participarem como um objeto adulto, +precisam assumir alguma responsabilidade.footnote:[Fonte: +_Refactoring, First Edition_, capítulo 3, seção _Bad Smells in Code: Data Class_ +(Mau cheiro no código: classe de dados), p. 87.] +____ + +Python oferece algumas formas de criar uma classe simples, +apenas uma coleção de campos, com pouca ou nenhuma funcionalidade adicional. +Esse padrão é conhecido como "classe de dados"—e `dataclasses` é um dos pacotes que suporta tal modelo. +Este((("data class builders", "topics covered"))) +capítulo trata de três diferentes fábricas de classes que +podem ser utilizadas como atalhos para escrever classes de dados: + +`collections.namedtuple`:: A forma mais simples—disponível desde o Python 2.6. +`typing.NamedTuple`:: Uma alternativa que requer dicas de tipo nos campos—desde o Python 3.5, +com a sintaxe `class` adicionada no 3.6. +`@dataclasses.dataclass`:: Um decorador de classe que permite mais customização +que as alternativas anteriores, acrescentando várias opções e, +potencialmente, mais complexidade—desde o Python 3.7. + +Após falar sobre essas fábricas de classes, vamos discutir o motivo de _classe de dados_ +ser também o nome de um((("code smells"))) _code smell_ (odor no código): +um padrão de programação que pode ser um sintoma de um mau design orientado a objetos. + +[NOTE] +==== +A classe `typing.TypedDict` pode((("TypedDict"))) +parecer sintaticamente com uma fábrica de classes de dados. +Ela usa uma sintaxe similar, e é descrita pouco após `typing.NamedTuple` na +«documentação do módulo `typing`» [.small]#[fpy.li/3n]# do Python 3.11. + +Entretanto, `TypedDict` não cria classes concretas que possam ser instanciadas. +Ela é apenas a sintaxe para escrever dicas de tipo para parâmetros de função e +variáveis que aceitarão valores de mapeamentos como registros, +enquanto suas chaves serão os nomes dos campos. +Veremos mais sobre isso na «Seção 15.3» [.small]#[vol.2, fpy.li/29]#. +==== + + +=== Novidades neste capítulo + +Este((("data class builders", "significant changes to"))) capítulo é novo +nesta segunda edição do _Python Fluente_. +A <> era parte do «Capítulo 2» da primeira edição, +mas o restante do capítulo é inteiramente inédito. + +Vamos começar por uma visão geral, por alto, das três fábricas de classes. + +[[data_class_overview_sec]] +=== Visão geral das fábricas de classes de dados + +Considere((("data class builders", "overview of", id="DCBover05"))) uma classe simples, +representando um par de coordenadas geográficas, como aquela no <>. + +[[coord_class_ex]] +._class/coordinates.py_ +==== +[source, python] +---- +include::../code/05-data-classes/class/coordinates.py[tags=COORDINATE] +---- +==== + +A tarefa da classe `Coordinate` é manter os atributos latitude e longitude. +Escrever o `+__init__+` padrão fica cansativo muito rápido, +especialmente se sua classe tiver mais que alguns poucos atributos: +cada um deles é mencionado três vezes! +E aquele código repetitivo não fornece algumas funcionalidades básicas +que esperamos de um objeto Python: + +[source, python] +---- +>>> from coordinates import Coordinate +>>> moscow = Coordinate(55.76, 37.62) +>>> moscow + <1> +>>> location = Coordinate(55.76, 37.62) +>>> location == moscow <2> +False +>>> (location.lat, location.lon) == (moscow.lat, moscow.lon) <3> +True +---- +<1> O `+__repr__+` herdado de `object` não é muito útil. +<2> O `==` não faz sentido; o método `+__eq__+` herdado de `object` compara os IDs dos objetos. +<3> Comparar duas coordenadas exige a comparação explícita de cada atributo. + +As fábricas de classes de dados tratadas nesse capítulo fornecem automaticamente +os métodos `+__init__+`, `+__repr__+`, e `+__eq__+` necessários, +além de outros recursos úteis. + +[NOTE] +==== +Nenhuma das fábricas de classes discutidas aqui depende de herança para funcionar. +Tanto `collections.namedtuple` quanto `typing.NamedTuple` criam subclasses de `tuple`. +O `@dataclass` é um decorador de classe, não afeta de forma alguma a hierarquia de classes. +Cada um deles utiliza técnicas diferentes de metaprogramação para +injetar métodos e atributos de dados na classe em construção. +==== + +Aqui está uma classe `Coordinate` criada com uma `namedtuple`—uma função +que fabrica uma subclasse de `tuple` com o nome e os campos especificados: + +[source, python] +---- +>>> from collections import namedtuple +>>> Coordinate = namedtuple('Coordinate', 'lat lon') +>>> issubclass(Coordinate, tuple) +True +>>> moscow = Coordinate(55.756, 37.617) +>>> moscow +Coordinate(lat=55.756, lon=37.617) <1> +>>> moscow == Coordinate(lat=55.756, lon=37.617) <2> +True +---- +<1> Um `+__repr__+` útil. +<2> Um `+__eq__+` que faz sentido. + +A `typing.NamedTuple`, mais recente, oferece a mesma funcionalidade e +acrescenta anotações de tipo a cada campo: + +[source, python] +---- +>>> import typing +>>> Coordinate = typing.NamedTuple('Coordinate', +... [('lat', float), ('lon', float)]) +>>> issubclass(Coordinate, tuple) +True +>>> typing.get_type_hints(Coordinate) +{'lat': , 'lon': } +---- + +[TIP] +==== +Uma tupla nomeada e com dicas de tipo pode também ser construída passando +os campos como argumentos nomeados, assim: + +[source, python] +---- +Coordinate = typing.NamedTuple('Coordinate', lat=float, lon=float) +---- + +Além de ser mais legível, essa forma permite fornecer o mapeamento de campos e +tipos como `**fields_and_types`. +==== + +Desde o Python 3.6, `typing.NamedTuple` pode também ser usada em uma instrução `class`, +com as anotações de tipo escritas como descrito na +«_PEP 526—Syntax for Variable Annotations_ (Sintaxe para Anotações de Variáveis)» [.small]#[fpy.li/pep526]#. +É mais legível, facilita sobrescrever métodos ou acrescentar métodos novos. +O <> é a mesma classe `Coordinate`, com um par de atributos `float` +e um `+__str__+` customizado, para mostrar a coordenada no formato 55.8°N, 37.6°E. + +[[coord_tuple_ex]] +._typing_namedtuple/coordinates.py_ +==== +[source, python] +---- +include::../code/05-data-classes/typing_namedtuple/coordinates.py[tags=COORDINATE] +---- +==== + +[WARNING] +==== +Apesar de `NamedTuple` aparecer na declaração `class` como uma superclasse, ela não é. +`typing.NamedTuple` usa a funcionalidade avançada de uma +metaclasse.footnote:[As metaclasses são um dos assuntos tratados no +«Capítulo 24» [.small]#[vol.3, fpy.li/24].]# para customizar a criação da classe do usuário.] +Confira isso: + +[source, python] +---- +>>> issubclass(Coordinate, typing.NamedTuple) +False +>>> issubclass(Coordinate, tuple) +True +---- + +==== + +No método `+__init__+` gerado por `typing.NamedTuple`, +os campos aparecem como parâmetros, na mesma ordem em que aparecem na declaração `class`. + +Assim como `typing.NamedTuple`, o decorador `dataclass` suporta a sintaxe da +«_PEP 526_» [.small]#[fpy.li/pep526]# para declarar atributos de instância. +O decorador lê as anotações das variáveis e gera métodos automaticamente para sua classe. +Como comparação, veja a classe `Coordinate` equivalente escrita com +o decorador `dataclass`. + +[[coord_dataclass_ex]] +._dataclass/coordinates.py_ +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/coordinates.py[tags=COORDINATE] +---- +==== + +Observe que o corpo da classe no <> e no <> +é idêntico—a diferença está na própria declaração `class`. +O decorador `@dataclass` não depende de herança ou de uma metaclasse, +então não deve interferir no uso desses mecanismos pelo +usuário.footnote:[Decoradores de classe são discutidos no «Capítulo 24» [.small]#[vol.3, fpy.li/24]]#, +na seção "Metaprogramação de classes", junto com as metaclasses. +Ambos são formas de customizar o comportamento de uma classe além do que seria possível com herança.] +A classe `Coordinate` no <> é uma subclasse de +`object`.((("", startref="DCBover05"))) + +[[dc_main_features_sec]] +==== Principais recursos + +As((("data class builders", "main features", id="DCBmain05"))) diferentes fábricas de classes de dados +têm muito em comum, como resume a <>. + +[[builders_compared_tbl]] +.Recursos selecionados, comparando as três fábricas de classes de dados; `x` é uma instância de uma classe de dados daquele tipo +[options="header", cols="3,4,4,6"] +|============================================================================================================================== +| | namedtuple | NamedTuple | dataclass +| instâncias mutáveis | NÃO | NÃO | SIM +| sintaxe de declaração de classe | NÃO | SIM | SIM +| criar um dict | `x._asdict()` | `x._asdict()` | `dataclasses.asdict(x)` +| obter nomes dos campos | `x._fields` | `x._fields` | `[f.name for f in dataclasses.fields(x)]` +| obter defaults | `x._field_defaults` | `x._field_defaults` | `[f.default for f in dataclasses.fields(x)]` +| obter tipos dos campos | N/A | `x.__annotations__` | `x.__annotations__` +| nova instância com modificações | `x._replace(…)` | `x._replace(…)` | `dataclasses.replace(x, …)` +| nova classe durante a execução | `namedtuple(…)` | `NamedTuple(…)` | `dataclasses.make_dataclass(…)` +|============================================================================================================================== + +[WARNING] +==== +As classes criadas por `typing.NamedTuple` e `@dataclass` têm um atributo `+__annotations__+`, +contendo as dicas de tipo dos campos. +Entretanto, ler `+__annotations__+` diretamente não é recomendado. +Em vez disso, a prática recomendada é chamar +«`inspect.get_annotations(MyClass)`» [.small]#[fpy.li/3p]# (a partir de Python 3.10) ou +«`typing.get_type_hints(MyClass)`» [.small]#[fpy.li/3z]# +(Python 3.5 a 3.9). +Isso porque tais funções fornecem serviços adicionais, +como a resolução de referências futuras nas dicas de tipo. +Voltaremos a isso bem mais tarde, na «Seção 15.5.1» [.small]#[vol.2, fpy.li/2a]#. +==== + +Vamos agora detalhar aqueles recursos principais. + + +===== Instâncias mutáveis + +A diferença fundamental entre essas três fábricas de classes é que +`collections.namedtuple` e `typing.NamedTuple` criam subclasses de `tuple`, +e portanto as instâncias são imutáveis. +Por default, `@dataclass` produz classes mutáveis. +Mas o decorador aceita o argumento nomeado `frozen`—que aparece no <>. +Quando `frozen=True`, a classe vai gerar uma exceção +se você tentar atribuir um valor a um campo após a instância ter sido inicializada. + + +[[class_syntax_feature]] +===== Sintaxe de declaração de classe + +Apenas `typing.NamedTuple` e `dataclass` suportam a sintaxe da instrução `class`, +o que facilita acrescentar métodos e docstrings à classe que está sendo criada. + +===== Construir um dict + +As duas variantes de tuplas nomeadas fornecem um método de instância (`._asdict`), +para construir um objeto `dict` a partir dos campos de uma instância de classe de dados. +O módulo `dataclasses` fornece uma função para fazer o mesmo: `dataclasses.asdict`. + +===== Obter nomes dos campos e valores default + +Todas as três fábricas de classes permitem que você obtenha os nomes dos campos e +os valores default (que podem ser configurados para cada campo). +Nas classes de tuplas nomeadas, aqueles metadados estão nos +atributos de classe `._fields` e `._fields_defaults`. +Você pode obter os mesmos metadados em uma classe decorada com `dataclass` usando +a função `fields` do módulo `dataclasses`. +Ela devolve uma tupla de objetos `Field` com vários atributos, incluindo `name` e `default`. + + +===== Obter os tipos dos campos + +Classes definidas com a ajuda de `typing.NamedTuple` e `@dataclass` +contêm um mapeamento dos nomes dos campos para seus tipos, o atributo de classe `+__annotations__+`. +Como já mencionado, use a função `typing.get_type_hints` em vez de ler diretamente de +`+__annotations__+`. + + +===== Nova instância com modificações + +Dada uma instância de tupla nomeada `x`, a chamada `+x._replace(**kwargs)+` +devolve uma nova instância com os valores de alguns atributos modificados, +de acordo com os argumentos nomeados incluídos na chamada. +A função de módulo `dataclasses.replace(x, **kwargs)` +faz o mesmo para uma instância de uma classe decorada com `dataclass`. + + +===== Nova classe durante a execução + +Apesar da sintaxe de declaração de classe ser mais legível, ela é estática, +registrada no código-fonte. +Um framework pode precisar criar classes de dados durante a execução. +Neste caso, use a sintaxe de chamada de função de `collections.namedtuple`, +que também é suportada por `typing.NamedTuple`. +O módulo `dataclasses` oferece a função `make_dataclass`, com o mesmo propósito. + +Após essa visão geral dos principais recursos das fábricas de classes de dados, +vamos examinar cada uma delas mais de perto, começando pela mais simples.((("", startref="DCBmain05"))) + + +[[classic_named_tuples_sec]] +=== Tuplas nomeadas clássicas + +A((("data class builders", "classic named tuples", id="DCBnamedt05")))((("tuples", +"classic named tuples", id="Tclassic05")))((("collections.namedtuple", +id="colnamedt05")))((("namedtuple", id="namedt05"))) +função `collections.namedtuple` é uma fábrica que cria subclasses de `tuple`, +acrescidas de nomes de campos, um nome de classe, e um `+__repr__+` informativo. +Classes criadas com `namedtuple` podem ser usadas onde quer que uma tupla seja necessária. +Na verdade, muitas funções da biblioteca padrão, que antes devolviam tuplas simples, +agora devolvem tuplas nomeadas, sem afetar de forma alguma o código que as utiliza. + +[TIP] +==== +Cada instância de uma classe criada por `namedtuple` usa exatamente +a mesma quantidade de memória usada por uma tupla, pois os nomes dos campos são armazenados na classe. +==== + +O <> mostra como poderíamos definir +uma tupla nomeada para registrar informações sobre uma cidade. + +[[ex_named_tuple_1]] +.Definindo e usando um tipo tupla nomeada +==== +[source, python] +---- +>>> from collections import namedtuple +>>> City = namedtuple('City', 'name country population coordinates') <1> +>>> tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667)) <2> +>>> tokyo +City(name='Tokyo', country='JP', population=36.933, +coordinates=(35.689722, 139.691667)) +>>> tokyo.population <3> +36.933 +>>> tokyo.coordinates +(35.689722, 139.691667) +>>> tokyo[1] +'JP' +---- +==== +<1> São necessários dois parâmetros para criar uma tupla nomeada: +um nome de classe e uma lista de nomes de campos, +que podem ser passados como um iterável de strings ou +como uma única string com os nomes delimitados por espaços. +<2> Na inicialização de uma instância, os valores dos campos devem ser passados +como argumentos posicionais separados (uma `tuple`, por outro lado, é inicializada com um único iterável) +<3> É possível acessar os campos por nome ou por posição. + +Como uma subclasse de `tuple`, `City` herda métodos úteis, tal como `+__eq__+` +e os métodos especiais para operadores de comparação—incluindo `+__lt__+`, +que permite ordenar listas de instâncias de `City`. + +Uma tupla nomeada oferece alguns atributos e métodos além daqueles herdados de `tuple`. +O <> demonstra os mais úteis: o atributo de classe `_fields`, +o método de classe `_make(iterable)`, e o método de instância `_asdict()`. + +<<< + +[[ex_named_tuple_2]] +.Atributos e métodos das tuplas nomeadas (continuando o exemplo anterior) +==== +[source, python] +---- +>>> City._fields <1> +('name', 'country', 'population', 'location') +>>> Coordinate = namedtuple('Coordinate', 'lat lon') +>>> delhi_data = ('Delhi NCR', 'IN', 21.935, Coordinate(28.613889, 77.208889)) +>>> delhi = City._make(delhi_data) <2> +>>> delhi._asdict() <3> +{'name': 'Delhi NCR', 'country': 'IN', 'population': 21.935, +'location': Coordinate(lat=28.613889, lon=77.208889)} +>>> import json +>>> json.dumps(delhi._asdict()) <4> +'{"name": "Delhi NCR", "country": "IN", "population": 21.935, +"location": [28.613889, 77.208889]}' +---- +==== +<1> `._fields` é uma tupla com os nomes dos campos da classe. +<2> `._make()` cria uma `City` a partir de um iterável; `City(*delhi_data)` faria o mesmo. +<3> `._asdict()` devolve um `dict` criado a partir da instância de tupla nomeada. +<4> `._asdict()` é útil para serializar os dados no formato JSON, por exemplo. + +[WARNING] +===== +Até Python 3.7, o método `_asdict` devolvia um `OrderedDict`. +Desde o Python 3.8, ele devolve um `dict` simples—o que não é problema, +agora que podemos confiar na ordem de inserção das chaves. +Se você precisar de um `OrderedDict`, a +«documentação do `_asdict`» [.small]#[fpy.li/3q]# +recomenda criar um com o resultado: `OrderedDict(x._asdict())`. +===== + +Desde o Python 3.7, a `namedtuple` aceita o argumento nomeado `defaults`, +fornecendo um iterável de N valores default para cada um dos N campos mais à direita na definição da classe. +O <> mostra como definir uma tupla nomeada +`Coordinate` com um valor default para o campo `reference`. + +<<< +[[ex_coord_tuple_default]] +.Atributos e métodos das tuplas nomeadas, continuando do <> +==== +[source, python] +---- +>>> Coordinate = namedtuple('Coordinate', 'lat lon reference', defaults=['WGS84']) +>>> Coordinate(0, 0) +Coordinate(lat=0, lon=0, reference='WGS84') +>>> Coordinate._field_defaults +{'reference': 'WGS84'} +---- +==== + +Na <>, mencionei que é mais fácil programar métodos com +a sintaxe de classe suportada por `typing.NamedTuple` e `@dataclass`. +Você também pode acrescentar métodos a uma `namedtuple`, mas é um remendo. +Pule a próxima caixinha se você não estiver interessada em gambiarras. + +[[hacking_namedtuple_box]] +.Remendando uma tupla nomeada para injetar um método +**** + +Lembre-se como criamos a classe `Card` no <> do <>: + +[source, python] +---- +Card = collections.namedtuple('Card', ['rank', 'suit']) +---- + +Mais adiante no <>, escrevi uma função `spades_high`, para ordenação. +Seria bom que aquela lógica estivesse encapsulada em um método de `Card`, +mas acrescentar `spades_high` a `Card` sem usar uma declaração `class` exige um remendo rápido: +definir a função e então atribuí-la a um atributo de classe. +O <> mostra como isso é feito: + + +[[ranked_card_ex]] +.frenchdeck.doctest: Acrescentando um atributo de classe e um método a `Card`, a `namedtuple` da <> +==== +[source, python] +---- +include::../code/05-data-classes/frenchdeck.doctest[tags=SPADES_HIGH] +---- +<1> Acrescenta um atributo de classe com valores para cada naipe. +<2> A função `spades_high` vai se tornar um método; o primeiro argumento não precisa ser chamado de `self`. +Ao ser invocada como método, ela receberá a instância no primeiro argumento. +<3> Anexa a função à classe `Card` como um método chamado `overall_rank`. +<4> Funciona! +==== + +Para uma melhor legibilidade e para ajudar na manutenção futura, +é muito melhor programar métodos dentro de uma declaração `class`. +Mas é bom saber que essa gambiarra é possível, pois às vezes pode ser +útil.footnote:[Se você conhece Ruby, sabe que injetar métodos é uma técnica bastante conhecida, +apesar de controversa, entre _rubystas_. +Em Python isso não é tão comum, pois não funciona com nenhum dos tipos embutidos—`str`, `list`, etc. +Considero essa limitação de Python uma bêncão.] + +Isso foi apenas um pequeno desvio para demonstrar o poder de uma linguagem dinâmica. +**** + +[[typed_named_tuples_sec]] +=== Tuplas nomeadas com tipo + +A((("typing.NamedTuple")))((("tuples", "typing.NamedTuple")))((("data class builders", "typed named tuples"))) +classe `Coordinate` com um campo default, do <>, +pode ser escrita usando `typing.NamedTuple`, como se vê no <>. + +[[coord_tuple_default_ex]] +._typing_namedtuple/coordinates2.py_ +==== +[source, python] +---- +include::../code/05-data-classes/typing_namedtuple/coordinates2.py[tags=COORDINATE] +---- +<1> Cada campo de instância precisa ter uma anotação de tipo. +<2> O campo `reference` é anotado com um tipo e um valor default. +==== + +As classes criadas por `typing.NamedTuple` não tem qualquer método +além daqueles que `collections.namedtuple` também gera, e aqueles herdados de `tuple`. +A única diferença é a presença do atributo de classe `+__annotations__+`, +que Python ignora durante a execução do programa. + +Dado que o principal recurso de `typing.NamedTuple` são as anotações de tipo, +vamos dar uma rápida olhada nisso antes de continuar nossa exploração das fábricas de classes de dados. + + +=== Introdução às dicas de tipo + +Dicas((("data class builders", "type hints", id="DCBhint05")))((("type hints (type annotations)", +"basics of", id="typehint05"))) de tipo (_type hints_, também chamadas anotações de tipo) são formas de +declarar o tipo esperado dos argumentos de funções, dos valores devolvidos em `return`, +das variáveis e dos atributos de objetos. + +[NOTE] +==== +Essa é uma introdução muito breve sobre dicas de tipo, +suficiente apenas para que a sintaxe e o propósito das anotações usadas +nas declarações de `typing.NamedTuple` e `@dataclass` façam sentido. +Vamos tratar de anotações de tipo nas assinaturas de função no <> +e de anotações mais avançadas no «Capítulo 15» [.small]#[vol.2, fpy.li/15]#. +Aqui vamos ver principalmente dicas com tipos embutidos simples, como `str`, `int`, e `float`, +que são provavelmente os tipos mais comuns usados para anotar campos em classes de dados. +==== + +A primeira coisa que você precisa saber sobre dicas de tipo é que elas não são checadas +pelo compilador de bytecode ou pelo interpretador de Python. + +[[no_runtime_effect_sec]] +==== Nenhum efeito durante a execução + +Pense nas dicas de tipo de Python como +"documentação que pode ser utilizada por IDEs e checadores de tipos". + +Isso porque as dicas de tipo não têm qualquer impacto sobre o comportamento +de programas em Python durante a execução. +Veja o <>. + +[[no_runtime_check_ex]] +.Python não checa dicas de tipo durante a execução de um programa +==== +[source, python] +---- +>>> import typing +>>> class Coordinate(typing.NamedTuple): +... lat: float +... lon: float +... +>>> trash = Coordinate('Ni!', None) +>>> print(trash) +Coordinate(lat='Ni!', lon=None) # <1> +---- +==== +<1> Eu avisei: não há checagem de tipos durante a execução! + +Se você incluir o código do <> em um módulo de Python, +ele vai rodar e exibir uma `Coordinate` sem sentido, +sem gerar erro ou aviso: + +[source] +---- +$ python3 nocheck_demo.py +Coordinate(lat='Ni!', lon=None) +---- + +O objetivo primário das dicas de tipo é ajudar os checadores de tipos externos, +como o «Mypy» [.small]#[fpy.li/mypy]# ou o checador de tipos embutido da +«IDE PyCharm» [.small]#[fpy.li/5-5]#. +Essas são ferramentas de análise estática: elas checam código-fonte Python "parado", +não código em execução. +Para observar o efeito das dicas de tipo, precisamos executar alguma dessas +ferramentas sobre seu código—como um _linter_ (analisador de código). +Por exemplo, eis o que o Mypy tem a dizer sobre o exemplo anterior: + +[source] +---- +$ mypy nocheck_demo.py +nocheck_demo.py:8: error: Argument 1 to "Coordinate" has +incompatible type "str"; expected "float" +nocheck_demo.py:8: error: Argument 2 to "Coordinate" has +incompatible type "None"; expected "float" +---- + +Como se vê, dada a definição de `Coordinate`, +o Mypy registra que os dois argumentos para criar uma instância devem ser do tipo `float`, +mas a atribuição a `trash` usa uma `str` e `None`.footnote:[No contexto das dicas de tipo, +`None` não é o singleton `NoneType`, mas um apelido para o próprio `NoneType`. +Se pararmos para pensar, isso é estranho, +mas agrada nossa intuição e torna as anotações de valores devolvidos por uma função mais fáceis de ler, +no caso comum de funções que devolvem `None`.] + +Vamos falar agora sobre a sintaxe e o significado das dicas de tipo. + +[[var_annotation_syntax]] +==== Sintaxe de anotação de variáveis + +Tanto((("variable annotations", "syntax of"))) `typing.NamedTuple` quanto `@dataclass` +usam a sintaxe de anotações de variáveis definida na «_PEP 526_» [.small]#[fpy.li/pep526]#. +Vamos ver aqui uma pequena introdução àquela sintaxe, +no contexto da definição de atributos em declarações `class`. + +A sintaxe básica da anotação de variáveis é : + +[source, python] +---- +var_name: some_type +---- + +A seção «_Acceptable type hints_ (Dicas de tipo aceitáveis)» [.small]#[fpy.li/5-6]# +na PEP 484 explica quais tipos podem ser usados. +Porém, no contexto da definição de uma classe de dados, os tipos mais úteis geralmente serão os seguintes: + +* Uma classe concreta, por exemplo `str` ou `FrenchDeck`. +* Um tipo de coleção parametrizado, como `list[int]`, `tuple[str, float]`, etc. +* `typing.Optional`, por exemplo `Optional[str]`—para declarar um campo que pode ser uma `str` ou `None`, +que também pode ser escrito como `str | None`. + +Você também pode inicializar uma variável com um valor. +Em uma declaração de `typing.NamedTuple` ou `@dataclass`, +aquele valor se tornará o default daquele atributo quando +o argumento for omitido na chamada de inicialização: + +[source, python] +---- +var_name: some_type = a_value +---- + +==== O significado das anotações de variáveis + +Vimos((("variable annotations", "meaning of", id="VAmean05"))) na <> +que dicas de tipo não têm qualquer efeito durante a execução de um programa. +Mas no momento da importação—quando um módulo é carregado—o Python as lê para construir +o dicionário `+__annotations__+`, usado por `typing.NamedTuple` e `@dataclass` +para aprimorar a classe. + +Vamos começar essa exploração no <>, com uma classe simples, +para mais tarde ver que recursos adicionais são acrescentados por `typing.NamedTuple` e `@dataclass`. + +[[ex_demo_plain]] +.meaning/demo_plain.py: uma classe básica com dicas de tipo +==== +[source, python] +---- +include::../code/05-data-classes/meaning/demo_plain.py[] +---- +==== +<1> `a` se torna um registro em `+__annotations__+`, mas é então descartada: +nenhum atributo chamado `a` é criado na classe. +<2> `b` é salvo como uma anotação, e também se torna um atributo de classe com o valor `1.1`. +<3> `c` é só um bom e velho atributo de classe básico, sem uma anotação. + +Podemos checar isso no console, primeiro lendo o `+__annotations__+` da `DemoPlainClass`, +e daí tentando obter os atributos chamados `a`, `b`, e `c`: + +[source, python] +---- +>>> from demo_plain import DemoPlainClass +>>> DemoPlainClass.__annotations__ +{'a': , 'b': } +>>> DemoPlainClass.a +Traceback (most recent call last): + File "", line 1, in +AttributeError: type object 'DemoPlainClass' has no attribute 'a' +>>> DemoPlainClass.b +1.1 +>>> DemoPlainClass.c +'spam' +---- + +Observe que o atributo especial `+__annotations__+` é criado pelo interpretador +para registrar dicas de tipo que aparecem no código-fonte—mesmo em uma classe básica. +O identificador `a` sobrevive apenas como uma anotação, não se torna um atributo da classe, +porque nenhum valor é atribuído a +ele.footnote:[O conceito de _undefined_, um dos erros mais tolos no design de JavaScript, +não existe no Python. +Obrigado, Guido!] +Os identificadores `b` e `c` se tornam atributos de classe porque recebem valores. + +Nenhum desses três atributos estará em uma nova instância de `DemoPlainClass`. +Se você criar um objeto `o = DemoPlainClass()`, `o.a` vai gerar um `AttributeError`, +enquanto `o.b` e `o.c` vão obter os atributos de classe com os valores +`1.1` e `'spam'`—que é apenas o comportamento normal de um objeto Python. + +===== Inspecionando uma typing.NamedTuple + +Agora vamos examinar uma classe criada com `typing.NamedTuple` (<>), +usando os mesmos atributos e anotações da `DemoPlainClass` do <>. + +[[ex_demo_nt]] +.meaning/demo_nt.py: uma classe criada com `typing.NamedTuple` +==== +[source, python] +---- +include::../code/05-data-classes/meaning/demo_nt.py[] +---- +==== +<1> `a` se torna uma anotação e também um atributo de instância. +<2> `b` é outra anotação, mas também se torna um atributo de instância com o valor default `1.1`. +<3> `c` é só um bom e velho atributo de classe comum; não será mencionado em nenhuma anotação. + +Inspecionando a `DemoNTClass`, temos o seguinte: + +[source, python] +---- +>>> from demo_nt import DemoNTClass +>>> DemoNTClass.__annotations__ +{'a': , 'b': } +>>> DemoNTClass.a +<_collections._tuplegetter object at 0x101f0f940> +>>> DemoNTClass.b +<_collections._tuplegetter object at 0x101f0f8b0> +>>> DemoNTClass.c +'spam' +---- + +Aqui vemos as mesmas anotações para `a` e `b` que vimos no <>. +Mas `typing.NamedTuple` cria os atributos de classe `a` e `b`. +O atributo c é apenas um atributo de classe simples, com o valor `'spam'`. + +<<< +Os atributos de classe `a` e `b` são((("descriptors"))) descritores +(_descriptors_)—um recurso avançado tratado no «Capítulo 23» [.small]#[vol.3, fpy.li/23]#. +Por ora, pense neles como similares a um _getter_ de propriedades do +objetofootnote:[NT: Um _getter_ é um método que devolve o valor de um atributo do objeto. +Para propriedades mutáveis, o _getter_ vem geralmente acompanhado por um _setter_, +que modifica a mesma propriedade. +Os nomes derivam dos verbos em inglês _get_ (obter, receber) e _set_ (definir, estabelecer).]: +métodos que não exigem o operador explícito de chamada `()` para obter um atributo de instância. +Na prática, isso significa que `a` e `b` vão funcionar como atributos de instância +somente para leitura—o que faz sentido, se lembrarmos que instâncias de `DemoNTClass` +são apenas tuplas enfeitadas, e tuplas são imutáveis. + +A `DemoNTClass` também recebe uma docstring customizada: + +[source, python] +---- +>>> DemoNTClass.__doc__ +'DemoNTClass(a, b)' +---- + +Vamos examinar uma instância de `DemoNTClass`: + +[source, python] +---- +>>> nt = DemoNTClass(8) +>>> nt.a +8 +>>> nt.b +1.1 +>>> nt.c +'spam' +---- + +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` 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`, +vai gerar uma exceção `AttributeError`, com mensagens de erro sutilmente diferentes. +Tente fazer isso, e reflita sobre as mensagens. + + +[[inspecting_dataclass_sec]] +===== Inspecionando uma classe decorada com dataclass + +Vamos agora examinar o <>. + +[[ex_demo_dc]] +.meaning/demo_dc.py: uma classe decorada com `@dataclass` +==== +[source, python] +---- +include::../code/05-data-classes/meaning/demo_dc.py[] +---- +==== +<1> `a` se torna uma anotação, e também um atributo de instância controlado por um descritor. +<2> `b` é outra anotação, e também se torna um atributo de instância +com um descritor e um valor default de `1.1`. +<3> `c` é apenas um atributo de classe comum; nenhuma anotação se refere a ele. + +Podemos então acessar os atributos `+__annotations__+`, `+__doc__+`, +`a`, `b`, `c` em `DemoDataClass`: + +[source, python] +---- +>>> from demo_dc import DemoDataClass +>>> DemoDataClass.__annotations__ +{'a': , 'b': } +>>> DemoDataClass.__doc__ +'DemoDataClass(a: int, b: float = 1.1)' +>>> DemoDataClass.a +Traceback (most recent call last): + File "", line 1, in +AttributeError: type object 'DemoDataClass' has no attribute 'a' +>>> DemoDataClass.b +1.1 +>>> DemoDataClass.c +'spam' +---- + +O `+__annotations__+` e o `+__doc__+` não guardam surpresas. +Entretanto, não há um atributo chamado `a` em `DemoDataClass`—diferente +do que ocorre na `DemoNTClass` do <>, +que inclui um descritor para obter `a` das instâncias da classe, +como atributos somente para leitura (aquele misterioso `<_collections._tuplegetter_>`). +Isso ocorre porque o atributo `a` só existirá nas instâncias de `DemoDataClass`. +Será um atributo público, que poderemos obter e definir, a menos que a classe seja _frozen_. +Mas `b` e `c` existem como atributos de classe, +com `b` contendo o valor default para o atributo de instância `b`, +enquanto `c` é apenas um atributo de classe que não será vinculado a instâncias. + +Vejamos como se parece uma instância de `DemoDataClass`: + +[source, python] +---- +>>> dc = DemoDataClass(9) +>>> dc.a +9 +>>> dc.b +1.1 +>>> dc.c +'spam' +---- + +Novamente, `a` e `b` são atributos de instância, +e `c` é um atributo de classe obtido através da instância. + +Como mencionado, instâncias de `DemoDataClass` são mutáveis—e +nenhuma checagem de tipos é realizada durante a execução: + +[source, python] +---- +>>> dc.a = 10 +>>> dc.b = 'oops' +---- + +Podemos fazer atribuições ainda mais ridículas: + +[source, python] +---- +>>> dc.c = 'whatever' +>>> dc.z = 'secret stash' +---- + +Agora a instância `dc` tem um atributo `c`—mas isso não muda o atributo de classe `c`. +E podemos adicionar um novo atributo `z`. +Isso é o comportamento normal de Python: instâncias podem ter seus próprios atributos, +que não aparecem na classe.footnote:[Definir um atributo após o `+__init__+` +prejudica a otimização de uso de memória com o compartilhamento das chaves do `+__dict__+`, +mencionada na +<>.]((("", startref="VAmean05")))((("", startref="typehint05")))((("", startref="DCBhint05"))) + + +=== Mais detalhes sobre @dataclass + +Até((("data class builders", "@dataclass", id="DCBatdataclass05")))((("@dataclass", +"keyword parameters accepted by"))) agora, só vimos exemplos simples do uso de `@dataclass`. +Esse decorador aceita vários argumentos nomeados. +Esta é sua assinatura: + +[source, python] +---- +@dataclass(*, init=True, repr=True, eq=True, order=False, + unsafe_hash=False, frozen=False) +---- + +O `*` na primeira posição significa que os parâmetros restantes são todos parâmetros nomeados. +A <> os descreve. + +[[dataclass_options_tbl]] +.Parâmetros nomeados aceitos pelo decorador `@dataclass` +[options="header",cols="2,2,2,5"] +|==================================================================================================================================================================== +| opção | efeito | default | notas +| `init` | Gera o `+__init__+` | `True` | Ignorado se o `+__init__+` for implementado pelo usuário. +| `repr` | Gera o `+__repr__+` | `True` | Ignorado se o `+__repr__+` for implementado pelo usuário. +| `eq` | Gera o `+__eq__+` | `True` | Ignorado se o `+__eq__+` for implementado pelo usuário. +| `order` | Gera `+__lt__+`, `+__le__+`, `+__gt__+`, `+__ge__+` | `False` | Se `True`, causa uma exceção se `eq=False`, ou se qualquer um dos métodos de comparação que seriam gerados estiver definido ou for herdado. +| `unsafe_hash` | Gera o `+__hash__+` | `False` | Semântica complexa e várias restrições—veja a: «documentação de dataclass» [.small]#[fpy.li/3r]#. +| `frozen` | Cria instâncias "imutáveis" | `False` | As instâncias estarão razoavelmente protegidas contra mudanças acidentais, mas não serão realmente imutáveis.footnote:[O `@dataclass` emula a imutabilidade criando um `+__setattr__+` e um `+__delattr__+` +que geram um `dataclass.FrozenInstanceError`—uma subclasse de `AttributeError`—quando o usuário tenta definir ou apagar o valor de um campo.] +|==================================================================================================================================================================== + + +Os((("@dataclass", "default settings"))) defaults são realmente +as configurações mais úteis para os casos de uso mais comuns. +As opções mais prováveis de serem modificadas de seus defaults são: + +`frozen=True`:: Protege as instâncias da classe de modificações acidentais. +`order=True`:: Permite ordenar as instâncias da classe de dados. + +Dada a natureza dinâmica de objetos Python, +não é muito difícil para um programador curioso contornar a proteção oferecida por `frozen=True`. +Mas os truques necessários são fáceis de perceber em uma revisão do código. + +Se((("@dataclass", "__hash__ method")))((("__hash__"))) +tanto o argumento `eq` quanto o `frozen` forem `True`, `@dataclass` produz um método +`+__hash__+` adequado, e daí as instâncias serão _hashable_. +O `+__hash__+` gerado usará dados de todos os campos que não forem +individualmente excluídos usando uma opção de campo, que veremos na <>. +Se `frozen=False` (o default), `@dataclass` definirá `+__hash__+` como `None`, +sinalizando que as instâncias não são hashable, e portanto sobrescrevendo o `+__hash__+` +de qualquer superclasse. + +A «_PEP 557—Data Classes_ (Classe de Dados)» [.small]#[fpy.li/pep557]# diz o seguinte sobre `unsafe_hash`: + +[quote] +____ +Apesar de não ser recomendado, você pode forçar `@dataclass` a criar um método +`+__hash__+` com `unsafe_hash=True`. +Pode ser esse o caso, se sua classe for logicamente imutável e mesmo assim possa ser modificada. +Este é um caso de uso especializado e deve ser considerado com cuidado. +____ + +Deixo o `unsafe_hash` por aqui. +Se você achar que precisa usar essa opção, leia a +«documentação de `dataclasses.dataclass`» [.small]#[fpy.li/3r]#. + + +Outras customizações da classe de dados gerada podem ser feitas no nível dos campos. + +[[field_options_sec]] +==== Opções de campo + +Já((("@dataclass", "field options", id="atdatafield05"))) vimos a opção de campo mais básica: +fornecer (ou não) um valor default com a dica de tipo. +Os campos de instância declarados se tornarão parâmetros no `+__init__+` gerado. +Python não permite parâmetros sem um default após parâmetros com defaults. +Então, após declarar um campo com um valor default, +cada um dos campos seguintes deve também ter um default. + +Valores default mutáveis são a fonte mais comum de bugs entre desenvolvedores Python iniciantes. +Em definições de função, um valor default mutável é facilmente corrompido, +quando uma invocação da função modifica o default, +mudando o comportamento nas invocações posteriores—um tópico que +vamos explorar na <>. +Atributos de classe são frequentemente usados como valores default de atributos para instâncias, +inclusive em classes de dados. +E o `@dataclass` usa os valores default nas dicas de tipo para gerar parâmetros com defaults no +`+__init__+`. +Para prevenir bugs, o `@dataclass` rejeita o <>. + +[[club_wrong_ex]] +._dataclass/club_wrong.py_: essa classe gera um `ValueError` +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/club_wrong.py[tags=CLUBMEMBER] +---- +==== + +Se você carregar o módulo com aquela classe `ClubMember`, o resultado será este: + +[source] +---- +$ python3 club_wrong.py +Traceback (most recent call last): + File "club_wrong.py", line 4, in + class ClubMember: + ...várias linhas omitidas... +ValueError: mutable default for field guests is not allowed: +use default_factory +---- + +A mensagem do `ValueError` explica o problema e sugere uma solução: usar a `default_factory`. +O <> mostra como corrigir a `ClubMember`. + +[[club_ex]] +._dataclass/club.py_: essa definição de `ClubMember` funciona +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/club.py[] +---- +==== + +No campo `guests` do <>, em vez de uma lista literal, +o valor default é definido chamando a função `dataclasses.field` com `default_factory=list`. + +O parâmetro `default_factory` permite que você forneça uma função, +classe ou qualquer outro invocável, que será chamado sem argumentos, +para gerar um valor default a cada vez que uma instância da classe de dados for criada. +Dessa forma, cada instância de `ClubMember` terá sua própria `list`—ao invés de +todas as instâncias compartilharem a mesma `list` da classe, +que raramente é o que queremos, e muitas vezes é um bug. + +[WARNING] +==== +É bom que `@dataclass` rejeite definições de classe com uma `list` como default em um campo. +Entretanto, entenda que isso é uma solução parcial, que se aplica apenas a `list`, `dict` e `set`. +Outros valores mutáveis usados como default não serão rejeitados por `@dataclass`. +É sua responsabilidade entender o problema e se lembrar de usar uma _factory_ +default para definir valores default mutáveis. +==== + +Se você estudar a documentação do módulo «`dataclasses`» [.small]#[fpy.li/3s]#, +verá um campo `list` definido com uma sintaxe nova, como no <>. + +[[club_generic_ex]] +._dataclass/club_generic.py_: essa definição de `ClubMember` é mais precisa +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/club_generic.py[] +---- +==== +<1> `list[str]` significa "uma lista de str." + +A nova sintaxe `list[str]` é um tipo genérico parametrizado: desde o Python 3.9, +o tipo embutido `list` aceita aquela notação com colchetes para especificar o tipo dos itens da lista. + +[WARNING] +==== +Antes de Python 3.9, as coleções embutidas não suportavam a notação de tipagem genérica. +Como uma solução temporária, há tipos correspondentes de coleções no módulo `typing`. +Se você precisa de uma dica de tipo para uma `list` parametrizada no Python 3.8 ou anterior, +você precisa importar e usar o tipo `List` de `typing`: `List[str]`. +Leia mais sobre isso na caixa <>. +==== + +Vamos tratar dos tipos genéricos no <>. +Por ora, observe que o <> e o <> estão ambos corretos, +e que o checador de tipos Mypy não reclama de nenhuma das duas definições de classe. +A diferença é que aquele `guests: list` significa que `guests` pode ser uma `list` +de objetos de qualquer tipo, enquanto `guests: list[str]` +diz que `guests` deve ser uma `list` na qual cada item é uma `str`. +Isso permite que o checador de tipos encontre (alguns) +bugs em código que insira itens inválidos na lista, ou que leia itens dela. + +A `default_factory` é possivelmente a opção mais comum da função `field`, +mas há várias outras, listadas na <>. + +[[field_options_tbl]] +.Parâmetros nomeados aceitos pela função `field` +[options="header",cols="3,3,8"] +|==================================================================================================================================================================== +| opção | default | significado +| `default` | `_MISSING_TYPE` footnote:[`dataclass._MISSING_TYPE` é um valor sentinela, indicando que a opção não foi fornecida. Ele existe para que se possa definir `None` como um valor default efetivo, um caso de uso comum.]| Valor default para o campo +| `default_factory` | `_MISSING_TYPE` | função com 0 parâmetros usada para produzir um valor default +| `init` | `True` | Incluir o campo nos parâmetros de `+__init__+` +| `repr` | `True` | Incluir o campo em `+__repr__+` +| `compare` | `True` | Usar o campo nos métodos de comparação `+__eq__+`, `+__lt__+`, etc. +| `hash` | ++None++footnote:[A opção `hash=None` significa que o campo será usado em `+__hash__+` apenas se `compare=True`.] | Incluir o campo no cálculo de `+__hash__+` +| `metadata` | `None` | Mapeamento com dados definidos pelo usuário; ignorado por `@dataclass` +|==================================================================================================================================================================== + +A opção `default` existe porque a chamada a `field` toma o lugar do valor default na anotação do campo. +Se você quisesse criar um campo `athlete` com o valor default `False`, +e também omitir aquele campo do método `+__repr__+`, escreveria o seguinte: + +[source, python] +---- +@dataclass +class ClubMember: + name: str + guests: list = field(default_factory=list) + athlete: bool = field(default=False, repr=False) +---- + + +==== Processamento pós-inicialização + +O((("@dataclass", "post-init processing", +id="atdatapost05")))((("__init__")))((("__post_init__"))) +método `+__init__+` gerado por `@dataclass` apenas recebe os argumentos passados e +os atribui (ou seus valores default, se o argumento não estiver presente) aos atributos de instância, +que são campos da instância. +Mas pode ser necessário fazer mais que isso para inicializar a instância. +Se for esse o caso, você pode fornecer um método `+__post_init__+`. +Quando esse método existir, `@dataclass` acrescentará código ao `+__init__+` gerado para invocar +`+__post_init__+` como o último passo da inicialização. + +Casos de uso comuns para `+__post_init__+` são validação e +o cálculo de valores de campos baseado em outros campos. +Vamos estudar um exemplo simples, que usa `+__post_init__+` pelos dois motivos. + +Primeiro, veja o comportamento esperado de uma subclasse de `ClubMember`, +chamada `HackerClubMember`, +ilustrado por doctests no <>. + +[[hackerclub_doctests_ex]] +.`dataclass/hackerclub.py`: doctests para `HackerClubMember` +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/hackerclub.py[tags=DOCTESTS] +---- +==== + +Observe que precisamos fornecer `handle` como um argumento nomeado, +pois `HackerClubMember` herda `name` e `guests` de `ClubMember`, +e acrescenta o campo `handle`. A docstring gerada para `HackerClubMember` +mostra a ordem dos campos na chamada de inicialização: + +[source, python] +---- +>>> HackerClubMember.__doc__ +"HackerClubMember(name: str, guests: list = , handle: str = '')" +---- + +Aqui `` apenas indica que um invocável produzirá +o valor default para `guests` (no nosso caso, a fábrica é a classe `list`). +O ponto é o seguinte: para fornecer um `handle` mas não `guests`, +precisamos passar `handle` como um argumento nomeado. + +A seção «Herança» [.small]#[fpy.li/3t]# na documentação do módulo `dataclasses` +explica como a ordem dos campos é analisada quando existem vários níveis de herança. + +[NOTE] +==== +No «Capítulo 14» [.small]#[vol.2, fpy.li/14]# vamos falar sobre o uso indevido da herança, +especialmente quando as superclasses não são abstratas. +Criar uma hierarquia de classes de dados é, em geral, uma má ideia, +mas nos serviu bem aqui para tornar o <> mais curto, +e permitir que nos concentrássemos na declaração do campo `handle` e na validação com +`+__post_init__+`. +==== + +<<< +O <> mostra a implementação da classe `HackerClubMember`. + +[[hackerclub_ex]] +.`dataclass/hackerclub.py`: classe de dados `HackerClubMember` +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/hackerclub.py[tags=HACKERCLUB] +---- +==== +<1> `HackerClubMember` estende `ClubMember`. +<2> `all_handles` é um atributo de classe. +<3> `handle` é um campo de instância do tipo `str`, +com uma string vazia como valor default; isso o torna opcional. +<4> Obtém a classe da instância. +<5> Se `self.handle` é a string vazia, a define como a primeira parte de `name`. +<6> Se `self.handle` está em `cls.all_handles`, gera um `ValueError`. +<7> Insere o novo `handle` em `cls.all_handles`. + + +O <> funciona como esperado, mas não é satisfatório para um checador de tipos estático. +A seguir veremos a razão disso, e como resolver o problema.((("", startref="atdatapost05"))) + +<<< +==== Atributos de classe tipados + +Se((("@dataclass", "typed class attributes"))) checarmos os tipos de <> com o Mypy, +seremos repreendidos: + +[source] +---- +$ mypy hackerclub.py +hackerclub.py:37: error: Need type annotation for "all_handles" +(hint: "all_handles: Set[] = ...") +Found 1 error in 1 file (checked 1 source file) +---- + +Infelizmente, a dica fornecida pelo Mypy (versão 0.910 quando essa seção foi revisada) +não é muito útil no contexto do uso de `@dataclass`. +Primeiro, ele sugere usar `Set`, mas desde o Python 3.9 podemos usar +`set`—sem a necessidade de importar `Set` de `typing`. +Mais importante: se acrescentarmos uma dica de tipo como `set[…]` +a `all_handles`, `@dataclass` vai encontrar essa anotação e +transformar `all_handles` em um campo de instância. +Vimos isso acontecer na <>. + +A forma de contornar esse problema, descrita na +«_PEP 526—Syntax for Variable Annotations_ (Sintaxe para Anotações de Variáveis)» [.small]#[fpy.li/5-11]# +é bem feia. +Para criar uma variável de classe com uma dica de tipo, precisamos usar um pseudo-tipo +chamado `typing.ClassVar`, +que aproveita a notação de tipos genéricos (`[]`) para definir o tipo da variável +e também para declará-la como um atributo de classe. + +Para deixar felizes tanto o checador de tipos quanto o `@dataclass`, +precisamos declarar o `all_handles` do <> assim: + +[source, python] +---- + all_handles: ClassVar[set[str]] = set() +---- + +Esta dica de tipo está dizendo o seguinte: + +[quote] +____ +`all_handles` é um atributo de classe do tipo ++set++-de-++str++, com um `set` vazio como valor default. +____ + +Precisamos também importar `ClassVar` do módulo `typing` para escrever aquela anotação. + +O decorador `@dataclass` não se importa com os tipos nas anotações, +exceto em dois casos, e este é um deles: se o tipo for `ClassVar`, +um campo de instância não será gerado para aquele atributo. + +O outro caso em que o tipo de um campo é relevante para `@dataclass` +é quando declaramos _variáveis apenas de inicialização_, nosso próximo tópico. + + +==== Variáveis de inicialização que não são campos + +Algumas((("@dataclass", "init-only variables")))((("variables", "init-only variables"))) +vezes pode ser necessário passar para `+__init__+` argumentos que não são campos de instância. +Tais argumentos são chamados "argumentos apenas de inicialização" (_init-only variables_) pela +«documentação de `dataclasses`» [.small]#[fpy.li/3v]#. +Para declarar um argumento desses, o módulo `dataclasses` oferece o pseudo-tipo `InitVar`, +que usa a mesma sintaxe de `typing.ClassVar`. +O exemplo dado na documentação é uma classe de dados com um campo inicializado +a partir de um banco de dados, e o objeto banco de dados precisa ser passado para o `+__init__+`. +O <> mostra o código que ilustra a seção «Variáveis de inicialização apenas» [.small]#[fpy.li/3v]#. + +[[initvar_ex]] +.Exemplo da documentação do módulo «`dataclasses`» [.small]#[fpy.li/3v]# +==== +[source, python] +---- +@dataclass +class C: + i: int + j: int | None = None + database: InitVar[DatabaseType | None] = None + + def __post_init__(self, database): + if self.j is None and database is not None: + self.j = database.lookup('j') + +c = C(10, database=my_database) +---- +==== + +Veja como o atributo `database` é declarado. +`InitVar` vai evitar que `@dataclass` trate `database` como um campo normal. +Ele não será definido como um atributo de instância, e a função `dataclasses.fields` não vai listá-lo. +Entretanto, `database` será um dos argumentos aceitos pelo `+__init__+` gerado, +e também será passado para o `+__post_init__+`. +Ao escrever este método, é preciso adicionar o parâmetro `database` à sua assinatura, +como mostra o <>. + +Esse longo tratamento de `@dataclass` cobriu os recursos mais importantes desse +decorador—alguns deles apareceram em seções anteriores, +como na <>, onde falamos em paralelo das três fábricas de classes de dados. +A «documentação de `dataclasses`» [.small]#[fpy.li/3v]# e a +«_PEP 526—Syntax for Variable Annotations_ (Sintaxe para Anotações de Variáveis)» [.small]#[fpy.li/pep526]# +têm todos os detalhes. + +Na próxima seção apresento um exemplo mais completo com o `@dataclass`. + + +[[dc_resource_sec]] +==== Exemplo de @dataclass: um registro Dublin Core + +Frequentemente as classes((("@dataclass", "example using", id="atdataexample05")))((("Dublin Core Schema"))) +criadas com o `@dataclass` vão ter mais campos que os exemplos muito curtos apresentados até aqui. +O «_Dublin Core_» [.small]#[fpy.li/5-12]# oferece a fundação para um exemplo mais típico de `@dataclass`. + +[quote, Dublin Core na Wikipedia] +____ +O Dublin Core é um esquema de metadados que visa descrever objetos digitais, +como vídeos, sons, imagens, textos e sites na Web. +Aplicações de Dublin Core utilizam XML e o RDF +(Resource Description Framework).footnote:[Fonte: O artigo «_Dublin Core_» [.small]#[fpy.li/3w]# na Wikipedia.] +____ + +O padrão define 15 campos opcionais; a classe `Resource`, no <>, usa 8 deles. + +[[resource_ex]] +._dataclass/resource.py_: código de `Resource`, inspirada no Dublin Core +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/resource.py[tags=DATACLASS] +---- +==== +<1> Este `Enum` vai fornecer valores de um tipo seguro para o campo `Resource.type`. +<2> `identifier` é o único campo obrigatório. +<3> `title` é o primeiro campo com um default. +Isso obriga todos os campos abaixo dele a fornecerem defaults. +<4> O valor de `date` pode ser uma instância de `datetime.date` ou `None`. +<5> O default do campo `type` é `ResourceType.BOOK`. + + +O <> mostra um doctest mostrando como um registro `Resource` aparece no código. + +[[resource_doctest_ex]] +._dataclass/resource.py_: testes da classe `Resource` +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/resource.py[tags=DOCTEST] +---- +==== + +O `+__repr__+` gerado pelo `@dataclass` está correto, +mas podemos torná-lo mais legível colocando um campo por linha. +Esse é o formato que queremos para `repr(book)`: + +[source, python] +---- +include::../code/05-data-classes/dataclass/resource_repr.py[tags=DOCTEST] +---- + +O <> é o código para o `+__repr__+`, produzindo o formato que aparece no trecho anterior. +Esse exemplo usa `dataclass.fields` para obter os nomes dos campos da classe de dados. + + +[[resource_repr_ex]] +.`dataclass/resource_repr.py`: código para o método `+__repr__+`, implementado na classe `Resource` do <> +==== +[source, python] +---- +include::../code/05-data-classes/dataclass/resource_repr.py[tags=REPR] +---- +==== +<1> Inicializa a lista `res`, para criar a string de saída com o nome da classe e o parênteses abrindo. +<2> Para cada campo `f` na classe... +<3> ...obtém o atributo nomeado da instância. +<4> Anexa uma linha indentada com o nome do campo e `repr(value)`—é isso que o `!r` faz. +<5> Acrescenta um parêntese fechando. +<6> Cria uma string de múltiplas linhas a partir de `res`, e devolve essa string. + +<<< + +Com este exemplo, inspirado na cultura de Dublin, Ohio, +concluímos nosso passeio pelas fábricas de classes de dados de Python. + +Classes de dados são úteis, mas podem estar sendo usadas de forma excessiva em seu projeto. +A próxima seção explica isso.((("", startref="atdataexample05")))((("", startref="DCBatdataclass05"))) + +[[dataclass_code_smell_sec]] +=== A classe de dados como _cheiro no código_ + +Independente((("data class builders", "data class as code smell", id="DCBsmell05")))((("code smells", +id="codesmells05"))) de você implementar uma classe de dados escrevendo todo o código ou +aproveitando as facilidades oferecidas por alguma das fábricas de classes descritas nesse capítulo, +fique alerta: isso pode sinalizar um problema em seu design. + +No livro +«_Refactoring: Improving the Design of Existing Code, 2nd ed._» [.small]#[fpy.li/42]# +(Addison-Wesley), Martin Fowler e Kent Beck apresentam um catálogo de +"_cheiros no código_"footnote:[NT: _Code smell_ em geral não é traduzido na literatura em +português—uma tradução quase literal seria "fedor no código". +Uma tradução mais gentil pode ser "cheiro no código", adotado aqui. +Mais gentil e menos enviesada: um "cheiro no código" nem sempre é indicação de um problema.]—padrões +no código que podem indicar a necessidade de refatoração. +O verbete intitulado "Data Class" +(_Classe de Dados_) começa assim: + +[quote] +____ +Estas são classes que têm campos, métodos para obter e atualizar os campos, e nada mais. +Tais classes são recipientes burros de dados, +e muitas vezes são manipuladas de forma excessivamente detalhada por outras classes. +____ + +No site pessoal de Fowler, há um post muito esclarecedor chamado +«_Code Smell_ (Cheiro no Código)» [.small]#[fpy.li/5-14]#. +Esse texto é muito relevante para nossa discussão, pois o autor usa a _classe de dados_ +como um exemplo de _cheiro no código_, e sugere alternativas para lidar com ele. +Abaixo está a tradução integral daquele artigo.footnote:[Fui colega de +Martin Fowler na Thoughtworks, então esperei apenas 20 minutos para +receber sua permissão.] + +[[code_smell_essay]] +.Cheiros no Código +**** + +*Por: Martin Fowler* + +Um cheiro no código é uma indicação superficial que frequentemente corresponde a um problema mais profundo no sistema. +O termo foi inventado por Kent Beck, enquanto ele me ajudava com meu livro, «_Refactoring_» [.small]#[fpy.li/5-15]#. + +<<< + +Esta rápida definição tem um par de detalhes sutis. +Primeiro, um cheiro é, por definição, algo rápido de detectar—é "cheiroso", como eu disse recentemente. +Um método longo é um bom exemplo disso—basta olhar o código e ver mais de uma dúzia de linhas de Java para meu nariz se contrair. + +O segundo detalhe é que cheiros nem sempre indicam um problema. +Alguns métodos longos são bons. +É preciso ir mais fundo para ver se há um problema subjacente ali. +Cheiros não são inerentemente ruins por si só—são frequentemente o indicador de um problema, +não o problema propriamente dito. + +Os melhores cheiros são algo fácil de detectar e que, na maioria das vezes, leva a problemas realmente interessantes. +Classes de dados (classes contendo só dados e nenhum comportamento [próprio]) são um bom exemplo. +Você olha para elas e se pergunta que comportamento deveria fazer parte daquela classe. +Então você começa a refatorar, para incluir ali aquele comportamento. +Muitas vezes, algumas perguntas simples e essas refatorações iniciais são +um passo vital para transformar um objeto anêmico em alguma coisa que realmente tenha classe. + +Uma coisa boa sobre cheiros é sua facilidade de detecção por pessoas inexperientes, +mesmo aquelas pessoas que não conhecem o suficiente para avaliar se há mesmo um problema +ou, se existir, para corrigi-lo. +Soube de um líder de uma equipe de desenvolvimento que elege um "cheiro da semana", +e pede às pessoas que procurem aquele cheiro e o apresentem para colegas mais experientes. +Fazer isso com um cheiro por vez é uma ótima maneira de ensinar gradualmente +os membros da equipe a serem programadores melhores. + +**** + +A principal ideia da programação orientada a objetos é manter o comportamento e os dados juntos, +na mesma unidade de código: uma classe. +Se uma classe é largamente utilizada mas não tem qualquer comportamento próprio significativo, +é bem provável que o código que interage com as instâncias dessa classe esteja espalhado +(ou mesmo duplicado) em métodos e funções ao longo de todo +o sistema—uma receita para dores de cabeça na manutenção. +Por isso, as refatorações de Fowler para lidar com uma classe de dados envolvem +trazer responsabilidades de volta para a classe. + +Levando o que foi dito acima em consideração, +há alguns cenários comuns onde faz sentido ter uma classe de dados com pouco ou nenhum comportamento. + +==== A classe de dados como um esboço + +Nesse cenário, a classe de dados é uma implementação simplista inicial de uma classe, +para dar início a um novo projeto ou módulo. +Com o tempo, a classe deve ganhar seus próprios métodos, +deixando de depender de métodos de outras classes para operar sobre suas instâncias. +O esboço é temporário; ao final do processo, +sua classe pode se tornar totalmente independente da fábrica usada inicialmente para criá-la. + +Python também é muito usado para resolução rápida de problemas e para experimentação, +e nesses casos faz sentido preservar o esboço. + +==== A classe de dados como representação intermediária + +Uma classe de dados pode ser útil para criar registros que serão exportados para o JSON +ou algum outro formato de intercomunicação, ou para manter dados que acabaram de ser importados, +cruzando alguma fronteira do sistema. +Todas as fábricas de classes de dados de Python oferecem um método ou função +para converter uma instância em um `dict` simples, +e você sempre pode invocar o construtor com um `dict`, +usado para passar argumentos nomeados expandidos com `**`. +Um `dict` assim é muito similar a um registro JSON. + +Nesse cenário, as instâncias da classe de dados devem ser tratadas como objetos +imutáveis—mesmo que os campos sejam mutáveis, +não deveriam ser modificados nessa forma intermediária. +Mudá-los significa perder o principal benefício de manter os dados e o comportamento próximos. +Quando o processo de importação/exportação exigir mudança nos valores, +você deve implementar seus próprios métodos de fábrica, +em vez de usar os métodos "as dict" existentes ou os construtores padrão. + +Vamos agora mudar de assunto e aprender como escrever padrões que "casam" +com instâncias de classes arbitrárias, +não apenas com as sequências e mapeamentos que vimos nas seções +<> e +<>.((("", startref="DCBsmell05")))((("", startref="codesmells05"))) + +[[pattern_instances_sec]] +=== Pattern matching com instâncias de classes + +Padrões de classe((("data class builders", "pattern matching class instances", +id="DCBpattern05")))((("pattern matching", "pattern matching class instances", id="PMpattern05"))) +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 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. +Vamos estudá-las nessa ordem. + +==== Padrões de classe simples + +Já((("simple class patterns"))) vimos um exemplo de padrões de +classe simples usados como sub-padrões na <>: + +[source, python] +---- + case [str(name), _, _, (float(lat), float(lon))]: +---- + +Aquele padrão casa com uma sequência de quatro itens, +onde o primeiro item deve ser uma instância de `str` e +o último item deve ser uma sequência de duas instâncias de `float`. + +A sintaxe dos padrões de classe se parece com a invocação de um construtor. +Abaixo temos um padrão de classe que "casa" com valores `float` +sem vincular uma variável (o corpo do `case` pode referir-se a `x` diretamente, +se necessário): + +[source, python] +---- + match x: + case float(): + do_something_with(x) +---- + +Mas isto provavelmente será um bug no seu código: + +[source, python] +---- + match x: + case float: # DANGER!!! + do_something_with(x) +---- + +No exemplo acima, `case float:` "casa" com qualquer sujeito, +pois Python entende `float` como uma variável, que é então vinculada ao sujeito. + +A sintaxe `float(x)` do padrão simples é um caso especial que se aplica apenas +a onze tipos embutidos "abençoados", listados no final da seção +«_Class Patterns_ (Padrões de Classe)» [.small]#[fpy.li/5-16]# da +«_PEP 634—Structural Pattern Matching: Specification_ (Pattern Matching Estrutural: Especificação)» +[.small]#[fpy.li/pep634]#. + +[source] +---- +bool bytearray bytes dict float frozenset int list set str tuple +---- + +Nessas classes, a variável que parece um argumento do construtor (por exemplo, +o `x` em `float(x)`) é vinculada à instância completa do sujeito ou +à parte do sujeito que "casa" com um sub-padrão, +como exemplificado por `str(name)` no padrão de sequência que vimos antes: + +[source, python] +---- + case [str(name), _, _, (float(lat), float(lon))]: +---- + +Se a classe não é um daqueles onze tipos embutidos abençoados, +então essas variáveis parecidas com argumentos representam padrões +a serem casados com atributos de uma instância daquela classe. + +[[keyword_class_patterns_sec]] +==== Padrões de classe nomeados + +Para((("keyword class patterns"))) entender como usar padrões de classe nomeados, +observe a classe `City` e suas cinco instâncias no <>, a seguir. + +[[ex_cities_match]] +.A classe `City` e algumas instâncias +==== +[source, python] +---- +include::../code/05-data-classes/match_cities.py[tags=CITY] +---- +==== + +Dadas essas definições, a seguinte função devolve uma lista de cidades asiáticas: + +[source, python] +---- +include::../code/05-data-classes/match_cities.py[tags=ASIA] +---- + +O padrão `City(continent='Asia')` encontra qualquer instância de `City` +onde o atributo `continent` seja igual a `'Asia'`, +independentemente do valor dos outros atributos. +Para capturar o valor do atributo `country`, você pode escrever: + +[source, python] +---- +include::../code/05-data-classes/match_cities.py[tags=ASIA_COUNTRIES] +---- + +O padrão `City(continent='Asia', country=cc)` encontra as mesmas cidades asiáticas, +mas agora a variável `cc` captura o valor do atributo `country` do sujeito. +Também funciona quando a variável do padrão tem o mesmo nome, `country`: + +[source, python] +---- + match city: + case City(continent='Asia', country=country): + results.append(country) +---- + +<<< +Padrões de classe nomeados são bastante legíveis, +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. + +[[positional_class_patterns_sec]] +==== Padrões de classe posicionais + +Dadas((("positional class patterns"))) as definições do <>, +a seguinte função devolveria uma lista de cidades asiáticas, +usando um padrão de classe posicional: + +[source, python] +---- +include::../code/05-data-classes/match_cities.py[tags=ASIA_POSITIONAL] +---- + +O padrão `City('Asia')` encontra qualquer instância de `City` na qual +o valor do primeiro atributo seja `Asia`, independentemente do valor dos outros atributos. +Se quiser obter o valor do atributo `country`, poderia escrever: + +[source, python] +---- +include::../code/05-data-classes/match_cities.py[tags=ASIA_COUNTRIES_POSITIONAL] +---- + +O padrão `City('Asia', _, country)` encontra as mesmas cidades de antes, +mas agora a variável `country` está vinculada ao terceiro atributo da instância. + +Mencionei o "primeiro" e o "terceiro" atributo, mas de onde vem este ordenamento? + +<<< +`City` (ou qualquer classe) funciona com padrões posicionais graças a +um atributo de classe especial chamado `+__match_args__+`, +que as fábricas de classe vistas nesse capítulo criam automaticamente. +Esse é o valor de `+__match_args__+` na classe `City`: + +[source, python] +---- +>>> City.__match_args__ +('continent', 'name', 'country') +---- + +Como se vê, `+__match_args__+` declara os nomes dos atributos na ordem +em que eles serão usados em padrões posicionais. + +Na «Seção 11.8» [.small]#[vol.2, fpy.li/2c]# vamos escrever código para definir +`+__match_args__+` em uma classe que criaremos sem a ajuda de uma fábrica de classes. + +[TIP] +==== +Você pode combinar argumentos nomeados e posicionais em um padrão. +Alguns, mas não todos, os atributos de instância disponíveis para o _match_ +podem estar listados no `+__match_args__+`. +Dessa forma, algumas vezes pode ser necessário usar argumentos nomeados em um padrão, +além dos argumentos posicionais. +==== + +=== Resumo do capítulo + +O((("data class builders", "overview of"))) tópico principal desse capítulo foi +as fábricas de classes de dados `collections.namedtuple`, `typing.NamedTuple`, +e `dataclasses.dataclass`. +Vimos como cada uma delas gera classes de dados a partir de descrições, +fornecidas como argumentos a uma função fábrica ou, no caso das duas últimas, +a partir de uma declaração `class` com dicas de tipo. +Especificamente, ambas as variantes de tupla produzem subclasses de `tuple`, +acrescentando apenas a capacidade de acessar os campos por nome, +e criando também um atributo de classe `_fields`, +que lista os nomes dos campos na forma de uma tupla de strings. + +A seguir colocamos lado a lado os principais recursos de cada uma das três fábricas de classes, +incluindo como extrair dados da instância como um `dict`, +como obter os nomes e valores default dos campos, +e como criar uma nova instância a partir de uma instância existente. + +Isso levou ao nosso primeiro contato com dicas de tipo, +especialmente aquelas usadas para anotar atributos em uma declaração `class`, +usando a notação introduzida no Python 3.6 com a +«_PEP 526—Syntax for Variable Annotations_ (Sintaxe para Anotações de Variáveis)» +[.small]#[fpy.li/pep526]#. +O aspecto mais surpreendente das dicas de tipo em geral +é o que elas não têm efeito durante a execução. +Python continua sendo uma linguagem dinâmica. +Ferramentas externas, como o Mypy, +são necessárias para aproveitar a informação de tipagem na +detecção de erros via análise estática do código-fonte. +Após um resumo básico da sintaxe da PEP 526, +estudamos os efeitos das anotações em uma classe simples e +em classes criadas por `typing.NamedTuple` e por `@dataclass`. + +Depois falamos sobre os recursos mais usados dentre os oferecidos por `@dataclass`, +e sobre a opção `default_factory` da função `dataclasses.field`. +Também demos uma olhada nas dicas de pseudo-tipo especiais `typing.ClassVar` e +`dataclasses.InitVar`, importantes no contexto das classes de dados. +Esse tópico central foi concluído com um exemplo baseado no schema Dublin Core, +ilustrando como usar `dataclasses.fields` para iterar sobre os atributos de uma instância de +`Resource` em um `+__repr__+` customizado. + +Então alertamos contra os possíveis usos abusivos das classes de dados, +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 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. + +[[dataclass_further_sec]] +=== Para saber mais + +A documentação((("data class builders", "further reading on"))) +padrão de Python para as fábricas de classes de dados vistas aqui é muito boa, e inclui muitos pequenos exemplos. +Em especial para `@dataclass`, a maior parte da +«_PEP 557—Data Classes_ (Classes de Dados)» [.small]#[fpy.li/pep557]# +foi copiada para a documentação do módulo «`dataclasses`» [.small]#[fpy.li/3s]#. +Entretanto, algumas seções informativas da +«_PEP 557_» [.small]#[fpy.li/pep557]# não foram copiadas, incluindo +«_Why not just use namedtuple?_" (Por que simplesmente não usar namedtuple?)» [.small]#[fpy.li/5-18]#, +«_Why not just use typing.NamedTuple?_ (Por que simplesmente não usar typing.NamedTuple?)» [.small]#[fpy.li/5-19]#, +e a seção +«_Rationale_ (Justificativa)» [.small]#[fpy.li/5-20]#, que termina com a seguinte _Q&A_: + +[quote, Eric V. Smith, PEP 557 "Justificativa"] +____ +Quando não é apropriado usar Classes de Dados? + +Quando for exigida compatibilidade da API com tuplas de dicts. +Quando for exigida validação de tipo além daquela oferecida +pelas PEPs 484 e 526, ou quando for exigida validação ou conversão de valores. +____ + +Em +«_RealPython.com_» [.small]#[fpy.li/5-21]#, +Geir Arne Hjelle escreveu +«_Ultimate guide to data classes in Python 3.7_ (O guia definitivo sobre data classes no Python 3.7)» [.small]#[fpy.li/5-22]# +guia bem completo. + +Na PyCon US 2018, Raymond Hettinger apresentou +«_Dataclasses: The code generator to end all code generators_ +(Dataclasses: o gerador de código para acabar com todos os geradores de código)» [.small]#[fpy.li/5-23]# +(video). + +Para mais recursos e funcionalidade avançada, incluindo validação, o +projeto «_attrs_» [.small]#[fpy.li/5-24]#, +liderado por Hynek Schlawack, surgiu anos antes de `dataclasses` e oferece mais facilidades, +com a promessa de "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_)". + +A influência do _attrs_ sobre o `@dataclass` é reconhecida por Eric V. Smith na PEP 557. +Isso provavelmente inclui a mais importante decisão de Smith sobre a API: +o uso de um decorador de classe em vez de uma classe base ou de +uma metaclasse para realizar a tarefa. + +Glyph—fundador do projeto Twisted—escreveu uma excelente introdução à _attrs_ em +«_The One Python Library Everyone Needs_ (A Biblioteca Python que Todo Mundo Precisa)» [.small]#[fpy.li/5-25]#. +A documentação da _attrs_ inclui uma «discussão sobre alternativas» [.small]#[fpy.li/5-26]#. + +O autor de livros, instrutor e cientista maluco da computação Dave Beazley escreveu o +«_cluegen_» [.small]#[fpy.li/5-27]#, um outro gerador de classes de dados. +Se você já assistiu a alguma palestra do David, +sabe que ele é um mestre na metaprogramação Python a partir de princípios básicos. +Então achei inspirador descobrir, no arquivo _README.md_ do _cluegen_, +o caso de uso concreto que o motivou a criar uma alternativa ao `@dataclass` de Python, +e sua filosofia de apresentar uma abordagem para resolver o problema, +ao invés de fornecer uma ferramenta: a ferramenta pode inicialmente ser mais rápida de usar, +mas a abordagem é mais flexível e pode ir tão longe quanto você queira. + +Sobre a _classe de dados_ como um cheiro no código, +a melhor fonte que encontrei foi o livro de Martin Fowler, _Refactoring_ ("Refatorando"), 2ª ed. +A versão mais recente não traz a citação da epígrafe deste capítulo, +"Classes de dados são como crianças...", mas apesar disso é a melhor edição do livro mais famoso de Fowler, +em especial para pythonistas, pois os exemplos são em JavaScript moderno, +que é mais próximo de Python que de Java—a linguagem usada na primeira edição. + +O site «_Refactoring Guru_ (Guru da Refatoração)» [.small]#[fpy.li/5-28]# também tem uma descrição do +«_data class code smell_ (classe de dados como cheiro no código)» [.small]#[fpy.li/5-29]#. + +.Ponto de vista +**** + +O((("data class builders", "Soapbox discussion")))((("Soapbox sidebars", "@dataclass"))) +verbete para «_Guido_» [.small]#[fpy.li/5-30]# no +«_The Jargon File_» [.small]#[fpy.li/5-31]# +é sobre Guido van Rossum. 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 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..." +____ + +Por um longo tempo, uma das peças ausentes da sintaxe de Python +foi uma forma rápida e padronizada de declarar atributos de instância em uma classe. +Muitas linguagens orientadas a objetos incluem esse recurso. +Aqui está parte da definição da classe `Point` em Smalltalk: + +[source, smalltalk] +---- +Object subclass: #Point + instanceVariableNames: 'x y' + classVariableNames: '' + package: 'Kernel-BasicObjects' +---- + +<<< +A segunda linha lista os nomes dos atributos de instância `x` e `y`. +Se existissem atributos de classe, eles estariam na terceira linha. + +Python sempre teve uma forma fácil de declarar um atributo de classe, +se ele tiver um valor inicial. +Mas atributos de instância são mais comuns, +e os programadores Python têm sido obrigados a olhar dentro do método `+__init__+` para encontrá-los, +sempre temerosos de que podem existir atributos de instância sendo criados +em outro lugar na classe—ou mesmo por funções e métodos de outras classes. + +Agora temos o `@dataclass`, viva! + +Mas ele traz seus próprios problemas + +Primeiro, quando você usa `@dataclass`, as dicas de tipo não são opcionais. +Pelos últimos sete anos, desde a +«_PEP 484—Type Hints_ (Dicas de Tipo)» [.small]#[fpy.li/pep484]#, +prometeram que elas sempre seriam opcionais. +Agora temos um novo recurso importante na linguagem que exige dicas de tipo. +Se você não gosta de toda essa tendência de tipagem estática, +pode querer usar a «`attrs`» [.small]#[fpy.li/5-24]# em vez de `@dataclass`. + +Em segundo lugar, a sintaxe da «_PEP 526_» [.small]#[fpy.li/pep526]# +para anotar atributos de instância e de classe inverte a convenção consagrada para declarações de classe: +tudo que era declarado no nível superior de um bloco `class` era um atributo de classe +(métodos também são atributos de classe). +Com a PEP 526 e o `@dataclass`, +qualquer atributo declarado no nível superior com uma dica de tipo se torna um atributo de instância: + +[source, python] +---- + @dataclass + class Spam: + repeat: int # instance attribute +---- + +Aqui, `repeat` também é um atributo de instância: + +[source, python] +---- + @dataclass + class Spam: + repeat: int = 99 # instance attribute +---- + +Mas se não houver dicas de tipo, +subitamente estamos de volta ao cenário em que declarações +no nível superior da classe pertencem apenas à classe: + +[source, python] +---- + @dataclass + class Spam: + repeat = 99 # class attribute! +---- + +Por fim, se você quiser anotar aquele atributo de classe com um tipo, +não pode usar tipos comuns, porque então ele se tornará um atributo de instância. +Você precisa recorrer àquela anotação usando o pseudo-tipo `ClassVar`, +que é uma gambiarra: + +[source, python] +---- + @dataclass + class Spam: + repeat: ClassVar[int] = 99 # aargh! +---- + +Aqui estamos falando sobre uma exceção da exceção da regra. +Me parece algo muito pouco pythônico. + +Não tomei parte nas discussões que levaram à PEP 526 ou à +«_PEP 557—Data Classes_ (Classes de Dados)» [.small]#[fpy.li/pep557]#, +mas aqui está uma sintaxe alternativa que eu gostaria de ver: + +[source, python] +---- +@dataclass +class HackerClubMember: + .name: str # <1> + .guests: list = field(default_factory=list) + .handle: str = '' + + all_handles = set() # <2> +---- +<1> Atributos de instância devem ser declarados com um prefixo `.` (ponto). +<2> Qualquer nome de atributo que não tenha um prefixo `.` +é um atributo de classe (como sempre foram). + +<<< +A gramática da linguagem teria que mudar para acomodar esta sintaxe. +Mas penso que é mais legível, e evita o problema da exceção-da-exceção. + +Queria poder pegar a máquina do tempo de Guido emprestada e voltar a 2017, +para convencer os mantenedores a aceitarem essa ideia. +**** + diff --git a/vol1/cap06.adoc b/vol1/cap06.adoc new file mode 100644 index 00000000..a271307c --- /dev/null +++ b/vol1/cap06.adoc @@ -0,0 +1,1357 @@ +[[ch_refs_mut_mem]] +== Referências, mutabilidade, e memória +:example-number: 0 +:figure-number: 0 + +[quote, Adaptado de “Alice Através do Espelho e o que Ela Encontrou Lá”, de Lewis Caroll] +____ +“Você está triste,” disse o Cavaleiro em um tom de voz ansioso: +“deixe eu cantar para você uma canção reconfortante. […] +O nome da canção se chama ‘OLHOS DE HADOQUE’.” + +“Oh, esse é o nome da canção?,” disse Alice, tentando parecer interessada. + +“Não, você não entendeu,” retorquiu o Cavaleiro, um pouco irritado. +“É assim que o nome É CHAMADO. +O nome na verdade é ‘O ENVELHECIDO HOMEM VELHO.‘” +____ + +Alice e o Cavaleiro((("object references", "distinction between objects and their names"))) +dão o tom do que veremos nesse capítulo. +O tema é a distinção entre objetos e seus nomes: +o nome não é o objeto, o nome é outra coisa. + +Começamos o capítulo apresentando uma metáfora para variáveis em Python: +variáveis são rótulos, não caixas. +Mesmo que você já domine variáveis de referência, +a analogia pode ainda ser útil para ilustrar questões de _aliasing_ (“apelidamento”) +para outra pessoa. + +Depois discutimos os conceitos de identidade, valor e apelidamento de objetos. +Uma característica surpreendente das tuplas é revelada: +elas são imutáveis, mas seus valores podem mudar. +Isso leva a uma discussão sobre cópias rasas e profundas. +Referências e parâmetros de funções são o tema seguinte: +o problema do parâmetro com default mutável e +formas seguras de lidar com argumentos mutáveis passados para nossas funções por clientes. + +As últimas seções do capítulo tratam de coleta de lixo (_garbage collection_), +da instrução `del` e de algumas otimizações com objetos imutáveis em Python. + +É um capítulo bastante árido, +mas os tópicos tratados podem explicar muitos bugs sutis em programas reais em Python, +além de boas práticas para evitá-los. + +=== Novidades neste capítulo + +Os tópicos tratados aqui são muito estáveis e fundamentais. +Não foi introduzida nenhuma mudança digna de nota nesta segunda edição. + +Acrescentei um exemplo usando `is` para testar a existência de um objeto sentinela, +e um aviso sobre o mau uso do operador `is` no final da <>. + +Este capítulo estava na Parte IV, mas decidi abordar esses temas mais cedo, +pois eles funcionam melhor como o encerramento da Parte II, “Estruturas de Dados”, +que como abertura de “Práticas de Orientação a Objetos" + +[NOTE] +==== +A seção sobre “Referências Fracas” da primeira edição deste livro agora é +«um post em _https://fluentpython.com_» [.small]#[fpy.li/weakref]#. +==== + +Vamos começar desaprendendo que uma variável é como uma caixa onde você guarda dados. + + +=== Variáveis não são caixas + +Em 1997, ((("object references", "variables as labels versus boxes", id="ORvar06")))((("variables", +"as labels versus boxes", secondary-sortas="labels versus boxes", id="Vlabel06"))) +fiz um curso de verão sobre Java no MIT. +A professora Lynn Steinfootnote:[Lynn Andrea Stein é uma aclamada educadora de ciências da computação. +Ela atualmente leciona no «Olin College of Engineering» [.small]#[fpy.li/6-1]#.] +explicou que a metáfora comum, de “variáveis como caixas”, +na verdade atrapalha o entendimento de variáveis de +referência em linguagens orientadas a objetos. +As variáveis em Python são como variáveis de referência em Java; +uma metáfora melhor é pensar em uma variável como uma etiqueta +que dá nome a um objeto. +O exemplo e a figura a seguir ajudam a entender o motivo disso. + +O <> é uma interação simples que não pode ser explicada por “variáveis como caixas”. + +[[ex_a_b_refs]] +.As variáveis `a` e `b` referem-se à mesma lista, não a cópias da lista. +==== +[source, python] +---- +>>> a = [1, 2, 3] <1> +>>> b = a <2> +>>> a.append(4) <3> +>>> b <4> +[1, 2, 3, 4] +---- +==== +<1> Cria uma lista [1, 2, 3] e a vincula à variável `a`. +<2> Vincula a variável `b` ao mesmo valor referenciado por `a`. +<3> Modifica a lista referenciada por `a`, anexando um novo item. +<4> É possível ver o efeito através da variável `b`. +Se você pensar em `b` como uma caixa que guardava uma cópia de +`[1, 2, 3]` da caixa `a`, este comportamento não faz sentido. + +A <> explica por que a metáfora da caixa está errada em Python, +enquanto etiquetas apresentam uma imagem mais útil para entender como variáveis funcionam. + +[[var-boxes-x-labels]] +.Se você imaginar variáveis como caixas, não é possível entender a atribuição em Python; por outro lado, imagine variáveis como etiquetas autocolantes e o <> é facilmente explicável. +image::../images/flpy_0601.png[Boxes and labels diagram] + +Assim, a instrução `b = a` não copia o conteúdo de uma caixa `a` para uma caixa `b`. +Ela cola uma nova etiqueta `b` no objeto que já tem a etiqueta `a`. + +A professora Stein também falava sobre atribuição de uma maneira bastante específica. +Por exemplo, quando discutia sobre um objeto representando uma gangorra em uma simulação, +ela dizia: +“A variável g foi atribuída à gangorra”, mas nunca “A gangorra foi atribuída à variável g”. +Com variáveis de referência, +faz mais sentido dizer que a variável é atribuída a um objeto, não o contrário. +Afinal, o objeto é criado antes da atribuição. +O <> prova que o lado direito de uma atribuição é processado primeiro. + +Já que o verbo “atribuir” é usado de diferentes maneiras, +“vincular” é uma alternativa melhor: +a declaração de atribuição em Python `x = …` vincula o nome `x` +ao objeto criado ou referenciado no lado direito. +E o objeto precisa existir antes que um nome possa ser vinculado a ele, +como demonstra o <>. + +[[ex_var_assign_after]] +.Variáveis são vinculadas a objetos somente após os objetos serem criados +==== +[source, python] +---- +>>> class Gizmo: +... def __init__(self): +... print(f'Gizmo id: {id(self)}') +... +>>> x = Gizmo() +Gizmo id: 4301489152 <1> +>>> y = Gizmo() * 10 <2> +Gizmo id: 4301489432 <3> +Traceback (most recent call last): + File "", line 1, in +TypeError: unsupported operand type(s) for *: 'Gizmo' and 'int' +>>> +>>> dir() <4> +['Gizmo', '__builtins__', '__doc__', '__loader__', '__name__', +'__package__', '__spec__', 'x'] +---- +==== +<1> A saída `Gizmo id: …` é um efeito colateral da criação de uma instância de `Gizmo`. +<2> Multiplicar uma instância de `Gizmo` levanta uma exceção. +<3> Aqui está a prova de que um segundo `Gizmo` foi de fato instanciado +antes que a multiplicação fosse tentada. +<4> Mas a variável `y` nunca foi criada, porque a exceção aconteceu +enquanto a parte direita da atribuição estava sendo executada. + +[TIP] +==== +Para entender uma atribuição em Python, leia primeiro o lado direito: +é ali que o objeto é criado ou recuperado. +Depois disso, a variável do lado esquerdo é vinculada ao objeto, +como uma etiqueta colada a ele. +Esqueça as caixas. +==== + +Como variáveis são apenas meras etiquetas, +nada impede que um objeto tenha várias etiquetas vinculadas a si. +Quando isso acontece, você tem _apelidos_ (aliases), +nosso próximo tópico.((("", startref="ORvar06")))((("", startref="Vlabel06"))) + + +=== Identidade, igualdade e apelidos + +Lewis Carroll((("object references", "aliasing", id="ORalias06")))((("aliasing", id="alias06"))) +é o pseudônimo literário do Prof. +Charles Lutwidge Dodgson. +O Sr. +Carroll não é apenas igual ao Prof. +Dodgson; +eles são exatamente a mesma pessoa. <> +expressa essa ideia em Python. + +[[ex_equal_and_same]] +.`charles` e `lewis` se referem ao mesmo objeto +==== +[source, python] +---- +>>> charles = {'name': 'Charles L. Dodgson', 'born': 1832} +>>> lewis = charles <1> +>>> lewis is charles +True +>>> id(charles), id(lewis) <2> +(4300473992, 4300473992) +>>> lewis['balance'] = 950 <3> +>>> charles +{'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950} +---- +==== +<1> `lewis` é um apelido para `charles`. +<2> O operador `is` e a função `id` confirmam essa afirmação. +<3> Adicionar um item a `lewis` é o mesmo que adicionar um item a `charles`. + +Entretanto, suponha que um impostor—vamos chamá-lo de Dr. +Alexander Pedachenko—diga +que é o verdadeiro Charles L. Dodgson, nascido em 1832. +Suas credenciais podem ser as mesmas, +mas o Dr. +Pedachenko não é o Prof. +Dodgson. <> ilustra esse cenário. + +[[alias_x_copy]] +.`charles` e `lewis` estão vinculados ao mesmo objeto; `alex` está vinculado a um objeto diferente de valor igual. +image::../images/flpy_0602.png[Alias x copy diagram] + +O <> constrói e testa o objeto `alex` como apresentado em <>. + +[[ex_equal_not_same]] +.`alex` e `charles` são iguais quando comparados, mas `alex` _não é_ `charles` +==== +[source, python] +---- +>>> charles = {'name': 'Charles L. Dodgson', 'born': 1832} +>>> alex = {'name': 'Charles L. Dodgson', 'born': 1832} <1> +>>> alex == charles <2> +True +>>> alex is not charles <3> +True +---- +==== +<1> `alex` é uma referência a um objeto que é uma réplica do objeto vinculado a `charles`. +<2> Os objetos são iguais quando comparados devido à implementação de `+__eq__+` na classe `dict`. +<3> Mas são objetos distintos. +Essa é a forma pythônica de escrever +a negação de uma comparação de identidade: `a is not b`. + +<> é um exemplo de _apelidamento_ (aliasing). +Naquele código, `lewis` e `charles` são apelidos: duas variáveis vinculadas ao mesmo objeto. +Por outro lado, `alex` não é um apelido para `charles`: +essas variáveis estão vinculadas a objetos diferentes. +Os +((("== (equality) operator")))((("equality (==) operator")))((("comparison operators"))) +objetos vinculados a `alex` e `charles` têm o mesmo +__valor__ (é isso que `==` compara) mas têm identidades diferentes. + +Na «Referência da Linguagem Python» [.small]#[fpy.li/2x]#, está escrito: + +[quote] +____ +A identidade de um objeto nunca muda após ele ter sido criado; +você pode pensar nela como o endereço do objeto na memória. +O operador `is` compara a identidade de dois objetos; a função +((("functions", "id() function")))((("id() function"))) `id()` +retorna um inteiro representando essa identidade. +____ + +O verdadeiro significado do `id` de um objeto depende da implementação da linguagem. +Em CPython, `id()` retorna o endereço de memória do objeto, +mas outro interpretador Python pode retornar algo diferente. +O ponto fundamental é que o `id` será sempre um valor numérico único, +e ele nunca mudará durante a vida do objeto. + +<<< +Na prática, raramente usamos a função `id()` quando programamos. +A verificação de identidade é feita, na maior parte das vezes, com o operador `is`, +que compara os IDs dos objetos, então nosso código não precisa chamar `id()` +explicitamente. +A seguir falamos sobre `is` versus `==`. + +[TIP] +==== +Para o revisor técnico Leonardo Rochael, +o uso mais frequente de `id()` ocorre durante o processo de debugging, +quando o `repr()` de dois objetos são semelhantes, +mas você precisa saber se duas referências são apelidos ou apontam para objetos diferentes. +Se as referências estão em contextos diferentes--por exemplo, em stack frames +diferentes--pode não ser viável usar `is`. +==== + + +[[choosing_eq_v_is_sec]] +==== Escolhendo entre == e is + +O operador ((("is operator"))) `==` compara os valores de objetos (os dados que eles contêm), +enquanto `is` compara suas identidades. + +Em geral, ao programar, nos preocupamos mais com os valores que com as identidades dos objetos. +Por este motivo `==` aparece com mais frequência que `is` em programas Python. + +Entretanto, se você estiver comparando uma variável com um singleton (um objeto único) +faz mais sentido usar `is`. +O caso mais comum é verificar se uma variável está vinculada a `None`. +Esta é a forma recomendada de fazer isso: + +[source, python] +---- +x is None +---- + +E a forma apropriada de escrever sua negação é: + +[source, python] +---- +x is not None +---- + +`None` é o singleton mais comum que testamos com `is`. +Objetos sentinela são outro exemplo de singletons que testamos com `is`. +Veja um modo de criar e testar um objeto sentinela: + +[source, python] +---- +END_OF_DATA = object() +# ... many lines +def traverse(...): + # ... more lines + if node is END_OF_DATA: + return + # etc. +---- + +O operador `is` é mais rápido que `==`, pois não pode ser sobrecarregado. +Daí Python não precisa encontrar e invocar métodos especiais para calcular seu resultado +e o processamento é tão simples quanto comparar dois IDs, que são números inteiros. +Por outro lado, `a == b` é açúcar sintático para `+a.__eq__(b)+`. +O método `+__eq__+`, herdado de `object`, compara os IDs dos objetos, +então produz o mesmo resultado de `is`. +Mas a maioria dos tipos embutidos sobrescreve `+__eq__+` com implementações mais úteis, +que levam em consideração os valores dos atributos dos objetos. +A determinação da igualdade pode envolver muito processamento--por exemplo, +quando se comparam coleções grandes ou estruturas aninhadas com muitos níveis. + +[WARNING] +==== +Normalmente estamos mais interessados na igualdade que na identidade de objetos, +por isso o operador `==` é mais utilizado que `is`. +O caso mais comum para uso de `is` é comparar com `None`. +O `is` também é útil para testar valores de classes derivadas de `enum.Enum`.footnote:[Agradeço +a Ruan Comelli, revisor da tradução brasileira, +por ter me lembrado o caso de uso do operador `is` com objetos `Enum`.] +Se não estiver seguro, use `==`. Em geral, é o que você quer, +e também funciona com `None` e valores de `Enum`, +ainda que seja um menos eficiente. +==== + +Para concluir essa discussão de identidade versus igualdade, +vamos ver como o tipo notoriamente imutável `tuple` não é assim tão invariável quanto você poderia supor. + + +[[tuple_relative_immutable_sec]] +==== A imutabilidade relativa das tuplas + +As tuplas, como((("tuples", "relative immutability of"))) a maioria das coleções em +Python#x2014;lists, dicts, sets, etc.—são contêineres: +armazenam referências para objetos.footnote:[Ao contrário de sequências +planas de tipo único, como `str`, `byte` e `array.array`, +que não contêm referências e sim seu conteúdo -- caracteres, bytes e números -- armazenado +em um espaço contíguo de memória.] + +Se os itens referenciados forem mutáveis, eles podem mudar, mesmo que a tupla em si não mude. +Em outras palavras, a imutabilidade das tuplas refere-se apenas ao conteúdo interno da +estrutura da instância de `tuple` (isto é, as referências que ela armazena), +e não se estende aos objetos referenciados. + +O <> ilustra uma situação em que o valor de uma tupla muda +como resultado de mudanças em um objeto mutável ali referenciado. +O que não pode nunca mudar em uma tupla é a identidade dos itens que ela contém. + +[[ex_mutable_tuples]] +.`t1` e `t2` inicialmente são iguais, mas a mudança em um item mutável dentro da tupla `t1` as torna diferentes +==== +[source, python] +---- +>>> t1 = (1, 2, [30, 40]) <1> +>>> t2 = (1, 2, [30, 40]) <2> +>>> t1 == t2 <3> +True +>>> id(t1[-1]) <4> +4302515784 +>>> t1[-1].append(99) <5> +>>> t1 +(1, 2, [30, 40, 99]) +>>> id(t1[-1]) <6> +4302515784 +>>> t1 == t2 <7> +False +---- +==== +<1> `t1` é imutável, mas `t1[-1]` é mutável. +<2> Cria a tupla `t2`, cujos itens são iguais àqueles de `t1`. +<3> Apesar de serem objetos distintos, `t1` e `t2` são iguais quando comparados, como esperado. +<4> Obtém o id da lista na posição `t1[-1]`. +<5> Modifica diretamente a lista `t1[-1]`. +<6> O id de `t1[-1]` não mudou, apenas seu valor. +<7> `t1` e `t2` agora são diferentes + +A imutabilidade relativa das tuplas está por trás do enigma da <>. +Esta é também razão pela qual não é possível gerar o hash de algumas tuplas, +como vimos na <>. + +A distinção entre igualdade e identidade tem outras implicações quando você precisa copiar um objeto. +Uma cópia é um objeto igual com um `id` diferente. +Mas se um objeto contém outros objetos, +é preciso que a cópia duplique os objetos internos ou eles podem ser compartilhados? +Não há uma resposta única. +A seguir discutimos esse ponto.((("", startref="ORalias06")))((("", startref="alias06"))) + +=== A princípio, cópias são rasas + +A ((("object references", "shallow copies", id="ORshallow06")))((("shallow copies", +id="shallow06")))((("copies", "shallow", id="Cshallow06")))((("lists", "shallow copies of", +id="Lshallow06")))forma mais fácil de copiar uma lista +(ou a maioria das coleções mutáveis nativas) é usando o construtor padrão do próprio tipo. +Por exemplo: + + +[source, python] +---- +>>> l1 = [3, [55, 44], (7, 8, 9)] +>>> l2 = list(l1) <1> +>>> l2 +[3, [55, 44], (7, 8, 9)] +>>> l2 == l1 <2> +True +>>> l2 is l1 <3> +False +---- +<1> `list(l1)` cria uma cópia de `l1`. +<2> As cópias são iguais... +<3> ...mas se referem a dois objetos diferentes. + +Para listas e outras sequências mutáveis, o atalho `l2 = l1[:]` também cria uma cópia. + +Contudo, tanto o construtor quanto `[:]` produzem uma _cópia rasa_ (shallow copy). +Isto é, o contêiner externo é duplicado, mas a cópia é preenchida com referências +para os mesmos itens contidos no contêiner original. +Isso economiza memória e não causa qualquer problema se todos os itens forem imutáveis. +Mas se existirem itens mutáveis, isso pode gerar surpresas desagradáveis. + +No <> criamos uma lista contendo outra lista e uma tupla, +e então fazemos algumas mudanças para ver como isso afeta os objetos referenciados. + +<<< +[TIP] +==== +Se você está conectado à internet, +recomendo que assista à animação interativa do +<> no «_Online Python Tutor_» [.small]#[fpy.li/6-3]#. +No momento em que escrevo, o link direto para um exemplo pronto no _pythontutor.com_ +não funciona sempre. +Mas a ferramenta é ótima, então vale a pena investir o tempo copiando e colando o código. +==== + + +[[ex_shallow_copy]] +.Criando uma cópia rasa de uma lista contendo outra lista; copie e cole esse código para vê-lo animado no Online Python Tutor +==== +[source, python] +---- +l1 = [3, [66, 55, 44], (7, 8, 9)] +l2 = list(l1) # <1> +l1.append(100) # <2> +l1[1].remove(55) # <3> +print('l1:', l1) +print('l2:', l2) +l2[1] += [33, 22] # <4> +l2[2] += (10, 11) # <5> +print('l1:', l1) +print('l2:', l2) +---- +==== +<1> `l2` é uma cópia rasa de `l1`. Este estado está representado na <>. +<2> Concatenar `100` a `l1` não tem qualquer efeito sobre `l2`. +<3> Aqui removemos `55` da lista interna `l1[1]`. Isso afeta `l2`, +pois `l2[1]` está associado à mesma lista em `l1[1]`. +<4> Para um objeto mutável como a lista referida por `l2[1]`, +o operador `+=` altera a lista diretamente. +Essa mudança é visível em `l1[1]`, +que é um apelido para `l2[1]`. +<5> `+=` em uma tupla cria uma nova tupla e reassocia a variável `l2[2]` a ela. +Isso é equivalente a fazer `l2[2] = l2[2] + (10, 11)`. +Agora as tuplas na última posição de `l1` e `l2` não são mais o mesmo objeto. +Veja <>. + +A saída do <> está no <>, +e o estado final dos objetos está representado na <>. + +[[shallow_copy1]] +.Estado do programa imediatamente após a atribuição `l2 = list(l1)` em <>. `l1` e `l2` se referem a listas diferentes, mas as listas compartilham referências para os mesmos objetos internos, a lista `[66, 55, 44]` e para a tupla `(7, 8, 9)`. (Diagrama gerado pelo Online Python Tutor) +image::../images/flpy_0603.png[align="left",pdfwidth=8cm] + +[[ex_shallow_copy_out]] +.Saída do <> +==== +[source, python] +---- +l1: [3, [66, 44], (7, 8, 9), 100] +l2: [3, [66, 44], (7, 8, 9)] +l1: [3, [66, 44, 33, 22], (7, 8, 9), 100] +l2: [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)] +---- +==== + +[[shallow_copy2]] +.Estado final de `l1` e `l2`: elas ainda compartilham referências para o mesmo objeto lista, que agora contém `[66, 44, 33, 22]`, mas a operação `l2[2] += (10, 11)` criou uma nova tupla com conteúdo `(7, 8, 9, 10, 11)`, sem relação com a tupla `(7, 8, 9)` referenciada por `l1[2]`. (Diagrama gerado pelo Online Python Tutor.) +image::../images/flpy_0604.png[align="left",pdfwidth=10cm] + +Já deve estar claro que cópias rasas são fáceis de criar, +mas podem ou não ser o que você quer. +Nosso próximo tópico é a criação de cópias profundas. +((("", startref="ORshallow06")))((("", startref="shallow06")))((("", +startref="Cshallow06")))((("", startref="Lshallow06"))) + +[[deep_x_shallow_copies]] +==== Cópias profundas e cópias rasas + +Trabalhar((("object references", "deep copies", id="ORdeep06")))((("copies", "deep", +id="Cdeep06")))((("deep copies", id="deepcopy06"))) +com cópias rasas nem sempre é um problema, +mas algumas vezes você vai precisar criar cópias profundas +(isto é, cópias que não compartilham referências de objetos internos). +O módulo `copy` oferece as funções `deepcopy` e `copy`, +que retornam cópias profundas e rasas de objetos arbitrários, respectivamente. + +Para ilustrar o uso de `copy()` e `deepcopy()`, <> define uma classe simples, +`Bus`, representando um ônibus escolar que é carregado com passageiros, +e então pega ou deixa passageiros ao longo de sua rota. + +[[ex_bus1]] +.Bus pega ou deixa passageiros +==== +[source, python] +---- +include::../code/06-obj-ref/bus.py[tags=BUS_CLASS] +---- +==== + +Agora, no <> interativo, vamos criar um objeto `bus1` e +dois clones: uma cópia rasa (`bus2`) e uma cópia profunda +(`bus3`). Então vemos o que acontece quando o `bus1` deixa um passageiro. + +[[ex_bus1_console]] +.Os efeitos do uso de `copy` versus `deepcopy` +==== +[source, python] +---- +>>> import copy +>>> bus1 = Bus(['Alice', 'Bill', 'Claire', 'David']) +>>> bus2 = copy.copy(bus1) +>>> bus3 = copy.deepcopy(bus1) +>>> id(bus1), id(bus2), id(bus3) +(4301498296, 4301499416, 4301499752) <1> +>>> bus1.drop('Bill') +>>> bus2.passengers +['Alice', 'Claire', 'David'] <2> +>>> id(bus1.passengers), id(bus2.passengers), id(bus3.passengers) +(4302658568, 4302658568, 4302657800) <3> +>>> bus3.passengers +['Alice', 'Bill', 'Claire', 'David'] <4> +---- +==== +<1> Usando `copy` e `deepcopy`, criamos três instâncias distintas de `Bus`. +<2> Após `bus1` deixar `'Bill'`, ele também desaparece de `bus2`. +<3> A inspeção do atributo dos `passengers` mostra que +`bus1` e `bus2` compartilham o mesmo objeto lista, pois `bus2` é uma cópia rasa de `bus1`. +<4> `bus3` é uma cópia profunda de `bus1`, +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 laço infinito. +A função `deepcopy` memoriza os objetos já copiados, +e trata referências cíclicas corretamente. +Isto é demonstrado no <>. + +[[ex_cycle1]] +.Referências cíclicas: `b` tem uma referência para `a`; `b` é concatenado a `a`; ainda assim, `deepcopy` consegue copiar `a`. +==== +[source, python] +---- +>>> a = [10, 20] +>>> b = [a, 30] +>>> a.append(b) +>>> a +[10, 20, [[...], 30]] +>>> from copy import deepcopy +>>> c = deepcopy(a) +>>> c +[10, 20, [[...], 30]] +---- +==== + +Além disso, algumas vezes uma cópia profunda pode ser profunda demais. +Por exemplo, objetos podem ter referências para recursos externos ou para +_singletons_ (objetos únicos) que não devem ser copiados. +Você pode controlar o comportamento de `copy` e de `deepcopy` +implementando os métodos especiais `+__copy__+` e `+__deepcopy__+`, +como descrito na +«documentação do módulo `copy`» [.small]#[fpy.li/43]# + +O compartilhamento de objetos através de apelidos também explica +como a passagem de parâmetros funciona em Python, +e o problema do uso de tipos mutáveis como parâmetros default. +Vamos falar sobre essas questões a seguir.((("", startref="deepcopy06")))((("", +startref="Cdeep06")))((("", startref="ORdeep06"))) + + +=== Parâmetros de função como referências + +O((("object references", "function parameters as references", +id="ORfparam06")))((("call by sharing")))((("parameters", "parameter passing"))) +único modo de passagem de parâmetros em Python é a _chamada por compartilhamento_ +(_call by sharing_). +É o mesmo modo usado na maioria das linguagens orientadas a objetos, +incluindo JavaScript, Ruby e Java (em Java isso se aplica aos tipos de referência; +tipos primitivos usam a chamada por valor). +Chamada por compartilhamento significa que cada parâmetro formal +da função recebe uma cópia de cada referência nos argumentos. +Em outras palavras, os parâmetros dentro da função se tornam apelidos dos argumentos passados. + +O resultado desse esquema é que a função pode modificar qualquer objeto mutável +passado a ela como parâmetro, mas não pode mudar a identidade daqueles objetos +(isto é, ela não pode substituir integralmente um objeto por outro). +O <> mostra uma função simples usando `+=` com um de seus parâmetros. +Quando passamos números, listas e tuplas para a função, +os argumentos originais são afetados de maneiras diferentes. +Veja só: + +[[ex_param_pass]] +.Uma função pode mudar qualquer objeto mutável que receba +==== +[source, python] +---- +>>> def f(a, b): +... a += b +... return a +... +>>> x = 1 +>>> y = 2 +>>> f(x, y) +3 +>>> x, y <1> +(1, 2) +>>> a = [1, 2] +>>> b = [3, 4] +>>> f(a, b) +[1, 2, 3, 4] +>>> a, b <2> +([1, 2, 3, 4], [3, 4]) +>>> t = (10, 20) +>>> u = (30, 40) +>>> f(t, u) <3> +(10, 20, 30, 40) +>>> t, u +((10, 20), (30, 40)) +---- +==== +<1> O número `x` não se altera. +<2> A lista `a` é alterada. +<3> A tupla `t` não se altera. + +Outra questão relacionada a parâmetros de função é o uso de valores mutáveis como defaults, +discutida a seguir. + + +[[mutable_default_parameter_sec]] +==== Por que evitar tipos mutáveis como default em parâmetros + +Parâmetros opcionais((("mutable parameters", id="muttype06")))((("parameters", "mutable", +id="Pmut06"))) com valores default são um ótimo recurso para definição de funções em Python, +permitindo que nossas APIs evoluam mantendo a compatibilidade com versões anteriores. +Entretanto, evite usar objetos mutáveis como valores default em parâmetros. + +Para ilustrar o motivo, no <> +modificamos o método `+__init__+` da classe `Bus` do <> para criar `HauntedBus`. +Tentamos ser espertos: em vez do valor default `passengers=None`, +temos `passengers=[]`, para evitar o `if` do `+__init__+` anterior. +Essa "esperteza" causa problemas. + +[[ex_haunted_bus]] +.Uma classe simples ilustrando o perigo de um default mutável +==== +[source, python] +---- +include::../code/06-obj-ref/haunted_bus.py[tags=HAUNTED_BUS_CLASS] +---- +==== +<1> Quando não passamos o argumento `passengers`, +esse parâmetro é vinculado ao objeto lista default, que inicialmente está vazia. +<2> Essa atribuição torna `self.passengers` um apelido de `passengers`, +que por sua vez é um apelido para a lista default, +quando um argumento `passengers` não é passado para a função. +<3> Quando os métodos `.remove()` e `.append()` são usados com `self.passengers`, +estamos, na verdade, mudando a lista default, que é um atributo do objeto-função. + +<> mostra o comportamento misterioso de `HauntedBus`. + +[[demo_haunted_bus]] +.Ônibus assombrados por passageiros fantasmas +==== +[source, python] +---- +>>> bus1 = HauntedBus(['Alice', 'Bill']) <1> +>>> bus1.passengers +['Alice', 'Bill'] +>>> bus1.pick('Charlie') +>>> bus1.drop('Alice') +>>> bus1.passengers <2> +['Bill', 'Charlie'] +>>> bus2 = HauntedBus() <3> +>>> bus2.pick('Carrie') +>>> bus2.passengers +['Carrie'] +>>> bus3 = HauntedBus() <4> +>>> bus3.passengers <5> +['Carrie'] +>>> bus3.pick('Dave') +>>> bus2.passengers <6> +['Carrie', 'Dave'] +>>> bus2.passengers is bus3.passengers <7> +True +>>> bus1.passengers <8> +['Bill', 'Charlie'] +---- +==== +<1> `bus1` começa com uma lista de dois passageiros. +<2> Até aqui, tudo bem: nenhuma surpresa em `bus1`. +<3> `bus2` começa vazio, então a lista vazia default é vinculada a `self.passengers`. +<4> `bus3` também começa vazio, e novamente a lista default é atribuída. +<5> A lista default não está mais vazia! +<6> Agora `Dave`, pego pelo `bus3`, aparece no `bus2`. +<7> O problema: `bus2.passengers` e `bus3.passengers` se referem à mesma lista. +<8> Mas `bus1.passengers` é uma lista diferente. + +O problema é que objetos `HauntedBus` que não recebem uma lista de passageiros +inicial compartilham a mesma lista de passageiros entre si. + +Este tipo de bug pode ser muito sutil. +Como o <> demonstra, +quando `HauntedBus` recebe uma lista com passageiros como parâmetro, +ele funciona como esperado. +Coisas estranhas acontecem somente quando `HauntedBus` começa vazio, +pois aí `self.passengers` se torna um apelido para o valor default do parâmetro `passengers`. +O problema é que cada valor default é processado quando a função é definida—normalmente +quando o módulo é carregado—e os valores default se tornam atributos do objeto-função. +Assim, se o valor default é um objeto mutável e você o altera, +a alteração vai afetar todas as futuras chamadas da função. + +Depois de rodar o <>, +podemos inspecionar o objeto `+HauntedBus.__init__+` +e ver fantasmas de estudantes assombrando o atributo `+__defaults__+`: + +[source, python] +---- +>>> dir(HauntedBus.__init__) # doctest: +ELLIPSIS +['__annotations__', '__call__', ..., '__defaults__', ...] +>>> HauntedBus.__init__.__defaults__ +(['Carrie', 'Dave'],) +---- + +Por fim, podemos verificar que `bus2.passengers` é um apelido vinculado +ao primeiro elemento do atributo `+HauntedBus.__init__.__defaults__+`: + +[source, python] +---- +>>> HauntedBus.__init__.__defaults__[0] is bus2.passengers +True +---- + +O problema com defaults mutáveis explica por que `None` é normalmente usado como +valor default para parâmetros que podem receber valores mutáveis. +No <>, `+__init__+` checa se o argumento `passengers` é `None`. +Se for, `self.passengers` é vinculado a uma nova lista vazia. +Se `passengers` não for `None`, +a implementação correta vincula uma cópia daquele argumento a `self.passengers`. +A próxima seção explica por que copiar o argumento é uma boa prática. + + +[[defensive_argument_sec]] +==== Programação defensiva com argumentos mutáveis + +Ao escrever uma função que recebe um argumento mutável, +você deve considerar com cuidado se o cliente que +chama sua função espera que o argumento passado seja modificado. + +Por exemplo, se sua função recebe um `dict` e precisa modificá-lo durante seu processamento, +esse efeito colateral deve ou não ser visível fora da função? +A resposta, na verdade, depende do contexto. +É tudo uma questão de alinhar as expectativas do autor da função com as do cliente da função. + +O último exemplo com ônibus neste capítulo mostra como o `TwilightBus` viola as expectativas +ao compartilhar sua lista de passageiros com seus clientes. +Antes de estudar a implementação, veja como a classe `TwilightBus` funciona pela +perspectiva de um cliente daquela classe, em <>. + +[[demo_twilight_bus]] +.Passageiros desaparecem quando são deixados por um `TwilightBus` +==== +[source, python] +---- +>>> basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat'] <1> +>>> bus = TwilightBus(basketball_team) <2> +>>> bus.drop('Tina') <3> +>>> bus.drop('Pat') +>>> basketball_team <4> +['Sue', 'Maya', 'Diana'] +---- +==== +<1> `basketball_team` contém o nome de cinco estudantes. +<2> Um `TwilightBus` é carregado com o time. +<3> O `bus` deixa uma estudante, depois outra. +<4> As passageiras desembarcadas desapareceram do time de basquete! + +<<< +`TwilightBus` viola o "Princípio da Menor Surpresa", uma boa prática do design de +interfaces.footnote:[Ver «_Principle of least astonishment_» [.small]#[fpy.li/6-5]#.] +Com certeza, é surpreendente: quando o ônibus deixa uma estudante, +o nome dela é removido da escalação do time de basquete. + + +<> é a implementação de `TwilightBus` e uma explicação do problema. + +[[ex_twilight_bus]] +.Classe simples mostrando o perigo de mudar argumentos recebidos +==== +[source, python] +---- +include::../code/06-obj-ref/twilight_bus.py[tags=TWILIGHT_BUS_CLASS] +---- +==== +[role="pagebreak-before less_space"] +<1> Aqui temos o cuidado de criar uma lista vazia quando `passengers` é `None`. +<2> Entretanto, esta atribuição transforma `self.passengers` em um apelido para `passengers`, +que por sua vez é um apelido para o argumento passado para `+__init__+` +(i.e. `basketball_team` em <>). +<3> Quando os métodos `.remove()` e `.append()` são usados com `self.passengers`, +estamos, na verdade, modificando a lista original recebida como argumento pelo construtor. + +O problema aqui é que o ônibus está apelidando a lista passada para o construtor. +Ao invés disso, ele deveria manter sua própria lista de passageiros. +A solução é simples: em `+__init__+`, quando o parâmetro `passengers` é fornecido, +`self.passengers` deveria ser inicializado com uma cópia daquela lista, +como fizemos, de forma correta, no <>: + +<<< +[source, python] +---- + def __init__(self, passengers=None): + if passengers is None: + self.passengers = [] + else: + self.passengers = list(passengers) <1> +---- +<1> Cria uma cópia da lista `passengers`, +ou converte o argumento para `list` se ele não for uma lista. + +Agora nossa manipulação interna da lista de passageiros não afetará +o argumento usado para inicializar o ônibus. +E com uma vantagem adicional, essa solução é mais flexível: +agora o argumento passado no parâmetro `passengers` pode ser +uma tupla ou qualquer outro tipo iterável, +como `set` ou mesmo resultados de uma consulta a um banco de dados, +pois o construtor de `list` aceita qualquer iterável. +Ao criar nossa própria lista, estamos também assegurando que ela suporta os métodos necessários, +`.remove()` e `.append()`, operações que usamos nos métodos `.pick()` e `.drop()`. + +[TIP] +==== +A menos que um método tenha o objetivo explícito de alterar um objeto recebido como argumento, +você deveria pensar bem antes de apelidar tal objeto e simplesmente vinculá-lo a +uma variável interna de sua classe. +Quando em dúvida, crie uma cópia. +Os clientes de sua classe ficarão mais felizes. +Claro, criar uma cópia não é grátis: há custos de memória e processamento. +Entretanto, uma API que causa bugs sutis é +um problema bem maior que uma que seja um pouco mais lenta ou que use mais recursos. +==== + +Agora vamos conversar sobre uma das instruções mais obscuras de Python: +`del`.((("", startref="ORfparam06")))((("", startref="muttype06")))((("", startref="Pmut06"))) + +<<< + +[[del_sec]] +=== del e coleta de lixo + +[quote, “Modelo de Dados” capítulo de A Referência da Linguagem Python] +____ +Os objetos((("object references", "del and garbage collection", +id="ORdel06")))((("garbage collection", id="garb06"))) nunca são destruídos explicitamente; +no entanto, quando eles se tornam inacessíveis, eles podem ser coletados como lixo. +____ + +A((("del statement", id="del06"))) primeira surpresa de `del` é não ser uma função, +mas uma instrução (_statement_). + +Escrevemos `del x` e não `del(x)`—apesar dessa última forma funcionar também, +mas apenas porque as expressões `x` e `(x)` em geral têm o mesmo significado em Python. + +O segundo aspecto surpreendente é que `del` apaga referências, não objetos. +A coleta de lixo pode eliminar um objeto da memória como resultado indireto de `del`, +se a variável apagada for a última referência ao objeto. +Reassociar uma variável também pode reduzir a zero o número de referências a um objeto, +causando sua destruição. + +[source, python] +---- +>>> a = [1, 2] <1> +>>> b = a <2> +>>> del a <3> +>>> b <4> +[1, 2] +>>> b = [3] <5> +---- +<1> Cria o objeto `[1, 2]` e vincula `a` a ele. +<2> Vincula `b` ao mesmo objeto `[1, 2]`. +<3> Apaga a referência `a`. +<4> `[1, 2]` não é afetado, pois `b` ainda aponta para ele. +<5> Reassociar `b` a um objeto diferente remove a última referência restante a `[1, 2]`. +Agora o coletor de lixo pode descartar aquele objeto. + + +[WARNING] +==== +Existe((("__del__"))) um método especial `+__del__+`, +mas ele não causa a remoção de uma instância e não deve ser invocado em seu código. +O método `+__del__+` é invocado pelo interpretador Python quando a instância está prestes a ser destruída, +para dar a ela a chance de liberar recursos externos. +É muito raro ser preciso implementar `+__del__+` em seu código, +mas ainda assim alguns programadores Python perdem tempo codando este método sem necessidade. +O uso correto de `+__del__+` é bastante complexo. +Consulte +«`+__del__+`» [.small]#[fpy.li/3x]# no capítulo "Modelo de Dados" em _A Referência da Linguagem Python_. +==== + +No CPython, o algoritmo primário de coleta de lixo é ((("reference counting")))a contagem de referências. +Essencialmente, cada objeto mantém uma contagem do número de referências apontando para si. +Assim que a contagem chega a zero, o objeto é imediatamente destruído: +CPython invoca o método `+__del__+` no objeto (se definido) +e daí libera a memória alocada para aquele objeto. +No CPython 2.0, um algoritmo de coleta de lixo geracional foi acrescentado, +para detectar grupos de objetos envolvidos em referências cíclicas—grupos que +podem ser inacessíveis mesmo que existam referências restantes, +quando todas as referências mútuas estão contidas dentro daquele grupo. +Outras implementações de Python têm coletores de lixo mais sofisticados, +que não se baseiam na contagem de referências, +o que significa que o método `+__del__+` pode não ser chamado +imediatamente quando não existem mais referências ao objeto. +Veja «_PyPy, Garbage Collection, and a Deadlock_» [.small]#[fpy.li/6-7]# +de A. Jesse Jiryu Davis para uma discussão sobre os usos próprios e impróprios de `+__del__+`. + +Para demonstrar o fim da vida de um objeto, <> usa `weakref.finalize` +para registrar uma função callback a ser chamada quando o objeto é destruído. + +[[ex_finalize]] +.Detectando o fim de um objeto quando não resta nenhuma referência apontando para ele + +==== +[source, python] +---- +>>> import weakref +>>> s1 = {1, 2, 3} +>>> s2 = s1 <1> +>>> def bye(): <2> +... print('...like tears in the rain.') +... +>>> ender = weakref.finalize(s1, bye) <3> +>>> ender.alive <4> +True +>>> del s1 +>>> ender.alive <5> +True +>>> s2 = 'spam' <6> +...like tears in the rain. +>>> ender.alive +False +---- +==== +<1> `s1` e `s2` são apelidos do mesmo conjunto, `{1, 2, 3}`. +<2> Para essa demonstração, a função `bye` não deve ser um método vinculado ao objeto prestes a ser destruído, +nem manter uma referência para o objeto. +<3> Registra o callback `bye` no objeto referenciado por `s1`. +<4> O atributo `.alive` é `True` antes do objeto `finalize` ser chamado. +<5> Como vimos, `del` não apaga o objeto, apenas a referência `s1` a ele. +<6> Reassociar a última referência, `s2`, torna `{1, 2, 3}` inacessível. +Ele é destruído, o callback `bye` é invocado, e `ender.alive` se torna `False`. + +O ponto principal de <> é mostrar explicitamente que `del` não apaga objetos, +mas que objetos podem ser apagados como uma consequência de +ficarem inacessíveis após o uso de `del`. + +Você pode estar se perguntando por que o objeto `{1, 2, 3}` foi destruído em <>. +Afinal, a referência `s1` foi passada para a função `finalize`, +que precisa tê-la mantido para conseguir monitorar o objeto e invocar o callback. +Isso funciona porque `finalize` mantém uma((("weak references"))) +_referência fraca_ (_weak reference_) para {1, 2, 3}. +Referências fracas não aumentam a contagem de referências de um objeto. +Assim, uma referência fraca não evita que o objeto alvo seja removido pelo coletor de lixo. +Referências fracas são úteis em cenários de caching, +pois não queremos que os objetos "cacheados" sejam mantidos vivos apenas +por terem uma referência no cache.((("", startref="ORdel06")))((("", startref="del06")))((("", startref="garb06"))) + +[NOTE] +==== +Referências fracas são um tópico muito especializado, +então decidi retirá-lo dessa segunda edição. +Em vez disso, publiquei a nota +«_Weak References_ em _https://fluentpython.com_» [.small]#[fpy.li/weakref]#. +==== + +<<< +=== Peças que Python prega com imutáveis + +[NOTE] +==== +Esta((("object references", "immutability and"))) seção opcional discute detalhes que +não são muito importantes para _usuários_ de Python, +e que podem não se aplicar a outras implementações da linguagem ou mesmo a futuras versões de CPython. +Mas já vi pessoas tropeçarem nesses casos obscuros e passarem a usar o((("is operator"))) +operador `is` incorretamente, então decidi abordar esses detalhes. +==== + +Fiquei((("tuples", "immutability and"))) surpreso ao descobrir que, dada uma tupla `t`, +a chamada `t[:]` não cria uma cópia, mas devolve uma referência para o mesmo objeto. +Da mesma forma, `tuple(t)` também retorna uma referência para a mesma +tupla.footnote:[Isso está claramente documentado. +Digite `help(tuple)` no console de Python e leia: +"Se o argumento é uma tupla, o valor de retorno é o mesmo objeto." +Pensei que sabia tudo sobre tuplas antes de escrever esse livro.] + +O <> demonstra esse fato. + +[[ex_same_tuple]] +.Uma tupla construída a partir de outra é, na verdade, exatamente a mesma tupla. +==== +[source, python] +---- +>>> t1 = (1, 2, 3) +>>> t2 = tuple(t1) +>>> t2 is t1 <1> +True +>>> t3 = t1[:] +>>> t3 is t1 <2> +True +---- +==== +<1> `t1` e `t2` estão vinculadas ao mesmo objeto +<2> Assim como `t3`. + +Podemos observar o mesmo comportamento com instâncias de `str`, `bytes` e `frozenset`. +Note que `frozenset` não é uma sequência, então `fs[:]` +não funciona se `fs` é um `frozenset`. +Mas `fs.copy()` tem o mesmo efeito: ele trapaceia e retorna uma referência ao mesmo objeto, +e não uma cópia.footnote:[Essa mentirinha inofensiva, +do método `copy` não copiar nada, é justificável pela compatibilidade da interface: +torna `frozenset` mais compatível com `set`. +De qualquer forma, não faz diferença para o usuário final +se dois objetos imutáveis idênticos são o mesmo ou são cópias.] + +<<< +O <> mostra outra otimização, relacionada aos tipos `str` e `int`: + +[[ex_same_string]] +.Strings e inteiros literais podem criar objetos compartilhados. +==== +[source, python] +---- +>>> s1 = 'ABC' +>>> s2 = 'ABC' # <1> +>>> s2 is s1 # <2> +True +>>> n1 = 10 +>>> n2 = 10 +>>> n1 is n2 # <3> +True +>>> n3 = 1729 +>>> n4 = 1729 +>>> n3 is n4 # <4> +False +---- +==== +<1> Criando duas `str` com o mesmo valor. +<2> Surpresa: `a` e `b` se referem ao mesmo objeto `str`! +<3> Alguns inteiros pequenos são compartilhados. +<4> Outros inteiros não são compartilhados. + +O((("interning"))) compartilhamento de strings literais é +uma técnica de otimização chamada _internalização_ (_interning_). +O CPython usa uma técnica similar com inteiros pequenos, +para evitar a duplicação desnecessária de números que aparecem com muita frequência em programas, +como 0, 1, -1, etc. +Observe que o CPython não internaliza todas as strings e inteiros, +e o critério pelo qual ele faz isso é um detalhe de implementação não documentado. + +[WARNING] +==== +Não dependa da internalização de `str` ou `int`! + +Sempre use `==` em vez de `is` para comparar strings ou inteiros. +A internalização é uma otimização opcional. +==== + +Os truques discutidos nessa seção, incluindo o comportamento de `frozenset.copy()`, +são mentiras inofensivas que economizam memória e tornam o interpretador mais rápido. +Não se preocupe, elas não trarão nenhum problema, pois se aplicam apenas a tipos imutáveis. +Provavelmente, o melhor uso para esse tipo de detalhe é ganhar apostas contra outros +Pythonistas.footnote:[Um péssimo uso dessas informações seria perguntar sobre elas +quando entrevistando candidatos a emprego ou criando perguntas para exames de "certificação". +Há inúmeros fatos mais importantes e úteis para testar conhecimentos sobre Python.] + +=== Resumo do capítulo + +Todo((("object references", "overview of"))) objeto em Python tem uma identidade, +um tipo e um valor. +Apenas o valor do objeto pode mudar ao longo do +tempo.footnote:[Na verdade, o tipo de um objeto pode ser modificado, +bastando para isso atribuir uma classe diferente ao atributo `+__class__+` do objeto. +Mas isso é uma perversão, e eu me arrependo de ter escrito essa nota de rodapé.] + +Se duas variáveis se referem a objetos imutáveis de valor igual (quando `a == b` é `True`), +na prática, dificilmente importa se elas se referem a cópias de mesmo valor +ou são apelidos do mesmo objeto, porque o valor de objeto imutável não muda. +Uma exceção é o caso das tuplas: se uma tupla contém referências para itens mutáveis, +então seu valor mudará quando o valor de um item mutável for alterado. +Na prática, esse cenário não é tão comum. +O que nunca muda numa coleção imutável são as identidades dos objetos mantidos ali. +A classe `frozenset` não sofre desse problema, +porque ela só pode conter elementos hashable, +e o valor de um objeto hashable não pode mudar, por definição. + +O fato de variáveis conterem referências tem muitas consequências práticas +para a programação em Python: + +* Uma atribuição simples não cria cópias. +* Uma atribuição composta com `+=` ou `*=` cria novos objetos se +a variável à esquerda da atribuição estiver vinculada a um objeto imutável, +mas pode modificar um objeto mutável diretamente. +* Atribuir um novo valor a uma variável existente não muda +o objeto previamente vinculado à variável. +Isso se chama _rebinding_ (re-vinculação); +a variável passa a se referir a um objeto diferente. +Se aquela variável era a última referência ao objeto anterior, +aquele objeto será eliminado pela coleta de lixo. + +* Parâmetros de função são passados como apelidos, +o que significa que a função pode alterar qualquer objeto mutável recebido como argumento. +Não há como evitar isso, exceto criando cópias locais ou usando objetos imutáveis +(i.e., passando uma tupla em vez de uma lista) +* Usar objetos mutáveis como valores default de parâmetros de função é perigoso, +pois se os parâmetros forem modificados pela função, o default muda, +afetando chamadas posteriores que usem o default. + +<<< +No CPython, um objeto é descartado assim que o número de referências a ele chega a zero. +Objetos também podem ser descartados se formarem grupos com +referências cíclicas sem nenhuma referência externa ao grupo. + +Em algumas situações, pode ser útil manter uma referência para um objeto que +não vai, por si só, manter o objeto vivo. +Um exemplo é uma classe que queira manter o registro de todas as suas instâncias atuais. +Isso pode ser feito com referências fracas, +um mecanismo de baixo nível encontrado nas coleções `WeakValueDictionary`, +`WeakKeyDictionary`, `WeakSet`, e na função `finalize` do módulo `weakref`. +Leia «_Weak References_» [.small]#[fpy.li/weakref]# +para aprender mais sobre `weakref` no _https://fluentpython.com_. + +=== Para saber mais + +O((("object references", "further reading on"))) capítulo +«Modelo de Dados» [.small]#[fpy.li/2j]# de _A Referência da Linguagem Python_ +inicia com uma explicação bastante clara sobre identidades e valores de objetos. + +Wesley Chun, autor da série _Core Python_, +apresentou «_Understanding Python's Memory Model, Mutability, and Methods_» [.small]#[fpy.li/6-8]# +na EuroPython 2011, discutindo não apenas o tema desse capítulo como também o uso de métodos especiais. + +Doug Hellmann escreveu os posts «_copy—Duplicate Objects_» [.small]#[fpy.li/6-9]# e +«weakref—Garbage-Collectable References to Objects» [.small]#[fpy.li/6-10]#, +cobrindo alguns dos tópicos que acabamos de tratar. + +Você pode encontrar mais informações sobre o coletor de lixo geracional do CPython em +«gc—Interface para o coletor de lixo» [.small]#[fpy.li/3y]#, +que começa com a frase "Este módulo fornece uma interface para o coletor de lixo opcional." +O adjetivo "opcional" pode ser surpreendente, +mas o capítulo «Modelo de Dados» [.small]#[fpy.li/2j]# também afirma: + +[quote] +____ +Uma implementação tem permissão para adiar a coleta de lixo ou omiti-la completamente -- é +um detalhe de implementação como a coleta de lixo é implementada, +desde que nenhum objeto que ainda esteja acessível seja coletado. +____ + +<<< +Pablo Galindo escreveu um texto mais aprofundado sobre o Coletor de Lixo em Python, em +«_Design of CPython’s Garbage Collector_» [.small]#[fpy.li/6-12]# +no «_Python Developer’s Guide_» [.small]#[fpy.li/6-13]#, +voltado para contribuidores novos e experientes da implementação CPython. + +O CPython 3.4 aperfeiçoou o tratamento de objetos que implementam `+__del__+`, +como descrito em «PEP 442—Safe object finalization» [.small]#[fpy.li/6-14]#. + +A Wikipedia tem um artigo sobre «string interning» [.small]#[fpy.li/6-15]#, +que menciona o uso desta técnica em várias linguagens, incluindo Python. + +A Wikipedia também tem um artigo sobre «_Haddocks' Eyes_» [.small]#[fpy.li/6-16]#, +a canção de Lewis Carroll que mencionei no início deste capítulo. +Os editores da Wikipedia escreveram que a letra é usada em trabalhos de lógica e filosofia +"para elaborar a condição simbólica do conceito de 'nome': +um nome como um marcador de identificação pode ser atribuído a qualquer coisa, +incluindo outro nome, introduzindo assim níveis diferentes de simbolização." + +<<< +.Ponto de vista +**** + +[role="soapbox-title"] +*Tratamento igual para todos os objetos* + +Aprendi((("object references", "Soapbox discussion")))((("Soapbox sidebars", +"equality (==) operator")))((("== (equality) operator")))((("equality (==) operator"))) +Java antes de conhecer Python. +O operador `==` em Java sempre me pareceu equivocado. +É mais comum que programadores estejam preocupados com a igualdade do que com a identidade. +Mas para objetos (não tipos primitivos), o `==` em Java compara identidades, não valores. +Mesmo para algo tão básico quanto comparar strings, +Java obriga você a usar o método `.equals`. +E mesmo assim, há outro problema: se você escrever `a.equals(b)` e `a` for `null`, +você causa uma _null pointer exception_ (exceção de ponteiro nulo). +Os projetistas de Java sentiram necessidade de sobrecarregar `+` para strings; +por que não seguiram com esta ideia e sobrecarregaram `==` também? + +Python faz melhor. +O operador `==` compara valores de objetos; `is` compara referências. +E como Python permite sobrecarregar operadores, +`==` funciona de forma sensata com todos os objetos na biblioteca padrão, +incluindo `None`, que é um objeto de verdade, ao contrário do `null` de Java. + +E claro, você pode definir `+__eq__+` nas suas próprias classes para controlar +o que `==` significa para suas instâncias. +Se você não sobrecarregar `+__eq__+`, o método herdado de `object` compara os IDs dos objetos, +então a regra básica é que cada instância de uma classe definida pelo usuário é considerada diferente. + +Estas são algumas das coisas que me fizeram mudar de Java para Python +assim que terminei de ler _The Python Tutorial_ em uma tarde de setembro de 1998. + +[role="soapbox-title"] +*Mutabilidade* + +Este((("Soapbox sidebars", "mutability")))((("mutable objects")))((("objects", "mutable"))) +capítulo não seria necessário se todos os objetos em Python fossem imutáveis. +Quando você está lidando com objetos imutáveis, +não faz diferença se as variáveis guardam os objetos em si ou referências para objetos compartilhados. + +Se `a == b` é verdade, e nenhum dos dois objetos pode mudar, +eles podem perfeitamente ser o mesmo objeto. +Por isso a internalização de strings é segura. +A identidade dos objetos só é importante quando esses objetos podem mudar. + +Em programação funcional "pura", todos os dados são imutáveis: +concatenar algo a uma coleção, na verdade, cria uma nova coleção. +Elixir é uma linguagem funcional prática e fácil de aprender, +na qual todos os tipos nativos são imutáveis, incluindo as listas. + +Python, por outro lado, não é uma linguagem funcional, +muito menos uma linguagem funcional pura. +Instâncias de classes definidas pelo usuário são mutáveis por padrão em Python—como +na maioria das linguagens orientadas a objetos. +Ao criar seus próprios objetos, você precisa tomar o cuidado adicional de torná-los imutáveis, +se este for um requisito. +Cada atributo do objeto precisa ser também imutável, +senão você termina criando algo como uma tupla: +imutável quanto ao `id` do objeto, +mas seu valor pode mudar se a tupla contiver um objeto mutável. + +Objetos mutáveis também são a razão pela qual programar com threads é tão difícil: +threads modificando objetos sem uma sincronização apropriada podem corromper dados. +Sincronização excessiva, por outro lado, causa deadlocks. +A linguagem e a plataforma Erlang—que inclui Elixir—foi projetada para maximizar +o tempo de execução em aplicações distribuídas de alta concorrência, +como aplicações de controle de telecomunicações. +Naturalmente, seus criadores tornaram os dados imutáveis por padrão. + +[role="soapbox-title"] +*Destruição de objetos e coleta de lixo* + +Não existe((("Soapbox sidebars", +"object destruction and garbage collection")))((("garbage collection"))) +em Python uma forma de destruir um objeto diretamente. +E essa omissão é uma grande qualidade: +se você pudesse destruir um objeto a qualquer momento, +o que aconteceria com as referências que apontam para ele? + +A coleta de lixo em CPython é feita principalmente por contagem de referências, +que é fácil de implementar, mas vulnerável a vazamentos de memória (_memory leaks_) +quando existem referências cíclicas. +Assim, com a versão 2.0 (de outubro de 2000), +um coletor de lixo geracional foi implementado, +e ele consegue descartar objetos inatingíveis que foram mantidos vivos por ciclos de referências. + +Mas a contagem de referências ainda está lá como mecanismo básico, +e ela causa a destruição imediata de objetos com zero referências. +Isso significa que, em CPython -- pelo menos por hora -- é seguro escrever: + +[source, python] +---- +open('test.txt', 'wt', encoding='utf-8').write('1, 2, 3') +---- + +Este código é seguro porque a contagem de referências do objeto arquivo será zero +após o método `write` retornar, e o arquivo será fechado quando o objeto for descartado. +Entretanto, a mesma linha não é segura em Jython ou IronPython, +que usam o coletor de lixo dos runtimes de seus ambientes +(a Java VM e a .NET CLR, respectivamente), que são mais sofisticados, +mas não se baseiam em contagem de referências, +e podem demorar mais para destruir o objeto e fechar o arquivo. +Em todos os casos, incluindo em CPython, a melhor prática é fechar o arquivo explicitamente, +e a forma mais confiável de fazer isso é usando a instrução `with`, +que garante o fechamento do arquivo mesmo se acontecerem +exceções enquanto ele estiver aberto. +Usando `with`, a linha anterior se torna: + +[source, python] +---- +with open('test.txt', 'wt', encoding='utf-8') as fp: + fp.write('1, 2, 3') +---- + +Se tiver interesse no assunto de coletores de lixo, +talvez queira ler o artigo de Thomas Perl, +«_Python Garbage Collector Implementations: CPython, PyPy and GaS_» [.small]#[fpy.li/6-17]#, +onde aprendi esses detalhes sobre a segurança de `open().write()` em CPython. + +[role="soapbox-title"] +*Passagem de parâmetros: chamada por compartilhamento* + +Uma maneira popular de explicar como a passagem de parâmetros +funciona em Python é a frase: +"Parâmetros são passados por valor, mas os valores são referências." +Isso não está errado, +mas causa confusão porque os modos mais comuns de passagem de parâmetros +em linguagens tradicionais são +_chamada por valor_ (a função recebe uma cópia dos argumentos) e +_chamada por referência_ (a função recebe um ponteiro para o argumento). + +<<< +Em Python, a função recebe uma cópia dos argumentos, +mas os argumentos são sempre referências. +Então o valor de um objeto referenciado pode ser alterado pela função, +se ele for mutável, mas sua identidade não. +Além disso, como a função recebe uma cópia da referência em um argumento, +reatribuir a essa referência no corpo da função não tem efeito fora da função. + +Adotei o termo _chamada por compartilhamento_ depois de +encontrar a definição de _call by sharing_ no livro +_Programming Language Pragmatics_, 3rd ed., de Michael L. Scott +(Morgan Kaufmann), seção _8.3.1: Parameter Modes._ + +**** diff --git a/vol1/cap07.adoc b/vol1/cap07.adoc new file mode 100644 index 00000000..b5267274 --- /dev/null +++ b/vol1/cap07.adoc @@ -0,0 +1,937 @@ +[[ch_func_objects]] +== Funções como objetos de primeira classe +:example-number: 0 +:figure-number: 0 + +[quote, Guido van Rossum, BDFL de Python] +____ +Nunca achei que Python tenha sido fortemente influenciado por linguagens funcionais, independentemente do que outros digam ou pensem. +Eu estava mais familiarizado com linguagens imperativas, como o C e o Algol e, apesar de ter tornado as funções objetos de primeira classe, não via Python como uma linguagem funcional.footnote:[«_Origins of Python's 'Functional' Features_ (As origens dos recursos 'funcionais' de Python)» [.small]#[fpy.li/7-1]#, do blog _The History of Python_ (A História de Python) do próprio Guido.]footnote:[_Benevolent Dictator For Life_ (Ditador Benevolente Vitalício). +Veja Guido van Rossum em «_Origin of BDFL_» [.small]#[fpy.li/bdfl]#.] +____ + +No Python, funções((("objects", "first-class")))((("first-class objects")))((("functions, as first-class objects", +"definition of term"))) são objetos de primeira classe. +Estudiosos de linguagens de programação definem um "objeto de primeira classe" +como uma entidade que pode ser: + +* Criada durante a execução de um programa +* Atribuída a uma variável ou a um elemento em uma estrutura de dados +* Passada como argumento para uma função +* Devolvida como o resultado de uma função + +Inteiros, strings e dicionários são outros exemplos de objetos de primeira classe no Python—nada de incomum aqui. +Tratar funções como objetos de primeira classe é um recurso essencial das linguagens funcionais, como Clojure, Elixir e Haskell. +Entretanto, funções de primeira classe são tão úteis que foram adotadas por linguagens muito populares, +como JavaScript, Go e até Java (desde o JDK 8), nenhuma das quais pretende ser uma "linguagem funcional". + +Esse capítulo e quase toda a Parte III do livro exploram as aplicações práticas de se tratar funções como objetos. + +[TIP] +==== +O termo "funções de primeira classe" é largamente usado como uma forma abreviada de +"funções como objetos de primeira classe". +Ele não é ideal, pois sugere a existência de uma "elite" entre as funções. +Em Python, todas as funções são de primeira classe. +==== + + +=== Novidades neste capítulo + +A seção "Os nove sabores de objetos invocáveis"((("functions, as first-class objects", "significant changes to"))) +(<>) se chamava "Sete sabores de objetos invocáveis" na primeira edição deste livro. +Os novos invocáveis são corrotinas nativas e geradores assíncronos, introduzidos no Python 3.5 e 3.6, respectivamente. +Ambos serão estudados no «Capítulo 21» [.small]#[vol.3, fpy.li/21]#, mas são mencionados aqui ao lado dos outros invocáveis. + +A <> é nova, e fala de um recurso que surgiu no Python 3.8: parâmetros somente posicionais. + +Transferi a discussão sobre acesso a anotações de funções durante a execução para a Seção 15.5 [.small]#[vol.2, fpy.li/58]#. +Quando escrevi a primeira edição, a «_PEP 484—Type Hints_ (Dicas de Tipo)» [.small]#[fpy.li/pep484]# +ainda estava sendo discutida, e as anotações eram usadas de várias formas diferentes. +Desde o Python 3.5, anotações precisam estar em conformidade com a PEP 484. +Assim, o melhor lugar para falar delas é na discussão sobre as dicas de tipo. + +[NOTE] +==== +A((("function parameters, introspection of")))((("parameters", "introspection of function parameters"))) +primeira edição desse livro continha seções sobre a introspecção de objetos função, +que desciam a detalhes de baixo nível e desviavam do assunto principal do capítulo. +Reuni aquelas seções em um post intitulado +«_Introspection of Function Parameters_» [.small]#[fpy.li/7-2]#, no _https://fluentpython.com_. +==== + +Agora vamos ver por que as funções de Python são objetos completos. + + +=== Tratando uma função como um objeto + +A((("functions, as first-class objects", "treating functions like objects", +id="FAFtreat07")))((("objects", "treating functions like", id="Otreat07"))) +sessão de console no <> mostra que funções de Python são objetos. +Ali criamos uma função, a chamamos, lemos seu atributo +`+__doc__+` e verificamos que o próprio objeto função é uma instância da classe `function`. + +[[func_object_demo]] +.Cria e testa uma função, e então lê seu `+__doc__+` e verifica seu tipo +==== +[source, python] +---- +>>> def factorial(n): <1> +... """returns n!""" +... return 1 if n < 2 else n * factorial(n - 1) +... +>>> factorial(42) +1405006117752879898543142606244511569936384000000000 +>>> factorial.__doc__ <2> +'returns n!' +>>> type(factorial) <3> + +---- +==== +<1> Isso é uma sessão do console, então estamos criando uma função "durante a execução". +<2> `+__doc__+` é um dos muitos atributos de objetos função. +<3> `factorial` é uma instância da classe `function`. + +O atributo `+__doc__+` é usado para gerar o texto de ajuda de um objeto. +No console de Python, a instrução `help(factorial)` mostrará uma tela como a <>. + +[[factorial_help]] +.Tela de ajuda de `factorial`; parte do texto vem do atributo `+__doc__+` +image::../images/flpy_0701.png[align="center",pdfwidth=10cm] + +O <> mostra a natureza de "primeira classe" de um objeto função. +Podemos atribuir tal objeto a uma variável `fact` e invocá-lo por esse nome. +Podemos também passar `factorial` como argumento para a função «`map`» [.small]#[fpy.li/44]#. +Invocar `map(function, iterable)` devolve um iterável no qual cada item é +o resultado de uma chamada ao primeiro argumento (uma função) +com elementos sucessivos do segundo argumento (um iterável), `range(11)` no exemplo. + + +[[func_object_demo2]] +.Invoca `factorial` através da variável `fact`, e passa `factorial` como argumento para `map` +==== +[source, python] +---- +>>> fact = factorial +>>> fact + +>>> fact(5) +120 +>>> map(factorial, range(11)) + +>>> list(map(factorial, range(11))) +[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800] +---- +==== + +Ter funções de primeira classe permite programar em um estilo funcional. +Um dos marcos da «programação funcional» [.small]#[fpy.li/45]# +é o uso de funções de ordem superior, nosso próximo tópico.((("", startref="Otreat07")))((("", startref="FAFtreat07"))) + + +=== Funções de ordem superior + +Uma((("functions, as first-class objects", "higher-order functions", +id="FAFhigh07")))((("higher-order functions", id="higher07")))((("functions", "higher-order functions", id="Fhigh07"))) +função que recebe uma função como argumento ou devolve uma função como resultado +é uma _função de ordem superior_. +Uma dessas funções é `map`, usada no <>. Outra é a função embutida `sorted`: +o argumento opcional `key` permite fornecer uma função, que será então aplicada na ordenação de cada item, como vimos na <>. +Por exemplo, para ordenar uma lista de palavras por tamanho, passe a função `len` como `key`, como no <>. + +[[higher_order_sort]] +.Ordenando uma lista de palavras por tamanho +==== +[source, python] +---- +>>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana'] +>>> sorted(fruits, key=len) +['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry'] +>>> +---- +==== + +Qualquer função com um argumento pode ser usada como chave. +Por exemplo, para criar um dicionário de rimas, pode ser útil ordenar cada palavra escrita ao contrário. +No <>, observe que as palavras na lista não são modificadas de forma alguma; +apenas suas versões escritas na ordem inversa são utilizadas como critério de ordenação. +Por isso as _berries_ aparecem juntas. + +<<< +[[higher_order_sort_reverse]] +.Ordenando uma lista de palavras pela ordem inversa de escrita +==== +[source, python] +---- +>>> def reverse(word): +... return word[::-1] +>>> reverse('testing') +'gnitset' +>>> sorted(fruits, key=reverse) +['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry'] +---- +==== + +No((("map function", id="map07")))((("functions", "filter, map, and reduce functions")))((("filter function", +id="filter07")))((("reduce function", id="reduce07")))((("apply function", id="apply07"))) +paradigma funcional de programação, algumas das funções de ordem superior mais conhecidas são `map`, `filter`, `reduce`, e `apply`. +A função `apply` foi descontinuada no Python 2.3 e removida no Python 3, por não ser mais necessária. +Se você precisar chamar uma função com um conjunto dinâmico de argumentos, +pode escrever `fn(*args, **kwargs)` em vez de `apply(fn, args, kwargs)` como fazíamos no século passado. + +As funções de ordem superior `map`, `filter` e `reduce` ainda existem, +mas temos alternativas melhores para a maioria de seus casos de uso. + +[[map_filter_reduce_sec]] +==== Substitutos modernos para map, filter, e reduce + +Linguagens funcionais((("list comprehensions (listcomps)", "versus map and filter functions")))((("generator expressions (genexps)"))) +normalmente oferecem as funções de ordem superior `map`, `filter`, e `reduce` (algumas vezes com nomes diferentes). +As funções `map` e `filter` ainda estão embutidas no Python, mas elas não são mais tão importantes +desde a introdução das compreensões de lista e das expressões geradoras. +Uma listcomp ou uma genexp fazem o mesmo que `map` e `filter` combinadas, e são mais legíveis. +Considere o <>. + + +[[reduce_x_sum]] +.Listas de fatoriais produzidas com `map` e `filter`, comparadas com alternativas escritas com compreensões de lista +==== +[source, python] +---- +>>> list(map(factorial, range(6))) <1> +[1, 1, 2, 6, 24, 120] +>>> [factorial(n) for n in range(6)] <2> +[1, 1, 2, 6, 24, 120] +>>> list(map(factorial, filter(lambda n: n % 2, range(6)))) <3> +[1, 6, 120] +>>> [factorial(n) for n in range(6) if n % 2] <4> +[1, 6, 120] +---- +==== +<1> Cria uma lista de fatoriais de 0! a 5!. +<2> Mesma operação, com uma compreensão de lista. +<3> Lista de fatoriais de números ímpares até 5!, usando `map` e `filter`. +<4> A compreensão de lista realiza a mesma tarefa, substituindo `map` e `filter`, +e tornando `lambda` desnecessário. + +No Python 3, `map` e `filter` devolvem geradores—uma forma de iterador—então +sua substituta direta agora é uma expressão geradora +(no Python 2, essas funções devolviam listas, então sua alternativa mais próxima era a compreensão de lista). + +A função `reduce` foi rebaixada de função embutida, no Python 2, para o módulo `functools` no Python 3. +Seu caso de uso mais comum, a somatória, é melhor atendido pela função embutida `sum`, +disponível desde o Python 2.3 (lançado em 2003). +A função `sum` é mais legível e mais eficiente: + +[[reduce_x_sum2]] +.Soma de inteiros de 0 a 99, realizada com `reduce` e `sum` +==== +[source, python] +---- +>>> from functools import reduce <1> +>>> from operator import add <2> +>>> reduce(add, range(100)) <3> +4950 +>>> sum(range(100)) <4> +4950 +>>> +---- +==== +<1> A partir de Python 3.0, `reduce` deixou de ser uma função embutida. +<2> Importa `add` para evitar a criação de uma função apenas para somar dois números. +<3> Soma os inteiros de 0 a 99. +<4> Mesma operação, com `sum`—não é preciso importar nem chamar `reduce` e `add`. + +[NOTE] +===================================================================== +A ideia comum de `sum` e `reduce` +é aplicar alguma operação sucessivamente a itens em uma série, +acumulando os resultados anteriores, reduzindo assim uma série de valores a um único valor. +===================================================================== + +Outras funções de redução embutidas são `all` e `any`: + +`all(iterable)`:: Devolve `True` se não há nenhum elemento falso no iterável; `all([])` devolve `True`. + +`any(iterable)`:: Devolve `True` se algum elemento do `iterable` for verdadeiro; +`any([])` devolve `False`. + +Dou uma explicação mais completa sobre `reduce` na «Seção 12.7» [.small]#[vol.2, fpy.li/59]#, +onde um exemplo mais longo, atravessando várias seções, cria um contexto +significativo para o uso dessa função. +As funções de redução serão resumidas mais à frente no livro, na «Seção 17.10» [.small]#[vol.3, fpy.li/5a]#, +quando estivermos tratando dos iteráveis. +Para usar uma função de ordem superior, às vezes é conveniente criar uma pequena +função, que será usada apenas uma vez como argumento para outra função. +As funções anônimas existem para isso. +Vamos falar delas a seguir.((("", startref="map07")))((("", startref="filter07")))((("", startref="reduce07")))((("", startref="apply07")))((("", startref="higher07")))((("", startref="FAFhigh07")))((("", startref="Fhigh07"))) + + +=== Funções anônimas + +A((("functions, as first-class objects", "anonymous functions")))((("anonymous functions")))((("lambda keyword")))((("keywords", "lambda keyword"))) +palavra reservada `lambda` cria uma função anônima dentro de uma expressão Python. + +Entretanto, a sintaxe simples de Python obriga que o corpo de uma `lambda` seja uma expressão. +Em outras palavras, o corpo não pode conter instruções como `while`, `try`, etc. +A atribuição com `=` também é uma instrução, então não pode ocorrer em um `lambda`. +A nova sintaxe da expressão de atribuição, usando `:=`, pode ser usada. +Porém, se você precisar dela, sua `lambda` provavelmente é complicada e difícil de ler, +e deveria ser refatorada para uma função nomeada usando `def`. + +O melhor uso das funções anônimas é no contexto de uma lista de argumentos para uma função de ordem superior. +Por exemplo, o <> é o exemplo do dicionário de rimas do +<> reescrito com `lambda`, sem definir uma função `reverse`. + +<<< +[[higher_order_sort_reverse_lambda]] +.Ordenando palavras escritas na ordem inversa com `lambda` +==== +[source, python] +---- +>>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana'] +>>> sorted(fruits, key=lambda word: word[::-1]) +['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry'] +---- +==== + +Fora do contexto limitado dos argumentos das funções de ordem superior, funções anônimas são pouco úteis no Python. +As restrições sintáticas tendem a tornar ilegíveis as `lambdas` não-triviais. +Se uma `lambda` é difícil de ler, sugiro fortemente seguir o conselho de Fredrik Lundh sobre refatoração. + +.A receita de Fredrik Lundh para refatoração de lambdas +**** + +Se você encontrar um trecho de código difícil de entender por causa de uma `lambda`, +Fredrik Lundh sugere o seguinte procedimento de refatoração: + +. Escreva um comentário explicando o que diabos aquela `lambda` faz. +. Estude o comentário por algum tempo, e pense em um nome que traduza sua essência. +. Converta a `lambda` para uma declaração `def`, usando aquele nome. +. Remova o comentário. + +Esses passos são uma citação do +«_Programação Funcional—COMO FAZER_» [.small]#[fpy.li/46]#, +leitura obrigatória. +**** + +A sintaxe `lambda` é apenas açúcar sintático: uma expressão `lambda` cria um objeto função, +exatamente como a declaração `def`. +Esse é apenas um dos vários tipos de objetos invocáveis no Python. +Na próxima seção revisaremos todos eles. + +<<< +[[flavors_of_callables]] +=== Os nove sabores de objetos invocáveis + +O((("functions, as first-class objects", "callable objects", id="FAFcall07")))((("callable objects", "nine types of", id="calobj07")))((("objects", "callable objects", id="Ocall07"))) operador de invocação `()` pode ser aplicado a outros objetos além de funções. +Para determinar se um objeto é invocável, use a função embutida `callable()`. +No Python 3.10, a «documentação do modelo de dados» [.small]#[fpy.li/47]# lista nove tipos invocáveis: + +[role="pagebreak-before less_space"] +Funções definidas pelo usuário:: Criadas((("user-defined functions"))) com instruções `def` ou expressões `lambda`. + +Funções embutidas:: Funções((("built-in functions"))) implementadas em C (no CPython), como `len` ou `time.strftime`. + +Métodos embutidos:: Métodos((("methods, as callable objects"))) implementados em C, como `dict.get`. + +Métodos:: Funções definidas no corpo de uma classe. + +Classes:: Quando((("classes", "as callable objects"))) invocada, uma classe executa seu método +`+__new__+` para criar uma instância, e a seguir `+__init__+`, para inicializá-la. Então a instância é devolvida ao usuário. +Como não existe um operador `new` no Python, invocar uma classe é como invocar uma função. + +Instâncias de classe:: Se uma classe define um método `+__call__+`, suas instâncias podem então ser invocadas como funções—esse é o assunto da próxima seção. + +Funções geradoras:: Funções ou métodos que usam a palavra reservada((("yield keyword")))((("keywords", "yield keyword")))((("generators", "generator functions in Python standard library"))) `yield` em seu corpo. +Quando chamadas, devolvem um objeto gerador. + +Funções de corrotinas nativas:: Funções((("native coroutines", "functions defined with async def"))) ou métodos definidos com `async def`. Quando chamados, devolvem um objeto corrotina. +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 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`. +Elas são o assunto do «Capítulo 21» [.small]#[vol.3, fpy.li/21]#. + + +[TIP] +==== +Dada a variedade dos tipos de invocáveis existentes no Python, a forma mais segura de determinar se um objeto é invocável é usando a função embutida `callable()`: + +---- +>>> abs, str, 'Ni!' +(, , 'Ni!') +>>> [callable(obj) for obj in (abs, str, 'Ni!')] +[True, True, False] +---- +==== + +Vamos agora criar instâncias de classes que funcionam como objetos invocáveis.((("", startref="FAFcall07")))((("", startref="calobj07")))((("", startref="Ocall07"))) + + +[[user_callables]] +=== Tipos invocáveis definidos pelo usuário + +Além((("functions, as first-class objects", "user-defined callable types")))((("callable objects", "user-defined")))((("objects", "user-defined callable objects"))) das funções serem objetos reais, +também é possível fazer com que objetos arbitrários se comportem como funções. +Para isso, basta implementar o método de instância `+__call__+`. + +O <> implementa uma classe `BingoCage`. Uma instância é criada a partir de qualquer iterável, e mantém uma `list` interna de itens, em ordem aleatória. +Invocar a instância extrai um item.footnote:[Por que criar uma `BingoCage` quando já temos `random.choice`? A função `choice` pode devolver o mesmo item múltiplas vezes, pois o item escolhido não é removido da coleção usada. +Invocações de `BingoCage` nunca devolvem um resultado duplicado—desde que a instância tenha sido preenchida com valores únicos.] + +[[ex_bingo_callable]] +.bingocall.py: uma `BingoCage` faz apenas uma coisa: escolhe itens de uma lista embaralhada +==== +[source, python] +---- +include::../code/07-1class-func/bingocall.py[tags=BINGO] +---- +==== +<1> `+__init__+` aceita qualquer iterável; criar uma cópia local evita efeitos colaterais inesperados sobre qualquer `list` passada como argumento. +<2> `shuffle` sempre vai funcionar, pois `self._items` é uma `list`. +<3> O método principal. +<4> Se `self._items` estiver vazia, gera exceção com uma mensagem clara. +<5> Atalho para `bingo.pick()`: `bingo()`. + +Aqui está uma demonstração simples do <>. Observe como uma instância de `bingo` pode ser invocada como uma função, e como a função embutida `callable()` a reconhece como um objeto invocável: + +[source, python] +---- +include::../code/07-1class-func/bingocall.py[tags=BINGO_DEMO] +---- + +Uma classe que implementa `+__call__+` é uma forma fácil de criar objetos similares a funções, +com algum estado interno que precisa ser mantido de uma invocação para outra, +como os itens restantes na `BingoCage`. +Outro bom caso de uso para `+__call__+` é a implementação de decoradores. +Decoradores devem ser invocáveis, e muitas vezes é conveniente "lembrar" algo entre chamadas ao decorador, +por exemplo, para _memoization_ (a manutenção dos resultados de algum processamento complexo e/ou demorado para uso posterior) +ou para separar uma implementação complexa entre vários métodos. + +A abordagem funcional para a criação de funções com estado interno é através do uso de clausuras (_closures_). +Clausuras e decoradores são o assunto do «Capítulo 9» [.small]#[vol.2, fpy.li/9]#. + +Vamos agora explorar a poderosa sintaxe oferecida pelo Python para declarar parâmetros de funções, e para passar argumentos para elas. + + +=== De parâmetros posicionais a parâmetros somente nomeados + +Um((("functions, as first-class objects", "flexible parameter handling and", +id="FAFflex07")))((("positional parameters", id="pospar07")))((("parameters", "positional", id="Pposition07"))) dos melhores recursos das funções Python é +sua sintaxe muito flexível para declaração e tratamento de parâmetros. +Exemplos((("unpacking", "iterables and mappings")))((("star (*) operator")))((("* (star) operator")))((("** (double star) operator")))((("double star (**) operator"))) disso são os usos de `+*+` e `+**+` para +desempacotar e capturar iteráveis e mapeamentos em argumentos separados na chamada de uma função. +Para ver esses recursos em ação, veja o código do <> e os testes mostrando seu uso no <>. + +[[tagger_ex]] +.`tag` gera elementos HTML; um argumento somente nomeado `class_` é usado para passar atributos "class"; o `_` é necessário porque `class` é uma palavra reservada no Python +==== +[source, python] +---- +include::../code/07-1class-func/tagger.py[tags=TAG_FUNC] +---- +==== + +A função `tag` pode ser invocada de muitas formas. Veja o <>. + +[[tagger_demo]] +.Algumas das muitas formas de invocar a função `tag` do <> +==== +[source, python] +---- +include::../code/07-1class-func/tagger.py[tags=TAG_DEMO] +---- +==== +<1> Um argumento posicional único produz uma `tag` vazia com aquele nome. +<2> Argumentos após o primeiro são capturados por `*content` em uma `tuple`. +<3> Argumentos nomeados que não são mencionados explicitamente na assinatura de `tag` são capturados por `**attrs` como um `dict`. +<4> O parâmetro `class_` só pode ser passado como um argumento nomeado. +<5> Um argumento posicional também pode ser passado por nome. +<6> Prefixar o `dict` `my_tag` com `+**+` passa todos os seus itens como argumentos separados, +que são então vinculados aos parâmetros nomeados, com o restante sendo capturado por `**attrs`. +Neste caso podemos ter uma chave `'class'` no `dict` de argumentos, porque é uma string, e não colide com a palavra reservada `class`. + +Argumentos somente nomeados((("keyword-only arguments")))((("parameters", "keyword-only")))((("arguments", "keyword-only arguments"))) são um recurso de Python 3. No <>, o parâmetro `class_` só pode ser passado como um argumento nomeado—ele nunca captura argumentos posicionais não-nomeados. +Para especificar argumentos somente nomeados ao definir uma função, eles devem ser nomeados após o argumento prefixado por `+*+`. Se você não quer incluir argumentos posicionais variáveis, mas ainda assim deseja incluir argumentos somente nomeados, coloque um `+*+` sozinho na assinatura, assim: + +[source, python] +---- +>>> def f(a, *, b): +... return a, b +... +>>> f(1, b=2) +(1, 2) +>>> f(1, 2) +Traceback (most recent call last): + File "", line 1, in +TypeError: f() takes 1 positional argument but 2 were given +---- + +Observe que argumentos somente nomeados não precisam ter um valor default: eles podem ser obrigatórios, como o `b` no exemplo acima. + + +[[positional_only_params]] +==== Parâmetros somente posicionais + +Desde o Python 3.8, assinaturas de funções definidas pelo usuário podem especificar parâmetros somente posicionais. +Esse recurso sempre existiu para funções embutidas, tal como `divmod(a, b)`, +que só pode ser chamada com parâmetros posicionais, e não na forma `divmod(a=10, b=4)`. + +Para definir uma função que requer parâmetros somente posicionais, use `/` na lista de parâmetros. + +Este exemplo, copiado de «O que há de novo no Python 3.8» [.small]#[fpy.li/48]#, mostra como emular a função embutida `divmod`: + +[source, python] +---- +def divmod(a, b, /): + return (a // b, a % b) +---- + +<<< +Todos os argumentos à esquerda da `/` são somente posicionais. +Após a `/`, você pode especificar outros argumentos, que funcionam da forma usual. + +[WARNING] +==== +Uma `/` na lista de parâmetros é um erro de sintaxe no Python 3.7 ou anteriores. +==== + +Por exemplo, considere a função `tag` do <>. +Se quisermos que o parâmetro `name` seja somente posicional, podemos acrescentar uma `/` após aquele parâmetro na assinatura da função, assim: + +[source, python] +---- +def tag(name, /, *content, class_=None, **attrs): + ... +---- + +Você pode encontrar outros exemplos de parâmetros somente posicionais no já citado +«O que há de novo no Python 3.8» [.small]#[fpy.li/48]# e na «_PEP 570_» [.small]#[fpy.li/pep570]#. + +Após esse mergulho nos recursos flexíveis de declaração de argumentos no Python, o resto desse capítulo trata dos pacotes da biblioteca padrão mais úteis para programar em um estilo funcional.((("", startref="FAFflex07")))((("", startref="pospar07")))((("", startref="Pposition07"))) + + +=== 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, 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]] +==== O módulo operator + +Na((("operator module", id="opmod07"))) programação funcional, é muitas vezes conveniente usar um operador aritmético como uma função. +Por exemplo, suponha que você queira multiplicar uma sequência de números para calcular fatoriais, mas sem usar recursão. +Para calcular a soma, podemos usar `sum`, mas não há uma função equivalente para multiplicação. +Você poderia usar ``reduce``—como vimos na <>—mas isso exige uma função para multiplicar dois itens da sequência. +O <> mostra como resolver esse problema usando `lambda`. + +[[fact_reduce_lambda_ex]] +.Fatorial implementado com `reduce` e uma função anônima +==== +[source, python] +---- +from functools import reduce + +def factorial(n): + return reduce(lambda a, b: a*b, range(1, n+1)) +---- +==== + +O módulo `operator` oferece funções equivalentes a dezenas de operadores, +para você não precisar escrever funções triviais como `lambda a, b: a*b`. +Com ele, podemos reescrever o <> como no <>. + +[[fact_reduce_operator_ex]] +.Fatorial implementado com `reduce` e `operator.mul` +==== +[source, python] +---- +from functools import reduce +from operator import mul + +def factorial(n): + return reduce(mul, range(1, n+1)) +---- +==== + +Outro grupo de "lambdas de um só truque" que `operator` substitui são funções +para extrair itens de sequências ou para ler atributos de objetos: +`itemgetter` e `attrgetter` são fábricas que criam funções customizadas +para fazer exatamente isso. + +O <> mostra um uso frequente de `itemgetter`: ordenar uma lista de tuplas pelo valor de um campo. +No exemplo, as cidades são exibidas por ordem de código de país (campo 1). +Essencialmente, `itemgetter(1)` cria uma função que, dada uma coleção, devolve o item no índice 1. +Isso é mais fácil de escrever e ler que `lambda fields: fields[1]`, que faz a mesma coisa. + +[[itemgetter_demo]] +.Demonstração de `itemgetter` para ordenar uma lista de tuplas (mesmos dados do <>) +==== +[source, python] +---- +>>> metro_data = [ +... ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)), +... ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)), +... ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)), +... ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)), +... ('São Paulo', 'BR', 19.649, (-23.547778, -46.635833)), +... ] +>>> +>>> from operator import itemgetter +>>> for city in sorted(metro_data, key=itemgetter(1)): +... print(city) +... +('São Paulo', 'BR', 19.649, (-23.547778, -46.635833)) +('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)) +('Tokyo', 'JP', 36.933, (35.689722, 139.691667)) +('Mexico City', 'MX', 20.142, (19.433333, -99.133333)) +('New York-Newark', 'US', 20.104, (40.808611, -74.020386)) +---- +==== + +Se você passar vários índices como argumentos para `itemgetter`, a função criada por ela vai devolver tuplas com os valores extraídos, algo que pode ser útil para ordenar((("keys", "sorting multiple"))) usando chaves múltiplas: + +[source, python] +---- +>>> cc_name = itemgetter(1, 0) +>>> for city in metro_data: +... print(cc_name(city)) +... +('JP', 'Tokyo') +('IN', 'Delhi NCR') +('MX', 'Mexico City') +('US', 'New York-Newark') +('BR', 'São Paulo') +---- + +Como `itemgetter` usa o operador `[]`, ela suporta não apenas sequências, mas também mapeamentos e qualquer classe que implemente +`+__getitem__+`. + +Uma irmã de `itemgetter` é `attrgetter`, para obter atributos por nome. +Se você passar os nomes de vários atributos para `attrgetter`, ela devolve uma tupla de valores. +Além disso, se o nome de qualquer argumento contiver um `.` (ponto), `attrgetter` +navegará por objetos aninhados para encontrar o atributo. +Esses comportamentos são apresentados no <>. +É uma sessão de console um pouco longa, pois precisamos criar uma +estrutura aninhada para demonstrar o tratamento de atributos com `.` por `attrgetter`. + +[[attrgetter_demo]] +.Demonstração de `attrgetter` para processar uma lista previamente definida de `namedtuple` chamada `metro_data` (a mesma lista do <>) +==== +[source, python] +---- +>>> from collections import namedtuple +>>> LatLon = namedtuple('LatLon', 'lat lon') # <1> +>>> Metropolis = namedtuple('Metropolis', 'name cc pop coord') # <2> +>>> metro_areas = [Metropolis(name, cc, pop, LatLon(lat, lon)) # <3> +... for name, cc, pop, (lat, lon) in metro_data] +>>> metro_areas[0] +Metropolis(name='Tokyo', cc='JP', pop=36.933, coord=LatLon(lat=35.689722, +lon=139.691667)) +>>> metro_areas[0].coord.lat # <4> +35.689722 +>>> from operator import attrgetter +>>> name_lat = attrgetter('name', 'coord.lat') # <5> +>>> +>>> for city in sorted(metro_areas, key=attrgetter('coord.lat')): # <6> +... print(name_lat(city)) # <7> +... +('São Paulo', -23.547778) +('Mexico City', 19.433333) +('Delhi NCR', 28.613889) +('Tokyo', 35.689722) +('New York-Newark', 40.808611) +---- +==== +<1> Usa `namedtuple` para definir `LatLon`. +<2> Também define `Metropolis`. +<3> Cria a lista `metro_areas` com instâncias de `Metropolis`; observe o desempacotamento da tupla aninhada para extrair `(lat, lon)` e usá-los para criar o `LatLon` do atributo `coord` de `Metropolis`. +<4> Obtém a latitude de dentro de `metro_areas[0]` . +<5> Define um `attrgetter` para obter `name` e o atributo aninhado `coord.lat`. +<6> Usa `attrgetter` novamente para ordenar uma lista de cidades pela latitude. +<7> Usa o +attrgetter+ `name_lat` para exibir apenas o nome e a latitude da cidade. + + +Abaixo está uma lista parcial das funções definidas em `operator` +(filtrei os nomes iniciando com `_` porque são, em sua maioria, detalhes de implementação): + +[source, python] +---- +>>> [name for name in dir(operator) if not name.startswith('_')] +['abs', 'add', 'and_', 'attrgetter', 'concat', 'contains', +'countOf', 'delitem', 'eq', 'floordiv', 'ge', 'getitem', 'gt', +'iadd', 'iand', 'iconcat', 'ifloordiv', 'ilshift', 'imatmul', +'imod', 'imul', 'index', 'indexOf', 'inv', 'invert', 'ior', +'ipow', 'irshift', 'is_', 'is_not', 'isub', 'itemgetter', +'itruediv', 'ixor', 'le', 'length_hint', 'lshift', 'lt', 'matmul', +'methodcaller', 'mod', 'mul', 'ne', 'neg', 'not_', 'or_', 'pos', +'pow', 'rshift', 'setitem', 'sub', 'truediv', 'truth', 'xor'] +---- + +A maior parte dos 54 nomes listados é mais ou menos evidente. +O grupo de nomes com um `i` inicial e o nome de um +operador—por exemplo `iadd`, `iand`, etc—correspondem aos operadores de atribuição aumentada—por exemplo, `+=`, `&=`, etc. +Essas funções mudam seu primeiro argumento internamente, se o argumento for mutável; +se não, funcionam como seus pares sem o prefixo `i`: apenas devolvem o resultado da operação. + +Das funções restantes de `operator`, `methodcaller` será a última que veremos. +Ela é algo similar a `attrgetter` e `itemgetter`, no sentido de criar uma função durante a execução. +A função criada invoca por nome um método do objeto passado como argumento, como mostra o <>. + +[[methodcaller_demo]] +.Demonstração de `methodcaller`: o segundo teste mostra a vinculação de argumentos adicionais +==== +[source, python] +---- +>>> from operator import methodcaller +>>> s = 'The time has come' +>>> upcase = methodcaller('upper') +>>> upcase(s) +'THE TIME HAS COME' +>>> hyphenate = methodcaller('replace', ' ', '-') +>>> hyphenate(s) +'The-time-has-come' +---- +==== + +O primeiro teste no <> está ali apenas para mostrar o funcionamento de `methodcaller`; se você precisa usar `str.upper` como uma função, basta chamá-lo na classe `str`, passando uma string como argumento, assim: + +[source, python] +---- +>>> str.upper(s) +'THE TIME HAS COME' +---- + +O segundo teste do <> mostra que `methodcaller` pode também executar uma aplicação parcial para fixar alguns argumentos, como faz a função `functools.partial`. Esse é nosso próximo tópico.((("", startref="opmod07"))) + +[[functools_partial_sec]] +==== Fixando argumentos com functools.partial + +O((("functools module", "freezing arguments with", id="functools07")))((("arguments", "freezing with functools.partial"))) módulo `functools` oferece várias funções de ordem superior. +Já vimos `reduce` na <>. +Uma outra é `partial`: dado um invocável, ela produz um novo invocável com alguns dos argumentos do invocável original vinculados a valores pré-determinados. +Isso é útil para adaptar uma função que recebe um ou mais argumentos a uma API que requer uma função de _callback_ com menos argumentos. +O <> é uma demonstração trivial. + +[[ex_partial_mul]] +.Empregando `partial` para usar uma função com dois argumentos onde é necessário um invocável com apenas um argumento +==== +[source, python] +---- +>>> from operator import mul +>>> from functools import partial +>>> triple = partial(mul, 3) <1> +>>> triple(7) <2> +21 +>>> list(map(triple, range(1, 10))) <3> +[3, 6, 9, 12, 15, 18, 21, 24, 27] +---- +==== +<1> Cria uma nova função `triple` a partir de `mul`, vinculando o primeiro argumento posicional a `3`. +<2> Testa a função. +<3> Usa `triple` com `map`; `mul` não funcionaria com `map` nesse exemplo. + +Um exemplo mais útil envolve a função `unicode.normalize`, que vimos na <>. Se você trabalha com texto em muitas línguas diferentes, pode querer aplicar `unicode.normalize('NFC', s)` a qualquer string `s`, antes de compará-la ou armazená-la. Se você precisa disso com frequência, é conveniente ter uma função `nfc` para executar essa tarefa, como no <>. + +[[ex_partial_nfc]] +.Criando uma função para normalizar Unicode com `partial` +==== +[source, python] +---- +>>> import unicodedata, functools +>>> nfc = functools.partial(unicodedata.normalize, 'NFC') +>>> s1 = 'café' +>>> s2 = 'cafe\u0301' +>>> s1, s2 +('café', 'café') +>>> s1 == s2 +False +>>> nfc(s1) == nfc(s2) +True +---- +==== + +A função `partial` recebe um invocável como primeiro argumento, seguido de um número arbitrário de argumentos posicionais ou nomeados para vincular. +O <> mostra o uso de `partial` com a função `tag` (do <>), para fixar um argumento posicional e um argumento nomeado. + +[[partial_demo]] +.Demonstração de `partial` aplicada à função `tag`, do <> +==== +[source, python] +---- +>>> from tagger import tag +>>> tag + <1> +>>> from functools import partial +>>> picture = partial(tag, 'img', +class_='pic-frame') <2> +>>> picture(src='wumpus.jpeg') +'' <3> +>>> picture +functools.partial(, 'img', class_='pic-frame') <4> +>>> picture.func <5> + +>>> picture.args, picture.keywords +(('img',), {'class_': 'pic-frame'}) +---- +==== +<1> Importa `tag` do <> e mostra seu `id`. +<2> Cria a função `picture` a partir de `tag`, fixando o primeiro argumento posicional em `'img'` e o argumento nomeado `class_` em `'pic-frame'`. +<3> `picture` funciona como esperado. +<4> `partial()` devolve um objeto `functools.partial`.footnote:[O «código-fonte» [.small]#[fpy.li/7-9]# de _functools.py_ revela que `functools.partial` é implementada em C e é usada por padrão. +Se ela não estiver disponível, uma implementação em Python puro de `partial` está disponível desde o Python 3.4.] +<5> Um objeto `functools.partial` tem atributos que fornecem acesso à função original e aos argumentos fixados. + +A função `functools.partialmethod` faz o mesmo que `partial`, mas serve para trabalhar com métodos. + +O módulo `functools` também inclui funções de ordem superior para serem usadas como decoradores de função, como `cache` e `singledispatch`, entre outras. +Essas funções são tratadas no «Capítulo 9» [.small]#[vol.2, fpy.li/9]#, que também explica como implementar decoradores customizados.((("", startref="FAFfp07")))((("", startref="fprogpack07")))((("", startref="functools07"))) + + +=== Resumo do capítulo + +O((("functions, as first-class objects", "overview of"))) objetivo deste capítulo foi explorar a natureza das funções como objetos de primeira classe no Python. +As principais consequências disso são a possibilidade de atribuir funções a variáveis, passá-las para outras funções, armazená-las em estruturas de dados e acessar os atributos de funções, permitindo que frameworks e ferramentas usem essas informações. + +Funções de ordem superior, parte importante da programação funcional, são comuns no Python. +As funções embutidas `sorted`, `min` e `max`, além de `functools.partial`, são exemplos de funções de ordem superior muito usadas na linguagem. +O uso de `map`, `filter` e `reduce` já não é tão frequente como costumava ser, graças às compreensões de lista (e estruturas similares, como as expressões geradoras) e à adição de funções embutidas de redução como `sum`, `all` e `any`. + +Desde o Python 3.6, existem nove sabores de invocáveis, como funções simples criadas com `lambda` ou instâncias de classes que implementam `+__call__+`. +Geradores e corrotinas também são invocáveis, mas seu comportamento é bem diferente. +Qualquer invocável pode ser detectado pela função embutida `callable()`. +Invocáveis oferecem uma rica sintaxe para declarar parâmetros formais, +incluindo parâmetros nomeados e parâmetros somente posicionais. + +Por fim, vimos algumas funções do módulo `operator` e `functools.partial`, +que facilitam a programação funcional, reduzindo a necessidade de lidar com a sintaxe limitada do `lambda` em Python. + + +[[first_cls_fn_further_reading_sec]] +=== Para saber mais + +Nos((("functions, as first-class objects", "further reading on"))) próximos capítulos, continuaremos nossa jornada pela programação com objetos função. +O <> é dedicado às dicas de tipo nos parâmetros de função e nos valores devolvidos por elas. +O «Capítulo 9» [.small]#[vol.2, fpy.li/9]# mergulha nos decoradores de função—um tipo especial de função de ordem superior—e no mecanismo de clausura (_closure_) que os faz funcionar. +O «Capítulo 10» [.small]#[vol.2, fpy.li/10]# mostra como as funções de primeira classe podem simplificar alguns padrões de projetos clássicos (_design patterns_) orientados a objetos. + +Em _A Referência da Linguagem Python_, a seção «3.2. A hierarquia de tipos padrão» [.small]#[fpy.li/47]# mostra os nove tipos invocáveis, juntamente com todos os outros tipos embutidos. + +O capítulo 7 do +«_Python Cookbook_, 3rd ed.» [.small]#[fpy.li/pycook3]# de David Beazley e Brian K. Jones, +é um excelente complemento a esse capítulo. + +Veja a «_PEP 3102—Keyword-Only Arguments_ (Argumentos somente nomeados)» [.small]#[fpy.li/pep3102]# +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 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?)» +[.small]#[fpy.li/7-12]#, tem uma resposta muito informativa (e engraçada) escrita por Alex Martelli, co-autor do clássico _Python in a Nutshell_. + +Refletindo sobre a pergunta "Seria Python uma linguagem funcional?", criei uma de minhas palestras favoritas, +_Beyond Paradigms_ (Além dos Paradigmas), que apresentei na PyCaribbean, na PyBay e na PyConDE. +Veja os «slides» [.small]#[fpy.li/7-13]# e o +«vídeo» [.small]#[fpy.li/7-14]# da apresentação em Berlim—onde conheci Miroslav Šedivý e Jürgen Gmach, +dois dos revisores técnicos desse livro. + +[[soapbox_1st_class_fn]] +.Ponto de vista +**** + +[role="soapbox-title"] +**Python é uma linguagem funcional?** + +Em((("functions, as first-class objects", +"Soapbox discussion")))((("Soapbox sidebars", "functional programming with Python")))((("Python", +"functional programming with")))((("functional programming", "with Python", +secondary-sortas="Python"))) algum momento do ano 2000, +eu estava participando de uma oficina sobre o framework Zope na Zope Corporation, +nos EUA, quando Guido van Rossum entrou na sala (ele não era o instrutor). +Na seção de perguntas e respostas que se seguiu, alguém perguntou quais recursos de Python ele tinha trazido de outras linguagens. +A resposta de Guido: "Tudo que é bom no Python foi roubado de outras linguagens." + +Shriram Krishnamurthi, professor de Ciência da Computação na Brown University, +inicia seu artigo, +«_Teaching Programming Languages in a Post-Linnaean Age_ (Ensinando Linguagens de Programação em uma Era Pós-Lineu)» [.small]#[fpy.li/7-15]#, assim: + +[quote] +____ +Os "paradigmas" de linguagens de programação são um legado moribundo e tedioso de uma era passada. +Os atuais projetistas de linguagens não têm qualquer respeito por eles, então por que nossos cursos aderem servilmente a tais "paradigmas"? +____ + +Nesse artigo, Python é mencionado nominalmente na seguinte passagem: + +[quote] +____ +E como descrever linguagens como Python, Ruby ou Perl? +Seus criadores não têm paciência com as sutilezas dessas nomenclaturas de Lineu; +eles pegam emprestados todos os recursos que desejam, criando misturas que desafiam totalmente uma caracterização. +____ + +Krishnamurthi argumenta que, ao invés de tentar classificar as linguagens com alguma taxonomia, seria mais útil olhar para elas como agregados de recursos. +Suas ideias inspiraram minha palestra "Beyond Paradigms" ("Para Além dos Paradigmas"), mencionada no final da <>. + +Mesmo se esse não fosse o objetivo de Guido, dotar Python de funções de primeira classe abriu as portas para a programação funcional. +Em seu post, «_Origins of Python's 'Functional' Features_ (As Origens dos Recursos 'Funcionais' do Python)» [.small]#[fpy.li/7-1]#, +ele conta que `map`, `filter`, e `reduce` foram a primeira motivação para a inclusão do `lambda` ao Python. +Todos esses recursos foram adicionados juntos ao Python 1.0 em 1994, por Amrit Prem, +de acordo com o arquivo «`Misc/HISTORY`» [.small]#[fpy.li/7-17]# no código-fonte do CPython. + +Funções como `map`, `filter` e `reduce` surgiram inicialmente em Lisp, a primeira linguagem funcional. +Lisp, entretanto, não limita o que pode ser feito dentro de uma `lambda`, pois tudo em Lisp é uma expressão. +Python usa uma sintaxe orientada a instruções (_statement oriented syntax_), +na qual as expressões não podem conter instruções, +e muitas das estruturas da linguagem são instruções--incluindo `try/catch`, +que é o que mais sinto falta quando escrevo uma `lambda`. +É o preço a pagar pela sintaxe extremamente legível de Python.footnote:[Há também o problema da perda de indentação quando colamos trechos de código em fóruns na Web, mas isso é outro assunto.] Lisp tem muitas virtudes, mas legibilidade não é uma delas. + +Ironicamente, roubar a sintaxe de compreensão de lista de outra linguagem +funcional—Haskell—eliminou muitos casos de uso para `map` e `filter`, e também `lambda`. + +Além da sintaxe limitada das funções anônimas, +o maior obstáculo para uma adoção mais ampla de idiomas de programação funcional no Python é +a ausência da eliminação de chamadas de cauda, uma otimização que permite +o processamento eficiente de uma função que faz uma chamada recursiva na "cauda" de seu corpo, +sem aumentar a pilha de execução a cada recursão. +Em outro post, +«_Tail Recursion Elimination_ (Eliminação de Recursão de Cauda)» [.small]#[fpy.li/7-18]#, +Guido apresenta várias razões pelas quais tal otimização não é adequada ao Python. +O post é uma ótima leitura por seus argumentos técnicos, +e mais ainda porque as três primeiras e mais importantes razões dadas são questões de usabilidade. +Python não é um prazer de usar, aprender e ensinar por acidente. +Guido foi intencional. + +Então cá estamos: Python não é, intencionalmente, uma linguagem funcional—seja lá o que isso signifique. +Python só pega emprestadas algumas boas ideias de linguagens funcionais. +**** + +<<< +**** +[role="soapbox-title"] +**O problema das funções anônimas** + +Além((("Soapbox sidebars", "anonymous functions")))((("anonymous functions")))(((""))) +das restrições sintáticas específicas de Python, +funções anônimas têm uma séria desvantagem em qualquer linguagem: elas não têm nome. + +Estou brincando, mas não muito. +Os _stack traces_ são mais fáceis de ler quando as funções têm nome. +Funções anônimas são um atalho conveniente, nos divertimos programando com elas, mas algumas vezes elas são levadas longe demais—especialmente se a linguagem e o ambiente encorajam o aninhamento profundo de funções anônimas, como faz o JavaScript combinado com o Node.js. +Ter muitas funções anônimas aninhadas torna a depuração e o tratamento de erros mais difíceis. +A programação assíncrona no Python é mais estruturada, talvez pela sintaxe limitada do `lambda` impedir seu abuso e forçar uma abordagem mais explícita. +Promessas, futuros e adiados (_deferreds_) são conceitos usados nas APIs assíncronas modernas. + +Prometo escrever mais sobre programação assíncrona no futuro, +mas esse assunto será adiado até o «Capítulo 21» [.small]#[vol.3, fpy.li/21]#. + +**** diff --git a/vol1/cap08.adoc b/vol1/cap08.adoc new file mode 100644 index 00000000..7b1272b8 --- /dev/null +++ b/vol1/cap08.adoc @@ -0,0 +1,2586 @@ +[[ch_type_hints_def]] +== Dicas de tipo em funções +:example-number: 0 +:figure-number: 0 + +[quote, Guido van Rossum, Jukka Lehtosalo, e Łukasz Langa, PEP 484—Type Hints] +____ +É preciso enfatizar que *Python continuará sendo uma linguagem de tipagem dinâmica, +e os autores não têm qualquer intenção de algum dia tornar dicas de tipo obrigatórias, +mesmo por mera convenção*.footnote:[«_PEP 484—Type Hints_» [.small]#[fpy.li/8-1]#, seção _Rationale and Goals_; negritos mantidos do original.] +____ + +Dicas de tipo((("functions, type hints in", +"benefits and drawbacks of")))((("type hints (type annotations)", "benefits and drawbacks of"))) +foram a maior mudança na história de Python desde +«a unificação de tipos e classes» [.small]#[fpy.li/descr101]# +no Python 2.2, lançado em 2001. +Entretanto, as dicas de tipo não beneficiam igualmente a todas as pessoas que usam Python. +Por isso deverão ser sempre opcionais. + +A +«_PEP 484—Type Hints_ (Dicas de Tipo)» [.small]#[fpy.li/pep484]# +introduziu a sintaxe e a semântica para declarações explícitas de tipo +em argumentos de funções, valores de retorno e variáveis. +O objetivo é ajudar ferramentas de desenvolvimento a encontrarem bugs no código-fonte de programas em Python +através de análise estática, isto é, sem executar o código através de testes. + +Os maiores beneficiários são desenvolvedores profissionais que usam +IDEs (_Ambientes Integrados de Desenvolvimento_) +e CI (_Integração Contínua_). +A análise de custo-benefício que torna as dicas de tipo atrativas para esse grupo não se aplica a todas as pessoas que programam em Python. + +A base de usuários de Python vai muito além dessa classe de profissionais. +Ela inclui cientistas, comerciantes, jornalistas, artistas, inventores, analistas e estudantes de inúmeras áreas, entre outros. +Para a maioria dessas pessoas, o custo de aprender dicas de tipo será maior—exceto para +a minoria que já conhece outra linguagem com tipos estáticos, subtipos e tipos genéricos. +Os benefícios serão menores para muitas pessoas, dada a forma como elas interagem com Python, +o tamanho menor de suas bases de código e de suas equipes—muitas vezes "equipes solo". + +A tipagem dinâmica, default de Python, é mais simples e mais expressiva quando +programamos para explorar dados e ideias, como acontece em ciência de dados, +computação criativa e aprendendo a programar. + +<<< +Este capítulo se concentra nas dicas de tipo de Python nas assinaturas de função. +O «Capítulo 15» [.small]#[vol.2, fpy.li/15]# explora as dicas de tipo no contexto de classes e outros recursos do módulo `typing`. + +Os((("functions, type hints in", "topics covered")))((("type hints (type annotations)", "topics covered"))) +tópicos mais importantes aqui são: + +* Uma introdução prática à tipagem gradual com Mypy +* As perspectivas complementares da tipagem pato (_duck typing_) e da tipagem nominal +* A revisão das principais categorias de tipos que podem surgir em anotações (cerca de 60% do capítulo) +* Dicas de tipo em parâmetros variádicos (`\*args`, `**kwargs`) +* Limitações e desvantagens das dicas de tipo e da tipagem estática. + +=== Novidades neste capítulo + +Este((("functions, type hints in", "significant changes to")))((("type hints (type annotations)", "significant changes to"))) +capítulo é completamente novo. +As dicas de tipo apareceram no Python 3.5, após eu ter terminado de escrever a primeira edição de _Python Fluente_. + +Dadas as limitações de um sistema de tipagem estática, +a melhor ideia da PEP 484 foi propor um _sistema de tipagem gradual_. +Vamos começar definindo o que isso significa. + +=== Sobre tipagem gradual + +A PEP 484((("functions, type hints in", "gr adual typing", +id="FTHgrad08")))((("type hints (type annotations)", "gradual typing", +id="THgrad0 8")))((("gradual type system", "basics of"))) +introduziu no Python um _sistema de tipagem gradual_. +Outras linguagens com sistemas de tipagem gradual são Typescript da Microsoft, +Dart (a linguagem do SDK Flutter, criado pelo Google), +e Hack (um dialeto de PHP compilado para a máquina virtual HHVM do Facebook). +O próprio checador de tipos MyPy começou como uma linguagem: +um dialeto de Python de tipagem gradual com seu próprio interpretador. +Guido van Rossum convenceu o criador do MyPy, Jukka Lehtosalo, +a transformá-lo em uma ferramenta para checar código Python anotado. + +Eis uma função com anotações de tipos: + +[source, python] +---- +def tokenize(s: str) -> list[str]: + "Convert a string into a list of tokens." + return s.replace('(', ' ( ').replace(')', ' ) ').split() +---- + +A assinatura informa: `tokenize` recebe uma `str` +e devolve uma `list[str]`: uma lista de strings. +Usaremos esta função em um _parser_ na «Seção 18.3.3» [.small]#[vol.3, fpy.li/5e]#. + +Um sistema de tipagem gradual: + +É opcional:: +Por default, o checador de tipos não deve emitir avisos para código que não tenha dicas de tipo. +Em vez disso, o checador supõe o tipo `Any` quando não consegue determinar o tipo de um objeto. +O tipo `Any` é considerado compatível com todos os outros tipos. +Não captura erros de tipagem durante a execução do código:: +Dicas de tipo são usadas por checadores de tipos, analisadores de código-fonte (_linters_) e IDEs para emitir avisos. +Eles não evitam que valores inconsistentes sejam passados para funções ou atribuídos a variáveis durante a execução. +Por exemplo, nada impede que alguém chame +`tokenize(42)`, apesar da anotação de tipo do argumento `s: str`. +A chamada ocorrerá, e teremos um erro de execução no corpo da função. +Não melhora o desempenho:: +Anotações de tipo fornecem dados que poderiam, em tese, permitir otimizações do bytecode gerado. +Mas, até julho de 2021, tais otimizações não ocorrem em nenhum ambiente Python que eu +conheça.footnote:[Um compilador JIT ("just-in-time", compiladores que transformam o bytecode +gerado pelo interpretador em código da máquina-alvo no momento da execução) +como o do PyPy tem informações muito melhores que as dicas de tipo: +ele monitora o programa Python durante a execução, detecta os tipos concretos em uso, +e gera código de máquina otimizado para aqueles tipos concretos.] + +O melhor aspecto de usabilidade da tipagem gradual é que as anotações são sempre opcionais. + +Nos sistemas de tipagem estáticos, a maioria das restrições de tipo são fáceis de expressar, +muitas são desajeitadas, muitas são difíceis, e algumas são impossíveis. +Por exemplo, em julho de 2021, +tipos recursivos não tinham suporte—veja as questões +«_182, Define a JSON type_» [.small]#[fpy.li/8-2]# +«_731, Support recursive types_» [.small]#[fpy.li/8-3]# do MyPy. + +É possível que você escreva um ótimo programa em Python, +com uma boa cobertura de testes, todos passando, +mas ainda assim não consiga escrever dicas de tipo que satisfaçam um checador de tipos. +Nesse caso, omita as dicas de tipo problemáticas e entregue o programa! + +Dicas de tipo são opcionais em todos os níveis: +você pode criar ou usar pacotes inteiros sem dicas de tipo, +pode silenciar o checador ao importar algum módulo sem dicas de tipo, +e você também pode colocar comentários especiais, +para que o checador de tipos ignore certas linhas do seu código. + +[TIP] +==== +Tentar impor uma cobertura de 100% de dicas de tipo irá provavelmente estimular seu uso de forma impensada, +apenas para satisfazer essa métrica. +Isso também vai impedir equipes de aproveitarem da melhor forma possível o potencial e a flexibilidade de Python. +Código sem dicas de tipo deve ser aceito sem objeções quando anotações tornam +o uso de uma API menos amigável ou quando complicam demais seu desenvolvimento. +==== + +=== Tipagem gradual na prática + +Vamos ((("gradual type system", "in practice", id="GRSpract08"))) ver como a tipagem gradual funciona na prática, +começando com uma função simples e acrescentando gradativamente a ela dicas de tipo, +guiados pelo((("Mypy type checker", id="mypy08"))) Mypy. + +[NOTE] +==== +Há muitos((("Python type checkers"))) checadores de tipos para Python compatíveis com a PEP 484, +incluindo o «pytype» [.small]#[fpy.li/8-4]# do Google, +o «Pyright» [.small]#[fpy.li/8-5]# da Microsoft, +o «Pyre» [.small]#[fpy.li/8-6]# do Facebook—além de checadores incluídos em IDEs como o PyCharm. +Decidi usar o «Mypy» [.small]#[fpy.li/mypy]# nos exemplos por ser o mais conhecido. +Entretanto, algum daqueles outros pode ser mais adequado para alguns projetos ou equipes. +O Pytype, por exemplo, foi projetado para lidar com bases de código sem nenhuma dica de tipo e ainda assim gerar recomendações úteis. +Ele é mais tolerante que o MyPy, e consegue também gerar anotações para o seu código. +==== + +Vamos anotar uma função `show_count`, que retorna uma string com um número e uma palavra no singular ou no plural, dependendo do número: + +[source, python] +---- +include::../code/08-def-type-hints/messages/no_hints/messages.py[tags=SHOW_COUNT_DOCTEST] +---- + +<> mostra o código-fonte de `show_count`, sem anotações. + +[[msgs_no_hints]] +.`show_count` de _messages.py_ sem dicas de tipo. +==== +[source, python] +---- +include::../code/08-def-type-hints/messages/no_hints/messages.py[tags=SHOW_COUNT] +---- +==== + +==== Usando o Mypy + +Para começar a checagem de tipos, rodamos o comando `mypy` passando o módulo _messages.py_ como parâmetro: + +[source] +---- +…/no_hints/ $ pip install mypy +[muitas mensagens omitidas...] +…/no_hints/ $ mypy messages.py +Success: no issues found in 1 source file +---- + +Na configuração default, o Mypy não reporta problemas com o <>. + +[WARNING] +==== +Durante a revisão deste capítulo estou usando Mypy 0.910, a versão mais recente no momento (em julho de 2021). +A «_Introduction_» [.small]#[fpy.li/8-7]# do Mypy adverte que ele "é oficialmente software beta. +Mudanças ocasionais irão quebrar a compatibilidade com versões mais antigas." +O Mypy está gerando pelo menos um relatório diferente daquele que recebi quando escrevi o capítulo, em abril de 2020. +E quando você estiver lendo essas linhas, talvez os resultados também sejam diferentes daqueles mostrados aqui. +==== + +Se a assinatura de uma função não tem anotações, Mypy a ignora por default—a menos que seja configurado de outra forma. + +O <> também inclui testes unitários do `pytest`. +Este é o código de _messages_test.py_: + +[[msgs_test_no_hints]] +._messages_test.py_ sem dicas de tipo. +==== +[source, python] +---- +include::../code/08-def-type-hints/messages/no_hints/messages_test.py[] +---- +==== + +Agora vamos acrescentar dicas de tipo, guiados pelo Mypy. + + +==== Tornando o Mypy mais rigoroso + +A opção de linha de comando `--disallow-untyped-defs` faz o Mypy apontar todas as +definições de funções que não tenham dicas de tipo para todos os argumentos e para o valor de retorno. + +Usando `--disallow-untyped-defs` com o arquivo de teste produz três erros e uma observação: + +[source] +---- +…/no_hints/ $ mypy --disallow-untyped-defs messages_test.py +messages.py:14: error: Function is missing a type annotation +messages_test.py:10: error: Function is missing a type annotation +messages_test.py:15: error: Function is missing a return type annotation +messages_test.py:15: note: Use "-> None" if function does not return a value +Found 3 errors in 2 files (checked 1 source file) +---- + +Nas primeiras etapas da tipagem gradual, prefiro usar outra opção: + +`--disallow-incomplete-defs` + +Com ela, o Mypy não me dá nenhuma nova informação num primeiro momento: + +[source] +---- +…/no_hints/ $ mypy --disallow-incomplete-defs messages_test.py +Success: no issues found in 1 source file +---- + +Agora acrescento apenas o tipo do retorno a `show_count` em _messages.py_: + +[source, python] +---- +def show_count(count, word) -> str: +---- + +Isso é suficiente para fazer o Mypy olhar para o código. +Usando a mesma linha de comando anterior para checar _messages_test.py_ +fará o Mypy examinar novamente o _messages.py_: + +[source] +---- +…/no_hints/ $ mypy --disallow-incomplete-defs messages_test.py +messages.py:14: error: Function is missing a type annotation +for one or more arguments +Found 1 error in 1 file (checked 1 source file) +---- + +Agora posso gradualmente acrescentar dicas de tipo, função por função, +sem receber avisos sobre as funções onde ainda não adicionei anotações. +Esta é uma assinatura completamente anotada que satisfaz o Mypy: + +[source, python] +---- +def show_count(count: int, word: str) -> str: +---- + +[TIP] +==== +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. +Aqui está um _mypy.ini_ simples, para servir de base: + +---- +[mypy] +python_version = 3.9 +warn_unused_configs = True +disallow_incomplete_defs = True +---- +==== + + +==== Um valor default para um argumento + +A função `show_count` no <> só funciona com substantivos regulares. +Se o plural não pode ser composto acrescentando um `'s'`, devemos deixar o usuário fornecer a forma plural, assim: + +[source, python] +---- +>>> show_count(3, 'mouse', 'mice') +'3 mice' +---- + +Vamos experimentar um pouco de "desenvolvimento orientado a tipos". +Primeiro acrescento um teste usando aquele terceiro argumento. +Não esqueça de adicionar a dica do tipo de retorno à função de teste, +senão o Mypy não vai inspecioná-la. + +[source, python] +---- +def test_irregular() -> None: + got = show_count(2, 'child', 'children') + assert got == '2 children' +---- + +O Mypy detecta o erro: +[source, python] +---- +…/hints_2/ $ mypy messages_test.py +messages_test.py:22: error: Too many arguments for "show_count" +Found 1 error in 1 file (checked 1 source file) +---- + +Então edito `show_count`, acrescentando o argumento opcional `plural`: + +[[msgs_optional_str_param]] +.`showcount` de _hints_2/messages.py_ com um argumento opcional +==== +[source, python] +---- +include::../code/08-def-type-hints/messages/hints_2/messages.py[tags=SHOW_COUNT] +---- +==== + +E agora o Mypy reporta "Success." + +[WARNING] +==== +Aqui está um erro de digitação que Python não reconhece. +Você consegue encontrá-lo? + +[source, python] +---- +def hex2rgb(color=str) -> tuple[int, int, int]: +---- + +O relatório de erros do Mypy não é muito útil: + +[source] +---- +colors.py:24: error: Function is missing a type + annotation for one or more arguments +---- + +A dica de tipo para o argumento `color` deveria ser `color: str`. +Escrevi `color=str`, que não é uma anotação: ele determina que o valor default de `color` é `str`. +Na minha experiência, esse é um erro comum e fácil de passar despercebido, +especialmente em dicas de tipo complexas. +==== + +Os seguintes detalhes são considerados um bom estilo para dicas de tipo: + +* Sem espaço entre o nome do parâmetro e o `:`; um espaço após o `:` +* Espaços dos dois lados do `=` que precede um valor default de parâmetro. + +Por outro lado, a PEP 8 diz que não deve haver espaço em torno de `=` +se não há nenhuma dica de tipo para aquele parâmetro específico. + +.Estilo de Código: use flake8 e blue + +**** +Em vez de((("flake8 tool")))((("blue tool"))) decorar essas regrinhas bobas, use ferramentas como +«_flake8_» [.small]#[fpy.li/8-9]# e «_blue_» [.small]#[fpy.li/8-10]#. O +_flake8_ informa sobre o estilo do código e várias outras questões, +enquanto o _blue_ reescreve o código-fonte com base na (maioria) das regras prescritas pela ferramenta de formatação de código +«_black_» [.small]#[fpy.li/8-11]#. + +Se o objetivo é impor um estilo de programação "padrão", _blue_ é melhor que _black_, +porque segue o estilo próprio de Python, de usar aspas simples por default e aspas duplas como alternativa. + +[source, python] +---- +>>> "I prefer single quotes" +'I prefer single quotes' +---- + +No CPython, a preferência por aspas simples está incorporada no `repr()`, entre outros lugares. +O módulo «_doctest_» [.small]#[fpy.li/doctest]# depende do `repr()` usar aspas simples por default. + +Um dos autores do _blue_ é «Barry Warsaw» [.small]#[fpy.li/8-12]#, co-autor da PEP 8, +core developer de Python desde 1994 e membro do Python Steering Council (Conselho Diretivo) desde 2019. +Estamos em ótima companhia quando escolhemos usar aspas simples. + +Se você for obrigado a usar o _black_, use a opção `black -S`. Isso deixará suas aspas intocadas. +**** + + +[[dealing_with_none_sec]] +==== Usando None como default +No <>, o parâmetro `plural` está anotado como `str`, +e o valor default é `''`. Assim não há conflito de tipo. + +Eu gosto dessa solução, mas em outros contextos `None` é um default melhor. +Se o parâmetro opcional requer um tipo mutável, então `None` é o único default sensato, +como vimos na <>. + +Com `None` como default para o parâmetro `plural`, a assinatura ficaria assim: + +[source, python] +---- +from typing import Optional + +def show_count(count: int, singular: str, plural: Optional[str] = None) -> str: +---- + +Vamos destrinchar essa linha: + +* `Optional[str]` significa que `plural` pode ser uma `str` ou `None`. +* É obrigatório fornecer explicitamente o valor default `= None`. + +Se você não atribuir um valor default a `plural`, o runtime de Python vai tratar o parâmetro como obrigatório. +Lembre-se: durante a execução do programa, as dicas de tipo são ignoradas. + +Veja que é preciso importar `Optional` do módulo `typing`. Quando importamos tipos, +é uma boa prática usar a sintaxe `from typing import X`, para reduzir o tamanho das assinaturas das funções. + +[WARNING] +==== +`Optional` não é um bom nome, pois aquela anotação não torna o argumento opcional. +O que o torna opcional é a atribuição de um valor default ao parâmetro. +`Optional[str]` significa apenas: o tipo desse parâmetro pode ser `str` ou `NoneType`. +Nas linguagens Haskell e Elm, um tipo parecido se chama `Maybe`. +==== + +Agora que tivemos um primeiro contato concreto com a tipagem gradual, +vamos examinar o que o conceito de _tipo_ significa na +prática.((("", startref="FTHgrad08")))((("", startref="THgrad08")))((("", startref="GRSpract08")))((("", startref="mypy08"))) + +[[types_defined_by_ops_sec]] +=== Tipos são definidos pelas operações possíveis + + +[quote, PEP 483—A Teoria das Dicas de Tipo] +____ + +Há muitas definições do conceito de tipo na literatura. +Aqui vamos assumir que tipo é um conjunto de valores e +um conjunto de funções que podem ser aplicadas àqueles valores. +____ + +Na((("functions, type hints in", "supported operations and", id="FTHsupport08")))((("type hints (type annotations)", +"supported operations and", id="THsup08"))) prática, +é mais útil considerar o conjunto de operações possíveis como a característica que define um tipo.footnote:[Em +Python não há sintaxe para controlar o conjunto de possíveis valores de um tipo, +exceto para tipos `Enum`. +Por exemplo, não é possível, usando dicas de tipo, +definir `Quantity` como um número inteiro entre 1 e 10000, +ou `AirportCode` como uma sequência de 3 letras. +O NumPy oferece `uint8`, `int16`, e outros tipos numéricos ligados à arquitetura do hardware, +mas na biblioteca padrão de Python encontramos apenas +tipos com pequenos conjuntos de valores (`NoneType`, `bool`) +ou conjuntos muito grandes (`float`, `int`, `str`, todas as tuplas possíveis, etc.).] + +Por exemplo, pensando nas operações possíveis, +quais são os tipos válidos para `x` na função a seguir? + +[source, python] +---- +def double(x): + return x * 2 +---- + +O tipo do parâmetro `x` pode ser numérico (`int`, `complex`, `Fraction`, `numpy.uint32`, etc.), +mas também pode ser uma sequência (`str`, `tuple`, `list`, `array`), uma `numpy.array` N-dimensional, +ou qualquer outro tipo que implemente ou herde um método `+__mul__+` que aceite um inteiro como argumento. + +Entretanto, considere a anotação `double` abaixo. +Ignore por enquanto a ausência do tipo do retorno, vamos nos concentrar no tipo do parâmetro: + +[source, python] +---- +from collections import abc + +def double(x: abc.Sequence): + return x * 2 +---- + +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 +como `str`, `tuple`, `list`, `array`, etc., bem como com números, +pois durante a execução as dicas de tipo são ignoradas. +Mas o checador de tipos se preocupa apenas com o que +estiver explicitamente declarado, e `abc.Sequence` não suporta `+__mul__+`. + +Por isso o título desta seção é "Tipos são definidos pelas operações possíveis." +O runtime de Python aceita qualquer objeto como argumento `x` nas duas versões da função `double`. +O cálculo de `x * 2` pode funcionar, ou pode causar um `TypeError`, +se a operação não for suportada por `x`. +Por outro lado, Mypy vai marcar `x * 2` como um erro quando analisar o código-fonte +anotado de `double`, pois é uma operação não suportada pelo tipo declarado `x: abc.Sequence`. + +<<< +Em um sistema de tipagem gradual, acontece uma interação entre duas perspectivas diferentes de tipo: + +Tipagem pato (_duck typing_):: +A ((("duck typing"))) perspectiva adotada por Smalltalk—a primeira linguagem orientada a objetos—bem +como em Python, JavaScript, e Ruby. +Objetos têm tipo, mas variáveis (incluindo parâmetros) não têm. +Na prática, não importa qual o tipo declarado de um objeto, importam apenas as operações que ele realmente suporta. +Se eu considero que um pato faz _quack_, e posso invocar `ave.quack()` então, nesse contexto, `ave` é um pato. +Por definição, a tipagem pato só é verificada durante a execução, +quando se tenta aplicar operações sobre os objetos. +Isso é mais flexível que a _tipagem nominal_, +ao preço de permitir mais erros durante a execução. +Tipagem pato é uma forma implícita (não declarada) de _tipagem estrutural_, +que a classe especial `typing.Protocol` permite explicitar em anotações de tipo a partir do Python 3.8. +Vamos falar sobre `typing.Protocol` neste capítulo, na <>, +e com mais detalhes no «Capítulo 13» [.small]#[vol.2, fpy.li/13].]#] + +Tipagem nominal:: +É a ((("nominal typing"))) perspectiva adotada em linguagens estáticas como +{cpp}, Java, C#, e suportada em Python com anotações de tipo. +Objetos e variáveis têm tipos. +Objetos só existem durante a execução, mas o checador de tipos só se importa com o código-fonte, +onde as variáveis (incluindo parâmetros de função) têm dicas de tipo. +Se `Duck` é uma subclasse de `Bird`, +você pode atribuir uma instância de `Duck` a um parâmetro anotado como `birdie: Bird`. +Mas no corpo da função, o checador considera a chamada `birdie.quack()` ilegal, +pois `birdie` é nominalmente um `Bird`, e aquela classe não fornece o método `.quack()`. +Não interessa que o argumento real, durante a execução, é um `Duck`, +porque a tipagem nominal é checada estaticamente. +O checador de tipos não executa qualquer trecho do programa, ele apenas analisa o código-fonte. +Isso é mais rígido que a tipagem pato, +com a vantagem de capturar alguns bugs durante o desenvolvimento, +ou mesmo quando estamos digitando código em um IDE. + +O <> é um exemplo bobo que contrapõe tipagem pato e tipagem nominal, +bem como checagem de tipos estática e comportamento durante a execução.footnote:[Muitas +vezes a herança é sobreutilizada e difícil de justificar em exemplos pequenos. +Então por favor aceite esse exemplo com animais como uma rápida ilustração de sub-tipagem.] + +<<< +[[birds_module_ex]] +._birds.py_ +==== +[source, python] +---- +include::../code/08-def-type-hints/birds/birds.py[] +---- +==== +<1> `Duck` é uma subclasse de `Bird`. +<2> `alert` não tem dicas de tipo, então o checador a ignora. +<3> `alert_duck` aceita um argumento do tipo `Duck`. +<4> `alert_bird` aceita um argumento do tipo `Bird`. + +Verificando _birds.py_ com Mypy, encontramos um problema: + +[source] +---- +…/birds/ $ mypy birds.py +birds.py:16: error: "Bird" has no attribute "quack" +Found 1 error in 1 file (checked 1 source file) +---- + +Só de analisar o código-fonte, Mypy percebe que `alert_bird` é problemático: +a dica de tipo declara o parâmetro `birdie` como do tipo `Bird`, +mas o corpo da função invoca `birdie.quack()`, e a classe `Bird` não tem esse método. + +Agora vamos tentar usar o módulo `birds` em _daffy.py_ no <>. + +<<< +[[daffy_module_ex]] +._daffy.py_ +==== +[source, python] +---- +include::../code/08-def-type-hints/birds/daffy.py[] +---- +==== +<1> Chamada válida, pois `alert` não tem dicas de tipo. +<2> Chamada válida, pois `alert_duck` recebe um argumento do tipo `Duck` e `daffy` é um `Duck`. +<3> Chamada válida, pois `alert_bird` recebe um argumento do tipo `Bird`, +e `daffy` também é um `Bird`, a superclasse de `Duck`. + +Mypy reporta o mesmo erro em _daffy.py_, sobre a chamada a `quack` na função `alert_bird` definida em _birds.py_: + +[source] +---- +…/birds/ $ mypy daffy.py +birds.py:16: error: "Bird" has no attribute "quack" +Found 1 error in 1 file (checked 1 source file) +---- + +Mas o Python não vê qualquer problema com _daffy.py_ em si: as três chamadas de função estão OK. + +Agora, rodando _daffy.py_, o resultado é o seguinte: + +[source] +---- +…/birds/ $ python3 daffy.py +Quack! +Quack! +Quack! +---- + +Funciona perfeitamente! Viva a tipagem pato! + +Durante a execução do programa, Python não se importa com os tipos declarados. +Ele usa apenas tipagem pato. +O Mypy apontou um erro em `alert_bird`, mas a chamada da função com `daffy` funciona corretamente quando executada. +À primeira vista isso pode surpreender muitos pythonistas: +um checador de tipos estático muitas vezes encontra erros em código que sabemos que vai funcionar quando executado. + +Entretanto, se daqui a alguns meses você for encarregado de estender o exemplo bobo do pássaro, +você agradecerá ao Mypy. +Observe este módulo _woody.py_, que também usa `birds`, no <>. + +[[woody_module_ex]] +._woody.py_ +==== +[source, python] +---- +include::../code/08-def-type-hints/birds/woody.py[] +---- +==== + +O Mypy encontra dois erros ao checar _woody.py_: + +[source] +---- +…/birds/ $ mypy woody.py +birds.py:16: error: "Bird" has no attribute "quack" +woody.py:5: error: Argument 1 to "alert_duck" has incompatible type +"Bird"; expected "Duck" +Found 2 errors in 2 files (checked 1 source file) +---- + +O primeiro erro é em _birds.py_: a chamada a `birdie.quack()` em `alert_bird`, que já vimos antes. +O segundo erro é em _woody.py_: `woody` é uma instância de `Bird`, +então a chamada `alert_duck(woody)` é inválida, pois aquela função exige um `Duck.` +Todo `Duck` é um `Bird`, mas nem todo `Bird` é um `Duck`. + +Durante a execução, nenhuma das duas chamadas em _woody.py_ funcionaria. +A sucessão de falhas é melhor ilustrada em uma sessão no console, +através das mensagens de erro, no <>. + +<<< +[[birdie_errors_ex]] +.Erros durante a execução e como o Mypy poderia ter ajudado +==== +[source, python] +---- +>>> from birds import * +>>> woody = Bird() +>>> alert(woody) # <1> +Traceback (most recent call last): + ... +AttributeError: 'Bird' object has no attribute 'quack' +>>> +>>> alert_duck(woody) # <2> +Traceback (most recent call last): + ... +AttributeError: 'Bird' object has no attribute 'quack' +>>> +>>> alert_bird(woody) # <3> +Traceback (most recent call last): + ... +AttributeError: 'Bird' object has no attribute 'quack' +---- +==== +<1> O Mypy não tinha como detectar esse erro, pois não há dicas de tipo em `alert`. +<2> O Mypy avisou do problema: `Argument 1 to "alert_duck" has incompatible type "Bird"; +expected "Duck"` (_Argumento 1 para `alert_duck` é do tipo incompatível "Bird"; argumento esperado era "Duck"_) +<3> O Mypy está avisando desde o <> que o corpo da função `alert_bird` está errado: +`"Bird" has no attribute "quack"` (_Bird não tem um atributo "quack"_) + +Este pequeno experimento mostra que a tipagem pato é mais fácil para o iniciante e mais flexível, +porém permite que operações não suportadas causem erros durante a execução. +A tipagem nominal detecta os erros antes da execução, +mas algumas vezes rejeita código que seria executado sem erros—como +a chamada a `alert_bird(daffy)` no <>. + +Mesmo que funcione às vezes, o nome da função `alert_bird` está ruim: +seu código exige um objeto que suporte o método `.quack()`, que não existe em `Bird`. + +Nesse exemplo bobo, as funções têm uma linha apenas. +Mas na vida real elas poderiam ser mais longas, +e poderiam passar o argumento `birdie` para outras funções, +e a origem daquele argumento poderia estar a muitas chamadas de função de distância, +dificultando localizar a causa do erro durante a execução. +O checador de tipos impede que muitos erros como esse aconteçam durante a execução de um programa. + + +[NOTE] +==== +O valor das dicas de tipo é questionável nos pequenos exemplos que cabem em um livro. +Os benefícios crescem com o tamanho da base de código. +É por essa razão que empresas com milhões de linhas de código em +Python—como Dropbox, Google e Facebook—investiram em equipes e ferramentas para +promover a adoção global de dicas de tipo internamente, +e hoje têm partes significativas e crescentes de sua base de código com +anotações de tipo checadas em suas _pipelines_ de integração contínua. +==== + +Nessa seção exploramos as relações de tipos e operações na tipagem pato e na tipagem nominal, +começando com a função simples `double()`, que deixamos sem dicas de tipo. +Agora vamos dar uma olhada nos tipos mais importantes ao anotar funções. + +Veremos um bom modo de adicionar dicas de tipo a `double()` na <>. +Mas antes disso, há tipos mais importantes para conhecer.((("", startref="FTHsupport08")))((("", startref="THsup08"))) + + +=== Tipos próprios para anotações + +Quase((("functions, type hints in", "types usable in annotations", +id="FTHusable08")))((("type hints (type annotations)", "types usable in", id="THTusable08"))) +todos os tipos em Python podem ser usados em dicas de tipo, mas há restrições e recomendações. +Além disso, o((("typing module"))) módulo `typing` introduziu anotações especiais com uma semântica às vezes surpreendente. + +Essa seção trata dos principais tipos que você pode usar em anotações: + +* `typing.Any` +* Tipos e classes simples +* `typing.Optional` e `typing.Union` +* Coleções genéricas, incluindo tuplas e mapeamentos +* Classes base abstratas +* Iteradores genéricos +* Genéricos parametrizados e `TypeVar` +* `typing.Protocols`, crucial para tipagem pato estática +* `typing.Callable` +* `typing.NoReturn`, um bom modo de encerrar essa lista. + +Vamos falar de um de cada vez, começando por um tipo que é estranho, +aparentemente inútil, mas de uma importância fundamental. + +==== O tipo Any + +A((("Any type", id="anytype08")))((("dynamic type", id="dynamic08")))((("gradual type system", "Any type", +id="GTSany08"))) pedra fundamental de qualquer sistema de tipagem gradual é o tipo `Any`, +também conhecido como o _tipo dinâmico_. +Quando um checador de tipos vê uma função sem tipo como esta: + +[source, python] +---- +def double(x): + return x * 2 +---- + +ele supõe isto: +[source, python] +---- +def double(x: Any) -> Any: + return x * 2 +---- + +Isso significa que o argumento `x` e o valor de retorno podem ser de qualquer tipo, +inclusive de tipos diferentes. +Assume-se que `Any` pode suportar qualquer operação possível. + +Compare `Any` com `object`. Considere essa assinatura: +[source, python] +---- +def double(x: object) -> object: +---- + +Essa função também aceita argumentos de todos os tipos, porque todos os tipos são _subtipo-de_ `object`. + +Entretanto, um checador de tipos vai rejeitar essa função: +[source, python] +---- +def double(x: object) -> object: + return x * 2 +---- +O problema é que `object` não suporta a operação `+__mul__+`. Veja o que diz o Mypy: + +[source] +---- +…/birds/ $ mypy double_object.py +double_object.py:2: error: Unsupported operand types for * ("object" and "int") +Found 1 error in 1 file (checked 1 source file) +---- + +Tipos mais gerais têm interfaces mais restritas, isto é, eles suportam menos operações. +A classe `object` implementa menos operações que `abc.Sequence`, +que implementa menos operações que `abc.MutableSequence`, +que por sua vez implementa menos operações que `list`. + +Mas `Any` é um tipo mágico que reside tanto no topo quanto na base da hierarquia de tipos. +Ele é simultaneamente o tipo mais geral—então um argumento `n: Any` aceita valores de +qualquer tipo—e o tipo mais especializado, suportando assim todas as operações possíveis. +Pelo menos é assim que o checador de tipos entende `Any`. + +Claro, nenhum tipo consegue suportar qualquer operação possível, +então usar `Any` impede o checador de tipos de cumprir sua missão: +detectar operações que podem falhar antes que +seu programa levante uma exceção durante a execução.((("", startref="GTSany08"))) + +[[consistent_with_sec]] +===== Subtipo-de ou consistente-com + +Sistemas((("gradual type system", "subtype-of versus consistent-with relationships", +id="GTSsub08")))((("subtype-of relationships"))) +tradicionais de tipagem nominal orientados a objetos se baseiam na relação _subtipo-de_. +Dada uma classe `T1` e uma subclasse `T2`, então `T2` é _subtipo-de_ `T1`. + +Observe este código: + +[source, python] +---- +class T1: + ... + +class T2(T1): + ... + +def f1(p: T1) -> None: + ... + +o2 = T2() + +f1(o2) # OK +---- + +A chamada `f1(o2)` é uma aplicação do Princípio de Substituição de Liskov +(_Liskov Substitution Principle_, LSP). + +Barbara Liskovfootnote:[Professora do MIT, designer de linguagens de programação e homenageada com o Turing Award em 2008. +Wikipedia: «Barbara Liskov» [.small]#[fpy.li/49]#.] +definiu _é subtipo-de_ em termos das operações suportadas. +Se um objeto do tipo `T2` substitui um objeto do tipo `T1` e +o programa continua se comportando de forma correta, então `T2` é _subtipo-de_ `T1`. + +Seguindo com o código visto acima, essa parte mostra uma violação do LSP: + + +[source, python] +---- +def f2(p: T2) -> None: + ... + +o1 = T1() + +f2(o1) # type error +---- + +Do ponto de vista das operações suportadas, faz todo sentido: +como uma subclasse, `T2` herda e precisa suportar todas as operações suportadas por `T1`. +Então uma instância de `T2` pode ser usada em qualquer lugar onde se espera uma instância de `T1`. +Mas o contrário não é necessariamente verdadeiro: +`T2` pode implementar métodos adicionais, então uma instância de `T1` +não pode ser usada onde se espera uma instância de `T2`. +Este((("behavioral subtyping"))) foco nas operações suportadas se reflete no nome +«_behavioral subtyping_ (subtipagem comportamental)» [.small]#[fpy.li/8-15]#, +também usado para se referir ao LSP. + +Em((("consistent-with relationships"))) um sistema de tipagem gradual há outra relação, +_consistente-com_ (_consistent-with_), +que se aplica sempre que _subtipo-de_ puder ser aplicado, com regras especiais para o tipo `Any`. + +<<< +As regras para _consistente-com_ são: + +. Dados `T1` e um subtipo `T2`, então `T2` é _consistente-com_ `T1` (substituição de Liskov). +. Todo tipo é _consistente-com_ `Any`: você pode passar objetos de qualquer tipo em um argumento declarado como `Any`. +. `Any` é _consistente-com_ todos os tipos: você sempre pode passar um objeto de tipo `Any` onde um argumento de outro tipo for esperado. + +Considerando as definições anteriores dos objetos `o1` e `o2`, +aqui estão alguns exemplos de código válido, ilustrando as regras #2 e #3: + +[source, python] +---- +def f3(p: Any) -> None: + ... + +o0 = object() +o1 = T1() +o2 = T2() + +f3(o0) # +f3(o1) # tudo certo: regra #2 +f3(o2) # + +def f4(): # tipo implícito de retorno: `Any` + ... + +o4 = f4() # tipo inferido: `Any` + +f1(o4) # +f2(o4) # tudo certo: regra #3 +f3(o4) # +---- + +Todo sistema de tipagem gradual precisa de um tipo coringa como `Any` + +[TIP] +==== +O verbo "inferir" é um sinônimo bonito para "adivinhar", quando usado no contexto da análise de tipos. +Checadores de tipo modernos, em Python e outras linguagens, +não precisam de anotações de tipo em todo lugar porque conseguem inferir o tipo de muitas expressões. +Por exemplo, se eu escrever `x = len(s) * 10`, +o checador não precisa de uma declaração local explícita para saber que `x` é um `int`, +desde que a ferramenta consiga acessar dicas de tipo para `len` em algum lugar. +==== + +Agora podemos explorar o restante dos tipos usados em anotações. +((("", startref="GTSsub08")))((("", startref="dynamic08")))((("", startref="anytype08"))) + +==== Tipos simples e classes + +Tipos simples((("gradual type system", "simple types and classes"))) +como `int`, `float`, `str`, e `bytes` podem ser usados diretamente em dicas de tipo. +Classes concretas da biblioteca padrão, +de pacotes externos ou definidas pelo usuário (ex. `FrenchDeck`, `Vector2d`, e `Duck`) +também podem ser usadas em dicas de tipo. + +Classes base abstratas também são úteis aqui. +Voltaremos a elas quando formos estudar os tipos coleção, na <>. + +Para classes, _consistente-com_ é definido como _subtipo-de_: +uma subclasse é _consistente-com_ todas as suas superclasses. + +Entretanto, "a praticidade se sobrepõe à pureza", então há uma exceção importante, discutida em seguida. + +[[int_complex_tip]] +.int é consistente-com complex +[TIP] +==== +Não há nenhuma relação nominal de subtipo entre os tipos nativos `int`, `float` e `complex`: eles são subclasses diretas de `object`. +Mas a PEP 484 «decretou» [.small]#[fpy.li/cardxvi]# +que `int` é _consistente-com_ `float`, e `float` é _consistente-com_ `complex`. +Na prática, faz sentido: +`int` implementa todas as operações que `float` implementa, +e `int` implementa operações adicionais para os operadores binários `&`, `|`, `<<`, etc. +O resultado final é o seguinte: `int` é _consistente-com_ `complex`. +Para `i = 3`, `i.real` é `3` e `i.imag` é `0`. +==== + + +==== Os tipos Optional e Union + +Vimos((("gradual type system", "Optional and Union types")))((("Union type")))((("Optional type"))) +o tipo especial `Optional` na <>. +Ele resolve o problema de ter `None` como default, como no exemplo daquela seção: + +[source, python] +---- +from typing import Optional + +def show_count(count: int, singular: str, plural: Optional[str] = None) -> str: +---- + +A sintaxe `Optional[str]` é na verdade um atalho para `Union[str, None]`, +que significa que o tipo de `plural` pode ser `str` ou `None`. + +.Uma sintaxe melhor para Optional e Union em Python 3.10 + +[TIP] +==== +Desde o Python 3.10 é possível escrever `str | bytes` em vez de `Union[str, bytes]`. +É menos digitação, e não precisamos importar `Optional` ou `Union` de `typing`. +Compare a sintaxe antiga com a nova para a dica de tipo do parâmetro `plural` em `show_count`: + +[source, python] +---- +plural: Optional[str] = None # Python < 3.10 +plural: str | None = None # Python >= 3.10 +---- + +O operador `|` também funciona com `isinstance` e `issubclass` para declarar o segundo argumento: `isinstance(x, int | str)`. +Para saber mais, veja «_PEP 604—Complementary syntax for Union[]_» [.small]#[fpy.li/pep604]#. +==== + +A assinatura da função nativa `ord` é um exemplo simples de `Union`: ela aceita `str` ou `bytes`, +e devolve um `int`.footnote:[Para ser mais preciso, `ord` só aceita `str` ou `bytes` com `len(s) == 1`. +Mas no momento o sistema de tipagem não consegue expressar essa restrição.] + +[source, python] +---- +def ord(c: Union[str, bytes]) -> int: ... +---- + +Veja a seguir uma função que aceita uma `str`, mas pode devolver `str` ou `float`: + +<<< +[source, python] +---- +from typing import Union + +def parse_token(token: str) -> Union[str, float]: + try: + return float(token) + except ValueError: + return token +---- + +Se possível, evite criar funções com `Union` no tipo de +retorno, pois isso exige um esforço extra do usuário: +para saber o que fazer com o valor recebido da função, pode ser +necessário checar o tipo do resultado durante a execução. +Mas a `parse_token` no código acima é um caso de uso justificado +no contexto de interpretador de expressões simples. + +[TIP] +==== +Na <>, vimos funções que aceitam tanto `str` quanto `bytes` como argumento, +mas retornam uma `str` se o argumento for `str` ou `bytes`, se o argumento for `bytes`. +Nesses casos, o tipo de retorno é determinado pelo tipo da entrada, +então `Union` não é uma solução precisa. +Para anotar tais funções corretamente, +precisamos usar uma variável de tipo—apresentada em +<>—ou sobrecarga (_overloading_), +que veremos na «Seção 15.2» [.small]#[vol.2, fpy.li/5f]#. +==== + +`Union[]` exige pelo menos dois tipos. +Tipos `Union` aninhados têm o mesmo efeito que uma `Union` "achatada". +Então esta dica de tipo: + +[source, python] +---- +Union[A, B, Union[C, D, E]] +---- + +Tem o mesmo efeito desta: + +[source, python] +---- +Union[A, B, C, D, E] +---- + +`Union` é mais útil com tipos que não sejam consistentes entre si. +Por exemplo: `Union[int, float]` é redundante, pois `int` é _consistente-com_ `float`. +Se você usar apenas `float` para anotar um parâmetro, ele vai também aceitar valores `int`. + + +[[simple_collections_type_sec]] +==== Coleções genéricas + +A maioria((("gradual type system", "generic collections", id="GTSgeneric08")))((("generic collections", "type annotations and", id="generic08"))) das coleções em Python são heterogêneas. Por exemplo, você pode inserir qualquer combinação de tipos diferentes em uma `list`. +Entretanto, na prática isso não é muito útil: se você colocar objetos em uma coleção, +você certamente vai querer executar alguma operação com eles mais tarde, +e normalmente isso significa que eles precisam compartilhar pelo menos um método em +comum.footnote:[Em ABC—a linguagem que mais influenciou o design inicial de Python—cada +lista só aceita valores de um tipo: o tipo do primeiro item que você colocasse ali.] + +Tipos genéricos podem ser declarados com parâmetros de tipo, para especificar o tipo de item com o qual eles conseguem trabalhar. +Por exemplo, uma `list` pode ser parametrizada para restringir o tipo de elemento ali contido, como se pode ver no <>. + +[[tokenize_ex]] +.`tokenize` com dicas de tipo para Python ≥ 3.9 +==== +[source, python] +---- +def tokenize(text: str) -> list[str]: + return text.upper().split() +---- +==== + +Em Python ≥ 3.9, isso significa que `tokenize` retorna uma `list` onde todos os elementos são do tipo `str`. + +As anotações `stuff: list` e `stuff: list[Any]` significam a mesma coisa: `stuff` é uma lista de objetos de qualquer tipo. + +[TIP] +==== +Se estiver usando Python 3.8 ou anterior, o conceito é o mesmo, +mas você precisa de mais código para funcionar, como explicado em +<>. +==== + +A «PEP 585—Type Hinting Generics In Standard Collections» [.small]#[fpy.li/8-16]# +lista as coleções da biblioteca padrão que aceitam dicas de tipo genéricas. +A lista a seguir mostra apenas as coleções que usam a forma mais simples de dica de tipo genérica, `container[item]`: + +[source] +---- +list collections.deque abc.Sequence abc.MutableSequence +set abc.Container abc.Set abc.MutableSet +frozenset abc.Collection +---- + +Os tipos `tuple` e mapping aceitam dicas de tipo mais complexas, como veremos em suas respectivas seções. + +No Python 3.10, não há uma boa maneira de anotar `array.array`, levando em consideração o argumento `typecode` do construtor, que determina se o array contém inteiros ou floats. +Um problema ainda mais complicado é checar a faixa dos inteiros, para prevenir `OverflowError` durante a execução, ao se adicionar novos elementos. +Por exemplo, um `array` com `typecode=B` só pode receber valores `int` de 0 a 255. +Até Python 3.11, o sistema de tipagem estática de Python não consegue lidar com esse desafio.((("", startref="GTSgeneric08")))((("", startref="generic08"))) + +[[legacy_deprecated_typing_box]] +.Suporte a tipos de coleção descontinuados +**** + +(Você pode pular esse box se usa apenas Python 3.9 ou posterior.) + +Em((("deprecated collection types")))((("gradual type system", "legacy support and deprecated collection types"))) Python 3.7 e 3.8, você precisa importar um `+__future__+` para fazer a notação `[]` funcionar com as coleções nativas, tal como `list`, como ilustrado no <>. + +[[tokenize_3_7_ex]] +.`tokenize` com dicas de tipo para Python ≥ 3.7 +==== +[source, python] +---- +from __future__ import annotations + +def tokenize(text: str) -> list[str]: + return text.upper().split() +---- +==== + +Este `+__future__+` não funciona com Python 3.6 ou anterior. +O <> mostra como anotar `tokenize` para funcionar com Python ≥ 3.5. + +[[tokenize_3_5_ex]] +.`tokenize` com dicas de tipo para Python ≥ 3.5 +==== +[source, python] +---- +from typing import List + +def tokenize(text: str) -> List[str]: + return text.upper().split() +---- +==== + +Para fornecer um suporte inicial a dicas de tipo genéricas, +os autores da PEP 484 criaram dúzias de tipos genéricos no módulo `typing`. +A <> mostra alguns deles. +Para a lista completa, consulte a documentação do módulo «_typing_» [.small]#[fpy.li/4a]#. + +[[generic_collections_tbl]] +.Alguns tipos de coleção e seus equivalentes nas dicas de tipo +[options="header"] +|=========================================================== +|Collection |Type hint equivalent +|`list` |`typing.List` +|`set` |`typing.Set` +|`frozenset` |`typing.FrozenSet` +|`collections.deque` |`typing.Deque` +|`collections.abc.MutableSequence` |`typing.MutableSequence` +|`collections.abc.Sequence` |`typing.Sequence` +|`collections.abc.Set` |`typing.AbstractSet` +|`collections.abc.MutableSet` |`typing.MutableSet` +|=========================================================== + +A «PEP 585—Type Hinting Generics In Standard Collections» [.small]#[fpy.li/pep585]# deu início a um processo de vários anos para melhorar a usabilidade das dicas de tipo genéricas. +Podemos resumir esse processo em quatro etapas: + + +. Introduzir `from {dunder}future{dunder} import annotations` no Python 3.7 para permitir o uso das classes da biblioteca padrão como genéricos com a notação `list[str]`. + +. 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 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. +**** + +Agora veremos como anotar tuplas genéricas. + +[[tuple_type_sec]] +==== Tipos tuple + +Há ((("gradual type system", "tuple types", id="GTStupple08")))((("tuples", "type hints (type annotations)", id="Thint08"))) +três maneiras de anotar o tipo `tuple`, dependendo de como você usa a tupla: + +* Tupla como registro +* Tupla como registro com campos nomeados +* Tupla como sequência imutável + +===== Tuplas como registros + +Se você está usando uma `tuple` como um registro, use o tipo `tuple` nativo e declare os tipos dos campos dentro dos `[]`. +Por exemplo, para anotar uma tupla com nome da cidade, população e país: +`('Shanghai', 24.28, 'China')`, use `tuple[str, float, str]` na dica de tipo. + +Observe uma função que recebe um par de coordenadas geográficas e retorna um «Geohash» [.small]#[fpy.li/8-18]#, usada assim: + +[source, python] +---- +>>> shanghai = 31.2304, 121.4737 +>>> geohash(shanghai) +'wtw3sjq6q' +---- + +O <> mostra a função `geohash`, que usa o pacote `geolib` do PyPI. + +[[geohash_ex_1]] +._coordinates.py_ com a função `geohash` +==== +[source, python] +---- +include::../code/08-def-type-hints/coordinates/coordinates.py[tags=GEOHASH] +---- +==== +<1> Comentário para que o Mypy ignore que `geolib` não tem dicas de tipo. +<2> O parâmetro `lat_lon`, anotado como uma `tuple` com dois campos `float`. + +<<< +===== Tuplas como registros com campos nomeados + +Para anotar uma tupla com muitos campos, +ou tipos específicos de tupla que seu código usa com frequência, +recomendo fortemente usar `typing.NamedTuple`, como visto no <>. +O <> mostra uma variante de <> com `NamedTuple`. + +[[geohash_ex_2]] +._coordinates_named.py_ com `NamedTuple`, `Coordinates` e a função `geohash` +==== +[source, python] +---- +include::../code/08-def-type-hints/coordinates/coordinates_named.py[tags=GEOHASH] +---- +==== + +Como explicado na <>, +`typing.NamedTuple` é uma fábrica de subclasses de `tuple`, +então `Coordinate` é _consistente-com_ `tuple[float, float]`, +mas o inverso não é verdadeiro—afinal, `Coordinate` tem métodos extras adicionados por `NamedTuple`, +como `._asdict()`, e também poderia ter métodos definidos pelo usuário. + +Na prática, isso significa que é seguro (do ponto de vista do tipo do argumento) +passar uma instância de `Coordinate` para a função `display`, definida assim: + +[source, python] +---- +include::../code/08-def-type-hints/coordinates/coordinates_named.py[tags=DISPLAY] +---- + +<<< +===== Tuplas como sequências imutáveis + +Para anotar tuplas de tamanho desconhecido, usadas como listas imutáveis, você precisa especificar um único tipo, +seguido de uma vírgula e `\...` +(isto é o símbolo de reticências de Python, formado por três caracteres `.`, não o caractere Unicode `U+2026`—`HORIZONTAL ELLIPSIS`). + +Por exemplo, `tuple[int, \...]` é uma tupla com itens `int`. + +As reticências indicam que qualquer número de elementos >= 1 é aceitável. +Não há como especificar campos de tipos diferentes para tuplas de tamanho arbitrário. + +As anotações `stuff: tuple[Any, \...]` e `stuff: tuple` são equivalentes: +`stuff` é uma tupla de tamanho desconhecido contendo objetos de qualquer tipo. + +Aqui temos uma função `columnize`, que transforma uma sequência em uma tabela de colunas e células, +na forma de uma lista de tuplas de tamanho desconhecido. +É útil para mostrar os itens em colunas, assim: + +[source, python] +---- +>>> animals = 'drake fawn heron ibex koala lynx tahr xerus yak zapus'.split() +>>> table = columnize(animals) +>>> table +[('drake', 'koala', 'yak'), ('fawn', 'lynx', 'zapus'), ('heron', 'tahr'), + ('ibex', 'xerus')] +>>> for row in table: +... print(''.join(f'{word:10}' for word in row)) +... +drake koala yak +fawn lynx zapus +heron tahr +ibex xerus +---- + +O <> mostra a implementação de `columnize`. +Observe o tipo((("", startref="Thint08")))((("", startref="GTStupple08"))) do retorno: + +[source, python] +---- +list[tuple[str, ...]] +---- + +[[columnize_ex]] +._columnize.py_ retorna uma lista de tuplas de strings +==== +[source, python] +---- +include::../code/08-def-type-hints/columnize.py[tags=COLUMNIZE] +---- +==== + + +[[mapping_type_sec]] +==== Mapeamentos genéricos + +Tipos de mapeamento genéricos((("gradual type system", "generic mappings")))((("generic mapping types"))) +são anotados como `MappingType[KeyType, ValueType]`. +O tipo nativo `dict` e os tipos de mapeamento em `collections` e `collections.abc` +aceitam essa notação em Python ≥ 3.9. +Para versões mais antigas, use `typing.Dict` e outros tipos de mapeamento do módulo `typing`, +como discutimos em <>. + +O <> mostra um uso na prática de uma função que retorna um +«índice invertido» [.small]#[fpy.li/8-19]# para permitir a busca de caracteres Unicode pelo nome—uma variação do <> +mais adequada para código server-side, que estudaremos no «Capítulo 21» [.small]#[vol.3, fpy.li/21]#. +Dado o início e o final dos códigos de caractere Unicode, +`name_index` retorna um `dict[str, set[str]]`, +que é um índice invertido mapeando cada palavra para um conjunto de caracteres que tem aquela palavra em seus nomes. +Por exemplo, após indexar os caracteres ASCII de 32 a 64, +aqui estão os conjuntos de caracteres mapeados para as palavras `'SIGN'` e `'DIGIT'`, +e a forma de encontrar o caractere chamado `'DIGIT EIGHT'`: + +[source, python] +---- +>>> index = name_index(32, 65) +>>> index['SIGN'] +{'$', '>', '=', '+', '<', '%', '#'} +>>> index['DIGIT'] +{'8', '5', '6', '2', '3', '0', '1', '4', '7', '9'} +>>> index['DIGIT'] & index['EIGHT'] +{'8'} +---- + +O <> mostra o código-fonte de _charindex.py_ com a função `name_index`. +Além de uma dica de tipo `dict[]`, o exemplo tem as novidades `:=` e `yield`. + +[[charindex_ex]] +._charindex.py_ +==== +[source, python] +---- +include::../code/08-def-type-hints/charindex.py[tags=CHARINDEX] +---- +==== +<1> `tokenize` é uma função geradora, assunto do «Capítulo 17» [.small]#[vol.3, fpy.li/17]#. +<2> A variável local `index` está anotada. +Sem a dica, o Mypy diz: +`Need type annotation for 'index' (hint: "index: dict[, ] = ...")`. +<3> O operador morsa (_walrus operator_) `:=` atribui a `name` o resultado da chamada a `unicodedata.name()`, e o valor da expressão inteira é aquele resultado. Quando o resultado é `''`, ele é falso, e o `index` não é atualizado.footnote:[Uso `:=` quando faz sentido em alguns exemplos, mas não trato desse operador no livro. +Veja «_PEP 572—Assignment Expressions_ (Expressões de atribuição)» [.small]#[fpy.li/pep572]# para entender os detalhes dos operadores de atribuição.] + +[NOTE] +==== +Ao armazenar um registro JSON em um `dict`, todas as chaves são `str`, +mas o valor associado a cada chave pode ter um tipo diferente. +Isso é tratado na «Seção 15.3» [.small]#[vol.2, fpy.li/29]#, sobre `typing.TypedDict`. +==== + + +[[type_hint_abc_sec]] +==== ABC: classes base abstratas + +[quote, Lei de Postel ou Princípio da Robustez] + +____ +Seja conservador no que envia, mas liberal no que aceita. +____ + +A <> apresenta((("gradual type system", "abstract base classes", id="GTSabstract08")))((("ABCs (abstract base classes)", "type hints (type annotations)", id="ABChint08"))) várias classes abstratas de `collections.abc`. +Idealmente, uma função deveria aceitar argumentos desses tipos abstratos--ou seus equivalentes de `typing` antes de Python 3.9--e não tipos concretos. +Isso dá mais flexibilidade a quem chama a função. + + +Considere essa assinatura de função: + +[source, python] +---- +from collections.abc import Mapping + +def name2hex(name: str, color_map: Mapping[str, int]) -> str: +---- + +Usar `abc.Mapping` permite ao usuário da função fornecer uma instância de `dict`, `defaultdict`, `ChainMap`, uma subclasse de `UserDict` subclass, ou qualquer outra classe que seja um _subtipo-de_ `Mapping`. + +Por outro lado, veja essa assinatura: + +[source, python] +---- +def name2hex(name: str, color_map: dict[str, int]) -> str: +---- + +Agora `color_map` precisa ser um `dict` ou um de seus subtipos, tal como `defaultdict` ou `OrderedDict`. +Especificamente, uma subclasse de `collections.UserDict` não passaria pela checagem de tipo para `color_map`, +apesar de ser a maneira recomendada de criar mapeamentos definidos pelo usuário, como vimos na <>. +O Mypy rejeitaria um `UserDict` ou uma instância de classe derivada dele, porque `UserDict` não é uma subclasse de `dict`; elas são irmãs. +Ambas são subclasses `abc.MutableMapping`.footnote:[Na verdade, +`dict` é uma subclasse virtual de `abc.MutableMapping`. +O conceito de subclasse virtual será explicado no «Capítulo 13» [.small]#[vol.2, fpy.li/13]#. +Por hora, basta saber que `issubclass(dict, abc.MutableMapping)` é `True`, +apesar de `dict` ser implementada em C, herdando apenas de `object`.] + +Assim, em geral é melhor usar `abc.Mapping` ou `abc.MutableMapping` em dicas de tipos de parâmetros, em vez de `dict` (ou `typing.Dict` em código antigo). +Se a função `name2hex` não precisar modificar o `color_map` recebido, a dica de tipo mais precisa para `color_map` é `abc.Mapping`. +Desse jeito, quem chama não precisa fornecer um objeto que implemente métodos como `setdefault`, `pop`, e `update`, que fazem parte da interface de `MutableMapping`, mas não de `Mapping`. +Isso reflete a segunda parte da lei de Postel: + "[seja] liberal no que aceita." + +A lei de Postel também nos diz para sermos conservadores no que enviamos. +O valor de retorno de uma função é sempre um objeto concreto, +então a dica de tipo do valor de saída deve ser um tipo concreto, +como no exemplo em <>—que usa `list[str]`: + +[source, python] +---- +def tokenize(text: str) -> list[str]: + return text.upper().split() +---- + +No verbete de «`typing.List`» [.small]#[fpy.li/4c]#, a documentação do Python 3.10 diz +(NT: tradução abaixo não oficial) + +[quote] +____ +Versão genérica de `list`. Útil para anotar tipos de retorno. +Para anotar argumentos é preferível usar um tipo de coleção abstrata, como `Sequence` ou `Iterable`. +____ + +Comentários similares aparecem nos verbetes de «`typing.Dict`» [.small]#[fpy.li/8-21]# +e «`typing.Set`» [.small]#[fpy.li/8-22]#. + +Lembre-se de que a maioria das ABCs de `collections.abc` e outras classes concretas de `collections`, +bem como as coleções nativas, suportam notação de dica de tipo genérica como `collections.deque[str]` desde o Python 3.9. +As coleções correspondentes em `typing` só servem para suportar código escrito em Python 3.8 ou anterior. +A lista completa de classes que se tornaram genéricas aparece na seção «_Implementation_» [.small]#[fpy.li/8-16]# da +«_PEP 585—Type Hinting Generics In Standard Collections_» [.small]#[fpy.li/pep585]# . + +Para encerrar nossa discussão de ABCs em dicas de tipo, precisamos falar sobre as ABCs numéricas. + +<<< +[[numeric_tower_warning_sec]] +===== A queda da torre numérica + +O((("numbers package")))((("numeric tower"))) pacote +«`numbers`» [.small]#[fpy.li/4d]# +define "torre numérica" (_numeric tower_) descrita na +«_PEP 3141—A Type Hierarchy for Numbers_» [.small]#[fpy.li/pep3141]#. +A torre é uma hierarquia linear de ABCs, com a classe `numbers.Number` no topo: + +---- +Number + │ + └─ Complex + │ + └─ Real + │ + └─ Rational + │ + └─ Integral +---- + +Esses ABCs funcionam perfeitamente para checagem de tipo durante a execução, +mas não servem para checagem de tipo estática. +A seção «_Numeric Tower_» [.small]#[fpy.li_/cardxvi]# +da PEP 484 rejeita as ABCs `numbers` e +manda tratar os tipos nativos `complex`, `float`, e `int` como casos especiais, +como explicado em <>. +Voltaremos a essa questão na «Seção 13.6.8» [.small]#[vol.2, fpy.li/5g]#, +que é dedicada a comparar protocolos e ABCs. + +Na prática, se você quiser anotar argumentos numéricos para checagem de tipos estática, +há algumas opções: + +. Usar um dos tipos concretos (`int`, `float`, ou `complex`) como recomendado pela PEP 484. +. Declarar um tipo union como `Union[float, Decimal, Fraction]`. +. Se quiser evitar tipos concretos, use protocolos numéricos como `SupportsFloat`, tratados na «Seção 13.6.2» [.small]#[vol.2, fpy.li/5h]#. + +A <> é um pré-requisito para entender os protocolos numéricos. + +Antes disso, vamos examinar uma ABC muito útil em dicas de tipo: `Iterable`.((("", startref="GTSabstract08")))((("", startref="ABChint08"))) + +<<< +==== Iterable + +A((("gradual type system", "Iterable", id="GTSiterable08")))((("Iterable interface", +id="iterable08")))((("interfaces", "Iterable interface"))) +documentação de «`typing.List`» [.small]#[fpy.li/4c]# +que citei acima recomenda `Sequence` e `Iterable` para dicas de tipo de parâmetros de função. + +Esse é um exemplo de argumento `Iterable`, na função `math.fsum` da biblioteca padrão: + +[source, python] +---- +def fsum(__seq: Iterable[float]) -> float: +---- + +.Arquivos Stub e o Projeto Typeshed +[TIP] +==== +Até((("Typeshed project"))) Python 3.10, a biblioteca padrão não tem anotações, +mas Mypy, PyCharm, etc. conseguem encontrar as dicas de tipo necessárias no projeto +«_Typeshed_» [.small]#[fpy.li/8-26]#, +na forma de _arquivos stub_: arquivos de código-fonte especiais, +com a extensão _.pyi_, que contém assinaturas anotadas de métodos e funções, +sem a implementação—semelhante a arquivos _.h_ na linguagem C. + +A assinatura para `math.fsum` está em «`/stdlib/2and3/math.pyi`» [.small]#[fpy.li/8-27]#. +Os sublinhados iniciais em `__seq` são uma convenção estabelecida na PEP 484 para parâmetros apenas posicionais, +como explicado na <>. +==== + +O <> é outro exemplo do uso de um parâmetro `Iterable`, que produz itens que são `tuple[str, str]`. A função é usada assim: + +[source, python] +---- +>>> l33t = [('a', '4'), ('e', '3'), ('i', '1'), ('o', '0')] +>>> text = 'mad skilled noob powned leet' +>>> from replacer import zip_replace +>>> zip_replace(text, l33t) +'m4d sk1ll3d n00b p0wn3d l33t' +---- + +O <> mostra a implementação. + +[[replacer_ex]] +._replacer.py_ +==== +[source, python] +---- +include::../code/08-def-type-hints/replacer.py[tags=ZIP_REPLACE] +---- +==== +<1> `FromTo` é um _apelido de tipo_: atribui `tuple[str, str]` a `FromTo`, +para tornar a assinatura de `zip_replace` mais concisa. +<2> `changes` precisa ser um `Iterable[FromTo]`; +é o mesmo que escrever `Iterable[tuple[str, str]]`, mas é mais curto e mais fácil de ler. + +.O TypeAlias Explícito em Python 3.10 +[TIP] +==== +«_PEP 613—Explicit Type Aliases_» [.small]#[fpy.li/pep613]# introduziu um tipo especial, +`TypeAlias`, para tornar mais explícita a criação de apelidos de tipos. +A partir do Python 3.10, a sintaxe preferencial é: + +[source, python] +---- +from typing import TypeAlias + +FromTo: TypeAlias = tuple[str, str] +---- +==== + + +===== abc.Iterable versus abc.Sequence + +Tanto((("abc.Iterable")))((("abc.Sequence"))) `math.fsum` quanto `replacer.zip_replace` +têm que percorrer todos os argumentos do `Iterable` para produzir um resultado. +Dado um iterável sem fim como entrada (como o gerador `itertools.cycle`), +estas funções consumiriam toda a memória e derrubariam o processo Python. +Apesar desse perigo potencial, é muito comum no Python moderno se oferecer funções que aceitam um `Iterable` como argumento, +mesmo se elas têm que processar a estrutura inteira para obter um resultado. +Isso dá a quem chama a função a opção de fornecer um gerador como dado de entrada, +em vez de uma sequência pré-construída, +podendo economizar bastante memória se o número de itens de entrada for grande. + +Por outro lado, a função `columnize` no <> requer uma `Sequence`, +não um `Iterable`, pois ela precisa obter o `len()` do argumento para calcular previamente o número de linhas. + +Assim como `Sequence`, o melhor uso de `Iterable` é como tipo de argumento. +Ele é muito vago como um tipo de saída. +Uma função deve ser mais precisa sobre o tipo concreto que retorna. + +O tipo `Iterator`, usado como tipo de retorno no <>, +está intimamente relacionado a `Iterable`. +Voltaremos a ele em «Capítulo 17» [.small]#[vol.3, fpy.li/17]#, que trata de geradores e +iteradores clássicos.((("", startref="GTSiterable08")))((("", startref="iterable08"))) + +[[param_generics_typevar_sec]] +==== Genéricos parametrizados e TypeVar + +Um((("gradual type system", "parameterized generics and TypeVar", +id="GTSparam08")))((("generic collections", "parameterized generics and TypeVar", +id="GCtypvar08")))((("TypeVar", id="typevar08"))) +genérico parametrizado é um tipo genérico, escrito na forma `list[T]`, +onde `T` é uma variável de tipo que será vinculada a um tipo específico a cada uso. +Isso permite que o tipo de um argumento determine o tipo do resultado da função. + +O <> define `sample`, uma função que recebe dois argumentos: +uma `Sequence` de elementos de tipo `T` e um `int`. +Ela retorna uma `list` de elementos do mesmo tipo `T`, escolhidos aleatoriamente do primeiro argumento. + +O <> mostra a implementação. + +[[generic_sample_ex]] +._sample.py_ +==== +[source, python] +---- +include::../code/08-def-type-hints/sample.py[tags=SAMPLE] +---- +==== + +<<< +Aqui estão dois exemplos que ilustram por que usei uma variável de tipo em `sample`: + +* Se chamada com uma tupla de tipo `tuple[int, \...]` (que é _consistente-com_ `Sequence[int]`) +então o tipo parametrizado é `int`, e o tipo de retorno é `list[int]`. +* Se chamada com uma `str` (que é _consistente-com_ `Sequence[str]`) +então o tipo parametrizado é `str`, e o tipo do retorno é `list[str]`. + + +[role="man-height-2in"] +.Por que TypeVar é necessário? +[NOTE] +==== +Os autores da PEP 484 queriam introduzir dicas de tipo ao acrescentar o módulo `typing`, +sem mudar nada mais na linguagem. +Com uma metaprogramação esperta, eles poderiam fazer o operador `[]` funcionar para classes como `Sequence[T]`. +Mas o nome da variável `T` dentro dos colchetes precisa ser definido em algum lugar—do contrário, +o interpretador Python necessitaria de mudanças mais profundas +para suportar a notação de tipos genéricos como um caso especial de `[]`. +Por isso o construtor `typing.TypeVar` é necessário: +para introduzir o nome da variável no _namespace_ (espaço de nomes) atual. +Linguagens como Java, C# e TypeScript não exigem que a variável de tipo seja declarada previamente, +então elas não precisam de algo equivalente à pseudo-classe `TypeVar` de Python. +==== + + +Outro exemplo é a função `statistics.mode` da biblioteca padrão, que retorna o ponto de dado mais comum de uma série. + +Eis um exemplo de uso da «documentação» [.small]#[fpy.li/4e]#: + +[source, python] +---- +>>> mode([1, 1, 2, 3, 3, 3, 3, 4]) +3 +---- + +<<< + +Sem o uso de `TypeVar`, `mode` poderia ter uma assinatura como a apresentada no <>. + +[[mode_float_ex]] +._mode_float.py_: `mode` que opera com `float` e seus subtipos footnote:[A implementação aqui é mais simples que aquela do módulo «`statistics`» [.small]#[fpy.li/8-29]# na biblioteca padrão de Python] +==== +[source, python] +---- +include::../code/08-def-type-hints/mode/mode_float.py[tags=MODE_FLOAT] +---- +==== + +Muitos dos usos de `mode` envolvem valores `int` ou `float`, mas Python tem outros tipos numéricos, e é desejável que o tipo de retorno siga o tipo dos elementos do `Iterable` recebido. +Podemos melhorar aquela assinatura usando `TypeVar`. Vamos começar com uma assinatura parametrizada simples, mas errada. + +[source, python] +---- +from collections.abc import Iterable +from typing import TypeVar + +T = TypeVar('T') + +def mode(data: Iterable[T]) -> T: +---- + +Quando aparece pela primeira vez na assinatura, o parâmetro de tipo `T` aceita qualquer tipo. +Da segunda vez que aparece, só aceitará o mesmo tipo vinculado da primeira vez. + +Assim, qualquer iterável é _consistente-com_ `Iterable[T]`, +incluindo iterável de tipos _unhashable_ que `collections.Counter` não consegue tratar. +Precisamos restringir os tipos possíveis de se atribuir a `T`. +Veremos diferentes modos de fazer isso nas duas seções seguintes. + +<<< +[[typevar_constraints_sec]] +===== TypeVar restrita + +O `TypeVar` aceita argumentos posicionais adicionais para restringir o tipo parametrizado. +Podemos melhorar a assinatura de `mode` para aceitar um número específico de tipos, assim: + +[source, python] +---- +from collections.abc import Iterable +from decimal import Decimal +from fractions import Fraction +from typing import TypeVar + +NumberT = TypeVar('NumberT', float, Decimal, Fraction) + +def mode(data: Iterable[NumberT]) -> NumberT: +---- + +Está melhor que antes, e era a assinatura de `mode` em +«`statistics.pyi`» [.small]#[fpy.li/8-30]#, o arquivo stub em `typeshed` em 25 de maio de 2020. + +Entretanto, a documentação em «`statistics.mode`» [.small]#[fpy.li/8-28]# inclui esse exemplo: + +[source, python] +---- +>>> mode(["red", "blue", "blue", "red", "green", "red", "red"]) +'red' +---- + +Na pressa, poderíamos apenas adicionar `str` à definição de `NumberT`: + +[source, python] +---- +NumberT = TypeVar('NumberT', float, Decimal, Fraction, str) +---- + +Com certeza funciona, mas `NumberT` estaria muito mal batizado se aceitasse `str`. +Mais importante, não podemos ficar listando tipos para sempre, cada vez que percebermos que `mode` pode lidar com outro deles. +Podemos fazer melhor com um outro recurso de `TypeVar`, como veremos a seguir. + +[[bounded_typevar_sec]] +===== TypeVar delimitada + +Examinando o corpo de `mode` no <>, +vemos que a classe `Counter` é usada para ordenação. +`Counter` é baseada em `dict`, então o tipo do elemento do iterável `data` precisa ser _hashable_. + +A princípio, essa assinatura pode parecer que funciona: + +[source, python] +---- +from collections.abc import Iterable, Hashable + +def mode(data: Iterable[Hashable]) -> Hashable: +---- + +Agora o problema é que o tipo do item retornado é `Hashable`: +um ABC que implementa apenas o método `+__hash__+`. +Então o checador de tipos não vai permitir que façamos nada com o valor retornado, +exceto chamar seu método `hash()`. Não é muito útil. + +A solução está em outro parâmetro opcional de `TypeVar`: +o parâmetro representado pela palavra-chave `bound`. +Ele estabelece um limite superior para os tipos aceitos. +No <>, temos `bound=Hashable`. +Isso significa que o tipo do parâmetro pode ser `Hashable` ou +qualquer _subtipo-de_ `Hashable`.footnote:[Forneci essa solução para `typeshed`, +e em 26 de maio de 2020 `mode` aparecia anotado assim em +«`statistics.pyi`» [.small]#[fpy.li/8-32]#.] + +[[mode_hashable_ex]] +._mode_hashable.py_: comoo o <>, com assinatura mais flexível +==== +[source, python] +---- +include::../code/08-def-type-hints/mode/mode_hashable.py[tags=MODE_HASHABLE_T] +---- +==== + +Em resumo: + +* Uma variável de tipo restrita será vinculada a um dos tipos nomeados na declaração do `TypeVar`. +* Uma variável de tipo delimitada será vinculada ao tipo inferido da expressão, +desde que o tipo inferido seja _consistente-com_ o limite declarado pelo argumento `bound=` do TypeVar. + +[NOTE] +==== +É uma pena que a palavra-chave do argumento para declarar uma `TypeVar` delimitada se chame `bound=`, +(limite, como substantivo) pois o verbo "bound" (passado do verbo _to bind_, ligar ou vincular) +é muito usado em inglês para descrever a associação de um valor a uma variável. +Seria melhor que a palavra-chave do argumento fosse `boundary=`, +um substantivo mais comum e explícito que também significa _limite_. +==== + +O construtor de `typing.TypeVar` tem outros parâmetros opcionais (`covariant` e `contravariant`) +que envolvem conceitos avançados que vamos deixar para a «Seção 15.7» [.small]#[vol.2, fpy.li/5j]#. +Vamos concluir essa introdução a `TypeVar` com `AnyStr`. + +===== A variável de tipo pré-definida AnyStr + +O((("AnyStr"))) módulo `typing` inclui uma `TypeVar` pré-definida chamada `AnyStr`, +restrita aos tipos `bytes` e `str`. +Ele é definido assim: + +[source, python] +---- +AnyStr = TypeVar('AnyStr', bytes, str) +---- + +O tipo `AnyStr` é usado em funções da biblioteca padrão que aceitam tanto `bytes` quanto `str`, +e retornam valores do tipo recebido. + +Agora veremos `typing.Protocol`, um novo recurso de Python 3.8, +que permite um uso mais pythônico de dicas de +tipo.((("", startref="GTSparam08")))((("", startref="GCtypvar08")))((("", startref="typevar08"))) + +[[protocols_in_fn_sec]] +==== Protocolos estáticos + +[NOTE] +==== +Em ((("gradual type system", "static protocols", +id="GTSstatic08")))((("static protocols", "type hints (type annotations)", id="stprot08"))) +programação orientada a objetos, o conceito de um "protocolo" como uma interface informal é tão antigo quanto Smalltalk, +e tem sido parte essencial de Python desde o início. +Entretanto, no contexto de dicas de tipo, um protocolo é uma subclasse de `typing.Protocol`, +definindo uma interface que um checador de tipos pode analisar. +Os dois tipos de protocolo são tratados no «Capítulo 13» [.small]#[vol.2, fpy.li/13]#. +Aqui apresento apenas uma rápida introdução no contexto de anotações de função. +==== + +<<< + +O((("Protocol type", id="proto08"))) tipo `Protocol`, como descrito em +«_PEP 544—Protocols: Structural subtyping (static duck typing)_» [.small]#[fpy.li/pep544]#, +é similar às interfaces em Go: um tipo protocolo é definido especificando um ou mais métodos, +e o checador de tipos analisa se aqueles métodos estão implementados quando um tipo daquele protocolo +é exigido por uma anotação. + +Em Python, uma definição de protocolo é escrita como uma subclasse de `typing.Protocol`. +Entretanto, classes que _implementam_ um protocolo não precisam herdar, +registrar ou declarar qualquer relação com a classe que _define_ o protocolo. +É função do checador de tipos encontrar os tipos de protocolos disponíveis e checar sua utilização. + +Abaixo temos um problema que pode ser resolvido com a ajuda de `Protocol` e `TypeVar`. +Suponha que você quisesse criar uma função `top(it, n)`, que retorna os `n` maiores elementos do iterável `it`: + +[source, python] +---- +include::../code/08-def-type-hints/comparable/top.py[tags=TOP_DOCTEST] +---- + +A função genérica parametrizada `top` poderia ser implementada como no <>. + +[[top_undefined_t_ex]] +.a função `top` function com um parâmetro de tipo `T` indefinido +==== +[source, python] +---- +def top(series: Iterable[T], length: int) -> list[T]: + ordered = sorted(series, reverse=True) + return ordered[:length] +---- +==== + +O problema é: como restringir `T`? +Não pode ser `Any` ou `object`, pois `series` precisa funcionar com `sorted`. +A função `sorted` nativa na verdade aceita `Iterable[Any]`, +mas só porque o parâmetro opcional `key` recebe uma função que calcula uma chave de ordenação arbitrária para cada elemento. +O que acontece se você passar para `sorted` uma lista de objetos simples, +mas não fornecer um argumento `key`? +Vamos tentar: + +[source, python] +---- +>>> l = [object() for _ in range(4)] +>>> l +[, , +, ] +>>> sorted(l) +Traceback (most recent call last): + File "", line 1, in +TypeError: '<' not supported between instances of 'object' and 'object' +---- + +A mensagem de erro mostra que `sorted` usa o operador `<` nos elementos do iterável. +É só isso? Vamos tentar outro experimento rápido:footnote:[É +ótimo poder abrir um console iterativo e contar com a tipagem pato para explorar recursos da linguagem, +como acabei de fazer. +Sinto muita falta deste tipo de exploração quando uso linguagem que não tem esse recurso.] + +[source, python] +---- +>>> class Spam: +... def __init__(self, n): self.n = n +... def __lt__(self, other): return self.n < other.n +... def __repr__(self): return f'Spam({self.n})' +... +>>> l = [Spam(n) for n in range(5, 0, -1)] +>>> l +[Spam(5), Spam(4), Spam(3), Spam(2), Spam(1)] +>>> sorted(l) +[Spam(1), Spam(2), Spam(3), Spam(4), Spam(5)] +---- + +Isso confirma a suspeita: eu consigo passar uma lista de `Spam` para `sort`, +porque `Spam` implementa `+__lt__+`, o método especial do operador `<`. + +Então o parâmetro de tipo `T` no <> deveria ser limitado a tipos que implementam `+__lt__+`. +No <>, precisávamos de um parâmetro de tipo que implementava `+__hash__+`, para poder usar `typing.Hashable` como limite superior do parâmetro de tipo. +Mas agora não há um tipo adequado em `typing` ou `abc` para usarmos, então precisamos criar um. + +O <> mostra o novo tipo `SupportsLessThan`, um `Protocol`. + +[[comparable_protocol_ex]] +._comparable.py_: a definição de um tipo `Protocol`, `SupportsLessThan` +==== +[source, python] +---- +include::../code/08-def-type-hints/comparable/comparable.py[] +---- +==== +<1> Um protocolo é uma subclasse de `typing.Protocol`. +<2> O corpo do protocolo tem uma ou mais definições de método, com `\...` no lugar da implementação. + +Um tipo `T` é _consistente-com_ um protocolo `P` se `T` implementa todos os métodos definidos em `P`, +com assinaturas de tipo correspondentes. + +Dado `SupportsLessThan`, agora podemos definir essa versão funcional de `top` no <>. + +[[top_protocol_ex]] +._top.py_: definição da função `top` usando uma `TypeVar` com `bound=SupportsLessThan` +==== +[source, python] +---- +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`. +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`. + +<<< +[[ex_top_protocol_test]] +._top_test.py_: visão parcial da bateria de testes para `top` +==== +[source, python] +---- +include::../code/08-def-type-hints/comparable/top_test.py[tags=TOP_IMPORT] + +# muitas linhas omitidas + +include::../code/08-def-type-hints/comparable/top_test.py[tags=TOP_TEST] +---- +==== +<1> A constante `typing.TYPE_CHECKING` é sempre `False` durante a execução do programa, mas os checadores de tipos fingem que ela é `True` quando estão fazendo a checagem. +<2> Declaração de tipo explícita para a variável `series`, para tornar mais fácil a leitura da saída do +Mypy.footnote:[Sem essa dica de tipo, o Mypy inferiria o tipo de `series` como +`Generator[Tuple[builtins.int, builtins.str*], None, None]`, +que é prolixo mas _consistente-com_ `Iterator[tuple[int, str]]`, +como veremos na «Seção 17.12» [.small]#[vol.3, fpy.li/5k].]#] +<3> Este `if` evita que as três linhas seguintes sejam executadas durante o teste. +<4> `reveal_type()` não pode ser chamada durante a execução, porque não é uma função regular, mas sim um mecanismo de depuração do Mypy - por isso não há `import` para ela. +Mypy produzirá uma mensagem de depuração para cada chamada à pseudo-função `reveal_type()`, mostrando o tipo inferido do argumento. +<5> Essa linha será marcada pelo Mypy como um erro. + +Os testes anteriores são bem-sucedidos—mas eles funcionariam de qualquer forma, com ou sem dicas de tipo em _top.py_. +Mais precisamente, se eu checar aquele arquivo de teste com o Mypy, verei que o `TypeVar` está funcionando como esperado. +Veja a saída do comando `mypy` no <>. + +[WARNING] +==== +Desde o Mypy 0.910 (julho de 2021), em alguns casos a saída de `reveal_type` +não mostra precisamente os tipos que declarei, mas mostra tipos compatíveis. +Por exemplo, não usei `typing.Iterator` e sim `abc.Iterator`. +Pode ignorar este detalhe. +O relatório do Mypy ainda é útil. +Vou fingir que este problema do Mypy já foi corrigido quando for discutir os resultados. +==== + +[[top_protocol_mypy_output]] +.Saída do _mypy top_test.py_ (linhas quebradas para facilitar a leitura) +==== +[source] +---- +…/comparable/ $ mypy top_test.py +top_test.py:32: note: + Revealed type is "typing.Iterator[Tuple[builtins.int, builtins.str]]" <1> +top_test.py:33: note: + Revealed type is "builtins.list[Tuple[builtins.int, builtins.str]]" +top_test.py:34: note: + Revealed type is "builtins.list[Tuple[builtins.int, builtins.str]]" <2> +top_test.py:41: note: + Revealed type is "builtins.list[builtins.object*]" <3> +top_test.py:43: error: + Value of type variable "LT" of "top" cannot be "object" <4> +Found 1 error in 1 file (checked 1 source file) +---- +==== +<1> Em `test_top_tuples`, `reveal_type(series)` mostra que ele é um `Iterator[tuple[int, str]]` que declarei explicitamente. +<2> `reveal_type(result)` confirma que o tipo produzido pela chamada a `top` é o que eu queria: +dado o tipo de `series`, o `result` é `list[tuple[int, str]]`. +<3> Em `test_top_objects_error`, `reveal_type(series)` mostra que ele é uma `list[object*]`. +Mypy põe um `*` após qualquer tipo que tenha sido inferido: não anotei o tipo de `series` nesse teste. +<4> Mypy marca o erro que esse teste produz intencionalmente: +o tipo dos elementos do `Iterable` `series` não pode ser `object` (ele precisa ser do tipo `SupportsLessThan`). + +A principal vantagem de um tipo protocolo sobre as ABCs é que +uma classe não precisa de nenhuma declaração especial para ser _consistente-com_ um tipo protocolo. +Isso permite que um protocolo seja criado aproveitando tipos pré-existentes, +ou tipos implementados em bases de código que não estão sob nosso controle. +Eu não tenho que derivar ou registrar `str`, `tuple`, `float`, `set`, etc. +com `SupportsLessThan` para usá-los onde um parâmetro `SupportsLessThan` é esperado. +Eles só precisam implementar `+__lt__+`. +E o checador de tipos ainda será capaz de realizar seu trabalho, +porque `SupportsLessThan` está explicitamente declarado como um +`Protocol`—diferente dos protocolos implícitos comuns na tipagem pato, +que são invisíveis para o checador de tipos. + +A classe especial `Protocol` foi introduzida na +«_PEP 544—Protocols: Structural subtyping (static duck typing)_» [.small]#[fpy.li/pep544]#. +O <> demonstra((("static duck typing"))) por que esse recurso +é conhecido como a tipagem pato estática (_static duck typing_): +a solução para anotar o parâmetro `series` de `top` era dizer "O tipo nominal de `series` não importa, +desde que ele implemente o método `+__lt__+`." +Em Python, a tipagem pato sempre permitiu dizer isso de forma implícita, +deixando os checadores de tipos no escuro. +Um checador de tipos não consegue ler o código-fonte em C do CPython, +ou executar experimentos no console para descobrir que `sorted` só requer que seus elementos suportem `<`. + +Agora podemos tornar a tipagem pato explícita para os checadores de tipo estáticos. +Por isso faz sentido dizer que `typing.Protocol` nos oferece tipagem pato +estática.footnote:[Não sei quem inventou a expressão _static duck typing_, +mas ela se tornou mais popular com a linguagem Go, +que tem uma semântica de interfaces mais parecida com os protocolos de Python +que com as interfaces nominais de Java.] + +Há mais para falar sobre `typing.Protocol`. +Vamos voltar a ele no «Capítulo 13» [.small]#[vol.2, fpy.li/13]# +onde comparamos as abordagens da tipagem estrutural, +da tipagem pato e das ABCs—outro modo de formalizar protocolos. +Além disso, a «Seção 15.2» [.small]#[vol.2, fpy.li/5f]# explica como +declarar assinaturas de funções com sobrecarga (_overload_) com `@typing.overload`, +e inclui um exemplo bastante extenso usando `typing.Protocol` e uma `TypeVar` delimitada. + +[NOTE] +==== +O `typing.Protocol` torna possível anotar a função `double` na <> sem perder funcionalidade. +O segredo é definir uma classe de protocolo com o método `+__mul__+`. +Convido você a fazer isso como um exercício. +A solução está na «Seção 13.6.1» [.small]#[vol.2, fpy.li/5m]#.((("", startref="GTSstatic08")))((("", startref="stprot08")))((("", startref="proto08"))) +==== + + +==== Callable + +Para((("gradual type system", "Callable type", id="GTScallable08")))((("Callable type", id="callable08"))) anotar parâmetros de callback ou objetos _callable_ retornados por funções de ordem superior, o módulo `collections.abc` oferece o tipo `Callable`, disponível no módulo `typing` para quem ainda não estiver usando Python 3.9. +Um tipo `Callable` é parametrizado assim: + +[source, python] +---- +Callable[[ParamType1, ParamType2], ReturnType] +---- + +A lista de parâmetros `[ParamType1, ParamType2]` pode ter zero ou mais tipos. + +Aqui está um exemplo no contexto de uma função `repl`, +parte do interpretador iterativo simples que veremos na +«Seção 18.3» [.small]#[vol.3, fpy.li/6a]#:footnote:[REPL +significa Read-Eval-Print-Loop (_Ler-Calcular-Imprimir-Recomeçar_), o comportamento básico de interpretadores iterativos.] + +[source, python] +---- +def repl(input_fn: Callable[[Any], str] = input]) -> None: +---- + +Durante a utilização normal, a função `repl` usa a função `input` nativa de Python para ler expressões inseridas pelo usuário. +Entretanto, para testagem automatizada ou para integração com outras fontes de entrada, +`repl` aceita um parâmetro `input_fn` opcional: +um `Callable` com o mesmo parâmetro e tipo de retorno de `input`. + +A função embutida `input` tem a seguinte assinatura no typeshed: + +[source, python] +---- +def input(__prompt: Any = ...) -> str: ... +---- + +<<< +A assinatura de `input` é _consistente-com_ esta dica de tipo `Callable`: + +[source, python] +---- +Callable[[Any], str] +---- +Não existe sintaxe para declarar o tipo de argumentos opcionais ou nomeados. +A «documentação» [.small]#[fpy.li/4f]# +de `typing.Callable` diz "tais funções são raramente usadas como tipo de callback." +Se você precisar de uma dica de tipo para acompanhar uma função com assinatura mais flexível, +substitua a lista de parâmetros por `\...` - assim: + +[source, python] +---- +Callable[..., ReturnType] +---- + +A interação de parâmetros de tipo genéricos com uma hierarquia de tipos introduz um novo conceito: variância. + +[[callable_variance_sec]] +===== Variância em tipos callable + +Imagine um sistema de controle de temperatura com uma função `update` simples, como mostrada no <>.((("variance", "in callable types")))((("covariance", see="variance")))((("contravariance", see="variance"))) +A função `update` chama a função `probe` para obter a temperatura atual, e chama `display` para mostrar a temperatura para o usuário. +`probe` e `display` são ambas passadas como argumentos para `update`, por motivos didáticos. +O objetivo do exemplo é contrastar duas anotações de `Callable`: uma como tipo de retorno e outra como tipo de parâmetro. + +[[callable_variance_ex]] +.Ilustrando a variância. +==== +[source, python] +---- +include::../code/08-def-type-hints/callable/variance.py[] +---- +==== +<1> `update` recebe duas funções callable como argumentos. +<2> `probe` precisa ser uma callable que não recebe nenhum argumento e retorna um `float` +<3> `display` recebe um argumento `float` e retorna `None`. +<4> `probe_ok` é _consistente-com_ `Callable[[], float]` porque retornar um `int` não quebra código que espera um `float`. +<5> `display_wrong` não é _consistente-com_ `Callable[[float], None]` porque nada garante que uma função esperando um `int` consiga lidar com um `float`; por exemplo, a função `hex` de Python aceita um `int` mas rejeita um `float`. +<6> O Mypy marca essa linha porque `display_wrong` é incompatível com a dica de tipo no parâmetro `display` em `update`. +<7> `display_ok` é _consistente_com_ `Callable[[float], None]` porque uma função que aceita um `complex` também consegue lidar com um argumento `float`. +<8> Mypy está satisfeito com essa linha. + +Resumindo, +não há problema em fornecer uma função de callback que retorne um `int` quando o código espera uma função callback que retorne um `float`, porque um valor `int` sempre pode ser usado onde um `float` é esperado. + +Formalmente, dizemos que `Callable[[], int]` é _subtipo-de_ `Callable[[], float]`, +assim como `int` é _subtipo-de_ `float`. +Isso significa que `Callable` é _covariante_ no que diz respeito aos tipos de retorno, +porque a relação _subtipo-de_ dos tipos `int` e `float` aponta na mesma direção +que os tipos `Callable` que os usam como tipos de retorno. + +Por outro lado, é um erro de tipo fornecer uma função callback que recebe um argumento `int` +quando é se espera um callback que possa processar um `float`. + +Formalmente, `Callable[[int], None]` não é _subtipo-de_ `Callable[[float], None]`. +Apesar de `int` ser _subtipo-de_ `float`, no `Callable` parametrizado a relação é invertida: +`Callable[[float], None]` é _subtipo-de_ `Callable[[int], None]`. +Assim dizemos que aquele `Callable` é _contravariante_ a respeito dos tipos de parâmetros declarados. + +A «Seção 15.7» [.small]#[vol.2, fpy.li/5j]# explica variância em mais detalhes e +com exemplos de tipos invariantes, covariantes e contravariantes. + +[TIP] +==== +Por hora, saiba que a maioria dos tipos genéricos parametrizados são _invariantes_, portanto mais simples. +Por exemplo, se eu declaro `scores: list[float]`, +isso me diz exatamente o que posso atribuir a `scores`. +Não posso atribuir objetos declarados como `list[int]` ou `list[complex]`: + +* Um objeto `list[int]` não é aceitável porque ele não pode conter valores `float` que meu código pode precisar colocar em `scores`. +* Um objeto `list[complex]` não é aceitável porque meu código pode precisar ordenar `scores` para encontrar a mediana, mas `complex` não fornece o método `+__lt__+`, então `list[complex]` não é ordenável. +==== + +Agora chegamos ou último tipo especial que examinaremos nesse capítulo. + +[[noreturn_sec]] +==== NoReturn + +Esse((("gradual type system", "NoReturn type")))((("NoReturn type"))) é um tipo especial usado apenas para anotar o tipo de retorno de funções +que nunca devolvem um resultado. +Normalmente, elas existem para gerar exceções. +Há dúzias dessas funções na biblioteca padrão. +Por exemplo, `sys.exit()` levanta `SystemExit` para encerrar o processo Python. +A assinatura no `typeshed` é: + +[source, python] +---- +def exit(__status: object = ...) -> NoReturn: ... +---- + +O parâmetro `+__status__+` é apenas posicional, e tem um valor default. +Arquivos stub não declaram valores default, em vez disso usam `\...`. +O tipo de `__status` é `object`, o que significa que pode também ser `None`, +então seria redundante escrever `Optional[object]`. + +Na «Seção 24.5» [.small]#[vol.3, fpy.li/5n]#, +o método `__flag_unknown_attrs` tem o tipo de retorno `NoReturn`, +pois sua função é produzir uma mensagem de erro detalhada e amigável, +e então levantar `AttributeError`. + +A última seção desse capítulo épico é sobre parâmetros posicionais e variádicos. +((("", startref="THTusable08")))((("", startref="FTHusable08"))) + +[[arbitrary_arguments_sec]] +=== Anotando parâmetros apenas posicionais e variádicos + +Lembra((("functions, type hints in", +"annotating positional only and variadic parameters")))((("type hints (type annotations)", +"annotating positional only and variadic parameters")))((("parameters", +"annotating positional only and variadic parameters")))((("variadic parameters"))) +da função `tag` do <>? +Da última vez que vimos sua assinatura foi na <>: + +[source, python] +---- +def tag(name, /, *content, class_=None, **attrs): +---- + +Aqui está `tag`, com as anotações escritas em várias linhas—uma +convenção comum para assinaturas longas, +com quebras de linha como o formatador «_blue_» [.small]#[fpy.li/8-10]# faria: + +[source, python] +---- +from typing import Optional + +def tag( + name: str, + /, + *content: str, + class_: Optional[str] = None, + **attrs: str, +) -> str: +---- + +Observe a dica de tipo `*content: str`, para parâmetros posicionais variádicos, +que aceita zero ou mais argumentos posicionais. +A anotação declara que aqueles argumentos têm que ser do tipo `str`. +O tipo da variável local `content` no corpo da função será `tuple[str, \...]`. + +A dica de tipo para argumentos nomeados variádicos é `+**attrs: str+` neste exemplo, +portanto o tipo de `attrs` dentro da função será `dict[str, str]`. +Para uma dica de tipo como `+**attrs: float+`, +o tipo de `attrs` na função seria `dict[str, float]`. + +Se for necessário que o parâmetro `attrs` aceite valores de tipos diferentes, +é preciso usar uma `Union[]` ou `Any`: `+**attrs: Any+`. + +A notação `/` para parâmetros puramente posicionais está disponível a partir do Python 3.8. +Em Python 3.7 ou anterior, é um erro de sintaxe. +A «convenção da PEP 484» [.small]#[fpy.li/8-36]# é prefixar +o nome de cada parâmetro puramente posicional com dois sublinhados. +Veja a assinatura de `tag` novamente, agora em duas linhas, +usando a convenção da PEP 484: + +[source, python] +---- +from typing import Optional + +def tag(__name: str, *content: str, class_: Optional[str] = None, + **attrs: str) -> str: +---- + +O Mypy sabe checar as duas formas de declarar parâmetros puramente posicionais. + +Para encerrar esse capítulo, vamos considerar brevemente os limites das dicas de tipo e do sistema de tipagem estática que elas suportam. + +=== Tipos imperfeitos e testes poderosos + +Os mantenedores((("functions, type hints in", +"flawed typing and strong testing")))((("type hints (type annotations)", +"flawed typing and strong testing")))((("flawed typing")))((("strong testing"))) +de grandes bases de código corporativas relatam que muitos bugs são encontrados por checadores de tipos estáticos, +e o custo de resolvê-los é menor que se os mesmos bugs fossem descobertos apenas após o código estar rodando em produção. +Entretanto, é essencial observar que testes automatizados já eram uma boa prática +largamente adotada muito antes da tipagem estática ser introduzida nas empresas que conheço usando Python. + +Mesmo em contextos onde ela é mais benéfica, a tipagem estática não pode ser elevada a árbitro final da correção. +Não é difícil encontrar: + +Falsos Positivos:: Ferramentas indicam erros de tipagem em código correto. +Falsos Negativos:: Ferramentas não indicam erros em código incorreto. + +Além disso, se formos forçados a checar o tipo de tudo, perdemos um pouco do poder expressivo de Python: + +* Alguns recursos convenientes não podem ser checados de forma estática: por exemplo, +o desempacotamento de argumentos como em `config(**settings)`. +* Recursos avançados como propriedades, descritores, metaclasses e metaprogramação em geral, +têm suporte muito deficiente ou estão além da compreensão dos checadores de tipo. +* Checadores de tipo ficam obsoletos e/ou incompatíveis após o lançamento de novas versões de Python, +rejeitando ou mesmo quebrando ao analisar código com novos recursos da linguagem—às vezes com atrasos de +mais de um ano. + +Restrições comuns de dados não podem ser expressas no sistema de tipo, mesmo restrições simples. +Por exemplo, dicas de tipo são incapazes de assegurar que "quantidade deve ser um inteiro > 0" +ou que "label deve ser uma string com 6 a 12 letras em ASCII." +Em geral, dicas de tipo não são úteis para localizar erros na lógica do negócio subjacente ao código. + +Dadas essas ressalvas, dicas de tipo não podem ser o pilar central da qualidade do software, +e torná-las obrigatórias sem qualquer exceção só amplificaria os aspectos negativos. + +Considere o checador de tipos estático como uma das ferramentas +na infraestrutura moderna de integração de código, ao lado de testadores, +analisadores de código (_linters_), etc. +O objetivo de uma estrutura de produção de integração de código é reduzir as falhas no software, +e testes automatizados podem encontrar muitos bugs que estão fora do alcance de dicas de tipo. +Qualquer código que possa ser escrito em Python pode ser testado em Python—com ou sem dicas de tipo. + +<<< +[NOTE] +==== +O título e a conclusão dessa seção foram inspirados pelo artigo +«_Strong Typing vs. Strong Testing_» [.small]#[fpy.li/8-37]# de Bruce Eckel, +também publicado na antologia «_The Best Software Writing I_» [.small]#[fpy.li/8-38]#, +editada por Joel Spolsky (Apress). +Bruce é um fã de Python, e autor de livros sobre {cpp}, Java, Scala, e Kotlin. +Naquele texto, ele conta como foi um defensor da tipagem estática até aprender Python, e conclui: +"Se um programa em Python tem testes unitários adequados, ele poderá ser tão robusto quanto um programa em {cpp}, Java, ou C# com testes unitários adequados (mas será mais rápido escrever os testes em Python). +==== + +// [role="pagebreak-before less_space"] +Isso encerra nossa cobertura das dicas de tipo em Python por agora. +Elas serão também o ponto central do «Capítulo 15» [.small]#[vol.2, fpy.li/15]#, que trata de classes genéricas, +variância, assinaturas sobrecarregadas, coerção de tipos (_type casting_), entre outros tópicos. +Até lá, as dicas de tipo aparecerão em várias funções ao longo do livro. + + +=== Resumo do capítulo + +Começamos((("functions, type hints in", "overview of")))((("type hints (type annotations)", "overview of"))) +com uma pequena introdução ao conceito de tipagem gradual, depois adotamos uma abordagem prática. +É difícil ver como a tipagem gradual funciona sem uma ferramenta que efetivamente leia as dicas de tipo, +então desenvolvemos uma função anotada guiados pelos relatórios de erro do Mypy. + +Voltando à ideia de tipagem gradual, vimos como ela é um híbrido da tipagem pato tradicional +de Python e da tipagem nominal mais familiar aos usuários de Java, {cpp} e de outras linguagens de tipagem estática. + +A maior parte do capítulo foi dedicada a apresentar os principais grupos de tipos usados em anotações. +Muitos dos tipos discutidos estão relacionados a tipos conhecidos de objetos de Python, +como coleções, tuplas e callables - estendidos para suportar notação genérica do tipo `Sequence[float]`. +Muitos daqueles tipos são substitutos temporários, +implementados no módulo `typing` antes que os tipos padrão fossem modificados para suportar genéricos, no Python 3.9. +Alguns desses tipos são entidades especiais: +`Any`, `Optional`, `Union`, e `NoReturn` não têm qualquer relação com objetos reais na memória, +existem apenas no domínio abstrato do sistema de tipos. + +Estudamos genéricos parametrizados e variáveis de tipo, +que trazem mais flexibilidade para as dicas de tipo sem sacrificar a segurança da tipagem. + +Genéricos parametrizáveis se tornam ainda mais expressivos com o uso de `Protocol`. +Como só surgiu no Python 3.8, `Protocol` ainda não é muito usado—mas é muito importante. +`Protocol` possibilita a tipagem pato estática: +é a ponte fundamental entre o núcleo de Python, construído sobre tipagem pato, +e a tipagem nominal que permite a checadores de tipos estáticos encontrarem bugs. + +Ao discutir alguns desses tipos, usamos o Mypy para localizar erros de checagem de tipo e tipos inferidos, +com a ajuda da função mágica `reveal_type()` do Mypy. + +A seção final mostrou como anotar parâmetros exclusivamente posicionais e parâmetros variádicos. + +Dicas de tipo são um tópico complexo e em constante evolução. +Felizmente são um recurso opcional. +Vamos manter Python acessível para a maior base de usuários possível, +e parar de insistir que todo código Python precisa ter dicas de tipo—como +já vi em sermões públicos de evangelistas da tipagem estática. + +O BDFL emérito liderou o movimento de inclusão de dicas de tipo em Python, +então é muito justo que esse capítulo comece e termine com suas palavras. + +[quote, Guido van Rossum] +____ +Não gostaria de uma versão de Python na qual eu fosse moralmente obrigado a adicionar dicas de tipo o tempo todo. +Realmente acho que dicas de tipo têm seu lugar, mas há muitas ocasiões em que elas não valem a pena, +e é maravilhoso que possamos escolher usá-las.footnote:[Do vídeo no Youtube, +«_Type Hints by Guido van Rossum (March 2015)_» [.small]#[fpy.li/8-39]#. +A citação começa em «13'40"» [.small]#[fpy.li/8-40]#. +Editei levemente a transcrição para facilitar a leitura.] +____ + + +=== Para saber mais + +Bernát Gábor escreveu((("functions, type hints in", "further reading on")))((("type hints (type annotations)", "further reading on"))) em +«_The state of type hints in Python_» [.small]#[fpy.li/8-41]#: + +[quote] +____ +Dicas de tipo devem ser usadas sempre que vale a pena escrever testes unitários. +____ + +Sou um grande fã de testes, mas também escrevo muito código exploratório. +Quando estou explorando, testes e dicas de tipo não ajudam. +São um entrave. + +Esse post do Gábor é uma das melhores introduções a dicas de tipo em Python que já encontrei, junto com o texto de Geir Arne Hjelle, +«_Python Type Checking (Guide)_» [.small]#[fpy.li/8-42]#. +«_Hypermodern Python Chapter 4: Typing_» [.small]#[fpy.li/8-43]#, +de Claudio Jolowicz, é uma introdução mais curta que também fala de validação de checagem de tipo durante a execução. + +Para uma abordagem mais aprofundada, a «documentação do Mypy» [.small]#[fpy.li/8-44]# +é a melhor fonte. +Ela é útil independentemente do checador de tipos que você esteja usando, pois tem páginas de tutorial e de referência sobre tipagem em Python em geral - não apenas sobre o próprio Mypy. + +Lá você também encontrará uma conveniente +«página de referência (ou _cheat sheet)» [.small]#[fpy.li/8-45]# +e uma página muito útil sobre +«problemas comuns e suas soluções» [.small]#[fpy.li/8-46]# . + +A documentação do módulo «`typing`» [.small]#[fpy.li/4a]# é uma boa referência rápida, mas não entra em muitos detalhes. + +A «_PEP 483—The Theory of Type Hints_» [.small]#[fpy.li/pep483]# inclui uma explicação aprofundada sobre variância, usando `Callable` para ilustrar a contravariância. +As referências definitivas são as PEP relacionadas a tipagem. +Já existem mais de 20 delas. +O público-alvo das PEPs são os _core developers_ (mantenedores da linguagem em si) e o Steering Council de Python, +então elas pressupõem muito conhecimento prévio, e não são uma leitura leve. + +Como já mencionado, o «Capítulo 15» [.small]#[vol.2, fpy.li/15]# cobre outros tópicos sobre tipagem, e a +«Seção 15.10» [.small]#[vol.2, fpy.li/5p]# traz referências adicionais, +incluindo uma tabela com a longa lista das PEPs +sobre tipagem aprovadas ou em discussão até o final de 2021. + +«_Awesome Python Typing_» [.small]#[fpy.li/8-47]# é uma ótima coleção de links para ferramentas e referências. + +<<< +[[type_hints_in_def_soapbox]] +.Ponto de vista +**** + +[role="soapbox-title"] +**Apenas Pedale** + +[quote, Grant Petersen, Just Ride: A Radically Practical Guide to Riding Your Bike (Apenas Pedale: Um Guia Radicalmente Prático sobre o Uso de sua Bicicleta) (Workman Publishing)] +____ +Esqueça((("functions, type hints in", "Soapbox discussion", id="FTHsoap08")))((("type hints (type annotations)", "Soapbox discussion", id="THsoag08"))) as desconfortáveis bicicletas ultraleves, as malhas brilhantes, os sapatos desajeitados que se prendem a pedais minúsculos, o esforço de quilômetros intermináveis. +Em vez disso, faça como você fazia quando era criança—suba na sua bicicleta e descubra o puro prazer de pedalar. +____ + + +Se((("Soapbox sidebars", "type hints (type annotations)", id="SStypehints08"))) programar não é sua profissão principal, mas uma ferramenta útil no seu trabalho ou algo que você faz para aprender, experimentar e se divertir, você provavelmente não precisa de dicas de tipo mais que a maioria dos ciclistas precisa de sapatos com solas rígidas e presilhas metálicas. + +Apenas programe. + +[role="soapbox-title"] +**Duck Typing FTW** + +A tipagem pato((("Soapbox sidebars", "duck typing")))((("duck typing"))) +encaixa bem no meu cérebro, e a tipagem pato estática é um bom meio termo, +permitindo checagem estática de tipo sem perder muito da flexibilidade que +alguns sistemas de tipagem nominal dificultam ou até impedem. + +Antes da PEP 544, toda essa ideia de dicas de tipo me parecia completamente não-pythônica. +Fiquei muito feliz quando vi `typing.Protocol` surgir em Python. +Ele traz equilíbrio para a Força. +**** + +<<< +**** +[role="soapbox-title"] +**O Efeito Cognitivo da Tipagem** + +Eu me preocupo com o efeito que as dicas de tipo terão sobre o estilo de programação em Python. + +Concordo que usuários da maioria das APIs se beneficiam de dicas de tipo. +Mas Python me atraiu—entre outras razões—porque oferece funções tão poderosas +que substituem APIs inteiras, e podemos escrever nós mesmos funções poderosas similares. +Considere a função nativa «`max()`» [.small]#[fpy.li/8-48]#. +Ela é poderosa, mas é fácil de entender. +Porém, vou mostrar na «Seção 15.2.1» [.small]#[vol.2, fpy.li/5q]# que são necessárias +14 linhas de dicas de tipo para anotar corretamente essa função—sem contar a definição de um `typing.Protocol` +e algumas definições de `TypeVar` para sustentar aquelas dicas de tipo. + +Receio que a adoção de dicas de tipo em bibliotecas desencoraje programadores +de oferecer funções flexíveis como `max` no futuro. + +De acordo com o verbete em inglês na Wikipedia, «relatividade linguística» [.small]#[fpy.li/8-49]#—ou a hipótese Sapir–Whorf—é +um "princípio alegando que a estrutura de uma linguagem afeta a visão de mundo ou a cognição de seus falantes." + +A Wikipedia continua: + +* A versão _forte_ diz que a linguagem _determina_ o pensamento, +e que categorias linguísticas limitam e determinam as categorias cognitivas. +* A versão _fraca_ diz que as categorias linguísticas e seu uso +apenas _influenciam_ o pensamento e as decisões. + +Linguistas em geral concordam que a versão forte é falsa, +mas há evidência empírica apoiando a versão fraca. + +Não conheço estudos específicos com linguagens de programação, +mas elas tiveram grande impacto sobre a forma como eu abordo problemas. +A primeira linguagem de programação que usei profissionalmente foi o Applesoft BASIC, +na era dos computadores de 8 bits. +Recursão não era diretamente suportada pelo BASIC. +Você teria que gerenciar uma pilha na unha para implementar um algoritmo recursivo. +Então eu nunca pensava em usar algoritmos ou estruturas de dados recursivos. +Eu sabia, em algum nível conceitual, que tais coisas existiam, +mas elas não eram parte de meu arsenal de técnicas de resolução de problemas. + +Décadas mais tarde, quando aprendi Elixir, +gostei de resolver problemas com recursão e usei essa técnica além da conta—até +descobrir que muitas das minhas soluções seriam mais simples se eu +usasse funções existentes nos módulos `Enum` e `Stream` do Elixir. +Aprendi que o código idiomático de aplicações em Elixir raramente contém chamadas recursivas +explícitas—em vez disso, usam enums e streams que implementam recursão por trás dos panos. + +A relatividade linguística pode explicar a ideia recorrente (e também não provada) que aprender linguagens de programação diferentes torna alguém um programador melhor, especialmente quando as linguagens em questão suportam diferentes paradigmas de programação. +Praticar com Elixir me tornou mais propenso a aplicar padrões funcionais quando escrevo programas em Python ou Go. + +Agora voltando à Terra. + +O pacote _requests_ provavelmente teria uma API muito diferente se Kenneth Reitz tivesse decidido +anotar todas as suas funções (ou tivesse recebido ordens de seu chefe para fazê-lo). +Seu objetivo era escrever uma API que fosse fácil de usar, flexível e poderosa. +Ele conseguiu, dada a fantástica popularidade de _requests_: em maio de 2020, +ela estava em 4º lugar nas «_PyPI Stats_» [.small]#[fpy.li/8-50]#, com 2,6 milhões de downloads diários. +A 1ª era a _urllib3_, uma dependência de _requests_. + +Em 2017 os mantenedores de _requests_ «decidiram» [.small]#[fpy.li/8-51]# +não perder seu tempo escrevendo dicas de tipo. +Um deles, Cory Benfield, escreveu um e-mail dizendo: + +[quote] +____ +Acho que bibliotecas com APIs 'pythônicas' são as menos propensas a adotar esse sistema de tipagem, pois ele vai adicionar muito pouco valor a elas. +____ + +<<< +Naquela mensagem, Benfield incluiu esse exemplo extremo de uma tentativa de definição de tipo para o +argumento nomeado `files` em «`requests.request()`» [.small]#[fpy.li/8-53]#: + +---- +Optional[ + Union[ + Mapping[ + basestring, + Union[ + Tuple[basestring, Optional[Union[basestring, file]]], + Tuple[basestring, Optional[Union[basestring, file]], + Optional[basestring]], + Tuple[basestring, Optional[Union[basestring, file]], + Optional[basestring], Optional[Headers]] + ] + ], + Iterable[ + Tuple[ + basestring, + Union[ + Tuple[basestring, Optional[Union[basestring, file]]], + Tuple[basestring, Optional[Union[basestring, file]], + Optional[basestring]], + Tuple[basestring, Optional[Union[basestring, file]], + Optional[basestring], Optional[Headers]] + ] + ] + ] +] +---- + +E tudo isto ainda depende desta definição: + +---- +Headers = Union[ + Mapping[basestring, basestring], + Iterable[Tuple[basestring, basestring]], +] +---- + +<<< +Você acha que _requests_ seria como é se os mantenedores insistissem em ter uma cobertura de dicas de tipo de 100%? +_SQLAlchemy_ é outro pacote importante que não trabalha muito bem com dicas de tipo. + +O que torna essas bibliotecas fabulosas é incorporarem a natureza dinâmica de Python. + +Apesar das dicas de tipo trazerem benefícios, há também um preço a ser pago. + +Primeiro, há o investimento significativo de aprender como o sistema de tipos funciona. +Esse é um preço alto, cobrado uma vez. + +Mas há também um custo recorrente, eterno. + +Perdemos parte do poder expressivo de Python se insistimos que tudo precisa estar sob a checagem de tipos. +Recursos excelentes estão além da capacidade de compreensão dos checadores de tipos, +por exemplo o desempacotamento de argumentos: `config(**settings)`. + +Se quiser ter uma chamada como `config(**settings)` checada quanto ao tipo, +precisa explicitar cada argumento. +Isso me traz lembranças de programas em Turbo Pascal, que escrevi 35 anos atrás. + +Bibliotecas que usam metaprogramação são difíceis ou impossíveis de anotar. +Claro que a metaprogramação pode ser mal usada, +mas isso também é algo que torna muitos pacotes de Python divertidos de usar. + +Se dicas de tipo se tornarem obrigatórias sem exceções, +por uma decisão gerencial em grandes empresas, +aposto que logo veremos pessoas usando geração de código +para reduzir a verbosidade em programas Python, +uma prática comum com linguagens menos dinâmicas. + +Para alguns projetos e contextos, dicas de tipo simplesmente não fazem sentido. +Mesmo em contextos onde elas podem ser úteis, +não são úteis o tempo todo. +Qualquer política razoável sobre o uso de dicas de tipo precisa conter exceções. + +<<< +Alan Kay, que inventou o termo "programação orientada a objetos" e +depois recebeu o prêmio Turing, certa vez disse: + +[quote] +____ +Algumas pessoas são completamente religiosas no que diz respeito a sistemas de tipo, +e como um matemático eu adoro a ideia de sistemas de tipos, +mas ninguém até agora inventou um que tenha alcance o suficiente.footnote:[Fonte: +«_A Conversation with Alan Kay_» [.small]#[fpy.li/8-54]#.] +____ + +Obrigado, Guido, pela tipagem opcional. +Vamos usá-la como foi pensada, e não tentar anotar tudo em conformidade estrita com +um estilo de programação que se parece com Java 1.5.((("", startref="SStypehints08"))) + +[role="soapbox-title"] +**Generics ou specifics?** + +De((("Soapbox sidebars", "generic collections")))((("generic collections", +"Soapbox discussion"))) uma perspectiva de Python, +o uso do termo "genérico" na tipagem é invertido. +Os sentidos comuns do termo "genérico" são +"aplicável integralmente a um grupo ou uma classe" ou "sem uma marca distintiva." + +Considere `list` versus `list[str]`. +O primeiro tipo é genérico: a lista aceita qualquer objeto. +O segundo é específico: só aceita itens do tipo `str`. + +Por outro lado, o termo faz sentido em Java. +Antes de Java 1.5, todas as coleções de Java (exceto a mágica `array`) eram "specific": +só podiam conter referências a `Object`, +então era necessário converter os itens que saíam de uma coleção antes que eles pudessem ser usados. +Com Java 1.5, as coleções ganharam parâmetros de tipo, +e se tornaram "generic."((("", startref="THsoag08")))((("", startref="FTHsoap08"))) + +**** + +// para dar um respiro antes das notas +  diff --git a/vol1/titulos-vol1.adoc b/vol1/titulos-vol1.adoc new file mode 100644 index 00000000..ea53998b --- /dev/null +++ b/vol1/titulos-vol1.adoc @@ -0,0 +1,43 @@ +// Substituições para referências cruzadas a partir do volume I + +// Vol.1 +:vo_data_str: Volume 1: dados + funções +// Parte I +:pa_data: Parte I—Estruturas de dados +:ch_data_model: Cap. 1—O modelo de dados de Python +:ch_sequences: Cap. 2—Uma coleção de sequências +:ch_dicts_sets: Cap. 3—Dicionários e conjuntos +:ch_str_bytes: Cap. 4—Texto em Unicode versus Bytes +:ch_dataclass: Cap. 5—Fábricas de classes de dados +:ch_refs_mut_mem: Cap. 6—Referências, mutabilidade, e memória +// Parte II +:pa_func_obj_a: Parte II.a—Funções como objetos +:ch_func_objects: Cap. 7—Funções como objetos de primeira classe +:ch_type_hints_def: Cap. 8—Dicas de tipo em funções +// Vol.2 +:pa_func_obj_b: Parte II.b—Funções como objetos +:vo_func_cls: Volume 2: classes + protocolos +: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 +: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) +:ch_inheritance: Cap. 14—Herança: para o bem ou para o mal (vol. 2) +:ch_more_types: Cap. 15—Mais dicas de tipo (vol. 2) +:ch_op_overload: Cap. 16—Sobrecarga de operadores (vol. 2) +// Vol.3 +:vo_ctrl_meta: Volume 3: controle + metaprogramação +// Parte IV +:pa_ctrl_flow: Parte IV—Controle de fluxo +:ch_generators: Cap. 17—Iteradores, geradores e corrotinas clássicas (vol. 3) +:ch_with_match: Cap. 18—Instruções with, match, e blocos else (vol. 3) +:ch_concurrency_models: Cap. 19—Modelos de concorrência em Python (vol. 3) +:ch_executors: Cap. 20—Executores concorrentes (vol. 3) +:ch_async: Cap. 21—Programação assíncrona (vol. 3) +// Parte V +:pa_metaprog: Parte V—Metaprogramação +:ch_dynamic_attrs: Cap. 22—Atributos dinâmicos e propriedades (vol. 3) +:ch_descriptors: Cap. 23—Descritores de Atributos (vol. 3) +:ch_class_metaprog: Cap. 24—Metaprogramação de classes (vol. 3) \ No newline at end of file diff --git a/vol1/titulos-vol1.txt b/vol1/titulos-vol1.txt new file mode 100644 index 00000000..ef7a4879 --- /dev/null +++ b/vol1/titulos-vol1.txt @@ -0,0 +1,37 @@ + +Volume 1: dados + funções +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 +5. Fábricas de classes de dados +6. Referências, mutabilidade, e memória +Parte II.a. Funções como objetos +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 +9. Decoradores e Clausuras (vol. 2) +10. Padrões de projeto com funções (vol. 2) +Parte III. Classes e protocolos +11. Um objeto pythônico (vol. 2) +12. Métodos especiais para sequências (vol. 2) +13. Interfaces, protocolos, e ABCs (vol. 2) +14. Herança: para o bem ou para o mal (vol. 2) +15. Mais dicas de tipo (vol. 2) +16. Sobrecarga de operadores (vol. 2) + +Volume 3: controle + metaprogramação +Parte IV. Controle de fluxo +17. Iteradores, geradores e corrotinas clássicas (vol. 3) +18. Instruções with, match, e blocos else (vol. 3) +19. Modelos de concorrência em Python (vol. 3) +20. Executores concorrentes (vol. 3) +21. Programação assíncrona (vol. 3) + +Parte V. Metaprogramação +22. Atributos dinâmicos e propriedades (vol. 3) +23. Descritores de Atributos (vol. 3) +24. Metaprogramação de classes (vol. 3) \ No newline at end of file diff --git a/vol1/vol1-cor.adoc b/vol1/vol1-cor.adoc new file mode 100644 index 00000000..c7a14fb3 --- /dev/null +++ b/vol1/vol1-cor.adoc @@ -0,0 +1,65 @@ += Python Fluente, 2ª edição: volume 1: Dados e Funções +: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[] +include::titulos-vol1.adoc[] +:revisao: 15 + +include::Copyright-cor.adoc[] + +[dedication%notitle%discrete,toclevels=0] += Dedicatória +__Para Marta, com todo o meu amor.__ + +toc::[] + +include::Prefacio.adoc[] + +[[data_structures_part]] += Parte I: Estruturas de dados +:sectnums: + +include::cap01.adoc[] +include::cap02.adoc[] +include::cap03.adoc[] +include::cap04.adoc[] +include::cap05.adoc[] +include::cap06.adoc[] + +[[function_objects_part]] += Parte II.a: Funções como objetos + +[NOTE] +==== +Publicamos esta versão impressa de _Python Fluente, 2ª Edição_ +em três volumes, com oito capítulos por volume. + +A Parte II ficou dividida entre os Volumes 1 e 2. + +Os capítulos 7 e 8 estão neste Volume 1. + +A Parte II.b está no Volume 2, e também na Web: + +«Capítulo 9—Decoradores e Clausuras» [.small]#[fpy.li/9]# + +«Capítulo 10—Padrões de projeto com funções» [.small]#[fpy.li/10]#. +==== + + +include::cap07.adoc[] +include::cap08.adoc[] \ No newline at end of file diff --git a/vol1/vol1-pb.adoc b/vol1/vol1-pb.adoc new file mode 100644 index 00000000..cb53a230 --- /dev/null +++ b/vol1/vol1-pb.adoc @@ -0,0 +1,65 @@ += Python Fluente, 2ª edição: volume 1: Dados e Funções +: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[] +include::titulos-vol1.adoc[] +:revisao: 15 + +include::Copyright-pb.adoc[] + +[dedication%notitle%discrete,toclevels=0] += Dedicatória +__Para Marta, com todo o meu amor.__ + +toc::[] + +include::Prefacio.adoc[] + +[[data_structures_part]] += Parte I: Estruturas de dados +:sectnums: + +include::cap01.adoc[] +include::cap02.adoc[] +include::cap03.adoc[] +include::cap04.adoc[] +include::cap05.adoc[] +include::cap06.adoc[] + +[[function_objects_part]] += Parte II.a: Funções como objetos + +[NOTE] +==== +Publicamos esta versão impressa de _Python Fluente, 2ª Edição_ +em três volumes, com oito capítulos por volume. + +A Parte II ficou dividida entre os Volumes 1 e 2. + +Os capítulos 7 e 8 estão neste Volume 1. + +A Parte II.b está no Volume 2, e também na Web: + +«Capítulo 9—Decoradores e Clausuras» [.small]#[fpy.li/9]# + +«Capítulo 10—Padrões de projeto com funções» [.small]#[fpy.li/10]#. +==== + + +include::cap07.adoc[] +include::cap08.adoc[] \ No newline at end of file diff --git a/vol2/.gitignore b/vol2/.gitignore new file mode 100644 index 00000000..87e46c6b --- /dev/null +++ b/vol2/.gitignore @@ -0,0 +1 @@ +vol2.pdf \ No newline at end of file diff --git a/vol2/Copyright-cor.adoc b/vol2/Copyright-cor.adoc new file mode 100644 index 00000000..404d2455 --- /dev/null +++ b/vol2/Copyright-cor.adoc @@ -0,0 +1,49 @@ +[colophon%discrete%notitle%nonfacing,toclevels=0] += Copyright +:volume: 2-cor +: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 + +https://fpy.li/ccby[_Atribuição-NãoComercial-SemDerivações 4.0 Internacional_] + +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 + +1ª edição: 2015 + +2ª edição: 2022 + +Revisão: `pyfl2-vol2-cor-2026-03-23.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.; cor; 17 cm × 24 cm + + ISBN: 978-65-989778-2-5 + 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/Copyright-pb.adoc b/vol2/Copyright-pb.adoc new file mode 100644 index 00000000..2a74d244 --- /dev/null +++ b/vol2/Copyright-pb.adoc @@ -0,0 +1,49 @@ +[colophon%discrete%notitle%nonfacing,toclevels=0] += Copyright +:volume: 2-pb +: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 + +https://fpy.li/ccby[_Atribuição-NãoComercial-SemDerivações 4.0 Internacional_] + +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 + +1ª edição: 2015 + +2ª edição: 2022 + +Revisão: `pyfl2-vol2-pb-2026-03-23.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 00000000..3ba43b44 --- /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/blurb-contra-capa.md b/vol2/blurb-contra-capa.md new file mode 100644 index 00000000..0d9f703c --- /dev/null +++ b/vol2/blurb-contra-capa.md @@ -0,0 +1,17 @@ +PYTHON FLUENTE, 2ª Edição • Volume 2 + +Neste volume você vai explorar decoradores de funções, +clausuras, e padrões de projeto com funções de primeira classe. + +Daí mergulhamos na programação orientada a objetos. +Veremos como os métodos `__dunder__` +permitem criar objetos que integram naturalmente +com as instruções da linguagem e sua biblioteca padrão. + +Os exemplos incluem suporte a casamento de padrões, proteção de atributos, +herança simples e múltipla, interfaces, +classes abstratas, sobrecarga de operadores, +e anotações de tipo avançadas. + +Python Fluente é único em sua abrangência e profundidade, +sempre com exemplos práticos. diff --git a/vol2/cap09.adoc b/vol2/cap09.adoc new file mode 100644 index 00000000..184bd18e --- /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 _(closures)_ + +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> Este é 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 00000000..83c64cdc
--- /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 é útil, 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 00000000..3dc793f7
--- /dev/null
+++ b/vol2/cap11.adoc
@@ -0,0 +1,1553 @@
+[[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 de 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]]
+=== Uma `Vector2d` _hashable_
+
+Da((("Pythonic objects", "hashable Vector2d", id="POhash11")))((("Vector2d",
+"hashable", id="V2dhash11"))) forma como `Vector2d` está definida até agora,
+suas instâncias 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]]
+=== Apoio ao 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 é um erro:
+
+[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 de `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_sec]]
+=== 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 00000000..910bc6f0
--- /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`: uma sequência definida 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 devolve 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` e `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` e `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 é esgotado.
+<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 esgotado.
+
+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: formatação
+
+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 00000000..877cdbf4
--- /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 do 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 _https://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 despercebida
+====
+[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 de `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 recomendado 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 idiomatismos 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 00000000..d84724ee
--- /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 ao 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 biblioteca
+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` e `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ção 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 biblioteca 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 de 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 00000000..0e990842
--- /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]]
+=== A anotação `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'` e `'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
+`inspect.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 00000000..19b671da
--- /dev/null
+++ b/vol2/cap16.adoc
@@ -0,0 +1,1560 @@
+[[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 com esta fórmula:
+
+[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 com números exatos.
+
+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 de que instâncias de `Vector` são iteráveis, e o `+Vector.__init__+`
+recebe um argumento iterável, por isso as implementações de `+__neg__+` e
+`+__pos__+` ficaram 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 em `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
new file mode 100644
index 00000000..0a7c8004
--- /dev/null
+++ b/vol2/vol2-cor.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: 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: 8
+:revisao: 10
+
+include::Copyright-cor.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/vol2/vol2-pb.adoc b/vol2/vol2-pb.adoc
new file mode 100644
index 00000000..b3d9c88a
--- /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 00000000..332400d2
--- /dev/null
+++ b/vol3/Copyright-cor.adoc
@@ -0,0 +1,49 @@
+[colophon%discrete%notitle%nonfacing,toclevels=0]
+= Copyright
+:volume: 3-cor
+:isbn-pb: 978-65-989778-1-8
+:isbn-cor: 978-65-989778-6-3 
+
+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 +
+https://fpy.li/ccby[_Atribuição-NãoComercial-SemDerivações 4.0 Internacional_] +
+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 +
+1ª edição: 2015 +
+2ª edição: 2022 +
+Revisão: `pyfl2-vol3-cor-2026-03-23.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.
+        459 p.; il.; cor; 17 cm × 24 cm
+
+        ISBN: 978-65-989778-6-3
+        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/Copyright-pb.adoc b/vol3/Copyright-pb.adoc
new file mode 100644
index 00000000..c2b61566
--- /dev/null
+++ b/vol3/Copyright-pb.adoc
@@ -0,0 +1,49 @@
+[colophon%discrete%notitle%nonfacing,toclevels=0]
+= Copyright
+:volume: 3-pb
+:isbn-pb: 978-65-989778-1-8
+:isbn-cor: 978-65-989778-6-3 
+
+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 +
+https://fpy.li/ccby[_Atribuição-NãoComercial-SemDerivações 4.0 Internacional_] +
+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 +
+1ª edição: 2015 +
+2ª edição: 2022 +
+Revisão: `pyfl2-vol3-pb-2026-03-23.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.
+        459 p.; il.; 17 cm × 24 cm
+
+        ISBN: 978-65-989778-1-8
+        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 00000000..94aacf25
--- /dev/null
+++ b/vol3/README.md
@@ -0,0 +1,17 @@
+# 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 e 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/blurb-contra-capa.md b/vol3/blurb-contra-capa.md
new file mode 100644
index 00000000..2511dea4
--- /dev/null
+++ b/vol3/blurb-contra-capa.md
@@ -0,0 +1,5 @@
+PYTHON FLUENTE, 2ª Edição • Volume 3
+
+Começamos este volume estudando mecanismos de controle de fluxo característicos do Python como geradores e gerenciadores de contexto. Mais da metade desta parte é dedicada à programação concorrente com threads, processos e corrotinas assíncronas, comparando estas opções e explorando os padrões de arquitetura que viabilizam o uso de Python em escala.
+
+Os capítulos finais apresentam propriedades, atributos dinâmicos, descritores de atributos, decoradores de classes, e metaclasses. Não são técnicas para o dia-a-dia no desenvolvimento de aplicações, mas são essenciais para dominar e construir frameworks: o conhecimento avançado que você precisa para liderar times com Python.
diff --git a/vol3/cap17.adoc b/vol3/cap17.adoc
new file mode 100644
index 00000000..4c2e9938
--- /dev/null
+++ b/vol3/cap17.adoc
@@ -0,0 +1,2876 @@
+[[ch_generators]]
+== Iteradores, geradores e corrotinas clássicas
+:example-number: 0
+:figure-number: 0
+
+[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 de que estou usando
+abstrações que não são suficientemente poderosas—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.]
+
+____
+
+
+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:
+
+* O laço `for`
+* Compreensões de lista, dict e set
+* Atribuições com desempacotamento de tuplas
+* Criação de instâncias de coleções
+
+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 é 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 aproveitar 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 usados de formas
+muito diferentes e não devem ser misturadas
+
+
+=== Novidades neste capítulo
+
+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`.
+
+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.
+
+
+=== 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 https://fpy.li/1[«Capítulo 1»] (vol.1). Agora veremos exatamente
+por que isso acontece.
+
+O <> mostra uma classe `Sentence` que permite
+ler as palavras de um texto por índice.
+
+[[ex_sentence0]]
+.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` preserva 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 https://fpy.li/cb[«Seção 12.3»] (vol.2).
+
+
+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`
+====
+[source, python]
+----
+>>> s = Sentence('"The time has come," the Walrus said,')  # <1>
+>>> s
+Sentence('"The time ha... Walrus said,')  # <2>
+>>> for word in s:  # <3>
+...     print(word)
+The
+time
+has
+come
+the
+Walrus
+said
+>>> list(s)  # <4>
+['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:
+
+[source, python]
+----
+>>> s[0]
+'The'
+>>> s[5]
+'Walrus'
+>>> s[-1]
+'said'
+----
+
+Programadores Python sabem que sequências são iteráveis. Agora vamos descobrir
+exatamente o porquê disso.((("", startref="Isequence17")))((("",
+startref="seqpro17")))
+
+[[iter_func_sec]]
+=== Como a função `iter` devolve iteradores
+
+Para((("functions", "iter() function")))((("iterators", "iter() function",
+id="Iinterfun17")))((("iter() function", id="iterfunc17")))
+iterar sobre um objeto `x`, o interpretador Python invoca `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, a partir de `0` (zero).
+
+. 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.
+
+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 este método. A iteração automática via `+__getitem__+` existe para
+manter a compatibilidade retroativa, e pode desaparecer em algum momento—mas
+eu duvido que será removida no futuro.
+
+Como mencionado na https://fpy.li/cc[«Seção 13.4.1»] (vol.2), 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]
+----
+>>> class Spam:
+...     def __getitem__(self, i):
+...         print('->', i)
+...         raise IndexError()
+...
+>>> spam_can = Spam()
+>>> iter(spam_can)
+
+>>> list(spam_can)
+-> 0
+[]
+>>> from collections import abc
+>>> isinstance(spam_can, abc.Iterable)
+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.
+
+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 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 registrar como subclasse virtual,
+pois `abc.Iterable`
+implementa o `+__subclasshook__+`, como visto na https://fpy.li/cd[«Seção 13.5.8»] (vol.2).
+Demonstração:
+
+[source, python]
+----
+>>> class GooseSpam:
+...     def __iter__(self):
+...         pass
+...
+>>> from collections import abc
+>>> issubclass(GooseSpam, abc.Iterable)
+True
+>>> goose_spam_can = GooseSpam()
+>>> isinstance(goose_spam_can, abc.Iterable)
+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.
+
+====
+
+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 é bem explícita:
+`TypeError: 'C' object is not iterable` (o objeto 'C' não é
+iterável). Se você quiser fazer algo 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 erros.
+
+A função embutida `iter()` é usada mais pelo próprio Python do que em
+código que nós escrevemos.
+Vejamos outra forma de usá-la, menos 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. 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
+enquanto o valor `1` não é sorteado:
+
+[source, python]
+----
+>>> def d6():
+...     return randint(1, 6)
+...
+>>> d6_iter = iter(d6, 1)
+>>> d6_iter
+
+>>> for roll in d6_iter:
+...     print(roll)
+...
+4
+3
+6
+3
+----
+
+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 esgotado. Para recomeçar, é necessário reconstruir o iterador,
+invocando novamente `iter()`.
+
+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()` é construir um
+leitor bloco-a-bloco (_block reader_).
+Por exemplo, ler blocos de 64 bytes de um arquivo binário de banco de dados
+até que o final do arquivo seja atingido:
+
+____
+
+[source, python]
+----
+from functools import partial
+
+with open('mydata.db', 'rb') as f:
+    read_block = partial(f.read, 64)
+    for block in iter(read_block, b''):
+        process_block(block)
+----
+
+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")))
+
+
+=== 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:
+
+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.
+
+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]
+----
+>>> s = 'ABC'
+>>> for char in s:
+...     print(char)
+...
+A
+B
+C
+----
+
+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]
+----
+>>> s = 'ABC'
+>>> it = iter(s)  # <1>
+>>> while True:
+...     try:
+...         char = next(it)  # <2>
+...     except StopIteration:  # <3>
+...         del it  # <4>
+...         break  # <5>
+...     print(char)  # <6>
+A
+B
+C
+----
+<1> Cria um iterador `it` a partir de um iterável.
+<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 laço.
+<6> Exibe `char`. Esta variável continua existindo depois do laço.
+
+`StopIteration` sinaliza que o iterador esgotou.
+Esta exceção é tratada internamente pelo Python, dentro 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.
+
+`+__iter__+`:: Devolve `self`; assim o iterador pode ser
+usado quando um iterável é esperado. Por exemplo, em um laço `for`.
+
+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 <>.
+
+[[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[align="center",pdfwidth=9cm]
+
+O código-fonte de `collections.abc.Iterator` aparece no <>.
+
+[[abc_iterator_src]]
+.Classe `abc.Iterator`; extraído de https://fpy.li/17-5[__Lib/_collections_abc.py__]
+====
+[source, python]
+----
+class Iterator(Iterable):
+
+    __slots__ = ()
+
+    @abstractmethod
+    def __next__(self):
+        """Return the next item from the iterator.
+        When exhausted, raise StopIteration"""
+        raise StopIteration
+
+    def __iter__(self):
+        return self
+
+    @classmethod
+    def __subclasshook__(cls, C):  # <1>
+        if cls is Iterator:
+            return _check_methods(C, '__iter__', '__next__')  # <2>
+        return NotImplemented
+----
+====
+
+<1> `+__subclasshook__+` suporta a checagem de tipos estrutural com `isinstance`
+e `issubclass`. Vimos isso na https://fpy.li/cd[«Seção 13.5.8»] (vol.2).
+
+<2> `_check_methods` percorre o atributo `+__mro__+` da classe, para checar se
+os métodos estão implementados em alguma superclasse.
+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 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 detectar os atributos "__iter__" e "__next__".
+----
+
+Isto é 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__+`, este teste funciona mesmo quando a
+classe de `x` não é uma subclasse real ou virtual de `Iterator`.
+
+====
+
+Voltando à nossa classe `Sentence` no <>, usando o console de
+Python podemos ver claramente como o iterador é criado por `iter()` e
+consumido por `next()`:
+
+[source, python]
+----
+>>> s3 = Sentence('Life of Brian')  # <1>
+>>> it = iter(s3)  # <2>
+>>> it  # doctest: +ELLIPSIS
+
+>>> next(it)  # <3>
+'Life'
+>>> next(it)
+'of'
+>>> next(it)
+'Brian'
+>>> next(it)  # <4>
+Traceback (most recent call last):
+  ...
+StopIteration
+>>> list(it)  # <5>
+[]
+>>> list(iter(s3))  # <6>
+['Life', 'of', 'Brian']
+----
+<1> Cria uma sentença `s3` com três palavras.
+<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 esgotado, um iterador vai sempre lançar `StopIteration`,
+indicando que não há mais itens.
+<6> Para percorrer a sentença novamente, precisamos 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 precisar
+começar de novo, invoque `iter()` novamente no iterável que criou o
+iterador original. Invocar `iter()` no próprio iterador esgotado não funciona,
+pois—como já mencionado—a implementação de `+Iterator.__iter__+`
+apenas devolve `self`, e isso não reinicia o iterador.
+
+Esta interface minimalista faz sentido 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 este ótimo exemplo.]
+
+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 codar variações de `Sentence` que implementam `+__iter__+` para
+devolver iteradores.((("", startref="IOvie17")))((("", startref="IEvio17")))
+
+
+=== 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.
+
+
+==== Sentence versão #2: um iterador clássico
+
+A próxima implementação de `Sentence` segue a forma do padrão de projeto _Iterator_ clássico, do livro _Padrões de Projeto_.
+Observe que isso não é Python idiomático, como as refatorações seguintes deixarão claro.
+Mas é útil para mostrar a distinção entre uma coleção iterável e um iterador que trabalha com ela.
+
+A classe `Sentence` no <> é iterável por implementar o método especial `+__iter__+`,
+que cria e devolve um `SentenceIterator`. É assim que um iterável e um iterador se relacionam.
+
+[[ex_sentence1]]
+.sentence_iter.py: `Sentence` implementada usando o padrão _Iterator_
+====
+[source, python]
+----
+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`. Esta versão não tem `+__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` preserva 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`, levanta `StopIteration`.
+<7> Incrementa `self.index`.
+<8> Devolve a palavra.
+<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 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__+`.
+
+É 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 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 implementa 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 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:
+
+[quote]
+____
+Use o padrão Iterator para:
+
+* Acessar o conteúdo de um objeto agregado sem expor sua representação interna.
+
+* Suportar travessias múltiplas de objetos agregados.
+
+* 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 a partir de um mesmo objeto iterável, e cada
+iterador deve preservar 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 isto precisamos da classe
+`SentenceIterator` neste exemplo.
+
+Agora((("yield keyword", id="yielda17")))((("keywords", "yield keyword",
+id=Kyielda17"))) que sabemos como funciona o padrão _Iterator_
+clássico, veremos que não precisamos
+"escrever à mão" nenhuma classe de iterador em Python,
+graças à instrução `yield`, que foi inspirada pela linguagem
+https://fpy.li/17-7[_CLU_], criada por um time liderado por Barbara Liskov.
+
+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 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 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. 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 precisamos escrever uma classe iteradora!
+
+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
+<> é um objeto gerador, criado automaticamente quando o
+método `+__iter__+` é invocado, porque neste exemplo `+__iter__+` é uma função geradora.
+
+A seguir: uma explicação bem completa sobre geradores.
+
+
+==== 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, uma função geradora é
+uma fábrica de geradores.
+
+[role="man-height3"]
+[TIP]
+====
+
+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.]
+
+[[gen-func-ex-three-yield]]
+.Uma função geradora que produz três números((("generators", "examples of", id="genex17")))
+====
+[source, python]
+----
+>>> def gen_123():
+...     yield 1  # <1>
+...     yield 2
+...     yield 3
+...
+>>> gen_123  # doctest: +ELLIPSIS
+  # <2>
+>>> gen_123()   # doctest: +ELLIPSIS
+  # <3>
+>>> for i in gen_123():  # <4>
+...     print(i)
+1
+2
+3
+>>> g = gen_123()  # <5>
+>>> next(g)  # <6>
+1
+>>> next(g)
+2
+>>> next(g)
+3
+>>> next(g)  # <7>
+Traceback (most recent call last):
+  ...
+StopIteration
+----
+====
+
+<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 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 termina, 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 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" 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 o objeto gerador levantar
+`StopIteration`. Se você escrever `return x`
+na função geradora, é possível obter o valor de `x` embrulhado na
+exceção `StopIteration`, mas normalmente isso é feito usando a
+sintaxe `yield from`, como veremos na <>.
+
+====
+
+O <> torna mais explícita a interação entre um laço `for` e o corpo da função geradora.
+
+[[ex_gen_ab]]
+.Uma função geradora que exibe mensagens quando roda
+====
+[source, python]
+----
+>>> def gen_AB():
+...     print('start')
+...     yield 'A'          # <1>
+...     print('continue')
+...     yield 'B'          # <2>
+...     print('end.')      # <3>
+...
+>>> for c in gen_AB():     # <4>
+...     print('-->', c)    # <5>
+...
+start     <6>
+--> A     <7>
+continue  <8>
+--> B     <9>
+end.      <10>
+>>>       <11>
+----
+====
+
+<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.
+
+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.
+
+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, valendo-se de uma
+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 avaliação á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 são mais descritivos.]
+
+Até aqui, nossas implementações de `Sentence` não são preguiçosas,
+pois o `+__init__+` cria avidamente 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 deste trabalho será inútil se o usuário iterar apenas sobre as primeiras palavras.
+Se você está se perguntando 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 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.
+O código está no <>.
+
+[[ex_sentence3]]
+.sentence_gen2.py: `Sentence` implementada usando uma função geradora que invoca a função geradora `re.finditer`
+====
+[source, python]
+----
+include::../code/17-it-generator/sentence_gen2.py[tags=SENTENCE_GEN2]
+----
+====
+<1> Não é necessário manter uma lista `words`.
+<2> `finditer` cria um iterador sobre os termos encontrados com `RE_WORD` em `self.text`, produzindo instâncias de `MatchObject`.
+<3> `match.group()` extrai o texto da instância de `MatchObject`.
+
+Geradores são um ótimo atalho, mas o código pode ser ainda mais conciso com uma expressão geradora.
+
+
+==== Sentence versão #5: Expressão geradora preguiçosa
+
+Podemos substituir funções geradoras simples—como aquela na última
+classe `Sentence` (no <>)—por uma expressão geradora.
+Assim como uma compreensão de lista cria listas, uma expressão geradora cria objetos geradores.
+O <> compara o comportamento nos dois casos.
+
+[[ex_gen_ab_genexp]]
+.A função geradora `gen_AB` é usada primeiro por uma compreensão de lista, depois por uma expressão geradora
+====
+[source, python]
+----
+>>> def gen_AB():  # <1>
+...     print('start')
+...     yield 'A'
+...     print('continue')
+...     yield 'B'
+...     print('end.')
+...
+>>> res1 = [x*3 for x in gen_AB()]  # <2>
+start
+continue
+end.
+>>> for i in res1:  # <3>
+...     print('-->', i)
+...
+--> AAA
+--> BBB
+>>> res2 = (x*3 for x in gen_AB())  # <4>
+>>> res2
+ at 0x10063c240>
+>>> for i in res2:  # <5>
+...     print('-->', i)
+...
+start      # <6>
+--> AAA
+continue
+--> BBB
+end.
+----
+====
+<1> Esta é 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> Este 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 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 <>.
+
+[[ex_sentence4]]
+.sentence_genexp.py: `Sentence` implementada usando uma expressão geradora
+====
+[source, python]
+----
+include::../code/17-it-generator/sentence_genexp.py[tags=SENTENCE_GENEXP]
+----
+====
+
+A única diferença com o <> é o método `+__iter__+`, que aqui não é uma função geradora (ela não contém uma instrução `yield`) mas usa uma expressão geradora para criar um gerador e devolvê-lo. O resultado final é o mesmo: quem invoca `+__iter__+` recebe um objeto gerador.
+
+Expressões geradoras são "açúcar sintático": sempre podem ser substituídas por funções geradoras, mas às vezes são mais convenientes. A próxima seção trata do uso de expressões geradoras.((("", startref="Ilazy17")))((("", startref="Glazy17")))((("", startref="lazysen17")))
+
+
+=== Quando usar expressões geradoras
+
+Usei((("generators", "when to use generator expressions")))((("generator
+expressions (genexps)"))) expressões geradoras quando implementamos a
+classe `Vector` no Exemplo 16 do https://fpy.li/12[«Capítulo 12»] (vol.2). Estes
+métodos contêm expressões geradoras: `+__eq__+`, `+__hash__+`, `+__abs__+`,
+`angle`, `angles`, `format`, `+__add__+`, e `+__mul__+`.
+Em todos eles, uma compreensão de
+lista também funcionaria, usando mais memória para armazenar os
+valores da lista intermediária.
+
+No <>, vimos que uma expressão geradora é um atalho sintático para
+criar um gerador sem definir e invocar uma função. Por outro lado, funções
+geradoras são mais flexíveis: podemos programar uma lógica complexa, com
+várias instruções, e podemos até usá-las como _corrotinas_, como veremos na
+<>.
+
+Nos casos mais simples, uma expressão geradora é mais fácil de ler de relance, como mostra o exemplo de `Vector`.
+
+Minha regra básica para escolher qual sintaxe usar é simples: se a expressão geradora exige mais que um par de linhas, prefiro escrever uma função geradora, em nome da legibilidade.
+
+[TIP]
+.Dica de sintaxe
+====
+
+Quando uma expressão geradora é passada como único argumento a uma função ou construtor,
+não é necessário escrever um par de parênteses para
+invocar função e outro par ao redor da expressão geradora.
+Um único par é
+suficiente, como na invocação do construtor `Vector` no método `+__mul__+` do
+Exemplo 16 do https://fpy.li/12[«Capítulo 12»] (vol.2), reproduzido abaixo:
+
+[source, python]
+----
+def __mul__(self, scalar):
+    if isinstance(scalar, numbers.Real):
+        return Vector(n * scalar for n in self)
+    else:
+        return NotImplemented
+----
+
+Entretanto, se a invocação exigir mais argumentos após a expressão
+geradora, é preciso cercá-la com parênteses para evitar um
+`SyntaxError`.
+
+====
+
+Os exemplos de `Sentence` vistos até aqui mostram geradores fazendo o papel do padrão _Iterator_ clássico: obter itens de uma coleção.
+Mas podemos também usar geradores para produzir valores sem acessar uma estrutura de dados.
+A próxima seção mostra um exemplo.
+
+Mas antes, uma pequena discussão sobre os conceitos sobrepostos de _iterador_  e _gerador_.
+
+.Comparando iteradores e geradores
+****
+
+Na((("iterators", "versus generators", secondary-sortas="generators")))((("generators", "versus iterators", secondary-sortas="iterators"))) documentação e na base de código oficiais de Python, a terminologia em torno de iteradores e geradores é inconsistente e está em evolução.
+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 laço `for` ou outro mecanismo de iteração, ou chamando `next(it)` explicitamente no iterador—apesar desse uso explícito incomum.
+    Na prática, a maioria dos iteradores que usamos no Python são _geradores_.
+
+gerador::
+    Um iterador criado pelo compilador do Python.
+    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__+`, 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://fpy.li/99[_Glossário de Python_]
+introduziu recentemente o termo
+https://fpy.li/9a[«iterador gerador»]
+para se referir a objetos geradores criados por funções geradoras,
+enquanto o verbete para
+https://fpy.li/9b[«expressão geradora»] diz que ela devolve um "iterador".
+
+Mas, para o interpretador Python,
+os objetos devolvidos em ambos os casos são objetos geradores:
+
+[source, python]
+----
+>>> def g():
+...     yield 0
+...
+>>> g()
+
+>>> ge = (c for c in 'XYZ')
+>>> ge
+ at 0x10e936ce0>
+>>> type(g()), type(ge)
+(, )
+----
+
+****
+
+=== Um gerador de progressão aritmética
+
+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 é útil 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 veremos 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 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 os valores gerados.
+
+[[ap_class_demo]]
+.Demonstração de uma classe `ArithmeticProgression`
+====
+[source, python]
+----
+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`.
+O <> mostra a implementação da classe `ArithmeticProgression`.
+
+[[ex_ap_class]]
+.A classe `ArithmeticProgression`
+====
+[source, python]
+----
+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> Esta 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»].]
+
+<4> Para melhorar a legibilidade, o sinalizador `forever` será `True` se o atributo `self.end` for `None`, resultando em uma série ilimitada.
+
+<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 laço `while` terminar.
+
+<<<
+Na última linha do <>,
+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 revelam a diferença:
+
+[source, python]
+----
+>>> 100 * 1.1
+110.00000000000001
+>>> sum(1.1 for _ in range(100))
+109.99999999999982
+>>> 1000 * 1.1
+1100.0
+>>> sum(1.1 for _ in range(1000))
+1100.0000000000086
+----
+
+A classe `ArithmeticProgression` do <> 
+é outro exemplo do uso de uma função geradora para implementar o método especial `+__iter__+`.
+Agora, se o único objetivo de uma classe é criar um gerador pela implementação de `+__iter__+`,
+podemos substituir a classe inteira por uma função geradora.
+Afinal, uma função geradora é uma fábrica de geradores.
+
+O <> mostra uma função geradora chamada `aritprog_gen`, que
+realiza a mesma tarefa da `ArithmeticProgression`, com menos código. Se, em
+vez de chamar `ArithmeticProgression`, você chamar `aritprog_gen`, os testes no
+<> são todos bem-sucedidos.footnote:[O diretório
+__17-it-generator/__ no https://fpy.li/code[«repositório de código»] do _Python
+Fluente_ inclui doctests e um script, __aritprog_runner.py__, que roda os testes
+contra todas as variações dos scripts __aritprog*.py__.]
+
+
+[[ex_ap_genfunc1]]
+.a função geradora `aritprog_gen`
+====
+[source, python]
+----
+include::../code/17-it-generator/aritprog_v2.py[tags=ARITPROG_GENFUNC]
+----
+====
+
+<<<
+O <> é elegante, mas lembre-se: há muitos geradores prontos para uso na biblioteca padrão, e a próxima seção vai mostrar uma implementação mais curta, usando o módulo `itertools`.
+
+
+[[ap_itertools_sec]]
+==== Progressão aritmética com itertools
+
+O((("itertools module"))) módulo `itertools` no Python 3.10 contém 20 funções geradoras, que podem ser combinadas de várias maneiras interessantes.
+
+Por exemplo, a função `itertools.count` devolve um gerador que produz números. Sem argumentos, ele produz uma série de inteiros começando de `0`. Mas você pode fornecer os valores opcionais `start` e `step`, para obter um resultado similar ao das nossas funções `aritprog_gen`:
+
+[source, python]
+----
+>>> import itertools
+>>> gen = itertools.count(1, .5)
+>>> next(gen)
+1
+>>> next(gen)
+1.5
+>>> next(gen)
+2.0
+>>> next(gen)
+2.5
+----
+
+
+[WARNING]
+====
+`itertools.count` nunca para, então se você chamar `list(count())`, Python vai tentar criar uma `list` que preencheria todos os chips de memória já fabricados.
+Na prática, sua máquina vai ficar muito mal-humorada bem antes da chamada fracassar.
+====
+
+Por outro lado, temos também a função `itertools.takewhile`: ela devolve um
+gerador que consome outro gerador e para quando um predicado resulta falso.
+Então podemos combinar os dois e escrever o seguinte:
+
+<<<
+[source, python]
+----
+>>> gen = itertools.takewhile(lambda n: n < 3, itertools.count(1, .5))
+>>> list(gen)
+[1, 1.5, 2.0, 2.5]
+----
+
+Aproveitando `takewhile` e `count`, o <> fica mais conciso.
+
+[[ex_almost_aritprog]]
+.aritprog_v3.py: funciona como as funções `aritprog_gen` anteriores
+====
+[source, python]
+----
+include::../code/17-it-generator/aritprog_v3.py[tags=ARITPROG_ITERTOOLS]
+----
+====
+
+Observe que  `aritprog_gen` no <> não é uma função geradora: não há um `yield` em seu corpo.
+Mas ela devolve um gerador, exatamente como faz uma função geradora.
+
+A lição do <> é:
+ao implementar geradores, veja 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")))
+
+
+[[stdlib_generators_sec]]
+=== Funções geradoras na biblioteca padrão
+
+A((("generators", "generator functions in Python standard library",
+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, agregados ou reordenados.
+
+==== Funções geradoras de seleção
+
+[[filter_fig_2]]
+.Funções geradoras de seleção criam geradores que selecionam itens conforme algum critério.
+image::../images/diag-filter.png[align="center",pdfwidth=8cm]
+
+((("filtering generator functions")))O primeiro grupo são funções geradoras de
+seleção: elas devolvem um gerador que produz um subconjunto dos itens do
+iterável de entrada, sem mudar os itens em si.
+
+O exemplo mais conhecido é a função embutida `filter`:
+
+`filter(predicate, it)`::
+    Aplica `predicate` para cada item de `iterable`, produzindo o item se
+    `predicate(item)` resulta 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.
+
+A seção de console no <> demonstra o uso dos geradores de seleção.
+
+[[demo_filter_genfunc]]
+.Exemplos de funções geradoras de seleção
+====
+[source, python]
+----
+>>> def vowel(c):
+...     return c.lower() in 'aeiou'
+...
+>>> list(filter(vowel, 'Aardvark'))
+['A', 'a', 'a']
+>>> import itertools
+>>> list(itertools.filterfalse(vowel, 'Aardvark'))
+['r', 'd', 'v', 'r', 'k']
+>>> list(itertools.dropwhile(vowel, 'Aardvark'))
+['r', 'd', 'v', 'a', 'r', 'k']
+>>> list(itertools.takewhile(vowel, 'Aardvark'))
+['A', 'a']
+>>> list(itertools.compress('Aardvark', (1, 0, 1, 1, 0, 1)))
+['A', 'r', 'd', 'a']
+>>> list(itertools.islice('Aardvark', 4))
+['A', 'a', 'r', 'd']
+>>> list(itertools.islice('Aardvark', 4, 7))
+['v', 'a', 'r']
+>>> list(itertools.islice('Aardvark', 1, 7, 2))
+['a', 'd', 'a']
+----
+====
+
+
+[[filter_genfunc_tbl]]
+.`itertools`: funções geradoras de seleção
+[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).
+|=======================
+
+<<<
+==== Funções geradoras de transformação
+
+[[map_fig_2]]
+.Funções geradoras de transformação criam geradores que aplicam uma função aos itens da entrada, produzindo itens transformados.
+image::../images/diag-map.png[align="center",pdfwidth=8cm]
+
+O((("mappings", "mapping generator functions"))) grupo seguinte contém os
+geradores de transformação. Estes geradores 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 esgotado.
+
+[[mapping_genfunc_tbl]]
+.Funções geradoras de transformação (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 transformação:
+
+[[mapping_genfunc_itertools_tbl]]
+.`itertools`: funções geradoras de transformação
+[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`.
+
+[[demo_accumulate_genfunc]]
+.Exemplos das funções geradoras de `itertools.accumulate`
+====
+[source, python]
+----
+>>> 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>
+[5, 4, 2, 2, 2, 2, 2, 0, 0, 0]
+>>> list(itertools.accumulate(sample, max))  # <3>
+[5, 5, 5, 8, 8, 8, 8, 8, 9, 9]
+>>> import operator
+>>> list(itertools.accumulate(sample, operator.mul))  # <4>
+[5, 20, 40, 320, 2240, 13440, 40320, 0, 0, 0]
+>>> list(itertools.accumulate(range(1, 11), operator.mul))
+[1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]  # <5>
+----
+====
+<1> Soma acumulada.
+<2> Mínimo corrente.
+<3> Máximo corrente.
+<4> Produto acumulado.
+<5> Fatoriais de `1!` a `10!`.
+
+As demais funções de transformação são demonstradas no <>.
+
+[[demo_mapping_genfunc]]
+.Exemplos de funções geradoras de transformação
+====
+[source, python]
+----
+>>> list(enumerate('albatroz', 1))  # <1>
+[(1, 'a'), (2, 'l'), (3, 'b'), (4, 'a'), (5, 't'), (6, 'r'), (7, 'o'), (8, 'z')]
+>>> import operator
+>>> list(map(operator.mul, range(11), range(11)))  # <2>
+[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
+>>> list(map(operator.mul, range(11), [2, 4, 8]))  # <3>
+[0, 4, 16]
+>>> list(map(lambda a, b: (a, b), range(11), [2, 4, 8]))  # <4>
+[(0, 2), (1, 4), (2, 8)]
+>>> import itertools
+>>> list(itertools.starmap(operator.mul, enumerate('albatroz', 1)))  # <5>
+['a', 'll', 'bbb', 'aaaa', 'ttttt', 'rrrrrr', 'ooooooo', 'zzzzzzzz']
+>>> sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
+>>> list(itertools.starmap(lambda a, b: b / a,
+...     enumerate(itertools.accumulate(sample), 1)))  # <6>
+[5.0, 4.5, 3.6666666666666665, 4.75, 5.2, 5.333333333333333,
+5.0, 4.375, 4.888888888888889, 4.5]
+----
+====
+<1> Número de letras na palavra, começando por `1`.
+<2> Os quadrados dos inteiros de `0` a `10`.
+<3> Multiplicando os números de dois iteráveis em paralelo; os resultados cessam quando o iterável menor termina.
+<4> Isso é o que faz a função embutida `zip`.
+<5> Repete cada letra na palavra de acordo com a posição da letra na palavra, começando por `1`.
+<6> Média corrente.
+
+
+==== Funções geradoras de mesclagem
+
+A seguir temos o grupo de geradores de mesclagem (_merge_).
+Elas mesclam os itens de vários iteráveis de entrada.
+
+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 é esgotado, 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`.
+
+<<<
+[[merging_genfunc_tbl]]
+.`itertools`: funções geradoras que mesclam os iteráveis de entrada
+[options="header", cols="7,13"]
+|===================
+|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 esgotado,  preenchendo os itens ausentes com o `fillvalue`
+|===================
+
+O <> demonstra o uso destas funções geradoras. 
+Lembre-se de que o nome `zip` refere-se ao zíper (ou fecho-éclair) e não tem relação com a compressão de dados.
+
+
+[[demo_merging_genfunc]]
+.Exemplos de funções geradoras de fusão
+====
+[source, python]
+----
+>>> list(itertools.chain('ABC', range(2)))  # <1>
+['A', 'B', 'C', 0, 1]
+>>> list(itertools.chain(enumerate('ABC')))  # <2>
+[(0, 'A'), (1, 'B'), (2, 'C')]
+>>> list(itertools.chain.from_iterable(enumerate('ABC')))  # <3>
+[0, 'A', 1, 'B', 2, 'C']
+>>> list(zip('ABC', range(5), [10, 20, 30, 40]))  # <4>
+[('A', 0, 10), ('B', 1, 20), ('C', 2, 30)]
+>>> list(itertools.zip_longest('ABC', range(5)))  # <5>
+[('A', 0), ('B', 1), ('C', 2), (None, 3), (None, 4)]
+>>> list(itertools.zip_longest('ABC', range(5), fillvalue='?'))  # <6>
+[('A', 0), ('B', 1), ('C', 2), ('?', 3), ('?', 4)]
+----
+====
+[role="pagebreak-before less_space"]
+<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 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 até o fim, preenchendo as tuplas de saída com `None` nas posições correspondentes aos iteráveis esgotados antes do iterável mais longo.
+<6> O argumento nomeado `fillvalue` especifica um valor de preenchimento customizado.
+
+O gerador `itertools.product` é uma forma preguiçosa de gerar produtos cartesianos.
+Na https://fpy.li/ce[«Seção 2.3.3»] (vol.1), criamos produtos cartesianos de modo ávido
+usando compreensões de lista com mais de uma instrução `for`.
+Expressões geradoras com várias 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`
+====
+[source, python]
+----
+>>> list(itertools.product('ABC', range(2)))  # <1>
+[('A', 0), ('A', 1), ('B', 0), ('B', 1), ('C', 0), ('C', 1)]
+>>> suits = 'spades hearts diamonds clubs'.split()
+>>> list(itertools.product('AK', suits))  # <2>
+[('A', 'spades'), ('A', 'hearts'), ('A', 'diamonds'), ('A', 'clubs'),
+('K', 'spades'), ('K', 'hearts'), ('K', 'diamonds'), ('K', 'clubs')]
+>>> list(itertools.product('ABC'))  # <3>
+[('A',), ('B',), ('C',)]
+>>> list(itertools.product('ABC', repeat=2))  # <4>
+[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'B'),
+('B', 'C'), ('C', 'A'), ('C', 'B'), ('C', 'C')]
+>>> list(itertools.product(range(2), repeat=3))
+[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0),
+(1, 0, 1), (1, 1, 0), (1, 1, 1)]
+>>> rows = itertools.product('AB', range(2), repeat=2)
+>>> for row in rows: print(row)
+...
+('A', 0, 'A', 0)
+('A', 0, 'A', 1)
+('A', 0, 'B', 0)
+('A', 0, 'B', 1)
+('A', 1, 'A', 0)
+('A', 1, 'A', 1)
+('A', 1, 'B', 0)
+('A', 1, 'B', 1)
+('B', 0, 'A', 0)
+('B', 0, 'A', 1)
+('B', 0, 'B', 0)
+('B', 0, 'B', 1)
+('B', 1, 'A', 0)
+('B', 1, 'A', 1)
+('B', 1, 'B', 0)
+('B', 1, 'B', 1)
+----
+====
+<1> O produto cartesiano de uma `str` com três caracteres e um `range` com dois inteiros produz seis tuplas (porque `3 * 2` é `6`).
+<2> O produto de duas cartas altas (`'AK'`) e quatro naipes é uma série de oito tuplas.
+<3> Dado um único iterável, `product` produz uma série de tuplas de um elemento—muito pouco útil.
+<4> O argumento nomeado `repeat=N` diz à função para consumir cada iterável de entrada `N` vezes.
+
+Outras((("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]]
+.`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 com `out_len` itens a partir dos itens produzidos por `it`
+|`combinations_with_replacement( +
+it, out_len)`|Produz combinações com `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`, somando `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, indefinidamente
+|`pairwise(it)`|Produz pares sobrepostos sucessivos, obtidos do iterável de entrada (novidade do 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 item repetidamente, indefinidamente, a menos que um número de `times` (vezes) seja especificado
+|====================
+
+As funções `count` e `repeat` de `itertools` devolvem geradores que conjuram itens do nada:
+elas não consomem itens de um iterável de entrada.
+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`.
+
+[[demo_count_repeat_genfunc]]
+.`count`, `cycle`, `pairwise`, e `repeat`
+====
+[source, python]
+----
+>>> ct = itertools.count()  # <1>
+>>> next(ct)  # <2>
+0
+>>> next(ct), next(ct), next(ct)  # <3>
+(1, 2, 3)
+>>> list(itertools.islice(itertools.count(1, .3), 3))  # <4>
+[1, 1.3, 1.6]
+>>> cy = itertools.cycle('ABC')  # <5>
+>>> next(cy)
+'A'
+>>> list(itertools.islice(cy, 7))  # <6>
+['B', 'C', 'A', 'B', 'C', 'A', 'B']
+>>> list(itertools.pairwise(range(7)))  # <7>
+[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
+>>> rp = itertools.repeat(7)  # <8>
+>>> next(rp), next(rp)
+(7, 7)
+>>> list(itertools.repeat(8, 4))  # <9>
+[8, 8, 8, 8]
+>>> list(map(operator.mul, range(11), itertools.repeat(5)))  # <10>
+[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
+----
+====
+<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 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 um gerador `repeat` que 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`.
+
+As funções geradoras `combinations`, `combinations_with_replacement` e
+`permutations`--juntamente com  `product`—são chamadas _geradoras combinatórias_
+na https://fpy.li/9c[«documentação do `itertools`»]. Também há uma
+relação muito próxima entre `itertools.product` e o restante das funções
+_combinatórias_, como mostra o <>.
+
+[[demo_conbinatoric_genfunc]]
+.Funções geradoras combinatórias produzem múltiplos valores para cada item de entrada
+====
+[source, python]
+----
+>>> list(itertools.combinations('ABC', 2))  # <1>
+[('A', 'B'), ('A', 'C'), ('B', 'C')]
+>>> list(itertools.combinations_with_replacement('ABC', 2))  # <2>
+[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'B'), ('B', 'C'), ('C', 'C')]
+>>> list(itertools.permutations('ABC', 2))  # <3>
+[('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
+>>> list(itertools.product('ABC', repeat=2))  # <4>
+[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'B'), ('B', 'C'),
+('C', 'A'), ('C', 'B'), ('C', 'C')]
+----
+====
+<1> Todas as combinações com `len()==2` a partir dos itens em `'ABC'`; a ordem dos itens nas tuplas geradas é irrelevante (elas poderiam ser conjuntos).
+<2> Todas as combinações com `len()==2` a partir dos itens em `'ABC'`, incluindo combinações com itens repetidos.
+<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`).
+
+==== Funções geradoras de rearranjo
+
+As últimas funções geradoras que vamos examinar nessa seção
+produzem 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 único gerador tratado neste capítulo que não
+aceita qualquer iterável como entrada, apenas sequências. Faz sentido: como
+`reversed` 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.
+
+[[expanding_genfunc_tbl2]]
+.Funções geradoras de rearranjo
+[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
+|=====================
+
+O <> demonstra o uso de `itertools.groupby` e da função embutida `reversed`. Observe que `itertools.groupby` assume que o iterável de entrada está ordenado pelo critério de agrupamento, ou que pelo menos os itens estejam agrupados por aquele critério—mesmo que não estejam completamente ordenados.
+
+[[groupby_fig_2]]
+.A função geradora `groupby` produz vários geradores, agrupando os itens da entrada segundo algum critério.
+image::../images/diag-groupby.png[align="center",pdfwidth=8cm]
+
+O revisor técnico Miroslav Šedivý sugeriu esse caso de uso:
+você pode ordenar objetos `datetime` em ordem cronológica, e então `groupby` por dia da semana, para obter o grupo com os dados de segunda-feira, seguidos pelos dados de terça, etc., e então da segunda (da semana seguinte) novamente, e assim por diante.
+
+[[demo_groupby_reversed_genfunc]]
+.`itertools.groupby`
+====
+[source, python]
+----
+>>> list(itertools.groupby('LLLLAAGGG'))  # <1>
+[('L', ),
+('A', ),
+('G', )]
+>>> for char, group in itertools.groupby('LLLLAAAGG'):  # <2>
+...     print(char, '->', list(group))
+...
+L -> ['L', 'L', 'L', 'L']
+A -> ['A', 'A',]
+G -> ['G', 'G', 'G']
+>>> animals = ['duck', 'eagle', 'rat', 'giraffe', 'bear',
+...            'bat', 'dolphin', 'shark', 'lion']
+>>> animals.sort(key=len)  # <3>
+>>> animals
+['rat', 'bat', 'duck', 'bear', 'lion', 'eagle', 'shark',
+'giraffe', 'dolphin']
+>>> for length, group in itertools.groupby(animals, len):  # <4>
+...     print(length, '->', list(group))
+...
+3 -> ['rat', 'bat']
+4 -> ['duck', 'bear', 'lion']
+5 -> ['eagle', 'shark']
+7 -> ['giraffe', 'dolphin']
+>>> for length, group in itertools.groupby(reversed(animals), len): # <5>
+...     print(length, '->', list(group))
+...
+7 -> ['dolphin', 'giraffe']
+5 -> ['shark', 'eagle']
+4 -> ['lion', 'bear', 'duck']
+3 -> ['bat', 'rat']
+>>>
+----
+====
+<1> `groupby` produz tuplas de `(key, group_generator)`.
+<2> Tratar geradores `groupby` envolve iteração aninhada: neste caso, o laço `for` externo e o construtor de `list` interno.
+<3> Ordena `animals` pelo tamanho de cada string.
+<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.
+Estes geradores podem ser consumidos de forma independente, como mostra o
+<>.
+
+[[demo_tee_genfunc]]
+.`itertools.tee` produz múltiplos geradores, cada um produzindo todos os itens do gerador de entrada
+====
+[source, python]
+----
+>>> list(itertools.tee('ABC'))
+[, ]
+>>> g1, g2 = itertools.tee('ABC')
+>>> next(g1)
+'A'
+>>> next(g2)
+'A'
+>>> next(g2)
+'B'
+>>> list(g1)
+['B', 'C']
+>>> list(g2)
+['C']
+>>> list(zip(*itertools.tee('ABC')))
+[('A', 'A'), ('B', 'B'), ('C', 'C')]
+----
+====
+
+Observe que vários exemplos nesta seção usam combinações de funções geradoras.
+Esta é uma característica poderosa destas funções:
+como recebem geradores como argumentos e devolvem geradores como resultado,
+elas podem ser combinadas de muitas formas diferentes.
+
+Vamos agora revisar outro grupo de funções da biblioteca padrão que lidam com iteráveis.((("", startref="Ggenfunc17")))
+
+<<<
+[[iterable_reducing_sec]]
+=== Funções de redução de iteráveis
+
+[[reduce_fig_2]]
+.A função `reduce` aplica uma função aos sucessivos itens da entrada, gerando um único resultado acumulado.
+image::../images/diag-reduce.png[align="center",pdfwidth=8cm]
+
+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".
+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 ao 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 simplificarem os casos de uso mais comuns de `functools.reduce`.
+Vimos uma explicação mais aprofundada sobre `functools.reduce` na https://fpy.li/59[«Seção 12.7»] (vol.2).
+
+Nos casos de `all` e `any`, há uma importante otimização não suportada por `functools.reduce`:
+`all` e `any` implementam o retorno por 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", 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
+|`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
+|`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`.
+
+[[all_any_demo]]
+.Resultados de `all` e `any` para algumas sequências
+====
+[source, python]
+----
+>>> all([1, 2, 3])
+True
+>>> all([1, 0, 3])
+False
+>>> all([])
+True
+>>> any([1, 2, 3])
+True
+>>> any([1, 0, 3])
+True
+>>> any([0, 0.0])
+False
+>>> any([])
+False
+>>> g = (n for n in [0, 0.0, 7, 8])
+>>> any(g)  # <1>
+True
+>>> next(g)  # <2>
+8
+----
+====
+<1> `any` iterou sobre `g` até `g` produzir `7`; neste momento `any` parou e devolveu `True`.
+<2> É por isso que `8` ainda restava.
+
+Outra função embutida que recebe um iterável e devolve outra coisa é `sorted`.
+Diferente de `reversed`, que é uma função geradora, `sorted` cria e devolve uma nova `list`.
+Afinal, cada um dos itens no iterável de entrada precisa ser lido para que todos possam ser ordenados, e a ordenação acontece em uma `list`; `sorted` então apenas devolve aquela `list` após terminar seu processamento.
+Menciono `sorted` aqui porque ela consome um iterável arbitrário.
+
+Claro, `sorted` e as funções de redução só funcionam com iteráveis que terminam.
+Caso contrário, eles seguirão consumindo itens e nunca devolverão um resultado.
+
+[NOTE]
+====
+Se você chegou até aqui, já viu o conteúdo mais importante e útil deste capítulo.
+As seções restantes tratam de recursos avançados de geradores,
+que a maioria de nós não vê ou precisa com muita frequência,
+tal como a instrução `yield from` e as corrotinas clássicas.
+
+Há também seções sobre dicas de tipo para iteráveis, iteradores e corrotinas clássicas.
+====
+
+A sintaxe `yield from` fornece uma nova forma de combinar geradores. É nosso próximo assunto.((("", startref="Greduc17")))((("", startref="Ireduc17")))((("", startref="redfunc17")))
+
+
+[[yield_from_sec0]]
+=== Subgeradores com `yield from`
+
+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 laço `for` quando um gerador precisava produzir valores de outro gerador:
+
+[source, python]
+----
+>>> def sub_gen():
+...     yield 1.1
+...     yield 1.2
+...
+>>> def gen():
+...     yield 1
+...     for i in sub_gen():
+...         yield i
+...     yield 2
+...
+>>> for x in gen():
+...     print(x)
+...
+1
+1.1
+1.2
+2
+----
+
+Podemos obter o mesmo resultado usando `yield from`, como se vê no <>.
+
+[[ex_simple_yield_from]]
+.Experimentando `yield from`
+====
+[source, python]
+----
+>>> def sub_gen():
+...     yield 1.1
+...     yield 1.2
+...
+>>> def gen():
+...     yield 1
+...     yield from sub_gen()
+...     yield 2
+...
+>>> for x in gen():
+...     print(x)
+...
+1
+1.1
+1.2
+2
+----
+====
+
+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 esgotar.
+Os valores produzidos por `sub_gen` passam através de `gen` diretamente para o laço `for` do cliente.
+Enquanto isso, `gen` está suspenso e não pode ver os valores que passam por ele.
+`gen` continua apenas quando `sub_gen` termina.
+
+Quando o subgerador contém uma instrução `return` com um valor, aquele valor pode ser capturado pelo gerador delegante, com o uso de `yield from` como parte de uma expressão.
+Veja a demonstração no <>.
+
+[[ex_simple_yield_from_return]]
+.`yield from` recebe o valor devolvido pelo subgerador
+====
+[source, python]
+----
+>>> def sub_gen():
+...     yield 1.1
+...     yield 1.2
+...     return 'Done!'
+...
+>>> def gen():
+...     yield 1
+...     result = yield from sub_gen()
+...     print('<--', result)
+...     yield 2
+...
+>>> for x in gen():
+...     print(x)
+...
+1
+1.1
+1.2
+<-- Done!
+2
+----
+====
+
+Agora que já vimos o básico sobre `yield from`, vamos estudar alguns exemplos simples mas práticos de sua utilização.
+
+[[reinventing_chain_sec]]
+==== Reinventando `chain`
+
+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.
+A maioria das funções de `itertools` são escritas em C, incluindo `chain`.
+Abaixo está uma implementação caseira de `chain`, com laços `for`
+aninhados, em Python:
+
+[source, python]
+----
+>>> def chain(*iterables):
+...     for it in iterables:
+...         for i in it:
+...             yield i
+...
+>>> s = 'ABC'
+>>> r = range(3)
+>>> list(chain(s, r))
+['A', 'B', 'C', 0, 1, 2]
+----
+
+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]
+----
+>>> def chain(*iterables):
+...     for i in iterables:
+...         yield from i
+...
+>>> list(chain(s, t))
+['A', 'B', 'C', 0, 1, 2]
+----
+
+O uso de `yield from` neste exemplo está correto, e o código é mais legível, mas parece açúcar sintático, com pouco ganho real.
+Vamos então desenvolver um exemplo mais interessante.
+
+
+[[traversing_tree_sec]]
+==== Percorrendo uma árvore
+
+Nessa((("tree structures, traversing", id="treetravers17"))) seção, veremos `yield from` em um script para percorrer uma estrutura de árvore.
+Vou desenvolvê-lo passo a passo, em _baby steps_ (passinhos de bebê).
+
+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 passinho será exibir
+o nível zero.
+
+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
+====
+[source, python]
+----
+include::../code/17-it-generator/tree/step0/tree.py[]
+----
+====
+
+A saída do <> tem apenas uma linha:
+
+[source]
+----
+BaseException
+----
+
+O próximo pequeno passo nos leva ao nível 1.
+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:
+
+[source]
+----
+$ python3 tree.py
+BaseException
+    Exception
+    GeneratorExit
+    SystemExit
+    KeyboardInterrupt
+----
+
+O <> produz a saída acima.
+
+[[ex_tree_step1]]
+.tree/step1/tree.py: produz o nome da classe raiz e das subclasses diretas
+====
+[source, python]
+----
+include::../code/17-it-generator/tree/step1/tree.py[]
+----
+====
+<1> Para suportar a saída indentada, produz o nome da classe e seu nível na hierarquia.
+<2> Usa o método especial `+__subclasses__+` para obter uma lista de subclasses.
+<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 classe raiz, processando as subclasses 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 então delega para `sub_tree`
+====
+[source, python]
+----
+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 ao gerador `tree` ...
+<3> ... e são recebidos aqui diretamente.
+
+Seguindo o método de _baby steps_, apresento o código mais simples que consigo imaginar para chegar ao nível 2.
+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
+====
+[source, python]
+----
+include::../code/17-it-generator/tree/step3/tree.py[]
+----
+====
+
+Este é o resultado da execução de _step3/tree.py_, do <>:
+
+[source]
+----
+$ python3 tree.py
+BaseException
+    Exception
+        TypeError
+        StopAsyncIteration
+        StopIteration
+        ImportError
+        OSError
+        EOFError
+        RuntimeError
+        NameError
+        AttributeError
+        SyntaxError
+        LookupError
+        ValueError
+        AssertionError
+        ArithmeticError
+        SystemError
+        ReferenceError
+        MemoryError
+        BufferError
+        Warning
+    GeneratorExit
+    SystemExit
+    KeyboardInterrupt
+----
+
+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 laço `for` aninhado.
+
+Não há novidades no resto do programa, então o <> mostra apenas o gerador `sub_tree`.
+
+<<<
+[[ex_tree_step4]]
+.O gerador `sub_tree` de _tree/step4/tree.py_
+====
+[source, python]
+----
+include::../code/17-it-generator/tree/step4/tree.py[tags=SUB_TREE]
+----
+====
+
+Há um padrão claro no <>.
+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 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 <>.
+
+[[ex_tree_step5]]
+.tree/step5/tree.py: a `sub_tree` recursiva vai tão longe quanto a memória permitir
+====
+[source, python]
+----
+include::../code/17-it-generator/tree/step5/tree.py[]
+----
+====
+
+O <> pode percorrer árvores de qualquer profundidade,
+limitado apenas pelo limite de recursão de Python.
+O limite default permite 1.000 funções pendentes.
+
+Qualquer bom tutorial sobre recursão enfatizará a importância de ter um caso base,
+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 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.
+
+O <> funciona como planejado,
+mas podemos fazê-lo mais conciso recordando o padrão que observamos quando
+alcançamos o nível 3 (no <>):
+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 laço aninhado por `yield from`.
+Agora podemos fundir `tree` e `sub_tree` em um único gerador.
+O <> é o último passo deste exemplo.
+
+[[ex_tree_step6]]
+.tree/step6/tree.py: chamadas recursivas de `tree` passam um argumento `level` incrementado
+====
+[source, python]
+----
+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 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")))
+
+[[generic_iterable_types_sec]]
+=== Tipos iteráveis genéricos
+
+A((("generators", "generic iterable types")))((("iterators", "generic iterable types"))) biblioteca padrão de Python contém muitas funções que aceitam argumentos iteráveis.
+Em seu código, tais funções podem ser anotadas como a função `zip_replace` no 
+Exemplo 15 do https://fpy.li/8[«Capítulo 8»] (vol.1),
+usando `collections.abc.Iterable`. Veja o <>.
+
+[[replacer_iterable_ex]]
+.replacer.py devolve um iterador de tuplas de strings
+====
+[source, python]
+----
+include::../code/08-def-type-hints/replacer.py[tags=ZIP_REPLACE]
+----
+====
+<1> Define um apelido (_alias_) de tipo; isso não é obrigatório, mas torna a próxima dica de tipo mais legível.
+Desde o Python 3.10, a variável `FromTo` deve ter uma dica de tipo `typing.TypeAlias`, para esclarecer a razão para esta atribuição: +
+`FromTo: TypeAlias = tuple[str, str]`
+<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 o famoso gerador de Fibonacci, anotado.
+
+[[fibo_gen_annot_ex]]
+._fibo_gen.py_: `fibonacci` devolve um gerador de inteiros
+====
+[source, python]
+----
+include::../code/17-it-generator/fibo_gen.py[]
+----
+====
+
+Observe que o tipo `Iterator` é usado para geradores criados como funções com `yield`,
+bem como para iteradores escritos "à 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 redundante 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`.
+
+[[iter_gen_type_ex]]
+.itergentype.py: duas formas de anotar iteradores
+====
+[source, python]
+----
+include::../code/17-it-generator/iter_gen_type.py[]
+----
+====
+<1> Uma expressão geradora que produz palavras reservadas de Python com menos de `5` caracteres.
+<2> O Mypy infere: `typing.Generator[builtins.str*, None, None]`.footnote:[Na versão 0.910, a versão mais recente disponível quando escrevi este capítulo), o Mypy ainda utiliza os tipos descontinuados de `typing`.]
+<3> Isso também produz strings, mas acrescentei uma dica de tipo explícita.
+<4> Tipo revelado: `typing.Iterator[builtins.str]`.
+
+`abc.Iterator[str]` é _consistente-com_ `abc.Generator[str, None, None]`,
+assim o Mypy não reporta erros na checagem de tipos no <>.
+
+`Iterator[T]` é um atalho para `Generator[T, None, None]`.
+Ambas as anotações significam "um gerador que produz itens do tipo `T`, mas não consome ou devolve valores."
+Geradores capazes de consumir e devolver valores são corrotinas, nosso próximo tópico.
+
+
+[[classic_coroutines_sec]]
+=== Corrotinas clássicas
+
+[NOTE]
+====
+
+A https://fpy.li/pep342[_PEP 342—Coroutines via Enhanced Generators_]
+(Corrotinas via geradores aprimorados) introduziu o método `.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
+use uma terminologia inconsistente para se referir a geradores usados como
+corrotinas, obrigando-me a adotar o qualificador "corrotina clássica", para
+diferenciá-las das novas "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 pela biblioteca `asyncio`.
+
+====
+
+Entender as corrotinas clássicas((("coroutines", "understanding classic"))) no
+Python é mais confuso porque elas são, na verdade, geradores usados 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 https://fpy.li/cf[«Seção 2.4»] (vol.1) que é possível usar instâncias de
+`tuple` como registros ou como sequências imutáveis. Quando usadas como um
+registro, espera-se que uma tupla tenha um número fixo de itens, e cada
+item pode ter um tipo diferente. Quando usadas como listas imutáveis, uma tupla
+pode ter qualquer tamanho, e espera-se que todos os itens sejam do mesmo tipo.
+Por isso, há duas formas de anotar tuplas com dicas de tipo:
+
+[source, python]
+----
+# Um registro de cidade, como nome, país e população:
+city: tuple[str, str, int]
+
+# Uma sequência imutável de nomes de domínios:
+domains: tuple[str, ...]
+----
+
+Algo similar ocorre com geradores.
+Eles são normalmente usadas como iteradores, mas podem também ser usados 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 geradores e corrotinas em Python são tão diferentes que há duas formas de escrever dicas de tipo para elas:
+
+[source, python]
+----
+# A variável `readings` pode ser vinculada a um iterador
+# ou a um objeto gerador que produz itens `float`:
+readings: Iterator[float]
+
+# A variável `sim_taxi` pode ser vinculada a uma corrotina
+# representando um táxi em uma simulação de eventos discretos.
+# Ela produz eventos, recebe um `float` de data/hora, e devolve
+# o número de viagens realizadas durante a simulação:
+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 geradores são mais frequentemente usados como iteradores.
+
+A
+https://fpy.li/9f[«documentação do módulo `typing`»]
+descreve assim os parâmetros de tipo formais de `Generator`:
+
+[source, python]
+----
+Generator[YieldType, SendType, ReturnType]
+----
+
+O `SendType` só é relevante quando o gerador é usado como uma corrotina.
+Aquele parâmetro é o tipo de `x` na chamada `gen.send(x)`.
+É 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 fazem as funções comuns.
+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)`.
+
+O tipo `Generator` tem os mesmos parâmetros de tipo de
+https://fpy.li/typecoro[`typing.Coroutine`]:
+
+[source, python]
+----
+Coroutine[YieldType, SendType, ReturnType]
+----
+
+A
+https://fpy.li/typecoro[documentação de `typing.Coroutine`]
+diz literalmente:
+"A variância e a ordem das variáveis de tipo correspondem às de `Generator`."
+Mas `typing.Coroutine` (descontinuada)
+e `collections.abc.Coroutine` (genérica a partir de Python 3.9)
+foram projetadas para anotar apenas corrotinas nativas, e não corrotinas clássicas.
+Se você quiser usar dicas de tipo em corrotinas clássicas,
+terá que anotá-las como
+`Generator[YieldType, SendType, ReturnType]`.
+
+David Beazley criou algumas das melhores palestras e algumas das oficinas mais abrangentes sobre corrotinas clássicas.
+No https://fpy.li/17-18[material de seu curso na PyCon 2009] há um slide chamado
+_Keeping It Straight_ ("Cada coisa em seu lugar"), onde se lê:
+
+____
+* Geradoras produzem dados para iteração
+* Corrotinas são consumidoras de dados
+* Para não explodir seu cérebro, não misture os dois conceitos
+* Corrotinas não têm relação com iteração
+* Nota: Há uma forma de fazer `yield` produzir um valor em uma corrotina,
+mas isso não está ligado à iteração.footnote:[Slide 33, _Keeping It Straight_ em
+https://fpy.li/17-18[_A Curious Course on Coroutines and Concurrency_]
+(Um Curioso Curso sobre Corrotinas e Concorrência).]
+____
+
+
+Vamos ver agora como as corrotinas clássicas funcionam.
+
+
+==== Exemplo: corrotina para computar uma média móvel
+
+Quando((("coroutines", "computing running averages", id="CRavg17")))((("running averages, computing",
+id="runavg17")))((("averages, computing", id="avg17")))
+discutimos clausuras no https://fpy.li/9[«Capítulo 9»] (vol.2), estudamos objetos para
+computar uma média móvel. Na https://fpy.li/c8[«Seção 9.6»],
+o Exemplo 7 mostra uma classe e o Exemplo 8
+apresenta uma função de ordem superior devolvendo uma
+função que preserva em sua clausura as variáveis `total` e `count` entre
+uma invocação e a próxima.
+O <> mostra como fazer o mesmo com uma
+corrotina.footnote:[Este exemplo foi inspirado por um trecho enviado por Jacob
+Holm à lista Python-ideas, em uma mensagem intitulada
+https://fpy.li/17-20[_Yield-From: Finalization guarantees_] (Yield-From:
+Garantias de finalização). Algumas variantes aparecem mais tarde na mesma
+thread, e Holm dá mais explicações na
+https://fpy.li/17-21[«mensagem 003912»].]
+
+[[ex_coroaverager]]
+.coroaverager.py: corrotina para computar uma média móvel
+====
+[source, python]
+----
+include::../code/17-it-generator/coroaverager.py[tags=CORO_AVERAGER]
+----
+====
+
+<1> Esta função devolve um gerador que produz valores `float`, aceita valores
+`float` via `.send()`, e não devolve um valor útil ao retornar.footnote:[Na
+verdade, ela nunca retorna, a menos que uma exceção interrompa o laço. O Mypy
+0.910 aceita tanto `None` quanto `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 analisar completamente o
+código da corrotina, pelo menos em sua versão 0.910.]
+
+<2> Este laço infinito significa que a corrotina continuará produzindo médias
+enquanto o código cliente enviar valores.
+
+<3> A instrução `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:
+não precisamos usar atributos de uma instância ou uma clausura para preservar o contexto
+enquanto a corrotina está suspensa, esperando pelo próximo `.send()`.
+Por isso as corrotinas são substitutas atraentes para _callbacks_
+em programação assíncrona—elas preservam o estado local entre ativações.
+
+O <> executa doctests mostrando a corrotina `averager` em operação.
+
+[[ex_coroaverager_test]]
+.coroaverager.py: doctests da corrotina de média móvel do <>
+====
+[source, python]
+----
+include::../code/17-it-generator/coroaverager.py[tags=CORO_AVERAGER_TEST]
+----
+====
+<1> Cria o objeto corrotina.
+<2> Inicializa a corrotina. Isso produz o valor inicial de `average`: 0.0.
+<3> Agora estamos conversando: cada chamada a `.send()` produz a média atual.
+
+No <>, a chamada `next(coro_avg)` faz a corrotina avançar até o `yield`, produzindo o valor inicial de `average`.
+Também é possível inicializar a corrotina chamando `coro_avg.send(None)`—na verdade é isso que a função embutida `next()` faz.
+Mas você não pode enviar qualquer valor diferente de `None`,
+pois a corrotina só pode aceitar um valor enviado quando está suspensa, em uma linha de `yield`.
+Invocar `next()` ou `.send(None)` para avançar até o primeiro `yield` é conhecido como 
+_priming the coroutine_ (preparando a corrotina).
+
+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 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 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]]
+.coroaverager.py: continuando de <>
+====
+[source, python]
+----
+include::../code/17-it-generator/coroaverager.py[tags=CORO_AVERAGER_TEST_CONT]
+----
+====
+<1> `coro_avg` é a instância criada no <>.
+<2> O método `.close()` gera uma exceção `GeneratorExit` na expressão `yield` suspensa.
+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 levanta `StopIteration`.
+
+Além do método `.send()`, a
+https://fpy.li/pep342[_PEP 342—Coroutines via Enhanced Generators_]
+(Corrotinas via geradores aprimorados) também criou 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]]
+==== Devolvendo um valor a partir de uma corrotina
+
+Vamos((("coroutines", "returning values from", id="Cvalue17"))) agora estudar outra corrotina para computar uma média.
+Esta versão não produzirá resultados parciais.
+Em vez disso, ela devolve uma tupla com o número de termos e a média.
+Dividi a listagem em duas partes, no <> e no <>.
+
+[[ex_returning_averager_top]]
+.coroaverager2.py: a primeira parte do arquivo
+====
+[source, python]
+----
+include::../code/17-it-generator/coroaverager2.py[tags=RETURNING_AVERAGER_TOP]
+----
+====
+
+<1> A corrotina `averager2` no <> vai devolver uma
+instância de `Result`.
+
+<2> `Result` é, na verdade, uma subclasse de `tuple`, que tem um método
+`.count()`, que não preciso neste exemplo. O comentário `# type: ignore` evita que o Mypy
+reclame sobre a redeclaração do atributo `count`.footnote:[Considerei renomear o
+atributo, mas `count` é o melhor nome para a variável local na corrotina, e é o
+nome que usei para essa variável em exemplos similares ao longo do livro, então
+faz sentido usar o mesmo nome no atributo de `Result`. Não hesito em usar `# type:
+ignore` para evitar as limitações e os aborrecimentos de checadores de tipos
+estáticos, quando se submeter à ferramenta tornaria o código pior ou
+desnecessariamente complicado.]
+
+<3> Uma classe para criar um valor sentinela com um `+__repr__+` legível.
+
+<4> O valor sentinela que vou usar para fazer a corrotina parar de coletar dados
+e devolver um resultado.
+
+<5> Vou usar esse apelido de tipo para o segundo parâmetro de tipo devolvido
+pela corrotina `Generator`, o parâmetro `SendType`.
+
+A definição de `SendType` também funciona no Python 3.10 mas, se não for
+necessário suportar versões mais antigas, é melhor escrever a anotação assim,
+após importar `TypeAlias` de `typing`:
+
+[source, python]
+----
+SendType: TypeAlias = float | Sentinel
+----
+
+Usar `|` em vez de `typing.Union` é tão conciso e legível que eu provavelmente não criaria aquele apelido de tipo. Em vez disso, escreveria a assinatura de `averager2` assim:
+
+[source, python]
+----
+def averager2(verbose:bool=False) -> Generator[None, float|Sentinel, Result]:
+----
+
+Vamos agora estudar o código da corrotina em si no <>.
+
+<<<
+[[ex_returning_averager_coro]]
+.coroaverager2.py: corrotina que devolve um resultado final
+====
+[source, python]
+----
+include::../code/17-it-generator/coroaverager2.py[tags=RETURNING_AVERAGER]
+----
+====
+<1> Para essa corrotina, o tipo produzido ('YieldType') é `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, pois elas consomem dados. Esta linha produz `None`, mas recebe um `term` na próxima invocação de `.send(term)`.
+<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 (eu não poderia somar um `float` a um objeto que pode ser um `float` ou um `Sentinel`).
+<5> Esta linha só será alcançada se um `Sentinel` for enviado para a corrotina.
+
+Vamos ver agora como podemos usar essa corrotina,
+começando por um exemplo simples, que não devolve um resultado (no <>).
+
+<<<
+[[ex_coro_averager2_demo_1]]
+.coroaverager2.py: doctest mostrando `.cancel()`
+====
+[source, python]
+----
+include::../code/17-it-generator/coroaverager2.py[tags=RETURNING_AVERAGER_DEMO_1]
+----
+====
+<1> Lembre-se de que `averager2` não produz resultados parciais. Ela produz `None`, que o console de Python omite.
+<2> Invocar `.close()` nessa corrotina a faz parar, mas não devolve um resultado, pois a exceção `GeneratorExit` é gerada na linha `yield` da corrotina, então a instrução `return` nunca é alcançada.
+
+Vamos então fazê-la funcionar, no <>.
+
+[[ex_coro_averager2_demo_2]]
+.coroaverager2.py: doctest mostrando `StopIteration` com um `Result`
+====
+[source, python]
+----
+include::../code/17-it-generator/coroaverager2.py[tags=RETURNING_AVERAGER_DEMO_2]
+----
+====
+<1> Enviar um valor `Sentinel()` 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!
+
+<<<
+Esta ideia de "contrabandear" o valor devolvido para fora de uma corrotina
+dentro de uma exceção `StopIteration` é um _hack_. Entretanto, este _hack_ é parte da
+https://fpy.li/pep342[_PEP 342—Coroutines via Enhanced Generators_],
+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
+_A Referência da Linguagem Python_.
+
+
+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]]
+.coroaverager2.py: doctest mostrando `StopIteration` com um `Result`
+====
+[source, python]
+----
+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 ao tratar a exceção `StopIteration`, que
+sinaliza 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 o gerador for acionado.
+
+<3> Devolve o resultado. Ele também estará encapsulado em `StopIteration`.
+
+<4> Cria o objeto corrotina delegante.
+
+<5> Este 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
+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. Acrescente o tratamento de exceções e o método de corrotina `.throw()`,
+e os exemplos ficam ainda mais complicados. Não vou tratar de `.throw()` nesse
+livro porque—como `.send()`—ele só é útil para controlar corrotinas
+"manualmente", e não recomendo fazer isso, a menos que você esteja criando um
+novo framework baseado em corrotinas do zero .
+
+[NOTE]
+====
+Se tiver interesse em uma discussão mais profunda sobre corrotinas clássicas—incluindo o método `.throw()`—por favor veja
+https://fpy.li/oldcoro[_Classic Coroutines_] no site que acompanha o livro,
+_https://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, qualquer trabalho produtivo com corrotinas exige o apoio de um framework especializado.
+É 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 as corrotinas nativas mais visíveis no código,
+um grande benefício por si só.
+Internamente, as corrotinas nativas usam `await` em vez de `yield from` para delegar a outras corrotinas.
+O <> é todo sobre este assunto.
+
+Vamos agora encerrar o capítulo com uma seção alucinante sobre co-variância e contravariância em dicas de tipo para corrotinas.((("", startref="Cvalue17")))
+
+
+[[generic_classic_coroutine_types_sec]]
+==== Dicas de tipo genéricas para corrotinas clássicas
+
+Na((("coroutines", "generic type hints for")))((("type hints (type annotations)", "generic type hints for coroutines")))
+https://fpy.li/cg[«Seção 15.7.4.3»] (vol.2),
+mencionei `typing.Generator` como um dos poucos tipos da biblioteca padrão com um parâmetro de tipo contravariante.
+Agora que estudamos as corrotinas clássicas, estamos prontos para entender este tipo genérico.
+
+É assim que `typing.Generator` era https://fpy.li/17-25[declarado]
+no módulo _typing.py_ de Python 3.6:footnote:[Desde o Python 3.7,
+`typing.Generator` e outros tipos que correspondem a ABCs em `collections.abc` foram refatorados e encapsulados nas ABCs correspondentes, então seus parâmetros genéricos não são visíveis no código-fonte de _typing.py_ . Por isso estou fazendo referência ao código-fonte do Python 3.6 aqui.]
+
+[source, python]
+----
+T_co = TypeVar('T_co', covariant=True)
+V_co = TypeVar('V_co', covariant=True)
+T_contra = TypeVar('T_contra', contravariant=True)
+
+# muitas linhas omitidas
+
+class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co],
+                extra=_G_base):
+----
+
+Esta declaração de tipo genérico significa que uma dica de tipo de `Generator` requer aqueles três parâmetros de tipo que vimos antes:
+
+[source, python]
+----
+my_coro : Generator[YieldType, SendType, ReturnType]
+----
+
+Nas declarações das variáveis de tipo nos parâmetros formais, vemos que `YieldType` e `ReturnType` são covariantes, mas `SendType` é contravariante.
+Para entender a razão disso, considere que `YieldType` e `ReturnType` são tipos de "saída".
+Ambos descrevem dados que saem do objeto corrotina—isto é, o objeto gerador quando usado como um objeto corrotina..
+
+Faz sentido que esses parâmetros sejam covariantes, pois qualquer código esperando uma corrotina que produz números de ponto flutuante pode usar uma corrotina que produz inteiros.
+Por isso `Generator` é covariante em seu parâmetro `YieldType`.
+O mesmo raciocínio se aplica ao parâmetro `ReturnType`—também covariante.
+
+Usando a notação introduzida na https://fpy.li/ch[«Seção 15.7.4.2»] (vol.2), a covariância do primeiro e do terceiro parâmetros pode ser expressa pelos símbolos `:>` apontando para a mesma direção:
+
+[source]
+----
+                       float :> int
+Generator[float, Any, float] :> Generator[int, Any, int]
+----
+
+`YieldType` e `ReturnType` são exemplos da primeira regra apresentada na https://fpy.li/cj[«Seção 15.7.4.4»] (vol.2):
+
+[quote]
+____
+. Se um parâmetro de tipo formal define um tipo para dados que saem de um objeto, ele pode ser covariante.
+____
+
+Por outro lado, `SendType` é um parâmetro de "entrada":
+ele é o tipo do argumento `value` do método `.send(value)` do objeto corrotina.
+Código cliente que precisa enviar números de ponto flutuante para uma corrotina não pode usar uma corrotina que receba `int` como o `SendType`, porque `float` não é um subtipo de `int`.
+Em outras palavras, `float` não é _consistente-com_ `int`.
+Mas o cliente pode usar uma corrotina que tenha `complex` como `SendType`,
+pois `float` é um subtipo de `complex`,
+e portanto `float` é _consistente-com_ `complex`.
+
+A notação `:>` torna visível a contravariância do segundo parâmetro:
+
+[source]
+----
+                     float :> int
+Generator[Any, float, Any] <: Generator[Any, int, Any]
+----
+
+Este é um exemplo da segunda regra geral da variância:
+
+[quote]
+____
+[start=2]
+. Se um parâmetro de tipo formal define um tipo para dados que entram em um objeto após sua construção inicial, ele pode ser contravariante.
+____
+
+Esta alegre discussão sobre variância encerra o capítulo mais longo do livro.
+
+<<<
+=== Resumo do capítulo
+
+A iteração((("iterators", "overview of")))((("generators", "overview of")))((("coroutines", "overview of"))) está integrada tão profundamente à linguagem que gosto de dizer que Python _groks_ iteráveis.footnote:[De acordo com o https://fpy.li/17-26[_Jargon file_], "to grok" não é meramente entender algo, mas absorver de uma forma que "aquilo se torna parte de você, parte de sua identidade".]
+A integração do padrão _Iterator_ na semântica de Python é um exemplo perfeito de como padrões de projeto não são aplicáveis a todas as linguagens de programação.
+No Python, um _Iterator_ clássico, implementado "à mão", como no <>, não tem qualquer função prática, exceto como exemplo didático.
+
+Neste capítulo, criamos algumas versões de uma classe para iterar sobre palavras individuais em arquivos de texto (que podem ser muito grandes).
+Vimos como Python usa a função embutida `iter()` para criar iteradores a partir de objetos similares a sequências.
+Criamos um iterador clássico como uma classe com `+__next__()+`, e então usamos geradores,
+para tornar a classe `Sentence` mais concisa e legível a cada nova refatoração.
+
+Daí criamos um gerador de progressões aritméticas, e mostramos como usar o módulo `itertools` para simplificar o código.
+A isso se seguiu uma revisão da maioria das funções geradoras de uso geral na biblioteca padrão.
+
+A seguir estudamos expressões `yield from` no contexto de geradores simples, com os exemplos `chain` e `tree`.
+
+A última seção foi sobre corrotinas clássicas, um tópico de importância decrescente após a introdução das corrotinas nativas, no Python 3.5.
+Apesar de difíceis de usar na prática, corrotinas clássicas são os alicerces das corrotinas nativas, e a expressão `yield from` é precursora direta de `await`.
+
+Também abordamos dicas de tipo para os tipos `Iterable`, `Iterator`, e `Generator`—com esse último oferecendo um raro exemplo concreto de um parâmetro de tipo contravariante.
+
+<<<
+=== Para saber mais
+
+Uma((("iterators", "further reading on")))((("generators", "further reading on")))((("coroutines", "further reading on"))) explicação técnica detalhada sobre geradores aparece na _A Referência da Linguagem Python_, em https://fpy.li/9g[_Expressões yield_].
+A PEP onde as funções geradoras foram definidas é a https://fpy.li/pep255[_PEP 255--Simple Generators_].
+
+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 uso 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 biblioteca 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, acompanhados de muitos exemplos e
+receitas úteis.
+
+_Iterators and Generators_, 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`.
+
+Sebastian Rittau—atualmente um dos principais colaboradores do _typeshed_—explica por que iteradores devem ser iteráveis. Ele observou, em 2006, que https://fpy.li/17-31[_Java: Iterators are not Iterable_] (Java: Iteradores não são Iteráveis).
+
+A sintaxe de `yield from` é explicada, com exemplos, na seção _What's New in
+Python 3.3_ (Novidades no Python 3.3) da
+https://fpy.li/17-32[_PEP 380-Syntax for Delegating to a Subgenerator_] (Sintaxe para Delegar para um Subgerador).
+Meu artigo
+https://fpy.li/oldcoro[_Classic Coroutines_]
+_http://fluentpython.com_
+explica `yield from` em
+profundidade, incluindo pseudo-código em Python de sua implementação em C.
+
+<<<
+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:
+https://fpy.li/17-33[_Generator Tricks for Systems Programmers_] (Truques com Geradoras para Programadores de Sistemas).
+Na PyCon US 2009 aconteceu o lendário
+https://fpy.li/17-34[_A Curious Course on Coroutines and Concurrency_] (Um Curioso Curso sobre Corrotinas e Concorrência)
+(links de vídeo difíceis de encontrar para as três partes:
+https://fpy.li/17-35[parte 1],
+https://fpy.li/17-36[parte 2], e
+https://fpy.li/17-37[parte 3]).
+Seu tutorial na PyCon 2014 em Montreal foi
+https://fpy.li/17-38[_Generators: The Final Frontier_] (Geradores: A Fronteira Final), onde ele apresenta mais exemplos de concorrência—então é, na  verdade, mais relacionado aos tópicos do <>.
+Dave não consegue deixar de explodir cérebros em suas aulas, então, na última parte de _A Fronteira Final_, corrotinas substituem o padrão clássico _Visitor_ em um analisador de expressões aritméticas.
+
+Corrotinas permitem organizar o código de novas maneiras e, assim como a recursão e o polimorfismo (despacho dinâmico),
+leva um tempo para a gente se dar conta de suas possibilidades.
+Um exemplo interessante de um algoritmo clássico reescrito com corrotinas aparece no post
+https://fpy.li/17-39[_Greedy algorithm with coroutines_] (Algoritmo guloso com corrotinas), de James Powell.
+
+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.
+Slatkin apresenta o melhor exemplo que já vi do controle de corrotinas com `yield from`:
+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.
+Publiquei o https://fpy.li/17-43[«exemplo refatorado»] como um https://fpy.li/17-44[_GitHub gist_].
+
+<<<
+.Ponto de Vista
+****
+
+*A interface Iterador minimalista de Python*
+
+Na((("iterators", "Soapbox discussion")))((("generators",
+"Soapbox discussion")))((("coroutines", "Soapbox discussion")))((("Soapbox sidebars",
+"minimalistic iterator interface"))) seção _Implementação_ do padrão
+_Iterator_,footnote:[Gamma et. al., _Design Patterns: Elements of Reusable
+Object-Oriented Software_, p. 261.] a Gangue dos Quatro escreveu:
+
+[quote]
+____
+A interface mínima de Iterator consiste das operações First, Next, IsDone, e CurrentItem.
+____
+
+Entretanto, esta mesma frase tem uma nota de rodapé, onde se lê:
+
+[quote]
+____
+Podemos tornar esta interface ainda menor, fundindo Next, IsDone, e CurrentItem em uma única operação que avança para o próximo objeto e o devolve. Se a travessia estiver encerrada, esta operação daí devolve um valor especial (0, por exemplo), que marca o final da iteração.
+____
+
+Isto é próximo do que temos em Python: um único método `+__next__+`, faz o serviço.
+Mas em vez de uma sentinela, que poderia passar despercebida por engano ou distração, a exceção `StopIteration` sinaliza o final da iteração. Simples e correto: esse é o jeito de Python.
+
+*Geradoras plugáveis*
+
+Qualquer((("Soapbox sidebars", "pluggable generators", id="SSplgen17"))) um que gerencie grandes conjuntos de dados encontra muitos usos para geradores.
+Esta é 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 a LILACS (Literatura Latino-Americana e do Caribe em Ciências da Saúde) e a SciELO (Scientific Electronic Library Online), duas bases de dados abrangentes, indexando a literatura de pesquisa em ciências da saúde produzida na região.
+
+Desde o final dos anos 1980, o sistema de banco de dados usado para gerenciar a
+LILACS é o CDS/ISIS, um banco de dados não-relacional orientado a documentos,
+criado pela UNESCO. Uma de minhas tarefas era pesquisar alternativas para uma
+possível migração da LILACS--e eventualmente também a SciELO--para um banco de
+dados documental moderno de código aberto, tal como o CouchDB ou o MongoDB.
+Naquela época publiquei um artigo científico 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 CouchDB: Bancos de Dados e Modelos de Dados para Registros Bibliográficos).
+
+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 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 laço de
+escrita JSON já era complicado, pois o script aceitava
+várias opções na linha de comando para reestruturar cada registro de saída.
+Seria ainda mais complicado ler dados usando duas APIs diferentes no mesmo laço `for` onde o JSON era produzido.
+
+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.]
+
+<<<
+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`:: Função geradora que lê arquivos _.iso_ (que se presume estarem no formato ISO-2709). Ela aceita dois argumentos: 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`:: Função geradora que lê arquivos _.mst_.footnote:[A biblioteca usada para ler o complicado formato binário _.mst_ é escrita em Java, então esta 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_] no repositório. As dependências são importadas dentro das funções geradoras que precisam delas, então o script pode rodar mesmo quando apenas uma das bibliotecas externas está instalada.] 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`:: Função que escreve os 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.
+
+Aproveitando as funções geradoras, consegui isolar completamente a leitura da escrita.
+Claro, a maneira mais simples de isolar 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 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, da Biblioteca do Congresso dos EUA—será fácil acrescentar uma terceira função geradora para implementar a lógica de leitura, sem mudar nada na complicada função `write_json`.
+
+Não foi uma grande inovação, mas é um exemplo real onde os geradores permitiram
+uma 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/vol3/cap18.adoc b/vol3/cap18.adoc
new file mode 100644
index 00000000..74601f10
--- /dev/null
+++ b/vol3/cap18.adoc
@@ -0,0 +1,1883 @@
+[[ch_with_match]]
+== Instruções `with`, `match`, e blocos `else`
+:example-number: 0
+:figure-number: 0
+
+[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.]
+
+____
+
+
+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 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 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 com as instruções `for`, `while` e `try`.
+
+=== Novidades neste capítulo
+
+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
+agrupados entre parênteses, desde o Python 3.10.
+
+Vamos começar com a poderosa instrução `with`.
+
+[[context_managers_sec]]
+=== 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 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((("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://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`»].
+
+* Configurar ambientes customizados para operações
+aritméticas com objetos `Decimal`—veja a
+https://fpy.li/9r[«documentação de `decimal.localcontext`»].
+
+* 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
+é garantir que um 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
+====
+[source, python]
+----
+>>> with open('mirror.py') as fp:  # <1>
+...     src = fp.read(60)  # <2>
+...
+>>> len(src)
+60
+>>> fp  # <3>
+<_io.TextIOWrapper name='mirror.py' mode='r' encoding='UTF-8'>
+>>> fp.closed, fp.encoding  # <4>
+(True, 'UTF-8')
+>>> fp.read(60)  # <5>
+Traceback (most recent call last):
+  File "", line 1, in 
+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`.
+
+<2> Lê `60` caracteres Unicode de `fp`.
+
+<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`.
+
+<4> Podemos acessar atributos do objeto `fp`.
+
+<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 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.
+
+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`
+====
+[source, python]
+----
+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` é 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.
+
+
+O <> mostra a implementação de `LookingGlass`.
+
+[[looking_glass_ex]]
+.mirror.py: código da classe gerenciadora de contexto `LookingGlass`
+====
+[source, python]
+----
+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 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.
+
+[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`.
+
+====
+
+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`.
+
+`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 invocar manualmente seus métodos `+__enter__+` e
+`+__exit__+`.
+
+[[looking_glass_demo_2]]
+.Exercitando o `LookingGlass` sem um bloco `with`
+====
+[source, python]
+----
+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")))
+
+
+.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)].
+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:
+
+[source, python]
+----
+with (
+    CtxManager1() as example1,
+    CtxManager2() as example2,
+    CtxManager3() as example3,
+):
+    ...
+----
+
+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, classes e
+decoradores convenientes para desenvolver, combinar e usar gerenciadores
+de contexto.
+
+[[context_utilities_sec]]
+==== Utilitários da `contextlib`
+
+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`.
+
+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__+`.
+
+`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.
+
+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 uma 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.
+
+`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.
+
+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 <>.
+
+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`.
+
+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__+` é invocado, no final do bloco.
+
+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
+====
+[source, python]
+----
+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 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 é suspenso neste 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.
+
+[[looking_glass_gen_demo]]
+.Testando a função gerenciadora de contexto `looking_glass`
+====
+[source, python]
+----
+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`.
+
+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:
+
+. 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 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 <> agora trata a exceção
+`ZeroDivisionError`, tornando-o funcionalmente
+equivalente ao <>, que é uma classe.
+
+[[looking_glass_gen_exc_ex]]
+.mirror_gen_exc.py: gerenciador de contexto baseado em gerador com tratamento de erro—mesmo comportamento de <>
+====
+[source, python]
+----
+include::../code/18-with-match/mirror_gen_exc.py[tags=MIRROR_GEN_EXC]
+----
+====
+<1> Cria uma variável para uma possível mensagem de erro; essa é a primeira mudança em relação a <>.
+<2> Trata `ZeroDivisionError`, fixando uma mensagem de erro.
+<3> Desfaz o _monkey-patching_ de `sys.stdout.write`.
+<4> Mostra a mensagem de erro, se ela foi determinada.
+
+Lembre-se de 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.
+
+[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 dos revisores técnicos desse livro. Muito bem colocado, Leo!]
+
+====
+
+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.
+
+[[looking_glass_gen_deco_demo]]
+.O gerenciador de contexto `looking_glass` também funciona como um decorador.
+====
+[source, python]
+----
+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.
+
+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 a
+função `inplace` apresentada naquele artigo.
+
+[[inplace_ex]]
+.Um gerenciador de contexto para reescrever arquivos no lugar
+====
+[source, python]
+----
+import csv
+
+with inplace(csvfilename, 'r', newline='') as (infh, outfh):
+    reader = csv.reader(infh)
+    writer = csv.writer(outfh)
+
+    for row in reader:
+        row += ['new', 'columns']
+        writer.writerow(row)
+----
+====
+
+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. Isto é 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»]), 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.
+
+Isto conclui nossa revisão da instrução `with` e dos gerenciadores de contexto.
+
+Vamos agora estudar a instrução `match/case` em um exemplo maior
+do que aqueles que vimos quando falamos de casamenteo de padrões nos capítulos
+anteriores.((("", startref="wmebcontextm18")))((("", startref="CMatcontextm18")))((("",
+startref="atcontextm18")))
+
+<<<
+[[pattern_matching_case_study_sec]]
+=== Estudo de caso: `match/case` no _lis.py_
+
+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")))
+https://fpy.li/ck[«Seção 2.6.1»] (vol.1), 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. Nesta seção quero apresentar uma visão geral
+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 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.
+
+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á 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 têm comportamentos muito
+diferentes de qualquer função.]
+
+O <> mostra um exemplo simples em Scheme.
+
+<<<
+[[ex_gcd_scheme]]
+.Maior divisor comum em Scheme
+====
+[source, scheme]
+----
+(define (mod m n)
+    (- m (* n (quotient m n))))
+
+(define (gcd m n)
+    (if (= n 0)
+        m
+        (gcd n (mod m n))))
+
+(display (gcd 18 45))
+----
+====
+
+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 (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
+====
+[source, python]
+----
+def mod(m, n):
+    return m - (m // n * n)
+
+def gcd(m, n):
+    if n == 0:
+        return m
+    else:
+        return gcd(n, mod(m, n))
+
+print(gcd(18, 45))
+----
+====
+
+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 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
+https://fpy.li/18-15[_18-with-match/lispy/py3.10/_],
+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.
+
+[[lis_top_ex]]
+.lis.py: início do arquivo
+====
+[source, python]
+----
+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_), 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 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 aninhadas.
+
+[[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 (<>).
+
+[[lis_parser_ex]]
+.lis.py: as principais funções do analisador
+====
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/lis.py[lines=27..36]
+    # mais código do analisador omitido na listagem do livro
+----
+====
+
+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êntese 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 extraídos do https://fpy.li/18-18[_lispy/py3.10/examples_test.py_]:
+
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=PARSE]
+----
+
+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`.
+
+. 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.
+
+. 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[align="center",pdfwidth=12cm]
+
+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` guardam no atributo `self.maps`, que é lista de mapeamentos. O método
+`change` é necessário para suportar a instrução `(set! …)` do Scheme, descrita mais
+tarde.
+
+<<<
+[[environment_class_ex]]
+._lis.py_: a classe `Environment`
+====
+[source, python]
+----
+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.]
+Tentar mudar uma chave não encontrada causa um `KeyError`.
+
+Esse doctest mostra como `Environment` funciona:
+
+[source, python]
+----
+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 (<>).
+
+[[lis_std_env_ex]]
+.lis.py: `standard_env()` constrói e devolve o ambiente global
+====
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/lis.py[lines=77..85]
+            # omitidas: várias definições de funções
+include::../code/18-with-match/lispy/py3.10/lis.py[lines=92..97]
+            # 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, algumas renomeadas, como `callable`
+para `procedure?`, ou mapeadas diretamente, como `round`.
+
+==== O REPL
+
+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 o 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 esquecermos de fechar um parêntese,
+_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 que 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]]
+.As funções do REPL
+====
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/lis.py[tags=REPL]
+----
+====
+
+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 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`.
+
+<<<
+
+`lispstr(exp: object) -> str`::
+    A função inversa de `parse`:
+    dado um objeto Python representando a AST de uma expressão,
+    `lispstr` devolve o código-fonte correspondente.
+    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—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.
+
+[[ex_evaluate_match]]
+.`evaluate` recebe uma expressão e calcula seu valor
+====
+[source, python]
+----
+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 casos coloquei comentários mostrando uma expressão-S que
+casaria com o padrão quando transformada em uma lista de Python. Os doctests
+extraídos de https://fpy.li/18-21[_examples_test.py_] demonstram cada `case`.
+
+
+[[eval_atom_sec]]
+===== avaliando números
+
+[source, python]
+----
+    case int(x) | float(x):
+        return x
+----
+
+Padrão:::
+    Instância de `int` ou `float`.
+
+Ação:::
+    Devolve o próprio valor.
+
+Exemplo:::
++
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_NUMBER]
+----
+
+===== avaliando símbolos
+
+[source, python]
+----
+    case Symbol(var):
+        return env[var]
+----
+
+Padrão:::
+    Instância de `Symbol`, isto é, uma `str` usada como identificador.
+
+Ação:::
+    Consulta `var` em `env` e devolve seu valor.
+
+Exemplos:::
++
+[source, python]
+----
+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.
+
+[source, python]
+----
+    # (quote (99 bottles of beer))
+    case ['quote', x]:
+        return x
+----
+
+Padrão:::
+    Lista começando com o símbolo `'quote'`, seguido de uma expressão `x`.
+
+Ação:::
+    Devolve `x` sem avaliá-la.
+
+Exemplos:::
++
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_QUOTE]
+----
+
+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
+* `(/ 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 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 têm 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.
+****
+
+
+===== (if …)
+
+[source, python]
+----
+    # (if (< x 0) 0 x)
+    case ['if', test, consequence, alternative]:
+        if evaluate(test, env):
+            return evaluate(consequence, env)
+        else:
+            return evaluate(alternative, env)
+----
+
+Padrão:::
+    Lista começando com `'if'` seguida de três expressões:
+    `test`, `consequence`, e `alternative`.
+
+Ação:::
+    Avalia `test`:
+    * Se verdadeira, avalia `consequence` e devolve seu valor.
+    * Caso contrário, avalia `alternative` e devolve seu valor.
+
+Exemplos:::
++
+[source, python]
+----
+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 <>.
+
+===== (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 …)`.
+
+[source, python]
+----
+    # (lambda (a b) (/ (+ a b) 2))
+    case ['lambda' [*parms], *body] if body:
+        return Procedure(parms, body, env)
+----
+
+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 `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.
+
+Exemplo:::
++
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_LAMBDA]
+----
+
+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 declarada.
+Vamos estudar o código de `Procedure` daqui a pouco.
+
+
+[role="pagebreak-before less_space"]
+===== (define …)
+
+A palavra reservada `define` é usada em duas formas sintáticas diferentes.
+A mais simples é:
+
+[source, python]
+----
+    # (define half (/ 1 2))
+    case ['define', Symbol(name), value_exp]:
+        env[name] = evaluate(value_exp, env)
+----
+
+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.
+
+<<<
+Exemplo:::
++
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_DEFINE]
+----
+
+O doctest para este `case` cria um `global_env`, para podermos verificar que
+`evaluate` coloca `answer` dentro daquele `Environment`.
+
+Podemos usar a 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.
+
+[source, python]
+----
+
+    # (define (average a b) (/ (+ a b) 2))
+    case ['define', [Symbol(name), *parms], *body] if body:
+        env[name] = Procedure(parms, body, env)
+----
+
+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)
+
+Ação:::
+    * 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.
+
+<<<
+
+[[test_case_defun]]
+.Definindo uma função chamada `%`, que calcula uma porcentagem
+====
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_DEFUN]
+----
+====
+
+Após invocar `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.
+
+
+
+===== (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
+programaçã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]
+----
+    # (set! n (+ n 1))
+    case ['set!', Symbol(name), value_exp]:
+        env.change(name, evaluate(value_exp, env))
+----
+
+Padrão:::
+    Lista começando com `'set!'`, seguida de um `Symbol` e de uma expressão.
+
+Ação:::
+    Atualiza o valor de `name` em `env` com o resultado da avaliação da expressão.
+
+O método `Environment.change` atravessa os ambientes encadeados de local para global,
+e atualiza a primeira ocorrência de `name` com o novo valor.
+Se não estivéssemos implementando a palavra reservada `'set!'`,
+esse interpretador poderia usar apenas o `ChainMap` de Python para implementar `env`,
+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 keyword")))((("keywords", "nonlocal keyword"))) uso da forma
+`set!` tem relação com a 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
+https://fpy.li/cm[«Seção 9.7»] (vol.2).
+
+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)` será sempre uma variável local, criada ou atualizada no ambiente
+local.
+
+Ambos, `nonlocal` e `(set! …)`, são necessários para atualizar o estado do
+programa mantido em variáveis dentro de uma clausura. O
+Exemplo 13 do https://fpy.li/9[«Capítulo 9»] (vol.2)
+demonstrou o uso de `nonlocal` para implementar uma função
+que calcula uma média contínua, preservando os itens `count` e `total` em uma
+clausura. Aqui está a mesma ideia, escrita no subconjunto de Scheme de _lis.py_:
+
+[source, scheme]
+----
+(define (make-averager)
+    (define count 0)
+    (define total 0)
+    (lambda (new-value)
+        (set! count (+ count 1))
+        (set! total (+ total new-value))
+        (/ total count)
+    )
+)
+(define avg (make-averager))  # <1>
+(avg 10)  # <2>
+(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`, inicializadas com 0; vincula a clausura a `avg`.
+<2> Devolve 10.0.
+<3> Devolve 10.5.
+<4> Devolve 12.0.
+
+O código acima é um dos testes em https://fpy.li/18-18[_lispy/py3.10/examples_test.py_].
+
+****
+
+Agora chegamos a uma chamada de função.
+
+[[function_call_sec]]
+===== Chamada de função
+
+[source, python]
+----
+    # (gcd (* 2 105) 84)
+    case [func_exp, *args] if func_exp not in KEYWORDS:
+        proc = evaluate(func_exp, env)
+        values = [evaluate(arg, env) for arg in args]
+        return proc(*values)
+----
+
+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 <>.
+
+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.
+--
+
+Açã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.
+    * Invocar `proc` com os valores como argumentos separados, devolvendo o resultado.
+
+Exemplo:::
++
+[source, python]
+----
+include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_CALL]
+----
+
+Esse doctest continua do <>:
+ele assume que `global_env` contém uma função chamada `%`.
+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 sujeito que seja uma sequência de um ou mais itens.
+Entretanto, se `func_exp` é uma palavra reservada e o
+sujeito não casou com nenhum dos `case` anteriores,
+então temos 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` "coringa" gera um `SyntaxError`:
+
+[source, python]
+----
+    case _:
+        raise SyntaxError(lispstr(exp))
+----
+
+Aqui está um exemplo de um `(lambda …)` malformado, identificado como um `SyntaxError`:
+
+[source, python]
+----
+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'` 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
+no contexto de um ambiente. A definição de função inclui o nome dos parâmetros e as
+expressões que formam 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 https://fpy.li/c8[«Seção 9.6»] (vol.2).
+
+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> `+__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> `+__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 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.
+
+Há um par de funções simples após `evaluate` em https://fpy.li/18-24[_lis.py_]:
+`run` lê um programa Scheme completo e o executa,
+e `main` chama `run` ou `repl`, dependendo da linha de comando—parecido com o modo como Python faz.
+Não vou descrever essas funções, pois não há nada novo ali.
+Meus objetivos aqui eram compartilhar com vocês a beleza do pequeno interpretador de Norvig,
+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 casamento de padrões,
+vamos formalizar o conceito de um padrão-OU (_OR-pattern_).((("", startref="lispyproced18")))
+
+==== Usando padrões-OU
+
+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]
+----
+    case int(x) | float(x):
+        return x
+----
+
+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`,
+independentemente 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.
+====
+
+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]
+----
+    # (λ (a b) (/ (+ a b) 2) )
+    case ['lambda' | 'λ', [*parms], *body] if body:
+        return Procedure(parms, body, env)
+----
+
+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")))
+
+=== Faça isso, então aquilo: blocos `else` além do `if`
+
+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` atrapalhou
+meu entendimento destas cláusulas, mas acabei me acostumando.
+
+Aqui estão as regras:
+
+`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 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 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.
+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 destas cláusulas `else`,
+mas do ponto de vista do design da linguagem,
+a palavra `else` foi uma escolha infeliz, porque
+`else` implica em uma alternativa excludente,
+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 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 criar variáveis de controle ou codar instruções
+`if` adicionais.
+
+O uso de `else` em laços em geral segue o padrão desse trecho:
+
+[source, python]
+----
+for item in my_list:
+    if item.flavor == 'banana':
+        break
+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á invocada se a
+`dangerous_call()` não gerar uma exceção, correto?
+
+[source, python]
+----
+try:
+    dangerous_call()
+    after_call()
+except OSError:
+    log('OSError...')
+----
+
+Entretanto, não há um bom motivo para colocar a `after_call()` dentro do bloco `try`.
+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]
+----
+try:
+    dangerous_call()
+except OSError:
+    log('OSError...')
+else:
+    after_call()
+----
+
+Agora fica explícito 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` é bastante 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.
+
+____
+
+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”. 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 é caracterizado 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`.
+
+[NOTE]
+====
+
+Quando a instrução `match` foi proposta, algumas pessoas (inclusive eu)
+acharam que ela também devia ter uma cláusula `else`. Afinal 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
+o significado da instrução `with` e os gerenciadores de contexto, indo
+além do 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
+após 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 funcionamento 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_ foi um instrumento
+para explorarmos mais que casamento de padrões. Ele mostra como diferentes partes
+de um interpretador trabalham juntas, ilustrando conceitos fundamentais do
+próprio Python: por que 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://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 por que 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 e 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 uma 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
+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 explorar é o
+https://fpy.li/18-42[_Racket_].
+
+
+<<<
+[[soapbox_with_match]]
+.Ponto de vista
+****
+
+
+*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." Eu tive uma reação
+similar, inicialmente. 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:
+
+[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.
+Provavelmente é possível fazer coisas muito profundas com a instrução `with`.
+Seus melhores usos ainda estão por ser descobertos.
+Acredito que, se fizerem bom uso dela,
+ela será copiada em outras linguagens,
+e todas as linguagens futuras vão incluí-la.
+Vocês podem fazer parte da descoberta de algo
+quase tão profundo quanto a invenção da própria sub-rotina.
+
+____
+
+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.
+
+Se você precisa convencer alguém que de Python é sensacional, assista à palestra de
+abertura de Hettinger. A parte sobre gerenciadores de contexto fica entre 23:00
+e 26:15. Mas a palestra inteira é excelente.
+
+<<<
+*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 de _Proper Tail Calls_),
+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»].
+
+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]
+----
+def factorial(n):
+    if n < 2:
+       return 1
+    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.
+
+Aqui está uma alternativa que usa uma chamada de cauda,
+e é portanto recursiva de cauda (_tail recursive_):
+
+[source, python]
+----
+def factorial_tc(n, product=1):
+    if n < 1:
+        return product
+    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, 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 de 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 fazer 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.
+
+*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)
+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 rastreando algumas chamadas em um depurador.
+
+____
+
+<<<
+Em 2015, PTC foram incluídas no padrão ECMAScript 6 para JavaScript.
+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 do interpretador 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 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:
+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.
+
+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]
+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 percorrer,
+por exemplo, uma árvore de parsing típica,
+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 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")))
+
+*A opinião de Norvig sobre `evaluate()` com casamento de padrões*
+
+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 as guardas que escrevi,
+ele teria exatamente um `case` por palavra reservada,
+e teria testes dentro de cada `case`, para fornecer mensagens de `SyntaxError`
+mais específicas—por exemplo, quando o corpo estiver vazio.
+
+Se eu fizesse assim, a expressão guarda nesta linha seria desnecessária:
+
+[source, python]
+----
+        case [func_exp, *args] if func_exp not in KEYWORDS:
+----
+
+A guarda seria redundante
+pois todas as palavras reservadas já teriam sido tratadas antes do `case` para chamadas de função.
+
+Provavelmente seguirei o conselho do professor Norvig quando
+acrescentar funcionalidades ao
+https://fpy.li/18-54[_mylis_].
+
+Mas a forma como estruturei `evaluate` no <>
+tem algumas vantagens didáticas neste livro:
+
+* o exemplo é paralelo à implementação original de Norvig 
+com `if/elif/…` no 
+Exemplo 11 do https://fpy.li/2[«Capítulo 2»] (vol.1);
+* as cláusulas `case` demonstram mais recursos de casamento de padrões;
+* o código ficou mais legível, na minha opinião.((("", startref="WMEBsoap18")))
+
+****
diff --git a/vol3/cap19.adoc b/vol3/cap19.adoc
new file mode 100644
index 00000000..4a6363a2
--- /dev/null
+++ b/vol3/cap19.adoc
@@ -0,0 +1,1812 @@
+[[ch_concurrency_models]]
+== Modelos de concorrência em Python
+:example-number: 0
+:figure-number: 0
+
+[quote, Rob Pike, Co-criador da linguagem Go]
+____
+Concorrência é lidar com muitas coisas ao mesmo tempo. +
+Paralelismo é fazer muitas coisas ao mesmo tempo. +
+Não são iguais, mas têm relação. +
+[Concorrência] é sobre estrutura, [paralelismo] é 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 da palestra https://fpy.li/19-1[_Concurrency Is Not Parallelism_] (Concorrência não é paralelismo).]
+
+____
+
+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. 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.
+Um laptop moderno com quatro núcleos de CPU rotineiramente está executando mais de 200 processos a qualquer momento, sob uso normal, casual.
+Para executar 200 tarefas em paralelo, você precisaria de 200 núcleos.
+Portanto, na prática, a maior parte da computação 
+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 paralelo.
+
+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 à 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 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 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."
+
+=== 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 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 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
+[WARNING]
+====
+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 ecosistema 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ê invoca uma função, o código que faz a chamada aguarda até que a 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 cliente pode cercar aquela chamada com um bloco `try/except` para tratar o erro.
+
+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 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 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 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.
+
+Por fim, as corrotinas e threads de Python não são adequadas para tarefas de uso intensivo da CPU, como veremos.
+
+Programação concorrente envolve conceitos e modelos de programação que podem ser novidade para você.
+Então vamos primeiro garantir que estamos na mesma página em relação a alguns conceitos centrais.
+
+=== Um pouco de jargão
+
+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 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).
+
+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.
+    Python suporta de forma nativa três tipos de unidade de execução:
+    _processos_, _threads_, e _corrotinas_.
+
+<<<
+Processo::
+    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 rodam rotineiramente 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 outros processos sejam executados.
+    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 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).
+    Isso facilita o compartilhamento de informações entre threads, mas pode também levar à corrupção de dados,
+    se uma thread está lendo um objeto enquanto ele está sendo modificado por outra thread.
+    Como os processos, as threads também possibilitam a _multitarefa preemptiva_ sob a supervisão do agendador do SO.
+    Uma thread consome menos recursos que um processo para realizar a mesma tarefa.
+
+
+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 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 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".]
+    Filas permitem que unidades de execução separadas troquem dados da aplicação e mensagens de controle, como códigos de erro e sinais de término.
+    A implementação de uma fila varia de acordo com o modelo de concorrência subjacente: o pacote `queue` na biblioteca padrão de Python fornece classes de fila para suportar threads, já os pacotes `multiprocessing` e `asyncio` implementam suas próprias classes de fila. Os pacotes `queue` e `asyncio` também incluem filas não FIFO: `LifoQueue` e `PriorityQueue`.
+
+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 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.
+    A variante mais simples de trava é conhecida também como mutex (de _mutual exclusion_, exclusão mútua).
+    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 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")))
+
+==== Processos, threads, e a infame GIL de Python
+
+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 escritos em qualquer linguagem.
+
+. 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_.
+
+. O acesso à contagem de referências a objetos e outros estados internos do interpretador é controlado por uma trava,
+a((("Global Interpreter Lock (GIL)"))) Global Interpreter Lock (GIL) ou _Trava Global do Interpretador_.
+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, mesmo que a CPU tenha vários núcleos.
+
+. 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, liberando a GIL.footnote:[Invoque https://fpy.li/a5[`sys.getswitchinterval()`] para obter o intervalo; ele pode ser modificado com https://fpy.li/ag[`sys.setswitchinterval(s)`].]
+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 demorada.
+
+. Toda função na biblioteca padrão de Python que executa uma _syscall_ libera a
+GIL.footnote:[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` e `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 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"].]
+
+. 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, parte da https://fpy.li/a7[documentação do módulo `threading`]:
+
+[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 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.
+____
+
+O parágrafo anterior começa com "Detalhe de implementação do CPython" porque a
+GIL não é parte da definição da linguagem Python. As implementações Jython e o
+IronPython não têm uma GIL. Infelizmente, ambas estão ficando para trás, ainda
+compatíveis apenas com Python 2.7 e 3.4, respectivamente. O interpretador de
+alto desempenho https://fpy.li/19-9[PyPy] também tem uma GIL em suas versões
+2.7, 3.8 e 3.9 (a mais recente em março de 2021).
+
+[NOTE]
+====
+Esta seção não mencionou corrotinas, por que por default elas compartilham a mesma thread Python entre si e com o laço de eventos supervisor fornecido por um framework assíncrono—então não são afetadas pela GIL.
+É 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 <>.
+====
+
+Mas chega de conceitos por agora. Vamos ver algum código.((("", startref="CMconcepts19")))
+
+[[concurrent_hello_world]]
+=== Um "Olá mundo" concorrente
+
+Durante((("concurrency models", "Hello World example", id="CMhello19"))) uma discussão sobre threads e sobre como evitar a GIL,
+o contribuidor do Python Michele Simionato https://fpy.li/19-10[postou um exemplo] que é praticamente um "Olá Mundo" concorrente:
+o programa mais simples possível mostrando como o Python pode "assobiar e chupar cana ao mesmo tempo".
+
+O programa de Simionato usa `multiprocessing`,
+mas eu o adaptei para apresentar também `threading` e `asyncio`.
+Vamos começar com a versão `threading`, que pode parecer familiar se você já estudou threads em Java ou C.
+
+
+==== Caracteres animados com threads
+
+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 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 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.
+image::../images/flpy_1901.png[Captura de tela do console mostrando a saída dos dois exemplos.]
+
+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]]
+.spinner_thread.py: as funções `spin` e `slow`
+====
+[source, python]
+----
+include::../code/19-concurrency/spinner_thread.py[tags=SPINNER_THREAD_TOP]
+----
+====
+<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.
+
+[TIP]
+====
+O primeiro detalhe importante deste exemplo é que `time.sleep()` bloqueia a thread que a chama, mas libera a GIL, permitindo que outras threads Python rodem.
+====
+
+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 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.
+
+
+<<<
+[[spinner_thread_rest_ex]]
+.spinner_thread.py: as funções `supervisor` e `main`
+====
+[source, python]
+----
+include::../code/19-concurrency/spinner_thread.py[tags=SPINNER_THREAD_REST]
+----
+====
+<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 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 o estado de `Event` para `True`; isto vai encerrar o laço `for` em `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` 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")))
+
+
+==== Animação com processos
+
+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 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`, 
+facilitando a conversão de programas simples de threads para processos, como mostra o _spinner_proc.py_ (<>).
+
+[[spinner_proc_ex]]
+.spinner_proc.py: apenas as partes modificadas são mostradas; todo o resto é idêntico a spinner_thread.py
+====
+[source, python]
+----
+include::../code/19-concurrency/spinner_proc.py[tags=SPINNER_PROC_IMPORTS]
+
+# as funções spin, slow e main são iguais a spinner_thread.py
+
+include::../code/19-concurrency/spinner_proc.py[tags=SPINNER_PROC_SUPER]
+----
+====
+<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 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 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`, 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, 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 de 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://fpy.li/a9[`ShareableList`]
+para mais detalhes.
+====
+
+Agora vamos ver como o mesmo comportamento pode ser obtido com corrotinas em vez de threads ou processos.
+
+[[spinner_async_sec]]
+==== Animação com corrotinas
+
+[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. Esta seção é apenas uma introdução rápida, para contrastar esta 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 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.
+
+[[spinner_async_start_ex]]
+.spinner_async.py: a função `main` e a corrotina `supervisor`
+====
+[source, python]
+----
+include::../code/19-concurrency/spinner_async.py[tags=SPINNER_ASYNC_START]
+----
+====
+<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 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 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 <>.
+
+
+O <> demonstra as três principais formas de rodar uma corrotina:
+
+`asyncio.run(coro())`::
+    É 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` neste exemplo. Esta chamada bloqueia até que `coro` retorne. O resultado de `coro` será o resultado de `run`.
+`asyncio.create_task(coro())`::
+    É invocada dentro de uma corrotina para agendar a execução futura de outra corrotina.
+    Essa chamada não suspende a corrotina atual.
+    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()`::
+    É 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 laço de eventos.
+====
+
+Vamos estudar agora as corrotinas `spin` e `slow` no <>.
+
+[[spinner_async_top_ex]]
+.spinner_async.py: as corrotinas `spin` e `slow`
+====
+[source, python]
+----
+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 laço.
+<4> A corrotina `slow` também usa `await asyncio.sleep` em vez de `time.sleep`.
+
+<<<
+===== 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 <>.
+
+[[spinner_async_time_sleep_ex]]
+.spinner_async.py: substituindo `await asyncio.sleep(3)` por `time.sleep(3)`
+====
+[source, python]
+----
+async def slow() -> int:
+    time.sleep(3)
+    return 42
+----
+====
+
+Observar o comportamento é mais memorável que ler sobre ele.
+Vai lá, eu espero.
+
+Ao rodar o experimento, você vê o seguinte:
+
+. O objeto `spinner` aparece: `>`.
+. 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 de 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.
+No <>, vamos nos concentrar no que ocorre nas corrotinas `supervisor` e `slow` durante o experimento proposto.
+
+[[spinner_async_experiment_ex]]
+.spinner_async_experiment.py: as corrotinas `supervisor` e `slow`
+====
+[source, python]
+----
+include::../code/19-concurrency/spinner_async_experiment.py[tags=SPINNER_ASYNC_EXPERIMENT]
+----
+====
+<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 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 assíncronas, a menos que você queira pausar o programa inteiro.
+Se uma corrotina precisa passar algum tempo sem fazer nada, use `await asyncio.sleep(DELAY)`.
+Isto devolve o controle para o laço de eventos do `asyncio`, que pode acionar outras corrotinas pendentes.((("", startref="Cspin19")))((("", startref="Scoroutine19")))
+====
+
+[[gevent_box]]
+.Greenlet e gevent
+****
+Ao((("greenlet package"))) discutir concorrência com corrotinas,
+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`,
+e assim são mais fáceis de integrar a bases de código sequencial existentes.
+O https://fpy.li/19-15[SQL Alchemy 1.4 ORM] usa greenlets
+internamente para implementar sua nova
+https://fpy.li/19-16[API assíncrona] compatível com `asyncio`.
+
+A((("gevent library"))) biblioteca de programação de redes
+https://fpy.li/19-17[_gevent_] modifica o módulo `socket` padrão de Python via _monkey patching_, tornando-o não-bloqueante ao substituir parte do código por greenlets.
+Na maior parte dos casos, _gevent_ é transparente para o código em seu entorno,
+tornando mais fácil adaptar aplicações e bibliotecas sequenciais—tal como drivers de bancos de dados—para executar E/S de rede de forma concorrente.
+https://fpy.li/19-18[Inúmeros projetos open source]
+usam _gevent_, incluindo o muito usado
+https://fpy.li/gunicorn[_Gunicorn_]—mencionado na <>.
+****
+
+==== 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 a parte mais importante destes exemplos. Vamos compará-las
+em detalhes.
+
+[[thread_supervisor_ex]]
+.spinner_thread.py: a função `supervisor` com threads
+====
+[source, python]
+----
+def supervisor() -> int:
+    done = Event()
+    spinner = Thread(target=spin,
+                     args=('thinking!', done))
+    print('spinner object:', spinner)
+    spinner.start()
+    result = slow()
+    done.set()
+    spinner.join()
+    return result
+----
+====
+
+Para comparar, o <> mostra a corrotina `supervisor` do <>.
+
+[[asyncio_supervisor_ex]]
+.spinner_async.py: a corrotina assíncrona `supervisor`
+====
+[source, python]
+----
+async def supervisor() -> int:
+    spinner = asyncio.create_task(spin('thinking!'))
+    print('spinner object:', spinner)
+    result = await slow()
+    spinner.cancel()
+    return result
+----
+====
+
+Aqui está um resumo das diferenças e semelhanças notáveis entre as duas implementações de `supervisor`:
+
+* Uma `asyncio.Task` é aproximadamente equivalente  a `threading.Thread`.
+* 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(…)` 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`.
+
+Esta comparação ajuda a entender como a concorrência é orquestrada com `asyncio`,
+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
+sabe quão desafiador é estruturar o programa, porque o agendador pode interromper uma thread a qualquer momento.
+É preciso lembrar de manter travas para proteger seções críticas do programa, para evitar ser interrompido no meio de uma operação de muitas etapas—algo que poderia deixar dados em um estado inválido.
+
+Com corrotinas, seu código está protegido de interrupções arbitrárias.
+É preciso chamar `await` explicitamente para deixar o resto do programa rodar.
+Em vez de manter travas para sincronizar as operações de múltiplas threads,
+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` 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 uma 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.
+
+Mas, tudo pode mudar trocando `sleep()` por `is_prime()` (<>).
+
+[[def_is_prime_ex]]
+.primes.py: uma checagem de números primos fácil de entender, do exemplo em https://fpy.li/aa[`ProcessPoolExecutor`] na documentação de Python
+====
+[source, python]
+----
+include::../code/19-concurrency/primes/primes.py[tags=IS_PRIME]
+----
+====
+
+A função `is_prime` é intensiva em CPU.
+Ela retorna `True` se o argumento for um número primo, `False` se não for.
+A chamada `is_prime(5_000_111_000_222_021)` leva cerca de 3s no laptop
+da firma que estou usando
+agora.footnote:[É um MacBook Pro 15” de 2018, com uma CPU Intel Core i7 2.2 GHz de 6 núcleos.]
+
+==== Teste Rápido
+
+Dado o que vimos até aqui,
+pare um instante para pensar sobre a seguinte questão, de três partes.
+Uma das partes da resposta é um pouco mais complicada (pelo menos para mim foi).
+
+[quote]
+____
+O que aconteceria à animação após as seguintes modificações,
+presumindo que `n = 5_000_111_000_222_021`—aquele número primo que minha máquina levou 3s para checar:
+
+. Em _spinner_proc.py_, substitua `time.sleep(3)` por uma chamada a `is_prime(n)`?
+. Em _spinner_thread.py_, substitua `time.sleep(3)` por uma chamada a `is_prime(n)`?
+. Em _spinner_async.py_, substitua `await asyncio.sleep(3)` por uma chamada a `is_prime(n)`?
+____
+
+Antes de executar o código ou continuar lendo,
+recomendo dedicar um tempo para considerar as perguntas e formular suas respostas.
+Depois, copie, modifique e rode os exemplos _spinner_ como sugerido.
+
+Agora as respostas, da mais fácil para a mais difícil.
+
+Resposta para multiprocessamento (_spinner_proc.py_)::
+A animação é controlada por um processo filho,
+então continua girando enquanto o teste de números primos é computado no processo raiz.
+Isso é verdade porque hoje usamos sistemas operacionais capazes de multitarefa preemptiva:
+o SO sabe suspender um processo demorado para dar chance a outros processos.footnote:[O
+Windows antes da era NT e o MacOS antes da era OSX não eram preemptivos,
+então qualquer processo podia tomar 100% da CPU e paralisar o sistema inteiro.
+Não estamos inteiramente livres desse tipo de problema hoje,
+mas confie na minha barba branca:
+esse tipo de coisa assombrava todos os usuários nos anos 1990,
+e a única cura era um reset de hardware.]
+
+<<<
+Resposta para asyncio (_spinner_async.py_)::
+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 é o 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`.
+Quando `is_prime` retornar, `slow` vai retornar também, e `supervisor` retomará a execução, cancelando a tarefa `spinner` antes dela ser executada sequer uma vez.
+O programa parecerá congelado por aproximadamente 3s, e então mostrará a resposta.((("", startref="CMimpact19")))((("", startref="gil19")))((("", startref="SPgil19")))
+
+Resposta para threads (_spinner_thread.py_)::
+A((("threads", "Global Interpreter Lock impact"))) animação é controlada por uma thread secundária, então continua girando enquanto o teste de número primo é computado na thread principal.
+
+Não acertei essa resposta inicialmente:
+eu esperava que a animação congelasse, porque superestimei o impacto da 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 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 mais tempo da CPU, seu programa será mais lento que um programa sequencial.
+
+<<<
+._Power nap_ (soneca rápida) com `sleep(0)`
+****
+Um((("spinners (loading indicators)", "keeping alive"))) jeito 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 laço de eventos, como no <>.
+
+[[example-19-11]]
+.spinner_async_nap.py: `is_prime` agora é uma corrotina
+====
+[source, python]
+----
+include::../code/19-concurrency/primes/spinner_prime_async_nap.py[tags=PRIME_NAP]
+----
+====
+<1> Dormir a cada 50.000 iterações (porque o argumento `step` em `range` é 2).
+
+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 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)` pode ser uma medida paliativa até o código assíncrono ser refatorado para delegar computações de uso intensivo da CPU para outro processo.
+Veremos como 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 <>.
+****
+
+Até aqui experimentamos com uma única chamada para uma função de uso intensivo de CPU. A próxima seção apresenta a execução concorrente de múltiplas chamadas de uso intensivo da CPU.
+
+[[naive_multiprocessing_sec]]
+=== Um banco de processos caseiro
+
+[NOTE]
+====
+
+Não conheço uma tradução consagrada para _process pool_,
+mas "um banco de processos" faz sentido.
+A ideia é que vários processos são iniciados e ficam aguardando tarefas.
+Subir um processo é demorado e custoso, então é bom ter processos
+prontos para realizar tarefas, e também aproveitar cada processo para
+computar várias tarefas ao longo da sua vida.
+
+Escrevi((("concurrency models", "process pools", id="CMprocess19")))((("process pools", "example problem")))
+esta seção para mostrar o uso de um banco de processos em cenários de uso intensivo de CPU,
+com filas para distribuir tarefas para os processos, e coletar os resultados.
+O <> apresenta uma forma mais simples de distribuir tarefas para processos:
+o `ProcessPoolExecutor` do pacote `concurrent.futures`, que também usa filas,
+mas elas não são visíveis para o usuário.
+
+====
+
+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:
+
+<<<
+[source]
+----
+$ python3 sequential.py
+               2  P  0.000001s
+ 142702110479723  P  0.568328s
+ 299593572317531  P  0.796773s
+3333333333333301  P  2.648625s
+3333333333333333     0.000007s
+3333335652092209     2.672323s
+4444444444444423  P  3.052667s
+4444444444444444     0.000001s
+4444444488888889     3.061083s
+5555553133149889     3.451833s
+5555555555555503  P  3.556867s
+5555555555555555     0.000007s
+6666666666666666     0.000001s
+6666666666666719  P  3.781064s
+6666667141414921     3.778166s
+7777777536340681     4.120069s
+7777777777777753  P  4.141530s
+7777777777777777     0.000007s
+9999999999999917  P  4.678164s
+9999999999999999     0.000007s
+Total time: 40.31
+----
+
+Os resultados aparecem em três colunas:
+
+* O número a ser checado.
+* `P` se é um número primo, vazia se é um número composto.
+* Tempo decorrido para checar se aquele número é primo.
+
+Neste exemplo, o tempo total é aproximadamente a soma do tempo de cada checagem,
+mas está computado separadamente, como se vê no <>.
+
+[[primes_sequential_ex]]
+.sequential.py: checagem sequencial de números primos
+====
+[source, python]
+----
+include::../code/19-concurrency/primes/sequential.py[]
+----
+====
+[role="pagebreak-before less_space"]
+<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.
+
+<<<
+[[proc_based_solution]]
+==== Solução baseada em processos
+
+O((("process pools", "process-based solution"))) próximo exemplo, _procs.py_, mostra o uso de múltiplos processos para distribuir a checagem de números primos por muitos núcleos da CPU.
+Esses são os tempos obtidos com _procs.py_:
+
+[source]
+----
+$ python3 procs.py
+Checking 20 numbers with 12 processes:
+               2  P  0.000002s
+3333333333333333     0.000021s
+4444444444444444     0.000002s
+5555555555555555     0.000018s
+6666666666666666     0.000002s
+ 142702110479723  P  1.350982s
+7777777777777777     0.000009s
+ 299593572317531  P  1.981411s
+9999999999999999     0.000008s
+3333333333333301  P  6.328173s
+3333335652092209     6.419249s
+4444444488888889     7.051267s
+4444444444444423  P  7.122004s
+5555553133149889     7.412735s
+5555555555555503  P  7.603327s
+6666666666666719  P  7.934670s
+6666667141414921     8.017599s
+7777777536340681     8.339623s
+7777777777777753  P  8.388859s
+9999999999999917  P  8.117313s
+20 checks in 9.58s
+----
+
+A última linha dos resultados mostra que _procs.py_ foi 4,2 vezes mais rápido que _sequential.py_.
+
+
+==== Entendendo os tempos decorridos
+
+Observe((("process pools", "understanding elapsed times"))) que o tempo decorrido na primeira coluna é o tempo para checar aquele número específico. Por exemplo, `is_prime(7777777777777753)` demorou quase 8,4s para retornar `True`. Enquanto isso, outros processos estavam checando outros números em paralelo.
+
+Há 20 números para serem checados. Escrevi _procs.py_ para iniciar um número de processos de trabalho igual ao número de núcleos na CPU, como determinado por `multiprocessing.cpu_count()`.
+
+O tempo total neste caso é muito menor que a soma dos tempos decorridos para cada checagem individual. Há algum tempo gasto em iniciar processos e na comunicação entre processos, então o resultado final é que a versão multiprocessos é apenas cerca de 4,2 vezes mais rápida que a sequencial. Isso é bom, mas um pouco desapontador, considerando que o código inicia 12 processos, para usar todos os núcleos desse laptop.
+
+[NOTE]
+====
+
+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 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 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
+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 <>.
+
+
+[[ex_primes_procs_top]]
+.procs.py: checagem de primos com múltiplos processos; importações, tipos, e funções
+====
+[source, python]
+----
+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. 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. Preservar `n` 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 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 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`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 para cada processo, para encerrá-los.
+
+<<<
+[[good_poison_pill_tip]]
+.Laços, sentinelas e pílulas venenosas
+****
+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»] como sentinelas.
+A https://fpy.li/pep661[_PEP 661—Sentinel Values_]
+propõe um tipo sentinela padrão.
+****
+
+Agora vamos estudar a função `main` de _procs.py_ no <>.
+
+[[primes_procs_main_ex]]
+.procs.py: checagem de números primos com múltiplos processos; função `main`
+====
+[source, python]
+----
+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 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 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 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,
+podemos ter _tracebacks_ difíceis de analisar,
+com referências a exceções de `FileNotFoundError` levantadas 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 _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, acontece um erro.
+Esta é a corrida.
+
+Se tiver curiosidade, veja o https://fpy.li/19-25[«diff que mostra o bug e a correção»]
+(mas saiba que depois eu refatorei o exemplo para delegar partes de `main`
+para as funções `start_jobs` e `report`).
+Escrevi um
+https://fpy.li/19-26[_README.md_]
+no mesmo diretório explicando o problema e a solução.((("", startref="PPmulticore19")))
+====
+
+
+==== Experimentando com mais ou menos processos
+
+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]
+----
+$ 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 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 <>.
+
+[[procs_x_time_fig]]
+.Mediana dos tempos de execução para cada número de subprocessos de 1 a 20. O maior tempo mediano foi 40,81s, com 1 processo. O tempo mediano mais baixo foi 10,39s, com 6 processos, indicado pela linha pontilhada.
+image::../images/flpy_1902.png[Mediana dos tempos de execução para cada número de processos, de 1 a 20.]
+
+Neste laptop de 6 núcleos, o menor tempo mediano ocorreu com 6 processos: 10.39s—marcado pela linha pontilhada na <>.
+Seria de se esperar que o tempo de execução aumentasse após 6 processos, devido à disputa pela CPU,
+e ele atingiu um máximo local de 12.51s, com 10 processos.
+Eu não esperava e não sei explicar por que o desempenho melhorou com 11 processos e permaneceu praticamente igual com 13 a 20 processos,
+com tempos medianos apenas ligeiramente maiores que o menor tempo mediano com 6 processos.
+
+[[thread_non_solution_sec]]
+==== 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:[Veja
+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 (<>).
+
+As((("", startref="CMprocess19"))) demais seções nesse capítulo procuram responder à questão:
+
+[quote]
+____
+Dadas as limitações discutidas até aqui, como é possível que Python seja tão bem-sucedido em um mundo de CPUs com múltiplos núcleos?
+____
+
+
+[[py_in_multicore_world_sec]]
+=== Python no mundo paralelo
+
+Considere((("Python", "functioning with multicore processors",
+id="Pmulti19")))((("concurrency models", "multicore processors and",
+id="CMmulti19")))((("multicore processing", "increased availability of"))) o
+seguinte trecho do artigo
+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 e muito citado desde então:
+
+[quote]
+____
+
+Os fabricantes e arquiteturas de processadores mais importantes, desde Intel e 
+AMD até Sparc e PowerPC, esgotaram o potencial da maioria das abordagens
+tradicionais de aumento do desempenho das CPUs. Ao invés de aumentar a frequência
+do _clock_ [dos processadores] e acelerar o processamento das instruções
+sequenciais, eles estão se voltando em massa para o
+hyper-threading 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 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 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 algoritmo 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,
+graças a bibliotecas e arquiteturas de software que contornam as limitações do CPython.
+
+Agora vamos discutir como Python é usado em administração de sistemas, ciência de dados,
+e desenvolvimento de aplicações para servidores no mundo do processamento distribuído e dos multi-núcleos de 2023.
+
+==== 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 (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 criados pela grande
+comunidade de usuários da linguagem.
+
+<<<
+Na administração de sistemas, scripts Python automatizam tarefas de configuração, enviando
+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á muitos projetos populares baseados em Python para gerenciar clusters (_agrupamentos_) de servidores:
+ferramentas como
+https://fpy.li/19-30[_Ansible_] e
+https://fpy.li/19-31[_Salt_],
+e 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."
+
+
+==== Ciência de dados
+
+A ciência de dados—incluindo((("multicore processing", "data science")))((("data science"))) a inteligência artificial—e a computação científica estão muito bem servidas pelo Python.
+
+Aplicações nesses campos são de processamento intensivo, mas os usuários de Python se beneficiam de um vasto ecossistema de bibliotecas de computação numérica, escritas em
+C, {cpp}, Fortran, Cython, etc.—muitas delas capazes de aproveitar os benefícios de máquinas multi-núcleo, GPUs, e/ou computação paralela distribuída em clusters heterogêneos.
+
+Em 2021, o ecossistema de ciência de dados de Python já incluía algumas ferramentas impressionantes:
+
+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, que pode ser executado através da rede em máquinas remotas.
+    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 inclui 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»]
+    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 _PyTorch_ pelo Facebook.
+
+https://fpy.li/dask[Dask]::
+    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 recomendo 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 aproveitam o melhor de Python e superam as
+limitações do runtime do CPython.
+
+<<<
+[[server_side_sec]]
+==== 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
+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*
+
+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 fazem o que precisa ser feito.footnote:[Fonte:
+Thoughtworks Technology Advisory Board, https://fpy.li/19-40[_Technology
+Radar—November 2015_].]
+
+____
+
+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).
+
+Observe a <>, um dos muitos diagramas de arquitetura do livro de Kleppmann.
+
+[[one_possible_architecture_fig]]
+.Uma arquitetura possível combinando diversos componentes. Diagrama adaptado da Figura 1-1, de _Designing Data-Intensive Applications_ de Martin Kleppmann (O'Reilly).
+image::../images/flpy_1903.png[align="center",pdfwidth=12cm]
+
+
+Estes são 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 documentos: _Apache CouchDB_, _MongoDB_
+* Full-text indexes (_índices de texto integral_): _Elasticsearch_, _Apache Solr_
+* Filas de mensagens: _RabbitMQ_, _Redis_
+
+Há outros produtos de código aberto extremamente robustos em cada uma dessas categorias.
+Os grandes fornecedores de serviços na nuvem também oferecem suas próprias alternativas proprietárias
+
+<<<
+O diagrama de Kleppmann é genérico e independente da linguagem—como seu livro.
+Para aplicações de servidor em Python, dois componentes específicos são comumente utilizados:
+
+* Um servidor de aplicação, para distribuir a carga entre várias instâncias da
+aplicação Python. O servidor de aplicação apareceria perto do topo na
+<>, processando as requisições dos clientes antes
+delas chegarem ao código da aplicação.
+
+* Uma fila de tarefas construída em torno da fila de mensagens no lado direito da <>, oferecendo uma API de alto nível e mais fácil de usar, para distribuir tarefas para processos rodando em outras máquinas.
+
+As duas próximas seções exploram esses componentes, recomendados pelas boas práticas de implementações de aplicações Python de servidor.
+
+
+[[wsgi_app_server_sec]]
+==== Servidores de aplicação WSGI
+
+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 integrar os dois diagramas, a
+<> inteira substituiria o
+retângulo sólido _Application code_ (código da
+aplicação) no topo da <>.
+
+====
+
+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/gunicorn[_Gunicorn_]
+
+* https://fpy.li/19-43[_NGINX Unit_]
+
+[[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 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[align="center",pdfwidth=11cm]
+
+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.
+
+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ém, o _uWSGI_ é mais difícil de configurar corretamente que o
+_Gunicorn_.footnote:[Os engenheiros da Bloomberg Peter Sperl e 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_.
+O _mod_wsgi_ e o _Gunicorn_ só suportam aplicações Web Python,
+enquanto o _uWSGI_ e o _NGINX Unit_ funcionam também com outras linguagens.
+Para saber mais, consulte a documentação de cada um deles.
+
+O ponto principal: todos esses servidores de aplicação podem, potencialmente, utilizar todos os núcleos de CPU no servidor, criando múltiplos processos Python para executar aplicações Web tradicionais escritas no bom e velho código sequencial em _Django_, _Flask_, _Pyramid_, etc.
+Isto explica como é possível ganhar a vida como desenvolvedor Python em aplicações _server side_ sem nunca ter estudado os módulos `threading`, `multiprocessing`, ou `asyncio`:
+o servidor de aplicação lida de forma transparente com a concorrência.
+
+[[asgi_note]]
+.ASGI—Asynchronous Server Gateway Interface
+[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`, 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 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.
+
+[[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 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 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.
+
+<<<
+[NOTE]
+====
+
+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 Django _produz_ requisições de serviç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:
+
+[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.
+* Assegurar-se de que algo seja feito, através de 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 à medida que a demanda cresce.
+Por isso a _Celery_ e a _RQ_ são chamadas de filas de tarefas distribuídas.
+
+Lembre-se de que nosso simples _procs.py_ (<>) usava duas filas:
+uma para requisitar 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 a _RabbitMQ_ ou a _Amazon SQS_, e também alguns bancos de dados para armazenar resultados.
+
+<<<
+Isto encerra nossa introdução à concorrência em Python.
+Os dois próximos capítulos continuam nesse tema com mais código, demonstrando 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:
+
+* Threads, com o pacote `threading`
+* Processo, com `multiprocessing`
+* Corrotinas assíncronas com `asyncio`
+
+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.
+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 interrompe periodicamente 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 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últiplos núcleos. A GIL de Python
+faz com que as threads sejam mais lentas 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 <>.
+Por exemplo, a GIL não afeta muitos dos casos de uso de Python em administração de sistemas.
+Por outro lado, as comunidades de ciência de dados e de desenvolvimento para servidores evitaram os problemas com a GIL usando soluções robustas, criadas sob medida para suas necessidades específicas.
+
+<<<
+As últimas duas seções mencionaram os dois elementos comuns que sustentam o uso de Python em aplicações de servidor escaláveis:
+servidores de aplicação WSGI e filas de tarefas distribuídas.
+
+[[concurrency_further_reading_sec]]
+=== Para saber mais
+
+Este((("concurrency models", "further reading on", id="CMfurther19"))) capítulo tem uma extensa lista de referências, então a dividi em subseções.
+
+[[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 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 a _threading_ em 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).
+
+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://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) têm 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),
+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 a _Celery_.
+
+_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]
+____
+Estes temas se repetem com frequência:
+
+* Programação com threads torna o código difícil de analisar.
+
+* Programação com threads é um modelo ineficiente para concorrência em larga escala (milhares de tarefas concorrentes).
+____
+
+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 até os fáceis são desafiadores.
+
+
+==== A GIL
+
+Se((("Global Interpreter Lock (GIL)"))) você ficou curioso sobre a GIL, lembre-se de 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-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_):
+um mergulho profundo no suporte nativo de Python a threads e processos,
+incluindo o gerenciamento da GIL por extensões usando a API C/Python.
+
+David Beazley apresentou uma exploração detalhada em https://fpy.li/19-65[_Understanding the Python GIL_]
+(Entendendo a GIL de Python).footnote:[Agradeço a Lucas Brunialti por me enviar um link para essa palestra.]
+No slide 54 da https://fpy.li/19-66[apresentação],
+Beazley relata um aumento no tempo de processamento de um benchmark específico 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 um novo algoritmo para a GIL) no relatório de bug submetido por Beazley:
+https://fpy.li/19-68[_Python issue #7946_].
+
+
+==== Concorrência além da biblioteca padrão
+
+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 intituladas
+_Development Environments_, _Data_, _Web Development_ e _DevOps_, entre outras.
+
+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_] 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_, escrito inteiramente em Python."
+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 publicaram o livro gratuitamente online em https://fpy.li/19-76[_cosmicpython.com_].
+
+Duas bibliotecas elegantes e fáceis de usar para 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 laço `for` por múltiplas CPUs.
+Ambos os pacotes são baseados na biblioteca _multiprocessing_.
+
+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 para execução em paralelo.
+Snow é o autor da https://fpy.li/pep554[_PEP 554-Multiple Interpreters in the Stdlib_]
+(Múltiplos interpretadores na biblioteca padrão).
+A PEP 554 assenta as bases para melhorias futuras,
+que podem um dia permitir que Python use múltiplos núcleos sem pagar os altos custos de inicialização, memória, e comunicação
+do _multiprocessing_.
+Um dos grandes empecilhos é a iteração entre múltiplos subinterpretadores ativos e extensões que assumem a existência de um único interpretador.
+
+Mark Shannon—também um mantenedor do Python—criou uma
+https://fpy.li/19-80[«tabela»] bem ú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
+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 por mensagens
+enviadas por 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-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_],
+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 direta ao Python,
+mas são importantes para aprofundar o tema das arquiteturas escaláveis com
+suporte à concorrência.
+
+==== Concorrência e escalabilidade para além de Python
+
+https://fpy.li/19-92[_RabbitMQ in Action_] (Manning), de Alvaro Videla e Jason J. W. Williams, é uma introdução muito bem escrita ao _RabbitMQ_ e ao padrão AMQP (_Advanced Message Queuing Protocol_, Protocolo Avançado de Enfileiramento de Mensagens), com exemplos em Python, PHP, e Ruby.
+Independente do resto de seu stack tecnológico, e mesmo se você planeja usar _Celery_ com _RabbitMQ_ debaixo dos panos,
+recomendo esse livro por sua abordagem dos conceitos, da motivação e dos modelos das filas de mensagem distribuídas, bem como a operação e configuração do _RabbitMQ_ em larga escala.
+
+Aprendi muito lendo https://fpy.li/19-93[_Seven Concurrency Models in Seven Weeks_],
+de Paul Butcher (Pragmatic Bookshelf), que traz o eloquente subtítulo _When Threads Unravel_.footnote:[NT: Trocadilho intraduzível com thread no sentido de "fio" ou "linha", algo como "Quando as linhas desfiam."]
+O capítulo 1 do livro apresenta os conceitos centrais e os desafios da programação com threads e travas em Java.footnote:[As APIs `threading` e `concurrent.futures` do Python foram fortemente influenciadas pela biblioteca padrão de Java.]
+Os outros seis capítulos do livro são dedicados ao que o autor considera as melhores alternativas para programação concorrente e paralela, e como funcionam com diferentes linguagens, ferramentas e bibliotecas.
+Os exemplos usam Java, Clojure, Elixir, e
+C (no capítulo sobre programação paralela com o framework https://fpy.li/19-94[_OpenCL_]).
+O modelo CSP é exemplificado com código Clojure, e não Go—que popularizou esta abordagem.
+Elixir é a linguagem dos exemplos que ilustram o modelo de atores.
+Um https://fpy.li/19-95[«capítulo bonus»] (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 criou uma série de artigos 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 da série»] é 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_]
+(Projetando Aplicações Intensivas em Dados), 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 assistir ao excelente 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)
+(https://fpy.li/19-99[«video só para assinantes»] na _O'Reilly Learning Platform_).
+Apesar do título, aos 9:35 no vídeo, Cesarini explica:
+
+[quote]
+____
+Muito pouco do que vou dizer será específico de Erlang […].
+Resta o fato de que o Erlang remove muitas dificuldades acidentais no desenvolvimento de sistemas resilientes 
+que nunca falham, além de serem escaláveis.
+Então será mais fácil se vocês usarem Erlang ou uma linguagem rodando na máquina virtual Erlang.
+____
+
+Aquele workshop foi baseado nos últimos quatro capítulos do
+https://fpy.li/19-100[_Designing for Scalability with Erlang/OTP_]
+de Francesco Cesarini e Steve Vinoski (O'Reilly).
+
+Desenvolver((("KISS principle"))) sistemas distribuídos é desafiador e empolgante, mas cuidado com a
+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_: "Deixe Simples, Idiota")
+continua sendo uma recomendação sábia de engenharia.
+
+Veja também o artigo
+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 "têm desempenho pior que uma thread em todas as configurações documentadas."
+Estes((("", startref="CMfurther19"))) resultados me lembram uma piada clássica entre _hackers_:
+
+[quote]
+____
+Meu script Perl é mais rápido que seu cluster Hadoop.
+____
+
+
+[[concurrency_models_soapbox]]
+.Ponto de vista
+****
+
+*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 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.
+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 a instrução `RETURN` saltar de volta.
+Subrotinas 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ênçãos.
+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.
+Pode ser fácil para um projetista de linguagens acrescentá-las a uma linguagem,
+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 _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 deste
+capítulo. Então temos aqui os 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 ter um estado interno, mas não pode compartilhar este estado com outros atores.
+* Atores só podem se comunicar enviando e recebendo mensagens.
+* Mensagens contém só 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 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ó acho que lidar com tais mecanismos de baixo nível não é um bom uso do meu tempo
+quando escrevo aplicações—e não módulos do kernel, drivers de hardware, ou servidores de bancos de dados.
+
+Sempre me reservo o direito de mudar de opinião. Mas neste momento, estou
+convencido de 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/vol3/cap20.adoc b/vol3/cap20.adoc
new file mode 100644
index 00000000..85329284
--- /dev/null
+++ b/vol3/cap20.adoc
@@ -0,0 +1,1277 @@
+[[ch_executors]]
+== Executores concorrentes
+:example-number: 0
+:figure-number: 0
+
+[quote, Michele Simionato, profundo pensador de Python.]
+____
+Quem fala mal de threads são tipicamente programadoras de sistemas,
+que têm em mente casos de uso que a programadora de aplicações típica nunca encontrará na vida.[...]
+Em 99% dos casos de uso que a programadora de aplicações poderá encontrar,
+o modelo simples de disparar um monte de threads independentes
+e coletar os resultados em uma fila é tudo que se precisa
+saber.footnote:[Trecho do texto 
+https://fpy.li/20-1[_Threads, processes and concurrency in Python: some thoughts_]
+(Threads, processos e concorrência em Python: algumas reflexões), resumido assim pelo autor:
+"Removendo exageros sobre a (não-)revolução dos múltiplos núcleos e alguns comentários
+sensatos (oxalá) sobre threads e outras formas de concorrência."]
+____
+
+
+Este((("concurrent executors", "purpose of"))) capítulo se concentra nas subclasses
+de `concurrent.futures.Executor`, que incorporam o modelo descrito por
+Michele Simionato: "disparar um monte
+de threads independentes e coletar os resultados em uma fila".
+Executores concorrentes implementam internamente este modelo,
+não apenas com threads mas também com processos—que oferecem melhor desempenho
+em tarefas de processamento intensivas no uso de CPUs.
+
+Também((("futures", "definition of term"))) introduzo aqui o conceito de
+_futures_—objetos que representam a execução assíncrona de uma operação,
+similares aos _promises_ de JavaScript.
+Esta ideia é a fundação de `concurrent.futures`
+bem como do pacote `asyncio`, assunto do <>.
+
+
+=== Novidades neste capítulo
+Mudei((("concurrent executors", "significant changes to"))) o título deste capítulo de
+"Concorrência com futures" para "Executores concorrentes",
+porque os executores são o recurso de alto nível mais importante tratado aqui.
+_Futures_ são objetos de baixo nível, tratados na <>,
+mas quase invisíveis no resto do capítulo.
+
+Todos os exemplos de clientes HTTP agora usam a biblioteca
+https://fpy.li/httpx[_HTTPX_], que oferece APIs síncronas e assíncronas.
+
+Simplifiquei a configuração para os experimentos na <>,
+porque desde o Python 3.7 o pacote https://fpy.li/20-2[`http.server`]
+é _multi-thread_. Antes, o `http.server` só usava uma thread,
+então não servia para experimentos com clientes concorrentes,
+o que me obrigou a usar um servidor externo na primeira edição.
+
+A <> agora demonstra como um executor simplifica o
+código que vimos na <>.
+
+Por fim, movi a maior parte da teoria para o <>,
+_Modelos de concorrência em Python_.
+
+[[ex_web_downloads_sec]]
+=== Downloads concorrentes da Web
+
+A((("network I/O", "essential role of concurrency in",
+id="IOconcur20")))((("concurrent executors", "concurrent Web downloads",
+id="CEwebdown20"))) concorrência é essencial para uma comunicação eficiente via
+rede: em vez de esperar de braços cruzados por respostas de máquinas remotas, a
+aplicação pode fazer outra coisa enquanto a resposta não
+chega.
+
+Para demonstrar, escrevi três programas simples que baixam da Web imagens de 20 bandeiras de países.
+O primeiro, _flags.py_, roda sequencialmente:
+ele só requisita a imagem seguinte quando a anterior foi baixada e salva localmente.
+Os outros dois scripts fazem downloads concorrentes:
+eles requisitam várias imagens quase ao mesmo tempo, e as salvam conforme chegam.
+O script _flags_threadpool.py_ usa o pacote `concurrent.futures`,
+enquanto _flags_asyncio.py_ usa `asyncio`.
+
+Os scripts baixam imagens de _https://fluentpython.com_, que usa uma CDN
+(_Content Delivery Network_, Rede de Entrega de Conteúdo),
+então você pode observar resultados mais lentos nos primeiros testes.
+Obtive os resultados no <> após vários testes,
+então o cache da CDN estava "quente" (carregado com os dados).
+
+O <> mostra o resultado da execução dos três scripts, três vezes cada um.
+
+[[ex_flags_sample_runs]]
+.Saida dos scripts flags.py, flags_threadpool.py, e flags_asyncio.py
+====
+[source, text]
+----
+$ python3 flags.py
+BD BR CD CN DE EG ET FR ID IN IR JP MX NG PH PK RU TR US VN  <1>
+20 flags downloaded in 7.26s  <2>
+$ python3 flags.py
+BD BR CD CN DE EG ET FR ID IN IR JP MX NG PH PK RU TR US VN
+20 flags downloaded in 7.20s
+$ python3 flags.py
+BD BR CD CN DE EG ET FR ID IN IR JP MX NG PH PK RU TR US VN
+20 flags downloaded in 7.09s
+$ python3 flags_threadpool.py
+DE BD CN JP ID EG NG BR RU CD IR MX US PH FR PK VN IN ET TR
+20 flags downloaded in 1.37s  <3>
+$ python3 flags_threadpool.py
+EG BR FR IN BD JP DE RU PK PH CD MX ID US NG TR CN VN ET IR
+20 flags downloaded in 1.60s
+$ python3 flags_threadpool.py
+BD DE EG CN ID RU IN VN ET MX FR CD NG US JP TR PK BR IR PH
+20 flags downloaded in 1.22s
+$ python3 flags_asyncio.py  <4>
+BD BR IN ID TR DE CN US IR PK PH FR RU NG VN ET MX EG JP CD
+20 flags downloaded in 1.36s
+$ python3 flags_asyncio.py
+RU CN BR IN FR BD TR EG VN IR PH CD ET ID NG DE JP PK MX US
+20 flags downloaded in 1.27s
+$ python3 flags_asyncio.py
+RU IN ID DE BR VN PK MX US IR ET EG NG BD FR CN JP PH CD TR  <5>
+20 flags downloaded in 1.42s
+----
+====
+
+<1> A saída de cada execução começa com os códigos dos países de cada bandeira a medida que as imagens são baixadas, e termina com uma mensagem mostrando o tempo decorrido. Note que as siglas dos países estão em ordem alfabética, para podermos comparar com a ordem dos resultados nas versões concorrentes.
+
+<2> _flags.py_ precisou em média de 7,18s para baixar 20 imagens.
+
+<3> A média para _flags_threadpool.py_ foi 1,40s.
+
+<4> Já _flags_asyncio.py_, obteve um tempo médio de 1,35s.
+
+<5> Note a ordem dos códigos dos países: nos scripts concorrentes, as imagens foram baixadas em ordem diferente a cada vez.
+
+As versões concorrentes são mais de cinco vezes mais rápidas que o script sequencial,
+mas entre as versões concorrentes não há diferença significativa de desempenho. 
+Nestes exemplos, a tarefa é baixar 20 arquivos, com poucos kilobytes cada um.
+Se você escalar a tarefa para centenas de downloads,
+os scripts concorrentes podem superar o código sequencial por um fator de 20 ou mais.
+
+<<<
+[WARNING]
+====
+
+Ao testar clientes HTTP concorrentes acessando servidores Web públicos, você pode
+acidentalmente lançar um ataque de negação de serviço (DoS, _Denial of Service_,
+negação de serviço),
+ou se tornar suspeito de tentar um ataque. No caso do
+<> não há problema, pois aqueles scripts estão codificados
+para realizar apenas 20 requisições. Mais adiante neste capítulo usaremos o
+pacote `http.server` de Python para executar localmente outros testes com
+centenas de requisições.
+
+====
+
+Vamos agora estudar as implementações de dois dos scripts testados no
+<>: _flags.py_ e _flags_threadpool.py_. Vou deixar o
+terceiro, _flags_asyncio.py_, para o <>, mas queria demonstrar os três
+juntos para fazer duas observações:
+
+. Independente dos elementos de concorrência que você use—threads ou
+corrotinas—haverá um ganho enorme de desempenho sobre código sequencial em
+operações de E/S de rede, se o script for escrito corretamente.
+
+. Para clientes HTTP que podem controlar quantas requisições eles fazem, não há
+diferenças significativas de desempenho entre threads e
+corrotinas.footnote:[Para servidores que podem receber requisições de muitos
+clientes, há uma diferença: as corrotinas escalam melhor, pois usam menos
+memória que as threads, e também reduzem o custo das mudanças de contexto, que
+mencionei na <>.]
+
+Agora vamos ao código.((("", startref="IOconcur20")))
+
+
+==== Um script de download sequencial
+
+O <> contém((("network I/O", "sequential download script",
+id="IOsequen20"))) a implementação de _flags.py_, o primeiro script que rodamos
+no <>. Não é muito interessante, mas vamos reutilizar a
+maior parte do código e das configurações para implementar os scripts
+concorrentes, então ele merece alguma atenção.
+
+[NOTE]
+====
+
+Por uma questão didática, o <> não faz tratamento de erros.
+Vamos tratar exceções em outros exemplos, mas agora vamos focar na
+estrutura básica do código, para facilitar a comparação deste script com os
+scripts que usam concorrência.
+
+====
+
+<<<
+[[flags_module_ex]]
+.flags.py: script de download sequencial; algumas funções serão reutilizadas pelos outros scripts
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags.py[tags=FLAGS_PY]
+----
+====
+
+<1> Importa a biblioteca `httpx`. Ela não é parte da biblioteca padrão. Assim,
+por convenção, a importação aparece após os módulos da biblioteca padrão e uma
+linha em branco.
+
+<2> Lista do código de país ISO 3166 para os 20 países mais populosos, em ordem
+decrescente de população.
+
+<3> O diretório com as imagens das bandeiras.footnote:[As imagens são
+originalmente do https://fpy.li/20-4[CIA World Factbook], uma publicação de
+domínio público do governo norte-americano. Copiei as imagens para o meu site,
+para evitar o risco de lançar um ataque de DoS contra _cia.gov_.]
+
+<4> Diretório local onde as imagens são salvas.
+
+<5> Salva os bytes de `img` em `filename` no `DEST_DIR`.
+
+<6> Dado um código de país, constrói a URL e baixa a imagem, retornando o
+conteúdo binário da resposta.
+
+<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 neste exemplo, mas eu queria destacar
+essa importante diferença entre _HTTPX_ e _requests_. Além disso, definir
+`follow_redirects=True` no exemplo permite que eu coloque os
+arquivos de imagem em outro lugar no futuro. Considero sensata a configuração default
+do _HTTPX_, `follow_redirects=False`, pois
+redirecionamentos inesperados podem mascarar requisições desnecessárias e
+complicar o diagnóstico de erros.]
+
+<9> Não há tratamento de erros neste script, mas o método `.raise_for_status` 
+lança uma exceção se o status da resposta HTTP não está na faixa 2XX, para evitar
+falhas silenciosas.
+
+<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; devolve 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 quebra de linha no final de
+cada `print` por um espaço, assim todos os códigos de país aparecem
+na mesma linha. O argumento `flush=True` é necessário porque,
+por default, o Python usa um buffer de linha na saída padrão, 
+então só após uma quebra de linha o resultado seria visível no terminal.
+A opção `flush=True` força o esvaziamento do buffer,
+então podemos ver as siglas aparecendo progressivamente.
+
+<13> `main` precisa ser invocada passando a função que fará os downloads; assim
+podemos usar `main` como uma função de biblioteca com outras implementações de
+`download_many` nos exemplos de `threadpool` e `ascyncio`.
+
+<14> Cria o `DEST_DIR` se necessário; não acusa erro se o diretório existir.
+
+<15> Registra e apresenta o tempo decorrido após rodar a função `downloader`.
+
+<16> Invoca `main` com a função `download_many`.
+
+[TIP]
+====
+A biblioteca https://fpy.li/httpx[_HTTPX_] é inspirada no pacote pythônico
+https://fpy.li/20-5[_requests_],
+mas foi desenvolvida sobre bases mais modernas.
+Em particular, _HTTPX_ oferece APIs síncronas e assíncronas,
+então podemos usá-la em todos os exemplos de clientes HTTP neste capítulo e no próximo.
+
+A biblioteca padrão do Python contém o módulo `urllib.request`,
+mas sua API é somente síncrona, e não é nada amigável.
+====
+
+Não há nada de novo em _flags.py_, mas serve de base para comparação
+com outros scripts, e o usei como uma biblioteca, para evitar código redundante ao implementá-los.
+
+Vamos ver agora implementação concorrente usando `concurrent.futures`.((("", startref="IOsequen20")))
+
+[[downloading_with_futures_sec]]
+==== Download com `concurrent.futures`
+
+Os((("network I/O", "downloading with concurrent.futures",
+id="IOfutures20")))((("concurrent.futures", "downloading with", id="confut20")))
+principais recursos do pacote `concurrent.futures` são as classes
+`ThreadPoolExecutor` e `ProcessPoolExecutor`, que implementam uma API para
+submeter  invocáveis (_callables_) para execução em um banco de threads ou
+processos (_thread pool_ ou _process pool_).
+As classes gerenciam de forma transparente um banco de threads ou
+processos de trabalho, e filas para distribuição de tarefas e coleta de
+resultados. Mas a interface é de um nível muito alto,
+então não precisamos lidar diretamente com estes
+componentes em um caso de uso simples como nossos downloads de bandeiras.
+
+O <> mostra a forma mais fácil de implementar os downloads de forma concorrente, usando o método `ThreadPoolExecutor.map`.
+
+<<<
+[[flags_threadpool_ex]]
+.flags_threadpool.py: script de download com threads, usando `futures.ThreadPoolExecutor`
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags_threadpool.py[tags=FLAGS_THREADPOOL]
+----
+====
+<1> Reutiliza algumas funções do módulo `flags` (<>).
+<2> Função para baixar uma única imagem; é ela que cada thread de trabalho vai executar.
+<3> Instancia o `ThreadPoolExecutor` como um gerenciador de contexto;
+o método `executor.__exit__` vai invocar `executor.shutdown(wait=True)`,
+que bloqueia até que todas as threads terminem de rodar.
+<4> O método `map` é similar à função embutida `map`, mas a função `download_one` será chamada de forma concorrente por múltiplas threads; ele devolve um gerador que você pode iterar para recuperar o valor devolvido por cada chamada da função—neste caso, cada chamada a `download_one` vai retornar um código de país.
+<5> Devolve 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 devolvido 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 laço `for` na função `download_many` do <>. Esta é
+uma refatoração comum quando em código concorrente: transformar
+o corpo de um laço `for` sequencial em uma função a ser chamada de modo
+concorrente.
+
+[TIP]
+====
+O <> é muito curto porque pude reutilizar a maior parte das funções do script sequencial _flags.py_.
+Uma das melhores características do `concurrent.futures` é facilitar a execução concorrente de código sequencial.
+====
+
+O construtor de `ThreadPoolExecutor` recebe muitos argumentos além dos mostrados
+aqui, mas o primeiro e mais importante é `max_workers`, definindo o número
+máximo de threads de trabalho a serem executadas. Quando `max_workers` é `None`
+(o default), `ThreadPoolExecutor` decide seu valor
+usando a seguinte expressão, desde o Python 3.8:
+
+[source, python]
+----
+max_workers = min(32, os.cpu_count() + 4)
+----
+
+A justificativa é apresentada na https://fpy.li/aj[documentação de `ThreadPoolExecutor`]:
+
+[quote]
+____
+
+Este valor default reserva pelo menos 5 threads de trabalho para tarefas de E/S.
+Ele utiliza no máximo 32 núcleos da CPU para tarefas de processamento que
+liberam a GIL. E ele evita usar implicitamente recursos demais em
+máquinas com muitos núcleos.
+
+`ThreadPoolExecutor` agora também reutiliza threads de inativas antes
+de iniciar a quantidade de threads de trabalho definida em `max_workers`.
+
+____
+
+Concluindo: o valor default calculado de `max_workers` é razoável, e
+`ThreadPoolExecutor` evita iniciar novas threads de trabalho desnecessariamente.
+Entender a lógica por trás de `max_workers` pode ajudar a decidir quando e como
+definir um valor diferente do default em seu código.
+
+<<<
+A biblioteca chama-se `concurrency.futures`, mas não há qualquer _future_
+à vista no <>,
+então você pode estar se perguntando onde eles estão.
+A próxima seção explica.((("", startref="confut20")))((("", startref="IOfutures20")))
+
+[[where_futures_sec]]
+==== Onde estão os _futures_?
+
+Os((("network I/O", "role of futures", id="IOfuturesrole20")))((("futures",
+"basics of", id="Fbasic20"))) _futures_ (literalmente "futuros") são componentes
+centrais de `concurrent.futures` e de `asyncio`, mas como usuários dessas
+bibliotecas, raramente os vemos. O <> depende de _futures_
+por trás do palco, mas o código apresentado não lida diretamente com objetos
+desta classe. Esta seção apresenta uma visão geral dos _futures_, com um exemplo
+mostrando-os em ação.
+
+Desde o Python 3.4, há duas classes chamadas `Future` na biblioteca padrão:
+`concurrent.futures.Future` e `asyncio.Future`.
+Elas têm o mesmo propósito:
+uma instância de qualquer uma das classes `Future` representa um processamento adiado,
+que pode ou não ter sido completado.
+Isso é algo similar à classe `Deferred` no Twisted,
+a classe `Future` no Tornado, e objetos `Promise` em JavaScript moderno.
+
+Os _futures_ encapsulam operações pendentes de forma que possamos colocá-los em filas,
+verificar se terminaram, e recuperar resultados (ou exceções) quando eles ficam disponíveis.
+
+É importante entender que eu e você não devemos criar _futures_ diretamente:
+eles devem ser instanciados exclusivamente pelo framework de concorrência,
+seja ele `concurrent.futures` ou `asyncio`.
+O motivo é que uma instância de `Future` representa algo que será executado em algum momento,
+portanto precisa ser agendado para rodar, e quem agenda tarefas é o framework.
+
+Especificamente, instâncias de `concurrent.futures.Future` são criadas como resultado de submeter um objeto invocável (_callable_)
+para execução a uma subclasse de `concurrent.futures.Executor`.
+Por exemplo, o método `Executor.submit()` recebe um invocável, agenda sua execução, e devolve um `Future`.
+
+O código da aplicação não deve mudar o estado de um _future_:
+o framework de concorrência muda o estado de um _future_ quando
+o processamento que ele representa termina, e não controlamos quando isso acontece.
+
+<<<
+Os dois tipos de `Future` têm um método `.done()` não-bloqueante, que devolve um `bool` informando se o invocável encapsulado por aquele `future` foi ou não executado.
+Entretanto, em vez de perguntar repetidamente se um _future_ terminou, o código cliente em geral pede para ser notificado.
+Por isso as duas classes `Future` têm um método `.add_done_callback()`:
+você passa a ele uma função que será invocada com o _future_ como único argumento, quando o _future_ tiver terminado.
+Aquela função de callback será invocada na mesma thread ou processo de trabalho que rodou a função encapsulada no _future_.
+
+Há também um método `.result()`, que funciona igual nas duas classes quando a execução do _future_ termina:
+ele devolve o resultado do invocável, ou relança qualquer exceção que possa ter aparecido quando o invocável foi executado.
+Entretanto, quando o _future_ não terminou, o comportamento do método `result` é bem diferente entre os dois sabores de `Future`.
+Em uma instância de `concurrency.futures.Future`,
+invocar `f.result()` vai bloquear a thread que chamou até o resultado ficar pronto.
+Um argumento `timeout` opcional pode ser passado, e se o _future_ não tiver terminado após aquele tempo, o método `result` gera um `TimeoutError`.
+O método `asyncio.Future.result` não suporta um timeout, e `await` é a forma preferencial de obter o resultado de _futures_ no `asyncio`—mas `await` não funciona com instâncias de `concurrency.futures.Future`.
+
+Várias funções em ambas as bibliotecas devolvem _futures_; outras os usam em sua implementação de uma forma transparente para o usuário.
+Um exemplo deste último caso é o `Executor.map`, que vimos no <>:
+ele devolve 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.
+
+==== Lidando com _futures_
+
+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 devolve 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 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`.
+O código de `download_many` aumentou de 5 para 17 linhas,
+mas agora podemos inspecionar os misteriosos _futures_.
+As outras funções são idênticas às do <>.
+
+[[flags_threadpool_futures_ex]]
+.flags_threadpool_futures.py: substitui `executor.map` por `executor.submit` e `futures.as_completed` na função `download_many`
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags_threadpool_futures.py[tags=FLAGS_THREADPOOL_AS_COMPLETED]
+----
+====
+[role="pagebreak-before less_space"]
+<1> Para esta demonstração, usa apenas os cinco países mais populosos.
+<2> Configura `max_workers` para `3`, para podermos ver os _futures_ pendentes na saída.
+<3> Itera pelos códigos de país em ordem alfabética, para deixar claro que os resultados vão aparecer fora de ordem.
+<4> `executor.submit` agenda o invocável a ser executado, e devolve o `future` representando essa operação pendente.
+<5> Armazena cada `future`, para podermos recuperá-los mais tarde com `as_completed`.
+<6> Mostra uma mensagem com o código do país e seu respectivo `future`.
+<7> `as_completed` produz _futures_ à medida que eles terminam.
+<8> Recupera o resultado deste `future`.
+<9> Exibe o `future` e seu resultado.
+
+A chamada a `future.result()` nunca bloqueará a thread neste
+exemplo, pois `future` está vindo de `as_completed`. O
+<> mostra a saída de uma execução do
+<>.
+
+[[flags_threadpool_futures_run]]
+.Saída de flags_threadpool_futures.py
+====
+[source, text]
+----
+$ python3 flags_threadpool_futures.py
+Scheduled for BR:  <1>
+Scheduled for CN: 
+Scheduled for ID: 
+Scheduled for IN:  <2>
+Scheduled for US: 
+CN  result: 'CN' <3>
+BR ID  result: 'BR' <4>
+ result: 'ID'
+IN  result: 'IN'
+US  result: 'US'
+
+5 downloads in 0.70s
+----
+====
+<1> Os _futures_ são agendados em ordem alfabética; o `repr()` de um `future` mostra seu estado: os três primeiros estão `running`, pois há três threads de trabalho.
+<2> Os dois últimos `futures` estão `pending`; esperando pelas threads de trabalho.
+<3> O primeiro `CN` aqui é a saída de `download_one` em uma thread de trabalho; o resto da linha é a saída de `download_many`.
+<4> Aqui, duas threads exibem códigos antes que `download_many` na thread principal possa mostrar o resultado da primeira thread.
+
+[TIP]
+====
+Recomendo brincar com _flags_threadpool_futures.py_.
+Se você o rodar várias vezes, verá a ordem dos resultados variar.
+Aumentar `max_workers` para `5` aumentará a variação na ordem dos resultados.
+Diminuir aquele valor para `1` fará o script rodar sequencialmente, e a ordem dos resultados será sempre a ordem das chamadas a `submit`.
+====
+
+<<<
+Vimos duas variantes do script de download usando `concurrent.futures`:
+uma no <> com `ThreadPoolExecutor.map` e
+uma no <> com `futures.as_completed`.
+Se tem curiosidade sobre o código de _flags_asyncio.py_,
+veja o <> do <>, onde ele é explicado.
+
+Agora vamos dar uma olhada rápida em um modo simples de evitar a GIL para
+tarefas que usam a CPU intensivamente, usando 
+`concurrent.futures`.((("", startref="Fbasic20")))((("", startref="IOfuturesrole20")))((("", startref="CEwebdown20")))
+
+[[launching_processes_sec]]
+=== 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://fpy.li/am[página de documentação] de
+`concurrent.futures` tem o 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 vários processos Python usando a classe
+`ProcessPoolExecutor`.
+
+As classes `ProcessPoolExecutor` e `ThreadPoolExecutor` implementam a interface
+https://fpy.li/20-9[`Executor`],
+então é fácil mudar de uma solução baseada em threads para uma baseada em processos usando `concurrent.futures`.
+
+Não há vantagem em usar um `ProcessPoolExecutor` no exemplo de download de bandeiras ou em qualquer tarefa limitada por E/S.
+É fácil comprovar, alterando as seguintes linhas no <>:
+
+[source, python]
+----
+def download_many(cc_list: list[str]) -> int:
+    with futures.ThreadPoolExecutor() as executor:
+----
+
+para:
+
+[source, python]
+----
+def download_many(cc_list: list[str]) -> int:
+    with futures.ProcessPoolExecutor() as executor:
+----
+
+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 of `ProcessPoolExecutor` é em tarefas de uso intensivo da CPU.
+Vamos voltar ao exemplo de teste de checagem de números primos da <>, e reescrevê-lo com `concurrent.futures`.
+
+[[multicore_prime_redux_sec]]
+==== A volta do checador de primos multi-núcleos
+
+Na <>, estudamos _procs.py_, um script que checava se alguns números grandes eram primos usando `multiprocessing`.
+No <> resolvemos o mesmo problema com o programa _proc_pool.py_, usando um `ProcessPool.Executor`.
+Do primeiro `import` até a chamada a `main()` no final, _procs.py_ tem 43 linhas de código não-vazias, e _proc_pool.py_ tem 31—uma redução de 28%.
+
+[[proc_pool_py]]
+.proc_pool.py: _procs.py_ reescrito com `ProcessPoolExecutor`
+====
+[source, python]
+----
+include::../code/20-executors/primes/proc_pool.py[tags=PRIMES_POOL]
+----
+====
+
+<1> Não precisamos importar `multiprocessing`, `SimpleQueue` etc.;
+`concurrent.futures` esconde tudo isso.
+
+<2> A tupla `PrimeResult` e a função `check` são as mesmas que vimos em
+_procs.py_, mas não precisamos mais das filas nem da função `worker`.
+
+<3> Em vez de definir quantos processos de trabalho serão
+usados se um argumento não for passado na linha de comando, atribuímos `None` a
+`workers` e deixamos o `ProcessPoolExecutor` decidir.
+
+<4> Aqui criei o `ProcessPoolExecutor` antes do bloco `with` em `⑦`, para poder
+mostrar o número real de processos na próxima linha.
+
+<5> `_max_workers_` é um atributo de instância não documentado de um
+`ProcessPoolExecutor`. Decidi usá-lo para mostrar o número de processos de
+trabalho criados quando a variável `workers` é `None`. O _Mypy_
+reclama quando eu acesso esse atributo, então coloquei o comentário `type:
+ignore` para silenciar o aviso.
+
+<6> Ordena os números a serem checados em ordem descendente. Isto vai mostrar a
+diferença no comportamento de _proc_pool.py_ quando comparado a _procs.py_. Veja
+a explicação após esse exemplo.
+
+<7> Usa o `executor` como um gerenciador de contexto.
+
+<8> A chamada a `executor.map` devolve as instâncias de `PrimeResult` retornadas
+por `check` na mesma ordem dos argumentos `numbers`.
+
+Se você rodar o <>, verá os resultados aparecendo em ordem rigorosamente descendente, como mostrado no <>.
+Por outro lado, a ordem da saída de _procs.py_ (da <>) é determinada pela dificuldade em checar se cada número é ou não primo.
+Por exemplo, _procs.py_ mostra o resultado para 7777777777777777 próximo ao topo, pois ele tem 7 como divisor, então `is_prime` rapidamente determina que ele não é primo.
+
+O número 7777777536340681 é 88191709^2^. Como este fator é grande, `is_prime` vai demorar mais para determinar que é um número composto,
+e ainda mais para descobrir que 7777777777777753 é primo. Por isso esses números aparecem perto do final da saída do _procs.py_ original.
+
+Ao rodar _proc_pool.py_, podemos observar não apenas a ordem descendente dos resultados, mas também que o programa parece emperrar após mostrar o resultado de 9999999999999999.
+
+[[proc_pool_py_output]]
+.Saída de proc_pool.py
+====
+[source, text]
+----
+$ ./proc_pool.py
+Checking 20 numbers with 12 processes:
+9999999999999999     0.000024s  # <1>
+9999999999999917  P  9.500677s  # <2>
+7777777777777777     0.000022s  # <3>
+7777777777777753  P  8.976933s
+7777777536340681     8.896149s
+6666667141414921     8.537621s
+6666666666666719  P  8.548641s
+6666666666666666     0.000002s
+5555555555555555     0.000017s
+5555555555555503  P  8.214086s
+5555553133149889     8.067247s
+4444444488888889     7.546234s
+4444444444444444     0.000002s
+4444444444444423  P  7.622370s
+3333335652092209     6.724649s
+3333333333333333     0.000018s
+3333333333333301  P  6.655039s
+ 299593572317531  P  2.072723s
+ 142702110479723  P  1.461840s
+               2  P  0.000001s
+Total time: 9.65s
+----
+====
+<1> Esta linha aparece muito rápido.
+<2> Esta linha demora mais de 9,5s para aparecer.
+<3> As linhas restantes aparecem quase imediatamente.
+
+Agora vamos entender o comportamento estranho de _proc_pool.py_:
+
+* Como mencionado antes, `executor.map(check, numbers)` devolve o resultado na mesma ordem em que `numbers` é submetido.
+* 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`. Neste laptop foram 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.
+* Quando o processo de trabalho encarregado de 9999999999999917 finalmente determina que ele é primo, todos os outros processos já completaram suas últimas tarefas, então os resultados aparecem logo depois.
+
+[NOTE]
+====
+
+Apesar do progresso de _proc_pool.py_ não ser tão visível quanto o de
+_procs.py_ na <>, o tempo total de execução é praticamente igual
+ao retratado na <> do <>, 
+para as mesmas quantidades de processos de trabalho e núcleos de CPU.
+
+====
+
+Entender como programas concorrentes se comportam não é fácil,
+então aqui está um segundo experimento que pode ajudar a visualizar o
+funcionamento de `Executor.map`.((("", startref="CFlaunch20")))((("",
+startref="CElaunch20")))((("", startref="Plaunch20")))
+
+=== Experimento com `Executor.map`
+
+
+Vamos((("concurrent executors", "Executor.map",
+id="CEexecutormap20")))((("Executor.map", id="execumap20"))) investigar
+`Executor.map`, agora usando um `ThreadPoolExecutor` com três threads de
+trabalho rodando cinco invocáveis que exibem mensagens com data/hora. O código
+está no <>, o resultado no <>.
+
+[[demo_executor_map_ex]]
+.demo_executor_map.py: Uma demonstração simples do método map de `ThreadPoolExecutor`
+====
+[source, python]
+----
+include::../code/20-executors/demo_executor_map.py[tags=EXECUTOR_MAP]
+----
+====
+
+<1> Esta função exibe o momento da execução no formato `[HH:MM:SS]` e os
+argumentos recebidos.
+
+<2> `loiter` significa "vadiar"; esta função não faz nada além mostrar uma
+mensagem quando inicia, cochilar por `n` segundos, e mostrar uma mensagem quando
+termina; usei tabulações para indentar as mensagens de acordo com o valor de `n` 
+
+<3> `loiter` devolve `n + 10`, para que o valor devolvido seja diferente do
+argumento `n`, tornando mais visível o fluxo de dados.
+
+<4> Cria um `ThreadPoolExecutor` com três threads.
+
+<5> Submete cinco tarefas para o `executor`. Como há três threads,
+apenas três daquelas tarefas vão iniciar imediatamente: a chamadas `loiter(0)`,
+`loiter(1)`, e `loiter(2)`; `executor.map` não bloqueia, retorna imediatamente.
+
+<6> Exibe `results` devolvido por `executor.map`: é um
+gerador, como se vê na saída no <>.
+
+<7> A chamada `enumerate` no laço `for` invocará implicitamente
+`next(results)`, que por sua vez invocará `f.result()` no _future_ (interno)
+`f`, representando a primeira chamada, `loiter(0)`. O método `result`
+bloqueará a thread até que o _future_ termine, portanto cada volta nesse laço vai
+esperar até que o próximo resultado esteja disponível.
+
+
+Sugiro que você rode o <> para ver o resultado sendo exibido incrementalmente.
+
+Para experimentar, altere o argumento `max_workers` do `ThreadPoolExecutor`.
+Mude também a chamada a `range`, que produz os argumentos para invocar `executor.map`—ou
+forneça uma lista de valores escolhidos, para criar intervalos diferentes.
+
+O <> mostra um teste do <>.
+
+[[demo_executor_map_run]]
+.Amostra da execução de demo_executor_map.py, do <>
+====
+[source, text]
+----
+$ python3 demo_executor_map.py
+[15:56:50] Script starting.  <1>
+[15:56:50] loiter(0): napping for 0s...  <2>
+[15:56:50] loiter(0): done.
+[15:56:50]      loiter(1): napping for 1s...  <3>
+[15:56:50]              loiter(2): napping for 2s...
+[15:56:50] results:   <4>
+[15:56:50]                      loiter(3): napping for 3s...  <5>
+[15:56:50] Waiting for individual results:
+[15:56:50] result 0: 10  <6>
+[15:56:51]      loiter(1): done. <7>
+[15:56:51]                              loiter(4): napping for 4s...
+[15:56:51] result 1: 11  <8>
+[15:56:52]              loiter(2): done.  <9>
+[15:56:52] result 2: 12
+[15:56:53]                      loiter(3): done.
+[15:56:53] result 3: 13
+[15:56:55]                              loiter(4): done.  <10>
+[15:56:55] result 4: 14
+----
+====
+
+<1> Este teste começou em 15:56:50.
+
+<2> A primeira thread executa `loiter(0)`, então vai cochilar por 0s e retornar
+antes mesmo da segunda thread ter chance de começar, mas YMMV.footnote:[Acrônimo
+de _your mileage may vary_, algo como "sua quilometragem pode variar", querendo
+dizer "seu caso pode ser diferente". Com threads, você nunca sabe a sequência
+exata de eventos que deveriam acontecer quase ao mesmo tempo; é possível que, em
+outra máquina, se veja `loiter(1)` começar antes de `loiter(0)` terminar,
+especialmente porque `sleep` sempre libera a GIL, então Python pode mudar para
+outra thread mesmo se você dormir por 0s.]
+
+<3> `loiter(1)` e `loiter(2)` começam imediatamente (como o banco de threads tem
+três threads de trabalho, é possível rodar três funções concorrentemente).
+
+<4> Isto mostra que `results` devolvido 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 processar `loiter(3)`.
+
+<6> Aqui é onde 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_ completar. Neste caso, ele não vai
+bloquear porque a chamada a `loiter(0)` terminou antes deste 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 segunda thread está livre para
+iniciar `loiter(4)`.
+
+<8> O resultado de `loiter(1)` é exibido: `11`. 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 cochilou por 4s.
+
+<<<
+A função `Executor.map` é fácil de usar, mas muitas vezes é preferível obter o resultado
+de cada tarefa assim que esteja pronta, e não necessariamente na ordem em que foram submetidas.
+Para isso, precisamos de uma combinação do método
+`Executor.submit` e da função `futures.as_completed` como vimos no
+<>. Vamos voltar a essa técnica na
+<>.
+
+[TIP]
+====
+
+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 vêm 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")))
+
+
+[[flags2_sec]]
+=== Barra de progresso e tratamento de erros
+
+Como((("concurrent executors", "downloads with progress display and error handling",
+id="CEdown20")))((("network I/O", "downloads with progress display and error handling",
+id="IOprog20")))((("progress displays",
+id="progdisp20")))((("error handling, in network I/O", secondary-sortas="network I/O",
+id="EHnetwork20"))) mencionado, os scripts na <> não
+têm tratamento de erros, para torná-los mais fáceis de ler e para comparar a
+estrutura das três abordagens: sequencial, com threads e assíncrona.
+
+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 da série `flags2`, incluindo a função `main`, que cuida de
+ler os argumentos da linha de comando, medir o tempo e mostrar os
+resultados. Isto é 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
+https://fpy.li/code[«repositório de exemplos»], arquivo
+https://fpy.li/20-10[_20-executors/getflags/flags2_common.py_].
+
+flags2_sequential.py:: Um cliente HTTP sequencial com tratamento de erro correto e a exibição de uma barra de progresso. Sua função `download_one` também é usada por `flags2_threadpool.py`.
+
+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`. Será explicado na <>, no <>.
+
+[[careful_testing_clients]]
+[WARNING]
+.Tenha cuidado ao testar clientes concorrentes
+====
+Ao testar clientes HTTP concorrentes em servidores Web públicos, você pode gerar muitas requisições por segundo, e é assim que ataques de negação de serviço (DoS, _denial-of-service_) são feitos.
+Monitore cuidadosamente seus clientes quando for usar servidores públicos.
+Para testar, configure um servidor HTTP local. Veja instruções após o <>.
+====
+
+A característica mais visível dos exemplos `flags2` é sua barra de progresso
+animada em modo texto, implementada com o https://fpy.li/20-11[pacote _tqdm_].
+Publiquei no YouTube um
+https://fpy.li/20-12[«vídeo de 108s»] mostrando a barra de
+progresso e comparando a velocidade dos três scripts `flags2`. No vídeo, começo
+com o download sequencial, mas interrompo a execução após 32s. O script
+demoraria mais de 5 minutos para acessar 676 URLs e baixar 194 bandeiras. Então
+rodo o script que usa threads e o script que usa `asyncio`, três vezes cada um, e todas
+as vezes eles completam a tarefa em 6s ou menos (isto é mais de 60 vezes mais
+rápido). A <> mostra duas capturas de tela: durante e após
+a execução de _flags2_threadpool.py_.
+
+[[flags2_progress_fig]]
+.Acima: flags2_threadpool.py rodando com a barra de progresso em tempo real gerada pelo tqdm; abaixo: mesma janela do terminal após o script terminar de rodar.
+image::../images/flpy_2001.png[flags2_threadpool.py running with progress bar]
+
+O exemplo de uso mais simples do _tqdm_ aparece em um _.gif_ animado, no https://fpy.li/20-13[_README.md_] do projeto.
+Se você digitar o código abaixo no console de Python após instalar o pacote _tqdm_,
+uma barra de progresso animada aparecerá no lugar onde está o comentário:
+
+[source, python]
+----
+>>> import time
+>>> from tqdm import tqdm
+>>> for i in tqdm(range(1000)):
+...     time.sleep(.01)
+...
+>>> # -> a barra de progresso aparece aqui <-
+----
+
+Além do efeito bonitinho, o gerador `tqdm` também é interessante
+conceitualmente: ele consome qualquer iterável, e produz um iterador que,
+enquanto é consumido, mostra a barra de progresso e estima o tempo restante para
+completar todas as iterações. Para fazer a estimativa, o `tqdm` precisa
+receber um iterável que implemente `len`, ou o argumento
+`total=` com o número esperado de itens. Integrar o `tqdm` com nossos exemplos
+`flags2` permite observar mais profundamente o
+funcionamento real dos scripts concorrentes, obrigando-nos a usar as funções
+https://fpy.li/20-7[`futures.as_completed`] e
+https://fpy.li/20-15[`asyncio.as_completed`], para que o `tqdm` atualize
+a barra de progresso conforme cada `future` termina.
+
+A outra característica dos exemplos `flags2` é a interface de linha de comando.
+Todos os três scripts aceitam várias opções para experimentar,
+que você pode ver passando a opção `-h`.
+O <> mostra o texto de ajuda.
+
+[[flags2_help_demo]]
+.Tela de ajuda dos scripts da série flags2
+====
+[source, text]
+----
+$ python3 flags2_threadpool.py -h
+usage: flags2_threadpool.py [-h] [-a] [-e] [-l N] [-m CONCURRENT] [-s LABEL]
+                            [-v]
+                            [CC [CC ...]]
+
+Download flags for country codes. Default: top 20 countries by population.
+
+positional arguments:
+  CC                    country code or 1st letter (eg. B for BA...BZ)
+
+optional arguments:
+  -h, --help            show this help message and exit
+  -a, --all             get all available flags (AD to ZW)
+  -e, --every           get flags for every possible code (AA...ZZ)
+  -l N, --limit N       limit to N first codes
+  -m CONCURRENT, --max_req CONCURRENT
+                        maximum concurrent requests (default=30)
+  -s LABEL, --server LABEL
+                        Server to hit; one of DELAY, ERROR, LOCAL, REMOTE
+                        (default=LOCAL)
+  -v, --verbose         output detailed progress info
+
+----
+====
+
+Todos os argumentos são opcionais. Mas o `-s/--server` é essencial para os testes:
+ele permite escolher qual servidor HTTP e qual porta serão usados no teste.
+Passe um desses parâmetros (insensíveis a maiúsculas/minúsculas) para determinar onde o script vai buscar as bandeiras:
+
+`LOCAL`:: Usa `http://localhost:8000/flags`; esse é o default. Você deve configurar um servidor HTTP local, respondendo na porta 8000. Veja as instruções na nota a seguir.
+
+`REMOTE`:: Usa `http://fluentpython.com/data/flags`; este é meu site público, hospedado em um servidor compartilhado. Por favor, não o martele com requisições concorrentes excessivas. O domínio _https://fluentpython.com_ é gerenciado pela CDN da https://fpy.li/20-16[_Cloudflare_], então você pode notar que os primeiros downloads são mais lentos, mas ficam mais rápidos conforme o cache da CDN é carregado.
+
+`DELAY`:: Usa `http://localhost:8001/flags`; um servidor atrasando as respostas HTTP deve responder na porta 8001. Escrevi o _slow_server.py_ para facilitar o experimento. Ele está no diretório _20-futures/getflags/_ do https://fpy.li/code[repositório de código do _Python Fluente_]. Veja as instruções na nota a seguir.
+
+`ERROR`:: Usa `http://localhost:8002/flags`; um servidor devolvendo alguns erros HTTP deve responder na porta 8002. Instruções a seguir.
+
+[[setting_up_servers_box]]
+.Configurando os servidores de teste
+[NOTE]
+====
+
+Escrevi((("test servers")))((("servers", "test servers"))) 
+instruções para subir servidores HTTP para testes
+usando apenas Python ≥ 3.9 (nenhuma biblioteca externa) em
+https://fpy.li/20-17[_20-executors/getflags/README.adoc_]
+no https://fpy.li/code[«repositório de exemplos»] .
+Em resumo, o _README.adoc_ descreve como rodar:
+
+`python3 -m http.server`:: O servidor `LOCAL` na porta 8000
+`python3 slow_server.py`:: O servidor `DELAY` na porta 8001, que acrescenta um atraso aleatório de 0,5s a 5s antes de cada resposta
+`python3 slow_server.py 8002 --error-rate .25`:: O servidor `ERROR` na porta 8002, que além do atraso aleatório tem uma chance de 25% de retornar um erro https://fpy.li/20-18["418 I'm a teapot"] como resposta
+
+====
+
+<<<
+Por padrão, cada script _flags2*.py_ irá baixar as bandeiras dos 20 países mais populosos do servidor `LOCAL` (`http://localhost:8000/flags`), usando um número default de conexões concorrentes, que varia de script para script.
+O <> mostra uma execução padrão do script  _flags2_sequential.py_ usando as configurações default.
+Para rodá-lo, você precisa de um servidor local, como explicado acima.
+
+[[flags2_sequential_run]]
+.Rodando flags2_sequential.py com todos os defaults: `site LOCAL`, as 20 bandeiras dos países mais populosos, 1 conexão concorrente
+====
+[source]
+----
+$ python3 flags2_sequential.py
+LOCAL site: http://localhost:8000/flags
+Searching for 20 flags: from BD to VN
+1 concurrent connection will be used.
+--------------------
+20 flags downloaded.
+Elapsed time: 0.10s
+----
+====
+
+Você pode selecionar as bandeiras a serem baixadas de várias formas.
+O <> mostra como baixar todas as bandeiras com códigos de país começando pelas letras A, B ou C.
+
+[[flags2_threadpool_run]]
+.Roda flags2_threadpool.py para obter do servidor `DELAY` todas as bandeiras com prefixos de códigos de país A, B ou C
+====
+[source]
+----
+$ python3 flags2_threadpool.py -s DELAY a b c
+DELAY site: http://localhost:8001/flags
+Searching for 78 flags: from AA to CZ
+30 concurrent connections will be used.
+--------------------
+43 flags downloaded.
+35 not found.
+Elapsed time: 1.72s
+----
+====
+
+Independente de como os códigos de país são selecionados, o número de bandeiras a serem obtidas pode ser limitado com a opção `-l/--limit`. O <> demonstra como fazer exatamente 100 requisições, combinando a opção `-a` para obter todas as bandeiras com `-l 100`.
+
+[[flags2_asyncio_run]]
+.Roda flags2_asyncio.py para baixar 100 bandeiras (`-al 100`) do servidor `ERROR`, usando 100 requisições concorrentes (`-m 100`)
+====
+[source]
+----
+$ python3 flags2_asyncio.py -s ERROR -al 100 -m 100
+ERROR site: http://localhost:8002/flags
+Searching for 100 flags: from AD to LK
+100 concurrent connections will be used.
+--------------------
+73 flags downloaded.
+27 errors.
+Elapsed time: 0.64s
+----
+====
+
+Vimos a interface de usuário dos exemplos _flags2_. Agora veremos como eles estão implementados.
+
+
+==== Tratamento de erros nos exemplos _flags2_
+
+A estratégia comum nos três exemplos para lidar com erros HTTP é que
+erros 404 (not found) são tratados pela função encarregada de baixar um único
+arquivo (`download_one`). Outras exceções são tratadas pela
+função `download_many` ou pela corrotina `supervisor`—no exemplo com `asyncio`.
+
+Vamos novamente começar estudando o código sequencial, que é mais fácil de
+compreender, e boa parte dele será reutilizado pelo script com um banco de threads. O
+<> mostra as funções que efetivamente fazem os downloads
+nos scripts _flags2_sequential.py_ e _flags2_threadpool.py_.
+
+[[flags2_basic_http_ex]]
+.flags2_sequential.py: funções básicas encarregadas dos downloads; ambas são reutilizadas no flags2_threadpool.py
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags2_sequential.py[tags=FLAGS2_BASIC_HTTP_FUNCTIONS]
+----
+====
+<1> Importa a biblioteca de exibição de barra de progresso `tqdm`, e diz ao Mypy para não checá-la.footnote:[Em setembro de 2021 não havia dicas de tipo na versão (então) atual do `tqdm`. Tudo bem. O mundo não vai acabar por causa disso. Obrigado, Guido, pela tipagem opcional!]
+<2> Importa algumas funções e um `Enum` do módulo `flags2_common`.
+<3> Dispara um `HTTPStatusError` se o código de status do HTTP não está em `range(200, 300)`.
+<4> `download_one` trata o `HTTPStatusError`, especificamente para tratar o código HTTP 404...
+<5> ...mudando seu `status` local para `DownloadStatus.NOT_FOUND`; `DownloadStatus` é um `Enum` importado de _flags2_common.py_.
+<6> Qualquer outra exceção de `HTTPStatusError` é re-emitida e propagada para quem chamou a função.
+<7> Se a opção de linha de comando `-v/--verbose` está vigente, o código do país e a mensagem de status são exibidos; é assim que você verá o progresso no modo `verbose`.
+
+O <> é a versão sequencial de
+`download_many`. O código é simples, mas vale a pena estudar para compará-lo com
+as versões concorrentes que veremos depois. Note como ele exibe o
+progresso, trata erros e conta os downloads.
+
+[[flags2_download_many_seq]]
+.flags2_sequential.py: a implementação sequencial de `download_many`
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags2_sequential.py[tags=FLAGS2_DOWNLOAD_MANY_SEQUENTIAL]
+----
+====
+<1> Este `Counter` vai registrar os diferentes resultados possíveis dos downloads: `DownloadStatus.OK`, `DownloadStatus.NOT_FOUND`, ou `DownloadStatus.ERROR`.
+<2> `cc_iter` preserva a lista de códigos de país recebidos como argumentos, em ordem alfabética.
+<3> Se não estamos rodando em modo `verbose`, `cc_iter` é passado para o `tqdm`, que devolve um iterador que produz os itens em `cc_iter` enquanto também anima a barra de progresso.
+<4> Faz chamadas sucessivas a `download_one`.
+<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 um `try/except`.
+<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`.
+<11> No modo `verbose`, mostra a mensagem de erro do código de país atual, se houver.
+<12> Devolve `counter` para que `main` possa mostrar os números no relatório final.
+
+
+Agora vamos estudar _flags2_threadpool.py_, o exemplo com banco de threads aperfeiçoado.
+
+[[using_futures_as_completed_sec]]
+==== Usando `futures.as_completed`
+
+Para((("futures.as_completed", id="futuresas20"))) integrar a barra de progresso
+do _tqdm_ e tratar os erros a cada requisição, o script _flags2_threadpool.py_
+usa o `futures.ThreadPoolExecutor` com a função `futures.as_completed`.
+O <> é a listagem completa de _flags2_threadpool.py_.
+Apenas a função `download_many` é implementada; as outras funções são
+reutilizadas de _flags2_common.py_ e _flags2_sequential.py_.
+
+[[flags2_threadpool_full]]
+.flags2_threadpool.py: listagem completa
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags2_threadpool.py[tags=FLAGS2_THREADPOOL]
+----
+====
+
+<1> Reutiliza `download_one` de `flags2_sequential` (<>).
+
+<2> Se a opção de linha de comando `-m/--max_req` não é passada, este será o
+número máximo de requisições concorrentes, definido como o tamanho do banco de
+threads; o número real pode ser menor se o número de bandeiras a serem baixadas
+for menor.
+
+<3> `MAX_CONCUR_REQ` limita o número máximo de requisições concorrentes
+independente do número de bandeiras a serem baixadas ou da opção de linha de
+comando `-m/--max_req`. É uma medida de segurança, para evitar iniciar threads
+demais, com seu uso significativo de memória.
+
+<4> Cria o `executor` com `max_workers` determinado por `concur_req`, calculado
+pela função `main` como o menor valor entre `MAX_CONCUR_REQ`, o tamanho de
+`cc_list`, ou a opção de linha de comando `-m/--max_req`. Assim evitamos criar
+mais threads que o necessário.
+
+<5> Este `dict` vai mapear cada instância de `Future`—representando um
+download—com o respectivo código de país, para exibição de erros.
+
+<6> Itera sobre a lista de códigos de país em ordem alfabética. A ordem dos
+resultados vai depender, mais do que de qualquer outra coisa, do tempo das
+respostas HTTP; mas se o tamanho do banco de threads (dado por `concur_req`) for
+muito menor que  `len(cc_list)`, você poderá ver os downloads aparecendo em
+ordem alfabética.
+
+<7> Cada chamada a `executor.submit` agenda a execução de um invocável e
+devolve uma instância de `Future`. O primeiro argumento é o invocável, o
+restante são os argumentos que ele receberá.
+
+<8> Armazena o `future` e o código de país no `dict`.
+
+<9> `futures.as_completed` devolve um iterador que produz _futures_ conforme
+cada tarefa é completada.
+
+<10> Se não estiver no modo `verbose`, passa o resultado de `as_completed` para a
+função `tqdm`, para mostrar a barra de progresso; como `done_iter` não tem
+`len`, precisamos informar o gerador `tqdm` qual o número de itens esperado com o
+argumento `total=`, para que ele possa estimar o trabalho restante.
+
+<11> Itera sobre os _futures_ conforme eles vão terminando.
+
+<12> Chamar o método `result` em um _future_ devolve o valor devolvido pela
+invocável ou dispara qualquer exceção que tenha sido capturada quando o
+invocável foi executado. Este método pode bloquear, esperando por uma
+resolução. Mas não nesse exemplo, porque `as_completed` só produz _futures_ que
+terminaram sua execução.
+
+<13> Trata exceções em potencial; o resto desta função é idêntica à função
+`download_many` no <>, exceto pela observação a
+seguir.
+
+<14> Para dar contexto à mensagem de erro, recupera o código de país do
+`to_do_map`, usando o `future` atual como chave. Isso não era necessário na
+versão sequencial, pois estávamos iterando sobre a lista de códigos de país,
+então sabíamos qual era o `cc` atual; aqui estamos iterando sobre _futures_.
+
+
+[TIP]
+====
+
+O <> usa um padrão que é muito útil com
+`futures.as_completed`: construir um `dict` mapeando cada _future_ a outros
+dados que podem ser úteis quando o _future_ terminar de executar. Aqui o
+`to_do_map` mapeia cada _future_ ao código de país atribuído a ele.
+Assim fica mais fácil o pós-processamento dos resultados dos _futures_, apesar deles
+serem produzidos fora de ordem.
+
+====
+
+As threads de Python são bastante adequadas a aplicações de uso intensivo de E/S, e o pacote `concurrent.futures` as torna relativamente simples de implementar em certos casos de uso. Com `ProcessPoolExecutor` você também pode resolver problemas de uso intensivo de CPU em múltiplos núcleos—se o processamento for https://fpy.li/20-19["embaraçosamente paralelo"]. Isso encerra nossa introdução básica a `concurrent.futures`.((("", startref="futuresas20")))((("", startref="EHnetwork20")))((("", startref="progdisp20")))((("", startref="IOprog20")))((("", startref="CEdown20")))
+
+
+=== Resumo do capítulo
+
+Começamos((("concurrent executors", "overview of"))) o capítulo comparando dois
+clientes HTTP concorrentes com um sequencial, demonstrando que as soluções
+concorrentes mostram um ganho significativo de desempenho sobre o script
+sequencial.
+
+Após estudar o primeiro exemplo, baseado no `concurrent.futures`, olhamos mais
+de perto os objetos _future_, instâncias de `concurrent.futures.Future` ou de
+`asyncio.Future`, enfatizando as semelhanças entre
+essas classes (suas diferenças serão examinadas no <>). Vimos como
+criar _futures_ chamando `Executor.submit`, e como iterar sobre _futures_ que
+terminaram sua execução com `concurrent.futures.as_completed`.
+
+Então discutimos o uso de múltiplos processos com a classe
+`concurrent.futures.ProcessPoolExecutor`, para evitar a GIL e usar múltiplos
+núcleos de CPU, simplificando o checador de números primos multi-núcleo que
+vimos antes no <>.
+
+Na seção seguinte, estudamos como funciona a classe `ThreadPoolExecutor`,
+com um exemplo didático, iniciando tarefas que não faziam nada por alguns
+segundos, exceto exibir seu status e a hora naquele instante.
+
+Então voltamos para os exemplos de download de bandeiras. Melhorar aqueles
+exemplos com uma barra de progresso e tratamento de erro adequado nos ajudou a
+explorar melhor a função geradora `future.as_completed` mostrando um modelo
+comum: armazenar _futures_ em um `dict` para vincular a eles informações adicionais
+quando são submetidos, para usá-las quando o _future_
+sai do gerador `as_completed`.
+
+
+=== Para saber mais
+
+O((("concurrent executors", "further reading on"))) pacote `concurrent.futures`
+foi uma contribuição de Brian Quinlan, que o apresentou em uma palestra
+sensacional intitulada
+https://fpy.li/20-20[_The Future Is Soon!_] (O futuro é logo!), na
+PyCon Australia 2010. A palestra de Quinlan não tinha slides; ele mostra o que a
+biblioteca faz digitando código diretamente no console de Python. Como exemplo
+motivador, a apresentação inclui um pequeno vídeo com o cartunista/programador
+do XKCD, Randall Munroe, executando um ataque de negação de serviço (DoS)
+acidental contra o Google Maps, para criar um mapa colorido de tempos de
+locomoção pela cidade. A introdução formal à biblioteca é a
+https://fpy.li/pep3148[PEP 3148—`futures`: execute computations
+asynchronously] (executar processamentos assíncronos). Na PEP,
+Quinlan escreveu que a biblioteca `concurrent.futures` foi "muito influenciada
+pelo pacote `java.util.concurrent` de Java."
+
+Para mais recursos sobre `concurrent.futures`, por favor consulte o
+<>. As referências que tratam de `threading` e
+`multiprocessing` de Python na <> também
+tratam do `concurrent.futures`.
+
+.Ponto de vista
+****
+
+*Evitando Threads*
+
+[quote, David Beazley, instrutor de Python e cientista louco.]
+____
+Concorrência: um dos tópicos mais difíceis na ciência da computação (normalmente é melhor
+evitá-lo).footnote:[Slide #9 do tutorial
+https://fpy.li/20-21[_A Curious Course on Coroutines and Concurrency_]
+(Um Curioso Curso sobre Corrotinas e Concorrência), apresentado na PyCon 2009.]
+____
+
+Concordo((("concurrent executors", "Soapbox discussion")))((("Soapbox sidebars",
+"thread avoidance")))((("threads", "thread avoidance")))
+com as citações aparentemente contraditórias de David Beazley e Michele Simionato no início desse capítulo.
+
+Assisti a um curso de graduação sobre concorrência no IME/USP.
+Tudo o que vimos foi programação de https://fpy.li/an[«threads POSIX»].
+O que aprendi: não quero gerenciar threads e travas na unha,
+pela mesma razão que não quero gerenciar a alocação e desalocação de memória na unha.
+Estas tarefas são para programadores de sistemas, que têm 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://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 chamados problemas "embaraçosamente paralelos"—ao contrário de
+sistemas operacionais ou servidores de banco de dados, como aponta Simionato
+na epígrafe deste capítulo.
+
+Para problemas de concorrência mais complexos, threads e travas também não são a
+solução. Ao nível do sistema operacional, as threads nunca vão desaparecer. Mas
+todas as linguagens de programação que achei interessantes nos últimos 20 anos
+oferecem abstrações de alto nível para concorrência, como demonstra o excelente
+livro de Paul Butcher, https://fpy.li/20-24[_Seven Concurrency Models in Seven
+Weeks_] (Sete Modelos de Concorrência em Sete Semanas). Go, Elixir, e
+Clojure estão entre elas. Erlang—a linguagem de implementação do Elixir—é um
+exemplo claro de uma linguagem projetada desde o início pensando em
+concorrência. Erlang não me empolga só porque acho sua sintaxe deselegante.
+Python me acostumou mal.
+
+José Valim, antigo mantenedor do _Ruby on Rails_, projetou o
+Elixir com uma sintaxe moderna e agradável. Como Lisp e Clojure, o Elixir
+implementa macros sintáticas. Isto é uma faca de dois gumes. Macros sintáticas
+permitem criar DSLs—sigla de _Domain-Specific Language_
+(Linguagem de Domínio Específico), facilitando determinadas tarefas.
+Mas a proliferação de sub-linguagens pode levar a
+bases de código incompatíveis e à fragmentação da comunidade. O Lisp se afogou
+em um mar de macros, cada empresa e grupo de desenvolvedores Lisp usando seu
+próprio dialeto arcano. A padronização que criou o Common Lisp resultou em uma
+linguagem inchada quando muitas macros foram incorporadas ao padrão.
+Espero que José Valim inspire a comunidade do Elixir a evitar
+um destino semelhante. Até agora, o cenário parece bom. A biblioteca
+de banco de dados https://fpy.li/20-25[_Ecto_] é muito agradável de usar:
+um grande exemplo do uso de macros para criar uma DSL flexível e amigável para
+interagir com bases de dados relacionais e não-relacionais.
+
+Como, Elixir, Go é uma linguagem moderna com ideias novas.
+Mas, em alguns aspectos, é uma linguagem conservadora, se comparada ao Elixir.
+Go não tem macros, e sua sintaxe é mais simples que a de Python.
+Go não suporta herança ou sobrecarga de operadores, e oferece menos oportunidades para metaprogramação que Python.
+Estas limitações são consideradas vantagens.
+Elas proporcionam comportamento e desempenho mais previsíveis.
+Isto é uma grande vantagem em ambientes de missão crítica altamente concorrentes,
+onde o Go pretende substituir {cpp}, Java e Python.
+
+<<<
+Enquanto Elixir e Go são competidores diretos no espaço da alta concorrência,
+seus projetos e filosofias atraem públicos diferentes.
+Ambos têm boas chances de prosperar.
+Mas historicamente, as linguagens mais conservadoras tendem a atrair mais programadores.
+
+****
diff --git a/vol3/cap21.adoc b/vol3/cap21.adoc
new file mode 100644
index 00000000..eb453a57
--- /dev/null
+++ b/vol3/cap21.adoc
@@ -0,0 +1,2570 @@
+[[ch_async]]
+== Programação assíncrona
+:example-number: 0
+:figure-number: 0
+
+[quote, Alvaro Videla & Jason J. W. Williams, RabbitMQ in Action]
+____
+O problema com as abordagens usuais da programação assíncrona é
+que são propostas do tipo "tudo ou nada".
+Ou você reescreve todo o código, de forma que nada nele bloqueie, 
+ou está só perdendo tempo.footnote:[Videla & Williams,
+_RabbitMQ in Action (Manning, 2012)_,
+_Solving Problems with Rabbit: coding and patterns_, p. 61]
+____
+
+
+Este((("asynchronous programming", "topics covered"))) capítulo trata de três grandes tópicos interligados:
+
+* As instruções `async def`, `await`, `async with`, e `async for`;
+* Objetos que suportam estas instruções através de métodos especiais como `+__await__+`, `+__aiter__+` etc.,
+como corrotinas nativas e variantes assíncronas de gerenciadores de contexto, iteráveis, geradores e compreensões;
+* `asyncio` e outras bibliotecas assíncronas.
+
+Este capítulo parte das ideias de iteráveis e geradores (<>,
+em particular a <>), gerenciadores de contexto (<>),
+e conceitos gerais de programação concorrente (<>).
+
+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 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 _FastAPI_. A seguir estudaremos outros objetos
+suportados pelas palavras-chave `async/await`: funções geradoras assíncronas,
+compreensões assíncronas, e expressões geradoras assíncronas. Para enfatizar
+que estes recursos não estarão limitados ao `asyncio`, veremos
+um exemplo reescrito para usar o _Curio_—o inovador e influente framework
+inventado por David Beazley. Finalizando o capítulo, escrevi uma pequena seção sobre vantagens e
+armadilhas da programação assíncrona.
+
+Temos um longo caminho à frente. Teremos espaço apenas para exemplos básicos,
+mas eles vão ilustrar as características mais importantes de cada ideia.
+
+
+[TIP]
+====
+A https://fpy.li/21-1[documentação do `asyncio`] melhorou((("asyncio package", "documentation"))) muito após Yury Selivanovfootnote:[Selivanov implementou `async/await` no Python, e escreveu as PEPs relacionadas:
+https://fpy.li/pep492[492],
+https://fpy.li/pep525[525], e
+https://fpy.li/pep530[530].
+] reorganizá-la, dando maior destaque às funções úteis para desenvolvedores de aplicações.
+A maior parte da API de `asyncio` consiste em funções e classes voltadas para
+criadores de pacotes como frameworks Web e drivers de bancos de dados, ou seja,
+são necessários para criar bibliotecas assíncronas, mas não aplicações.
+
+Para mais profundidade sobre `asyncio`, recomendo
+https://fpy.li/hattingh[_Using Asyncio in Python_]
+de Caleb Hattingh (O'Reilly).
+Transparência: Caleb é um dos revisores técnicos deste livro.
+====
+
+
+=== Novidades neste capítulo
+
+Quando((("asynchronous programming", "significant changes to"))) escrevi a
+primeira edição de _Python Fluente_, a biblioteca `asyncio` era provisória e as
+palavras-chave `async/await` não existiam. Por isso atualizei todos os exemplos
+deste capítulo. Também criei novos exemplos: scripts de sondagem de domínios, um
+serviço Web com _FastAPI_, e experimentos com o novo modo assíncrono do console
+do Python.
+
+Novas seções tratam de recursos da linguagem inexistentes naquele momento, como
+corrotinas nativas, `async with`, `async for`, e os objetos que suportam essas
+instruções.
+
+As ideias na <> refletem lições importantes
+que aprendi na prática, por isso eu a considero leitura essencial para
+qualquer um trabalhando com programação assíncrona. Elas podem ajudar você a
+evitar muitos problemas—no Python ou mesmo no Node.js.
+
+Por fim, removi vários parágrafos sobre `asyncio.Futures`, que agora considero parte das APIs de baixo nível do `asyncio`.
+
+[role="pagebreak-before less_space"]
+=== Algumas definições.
+
+Na ((("asynchronous programming", "relevant terminology"))) <>,
+vimos que Python oferece três tipos de corrotinas desde a versão 3.5:
+
+Corrotina nativa::
+    Uma((("native coroutines", "definition of term")))((("coroutines", "types
+    of"))) função corrotina definida com `async def`. Uma
+    corrotina nativa pode acionar outra corrotina nativa, usando a instrução
+    `await`, semelhante ao funcionamento de `yield from` em corrotinas
+    clássicas. A instrução `async def` sempre define uma corrotina nativa,
+    mesmo se a instrução `await` não aparecer em seu corpo. A instrução
+    `await` só pode ser usada dentro de uma corrotina nativa.footnote:[Há uma
+    exceção a essa regra: se você iniciar Python com a opção `-m asyncio`, pode
+    então usar `await` diretamente no prompt `>>>` para controlar uma corrotina
+    nativa. Isto é explicado na <>.]
+
+Corrotina clássica::
+    Uma função geradora que consome dados enviados a ela via chamadas a `my_coro.send(data)`, e que lê aqueles dados usando `yield` em uma expressão.
+    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 geradores::
+    Uma((("generators", "generator-based coroutines")))((("coroutines", "generator-based"))) função geradora decorada com `@types.coroutine`—introduzido no Python 3.5.
+    Esse decorador torna o gerador compatível com a nova instrução `await`.
+
+Neste 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.
+    Ela devolve um objeto gerador assíncrono que implementa `+__anext__+`, um método corrotina para obter o próximo item.
+
+.`@asyncio.coroutine` não tem futurofootnote:[Perdoe o jogo de palavras.]
+[WARNING]
+====
+
+O((("@asyncio.coroutine decorator"))) decorador `@asyncio.coroutine` para
+corrotinas clássicas e corrotinas baseadas em gerador foi descontinuado no
+Python 3.8, e está previsto para ser removido no Python 3.11, de acordo com o
+https://fpy.li/21-2[«Issue 43216»]. Por outro lado, `@types.coroutine` deve
+continuar existindo, como se vê aqui: https://fpy.li/21-3[«Issue 36921»]. Este
+decorador não é mais suportado pelo `asyncio`, mas é usado em código interno nos
+frameworks assíncronos _Curio_ e _Trio_.
+
+====
+
+
+=== Sondando domínios com `asyncio`
+
+Imagine((("asyncio package", "example script", id="APexample21")))((("asynchronous programming", "asyncio script example", id="APRscript21")))
+que você esteja prestes a lançar um novo blog sobre Python,
+e planeje registrar um domínio usando uma palavra-chave de Python e o sufixo _.DEV_—por exemplo, _AWAIT.DEV._
+O <> é um script usando `asyncio` que verifica vários domínios de forma concorrente.
+Essa é a saída produzida pelo script:
+
+[source, text]
+----
+$ python3 blogdom.py
+  with.dev
++ elif.dev
++ def.dev
+  from.dev
+  else.dev
+  or.dev
+  if.dev
+  del.dev
++ as.dev
+  none.dev
+  pass.dev
+  true.dev
++ in.dev
++ for.dev
++ is.dev
++ and.dev
++ try.dev
++ not.dev
+----
+
+Observe que os domínios aparecem fora de ordem. Se você rodar o script, os verá
+sendo exibidos um após o outro, em intervalos variados. O sinal de `+` indica
+que sua máquina foi capaz de resolver o domínio via DNS. Caso contrário, o
+domínio não está em uso, e pode estar disponível para
+adquirir.footnote:[`true.dev` está disponível por US$ 360,00 ao ano no momento
+em que escrevo esta nota. Também vi que `for.dev` está registrado, mas seu DNS
+não está configurado.]
+
+No _blogdom.py_, a sondagem de DNS é feita por objetos corrotinas nativas. Como
+as operações assíncronas são intercaladas, o tempo necessário para verificar 18
+domínios é bem menor que se eles fossem verificados sequencialmente. Na verdade,
+o tempo total é quase o igual ao da resposta mais lenta, em vez da soma dos
+tempos de todas as respostas do DNS.
+
+O <> mostra o código dp _blogdom.py_.
+
+[[blogdom_ex]]
+.blogdom.py: procura domínios para um blog sobre Python
+====
+[source, python]
+----
+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 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 laço de eventos e retorna só quando o laço termina. É prática comum em scripts com `asyncio` implementar `main` como uma corrotina e acioná-la com `asyncio.run`
+no `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 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.
+A função `asyncio.get_event_loop` está https://fpy.li/as[«descontinuada desde o Python 3.10»]; eventualmente será um apelido para `asyncio.get_running_loop`.
+====
+
+==== O truque de Guido para ler código assíncrono
+
+Há muitos conceitos novos para entender no `asyncio`,
+mas a lógica básica do <> é fácil de compreender se você usar o truque sugerido pelo próprio Guido van Rossum:
+finja que as palavras-chave `async` e `await` não estão ali.
+Fazendo isso, você vai perceber que a lógica de uma corrotina pode ser lida como uma função sequencial.
+
+Por exemplo, imagine que o corpo desta corrotina...
+
+[source, python]
+----
+async def probe(domain: str) -> tuple[str, bool]:
+    laço = asyncio.get_running_loop()
+    try:
+        await loop.getaddrinfo(domain, None)
+    except socket.gaierror:
+        return (domain, False)
+    return (domain, True)
+----
+
+...funciona como a função abaixo, exceto que, magicamente, ela nunca bloqueia a execução do laço de eventos:
+
+[source, python]
+----
+def probe(domain: str) -> tuple[str, bool]:  # no async
+    laço = asyncio.get_running_loop()
+    try:
+        loop.getaddrinfo(domain, None)  # no await
+    except socket.gaierror:
+        return (domain, False)
+    return (domain, True)
+----
+
+Usar a sintaxe `await loop.getaddrinfo(...)` evita o bloqueio, porque `await` suspende somente 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 laço de eventos,
+não para a corrotina `probe(‘if.dev’)`, que está suspensa.
+Enquanto isso, o laço de eventos pode ativar outros objetos corrotina pendentes,
+tal como `probe('or.dev')`.
+
+Quando o laço de eventos recebe a resposta da consulta `getaddrinfo('if.dev',
+None)`, aquele objeto corrotina específico prossegue sua execução, e devolve o
+controle para `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. Mas eles podem lidar com qualquer objeto "esperável". Esse conceito
+será explicado a seguir.((("", startref="APexample21")))((("",
+startref="APRscript21")))
+
+=== Novo conceito: esperável _(awaitable)_
+
+A((("asynchronous programming", "awaitables")))((("await keyword")))((("keywords",
+"await keyword"))) palavra-chave `for` funciona com
+"iteráveis" (_iterable_). A palavra-chave `await` funciona com "esperáveis" (_awaitable_).
+
+[NOTE]
+====
+Os tradutores da documentação do Python em português traduziram _awaitable_ como
+"aguardável". Adotei "esperável" por ser mais simples.
+E também porque nem todo esperável é agradável ;-)
+====
+
+Como usuário do `asyncio`, estes são os esperáveis que você verá diariamente:
+
+* Um _objeto corrotina nativo_, que você obtém invocando uma _função corrotina nativa_
+* Uma tarefa `asyncio.Task`, criada ao invocar `asyncio.create_task(…)` passando um objeto corrotina nativo.
+
+Entretanto, o código do usuário final nem sempre precisa acionar uma `Task` com `await`.
+Usamos `asyncio.create_task(coro())` para agendar `one_coro` para execução concorrente, sem esperar que retorne.
+Foi o que fizemos com a corrotina `spinner` em _spinner_async.py_
+(<> do <>).
+Criar a tarefa é o suficiente para agendar o acionamento da corrotina pelo laço de eventos.
+
+[WARNING]
+====
+
+Mesmo que você não precise cancelar a tarefa ou esperar pelo resultado,
+é necessário preservar o objeto `Task` devolvido por `create_task`,
+salvando-o em uma variável ou coleção que você controla.footnote:[Agradeço
+ao leitor Samuel Woodward por reportar este erro para a O'Reilly em fevereiro de 2023]
+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 serem acionadas. Por isso você precisa criar referências fortes para
+preservar cada tarefa na memória.
+Veja a documentação de
+https://fpy.li/at[`asyncio.create_task`].
+Sobre referências fracas, escrevi o artigo
+https://fpy.li/weakref[«Weak References»] no _https://fluentpython.com_.
+
+====
+
+<<<
+Por outro lado, usamos `await other_coro()` para executar `other_coro` agora mesmo
+e esperar que ela termine, porque precisamos do resultado para prosseguir.
+Em _spinner_async.py_, a corrotina `supervisor` usava `res = await slow()`
+para executar `slow` e aguardar seu resultado..
+
+Ao implementar bibliotecas assíncronas ou contribuir para o próprio `asyncio`,
+você pode também encontrar estes esperáveis de baixo nível:
+
+* Um objeto com um método `+__await__+` que devolve um iterador; por exemplo, uma instância de `asyncio.Future` (`asyncio.Task` é uma subclasse de `asyncio.Future`)
+* Objetos escritos em outras linguagens usando a API Python/C, com uma função `tp_as_async.am_await`, que devolve um iterador (similar ao método `+__await__+`)
+
+As bases de código existentes podem também conter um tipo adicional de
+esperável: _objetos corrotina baseados em geradores_, que estão no processo de
+serem descontinuados.
+
+[NOTE]
+====
+
+A PEP 492 https://fpy.li/21-7[«afirma»] que a expressão `await` "usa a
+implementação de `yield from` com um passo adicional de validação
+de seu argumento" e que “`await` só aceita um esperável.” A PEP não explica
+aquela implementação em detalhes, mas cita a
+https://fpy.li/pep380[_PEP 380_], que introduziu `yield from`.
+Escrevi uma explicação detalhada no texto
+https://fpy.li/oldcoro[«Classic Coroutines»], seção
+https://fpy.li/21-8[«The Meaning of `yield from`»] (O significado de `yield from`).
+
+====
+
+Agora vamos estudar a versão `asyncio` do script para baixar figuras da Web.
+
+[[flags_asyncio_sec]]
+=== Downloads com `asyncio` e _HTTPX_
+
+O((("asyncio package", "downloading with", id="APdown21")))((("network I/O",
+"downloading with asyncio", id="NIOdownload21")))((("HTTPX library", id="httpx21")))
+script _flags_asyncio.py_ baixa um conjunto fixo de 20 bandeiras de _https://fluentpython.com_.
+Já mencionamos este script na <>,
+mas agora vamos examiná-lo em detalhes, aplicando os conceitos que acabamos de ver.
+
+<<<
+No Python 3.10, o `asyncio` só suporta TCP e UDP,
+e não temos pacotes de cliente ou servidor HTTP assíncronos na biblioteca padrão.
+Estou usando o https://fpy.li/httpx[_HTTPX_] nos exemplos de clientes HTTP.
+
+Vamos explorar o _flags_asyncio.py_ a partir do final do módulo, isto é,
+olhando primeiro as funções que iniciam as ações no <>.
+
+[WARNING]
+====
+Para deixar o código mais fácil de ler, _flags_asyncio.py_ não faz tratamento de erros.
+Nesta introdução a `async/await` é bom se concentrar inicialmente no "caminho feliz" (_happy path_),
+para entender como funções comuns e corrotinas são organizadas em um programa.
+A partir da <>, os exemplos incluem tratamento de erros e outros recursos.
+
+Os exemplos __flags*__ aqui e no <> compartilham código e dados, então os coloquei juntos no diretório
+https://fpy.li/21-9[_example-code-2e/20-executors/getflags_].
+====
+
+
+[[flags_asyncio_start_ex]]
+.flags_asyncio.py: funções de inicialização
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags_asyncio.py[tags=FLAGS_ASYNCIO_START]
+----
+====
+
+<1> Esta tem que ser uma função comum—não uma corrotina—para podermos passá-la
+para função `main` do módulo _flags.py_ (<> do <>)
+no passo `⑦`.
+
+<2> Executa o laço de eventos, acionando o objeto corrotina
+`supervisor(cc_list)` até que ele retorne. Esta função ficará bloqueada enquanto o laço de
+eventos roda. O resultado de `supervisor(cc_list)` será devolvido por `asyncio_run`.
+
+<3> Operações assíncronas de cliente HTTP no `httpx` são métodos de
+`AsyncClient`, que também é um gerenciador de contexto assíncrono,
+para uso com `async with`. Trata-se de um gerenciador de contexto com
+métodos corrotina para inicialização e
+encerramento (detalhes logo mais na <>).
+
+<4> Cria uma lista de objetos corrotina, invocando 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 submetidos.
+
+<6> `supervisor` devolve o tamanho da lista vinda de `asyncio.gather`.
+
+<7> Invocamos `main` do `flags.py` (<> do <>)
+
+
+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 e funções de download
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags_asyncio.py[tags=FLAGS_ASYNCIO_TOP]
+----
+====
+
+<1> `httpx` precisa ser instalado; não vem com a biblioteca padrão.
+
+<2> Reutiliza código de _flags.py_ (<> do <>).
+
+<3> `download_one` precisa ser uma corrotina nativa, para acionar
+`get_flag` com `await`. Quando recebe a resposta, exibe o código de país
+bandeira, 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`.
+
+<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]
+====
+
+Idealmente, a invocação de `save_flag` dentro de `get_flag` deveria ser assíncrona,
+evitando bloquear o laço de eventos com uma operação de E/S.
+Entretanto, atualmente `asyncio` não oferece uma API assíncrona para acessar o sistema de arquivos—como faz o Node.js.
+
+A <> vai mostrar como delegar `save_flag` para uma thread.
+====
+
+O seu código delega para as corrotinas do `httpx` explicitamente, usando `await`, ou implicitamente, usando os métodos especiais dos gerenciadores de contexto assíncronos, como `AsyncClient` e `ClientResponse`—como veremos na <>.
+
+
+==== O segredo das corrotinas nativas: humildes geradores
+
+A((("native coroutines", "humble generators and")))((("humble generators")))((("generators", "humble generators"))) diferença fundamental entre os exemplos de corrotinas clássicas vistas na <> e _flags_asyncio.py_ é que não há chamadas a `.send()` ou expressões `yield` visíveis nesse último.
+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 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 direta entre uma biblioteca como a _HTTPX_ e o laço de eventos do framework assíncrono.
+image::../images/flpy_2101.png[Diagrama do canal await]
+
+Debaixo dos panos, o laço de eventos do `asyncio` faz as chamadas a `.send` que
+acionam as nossas corrotinas, e nossas corrotinas acionam outras corrotinas com `await`,
+inclusive corrotinas da biblioteca. Como já mencionado, a maior parte da
+implementação de `await` vem de `yield from`, que internamente invoca `.send`
+para acionar as corrotinas.
+
+O canal `await` termina em um esperável de baixo nível da biblioteca _HTTPX_,
+que devolve um gerador que o laço de eventos pode acionar em resposta a eventos
+tais como E/S de rede ou timers. 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`, podemos ter
+múltiplos canais de `await` ao mesmo tempo, permitindo acionar
+operações de E/S concorrentes em um único laço de eventos, em uma única
+thread.
+
+==== O problema do tudo ou nada
+
+No <>, note que eu reutilizei a função `get_flag` de
+_flags.py_ (<> do <>). 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 acionada com `await` ou `asyncio.create_task`. Assim o controle é
+devolvido ao laço de eventos, enquanto não chega a resposta da operação de envio
+ou recebimento de dados. Se for inviável reescrever a função bloqueante como
+uma corrotina, devemos executá-la em outra thread ou processo, como
+veremos na <>.
+
+Por isso escolhi a epígrafe desse capítulo, que inclui o conselho:
+"Ou você reescreve todo o código, de forma que nada nele bloqueie 
+ou está só perdendo tempo."
+
+Pela mesma razão, também não pude reutilizar a
+função `download_one` de _flags_threadpool.py_
+(<> do <>).
+O código no <> aciona `get_flag` com `await`,
+então `download_one` precisa também ser uma corrotina.
+Para cada requisição, `supervisor` cria um objeto corrotina `download_one`,
+e eles são todos acionados pela corrotina `asyncio.gather`.
+
+Vamos agora estudar a instrução `async with`, que apareceu em `supervisor`
+(<>) e `get_flag` (<>).((("", startref="APdown21")))((("", startref="NIOdownload21")))((("", startref="httpx21")))
+
+
+[[async_context_manager_sec]]
+=== Gerenciadores de contexto assíncronos
+
+Na((("context managers", "asynchronous", id="CMasync21")))((("asynchronous programming", "asynchronous context managers", id="APRaconman21"))) <>, vimos como um objeto pode ser usado para executar código antes e depois do corpo de um bloco `with`, se sua classe oferecer os métodos `+__enter__+` e `+__exit__+`.
+
+Agora, considere o <>, que usa o driver PostgreSQL https://fpy.li/21-10[_asyncpg_] compatível com o `asyncio` (https://fpy.li/21-11[documentação do _asyncpg_ sobre transações]).
+
+[[asyncpg_transaction_no_context_ex]]
+.Código exemplo da documentação do driver PostgreSQL _asyncpg_
+====
+[source, python]
+----
+tr = connection.transaction()
+await tr.start()
+try:
+    await connection.execute("INSERT INTO mytable VALUES (1, 2, 3)")
+except:
+    await tr.rollback()
+    raise
+else:
+    await tr.commit()
+----
+====
+
+Uma transação de banco de dados se presta naturalmente a protocolo de
+gerenciador de contexto: a transação precisa ser iniciada, dados são modificados
+com `connection.execute`, e então temos que fazer um _rollback_ (reversão) ou um
+_commit_ (confirmação), dependendo do resultado das mudanças.
+
+Em((("asyncpg"))) um driver assíncrono como o _asyncpg_, a configuração e a
+execução precisam acontecer em corrotinas, para que outras operações possam
+ocorrer de forma concorrente. Entretanto, a implementação da instrução `with`
+clássica não suporta corrotinas na implementação dos métodos `+__enter__+` ou
+`+__exit__+`.
+
+Por isso a
+https://fpy.li/pep492[_PEP 492—Coroutines with `async` and `await` syntax_] 
+(Corrotinas com a sintaxe `async` e `await`) introduziu a instrução `async with`,
+que funciona com gerenciadores de contexto assíncronos: objetos
+que implementam os métodos `+__aenter__+` e `+__aexit__+` como corrotinas.
+
+Usando `async with`, o <> pode ser escrito como
+esse outro exemplo da https://fpy.li/21-11[«documentação do _asyncpg_»]:
+
+[source, python]
+----
+async with connection.transaction():
+    await connection.execute("INSERT INTO mytable VALUES (1, 2, 3)")
+----
+
+Na
+https://fpy.li/21-13[«classe `asyncpg.Transaction`»],
+o método corrotina `+__aenter__+` executa `await self.start()`, e a corrotina
+`+__aexit__+` aciona um dos métodos corrotina privados `+__rollback+` ou
+`+__commit+`, dependendo da ocorrência ou não de uma exceção. Usar corrotinas para
+implementar `Transaction` como um gerenciador de contexto assíncrono permite ao
+_asyncpg_ controlar muitas transações concorrentemente.
+
+.Caleb Hattingh sobre o asyncpg
+[TIP]
+====
+Outro detalhe impressionante sobre o _asyncpg_
+é que ele também contorna a falta de suporte à alta-concorrência do PostgreSQL
+(que usa um processo servidor por conexão)
+implementando um banco de conexões para conexões internas ao próprio Postgres.
+
+Isto significa que não precisamos de ferramentas adicionais (por exemplo o _pgbouncer_),
+como explicado na https://fpy.li/21-14[«documentação»]
+ do _asyncpg_.
+====
+
+Voltando ao _flags_asyncio.py_, a classe `AsyncClient` do `httpx` é um
+gerenciador de contexto assíncrono, para poder acionar esperáveis em seus métodos
+corrotina especiais `+__aenter__+` e `+__aexit__+`.
+
+
+[NOTE]
+====
+
+A <> mostra como usar a `contextlib` de Python para
+criar um gerenciador de contexto assíncrono sem precisar escrever uma classe.
+Esta explicação aparece mais tarde nesse capítulo por causa de um pré-requisito:
+a <>.
+
+====
+
+Agora vamos melhorar o exemplo `asyncio` de download de bandeiras com uma barra
+de progresso, que nos levará a explorar um pouco mais a API do `asyncio`.((("",
+startref="APRaconman21")))((("", startref="CMasync21")))
+
+
+[[flags2_asyncio_sec]]
+=== Melhorando o download de bandeiras assíncrono
+
+Vamos((("asynchronous programming", "enhancing asyncio downloader", id="APRenhanc21")))((("asyncio package", "enhancing asyncio downloader", id="APenhanc21")))((("network I/O", "enhancing asyncio downloader", id="NIOenhance21"))) recordar a <>, na qual o conjunto de exemplos `flags2` compartilhava a mesma interface de linha de comando, e todos mostravam uma barra de progresso enquanto os downloads aconteciam. Eles também incluíam tratamento de erros.
+
+[TIP]
+====
+Encorajo você a brincar com os exemplos `flags2`, para desenvolver uma intuição sobre o funcionamento de clientes HTTP concorrentes.
+Use a opção `-h` para ver a tela de ajuda no  <>.
+Use as opções de linha de comando `-a`, `-e`, e `-l` para controlar o número de downloads,
+e a opção `-m` para estabelecer o número de downloads concorrentes.
+Execute testes com os servidores `LOCAL`, `REMOTE`, `DELAY`, e `ERROR`.
+Descubra o número ótimo de downloads concorrentes para maximizar a taxa de transferência de cada servidor.
+Varie as opções dos servidores de teste, como descrito na caixa _<>_ da <>.
+====
+
+Por exemplo, o <> mostra uma tentativa de obter 100 bandeiras (`-al 100`) do servidor `ERROR`,
+usando 100 conexões concorrentes (`-m 100`).
+Os 48 erros no resultado são ou HTTP 418 ou erros de tempo de espera excedido (_time-out_)—o [mau]comportamento esperado do _slow_server.py_.
+
+[[flags2_asyncio_run_repeat]]
+.Running flags2_asyncio.py
+====
+[source]
+----
+$ python3 flags2_asyncio.py -s ERROR -al 100 -m 100
+ERROR site: http://localhost:8002/flags
+Searching for 100 flags: from AD to LK
+100 concurrent connections will be used.
+100%|█████████████████████████████████████████| 100/100 [00:03<00:00, 30.48it/s]
+--------------------
+ 52 flags downloaded.
+ 48 errors.
+Elapsed time: 3.31s
+----
+====
+
+[WARNING]
+.Seja responsável ao testar clientes concorrentes
+====
+Mesmo que o tempo total de download não seja muito diferente entre os clientes HTTP na versão com threads e na versão `asyncio` HTTP , o
+_asyncio_ é capaz de enviar requisições mais rápido, então aumenta a probabilidade do servidor suspeitar de um ataque DoS.
+Para testar estes clientes concorrentes em sua capacidade máxima, por favor use servidores HTTP locais em seus testes,
+como explicado na caixa _<>_ da <>.
+====
+
+Agora vejamos como o _flags2_asyncio.py_ é implementado.
+
+[[using_as_completed_sec]]
+==== Usando `asyncio.as_completed` e uma thread
+
+No((("threads", "enhancing asyncio downloader", id="Tenhance21")))
+<>, passamos várias corrotinas para `asyncio.gather`, que
+devolve uma lista com os resultados das corrotinas na ordem em que foram
+submetidas. Isto significa que `asyncio.gather` só pode retornar quando todos os
+esperáveis terminarem. Entretanto, para atualizar a barra de progresso,
+precisamos receber cada resultado assim que ele está pronto.
+
+Felizmente existe uma equivalente assíncrona da função geradora `as_completed`
+que usamos no exemplo de banco de threads com a barra de progresso,
+(<> do <>).
+
+O <> mostra o início do script _flags2_asyncio.py_, onde as
+corrotinas `get_flag` e `download_one` são definidas. O <>
+lista o restante do código-fonte, com `supervisor` e `download_many`. O script é
+maior que _flags_asyncio.py_ por causa do tratamento de erros.
+
+[[flags2_asyncio_top]]
+.flags2_asyncio.py: parte superior (inicial) do script; o resto do código está no <>
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags2_asyncio.py[tags=FLAGS2_ASYNCIO_TOP]
+----
+====
+
+<1> `get_flag` é muito similar à versão sequencial no <>.
+Primeira diferença: ela requer o parâmetro `client`.
+
+<2> Segunda e terceira diferenças: `.get` é um método de `AsyncClient`, e é uma
+corrotina, então precisamos acioná-la com `await`.
+
+<3> Usa o `semaphore` como um gerenciador de contexto assíncrono, assim o
+programa todo não é bloqueado; apenas essa corrotina é suspensa quando o
+contador do semáforo é zero. Veremos mais sobre isso na caixa _<>_,
+<>.
+
+<4> A lógica de tratamento de erro é idêntica à de `download_one`, do
+<> do <>.
+
+<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"—pois
+ler/escrever arquivos é https://fpy.li/21-15[«milhares de vezes mais demorado»]
+que ler/escrever na RAM. Se você estiver usando
+https://fpy.li/av[«armazenamento conectado à rede»], acessar "o disco"
+significa fazer E/S na rede local.
+
+Desde o Python 3.9, a corrotina `asyncio.to_thread` facilitou delegar operações
+de arquivo para um banco de threads fornecido pelo `asyncio`. Se você precisa
+suportar Python 3.7 ou 3.8, a <> mostra como fazer
+isso, adicionando algumas linhas ao seu programa. Mas antes, vamos terminar
+nosso estudo do código do cliente HTTP.
+
+[[limiting_req_with_semaphore_sec]]
+==== Limitando as requisições com um semáforo
+
+Clientes de rede((("throttling", id="throttle21")))((("semaphores",
+id="semaphores21"))) como os que estamos estudando devem ser _throttled_
+(limitados no desempenho) para não martelarem o servidor com um número excessivo de
+requisições concorrentes.
+
+Um https://fpy.li/aw[_semáforo_]
+é uma estrutura primitiva de sincronização, mais flexível que uma trava.
+O mesmo semáforo é usado por várias corrotinas, com um número máximo configurável.
+Assim podemos limitar o número de corrotinas concorrentes ativas usando o semáforo.
+A caixa _<>_ tem mais informações.
+
+No _flags2_threadpool.py_ (<> do <>), a
+limitação de desempenho (_throttling_) foi feita na função `download_many`
+instanciando o `ThreadPoolExecutor` passando um número máximo de threads no
+argumento `max_workers`. Em _flags2_asyncio.py_, um `asyncio.Semaphore` é criado
+pela função `supervisor` (mostrada no <>) e passado como o
+argumento `semaphore` para `download_one` no <>.
+
+[[about_semaphores_box]]
+.Semáforos no Python
+****
+
+O cientista da computação Edsger W. Dijkstra inventou o
+https://fpy.li/aw[«semáforo»] no início dos anos 1960. É uma ideia simples, mas
+tão flexível que a maioria dos outros objetos de sincronização—como as travas e
+as barreiras—podem ser construídas a partir de semáforos. Há três classes
+`Semaphore` na biblioteca padrão de Python: uma em `threading`, outra em
+`multiprocessing`, e uma terceira em `asyncio`. Essas classes têm interfaces
+parecidas, mas suas implementações são bem diferentes. Aqui apresento a versão
+de `asyncio`.
+
+Um `asyncio.Semaphore` tem um contador interno que é decrementado toda vez que
+acionamos o método corrotina `.acquire()` com `await`.
+O contador é incrementado
+quando invocamos o método `.release()`—que não é uma corrotina porque nunca
+bloqueia. O valor inicial do contador é definido quando o `Semaphore` é
+instanciado:
+
+[source, python]
+----
+    semaphore = asyncio.Semaphore(concur_req)
+----
+
+Fazer `await` em `.acquire()` não bloqueia quando o contador
+interno é maior que zero.
+Mas o contador está zerado, `.acquire()` suspende
+a corrotina que chamou `await` até que alguma outra corrotina chame
+`.release()` no mesmo `Semaphore`, incrementando assim o contador.
+
+Em vez de usar estes métodos diretamente,
+é mais seguro usar o `semaphore` como um gerenciador de contexto assíncrono,
+como fiz na função `download_one` em <>:
+
+[source, python]
+----
+        async with semaphore:
+            image = await get_flag(client, base_url, cc)
+----
+
+O método corrotina `+Semaphore.__aenter__+` espera por `.acquire()`
+(usando `await` internamente),
+e seu método corrotina `+__aexit__+` invoca `.release()`.
+Este bloco `async with` garante que no máximo `concur_req`
+instâncias de corrotinas `get_flags` estarão ativas em qualquer momento.
+
+Cada uma das classes `Semaphore` na biblioteca padrão tem uma subclasse
+`BoundedSemaphore`, que impõe uma restrição adicional: o contador interno não
+pode nunca ficar maior que o valor inicial, impedindo mais operações
+`.release()` que `.acquire()`.footnote:[Agradeço a Guto Maia, que notou que o
+conceito de semáforo não era explicado quando leu o primeiro rascunho deste
+capítulo.]
+
+****
+
+Agora vamos olhar o resto do script no <>.
+
+[[flags2_asyncio_rest]]
+.flags2_asyncio.py: continuação de <>
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags2_asyncio.py[tags=FLAGS2_ASYNCIO_START]
+----
+====
+
+<1> `supervisor` recebe os mesmos argumentos que a função `download_many`, mas
+não pode ser invocada diretamente de `main`, pois é uma corrotina e não uma
+função comum 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 definidas 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 laço
+`for` abaixo porque posso precisar envolvê-la com o iterador `tqdm` para a barra
+de progresso, dependendo da opção de verbosidade na linha de comando.
+
+<5> Envolve o iterador `as_completed` com a função geradora `tqdm`, para mostrar
+o progresso.
+
+<6> Declara e inicializa `error` com `None`; esta variável será usada para
+salvar uma exceção além do bloco `try/except`, se alguma for levantada.
+
+<7> Itera pelos objetos corrotina que terminaram a execução; este laço é similar
+ao de `download_many` no <> do <>.
+
+<8> Aciona a corrotina com `await` para obter seu resultado. Isto não bloqueia porque
+`as_completed` só produz corrotinas que já terminaram.
+
+<9> Esta atribuição é necessária porque o escopo da variável `exc` é limitado a
+esta cláusula `except`, mas preciso preservar o valor para uso posterior.
+
+<10> Mesmo que acima.
+
+<11> Se houve um erro, muda o `status`.
+
+<12> 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
+laço de eventos com `asyncio.run`, coletando o contador que `supervisor` devolve
+quando o laço de eventos termina.
+
+No <>, não pudemos usar o mapeamento de `futures` para os
+códigos de país que vimos em <> do <>,
+porque os esperáveis devolvidos por `asyncio.as_completed` não são
+necessariamente os mesmos esperáveis que passamos na invocação de
+`as_completed`. Internamente, a lógica do `asyncio` pode embrulhar os esperáveis
+que fornecemos por outros esperáveis que afinal produzirão os mesmos
+resultados.footnote:[Iniciei uma discussão sobre esta questão 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 e Victor Stinner e fornece detalhes sobre a
+implementação de `as_completed`, bem como 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 país da
+exceção. Para fazer isso, preservei 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: instruções como laços e `try/except` não criam um escopo local nos 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
+dentro daquela cláusula `except` específica.
+
+====
+
+Isto encerra nossa discussão da funcionalidade de um cliente Web
+com tratamento de erros e barra de progresso, implementado com `asyncio`.
+
+O próximo exemplo demonstra um modelo simples de execução de uma tarefa
+assíncrona após outra usando corrotinas.
+
+==== Fazendo múltiplas requisições para cada download
+
+
+Pessoas com experiência em JavaScript sabem que rodar uma função assíncrona após
+outra acabou gerando o padrão de funções aninhadas conhecido como
+https://fpy.li/21-20[_pyramid of doom_] (pirâmide da perdição). A palavra-chave
+`await` desfaz a maldição. Por isso `await` agora é parte de Python e de
+JavaScript.((("", startref="throttle21")))((("", startref="semaphores21")))
+Vamos a um exemplo.
+
+Suponha que você queira salvar cada bandeira com o nome do país e o código, em
+vez de apenas o código. Agora você precisa fazer duas requisições HTTP por
+bandeira: uma para pegar a imagem da bandeira propriamente dita, a outra para
+pegar o arquivo _metadata.json_, no mesmo diretório da imagem—é nesse arquivo
+que o nome do país está registrado.
+
+Coordenar múltiplas requisições na mesma tarefa é fácil no script com threads:
+basta fazer uma requisição depois a outra, bloqueando a thread duas vezes, e
+preservando os dados (código e nome do país) em variáveis locais, prontas para
+serem usadas na hora de salvar o arquivo. Se você precisasse fazer o mesmo em um
+script assíncrono com callbacks, precisaria de funções aninhadas, de forma que o
+código e o nome do país estivessem disponíveis em clausuras até o momento de
+salvar o arquivo, pois cada callback roda em um escopo local diferente. A
+palavra-chave `await` evita este aninhamento, permitindo que
+você acione as requisições assíncronas uma após a outra, compartilhando o escopo
+local da corrotina que aciona as ações.
+
+
+[TIP]
+====
+
+Se você está trabalhando com programação de aplicações assíncronas no Python
+moderno e recorre a uma grande quantidade de callbacks, provavelmente está
+aplicando modelos antigos, que não fazem mais sentido no Python atual. Isso é
+justificável se você estiver escrevendo uma biblioteca que se conecta a código
+legado ou código de baixo nível sem suporte a corrotinas. De qualquer
+forma, a discussão no StackOverflow,
+https://fpy.li/21-21[_What is the use case for future.add_done_callback()?_]
+(Qual o caso de uso para future.add_done_callback()?) explica por que
+callbacks são necessários em código de baixo nível, mas não são muito úteis
+hoje em dia em código Python no nível da aplicação.
+
+====
+
+A terceira variante do script `asyncio` de download de bandeiras traz algumas mudanças:
+
+`get_country`:: Esta nova corrotina baixa o arquivo _metadata.json_ daquele código de país, e extrai dele o nome do país.
+`download_one`:: Esta corrotina agora usa `await` para delegar para `get_flag` e para a nova corrotina `get_country`, usando o resultado dessa última para compor o nome do arquivo a ser salvo.
+
+Vamos começar com o código de `get_country` (<>).
+Note que é muito parecido com o `get_flag` do <>.
+
+<<<
+[[flags3_asyncio_get_country]]
+.flags3_asyncio.py: corrotina `get_country`
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags3_asyncio.py[tags=FLAGS3_ASYNCIO_GET_COUNTRY]
+----
+====
+<1> Esta corrotina devolve uma string com o nome do país—se tudo correr bem.
+<2> `metadata` vai receber um `dict` Python construído a partir do conteúdo JSON da resposta.
+<3> Devolve o nome do país.
+
+Agora vamos ver o `download_one` modificado do <>, que tem apenas algumas linhas diferentes da corrotina de mesmo nome do <>.
+
+[[flags3_asyncio]]
+.flags3_asyncio.py: corrotina `download_one`
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags3_asyncio.py[tags=FLAGS3_ASYNCIO_DOWNLOAD_ONE]
+----
+====
+<1> Retém o `semaphore` para acionar `get_flag`...
+<2> ...e novamente para acionar `get_country`.
+<3> Usa o nome do país para criar um nome de arquivo.
+Como usuário da linha de comando, não gosto de espaços em nomes de arquivo.
+
+Muito melhor que callbacks aninhados!
+
+Coloquei as chamadas a `get_flag` e `get_country` em blocos `with` separados,
+controlados pelo `semaphore` porque é uma boa prática reter semáforos e travas
+pelo menor tempo possível.
+
+Eu poderia ter agendado as funções `get_flag` e `get_country`, em
+paralelo, usando `asyncio.gather`, mas se `get_flag` levantar uma exceção não
+haverá imagem para salvar, então é inútil rodar `get_country`. Mas há casos
+em que faz sentido usar `asyncio.gather` para acessar várias APIs simultaneamente,
+em vez de esperar por uma resposta antes de fazer a próxima requisição
+
+Em _flags3_asyncio.py_, a sintaxe `await` aparece seis vezes, e `async with` três vezes.
+Espero que você esteja pegando o jeito da programação assíncrona em Python.
+
+Um desafio é saber quando você precisa usar `await` e quando você não pode usá-la.
+A resposta, em princípio, é fácil: use `await` para
+acionar corrotinas e outros esperáveis, como instâncias de `asyncio.Task`.
+Mas algumas APIs são complexas, misturam corrotinas e funções comuns
+de forma aparentemente arbitrária, como a classe `StreamWriter` que usaremos no <>.
+
+O <> encerra o grupo de exemplos _flags_. Vamos agora discutir o
+uso de executores de threads ou processos na programação assíncrona.((("",
+startref="APRenhanc21")))((("", startref="APenhanc21")))((("",
+startref="Tenhance21")))((("", startref="NIOenhance21")))
+
+[[delegating_to_executors_sec]]
+=== 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 o Python para programação assíncrona é a
+biblioteca padrão do Node.js, que inclui APIs assíncronas para todo tipo de
+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
+usar thread principal para ler e escrever no armazenamento bloqueia o laço de
+eventos.
+
+Na corrotina `download_one` de <>, usei a seguinte linha
+para salvar a imagem baixada:
+
+[source, python]
+----
+        await asyncio.to_thread(save_flag, image, f'{cc}.gif')
+----
+
+Como mencionei antes, o `asyncio.to_thread` foi acrescentado no Python 3.9.
+Se você precisa suportar 3.7 ou 3.8,
+substitua aquela linha pelas linhas em  <>.
+
+[[flags2_asyncio_executor_fragment]]
+.Linhas para usar no lugar de `await asyncio.to_thread`
+====
+[source, python]
+----
+include::../code/20-executors/getflags/flags2_asyncio_executor.py[tags=FLAGS2_ASYNCIO_EXECUTOR]
+----
+====
+<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, um `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, use
+`functool.partial`, como descrito na
+https://fpy.li/ax[«documentação de `run_in_executor`»].
+
+A função mais nova `asyncio.to_thread` é mais fácil de usar e mais flexível, já que também aceita argumentos nomeados.
+
+A própria implementação de `asyncio` usa `run_in_executor` debaixo dos panos em
+alguns pontos. Por exemplo, a corrotina `loop.getaddrinfo(…)`,  que vimos no
+<> é implementada invocando a função `getaddrinfo` do módulo
+`socket`—uma função bloqueante que pode levar alguns segundos para retornar,
+pois depende de resolução de DNS.
+
+Um padrão comum em APIs assíncronas é encapsular em corrotinas quaisquer
+chamadas bloqueantes que sejam detalhes de implementação, e usar
+`run_in_executor` dentro das corrotinas para executar as chamadas bloqueantes.
+Assim é possível apresentar uma interface consistente de corrotinas a serem
+acionadas com `await`, escondendo as threads que precisam ser usadas por razões
+pragmáticas.
+
+Por exemplo, o driver assíncrono do MongoDB para Python, chamado
+https://fpy.li/21-23[_Motor_], tem uma API compatível com `async/await` que na
+verdade é uma fachada, escondendo um banco de threads que conversa com o
+MongoDB. O criador do _Motor_, A. Jesse Jiryu Davis, explica suas razões em
+https://fpy.li/21-24[_Response to "Asynchronous Python and Databases"_]
+(Resposta a "Python Assíncrono e os Bancos de Dados"). Spoiler: Jiryu Davis
+descobriu que um banco de threads tem melhor desempenho no caso de uso
+específico de um driver de banco de dados—desmascarando o mito de que abordagens
+assíncronas são sempre mais eficientes que threads para E/S de rede.
+
+A principal razão para passar um `Executor` explícito para
+`loop.run_in_executor` é utilizar um `ProcessPoolExecutor`, se a função ocupar
+intensivamente a CPU. Assim ela rodará em um processo Python diferente, evitando
+a disputa pela GIL. Por seu alto custo de inicialização, seria melhor iniciar o
+`ProcessPoolExecutor` no `supervisor`, e passá-lo para as corrotinas que
+precisem utilizá-lo.
+
+O revisor Caleb Hattingh (autor de
+https://fpy.li/hattingh[_Using Asyncio in Python_])
+me passou o seguinte aviso sobre executores e o `asyncio`.
+
+.O aviso de Caleb sobre run_in_executors
+[WARNING]
+====
+
+Usar `run_in_executor` pode produzir problemas difíceis de depurar, já que o
+cancelamento não funciona da forma esperada. Corrotinas que usam
+executores apenas fingem terminar: a thread subjacente (se for um
+`ThreadPoolExecutor`) não tem um mecanismo de cancelamento. Por exemplo, uma
+thread de longa duração criada dentro de uma chamada a `run_in_executor` pode
+impedir que seu programa `asyncio` encerre de forma limpa: `asyncio.run` vai
+esperar para retornar até o executor terminar completamente, e vai esperar para
+sempre se os serviços iniciados pelo executor não pararem sozinhos de alguma
+forma. Minha barba branca preferia que o nome da função fosse
+`run_in_executor_uncancellable`.
+
+====
+
+Agora saímos de scripts clientes para escrever servidores com o `asyncio`.((("",
+startref="exedel21")))((("", startref="APRdelegat21")))
+
+
+=== Programando servidores assíncronos
+
+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://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`.
+
+Estes servidores permitem que os usuários pesquisem caracteres Unicode buscando
+palavras que ocorrem em seus nomes fornecidos pelo módulo `unicodedata` que discutimos na
+https://fpy.li/cn[«Seção 4.9»] (vol.1). A <> mostra uma sessão com o
+_web_mojifinder.py_, o primeiro servidor que escreveremos.
+
+[[web_mojifinder_result]]
+.Janela de navegador mostrando os resultados da busca por "mountain" no serviço web_mojifinder.py.
+image::../images/flpy_2102.png[Captura de tela de conexão do Firefox com o web_mojifinder.py]
+
+A lógica de busca no Unicode nesses exemplos é a classe `InvertedIndex` no
+módulo _charindex.py_ no https://fpy.li/code[«repositório de código do _Python
+Fluente_»]. Não há nada concorrente naquele pequeno módulo, então 
+o box opcional a seguir contém apenas uma breve explicação sobre ele.
+Você pode pular para a implementação do servidor HTTP na <>.
+
+.Conhecendo o índice invertido
+****
+
+Um((("inverted indexes"))) índice invertido normalmente mapeia palavras a
+documentos onde elas ocorrem.
+Nos exemplos _mojifinder_, cada "documento" é o nome de um caractere Unicode.
+A classe `charindex.InvertedIndex` indexa cada palavra que aparece no nome de
+cada caractere no banco de dados Unicode, e cria um índice invertido em um `defaultdict`.
+Por exemplo, para indexar o caractere U+0037—DIGIT SEVEN—o construtor
+de `InvertedIndex` anexa o caractere `'7'` aos registros sob as chaves `'DIGIT'` e `'SEVEN'`.
+Após indexar os dados do Unicode 13 incluídos no Python 3.10, `'DIGIT'` será mapeado para
+868 caracteres que têm esta palavra em seus nomes;
+e `'SEVEN'` para  143, incluindo U+1F556—CLOCK FACE SEVEN OCLOCK e
+U+2790—DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SEVEN.
+
+Veja na <> uma demonstração usando os caracteres indexados para as palavras `'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. Ele vem do 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
+image::../images/flpy_2103.png[Captura de tela do console de Python]
+
+O método `InvertedIndex.search` quebra a consulta em palavras separadas, e devolve a interseção dos registros para cada palavra.
+É por isso que buscar por "face" encontra 171 resultados, "cat" encontra 14, mas "cat face" apenas 10.
+
+Esta é 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://fpy.li/az[«Listas Invertidas»] na Wikipedia para saber mais.
+****
+
+[[fastapi_web_service_sec]]
+==== Um serviço Web com _FastAPI_
+
+Escrevi((("FastAPI framework", id="fastapi21"))) o próximo
+exemplo—_web_mojifinder.py_—usando o https://fpy.li/21-28[_FastAPI_]: um dos
+frameworks ASGI para desenvolvimento Web em Python, mencionado na
+<>. A <> é uma captura de tela da
+interface de usuário. É uma aplicação simples, de uma página só (_SPA_,
+_Single Page Application_): após o download inicial do HTML, a interface é
+atualizada via JavaScript no cliente, em comunicação com o servidor.
+
+[[web_mojifinder_schema]]
+.Documentação OpenAPI do ponto de acesso `/search`, gerada automaticamente.
+image::../images/flpy_2104.png[Captura de tela do Firefox mostrando o schema OpenAPI para o ponto de acesso `/search`]
+
+<<<
+O _FastAPI_ foi projetado para implementar o lado servidor de SPAs e aplicativos
+de celular, que consistem principalmente de pontos de acesso de APIs Web, devolvendo
+respostas JSON em vez de HTML renderizado no servidor. O _FastAPI_ se vale de
+decoradores, dicas de tipo e introspecção de código para eliminar muito
+código repetitivo das APIs Web, e também gera uma
+documentação no padrão OpenAPI do https://fpy.li/21-29[_Swagger_].
+A <> mostra a página `/docs` do
+_web_mojifinder.py_, gerada automaticamente.
+
+O <> é o código de _web_mojifinder.py_, mas é só
+código do lado servidor. Quando você acessa a URL raiz `/`, o servidor envia o
+arquivo _form.html_, que contém 81 linhas de código, incluindo 54 linhas de
+JavaScript para comunicação com o servidor e preenchimento da tabela com os
+resultados. Se tiver interesse em ler JavaScript puro sem uso de frameworks,
+confira o _21-async/mojifinder/static/form.html_ no
+https://fpy.li/code[«repositório de código»] do _Python Fluente_.
+
+Para rodar o _web_mojifinder.py_, você precisa instalar dois pacotes e suas
+dependências: _FastAPI_ e _uvicorn_.footnote:[Você pode usar outro servidor ASGI
+no lugar do _uvicorn_, como o _hypercorn_ ou o _Daphne_. Veja na documentação
+oficial do ASGI a https://fpy.li/21-30[«página sobre implementações»] para
+mais informações.]
+
+Este é o comando para executar o <> com _uvicorn_ em modo de desenvolvimento:
+
+[source, shell]
+----
+$ uvicorn web_mojifinder:app --reload
+----
+
+os parâmetros são:
+
+`web_mojifinder:app`::
+  O nome do pacote, dois pontos, e o nome da aplicação ASGI definida nele—`app` é o nome usado por convenção.
+   
+`--reload`::
+  Faz o _uvicorn_ monitorar mudanças no código-fonte da aplicação, e recarregá-la automaticamente. Útil apenas durante o desenvolvimento.
+
+Vamos agora olhar o código-fonte do _web_mojifinder.py_.
+
+
+[[web_mojifinder_ex]]
+.web_mojifinder.py: código-fonte completo
+====
+[source, python]
+----
+include::../code/21-async/mojifinder/web_mojifinder.py[]
+----
+====
+
+<1> Não relacionado ao tema desse capítulo, mas digno de nota: o uso elegante do
+operador `/` sobrecarregado por `pathlib`.footnote:[Agradeço ao revisor técnico
+Miroslav Šedivý por apontar bons lugares para usar `pathlib` nos exemplos de
+código.]
+
+<2> Esta linha define a app ASGI. Ela poderia ser apenas `app =
+FastAPI()`. Os parâmetros são metadados para a documentação da API,
+gerada automaticamente.
+
+<3> Um schema _pydantic_ para uma resposta JSON, com campos `char` e
+`name`. Como mencionado no https://fpy.li/8[«Capítulo 8»] (vol.1), o
+_pydantic_ valida os dados durante a execução, com base nas anotações de tipo.
+
+<4> Cria o `index` e carrega o formulário HTML estático, anexando ambos ao
+`app.state` para uso posterior.
+
+<5> Roda `init` quando esse módulo é carregado pelo servidor ASGI.
+
+<6> Rota para o ponto de acesso `/search`; `response_model` usa aquele modelo
+`CharName` do _pydantic_ para descrever o formato da resposta.
+
+<7> O _FastAPI_ assume que qualquer parâmetro que apareça na assinatura da
+função ou da corrotina e que não esteja no caminho da rota será passado na
+string de consulta HTTP, isto é, `/search?q=cat`. Como `q` não tem default, a
+_FastAPI_ devolverá um status 422 (Unprocessable Entity, _Entidade
+Não-Processável_) se `q` não estiver presente na string da consulta.
+
+<8> Devolver um iterável de `dicts` compatível com o schema `response_model`
+permite ao _FastAPI_ criar uma resposta JSON de acordo com o `response_model` no
+decorador `@app.get`,
+
+<9> Funções regulares (isto é, não-assíncronas) também podem ser usadas para
+produzir respostas.
+
+<10> Este módulo não tem uma função principal. É carregado e acionado pelo
+servidor ASGI—neste exemplo, o _uvicorn_.
+
+O <> não faz chamada direta ao `asyncio`.
+O _FastAPI_ é construído sobre o toolkit ASGI _Starlette_, que por sua vez usa o `asyncio`.
+
+Note também que o corpo de `search` não usa `await`, `async with`, ou `async for`,
+então poderia ser uma função comum.
+Defini `search` como uma corrotina apenas para mostrar que o _FastAPI_ sabe como lidar com elas.
+Em uma aplicação real, a maioria dos pontos de acesso serão consultas
+a bancos de dados ou acessos a outros servidores remotos,
+então é uma vantagem importante do _FastAPI_—e dos frameworks ASGI em geral—
+suportarem corrotinas que podem se valer de bibliotecas assíncronas para E/S de rede.
+
+[TIP]
+====
+
+As funções `init` e `form` para carregar e entregar o HTML estático do
+formulário são gambiarras para manter esse exemplo curto e fácil de rodar
+sem mais configurações.
+
+A melhor prática é ter um proxy/balanceador de carga na frente do
+ASGI, servindo todos os recursos estáticos, e também usar uma _CDN_ 
+(Rede de Entrega de Conteúdo) quando possível.
+
+Um proxy/balanceador de carga deste tipo é o https://fpy.li/21-32[_Traefik_],
+descrito como um _edge router_ (roteador de ponta), que "recebe requisições em
+nome de seu sistema e descobre quais componentes são responsáveis por lidar com
+elas." O site do _FastAPI_ apresenta 
+https://fpy.li/c5/[«ferramentas de geração de projeto»]
+que organizam o código para usar o _Trafik_ e outros sofwares auxiliares.
+
+====
+
+Os entusiastas da tipagem estática podem ter notado que não coloquei dicas de
+tipo para os resultados devolvidos por `search` e `form`. Em vez disto, o
+_FastAPI_ aceita o argumento nomeado `response_model=` nos decoradores de rota.
+A página https://fpy.li/21-34[_Response Model - Return Type_] da documentação do _FastAPI_
+explica:
+
+[quote]
+____
+
+O modelo de resposta é declarado neste parâmetro em vez de como uma anotação de
+tipo de resultado devolvido por uma função, porque a função de rota pode não
+devolver aquele modelo de resposta mas sim um `dict`, um objeto do banco de dados
+ou algum outro modelo, e então usar o `response_model` para realizar a validação
+de campos e a serialização.
+____
+
+Por exemplo, em `search`, devolvi um gerador de itens `dict` e não uma lista
+de objetos `CharName`, mas isso basta para o _FastAPI_ e o _pydantic_
+validarem meus dados e construírem a resposta JSON apropriada, compatível com
+`response_model=list[CharName]`.
+
+Agora vamos analisar outro servidor que 
+usa _sockets_ TCP e a biblioteca padrão do Python para responder consultas via
+Telnet no terminal.((("", startref="fastapi21")))
+
+<<<
+==== Um servidor TCP com `asyncio`
+
+O((("TCP servers", id="tcp21")))((("servers", "TCP servers", id="Stcp22")))
+programa _tcp_mojifinder.py_ usa TCP puro para se comunicar com um cliente como
+o Telnet ou o Netcat, então pude escrevê-lo usando `asyncio` sem dependências
+externas, e sem reinventar o HTTP. A <> mostra a interface
+de usuário em modo texto.
+
+
+[[tcp_mojifinder_demo]]
+.Sessão de telnet com o servidor tcp_mojifinder.py: consultando "fire."
+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_ (descontando o HTML e o JavaScript daquele exemplo).
+Por isso dividi a apresentação em três partes:
+<>, <>, e <>.
+
+O topo do arquivo _tcp_mojifinder.py_, com as instruções `import`, está no <>.
+Mas vou começar descrevendo a corrotina `supervisor` e a função `main` que controlam o programa.
+
+[[ex_tcp_mojifinder_main]]
+.tcp_mojifinder.py: um servidor TCP simples; continua no <>
+====
+[source, python]
+----
+include::../code/21-async/mojifinder/tcp_mojifinder.py[tags=TCP_MOJIFINDER_MAIN]
+----
+====
+
+<1> Este `await` devolve uma instância de `asyncio.Server`, um servidor TCP baseado
+em _sockets_. Por padrão, `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`, uma
+função de _callback_ para rodar ao iniciar a conexão com cada cliente.
+Pode ser uma função ou uma corrotina, mas deve aceitar exatamente
+dois argumentos: um `asyncio.StreamReader` e um `asyncio.StreamWriter`.
+Minha corrotina `finder` também precisa receber um `index`, por isso
+usei `functools.partial` para vincular aquele parâmetro e obter um invocável que
+recebe o leitor (`StreamReader`) e o escritor (`StreamWriter`).
+
+<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` em maio de 2021.
+Veja https://fpy.li/21-36[«Issue #5535 no _typeshed_»].footnote:[O bug
+#5535 está resolvido desde outubro de 2021, mas o Mypy não lançou uma nova versão
+até o fechamento desta edição, então o erro permanece.]
+
+<5> Exibe 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 fazer `await` em `serve_forever`, para que meu
+`supervisor` seja suspenso aqui. Do contrário, 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 laço de eventos rodando `supervisor`.
+
+<9> Captura `KeyboardInterrupt` para evitar um traceback ruidoso quando
+encerramos o servidor teclando 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 no
+<>.
+
+[[tcp_mojifinder_server_demo]]
+.tcp_mojifinder.py: o lado servidor da sessão mostrada na <>
+====
+[source, text]
+----
+$ python3 tcp_mojifinder.py
+Building index.  # <1>
+Serving on ('127.0.0.1', 2323). Hit CTRL-C to stop.  # <2>
+ From ('127.0.0.1', 58192): 'cat face'   # <3>
+   To ('127.0.0.1', 58192): 10 results.
+ From ('127.0.0.1', 58192): 'fire'       # <4>
+   To ('127.0.0.1', 58192): 11 results.
+ From ('127.0.0.1', 58192): '\x00'       # <5>
+Close ('127.0.0.1', 58192).              # <6>
+^C  # <7>
+Server shut down.  # <8>
+$
+----
+====
+<1> Saída de `main`. Antes da próxima linha aparecer, notei um intervalo de 0,6s na minha máquina, enquanto o índice era construído.
+<2> Saída de `supervisor`.
+<3> Primeira volta do laço `while` na função `finder` do <>. A pilha TCP/IP atribuiu a porta 58192 a meu cliente Telnet. Se você conectar diversos clientes ao servidor, verá suas diferentes portas aparecerem na saída.
+<4> Segunda iteração do laço `while` em `finder`.
+<5> Teclei CTRL-C no terminal do cliente; o laço `while` em `finder` termina.
+<6> A corrotina `finder` exibe esta mensagem e encerra. Enquanto isso o servidor continua rodando, pronto para receber outros clientes.
+<7> Teclei 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 laço de eventos, a corrotina
+`supervisor` rapidamente exibe a mensagem `Serving on...`,
+e fica suspensa na última linha:
+
+[source, python]
+----
+    await server.serve_forever()
+----
+
+Neste ponto o controle flui para o laço de eventos do `asyncio` 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 a rede para enviar ou
+receber dados.
+
+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. Desta
+forma, milhares de clientes podem ser atendidos concorrentemente por este
+servidor simples. Isto segue até que ocorra um `KeyboardInterrupt` no servidor
+ou que seu processo seja encerrado 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 <>
+====
+[source, python]
+----
+include::../code/21-async/mojifinder/tcp_mojifinder.py[tags=TCP_MOJIFINDER_TOP]
+----
+====
+
+<1> `format_results` é útil para mostrar os resultados 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> Este 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, é uma função
+comum. Esta linha envia o prompt `?>`.
+
+<6> `StreamWriter.drain` esvazia o buffer de `writer`; ela é uma corrotina,
+então precisa ser acionada com `await`.
+
+<7> `StreamWriter.readline` é uma corrotina que devolve `bytes`.
+
+<8> Se nenhum byte foi recebido, o cliente fechou a conexão, então sai do loop.
+
+<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 laço se um caractere de controle ou null foi recebido.
+
+<13> `search` realiza a busca; 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://fpy.li/b4[documentação do método `.close()`].
+
+<17> Registra o final dessa sessão do cliente no console do servidor.
+
+O último trecho deste código é a corrotina `search`, <>.
+
+[[tcp_mojifinder_search]]
+.tcp_mojifinder.py: corrotina `search`
+====
+[source, python]
+----
+include::../code/21-async/mojifinder/tcp_mojifinder.py[tags=TCP_MOJIFINDER_SEARCH]
+----
+====
+<1> `search` precisa ser uma corrotina, pois escreve em um `StreamWriter` e precisa acionar o método corrotina `.drain()`.
+<2> Consulta o índice invertido.
+<3> Esta expressão geradora produzirá strings de bytes codificadas em UTF-8 com o ponto de código Unicode, o caractere, seu nome e uma sequência `CRLF` (_Return+Line Feed_), isto é, `b'U+0039\t9\tDIGIT NINE\r\n'`.
+<4> Envia `lines`. Surpreendentemente, `writer.writelines` não é uma corrotina.
+<5> Mas `writer.drain()` é uma corrotina. Não esqueça do `await`!
+<6> Constrói e envia uma linha de status.
+
+Observe que toda a E/S de rede em _tcp_mojifinder.py_ é feita em `bytes`; precisamos decodificar os `bytes` recebidos da rede, e codificar strings antes de enviá-las. No Python 3, a codificação default é UTF-8, e foi o que usei implicitamente em todas as chamadas a `encode` e `decode` nesse exemplo.
+
+[WARNING]
+====
+
+Note que alguns dos métodos de E/S são corrotinas, e precisam ser acionados com
+`await`, enquanto outros são funções comuns. Por exemplo, `StreamWriter.write`
+é uma função, 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 escrevi a primeira edição desse livro, sugeri uma melhoria na
+https://fpy.li/b5[«documentação da API»] do `asyncio` para indicar com mais
+clareza as corrotinas (antes era preciso ler todo o texto sobre uma corrotina
+para encontrar a informação, porque elas eram formatadas como as funções).
+
+====
+
+O código de _tcp_mojifinder.py_ se vale da 
+https://fpy.li/21-40[«API de streams»] de alto nível do `asyncio`,
+que fornece um servidor pronto para usar,
+então só precisamos codar 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 nas abstrações de 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 a API de baixo nível.
+
+Nosso próximo tópico é a instrução `async for` e os objetos que a fazem
+funcionar.((("", startref="tcp21")))((("", startref="APRwrit21")))((("",
+startref="APwrite21")))((("", startref="Stcp22")))((("", startref="Sasyncio21")))
+
+
+=== Iteráveis assíncronos
+
+Na((("asynchronous programming", "iteration and iterables",
+id="APRiteration21")))((("iterables", "asynchronous",
+id="ITasync21")))((("iterators", "asynchronous", id="ITERasync21")))
+<> vimos como `async with` funciona com objetos que
+implementam os métodos `+__aenter__+` e `+__aexit__+`, devolvendo
+esperáveis—normalmente na forma de objetos corrotina.
+
+De forma análoga, `async for` funciona com _iteráveis assíncronos_: objetos que
+implementam `+__aiter__+`. Entretanto, `+__aiter__+` precisa ser um método
+normal—não um método corrotina—e precisa devolver um _iterador assíncrono_.
+
+<<<
+Um iterador assíncrono fornece um método corrotina `+__anext__+` que devolve um
+esperável—muitas vezes um objeto corrotina. Também se espera que eles
+implementem `+__aiter__+`, que normalmente devolve `self`. Isso espelha a
+importante distinção entre iteráveis e iteradores que vimos na
+<>.
+
+A https://fpy.li/21-43[«documentação»] do driver assíncrono de PostgreSQL _aiopg_
+traz um exemplo que ilustra o uso de `async for` para iterar sobre as linhas de
+resultados devolvidas pelo objeto cursor, definido no driver do banco de dados.
+
+[source, python]
+----
+async def go():
+    pool = await aiopg.create_pool(dsn)
+    async with pool.acquire() as conn:
+        async with conn.cursor() as cur:
+            await cur.execute("SELECT 1")
+            ret = []
+            async for row in cur:
+                ret.append(row)
+            assert ret == [(1,)]
+----
+
+Neste exemplo, a consulta vai devolver só uma 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ó. Por isso
+é 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 chegarem do PostgreSQL.
+
+
+[[async_gen_func_sec]]
+==== Funções geradoras assíncronas
+
+Você((("generators", "asynchronous generator functions", id="Gasync21"))) pode
+implementar um iterador assíncrono escrevendo uma classe com `+__anext__+` e
+`+__aiter__+`, mas há um jeito mais fácil: escreva uma função declarada com
+`async def` que use `yield` em seu corpo. Isto é semelhante à forma como funções
+geradoras simplificam o implementar o padrão do Iterador clássico.
+
+Vamos estudar um exemplo simples usando `async for` e implementando um gerador
+assíncrono. No <> vimos _blogdom.py_, um script que sondava nomes de
+domínio. Suponha agora que encontramos outros usos para a corrotina `probe`
+definida ali, e decidimos colocá-la em um novo módulo (_domainlib.py_) com
+um novo gerador assíncrono `multi_probe`, que recebe uma lista de nomes de
+domínio e produz resultados conforme eles são sondados.
+
+Vamos ver a implementação de _domainlib.py_ logo, mas primeiro examinaremos como
+ele é usado com o novo console assíncrono de Python.
+
+[[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 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_.
+Então execute:
+
+[source, shell]
+----
+$ python -m asyncio
+----
+
+Você verá o console iniciar, mais ou menos assim:
+
+[source, shell]
+----
+asyncio REPL 3.9.1 (v3.9.1:1e5d33e9b9, Dec  7 2020, 12:10:52)
+[Clang 6.0 (clang-600.0.57)] on darwin
+Use "await" directly instead of "asyncio.run()".
+Type "help", "copyright", "credits" or "license" for more information.
+>>> import asyncio
+>>>
+----
+
+Note como o cabeçalho diz que você pode usar `await` em vez de
+`asyncio.run()` para acionar corrotinas e outros esperáveis.
+E mais: eu não digitei `import asyncio`.
+O módulo `asyncio` é automaticamente importado e a instrução
+`import asyncio` é exibida para deixar isso evidente.
+
+[role="pagebreak-before less_space"]
+Vamos agora importar _domainlib.py_ e brincar com suas duas corrotinas: `probe` e `multi_probe` (<>).
+
+[[domainlib_demo_repl]]
+.Experimentando com _domainlib.py_ após executar `python3 -m asyncio`
+====
+[source, python]
+----
+>>> await asyncio.sleep(3, 'Bom dia!')  # <1>
+'Bom dia!'
+>>> from domainlib import *
+>>> await probe('python.org')  # <2>
+Result(domain='python.org', found=True)  # <3>
+>>> names = 'python.org rust-lang.org golang.org xyz.invalid'.split()  # <4>
+>>> async for result in multi_probe(names):  # <5>
+...      print(*result, sep='\t')
+...
+golang.org      True    # <6>
+xyz.invalid     False
+python.org      True
+rust-lang.org   True
+>>>
+----
+====
+<1> Experimente um simples `await` para ver o console assíncrono em ação. Dica: `asyncio.sleep()` pode receber um segundo argumento opcional que será devolvido através do `await`.
+<2> Acione a corrotina `probe`.
+<3> A versão de `probe` em `domainlib` devolve uma `NamedTuple` chamada `Result`.
+<4> Faça uma lista de domínios. O domínio de nível superior `.invalid` é reservado para testes. Consultas ao DNS por tais domínios sempre recebem uma resposta NXDOMAIN dos servidores DNS, que significa "este domínio não existe."footnote:[Veja https://fpy.li/21-45[_RFC 6761—Special-Use Domain Names_].]
+<5> Itera com `async for` sobre o gerador assíncrono `multi_probe` para exibir os resultados.
+<6> Note que os resultados não estão na ordem em que os domínios foram enviados a `multiprobe`. Eles aparecem quando cada resposta do DNS chega.
+
+O <> mostra que `multi_probe` é um gerador assíncrono, pois é compatível com `async for`. Vamos executar mais alguns experimentos, continuando com o <>.
+
+<<<
+[[domainlib_more_exp_repl]]
+.Mais experimentos, continuação do <>
+====
+[source, python]
+----
+>>> probe('python.org')  # <1>
+
+>>> multi_probe(names)  # <2>
+
+>>> for r in multi_probe(names):  # <3>
+...    print(r)
+...
+Traceback (most recent call last):
+   ...
+TypeError: 'async_generator' object is not iterable
+----
+====
+<1> Invocar uma corrotina nativa devolve um objeto corrotina.
+<2> Invocar um gerador assíncrono devolve um objeto `async_generator`.
+<3> Não podemos usar um laço `for` comum para percorrer geradores assíncronos,
+porque eles implementam `+__aiter__+` em vez de `+__iter__+`.
+
+Geradores assíncronos são acionados pelas palavras-chave `async for`,
+que pode ser uma instrução de laço (como visto em <>), 
+mas também podem aparecer em compreensões assíncronas, que veremos mais tarde.
+
+
+===== Implementando um gerador assíncrono
+
+Aqui está o módulo _domainlib.py_, com o gerador assíncrono `multi_probe`:
+
+[[domainlib_ex]]
+.domainlib.py: funções para sondar domínios
+====
+[source, python]
+----
+include::../code/21-async/domains/asyncio/domainlib.py[]
+----
+====
+<1> `NamedTuple` torna o resultado de `probe` mais fácil de ler e depurar.
+<2> Este apelido de tipo serve para evitar que a linha seguinte fique grande demais em uma listagem impressa em um livro.
+<3> `probe` agora recebe um argumento opcional `loop`, para evitar chamadas repetidas a `get_running_loop` toda vez que esta corrotina é acionada no laço em `multi_probe`.
+<4> Uma função geradora assíncrona produz um objeto gerador assíncrono, que pode ser anotado como `AsyncIterator[TipoDoItem]`.
+<5> Constrói uma lista de objetos corrotina `probe`, cada um com um `domain` diferente.
+<6> Isto não é `async for` porque `asyncio.as_completed` é um gerador clássico.
+<7> Aciona o objeto corrotina para obter o resultado.
+<8> Produz um `result`. Esta linha faz com que `multi_probe` seja um gerador assíncrono.
+
+<<<
+[NOTE]
+====
+O corpo do laço `for` no <> poderia ser mais conciso:
+
+[source, python]
+----
+    for coro in asyncio.as_completed(coros):
+        yield await coro
+----
+
+Python interpreta isso como `yield (await coro)`, então funciona.
+Achei que poderia ser confuso usar esse atalho no primeiro exemplo
+de gerador assíncrono no livro, então dividi em duas linhas.
+====
+
+Uma vez que temos o _domainlib.py_, podemos demonstrar o uso do
+gerador assíncrono `multi_probe` em _domaincheck.py_:
+um script que recebe um sufixo de domínio e busca por domínios
+criados a partir de palavras-chave curtas de Python.
+
+Aqui está uma amostra da saída de _domaincheck.py_:
+
+[source, text]
+----
+$ ./domaincheck.py net
+FOUND           NOT FOUND
+=====           =========
+in.net
+del.net
+true.net
+for.net
+is.net
+                none.net
+try.net
+                from.net
+and.net
+or.net
+else.net
+with.net
+if.net
+as.net
+                elif.net
+                pass.net
+                not.net
+                def.net
+----
+
+Graças à _domainlib_, o código de _domaincheck.py_ é bem direto:
+
+[[domaincheck_ex]]
+.domaincheck.py: utilitário para sondar domínios usando domainlib
+====
+[source, python]
+----
+include::../code/21-async/domains/asyncio/domaincheck.py[]
+----
+====
+<1> Gera palavras-chave de tamanho até `4`.
+<2> Gera nomes de domínio com o sufixo recebido como TLD (_Top Level Domain_).
+<3> Formata um cabeçalho para a saída tabular.
+<4> Itera de forma assíncrona sobre `multi_probe(domains)`.
+<5> Define `indent` como zero ou dois tabs, para colocar o resultado na coluna apropriada.
+<6> Roda a corrotina `main` com o argumento de linha de comando passado.
+
+Geradores têm uma outra utilidade, não relacionado à iteração:
+eles podem ser usados como gerenciadores de contexto. Isso também se aplica aos geradores assíncronos.
+
+[[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 precisar, considere usar o decorador
+https://fpy.li/b6[`@asynccontextmanager`], incluído no módulo `contextlib` no
+Python 3.7. É análogo 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,
+https://fpy.li/hattingh[_Using Asyncio in Python_]. O <> é o código de Caleb—com uma única mudança e o acréscimo das explicações.
+
+[[asynccontextmanager_ex]]
+.Exemplo usando `@asynccontextmanager` e `loop.run_in_executor`
+====
+[source, python]
+----
+from contextlib import asynccontextmanager
+
+
+@asynccontextmanager
+async def web_page(url):  # <1>
+    laço = asyncio.get_running_loop()   # <2>
+    data = await loop.run_in_executor(  # <3>
+        None, download_webpage, url)
+    yield data                          # <4>
+    await loop.run_in_executor(None, update_stats, url)  # <5>
+
+
+async with web_page('google.com') as data:  # <6>
+    process(data)
+----
+====
+<1> A função decorada precisa ser um gerador assíncrono.
+<2> 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 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`.
+
+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://fpy.li/b6[documentação do `contextlib`].
+
+Por fim, vamos terminar nossa jornada pelas funções geradoras assíncronas comparando-as com as corrotinas nativas.
+
+===== Geradores assíncronos versus corrotinas nativas
+
+Aqui((("native coroutines", "versus asynchronous generators", secondary-sortas="asynchronous generators"))) estão algumas semelhanças e diferenças fundamentais entre uma corrotina nativa e uma função geradora assíncrona:
+
+* Ambas são declaradas com `async def`.
+* Um gerador assíncrono sempre tem uma expressão `yield` em seu corpo—é isso que o torna um gerador. Uma corrotina nativa nunca contém um `yield`.
+* Uma corrotina nativa pode devolver (`return`) algum valor diferente de `None`, mas um gerador assíncrono só pode usar instruções `return` vazias.
+* Corrotinas nativas são esperáveis: elas podem ser acionadas por expressões `await` ou passadas para uma das muitas funções do `asyncio` que aceitam argumentos esperáveis, como `create_task` ou `gather`. Em contrapartida, geradores assíncronos não são esperáveis. Eles são iteráveis assíncronos, acionados por `async for` ou por compreensões assíncronas.
+
+Hora de falar sobre as tais compreensões assíncronas.((("", startref="Gasync21")))
+
+==== Compreensões assíncronas e expressões geradoras assíncronas
+
+A https://fpy.li/pep530[_PEP 530—Asynchronous Comprehensions_]
+introduziu((("generator expressions (genexps)",
+id="genexp21")))((("list comprehensions (listcomps)", "asynchronous", id="LCasync21")))
+o uso de `async for` e `await` na sintaxe de compreensões e expressões geradoras, a partir do Python 3.6.
+
+A única sintaxe definida na PEP 530 que pode aparecer fora do corpo
+de uma `async def` é uma expressão geradora assíncrona.
+
+===== Definindo e usando uma expressão geradora assíncrona
+
+Dado o gerador assíncrono `multi_probe` do <>,
+poderíamos escrever outro gerador assíncrono que devolvesse apenas os nomes de domínios encontrados.
+Aqui está uma forma de fazer isso—novamente usando o console assíncrono iniciado com `-m asyncio`:
+
+[source, python]
+----
+>>> from domainlib import multi_probe
+>>> names = 'python.org rust-lang.org golang.org xyz.invalid'.split()
+>>> gen_found = (name async for name, found
+...              in multi_probe(names) if found)  # <1>
+>>> gen_found
+ at 0x10a8f9700>  # <2>
+>>> async for name in gen_found:  # <3>
+...     print(name)
+...
+golang.org
+python.org
+rust-lang.org
+----
+<1> O uso de `async for` define uma expressão geradora assíncrona. Ela pode ser definida em qualquer lugar de um módulo Python.
+<2> A expressão geradora assíncrona cria um objeto `async_generator`—exatamente o mesmo tipo de objeto devolvido por uma função geradora assíncrona como `multi_probe`.
+<3> O objeto gerador assíncrono é acionado pela instrução `async for`,
+que por sua vez só pode aparecer dentro do corpo de uma `async def`
+ou no console assíncrono mágico que usei nesse exemplo.
+
+Resumindo: uma expressão geradora assíncrona pode ser definida
+em qualquer ponto do seu programa, mas só pode ser acionada
+dentro de uma corrotina nativa ou de uma função geradora assíncrona.
+
+Agora veremos as demais construções sintáticas propostas na PEP 530.
+
+===== Compreensões assíncronas
+
+Diferente das expressões geradoras assíncronas,
+as compreensões assíncronas só podem ser definidas e
+usadas dentro de corrotinas nativas ou de funções geradoras assíncronas.
+Isso faz sentido porque as compreensões são ávidas (_eager_): elas
+são executadas imediatamente para construir uma lista.
+Em contraste, as expressões geradoras (assíncronas ou não), são
+preguiçosas (_lazy_). Elas criam um objeto gerador
+que só será executado quando um laço percorrer o gerador.
+
+Yury Selivanov—autor da PEP 530—justificou a necessidade de compreensões
+assíncronas com três trechos curtos de código, reproduzidos a seguir.
+
+Podemos concordar que deveria ser possível reescrever esse código:
+
+[source, python]
+----
+result = []
+async for i in aiter():
+    if i % 2:
+        result.append(i)
+----
+
+assim:
+
+[source, python]
+----
+result = [i async for i in aiter() if i % 2]
+----
+
+Além disso, dada uma corrotina nativa `fun`, deveria ser possível escrever isso:
+
+[source, python]
+----
+result = [await fun() for fun in funcs]
+----
+
+[TIP]
+====
+Usar `await` em uma compreensão de lista é similar a usar `asyncio.gather`.
+Mas `gather` nos dá 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://fpy.li/b7[«documentação de `asyncio.gather`»]
+para mais informações.
+====
+
+Voltemos ao console assíncrono mágico:
+
+[source, python]
+----
+>>> names = 'python.org rust-lang.org golang.org xyz.invalid'.split()
+>>> names = sorted(names)
+>>> coros = [probe(name) for name in names]
+>>> await asyncio.gather(*coros)
+[Result(domain='golang.org', found=True),
+Result(domain='xyz.invalid', found=False),
+Result(domain='python.org', found=True),
+Result(domain='rust-lang.org', found=True)]
+>>> [await probe(name) for name in names]
+[Result(domain='golang.org', found=True),
+Result(domain='xyz.invalid', found=False),
+Result(domain='python.org', found=True),
+Result(domain='rust-lang.org', found=True)]
+>>>
+----
+
+Usei `sorted` para ordenar a lista de nomes e mostrar que os resultados chegam
+na ordem em que foram submetidos, nos dois casos.
+
+A PEP 530 também permite o uso de `async for` e `await` 
+compreensões de `dict` e de `set`. Por exemplo, aqui está uma
+compreensão de `dict` para armazenar os resultados de `multi_probe` no console
+assíncrono:
+
+[source, python]
+----
+>>> {name: found async for name, found in multi_probe(names)}
+{'golang.org': True, 'python.org': True, 'xyz.invalid': False,
+'rust-lang.org': True}
+----
+
+Podemos usar a palavra-chave `await` na expressão antes das cláusulas `for` ou
+`async for`, e também na expressão após a cláusula `if`. Aqui está uma
+compreensão de `set` no console assíncrono, coletando apenas os domínios
+encontrados.
+
+[source, python]
+----
+>>> {name for name in names if (await probe(name)).found}
+{'rust-lang.org', 'python.org', 'golang.org'}
+----
+
+Precisei colocar parênteses adicionais ao redor da expressão `await` devido à
+precedência mais alta do operador `.` (ponto) de `+__getattr__+`.
+
+Relembrando, todas essas compreensões só podem aparecer no corpo de uma `async def` ou no console assíncrono encantado.
+
+Agora vamos discutir uma característica muito importante das instruções e expressões `async` e dos objetos que eles criam:
+Estas construções são muito usadas com o `asyncio` mas, na verdade, 
+são independentes da biblioteca.((("", startref="LCasync21")))((("", startref="genexp21")))((("", startref="ITERasync21")))((("", startref="ITasync21")))((("", startref="APRiteration21")))
+
+
+=== Sondando domínios com _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 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 pessoa
+suficientemente motivada pode escrever seu ambiente de runtime e um framework
+assíncrono para acionar corrotinas nativas, geradores assíncronos, etc.
+
+Foi o que fez David Beazley em seu projeto https://fpy.li/21-49[_Curio_].
+Beazley estava interessado em repensar como estes recursos da linguagem poderiam
+ser usados em um framework desenvolvido do zero, sem carregar uma bagagem do
+passado. Lembre-se de que o `asyncio` foi lançado no Python 3.4, quando não existiam
+as instruções `async`, e em vez de `await` usávamos `yield from`.
+Portanto, a API original do `asyncio` não podia oferecer gerenciadores de
+contexto assíncronos, iteradores assíncronos e tudo o mais que as palavras-chave
+`async/await` tornaram possível. O resultado é que o _Curio_ tem uma API mais
+elegante e uma implementação mais simples quando comparado ao `asyncio`.
+
+[WARNING]
+====
+O _Curio_ é uma prova de conceito, e David Beazley não está mais trabalhando
+no projeto. Sua influência mais marcante está no framework
+https://fpy.li/21-58[_Trio_], que continua evoluindo e tem mais suporte
+de bibliotecas.
+
+Se você estiver usando Python 3.12 ou superior, precisará instalar
+um _fork_ atualizado publicado no PyPI como https://fpy.li/c6[_curio-compat_].
+====
+
+O <> mostra o script _blogdom.py_ (<>) reescrito
+para usar o _Curio_.
+
+<<<
+[[blogdom_curio_ex]]
+.blogdom.py: <>, agora usando o _Curio_
+====
+[source, python]
+----
+include::../code/21-async/domains/curio/blogdom.py[lines=2..]
+----
+====
+
+<1> `probe` não precisa obter o laço de eventos, porque...
+
+<2> ...`getaddrinfo` é uma função de `curio.socket`, não um
+método de um objeto `loop`—como no `asyncio`.
+
+<3> Um `TaskGroup` é um conceito central no _Curio_, para monitorar e controlar
+várias corrotinas, garantindo que elas todas sejam acionadas e encerradas, sem
+deixar alguma para trás.
+
+<4> `TaskGroup.spawn` é como você inicia uma corrotina, gerenciada por uma
+instância específica de `TaskGroup`. A corrotina é embrulhada em uma `Task`.
+
+<5> Iterar com `async for` sobre um `TaskGroup` produz instâncias de `Task` à
+medida que cada uma termina. Isto corresponde à linha em <> que usa +
+`for … in as_completed(…):`
+
+<6> O _Curio_ foi pioneiro no uso dessa maneira simples de iniciar um programa
+assíncrono em Python.
+
+Para ilustrar este último ponto: lendo os exemplos de código de `asyncio` na
+primeira edição do _Python Fluente_, verá linhas como estas repetidas várias
+vezes:
+
+[source, python]
+----
+    laço = asyncio.get_event_loop()
+    loop.run_until_complete(main())
+    loop.close()
+----
+
+==== Concorrência estruturada
+
+Um `TaskGroup` do _Curio_ é um gerenciador de contexto assíncrono que substitui
+várias APIs e padrões de codificação repetitivos do `asyncio`. Acabamos de ver como
+iterar sobre um `TaskGroup` torna a função `asyncio.as_completed(…)`
+desnecessária.
+
+Outro exemplo: em vez da função especial `gather`, este trecho da
+https://fpy.li/21-50[documentação de "Task Groups"]
+coleta os resultados de todas as tarefas no grupo:
+
+[source, python]
+----
+async with TaskGroup(wait=all) as g:
+    await g.spawn(coro1)
+    await g.spawn(coro2)
+    await g.spawn(coro3)
+print('Results:', g.results)
+----
+
+Objetos `TaskGroup` ((("structured concurrency")))((("concurrency models",
+"structured concurrency"))) suportam
+https://fpy.li/21-51[«concorrência estruturada»]:
+uma disciplina de programação concorrente que organiza todas a atividades de um
+grupo de tarefas assíncronas em um bloco de código com apenas um ponto de
+entrada e uma saída. Isto é análogo à programação estruturada, 
+que introduziu instruções de bloco para limitar os pontos de
+entrada e saída de condicionais, laços e sub-rotinas, e eliminou
+a instrução `GOTO` que permitia desviar a execução direto
+para qualquer outra linha do código.
+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 finalizadas ou canceladas e
+qualquer exceção foi levantada.
+
+[NOTE]
+====
+
+A concorrência estruturada está sendo adotada pelo `asyncio`.
+Uma evidência é a
+https://fpy.li/pep654[_PEP 654–Exception Groups and except*_], que foi
+aprovada para o Python 3.11. A seção
+https://fpy.li/21-53[_Motivation_] menciona as _nurseries_ (creches)
+do _Trio_, que correspondem aos `TaskGroup` do _Curio_:
+"Implementar uma API de acionamento de tarefas melhor no `asyncio`,
+inspirada pelas _nurseries_ do Trio, foi a principal motivação desta
+PEP."
+
+====
+
+Outra inovação 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`. Sim,
+o _Curio_ pode ser executado em uma thread ao lado do
+`asyncio` em outra thread, no mesmo processo, comunicando-se através de
+`UniversalQueue` e de `UniversalEvent`. A API destas classes "universais" é
+a mesma dentro e fora de corrotinas, mas em uma corrotina é preciso 
+acionar os métodos 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_],
+mas não sei de nenhuma biblioteca assíncrona de banco de dados que o suporte
+nesse momento. No repositório do _Curio_ há um conjunto impressionante de
+https://fpy.li/21-56[«exemplos de programação para rede»], incluindo um que
+utiliza _WebSocket_, e outro implementando o algoritmo concorrente
+https://fpy.li/21-57[_RFC 8305—Happy Eyeballs_], para conexão com pontos de acesso
+IPv6 revertendo rapidamente para IPv4 quando necessário.
+
+O design do _Curio_ foi muito influente.
+o framework https://fpy.li/21-58[_Trio_], iniciado por Nathaniel J. Smith,
+foi muito inspirado nele.
+O _Curio_ pode também ter estimulado os contribuidores de Python a melhorar a usabilidade da API do `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 métodos de `loop`,
+ou precisavam do laço de eventos como um argumento.
+Em versões mais recentes de Python, acesso direto ao laço não é mais tão necessário e,
+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")))
+
+=== Dicas de tipo para objetos assíncronos
+
+O((("asynchronous programming", "type hinting asynchronous objects")))((("type hints (type annotations)",
+"for asynchronous objects", secondary-sortas="asynchronous objects")))
+tipo devolvido por uma corrotina nativa é o tipo do objeto que você obtém quando
+usa `await` naquela corrotina, que é o tipo do objeto devolvido pela instrução
+`return` no corpo da corrotina nativa. Isto é mais simples que as anotações
+de corrotinas clássicas, discutidas na <>.
+
+Neste capítulo vimos vários exemplos de corrotinas nativas anotadas,
+incluindo a `probe` do <>:
+
+[source, python]
+----
+async def probe(domain: str) -> tuple[str, bool]:
+    try:
+        await socket.getaddrinfo(domain, None)
+    except socket.gaierror:
+        return (domain, False)
+    return (domain, True)
+----
+
+Se você precisar anotar um parâmetro que recebe um objeto corrotina como argumento,
+então o tipo genérico é:
+
+[source, python]
+----
+class typing.Coroutine(Awaitable[V_co], Generic[T_co, T_contra, V_co]):
+    ...
+----
+
+Aquele tipo e os tipos seguintes foram introduzidos no Python 3.5 e 3.6 para
+anotar objetos assíncronos:
+
+[source, python]
+----
+class typing.AsyncContextManager(Generic[T_co]):
+    ...
+class typing.AsyncIterable(Generic[T_co]):
+    ...
+class typing.AsyncIterator(AsyncIterable[T_co]):
+    ...
+class typing.AsyncGenerator(AsyncIterator[T_co],
+                            Generic[T_co, T_contra]):
+    ...
+class typing.Awaitable(Generic[T_co]):
+    ...
+----
+
+Com Python ≥ 3.9, use os equivalentes definidos em `collections.abc`.
+
+Quero destacar três aspectos destes tipos genéricos.
+
+Primeiro: eles são todos covariantes no primeiro parâmetro de tipo, que é o tipo
+dos itens produzidos a partir destes objetos. Lembre-se da regra #1 da
+https://fpy.li/cj[«Seção 15.7.4.4»] (vol.2):
+
+[quote]
+____
+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 no penúltimo parâmetro.
+Aquele é o tipo do argumento passado ao método de baixo nível
+`.send()`, que o laço de eventos invoca para acionar geradores assíncronos e
+corrotinas. Desta forma, é um tipo de "entrada", então
+vale a regra #2 da variância:
+
+[quote]
+____
+Se um parâmetro de tipo formal define um tipo para um dado que entra
+no objeto após sua construção inicial, ele pode ser contravariante.
+____
+
+Terceiro: `AsyncGenerator` não tem tipo de retorno, ao contrário de `typing.Generator`,
+usado para anotar corrotinas clássicas, apesar do nome que sugere outra coisa,
+como vimos na <>.
+Devolver um valor levantando `StopIteration(value)` foi a gambiarra que permitiu
+a geradores funcionarem como corrotinas clássicas suportando `yield from`, como
+vimos na <>. Não há tal sobreposição entre os objetos
+assíncronos: objetos `AsyncGenerator` produzem itens mas não devolvem
+um resultado final, e são completamente
+separados de objetos corrotina que devolvem um resultado, mas não usam `yield`.
+
+Por fim, vamos discutir rapidamente as vantagens e desafios da programação assíncrona.
+
+
+[[how_async_works_and_does_not_sec]]
+=== Como a programação assíncrona funciona e como não funciona
+
+As seções finais deste capítulo discutem ideias de alto nível sobre
+programação assíncrona, independente da linguagem ou da biblioteca usadas.
+
+Vamos começar explicando por que a programação assíncrona é útil,
+seguido por um mito popular e como lidar com ele.
+
+
+[[around_blocking_calls_sec]]
+==== Correndo em círculos em torno de chamadas bloqueantes
+
+Ryan Dahl, o((("asynchronous programming", "benefits of"))) inventor do Node.js,
+introduz a filosofia do projeto dizendo "Estamos fazendo E/S de
+forma totalmente errada."footnote:[Vídeo: https://fpy.li/21-59[_Introduction to
+Node.js_], em 4:55.] Ele define uma "função
+bloqueante" como uma função que faz E/S de arquivo ou rede, e argumenta que elas
+não podem ser tratadas da mesma forma que tratamos funções não-bloqueantes.
+Para explicar a razão disso, ele apresenta os números na segunda coluna da
+<>.
+
+[[latency_tbl]]
+.Latência em computadores modernos para ler dados em diferentes dispositivos. A terceira coluna mostra os tempos proporcionais em uma escala fácil de entender para nós, humanos vagarosos.
+[options="header", cols="1,>1,^2"]
+|=======================================================
+|Dispositivo  |Ciclos de CPU |Escala proporcional "humana"
+|cache L1     |3             |3 segundos
+|cache L2     |14            |14 segundos
+|RAM          |250           |250 segundos
+|HD local     |41.000.000    |1,3 anos
+|rede         |240.000.000   |7,6 anos
+|=======================================================
+
+
+Para entender a <>,
+tenha em mente que as CPUs modernas, com seus _clocks_ em frequências na casa dos GHz, rodam bilhões de ciclos por segundo.
+Suponha que uma CPU rode exatamente 1 bilhão de ciclos por segundo.
+Tal CPU pode realizar mais de 333 milhões de leituras do cache L1 em 1 segundo,
+ou 4 (quatro!) leituras da rede no mesmo segundo.
+A terceira coluna da <> coloca os números em perspectiva, multiplicando a segunda coluna por um fator constante.
+Então, em um universo alternativo, se uma leitura da RAM demorasse 250 segundos, uma leitura da rede demoraria 7,6 anos!
+A diferença quantitativa é tão grande que se torna uma diferença qualitativa importante:
+esperar 250 segundos é muito diferente de esperar 7,6 anos!
+
+A <> explica por que uma abordagem disciplinada da programação
+assíncrona pode levar a servidores de alto desempenho. O desafio é alcançar
+esta disciplina. O primeiro passo é reconhecer que um sistema limitado apenas
+por E/S é uma fantasia.
+
+[[myth_iobound_sec]]
+==== O mito dos sistemas limitados por E/S
+
+Um((("asynchronous programming", "myth of I/O-bound systems")))((("network I/O",
+"myth of I/O-bound systems"))) meme exaustivamente repetido é que programação
+assíncrona é boa para _I/O bound systems_ (sistemas limitados por E/S), ou seja,
+sistemas onde o gargalo é a entrada e saída de dados, e não o processamento de
+dados na CPU. 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 CPU e na memória. 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. Até mesmo sistemas triviais revelam isso, sob stress. Na
+caixa _<>_ ao final deste capítulo escrevi sobre dois programas assíncronos sofrendo com
+funções limitadas pela CPU que atrasavam o 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,
+lidar com elas é a chave do sucesso na programação assíncrona.
+
+[[avoid_cpu_trap_sec]]
+==== Evitando as armadilhas do uso da CPU
+
+Se((("asynchronous programming", "avoiding CPU-bound traps")))((("CPU-bound
+systems"))) você está usando Python em larga escala, precisa ter testes
+automatizados especificamente para detectar regressões de desempenho
+assim que elas acontecem. Isso é de importância crítica com código assíncrono,
+mas é relevante também para código Python baseado em threads—por causa da GIL.
+Se você esperar até a lentidão começar a incomodar a equipe de desenvolvimento,
+será tarde demais. A solução poderá exigir mudanças drásticas.
+
+<<<
+Aqui estão algumas opções para quando você identifica gargalos de uso da CPU:
+
+* Delegar a tarefa para um banco de processos Python.
+* Delegar a tarefa para uma fila de tarefas externa.
+* Reescrever o código relevante em Cython, C, Rust ou alguma outra linguagem que compile para código de máquina e faça interface com a API Python/C, de preferência liberando a GIL.
+* Decidir que pode tolerar a perda de desempenho e deixar como está—mas registre essa decisão, para ficar mais fácil revertê-la no futuro.
+
+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 de deixar como está entra na conta de https://fpy.li/b8[«dívida tecnológica»].
+
+Programação concorrente é um tópico fascinante, e eu gostaria de escrever mais.
+Mas não é o foco principal deste livro,
+e este já é um dos capítulos mais longos, então vamos encerrar por aqui.
+
+
+=== Resumo do capítulo
+
+[quote, Alvaro Videla e Jason J. W. Williams, RabbitMQ in Action]
+____
+O problema com as abordagens usuais da programação assíncrona é
+que elas são propostas do tipo "tudo ou nada".
+Ou você reescreve todo o código, de forma que nada nele bloqueie, 
+ou você está só perdendo tempo.
+____
+
+
+Escolhi((("asynchronous programming", "overview of"))) esta epígrafe para este
+capítulo por duas razões. Em um nível mais alto, ela nos lembra de evitar o
+bloqueio do laço de eventos, delegando tarefas lentas para outra unidade de
+processamento, desde uma thread ou processo local, 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 de repente pode complicar o seu trabalho.
+
+<<<
+Após os exemplos simples com o _spinner_ no <>, aqui
+nosso maior foco foi a programação assíncrona com corrotinas nativas, começando
+com o exemplo de sondagem de DNS _blogdom.py_, seguido pelo conceito de
+esperável. No código-fonte de _flags_asyncio.py_ encontramos o primeiro
+exemplo de um gerenciador de contexto assíncrono: `httpx.AsyncClient`.
+
+As variantes mais avançadas do programa de download de bandeiras apresentaram
+duas funções poderosas: o gerador `asyncio.as_completed` e a corrotina
+`loop.run_in_executor` para delegar tarefas para threads ou processos.
+Também vimos o conceito e a aplicação de um semáforo,
+para limitar o número de downloads concorrentes—como se espera de um
+cliente HTTP bem comportado.
+
+A programação assíncrona para servidores foi apresentada com os exemplos
+_mojifinder_: um serviço Web usando a _FastAPI_ e o _tcp_mojifinder.py_—este
+último utilizando apenas o protocolo TCP e o `asyncio`.
+
+A seguir, iteração assíncrona e iteráveis assíncronos foram o principal tópico,
+com seções sobre `async for`, o console assíncrono de Python, geradores
+assíncronos, expressões geradoras assíncronas, e compreensões assíncronas.
+
+O último exemplo do capítulo foi o _blogdom.py_ reescrito com o framework
+_Curio_, demonstrando como os recursos de programação assíncrona de Python não
+estão presos ao pacote `asyncio`. O _Curio_ também demonstra o conceito de
+_concorrência estruturada_, que poderá ter um grande impacto muitas
+linguagens de programação, trazendo mais clareza para o código concorrente.
+
+Por fim, a <> apresentou
+o principal atrativo da programação assíncrona: não perder tempo
+esperando por E/S.
+Também vimos que não existem sistemas limitados só por E/S,
+e como lidar com as inevitáveis partes do seu programa assíncrono
+que utilizam intensivamente a CPU.
+
+
+=== Para saber mais
+
+A((("asynchronous programming", "further reading on"))) palestra 
+de David Beazley na abertura da PyOhio 2016,
+https://fpy.li/21-61[_Fear and Awaiting in Async_]
+(Medo e espera em [programação] assíncrona) é uma introdução incrível
+com "código ao vivo" demonstrando os
+recursos da linguagem viabilizados pela contribuição de Yury Selivanov ao
+Python 3.5: as palavras-chave `async/await`. Em certo momento, Beazley reclama
+que `await` não pode ser usada em compreensões de lista, mas isso foi resolvido
+por Selivanov na
+https://fpy.li/pep530[_PEP 530—Asynchronous Comprehensions_],
+implementada mais tarde naquele mesmo ano, no Python 3.6.
+
+Fora isso, todo o resto da palestra de Beazley é atemporal, pois ele revela
+como os objetos assíncronos vistos neste capítulo funcionam, sem ajuda de
+qualquer framework—com uma simples função `run` que invoca `.send(None)` para
+acionar corrotinas. Apenas no final Beazley mostra o
+https://fpy.li/21-62[_Curio_], que ele havia começado a desenvolver naquele ano,
+como uma prova de conceito, para ver o quão longe seria possível levar a programação
+assíncrona usando apenas corrotinas, sem callbacks ou _futures_. 
+Como vimos, dá para ir muito longe—como demonstra o _Curio_ e o desenvolvimento
+posterior do https://fpy.li/21-58[_Trio_] por Nathaniel J. Smith. A
+documentação do _Curio_ contém https://fpy.li/21-64[«links»] para outras
+palestras de Beazley sobre o assunto.
+
+Além de criar o _Trio_,
+Nathaniel J. Smith escreveu dois artigos muito profundos, que eu recomendo:
+https://fpy.li/21-65[_Some thoughts on asynchronous API design in a post-async/await world_]
+(Algumas reflexões sobre o design de APIs assíncronas em um mundo pós-async/await),
+comparando os designs do _Curio_ e do `asyncio`, e
+https://fpy.li/21-66[_Notes on structured concurrency, or: `go` statement considered harmful_]
+(Notas sobre concorrência estruturada, ou: a instrução `go` considerada nociva),
+sobre concorrência estruturada. Smith também deu uma longa e informativa resposta à questão:
+https://fpy.li/21-67[_What is the core difference between `asyncio` and Trio?]
+(Qual é a principal diferença entre `asyncio` e Trio?) no StackOverflow.
+
+Para aprender mais sobre o pacote `asyncio`, já mencionei os melhores recursos
+que conheço no início do capítulo: a 
+https://fpy.li/b9[«documentação oficial»], após a
+https://fpy.li/21-69[«profunda reorganização»] iniciada
+por Yury Selivanov em 2018, e o livro de Caleb Hattingh,
+https://fpy.li/hattingh[_Using Asyncio in Python_] (O'Reilly).
+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 de 30 minutos, muito acessível, à programação assíncrona em geral e também ao `asyncio`,
+assista a palestra
+https://fpy.li/21-71[_Asynchronous Python for the Complete Beginner_] (Python Assíncrono para o Iniciante Total),
+de Miguel Grinberg, apresentada na PyCon 2017. Outra ótima introdução é
+https://fpy.li/21-72[_Demystifying Python's Async and Await Keywords_] (Desmistificando as Palavras-Chave Async e Await de Python),
+apresentada por Michael Kennedy, onde aprendi sobre a biblioteca
+https://fpy.li/21-73[_unsync_],
+que fornece um decorador para delegar a execução de corrotinas,
+funções dedicadas a E/S e funções de uso intensivo de CPU para `asyncio`,
+`threading`, ou `multiprocessing`, conforme a necessidade.
+
+Na EuroPython 2019, Lynn Root—uma das líderes mundiais das https://fpy.li/21-74[_PyLadies_]—apresentou a excelente
+https://fpy.li/21-75[_Advanced asyncio: Solving Real-world Production Problems_]
+(Asyncio Avançado: Resolvendo Problemas de Produção do Mundo Real),
+a partir de sua experiência usando Python como engenheira no Spotify.
+
+Em 2020, Łukasz Langa gravou uma ótima série de vídeos sobre o `asyncio`, começando com
+https://fpy.li/21-76[_Learn Python's AsyncIO #1—The Async Ecosystem_]
+(Aprenda o AsyncIO de Python—O Ecossistema Async).
+Langa também fez um vídeo muito bacana,
+https://fpy.li/21-77[_AsyncIO + Music_],
+para a PyCon 2020, que mostra o `asyncio` aplicado a um domínio orientado a eventos muito concreto,
+e também explica esta aplicação do início ao fim.
+
+Outra área dominada por programação orientada a eventos são os sistemas embarcados.
+Por isso Damien George adicionou o suporte a `async/await` em seu interpretador
+https://fpy.li/21-78[_MicroPython_] para microcontroladores.
+Na PyCon Australia 2018, Matt Trentini demonstrou a biblioteca
+https://fpy.li/21-79[_uasyncio_],
+um subconjunto de `asyncio` que é parte da biblioteca padrão do MicroPython.
+
+Para uma visão de mais alto nível sobre a programação assíncrona em Python, leia o post
+https://fpy.li/21-80[_Python async frameworks—Beyond developer tribalism_]
+(Frameworks assíncronos de Python—para além do tribalismo dos desenvolvedores), de Tom Christie.
+
+Por fim, recomendo
+https://fpy.li/21-81[_What Color Is Your Function?_]
+(Qual a Cor da Sua Função?) de Bob Nystrom,
+discutindo os modelos de execução incompatíveis entre funções comuns e
+funções assíncronas—que chamamos de corrotinas—em JavaScript, Python, C# e outras linguagens.
+Alerta de spoiler: a conclusão de Nystrom é que a linguagem que acertou nessa área foi Go,
+onde todas as funções têm a mesma cor. Gosto disso no Go.
+Mas também acho que Nathaniel J. Smith tem razão quando escreveu
+https://fpy.li/21-66[_Go statement considered harmful_]
+(Instrução `go` considerada nociva).
+Nada é perfeito, e programação concorrente é sempre difícil.
+
+
+[[async_soapbox]]
+.Ponto de vista
+****
+
+
+*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 laço de
+eventos embutido do `asyncio`." Os _benchmarks_ (números de desempenho)
+apresentados no https://fpy.li/21-84[«post»] de Selivanov anunciando a
+biblioteca, em 2016, eram muito impressionantes: "ela é pelo menos
+2x mais rápida que o nodejs e gevent, bem como qualquer outro framework
+assíncrono de Python. O desempenho do `asyncio` com o _uvloop_ é próximo ao
+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
+ por múltiplas corrotinas acionadas por um laço de eventos, tudo 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 de que a
+ concorrência não exige múltiplas threads ou múltiplos núcleos de CPU.]
+ 
+ . Que o código Python 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 do _uvloop_
+com a https://fpy.li/21-86[_aiohttp_]—uma das primeiras bibliotecas HTTP
+completas construídas sobre o `asyncio`:
+
+[quote]
+____
+
+Entretanto, o gargalo de desempenho no _aiohttp_ estava em seu parser de HTTP,
+que era tão lento que pouco importava a velocidade da biblioteca de E/S
+subjacente. Para tornar as coisas mais interessantes, criamos uma biblioteca
+para Python usar a _http-parser_ (a biblioteca em C do parser do Node.js,
+originalmente desenvolvida para o _NGINX_). A biblioteca é chamada _httptools_,
+e está disponível no Github e no PyPI.
+
+____
+
+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 caso, a análise de cabeçalhos HTTP é intensiva em CPU, e tinha uma
+implementação lenta, em Python, na biblioteca _aiohttp_ quando Selivanov
+realizou os testes em 2016. 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 que usa intensivamente a CPU, os ganhos de desempenho
+de um laço de eventos mais rápido eram perdidos.
+
+*Morte lenta*
+
+Em((("Soapbox sidebars", "Twisted library")))((("Twisted library"))) vez de um
+simples servidor eco, imagine um sistema Python complexo e em evolução, com
+milhares de linhas de código assíncrono, e conectado a muitas bibliotecas
+externas.
+
+Anos atrás me pediram para ajudar a diagnosticar problemas de desempenho em um
+sistema assim. Ele era escrito em Python 2.7, com o framework
+https://fpy.li/21-88[_Twisted_]—uma biblioteca sólida, de alto desempenho,
+precursora do próprio `asyncio`.
+
+Python era usado para construir uma fachada para a interface Web,
+integrando funcionalidades fornecidas por bibliotecas pré-existentes e
+ferramentas de linha de comando escritas em outras
+linguagens—mas não projetadas para execução concorrente.
+
+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 daquele 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 passar do tempo,
+os desenvolvedores
+notaram que o desempenho do sistema estava piorando, e o time não
+conseguia localizar os principais gargalos.
+
+O que estava acontecendo: cada nova funcionalidade introduzia mais código
+intensivo em CPU, atrasando o laço de eventos do _Twisted_. O papel de Python
+como uma linguagem de integração entre processos externos implicava em
+muita interpretação de dados, serialização, desserialização, e conversões
+entre formatos. Não havia um gargalo único: o problema estava espalhado por
+incontáveis pequenas funções criadas ao longo de meses de desenvolvimento.
+
+<<<
+A solução seria repensar a arquitetura do sistema, reescrever muito código, usar
+uma fila de tarefas, e talvez criar microsserviços ou bibliotecas customizadas,
+escritas em linguagens mais eficientes no processamento concorrente intensivo em
+CPU (eu sugeri Go para esta finalidade). Os gestores não quiseram
+fazer aquele investimento adicional, e o projeto foi cancelado semanas depois
+deste diagnóstico.
+
+Quando contei essa história para Glyph Lefkowitz—fundador do projeto
+_Twisted_—ele falou que é prioritário decidir quais ferramentas serão usadas para
+executar tarefas intensivas em CPU sem atrapalhar o laço de eventos, logo no
+início de qualquer projeto envolvendo programação assíncrona. Esta conversa com
+Glyph foi a inspiração para a <>.
+
+****
diff --git a/vol3/cap22.adoc b/vol3/cap22.adoc
new file mode 100644
index 00000000..f7f5f6bf
--- /dev/null
+++ b/vol3/cap22.adoc
@@ -0,0 +1,1712 @@
+[[ch_dynamic_attrs]]
+== Atributos dinâmicos e propriedades
+:example-number: 0
+:figure-number: 0
+
+[quote, Martelli, Ravenscroft & Holden, Why properties are important (Porque propriedades são importantes)]
+____
+
+As propriedades são muito importantes porque tornam perfeitamente seguro, e até
+aconselhável, expor publicamente atributos de dados como parte da interface pública
+de sua classe.footnote:[Alex Martelli, Anna Ravenscroft & Steve Holden,
+https://fpy.li/pynut3[Python in a Nutshell, Third Edition] (O'Reilly), p. 123.]
+
+____
+
+No Python((("dynamic attributes and properties", "dynamic versus virtual attributes"))),
+atributos de dados (ou campos) e métodos são conhecidos conjuntamente como _atributos_ .
+Um método é um atributo invocável.
+Atributos dinâmicos oferecem a mesma interface que os atributos de dados—isto é, `obj.atrib`—mas são computados sob demanda.
+Isso atende ao _Princípio de Acesso Uniforme_ de Bertrand Meyer:
+
+[quote, Bertrand Meyer, Object-Oriented Software Construction (Construção de Software Orientada a Objetos)]
+____
+
+Todos os serviços oferecidos por um módulo devem estar disponíveis através +
+de uma notação uniforme, que não revele se eles são implementados por
+armazenamento ou
+por computação.footnote:[Bertrand Meyer, Object-Oriented Software Construction,
+2nd ed. (Pearson), p. 57.]
+
+____
+
+Há várias formas de implementar atributos dinâmicos em Python.
+Este capítulo trata das mais simples: o decorador `@property` e o método especial `+__getattr__+`.
+
+Uma((("virtual attributes")))((("attributes", "virtual attributes"))) classe
+definida pelo usuário pode implementar `+__getattr__+` para oferecer uma
+variação de atributos dinâmicos que chamo de _atributos virtuais_: atributos
+que não são declarados explicitamente em lugar algum no código-fonte da classe,
+e que não estão presentes no `+__dict__+` das instâncias, mas que podem ser
+obtidos de algum outro lugar ou calculados sob demanda sempre que um usuário
+tenta ler um atributo inexistente tal como `obj.ausente`.
+
+Programar atributos dinâmicos e virtuais é o tipo de metaprogramação que autores
+de frameworks fazem. Entretanto, como as técnicas básicas no Python são simples,
+podemos usá-las em tarefas cotidianas de processamento de dados. É por aí que
+iniciaremos esse capítulo.
+
+
+=== Novidades neste capítulo
+
+As atualizações ((("dynamic attributes and properties", "significant changes to")))
+deste capítulo foram motivadas pela discussão relativa a
+`@functools.cached_property` (introduzido no Python 3.8), o uso
+combinado de `@property` e `@functools.cache` (novo no 3.9). Isto afetou o
+código das classes `Record` e `Event`, que aparecem na <>.
+Também fiz uma refatoração para aproveitar a otimização da
+https://fpy.li/pep412[_PEP 412—Key-Sharing Dictionary_]
+(Dicionário com chaves compartilhadas).
+
+Para enfatizar as características mais relevantes, e ao mesmo tempo manter os
+exemplos legíveis, removi algum código não-essencial—fundindo a antiga classe
+`DbRecord` com `Record`, substituindo `shelve.Shelve` por um `dict`, e suprimindo
+a lógica para baixar o conjunto de dados da OSCON—que os exemplos agora carregam de
+um arquivo local, disponível no https://fpy.li/code[«repositório de código»] do
+_Python Fluente_.
+
+=== Explorando dados com atributos dinâmicos
+
+Nos((("dynamic attributes and properties", "data wrangling with dynamic attributes", id="DAPwrangl22")))((("data wrangling", "with dynamic attributes", secondary-sortas="dynamic attributes", id="DWdyatt22"))) próximos exemplos, vamos nos valer dos atributos dinâmicos para trabalhar com um conjunto de dados JSON publicado pela O'Reilly, para a conferência OSCON 2014. O <> mostra quatro registros daquele conjunto de dados.footnote:[A OSCON—O'Reilly Open Source Conference (_Conferência O'Reilly de Código Aberto_)—foi uma vítima da pandemia de COVID-19. O arquivo JSON original de 744 KB, que usei para esses exemplos, não está mais disponível online hoje (10 de janeiro de 2021). Você pode obter uma cópia do https://fpy.li/22-1[_osconfeed.json_] no repositório de exemplos do livro.]
+
+[[ex_osconfeed_json]]
+.Amostra de registros do osconfeed.json, com alguns dados abreviados.
+====
+[source, json]
+----
+include::../code/22-dyn-attr-prop/oscon/osconfeed-sample.json[]
+----
+====
+
+O <> mostra 4 dos 895 registros do arquivo JSON. O conjunto
+completo total é um único objeto JSON, com a chave `"Schedule"` (cronograma), e seu
+valor é outro mapeamento com quatro chaves: `"conferences"` (conferências),
+`"events"` (eventos), `"speakers"` (palestrantes), e `"venues"` (locais).
+Cada uma destas quatro chaves aponta para uma lista de registros. No
+conjunto de dados completo, as listas de `"events"`, `"speakers"` e
+`"venues"` contêm dezenas ou centenas de registros, mas `"conferences"`
+contém apenas aquele único registro exibido na segunda linha do <>. Cada
+registro inclui um campo `"serial"`, que é um identificador único do registro
+dentro da lista onde ele está.
+
+Usei o console de Python para explorar os dados interativamente, como mostra o <>.
+
+[[ex_osconfeed_explore]]
+.Exploração interativa do osconfeed.json
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/osconfeed_explore.rst[]
+----
+====
+<1> `feed` é um `dict` contendo dicts e listas aninhados, com valores string e inteiros.
+<2> Lista as quatro coleções de registros dentro de `'Schedule'`. 
+<3> Exibe a contagem de registros para cada coleção.
+<4> Navega pelos dicts e listas aninhados para obter o nome da última palestrante (`speaker`).
+<5> Obtém o número de série daquela palestrante.
+<6> Cada evento tem uma lista `'speakers'`, com o número de série de zero ou mais palestrantes.((("", startref="DWdyatt22")))
+
+
+==== Explorando dados JSON e similares com atributos dinâmicos
+
+O <> é((("data wrangling", "JSON-like data",
+id="DWjsaon22")))((("JSON-like data", id="jsonlike22"))) simples,
+mas esta sintaxe é inconveniente:
+
+[source, python]
+----
+feed['Schedule']['events'][40]['name']
+----
+
+Em JavaScript, é possível obter o mesmo valor escrevendo
+`feed.Schedule.events[40].name`.
+Não é difícil implementar uma classe parecida com
+um `dict` para fazer o mesmo em Python—há inúmeras implementações na
+Web.footnote:[Dois exemplos são https://fpy.li/22-2[`AttrDict`] e
+https://fpy.li/22-3[`addict`].]
+Escrevi `FrozenJSON`, que é mais simples que a maioria das soluções porque
+só permite leitura: ela serve apenas para explorar os dados. `FrozenJSON` é
+recursiva, lidando automaticamente com mapeamentos e listas aninhados.
+
+O <> demonstra `FrozenJSON`; código-fonte no <>.
+
+[[ex_explore0_demo]]
+.`FrozenJSON`, do <>, permite ler atributos como `talk.name`, e invocar métodos como `++feed.keys()++` e `++feed.items()++`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/explore0.py[tags=EXPLORE0_DEMO]
+----
+====
+<1> Cria uma instância de `FrozenJSON` a partir de `raw_feed`, feito de dicts e listas aninhados.
+<2> `FrozenJSON` permite percorrer dicts aninhados usando a notação de atributos; aqui exibimos o tamanho da lista de palestrantes.
+<3> Métodos dos dicts subjacentes também podem ser acessados; por exemplo, `.keys()`, para recuperar os nomes das coleções de registros.
+<4> Usando `items()`, podemos buscar os nomes das listas de registros e seus conteúdos, para exibir o `len()` de cada uma.
+<5> Uma `list`, tal como `feed.Schedule.speakers`, permanece uma lista, mas os itens dentro dela, se forem mapeamentos, são convertidos em um `FrozenJSON`.
+<6> O item 40 na lista `events` era um objeto JSON; agora ele é uma instância de `FrozenJSON`.
+<7> Registros de eventos têm uma lista de `speakers` com os números de série dos palestrantes.
+<8> Tentar ler um atributo inexistente gera uma exceção `KeyError`, em vez da `AttributeError` usual.
+
+A pedra angular da classe `FrozenJSON` é o método `+__getattr__+`, que já usamos
+no exemplo `Vector` da https://fpy.li/cp[«Seção 12.6»] (vol.2), para recuperar componentes
+de `Vector` por letra: `v.x`, `v.y`, `v.z`, etc.
+
+É importante lembrar que o
+método especial `+__getattr__+` só é invocado pelo interpretador quando o
+processo habitual não consegue recuperar um atributo (isto é, quando o atributo
+acessado não é encontrado na instância, nem na sua classe ou suas superclasses).
+
+O passo `⑧` do <> expõe um pequeno problema em meu código:
+tentar ler um atributo ausente deveria produzir uma exceção `AttributeError`, e
+não a `KeyError` gerada. Quando implementei o tratamento de erro para fazer
+isso, o método `+__getattr__+` se tornou duas vezes mais longo, ofuscando
+a essência da lógica que eu queria apresentar. Dado que os usuários
+devem saber que uma `FrozenJSON` é criada a partir de mapeamentos e listas,
+levantar `KeyError` não é tão confuso assim.
+
+
+[[ex_explore0]]
+.explore0.py: transforma um conjunto de dados JSON em um `FrozenJSON` contendo objetos `FrozenJSON` aninhados, listas e tipos simples
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/explore0.py[tags=EXPLORE0]
+----
+====
+
+<1> Cria um `dict` a partir do argumento `mapping`. Isso garante que teremos um
+mapeamento ou algo que poderá ser convertido para isso. O sublinhado duplo
+no prefixo de `+__data+` o torna um atributo privado.
+
+<2> `+__getattr__+` é invocado só quando não existe um atributo com aquele
+`name`.
+
+<3> Se `name` corresponde a um atributo da instância de `dict` `++__data++`,
+devolve aquele atributo. É assim que chamadas como `feed.keys()` são tratadas: o
+método `keys` é um atributo do `dict` `__data`.
+
+<4> Caso contrário, obtém o item `+self.__data+` com a chave `name`, e
+devolve o resultado da chamada `FrozenJSON.build()` com aquele
+argumento.
+
+<5> Implementar `+__dir__+` suporta a função embutida `dir()`, que por sua vez
+suporta _auto-complete_ no console padrão de
+Python, bem como no IPython, no Jupyter Notebook, etc. Este código simples vai
+permitir _auto-complete_ recursivo baseado nas chaves em
+`+self.__data+`, porque `+__getattr__+` cria instâncias de `FrozenJSON` sob
+demanda, facilitando a exploração interativa dos dados.
+
+<6> Este é um construtor alternativo, um uso comum do decorador `@classmethod`.
+
+<7> Se `obj` é um mapeamento, cria um `FrozenJSON` com ele. Este é um exemplo de
+tipagem ganso—veja a https://fpy.li/cq[«Seção 13.5»] (vol.2) caso precise rever este conceito.
+
+<8> Se for uma `MutableSequence`, criamos uma `list`, passando recursivamente
+cada item em `obj` para `.build()`.
+
+<9> Se não for um `dict` ou uma `list`, devolve o item como está.
+
+Cada instância de `FrozenJSON` contém um atributo de instância privado
+`+__data+`, armazenado sob o nome `++_FrozenJSON__data++`, como explicado na
+https://fpy.li/cr[«Seção 11.10»] (vol.2).
+
+Tentativas de recuperar atributos por outros nomes vão disparar `+__getattr__+`.
+Primeiro, esse método verá se o `dict` vinculado a `+self.__data+` tem um
+atributo (não uma chave!) com aquele nome.
+Assim podemos invocar métodos do `dict`, como `.items()`, pois neste caso
+o `FrozenJSON` vai invocar `+self.__data.items()+`.
+Se `+self.__data+` não tiver um atributo com o `name` dado, `+__getattr__+`
+usa `name` como chave para recuperar um item de
+`+self.__data+`, e passa aquele item para `FrozenJSON.build`. Assim podemos
+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`.
+Isto funciona bem com um conjunto de dados não muito grande,
+em um script que só será usado para explorar ou converter os dados.
+
+Qualquer script que gera dinamicamente nomes de atributos a partir de
+dados arbitrários precisa lidar com uma questão: as chaves nos dados
+podem não ser nomes adequados de atributos. A próxima seção fala disso.((("",
+startref="jsonlike22")))((("", startref="DWjsaon22")))
+
+
+[[dynamic_names_sec]]
+==== O problema do nome de atributo inválido
+
+O((("data wrangling", "invalid attribute name problem")))((("invalid attribute name problem")))
+código de `FrozenJSON` não funciona com nomes de atributos que
+sejam palavras reservadas de Python. Por exemplo, se você criar um objeto assim:
+
+[source, python]
+----
+>>> student = FrozenJSON({'name': 'Jim Bo', 'class': 1982})
+----
+
+não será possível acessar `student.class`, porque `class` é uma palavra reservada no Python:
+
+[source, python]
+----
+>>> student.class
+  File "", line 1
+    student.class
+         ^
+SyntaxError: invalid syntax
+----
+
+Claro, sempre é possível fazer assim:
+
+[source, python]
+----
+>>> getattr(student, 'class')
+1982
+----
+
+Mas a ideia de `FrozenJSON` é oferecer acesso conveniente aos dados, então uma
+solução melhor é verificar se uma chave no mapeamento passado para
+`+FrozenJSON.__init__+` é uma palavra reservada e, em caso positivo, anexar um
+`_` a ela, de forma que o atributo possa ser acessado assim:
+
+[source, python]
+----
+>>> student.class_
+1982
+----
+
+Podemos fazer isto substituindo o `+__init__+` de uma linha do <> pela versão no <>.
+
+[[ex_explore1]]
+.explore1.py: anexa um `_` a nomes de atributo que são palavras reservadas do Python
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/explore1.py[tags=EXPLORE1]
+----
+====
+<1> A função `keyword.iskeyword(…)` é o que precisamos; para usá-la, precisamos importar o módulo `keyword`;
+a importação está antes deste trecho do código.
+
+Um problema similar pode surgir se uma chave em um registro JSON não for um identificador válido em Python:
+
+
+[source, python]
+----
+>>> x = FrozenJSON({'2be':'or not'})
+>>> x.2be
+  File "", line 1
+    x.2be
+      ^
+SyntaxError: invalid syntax
+----
+
+Essas chaves problemáticas são fáceis de detectar no Python 3, porque a classe
+`str` oferece o método `s.isidentifier()`, que informa se `s` é um identificador
+Python válido, de acordo com a gramática da linguagem. Mas transformar uma chave
+que não seja um identificador válido em um nome de atributo válido não é
+trivial. Uma solução seria implementar `+__getitem__+` para permitir acesso a
+atributos usando uma notação como `x['2be']`. Em nome da simplicidade, não vou
+me preocupar com esse problema.
+
+Após essa pequena conversa sobre os nomes de atributos dinâmicos, vamos examinar
+outra característica essencial de `FrozenJSON`: a lógica do método de classe
+`build`. Este método é invocado por `+__getattr__+` para devolver um tipo
+diferente de objeto, dependendo do valor do atributo que está sendo acessado:
+estruturas aninhadas são convertidas para instâncias de `FrozenJSON` ou listas
+de instâncias de `FrozenJSON`.
+
+Em vez de usar um método de classe, podemos implementar esta lógica no método especial
+`+__new__+`, como veremos a seguir.
+
+
+[[flexible_new_sec]]
+==== Criação flexível de objetos com `+__new__+`
+
+Muitas((("data wrangling", "flexible object creation",
+id="DWflex22")))((("*_new*_",
+id="new22")))((("objects", "flexible object creation", id="Oflex22"))) vezes
+dizemos que o `+__init__+` é o "método construtor", mas isso é porque adotamos o
+jargão de outras linguagens.
+No Python, `+__init__+` recebe `self` como primeiro
+argumento, portanto o objeto já foi construído pelo interpretador quando ele invoca
+`+__init__+`. Além disso, `+__init__+` não devolve um valor. Então, na
+verdade, esse método é um inicializador, não propriamente um construtor.footnote:[Em Java acontece
+a mesma coisa: a variável mágica `this` dentro de um "construtor" em Java já aponta
+para um objeto previamente construído e alocado na memória, e o que resta para o seu código é apenas
+inicializá-lo.]
+
+Quando uma classe é invocada para criar uma instância, Python invoca o método especial 
+`+__new__+` da classe para construir a instância.
+
+É um método de classe, mas recebe tratamento especial,
+então o decorador `@classmethod` não é aplicado a ele.
+Python recebe a instância devolvida por `+__new__+`,
+e daí a passa como o primeiro argumento (`self`) para `+__init__+`. 
+Raramente precisamos escrever um `+__new__+`,
+pois a implementação herdada de `object` atende aos casos comuns.
+
+Se necessário, o método `+__new__+` pode devolver uma instância de uma classe diferente.
+Quando isso acontece, o interpretador não invoca `+__init__+`.
+Em outras palavras, a lógica de Python para criar um objeto é similar a esse pseudo-código:
+
+[source, python]
+----
+# pseudocódigo
+def criar(a_classe, algum_arg):
+    novo_objeto = a_classe.__new__(algum_arg)
+    if isinstance(novo_objeto, a_classe):
+        novo_objeto.__init__(algum_arg)
+    return novo_objeto
+
+# as instruções abaixo são praticamente equivalentes
+p = Quitute('pão de queijo')
+p = criar(Quitute, 'pão de queijo')
+----
+
+O <> mostra uma variante de `FrozenJSON` onde refatorei a
+lógica do método `build` para o método `+__new__+`.
+
+[[ex_explore2]]
+.explore2.py: usando `+__new__+` para criar novos objetos, que podem ou não ser instâncias de `FrozenJSON`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/explore2.py[tags=EXPLORE2]
+----
+====
+
+<1> Por ser um método de classe, `+__new__+` recebe como primeiro argumento
+ é a própria classe, e os demais argumentos são os mesmos passados
+para `+__init__+`, exceto o `self`.
+
+<2> O comportamento default é delegar para o `+__new__+` de uma superclasse.
+Neste caso, estamos invocando o `+__new__+` da classe `object`, passando
+`FrozenJSON` como único argumento.
+
+<3> As linhas restantes de `+__new__+` são exatamente as do antigo método
+`build`.
+
+<4> Aqui é onde invocávamos `FrozenJSON.build`; agora invocamos apenas a
+classe, e Python internamente invoca `+FrozenJSON.__new__+`.
+
+O método `+__new__+` recebe uma classe como primeiro argumento porque,
+normalmente, o objeto criado será uma instância daquela classe. Então, em
+`+FrozenJSON.__new__+`, quando a expressão `+super().__new__(cls)+`
+invoca `+object.__new__(FrozenJSON)+`, a instância criada pela classe `object`
+será uma instância de `FrozenJSON`. O atributo `+__class__+` da nova
+instância terá uma referência para `FrozenJSON`, apesar de que a instância
+será construída por `+object.__new__+`,
+implementado em C, nas entranhas do Python.
+
+O conjunto de dados da OSCON está organizado de um jeito pouco amigável à
+exploração interativa. Por exemplo, o evento no índice `40`, intitulado `'There
+*Will* Be Bugs'` (Haverá Bugs) tem dois palestrantes, `3471` e `5199`. Encontrar
+os nomes dos palestrantes é chato, pois esses são números de série e a lista
+`Schedule.speakers` não está indexada por eles. Para obter cada palestrante,
+precisamos iterar sobre a lista até encontrar um registro com o número de série
+correspondente. Nossa próxima tarefa é reestruturar os dados para preparar a
+recuperação automática de registros relacionados.((("",
+startref="Oflex22")))((("", startref="new22")))((("",
+startref="DWflex22")))((("", startref="DAPwrangl22")))
+
+[[computed_props_sec]]
+=== Propriedades computadas
+
+Vimos o decorador `@property` pela primeira vez na
+https://fpy.li/c9[«Seção 11.7»] (vol.2).
+No Exemplo 7 daquela seção usamos duas propriedades na classe `Vector2d`
+para que os atributos `x` e `y` fossem limitados à leitura (_read-only_).
+Aqui veremos 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 da OSCON contêm números de série
+apontando para registros nas listas `'speakers'` e `'venues'`, como se fossem
+chaves estrangeiras em um banco de dados relacional. Por
+exemplo, esse é o registro de uma palestra (com a descrição parcial terminando
+em reticências):
+
+[source, json]
+----
+include::../code/22-dyn-attr-prop/oscon/osconfeed-talk.json[]
+----
+
+Vamos implementar uma classe `Event` com propriedades `venue` e `speakers`, para devolver automaticamente os dados relacionados—em outras palavras, "desreferenciar" o número de série.
+Dada uma instância de `Event`, o <> mostra o comportamento desejado.
+
+[[ex22-7-added-uuid]]
+.Ler `venue` e `speakers` devolve objetos `Record`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v4.py[tags=SCHEDULE4_DEMO]
+----
+====
+<1> Dada uma instância de `Event`...
+<2> ...acessar `event.venue` devolve um objeto `Record` em vez de um número de série.
+<3> Agora é fácil obter o nome do `venue`.
+<4> A propriedade `event.speakers` devolve uma lista de instâncias de `Record`.
+
+Como sempre, vamos criar o código passo a passo,
+começando com a classe `Record` e uma função para
+ler dados JSON e devolver um `dict` com instâncias de `Record`.
+
+==== Passo 1: criação de atributos baseados em dados
+
+O <> mostra((("computed properties",
+"data-driven attribute creation", id="CPdatadriven22")))
+o doctest para orientar este primeiro passo.
+
+[[ex_schedule_v1_demo]]
+.Testando schedule_v1.py (do <>)
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v1.py[tags=SCHEDULE1_DEMO]
+----
+====
+
+<<<
+
+<1> Constrói um `dict` com os dados JSON; a função `load` está no <>.
+<2> As chaves em `records` são strings criadas a partir do tipo de registro e do número de série.
+<3> `speaker` é uma instância da classe `Record`, definida no <>.
+<4> Campos do JSON original podem ser acessados como atributos de instância de `Record`.
+
+O código de _schedule_v1.py_ está no <>.
+
+[[ex_schedule_v1]]
+.schedule_v1.py: reorganizando os dados de agendamento da OSCON
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v1.py[tags=SCHEDULE1]
+----
+====
+
+<1> Isto é um atalho comum para construir uma instância com atributos criados a partir de argumentos nomeados (a explicação detalhada está abaixo).
+
+<2> Usa o campo `serial` para criar a representação customizada de `Record` exibida no <>.
+
+<3> `load` vai devolver um `dict` de instâncias de `Record` no final.
+
+<4> Analisa o JSON, devolvendo objetos Python nativos: listas, dicts, strings, números, etc.
+
+<5> Itera sobre as quatro listas principais, chamadas `'conferences'`, `'events'`, `'speakers'`, e `'venues'`.
+
+<6> `record_type` é o nome da lista sem o último caractere, então `speakers` se
+torna `speaker`. No Python ≥ 3.9, podemos fazer isso de forma mais explícita com
+`collection.removesuffix('s')`—veja a https://fpy.li/pep616[_PEP 616—String
+methods to remove prefixes and suffixes_] (Métodos de string para remover prefixos
+e sufixos).
+
+<7> Cria a `key` no formato `'speaker.3471'`.
+
+<8> Cria uma instância de `Record` e a armazena em `records` com a chave `key`.
+
+
+O método `+Record.__init__+` ilustra um velho truque. Lembre-se de que o
+`+__dict__+` de um objeto é onde são guardados seus atributos—a menos que
+`+__slots__+` seja declarado na classe, como vimos na https://fpy.li/28[«Seção 11.11»] (vol.2). Daí,
+atualizar o `+__dict__+` de uma instância é uma maneira fácil de criar um
+punhado de atributos naquela instância.footnote:[`Bunch` ou "punhado" é o nome
+da classe usada por Alex Martelli para compartilhar essa dica em uma receita de
+2001 intitulada https://fpy.li/22-4["The simple but handy ‘collector of a bunch
+of named stuff’ class" (_Uma classe simples mas prática 'coletora de um punhado
+de coisas nomeadas'_)].]
+
+[NOTE]
+====
+
+Dependendo da aplicação, a classe `Record` pode ter que lidar com chaves que não
+sejam nomes de atributo válidos, como vimos na <>. Tratar
+essa questão nos desviaria da ideia principal deste exemplo, e o
+problema não ocorre no conjunto de dados que estamos explorando.
+
+====
+
+A definição de `Record` no <> é tão simples que você pode estar
+se perguntando por que não a usei antes, em vez de `FrozenJSON`.
+São duas razões. Primeiro, `FrozenJSON` funciona convertendo recursivamente os
+mapeamentos aninhados e listas, mas `Record` não precisa fazer isso, pois nosso
+conjunto de dados convertido não contém mapeamentos aninhados. Os
+registros contêm apenas strings, inteiros, listas de strings e listas de
+inteiros. A segunda razão: `FrozenJSON` permite acessar aos atributos no `dict`
+embutido `+__data+`—para invocar métodos como `.keys()`.
+`Record` não oferece este acessso.
+
+[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://fpy.li/bd[`types.SimpleNamespace`],
+https://fpy.li/be[`argparse.Namespace`], e
+https://fpy.li/bf[`multiprocessing.managers.Namespace`].
+Escrevi a classe `Record`, mais simples, para destacar a ideia essencial:
+`+__init__+` preenchendo o `+__dict__+` da instância.
+
+====
+
+Após reorganizar o conjunto de dados de cronograma, podemos aprimorar a classe
+`Record` para obter automaticamente registros de `venue` e `speaker`
+referenciados em um registro `event`. Vamos utilizar propriedades para fazer
+exatamente isso nos próximos exemplos.((("", startref="CPdatadriven22")))
+
+
+[[oscon_schedule_v2_sec]]
+==== Passo 2: Propriedades para recuperar um registro relacionado
+
+O((("computed properties", "property to retrieve linked records",
+id="CPlinked22"))) objetivo da próxima versão é: dado um registro `event`, ler
+sua propriedade `venue` vai devolver um `Record`. Isso é similar ao que o ORM
+(_Object Relational Mapping_, Mapeamento Relacional de Objetos) do Django faz
+quando acessamos um campo `ForeignKey`: em vez da chave, recebemos o
+objeto relacionado.
+
+Vamos começar pela propriedade `venue`. Veja a interação parcial no <>.
+
+[[ex_schedule_v2_demo]]
+.Extratos dos doctests de schedule_v2.py
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v2.py[tags=SCHEDULE2_DEMO]
+----
+====
+<1> O método estático `Record.fetch` obtém um `Record` ou um  `Event` do conjunto de dados.
+<2> Observe que `event` é uma instância da classe `Event`.
+<3> Acessar `event.venue` devolve uma instância de `Record`.
+<4> Agora é fácil encontrar o nome de um `event.venue`.
+<5> `event` também tem um atributo `venue_serial`, vindo dos dados JSON.
+
+`Event` é uma subclasse de `Record`, acrescentando um `venue` para obter os registros relacionados, e um método `+__repr__+` especializado.
+
+O código dessa seção está no módulo https://fpy.li/22-8[_schedule_v2.py_], no
+https://fpy.li/code[repositório de código do _Python Fluente_].
+O exemplo tem aproximadamente 50 linhas, então vou apresentá-lo em partes, começando pela classe `Record` aperfeiçoada.
+
+[[ex_schedule_v2_record]]
+.schedule_v2.py: a classe `Record` com um novo método `fetch`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v2.py[tags=SCHEDULE2_RECORD]
+----
+====
+<1> `inspect` será usado em `load`, lista do no <>.
+<2> No final, o atributo de classe privado `+__index+` preservará a referência ao `dict` devolvido por `load`.
+<3> `fetch` é um `staticmethod`, para deixar explícito que seu efeito não é influenciado pela classe ou pela instância de onde ele é invocado.
+<4> Preenche o `+Record.__index+`, se necessário.
+<5> E o utiliza para obter um registro com uma dada `key`.
+
+[TIP]
+====
+Esse é um exemplo onde o uso de `staticmethod` faz sentido.
+O método `fetch` sempre age sobre o atributo de classe `Record.__index`, mesmo quando invocado desde uma subclasse, como `Event.fetch()`—que exploraremos a seguir.
+Seria equivocado programá-lo como um método de classe, pois o primeiro argumento, `cls`, nunca é usado.
+====
+
+Agora podemos usar a propriedade na classe `Event`, listada no <>.
+
+[[ex_schedule_v2_event]]
+.schedule_v2.py: a classe `Event`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v2.py[tags=SCHEDULE2_EVENT]
+----
+====
+[role="pagebreak-before less_space"]
+<1> `Event` estende `Record`.
+<2> Se a instância tem um atributo `name`, esse atributo será usado para produzir uma representação customizada.
+Se não, invoca `+__repr__+` de `Record`.
+<3> A propriedade `venue` cria uma `key` a partir do atributo `venue_serial`, e a passa para o método de classe `fetch`, herdado de `Record` (a razão para usar `+self.__class__+` logo ficará clara).
+
+A segunda linha do método `venue` no <> devolve
+`+self.__class__.fetch(key)+`.
+Por que não podemos invocar `self.fetch(key)` diretamente?
+A forma direta 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 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.
+
+
+
+[WARNING]
+====
+Ao criar nomes de atributos de instância a partir de dados, sempre existe o risco de bugs causados pelo ocultamento de atributos da classe—como métodos—ou perda de dados pela sobrescrita acidental de atributos de instância existentes. Estes problemas talvez expliquem por que os dicts de Python não são como objetos JavaScript, e isto é uma vantagem.
+====
+
+Se a classe `Record` se comportasse mais como um mapeamento, implementando um `+__getitem__+` dinâmico em vez de um `+__getattr__+` dinâmico, não haveria risco de bugs por ocultamento ou sobrescrita. Um mapeamento customizado seria provavelmente a forma pythônica de implementar `Record`. Mas se eu tivesse seguido por aquele caminho, não estaríamos estudando os truques e as armadilhas da programação dinâmica de atributos.
+
+A parte final deste exemplo é a função `load` revisada, no <>.
+
+[[ex_schedule_v2_load]]
+.schedule_v2.py: a função `load`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v2.py[tags=SCHEDULE2_LOAD]
+----
+====
+
+<1> Até aqui, nenhuma mudança em relação ao `load` em _schedule_v1.py_ (do
+<>).
+
+<2> Muda a primeira letra de `record_type` para maiúscula, para criar um
+possível nome de classe; por exemplo, `'event'` se torna `'Event'`.
+
+<3> Obtém um objeto com aquele nome do escopo global do módulo; se aquele objeto
+não existir, obtém a classe `Record`.
+
+<4> Se o objeto recém-obtido é uma classe, e é uma subclasse de `Record`...
+
+<5> ...vincula o nome `factory` a ele. Isto significa que `factory` pode ser
+qualquer subclasse de `Record`, dependendo do `record_type`.
+
+<6> Caso contrário, vincula `Record` ao nome `factory`.
+
+<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
+uma instância de `Record` ou de 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 você definir classes chamadas `Speaker` ou `Venue`, `load` usará automaticamente
+aquelas classes ao criar e armazenar registros, em vez da classe `Record`.
+
+Agora vamos aplicar a mesma ideia à nova propriedade `speakers`, na classe `Events`.((("", startref="CPlinked22")))
+
+[[property_overriding_sec]]
+==== Passo 3: Propriedade sobrescrevendo atributo existente
+
+O((("computed properties", "property overriding existing attributes"))) nome da
+propriedade `venue` no <> não corresponde a um nome de
+campo nos registros da coleção `"events"`. Seus dados vêm de um campo chamado
+`venue_serial`. Por outro lado, cada registro na coleção `events` tem um campo
+`speakers`, contendo uma lista de números de série. Queremos expor essa
+informação na forma de uma propriedade `speakers` em instâncias de `Event`, que
+devolverá uma lista de instâncias de `Record`. Esta colisão de nomes exige uma
+atenção especial, como revela o <>.
+
+<<<
+[[ex_schedule_v3_speakers]]
+.schedule_v3.py: a propriedade `speakers`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v3.py[tags=SCHEDULE3_SPEAKERS]
+----
+====
+<1> Os dados que precisamos estão em um atributo `speakers`, mas precisamos obtê-los diretamente
+do `+__dict__+` da instância, para evitar uma chamada recursiva à propriedade `speakers`.
+<2> Devolve uma lista com todos os registros com chaves correspondendo aos números em `spkr_serials`.
+
+Dentro do método `speakers`, uma tentativa de ler `self.speakers` invocará o
+mesmo método, provocando um `RecursionError`. Entretanto,
+acessando via `+self.__dict__['speakers']+`, evitamos o algoritmo de Python para
+busca de atributos, a propriedade não é acessada, 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
+existe uma propriedade com o nome `my_attr`, aquela propriedade oculta um
+atributo de instância com o mesmo nome. Isto será demonstrado com exemplos na
+<>, e o <> revelará que uma
+propriedade é implementada como um _descriptor_ (descritor de atributo), 
+uma abstração mais geral e poderosa.
+
+====
+
+Quando programei 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 nos dados da OSCON contêm poucos palestrantes,
+então programar algo mais complexo seria uma otimização prematura. Entretanto,
+criar um _cache_ de uma propriedade é uma necessidade comum—mas não trivial.
+Veremos então como fazer isso nos próximos exemplos.
+
+
+[[cached_property_sec]]
+==== Passo 4: Um _cache_ de propriedades sob medida
+
+Fazer((("computed properties", "property caching", id="CPpcach22"))) _caching_
+de propriedades é uma necessidade comum, pois há a expectativa de que uma
+expressão como `event.venue` deveria ser pouco dispendiosa.footnote:[Isso é, na
+verdade, uma desvantagem do Princípio de Acesso Uniforme de Meyer, mencionada no
+início deste capítulo. Quem tiver interesse nesta discussão pode ler o
+<> opcional.] Alguma forma de _caching_ poderia se tornar
+necessária caso o método `Record.fetch`, invocado nas propriedades de `Event`,
+precise consultar um banco de dados ou uma API Web.
+
+Na primeira edição de _Python Fluente_,
+programei a lógica customizada de _caching_ para o método `speakers`,
+como mostra o <>.
+
+[[ex_schedule_v4_hasattr]]
+.A lógica de _caching_ customizada usando `hasattr` impede a otimização de compartilhamento de chaves
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v4_hasattr.py[tags=SCHEDULE4_HASATTR_CACHE]
+----
+====
+<1> Se a instância não tem um atributo chamado `__speaker_objs`, obtém os objetos `speaker` e os armazena ali..
+<2> Devolve `self.__speaker_objs`.
+
+O _caching_ caseiro no <> é bastante direto, mas criar atributos após a inicialização da instância frustra a otimização da
+https://fpy.li/pep412[PEP 412—Key-Sharing Dictionary]
+(Dicionário com chaves compartilhadas), como explicado na https://fpy.li/82[«Seção 3.9»] (vol.1).
+Dependendo do tamanho da massa de dados, a diferença de uso de memória pode ser importante.
+
+Uma solução manual similar, compatível com a otimização de
+compartilhamento de chaves, implica em escrever um `+__init__+` para a classe
+`Event`, para criar o atributo de instância `+__speaker_objs+` inicializado para `None`, e
+então usá-lo no método `speakers`. Veja o <>.
+
+[[ex_schedule_v4]]
+.Armazenamento definido em `+__init__+` para viabilizar a otimização de compartilhamento de chaves
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v4.py[tags=SCHEDULE4_INIT]
+# 15 lines omitted...
+include::../code/22-dyn-attr-prop/oscon/schedule_v4.py[tags=SCHEDULE4_CACHE]
+----
+====
+
+O <> e o <> ilustram técnicas simples de
+_caching_ bastante comuns em bases de código Python legadas. Entretanto, em
+programas com múltiplas threads, _caches_ manuais como aqueles introduzem
+condições de corrida que podem levar à corrupção de dados.
+Se duas threads estão lendo uma propriedade que não foi armazenada no _cache_
+anteriormente, a primeira thread precisará computar os dados para o atributo de
+_cache_ `+__speaker_objs+` e a segunda thread pode ler
+um valor inconsistente do _cache_.
+
+Felizmente, Python 3.8 introduziu o decorador `@functools.cached_property`, que
+é seguro para uso com threads (_thread safe_). Infelizmente, ele vem com algumas ressalvas,
+discutidas a seguir.((("", startref="CPpcach22")))
+
+[[caching_properties_sec]]
+==== Passo 5: _Caching_ de propriedades com `functools`
+
+O((("computed properties", "caching properties with functools", id="CPfunctool22")))((("functools module", "caching properties with", id="functools22"))) módulo `functools` oferece três decoradores para _caching_.
+Vimos `@cache` e `@lru_cache` na 
+https://fpy/li/ca[«Seção 9.9.1»] (vol.2).
+Python 3.8 incorporou `@cached_property`.
+
+O decorador `functools.cached_property` faz _cache_ do resultado de um método em uma variável de instância com o mesmo nome.
+
+Por exemplo, no <>, o valor computado pelo método `venue` é armazenado em um atributo `venue`, em `self`.
+Após isso, quando código cliente tenta ler `venue`, o recém-criado atributo de instância `venue` é usado, em vez do método.
+
+[[ex_schedule_v5_cached_property]]
+.Uso simples de uma `@cached_property`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v5.py[tags=SCHEDULE5_CACHED_PROPERTY]
+----
+====
+
+Na <>, vimos que uma propriedade oculta um atributo de instância de mesmo nome.
+Se isso é verdade, como `@cached_property` pode funcionar?
+Se a propriedade sobrescreve o atributo de instância, o atributo `venue` será ignorado e o método `venue` será sempre invocado,
+computando a `key` e rodando `fetch` todas as vezes!
+
+A((("attribute descriptors", "overriding versus nonoverriding")))((("nonoverriding descriptors")))((("overriding descriptors"))) resposta é um pouco triste: `cached_property` é um nome enganador.
+O decorador `@cached_property` não cria uma propriedade completa, ele cria um _descritor não dominante_. Um descritor é um objeto que gerencia o acesso a um atributo em outra classe.
+Vamos mergulhar nos descritores no <>.
+O decorador `property` é uma API de alto nível para criar um _descritor dominante_.
+O <> inclui uma explicação completa sobre descritores _dominantes_ e _não dominantes_.
+
+Por hora, vamos deixar de lado a implementação 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://fpy.li/bg[«Documentação do
+Python»]:
+
+[quote]
+____
+
+A mecânica de `cached_property()` é um tanto diferente da de `property()`. Uma
+propriedade normal bloqueia a escrita em atributos, a menos que um _setter_ seja
+definido. Uma `cached_property`, por outro lado, permite a escrita.
+
+O decorador `cached_property` só funciona para consultas e apenas quando um
+atributo de mesmo nome não existe. Quando acionada, `cached_property` escreve no
+atributo de mesmo nome. Leituras e escritas subsequentes daquele atributo têm
+precedência sobre o método decorado com `cached_property` e ele funciona como um
+atributo normal.
+
+O valor "cacheado" (_cached_) pode ser excluído apagando-se o atributo.
+Isto permite que o método `cached_property` rode novamente.footnote:[Fonte:
+documentação de https://fpy.li/bg[@functools.cached_property]. Sei que o autor
+dessa explicação é Raymond Hettinger porque ele a escreveu em resposta a um
+problema que eu 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). Hettinger é um grande colaborador da
+documentação oficial de Python e de pacotes importantes da biblioteca padrão,
+como o sensacional `itertools`.]
+
+____
+
+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.
+
+[WARNING]
+====
+`@cached_property` tem algumas importantes limitações:
+
+* Ele não pode ser usado como um substituto direto de `@property`
+se o método decorado acessa um atributo de instância de mesmo nome.
+* Ele não pode ser usado em uma classe que defina `+__slots__+`.
+* Ele impede a otimização de chaves compartilhadas do `+__dict__+` da instância, pois cria um atributo de instância após o `+__init__+`.
+====
+
+Apesar destas 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).footnote:[Veja
+https://fpy.li/bq[«Mutex recursivo»] na Wikipédia.]
+
+
+A
+https://fpy.li/bh[documentação] de `@cached_property`
+recomenda uma solução alternativa que podemos usar com `speakers`:
+empilhar decoradores.
+
+[[ex_schedule_v5_property_over_cache]]
+.Empilhando `@property` sobre `@cache`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/oscon/schedule_v5.py[tags=SCHEDULE5_PROPERTY_OVER_CACHE]
+----
+====
+<1> A ordem é importante: `@property` vai acima...
+<2> ...de `@cache`.
+
+Lembre-se do significado desta sintaxe com decoradores empilhados,
+que vimos no Exemplo 18 do https://fpy.li/9[«Capítulo 9»] (vol.2).
+As três primeiras linhas do <> fazem isso:
+
+[source, python]
+----
+speakers = property(cache(speakers))
+----
+
+O `@cache` é aplicado a `speakers`, devolvendo uma nova função.
+Esta função é então decorada por `@property`,
+que a substitui por uma propriedade criada na hora.
+
+Isto encerra nossa discussão de propriedades somente para leitura e decoradores de _caching_, explorando o conjunto de dados da OSCON.
+
+Na próxima seção iniciamos uma nova série de exemplos, criando propriedades de leitura e escrita.((("", startref="DAPcomputp22")))((("", startref="CPfunctool22")))((("", startref="functools22")))
+
+[[prop_validation_sec]]
+=== Propriedades para validação de atributos
+
+Além((("attributes", "using properties for attribute validation", id="Aval22")))((("dynamic attributes and properties", "using properties for attribute validation", id="DAPval22"))) de computar valores de atributos, as propriedades também são usadas para impor regras de negócio, transformando um atributo público em um atributo protegido por um _getter_ e um _setter_, sem afetar o código cliente. Vamos explorar um exemplo mais elaborado.
+
+
+==== LineItem Versão #1: Classe para um item em um pedido
+
+Imagine uma aplicação para uma loja que vende alimentos orgânicos a granel, onde os
+fregueses podem encomendar nozes, frutas secas e cereais por peso. Neste
+sistema, cada pedido contém uma sequência de produtos, e cada produto é
+representado por uma instância de uma classe, como no <>.
+
+
+[[lineitem_class_v1]]
+.bulkfood_v1.py: a classe `LineItem` mais simples
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/bulkfood/bulkfood_v1.py[tags=LINEITEM_V1]
+----
+====
+
+Este código é simples e agradável. Talvez simples demais. <> mostra um problema.
+
+[[lineitem_problem_v1]]
+.Peso negativo resulta em subtotal negativo
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/bulkfood/bulkfood_v1.py[tags=LINEITEM_PROBLEM_V1]
+----
+====
+
+Apesar de ser um exemplo inventado, não é tão fantasioso quanto se poderia imaginar. Aqui está uma história do início da Amazon.com:
+
+[quote, Jeff Bezos, fundador da Amazon.com]
+____
+Descobrimos que os clientes podiam encomendar uma quantidade negativa de livros! E nós creditaríamos seus cartões de crédito com o preço e, suponho, esperaríamos que eles nos enviassem os livros.footnote:[Citação direta de Jeff Bezos no artigo do _Wall Street Journal_ https://fpy.li/22-16[_Birth of a Salesman_] (O Nascimento de um Vendedor) publicado em 15 de outubro de 2011. Pelo menos em 2023, é necessário ser assinante para ler o artigo.]
+____
+
+Como evitar isso?
+Poderíamos mudar a interface de `LineItem` para usar um _getter_ e um _setter_ para o atributo `weight` (peso).
+Este seria o estilo Java, e não está errado, mas podemos fazer melhor.
+É natural definir o `weight` de um item apenas atribuindo um valor a este atributo;
+e talvez o sistema esteja em produção, com outras partes já acessando `item.weight` diretamente.
+Neste caso, o estilo Python seria substituir o atributo de dados por uma propriedade.
+
+
+==== LineItem versão #2: Uma propriedade de validação
+
+Implementar uma propriedade nos permitirá usar um _getter_ e um _setter_,
+sem mudar a interface pública de `LineItem`:
+para definir o `weight` de um `LineItem` ainda poderemos escrever
+`raisins.weight = 12`.
+
+<<<
+O <> lista o código de uma propriedade de leitura e escrita para `weight`.
+
+[[lineitem_class_v2]]
+.bulkfood_v2.py: um `LineItem` com uma propriedade `weight`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/bulkfood/bulkfood_v2.py[tags=LINEITEM_V2]
+----
+====
+<1> Aqui o _setter_ da propriedade já está em uso, assegurando que nenhuma instância com peso negativo possa ser criada.
+<2> `@property` decora o método _getter_.
+<3> Todos os métodos que implementam a propriedade compartilham o mesmo nome do atributo público: `weight`.
+<4> O valor é armazenado em um atributo privado `+__weight+`.
+<5> O _getter_ decorado ganha um atributo `.setter`, que também é um decorador; isto conecta o _getter_ e o _setter_.
+<6> Se o valor for maior que zero, atualizamos o `+__weight+` privado.
+<7> Caso contrário, um `ValueError` é levantado.
+
+
+<<<
+Agora não é possível criar uma `LineItem` com peso inválido:
+
+[source, python]
+----
+>>> walnuts = LineItem('walnuts', 0, 10.00)
+Traceback (most recent call last):
+    ...
+ValueError: value must be > 0
+----
+
+Assim protegemos `weight` impedindo que usuários forneçam valores negativos.
+Fregueses normalmente não podem definir o preço de um produto, mas um
+erro administrativo ou um bug poderiam criar um `LineItem` com um `price`
+negativo. Para evitar isso, poderíamos também transformar `price` em uma
+propriedade, mas isso levaria a alguma repetição no nosso código.
+
+Lembre-se da citação de Paul Graham no <>: "Quando vejo padrões
+em meus programas, considero isso um mau sinal." A cura para a repetição é a
+abstração. Há duas maneiras de abstrair definições de propriedades: usar uma
+fábrica de propriedades ou uma classe descritora. A abordagem via classe
+descritora é mais flexível, e dedicaremos o <> a uma discussão
+completa deste mecanismo. Na verdade, propriedades são, elas mesmas, implementadas
+como classes descritoras. Mas aqui vamos seguir com nossa exploração das
+propriedades, implementando uma fábrica de propriedades em forma de função.
+
+Mas antes de podermos implementar uma fábrica de propriedades, precisamos
+entender melhor as propriedades em si.((("", startref="Aval22")))((("",
+startref="DAPval22")))
+
+
+=== Propriedades em profundidade
+
+Apesar((("dynamic attributes and properties", "property class",
+id="DAPpclass22")))((("property class", id="proclass22"))) de ser frequentemente
+usada como um decorador, `property` é na verdade uma classe embutida. No Python,
+funções e classes são muitas vezes intercambiáveis, pois ambas são invocáveis e
+não há um operador `new` para instanciação de objeto, então invocar um
+construtor não é diferente de invocar uma função fábrica. E ambas podem ser
+usadas como decoradores, desde que elas devolvam um novo invocável, que seja um
+substituto adequado do invocável decorado.
+
+Esta é a assinatura completa do construtor de `property`:
+
+[source, python]
+----
+property(fget=None, fset=None, fdel=None, doc=None)
+----
+
+Todos os argumentos são opcionais, e se uma função não é fornecida para algum deles,
+a operação correspondente não será permitida pela propriedade.
+
+O tipo `property` foi introduzido no Python 2.2, mas a sintaxe `@` do decorador
+só surgiu no Python 2.4. Então, por alguns anos, propriedades eram definidas
+passando as funções de acesso nos dois primeiros argumentos.
+
+A sintaxe "clássica" para definir propriedades sem decoradores é ilustrada pelo <>.
+
+[[lineitem_class_v2b]]
+.bulkfood_v2b.py: igual ao <>, sem usar decoradores
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/bulkfood/bulkfood_v2b.py[tags=LINEITEM_V2B]
+----
+====
+<1> Um _getter_ simples.
+<2> Um _setter_ simples.
+<3> Cria a `property` e a vincula a um atributo da classe.
+
+Em algumas situações, a forma clássica é melhor que a sintaxe do decorador; o código da fábrica de propriedade, que discutiremos em breve, é um exemplo. Por outro lado, no corpo de uma classe com muitos métodos, os decoradores tornam explícito quais são os _getters_ e os _setters_, sem depender da convenção do uso dos prefixos `get` e `set` em seus nomes.
+
+A presença de uma propriedade em uma classe afeta como os atributos nas instâncias daquela classe podem ser encontrados, de uma forma que à primeira vista pode ser surpreendente. A próxima seção explica isso.
+
+
+[[prop_override_instance]]
+==== Propriedades sobrescrevem atributos de instância
+
+Propriedades são sempre atributos de uma classe,
+mas elas controlam o acesso a atributos nas instâncias da classe.
+
+Na https://fpy.li/cs[«Seção 11.12»] (vol.2),
+vimos que quando uma instância e sua classe têm um atributo de dados com o mesmo nome,
+o atributo de instância sobrescreve, ou oculta,
+o atributo da classe—ao menos quando lido através daquela instância.
+O <> ilustra esse ponto.
+
+[[attr_override_demo1]]
+.Atributo de instância oculta o atributo de classe `data`
+====
+[source, python]
+----
+>>> class Class:  # <1>
+...     data = 'the class data attr'
+...     @property
+...     def prop(self):
+...         return 'the prop value'
+...
+>>> obj = Class()
+>>> vars(obj)  # <2>
+{}
+>>> obj.data  # <3>
+'the class data attr'
+>>> obj.data = 'bar' # <4>
+>>> vars(obj)  # <5>
+{'data': 'bar'}
+>>> obj.data  # <6>
+'bar'
+>>> Class.data  # <7>
+'the class data attr'
+----
+====
+<1> Define `Class` com dois atributos de classe: o atributo `data` e a propriedade `prop`.
+<2> `vars` devolve o `+__dict__+` de `obj`, mostrando que ele não tem atributos de instância.
+<3> Ler de `obj.data` obtém o valor de `Class.data`.
+<4> Escrever em `obj.data` cria um atributo de instância.
+<5> Inspeciona a instância, para ver o atributo de instância.
+<6> Ler agora de `obj.data` obtém o valor do atributo da instância.
+Quanto lido a partir da instância `obj`, o `data` da instância oculta o `data` da classe.
+<7> O atributo `Class.data` está intacto.
+
+
+Agora vamos tentar  sobrescrever o atributo `prop` na instância `obj`. Continuando a sessão de console anterior, temos o  <>.
+
+[[attr_override_demo2]]
+.Um atributo de instância não oculta uma propriedade da classe (continuando do <>)
+====
+[source, python]
+----
+>>> Class.prop  # <1>
+
+>>> obj.prop  # <2>
+'the prop value'
+>>> obj.prop = 'foo'  # <3>
+Traceback (most recent call last):
+  ...
+AttributeError: can't set attribute
+>>> obj.__dict__['prop'] = 'foo'  # <4>
+>>> vars(obj)  # <5>
+{'data': 'bar', 'prop': 'foo'}
+>>> obj.prop  # <6>
+'the prop value'
+>>> Class.prop = 'baz'  # <7>
+>>> obj.prop  # <8>
+'foo'
+----
+====
+<1> Ler `prop` diretamente de `Class` obtém o próprio objeto propriedade, sem executar seu método _getter_.
+<2> Ler `obj.prop` executa o _getter_ da propriedade.
+<3> Tentar definir um atributo `prop` na instância falha.
+<4> Inserir `'prop'` diretamente em `+obj.__dict__+` funciona.
+<5> Podemos ver que agora `obj` tem dois atributos de instância: `data` e `prop`.
+<6> Entretanto, ler `obj.prop` ainda executa o _getter_ da propriedade. A propriedade não é ocultada pelo atributo de instância.
+<7> Sobrescrever `Class.prop` destrói o objeto propriedade.
+<8> Agora `obj.prop` obtém o atributo de instância. `Class.prop` não é mais uma propriedade, então  ela não mais sobrescreve `obj.prop`.
+
+Como uma demonstração final, vamos adicionar uma propriedade a `Class`, e vê-la  sobrescrever um atributo de instância. O <> retoma a sessão onde o <> parou.
+
+[[attr_override_demo3]]
+.Uma nova propriedade de classe oculta o atributo de instância existente (continuando do <>)
+====
+[source, python]
+----
+>>> obj.data  # <1>
+'bar'
+>>> Class.data  # <2>
+'the class data attr'
+>>> Class.data = property(lambda self: 'the "data" prop value')  # <3>
+>>> obj.data  # <4>
+'the "data" prop value'
+>>> del Class.data  # <5>
+>>> obj.data  # <6>
+'bar'
+----
+====
+<1> `obj.data` obtém o atributo de instância `data`.
+<2> `Class.data` obtém o atributo de classe `data`.
+<3> Sobrescreve `Class.data` com uma nova propriedade.
+<4> `obj.data` está agora ocultado pela propriedade `Class.data`.
+<5> Apaga a propriedade .
+<6> `obj.data` agora lê novamente o atributo de instância `data`.
+
+<<<
+O ponto principal desta seção é que uma expressão como `obj.data` não começa a
+busca por `data` em `obj`. A busca na verdade começa em `+obj.__class__+`, e
+Python só olha para a instância `obj` se não houver uma propriedade
+chamada `data` na classe. Isto se aplica a((("overriding descriptors"))) _descritores
+dominantes_ em geral, dos quais as propriedades são apenas um exemplo. 
+Para mais detalhes, aguarde o <>.
+
+Voltemos às propriedades. Toda unidade de código de Python—módulos, funções,
+classes, métodos—pode conter uma docstring. O próximo tópico mostra como anexar
+documentação às propriedades.
+
+
+==== Documentação de propriedades
+
+Quando ferramentas como uma IDE ou a função `help()` do console precisam mostrar a
+documentação de uma propriedade, elas extraem a informação do atributo
+`+__doc__+` da propriedade.
+
+Se usada com a sintaxe clássica de invocação, `property` pode receber a string de documentação no argumento `doc`:
+
+[source, python]
+----
+    weight = property(get_weight, set_weight, doc='weight in kilograms')
+----
+
+Alternativamente, a docstring do método _getter_—aquele que recebe o decorador `@property`—é usada
+como documentação da propriedade toda. A <> mostra telas de
+ajuda geradas a partir do código no <>.
+
+
+[[help_foo_screens]]
+.Capturas de tela do console de Python para os comandos `help(Foo.bar)` e `help(Foo)`. O código-fonte está no <>.
+image::../images/flpy_2201.png[Screenshots of the Python console]
+
+[[ex_foo_property_doc]]
+.Documentação para uma propriedade
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/doc_property.py[tags=DOC_PROPERTY]
+----
+====
+
+Agora que cobrimos o essencial sobre as propriedades, vamos voltar para a
+questão de proteger os atributos `weight` e `price` de `LineItem`, para que eles
+só aceitem valores maiores que zero—mas sem codar na unha dois pares
+de _getters/setters_ praticamente idênticos.((("",
+startref="DAPpclass22")))((("", startref="proclass22")))
+
+[[coding_prop_factory_sec]]
+=== Uma fábrica de propriedades
+
+Vamos((("dynamic attributes and properties", "coding property factories",
+id="DAPfactory22")))((("quantity properties", id="quantprop22"))) programar uma
+fábrica para criar propriedades `quantity` (quantidade). Os
+os atributos gerenciados representam quantidades que não podem ser negativas ou
+zero na aplicação. O <> mostra a aparência
+cristalina da classe `LineItem` usando duas instâncias de propriedades
+`quantity`: uma para gerenciar o atributo `weight`, a outra para o `price`.
+
+[[lineitem_class_v2prop_class]]
+.bulkfood_v2prop.py: a fábrica de propriedades `quantity` em ação
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py[tags=LINEITEM_V2_PROP_CLASS]
+----
+====
+
+<1> A fábrica cria a propriedade customizada, `weight`...
+
+<2> e a propriedade, `price`, ambas como atributos da classse.
+
+<3> A propriedade já está ativa, rejeitando peso negativo ou `0`.
+
+<4> As propriedades também são usadas aqui, para recuperar os valores armazenados na instância.
+
+Recorde que propriedades são atributos de classe. Ao criar cada propriedade
+`quantity`, precisamos passar o nome do atributo de `LineItem` que será
+gerenciado por aquela propriedade específica.
+É chato escrever `weight` duas vezes assim:
+
+[source, python]
+----
+    weight = quantity('weight')
+----
+
+Mas evitar tal repetição é complicado, pois a propriedade não tem como saber
+qual nome de atributo será vinculado a ela. Lembre-se: o lado direito de uma
+atribuição é avaliado primeiro, então quando `quantity()` é invocada, o atributo
+`weight` ainda não existe na classe.
+
+[NOTE]
+====
+Aperfeiçoar a propriedade `quantity` para que o usuário
+não precise redigitar o nome do atributo é um problema
+não trivial de metaprogramação, que resolveremos no <>.
+====
+
+O <> apresenta a fábrica de propriedades
+`quantity`.
+
+[[lineitem_class_v2prop]]
+.bulkfood_v2prop.py: a fábrica de propriedades `quantity`
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py[tags=LINEITEM_V2_PROP_FACTORY_FUNCTION]
+----
+====
+<1> O argumento `storage_name`, onde os dados de cada propriedade são armazenados; para `weight`, o nome do armazenamento será `'weight'`.
+<2> O primeiro argumento do `qty_getter` poderia se chamar `self`, mas soaria estranho, pois isso não é o corpo de uma classe; `instance` se refere à instância de `LineItem` onde o atributo será armazenado.
+<3> `qty_getter` se refere a `storage_name`, então ele será preservado na clausura desta função; o valor é obtido diretamente de `+instance.__dict__+`, para contornar a propriedade e evitar uma recursão infinita.
+<4> `qty_setter` é definido, e também recebe `instance` como primeiro argumento.
+<5> O `value` é armazenado diretamente no `+instance.__dict__+`, novamente contornando a propriedade.
+<6> Cria e devolve um objeto propriedade customizado.
+
+As partes do <> que merecem um estudo mais cuidadoso giram em torno da variável `storage_name`.
+
+Quando programamos uma propriedade da maneira tradicional, o nome do atributo onde um valor será armazenado está definido explicitamente nos métodos _getter_ e _setter_.
+Mas aqui as funções `qty_getter` e `qty_setter` são genéricas, e dependem da variável `storage_name` para saber onde ler/escrever o atributo gerenciado no `+__dict__+` da instância.
+Cada vez que a fábrica `quantity` é invocada para criar uma propriedade, `storage_name` precisa ser definida com um valor único.
+
+As funções `qty_getter` e `qty_setter` serão encapsuladas pelo objeto `property`, criado na última linha da função fábrica. Mais tarde, quando forem chamadas para cumprir seus papéis, estas funções lerão a `storage_name` de suas clausuras para saber onde os valores dos atributos gerenciados estão armazenados.
+
+No <>, criei e inspecionei uma instância de `LineItem`, expondo os atributos armazenados.
+
+<<<
+[[lineitem_class_v2prop_demo]]
+.bulkfood_v2prop.py: explorando propriedades e atributos de armazenamento
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py[tags=LINEITEM_V2_PROP_DEMO]
+----
+====
+<1> Lendo o `weight` e o `price` através das propriedades que ocultam os atributos de instância de mesmo nome.
+<2> Usando `vars` para inspecionar a instância `nutmeg`: aqui vemos os reais atributos de instância  usados para armazenar os valores.
+
+Observe como as propriedades criadas por nossa fábrica se valem do comportamento descrito na <>:
+a propriedade `weight` sobrescreve o atributo de instância `weight`, de forma que qualquer referência a `self.weight` ou `nutmeg.weight` é tratada pelas funções da propriedade, e a única maneira de contornar a lógica da propriedade é acessando diretamente o `+__dict__+` da instância.
+
+O código do <> pode ser um pouco complicado, mas é conciso: seu tamanho é idêntico ao do par _getter/setter_ decorado que define apenas a propriedade `weight` no <>. A definição de  `LineItem` no <> é mais legível sem o ruído de _getters_ e _setters_.
+
+Em um sistema real, o mesmo tipo de validação pode aparecer em muitos campos
+espalhados por várias classes, e a fábrica `quantity` estaria em um módulo
+utilitário, pronta para uso em todas as partes do sistema que precisem dela.
+Depois, aquela fábrica simples poderia ser refatorada em uma classe descritora
+mais extensível, com subclasses especializadas realizando diferentes validações.
+Faremos isso no <>.
+
+Vamos agora encerrar a discussão das propriedades com a questão da exclusão de atributos.((("", startref="DAPfactory22")))((("", startref="quantprop22")))
+
+<<<
+[[attribute_deletion_sec]]
+=== Tratando a exclusão de atributos
+
+Podemos((("dynamic attributes and properties", "handling attribute deletion")))((("attributes", "handling attribute deletion")))((("del statement"))) usar a instrução `del` para excluir não apenas variáveis, mas também atributos:
+
+[source, python]
+----
+>>> class Demo:
+...    pass
+...
+>>> d = Demo()
+>>> d.color = 'green'
+>>> d.color
+'green'
+>>> del d.color
+>>> d.color
+Traceback (most recent call last):
+  File "", line 1, in 
+AttributeError: 'Demo' object has no attribute 'color'
+----
+
+Na prática, a exclusão de atributos não é algo que se faça todo dia no Python, e a necessidade de lidar com isso no caso de uma propriedade é ainda mais rara. Mas tal operação é suportada, e consigo pensar em um exemplo bobo para demonstrá-la.
+
+Em uma definição de propriedade, o decorador `@my_property.deleter` encapsula o método responsável por excluir o atributo gerenciado pela propriedade.
+Como prometido, o tolo <> foi inspirado pela cena com o Cavaleiro Negro, do filme _Monty Python e o Cálice Sagrado_.footnote:[Aquela cena sangrenta está https://fpy.li/22-17[disponível no Youtube] quando reviso essa seção, em outubro de 2021.]
+
+[[ex_black_knight]]
+.blackknight.py
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/blackknight.py[tags=BLACK_KNIGHT]
+----
+====
+
+Os doctests em _blackknight.py_ estão no <>.
+
+[[demo_black_knight]]
+.blackknight.py: doctests para <> (o Cavaleiro Negro nunca reconhece a derrota)
+====
+[source, python]
+----
+include::../code/22-dyn-attr-prop/blackknight.py[tags=BLACK_KNIGHT_DEMO]
+----
+====
+
+Usando a sintaxe clássica de invocação em vez de decoradores, o argumento `fdel` configura a função de exclusão.
+Por exemplo, a propriedade `member` seria escrita assim no corpo da classe `BlackKnight`:
+
+[source, python]
+----
+    member = property(member_getter, fdel=member_deleter)
+----
+
+Se você não estiver usando uma propriedade, a exclusão de atributos pode ser tratada implementando o método especial de nível mais baixo `+__delattr__+`, apresentado na <>. Programar uma classe tola com `+__delattr__+` fica como exercício para a leitora que queira procrastinar.
+
+Propriedades são recursos poderosos, mas algumas vezes alternativas mais simples ou de nível mais baixo são preferíveis.
+Na seção final deste capítulo, vamos revisar algumas das APIs essenciais oferecidas pelo Python para programação de atributos dinâmicos.
+
+
+=== Atributos e funções essenciais para tratamento de atributos
+
+Ao longo((("dynamic attributes and properties", "essential attributes and functions for attribute handling", id="DAPessential22"))) deste capítulo, e mesmo antes no livro, usamos algumas funções embutidas e métodos especiais oferecidos pelo Python para lidar com atributos dinâmicos. Esta seção os reúne em um único lugar para uma visão geral, pois sua documentação está espalhada na documentação oficial.
+
+
+==== Atributos especiais que afetam o tratamento de atributos
+
+O comportamento de muitas das funções e dos métodos especiais elencados nas próximas seções dependem de três atributos especiais:
+
+`+__class__+`:: Uma((("__class__"))) referência à classe do objeto (isto
+é, `+obj.__class__+` é o mesmo que `type(obj)`). Python procura por métodos especiais tal como
+`+__getattr__+` apenas na classe do objeto, e não nas instâncias em si.
+
+`+__dict__+`:: Um((("__dict__")))((("__slots__"))) mapeamento que armazena os atributos passíveis de escrita de um objeto ou de uma classe. Um objeto que tenha um `+__dict__+` pode ter novos atributos arbitrários definidos a qualquer tempo. Se uma classe tem um atributo `+__slots__+`, então suas instâncias não podem ter um `+__dict__+`. Veja `+__slots__+` (abaixo).
+
+`+__slots__+`:: Um atributo que pode ser definido em uma classe para economizar memória.
+`+__slots__+` é uma `tuple` de strings, nomeando os atributos permitidosfootnote:[Alex Martelli assinala que, apesar de `+__slots__+` poder ser definido como uma `list`, é melhor ser explícito e sempre usar uma `tuple`, pois modificar a lista em `+__slots__+` após o processamento do corpo da classe não tem qualquer efeito. Assim, seria equivocado usar uma sequência mutável ali.]. Se o
+nome `+'__dict__'+` não estiver em `+__slots__+`, as instâncias daquela classe então não terão um `+__dict__+` próprio, e apenas os atributos listados em `+__slots__+` serão permitidos naquelas instâncias.
+Revise a https://fpy.li/28[«Seção 11.11»] (vol.2) para recordar esse tópico.
+
+[[bif_attribute_handling]]
+==== Funções embutidas para tratamento de atributos
+
+Essas cinco funções embutidas executam leitura, escrita e introspecção de atributos de objetos:
+
+`dir([object])`:: Lista((("dir([object]) function")))((("functions", "dir([object]) function")))
+a maioria dos atributos de um objeto. A
+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 é listado por `dir`, mas as chaves de `+__dict__+` são listadas.
+Vários atributos especiais de classes, como 
+`+__mro__+`, `+__bases__+` e `+__name__+`, também não são listados por `dir`.
+Você pode customizar o comportamento de `dir` implementando o método especial `+__dir__+`,
+como vimos no <>.
+Se o argumento opcional `object` não for passado, `dir` lista os nomes definidos no escopo atual.
+
+`getattr(object, name[, default])`:: Devolve((("functions", "getattr(object, name[, default]) function")))((("getattr(object, name[, default]) function"))) o atributo identificado pela string `name` no `object`. O principal caso de uso é obter atributos (ou métodos) cujos nomes são conhecidos só durante a execução do programa. Esta função pode recuperar um atributo da classe do objeto ou de uma superclasse. Se tal atributo não existir, `getattr` gera uma `AttributeError` ou devolve o valor `default`, se este argumento for passado.
+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://fpy.li/bk[«documentação»] explica: "Isto é implementado chamando `getattr(object, name)` e vendo se levanta um `AttributeError` ou não."
+
+`setattr(object, name, value)`:: Atribui((("functions", "setattr(object, name, value) function"))) o `value` ao atributo denominado `name` do `object`, se o `object` permitir essa operação. Isto pode criar um novo atributo ou sobrescrever um atributo existente.
+
+`vars([object])`:: Devolve((("vars([object]) function")))((("functions", "setattr(object, name, value) function"))) o `+__dict__+` de `object`; `vars` não funciona com instâncias de classes que definem `+__slots__+` e não têm um `+__dict__+` (compare com `dir`, que aceita essas instâncias). Sem argumentos, `vars()` faz o mesmo que `locals()`: devolve um `dict` representando o escopo local.
+
+[[special_methods_for_attr_sec]]
+==== Métodos especiais para tratamento de atributos
+
+Quando implementados em uma classe definida pelo usuário, os métodos especiais listados abaixo controlam a recuperação, a atualização, a exclusão e a listagem de atributos.
+
+Acessos((("getattr function")))((("setattr function")))((("functions", "hasattr function")))((("hasattr function")))((("functions", "getattr function")))((("functions", "setattr funcion"))) a atributos, usando a notação de ponto ou as funções embutidas `getattr`, `hasattr` e `setattr` disparam os métodos especiais correspondentes, listados aqui. A leitura e escrita de atributos como chaves no
+`+__dict__+` da instância não dispara esses métodos especiais—e esta é a forma habitual de evitá-los quando necessário.
+
+A seção https://fpy.li/bm["3.3.11. Pesquisa de método especial"] do capítulo "Modelo de dados" adverte:
+
+[quote]
+____
+Para classes customizadas, as invocações implícitas de métodos especiais só têm garantia de funcionar corretamente se definidas em um tipo de objeto [a classe], não no dicionário de instância do objeto.
+____
+
+Em outras palavras, assuma que os métodos especiais serão acessados na própria
+classe, mesmo quando o alvo da invocação é uma instância. Por esta razão, métodos
+especiais não são ocultados por atributos de instância de mesmo nome.
+
+Nos exemplos a seguir, assuma que há uma classe chamada `Class`, que `obj` é uma instância de `Class`, e que `atrib` é um atributo de `obj`.
+
+Para cada um destes métodos especiais, não importa se o acesso ao atributo é
+feito usando a notação de ponto ou uma das funções embutidas listadas na
+<>. Por exemplo, tanto `obj.atrib` quanto `getattr(obj,
+'atrib')` disparam `+Class.__getattribute__(obj, 'atrib')+`.
+
+<<<
+
+`+__delattr__(self, name)+`:: Sempre((("__delattr__"))) invocado quando ocorre uma tentativa de excluir um atributo usando a instrução `del`; por exemplo, `del obj.atrib` dispara `+Class.__delattr__(obj, 'atrib')+`.
+Se `atrib` for uma propriedade, seu método de exclusão nunca será invocado se a classe implementar
+`+__delattr__+`.
+
+`+__dir__(self)+`:: Invocado((("__dir__"))) quando `dir` é invocado sobre um objeto, para fornecer uma lista de atributos; por exemplo, `dir(obj)` dispara
+`+Class.__dir__(obj)+`. Também é usado pelo recurso de auto-completar em todos os consoles modernos de Python.
+
+`+__getattr__(self, name)+`::
+Invocado((("__getattr__"))) apenas quando uma
+tentativa de obter o atributo nomeado falha, após `obj`, `Class` e suas
+superclasses serem pesquisadas. As expressões `obj.ausente`, `getattr(obj,
+'ausente')` e `hasattr(obj, 'ausente')` podem disparar
+`+Class.__getattr__(obj, 'ausente')+`, mas apenas se um atributo com o
+nome `'ausente'` não for encontrado em `obj` ou em `Class` e suas superclasses.
+
+`+__getattribute__(self, name)+`:: Sempre((("__getattribute__"))) invocado quando há uma tentativa de obter o atributo nomeado diretamente a partir de código Python (o interpretador pode ignorar isso em alguns casos, por exemplo para obter o método `+__repr__+`). A notação de ponto e as funções embutidas `getattr` e `hasattr` disparam este método. O método `+__getattr__+` só é invocado após `+__getattribute__+`, e apenas quando este gera um `AttributeError`. Para acessar atributos da instância `obj` sem entrar em uma recursão infinita, implementações de `+__getattribute__+` devem usar `+super().__getattribute__(obj, name)+`.
+
+`+__setattr__(self, name, value)+`::
+Sempre((("__setattr__"))) invocado quando há uma
+tentativa de atribuir um valor ao atributo nomeado. A notação de ponto e a
+função embutida `setattr` disparam esse método; por exemplo, tanto `obj.atrib =
+42` quanto `setattr(obj, 'atrib', 42)` disparam `+Class.__setattr__(obj, 'atrib', 42)+`.
+
+<<<
+
+[WARNING]
+====
+
+Na prática, por serem invocados incondicionalmente, afetando praticamente todos
+os acessos a atributos, os métodos especiais `+__getattribute__+` e
+`+__setattr__+` são mais difíceis de usar corretamente que `+__getattr__+`, que
+só trata acessos a atributos que não existem. Usar propriedades ou descritores
+costuma ser mais fácil que definir `+__getattribute__+` ou
+`+__setattr__+`.
+
+====
+
+Isso conclui nosso mergulho nas propriedades, nos métodos especiais e nas outras técnicas de programação de atributos dinâmicos.((("", startref="DAPessential22")))
+
+
+=== Resumo do capítulo
+
+Começamos((("dynamic attributes and properties", "overview of"))) nossa
+discussão dos atributos dinâmicos mostrando exemplos práticos de classes
+simples para facilitar a exploração de um conjunto de dados em JSON. O primeiro
+exemplo foi a classe `FrozenJSON`, que converte listas e dicts aninhados em
+instâncias aninhadas de `FrozenJSON`, e em listas de instâncias da mesma classe.
+O código de `FrozenJSON` demonstrou o uso do método especial `+__getattr__+`
+para converter estruturas de dados sob demanda, sempre que seus atributos eram
+lidos. A última versão de `FrozenJSON` mostrou o uso do método construtor
+`+__new__+` para transformar uma classe em uma fábrica flexível de objetos, não
+restrita a instâncias de si mesma.
+
+Convertemos então o conjunto de dados JSON em um `dict` que armazena instâncias da classe `Record`.
+A primeira versão de `Record` tinha apenas algumas linhas e apresentou o truque de usar `+self.__dict__.update(**kwargs)+` para criar atributos arbitrários a partir de argumentos nomeados passados para `+__init__+`.
+A segunda passagem acrescentou a classe `Event`, implementando a recuperação automática de registros relacionados através de propriedades.
+
+Propriedades que devolvem valores calculados algumas vezes necessitam de _caching_, e falamos de algumas formas de fazer isso.
+Após descobrir que `@functools.cached_property` não é sempre aplicável, aprendemos sobre uma alternativa: a combinação de `@property` acima de `@functools.cache`, nesta ordem.
+
+A discussão sobre propriedades continuou com a classe `LineItem`, onde criamos
+uma propriedade para evitar que um atributo `weight` receba valores negativos ou
+zero, que não fazem sentido na lógica do negócio. Após um aprofundamento da
+sintaxe e da semântica das propriedades, criamos uma fábrica de propriedades
+para aplicar a mesma validação a `weight` e a `price`, sem precisar escrever
+múltiplos _getters_ e _setters_. A fábrica de propriedades se apoiou em
+conceitos sutis—como clausuras e a sobreposição de atributos de instância por
+propriedades—para fornecer uma solução genérica elegante, usando para isso o
+mesmo número de linhas que usamos antes para escrever manualmente a definição de
+uma única propriedade.
+
+Por fim, demos uma rápida passada pelo tratamento da exclusão de atributos com propriedades, seguida por um resumo dos principais atributos especiais, funções embutidas e métodos especiais que suportam a metaprogramação de atributos no núcleo da linguagem Python.
+
+
+=== 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
+capítulo
+https://fpy.li/bn[«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[«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[«Pesquisa de método especial»]. 
+A seção https://fpy.li/bt[«Atributos especiais»] trata dos atributos `+__class__+` e `+__dict__+`.
+
+O https://fpy.li/pycook3[_Python Cookbook, 3rd. ed._], de David Beazley e Brian
+K. Jones (O’Reilly), tem várias receitas relacionadas aos tópicos deste
+capítulo, mas eu destaco três delas: A
+_Recipe 8.8. Extending a Property in a Subclass_
+(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_
+(Delegando o Acesso a Atributos) implementa uma classe
+de fachada, demonstrando a maioria dos métodos especiais da
+<> deste livro; e a
+_Recipe 9.21. Avoiding Repetitive Property Methods_
+(Evitando Métodos de Propriedade Repetitivos),
+que foi a base da função fábrica de propriedades
+que apresentei no <>.
+
+O https://fpy.li/pynut3[_Python in a Nutshell_, 3rd. ed.], de Alex Martelli,
+Anna Ravenscroft e Steve Holden (O'Reilly), é rigoroso e objetivo. Eles dedicam
+apenas três páginas a propriedades, mas isto foi possível 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  implementadas debaixo
+dos panos. Assim, quando Martelli et al. chegam às 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 _Design by
+Contract_ [Projeto por Contrato], projetou a linguagem Eiffel,
+e escreveu o excelente _Object-Oriented
+Software Construction_, 2ª ed. (Pearson). Os primeiros seis capítulos são
+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. Spoiler: na última página do livro o autor revela que a "notação"
+extremamente legível usada como pseudocódigo ao longo livro é
+na verdade a linguagem Eiffel.
+
+
+<<<
+[[properties_soapbox]]
+.Ponto de Vista
+****
+
+O Princípio de Acesso Uniforme de Meyer((("Soapbox sidebars", "Uniform Access Principle", id="Suaccess22")))((("Uniform Access Principle", id="uaccess22")))((("Meyer's Uniform Access Principle", id="meyerua22")))((("dynamic attributes and properties", "Soapbox discussion", id="DAPsoap22"))) é esteticamente atraente. Como um programador usando uma API, eu não deveria ter de me preocupar se `product.price` simplesmente lê um atributo de dados ou executa uma computação. Como um consumidor e um cidadão, eu me importo: no comércio online atual, o valor de `product.price` muitas vezes depende de quem está perguntando, então ele certamente não é um mero atributo de dados. Na verdade, é uma prática comum apresentar um preço mais baixo se a consulta vem de fora da loja—por exemplo, de um mecanismo de comparação de preços. Isso pune os fregueses fiéis, que gostam de explorar sua loja favorita. Mas estou divagando.
+
+A digressão anterior toca um ponto relevante para a programação: apesar do
+Princípio de Acesso Uniforme fazer todo sentido em um mundo ideal, na realidade
+os usuários de uma API podem precisar saber se ler `product.price` é
+potencialmente dispendioso ou demorado demais. Isto é um problema geral das abstrações
+em programação: elas dificultam raciocinar sobre o custo de computar
+uma expressão durante a execução. Por outro lado, abstrações
+permitem aos usuários da linguagem fazerem mais com menos código. É uma troca. Como de
+hábito em questões de engenharia de software, o
+https://fpy.li/22-26[«wiki original»] de Ward Cunningham contém argumentos inteligentes sobre os prós e contras do
+https://fpy.li/22-27[_Uniform Access Principle_] (Princípio de Acesso Uniforme).
+
+Em linguagens de programação orientadas a objetos, a aplicação ou violação do Princípio de Acesso Uniforme muitas vezes gira em torno da sintaxe de leitura de atributos de dados públicos versus a invocação de métodos _getter/setter_.
+
+Smalltalk e Ruby resolvem esta questão de uma forma simples e elegante: elas não suportam nenhuma forma de atributos de dados públicos. Todo atributo de instância nessas linguagens é privado, então qualquer acesso a eles deve passar por métodos. Mas sua sintaxe torna isso indolor: em Ruby, `product.price` invoca o _getter_ de `price`; em Smalltalk, ele é simplesmente `product price`.
+
+<<<
+Na outra ponta do espectro, a linguagem Java permite ao programador escolher entre quatro modificadores de nível de acesso—incluindo o default sem nome que o https://fpy.li/22-28[«Java Tutorial»] chama de "_package-private_".
+
+A prática geral, entretanto, não concorda com a sintaxe definida pelos projetistas de Java. Todos no campo de Java concordam que atributos devem ser `private`, e é necessário escrever isso explicitamente todas as vezes, porque não é o default. Quando todos os atributos são privados, todo acesso a eles de fora da classe precisa passar por métodos de acesso. Os IDEs de Java incluem atalhos para gerar métodos de acesso automaticamente. Infelizmente, o IDE não ajuda quando você precisa ler aquele código seis meses depois. É problema seu navegar por um oceano de métodos de acesso que não fazem nada, para encontrar aqueles que adicionam valor, implementando alguma lógica do negócio.
+
+Alex Martelli representa a maioria da comunidade Python quando chama métodos de acesso de "idiomatismos patéticos", e então apresenta
+exemplos que parecem muito diferentes, mas fazem a mesma coisa:footnote:[Alex Martelli, _Python in a Nutshell_, 2ª ed. (O'Reilly), p. 101.]
+
+[source, python]
+----
+um_objeto.contagem += 1
+# bem melhor que...
+um_objeto.set_contagem(um_objeto.get_contagem() + 1)
+----
+
+Algumas vezes, ao projetar uma API, me pergunto se todo método que não recebe
+argumentos (além de `self`) é uma função pura (isto é, devolve um valor mas não
+tem efeitos colaterais) não deveria ser substituído por uma propriedade somente
+de leitura. Neste capítulo, o método `LineItem.subtotal` (no
+<>) seria um bom candidato a se tornar uma
+propriedade somente para leitura. Claro, isso exclui métodos projetados para
+modificar o objeto, tal como `my_list.clear()`. Seria uma péssima ideia
+transformar isso em uma propriedade, de tal forma que o mero acesso a
+`my_list.clear` apagaria o conteúdo da lista!
+
+Na biblioteca GPIO https://fpy.li/22-29[_Pingo_] (mencionada na
+https://fpy.li/ct[«Seção 3.5.2»], vol.1), grande
+parte da API do usuário está baseada em propriedades. Por exemplo, para ler o
+valor atual de uma porta analógica escrevemos `pin.value`,
+e para definir o
+modo de uma porta,
+`pin.mode = OUT`.
+
+<<<
+Por trás da cortina, ler o
+valor de uma porta analógica ou definir o modo de uma porta digital pode
+implicar em bastante código, dependendo do driver específico da placa. Decidimos
+usar propriedades no Pingo porque queríamos que a API fosse confortável de usar
+até mesmo em ambientes interativos como um Jupyter Notebook, e achamos que
+`pin.mode = OUT` é mais bonito e fácil de digitar que
+`pin.set_mode(OUT)`.
+
+Apesar de achar a solução de Smalltalk e de Ruby mais limpa, acho que a
+abordagem de Python faz mais sentido que a de Java. Podemos começar simples,
+programando elementos de dados como atributos públicos, pois sabemos que eles
+sempre podem ser encapsulados por propriedades (ou descritores, dos quais
+falaremos no próximo capítulo).((("", startref="meyerua22")))((("",
+startref="uaccess22")))((("", startref="Suaccess22")))
+
+**`+__new__+` é melhor que `new`**
+
+Em Python((("function-class duality")))((("Soapbox sidebars",
+"function-class duality"))) a instanciação de um objeto usa mesma
+sintaxe que a invocação de uma função: `my_obj = spam()`,
+onde `spam` pode ser uma classe ou qualquer
+outro invocável.
+
+Outras linguagens, influenciadas pela sintaxe do {cpp}, têm um operador `new`
+que faz a instanciação parecer diferente de uma invocação. Na maior parte do
+tempo, o usuário de uma API não se importa se `spam` é uma função ou uma classe.
+Por anos tive a impressão que `property` era uma função. No uso cotidiano, não
+faz diferença.
+
+Há muitas boas razões para substituir construtores por fábricas.footnote:[As razões que menciono foram apresentadas no artigo intitulado https://fpy.li/22-30[_Java's new Considered Harmful_] (new de Java considerado nocivo), de Jonathan Amsterdam, publicado na Dr. Dobbs Journal, e em _Consider static factory methods instead of constructors_ (Considere substituir construtores por métodos fábrica estáticos), que é o Item 1 do premiado livro _Effective Java_, 3ª ed., de Joshua Bloch (Addison-Wesley).] Um motivo comum é limitar o número de instâncias, devolvendo objetos construídos anterioremente (como no padrão de projeto Singleton). Um uso relacionado é fazer _caching_ de uma construção de objeto dispendiosa. Além disso, às vezes é conveniente devolver objetos de tipos diferentes, dependendo dos argumentos passados.
+
+Programar um construtor é simples; fornecer uma fábrica aumenta a flexibilidade
+às custas de mais código. Em linguagens com um operador `new`, o projetista de
+uma API precisa decidir se vai fornecer um construtor simples ou
+investir em uma fábrica. Se a escolha inicial estiver errada, a correção pode
+ser cara—tudo porque `new` é um operador.
+
+<<<
+Às vezes pode ser conveniente pegar o caminho inverso, e substituir uma função simples por uma classe.
+
+Em Python, classes e funções são intercambiáveis em muitas situações.
+Não apenas pela ausência de um operador `new` para construir instâncias,
+mas também porque existe o método especial `+__new__+`,
+que pode transformar uma classe em uma fábrica que produz objetos de diferentes tipos
+(como vimos na <>), ou devolve instâncias pré-fabricadas em vez de criar uma nova instância toda vez.
+
+Esta dualidade função-classe seria mais fácil de aproveitar se a
+https://fpy.li/22-31[PEP 8—Style Guide for Python Code] (Guia de Estilo para Código Python)
+não recomendasse `CamelCase` para nomes de classes.
+Por outro lado, dezenas de classes na biblioteca padrão têm nomes apenas em minúsculas
+(por exemplo, `property`, `str`, `defaultdict`, etc.).
+Daí que talvez o uso de nomes de classe só com minúsculas seja uma vantagem, e não um bug.
+Mas independente do ponto de vista, esta inconsistência
+no uso de maiúsculas e minúsculas nos nomes de classes na biblioteca padrão de Python é desconfortável.
+
+Apesar da invocação de uma função não ser diferente da invocação de uma classe,
+é bom saber qual é qual porque podemos criar uma subclasse de uma classe, mas não de uma função.
+Então eu, pessoalmente, uso `CamelCase` nas classes que escrevo,
+e gostaria que todas as classea na biblioteca padrão de Python seguissem alguma convenção
+para não evitar casos como `collections.OrderedDict` e `collections.defaultdict`.((("", startref="DAPsoap22")))
+****
diff --git a/vol3/cap23.adoc b/vol3/cap23.adoc
new file mode 100644
index 00000000..74157a6e
--- /dev/null
+++ b/vol3/cap23.adoc
@@ -0,0 +1,818 @@
+[[ch_descriptors]]
+== Descritores de Atributos
+:example-number: 0
+:figure-number: 0
+
+[quote, Raymond Hettinger, guru e mantenedor do Python]
+____
+
+Aprender sobre descritores não apenas dá acesso a um conjunto maior de ferramentas,
+também cria uma compreensão melhor sobre o funcionamento do 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, são descritores os campos de registros em um ORM (_Object Relational Mapping_, Mapeamento
+Objeto-Relacional) como o _SQLAlchemy_ ou ORM do _Django_.
+Nestes sistemas, os descritores controlam a conversão
+e validação de dados dos atributos de objetos Python
+para campos nas tabelas do banco de dados.
+
+Um((("__get__")))((("__set__")))((("__delete__")))
+descritor é uma classe que implementa um protocolo dinâmico, composto pelos
+métodos `+__get__+`, `+__set__+`, e `+__delete__+`. A classe `property`
+implementa o protocolo de descritor completo. 
+Você ponde implementar apenas parte do protocolo de descritor, 
+como ocorre com protocolos dinâmicos em geral.
+Na verdade, a maioria dos
+descritores que vemos em código real implementa apenas `+__get__+` e
+`+__set__+`, e muitos têm só um destes métodos.
+
+Descritores são um recurso característico de Python, presentes não apenas no nível das aplicações, mas também na infraestrutura da linguagem.
+Funções definidas pelo usuário são descritores. Veremos como o protocolo do descritor permite que métodos operem como métodos vinculados ou funções, dependendo de como são acessados.
+
+Entender os descritores é crucial para dominar Python. Este capítulo é sobre isso.
+
+Nas próximas páginas((("attribute descriptors", "topics covered"))) vamos refatorar o exemplo da loja de alimentos orgânicos a granel, visto na <>, substituindo propriedades por descritores.
+Isto tornará mais fácil reutilizar a lógica de validação de atributos em diferentes classes.
+
+Vamos estudar os conceitos de descritores dominantes e não dominantes, e entender que as funções de Python são descritores.
+Para finalizar, veremos algumas dicas para a implementação de descritores.
+
+
+[[whats_new_descriptor_sec]]
+=== Novidades neste capítulo
+
+O((("attribute descriptors", "significant changes to"))) exemplo do descritor
+`Quantity`, na <>, ficou muito mais simples graças ao
+método especial `+__set_name__+`, adicionado ao protocolo de descritor no Python
+3.6. Naquela mesma seção, removi o exemplo da fábrica de propriedades, pois ele se
+tornou irrelevante: o ponto ali era mostrar uma solução alternativa para o
+problema de `Quantity`, mas com `+__set_name__+` o exemplo ficou redundante.
+Removi a classe `AutoStorage`, que aparecia na <> pelo mesmo motivo.
+
+[[validating_descriptor_sec]]
+=== Descritor para validação de atributos
+
+Como((("attribute descriptors", "attribute validation", id="ADvalidation23")))((("attributes", "using attribute descriptors for validation", id="ATvalidation23"))) vimos na <>, uma fábrica de propriedades é uma forma de evitar código repetitivo de _getters_ e _setters_, aplicando padrões de programação funcional.
+Uma fábrica de propriedades é uma função de ordem superior que cria um conjunto de funções de acesso parametrizadas e constrói uma instância de propriedade customizada, com clausuras para preservar configurações como `storage_name`.
+A forma orientada a objetos de resolver o mesmo problema é uma classe descritora.
+
+Vamos seguir com a série de exemplos `LineItem` de onde paramos, na <>, refatorando a fábrica de propriedades `quantity` em uma classe descritora `Quantity`.
+Isso vai torná-la mais fácil de usar.
+
+==== LineItem versão #3: Um descritor simples
+
+Como dito na introdução, uma classe que implemente um método `+__get__+`, um `+__set__+`, ou um
+`+__delete__+` é um descritor. Para usar um descritor, declaramos instâncias dele como atributos em outra classe.
+
+
+Vamos criar um descritor `Quantity`, e a classe `LineItem` vai usar duas instâncias de `Quantity`: uma para gerenciar o atributo `weight`, a outra para `price`. Um((("UML class diagrams", "managed and descriptor classes"))) diagrama ajuda: dê uma olhada na <>.
+
+[[lineitem3_uml]]
+.Diagrama de classe UML para `LineItem` usando uma classe descritora chamada `Quantity`. Atributos sublinhados no UML são atributos de classe. Observe que [.underline]#weight# e [.underline]#price# são instâncias de `Quantity` vinculadas à classe `LineItem` (são atributos da classe), mas cada instância de `LineItem` também tem atributos `weight` e `price`, onde estes valores são armazenados na própria instância.
+image::../images/flpy_2301.png[align="center", pdfwidth=10cm]
+
+Note que a palavra `weight` aparece duas vezes na <>, pois na verdade há dois atributos diferentes chamados `weight`: um é um atributo de classe de `LineItem`, o outro é um atributo de instância que existirá em cada objeto `LineItem`. O mesmo se aplica a `price`.
+
+===== Termos para entender descritores
+
+Implementar((("attribute descriptors", "relevant terminology"))) e usar descritores envolve várias peças.
+Vou utilizar termos e definições abaixo nas descrições dos exemplos desse capítulo.
+Será mais fácil entendê-los após ver o código, mas quis colocar todas as definições no início, +
+para você poder voltar aqui quando precisar.
+
+Classe descritora (_descriptor class_):: Uma((("descriptor classes"))) classe que implementa o protocolo de descritor. Por exemplo, `Quantity` na <>.
+
+Classe gerenciada (_managed class_):: A((("managed classes"))) classe onde as instâncias do descritor são declaradas, como atributos de classe. Na <>, `LineItem` é a classe gerenciada.
+
+Instância do descritor (_descriptor instance_):: Cada((("descriptor instances"))) instância de uma classe descritora declarada como um atributo de classe na classe gerenciada. Na <>, cada instância do descritor está representada pela seta de composição com um nome sublinhado (na UML, o sublinhado indica um atributo de classe). Os diamantes pretos tocam a classe `LineItem`, que contém as instâncias do descritor.
+
+Instância gerenciada (_managed instance_):: Uma((("managed instances"))) instância da classe gerenciada. Neste exemplo, instâncias de `LineItem` são as instâncias gerenciadas (elas não aparecem no diagrama de classe).
+
+Atributo de armazenamento (_storage attribute_):: Um((("storage attributes"))) atributo da instância gerenciada que guarda o valor de um atributo gerenciado para aquela instância específica. Na <>, os atributos de instância `weight` e `price` de `LineItem` são atributos de armazenamento. Eles são diferentes das instâncias do descritor, que são sempre atributos de classe.
+
+Atributo gerenciado (_managed attribute_):: Um((("managed attributes"))) atributo público na classe gerenciada que é controlado por uma instância de um descritor, com os valores preservados em atributos de armazenamento. Uma instância do descritor e um atributo de armazenamento fornecem a infraestrutura para um atributo gerenciado.
+
+É importante entender que instâncias de `Quantity` são atributos de classe de `LineItem`. Este((("UML class diagrams", "annotated with MGN"))) ponto fundamental é realçado pelas engenhocas (_mills_) e bugigangas (_gizmos_) na <>.
+
+[[lineitem3_uml_mgn]]
+.Diagrama de classe UML anotado com MGN (Mills & Gizmos Notation—Notação de Engenhocas e Bugigangas): classes são engenhocas que produzem bugigangas—as instâncias. A engenhoca `Quantity` produz duas bugigangas de cabeça redonda, que são anexadas à engenhoca `LineItem`: [.underline]#weight# e [.underline]#price#. A engenhoca `LineItem` produz bugigangas retangulares que têm seus próprios atributos `weight` e `price`, onde aqueles valores são armazenados.
+image::../images/flpy_2302.png[align="center", pdfwidth=11cm]
+
+[role="pagebreak-before less_space"]
+[[mgn_box1]]
+.Apresentando a notação Engenhocas & Bugigangas (_Mills & Gizmos_)
+****
+Após((("Mills & Gizmos Notation (MGN)"))) explicar descritores várias vezes, percebi que a UML não é muito boa para mostrar as relações entre classes e instâncias, tal como a relação entre uma classe gerenciada e as instâncias do descritor.footnote:[Classes e instâncias são representadas por retângulos em diagramas de classe UML. Há diferenças visuais, mas instâncias raramente aparecem em diagramas de classe, então desenvolvedores podem não reconhecê-las como tal.] Daí inventei minha própria "linguagem", a Notação Engenhocas e Bugigangas (MGN), que uso para anotar diagramas UML.
+
+Desenhei a MGN para tornar mais evidente a diferença entre classes e instâncias. Veja a <>. Na MGN, uma classe aparece como uma "engenhoca", (_mill_) uma máquina complicada que produz bugigangas (_gizmos_). Classes/engenhocas são máquinas com alavancas e mostradores. As bugigangas são as instâncias, e elas têm uma aparência mais simples. Quando este livro é produzido em cores, as bugigangas têm a mesma cor da engenhoca que as produziu.
+
+[[mgn_diagram_demo]]
+.Esboço MGN mostrando a classe `LineItem` produzindo três instâncias, e `Quantity` produzindo duas. Uma instância de `Quantity` está recuperando um valor armazenado em uma instância de `LineItem`.
+image::../images/flpy_2303.png[Esboço MGN para `LineItem` e `Quantity`]
+
+Para este exemplo, desenhei instâncias de `LineItem` como linhas em uma fatura tabular, com três células representando os três atributos (`description`, `weight` e `price`). Como as instâncias de `Quantity` são descritores, elas têm uma lente de aumento para ler (_get_) os valores, e uma garra para escrever (_set_) os valores. Quando chegarmos às metaclasses, você me agradecerá por esses desenhos.
+****
+
+<<<
+Chega de rabiscos por enquanto. Aqui está o código: o <> mostra a classe descritora `Quantity`, e o <> lista a nova classe `LineItem` usando duas instâncias de `Quantity`.
+
+[[quantity_v3]]
+.bulkfood_v3.py: o descritor `Quantity` não aceita valores negativos
+====
+[source, python]
+----
+include::../code/23-descriptor/bulkfood/bulkfood_v3.py[tags=LINEITEM_QUANTITY_V3]
+----
+====
+
+<1> O descritor é um recurso baseado em protocolo: não é necessário herdar de uma classe específica, basta implementar `+__get__+` ou `+__set__+`.
+
+<2> Cada instância de `Quantity` terá um atributo `storage_name`: é o nome do atributo de armazenamento que vai preservar o valor nas instâncias gerenciadas.
+
+<3> O `+__set__+` é invocado quando ocorre uma tentativa de atribuir um valor a um atributo gerenciado. Aqui, `self` é a instância do descritor `Quantity`, (isto é, `LineItem.weight` ou `LineItem.price`), `instance` é a instância gerenciada (uma instância de `LineItem`) e `value` é o valor que está sendo atribuído.
+
+<4> Precisamos armazenar o valor do atributo diretamente no `+__dict__+`; invocar `setattr(instance, self.storage_name, value)` dispararia novamente o método `+__set__+`, levando a uma recursão infinita.
+
+<5> Precisamos implementar `+__get__+`, pois o nome do atributo gerenciado pode não ser igual ao `storage_name`. O argumento `owner` será explicado a seguir.
+
+<<<
+Implementar `+__get__+` é necessário porque um usuário poderia escrever isto:
+
+[source, python]
+----
+class Casa:
+    quartos = Quantity('cômodos')
+----
+
+Na classe `Casa`, o atributo gerenciado é `quartos`, mas o atributo de armazenamento é `cômodos`.
+Dada uma instância de `Casa` chamada `meu_lar`, acessar e modificar `meu_lar.quartos` passa pela instância do descritor `Quantity` vinculado a `quartos`, mas acessar e modificar `meu_lar.cômodos` não passa pelo descritor.
+
+Observe que `+__get__+` recebe três argumentos: `self`, `instance` e `owner`. O argumento `owner` é uma referência à classe gerenciada (por exemplo, `LineItem`), e é útil se você quiser que o descritor suporte o acesso a um atributo de classe—talvez para emular o comportamento default de Python, de procurar um atributo de classe quando o nome não é encontrado na instância.
+
+Se um atributo gerenciado como `weight`, é acessado através da classe como
+`LineItem.weight`, o método `+__get__+` do descritor recebe `None` no argumento `instance`.
+
+Para suportar introspecção e outras técnicas de metaprogramação pelo usuário, é uma boa prática fazer `+__get__+` devolver a instância do descritor quando o atributo gerenciado é acessado através da classe. Para fazer isso, escreveríamos `+__get__+` assim:
+
+[source, python]
+----
+    def __get__(self, instance, owner):
+        if instance is None:
+            return self
+        else:
+            return instance.__dict__[self.storage_name]
+----
+
+O <> demonstra o uso de `Quantity` em `LineItem`.
+
+<<<
+[[lineitem_class_v3]]
+.bulkfood_v3.py: descritores `Quantity` gerenciam atributos em `LineItem`
+====
+[source, python]
+----
+include::../code/23-descriptor/bulkfood/bulkfood_v3.py[tags=LINEITEM_V3]
+----
+====
+<1> A primeira instância do descritor vai gerenciar o atributo `weight`.
+<2> A segunda instância do descritor vai gerenciar o atributo `price`.
+<3> O restante do corpo da classe é tão simples e limpo como o código original em _bulkfood_v1.py_ (no <>).
+
+O código no <> evita a venda de trufas por $0:footnote:[O quilo de trufas brancas custa milhares de reais. Impedir a venda de trufas por $0,01 fica como exercício para a leitora com espírito de aventura. Conheço o caso real de uma pessoa que comprou uma enciclopédia sobre estatística de 1.800 dólares por 18 dólares, devido a um erro em uma loja online (neste caso não foi na _Amazon.com_).]
+
+[source, python]
+----
+>>> truffle = LineItem('White truffle', 100, 0)
+Traceback (most recent call last):
+    ...
+ValueError: value must be > 0
+----
+
+[WARNING]
+====
+Ao programar os métodos `+__get__+` e `+__set__+` de um descritor, tenha em mente o significado dos argumentos `self` e `instance`: `self` é a instância do descritor, `instance` é a instância gerenciada. Descritores que gerenciam atributos de instância devem armazenar os valores nas instâncias gerenciadas. É por isso que Python fornece o argumento `instance` aos métodos do descritor.
+====
+
+Pode ser tentador armazenar o valor de cada atributo gerenciado no próprio do descritor, mas é um erro. 
+
+<<<
+Em outras palavras, seria errado armazenar o valor no `+self.__dict__+`:
+
+[source, python]
+----
+    self.__dict__[self.storage_name] = value  # errado
+----
+
+O correto é armazenar o valor no `+instance.__dict__+`:
+
+[source, python]
+----
+    instance.__dict__[self.storage_name] = value  # certo
+----
+
+Para entender por que a primeira opção não funciona, pense no significado dos dois primeiros
+argumentos passados a `+__set__+`: `self` e `instance`. Aqui, `self` é a
+instância do descritor, que na verdade é um atributo de classe da classe
+gerenciada. Você pode ter milhares de instâncias de `LineItem` na memória em um
+dado momento, mas terá apenas duas instâncias dos descritores: os atributos de
+classe `LineItem.weight` e `LineItem.price`. Então, qualquer coisa armazenada
+nas próprias instâncias do descritor é na verdade parte de um atributo de classe
+de `LineItem`, e portanto é compartilhada por todas as instâncias de `LineItem`.
+
+
+==== Evitando repetir o nome do atributo gerenciado
+
+Um inconveniente do <> é a necessidade de repetir os nomes dos atributos quando os descritores são instanciados no corpo da classe gerenciada. Seria bom se a classe `LineItem` pudesse ser declarada assim:
+
+[source, python]
+----
+class LineItem:
+    weight = Quantity()
+    price = Quantity()
+
+    # o restante dos métodos permanece igual
+----
+
+Da forma como está escrito, o <> exige nomear explicitamente cada `Quantity`, algo não apenas inconveniente, mas também perigoso. Se um programador, ao copiar e colar código, se esquecer de editar os dois nomes, e terminar com uma linha como `price = Quantity('weight')`, o programa vai se comportar de forma muito errática, sobrescrevendo o valor de `weight` sempre que `price` for definido.
+
+<<<
+O problema é que o lado direito de uma atribuição é executado antes da variável
+existir—como vimos no https://fpy.li/6[«Capítulo 6»] (vol.1). A expressão `Quantity()` é avaliada para criar uma instância do descritor, e não há como o código na classe `Quantity` adivinhar o nome da variável à qual o descritor será vinculado (por exemplo, `weight` ou `price`).
+
+Felizmente, o protocolo de descritor agora suporta o método `+__set_name__+`. Veremos a seguir como usá-lo.
+
+[NOTE]
+====
+Nomear automaticamente o atributo de armazenamento de um descritor era uma tarefa espinhosa. Na primeira edição do _Python Fluente_, dediquei várias páginas e muitas linhas de código neste capítulo e no seguinte para apresentar diferentes soluções, incluindo o uso de um decorador de classe e depois metaclasses (no <>).
+Tudo isso ficou mais simples no Python 3.6.
+====
+
+[[auto_storage_sec]]
+==== LineItem versão #4: Nomeando atributos de armazenamento automaticamente
+
+Para não ter que repetir o nome do atributo ao criar uma instância de descritor, vamos implementar
+`+__set_name__+`, para definir o `storage_name` de cada instância de `Quantity`. O método especial
+`+__set_name__+` foi acrescentado ao protocolo de descritor no Python 3.6.
+
+O interpretador invoca `+__set_name__+` em cada descritor encontrado no corpo de uma `class`—se o descritor implementar esse método.footnote:[Mais precisamente, `+__set_name__+` é invocado por `+type.__new__+`—o construtor de objetos que representam classes. A classe embutida `type` é na verdade uma metaclasse, a classe default de classes definidas pelo usuário. Isso é um pouco difícil de entender de início, mas fique tranquila: o <> é dedicado à configuração dinâmica de classes, incluindo o conceito de metaclasses.]
+
+No <>, a classe descritora `Quantity` não precisa de um `+__init__+`.
+Em vez disso, `+__set_item__+` armazena o nome do atributo de armazenamento.
+
+[[lineitem_class_v4]]
+.bulkfood_v4.py: `+__set_name__+` define o nome para cada instância do descritor `Quantity`
+====
+[source, python]
+----
+include::../code/23-descriptor/bulkfood/bulkfood_v4.py[tags=LINEITEM_V4]
+----
+====
+<1> `self` é a instância do descritor (não a instância gerenciada), `owner` é a classe gerenciada e `name` é o nome do atributo de `owner` ao qual essa instância do descritor foi atribuída no corpo da classe de `owner`.
+<2> Isso é o que o `+__init__+` fazia no <>.
+<3> O método `+__set__+` aqui é exatamente igual ao do <>.
+<4> Não é necessário implementar `+__get__+`, porque o nome do atributo de armazenamento é igual ao nome do atributo gerenciado. A expressão `product.price` obtém o atributo `price` diretamente da instância de `LineItem`.
+<5> Não é necessário passar o nome do atributo gerenciado para o construtor de `Quantity`. Esse era o objetivo dessa versão.
+
+Olhando para o <>, pode parecer muito código só para gerenciar um par de atributos,
+mas é importante perceber que agora abstraímos a lógica do descritor em uma unidade de código separada e reutilizável: a classe `Quantity`.
+
+<<<
+Normalmente não definimos um descritor no mesmo módulo em que ele é usado,
+mas em um módulo auxiliar separado,
+para ser usado por toda a aplicação—ou mesmo por muitas aplicações,
+se estivermos desenvolvendo uma biblioteca ou um framework.
+
+Tendo isso em mente, o <> representa melhor o uso típico de um descritor.
+
+[[lineitem_class_v4c]]
+.bulkfood_v4c.py: uma definição mais limpa de `LineItem`; a classe descritora `Quantity` agora reside no módulo importado `model_v4c`
+====
+[source, python]
+----
+include::../code/23-descriptor/bulkfood/bulkfood_v4c.py[tags=LINEITEM_V4C]
+----
+====
+<1> Importa o módulo `model_v4c`, onde `Quantity` é implementada.
+<2> Coloca `model.Quantity` em uso.
+
+Usuários do Django vão perceber que o <> se parece muito com uma definição de modelo. Isso não é uma coincidência: os campos de modelos Django são descritores.
+
+Já que descritores são implementados como classes, podemos aproveitar a herança para reutilizar parte do código que já temos em novos descritores. É o que faremos na próxima seção.
+
+[[new_descr_type_sec]]
+==== LineItem versão #5: um novo tipo descritor
+
+A loja imaginária de comida orgânica encontra um problema: de alguma forma, uma
+instância de um produto foi criada com uma descrição vazia, e o pedido não pode
+ser processado. Para prevenir isso, criaremos um novo descritor: `NonBlank`. Ao
+projetar `NonBlank`, percebemos que ele será muito parecido com o descritor
+`Quantity`, exceto pela lógica de validação.
+
+Isto sugere uma refatoração, resultando em `Validated`, uma classe abstrata que sobrescreve um método
+`+__set__+`, invocando o método `validate`, que precisa ser implementado por subclasses.
+
+Vamos então reescrever `Quantity` e implementar `NonBlank`, herdando de `Validated` e programando apenas os métodos `validate`.
+
+A relação entre `Validated`, `Quantity` e `NonBlank` é uma aplicação do padrão _Template Method_ (Método Gabarito),
+como descrito no clássico _Design Patterns_:
+
+[quote]
+____
+O Método Gabarito define um algoritmo em termos de operações abstratas que subclasses sobrescrevem para fornecer o comportamento concreto.footnote:[Gamma et al., _Design Patterns: Elements of Reusable Object-Oriented Software_, p. 326.]
+____
+
+No <>, `+Validated.__set__+` é um método gabarito e `self.validate` é a operação abstrata.
+
+[[model_v5_abc]]
+.model_v5.py: the `Validated` ABC
+====
+[source, python]
+----
+include::../code/23-descriptor/bulkfood/model_v5.py[tags=MODEL_V5_VALIDATED_ABC]
+----
+====
+<1> `+__set__+` delega a validação para o método `validate`...
+<2> ...e então usa o `value` devolvido para atualizar o valor armazenado.
+<3> `validate` é um método abstrato; este é o método que "preenche" gabarito `+__set__+`, definindo parte do seu comportamento.
+
+Alex Martelli prefere chamar este padrão de projeto _Self-Delegation_ (Auto-Delegação),
+e concordo que é um nome mais descritivo: a primeira linha de `+__set__+` auto-delega para
+`validate`, ou seja, invoca outro método na mesma instância de uma subclasse concreta de `Validated`.footnote:[Slide #50 da palestra https://fpy.li/23-1[_Python Design Patterns_], de Alex Martelli. Altamente recomendada.]
+
+As subclasses concretas de `Validated` neste exemplo são `Quantity` e `NonBlank`:
+
+[[model_v5_sub]]
+.model_v5.py: `Quantity` e `NonBlank`, subclasses concretas de `Validated`
+====
+[source, python]
+----
+include::../code/23-descriptor/bulkfood/model_v5.py[tags=MODEL_V5_VALIDATED_SUB]
+----
+====
+<1> Implementação exigida pelo método abstrato `Validated.validate`.
+<2> Se não sobrar nada após a remoção dos espaços em branco antes e depois do valor, este é rejeitado.
+<3> Exigir que os métodos `validate` concretos devolvam o valor validado dá a eles a oportunidade de limpar, converter ou normalizar os dados recebidos. Neste caso, `value` é devolvido sem espaços iniciais ou finais.
+
+Usuários de _model_v5.py_ não precisam saber todos esses detalhes. O que importa é poder usar `Quantity` e `NonBlank` para automatizar a validação de atributos de instância. Veja a última classe `LineItem` no <>.
+
+[[lineitem_class_v5]]
+.bulkfood_v5.py: `LineItem` usando os descritores `Quantity` e `NonBlank`
+====
+[source, python]
+----
+include::../code/23-descriptor/bulkfood/bulkfood_v5.py[tags=LINEITEM_V5]
+----
+====
+<1> Importa o módulo `model_v5`, dando a ele um nome amigável.
+<2> Usa `model.NonBlank`. O restante do código não foi modificado.
+
+Os exemplos de `LineItem` que vimos neste capítulo demonstram um uso típico de
+descritores para gerenciar atributos de dados. Descritores como `Quantity` são
+chamados _descritores dominantes_, pois seu método `+__set__+` sobrescreve (isto é,
+intercepta e impede) a atribuição de um atributo de instância com o mesmo nome na
+instância gerenciada. Entretanto, há também _descritores não dominantes_. Vamos
+explorar essa diferença detalhadamente na próxima seção.((("",
+startref="ADvalidation23")))((("", startref="ATvalidation23")))
+
+
+=== Descritores dominantes ou não dominantes
+
+Recordando((("attribute descriptors", "overriding versus nonoverriding",
+id="ADovervnon23")))((("nonoverriding descriptors",
+id="nonover23")))((("overriding descriptors", id="overrid23"))), há uma
+importante assimetria na forma como Python lida com atributos.
+Em uma classe sem descritores, ler um atributo
+através de uma instância devolve o atributo definido na instância.
+Mas se tal atributo não existir na instância, um atributo de classe será obtido.
+Por outro lado, uma atribuição a um atributo em uma instância cria o
+atributo na instância, sem afetar a classe de forma alguma.
+
+Esta assimetria também afeta descritores, criando duas grandes categorias de descritores, dependendo do método `+__set__+` estar ou não implementado.
+Se `+__set__+` estiver presente, a classe é um descritor dominante (_overriding descriptor_); caso contrário, ela é um descritor não dominante
+(_non-overriding descriptor_).
+Estes termos farão sentido quando examinarmos os comportamentos de descritores nos próximos exemplos.
+
+Usei algumas funções auxiliares definidas no <> para observar as diferentes categorias de descritores, 
+Sua lógica não é importante, mas elas são usadas nas invocações a `print_args` no <>.
+
+[[descriptorkinds_aux_ex]]
+.descriptorkinds.py: funções auxiliares para formatar e exibir objetos
+====
+[source, python]
+----
+include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_AUX]
+----
+====
+
+Agora vamos criar três classes de descritores, e uma classe `Managed` onde os descritores são instalados.
+
+<<<
+[[descriptorkinds_ex]]
+.descriptorkinds.py: para estudar os comportamentos de descritores
+====
+[source, python]
+----
+include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS]
+----
+====
+<1> Uma classe descritora dominante com `+__get__+` e `+__set__+`.
+<2> A função `print_args` é chamada por todos os métodos do descritor neste exemplo.
+<3> Um descritor dominante sem um método `+__get__+`.
+<4> Nenhum método `+__set__+` aqui, então este é um descritor não dominante.
+<5> A classe gerenciada, usando uma instância de cada uma das classes descritoras.
+<6> O método `spam` está aqui para efeito de comparação, pois métodos também são descritores.
+
+Nas próximas seções, examinaremos o comportamento de leitura e escrita de atributos na classe `Managed` e em uma de suas instâncias, passando por cada um dos diferentes descritores definidos.
+
+[[overriding_descriptor_sec]]
+==== Descritor dominante
+
+Um descritor que implementa o método `+__set__+` é um _descritor dominante_ pois, apesar de ser um atributo de classe, um descritor que implementa `+__set__+` irá  sobrescrever tentativas de atribuição a atributos de instância. É assim que o <> foi implementado. Propriedades também são descritores dominantes: se você não fornecer uma função _setter_, o `+__set__+` default da classe `property` vai gerar um `AttributeError`, para sinalizar que o atributo é somente para leitura.
+
+[WARNING]
+====
+Contribuidores e autores da comunidade Python usam termos diferentes ao discutir esses conceitos. Adotei "descritor dominante" (_overriding descriptor_), do livro _Python in a Nutshell_.
+A documentação oficial de Python usa "descritor de dados" (_data descriptor_) mas "descritor dominante" destaca o comportamento especial.
+Descritores dominantes também são chamados "descritores forçados" (_enforced descriptors_).
+Sinônimos para descritores não dominantes incluem "descritores sem dados" (_nondata descriptors_, na documentação oficial em português) ou "descritores ocultáveis" (_shadowable descriptors_).
+====
+
+Dado o código no <>, alguns experimentos com um descritor dominante podem ser vistos no <>.
+
+<<<
+[[descriptorkinds_demo1]]
+.O comportamento de um descritor dominante
+====
+[source, python]
+----
+include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO1]
+----
+====
+<1> Cria o objeto `Managed`, para testes.
+<2> `obj.over` aciona o método `+__get__+` do descritor, passando a instância gerenciada `obj` como segundo argumento.
+<3> `Managed.over` aciona o método `+__get__+` do descritor, passando `None` como segundo argumento (`instance`).
+<4> Atribuir a `obj.over` aciona o método `+__set__+` do descritor, passando o valor `7` como último argumento.
+<5> Ler `obj.over` ainda invoca o método `+__get__+` do descritor.
+<6> Contorna o descritor, definindo um valor diretamente no `+obj.__dict__+`.
+<7> Verifica se aquele valor está no `+obj.__dict__+`, sob a chave `over`.
+<8> Entretanto, mesmo com um atributo de instância chamado `over`, o descritor `Managed.over` continua interceptando tentativas de ler `obj.over`.
+
+<<<
+==== Descritor dominante sem `+__get__+`
+
+Propriedades e outros descritores dominantes, como os campos de modelo do Django, implementam tanto `+__set__+` quanto `+__get__+`. Mas também é possível implementar apenas `+__set__+`, como vimos no <>. Neste caso, apenas a escrita é controlada pelo descritor. Ler o descritor através de uma instância irá devolver o próprio objeto descritor, pois não há um
+`+__get__+` para tratar daquele acesso. Se um atributo de instância de mesmo nome for criado com um novo valor, através de acesso direto ao `+__dict__+` da instância, o método `+__set__+` continuará interceptando tentativas posteriores de definir aquele atributo, mas a leitura do atributo vai simplesmente devolver o novo valor na instância, em vez de devolver o objeto descritor. Em outras palavras, o atributo de instância vai ocultar o descritor, mas apenas na leitura. Veja o <>.
+
+[[descriptorkinds_demo2]]
+.Descritor dominante sem `+__get__+`
+====
+[source, python]
+----
+include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO2]
+----
+====
+<1> Este descritor dominante não tem um método `+__get__+`, então ler `obj.over_no_get` obtém a instância do descritor a partir da classe.
+<2> A mesma coisa acontece se obtivermos a instância do descritor diretamente da classe gerenciada.
+<3> Tentar definir um valor para `obj.over_no_get` invoca o método `+__set__+` do descritor.
+<4> Como nosso `+__set__+` não faz modificações, ler `obj.over_no_get` obtém a instância do descritor na classe gerenciada.
+<5> Definindo um atributo de instância chamado `over_no_get` direto no `+__dict__+` da instância.
+<6> Agora aquele atributo de instância `over_no_get` oculta o descritor, mas só na leitura.
+<7> Tentar atribuir um valor a `obj.over_no_get` continua passando pelo `+__set__+` do descritor.
+<8> Mas na leitura, aquele descritor é ocultado enquanto existir um atributo de instância de mesmo nome.
+
+
+==== Descritor não dominante
+
+Um descritor que não implementa `+__set__+` é um descritor não dominante. Definir um atributo de instância com o mesmo nome vai ocultar o descritor, tornando-o incapaz de tratar aquele atributo naquela instância específica. Métodos e a `@functools.cached_property` são implementados como descritores não dominantes. O <> mostra o comportamento de um descritor não dominante.
+
+[[descriptorkinds_demo3]]
+.Comportamento de um descritor não dominante
+====
+[source, python]
+----
+include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO3]
+----
+====
+<1> `obj.non_over` aciona o método `+__get__+` do descritor, passando `obj` como segundo argumento.
+<2> `Managed.non_over` é um descritor não dominante, então não há um `+__set__+` para interferir com essa atribuição.
+<3> O `obj` agora tem um atributo de instância chamado `non_over`, que oculta o atributo do descritor de mesmo nome na classe `Managed`.
+<4> O descritor `Managed.non_over` ainda está lá, e intercepta esse acesso através da classe.
+<5> Se o atributo de instância `non_over` for excluído...
+<6> ...então ler `obj.non_over` encontra o método `+__get__+` do descritor; mas observe que o segundo argumento é a instância gerenciada.
+
+Nos exemplos anteriores, vimos várias atribuições a um atributo de instância com nome igual ao do descritor, com resultados diferentes dependendo da presença ou não de um método `+__set__+` no descritor.
+
+A definição de atributos na classe não pode ser controlada por descritores ligados à mesma classe.
+Em especial, isso significa que os próprios atributos do descritor podem ser danificados por atribuições à classe, como explicado na próxima seção.
+
+
+==== Sobrescrevendo um descritor em uma classe
+
+Independente de o descritor ser dominante ou não, ele pode ser sobrescrito por uma atribuição à classe. Isso é uma((("monkey-patching"))) técnica de _monkey-patching_ mas, no <>, os descritores são substituídos por números inteiros, algo que certamente quebraria a lógica de qualquer classe que dependesse dos descritores para seu funcionamento correto.
+
+[[descriptorkinds_demo4]]
+.Qualquer descritor pode ser sobrescrito na própria classe
+====
+[source, python]
+----
+include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO4]
+----
+====
+<1> Cria uma nova instância para testes posteriores.
+<2> Sobrescreve os atributos dos descritores na classe.
+<3> Os descritores realmente desapareceram.
+
+O <> expõe outra assimetria entre a leitura e a escrita de atributos: apesar da leitura de um atributo de classe poder ser controlada por um `+__get__+` de um descritor ligado à classe gerenciada, a escrita em um atributo de classe não pode ser tratada por um `+__set__+` de um descritor ligado à mesma classe.
+
+[TIP]
+====
+Para controlar a escrita a atributos em uma classe, é preciso associar descritores à classe da classe—em outras palavras, à metaclasse. Por default, a metaclasse de classes definidas pelo usuário é `type`, e não podemos acrescentar atributos a `type`. Mas, no <>, vamos criar nossas próprias metaclasses.
+====
+
+Vamos ver agora como descritores são usados para implementar métodos no Python.((("", startref="ADovervnon23")))((("", startref="nonover23")))((("", startref="overrid23")))
+
+[[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 (_bound method_) 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]]
+.Um método é um descritor não dominante
+====
+[source, python]
+----
+include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO5]
+----
+====
+<1> Ler de `obj.spam` obtém um objeto método vinculado.
+<2> Mas ler de `Managed.spam` obtém uma função.
+<3> Atribuir um valor a `obj.spam` oculta o atributo de classe, tornando o método `spam` inacessível a partir da instância `obj`.
+
+Funções não implementam `+__set__+`, portanto não são descritores dominantes, como mostra a última linha do <>.
+
+A outra lição fundamental do <> é que `obj.spam` e
+`Managed.spam` devolvem objetos diferentes. Como de hábito com descritores, o
+`+__get__+` de uma função devolve uma referência para a própria função quando o
+acesso ocorre através da classe gerenciada. Mas quando o acesso vem através da
+instância, o `+__get__+` da função devolve um método vinculado: um invocável que
+embrulha a função e vincula a instância gerenciada (no exemplo, `obj`) ao
+primeiro argumento da função (isto é, `self`), como faz a função
+`functools.partial`, que vimos na https://fpy.li/cv[«Seção 7.8.2»] (vol.1).
+Para um entendimento mais profundo deste mecanismo, dê uma olhada no
+<>.
+
+[[func_descriptor_ex]]
+.method_is_descriptor.py: uma classe `Text`, derivada de `UserString`
+====
+[source, python]
+----
+include::../code/23-descriptor/method_is_descriptor.py[tags=FUNC_DESCRIPTOR_EX]
+----
+====
+
+Vamos então investigar o método `Text.reverse`. Veja o <>.
+
+[[func_descriptor_demo]]
+.Experimentos com um método
+====
+[source, python]
+----
+include::../code/23-descriptor/method_is_descriptor.py[tags=FUNC_DESCRIPTOR_DEMO]
+----
+====
+
+<1> Seguindo a convenção, o `repr` de uma instância de `Text` parece uma chamada ao construtor
+de `Text` que criaria uma instância igual.
+
+<2> O método `reverse` devolve o texto escrito de trás para frente.
+
+<3> Um método invocado na classe funciona como uma função.
+
+<4> Observe os tipos diferentes: `function` e `method`.
+
+<5> `Text.reverse` opera como uma função, mesmo ao trabalhar com objetos que não
+são instâncias de `Text`.
+
+<6> Toda função é um descritor não dominante. Invocar seu `+__get__+` com uma
+instância obtém um método vinculado àquela instância.
+
+<7> Invocar o `+__get__+` da função com `None` como argumento `instance` obtém a
+própria função.
+
+<8> A expressão `word.reverse` invoca `+Text.reverse.__get__(word)+`,
+devolvendo o método vinculado, mas sem invocá-lo.
+
+<9> O objeto método vinculado tem um atributo `+__self__+`, contendo uma
+referência à instância na qual o método foi invocado.
+
+<10> O atributo `+__func__+` do método vinculado é uma referência à função
+original, implementada na classe gerenciada.
+
+<<<
+O método vinculado contém um método `+__call__+`, que trata a invocação em si. Este método chama a função original, referenciada em `+__func__+`, passando o atributo `+__self__+` do método como primeiro argumento. É assim que funciona a vinculação automática do argumento `self` convencional.
+
+A conversão de funções em métodos vinculados é um exemplo perfeito de como descritores são usados na infraestrutura da linguagem.
+
+Após este mergulho profundo no funcionamento de descritores e métodos, vamos repassar alguns conselhos práticos sobre seu uso.((("", startref="ADmethodsas23")))
+
+[[descriptor_usage_sec]]
+=== Dicas para usar descritores
+
+A((("attribute descriptors", "descriptor usage tips", id="ADtips23"))) lista a seguir trata de algumas consequências práticas das características dos descritores descritas acima:
+
+Use `property` para não complicar demais:: A classe embutida `property` cria descritores dominantes, implementando `+__set__+` e `+__get__+`, mesmo se um método _setter_ não for definido.footnote:[Um método `+__delete__+` também é fornecido pelo decorador `property`, mesmo se você não definir um método _deleter_ (de exclusão).]
+O `+__set__+` default de uma propriedade gera um `AttributeError: can't set attribute` (não é permitido setar o atributo), então uma propriedade é a forma mais fácil de criar um atributo somente para leitura, evitando o problema descrito a seguir.
+
+Descritores somente para leitura exigem um `+__set__+`:: Se você usar uma classe descritora para implementar um atributo somente para leitura, precisa lembrar de programar tanto `+__get__+` quanto
+`+__set__+`. Caso contrário, você terá um descritor não-dominante, e setar um atributo com o mesmo nome em uma instância vai ocultar o descritor. O método `+__set__+` de um atributo somente para leitura deve apenas levantar `AttributeError` com uma mensagem adequada.footnote:[Python não é consistente nestas mensagens. Tentar modificar o atributo `c.real` de um número `complex` resulta em um `AttributeError: readonly attribute` (atributo somente para leitura), mas uma tentativa de mudar
+`c.conjugate` (um método de `complex`) levanta um `AttributeError: 'complex' object attribute 'conjugate' is read-only` (o atributo 'conjugate' do objeto 'complex' é somente para leitura). Até "read-only" está escrito de maneira diferente nas mensagens em inglês).]
+
+Descritores de validação podem funcionar apenas com `+__set__+`:: Em um descritor projetado apenas para validação, o método `+__set__+` deve verificar o argumento `value` recebido e, se ele for válido, atualizar o `+__dict__+` da instância diretamente, usando o nome da instância do descritor como chave. Assim, ler o atributo de mesmo nome a partir da instância será tão rápido quanto possível, pois não vai precisar de um `+__get__+`. Veja o <>.
+
+Um _cache_ eficiente pode ser feito só com `+__get__+`:: Implementando só o método `+__get__+`, você cria um descritor não dominante.
+Eles são úteis para executar alguma computação custosa e então armazenar o resultado, definindo um atributo com o mesmo nome na instânciafootnote:[Entretanto, lembre-se de que criar atributos de instância após o método `+__init__+` frustra a otimização de memória através de compartilhamento de chaves, como discutido na https://fpy.li/82[«Seção 3.9»] (vol.1).].
+O atributo de mesmo nome na instância vai ocultar o descritor, daí acessos subsequentes àquele atributo vão buscá-lo diretamente no `+__dict__+` da instância, sem acionar mais o `+__get__+` do descritor.
+O decorador `@functools.cached_property` constrói um descritor não dominante.
+
+Métodos não especiais podem 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 `meu_obj.o_método = 7`, significa que acessos posteriores a `o_método` através daquela instância irão obter o número ++7++—sem afetar a classe ou outras instâncias. Isto se aplica aos métodos especiais. O interpretador só acessa 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 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 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.
+
+[NOTE]
+====
+A classe `FrozenJSON` no <> está a salvo de atributos de instância ocultando métodos, pois seus únicos métodos são métodos especiais e o método de classe `build`. Métodos de classe são seguros desde que sejam sempre acessados através da classe, como fiz com `FrozenJSON.build` no <>—mais tarde substituído por `+__new__+` no <>. As classes `Record` e `Event`, apresentadas na <>, também estão a salvo: elas implementam apenas métodos especiais, métodos estáticos e propriedades. Propriedades são descritores dominantes, então não são ocultadas por atributos de instância.
+====
+
+Para encerrar esse capítulo, vamos falar de dois recursos que vimos com as propriedades, mas não no contexto dos descritores: documentação e o tratamento de tentativas de excluir um atributo gerenciado.((("", startref="ADtips23")))
+
+[[descriptor_doc_del_sec]]
+=== Docstrings e exclusão de descritores
+
+[[descriptor_help_screens]]
+.Capturas de tela do console de Python após os comandos `help(LineItem.weight)` e `help(LineItem)`.
+image::../images/flpy_2304.png[Capturas de tela do console de Python com a ajuda de descritores.]
+
+A((("attribute descriptors", "descriptor docstring and overriding deletion"))) docstring de uma classe descritora é usada para documentar todas as instâncias do descritor na classe gerenciada.
+A <> mostra as telas de ajuda para a classe `LineItem` com os descritores `Quantity` e `NonBlank`, do
+<> e do <>.
+
+Isso é um tanto insatisfatório. No caso de `LineItem`, seria bom acrescentar, por exemplo, a informação de que `weight` deve ser expresso em quilogramas. Isso seria trivial com propriedades, pois cada propriedade controla um atributo gerenciado específico. Mas com descritores, a mesma classe descritora `Quantity` é usada para `weight` e `price`.footnote:[Customizar o texto de ajuda para cada instância do descritor é surpreendentemente difícil. Uma solução exige criar dinamicamente uma classe invólucro (_wrapper_) para cada instância do descritor.]
+
+O segundo detalhe que discutimos com propriedades, mas não com descritores, é o
+tratamento de tentativas de apagar um atributo gerenciado. Isso pode ser feito
+implementando um método `+__delete__+` na classe descritora. Omiti
+deliberadamente falar de `+__delete__+`, porque seu uso no mundo
+real é raro. Se você precisar disso, por favor consulte a seção
+https://fpy.li/c7[«Implementando descritores»] na documentação do
+_Modelo de dados de Python_. Escrever um exemplo com uma classe descritora
+boba com `+__delete__+` fica como exercício para o leitor com excesso de tempo livre.
+
+
+
+=== Resumo do capítulo
+
+O((("attribute descriptors", "overview of"))) primeiro exemplo deste capítulo foi uma continuação dos exemplos `LineItem` do <>. No <>, substituímos propriedades por descritores. Vimos que um descritor é uma classe que fornece instâncias instaladas como atributos na classe gerenciada. Discutir esse mecanismo exigiu uma terminologia especial, apresentando termos como _instância gerenciada_ e _atributo de armazenamento_.
+
+Na <>, removemos a exigência de descritores `Quantity` serem
+instanciados com um `storage_name` explícito. A solução foi implementar o método especial `+__set_name__+` em
+`Quantity`, para preservar o nome da propriedade gerenciada como
+`self.storage_name`.
+
+A <> mostrou como criar uma subclasse de uma classe descritora abstrata, para compartilhar código ao programar descritores especializados com alguma funcionalidade em comum.
+
+Examinamos então os comportamentos diferentes de descritores, fornecendo ou omitindo o método
+`+__set__+`, criando uma distinção fundamental entre descritores dominantes e não dominantes. Por meio de testes detalhados, revelamos quando os descritores estão no controle, e quando são ocultados, contornados ou sobrescritos.
+
+Em seguida, estudamos uma categoria específica de descritores não dominantes: métodos.
+Experimentos no console revelaram como uma função associada a uma classe se torna um método ao ser acessada através de uma instância, graças ao protocolo de descritores.
+
+Para concluir o capítulo, a <> trouxe dicas práticas, e a <> forneceu um rápido olhar sobre como documentar descritores.
+
+[NOTE]
+====
+Como observado na <>, vários exemplos deste capítulo se tornaram mais simples graças ao método especial `+__set_name__+` do protocolo de descritor, adicionado no Python 3.6. Isso é evolução da linguagem!
+====
+
+=== Para saber mais
+
+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[«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 fez a apresentação
+_Python's Object Model_ (O Modelo de Objetos do Python), tratando com
+profundidade de propriedades e descritores: https://fpy.li/23-5[«slides»] e
+https://fpy.li/23-6[«video»].
+
+[WARNING]
+====
+Cuidado, qualquer tratamento de descritores escrito ou gravado antes da PEP 487 ser adotada, em 2016, corre o risco de conter exemplos desnecessariamente complicados hoje, pois `+__set_name__+` não era suportado nas versões de Python anteriores a 3.6.
+====
+
+Para mais exemplos práticos, o _Python Cookbook_, 3ª ed., de David Beazley e
+Brian K. Jones (O’Reilly), traz muitas receitas ilustrando descritores, entre
+as quais destaco
+_6.12. Reading Nested and Variable-Sized Binary Structures_
+(Lendo Estruturas Binárias Aninhadas e de Tamanho Variável),
+_8.10. Using Lazily Computed Properties_
+(Usando Propriedades Computadas de Forma Preguiçosa),
+_8.13. Implementing a Data Model or Type System_
+(Implementando um Modelo de Dados ou um Sistema de Tipos) e
+_9.9. Defining Decorators As Classes_ 
+(Definindo Decoradores como Classes).
+Essa última receita trata das questões profundas envolvidas na interação entre
+decoradores de função, descritores e métodos, e de como um decorador de função
+implementado como uma classe, com `+__call__+`, também precisa implementar
+`+__get__+` se quiser funcionar com métodos.
+
+<<<
+A https://fpy.li/pep487[_PEP 487—Simpler customization of class creation_]
+(Customização simplificada da criação de classes)
+introduziu o método especial `+__set_name__+` e inclui um exemplo de um
+https://fpy.li/23-7[«descritor de validação»].
+
+//[role="pagebreak-before"]
+.Ponto de vista
+****
+
+
+*O design do parâmetro `self`*
+
+A((("attribute descriptors", "Soapbox discussion")))((("Soapbox sidebars",
+"explicit self argument")))((("explicit self argument"))) exigência de declarar
+`self` explicitamente como o primeiro parâmetro em métodos foi uma decisão de
+design controversa no Python. Eu me acostumei em menos de 20 anos!
+Esta decisão é um exemplo de "pior é melhor"
+(_worse is better_): a filosofia de design descrita pelo cientista da
+computação Richard P. Gabriel em
+https://fpy.li/23-8[_The Rise of Worse is Better_]
+(A Ascensão do Pior é Melhor). A primeira prioridade dessa filosofia
+é "simplicidade", que Gabriel apresenta assim:
+
+[quote]
+____
+O design deve ser simples, tanto na implementação quanto na interface.
+É mais importante simplificar a implementação do que a interface.
+A simplicidade é a consideração mais importante em um design.
+____
+
+O `self` explícito de Python incorpora esta filosofia de design.
+A implementação é simples—até mesmo elegante—em prejuízo da usabilidade:
+uma assinatura de método como `def zfill(self, width):` não corresponde, visualmente, à invocação `label.zfill(8)`.
+
+A linguagem Modula-3, que Guido estudou antes de inventar o Python, introduziu esta convenção com o mesmo identificador, `self`.
+Mas há uma diferença crucial: em Modula-3, interfaces são declaradas separadamente de sua implementação,
+e na declaração da interface o argumento `self` é omitido.
+Então, da perspectiva do usuário, um método aparece em uma declaração de interface com a mesma quantidade de parâmetros necessários para invocá-lo.
+
+Ao longo do tempo, as mensagens de erro de Python relacionadas a argumentos de métodos se tornaram mais claras.
+Em um método definido pelo usuário com um argumento além de `self`, se o usuário invocasse
+`obj.meth()`, Python 2.7 gerava:
+
+[source]
+----
+TypeError: meth() takes exactly 2 arguments (1 given)
+(meth() recebe exatamente 2 argumentos (1 passado))
+----
+
+No Python 3, a confusa contagem de argumentos não é mencionada, mas o argumento ausente é nomeado:
+
+[source]
+----
+TypeError: meth() missing 1 required positional argument: 'x'
+(1 argumento posicional obrigatório faltando em meth(): 'x')
+----
+
+Além do uso de `self` como um argumento explícito, a exigência de qualificar
+cada acesso a atributos de instância com `self` também é criticada. Veja, por
+exemplo, o famoso post _Python Warts_ (Verrugas de Python) de A. M.
+Kuchling (https://fpy.li/23-9[«cópia arquivada»] no _Internet Archive_);
+o próprio Kuchling não se incomoda
+muito com o qualificador `self`, mas ele o menciona—provavelmente ecoando
+opiniões do grupo _comp.lang.python_. Pessoalmente não me importo em digitar o
+qualificador `self`: é bom para distinguir variáveis locais de atributos. Minha
+questão é com o uso de `self` na instrução `def`.
+
+Quem estiver triste com o `self` explícito de Python pode se sentir bem melhor após considerar a
+https://fpy.li/23-10[«semântica desconcertante»] do `this` implícito em JavaScript.
+Guido teve boas razões para fazer `self` funcionar como funciona, e ele escreveu sobre elas em
+https://fpy.li/23-11[_Adding Support for User-Defined Classes_]
+(Adicionando Suporte a Classes Definidas pelo Usuário), 
+em seu blog, _The History of Python_ (A História de Python).
+****
+
diff --git a/vol3/cap24.adoc b/vol3/cap24.adoc
new file mode 100644
index 00000000..05769e0a
--- /dev/null
+++ b/vol3/cap24.adoc
@@ -0,0 +1,1875 @@
+[[ch_class_metaprog]]
+== Metaprogramação de classes
+:example-number: 0
+:figure-number: 0
+
+[quote, Brian W. Kernighan e P. J. Plauger, The Elements of Programming Style]
+____
+Todo mundo sabe que depurar um programa é duas vezes mais difícil que escrever o mesmo programa.
+Mas daí, se você der tudo de si ao escrever o programa, como vai conseguir depurá-lo?footnote:[Citação extraída do capítulo 2, _Expression_ (Expressão), página 10, de _The Elements of Programming Style, Second Edition (NT: "Elementos de Estilo de Programação"; não encontramos edição traduzida deste livro.)]
+____
+
+Metaprogramação((("class metaprogramming", "benefits and drawbacks of"))) de classes é a arte de criar ou customizar classes durante a execução do programa.
+Em Python, classes são objetos de primeira classe, então uma função pode criar uma nova classe a qualquer momento, sem usar a palavra-chave `class`,
+e sem manipular código-fonte ou bytecodes.
+
+Decoradores de classes também são funções, mas servem para inspecionar, modificar ou até substituir a classe decorada por outra classe.
+Por fim, metaclasses são a ferramenta mais avançada para metaprogramação de classes: elas permitem a criação de categorias de classes inteiramente novas, com características especiais, como as classes base abstratas, que já vimos antes.
+
+Metaclasses são poderosas, mas difíceis de justificar na prática, e ainda mais difíceis de entender direito. Decoradores de classe resolvem muitos dos mesmos problemas, e são mais fáceis de compreender. Além disso, Python 3.6 implementou a
+https://fpy.li/pep487[_PEP 487—Simpler customization of class creation_]
+(Customização simplificada da criação de classes), fornecendo métodos especiais para tarefas que antes exigiam metaclasses ou decoradores de classe.footnote:[Isso não quer dizer que a PEP 487 quebrou código que usava aqueles recursos, mas apenas que parte do código que utilizava decoradores de classe ou metaclasses antes de Python 3.6 pode agora ser refatorado para usar classes comuns, resultando em um código mais simples e possivelmente mais eficiente.]
+
+Este capítulo apresenta as técnicas de metaprogramação de classes em ordem ascendente de complexidade.
+
+
+[WARNING]
+====
+Esse é um tópico empolgante, e é fácil se deixar levar pelo entusiasmo.
+Então preciso deixar aqui esse conselho.
+
+Em nome da legibilidade e facilidade de manutenção, você provavelmente deveria evitar as técnicas descritas neste capítulo em aplicações.
+
+Por outro lado, caso queira escrever o próximo grande framework de Python, estas são suas ferramentas de trabalho.
+====
+
+=== Novidades neste capítulo
+
+Todo((("class metaprogramming", "significant changes to"))) o código do capítulo _Metaprogramação de Classes_ da primeira edição do _Python Fluente_ ainda funciona corretamente.
+Entretanto, alguns dos exemplos antigos podem ser bastante simplificados com recursos surgidos desde o Python 3.6.
+
+Substituí aqueles exemplos por outros, enfatizando os novos recursos de metaprogramação ou acrescentando novos requisitos para justificar o uso de técnicas mais avançadas.
+Alguns destes exemplos novos se valem de dicas de tipo para fornecer fábricas de classes similares ao decorador `@dataclass` e a `typing.NamedTuple`.
+
+A <> é nova, trazendo algumas considerações de alto nível sobre a aplicabilidade das metaclasses.
+
+[TIP]
+====
+Algumas das melhores refatorações tratam de remover código tornado redundante por formas modernas e mais simples de resolver o mesmo problema.
+Isso se aplica tanto a código em produção quanto 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_sec]]
+=== Classes como objetos
+
+Como((("class metaprogramming", "classes as objects"))) acontece com a maioria
+dos elementos da linguagem Python, classes também são objetos. Toda classe
+tem alguns atributos definidos no _Modelo de Dados_ de Python, documentados na
+seção https://fpy.li/bt[_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__+`, e `+__mro__+`. Outros atributos de classe padrão são:
+
+`+cls.__bases__+`:: A((("cls.__bases__"))) tupla de classes base da classe.
+
+`+cls.__qualname__+`:: O((("cls.__qualname__"))) nome qualificado de uma classe ou função, que é um caminho pontuado, desde o escopo global do módulo até a definição da classe. Isso é relevante quando a classe é definida dentro de outra classe.
+Por exemplo, na classe https://fpy.li/24-2[`Ox`] que ilustra um modelo na da documentação do Django, há uma classe aninhada chamada `Meta`. O `+__qualname__+` de `Meta` é `Ox.Meta`, mas seu `+__name__+` é apenas `Meta`.
+A especificação para este atributo está na
+https://fpy.li/24-3[_PEP 3155—Qualified name for classes and functions_] (Nome qualificado para classes e funções).
+
+`+cls.__subclasses__()+`:: Este((("cls.__subclasses__()"))) método devolve uma lista das subclasses imediatas da classe.
+A implementação usa referências fracas, para evitar referências circulares entre a superclasse e suas subclasses.
+Cada subclasse tem referências fortes para suas superclasses em seu atributo `+__bases__+`.
+O método lista as subclasses na memória naquele momento. Subclasses em módulos ainda não importados não aparecerão no resultado.
+
+`cls.mro()`:: O((("cls.mro()"))) interpretador invoca este método quando está criando uma classe, para obter a tupla de superclasses armazenada no atributo `+__mro__+` da classe.
+Uma metaclasse pode  sobrescrever este método, para customizar a ordem de resolução de métodos da classe em construção.
+
+[TIP]
+====
+Nenhum dos atributos mencionados nesta seção aparece na lista devolvida pela função `dir(…)`.
+====
+
+Agora, se uma classe é um objeto, o que é a classe de uma classe?
+
+<<<
+=== `type`: a fábrica de classes embutida
+
+Normalmente((("class metaprogramming", "built-in class factory")))
+pensamos em `type` como uma função que devolve a classe de um objeto,
+porque é isso que `type(my_object)` faz: devolve `+my_object.__class__+`.
+
+Entretanto, `type` é uma classe que cria uma nova classe, quando invocada com três argumentos.
+Considere esta classe simples:
+
+[source, python]
+----
+class MyClass(MyMixin, MySuperClass):
+    x = 42
+
+    def x2(self):
+        return self.x * 2
+----
+
+Usando o construtor `type`, podemos criar uma classe idêntica durante a execução, com o seguinte código:
+
+[source, python]
+----
+MyClass = type('MyClass',
+               (MyMixin, MySuperClass),
+               {'x': 42, 'x2': lambda self: self.x * 2},
+          )
+----
+
+Quando Python lê uma instrução `class`, invoca `type` para construir um objeto classe assim:
+
+[source, python]
+----
+new_class = type(name, bases, cls_dict)
+----
+
+Onde os parâmetros são:
+
+`name`::
+    O identificador que aparece após a palavra-chave `class`, por exemplo, `MyClass`.
+`bases`::
+    A tupla de superclasses passada entre parênteses após o identificador da classe, ou
+    `(object,)`, caso nenhuma superclasse seja mencionada na instrução `class`.
+`cls_dict`::
+    Um mapeamento entre nomes de atributos e valores.
+    Invocáveis se tornam métodos, como vimos na <>.
+
+[NOTE]
+====
+
+O construtor `type` aceita argumentos nomeados opcionais, que são ignorados por
+`type`, mas são passados para `+__init_subclass__+`, que deve
+consumí-los. Vamos estudar esse método especial na
+<>, mas tratarei o uso de argumentos
+nomeados. Para saber mais sobre isso, por favor leia a
+https://fpy.li/pep487[_PEP 487_] sobre formas modernas de customizar a criação de classes.
+
+====
+
+A classe `type` é uma((("metaclasses", "definition of term"))) _metaclasse_: uma classe que cria classes.
+Em outras palavras, instâncias da classe `type` são classes.
+A biblioteca padrão contém algumas outras metaclasses, mas `type` é a  default:
+
+[source, python]
+----
+>>> type(7)
+
+>>> type(int)
+
+>>> type(OSError)
+
+>>> class Whatever:
+...     pass
+...
+>>> type(Whatever)
+
+----
+
+Vamos criar metaclasses customizadas na <>.
+Agora, vamos usar a classe embutida `type` para criar uma função que constrói classes.
+
+
+=== Uma função fábrica de classes
+
+A((("class metaprogramming", "class factory function", id="CMfacfun24"))) biblioteca padrão contém uma função fábrica de classes que já apareceu várias vezes aqui: `collections.namedtuple`.
+No https://fpy.li/5[«Capítulo 5»] (vol.1) também vimos `typing.NamedTuple` e `@dataclass`.
+Estas fábricas de classe usam técnicas que veremos neste capítulo.
+
+Vamos começar com uma fábrica muito simples, para classes de objetos mutáveis—a substituta mais simples possível de `@dataclass`.
+
+Suponha que eu esteja escrevendo uma aplicação para um _pet shop_,
+e queira armazenar dados sobre cães como registros simples.
+Mas não quero escrever código padronizado como esse:
+
+[source, python]
+----
+class Dog:
+    def __init__(self, name, weight, owner):
+        self.name = name
+        self.weight = weight
+        self.owner = owner
+----
+
+Tédio: `name`, `name`, `name`, `weight`, `weight`, `weight`... E ainda falta um bom `repr`:
+
+[source, python]
+----
+>>> rex = Dog('Rex', 30, 'Bob')
+>>> rex
+<__main__.Dog object at 0x2865bac>
+----
+
+Inspirados por `collections.namedtuple`, criaremos uma `record_factory`,
+que cria classes simples como `Dog`. O <> mostra como ela deve funcionar.
+
+[[record_factory_demo]]
+.Testando `record_factory`, uma fábrica de classes simples
+====
+[source, python]
+----
+include::../code/24-class-metaprog/factories.py[tags=RECORD_FACTORY_DEMO]
+----
+====
+<1> A fábrica pode ser invocada como `namedtuple`: nome da classe, seguido de uma string contendo os nomes dos atributos separados por espaços.
+<2> Um `repr` agradável.
+<3> Instâncias são iteráveis, então elas podem ser convenientemente desempacotadas em uma atribuição...
+<4> ...ou quando são passadas para funções como `format`.
+<5> As instâncias são mutáveis.
+<6> A classe recém-criada herda de `object`—não tem relação com nossa fábrica.
+
+O código para `record_factory` está no <>.footnote:[Agradeço ao meu amigo J. S. O. Bueno por ter contribuído com esse exemplo.]
+
+[[record_factory_ex]]
+.record_factory.py: uma classe fábrica simples
+====
+[source, python]
+----
+include::../code/24-class-metaprog/factories.py[tags=RECORD_FACTORY]
+----
+====
+<1> O usuário pode fornecer os nomes dos campos como uma string única ou como um iterável de strings.
+<2> Aceita argumentos como os dois primeiros de `collections.namedtuple`; devolve `type`—isto é, uma classe.
+<3> Cria uma tupla de nomes que será o `+__slots__+` da nova classe.
+<4> Esta função se tornará o método `+__init__+` na nova classe. Ela aceita argumentos posicionais e/ou nomeados.footnote:[Não acrescentei dicas de tipo aos argumentos porque os tipos reais são `Any`. Escrevi a dica do tipo devolvido (`None`) para que o Mypy não deixe de checar o método.]
+<5> Produz os valores dos campos na ordem dada por `+__slots__+`.
+<6> Produz um `repr` agradável, iterando sobre `+__slots__+` e `self`.
+<7> Monta um dicionário de atributos de classe.
+<8> Cria e devolve a nova classe, invocando o construtor de `type`.
+<9> Converte `names` separados por espaços ou vírgulas em uma lista de `str`.
+
+O <> é a primeira vez que vemos `type` em uma dica de tipo:
+indica que `record_factory` devolve uma classe.
+
+A última linha de `record_factory` no <> cria uma classe cujo
+nome é o valor de `cls_name`, com `object` como sua única classe base imediata,
+e um espaço de nomes (_namespace_) carregado com `+__slots__+` e os
+métodos de instância `+__init__+`, `+__iter__+`, e
+`+__repr__+`.
+
+Poderíamos ter dado qualquer outro nome ao atributo de classe `+__slots__+`, mas
+daí teríamos que implementar `+__setattr__+` para validar os nomes dos atributos
+em uma atribuição, porque em nossas classes similares a registros queremos que o
+conjunto de atributos seja sempre o mesmo e na mesma ordem. Entretanto,
+lembre-se de que a principal característica de `+__slots__+` é economizar memória
+quando estamos lidando com milhões de instâncias, e que usar `+__slots__+` traz
+algumas desvantagens, discutidas na https://fpy.li/28[«Seção 11.11»] (vol.2).
+
+[WARNING]
+====
+
+Instâncias de classes criadas por `record_factory` não são serializáveis—isto
+é, elas não podem ser exportadas em formato binário pela função `dump` do módulo `pickle`.
+Resolver este problema está além do escopo deste exemplo, cujo 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 a instrução `class`, e a enriquece automaticamente com mais funcionalidade.((("", startref="CMfacfun24")))
+
+
+[[enhancing_with_init_subclass_sec]]
+=== 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_].
+Falamos pela primeira vez do método especial para descritores `+__set_name__+` na <>.
+Agora vamos estudar `+__init_subclass__+`.
+
+No https://fpy.li/5[«Capítulo 5»] (vol.1), vimos como `typing.NamedTuple` e `@dataclass` usam a sintaxe de `class` para definir atributos em uma
+classe, que então é enriquecida com métodos úteis, como `+__init__+`, `+__repr__+`, `+__eq__+`,
+etc.
+
+Ambas as fábricas de classes leem as dicas de tipo na instrução `class` do usuário para enriquecer a classe. Estas dicas de tipo também permitem que checadores de tipos estáticos validem código que define ou lê aqueles atributos.
+Entretanto, `NamedTuple` e `@dataclass` não usam as dicas de tipo para validação de atributos durante a execução.
+A classe `Checked`, no próximo exemplo, faz isso.
+
+<<<
+
+[NOTE]
+====
+
+Não é possível suportar toda dica de tipo estática concebível para checagem de
+tipos durante a execução. Entretanto, alguns tipos que são também classes
+concretas podem ser usados com `Checked`. Isto inclui tipos simples, usados com
+frequência para o conteúdo de campos, como  `str`, `int`, `float`, e `bool`, bem
+como listas destes tipos.
+
+====
+
+O <> mostra como usar `Checked` para criar uma classe `Movie`.
+
+[[checked_demo1_ex]]
+.initsub/checkedlib.py: doctest para a criação de uma subclasse `Movie` de `Checked`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=MOVIE_DEFINITION]
+----
+====
+<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.
+
+
+Os construtores usados como dicas de tipo podem ser qualquer invocável que receba zero ou um argumento, e devolva um valor adequado ao tipo do campo pretendido ou rejeite o argumento, gerando um `TypeError` ou um `ValueError`.
+
+<<<
+Usar tipos embutidos para as anotações no <> significa que os valores devem ser aceitáveis pelo construtor do tipo.
+Para `int`, isso significa qualquer `x` tal que `int(x)` devolva um `int`.
+Para `str`, qualquer coisa serve durante a execução, pois `str(x)` funciona com qualquer `x` no
+Python.footnote:[Isso é verdade para qualquer objeto, exceto quando sua classe sobrescreve os métodos `+__str__+` ou `+__repr__+`, herdados de `object`, por uma implementação que não funcione.]
+
+Quando chamado sem argumentos, o construtor deve devolver um valor default de seu tipo.footnote:[Essa solução evita usar `None` como default. Evitar valores nulos é uma https://fpy.li/24-5[boa ideia]. Em geral, eles são difíceis de evitar, mas em alguns casos isso é fácil. Tanto no Python quanto no SQL, prefiro representar dados ausentes em um campo de texto como uma string vazia em vez de `None` ou `NULL`. Aprender Go reforçou essa ideia: em Go, variáveis e campos struct de tipos primitivos são inicializados por default com um "valor zero" (_zero value_). Se você estiver curiosa, veja a página https://fpy.li/24-6["Zero values" ("Valores zero")] no _Tour of Go_ ("Tour do Go") online]
+
+Esse é o comportamento padrão de construtores embutidos no Python:
+
+[source, python]
+----
+>>> int(), float(), bool(), str(), list(), dict(), set()
+(0, 0.0, False, '', [], {}, set())
+----
+
+Em uma subclasse de `Checked` como `Movie`, parâmetros ausentes criam instâncias com os valores default devolvidos pelos construtores dos campos. Por exemplo:
+
+[source, python]
+----
+include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=MOVIE_DEFAULTS]
+----
+
+Os construtores são usados para validação durante a instanciação, e quando um atributo é definido diretamente em uma instância:
+
+[source, python]
+----
+include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=MOVIE_TYPE_VALIDATION]
+----
+
+<<<
+.Subclasses de `Checked` e a verificação estática de tipos
+[WARNING]
+====
+Em um arquivo de código-fonte _.py_ contendo uma instância `movie` da classe `Movie`, como definida no <>, o Mypy marca essa atribuição como um erro de tipo:
+
+[source, python]
+----
+movie.year = 'MCMLXXII'
+----
+
+Entretanto, o Mypy não consegue detectar erros de tipo nesta chamada ao construtor:
+
+[source, python]
+----
+megahit = Movie(title='Avatar', year='MMIX')
+----
+
+Isso porque `Movie` herda `+Checked.__init__+`,
+e a assinatura daquele método deve aceitar qualquer argumento nomeado, para suportar classes arbitrárias definidas pelo usuário.
+
+Por outro lado, se você declarar um campo de uma subclasse de `Checked` com a dica de tipo
+`list[float]`, o Mypy pode sinalizar atribuições de listas com tipos incompatíveis, mas `Checked` vai ignorar o parâmetro de tipo e tratá-lo como igual a `list`.
+====
+
+Vamos ver agora a implementação de _checkedlib.py_.
+A primeira classe é o descritor `Field`, como mostra o <>.
+
+[[checked_field_ex]]
+.initsub/checkedlib.py: a classe descritora `Field`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=CHECKED_FIELD]
+----
+====
+<1> Lembre-se, desde o Python 3.9, o tipo `Callable` para anotações é a ABC em
+`collections.abc`, e não `typing.Callable` que foi descontinuado.
+<2> Essa é a dica de tipo mínima para `Callable` mínima; o parâmetro de tipo e o tipo devolvido são implicitamente `Any`.
+<3> Para checagem durante a execução, usamos o embutido `callable`.footnote:[Na minha opinião, `callable` deveria se tornar adequado para dicas de tipo. Em 6 de maio de 2021, quando essa nota foi escrita, essa ainda era uma https://fpy.li/24-7[questão aberta].] O teste contra `type(None)` é necessário porque Python entende `None` em um tipo como `NoneType`, a classe de `None` (e portanto invocável), mas esse é um construtor inútil, que apenas devolve `None`.
+<4> Se `+Checked.__init__+` definir `value` como `\...` (o objeto embutido `Ellipsis`), invocamos o construtor sem argumentos.
+<5> Caso contrário, invocamos o `constructor` com o `value` fornecido.
+<6> Se `constructor` levantar uma destas exceções, levantamos um `TypeError` com uma mensagem útil, incluindo os nomes do campo e do construtor; por exemplo, `'MMIX' não é compatível com year:int`.
+<7> Se nenhuma exceção for levantada, o `value` é armazenado no `+instance.__dict__+`.
+
+Em `+__set__+`, precisamos capturar `TypeError` e `ValueError`, pois os construtores embutidos podem levantar qualquer uma ou outra, dependendo do argumento.
+Por exemplo, `float(None)` levanta `TypeError`, mas `float('A')` levanta `ValueError`.
+Por outro lado, `float('8')` não causa qualquer erro, e devolve `8.0`.
+Vamos combinar que, neste exemplo simples, este é um recurso, não um bug.
+
+<<<
+
+[TIP]
+====
+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 <>.
+====
+
+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 este exemplo.
+O restante dos métodos está no <>.
+
+[[ex_checked_class_top]]
+.initsub/checkedlib.py: os métodos mais importantes da classe `Checked`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=CHECKED_TOP]
+----
+====
+
+<1> Escrevi este método de classe para isolar 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 https://fpy.li/2a[«Seção 15.5.1»] (vol.2) para uma discussão dos problemas com
+essas funções.
+
+<2> `+__init_subclass__+` é chamado sempre que 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 a seguir a caixa _<>_.
+
+<3> `+super().__init_subclass__()+` não é estritamente necessário, mas é
+serve para suportar superclasses que implementem `+.__init_subclass__()+` na
+mesma árvore de herança. Veja a https://fpy.li/cw[«Seção 14.4»] (vol.2).
+
+<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...
+
+<7> ...obtém o `value` correspondente de `kwargs` e o remove de `kwargs`. Usar
+`\...` (o objeto `Ellipsis`) como default nos permite distinguir entre
+argumentos com valor `None` de argumentos ausentes.footnote:[Como mencionado em
+<>, o objeto `Ellipsis` é um valor sentinela conveniente e
+seguro. Ele existe no Python há muito tempo, mas recentemente mais usos têm sido
+encontrados para ele, como vemos nas dicas de tipo e no NumPy.]
+
+<8> Invocar `setattr` aciona `+Checked.__setattr__+`
+(<>).
+
+<9> Se houver itens remanescentes em `kwargs`, seus nomes não correspondem a
+qualquer dos campos declarados, e `+__init__+` vai falhar.
+
+<10> Este erro é informado por `+__flag_unknown_attrs+`, listado no
+<>. Ele recebe um argumento `*names` com os nomes de
+atributos desconhecidos. Usei um único asterisco em `*kwargs`, para passar suas
+chaves como uma sequência de argumentos, sem passar os valores.
+
+
+[[init_subclass_not_typical_box]]
+.`+__init_subclass__+` não é um método de classe típico
+****
+
+O primeiro argumento que Python passa para `+__init_subclass__+` é uma classe.
+Entretanto, esta não é a classe onde `+__init_subclass__+` está definido,
+mas sim uma subclasse que herda daquela classe.
+Este comportamento é diferente de `+__new__+` e de métodos de classe decorados com `@classmethod`,
+onde o primeiro argumento é sempre a própria classe.
+
+Então `+__init_subclass__+` não é um método de classe no sentido usual, e considero enganoso nomear seu primeiro argumento `cls`.
+Prefiro o nome `subcls`. A
+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..."
+
+****
+
+Vamos examinar os demais métodos da classe `Checked`
+iniciada do <>.
+Observe que prefixei os nomes dos métodos `+_fields+` e `+_asdict+` com `+_+`.
+A motivação foi a mesma da API de `collections.namedtuple`:
+reduzir a chance de colisões de nomes de métodos com os campos definidos pelo usuário.
+
+[[checked_class_bottom_ex]]
+.initsub/checkedlib.py: métodos restantes da classe `Checked`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=CHECKED_BOTTOM]
+----
+====
+<1> Intercepta qualquer tentativa de definir um atributo de instância. Isto é necessário para evitar a definição de um atributo desconhecido.
+
+<2> Se o `name` do atributo é conhecido, busca o `descriptor` correspondente.
+
+<3> Normalmente não é preciso invocar o `+__set__+` do descritor explicitamente. Aqui é necessário porque `+__setattr__+` intercepta todas as tentativas de definir um atributo em uma instância, mesmo na presença de um descritor dominante, tal como `Field`.
+
+<4> Caso contrário, o atributo `name` é desconhecido, e uma exceção será levantada por
+`+__flag_unknown_attrs+`.
+
+<5> Cria uma mensagem de erro útil, listando todos os argumentos inesperados, e levanta `AttributeError`.
+Este é um raro exemplo do tipo especial `NoReturn`, tratado na https://fpy.li/8f[«Seção 8.5.12»] (vol.1).
+
+<6> Cria um `dict` a partir dos atributos de um objeto `Movie`. Eu chamaria este método de
+`+_as_dict+`, mas segui a convenção iniciada com o método `+_asdict+` em `collections.namedtuple`.
+
+<7> Implementar um `+__repr__+` agradável é a principal razão para ter `_asdict` neste exemplo.
+
+O exemplo `Checked` mostra como tratar descritores dominantes ao implementar `+__setattr__+` para bloquear a definição arbitrária de atributos após a instanciação.
+É possível debater se vale a pena implementar `+__setattr__+` neste exemplo.
+Sem ele, definir `movie.director = 'Greta Gerwig'` funcionaria, mas o atributo `director` não seria verificado de forma alguma, não apareceria no `+__repr__+` nem seria incluído no `dict` devolvido por `+_asdict+`—ambos definidos no <>.
+
+Em _record_factory.py_ (no <>), solucionei essa questão usando o atributo de classe `+__slots__+`.
+Entretanto, essa solução mais simples não é viável aqui, como explicado a seguir.
+
+[[why_cannot_config_slots_sec]]
+==== Por que `+__init_subclass__+` não pode configurar `+__slots__+`?
+
+O((("__slots__"))) atributo `+__slots__+` só tem efeito quando é um dos itens do espaço de nomes da classe passado para `+type.__new__+`.
+Acrescentar `+__slots__+` a uma classe existente não funciona.
+Python invoca `+__init_subclass__+` apenas após a classe ser criada—neste ponto, é tarde demais para configurar `+__slots__+`.
+Um decorador de classes também não pode configurar `+__slots__+`, pois ele é aplicado ainda mais tarde que `+__init_subclass__+`.
+Vamos explorar essas questões de sincronia na <>.
+
+<<<
+Para configurar `+__slots__+` durante a execução, nosso código precisa criar o espaço de nomes da classe a ser passado como último argumento de `+type.__new__+`.
+Para fazer isso, podemos escrever uma função fábrica de classes, como _record_factory.py_, ou optar pelo caminho radical, e implementar uma metaclasse.
+Veremos como configurar `+__slots__+` dinamicamente na <>.
+
+Antes da https://fpy.li/pep487[_PEP 487_] simplificar a customização da criação de classes com
+`+__init_subclass__+`, no Python 3.7, uma funcionalidade similar só poderia ser implementada usando um decorador de classe.
+Pergunte-me como.((("", startref="initsub24")))((("", startref="CMinitsub24")))
+
+
+=== Um decorador de classes
+
+Um((("class metaprogramming", "enhancing classes with class decorators",
+id="CMcdecorator24")))((("decorators and closures", "enhancing classes with
+class decorators", id="DACcdeco24"))) decorador de classes tem comportamento
+semelhante a um decorador de funções: recebe uma classe decorada
+como argumento, e devolve uma classe para substituir a classe decorada.
+Decoradores de classe normalmente devolvem a própria classe decorada, após
+injetar novos métodos nela. Talvez, a razão
+mais comum para escolher um decorador de classes, em vez do 
+`+__init_subclass__+`, é evitar interferência com outros mecanismos de classes,
+como herança e metaclasses. Esta justificativa aparece no resumo da
+https://fpy.li/24-9[_PEP 557–Data Classes_] para explicar
+por que ela foi implementada como um decorador de classes.
+
+Nesta seção vamos estudar _checkeddeco.py_, que oferece a mesma funcionalidade de _checkedlib.py_, mas usando um decorador de classe.
+Como sempre, começamos examinando um exemplo de uso, extraído dos doctests em _checkeddeco.py_ (no <>).
+
+[[checkeddeco_demo1_ex]]
+.checkeddeco.py: criando uma classe `Movie` decorada com `@checked`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/decorator/checkeddeco.py[tags=MOVIE_DEFINITION]
+----
+====
+
+A única diferença entre o <> e o <> é a forma como a classe `Movie` é declarada: ela é decorada com `@checked` em vez de ser uma subclasse de `Checked`.
+Fora isso, o comportamento externo é o mesmo, incluindo a validação de tipo e a atribuição de valores default, apresentados após
+o <>, na <>.
+
+Vamos olhar agora para a implementação de _checkeddeco.py_.
+As importações e a classe `Field` são as mesmas de _checkedlib.py_, listadas no <>.
+Em _checkeddeco.py_ não há qualquer outra classe, apenas funções.
+
+A lógica antes implementada em `+__init_subclass__+` agora é parte da função `checked`—o decorador de classes listado no <>.
+
+[[checkeddeco_decorators_ex]]
+.checkeddeco.py: o decorador de classes
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/decorator/checkeddeco.py[tags=CHECKED_DECORATOR]
+----
+====
+
+<1> Lembre-se de que classes são instâncias de `type`. Estas dicas de tipo indicam
+que este é um decorador de classes: recebe uma classe e devolve
+uma classe.
+
+<2> `+_fields+` agora é uma função de alto nível definida mais tarde no módulo
+(<>).
+
+<3> Troca 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`.
+
+<7> Devolve a `cls` decorada, cumprindo o contrato básico de um decorador de
+classes.
+
+Todas as funções no primeiro nível de _checkeddeco.py_ estão prefixadas com um sublinhado, exceto o decorador `checked`.
+Essa convenção de nomenclatura faz sentido por duas razões:
+
+* `checked` é parte da interface pública do módulo _checkeddeco.py_, as outras funções não.
+* As funções no <> serão injetadas na classe decorada,
+e o `+_+` inicial reduz as chances de um conflito de nomes com atributos e métodos definidos pelo usuário na classe decorada.
+
+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 <>.
+
+Observe que a função `+_fields+` tem dois papéis em _checkeddeco.py_.
+Ela é usada como uma função normal na primeira linha do decorador `checked` e será também injetada como um método de classe na classe decorada.
+
+[[checkeddeco_methods_ex]]
+.checkeddeco.py: os métodos que serão injetados na classe decorada
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/decorator/checkeddeco.py[tags=CHECKED_METHODS]
+----
+====
+
+O módulo _checkeddeco.py_ implementa um decorador de classes simples, mas usável.
+O `@dataclass` de Python faz mais.
+Ele suporta várias opções de configuração, acrescenta métodos à classe decorada, trata ou avisa sobre conflitos com métodos definidos pelo usuário na classe decorada, e até percorre o `+__mro__+` para coletar atributos definidos pelo usuário declarados em superclasses da classe decorada.
+O https://fpy.li/24-10[«código-fonte»] do pacote `dataclasses` no Python 3.9 tem mais de 1200 linhas.
+
+Para fazer metaprogramação de classes, precisamos saber quando o interpretador Python avalia cada bloco de código durante a criação de uma classe.
+É disso que falaremos a seguir.((("", startref="DACcdeco24")))((("", startref="CMcdecorator24")))
+
+[[import_v_runtime_sec]]
+=== O que acontece quando: importação versus execução
+
+Programadores Python((("class metaprogramming", "import time versus runtime",
+id="CMimport24")))((("import time versus runtime"))) falam de "momento da
+importação" (_import time_) versus "momento de execução" (_run time_), mas estes
+termos não têm definições precisas e há uma zona cinzenta entre eles.
+
+No momento da importação, o interpretador:
+
+. Analisa o código-fonte do módulo _.py_ em uma passagem, da primeira até a última linha. É aqui que um `SyntaxError` pode ocorrer.
+. Compila o _bytecode_ a ser executado.
+. Executa o código no nível superior do módulo compilado.
+
+Se existir um arquivo _.pyc_ atualizado no `+__pycache__+` local,
+a análise e a compilação são omitidas, pois o _bytecode_ está pronto para ser executado.
+
+Apesar de a análise e a compilação serem definitivamente atividades de "importação",
+outras coisas podem acontecer durante este processo,
+pois quase todas as instruções no Python são executáveis,
+pois podem rodar código do usuário
+e modificar o estado do programa.
+
+Em especial, a instrução `import` não é meramente uma
+declaração, como é em Java, onde ela serve para informar
+o compilador sobre os pacotes necessários.
+Em Python, `import` serve para isso, mas também carrega e executa todo o código no
+nível superior de um módulo, quando ele é importado para o processo Python pela
+primeira vez. Importações posteriores do mesmo módulo usarão um _cache_, e então
+o único efeito será a vinculação dos objetos importados a nomes no módulo
+cliente. O código executado em consequência de um `import` pode fazer qualquer
+coisa, incluindo ações típicas da "execução", como escrever em um arquivo de log
+ou conectar-se a um banco de dados.footnote:[Não estou dizendo que é uma boa
+ideia abrir uma conexão com um banco de dados só porque o módulo foi importado,
+apenas apontando que isso pode ser feito.] Por isso a fronteira entre a
+"importação" e a "execução" é difusa: `import` pode acionar todo tipo de
+comportamento de "execução", porque a instrução `import` e a função embutida
+`+__import__()+` podem ser usadas dentro de qualquer função normal.
+
+Tudo isso é bastante abstrato e sutil, então vamos fazer alguns experimentos para ver o que acontece, e quando.
+
+
+[[evaldemo_sec]]
+==== Experimentos com as etapas de avaliação
+
+Considere um script _evaldemo.py_, que usa um decorador de classes, um descritor e uma fábrica de classes baseada em `+__init_subclass__+`, todos definidos em um módulo _builderlib.py_.
+Os módulos usados têm várias chamadas a `print`, para revelar o que acontece por baixo dos panos. Fora isso, eles não fazem nada de útil. O objetivo destes experimentos é observar a ordem na qual essas chamadas a `print` acontecem.
+
+[WARNING]
+====
+
+Aplicar um decorador de classes e uma fábrica de classes com
+`+__init_subclass__+` juntos, em uma única classe, é provavelmente um sinal de
+excesso de engenharia ou de desespero. Esta combinação incomum é útil nestes
+experimentos, para comparar em que momento um decorador de
+classes e `+__init_subclass__+` alteram a classe.
+
+====
+
+Vamos começar examinando _builderlib.py_, dividido em duas partes: o <> e o <>.
+
+[[builderlib_top_ex]]
+.builderlib.py: primeira parte do módulo
+====
+[source, python]
+----
+include::../code/24-class-metaprog/evaltime/builderlib.py[tags=BUILDERLIB_TOP]
+----
+====
+<1> Essa é uma fábrica de classes para implementar...
+<2> ...um método `+__init_subclass__+`.
+<3> Define uma função para ser adicionada à subclasse na atribuição abaixo.
+<4> Um decorador de classes.
+<5> Função a ser adicionada à classe decorada.
+<6> Devolve a classe recebida como argumento.
+
+Continuando _builderlib.py_ no <>...
+
+[[builderlib_bottom_ex]]
+.builderlib.py: a parte final do módulo
+====
+[source, python]
+----
+include::../code/24-class-metaprog/evaltime/builderlib.py[tags=BUILDERLIB_BOTTOM]
+----
+====
+<1> Uma classe descritora para demonstrar quando...
+<2> ...uma instância do descritor é criada, e quando...
+<3> ...`+__set_name__+` será invocado durante a criação da classe `owner`.
+<4> Como os outros métodos, este `+__set__+` não faz nada, exceto exibir seus argumentos.
+
+Se importarmos _builderlib.py_ no console de Python, veremos o seguinte:
+
+[source, python]
+----
+>>> import builderlib
+@ builderlib module start
+@ Builder body
+@ Descriptor body
+@ builderlib module end
+----
+
+Note que as linhas exibidas por _builderlib.py_ têm uma `@` à esquerda.
+
+Agora voltamos a atenção para _evaldemo.py_, que acionará métodos especiais em
+_builderlib.py_ (no <>).
+
+[[evaldemo_ex]]
+.evaldemo.py: script para experimentar com _builderlib.py_
+====
+[source, python]
+----
+include::../code/24-class-metaprog/evaltime/evaldemo.py[]
+----
+====
+<1> Aplica um decorador.
+<2> Cria uma subclasse de `Builder` para acionar seu `+__init_subclass__+`.
+<3> Instancia o descritor.
+<4> Isso só será chamado se o módulo for executado como o programa principal.
+
+As chamadas a `print` em _evaldemo.py_ têm um `#` como prefixo.
+Se você abrir o console novamente e importar _evaldemo.py_, a saída aparece no <>.
+
+[[evaldemo_console_ex]]
+.Experimentos de console com _evaldemo.py_
+====
+[source, python]
+----
+>>> import evaldemo
+@ builderlib module start  <1>
+@ Builder body
+@ Descriptor body
+@ builderlib module end
+# evaldemo module start
+# Klass body  <2>
+@ Descriptor.__init__()  <3>
+@ Descriptor.__set_name__(,
+      , 'attr')                <4>
+@ Builder.__init_subclass__()  <5>
+@ deco()  <6>
+# evaldemo module end
+----
+====
+<1> As primeiras quatro linhas são o resultado de `from builderlib import …` +
+Elas não aparecerão se você usar a mesma sessão do console do experimento anterior, pois _builderlib.py_ já estará importado.
+<2> Isto sinaliza que Python começou a ler o corpo de `Klass`. Neste momento o objeto classe ainda não existe.
+<3> A instância do descritor é criada e vinculada a `attr`, no espaço de nomes que Python passará para o construtor default do objeto classe: `+type.__new__+`.
+<4> Neste ponto, a função embutida de Python `+type.__new__+` já criou o objeto `Klass` e invoca
+`+__set_name__+` em cada instância das classes do descritor que oferecem aquele método, passando `Klass` como argumento `owner`.
+<5> `+type.__new__+` então chama `+__init_subclass__+` na superclasse de `Klass`, passando `Klass` como único argumento.
+<6> Quando `+type.__new__+` devolve o objeto classe, Python aplica o decorador. Neste exemplo, a classe devolvida por `deco` está vinculada a `Klass` no espaço de nomes do módulo
+
+A implementação de `+type.__new__+` está escrita em C.
+O comportamento que acabei de descrever está documentado na seção
+https://fpy.li/bx[«Criando o objeto classe»], no capítulo
+_Modelo de Dados_ da referência de Python.
+
+Note que a função `main()` de _evaldemo.py_ (<>)
+rodou durante a sessão no console (<>), portanto nenhuma instância de `Klass` foi criada.
+Todas as ações que vimos foram acionadas por operações no momento da importação:
+importar `builderlib` e definir `Klass`.
+
+Se você executar _evaldemo.py_ como um script, verá a mesma saída do <>,
+com mais linhas logo antes do final.
+As linhas adicionais são o resultado da execução de `main()` no <>:
+
+[[evaldemo_script_ex]]
+.Executando _evaldemo.py_ como um programa
+====
+[source]
+----
+$ ./evaldemo.py
+[... 9 linhas omitidas ...]
+@ deco()  <1>
+@ Builder.__init__()  <2>
+# Klass.__init__()
+@ SuperA.__init_subclass__:inner_0()  <3>
+@ deco:inner_1()  <4>
+@ Descriptor.__set__(, , 999)  <5>
+# evaldemo module end
+----
+====
+<1> As 10 primeiras linhas—incluindo essa—são as mesma que aparecem no <>.
+<2> Saída de `+super().__init__()+` em `+Klass.__init__+`.
+<3> Saída de `obj.method_a()` em `main`; o `method_a` foi injetado por
+`+SuperA.__init_subclass__+`.
+<4> Saída de `obj.method_b()` em `main`; `method_b` foi injetado por `deco`.
+<5> Saída de `obj.attr = 999` em `main`.
+
+Uma classe base com `+__init_subclass__+` ou um decorador de classes são ferramentas poderosas, mas elas estão limitadas a trabalhar sobre uma classe já criada por `+type.__new__+` por baixo dos panos.
+Nas raras ocasiões em que for preciso ajustar os argumentos passados a `+type.__new__+`, uma metaclasse é necessária.
+Esse é o destino final deste capítulo—e deste livro!((("", startref="CMimport24")))
+
+
+[[metclass101_sec]]
+=== Introdução às metaclasses
+
+
+[quote, Tim Peters, inventor do algoritmo timsort e prolífico mantenedor de Python]
+____
+
+[Metaclasses] são uma mágica tão profunda que 99% dos usuários jamais
+deveriam se preocupar com elas. Quem se pergunta se precisa delas, não precisa
+(quem realmente precisa de metaclasses sabe com certeza, e não precisa que
+lhe expliquem a razão).footnote:[Mensagem a comp.lang.python, assunto:
+https://fpy.li/24-12[_Acrimony in c.l.p._] (animosidade na c.l.p., o grupo _comp.lang.python_).
+Esta é
+outra parte da mesma mensagem de 23 de dezembro de 2002, que citei no _Prefácio_ de _Python Fluente_.
+O TimBot estava inspirado naquele dia.]
+
+____
+
+
+
+Uma((("metaclasses", "basics of", id="MCbasics24")))((("class metaprogramming", "metaclass basics", id="CMmetabasic24"))) metaclasse é uma fábrica de classes.
+Diferente de `record_factory`, do <>,
+uma metaclasse é escrita como uma classe.
+Em outras palavras, uma metaclasse é uma classe cujas instâncias são classes.
+A <> usa a _Notação de Engenhocas e Bugigangas_ para ilustrar uma metaclasse:
+uma engenhoca que produz outra engenhoca.
+
+[[meta_class_and_class_mgn]]
+.Uma metaclasse é uma classe que cria classes.
+image::../images/flpy_2401.png[align="center", pdfwidth=10cm]
+
+Pense no modelo de objetos de Python: classes são objetos, portanto cada classe deve ser uma instância de alguma outra classe.
+Por default, as classes de Python são instâncias de `type`.
+Em outras palavras, `type` é a metaclasse da maioria das classes, sejam elas embutidas ou definidas pelo usuário:
+
+[source, python]
+----
+>>> str.__class__
+
+>>> from bulkfood_v5 import LineItem
+>>> LineItem.__class__
+
+>>> type.__class__
+
+----
+
+Para evitar regressões infinitas, a classe de `type` é `type`, como mostra a última linha.
+
+Observe que não estou dizendo que `str` ou `LineItem` são subclasses de `type`. Estou dizendo que `str` e `LineItem` são instâncias de `type`.
+Elas são todas subclasses de `object`. A <> pode ajudar você a contemplar essa estranha realidade.
+
+[[class_hier_2tops_uml]]
+.Os dois diagramas são verdadeiros. O da esquerda enfatiza que `str`, `type`, e `LineItem` são subclasses de `object`. O da direita ressalta que `str`, `object`, e `LineItem` são instâncias de `type`, pois todas são classes.
+image::../images/flpy_2402.png[align="center", pdfwidth=10cm]
+
+[NOTE]
+====
+
+As classes `object` e `type` têm uma relação singular: `object` é uma instância
+de `type`, e `type` é uma subclasse de `object`. Esta relação é "mágica": ela
+não pode ser expressa em Python, porque cada uma das classes teria que existir
+antes da outra poder ser definida. O fato de `type` ser uma instância de si
+mesma também é mágico.
+
+====
+
+O próximo trecho mostra que a classe de `collections.Iterable` é `abc.ABCMeta`.
+Observe que `Iterable` é uma classe abstrata, mas `ABCMeta` é uma classe concreta--afinal, `Iterable` é uma instância de `ABCMeta`:
+
+[source, python]
+----
+>>> from collections.abc import Iterable
+>>> Iterable.__class__
+
+>>> import abc
+>>> from abc import ABCMeta
+>>> ABCMeta.__class__
+
+----
+
+Por fim, a classe de `ABCMeta` também é `type`.
+Toda classe é uma instância de `type`, direta ou indiretamente, mas só metaclasses são também subclasses de `type`.
+Esta é a relação mais importante para entender as metaclasses:
+uma metaclasse, tal como `ABCMeta`, herda de `type` o poder de criar classes.
+A <> ilustra essa relação fundamental.
+
+[[metaclass_abcmeta_uml]]
+.`Iterable` é uma subclasse de `object` e uma instância de `ABCMeta`. Tanto `object` quanto `ABCMeta` são instâncias de `type`, mas a relação crucial aqui é que `ABCMeta` também é uma subclasse de `type`, porque `ABCMeta` é uma metaclasse. Neste diagrama, `Iterable` é a única classe abstrata.
+image::../images/flpy_2403.png[align="center", pdfwidth=6cm]
+
+A lição importante aqui é que metaclasses são subclasses de `type`, e é isso que permite a elas funcionarem como fábricas de classes.
+Uma metaclasse pode customizar suas instâncias implementando métodos especiais, como demonstram as próximas seções.((("", startref="MCbasics24")))
+
+[[how_metaclass_customizes]]
+==== Como uma metaclasse customiza uma classe
+
+Para((("metaclasses", "customizing classes"))) usar uma metaclasse, é crucial entender como
+`+__new__+` funciona em qualquer classe.
+Isto foi discutido na <>.
+
+A mesma mecânica se repete no nível "meta", quando uma metaclasse está prestes a criar uma nova instância, que é uma classe.
+Considere a declaração abaixo:
+
+[source, python]
+----
+class Klass(SuperKlass, metaclass=MetaKlass):
+    x = 42
+    def __init__(self, y):
+        self.y = y
+----
+
+Para processar o bloco `class` acima, o interpretador invoca `+MetaKlass.__new__+`,
+cuja implementação herdada da classe `type` recebe os seguintes argumentos:
+
+<<<
+
+`meta_cls`:: A própria metaclasse, porque `+__new__+` funciona como um método de classe. +
+Ex: `MetaKlass`
+
+`cls_name`:: O nome da classe a ser criada, como uma string. Ex: `'Klass'`
+
+`bases`:: Uma tupla com as superclasses da classe a ser criada. Ex: `(SuperKlass,)`
+
+`cls_dict`:: Os métodos e outros atributos da classe a ser criada. Ex:
++
+--
+[source, python]
+----
+{'x': 42, '__init__': }
+----
+--
+
+Ao implementar `+MetaKlass.__new__+`, podemos inspecionar e modificar aqueles
+argumentos antes de passá-los para `+super().__new__+`, que por fim invocará
+`+type.__new__+` para criar o novo objeto classe.
+Após `+super().__new__+` retornar, podemos aplicar processamentos
+adicionais à classe recém-criada, antes de devolvê-la para o Python. 
+
+Depois de invocar `+MetaKlass.__new__+` e receber a nova `Klass`,
+Python então invoca `+SuperKlass.__init_subclass__+`,
+passando a classe que criamos. 
+Se existir um decorador de classe acima do bloco `class`, ele é aplicado
+depois que `+__init_subclass__+` retorna.
+Finalmente, Python
+vincula o objeto classe a seu nome no espaço de nomes atual.
+No caso mais comum, a instrução `class` está no primeiro nível do módulo,
+e a `Klass` é inserida no espaço de nomes global do módulo.
+
+O processamento mais comum realizado no `+__new__+` de uma metaclasse é
+adicionar ou substituir itens no `cls_dict`, que representa o espaço
+de nomes da classe em construção.
+Por exemplo, podemos injetar métodos na classe em construção adicionando
+funções a `cls_dict`. Entretanto, observe que adicionar métodos pode também ser
+feito após a classe ser criada, e é por essa razão que podemos fazer isso usando
+`+__init_subclass__+` ou um decorador de classe.
+
+<<<
+Um atributo que precisa ser adicionado a `cls_dict` antes de se executar `+type.__new__+` é
+`+__slots__+`, como discutido na <>.
+O método `+__new__+` de uma metaclasse é o lugar ideal para configurar `+__slots__+`.
+A próxima seção mostra como fazer isso.
+
+
+[[nice_metaclass_sec]]
+==== Um belo exemplo de metaclasse
+
+A((("metaclasses", "example metaclass", id="MCexample24")))
+metaclasse `MetaBunch` apresentada aqui é uma variação do último exemplo no
+Capítulo 4 do _Python in a Nutshell, 3rd ed._, de Alex
+Martelli, Anna Ravenscroft, e Steve Holden, escrito para rodar sob Python 2.7 e
+3.5.footnote:[Os autores gentilmente me deram permissão para usar seu exemplo.
+`MetaBunch` apareceu pela primeira vez em uma mensagem enviada por Martelli para
+o grupo comp.lang.python, em 7 de julho de 2002, com o assunto
+https://fpy.li/24-13["a nice metaclass example (was Re: structs in python)" (_um
+belo exemplo de metaclasse (era Re: structs no python)_)], na sequência de uma
+discussão sobre estruturas de dados similares a registros no Python. O código
+original de Martelli, para Python 2.2, ainda roda após uma única modificação:
+para usar uma metaclasse no Python 3, é necessário usar o argumento nomeado
+`metaclass` na declaração da classe (por exemplo, `Bunch(metaclass=MetaBunch)`),
+em vez da convenção antiga, que era adicionar um atributo `+__metaclass__+` no
+corpo da classe.] Assumindo o uso de Python 3.6 ou mais recente, pude
+simplificar ainda mais o código.
+
+Mas primeiro vamos ver o que a classe base `Bunch` oferece:
+
+[source, python]
+----
+include::../code/24-class-metaprog/metabunch/from3.6/bunch.py[tags=BUNCH_POINT_DEMO_1]
+----
+
+Subclasses de `Bunch` usam atributos de classe com valores atribuídos, que então se tornam os valores default dos atributos de instância.
+O `+__repr__+` gerado omite os argumentos cujos valores são iguais aos defaults.
+
+`MetaBunch`—a metaclasse de `Bunch`—gera `+__slots__+` para a nova classe a
+partir de atributos de classe declarados na classe do usuário.
+Isto impede que atributos com nomes não declarados sejam
+criados na instanciação ou por atribuição posterior.
+
+<<<
+
+[source, python]
+----
+include::../code/24-class-metaprog/metabunch/from3.6/bunch.py[tags=BUNCH_POINT_DEMO_2]
+----
+
+Vamos agora mergulhar no elegante código de `MetaBunch`, no <>.
+
+[[metabunch_ex]]
+.metabunch/from3.6/bunch.py: a metaclasse `MetaBunch` e a classe `Bunch`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/metabunch/from3.6/bunch.py[tags=METABUNCH]
+----
+====
+
+<1> Para criar uma nova metaclasse, herdamos de `type`.
+
+<2> `+__new__+` funciona como um método de classe, mas a classe é uma
+metaclasse, então prefiro nomear o primeiro argumento `meta_cls` (`mcs` é uma
+alternativa comum). Os três argumentos seguintes são os mesmos da assinatura de
+três parâmetros de `type()`, quando invocada diretamente para criar uma classe.
+
+<3> `defaults` vai preservar um mapeamento de nomes de atributos e seus valores
+default.
+
+<4> Isto será injetado na nova classe.
+
+<5> Lê `defaults` e define o atributo de instância correspondente, com o valor
+extraído de `kwargs`, ou um valor default.
+
+<6> Se ainda houver itens em `kwargs`, isso significa que não há posição
+restante onde possamos colocá-los. Adotamos a prática de _falhar rápido_,
+então não queremos ignorar silenciosamente os itens em excesso.
+
+<7> `+__repr__+` devolve uma string que se parece com uma chamada ao
+construtor—por exemplo, `Point(x=3)`, exibindo somente os atributos
+com valores diferentes dos respectivos valores default.
+
+<8> Inicializa o espaço de nomes para a nova classe.
+
+<9> Itera sobre o espaço de nomes da classe fornecida pelo usuário.
+
+<10> Se um `name` no formato _dunder_ é encontrado,
+copia o item para o espaço de nomes da nova classe, a menos que ele já esteja
+lá. Isto evita que usuários sobrescrevam `+__init__+`, `+__repr__+` e outros
+atributos definidos pelo Python, como `+__qualname__+` e `+__module__+`.
+
+<11> Se `name` não for um _dunder_, acrescenta `name` a `+__slots__+` e armazena
+seu `value` em `defaults`.
+
+<12> Cria e devolve a nova classe.
+
+<13> Fornece uma classe base, assim os usuários não precisam ver `MetaBunch`.
+
+`MetaBunch` funciona porque tem a oportunidade de configurar `+__slots__+` antes
+de invocar `+super().__new__+` para criar a classe final. Como sempre em
+metaprogramação, o fundamental é entender a sequência de ações. Vamos fazer
+outro experimento sobre as etapas de avaliação, agora com uma metaclasse.((("",
+startref="MCexample24")))
+
+
+==== Experimento com as etapas de avaliação de metaclasses
+
+Esta((("metaclasses", "metaclass evaluation time experiment", id="MCtime24")))
+é uma variação da <>, acrescentando uma metaclasse para ter mais emoção.
+O módulo _builderlib.py_ é o mesmo de antes, mas o script principal agora é _evaldemo_meta.py_,
+listado no <>.
+
+[[evaldemo_meta_ex]]
+.evaldemo_meta.py: experimentando com uma metaclasse
+====
+[source, python]
+----
+include::../code/24-class-metaprog/evaltime/evaldemo_meta.py[]
+----
+====
+<1> Importa `MetaKlass` de _metalib.py_, que veremos no <>.
+<2> Declara `Klass` como uma subclasse de `Builder` e uma instância de `MetaKlass`.
+<3> Este método é injetado por `+MetaKlass.__new__+`, como veremos adiante.
+
+
+[WARNING]
+====
+Em nome da ciência, o <> desafia qualquer razão prática e aplica três técnicas diferentes de metaprogramação em `Klass`:
+um decorador, uma classe base usando `+__init_subclass__+`, e uma metaclasse customizada.
+Se você fizer isto em código de produção, não me culpe.
+Aqui o objetivo é observar a ordem na qual as três técnicas interferem no processo de criação de uma classe.
+====
+
+Como no experimento anterior com as etapas de avaliação, este exemplo não faz nada, apenas exibe mensagens revelando o fluxo de execução.
+O <> mostra a primeira parte do código de _metalib.py_—o restante está no <>.
+
+<<<
+
+[[metalib_top_ex]]
+.metalib.py: a classe `NosyDict`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/evaltime/metalib.py[tags=METALIB_TOP]
+----
+====
+
+Escrevi a classe `NosyDict` para  sobrescrever `+__setitem__+` e exibir cada `key` e cada `value` conforme eles são definidos.
+A metaclasse vai usar uma instância de `NosyDict` para guardar o espaço de nomes da classe em construção, revelando um pouco mais sobre o funcionamento interno do Python.
+
+A principal atração de _metalib.py_ é a metaclasse no <>.
+Ela implementa o método especial `+__prepare__+`, um método de classe que Python só invoca em metaclasses.
+O método `+__prepare__+` oferece a primeira oportunidade para influenciar o processo de criação de uma nova classe.
+
+[TIP]
+====
+Ao programar uma metaclasse, acho útil adotar a seguinte convenção de nomenclatura para argumentos de métodos especiais:
+
+* Usar `cls` em vez de `self` para métodos de instância, pois a instância é uma classe.
+
+* Usar `meta_cls` em vez de `cls` para métodos de classe, pois a classe é uma metaclasse.
+Lembre-se de que `+__new__+` se comporta como um método de classe mesmo sem o decorador `@classmethod`.
+====
+
+<<<
+
+[[metalib_bottom_ex]]
+.metalib.py: a `MetaKlass`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/evaltime/metalib.py[tags=METALIB_BOTTOM]
+----
+====
+<1> `+__prepare__+` deve ser declarado como um método de classe.
+Ele não é um método de instância, pois a classe em construção ainda não existe quando Python invoca `+__prepare__+`.
+<2> Python invoca `+__prepare__+` em uma metaclasse para obter um mapeamento para guardar o espaço de nomes da classe em construção.
+<3> Devolve uma instância de `NosyDict` para ser usada como o espaço de nomes.
+<4> `cls_dict` é uma instância de `NosyDict` devolvida por `+__prepare__+`.
+<5> `+type.__new__+` exige um `dict` de verdade como último argumento (não um mapeamento qualquer), então fornecemos o atributo `data` de `NosyDict`, herdado de `UserDict`.
+<6> Injeta um método na classe recém-criada.
+<7> Como sempre, `+__new__+` devolve o objeto que acaba de ser criado—neste caso, a nova classe.
+<8> Definir `+__repr__+` em uma metaclasse permite customizar o `repr()` de objetos classe.
+
+O principal caso de uso para `+__prepare__+` antes de Python 3.6 era fornecer um
+`OrderedDict` para preservar os atributos de uma classe em construção,
+para que o `+__new__+` da metaclasse pudesse processar aqueles atributos na ordem
+em que aparecem no código-fonte da definição de classe do usuário.
+Agora que `dict` preserva a ordem de inserção, `+__prepare__+` raramente é necessário.
+Veremos um uso criativo para ele na <>.
+
+Importar _metalib.py_ no console de Python não é muito empolgante.
+Observe o uso de `%` para prefixar as linhas geradas por esse módulo:
+
+[source, python]
+----
+>>> import metalib
+% metalib module start
+% MetaKlass body
+% metalib module end
+----
+
+Muitas coisas acontecem quando importamos _evaldemo_meta.py_, como visto no  <>.
+
+[[evaldemo_meta_console_ex]]
+.Experimento com _evaldemo_meta.py_ no console
+====
+[source, python]
+----
+>>> import evaldemo_meta
+@ builderlib module start
+@ Builder body
+@ Descriptor body
+@ builderlib module end
+% metalib module start
+% MetaKlass body
+% metalib module end
+# evaldemo_meta module start  <1>
+% MetaKlass.__prepare__(, 'Klass',  <2>
+                        (,))
+% NosyDict.__setitem__(, '__module__',
+  'evaldemo_meta')  <3>
+% NosyDict.__setitem__(, '__qualname__', 'Klass')
+# Klass body
+@ Descriptor.__init__()  <4>
+% NosyDict.__setitem__(, 'attr',
+  )  <5>
+% NosyDict.__setitem__(, '__init__',
+                       )  <6>
+% NosyDict.__setitem__(, '__repr__',
+                       )
+% NosyDict.__setitem__(, '__classcell__', )
+% MetaKlass.__new__(, 'Klass',
+                    (,),
+                    )  <7>
+@ Descriptor.__set_name__(,
+                          , 'attr')  <8>
+@ Builder.__init_subclass__()
+@ deco()
+# evaldemo_meta module end
+----
+====
+<1> As linhas acima desta são exibidas durante a importação de _builderlib.py_ e _metalib.py_.
+<2> Python invoca `+__prepare__+` para iniciar o processamento de uma instrução `class`.
+<3> Antes de analisar o corpo da classe, Python acrescenta `+__module__+` e `+__qualname__+` ao espaço de nomes de uma classe em construção.
+<4> A instância do descritor é criada...
+<5> ...e vinculada a `attr` no espaço de nomes da classe.
+<6> Os métodos `+__init__+` e `+__repr__+` são definidos e adicionados ao espaço de nomes.
+<7> Após terminar o processamento do corpo da classe, Python chama `+MetaKlass.__new__+`.
+<8> Após o método `+__new__+` da metaclasse devolver a classe recém-criada,
+os métodos `+__set_name__+`, `+__init_subclass__+` e o decorador `@deco` são invocados nesta ordem, 
+
+<<<
+Se executarmos _evaldemo_meta.py_ como um script, `main()` é invocada, e mais coisas acontecem. Veja o <>.
+
+[[evaldemo_meta_script_ex]]
+.Rodando _evaldemo_meta.py_ como um programa
+====
+[source]
+----
+$ ./evaldemo_meta.py
+[... 20 linhas omitidas ...]
+@ deco()  <1>
+@ Builder.__init__()
+# Klass.__init__()
+@ SuperA.__init_subclass__:inner_0()
+@ deco:inner_1()
+% MetaKlass.__new__:inner_2()  <2>
+@ Descriptor.__set__(, , 999)
+# evaldemo_meta module end
+----
+====
+<1> As primeiras 21 linhas—incluindo esta—são as mesmas que aparecem no <>.
+<2> Acionado por `obj.method_c()` em `main`; `method_c` foi injetado por `+MetaKlass.__new__+`.
+
+Vamos agora voltar à ideia da classe `Checked`, com descritores `Field`
+implementando validação de tipo durante a execução, e ver como aquilo pode ser
+feito com uma metaclasse.((("", startref="CMmetabasic24")))((("",
+startref="MCtime24")))
+
+
+=== `Checked`, agora com metaclasse
+
+Não((("class metaprogramming", "metaclass solution for checkedlib.py",
+id="CMcheck24")))((("metaclasses", "metaclass solution for checkedlib.py",
+id="MCchecked24"))) quero estimular a otimização prematura nem excessos de engenharia (_over),
+então aqui temos um cenário de ficção para justificar reescrever _checkedlib.py_ com `+__slots__+`,
+exigindo a aplicação de uma metaclasse.
+Fique à vontade para pular a historinha.
+
+<<<
+.Senta que lá vem história
+****
+Nosso _checkedlib.py_ usando `+__init_subclass__+` é um sucesso na empresa, e em qualquer dado momento nossos servidores de produção têm milhões de instâncias de subclasses de `Checked` em suas memórias.
+
+Analisando (_profiling_) o uso de memória durante a execução, constatamos que usar `+__slots__+` pode reduzir os custos de hospedagem, por duas razões:
+
+* Menos uso de memória, já que as instâncias de `Checked` não precisarão ter seus próprios
+`+__dict__+`
+* Melhor desempenho, pela remoção de `+__setattr__+`, que foi criado só para bloquear atributos indesejados,
+mas é acionado na instanciação e para todas as definições de atributos antes de
+`+Field.__set__+` ser invocado para fazer seu trabalho
+
+****
+
+
+O módulo _metaclass/checkedlib.py_, que estudaremos a seguir, é um substituto
+direto para _initsub/checkedlib.py_. Os doctests contidos nos dois módulos são
+idênticos, bem como os arquivos _checkedlib_test.py_ para o _pytest_.
+
+A complexidade de _checkedlib.py_ é ocultada do usuário.
+Aqui está o código-fonte de um script que usa o pacote:
+
+[[checked_demo_ex]]
+.metaclass/checked_demo.py: exemplo de uso da classe `Checked`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/metaclass/checked_demo.py[tags=MOVIE_DEMO]
+----
+====
+
+<<<
+Esta definição concisa da classe `Movie` se vale de três instâncias do descritor de validação `Field`, uma configuração de `+__slots__+`, cinco métodos herdados de `Checked` e uma metaclasse para juntar tudo isso.
+A única parte visível de `checkedlib` é a classe base `Checked`.
+
+Estude a <>.
+A Notação Engenhocas e Bugigangas((("UML class diagrams", "annotated with MGN"))) complementa o diagrama de classes UML, tornando mais visível a relação entre classes e instâncias.
+
+[[checkedlib_uml_mgn]]
+.Diagrama de classes UML com MGN: a meta-engenhoca `CheckedMeta` cria a engenhoca `Movie`. A engenhoca `Field` cria os descritores `title`, `year`, e `box_office`, que são atributos de classe de `Movie`. Os dados dos campos são armazenados nos atributos `+_title+`, `+_year+` e `+_box_office+` em cada instância de `Movie`. Note a fronteira do pacote `checkedlib`. O desenvolvedor de `Movie` não precisa entender todo o maquinário dentro de _checkedlib.py_.
+image::../images/flpy_2404.png[Diagrama de classes UML+MGN para `CheckedMeta`, `Movie` etc.]
+
+
+Por exemplo, uma classe `Movie` usando a nova _checkedlib.py_ é uma instância de `CheckedMeta` e uma subclasse de `Checked`.
+Os atributos de classe `title`, `year` e `box_office` de `Movie` são três instâncias diferentes de `Field`.
+Cada instância de `Movie` tem seus próprios atributos `_title`, `_year` e `_box_office`, para armazenar os valores dos campos correspondentes.
+
+Vamos agora estudar o código, começando pela classe `Field` do <>.
+A classe descritora `Field` está um pouco diferente. Nos exemplos anteriores, cada instância do descritor `Field` armazenava seu valor na instância gerenciada, usando um atributo de mesmo nome. Por exemplo, na classe `Movie`, o descritor `title` armazenava o valor do campo em um atributo `title` na instância gerenciada.
+Isso tornava desnecessário que `Field` implementasse um método `+__get__+`.
+
+Entretanto, quando uma classe como `Movie` usa `+__slots__+`, ela não pode ter atributos de classe e atributos de instância com o mesmo nome. Cada instância do descritor é um atributo de classe, e agora precisamos de atributos de armazenamento separados em cada instância. O código usa o nome do descritor prefixado por um único `_`.
+Portanto, instâncias de `Field` têm atributos `name` e `storage_name` distintos, e implementamos
+`+Field.__get__+`.
+
+O <> mostra o código-fonte de `Field`, com os textos explicativos descrevendo apenas as mudanças nessa versão.
+
+[[checked_field_meta_ex]]
+.metaclass/checkedlib.py: o descritor `Field` com `storage_name` e `+__get__+`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/metaclass/checkedlib.py[tags=CHECKED_FIELD]
+----
+====
+<1> Define `storage_name` a partir do argumento `name`.
+<2> Se `+__get__+` recebe `None` como argumento `instance`, o descritor está sendo lido desde a própria classe gerenciada, não de uma instância gerenciada. Neste caso devolvemos o descritor.
+<3> Caso contrário, devolve o valor armazenado no atributo chamado `storage_name`.
+<4> `+__set__+` agora usa `setattr` para definir ou atualizar o atributo gerenciado.
+
+O <> mostra o código da metaclasse que controla este exemplo.
+
+[[checked_metaclass_ex]]
+.metaclass/checkedlib.py: a metaclasse `CheckedMeta`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/metaclass/checkedlib.py[tags=CHECKED_META]
+----
+====
+<1> `+__new__+` é o único método implementado em `CheckedMeta`.
+<2> Só altera a classe se seu `cls_dict` não incluir `+__slots__+`. Se `+__slots__+` já existe, assumimos que esta é a classe base `Checked` e não uma subclasse definida pelo usuário, e cria a classe sem modificações.
+<3> Nos exemplos anteriores usamos `typing.get_type_hints` para obter as dicas de tipo, mas aquilo exige uma classe existente como primeiro argumento. Neste ponto, a classe que estamos configurando ainda não existe, então precisamos recuperar `+__annotations__+` diretamente do `cls_dict`—o espaço de nomes da classe em construção, que Python passa como último argumento para o `+__new__+` da metaclasse.
+<4> Itera sobre `type_hints` para...
+<5> ...criar um `Field` para cada atributo anotado...
+<6> ...sobrescreve o item correspondente em `cls_dict` com a instância de `Field`...
+<7> ...e acrescenta o `storage_name` do campo à lista que usaremos para...
+<8> ...preencher o `+__slots__+` no `cls_dict`—o espaço de nomes da classe em construção.
+<9> Por fim, invocamos `+super().__new__+`.
+
+A última parte de _metaclass/checkedlib.py_ é a classe `Checked`. Usuários da _checkedlib_ criam subclasses de `Checked`, como `Movie` no <>.
+
+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:
+
+. 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__+`.
+. A remoção de `+__setattr__+`, pois a definição de `+__slots__+` na classe definida pelo usuário já impede a criação de atributos não declarados.
+
+O <> é a listagem completa da versão final de `Checked`.
+
+[[checked_baseclass_ex]]
+.metaclass/checkedlib.py: a classe base `Checked`
+====
+[source, python]
+----
+include::../code/24-class-metaprog/checked/metaclass/checkedlib.py[tags=CHECKED_CLASS]
+----
+====
+
+Isto conclui nossa terceira versão de uma fábrica de classes com descritores validados.
+A próxima seção trata de algumas questões gerais relacionadas a metaclasses.((("", startref="MCchecked24")))((("", startref="CMcheck24")))
+
+[[metaclases_real_world_sec]]
+=== Metaclasses no mundo real
+
+Metaclasses((("metaclasses", "considerations for use",
+id="MCconsider24")))((("class metaprogramming", "metaclass issues",
+id="CMissue24"))) são poderosas mas complexas. Antes de se decidir a implementar
+uma metaclasse, considere os pontos a seguir.
+
+
+[[metaclass_modern_features_sec]]
+==== Recursos modernos simplificam ou substituem as metaclasses
+
+Ao longo do tempo, vários casos de uso comum de metaclasses se tornaram redundantes devido a novos recursos da linguagem:
+
+Decoradores de classes:: Mais simples de entender que metaclasses, e com menor probabilidade de causar conflitos com classes base e metaclasses.
+
+`+__set_name__+`:: Elimina a necessidade de uma metaclasse com lógica customizada para definir automaticamente o nome de um descritor.footnote:[Na primeira edição de _Python Fluente_, as versões mais avançadas da classe `LineItem` usavam uma metaclasse apenas para definir o nome do armazenamento dos atributos. Veja o código nas metaclasses do https://fpy.li/24-14[exemplo da comida a granel], no repositório de código da primeira edição.]
+
+`+__init_subclass__+`:: Fornece uma forma de customizar a criação de classes que é transparente para o usuário final e ainda mais simples que um decorador—mas pode introduzir conflitos em uma hierarquia de classes complexa.
+
+O `dict` embutido preservando a ordem de inserção de chaves:: Eliminou((("keys", "preserving key insertion order"))) a principal razão para usar `+__prepare__+`, que era
+fornecer um `OrderedDict` para armazenar o espaço de nomes de uma classe em construção.
+Python só invoca `+__prepare__+` em metaclasses e então, se fosse necessário processar o espaço de nomes da classe na ordem em que eles aparecem no código-fonte, antes de Python 3.6 era preciso usar uma metaclasse.
+
+Em 2021, todas as versões sob manutenção ativa do CPython suportam todos os recursos listados acima.
+
+Sigo defendendo esses recursos porque vejo muita complexidade desnecessária em nossa profissão, e as metaclasses são uma porta de entrada para a complexidade.
+
+
+==== Metaclasses são um recurso estável da linguagem
+
+As metaclasses foram introduzidas no Python em 2002, junto com as assim chamadas
+_new-style classes_ (classes com novo estilo), descritores e propriedades.
+
+É impressionante que o exemplo do `MetaBunch`, postado pela primeira vez por
+Alex Martelli em julho de 2002, ainda funcione no Python 3.9—a única modificação
+é a forma de especificar a metaclasse a ser usada, que no Python 3 fazemos com esta sintaxe:
+
+[source, python]
+----
+class Bunch(metaclass=MetaBunch):
+    ...
+----
+
+<<<
+Nenhum dos recursos modernos citados na <> quebrou
+código existente que usava metaclasses. Mas um código legado com metaclasses
+frequentemente pode ser simplificado através do uso daqueles recursos,
+especialmente ignorando versões de Python anteriores à 3.6—que hoje são obsoletas.
+
+
+==== Uma classe só pode ter uma metaclasse
+
+Se sua declaração de classe envolver duas ou mais metaclasses, você verá essa intrigante mensagem de erro:
+
+[source]
+----
+TypeError: metaclass conflict: the metaclass of a derived class
+must be a (non-strict) subclass of the metaclasses of all its bases
+
+(conflito de metaclasses: a metaclasse de uma classe derivada deve
+ser uma subclasse (não-estrita) das metaclasses de todas as suas bases)
+----
+
+Isso pode acontecer mesmo sem herança múltipla.
+Por exemplo, a declaração abaixo pode levantar aquele `TypeError`:
+
+[source, python]
+----
+class Record(abc.ABC, metaclass=PersistentMeta):
+    pass
+----
+
+Vimos que `abc.ABC` é uma instância da metaclasse `abc.ABCMeta`.
+Se aquela metaclasse `Persistent` não for uma subclasse de `abc.ABCMeta`,
+você tem um conflito de metaclasses.
+
+Há duas maneiras de lidar com esse erro:
+
+* Encontre outra forma de fazer o que precisa ser feito, evitando o uso de pelo menos uma das metaclasses envolvidas.
+* Escreva a sua própria metaclasse `PersistentABCMeta` como uma subclasse tanto de `abc.ABCMeta` quanto de `PersistentMeta`, usando herança múltipla, e faça dela a única metaclasse de `Record`.footnote:[Se você sentiu vertigem ao ponderar sobre as implicações de herança múltipla com metaclasses, bom para você. Eu também passaria longe dessa solução.]
+
+<<<
+[TIP]
+====
+
+A solução de uma metaclasse com duas metaclasses base pode ser implementada para atender um prazo em caso de desespero.
+Na minha experiência, a programação de metaclasses sempre leva mais tempo que o esperado,
+tornando esta abordagem arriscada diante de um prazo inflexível.
+Se fizer isso e cumprir o prazo previsto, seu código pode conter bugs sutis.
+E mesmo na ausência de bugs conhecidos, esta abordagem deveria ser considerada uma dívida técnica,
+pelo simples fato de ser difícil de entender e manter.
+
+====
+
+
+==== Metaclasses devem ser detalhes de implementação
+
+Além de `type`, existem apenas outras seis metaclasses em toda a biblioteca padrão de Python 3.9.
+As metaclasses mais utilizadas (indiretamente) provavelmnete são `abc.ABCMeta`, `typing.NamedTupleMeta` e
+`enum.EnumMeta`.
+Nenhuma delas deve aparecer explicitamente no código da aplicação, mas somente em bibliotecas.
+Podemos considerá-las detalhes de implementação.
+
+Apesar de ser possível fazer metaprogramações muito loucas com metaclasses, é melhor se ater ao
+https://fpy.li/24-15[princípio do menor espanto], de forma que a maioria dos usuários possa de fato considerar metaclasses detalhes de implementação.footnote:[Eu ganhei a vida por alguns anos escrevendo código para Django, antes de resolver estudar como os campos dos modelos Django eram implementados. Só então aprendi sobre descritores e metaclasses.]
+
+Nos últimos anos, algumas metaclasses na biblioteca padrão de Python foram
+substituídas por outros mecanismos, sem afetar a API pública de seus pacotes. A
+forma mais simples de resguardar estas APIs para o futuro é oferecer uma classe
+normal, da qual usuários podem então criar subclasses para acessar a
+funcionalidade fornecida pela metaclasse, como fiz em vários exemplos.
+
+Para encerrar nossa conversa sobre metaprogramação de classes, vou mostrar
+o pequeno exemplo de metaclasse mais interessante que encontrei durante
+minha pesquisa para esse capítulo.((("", startref="CMissue24")))((("",
+startref="MCconsider24")))
+
+<<<
+[[metahack_sec]]
+=== Um _hack_ de metaclasse com `+__prepare__+`
+
+Quando((("class metaprogramming", "__prepare__ method", id="CMjprepare24",
+secondary-sortas="prepare")))((("__prepare__",
+id="prepare24"))) atualizei esse capítulo para a segunda edição, procurei
+exemplos simples mas interessantes para substituir o código de
+`LineItem` no exemplo da loja de comida a granel, que não exige mais
+o uso de metaclasses desde o Python 3.6.
+
+João S. O. Bueno—mais conhecido como JS na comunidade Python brasileira,
+me apresentou a ideia de metaclasse mais curiosa que já vi.
+
+Uma aplicação de sua ideia é criar uma classe que gera constantes numéricas automaticamente:
+
+[source, python]
+----
+include::../code/24-class-metaprog/autoconst/autoconst_demo.py[tags=AUTOCONST]
+----
+
+Sim, isto funciona do jeito que está! Este código é um doctest em _autoconst_demo.py_.
+
+Aqui está a classe base fácil de usar `AutoConst` , e a metaclasse por trás dela, implementadas em _autoconst.py_:
+
+[source, python]
+----
+include::../code/24-class-metaprog/autoconst/autoconst.py[tags=AUTOCONST]
+----
+
+É só isso.
+
+Claramente, o truque está em `WilyDict`.
+
+Quando Python processa o espaço de nomes da classe do usuário e lê `banana`, ele
+procura aquele nome no mapeamento fornecido por `+__prepare__+`.
+Neste caso, é uma instância
+de `WilyDict` que implementa o método `+__missing__+`, que vimos na
+https://fpy.li/ct[«Seção 3.5.2»] (vol.1).
+
+A instância de `WilyDict` inicialmente não contém uma chave
+`'banana'`, então o método `+__missing__+` é acionado. Ele cria um item na hora,
+com a chave `'banana'` e o valor `0`, e devolve este valor. Python se
+contenta com isso, e daí tenta acessar `'coconut'`. `WilyDict` imediatamente
+adiciona aquele item com o valor `1`, e o devolve. O mesmo acontece com
+`'vanilla'`, que é então mapeado para `2`.
+
+Já vimos `+__prepare__+` e `+__missing__+` antes.
+A inovação é como JS os juntou.
+
+Aqui está o código-fonte de `WilyDict`, a última parte de _autoconst.py_:
+
+[source, python]
+----
+include::../code/24-class-metaprog/autoconst/autoconst.py[tags=WilyDict]
+----
+
+Enquanto experimentava, descobri que Python procurava `+__name__+` no espaço de
+nomes da classe em construção, fazendo com que `WilyDict` acrescentasse um item
+`+__name__+` e incrementasse `+__next_value+`. Eu então inseri uma instrução
+`if` em `+__missing__+`, para levantar um `KeyError` quando uma chave se parece com
+um atributo _dunder_.
+
+Me diverti adicionando mais funcionalidades a `AutoConstMeta` e `AutoConst`, mas em vez de compartilhar meus experimentos, vou deixar vocês se divertirem, brincando com o _hack_ genial de JS.
+
+<<<
+Aqui estão algumas ideias:
+
+* Torne possível obter o nome da constante a partir do valor. Por exemplo, `Flavor[2]` devolveria `'vanilla'`. Você pode fazer isso implementando `+__getitem__+` em `AutoConstMeta`. Desde o
+Python 3.9, é possível implementar
+`+__class_getitem__+` na própria `AutoConst`.
+
+* Suporte a iteração sobre a classe, implementando `+__iter__+` na metaclasse.
+Eu faria `+__iter__+` produzir as constantes na forma de pares `(name, value)`.
+
+* Implemente uma nova variante de `Enum`. Isso é um empreendimento
+épico, pois o pacote `enum` está cheio de truques, incluindo a metaclasse
+`EnumMeta`, com centenas de linhas de código e um método `+__prepare__+` bem complicado.
+
+Divirta-se!
+
+[NOTE]
+====
+
+O método especial `+__class_getitem__+` foi introduzido no Python 3.9 para
+suportar tipos genéricos, como parte da
+https://fpy.li/pep585[_PEP 585—Type Hinting Generics in Standard Collections_]
+(Dicas de Tipos Genéricas em Coleções Padrão).
+Graças a `+__class_getitem__+`, os mantenedores de Python não
+precisaram escrever uma nova metaclasse para que os tipos embutidos
+implementassem `+__getitem__+`, de modo que fosse possível escrever dicas de
+tipo genéricas, tal como `list[int]`. Esse é um recurso limitado, mas
+representativo, de um caso de uso mais amplo para metaclasses: implementar
+operadores e outros métodos especiais para funcionarem ao nível da classe, tal
+como fazer a própria classe iterável, como as subclasses de `Enum`.((("",
+startref="prepare24")))((("", startref="CMjprepare24")))
+
+====
+
+=== Para encerrar
+
+Metaclasses, bem((("class metaprogramming", "useful applications of metaclasses")))((("metaclasses", "useful applications of"))) como decoradores de classes e `+__init_subclass__+`, +
+são úteis para:
+
+- Registro de subclasses
+- Validação estrutural de subclasses
+- Aplicar decoradores a muitos métodos ao mesmo tempo
+- Serialização de objetos
+- Mapeamento objeto-relacional
+- Persistência automática de objetos
+- Implementar métodos especiais a nível de classe
+- Implementar recursos de classes encontrados em outras linguagens, como
+https://fpy.li/24-17[_traits_] 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
+_Pássaros aquáticos e as ABCs_, na https://fpy.li/cq[«Seção 13.5»] (vol.2):
+
+[quote]
+____
+
+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 tais profundezas.
+____
+
+Acredito que o conselho de Martelli se aplica não apenas a ABCs e metaclasses,
+mas também a hierarquias de classe, sobrecarga de operadores, decoradores de funções, descritores, decoradores de classes e fábricas de classes usando `+__init_subclass__+`.
+
+Em princípio, essas ferramentas poderosas existem para suportar o desenvolvimento de bibliotecas e frameworks.
+Naturalmente, as aplicações devem _usar_ tais ferramentas, na forma oferecida pela biblioteca padrão de Python ou por pacotes externos.
+Mas _implementá-las_ em código de aplicações é frequentemente resultado de uma abstração prematura.
+
+[quote, DHH, criador de Ruby on Rails]
+____
+Bons frameworks são extraídos, não inventados.footnote:[Esta frase é muito citada.
+Encontrei uma citação direta antiga no blog de DHH, em https://fpy.li/24-19[post] de 2005.]
+____
+
+
+=== Resumo do capítulo
+
+Este((("class metaprogramming", "overview of"))) capítulo começou com uma revisão dos atributos encontrados em objetos classe, como `+__qualname__+` e o método `+__subclasses__()+`.
+A seguir, vimos como a classe embutida `type` pode ser usada para criar classes durante a execução.
+
+Apresentei o método especial `+__init_subclass__+` na primeira versão de uma
+classe base `Checked`, projetada para substituir dicas de tipo de atributos em
+subclasses definidas pelo usuário por instâncias do descritor `Field`, que usam
+construtores para validar o tipo dos atributos durante a execução.
+
+Implementei a mesma ideia com um decorador de classes `@checked`, que acrescenta
+recursos a classes definidas pelo usuário, de forma similar ao que pode ser
+feito com `+__init_subclass__+`. Vimos que `+__init_subclass__+` ou um
+decorador de classes não conseguem configurar `+__slots__+` dinamicamente, pois atuam
+apenas após a criação da classe, e `+__slots__+` tem que ser definido antes.
+
+Desvendamos os conceitos de "momento de importação" (_import time_)
+e "momento de execução" (_run time_)
+com experimentos mostrando a ordem na qual o código Python é executado quando
+módulos, descritores, decoradores de classe e `+__init_subclass__+` são acionados.
+
+Nossa exploração de metaclasses começou com uma explicação geral de `type` como uma metaclasse,
+e sobre como metaclasses definidas pelo usuário podem implementar `+__new__+`, para customizar as classes que criam.
+Vimos então nossa primeira metaclasse customizada, o clássico exemplo `MetaBunch`, usando
+`+__slots__+`.
+A seguir, outro experimento com etapas de avaliação demonstrou como os métodos `+__prepare__+` e
+`+__new__+` de uma metaclasse são invocados antes de `+__init_subclass__+`
+e decoradores de classe, oferecendo mais oportunidades para customização de classes.
+
+Estudamos uma terceira versão de uma fábrica de classes `Checked`, com descritores `Field` e
+uma configuração customizada de `+__slots__+`, seguida de
+considerações gerais sobre o uso de metaclasses na prática.
+
+Por fim, vimos o hack `AutoConst`, inventado por João S. O. Bueno, baseado na
+brilhante ideia de uma metaclasse com `+__prepare__+` devolvendo um mapeamento
+que implementa `+__missing__+`. Em menos de 20 linhas de código, _autoconst.py_
+demonstra o poder da combinação de técnicas de metaprogramação no Python.
+
+Nunca encontrei outra linguagem como Python, fácil para iniciantes, prática para
+profissionais e empolgante para hackers. Obrigado, Guido van Rossum e todos que
+a fazem ser assim.
+
+
+[[ch21-furtherreading]]
+=== Para saber mais
+
+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://fpy.li/by[«Personalizando a criação de classe»] no capítulo
+_Modelo 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[«Atributos especiais»] do capítulo _Tipos embutidos_ da _Biblioteca Padrão de Python_
+também são leituras fundamentais.
+
+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` e `types.prepare_class`.
+
+Decoradores de classes foram formalizados na
+https://fpy.li/24-25[_PEP 3129—Class Decorators_]
+(Decoradores de Classes), escrita por Collin Winter, com a
+implementação de referência desenvolvida por Jack Diederich.
+A palestra 
+https://fpy.li/24-26[_Class Decorators: Radically Simple_]
+(Decoradores de Classes: Radicalmente Simples) na PyCon 2009,
+também de Jack Diederich, é
+uma rápida introdução a este recurso. Além de `@dataclass`, um exemplo
+interessante—e mais simples—de decorador de classes na biblioteca padrão de
+Python é https://fpy.li/7q[`functools.total_ordering`], 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), onde o método especial `+__prepare__+` foi proposto.
+
+<<<
+O _Python in a Nutshel_ 3rd. ed., de Alex Martelli, Anna Ravenscroft, e Steve Holden,
+é uma referência, mas foi escrito antes da https://fpy.li/pep487[_PEP 487_]
+simplificar o processo de customizar classes na sua criação. O principal exemplo
+de metaclasse no livro—`MetaBunch`—ainda é válido, pois não pode ser escrito com
+mecanismos mais simples.
+O _Effective Python_ 2nd. ed. (Addison-Wesley), de
+Brett Slatkin, traz vários exemplos atualizados de técnicas de criação de
+classes, incluindo metaclasses.
+
+Para aprender sobre as origens da
+metaprogramação de classes no Python, recomendo o artigo de Guido van Rossum de
+2003,
+https://fpy.li/24-28[_Unifying types and classes in Python 2.2_]
+(Unificando tipos e classes no Python 2.2). O texto se aplica também ao
+Python moderno, pois cobre as chamadas "classes novo estilo"—o comportamento padrão no Python 3—incluindo descritores e
+metaclasses. Uma das referências citadas por Guido é _Putting Metaclasses to
+Work: a New Dimension in Object-Oriented Programming_, de Ira R. Forman e Scott
+H. Danforth (Addison-Wesley), livro para o qual ele deu cinco estrelas na
+_Amazon.com_, escrevendo o seguinte comentário:
+
+[quote]
+____
+
+*Este livro contribuiu para o projeto das metaclasses no Python 2.2*
+
+Pena que esteja fora de catálogo; sempre me refiro a ele como o melhor tutorial
+que conheço para o difícil tópico da herança múltipla cooperativa, suportada
+pelo Python através da função `super()`.footnote:[Comprei um exemplar usado, e
+achei uma leitura muito difícil. Não cheguei ao final.]
+
+____
+
+Se você curte metaprogramação, talvez gostaria que Python suportasse
+o recurso definitivo de metaprogramação: macros sintáticas,
+como as oferecidas pela família de linguagens Lisp e—mais recentemente—pelo Elixir e pelo Rust.
+Macros sintáticas são mais poderosas e menos sujeitas a erros que as macros primitivas de substituição de código da linguagem C.
+Elas são funções especiais que reescrevem código-fonte para código padronizado, usando uma sintaxe customizada, antes da etapa de compilação,
+permitindo a desenvolvedores introduzir novas estruturas na linguagem sem modificar o compilador.
+Como a sobrecarga de operadores, macros sintáticas podem ser mal usadas.
+Mas, desde que a comunidade entenda e controle as desvantagens, elas suportam abstrações poderosas e amigáveis,
+como as DSLs (_Domain-Specific Languages_, Linguagens de Domínio Específico).
+
+<<<
+Em setembro de 2020, Marc Shannon, um dos mantenedores de Python, publicou a
+https://fpy.li/pep638[_PEP 638—Syntactic Macros_], uma proposta de macros sintáticas.
+Um ano após sua publicação inicial (quando escrevo essas linhas), a PEP 638 ainda era um rascunho e não havia discussões contínuas sobre ela.
+Claramente não é uma prioridade muito alta entre os mantenedores de Python.
+Eu gostaria de ver a PEP 638 sendo melhor discutida e, por fim, aprovada.
+Macros sintáticas permitiriam à comunidade Python experimentar com novos recursos controversos,
+tal como o "operador morsa" (_operador walrus_) (https://fpy.li/pep572[PEP 572]),
+casamento de padrões (https://fpy.li/pep634[PEP 634]) e regras alternativas para avaliação de dicas de tipo
+(PEPs https://fpy.li/pep563[563] e
+https://fpy.li/pep649[649]),
+antes que se fizessem modificações permanentes no núcleo da linguagem.
+Nesse meio tempo, podemos sentir o gosto das macros sintáticas com o pacote
+https://fpy.li/24-29[MacroPy].
+
+
+<<<
+.Ponto de vista
+****
+
+Vou((("class metaprogramming", "Soapbox discussion")))((("Soapbox sidebars", "programming language design"))) iniciar o último ponto de vista no livro com uma longa citação de Brian Harvey e Matthew Wright, dois professores de ciência da computação da Universidade da California (Berkeley e Santa Barbara). Em seu livro, _Simply Scheme: Introducing Computer Science_ ("Simplesmente Scheme: Introduzindo a Ciência da Computação") (MIT Press), Harvey e Wright escreveram:
+
+
+[quote, Brian Harvey e Matthew Wright, no prefácio de Simply Scheme]
+____
+Há duas escolas de pensamento sobre o ensino de ciência da computação. Podemos representar as duas visões de uma forma caricatural, assim:
+
+
+. *A visão conservadora*: Programas de computador se tornaram muito grandes e
+complexos para serem apreendidos pela mente humana. Portanto, a tarefa da
+educação na ciência da computação é ensinar os estudantes como se disciplinarem,
+de tal forma que 500 programadores medíocres possam se juntar e produzir um
+programa que atende às especificações.
+
+. *A visão radical*: Programas de computador se tornaram muito grandes e
+complexos para serem apreendidos pela mente humana. Portanto, a tarefa da
+educação na ciência da computação é ensinar os estudantes como expandir suas
+mentes até abarcar os programas, aprendendo a pensar com um vocabulário
+de ideias maiores, mais poderosas e mais flexíveis que as óbvias. Cada
+unidade de pensamento programático deve gerar uma grande recompensa para as
+capacidades do programa.footnote:[Brian Harvey e Matthew Wright, _Simply Scheme_
+(MIT Press, 1999), p. xvii. O livro completo está disponível em
+https://fpy.li/24-30[Berkeley.edu].]
+
+____
+
+As descrições exageradas de Harvey e Wright versam sobre o ensino de ciência da computação,
+mas também se aplicam ao projeto de linguagens de programação.
+Neste ponto você já deve ter adivinhado que concordo com a visão "radical",
+e acredito que Python foi projetado neste espírito.
+
+****
+
+<<<
+
+****
+
+A ideia de propriedade é um grande avanço em relação à abordagem "métodos de acesso desde o início",
+praticamente exigida em Java e suportada pela geração de _getters/setters_ por atalhos de teclado nas IDEs Java.
+A principal vantagem das propriedades é nos permitir começar a criar nossos programas
+simplesmente expondo atributos publicamente—no espírito do _KISS_—sabendo
+que um atributo público pode se tornar uma propriedade a qualquer momento sem quebrar código existente.
+Mas a ideia de descritor vai muito além disso,
+fornecendo um framework para abstrair lógica repetitiva para acessar atributos.
+Este framework é tão eficaz que mecanismos essenciais de Python o utilizam por baixo dos panos.
+
+Outra ideia poderosa são as funções como objetos de primeira classe,
+abrindo caminho para funções de ordem superior. E acontece que a
+combinação de descritores e funções de ordem superior permite a unificação de
+funções e métodos. O `+__get__+` de uma função cria um objeto método sob demanda,
+vinculando a instância ao argumento `self`. Isto é
+elegante.footnote:[_Machine Beauty: Elegance and the Heart of Technology_
+(Beleza de Máquina: A Elegância e o Coração da Tecnologia), de David Gelernter
+(Basic Books), começa com uma discussão intrigante sobre elegância e estética em
+obras de engenharia, de pontes a software. Os capítulos posteriores não são tão
+bons, mas o início vale o preço.]
+
+Por fim, temos a ideia de classes como objetos de primeira
+classe. É uma façanha impressionante de design que uma linguagem acessível para
+iniciantes forneça abstrações poderosas, como fábricas de classes, decoradores de
+classes, e metaclasses completas definidas pelo usuário. Melhor ainda, os
+recursos avançados estão integrados de forma que não atrapalham a programação
+casual (eles na verdade ajudam, por trás dos panos). A conveniência e o
+sucesso de frameworks como o Django e o SQLAlchemy devem muito às metaclasses.
+Ao longo dos anos, a metaprogramação de classes em Python está se tornando cada
+vez mais simples, pelo menos para os casos de uso comuns. Os melhores recursos
+da linguagem são aqueles que beneficiam a todos, mesmo que alguns usuários de
+Python não os conheçam. Mas esses usuários sempre podem aprender, e criar a
+próxima grande biblioteca.
+
+Mande notícias sobre suas contribuições ao ecossistema e à comunidade de Python!
+****
+
+<<<
+
diff --git a/vol3/vol3-cor.adoc b/vol3/vol3-cor.adoc
new file mode 100644
index 00000000..4dd55c12
--- /dev/null
+++ b/vol3/vol3-cor.adoc
@@ -0,0 +1,41 @@
+= Python Fluente, 2ª edição: volume 3: Fluxo 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::cap17.adoc[]
+include::cap18.adoc[]
+include::cap19.adoc[]
+include::cap20.adoc[]
+include::cap21.adoc[]
+include::cap22.adoc[]
+include::cap23.adoc[]
+include::cap24.adoc[]
diff --git a/vol3/vol3-pb.adoc b/vol3/vol3-pb.adoc
new file mode 100644
index 00000000..77d09cbc
--- /dev/null
+++ b/vol3/vol3-pb.adoc
@@ -0,0 +1,41 @@
+= Python Fluente, 2ª edição: volume 3: Fluxo e Metaprogramação
+: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: 16
+:revisao: 10
+
+include::Copyright-pb.adoc[]
+
+[dedication%notitle%discrete,toclevels=0]
+= Dedicatória
+__Para Marta, com todo o meu amor.__
+
+toc::[]
+
+= Parte IV: Classes e Protocolos
+:sectnums:
+
+include::cap17.adoc[]
+include::cap18.adoc[]
+include::cap19.adoc[]
+include::cap20.adoc[]
+include::cap21.adoc[]
+include::cap22.adoc[]
+include::cap23.adoc[]
+include::cap24.adoc[]