创建/模拟不可变内置类型的可变子类

2024-05-16 03:46:34 发布

您现在位置:Python中文网/ 问答频道 /正文

问题是:

我实现了一个具有相当复杂的内部行为的类,它在所有意图和目的中都假装是int类型。然后,作为最重要的一员,我真的希望我的类能够成功地通过int的isinstance()和issubclass()检查。到目前为止我失败了。在

这是一个小的演示类,我用它来测试这个概念。我尝试从objectint继承它,虽然从int继承它可以通过检查,但它也破坏了它的一些行为:

#class DemoClass(int):
class DemoClass(object):
    _value = 0
    def __init__(self, value = 0):
        print 'init() called'
        self._value = value
    def __int__(self):
        print 'int() called'
        return self._value + 2
    def __index__(self):
        print 'index() called'
        return self._value + 2
    def __str__(self):
        print 'str() called'
        return str(self._value + 2)
    def __repr__(self):
        print 'repr() called'
        return '%s(%d)' % (type(self).__name__, self._value)
    # overrides for other magic methods skipped as irrelevant

a = DemoClass(3)

print a         # uses __str__() in both cases
print int(a)    # uses __int__() in both cases
print '%d' % a  # __int__() is only called when inheriting from object

rng = range(10)
print rng[a]    # __index__() is only called when inheriting from object

print isinstance(a, int)
print issubclass(DemoClass, int)

本质上,从一个不可变的类继承会产生一个不可变的类,Python通常会使用基类raw value,而不是我精心设计的magic方法。不好的。在

我已经看过抽象基类,但它们似乎在做完全相反的事情:它们并没有使我的类看起来像一个内置类型的子类,而是让一个类假装成一个超类。在

使用__new__(cls, ...)似乎也不是一个解决方案。如果你想要的只是在实际创建对象之前修改它的起始值,这是很好的,但是我想逃避不变性诅咒。尝试使用object.__new__()也没有结果,因为Python只是抱怨使用object.__new__来创建int对象是不安全的。在

尝试从(int,dict)继承我的类并使用dict.__new__()也不太成功,因为Python apperenty不允许在单个类中组合它们。在

我怀疑可以用元类找到解决方法,但到目前为止也没有成功使用元类,主要是因为我的大脑不够弯曲,无法正确理解它们。我还在努力,但看起来我不会很快就有结果的。在

所以,问题是:即使我的类是非常可变的,是否有可能继承或模仿不可变类型的继承?只要找到解决方案(假设它存在),类继承结构对我来说并不重要。在


Tags: self类型newindexreturnobjectvaluedef
3条回答

到目前为止,还没有其他的解决方案被提出,所以下面是我最后使用的解决方案(大致基于Serge Ballesta的答案):

def forge_inheritances(disguise_heir = {}, disguise_type = {}, disguise_tree = {},
                       isinstance = None, issubclass = None, type = None):
    """
    Monkey patch isinstance(), issubclass() and type() built-in functions to create fake inheritances.

    :param disguise_heir: dict of desired subclass:superclass pairs; type(subclass()) will return subclass
    :param disguise_type: dict of desired subclass:superclass pairs, type(subclass()) will return superclass
    :param disguise_tree: dict of desired subclass:superclass pairs, type(subclass()) will return superclass for subclass and all it's heirs
    :param isinstance: optional callable parameter, if provided it will be used instead of __builtins__.isinstance as Python real isinstance() function.
    :param issubclass: optional callable parameter, if provided it will be used instead of __builtins__.issubclass as Python real issubclass() function.
    :param type: optional callable parameter, if provided it will be used instead of __builtins__.type as Python real type() function.
    """

    if not(disguise_heir or disguise_type or disguise_tree): return

    import __builtin__
    from itertools import chain

    python_isinstance = __builtin__.isinstance if isinstance is None else isinstance
    python_issubclass = __builtin__.issubclass if issubclass is None else issubclass
    python_type       = __builtin__.type if type is None else type

    def disguised_isinstance(obj, cls, honest = False):
        if cls == disguised_type: cls = python_type
        if honest:
            if python_isinstance.__name__ == 'disguised_isinstance':
                return python_isinstance(obj, cls, True)
            return python_isinstance(obj, cls)
        if python_type(cls) == tuple:
            return any(map(lambda subcls: disguised_isinstance(obj, subcls), cls))
        for subclass, superclass in chain(disguise_heir.iteritems(),
                                          disguise_type.iteritems(),
                                          disguise_tree.iteritems()):
            if python_isinstance(obj, subclass) and python_issubclass(superclass, cls):
                return True
        return python_isinstance(obj, cls)
    __builtin__.isinstance = disguised_isinstance

    def disguised_issubclass(qcls, cls, honest = False):
        if cls == disguised_type: cls = python_type
        if honest:
            if python_issubclass.__name__ == 'disguised_issubclass':
                return python_issubclass(qcls, cls, True)
            return python_issubclass(qcls, cls)
        if python_type(cls) == tuple:
            return any(map(lambda subcls: disguised_issubclass(qcls, subcls), cls))
        for subclass, superclass in chain(disguise_heir.iteritems(),
                                          disguise_type.iteritems(),
                                          disguise_tree.iteritems()):
            if python_issubclass(qcls, subclass) and python_issubclass(superclass, cls):
                return True
        return python_issubclass(qcls, cls)
    __builtin__.issubclass = disguised_issubclass

    if not(disguise_type or disguise_tree): return # No need to patch type() if these are empty

    def disguised_type(obj, honest = False, extra = None):
        if (extra is not None):
            # this is a call to create a type instance, we must not touch it
            return python_type(obj, honest, extra)
        if honest:
            if python_type.__name__ == 'disguised_type':
                return python_type(obj, True)
            return python_type(obj)
        for subclass, superclass in disguise_type.iteritems():
            if obj == subclass:
                return superclass
        for subclass, superclass in disguise_tree.iteritems():
            if python_isinstance(obj, subclass):
                return superclass
        return python_type(obj)
    __builtin__.type       = disguised_type

if __name__ == '__main__':
    class A(object): pass
    class B(object): pass
    class C(object): pass

    forge_inheritances(disguise_type = { C: B, B: A })

    print issubclass(B, A) # prints True
    print issubclass(C, B) # prints True
    print issubclass(C, A) # prints False - cannot link two fake inheritances without stacking

通过向isinstance()issubclass()type()调用提供可选的honest参数,可以忽略伪造的继承。在

使用示例。在

使类B成为A的假继承人:

^{pr2}$

使类B假装A

class A(object): pass
class B(object): pass
forge_inheritances(disguise_type = { B: A})
b = B()
print type(b) # prints "<class '__main__.A'>"
print type(b, honest = True) # prints "<class '__main__.B'>"

使类B所有它的继承者假装为类A

class A(object): pass
class B(object): pass
class D(B): pass
forge_inheritances(disguise_tree = { B: A})
d = D()
print type(d) # prints "<class '__main__.A'>"

通过堆叠对forge_inheritances()的调用,可以实现多层假继承:

class A(object): pass
class B(object): pass
class C(object): pass
forge_inheritance(disguise_heir = { B: A})
forge_inheritance(disguise_heir = { C: B})
c = C()
print isinstance(c, A) # prints True

显然,这种黑客攻击不会以任何方式影响super()调用和属性/方法继承,这里的主要目的只是在无法直接修复的情况下欺骗isinstance()和{}检查。在

这里的问题不是不变性,而是简单的继承。如果DemoClass是int的一个子类,则为DemoClass类型的每个对象构造一个真的int,并且将直接使用而不调用__int__在任何可以使用int的地方,只要尝试a + 2。在

我宁愿在这里简单地作弊。我只需要使objectDemoClass子类,并在自定义函数后面隐藏内置的isinstance

class DemoClass(object):
    ...

def isinstance(obj, cls):
    if __builtins__.isinstance(obj, DemoClass) and issubclass(int, cls):
        return True
    else:
        return __builtins__.isinstance(obj, cls)

然后我可以:

^{pr2}$

所以,如果我理解正确,你有:

def i_want_int(int_):
    # can't read the code; it uses isinstance(int_, int)

您需要调用i_want_int(DemoClass()),其中DemoClass可以通过__int__方法转换为{}。在

如果要子类int,实例的值在创建时确定。在

如果您不想在任何地方都编写对int的转换(比如i_want_int(int(DemoClass()))),那么我能想到的最简单的方法是为i_want_int定义包装,执行转换:

^{pr2}$

相关问题 更多 >