包装一个 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()
第一次有效,第二次却不行? wr0
和 wr2
都是 Wrapped 类型,调用 wr2.theirfun()
不会引发错误,但它不会按预期将 1 添加到 wr2.data
。
抱歉,我不是寻找以下替代方法:
- 猴子补丁。我的代码库很重要,我不知道如何确保补丁将通过 import 语句的网络传播。
- 为每个第三方包的所有这些棘手方法编写单独的包装器方法。他们太多了。
ETA:有几个有用的答案引用了 Wrapper
class 之外的基础 _obj
属性。但是,这种方法的重点是可扩展性,因此此功能需要在 Wrapper
class 内。 myfun
需要在其定义中不引用 _obj
的情况下按预期运行。
问题在于您在 Wrapped
class 中实施 myfun
。您只更新 class' 实例的 data
成员,但包装的 class'(ClassToWrap
实例即 _obj
)data
成员是已过时,使用来自 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_obj
是 Wrapped
的实例,而不是 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
我需要写一个 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()
第一次有效,第二次却不行? wr0
和 wr2
都是 Wrapped 类型,调用 wr2.theirfun()
不会引发错误,但它不会按预期将 1 添加到 wr2.data
。
抱歉,我不是寻找以下替代方法:
- 猴子补丁。我的代码库很重要,我不知道如何确保补丁将通过 import 语句的网络传播。
- 为每个第三方包的所有这些棘手方法编写单独的包装器方法。他们太多了。
ETA:有几个有用的答案引用了 Wrapper
class 之外的基础 _obj
属性。但是,这种方法的重点是可扩展性,因此此功能需要在 Wrapper
class 内。 myfun
需要在其定义中不引用 _obj
的情况下按预期运行。
问题在于您在 Wrapped
class 中实施 myfun
。您只更新 class' 实例的 data
成员,但包装的 class'(ClassToWrap
实例即 _obj
)data
成员是已过时,使用来自 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_obj
是 Wrapped
的实例,而不是 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