Loading...
Searching...
No Matches
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 <boost/python/converter/from_python.hpp>
18#include <boost/python/converter/registered.hpp>
19#include <boost/python/converter/rvalue_from_python_data.hpp>
20#include <boost/python/extract.hpp>
21#include <boost/python/handle.hpp>
22#include <boost/python/object.hpp>
23
24#include <boost/function.hpp>
25
26#include <functional>
27
28PXR_NAMESPACE_OPEN_SCOPE
29
30template <typename T>
31struct TfPyFunctionFromPython;
32
33template <typename Ret, typename... Args>
34struct TfPyFunctionFromPython<Ret (Args...)>
35{
36 struct Call
37 {
38 TfPyObjWrapper callable;
39
40 Ret operator()(Args... args) {
41 TfPyLock lock;
42 return TfPyCall<Ret>(callable)(args...);
43 }
44 };
45
46 struct CallWeak
47 {
48 TfPyObjWrapper weak;
49
50 Ret operator()(Args... args) {
51 using namespace boost::python;
52 // Attempt to get the referenced callable object.
53 TfPyLock lock;
54 object callable(handle<>(borrowed(PyWeakref_GetObject(weak.ptr()))));
55 if (TfPyIsNone(callable)) {
56 TF_WARN("Tried to call an expired python callback");
57 return Ret();
58 }
59 return TfPyCall<Ret>(callable)(args...);
60 }
61 };
62
63 struct CallMethod
64 {
65 TfPyObjWrapper func;
66 TfPyObjWrapper weakSelf;
67
68 Ret operator()(Args... args) {
69 using namespace boost::python;
70 // Attempt to get the referenced self parameter, then build a new
71 // instance method and call it.
72 TfPyLock lock;
73 PyObject *self = PyWeakref_GetObject(weakSelf.ptr());
74 if (self == Py_None) {
75 TF_WARN("Tried to call a method on an expired python instance");
76 return Ret();
77 }
78 object method(handle<>(PyMethod_New(func.ptr(), self)));
79 return TfPyCall<Ret>(method)(args...);
80 }
81 };
82
83 TfPyFunctionFromPython() {
84 RegisterFunctionType<boost::function<Ret (Args...)>>();
85 RegisterFunctionType<std::function<Ret (Args...)>>();
86 }
87
88 template <typename FuncType>
89 static void
90 RegisterFunctionType() {
91 using namespace boost::python;
92 converter::registry::
93 insert(&convertible, &construct<FuncType>, type_id<FuncType>());
94 }
95
96 static void *convertible(PyObject *obj) {
97 return ((obj == Py_None) || PyCallable_Check(obj)) ? obj : 0;
98 }
99
100 template <typename FuncType>
101 static void construct(PyObject *src, boost::python::converter::
102 rvalue_from_python_stage1_data *data) {
103 using std::string;
104 using namespace boost::python;
105
106 void *storage = ((converter::rvalue_from_python_storage<FuncType> *)
107 data)->storage.bytes;
108
109 if (src == Py_None) {
110 new (storage) FuncType();
111 } else {
112
113 // In the case of instance methods, holding a strong reference will
114 // keep the bound 'self' argument alive indefinitely, which is
115 // undesirable. Unfortunately, we can't just keep a weak reference to
116 // the instance method, because python synthesizes these on-the-fly.
117 // Instead we do something like what PyQt's SIP does, and break the
118 // method into three parts: the class, the function, and the self
119 // parameter. We keep strong references to the class and the
120 // function, but a weak reference to 'self'. Then at call-time, if
121 // self has not expired, we build a new instancemethod and call it.
122 //
123 // Otherwise if the callable is a lambda (checked in a hacky way, but
124 // mirroring SIP), we take a strong reference.
125 //
126 // For all other callables, we attempt to take weak references to
127 // them. If that fails, we take a strong reference.
128 //
129 // This is all sort of contrived, but seems to have the right behavior
130 // for most usage patterns.
131
132 object callable(handle<>(borrowed(src)));
133 PyObject *pyCallable = callable.ptr();
134 PyObject *self =
135 PyMethod_Check(pyCallable) ?
136 PyMethod_GET_SELF(pyCallable) : NULL;
137
138 if (self) {
139 // Deconstruct the method and attempt to get a weak reference to
140 // the self instance.
141 object func(handle<>(borrowed(PyMethod_GET_FUNCTION(
142 pyCallable))));
143 object weakSelf(handle<>(PyWeakref_NewRef(self, NULL)));
144 new (storage)
145 FuncType(CallMethod{
146 TfPyObjWrapper(func),
147 TfPyObjWrapper(weakSelf)
148 });
149
150 } else if (PyObject_HasAttrString(pyCallable, "__name__") &&
151 extract<string>(callable.attr("__name__"))()
152 == "<lambda>") {
153 // Explicitly hold on to strong references to lambdas.
154 new (storage) FuncType(Call{TfPyObjWrapper(callable)});
155 } else {
156 // Attempt to get a weak reference to the callable.
157 if (PyObject *weakCallable =
158 PyWeakref_NewRef(pyCallable, NULL)) {
159 new (storage)
160 FuncType(
161 CallWeak{TfPyObjWrapper(
162 object(handle<>(weakCallable)))});
163 } else {
164 // Fall back to taking a strong reference.
165 PyErr_Clear();
166 new (storage) FuncType(Call{TfPyObjWrapper(callable)});
167 }
168 }
169 }
170
171 data->convertible = storage;
172 }
173};
174
175PXR_NAMESPACE_CLOSE_SCOPE
176
177#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:105
Boost Python object wrapper.
Definition: pyObjWrapper.h:80
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