Полностью обернуть объект в Python - PullRequest
30 голосов
/ 18 сентября 2009

Я хочу полностью обернуть объект, чтобы все запросы атрибутов и методов пересылались объекту, который он обертывает, но также переопределял любые методы или переменные, которые мне нужны, а также предоставлял некоторые из моих собственных методов. Этот класс-обертка должен отображаться на 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)

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

Ответы [ 6 ]

34 голосов
/ 18 сентября 2009

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

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% -ного решения в полдюжины строк? -)

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

5 голосов
/ 24 августа 2015

Пожалуйста, посмотрите на 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)
4 голосов
/ 18 сентября 2009

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

__setattr__ хитрее, потому что его всегда называют. Убедитесь, что вы используете вызов суперкласса или object.__setattr__ при настройке собственных атрибутов; использование setattr() в __setattr__ приведет к бесконечной рекурсии.

Последний бит, влияющий на экземпляр, очень сложен. Вы можете сделать это с помощью присваивания переменной вашего экземпляра оболочки. __class__ (но это также переопределяет порядок разрешения словаря классов) или путем динамического конструирования вашего типа оболочки с использованием метакласса. Поскольку isinstance в коде Python встречается редко, на самом деле пытаться его обмануть представляется излишним.

Дополнительная информация о методах доступа к специальным атрибутам.

Еще больше информации о них, а также некоторая помощь с метаклассами.

1 голос
/ 18 сентября 2009

Примечание: Этот ответ действительно старый и может не работать на современных питонах. Я считаю, что раньше работал на 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)

Возможность обернуть объекты не нового стиля оставлена ​​читателю в качестве упражнения:)

0 голосов
/ 15 июля 2015

Следующий подход аналогичен Алексу Мартелли , но для меня он более читабелен:

Вместо создания нового класса я создаю классоподобную функцию, которая создает экземпляр базового объекта и перезаписывает необходимые функции следующим образом:

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')

Выход:

This is C1, p=abc

This is C2, p is cde
This is C2.g(xyz), p is cde
0 голосов
/ 18 сентября 2009

Под «существующим объектом» вы имеете в виду экземпляр другого класса? Похоже, мне просто нужно наследовать от базового класса. Когда вы создаете новый объект, передайте детали базового объекта или добавьте метод в ваш новый класс, который копирует данные экземпляра базового класса в себя.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...