如何在Python中模拟输入
我最近在Python中开发了一个叫做 DocumentWrapper
的类,这个类是围绕某个ORM文档对象创建的,目的是在不改变它的接口的情况下,透明地为它添加一些功能。
不过我遇到了一个问题。假设我有一个被这个类包装的 User
对象。当我调用 isinstance(some_var, User)
时,它会返回 False
,因为 some_var
实际上是 DocumentWrapper
的一个实例。
有没有办法在Python中“伪装”一个对象的类型,让这个调用返回 True
呢?
6 个回答
你可以使用 __instancecheck__
这个特殊的方法来改变默认的 isinstance
行为:
@classmethod
def __instancecheck__(cls, instance):
return isinstance(instance, User)
这只有在你想让你的对象成为一个 透明 的包装器时才需要;也就是说,如果你希望 DocumentWrapper
的表现像 User
一样。否则,你只需将被包装的类作为一个属性暴露出来。
这是 Python 3 的新增功能;它是和抽象基类一起引入的。在 Python 2 中你无法做到这一点。
在你的包装类 DocumentWrapper
中重写 __class__
:
class DocumentWrapper(object):
@property
def __class__(self):
return User
>>> isinstance(DocumentWrapper(), User)
True
这样就不需要对被包装的类 User
进行任何修改了。
Python 的 Mock 也做了类似的事情(可以查看 mock.py 文件的第612行,版本是 mock-2.0.0,网上找不到相关的链接,抱歉)。
在Python中,检查一个对象的类型通常被认为是一种反模式。这意味着大多数情况下这样做并不好。不过在某些情况下,检查对象的“鸭子类型”是有意义的,简单来说就是:
hasattr(some_var, "username")
但即使这样做也不太理想,比如有一些原因可能导致这个表达式返回错误,即使一个包装器使用了一些魔法,比如__getattribute__
来正确地代理这个属性。
通常来说,最好让变量只接受一种抽象类型,可能还包括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()”返回真,类必须出现在实例的基类中,或者类必须有一个__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