如何在Python中模拟输入

27 投票
6 回答
15565 浏览
提问于 2025-04-16 22:09

我最近在Python中开发了一个叫做 DocumentWrapper 的类,这个类是围绕某个ORM文档对象创建的,目的是在不改变它的接口的情况下,透明地为它添加一些功能。

不过我遇到了一个问题。假设我有一个被这个类包装的 User 对象。当我调用 isinstance(some_var, User) 时,它会返回 False,因为 some_var 实际上是 DocumentWrapper 的一个实例。

有没有办法在Python中“伪装”一个对象的类型,让这个调用返回 True 呢?

6 个回答

19

你可以使用 __instancecheck__ 这个特殊的方法来改变默认的 isinstance 行为:

@classmethod
def __instancecheck__(cls, instance):
    return isinstance(instance, User)

这只有在你想让你的对象成为一个 透明 的包装器时才需要;也就是说,如果你希望 DocumentWrapper 的表现像 User 一样。否则,你只需将被包装的类作为一个属性暴露出来。

这是 Python 3 的新增功能;它是和抽象基类一起引入的。在 Python 2 中你无法做到这一点。

20

在你的包装类 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,网上找不到相关的链接,抱歉)。

10

在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

撰写回答