根据宫城先生的例子,我的解决方案位于问题的底部
我不确定如何最好地表达标题。我的想法如下。我有一些实现的抽象基类。其中一些实现将彼此称为逻辑的一部分,简化如下:
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
的实例。为了使我的想法可行,必须在Y1
中将其变为Y2.__init__
。但是,事实并非如此:
print(y.x1.g()) # AttributeError: 'X1' object has no attribute 'g'
我认为我可能正在寻找一种将X
转换为抽象元类的方法,以便其实现需要一个'base'参数成为类,然后可以将其实例化。然后,在类中使用此参数以实例化具有正确基数的其他实现。
在基类中创建具有新功能的实例将如下所示:
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'
但是我不知道如何创建一个可以做到这一点的元类。我想听听一个元类是否是正确的主意,如果是的话,该怎么做。
解决方案
使用宫城先生的例子,我可以得到一些我认为可行的东西。这种行为接近我的元类想法。
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'>
答案 0 :(得分:3)
由于您正在寻找一种自定义类的方法,所以最简单的方法就是这样做:
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允许实例具有属性,而无需类来定义其字段。可以将类属性视为鸭子式元类。
答案 1 :(得分:1)
您的问题是一个经常出现的问题,即您想更改现有类的行为。您可以通过继承它并添加新行为来实现这一方面。然后,您为此子类创建的所有实例都具有新行为。
但是您还希望其他人(在这种情况下为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
),并使其更灵活地适合您的用例。