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

2024-10-04 05:34:28 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图创建一个类,该类封装了一个将在多个其他对象中使用的值。出于计算原因,目的是只计算一次此包装值,并将对该值的引用传递给其用户。我认为这在普通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属性上的方法


Tags: the对象方法selfreturn定义valuedef
2条回答

本例中的+运算符不起作用,因为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

谈论“参考传递”只会让你感到困惑。将这些术语保留在您可以选择的语言中,并保留在不同的语言中。在Python中,您总是将对象四处传递,这种传递相当于“按引用传递”所有对象,从None传递到int,再传递到活动的异步网络连接池实例

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

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

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

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

然而,正如我上面所描述的,它可以完成。我甚至在pypi上有一个小的(长而未维护的)包,可以实现这一点,为未来包装一个代理。 代码位于https://bitbucket.org/jsbueno/lelo/src/master/lelo/_lelo.py

相关问题 更多 >