0

Полностью завернуть объект в Python

12

Я хочу полностью обернуть объект так, чтобы все запросы на атрибуты и методы перенаправлялись к оборачиваемому объекту, при этом переопределяя любые методы или переменные, которые мне нужны, а также предоставляя некоторые собственные методы. Этот класс-обертка должен выглядеть на 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', ...]:
            # вернуть запрашиваемый метод
        else:
            return getattr(self.baseObject, attr)

Но я не очень хорошо знаком с переопределением методов __getattr__, __setattr__ и __hasattr__, поэтому не уверен, как это правильно сделать.

3 ответ(ов)

0

Самый простой способ в большинстве случаев, вероятно, выглядит так:

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

    def overriddenMethod(self):
        ...

Работая таким образом, т.е. переназначая __class__ и __dict__, вам нужно только предоставить ваши переопределения — стандартные механизмы получения и установки атрибутов в Python позаботятся об остальном... в большинстве случаев.

Вы столкнетесь с проблемами только в том случае, если baseObject.__class__ определяет __slots__, в этом случае подход с множественным наследованием не сработает и вам придется использовать громоздкий __getattr__ (как уже упоминали, вам не нужно беспокоиться о том, что он будет вызван с переопределяемыми атрибутами, потому что это не случится!), __setattr__ (что более проблематично, так как он вызывается для каждого атрибута) и так далее; делать так, чтобы isinstance и спецметоды работали, требует кропотливой и сложной работы.

По сути, __slots__ означает, что класс является особым, а каждый экземпляр — легковесным "значенческим объектом", который не должен подвергаться дальнейшей сложной манипуляции, упаковке и т.д., поскольку необходимость сэкономить несколько байт на экземпляр этого класса перевешивает все нормальные заботы о гибкости и прочем; не удивительно, что работа с такими редкими классами так же гладко и гибко, как с более чем 99% объектов Python, является настоящей болью. Так нужно ли вам справляться с __slots__ (до степени написания, тестирования, отладки и поддержки сотен строк кода только для таких крайних случаев), или же решение на 99% в полдюжины строк будет достаточно?

Также стоит отметить, что это может привести к утечкам памяти, так как создание подкласса добавляет этот подкласс в список подклассов базового класса, и он не удаляется, когда все экземпляры подкласса собираются сборщиком мусора.

0

Смотрите на 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)

Этот код представляет класс-обёртку, который позволяет вам "заворачивать" другие объекты. Он переопределяет метод __getattr__, чтобы при обращении к атрибутам сначала проверять, есть ли они у самого экземпляра класса Wrapper. Если атрибут не найден, то он извлекается у обёрнутого объекта. Это позволяет использовать классы-обёртки для инкапсуляции функциональности других объектов.

0

Примечание: Этот ответ действительно старый и может не работать на современных версиях Python. Я полагаю, он работал на 2.6.

Начните с этого кода и изменяйте его в цикле по своему усмотрению:

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)

Возможность обернуть объекты, которые не являются "новыми стилевыми", оставлена читателю в качестве упражнения 😃

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь