【问题标题】:Call Method on Every Ancestor in Python在 Python 中调用每个祖先的方法
【发布时间】:2010-12-31 16:25:09
【问题描述】:

我在 python 中有一个“D”类的对象,我想依次执行“D”定义的“运行”方法及其每个祖先(“A”、“B”和“C”) .

我可以这样做

class A(object):
    def run_all(self):
        # I prefer to execute in revere MRO order
        for cls in reversed(self.__class__.__mro__):
            if hasattr(cls, 'run'):
                # This works
                cls.run(self)
                # This doesn't
                #cls.__getattribute__(self, 'run')()

    def run(self):
        print "Running A"

class B(A):
    def run(self):
        print "Running B"

class C(A):
    def run(self):
        print "Running C"

class D(C, B):
    def run(self):
        print "Running D"

if __name__ == "__main__":
    D().run_all()

结果

$ python test.py 
Running A
Running B
Running C
Running D

但实际上我不知道要执行的方法的名称。但是,如果我使用 getattribute() (请参阅注释)行尝试此操作,它将不起作用:

$ python test.py 
Running D
Running D
Running D
Running D

所以我的问题是:

  1. 为什么不起作用?

  2. 这是最好的解决方法吗?

【问题讨论】:

    标签: python datamodel


    【解决方案1】:

    如果您可以更改所有 run 实现(并在 D 中调用 run 而不是 run_all),则此方法有效:

    class A(object):
        def run(self):
            print "Running A"
    
    class B(A):
        def run(self):
            super(B, self).run()
            print "Running B"
    
    class C(A):
        def run(self):
            super(C, self).run()
            print "Running C"
    
    class D(C, B):
        def run(self):
            super(D, self).run()
            print "Running D"
    
    if __name__ == "__main__":
        D().run()
    

    请注意,我在根类中使用super——它“知道”没有进一步的超类可以达到(object 没有定义run 方法)。不幸的是,在 Python 2 中,这不可避免地很冗长(也不太适合通过装饰器实现)。

    如果我正确理解您的目的,您对hasattr 的检查非常脆弱——如果一个类定义了或继承它,它会发现一个类“具有”该属性。因此,如果您有一个不覆盖run 但确实发生在__mro__ 上的中间类,则它继承的run 版本在您的方法中被调用两次。例如,考虑:

    class A(object):
        def run_all(self):
            for cls in reversed(self.__class__.__mro__):
                if hasattr(cls, 'run'):
                    getattr(cls, 'run')(self)
        def run(self):
            print "Running A"
    class B(A): pass
    class C(A):
        def run(self):
            print "Running C"
    class D(C, B): pass
    
    if __name__ == "__main__":
        D().run_all()
    

    打印出来

    Running A
    Running A
    Running C
    Running C
    

    对于BD 继承而不覆盖的run 版本有两个“口吃”(分别来自AC)。假设我是对的,这不是您想要的效果,如果您想避免使用super,您可以尝试将run_all 更改为:

    def run_all(self):
        for cls in reversed(self.__class__.__mro__):
            meth = cls.__dict__.get('run')
            if meth is not None: meth(self)
    

    在我的最新示例中,在AC 中仅用两个不同的defs 替换run,使示例打印:

    Running A
    Running C
    

    我怀疑这可能更接近你想要的。

    还有一点:不要重复工作——hasattr 保护 getattr,或 in 测试保护 dict 访问——保护中的检查和受保护的访问器都必须在内部重复完全相同的工作,没有好的目的。相反,将None 的第三个参数用于单个getattr 调用(或dict 的get 方法):这意味着如果该方法不存在,您将检索None 值,然后您可以防止 调用 发生这种情况。这正是 dicts 有 get 方法和 getattr 有第三个可选“默认”参数的原因:为了便于应用 DRY,“不要重复自己”,这是良好编程的一个非常重要的格言!- )

    【讨论】:

    • 这正是我要找的,你对“口吃”的看法是对的,我没有想到这一点。谢谢大家!
    • 另外,据我了解,要将 super 与多重继承一起使用,您确实需要从“根”类调用它才能遍历整个树/钻石/任何东西(正如罗伯托在下面所做的那样) ,虽然我可能是错的。
    【解决方案2】:

    你不应该使用__getattribute__ 方法..

    只需执行以下操作:

    getattr(cls, 'run')(self)
    

    【讨论】:

    • 你可以使用 getattribute,但它看起来很“不好看” - cls.__getattribute__(cls, 'run')(self)
    【解决方案3】:

    为什么不直接使用super?虽然有些consider it harmful,但它的设计正是考虑到这种场景,我会毫不犹豫地使用它。

    来自Python documentation

    这对于访问继承的很有用 在 a 中被覆盖的方法 班级。搜索顺序与 getattr() 使用的,除了 类型本身被跳过。 [...] 这 使实施成为可能 “钻石图”,其中多个基础 类实现相同的方法。

    更新:在你的情况下,它会变成这样:

    class A(object):
    
        def run(self):
            print "Running A"
    
    class B(A):
        def run(self):
            super(B, self).run()
            print "Running B"
    
    class C(A):
        def run(self):
            super(C, self).run()
            print "Running C"
    
    class D(C, B):
        def run(self):
            super(D, self).run()
            print "Running D"
    
    if __name__ == "__main__":
        D().run()
    

    【讨论】:

    • 我最初尝试过,但是我遇到了 super(self.__class__, self) 的问题(它似乎总是返回 self.__class__,至少在上述情况下)而且我不想要用户每次都必须调用它。
    • 是的,super 需要明确的类规范,就像我回答的第一个代码建议一样——在其中使用 self.__class__ 无法正常工作。 (顺便说一句,Python 3 更好一些)。
    • 为什么不直接使用 super(D, self).run() 呢?我在主要答案中举了一个例子。
    • 在我的情况下,子类将代表可以继承的服务器配置等,我试图在 python 的限制内尽可能地保持语法的声明性。所以我不希望每个子类的每个方法都必须手动调用 super。
    • @Roberto,你说“你甚至需要在你的层次结构的“根”类上调用 super ”是完全错误的!只需尝试您的代码,您就会看到带有AttributeError: 'super' object has no attribute 'run' 的回溯;删除你声称需要的A.run 中的super 调用,就像我的答案的代码省略了它一样,你会看到回溯消失,一切都正常工作......当然,它在我的答案中工作,因为,一旦这个可怕的错误得到修复,你的代码就和我的一样了;-)。
    猜你喜欢
    • 2012-08-29
    • 1970-01-01
    • 1970-01-01
    • 2017-02-20
    • 1970-01-01
    • 2013-02-06
    • 1970-01-01
    • 2021-04-26
    • 1970-01-01
    相关资源
    最近更新 更多