Julio Biason
5 years ago
4 changed files with 719 additions and 0 deletions
@ -0,0 +1,75 @@ |
|||||||
|
+++ |
||||||
|
title = "Microservices: Chassis" |
||||||
|
date = 2020-04-17 |
||||||
|
|
||||||
|
[taxonomies] |
||||||
|
tags = ["microservices", "chassis", "frameworks", "languages"] |
||||||
|
+++ |
||||||
|
|
||||||
|
The chassis for a microservices fleet is defined as the libraries and |
||||||
|
frameworks that one should use when creating a new microservice. |
||||||
|
|
||||||
|
<!-- more --> |
||||||
|
|
||||||
|
The "chassis" is actually a [known |
||||||
|
pattern](https://microservices.io/patterns/microservice-chassis.html), but the |
||||||
|
literature points it as a decision about the libraries and frameworks that |
||||||
|
should be used when creating microservices. |
||||||
|
|
||||||
|
For example, if you're working with Java, you'd probably pick something like |
||||||
|
Spring Boot as a chassis for your microservices, in a way that anyone creating |
||||||
|
a new microservice already have the library (and local knowledge) on how to |
||||||
|
build it. |
||||||
|
|
||||||
|
And, for each language, you need to pick a different chassis -- you can't use |
||||||
|
Spring Boot with Python, for example. |
||||||
|
|
||||||
|
You may have noticed that "but" in the second paragraph. Personally, I think |
||||||
|
the choice of chassis go way beyond just creating a microservices. |
||||||
|
|
||||||
|
## Shared Knowledge |
||||||
|
|
||||||
|
One of the major factors of using chassis for your microservices is the shared |
||||||
|
knowledge between teams. Teams that are using the same chassis can exchange |
||||||
|
solutions on how to solve some problems, how to make processing faster, new |
||||||
|
releases information and so on. |
||||||
|
|
||||||
|
Even if the teams will never touch each other's code, the simply fact that |
||||||
|
they can share these information between them is a huge boom. |
||||||
|
|
||||||
|
And even for teams using different languages is a major point: One team can |
||||||
|
point that their framework allows them to do things in a more simpler way that |
||||||
|
can be researched by some other team, using a complete different framework, in |
||||||
|
a complete different language, on how to build the same stuff. |
||||||
|
|
||||||
|
## Applying a Common View |
||||||
|
|
||||||
|
While the pattern describes only frameworks and libraries, the choices of |
||||||
|
surrounding services also makes part of the chassis, in my opinion. |
||||||
|
|
||||||
|
For example, the teams pick Kafka as a messaging broker between services -- |
||||||
|
which would allow any team, on any framework, on any language, to use the same |
||||||
|
service for exchanging messages -- allowing any team that has a need |
||||||
|
for a message broker can use the same install (but using different |
||||||
|
topics) and reduce maintenance costs. But what happens when one team decides |
||||||
|
to use Kafka as a database and put a retention to "forever"? That would |
||||||
|
utterly confused everyone else. "Why is this topic getting bigger and |
||||||
|
bigger?" Worse, without a well described DevOps documentation, someone may see |
||||||
|
that growing topic, check it out, see that is has no retention policy and add |
||||||
|
one based on the other projects. |
||||||
|
|
||||||
|
Another example: For highly relational data, there is a PostgreSQL |
||||||
|
installation for everyone. Each team have their own database and users. But |
||||||
|
one team, which got responsible for two microservices, have one with data |
||||||
|
which is relational and another that basically requires a key-value store. |
||||||
|
Instead of asking for key-value store, they decide to create a database with a |
||||||
|
single table, with a key field and a large text field for storing JSONs. |
||||||
|
This, again, breaks the defined chassis, as one would expect PostgreSQL to be |
||||||
|
the relational database and not as a key-value store. |
||||||
|
|
||||||
|
## Conclusion |
||||||
|
|
||||||
|
Chassis are good for microservices development due their quick development |
||||||
|
start up and shared knowledge, but they go way beyond just frameworks and |
||||||
|
libraries: They related to everything around the services and the way they are |
||||||
|
viewed by each microservice. |
@ -0,0 +1,88 @@ |
|||||||
|
+++ |
||||||
|
title = "Microserviços: Chassi" |
||||||
|
date = 2020-04-17 |
||||||
|
|
||||||
|
[taxonomies] |
||||||
|
tags = ["microserviços", "chassi", "framework", "linguagens"] |
||||||
|
+++ |
||||||
|
|
||||||
|
O chassi de um fleet de microserviços é definido como as bibliotecas e |
||||||
|
frameworks que alguém deve usar quando está criando um novo microserviço. |
||||||
|
|
||||||
|
<!-- more --> |
||||||
|
|
||||||
|
O "chassi" é um [design |
||||||
|
pattern](https://microservices.io/patterns/microservice-chassis.html) |
||||||
|
conhecido, mas a literatura fala sobre a escolha de bibliotecas e frameworks |
||||||
|
que devem ser usados quando se está criando um microserviço. |
||||||
|
|
||||||
|
Por exemplo, se você está trabalhando com Java, você provavelmente teria algo |
||||||
|
como Spring Boot como chassi para os seus microserviços, de forma que qualquer |
||||||
|
um que comece um microserviço já tenha uma biblioteca (e conhecimento local) |
||||||
|
de como construir. |
||||||
|
|
||||||
|
E, para cada linguagem, você precisa escolher um chassi diferente -- você não |
||||||
|
pode usar Spring Boot com Python, por exemplo. |
||||||
|
|
||||||
|
Você deve ter notado que eu coloquei um "mas" no segundo parágrafo. |
||||||
|
Pessoalmente, eu acho que a escolha do chassi vai bem além da criação de |
||||||
|
microserviços. |
||||||
|
|
||||||
|
## Conhecimento Compartilhado |
||||||
|
|
||||||
|
Um dos maiores fatores de se usar um chassi para o seu microserviço é o |
||||||
|
conhecimento compartilhado entre os times. Times que utilizam o mesmo chassi |
||||||
|
podem trocar informações em como solucionar alguns problemas, como fazer o |
||||||
|
processamento ficar mais rápido, informações sobre releases novas e assim por |
||||||
|
diante. |
||||||
|
|
||||||
|
Mesmo que os times nunca mexam nos códigos dos outros, o simples fato que eles |
||||||
|
podem compartilhar essas informações entre eles é um grande avanço. |
||||||
|
|
||||||
|
E mesmo para times que utilizam linguagens diferentes isso é um grande ponto: |
||||||
|
Um time pode descrever como o framework que eles utilizam permite fazer alguma |
||||||
|
coisa de forma mais simples, de forma que outro time possa pesquisar se o seu |
||||||
|
chassi permite fazer algo da mesma forma. |
||||||
|
|
||||||
|
## Aplicando uma Visão Comum |
||||||
|
|
||||||
|
Enquanto o pattern descreve apenas frameworks e bibliotecas, as escolhas dos |
||||||
|
serviços ao redor do serviço também faz parte do chassi, na minha opinião. |
||||||
|
|
||||||
|
Por exemplo, um time decide usar Kafka como mensageria[^1] entre serviços -- o |
||||||
|
que permite que qualquer outro time, usando qualquer outro framework, em |
||||||
|
qualquer outra linguagem, a usar o mesmo serviço para troca de mensagens -- |
||||||
|
permitindo que qualquer time que precise usar um serviço de troca de mensagens |
||||||
|
possa usar a mesma instalação (mas usando tópicos diferentes), reduzindo |
||||||
|
custos de manutenção. Mas o que acontece quando um time decide usar Kafka como |
||||||
|
banco de dados e define o tempo de retenção para "sempre"? Isso iria confundir |
||||||
|
completamente todos os outros. "Por que esse tópico está sempre crescendo?" |
||||||
|
Pior, sem uma documentação de DevOps bem descrita, alguém pode ver o tópico |
||||||
|
crescendo, verificar, ver que a política de retenção está diferente do resto e |
||||||
|
adicionar um baseada em outros projetos. |
||||||
|
|
||||||
|
Outro exemplo: Para dados relacionados, há uma instalação do PostgreSQL para |
||||||
|
todo mundo. Cada time tem o seu próprio database e usuários. Mas um time, que |
||||||
|
acabou ficando responsável por dois microserviços, tem um serviço com dados |
||||||
|
que são relacionais e outro que basicamente necessita de um armazenamento de |
||||||
|
chave-valor. Ao invés de pedir por um banco de dados chave-valor, o time |
||||||
|
decide criar um database com apenas uma tabela, com um campo para a chave e um |
||||||
|
campo de texto para guardar JSONs. Isso, novamente, quebra a definição do |
||||||
|
chassi, pois seria esperado que o PostgreSQL fosse usado como banco |
||||||
|
relacional e não como um banco chave-valor. |
||||||
|
|
||||||
|
## Conclusão |
||||||
|
|
||||||
|
Chassis são bons para o desenvolvimento de microserviços por permitir um |
||||||
|
início de desenvolvimento rápido e com compartilhamento de conhecimento, mas |
||||||
|
eles vão além de apenas frameworks e bibliotecas: eles relacionado tudo ao |
||||||
|
redor dos serviços e como esses são vistos por cada microserviço. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
[^1]: "Message broker". |
||||||
|
|
||||||
|
<!-- |
||||||
|
vim:spelllang=pt: |
||||||
|
--> |
||||||
|
|
@ -0,0 +1,273 @@ |
|||||||
|
+++ |
||||||
|
title = "You Don't Need range()" |
||||||
|
date = 2020-04-16 |
||||||
|
|
||||||
|
[taxonomies] |
||||||
|
tags = ["code", "python", "range"] |
||||||
|
+++ |
||||||
|
|
||||||
|
Beginners in Python tend to use `range()` for iterating over lists. This is |
||||||
|
not really necessary. |
||||||
|
|
||||||
|
<!-- more --> |
||||||
|
|
||||||
|
When people start programming Python, they tend to use constructions coming |
||||||
|
from other languages, so they iterate over a list with something like: |
||||||
|
|
||||||
|
```python |
||||||
|
a_list = [1, 2, 3, 4] |
||||||
|
for i in range(len(a_list)): |
||||||
|
print(a_list[i]) |
||||||
|
``` |
||||||
|
|
||||||
|
But Python have the concept of "iterable", meaning some things can be iterated |
||||||
|
over, without the need of accessing each element individually. For example, |
||||||
|
our previous list can be iterated with: |
||||||
|
|
||||||
|
```python |
||||||
|
a_list = [1, 2, 3, 4] |
||||||
|
for value in a_list: |
||||||
|
print(value) |
||||||
|
``` |
||||||
|
|
||||||
|
"For every element in `a_list`, retrieve it and name it `value`." |
||||||
|
|
||||||
|
A lot of elements are iterable: Strings are iterable, returning every |
||||||
|
character in them; dictionaries are iterable, returning every key in them; |
||||||
|
sets are iterable, returning every element in them; tuples are iterable, |
||||||
|
returning every value in them; generators are iterable, return the next value |
||||||
|
they can produce. |
||||||
|
|
||||||
|
But what if you need to iterate over more than one iterable at the same time? |
||||||
|
|
||||||
|
## Enters `zip()` |
||||||
|
|
||||||
|
That's where `zip()` comes in. `zip()` allows you to merge iterables: |
||||||
|
|
||||||
|
```python |
||||||
|
a_list = [1, 2, 3, 4] |
||||||
|
a_tuple = ('a', 'b', 'c', 'd') |
||||||
|
for mixed_tuple in zip(a_list, a_tuple): |
||||||
|
print(mixed_tuple) |
||||||
|
``` |
||||||
|
|
||||||
|
This code prints out: |
||||||
|
|
||||||
|
``` |
||||||
|
(1, 'a') |
||||||
|
(2, 'b') |
||||||
|
(3, 'c') |
||||||
|
(4, 'd') |
||||||
|
``` |
||||||
|
|
||||||
|
What `zip()` does is create a tuple with the first element of the first |
||||||
|
iterable and the first element of the second iterable; then the second element |
||||||
|
of the first iterable and the second element of the second iterable; and so |
||||||
|
on. You can put as many iterables as you want in `zip()` and it will just |
||||||
|
create larger tuples for each interaction. |
||||||
|
|
||||||
|
## Interlude: Destructuring |
||||||
|
|
||||||
|
One of the cool things in Python is "destructuring". Destructuring |
||||||
|
(de-structuring or more like "breaking apart a structure") allows one to |
||||||
|
extract elements from a iterable directly. |
||||||
|
|
||||||
|
For example, if you have a tuple with two elements: |
||||||
|
|
||||||
|
```python |
||||||
|
a_tuple = (1, 2) |
||||||
|
``` |
||||||
|
|
||||||
|
... you'd probably take every element of it in separate variables with |
||||||
|
something like |
||||||
|
|
||||||
|
```python |
||||||
|
a = a_tuple[0] |
||||||
|
b = a_tuple[1] |
||||||
|
``` |
||||||
|
|
||||||
|
But with destructuring, you can do this in a single pass with |
||||||
|
|
||||||
|
```python |
||||||
|
(a, b) = a_tuple |
||||||
|
``` |
||||||
|
|
||||||
|
This code and the one above it will do exactly the same thing. |
||||||
|
|
||||||
|
But why destructuring is important if we are talking about iterating over |
||||||
|
elements? 'Cause `for` also has the destructuring capabilities: |
||||||
|
|
||||||
|
```python |
||||||
|
a_list = [1, 2, 3, 4] |
||||||
|
a_tuple = ('b', 'c', 'd', 'f') |
||||||
|
a_string = 'aeio' |
||||||
|
|
||||||
|
for (a_number, lowercase_char, uppercase_char) in zip(a_list, a_tuple, a_string): |
||||||
|
print(a_number) |
||||||
|
print(lowercase_char) |
||||||
|
print(uppercase_char) |
||||||
|
print() |
||||||
|
``` |
||||||
|
|
||||||
|
{% note() %} |
||||||
|
Remember that I said that strings are also iterables and each iteration would |
||||||
|
return a character? That's it. |
||||||
|
{% end %} |
||||||
|
|
||||||
|
But what happens when one of the iterables is smaller than the other one? |
||||||
|
|
||||||
|
```python |
||||||
|
a_short_list = [1, 2] |
||||||
|
a_long_list [10, 20, 30, 40, 50, 60, 70, 80, 90] |
||||||
|
for (small, big) in zip(a_short_list, a_long_list): |
||||||
|
print(small, big) |
||||||
|
``` |
||||||
|
|
||||||
|
That will print |
||||||
|
|
||||||
|
``` |
||||||
|
1 10 |
||||||
|
2 20 |
||||||
|
``` |
||||||
|
|
||||||
|
`zip()` stops when the shortest iterable have no more elements. To go as far |
||||||
|
as the longest iterable, you need `itertools.zip_longest()`. |
||||||
|
|
||||||
|
## `itertools.zip_longest()` |
||||||
|
|
||||||
|
`zip_longest()`, part of the `itertools` module, will transverse the iterables |
||||||
|
till every one of them have no more elements. What happens with the shortest |
||||||
|
of those is that its value will be replaced with `None`. Using our previous |
||||||
|
example: |
||||||
|
|
||||||
|
```python |
||||||
|
import itertools |
||||||
|
|
||||||
|
a_short_list = [1, 2] |
||||||
|
a_long_list [10, 20, 30, 40, 50, 60, 70, 80, 90] |
||||||
|
for (small, big) in itertools.zip_longest(a_short_list, a_long_list): |
||||||
|
print(small, big) |
||||||
|
``` |
||||||
|
|
||||||
|
That will print: |
||||||
|
|
||||||
|
``` |
||||||
|
1 10 |
||||||
|
2 20 |
||||||
|
None 30 |
||||||
|
None 40 |
||||||
|
None 50 |
||||||
|
None 60 |
||||||
|
None 70 |
||||||
|
None 80 |
||||||
|
None 90 |
||||||
|
``` |
||||||
|
|
||||||
|
## Careful with generators |
||||||
|
|
||||||
|
One thing you must be careful when using `zip()` and `zip_longest()` are |
||||||
|
generators. Why? Because some of them have no end. |
||||||
|
|
||||||
|
Let's take one example: `cycle()`. `cycle()`, also part of the itertools |
||||||
|
module, is a generator that, on request, returns the next element of an |
||||||
|
iterable but, as soon as this iterable is over, it starts over. For example |
||||||
|
(and I'm tacking `zip()` around this just for the sake of staying on topic, |
||||||
|
and you don't need to use `zip()` with `cycle()`): |
||||||
|
|
||||||
|
```python |
||||||
|
a_list = [10, 20, 30, 40, 50, 60, 70, 80, 90] |
||||||
|
for (bullet, value) in zip(cycle(['-', '*', '.']), a_list): |
||||||
|
print(bullet, value) |
||||||
|
``` |
||||||
|
|
||||||
|
That will produce: |
||||||
|
|
||||||
|
``` |
||||||
|
- 10 |
||||||
|
* 20 |
||||||
|
. 30 |
||||||
|
- 40 |
||||||
|
* 50 |
||||||
|
. 60 |
||||||
|
- 70 |
||||||
|
* 80 |
||||||
|
. 90 |
||||||
|
``` |
||||||
|
|
||||||
|
What happened here is that `zip()` took the first value of the first iterable, |
||||||
|
our `cycle(['-', '*', '.'])`, which was the first value of its iterable, |
||||||
|
`'-'`, and the second value of the second iterable, `10`; next iteration, the |
||||||
|
second value of `cycle()` was `'*'` and the second value of `a_list` was `20`; |
||||||
|
third iteration, `cycle()` returned `'.'` and `a_list` returned `30`; now, on |
||||||
|
the fourth iteration, `cycle()` was asked for a value and, with its iterable |
||||||
|
exhausted, it returned to the first value, returning `'-'` again. |
||||||
|
|
||||||
|
Ok, cool? |
||||||
|
|
||||||
|
So, what's the problem with generators? |
||||||
|
|
||||||
|
Some generators -- like `cycle()` above -- do not have an end. If you replace |
||||||
|
`zip()` with `zip_longest()` on the code above, you'll see that the code will |
||||||
|
never stop. It's not every generator the can produce values continuously, |
||||||
|
though, so you can mess with them with no issue. |
||||||
|
|
||||||
|
{% note() %} |
||||||
|
It's not `zip_longest()` that may have an issue. You can put two `cycle()`s in |
||||||
|
a `zip()` and it will keep producing tuples with no end. |
||||||
|
{% end %} |
||||||
|
|
||||||
|
All nice and dandy, but what if I need to show the index itself? |
||||||
|
|
||||||
|
## `enumerate()` to the rescue! |
||||||
|
|
||||||
|
Ok, so we talked about mixing more than one iterable, but what if we need the |
||||||
|
position? What if we have a list of ordered results and we need to show the |
||||||
|
position itself? |
||||||
|
|
||||||
|
Again, you may be temped to use `range()`: |
||||||
|
|
||||||
|
```python |
||||||
|
winners = ['first place', 'second place', 'third place', 'fourth place'] |
||||||
|
for pos in range(len(winners)): |
||||||
|
print(pos + 1, winners[pos].capitalize()) |
||||||
|
``` |
||||||
|
|
||||||
|
That will print: |
||||||
|
|
||||||
|
``` |
||||||
|
1 First place |
||||||
|
2 Second place |
||||||
|
3 Third place |
||||||
|
4 Fourth place |
||||||
|
``` |
||||||
|
|
||||||
|
One may also try to be clever and mix our newly found knowledge about `zip()` |
||||||
|
and do: |
||||||
|
|
||||||
|
```python |
||||||
|
winners = ['first place', 'second place', 'third place', 'fourth place'] |
||||||
|
for (pos, name) in zip(range(len(winners)), winners): |
||||||
|
print(pos + 1, name.capitalize()) |
||||||
|
``` |
||||||
|
|
||||||
|
... which ,personally, looks even more cumbersome than the first option. But |
||||||
|
Python have another generator called `enumerate()` that takes one single |
||||||
|
iterable, but produces tuples with the index of it and its value: |
||||||
|
|
||||||
|
```python |
||||||
|
winners = ['first place', 'second place', 'third place', 'fourth place'] |
||||||
|
for (pos, name) in enumerate(winners): |
||||||
|
print(pos + 1, name.capitalize()) |
||||||
|
``` |
||||||
|
|
||||||
|
Even better, `enumerate()` have an option to define with will be the value of |
||||||
|
the first element, so instead of that `pos + 1` in the `print()` statement, we |
||||||
|
can replace the enumerate to `enumerate(winners, start=1)` and remove the |
||||||
|
addition in `print()`. |
||||||
|
|
||||||
|
## Conclusion |
||||||
|
|
||||||
|
Iterables is one of the powerhouses of Python, as you may have noticed in the |
||||||
|
beginning with the number of things that can be iterated over. Understanding |
||||||
|
those will help you write better and more concise Python code, without losing |
||||||
|
meaning. |
@ -0,0 +1,283 @@ |
|||||||
|
+++ |
||||||
|
title = "Você Não Precisa de range()" |
||||||
|
date = 2020-04-16 |
||||||
|
|
||||||
|
[taxonomies] |
||||||
|
tags = ["código", "python", "range"] |
||||||
|
+++ |
||||||
|
|
||||||
|
Quem está começando com Python tende a usar `range()` quando precisa iterar |
||||||
|
sobre listas. Mas isso não é realmente necessário. |
||||||
|
|
||||||
|
<!-- more --> |
||||||
|
|
||||||
|
Quando as pessoas começam a programar em Python, elas tendem a usar |
||||||
|
construções vindas de outras linguagens, e por isso iteram sobre uma lista da |
||||||
|
seguinte forma: |
||||||
|
|
||||||
|
```python |
||||||
|
a_list = [1, 2, 3, 4] |
||||||
|
for i in range(len(a_list)): |
||||||
|
print(a_list[i]) |
||||||
|
``` |
||||||
|
|
||||||
|
Mas Python tem o conceito de "iteráveis", o que quer dizer que algumas coisas |
||||||
|
podem ser iteradas diretamente, sem precisar acessar cada elemento |
||||||
|
individualmente. Por exemplo, nossa lista anterior poderia ser iterada com: |
||||||
|
|
||||||
|
```python |
||||||
|
a_list = [1, 2, 3, 4] |
||||||
|
for value in a_list: |
||||||
|
print(value) |
||||||
|
``` |
||||||
|
|
||||||
|
"Para cada elemento em `a_list`, recupere-o e chame-o de `value`." |
||||||
|
|
||||||
|
Vários elementos são iteráveis: Strings são iteráveis, retornando cada |
||||||
|
caractere nelas; dicionários são iteráveis, retornado cada chave neles; |
||||||
|
conjuntos são iteráveis, retornado cada elemento neles; tuplas são iteráveis, |
||||||
|
retornando cada elemento nelas; generators são iteráveis, retornando o próximo |
||||||
|
valor que eles conseguem produzir. |
||||||
|
|
||||||
|
Mas e se precisássemos iterar sobre mais de um elemento ao mesmo tempo? |
||||||
|
|
||||||
|
## Entra o `zip()` |
||||||
|
|
||||||
|
É aí que o `zip()` entra. `zip()` permite que você junte dois iteráveis: |
||||||
|
|
||||||
|
```python |
||||||
|
a_list = [1, 2, 3, 4] |
||||||
|
a_tuple = ('a', 'b', 'c', 'd') |
||||||
|
for mixed_tuple in zip(a_list, a_tuple): |
||||||
|
print(mixed_tuple) |
||||||
|
``` |
||||||
|
|
||||||
|
Esse código imprime: |
||||||
|
|
||||||
|
``` |
||||||
|
(1, 'a') |
||||||
|
(2, 'b') |
||||||
|
(3, 'c') |
||||||
|
(4, 'd') |
||||||
|
``` |
||||||
|
|
||||||
|
O que o `zip()` faz é criar uma tupla com o primeiro elemento do primeiro |
||||||
|
iterável e o primeiro elemento do segundo iterável; depois com o segundo |
||||||
|
elemento do primeiro iterável e o segundo elemento do segundo iterável; e |
||||||
|
assim por diante. Você pode colocar quantos iteráveis você quiser no `zip()` e |
||||||
|
ele ira produzir tuplas maiores em cada iteração. |
||||||
|
|
||||||
|
## Interlúdio: Destruturação |
||||||
|
|
||||||
|
Uma das coisas legais de Python é "destruturação". Destruturação |
||||||
|
(de-estruturar ou mais como "quebrar uma estrutura") permite que elementos de |
||||||
|
um iterável sejam extraídos diretamente. |
||||||
|
|
||||||
|
Por exemplo, se você tem uma tupla com dois elementos: |
||||||
|
|
||||||
|
```python |
||||||
|
a_tuple = (1, 2) |
||||||
|
``` |
||||||
|
|
||||||
|
... você provavelmente iria extrair cada um dos elementos com alguma coisa do |
||||||
|
tipo: |
||||||
|
|
||||||
|
|
||||||
|
```python |
||||||
|
a = a_tuple[0] |
||||||
|
b = a_tuple[1] |
||||||
|
``` |
||||||
|
|
||||||
|
Mas com destruturação, você pode fazer isso numa única passada com: |
||||||
|
|
||||||
|
```python |
||||||
|
(a, b) = a_tuple |
||||||
|
``` |
||||||
|
|
||||||
|
Este código e o acima dele fazem exatamente a mesma coisa. |
||||||
|
|
||||||
|
Mas porque destruturação é importante se estamos falando sobre iterar sobre |
||||||
|
elementos? Porque `for` também tem a capacidade de destruturar: |
||||||
|
|
||||||
|
```python |
||||||
|
a_list = [1, 2, 3, 4] |
||||||
|
a_tuple = ('b', 'c', 'd', 'f') |
||||||
|
a_string = 'aeio' |
||||||
|
|
||||||
|
for (a_number, lowercase_char, uppercase_char) in zip(a_list, a_tuple, a_string): |
||||||
|
print(a_number) |
||||||
|
print(lowercase_char) |
||||||
|
print(uppercase_char) |
||||||
|
print() |
||||||
|
``` |
||||||
|
|
||||||
|
{% note() %} |
||||||
|
Lembra que eu falei que strings também eram iteráveis e cada iteração traz um |
||||||
|
caractere? É isso. |
||||||
|
{% end %} |
||||||
|
|
||||||
|
Mas o que acontece quando um dos iteráveis é menor que o outro? |
||||||
|
|
||||||
|
```python |
||||||
|
a_short_list = [1, 2] |
||||||
|
a_long_list [10, 20, 30, 40, 50, 60, 70, 80, 90] |
||||||
|
for (small, big) in zip(a_short_list, a_long_list): |
||||||
|
print(small, big) |
||||||
|
``` |
||||||
|
|
||||||
|
Esse código imprime: |
||||||
|
|
||||||
|
``` |
||||||
|
1 10 |
||||||
|
2 20 |
||||||
|
``` |
||||||
|
`zip()` pára quando o menor iterável não tem mais elementos. Para consumir |
||||||
|
todos os elementos do iterável mais longo, você precisa de |
||||||
|
`itertools.zip_longest()`. |
||||||
|
|
||||||
|
## `itertools.zip_longest()` |
||||||
|
|
||||||
|
`zip_longest()`, parte do módulo `itertools`, irá percorrer os iteráveis até |
||||||
|
que nenhum deles tenha mais elementos. O que acontece com o menor deles é que |
||||||
|
os seus valores são substituídos por `None`. Usando nosso exemplo anterior: |
||||||
|
|
||||||
|
```python |
||||||
|
import itertools |
||||||
|
|
||||||
|
a_short_list = [1, 2] |
||||||
|
a_long_list [10, 20, 30, 40, 50, 60, 70, 80, 90] |
||||||
|
for (small, big) in itertools.zip_longest(a_short_list, a_long_list): |
||||||
|
print(small, big) |
||||||
|
``` |
||||||
|
|
||||||
|
Isso irá imprimir: |
||||||
|
|
||||||
|
``` |
||||||
|
1 10 |
||||||
|
2 20 |
||||||
|
None 30 |
||||||
|
None 40 |
||||||
|
None 50 |
||||||
|
None 60 |
||||||
|
None 70 |
||||||
|
None 80 |
||||||
|
None 90 |
||||||
|
``` |
||||||
|
|
||||||
|
## Cuidado com generators |
||||||
|
|
||||||
|
Uma coisa que você precisa ter cuidado quando estiver usando `zip()` ou |
||||||
|
`zip_longest()` são generators. Por que? Porque alguns deles não tem fim. |
||||||
|
|
||||||
|
Vamos usar um exemplo: `cycle()`. `cycle()`, também parte do módulo itertools, |
||||||
|
é um generator que, quando for pedido um valor, retorna o próximo valor de um |
||||||
|
iterável mas, quando chegar ao fim deste, retorna pro começo. Por exemplo (e |
||||||
|
eu estou usando `zip()` apenas para nos mantermos no tópico, mas não é preciso |
||||||
|
usar `zip()` para usar `cycle()`): |
||||||
|
|
||||||
|
```python |
||||||
|
a_list = [10, 20, 30, 40, 50, 60, 70, 80, 90] |
||||||
|
for (bullet, value) in zip(cycle(['-', '*', '.']), a_list): |
||||||
|
print(bullet, value) |
||||||
|
``` |
||||||
|
|
||||||
|
Este código produz: |
||||||
|
|
||||||
|
``` |
||||||
|
- 10 |
||||||
|
* 20 |
||||||
|
. 30 |
||||||
|
- 40 |
||||||
|
* 50 |
||||||
|
. 60 |
||||||
|
- 70 |
||||||
|
* 80 |
||||||
|
. 90 |
||||||
|
``` |
||||||
|
|
||||||
|
O que acontece é que `zip()` pegou o primeiro elemento do primeiro iterável, |
||||||
|
nosso `cycle(['-', '*', '.'])`, que tem como primeiro valor no seu iterável |
||||||
|
`'-'` e o segundo valor do segundo iterável, `10`; na próxima iteração, o |
||||||
|
segundo valor de `cycle()` foi `'*'` e o segundo valor de `a_list` foi `20`; |
||||||
|
na terceira iteração, `cycle()` retornou `'.'` e `a_list` `30`; agora, na |
||||||
|
quarta iteração, foi pedido um valor ao `cycle()` e, como o seu iterável |
||||||
|
terminou, ele retorno o primeiro valor, retornando `'-'` de novo. |
||||||
|
|
||||||
|
Certo? |
||||||
|
|
||||||
|
Então qual o problema com generators? |
||||||
|
|
||||||
|
Alguns generators -- como o `cycle()` acima -- não tem fim. Se você trocar |
||||||
|
`zip()` por `zip_longest()` no exemplo acima, você vai ver que o código não |
||||||
|
irá terminar. Não são todos os generators que produzem valores de forma |
||||||
|
infinita, e você pode usá-los sem problema. |
||||||
|
|
||||||
|
{% note() %} |
||||||
|
Não é só `zip_longest()` que tem problemas. Você pode botar dois `cycle()`s |
||||||
|
num `zip()` e ele vai ficar gerando tuplas sem parar. |
||||||
|
{% end %} |
||||||
|
|
||||||
|
Certo, legal, mas e se eu precisar mostrar o índice também? |
||||||
|
|
||||||
|
## `enumerate()` ao resgate! |
||||||
|
|
||||||
|
Então, nós falamos sobre usar dois iteráveis ao mesmo tempo, mas e se |
||||||
|
precisarmos da posição também? E se a nossa lista for uma lista de resultados |
||||||
|
ordenados e nós precisamos mostrar a posição em si? |
||||||
|
|
||||||
|
De novo, você pode ficar tentado a usar `range()`: |
||||||
|
|
||||||
|
```python |
||||||
|
winners = ['first place', 'second place', 'third place', 'fourth place'] |
||||||
|
for pos in range(len(winners)): |
||||||
|
print(pos + 1, winners[pos].capitalize()) |
||||||
|
``` |
||||||
|
|
||||||
|
Isso irá imprimir: |
||||||
|
|
||||||
|
``` |
||||||
|
1 First place |
||||||
|
2 Second place |
||||||
|
3 Third place |
||||||
|
4 Fourth place |
||||||
|
``` |
||||||
|
|
||||||
|
Uma das coisas que você pode tentar ser esperto é tentar misturar o seu novo |
||||||
|
conhecimento sobre `zip()` e fazer: |
||||||
|
|
||||||
|
```python |
||||||
|
winners = ['first place', 'second place', 'third place', 'fourth place'] |
||||||
|
for (pos, name) in zip(range(len(winners)), winners): |
||||||
|
print(pos + 1, name.capitalize()) |
||||||
|
``` |
||||||
|
|
||||||
|
... que, pessoalmente, parece mais complexo do que a primeira opção. Mas |
||||||
|
Python tem outro generator chamado `enumerate()` que recebe um único iterável, |
||||||
|
mas produz tuplas com o índice e seu valor: |
||||||
|
|
||||||
|
```python |
||||||
|
winners = ['first place', 'second place', 'third place', 'fourth place'] |
||||||
|
for (pos, name) in enumerate(winners): |
||||||
|
print(pos + 1, name.capitalize()) |
||||||
|
``` |
||||||
|
|
||||||
|
Melhor ainda, `enumerate()` tem uma opção para definir o valor inicial do |
||||||
|
primeiro elemento, e ao invés de usar `pos + 1` no `print()`, nós podemos |
||||||
|
mudar o enumerate para `enumerate(winners, start=1)` e remover a adição no |
||||||
|
`print()`. |
||||||
|
|
||||||
|
## Conclusão |
||||||
|
|
||||||
|
Iteráveis são as grandes potências de Python, como você pode ter percebido com |
||||||
|
a lista de coisas que podem ser iteradas. Entendendo-os vai lhe ajudar a |
||||||
|
escrever código Python melhor e mais conciso, sem perda de significado. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
Esse conteúdo foi criado baseado nas discussões no [Telegram do |
||||||
|
PyTche](https://t.me/pytche). Se quiser, junte-se a nós para conversarmos |
||||||
|
sobre Python. |
||||||
|
|
||||||
|
<!-- |
||||||
|
vim:spelllang=pt: |
||||||
|
--> |
Loading…
Reference in new issue