Logo ROOT  
Reference Guide
TClonesArrayPyz.cxx
Go to the documentation of this file.
1// Author: Enric Tejedor CERN 02/2019
2// Original PyROOT code by Wim Lavrijsen, LBL
3
4/*************************************************************************
5 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
6 * All rights reserved. *
7 * *
8 * For the licensing terms see $ROOTSYS/LICENSE. *
9 * For the list of contributors see $ROOTSYS/README/CREDITS. *
10 *************************************************************************/
11
12// Bindings
13#include "CPyCppyy.h"
14#include "PyROOTPythonize.h"
15#include "CPPInstance.h"
16#include "MemoryRegulator.h"
17#include "Utility.h"
18#include "PyzCppHelpers.hxx"
19
20// ROOT
21#include "TClass.h"
22#include "TClonesArray.h"
23#include "TBufferFile.h"
24
25using namespace CPyCppyy;
26
27// Clone an object into a position of a TClonesArray
28static TObject *CloneObjectInPlace(const TObject *obj, TClonesArray *cla, int index)
29{
30 // Get or create object with default constructor at index
31 char *arrObj = (char *)cla->ConstructedAt(index);
32 if (!arrObj) {
33 PyErr_Format(PyExc_RuntimeError, "Failed to create new object at index %d of TClonesArray", index);
34 return nullptr;
35 }
36
37 auto baseOffset = obj->IsA()->GetBaseClassOffset(TObject::Class());
38 auto newObj = (TObject *)(arrObj + baseOffset);
39
40 // Create a buffer where the object will be streamed
41 TBufferFile buffer(TBuffer::kWrite, cla->GetClass()->Size());
42 buffer.MapObject(obj); // register obj in map to handle self reference
43 const_cast<TObject *>(obj)->Streamer(buffer);
44
45 // Read new object from buffer
46 buffer.SetReadMode();
47 buffer.ResetMap();
48 buffer.SetBufferOffset(0);
49 buffer.MapObject(newObj); // register obj in map to handle self reference
50 newObj->Streamer(buffer);
51 newObj->ResetBit(kIsReferenced);
52 newObj->ResetBit(kCanDelete);
53
54 return newObj;
55}
56
57// Convert Python index into straight C index
58static PyObject *PyStyleIndex(PyObject *self, PyObject *index)
59{
60 Py_ssize_t idx = PyInt_AsSsize_t(index);
61 if (idx == (Py_ssize_t)-1 && PyErr_Occurred())
62 return nullptr;
63
64 // To know the capacity of a TClonesArray, we need to invoke GetSize
65 PyObject *pysize = CallPyObjMethod(self, "GetSize");
66 if (!pysize) {
67 PyErr_SetString(PyExc_RuntimeError, "unable to get the size of TClonesArray");
68 return nullptr;
69 }
70
71 Py_ssize_t size = PyInt_AsSsize_t(pysize);
72 Py_DECREF(pysize);
73 if (idx >= size || (idx < 0 && idx < -size)) {
74 PyErr_SetString(PyExc_IndexError, "index out of range");
75 return nullptr;
76 }
77
78 PyObject *pyindex = nullptr;
79 if (idx >= 0) {
80 Py_INCREF(index);
81 pyindex = index;
82 } else {
83 pyindex = PyLong_FromSsize_t(size + idx);
84 }
85
86 // We transfer ownership to the caller, who needs to decref
87 return pyindex;
88}
89
90// Customize item setting
92{
93 CPPInstance *pyobj = nullptr;
94 PyObject *idx = nullptr;
95 if (!PyArg_ParseTuple(args, const_cast<char *>("OO!:__setitem__"), &idx, &CPPInstance_Type, &pyobj))
96 return nullptr;
97
98 if (!self->GetObject()) {
99 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
100 return nullptr;
101 }
102
103 auto pyindex = PyStyleIndex((PyObject *)self, idx);
104 if (!pyindex)
105 return nullptr;
106 auto index = (int)PyLong_AsLong(pyindex);
107 Py_DECREF(pyindex);
108
109 // Get hold of the actual TClonesArray
110 auto cla = (TClonesArray *)GetTClass(self)->DynamicCast(TClonesArray::Class(), self->GetObject());
111
112 if (!cla) {
113 PyErr_SetString(PyExc_TypeError, "attempt to call with null object");
114 return nullptr;
115 }
116
117 if (Cppyy::GetScope(cla->GetClass()->GetName()) != pyobj->ObjectIsA()) {
118 PyErr_Format(PyExc_TypeError, "require object of type %s, but %s given", cla->GetClass()->GetName(),
119 Cppyy::GetFinalName(pyobj->ObjectIsA()).c_str());
120 return nullptr;
121 }
122
123 // Destroy old object, if applicable
124 if (((const TClonesArray &)*cla)[index]) {
125 cla->RemoveAt(index);
126 }
127
128 if (pyobj->GetObject()) {
129 auto object = CloneObjectInPlace((TObject *)pyobj->GetObject(), cla, index);
130 if (!object)
131 return nullptr;
132
133 // Unregister the previous pair (C++ obj, Py obj)
134 auto pyclass = PyObject_GetAttrString((PyObject*)pyobj, "__class__");
135 MemoryRegulator::UnregisterPyObject(pyobj, pyclass);
136 Py_DECREF(pyclass);
137
138 // Delete the previous C++ object (if Python owns)
139 if (pyobj->fFlags & CPPInstance::kIsOwner)
140 delete static_cast<TObject *>(pyobj->GetObject());
141
142 // Update the Python proxy with the new C++ object and register the new pair
143 pyobj->Set(object);
144 MemoryRegulator::RegisterPyObject(pyobj, object);
145
146 // A TClonesArray is always the owner of the objects it contains
147 pyobj->CppOwns();
148 }
149
151}
152
153////////////////////////////////////////////////////////////////////////////
154/// \brief Customize the setting of an item of a TClonesArray.
155/// \param[in] self Always null, since this is a module function.
156/// \param[in] args Pointer to a Python tuple object containing the arguments
157/// received from Python.
158///
159/// Inject a __setitem__ implementation that customizes the setting of an item
160/// into a TClonesArray.
161///
162/// The __setitem__ pythonization that TClonesArray inherits from TSeqCollection
163/// does not apply in this case and a redefinition is required. The reason is
164/// TClonesArray sets objects by constructing them in-place, which is impossible
165/// to support as the Python object given as value must exist a priori. It can,
166/// however, be memcpy'd and stolen.
168{
169 PyObject *pyclass = PyTuple_GetItem(args, 0);
170 Utility::AddToClass(pyclass, "__setitem__", (PyCFunction)SetItem);
172}
#define PyInt_AsSsize_t
Definition: CPyCppyy.h:229
#define Py_RETURN_NONE
Definition: CPyCppyy.h:281
void Class()
Definition: Class.C:29
_object PyObject
Definition: PyMethodBase.h:41
TClass * GetTClass(const CPyCppyy::CPPInstance *pyobj)
PyObject * CallPyObjMethod(PyObject *obj, const char *meth)
Set of helper functions that are invoked from the C++ implementation of pythonizations.
static PyObject * PyStyleIndex(PyObject *self, PyObject *index)
PyObject * SetItem(CPPInstance *self, PyObject *args)
static TObject * CloneObjectInPlace(const TObject *obj, TClonesArray *cla, int index)
@ kCanDelete
Definition: TObject.h:354
@ kIsReferenced
Definition: TObject.h:357
Cppyy::TCppType_t ObjectIsA(bool check_smart=true) const
Definition: CPPInstance.h:106
The concrete implementation of TBuffer for writing/reading to/from a ROOT file or socket.
Definition: TBufferFile.h:46
void MapObject(const TObject *obj, UInt_t offset=1) override
Add object to the fMap container.
Definition: TBufferIO.cxx:163
void ResetMap() override
Delete existing fMap and reset map counter.
Definition: TBufferIO.cxx:288
@ kWrite
Definition: TBuffer.h:72
void SetBufferOffset(Int_t offset=0)
Definition: TBuffer.h:92
void SetReadMode()
Set buffer in read mode.
Definition: TBuffer.cxx:302
void * DynamicCast(const TClass *base, void *obj, Bool_t up=kTRUE)
Cast obj of this class type up to baseclass cl if up is true.
Definition: TClass.cxx:4878
Int_t Size() const
Return size of object of this class.
Definition: TClass.cxx:5667
An array of clone (identical) objects.
Definition: TClonesArray.h:32
TClass * GetClass() const
Definition: TClonesArray.h:56
virtual TObject * RemoveAt(Int_t idx)
Remove object at index idx.
TObject * ConstructedAt(Int_t idx)
Get an object at index 'idx' that is guaranteed to have been constructed.
Mother of all ROOT objects.
Definition: TObject.h:37
bool AddToClass(PyObject *pyclass, const char *label, PyCFunction cfunc, int flags=METH_VARARGS)
Definition: Utility.cxx:169
PyTypeObject CPPInstance_Type
RPY_EXPORTED std::string GetFinalName(TCppType_t type)
RPY_EXPORTED TCppScope_t GetScope(const std::string &scope_name)
PyObject * AddSetItemTCAPyz(PyObject *self, PyObject *args)
Customize the setting of an item of a TClonesArray.