【问题标题】:python: nicer base 'object' for multiple inheritancepython:用于多重继承的更好的基本“对象”
【发布时间】:2016-12-18 20:47:05
【问题描述】:

最近我尝试过使用多类继承(在 Python 3 中),这是我以前从未做过的(从未真正使用过它)。

我很惊讶它有多“糟糕”。它的行为不像我预期的那样。我了解 MRO 和订单解决方案,但除非我遗漏了什么,否则该设计不允许在链的末端出现额外的参数。

让我举一个基本的例子:

class BaseA(object):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if kwargs.pop('is_cool', False):
            self.a = True
        else:
            self.a = False


class FixedBaseA(object):
    def __init__(self, *args, **kwargs):
        if kwargs.pop('is_cool', False):
            self.a = True
        else:
            self.a = False
        # if you move super to the top (see BaseA), MyThing instances will raise an error
        # Note: instead of having super down here, one could have explicitly extracted
        # the keyword 'is_cool' in the definition of __init__ method
        # --> __init__(self, *args, is_cool=False, **kwargs)
        super().__init__(*args, **kwargs)


class BaseB(object):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if kwargs.pop('is_cool', False):
            self.b = True
        else:
            self.b = False


class MyThing(BaseA, BaseB):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class FixedMyThing(FixedBaseA, BaseB):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class FixedMyThing2(BaseB, FixedBaseA):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class MyThing2(BaseB, BaseA):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

您可能已经猜到了,我希望的行为是:

  • 无论我在继承类中为 BaseA 和 BaseB 给出什么顺序,我最终都会正确设置属性 ab,并且没有错误
  • 提供额外参数不会引发错误

当然不是这样。以下是一些测试:

def test(cls, *args, **kwargs):
    try:
        x = cls(*args, **kwargs)
        print('{:} --> a, b = {:}, {:}'.format(cls.__name__, x.a, x.b))
    except TypeError as err:
        print('[ERR] {:} raised a TypeError: {:}'.format(cls.__name__, err))

test(MyThing, is_cool=True)
test(FixedMyThing, is_cool=True)
test(MyThing2, is_cool=True)
test(FixedMyThing2, is_cool=True)
test(FixedMyThing2, z=3.4)

给出:

[ERR] MyThing raised a TypeError: object.__init__() takes no parameters
FixedMyThing --> a, b = True, False
[ERR] MyThing2 raised a TypeError: object.__init__() takes no parameters
FixedMyThing2 --> a, b = True, True
[ERR] FixedMyThing2 raised a TypeError: object.__init__() takes no parameters

现在我的问题不是关于为什么会发生这种情况,有可以在网上找到的解释。虽然我无法判断这些原因,但我想知道为什么 object 类型/类在接受任何额外参数时没有表现出不同的行为。

所以真正的问题是:用下面的类替换object 是个坏主意吗?如果是,为什么?

class BaseObject(object):
    def __init__(self, *args, **kwargs):
        try:
            # this is necessary to pass along parameters for next-in-line inherited classes
            super().__init__(*args, **kwargs)
        except TypeError as err:
            if err.__str__().startswith('object.__init__() takes no parameters'):
                pass
            else:
                raise

在任何地方使用此类代替object 允许交换逻辑上可以交换的类的顺序(即它们的属性/方法不重叠),并且允许使用额外的参数而不会遇到任何麻烦全部,但仍会在需要时引发错误。

MyThing --> a, b = True, True
FixedMyThing --> a, b = True, False
MyThing2 --> a, b = True, True
FixedMyThing2 --> a, b = True, True
FixedMyThing2 --> a, b = False, False

对此有任何想法都会很好。

【问题讨论】:

    标签: python python-3.x class object multiple-inheritance


    【解决方案1】:

    首先,在 Python 3 中,您不需要从 object 继承。这仅适用于 Python 2;现在已经隐式完成了。


    我想知道为什么对象类型/类在接受任何额外参数时没有表现出不同的行为。

    我不确定你在这里的意思。

    • 如果你问为什么object 不带任何参数,那是因为它对它们没有用处。提出错误总是比默默地忽略意外情况要好。 (“错误永远不应该静默传递。除非明确地静默。”——Python 之禅)
    • 如果您问为什么您的类接受额外的参数:那是因为您使用 *args, **kwargs 明确允许它们。

    所以真正的问题是:用下面的类替换 object 是个坏主意吗?如果是,为什么?

    是的,因为你默默地忽略了一个错误。


    一般来说,不建议使用*args, **kwargs,除非您使用大量参数,或者您正在编写中间件库(即,将参数从使用您的库传递到您使用的库)。

    相反,您应该像这样重写BaseA

    class BaseA(object):
        def __init__(self, is_cool=False):
            super().__init__()
            if is_cool:
                self.a = True
            else:
                self.a = False
    

    其他课程也是如此。


    现在,更多题外话:

    您可以进一步清理BaseA,因为您使用的是 Python 3:

    class BaseA(object):
        def __init__(self, is_cool=False):
            if is_cool:
                self.a = True
            else:
                self.a = False
    

    你甚至可以去掉不必要的析取:

    class BaseA(object):
        def __init__(self, is_cool=False):
            self.a = is_cool
    

    【讨论】:

    • 感谢您的回答。是的,这些示例当然可以更简洁,我只是快速编写它们。我同意不应该默默地忽略错误,但在object 的情况下,我看不出引发错误的意义:它只是意味着必须在到达object 之前消耗所有参数。虽然不建议使用*args, **kwargs,但它是完全有效的,并且对于您提到的灵活性和中间件非常有用。但是object 或多或少会阻止使用它们。它认为这应该留给编码人员,这将与 Python 的其余部分(例如函数)更加一致
    猜你喜欢
    • 1970-01-01
    • 2015-05-24
    • 2015-10-29
    • 2014-10-13
    • 1970-01-01
    • 1970-01-01
    • 2021-03-15
    • 2011-04-13
    • 1970-01-01
    相关资源
    最近更新 更多