创建/模仿不可变内置类型

时间:2015-12-10 09:08:37

标签: python metaclass

问题:

我已经实现了一个具有相当复杂的内部行为的类,它假装为int类型,用于所有意图和目的。然后,作为一个樱桃,我真的希望我的类成功通过isinstance()和issubclass()检查int。到目前为止我失败了。

这是一个我用来测试这个概念的小型演示类。我尝试从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通常会使用基类原始值而不是我精心设计的魔术方法。不好。

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

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

尝试从(int,dict)继承我的类并使用dict.__new__()并不是很成功,因为Python apparenty不允许将它们组合在一个类中。

怀疑可能会在元类中找到解决方案,但到目前为止还没有成功使用它们,主要是因为我的大脑根本没有弯曲到足以理解它们。我还在尝试,但看起来我不会很快得到结果。

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

3 个答案:

答案 0 :(得分:4)

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

我宁愿在这里简单地欺骗isinstance。我只是将DemoClass的{​​{1}}子类和隐藏自定义函数后面的内置object

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)

答案 1 :(得分:0)

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

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

您需要致电i_want_int(DemoClass()),其中DemoClass可通过int方式转换为__int__

如果你想继承int,实例'值在创建时确定。

如果您不想在任何地方(int)编写转化为i_want_int(int(DemoClass())),我可以考虑的最简单方法是为i_want_int定义包装器,进行转换:

def i_want_something_intlike(intlike):
    return i_want_int(int(intlike))

答案 2 :(得分:0)

到目前为止,还没有提出替代解决方案,所以这就是我最终使用的解决方案(松散地基于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

通过向honestisinstance()issubclass()来电提供可选的type()参数,可以忽略伪造的继承。

用法示例。

让班级B成为班级A的假继承人:

class A(object): pass
class B(object): pass
forge_inheritances(disguise_heir = { B: A })
b = B()
print isinstance(b, A) # prints True
print isinstance(b, A, honest = True) # prints False

让课程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

显然,这个hack不会以任何方式影响super()调用和属性/方法继承,这里的主要目的只是在你的情况下欺骗isinstance()type(inst) == class检查无法直接修复它们。