Pular para o conteúdo

Como usar o deep copy e shallow copy no Python?

[

Cópia Profunda vs Cópia Rasa de Objetos em Python

Por Dan Bader

Em Python, fazer uma atribuição de variáveis não cria cópias de objetos, apenas faz com que os nomes se refiram a um objeto. Isso geralmente não faz diferença para objetos imutáveis.

No entanto, para trabalhar com objetos mutáveis ou coleções de objetos mutáveis, pode ser útil criar cópias reais ou clones desses objetos.

Às vezes, você pode precisar de cópias em que possa fazer modificações sem modificar automaticamente o objeto original ao mesmo tempo. Neste artigo, vou explicar como copiar ou “clonar” objetos em Python 3 e algumas ressalvas envolvidas.

Fazendo Cópias Rasas

As coleções mutáveis embutidas em Python, como listas, dicionários e conjuntos, podem ser copiadas chamando suas funções de fábrica em uma coleção existente:

new_list = list(original_list)
new_dict = dict(original_dict)
new_set = set(original_set)

No entanto, esse método não funciona para objetos personalizados e, além disso, ele cria apenas cópias rasas. Para objetos compostos como listas, dicionários e conjuntos, há uma diferença importante entre cópias rasas e profundas:

  • Uma cópia rasa cria um novo objeto de coleção e então popula-o com referências aos objetos filhos encontrados no objeto original. Em essência, uma cópia rasa é apenas um nível de profundidade. O processo de cópia não se repete e, portanto, não cria cópias dos objetos filhos em si.

  • Uma cópia profunda faz o processo recursivamente. Significa que primeiro se constrói um novo objeto de coleção e, em seguida, o preenche recursivamente com cópias dos objetos filhos encontrados no objeto original. Copiar um objeto dessa maneira percorre toda a árvore de objetos para criar um clone totalmente independente do objeto original e de todos os seus filhos.

Agora vamos olhar alguns exemplos para deixar clara essa diferença entre cópias profundas e rasas.

Exemplo de Cópia Rasa

No exemplo a seguir, vamos criar uma nova lista aninhada e fazer uma cópia rasa dela usando a função list():

xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
ys = list(xs) # Faz uma cópia rasa

Isso significa que ys agora será um objeto novo e independente com o mesmo conteúdo que xs. Podemos verificar isso inspecionando ambos os objetos:

print(xs)
# [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(ys)
# [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Para confirmar que ys é realmente independente do original, vamos fazer um pequeno experimento. Podemos adicionar uma nova sublista ao original (xs) e em seguida verificar se essa modificação não afetou a cópia (ys):

xs.append(['nova sublista'])
print(xs)
# [[1, 2, 3], [4, 5, 6], [7, 8, 9], ['nova sublista']]
print(ys)
# [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Como podemos ver, isso teve o efeito esperado. Modificar a lista copiada em um nível superficial não foi um problema.

No entanto, como fizemos apenas uma cópia rasa da lista original, ys ainda contém referências aos objetos filhos originais.

Fazendo Cópias Profundas

Para fazer uma cópia profunda, podemos usar a biblioteca copy do Python:

import copy
xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
ys = copy.deepcopy(xs) # Faz uma cópia profunda

Agora, ys é um clone completo de xs. Vamos verificar novamente:

print(xs)
# [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(ys)
# [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

A diferença é que, com a cópia profunda, os objetos filhos também são clonados. Isso é confirmado quando fazemos uma modificação superficial em xs e verificamos que ys permanece inalterada:

xs.append(['nova sublista'])
print(xs)
# [[1, 2, 3], [4, 5, 6], [7, 8, 9], ['nova sublista']]
print(ys)
# [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Copiando Objetos Arbitrários em Python

Para copiar objetos personalizados, a classe do objeto pode implementar o método mágico __copy__() e/ou __deepcopy__() para controlar o processo de cópia.

Tomemos como exemplo uma classe Person:

class Person:
def __init__(self, name):
self.name = name
def __repr__(self):
return f"Person({self.name})"

Podemos fazer uma cópia rasa dessa classe usando a função copy.copy() da biblioteca copy:

import copy
p1 = Person("Alice")
p2 = copy.copy(p1) # Faz uma cópia rasa de p1

No entanto, se quisermos fazer uma cópia profunda, precisamos implementar o método __copy__() na classe Person:

class Person:
def __init__(self, name):
self.name = name
def __repr__(self):
return f"Person({self.name})"
def __copy__(self):
return Person(self.name)

Agora podemos fazer uma cópia profunda usando a função copy.deepcopy():

import copy
p1 = Person("Alice")
p2 = copy.deepcopy(p1) # Faz uma cópia profunda de p1

Isso nos dará um clone completo do objeto p1 com todos os seus atributos e métodos.

3 Coisas para Lembrar

  1. Atribuição de variáveis em Python não cria cópias de objetos, apenas faz com que os nomes se refiram a um objeto.

  2. Cópias rasas criam um novo objeto e popula-o com referências aos objetos filhos encontrados no objeto original. As modificações superficiais em cópias rasas não afetam o objeto original, mas as modificações nos objetos filhos sim.

  3. Cópias profundas, por outro lado, criam um clone totalmente independente do objeto original e de todos os seus filhos. As modificações em cópias profundas não afetam nem o objeto original nem seus filhos.

Em resumo, ao trabalhar com objetos mutáveis em Python, é importante entender a diferença entre cópias rasas e profundas. Dependendo do caso de uso, você pode escolher o método de cópia mais adequado para evitar efeitos colaterais indesejados.

Gostou deste artigo? Compartilhe com seus amigos desenvolvedores e continue aprendendo mais sobre Python!