Adding Methods To An Existing Class Instance, Or How To "subclass" An Instance
Solution 1:
First, I think the sanest option would be to patch the digraph
instance to add the methods you need, and include patching __copy__
in that, or even stick with your wrapper, and use a metaclass to add proxies for magic methods, as suggested in this answer to the question you linked to.
That said, I was recently toying around with the idea of "magically" subclassing an instance, and figured I'd share my findings with you, since you're toying with the very same thing. Here's the code I came up with:
defretype_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:
ifnot objname.startswith('__'):
setattr(type(recvinst), objname, dct[objname])
return recvinst
The idea is to redefine the __class__
of the instance, changing it to be a new class of our choosing, and add the original value of __class__
to inst.__bases__
(along with the __bases__
of the new type). Additionally, we copy the __dict__
of the new type into the instance. This sounds fairly crazy and probably is, but in the little bit of testing I did with it, it seemed to (mostly) actually work:
classMagicThread(object):
defmagic_method(self):
print("This method is magic")
t = Thread()
m = retype_instance(t, MagicThread)
print m.__class__
printtype(m)
printtype(m).__mro__
printisinstance(m, Thread)
printdir(m)
m.magic_method()
print t.is_alive()
print t.name
printisinstance(m, MagicThread)
Output:
<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-1False
All of that output is exactly as we'd like, except for the last line - isinstance(m, MagicThread)
is False
. This is because we didn't actually assign __class__
to the MagicMethod
class we defined. Instead, we created a separate class with the same name and all the same methods/attributes. Ideally this could be addressed by actually redefining the __bases__
of MagicThread
inside retype_instance
, but Python won't allow this:
TypeError: __bases__ assignment: 'Thread' deallocator differs from'object'
This appears to be a bug in Python dating all the way back to 2003. It hasn't been fixed yet, probably because dynamically redefining __bases__
on an instance is a strange and probably bad idea!
Now, if you don't care about being able to use isinstance(obj, ColliderGraph)
, the above might work for you. Or it might fail in strange, unexpected ways. I really wouldn't recommend using this in any production code, but I figured I'd share it.
Post a Comment for "Adding Methods To An Existing Class Instance, Or How To "subclass" An Instance"