Python: 整个层次结构的子类
Python: Subclass whole hierarchy
我的解决方案在问题的底部,基于 MisterMiyagi 的示例
我不确定如何最好地表达标题。我的想法如下。我有一个带有一些实现的抽象基础 class。其中一些实现将彼此引用作为其逻辑的一部分,简化如下:
import abc
# the abstract class
class X(abc.ABC):
@abc.abstractmethod
def f(self):
raise NotImplementedError()
# first implementation
class X1(X):
def f(self):
return 'X1'
# second implementation, using instance of first implementation
class X2(X):
def __init__(self):
self.x1 = X1()
def f(self):
return self.x1.f() + 'X2'
# demonstration
x = X2()
print(x.f()) # X1X2
print(x.x1.f()) # X1
现在我想在某个地方使用这些 classes,比方说在另一个模块中。但是,我想向层次结构中的所有 classes 添加一些额外的功能(例如函数 g
)。我可以通过将它添加到基础 class X
来做到这一点,但我想保留单独定义的功能。例如,我可能想像这样定义新功能:
class Y(X):
def g(self):
return self.f() + 'Y1'
这将创建另一个具有新功能的基础 class,但当然不会将其添加到现有实现 X1
和 X2
中。我必须使用钻石继承来获得它:
class Y1(X1, Y):
pass
class Y2(X2, Y):
pass
# now I can do this:
y = Y2()
print(y.g()) # X1X2Y1
以上操作正常,但仍有问题。在 X2.__init__
中,创建了 X1
的一个实例。为了让我的想法奏效,这必须变成 Y1
in Y2.__init__
。但是当然不是这样的:
print(y.x1.g()) # AttributeError: 'X1' object has no attribute 'g'
我想我可能正在寻找一种将 X
变成抽象元 class 的方法,这样它的实现需要一个 'base' 参数才能变成 classes,然后可以实例化。然后在 class 中使用此参数来实例化具有正确基础的其他实现。
在基础 class 中创建具有新功能的实例将如下所示:
class Y:
def g(self):
return self.f() + 'Y1'
X2(Y)()
这将导致 object 等同于以下 class 的实例:
class X2_with_Y:
def __init__(self):
self.x1 = X1(Y)()
def f(self):
return self.x1.f() + 'X2'
def g(self):
return self.f() + 'Y1'
但是我不知道如何创建可以执行此操作的元class。我想听听 metaclass 是否是正确的想法,如果是的话,如何去做。
解决方案
使用 MisterMiyagi 的示例,我得到了一些我认为可行的东西。该行为接近于我的 metaclass 想法。
import abc
class X(abc.ABC):
base = object # default base class
@classmethod
def __class_getitem__(cls, base):
if cls.base == base:
# makes normal use of class possible
return cls
else:
# construct a new type using the given base class and also remember the attribute for future instantiations
name = f'{cls.__name__}[{base.__name__}]'
return type(name, (base, cls), {'base': base})
@abc.abstractmethod
def f(self):
raise NotImplementedError()
class X1(X):
def f(self):
return 'X1'
class X2(X):
def __init__(self):
# use the attribute here to get the right base for the other subclass
self.x1 = X1[self.base]()
def f(self):
return self.x1.f() + 'X2'
# just the wanted new functionality
class Y(X):
def g(self):
return self.f() + 'Y1'
用法是这样的:
# demonstration
y = X2[Y]()
print(y.g()) # X1X2Y1
print(y.x1.g()) # X1Y1
print(type(y)) # <class 'abc.X2[Y]'>
# little peeve here: why is it not '__main__.X2[Y]'?
# the existing functionality also still works
x = X2()
print(x.f()) # X1X2
print(x.x1.f()) # X1
print(type(x)) # <class '__main__.X2'>
由于您正在寻找自定义 classes 的方法,最简单的方法就是这样做:
class X2(X):
component_type = X1
def __init__(self):
self.x1 = self.component_type()
class Y2(X2, Y):
component_type = Y1
由于 component_type
是一个 class 属性,它允许特化相同 class.[=20= 的不同变体(阅读:subclasses) ]
请注意,您当然可以使用其他代码来构造此类 classes。 Class方法可用于创建新的派生 classes。
假设你的 classes 能够选择正确的 subclasses from their hierarchy。
class X2(X):
hierarchy = X
@classmethod
def specialise(cls, hierarchy_base):
class Foo(cls, hierarchy_base):
hierarchy = hierarchy_base
return Foo
Y2 = X2.specialise(Y)
因为Python3.7,可以用__class_getitem__
把上面写成Y2 = X2[Y]
,类似于Tuple
如何特化到Tuple[int]
.
class X2(X):
hierarchy = X
def __class_getitem__(cls, hierarchy_base):
class Foo(cls, hierarchy_base):
hierarchy = hierarchy_base
return Foo
Y2 = X2[Y]
Class 属性通常服务于 metaclass 字段的功能,因为它们准确地表达了这一点。理论上,设置一个class属性相当于添加一个metaclass字段,然后将其设置为每个class的属性。这里利用的是 Python 允许实例具有属性而无需 classes 定义它们的字段。可以将 class 属性视为鸭子类型的元 classes.
您的问题经常出现,即您想更改现有 class 的行为。您可以通过继承它并添加新行为来实现这一方面。您随后创建的此子class 的所有实例都具有新行为。
但您还希望 其他人(在本例中是 X2
)创建此 进一步 实例class 现在改为使用添加的行为创建您自己的 subclass 的实例。
这也算是多管闲事了。我的意思是,如果 class X2
想要创建 X1
的实例,你是谁(只是 X2
的用户!)告诉它它应该创建别的东西??也许它不能正常使用非 X1
!
类型的东西
但是——当然。到过那里。做到了。我知道有时会出现这种需求。
实现这一目标的直接方法是让 class X2
合作。这意味着,与其创建 class X1
的实例,不如创建作为参数传递的 class 的实例:
class X2(X):
def __init__(self, x1_class=X1):
self.x1 = x1_class()
这也可以使用方法覆盖而不是参数传递很好地嵌入:
class X2(X):
@classmethod
def my_x1(cls):
return X1
def __init__(self):
self.x1 = self.my_x1()
然后在另一个模块中:
class Y2(X2, Y):
@classmethod
def my_x1(cls):
return Y1
但是如果你能改变X2
,所有这一切都有效,在某些情况下你不能这样做(因为X
的模块是第三方提供的,甚至是内置库, 所以实际上是只读的)。
在这些情况下,您可以考虑猴子修补:
def patched_init(self):
self.x1 = Y1()
X1.__init__ = patched_init
可以使用单元测试模块中已知的模拟来实现类似的解决方案。但所有这些都有一个共同点,即它们适用于所用 classes 当前实现的私密细节。一旦这些发生变化,代码就会中断。
因此,如果可以的话,最好为您的项目准备基础 classes (X2
) 并使其更灵活地适应您的用例。
我的解决方案在问题的底部,基于 MisterMiyagi 的示例
我不确定如何最好地表达标题。我的想法如下。我有一个带有一些实现的抽象基础 class。其中一些实现将彼此引用作为其逻辑的一部分,简化如下:
import abc
# the abstract class
class X(abc.ABC):
@abc.abstractmethod
def f(self):
raise NotImplementedError()
# first implementation
class X1(X):
def f(self):
return 'X1'
# second implementation, using instance of first implementation
class X2(X):
def __init__(self):
self.x1 = X1()
def f(self):
return self.x1.f() + 'X2'
# demonstration
x = X2()
print(x.f()) # X1X2
print(x.x1.f()) # X1
现在我想在某个地方使用这些 classes,比方说在另一个模块中。但是,我想向层次结构中的所有 classes 添加一些额外的功能(例如函数 g
)。我可以通过将它添加到基础 class X
来做到这一点,但我想保留单独定义的功能。例如,我可能想像这样定义新功能:
class Y(X):
def g(self):
return self.f() + 'Y1'
这将创建另一个具有新功能的基础 class,但当然不会将其添加到现有实现 X1
和 X2
中。我必须使用钻石继承来获得它:
class Y1(X1, Y):
pass
class Y2(X2, Y):
pass
# now I can do this:
y = Y2()
print(y.g()) # X1X2Y1
以上操作正常,但仍有问题。在 X2.__init__
中,创建了 X1
的一个实例。为了让我的想法奏效,这必须变成 Y1
in Y2.__init__
。但是当然不是这样的:
print(y.x1.g()) # AttributeError: 'X1' object has no attribute 'g'
我想我可能正在寻找一种将 X
变成抽象元 class 的方法,这样它的实现需要一个 'base' 参数才能变成 classes,然后可以实例化。然后在 class 中使用此参数来实例化具有正确基础的其他实现。
在基础 class 中创建具有新功能的实例将如下所示:
class Y:
def g(self):
return self.f() + 'Y1'
X2(Y)()
这将导致 object 等同于以下 class 的实例:
class X2_with_Y:
def __init__(self):
self.x1 = X1(Y)()
def f(self):
return self.x1.f() + 'X2'
def g(self):
return self.f() + 'Y1'
但是我不知道如何创建可以执行此操作的元class。我想听听 metaclass 是否是正确的想法,如果是的话,如何去做。
解决方案
使用 MisterMiyagi 的示例,我得到了一些我认为可行的东西。该行为接近于我的 metaclass 想法。
import abc
class X(abc.ABC):
base = object # default base class
@classmethod
def __class_getitem__(cls, base):
if cls.base == base:
# makes normal use of class possible
return cls
else:
# construct a new type using the given base class and also remember the attribute for future instantiations
name = f'{cls.__name__}[{base.__name__}]'
return type(name, (base, cls), {'base': base})
@abc.abstractmethod
def f(self):
raise NotImplementedError()
class X1(X):
def f(self):
return 'X1'
class X2(X):
def __init__(self):
# use the attribute here to get the right base for the other subclass
self.x1 = X1[self.base]()
def f(self):
return self.x1.f() + 'X2'
# just the wanted new functionality
class Y(X):
def g(self):
return self.f() + 'Y1'
用法是这样的:
# demonstration
y = X2[Y]()
print(y.g()) # X1X2Y1
print(y.x1.g()) # X1Y1
print(type(y)) # <class 'abc.X2[Y]'>
# little peeve here: why is it not '__main__.X2[Y]'?
# the existing functionality also still works
x = X2()
print(x.f()) # X1X2
print(x.x1.f()) # X1
print(type(x)) # <class '__main__.X2'>
由于您正在寻找自定义 classes 的方法,最简单的方法就是这样做:
class X2(X):
component_type = X1
def __init__(self):
self.x1 = self.component_type()
class Y2(X2, Y):
component_type = Y1
由于 component_type
是一个 class 属性,它允许特化相同 class.[=20= 的不同变体(阅读:subclasses) ]
请注意,您当然可以使用其他代码来构造此类 classes。 Class方法可用于创建新的派生 classes。
假设你的 classes 能够选择正确的 subclasses from their hierarchy。
class X2(X):
hierarchy = X
@classmethod
def specialise(cls, hierarchy_base):
class Foo(cls, hierarchy_base):
hierarchy = hierarchy_base
return Foo
Y2 = X2.specialise(Y)
因为Python3.7,可以用__class_getitem__
把上面写成Y2 = X2[Y]
,类似于Tuple
如何特化到Tuple[int]
.
class X2(X):
hierarchy = X
def __class_getitem__(cls, hierarchy_base):
class Foo(cls, hierarchy_base):
hierarchy = hierarchy_base
return Foo
Y2 = X2[Y]
Class 属性通常服务于 metaclass 字段的功能,因为它们准确地表达了这一点。理论上,设置一个class属性相当于添加一个metaclass字段,然后将其设置为每个class的属性。这里利用的是 Python 允许实例具有属性而无需 classes 定义它们的字段。可以将 class 属性视为鸭子类型的元 classes.
您的问题经常出现,即您想更改现有 class 的行为。您可以通过继承它并添加新行为来实现这一方面。您随后创建的此子class 的所有实例都具有新行为。
但您还希望 其他人(在本例中是 X2
)创建此 进一步 实例class 现在改为使用添加的行为创建您自己的 subclass 的实例。
这也算是多管闲事了。我的意思是,如果 class X2
想要创建 X1
的实例,你是谁(只是 X2
的用户!)告诉它它应该创建别的东西??也许它不能正常使用非 X1
!
但是——当然。到过那里。做到了。我知道有时会出现这种需求。
实现这一目标的直接方法是让 class X2
合作。这意味着,与其创建 class X1
的实例,不如创建作为参数传递的 class 的实例:
class X2(X):
def __init__(self, x1_class=X1):
self.x1 = x1_class()
这也可以使用方法覆盖而不是参数传递很好地嵌入:
class X2(X):
@classmethod
def my_x1(cls):
return X1
def __init__(self):
self.x1 = self.my_x1()
然后在另一个模块中:
class Y2(X2, Y):
@classmethod
def my_x1(cls):
return Y1
但是如果你能改变X2
,所有这一切都有效,在某些情况下你不能这样做(因为X
的模块是第三方提供的,甚至是内置库, 所以实际上是只读的)。
在这些情况下,您可以考虑猴子修补:
def patched_init(self):
self.x1 = Y1()
X1.__init__ = patched_init
可以使用单元测试模块中已知的模拟来实现类似的解决方案。但所有这些都有一个共同点,即它们适用于所用 classes 当前实现的私密细节。一旦这些发生变化,代码就会中断。
因此,如果可以的话,最好为您的项目准备基础 classes (X2
) 并使其更灵活地适应您的用例。