Loading...
Searching...
No Matches
pyFunction.h
1//
2// Copyright 2016 Pixar
3//
4// Licensed under the Apache License, Version 2.0 (the "Apache License")
5// with the following modification; you may not use this file except in
6// compliance with the Apache License and the following modification to it:
7// Section 6. Trademarks. is deleted and replaced with:
8//
9// 6. Trademarks. This License does not grant permission to use the trade
10// names, trademarks, service marks, or product names of the Licensor
11// and its affiliates, except as required to comply with Section 4(c) of
12// the License and to reproduce the content of the NOTICE file.
13//
14// You may obtain a copy of the Apache License at
15//
16// http://www.apache.org/licenses/LICENSE-2.0
17//
18// Unless required by applicable law or agreed to in writing, software
19// distributed under the Apache License with the above modification is
20// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21// KIND, either express or implied. See the Apache License for the specific
22// language governing permissions and limitations under the Apache License.
23//
24#ifndef PXR_BASE_TF_PY_FUNCTION_H
25#define PXR_BASE_TF_PY_FUNCTION_H
26
27#include "pxr/pxr.h"
28
29#include "pxr/base/tf/pyCall.h"
30#include "pxr/base/tf/pyLock.h"
31#include "pxr/base/tf/pyObjWrapper.h"
32#include "pxr/base/tf/pyUtils.h"
33
34#include <boost/python/converter/from_python.hpp>
35#include <boost/python/converter/registered.hpp>
36#include <boost/python/converter/rvalue_from_python_data.hpp>
37#include <boost/python/extract.hpp>
38#include <boost/python/handle.hpp>
39#include <boost/python/object.hpp>
40
41#include <boost/function.hpp>
42
43#include <functional>
44
45PXR_NAMESPACE_OPEN_SCOPE
46
47template <typename T>
48struct TfPyFunctionFromPython;
49
50template <typename Ret, typename... Args>
51struct TfPyFunctionFromPython<Ret (Args...)>
52{
53 struct Call
54 {
55 TfPyObjWrapper callable;
56
57 Ret operator()(Args... args) {
58 TfPyLock lock;
59 return TfPyCall<Ret>(callable)(args...);
60 }
61 };
62
63 struct CallWeak
64 {
65 TfPyObjWrapper weak;
66
67 Ret operator()(Args... args) {
68 using namespace boost::python;
69 // Attempt to get the referenced callable object.
70 TfPyLock lock;
71 object callable(handle<>(borrowed(PyWeakref_GetObject(weak.ptr()))));
72 if (TfPyIsNone(callable)) {
73 TF_WARN("Tried to call an expired python callback");
74 return Ret();
75 }
76 return TfPyCall<Ret>(callable)(args...);
77 }
78 };
79
80 struct CallMethod
81 {
82 TfPyObjWrapper func;
83 TfPyObjWrapper weakSelf;
84
85 Ret operator()(Args... args) {
86 using namespace boost::python;
87 // Attempt to get the referenced self parameter, then build a new
88 // instance method and call it.
89 TfPyLock lock;
90 PyObject *self = PyWeakref_GetObject(weakSelf.ptr());
91 if (self == Py_None) {
92 TF_WARN("Tried to call a method on an expired python instance");
93 return Ret();
94 }
95 object method(handle<>(PyMethod_New(func.ptr(), self)));
96 return TfPyCall<Ret>(method)(args...);
97 }
98 };
99
100 TfPyFunctionFromPython() {
101 RegisterFunctionType<boost::function<Ret (Args...)>>();
102 RegisterFunctionType<std::function<Ret (Args...)>>();
103 }
104
105 template <typename FuncType>
106 static void
107 RegisterFunctionType() {
108 using namespace boost::python;
109 converter::registry::
110 insert(&convertible, &construct<FuncType>, type_id<FuncType>());
111 }
112
113 static void *convertible(PyObject *obj) {
114 return ((obj == Py_None) || PyCallable_Check(obj)) ? obj : 0;
115 }
116
117 template <typename FuncType>
118 static void construct(PyObject *src, boost::python::converter::
119 rvalue_from_python_stage1_data *data) {
120 using std::string;
121 using namespace boost::python;
122
123 void *storage = ((converter::rvalue_from_python_storage<FuncType> *)
124 data)->storage.bytes;
125
126 if (src == Py_None) {
127 new (storage) FuncType();
128 } else {
129
130 // In the case of instance methods, holding a strong reference will
131 // keep the bound 'self' argument alive indefinitely, which is
132 // undesirable. Unfortunately, we can't just keep a weak reference to
133 // the instance method, because python synthesizes these on-the-fly.
134 // Instead we do something like what PyQt's SIP does, and break the
135 // method into three parts: the class, the function, and the self
136 // parameter. We keep strong references to the class and the
137 // function, but a weak reference to 'self'. Then at call-time, if
138 // self has not expired, we build a new instancemethod and call it.
139 //
140 // Otherwise if the callable is a lambda (checked in a hacky way, but
141 // mirroring SIP), we take a strong reference.
142 //
143 // For all other callables, we attempt to take weak references to
144 // them. If that fails, we take a strong reference.
145 //
146 // This is all sort of contrived, but seems to have the right behavior
147 // for most usage patterns.
148
149 object callable(handle<>(borrowed(src)));
150 PyObject *pyCallable = callable.ptr();
151 PyObject *self =
152 PyMethod_Check(pyCallable) ?
153 PyMethod_GET_SELF(pyCallable) : NULL;
154
155 if (self) {
156 // Deconstruct the method and attempt to get a weak reference to
157 // the self instance.
158 object func(handle<>(borrowed(PyMethod_GET_FUNCTION(
159 pyCallable))));
160 object weakSelf(handle<>(PyWeakref_NewRef(self, NULL)));
161 new (storage)
162 FuncType(CallMethod{
163 TfPyObjWrapper(func),
164 TfPyObjWrapper(weakSelf)
165 });
166
167 } else if (PyObject_HasAttrString(pyCallable, "__name__") &&
168 extract<string>(callable.attr("__name__"))()
169 == "<lambda>") {
170 // Explicitly hold on to strong references to lambdas.
171 new (storage) FuncType(Call{TfPyObjWrapper(callable)});
172 } else {
173 // Attempt to get a weak reference to the callable.
174 if (PyObject *weakCallable =
175 PyWeakref_NewRef(pyCallable, NULL)) {
176 new (storage)
177 FuncType(
178 CallWeak{TfPyObjWrapper(
179 object(handle<>(weakCallable)))});
180 } else {
181 // Fall back to taking a strong reference.
182 PyErr_Clear();
183 new (storage) FuncType(Call{TfPyObjWrapper(callable)});
184 }
185 }
186 }
187
188 data->convertible = storage;
189 }
190};
191
192PXR_NAMESPACE_CLOSE_SCOPE
193
194#endif // PXR_BASE_TF_PY_FUNCTION_H
Miscellaneous Utilities for dealing with script.
TF_API bool TfPyIsNone(boost::python::object const &obj)
Return true iff obj is None.
Convenience class for accessing the Python Global Interpreter Lock.
Definition: pyLock.h:122
Boost Python object wrapper.
Definition: pyObjWrapper.h:97
TF_API PyObject * ptr() const
Underlying PyObject* access.
#define TF_WARN(...)
Issue a warning, but continue execution.
Definition: diagnostic.h:149
Utilities for calling python callables.
Provide a way to call a Python callable.
Definition: pyCall.h:57