如果您将基础 class 的可选功能存储在辅助 class 中,辅助 class 是否应该继承基础 class?
If you store optional functionality of a base class in a secondary class, should the secondary class subclass the base class?
我知道标题可能有点混乱,所以让我举个例子。假设您有一个基数 class Base
,它打算被子class 编辑以创建更复杂的 objects。但是你也有可选的功能,你不需要每个 subclass,所以你把它放在辅助 class OptionalStuffA
中,它总是打算成为 subclass与基数 class 一起编辑。你是否也应该让次要 class 成为 Base
的子 class?
这当然只有当你有多个 OptionalStuff
class 并且你想以不同的方式组合它们时才有意义,因为否则你不需要 subclass Base
和 OptionalStuffA
(并且 OptionalStuffA
是 Base
的子 class 所以你只需要子 class OptionalStuffA
).我知道如果 Base
被不止一次继承,这对 MRO 应该没有什么影响,但我不确定让所有次要 classes 继承自是否有任何缺点Base
.
下面是一个示例场景。我还将 QObject
class 作为 'third party' 标记 class 投入使用,其功能是辅助 class 之一工作所必需的。我在哪里分class呢?下面的示例显示了到目前为止我是如何做到的,但我怀疑这是要走的路。
from PyQt5.QtCore import QObject
class Base:
def __init__(self):
self._basic_stuff = None
def reset(self):
self._basic_stuff = None
class OptionalStuffA:
def __init__(self):
super().__init__()
self._optional_stuff_a = None
def reset(self):
if hasattr(super(), 'reset'):
super().reset()
self._optional_stuff_a = None
def do_stuff_that_only_works_if_my_children_also_inherited_from_Base(self):
self._basic_stuff = not None
class OptionalStuffB:
def __init__(self):
super().__init__()
self._optional_stuff_b = None
def reset(self):
if hasattr(super(), 'reset'):
super().reset()
self._optional_stuff_b = None
def do_stuff_that_only_works_if_my_children_also_inherited_from_QObject(self):
print(self.objectName())
class ClassThatIsActuallyUsed(Base, OptionalStuffA, OptionalStuffB, QObject):
def __init__(self):
super().__init__()
self._unique_stuff = None
def reset(self):
if hasattr(super(), 'reset'):
super().reset()
self._unique_stuff = None
我从你的问题中得到的是,你希望根据不同的条件拥有不同的功能和属性,这听起来是使用 MetaClass 的充分理由。
这完全取决于您的每个 class 的复杂程度,以及您要构建什么,如果它是针对某个库或 API,那么如果使用得当,MetaClass 可以发挥神奇作用。
MetaClass 非常适合根据某种条件向 class 添加函数和 属性,您只需将所有子 class 函数添加到一个元 class 并将该 MetaClass 添加到您的主 class
从哪里开始
您可以阅读 MetaClass here, or you can watch it here。
在您更好地了解 MetaClass 之后,请参阅 here and here, but before that take a brief look on how the Django Form 的 Django ModelForm 的源代码,这将使您了解如何实现它。
这就是我的实现方式。
#You can also inherit it from other MetaClass but type has to be top of inheritance
class meta_class(type):
# create class based on condition
"""
msc: meta_class, behaves much like self (not exactly sure).
name: name of the new class (ClassThatIsActuallyUsed).
base: base of the new class (Base).
attrs: attrs of the new class (Meta,...).
"""
def __new__(mcs, name, bases, attrs):
meta = attrs.get('Meta')
if(meta.optionA){
attrs['reset'] = resetA
}if(meta.optionB){
attrs['reset'] = resetB
}if(meta.optionC){
attrs['reset'] = resetC
}
if("QObject" in bases){
attrs['do_stuff_that_only_works_if_my_children_also_inherited_from_QObject'] = functionA
}
return type(name, bases, attrs)
class Base(metaclass=meta_class): #you can also pass kwargs to metaclass here
#define some common functions here
class Meta:
# Set default values here for the class
optionA = False
optionB = False
optionC = False
class ClassThatIsActuallyUsed(Base):
class Meta:
optionA = True
# optionB is False by default
optionC = True
编辑:详细说明了如何实现 MetaClass。
让我从另一种选择开始。在下面的示例中,Base.foo
方法是一个普通的身份函数,但选项可以覆盖它。
class Base:
def foo(self, x):
return x
class OptionDouble:
def foo(self, x):
x *= 2 # preprocess example
return super().foo(x)
class OptionHex:
def foo(self, x):
result = super().foo(x)
return hex(result) # postprocess example
class Combined(OptionDouble, OptionHex, Base):
pass
b = Base()
print(b.foo(10)) # 10
c = Combined()
print(c.foo(10)) # 2x10 = 20, as hex string: "0x14"
关键是在定义Combined
的基数时,在Base
之前指定了Option
:
class Combined(OptionDouble, OptionHex, Base):
从左到右阅读 class 个名字,在这个简单的例子中
这是 foo()
实现的排序顺序。
它被称为方法解析顺序(MRO)。
它还定义了 super()
的确切含义,特别是 classes,这很重要,因为 Options
是作为 super()
实现
的包装器编写的
如果反过来,就不行了:
class Combined(Base, OptionDouble, OptionHex):
pass
c = Combined()
print(Combined.__mro__)
print(c.foo(10)) # 10, options not effective!
在这种情况下,首先调用 Base
实现,它直接 returns 结果。
您可以手动处理正确的基本顺序,也可以编写一个函数来检查它。它遍历 MRO 列表,一旦它看到 Base
,它就不允许在它后面有 Option
。
class Base:
def __init_subclass__(cls, *args, **kwargs):
super().__init_subclass__(*args, **kwargs)
base_seen = False
for mr in cls.__mro__:
if base_seen:
if issubclass(mr, Option):
raise TypeError( f"The order of {cls.__name__} base classes is incorrect")
elif mr is Base:
base_seen = True
def foo(self, x):
return x
class Option:
pass
class OptionDouble(Option):
...
class OptionHex(Option):
...
现在回答你的评论。我写道,@wettler 的方法可以简化。我的意思是这样的:
class Base:
def __init_subclass__(cls, *args, **kwargs):
super().__init_subclass__(*args, **kwargs)
print("options for the class", cls.__name__)
print('A', cls.optionA)
print('B', cls.optionB)
print('C', cls.optionC)
# ... modify the class according to the options ...
bases = cls.__bases__
# ... check if QObject is present in bases ...
# defaults
optionA = False
optionB = False
optionC = False
class ClassThatIsActuallyUsed(Base):
optionA = True
optionC = True
此演示将打印:
options for the class ClassThatIsActuallyUsed
A True
B False
C True
我知道标题可能有点混乱,所以让我举个例子。假设您有一个基数 class Base
,它打算被子class 编辑以创建更复杂的 objects。但是你也有可选的功能,你不需要每个 subclass,所以你把它放在辅助 class OptionalStuffA
中,它总是打算成为 subclass与基数 class 一起编辑。你是否也应该让次要 class 成为 Base
的子 class?
这当然只有当你有多个 OptionalStuff
class 并且你想以不同的方式组合它们时才有意义,因为否则你不需要 subclass Base
和 OptionalStuffA
(并且 OptionalStuffA
是 Base
的子 class 所以你只需要子 class OptionalStuffA
).我知道如果 Base
被不止一次继承,这对 MRO 应该没有什么影响,但我不确定让所有次要 classes 继承自是否有任何缺点Base
.
下面是一个示例场景。我还将 QObject
class 作为 'third party' 标记 class 投入使用,其功能是辅助 class 之一工作所必需的。我在哪里分class呢?下面的示例显示了到目前为止我是如何做到的,但我怀疑这是要走的路。
from PyQt5.QtCore import QObject
class Base:
def __init__(self):
self._basic_stuff = None
def reset(self):
self._basic_stuff = None
class OptionalStuffA:
def __init__(self):
super().__init__()
self._optional_stuff_a = None
def reset(self):
if hasattr(super(), 'reset'):
super().reset()
self._optional_stuff_a = None
def do_stuff_that_only_works_if_my_children_also_inherited_from_Base(self):
self._basic_stuff = not None
class OptionalStuffB:
def __init__(self):
super().__init__()
self._optional_stuff_b = None
def reset(self):
if hasattr(super(), 'reset'):
super().reset()
self._optional_stuff_b = None
def do_stuff_that_only_works_if_my_children_also_inherited_from_QObject(self):
print(self.objectName())
class ClassThatIsActuallyUsed(Base, OptionalStuffA, OptionalStuffB, QObject):
def __init__(self):
super().__init__()
self._unique_stuff = None
def reset(self):
if hasattr(super(), 'reset'):
super().reset()
self._unique_stuff = None
我从你的问题中得到的是,你希望根据不同的条件拥有不同的功能和属性,这听起来是使用 MetaClass 的充分理由。 这完全取决于您的每个 class 的复杂程度,以及您要构建什么,如果它是针对某个库或 API,那么如果使用得当,MetaClass 可以发挥神奇作用。
MetaClass 非常适合根据某种条件向 class 添加函数和 属性,您只需将所有子 class 函数添加到一个元 class 并将该 MetaClass 添加到您的主 class
从哪里开始
您可以阅读 MetaClass here, or you can watch it here。 在您更好地了解 MetaClass 之后,请参阅 here and here, but before that take a brief look on how the Django Form 的 Django ModelForm 的源代码,这将使您了解如何实现它。
这就是我的实现方式。
#You can also inherit it from other MetaClass but type has to be top of inheritance
class meta_class(type):
# create class based on condition
"""
msc: meta_class, behaves much like self (not exactly sure).
name: name of the new class (ClassThatIsActuallyUsed).
base: base of the new class (Base).
attrs: attrs of the new class (Meta,...).
"""
def __new__(mcs, name, bases, attrs):
meta = attrs.get('Meta')
if(meta.optionA){
attrs['reset'] = resetA
}if(meta.optionB){
attrs['reset'] = resetB
}if(meta.optionC){
attrs['reset'] = resetC
}
if("QObject" in bases){
attrs['do_stuff_that_only_works_if_my_children_also_inherited_from_QObject'] = functionA
}
return type(name, bases, attrs)
class Base(metaclass=meta_class): #you can also pass kwargs to metaclass here
#define some common functions here
class Meta:
# Set default values here for the class
optionA = False
optionB = False
optionC = False
class ClassThatIsActuallyUsed(Base):
class Meta:
optionA = True
# optionB is False by default
optionC = True
编辑:详细说明了如何实现 MetaClass。
让我从另一种选择开始。在下面的示例中,Base.foo
方法是一个普通的身份函数,但选项可以覆盖它。
class Base:
def foo(self, x):
return x
class OptionDouble:
def foo(self, x):
x *= 2 # preprocess example
return super().foo(x)
class OptionHex:
def foo(self, x):
result = super().foo(x)
return hex(result) # postprocess example
class Combined(OptionDouble, OptionHex, Base):
pass
b = Base()
print(b.foo(10)) # 10
c = Combined()
print(c.foo(10)) # 2x10 = 20, as hex string: "0x14"
关键是在定义Combined
的基数时,在Base
之前指定了Option
:
class Combined(OptionDouble, OptionHex, Base):
从左到右阅读 class 个名字,在这个简单的例子中
这是 foo()
实现的排序顺序。
它被称为方法解析顺序(MRO)。
它还定义了 super()
的确切含义,特别是 classes,这很重要,因为 Options
是作为 super()
实现
如果反过来,就不行了:
class Combined(Base, OptionDouble, OptionHex):
pass
c = Combined()
print(Combined.__mro__)
print(c.foo(10)) # 10, options not effective!
在这种情况下,首先调用 Base
实现,它直接 returns 结果。
您可以手动处理正确的基本顺序,也可以编写一个函数来检查它。它遍历 MRO 列表,一旦它看到 Base
,它就不允许在它后面有 Option
。
class Base:
def __init_subclass__(cls, *args, **kwargs):
super().__init_subclass__(*args, **kwargs)
base_seen = False
for mr in cls.__mro__:
if base_seen:
if issubclass(mr, Option):
raise TypeError( f"The order of {cls.__name__} base classes is incorrect")
elif mr is Base:
base_seen = True
def foo(self, x):
return x
class Option:
pass
class OptionDouble(Option):
...
class OptionHex(Option):
...
现在回答你的评论。我写道,@wettler 的方法可以简化。我的意思是这样的:
class Base:
def __init_subclass__(cls, *args, **kwargs):
super().__init_subclass__(*args, **kwargs)
print("options for the class", cls.__name__)
print('A', cls.optionA)
print('B', cls.optionB)
print('C', cls.optionC)
# ... modify the class according to the options ...
bases = cls.__bases__
# ... check if QObject is present in bases ...
# defaults
optionA = False
optionB = False
optionC = False
class ClassThatIsActuallyUsed(Base):
optionA = True
optionC = True
此演示将打印:
options for the class ClassThatIsActuallyUsed
A True
B False
C True