Python is considered one of the best programming languages for starters. The opinion is shared by industry leaders and academic researchers alike, and they are not wrong. Still, newbies can get confused.
Let’s take dynamic typing as an example, which sounds amazing at first. Thanks to it, Python literally figures out for itself what kind of value a variable can have, and you don’t have to waste another line of code on it. That way everything goes faster!
At least initially. Then you miss a single line – yes, one! — and your entire project crashes before it finishes running.
Yes, other languages also use dynamic typing; but, in the case of Python, this is just the beginning of the gotchas to watch out for.
Don’t get me wrong: Python is an incredibly broad language and one that is growing at an alarming rate. He receives a new version a year; meanwhile, for example, C++ ships a new version every three years or so. Still, the examples of confusion I’ve chosen will likely continue to be used for quite some time.
variable hunting
When I started my PhD a few years ago, I wanted to improve on existing software that was written by a colleague. I understood the basic idea, and my colleague even wrote an article about the program as a form of documentation.
Even so, I had to read thousands of lines of Python code to know for sure which part did what, and where I could put the new features I had in mind. That’s where there was a problem…
All the code was full of variables that weren’t declared anywhere. In order to understand what each variable is for, I had to search for it throughout a file or, more often, throughout the entire project.
And there’s another complication: a variable usually has a name inside a function, but when the function is actually called, it’s called something else entirely. There’s more: a variable can be linked with a class that is linked to another variable of another class, which influences a totally different class… and so on.
There are other people who suffer from this. O Zen of Python, a list of principles for this language, clearly states that “explicit is better than implicit”. But it’s so easy to make variables implicit in Python that, especially in large projects, the chance of creating a mess is quite high.
I don’t know this variable
In the example below, the function receives any number and multiplies it by 2 (called numero_inicial
):
def minha_funcao(numero):
numero_inicial = 2
return numero_inicial*numero
numero_inicial
On the last line, we’re calling numero_inicial
. Then Python generates an error message saying that name is not defined. This makes sense, because the variable only exists inside of the function, no outside her.
In Python, if you’re defining a variable inside a function, that variable won’t work outside the function. In other words, it is out of scope.
But what if it was the other way around? That is, what if I define a variable outside of a function and then referencing it inside of a function?
x = 2
def adicionar_5():
x = x + 5
print(x)
adicionar_5()
This should reset the value of x
for 2 + 5 = 7. Python displays an error message instead, because the local variable x
was referenced before of the attribution.
It’s something weird, right? I’ve stumbled across this many times when trying to define functions in one class that are called from another class. It took me a while to get to the root of the problem.
The reasoning behind this is that the x
inside of the function is different from x
outside it, so you can’t just change it however you want.
Fortunately, there is a simple solution for this problem: just throw a global
before the x
!
x = 2
def adicionar_5():
global x
x = x + 5
print(x)
adicionar_5()
That is, scope not only protects variables inside functions from the outside world. In Python, the external world is protected from local variables.
Mothers and daughters
If you use object-oriented programming – almost everyone does – classes are everywhere in your Python code. And one of the most useful features of the classes is (drum roll)…
… heritage!
That is, if you have a parent (or main) class with some properties, you can create child (or secondary) classes that inherit the same properties. So:
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
Child classes inherit the fact that x = 1
then we can call it and get the same result for both child and parent classes.
And if we change the attribute x
of a child class, it should only change within that class. For example, if the daughter dyes her hair, it does not change the mother’s hair color. So it goes like this:
filha1.x = 5
print(mae.x, filha1.x, filha2.x)
# isso retorna: 1 5 1
Continuing our comparison, we ask what happens when the mother dyes her hair: do the daughters’ hair color also change? Should not.
But in Python…
mae.x = 9
print(mae.x, filha1.x, filha2.x)
# isso retorna: 9 5 9
Oh, God.
This happens because of method resolution order from Python. Basically, child classes inherit everything the parent class has, as long as not stated otherwise. So, in Python’s world, if you don’t protest ahead of time, Mommy will dye her daughters’ hair whenever she dyes her own.
Is it the same or not?

The comparison operator equality ==
and comparison of identity is
they are two different things. This is subtle, and can get messy if you get confused.
Consider the following lists:
lista1 = []
lista2 = []
lista3 = lista1
You might naively think that the three lists are the same because they are all empty.
But not! Look at this:
lista1 == lista2
# retorna True, como esperado
lista1 is lista2
# retorna False
The second operation returns False because lista1
occupies a different chunk of memory than lista2
. They have different names and were started separately.
Let’s continue:
lista1 is lista3
# retorna True
What?
In this case, we would say that list3 is indeed the same as list1: in fact, they both point to the same piece of memory. It’s different than when I do this:
lista3 = lista1 + lista1
lista1 is lista3
# retorna False
This time it is false because a new piece of memory was used to store the values of the lista3
.
Fortunately, the vast majority of people aren’t working with a series of empty lists. Still, this question should pop up several times as you program: should I use ==
or is
inside a conditional structure if
?
The += operator can confuse you
In Python, the operator +=
allows you to perform sums more quickly. For example, in the code below:
x = 5
x += 3
The value of x
will become 5 + 3 = 8.
Right. Now consider the following:
nova_lista = [3, 5, 'paulo', 9]
nova_lista += 'ana'
# isso retorna [3, 5, 'paulo', 9, 'a', 'n', 'a']
Jeez. He separated the 'ana'
!
It would certainly be less complicated if we used +=
for a number? For example:
nova_lista += 34
Unfortunately, this will result in an error message saying “’int’ object is not iterable”. It’s not good…
Maybe we can’t add numbers to lists? In principle that’s it, but we can add numbers to elements of lists, provided that these elements are numbers themselves.
In lists, Python assigns position 0 to the first element, position 1 to the second element, and so on. Therefore, nova_lista[0]
matches the first item in the list. What happens if we use this operation?
nova_lista[0] += 8
It will add 8 to the first item in the list, which is number 3. So the list will look like this: [11, 5, ‘paulo’, 9, ‘a’, ‘n’, ‘a’].
But don’t try to add number to word! This operation…
# nova_lista[2] é igual a 'paulo'
nova_lista[2] += 9
… will lead to an error message warning that it is only possible to concatenate a string with another string, not with an integer.
That is, you can “add” one word to another:
nova_lista[2] += 'higa'
# retorna [11, 5, 'paulohiga', 9, 'a', 'n', 'a']
Isn’t it curious how 'ana'
was split when we included it in the list, but 'higa'
remained in full because it was added as a string?
The moral of the story is: always test your code before using the operator +=
!
incomplete cleaning

In lists, sometimes we need to remove elements that we are not going to use anymore; this takes us to the function remove()
.
Consider the following:
minhalista = [0, 22, 'Felipe', 41, 22, 'Lucas', 24]
minhalista.remove(22)
# isso retorna [0, 'Felipe', 41, 22, 'Lucas', 24]
I told him to remove an element equal to 22. Note that remove()
does not delete all entries “22”, only the first one.
If you want to remove from the list all the numbers 22 (or any other element you don’t want), you can write a loop like this one:
while 22 in minhalista: minhalista.remove(22)
# retorna [0, 'Felipe', 41, 'Lucas', 24]
A more elegant alternative would be to use list comprehension, like this:
minhalista = [x for x in minhalista if x != 22]
# retorna [0, 'Felipe', 41, 'Lucas', 24]
Bonus: pop() and clear()
In some cases you use a list as a stack of items, where you want to take the last item and do something with it. This is what the function is for pop()
.
The last item in the list above is 24, so the function pop()
will get this number:
idade = minhalista.pop()
# é 24, conforme a lista acima
Now the list has changed, and the last item is now ‘Lucas’. That is, this element will be hooked by the function pop()
:
nome = minhalista.pop()
# é 'Lucas'
Now we can use the variables idade
and nome
:
f'{nome} tem {idade} anos.'
# retorna 'Lucas tem 24 anos.'
f'{minhalista}'
# retorna "[0, 'Felipe', 41]"
Tired of all this cop-out? The function clear()
clears your list and your mind:
minhalista.clear()
f'{minhalista}'
# retorna []
This can even confuse, but it’s genius
Suppose the following: I have a list of numbers from 0 to 9, and I want to remove all that are divisible by 3.
I could do a loop dividing all the numbers in this list by 3, and excluding those that leave a zero remainder on division. Would be like this:
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
Unfortunately, this loop does not work, and will result in an error that the list index is “out of range”.
Basically, the loop deletes numbers from the list, so it will have less than 10 elements. But because of the “range(10)” condition, Python expects it to ever have 10 numbers. It will try to reach the 10th element – which will not exist – and give an error.
There is a very elegant solution for this case:
meus_numeros = [x for x in range(10) if x%3 != 0]
With just one line of code, we got the result we wanted: [1, 2, 4, 5, 7, 8].
In the code, the expression between square brackets []
is list comprehension – basically an abbreviated form for loops.
List comprehensions are usually a little faster than regular loops, which is nice if you’re dealing with large datasets.
Here we are just adding a clause if
to tell list comprehension that it should not include numbers that are divisible by 3.
Unlike some of the phenomena described above, this is not a case of Python madness. Even though beginners might stumble over this at first, this is genius.
join the discussion
Discuss this post and leave your comments in TB Community!
https://tecnoblog.net/especiais/o-python-tem-algumas-pegadinhas-que-os-iniciantes-precisam-conhecer/