向现有类实例添加方法,或如何“子类化”实例

2024-09-29 22:26:56 发布

您现在位置:Python中文网/ 问答频道 /正文

我使用的是一个包,它给了我一个对象,里面装满了一堆数据,我不想费心手动序列化这些数据并用来初始化另一个对象。我想做的是为我自己的目的在对象上附加一堆额外的方法。在

理想情况下,我希望神奇地将实例子类化,但这似乎不可能。Monkey-patching可能“有用”,但互联网上说这不是什么好形式,因为我的代码的其他部分实际上可能会在其他地方使用本机类,这看起来很危险。在

我尝试创建一个包装器对象,但是很多(全部?),所以它是不完整的。处理一堆传递函数定义(例如def __iter__(self): return iter(object.__getattribute__(self, '_digraph')))似乎很笨拙(我可能会忘记一个)。在

class ColliderGraph(object):
    def __init__(self, digraph):
        self._digraph = digraph

    def __getattribute__(self, name):
        cls_attrs = ['_digraph', 'whereis', 'copy'] # more to follow.
        if name not in cls_attrs:
            return object.__getattribute__(
                    object.__getattribute__(self, '_digraph'), name)
        else:
            return object.__getattribute__(self, name)
        #return getattr(self._digraph, name)

    def whereis(self, node):
        """find a node inside other nodes of the digraph"""

    def copy(self):
        return ColliderGraph(self._digraph.copy())

在另一个更有限的地方,我开始使用一个单数函数patch the instance,如下所示:

^{pr2}$

但是如果调用.copy(),那么它就失去了升级(我想我也可以修补它……),并且以这种方式添加一堆方法似乎很难看,但也许是可行的。在

有更好的出路吗?在


Tags: 数据对象方法nameselfreturnobjectdef
1条回答
网友
1楼 · 发布于 2024-09-29 22:26:56

首先,我认为最明智的选择是修补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__复制到实例中。这听起来相当疯狂,也许是,但在我用它做的一点测试中,它似乎(大部分)实际起作用:

^{pr2}$

输出:

<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类。相反,我们创建了一个具有相同名称和所有相同方法/属性的单独的类。理想情况下,这可以通过在retype_instance内重新定义MagicThread__bases__来解决,但Python不允许这样做:

TypeError: __bases__ assignment: 'Thread' deallocator differs from 'object'

这似乎是一个可以追溯到2003年的bug in Python。它还没有被修复,可能是因为在一个实例上动态地重新定义__bases__是一个奇怪的想法,而且可能是个坏主意!在

现在,如果您不关心能否使用isinstance(obj, ColliderGraph),那么上面的方法可能对您有用。或者它可能会以奇怪的、意想不到的方式失败。我真的不建议在任何生产代码中使用它,但我想我会分享它。在

相关问题 更多 >

    热门问题