Create/imitate 不可变内置类型的可变子类
Create/imitate mutable subclass of immutable built-in type
问题:
我实现了一个具有相当复杂内部行为的 class,它伪装成一个 int
类型用于所有意图和目的。然后,作为最重要的樱桃,我真的希望我的 class 成功通过 isinstance() 和 issubclass() 检查 int
。到目前为止我失败了。
这是我用来测试概念的一个小演示 class。我尝试从 object
和 int
继承它,虽然从 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)
本质上,从不可变的 class 继承会导致不可变的 class,并且 Python 将经常使用基础 class 原始值而不是我精心设计的魔法方法。不好。
我看过抽象基 classes,但他们似乎在做完全相反的事情:而不是让我的 class 看起来像一个内置的子class-在类型上,他们让一个 class 假装是一个超级 class 到一个。
使用 __new__(cls, ...)
似乎也不是解决方案。如果您只想在实际创建对象之前修改对象起始值,那很好,但我想规避 不变性诅咒。尝试使用 object.__new__()
也没有结果,因为 Python 只是抱怨说使用 object.__new__
来创建一个 int
对象是不安全的。
尝试从 (int, dict) 继承我的 class 并使用 dict.__new__()
也不是很成功,因为 Python 显然不允许将它们组合成一个 class.
我 怀疑 可以使用 metaclasses 找到解决方案,但到目前为止也没有成功,主要是因为我的大脑根本就没有'不够弯曲以正确理解它们。我还在努力,但看起来我不会很快得到结果。
所以,问题是:即使我的 class 非常可变,是否有可能从不可变类型继承或模仿继承? Class 只要找到解决方案(假设它存在),继承结构对我来说并不重要。
这里的问题不是不变性,而是简单的继承。如果 DemoClass
是 int 的子类,则为每个 DemoClass
类型的对象构造一个真正的 int
并将直接使用而无需调用 __int__
任何可以使用 int 的地方,试试 a + 2
.
我宁愿在这里尝试简单地作弊 isinstance
。我只想让 DemoClass
成为 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)
然后我可以做:
>>> a = DemoClass(3)
init() called
>>> isinstance("abc", str)
True
>>> isinstance(a, DemoClass)
True
>>> isinstance(a, int)
True
>>> issubclass(DemoClass, int)
False
所以,如果我理解正确的话,你有:
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))
到目前为止,还没有提出替代解决方案,所以这是我最后使用的解决方案(大致基于 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
参数来忽略伪造的继承。
使用示例。
让 class B
成为 class 的假继承人 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
让classB
假装成为classA
:
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'>"
让classB
和继承人伪装成classA
:
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()
调用和 attribute/method 继承,这里的主要目的只是欺骗 isinstance()
和 type(inst) == class
签入无法直接修复它们的情况。
问题:
我实现了一个具有相当复杂内部行为的 class,它伪装成一个 int
类型用于所有意图和目的。然后,作为最重要的樱桃,我真的希望我的 class 成功通过 isinstance() 和 issubclass() 检查 int
。到目前为止我失败了。
这是我用来测试概念的一个小演示 class。我尝试从 object
和 int
继承它,虽然从 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)
本质上,从不可变的 class 继承会导致不可变的 class,并且 Python 将经常使用基础 class 原始值而不是我精心设计的魔法方法。不好。
我看过抽象基 classes,但他们似乎在做完全相反的事情:而不是让我的 class 看起来像一个内置的子class-在类型上,他们让一个 class 假装是一个超级 class 到一个。
使用 __new__(cls, ...)
似乎也不是解决方案。如果您只想在实际创建对象之前修改对象起始值,那很好,但我想规避 不变性诅咒。尝试使用 object.__new__()
也没有结果,因为 Python 只是抱怨说使用 object.__new__
来创建一个 int
对象是不安全的。
尝试从 (int, dict) 继承我的 class 并使用 dict.__new__()
也不是很成功,因为 Python 显然不允许将它们组合成一个 class.
我 怀疑 可以使用 metaclasses 找到解决方案,但到目前为止也没有成功,主要是因为我的大脑根本就没有'不够弯曲以正确理解它们。我还在努力,但看起来我不会很快得到结果。
所以,问题是:即使我的 class 非常可变,是否有可能从不可变类型继承或模仿继承? Class 只要找到解决方案(假设它存在),继承结构对我来说并不重要。
这里的问题不是不变性,而是简单的继承。如果 DemoClass
是 int 的子类,则为每个 DemoClass
类型的对象构造一个真正的 int
并将直接使用而无需调用 __int__
任何可以使用 int 的地方,试试 a + 2
.
我宁愿在这里尝试简单地作弊 isinstance
。我只想让 DemoClass
成为 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)
然后我可以做:
>>> a = DemoClass(3)
init() called
>>> isinstance("abc", str)
True
>>> isinstance(a, DemoClass)
True
>>> isinstance(a, int)
True
>>> issubclass(DemoClass, int)
False
所以,如果我理解正确的话,你有:
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))
到目前为止,还没有提出替代解决方案,所以这是我最后使用的解决方案(大致基于 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
参数来忽略伪造的继承。
使用示例。
让 class B
成为 class 的假继承人 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
让classB
假装成为classA
:
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'>"
让classB
和继承人伪装成classA
:
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()
调用和 attribute/method 继承,这里的主要目的只是欺骗 isinstance()
和 type(inst) == class
签入无法直接修复它们的情况。