Python __copy__ Method
Last modified April 8, 2025
This comprehensive guide explores Python's __copy__ method, the
special method that controls shallow copying behavior. We'll cover basic usage,
custom implementations, and practical examples.
Basic Definitions
The __copy__ method defines how an object should be copied when
the copy.copy() function is called. It returns a shallow copy of
the object.
Key characteristics: it takes no parameters (except self), should return a new
object, and is called by copy.copy(). For deep copies, implement
__deepcopy__ instead.
Basic __copy__ Implementation
Here's a simple implementation showing how __copy__ works with
basic objects. It demonstrates the default behavior and customization.
import copy
class Box:
def __init__(self, width, height):
self.width = width
self.height = height
def __copy__(self):
print("__copy__ called")
return Box(self.width, self.height)
def __repr__(self):
return f"Box({self.width}, {self.height})"
original = Box(10, 20)
copy_obj = copy.copy(original)
print(copy_obj)
This example shows a basic __copy__ implementation. When
copy.copy() is called, it invokes __copy__ which
creates a new Box with the same dimensions.
The output would show "__copy__ called" and print the copied Box. Both objects are separate instances with identical attribute values.
Shallow Copy vs Deep Copy
This example demonstrates the difference between shallow and deep copies when
dealing with nested objects and the role of __copy__.
import copy
class Container:
def __init__(self, items):
self.items = items
def __copy__(self):
print("Shallow copy")
return Container(self.items)
def __deepcopy__(self, memo):
print("Deep copy")
return Container(copy.deepcopy(self.items, memo))
original = Container([[1, 2], [3, 4]])
shallow = copy.copy(original)
deep = copy.deepcopy(original)
original.items[0][0] = 99
print("Shallow:", shallow.items) # Affected by change
print("Deep:", deep.items) # Unaffected
This example shows how shallow copies share references to nested objects while
deep copies create completely independent copies. The __copy__
method handles the shallow copy case.
After modifying the original's nested list, the shallow copy reflects the change while the deep copy remains unchanged. This demonstrates reference sharing.
Custom Copy Behavior
__copy__ can be customized to control exactly what gets copied and
how. This example shows selective attribute copying.
import copy
class Account:
def __init__(self, balance, transactions):
self.balance = balance
self.transactions = transactions
self.last_access = None
def __copy__(self):
print("Creating account copy")
new_account = Account(self.balance, self.transactions.copy())
new_account.last_access = "Copied from original"
return new_account
original = Account(1000, ["deposit 500", "withdraw 200"])
copy_acc = copy.copy(original)
original.balance = 1500
original.transactions.append("withdraw 300")
print("Original:", original.balance, original.transactions)
print("Copy:", copy_acc.balance, copy_acc.transactions)
print("Copy access:", copy_acc.last_access)
This Account class implements custom copy behavior. The balance is copied as-is, transactions get a shallow copy, and last_access gets a special value.
Changes to the original's balance don't affect the copy (primitive type), but
transaction list changes would affect the copy if not for the explicit
copy() call in __copy__.
Copying Immutable Objects
For immutable objects, __copy__ can often just return self since
the object can't be modified. This example demonstrates this optimization.
import copy
class ImmutablePoint:
def __init__(self, x, y):
self._x = x
self._y = y
@property
def x(self):
return self._x
@property
def y(self):
return self._y
def __copy__(self):
print("Immutable copy")
return self # Safe because object is immutable
def __repr__(self):
return f"Point({self.x}, {self.y})"
point = ImmutablePoint(3, 4)
point_copy = copy.copy(point)
print(point is point_copy) # True, same object
This immutable point class returns itself from __copy__ since its
state can't change. This is safe and efficient for immutable objects.
The is check confirms both variables reference the same object.
This optimization prevents unnecessary object creation for immutable types.
Combining __copy__ with __deepcopy__
This example shows a class implementing both copy methods to handle different copying scenarios appropriately.
import copy
class Node:
def __init__(self, value, children=None):
self.value = value
self.children = children if children is not None else []
def __copy__(self):
print("Shallow copying Node")
return Node(self.value, self.children)
def __deepcopy__(self, memo):
print("Deep copying Node")
return Node(copy.deepcopy(self.value, memo),
[copy.deepcopy(child, memo) for child in self.children])
root = Node(1, [Node(2), Node(3)])
shallow_root = copy.copy(root)
deep_root = copy.deepcopy(root)
root.children.append(Node(4))
print("Original children count:", len(root.children))
print("Shallow copy children count:", len(shallow_root.children))
print("Deep copy children count:", len(deep_root.children))
This Node class implements both copy methods. The shallow copy shares child references while the deep copy creates a complete independent tree structure.
After adding a child to the original, the shallow copy reflects this change (shared reference) while the deep copy maintains its original structure.
Best Practices
- Implement both methods: Provide
__copy__and__deepcopy__when needed - Document behavior: Clearly document your copy semantics
- Handle all attributes: Ensure all relevant attributes are copied
- Consider immutability: Return self for truly immutable objects
- Use copy module: Leverage
copyanddeepcopyin implementations
Source References
Author
List all Python tutorials.