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-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{
166// Follow operator*() if present (available in python as __deref__), so that
167// smart pointers behave as expected.
169 // TODO: these calls come from TemplateProxy and are unlikely to be needed in practice,
170 // whereas as-is, they can accidentally dereference the result of end() on some STL
171 // containers. Obviously, this is a dumb hack that should be resolved more fundamentally.
173 return nullptr;
174 }
175
177 PyErr_SetString(PyExc_TypeError, "getattr(): attribute name must be string");
178
180 if (!pyptr)
181 return nullptr;
182
183// prevent a potential infinite loop
184 if (Py_TYPE(pyptr) == Py_TYPE(self)) {
187 PyErr_Format(PyExc_AttributeError, "%s has no attribute \'%s\'",
191
193 return nullptr;
194 }
195
198 return result;
199}
200
201//-----------------------------------------------------------------------------
203{
204// Follow operator->() if present (available in python as __follow__), so that
205// smart pointers behave as expected.
207 PyErr_SetString(PyExc_TypeError, "getattr(): attribute name must be string");
208
210 if (!pyptr)
211 return nullptr;
212
215 return result;
216}
217
218//- pointer checking bool converter -------------------------------------------
220{
221 if (!CPPInstance_Check(self)) {
222 PyErr_SetString(PyExc_TypeError, "C++ object proxy expected");
223 return nullptr;
224 }
225
226 if (!((CPPInstance*)self)->GetObject())
228
230}
231
232//- vector behavior as primitives ----------------------------------------------
233#if PY_VERSION_HEX < 0x03040000
234#define PyObject_LengthHint _PyObject_LengthHint
235#endif
236
237// TODO: can probably use the below getters in the InitializerListConverter
238struct ItemGetter {
239 ItemGetter(PyObject* pyobj) : fPyObject(pyobj) { Py_INCREF(fPyObject); }
240 virtual ~ItemGetter() { Py_DECREF(fPyObject); }
241 virtual Py_ssize_t size() = 0;
242 virtual PyObject* get() = 0;
243 PyObject* fPyObject;
244};
245
246struct CountedItemGetter : public ItemGetter {
247 CountedItemGetter(PyObject* pyobj) : ItemGetter(pyobj), fCur(0) {}
248 Py_ssize_t fCur;
249};
250
251struct TupleItemGetter : public CountedItemGetter {
252 using CountedItemGetter::CountedItemGetter;
253 Py_ssize_t size() override { return PyTuple_GET_SIZE(fPyObject); }
254 PyObject* get() override {
255 if (fCur < PyTuple_GET_SIZE(fPyObject)) {
256 PyObject* item = PyTuple_GET_ITEM(fPyObject, fCur++);
258 return item;
259 }
260 PyErr_SetString(PyExc_StopIteration, "end of tuple");
261 return nullptr;
262 }
263};
264
265struct ListItemGetter : public CountedItemGetter {
266 using CountedItemGetter::CountedItemGetter;
267 Py_ssize_t size() override { return PyList_GET_SIZE(fPyObject); }
268 PyObject* get() override {
269 if (fCur < PyList_GET_SIZE(fPyObject)) {
270 PyObject* item = PyList_GET_ITEM(fPyObject, fCur++);
272 return item;
273 }
274 PyErr_SetString(PyExc_StopIteration, "end of list");
275 return nullptr;
276 }
277};
278
279struct SequenceItemGetter : public CountedItemGetter {
280 using CountedItemGetter::CountedItemGetter;
281 Py_ssize_t size() override {
282 Py_ssize_t sz = PySequence_Size(fPyObject);
283 if (sz < 0) {
284 PyErr_Clear();
285 return PyObject_LengthHint(fPyObject, 8);
286 }
287 return sz;
288 }
289 PyObject* get() override { return PySequence_GetItem(fPyObject, fCur++); }
290};
291
292struct IterItemGetter : public ItemGetter {
293 using ItemGetter::ItemGetter;
294 Py_ssize_t size() override { return PyObject_LengthHint(fPyObject, 8); }
295 PyObject* get() override { return (*(Py_TYPE(fPyObject)->tp_iternext))(fPyObject); }
296};
297
298static ItemGetter* GetGetter(PyObject* args)
299{
300// Create an ItemGetter to loop over the iterable argument, if any.
301 ItemGetter* getter = nullptr;
302
303 if (PyTuple_GET_SIZE(args) == 1) {
304 PyObject* fi = PyTuple_GET_ITEM(args, 0);
306 return nullptr; // do not accept string to fill std::vector<char>
307
308 // TODO: this only tests for new-style buffers, which is too strict, but a
309 // generic check for Py_TYPE(fi)->tp_as_buffer is too loose (note that the
310 // main use case is numpy, which offers the new interface)
312 return nullptr;
313
315 getter = new TupleItemGetter(fi);
316 else if (PyList_CheckExact(fi))
317 getter = new ListItemGetter(fi);
318 else if (PySequence_Check(fi))
319 getter = new SequenceItemGetter(fi);
320 else {
322 if (iter) {
323 getter = new IterItemGetter{iter};
324 Py_DECREF(iter);
325 }
326 else PyErr_Clear();
327 }
328 }
329
330 return getter;
331}
332
333namespace {
334
336{
337 static bool compiled = false;
338
339 if (compiled)
340 return;
341
342 compiled = true;
343
344 auto code = R"(
345namespace __cppyy_internal {
346
347template <class T>
348struct ptr_iterator {
349 T *cur;
350 T *end;
351
352 ptr_iterator(T *c, T *e) : cur(c), end(e) {}
353
354 T &operator*() const { return *cur; }
355 ptr_iterator &operator++()
356 {
357 ++cur;
358 return *this;
359 }
360 bool operator==(const ptr_iterator &other) const { return cur == other.cur; }
361 bool operator!=(const ptr_iterator &other) const { return !(*this == other); }
362};
363
364template <class T>
365ptr_iterator<T> make_iter(T *begin, T *end)
366{
367 return {begin, end};
368}
369
370} // namespace __cppyy_internal
371
372// Note: for const span<T>, T is const-qualified here
373template <class T>
374auto __cppyy_internal_begin(T &s) noexcept
375{
376 return __cppyy_internal::make_iter(s.data(), s.data() + s.size());
377}
378
379// Note: for const span<T>, T is const-qualified here
380template <class T>
381auto __cppyy_internal_end(T &s) noexcept
382{
383 // end iterator = begin iterator with cur == end
384 return __cppyy_internal::make_iter(s.data() + s.size(), s.data() + s.size());
385}
386 )";
387 Cppyy::Compile(code, /*silent*/ true);
388}
389
391{
392 static PyObject *pyFunc = nullptr;
393 if (!pyFunc) {
396 pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_begin");
397 if (!pyFunc) {
398 PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper "
399 "'__cppyy_internal_begin' for std::span pythonization");
400 }
401 }
402 return pyFunc;
403}
404
406{
407 static PyObject *pyFunc = nullptr;
408 if (!pyFunc) {
411 pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_end");
412 if (!pyFunc) {
413 PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper "
414 "'__cppyy_internal_end' for std::span pythonization");
415 }
416 }
417 return pyFunc;
418}
419
420} // namespace
421
423{
424 auto begin = spanBegin();
425 if (!begin)
426 return nullptr;
427 return PyObject_CallOneArg(begin, self);
428}
429
431{
432 auto spanEnd();
433 if (!end)
434 return nullptr;
435 return PyObject_CallOneArg(end, self);
436}
437
438static bool FillVector(PyObject* vecin, PyObject* args, ItemGetter* getter)
439{
440 Py_ssize_t sz = getter->size();
441 if (sz < 0)
442 return false;
443
444// reserve memory as applicable
445 if (0 < sz) {
446 PyObject* res = PyObject_CallMethod(vecin, (char*)"reserve", (char*)"n", sz);
447 Py_DECREF(res);
448 } else // i.e. sz == 0, so empty container: done
449 return true;
450
451 bool fill_ok = true;
452
453// two main options: a list of lists (or tuples), or a list of objects; the former
454// are emplace_back'ed, the latter push_back'ed
456 if (!fi) PyErr_Clear();
458 // use emplace_back to construct the vector entries one by one
459 PyObject* eb_call = PyObject_GetAttrString(vecin, (char*)"emplace_back");
461 bool value_is_vector = false;
463 // if the value_type is a vector, then allow for initialization from sequences
464 if (std::string(CPyCppyy_PyText_AsString(vtype)).rfind("std::vector", 0) != std::string::npos)
465 value_is_vector = true;
466 } else
467 PyErr_Clear();
469
470 if (eb_call) {
472 for (int i = 0; /* until break */; ++i) {
473 PyObject* item = getter->get();
474 if (item) {
476 eb_args = PyTuple_New(1);
478 } else if (PyTuple_CheckExact(item)) {
479 eb_args = item;
480 } else if (PyList_CheckExact(item)) {
483 for (Py_ssize_t j = 0; j < isz; ++j) {
487 }
489 } else {
491 PyErr_Format(PyExc_TypeError, "argument %d is not a tuple or list", i);
492 fill_ok = false;
493 break;
494 }
497 if (!ebres) {
498 fill_ok = false;
499 break;
500 }
502 } else {
503 if (PyErr_Occurred()) {
506 fill_ok = false;
507 else { PyErr_Clear(); }
508 }
509 break;
510 }
511 }
513 }
514 } else {
515 // use push_back to add the vector entries one by one
516 PyObject* pb_call = PyObject_GetAttrString(vecin, (char*)"push_back");
517 if (pb_call) {
518 for (;;) {
519 PyObject* item = getter->get();
520 if (item) {
523 if (!pbres) {
524 fill_ok = false;
525 break;
526 }
528 } else {
529 if (PyErr_Occurred()) {
532 fill_ok = false;
533 else { PyErr_Clear(); }
534 }
535 break;
536 }
537 }
539 }
540 }
541 Py_XDECREF(fi);
542
543 return fill_ok;
544}
545
546PyObject* VectorIAdd(PyObject* self, PyObject* args, PyObject* /* kwds */)
547{
548// Implement fast __iadd__ on std::vector (generic __iadd__ is in Python)
549 ItemGetter* getter = GetGetter(args);
550
551 if (getter) {
552 bool fill_ok = FillVector(self, args, getter);
553 delete getter;
554
555 if (!fill_ok)
556 return nullptr;
557
559 return self;
560 }
561
562// if no getter, it could still be b/c we have a buffer (e.g. numpy); looping over
563// a buffer here is slow, so use insert() instead
564 if (PyTuple_GET_SIZE(args) == 1) {
565 PyObject* fi = PyTuple_GET_ITEM(args, 0);
568 if (vend) {
569 // when __iadd__ is overriden, the operation does not end with
570 // calling the __iadd__ method, but also assigns the result to the
571 // lhs of the iadd. For example, performing vec += arr, Python
572 // first calls our override, and then does vec = vec.iadd(arr).
575
576 if (!it)
577 return nullptr;
578
579 Py_DECREF(it);
580 // Assign the result of the __iadd__ override to the std::vector
582 return self;
583 }
584 }
585 }
586
587 if (!PyErr_Occurred())
588 PyErr_SetString(PyExc_TypeError, "argument is not iterable");
589 return nullptr; // error already set
590}
591
592
593PyObject* VectorInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
594{
595// Specialized vector constructor to allow construction from containers; allowing
596// such construction from initializer_list instead would possible, but can be
597// error-prone. This use case is common enough for std::vector to implement it
598// directly, except for arrays (which can be passed wholesale) and strings (which
599// won't convert properly as they'll be seen as buffers)
600
601 ItemGetter* getter = GetGetter(args);
602
603 if (getter) {
604 // construct an empty vector, then back-fill it
606 if (!result) {
607 delete getter;
608 return nullptr;
609 }
610
611 bool fill_ok = FillVector(self, args, getter);
612 delete getter;
613
614 if (!fill_ok) {
616 return nullptr;
617 }
618
619 return result;
620 }
621
622// The given argument wasn't iterable: simply forward to regular constructor
624 if (realInit) {
625 PyObject* result = PyObject_Call(realInit, args, nullptr);
627 return result;
628 }
629
630 return nullptr;
631}
632
633//---------------------------------------------------------------------------
635{
636 PyObject* pydata = CallPyObjMethod(self, "__real_data");
638 return pydata;
639
641 if (!pylen) {
642 PyErr_Clear();
643 return pydata;
644 }
645
646 long clen = PyInt_AsLong(pylen);
648
650 ((CPPInstance*)pydata)->CastToArray(clen);
651 return pydata;
652 }
653
654 ((LowLevelView*)pydata)->resize((size_t)clen);
655 return pydata;
656}
657
658
659// This function implements __array__, added to std::vector python proxies and causes
660// a bug (see explanation at Utility::AddToClass(pyclass, "__array__"...) in CPyCppyy::Pythonize)
661// The recursive nature of this function, passes each subarray (pydata) to the next call and only
662// the final buffer is cast to a lowlevel view and resized (in VectorData), resulting in only the
663// first 1D array to be returned. See https://github.com/root-project/root/issues/17729
664// It is temporarily removed to prevent errors due to -Wunused-function, since it is no longer added.
665#if 0
666//---------------------------------------------------------------------------
668{
669 PyObject* pydata = VectorData(self, nullptr);
674 return newarr;
675}
676#endif
677
678//-----------------------------------------------------------------------------
679static PyObject* vector_iter(PyObject* v) {
681 if (!vi) return nullptr;
682
683 vi->ii_container = v;
684
685// tell the iterator code to set a life line if this container is a temporary
686 vi->vi_flags = vectoriterobject::kDefault;
687#if PY_VERSION_HEX >= 0x030e0000
689#else
690 if (Py_REFCNT(v) <= 1 || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue))
691#endif
693
694 Py_INCREF(v);
695
697 if (pyvalue_type) {
699 if (pyvalue_size) {
700 vi->vi_stride = PyLong_AsLong(pyvalue_size);
702 } else {
703 PyErr_Clear();
704 vi->vi_stride = 0;
705 }
706
708 std::string value_type = CPyCppyy_PyText_AsString(pyvalue_type);
709 value_type = Cppyy::ResolveName(value_type);
710 vi->vi_klass = Cppyy::GetScope(value_type);
711 if (!vi->vi_klass) {
712 // look for a special case of pointer to a class type (which is a builtin, but it
713 // is more useful to treat it polymorphically by allowing auto-downcasts)
714 const std::string& clean_type = TypeManip::clean_type(value_type, false, false);
716 if (c && TypeManip::compound(value_type) == "*") {
717 vi->vi_klass = c;
719 }
720 }
721 if (vi->vi_klass) {
722 vi->vi_converter = nullptr;
723 if (!vi->vi_flags) {
724 if (value_type.back() != '*') // meaning, object stored by-value
726 }
727 } else
728 vi->vi_converter = CPyCppyy::CreateConverter(value_type);
729 if (!vi->vi_stride) vi->vi_stride = Cppyy::SizeOf(value_type);
730
731 } else if (CPPScope_Check(pyvalue_type)) {
732 vi->vi_klass = ((CPPClass*)pyvalue_type)->fCppType;
733 vi->vi_converter = nullptr;
734 if (!vi->vi_stride) vi->vi_stride = Cppyy::SizeOf(vi->vi_klass);
735 if (!vi->vi_flags) vi->vi_flags = vectoriterobject::kNeedLifeLine;
736 }
737
738 PyObject* pydata = CallPyObjMethod(v, "__real_data");
739 if (!pydata || Utility::GetBuffer(pydata, '*', 1, vi->vi_data, false) == 0)
740 vi->vi_data = CPPInstance_Check(pydata) ? ((CPPInstance*)pydata)->GetObjectRaw() : nullptr;
742
743 } else {
744 PyErr_Clear();
745 vi->vi_data = nullptr;
746 vi->vi_stride = 0;
747 vi->vi_converter = nullptr;
748 vi->vi_klass = 0;
749 vi->vi_flags = 0;
750 }
751
753
754 vi->ii_pos = 0;
755 vi->ii_len = PySequence_Size(v);
756
758 return (PyObject*)vi;
759}
760
762{
763// Implement python's __getitem__ for std::vector<>s.
764 if (PySlice_Check(index)) {
765 if (!self->GetObject()) {
766 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
767 return nullptr;
768 }
769
772
773 start, stop, step;
775
777 if (!AdjustSlice(nlen, start, stop, step))
778 return nseq;
779
780 const Py_ssize_t sign = step < 0 ? -1 : 1;
781 for (Py_ssize_t i = start; i*sign < stop*sign; i += step) {
784 CallPyObjMethod(nseq, "push_back", item);
787 }
788
789 return nseq;
790 }
791
793}
794
795
797
799{
800// std::vector<bool> is a special-case in C++, and its return type depends on
801// the compiler: treat it special here as well
802 if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) {
804 "require object of type std::vector<bool>, but %s given",
805 Cppyy::GetScopedFinalName(self->ObjectIsA()).c_str());
806 return nullptr;
807 }
808
809 if (!self->GetObject()) {
810 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
811 return nullptr;
812 }
813
814 if (PySlice_Check(idx)) {
817
818 start, stop, step;
821 if (!AdjustSlice(nlen, start, stop, step))
822 return nseq;
823
824 const Py_ssize_t sign = step < 0 ? -1 : 1;
825 for (Py_ssize_t i = start; i*sign < stop*sign; i += step) {
828 CallPyObjMethod(nseq, "push_back", item);
831 }
832
833 return nseq;
834 }
835
837 if (!pyindex)
838 return nullptr;
839
842
843// get hold of the actual std::vector<bool> (no cast, as vector is never a base)
844 std::vector<bool>* vb = (std::vector<bool>*)self->GetObject();
845
846// finally, return the value
847 if (bool((*vb)[index]))
850}
851
853{
854// std::vector<bool> is a special-case in C++, and its return type depends on
855// the compiler: treat it special here as well
856 if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) {
858 "require object of type std::vector<bool>, but %s given",
859 Cppyy::GetScopedFinalName(self->ObjectIsA()).c_str());
860 return nullptr;
861 }
862
863 if (!self->GetObject()) {
864 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
865 return nullptr;
866 }
867
868 int bval = 0; PyObject* idx = nullptr;
869 if (!PyArg_ParseTuple(args, const_cast<char*>("Oi:__setitem__"), &idx, &bval))
870 return nullptr;
871
873 if (!pyindex)
874 return nullptr;
875
878
879// get hold of the actual std::vector<bool> (no cast, as vector is never a base)
880 std::vector<bool>* vb = (std::vector<bool>*)self->GetObject();
881
882// finally, set the value
883 (*vb)[index] = (bool)bval;
884
886}
887
888
889//- array behavior as primitives ----------------------------------------------
890PyObject* ArrayInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
891{
892// std::array is normally only constructed using aggregate initialization, which
893// is a concept that does not exist in python, so use this custom constructor to
894// to fill the array using setitem
895
896 if (args && PyTuple_GET_SIZE(args) == 1 && PySequence_Check(PyTuple_GET_ITEM(args, 0))) {
897 // construct the empty array, then fill it
899 if (!result)
900 return nullptr;
901
902 PyObject* items = PyTuple_GET_ITEM(args, 0);
904 if (PySequence_Size(self) != fillsz) {
905 PyErr_Format(PyExc_ValueError, "received sequence of size %zd where %zd expected",
908 return nullptr;
909 }
910
912 for (Py_ssize_t i = 0; i < fillsz; ++i) {
918 if (!sires) {
921 return nullptr;
922 } else
924 }
926
927 return result;
928 } else
929 PyErr_Clear();
930
931// The given argument wasn't iterable: simply forward to regular constructor
933 if (realInit) {
934 PyObject* result = PyObject_Call(realInit, args, nullptr);
936 return result;
937 }
938
939 return nullptr;
940}
941
942
943//- map behavior as primitives ------------------------------------------------
945{
946// construct an empty map, then fill it with the key, value pairs
948 if (!result)
949 return nullptr;
950
952 for (Py_ssize_t i = 0; i < PySequence_Size(pairs); ++i) {
954 PyObject* sires = nullptr;
955 if (pair && PySequence_Check(pair) && PySequence_Size(pair) == 2) {
956 PyObject* key = PySequence_GetItem(pair, 0);
960 Py_DECREF(key);
961 }
962 Py_DECREF(pair);
963 if (!sires) {
966 if (!PyErr_Occurred())
967 PyErr_SetString(PyExc_TypeError, "Failed to fill map (argument not a dict or sequence of pairs)");
968 return nullptr;
969 } else
971 }
973
974 return result;
975}
976
977PyObject* MapInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
978{
979// Specialized map constructor to allow construction from mapping containers and
980// from tuples of pairs ("initializer_list style").
981
982// PyMapping_Check is not very discriminatory, as it basically only checks for the
983// existence of __getitem__, hence the most common cases of tuple and list are
984// dropped straight-of-the-bat (the PyMapping_Items call will fail on them).
985 if (PyTuple_GET_SIZE(args) == 1 && PyMapping_Check(PyTuple_GET_ITEM(args, 0)) && \
987 PyObject* assoc = PyTuple_GET_ITEM(args, 0);
988#if PY_VERSION_HEX < 0x03000000
989 // to prevent warning about literal string, expand macro
990 PyObject* items = PyObject_CallMethod(assoc, (char*)"items", nullptr);
991#else
992 // in p3, PyMapping_Items isn't a macro, but a function that short-circuits dict
994#endif
995 if (items && PySequence_Check(items)) {
998 return result;
999 }
1000
1002 PyErr_Clear();
1003
1004 // okay to fall through as long as 'self' has not been created (is done in MapFromPairs)
1005 }
1006
1007// tuple of pairs case (some mapping types are sequences)
1008 if (PyTuple_GET_SIZE(args) == 1 && PySequence_Check(PyTuple_GET_ITEM(args, 0)))
1009 return MapFromPairs(self, PyTuple_GET_ITEM(args, 0));
1010
1011// The given argument wasn't a mapping or tuple of pairs: forward to regular constructor
1013 if (realInit) {
1014 PyObject* result = PyObject_Call(realInit, args, nullptr);
1016 return result;
1017 }
1018
1019 return nullptr;
1020}
1021
1023{
1024// Implement python's __contains__ for std::map/std::set
1025 PyObject* result = nullptr;
1026
1027 PyObject* iter = CallPyObjMethod(self, "find", obj);
1028 if (CPPInstance_Check(iter)) {
1029 PyStrings::gEnd);
1030 if (CPPInstance_Check(end)) {
1031 if (!PyObject_RichCompareBool(iter, end, Py_EQ)) {
1033 result = Py_True;
1034 }
1035 }
1036 Py_XDECREF(end);
1037 }
1038 Py_XDECREF(iter);
1039
1040 if (!result) {
1041 PyErr_Clear(); // e.g. wrong argument type, which should always lead to False
1043 result = Py_False;
1044 }
1045
1046 return result;
1047}
1048
1049
1050//- set behavior as primitives ------------------------------------------------
1051PyObject* SetInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
1052{
1053// Specialized set constructor to allow construction from Python sets.
1054 if (PyTuple_GET_SIZE(args) == 1 && PySet_Check(PyTuple_GET_ITEM(args, 0))) {
1055 PyObject* pyset = PyTuple_GET_ITEM(args, 0);
1056
1057 // construct an empty set, then fill it
1059 if (!result)
1060 return nullptr;
1061
1063 if (iter) {
1064 PyObject* ins_call = PyObject_GetAttrString(self, (char*)"insert");
1065
1066 IterItemGetter getter{iter};
1067 Py_DECREF(iter);
1068
1069 PyObject* item = getter.get();
1070 while (item) {
1072 Py_DECREF(item);
1073 if (!insres) {
1076 return nullptr;
1077 } else
1079 item = getter.get();
1080 }
1082 }
1083
1084 return result;
1085 }
1086
1087// The given argument wasn't iterable: simply forward to regular constructor
1089 if (realInit) {
1090 PyObject* result = PyObject_Call(realInit, args, nullptr);
1092 return result;
1093 }
1094
1095 return nullptr;
1096}
1097
1098
1099//- STL container iterator support --------------------------------------------
1100static const ptrdiff_t PS_END_ADDR = 7; // non-aligned address, so no clash
1101static const ptrdiff_t PS_FLAG_ADDR = 11; // id.
1102static const ptrdiff_t PS_COLL_ADDR = 13; // id.
1103
1105{
1106// Implement python's __iter__ for low level views used through STL-type begin()/end()
1108
1109 if (LowLevelView_Check(iter)) {
1110 // builtin pointer iteration: can only succeed if a size is available
1112 if (sz == -1) {
1113 Py_DECREF(iter);
1114 return nullptr;
1115 }
1116 PyObject* lliter = Py_TYPE(iter)->tp_iter(iter);
1117 ((indexiterobject*)lliter)->ii_len = sz;
1118 Py_DECREF(iter);
1119 return lliter;
1120 }
1121
1122 if (iter) {
1123 Py_DECREF(iter);
1124 PyErr_SetString(PyExc_TypeError, "unrecognized iterator type for low level views");
1125 }
1126
1127 return nullptr;
1128}
1129
1131{
1132// Implement python's __iter__ for std::iterator<>s
1134 if (iter) {
1135 PyStrings::gEnd);
1136 if (end) {
1137 if (CPPInstance_Check(iter)) {
1138 // use the data member cache to store extra state on the iterator object,
1139 // without it being visible on the Python side
1140 auto& dmc = ((CPPInstance*)iter)->GetDatamemberCache();
1141 dmc.push_back(std::make_pair(PS_END_ADDR, end));
1142
1143 // set a flag, indicating first iteration (reset in __next__)
1145 dmc.push_back(std::make_pair(PS_FLAG_ADDR, Py_False));
1146
1147 // make sure the iterated over collection remains alive for the duration
1148 Py_INCREF(self);
1149 dmc.push_back(std::make_pair(PS_COLL_ADDR, self));
1150 } else {
1151 // could store "end" on the object's dictionary anyway, but if end() returns
1152 // a user-customized object, then its __next__ is probably custom, too
1153 Py_DECREF(end);
1154 }
1155 }
1156 }
1157 return iter;
1158}
1159
1160//- generic iterator support over a sequence with operator[] and size ---------
1161//-----------------------------------------------------------------------------
1162static PyObject* index_iter(PyObject* c) {
1164 if (!ii) return nullptr;
1165
1166 Py_INCREF(c);
1167 ii->ii_container = c;
1168 ii->ii_pos = 0;
1169 ii->ii_len = PySequence_Size(c);
1170
1172 return (PyObject*)ii;
1173}
1174
1175
1176//- safe indexing for STL-like vector w/o iterator dictionaries ---------------
1177/* replaced by indexiterobject iteration, but may still have some future use ...
1178PyObject* CheckedGetItem(PyObject* self, PyObject* obj)
1179{
1180// Implement a generic python __getitem__ for STL-like classes that are missing the
1181// reflection info for their iterators. This is then used for iteration by means of
1182// consecutive indices, it such index is of integer type.
1183 Py_ssize_t size = PySequence_Size(self);
1184 Py_ssize_t idx = PyInt_AsSsize_t(obj);
1185 if ((size == (Py_ssize_t)-1 || idx == (Py_ssize_t)-1) && PyErr_Occurred()) {
1186 // argument conversion problem: let method itself resolve anew and report
1187 PyErr_Clear();
1188 return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj);
1189 }
1190
1191 bool inbounds = false;
1192 if (idx < 0) idx += size;
1193 if (0 <= idx && 0 <= size && idx < size)
1194 inbounds = true;
1195
1196 if (inbounds)
1197 return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj);
1198 else
1199 PyErr_SetString( PyExc_IndexError, "index out of range" );
1200
1201 return nullptr;
1202}*/
1203
1204
1205//- pair as sequence to allow tuple unpacking --------------------------------
1207{
1208// For std::map<> iteration, unpack std::pair<>s into tuples for the loop.
1209 long idx = PyLong_AsLong(pyindex);
1210 if (idx == -1 && PyErr_Occurred())
1211 return nullptr;
1212
1213 if (!CPPInstance_Check(self) || !((CPPInstance*)self)->GetObject()) {
1214 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
1215 return nullptr;
1216 }
1217
1218 if ((int)idx == 0)
1220 else if ((int)idx == 1)
1222
1223// still here? Trigger stop iteration
1224 PyErr_SetString(PyExc_IndexError, "out of bounds");
1225 return nullptr;
1226}
1227
1228//- simplistic len() functions -----------------------------------------------
1230 return PyInt_FromLong(2);
1231}
1232
1233
1234//- shared/unique_ptr behavior -----------------------------------------------
1235PyObject* SmartPtrInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
1236{
1237// since the shared/unique pointer will take ownership, we need to relinquish it
1239 if (realInit) {
1240 PyObject* result = PyObject_Call(realInit, args, nullptr);
1242 if (result && PyTuple_GET_SIZE(args) == 1 && CPPInstance_Check(PyTuple_GET_ITEM(args, 0))) {
1244 if (!(cppinst->fFlags & CPPInstance::kIsSmartPtr)) cppinst->CppOwns();
1245 }
1246 return result;
1247 }
1248 return nullptr;
1249}
1250
1251
1252//- string behavior as primitives --------------------------------------------
1253#if PY_VERSION_HEX >= 0x03000000
1254// TODO: this is wrong, b/c it doesn't order
1257}
1258#endif
1259static inline
1260PyObject* CPyCppyy_PyString_FromCppString(std::string_view s, bool native=true) {
1261 if (native)
1262 return PyBytes_FromStringAndSize(s.data(), s.size());
1263 return CPyCppyy_PyText_FromStringAndSize(s.data(), s.size());
1264}
1265
1266static inline
1267PyObject* CPyCppyy_PyString_FromCppString(std::wstring_view s, bool native=true) {
1268 PyObject* pyobj = PyUnicode_FromWideChar(s.data(), s.size());
1269 if (pyobj && native) {
1270 PyObject* pybytes = PyUnicode_AsEncodedString(pyobj, "UTF-8", "strict");
1272 pyobj = pybytes;
1273 }
1274 return pyobj;
1275}
1276
1277#define CPPYY_IMPL_STRING_PYTHONIZATION(type, name) \
1278static inline \
1279PyObject* name##StringGetData(PyObject* self, bool native=true) \
1280{ \
1281 if (CPyCppyy::CPPInstance_Check(self)) { \
1282 type* obj = ((type*)((CPPInstance*)self)->GetObject()); \
1283 if (obj) return CPyCppyy_PyString_FromCppString(*obj, native); \
1284 } \
1285 PyErr_Format(PyExc_TypeError, "object mismatch (%s expected)", #type); \
1286 return nullptr; \
1287} \
1288 \
1289PyObject* name##StringStr(PyObject* self) \
1290{ \
1291 PyObject* pyobj = name##StringGetData(self, false); \
1292 if (!pyobj) { \
1293 /* do a native conversion to make printing possible (debatable) */ \
1294 PyErr_Clear(); \
1295 PyObject* pybytes = name##StringGetData(self, true); \
1296 if (pybytes) { /* should not fail */ \
1297 pyobj = PyObject_Str(pybytes); \
1298 Py_DECREF(pybytes); \
1299 } \
1300 } \
1301 return pyobj; \
1302} \
1303 \
1304PyObject* name##StringBytes(PyObject* self) \
1305{ \
1306 return name##StringGetData(self, true); \
1307} \
1308 \
1309PyObject* name##StringRepr(PyObject* self) \
1310{ \
1311 PyObject* data = name##StringGetData(self, true); \
1312 if (data) { \
1313 PyObject* repr = PyObject_Repr(data); \
1314 Py_DECREF(data); \
1315 return repr; \
1316 } \
1317 return nullptr; \
1318} \
1319 \
1320PyObject* name##StringIsEqual(PyObject* self, PyObject* obj) \
1321{ \
1322 PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \
1323 if (data) { \
1324 PyObject* result = PyObject_RichCompare(data, obj, Py_EQ); \
1325 Py_DECREF(data); \
1326 return result; \
1327 } \
1328 return nullptr; \
1329} \
1330 \
1331PyObject* name##StringIsNotEqual(PyObject* self, PyObject* obj) \
1332{ \
1333 PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \
1334 if (data) { \
1335 PyObject* result = PyObject_RichCompare(data, obj, Py_NE); \
1336 Py_DECREF(data); \
1337 return result; \
1338 } \
1339 return nullptr; \
1340}
1341
1342// Only define STLStringCompare:
1343#define CPPYY_IMPL_STRING_PYTHONIZATION_CMP(type, name) \
1344CPPYY_IMPL_STRING_PYTHONIZATION(type, name) \
1345PyObject* name##StringCompare(PyObject* self, PyObject* obj) \
1346{ \
1347 PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \
1348 int result = 0; \
1349 if (data) { \
1350 result = PyObject_Compare(data, obj); \
1351 Py_DECREF(data); \
1352 } \
1353 if (PyErr_Occurred()) \
1354 return nullptr; \
1355 return PyInt_FromLong(result); \
1356}
1357
1361
1362static inline std::string* GetSTLString(CPPInstance* self) {
1363 if (!CPPInstance_Check(self)) {
1364 PyErr_SetString(PyExc_TypeError, "std::string object expected");
1365 return nullptr;
1366 }
1367
1368 std::string* obj = (std::string*)self->GetObject();
1369 if (!obj)
1370 PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer");
1371
1372 return obj;
1373}
1374
1376{
1377 std::string* obj = GetSTLString(self);
1378 if (!obj)
1379 return nullptr;
1380
1381 char* keywords[] = {(char*)"encoding", (char*)"errors", (char*)nullptr};
1382 const char* encoding = nullptr; const char* errors = nullptr;
1384 const_cast<char*>("s|s"), keywords, &encoding, &errors))
1385 return nullptr;
1386
1387 return PyUnicode_Decode(obj->data(), obj->size(), encoding, errors);
1388}
1389
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
1408{
1409 std::string* obj = GetSTLString(self);
1410 if (!obj)
1411 return nullptr;
1412
1413// both str and std::string have a method "replace", but the Python version only
1414// accepts strings and takes no keyword arguments, whereas the C++ version has no
1415// overload that takes a string
1416
1417 if (2 <= PyTuple_GET_SIZE(args) && CPyCppyy_PyText_Check(PyTuple_GET_ITEM(args, 0))) {
1418 PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size());
1419 PyObject* meth = PyObject_GetAttrString(pystr, (char*)"replace");
1422 Py_DECREF(meth);
1423 return result;
1424 }
1425
1426 PyObject* cppreplace = PyObject_GetAttrString((PyObject*)self, (char*)"__cpp_replace");
1427 if (cppreplace) {
1428 PyObject* result = PyObject_Call(cppreplace, args, nullptr);
1430 return result;
1431 }
1432
1433 PyErr_SetString(PyExc_AttributeError, "\'std::string\' object has no attribute \'replace\'");
1434 return nullptr;
1435}
1436
1437#define CPYCPPYY_STRING_FINDMETHOD(name, cppname, pyname) \
1438PyObject* STLString##name(CPPInstance* self, PyObject* args, PyObject* /*kwds*/) \
1439{ \
1440 std::string* obj = GetSTLString(self); \
1441 if (!obj) \
1442 return nullptr; \
1443 \
1444 PyObject* cppmeth = PyObject_GetAttrString((PyObject*)self, (char*)#cppname);\
1445 if (cppmeth) { \
1446 PyObject* result = PyObject_Call(cppmeth, args, nullptr); \
1447 Py_DECREF(cppmeth); \
1448 if (result) { \
1449 if (PyLongOrInt_AsULong64(result) == (PY_ULONG_LONG)std::string::npos) {\
1450 Py_DECREF(result); \
1451 return PyInt_FromLong(-1); \
1452 } \
1453 return result; \
1454 } \
1455 PyErr_Clear(); \
1456 } \
1457 \
1458 PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size());\
1459 PyObject* pymeth = PyObject_GetAttrString(pystr, (char*)#pyname); \
1460 Py_DECREF(pystr); \
1461 PyObject* result = PyObject_CallObject(pymeth, args); \
1462 Py_DECREF(pymeth); \
1463 return result; \
1464}
1465
1466// both str and std::string have method "find" and "rfin"; try the C++ version first
1467// and fall back on the Python one in case of failure
1470
1472{
1473 std::string* obj = GetSTLString(self);
1474 if (!obj)
1475 return nullptr;
1476
1477 PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size());
1480 return attr;
1481}
1482
1483
1484#if 0
1486{
1487// force C++ string types conversion to Python str per Python __repr__ requirements
1489 if (!res || CPyCppyy_PyText_Check(res))
1490 return res;
1492 Py_DECREF(res);
1493 return str_res;
1494}
1495
1497{
1498// force C++ string types conversion to Python str per Python __str__ requirements
1500 if (!res || CPyCppyy_PyText_Check(res))
1501 return res;
1503 Py_DECREF(res);
1504 return str_res;
1505}
1506#endif
1507
1509{
1510// std::string objects hash to the same values as Python strings to allow
1511// matches in dictionaries etc.
1514 Py_DECREF(data);
1515 return h;
1516}
1517
1518
1519//- string_view behavior as primitive ----------------------------------------
1521{
1522// if constructed from a Python unicode object, the constructor will convert it
1523// to a temporary byte string, which is likely to go out of scope too soon; so
1524// buffer it as needed
1526 if (realInit) {
1527 PyObject *strbuf = nullptr, *newArgs = nullptr;
1528 if (PyTuple_GET_SIZE(args) == 1) {
1529 PyObject* arg0 = PyTuple_GET_ITEM(args, 0);
1530 if (PyUnicode_Check(arg0)) {
1531 // convert to the expected bytes array to control the temporary
1532 strbuf = PyUnicode_AsEncodedString(arg0, "UTF-8", "strict");
1533 newArgs = PyTuple_New(1);
1536 } else if (PyBytes_Check(arg0)) {
1537 // tie the life time of the provided string to the string_view
1538 Py_INCREF(arg0);
1539 strbuf = arg0;
1540 }
1541 }
1542
1543 PyObject* result = PyObject_Call(realInit, newArgs ? newArgs : args, nullptr);
1544
1547
1548 // if construction was successful and a string buffer was used, add a
1549 // life line to it from the string_view bound object
1550 if (result && self && strbuf)
1553
1554 return result;
1555 }
1556 return nullptr;
1557}
1558
1559
1560//- STL iterator behavior ----------------------------------------------------
1562{
1563// Python iterator protocol __next__ for STL forward iterators.
1564 bool mustIncrement = true;
1565 PyObject* last = nullptr;
1566 if (CPPInstance_Check(self)) {
1567 auto& dmc = ((CPPInstance*)self)->GetDatamemberCache();
1568 for (auto& p: dmc) {
1569 if (p.first == PS_END_ADDR) {
1570 last = p.second;
1571 Py_INCREF(last);
1572 } else if (p.first == PS_FLAG_ADDR) {
1573 mustIncrement = p.second == Py_True;
1574 if (!mustIncrement) {
1575 Py_DECREF(p.second);
1577 p.second = Py_True;
1578 }
1579 }
1580 }
1581 }
1582
1583 PyObject* next = nullptr;
1584 if (last) {
1585 // handle special case of empty container (i.e. self is end)
1586 if (!PyObject_RichCompareBool(last, self, Py_EQ)) {
1587 bool iter_valid = true;
1588 if (mustIncrement) {
1589 // prefer preinc, but allow post-inc; in both cases, it is "self" that has
1590 // the updated state to dereference
1592 if (!iter) {
1593 PyErr_Clear();
1594 static PyObject* dummy = PyInt_FromLong(1l);
1596 }
1598 Py_XDECREF(iter);
1599 }
1600
1601 if (iter_valid) {
1603 if (!next) PyErr_Clear();
1604 }
1605 }
1606 Py_DECREF(last);
1607 }
1608
1609 if (!next) PyErr_SetString(PyExc_StopIteration, "");
1610 return next;
1611}
1612
1613
1614//- STL complex<T> behavior --------------------------------------------------
1615#define COMPLEX_METH_GETSET(name, cppname) \
1616static PyObject* name##ComplexGet(PyObject* self, void*) { \
1617 return PyObject_CallMethodNoArgs(self, cppname); \
1618} \
1619static int name##ComplexSet(PyObject* self, PyObject* value, void*) { \
1620 PyObject* result = PyObject_CallMethodOneArg(self, cppname, value); \
1621 if (result) { \
1622 Py_DECREF(result); \
1623 return 0; \
1624 } \
1625 return -1; \
1626} \
1627PyGetSetDef name##Complex{(char*)#name, (getter)name##ComplexGet, (setter)name##ComplexSet, nullptr, nullptr};
1628
1631
1634 if (!real) return nullptr;
1635 double r = PyFloat_AsDouble(real);
1636 Py_DECREF(real);
1637 if (r == -1. && PyErr_Occurred())
1638 return nullptr;
1639
1641 if (!imag) return nullptr;
1642 double i = PyFloat_AsDouble(imag);
1643 Py_DECREF(imag);
1644 if (i == -1. && PyErr_Occurred())
1645 return nullptr;
1646
1647 return PyComplex_FromDoubles(r, i);
1648}
1649
1652 if (!real) return nullptr;
1653 double r = PyFloat_AsDouble(real);
1654 Py_DECREF(real);
1655 if (r == -1. && PyErr_Occurred())
1656 return nullptr;
1657
1659 if (!imag) return nullptr;
1660 double i = PyFloat_AsDouble(imag);
1661 Py_DECREF(imag);
1662 if (i == -1. && PyErr_Occurred())
1663 return nullptr;
1664
1665 std::ostringstream s;
1666 s << '(' << r << '+' << i << "j)";
1667 return CPyCppyy_PyText_FromString(s.str().c_str());
1668}
1669
1671{
1672 return PyFloat_FromDouble(((std::complex<double>*)self->GetObject())->real());
1673}
1674
1675static int ComplexDRealSet(CPPInstance* self, PyObject* value, void*)
1676{
1677 double d = PyFloat_AsDouble(value);
1678 if (d == -1.0 && PyErr_Occurred())
1679 return -1;
1680 ((std::complex<double>*)self->GetObject())->real(d);
1681 return 0;
1682}
1683
1684PyGetSetDef ComplexDReal{(char*)"real", (getter)ComplexDRealGet, (setter)ComplexDRealSet, nullptr, nullptr};
1685
1686
1688{
1689 return PyFloat_FromDouble(((std::complex<double>*)self->GetObject())->imag());
1690}
1691
1692static int ComplexDImagSet(CPPInstance* self, PyObject* value, void*)
1693{
1694 double d = PyFloat_AsDouble(value);
1695 if (d == -1.0 && PyErr_Occurred())
1696 return -1;
1697 ((std::complex<double>*)self->GetObject())->imag(d);
1698 return 0;
1699}
1700
1701PyGetSetDef ComplexDImag{(char*)"imag", (getter)ComplexDImagGet, (setter)ComplexDImagSet, nullptr, nullptr};
1702
1704{
1705 double r = ((std::complex<double>*)self->GetObject())->real();
1706 double i = ((std::complex<double>*)self->GetObject())->imag();
1707 return PyComplex_FromDoubles(r, i);
1708}
1709
1710
1711} // unnamed namespace
1712
1713
1714//- public functions ---------------------------------------------------------
1715namespace CPyCppyy {
1716 std::set<std::string> gIteratorTypes;
1717}
1718
1719static inline
1720bool run_pythonizors(PyObject* pyclass, PyObject* pyname, const std::vector<PyObject*>& v)
1721{
1722 PyObject* args = PyTuple_New(2);
1725
1726 bool pstatus = true;
1727 for (auto pythonizor : v) {
1729 if (!result) {
1730 pstatus = false; // TODO: detail the error handling
1731 break;
1732 }
1734 }
1735 Py_DECREF(args);
1736
1737 return pstatus;
1738}
1739
1740bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name)
1741{
1742// Add pre-defined pythonizations (for STL and ROOT) to classes based on their
1743// signature and/or class name.
1744 if (!pyclass)
1745 return false;
1746
1748
1749//- method name based pythonization ------------------------------------------
1750
1751// for smart pointer style classes that are otherwise not known as such; would
1752// prefer operator-> as that returns a pointer (which is simpler since it never
1753// has to deal with ref-assignment), but operator* plays better with STL iters
1754// and algorithms
1759
1760// for pre-check of nullptr for boolean types
1762#if PY_VERSION_HEX >= 0x03000000
1763 const char* pybool_name = "__bool__";
1764#else
1765 const char* pybool_name = "__nonzero__";
1766#endif
1768 }
1769
1770 // for STL containers, and user classes modeled after them. Guard the alias to
1771 // __len__ by verifying that size() returns an integer type and the class has
1772 // begin()/end() or operator[] (i.e. is container-like). This prevents bool()
1773 // returning False for valid objects whose size() returns non-integer types like
1774 // std::optional<std::size_t>. Skip if size() has multiple overloads, as that
1775 // indicates it is not the simple container-style size() one would map to __len__.
1776 if (HasAttrDirect(pyclass, PyStrings::gSize, /*mustBeCPyCppyy=*/true) || HasAttrInMRO(pyclass, PyStrings::gSize)) {
1777 bool sizeIsInteger = false;
1780 auto *ol = (CPPOverload *)pySizeMethod;
1781 if (ol->HasMethods() && ol->fMethodInfo->fMethods.size() == 1) {
1783 ol->fMethodInfo->fMethods[0]->Reflex(Cppyy::Reflex::RETURN_TYPE, Cppyy::Reflex::AS_STRING);
1784 if (pyrestype) {
1787 }
1788 }
1789 }
1791
1792 if (sizeIsInteger) {
1796 if (hasIterators || hasSubscript) {
1797 Utility::AddToClass(pyclass, "__len__", "size");
1798 }
1799 }
1800 }
1801
1802 if (!IsTemplatedSTLClass(name, "vector") && // vector is dealt with below
1805 // obtain the name of the return type
1806 const auto& v = Cppyy::GetMethodIndicesFromName(klass->fCppType, "begin");
1807 if (!v.empty()) {
1808 // check return type; if not explicitly an iterator, add it to the "known" return
1809 // types to add the "next" method on use
1811 const std::string& resname = Cppyy::GetMethodResultType(meth);
1812 bool isIterator = gIteratorTypes.find(resname) != gIteratorTypes.end();
1814 if (resname.find("iterator") == std::string::npos)
1815 gIteratorTypes.insert(resname);
1816 isIterator = true;
1817 }
1818
1819 if (isIterator) {
1820 // install iterator protocol a la STL
1823 } else {
1824 // still okay if this is some pointer type of builtin persuasion (general class
1825 // won't work: the return type needs to understand the iterator protocol)
1826 std::string resolved = Cppyy::ResolveName(resname);
1827 if (resolved.back() == '*' && Cppyy::IsBuiltin(resolved.substr(0, resolved.size()-1))) {
1830 }
1831 }
1832 }
1833 }
1834 if (!((PyTypeObject*)pyclass)->tp_iter && // no iterator resolved
1836 // Python will iterate over __getitem__ using integers, but C++ operator[] will never raise
1837 // a StopIteration. A checked getitem (raising IndexError if beyond size()) works in some
1838 // cases but would mess up if operator[] is meant to implement an associative container. So,
1839 // this has to be implemented as an iterator protocol.
1842 }
1843 }
1844
1845// operator==/!= are used in op_richcompare of CPPInstance, which subsequently allows
1846// comparisons to None; if no operator is available, a hook is installed for lazy
1847// lookups in the global and/or class namespace
1848 if (HasAttrDirect(pyclass, PyStrings::gEq, true) && \
1849 Cppyy::GetMethodIndicesFromName(klass->fCppType, "__eq__").empty()) {
1851 if (!klass->fOperators) klass->fOperators = new Utility::PyOperators();
1852 klass->fOperators->fEq = cppol;
1853 // re-insert the forwarding __eq__ from the CPPInstance in case there was a Python-side
1854 // override in the base class
1855 static PyObject* top_eq = nullptr;
1856 if (!top_eq) {
1859 Py_DECREF(top_eq); // make it borrowed
1861 }
1863 }
1864
1865 if (HasAttrDirect(pyclass, PyStrings::gNe, true) && \
1866 Cppyy::GetMethodIndicesFromName(klass->fCppType, "__ne__").empty()) {
1868 if (!klass->fOperators) klass->fOperators = new Utility::PyOperators();
1869 klass->fOperators->fNe = cppol;
1870 // re-insert the forwarding __ne__ (same reason as above for __eq__)
1871 static PyObject* top_ne = nullptr;
1872 if (!top_ne) {
1875 Py_DECREF(top_ne); // make it borrowed
1877 }
1879 }
1880
1881#if 0
1883 // guarantee that the result of __repr__ is a Python string
1884 Utility::AddToClass(pyclass, "__cpp_repr", "__repr__");
1886 }
1887
1889 // guarantee that the result of __str__ is a Python string
1890 Utility::AddToClass(pyclass, "__cpp_str", "__str__");
1892 }
1893#endif
1894
1895 if (Cppyy::IsAggregate(((CPPClass*)pyclass)->fCppType) && name.compare(0, 5, "std::", 5) != 0 &&
1896 name.compare(0, 6, "tuple<", 6) != 0) {
1897 // create a pseudo-constructor to allow initializer-style object creation
1898 Cppyy::TCppType_t kls = ((CPPClass*)pyclass)->fCppType;
1900 if (ndata) {
1901 std::string rname = name;
1903
1904 std::ostringstream initdef;
1905 initdef << "namespace __cppyy_internal {\n"
1906 << "void init_" << rname << "(" << name << "*& self";
1907 bool codegen_ok = true;
1908 std::vector<std::string> arg_types, arg_names, arg_defaults;
1909 arg_types.reserve(ndata); arg_names.reserve(ndata); arg_defaults.reserve(ndata);
1910 for (Cppyy::TCppIndex_t i = 0; i < ndata; ++i) {
1912 continue;
1913
1914 const std::string& txt = Cppyy::GetDatamemberType(kls, i);
1915 const std::string& res = Cppyy::IsEnum(txt) ? txt : Cppyy::ResolveName(txt);
1916 const std::string& cpd = TypeManip::compound(res);
1917 std::string res_clean = TypeManip::clean_type(res, false, true);
1918
1919 if (res_clean == "internal_enum_type_t")
1920 res_clean = txt; // restore (properly scoped name)
1921
1922 if (res.rfind(']') == std::string::npos && res.rfind(')') == std::string::npos) {
1923 if (!cpd.empty()) arg_types.push_back(res_clean+cpd);
1924 else arg_types.push_back("const "+res_clean+"&");
1925 arg_names.push_back(Cppyy::GetDatamemberName(kls, i));
1926 if ((!cpd.empty() && cpd.back() == '*') || Cppyy::IsBuiltin(res_clean))
1927 arg_defaults.push_back("0");
1928 else {
1931 }
1932 } else {
1933 codegen_ok = false; // TODO: how to support arrays, anonymous enums, etc?
1934 break;
1935 }
1936 }
1937
1938 if (codegen_ok && !arg_types.empty()) {
1939 bool defaults_ok = arg_defaults.size() == arg_types.size();
1940 for (std::vector<std::string>::size_type i = 0; i < arg_types.size(); ++i) {
1941 initdef << ", " << arg_types[i] << " " << arg_names[i];
1942 if (defaults_ok) initdef << " = " << arg_defaults[i];
1943 }
1944 initdef << ") {\n self = new " << name << "{";
1945 for (std::vector<std::string>::size_type i = 0; i < arg_names.size(); ++i) {
1946 if (i != 0) initdef << ", ";
1947 initdef << arg_names[i];
1948 }
1949 initdef << "};\n} }";
1950
1951 if (Cppyy::Compile(initdef.str(), true /* silent */)) {
1952 Cppyy::TCppScope_t cis = Cppyy::GetScope("__cppyy_internal");
1953 const auto& mix = Cppyy::GetMethodIndicesFromName(cis, "init_"+rname);
1954 if (mix.size()) {
1955 if (!Utility::AddToClass(pyclass, "__init__",
1956 new CPPFunction(cis, Cppyy::GetMethod(cis, mix[0]))))
1957 PyErr_Clear();
1958 }
1959 }
1960 }
1961 }
1962 }
1963
1964
1965//- class name based pythonization -------------------------------------------
1966
1967 if (IsTemplatedSTLClass(name, "span")) {
1968 // libstdc++ (GCC >= 15) implements std::span::iterator using a private
1969 // nested tag type, which makes the iterator non-instantiable by
1970 // CallFunc-generated wrappers (the return type cannot be named without
1971 // violating access rules).
1972 //
1973 // To preserve correct Python iteration semantics, we replace begin()/end()
1974 // for std::span to return a custom pointer-based iterator instead. This
1975 // avoids relying on std::span::iterator while still providing a real C++
1976 // iterator object that CPyCppyy can also wrap and expose via
1977 // __iter__/__next__.
1980 }
1981
1982 if (IsTemplatedSTLClass(name, "vector")) {
1983
1984 // std::vector<bool> is a special case in C++
1986 if (klass->fCppType == sVectorBoolTypeID) {
1989 } else {
1990 // constructor that takes python collections
1991 Utility::AddToClass(pyclass, "__real_init", "__init__");
1993
1994 // data with size
1995 Utility::AddToClass(pyclass, "__real_data", "data");
1997
1998 // The addition of the __array__ utility to std::vector Python proxies causes a
1999 // bug where the resulting array is a single dimension, causing loss of data when
2000 // converting to numpy arrays, for >1dim vectors. Since this C++ pythonization
2001 // was added with the upgrade in 6.32, and is only defined and used recursively,
2002 // the safe option is to disable this function and no longer add it.
2003#if 0
2004 // numpy array conversion
2006#endif
2007
2008 // checked getitem
2010 Utility::AddToClass(pyclass, "_getitem__unchecked", "__getitem__");
2012 }
2013
2014 // vector-optimized iterator protocol
2016
2017 // optimized __iadd__
2019
2020 // helpers for iteration
2021 const std::string& vtype = Cppyy::ResolveName(name+"::value_type");
2022 if (vtype.rfind("value_type") == std::string::npos) { // actually resolved?
2026 }
2027
2028 size_t typesz = Cppyy::SizeOf(name+"::value_type");
2029 if (typesz) {
2033 }
2034 }
2035 }
2036
2037 else if (IsTemplatedSTLClass(name, "array")) {
2038 // constructor that takes python associative collections
2039 Utility::AddToClass(pyclass, "__real_init", "__init__");
2041 }
2042
2043 else if (IsTemplatedSTLClass(name, "map") || IsTemplatedSTLClass(name, "unordered_map")) {
2044 // constructor that takes python associative collections
2045 Utility::AddToClass(pyclass, "__real_init", "__init__");
2047
2049 }
2050
2051 else if (IsTemplatedSTLClass(name, "set")) {
2052 // constructor that takes python associative collections
2053 Utility::AddToClass(pyclass, "__real_init", "__init__");
2055
2057 }
2058
2059 else if (IsTemplatedSTLClass(name, "pair")) {
2062 }
2063
2064 if (IsTemplatedSTLClass(name, "shared_ptr") || IsTemplatedSTLClass(name, "unique_ptr")) {
2065 Utility::AddToClass(pyclass, "__real_init", "__init__");
2067 }
2068
2069 else if (!((PyTypeObject*)pyclass)->tp_iter && \
2070 (name.find("iterator") != std::string::npos || gIteratorTypes.find(name) != gIteratorTypes.end())) {
2071 ((PyTypeObject*)pyclass)->tp_iternext = (iternextfunc)STLIterNext;
2075 }
2076
2077 else if (name == "string" || name == "std::string") { // TODO: ask backend as well
2086 Utility::AddToClass(pyclass, "__cpp_find", "find");
2088 Utility::AddToClass(pyclass, "__cpp_rfind", "rfind");
2090 Utility::AddToClass(pyclass, "__cpp_replace", "replace");
2093
2094 // to allow use of std::string in dictionaries and findable with str
2096 }
2097
2098 else if (name == "basic_string_view<char,char_traits<char> >" || name == "std::basic_string_view<char>") {
2099 Utility::AddToClass(pyclass, "__real_init", "__init__");
2107 }
2108
2109// The first condition was already present in upstream CPyCppyy. The other two
2110// are special to ROOT, because its reflection layer gives us the types without
2111// the "std::" namespace. On some platforms, that applies only to the template
2112// arguments, and on others also to the "basic_string".
2113 else if (name == "std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> >"
2114 || name == "basic_string<wchar_t,char_traits<wchar_t>,allocator<wchar_t> >"
2115 || name == "std::basic_string<wchar_t,char_traits<wchar_t>,allocator<wchar_t> >"
2116 ) {
2123 }
2124
2125 else if (name == "complex<double>" || name == "std::complex<double>") {
2126 Utility::AddToClass(pyclass, "__cpp_real", "real");
2128 Utility::AddToClass(pyclass, "__cpp_imag", "imag");
2132 }
2133
2134 else if (IsTemplatedSTLClass(name, "complex")) {
2135 Utility::AddToClass(pyclass, "__cpp_real", "real");
2137 Utility::AddToClass(pyclass, "__cpp_imag", "imag");
2141 }
2142
2143// direct user access; there are two calls here:
2144// - explicit pythonization: won't fall through to the base classes and is preferred if present
2145// - normal pythonization: only called if explicit isn't present, falls through to base classes
2146 bool bUserOk = true; PyObject* res = nullptr;
2150 bUserOk = (bool)res;
2151 } else {
2153 if (func) {
2154 res = PyObject_CallFunctionObjArgs(func, pyclass, pyname, nullptr);
2155 Py_DECREF(func);
2156 bUserOk = (bool)res;
2157 } else
2158 PyErr_Clear();
2159 }
2160 if (!bUserOk) {
2162 return false;
2163 } else {
2164 Py_XDECREF(res);
2165 // pyname handed to args tuple below
2166 }
2167
2168// call registered pythonizors, if any: first run the namespace-specific pythonizors, then
2169// the global ones (the idea is to allow writing a pythonizor that see all classes)
2170 bool pstatus = true;
2172 auto &pyzMap = pythonizations();
2173 if (!outer_scope.empty()) {
2174 auto p = pyzMap.find(outer_scope);
2175 if (p != pyzMap.end()) {
2177 name.substr(outer_scope.size()+2, std::string::npos).c_str());
2180 }
2181 }
2182
2183 if (pstatus) {
2184 auto p = pyzMap.find("");
2185 if (p != pyzMap.end())
2186 pstatus = run_pythonizors(pyclass, pyname, p->second);
2187 }
2188
2190
2191// phew! all done ...
2192 return pstatus;
2193}
#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:385
#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:381
#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)
_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
std::ios_base::fmtflags fFlags
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:110
const_iterator end() const
PyObject * gCTypesType
Definition PyStrings.cxx:39
PyObject * gRealInit
Definition PyStrings.cxx:42
PyObject * gExPythonize
Definition PyStrings.cxx:72
PyObject * gLifeLine
Definition PyStrings.cxx:29
PyObject * gGetItem
Definition PyStrings.cxx:23
PyObject * gCppBool
Definition PyStrings.cxx:11
PyObject * gCppReal
Definition PyStrings.cxx:64
PyObject * gPythonize
Definition PyStrings.cxx:73
PyObject * gTypeCode
Definition PyStrings.cxx:38
PyObject * gPostInc
Definition PyStrings.cxx:18
PyObject * gCppImag
Definition PyStrings.cxx:65
PyObject * gValueSize
Definition PyStrings.cxx:62
PyObject * gSetItem
Definition PyStrings.cxx:25
PyObject * gGetNoCheck
Definition PyStrings.cxx:24
PyObject * gCppRepr
Definition PyStrings.cxx:35
PyObject * gValueType
Definition PyStrings.cxx:61
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:813
bool AddToClass(PyObject *pyclass, const char *label, PyCFunction cfunc, int flags=METH_VARARGS)
Definition Utility.cxx:186
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:24
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:22
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:53
RPY_EXPORTED std::string ResolveName(const std::string &cppitem_name)
TCppScope_t TCppType_t
Definition cpp_cppyy.h:19
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:18
RPY_EXPORTED std::string GetMethodResultType(TCppMethod_t)
RPY_EXPORTED std::string GetDatamemberName(TCppScope_t scope, TCppIndex_t idata)