【问题标题】:How to fake type with Python如何用 Python 伪造类型
【发布时间】:2011-07-23 22:04:16
【问题描述】:

我最近在 Python 中围绕一些 ORM 文档对象开发了一个名为 DocumentWrapper 的类,以透明地为其添加一些功能,而无需以任何方式更改其接口。

我只有一个问题。假设我有一些 User 包含在其中的对象。调用isinstance(some_var, User) 将返回False,因为some_var 确实是DocumentWrapper 的一个实例。

有没有办法在 Python 中伪造对象的类型以具有相同的调用返回 True

【问题讨论】:

  • isinstance(some_var.user, User)?你到底想做什么?
  • 只是想拥有一个透明的包装器,它的行为完全类似于被包装的类。包括 isinstance。多重继承不是解决方案,至少因为 User 只是 DocumentWrapper 包装的众多类之一。 (我无法控制这些类,我无法更改它们的继承树。)

标签: python class types wrapper


【解决方案1】:

您可以使用__instancecheck__ 魔术方法来覆盖默认的isinstance 行为:

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

仅当您希望您的对象成为 透明 包装器时;也就是说,如果你想让DocumentWrapper 表现得像User。否则,只需将包装的类作为属性公开即可。

这是 Python 3 的补充;它带有抽象基类。你不能在 Python 2 中做同样的事情。

【讨论】:

【解决方案2】:

在你的包装类DocumentWrapper中覆盖__class__

class DocumentWrapper(object):

  @property
  def __class__(self):
    return User

>>> isinstance(DocumentWrapper(), User)
True

这种方式不需要对包装类 User 进行修改。

Python Mock 也是如此(请参阅 mock-2.0.0 中的 mock.py:612,无法在线找到可链接的资源,抱歉)。

【讨论】:

    【解决方案3】:

    测试对象的类型 通常是python 中的一种反模式。在某些情况下,测试对象的 “鸭子类型” 是有意义的,例如:

    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,因为该值存在于类创建时间

    编辑:正如 cmets 中所指出的,以上仅适用于一些简单类型,如果您需要在目标对象上代理更精细的属性(例如方法),请参阅以下答案:Python - Faking Type Continued

    【讨论】:

    • 非常感谢您的宝贵帮助 =)
    • 为什么(看似多余的)__metaclass__ = type 是必要的?
    • 您已经有效地对descriptor protocol 进行了绝育,例如wrap_user(obj).extra_feature() 返回一个未绑定的方法。您至少需要检查从self_dict 检索的对象上的__get__ 方法。有关某人提出的后续问题,请参阅 Python - Faking Type Continued
    • wrapped_user() 的调用似乎返回None,这是有意的吗?
    • @oarfish:当然不是。空的 __new__ 是错误的,只是删除它可以改善事情,但我不确定 7 年前我试图与那个例子交流什么......
    【解决方案4】:

    这里是使用元类的解决方案,但是你需要修改包装的类:

    >>> 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
    

    【讨论】:

    • 大灯泡,你需要修改封装的类。所以你只能“伪造”你自己的类型,例如不是字符串或整数。
    【解决方案5】:

    听起来您想测试 DocumentWrapper 包装的对象的类型,而不是 DocumentWrapper 本身的类型。如果这是正确的,那么DocumentWrapper 的接口需要公开该类型。例如,您可以向 DocumentWrapper 类添加一个方法,该方法返回包装对象的类型。但我不认为对 isinstance 模棱两可的调用,通过让它在不是的时候返回 True,是解决这个问题的正确方法。

    【讨论】:

      【解决方案6】:

      最好的方法是从 User 本身继承 DocumentWrapper,或者混入模式并从多个类中进行多重继承

       class DocumentWrapper(User, object)
      

      您也可以通过操纵 obj.__class__ 来伪造 isinstance() 结果,但这是深层次的魔法,不应该这样做。

      【讨论】:

      • 谢谢。用户不是包装的唯一文档类型,因此不幸的是,这不起作用。但是谢谢,我什至不知道 Python 可以实现多重继承 =)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-06
      • 2010-11-15
      • 1970-01-01
      • 2014-05-16
      相关资源
      最近更新 更多