copium 0.1.0a1.dev158
Fast drop-in replacement for copy.deepcopy()
Requires Python: >=3.10

<h1>
copium
<a href="https://pypi.python.org/pypi/copium">
  <img src="https://img.shields.io/pypi/v/copium.svg" alt="PyPI Version Badge">
</a>
<a href="https://pypi.python.org/pypi/copium">
  <img src="https://img.shields.io/pypi/l/copium.svg" alt="PyPI License Badge">
</a>
<a href="https://pypi.python.org/pypi/copium">
  <img src="https://img.shields.io/pypi/pyversions/copium.svg" alt="PyPI Python Versions Badge">
</a>
<a href="https://github.com/Bobronium/copium/actions">
  <img src="https://github.com/Bobronium/copium/actions/workflows/cd.yaml/badge.svg" alt="CD Status Badge">
</a>
<a href="https://codspeed.io/Bobronium/copium">
<img src="https://img.shields.io/badge/Codspeed-copium-8A2BE2?style=flat&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MCA0MCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQgbWVldCI%2BCiAgICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTAwLDEwKSB0cmFuc2xhdGUoMTIwLDEyKSBzY2FsZSgxLjMpIHRyYW5zbGF0ZSgtMTIwLC0xMikiPiI%2BCiAgICAgICAgPHBhdGggZmlsbD0iI0VENkUzRCIgZmlsbC1ydWxlPSJldmVub2RkIgogICAgICAgICAgICAgIGQ9Ik0xMTAuMTIxIDE3LjExN2MuNzY2LjE3IDEuMzA4LjA1IDEuMzkyLjA2NGwuMDA0LjAwMWMxLjI3NS42OTEgMi4yMDIgMS4yNzkgMy4wOTcgMS42NTVsLS4xMDcuMDFxLTEuMDkyLjE3Mi0xLjU3Mi4yNWMtMy4yNzYuNTMzLTQuODg0LS4zOTgtNC41MzItMS44My4xNDItLjU3OC45MzgtLjMyNCAxLjcxOC0uMTVtMTEuMDA0LTEzLjkxYzIuMDc0IDEuNTM0IDIuNjcgMi4zMzEgMy43NzQgMy41NTUgMi43MDggMCA0LjIyIDIuMDI2IDMuNzM1IDUuMDQ2LS4zMDggMS45MjEtNC4xNSAxLjI0Ni01LjA2IDBxLS45MTIuODI2LTQuNDgzIDMuNjYzbC0uMDk3LjA3NmMtLjY5NS41NTMtMy4zNzcuMzc2LTMuNjM0LjE4N3EuODA2LTEuMzI1IDEuMTYxLTIuMDcyYy4zNTYtLjc0NS42MDUtMS40OTMuNjA1LTIuNzMyIDAtMS4yMzgtLjY5NS0yLjI5LTIuMTY2LTIuMjYzYS4yOC4yOCAwIDAgMC0uMjc0LjI5NWMwIC4xOTUuMTQ1LjI5Ni4yNzQuMjk2Ljc3OSAwIDEuMzI1Ljk2OCAxLjMyNSAxLjY3My4wMDEuNzA0LS4xMTEgMS4yNzUtLjQ0NCAyLjEzNC0uMjg3Ljc0MS0xLjQ0NCAyLjU4My0xLjc0NSAyLjc2N2EuMjc4LjI3OCAwIDAgMCAuMDQyLjQ4NnEuMDMxLjAxNS4wNzkuMDMuMS4wMzIuMjUzLjA3MWMuMjYyLjA2NC41ODEuMTIxLjk0LjE2My45ODcuMTEzIDIuMDk0LjA5IDMuMjc0LS4xMmwuMDQ1LS4wMDljLjM1Mi0uMDY0Ljg2NS0uMDY5IDEuMzcyLS4wMDMuNTkzLjA3OCAxLjEzMy4yNDQgMS41NDMuNDkzLjM2LjIxOC42MDguNDkuNzM1LjgybC4wMTIuMDM2cS4wOC4yNjMuMDguNTY2YzAgMS4wODMtMi4zMDguNDM0LTQuOTc2LjMxOGE5IDkgMCAwIDAtLjYxLS4wMDJjLTEuMDg5LS4wNTUtMS45ODUtLjM3NC0zLjE4Ni0uOTc0bC4wMjEtLjAwNHMtLjA5Mi0uMDM4LS4yMzgtLjEwNmMtLjM1Ni0uMTgyLS43NC0uMzg3LTEuMTYyLS42MTZoLS4wMDNjLS4zOTgtLjI0OC0uNzQ5LS41MjctLjgzOC0uNzc2LS4yMzMtLjY1MS0uMTE4LS42NTEuNzE1LTEuNjEzLTEuNDIyLjE3NS0xLjQ1Ny4yNzYtMy4wNzguMjc2cy00LjI5Mi0uMDgzLTQuMjkyLTEuNjdxMC0xLjU5IDIuMTYxLTEuMjM2LS41MjctMi44OSAxLjgwNy01LjJjNC4wNzYtNC4wMzUgOS41NzggMS41MjUgMTMuMzUgMS41MjUgMS43MTYgMC0zLjAyNS0yLjY5My00Ljk5NS0zLjQ1NnMxLjEzMS0zLjcyOSAzLjk3OC0xLjYyNG00Ljc0OCA1LjU1MmMtLjMxIDAtLjU2MS4yNy0uNTYxLjYwNXMuMjUxLjYwNC41NjEuNjA0LjU2MS0uMjcuNTYxLS42MDQtLjI1MS0uNjA1LS41NjEtLjYwNSIKICAgICAgICAgICAgICBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KICAgIDwvZz4KPC9zdmc%2B&logoSize=auto&labelColor=1B2330&color=ED6E3D&link=https%3A%2F%2Fcodspeed.io%2FBobronium%2Fcopium" alt="Codspeed Badge">
</a>
</h1>

<div align="center"><h3>Makes Python <code>deepcopy()</code> fast.</h3></div>

<div align="left">
  <picture>
    <source srcset="https://raw.githubusercontent.com/Bobronium/copium/b177d428dbb172fa9e22831d32db04c88b04ece1/assets/copium_logo_512.png" media="(prefers-color-scheme: dark)">
    <source srcset="https://raw.githubusercontent.com/Bobronium/copium/b177d428dbb172fa9e22831d32db04c88b04ece1/assets/copium_logo_light_512.png" media="(prefers-color-scheme: light)">
    <img src="https://raw.githubusercontent.com/Bobronium/copium/b177d428dbb172fa9e22831d32db04c88b04ece1/assets/copium_logo_512.png" alt="Copium Logo" width="200" align="left">
  </picture>
  <a href="https://github.com/Bobronium/copium/blob/1e4197bd54cc87659de44fb7cd129efb70f2af5d/showcase.ipynb">
    <picture>
      <source srcset="https://raw.githubusercontent.com/Bobronium/copium/1e4197bd54cc87659de44fb7cd129efb70f2af5d/assets/chart_dark.svg" media="(prefers-color-scheme: dark)">
      <source srcset="https://raw.githubusercontent.com/Bobronium/copium/1e4197bd54cc87659de44fb7cd129efb70f2af5d/assets/chart_light.svg" media="(prefers-color-scheme: light)">
      <img src="https://raw.githubusercontent.com/Bobronium/copium/1e4197bd54cc87659de44fb7cd129efb70f2af5d/assets/chart_light.svg" alt="Self-contained IPython showcase" width="500">
    </picture>
  </a>

</div>


## Highlights

- ⚡ **4-28x faster** on built-in types
- 🧠 **~30% less memory** per copy
- ✨ requires **zero code changes**
- 🧪 passes Python's [test_copy.py](https://github.com/python/cpython/blob/41b9ad5b38e913194a5cc88f0e7cfc096787b664/Lib/test/test_copy.py)
- 📦 pre-built wheels for Python 3.10–3.14
     on Linux/macOS/Windows (x64/ARM64)
- 🔓 passes all tests on **free-threaded** Python builds

## Installation

```bash
pip install 'copium[autopatch]'
```

This will effortlessly make `copy.deepcopy()` fast in current environment.

> [!WARNING]
> `copium` hasn't seen wide production use yet. Expect bugs.

### For manual usage
```bash
pip install copium
```

## Manual usage

> [!TIP]
> You can skip this section if you depend on `copium[autopatch]`.

```py
import copium

assert copium.deepcopy(x := []) is not x
```

The `copium` module includes all public declarations of stdlib `copy` module, so it's generally safe
to:

```diff
- from copy import copy, deepcopy, Error
+ from copium import copy, deepcopy, Error
```

---

> [!TIP]
> Next sections will likely make more sense if you read CPython docs on
> deepcopy: https://docs.python.org/3/library/copy.html

## How is it so fast?

- #### Zero interpreter overhead for built-in containers and atomic types
  ##### If your data consist only of the types below, `deepcopy` operation won't touch the interpreter:
    - natively supported containers: `tuple`, `dict`, `list`, `set`, `frozenset`, `bytearray` and
      `types.MethodType`
    - natively supported atomics: `type(None)`, `int`, `str`, `bytes`, `float`, `bool`, `complex`,
      `types.EllipsisType`, `types.NotImplementedType`, `range`, `property`, `weakref.ref`,
      `re.Pattern`, `decimal.Decimal`, `fractions.Fraction`, `types.CodeType`, `types.FunctionType`,
      `types.BuiltinFunctionType`, `types.ModuleType`
- #### Native memo
    - no time spent on creating extra `int` object for `id(x)`
    - hash is computed once for lookup and reused to store the copy
    - keepalive is a lightweight vector of pointers instead of a `list`
    - memo object is not tracked in GC, unless stolen in custom `__deepcopy__`
- #### Native __reduce__ handling
  When type's `__reduce__` strictly follows the protocol, `copium` handles returned values natively,
  without interpreter overhead, the same way CPython pickle implementation does.
  
  [What if there's type mismatch?](#pickle-protocol)
- #### Cached memo
  Rather than creating a new memo object for each `deepcopy` and discarding it after, copium stores
  one per thread and reuses it. Referenced objects are cleared, but some amount of memory stays
  reserved, avoiding malloc/free overhead for typical workloads. 
- #### Zero overhead patch on Python 3.12+
  `deepcopy` function object stays the same after patch, only its [
  `vectorcall`](https://peps.python.org/pep-0590/) is changed.


## Compatibility notes

`copium.deepcopy()` designed to be drop-in replacement for `copy.deepcopy()`,
still there are minor deviations from stdlib you should be aware of.

### Pickle protocol

stdlib's `copy` tolerates some deviations from the pickle protocol that `pickle` itself reject 
(see https://github.com/python/cpython/issues/141757).

`copium` strictly follows stdlib semantics: if `__reduce__` 
returns a list instead of a tuple for args, or a mapping instead of a dict for kwargs, 
`copium` will coerce them the same way stdlib would 
(via `*args` unpacking, `**kwargs` merging, `.items()` iteration, etc.). 
Errors from malformed `__reduce__` results match what `copy.deepcopy` produces.

### Memo handling

With native memo, custom `__deepcopy__` receives a `copium.memo`,
which is fully compatible with how `copy.deepcopy()` uses it internally.

Per [Python docs](https://docs.python.org/3/library/copy.html#object.__deepcopy__), custom `__deepcopy__` methods should treat memo as an opaque object and just pass
it through in any subsequent `deepcopy` calls. 

However, some native extensions that implement `__deepcopy__` on their objects 
may require exact `dict` object to be passed as `memo` argument. 
Typically, in this case, they raise `TypeError` or `AssertionError`. 

copium will attempt to recover by calling `__deepcopy__` again with `dict` memo. If that second call
succeeds, a warning with clear suggestions will be emitted, otherwise the error will be raised as
is.

[Tracking issue](https://github.com/Bobronium/copium/issues/31)

<details>
<summary>Example</summary>

```python-repl
>>> import copium
>>> class CustomType:
...     def __deepcopy__(self, memo):
...         if not isinstance(memo, dict):
...             raise TypeError("I'm enforcing memo to be a dict")
...         return self
... 
>>> print("Copied successfully: ", copium.deepcopy(CustomType()))
<python-input-2>:1: UserWarning: 

Seems like 'copium.memo' was rejected inside '__main__.CustomType.__deepcopy__':

Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    
  File "<python-input-1>", line 4, in __deepcopy__
    raise TypeError("I'm enforcing memo to be a dict")
TypeError: I'm enforcing memo to be a dict

copium was able to recover from this error, but this is slow and unreliable.

Fix:

  Per Python docs, '__main__.CustomType.__deepcopy__' should treat memo as an opaque object.
  See: https://docs.python.org/3/library/copy.html#object.__deepcopy__

Workarounds:

    local  change deepcopy(CustomType()) to deepcopy(CustomType(), {})
           -> copium uses dict memo in this call (recommended)

   global  export COPIUM_USE_DICT_MEMO=1
           -> copium uses dict memo everywhere (~1.3-2x slowdown, still faster than stdlib)

   silent  export COPIUM_NO_MEMO_FALLBACK_WARNING='TypeError: I'm enforcing memo to be a dict'
           -> 'deepcopy(CustomType())' stays slow to deepcopy

explosive  export COPIUM_NO_MEMO_FALLBACK=1
           -> 'deepcopy(CustomType())' raises the error above

Copied successfully:  <__main__.CustomType object at 0x104d1cad0>
```

</details>

## Credits
 
- [@sobolevn](https://github.com/sobolevn) for constructive feedback on C code / tests quality
- [@eendebakpt](https://github.com/eendebakpt) for C implementation of parts of `copy.deepcopy` in https://github.com/python/cpython/pull/91610 — used as early reference
- [@orsinium](https://github.com/orsinium) for [svg.py](https://github.com/orsinium-labs/svg.py) — used to generate main chart
- [@provencher](https://github.com/provencher) for repoprompt.com — used it to build context for LLMs/editing
- Anthropic/OpenAI/xAI for translating my ideas to compilable C code and educating me on the subject
- One special lizard 🦎