包装一个 class 其方法 return 个 class 实例

Wrapping a class whose methods return instances of that class

我需要写一个 class 来包装来自第三方包的 classes。通常,第三方 class 具有 return 第三方 class 实例的方法。这些方法的包装版本必须将这些实例转换为包装 class 的实例,但我无法使其工作。我正在使用 Python 2.7 和新式 classes.

基于Create a wrapper class to call a pre and post function around existing functions?,我有以下内容。

import copy

class Wrapper(object):
    __wraps__  = None

    def __init__(self, obj):
        if self.__wraps__ is None:
            raise TypeError("base class Wrapper may not be instantiated")
        elif isinstance(obj, self.__wraps__):
            self._obj = obj
        else:
            raise ValueError("wrapped object must be of %s" % self.__wraps__)

    def __getattr__(self, name):
        orig_attr = self._obj.__getattribute__(name)
        if callable(orig_attr):
            def hooked(*args, **kwargs):
                result = orig_attr(*args, **kwargs)
                if result == self._obj:
                    return result
                return self.__class__(result)
            return hooked
        else:
            return orig_attr

class ClassToWrap(object):
    def __init__(self, data):
        self.data = data

    def theirfun(self):
        new_obj = copy.deepcopy(self)
        new_obj.data += 1
        return new_obj

class Wrapped(Wrapper):
    __wraps__ = ClassToWrap

    def myfun(self):
        new_obj = copy.deepcopy(self)
        new_obj.data += 1
        return new_obj

obj = ClassToWrap(0)
wr0 = Wrapped(obj)
print wr0.data
>> 0
wr1 = wr0.theirfun()
print wr1.data
>> 1
wr2 = wr1.myfun()
print wr2.data
>> 2
wr3 = wr2.theirfun()
print wr3.data
>> 2

为什么 theirfun() 第一次有效,第二次却不行? wr0wr2 都是 Wrapped 类型,调用 wr2.theirfun() 不会引发错误,但它不会按预期将 1 添加到 wr2.data

抱歉,我不是寻找以下替代方法:

  1. 猴子补丁。我的代码库很重要,我不知道如何确保补丁将通过 import 语句的网络传播。
  2. 为每个第三方包的所有这些棘手方法编写单独的包装器方法。他们太多了。

ETA:有几个有用的答案引用了 Wrapper class 之外的基础 _obj 属性。但是,这种方法的重点是可扩展性,因此此功能需要在 Wrapper class 内。 myfun 需要在其定义中不引用 _obj 的情况下按预期运行。

问题在于您在 Wrapped class 中实施 myfun。您只更新 class' 实例的 data 成员,但包装的 class'(ClassToWrap 实例即 _objdata 成员是已过时,使用来自 theirfun 的上一次调用的值。

您需要在两个实例之间同步数据值:

class Wrapper(object):
    ...
    def __setattr__(self, attr, val):
        object.__setattr__(self, attr, val)
        if getattr(self._obj, attr, self._obj) is not self._obj: # update _obj's member if it exists
            setattr(self._obj, attr, getattr(self, attr))


class Wrapped(Wrapper):
    ...
    def myfun(self):
        new_obj = copy.deepcopy(self)
        new_obj.data += 1
        return new_obj

obj = ClassToWrap(0)
wr0 = Wrapped(obj)
print wr0.data
# 0
wr1 = wr0.theirfun()
print wr1.data
# 1
wr2 = wr1.myfun()
print wr2.data
# 2
wr3 = wr2.theirfun()
print wr3.data
# 3
wr4 = wr3.myfun()
print wr4.data
# 4
wr5 = wr4.theirfun()
print wr5.data
# 5

问题出在 myfun 中的作业 new_obj.data += 1。它的问题是 new_objWrapped 的实例,而不是 ClassToWrap 的实例。您的 Wrapper 基础 class 仅支持查找代理对象的属性。它不支持对属性的分配。扩充赋值两者兼而有之,因此它不能完全正确地工作。

您可以通过稍微改变一下来让您的 myfun 工作:

def myfun(self):
    new_obj = copy.deepcopy(self._obj)
    new_obj.data += 1
    return self.__class__(new_obj) # alternative spelling: return type(self)(new_obj)

另一种解决问题的方法是向 Wrapper 添加一个 __setattr__ 方法,但要让它正常工作(不干扰代理 class 自己的属性) 会有点尴尬。

与您当前的问题无关,您还可能在 hooked 包装器函数中泄漏包装对象。如果您调用的方法 return 是调用它的对象(例如,该方法执行 return self),那么您当前正在 return 解包该对象。您可能想将 return result 更改为 return self 以便 return 当前包装器。您可能还需要检查 return 值以查看它是否是您能够包装的类型。当前,如果方法 return 是字符串或数字或包装类型实例以外的任何其他内容,您的代码将失败。

        def hooked(*args, **kwargs):
            result = orig_attr(*args, **kwargs)
            if result == self._obj:
                return self                            # fix for leaking objects
            elif isisntance(result, self.__wraps__): # new type check
                return self.__class__(result)
            else:
                return result              # fallback for non-wrappable return values