python 是否可以在子类中使用'_getattr__'生成的方法?

p3rjfoxz  于 2023-01-19  发布在  Python
关注(0)|答案(1)|浏览(118)

我有一个类,它的方法可能是自动生成的,也可能不是自动生成的。我希望能够从子类调用这些方法,但不知道如何实现。
请看下面的代码:

class Upgrader:
    max = 99

    def _upgrade_no_op(self, db):
        if self.version < Upgrader.max:
            self.version += 1

    def __getattribute__(self, key):
        try:
            return super().__getattribute__(key)    
        except AttributeError:
            if key.startswith("upgrade_v"):    
                nr = int(key[9:])
                if 0 <= nr < self.max:                   
                    return self._upgrade_no_op
            raise

class Foo(Upgrader):
    version = 1
    def upgrade_v2(self):
        # None of these work:

        # Upgrade.upgrade_v2(self)
        # super().upgrade_v2()
        # super(Foo,self).upgrade_v2()
        # x = Upgrade.__getattribute__(self, "upgrade_v2");  # bound to Foo.upgrade_v2
        breakpoint()

Foo().upgrade_v2()

在我的实际代码中,在FooUpgrader之间还有一两个其他类,但您应该明白我的意思。
有没有办法做到这一点,最好不用编写我自己的super()版本?

jgwigjjp

jgwigjjp1#

你有几个问题:

  1. return super().__getattribute__(key)在超类(通常为object)上查找__getattribute__,但是key的实际查找是普通查找,它没有绕过MRO中的类,它只是从MRO中的 * 第一 * 类(self实际上是其示例的类)重新开始查找。它会跳过MRO中前面的类,执行一次查找,然后就完成了。这是当属性存在并且从类的方法外部调用时所需要的(Foo().update_v2()的 * initial * 调用首先通过Updater.__getattribute__来查找Foo.update_v2),但是当Foo.update_v2试图调用update_v2的"可能不存在"父版本时,即使当你设法调用父__getattribute__(例如,通过直接调用Upgrader.__getattribute__(self, "upgrade_v2")())时,它也只是再次给你Foo.update_v2,导致无限递归。
  2. super()绕过了许多动态查找机制;在MRO中调用类之后,它只会找到与每个类关联的具有适当名称的内容,但不会尝试在每个类上调用__getattr____getattribute__(这会大大降低速度,并使代码复杂化)。让它动态地截取 * 所有 * 查找,以便 * 在查找期间 * 将内容插入继承链的中间,这充其量是某种真正强烈的代码味道。
    在99.99%以上的情况下,您根本不需要动态查找处理,在极少数情况下,您几乎总是需要__getattr__(只有在无法以任何其他方式找到名称时才调用它),而不是__getattribute__(无条件调用它)。
    但是,这两种特殊方法都不能以您希望的方式与super()触发的查找一起工作,因此,如果您真的 * 需要 * 类似的东西,您将不得不手动重新实现super()正在做的事情,但还要添加动态查找特殊方法支持(手动检查示例的MRO,从每个类中寻找您需要的内容,如果缺少,手动调用__getattr__/__getattribute__来查看它是否可以生成它,如果也失败,则继续下一个类)。不要试图去做。
    我强烈怀疑您这里有an XY problem。您尝试做的东西本质上是脆弱的,只会让 * 任何 *在Foo.upgrade_v2中的super().upgrade_v2()实际上可能在某个类中找到有用的函数,该函数与Foo一起被某个假设的第三个类继承,该第三个类涉及菱形继承模式,该模式返回到Upgrader。这已经够复杂的了,现在您要添加__getattribute__(它会减慢类示例的 * 每次 * 使用,并且即使没有任何类型的继承,它本身也有很多陷阱);这不是个好主意。
    如果像本例中那样,您有一个应该存在的固定方法集,那么只需预先生成它们,并且完全避免使用__getattribute__
class Upgrader:
    max = 99

    def _upgrade_no_op(self):
        if self.version < Upgrader.max:
            self.version += 1

# Dynamically bind _upgrade_no_op to each name we intend to support on Upgrader
for i in range(Upgrader.max):
    setattr(Upgrader, f'upgrade_v{i}', Upgrader._upgrade_no_op)

class Foo(Upgrader):
    version = 1
    def upgrade_v2(self):
        # Upgrade.upgrade_v2(self)
        super().upgrade_v2()  # Works just fine

f = Foo()
print(f.version)  # See original version of 1 here
f.upgrade_v2()
print(f.version)  # See updated version of 2 here

在线试用!
您的代码将工作,运行速度显著加快(* every * 属性和方法查找中不涉及Python级别的函数调用),而且它不会让维护人员发疯地试图弄清楚您在做什么。

相关问题