上下文
我想从具有大量 API 的库 (xarray) 中继承一个类,但我无法在上游修改该类。这个父类(super class) (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
实例,然后获取 来自
我只需要处理 xarray.Dataset
的 MyDatasetnew_attribute
数据。 (它私下存储在方法被调用的 MyDataset
的实例中。)
因此我可以在 MyDataset
的 __init__
中写一些东西来装饰从 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
会发生什么?我可以通过某种方式使用元类来为所有子类提供这种行为吗?这只是一个糟糕的想法,会导致难以理解的代码或错误行为吗?
最佳答案
您可以在您的类中包装数据集函数。我不确定这是否适用于所有情况。你如何处理 namespace 冲突可能是个问题,因为你没有实际的继承来帮助你。
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
一起保留的任何元数据;这不是我的第一选择。
关于python - 装饰所有继承的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52744220/