New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Decorator (function which gets called prior to all other functions) #1798
Comments
What I've done in Drake is just call the wrapping function manually. In general, Python decorators like this: def my_decorator(x):
def wrapper(cls):
return do_stuff(x, cls)
return wrapper
@my_decorator(x)
class MyClass: ... are equivalent to: class MyClass: ....
MyClass = my_decorator(x)(MyClass) So you can do this in // Let's say your decorator is defined in the module `my_project.meta`.
py::function my_decorator = py::module::import("my_project.meta").attr("my_decorator");
// Define your class normally.
py::class_<MyClass> my_class(m, "MyClass");
my_class // BR
.def(py::init())
.def(...);
// Decorate - you don't really need the reassignment if your decorator mutates the original object,
// instead of returning a proxy / wrapper / whatevs.
m.attr("MyClass") = my_decorator(x)(my_class); |
Followup: Python docs link - primary sources FTW! |
Hi eacousineau, I appreciate the comment. I am having trouble to compile this line though: m.attr("MyClass") = my_decorator(x)(my_class); What is And can I make my decorator be a C++ function? (i.e. not importing it from an external python package)? |
I figure out the correct way to write decorator in pybind11 with @eacousineau comment. use example in PEP 443 -- Single-dispatch generic functions from functools import singledispatch
@singledispatch
def fun(arg, verbose=False):
if verbose:
print("Let me just say,", end=" ")
print(arg)
@fun.register(int)
def _(arg, verbose=False):
if verbose:
print("Strength in numbers, eh?", end=" ")
print(arg)
@fun.register(list)
def _(arg, verbose=False):
if verbose:
print("Enumerate this:")
for i, e in enumerate(arg):
print(i, e)
def nothing(arg, verbose=False):
print("Nothing.")
fun.register(type(None), nothing)
@fun.register(float)
def fun_num(arg, verbose=False):
if verbose:
print("Half of your number:", end=" ")
print(arg/2)
fun("Hello,world")
fun("test.", verbose=True)
fun(42, verbose=True)
fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
fun(None)
fun(1.23) first , implement fun in pybind11 like this: namespace py = pybind11;
py::object singledispatch = py::module::import("functools").attr("singledispatch");
m.def(
"fun", [](py::object arg, bool verbose = false) {
if (verbose)
{
py::print("Let me just say", py::arg("end") = " ");
}
py::print(arg);
},
py::arg("arg"), py::arg("verbose") = false);
m.attr("fun") = singledispatch(m.attr("fun"));//use singledispatch wrap fun object Now you can use And if you has decorator in python like this: @fun.register(str)
def fun_str(arg, verbose=False):
if verbose:
print("get string", end=" ")
print(arg) In pybind11 implement like this: m.def(
"fun_str", [](py::str arg, bool verbose = false) {
if (verbose)
{
py::print("get string", py::arg("end") = " ");
}
py::print(arg);
},
py::arg("arg"), py::arg("verbose") = false);
//decorator
auto fun = m.attr("fun");
auto fun_register = fun.attr("register");
//@fun.register(str) accept object type, you should get str object from builtins module
//and write just like function call
py::object string_ = py::module::import("builtins").attr("str");
m.attr("fun_str") = fun_register(string_)(m.attr("fun_str")); You can get executable example from pybind11 & python decorator. If you have any questions,please contact me. |
Thanks for answering, @eacousineau and @liff-engineer! |
I recently studied the implementation of this feature. flask decorators like this: from flask import Flask
app = Flask(__name__)
@app.route('/admin')
def hello_admin():
return 'Hello Admin' Implementation using pybind11 :
#include <iostream>
#include <string>
#include <map>
#include <pybind11/embed.h>
#include <pybind11/functional.h>
namespace py = pybind11;
class Flask {
public:
Flask() = default;
Flask(const Flask&) = delete;
Flask& operator= (const Flask&) = delete;
Flask* operator&() = delete;
const Flask operator&() const = delete;
static Flask *GetInstance() {
if (_this == nullptr) {
_this = new Flask();
}
return _this;
}
/* decorator: implementation method 2 */
static auto decorator(const py::function &fun) {
_this = GetInstance();
/* call back fun or do anything */
//fun(2);
/* register function */
_this->py_cb.insert(std::make_pair(_this->currRule, fun));
return fun;
};
static auto route(std::string rule) {
_this = GetInstance();
_this->currRule = rule;
py::function decorator = py::module::import("myFlask").attr("decorator");
return decorator;
}
static void run(std::string fun, std::string name) {
_this = GetInstance();
if (_this->py_cb.find(fun) != _this->py_cb.end()) {
_this->py_cb[fun](name);
}
}
private:
class GC {
public:
explicit GC() {};
~GC() {
if (_this) {
delete _this;
_this = nullptr;
}
}
};
private:
static Flask *_this;
static GC gc;
std::string currRule;
std::map<std::string, py::object> py_cb;
};
Flask *Flask::_this = nullptr;
Flask::GC Flask::gc;
PYBIND11_EMBEDDED_MODULE(myFlask, m) {
#if 1
/* decorator: implementation method 1 */
m.def("decorator", [](const py::function &fun) -> py::function {
/* call Flask::decorator */
Flask::decorator(fun);
return fun;
});
#else
/* decorator: implementation method 2 */
m.def("decorator", &Flask::decorator);
#endif
py::class_<Flask> fl(m, "Flask");
fl.def(py::init(&Flask::GetInstance));
fl.def_static("run", &Flask::run);
#if 0
/* route: implementation method 1 */
fl.def_static("route", &Flask::route);
#else
static auto wrapper = m.attr("decorator");
/* route: implementation method 2 */
fl.def_static("route", [](std::string rule) -> auto {
/* call Flask::route */
Flask::route(rule);
return wrapper;
});
#endif
}
auto main(int argc, char **argv) -> int {
py::scoped_interpreter python;
py::module t = py::module::import("example_wrapper");
auto app = py::module::import("myFlask").attr("Flask");
app.attr("run")("/hello", "Bob");
Flask::run("/hello", "Eve");
Flask::run("/welcome", "This is a demo of the implementation of decorator");
return 0;
}
from myFlask import Flask
app = Flask()
@app.route('/hello')
def hello(name):
print('hello {}'.format(name))
@app.route('/welcome')
def welcome(text):
print(text)
app.run('/hello', 'Alice') |
This Python code adds a
decorator
to a class:Now my question is, is it possible to add such a decorator to a pybind class definition? Perhaps something like this?
Any advice appreciated.
The text was updated successfully, but these errors were encountered: