Library of Assembled Shared Sources
export_traits_function.h
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2023-2025 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43#pragma once
44
45#include "python_common.h"
46#include "pyobject_plus.h"
47#include "export_traits.h"
48#include "pyobject_call.inl"
49#include "_lass_module.h"
50
51namespace lass::python
52{
53
54namespace impl
55{
56
57class FunctionImplBase
58{
59public:
60 virtual ~FunctionImplBase() = default;
61 virtual PyObject* call(PyObject* args) const = 0;
62};
63
64template <typename R, typename... Args>
65class FunctionImpl: public FunctionImplBase
66{
67public:
68 using TFunction = std::function<R(Args...)>;
69
70 FunctionImpl(TFunction func) : func_(std::move(func)) {}
71
72 PyObject* call(PyObject* args) const override
73 {
74 return impl::callFunction(args, func_);
75 }
76
77 TFunction function() const { return func_; }
78private:
79 TFunction func_;
80};
81
82class LASS_PYTHON_DLL Function: public PyObjectPlus
83{
84 PY_HEADER(PyObjectPlus)
85public:
86 using TPimpl = std::unique_ptr<FunctionImplBase>;
87
88 Function(TPimpl pimpl);
89
90 template <typename R, typename... Args>
91 Function(std::function<R(Args...)> v):
92 Function(TPimpl(new FunctionImpl<R, Args...>(std::move(v))))
93 {
94 }
95
96 template <typename R, typename... Args>
97 bool get(std::function<R(Args...)>& v)
98 {
99 if (auto p = dynamic_cast< FunctionImpl<R, Args...>* >(pimpl_.get()))
100 {
101 v = p->function();
102 return true;
103 }
104 return false;
105 }
106
107 static PyObject* _tp_call(PyObject* self, PyObject* args, PyObject* kwargs);
108private:
109 std::unique_ptr<FunctionImplBase> pimpl_;
110};
111
112template <typename R, typename... Args>
113class Callable
114{
115public:
116 Callable(TPyObjPtr callable):
117 callable_(std::move(callable))
118 {
119 }
120
121 R operator()(typename util::CallTraits<Args>::TParam... args) const
122 {
123 LockGIL LASS_UNUSED(lock);
124 LASS_ASSERT(callable_);
125 const TPyObjPtr oArgs = makeTuple(args...);
126 const TPyObjPtr result(PyObject_CallObject(callable_.get(), oArgs.get()));
127 if (!result)
128 {
129 fetchAndThrowPythonException(LASS_PRETTY_FUNCTION);
130 }
131 typedef ArgumentTraits<R> TraitsR;
132 typename TraitsR::TStorage temp;
133 if (pyGetSimpleObject(result.get(), temp) != 0)
134 {
135 fetchAndThrowPythonException(LASS_PRETTY_FUNCTION);
136 }
137 return TraitsR::arg(temp);
138 }
139
140 TPyObjPtr callable() const { return callable_; }
141private:
142 TPyObjPtr callable_;
143};
144
145}
146
147/** Bidirectional wrapper between Callable objects and std::function.
148 *
149 * Accepts Callable objects and wraps them in a std::function without checking the
150 * parameter types or return type, allowing to call them from C++. The parameters
151 * and return type are only checked when the function is called, and an exception will
152 * be raised if the types do not match.
153 *
154 * From C++ to Python, the std::function is converted to a Callable object, so that it
155 * can be called from Python. Again, the parameter types and return type will be
156 * checked when the function is called.
157 *
158 * In both directions, unwrapping a previously wrapped function will be attempted.
159 * I.e. if the std::function passed to Python already wraps a Callable Python object,
160 * this will be unwrapped and the Callable object will be passed back to Python. In the
161 * other direction, if a Callable object that was passed to C++ already wraps a
162 * std::function, *and* this std::function matches the correct signature, then the
163 * std::function will be unwrapped and passed to C++. In other words, both directions
164 * will guarantee a perfect round trip if possible. In all other cases, the Callable
165 * or std::function will be wrapped in a new std::function or Callable object.
166 *
167 * @ingroup PyExportTraits
168 */
169template <typename R, typename... Args>
170struct PyExportTraits< std::function<R(Args...)> >
171{
172 static constexpr const char* py_typing = "Callable[[Args...], R]";
173
174 static PyObject* build(std::function<R(Args...)> v)
175 {
176 using TCallable = impl::Callable<R, Args...>;
177 if (TCallable* p = v.template target<TCallable>())
178 {
179 PyObject* r = p->callable().get();
180 Py_XINCREF(r);
181 return r;
182 }
183 return new impl::Function(std::move(v));
184 }
185 static int get(PyObject* obj, std::function<R(Args...)>& v)
186 {
187 impl::initLassModule(); // ensure the module is initialized
188 if (PyType_IsSubtype(obj->ob_type, impl::Function::_lassPyClassDef.type()))
189 {
190 if (static_cast<impl::Function*>(obj)->get(v))
191 {
192 return 0;
193 }
194 }
195 if (!PyCallable_Check(obj))
196 {
197 PyErr_SetString(PyExc_TypeError, "Not callable");
198 return 1;
199 }
200 using TCallable = impl::Callable<R, Args...>;
201 v = TCallable(fromNakedToSharedPtrCast<PyObject>(obj));
202 return 0;
203 }
204};
205
206}
void fetchAndThrowPythonException(std::string loc)
Fetch the current Python exception and throw it as a C++ PythonException.
PyObjectPtr< PyObject >::Type TPyObjPtr
PyObjectPtr to a PyObject.
#define PY_HEADER(t_parentClass)
Place as first line of your Pythonized class.
lass::util::SharedPtr< T, PyObjectStorage, PyObjectCounter > fromNakedToSharedPtrCast(PyObject *object)
fromNakedToSharedPtrCast.
Comprehensive C++ to Python binding library.
by copy, general case assumes shadow type or PyObjectPlus based type.