pybind / pybind11

Seamless operability between C++11 and Python
https://pybind11.readthedocs.io/
Other
15.66k stars 2.1k forks source link

Cannot call embedded function from within class function #1452

Open TheGreatMonkey opened 6 years ago

TheGreatMonkey commented 6 years ago

I've written a c++ binding that allows me to post a simple message such that:

import foo
foo.func("message")

when called in python will post "message".

However, if I call the same embedded function from within a class method such as:

import foo

class bar(object):
  def func2(self):
    foo.func("message")

I will get an exception error_already_set. This occurs if I call the function from python or from c++. I'm not sure if this is a pybind11 bug, or if I just missed something.

TheGreatMonkey commented 6 years ago

After some experimentation I discovered that the problem is pybind cannot see the import form inside the scope of the function.

import foo

def bar():
  foo.func()

will always cause an error_already_set exception.

However;

def bar():
  import foo
  foo.func()

will function properly.

jagerman commented 6 years ago

I can't reproduce this:

>>> import f
>>> def bar():
...     f.func()
... 
>>> bar()
hello
>>> class c(object):
...     def func2(self):
...         f.func()
... 
>>> c().func2()
hello

(This is a module consisting of a simple: m.def("func", []() { py::print("hello"); });).

I suspect the error is coming from something else being done inside the foo.func call.

TheGreatMonkey commented 6 years ago

Thanks for the response. I see that I accidently left out some key details, my apologies.

I'm building a c++ application which uses two way communication with python. The application is built using the Urho3D game engine.

I have MyAppPython.h which looks like this

#pragma once

#include <Urho3D/Input/InputEvents.h>

#ifdef _DEBUG
#undef _DEBUG
#include <python.h>
#define _DEBUG
#else
#include <python.h>
#endif
#include <embed.h>
#include <fstream>
#include <iostream>

namespace py = pybind11;
using namespace py::literals;

std::string test_py = 
(
    R"(
class PyTest(object) :
    def __init__(self, message = "Test Object initialized") :
        import MyApp
        self.message = message
        self.iter = 0
        message = self.message
        MyApp.DebugInfo(message)

    def start(self) :
        import MyApp
        self.message = "Starting Python Object"
        self.iter = self.iter + 1
        message = self.message
        MyApp.DebugInfo(message)

    def update(self) :
        import MyApp
        self.message = "Python Object Update Cycle:"
        self.iter = self.iter + 1
        iterstr = str(self.iter)
        message = self.message + iterstr
        MyApp.DebugInfo(message)

Object = PyTest()
    )"
);

std::string Import_File(std::string filename_)
{
    std::ifstream ifs_(filename_);
    std::string content_((std::istreambuf_iterator<char>(ifs_)),
        (std::istreambuf_iterator<char>()));
    return content_;
}

void DebugInfo(std::string string_)
{
    String LogMessage_(string_.c_str());
    URHO3D_LOGINFO(LogMessage_);
}

PYBIND11_EMBEDDED_MODULE(MyApp, m) {
    // `m` is a `py::module` which is used to bind functions and classes
    m.def("DebugInfo", &DebugInfo);
}

and a from my main.cpp it's called using

py::scoped_interpreter guard{};
auto locals = py::dict();
py::exec(test_py, py::globals(), locals);
thing_ = locals["Object "].cast<py::object>();
thing_.attr("start")();

The above code is with the work around with the import call inside the scope of each method. If the import call is at the global level it will throw the error_already_set error against cast.h line 1926, or if it's in the init method; against eval.h line 49

If the problem isn't readily apparent from my code, I can port it to an clean Urho3D repo for testing and sharing.

jquesnelle commented 5 years ago

@TheGreatMonkey I seem to running into the same thing -- did you ever figure this out?

jquesnelle commented 5 years ago

Well, I figured this out. Basically, you don't actually want a blank py::dict for locals; it will cause all sorts of problems. If you look embedding sample code from the docs or the tests, the locals value always copies the global scope.

See:

Your options are to copy the global scope, or, in this case, simply don't pass in a locals

py::scoped_interpreter guard{}; 
auto globals = py::globals();
py::exec(test_py, globals);
thing_ = globals["Object"].cast<py::object>(); 
thing_.attr("start")();

It looks like that in the case of top-level code (not inside any module), the globals variable holds the values at this scope.