Как подделать/проксировать класс в Python

Я написал некоторую оболочку, у которой есть другой объект как атрибут. Эта оболочка проксирует (пересылает) все запросы атрибутов с __getattr__ и __setattr__ к объекту, сохраненному в качестве атрибута. Что еще мне нужно для обеспечения моего прокси-сервера, чтобы оболочка выглядела как обернутый класс при обычных обстоятельствах?

Я полагаю, что мне нужно исправить такие вещи, как наследование, возможно, __repr__,... Что еще мне нужно позаботиться и как я могу наследовать, чтобы instanceof() работал?

EDIT: Моя попытка создать прокси-сервер функции, однако, поскольку я не понимаю рецепт полностью, он терпит неудачу: (

setattr_=object.__setattr__
getattr_=object.__getattribute__

class Proxy(object):
    __slots__=["_func", "_params", "_kwargs", "_obj", "_loaded", "__weakref__"]
    def __init__(self, func, *params, **kwargs):
        setattr_(self, "_func", func)
        setattr_(self, "_params", params)
        setattr_(self, "_kwargs", kwargs)

        setattr_(self, "_obj", None)
        setattr_(self, "_loaded", False)

    def _get_obj(self):
        if getattr_(self, "_loaded")==False:
            print("Loading")
            setattr_(self, "_obj", getattr_(self, "_func")(*getattr_(self, "_params"), **getattr_(self, "_kwargs")))
            setattr_(self, "_loaded", True)

        return getattr_(self, "_obj")
    #
    # proxying (special cases)
    #
    def __getattribute__(self, name):
        return getattr(getattr_(self, "_get_obj")(), name)
    def __delattr__(self, name):
        delattr(getattr_(self, "_get_obj")(), name)
    def __setattr__(self, name, value):
        setattr(getattr_(self, "_get_obj")(), name, value)

    def __nonzero__(self):
        return bool(getattr_(self, "_get_obj")())
    def __str__(self):
        return str(getattr_(self, "_get_obj")())
    def __repr__(self):
        return repr(getattr_(self, "_get_obj")())

    #
    # factories
    #
    _special_names=[
        '__abs__', '__add__', '__and__', '__call__', '__cmp__', '__coerce__',
        '__contains__', '__delitem__', '__delslice__', '__div__', '__divmod__',
        '__eq__', '__float__', '__floordiv__', '__ge__', '__getitem__',
        '__getslice__', '__gt__', '__hash__', '__hex__', '__iadd__', '__iand__',
        '__idiv__', '__idivmod__', '__ifloordiv__', '__ilshift__', '__imod__',
        '__imul__', '__int__', '__invert__', '__ior__', '__ipow__', '__irshift__',
        '__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__', '__len__',
        '__long__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__',
        '__neg__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__',
        '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__',
        '__repr__', '__reversed__', '__rfloorfiv__', '__rlshift__', '__rmod__',
        '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__',
        '__rtruediv__', '__rxor__', '__setitem__', '__setslice__', '__sub__',
        '__truediv__', '__xor__', 'next',
    ]

    @classmethod
    def _create_class_proxy(cls, theclass):
        """creates a proxy for the given class"""

        def make_method(name):
            def method(self, *args, **kw):
                return getattr(getattr_(self, "_get_obj")(), name)(*args, **kw)
            return method

        namespace={}
        for name in cls._special_names:
            if hasattr(theclass, name):
                namespace[name]=make_method(name)
        return type("%s(%s)"%(cls.__name__, theclass.__name__), (cls,), namespace)

    def __new__(cls, obj, *args, **kwargs):
        """
        creates an proxy instance referencing `obj`. (obj, *args, **kwargs) are
        passed to this class' __init__, so deriving classes can define an 
        __init__ method of their own.
        note: _class_proxy_cache is unique per deriving class (each deriving
        class must hold its own cache)
        """
        try:
            cache=cls.__dict__["_class_proxy_cache"]
        except KeyError:
            cls._class_proxy_cache=cache={}
        try:
            theclass=cache[obj.__class__]
        except KeyError:
            cache[obj.__class__]=theclass=cls._create_class_proxy(obj.__class__)
        ins=object.__new__(theclass)
        theclass.__init__(ins, obj, *args, **kwargs)
        return ins

if __name__=='__main__':
    def t(x, y):
        print("Running t")
        return x+y

    a=Proxy(t, "a", "b")
    print("Go")
    print(a+"c")

Ответ 1

Эта проблема достаточно хорошо решена с помощью этого рецепта:

Object Proxying (рецепт Python)

Общая идея, которой вы должны следовать, заключается в том, что большинство методов в классах получают доступ через некоторую комбинацию __getattr__ и __getattribute__ либо к самому классу, либо к его метаклассу, но это не относится к специальным методам python, те, которые начинаются и заканчиваются двойным подчеркиванием, для тех, которые должны быть найдены, они должны быть действительными методами для фактического класса, без возможности проксирования атрибута.

Какой из этих методов вы должны предоставить, очевидно, будет зависеть от того, какой из этих методов предлагает сам класс proxied. Специальные методы, которые необходимо предоставить для isinstance(), это методы __instancecheck__ и __subclasscheck__. для работы repr() вы также должны определить соответствующий __repr__() в самом прокси-классе.

Ответ 2

Как правило, вы можете использовать библиотеку wrapt (pypi), которая делает тяжелый подъем для вас:

Модуль wrapt очень сильно фокусируется на правильности. Поэтому он выходит за рамки существующих механизмов, таких как functools.wraps(), чтобы гарантировать, что декораторы сохраняют интроспективность, подписи, возможности проверки типов и т.д. [...]

Чтобы обеспечить максимально возможную минимальную нагрузку, модуль расширения C используется для критически важных компонентов

поддерживает создание пользовательских классов-оболочек. Чтобы добавить свои собственные атрибуты, вам необходимо объявить их таким образом, что wrapt не пытается передать их в завернутый экземпляр. Вы можете:

  • Префикс атрибута _self_ и добавление свойств для доступа
  • Объявить атрибут на уровне класса, в дополнение к __init__
  • Используйте слоты, если это подходит для вашего класса (не указано в документах), например:

    class ExtendedMesh(ObjectProxy):
        __slots__ = ('foo')
    
        def __init__(self, subject):
            super().__init__(subject)
            self.foo = "bar"
    

Он также поддерживает обертки функций, которые могут соответствовать вашей цели.