Python é uma linguagem excelente pra quem está começando no mundo da programação, mas possui muitos detalhes que podem passar despercebidos pelos iniciantes.
Neste post, você aprenderá 10 truques que todo programador Python deveria conhecer para escrever códigos melhores!
01 - Trocar valores de variáveis sem usar intermediários
Em muitas linguagens, trocar valores entre duas variáveis exige uma terceira variável temporária, como por exemplo em C:
#include <stdio.h>
int main() {
int a = 5;
int b = 10;
int temp;
temp = a;
a = b;
b = temp;
printf("Output:\n");
printf("a = %d\n", a); // a = 10
printf("b = %d\n", b); // b = 5
return 0;
}
Em Python, podemos fazer isso de forma elegante:
x, y = 5, 10
x, y = y, x
print(x, y) # Output: 10 5
Essa abordagem usa o tuple destructuring para facilitar a troca.
02 - PEP 8
PEP 8 é o guia de estilo oficial do Python. Segui-lo melhora a legibilidade e padroniza o código. Algumas regras importantes:
Use 4 espaços por indentação (não use TAB)
Nomes de variáveis em
snake_case
import os
def my_function():
print("Following PEP 8!")
Se você utiliza o VsCode, então você pode instalar extensão autopep8 pra te ajudar.
03 - Operador Walrus
O operador walrus, que foi introduzido no Python 3.8, permite atribuir valores dentro de expressões, reduzindo a repetição de código:
# Without Walrus Operator
n_len = len([1, 2, 3, 4])
if n_len > 3:
print(f"The list has {n_len} elements")
# With Walrus Operator
if (n := len([1, 2, 3, 4])) > 3:
print(f"The list has {n} elements")
Esse operador é útil para evitar a duplicação de chamadas de função (no caso, a função len()
) e melhorar a legibilidade do código, especialmente em loops e condicionais.
Curiosidade: Esse operador tem esse nome já que parece os dentes de uma morsa (Walrus).
04 - Operador Is
Muita gente não sabe, mas o operador is
verifica se duas variáveis apontam para o mesmo objeto na memória:
a = [1, 2, 3]
b = a
print(a is b) # True
c = [1, 2, 3]
print(a is c) # False
Esse operador também é muito utilizado para verificar se um objeto é None
a = None
if a is None:
print("a is None")
⚠️ Atenção: Para comparar valores, use ==
, pois is
pode levar a resultados inesperados com tipos imutáveis como strings e números por conta do interning:
x = 256
y = 256
print(x is y) # True (small values can be interned)
05 - Copiar objetos
Atribuir uma variável a outra (=
) apenas cria uma referência ao mesmo objeto.
Para criar cópias de verdade, você precisa usar a biblioteca copy
. E preste bastante atenção, já que existem 2 tipos de cópia:
Cópia rasa (shallow) não copia objetos aninhados, somente sua estrutura
Cópia profunda (deep) copia todos os objetos.
Vamos ver um exemplo:
import copy
original_dict = {
'a': 1,
'b': [2, 3, 4],
'c': {'x': 5, 'y': 6}
}
shallow_copy = copy.copy(original_dict)
deep_copy = copy.deepcopy(original_dict)
# Modifying the original object
original_dict['a'] = 100
original_dict['b'][0] = 200 # shallow_copy will have this value even if it is not modified \0/
original_dict['c']['x'] = 300
print("Original:", original_dict)
print("Shallow Copy:", shallow_copy)
print("Deep Copy:", deep_copy)
# Original: {'a': 100, 'b': [200, 3, 4], 'c': {'x': 300, 'y': 6}}
# Shallow Copy: {'a': 1, 'b': [200, 3, 4], 'c': {'x': 300, 'y': 6}} <---------------
# Deep Copy: {'a': 1, 'b': [2, 3, 4], 'c': {'x': 5, 'y': 6}}
No exemplo acima, é possível ver que a propriedade shallow_copy['b'][0]
armazena o valor 200
mesmo não sendo modificado em nenhum lugar do código.
Nesse caso, como a cópia é rasa, uma referência do dicionário original_dict['b']
foi copiado para a propriedade shallow_copy['b']
. Em outras palavras, as propriedades original_dict['b']
e shallow_copy['b']
apontam para o mesmo endereço de memória.
Em contra partida, o dicionário deep_copy
foi criado completamente independente dos demais, tendo endereços de memória que não são compartilhados entre os dicionários.
Resumo:
Shallow Copy: Copia apenas a estrutura, mas compartilha referências para objetos internos (listas, dicionários, etc).
Deep Copy: Copia tudo recursivamente, criando objetos independentes.
06 - Índices negativos
Python permite acessar elementos de listas a partir do final usando índices negativos:
Último item:
lista[-1]
Penúltimo item:
lista[-2]
…
numbers = [10, 20, 30, 40]
print(numbers[-1]) # 40
print(numbers[-2]) # 30
07 - Monkey Patching
Monkey patching é a técnica de modificar ou substituir métodos e atributos de classes existentes em tempo de execução.
É útil para corrigir bugs, modificar o comportamento de bibliotecas de terceiros ou adicionar funcionalidades sem alterar o código-fonte original, principalmente para realizar testes.
No entanto, seu uso deve ser cuidadoso, pois pode introduzir efeitos colaterais inesperados.
class MathOperations:
def add(self, a, b) :
return a + b
def subtract(self, a, b) :
return a - b
# Define a new function to be added to the class
def multiply(self, a, b):
return a * b
MathOperations.multiply = multiply
# Now, we can use the 'multiply' method as if it was part of the original class
math_instance = MathOperations()
result = math_instance.multiply(3, 4)
print("Result of multiplication: ", result) # Result of multiplication: 12
No exemplo acima, um efeito indesejado foi ADICIONAR um método a uma classe em runtime.
Imagine se o método
multiply
e a classeMathOperation
estiverem em arquivos distintos. Quanto tempo você demoraria pra perceber que em algum lugar do código existe uma linha que atribui um novo método a uma classe?
08 - Timer context
O uso de context managers com with
permite medir o tempo de execução de um bloco de código de maneira conveniente e automática.
Isso é útil para otimização de desempenho, análise de tempo de execução de funções críticas e depuração de gargalos de código.
import time
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, *args):
print(f"Execution time: {time.time() - self.start:.4f}s")
with Timer():
sum(range(10**6))
# Execution time: 0.0194s
09 - Especificadores de acesso
Python não possui controle estrito de acesso como outras linguagens, mas utiliza convenções para indicar a visibilidade dos atributos e métodos de uma classe:
_protegido
: Indica que um atributo ou método é protegido e deve ser usado apenas dentro da classe e subclasses.__privado
: Indica que um atributo ou método deve ser privado, mas ainda pode ser acessado comobj._Classe__privado
(o que acredito ser justificável utilizar esse tipo de acesso SOMENTE em cenários de testes).
class MyClass:
def __init__(self):
self.public = "can be accessed"
self._protected = "use with caution"
self.__private = "should not be accessed directly"
obj = MyClass()
print(obj.public) # OK
print(obj._protected) # Convention: do not modify
# print(obj.__private) # Error
10 - else
no try/except
O bloco else
dentro de um try/except
permite separar o código que deve ser executado somente quando nenhuma exceção ocorrer.
Isso melhora a organização e evita a execução desnecessária de código dentro do try
, reduzindo riscos de exceções inesperadas.
try:
result = 10 / 2
except ZeroDivisionError:
print("Error: Division by zero!")
else:
print("Everything is fine! Result:", result)
Conclusão
Esses são apenas alguns dos muitos detalhes que tornam Python, a linguagem das cobras, uma linguagem tão poderosa e versátil.
Ao dominar essas técnicas, você será capaz de escrever códigos mais eficientes, legíveis e robustos.
Agora, se você deseja aprofundar ainda mais seus conhecimentos e aprender boas práticas para escrever um código bem mais profissional, eu REALMENTE recomendo o livro: Robust Python. Esse livro me fez enxergar Python com outros olhos!