O Python tem algumas pegadinhas que os iniciantes precisam conhecer

Código do Python pode levar a uma caça às variáveis, além de confundir novatos com alguns operadores; confira a lista com exemplos práticos

Avatar photo
• Atualizado há 1 ano e 6 meses

O Python é tido como uma das melhores linguagens de programação para iniciantes. A opinião é compartilhada por líderes da indústria e por pesquisadores acadêmicos, e eles não estão errados. Ainda assim, os novatos podem se confundir.

Tomemos como exemplo a tipagem dinâmica, que parece algo incrível no começo. Graças a ela, o Python literalmente descobre por si próprio que tipo de valor uma variável pode ter, e você não precisa desperdiçar outra linha de código com isso. Assim tudo fica mais rápido!

Pelo menos inicialmente. Aí você erra em uma única linha – sim, uma! — e todo o seu projeto trava antes de terminar de ser executado.

Sim, outras linguagens também usam tipagem dinâmica; mas, no caso do Python, este é apenas o começo das pegadinhas para se atentar.

Este post foi publicado originalmente no Medium (parte 1 e parte 2); ele foi republicado, traduzido e adaptado pelo Tecnoblog sob permissão de Ari Joury.

Não me entenda mal: o Python é uma linguagem incrivelmente ampla e que cresce numa velocidade assustadora. Ele recebe uma nova versão por ano; enquanto isso, por exemplo, a C++ entrega uma nova versão a cada três anos ou mais. Ainda assim, os exemplos de confusão que eu escolhi provavelmente continuarão sendo usados por um bom tempo.

Caça às variáveis

Quando comecei meu doutorado há alguns anos, eu queria aprimorar um software existente que foi escrito por um colega. Eu entendi a ideia básica, e meu colega até escreveu um artigo sobre o programa como forma de documentação.

Mesmo assim, eu precisei ler milhares de linhas de código Python para saber com certeza qual parte fazia o quê, e onde eu poderia colocar os novos recursos que tinha em mente. Foi aí que deu problema…

Todo o código estava cheio de variáveis que não foram declaradas em nenhum lugar. A fim de entender para que serve cada variável, tive que procurá-la ao longo de um arquivo ou, mais frequentemente, em todo o projeto.

E há outra complicação: uma variável geralmente tem um nome dentro de uma função, mas quando a função é realmente acionada, ela se chama algo totalmente diferente. Tem mais: uma variável pode ser interligada com uma classe que está ligada a outra variável de outra classe, o que influencia uma classe totalmente diferente… e assim vai.

Há outras pessoas que sofrem com isso. O Zen of Python, uma lista de princípios para esta linguagem, diz claramente que ”explícito é melhor que implícito”. Mas é tão fácil fazer variáveis implícitas em Python que, especialmente em projetos grandes, a chance de criar uma confusão é bastante alta.

Desconheço essa variável

No exemplo abaixo, a função recebe um número qualquer e o multiplica por 2 (chamado de numero_inicial):

def minha_funcao(numero):
    numero_inicial = 2
    return numero_inicial*numero

numero_inicial

Na última linha, estamos chamamos numero_inicial. Aí, o Python gera uma mensagem de erro dizendo que esse nome não está definido. Isso faz sentido, porque a variável só existe dentro da função, não fora dela.

Em Python, se você estiver definindo uma variável dentro de uma função, essa variável não funcionará fora da função. Ou seja, ela está fora de escopo.

Mas e se fosse ao contrário? Ou seja, e se eu definir uma variável fora de uma função e, em seguida, referenciá-la dentro de uma função?

x = 2

def adicionar_5():
    x = x + 5
    print(x)

adicionar_5()

Isso deveria redefinir o valor de x para 2 + 5 = 7. Em vez disso, o Python exibe uma mensagem de erro, porque a variável local x foi referenciada antes da atribuição.

É algo estranho, certo? Eu tropecei nisso muitas vezes ao tentar definir funções em uma classe que são chamadas a partir de outra classe. Demorei um pouco para chegar à raiz do problema.

O raciocínio por trás disso é que o x dentro da função é diferente do x fora dela, então você não pode simplesmente alterá-lo do jeito que quiser.

Felizmente, existe uma solução simples para este problema: é só tacar um global antes do x!

x = 2

def adicionar_5():
    global x
    x = x + 5
    print(x)

adicionar_5()

Ou seja, o escopo não apenas protege do mundo externo as variáveis dentro das funções. No Python, o mundo externo é protegido de variáveis locais.

Mães e filhas

Se você usa programação orientada a objetos – quase todo mundo usa – as classes estão por toda parte em seu código Python. E uma das características mais úteis das classes é (rufar de tambores)…

… herança!

Ou seja, se você tem uma classe mãe (ou principal) com algumas propriedades, você pode criar classes filhas (ou secundárias) que herdam as mesmas propriedades. Assim:

class mae(object):
    x = 1

class filha1(mae):
    pass

class filha2(mae):
    pass 

print(mae.x, filha1.x, filha2.x) 

# isso retorna o seguinte: 1 1 1

As classes filhas herdam o fato de que x = 1, então podemos chamá-la e obter o mesmo resultado para as classes secundárias e principal.

E se alterarmos o atributo x de uma classe filha, ele deve mudar apenas dentro dessa classe. Por exemplo, se a filha pinta o cabelo, isso não muda a cor de cabelo da mãe. Então fica assim:

filha1.x = 5

print(mae.x, filha1.x, filha2.x)

# isso retorna: 1 5 1

Seguindo nossa comparação, perguntamos o que acontece quando a mãe pinta o cabelo: a cor do cabelo das filhas também muda? Não deveria.

Mas no Python…

mae.x = 9

print(mae.x, filha1.x, filha2.x)

# isso retorna: 9 5 9

Ai, Deus.

Isso acontece por causa da ordem de resolução do método do Python. Basicamente, as classes filhas herdam tudo o que a classe mãe tem, desde que o contrário não seja declarado. Então, no mundo do Python, se você não protestar com antecedência, a mamãe vai pintar o cabelo das filhas sempre que tingir o dela própria.

É igual ou não é?

Livro sobre Python
Livro sobre Python (Imagem: Ian Brown / Flickr)

O operador de comparação de igualdade == e de comparação de identidade is são duas coisas diferentes. Isso é sutil, e pode virar uma bagunça caso você se confunda.

Considere as seguintes listas:

lista1 = []

lista2 = []

lista3 = lista1

Você poderia pensar, ingenuamente, que as três listas são iguais porque estão todas vazias.

Mas não! Olha isso:

lista1 == lista2 

# retorna True, como esperado

lista1 is lista2

# retorna False

A segunda operação retorna False porque lista1 ocupa um pedaço diferente de memória que lista2. Elas têm nomes diferentes e foram iniciadas separadamente.

Continuemos:

lista1 is lista3 

# retorna True

Quê?

Nesse caso, nós diríamos que a lista3 é mesmo igual à lista1: de fato, ambas apontam para o mesmo pedaço de memória. É diferente de quando eu faço isso:

lista3 = lista1 + lista1

lista1 is lista3

# retorna False

Dessa vez é falso porque um novo pedaço de memória foi usado para guardar os valores da lista3.

Felizmente, a vasta maioria das pessoas não está trabalhando com uma série de listas vazias. Ainda assim, essa dúvida deve aparecer várias vezes enquanto você programa: devo usar == ou is dentro de uma estrutura condicional if?

O operador += pode confundir você

Em Python, o operador += permite realizar somas mais rapidamente. Por exemplo, no código abaixo:

x = 5

x += 3

O valor de x passará a ser 5 + 3 = 8.

Certo. Agora considere o seguinte:

nova_lista = [3, 5, 'paulo', 9]

nova_lista += 'ana'

# isso retorna [3, 5, 'paulo', 9, 'a', 'n', 'a']

Eita. Ele separou a 'ana'!

Certamente seria menos complicado se nós usamos += para um número? Por exemplo:

nova_lista += 34

Infelizmente, isso resultará em uma mensagem de erro, dizendo que “‘int’ object is not iterable”. Não é nada bom…

Talvez não possamos adicionar números a listas? Em princípio é isso, mas podemos somar números a elementos de listas, desde que esses elementos sejam números propriamente ditos.

Em listas, o Python atribui a posição 0 ao primeiro elemento, posição 1 ao segundo elemento, e assim vai. Portanto, nova_lista[0] corresponde ao primeiro item da lista. O que acontece se usarmos essa operação?

nova_lista[0] += 8

Ele vai somar 8 ao primeiro item da lista, que é o número 3. Por isso, a lista ficará assim: [11, 5, ‘paulo’, 9, ‘a’, ‘n’, ‘a’].

Mas não tente somar número com palavra! Esta operação…

# nova_lista[2] é igual a 'paulo'

nova_lista[2] += 9

… levará a uma mensagem de erro avisando que só é possível concatenar uma string com outra string, não com um número inteiro.

Ou seja, você poderá “somar” uma palavra com outra:

nova_lista[2] += 'higa'

# retorna [11, 5, 'paulohiga', 9, 'a', 'n', 'a']

Não é curioso como 'ana' foi dividido quando incluímos na lista, mas 'higa' permaneceu por inteiro porque foi adicionado como string?

A moral da história é: sempre teste seu código antes de usar o operador +=!

Limpeza incompleta

Código Python na tela
Código Python na tela (Imagem: Johnson Martin / Pixabay)

Em listas, às vezes precisamos retirar elementos que não vamos usar mais; isso nos leva à função remove().

Considere o seguinte:

minhalista = [0, 22, 'Felipe', 41, 22, 'Lucas', 24]

minhalista.remove(22)

# isso retorna [0, 'Felipe', 41, 22, 'Lucas', 24]

Eu disse para ele retirar um elemento igual a 22. Note que remove() não apaga todas as entradas “22”, só a primeira.

Se você quiser remover da lista todos os números 22 (ou qualquer outro elemento que você não queira), você pode escrever um loop como este aqui:

while 22 in minhalista: minhalista.remove(22)

# retorna [0, 'Felipe', 41, 'Lucas', 24]

Uma alternativa mais elegante seria usar a compreensão de lista, assim:

minhalista = [x for x in minhalista if x != 22]

# retorna [0, 'Felipe', 41, 'Lucas', 24]

Bônus: pop() e clear()

Em alguns casos você usa uma lista como um empilhamento de itens, na qual você quer pegar o último item e fazer algo com ele. Para isso serve a função pop().

O último item da lista acima é 24, então a função pop() vai pegar esse número:

idade = minhalista.pop() 
# é 24, conforme a lista acima

Agora a lista mudou, e o último item passou a ser ‘Lucas’. Ou seja, esse elemento será fisgado pela função pop():

nome = minhalista.pop()

# é 'Lucas'

Agora podemos usar as variáveis idade e nome:

f'{nome} tem {idade} anos.'
# retorna 'Lucas tem 24 anos.'

f'{minhalista}'
# retorna "[0, 'Felipe', 41]"

Cansou de todo esse tira-tira? A função clear() limpa sua lista e sua mente:

minhalista.clear()

f'{minhalista}' 
# retorna []

Isso pode até confundir, mas é genial

Suponha o seguinte: eu tenho uma lista dos números de 0 a 9, e quero remover todos os que são divisíveis por 3.

Eu poderia fazer um loop dividindo todos os números dessa lista por 3, e excluindo aqueles que deixem resto zero na divisão. Ficaria assim:

meus_numeros = [x for x in range(10)]  
# isso gera o conjunto [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

for x in range(len(meus_numeros)):
    if meus_numeros[x]%3 == 0:
        meus_numeros.remove(meus_numeros[x])

# isso deveria remover os números que são divisíveis por 3, ou seja, que deixam resto zero ao serem divididos por 3

Infelizmente, esse loop não funciona, e resultará em um erro de que o índice da lista está “fora do intervalo”.

Basicamente, o loop exclui números da lista, então ela ficará com menos de 10 elementos. Mas, por causa da condição “range(10)”, o Python espera que ela sempre tenha 10 números. Ele vai tentar chegar ao 10º elemento – que não vai existir – e dar erro.

Há uma solução muito elegante para este caso:

meus_numeros = [x for x in range(10) if x%3 != 0]

Com apenas uma linha de código, conseguimos o resultado que a gente queria: [1, 2, 4, 5, 7, 8].

No código, a expressão entre colchetes [] é uma compreensão de lista – basicamente uma forma resumida para os loops.

As compreensões de lista geralmente são um pouco mais rápidas do que os loops comuns, o que é legal se você estiver lidando com grandes conjuntos de dados.

Aqui, estamos apenas adicionando uma cláusula if para informar à compreensão da lista que ela não deve incluir os números que são divisíveis por 3.

Ao contrário de alguns dos fenômenos descritos acima, este não é um caso de loucura do Python. Mesmo que os iniciantes possam tropeçar nisso no início, isto aqui é genial.

Participe da discussão

Discuta este post e deixe seus comentários na TB Comunidade!

Relacionados

Escrito por

Ari Joury

Ari Joury

Ari Joury tem PhD em física de partículas elementares pela Universidade de Sorbonne (França), onde desenvolveu várias ferramentas de software para entender melhor os modelos de matéria escura; e quer alcançar pessoas em todo o mundo com sua formação em ciência e tecnologia.