Полностью завернуть объект в Python
Я хочу полностью обернуть объект так, чтобы все запросы на атрибуты и методы перенаправлялись к оборачиваемому объекту, при этом переопределяя любые методы или переменные, которые мне нужны, а также предоставляя некоторые собственные методы. Этот класс-обертка должен выглядеть на 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 ответ(ов)
Самый простой способ в большинстве случаев, вероятно, выглядит так:
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% в полдюжины строк будет достаточно?
Также стоит отметить, что это может привести к утечкам памяти, так как создание подкласса добавляет этот подкласс в список подклассов базового класса, и он не удаляется, когда все экземпляры подкласса собираются сборщиком мусора.
Смотрите на 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
. Если атрибут не найден, то он извлекается у обёрнутого объекта. Это позволяет использовать классы-обёртки для инкапсуляции функциональности других объектов.
Примечание: Этот ответ действительно старый и может не работать на современных версиях 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)
Возможность обернуть объекты, которые не являются "новыми стилевыми", оставлена читателю в качестве упражнения 😃
Вызов функции модуля по его имени (строке)
Как перечислить все функции в модуле?
Как изменить порядок столбцов в DataFrame?
'pip' не распознан как командa внутреннего или внешнего формата
Почему statistics.mean() работает так медленно?