Python Idiomático
Posted on Sáb 03 Outubro 2020 in python
Código Pythonico
Código Pythonico
Table of Contents
- 1 iterar sobre listas
- 2 "desempacotando" * múltiplos valores - lendo uma lista
- 3 Cuidado ao ter listas como parâmetros default
- 4 Troca / Substituição de valores
- 5 Igualdade de Tipo ou Valor - is ou ==
- 6 Propriedades de classes - use o @property
- 7 Parâmetro opcional - cuidado com default = None
- 8 Use gestores de contexto - with
- 9 Python Select-case ou IF exclusivo
1 iterar sobre listas
>>> x = [1, 3, 5, 4, 2, 0]
>>> for i in x:
... print(i)
...
1
3
5
4
2
0
em ordem reversa usando o reversed.
É o mesmo que x[::-1] (step) só que mais "limpo" na minha opinião.
>>> for i in reversed(x):
... print(i)
...
0
2
4
5
3
1
OU ainda com um índice, usando enumerate
>>> for k, i in enumerate(x):
... print(f'Índice: {k} - item {i}')
...
Índice: 0 - item 1
Índice: 1 - item 3
Índice: 2 - item 5
Índice: 3 - item 4
Índice: 4 - item 2
Índice: 5 - item 0
2 "desempacotando" * múltiplos valores - lendo uma lista
O * pode ser útil, ao invés de criar um monte de variáveis inúteis ou iterar com um for ou mesmo um listcomprehension para ler um valor
>>> primeiro, *os_meios, ultimo = [1, 2, 3, 4, 5]
>>> primeiro
1
>>> os_meios # cuidado, o `*` definie uma lista
[2, 3, 4]
>>> ultimo
5
reforçando .. pode haver um "erro" que não levanta nenhuma exceção.
>>> primeiro, *os_meios, ultimo = [1, 2]
>>> primeiro
1
>>> os_meios
[]
>>> ultimo
2
As vezes só queremos jogar alguns valores "fora" e explicitar isso
>>> nome, *_, email = ['O Nome', 'idade', 'logradouro', 'alguma outra coisa', 'c@mail.com']
>>> nome
'O Nome'
>>> _
['idade', 'logradouro', 'alguma outra coisa']
>>> email
'c@mail.com'
>>>
o uso do _ (underscore) não tem uma função especial, mas indica pra quem está lendo que aquele conteúdo não é importante - estou jogando fora"
3 Cuidado ao ter listas como parâmetros default
In [1]: def add_value(value, seq=[]):
...: seq.append(value)
...: return seq
...:
In [2]: add_value(2)
Out[2]: [2]
In [3]: add_value(3)
Out[3]: [2, 3] # era o esperado ?
A lista se acumula caso o parâmetro opcional seq não seja informado, e pode não ser o esperado !
de outro modo ...
- In [4]: def add_value(value, seq=None):
- ...: if seq is None: ...: seq = [] ...: seq.append(value) ...: return seq ...: ...:
In [5]: add_value(1) Out[5]: [1]
In [6]: add_value(2) Out[6]: [2]
In [7]: add_value(2, _) # _ = última saída no iPython. Out[7]: [2, 2]
4 Troca / Substituição de valores
Em Python não é necessário uma terceira variável para trocar valores entre variáveis.
# 3 variáveis ?
var_3 = a
x = y
y = var_3
# Não preciso ... isso funciona.
x, y = y, x
5 Igualdade de Tipo ou Valor - is ou ==
is não é somente mais fácil de ler mas é também mais rápido porque is não pode ser sobrecarregado e o interpretador faz menos verificações (dunder methods).
x is None
x is True
x is False
Dica
Não confunda igualde de valor e de identididade.
is é um operador de identidade
== é um operador de valor
veja mais em https://docs.python.org/3.4/library/operator.html
# correto
if foo is not None:
...
# errado ...
if not foo is None:
...
# correto:
if greeting:
...
# errado
if greeting == True:
...
# errado ou ainda pior
if greeting is True:
...
6 Propriedades de classes - use o @property
EM Python não usamos os chamados "getters and setters", não do mesmo jeito que em C ou Java.
Em Python resumindamente:
Decorator @property
Decorator #<attribute_name>.setter (repare que o nome da função é o mesmo !!!)
Em Python não há sobrecarga de métodos, mas aqui é possível por causa dos "decorators"
Existem outras maneiras de se escrever um setter mas não tão elegante, ou ao menos não tão "pythonico".
class MinhaClasse:
def __init__(self, valor):
self.calculado = valor
@property
def calculado(self):
return self.__calculado
@calculado.setter
def calculado(self, valor):
"""limita a propriedade `limitado` entre 0 e 10."""
if valor > 10:
self.__calculado = 10
elif valor < 0:
self.__calculado = 0
else:
self.__calculado = valor
In [2]: c = MinhaClasse(-1000)
In [3]: c.calculado
Out[3]: 0
In [4]: c = MinhaClasse(1000)
In [5]: c.calculado
Out[5]: 10
In [6]: c.calculado = 10000 # atribuindo diretamente
In [7]: c.calculado # reultado
Out[7]: 10
In [8]: c.calculado = -10000 # atribuindo diretamente
In [9]: c.calculado # reultado
Out[9]: 0
7 Parâmetro opcional - cuidado com default = None
em funções com parâmetros opcionais, caso None seja um valor aceitável, como saber se este valor foi informado ou se é o valor default.
Podemos definir um objeto como abaixo
_NOT_PASSED = object()
e testá-lo dentro
def uma_funcao(x, y=None):
"""funcao de exemplo
Paramters:
x int: parâmetro 1 obrigatório
y int: parâmetro 2 *opcional*
"""
if y is _NOT_PASSED:
print('y não informado, o valor default será utilizado.')
8 Use gestores de contexto - with
with open('arquivo.txt', 'w') as my_file:
... código
não tem como esquecer o arquivo aberto
entenda melhor e saiba o que mais pode fazer com a contextlib
9 Python Select-case ou IF exclusivo
from collections import defaultdict
OPT_0 = 0
OPT_1 = 1
OPT_2 = 2
OPT_6 = 6
# isso é o mesmo que 4 `if`s ...
# SELECT_CASE_TYPE[0] => 'tipo_0'
SELECT_CASE_TYPE = defaultdict( # defaultdict nunca da keyerror - int default=0
int, {
(OPT_0, 'tipo_0'),
(OPT_1, 'tipo_1'),
(OPT_2, 'tipo_2'),
(OPT_6, 'tipo_6'),
}
)
>>> SELECT_CASE_TYPE[0]
'tipo_0'
é aqui tem lambda mas ainda mantém o código simples e podemos comentar
uma versão mais madura, e que pode ser usada para agrupar as opções e alguns métodos úteis...
from collections import defaultdict
class Opcoes:
"""Opções de alguma coisa.
Como um conjunto de `if` ou `selec case/swicth`
Essa classe possui atributos que são aliases para opções (neste caso inteiros)
Possui um método que faz o papel de um select case (ou conjunto de ifs).
>>>Opcoes.opt_1
1
>>>Opcoes.opt_2
2
>>>Opcoes.opcao(1)
'tipo_1'
>>>Opcoes.opcao(Opcoes.opt_3)
'tipo_6'
"""
# opções possíveis
opt_0 = 0
opt_1 = 1
opt_2 = 2
opt_3 = 6
@classmethod
def opcao(cls, valor):
selecionado = {
cls.opt_0: 'tipo_0',
cls.opt_1: 'tipo_1',
cls.opt_2: 'tipo_2',
cls.opt_3: 'tipo_6',
}
return selecionado[valor]