Nas
partes anteriores, vimos fatias, coleções de descompactação / embalagem e alguns recursos de operações e tipos booleanos.
Os
comentários mencionaram a possibilidade de multiplicar coleções por um escalar:
a = [0] * 3 s = 'a' * 2 print(a, s)
Um desenvolvedor python mais ou menos experiente sabe que falta um mecanismo de
cópia ao escrever a = [0] b = a b[0] = 1 print(a, b)
O que produzirá o seguinte código?
b = a * 2 b[0] = 2 print(a, b)
O Python, neste caso, trabalha com o princípio da menor surpresa: na variável a, temos uma unidade, ou seja, b pode ser declarada e como
b = [1] * 2
O comportamento neste caso será o mesmo:
b = a * 2 b[0] = 2 print(a, b)
Mas não tão simples, o python copia o conteúdo da lista quantas vezes você o multiplicou e constrói uma nova lista. E nas listas, os links para valores são armazenados e, se, ao copiar referências para tipos imutáveis, tudo está bem, mas com mutável o efeito da ausência de cópia durante a gravação sai.
row = [0] * 2 matrix = [row] * 2 print(matrix)
Listar geradores e numpy para ajudá-lo neste caso.
As listas podem ser adicionadas e até incrementadas, e qualquer iterador pode estar à direita:
a = [0] a += (1,) a += {2} a += "ab" a += {1: 2} print(a)
Pergunta com um truque (para uma entrevista): em python, os parâmetros são passados por referência ou por valor?
def inc(a): a += 1 return a a = 5 print(inc(a)) print(a)
Como vemos, as mudanças no valor no corpo da função não alteraram o valor do objeto fora dele; daí podemos concluir que os parâmetros são passados por valor e escrevemos o seguinte código:
def appended(a): a += [1] return a a = [5] print(appended(a))
Idiomas como C ++ têm variáveis armazenadas na pilha e na memória dinâmica. Quando a função é chamada, colocamos todos os argumentos na pilha, após o que transferimos o controle para a função. Ela conhece os tamanhos e deslocamentos das variáveis na pilha, para poder interpretá-las corretamente.
Ao mesmo tempo, temos duas opções: copiar a memória da variável na pilha ou colocar uma referência ao objeto na memória dinâmica (ou em níveis mais altos da pilha).
Obviamente, ao alterar os valores na pilha de funções, os valores na memória dinâmica não mudam, mas ao alterar a área de memória por referência, modificamos a memória compartilhada, para que todos os links para a mesma área de memória “vejam” o novo valor.
Em python, eles abandonaram um mecanismo semelhante, a substituição é o mecanismo de atribuição do nome da variável com o objeto, por exemplo, ao criar uma variável:
var = "john"
O intérprete cria o objeto "john" e o "nome" var e, em seguida, associa o objeto ao nome fornecido.
Quando uma função é chamada, nenhum novo objeto é criado; em vez disso, um nome é criado em seu escopo associado a um objeto existente.
Mas python tem tipos mutáveis e imutáveis. O segundo, por exemplo, inclui números: durante operações aritméticas, os objetos existentes não são alterados, mas um novo objeto é criado, ao qual o nome existente é associado. Se, depois disso, nenhum nome estiver associado ao objeto antigo, ele será excluído usando o mecanismo de contagem de links.
Se o nome estiver associado a uma variável do tipo variável, durante as operações com ele a memória do objeto será alterada; portanto, todos os nomes associados a essa área de memória “verão” as alterações.
Você pode ler sobre isso na
documentação , descrita em mais detalhes
aqui .
Outro exemplo:
a = [1, 2, 3, 4, 5] def rev(l): l.reverse() return l l = a print(a, l)
Mas e se decidíssemos alterar a variável fora da função? Nesse caso, o modificador global nos ajudará:
def change(): global a a += 1 a = 5 change() print(a)
Nota: não faça isso (não, sério, não use variáveis globais em seus programas e, especialmente, não em seus próprios). É melhor retornar apenas alguns valores da função:
def func(a, b): return a + 1, b + 1
No entanto, em python, há outro escopo e a palavra-chave correspondente:
def private(value=None): def getter(): return value def setter(v): nonlocal value value = v return getter, setter vget, vset = private(42) print(vget())
Neste exemplo, criamos uma variável que pode ser alterada (e cujo valor é obtido) apenas por meio de métodos, você pode usar um mecanismo semelhante nas classes:
def private(value=None): def getter(): return value def setter(v): nonlocal value value = v return getter, setter class Person: def __init__(self, name): self.getid, self.setid = private(name) adam = Person("adam") print(adam.getid()) print(adam.setid("john")) print(adam.getid()) print(dir(adam))
Mas talvez seja melhor nos limitarmos às
propriedades ou à definição de
__getattr__ ,
__setattr__ .
Você pode até definir
__delattr__ .
Outro recurso do python é a presença de dois métodos para obter o atributo: __getattr__ e
__getattribute__ .
Qual é a diferença entre eles? O primeiro é chamado apenas se o atributo na classe não foi encontrado e o segundo é incondicional. Se ambos forem declarados na classe, __getattr__ será chamado apenas se for explicitamente chamado em __getattribute__ ou se __getattribute__ tiver gerado um AttributeError.
class Person(): def __getattr__(self, item): print("__getattr__") if item == "name": return "john" raise AttributeError def __getattribute__(self, item): print("__getattribute__") raise AttributeError person = Person() print(person.name)
E, finalmente, um exemplo de como o python manipula livremente variáveis e escopos:
e = 42 try: 1 / 0 except Exception as e: pass print(e)
A propósito, este é talvez o único exemplo em que o segundo python é melhor que o terceiro, porque gera:
... print(e)