All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
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 
45 PXR_NAMESPACE_OPEN_SCOPE
46 
47 template <typename T>
48 struct TfPyFunctionFromPython;
49 
50 template <typename Ret, typename... Args>
51 struct 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 #if PY_MAJOR_VERSION == 2
85  TfPyObjWrapper cls;
86 #endif
87 
88  Ret operator()(Args... args) {
89  using namespace boost::python;
90  // Attempt to get the referenced self parameter, then build a new
91  // instance method and call it.
92  TfPyLock lock;
93  PyObject *self = PyWeakref_GetObject(weakSelf.ptr());
94  if (self == Py_None) {
95  TF_WARN("Tried to call a method on an expired python instance");
96  return Ret();
97  }
98 #if PY_MAJOR_VERSION == 2
99  object method(handle<>(PyMethod_New(func.ptr(), self, cls.ptr())));
100 #else
101  object method(handle<>(PyMethod_New(func.ptr(), self)));
102 #endif
103  return TfPyCall<Ret>(method)(args...);
104  }
105  };
106 
107  TfPyFunctionFromPython() {
108  RegisterFunctionType<boost::function<Ret (Args...)>>();
109  RegisterFunctionType<std::function<Ret (Args...)>>();
110  }
111 
112  template <typename FuncType>
113  static void
114  RegisterFunctionType() {
115  using namespace boost::python;
116  converter::registry::
117  insert(&convertible, &construct<FuncType>, type_id<FuncType>());
118  }
119 
120  static void *convertible(PyObject *obj) {
121  return ((obj == Py_None) || PyCallable_Check(obj)) ? obj : 0;
122  }
123 
124  template <typename FuncType>
125  static void construct(PyObject *src, boost::python::converter::
126  rvalue_from_python_stage1_data *data) {
127  using std::string;
128  using namespace boost::python;
129 
130  void *storage = ((converter::rvalue_from_python_storage<FuncType> *)
131  data)->storage.bytes;
132 
133  if (src == Py_None) {
134  new (storage) FuncType();
135  } else {
136 
137  // In the case of instance methods, holding a strong reference will
138  // keep the bound 'self' argument alive indefinitely, which is
139  // undesirable. Unfortunately, we can't just keep a weak reference to
140  // the instance method, because python synthesizes these on-the-fly.
141  // Instead we do something like what PyQt's SIP does, and break the
142  // method into three parts: the class, the function, and the self
143  // parameter. We keep strong references to the class and the
144  // function, but a weak reference to 'self'. Then at call-time, if
145  // self has not expired, we build a new instancemethod and call it.
146  //
147  // Otherwise if the callable is a lambda (checked in a hacky way, but
148  // mirroring SIP), we take a strong reference.
149  //
150  // For all other callables, we attempt to take weak references to
151  // them. If that fails, we take a strong reference.
152  //
153  // This is all sort of contrived, but seems to have the right behavior
154  // for most usage patterns.
155 
156  object callable(handle<>(borrowed(src)));
157  PyObject *pyCallable = callable.ptr();
158  PyObject *self =
159  PyMethod_Check(pyCallable) ?
160  PyMethod_GET_SELF(pyCallable) : NULL;
161 
162  if (self) {
163  // Deconstruct the method and attempt to get a weak reference to
164  // the self instance.
165 #if PY_MAJOR_VERSION == 2
166  object cls(handle<>(borrowed(PyMethod_GET_CLASS(pyCallable))));
167 #endif
168  object func(handle<>(borrowed(PyMethod_GET_FUNCTION(
169  pyCallable))));
170  object weakSelf(handle<>(PyWeakref_NewRef(self, NULL)));
171  new (storage)
172  FuncType(CallMethod{
173  TfPyObjWrapper(func),
174  TfPyObjWrapper(weakSelf)
175 #if PY_MAJOR_VERSION == 2
176  , TfPyObjWrapper(cls)
177 #endif
178  });
179 
180  } else if (PyObject_HasAttrString(pyCallable, "__name__") &&
181  extract<string>(callable.attr("__name__"))()
182  == "<lambda>") {
183  // Explicitly hold on to strong references to lambdas.
184  new (storage) FuncType(Call{TfPyObjWrapper(callable)});
185  } else {
186  // Attempt to get a weak reference to the callable.
187  if (PyObject *weakCallable =
188  PyWeakref_NewRef(pyCallable, NULL)) {
189  new (storage)
190  FuncType(
191  CallWeak{TfPyObjWrapper(
192  object(handle<>(weakCallable)))});
193  } else {
194  // Fall back to taking a strong reference.
195  PyErr_Clear();
196  new (storage) FuncType(Call{TfPyObjWrapper(callable)});
197  }
198  }
199  }
200 
201  data->convertible = storage;
202  }
203 };
204 
205 PXR_NAMESPACE_CLOSE_SCOPE
206 
207 #endif // PXR_BASE_TF_PY_FUNCTION_H
Utilities for calling python callables.
#define TF_WARN(...)
Issue a warning, but continue execution.
Definition: diagnostic.h:149
Miscellaneous Utilities for dealing with script.
Provide a way to call a Python callable.
Definition: pyCall.h:57
Boost Python object wrapper.
Definition: pyObjWrapper.h:66
TF_API bool TfPyIsNone(boost::python::object const &obj)
Return true iff obj is None.