关于python:包装一个类,该类的方法返回该类的实例

Wrapping a class whose methods return instances of that class

我需要编写一个类来包装来自第三方包的类。通常,第三方类具有返回第三方类实例的方法。这些方法的包装版本必须将这些实例转换为包装类的实例,但我无法使其工作。我正在将python 2.7与新样式的类一起使用。

基于创建一个包装类来围绕现有函数调用pre和post函数?,我有以下内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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都是包装类型,调用wr2.theirfun()不会产生错误,但不会像预期的那样向wr2.data添加1。

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

  • 猴子修补。我的代码库非常重要,我不知道如何确保补丁可以通过导入语句的Web进行传播。
  • 为每个第三方包的所有这些棘手方法编写单独的包装方法。它们太多了。
  • eta:有几个有用的答案引用了Wrapper类之外的基础_obj属性。但是,这种方法的要点是可扩展性,因此该功能需要在Wrapper类中。myfun的定义中不需要引用_obj而需要按预期行事。


    问题出在myfun中的分配new_obj.data += 1上。问题在于,new_objWrapped的实例,而不是ClassToWrap的实例。您的Wrapper基类只支持查找代理对象的属性。它不支持属性的赋值。增广的分配可以同时完成这两个任务,因此它不能完全正确地工作。

    你可以把你的EDOCX1[0]稍微改变一下:

    1
    2
    3
    4
    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__方法,但是让它正常工作(不干扰代理类自身的属性)会有点尴尬。

    与您当前的问题无关,您还可能泄漏hooked包装器函数中的包装对象。如果您调用的方法返回它被调用的对象(例如,该方法执行了return self),则当前返回的是未包装的对象。您可能希望将return result更改为return self,以便返回当前包装。您可能还需要检查返回值,看看它是否是您能够包装的类型。当前,如果一个方法返回一个字符串或数字或除包装类型的实例以外的任何内容,则代码将失败。

    1
    2
    3
    4
    5
    6
    7
    8
            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


    问题在于您在Wrapped类中实现了myfun。您只更新类"instance"的data成员,但使用来自theirfun的前一个调用的值,包装类(ClassToWrap实例,即_objdata成员已过时。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    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