Módulo `copy` do Python
Este artigo explica o módulo copy do Python.
Focando nas diferenças entre cópias rasas e profundas, fornecemos uma explicação clara — desde os mecanismos básicos de duplicação de objetos até aplicações em classes personalizadas — juntamente com exemplos práticos.
YouTube Video
Módulo copy do Python
O módulo copy do Python é o módulo padrão para lidar com duplicação (cópia) de objetos. O objetivo é entender a diferença entre cópias rasas e profundas e ser capaz de controlar o comportamento de cópia para objetos personalizados.
Básico: O que é uma cópia rasa?
Aqui ilustramos o comportamento de cópias rasas para objetos mutáveis como listas e dicionários. Uma cópia rasa duplica apenas o objeto de nível superior e compartilha as referências internas.
1# Example: shallow copy with lists and dicts
2import copy
3
4# A nested list
5original = [1, [2, 3], 4]
6shallow = copy.copy(original)
7
8# Mutate nested element
9shallow[1].append(99)
10
11print("original:", original) # nested change visible in original
12print("shallow: ", shallow)Neste código, a lista de nível superior é duplicada, mas as sublistas internas são compartilhadas por referência, portanto shallow[1].append(99) também se reflete em original. Esse é um comportamento típico de uma cópia rasa.
O que é uma cópia profunda?
Uma cópia profunda duplica recursivamente o objeto e todas as suas referências internas, produzindo um objeto independente. Use-a quando quiser duplicar com segurança estruturas complexas e aninhadas.
1# Example: deep copy with nested structures
2import copy
3
4original = [1, [2, 3], {'a': [4, 5]}]
5deep = copy.deepcopy(original)
6
7# Mutate nested element of the deep copy
8deep[1].append(100)
9deep[2]['a'].append(200)
10
11print("original:", original) # original remains unchanged
12print("deep: ", deep)Neste exemplo, alterações em deep não afetam original. deepcopy copia todo o grafo de objetos, produzindo uma réplica independente.
Como decidir qual usar
Qual cópia usar deve ser determinado pela estrutura do objeto e pelo seu propósito. Se você não quiser que alterações em elementos mutáveis internos afetem o objeto original, deepcopy é apropriado. Isso porque ela duplica recursivamente todo o objeto, criando uma cópia completamente independente.
Por outro lado, se duplicar apenas o objeto de nível superior for suficiente e você valoriza velocidade e eficiência de memória, copy (uma cópia rasa) é mais adequado.
- Você quer evitar que o original mude ao modificar elementos mutáveis internos → use
deepcopy. - Duplicar apenas o nível superior é suficiente e você quer priorizar desempenho (velocidade/memória) → use
copy(cópia rasa).
Tratando objetos embutidos e imutáveis
Objetos imutáveis (por exemplo, int, str e tuplas cujos conteúdos são imutáveis) geralmente não precisam ser copiados. copy.copy pode retornar o mesmo objeto quando apropriado.
1# Example: copying immutables
2import copy
3
4a = 42
5b = copy.copy(a)
6print(a is b) # True for small ints (implementation detail)
7
8s = "hello"
9t = copy.deepcopy(s)
10print(s is t) # True (strings are immutable)Como copiar objetos imutáveis traz pouco benefício de eficiência, o mesmo objeto pode ser reutilizado. Raramente isso é algo com que você precise se preocupar no design da aplicação.
Classes personalizadas: definindo __copy__ e __deepcopy__
Se a cópia padrão não for o que você espera para sua classe, você pode fornecer sua própria lógica de cópia. Se você definir __copy__ e __deepcopy__, copy.copy e copy.deepcopy irão usá-los.
1# Example: customizing copy behavior
2import copy
3
4class Node:
5 def __init__(self, value, children=None):
6 self.value = value
7 self.children = children or []
8
9 def __repr__(self):
10 return f"Node({self.value!r}, children={self.children!r})"
11
12 def __copy__(self):
13 # Shallow copy: create new Node, but reuse children list reference
14 new = self.__class__(self.value, self.children)
15 return new
16
17 def __deepcopy__(self, memo):
18 # Deep copy: copy value and deepcopy each child
19 new_children = [copy.deepcopy(child, memo) for child in self.children]
20 new = self.__class__(copy.deepcopy(self.value, memo), new_children)
21 memo[id(self)] = new
22 return new- Ao implementar
__copy__e__deepcopy__, você pode controlar de forma flexível o comportamento de cópia específico da classe. Por exemplo, você pode tratar casos em que certos atributos devem apontar para (compartilhar) o mesmo objeto, enquanto outros atributos devem ser duplicados como objetos completamente novos. - O argumento
memopassado para__deepcopy__é um dicionário que registra os objetos já processados durante a cópia recursiva e evita loops infinitos causados por referências circulares.
1# Build tree
2root_node = Node('root', [Node('child1'), Node('child2')])
3
4shallow_copy = copy.copy(root_node) # shallow copy
5deep_copy = copy.deepcopy(root_node) # deep copy
6
7# Modify children
8# affects both root_node and shallow_copy
9shallow_copy.children.append(Node('child_shallow'))
10# affects deep_copy only
11deep_copy.children.append(Node('child_deep'))
12
13# Print results
14print("root_node:", root_node)
15print("shallow_copy:", shallow_copy)
16print("deep_copy:", deep_copy)- Neste código, a variável
shallow_copyé criada por meio de uma 'cópia rasa', portanto apenas o nível superior do objeto é duplicado, e os objetos que ele referencia internamente (neste caso, a listachildren) são compartilhados com oroot_nodeoriginal. Como resultado, quando você adiciona um novo nó ashallow_copy, a listachildrencompartilhada é atualizada e a alteração também se reflete no conteúdo deroot_node. - Por outro lado, a variável
deep_copyé criada por meio de uma 'cópia profunda', portanto a estrutura interna é duplicada recursivamente, produzindo uma árvore completamente independente deroot_node. Portanto, mesmo que você adicione um novo nó adeep_copy, isso não afetaroot_node.
Referências cíclicas (objetos recursivos) e a importância de memo
Quando um grafo de objetos complexo referencia a si mesmo (referências cíclicas), deepcopy usa um dicionário memo para rastrear objetos já copiados e evitar loops infinitos.
1# Example: recursive list and deepcopy memo demonstration
2import copy
3
4a = []
5b = [a]
6a.append(b) # a -> [b], b -> [a] (cycle)
7
8# deepcopy can handle cycles
9deep = copy.deepcopy(a)
10print("deep copy succeeded, length:", len(deep))- Internamente,
deepcopyusamemopara referir-se a objetos já duplicados, permitindo uma cópia segura mesmo na presença de ciclos. Você precisa de um mecanismo semelhante ao realizar recursão manualmente.
copyreg e cópia personalizada em estilo de serialização (avançado)
Se você quiser registrar um comportamento especial de cópia em coordenação com bibliotecas, pode usar o módulo padrão copyreg para registrar como os objetos são (re)construídos. Isso é útil para objetos complexos e tipos de extensões em C.
1# Example: using copyreg to customize pickling/copy behavior (brief example)
2import copy
3import copyreg
4
5class Wrapper:
6 def __init__(self, data):
7 self.data = data
8
9def reduce_wrapper(obj):
10 # Return callable and args so object can be reconstructed
11 return (Wrapper, (obj.data,))
12
13copyreg.pickle(Wrapper, reduce_wrapper)
14
15w = Wrapper([1, 2, 3])
16w_copy = copy.deepcopy(w)
17print("w_copy.data:", w_copy.data)- Usando
copyreg, você pode fornecer regras de reconstrução que afetam tantopicklequantodeepcopy. É uma API avançada e, na maioria dos casos,__deepcopy__é suficiente.
Advertências e armadilhas práticas
Há vários pontos importantes para garantir o comportamento correto ao usar o módulo copy. Abaixo explicamos armadilhas comuns que você pode encontrar no desenvolvimento e como evitá-las.
- Desempenho
deepcopypode consumir quantidades significativas de memória e CPU, portanto, considere cuidadosamente se é necessário. - Manipulação de elementos que você deseja compartilhar
Se você quiser que alguns atributos, como caches grandes, permaneçam compartilhados, evite intencionalmente copiar suas referências dentro de
__deepcopy__. - Estado interno imutável Se você mantiver dados imutáveis internamente, a cópia pode ser desnecessária.
- Threads e recursos externos Recursos que não podem ser copiados, como sockets e descritores de arquivo, ou não fazem sentido copiar ou causarão erros; portanto, você deve evitar copiá-los por design.
Exemplo prático: um padrão para atualizar dicionários com segurança
Ao atualizar um objeto de configuração complexo, este exemplo usa deepcopy para proteger as configurações originais.
1# Example: safely update a nested configuration using deepcopy
2import copy
3
4default_config = {
5 "db": {"host": "localhost", "ports": [5432]},
6 "features": {"use_cache": True, "cache_sizes": [128, 256]},
7}
8
9# Create a working copy to modify without touching defaults
10working = copy.deepcopy(default_config)
11working["db"]["ports"].append(5433)
12working["features"]["cache_sizes"][0] = 512
13
14print("default_config:", default_config)
15print("working:", working)Usando deepcopy, você pode criar configurações derivadas com segurança sem arriscar danos às configurações padrão. Isso é especialmente útil para estruturas mutáveis aninhadas, como configurações.
Melhores práticas
Para usar o módulo copy de forma segura e eficaz, é importante ter em mente as seguintes diretrizes práticas.
- Escolha entre cópias rasas e profundas com base em se ocorrerão mudanças e em seu escopo.
- Implemente
__copy__e__deepcopy__em classes personalizadas para obter o comportamento esperado. - Como cópias profundas são custosas, reduza a necessidade de cópia por meio do design quando possível. Considere imutabilidade e métodos de clonagem explícitos, entre outras técnicas.
- Ao lidar com referências cíclicas, aproveite
deepcopyou forneça manualmente um mecanismo semelhante amemo. - Projete de modo que recursos externos, como manipuladores de arquivo e threads, não sejam copiados.
Conclusão
O módulo copy é a ferramenta padrão para duplicação de objetos em Python. Ao entender corretamente as diferenças entre cópias rasas e profundas e implementar um comportamento de cópia personalizado quando necessário, você pode realizar duplicações de forma segura e previsível. Esclarecendo na fase de design se a cópia é realmente necessária e o que deve ser compartilhado, você pode evitar bugs desnecessários e problemas de desempenho.
Você pode acompanhar o artigo acima usando o Visual Studio Code em nosso canal do YouTube. Por favor, confira também o canal do YouTube.