我最近在Python中的一些ORM文档对象周围开发了一个名为DocumentWrapper
的类,以透明地向其添加一些功能,而无需以任何方式更改其接口。
我只有一个问题。假设我有一些User
对象包含在其中。调用isinstance(some_var, User)
将返回False
,因为some_var
确实是DocumentWrapper
的实例。
有没有办法伪造Python中对象的类型以使相同的调用返回True
?
答案 0 :(得分:13)
您可以使用__instancecheck__
魔术方法覆盖默认的isinstance
行为:
@classmethod
def __instancecheck__(cls, instance):
return isinstance(instance, User)
仅当您希望对象成为透明包装时;也就是说,如果您希望DocumentWrapper
的行为类似于User
。否则,只需将包装的类作为属性公开。
这是Python 3的补充;它带有抽象基类。你不能在Python 2中做同样的事。
答案 1 :(得分:7)
测试对象的类型通常是python中的反模式。在某些情况下,测试对象的“duck type”是有意义的,例如:
hasattr(some_var, "username")
但即使这样也是不可取的,例如,即使包装器使用__getattribute__
的一些魔法来正确代理属性,也有可能返回false的原因。
通常首选允许变量只采用一种抽象类型,可能只需None
。应该通过将可选类型的数据传递到不同的变量来实现基于不同输入的不同行为。你想做这样的事情:
def dosomething(some_user=None, some_otherthing=None):
if some_user is not None:
#do the "User" type action
elif some_otherthing is not None:
#etc...
else:
raise ValueError("not enough arguments")
当然,这一切都假设您对进行类型检查的代码有一定程度的控制。假设不是。要使“isinstance()”返回true,该类必须出现在实例的基础中,或者该类必须具有__instancecheck__
。由于您不为该类控制这些内容,您必须在实例上使用一些恶作剧。做这样的事情:
def wrap_user(instance):
class wrapped_user(type(instance)):
__metaclass__ = type
def __init__(self):
pass
def __getattribute__(self, attr):
self_dict = object.__getattribute__(type(self), '__dict__')
if attr in self_dict:
return self_dict[attr]
return getattr(instance, attr)
def extra_feature(self, foo):
return instance.username + foo # or whatever
return wrapped_user()
我们正在做的是在我们需要包装实例时动态创建一个新类,并实际上从包装对象的__class__
继承。如果原始版本有一些我们实际上不想遇到的额外行为(比如查找具有某个类名的数据库表),我们还会覆盖覆盖__metaclass__
的额外麻烦。这种风格的一个很好的便利是我们永远不必在包装类上创建任何实例属性,没有self.wrapped_object
,因为该值存在于类创建时。
编辑:正如评论中所指出的,上面只适用于某些简单类型,如果你需要在目标对象上代理更精细的属性(比如方法),那么请看下面的答案:Python - Faking Type Continued
答案 2 :(得分:6)
在您的包装类DocumentWrapper
中覆盖class DocumentWrapper(object):
@property
def __class__(self):
return User
>>> isinstance(DocumentWrapper(), User)
True
:
User
这样就不需要修改包装的类SELECT * from attendance_record as a inner join session as s
on a.session_id = s.session_id
inner join student as st
on a.student_id = st.s_id
。
Python Mock也是这样做的(请参阅mock-2.0.0中的mock.py:612,找不到在线链接的来源,抱歉)。
答案 3 :(得分:1)
以下是使用元类的解决方案,但您需要修改包装类:
>>> class DocumentWrapper:
def __init__(self, wrapped_obj):
self.wrapped_obj = wrapped_obj
>>> class MetaWrapper(abc.ABCMeta):
def __instancecheck__(self, instance):
try:
return isinstance(instance.wrapped_obj, self)
except:
return isinstance(instance, self)
>>> class User(metaclass=MetaWrapper):
pass
>>> user=DocumentWrapper(User())
>>> isinstance(user,User)
True
>>> class User2:
pass
>>> user2=DocumentWrapper(User2())
>>> isinstance(user2,User2)
False
答案 4 :(得分:0)
听起来你想要测试DocumentWrapper
包裹的对象的类型,而不是DocumentWrapper
本身的类型。如果这是正确的,那么DocumentWrapper
的接口需要公开该类型。例如,您可以向DocumentWrapper
类添加一个返回包装对象类型的方法。但是我不认为调用isinstance
是不明确的,通过让它返回True,当它不是时,是解决这个问题的正确方法。
答案 5 :(得分:0)
最好的方法是从用户本身继承DocumentWrapper,或者从多个类中混合使用模式并进行多重继承
class DocumentWrapper(User, object)
你也可以通过操纵obj.__class__
伪造isinstance()结果,但这是一种深层次的魔法,不应该这样做。