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