Динамическое присоединение метода к существующему объекту Python, сгенерированному с помощью swig?

Я работаю с классом Python, и у меня нет доступа на запись к его объявлению. Как я могу прикрепить собственный метод (например, __str__) к объектам, созданным из этого класса, без изменения объявления класса?

РЕДАКТИРОВАТЬ: Спасибо за все ваши ответы. Я перепробовал их все, но они не решили мою проблему. Вот минимальный пример, который, я надеюсь, прояснит проблему. Я использую swig для обертывания класса C ++, и цель состоит в том, чтобы переопределить __str__ функцию объекта, возвращаемого модулем swig. Я использую cmake для построения примера:

test.py

import example

ex = example.generate_example(2)

def prnt(self):
    return str(self.x)

#How can I replace the __str__ function of object ex with prnt?
print ex
print prnt(ex)

example.hpp

struct example
{
    int x;
};

example generate_example(int x);

example.cpp

#include "example.hpp"
#include <iostream>

example generate_example(int x)
{
    example ex;
    ex.x = x;
    return ex;
}

int main()
{
    example ex = generate_example(2);
    std::cout << ex.x << "\n";
    return 1;
}

example.i

%module example

%{
#include "example.hpp"
%}

%include "example.hpp"

CMakeLists.txt

cmake_minimum_required(VERSION 2.6)

find_package(SWIG REQUIRED)
include(${SWIG_USE_FILE})

find_package(PythonLibs)
include_directories(${PYTHON_INCLUDE_PATH})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})

set_source_files_properties(example.i PROPERTIES CPLUSPLUS ON)
swig_add_module(example python example.i example)
swig_link_libraries(example ${PYTHON_LIBRARIES})

if(APPLE)
    set(CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS "${CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS} -flat_namespace")
endif(APPLE)

Чтобы собрать и запустить test.py, скопируйте все файлы в каталог и в этом каталоге запустите

cmake .
make 
python test.py

Это приводит к следующему выводу:

<example.example; proxy of <Swig Object of type 'example *' at 0x10021cc40> >
2

Как видите, у объекта swig есть собственная функция str, и это то, что я пытаюсь переопределить.


person D R    schedule 05.09.2009    source источник
comment
Другой вопрос StackOverflow: stackoverflow.com / questions / 972 /   -  person Craig McQueen    schedule 05.09.2009


Ответы (5)


Если вы создадите класс-оболочку, он будет работать с любым другим классом, встроенным или нет. Это называется «сдерживание и делегирование», и это обычная альтернатива наследованию:

class SuperDuperWrapper(object):
    def __init__(self, origobj):
        self.myobj = origobj
    def __str__(self):
        return "SUPER DUPER " + str(self.myobj)
    def __getattr__(self,attr):
        return getattr(self.myobj, attr)

Метод __getattr__ делегирует все запросы неопределенных атрибутов в вашем объекте SuperDuperWrapper содержащемуся объекту myobj. Фактически, учитывая динамическую типизацию Python, вы можете использовать этот класс для обертывания SuperDuper практически всего:

s = "hey ho!"
sds = SuperDuperWrapper(s)
print sds

i = 100
sdi = SuperDuperWrapper(i)
print sdi

Печать:

SUPER DUPER hey ho!
SUPER DUPER 100

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

print sds.split()
['hey', 'ho!']
person PaulMcG    schedule 05.09.2009
comment
Я использовал эту технику в самом начале своей карьеры на Python, чтобы написать перехватчик для прокси-сервера удаленного объекта CORBA - клиент хотел, чтобы один конкретный удаленный метод был немного улучшен, но в остальном все остальное должно было вести себя так же. Только недавно перешедший с C ++, я был потрясен тем, как мало кода потребовалось для написания, в отличие от того, что C ++ заставил бы меня сделать. - person PaulMcG; 05.09.2009
comment
Спасибо, Пол! Это именно то, что я искал. - person D R; 05.09.2009
comment
Проблема с этим подходом заключается в том, что получившийся завернутый объект невозможно выбрать, что иногда является большой проблемой. - person Boris Gorelik; 15.09.2011
comment
+1. Я был удивлен, что это сработало для COM-объекта. Я ожидал, что getattr потерпит неудачу. - person Steven Rumbalski; 06.03.2012
comment
Добавление дополнительных функций обычно называется декоратором. - person jmoz; 19.07.2012
comment
Доступ не всегда, как если бы это был базовый объект. Например isinstance(s, basestring == True, но isinstance(sds, basestring) == False. Точно так же функции id() и type() возвращают разные результаты. - person martineau; 30.11.2012
comment
Это слишком питонический? Есть ли другие предложения для разработчиков, пишущих код для товарищей по команде can-only-read-python? Хотел бы иметь другие ресурсы по C ++ на этот срок. - person tomriddle_1234; 05.12.2014

>>> class C(object):
...     pass
... 
>>> def spam(self):
...     return 'spam'
... 
>>> C.__str__ = spam
>>> print C()
spam

Это не будет работать с классами, которые используют __slots__.

person Bastien Léonard    schedule 05.09.2009
comment
К сожалению, в моем случае это не работает. Класс, который я пытаюсь переопределить, был автоматически сгенерирован с использованием swig из файлов заголовков C ++. - person D R; 05.09.2009

Создайте подкласс. Пример:

>>> import datetime
>>> t = datetime.datetime.now()
>>> datetime.datetime.__str__ = lambda self: 'spam'
...
TypeError: cant set attributes of built-in/extension type 'datetime.datetime'
>>> t.__str__ = lambda self: 'spam'
...
AttributeError: 'datetime.datetime' object attribute '__str__' is read-only
>>> class mydate(datetime.datetime):
    def __str__(self):
        return 'spam'

>>> myt = mydate.now()
>>> print t
2009-09-05 13:11:34.600000
>>> print myt
spam
person Alex    schedule 05.09.2009
comment
Спасибо за предложение, Алекс. Это правда, что я могу легко создать подкласс, но это мне не очень поможет, потому что объект, с которым я работаю, возвращается функцией, которую я не могу изменить. В контексте вашего примера этот объект имеет тип datetime. Я хотел бы переопределить его функцию __str__, поэтому для того, чтобы это было полезно, мне нужен способ привести объект datetime к mydate. - person D R; 05.09.2009


Обратите внимание, что, используя идею подкласса Алекса, вы можете немного помочь себе, используя "from ... import ... as":

from datetime import datetime as datetime_original

class datetime(datetime_original):
   def __str__(self):
      return 'spam'

Итак, теперь у класса стандартное имя, но другое поведение.

>>> print datetime.now()
    'spam'

Конечно, это может быть опасно ...

person Andrew Jaffe    schedule 05.09.2009