繁体   English   中英

拦截python类中的魔术方法调用

[英]Intercept magic method calls in python class

我正在尝试创建一个包含将在多个其他对象中使用的值的类。 出于计算的原因,目标是让这个包装的值只计算一次,并将对该值的引用传递给它的用户。 由于其对象容器模型,我不相信在 vanilla python 中这是可能的。 相反,我的方法是一个传递的包装类,定义如下:

class DynamicProperty():

    def __init__(self, value = None):
        # Value of the property
        self.value: Any = value

    def __repr__(self):
        # Use value's repr instead
        return repr(self.value)

    def __getattr__(self, attr):
        # Doesn't exist in wrapper, get it from the value 
        # instead
        return getattr(self.value, attr)

以下按预期工作:

wrappedString = DynamicProperty("foo")
wrappedString.upper()  # 'FOO'

wrappedFloat = DynamicProperty(1.5)
wrappedFloat.__add__(2)  # 3.5

但是,通过正常语法隐式调用__add__失败:

wrappedFloat + 2  # TypeError: unsupported operand type(s) for 
                  # +: 'DynamicProperty' and 'float'

有没有办法拦截这些隐式方法调用,而无需为 DynamicProperty 显式定义魔术方法来调用其value属性上的方法?

谈论“通过引用传递”只会让你感到困惑。 将该术语保留在您可以选择的语言中,并在其中有所作为。 在 Python 中,你总是传递对象- 这种传递相当于“通过引用传递” - 对于所有对象 - 从 None 到 int 到实时异步网络连接池实例。

顺便说一句:语言遵循的从对象中检索属性的算法很复杂,有细节 - 实现__getattr__只是冰山一角。 完整阅读名为“数据模型”的文档将使您更好地掌握检索属性所涉及的所有机制。

也就是说,这里是“magic”或“dunder”方法的工作原理-(名称前有两个下划线和名称后有两个下划线的特殊函数):当您使用需要存在实现它的方法的运算符时(如__add__对于+ ),语言会检查对象的__add__方法 - 而不是实例。 类上的__getattr__只能为该类的实例动态创建属性。 但这不是唯一的问题:您可以创建一个元类(从type继承)并在这个元类上放置一个__getattr__方法。 对于您从 Python 执行的所有查询,看起来您的对象在其类中具有__add__ (或任何其他 dunder 方法)。 然而,对于 dunder 方法,Python 不通过正常的属性查找机制——它直接“查看”类,如果 dunder 方法“物理地”在那里。 内存结构中有一些插槽,用于保存每个可能的 dunder 方法的类 - 它们要么引用相应的方法,要么为“空”(在 Python 端用 C 编码时,这是“可见的”,默认情况下dir将在这些方法存在时显示它们,如果不存在则省略它们)。 如果它们不存在,Python 只会“说”该对象没有实现该操作和周期。

使用您想要的代理对象解决这个问题的方法是创建一个代理类,该类具有您要包装的类中的 dunder 方法,或者具有所有可能的方法,并在被调用时检查底层对象是否实际实现被调用的方法。

这就是为什么“严肃”的代码很少(如果有的话)提供真正的“透明”代理对象的原因。 也有例外,但是从“Weakrefs”到“super()”,到 concurrent.futures,仅举几个核心语言和 stdlib 中的一些,没有人尝试“完全工作的透明代理”-相反,api 是更像是您在包装器上调用“.value()”或“.result()”方法以获取原始对象本身。

但是,正如我上面描述的那样,它可以做到。 我什至在 pypi 上有一个小的(长期未维护的)包来做到这一点,为未来包装一个代理。 代码位于https://bitbucket.org/jsbueno/lelo/src/master/lelo/_lelo.py

您的情况下的+运算符不起作用,因为DynamicProperty不继承自float 看:

>>> class Foo(float):
    pass

>>> Foo(1.5) + 2
3.5

因此,您需要进行某种动态继承:

def get_dynamic_property(instance):

    base = type(instance)

    class DynamicProperty(base):
        pass

    return DynamicProperty(instance)


wrapped_string = get_dynamic_property("foo")
print(wrapped_string.upper())

wrapped_float = get_dynamic_property(1.5)
print(wrapped_float + 2)

输出:

FOO
3.5

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM