首先,我认为最明智的选择是修补digraph 实例以添加您需要的方法,并在其中包括修补__copy__,或者甚至坚持使用您的包装器,并使用元类为魔术方法,正如this answer 中建议的那样,您链接到的问题。
也就是说,我最近正在玩弄“神奇地”子类化实例的想法,并想我会与您分享我的发现,因为您正在玩弄同样的事情。这是我想出的代码:
def retype_instance(recvinst, sendtype, metaklass=type):
""" Turn recvinst into an instance of sendtype.
Given an instance (recvinst) of some class, turn that instance
into an instance of class `sendtype`, which inherits from
type(recvinst). The output instance will still retain all
the instance methods and attributes it started with, however.
For example:
Input:
type(recvinst) == Connection
sendtype == AioConnection
metaklass == CoroBuilder (metaclass used for creating AioConnection)
Output:
recvinst.__class__ == AioConnection
recvinst.__bases__ == bases_of_AioConnection +
Connection + bases_of_Connection
"""
# Bases of our new instance's class should be all the current
# bases, all of sendtype's bases, and the current type of the
# instance. The set->tuple conversion is done to remove duplicates
# (this is required for Python 3.x).
bases = tuple(set((type(recvinst),) + type(recvinst).__bases__ +
sendtype.__bases__))
# We change __class__ on the instance to a new type,
# which should match sendtype in every where, except it adds
# the bases of recvinst (and type(recvinst)) to its bases.
recvinst.__class__ = metaklass(sendtype.__name__, bases, {})
# This doesn't work because of http://bugs.python.org/issue672115
#sendtype.__bases__ = bases
#recv_inst.__class__ = sendtype
# Now copy the dict of sendtype to the new type.
dct = sendtype.__dict__
for objname in dct:
if not objname.startswith('__'):
setattr(type(recvinst), objname, dct[objname])
return recvinst
想法是重新定义实例的__class__,将其更改为我们选择的新类,并将__class__的原始值添加到inst.__bases__(连同新的__bases__类型)。此外,我们将新类型的__dict__ 复制到实例中。这听起来很疯狂,而且可能确实如此,但在我用它做的一点点测试中,它似乎(大部分)确实有效:
class MagicThread(object):
def magic_method(self):
print("This method is magic")
t = Thread()
m = retype_instance(t, MagicThread)
print m.__class__
print type(m)
print type(m).__mro__
print isinstance(m, Thread)
print dir(m)
m.magic_method()
print t.is_alive()
print t.name
print isinstance(m, MagicThread)
输出:
<class '__main__.MagicThread'>
<class '__main__.MagicThread'>
(<class '__main__.MagicThread'>, <class 'threading.Thread'>, <class 'threading._Verbose'>, <type 'object'>)
True
['_Thread__args', '_Thread__block', '_Thread__bootstrap', '_Thread__bootstrap_inner', '_Thread__daemonic', '_Thread__delete', '_Thread__exc_clear', '_Thread__exc_info', '_Thread__ident', '_Thread__initialized', '_Thread__kwargs', '_Thread__name', '_Thread__started', '_Thread__stderr', '_Thread__stop', '_Thread__stopped', '_Thread__target', '_Verbose__verbose', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_block', '_note', '_reset_internal_locks', '_set_daemon', '_set_ident', 'daemon', 'getName', 'ident', 'isAlive', 'isDaemon', 'is_alive', 'join', 'magic_method', 'name', 'run', 'setDaemon', 'setName', 'start']
This method is magic
False
Thread-1
False
除了最后一行 - isinstance(m, MagicThread) 是 False 之外,所有输出都完全符合我们的要求。这是因为我们实际上并没有将__class__ 分配给我们定义的MagicMethod 类。相反,我们创建了一个具有相同名称和所有相同方法/属性的 separate 类。理想情况下,这可以通过在retype_instance 中重新定义MagicThread 的__bases__ 来解决,但Python 不允许这样做:
TypeError: __bases__ assignment: 'Thread' deallocator differs from 'object'
这似乎是一个可以追溯到 2003 年的 bug in Python。它还没有得到修复,可能是因为在实例上动态重新定义 __bases__ 是一个奇怪且可能是坏主意!
现在,如果您不关心能否使用isinstance(obj, ColliderGraph),上述方法可能对您有用。或者它可能会以奇怪的、意想不到的方式失败。我真的不建议在任何生产代码中使用它,但我想我会分享它。