在Python中从具有多重继承的右父类调用函数

qojgxg4l  于 2023-03-09  发布在  Python
关注(0)|答案(3)|浏览(148)

我有两个类AB,它们都有一个返回字符串的方法get_type(),在两个类的__init__()中调用该方法来设置示例属性self.type。现在有一个继承自AB的子类,在它里面's __init__(),它以AB的示例作为参数,根据它是用A还是B的示例示例化的,我调用父对象对应的__init__(),大概是因为多重继承的查找顺序,始终调用A父级的get_type()方法,即使C是使用B示例示例化的。请参阅下面的代码:

class A:
    def __init__(self):
        self.type = self.get_type()
    def get_type(self):
        return 'A'
class B: 
    def __init__(self):
        self.type = self.get_type()
    def get_type(self):
        return 'B'
class C(A, B):
    def __init__(self, instance):
        if isinstance(instance, A):
            A.__init__(self)
            print('Inheriting from A')
        else:
            B.__init__(self)
            print('Inheriting from B')
            
a = A()
b = B()
c = C(b)
print(a.type, b.type, c.type)

>>> Inheriting from B
>>> A B A

所以我希望c.type是'B',因为它继承自B类。我可以使用super()函数来获得我想要的行为吗?

umuewwlo

umuewwlo1#

除了你为什么要这么做之外,下面是你的要求:
当你有冲突的(或潜在冲突的)方法名时,Python的机制是在方法名前加上两个下划线(__),大多数文章和第三方文档都把这称为“在Python中创建私有属性/方法的方法”,但事实并非如此:Python根本不使用私有属性,而是按照惯例(使用单个_作为前缀)。
使用两个下划线,而不是一个,触发方法或属性的内部名称转换,以透明的方式将类名作为前缀添加到目标方法中。这被称为“名称修饰”。
对于您的代码,它将在没有进一步更改的情况下开箱即用。如果您检查运行的代码(例如print(dir(self))),您将看到您的方法将分别命名为A._A__get_typeB._B__get_type(这两个方法都将出现在C中)

class A:
    def __init__(self):
        self.type = self.__get_type()
    def __get_type(self):
        return 'A'
class B: 
    def __init__(self):
        self.type = self.__get_type()
    def __get_type(self):
        return 'B'

但是,请注意C中的任何方法都不能“看到”.get_type:这个方法现在对每个超类都是私有的。2两者都可以通过转换后的名字从C中访问,如果显式使用的话,很坚韧。

fjnneemd

fjnneemd2#

看起来您正在尝试执行某种类型的 * 条件继承 ,这里是similar question 条件继承 * 可以被视为反模式,因为它会使代码更难理解和维护,无论如何,您可以使用一些已知的模式(如 Wrapper/Decorator 模式)来实现它,在注解中有一个很好的例子。2你可以使用的另一个模式是 Composition 模式,下面是一个例子:

class A:
    def __init__(self):
        self.type = self.get_type()

    def get_type(self):
        return 'A'

class B:
    def __init__(self):
        self.type = self.get_type()

    def get_type(self):
        return 'B'

class C:
    @classmethod
    def composed_width(cls, Klass, *args, **kwargs):
        class Composed(cls, Klass):
            pass
        return Composed(*args, **kwargs)

    def get_type_wrapped(self):
        return f'WrappedType[{self.get_type()}]'

c_a = C.composed_width(A)
c_b = C.composed_width(B)

print(c_a.type, c_a.get_type_wrapped())
print(c_b.type, c_b.get_type_wrapped())

>>> A WrappedType[A]
>>> B WrappedType[B]

方法get_type_wrapped只是给予了一个 shared 方法的例子,其思想只是创建一个局部类,该局部类 * 组合 * 类C和另一个作为参数传递的类,并返回它的一个示例。

ni65a41a

ni65a41a3#

我想我明白你的目标是什么。首先,让我们看看为什么你的方法是有缺陷的:

当前方法的问题

根据您的类布局,C既是A又是B。原因是继承形成了“是a”关系。由此,隐含的期望是该关系满足“Liskov替换原理”,也就是说,您可以在代码需要AB的地方提供C。我相信这不是您的意图,尽管很接近。

可能的替代方法

您在注解中提到,AB都有一个公共接口,甚至可以实现为抽象基类(https://docs.python.org/3/library/abc.html)。我们称之为I(如在I界面中)使讨论更容易。它是作为ABC存在还是仅仅是一种约定并不重要(提供类似方法的类,又名“Duck Typing”),Python在这方面是允许的。
有了I接口,我们实际上就有了三个类,它们都实现了这个接口。如果你现在编写代码期望I(而不是具体的AB),你应该能够将ABC中的任何一个传递给它(同样,Liskov替换)。
此外,C不必同时继承AB,它可以包含其中之一。实际上,它可以只包含一个I,以便将来更容易地扩展到更多此类类型。I所需的任何方法(您的草图中只有get_type())可以通过简单地将其转发到包含的I示例来实现。

注解

  • 为什么要把C放在首位?我怀疑它是否需要成为一个类。不是所有的东西都是类,也不是所有的东西都应该是类!我的猜测是大部分都被一个公共的抽象基类淘汰了。我能看到的唯一真实的的优点是交换底层AB的能力,从而切换行为。
  • 阅读get_type()是一种代码气味。我有一种想法,你正在实现一个类型系统(Python有它自己的),你正在尝试实现多态性或类似的东西。这不一定是一个问题,但它可能需要一些审查在未来。
  • 看看https://en.wikipedia.org/wiki/Decorator_pattern,它是类似的,也许还有其他一些相关的模式,不管它们是否适合您的用例。

相关问题