You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
245 lines
20 KiB
245 lines
20 KiB
11 months ago
|
<!DOCTYPE html>
|
||
|
<html lang="en">
|
||
|
<head>
|
||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||
|
|
||
|
<!-- Enable responsiveness on mobile devices-->
|
||
|
<!-- viewport-fit=cover is to support iPhone X rounded corners and notch in landscape-->
|
||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, viewport-fit=cover">
|
||
|
|
||
|
<title>Julio Biason .Me 4.3</title>
|
||
|
|
||
|
<!-- CSS -->
|
||
|
<link rel="stylesheet" href="https://blog.juliobiason.me/print.css" media="print">
|
||
|
<link rel="stylesheet" href="https://blog.juliobiason.me/poole.css">
|
||
|
<link rel="stylesheet" href="https://blog.juliobiason.me/hyde.css">
|
||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Sans:400,400italic,700|Abril+Fatface">
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
</head>
|
||
|
|
||
|
<body class=" ">
|
||
|
|
||
|
<div class="sidebar">
|
||
|
<div class="container sidebar-sticky">
|
||
|
<div class="sidebar-about">
|
||
|
|
||
|
<a href="https://blog.juliobiason.me"><h1>Julio Biason .Me 4.3</h1></a>
|
||
|
|
||
|
<p class="lead">Old school dev living in a 2.0 dev world</p>
|
||
|
|
||
|
|
||
|
</div>
|
||
|
|
||
|
<ul class="sidebar-nav">
|
||
|
|
||
|
|
||
|
<li class="sidebar-nav-item"><a href="/">English</a></li>
|
||
|
|
||
|
<li class="sidebar-nav-item"><a href="/pt">Português</a></li>
|
||
|
|
||
|
<li class="sidebar-nav-item"><a href="/tags">Tags (EN)</a></li>
|
||
|
|
||
|
<li class="sidebar-nav-item"><a href="/pt/tags">Tags (PT)</a></li>
|
||
|
|
||
|
|
||
|
</ul>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
|
||
|
<div class="content container">
|
||
|
|
||
|
<div class="post">
|
||
|
<h1 class="post-title">Pyngos de Python I</h1>
|
||
|
<span class="post-date">
|
||
|
2023-03-27
|
||
|
|
||
|
<a href="https://blog.juliobiason.me/pt/tags/python/">#python</a>
|
||
|
|
||
|
<a href="https://blog.juliobiason.me/pt/tags/generators/">#generators</a>
|
||
|
|
||
|
</span>
|
||
|
<p>"Pyngos de Python" são pequenas explicações de Python.</p>
|
||
|
<p>Nesse post, vamos falor sobre generators.</p>
|
||
|
<span id="continue-reading"></span>
|
||
|
<p>Vamos começar falando sobre list comprehensions, que são bem comuns em Python.
|
||
|
De forma gera, um list comprehension é definido como</p>
|
||
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span>[transformação
|
||
|
</span><span> </span><span style="color:#b48ead;">for </span><span>variável
|
||
|
</span><span> </span><span style="color:#b48ead;">in </span><span>iterável
|
||
|
</span><span> </span><span style="color:#b48ead;">if </span><span>condição]
|
||
|
</span></code></pre>
|
||
|
<ul>
|
||
|
<li><code>iterável</code> é o container com os elementos que queremos percorrer;</li>
|
||
|
<li><code>variável</code> define qual vai ser o nome da variável que vamos lidar cada um dos
|
||
|
elementos do <code>iterável</code>;</li>
|
||
|
<li><code>transformação</code> é qualquer transformação que queremos fazer sobre <code>variável</code>;</li>
|
||
|
<li><code>condição</code> é um opcional caso queiremos processar apenas alguns elementos.</li>
|
||
|
</ul>
|
||
|
<p>Um exemplo de list comprehension em ação:</p>
|
||
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span>lista = [</span><span style="color:#d08770;">1</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">4</span><span>]
|
||
|
</span><span>lc = [i * </span><span style="color:#d08770;">2 </span><span style="color:#b48ead;">for </span><span>i </span><span style="color:#b48ead;">in </span><span>lista]
|
||
|
</span><span style="color:#96b5b4;">print</span><span>(lc) </span><span style="color:#65737e;"># [2, 4, 6, 8]
|
||
|
</span></code></pre>
|
||
|
<p>Embora útil, existe um problema: List comprehensions geram uma lista com, no
|
||
|
máximo, o mesmo tamanho do iterável original; se você tiver um array de 500.000
|
||
|
elementos, um list comprehension que não tenha uma condição vai gerar outro
|
||
|
array com 500.000 elementos.</p>
|
||
|
<p>E, em alguns casos, isso não é necessário.</p>
|
||
|
<p>Antes de ver onde generators podem ser usados, veremos a sintaxe de um:</p>
|
||
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span>(transformação
|
||
|
</span><span> </span><span style="color:#b48ead;">for </span><span>variável
|
||
|
</span><span> </span><span style="color:#b48ead;">in </span><span>iterável
|
||
|
</span><span> </span><span style="color:#b48ead;">if </span><span>condição)
|
||
|
</span></code></pre>
|
||
|
<p>Como pode ser visto, a sintaxe é bem semelhante; a diferença é que
|
||
|
comprehensions usam <code>[]</code>, enquanto generators usam <code>()</code>.</p>
|
||
|
<p>E como exemplo:</p>
|
||
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span>lista = [</span><span style="color:#d08770;">1</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">4</span><span>]
|
||
|
</span><span>gen = (i * </span><span style="color:#d08770;">2 </span><span style="color:#b48ead;">for </span><span>i </span><span style="color:#b48ead;">in </span><span>lista)
|
||
|
</span><span style="color:#96b5b4;">print</span><span>(gen) </span><span style="color:#65737e;"># <generator object <genexpr> at 0x7f7f30843df0>
|
||
|
</span></code></pre>
|
||
|
<p>O que diabos é esse <code>generator object</code>?</p>
|
||
|
<p>Generators não geram os dados todos numa passada; os dados somente são
|
||
|
processados quando pedidos. A forma de pedir o próximo elemento é usando a
|
||
|
função <code>next</code>; quando o generator encontra o final do iterável, ele levanta a
|
||
|
exceção <code>StopIteration</code>:</p>
|
||
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span>lista = [</span><span style="color:#d08770;">1</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">4</span><span>]
|
||
|
</span><span>gen = (i * </span><span style="color:#d08770;">2 </span><span style="color:#b48ead;">for </span><span>i </span><span style="color:#b48ead;">in </span><span>lista)
|
||
|
</span><span style="color:#96b5b4;">print</span><span>(</span><span style="color:#96b5b4;">next</span><span>(gen)) </span><span style="color:#65737e;"># 2
|
||
|
</span><span style="color:#96b5b4;">print</span><span>(</span><span style="color:#96b5b4;">next</span><span>(gen)) </span><span style="color:#65737e;"># 4
|
||
|
</span><span style="color:#96b5b4;">print</span><span>(</span><span style="color:#96b5b4;">next</span><span>(gen)) </span><span style="color:#65737e;"># 6
|
||
|
</span><span style="color:#96b5b4;">print</span><span>(</span><span style="color:#96b5b4;">next</span><span>(gen)) </span><span style="color:#65737e;"># 8
|
||
|
</span><span style="color:#96b5b4;">print</span><span>(</span><span style="color:#96b5b4;">next</span><span>(gen)) </span><span style="color:#65737e;"># Exceção: StopIteration
|
||
|
</span></code></pre>
|
||
|
<p>Curiosamente, <code>for</code> sabe lidar com <code>StopIteration</code> e <code>next()</code>, o que torna
|
||
|
possível usar um generator diretamente no <code>for</code>:</p>
|
||
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span>lista = [</span><span style="color:#d08770;">1</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">4</span><span>]
|
||
|
</span><span style="color:#b48ead;">for </span><span>i </span><span style="color:#b48ead;">in </span><span>(i * </span><span style="color:#d08770;">2 </span><span style="color:#b48ead;">for </span><span>i </span><span style="color:#b48ead;">in </span><span>l):
|
||
|
</span><span> </span><span style="color:#96b5b4;">print</span><span>(i) </span><span style="color:#65737e;"># 2, 4, 6, 8
|
||
|
</span><span style="color:#65737e;"># Nenhuma exceção aqui.
|
||
|
</span></code></pre>
|
||
|
<p>Mas é a vantagem de usar generators?</p>
|
||
|
<p>A primeira vantagem pode ser vista no <code>for</code> acima: Imagine que <code>lista</code> tem
|
||
|
500.000 elementos. Usar list comprehensions não mudaria nada no código (com a
|
||
|
exceção de usar <code>[]</code> ao invés de <code>()</code>), mas estamos gerando a multiplicação
|
||
|
somente quando necessário. Agora imagine que estamos procurando algo na lista
|
||
|
original e vamos parar assim que encontrarmos o registro: com list
|
||
|
comprehension, a nova lista será sempre gerada, e se o o elemento procurado for
|
||
|
o primeiro, acabamos gerando 499.999 elementos que não vamos usar. Com
|
||
|
generators, no momento que encerramos a procura, nada mais é gerado -- e
|
||
|
somente o elemento procurado é gerado.</p>
|
||
|
<p>Um exemplo mais real: Arquivos são iteráveis, onde cada requisição é uma linha
|
||
|
do arquivo. Se o arquivo sendo processado é um CSV, podemos fazer um generator
|
||
|
que separa os campos sobre a iteração do arquivo enquanto procuramos um
|
||
|
registro específico:</p>
|
||
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">with </span><span style="color:#96b5b4;">open</span><span>('</span><span style="color:#a3be8c;">arquivo.csv</span><span>') </span><span style="color:#b48ead;">as </span><span>origem:
|
||
|
</span><span> </span><span style="color:#b48ead;">for </span><span>registro </span><span style="color:#b48ead;">in </span><span>(linha.</span><span style="color:#bf616a;">split</span><span>('</span><span style="color:#a3be8c;">,</span><span>') </span><span style="color:#b48ead;">for </span><span>linha </span><span style="color:#b48ead;">in </span><span>origem):
|
||
|
</span><span> </span><span style="color:#b48ead;">if </span><span>registro[</span><span style="color:#d08770;">1</span><span>] == '</span><span style="color:#a3be8c;">entrada</span><span>':
|
||
|
</span><span> </span><span style="color:#b48ead;">return </span><span>registro[</span><span style="color:#d08770;">2</span><span>]
|
||
|
</span></code></pre>
|
||
|
<p>Neste código, estamos procurando a linha do CSV cujo 2o elemento (listas
|
||
|
começam em 0) tem o valor "entrada"; quando encontrarmos, retornamos o valor da
|
||
|
coluna seguinte. A medida que o <code>for</code> for pedindo valores, o generator é
|
||
|
chamado; o generator que criamos quebra a linha usando "," como separador; como
|
||
|
o generator usa o iterável do arquivo (que, por baixo dos panos, também é um
|
||
|
generator), somente quando for pedido um registro é que uma linha será lida;
|
||
|
somente quando a linha vier é que vai ser feito o split. E se, por algum
|
||
|
motivo, o registro procurando for o primeiro, foi somente lida uma linha do
|
||
|
arquivo<sup class="footnote-reference"><a href="#1">1</a></sup> e feito o split somente uma vez.</p>
|
||
|
<h2 id="bonus-generator-functions">BÔNUS: Generator Functions!</h2>
|
||
|
<p>Existe uma forma de criar uma função que age como um generator, usando o
|
||
|
statement <code>yield</code>, da mesma forma que se usaria o statement <code>return</code>. A
|
||
|
diferença é que quando o Python encontra <code>yield</code>, ao invés de destruir tudo que
|
||
|
estava na função, ele guarda a posição atual e, na chamada do <code>next()</code>,
|
||
|
continua naquela posição.</p>
|
||
|
<p>Por exemplo, se tivermos:</p>
|
||
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">double</span><span>(</span><span style="color:#bf616a;">lista</span><span>):
|
||
|
</span><span> </span><span style="color:#b48ead;">for </span><span>i </span><span style="color:#b48ead;">in </span><span>lista:
|
||
|
</span><span> </span><span style="color:#b48ead;">return </span><span>i * </span><span style="color:#d08770;">2
|
||
|
</span><span>
|
||
|
</span><span style="color:#bf616a;">double</span><span>([</span><span style="color:#d08770;">1</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">4</span><span>])
|
||
|
</span></code></pre>
|
||
|
<p>Irá retornar apenas <code>2</code> porque, ao ver o <code>return</code>, o Python vai destruir tudo
|
||
|
que a função já fez e retornar o valor indicado -- incluindo encerrar o <code>for</code>
|
||
|
antes de chegar no final.</p>
|
||
|
<p>Com generator functions, teríamos:</p>
|
||
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">double</span><span>(</span><span style="color:#bf616a;">lista</span><span>):
|
||
|
</span><span> </span><span style="color:#b48ead;">for </span><span>i </span><span style="color:#b48ead;">in </span><span>lista:
|
||
|
</span><span> </span><span style="color:#b48ead;">return </span><span>i
|
||
|
</span><span>
|
||
|
</span><span>
|
||
|
</span><span>gen = </span><span style="color:#bf616a;">double</span><span>([</span><span style="color:#d08770;">1</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">4</span><span>])
|
||
|
</span><span style="color:#96b5b4;">next</span><span>(gen) </span><span style="color:#65737e;"># 2
|
||
|
</span><span style="color:#96b5b4;">next</span><span>(gen) </span><span style="color:#65737e;"># 4
|
||
|
</span><span style="color:#96b5b4;">next</span><span>(gen) </span><span style="color:#65737e;"># 6
|
||
|
</span><span style="color:#96b5b4;">next</span><span>(gen) </span><span style="color:#65737e;"># 8
|
||
|
</span><span style="color:#96b5b4;">next</span><span>(gen) </span><span style="color:#65737e;"># StopIteration
|
||
|
</span></code></pre>
|
||
|
<p>Note que a chamada para a função é que retorna um generator. Tentar fazer</p>
|
||
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">double</span><span>(</span><span style="color:#bf616a;">lista</span><span>):
|
||
|
</span><span> </span><span style="color:#b48ead;">for </span><span>i </span><span style="color:#b48ead;">in </span><span>lista:
|
||
|
</span><span> </span><span style="color:#b48ead;">return </span><span>i
|
||
|
</span><span>
|
||
|
</span><span>
|
||
|
</span><span style="color:#96b5b4;">next</span><span>(</span><span style="color:#bf616a;">double</span><span>([</span><span style="color:#d08770;">1</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">4</span><span>])) </span><span style="color:#65737e;"># 2
|
||
|
</span><span style="color:#96b5b4;">next</span><span>(</span><span style="color:#bf616a;">double</span><span>([</span><span style="color:#d08770;">1</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">4</span><span>])) </span><span style="color:#65737e;"># 2
|
||
|
</span><span style="color:#96b5b4;">next</span><span>(</span><span style="color:#bf616a;">double</span><span>([</span><span style="color:#d08770;">1</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">4</span><span>])) </span><span style="color:#65737e;"># 2
|
||
|
</span><span style="color:#96b5b4;">next</span><span>(</span><span style="color:#bf616a;">double</span><span>([</span><span style="color:#d08770;">1</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">4</span><span>])) </span><span style="color:#65737e;"># 2
|
||
|
</span><span style="color:#d08770;">...
|
||
|
</span></code></pre>
|
||
|
<p>... vai gerar um novo generator a cada chamada.</p>
|
||
|
<p>Ainda, é possível que a função tenha mais de um <code>yield</code>:</p>
|
||
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">double</span><span>(</span><span style="color:#bf616a;">lista</span><span>):
|
||
|
</span><span> </span><span style="color:#b48ead;">yield </span><span>lista[</span><span style="color:#d08770;">0</span><span>] * </span><span style="color:#d08770;">2
|
||
|
</span><span> </span><span style="color:#b48ead;">yield </span><span>lista[</span><span style="color:#d08770;">1</span><span>] * </span><span style="color:#d08770;">2
|
||
|
</span><span> </span><span style="color:#b48ead;">yield </span><span>lista[</span><span style="color:#d08770;">2</span><span>] * </span><span style="color:#d08770;">2
|
||
|
</span><span>
|
||
|
</span><span>gen = </span><span style="color:#bf616a;">double</span><span>([</span><span style="color:#d08770;">4</span><span>, </span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">1</span><span>])
|
||
|
</span><span style="color:#96b5b4;">next</span><span>(gen) </span><span style="color:#65737e;"># 8
|
||
|
</span><span style="color:#96b5b4;">next</span><span>(gen) </span><span style="color:#65737e;"># 6
|
||
|
</span><span style="color:#96b5b4;">next</span><span>(gen) </span><span style="color:#65737e;"># 4
|
||
|
</span><span style="color:#96b5b4;">next</span><span>(gen) </span><span style="color:#65737e;"># StopIteration
|
||
|
</span></code></pre>
|
||
|
<p>Aqui, a primeira chamada de <code>next()</code> vai retornar o valor do primeiro <code>yield</code>,
|
||
|
que é o primeiro elemento da lista multiplicado por 2; o próximo <code>next()</code> vai
|
||
|
executar o comando logo depois do primeiro <code>yield</code>, que é o segundo <code>yield</code>; e
|
||
|
a terceira chamada vai continuar a execução logo depois desse, que é o terceiro
|
||
|
<code>yield</code>. Como o código termina aí, o generator vai levantar a exceção
|
||
|
<code>StopIteration</code>.</p>
|
||
|
<p>Mas o que aconteceria se... a função nunca retornasse nada?</p>
|
||
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">gen</span><span>():
|
||
|
</span><span> i = </span><span style="color:#d08770;">0
|
||
|
</span><span> </span><span style="color:#b48ead;">while </span><span style="color:#d08770;">True</span><span>:
|
||
|
</span><span> </span><span style="color:#b48ead;">yield </span><span>i * </span><span style="color:#d08770;">2
|
||
|
</span><span> i += </span><span style="color:#d08770;">1
|
||
|
</span></code></pre>
|
||
|
<p>Neste caso, usando <code>next()</code> no generator, a primeira vez será retornado "0"; o
|
||
|
<code>next()</code> seguinte irá continuar o código, somando "1" ao nosso contador,
|
||
|
retornando para o começo do loop e retornando "2"; e assim sucessivamente até o
|
||
|
fim do mundo (ou até ser pressionado Ctrl+C, desligado o computador ou atingido
|
||
|
o número máximo permitido para inteiros em Python).</p>
|
||
|
<hr />
|
||
|
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
|
||
|
<p>Tecnicamente, vai ser lido mais, porque o Python usa "buffers" de
|
||
|
leitura, carregando blocos e depois enviando apenas os bytes desde a última
|
||
|
posição lida até o caracter de nova linha. Mas, para simplificar as coisas,
|
||
|
imaginem que apenas uma linha é lida mesmo.</p>
|
||
|
</div>
|
||
|
|
||
|
</div>
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
</div>
|
||
|
|
||
|
</body>
|
||
|
|
||
|
</html>
|