Julio Biason
2 years ago
5 changed files with 283 additions and 12 deletions
@ -0,0 +1,225 @@
|
||||
+++ |
||||
title = "Pyngos de Python I" |
||||
date = 2023-03-27 |
||||
|
||||
[taxonomies] |
||||
tags = ["python", "generators"] |
||||
+++ |
||||
|
||||
"Pyngos de Python" são pequenas explicações de Python. |
||||
|
||||
Nesse post, vamos falor sobre generators. |
||||
|
||||
<!-- more --> |
||||
|
||||
Vamos começar falando sobre list comprehensions, que são bem comuns em Python. |
||||
De forma gera, um list comprehension é definido como |
||||
|
||||
```python |
||||
[transformação |
||||
for variável |
||||
in iterável |
||||
if condição] |
||||
``` |
||||
|
||||
* `iterável` é o container com os elementos que queremos percorrer; |
||||
* `variável` define qual vai ser o nome da variável que vamos lidar cada um dos |
||||
elementos do `iterável`; |
||||
* `transformação` é qualquer transformação que queremos fazer sobre `variável`; |
||||
* `condição` é um opcional caso queiremos processar apenas alguns elementos. |
||||
|
||||
Um exemplo de list comprehension em ação: |
||||
|
||||
```python |
||||
lista = [1, 2, 3, 4] |
||||
lc = [i * 2 for i in lista] |
||||
print(lc) # [2, 4, 6, 8] |
||||
``` |
||||
|
||||
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. |
||||
|
||||
E, em alguns casos, isso não é necessário. |
||||
|
||||
Antes de ver onde generators podem ser usados, veremos a sintaxe de um: |
||||
|
||||
```python |
||||
(transformação |
||||
for variável |
||||
in iterável |
||||
if condição) |
||||
``` |
||||
|
||||
Como pode ser visto, a sintaxe é bem semelhante; a diferença é que |
||||
comprehensions usam `[]`, enquanto generators usam `()`. |
||||
|
||||
E como exemplo: |
||||
|
||||
```python |
||||
lista = [1, 2, 3, 4] |
||||
gen = (i * 2 for i in lista) |
||||
print(gen) # <generator object <genexpr> at 0x7f7f30843df0> |
||||
``` |
||||
|
||||
O que diabos é esse `generator object`? |
||||
|
||||
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 `next`; quando o generator encontra o final do iterável, ele levanta a |
||||
exceção `StopIteration`: |
||||
|
||||
```python |
||||
lista = [1, 2, 3, 4] |
||||
gen = (i * 2 for i in lista) |
||||
print(next(gen)) # 2 |
||||
print(next(gen)) # 4 |
||||
print(next(gen)) # 6 |
||||
print(next(gen)) # 8 |
||||
print(next(gen)) # Exceção: StopIteration |
||||
``` |
||||
|
||||
Curiosamente, `for` sabe lidar com `StopIteration` e `next()`, o que torna |
||||
possível usar um generator diretamente no `for`: |
||||
|
||||
```python |
||||
lista = [1, 2, 3, 4] |
||||
for i in (i * 2 for i in l): |
||||
print(i) # 2, 4, 6, 8 |
||||
# Nenhuma exceção aqui. |
||||
``` |
||||
|
||||
Mas é a vantagem de usar generators? |
||||
|
||||
A primeira vantagem pode ser vista no `for` acima: Imagine que `lista` tem |
||||
500.000 elementos. Usar list comprehensions não mudaria nada no código (com a |
||||
exceção de usar `[]` ao invés de `()`), 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. |
||||
|
||||
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: |
||||
|
||||
```python |
||||
with open('arquivo.csv') as origem: |
||||
for registro in (linha.split(',') for linha in origem): |
||||
if registro[1] == 'entrada': |
||||
return registro[2] |
||||
``` |
||||
|
||||
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 `for` 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[^1] e feito o split somente uma vez. |
||||
|
||||
## BÔNUS: Generator Functions! |
||||
|
||||
Existe uma forma de criar uma função que age como um generator, usando o |
||||
statement `yield`, da mesma forma que se usaria o statement `return`. A |
||||
diferença é que quando o Python encontra `yield`, ao invés de destruir tudo que |
||||
estava na função, ele guarda a posição atual e, na chamada do `next()`, |
||||
continua naquela posição. |
||||
|
||||
Por exemplo, se tivermos: |
||||
|
||||
```python |
||||
def double(lista): |
||||
for i in lista: |
||||
return i * 2 |
||||
|
||||
double([1, 2, 3, 4]) |
||||
``` |
||||
|
||||
Irá retornar apenas `2` porque, ao ver o `return`, o Python vai destruir tudo |
||||
que a função já fez e retornar o valor indicado -- incluindo encerrar o `for` |
||||
antes de chegar no final. |
||||
|
||||
Com generator functions, teríamos: |
||||
|
||||
```python |
||||
def double(lista): |
||||
for i in lista: |
||||
return i |
||||
|
||||
|
||||
gen = double([1, 2, 3, 4]) |
||||
next(gen) # 2 |
||||
next(gen) # 4 |
||||
next(gen) # 6 |
||||
next(gen) # 8 |
||||
next(gen) # StopIteration |
||||
``` |
||||
|
||||
Note que a chamada para a função é que retorna um generator. Tentar fazer |
||||
|
||||
```python |
||||
def double(lista): |
||||
for i in lista: |
||||
return i |
||||
|
||||
|
||||
next(double([1, 2, 3, 4])) # 2 |
||||
next(double([1, 2, 3, 4])) # 2 |
||||
next(double([1, 2, 3, 4])) # 2 |
||||
next(double([1, 2, 3, 4])) # 2 |
||||
... |
||||
``` |
||||
|
||||
... vai gerar um novo generator a cada chamada. |
||||
|
||||
Ainda, é possível que a função tenha mais de um `yield`: |
||||
|
||||
```python |
||||
def double(lista): |
||||
yield lista[0] * 2 |
||||
yield lista[1] * 2 |
||||
yield lista[2] * 2 |
||||
|
||||
gen = double([4, 3, 2, 1]) |
||||
next(gen) # 8 |
||||
next(gen) # 6 |
||||
next(gen) # 4 |
||||
next(gen) # StopIteration |
||||
``` |
||||
|
||||
Aqui, a primeira chamada de `next()` vai retornar o valor do primeiro `yield`, |
||||
que é o primeiro elemento da lista multiplicado por 2; o próximo `next()` vai |
||||
executar o comando logo depois do primeiro `yield`, que é o segundo `yield`; e |
||||
a terceira chamada vai continuar a execução logo depois desse, que é o terceiro |
||||
`yield`. Como o código termina aí, o generator vai levantar a exceção |
||||
`StopIteration`. |
||||
|
||||
Mas o que aconteceria se... a função nunca retornasse nada? |
||||
|
||||
```python |
||||
def gen(): |
||||
i = 0 |
||||
while True: |
||||
yield i * 2 |
||||
i += 1 |
||||
``` |
||||
|
||||
Neste caso, usando `next()` no generator, a primeira vez será retornado "0"; o |
||||
`next()` 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). |
||||
|
||||
--- |
||||
|
||||
[^1]: 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. |
@ -0,0 +1,11 @@
|
||||
+++ |
||||
title = "Degenerated Brief Tin" |
||||
date = 2023-01-04 |
||||
|
||||
[taxonomies] |
||||
tags = ["projects", "personal", "mastodon", "activitypub", "download", "bookmarks"] |
||||
+++ |
||||
|
||||
Download Bookmarked Toots. |
||||
|
||||
* [Megalodon](https://docs.rs/megalodon/latest/megalodon/index.html) |
Loading…
Reference in new issue