private-attribute-cpp 2.1.4__tar.gz → 2.1.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {private_attribute_cpp-2.1.4/private_attribute_cpp.egg-info → private_attribute_cpp-2.1.5}/PKG-INFO +1 -1
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/private_attribute.cpp +207 -166
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5/private_attribute_cpp.egg-info}/PKG-INFO +1 -1
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/private_attribute_cpp.egg-info/SOURCES.txt +1 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/setup.py +1 -1
- private_attribute_cpp-2.1.5/tests/test_gc_collect.py +145 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/LICENSE +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/MANIFEST.in +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/README.md +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/picosha2.h +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/private_attribute.pyi +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/private_attribute_cpp.egg-info/dependency_links.txt +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/private_attribute_cpp.egg-info/not-zip-safe +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/private_attribute_cpp.egg-info/top_level.txt +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/setup.cfg +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/tests/test_attr_classify_owner.py +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/tests/test_class_private_attrs.py +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/tests/test_private_abc.py +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/tests/test_private_attrs.py +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/tests/test_private_func.py +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/tests/test_private_inheritance.py +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/tests/test_private_method.py +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/tests/test_private_wrap.py +0 -0
{private_attribute_cpp-2.1.4/private_attribute_cpp.egg-info → private_attribute_cpp-2.1.5}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: private_attribute_cpp
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.5
|
|
4
4
|
Summary: A Python package that provides a way to define private attributes in C++ implementation.
|
|
5
5
|
Home-page: https://github.com/Locked-chess-official/private_attribute_cpp
|
|
6
6
|
Author: HuangHaoHua
|
|
@@ -203,6 +203,7 @@ namespace {
|
|
|
203
203
|
};
|
|
204
204
|
static std::unordered_map<uintptr_t, std::unordered_map<uintptr_t, std::shared_ptr<std::shared_mutex>>>
|
|
205
205
|
all_object_mutex, all_type_subclass_mutex;
|
|
206
|
+
static std::mutex object_creatmutex_mutex, type_createmutex_mutex, type_parenttype_createmutex_mutex;
|
|
206
207
|
static std::unordered_map<uintptr_t, std::vector<uintptr_t>> all_type_parent_id;
|
|
207
208
|
// all type tp_getattro map
|
|
208
209
|
static std::unordered_map<uintptr_t, getattrofunc> all_type_getattro;
|
|
@@ -221,13 +222,64 @@ namespace {
|
|
|
221
222
|
static inquiry captured_subtype_clear = NULL;
|
|
222
223
|
// all type tp_finalizer map
|
|
223
224
|
static std::unordered_map<uintptr_t, destructor> all_type_finalize;
|
|
225
|
+
// all type tp_del map (old-style destructor; see PrivateAttr_tp_del)
|
|
226
|
+
static std::unordered_map<uintptr_t, destructor> all_type_del;
|
|
224
227
|
|
|
225
228
|
static std::shared_mutex all_register_new_metaclass_mutex;
|
|
226
229
|
static std::unordered_map<uintptr_t, PyObjectStorage> all_register_type_weak_ref;
|
|
230
|
+
static std::vector<PyObjectStorage> store_module_self;
|
|
227
231
|
};
|
|
228
232
|
};
|
|
229
233
|
|
|
230
|
-
static void
|
|
234
|
+
static inline void
|
|
235
|
+
object_create_mutex(uintptr_t final_id, uintptr_t obj_id) noexcept
|
|
236
|
+
{
|
|
237
|
+
// get object_creatmutex_mutex
|
|
238
|
+
std::lock_guard<std::mutex> lock(AllData::object_creatmutex_mutex);
|
|
239
|
+
if (::AllData::all_object_attr[final_id].find(obj_id) == ::AllData::all_object_attr[final_id].end()) {
|
|
240
|
+
::AllData::all_object_attr[final_id][obj_id] = {};
|
|
241
|
+
}
|
|
242
|
+
if (::AllData::all_object_mutex.find(final_id) == ::AllData::all_object_mutex.end()) {
|
|
243
|
+
::AllData::all_object_mutex[final_id] = {};
|
|
244
|
+
}
|
|
245
|
+
if (::AllData::all_object_mutex[final_id].find(obj_id) == ::AllData::all_object_mutex[final_id].end()) {
|
|
246
|
+
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
247
|
+
::AllData::all_object_mutex[final_id][obj_id] = lock;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
static inline void
|
|
252
|
+
type_create_mutex(uintptr_t typ_id) noexcept {
|
|
253
|
+
std::lock_guard<std::mutex> lock(AllData::type_createmutex_mutex);
|
|
254
|
+
if (::AllData::type_attr_dict.find(typ_id) == ::AllData::type_attr_dict.end()) {
|
|
255
|
+
::AllData::type_attr_dict[typ_id] = {};
|
|
256
|
+
}
|
|
257
|
+
if (::AllData::all_type_mutex.find(typ_id) == ::AllData::all_type_mutex.end()) {
|
|
258
|
+
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
259
|
+
::AllData::all_type_mutex[typ_id] = lock;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
static inline void
|
|
264
|
+
type_parenttype_create_mutex(uintptr_t final_id, uintptr_t typ_id) noexcept
|
|
265
|
+
{
|
|
266
|
+
std::lock_guard<std::mutex> lock(AllData::type_parenttype_createmutex_mutex);
|
|
267
|
+
if (::AllData::all_type_subclass_attr.find(final_id) == ::AllData::all_type_subclass_attr.end()) {
|
|
268
|
+
::AllData::all_type_subclass_attr[final_id] = {};
|
|
269
|
+
}
|
|
270
|
+
if (::AllData::all_type_subclass_attr[final_id].find(typ_id) == ::AllData::all_type_subclass_attr[final_id].end()) {
|
|
271
|
+
::AllData::all_type_subclass_attr[final_id][typ_id] = {};
|
|
272
|
+
}
|
|
273
|
+
if (::AllData::all_type_subclass_mutex.find(final_id) == ::AllData::all_type_subclass_mutex.end()) {
|
|
274
|
+
::AllData::all_type_subclass_mutex[final_id] = {};
|
|
275
|
+
}
|
|
276
|
+
if (::AllData::all_type_subclass_mutex[final_id].find(typ_id) == ::AllData::all_type_subclass_mutex[final_id].end()) {
|
|
277
|
+
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
278
|
+
::AllData::all_type_subclass_mutex[final_id][typ_id] = lock;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
static inline void
|
|
231
283
|
clean_all_storages() noexcept
|
|
232
284
|
{
|
|
233
285
|
AllData::cache.clear();
|
|
@@ -248,7 +300,9 @@ clean_all_storages() noexcept
|
|
|
248
300
|
AllData::all_type_traverse.clear();
|
|
249
301
|
AllData::all_type_clear.clear();
|
|
250
302
|
AllData::all_type_finalize.clear();
|
|
303
|
+
AllData::all_type_del.clear();
|
|
251
304
|
AllData::all_register_type_weak_ref.clear();
|
|
305
|
+
AllData::store_module_self.clear();
|
|
252
306
|
}
|
|
253
307
|
|
|
254
308
|
struct FinalObject
|
|
@@ -563,16 +617,7 @@ id_getattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyOb
|
|
|
563
617
|
PyErr_SetString(PyExc_TypeError, "type not found");
|
|
564
618
|
return NULL;
|
|
565
619
|
}
|
|
566
|
-
|
|
567
|
-
::AllData::all_object_attr[final_id][obj_id] = {};
|
|
568
|
-
}
|
|
569
|
-
if (::AllData::all_object_mutex.find(final_id) == ::AllData::all_object_mutex.end()) {
|
|
570
|
-
::AllData::all_object_mutex[final_id] = {};
|
|
571
|
-
}
|
|
572
|
-
if (::AllData::all_object_mutex[final_id].find(obj_id) == ::AllData::all_object_mutex[final_id].end()) {
|
|
573
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
574
|
-
::AllData::all_object_mutex[final_id][obj_id] = lock;
|
|
575
|
-
}
|
|
620
|
+
object_create_mutex(final_id, obj_id);
|
|
576
621
|
PyObject* result = NULL;
|
|
577
622
|
if (final_object.status != -1) {
|
|
578
623
|
result = final_object.result;
|
|
@@ -699,16 +744,7 @@ id_setattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyOb
|
|
|
699
744
|
PyErr_SetString(PyExc_TypeError, "type not found");
|
|
700
745
|
return -1;
|
|
701
746
|
}
|
|
702
|
-
|
|
703
|
-
::AllData::all_object_attr[final_id][obj_id] = {};
|
|
704
|
-
}
|
|
705
|
-
if (::AllData::all_object_mutex.find(final_id) == ::AllData::all_object_mutex.end()) {
|
|
706
|
-
::AllData::all_object_mutex[final_id] = {};
|
|
707
|
-
}
|
|
708
|
-
if (::AllData::all_object_mutex[final_id].find(obj_id) == ::AllData::all_object_mutex[final_id].end()) {
|
|
709
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
710
|
-
::AllData::all_object_mutex[final_id][obj_id] = lock;
|
|
711
|
-
}
|
|
747
|
+
object_create_mutex(final_id, obj_id);
|
|
712
748
|
// first: call __set__ method
|
|
713
749
|
PyObject* result = NULL;
|
|
714
750
|
if (final_object.status != -1) {
|
|
@@ -764,13 +800,7 @@ type_setattr(PyObject* typ, const std::string& attr_name, PyObject* value) noexc
|
|
|
764
800
|
return -1;
|
|
765
801
|
}
|
|
766
802
|
if (final_id == typ_id) {
|
|
767
|
-
|
|
768
|
-
::AllData::type_attr_dict[typ_id] = {};
|
|
769
|
-
}
|
|
770
|
-
if (::AllData::all_type_mutex.find(typ_id) == ::AllData::all_type_mutex.end()) {
|
|
771
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
772
|
-
::AllData::all_type_mutex[typ_id] = lock;
|
|
773
|
-
}
|
|
803
|
+
type_create_mutex(typ_id);
|
|
774
804
|
{
|
|
775
805
|
std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[typ_id]);
|
|
776
806
|
::AllData::type_attr_dict[typ_id][final_key] = value;
|
|
@@ -779,19 +809,7 @@ type_setattr(PyObject* typ, const std::string& attr_name, PyObject* value) noexc
|
|
|
779
809
|
} else {
|
|
780
810
|
// The accessed class is a subclass that stores its own value for a
|
|
781
811
|
// parent's private name separately: all_type_subclass_attr[final_id][typ_id].
|
|
782
|
-
|
|
783
|
-
::AllData::all_type_subclass_attr[final_id] = {};
|
|
784
|
-
}
|
|
785
|
-
if (::AllData::all_type_subclass_attr[final_id].find(typ_id) == ::AllData::all_type_subclass_attr[final_id].end()) {
|
|
786
|
-
::AllData::all_type_subclass_attr[final_id][typ_id] = {};
|
|
787
|
-
}
|
|
788
|
-
if (::AllData::all_type_subclass_mutex.find(final_id) == ::AllData::all_type_subclass_mutex.end()) {
|
|
789
|
-
::AllData::all_type_subclass_mutex[final_id] = {};
|
|
790
|
-
}
|
|
791
|
-
if (::AllData::all_type_subclass_mutex[final_id].find(typ_id) == ::AllData::all_type_subclass_mutex[final_id].end()) {
|
|
792
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
793
|
-
::AllData::all_type_subclass_mutex[final_id][typ_id] = lock;
|
|
794
|
-
}
|
|
812
|
+
type_parenttype_create_mutex(final_id, typ_id);
|
|
795
813
|
{
|
|
796
814
|
std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_subclass_mutex[final_id][typ_id]);
|
|
797
815
|
::AllData::all_type_subclass_attr[final_id][typ_id][final_key] = value;
|
|
@@ -830,16 +848,7 @@ id_delattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyOb
|
|
|
830
848
|
PyErr_SetString(PyExc_TypeError, "type not found");
|
|
831
849
|
return -1;
|
|
832
850
|
}
|
|
833
|
-
|
|
834
|
-
::AllData::all_object_attr[final_id][obj_id] = {};
|
|
835
|
-
}
|
|
836
|
-
if (::AllData::all_object_mutex.find(final_id) == ::AllData::all_object_mutex.end()) {
|
|
837
|
-
::AllData::all_object_mutex[final_id] = {};
|
|
838
|
-
}
|
|
839
|
-
if (::AllData::all_object_mutex[final_id].find(obj_id) == ::AllData::all_object_mutex[final_id].end()) {
|
|
840
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
841
|
-
::AllData::all_object_mutex[final_id][obj_id] = lock;
|
|
842
|
-
}
|
|
851
|
+
object_create_mutex(final_id, obj_id);
|
|
843
852
|
// first: find attribute on type to find "__delete__"
|
|
844
853
|
PyObject* result = NULL;
|
|
845
854
|
if (final_object.status == 0) {
|
|
@@ -897,13 +906,7 @@ type_delattr(PyObject* typ, const std::string& attr_name) noexcept
|
|
|
897
906
|
return -1;
|
|
898
907
|
}
|
|
899
908
|
if (typ_id == final_id) {
|
|
900
|
-
|
|
901
|
-
::AllData::type_attr_dict[typ_id] = {};
|
|
902
|
-
}
|
|
903
|
-
if (::AllData::all_type_mutex.find(typ_id) == ::AllData::all_type_mutex.end()) {
|
|
904
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
905
|
-
::AllData::all_type_mutex[typ_id] = lock;
|
|
906
|
-
}
|
|
909
|
+
type_create_mutex(typ_id);
|
|
907
910
|
std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[typ_id]);
|
|
908
911
|
if (::AllData::type_attr_dict[typ_id].find(final_key) == ::AllData::type_attr_dict[typ_id].end()) {
|
|
909
912
|
const char* type_name = get_name_from_tp_name((PyTypeObject*)typ);
|
|
@@ -915,19 +918,7 @@ type_delattr(PyObject* typ, const std::string& attr_name) noexcept
|
|
|
915
918
|
}
|
|
916
919
|
::AllData::type_attr_dict[typ_id].erase(final_key);
|
|
917
920
|
} else {
|
|
918
|
-
|
|
919
|
-
::AllData::all_type_subclass_attr[final_id] = {};
|
|
920
|
-
}
|
|
921
|
-
if (::AllData::all_type_subclass_attr[final_id].find(typ_id) == ::AllData::all_type_subclass_attr[final_id].end()) {
|
|
922
|
-
::AllData::all_type_subclass_attr[final_id][typ_id] = {};
|
|
923
|
-
}
|
|
924
|
-
if (::AllData::all_type_subclass_mutex.find(final_id) == ::AllData::all_type_subclass_mutex.end()) {
|
|
925
|
-
::AllData::all_type_subclass_mutex[final_id] = {};
|
|
926
|
-
}
|
|
927
|
-
if (::AllData::all_type_subclass_mutex[final_id].find(typ_id) == ::AllData::all_type_subclass_mutex[final_id].end()) {
|
|
928
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
929
|
-
::AllData::all_type_subclass_mutex[final_id][typ_id] = lock;
|
|
930
|
-
}
|
|
921
|
+
type_parenttype_create_mutex(final_id, typ_id);
|
|
931
922
|
std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_subclass_mutex[final_id][typ_id]);
|
|
932
923
|
if (::AllData::all_type_subclass_attr[final_id][typ_id].find(final_key) == ::AllData::all_type_subclass_attr[final_id][typ_id].end()) {
|
|
933
924
|
const char* type_name = get_name_from_tp_name((PyTypeObject*)typ);
|
|
@@ -1364,6 +1355,8 @@ typedef struct {
|
|
|
1364
1355
|
static void PrivateAttr_object_init_private_dict(uintptr_t obj_id, uintptr_t type_id) noexcept;
|
|
1365
1356
|
static int PrivateAttr_tp_setattro(PyObject* self, PyObject* name, PyObject* value) noexcept;
|
|
1366
1357
|
static void PrivateAttr_tp_finalize(PyObject* self) noexcept;
|
|
1358
|
+
static void PrivateAttr_tp_del(PyObject* self) noexcept;
|
|
1359
|
+
static void clear_object_related(uintptr_t id_self, uintptr_t typ_id) noexcept;
|
|
1367
1360
|
|
|
1368
1361
|
static PyObject*
|
|
1369
1362
|
PrivateAttr_tp_getattro(PyObject* self, PyObject* name) noexcept
|
|
@@ -1444,55 +1437,53 @@ PrivateAttr_tp_setattro(PyObject* self, PyObject* name, PyObject* value) noexcep
|
|
|
1444
1437
|
static void
|
|
1445
1438
|
PrivateAttr_tp_finalize(PyObject* self) noexcept
|
|
1446
1439
|
{
|
|
1447
|
-
uintptr_t id_self = (uintptr_t)self;
|
|
1448
1440
|
PyTypeObject* typ = Py_TYPE(self);
|
|
1449
1441
|
uintptr_t typ_id = (uintptr_t)typ;
|
|
1450
|
-
Py_ssize_t original_ref = Py_REFCNT(self);
|
|
1451
1442
|
if (::AllData::all_type_finalize.find(typ_id) != ::AllData::all_type_finalize.end()){
|
|
1452
1443
|
if (::AllData::all_type_finalize[typ_id]) {
|
|
1453
1444
|
::AllData::all_type_finalize[typ_id](self);
|
|
1454
1445
|
ensure_tp(typ);
|
|
1455
1446
|
}
|
|
1456
1447
|
}
|
|
1457
|
-
|
|
1448
|
+
// Per-object cleanup moved to PrivateAttr_tp_del (see below).
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// ========================================================================
|
|
1452
|
+
// tp_del : per-object cleanup for private attributes.
|
|
1453
|
+
//
|
|
1454
|
+
// subtype_dealloc calls tp_del in BOTH dealloc branches (non-GC typeobject.c
|
|
1455
|
+
// ~2737 and GC ~2814) before clear_slots / basedealloc, and explicitly checks
|
|
1456
|
+
// Py_REFCNT(self) > 0 to detect resurrection. This is the natural hook for
|
|
1457
|
+
// clearing ::AllData per-object entries:
|
|
1458
|
+
// - it is reached on every object death path (refcount drop AND cyclic GC),
|
|
1459
|
+
// - the resurrection check is explicit and readable here (Py_REFCNT > 0),
|
|
1460
|
+
// - no dependency on tp_finalize / weakref / slots / __dict__.
|
|
1461
|
+
// We keep PrivateAttr_tp_finalize (so user __del__ / tp_finalize still runs
|
|
1462
|
+
// via PyObject_CallFinalizerFromDealloc) but it no longer clears storage.
|
|
1463
|
+
// ========================================================================
|
|
1464
|
+
static void
|
|
1465
|
+
PrivateAttr_tp_del(PyObject* self) noexcept
|
|
1466
|
+
{
|
|
1467
|
+
uintptr_t id_self = (uintptr_t)self;
|
|
1468
|
+
PyTypeObject* typ = Py_TYPE(self);
|
|
1469
|
+
uintptr_t typ_id = (uintptr_t)typ;
|
|
1470
|
+
|
|
1471
|
+
// If a user tp_finalize / tp_del resurrected the object, subtype_dealloc
|
|
1472
|
+
// returns without freeing, so we must NOT clear the private storage yet.
|
|
1473
|
+
if (Py_REFCNT(self) > 0) {
|
|
1458
1474
|
return;
|
|
1459
1475
|
}
|
|
1460
|
-
std::vector<uintptr_t> parent_ids;
|
|
1461
|
-
if (::AllData::all_type_parent_id.find(typ_id) != ::AllData::all_type_parent_id.end()){
|
|
1462
|
-
parent_ids = ::AllData::all_type_parent_id[typ_id];
|
|
1463
|
-
}
|
|
1464
1476
|
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
if (::AllData::all_object_mutex.find(typ_id) != ::AllData::all_object_mutex.end()){
|
|
1474
|
-
auto& all_object_mutex = ::AllData::all_object_mutex[typ_id];
|
|
1475
|
-
if (all_object_mutex.find(id_self) != all_object_mutex.end()){
|
|
1476
|
-
all_object_mutex.erase(id_self);
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
// second: clear the above in parent types
|
|
1480
|
-
for (auto& parent_id : parent_ids){
|
|
1481
|
-
if (::AllData::all_object_attr.find(parent_id) != ::AllData::all_object_attr.end()){
|
|
1482
|
-
auto& all_object_attr = ::AllData::all_object_attr[parent_id];
|
|
1483
|
-
if (all_object_attr.find(id_self) != all_object_attr.end()){
|
|
1484
|
-
all_object_attr.erase(id_self);
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
if (::AllData::all_object_mutex.find(parent_id) != ::AllData::all_object_mutex.end()){
|
|
1488
|
-
auto& all_object_mutex = ::AllData::all_object_mutex[parent_id];
|
|
1489
|
-
if (all_object_mutex.find(id_self) != all_object_mutex.end()){
|
|
1490
|
-
all_object_mutex.erase(id_self);
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1477
|
+
// Chain to the original tp_del if there is one (we are called instead of
|
|
1478
|
+
// the original, so we must invoke it to keep old-style destructors working).
|
|
1479
|
+
if (::AllData::all_type_del.find(typ_id) != ::AllData::all_type_del.end()) {
|
|
1480
|
+
destructor orig = ::AllData::all_type_del[typ_id];
|
|
1481
|
+
if (orig && orig != PrivateAttr_tp_del) {
|
|
1482
|
+
orig(self);
|
|
1493
1483
|
}
|
|
1494
|
-
clear_obj(id_self);
|
|
1495
1484
|
}
|
|
1485
|
+
|
|
1486
|
+
clear_object_related(id_self, typ_id);
|
|
1496
1487
|
}
|
|
1497
1488
|
|
|
1498
1489
|
// shared helper for clearing object-related data (used by tp_clear and tp_finalize)
|
|
@@ -1689,15 +1680,14 @@ PrivateAttrType_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept
|
|
|
1689
1680
|
}
|
|
1690
1681
|
PrivateAttrRecursionGuard guard((uintptr_t)self);
|
|
1691
1682
|
|
|
1692
|
-
// Call
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
traverseproc orig = get_type_need_tp_traverse(typ);
|
|
1683
|
+
// Call the real CPython type_traverse (PyType_Type.tp_traverse) so the
|
|
1684
|
+
// class's own hard self-cycle (tp_dict <-> tp_mro <-> tp_bases) is visible
|
|
1685
|
+
// to the cyclic GC. Walking the metaclass base chain here would land on the
|
|
1686
|
+
// instance-level delegating defaults (subtype_traverse / PrivateAttr_tp_traverse)
|
|
1687
|
+
// that re-enter our guard and return 0, leaving the class's internal cycle
|
|
1688
|
+
// invisible to the collector and the class uncollectable.
|
|
1689
|
+
{
|
|
1690
|
+
traverseproc orig = PyType_Type.tp_traverse; // == type_traverse
|
|
1701
1691
|
if (orig) {
|
|
1702
1692
|
int res = orig(self, visit, arg);
|
|
1703
1693
|
if (res) return res;
|
|
@@ -1779,13 +1769,18 @@ PrivateAttrType_tp_clear(PyObject* self) noexcept
|
|
|
1779
1769
|
}
|
|
1780
1770
|
PrivateAttrRecursionGuard guard((uintptr_t)self);
|
|
1781
1771
|
|
|
1782
|
-
// Call
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1772
|
+
// Call the real CPython type_clear (PyType_Type.tp_clear) so the class's
|
|
1773
|
+
// hard self-cycle (tp_dict / tp_mro / ht_module) is actually broken when the
|
|
1774
|
+
// cyclic GC decides this class is garbage. The walker path would resolve to
|
|
1775
|
+
// the instance-level delegating defaults (subtype_clear / PrivateAttr_tp_clear)
|
|
1776
|
+
// that re-enter our guard and return 0, leaving the cycle intact and the
|
|
1777
|
+
// class permanently stuck in gc.garbage.
|
|
1778
|
+
{
|
|
1779
|
+
inquiry orig = PyType_Type.tp_clear; // == type_clear
|
|
1780
|
+
if (orig) {
|
|
1781
|
+
int result = orig(self);
|
|
1782
|
+
if (result) return result;
|
|
1783
|
+
}
|
|
1789
1784
|
}
|
|
1790
1785
|
|
|
1791
1786
|
// Clear AllData entries associated with this type. PyObjectStorage destructor
|
|
@@ -1866,10 +1861,11 @@ static PyTypeObject PrivateAttrType = {
|
|
|
1866
1861
|
(getattrofunc)PrivateAttrType_getattr, // tp_getattro
|
|
1867
1862
|
(setattrofunc)PrivateAttrType_setattr, // tp_setattro
|
|
1868
1863
|
0, // tp_as_buffer
|
|
1869
|
-
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
|
1864
|
+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
|
1865
|
+
| Py_TPFLAGS_HAVE_GC, // tp_flags
|
|
1870
1866
|
"metaclass for private attributes", // tp_doc
|
|
1871
|
-
|
|
1872
|
-
|
|
1867
|
+
PrivateAttrType_tp_traverse, // tp_travers
|
|
1868
|
+
PrivateAttrType_tp_clear, // tp_clear
|
|
1873
1869
|
0, // tp_richcompare
|
|
1874
1870
|
0, // tp_weaklistoffset
|
|
1875
1871
|
0, // tp_iter
|
|
@@ -1939,13 +1935,7 @@ type_get_final_attr(uintptr_t type_id, const std::string& name) noexcept
|
|
|
1939
1935
|
} else {
|
|
1940
1936
|
key = default_random_string(type_id, name);
|
|
1941
1937
|
}
|
|
1942
|
-
|
|
1943
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
1944
|
-
::AllData::all_type_mutex[type_id] = lock;
|
|
1945
|
-
}
|
|
1946
|
-
if (::AllData::type_attr_dict.find(type_id) == ::AllData::type_attr_dict.end()) {
|
|
1947
|
-
::AllData::type_attr_dict[type_id] = {};
|
|
1948
|
-
}
|
|
1938
|
+
type_create_mutex(type_id);
|
|
1949
1939
|
std::shared_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[type_id]);
|
|
1950
1940
|
auto& item_set = ::AllData::type_attr_dict[type_id];
|
|
1951
1941
|
if (item_set.find(key) != item_set.end()) {
|
|
@@ -1980,13 +1970,7 @@ type_get_final_attr(uintptr_t type_id, const std::string& name) noexcept
|
|
|
1980
1970
|
} else {
|
|
1981
1971
|
key = default_random_string(now_visited_id, name);
|
|
1982
1972
|
}
|
|
1983
|
-
|
|
1984
|
-
::AllData::all_type_subclass_mutex[parent_id] = {};
|
|
1985
|
-
}
|
|
1986
|
-
if (::AllData::all_type_subclass_mutex[parent_id].find(now_visited_id) == ::AllData::all_type_subclass_mutex[parent_id].end()) {
|
|
1987
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
1988
|
-
::AllData::all_type_subclass_mutex[parent_id][now_visited_id] = lock;
|
|
1989
|
-
}
|
|
1973
|
+
type_parenttype_create_mutex(parent_id, now_visited_id);
|
|
1990
1974
|
std::shared_lock<std::shared_mutex> lock(*::AllData::all_type_subclass_mutex[parent_id][now_visited_id]);
|
|
1991
1975
|
if (now_mro_dict[now_visited_id].find(key) != now_mro_dict[now_visited_id].end()) {
|
|
1992
1976
|
PyObject* obj = now_mro_dict[now_visited_id][key];
|
|
@@ -2010,17 +1994,12 @@ type_get_final_attr(uintptr_t type_id, const std::string& name) noexcept
|
|
|
2010
1994
|
} else {
|
|
2011
1995
|
key = default_random_string(parent_id, name);
|
|
2012
1996
|
}
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
std::shared_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[parent_id]);
|
|
2020
|
-
if (item_set.find(key) != item_set.end()) {
|
|
2021
|
-
PyObject* obj = item_set[key];
|
|
2022
|
-
return obj;
|
|
2023
|
-
}
|
|
1997
|
+
type_create_mutex(parent_id);
|
|
1998
|
+
auto& item_set = ::AllData::type_attr_dict[parent_id];
|
|
1999
|
+
std::shared_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[parent_id]);
|
|
2000
|
+
if (item_set.find(key) != item_set.end()) {
|
|
2001
|
+
PyObject* obj = item_set[key];
|
|
2002
|
+
return obj;
|
|
2024
2003
|
}
|
|
2025
2004
|
}
|
|
2026
2005
|
}
|
|
@@ -2497,7 +2476,10 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
|
|
|
2497
2476
|
need_value = value;
|
|
2498
2477
|
}
|
|
2499
2478
|
if (data.private_attrs_vector_string.find(attr_name) != data.private_attrs_vector_string.end()) {
|
|
2500
|
-
|
|
2479
|
+
// PyObjectStorage assignment INCREFs the value (RAII); the
|
|
2480
|
+
// reference is released when need_remove_itself is cleared
|
|
2481
|
+
// (PrivateAttrCreationData::clear). No manual INCREF here,
|
|
2482
|
+
// otherwise the value leaks (+1) when the class is destroyed.
|
|
2501
2483
|
data.need_remove_itself[attr_name] = need_value;
|
|
2502
2484
|
PyDict_DelItem(data.attrs_copy, key);
|
|
2503
2485
|
continue;
|
|
@@ -2510,7 +2492,7 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
|
|
|
2510
2492
|
if (data.need_remove_subclass.find(need_remove_subclass_id) == data.need_remove_subclass.end()) {
|
|
2511
2493
|
data.need_remove_subclass[need_remove_subclass_id] = {};
|
|
2512
2494
|
}
|
|
2513
|
-
|
|
2495
|
+
// PyObjectStorage assignment INCREFs (RAII); no manual INCREF.
|
|
2514
2496
|
data.need_remove_subclass[need_remove_subclass_id][attr_name] = need_value;
|
|
2515
2497
|
PyDict_DelItem(data.attrs_copy, key);
|
|
2516
2498
|
continue;
|
|
@@ -2568,17 +2550,18 @@ static setattrofunc get_need_tp_setattro(PyTypeObject* cls) noexcept;
|
|
|
2568
2550
|
static traverseproc get_need_tp_traverse(PyTypeObject* cls) noexcept;
|
|
2569
2551
|
static inquiry get_need_tp_clear(PyTypeObject* cls) noexcept;
|
|
2570
2552
|
static destructor get_need_tp_finalize(PyTypeObject* cls) noexcept;
|
|
2553
|
+
static destructor get_need_tp_del(PyTypeObject* cls) noexcept;
|
|
2571
2554
|
|
|
2572
2555
|
// instance-level traverse/clear for types that use private attributes
|
|
2573
2556
|
static int PrivateAttr_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept;
|
|
2574
2557
|
static int PrivateAttr_tp_clear(PyObject* self) noexcept;
|
|
2558
|
+
static void PrivateAttr_tp_del(PyObject* self) noexcept;
|
|
2559
|
+
// shared helper to clear object related data (avoid double clear)
|
|
2560
|
+
static void clear_object_related(uintptr_t id_self, uintptr_t typ_id) noexcept;
|
|
2575
2561
|
// metaclass-level wrappers (if needed)
|
|
2576
2562
|
static int PrivateAttrType_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept;
|
|
2577
2563
|
static int PrivateAttrType_tp_clear(PyObject* self) noexcept;
|
|
2578
2564
|
|
|
2579
|
-
// shared helper to clear object related data (avoid double clear)
|
|
2580
|
-
static void clear_object_related(uintptr_t id_self, uintptr_t typ_id) noexcept;
|
|
2581
|
-
|
|
2582
2565
|
static void
|
|
2583
2566
|
ensure_tp(PyTypeObject* type_instance) noexcept
|
|
2584
2567
|
{
|
|
@@ -2673,6 +2656,24 @@ ensure_tp(PyTypeObject* type_instance) noexcept
|
|
|
2673
2656
|
}
|
|
2674
2657
|
}
|
|
2675
2658
|
}
|
|
2659
|
+
{
|
|
2660
|
+
if (type_instance->tp_del != PrivateAttr_tp_del) {
|
|
2661
|
+
if (!type_instance->tp_del && ::AllData::all_type_del.find(type_id) == ::AllData::all_type_del.end()) {
|
|
2662
|
+
::AllData::all_type_del[type_id] = get_need_tp_del(type_instance);
|
|
2663
|
+
} else {
|
|
2664
|
+
::AllData::all_type_del[type_id] = type_instance->tp_del;
|
|
2665
|
+
}
|
|
2666
|
+
type_instance->tp_del = PrivateAttr_tp_del;
|
|
2667
|
+
} else if (::AllData::all_type_del.find(type_id) == ::AllData::all_type_del.end()) {
|
|
2668
|
+
PyTypeObject* base = type_instance->tp_base;
|
|
2669
|
+
uintptr_t base_id = (uintptr_t)(base);
|
|
2670
|
+
if (::AllData::all_type_del.find(base_id) != ::AllData::all_type_del.end()) {
|
|
2671
|
+
::AllData::all_type_del[type_id] = ::AllData::all_type_del[base_id];
|
|
2672
|
+
} else if (base && base->tp_del && base->tp_del != PrivateAttr_tp_del) {
|
|
2673
|
+
::AllData::all_type_del[type_id] = base->tp_del;
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2676
2677
|
}
|
|
2677
2678
|
|
|
2678
2679
|
static void
|
|
@@ -3092,6 +3093,25 @@ get_need_tp_clear(PyTypeObject* cls) noexcept
|
|
|
3092
3093
|
return NULL;
|
|
3093
3094
|
}
|
|
3094
3095
|
|
|
3096
|
+
static destructor
|
|
3097
|
+
get_need_tp_del(PyTypeObject* cls) noexcept
|
|
3098
|
+
{
|
|
3099
|
+
PyTypeObject* base = cls->tp_base;
|
|
3100
|
+
while (base) {
|
|
3101
|
+
if (base->tp_del != PrivateAttr_tp_del) {
|
|
3102
|
+
return base->tp_del;
|
|
3103
|
+
} else if (::AllData::all_type_del.find((uintptr_t)base) != ::AllData::all_type_del.end()) {
|
|
3104
|
+
if (::AllData::all_type_del[(uintptr_t)base]) {
|
|
3105
|
+
return ::AllData::all_type_del[(uintptr_t)base];
|
|
3106
|
+
}
|
|
3107
|
+
base = base->tp_base;
|
|
3108
|
+
} else {
|
|
3109
|
+
base = base->tp_base;
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
return NULL;
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3095
3115
|
// NOTE: register_finalize is defined later in this file; forward-declare it
|
|
3096
3116
|
// here because get_type_need_tp_finalize() needs to compare against it.
|
|
3097
3117
|
static void register_finalize(PyObject* cls) noexcept;
|
|
@@ -3311,7 +3331,20 @@ PrivateAttrType_finalize(PyObject* cls) noexcept
|
|
|
3311
3331
|
if (::AllData::all_type_finalize.find(typ_id) != ::AllData::all_type_finalize.end()) {
|
|
3312
3332
|
::AllData::all_type_finalize.erase(typ_id);
|
|
3313
3333
|
}
|
|
3334
|
+
if (::AllData::all_type_del.find(typ_id) != ::AllData::all_type_del.end()) {
|
|
3335
|
+
::AllData::all_type_del.erase(typ_id);
|
|
3336
|
+
}
|
|
3314
3337
|
::AllData::all_type_mutex.erase(typ_id);
|
|
3338
|
+
// Clear the per-type buckets of the object-attr tables (created in
|
|
3339
|
+
// PrivateAttrType_postprocess / PrivateAttr_object_init_private_dict);
|
|
3340
|
+
// otherwise every class that ever got instances leaves an empty bucket
|
|
3341
|
+
// behind and all_object_attr / all_object_mutex grow forever.
|
|
3342
|
+
if (::AllData::all_object_attr.find(typ_id) != ::AllData::all_object_attr.end()) {
|
|
3343
|
+
::AllData::all_object_attr.erase(typ_id);
|
|
3344
|
+
}
|
|
3345
|
+
if (::AllData::all_object_mutex.find(typ_id) != ::AllData::all_object_mutex.end()) {
|
|
3346
|
+
::AllData::all_object_mutex.erase(typ_id);
|
|
3347
|
+
}
|
|
3315
3348
|
clear_obj(typ_id);
|
|
3316
3349
|
}
|
|
3317
3350
|
|
|
@@ -3563,6 +3596,7 @@ metaclass_weakref_callback(PyObject* self, PyObject* /*weakref*/) noexcept
|
|
|
3563
3596
|
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(traverse);
|
|
3564
3597
|
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(clear);
|
|
3565
3598
|
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(finalize);
|
|
3599
|
+
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(del);
|
|
3566
3600
|
|
|
3567
3601
|
Py_RETURN_NONE;
|
|
3568
3602
|
}
|
|
@@ -4133,6 +4167,12 @@ atexit_register_clean_func(void) noexcept
|
|
|
4133
4167
|
PyMODINIT_FUNC
|
|
4134
4168
|
PyInit_private_attribute(void) noexcept
|
|
4135
4169
|
{
|
|
4170
|
+
// check if AllData::store_module_self has first value
|
|
4171
|
+
if (AllData::store_module_self.size() >= 1) {
|
|
4172
|
+
PyObject* m = AllData::store_module_self[0];
|
|
4173
|
+
Py_INCREF(m);
|
|
4174
|
+
return m;
|
|
4175
|
+
}
|
|
4136
4176
|
if (init_all_slots() <0 ||
|
|
4137
4177
|
atexit_register_clean_func() < 0 ||
|
|
4138
4178
|
PyType_Ready(&PrivateWrapType) < 0 ||
|
|
@@ -4142,19 +4182,28 @@ PyInit_private_attribute(void) noexcept
|
|
|
4142
4182
|
PyType_Ready(&PrivateTempType) < 0) {
|
|
4143
4183
|
return NULL;
|
|
4144
4184
|
}
|
|
4185
|
+
// Eagerly create PrivateAttrBase so that CPython's subtype_traverse /
|
|
4186
|
+
// subtype_clear pointers are captured at import time (see
|
|
4187
|
+
// PrivateAttrCaptureGuard and ::AllData::captured_subtype_*), instead of
|
|
4188
|
+
// waiting for the first lazy attribute access to the module.
|
|
4189
|
+
PyObject* private_attr_base = PrivateModule_get_PrivateAttrBase(NULL, NULL);
|
|
4190
|
+
if (!private_attr_base) {
|
|
4191
|
+
return NULL;
|
|
4192
|
+
}
|
|
4145
4193
|
PyObject* all = PyList_New(0);
|
|
4146
4194
|
if (!all) {
|
|
4195
|
+
Py_DECREF(private_attr_base);
|
|
4147
4196
|
return NULL;
|
|
4148
4197
|
}
|
|
4149
4198
|
PyObject* m = PyModule_Create(&def);
|
|
4150
4199
|
if (!m) {
|
|
4151
4200
|
Py_DECREF(all);
|
|
4201
|
+
Py_DECREF(private_attr_base);
|
|
4152
4202
|
return NULL;
|
|
4153
4203
|
}
|
|
4154
4204
|
#ifdef Py_GIL_DISABLED
|
|
4155
4205
|
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
|
4156
4206
|
#endif
|
|
4157
|
-
|
|
4158
4207
|
PyModule_AddObject(m, "__all__", all);
|
|
4159
4208
|
/* all contains:
|
|
4160
4209
|
* PrivateWrapProxy
|
|
@@ -4176,16 +4225,8 @@ PyInit_private_attribute(void) noexcept
|
|
|
4176
4225
|
PyList_AppendString(all, "ensure_metaclass");
|
|
4177
4226
|
Py_SET_TYPE(m, &PrivateModuleType);
|
|
4178
4227
|
|
|
4179
|
-
// Eagerly create PrivateAttrBase so that CPython's subtype_traverse /
|
|
4180
|
-
// subtype_clear pointers are captured at import time (see
|
|
4181
|
-
// PrivateAttrCaptureGuard and ::AllData::captured_subtype_*), instead of
|
|
4182
|
-
// waiting for the first lazy attribute access to the module.
|
|
4183
|
-
PyObject* private_attr_base = PrivateModule_get_PrivateAttrBase(NULL, NULL);
|
|
4184
|
-
if (!private_attr_base) {
|
|
4185
|
-
Py_DECREF(m);
|
|
4186
|
-
return NULL;
|
|
4187
|
-
}
|
|
4188
4228
|
Py_DECREF(private_attr_base);
|
|
4229
|
+
AllData::store_module_self.push_back(m); // store module self
|
|
4189
4230
|
|
|
4190
4231
|
return m;
|
|
4191
4232
|
}
|
{private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5/private_attribute_cpp.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: private_attribute_cpp
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.5
|
|
4
4
|
Summary: A Python package that provides a way to define private attributes in C++ implementation.
|
|
5
5
|
Home-page: https://github.com/Locked-chess-official/private_attribute_cpp
|
|
6
6
|
Author: HuangHaoHua
|
|
@@ -12,6 +12,7 @@ private_attribute_cpp.egg-info/not-zip-safe
|
|
|
12
12
|
private_attribute_cpp.egg-info/top_level.txt
|
|
13
13
|
tests/test_attr_classify_owner.py
|
|
14
14
|
tests/test_class_private_attrs.py
|
|
15
|
+
tests/test_gc_collect.py
|
|
15
16
|
tests/test_private_abc.py
|
|
16
17
|
tests/test_private_attrs.py
|
|
17
18
|
tests/test_private_func.py
|
|
@@ -18,7 +18,7 @@ readme = open('README.md').read()
|
|
|
18
18
|
|
|
19
19
|
setup(
|
|
20
20
|
name='private_attribute_cpp',
|
|
21
|
-
version='2.1.
|
|
21
|
+
version='2.1.5',
|
|
22
22
|
author="HuangHaoHua",
|
|
23
23
|
author_email="13140752715@example.com",
|
|
24
24
|
description='A Python package that provides a way to define private attributes in C++ implementation.',
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""GC regression tests for the metaclass type_traverse/type_clear fix.
|
|
2
|
+
|
|
3
|
+
Validates that class objects created by the private-attribute machinery are
|
|
4
|
+
actually reclaimed by the cyclic GC (previously they leaked because the
|
|
5
|
+
metaclass traverse resolved to the instance-level delegating defaults instead
|
|
6
|
+
of CPython's real type_traverse/type_clear).
|
|
7
|
+
|
|
8
|
+
Run: PYTHONPATH=. python test_gc_collect.py
|
|
9
|
+
"""
|
|
10
|
+
import gc
|
|
11
|
+
import sys
|
|
12
|
+
import unittest
|
|
13
|
+
import weakref
|
|
14
|
+
|
|
15
|
+
import private_attribute
|
|
16
|
+
from private_attribute import PrivateAttrBase
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def make_class(i, name="C"):
|
|
20
|
+
class C(PrivateAttrBase):
|
|
21
|
+
__private_attrs__ = ("_x",)
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self._x = i
|
|
24
|
+
def get(self):
|
|
25
|
+
return self._x
|
|
26
|
+
C.__name__ = name
|
|
27
|
+
return C
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def alive_after_del_collect(maker, n):
|
|
31
|
+
"""Create n classes, drop them, collect; return how many are still alive."""
|
|
32
|
+
refs = []
|
|
33
|
+
for i in range(n):
|
|
34
|
+
C = maker(i, f"Probe{i}")
|
|
35
|
+
refs.append(weakref.ref(C))
|
|
36
|
+
del C
|
|
37
|
+
for _ in range(3):
|
|
38
|
+
gc.collect()
|
|
39
|
+
return sum(1 for w in refs if w() is not None)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestClassCollect(unittest.TestCase):
|
|
43
|
+
def test_empty_class_collected(self):
|
|
44
|
+
def maker(i, name):
|
|
45
|
+
class E(PrivateAttrBase):
|
|
46
|
+
pass
|
|
47
|
+
E.__name__ = name
|
|
48
|
+
return E
|
|
49
|
+
self.assertEqual(alive_after_del_collect(maker, 20), 0)
|
|
50
|
+
|
|
51
|
+
def test_class_with_private_attrs_collected(self):
|
|
52
|
+
self.assertEqual(alive_after_del_collect(make_class, 20), 0)
|
|
53
|
+
|
|
54
|
+
def test_class_with_private_value_freed(self):
|
|
55
|
+
class Holder:
|
|
56
|
+
pass
|
|
57
|
+
refs = []
|
|
58
|
+
for i in range(20):
|
|
59
|
+
h = Holder()
|
|
60
|
+
class H(PrivateAttrBase):
|
|
61
|
+
__private_attrs__ = ("_cv",)
|
|
62
|
+
_cv = h
|
|
63
|
+
refs.append(weakref.ref(h))
|
|
64
|
+
del h, H
|
|
65
|
+
for _ in range(3):
|
|
66
|
+
gc.collect()
|
|
67
|
+
self.assertEqual(sum(1 for w in refs if w() is not None), 0)
|
|
68
|
+
|
|
69
|
+
def test_mutual_class_cycle_collected(self):
|
|
70
|
+
def cyc():
|
|
71
|
+
class A(PrivateAttrBase):
|
|
72
|
+
__private_attrs__ = ("_ref",)
|
|
73
|
+
_ref = None
|
|
74
|
+
def set_ref(cls, v):
|
|
75
|
+
cls._ref = v
|
|
76
|
+
class B(PrivateAttrBase):
|
|
77
|
+
__private_attrs__ = ("_ref",)
|
|
78
|
+
_ref = None
|
|
79
|
+
def set_ref(cls, v):
|
|
80
|
+
cls._ref = v
|
|
81
|
+
A.set_ref(A, B)
|
|
82
|
+
B.set_ref(B, A)
|
|
83
|
+
wa = weakref.ref(A)
|
|
84
|
+
wb = weakref.ref(B)
|
|
85
|
+
del A, B
|
|
86
|
+
for _ in range(3):
|
|
87
|
+
gc.collect()
|
|
88
|
+
return wa() is not None, wb() is not None
|
|
89
|
+
a, b = cyc()
|
|
90
|
+
self.assertFalse(a)
|
|
91
|
+
self.assertFalse(b)
|
|
92
|
+
|
|
93
|
+
def test_self_referencing_class_collected(self):
|
|
94
|
+
def cyc():
|
|
95
|
+
class S(PrivateAttrBase):
|
|
96
|
+
__private_attrs__ = ("_self_ref",)
|
|
97
|
+
_self_ref = None
|
|
98
|
+
def set_self(cls, v):
|
|
99
|
+
cls._self_ref = v
|
|
100
|
+
S.set_self(S, S)
|
|
101
|
+
w = weakref.ref(S)
|
|
102
|
+
del S
|
|
103
|
+
for _ in range(3):
|
|
104
|
+
gc.collect()
|
|
105
|
+
return w() is not None
|
|
106
|
+
self.assertFalse(cyc())
|
|
107
|
+
|
|
108
|
+
def test_nested_class_collected(self):
|
|
109
|
+
def nested():
|
|
110
|
+
class Outer(PrivateAttrBase):
|
|
111
|
+
__private_attrs__ = ("_inner",)
|
|
112
|
+
def make(self):
|
|
113
|
+
class Inner(PrivateAttrBase):
|
|
114
|
+
__private_attrs__ = ("_v",)
|
|
115
|
+
def __init__(self):
|
|
116
|
+
self._v = 1
|
|
117
|
+
self._inner = Inner
|
|
118
|
+
return Inner
|
|
119
|
+
o = Outer()
|
|
120
|
+
Inner = o.make()
|
|
121
|
+
wi = weakref.ref(Inner)
|
|
122
|
+
wo = weakref.ref(o)
|
|
123
|
+
del o, Inner
|
|
124
|
+
for _ in range(3):
|
|
125
|
+
gc.collect()
|
|
126
|
+
return wi() is not None, wo() is not None
|
|
127
|
+
inner, outer = nested()
|
|
128
|
+
self.assertFalse(inner)
|
|
129
|
+
self.assertFalse(outer)
|
|
130
|
+
|
|
131
|
+
def test_private_attr_semantics_still_work(self):
|
|
132
|
+
class C(PrivateAttrBase):
|
|
133
|
+
__private_attrs__ = ("_x",)
|
|
134
|
+
def __init__(self):
|
|
135
|
+
self._x = 7
|
|
136
|
+
def get(self):
|
|
137
|
+
return self._x
|
|
138
|
+
c = C()
|
|
139
|
+
self.assertEqual(c.get(), 7)
|
|
140
|
+
with self.assertRaises(AttributeError):
|
|
141
|
+
c._x
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/tests/test_attr_classify_owner.py
RENAMED
|
File without changes
|
{private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/tests/test_class_private_attrs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.5}/tests/test_private_inheritance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|