All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
pyFunction.h
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"
14#include "pxr/base/tf/pyObjWrapper.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
26PXR_NAMESPACE_OPEN_SCOPE
27
28template <typename T>
29struct TfPyFunctionFromPython;
30
31template <typename Ret, typename... Args>
32struct TfPyFunctionFromPython<Ret (Args...)>
33{
34 struct Call
35 {
36 TfPyObjWrapper callable;
37
38 Ret operator()(Args... args) {
39 TfPyLock lock;
40 return TfPyCall<Ret>(callable)(args...);
41 }
42 };
43
44 struct CallWeak
45 {
46 TfPyObjWrapper weak;
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 {
63 TfPyObjWrapper func;
64 TfPyObjWrapper weakSelf;
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
81 TfPyFunctionFromPython() {
82 RegisterFunctionType<std::function<Ret (Args...)>>();
83 }
84
85 template <typename FuncType>
86 static void
87 RegisterFunctionType() {
88 using namespace pxr_boost::python;
89 converter::registry::
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
172PXR_NAMESPACE_CLOSE_SCOPE
173
174#endif // PXR_BASE_TF_PY_FUNCTION_H
Miscellaneous Utilities for dealing with script.
TF_API bool TfPyIsNone(pxr_boost::python::object const &obj)
Return true iff obj is None.
Convenience class for accessing the Python Global Interpreter Lock.
Definition: pyLock.h:105
Boost Python object wrapper.
Definition: pyObjWrapper.h:79
TF_API PyObject * ptr() const
Underlying PyObject* access.
#define TF_WARN(...)
Issue a warning, but continue execution.
Definition: diagnostic.h:132
Utilities for calling python callables.
Provide a way to call a Python callable.
Definition: pyCall.h:40