scverse / anndata

Annotated data.
http://anndata.readthedocs.io
BSD 3-Clause "New" or "Revised" License
577 stars 152 forks source link

Deepcopy of a copy after view raises an error #656

Closed michalk8 closed 2 years ago

michalk8 commented 2 years ago
import scanpy as sc
from copy import deepcopy, copy

adata = sc.datasets.paul15()
sc.pp.pca(adata)
sc.pp.neighbors(adata)

deepcopy(adata[:, 0].copy())  
# copy(adata[:, 0].copy())  this is fine

# also fine if I remove all stuff from .obsp:
# del adata.obsp['distances']
# del adata.obsp['neighbors']

Traceback

```pytb --------------------------------------------------------------------------- TypeError Traceback (most recent call last) /tmp/ipykernel_467014/1736390965.py in 6 sc.pp.neighbors(adata) 7 ----> 8 deepcopy(adata[:, 0].copy()) ~/.miniconda3/envs/cellrank/lib/python3.8/copy.py in deepcopy(x, memo, _nil) 170 y = x 171 else: --> 172 y = _reconstruct(x, memo, *rv) 173 174 # If is its own copy, don't memoize. ~/.miniconda3/envs/cellrank/lib/python3.8/copy.py in _reconstruct(x, memo, func, args, state, listiter, dictiter, deepcopy) 268 if state is not None: 269 if deep: --> 270 state = deepcopy(state, memo) 271 if hasattr(y, '__setstate__'): 272 y.__setstate__(state) ~/.miniconda3/envs/cellrank/lib/python3.8/copy.py in deepcopy(x, memo, _nil) 144 copier = _deepcopy_dispatch.get(cls) 145 if copier is not None: --> 146 y = copier(x, memo) 147 else: 148 if issubclass(cls, type): ~/.miniconda3/envs/cellrank/lib/python3.8/copy.py in _deepcopy_dict(x, memo, deepcopy) 228 memo[id(x)] = y 229 for key, value in x.items(): --> 230 y[deepcopy(key, memo)] = deepcopy(value, memo) 231 return y 232 d[dict] = _deepcopy_dict ~/.miniconda3/envs/cellrank/lib/python3.8/copy.py in deepcopy(x, memo, _nil) 170 y = x 171 else: --> 172 y = _reconstruct(x, memo, *rv) 173 174 # If is its own copy, don't memoize. ~/.miniconda3/envs/cellrank/lib/python3.8/copy.py in _reconstruct(x, memo, func, args, state, listiter, dictiter, deepcopy) 268 if state is not None: 269 if deep: --> 270 state = deepcopy(state, memo) 271 if hasattr(y, '__setstate__'): 272 y.__setstate__(state) ~/.miniconda3/envs/cellrank/lib/python3.8/copy.py in deepcopy(x, memo, _nil) 144 copier = _deepcopy_dispatch.get(cls) 145 if copier is not None: --> 146 y = copier(x, memo) 147 else: 148 if issubclass(cls, type): ~/.miniconda3/envs/cellrank/lib/python3.8/copy.py in _deepcopy_dict(x, memo, deepcopy) 228 memo[id(x)] = y 229 for key, value in x.items(): --> 230 y[deepcopy(key, memo)] = deepcopy(value, memo) 231 return y 232 d[dict] = _deepcopy_dict ~/.miniconda3/envs/cellrank/lib/python3.8/copy.py in deepcopy(x, memo, _nil) 144 copier = _deepcopy_dispatch.get(cls) 145 if copier is not None: --> 146 y = copier(x, memo) 147 else: 148 if issubclass(cls, type): ~/.miniconda3/envs/cellrank/lib/python3.8/copy.py in _deepcopy_dict(x, memo, deepcopy) 228 memo[id(x)] = y 229 for key, value in x.items(): --> 230 y[deepcopy(key, memo)] = deepcopy(value, memo) 231 return y 232 d[dict] = _deepcopy_dict ~/.miniconda3/envs/cellrank/lib/python3.8/copy.py in deepcopy(x, memo, _nil) 151 copier = getattr(x, "__deepcopy__", None) 152 if copier is not None: --> 153 y = copier(memo) 154 else: 155 reductor = dispatch_table.get(cls) /opt/projects/helmholtz/anndata/anndata/_core/views.py in __deepcopy__(self, memo) 55 # TODO: This makes `deepcopy(obj)` return `obj._view_args.parent._adata_ref`, fix it 56 def __deepcopy__(self, memo): ---> 57 parent, attrname, keys = self._view_args 58 return deepcopy(getattr(parent._adata_ref, attrname)) 59 TypeError: cannot unpack non-iterable NoneType object ```

Version: 0.7.7.dev17+g4564d1b

michalk8 commented 2 years ago

Update: happens when the connectivities are a subtype of anndata._core.views.SparseCSRView. Am also wondering, why it creates the view, since we're subsetting along var-axis, not obs-axis.

flying-sheep commented 2 years ago

Looks like a wrong assumption, as _view_args can be `None. The stack is (deepest first):

The purpose of _with_data is to create a new object instead of a view, so maybe we need to override it in SparseCSRView and SparseCSCView to use the underlying cs*_matrix class instead of self.__class__.

ivirshup commented 2 years ago

I recall this being a problem in the past too, though don't remember the specifics.

Fixes would be welcome.