Python Idiomático

Posted on Sáb 03 Outubro 2020 in python

Código Pythonico

Código Pythonico

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]