Logo ROOT   6.14/05
Reference Guide
PyMethodBase.cxx
Go to the documentation of this file.
1 // @(#)root/tmva/pymva $Id$
2 // Authors: Omar Zapata, Lorenzo Moneta, Sergei Gleyzer 2015, Stefan Wunsch 2017
3 
4 /**********************************************************************************
5  * Project: TMVA - a Root-integrated toolkit for multivariate data analysis *
6  * Package: TMVA *
7  * Class : PyMethodBase *
8  * *
9  * Description: *
10  * Virtual base class for all MVA method based on python *
11  * *
12  **********************************************************************************/
13 
14 #include <Python.h> // Needs to be included first to avoid redefinition of _POSIX_C_SOURCE
15 #include <TMVA/PyMethodBase.h>
16 
17 #include "TMVA/DataSet.h"
18 #include "TMVA/DataSetInfo.h"
19 #include "TMVA/MsgLogger.h"
20 #include "TMVA/Results.h"
21 #include "TMVA/Timer.h"
22 
23 #include <TApplication.h>
24 
25 #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
26 #include <numpy/arrayobject.h>
27 
28 #include <fstream>
29 #include <wchar.h>
30 
31 using namespace TMVA;
32 
34 
35 // NOTE: Introduce here nothing that breaks if multiple instances
36 // of the same method share these objects, e.g., the local namespace.
40 
44 
47 
48 class PyGILRAII {
49  PyGILState_STATE m_GILState;
50 public:
51  PyGILRAII():m_GILState(PyGILState_Ensure()){}
52  ~PyGILRAII(){PyGILState_Release(m_GILState);}
53 };
54 
55 ///////////////////////////////////////////////////////////////////////////////
56 
57 PyMethodBase::PyMethodBase(const TString &jobName,
58  Types::EMVA methodType,
59  const TString &methodTitle,
60  DataSetInfo &dsi,
61  const TString &theOption ): MethodBase(jobName, methodType, methodTitle, dsi, theOption),
62  fClassifier(NULL)
63 {
64  if (!PyIsInitialized()) {
65  PyInitialize();
66  }
67 
68  // Set up private local namespace for each method instance
69  fLocalNS = PyDict_New();
70  if (!fLocalNS) {
71  Log() << kFATAL << "Can't init local namespace" << Endl;
72  }
73 }
74 
75 ///////////////////////////////////////////////////////////////////////////////
76 
78  DataSetInfo &dsi,
79  const TString &weightFile): MethodBase(methodType, dsi, weightFile),
80  fClassifier(NULL)
81 {
82  if (!PyIsInitialized()) {
83  PyInitialize();
84  }
85 
86  // Set up private local namespace for each method instance
87  fLocalNS = PyDict_New();
88  if (!fLocalNS) {
89  Log() << kFATAL << "Can't init local namespace" << Endl;
90  }
91 }
92 
93 ///////////////////////////////////////////////////////////////////////////////
94 
96 {
97 }
98 
99 ///////////////////////////////////////////////////////////////////////////////
100 /// Evaluate Python code
101 ///
102 /// \param[in] code Python code as string
103 /// \return Python object from evaluation of code line
104 ///
105 /// Take a Python code as input and evaluate it in the local namespace. Then,
106 /// return the result as Python object.
107 
109 {
110  if(!PyIsInitialized()) PyInitialize();
111  PyObject *pycode = Py_BuildValue("(sOO)", code.Data(), fGlobalNS, fLocalNS);
112  PyObject *result = PyObject_CallObject(fEval, pycode);
113  Py_DECREF(pycode);
114  return result;
115 }
116 
117 ///////////////////////////////////////////////////////////////////////////////
118 /// Initialize Python interpreter
119 ///
120 /// NOTE: We introduce a shared global namespace `fGlobalNS`, but using
121 /// a private local namespace `fLocalNS`. This prohibits the interference
122 /// of instances of the same method with the same factory, e.g., by overriding
123 /// variables in the same local namespace.
124 
126 {
128 
129  bool pyIsInitialized = PyIsInitialized();
130  if (!pyIsInitialized) {
131  Py_Initialize();
132  }
133 
134  PyGILRAII thePyGILRAII;
135 
136  if (!pyIsInitialized) {
137  _import_array();
138  }
139 
140  fMain = PyImport_AddModule("__main__");
141  if (!fMain) {
142  Log << kFATAL << "Can't import __main__" << Endl;
143  Log << Endl;
144  }
145 
146  fGlobalNS = PyModule_GetDict(fMain);
147  if (!fGlobalNS) {
148  Log << kFATAL << "Can't init global namespace" << Endl;
149  Log << Endl;
150  }
151 
152  #if PY_MAJOR_VERSION < 3
153  //preparing objects for eval
154  PyObject *bName = PyUnicode_FromString("__builtin__");
155  // Import the file as a Python module.
156  fModuleBuiltin = PyImport_Import(bName);
157  if (!fModuleBuiltin) {
158  Log << kFATAL << "Can't import __builtin__" << Endl;
159  Log << Endl;
160  }
161  #else
162  //preparing objects for eval
163  PyObject *bName = PyUnicode_FromString("builtins");
164  // Import the file as a Python module.
165  fModuleBuiltin = PyImport_Import(bName);
166  if (!fModuleBuiltin) {
167  Log << kFATAL << "Can't import builtins" << Endl;
168  Log << Endl;
169  }
170  #endif
171 
172  PyObject *mDict = PyModule_GetDict(fModuleBuiltin);
173  fEval = PyDict_GetItemString(mDict, "eval");
174  fOpen = PyDict_GetItemString(mDict, "open");
175 
176  Py_DECREF(bName);
177  Py_DECREF(mDict);
178  //preparing objects for pickle
179  PyObject *pName = PyUnicode_FromString("pickle");
180  // Import the file as a Python module.
181  fModulePickle = PyImport_Import(pName);
182  if (!fModulePickle) {
183  Log << kFATAL << "Can't import pickle" << Endl;
184  Log << Endl;
185  }
186  PyObject *pDict = PyModule_GetDict(fModulePickle);
187  fPickleDumps = PyDict_GetItemString(pDict, "dump");
188  fPickleLoads = PyDict_GetItemString(pDict, "load");
189 
190  Py_DECREF(pName);
191  Py_DECREF(pDict);
192 }
193 
194 ///////////////////////////////////////////////////////////////////////////////
195 // Finalize Python interpreter
196 
198 {
199  Py_Finalize();
200  if (fEval) Py_DECREF(fEval);
201  if (fModuleBuiltin) Py_DECREF(fModuleBuiltin);
202  if (fPickleDumps) Py_DECREF(fPickleDumps);
203  if (fPickleLoads) Py_DECREF(fPickleLoads);
204  if(fMain) Py_DECREF(fMain);//objects fGlobalNS and fLocalNS will be free here
205 }
206 
207 ///////////////////////////////////////////////////////////////////////////////
208 /// Set program name for Python interpeter
209 ///
210 /// \param[in] name Program name
211 
213 {
214  #if PY_MAJOR_VERSION < 3
215  Py_SetProgramName(const_cast<char*>(name.Data()));
216  #else
217  Py_SetProgramName((wchar_t *)name.Data());
218  #endif
219 }
220 
221 
222 ///////////////////////////////////////////////////////////////////////////////
223 
224 size_t mystrlen(const char* s) { return strlen(s); }
225 
226 ///////////////////////////////////////////////////////////////////////////////
227 
228 size_t mystrlen(const wchar_t* s) { return wcslen(s); }
229 
230 ///////////////////////////////////////////////////////////////////////////////
231 /// Get program name from Python interpreter
232 ///
233 /// \return Program name
234 
236 {
237  auto progName = ::Py_GetProgramName();
238  return std::string(progName, progName + mystrlen(progName));
239 }
240 
241 ///////////////////////////////////////////////////////////////////////////////
242 /// Check Python interpreter initialization status
243 ///
244 /// \return Boolean whether interpreter is initialized
245 
247 {
248  if (!Py_IsInitialized()) return kFALSE;
249  if (!fEval) return kFALSE;
250  if (!fModuleBuiltin) return kFALSE;
251  if (!fPickleDumps) return kFALSE;
252  if (!fPickleLoads) return kFALSE;
253  return kTRUE;
254 }
255 
256 ///////////////////////////////////////////////////////////////////////////////
257 /// Serialize Python object
258 ///
259 /// \param[in] path Path where object is written to file
260 /// \param[in] obj Python object
261 ///
262 /// The input Python object is serialized and written to a file. The Python
263 /// module `pickle` is used to do so.
264 
265 void PyMethodBase::Serialize(TString path, PyObject *obj)
266 {
267  if(!PyIsInitialized()) PyInitialize();
268 
269  PyObject *file_arg = Py_BuildValue("(ss)", path.Data(),"wb");
270  PyObject *file = PyObject_CallObject(fOpen,file_arg);
271  PyObject *model_arg = Py_BuildValue("(OO)", obj,file);
272  PyObject *model_data = PyObject_CallObject(fPickleDumps , model_arg);
273 
274  Py_DECREF(file_arg);
275  Py_DECREF(file);
276  Py_DECREF(model_arg);
277  Py_DECREF(model_data);
278 }
279 
280 ///////////////////////////////////////////////////////////////////////////////
281 /// Unserialize Python object
282 ///
283 /// \param[in] path Path to serialized Python object
284 /// \param[in] obj Python object where the unserialized Python object is loaded
285 /// \return Error code
286 
288 {
289  // Load file
290  PyObject *file_arg = Py_BuildValue("(ss)", path.Data(),"rb");
291  PyObject *file = PyObject_CallObject(fOpen,file_arg);
292  if(!file) return 1;
293 
294  // Load object from file using pickle
295  PyObject *model_arg = Py_BuildValue("(O)", file);
296  *obj = PyObject_CallObject(fPickleLoads , model_arg);
297  if(!obj) return 2;
298 
299  Py_DECREF(file_arg);
300  Py_DECREF(file);
301  Py_DECREF(model_arg);
302 
303  return 0;
304 }
305 
306 ///////////////////////////////////////////////////////////////////////////////
307 /// Execute Python code from string
308 ///
309 /// \param[in] code Python code as string
310 /// \param[in] errorMessage Error message which shall be shown if the execution fails
311 /// \param[in] start Start symbol
312 ///
313 /// Helper function to run python code from string in local namespace with
314 /// error handling
315 /// `start` defines the start symbol defined in PyRun_String (Py_eval_input,
316 /// Py_single_input, Py_file_input)
317 
318 void PyMethodBase::PyRunString(TString code, TString errorMessage, int start) {
319  fPyReturn = PyRun_String(code, start, fGlobalNS, fLocalNS);
320  if (!fPyReturn) {
321  Log() << kWARNING << "Failed to run python code: " << code << Endl;
322  Log() << kWARNING << "Python error message:" << Endl;
323  PyErr_Print();
324  Log() << kFATAL << errorMessage << Endl;
325  }
326 }
PyMethodBase(const TString &jobName, Types::EMVA methodType, const TString &methodTitle, DataSetInfo &dsi, const TString &theOption="")
MsgLogger & Endl(MsgLogger &ml)
Definition: MsgLogger.h:158
PyObject * fClassifier
Definition: PyMethodBase.h:120
PyObject * fPyReturn
Definition: PyMethodBase.h:126
static PyObject * fModulePickle
Definition: PyMethodBase.h:137
MsgLogger & Log() const
Definition: Configurable.h:122
Virtual base Class for all MVA method.
Definition: MethodBase.h:109
int Int_t
Definition: RtypesCore.h:41
static void Serialize(TString file, PyObject *classifier)
Serialize Python object.
static int PyIsInitialized()
Check Python interpreter initialization status.
static void PyInitialize()
Initialize Python interpreter.
void PyRunString(TString code, TString errorMessage="Failed to run python code", int start=Py_single_input)
Execute Python code from string.
static PyObject * fEval
Definition: PyMethodBase.h:133
PyObject * Eval(TString code)
Evaluate Python code.
Class that contains all the data information.
Definition: DataSetInfo.h:60
static void PyFinalize()
static TString Py_GetProgramName()
Get program name from Python interpreter.
static void PySetProgramName(TString name)
Set program name for Python interpeter.
static PyObject * fOpen
Definition: PyMethodBase.h:134
const Bool_t kFALSE
Definition: RtypesCore.h:88
size_t mystrlen(const char *s)
static PyObject * fModuleBuiltin
Definition: PyMethodBase.h:132
#define ClassImp(name)
Definition: Rtypes.h:359
static Int_t UnSerialize(TString file, PyObject **obj)
Unserialize Python object.
static constexpr double s
ostringstream derivative to redirect and format output
Definition: MsgLogger.h:59
Abstract ClassifierFactory template that handles arbitrary types.
static PyObject * fPickleLoads
Definition: PyMethodBase.h:139
static PyObject * fPickleDumps
Definition: PyMethodBase.h:138
Definition: file.py:1
virtual ~PyMethodBase()
PyObject * fLocalNS
Definition: PyMethodBase.h:143
static PyObject * fMain
Definition: PyMethodBase.h:141
const Bool_t kTRUE
Definition: RtypesCore.h:87
char name[80]
Definition: TGX11.cxx:109
static PyObject * fGlobalNS
Definition: PyMethodBase.h:142
_object PyObject
Definition: TPyArg.h:20