Подтвердить что ты не робот

Python Properties & Swig

Я пытаюсь создать привязки python для некоторого кода на С++, используя swig. Кажется, у меня возникла проблема с попыткой создания свойств python из некоторых функций доступа, которые у меня есть для таких методов, как:

class Player {
public:
  void entity(Entity* entity);
  Entity* entity() const;
};

Я попытался создать свойство, используя функцию свойства python, но кажется, что создаваемые классы оболочки оболочки не совместимы с ним, по крайней мере, для сеттеров.

Как вы создаете свойства с помощью swig?

4b9b3361

Ответ 1

О, это сложно (и весело). SWIG не признает это как возможность генерировать @property: я думаю, было бы слишком легко проскользнуть и распознать множество ложных срабатываний, если бы это было сделано не очень тщательно. Однако, поскольку SWIG не будет делать это при создании С++, все еще вполне возможно сделать это на Python, используя небольшой метакласс.

Итак, ниже, допустим, у нас есть класс Math, который позволяет нам устанавливать и получать целочисленную переменную с именем "pi". Затем мы можем использовать этот код:

example.h

#ifndef EXAMPLE_H
#define EXAMPLE_H

class Math {
 public:
    int pi() const {
        return this->_pi;
    }

    void pi(int pi) {
        this->_pi = pi;
    }

 private:
    int _pi;
};

#endif

example.i

%module example

%{
    #define SWIG_FILE_WITH_INIT
    #include "example.h"
%}

[essentially example.h repeated again]

example.cpp

#include "example.h"

util.py

class PropertyVoodoo(type):
    """A metaclass. Initializes when the *class* is initialized, not
    the object. Therefore, we are free to muck around the class
    methods and, specifically, descriptors."""

    def __init__(cls, *a):
        # OK, so the list of C++ properties using the style described
        # in the OP is stored in a __properties__ magic variable on
        # the class.
        for prop in cls.__properties__:

            # Get accessor.
            def fget(self):
                # Get the SWIG class using super. We have to use super
                # because the only information we're working off of is
                # the class object itself (cls). This is not the most
                # robust way of doing things but works when the SWIG
                # class is the only superclass.
                s = super(cls, self)

                # Now get the C++ method and call its operator().
                return getattr(s, prop)()

            # Set accessor.
            def fset(self, value):
                # Same as above.
                s = super(cls, self)

                # Call its overloaded operator(int value) to set it.
                return getattr(s, prop)(value)

            # Properties in Python are descriptors, which are in turn
            # static variables on the class. So, here we create the
            # static variable and set it to the property.
            setattr(cls, prop, property(fget=fget, fset=fset))

        # type() needs the additional arguments we didn't use to do
        # inheritance. (Parent classes are passed in as arguments as
        # part of the metaclass protocol.) Usually a = [<some swig
        # class>] right now.
        super(PropertyVoodoo, cls).__init__(*a)

        # One more piece of work: SWIG selfishly overrides
        # __setattr__. Normal Python classes use object.__setattr__,
        # so that what we use here. It not really important whose
        # __setattr__ we use as long as we skip the SWIG class in the
        # inheritance chain because SWIG __setattr__ will skip the
        # property we just created.
        def __setattr__(self, name, value):
            # Only do this for the properties listed.
            if name in cls.__properties__:
                object.__setattr__(self, name, value)
            else:
                # Same as above.
                s = super(cls, self)

                s.__setattr__(name, value)

        # Note that __setattr__ is supposed to be an instance method,
        # hence the self. Simply assigning it to the class attribute
        # will ensure it an instance method; that is, it will *not*
        # turn into a static/classmethod magically.
        cls.__setattr__ = __setattr__

somefile.py

import example
from util import PropertyVoodoo

class Math(example.Math):
    __properties__ = ['pi']
    __metaclass__  = PropertyVoodoo

m = Math()
print m.pi
m.pi = 1024
print m.pi
m.pi = 10000
print m.pi

Итак, конечный результат состоит в том, что вам нужно создать класс-оболочку для каждого класса SWIG Python, а затем ввести две строки: один, чтобы отметить, какие методы должны быть преобразованы в свойствах, а другой - для метакласса.

Ответ 2

Существует простой способ сделать свойства python из методов с swig.
Предположим, что код С++ Example.h:

Заголовок С++

class Example{
    public:
      void SetX(int x);
      int  GetX() const;
    };

Позволяет преобразовать этот setter и getter в python owny 'x'. Трюк находится в файле .i. Мы добавляем некоторый "swiggy" встроенный код python (с% pythoncode), который вставляется в тело результирующего класса python (в автогенерированном коде Python).

Swig wrapping Example.i

%module example
%{
     #include "example.h"
%}

class Example{
    public:
      void SetX(int x);
      int  GetX() const;

      %pythoncode %{
         __swig_getmethods__["x"] = GetX
         __swig_setmethods__["x"] = SetX
         if _newclass: x = property(GetX, SetX)
      %}
    };

Проверьте код python:

тестовый код python

import example

test = example.Example()
test.x = 5
print "Ha ha ha! It works! X = ", repr(test.x)

Вот и все!



Сделайте это проще!

Нет необходимости переписывать определение класса. Благодаря совету Джошуа, можно использовать директиву SWIG% extend ClassName {}.

Swig wrapping Example.i

%module example
%{
     #include "example.h"
%}

%extend Example{
      %pythoncode %{
         __swig_getmethods__["x"] = GetX
         __swig_setmethods__["x"] = SetX
         if _newclass: x = property(GetX, SetX)
      %}
    };

Скрытие сеттера и функции геттера

Как можно видеть, test.GetX() и test.SetX() все еще находятся на месте после преобразования. Их можно скрыть:

a) Переименуйте функции, используя% rename, добавьте '_' в начало, создав таким образом методы "private" для python. В интерфейсе SWIG.i Example.i

...
class Example{
   %rename(_SetX) SetX(int);
   %rename(_GetX) GetX();
...

(% rename может быть помещено в какое-то отдельное место, чтобы сохранить возможность преобразования этого класса на другие языки, которые не нуждаются в этих "_" )

b) или можно сыграть с функцией% ( "тень" )

Почему это так?

Почему мы должны использовать такие вещи для преобразования методов в свойство с помощью SWIG? Как было сказано, SWIG эгоистично переопределяет _setattr _, поэтому для регистрации функций и останова в режиме swig нужно использовать _swig_getmethods _ и _swig_setmethods _.

Почему это можно предпочесть?

Методы, перечисленные выше, особенно с PropertyVoodoo,... Это похоже на сжигание дома, чтобы жарить яйцо. Также он разбивает макеты классов, так как нужно создавать унаследованные классы для создания свойств python из методов С++. Я имею в виду, если класс Cow возвращает класс Milk, а унаследованный класс - MilkWithProperties (Milk), как заставить Cow производить MilkWithProperties?

Этот подход позволяет:

  • явно контролировать, какие методы С++ конвертировать в свойства python
  • правила преобразования находятся в файлах интерфейса swig (*. i), где они должны быть
  • один результирующий автогенерированный файл .py
  • пребывание в синтаксисе swig вставки в swig -генерированном файле .py.
  • % pythoncode игнорируется, если один переносит библиотеку на другие языки.

Обновление В более новой версии SWIG отказался от _swig_property, поэтому просто используйте свойство . Он работает со старой версией swig. Я изменил сообщение.

Ответ 3

Использовать атрибуты .i

В папке SWIG Lib находится файл с именем "attributes.i", который не обсуждается в документации, но содержит встроенную документацию.

Все, что вам нужно сделать, это добавить следующую строку в файл интерфейса.

%include <attributes.i>

Затем вы получаете несколько макросов (например, атрибут%) для определения атрибутов из существующих методов.

Отрывок из документации в файле attributes.i:

Следующие макросы преобразуют пару методов set/get в "родной" атрибут. Используйте атрибут%, если у вас есть пара методов get/set для примитивный тип, например:

  %attribute(A, int, a, get_a, set_a);

  struct A
  {
    int get_a() const;
    void set_a(int aa);
  };

Ответ 4

Проблема с метаклассом Hao ProperyVoodoo заключается в том, что при наличии нескольких свойств в списке свойств все свойства ведут себя так же, как и последний в списке. Например, если у меня были имена списков или свойств [ "x", "y", "z" ], тогда свойства, сгенерированные для всех трех, будут использовать те же аксессоры, что и "z".

После небольшого эксперимента, я считаю, что я решил, что эта проблема вызвана тем, как Python обрабатывает замыкания (т.е. имена внутри вложенных функций, которые относятся к переменным в области содержимого). Чтобы решить проблему, вам нужно получить локальные копии переменной имени свойства в методы fget и fset. Достаточно легко прокрасть их, используя аргументы по умолчанию:

# (NOTE: Hao comments removed for brevity)
class PropertyVoodoo(type):

def __init__(cls, *a):

    for prop in cls.__properties__:

        def fget(self, _prop = str(prop)):
            s = super(cls, self)
            return getattr(s, _prop)()


        def fset(self, value, _prop = str(prop)):
            s = super(cls, self)
            return getattr(s, _prop)(value)

        setattr(cls, prop, property(fget=fget, fset=fset))

    super(PropertyVoodoo, cls).__init__(*a)

    def __setattr__(self, name, value):
        if name in cls.__properties__:
            object.__setattr__(self, name, value)
        else:
            s = super(cls, self)
            s.__setattr__(name, value)

    cls.__setattr__ = __setattr__

Обратите внимание, что на самом деле вполне безопасно давать fget и fset дополнительные параметры _prop, потому что класс property() никогда не будет явно передавать им значения, что означает, что они всегда будут значением по умолчанию (это копия строки, на которую ссылается prop, в момент создания каждого метода fget и fset).

Ответ 5

У меня была такая же проблема, и совет по использованию% pythoncode работал у меня. Вот что я сделал:

class Foo {
  // ...
  std::string get_name();
  bool set_name(const std::string & name);
};

В обертке:

%include "foo.h"
%pythoncode %{
def RaiseExceptionOnFailure(mutator):
  def mutator(self, v):
    if not mutator(self, v):
     raise ValueError("cannot set property")
  return wrapper
Foo.name = property(Foo.get_name, RaiseExceptionOnFailure(Foo.set_name))
%}

Ответ 6

Из http://www.swig.org/Doc2.0/SWIGDocumentation.html#SWIG_adding_member_functions:

Малоизвестная особенность директивы% extend заключается в том, что она также может быть используется для добавления синтезированных атрибутов или для изменения поведения существующие атрибуты данных. Например, предположим, что вы хотели сделать значение атрибута только для чтения Vector вместо метода.

Итак, в вашем примере должно работать следующее:

%extend Player {
    Entity entity;
}

%{
Entity* Player_entity_get(Player* p) {
  return p->get_entity();
}
void Player_entityProp_set(Player* p, Entity* e) {
  p->set_entity(e);
}
%}