Python:子类整个层次结构
Posted
技术标签:
【中文标题】Python:子类整个层次结构【英文标题】:Python: Subclass whole hierarchy 【发布时间】:2019-02-28 23:56:35 【问题描述】:我的解决方案在问题的底部,基于 MisterMiyagi 的示例
我不确定如何最好地表达标题。我的想法如下。我有一个带有一些实现的抽象基类。其中一些实现将相互引用作为其逻辑的一部分,简化如下:
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
现在我想在某个地方使用这些类,比如说在另一个模块中。但是我想为层次结构中的所有类添加一些额外的功能(例如函数g
)。我可以通过将其添加到基类X
来做到这一点,但我想保留单独定义的功能。例如,我可能想像这样定义新功能:
class Y(X):
def g(self):
return self.f() + 'Y1'
这将创建另一个具有新功能的基类,但当然不会将其添加到现有实现 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
的一个实例。为了使我的想法起作用,这必须成为Y2.__init__
中的Y1
。但当然不是这样的:
print(y.x1.g()) # AttributeError: 'X1' object has no attribute 'g'
我认为我可能正在寻找一种将X
转换为抽象元类的方法,这样它的实现需要一个“基础”参数才能成为类,然后可以对其进行实例化。然后在类中使用此参数来实例化具有正确基础的其他实现。
在基类中创建具有新功能的实例将如下所示:
class Y:
def g(self):
return self.f() + 'Y1'
X2(Y)()
这将产生一个相当于以下类实例的对象:
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'
但是我不知道如何创建一个可以做到这一点的元类。我想知道元类是否是正确的想法,如果是,如何去做。
解决方案
使用 MisterMiyagi 的例子,我能够得到一些我认为可行的东西。这种行为与我的元类想法很接近。
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'>
【问题讨论】:
难道不能让X2
继承自X1
而不是实例化它(使用super().f() + 'X2'
)吗?这样你就可以简单地增加X1
就可以了。
不同Xi
实现之间的行为在现实中是完全不同的,所以我认为让它们从彼此继承而不是从基类继承是没有意义的。另外,正如我所说,我不想将此新功能添加到X
系列。这是我想在其之上添加的新功能,作为Y
系列,同时保留原始X
系列。我想这样做是为了分离功能,并有可能拥有一个Z
系列,它也建立在X
之上,但与Y
无关。
那么一般来说如何使用元类,您可以将族作为参数传递,例如class Y(X, family='Y')
。然后你可以在元类__new__
中相应地调整变量。
我认为这可能接近我的想法,但我不知道如何实现它。例如我不知道family
参数去哪里或者为什么'Y'
是一个字符串?
按照您的描述,不同的类层次结构之间存在一些重耦合。您确定要寻找专业,即class Y2(Y, X2[Y1])
,而不是装饰,即class Y2(Y(X2(Y1)))
?
【参考方案1】:
由于您正在寻找一种自定义类的方法,因此最简单的方法就是这样做:
class X2(X):
component_type = X1
def __init__(self):
self.x1 = self.component_type()
class Y2(X2, Y):
component_type = Y1
由于component_type
是一个类属性,它允许专门化同一类的不同变体(阅读:子类)。
请注意,您当然可以使用其他代码来构建此类。类方法可用于创建新的派生类。
假设你的班级能够选择正确的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]
类属性通常服务于元类字段的功能,因为它们准确地表达了这一点。理论上,设置一个类属性相当于添加一个元类字段,然后将其设置为每个类的属性。这里利用的是 Python 允许实例具有属性而无需定义其字段的类。可以将类属性视为鸭式元类。
【讨论】:
谢谢!虽然这样做的问题是我实际上有类X1...Xn
,它们都需要自己的Y1...Yn
,而且它们都需要了解彼此的所有信息,所以每个都需要attributescomponent_type_1...component_type_n
。我希望有一个不那么手动和冗长的解决方案。
@Marein 这是一个非常松散的描述。 X1...Xn
来自哪里 - 它们是手动定义的还是生成的? n
总是一样吗? Xn
需要全部 Xn-1, Xn-2, ...
还是只需要 Xn-1
? Xn, Xn-1, ...
关系是类所固有的,还是更适合由外部工厂处理?
抱歉含糊不清。我的意思是,在我的示例中,我只有X1
和X2
,但在我的实际用例中,我有更多X
的实现。它们是手动定义的,目前为n=4
。我的观点是 (n^2
) 定义所有组件类型变得非常冗长。
我知道您有更多课程,但完全不清楚它们之间的关系。例如,如果每个Xn
需要所有其他X1...Xn
,它可以从它的基类中获取这些。相反,如果每个Xn
都需要一个特定的Xn-1
,它必须知道它。
我想我能够在你的例子的帮助下做到这一点!我已经编辑了问题以显示结果。【参考方案2】:
你的问题是一个经常出现的问题,即你想改变现有类的行为。您可以通过继承它并添加新行为来实现这一方面。然后,您为此子类创建的所有实例都具有新行为。
但您也希望 其他人(在本例中为 X2
)创建该类的进一步实例,而不是创建您自己的子类的实例而是添加了行为。
这可以被认为是干涉别人的事务。我的意思是,如果X2
类想要创建X1
的实例,你是谁(X2
的用户!)告诉它应该创建其他东西?也许它不适用于非 X1
类型的东西!
但是——当然。到过那里。做到了。我知道有时会出现这种需求。
实现这一点的直接方法是让X2
类合作。这意味着,与其创建 X1
类的实例,不如创建作为参数传递的类的实例:
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
可以使用单元测试模块中已知的模拟来获得类似的解决方案。但所有这些都有一个共同点,即它们适用于所用类的当前实现的私密细节。一旦这些变化,代码就会中断。
因此,如果可以的话,最好为您的项目准备基类 (X2
) 并使其更灵活地用于您的用例。
【讨论】:
"这也算是插手了别人的事情。"这就是为什么我认为带有“base”参数的元类可能是一个好主意,因为X
本身就暴露了对外部类进行子类化的可能性。使用参数传递/方法覆盖解决方案,我仍然需要手动定义所有Yi
实现的所有Yi
变体。我需要将它们全部作为参数传递。我希望有一个不那么手动/冗长的解决方案。以上是关于Python:子类整个层次结构的主要内容,如果未能解决你的问题,请参考以下文章
子类化 UIButton 并将 UILabel 添加到视图层次结构会导致 currentImage 压缩