FreeCAD

Форк
0
/
PyObjectBase.cpp 
585 строк · 24.0 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de>              *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
6
 *   This library is free software; you can redistribute it and/or         *
7
 *   modify it under the terms of the GNU Library General Public           *
8
 *   License as published by the Free Software Foundation; either          *
9
 *   version 2 of the License, or (at your option) any later version.      *
10
 *                                                                         *
11
 *   This library  is distributed in the hope that it will be useful,      *
12
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
14
 *   GNU Library General Public License for more details.                  *
15
 *                                                                         *
16
 *   You should have received a copy of the GNU Library General Public     *
17
 *   License along with this library; see the file COPYING.LIB. If not,    *
18
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
19
 *   Suite 330, Boston, MA  02111-1307, USA                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23

24
#include "PreCompiled.h"
25

26
#ifndef _PreComp_
27
#include <sstream>
28
#endif
29

30
#include "PyObjectBase.h"
31
#include "Console.h"
32
#include "Interpreter.h"
33

34

35
#define ATTR_TRACKING
36

37
using namespace Base;
38

39
// clang-format off
40
PyObject* Base::PyExc_FC_GeneralError = nullptr;
41
PyObject* Base::PyExc_FC_FreeCADAbort = nullptr;
42
PyObject* Base::PyExc_FC_XMLBaseException = nullptr;
43
PyObject* Base::PyExc_FC_XMLParseException = nullptr;
44
PyObject* Base::PyExc_FC_XMLAttributeError = nullptr;
45
PyObject* Base::PyExc_FC_UnknownProgramOption = nullptr;
46
PyObject* Base::PyExc_FC_BadFormatError = nullptr;
47
PyObject* Base::PyExc_FC_BadGraphError = nullptr;
48
PyObject* Base::PyExc_FC_ExpressionError = nullptr;
49
PyObject* Base::PyExc_FC_ParserError = nullptr;
50
PyObject* Base::PyExc_FC_CADKernelError = nullptr;
51
PyObject* Base::PyExc_FC_PropertyError = nullptr;
52

53
typedef struct {            //NOLINT
54
    PyObject_HEAD
55
    PyObject* baseobject;
56
    PyObject* weakreflist;  /* List of weak references */
57
} PyBaseProxy;
58

59
// NOLINTNEXTLINE
60
PyObjectBase::PyObjectBase(void* voidp, PyTypeObject *T)
61
  : _pcTwinPointer(voidp)
62
{
63
#if PY_VERSION_HEX < 0x030b0000
64
    Py_TYPE(this) = T;
65
#else
66
    this->ob_type = T;
67
#endif
68
    _Py_NewReference(this);
69
#ifdef FC_LOGPYOBJECTS
70
    Base::Console().Log("PyO+: %s (%p)\n",T->tp_name, this);
71
#endif
72
    StatusBits.set(Valid); // valid, the second bit is NOT set, i.e. it's mutable
73
    StatusBits.set(Notify);
74
}
75

76
/// destructor
77
PyObjectBase::~PyObjectBase()
78
{
79
    PyGILStateLocker lock;
80
#ifdef FC_LOGPYOBJECTS
81
    Base::Console().Log("PyO-: %s (%p)\n",Py_TYPE(this)->tp_name, this);
82
#endif
83
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
84
    if (baseProxy && reinterpret_cast<PyBaseProxy*>(baseProxy)->baseobject == this) {
85
        Py_DECREF(baseProxy);
86
    }
87
    Py_XDECREF(attrDict);
88
}
89

90
/*------------------------------
91
 * PyObjectBase Type		-- Every class, even the abstract one should have a Type
92
------------------------------*/
93

94
/** \brief
95
 * To prevent subclasses of PyTypeObject to be subclassed in Python we should remove
96
 * the Py_TPFLAGS_BASETYPE flag. For example, the classes App::VectorPy and App::MatrixPy
97
 * have removed this flag and its Python proxies App.Vector and App.Matrix cannot be subclassed.
98
 * In case we want to allow to derive from subclasses of PyTypeObject in Python
99
 * we must either reimplement tp_new, tp_dealloc, tp_getattr, tp_setattr, tp_repr or set them to
100
 * 0 and define tp_base as 0.
101
 */
102

103
#if defined(__clang__)
104
# pragma clang diagnostic push
105
# pragma clang diagnostic ignored "-Wdeprecated-declarations"
106
#endif
107

108
static void
109
PyBaseProxy_dealloc(PyObject* self)
110
{
111
    /* Clear weakrefs first before calling any destructors */
112
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
113
    if (reinterpret_cast<PyBaseProxy*>(self)->weakreflist) {
114
        PyObject_ClearWeakRefs(self);
115
    }
116
    Py_TYPE(self)->tp_free(self);
117
}
118

119
static PyTypeObject PyBaseProxyType = {
120
    PyVarObject_HEAD_INIT(nullptr, 0)
121
    "PyBaseProxy",                                          /*tp_name*/
122
    sizeof(PyBaseProxy),                                    /*tp_basicsize*/
123
    0,                                                      /*tp_itemsize*/
124
    PyBaseProxy_dealloc,                                    /*tp_dealloc*/
125
#if PY_VERSION_HEX >= 0x03080000
126
    0,                                                      /*tp_vectorcall_offset*/
127
#else
128
    nullptr,                                                /*tp_print*/
129
#endif
130
    nullptr,                                                /*tp_getattr*/
131
    nullptr,                                                /*tp_setattr*/
132
    nullptr,                                                /*tp_compare*/
133
    nullptr,                                                /*tp_repr*/
134
    nullptr,                                                /*tp_as_number*/
135
    nullptr,                                                /*tp_as_sequence*/
136
    nullptr,                                                /*tp_as_mapping*/
137
    nullptr,                                                /*tp_hash*/
138
    nullptr,                                                /*tp_call */
139
    nullptr,                                                /*tp_str  */
140
    nullptr,                                                /*tp_getattro*/
141
    nullptr,                                                /*tp_setattro*/
142
    nullptr,                                                /*tp_as_buffer*/
143
    Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DEFAULT,               /*tp_flags */
144
    "Proxy class",                                          /*tp_doc */
145
    nullptr,                                                /*tp_traverse */
146
    nullptr,                                                /*tp_clear */
147
    nullptr,                                                /*tp_richcompare */
148
    offsetof(PyBaseProxy, weakreflist),                     /*tp_weaklistoffset */
149
    nullptr,                                                /*tp_iter */
150
    nullptr,                                                /*tp_iternext */
151
    nullptr,                                                /*tp_methods */
152
    nullptr,                                                /*tp_members */
153
    nullptr,                                                /*tp_getset */
154
    nullptr,                                                /*tp_base */
155
    nullptr,                                                /*tp_dict */
156
    nullptr,                                                /*tp_descr_get */
157
    nullptr,                                                /*tp_descr_set */
158
    0,                                                      /*tp_dictoffset */
159
    nullptr,                                                /*tp_init */
160
    nullptr,                                                /*tp_alloc */
161
    nullptr,                                                /*tp_new */
162
    nullptr,                                                /*tp_free   Low-level free-memory routine */
163
    nullptr,                                                /*tp_is_gc  For PyObject_IS_GC */
164
    nullptr,                                                /*tp_bases */
165
    nullptr,                                                /*tp_mro    method resolution order */
166
    nullptr,                                                /*tp_cache */
167
    nullptr,                                                /*tp_subclasses */
168
    nullptr,                                                /*tp_weaklist */
169
    nullptr,                                                /*tp_del */
170
    0,                                                      /*tp_version_tag */
171
    nullptr                                                 /*tp_finalize */
172
#if PY_VERSION_HEX >= 0x03090000
173
    ,0                                            //NOLINT  /*tp_vectorcall */
174
#if PY_VERSION_HEX >= 0x030c0000
175
    ,0                                                      /*tp_watched */
176
#endif
177
#elif PY_VERSION_HEX >= 0x03080000
178
    ,0                                                      /*tp_vectorcall */
179
    /* bpo-37250: kept for backwards compatibility in CPython 3.8 only */
180
    ,0                                                      /*tp_print */
181
#endif
182
};
183

184
PyTypeObject PyObjectBase::Type = {
185
    PyVarObject_HEAD_INIT(&PyType_Type,0)
186
    "PyObjectBase",                                         /*tp_name*/
187
    sizeof(PyObjectBase),                                   /*tp_basicsize*/
188
    0,                                                      /*tp_itemsize*/
189
    /* --- methods ---------------------------------------------- */
190
    PyDestructor,                                           /*tp_dealloc*/
191
#if PY_VERSION_HEX >= 0x03080000
192
    0,                                                      /*tp_vectorcall_offset*/
193
#else
194
    nullptr,                                                /*tp_print*/
195
#endif
196
    nullptr,                                                /*tp_getattr*/
197
    nullptr,                                                /*tp_setattr*/
198
    nullptr,                                                /*tp_compare*/
199
    __repr,                                                 /*tp_repr*/
200
    nullptr,                                                /*tp_as_number*/
201
    nullptr,                                                /*tp_as_sequence*/
202
    nullptr,                                                /*tp_as_mapping*/
203
    nullptr,                                                /*tp_hash*/
204
    nullptr,                                                /*tp_call */
205
    nullptr,                                                /*tp_str  */
206
    __getattro,                                             /*tp_getattro*/
207
    __setattro,                                             /*tp_setattro*/
208
    /* --- Functions to access object as input/output buffer ---------*/
209
    nullptr,                                                /* tp_as_buffer */
210
    /* --- Flags to define presence of optional/expanded features */
211
    Py_TPFLAGS_BASETYPE|Py_TPFLAGS_DEFAULT,                 /*tp_flags */
212
    "The most base class for Python binding",               /*tp_doc */
213
    nullptr,                                                /*tp_traverse */
214
    nullptr,                                                /*tp_clear */
215
    nullptr,                                                /*tp_richcompare */
216
    0,                                                      /*tp_weaklistoffset */
217
    nullptr,                                                /*tp_iter */
218
    nullptr,                                                /*tp_iternext */
219
    nullptr,                                                /*tp_methods */
220
    nullptr,                                                /*tp_members */
221
    nullptr,                                                /*tp_getset */
222
    nullptr,                                                /*tp_base */
223
    nullptr,                                                /*tp_dict */
224
    nullptr,                                                /*tp_descr_get */
225
    nullptr,                                                /*tp_descr_set */
226
    0,                                                      /*tp_dictoffset */
227
    nullptr,                                                /*tp_init */
228
    nullptr,                                                /*tp_alloc */
229
    nullptr,                                                /*tp_new */
230
    nullptr,                                                /*tp_free   Low-level free-memory routine */
231
    nullptr,                                                /*tp_is_gc  For PyObject_IS_GC */
232
    nullptr,                                                /*tp_bases */
233
    nullptr,                                                /*tp_mro    method resolution order */
234
    nullptr,                                                /*tp_cache */
235
    nullptr,                                                /*tp_subclasses */
236
    nullptr,                                                /*tp_weaklist */
237
    nullptr,                                                /*tp_del */
238
    0,                                                      /*tp_version_tag */
239
    nullptr                                                 /*tp_finalize */
240
#if PY_VERSION_HEX >= 0x03090000
241
    ,0                                            //NOLINT  /*tp_vectorcall */
242
#if PY_VERSION_HEX >= 0x030c0000
243
    ,0                                                      /*tp_watched */
244
#endif
245
#elif PY_VERSION_HEX >= 0x03080000
246
    ,0                                                      /*tp_vectorcall */
247
    /* bpo-37250: kept for backwards compatibility in CPython 3.8 only */
248
    ,0                                                      /*tp_print */
249
#endif
250
};
251

252
#if defined(__clang__)
253
# pragma clang diagnostic pop
254
#endif
255

256
PyObject* createWeakRef(PyObjectBase* ptr)
257
{
258
    static bool init = false;
259
    if (!init) {
260
       init = true;
261
       if (PyType_Ready(&PyBaseProxyType) < 0) {
262
           return nullptr;
263
        }
264
    }
265

266
    PyObject* proxy = ptr->baseProxy;
267
    if (!proxy) {
268
        proxy = PyType_GenericAlloc(&PyBaseProxyType, 0);
269
        ptr->baseProxy = proxy;
270
        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
271
        reinterpret_cast<PyBaseProxy*>(proxy)->baseobject = ptr;
272
    }
273

274
    PyObject* ref = PyWeakref_NewRef(proxy, nullptr);
275
    return ref;
276
}
277

278
PyObjectBase* getFromWeakRef(PyObject* ref)
279
{
280
    if (ref) {
281
        PyObject* proxy = PyWeakref_GetObject(ref);
282
        if (proxy && PyObject_TypeCheck(proxy, &PyBaseProxyType)) {
283
            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
284
            return static_cast<PyObjectBase*>(reinterpret_cast<PyBaseProxy*>(proxy)->baseobject);
285
        }
286
    }
287

288
    return nullptr;
289
}
290

291
/*------------------------------
292
 * PyObjectBase Methods 	-- Every class, even the abstract one should have a Methods
293
------------------------------*/
294
PyMethodDef PyObjectBase::Methods[] = {
295
    {nullptr, nullptr, 0, nullptr}        /* Sentinel */
296
};
297

298
PyObject* PyObjectBase::__getattro(PyObject * obj, PyObject *attro)
299
{
300
    const char *attr{};
301
    attr = PyUnicode_AsUTF8(attro);
302

303
    // For the __class__ attribute get it directly as with
304
    // ExtensionContainerPy::getCustomAttributes we may get
305
    // the wrong type object (#0003311)
306
    if (streq(attr, "__class__")) {
307
        PyObject* res = PyObject_GenericGetAttr(obj, attro);
308
        if (res) {
309
            return res;
310
        }
311
    }
312

313
    // This should be the entry in Type
314
    PyObjectBase* pyObj = static_cast<PyObjectBase*>(obj);
315
    if (!pyObj->isValid()){
316
        PyErr_Format(PyExc_ReferenceError, "Cannot access attribute '%s' of deleted object", attr);
317
        return nullptr;
318
    }
319

320
#ifdef ATTR_TRACKING
321
    // If an attribute references this as parent then reset it (bug #0002902)
322
    PyObject* cur = pyObj->getTrackedAttribute(attr);
323
    if (cur) {
324
        if (PyObject_TypeCheck(cur, &(PyObjectBase::Type))) {
325
            PyObjectBase* base = static_cast<PyObjectBase*>(cur);
326
            base->resetAttribute();
327
            pyObj->untrackAttribute(attr);
328
        }
329
    }
330
#endif
331

332
    PyObject* value = pyObj->_getattr(attr);
333
#ifdef ATTR_TRACKING
334
    if (value && PyObject_TypeCheck(value, &(PyObjectBase::Type))) {
335
        if (!static_cast<PyObjectBase*>(value)->isConst() &&
336
            !static_cast<PyObjectBase*>(value)->isNotTracking()) {
337
            static_cast<PyObjectBase*>(value)->setAttributeOf(attr, pyObj);
338
            pyObj->trackAttribute(attr, value);
339
        }
340
    }
341
    else
342
#endif
343
    if (value && PyCFunction_Check(value)) {
344
        // ExtensionContainerPy::initialization() transfers the methods of an
345
        // extension object by creating PyCFunction objects.
346
        // At this point no 'self' object is passed but is handled and determined
347
        // in ExtensionContainerPy::getCustomAttributes().
348
        // So, if we come through this section then it's an indication that
349
        // something is wrong with the Python types. For example, a C++ class
350
        // that adds an extension uses the same Python type as a wrapper than
351
        // another C++ class without this extension.
352
        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
353
        PyCFunctionObject* cfunc = reinterpret_cast<PyCFunctionObject*>(value);
354
        if (!cfunc->m_self) {
355
            Py_DECREF(cfunc);
356
            value = nullptr;
357
            PyErr_Format(PyExc_AttributeError, "<no object bound to built-in method %s>", attr);
358
        }
359
    }
360

361
    return value;
362
}
363

364
int PyObjectBase::__setattro(PyObject *obj, PyObject *attro, PyObject *value)
365
{
366
    const char *attr{};
367
    attr = PyUnicode_AsUTF8(attro);
368

369
    //Hint: In general we don't allow to delete attributes (i.e. value=0). However, if we want to allow
370
    //we must check then in _setattr() of all subclasses whether value is 0.
371
    if (!value) {
372
        PyErr_Format(PyExc_AttributeError, "Cannot delete attribute: '%s'", attr);
373
        return -1;
374
    }
375
    if (!static_cast<PyObjectBase*>(obj)->isValid()){
376
        PyErr_Format(PyExc_ReferenceError, "Cannot access attribute '%s' of deleted object", attr);
377
        return -1;
378
    }
379

380
#ifdef ATTR_TRACKING
381
    // If an attribute references this as parent then reset it
382
    // before setting the new attribute
383
    PyObject* cur = static_cast<PyObjectBase*>(obj)->getTrackedAttribute(attr);
384
    if (cur) {
385
        if (PyObject_TypeCheck(cur, &(PyObjectBase::Type))) {
386
            PyObjectBase* base = static_cast<PyObjectBase*>(cur);
387
            base->resetAttribute();
388
            static_cast<PyObjectBase*>(obj)->untrackAttribute(attr);
389
        }
390
    }
391
#endif
392

393
    int ret = static_cast<PyObjectBase*>(obj)->_setattr(attr, value);
394
#ifdef ATTR_TRACKING
395
    if (ret == 0) {
396
        static_cast<PyObjectBase*>(obj)->startNotify();
397
    }
398
#endif
399
    return ret;
400
}
401

402
/*------------------------------
403
 * PyObjectBase attributes	-- attributes
404
------------------------------*/
405
PyObject *PyObjectBase::_getattr(const char *attr)
406
{
407
    if (streq(attr, "__class__")) {
408
        // Note: We must return the type object here,
409
        // so that our own types feel as really Python objects
410
        Py_INCREF(Py_TYPE(this));
411
        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
412
        return reinterpret_cast<PyObject *>(Py_TYPE(this));
413
    }
414
    if (streq(attr, "__members__")) {
415
        // Use __dict__ instead as __members__ is deprecated
416
        return nullptr;
417
    }
418
    if (streq(attr,"__dict__")) {
419
        // Return the default dict
420
        PyTypeObject *tp = Py_TYPE(this);
421
        Py_XINCREF(tp->tp_dict);
422
        return tp->tp_dict;
423
    }
424
    if (streq(attr,"softspace")) {
425
        // Internal Python stuff
426
        return nullptr;
427
    }
428
    // As fallback solution use Python's default method to get generic attributes
429
    PyObject *w{}, *res{};
430
    w = PyUnicode_InternFromString(attr);
431
    if (w) {
432
        res = PyObject_GenericGetAttr(this, w);
433
        Py_XDECREF(w);
434
        return res;
435
    }
436
    // Throw an exception for unknown attributes
437
    PyTypeObject *tp = Py_TYPE(this);
438
    PyErr_Format(PyExc_AttributeError, "%.50s instance has no attribute '%.400s'", tp->tp_name, attr);
439
    return nullptr;
440
}
441

442
int PyObjectBase::_setattr(const char *attr, PyObject *value)
443
{
444
    if (streq(attr,"softspace")) {
445
        return -1; // filter out softspace
446
    }
447
    PyObject *w{};
448
    // As fallback solution use Python's default method to get generic attributes
449
    w = PyUnicode_InternFromString(attr); // new reference
450
    if (w) {
451
        // call methods from tp_getset if defined
452
        int res = PyObject_GenericSetAttr(this, w, value);
453
        Py_DECREF(w);
454
        return res;
455
    }
456
    // Throw an exception for unknown attributes
457
    PyTypeObject *tp = Py_TYPE(this);
458
    PyErr_Format(PyExc_AttributeError, "%.50s instance has no attribute '%.400s'", tp->tp_name, attr);
459
    return -1;
460
}
461

462
/*------------------------------
463
 * PyObjectBase repr    representations
464
------------------------------*/
465
PyObject *PyObjectBase::_repr()
466
{
467
    std::stringstream a;
468
    a << "<base object at " << _pcTwinPointer << ">";
469
# ifdef FCDebug
470
    Console().Log("PyObjectBase::_repr() not overwritten representation!");
471
# endif
472
    return Py_BuildValue("s", a.str().c_str());
473
}
474

475
// Tracking functions
476

477
void PyObjectBase::resetAttribute()
478
{
479
    if (attrDict) {
480
        // This is the attribute name to the parent structure
481
        // which we search for in the dict
482
        PyObject* key1 = PyBytes_FromString("__attribute_of_parent__");
483
        PyObject* key2 = PyBytes_FromString("__instance_of_parent__");
484
        PyObject* attr = PyDict_GetItem(attrDict, key1);
485
        PyObject* inst = PyDict_GetItem(attrDict, key2);
486
        if (attr) {
487
            PyDict_DelItem(attrDict, key1);
488
        }
489
        if (inst) {
490
            PyDict_DelItem(attrDict, key2);
491
        }
492
        Py_DECREF(key1);
493
        Py_DECREF(key2);
494
    }
495
}
496

497
void PyObjectBase::setAttributeOf(const char* attr, PyObject* par)
498
{
499
    if (!attrDict) {
500
        attrDict = PyDict_New();
501
    }
502

503
    PyObject* key1 = PyBytes_FromString("__attribute_of_parent__");
504
    PyObject* key2 = PyBytes_FromString("__instance_of_parent__");
505
    PyObject* attro = PyUnicode_FromString(attr);
506
    PyDict_SetItem(attrDict, key1, attro);
507
    PyDict_SetItem(attrDict, key2, par);
508
    Py_DECREF(attro);
509
    Py_DECREF(key1);
510
    Py_DECREF(key2);
511
}
512

513
void PyObjectBase::startNotify()
514
{
515
    if (!shouldNotify()) {
516
        return;
517
    }
518

519
    if (attrDict) {
520
        // This is the attribute name to the parent structure
521
        // which we search for in the dict
522
        PyObject* key1 = PyBytes_FromString("__attribute_of_parent__");
523
        PyObject* key2 = PyBytes_FromString("__instance_of_parent__");
524
        PyObject* attr = PyDict_GetItem(attrDict, key1);
525
        PyObject* parent = PyDict_GetItem(attrDict, key2);
526
        if (attr && parent) {
527
            // Inside __setattr of the parent structure the 'attr'
528
            // is being removed from the dict and thus its reference
529
            // counter will be decremented. To avoid to be deleted we
530
            // must tmp. increment it and afterwards decrement it again.
531
            Py_INCREF(parent);
532
            Py_INCREF(attr);
533
            Py_INCREF(this);
534

535
            __setattro(parent, attr, this);
536

537
            Py_DECREF(parent); // might be destroyed now
538
            Py_DECREF(attr); // might be destroyed now
539
            Py_DECREF(this); // might be destroyed now
540

541
            if (PyErr_Occurred()) {
542
                PyErr_Clear();
543
            }
544
        }
545
        Py_DECREF(key1);
546
        Py_DECREF(key2);
547
    }
548
}
549

550
PyObject* PyObjectBase::getTrackedAttribute(const char* attr)
551
{
552
    PyObject* obj = nullptr;
553
    if (attrDict) {
554
        obj = PyDict_GetItemString(attrDict, attr);
555
        obj = getFromWeakRef(obj);
556
    }
557
    return obj;
558
}
559

560
void PyObjectBase::trackAttribute(const char* attr, PyObject* obj)
561
{
562
    if (!attrDict) {
563
        attrDict = PyDict_New();
564
    }
565

566
    PyObject* obj_ref = createWeakRef(static_cast<PyObjectBase*>(obj));
567
    if (obj_ref) {
568
        PyDict_SetItemString(attrDict, attr, obj_ref);
569
    }
570
}
571

572
void PyObjectBase::untrackAttribute(const char* attr)
573
{
574
    if (attrDict) {
575
        PyDict_DelItemString(attrDict, attr);
576
    }
577
}
578

579
void PyObjectBase::clearAttributes()
580
{
581
    if (attrDict) {
582
        PyDict_Clear(attrDict);
583
    }
584
}
585
// clang-format on
586

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.