用 Python 完全包装一个对象

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1443129/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 22:15:11  来源:igfitidea点击:

Completely wrap an object in Python

pythonreflection

提问by Smashery

I am wanting to completely wrap an object so that all attribute and method requests get forwarded to the object it's wrapping, but also overriding any methods or variables that I want, as well as providing some of my own methods. This wrapper class should appear 100% as the existing class (isinstancemust act as if it is actually the class), however subclassing in itself is not going to cut it, as I want to wrap an existing object. Is there some solution in Python to do this? I was thinking something along the lines of:

我想完全包装一个对象,以便所有属性和方法请求都转发到它包装的对象,但也覆盖我想要的任何方法或变量,以及提供一些我自己的方法。这个包装类应该 100% 作为现有类出现(isinstance必须表现得好像它实际上是这个类),但是子类化本身并不会削减它,因为我想包装一个现有的对象。Python中是否有一些解决方案可以做到这一点?我在想一些事情:

class ObjectWrapper(BaseClass):
    def __init__(self, baseObject):
        self.baseObject = baseObject

    def overriddenMethod(self):
        ...

    def myOwnMethod1(self):
        ...

    ...

    def __getattr__(self, attr):
        if attr in ['overriddenMethod', 'myOwnMethod1', 'myOwnMethod2', ...]
            # return the requested method
        else:
            return getattr(self.baseObject, attr)

But I'm not that familiar with overriding __getattr__, __setattr__and __hasattr__, so I'm not sure how to get that right.

但是我对覆盖__getattr__, __setattr__and不太熟悉__hasattr__,所以我不确定如何正确使用。

回答by Alex Martelli

The simplest way in most cases is probably:

大多数情况下最简单的方法可能是:

class ObjectWrapper(BaseClass):
    def __init__(self, baseObject):
        self.__class__ = type(baseObject.__class__.__name__,
                              (self.__class__, baseObject.__class__),
                              {})
        self.__dict__ = baseObject.__dict__

    def overriddenMethod(self):
        ...

Working in this way, i.e. by reassigning self's __class__and __dict__in this fashion, you need only provide your overrides -- Python's normal attribute getting and setting mechanisms will do the rest... mostly.

以这种方式工作,即通过重新分配 self__class____dict__以这种方式,您只需要提供您的覆盖——Python 的正常属性获取和设置机制将完成其余的工作......主要是.

You'll be in trouble only if baseObject.__class__defines __slots__, in which case the multiple inheritance approach doesn't work and you do need the cumbersome __getattr__(as others said, at least you don't need to worry that it will be called with attributes you're overriding, as it won't!-), __setattr__(a greater pain, as it DOES get called for every attribute), etc; and making isinstanceand special methods work takes painstaking and cumbersome detailed work.

只有在baseObject.__class__定义时你才会遇到麻烦__slots__,在这种情况下多重继承方法不起作用并且你确实需要麻烦__getattr__(正如其他人所说,至少你不必担心它会被你的属性调用)重新覆盖,因为它不会!-),__setattr__(更大的痛苦,因为它确实被每个属性调用)等;而制作isinstance和特殊方法工作则需要艰苦而繁琐的细致工作。

Essentially, __slots__means that a class is a special, each instance a lightweight "value object" NOT to be subject to further sophisticated manipulation, wrapping, etc, because the need to save a few bytes per instance of that class overrides all the normal concerns about flexibility and so on; it's therefore not surprising that dealing with such extreme, rare classes in the same smooth and flexible way as you can deal with 99%+ of Python objects is truly a pain. So DO you need to deal with __slots__(to the point of writing, testing, debugging and maintaining hundreds of lines of code just for those corner cases), or will the 99% solution in half a dozen lines suffice?-)

从本质上讲,这__slots__意味着一个类是一个特殊的,每个实例都是一个轻量级的“值对象”,不会受到进一步复杂的操​​作、包装等的影响,因为需要为该类的每个实例节省几个字节覆盖了所有关于灵活性等;因此,毫不奇怪,以与处理 99% 以上的 Python 对象相同的流畅和灵活的方式处理如此极端、稀有的类确实是一种痛苦。那么您是否需要处理__slots__(仅针对这些极端情况编写、测试、调试和维护数百行代码),或者六行 99% 的解决方案就足够了吗?-)

It should also be noted that this may lead to memory leaks, as creating a subclass adds the subclass to the base class' list of subclasses, and isn't removed when all instances of the subclass are GC'd.

还应该注意的是,这可能会导致内存泄漏,因为创建子类会将子类添加到基类的子类列表中,并且当子类的所有实例都被 GC 处理时不会被删除。

回答by Phil Friesen

Please look at http://code.activestate.com/recipes/577555-object-wrapper-class/for the complete code, including important comments. It boils down to:

请查看http://code.activestate.com/recipes/577555-object-wrapper-class/以获取完整代码,包括重要注释。归结为:

class Wrapper(object):
    def __init__(self, obj):
        self._wrapped_obj = obj
    def __getattr__(self, attr):
        if attr in self.__dict__:
            return getattr(self, attr)
        return getattr(self._wrapped_obj, attr)

回答by Phil Friesen

__getattr__has the advantage that it's only called when the attribute does not exist, so you should not need an explicit list -- anything you don't define will automatically get proxied.

__getattr__它的优点是仅在属性不存在时调用,因此您不需要显式列表——任何您未定义的内容都将自动被代理。

__setattr__is trickier because it's always called. Make sure you use a superclass call or object.__setattr__when setting your own attributes; using setattr()within __setattr__will cause infinite recursion.

__setattr__比较棘手,因为它总是被调用。确保使用超类调用或object.__setattr__在设置自己的属性时;使用setattr()inside__setattr__会导致无限递归。

The final bit, affecting isinstance, is very difficult. You can do it with an assigment to your wrapper instance's .__class__variable (but this also overrides class dictionary resolution order), or by dynamically constructing your wrapper type using a metaclass. Since isinstance is so rare in Python code, it seems overkill to actually try to trick it.

影响 isinstance 的最后一点非常困难。您可以通过对包装器实例的 . __class__变量(但这也会覆盖类字典解析顺序),或者通过使用元类动态构造您的包装器类型。由于 isinstance 在 Python 代码中非常少见,实际上试图欺骗它似乎有点过分。

More information on special attribute access methods.

有关特殊属性访问方法的更多信息。

Even more information on them, plus some help with metaclasses.

有关它们的更多信息,以及有关元类的一些帮助。

回答by Henrik Gustafsson

Note:This answer is really old and might not work on modern Pythons. I believe it used to work on 2.6

注意:这个答案真的很旧,可能不适用于现代 Python。我相信它曾经适用于 2.6

Start with this and mess with stuff in the loop to suit your needs:

从这个开始,并在循环中处理一些东西以满足您的需求:

import inspect
class Delegate(object):
    def __init__(self, implementation):
        self.__class__ = implementation.__class__
        for n, m in inspect.getmembers(implementation, callable):
            if not n.startswith('_'):
                setattr(self, n, m)

The ability to wrap not-new-style-objects is left as an exercise to the reader :)

包装非新型对象的能力留给读者作为练习:)

回答by Jon Cage

By 'existing object' you mean an instance of another class? Sounds to me as though you just need to inherit from the base class. When you create your new object, pass in the details of the base object, or add a method in your new class which copies the data of the base class instance into itself.

“现有对象”是指另一个类的实例吗?听起来好像你只需要从基类继承。创建新对象时,传入基对象的详细信息,或在新类中添加一个方法,该方法将基类实例的数据复制到自身中。

回答by Boris Gorelik

The following approach is similar to the one by Alex Martelli, but is more readable to me:

以下方法类似于Alex Martelli 的方法,但对我来说更具可读性:

Instead of creating a new class, I create a class-like function that instantiates the underlying object and overwrites needed functions, as follows:

我没有创建新类,而是创建了一个类类函数,用于实例化底层对象并覆盖所需的函数,如下所示:

class C1:
    def __init__(self, p):
        self.p = p
    def f(self):
        print("This is C1, p=%s" % self.p)

def C2(p):
    c = C1(p)
    def new_f():
        print("This is C2, p is %s" % c.p)
    c.f = new_f
    def g(param):
        print("This is C2.g(%s), p is %s" % (param, c.p))
    c.g = g
    return c

c1 = C1('abc')
c1.f()

c2 = C2('cde')
c2.f()
c2.g('xyz')

The output:

输出:

This is C1, p=abc

This is C2, p is cde
This is C2.g(xyz), p is cde