10 Truques Escondidos do Python Que Poucos Dev's Usam

10 Truques Escondidos do Python Que Poucos Dev's Usam

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:

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:

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 classe MathOperation 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 com obj._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!