Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
Pythonize.cxx
Go to the documentation of this file.
1// Bindings
2#include "CPyCppyy.h"
3#include "Pythonize.h"
4#include "Converters.h"
5#include "CPPInstance.h"
6#include "CPPFunction.h"
7#include "CPPOverload.h"
8#include "CustomPyTypes.h"
9#include "LowLevelViews.h"
10#include "ProxyWrappers.h"
11#include "PyCallable.h"
12#include "PyStrings.h"
13#include "TypeManip.h"
14#include "Utility.h"
15
16// Standard
17#include <algorithm>
18#include <complex>
19#include <set>
20#include <stdexcept>
21#include <sstream>
22#include <string>
23#include <utility>
24
25
26//- data and local helpers ---------------------------------------------------
27namespace CPyCppyy {
28 extern PyObject* gThisModule;
29 std::map<std::string, std::vector<PyObject*>> &pythonizations();
30}
31
32namespace {
33
34// for convenience
35using namespace CPyCppyy;
36
37//-----------------------------------------------------------------------------
39// prevents calls to Py_TYPE(pyclass)->tp_getattr, which is unnecessary for our
40// purposes here and could tickle problems w/ spurious lookups into ROOT meta
42 if (dct) {
45 if (attr) {
48 return ret;
49 }
50 }
52 return false;
53}
54
56{
57 // Check base classes in the MRO (skipping the class itself) for a CPyCppyy overload.
58 PyObject *mro = ((PyTypeObject *)pyclass)->tp_mro;
59 if (mro && PyTuple_Check(mro)) {
60 for (Py_ssize_t i = 1; i < PyTuple_GET_SIZE(mro); ++i) {
61 if (HasAttrDirect(PyTuple_GET_ITEM(mro, i), pyname, /*mustBeCPyCppyy=*/true))
62 return true;
63 }
64 }
65 return false;
66}
67
69// get an attribute without causing getattr lookups
71 if (dct) {
74 return attr;
75 }
76 return nullptr;
77}
78
79//-----------------------------------------------------------------------------
80inline bool IsTemplatedSTLClass(const std::string& name, const std::string& klass) {
81// Scan the name of the class and determine whether it is a template instantiation.
82 auto pos = name.find(klass);
83 return (pos == 0 || pos == 5) && name.find("::", name.rfind(">")) == std::string::npos;
84}
85
86// to prevent compiler warnings about const char* -> char*
87inline PyObject* CallPyObjMethod(PyObject* obj, const char* meth)
88{
89// Helper; call method with signature: obj->meth().
90 Py_INCREF(obj);
91 PyObject* result = PyObject_CallMethod(obj, const_cast<char*>(meth), const_cast<char*>(""));
92 Py_DECREF(obj);
93 return result;
94}
95
96//-----------------------------------------------------------------------------
97inline PyObject* CallPyObjMethod(PyObject* obj, const char* meth, PyObject* arg1)
98{
99// Helper; call method with signature: obj->meth(arg1).
100 Py_INCREF(obj);
102 obj, const_cast<char*>(meth), const_cast<char*>("O"), arg1);
103 Py_DECREF(obj);
104 return result;
105}
106
107//-----------------------------------------------------------------------------
109{
110// Helper; converts python index into straight C index.
112 if (idx == (Py_ssize_t)-1 && PyErr_Occurred())
113 return nullptr;
114
116 if (idx >= size || (idx < 0 && idx < -size)) {
117 PyErr_SetString(PyExc_IndexError, "index out of range");
118 return nullptr;
119 }
120
121 PyObject* pyindex = nullptr;
122 if (idx >= 0) {
124 pyindex = index;
125 } else
127
128 return pyindex;
129}
130
131//-----------------------------------------------------------------------------
132inline bool AdjustSlice(const Py_ssize_t nlen, Py_ssize_t& start, Py_ssize_t& stop, Py_ssize_t& step)
133{
134// Helper; modify slice range to match the container.
135 if ((step > 0 && stop <= start) || (step < 0 && start <= stop))
136 return false;
137
138 if (start < 0) start = 0;
139 if (start >= nlen) start = nlen-1;
140 if (step >= nlen) step = nlen;
141
142 stop = step > 0 ? std::min(nlen, stop) : (stop >= 0 ? stop : -1);
143 return true;
144}
145
146//-----------------------------------------------------------------------------
148{
149// Helper; call method with signature: meth(pyindex).
152 if (!pyindex) {
154 return nullptr;
155 }
156
160 return result;
161}
162
163//- "smart pointer" behavior ---------------------------------------------------
165{
167 PyErr_SetString(PyExc_TypeError, "getattr(): attribute name must be string");
168
170 if (!pyptr)
171 return nullptr;
172
173// prevent a potential infinite loop
174 if (Py_TYPE(pyptr) == Py_TYPE(self)) {
177 PyErr_Format(PyExc_AttributeError, "%s object has no attribute \'%s\'",
181
183 return nullptr;
184 }
185
188 return result;
189}
190
192{
193// Follow operator*() if present (available in python as __deref__), so that
194// smart pointers behave as expected.
196 // TODO: these calls come from TemplateProxy and are unlikely to be needed in practice,
197 // whereas as-is, they can accidentally dereference the result of end() on some STL
198 // containers. Obviously, this is a dumb hack that should be resolved more fundamentally.
200 return nullptr;
201 }
202
203
205}
206
207//-----------------------------------------------------------------------------
209{
210// Follow operator->() if present (available in python as __follow__), so that
211// smart pointers behave as expected.
213}
214
215//- pointer checking bool converter -------------------------------------------
217{
218 if (!CPPInstance_Check(self)) {
219 PyErr_SetString(PyExc_TypeError, "C++ object proxy expected");
220 return nullptr;
221 }
222
223 if (!((CPPInstance*)self)->GetObject())
225
227}
228
229//- vector behavior as primitives ----------------------------------------------
230#if PY_VERSION_HEX < 0x03040000
231#define PyObject_LengthHint _PyObject_LengthHint
232#endif
233
234// TODO: can probably use the below getters in the InitializerListConverter
235struct ItemGetter {
236 ItemGetter(PyObject* pyobj) : fPyObject(pyobj) { Py_INCREF(fPyObject); }
237 virtual ~ItemGetter() { Py_DECREF(fPyObject); }
238 virtual Py_ssize_t size() = 0;
239 virtual PyObject* get() = 0;
240 PyObject* fPyObject;
241};
242
243struct CountedItemGetter : public ItemGetter {
244 CountedItemGetter(PyObject* pyobj) : ItemGetter(pyobj), fCur(0) {}
245 Py_ssize_t fCur;
246};
247
248struct TupleItemGetter : public CountedItemGetter {
249 using CountedItemGetter::CountedItemGetter;
250 Py_ssize_t size() override { return PyTuple_GET_SIZE(fPyObject); }
251 PyObject* get() override {
252 if (fCur < PyTuple_GET_SIZE(fPyObject)) {
253 PyObject* item = PyTuple_GET_ITEM(fPyObject, fCur++);
255 return item;
256 }
257 PyErr_SetString(PyExc_StopIteration, "end of tuple");
258 return nullptr;
259 }
260};
261
262struct ListItemGetter : public CountedItemGetter {
263 using CountedItemGetter::CountedItemGetter;
264 Py_ssize_t size() override { return PyList_GET_SIZE(fPyObject); }
265 PyObject* get() override {
266 if (fCur < PyList_GET_SIZE(fPyObject)) {
267 PyObject* item = PyList_GET_ITEM(fPyObject, fCur++);
269 return item;
270 }
271 PyErr_SetString(PyExc_StopIteration, "end of list");
272 return nullptr;
273 }
274};
275
276struct SequenceItemGetter : public CountedItemGetter {
277 using CountedItemGetter::CountedItemGetter;
278 Py_ssize_t size() override {
279 Py_ssize_t sz = PySequence_Size(fPyObject);
280 if (sz < 0) {
281 PyErr_Clear();
282 return PyObject_LengthHint(fPyObject, 8);
283 }
284 return sz;
285 }
286 PyObject* get() override { return PySequence_GetItem(fPyObject, fCur++); }
287};
288
289struct IterItemGetter : public ItemGetter {
290 using ItemGetter::ItemGetter;
291 Py_ssize_t size() override { return PyObject_LengthHint(fPyObject, 8); }
292 PyObject* get() override { return (*(Py_TYPE(fPyObject)->tp_iternext))(fPyObject); }
293};
294
295static ItemGetter* GetGetter(PyObject* args)
296{
297// Create an ItemGetter to loop over the iterable argument, if any.
298 ItemGetter* getter = nullptr;
299
300 if (PyTuple_GET_SIZE(args) == 1) {
301 PyObject* fi = PyTuple_GET_ITEM(args, 0);
303 return nullptr; // do not accept string to fill std::vector<char>
304
305 // TODO: this only tests for new-style buffers, which is too strict, but a
306 // generic check for Py_TYPE(fi)->tp_as_buffer is too loose (note that the
307 // main use case is numpy, which offers the new interface)
309 return nullptr;
310
312 getter = new TupleItemGetter(fi);
313 else if (PyList_CheckExact(fi))
314 getter = new ListItemGetter(fi);
315 else if (PySequence_Check(fi))
316 getter = new SequenceItemGetter(fi);
317 else {
319 if (iter) {
320 getter = new IterItemGetter{iter};
321 Py_DECREF(iter);
322 }
323 else PyErr_Clear();
324 }
325 }
326
327 return getter;
328}
329
330namespace {
331
333{
334 static bool compiled = false;
335
336 if (compiled)
337 return;
338
339 compiled = true;
340
341 auto code = R"(
342namespace __cppyy_internal {
343
344template <class T>
345struct ptr_iterator {
346 T *cur;
347 T *end;
348
349 ptr_iterator(T *c, T *e) : cur(c), end(e) {}
350
351 T &operator*() const { return *cur; }
352 ptr_iterator &operator++()
353 {
354 ++cur;
355 return *this;
356 }
357 bool operator==(const ptr_iterator &other) const { return cur == other.cur; }
358 bool operator!=(const ptr_iterator &other) const { return !(*this == other); }
359};
360
361template <class T>
362ptr_iterator<T> make_iter(T *begin, T *end)
363{
364 return {begin, end};
365}
366
367} // namespace __cppyy_internal
368
369// Note: for const span<T>, T is const-qualified here
370template <class T>
371auto __cppyy_internal_begin(T &s) noexcept
372{
373 return __cppyy_internal::make_iter(s.data(), s.data() + s.size());
374}
375
376// Note: for const span<T>, T is const-qualified here
377template <class T>
378auto __cppyy_internal_end(T &s) noexcept
379{
380 // end iterator = begin iterator with cur == end
381 return __cppyy_internal::make_iter(s.data() + s.size(), s.data() + s.size());
382}
383 )";
384 Cppyy::Compile(code, /*silent*/ true);
385}
386
388{
389 static PyObject *pyFunc = nullptr;
390 if (!pyFunc) {
393 pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_begin");
394 if (!pyFunc) {
395 PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper "
396 "'__cppyy_internal_begin' for std::span pythonization");
397 }
398 }
399 return pyFunc;
400}
401
403{
404 static PyObject *pyFunc = nullptr;
405 if (!pyFunc) {
408 pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_end");
409 if (!pyFunc) {
410 PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper "
411 "'__cppyy_internal_end' for std::span pythonization");
412 }
413 }
414 return pyFunc;
415}
416
417} // namespace
418
420{
421 auto begin = spanBegin();
422 if (!begin)
423 return nullptr;
424 return PyObject_CallOneArg(begin, self);
425}
426
428{
429 auto end = spanEnd();
430 if (!end)
431 return nullptr;
432 return PyObject_CallOneArg(end, self);
433}
434
435static bool FillVector(PyObject* vecin, PyObject* args, ItemGetter* getter)
436{
437 Py_ssize_t sz = getter->size();
438 if (sz < 0)
439 return false;
440
441// reserve memory as applicable
442 if (0 < sz) {
443 PyObject* res = PyObject_CallMethod(vecin, (char*)"reserve", (char*)"n", sz);
444 Py_DECREF(res);
445 } else // i.e. sz == 0, so empty container: done
446 return true;
447
448 bool fill_ok = true;
449
450// two main options: a list of lists (or tuples), or a list of objects; the former
451// are emplace_back'ed, the latter push_back'ed
453 if (!fi) PyErr_Clear();
455 // use emplace_back to construct the vector entries one by one
456 PyObject* eb_call = PyObject_GetAttrString(vecin, (char*)"emplace_back");
458 bool value_is_vector = false;
460 // if the value_type is a vector, then allow for initialization from sequences
461 if (std::string(CPyCppyy_PyText_AsString(vtype)).rfind("std::vector", 0) != std::string::npos)
462 value_is_vector = true;
463 } else
464 PyErr_Clear();
466
467 if (eb_call) {
469 for (int i = 0; /* until break */; ++i) {
470 PyObject* item = getter->get();
471 if (item) {
473 eb_args = PyTuple_New(1);
475 } else if (PyTuple_CheckExact(item)) {
476 eb_args = item;
477 } else if (PyList_CheckExact(item)) {
480 for (Py_ssize_t j = 0; j < isz; ++j) {
484 }
486 } else {
488 PyErr_Format(PyExc_TypeError, "argument %d is not a tuple or list", i);
489 fill_ok = false;
490 break;
491 }
494 if (!ebres) {
495 fill_ok = false;
496 break;
497 }
499 } else {
500 if (PyErr_Occurred()) {
503 fill_ok = false;
504 else { PyErr_Clear(); }
505 }
506 break;
507 }
508 }
510 }
511 } else {
512 // use push_back to add the vector entries one by one
513 PyObject* pb_call = PyObject_GetAttrString(vecin, (char*)"push_back");
514 if (pb_call) {
515 for (;;) {
516 PyObject* item = getter->get();
517 if (item) {
520 if (!pbres) {
521 fill_ok = false;
522 break;
523 }
525 } else {
526 if (PyErr_Occurred()) {
529 fill_ok = false;
530 else { PyErr_Clear(); }
531 }
532 break;
533 }
534 }
536 }
537 }
538 Py_XDECREF(fi);
539
540 return fill_ok;
541}
542
543PyObject* VectorIAdd(PyObject* self, PyObject* args, PyObject* /* kwds */)
544{
545// Implement fast __iadd__ on std::vector (generic __iadd__ is in Python)
546 ItemGetter* getter = GetGetter(args);
547
548 if (getter) {
549 bool fill_ok = FillVector(self, args, getter);
550 delete getter;
551
552 if (!fill_ok)
553 return nullptr;
554
556 return self;
557 }
558
559// if no getter, it could still be b/c we have a buffer (e.g. numpy); looping over
560// a buffer here is slow, so use insert() instead
561 if (PyTuple_GET_SIZE(args) == 1) {
562 PyObject* fi = PyTuple_GET_ITEM(args, 0);
565 if (vend) {
566 // when __iadd__ is overriden, the operation does not end with
567 // calling the __iadd__ method, but also assigns the result to the
568 // lhs of the iadd. For example, performing vec += arr, Python
569 // first calls our override, and then does vec = vec.iadd(arr).
572
573 if (!it)
574 return nullptr;
575
576 Py_DECREF(it);
577 // Assign the result of the __iadd__ override to the std::vector
579 return self;
580 }
581 }
582 }
583
584 if (!PyErr_Occurred())
585 PyErr_SetString(PyExc_TypeError, "argument is not iterable");
586 return nullptr; // error already set
587}
588
589
590PyObject* VectorInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
591{
592// Specialized vector constructor to allow construction from containers; allowing
593// such construction from initializer_list instead would possible, but can be
594// error-prone. This use case is common enough for std::vector to implement it
595// directly, except for arrays (which can be passed wholesale) and strings (which
596// won't convert properly as they'll be seen as buffers)
597
598 ItemGetter* getter = GetGetter(args);
599
600 if (getter) {
601 // construct an empty vector, then back-fill it
603 if (!result) {
604 delete getter;
605 return nullptr;
606 }
607
608 bool fill_ok = FillVector(self, args, getter);
609 delete getter;
610
611 if (!fill_ok) {
613 return nullptr;
614 }
615
616 return result;
617 }
618
619// The given argument wasn't iterable: simply forward to regular constructor
621 if (realInit) {
622 PyObject* result = PyObject_Call(realInit, args, nullptr);
624 return result;
625 }
626
627 return nullptr;
628}
629
630//---------------------------------------------------------------------------
632{
633 PyObject* pydata = CallPyObjMethod(self, "__real_data");
635 return pydata;
636
638 if (!pylen) {
639 PyErr_Clear();
640 return pydata;
641 }
642
643 long clen = PyInt_AsLong(pylen);
645
647 ((CPPInstance*)pydata)->CastToArray(clen);
648 return pydata;
649 }
650
651 ((LowLevelView*)pydata)->resize((size_t)clen);
652 return pydata;
653}
654
655
656// This function implements __array__, added to std::vector python proxies and causes
657// a bug (see explanation at Utility::AddToClass(pyclass, "__array__"...) in CPyCppyy::Pythonize)
658// The recursive nature of this function, passes each subarray (pydata) to the next call and only
659// the final buffer is cast to a lowlevel view and resized (in VectorData), resulting in only the
660// first 1D array to be returned. See https://github.com/root-project/root/issues/17729
661// It is temporarily removed to prevent errors due to -Wunused-function, since it is no longer added.
662#if 0
663//---------------------------------------------------------------------------
665{
666 PyObject* pydata = VectorData(self, nullptr);
671 return newarr;
672}
673#endif
674
675//-----------------------------------------------------------------------------
676static PyObject* vector_iter(PyObject* v) {
678 if (!vi) return nullptr;
679
680 vi->ii_container = v;
681
682// tell the iterator code to set a life line if this container is a temporary
683 vi->vi_flags = vectoriterobject::kDefault;
684#if PY_VERSION_HEX >= 0x030e0000
686#else
687 if (Py_REFCNT(v) <= 1 || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue))
688#endif
690
691 Py_INCREF(v);
692
694 if (pyvalue_type) {
696 if (pyvalue_size) {
697 vi->vi_stride = PyLong_AsLong(pyvalue_size);
699 } else {
700 PyErr_Clear();
701 vi->vi_stride = 0;
702 }
703
705 std::string value_type = CPyCppyy_PyText_AsString(pyvalue_type);
706 value_type = Cppyy::ResolveName(value_type);
707 vi->vi_klass = Cppyy::GetScope(value_type);
708 if (!vi->vi_klass) {
709 // look for a special case of pointer to a class type (which is a builtin, but it
710 // is more useful to treat it polymorphically by allowing auto-downcasts)
711 const std::string& clean_type = TypeManip::clean_type(value_type, false, false);
713 if (c && TypeManip::compound(value_type) == "*") {
714 vi->vi_klass = c;
716 }
717 }
718 if (vi->vi_klass) {
719 vi->vi_converter = nullptr;
720 if (!vi->vi_flags) {
721 if (value_type.back() != '*') // meaning, object stored by-value
723 }
724 } else
725 vi->vi_converter = CPyCppyy::CreateConverter(value_type);
726 if (!vi->vi_stride) vi->vi_stride = Cppyy::SizeOf(value_type);
727
728 } else if (CPPScope_Check(pyvalue_type)) {
729 vi->vi_klass = ((CPPClass*)pyvalue_type)->fCppType;
730 vi->vi_converter = nullptr;
731 if (!vi->vi_stride) vi->vi_stride = Cppyy::SizeOf(vi->vi_klass);
732 if (!vi->vi_flags) vi->vi_flags = vectoriterobject::kNeedLifeLine;
733 }
734
735 PyObject* pydata = CallPyObjMethod(v, "__real_data");
736 if (!pydata || Utility::GetBuffer(pydata, '*', 1, vi->vi_data, false) == 0)
737 vi->vi_data = CPPInstance_Check(pydata) ? ((CPPInstance*)pydata)->GetObjectRaw() : nullptr;
739
740 } else {
741 PyErr_Clear();
742 vi->vi_data = nullptr;
743 vi->vi_stride = 0;
744 vi->vi_converter = nullptr;
745 vi->vi_klass = 0;
746 vi->vi_flags = 0;
747 }
748
750
751 vi->ii_pos = 0;
752 vi->ii_len = PySequence_Size(v);
753
755 return (PyObject*)vi;
756}
757
759{
760// Implement python's __getitem__ for std::vector<>s.
761 if (PySlice_Check(index)) {
762 if (!self->GetObject()) {
763 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
764 return nullptr;
765 }
766
769
770 Py_ssize_t start, stop, step;
772
774 if (!AdjustSlice(nlen, start, stop, step))
775 return nseq;
776
777 const Py_ssize_t sign = step < 0 ? -1 : 1;
778 for (Py_ssize_t i = start; i*sign < stop*sign; i += step) {
781 CallPyObjMethod(nseq, "push_back", item);
784 }
785
786 return nseq;
787 }
788
790}
791
792
794
796{
797// std::vector<bool> is a special-case in C++, and its return type depends on
798// the compiler: treat it special here as well
799 if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) {
801 "require object of type std::vector<bool>, but %s given",
802 Cppyy::GetScopedFinalName(self->ObjectIsA()).c_str());
803 return nullptr;
804 }
805
806 if (!self->GetObject()) {
807 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
808 return nullptr;
809 }
810
811 if (PySlice_Check(idx)) {
814
815 Py_ssize_t start, stop, step;
818 if (!AdjustSlice(nlen, start, stop, step))
819 return nseq;
820
821 const Py_ssize_t sign = step < 0 ? -1 : 1;
822 for (Py_ssize_t i = start; i*sign < stop*sign; i += step) {
825 CallPyObjMethod(nseq, "push_back", item);
828 }
829
830 return nseq;
831 }
832
834 if (!pyindex)
835 return nullptr;
836
839
840// get hold of the actual std::vector<bool> (no cast, as vector is never a base)
841 std::vector<bool>* vb = (std::vector<bool>*)self->GetObject();
842
843// finally, return the value
844 if (bool((*vb)[index]))
847}
848
850{
851// std::vector<bool> is a special-case in C++, and its return type depends on
852// the compiler: treat it special here as well
853 if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) {
855 "require object of type std::vector<bool>, but %s given",
856 Cppyy::GetScopedFinalName(self->ObjectIsA()).c_str());
857 return nullptr;
858 }
859
860 if (!self->GetObject()) {
861 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
862 return nullptr;
863 }
864
865 int bval = 0; PyObject* idx = nullptr;
866 if (!PyArg_ParseTuple(args, const_cast<char*>("Oi:__setitem__"), &idx, &bval))
867 return nullptr;
868
870 if (!pyindex)
871 return nullptr;
872
875
876// get hold of the actual std::vector<bool> (no cast, as vector is never a base)
877 std::vector<bool>* vb = (std::vector<bool>*)self->GetObject();
878
879// finally, set the value
880 (*vb)[index] = (bool)bval;
881
883}
884
885
886//- array behavior as primitives ----------------------------------------------
887PyObject* ArrayInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
888{
889// std::array is normally only constructed using aggregate initialization, which
890// is a concept that does not exist in python, so use this custom constructor to
891// to fill the array using setitem
892
893 if (args && PyTuple_GET_SIZE(args) == 1 && PySequence_Check(PyTuple_GET_ITEM(args, 0))) {
894 // construct the empty array, then fill it
896 if (!result)
897 return nullptr;
898
899 PyObject* items = PyTuple_GET_ITEM(args, 0);
901 if (PySequence_Size(self) != fillsz) {
902 PyErr_Format(PyExc_ValueError, "received sequence of size %zd where %zd expected",
905 return nullptr;
906 }
907
909 for (Py_ssize_t i = 0; i < fillsz; ++i) {
915 if (!sires) {
918 return nullptr;
919 } else
921 }
923
924 return result;
925 } else
926 PyErr_Clear();
927
928// The given argument wasn't iterable: simply forward to regular constructor
930 if (realInit) {
931 PyObject* result = PyObject_Call(realInit, args, nullptr);
933 return result;
934 }
935
936 return nullptr;
937}
938
939
940//- map behavior as primitives ------------------------------------------------
942{
943// construct an empty map, then fill it with the key, value pairs
945 if (!result)
946 return nullptr;
947
949 for (Py_ssize_t i = 0; i < PySequence_Size(pairs); ++i) {
951 PyObject* sires = nullptr;
952 if (pair && PySequence_Check(pair) && PySequence_Size(pair) == 2) {
953 PyObject* key = PySequence_GetItem(pair, 0);
957 Py_DECREF(key);
958 }
959 Py_DECREF(pair);
960 if (!sires) {
963 if (!PyErr_Occurred())
964 PyErr_SetString(PyExc_TypeError, "Failed to fill map (argument not a dict or sequence of pairs)");
965 return nullptr;
966 } else
968 }
970
971 return result;
972}
973
974PyObject* MapInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
975{
976// Specialized map constructor to allow construction from mapping containers and
977// from tuples of pairs ("initializer_list style").
978
979// PyMapping_Check is not very discriminatory, as it basically only checks for the
980// existence of __getitem__, hence the most common cases of tuple and list are
981// dropped straight-of-the-bat (the PyMapping_Items call will fail on them).
982 if (PyTuple_GET_SIZE(args) == 1 && PyMapping_Check(PyTuple_GET_ITEM(args, 0)) && \
984 PyObject* assoc = PyTuple_GET_ITEM(args, 0);
985#if PY_VERSION_HEX < 0x03000000
986 // to prevent warning about literal string, expand macro
987 PyObject* items = PyObject_CallMethod(assoc, (char*)"items", nullptr);
988#else
989 // in p3, PyMapping_Items isn't a macro, but a function that short-circuits dict
991#endif
992 if (items && PySequence_Check(items)) {
995 return result;
996 }
997
999 PyErr_Clear();
1000
1001 // okay to fall through as long as 'self' has not been created (is done in MapFromPairs)
1002 }
1003
1004// tuple of pairs case (some mapping types are sequences)
1005 if (PyTuple_GET_SIZE(args) == 1 && PySequence_Check(PyTuple_GET_ITEM(args, 0)))
1006 return MapFromPairs(self, PyTuple_GET_ITEM(args, 0));
1007
1008// The given argument wasn't a mapping or tuple of pairs: forward to regular constructor
1010 if (realInit) {
1011 PyObject* result = PyObject_Call(realInit, args, nullptr);
1013 return result;
1014 }
1015
1016 return nullptr;
1017}
1018
1019#if __cplusplus <= 202002L
1021{
1022// Implement python's __contains__ for std::map/std::set
1023 PyObject* result = nullptr;
1024
1025 PyObject* iter = CallPyObjMethod(self, "find", obj);
1026 if (CPPInstance_Check(iter)) {
1028 if (CPPInstance_Check(end)) {
1029 if (!PyObject_RichCompareBool(iter, end, Py_EQ)) {
1031 result = Py_True;
1032 }
1033 }
1034 Py_XDECREF(end);
1035 }
1036 Py_XDECREF(iter);
1037
1038 if (!result) {
1039 PyErr_Clear(); // e.g. wrong argument type, which should always lead to False
1041 result = Py_False;
1042 }
1043
1044 return result;
1045}
1046#endif
1047
1048
1049//- set behavior as primitives ------------------------------------------------
1050PyObject* SetInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
1051{
1052// Specialized set constructor to allow construction from Python sets.
1053 if (PyTuple_GET_SIZE(args) == 1 && PySet_Check(PyTuple_GET_ITEM(args, 0))) {
1054 PyObject* pyset = PyTuple_GET_ITEM(args, 0);
1055
1056 // construct an empty set, then fill it
1058 if (!result)
1059 return nullptr;
1060
1062 if (iter) {
1063 PyObject* ins_call = PyObject_GetAttrString(self, (char*)"insert");
1064
1065 IterItemGetter getter{iter};
1066 Py_DECREF(iter);
1067
1068 PyObject* item = getter.get();
1069 while (item) {
1071 Py_DECREF(item);
1072 if (!insres) {
1075 return nullptr;
1076 } else
1078 item = getter.get();
1079 }
1081 }
1082
1083 return result;
1084 }
1085
1086// The given argument wasn't iterable: simply forward to regular constructor
1088 if (realInit) {
1089 PyObject* result = PyObject_Call(realInit, args, nullptr);
1091 return result;
1092 }
1093
1094 return nullptr;
1095}
1096
1097
1098//- STL container iterator support --------------------------------------------
1099static const ptrdiff_t PS_END_ADDR = 7; // non-aligned address, so no clash
1100static const ptrdiff_t PS_FLAG_ADDR = 11; // id.
1101static const ptrdiff_t PS_COLL_ADDR = 13; // id.
1102
1104{
1105// Implement python's __iter__ for low level views used through STL-type begin()/end()
1107
1108 if (LowLevelView_Check(iter)) {
1109 // builtin pointer iteration: can only succeed if a size is available
1111 if (sz == -1) {
1112 Py_DECREF(iter);
1113 return nullptr;
1114 }
1115 PyObject* lliter = Py_TYPE(iter)->tp_iter(iter);
1116 ((indexiterobject*)lliter)->ii_len = sz;
1117 Py_DECREF(iter);
1118 return lliter;
1119 }
1120
1121 if (iter) {
1122 Py_DECREF(iter);
1123 PyErr_SetString(PyExc_TypeError, "unrecognized iterator type for low level views");
1124 }
1125
1126 return nullptr;
1127}
1128
1130{
1131// Implement python's __iter__ for std::iterator<>s
1133 if (iter) {
1135 if (end) {
1136 if (CPPInstance_Check(iter)) {
1137 // use the data member cache to store extra state on the iterator object,
1138 // without it being visible on the Python side
1139 auto& dmc = ((CPPInstance*)iter)->GetDatamemberCache();
1140 dmc.push_back(std::make_pair(PS_END_ADDR, end));
1141
1142 // set a flag, indicating first iteration (reset in __next__)
1144 dmc.push_back(std::make_pair(PS_FLAG_ADDR, Py_False));
1145
1146 // make sure the iterated over collection remains alive for the duration
1147 Py_INCREF(self);
1148 dmc.push_back(std::make_pair(PS_COLL_ADDR, self));
1149 } else {
1150 // could store "end" on the object's dictionary anyway, but if end() returns
1151 // a user-customized object, then its __next__ is probably custom, too
1152 Py_DECREF(end);
1153 }
1154 }
1155 }
1156 return iter;
1157}
1158
1159//- generic iterator support over a sequence with operator[] and size ---------
1160//-----------------------------------------------------------------------------
1161static PyObject* index_iter(PyObject* c) {
1163 if (!ii) return nullptr;
1164
1165 Py_INCREF(c);
1166 ii->ii_container = c;
1167 ii->ii_pos = 0;
1168 ii->ii_len = PySequence_Size(c);
1169
1171 return (PyObject*)ii;
1172}
1173
1174
1175//- safe indexing for STL-like vector w/o iterator dictionaries ---------------
1176/* replaced by indexiterobject iteration, but may still have some future use ...
1177PyObject* CheckedGetItem(PyObject* self, PyObject* obj)
1178{
1179// Implement a generic python __getitem__ for STL-like classes that are missing the
1180// reflection info for their iterators. This is then used for iteration by means of
1181// consecutive indices, it such index is of integer type.
1182 Py_ssize_t size = PySequence_Size(self);
1183 Py_ssize_t idx = PyInt_AsSsize_t(obj);
1184 if ((size == (Py_ssize_t)-1 || idx == (Py_ssize_t)-1) && PyErr_Occurred()) {
1185 // argument conversion problem: let method itself resolve anew and report
1186 PyErr_Clear();
1187 return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj);
1188 }
1189
1190 bool inbounds = false;
1191 if (idx < 0) idx += size;
1192 if (0 <= idx && 0 <= size && idx < size)
1193 inbounds = true;
1194
1195 if (inbounds)
1196 return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj);
1197 else
1198 PyErr_SetString( PyExc_IndexError, "index out of range" );
1199
1200 return nullptr;
1201}*/
1202
1203
1204//- pair as sequence to allow tuple unpacking --------------------------------
1206{
1207// For std::map<> iteration, unpack std::pair<>s into tuples for the loop.
1208 long idx = PyLong_AsLong(pyindex);
1209 if (idx == -1 && PyErr_Occurred())
1210 return nullptr;
1211
1212 if (!CPPInstance_Check(self) || !((CPPInstance*)self)->GetObject()) {
1213 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
1214 return nullptr;
1215 }
1216
1217 if ((int)idx == 0)
1219 else if ((int)idx == 1)
1221
1222// still here? Trigger stop iteration
1223 PyErr_SetString(PyExc_IndexError, "out of bounds");
1224 return nullptr;
1225}
1226
1227//- simplistic len() functions -----------------------------------------------
1229 return PyInt_FromLong(2);
1230}
1231
1232
1233//- shared/unique_ptr behavior -----------------------------------------------
1234PyObject* SmartPtrInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
1235{
1236// since the shared/unique pointer will take ownership, we need to relinquish it
1238 if (realInit) {
1239 PyObject* result = PyObject_Call(realInit, args, nullptr);
1241 if (result && PyTuple_GET_SIZE(args) == 1 && CPPInstance_Check(PyTuple_GET_ITEM(args, 0))) {
1243 if (!(cppinst->fFlags & CPPInstance::kIsSmartPtr)) cppinst->CppOwns();
1244 }
1245 return result;
1246 }
1247 return nullptr;
1248}
1249
1250
1251//- string behavior as primitives --------------------------------------------
1252#if PY_VERSION_HEX >= 0x03000000
1253// TODO: this is wrong, b/c it doesn't order
1256}
1257#endif
1258static inline
1259PyObject* CPyCppyy_PyString_FromCppString(std::string_view s, bool native=true) {
1260 if (native)
1261 return PyBytes_FromStringAndSize(s.data(), s.size());
1262 return CPyCppyy_PyText_FromStringAndSize(s.data(), s.size());
1263}
1264
1265static inline
1266PyObject* CPyCppyy_PyString_FromCppString(std::wstring_view s, bool native=true) {
1267 PyObject* pyobj = PyUnicode_FromWideChar(s.data(), s.size());
1268 if (pyobj && native) {
1269 PyObject* pybytes = PyUnicode_AsEncodedString(pyobj, "UTF-8", "strict");
1271 pyobj = pybytes;
1272 }
1273 return pyobj;
1274}
1275
1276#define CPPYY_IMPL_STRING_PYTHONIZATION(type, name) \
1277static inline \
1278PyObject* name##StringGetData(PyObject* self, bool native=true) \
1279{ \
1280 if (CPyCppyy::CPPInstance_Check(self)) { \
1281 type* obj = ((type*)((CPPInstance*)self)->GetObject()); \
1282 if (obj) return CPyCppyy_PyString_FromCppString(*obj, native); \
1283 } \
1284 PyErr_Format(PyExc_TypeError, "object mismatch (%s expected)", #type); \
1285 return nullptr; \
1286} \
1287 \
1288PyObject* name##StringStr(PyObject* self) \
1289{ \
1290 PyObject* pyobj = name##StringGetData(self, false); \
1291 if (!pyobj) { \
1292 /* do a native conversion to make printing possible (debatable) */ \
1293 PyErr_Clear(); \
1294 PyObject* pybytes = name##StringGetData(self, true); \
1295 if (pybytes) { /* should not fail */ \
1296 pyobj = PyObject_Str(pybytes); \
1297 Py_DECREF(pybytes); \
1298 } \
1299 } \
1300 return pyobj; \
1301} \
1302 \
1303PyObject* name##StringBytes(PyObject* self) \
1304{ \
1305 return name##StringGetData(self, true); \
1306} \
1307 \
1308PyObject* name##StringRepr(PyObject* self) \
1309{ \
1310 PyObject* data = name##StringGetData(self, true); \
1311 if (data) { \
1312 PyObject* repr = PyObject_Repr(data); \
1313 Py_DECREF(data); \
1314 return repr; \
1315 } \
1316 return nullptr; \
1317} \
1318 \
1319PyObject* name##StringIsEqual(PyObject* self, PyObject* obj) \
1320{ \
1321 PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \
1322 if (data) { \
1323 PyObject* result = PyObject_RichCompare(data, obj, Py_EQ); \
1324 Py_DECREF(data); \
1325 return result; \
1326 } \
1327 return nullptr; \
1328} \
1329 \
1330PyObject* name##StringIsNotEqual(PyObject* self, PyObject* obj) \
1331{ \
1332 PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \
1333 if (data) { \
1334 PyObject* result = PyObject_RichCompare(data, obj, Py_NE); \
1335 Py_DECREF(data); \
1336 return result; \
1337 } \
1338 return nullptr; \
1339}
1340
1341// Only define STLStringCompare:
1342#define CPPYY_IMPL_STRING_PYTHONIZATION_CMP(type, name) \
1343CPPYY_IMPL_STRING_PYTHONIZATION(type, name) \
1344PyObject* name##StringCompare(PyObject* self, PyObject* obj) \
1345{ \
1346 PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \
1347 int result = 0; \
1348 if (data) { \
1349 result = PyObject_Compare(data, obj); \
1350 Py_DECREF(data); \
1351 } \
1352 if (PyErr_Occurred()) \
1353 return nullptr; \
1354 return PyInt_FromLong(result); \
1355}
1356
1360
1361static inline std::string* GetSTLString(CPPInstance* self) {
1362 if (!CPPInstance_Check(self)) {
1363 PyErr_SetString(PyExc_TypeError, "std::string object expected");
1364 return nullptr;
1365 }
1366
1367 std::string* obj = (std::string*)self->GetObject();
1368 if (!obj)
1369 PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer");
1370
1371 return obj;
1372}
1373
1375{
1376 std::string* obj = GetSTLString(self);
1377 if (!obj)
1378 return nullptr;
1379
1380 char* keywords[] = {(char*)"encoding", (char*)"errors", (char*)nullptr};
1381 const char* encoding = nullptr; const char* errors = nullptr;
1383 const_cast<char*>("s|s"), keywords, &encoding, &errors))
1384 return nullptr;
1385
1386 return PyUnicode_Decode(obj->data(), obj->size(), encoding, errors);
1387}
1388
1389#if __cplusplus <= 202302L
1391{
1392 std::string* obj = GetSTLString(self);
1393 if (!obj)
1394 return nullptr;
1395
1396 const char* needle = CPyCppyy_PyText_AsString(pyobj);
1397 if (!needle)
1398 return nullptr;
1399
1400 if (obj->find(needle) != std::string::npos) {
1402 }
1403
1405}
1406#endif
1407
1409{
1410 std::string* obj = GetSTLString(self);
1411 if (!obj)
1412 return nullptr;
1413
1414// both str and std::string have a method "replace", but the Python version only
1415// accepts strings and takes no keyword arguments, whereas the C++ version has no
1416// overload that takes a string
1417
1418 if (2 <= PyTuple_GET_SIZE(args) && CPyCppyy_PyText_Check(PyTuple_GET_ITEM(args, 0))) {
1419 PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size());
1420 PyObject* meth = PyObject_GetAttrString(pystr, (char*)"replace");
1423 Py_DECREF(meth);
1424 return result;
1425 }
1426
1427 PyObject* cppreplace = PyObject_GetAttrString((PyObject*)self, (char*)"__cpp_replace");
1428 if (cppreplace) {
1429 PyObject* result = PyObject_Call(cppreplace, args, nullptr);
1431 return result;
1432 }
1433
1434 PyErr_SetString(PyExc_AttributeError, "\'std::string\' object has no attribute \'replace\'");
1435 return nullptr;
1436}
1437
1438#define CPYCPPYY_STRING_FINDMETHOD(name, cppname, pyname) \
1439PyObject* STLString##name(CPPInstance* self, PyObject* args, PyObject* /*kwds*/) \
1440{ \
1441 std::string* obj = GetSTLString(self); \
1442 if (!obj) \
1443 return nullptr; \
1444 \
1445 PyObject* cppmeth = PyObject_GetAttrString((PyObject*)self, (char*)#cppname);\
1446 if (cppmeth) { \
1447 PyObject* result = PyObject_Call(cppmeth, args, nullptr); \
1448 Py_DECREF(cppmeth); \
1449 if (result) { \
1450 if (PyLongOrInt_AsULong64(result) == (PY_ULONG_LONG)std::string::npos) {\
1451 Py_DECREF(result); \
1452 return PyInt_FromLong(-1); \
1453 } \
1454 return result; \
1455 } \
1456 PyErr_Clear(); \
1457 } \
1458 \
1459 PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size());\
1460 PyObject* pymeth = PyObject_GetAttrString(pystr, (char*)#pyname); \
1461 Py_DECREF(pystr); \
1462 PyObject* result = PyObject_CallObject(pymeth, args); \
1463 Py_DECREF(pymeth); \
1464 return result; \
1465}
1466
1467// both str and std::string have method "find" and "rfin"; try the C++ version first
1468// and fall back on the Python one in case of failure
1471
1473{
1474 std::string* obj = GetSTLString(self);
1475 if (!obj)
1476 return nullptr;
1477
1478 PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size());
1481 return attr;
1482}
1483
1484
1485#if 0
1487{
1488// force C++ string types conversion to Python str per Python __repr__ requirements
1490 if (!res || CPyCppyy_PyText_Check(res))
1491 return res;
1493 Py_DECREF(res);
1494 return str_res;
1495}
1496
1498{
1499// force C++ string types conversion to Python str per Python __str__ requirements
1501 if (!res || CPyCppyy_PyText_Check(res))
1502 return res;
1504 Py_DECREF(res);
1505 return str_res;
1506}
1507#endif
1508
1510{
1511// std::string objects hash to the same values as Python strings to allow
1512// matches in dictionaries etc.
1515 Py_DECREF(data);
1516 return h;
1517}
1518
1519
1520//- string_view behavior as primitive ----------------------------------------
1522{
1523// if constructed from a Python unicode object, the constructor will convert it
1524// to a temporary byte string, which is likely to go out of scope too soon; so
1525// buffer it as needed
1527 if (realInit) {
1528 PyObject *strbuf = nullptr, *newArgs = nullptr;
1529 if (PyTuple_GET_SIZE(args) == 1) {
1530 PyObject* arg0 = PyTuple_GET_ITEM(args, 0);
1531 if (PyUnicode_Check(arg0)) {
1532 // convert to the expected bytes array to control the temporary
1533 strbuf = PyUnicode_AsEncodedString(arg0, "UTF-8", "strict");
1534 newArgs = PyTuple_New(1);
1537 } else if (PyBytes_Check(arg0)) {
1538 // tie the life time of the provided string to the string_view
1539 Py_INCREF(arg0);
1540 strbuf = arg0;
1541 }
1542 }
1543
1544 PyObject* result = PyObject_Call(realInit, newArgs ? newArgs : args, nullptr);
1545
1548
1549 // if construction was successful and a string buffer was used, add a
1550 // life line to it from the string_view bound object
1551 if (result && self && strbuf)
1554
1555 return result;
1556 }
1557 return nullptr;
1558}
1559
1560
1561//- STL iterator behavior ----------------------------------------------------
1563{
1564// Python iterator protocol __next__ for STL forward iterators.
1565 bool mustIncrement = true;
1566 PyObject* last = nullptr;
1567 if (CPPInstance_Check(self)) {
1568 auto& dmc = ((CPPInstance*)self)->GetDatamemberCache();
1569 for (auto& p: dmc) {
1570 if (p.first == PS_END_ADDR) {
1571 last = p.second;
1572 Py_INCREF(last);
1573 } else if (p.first == PS_FLAG_ADDR) {
1574 mustIncrement = p.second == Py_True;
1575 if (!mustIncrement) {
1576 Py_DECREF(p.second);
1578 p.second = Py_True;
1579 }
1580 }
1581 }
1582 }
1583
1584 PyObject* next = nullptr;
1585 if (last) {
1586 // handle special case of empty container (i.e. self is end)
1587 if (!PyObject_RichCompareBool(last, self, Py_EQ)) {
1588 bool iter_valid = true;
1589 if (mustIncrement) {
1590 // prefer preinc, but allow post-inc; in both cases, it is "self" that has
1591 // the updated state to dereference
1593 if (!iter) {
1594 PyErr_Clear();
1595 static PyObject* dummy = PyInt_FromLong(1l);
1597 }
1599 Py_XDECREF(iter);
1600 }
1601
1602 if (iter_valid) {
1604 if (!next) PyErr_Clear();
1605 }
1606 }
1607 Py_DECREF(last);
1608 }
1609
1610 if (!next) PyErr_SetString(PyExc_StopIteration, "");
1611 return next;
1612}
1613
1614
1615//- STL complex<T> behavior --------------------------------------------------
1616#define COMPLEX_METH_GETSET(name, cppname) \
1617static PyObject* name##ComplexGet(PyObject* self, void*) { \
1618 return PyObject_CallMethodNoArgs(self, cppname); \
1619} \
1620static int name##ComplexSet(PyObject* self, PyObject* value, void*) { \
1621 PyObject* result = PyObject_CallMethodOneArg(self, cppname, value); \
1622 if (result) { \
1623 Py_DECREF(result); \
1624 return 0; \
1625 } \
1626 return -1; \
1627} \
1628PyGetSetDef name##Complex{(char*)#name, (getter)name##ComplexGet, (setter)name##ComplexSet, nullptr, nullptr};
1629
1632
1635 if (!real) return nullptr;
1636 double r = PyFloat_AsDouble(real);
1637 Py_DECREF(real);
1638 if (r == -1. && PyErr_Occurred())
1639 return nullptr;
1640
1642 if (!imag) return nullptr;
1643 double i = PyFloat_AsDouble(imag);
1644 Py_DECREF(imag);
1645 if (i == -1. && PyErr_Occurred())
1646 return nullptr;
1647
1648 return PyComplex_FromDoubles(r, i);
1649}
1650
1653 if (!real) return nullptr;
1654 double r = PyFloat_AsDouble(real);
1655 Py_DECREF(real);
1656 if (r == -1. && PyErr_Occurred())
1657 return nullptr;
1658
1660 if (!imag) return nullptr;
1661 double i = PyFloat_AsDouble(imag);
1662 Py_DECREF(imag);
1663 if (i == -1. && PyErr_Occurred())
1664 return nullptr;
1665
1666 std::ostringstream s;
1667 s << '(' << r << '+' << i << "j)";
1668 return CPyCppyy_PyText_FromString(s.str().c_str());
1669}
1670
1672{
1673 return PyFloat_FromDouble(((std::complex<double>*)self->GetObject())->real());
1674}
1675
1676static int ComplexDRealSet(CPPInstance* self, PyObject* value, void*)
1677{
1678 double d = PyFloat_AsDouble(value);
1679 if (d == -1.0 && PyErr_Occurred())
1680 return -1;
1681 ((std::complex<double>*)self->GetObject())->real(d);
1682 return 0;
1683}
1684
1685PyGetSetDef ComplexDReal{(char*)"real", (getter)ComplexDRealGet, (setter)ComplexDRealSet, nullptr, nullptr};
1686
1687
1689{
1690 return PyFloat_FromDouble(((std::complex<double>*)self->GetObject())->imag());
1691}
1692
1693static int ComplexDImagSet(CPPInstance* self, PyObject* value, void*)
1694{
1695 double d = PyFloat_AsDouble(value);
1696 if (d == -1.0 && PyErr_Occurred())
1697 return -1;
1698 ((std::complex<double>*)self->GetObject())->imag(d);
1699 return 0;
1700}
1701
1702PyGetSetDef ComplexDImag{(char*)"imag", (getter)ComplexDImagGet, (setter)ComplexDImagSet, nullptr, nullptr};
1703
1705{
1706 double r = ((std::complex<double>*)self->GetObject())->real();
1707 double i = ((std::complex<double>*)self->GetObject())->imag();
1708 return PyComplex_FromDoubles(r, i);
1709}
1710
1711
1712} // unnamed namespace
1713
1714
1715//- public functions ---------------------------------------------------------
1716namespace CPyCppyy {
1717 std::set<std::string> gIteratorTypes;
1718}
1719
1720static inline
1721bool run_pythonizors(PyObject* pyclass, PyObject* pyname, const std::vector<PyObject*>& v)
1722{
1723 PyObject* args = PyTuple_New(2);
1726
1727 bool pstatus = true;
1728 for (auto pythonizor : v) {
1730 if (!result) {
1731 pstatus = false; // TODO: detail the error handling
1732 break;
1733 }
1735 }
1736 Py_DECREF(args);
1737
1738 return pstatus;
1739}
1740
1741bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name)
1742{
1743// Add pre-defined pythonizations (for STL and ROOT) to classes based on their
1744// signature and/or class name.
1745 if (!pyclass)
1746 return false;
1747
1749
1750//- method name based pythonization ------------------------------------------
1751
1752// for smart pointer style classes that are otherwise not known as such; would
1753// prefer operator-> as that returns a pointer (which is simpler since it never
1754// has to deal with ref-assignment), but operator* plays better with STL iters
1755// and algorithms
1760
1761// for pre-check of nullptr for boolean types
1763#if PY_VERSION_HEX >= 0x03000000
1764 const char* pybool_name = "__bool__";
1765#else
1766 const char* pybool_name = "__nonzero__";
1767#endif
1769 }
1770
1771 // for STL containers, and user classes modeled after them. Guard the alias to
1772 // __len__ by verifying that size() returns an integer type and the class has
1773 // begin()/end() or operator[] (i.e. is container-like). This prevents bool()
1774 // returning False for valid objects whose size() returns non-integer types like
1775 // std::optional<std::size_t>. Skip if size() has multiple overloads, as that
1776 // indicates it is not the simple container-style size() one would map to __len__.
1777 if (HasAttrDirect(pyclass, PyStrings::gSize, /*mustBeCPyCppyy=*/true) || HasAttrInMRO(pyclass, PyStrings::gSize)) {
1778 bool sizeIsInteger = false;
1781 auto *ol = (CPPOverload *)pySizeMethod;
1782 if (ol->HasMethods() && ol->fMethodInfo->fMethods.size() == 1) {
1784 ol->fMethodInfo->fMethods[0]->Reflex(Cppyy::Reflex::RETURN_TYPE, Cppyy::Reflex::AS_STRING);
1785 if (pyrestype) {
1788 }
1789 }
1790 }
1792
1793 if (sizeIsInteger) {
1797 if (hasIterators || hasSubscript) {
1798 Utility::AddToClass(pyclass, "__len__", "size");
1799 }
1800 }
1801 }
1802
1804 Utility::AddToClass(pyclass, "__contains__", "contains");
1805 }
1806
1807 if (!IsTemplatedSTLClass(name, "vector") && // vector is dealt with below
1810 // obtain the name of the return type
1811 const auto& v = Cppyy::GetMethodIndicesFromName(klass->fCppType, "begin");
1812 if (!v.empty()) {
1813 // check return type; if not explicitly an iterator, add it to the "known" return
1814 // types to add the "next" method on use
1816 const std::string& resname = Cppyy::GetMethodResultType(meth);
1817 bool isIterator = gIteratorTypes.find(resname) != gIteratorTypes.end();
1819 if (resname.find("iterator") == std::string::npos)
1820 gIteratorTypes.insert(resname);
1821 isIterator = true;
1822 }
1823
1824 if (isIterator) {
1825 // install iterator protocol a la STL
1828 } else {
1829 // still okay if this is some pointer type of builtin persuasion (general class
1830 // won't work: the return type needs to understand the iterator protocol)
1831 std::string resolved = Cppyy::ResolveName(resname);
1832 if (resolved.back() == '*' && Cppyy::IsBuiltin(resolved.substr(0, resolved.size()-1))) {
1835 }
1836 }
1837 }
1838 }
1839 if (!((PyTypeObject*)pyclass)->tp_iter && // no iterator resolved
1841 // Python will iterate over __getitem__ using integers, but C++ operator[] will never raise
1842 // a StopIteration. A checked getitem (raising IndexError if beyond size()) works in some
1843 // cases but would mess up if operator[] is meant to implement an associative container. So,
1844 // this has to be implemented as an iterator protocol.
1847 }
1848 }
1849
1850// operator==/!= are used in op_richcompare of CPPInstance, which subsequently allows
1851// comparisons to None; if no operator is available, a hook is installed for lazy
1852// lookups in the global and/or class namespace
1853 if (HasAttrDirect(pyclass, PyStrings::gEq, true) && \
1854 Cppyy::GetMethodIndicesFromName(klass->fCppType, "__eq__").empty()) {
1856 if (!klass->fOperators) klass->fOperators = new Utility::PyOperators();
1857 klass->fOperators->fEq = cppol;
1858 // re-insert the forwarding __eq__ from the CPPInstance in case there was a Python-side
1859 // override in the base class
1860 static PyObject* top_eq = nullptr;
1861 if (!top_eq) {
1864 Py_DECREF(top_eq); // make it borrowed
1866 }
1868 }
1869
1870 if (HasAttrDirect(pyclass, PyStrings::gNe, true) && \
1871 Cppyy::GetMethodIndicesFromName(klass->fCppType, "__ne__").empty()) {
1873 if (!klass->fOperators) klass->fOperators = new Utility::PyOperators();
1874 klass->fOperators->fNe = cppol;
1875 // re-insert the forwarding __ne__ (same reason as above for __eq__)
1876 static PyObject* top_ne = nullptr;
1877 if (!top_ne) {
1880 Py_DECREF(top_ne); // make it borrowed
1882 }
1884 }
1885
1886#if 0
1888 // guarantee that the result of __repr__ is a Python string
1889 Utility::AddToClass(pyclass, "__cpp_repr", "__repr__");
1891 }
1892
1894 // guarantee that the result of __str__ is a Python string
1895 Utility::AddToClass(pyclass, "__cpp_str", "__str__");
1897 }
1898#endif
1899
1900 if (Cppyy::IsAggregate(((CPPClass*)pyclass)->fCppType) && name.compare(0, 5, "std::", 5) != 0 &&
1901 name.compare(0, 6, "tuple<", 6) != 0) {
1902 // create a pseudo-constructor to allow initializer-style object creation
1903 Cppyy::TCppType_t kls = ((CPPClass*)pyclass)->fCppType;
1905 if (ndata) {
1906 std::string rname = name;
1908
1909 std::ostringstream initdef;
1910 initdef << "namespace __cppyy_internal {\n"
1911 << "void init_" << rname << "(" << name << "** self";
1912 bool codegen_ok = true;
1913 std::vector<std::string> arg_types, arg_names, arg_defaults;
1914 arg_types.reserve(ndata); arg_names.reserve(ndata); arg_defaults.reserve(ndata);
1915 for (Cppyy::TCppIndex_t i = 0; i < ndata; ++i) {
1917 continue;
1918
1919 const std::string& txt = Cppyy::GetDatamemberType(kls, i);
1920 const std::string& res = Cppyy::IsEnum(txt) ? txt : Cppyy::ResolveName(txt);
1921 const std::string& cpd = TypeManip::compound(res);
1922 std::string res_clean = TypeManip::clean_type(res, false, true);
1923
1924 if (res_clean == "internal_enum_type_t")
1925 res_clean = txt; // restore (properly scoped name)
1926
1927 if (res.rfind(']') == std::string::npos && res.rfind(')') == std::string::npos) {
1928 if (!cpd.empty()) arg_types.push_back(res_clean+cpd);
1929 else arg_types.push_back("const "+res_clean+"&");
1930 arg_names.push_back(Cppyy::GetDatamemberName(kls, i));
1931 if ((!cpd.empty() && cpd.back() == '*') || Cppyy::IsBuiltin(res_clean))
1932 arg_defaults.push_back("0");
1933 else {
1936 }
1937 } else {
1938 codegen_ok = false; // TODO: how to support arrays, anonymous enums, etc?
1939 break;
1940 }
1941 }
1942
1943 if (codegen_ok && !arg_types.empty()) {
1944 bool defaults_ok = arg_defaults.size() == arg_types.size();
1945 for (std::vector<std::string>::size_type i = 0; i < arg_types.size(); ++i) {
1946 initdef << ", " << arg_types[i] << " " << arg_names[i];
1947 if (defaults_ok) initdef << " = " << arg_defaults[i];
1948 }
1949 initdef << ") {\n *self = new " << name << "{";
1950 for (std::vector<std::string>::size_type i = 0; i < arg_names.size(); ++i) {
1951 if (i != 0) initdef << ", ";
1952 initdef << arg_names[i];
1953 }
1954 initdef << "};\n} }";
1955
1956 if (Cppyy::Compile(initdef.str(), true /* silent */)) {
1957 Cppyy::TCppScope_t cis = Cppyy::GetScope("__cppyy_internal");
1958 const auto& mix = Cppyy::GetMethodIndicesFromName(cis, "init_"+rname);
1959 if (mix.size()) {
1960 if (!Utility::AddToClass(pyclass, "__init__",
1961 new CPPFunction(cis, Cppyy::GetMethod(cis, mix[0]))))
1962 PyErr_Clear();
1963 }
1964 }
1965 }
1966 }
1967 }
1968
1969
1970//- class name based pythonization -------------------------------------------
1971
1972 if (IsTemplatedSTLClass(name, "span")) {
1973 // libstdc++ (GCC >= 15) implements std::span::iterator using a private
1974 // nested tag type, which makes the iterator non-instantiable by
1975 // CallFunc-generated wrappers (the return type cannot be named without
1976 // violating access rules).
1977 //
1978 // To preserve correct Python iteration semantics, we replace begin()/end()
1979 // for std::span to return a custom pointer-based iterator instead. This
1980 // avoids relying on std::span::iterator while still providing a real C++
1981 // iterator object that CPyCppyy can also wrap and expose via
1982 // __iter__/__next__.
1985 }
1986
1987 if (IsTemplatedSTLClass(name, "vector")) {
1988
1989 // std::vector<bool> is a special case in C++
1991 if (klass->fCppType == sVectorBoolTypeID) {
1994 } else {
1995 // constructor that takes python collections
1996 Utility::AddToClass(pyclass, "__real_init", "__init__");
1998
1999 // data with size
2000 Utility::AddToClass(pyclass, "__real_data", "data");
2001 PyErr_Clear(); // AddToClass might have failed for data
2003
2004 // The addition of the __array__ utility to std::vector Python proxies causes a
2005 // bug where the resulting array is a single dimension, causing loss of data when
2006 // converting to numpy arrays, for >1dim vectors. Since this C++ pythonization
2007 // was added with the upgrade in 6.32, and is only defined and used recursively,
2008 // the safe option is to disable this function and no longer add it.
2009#if 0
2010 // numpy array conversion
2012#endif
2013
2014 // checked getitem
2016 Utility::AddToClass(pyclass, "_getitem__unchecked", "__getitem__");
2018 }
2019
2020 // vector-optimized iterator protocol
2022
2023 // optimized __iadd__
2025
2026 // helpers for iteration
2027 const std::string& vtype = Cppyy::ResolveName(name+"::value_type");
2028 if (vtype.rfind("value_type") == std::string::npos) { // actually resolved?
2032 }
2033
2034 size_t typesz = Cppyy::SizeOf(name+"::value_type");
2035 if (typesz) {
2039 }
2040 }
2041 }
2042
2043 else if (IsTemplatedSTLClass(name, "array")) {
2044 // constructor that takes python associative collections
2045 Utility::AddToClass(pyclass, "__real_init", "__init__");
2047 }
2048
2049 else if (IsTemplatedSTLClass(name, "map") || IsTemplatedSTLClass(name, "unordered_map")) {
2050 // constructor that takes python associative collections
2051 Utility::AddToClass(pyclass, "__real_init", "__init__");
2053#if __cplusplus <= 202002L
2054 // From C++20, std::map and std::unordered_map already implement a contains() method.
2056#endif
2057 }
2058
2059 else if (IsTemplatedSTLClass(name, "set")) {
2060 // constructor that takes python associative collections
2061 Utility::AddToClass(pyclass, "__real_init", "__init__");
2063
2064#if __cplusplus <= 202002L
2065 // From C++20, std::set already implements a contains() method.
2067#endif
2068 }
2069
2070 else if (IsTemplatedSTLClass(name, "pair")) {
2073 }
2074
2075 if (IsTemplatedSTLClass(name, "shared_ptr") || IsTemplatedSTLClass(name, "unique_ptr")) {
2076 Utility::AddToClass(pyclass, "__real_init", "__init__");
2078 }
2079
2080 else if (!((PyTypeObject*)pyclass)->tp_iter && \
2081 (name.find("iterator") != std::string::npos || gIteratorTypes.find(name) != gIteratorTypes.end())) {
2082 ((PyTypeObject*)pyclass)->tp_iternext = (iternextfunc)STLIterNext;
2086 }
2087
2088 else if (name == "string" || name == "std::string") { // TODO: ask backend as well
2095#if __cplusplus <= 202302L
2096 // From C++23, std::sting already implements a contains() method.
2098#endif
2100 Utility::AddToClass(pyclass, "__cpp_find", "find");
2102 Utility::AddToClass(pyclass, "__cpp_rfind", "rfind");
2104 Utility::AddToClass(pyclass, "__cpp_replace", "replace");
2107
2108 // to allow use of std::string in dictionaries and findable with str
2110 }
2111
2112 else if (name == "basic_string_view<char,char_traits<char> >" || name == "std::basic_string_view<char>") {
2113 Utility::AddToClass(pyclass, "__real_init", "__init__");
2121 }
2122
2123// The first condition was already present in upstream CPyCppyy. The other two
2124// are special to ROOT, because its reflection layer gives us the types without
2125// the "std::" namespace. On some platforms, that applies only to the template
2126// arguments, and on others also to the "basic_string".
2127 else if (name == "std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> >"
2128 || name == "basic_string<wchar_t,char_traits<wchar_t>,allocator<wchar_t> >"
2129 || name == "std::basic_string<wchar_t,char_traits<wchar_t>,allocator<wchar_t> >"
2130 ) {
2137 }
2138
2139 else if (name == "complex<double>" || name == "std::complex<double>") {
2140 Utility::AddToClass(pyclass, "__cpp_real", "real");
2142 Utility::AddToClass(pyclass, "__cpp_imag", "imag");
2146 }
2147
2148 else if (IsTemplatedSTLClass(name, "complex")) {
2149 Utility::AddToClass(pyclass, "__cpp_real", "real");
2151 Utility::AddToClass(pyclass, "__cpp_imag", "imag");
2155 }
2156
2157// direct user access; there are two calls here:
2158// - explicit pythonization: won't fall through to the base classes and is preferred if present
2159// - normal pythonization: only called if explicit isn't present, falls through to base classes
2160 bool bUserOk = true; PyObject* res = nullptr;
2164 bUserOk = (bool)res;
2165 } else {
2167 if (func) {
2168 res = PyObject_CallFunctionObjArgs(func, pyclass, pyname, nullptr);
2169 Py_DECREF(func);
2170 bUserOk = (bool)res;
2171 } else
2172 PyErr_Clear();
2173 }
2174 if (!bUserOk) {
2176 return false;
2177 } else {
2178 Py_XDECREF(res);
2179 // pyname handed to args tuple below
2180 }
2181
2182// call registered pythonizors, if any: first run the namespace-specific pythonizors, then
2183// the global ones (the idea is to allow writing a pythonizor that see all classes)
2184 bool pstatus = true;
2186 auto &pyzMap = pythonizations();
2187 if (!outer_scope.empty()) {
2188 auto p = pyzMap.find(outer_scope);
2189 if (p != pyzMap.end()) {
2191 name.substr(outer_scope.size()+2, std::string::npos).c_str());
2194 }
2195 }
2196
2197 if (pstatus) {
2198 auto p = pyzMap.find("");
2199 if (p != pyzMap.end())
2200 pstatus = run_pythonizors(pyclass, pyname, p->second);
2201 }
2202
2204
2205// phew! all done ...
2206 return pstatus;
2207}
#define Py_TYPE(ob)
Definition CPyCppyy.h:196
#define Py_RETURN_TRUE
Definition CPyCppyy.h:272
#define Py_RETURN_FALSE
Definition CPyCppyy.h:276
#define PyInt_FromSsize_t
Definition CPyCppyy.h:217
#define CPyCppyy_PyText_FromStringAndSize
Definition CPyCppyy.h:85
#define PyBytes_Check
Definition CPyCppyy.h:61
#define PyInt_AsSsize_t
Definition CPyCppyy.h:216
#define CPyCppyy_PySliceCast
Definition CPyCppyy.h:189
#define CPyCppyy_PyText_AsString
Definition CPyCppyy.h:76
long Py_hash_t
Definition CPyCppyy.h:114
static PyObject * PyObject_CallMethodOneArg(PyObject *obj, PyObject *name, PyObject *arg)
Definition CPyCppyy.h:377
#define PyBytes_FromStringAndSize
Definition CPyCppyy.h:70
#define Py_RETURN_NONE
Definition CPyCppyy.h:268
#define CPyCppyy_PyText_Type
Definition CPyCppyy.h:94
static PyObject * PyObject_CallMethodNoArgs(PyObject *obj, PyObject *name)
Definition CPyCppyy.h:373
#define CPPYY__next__
Definition CPyCppyy.h:112
#define CPyCppyy_PyText_FromString
Definition CPyCppyy.h:81
#define CPyCppyy_PyText_Check
Definition CPyCppyy.h:74
bool PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *pyobject)
uint32_t fFlags
_object PyObject
#define CPPYY_IMPL_STRING_PYTHONIZATION_CMP(type, name)
static bool run_pythonizors(PyObject *pyclass, PyObject *pyname, const std::vector< PyObject * > &v)
#define COMPLEX_METH_GETSET(name, cppname)
#define CPYCPPYY_STRING_FINDMETHOD(name, cppname, pyname)
#define PyObject_LengthHint
void FillVector(std::vector< double > &v, int size, T *a)
#define d(i)
Definition RSha256.hxx:102
#define c(i)
Definition RSha256.hxx:101
#define h(i)
Definition RSha256.hxx:106
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t r
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t index
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void value
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t attr
char name[80]
Definition TGX11.cxx:148
const_iterator end() const
PyObject * gContains
Definition PyStrings.cxx:10
PyObject * gCTypesType
Definition PyStrings.cxx:40
PyObject * gRealInit
Definition PyStrings.cxx:43
PyObject * gExPythonize
Definition PyStrings.cxx:73
PyObject * gLifeLine
Definition PyStrings.cxx:30
PyObject * gGetItem
Definition PyStrings.cxx:24
PyObject * gCppBool
Definition PyStrings.cxx:12
PyObject * gCppReal
Definition PyStrings.cxx:65
PyObject * gPythonize
Definition PyStrings.cxx:74
PyObject * gTypeCode
Definition PyStrings.cxx:39
PyObject * gPostInc
Definition PyStrings.cxx:19
PyObject * gCppImag
Definition PyStrings.cxx:66
PyObject * gValueSize
Definition PyStrings.cxx:63
PyObject * gSetItem
Definition PyStrings.cxx:26
PyObject * gGetNoCheck
Definition PyStrings.cxx:25
PyObject * gCppRepr
Definition PyStrings.cxx:36
PyObject * gValueType
Definition PyStrings.cxx:62
void cppscope_to_legalname(std::string &cppscope)
std::string clean_type(const std::string &cppname, bool template_strip=true, bool const_strip=true)
std::string compound(const std::string &name)
std::string extract_namespace(const std::string &name)
Py_ssize_t GetBuffer(PyObject *pyobject, char tc, int size, void *&buf, bool check=true)
Definition Utility.cxx:919
bool AddToClass(PyObject *pyclass, const char *label, PyCFunction cfunc, int flags=METH_VARARGS)
Definition Utility.cxx:185
PyTypeObject VectorIter_Type
PyObject * GetScopeProxy(Cppyy::TCppScope_t)
static PyObject * GetAttrDirect(PyObject *pyclass, PyObject *pyname)
bool Pythonize(PyObject *pyclass, const std::string &name)
bool CPPOverload_Check(T *object)
Definition CPPOverload.h:94
std::map< std::string, std::vector< PyObject * > > & pythonizations()
bool CPPScope_Check(T *object)
Definition CPPScope.h:81
bool LowLevelView_Check(T *object)
bool CPPInstance_Check(T *object)
PyTypeObject IndexIter_Type
PyObject * gThisModule
Definition CPPMethod.cxx:30
CPYCPPYY_EXTERN Converter * CreateConverter(const std::string &name, cdims_t=0)
std::set< std::string > gIteratorTypes
const RequestId_t RETURN_TYPE
Definition Reflex.h:18
const FormatId_t AS_STRING
Definition Reflex.h:24
size_t TCppIndex_t
Definition cpp_cppyy.h:40
RPY_EXPORTED bool IsIntegerType(const std::string &type_name)
RPY_EXPORTED size_t SizeOf(TCppType_t klass)
intptr_t TCppMethod_t
Definition cpp_cppyy.h:38
RPY_EXPORTED bool IsDefaultConstructable(TCppType_t type)
RPY_EXPORTED bool IsEnum(const std::string &type_name)
RPY_EXPORTED std::vector< TCppIndex_t > GetMethodIndicesFromName(TCppScope_t scope, const std::string &name)
RPY_EXPORTED TCppIndex_t GetNumDatamembers(TCppScope_t scope, bool accept_namespace=false)
RPY_EXPORTED bool Compile(const std::string &code, bool silent=false)
RPY_EXPORTED TCppScope_t gGlobalScope
Definition cpp_cppyy.h:69
RPY_EXPORTED std::string ResolveName(const std::string &cppitem_name)
TCppScope_t TCppType_t
Definition cpp_cppyy.h:35
RPY_EXPORTED bool IsAggregate(TCppType_t type)
RPY_EXPORTED std::string GetScopedFinalName(TCppType_t type)
RPY_EXPORTED bool IsPublicData(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED bool IsBuiltin(const std::string &type_name)
RPY_EXPORTED bool IsStaticData(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED std::string GetDatamemberType(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED TCppMethod_t GetMethod(TCppScope_t scope, TCppIndex_t imeth)
RPY_EXPORTED bool IsSmartPtr(TCppType_t type)
RPY_EXPORTED TCppScope_t GetScope(const std::string &scope_name)
size_t TCppScope_t
Definition cpp_cppyy.h:34
RPY_EXPORTED std::string GetMethodResultType(TCppMethod_t)
RPY_EXPORTED std::string GetDatamemberName(TCppScope_t scope, TCppIndex_t idata)
CoordSystem::Scalar get(DisplacementVector2D< CoordSystem, Tag > const &p)