pandas-dev/pandas · error · TypeError
Expression objects are not copiable
Error message
Expression objects are not copiable
What it means
Raised by Expression.__copy__ (pandas/core/col.py:363). Expression objects capture a closure over a column reference (col_name) and a repr string; copying them would duplicate a deferred callable with no meaningful independent state, so copy.copy() is intentionally blocked to avoid silent aliasing bugs.
Source
Thrown at pandas/core/col.py:363
evaluated.append((condition, replacement))
return ser.case_when(evaluated)
# Keep repr compact; caselist may be large.
repr_str = f"{self!r}.case_when(...)"
return Expression(func, repr_str)
def __repr__(self) -> str:
return self._repr_str or "Expr(...)"
# Unsupported ops
def __bool__(self) -> NoReturn:
raise TypeError("boolean value of an expression is ambiguous")
def __iter__(self) -> NoReturn:
raise TypeError("Expression objects are not iterable")
def __copy__(self) -> NoReturn:
raise TypeError("Expression objects are not copiable")
def __deepcopy__(self, memo: dict[int, Any] | None) -> NoReturn:
raise TypeError("Expression objects are not copiable")
@set_module("pandas")
def col(col_name: Hashable) -> Expression:
"""
Generate deferred object representing a column of a DataFrame.
Any place which accepts ``lambda df: df[col_name]``, such as
:meth:`DataFrame.assign` or :meth:`DataFrame.loc`, can also accept
``pd.col(col_name)``.
.. versionadded:: 3.0.0
Parameters
----------View on GitHub (pinned to 71959b8cb9)
Solutions
- Do not copy the Expression; reconstruct it via `pd.col(col_name)` if you need a fresh reference.
- Remove the Expression from data structures that get copied, or replace it with the evaluated Series before copying.
- If a library force-copies, evaluate the expression against the DataFrame first so you copy a concrete Series instead.
Example fix
# before
import copy
expr = pd.col('x')
expr2 = copy.copy(expr)
# after
expr = pd.col('x')
expr2 = pd.col('x') # construct a fresh Expression Defensive patterns
Strategy: type-guard
Validate before calling
from pandas.core.col import Expression
def safe_copy(obj):
if isinstance(obj, Expression):
raise TypeError("Expression objects are not copiable; reconstruct via pd.col")
import copy
return copy.copy(obj) Type guard
from pandas.core.col import Expression
def is_copyable(obj) -> bool:
return not isinstance(obj, Expression) Try / catch
import copy
try:
obj2 = copy.copy(obj)
except TypeError as e:
if 'not copiable' in str(e):
obj2 = pd.col(obj._repr_col_name) # reconstruct from stored name
else:
raise Prevention
- Do not store Expressions in structures that get shallow-copied.
- Reconstruct Expressions via pd.col(name) instead of copying.
- Evaluate to a Series before passing to copy-heavy pipelines.
When it happens
Trigger: `copy.copy(pd.col('x'))`, or passing an Expression through a pipeline/library that defensively shallow-copies its inputs (e.g. some sklearn transformers, multiprocessing forks, deepcopy-heavy config loaders).
Common situations: Generic utility code that calls copy.copy on arbitrary objects. Deep-copy of a dict/list that happens to contain an Expression.
Related errors
- boolean value of an expression is ambiguous
- Expression objects are not iterable
- Expected Hashable, got: {type(col_name)}
- Column '{col_name}' not found in given DataFrame. Hint: did
- {left_base!r} is {right_base!r}
AI-assisted analysis of pandas-dev/pandas@71959b8cb9 (2026-08-07).
Data as JSON: /api/errors/67cb73c86e94a03d.
Report an issue: GitHub.