装饰所有继承的方法

时间:2018-10-10 15:51:38

标签: python python-3.x oop decorator python-xarray

上下文

我想从具有庞大API的库(xarray)中继承一个类,并且无法在上游修改该类。这个超类(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__的{​​{1}}中写一些东西来装饰从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 的子类,将会发生什么?我可以通过某种方式使用元类来给所有子类这种行为吗?这只是一个可怕的想法,会导致难以理解的代码或错误的行为吗?

1 个答案:

答案 0 :(得分:1)

您可以在类中包装数据集函数。我不确定这是否适用于所有情况。命名空间冲突的处理方式可能是个问题,因为您没有实际的继承来帮助您。

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一起保存的任何元数据;这不是我的首选。