【问题标题】:Wrapping a class whose methods return instances of that class包装一个类,其方法返回该类的实例
【发布时间】:2017-08-11 20:18:48
【问题描述】:

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

基于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. 猴子补丁。我的代码库很重要,我不知道如何确保补丁能够通过导入语句的网络传播。
  2. 为每个第三方包的所有这些棘手的方法编写单独的包装方法。它们太多了。

ETA:有几个有用的答案引用了 _obj 类之外的基础 _obj 属性。然而,这种方法的重点是可扩展性,所以这个功能需要在Wrapper 类中。 myfun 需要按预期运行,而不在其定义中引用 _obj

【问题讨论】:

    标签: python python-2.7 wrapper


    【解决方案1】:

    问题在于您在Wrapped 类中实现myfun。您只更新了类实例的data 成员,但包装类(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
    

    【讨论】:

    • 感谢这个有用的答案!它确实让我走到了那里,但请参阅对原始问题的编辑 - 我显然没有说得足够清楚。
    • 我的代码只是为了显示出了什么问题并进行了简单的修复。不一定适用于生产。您可以将_objdata 成员的更新移动到Wrapper 类中的静态方法中,并使用静态方法装饰myfun 以强制data 跨包装器和包装实例同步。也可能有其他更好的方法。
    • 不幸的是,在这种情况下我也不能依赖装饰器...myfun 必须自己工作。
    • @DaveKielpinski 查看__setattr__ 中的示例实现。
    • @DaveKielpinski 快速一个,我用getattr而不是hasattr更新了代码,因为后者不适用于Python2中的属性。见hynek.me/articles/hasattr
    【解决方案2】:

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

    您可以通过稍微改变一下 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)
    

    解决此问题的另一种方法是将__setattr__ 方法添加到Wrapper,但让它正常工作(不干扰代理类自身的属性)会有点尴尬。

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

            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
    

    【讨论】:

    • @Blckknght感谢您提供有用的答案!它确实让我走到了那里,但请参阅对原始问题的编辑 - 我显然没有说得足够清楚。
    • 我将if result == self.obj: 修改为if result is self.obj。包装 numpy 数组时,值相等测试给出了不好的结果。希望这不会破坏您的代码?
    • 哦,我只是从你的代码中复制了那部分逻辑,没有太多考虑。在这种情况下,使用is 几乎总是更合适,因为您确实希望包装由方法返回的对象的新副本。请注意,代理数组之类的对象所面临的一大挑战是运算符方法(如__add__)不会使用__getattr__,而是直接在类中查找它们。您需要编写自己版本的 every 操作符,底层对象可能实现。您可能想检查使用继承是否会更容易。
    • 如果我可以使用继承,我当然会。显然,我正在使用的非常知名的第三方包是故意以 not 的方式编写的,以支持继承。
    猜你喜欢
    • 1970-01-01
    • 2013-11-16
    • 1970-01-01
    • 1970-01-01
    • 2020-09-20
    • 1970-01-01
    • 2015-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多