por Flávio Juvenal
Estes slides sabem interpretar Python!
l = [1,2,3]
l[1:]
[2, 3]
você sabe o que acontece quando você cria uma variável com um nome igual a um built-in?
sum([1,2,3])
6
sum = 1 + 2 # SyntaxError?
sum([1,2,3])
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-6-426f9aa1ca06> in <module>() 1 sum = 1 + 2 # SyntaxError? ----> 2 sum([1,2,3]) TypeError: 'int' object is not callable
Use algum editor com syntax-highlighting para saber quando está sobrescrevendo um built-in! Quando precisar dar a uma variável um nome built-in, coloque underline no fim, como em sum_
.
você sabe quando um objeto em Python é (is
) outro?
a = 256
b = 256
a is b
True
a = 257
b = 257
a is b
False
Python tem uma cache de inteiros de [-5, 256], o que faz com que a
e b
sejam referências para o mesmo objeto. Como int
s em Python são imutáveis, essa cache não introduz nenhum bug.
Note que isto é um detalhe da implementação CPython e pode ser diferente em outras implementações. [1]
!cat int_example.py
a = 257 b = 257 print(a is b)
!python int_example.py
True
Python faz otimizações com as literais que ocorrem em um mesmo arquivo. Se quisermos comparar valor, devemos sempre usar ==
[2]
me = {'name': 'Fulano', 'age': 30}
people = [me] * 3
people
[{'age': 30, 'name': 'Fulano'}, {'age': 30, 'name': 'Fulano'}, {'age': 30, 'name': 'Fulano'}]
people[0]['name'] = 'Sicrano'
people
[{'age': 30, 'name': 'Sicrano'}, {'age': 30, 'name': 'Sicrano'}, {'age': 30, 'name': 'Sicrano'}]
[me] * 3
é equivalente a [me, me, me]
, ou seja, 3 referências para o mesmo objeto.
line = [' '] * 3
line
[' ', ' ', ' ']
game_table = [line] * 3
game_table
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
def print_game_table():
for l in game_table:
print(l)
print_game_table()
[' ', ' ', ' '] [' ', ' ', ' '] [' ', ' ', ' ']
game_table[2][0] = 'X'
print_game_table()
['X', ' ', ' '] ['X', ' ', ' '] ['X', ' ', ' ']
game_table[0] is game_table[1] and \
game_table[1] is game_table[2]
True
id(game_table[0]) == id(game_table[1]) == id(game_table[2])
True
id
representa a identidade do objeto. Se duas variáveis tem o mesmo id
, elas representam o mesmo objeto.
Em CPython, id
é o endereço do objeto na memória. [3]
# Nice 🙂
game_table = [[' ' for j in range(3)] for i in range(3)]
print_game_table()
[' ', ' ', ' '] [' ', ' ', ' '] [' ', ' ', ' ']
# Nice 🙂
game_table[2][0] = 'O'
print_game_table()
[' ', ' ', ' '] [' ', ' ', ' '] ['O', ' ', ' ']
game_table = [[' '] * 3 for i in range(3)]
print_game_table()
[' ', ' ', ' '] [' ', ' ', ' '] [' ', ' ', ' ']
game_table = [[{}] * 3 for i in range(3)]
game_table[0][0]['name'] = 'Fulano'
print_game_table()
[{'name': 'Fulano'}, {'name': 'Fulano'}, {'name': 'Fulano'}] [{}, {}, {}] [{}, {}, {}]
Resumindo:
id
para ver qual objeto uma variável representais
para ver se duas variáveis representam o mesmo objetofor
stuplas são imutáveis, mas tem alguma maneira de mudar seus elementos?
a = ([42],)
a[0] += [43]
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-23-a335abb8a98e> in <module>() 1 a = ([42],) ----> 2 a[0] += [43] TypeError: 'tuple' object does not support item assignment
a
([42, 43],)
+=
é operador de adição in-place. Ele faz duas coisas:
obj.__iadd__(rhs)
, que pode modificar o objeto caso ele seja mutável.obj.__iadd__(rhs)
para a variável.Apenas esta segunda ação falha no a[0] += [43]
quando a
é uma tupla. A primeira é feita! E, para listas, a primeira ação modifica a lista. [8] [9]
l = [42]
l + [43]
l
[42]
Não confunda! O +
não tem comportamento in-place. Ele retorna um novo objeto (pelo menos para os tipos built-in do Python).
você está usando variáveis de classe corretamente?
class Dog:
tricks = []
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
def print_tricks(self):
print(self.name, ' tricks:')
for trick in self.tricks:
print(trick)
teddy = Dog("Teddy")
teddy.add_trick("wat")
teddy.print_tricks()
Teddy tricks: wat
kika = Dog("Kika")
kika.add_trick("pray")
kika.print_tricks()
Kika tricks: wat pray
teddy.print_tricks()
Teddy tricks: wat pray
# Nice 🙂
class Dog:
def __init__(self, name):
self.tricks = []
self.name = name
# ...
Cuidado com a diferença entre variáveis de classe e de instância. [10]
Exemplo de bug real: [11]
class A:
x = 1
class B(A):
pass
class C(A):
pass
print(A.x, B.x, C.x)
1 1 1
B.x = 2
print(A.x, B.x, C.x)
1 2 1
A.x = 3
print(A.x, B.x, C.x)
3 2 3
Quando um atributo ou método não é encontrado em uma classe, ele é procurado nas suas classes mães, seguindo o MRO (Method Resolution Order).
class Homer:
x = 1
class Pikachu(Homer):
pass
homer_instance = Homer()
homer_instance.x = 2
print("Homer.x", Homer.x)
print("Pikachu.x", Pikachu.x)
Homer.x 1 Pikachu.x 1
Se uma variável é atribuída através da instância, ela é uma variável de instância. Se é atribuída através da classe, ela é uma variável de classe. [12] [13]
você entende as regras de escopo de Python?
x = 10
def next_x():
return x + 1
next_x()
11
x = 10
def increment():
x += 1
return x
increment()
--------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) <ipython-input-36-3416d537e798> in <module>() 3 x += 1 4 return x ----> 5 increment() <ipython-input-36-3416d537e798> in increment() 1 x = 10 2 def increment(): ----> 3 x += 1 4 return x 5 increment() UnboundLocalError: local variable 'x' referenced before assignment
Quando você faz uma atribuição para uma variável em um escopo, essa variável é automaticamente considerada como local nesse escopo e esconde qualquer variável com o mesmo nome no escopo externo. [4]
def counter():
x = 10
def increment_aux():
x += 1
return x
return increment_aux()
counter()
--------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) <ipython-input-37-991a07a4bcf3> in <module>() 5 return x 6 return increment_aux() ----> 7 counter() <ipython-input-37-991a07a4bcf3> in counter() 4 x += 1 5 return x ----> 6 return increment_aux() 7 counter() <ipython-input-37-991a07a4bcf3> in increment_aux() 2 x = 10 3 def increment_aux(): ----> 4 x += 1 5 return x 6 return increment_aux() UnboundLocalError: local variable 'x' referenced before assignment
A solução para não criar uma nova variável local e fazer uma atribuição é usar as keywords global
ou nonlocal
# Nice 🙂
x = 10
def increment():
global x
x += 1
return x
increment()
11
# Nice 🙂
def counter():
x = 10
def increment_aux():
nonlocal x
x += 1
return x
return increment_aux()
counter()
11
LEGB rule:
# Nice 🙂
def heads_or_tails(is_head):
if is_head:
coin = 'heads'
else:
coin = 'tails'
print(coin)
heads_or_tails(is_head=False)
tails
print(type) # built-in
type = 'global'
def generate_fn():
type = 'enclosing'
def fn():
type = 'local'
return type
return fn
print(generate_fn()())
<class 'type'> local
import dis
a = 1
def my_a():
return a
dis.dis(my_a)
4 0 LOAD_GLOBAL 0 (a) 3 RETURN_VALUE
a = 1
def my_a():
a += 1
return a
dis.dis(my_a)
3 0 LOAD_FAST 0 (a) 3 LOAD_CONST 1 (1) 6 INPLACE_ADD 7 STORE_FAST 0 (a) 4 10 LOAD_FAST 0 (a) 13 RETURN_VALUE
LOAD_GLOBAL: Loads the global onto the stack.
LOAD_FAST: Pushes a reference to the local onto the stack. [5]
você sabe quando default arguments são avaliados?
def add_samoieda(dogs=[]):
dogs.append("samoieda")
return dogs
add_samoieda(['samoieda'])
['samoieda', 'samoieda']
print(add_samoieda())
print(add_samoieda())
['samoieda'] ['samoieda', 'samoieda']
Default arguments são avaliados no momento da definição da função. Se eles forem mutáveis (como uma lista), vão compartilhar estado durante diferentes chamadas, o que normalmente é indesejado. [6]
add_samoieda.__defaults__
(['samoieda', 'samoieda'],)
add_samoieda.__defaults__[0].append('ohh')
add_samoieda()
['samoieda', 'samoieda', 'ohh', 'samoieda']
closures acessam variáveis como você espera?
def create_multipliers():
return [lambda x : i * x for i in range(5)]
for multiplier in create_multipliers():
print(multiplier(2), end=" ")
8 8 8 8 8
As closures em Python são late-binding. Ou seja, os valores de variáveis são acessados só no momento em que a closure é chamada.
Isso não é exclusivo para lambda
, o mesmo ocorre com def
[7]
# Nice 🙂
def create_multipliers():
return [lambda x, i=i : i * x for i in range(5)]
for multiplier in create_multipliers():
print(multiplier(2), end=" ")
0 2 4 6 8
A solução é usar o WTF anterior, já que Default Arguments são avaliados no momento de definição!
Confuso? Todos Estamos! 😧😧😧
Fontes:
[1] https://docs.python.org/3/c-api/long.html#c.PyLong_FromLong
[2] http://stackoverflow.com/a/15172182/145349
[3] https://docs.python.org/3/library/functions.html#id
[4] https://docs.python.org/3/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value
[5] https://docs.python.org/2/library/dis.html#opcode-LOAD_FAST
[6] https://docs.python.org/3/faq/programming.html#why-are-default-values-shared-between-objects
[7] https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result
[8] http://stackoverflow.com/a/21361412/145349
[9] https://docs.python.org/3/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works
[10] https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables
[11] https://github.com/allisson/django-pagseguro2/pull/6
[12] https://www.toptal.com/python/top-10-mistakes-that-python-programmers-make#common-mistake-2-using-class-variables-incorrectly
[13] https://www.toptal.com/python/python-class-attributes-an-overly-thorough-guide#handling-assignment