我正在尝试创建一个类来包装将在多个其他对象中使用的值。出于计算原因,目的是让这个包装值只计算一次,并将对该值的引用传递给它的用户。由于其对象容器模型,我认为这在 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 中,你总是传递 objects - 这种传递相当于“通过引用传递” - 对于 all 对象 - 从 None 到 int 再到实时异步网络连接池实例。
除此之外:语言遵循的从对象检索属性的算法很复杂,有详细信息 - 实现 __getattr__
只是冰山一角。完整阅读名为“Data Model”的文档将使您更好地掌握检索属性所涉及的所有机制。
就是说,这里是“魔术”或“双下划线”方法的工作原理 -(名称前后两个下划线的特殊函数):当您使用需要存在实现它的方法的运算符时(如 __add__
用于 +
),该语言检查对象的 class 是否有 __add__
方法 - 而不是实例。类上的 __getattr__
只能为该类的实例动态创建属性。
但这不是唯一的问题:您可以创建一个元类(继承自 type
)并在该元类上放置一个 __getattr__
方法。对于您从 Python 执行的所有查询,看起来您的对象在其类中具有 __add__
(或任何其他 dunder 方法)。但是,对于 dunder 方法,Python 不会通过正常的属性查找机制 - 如果 dunder 方法“物理上”在那里,它会直接“查找”类。内存结构中有插槽,用于保存每个可能的 dunder 方法的类 - 它们要么引用相应的方法,要么是“空”(在 Python 端用 C 编码时这是“可见的”,默认情况下dir
将在这些方法存在时显示它们,如果不存在则忽略它们)。如果它们不存在,Python 只会“说”该对象没有实现该操作和句点。
使用您想要的代理对象解决此问题的方法是创建一个代理类,该代理类具有您要包装的类中的 dunder 方法,或者具有所有可能的方法,并在被调用时检查是否底层对象实际上实现了调用的方法。
这就是为什么“严肃”的代码很少(如果有的话)提供真正的“透明”代理对象。有异常(exception),但是从“Weakrefs”到“super()”再到 concurrent.futures,仅举几个核心语言和 stdlib 中的例子,没有人尝试“完全工作的透明代理”——相反,api 是更像是您在包装器上调用“.value()”或“.result()”方法来获取原始对象本身。
但是,正如我上面所述,它可以完成。我什至在 pypi 上有一个小的(长期未维护的)包可以做到这一点,为 future 包装一个代理。 代码位于 https://bitbucket.org/jsbueno/lelo/src/master/lelo/_lelo.py
关于python - 拦截python类中的魔术方法调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60620112/