HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
pyFunction.h
Go to the documentation of this file.
1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the terms set forth in the LICENSE.txt file available at
5 // https://openusd.org/license.
6 //
7 #ifndef PXR_BASE_TF_PY_FUNCTION_H
8 #define PXR_BASE_TF_PY_FUNCTION_H
9 
10 #include "pxr/pxr.h"
11 
12 #include "pxr/base/tf/pyCall.h"
13 #include "pxr/base/tf/pyLock.h"
15 #include "pxr/base/tf/pyUtils.h"
16 
17 #include "pxr/external/boost/python/converter/from_python.hpp"
18 #include "pxr/external/boost/python/converter/registered.hpp"
19 #include "pxr/external/boost/python/converter/rvalue_from_python_data.hpp"
20 #include "pxr/external/boost/python/extract.hpp"
21 #include "pxr/external/boost/python/handle.hpp"
22 #include "pxr/external/boost/python/object.hpp"
23 
24 #include <functional>
25 
27 
28 template <typename T>
30 
31 template <typename Ret, typename... Args>
32 struct TfPyFunctionFromPython<Ret (Args...)>
33 {
34  struct Call
35  {
37 
38  Ret operator()(Args... args) {
39  TfPyLock lock;
40  return TfPyCall<Ret>(callable)(args...);
41  }
42  };
43 
44  struct CallWeak
45  {
47 
48  Ret operator()(Args... args) {
49  using namespace pxr_boost::python;
50  // Attempt to get the referenced callable object.
51  TfPyLock lock;
52  object callable(handle<>(borrowed(PyWeakref_GetObject(weak.ptr()))));
53  if (TfPyIsNone(callable)) {
54  TF_WARN("Tried to call an expired python callback");
55  return Ret();
56  }
57  return TfPyCall<Ret>(callable)(args...);
58  }
59  };
60 
61  struct CallMethod
62  {
65 
66  Ret operator()(Args... args) {
67  using namespace pxr_boost::python;
68  // Attempt to get the referenced self parameter, then build a new
69  // instance method and call it.
70  TfPyLock lock;
71  PyObject *self = PyWeakref_GetObject(weakSelf.ptr());
72  if (self == Py_None) {
73  TF_WARN("Tried to call a method on an expired python instance");
74  return Ret();
75  }
76  object method(handle<>(PyMethod_New(func.ptr(), self)));
77  return TfPyCall<Ret>(method)(args...);
78  }
79  };
80 
82  RegisterFunctionType<std::function<Ret (Args...)>>();
83  }
84 
85  template <typename FuncType>
86  static void
88  using namespace pxr_boost::python;
90  insert(&convertible, &construct<FuncType>, type_id<FuncType>());
91  }
92 
93  static void *convertible(PyObject *obj) {
94  return ((obj == Py_None) || PyCallable_Check(obj)) ? obj : 0;
95  }
96 
97  template <typename FuncType>
98  static void construct(PyObject *src, pxr_boost::python::converter::
99  rvalue_from_python_stage1_data *data) {
100  using std::string;
101  using namespace pxr_boost::python;
102 
103  void *storage = ((converter::rvalue_from_python_storage<FuncType> *)
104  data)->storage.bytes;
105 
106  if (src == Py_None) {
107  new (storage) FuncType();
108  } else {
109 
110  // In the case of instance methods, holding a strong reference will
111  // keep the bound 'self' argument alive indefinitely, which is
112  // undesirable. Unfortunately, we can't just keep a weak reference to
113  // the instance method, because python synthesizes these on-the-fly.
114  // Instead we do something like what PyQt's SIP does, and break the
115  // method into three parts: the class, the function, and the self
116  // parameter. We keep strong references to the class and the
117  // function, but a weak reference to 'self'. Then at call-time, if
118  // self has not expired, we build a new instancemethod and call it.
119  //
120  // Otherwise if the callable is a lambda (checked in a hacky way, but
121  // mirroring SIP), we take a strong reference.
122  //
123  // For all other callables, we attempt to take weak references to
124  // them. If that fails, we take a strong reference.
125  //
126  // This is all sort of contrived, but seems to have the right behavior
127  // for most usage patterns.
128 
129  object callable(handle<>(borrowed(src)));
130  PyObject *pyCallable = callable.ptr();
131  PyObject *self =
132  PyMethod_Check(pyCallable) ?
133  PyMethod_GET_SELF(pyCallable) : NULL;
134 
135  if (self) {
136  // Deconstruct the method and attempt to get a weak reference to
137  // the self instance.
138  object func(handle<>(borrowed(PyMethod_GET_FUNCTION(
139  pyCallable))));
140  object weakSelf(handle<>(PyWeakref_NewRef(self, NULL)));
141  new (storage)
142  FuncType(CallMethod{
143  TfPyObjWrapper(func),
144  TfPyObjWrapper(weakSelf)
145  });
146 
147  } else if (PyObject_HasAttrString(pyCallable, "__name__") &&
148  extract<string>(callable.attr("__name__"))()
149  == "<lambda>") {
150  // Explicitly hold on to strong references to lambdas.
151  new (storage) FuncType(Call{TfPyObjWrapper(callable)});
152  } else {
153  // Attempt to get a weak reference to the callable.
154  if (PyObject *weakCallable =
155  PyWeakref_NewRef(pyCallable, NULL)) {
156  new (storage)
157  FuncType(
158  CallWeak{TfPyObjWrapper(
159  object(handle<>(weakCallable)))});
160  } else {
161  // Fall back to taking a strong reference.
162  PyErr_Clear();
163  new (storage) FuncType(Call{TfPyObjWrapper(callable)});
164  }
165  }
166  }
167 
168  data->convertible = storage;
169  }
170 };
171 
173 
174 #endif // PXR_BASE_TF_PY_FUNCTION_H
getFileOption("OpenEXR:storage") storage
Definition: HDK_Image.dox:276
OIIO_FORCEINLINE vbool4 insert(const vbool4 &a, bool val)
Helper: substitute val for a[i].
Definition: simd.h:3556
TF_API bool TfPyIsNone(pxr_boost::python::object const &obj)
Return true iff obj is None.
#define TF_WARN
static void * convertible(PyObject *obj)
Definition: pyFunction.h:93
GLenum func
Definition: glcorearb.h:783
static void construct(PyObject *src, pxr_boost::python::converter::rvalue_from_python_stage1_data *data)
Definition: pyFunction.h:98
PXR_NAMESPACE_CLOSE_SCOPE PXR_NAMESPACE_OPEN_SCOPE
Definition: path.h:1425
#define PXR_NAMESPACE_CLOSE_SCOPE
Definition: pxr.h:74
**If you just want to fire and args
Definition: thread.h:618
Definition: format.h:1821
GLenum src
Definition: glcorearb.h:1793