Pybind11, как вызвать __repr__ объекта в std::vector?

Я привязываю тип my_type

py::class_<my_type, std::shared_ptr<my_type>>(m, "MyType")
        .def("__repr__", [](const my_type& o){return fmt::format("MyType: {}", o);});

а также std::vector с

py::bind_vector<std::vector<my_type>>(m, "MyTypeVector");

Как я могу/должен объявить здесь метод MyTypeVector __repr__, если я хочу, чтобы его вывод был последовательностью MyType.__repr__ для каждого объекта в контейнере?


person polortiz40    schedule 16.06.2020    source источник


Ответы (2)


это на самом деле очень просто. py::bind_vector — это всего лишь оболочка вокруг class_, поэтому вы можете добавлять к нему методы так же, как вы добавляете их к обычному классу.

В вашем случае вы можете просто сделать

py::bind_vector<std::vector<my_type>>(m, "MyTypeVector")
  .def("__repr__", [](const std::vector<my_type>& v) {// generate your string here;});

Поэтому для создания строкового представления я обычно определяю методы toString и оператор << в своих классах С++.

class BadData
{
// lots of stuff going on and removed here
    virtual void
    putMembers(std::ostream& out) const
    {
      out << "msg=" << >msg;
      out << ", ";
      out << "stack=" << stack;
    }

    virtual std::string
    toString() const
    {
      std::ostringstream out;
      out << "BadData(";
      putMembers(out);
      out << ")";
      return out.str();
    }
}

inline
std::ostream &operator<<(std::ostream& stream, const BadData &item)
{
  stream << item.toString();
  return stream;
}

У нас также есть оператор‹‹, определенный для коллекций stl

template<class T> inline
std::ostream& operator << (std::ostream& os, const std::vector<T>& v)
{
  std::ostringstream out;
  out << "Vector[";
  if (v.size() > 0) {
    for (auto ii = v.cbegin(); ii != v.cend() -1 ; ++ii) {
      out << *ii << ", ";
    }
    out << v.back();
  }
  out << "]";
  os << out.str();
  return os;
}

Итак, как только вы определили все эти операторы, ваш метод __repr__ может выглядеть так:

.def("__repr__", [](const std::vector<my_type>& v) {
    std::stringstream stream;
    stream << v;
    return stream.str(); 
})

или в случае вашего пользовательского класса, например

.def("__repr__", &::my_type::toString)
person Jesse C    schedule 16.06.2020
comment
Спасибо, Джесси, но я имел в виду следующее: что мне следует использовать в теле лямбды, где у вас есть // генерация вашей строки здесь, чтобы воспроизвести repr для каждого элемента - person polortiz40; 17.06.2020
comment
Цикл, генерирующий «строку представления»..? - person user2864740; 17.06.2020
comment
@user2864740 user2864740, могу ли я сгенерировать эту строку представления из ранее определенного 'repr' MyType? Или я должен ввести их снова вручную? - person polortiz40; 17.06.2020
comment
Возможно, тогда стоит перефразировать весь вопрос: тогда речь идет (в основном) о вызове метода для объекта Python/прокси «MyType». - person user2864740; 17.06.2020
comment
Я полагаю, что мог бы создать функцию для генерации этой строки и использовать ее как в repr MyType, так и в цикле repr MyTypeVector, таким образом, нет дублирования кода. - person polortiz40; 17.06.2020
comment
Я отредактировал, чтобы добавить метод, который мы используем для генерации наших значений __repr__. - person Jesse C; 17.06.2020

JesseC очень помог, но кто-то указал на слабость этого подхода: он вынуждает либо классы определять свой собственный оператор‹‹, либо программиста определять его в привязках (что является проблемой, если класс уже определил оператор ‹‹, но не соответствует тому, что он или она хочет в качестве их __repr__ вывода). Основной библиотеке не нужно знать, что она привязывается, и поэтому ее не следует заставлять реализовывать такой метод. С этой целью можно изменить оператор‹‹ в std::vector на:

template<class T>
inline std::string vector_repr(const std::vector<T>& v){
    std::ostringstream out;
    out << "Vector[";

    auto py_vector = py::cast(v);
    const auto separator = ", ";
    const auto* sep = "";

    for( auto obj : py_vector ){
        out << sep << obj.attr("__repr__")();
        sep = separator;
    }
    out << "]";

    return out.str();
}

вместе с привязкой

py::bind_vector<MyTypeVector>(m, "MyTypeVector")
    .def("__repr__", [](const MyTypeVector& v){
             return vector_repr(v);
});
person polortiz40    schedule 17.06.2020
comment
На самом деле это не вызывает метод toString(), а просто оператор‹‹ для ваших классов (который не нужно определять внутри класса). У нас уже есть методы toString() для всего нашего сгенерированного кода, что делает его использование в операторе ‹‹ логичным выбором, но вы можете просто реализовать оператор ‹‹ без toString. В целом, я думаю, что наличие оператора‹‹, определенного для всех ваших классов C++, в любом случае является суперполезным шаблоном, поскольку он упрощает регистрацию. - person Jesse C; 17.06.2020
comment
Я согласен, что иметь оператора‹‹ – хорошая практика. Но в этом случае оператор‹‹ должен соответствовать тому, что вы выводите в 'repr', а это не обязательно то, что вы хотели от оператора‹‹. Кроме того, если вы привязываете стороннюю библиотеку, вы не хотите, чтобы вас заставляли заходить внутрь и редактировать исходный код таким образом. - person polortiz40; 18.06.2020
comment
Но вы правы, я оговорился, сказав, что это вызывает метод toString(), я исправил это в своем ответе - person polortiz40; 18.06.2020
comment
да, это действительно вопрос согласованности дизайна. Я хочу, чтобы мои строковые представления С++ и Python совпадали. Так что в моем случае не имеет смысла реализовывать мои функции стробирования с зависимостями от python, потому что я хочу, чтобы мой чистый код на С++ также мог его использовать, и зачем для этого вызывать вызовы в python. Если вы просто заботитесь о python, вызов repr здесь имеет больше смысла. Вы также можете добавить operator<< к сторонним библиотекам в своем собственном исходном коде, не изменяя внешний исходный код. Просто нужно убедиться, что он находится в том же пространстве имен, что и сторонний объект. - person Jesse C; 18.06.2020
comment
Справедливо, но вы можете захотеть, чтобы формат строки Python отличался. Например, если cpp печатает my_type(a=1), но вы хотите, чтобы python печатал MyType(a=1) из-за разных руководств по стилю. Кроме того, если вы хотите определить __str__, а также __repr__, и сделать их разными, вы не можете сделать так, чтобы они оба полагались на оператор cpp‹‹. Я имел в виду случай, когда у третьей стороны есть определенный оператор‹‹, который не печатает именно то, что вам нужно. Если вы добавите своего собственного оператора ‹‹, я думаю, он будет конфликтовать с их. Вам придется отредактировать их исходный код, чтобы удалить/изменить их определение. - person polortiz40; 18.06.2020