private-attribute-cpp 2.1.3__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.3/private_attribute_cpp.egg-info → private_attribute_cpp-2.1.5}/PKG-INFO +5 -5
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/README.md +4 -4
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/private_attribute.cpp +229 -176
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/private_attribute.pyi +2 -2
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5/private_attribute_cpp.egg-info}/PKG-INFO +5 -5
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/private_attribute_cpp.egg-info/SOURCES.txt +1 -0
- {private_attribute_cpp-2.1.3 → 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.3 → private_attribute_cpp-2.1.5}/LICENSE +0 -0
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/MANIFEST.in +0 -0
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/picosha2.h +0 -0
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/private_attribute_cpp.egg-info/dependency_links.txt +0 -0
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/private_attribute_cpp.egg-info/not-zip-safe +0 -0
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/private_attribute_cpp.egg-info/top_level.txt +0 -0
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/setup.cfg +0 -0
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/tests/test_attr_classify_owner.py +0 -0
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/tests/test_class_private_attrs.py +0 -0
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/tests/test_private_abc.py +0 -0
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/tests/test_private_attrs.py +0 -0
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/tests/test_private_func.py +0 -0
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/tests/test_private_inheritance.py +0 -0
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/tests/test_private_method.py +0 -0
- {private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/tests/test_private_wrap.py +0 -0
{private_attribute_cpp-2.1.3/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
|
|
@@ -75,7 +75,7 @@ print(obj.expensive_api_call(10)) # works with all decorators applied
|
|
|
75
75
|
| 1 | PrivateWrapProxy | Decorator wrapper for arbitrary decorators | When needed |
|
|
76
76
|
| 2 | private_func=callable | Custom hidden-name generator | Optional |
|
|
77
77
|
| 3 | Pass private_func in class definition | Same as above | Optional |
|
|
78
|
-
| 4 | \_\_private_attrs\_\_ list | Declare which attributes are private |
|
|
78
|
+
| 4 | \_\_private_attrs\_\_ list | Declare which attributes are private | Optional (changed in 2.1.4) |
|
|
79
79
|
| 5 | @PrivateWrapProxy(...) | Make any decorator compatible with private attributes | When needed |
|
|
80
80
|
| 6 | method.result.xxx chain + dummy wrap | Fix decorator order and name conflicts | When needed |
|
|
81
81
|
|
|
@@ -240,8 +240,8 @@ TypeError: Can't instantiate abstract class MyClass without an implementation fo
|
|
|
240
240
|
|
|
241
241
|
## Notes
|
|
242
242
|
|
|
243
|
-
- All of the private attributes class must contain the `__private_attrs__` attribute.
|
|
244
|
-
- The `__private_attrs__` attribute must be a sequence of strings.
|
|
243
|
+
- ~~All of the private attributes class must contain the `__private_attrs__` attribute.~~(changed in 2.1.4)
|
|
244
|
+
- The `__private_attrs__` attribute must be a sequence of strings or just one string.
|
|
245
245
|
- You cannot define the name which in `__slots__` to `__private_attrs__`.
|
|
246
246
|
- When you define `__slots__` and `__private_attrs__` in one class, the attributes in `__private_attrs__` can also be defined in the methods, even though they are not in `__slots__`.
|
|
247
247
|
- All of the object that is the instance of the class "PrivateAttrBase" or its subclass are default to be unable to be pickled.
|
|
@@ -266,4 +266,4 @@ This package require the c++ module "[picosha2](https://github.com/okdshin/PicoS
|
|
|
266
266
|
|
|
267
267
|
## Support
|
|
268
268
|
|
|
269
|
-
Now it
|
|
269
|
+
Now it only support Cpython.
|
|
@@ -56,7 +56,7 @@ print(obj.expensive_api_call(10)) # works with all decorators applied
|
|
|
56
56
|
| 1 | PrivateWrapProxy | Decorator wrapper for arbitrary decorators | When needed |
|
|
57
57
|
| 2 | private_func=callable | Custom hidden-name generator | Optional |
|
|
58
58
|
| 3 | Pass private_func in class definition | Same as above | Optional |
|
|
59
|
-
| 4 | \_\_private_attrs\_\_ list | Declare which attributes are private |
|
|
59
|
+
| 4 | \_\_private_attrs\_\_ list | Declare which attributes are private | Optional (changed in 2.1.4) |
|
|
60
60
|
| 5 | @PrivateWrapProxy(...) | Make any decorator compatible with private attributes | When needed |
|
|
61
61
|
| 6 | method.result.xxx chain + dummy wrap | Fix decorator order and name conflicts | When needed |
|
|
62
62
|
|
|
@@ -221,8 +221,8 @@ TypeError: Can't instantiate abstract class MyClass without an implementation fo
|
|
|
221
221
|
|
|
222
222
|
## Notes
|
|
223
223
|
|
|
224
|
-
- All of the private attributes class must contain the `__private_attrs__` attribute.
|
|
225
|
-
- The `__private_attrs__` attribute must be a sequence of strings.
|
|
224
|
+
- ~~All of the private attributes class must contain the `__private_attrs__` attribute.~~(changed in 2.1.4)
|
|
225
|
+
- The `__private_attrs__` attribute must be a sequence of strings or just one string.
|
|
226
226
|
- You cannot define the name which in `__slots__` to `__private_attrs__`.
|
|
227
227
|
- When you define `__slots__` and `__private_attrs__` in one class, the attributes in `__private_attrs__` can also be defined in the methods, even though they are not in `__slots__`.
|
|
228
228
|
- All of the object that is the instance of the class "PrivateAttrBase" or its subclass are default to be unable to be pickled.
|
|
@@ -247,4 +247,4 @@ This package require the c++ module "[picosha2](https://github.com/okdshin/PicoS
|
|
|
247
247
|
|
|
248
248
|
## Support
|
|
249
249
|
|
|
250
|
-
Now it
|
|
250
|
+
Now it only support Cpython.
|
|
@@ -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
|
}
|
|
@@ -2333,10 +2312,23 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
|
|
|
2333
2312
|
return false;
|
|
2334
2313
|
}
|
|
2335
2314
|
|
|
2336
|
-
|
|
2337
|
-
if (
|
|
2338
|
-
|
|
2339
|
-
|
|
2315
|
+
PyObjectStorage __private_attrs__;
|
|
2316
|
+
if (PyDict_ContainsString(data.attrs, "__private_attrs__")) {
|
|
2317
|
+
__private_attrs__ = PyDict_GetItemString(data.attrs, "__private_attrs__");
|
|
2318
|
+
}
|
|
2319
|
+
else {
|
|
2320
|
+
__private_attrs__ = PyTuple_New(0);
|
|
2321
|
+
Py_DECREF(__private_attrs__);
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
if (PyUnicode_Check(__private_attrs__)) {
|
|
2325
|
+
PyObject* new___private_attrs__ = PyTuple_New(1);
|
|
2326
|
+
if (!new___private_attrs__) {
|
|
2327
|
+
return false;
|
|
2328
|
+
}
|
|
2329
|
+
PyTuple_SET_ITEM(new___private_attrs__, 0, __private_attrs__);
|
|
2330
|
+
__private_attrs__ = new___private_attrs__;
|
|
2331
|
+
Py_DECREF(__private_attrs__);
|
|
2340
2332
|
}
|
|
2341
2333
|
|
|
2342
2334
|
if (!PySequence_Check(__private_attrs__)) {
|
|
@@ -2379,8 +2371,7 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
|
|
|
2379
2371
|
|
|
2380
2372
|
for (const char** p = invalid_name; *p != NULL; p++) {
|
|
2381
2373
|
if (strcmp(attr_str.c_str(), *p) == 0) {
|
|
2382
|
-
|
|
2383
|
-
PyErr_SetString(PyExc_TypeError, error_msg.c_str());
|
|
2374
|
+
PyErr_Format(PyExc_TypeError, "Invalid name '%s' in '__private_attrs__'", attr_str.c_str());
|
|
2384
2375
|
return false;
|
|
2385
2376
|
}
|
|
2386
2377
|
}
|
|
@@ -2421,7 +2412,7 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
|
|
|
2421
2412
|
}
|
|
2422
2413
|
slot_seq = keys;
|
|
2423
2414
|
} else {
|
|
2424
|
-
slot_seq = PySequence_Fast(all_slots, "__slots__ must be a sequence");
|
|
2415
|
+
slot_seq = PySequence_Fast(all_slots, "__slots__ must be a string, dict or sequence");
|
|
2425
2416
|
}
|
|
2426
2417
|
if (!slot_seq) {
|
|
2427
2418
|
return false;
|
|
@@ -2433,9 +2424,9 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
|
|
|
2433
2424
|
PyObject* slot = PySequence_Fast_GET_ITEM(slot_seq, j);
|
|
2434
2425
|
if (PyUnicode_Check(slot)) {
|
|
2435
2426
|
const char* slot_cstr = PyUnicode_AsUTF8(slot);
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2427
|
+
std::string final_name = real_class_name(slot_cstr, data.class_name);
|
|
2428
|
+
if (data.private_attrs_vector_string.find(final_name) != data.private_attrs_vector_string.end()){
|
|
2429
|
+
PyErr_Format(PyExc_TypeError, "'__slots__' and '__private_attrs__' cannot have the same attribute name: '%s'", final_name.c_str());
|
|
2439
2430
|
Py_DECREF(slot_seq);
|
|
2440
2431
|
return false;
|
|
2441
2432
|
}
|
|
@@ -2485,7 +2476,10 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
|
|
|
2485
2476
|
need_value = value;
|
|
2486
2477
|
}
|
|
2487
2478
|
if (data.private_attrs_vector_string.find(attr_name) != data.private_attrs_vector_string.end()) {
|
|
2488
|
-
|
|
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.
|
|
2489
2483
|
data.need_remove_itself[attr_name] = need_value;
|
|
2490
2484
|
PyDict_DelItem(data.attrs_copy, key);
|
|
2491
2485
|
continue;
|
|
@@ -2498,7 +2492,7 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
|
|
|
2498
2492
|
if (data.need_remove_subclass.find(need_remove_subclass_id) == data.need_remove_subclass.end()) {
|
|
2499
2493
|
data.need_remove_subclass[need_remove_subclass_id] = {};
|
|
2500
2494
|
}
|
|
2501
|
-
|
|
2495
|
+
// PyObjectStorage assignment INCREFs (RAII); no manual INCREF.
|
|
2502
2496
|
data.need_remove_subclass[need_remove_subclass_id][attr_name] = need_value;
|
|
2503
2497
|
PyDict_DelItem(data.attrs_copy, key);
|
|
2504
2498
|
continue;
|
|
@@ -2556,17 +2550,18 @@ static setattrofunc get_need_tp_setattro(PyTypeObject* cls) noexcept;
|
|
|
2556
2550
|
static traverseproc get_need_tp_traverse(PyTypeObject* cls) noexcept;
|
|
2557
2551
|
static inquiry get_need_tp_clear(PyTypeObject* cls) noexcept;
|
|
2558
2552
|
static destructor get_need_tp_finalize(PyTypeObject* cls) noexcept;
|
|
2553
|
+
static destructor get_need_tp_del(PyTypeObject* cls) noexcept;
|
|
2559
2554
|
|
|
2560
2555
|
// instance-level traverse/clear for types that use private attributes
|
|
2561
2556
|
static int PrivateAttr_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept;
|
|
2562
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;
|
|
2563
2561
|
// metaclass-level wrappers (if needed)
|
|
2564
2562
|
static int PrivateAttrType_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept;
|
|
2565
2563
|
static int PrivateAttrType_tp_clear(PyObject* self) noexcept;
|
|
2566
2564
|
|
|
2567
|
-
// shared helper to clear object related data (avoid double clear)
|
|
2568
|
-
static void clear_object_related(uintptr_t id_self, uintptr_t typ_id) noexcept;
|
|
2569
|
-
|
|
2570
2565
|
static void
|
|
2571
2566
|
ensure_tp(PyTypeObject* type_instance) noexcept
|
|
2572
2567
|
{
|
|
@@ -2661,6 +2656,24 @@ ensure_tp(PyTypeObject* type_instance) noexcept
|
|
|
2661
2656
|
}
|
|
2662
2657
|
}
|
|
2663
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
|
+
}
|
|
2664
2677
|
}
|
|
2665
2678
|
|
|
2666
2679
|
static void
|
|
@@ -3080,6 +3093,25 @@ get_need_tp_clear(PyTypeObject* cls) noexcept
|
|
|
3080
3093
|
return NULL;
|
|
3081
3094
|
}
|
|
3082
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
|
+
|
|
3083
3115
|
// NOTE: register_finalize is defined later in this file; forward-declare it
|
|
3084
3116
|
// here because get_type_need_tp_finalize() needs to compare against it.
|
|
3085
3117
|
static void register_finalize(PyObject* cls) noexcept;
|
|
@@ -3299,7 +3331,20 @@ PrivateAttrType_finalize(PyObject* cls) noexcept
|
|
|
3299
3331
|
if (::AllData::all_type_finalize.find(typ_id) != ::AllData::all_type_finalize.end()) {
|
|
3300
3332
|
::AllData::all_type_finalize.erase(typ_id);
|
|
3301
3333
|
}
|
|
3334
|
+
if (::AllData::all_type_del.find(typ_id) != ::AllData::all_type_del.end()) {
|
|
3335
|
+
::AllData::all_type_del.erase(typ_id);
|
|
3336
|
+
}
|
|
3302
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
|
+
}
|
|
3303
3348
|
clear_obj(typ_id);
|
|
3304
3349
|
}
|
|
3305
3350
|
|
|
@@ -3551,6 +3596,7 @@ metaclass_weakref_callback(PyObject* self, PyObject* /*weakref*/) noexcept
|
|
|
3551
3596
|
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(traverse);
|
|
3552
3597
|
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(clear);
|
|
3553
3598
|
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(finalize);
|
|
3599
|
+
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(del);
|
|
3554
3600
|
|
|
3555
3601
|
Py_RETURN_NONE;
|
|
3556
3602
|
}
|
|
@@ -4121,6 +4167,12 @@ atexit_register_clean_func(void) noexcept
|
|
|
4121
4167
|
PyMODINIT_FUNC
|
|
4122
4168
|
PyInit_private_attribute(void) noexcept
|
|
4123
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
|
+
}
|
|
4124
4176
|
if (init_all_slots() <0 ||
|
|
4125
4177
|
atexit_register_clean_func() < 0 ||
|
|
4126
4178
|
PyType_Ready(&PrivateWrapType) < 0 ||
|
|
@@ -4130,19 +4182,28 @@ PyInit_private_attribute(void) noexcept
|
|
|
4130
4182
|
PyType_Ready(&PrivateTempType) < 0) {
|
|
4131
4183
|
return NULL;
|
|
4132
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
|
+
}
|
|
4133
4193
|
PyObject* all = PyList_New(0);
|
|
4134
4194
|
if (!all) {
|
|
4195
|
+
Py_DECREF(private_attr_base);
|
|
4135
4196
|
return NULL;
|
|
4136
4197
|
}
|
|
4137
4198
|
PyObject* m = PyModule_Create(&def);
|
|
4138
4199
|
if (!m) {
|
|
4139
4200
|
Py_DECREF(all);
|
|
4201
|
+
Py_DECREF(private_attr_base);
|
|
4140
4202
|
return NULL;
|
|
4141
4203
|
}
|
|
4142
4204
|
#ifdef Py_GIL_DISABLED
|
|
4143
4205
|
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
|
4144
4206
|
#endif
|
|
4145
|
-
|
|
4146
4207
|
PyModule_AddObject(m, "__all__", all);
|
|
4147
4208
|
/* all contains:
|
|
4148
4209
|
* PrivateWrapProxy
|
|
@@ -4164,16 +4225,8 @@ PyInit_private_attribute(void) noexcept
|
|
|
4164
4225
|
PyList_AppendString(all, "ensure_metaclass");
|
|
4165
4226
|
Py_SET_TYPE(m, &PrivateModuleType);
|
|
4166
4227
|
|
|
4167
|
-
// Eagerly create PrivateAttrBase so that CPython's subtype_traverse /
|
|
4168
|
-
// subtype_clear pointers are captured at import time (see
|
|
4169
|
-
// PrivateAttrCaptureGuard and ::AllData::captured_subtype_*), instead of
|
|
4170
|
-
// waiting for the first lazy attribute access to the module.
|
|
4171
|
-
PyObject* private_attr_base = PrivateModule_get_PrivateAttrBase(NULL, NULL);
|
|
4172
|
-
if (!private_attr_base) {
|
|
4173
|
-
Py_DECREF(m);
|
|
4174
|
-
return NULL;
|
|
4175
|
-
}
|
|
4176
4228
|
Py_DECREF(private_attr_base);
|
|
4229
|
+
AllData::store_module_self.push_back(m); // store module self
|
|
4177
4230
|
|
|
4178
4231
|
return m;
|
|
4179
4232
|
}
|
|
@@ -15,12 +15,12 @@ class MyClass(PrivateAttrBase):
|
|
|
15
15
|
return self._private_attr1
|
|
16
16
|
```
|
|
17
17
|
"""
|
|
18
|
-
from typing import Any, TypeVar, Callable, TypedDict, Sequence, Generic
|
|
18
|
+
from typing import Any, TypeVar, Callable, TypedDict, Sequence, Generic, Optional
|
|
19
19
|
from types import FunctionType
|
|
20
20
|
|
|
21
21
|
# define the dict that must have a key "__private_attrs__" and value must be the sequence of strings
|
|
22
22
|
class _PrivateAttrDict(TypedDict):
|
|
23
|
-
__private_attrs__: Sequence[str]
|
|
23
|
+
__private_attrs__: Optional[Sequence[str]]
|
|
24
24
|
|
|
25
25
|
_T = TypeVar('T')
|
|
26
26
|
|
{private_attribute_cpp-2.1.3 → 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
|
|
@@ -75,7 +75,7 @@ print(obj.expensive_api_call(10)) # works with all decorators applied
|
|
|
75
75
|
| 1 | PrivateWrapProxy | Decorator wrapper for arbitrary decorators | When needed |
|
|
76
76
|
| 2 | private_func=callable | Custom hidden-name generator | Optional |
|
|
77
77
|
| 3 | Pass private_func in class definition | Same as above | Optional |
|
|
78
|
-
| 4 | \_\_private_attrs\_\_ list | Declare which attributes are private |
|
|
78
|
+
| 4 | \_\_private_attrs\_\_ list | Declare which attributes are private | Optional (changed in 2.1.4) |
|
|
79
79
|
| 5 | @PrivateWrapProxy(...) | Make any decorator compatible with private attributes | When needed |
|
|
80
80
|
| 6 | method.result.xxx chain + dummy wrap | Fix decorator order and name conflicts | When needed |
|
|
81
81
|
|
|
@@ -240,8 +240,8 @@ TypeError: Can't instantiate abstract class MyClass without an implementation fo
|
|
|
240
240
|
|
|
241
241
|
## Notes
|
|
242
242
|
|
|
243
|
-
- All of the private attributes class must contain the `__private_attrs__` attribute.
|
|
244
|
-
- The `__private_attrs__` attribute must be a sequence of strings.
|
|
243
|
+
- ~~All of the private attributes class must contain the `__private_attrs__` attribute.~~(changed in 2.1.4)
|
|
244
|
+
- The `__private_attrs__` attribute must be a sequence of strings or just one string.
|
|
245
245
|
- You cannot define the name which in `__slots__` to `__private_attrs__`.
|
|
246
246
|
- When you define `__slots__` and `__private_attrs__` in one class, the attributes in `__private_attrs__` can also be defined in the methods, even though they are not in `__slots__`.
|
|
247
247
|
- All of the object that is the instance of the class "PrivateAttrBase" or its subclass are default to be unable to be pickled.
|
|
@@ -266,4 +266,4 @@ This package require the c++ module "[picosha2](https://github.com/okdshin/PicoS
|
|
|
266
266
|
|
|
267
267
|
## Support
|
|
268
268
|
|
|
269
|
-
Now it
|
|
269
|
+
Now it only support Cpython.
|
|
@@ -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
|
{private_attribute_cpp-2.1.3 → private_attribute_cpp-2.1.5}/tests/test_attr_classify_owner.py
RENAMED
|
File without changes
|
{private_attribute_cpp-2.1.3 → 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.3 → private_attribute_cpp-2.1.5}/tests/test_private_inheritance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|