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) 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{
154 PyErr_SetString(PyExc_TypeError, "getattr(): attribute name must be string");
155
157 if (!pyptr)
158 return nullptr;
159
160// prevent a potential infinite loop
161 if (Py_TYPE(pyptr) == Py_TYPE(self)) {
164 PyErr_Format(PyExc_AttributeError, "%s object has no attribute \'%s\'",
168
170 return nullptr;
171 }
172
175 return result;
176}
177
179{
180// Follow operator*() if present (available in python as __deref__), so that
181// smart pointers behave as expected.
183 // TODO: these calls come from TemplateProxy and are unlikely to be needed in practice,
184 // whereas as-is, they can accidentally dereference the result of end() on some STL
185 // containers. Obviously, this is a dumb hack that should be resolved more fundamentally.
187 return nullptr;
188 }
189
190
192}
193
194//-----------------------------------------------------------------------------
196{
197// Follow operator->() if present (available in python as __follow__), so that
198// smart pointers behave as expected.
200}
201
202//- pointer checking bool converter -------------------------------------------
204{
205 if (!CPPInstance_Check(self)) {
206 PyErr_SetString(PyExc_TypeError, "C++ object proxy expected");
207 return nullptr;
208 }
209
210 if (!((CPPInstance*)self)->GetObject())
212
214}
215
216//- vector behavior as primitives ----------------------------------------------
217#if PY_VERSION_HEX < 0x03040000
218#define PyObject_LengthHint _PyObject_LengthHint
219#endif
220
221// TODO: can probably use the below getters in the InitializerListConverter
222struct ItemGetter {
223 ItemGetter(PyObject* pyobj) : fPyObject(pyobj) { Py_INCREF(fPyObject); }
224 virtual ~ItemGetter() { Py_DECREF(fPyObject); }
225 virtual Py_ssize_t size() = 0;
226 virtual PyObject* get() = 0;
227 PyObject* fPyObject;
228};
229
230struct CountedItemGetter : public ItemGetter {
231 CountedItemGetter(PyObject* pyobj) : ItemGetter(pyobj), fCur(0) {}
232 Py_ssize_t fCur;
233};
234
235struct TupleItemGetter : public CountedItemGetter {
236 using CountedItemGetter::CountedItemGetter;
237 Py_ssize_t size() override { return PyTuple_GET_SIZE(fPyObject); }
238 PyObject* get() override {
239 if (fCur < PyTuple_GET_SIZE(fPyObject)) {
240 PyObject* item = PyTuple_GET_ITEM(fPyObject, fCur++);
242 return item;
243 }
244 PyErr_SetString(PyExc_StopIteration, "end of tuple");
245 return nullptr;
246 }
247};
248
249struct ListItemGetter : public CountedItemGetter {
250 using CountedItemGetter::CountedItemGetter;
251 Py_ssize_t size() override { return PyList_GET_SIZE(fPyObject); }
252 PyObject* get() override {
253 if (fCur < PyList_GET_SIZE(fPyObject)) {
254 PyObject* item = PyList_GET_ITEM(fPyObject, fCur++);
256 return item;
257 }
258 PyErr_SetString(PyExc_StopIteration, "end of list");
259 return nullptr;
260 }
261};
262
263struct SequenceItemGetter : public CountedItemGetter {
264 using CountedItemGetter::CountedItemGetter;
265 Py_ssize_t size() override {
266 Py_ssize_t sz = PySequence_Size(fPyObject);
267 if (sz < 0) {
268 PyErr_Clear();
269 return PyObject_LengthHint(fPyObject, 8);
270 }
271 return sz;
272 }
273 PyObject* get() override { return PySequence_GetItem(fPyObject, fCur++); }
274};
275
276struct IterItemGetter : public ItemGetter {
277 using ItemGetter::ItemGetter;
278 Py_ssize_t size() override { return PyObject_LengthHint(fPyObject, 8); }
279 PyObject* get() override { return (*(Py_TYPE(fPyObject)->tp_iternext))(fPyObject); }
280};
281
282static ItemGetter* GetGetter(PyObject* args)
283{
284// Create an ItemGetter to loop over the iterable argument, if any.
285 ItemGetter* getter = nullptr;
286
287 if (PyTuple_GET_SIZE(args) == 1) {
288 PyObject* fi = PyTuple_GET_ITEM(args, 0);
290 return nullptr; // do not accept string to fill std::vector<char>
291
292 // TODO: this only tests for new-style buffers, which is too strict, but a
293 // generic check for Py_TYPE(fi)->tp_as_buffer is too loose (note that the
294 // main use case is numpy, which offers the new interface)
296 return nullptr;
297
299 getter = new TupleItemGetter(fi);
300 else if (PyList_CheckExact(fi))
301 getter = new ListItemGetter(fi);
302 else if (PySequence_Check(fi))
303 getter = new SequenceItemGetter(fi);
304 else {
306 if (iter) {
307 getter = new IterItemGetter{iter};
308 Py_DECREF(iter);
309 }
310 else PyErr_Clear();
311 }
312 }
313
314 return getter;
315}
316
317namespace {
318
320{
321 static bool compiled = false;
322
323 if (compiled)
324 return;
325
326 compiled = true;
327
328 auto code = R"(
329namespace __cppyy_internal {
330
331template <class T>
332struct ptr_iterator {
333 T *cur;
334 T *end;
335
336 ptr_iterator(T *c, T *e) : cur(c), end(e) {}
337
338 T &operator*() const { return *cur; }
339 ptr_iterator &operator++()
340 {
341 ++cur;
342 return *this;
343 }
344 bool operator==(const ptr_iterator &other) const { return cur == other.cur; }
345 bool operator!=(const ptr_iterator &other) const { return !(*this == other); }
346};
347
348template <class T>
349ptr_iterator<T> make_iter(T *begin, T *end)
350{
351 return {begin, end};
352}
353
354} // namespace __cppyy_internal
355
356// Note: for const span<T>, T is const-qualified here
357template <class T>
358auto __cppyy_internal_begin(T &s) noexcept
359{
360 return __cppyy_internal::make_iter(s.data(), s.data() + s.size());
361}
362
363// Note: for const span<T>, T is const-qualified here
364template <class T>
365auto __cppyy_internal_end(T &s) noexcept
366{
367 // end iterator = begin iterator with cur == end
368 return __cppyy_internal::make_iter(s.data() + s.size(), s.data() + s.size());
369}
370 )";
371 Cppyy::Compile(code, /*silent*/ true);
372}
373
375{
376 static PyObject *pyFunc = nullptr;
377 if (!pyFunc) {
380 pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_begin");
381 if (!pyFunc) {
382 PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper "
383 "'__cppyy_internal_begin' for std::span pythonization");
384 }
385 }
386 return pyFunc;
387}
388
390{
391 static PyObject *pyFunc = nullptr;
392 if (!pyFunc) {
395 pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_end");
396 if (!pyFunc) {
397 PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper "
398 "'__cppyy_internal_end' for std::span pythonization");
399 }
400 }
401 return pyFunc;
402}
403
404} // namespace
405
407{
408 auto begin = spanBegin();
409 if (!begin)
410 return nullptr;
411 return PyObject_CallOneArg(begin, self);
412}
413
415{
416 auto end = spanEnd();
417 if (!end)
418 return nullptr;
419 return PyObject_CallOneArg(end, self);
420}
421
422static bool FillVector(PyObject* vecin, PyObject* args, ItemGetter* getter)
423{
424 Py_ssize_t sz = getter->size();
425 if (sz < 0)
426 return false;
427
428// reserve memory as applicable
429 if (0 < sz) {
430 PyObject* res = PyObject_CallMethod(vecin, (char*)"reserve", (char*)"n", sz);
431 Py_DECREF(res);
432 } else // i.e. sz == 0, so empty container: done
433 return true;
434
435 bool fill_ok = true;
436
437// two main options: a list of lists (or tuples), or a list of objects; the former
438// are emplace_back'ed, the latter push_back'ed
440 if (!fi) PyErr_Clear();
442 // use emplace_back to construct the vector entries one by one
443 PyObject* eb_call = PyObject_GetAttrString(vecin, (char*)"emplace_back");
445 bool value_is_vector = false;
447 // if the value_type is a vector, then allow for initialization from sequences
448 if (std::string(CPyCppyy_PyText_AsString(vtype)).rfind("std::vector", 0) != std::string::npos)
449 value_is_vector = true;
450 } else
451 PyErr_Clear();
453
454 if (eb_call) {
456 for (int i = 0; /* until break */; ++i) {
457 PyObject* item = getter->get();
458 if (item) {
460 eb_args = PyTuple_New(1);
462 } else if (PyTuple_CheckExact(item)) {
463 eb_args = item;
464 } else if (PyList_CheckExact(item)) {
467 for (Py_ssize_t j = 0; j < isz; ++j) {
471 }
473 } else {
475 PyErr_Format(PyExc_TypeError, "argument %d is not a tuple or list", i);
476 fill_ok = false;
477 break;
478 }
481 if (!ebres) {
482 fill_ok = false;
483 break;
484 }
486 } else {
487 if (PyErr_Occurred()) {
490 fill_ok = false;
491 else { PyErr_Clear(); }
492 }
493 break;
494 }
495 }
497 }
498 } else {
499 // use push_back to add the vector entries one by one
500 PyObject* pb_call = PyObject_GetAttrString(vecin, (char*)"push_back");
501 if (pb_call) {
502 for (;;) {
503 PyObject* item = getter->get();
504 if (item) {
507 if (!pbres) {
508 fill_ok = false;
509 break;
510 }
512 } else {
513 if (PyErr_Occurred()) {
516 fill_ok = false;
517 else { PyErr_Clear(); }
518 }
519 break;
520 }
521 }
523 }
524 }
525 Py_XDECREF(fi);
526
527 return fill_ok;
528}
529
530PyObject* VectorIAdd(PyObject* self, PyObject* args, PyObject* /* kwds */)
531{
532// Implement fast __iadd__ on std::vector (generic __iadd__ is in Python)
533 ItemGetter* getter = GetGetter(args);
534
535 if (getter) {
536 bool fill_ok = FillVector(self, args, getter);
537 delete getter;
538
539 if (!fill_ok)
540 return nullptr;
541
543 return self;
544 }
545
546// if no getter, it could still be b/c we have a buffer (e.g. numpy); looping over
547// a buffer here is slow, so use insert() instead
548 if (PyTuple_GET_SIZE(args) == 1) {
549 PyObject* fi = PyTuple_GET_ITEM(args, 0);
552 if (vend) {
553 // when __iadd__ is overriden, the operation does not end with
554 // calling the __iadd__ method, but also assigns the result to the
555 // lhs of the iadd. For example, performing vec += arr, Python
556 // first calls our override, and then does vec = vec.iadd(arr).
559
560 if (!it)
561 return nullptr;
562
563 Py_DECREF(it);
564 // Assign the result of the __iadd__ override to the std::vector
566 return self;
567 }
568 }
569 }
570
571 if (!PyErr_Occurred())
572 PyErr_SetString(PyExc_TypeError, "argument is not iterable");
573 return nullptr; // error already set
574}
575
576
577PyObject* VectorInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
578{
579// Specialized vector constructor to allow construction from containers; allowing
580// such construction from initializer_list instead would possible, but can be
581// error-prone. This use case is common enough for std::vector to implement it
582// directly, except for arrays (which can be passed wholesale) and strings (which
583// won't convert properly as they'll be seen as buffers)
584
585 ItemGetter* getter = GetGetter(args);
586
587 if (getter) {
588 // construct an empty vector, then back-fill it
590 if (!result) {
591 delete getter;
592 return nullptr;
593 }
594
595 bool fill_ok = FillVector(self, args, getter);
596 delete getter;
597
598 if (!fill_ok) {
600 return nullptr;
601 }
602
603 return result;
604 }
605
606// The given argument wasn't iterable: simply forward to regular constructor
608 if (realInit) {
609 PyObject* result = PyObject_Call(realInit, args, nullptr);
611 return result;
612 }
613
614 return nullptr;
615}
616
617//---------------------------------------------------------------------------
619{
620 PyObject* pydata = CallPyObjMethod(self, "__real_data");
622 return pydata;
623
625 if (!pylen) {
626 PyErr_Clear();
627 return pydata;
628 }
629
630 long clen = PyInt_AsLong(pylen);
632
634 ((CPPInstance*)pydata)->CastToArray(clen);
635 return pydata;
636 }
637
638 ((LowLevelView*)pydata)->resize((size_t)clen);
639 return pydata;
640}
641
642
643// This function implements __array__, added to std::vector python proxies and causes
644// a bug (see explanation at Utility::AddToClass(pyclass, "__array__"...) in CPyCppyy::Pythonize)
645// The recursive nature of this function, passes each subarray (pydata) to the next call and only
646// the final buffer is cast to a lowlevel view and resized (in VectorData), resulting in only the
647// first 1D array to be returned. See https://github.com/root-project/root/issues/17729
648// It is temporarily removed to prevent errors due to -Wunused-function, since it is no longer added.
649#if 0
650//---------------------------------------------------------------------------
652{
653 PyObject* pydata = VectorData(self, nullptr);
658 return newarr;
659}
660#endif
661
662//-----------------------------------------------------------------------------
663static PyObject* vector_iter(PyObject* v) {
665 if (!vi) return nullptr;
666
667 vi->ii_container = v;
668
669// tell the iterator code to set a life line if this container is a temporary
670 vi->vi_flags = vectoriterobject::kDefault;
671#if PY_VERSION_HEX >= 0x030e0000
673#else
674 if (Py_REFCNT(v) <= 1 || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue))
675#endif
677
678 Py_INCREF(v);
679
681 if (pyvalue_type) {
683 if (pyvalue_size) {
684 vi->vi_stride = PyLong_AsLong(pyvalue_size);
686 } else {
687 PyErr_Clear();
688 vi->vi_stride = 0;
689 }
690
692 std::string value_type = CPyCppyy_PyText_AsString(pyvalue_type);
693 value_type = Cppyy::ResolveName(value_type);
694 vi->vi_klass = Cppyy::GetScope(value_type);
695 if (!vi->vi_klass) {
696 // look for a special case of pointer to a class type (which is a builtin, but it
697 // is more useful to treat it polymorphically by allowing auto-downcasts)
698 const std::string& clean_type = TypeManip::clean_type(value_type, false, false);
700 if (c && TypeManip::compound(value_type) == "*") {
701 vi->vi_klass = c;
703 }
704 }
705 if (vi->vi_klass) {
706 vi->vi_converter = nullptr;
707 if (!vi->vi_flags) {
708 if (value_type.back() != '*') // meaning, object stored by-value
710 }
711 } else
712 vi->vi_converter = CPyCppyy::CreateConverter(value_type);
713 if (!vi->vi_stride) vi->vi_stride = Cppyy::SizeOf(value_type);
714
715 } else if (CPPScope_Check(pyvalue_type)) {
716 vi->vi_klass = ((CPPClass*)pyvalue_type)->fCppType;
717 vi->vi_converter = nullptr;
718 if (!vi->vi_stride) vi->vi_stride = Cppyy::SizeOf(vi->vi_klass);
719 if (!vi->vi_flags) vi->vi_flags = vectoriterobject::kNeedLifeLine;
720 }
721
722 PyObject* pydata = CallPyObjMethod(v, "__real_data");
723 if (!pydata || Utility::GetBuffer(pydata, '*', 1, vi->vi_data, false) == 0)
724 vi->vi_data = CPPInstance_Check(pydata) ? ((CPPInstance*)pydata)->GetObjectRaw() : nullptr;
726
727 } else {
728 PyErr_Clear();
729 vi->vi_data = nullptr;
730 vi->vi_stride = 0;
731 vi->vi_converter = nullptr;
732 vi->vi_klass = 0;
733 vi->vi_flags = 0;
734 }
735
737
738 vi->ii_pos = 0;
739 vi->ii_len = PySequence_Size(v);
740
742 return (PyObject*)vi;
743}
744
746{
747// Implement python's __getitem__ for std::vector<>s.
748 if (PySlice_Check(index)) {
749 if (!self->GetObject()) {
750 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
751 return nullptr;
752 }
753
756
757 Py_ssize_t start, stop, step;
759
761 if (!AdjustSlice(nlen, start, stop, step))
762 return nseq;
763
764 const Py_ssize_t sign = step < 0 ? -1 : 1;
765 for (Py_ssize_t i = start; i*sign < stop*sign; i += step) {
768 CallPyObjMethod(nseq, "push_back", item);
771 }
772
773 return nseq;
774 }
775
777}
778
779
781
783{
784// std::vector<bool> is a special-case in C++, and its return type depends on
785// the compiler: treat it special here as well
786 if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) {
788 "require object of type std::vector<bool>, but %s given",
789 Cppyy::GetScopedFinalName(self->ObjectIsA()).c_str());
790 return nullptr;
791 }
792
793 if (!self->GetObject()) {
794 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
795 return nullptr;
796 }
797
798 if (PySlice_Check(idx)) {
801
802 Py_ssize_t start, stop, step;
805 if (!AdjustSlice(nlen, start, stop, step))
806 return nseq;
807
808 const Py_ssize_t sign = step < 0 ? -1 : 1;
809 for (Py_ssize_t i = start; i*sign < stop*sign; i += step) {
812 CallPyObjMethod(nseq, "push_back", item);
815 }
816
817 return nseq;
818 }
819
821 if (!pyindex)
822 return nullptr;
823
826
827// get hold of the actual std::vector<bool> (no cast, as vector is never a base)
828 std::vector<bool>* vb = (std::vector<bool>*)self->GetObject();
829
830// finally, return the value
831 if (bool((*vb)[index]))
834}
835
837{
838// std::vector<bool> is a special-case in C++, and its return type depends on
839// the compiler: treat it special here as well
840 if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) {
842 "require object of type std::vector<bool>, but %s given",
843 Cppyy::GetScopedFinalName(self->ObjectIsA()).c_str());
844 return nullptr;
845 }
846
847 if (!self->GetObject()) {
848 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
849 return nullptr;
850 }
851
852 int bval = 0; PyObject* idx = nullptr;
853 if (!PyArg_ParseTuple(args, const_cast<char*>("Oi:__setitem__"), &idx, &bval))
854 return nullptr;
855
857 if (!pyindex)
858 return nullptr;
859
862
863// get hold of the actual std::vector<bool> (no cast, as vector is never a base)
864 std::vector<bool>* vb = (std::vector<bool>*)self->GetObject();
865
866// finally, set the value
867 (*vb)[index] = (bool)bval;
868
870}
871
872
873//- array behavior as primitives ----------------------------------------------
874PyObject* ArrayInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
875{
876// std::array is normally only constructed using aggregate initialization, which
877// is a concept that does not exist in python, so use this custom constructor to
878// to fill the array using setitem
879
880 if (args && PyTuple_GET_SIZE(args) == 1 && PySequence_Check(PyTuple_GET_ITEM(args, 0))) {
881 // construct the empty array, then fill it
883 if (!result)
884 return nullptr;
885
886 PyObject* items = PyTuple_GET_ITEM(args, 0);
888 if (PySequence_Size(self) != fillsz) {
889 PyErr_Format(PyExc_ValueError, "received sequence of size %zd where %zd expected",
892 return nullptr;
893 }
894
896 for (Py_ssize_t i = 0; i < fillsz; ++i) {
902 if (!sires) {
905 return nullptr;
906 } else
908 }
910
911 return result;
912 } else
913 PyErr_Clear();
914
915// The given argument wasn't iterable: simply forward to regular constructor
917 if (realInit) {
918 PyObject* result = PyObject_Call(realInit, args, nullptr);
920 return result;
921 }
922
923 return nullptr;
924}
925
926
927//- map behavior as primitives ------------------------------------------------
929{
930// construct an empty map, then fill it with the key, value pairs
932 if (!result)
933 return nullptr;
934
936 for (Py_ssize_t i = 0; i < PySequence_Size(pairs); ++i) {
938 PyObject* sires = nullptr;
939 if (pair && PySequence_Check(pair) && PySequence_Size(pair) == 2) {
940 PyObject* key = PySequence_GetItem(pair, 0);
944 Py_DECREF(key);
945 }
946 Py_DECREF(pair);
947 if (!sires) {
950 if (!PyErr_Occurred())
951 PyErr_SetString(PyExc_TypeError, "Failed to fill map (argument not a dict or sequence of pairs)");
952 return nullptr;
953 } else
955 }
957
958 return result;
959}
960
961PyObject* MapInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
962{
963// Specialized map constructor to allow construction from mapping containers and
964// from tuples of pairs ("initializer_list style").
965
966// PyMapping_Check is not very discriminatory, as it basically only checks for the
967// existence of __getitem__, hence the most common cases of tuple and list are
968// dropped straight-of-the-bat (the PyMapping_Items call will fail on them).
969 if (PyTuple_GET_SIZE(args) == 1 && PyMapping_Check(PyTuple_GET_ITEM(args, 0)) && \
971 PyObject* assoc = PyTuple_GET_ITEM(args, 0);
972#if PY_VERSION_HEX < 0x03000000
973 // to prevent warning about literal string, expand macro
974 PyObject* items = PyObject_CallMethod(assoc, (char*)"items", nullptr);
975#else
976 // in p3, PyMapping_Items isn't a macro, but a function that short-circuits dict
978#endif
979 if (items && PySequence_Check(items)) {
982 return result;
983 }
984
986 PyErr_Clear();
987
988 // okay to fall through as long as 'self' has not been created (is done in MapFromPairs)
989 }
990
991// tuple of pairs case (some mapping types are sequences)
992 if (PyTuple_GET_SIZE(args) == 1 && PySequence_Check(PyTuple_GET_ITEM(args, 0)))
993 return MapFromPairs(self, PyTuple_GET_ITEM(args, 0));
994
995// The given argument wasn't a mapping or tuple of pairs: forward to regular constructor
997 if (realInit) {
998 PyObject* result = PyObject_Call(realInit, args, nullptr);
1000 return result;
1001 }
1002
1003 return nullptr;
1004}
1005
1006#if __cplusplus <= 202002L
1008{
1009// Implement python's __contains__ for std::map/std::set
1010 PyObject* result = nullptr;
1011
1012 PyObject* iter = CallPyObjMethod(self, "find", obj);
1013 if (CPPInstance_Check(iter)) {
1015 if (CPPInstance_Check(end)) {
1016 if (!PyObject_RichCompareBool(iter, end, Py_EQ)) {
1018 result = Py_True;
1019 }
1020 }
1021 Py_XDECREF(end);
1022 }
1023 Py_XDECREF(iter);
1024
1025 if (!result) {
1026 PyErr_Clear(); // e.g. wrong argument type, which should always lead to False
1028 result = Py_False;
1029 }
1030
1031 return result;
1032}
1033#endif
1034
1035
1036//- set behavior as primitives ------------------------------------------------
1037PyObject* SetInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
1038{
1039// Specialized set constructor to allow construction from Python sets.
1040 if (PyTuple_GET_SIZE(args) == 1 && PySet_Check(PyTuple_GET_ITEM(args, 0))) {
1041 PyObject* pyset = PyTuple_GET_ITEM(args, 0);
1042
1043 // construct an empty set, then fill it
1045 if (!result)
1046 return nullptr;
1047
1049 if (iter) {
1050 PyObject* ins_call = PyObject_GetAttrString(self, (char*)"insert");
1051
1052 IterItemGetter getter{iter};
1053 Py_DECREF(iter);
1054
1055 PyObject* item = getter.get();
1056 while (item) {
1058 Py_DECREF(item);
1059 if (!insres) {
1062 return nullptr;
1063 } else
1065 item = getter.get();
1066 }
1068 }
1069
1070 return result;
1071 }
1072
1073// The given argument wasn't iterable: simply forward to regular constructor
1075 if (realInit) {
1076 PyObject* result = PyObject_Call(realInit, args, nullptr);
1078 return result;
1079 }
1080
1081 return nullptr;
1082}
1083
1084
1085//- STL container iterator support --------------------------------------------
1086static const ptrdiff_t PS_END_ADDR = 7; // non-aligned address, so no clash
1087static const ptrdiff_t PS_FLAG_ADDR = 11; // id.
1088static const ptrdiff_t PS_COLL_ADDR = 13; // id.
1089
1091{
1092// Implement python's __iter__ for low level views used through STL-type begin()/end()
1094
1095 if (LowLevelView_Check(iter)) {
1096 // builtin pointer iteration: can only succeed if a size is available
1098 if (sz == -1) {
1099 Py_DECREF(iter);
1100 return nullptr;
1101 }
1102 PyObject* lliter = Py_TYPE(iter)->tp_iter(iter);
1103 ((indexiterobject*)lliter)->ii_len = sz;
1104 Py_DECREF(iter);
1105 return lliter;
1106 }
1107
1108 if (iter) {
1109 Py_DECREF(iter);
1110 PyErr_SetString(PyExc_TypeError, "unrecognized iterator type for low level views");
1111 }
1112
1113 return nullptr;
1114}
1115
1117{
1118// Implement python's __iter__ for std::iterator<>s
1120 if (iter) {
1122 if (end) {
1123 if (CPPInstance_Check(iter)) {
1124 // use the data member cache to store extra state on the iterator object,
1125 // without it being visible on the Python side
1126 auto& dmc = ((CPPInstance*)iter)->GetDatamemberCache();
1127 dmc.push_back(std::make_pair(PS_END_ADDR, end));
1128
1129 // set a flag, indicating first iteration (reset in __next__)
1131 dmc.push_back(std::make_pair(PS_FLAG_ADDR, Py_False));
1132
1133 // make sure the iterated over collection remains alive for the duration
1134 Py_INCREF(self);
1135 dmc.push_back(std::make_pair(PS_COLL_ADDR, self));
1136 } else {
1137 // could store "end" on the object's dictionary anyway, but if end() returns
1138 // a user-customized object, then its __next__ is probably custom, too
1139 Py_DECREF(end);
1140 }
1141 }
1142 }
1143 return iter;
1144}
1145
1146//- generic iterator support over a sequence with operator[] and size ---------
1147//-----------------------------------------------------------------------------
1148static PyObject* index_iter(PyObject* c) {
1150 if (!ii) return nullptr;
1151
1152 Py_INCREF(c);
1153 ii->ii_container = c;
1154 ii->ii_pos = 0;
1155 ii->ii_len = PySequence_Size(c);
1156
1158 return (PyObject*)ii;
1159}
1160
1161
1162//- safe indexing for STL-like vector w/o iterator dictionaries ---------------
1163/* replaced by indexiterobject iteration, but may still have some future use ...
1164PyObject* CheckedGetItem(PyObject* self, PyObject* obj)
1165{
1166// Implement a generic python __getitem__ for STL-like classes that are missing the
1167// reflection info for their iterators. This is then used for iteration by means of
1168// consecutive indices, it such index is of integer type.
1169 Py_ssize_t size = PySequence_Size(self);
1170 Py_ssize_t idx = PyInt_AsSsize_t(obj);
1171 if ((size == (Py_ssize_t)-1 || idx == (Py_ssize_t)-1) && PyErr_Occurred()) {
1172 // argument conversion problem: let method itself resolve anew and report
1173 PyErr_Clear();
1174 return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj);
1175 }
1176
1177 bool inbounds = false;
1178 if (idx < 0) idx += size;
1179 if (0 <= idx && 0 <= size && idx < size)
1180 inbounds = true;
1181
1182 if (inbounds)
1183 return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj);
1184 else
1185 PyErr_SetString( PyExc_IndexError, "index out of range" );
1186
1187 return nullptr;
1188}*/
1189
1190
1191//- pair as sequence to allow tuple unpacking --------------------------------
1193{
1194// For std::map<> iteration, unpack std::pair<>s into tuples for the loop.
1195 long idx = PyLong_AsLong(pyindex);
1196 if (idx == -1 && PyErr_Occurred())
1197 return nullptr;
1198
1199 if (!CPPInstance_Check(self) || !((CPPInstance*)self)->GetObject()) {
1200 PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
1201 return nullptr;
1202 }
1203
1204 if ((int)idx == 0)
1206 else if ((int)idx == 1)
1208
1209// still here? Trigger stop iteration
1210 PyErr_SetString(PyExc_IndexError, "out of bounds");
1211 return nullptr;
1212}
1213
1214//- simplistic len() functions -----------------------------------------------
1216 return PyInt_FromLong(2);
1217}
1218
1219
1220//- shared/unique_ptr behavior -----------------------------------------------
1221PyObject* SmartPtrInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
1222{
1223// since the shared/unique pointer will take ownership, we need to relinquish it
1225 if (realInit) {
1226 PyObject* result = PyObject_Call(realInit, args, nullptr);
1228 if (result && PyTuple_GET_SIZE(args) == 1 && CPPInstance_Check(PyTuple_GET_ITEM(args, 0))) {
1230 if (!(cppinst->fFlags & CPPInstance::kIsSmartPtr)) cppinst->CppOwns();
1231 }
1232 return result;
1233 }
1234 return nullptr;
1235}
1236
1237
1238//- string behavior as primitives --------------------------------------------
1239#if PY_VERSION_HEX >= 0x03000000
1240// TODO: this is wrong, b/c it doesn't order
1243}
1244#endif
1245static inline
1246PyObject* CPyCppyy_PyString_FromCppString(std::string_view s, bool native=true) {
1247 if (native)
1248 return PyBytes_FromStringAndSize(s.data(), s.size());
1249 return CPyCppyy_PyText_FromStringAndSize(s.data(), s.size());
1250}
1251
1252static inline
1253PyObject* CPyCppyy_PyString_FromCppString(std::wstring_view s, bool native=true) {
1254 PyObject* pyobj = PyUnicode_FromWideChar(s.data(), s.size());
1255 if (pyobj && native) {
1256 PyObject* pybytes = PyUnicode_AsEncodedString(pyobj, "UTF-8", "strict");
1258 pyobj = pybytes;
1259 }
1260 return pyobj;
1261}
1262
1263#define CPPYY_IMPL_STRING_PYTHONIZATION(type, name) \
1264static inline \
1265PyObject* name##StringGetData(PyObject* self, bool native=true) \
1266{ \
1267 if (CPyCppyy::CPPInstance_Check(self)) { \
1268 type* obj = ((type*)((CPPInstance*)self)->GetObject()); \
1269 if (obj) return CPyCppyy_PyString_FromCppString(*obj, native); \
1270 } \
1271 PyErr_Format(PyExc_TypeError, "object mismatch (%s expected)", #type); \
1272 return nullptr; \
1273} \
1274 \
1275PyObject* name##StringStr(PyObject* self) \
1276{ \
1277 PyObject* pyobj = name##StringGetData(self, false); \
1278 if (!pyobj) { \
1279 /* do a native conversion to make printing possible (debatable) */ \
1280 PyErr_Clear(); \
1281 PyObject* pybytes = name##StringGetData(self, true); \
1282 if (pybytes) { /* should not fail */ \
1283 pyobj = PyObject_Str(pybytes); \
1284 Py_DECREF(pybytes); \
1285 } \
1286 } \
1287 return pyobj; \
1288} \
1289 \
1290PyObject* name##StringBytes(PyObject* self) \
1291{ \
1292 return name##StringGetData(self, true); \
1293} \
1294 \
1295PyObject* name##StringRepr(PyObject* self) \
1296{ \
1297 PyObject* data = name##StringGetData(self, true); \
1298 if (data) { \
1299 PyObject* repr = PyObject_Repr(data); \
1300 Py_DECREF(data); \
1301 return repr; \
1302 } \
1303 return nullptr; \
1304} \
1305 \
1306PyObject* name##StringIsEqual(PyObject* self, PyObject* obj) \
1307{ \
1308 PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \
1309 if (data) { \
1310 PyObject* result = PyObject_RichCompare(data, obj, Py_EQ); \
1311 Py_DECREF(data); \
1312 return result; \
1313 } \
1314 return nullptr; \
1315} \
1316 \
1317PyObject* name##StringIsNotEqual(PyObject* self, PyObject* obj) \
1318{ \
1319 PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \
1320 if (data) { \
1321 PyObject* result = PyObject_RichCompare(data, obj, Py_NE); \
1322 Py_DECREF(data); \
1323 return result; \
1324 } \
1325 return nullptr; \
1326}
1327
1328// Only define STLStringCompare:
1329#define CPPYY_IMPL_STRING_PYTHONIZATION_CMP(type, name) \
1330CPPYY_IMPL_STRING_PYTHONIZATION(type, name) \
1331PyObject* name##StringCompare(PyObject* self, PyObject* obj) \
1332{ \
1333 PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \
1334 int result = 0; \
1335 if (data) { \
1336 result = PyObject_Compare(data, obj); \
1337 Py_DECREF(data); \
1338 } \
1339 if (PyErr_Occurred()) \
1340 return nullptr; \
1341 return PyInt_FromLong(result); \
1342}
1343
1347
1348static inline std::string* GetSTLString(CPPInstance* self) {
1349 if (!CPPInstance_Check(self)) {
1350 PyErr_SetString(PyExc_TypeError, "std::string object expected");
1351 return nullptr;
1352 }
1353
1354 std::string* obj = (std::string*)self->GetObject();
1355 if (!obj)
1356 PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer");
1357
1358 return obj;
1359}
1360
1362{
1363 std::string* obj = GetSTLString(self);
1364 if (!obj)
1365 return nullptr;
1366
1367 char* keywords[] = {(char*)"encoding", (char*)"errors", (char*)nullptr};
1368 const char* encoding = nullptr; const char* errors = nullptr;
1370 const_cast<char*>("s|s"), keywords, &encoding, &errors))
1371 return nullptr;
1372
1373 return PyUnicode_Decode(obj->data(), obj->size(), encoding, errors);
1374}
1375
1376#if __cplusplus <= 202302L
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#endif
1394
1396{
1397 std::string* obj = GetSTLString(self);
1398 if (!obj)
1399 return nullptr;
1400
1401// both str and std::string have a method "replace", but the Python version only
1402// accepts strings and takes no keyword arguments, whereas the C++ version has no
1403// overload that takes a string
1404
1405 if (2 <= PyTuple_GET_SIZE(args) && CPyCppyy_PyText_Check(PyTuple_GET_ITEM(args, 0))) {
1406 PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size());
1407 PyObject* meth = PyObject_GetAttrString(pystr, (char*)"replace");
1410 Py_DECREF(meth);
1411 return result;
1412 }
1413
1414 PyObject* cppreplace = PyObject_GetAttrString((PyObject*)self, (char*)"__cpp_replace");
1415 if (cppreplace) {
1416 PyObject* result = PyObject_Call(cppreplace, args, nullptr);
1418 return result;
1419 }
1420
1421 PyErr_SetString(PyExc_AttributeError, "\'std::string\' object has no attribute \'replace\'");
1422 return nullptr;
1423}
1424
1425#define CPYCPPYY_STRING_FINDMETHOD(name, cppname, pyname) \
1426PyObject* STLString##name(CPPInstance* self, PyObject* args, PyObject* /*kwds*/) \
1427{ \
1428 std::string* obj = GetSTLString(self); \
1429 if (!obj) \
1430 return nullptr; \
1431 \
1432 PyObject* cppmeth = PyObject_GetAttrString((PyObject*)self, (char*)#cppname);\
1433 if (cppmeth) { \
1434 PyObject* result = PyObject_Call(cppmeth, args, nullptr); \
1435 Py_DECREF(cppmeth); \
1436 if (result) { \
1437 if (PyLongOrInt_AsULong64(result) == (PY_ULONG_LONG)std::string::npos) {\
1438 Py_DECREF(result); \
1439 return PyInt_FromLong(-1); \
1440 } \
1441 return result; \
1442 } \
1443 PyErr_Clear(); \
1444 } \
1445 \
1446 PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size());\
1447 PyObject* pymeth = PyObject_GetAttrString(pystr, (char*)#pyname); \
1448 Py_DECREF(pystr); \
1449 PyObject* result = PyObject_CallObject(pymeth, args); \
1450 Py_DECREF(pymeth); \
1451 return result; \
1452}
1453
1454// both str and std::string have method "find" and "rfin"; try the C++ version first
1455// and fall back on the Python one in case of failure
1458
1460{
1461 std::string* obj = GetSTLString(self);
1462 if (!obj)
1463 return nullptr;
1464
1465 PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size());
1468 return attr;
1469}
1470
1471
1472#if 0
1474{
1475// force C++ string types conversion to Python str per Python __repr__ requirements
1477 if (!res || CPyCppyy_PyText_Check(res))
1478 return res;
1480 Py_DECREF(res);
1481 return str_res;
1482}
1483
1485{
1486// force C++ string types conversion to Python str per Python __str__ requirements
1488 if (!res || CPyCppyy_PyText_Check(res))
1489 return res;
1491 Py_DECREF(res);
1492 return str_res;
1493}
1494#endif
1495
1497{
1498// std::string objects hash to the same values as Python strings to allow
1499// matches in dictionaries etc.
1502 Py_DECREF(data);
1503 return h;
1504}
1505
1506
1507//- string_view behavior as primitive ----------------------------------------
1509{
1510// if constructed from a Python unicode object, the constructor will convert it
1511// to a temporary byte string, which is likely to go out of scope too soon; so
1512// buffer it as needed
1514 if (realInit) {
1515 PyObject *strbuf = nullptr, *newArgs = nullptr;
1516 if (PyTuple_GET_SIZE(args) == 1) {
1517 PyObject* arg0 = PyTuple_GET_ITEM(args, 0);
1518 if (PyUnicode_Check(arg0)) {
1519 // convert to the expected bytes array to control the temporary
1520 strbuf = PyUnicode_AsEncodedString(arg0, "UTF-8", "strict");
1521 newArgs = PyTuple_New(1);
1524 } else if (PyBytes_Check(arg0)) {
1525 // tie the life time of the provided string to the string_view
1526 Py_INCREF(arg0);
1527 strbuf = arg0;
1528 }
1529 }
1530
1531 PyObject* result = PyObject_Call(realInit, newArgs ? newArgs : args, nullptr);
1532
1535
1536 // if construction was successful and a string buffer was used, add a
1537 // life line to it from the string_view bound object
1538 if (result && self && strbuf)
1541
1542 return result;
1543 }
1544 return nullptr;
1545}
1546
1547
1548//- STL iterator behavior ----------------------------------------------------
1550{
1551// Python iterator protocol __next__ for STL forward iterators.
1552 bool mustIncrement = true;
1553 PyObject* last = nullptr;
1554 if (CPPInstance_Check(self)) {
1555 auto& dmc = ((CPPInstance*)self)->GetDatamemberCache();
1556 for (auto& p: dmc) {
1557 if (p.first == PS_END_ADDR) {
1558 last = p.second;
1559 Py_INCREF(last);
1560 } else if (p.first == PS_FLAG_ADDR) {
1561 mustIncrement = p.second == Py_True;
1562 if (!mustIncrement) {
1563 Py_DECREF(p.second);
1565 p.second = Py_True;
1566 }
1567 }
1568 }
1569 }
1570
1571 PyObject* next = nullptr;
1572 if (last) {
1573 // handle special case of empty container (i.e. self is end)
1574 if (!PyObject_RichCompareBool(last, self, Py_EQ)) {
1575 bool iter_valid = true;
1576 if (mustIncrement) {
1577 // prefer preinc, but allow post-inc; in both cases, it is "self" that has
1578 // the updated state to dereference
1580 if (!iter) {
1581 PyErr_Clear();
1582 static PyObject* dummy = PyInt_FromLong(1l);
1584 }
1586 Py_XDECREF(iter);
1587 }
1588
1589 if (iter_valid) {
1591 if (!next) PyErr_Clear();
1592 }
1593 }
1594 Py_DECREF(last);
1595 }
1596
1597 if (!next) PyErr_SetString(PyExc_StopIteration, "");
1598 return next;
1599}
1600
1601
1602//- STL complex<T> behavior --------------------------------------------------
1603#define COMPLEX_METH_GETSET(name, cppname) \
1604static PyObject* name##ComplexGet(PyObject* self, void*) { \
1605 return PyObject_CallMethodNoArgs(self, cppname); \
1606} \
1607static int name##ComplexSet(PyObject* self, PyObject* value, void*) { \
1608 PyObject* result = PyObject_CallMethodOneArg(self, cppname, value); \
1609 if (result) { \
1610 Py_DECREF(result); \
1611 return 0; \
1612 } \
1613 return -1; \
1614} \
1615PyGetSetDef name##Complex{(char*)#name, (getter)name##ComplexGet, (setter)name##ComplexSet, nullptr, nullptr};
1616
1619
1622 if (!real) return nullptr;
1623 double r = PyFloat_AsDouble(real);
1624 Py_DECREF(real);
1625 if (r == -1. && PyErr_Occurred())
1626 return nullptr;
1627
1629 if (!imag) return nullptr;
1630 double i = PyFloat_AsDouble(imag);
1631 Py_DECREF(imag);
1632 if (i == -1. && PyErr_Occurred())
1633 return nullptr;
1634
1635 return PyComplex_FromDoubles(r, i);
1636}
1637
1640 if (!real) return nullptr;
1641 double r = PyFloat_AsDouble(real);
1642 Py_DECREF(real);
1643 if (r == -1. && PyErr_Occurred())
1644 return nullptr;
1645
1647 if (!imag) return nullptr;
1648 double i = PyFloat_AsDouble(imag);
1649 Py_DECREF(imag);
1650 if (i == -1. && PyErr_Occurred())
1651 return nullptr;
1652
1653 std::ostringstream s;
1654 s << '(' << r << '+' << i << "j)";
1655 return CPyCppyy_PyText_FromString(s.str().c_str());
1656}
1657
1659{
1660 return PyFloat_FromDouble(((std::complex<double>*)self->GetObject())->real());
1661}
1662
1663static int ComplexDRealSet(CPPInstance* self, PyObject* value, void*)
1664{
1665 double d = PyFloat_AsDouble(value);
1666 if (d == -1.0 && PyErr_Occurred())
1667 return -1;
1668 ((std::complex<double>*)self->GetObject())->real(d);
1669 return 0;
1670}
1671
1672PyGetSetDef ComplexDReal{(char*)"real", (getter)ComplexDRealGet, (setter)ComplexDRealSet, nullptr, nullptr};
1673
1674
1676{
1677 return PyFloat_FromDouble(((std::complex<double>*)self->GetObject())->imag());
1678}
1679
1680static int ComplexDImagSet(CPPInstance* self, PyObject* value, void*)
1681{
1682 double d = PyFloat_AsDouble(value);
1683 if (d == -1.0 && PyErr_Occurred())
1684 return -1;
1685 ((std::complex<double>*)self->GetObject())->imag(d);
1686 return 0;
1687}
1688
1689PyGetSetDef ComplexDImag{(char*)"imag", (getter)ComplexDImagGet, (setter)ComplexDImagSet, nullptr, nullptr};
1690
1692{
1693 double r = ((std::complex<double>*)self->GetObject())->real();
1694 double i = ((std::complex<double>*)self->GetObject())->imag();
1695 return PyComplex_FromDoubles(r, i);
1696}
1697
1698
1699} // unnamed namespace
1700
1701
1702//- public functions ---------------------------------------------------------
1703namespace CPyCppyy {
1704 std::set<std::string> gIteratorTypes;
1705}
1706
1707static inline
1708bool run_pythonizors(PyObject* pyclass, PyObject* pyname, const std::vector<PyObject*>& v)
1709{
1710 PyObject* args = PyTuple_New(2);
1713
1714 bool pstatus = true;
1715 for (auto pythonizor : v) {
1717 if (!result) {
1718 pstatus = false; // TODO: detail the error handling
1719 break;
1720 }
1722 }
1723 Py_DECREF(args);
1724
1725 return pstatus;
1726}
1727
1728bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name)
1729{
1730// Add pre-defined pythonizations (for STL and ROOT) to classes based on their
1731// signature and/or class name.
1732 if (!pyclass)
1733 return false;
1734
1736
1737//- method name based pythonization ------------------------------------------
1738
1739// for smart pointer style classes that are otherwise not known as such; would
1740// prefer operator-> as that returns a pointer (which is simpler since it never
1741// has to deal with ref-assignment), but operator* plays better with STL iters
1742// and algorithms
1747
1748// for pre-check of nullptr for boolean types
1750#if PY_VERSION_HEX >= 0x03000000
1751 const char* pybool_name = "__bool__";
1752#else
1753 const char* pybool_name = "__nonzero__";
1754#endif
1756 }
1757
1758// for STL containers, and user classes modeled after them
1759// the attribute must be a CPyCppyy overload, otherwise the check gives false
1760// positives in the case where the class has a non-function attribute that is
1761// called "size".
1762 if (HasAttrDirect(pyclass, PyStrings::gSize, /*mustBeCPyCppyy=*/ true)) {
1763 Utility::AddToClass(pyclass, "__len__", "size");
1764 }
1765
1767 Utility::AddToClass(pyclass, "__contains__", "contains");
1768 }
1769
1770 if (!IsTemplatedSTLClass(name, "vector") && // vector is dealt with below
1773 // obtain the name of the return type
1774 const auto& v = Cppyy::GetMethodIndicesFromName(klass->fCppType, "begin");
1775 if (!v.empty()) {
1776 // check return type; if not explicitly an iterator, add it to the "known" return
1777 // types to add the "next" method on use
1779 const std::string& resname = Cppyy::GetMethodResultType(meth);
1780 bool isIterator = gIteratorTypes.find(resname) != gIteratorTypes.end();
1782 if (resname.find("iterator") == std::string::npos)
1783 gIteratorTypes.insert(resname);
1784 isIterator = true;
1785 }
1786
1787 if (isIterator) {
1788 // install iterator protocol a la STL
1791 } else {
1792 // still okay if this is some pointer type of builtin persuasion (general class
1793 // won't work: the return type needs to understand the iterator protocol)
1794 std::string resolved = Cppyy::ResolveName(resname);
1795 if (resolved.back() == '*' && Cppyy::IsBuiltin(resolved.substr(0, resolved.size()-1))) {
1798 }
1799 }
1800 }
1801 }
1802 if (!((PyTypeObject*)pyclass)->tp_iter && // no iterator resolved
1804 // Python will iterate over __getitem__ using integers, but C++ operator[] will never raise
1805 // a StopIteration. A checked getitem (raising IndexError if beyond size()) works in some
1806 // cases but would mess up if operator[] is meant to implement an associative container. So,
1807 // this has to be implemented as an iterator protocol.
1810 }
1811 }
1812
1813// operator==/!= are used in op_richcompare of CPPInstance, which subsequently allows
1814// comparisons to None; if no operator is available, a hook is installed for lazy
1815// lookups in the global and/or class namespace
1816 if (HasAttrDirect(pyclass, PyStrings::gEq, true) && \
1817 Cppyy::GetMethodIndicesFromName(klass->fCppType, "__eq__").empty()) {
1819 if (!klass->fOperators) klass->fOperators = new Utility::PyOperators();
1820 klass->fOperators->fEq = cppol;
1821 // re-insert the forwarding __eq__ from the CPPInstance in case there was a Python-side
1822 // override in the base class
1823 static PyObject* top_eq = nullptr;
1824 if (!top_eq) {
1827 Py_DECREF(top_eq); // make it borrowed
1829 }
1831 }
1832
1833 if (HasAttrDirect(pyclass, PyStrings::gNe, true) && \
1834 Cppyy::GetMethodIndicesFromName(klass->fCppType, "__ne__").empty()) {
1836 if (!klass->fOperators) klass->fOperators = new Utility::PyOperators();
1837 klass->fOperators->fNe = cppol;
1838 // re-insert the forwarding __ne__ (same reason as above for __eq__)
1839 static PyObject* top_ne = nullptr;
1840 if (!top_ne) {
1843 Py_DECREF(top_ne); // make it borrowed
1845 }
1847 }
1848
1849#if 0
1851 // guarantee that the result of __repr__ is a Python string
1852 Utility::AddToClass(pyclass, "__cpp_repr", "__repr__");
1854 }
1855
1857 // guarantee that the result of __str__ is a Python string
1858 Utility::AddToClass(pyclass, "__cpp_str", "__str__");
1860 }
1861#endif
1862
1863 if (Cppyy::IsAggregate(((CPPClass*)pyclass)->fCppType) && name.compare(0, 5, "std::", 5) != 0 &&
1864 name.compare(0, 6, "tuple<", 6) != 0) {
1865 // create a pseudo-constructor to allow initializer-style object creation
1866 Cppyy::TCppType_t kls = ((CPPClass*)pyclass)->fCppType;
1868 if (ndata) {
1869 std::string rname = name;
1871
1872 std::ostringstream initdef;
1873 initdef << "namespace __cppyy_internal {\n"
1874 << "void init_" << rname << "(" << name << "*& self";
1875 bool codegen_ok = true;
1876 std::vector<std::string> arg_types, arg_names, arg_defaults;
1877 arg_types.reserve(ndata); arg_names.reserve(ndata); arg_defaults.reserve(ndata);
1878 for (Cppyy::TCppIndex_t i = 0; i < ndata; ++i) {
1880 continue;
1881
1882 const std::string& txt = Cppyy::GetDatamemberType(kls, i);
1883 const std::string& res = Cppyy::IsEnum(txt) ? txt : Cppyy::ResolveName(txt);
1884 const std::string& cpd = TypeManip::compound(res);
1885 std::string res_clean = TypeManip::clean_type(res, false, true);
1886
1887 if (res_clean == "internal_enum_type_t")
1888 res_clean = txt; // restore (properly scoped name)
1889
1890 if (res.rfind(']') == std::string::npos && res.rfind(')') == std::string::npos) {
1891 if (!cpd.empty()) arg_types.push_back(res_clean+cpd);
1892 else arg_types.push_back("const "+res_clean+"&");
1893 arg_names.push_back(Cppyy::GetDatamemberName(kls, i));
1894 if ((!cpd.empty() && cpd.back() == '*') || Cppyy::IsBuiltin(res_clean))
1895 arg_defaults.push_back("0");
1896 else {
1899 }
1900 } else {
1901 codegen_ok = false; // TODO: how to support arrays, anonymous enums, etc?
1902 break;
1903 }
1904 }
1905
1906 if (codegen_ok && !arg_types.empty()) {
1907 bool defaults_ok = arg_defaults.size() == arg_types.size();
1908 for (std::vector<std::string>::size_type i = 0; i < arg_types.size(); ++i) {
1909 initdef << ", " << arg_types[i] << " " << arg_names[i];
1910 if (defaults_ok) initdef << " = " << arg_defaults[i];
1911 }
1912 initdef << ") {\n self = new " << name << "{";
1913 for (std::vector<std::string>::size_type i = 0; i < arg_names.size(); ++i) {
1914 if (i != 0) initdef << ", ";
1915 initdef << arg_names[i];
1916 }
1917 initdef << "};\n} }";
1918
1919 if (Cppyy::Compile(initdef.str(), true /* silent */)) {
1920 Cppyy::TCppScope_t cis = Cppyy::GetScope("__cppyy_internal");
1921 const auto& mix = Cppyy::GetMethodIndicesFromName(cis, "init_"+rname);
1922 if (mix.size()) {
1923 if (!Utility::AddToClass(pyclass, "__init__",
1924 new CPPFunction(cis, Cppyy::GetMethod(cis, mix[0]))))
1925 PyErr_Clear();
1926 }
1927 }
1928 }
1929 }
1930 }
1931
1932
1933//- class name based pythonization -------------------------------------------
1934
1935 if (IsTemplatedSTLClass(name, "span")) {
1936 // libstdc++ (GCC >= 15) implements std::span::iterator using a private
1937 // nested tag type, which makes the iterator non-instantiable by
1938 // CallFunc-generated wrappers (the return type cannot be named without
1939 // violating access rules).
1940 //
1941 // To preserve correct Python iteration semantics, we replace begin()/end()
1942 // for std::span to return a custom pointer-based iterator instead. This
1943 // avoids relying on std::span::iterator while still providing a real C++
1944 // iterator object that CPyCppyy can also wrap and expose via
1945 // __iter__/__next__.
1948 }
1949
1950 if (IsTemplatedSTLClass(name, "vector")) {
1951
1952 // std::vector<bool> is a special case in C++
1954 if (klass->fCppType == sVectorBoolTypeID) {
1957 } else {
1958 // constructor that takes python collections
1959 Utility::AddToClass(pyclass, "__real_init", "__init__");
1961
1962 // data with size
1963 Utility::AddToClass(pyclass, "__real_data", "data");
1964 PyErr_Clear(); // AddToClass might have failed for data
1966
1967 // The addition of the __array__ utility to std::vector Python proxies causes a
1968 // bug where the resulting array is a single dimension, causing loss of data when
1969 // converting to numpy arrays, for >1dim vectors. Since this C++ pythonization
1970 // was added with the upgrade in 6.32, and is only defined and used recursively,
1971 // the safe option is to disable this function and no longer add it.
1972#if 0
1973 // numpy array conversion
1975#endif
1976
1977 // checked getitem
1979 Utility::AddToClass(pyclass, "_getitem__unchecked", "__getitem__");
1981 }
1982
1983 // vector-optimized iterator protocol
1985
1986 // optimized __iadd__
1988
1989 // helpers for iteration
1990 const std::string& vtype = Cppyy::ResolveName(name+"::value_type");
1991 if (vtype.rfind("value_type") == std::string::npos) { // actually resolved?
1995 }
1996
1997 size_t typesz = Cppyy::SizeOf(name+"::value_type");
1998 if (typesz) {
2002 }
2003 }
2004 }
2005
2006 else if (IsTemplatedSTLClass(name, "array")) {
2007 // constructor that takes python associative collections
2008 Utility::AddToClass(pyclass, "__real_init", "__init__");
2010 }
2011
2012 else if (IsTemplatedSTLClass(name, "map") || IsTemplatedSTLClass(name, "unordered_map")) {
2013 // constructor that takes python associative collections
2014 Utility::AddToClass(pyclass, "__real_init", "__init__");
2016#if __cplusplus <= 202002L
2017 // From C++20, std::map and std::unordered_map already implement a contains() method.
2019#endif
2020 }
2021
2022 else if (IsTemplatedSTLClass(name, "set")) {
2023 // constructor that takes python associative collections
2024 Utility::AddToClass(pyclass, "__real_init", "__init__");
2026
2027#if __cplusplus <= 202002L
2028 // From C++20, std::set already implements a contains() method.
2030#endif
2031 }
2032
2033 else if (IsTemplatedSTLClass(name, "pair")) {
2036 }
2037
2038 if (IsTemplatedSTLClass(name, "shared_ptr") || IsTemplatedSTLClass(name, "unique_ptr")) {
2039 Utility::AddToClass(pyclass, "__real_init", "__init__");
2041 }
2042
2043 else if (!((PyTypeObject*)pyclass)->tp_iter && \
2044 (name.find("iterator") != std::string::npos || gIteratorTypes.find(name) != gIteratorTypes.end())) {
2045 ((PyTypeObject*)pyclass)->tp_iternext = (iternextfunc)STLIterNext;
2049 }
2050
2051 else if (name == "string" || name == "std::string") { // TODO: ask backend as well
2058#if __cplusplus <= 202302L
2059 // From C++23, std::sting already implements a contains() method.
2061#endif
2063 Utility::AddToClass(pyclass, "__cpp_find", "find");
2065 Utility::AddToClass(pyclass, "__cpp_rfind", "rfind");
2067 Utility::AddToClass(pyclass, "__cpp_replace", "replace");
2070
2071 // to allow use of std::string in dictionaries and findable with str
2073 }
2074
2075 else if (name == "basic_string_view<char,char_traits<char> >" || name == "std::basic_string_view<char>") {
2076 Utility::AddToClass(pyclass, "__real_init", "__init__");
2084 }
2085
2086// The first condition was already present in upstream CPyCppyy. The other two
2087// are special to ROOT, because its reflection layer gives us the types without
2088// the "std::" namespace. On some platforms, that applies only to the template
2089// arguments, and on others also to the "basic_string".
2090 else if (name == "std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> >"
2091 || name == "basic_string<wchar_t,char_traits<wchar_t>,allocator<wchar_t> >"
2092 || name == "std::basic_string<wchar_t,char_traits<wchar_t>,allocator<wchar_t> >"
2093 ) {
2100 }
2101
2102 else if (name == "complex<double>" || name == "std::complex<double>") {
2103 Utility::AddToClass(pyclass, "__cpp_real", "real");
2105 Utility::AddToClass(pyclass, "__cpp_imag", "imag");
2109 }
2110
2111 else if (IsTemplatedSTLClass(name, "complex")) {
2112 Utility::AddToClass(pyclass, "__cpp_real", "real");
2114 Utility::AddToClass(pyclass, "__cpp_imag", "imag");
2118 }
2119
2120// direct user access; there are two calls here:
2121// - explicit pythonization: won't fall through to the base classes and is preferred if present
2122// - normal pythonization: only called if explicit isn't present, falls through to base classes
2123 bool bUserOk = true; PyObject* res = nullptr;
2127 bUserOk = (bool)res;
2128 } else {
2130 if (func) {
2131 res = PyObject_CallFunctionObjArgs(func, pyclass, pyname, nullptr);
2132 Py_DECREF(func);
2133 bUserOk = (bool)res;
2134 } else
2135 PyErr_Clear();
2136 }
2137 if (!bUserOk) {
2139 return false;
2140 } else {
2141 Py_XDECREF(res);
2142 // pyname handed to args tuple below
2143 }
2144
2145// call registered pythonizors, if any: first run the namespace-specific pythonizors, then
2146// the global ones (the idea is to allow writing a pythonizor that see all classes)
2147 bool pstatus = true;
2149 auto &pyzMap = pythonizations();
2150 if (!outer_scope.empty()) {
2151 auto p = pyzMap.find(outer_scope);
2152 if (p != pyzMap.end()) {
2154 name.substr(outer_scope.size()+2, std::string::npos).c_str());
2157 }
2158 }
2159
2160 if (pstatus) {
2161 auto p = pyzMap.find("");
2162 if (p != pyzMap.end())
2163 pstatus = run_pythonizors(pyclass, pyname, p->second);
2164 }
2165
2167
2168// phew! all done ...
2169 return pstatus;
2170}
#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 * gContains
Definition PyStrings.cxx:10
PyObject * gCTypesType
Definition PyStrings.cxx:40
PyObject * gRealInit
Definition PyStrings.cxx:43
PyObject * gExPythonize
Definition PyStrings.cxx:73
PyObject * gLifeLine
Definition PyStrings.cxx:30
PyObject * gGetItem
Definition PyStrings.cxx:24
PyObject * gCppBool
Definition PyStrings.cxx:12
PyObject * gCppReal
Definition PyStrings.cxx:65
PyObject * gPythonize
Definition PyStrings.cxx:74
PyObject * gTypeCode
Definition PyStrings.cxx:39
PyObject * gPostInc
Definition PyStrings.cxx:19
PyObject * gCppImag
Definition PyStrings.cxx:66
PyObject * gValueSize
Definition PyStrings.cxx:63
PyObject * gSetItem
Definition PyStrings.cxx:26
PyObject * gGetNoCheck
Definition PyStrings.cxx:25
PyObject * gCppRepr
Definition PyStrings.cxx:36
PyObject * gValueType
Definition PyStrings.cxx:62
void cppscope_to_legalname(std::string &cppscope)
std::string clean_type(const std::string &cppname, bool template_strip=true, bool const_strip=true)
std::string compound(const std::string &name)
std::string extract_namespace(const std::string &name)
Py_ssize_t GetBuffer(PyObject *pyobject, char tc, int size, void *&buf, bool check=true)
Definition Utility.cxx:893
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:40
RPY_EXPORTED size_t SizeOf(TCppType_t klass)
intptr_t TCppMethod_t
Definition cpp_cppyy.h:38
RPY_EXPORTED bool IsDefaultConstructable(TCppType_t type)
RPY_EXPORTED bool IsEnum(const std::string &type_name)
RPY_EXPORTED std::vector< TCppIndex_t > GetMethodIndicesFromName(TCppScope_t scope, const std::string &name)
RPY_EXPORTED TCppIndex_t GetNumDatamembers(TCppScope_t scope, bool accept_namespace=false)
RPY_EXPORTED bool Compile(const std::string &code, bool silent=false)
RPY_EXPORTED TCppScope_t gGlobalScope
Definition cpp_cppyy.h:69
RPY_EXPORTED std::string ResolveName(const std::string &cppitem_name)
TCppScope_t TCppType_t
Definition cpp_cppyy.h:35
RPY_EXPORTED bool IsAggregate(TCppType_t type)
RPY_EXPORTED std::string GetScopedFinalName(TCppType_t type)
RPY_EXPORTED bool IsPublicData(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED bool IsBuiltin(const std::string &type_name)
RPY_EXPORTED bool IsStaticData(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED std::string GetDatamemberType(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED TCppMethod_t GetMethod(TCppScope_t scope, TCppIndex_t imeth)
RPY_EXPORTED bool IsSmartPtr(TCppType_t type)
RPY_EXPORTED TCppScope_t GetScope(const std::string &scope_name)
size_t TCppScope_t
Definition cpp_cppyy.h:34
RPY_EXPORTED std::string GetMethodResultType(TCppMethod_t)
RPY_EXPORTED std::string GetDatamemberName(TCppScope_t scope, TCppIndex_t idata)
CoordSystem::Scalar get(DisplacementVector2D< CoordSystem, Tag > const &p)