Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RecursionError when defining __slots__ on subclass using Containment #4660

Closed
BorjaEst opened this issue Dec 8, 2020 · 4 comments
Closed

Comments

@BorjaEst
Copy link

BorjaEst commented Dec 8, 2020

What happened:
Inheritance of xarray.Dataset request the addition of __slots__:

/home/borja/miniconda3/envs/o3as/lib/python3.6/abc.py:133: FutureWarning: xarray subclass Model should explicitly define __slots__
  cls = super().__new__(mcls, name, bases, namespace, **kwargs)
/home/borja/Projects/o3skim/o3skim/sources.py:77: FutureWarning: Setting attribute 'dataset' on a 'Model' object. Explicitly define __slots__ to suppress this warning for legitimate custom attributes and raise an error when attempting variables assignments.
  self.dataset = ds

however when added, produces RecursionError.

What you expected to happen:
It should be possible to use Containment & Delegation on xarray classes.

Minimal Complete Verifiable Example:

import xarray

class MyDSet(xarray.Dataset):
    __slots__ = ['dataset',]
    def __init__(self, ds):
        self.dataset = ds

    def __getattr__(self, k):
        return getattr(self.dataset, k)

    def __setattr__(self, k, v):
        setattr(self.dataset, k, v)

Returns:

>>> reload(mytest)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/borja/miniconda3/envs/o3as/lib/python3.6/importlib/__init__.py", line 166, in reload
    _bootstrap._exec(spec, module)
  File "<frozen importlib._bootstrap>", line 618, in _exec
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/home/borja/Projects/o3skim/o3skim/mytest.py", line 3, in <module>
    class BCorrect(xarray.Dataset):
  File "/home/borja/miniconda3/envs/o3as/lib/python3.6/typing.py", line 978, in __new__
    self = super().__new__(cls, name, bases, namespace, _root=True)
  File "/home/borja/miniconda3/envs/o3as/lib/python3.6/typing.py", line 137, in __new__
    return super().__new__(cls, name, bases, namespace)
  File "/home/borja/miniconda3/envs/o3as/lib/python3.6/abc.py", line 133, in __new__
    cls = super().__new__(mcls, name, bases, namespace, **kwargs)
  File "/home/borja/miniconda3/envs/o3as/lib/python3.6/site-packages/xarray/core/common.py", line 199, in __init_subclass__
    if not hasattr(object.__new__(cls), "__dict__"):
  File "/home/borja/Projects/o3skim/o3skim/mytest.py", line 9, in __getattr__
    return getattr(self.dataset, k)
  File "/home/borja/Projects/o3skim/o3skim/mytest.py", line 9, in __getattr__
    return getattr(self.dataset, k)
  File "/home/borja/Projects/o3skim/o3skim/mytest.py", line 9, in __getattr__
    return getattr(self.dataset, k)
  [Previous line repeated 325 more times]
RecursionError: maximum recursion depth exceeded

Anything else we need to know?:
I would recommend to add the following test or similar to ensure it is working:

class X(xarray.Dataset):
    __slots__ = ['dataset']
    def __init__(self, ds):
        self.dataset = ds

ds = xarray.Dataset()
x = X(ds)

Environment:

Output of xr.show_versions()

INSTALLED VERSIONS

commit: None
python: 3.6.12 |Anaconda, Inc.| (default, Sep 8 2020, 23:10:56)
[GCC 7.3.0]
python-bits: 64
OS: Linux
OS-release: 5.4.0-56-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8
libhdf5: 1.10.5
libnetcdf: 4.6.3

xarray: 0.16.2
pandas: 1.1.4
numpy: 1.19.4
scipy: 1.5.4
netCDF4: 1.5.4
pydap: None
h5netcdf: None
h5py: None
Nio: None
zarr: None
cftime: 1.3.0
nc_time_axis: None
PseudoNetCDF: None
rasterio: None
cfgrib: None
iris: None
bottleneck: None
dask: 2.30.0
distributed: None
matplotlib: None
cartopy: None
seaborn: None
numbagg: None
pint: None
setuptools: 51.0.0.post20201207
pip: 20.3.1
conda: None
pytest: 6.1.2
IPython: None
sphinx: 3.2.1

@keewis
Copy link
Collaborator

keewis commented Dec 11, 2020

inheriting from a Dataset or DataArray is not something we support right now, although that might change in the future.

The reason your examples fail with a RecursionError is a different one, though, you also get the same error if you remove all references to xarray and pass a string to your class. As pointed out in the SO question you referenced, you need to change your __init__:

class MyClass:
    __slots__ = ("dataset",)

    def __init__(self, ds):
        self.__class__.dataset.__set__(self, ds)

    def __getattr__(self, k):
        return getattr(self.dataset, k)

    def __setattr__(self, k, v):
        setattr(self.dataset, k, v)

ds = xr.Dataset()
x = MyClass(ds)

but I've also seen the use of __new__ instead of __init__ to avoid this somewhat ugly trick.

Be aware that the result of __getattr__ as it is right now will remove the custom class, so if you would like to avoid that you will have to add some sort of check:

    def __getattr__(self, k):
        result = getattr(self.dataset, k)
        if isinstance(result, (xr.Dataset, xr.DataArray)):
            return type(self)(result)
        else:
            return result

@keewis
Copy link
Collaborator

keewis commented Jan 12, 2021

I assume this answered your question. If you disagree, feel free to reopen.

@keewis keewis closed this as completed Jan 12, 2021
@max-sixty
Copy link
Collaborator

Though we don't support it officially, I've found subclassing a Dataset can be useful when adding methods. Most operations on the object will not persist the subclass, though.

@BorjaEst
Copy link
Author

Sorry for my long time to reply, christmas...
@keewis thank you a lot for your anwer, I am glad to hear subclass might be supported one day. At the end, those are the python tools the programmers we work with.

For the moment, I will try to implement Extending xarray as you pointed using "xr.register_dataset_accessor". Create a new class that cannot be identified as 'isinstance(object, xarray.Dataset)' might be an issue in my case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants