Skip to content Skip to sidebar Skip to footer

Adding Methods To An Existing Class Instance, Or How To "subclass" An Instance

I'm using a package that gives me an object filled with a bunch of data that I don't want to bother to manually serialize and use to initialize another object. What I want to do is

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"