[英]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.