在 Python 中调用每个祖先的方法

发布于 2024-08-16 18:15:20 字数 1065 浏览 3 评论 0原文

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

我能够像这样完成此操作,

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. 这是否是解决此问题的最佳方法?

I have an object of class 'D' in python, and I want to sequentially execute the 'run' method as defined by 'D' and each of it's ancestors ('A', 'B' and 'C').

I'm able to accomplish this like this

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()

Which results in

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

However in practice I won't know the name of the method to be executed. But if I try this using getattribute() (see the commented) line it doesn't work:

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

So my questions are:

  1. Why isn't it working?

  2. Is this even the best way to go about this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

花伊自在美 2024-08-23 18:15:20

如果您同意更改所有 run 实现(并调用 run 而不是 D 中的 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()

请注意,我 < em>不要在根类中使用super——它“知道”没有进一步的超类可以向上(object没有定义<代码>运行方法)。不幸的是,在 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 版本, 有两个“口吃”(来自 A<分别为 /code> 和 C)。假设我是对的,这不是您想要的效果,如果您热衷于避免 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 中仅用两个不同的 def 来表示 run,使得示例打印:

Running A
Running C

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

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

If you're OK with changing all the run implementations (and calling run instead of run_all in D), this works:

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()

Note that I don't use super in the root class -- it "knows" there's no further superclass to go up to (object does not define a run method). Unfortunately, in Python 2, this is inevitably verbose (and not well suited to implementing via a decorator, either).

Your check on hasattr is quite fragile, if I understand your purposes correctly -- it will find that a class "has" the attribute if it defines or inherits it. So if you have an intermediate class that doesn't override run but does occur on the __mro__, the version of run it inherits gets called twice in your approach. E.g., consider:

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()

this prints

Running A
Running A
Running C
Running C

with two "stutters" for versions of run that B and D inherit without overriding (from A and C respectively). Assuming I'm right that this is not the effect you want, if you're keen to avoid super you could try changing run_all to:

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

which, substituted into my latest example with just two distinct defs for run in A and C, makes the example print:

Running A
Running C

which I suspect may be closer to what you want.

One more side point: don't repeat the work -- hasattr guarding getattr, or an in test guarding dict access -- both the check in the guard, and the guarded accessor, must repeat exactly the same work internally, to no good purpose. Rather, use a third argument of None to a single getattr call (or the get method of the dict): this means that if the method is absent you'll retrieve a None value, and then you can guard the call against that occurrence. This is exactly the reason dicts have a get method and getattr has a third optional "default" argument: to make it easy to apply DRY, "don't repeat yourself", a very important maxim of good programming!-)

围归者 2024-08-23 18:15:20

您不应该使用 __getattribute__ 方法..

只需执行以下操作:

getattr(cls, 'run')(self)

You should not use __getattribute__ method..

just do the following:

getattr(cls, 'run')(self)
风吹雪碎 2024-08-23 18:15:20

为什么不直接使用 super 呢?虽然有些认为它有害,但它的设计正是考虑到了这种情况,我会毫不犹豫地使用它。

来自 Python 文档

这对于访问继承的
已被重写的方法
班级。搜索顺序与
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()

Why don't you simply use super? Although some consider it harmful, it was designed with exactly this kind of scenario in mind, and I would use it without any hesitation.

From Python documentation:

This is useful for accessing inherited
methods that have been overridden in a
class. The search order is same as
that used by getattr() except that the
type itself is skipped. [...] This
makes it possible to implement
“diamond diagrams” where multiple base
classes implement the same method.

Update: In your case, it would become something like this:

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()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文