diff --git a/capitulos/cap06.adoc b/capitulos/cap06.adoc index dc6df13..98aea05 100644 --- a/capitulos/cap06.adoc +++ b/capitulos/cap06.adoc @@ -440,7 +440,7 @@ O((("object references", "function parameters as references", id="ORfparam06"))) 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 proximo exemplo demonstra: +// O próximo exemplo demonstra: [[ex_param_pass]] .Uma função pode mudar qualquer objeto mutável que receba diff --git a/capitulos/cap08.adoc b/capitulos/cap08.adoc index 206532c..79077fb 100644 --- a/capitulos/cap08.adoc +++ b/capitulos/cap08.adoc @@ -25,7 +25,7 @@ Entretanto, as dicas de tipo não beneficiam igualmente a todos as pessoas que u Por isso deverão ser sempre opcionais. A -https://fpy.li/pep484[PEP 484—Type Hints] introduziu a sintaxe e a semântica para desclaraçõ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. +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. @@ -62,7 +62,7 @@ Outras linguagens com sistemas de tipagem gradual são o Typescript da Microsoft Um sistema gradual de tipagem: É 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 compátivel com todos os outros tipos. +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. Não melhora a performance:: @@ -383,7 +383,7 @@ def double(x: abc.Sequence): 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`. +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: @@ -468,7 +468,7 @@ 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ê agredecerá ao Mypy. Observe esse módulo _woody.py_ module, que também usa `birds`, no <>. +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_ @@ -694,7 +694,7 @@ Todo sistema de tipagem gradual precisa de um tipo coringa como `Any` [TIP] ==== -O verbo "inferir" é um sinônimo bonito para "advinhar", 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. +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"))) @@ -848,7 +848,7 @@ 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 a 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 adcionar elementos a arrays. Por exemplo, uma `array` com `typecode=B` só pode receber valores `int` de 0 a 255. Atualmente o sistema de tipagem estática do Python não consegue lidar com esse desafio.((("", startref="GTSgeneric08")))((("", startref="generic08"))) +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 a 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 elementos a arrays. Por exemplo, uma `array` com `typecode=B` só pode receber valores `int` de 0 a 255. Atualmente 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 Legadas e Descontinuadas @@ -911,7 +911,7 @@ Podemos resumir esse processo em quatro etapas: . Tornar aquele comportamento o default no 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. +. 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. **** @@ -958,7 +958,7 @@ include::code/08-def-type-hints/coordinates/coordinates.py[tags=GEOHASH] [TIP] ==== -Com Pythom < 3.9, importe e use `typing.Tuple` nas dicas de tipo. +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. ==== @@ -1111,7 +1111,7 @@ Por outro lado, veja essa assinatura: 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`#. +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`.] @@ -1148,7 +1148,7 @@ Para encerrar nossa discussão de ABCs em dicas de tipo, precisamos falar sobre ===== 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 hieraquia linear de ABCs, com `Number` no topo: +A torre é uma hierarquia linear de ABCs, com `Number` no topo: * `Number` * `Complex` @@ -1230,7 +1230,7 @@ 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 mémó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 esconomia potencial de memória se o número de itens de entrada for grande. +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. @@ -1248,7 +1248,7 @@ 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 implementção. +O <> mostra a implementação. [[generic_sample_ex]] ._sample.py_ @@ -1294,7 +1294,7 @@ Aqui é uma exemplo de uso da https://docs.python.org/pt-br/3/library/statistics 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 implemantação aqui é mais simples que aquela do módulo https://fpy.li/8-29[`statistics`] na biblioteca padrão do Python] +._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] ---- @@ -1302,7 +1302,7 @@ include::code/08-def-type-hints/mode/mode_float.py[tags=MODE_FLOAT] ---- ==== -Muitos dos usos de `mode` envovem 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. +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] @@ -1590,7 +1590,7 @@ O <> demonstra((("static duck typing"))) porque esse recurso é 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 vericadores 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.] +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. @@ -1653,7 +1653,7 @@ A interação de parâmetros de tipo genéricos com uma hierarquia de tipos intr ===== 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 tempertura atual, e chama `display` para mostrar a temperatura para o usuário. +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]] @@ -1666,15 +1666,15 @@ 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 argumneto `float` e retorna `None`. +<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`. +<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> `disply_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 funcã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. +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. @@ -1690,7 +1690,7 @@ A <> no <> explica variância em mais detalhes e co [TIP] ==== -Por hora, saiba que a maioria dos tipos genéricos parametrizafos são _invariantes_, portanto mais simples. +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]`: @@ -1706,7 +1706,7 @@ Agora chegamos ou último tipo especial que examinaremos nesse capítulo. 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. +Há dúzias dessas funções na biblioteca padrão. Por exemplo, `sys.exit()` levanta `SystemExit` para encerrar o processo Python. @@ -1729,7 +1729,7 @@ A última seção desse capítulo épico é sobre parâmetros posicionais e vari ((("", startref="THTusable08")))((("", startref="FTHusable08"))) [[arbitrary_arguments_sec]] -=== Anotanto Parâmetros Apenas Posicionais e Variádicos +=== 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 <>: @@ -1784,7 +1784,7 @@ Para encerrar esse capítulo, vamos considerar brevemente os limites das dicas d === 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 escontrados 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. +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. @@ -1805,7 +1805,7 @@ Em geral, dicas de tipo não são úteis para localizar erros na lógica do neg 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 vericador 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. +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] @@ -1824,7 +1824,7 @@ Até lá, as dicas de tipo ainda vão ter participações especiais em vários e === 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ícl 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. +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. @@ -1842,7 +1842,7 @@ Como só surgiu no Python 3.8, `Protocol` ainda não é muito usado - mas é de `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 chacagem de tipo e tipos inferidos, com a ajuda da função mágica `reveal_type()` do Mypy. +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. @@ -1873,7 +1873,7 @@ Eu sou um grande fã de testes, mas também escrevo muito código exploratório. 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. +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. @@ -1888,7 +1888,7 @@ A documentação do módulo https://docs.python.org/pt-br/3/library/typing.html[ 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 devvelopers (_desenvolvedores principais da linguagem em si_) e o Steering Council do Python, então elas presupõe uma grande quantidade de conhecimento prévio, e certamente não são uma leitura leve. +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 @@ -1948,10 +1948,10 @@ Em 2017 os mantenedores de `requests` https://fpy.li/8-51[decidiram] não perder [quote] ____ -Acho que bibliotecas com APIs 'pithônicas' são as menos propensas a adotar esse sistema de tipagem, pois ele vai adicionar muito pouco valor a elas. +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()`]: +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[ @@ -2017,7 +2017,7 @@ 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: +Alan Kay, o recipiente do Turing Award que foi um dos pioneiros da programação orientada a objetos, certa vez disse: [quote] ____ diff --git a/capitulos/cap13.adoc b/capitulos/cap13.adoc index 86e2d70..766b470 100644 --- a/capitulos/cap13.adoc +++ b/capitulos/cap13.adoc @@ -30,7 +30,7 @@ Dependendo ((("interfaces", "ways of defining and using"))) da linguagem de prog 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 resumí-las assim: +Podemos resumi-las assim: Duck typing (_tipagem pato_):: A((("duck typing"))) abordagem default do Python para tipagem desde o início. @@ -64,7 +64,7 @@ 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] +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. @@ -148,7 +148,7 @@ Esperamos que uma sequência também suporte `len()`, através da implementaçã `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. +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. @@ -194,7 +194,7 @@ Só estou usando a figura para descrever o que uma `Sequence` completa deve ofer [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 Pyhthon 3.6, não existia uma ABC `Collection` - `Sequence` era uma subclasse direta de `Container`, `Iterable` e `Sized`. +.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] @@ -210,7 +210,7 @@ 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 memso quando o método `+__contains__+` não existe: ele faz uma busca sequencial para verificar se o item está presente. +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. @@ -229,7 +229,7 @@ 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 metodos diferentes para iterar sobre objetos. +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`. @@ -241,7 +241,7 @@ Agora vamos estudar outro exemplo que enfatiza a natureza dinâmica dos protocol ==== 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. +"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. @@ -327,7 +327,7 @@ 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. +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 @@ -476,7 +476,7 @@ Em Python, isso essencialmente significa evitar o uso de `isinstance` para verif 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 principalmete 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. +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: @@ -542,8 +542,8 @@ 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 tambem 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. +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] @@ -559,7 +559,7 @@ 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. +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. @@ -898,7 +898,7 @@ ____ 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 palavrs, nenhum outro decorador pode aparecer entre `@abstractmethod` e o comando `def`. +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. @@ -950,10 +950,10 @@ include::code/13-protocol-abc/lotto.py[tags=LOTTERY_BLOWER] 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 atribuimos `self._balls = iterable`, apelidando o argumento). +(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 recebecemos listas no argumento `iterable`, +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: @@ -977,7 +977,7 @@ E mais, neste momento verificadores de tipo estáticos não conseguem tratar sub 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 <>), +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"] @@ -998,7 +998,7 @@ include::code/13-protocol-abc/tombolist.py[] <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 documentáação do Python.] +<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, @@ -1162,7 +1162,7 @@ mas decidi que a apresentação inicial de dicas de tipo em funções precisava 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 discusssão sobre as ABCs numéricas e protocolos. +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]] @@ -1216,7 +1216,7 @@ 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 - se pressume que seu tipo seja a classe. +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`: @@ -1459,7 +1459,7 @@ por apontar que checagem de tipo não é apenas uma questão de verificar se o t Agora veremos como implementar um protocolo estático em uma classe definida pelo usuário. [[support_typing_proto]] -==== Suportanto um Protocolo Estático +==== 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`. @@ -1524,7 +1524,7 @@ Sem o `+__future__+` import of `annotations`, 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. +Quando isso acontecer, a importação será redundante mas inofensiva. Agora vamos criar - e depois estender - um novo protocolo estático.((("", startref="SPsupport13"))) @@ -1606,7 +1606,7 @@ vamos estudar algumas recomendações sobre essa prática.((("", startref="SPdes 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 escreveu um post definindo a https://fpy.li/13-33[_interface papel_]footnote:["papel" aqui é usado no sentido de incorporação de um personagem (NT)], uma ideia útil de ter em mente ao desenvolver protocolos. -Além disso, algumas vezes vemos 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 bibliotecaa separada. +Além disso, algumas vezes vemos 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, algo 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 @@ -1802,11 +1802,11 @@ 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 requisistos da interface definida na ABC. +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` tambem 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, +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 <>, @@ -1877,7 +1877,7 @@ 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 torrre numérica ser inadequada para verificação estática de tipo. +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. @@ -1899,7 +1899,7 @@ PROD: Please check for orphans within the Soapbox. I could not use section 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óteeses centrais do negócio," como definido or meu colega Paulo Caroli in https://fpy.li/13-58["Lean Inception"], +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. @@ -1911,7 +1911,7 @@ Isso foi feito para explicitamente permitir experimentação e receber feedback 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 profisiionais como o https://fpy.li/13-60[PyCharm], +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] @@ -1959,7 +1959,7 @@ Mas o monkey patching pode também ser útil, por exemplo, para fazer uma classe 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. -mAo 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. +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"] diff --git a/capitulos/cap19.adoc b/capitulos/cap19.adoc index 319e103..712ca86 100644 --- a/capitulos/cap19.adoc +++ b/capitulos/cap19.adoc @@ -36,7 +36,7 @@ Após uma pequena introdução conceitual, vamos estudar exemplos simples, para O último terço do capítulo é uma revisão geral de ferramentas, servidores de aplicação e filas de tarefa distribuídas (_distributed task queues_) de vários desenvolvedores—todos capazes de melhorar a performance e 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 segunbda 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 o YouTube, o DropBox, o Instagram, o Reddit e outros foram capazes de obter escalabidade quando começaram, usando Python como sua linguagem primária—apesar de alegações persistentes dizendo que "O Python não escala." +Por isso o YouTube, o DropBox, o Instagram, o Reddit e outros foram capazes de obter escalabilidade quando começaram, usando Python como sua linguagem primária—apesar de alegações persistentes dizendo que "O Python não escala." === Novidades nesse capítulo @@ -52,7 +52,7 @@ A <> é diferente do resto do livro: não há código 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 é bastante simples, mas como acompanhá-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 retornado. Se a função lançar uma exceção, o código de origem pode cercar aquela chamada com um bloco `try/except` para capturar o erro. +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 retornado. Se a função lançar uma exceção, o código de origem pode cercar aquela chamada com um bloco `try/except` para capturar o erro. Essas opções familiares não estão disponíveis quando você inicia uma thread 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. @@ -159,7 +159,7 @@ A thread pode então tentar readquirir a GIL, mas se existirem outras threads es . Quando escrevemos código Python, não temos controle sobre a GIL. Mas uma função embutida ou um 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 escrtia e leitura em disco, escrita e leitura na rede, e `time.sleep()`. Muitas funções de uso intensivo da CPU nas bibliotecas NumPy/SciPy, bem como as funções de compressão e descompressão dos módulos `zlib` and `bz2`, também liberam a GIL.footnote:[Os módulos `zlib` e `bz2` são mencionados nominalmente em uma https://fpy.li/19-6[mensagem de Antoine Pitrou na python-dev] (EN). Pitrou contribuiu para a lógica da divisão de tempo da GIL no Python 3.2.] +. Toda função na biblioteca padrão 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_. @@ -211,7 +211,7 @@ na mesma posição da tela.footnote:[O Unicode tem muitos caracteres úteis para <> 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á substituida por "Answer: 42" após 3 segundos. +.Os scripts spinner_thread.py e spinner_async.py produzem um resultado similar: o repr do objeto spinner e o texto "Answer: 42". Na captura de tela, spinner_async.py ainda está rodando, e a mensagem animada "/ thinking!" é apresentada; aquela linha será substituída por "Answer: 42" após 3 segundos. image::images/flpy_1901.png[Captura de tela do console mostrando a saída dos dois exemplos.] Vamos revisar o script _spinner_thread.py_ primeiro. O <> @@ -281,7 +281,7 @@ Agora vamos ver um exemplo similar usando o pacote `multiprocessing`.((("", star O((("spinners (loading indicators)", "created with multiprocessing package")))((("multiprocessing package"))) pacote `multiprocessing` permite executar tarefas concorrentes em processos Python separados em vez de threads. Quando você cria uma instância de `multiprocessing.Process`, todo um novo interpretador Python é iniciado como um processo filho, em segundo plano. -Como cada processo Python tem sua prórpia 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. +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_ (<>). @@ -317,7 +317,7 @@ No <>, o único dado que cruza a fronteira entre os processos [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 bibloteca padrão, mas ele não suporta instâncias de classes definidas pelo usuário. +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`] @@ -343,7 +343,7 @@ Alocar tempo da CPU para a execução de threads e processos é trabalho dos age 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 comerçarmos por uma função `main`, e depois olharmos a `supervisor`. +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]] @@ -381,7 +381,7 @@ The same issue is affecting other recently introduced Python keywords: `await`, O <> demonstra as três principais formas de rodar uma corrotina: `asyncio.run(coro())`:: - É chamado a partir de uma funcã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. + É 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. @@ -430,7 +430,7 @@ Vai lá, eu espero. Quando você roda o experimento, você vê isso: -. O ponterio animado é mostrado, de forma similar a isso: `>`. +. O ponteiro animado é mostrado, de forma similar a isso: `>`. . A animação nunca aparece. O programa trava por 3 segundos. . `Answer: 42` aparece e o programa termina. @@ -551,7 +551,7 @@ Para entregar o controle, você usa `await` para passar o controle de volta ao a 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 intesivo da CPU, para entender melhor a GIL, bem como o efeito de funções de processamento intensivo sobre código assíncrono.((("", startref="CMhello19"))) +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"))) @@ -588,7 +588,7 @@ Uma das partes da resposta é um pouco mais complicada (pelo menos para mim foi) [quote] ____ -O quê aconteceria à animação se fossem feitas as seguintes modificações, pressumindo que `n = 5_000_111_000_222_021`—aquele mesmo número primo que minha máquina levou 3,3s para checar: +O quê aconteceria à animação se fossem feitas as seguintes modificações, presumindo que `n = 5_000_111_000_222_021`—aquele 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)`? @@ -770,7 +770,7 @@ O tempo total neste caso é muito menor que a soma dos tempos decorridos para ca [NOTE] ==== -A função `multiprocessing.cpu_count()` retorna `12` no MacBook Pro que estou usando para escrever esse capítulo. Ele é na verdade um i7 com uma CPU de 6 núcleos, mas o SO informa 12 CPUs devido ao _hyperthreading_, uma tecnologia da Intel que executa duas threads por núcleo. Entretando, _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. +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]] @@ -943,7 +943,7 @@ 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 precisa de forma alguma 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ítmo 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.] +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 que linguagem goza hoje. ==== @@ -983,7 +983,7 @@ Em 2021, o ecossistema de ciência de dados de Python já incluia algumas ferram 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/Javascripot, 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. + 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 à performance das engines e dos navegadores Javascript modernos. @@ -1095,7 +1095,7 @@ 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 possivel ganhar a vida como desenvolvedor Python sem nunca ter estudado os módulos `threading`, `multiprocessing`, ou `asyncio`: +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]] @@ -1117,7 +1117,7 @@ Agora vamos examinar outra forma de evitar a GIL para obter uma melhor performan 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. -Entretando, algumas requisições exigem ações que podem demorar—por exemplo, enviar um email ou gerar um PDF. +Entretanto, algumas requisições exigem ações que podem demorar—por exemplo, enviar um email ou gerar um PDF. Este é o problema que filas de tarefas distribuídas foram projetadas para resolver. A https://fpy.li/19-47[_Celery_] e a https://fpy.li/19-48[_RQ_] são as mais conhecidas filas de tarefas de código aberto com uma API Python. @@ -1127,7 +1127,7 @@ Esses produtos encapsulam uma fila de mensagens e oferecem uma API de alto níve [NOTE] ==== -No contexto de filas de tarefaas, as palavras _produtor_ e _consumidor_ são usado no lugar da terminologia tradicional de cliente/servidor. Por exemplo, um processador de views _Django_ _produz_ requisições de serviço, que são colocadas em uma fila para serem _consumidas_ por um ou mais processos renderizadores de PDFs. +No contexto de filas de tarefas, as palavras _produtor_ e _consumidor_ são usado no lugar da terminologia tradicional de cliente/servidor. Por exemplo, um processador de views _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, cá estão alguns casos de uso: @@ -1285,7 +1285,7 @@ Ambos os pacotes são baseados na biblioteca _multiprocessing_. Eric Snow, um dos desenvolvedores principais 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útiplos subinterpretadores ativos e extensões que assumem a existência de um único interpretador. +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] @@ -1317,8 +1317,8 @@ recomendo esse livro por sua abordagem dos conceitos, da motivação e dos model 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:[Trocadilho intraduzível com thread no sentido de "fio"ou "linha", algo como "Quando os fios desfiam" (NT)] -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 influencidas 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 diferenes linguagens, ferramentas e bibliotecas. +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. @@ -1406,7 +1406,7 @@ Ao resumir o capítulo 1 de _Seven Concurrency Models in Seven Weeks_, Paul Butc [quote] ____ -A maior fraqueza da abordagem, entretando, é que programação com threads—e—travas é _difícil_. +A maior fraqueza da abordagem, entretanto, é que programação com threads—e—travas é _difícil_. Pode ser fácil para um projetista de linguagens acrescentá-las a uma linguagem, mas elas nos dão, a nós pobres programadores, muito pouca ajuda. ____