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// get an attribute without causing getattr lookups
58 if (dct) {
61 return attr;
62 }
63 return nullptr;
64}
65
66//-----------------------------------------------------------------------------
67inline bool IsTemplatedSTLClass(const std::string& name, const std::string& klass) {
68// Scan the name of the class and determine whether it is a template instantiation.
69 auto pos = name.find(klass);
70 return (pos == 0 || pos == 5) && name.find("::", name.rfind(">")) == std::string::npos;
71}
72
73// to prevent compiler warnings about const char* -> char*
74inline PyObject* CallPyObjMethod(PyObject* obj, const char* meth)
75{
76// Helper; call method with signature: obj->meth().
77 Py_INCREF(obj);
78 PyObject* result = PyObject_CallMethod(obj, const_cast<char*>(meth), const_cast<char*>(""));
79 Py_DECREF(obj);
80 return result;
81}
82
83//-----------------------------------------------------------------------------
84inline PyObject* CallPyObjMethod(PyObject* obj, const char* meth, PyObject* arg1)
85{
86// Helper; call method with signature: obj->meth(arg1).
87 Py_INCREF(obj);
89 obj, const_cast<char*>(meth), const_cast<char*>("O"), arg1);
90 Py_DECREF(obj);
91 return result;
92}
93
94//-----------------------------------------------------------------------------
96{
97// Helper; converts python index into straight C index.
99 if (idx == (Py_ssize_t)-1 && PyErr_Occurred())
100 return nullptr;
101
103 if (idx >= size || (idx < 0 && idx < -size)) {
104 PyErr_SetString(PyExc_IndexError, "index out of range");
105 return nullptr;
106 }
107
108 PyObject* pyindex = nullptr;
109 if (idx >= 0) {
111 pyindex = index;
112 } else
114
115 return pyindex;
116}
117
118//-----------------------------------------------------------------------------
119inline bool AdjustSlice(const Py_ssize_t nlen, Py_ssize_t& start, Py_ssize_t& stop, Py_ssize_t& step)
120{
121// Helper; modify slice range to match the container.
122 if ((step > 0 && stop <= start) || (step < 0 && start <= stop))
123 return false;
124
125 if (start < 0) start = 0;
126 if (start >= nlen-1;
127 if (step >= nlen) step = nlen;
128
129 stop = step > 0 ? std::min(nlen, stop) : (stop >= 0 ? stop : -1);
130 return true;
131}
132
133//-----------------------------------------------------------------------------
135{
136// Helper; call method with signature: meth(pyindex).
139 if (!pyindex) {
141 return nullptr;
142 }
143
147 return result;
148}
149
150//- "smart pointer" behavior ---------------------------------------------------
152{
153// Follow operator*() if present (available in python as __deref__), so that
154// smart pointers behave as expected.
156 // TODO: these calls come from TemplateProxy and are unlikely to be needed in practice,
157 // whereas as-is, they can accidentally dereference the result of end() on some STL
158 // containers. Obviously, this is a dumb hack that should be resolved more fundamentally.
160 return nullptr;
161 }
162
164 PyErr_SetString(PyExc_TypeError, "getattr(): attribute name must be string");
165
167 if (!pyptr)
168 return nullptr;
169
170// prevent a potential infinite loop
171 if (Py_TYPE(pyptr) == Py_TYPE(self)) {
174 PyErr_Format(PyExc_AttributeError, "%s has no attribute \'%s\'",
178
180 return nullptr;
181 }
182
185 return result;
186}
187
188//-----------------------------------------------------------------------------
190{
191// Follow operator->() if present (available in python as __follow__), so that
192// smart pointers behave as expected.
194 PyErr_SetString(PyExc_TypeError, "getattr(): attribute name must be string");
195
197 if (!pyptr)
198 return nullptr;
199
202 return result;
203}
204
205//- pointer checking bool converter -------------------------------------------
207{
208 if (!CPPInstance_Check(self)) {
209 PyErr_SetString(PyExc_TypeError, "C++ object proxy expected");
210 return nullptr;
211 }
212
213 if (!((CPPInstance*)self)->GetObject())
215
217}
218
219//- vector behavior as primitives ----------------------------------------------
220#if PY_VERSION_HEX < 0x03040000
221#define PyObject_LengthHint _PyObject_LengthHint
222#endif
223
224// TODO: can probably use the below getters in the InitializerListConverter
225struct ItemGetter {
226 ItemGetter(PyObject* pyobj) : fPyObject(pyobj) { Py_INCREF(fPyObject); }
227 virtual ~ItemGetter() { Py_DECREF(fPyObject); }
228 virtual Py_ssize_t size() = 0;
229 virtual PyObject* get() = 0;
230 PyObject* fPyObject;
231};
232
233struct CountedItemGetter : public ItemGetter {
234 CountedItemGetter(PyObject* pyobj) : ItemGetter(pyobj), fCur(0) {}
235 Py_ssize_t fCur;
236};
237
238struct TupleItemGetter : public CountedItemGetter {
239 using CountedItemGetter::CountedItemGetter;
240 Py_ssize_t size() override { return PyTuple_GET_SIZE(fPyObject); }
241 PyObject* get() override {
242 if (fCur < PyTuple_GET_SIZE(fPyObject)) {
243 PyObject* item = PyTuple_GET_ITEM(fPyObject, fCur++);
245 return item;
246 }
247 PyErr_SetString(PyExc_StopIteration, "end of tuple");
248 return nullptr;
249 }
250};
251
252struct ListItemGetter : public CountedItemGetter {
253 using CountedItemGetter::CountedItemGetter;
254 Py_ssize_t size() override { return PyList_GET_SIZE(fPyObject); }
255 PyObject* get() override {
256 if (fCur < PyList_GET_SIZE(fPyObject)) {
257 PyObject* item = PyList_GET_ITEM(fPyObject, fCur++);
259 return item;
260 }
261 PyErr_SetString(PyExc_StopIteration, "end of list");
262 return nullptr;
263 }
264};
265
266struct SequenceItemGetter : public CountedItemGetter {
267 using CountedItemGetter::CountedItemGetter;
268 Py_ssize_t size() override {
269 Py_ssize_t sz = PySequence_Size(fPyObject);
270 if (sz < 0) {
271 PyErr_Clear();
272 return PyObject_LengthHint(fPyObject, 8);
273 }
274 return sz;
275 }
276 PyObject* get() override { return PySequence_GetItem(fPyObject, fCur++); }
277};
278
279struct IterItemGetter : public ItemGetter {
280 using ItemGetter::ItemGetter;
281 Py_ssize_t size() override { return PyObject_LengthHint(fPyObject, 8); }
282 PyObject* get() override { return (*(Py_TYPE(fPyObject)->tp_iternext))(fPyObject); }
283};
284
285static ItemGetter* GetGetter(PyObject* args)
286{
287// Create an ItemGetter to loop over the iterable argument, if any.
288 ItemGetter* getter = nullptr;
289
290 if (PyTuple_GET_SIZE(args) == 1) {
291 PyObject* fi = PyTuple_GET_ITEM(args, 0);
293 return nullptr; // do not accept string to fill std::vector<char>
294
295 // TODO: this only tests for new-style buffers, which is too strict, but a
296 // generic check for Py_TYPE(fi)->tp_as_buffer is too loose (note that the
297 // main use case is numpy, which offers the new interface)
299 return nullptr;
300
302 getter = new TupleItemGetter(fi);
303 else if (PyList_CheckExact(fi))
304 getter = new ListItemGetter(fi);
305 else if (PySequence_Check(fi))
306 getter = new SequenceItemGetter(fi);
307 else {
309 if (iter) {
310 getter = new IterItemGetter{iter};
311 Py_DECREF(iter);
312 }
313 else PyErr_Clear();
314 }
315 }
316
317 return getter;
318}
319
320namespace {
321
323{
324 static bool compiled = false;
325
326 if (compiled)
327 return;
328
329 compiled = true;
330
331 auto code = R"(
332namespace __cppyy_internal {
333
334template <class T>
335struct ptr_iterator {
336 T *cur;
337 T *end;
338
339 ptr_iterator(T *c, T *e) : cur(c), end(e) {}
340
341 T &operator*() const { return *cur; }
342 ptr_iterator &operator++()
343 {
344 ++cur;
345 return *this;
346 }
347 bool operator==(const ptr_iterator &other) const { return cur == other.cur; }
348 bool operator!=(const ptr_iterator &other) const { return !(*this == other); }
349};
350
351template <class T>
352ptr_iterator<T> make_iter(T *begin, T *end)
353{
354 return {begin, end};
355}
356
357} // namespace __cppyy_internal
358
359// Note: for const span<T>, T is const-qualified here
360template <class T>
361auto __cppyy_internal_begin(T &s) noexcept
362{
363 return __cppyy_internal::make_iter(s.data(), s.data() + s.size());
364}
365
366// Note: for const span<T>, T is const-qualified here
367template <class T>
368auto __cppyy_internal_end(T &s) noexcept
369{
370 // end iterator = begin iterator with cur == end
371 return __cppyy_internal::make_iter(s.data() + s.size(), s.data() + s.size());
372}
373 )";
374 Cppyy::Compile(code, /*silent*/ true);
375}
376
378{
379 static PyObject *pyFunc = nullptr;
380 if (!pyFunc) {
383 pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_begin");
384 if (!pyFunc) {
385 PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper "
386 "'__cppyy_internal_begin' for std::span pythonization");
387 }
388 }
389 return pyFunc;
390}
391
393{
394 static PyObject *pyFunc = nullptr;
395 if (!pyFunc) {
398 pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_end");
399 if (!pyFunc) {
400 PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper "
401 "'__cppyy_internal_end' for std::span pythonization");
402 }
403 }
404 return pyFunc;
405}
406
407} // namespace
408
410{
411 auto begin = spanBegin();
412 if (!begin)
413 return nullptr;
414 return PyObject_CallOneArg(begin, self);
415}
416
418{
419 auto spanEnd();
420 if (!end)
421 return nullptr;
422 return PyObject_CallOneArg(end, self);
423}
424
425static bool FillVector(PyObject* vecin, PyObject* args, ItemGetter* getter)
426{
427 Py_ssize_t sz = getter->size();
428 if (sz < 0)
429 return false;
430
431// reserve memory as applicable
432 if (0 < sz) {
433 PyObject* res = PyObject_CallMethod(vecin, (char*)"reserve", (char*)"n", sz);
434 Py_DECREF(res);
435 } else // i.e. sz == 0, so empty container: done
436 return true;
437
438 bool fill_ok = true;
439
440// two main options: a list of lists (or tuples), or a list of objects; the former
441// are emplace_back'ed, the latter push_back'ed
443 if (!fi) PyErr_Clear();
445 // use emplace_back to construct the vector entries one by one
446 PyObject* eb_call = PyObject_GetAttrString(vecin, (char*)"emplace_back");
448 bool value_is_vector = false;
450 // if the value_type is a vector, then allow for initialization from sequences
451 if (std::string(CPyCppyy_PyText_AsString(vtype)).rfind("std::vector", 0) != std::string::npos)
452 value_is_vector = true;
453 } else
454 PyErr_Clear();
456
457 if (eb_call) {
459 for (int i = 0; /* until break */; ++i) {
460 PyObject* item = getter->get();
461 if (item) {
463 eb_args = PyTuple_New(1);
465 } else if (PyTuple_CheckExact(item)) {
466 eb_args = item;
467 } else if (PyList_CheckExact(item)) {
470 for (Py_ssize_t j = 0; j < isz; ++j) {
474 }
476 } else {
478 PyErr_Format(PyExc_TypeError, "argument %d is not a tuple or list", i);
479 fill_ok = false;
480 break;
481 }
484 if (!ebres) {
485 fill_ok = false;
486 break;
487 }
489 } else {
490 if (PyErr_Occurred()) {
493 fill_ok = false;
494 else { PyErr_Clear(); }
495 }
496 break;
497 }
498 }
500 }
501 } else {
502 // use push_back to add the vector entries one by one
503 PyObject* pb_call = PyObject_GetAttrString(vecin, (char*)"push_back");
504 if (pb_call) {
505 for (;;) {
506 PyObject* item = getter->get();
507 if (item) {
510 if (!pbres) {
511 fill_ok = false;
512 break;
513 }
515 } else {
516 if (PyErr_Occurred()) {
519 fill_ok = false;
520 else { PyErr_Clear(); }
521 }
522 break;
523 }
524 }
526 }
527 }
528 Py_XDECREF(fi);
529
530 return fill_ok;
531}
532
533PyObject* VectorIAdd(PyObject* self, PyObject* args, PyObject* /* kwds */)
534{
535// Implement fast __iadd__ on std::vector (generic __iadd__ is in Python)
536 ItemGetter* getter = GetGetter(args);
537
538 if (getter) {
539 bool fill_ok = FillVector(self, args, getter);
540 delete getter;
541
542 if (!fill_ok)
543 return nullptr;
544
546 return self;
547 }
548
549// if no getter, it could still be b/c we have a buffer (e.g. numpy); looping over
550// a buffer here is slow, so use insert() instead
551 if (PyTuple_GET_SIZE(args) == 1) {
552 PyObject* fi = PyTuple_GET_ITEM(args, 0);
555 if (vend) {
556 // when __iadd__ is overriden, the operation does not end with
557 // calling the __iadd__ method, but also assigns the result to the
558 // lhs of the iadd. For example, performing vec += arr, Python
559 // first calls our override, and then does vec = vec.iadd(arr).
562
563 if (!it)
564 return nullptr;
565
566 Py_DECREF(it);
567 // Assign the result of the __iadd__ override to the std::vector
569 return self;
570 }
571 }
572 }
573
574 if (!PyErr_Occurred())
575 PyErr_SetString(PyExc_TypeError, "argument is not iterable");
576 return nullptr; // error already set
577}
578
579
580PyObject* VectorInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
581{
582// Specialized vector constructor to allow construction from containers; allowing
583// such construction from initializer_list instead would possible, but can be
584// error-prone. This use case is common enough for std::vector to implement it
585// directly, except for arrays (which can be passed wholesale) and strings (which
586// won't convert properly as they'll be seen as buffers)
587
588 ItemGetter* getter = GetGetter(args);
589
590 if (getter) {
591 // construct an empty vector, then back-fill it
593 if (!result) {
594 delete getter;
595 return nullptr;
596 }
597
598 bool fill_ok = FillVector(self, args, getter);
599 delete getter;
600
601 if (!fill_ok) {
603 return nullptr;
604 }
605
606 return result;
607 }
608
609// The given argument wasn't iterable: simply forward to regular constructor
611 if (realInit) {
612 PyObject* result = PyObject_Call(realInit, args, nullptr);
614 return result;
615 }
616
617 return nullptr;
618}
619
620//---------------------------------------------------------------------------
622{
623 PyObject* pydata = CallPyObjMethod(self, "__real_data");
625 return pydata;
626
628 if (!pylen) {
629 PyErr_Clear();
630 return pydata;
631 }
632
633 long clen = PyInt_AsLong(pylen);
635
637 ((CPPInstance*)pydata)->CastToArray(clen);
638 return pydata;
639 }
640
641 ((LowLevelView*)pydata)->resize((size_t)clen);
642 return pydata;
643}
644
645
646// This function implements __array__, added to std::vector python proxies and causes
647// a bug (see explanation at Utility::AddToClass(pyclass, "__array__"...) in CPyCppyy::Pythonize)
648// The recursive nature of this function, passes each subarray (pydata) to the next call and only
649// the final buffer is cast to a lowlevel view and resized (in VectorData), resulting in only the
650// first 1D array to be returned. See https://github.com/root-project/root/issues/17729
651// It is temporarily removed to prevent errors due to -Wunused-function, since it is no longer added.
652#if 0
653//---------------------------------------------------------------------------
655{
656 PyObject* pydata = VectorData(self, nullptr);
661 return newarr;
662}
663#endif
664
665//-----------------------------------------------------------------------------
666static PyObject* vector_iter(PyObject* v) {
668 if (!vi) return nullptr;
669
670 vi->ii_container = v;
671
672// tell the iterator code to set a life line if this container is a temporary
673 vi->vi_flags = vectoriterobject::kDefault;
674#if PY_VERSION_HEX >= 0x030e0000
676#else
677 if (Py_REFCNT(v) <= 1 || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue))
678#endif
680
681 Py_INCREF(v);
682
684 if (pyvalue_type) {
686 if (pyvalue_size) {
687 vi->vi_stride = PyLong_AsLong(pyvalue_size);
689 } else {
690 PyErr_Clear();
691 vi->vi_stride = 0;
692 }
693
695 std::string value_type = CPyCppyy_PyText_AsString(pyvalue_type);
696 value_type = Cppyy::ResolveName(value_type);
697 vi->vi_klass = Cppyy::GetScope(value_type);
698 if (!vi->vi_klass) {
699 // look for a special case of pointer to a class type (which is a builtin, but it
700 // is more useful to treat it polymorphically by allowing auto-downcasts)
701 const std::string& clean_type = TypeManip::clean_type(value_type, false, false);
703 if (c && TypeManip::compound(value_type) == "*") {
704 vi->vi_klass = c;
706 }
707 }
708 if (vi->vi_klass) {
709 vi->vi_converter = nullptr;
710 if (!vi->vi_flags) {
711 if (value_type.back() != '*') // meaning, object stored by-value
713 }
714 } else
715 vi->vi_converter = CPyCppyy::CreateConverter(value_type);
716 if (!vi->vi_stride) vi->vi_stride = Cppyy::SizeOf(value_type);
717
718 } else if (CPPScope_Check(pyvalue_type)) {
719 vi->vi_klass = ((CPPClass*)pyvalue_type)->fCppType;
720 vi->vi_converter = nullptr;
721 if (!vi->vi_stride) vi->vi_stride = Cppyy::SizeOf(vi->vi_klass);
722 if (!vi->vi_flags) vi->vi_flags = vectoriterobject::kNeedLifeLine;
723 }
724
725 PyObject* pydata = CallPyObjMethod(v, "__real_data");
726 if (!pydata || Utility::GetBuffer(pydata, '*', 1, vi->vi_data, false) == 0)
727 vi->vi_data = CPPInstance_Check(pydata) ? ((CPPInstance*)pydata)->GetObjectRaw() : nullptr;
729
730 } else {
731 PyErr_Clear();
732 vi->vi_data = nullptr;
733 vi->vi_stride = 0;
734 vi->vi_converter = nullptr;
735 vi->vi_klass = 0;
736 vi->vi_flags = 0;
737 }
738
740
741 vi->ii_pos = 0;
742 vi->ii_len = PySequence_Size(v);
743
745 return (PyObject*)vi;
746}
747
749{
750// Implement python's __getitem__ for std::vector<>s.
751 if (PySlice_Check(index)) {
752 if (!self->GetObject()) {
753 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
754 return nullptr;
755 }
756
759
760 start, stop, step;
762
764 if (!AdjustSlice(nlen, start, stop, step))
765 return nseq;
766
767 const Py_ssize_t sign = step < 0 ? -1 : 1;
768 for (Py_ssize_t i = start; i*sign < stop*sign; i += step) {
771 CallPyObjMethod(nseq, "push_back", item);
774 }
775
776 return nseq;
777 }
778
780}
781
782
784
786{
787// std::vector<bool> is a special-case in C++, and its return type depends on
788// the compiler: treat it special here as well
789 if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) {
791 "require object of type std::vector<bool>, but %s given",
792 Cppyy::GetScopedFinalName(self->ObjectIsA()).c_str());
793 return nullptr;
794 }
795
796 if (!self->GetObject()) {
797 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
798 return nullptr;
799 }
800
801 if (PySlice_Check(idx)) {
804
805 start, stop, step;
808 if (!AdjustSlice(nlen, start, stop, step))
809 return nseq;
810
811 const Py_ssize_t sign = step < 0 ? -1 : 1;
812 for (Py_ssize_t i = start; i*sign < stop*sign; i += step) {
815 CallPyObjMethod(nseq, "push_back", item);
818 }
819
820 return nseq;
821 }
822
824 if (!pyindex)
825 return nullptr;
826
829
830// get hold of the actual std::vector<bool> (no cast, as vector is never a base)
831 std::vector<bool>* vb = (std::vector<bool>*)self->GetObject();
832
833// finally, return the value
834 if (bool((*vb)[index]))
837}
838
840{
841// std::vector<bool> is a special-case in C++, and its return type depends on
842// the compiler: treat it special here as well
843 if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) {
845 "require object of type std::vector<bool>, but %s given",
846 Cppyy::GetScopedFinalName(self->ObjectIsA()).c_str());
847 return nullptr;
848 }
849
850 if (!self->GetObject()) {
851 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
852 return nullptr;
853 }
854
855 int bval = 0; PyObject* idx = nullptr;
856 if (!PyArg_ParseTuple(args, const_cast<char*>("Oi:__setitem__"), &idx, &bval))
857 return nullptr;
858
860 if (!pyindex)
861 return nullptr;
862
865
866// get hold of the actual std::vector<bool> (no cast, as vector is never a base)
867 std::vector<bool>* vb = (std::vector<bool>*)self->GetObject();
868
869// finally, set the value
870 (*vb)[index] = (bool)bval;
871
873}
874
875
876//- array behavior as primitives ----------------------------------------------
877PyObject* ArrayInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
878{
879// std::array is normally only constructed using aggregate initialization, which
880// is a concept that does not exist in python, so use this custom constructor to
881// to fill the array using setitem
882
883 if (args && PyTuple_GET_SIZE(args) == 1 && PySequence_Check(PyTuple_GET_ITEM(args, 0))) {
884 // construct the empty array, then fill it
886 if (!result)
887 return nullptr;
888
889 PyObject* items = PyTuple_GET_ITEM(args, 0);
891 if (PySequence_Size(self) != fillsz) {
892 PyErr_Format(PyExc_ValueError, "received sequence of size %zd where %zd expected",
895 return nullptr;
896 }
897
899 for (Py_ssize_t i = 0; i < fillsz; ++i) {
905 if (!sires) {
908 return nullptr;
909 } else
911 }
913
914 return result;
915 } else
916 PyErr_Clear();
917
918// The given argument wasn't iterable: simply forward to regular constructor
920 if (realInit) {
921 PyObject* result = PyObject_Call(realInit, args, nullptr);
923 return result;
924 }
925
926 return nullptr;
927}
928
929
930//- map behavior as primitives ------------------------------------------------
932{
933// construct an empty map, then fill it with the key, value pairs
935 if (!result)
936 return nullptr;
937
939 for (Py_ssize_t i = 0; i < PySequence_Size(pairs); ++i) {
941 PyObject* sires = nullptr;
942 if (pair && PySequence_Check(pair) && PySequence_Size(pair) == 2) {
943 PyObject* key = PySequence_GetItem(pair, 0);
947 Py_DECREF(key);
948 }
949 Py_DECREF(pair);
950 if (!sires) {
953 if (!PyErr_Occurred())
954 PyErr_SetString(PyExc_TypeError, "Failed to fill map (argument not a dict or sequence of pairs)");
955 return nullptr;
956 } else
958 }
960
961 return result;
962}
963
964PyObject* MapInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
965{
966// Specialized map constructor to allow construction from mapping containers and
967// from tuples of pairs ("initializer_list style").
968
969// PyMapping_Check is not very discriminatory, as it basically only checks for the
970// existence of __getitem__, hence the most common cases of tuple and list are
971// dropped straight-of-the-bat (the PyMapping_Items call will fail on them).
972 if (PyTuple_GET_SIZE(args) == 1 && PyMapping_Check(PyTuple_GET_ITEM(args, 0)) && \
974 PyObject* assoc = PyTuple_GET_ITEM(args, 0);
975#if PY_VERSION_HEX < 0x03000000
976 // to prevent warning about literal string, expand macro
977 PyObject* items = PyObject_CallMethod(assoc, (char*)"items", nullptr);
978#else
979 // in p3, PyMapping_Items isn't a macro, but a function that short-circuits dict
981#endif
982 if (items && PySequence_Check(items)) {
985 return result;
986 }
987
989 PyErr_Clear();
990
991 // okay to fall through as long as 'self' has not been created (is done in MapFromPairs)
992 }
993
994// tuple of pairs case (some mapping types are sequences)
995 if (PyTuple_GET_SIZE(args) == 1 && PySequence_Check(PyTuple_GET_ITEM(args, 0)))
996 return MapFromPairs(self, PyTuple_GET_ITEM(args, 0));
997
998// The given argument wasn't a mapping or tuple of pairs: forward to regular constructor
1000 if (realInit) {
1001 PyObject* result = PyObject_Call(realInit, args, nullptr);
1003 return result;
1004 }
1005
1006 return nullptr;
1007}
1008
1010{
1011// Implement python's __contains__ for std::map/std::set
1012 PyObject* result = nullptr;
1013
1014 PyObject* iter = CallPyObjMethod(self, "find", obj);
1015 if (CPPInstance_Check(iter)) {
1016 PyStrings::gEnd);
1017 if (CPPInstance_Check(end)) {
1018 if (!PyObject_RichCompareBool(iter, end, Py_EQ)) {
1020 result = Py_True;
1021 }
1022 }
1023 Py_XDECREF(end);
1024 }
1025 Py_XDECREF(iter);
1026
1027 if (!result) {
1028 PyErr_Clear(); // e.g. wrong argument type, which should always lead to False
1030 result = Py_False;
1031 }
1032
1033 return result;
1034}
1035
1036
1037//- set behavior as primitives ------------------------------------------------
1038PyObject* SetInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
1039{
1040// Specialized set constructor to allow construction from Python sets.
1041 if (PyTuple_GET_SIZE(args) == 1 && PySet_Check(PyTuple_GET_ITEM(args, 0))) {
1042 PyObject* pyset = PyTuple_GET_ITEM(args, 0);
1043
1044 // construct an empty set, then fill it
1046 if (!result)
1047 return nullptr;
1048
1050 if (iter) {
1051 PyObject* ins_call = PyObject_GetAttrString(self, (char*)"insert");
1052
1053 IterItemGetter getter{iter};
1054 Py_DECREF(iter);
1055
1056 PyObject* item = getter.get();
1057 while (item) {
1059 Py_DECREF(item);
1060 if (!insres) {
1063 return nullptr;
1064 } else
1066 item = getter.get();
1067 }
1069 }
1070
1071 return result;
1072 }
1073
1074// The given argument wasn't iterable: simply forward to regular constructor
1076 if (realInit) {
1077 PyObject* result = PyObject_Call(realInit, args, nullptr);
1079 return result;
1080 }
1081
1082 return nullptr;
1083}
1084
1085
1086//- STL container iterator support --------------------------------------------
1087static const ptrdiff_t PS_END_ADDR = 7; // non-aligned address, so no clash
1088static const ptrdiff_t PS_FLAG_ADDR = 11; // id.
1089static const ptrdiff_t PS_COLL_ADDR = 13; // id.
1090
1092{
1093// Implement python's __iter__ for low level views used through STL-type begin()/end()
1095
1096 if (LowLevelView_Check(iter)) {
1097 // builtin pointer iteration: can only succeed if a size is available
1099 if (sz == -1) {
1100 Py_DECREF(iter);
1101 return nullptr;
1102 }
1103 PyObject* lliter = Py_TYPE(iter)->tp_iter(iter);
1104 ((indexiterobject*)lliter)->ii_len = sz;
1105 Py_DECREF(iter);
1106 return lliter;
1107 }
1108
1109 if (iter) {
1110 Py_DECREF(iter);
1111 PyErr_SetString(PyExc_TypeError, "unrecognized iterator type for low level views");
1112 }
1113
1114 return nullptr;
1115}
1116
1118{
1119// Implement python's __iter__ for std::iterator<>s
1121 if (iter) {
1122 PyStrings::gEnd);
1123 if (end) {
1124 if (CPPInstance_Check(iter)) {
1125 // use the data member cache to store extra state on the iterator object,
1126 // without it being visible on the Python side
1127 auto& dmc = ((CPPInstance*)iter)->GetDatamemberCache();
1128 dmc.push_back(std::make_pair(PS_END_ADDR, end));
1129
1130 // set a flag, indicating first iteration (reset in __next__)
1132 dmc.push_back(std::make_pair(PS_FLAG_ADDR, Py_False));
1133
1134 // make sure the iterated over collection remains alive for the duration
1135 Py_INCREF(self);
1136 dmc.push_back(std::make_pair(PS_COLL_ADDR, self));
1137 } else {
1138 // could store "end" on the object's dictionary anyway, but if end() returns
1139 // a user-customized object, then its __next__ is probably custom, too
1140 Py_DECREF(end);
1141 }
1142 }
1143 }
1144 return iter;
1145}
1146
1147//- generic iterator support over a sequence with operator[] and size ---------
1148//-----------------------------------------------------------------------------
1149static PyObject* index_iter(PyObject* c) {
1151 if (!ii) return nullptr;
1152
1153 Py_INCREF(c);
1154 ii->ii_container = c;
1155 ii->ii_pos = 0;
1156 ii->ii_len = PySequence_Size(c);
1157
1159 return (PyObject*)ii;
1160}
1161
1162
1163//- safe indexing for STL-like vector w/o iterator dictionaries ---------------
1164/* replaced by indexiterobject iteration, but may still have some future use ...
1165PyObject* CheckedGetItem(PyObject* self, PyObject* obj)
1166{
1167// Implement a generic python __getitem__ for STL-like classes that are missing the
1168// reflection info for their iterators. This is then used for iteration by means of
1169// consecutive indices, it such index is of integer type.
1170 Py_ssize_t size = PySequence_Size(self);
1171 Py_ssize_t idx = PyInt_AsSsize_t(obj);
1172 if ((size == (Py_ssize_t)-1 || idx == (Py_ssize_t)-1) && PyErr_Occurred()) {
1173 // argument conversion problem: let method itself resolve anew and report
1174 PyErr_Clear();
1175 return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj);
1176 }
1177
1178 bool inbounds = false;
1179 if (idx < 0) idx += size;
1180 if (0 <= idx && 0 <= size && idx < size)
1181 inbounds = true;
1182
1183 if (inbounds)
1184 return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj);
1185 else
1186 PyErr_SetString( PyExc_IndexError, "index out of range" );
1187
1188 return nullptr;
1189}*/
1190
1191
1192//- pair as sequence to allow tuple unpacking --------------------------------
1194{
1195// For std::map<> iteration, unpack std::pair<>s into tuples for the loop.
1196 long idx = PyLong_AsLong(pyindex);
1197 if (idx == -1 && PyErr_Occurred())
1198 return nullptr;
1199
1200 if (!CPPInstance_Check(self) || !((CPPInstance*)self)->GetObject()) {
1201 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
1202 return nullptr;
1203 }
1204
1205 if ((int)idx == 0)
1207 else if ((int)idx == 1)
1209
1210// still here? Trigger stop iteration
1211 PyErr_SetString(PyExc_IndexError, "out of bounds");
1212 return nullptr;
1213}
1214
1215//- simplistic len() functions -----------------------------------------------
1217 return PyInt_FromLong(2);
1218}
1219
1220
1221//- shared/unique_ptr behavior -----------------------------------------------
1222PyObject* SmartPtrInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
1223{
1224// since the shared/unique pointer will take ownership, we need to relinquish it
1226 if (realInit) {
1227 PyObject* result = PyObject_Call(realInit, args, nullptr);
1229 if (result && PyTuple_GET_SIZE(args) == 1 && CPPInstance_Check(PyTuple_GET_ITEM(args, 0))) {
1231 if (!(cppinst->fFlags & CPPInstance::kIsSmartPtr)) cppinst->CppOwns();
1232 }
1233 return result;
1234 }
1235 return nullptr;
1236}
1237
1238
1239//- string behavior as primitives --------------------------------------------
1240#if PY_VERSION_HEX >= 0x03000000
1241// TODO: this is wrong, b/c it doesn't order
1244}
1245#endif
1246static inline
1247PyObject* CPyCppyy_PyString_FromCppString(std::string_view s, bool native=true) {
1248 if (native)
1249 return PyBytes_FromStringAndSize(s.data(), s.size());
1250 return CPyCppyy_PyText_FromStringAndSize(s.data(), s.size());
1251}
1252
1253static inline
1254PyObject* CPyCppyy_PyString_FromCppString(std::wstring_view s, bool native=true) {
1255 PyObject* pyobj = PyUnicode_FromWideChar(s.data(), s.size());
1256 if (pyobj && native) {
1257 PyObject* pybytes = PyUnicode_AsEncodedString(pyobj, "UTF-8", "strict");
1259 pyobj = pybytes;
1260 }
1261 return pyobj;
1262}
1263
1264#define CPPYY_IMPL_STRING_PYTHONIZATION(type, name) \
1265static inline \
1266PyObject* name##StringGetData(PyObject* self, bool native=true) \
1267{ \
1268 if (CPyCppyy::CPPInstance_Check(self)) { \
1269 type* obj = ((type*)((CPPInstance*)self)->GetObject()); \
1270 if (obj) return CPyCppyy_PyString_FromCppString(*obj, native); \
1271 } \
1272 PyErr_Format(PyExc_TypeError, "object mismatch (%s expected)", #type); \
1273 return nullptr; \
1274} \
1275 \
1276PyObject* name##StringStr(PyObject* self) \
1277{ \
1278 PyObject* pyobj = name##StringGetData(self, false); \
1279 if (!pyobj) { \
1280 /* do a native conversion to make printing possible (debatable) */ \
1281 PyErr_Clear(); \
1282 PyObject* pybytes = name##StringGetData(self, true); \
1283 if (pybytes) { /* should not fail */ \
1284 pyobj = PyObject_Str(pybytes); \
1285 Py_DECREF(pybytes); \
1286 } \
1287 } \
1288 return pyobj; \
1289} \
1290 \
1291PyObject* name##StringBytes(PyObject* self) \
1292{ \
1293 return name##StringGetData(self, true); \
1294} \
1295 \
1296PyObject* name##StringRepr(PyObject* self) \
1297{ \
1298 PyObject* data = name##StringGetData(self, true); \
1299 if (data) { \
1300 PyObject* repr = PyObject_Repr(data); \
1301 Py_DECREF(data); \
1302 return repr; \
1303 } \
1304 return nullptr; \
1305} \
1306 \
1307PyObject* name##StringIsEqual(PyObject* self, PyObject* obj) \
1308{ \
1309 PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \
1310 if (data) { \
1311 PyObject* result = PyObject_RichCompare(data, obj, Py_EQ); \
1312 Py_DECREF(data); \
1313 return result; \
1314 } \
1315 return nullptr; \
1316} \
1317 \
1318PyObject* name##StringIsNotEqual(PyObject* self, PyObject* obj) \
1319{ \
1320 PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \
1321 if (data) { \
1322 PyObject* result = PyObject_RichCompare(data, obj, Py_NE); \
1323 Py_DECREF(data); \
1324 return result; \
1325 } \
1326 return nullptr; \
1327}
1328
1329// Only define STLStringCompare:
1330#define CPPYY_IMPL_STRING_PYTHONIZATION_CMP(type, name) \
1331CPPYY_IMPL_STRING_PYTHONIZATION(type, name) \
1332PyObject* name##StringCompare(PyObject* self, PyObject* obj) \
1333{ \
1334 PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \
1335 int result = 0; \
1336 if (data) { \
1337 result = PyObject_Compare(data, obj); \
1338 Py_DECREF(data); \
1339 } \
1340 if (PyErr_Occurred()) \
1341 return nullptr; \
1342 return PyInt_FromLong(result); \
1343}
1344
1348
1349static inline std::string* GetSTLString(CPPInstance* self) {
1350 if (!CPPInstance_Check(self)) {
1351 PyErr_SetString(PyExc_TypeError, "std::string object expected");
1352 return nullptr;
1353 }
1354
1355 std::string* obj = (std::string*)self->GetObject();
1356 if (!obj)
1357 PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer");
1358
1359 return obj;
1360}
1361
1363{
1364 std::string* obj = GetSTLString(self);
1365 if (!obj)
1366 return nullptr;
1367
1368 char* keywords[] = {(char*)"encoding", (char*)"errors", (char*)nullptr};
1369 const char* encoding = nullptr; const char* errors = nullptr;
1371 const_cast<char*>("s|s"), keywords, &encoding, &errors))
1372 return nullptr;
1373
1374 return PyUnicode_Decode(obj->data(), obj->size(), encoding, errors);
1375}
1376
1378{
1379 std::string* obj = GetSTLString(self);
1380 if (!obj)
1381 return nullptr;
1382
1383 const char* needle = CPyCppyy_PyText_AsString(pyobj);
1384 if (!needle)
1385 return nullptr;
1386
1387 if (obj->find(needle) != std::string::npos) {
1389 }
1390
1392}
1393
1395{
1396 std::string* obj = GetSTLString(self);
1397 if (!obj)
1398 return nullptr;
1399
1400// both str and std::string have a method "replace", but the Python version only
1401// accepts strings and takes no keyword arguments, whereas the C++ version has no
1402// overload that takes a string
1403
1404 if (2 <= PyTuple_GET_SIZE(args) && CPyCppyy_PyText_Check(PyTuple_GET_ITEM(args, 0))) {
1405 PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size());
1406 PyObject* meth = PyObject_GetAttrString(pystr, (char*)"replace");
1409 Py_DECREF(meth);
1410 return result;
1411 }
1412
1413 PyObject* cppreplace = PyObject_GetAttrString((PyObject*)self, (char*)"__cpp_replace");
1414 if (cppreplace) {
1415 PyObject* result = PyObject_Call(cppreplace, args, nullptr);
1417 return result;
1418 }
1419
1420 PyErr_SetString(PyExc_AttributeError, "\'std::string\' object has no attribute \'replace\'");
1421 return nullptr;
1422}
1423
1424#define CPYCPPYY_STRING_FINDMETHOD(name, cppname, pyname) \
1425PyObject* STLString##name(CPPInstance* self, PyObject* args, PyObject* /*kwds*/) \
1426{ \
1427 std::string* obj = GetSTLString(self); \
1428 if (!obj) \
1429 return nullptr; \
1430 \
1431 PyObject* cppmeth = PyObject_GetAttrString((PyObject*)self, (char*)#cppname);\
1432 if (cppmeth) { \
1433 PyObject* result = PyObject_Call(cppmeth, args, nullptr); \
1434 Py_DECREF(cppmeth); \
1435 if (result) { \
1436 if (PyLongOrInt_AsULong64(result) == (PY_ULONG_LONG)std::string::npos) {\
1437 Py_DECREF(result); \
1438 return PyInt_FromLong(-1); \
1439 } \
1440 return result; \
1441 } \
1442 PyErr_Clear(); \
1443 } \
1444 \
1445 PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size());\
1446 PyObject* pymeth = PyObject_GetAttrString(pystr, (char*)#pyname); \
1447 Py_DECREF(pystr); \
1448 PyObject* result = PyObject_CallObject(pymeth, args); \
1449 Py_DECREF(pymeth); \
1450 return result; \
1451}
1452
1453// both str and std::string have method "find" and "rfin"; try the C++ version first
1454// and fall back on the Python one in case of failure
1457
1459{
1460 std::string* obj = GetSTLString(self);
1461 if (!obj)
1462 return nullptr;
1463
1464 PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size());
1467 return attr;
1468}
1469
1470
1471#if 0
1473{
1474// force C++ string types conversion to Python str per Python __repr__ requirements
1476 if (!res || CPyCppyy_PyText_Check(res))
1477 return res;
1479 Py_DECREF(res);
1480 return str_res;
1481}
1482
1484{
1485// force C++ string types conversion to Python str per Python __str__ requirements
1487 if (!res || CPyCppyy_PyText_Check(res))
1488 return res;
1490 Py_DECREF(res);
1491 return str_res;
1492}
1493#endif
1494
1496{
1497// std::string objects hash to the same values as Python strings to allow
1498// matches in dictionaries etc.
1501 Py_DECREF(data);
1502 return h;
1503}
1504
1505
1506//- string_view behavior as primitive ----------------------------------------
1508{
1509// if constructed from a Python unicode object, the constructor will convert it
1510// to a temporary byte string, which is likely to go out of scope too soon; so
1511// buffer it as needed
1513 if (realInit) {
1514 PyObject *strbuf = nullptr, *newArgs = nullptr;
1515 if (PyTuple_GET_SIZE(args) == 1) {
1516 PyObject* arg0 = PyTuple_GET_ITEM(args, 0);
1517 if (PyUnicode_Check(arg0)) {
1518 // convert to the expected bytes array to control the temporary
1519 strbuf = PyUnicode_AsEncodedString(arg0, "UTF-8", "strict");
1520 newArgs = PyTuple_New(1);
1523 } else if (PyBytes_Check(arg0)) {
1524 // tie the life time of the provided string to the string_view
1525 Py_INCREF(arg0);
1526 strbuf = arg0;
1527 }
1528 }
1529
1530 PyObject* result = PyObject_Call(realInit, newArgs ? newArgs : args, nullptr);
1531
1534
1535 // if construction was successful and a string buffer was used, add a
1536 // life line to it from the string_view bound object
1537 if (result && self && strbuf)
1540
1541 return result;
1542 }
1543 return nullptr;
1544}
1545
1546
1547//- STL iterator behavior ----------------------------------------------------
1549{
1550// Python iterator protocol __next__ for STL forward iterators.
1551 bool mustIncrement = true;
1552 PyObject* last = nullptr;
1553 if (CPPInstance_Check(self)) {
1554 auto& dmc = ((CPPInstance*)self)->GetDatamemberCache();
1555 for (auto& p: dmc) {
1556 if (p.first == PS_END_ADDR) {
1557 last = p.second;
1558 Py_INCREF(last);
1559 } else if (p.first == PS_FLAG_ADDR) {
1560 mustIncrement = p.second == Py_True;
1561 if (!mustIncrement) {
1562 Py_DECREF(p.second);
1564 p.second = Py_True;
1565 }
1566 }
1567 }
1568 }
1569
1570 PyObject* next = nullptr;
1571 if (last) {
1572 // handle special case of empty container (i.e. self is end)
1573 if (!PyObject_RichCompareBool(last, self, Py_EQ)) {
1574 bool iter_valid = true;
1575 if (mustIncrement) {
1576 // prefer preinc, but allow post-inc; in both cases, it is "self" that has
1577 // the updated state to dereference
1579 if (!iter) {
1580 PyErr_Clear();
1581 static PyObject* dummy = PyInt_FromLong(1l);
1583 }
1585 Py_XDECREF(iter);
1586 }
1587
1588 if (iter_valid) {
1590 if (!next) PyErr_Clear();
1591 }
1592 }
1593 Py_DECREF(last);
1594 }
1595
1596 if (!next) PyErr_SetString(PyExc_StopIteration, "");
1597 return next;
1598}
1599
1600
1601//- STL complex<T> behavior --------------------------------------------------
1602#define COMPLEX_METH_GETSET(name, cppname) \
1603static PyObject* name##ComplexGet(PyObject* self, void*) { \
1604 return PyObject_CallMethodNoArgs(self, cppname); \
1605} \
1606static int name##ComplexSet(PyObject* self, PyObject* value, void*) { \
1607 PyObject* result = PyObject_CallMethodOneArg(self, cppname, value); \
1608 if (result) { \
1609 Py_DECREF(result); \
1610 return 0; \
1611 } \
1612 return -1; \
1613} \
1614PyGetSetDef name##Complex{(char*)#name, (getter)name##ComplexGet, (setter)name##ComplexSet, nullptr, nullptr};
1615
1618
1621 if (!real) return nullptr;
1622 double r = PyFloat_AsDouble(real);
1623 Py_DECREF(real);
1624 if (r == -1. && PyErr_Occurred())
1625 return nullptr;
1626
1628 if (!imag) return nullptr;
1629 double i = PyFloat_AsDouble(imag);
1630 Py_DECREF(imag);
1631 if (i == -1. && PyErr_Occurred())
1632 return nullptr;
1633
1634 return PyComplex_FromDoubles(r, i);
1635}
1636
1639 if (!real) return nullptr;
1640 double r = PyFloat_AsDouble(real);
1641 Py_DECREF(real);
1642 if (r == -1. && PyErr_Occurred())
1643 return nullptr;
1644
1646 if (!imag) return nullptr;
1647 double i = PyFloat_AsDouble(imag);
1648 Py_DECREF(imag);
1649 if (i == -1. && PyErr_Occurred())
1650 return nullptr;
1651
1652 std::ostringstream s;
1653 s << '(' << r << '+' << i << "j)";
1654 return CPyCppyy_PyText_FromString(s.str().c_str());
1655}
1656
1658{
1659 return PyFloat_FromDouble(((std::complex<double>*)self->GetObject())->real());
1660}
1661
1662static int ComplexDRealSet(CPPInstance* self, PyObject* value, void*)
1663{
1664 double d = PyFloat_AsDouble(value);
1665 if (d == -1.0 && PyErr_Occurred())
1666 return -1;
1667 ((std::complex<double>*)self->GetObject())->real(d);
1668 return 0;
1669}
1670
1671PyGetSetDef ComplexDReal{(char*)"real", (getter)ComplexDRealGet, (setter)ComplexDRealSet, nullptr, nullptr};
1672
1673
1675{
1676 return PyFloat_FromDouble(((std::complex<double>*)self->GetObject())->imag());
1677}
1678
1679static int ComplexDImagSet(CPPInstance* self, PyObject* value, void*)
1680{
1681 double d = PyFloat_AsDouble(value);
1682 if (d == -1.0 && PyErr_Occurred())
1683 return -1;
1684 ((std::complex<double>*)self->GetObject())->imag(d);
1685 return 0;
1686}
1687
1688PyGetSetDef ComplexDImag{(char*)"imag", (getter)ComplexDImagGet, (setter)ComplexDImagSet, nullptr, nullptr};
1689
1691{
1692 double r = ((std::complex<double>*)self->GetObject())->real();
1693 double i = ((std::complex<double>*)self->GetObject())->imag();
1694 return PyComplex_FromDoubles(r, i);
1695}
1696
1697
1698} // unnamed namespace
1699
1700
1701//- public functions ---------------------------------------------------------
1702namespace CPyCppyy {
1703 std::set<std::string> gIteratorTypes;
1704}
1705
1706static inline
1707bool run_pythonizors(PyObject* pyclass, PyObject* pyname, const std::vector<PyObject*>& v)
1708{
1709 PyObject* args = PyTuple_New(2);
1712
1713 bool pstatus = true;
1714 for (auto pythonizor : v) {
1716 if (!result) {
1717 pstatus = false; // TODO: detail the error handling
1718 break;
1719 }
1721 }
1722 Py_DECREF(args);
1723
1724 return pstatus;
1725}
1726
1727bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name)
1728{
1729// Add pre-defined pythonizations (for STL and ROOT) to classes based on their
1730// signature and/or class name.
1731 if (!pyclass)
1732 return false;
1733
1735
1736//- method name based pythonization ------------------------------------------
1737
1738// for smart pointer style classes that are otherwise not known as such; would
1739// prefer operator-> as that returns a pointer (which is simpler since it never
1740// has to deal with ref-assignment), but operator* plays better with STL iters
1741// and algorithms
1746
1747// for pre-check of nullptr for boolean types
1749#if PY_VERSION_HEX >= 0x03000000
1750 const char* pybool_name = "__bool__";
1751#else
1752 const char* pybool_name = "__nonzero__";
1753#endif
1755 }
1756
1757// for STL containers, and user classes modeled after them
1758// the attribute must be a CPyCppyy overload, otherwise the check gives false
1759// positives in the case where the class has a non-function attribute that is
1760// called "size".
1761 if (HasAttrDirect(pyclass, PyStrings::gSize, /*mustBeCPyCppyy=*/ true)) {
1762 Utility::AddToClass(pyclass, "__len__", "size");
1763 }
1764
1765 if (!IsTemplatedSTLClass(name, "vector") && // vector is dealt with below
1768 // obtain the name of the return type
1769 const auto& v = Cppyy::GetMethodIndicesFromName(klass->fCppType, "begin");
1770 if (!v.empty()) {
1771 // check return type; if not explicitly an iterator, add it to the "known" return
1772 // types to add the "next" method on use
1774 const std::string& resname = Cppyy::GetMethodResultType(meth);
1775 bool isIterator = gIteratorTypes.find(resname) != gIteratorTypes.end();
1777 if (resname.find("iterator") == std::string::npos)
1778 gIteratorTypes.insert(resname);
1779 isIterator = true;
1780 }
1781
1782 if (isIterator) {
1783 // install iterator protocol a la STL
1786 } else {
1787 // still okay if this is some pointer type of builtin persuasion (general class
1788 // won't work: the return type needs to understand the iterator protocol)
1789 std::string resolved = Cppyy::ResolveName(resname);
1790 if (resolved.back() == '*' && Cppyy::IsBuiltin(resolved.substr(0, resolved.size()-1))) {
1793 }
1794 }
1795 }
1796 }
1797 if (!((PyTypeObject*)pyclass)->tp_iter && // no iterator resolved
1799 // Python will iterate over __getitem__ using integers, but C++ operator[] will never raise
1800 // a StopIteration. A checked getitem (raising IndexError if beyond size()) works in some
1801 // cases but would mess up if operator[] is meant to implement an associative container. So,
1802 // this has to be implemented as an iterator protocol.
1805 }
1806 }
1807
1808// operator==/!= are used in op_richcompare of CPPInstance, which subsequently allows
1809// comparisons to None; if no operator is available, a hook is installed for lazy
1810// lookups in the global and/or class namespace
1811 if (HasAttrDirect(pyclass, PyStrings::gEq, true) && \
1812 Cppyy::GetMethodIndicesFromName(klass->fCppType, "__eq__").empty()) {
1814 if (!klass->fOperators) klass->fOperators = new Utility::PyOperators();
1815 klass->fOperators->fEq = cppol;
1816 // re-insert the forwarding __eq__ from the CPPInstance in case there was a Python-side
1817 // override in the base class
1818 static PyObject* top_eq = nullptr;
1819 if (!top_eq) {
1822 Py_DECREF(top_eq); // make it borrowed
1824 }
1826 }
1827
1828 if (HasAttrDirect(pyclass, PyStrings::gNe, true) && \
1829 Cppyy::GetMethodIndicesFromName(klass->fCppType, "__ne__").empty()) {
1831 if (!klass->fOperators) klass->fOperators = new Utility::PyOperators();
1832 klass->fOperators->fNe = cppol;
1833 // re-insert the forwarding __ne__ (same reason as above for __eq__)
1834 static PyObject* top_ne = nullptr;
1835 if (!top_ne) {
1838 Py_DECREF(top_ne); // make it borrowed
1840 }
1842 }
1843
1844#if 0
1846 // guarantee that the result of __repr__ is a Python string
1847 Utility::AddToClass(pyclass, "__cpp_repr", "__repr__");
1849 }
1850
1852 // guarantee that the result of __str__ is a Python string
1853 Utility::AddToClass(pyclass, "__cpp_str", "__str__");
1855 }
1856#endif
1857
1858 if (Cppyy::IsAggregate(((CPPClass*)pyclass)->fCppType) && name.compare(0, 5, "std::", 5) != 0 &&
1859 name.compare(0, 6, "tuple<", 6) != 0) {
1860 // create a pseudo-constructor to allow initializer-style object creation
1861 Cppyy::TCppType_t kls = ((CPPClass*)pyclass)->fCppType;
1863 if (ndata) {
1864 std::string rname = name;
1866
1867 std::ostringstream initdef;
1868 initdef << "namespace __cppyy_internal {\n"
1869 << "void init_" << rname << "(" << name << "*& self";
1870 bool codegen_ok = true;
1871 std::vector<std::string> arg_types, arg_names, arg_defaults;
1872 arg_types.reserve(ndata); arg_names.reserve(ndata); arg_defaults.reserve(ndata);
1873 for (Cppyy::TCppIndex_t i = 0; i < ndata; ++i) {
1875 continue;
1876
1877 const std::string& txt = Cppyy::GetDatamemberType(kls, i);
1878 const std::string& res = Cppyy::IsEnum(txt) ? txt : Cppyy::ResolveName(txt);
1879 const std::string& cpd = TypeManip::compound(res);
1880 std::string res_clean = TypeManip::clean_type(res, false, true);
1881
1882 if (res_clean == "internal_enum_type_t")
1883 res_clean = txt; // restore (properly scoped name)
1884
1885 if (res.rfind(']') == std::string::npos && res.rfind(')') == std::string::npos) {
1886 if (!cpd.empty()) arg_types.push_back(res_clean+cpd);
1887 else arg_types.push_back("const "+res_clean+"&");
1888 arg_names.push_back(Cppyy::GetDatamemberName(kls, i));
1889 if ((!cpd.empty() && cpd.back() == '*') || Cppyy::IsBuiltin(res_clean))
1890 arg_defaults.push_back("0");
1891 else {
1894 }
1895 } else {
1896 codegen_ok = false; // TODO: how to support arrays, anonymous enums, etc?
1897 break;
1898 }
1899 }
1900
1901 if (codegen_ok && !arg_types.empty()) {
1902 bool defaults_ok = arg_defaults.size() == arg_types.size();
1903 for (std::vector<std::string>::size_type i = 0; i < arg_types.size(); ++i) {
1904 initdef << ", " << arg_types[i] << " " << arg_names[i];
1905 if (defaults_ok) initdef << " = " << arg_defaults[i];
1906 }
1907 initdef << ") {\n self = new " << name << "{";
1908 for (std::vector<std::string>::size_type i = 0; i < arg_names.size(); ++i) {
1909 if (i != 0) initdef << ", ";
1910 initdef << arg_names[i];
1911 }
1912 initdef << "};\n} }";
1913
1914 if (Cppyy::Compile(initdef.str(), true /* silent */)) {
1915 Cppyy::TCppScope_t cis = Cppyy::GetScope("__cppyy_internal");
1916 const auto& mix = Cppyy::GetMethodIndicesFromName(cis, "init_"+rname);
1917 if (mix.size()) {
1918 if (!Utility::AddToClass(pyclass, "__init__",
1919 new CPPFunction(cis, Cppyy::GetMethod(cis, mix[0]))))
1920 PyErr_Clear();
1921 }
1922 }
1923 }
1924 }
1925 }
1926
1927
1928//- class name based pythonization -------------------------------------------
1929
1930 if (IsTemplatedSTLClass(name, "span")) {
1931 // libstdc++ (GCC >= 15) implements std::span::iterator using a private
1932 // nested tag type, which makes the iterator non-instantiable by
1933 // CallFunc-generated wrappers (the return type cannot be named without
1934 // violating access rules).
1935 //
1936 // To preserve correct Python iteration semantics, we replace begin()/end()
1937 // for std::span to return a custom pointer-based iterator instead. This
1938 // avoids relying on std::span::iterator while still providing a real C++
1939 // iterator object that CPyCppyy can also wrap and expose via
1940 // __iter__/__next__.
1943 }
1944
1945 if (IsTemplatedSTLClass(name, "vector")) {
1946
1947 // std::vector<bool> is a special case in C++
1949 if (klass->fCppType == sVectorBoolTypeID) {
1952 } else {
1953 // constructor that takes python collections
1954 Utility::AddToClass(pyclass, "__real_init", "__init__");
1956
1957 // data with size
1958 Utility::AddToClass(pyclass, "__real_data", "data");
1960
1961 // The addition of the __array__ utility to std::vector Python proxies causes a
1962 // bug where the resulting array is a single dimension, causing loss of data when
1963 // converting to numpy arrays, for >1dim vectors. Since this C++ pythonization
1964 // was added with the upgrade in 6.32, and is only defined and used recursively,
1965 // the safe option is to disable this function and no longer add it.
1966#if 0
1967 // numpy array conversion
1969#endif
1970
1971 // checked getitem
1973 Utility::AddToClass(pyclass, "_getitem__unchecked", "__getitem__");
1975 }
1976
1977 // vector-optimized iterator protocol
1979
1980 // optimized __iadd__
1982
1983 // helpers for iteration
1984 const std::string& vtype = Cppyy::ResolveName(name+"::value_type");
1985 if (vtype.rfind("value_type") == std::string::npos) { // actually resolved?
1989 }
1990
1991 size_t typesz = Cppyy::SizeOf(name+"::value_type");
1992 if (typesz) {
1996 }
1997 }
1998 }
1999
2000 else if (IsTemplatedSTLClass(name, "array")) {
2001 // constructor that takes python associative collections
2002 Utility::AddToClass(pyclass, "__real_init", "__init__");
2004 }
2005
2006 else if (IsTemplatedSTLClass(name, "map") || IsTemplatedSTLClass(name, "unordered_map")) {
2007 // constructor that takes python associative collections
2008 Utility::AddToClass(pyclass, "__real_init", "__init__");
2010
2012 }
2013
2014 else if (IsTemplatedSTLClass(name, "set")) {
2015 // constructor that takes python associative collections
2016 Utility::AddToClass(pyclass, "__real_init", "__init__");
2018
2020 }
2021
2022 else if (IsTemplatedSTLClass(name, "pair")) {
2025 }
2026
2027 if (IsTemplatedSTLClass(name, "shared_ptr") || IsTemplatedSTLClass(name, "unique_ptr")) {
2028 Utility::AddToClass(pyclass, "__real_init", "__init__");
2030 }
2031
2032 else if (!((PyTypeObject*)pyclass)->tp_iter && \
2033 (name.find("iterator") != std::string::npos || gIteratorTypes.find(name) != gIteratorTypes.end())) {
2034 ((PyTypeObject*)pyclass)->tp_iternext = (iternextfunc)STLIterNext;
2038 }
2039
2040 else if (name == "string" || name == "std::string") { // TODO: ask backend as well
2049 Utility::AddToClass(pyclass, "__cpp_find", "find");
2051 Utility::AddToClass(pyclass, "__cpp_rfind", "rfind");
2053 Utility::AddToClass(pyclass, "__cpp_replace", "replace");
2056
2057 // to allow use of std::string in dictionaries and findable with str
2059 }
2060
2061 else if (name == "basic_string_view<char,char_traits<char> >" || name == "std::basic_string_view<char>") {
2062 Utility::AddToClass(pyclass, "__real_init", "__init__");
2070 }
2071
2072// The first condition was already present in upstream CPyCppyy. The other two
2073// are special to ROOT, because its reflection layer gives us the types without
2074// the "std::" namespace. On some platforms, that applies only to the template
2075// arguments, and on others also to the "basic_string".
2076 else if (name == "std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> >"
2077 || name == "basic_string<wchar_t,char_traits<wchar_t>,allocator<wchar_t> >"
2078 || name == "std::basic_string<wchar_t,char_traits<wchar_t>,allocator<wchar_t> >"
2079 ) {
2086 }
2087
2088 else if (name == "complex<double>" || name == "std::complex<double>") {
2089 Utility::AddToClass(pyclass, "__cpp_real", "real");
2091 Utility::AddToClass(pyclass, "__cpp_imag", "imag");
2095 }
2096
2097 else if (IsTemplatedSTLClass(name, "complex")) {
2098 Utility::AddToClass(pyclass, "__cpp_real", "real");
2100 Utility::AddToClass(pyclass, "__cpp_imag", "imag");
2104 }
2105
2106// direct user access; there are two calls here:
2107// - explicit pythonization: won't fall through to the base classes and is preferred if present
2108// - normal pythonization: only called if explicit isn't present, falls through to base classes
2109 bool bUserOk = true; PyObject* res = nullptr;
2113 bUserOk = (bool)res;
2114 } else {
2116 if (func) {
2117 res = PyObject_CallFunctionObjArgs(func, pyclass, pyname, nullptr);
2118 Py_DECREF(func);
2119 bUserOk = (bool)res;
2120 } else
2121 PyErr_Clear();
2122 }
2123 if (!bUserOk) {
2125 return false;
2126 } else {
2127 Py_XDECREF(res);
2128 // pyname handed to args tuple below
2129 }
2130
2131// call registered pythonizors, if any: first run the namespace-specific pythonizors, then
2132// the global ones (the idea is to allow writing a pythonizor that see all classes)
2133 bool pstatus = true;
2135 auto &pyzMap = pythonizations();
2136 if (!outer_scope.empty()) {
2137 auto p = pyzMap.find(outer_scope);
2138 if (p != pyzMap.end()) {
2140 name.substr(outer_scope.size()+2, std::string::npos).c_str());
2143 }
2144 }
2145
2146 if (pstatus) {
2147 auto p = pyzMap.find("");
2148 if (p != pyzMap.end())
2149 pstatus = run_pythonizors(pyclass, pyname, p->second);
2150 }
2151
2153
2154// phew! all done ...
2155 return pstatus;
2156}
#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
size_t TCppIndex_t
Definition cpp_cppyy.h:24
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)