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
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
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.
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.
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.
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.
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.
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
?
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 +=
!
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]
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 []
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!