Módulo `copy` do Python

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 memo passado 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 lista children) são compartilhados com o root_node original. Como resultado, quando você adiciona um novo nó a shallow_copy, a lista children compartilhada é atualizada e a alteração também se reflete no conteúdo de root_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 de root_node. Portanto, mesmo que você adicione um novo nó a deep_copy, isso não afeta root_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, deepcopy usa memo para 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 tanto pickle quanto deepcopy. É 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 deepcopy pode 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 deepcopy ou forneça manualmente um mecanismo semelhante a memo.
  • 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.

YouTube Video