Декорирование всех унаследованных методов - PullRequest
0 голосов
/ 10 октября 2018

Context

Я хотел бы создать подкласс класса из библиотеки (xarray), у которой есть огромный API, и я не могу изменить класс вверх по течению.Этот суперкласс (xarray.Dataset) является общим классом хранения данных, который я хочу, чтобы подкласс сделал его более специфичным для моего варианта использования, добавив новые атрибуты и методы, сохранив при этом большую часть API.Я также хочу иметь возможность подклассифицировать мой новый класс в случае, если другому пользователю понадобится еще более конкретная функциональность.

Я пробовал различные подходы (которые я рад описать подробно), но я неуверен, что моя самая новая идея (декорирование всех унаследованных методов) возможна / ужасна.

Проблема

Причина, по которой я не могу просто создать подкласс, подобный этому:

class MyDataset(xarray.Dataset):
    def __init__(data, new_input)
        super.__init__(self, data)
        self.new_attribute = new_input

    def new_method(self)
        return self.new_attribute

Это потому, что многие унаследованные методы xarray.Dataset возвращают новые экземпляры объектов xarray.Dataset, что означает, что я потеряю свои новые атрибуты при выполнении общих операций над структурой данных с использованием этих методов.т.е.

ds = MyDataset(data, new_input)

# take the mean of my data over time, a common operation which uses an inherited method 
result_ds = ds.mean(dim=time)

# Now I will have lost my extra data
print(result_ds.new_attribute)  # will return either AttributeError or None depending on the implementation of the method

Мое предлагаемое решение

Я знаю, что я хочу, чтобы все методы, которые обычно возвращали экземпляр xarray.Dataset, вместо этого возвращали экземпляр MyDataset, и этополучить MyDataset от xarray.Dataset Мне просто нужно добавить данные new_attribute.(который хранится в частном порядке в экземпляре MyDataset, метод которого был вызван.)

Могу ли я поэтому написать что-то в __init__ из MyDataset, который украшает все методы, которые были унаследованы от super(),используя декоратор, который проверяет, является ли возвращаемое значение метода экземпляром xarray.Dataset, и если да, преобразует его в экземпляр MyDataset, используя мои дополнительные данные?Таким образом я мог бы сделать:

ds = MyDataset(data, new_input)

# use an inherited method
result_ds = ds.mean(dim=time)

# Extra data will still be there because the decorator added it on before returning it
print(result_ds.new_attribute)  # prints value of new_attribute

Я думаю, код должен выглядеть примерно так:

class MyDataset(xarray.Dataset):
    def __init__(data, new_input):
       super().__init__(self, data)
       self.new_attribute = new_input

       # Apply decorator to all inherited methods
       for callable in super().__dict__:
           return_val_decorator(callable, self.new_attribute)

    def new_method(self)
       return self.new_attribute

def return_val_decorator(func, extra_data, *args, **kwargs):
    def wrapper(extra_data, *args, **kwargs):
        result = func(*args, **kwargs)

        # If return value is an xarray dataset then reattach data
        if isinstance(result, xarray.Dataset):
            return _attach_extra_data(result, extra_data)
        else:
            return result
    return wrapper

Вопрос

Возможно ли это?Что произойдет, если я попытаюсь подкласс MyDataset?Могу ли я дать всем подклассам такое поведение с помощью метакласса?Это просто ужасная идея, которая приведет к непонятному коду или ошибочному поведению?

1 Ответ

0 голосов
/ 10 октября 2018

Вы можете обернуть функцию набора данных в вашем классе.Я не уверен, что это будет работать для всех, хотя.То, как вы справляетесь с коллизиями пространства имен, может быть проблемой, поскольку у вас нет фактического наследования, чтобы помочь вам.

class MyDataset:
    def __init__(self, *args, **kwargs):
        self.dataset = xarray.Dataset(*args, **kwargs)
    def new_method(self):
        pass # Do stuff here
    def __getattr__(self, func):
        refer = getattr(self.dataset, func)
        if callable(refer):
            return self._subclass_wrapper(refer)
        else:
            return refer
    def _subclass_wrapper(self, func):
        def _wrap_func(*args, **kwargs):
            data = func(*args, **kwargs)
            if isinstance(data, xarray.Dataset):
                my_new = self.copy()
                my_new.dataset = data
                return my_new
            else:
                return data
        return _wrap_func

Теоретически, это должно действовать как xarray.Dataset, за исключением любых функций xarray.Datasetкоторые возвращают объект xarray.Dataset, должны вместо этого возвращать копию объекта MyDataset, с заменой MyDataset.dataset на новый xarray.Dataset.

На практике я думаю, что я, вероятно, обработаю любые метаданныеЯ хотел остаться с xarray.Dataset другим способом;это не будет моим первым выбором.

...