private-attribute-cpp 2.0.5__tar.gz → 2.1.0__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.0.5/private_attribute_cpp.egg-info → private_attribute_cpp-2.1.0}/PKG-INFO +3 -1
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/README.md +2 -0
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/private_attribute.cpp +209 -83
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0/private_attribute_cpp.egg-info}/PKG-INFO +3 -1
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/setup.py +1 -1
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/tests/test_private_abc.py +5 -1
- private_attribute_cpp-2.1.0/tests/test_private_func.py +57 -0
- private_attribute_cpp-2.1.0/tests/test_private_inheritance.py +319 -0
- private_attribute_cpp-2.0.5/tests/test_private_func.py +0 -27
- private_attribute_cpp-2.0.5/tests/test_private_inheritance.py +0 -40
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/LICENSE +0 -0
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/MANIFEST.in +0 -0
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/picosha2.h +0 -0
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/private_attribute.pyi +0 -0
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/private_attribute_cpp.egg-info/SOURCES.txt +0 -0
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/private_attribute_cpp.egg-info/dependency_links.txt +0 -0
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/private_attribute_cpp.egg-info/not-zip-safe +0 -0
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/private_attribute_cpp.egg-info/top_level.txt +0 -0
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/setup.cfg +0 -0
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/tests/test_class_private_attrs.py +0 -0
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/tests/test_private_attrs.py +0 -0
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/tests/test_private_method.py +0 -0
- {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/tests/test_private_wrap.py +0 -0
{private_attribute_cpp-2.0.5/private_attribute_cpp.egg-info → private_attribute_cpp-2.1.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: private_attribute_cpp
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
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
|
|
@@ -250,6 +250,8 @@ TypeError: Can't instantiate abstract class MyClass without an implementation fo
|
|
|
250
250
|
- Don't use a decorator which will return the `_PrivateWrap` in `PrivateWrapProxy` which will raise `TypeError`.
|
|
251
251
|
- One class defined in another class cannot use another class's private attribute.
|
|
252
252
|
- One parent class defined an attribute which not in `__private_attrs__` or not a `PrivateAttrType` instance, the child class shouldn't contain the attribute in its `__private_attrs__`.
|
|
253
|
+
- Since 2.1.0: the code of a subclass can no longer access the private attributes of its parent classes - a parent's private attribute is only reachable from the parent's own code, or from a class that declares the same name in its own `__private_attrs__`.
|
|
254
|
+
- Since 2.1.0: if a subclass defines an attribute with the same name as a parent's private attribute, they are stored separately (instance attributes per declaring class, class-level attributes in `all_type_subclass_attr[parent][subclass]`). Class-level resolution is per-subject through the parent's code: reading the name on a subclass subject returns the subclass's own value, and the parent's own value is untouched. Such a same-name definition does NOT grant the subclass's own code access to the name.
|
|
253
255
|
- CPython may change "tp_getattro", "tp_setattro" and so on when you change the attribute "\_\_getattribute\_\_", "\_\_setattr\_\_" and so on. If you are fear about it, you can use `ensure_type` to reset those tp slots. For the other metaclasses, you can use `ensure_metaclass` to reset those tp slots. Also, don't set those methods on these classes in your code.
|
|
254
256
|
- `private_attribute.register_metaclass` must be called with the metaclass which supports weakref.
|
|
255
257
|
- Don't set `__static_attributes__` in private attribute class, or it will be removed.
|
|
@@ -231,6 +231,8 @@ TypeError: Can't instantiate abstract class MyClass without an implementation fo
|
|
|
231
231
|
- Don't use a decorator which will return the `_PrivateWrap` in `PrivateWrapProxy` which will raise `TypeError`.
|
|
232
232
|
- One class defined in another class cannot use another class's private attribute.
|
|
233
233
|
- One parent class defined an attribute which not in `__private_attrs__` or not a `PrivateAttrType` instance, the child class shouldn't contain the attribute in its `__private_attrs__`.
|
|
234
|
+
- Since 2.1.0: the code of a subclass can no longer access the private attributes of its parent classes - a parent's private attribute is only reachable from the parent's own code, or from a class that declares the same name in its own `__private_attrs__`.
|
|
235
|
+
- Since 2.1.0: if a subclass defines an attribute with the same name as a parent's private attribute, they are stored separately (instance attributes per declaring class, class-level attributes in `all_type_subclass_attr[parent][subclass]`). Class-level resolution is per-subject through the parent's code: reading the name on a subclass subject returns the subclass's own value, and the parent's own value is untouched. Such a same-name definition does NOT grant the subclass's own code access to the name.
|
|
234
236
|
- CPython may change "tp_getattro", "tp_setattro" and so on when you change the attribute "\_\_getattribute\_\_", "\_\_setattr\_\_" and so on. If you are fear about it, you can use `ensure_type` to reset those tp slots. For the other metaclasses, you can use `ensure_metaclass` to reset those tp slots. Also, don't set those methods on these classes in your code.
|
|
235
237
|
- `private_attribute.register_metaclass` must be called with the metaclass which supports weakref.
|
|
236
238
|
- Don't set `__static_attributes__` in private attribute class, or it will be removed.
|
|
@@ -178,7 +178,11 @@ namespace {
|
|
|
178
178
|
};
|
|
179
179
|
static std::unordered_map<uintptr_t, std::unordered_map<uintptr_t, PyCodeObject*>> type_allowed_code_map;
|
|
180
180
|
static std::unordered_map<uintptr_t, std::shared_ptr<std::shared_mutex>> all_type_mutex;
|
|
181
|
-
|
|
181
|
+
// private_func per type, stored with RAII ownership (PyObjectStorage
|
|
182
|
+
// INCREFs on assignment and DECREFs on destruction), so the map entry
|
|
183
|
+
// always owns exactly one reference - no dangling-pointer risk when the
|
|
184
|
+
// last external reference to the function disappears.
|
|
185
|
+
static std::unordered_map<uintptr_t, PyObjectStorage> type_need_call;
|
|
182
186
|
static std::unordered_map<uintptr_t, std::unordered_set<TwoStringTuple>> all_type_attr_set;
|
|
183
187
|
namespace {
|
|
184
188
|
static std::unordered_map<uintptr_t, std::unordered_map<uintptr_t,
|
|
@@ -225,6 +229,19 @@ static uintptr_t type_set_attr_long_long_guidance(uintptr_t type, const std::str
|
|
|
225
229
|
static bool type_private_attr(uintptr_t type, const std::string& name) noexcept;
|
|
226
230
|
static FinalObject type_get_final_attr(uintptr_t type_id, const std::string& name) noexcept;
|
|
227
231
|
|
|
232
|
+
// Result of classifying an attribute access.
|
|
233
|
+
// - type_id: the node on the MRO chain (a class) whose code is currently running
|
|
234
|
+
// AND that declares the attribute as private. 0 means "no private owner".
|
|
235
|
+
// - allowed: false means the access must be denied (a private attribute touched
|
|
236
|
+
// from outside its declaring class, or from a subclass of it).
|
|
237
|
+
struct AttrClassifyResult
|
|
238
|
+
{
|
|
239
|
+
uintptr_t type_id = 0;
|
|
240
|
+
bool allowed = true;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
static AttrClassifyResult attr_classify(uintptr_t typ_id, const std::string& name, PyCodeObject* code) noexcept;
|
|
244
|
+
|
|
228
245
|
static bool
|
|
229
246
|
is_class_code(uintptr_t typ_id, PyCodeObject* code) noexcept
|
|
230
247
|
{
|
|
@@ -251,27 +268,67 @@ is_type_private(uintptr_t typ_id, const std::string& name) noexcept
|
|
|
251
268
|
return false;
|
|
252
269
|
}
|
|
253
270
|
|
|
254
|
-
static
|
|
271
|
+
static AttrClassifyResult
|
|
255
272
|
attr_classify(uintptr_t typ_id, const std::string& name, PyCodeObject* code) noexcept
|
|
256
273
|
{
|
|
257
|
-
|
|
258
|
-
|
|
274
|
+
// Build the MRO chain: the actual class first, then its parents.
|
|
275
|
+
std::vector<uintptr_t> chain;
|
|
276
|
+
chain.push_back(typ_id);
|
|
277
|
+
if (::AllData::all_type_parent_id.find(typ_id) != ::AllData::all_type_parent_id.end()) {
|
|
278
|
+
auto& parent_ids = ::AllData::all_type_parent_id[typ_id];
|
|
279
|
+
chain.insert(chain.end(), parent_ids.begin(), parent_ids.end());
|
|
259
280
|
}
|
|
260
|
-
|
|
261
|
-
|
|
281
|
+
|
|
282
|
+
// Find the class whose code is currently running (the code owner).
|
|
283
|
+
uintptr_t code_class = 0;
|
|
284
|
+
for (auto& node : chain) {
|
|
285
|
+
if (is_class_code(node, code)) {
|
|
286
|
+
code_class = node;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
262
289
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
290
|
+
|
|
291
|
+
if (code_class == 0) {
|
|
292
|
+
// No class code was hit on the whole chain (e.g. the code belongs to a
|
|
293
|
+
// module-level function or an unrelated class): if the name is private
|
|
294
|
+
// to any class on the chain, deny the access.
|
|
295
|
+
for (auto& node : chain) {
|
|
296
|
+
if (is_type_private(node, name)) {
|
|
297
|
+
return {0, false};
|
|
268
298
|
}
|
|
269
|
-
|
|
270
|
-
|
|
299
|
+
}
|
|
300
|
+
return {0, true};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (is_type_private(code_class, name)) {
|
|
304
|
+
// Both conditions hold: the code is under this class AND the attribute
|
|
305
|
+
// is private to this very class -> allowed, and this node of the chain
|
|
306
|
+
// is the owner of the private attribute.
|
|
307
|
+
return {code_class, true};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// The code belongs to code_class but the name is not private to it: look
|
|
311
|
+
// upward (toward the base classes). If an ancestor declares the name
|
|
312
|
+
// private, a subclass is trying to touch a parent's private attribute.
|
|
313
|
+
// Note: a class-body definition reusing a parent's private name (a
|
|
314
|
+
// "shadow", stored separately in all_type_subclass_attr[parent][child])
|
|
315
|
+
// does NOT grant this class's code any access to the name.
|
|
316
|
+
bool found_code_class = false;
|
|
317
|
+
for (auto& node : chain) {
|
|
318
|
+
if (!found_code_class) {
|
|
319
|
+
if (node == code_class) {
|
|
320
|
+
found_code_class = true;
|
|
271
321
|
}
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (is_type_private(node, name)) {
|
|
325
|
+
return {0, false};
|
|
272
326
|
}
|
|
273
327
|
}
|
|
274
|
-
|
|
328
|
+
|
|
329
|
+
// Not private to the running class nor to any of its ancestors: treat it
|
|
330
|
+
// as a normal (non-private) attribute access.
|
|
331
|
+
return {0, true};
|
|
275
332
|
}
|
|
276
333
|
|
|
277
334
|
static std::string
|
|
@@ -437,13 +494,13 @@ static traverseproc get_need_tp_traverse(PyTypeObject* cls) noexcept;
|
|
|
437
494
|
static inquiry get_need_tp_clear(PyTypeObject* cls) noexcept;
|
|
438
495
|
|
|
439
496
|
static PyObject*
|
|
440
|
-
id_getattr(const std::string& attr_name, PyObject* obj, PyObject* typ) noexcept
|
|
497
|
+
id_getattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyObject* typ) noexcept
|
|
441
498
|
{
|
|
442
|
-
uintptr_t obj_id
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
FinalObject final_object = type_get_final_attr(
|
|
499
|
+
uintptr_t obj_id = (uintptr_t) obj;
|
|
500
|
+
// Class-level fallback resolves per-subject (the instance's type): a
|
|
501
|
+
// subclass's separately-stored shadow of a parent's private name is
|
|
502
|
+
// visible through the per-subject walk in type_get_final_attr.
|
|
503
|
+
FinalObject final_object = type_get_final_attr((uintptr_t)typ, attr_name);
|
|
447
504
|
if (final_object.status == -2) {
|
|
448
505
|
return NULL;
|
|
449
506
|
}
|
|
@@ -488,14 +545,14 @@ id_getattr(const std::string& attr_name, PyObject* obj, PyObject* typ) noexcept
|
|
|
488
545
|
result_typ = Py_TYPE(result);
|
|
489
546
|
if (!result_typ) return NULL;
|
|
490
547
|
}
|
|
491
|
-
bool has_get = false;
|
|
492
548
|
bool has_set = false;
|
|
549
|
+
descrgetfunc get = NULL;
|
|
493
550
|
if (result_typ) {
|
|
494
|
-
|
|
551
|
+
get = result_typ->tp_descr_get;
|
|
495
552
|
has_set = result_typ->tp_descr_set != NULL;
|
|
496
553
|
}
|
|
497
|
-
if (
|
|
498
|
-
PyObject* final_result =
|
|
554
|
+
if (get && has_set) {
|
|
555
|
+
PyObject* final_result = get(result, obj, typ);
|
|
499
556
|
ensure_tp((PyTypeObject*)typ);
|
|
500
557
|
ensure_subclass_tp((PyTypeObject*)typ);
|
|
501
558
|
return final_result;
|
|
@@ -512,9 +569,10 @@ id_getattr(const std::string& attr_name, PyObject* obj, PyObject* typ) noexcept
|
|
|
512
569
|
// if obj is a type, call result.__get__(None, obj)
|
|
513
570
|
if (PyType_Check(obj)) {
|
|
514
571
|
PyTypeObject* obj_type = Py_TYPE(python_obj);
|
|
515
|
-
|
|
572
|
+
auto funcptr = obj_type->tp_descr_get;
|
|
573
|
+
if (funcptr != NULL) {
|
|
516
574
|
lock.unlock();
|
|
517
|
-
return
|
|
575
|
+
return funcptr(python_obj, Py_None, obj);
|
|
518
576
|
}
|
|
519
577
|
}
|
|
520
578
|
Py_XINCREF(python_obj);
|
|
@@ -522,8 +580,8 @@ id_getattr(const std::string& attr_name, PyObject* obj, PyObject* typ) noexcept
|
|
|
522
580
|
}
|
|
523
581
|
}
|
|
524
582
|
|
|
525
|
-
if (
|
|
526
|
-
PyObject* final_result =
|
|
583
|
+
if (get) {
|
|
584
|
+
PyObject* final_result = get(result, obj, typ);
|
|
527
585
|
ensure_tp((PyTypeObject*)typ);
|
|
528
586
|
ensure_subclass_tp((PyTypeObject*)typ);
|
|
529
587
|
return final_result;
|
|
@@ -554,8 +612,9 @@ type_getattr(PyObject* typ, const std::string& attr_name) noexcept
|
|
|
554
612
|
}
|
|
555
613
|
if (result) {
|
|
556
614
|
PyTypeObject* type = Py_TYPE(result);
|
|
557
|
-
|
|
558
|
-
|
|
615
|
+
auto funcptr = type->tp_descr_get;
|
|
616
|
+
if (funcptr) {
|
|
617
|
+
PyObject* final_result = funcptr(result, Py_None, (PyObject*)type);
|
|
559
618
|
ensure_tp((PyTypeObject*)type);
|
|
560
619
|
ensure_subclass_tp((PyTypeObject*)type);
|
|
561
620
|
return final_result;
|
|
@@ -573,13 +632,11 @@ type_getattr(PyObject* typ, const std::string& attr_name) noexcept
|
|
|
573
632
|
}
|
|
574
633
|
|
|
575
634
|
static int
|
|
576
|
-
id_setattr(const std::string& attr_name, PyObject* obj, PyObject* typ, PyObject* value) noexcept
|
|
635
|
+
id_setattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyObject* typ, PyObject* value) noexcept
|
|
577
636
|
{
|
|
578
|
-
uintptr_t obj_id
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
final_id = type_set_attr_long_long_guidance(typ_id, attr_name);
|
|
582
|
-
FinalObject final_object = type_get_final_attr(typ_id, attr_name);
|
|
637
|
+
uintptr_t obj_id = (uintptr_t) obj;
|
|
638
|
+
// Class-level fallback resolves per-subject (the instance's type).
|
|
639
|
+
FinalObject final_object = type_get_final_attr((uintptr_t)typ, attr_name);
|
|
583
640
|
if (final_object.status == -2) {
|
|
584
641
|
return -1;
|
|
585
642
|
}
|
|
@@ -621,8 +678,9 @@ id_setattr(const std::string& attr_name, PyObject* obj, PyObject* typ, PyObject*
|
|
|
621
678
|
}
|
|
622
679
|
if (result) {
|
|
623
680
|
PyTypeObject* type = Py_TYPE(result);
|
|
624
|
-
|
|
625
|
-
|
|
681
|
+
auto funcptr = type->tp_descr_set;
|
|
682
|
+
if (funcptr) {
|
|
683
|
+
if (funcptr(result, obj, value) < 0) {
|
|
626
684
|
return -1;
|
|
627
685
|
}
|
|
628
686
|
return 0;
|
|
@@ -681,6 +739,8 @@ type_setattr(PyObject* typ, const std::string& attr_name, PyObject* value) noexc
|
|
|
681
739
|
}
|
|
682
740
|
return 0;
|
|
683
741
|
} else {
|
|
742
|
+
// The accessed class is a subclass that stores its own value for a
|
|
743
|
+
// parent's private name separately: all_type_subclass_attr[final_id][typ_id].
|
|
684
744
|
if (::AllData::all_type_subclass_attr.find(final_id) == ::AllData::all_type_subclass_attr.end()) {
|
|
685
745
|
::AllData::all_type_subclass_attr[final_id] = {};
|
|
686
746
|
}
|
|
@@ -703,13 +763,11 @@ type_setattr(PyObject* typ, const std::string& attr_name, PyObject* value) noexc
|
|
|
703
763
|
}
|
|
704
764
|
|
|
705
765
|
static int
|
|
706
|
-
id_delattr(const std::string& attr_name, PyObject* obj, PyObject* typ) noexcept
|
|
766
|
+
id_delattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyObject* typ) noexcept
|
|
707
767
|
{
|
|
708
|
-
uintptr_t obj_id
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
final_id = type_set_attr_long_long_guidance(typ_id, attr_name);
|
|
712
|
-
FinalObject final_object = type_get_final_attr(typ_id, attr_name);
|
|
768
|
+
uintptr_t obj_id = (uintptr_t) obj;
|
|
769
|
+
// Class-level fallback resolves per-subject (the instance's type).
|
|
770
|
+
FinalObject final_object = type_get_final_attr((uintptr_t)typ, attr_name);
|
|
713
771
|
if (final_object.status == -2) {
|
|
714
772
|
return -1;
|
|
715
773
|
}
|
|
@@ -751,8 +809,9 @@ id_delattr(const std::string& attr_name, PyObject* obj, PyObject* typ) noexcept
|
|
|
751
809
|
}
|
|
752
810
|
if (result) {
|
|
753
811
|
PyTypeObject* type = Py_TYPE(result);
|
|
754
|
-
|
|
755
|
-
|
|
812
|
+
auto funcptr = type->tp_descr_set;
|
|
813
|
+
if (funcptr) {
|
|
814
|
+
if (funcptr(result, result, NULL) < 0) {
|
|
756
815
|
return -1;
|
|
757
816
|
}
|
|
758
817
|
return 0;
|
|
@@ -920,12 +979,12 @@ static PyObject*
|
|
|
920
979
|
PrivateWrap_qualname(PyObject* obj, void* /*closure*/) noexcept
|
|
921
980
|
{
|
|
922
981
|
if (!obj) {
|
|
923
|
-
return PyUnicode_InternFromString("_PrivateWrap");
|
|
982
|
+
return PyUnicode_InternFromString("private_attribute._PrivateWrap");
|
|
924
983
|
}
|
|
925
984
|
PyObject* qualname = PyObject_GetAttrString(((PrivateWrapObject*)obj)->result, "__qualname__");
|
|
926
985
|
if (!qualname) {
|
|
927
986
|
PyErr_Clear();
|
|
928
|
-
return PyUnicode_InternFromString("_PrivateWrap");
|
|
987
|
+
return PyUnicode_InternFromString("private_attribute._PrivateWrap");
|
|
929
988
|
}
|
|
930
989
|
return qualname;
|
|
931
990
|
}
|
|
@@ -1012,8 +1071,9 @@ static PyObject*
|
|
|
1012
1071
|
PrivateWrap_descrget(PyObject* obj, PyObject* name, PyObject* cls) noexcept
|
|
1013
1072
|
{
|
|
1014
1073
|
PyObject* result = ((PrivateWrapObject*)obj)->result;
|
|
1015
|
-
|
|
1016
|
-
|
|
1074
|
+
auto funcptr = Py_TYPE(result)->tp_descr_get;
|
|
1075
|
+
if (funcptr) {
|
|
1076
|
+
return funcptr(result, name, cls);
|
|
1017
1077
|
} else {
|
|
1018
1078
|
Py_INCREF(result);
|
|
1019
1079
|
return result;
|
|
@@ -1024,8 +1084,9 @@ static int
|
|
|
1024
1084
|
PrivateWrap_descrset(PyObject* obj, PyObject* name, PyObject* value) noexcept
|
|
1025
1085
|
{
|
|
1026
1086
|
PyObject* result = ((PrivateWrapObject*)obj)->result;
|
|
1027
|
-
|
|
1028
|
-
|
|
1087
|
+
auto funcptr = Py_TYPE(result)->tp_descr_set;
|
|
1088
|
+
if (funcptr) {
|
|
1089
|
+
return funcptr(result, name, value);
|
|
1029
1090
|
} else {
|
|
1030
1091
|
PyErr_SetString(PyExc_AttributeError, "attribute is not settable");
|
|
1031
1092
|
return -1;
|
|
@@ -1275,14 +1336,21 @@ PrivateAttr_tp_getattro(PyObject* self, PyObject* name) noexcept
|
|
|
1275
1336
|
std::string name_str = PyUnicode_AsUTF8(name);
|
|
1276
1337
|
auto code = get_now_code();
|
|
1277
1338
|
if (type_private_attr(type_id, name_str)) {
|
|
1278
|
-
if (!code
|
|
1339
|
+
if (!code) {
|
|
1279
1340
|
Py_XDECREF(code);
|
|
1280
1341
|
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
1281
1342
|
return NULL;
|
|
1282
|
-
} else {
|
|
1283
|
-
Py_XDECREF(code);
|
|
1284
|
-
return id_getattr(name_str, self, (PyObject*)typ);
|
|
1285
1343
|
}
|
|
1344
|
+
AttrClassifyResult classify = attr_classify(type_id, name_str, code);
|
|
1345
|
+
Py_XDECREF(code);
|
|
1346
|
+
if (!classify.allowed) {
|
|
1347
|
+
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
1348
|
+
return NULL;
|
|
1349
|
+
}
|
|
1350
|
+
if (classify.type_id != 0) {
|
|
1351
|
+
return id_getattr(classify.type_id, name_str, self, (PyObject*)typ);
|
|
1352
|
+
}
|
|
1353
|
+
// owner is 0: not private for this access context -> normal attribute
|
|
1286
1354
|
}
|
|
1287
1355
|
Py_XDECREF(code);
|
|
1288
1356
|
if (::AllData::all_type_getattro.find(type_id) != ::AllData::all_type_getattro.end()){
|
|
@@ -1307,17 +1375,24 @@ PrivateAttr_tp_setattro(PyObject* self, PyObject* name, PyObject* value) noexcep
|
|
|
1307
1375
|
std::string name_str(c_name);
|
|
1308
1376
|
auto code = get_now_code();
|
|
1309
1377
|
if (type_private_attr(typ_id, name_str)) {
|
|
1310
|
-
if (!code
|
|
1378
|
+
if (!code) {
|
|
1311
1379
|
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
1312
1380
|
Py_XDECREF(code);
|
|
1313
1381
|
return -1;
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1382
|
+
}
|
|
1383
|
+
AttrClassifyResult classify = attr_classify(typ_id, name_str, code);
|
|
1384
|
+
Py_XDECREF(code);
|
|
1385
|
+
if (!classify.allowed) {
|
|
1386
|
+
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
1387
|
+
return -1;
|
|
1388
|
+
}
|
|
1389
|
+
if (classify.type_id != 0) {
|
|
1316
1390
|
if (!value) {
|
|
1317
|
-
return id_delattr(name_str, self, (PyObject*)typ);
|
|
1391
|
+
return id_delattr(classify.type_id, name_str, self, (PyObject*)typ);
|
|
1318
1392
|
}
|
|
1319
|
-
return id_setattr(name_str, self, (PyObject*)typ, value);
|
|
1393
|
+
return id_setattr(classify.type_id, name_str, self, (PyObject*)typ, value);
|
|
1320
1394
|
}
|
|
1395
|
+
// owner is 0: not private for this access context -> normal attribute
|
|
1321
1396
|
}
|
|
1322
1397
|
Py_XDECREF(code);
|
|
1323
1398
|
if (::AllData::all_type_setattro.find(typ_id) != ::AllData::all_type_setattro.end()){
|
|
@@ -1790,6 +1865,10 @@ get_string_hash_tuple2(const std::string& name) noexcept
|
|
|
1790
1865
|
static FinalObject
|
|
1791
1866
|
type_get_final_attr(uintptr_t type_id, const std::string& name) noexcept
|
|
1792
1867
|
{
|
|
1868
|
+
// type_id is the SUBJECT: the class whose attribute is being accessed.
|
|
1869
|
+
// The walk resolves per-subject: a subclass's separately stored value
|
|
1870
|
+
// (all_type_subclass_attr[parent][subclass]) shadows the parent's own
|
|
1871
|
+
// class-level value, satisfying class-level separate storage.
|
|
1793
1872
|
TwoStringTuple hash_tuple = get_string_hash_tuple2(name);
|
|
1794
1873
|
if (::AllData::all_type_attr_set.find(type_id) != ::AllData::all_type_attr_set.end()) {
|
|
1795
1874
|
if (::AllData::all_type_attr_set[type_id].find(hash_tuple) != ::AllData::all_type_attr_set[type_id].end()) {
|
|
@@ -2078,6 +2157,8 @@ struct PrivateAttrCreationData
|
|
|
2078
2157
|
std::unordered_set<std::string> private_attrs_vector_string;
|
|
2079
2158
|
std::vector<uintptr_t> all_need_analyse_base;
|
|
2080
2159
|
std::unordered_map<std::string, PyObjectStorage> need_remove_itself;
|
|
2160
|
+
// Class-body attributes whose name is private to a parent class: stored
|
|
2161
|
+
// separately in all_type_subclass_attr[parent][this class].
|
|
2081
2162
|
std::unordered_map<uintptr_t, std::unordered_map<std::string, PyObjectStorage>> need_remove_subclass;
|
|
2082
2163
|
PyObject* private_func = nullptr;
|
|
2083
2164
|
PyObject* base_kwds = nullptr;
|
|
@@ -2359,6 +2440,9 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
|
|
|
2359
2440
|
}
|
|
2360
2441
|
uintptr_t need_remove_subclass_id = need_remove_to_subclass(attr_name);
|
|
2361
2442
|
if (need_remove_subclass_id) {
|
|
2443
|
+
// The class body defines an attribute whose name is private to a
|
|
2444
|
+
// parent class. It is stored separately (class-level separate
|
|
2445
|
+
// storage) in all_type_subclass_attr[need_remove_subclass_id][this].
|
|
2362
2446
|
if (data.need_remove_subclass.find(need_remove_subclass_id) == data.need_remove_subclass.end()) {
|
|
2363
2447
|
data.need_remove_subclass[need_remove_subclass_id] = {};
|
|
2364
2448
|
}
|
|
@@ -2655,6 +2739,10 @@ PrivateAttrType_postprocess(PyObject* new_type, PrivateAttrCreationData& data) n
|
|
|
2655
2739
|
}
|
|
2656
2740
|
|
|
2657
2741
|
if (data.private_func) {
|
|
2742
|
+
// PyObjectStorage::operator=(PyObject*) takes its own reference here;
|
|
2743
|
+
// data's reference is released by PrivateAttrCreationData::clear()
|
|
2744
|
+
// right after this, and the map entry's reference is released when the
|
|
2745
|
+
// entry is erased in PrivateAttrType_finalize.
|
|
2658
2746
|
::AllData::type_need_call[type_id] = data.private_func;
|
|
2659
2747
|
}
|
|
2660
2748
|
|
|
@@ -2744,13 +2832,21 @@ PrivateAttrType_getattr(PyObject* cls, PyObject* name) noexcept
|
|
|
2744
2832
|
std::string name_str = PyUnicode_AsUTF8(name);
|
|
2745
2833
|
PyCodeObject* now_code = get_now_code();
|
|
2746
2834
|
if (type_private_attr(typ_id, name_str)) {
|
|
2747
|
-
if (!now_code
|
|
2835
|
+
if (!now_code) {
|
|
2748
2836
|
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
2749
2837
|
Py_XDECREF(now_code);
|
|
2750
2838
|
return NULL;
|
|
2751
2839
|
}
|
|
2840
|
+
AttrClassifyResult classify = attr_classify(typ_id, name_str, now_code);
|
|
2752
2841
|
Py_XDECREF(now_code);
|
|
2753
|
-
|
|
2842
|
+
if (!classify.allowed) {
|
|
2843
|
+
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
2844
|
+
return NULL;
|
|
2845
|
+
}
|
|
2846
|
+
if (classify.type_id != 0) {
|
|
2847
|
+
return type_getattr(cls, name_str);
|
|
2848
|
+
}
|
|
2849
|
+
// owner is 0: not private for this access context -> normal attribute
|
|
2754
2850
|
}
|
|
2755
2851
|
Py_XDECREF(now_code);
|
|
2756
2852
|
PyTypeObject* base = Py_TYPE(cls)->tp_base;
|
|
@@ -3091,13 +3187,21 @@ PrivateAttrType_setattr(PyObject* cls, PyObject* name, PyObject* value) noexcept
|
|
|
3091
3187
|
}
|
|
3092
3188
|
PyCodeObject* now_code = get_now_code();
|
|
3093
3189
|
if (type_private_attr(typ_id, name_str)) {
|
|
3094
|
-
if (!now_code
|
|
3190
|
+
if (!now_code) {
|
|
3095
3191
|
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
3096
3192
|
Py_XDECREF(now_code);
|
|
3097
3193
|
return -1;
|
|
3098
3194
|
}
|
|
3195
|
+
AttrClassifyResult classify = attr_classify(typ_id, name_str, now_code);
|
|
3099
3196
|
Py_XDECREF(now_code);
|
|
3100
|
-
|
|
3197
|
+
if (!classify.allowed) {
|
|
3198
|
+
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
3199
|
+
return -1;
|
|
3200
|
+
}
|
|
3201
|
+
if (classify.type_id != 0) {
|
|
3202
|
+
return type_setattr(cls, name_str, value);
|
|
3203
|
+
}
|
|
3204
|
+
// owner is 0: not private for this access context -> normal attribute
|
|
3101
3205
|
}
|
|
3102
3206
|
Py_XDECREF(now_code);
|
|
3103
3207
|
PyTypeObject* base = Py_TYPE(cls)->tp_base;
|
|
@@ -3127,8 +3231,7 @@ PrivateAttrType_finalize(PyObject* cls) noexcept
|
|
|
3127
3231
|
::AllData::type_allowed_code_map.erase(typ_id);
|
|
3128
3232
|
}
|
|
3129
3233
|
if (::AllData::type_need_call.find(typ_id) != ::AllData::type_need_call.end()) {
|
|
3130
|
-
|
|
3131
|
-
Py_XDECREF(need_call);
|
|
3234
|
+
// erase() destroys the PyObjectStorage, which DECREFs private_func.
|
|
3132
3235
|
::AllData::type_need_call.erase(typ_id);
|
|
3133
3236
|
}
|
|
3134
3237
|
if (::AllData::type_attr_dict.find(typ_id) != ::AllData::type_attr_dict.end()) {
|
|
@@ -3391,6 +3494,11 @@ register_finalize(PyObject* cls) noexcept
|
|
|
3391
3494
|
PrivateAttrType_finalize(cls);
|
|
3392
3495
|
}
|
|
3393
3496
|
|
|
3497
|
+
#define METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(name) do {\
|
|
3498
|
+
if (::AllData::all_type_##name.find(id) != ::AllData::all_type_##name.end()) {\
|
|
3499
|
+
::AllData::all_type_##name.erase(id);\
|
|
3500
|
+
}\
|
|
3501
|
+
} while (0)
|
|
3394
3502
|
|
|
3395
3503
|
static PyObject*
|
|
3396
3504
|
metaclass_weakref_callback(PyObject* self, PyObject* /*weakref*/) noexcept
|
|
@@ -3408,6 +3516,11 @@ metaclass_weakref_callback(PyObject* self, PyObject* /*weakref*/) noexcept
|
|
|
3408
3516
|
Py_DECREF(it->second);
|
|
3409
3517
|
::AllData::all_register_type_weak_ref.erase(it);
|
|
3410
3518
|
}
|
|
3519
|
+
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(getattro);
|
|
3520
|
+
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(setattro);
|
|
3521
|
+
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(traverse);
|
|
3522
|
+
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(clear);
|
|
3523
|
+
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(finalize);
|
|
3411
3524
|
|
|
3412
3525
|
Py_RETURN_NONE;
|
|
3413
3526
|
}
|
|
@@ -3664,6 +3777,19 @@ PrivateModule_get_PrivateAttrBase(PyObject* /*self*/, void* /*closure*/) noexcep
|
|
|
3664
3777
|
return PrivateAttrBase;
|
|
3665
3778
|
}
|
|
3666
3779
|
|
|
3780
|
+
static int
|
|
3781
|
+
PyList_AppendString(PyObject* list, const char* str)
|
|
3782
|
+
{
|
|
3783
|
+
PyObject* obj = PyUnicode_InternFromString(str);
|
|
3784
|
+
if (!obj) return -1;
|
|
3785
|
+
if (PyList_Append(list, obj) < 0) {
|
|
3786
|
+
Py_DECREF(obj);
|
|
3787
|
+
return -1;
|
|
3788
|
+
}
|
|
3789
|
+
Py_DECREF(obj);
|
|
3790
|
+
return 0;
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3667
3793
|
static PyObject*
|
|
3668
3794
|
PrivateModule_dir(PyObject* self, PyObject* /*args*/) noexcept
|
|
3669
3795
|
{
|
|
@@ -3674,14 +3800,14 @@ PrivateModule_dir(PyObject* self, PyObject* /*args*/) noexcept
|
|
|
3674
3800
|
Py_DECREF(parent_dir);
|
|
3675
3801
|
return NULL;
|
|
3676
3802
|
}
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3803
|
+
PyList_AppendString(attr_list, "PrivateWrapProxy");
|
|
3804
|
+
PyList_AppendString(attr_list, "PrivateAttrType");
|
|
3805
|
+
PyList_AppendString(attr_list, "PrivateAttrBase");
|
|
3806
|
+
PyList_AppendString(attr_list, "prepare");
|
|
3807
|
+
PyList_AppendString(attr_list, "postprocess");
|
|
3808
|
+
PyList_AppendString(attr_list, "register_metaclass");
|
|
3809
|
+
PyList_AppendString(attr_list, "ensure_type");
|
|
3810
|
+
PyList_AppendString(attr_list, "ensure_metaclass");
|
|
3685
3811
|
PyObject* result = PySequence_Concat(parent_dir, attr_list);
|
|
3686
3812
|
Py_DECREF(parent_dir);
|
|
3687
3813
|
Py_DECREF(attr_list);
|
|
@@ -3942,14 +4068,14 @@ PyInit_private_attribute(void) noexcept
|
|
|
3942
4068
|
* ensure_type
|
|
3943
4069
|
* ensure_metaclass
|
|
3944
4070
|
*/
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
4071
|
+
PyList_AppendString(all, "PrivateWrapProxy");
|
|
4072
|
+
PyList_AppendString(all, "PrivateAttrType");
|
|
4073
|
+
PyList_AppendString(all, "PrivateAttrBase");
|
|
4074
|
+
PyList_AppendString(all, "prepare");
|
|
4075
|
+
PyList_AppendString(all, "postprocess");
|
|
4076
|
+
PyList_AppendString(all, "register_metaclass");
|
|
4077
|
+
PyList_AppendString(all, "ensure_type");
|
|
4078
|
+
PyList_AppendString(all, "ensure_metaclass");
|
|
3953
4079
|
Py_SET_TYPE(m, &PrivateModuleType);
|
|
3954
4080
|
|
|
3955
4081
|
// Eagerly create PrivateAttrBase so that CPython's subtype_traverse /
|
{private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0/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.0
|
|
3
|
+
Version: 2.1.0
|
|
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
|
|
@@ -250,6 +250,8 @@ TypeError: Can't instantiate abstract class MyClass without an implementation fo
|
|
|
250
250
|
- Don't use a decorator which will return the `_PrivateWrap` in `PrivateWrapProxy` which will raise `TypeError`.
|
|
251
251
|
- One class defined in another class cannot use another class's private attribute.
|
|
252
252
|
- One parent class defined an attribute which not in `__private_attrs__` or not a `PrivateAttrType` instance, the child class shouldn't contain the attribute in its `__private_attrs__`.
|
|
253
|
+
- Since 2.1.0: the code of a subclass can no longer access the private attributes of its parent classes - a parent's private attribute is only reachable from the parent's own code, or from a class that declares the same name in its own `__private_attrs__`.
|
|
254
|
+
- Since 2.1.0: if a subclass defines an attribute with the same name as a parent's private attribute, they are stored separately (instance attributes per declaring class, class-level attributes in `all_type_subclass_attr[parent][subclass]`). Class-level resolution is per-subject through the parent's code: reading the name on a subclass subject returns the subclass's own value, and the parent's own value is untouched. Such a same-name definition does NOT grant the subclass's own code access to the name.
|
|
253
255
|
- CPython may change "tp_getattro", "tp_setattro" and so on when you change the attribute "\_\_getattribute\_\_", "\_\_setattr\_\_" and so on. If you are fear about it, you can use `ensure_type` to reset those tp slots. For the other metaclasses, you can use `ensure_metaclass` to reset those tp slots. Also, don't set those methods on these classes in your code.
|
|
254
256
|
- `private_attribute.register_metaclass` must be called with the metaclass which supports weakref.
|
|
255
257
|
- Don't set `__static_attributes__` in private attribute class, or it will be removed.
|
|
@@ -18,7 +18,7 @@ readme = open('README.md').read()
|
|
|
18
18
|
|
|
19
19
|
setup(
|
|
20
20
|
name='private_attribute_cpp',
|
|
21
|
-
version='2.0
|
|
21
|
+
version='2.1.0',
|
|
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.',
|
|
@@ -26,7 +26,9 @@ class MyClass(ABC, metaclass=PrivateAbcMeta):
|
|
|
26
26
|
pass
|
|
27
27
|
|
|
28
28
|
class ConcreteClass(MyClass):
|
|
29
|
-
|
|
29
|
+
# 2.1.0: a subclass can no longer access a parent's private attribute.
|
|
30
|
+
# If it needs the same name, it declares its own -> stored separately.
|
|
31
|
+
__private_attrs__ = ["_secret1", "_secret2"]
|
|
30
32
|
|
|
31
33
|
def __init__(self, secret1, secret2):
|
|
32
34
|
self._secret1 = secret1
|
|
@@ -49,6 +51,8 @@ class TestPrivateAbc(unittest.TestCase):
|
|
|
49
51
|
with self.assertRaises(AttributeError):
|
|
50
52
|
MyClass._secret1
|
|
51
53
|
self.assertEqual(MyClass.get_secret1(), 10)
|
|
54
|
+
# get_secret1 is MyClass's own code, so it reads MyClass's private
|
|
55
|
+
# attribute even when called through the subclass.
|
|
52
56
|
self.assertEqual(ConcreteClass.get_secret1(), 10)
|
|
53
57
|
|
|
54
58
|
if __name__ == '__main__':
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import private_attribute
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
def my_func(obj_id, attr):
|
|
5
|
+
return f"Object ID: {obj_id}, String: {attr}"
|
|
6
|
+
|
|
7
|
+
class MyClass(private_attribute.PrivateAttrBase, private_func=my_func):
|
|
8
|
+
__private_attrs__ = ["_secret"]
|
|
9
|
+
__slots__ = ["public_attr"]
|
|
10
|
+
|
|
11
|
+
def __init__(self, secret, public_attr):
|
|
12
|
+
self._secret = secret
|
|
13
|
+
self.public_attr = public_attr
|
|
14
|
+
|
|
15
|
+
def get_secret(self):
|
|
16
|
+
return self._secret
|
|
17
|
+
|
|
18
|
+
class TestPrivateFunc(unittest.TestCase):
|
|
19
|
+
def test_private_func(self):
|
|
20
|
+
obj = MyClass("12345", "public_value")
|
|
21
|
+
self.assertEqual(obj.get_secret(), "12345")
|
|
22
|
+
self.assertEqual(obj.public_attr, "public_value")
|
|
23
|
+
with self.assertRaises(AttributeError):
|
|
24
|
+
obj._secret
|
|
25
|
+
|
|
26
|
+
def test_private_func_local_function_gc(self):
|
|
27
|
+
# Regression: a class created with a LOCAL private_func must not leave
|
|
28
|
+
# a dangling pointer in type_need_call after the function is collected.
|
|
29
|
+
# Previously this crashed with a segfault in PrivateAttrType_finalize
|
|
30
|
+
# during gc.collect().
|
|
31
|
+
import gc
|
|
32
|
+
|
|
33
|
+
def local_func(obj_id, attr):
|
|
34
|
+
return f"local_{obj_id}_{attr}"
|
|
35
|
+
|
|
36
|
+
class LocalParent(private_attribute.PrivateAttrBase, private_func=local_func):
|
|
37
|
+
__private_attrs__ = ["_v"]
|
|
38
|
+
|
|
39
|
+
def __init__(self):
|
|
40
|
+
self._v = 1
|
|
41
|
+
|
|
42
|
+
def get_v(self):
|
|
43
|
+
return self._v
|
|
44
|
+
|
|
45
|
+
class LocalChild(LocalParent):
|
|
46
|
+
__private_attrs__ = []
|
|
47
|
+
|
|
48
|
+
obj = LocalChild()
|
|
49
|
+
self.assertEqual(obj.get_v(), 1)
|
|
50
|
+
del obj
|
|
51
|
+
# drop the local classes and the local function, then collect
|
|
52
|
+
del LocalChild, LocalParent, local_func
|
|
53
|
+
for _ in range(3):
|
|
54
|
+
gc.collect()
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
unittest.main()
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import private_attribute
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
class BaseClass(private_attribute.PrivateAttrBase):
|
|
5
|
+
__private_attrs__ = ["_value"]
|
|
6
|
+
__slots__ = []
|
|
7
|
+
|
|
8
|
+
def __init__(self, value):
|
|
9
|
+
super().__init__()
|
|
10
|
+
self._value = value
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def value(self):
|
|
14
|
+
return self._value
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SubClass(BaseClass):
|
|
18
|
+
__private_attrs__ = ["_other_value"]
|
|
19
|
+
__slots__ = []
|
|
20
|
+
|
|
21
|
+
def __init__(self, value, other_value):
|
|
22
|
+
super().__init__(value)
|
|
23
|
+
self._other_value = other_value
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def other_value(self):
|
|
27
|
+
return self._other_value
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestPrivateInheritance(unittest.TestCase):
|
|
31
|
+
def test_private_inheritance(self):
|
|
32
|
+
obj = SubClass(1, 2)
|
|
33
|
+
self.assertEqual(obj.value, 1)
|
|
34
|
+
self.assertEqual(obj.other_value, 2)
|
|
35
|
+
with self.assertRaises(AttributeError):
|
|
36
|
+
obj._value
|
|
37
|
+
with self.assertRaises(AttributeError):
|
|
38
|
+
obj._other_value
|
|
39
|
+
|
|
40
|
+
def test_subclass_cannot_access_parent_private(self):
|
|
41
|
+
# 2.1.0: code of a subclass must not touch a parent's private attribute.
|
|
42
|
+
class Parent(private_attribute.PrivateAttrBase):
|
|
43
|
+
__private_attrs__ = ["_secret"]
|
|
44
|
+
|
|
45
|
+
def __init__(self):
|
|
46
|
+
self._secret = 1
|
|
47
|
+
|
|
48
|
+
class Child(Parent):
|
|
49
|
+
__private_attrs__ = []
|
|
50
|
+
|
|
51
|
+
def read_parent_secret(self):
|
|
52
|
+
return self._secret
|
|
53
|
+
|
|
54
|
+
def write_parent_secret(self):
|
|
55
|
+
self._secret = 2
|
|
56
|
+
|
|
57
|
+
obj = Child()
|
|
58
|
+
with self.assertRaises(AttributeError):
|
|
59
|
+
obj.read_parent_secret()
|
|
60
|
+
with self.assertRaises(AttributeError):
|
|
61
|
+
obj.write_parent_secret()
|
|
62
|
+
|
|
63
|
+
def test_same_name_instance_attrs_stored_separately(self):
|
|
64
|
+
# 2.1.0: subclass and parent may both declare the same private name;
|
|
65
|
+
# each class's code accesses its own storage.
|
|
66
|
+
class Parent(private_attribute.PrivateAttrBase):
|
|
67
|
+
__private_attrs__ = ["_value"]
|
|
68
|
+
|
|
69
|
+
def __init__(self, value):
|
|
70
|
+
self._value = value
|
|
71
|
+
|
|
72
|
+
def get_parent_value(self):
|
|
73
|
+
return self._value
|
|
74
|
+
|
|
75
|
+
class Child(Parent):
|
|
76
|
+
__private_attrs__ = ["_value"]
|
|
77
|
+
|
|
78
|
+
def __init__(self, value):
|
|
79
|
+
super().__init__(value) # writes Parent's _value
|
|
80
|
+
self._value = value * 10 # writes Child's _value
|
|
81
|
+
|
|
82
|
+
def get_child_value(self):
|
|
83
|
+
return self._value
|
|
84
|
+
|
|
85
|
+
obj = Child(3)
|
|
86
|
+
self.assertEqual(obj.get_parent_value(), 3)
|
|
87
|
+
self.assertEqual(obj.get_child_value(), 30)
|
|
88
|
+
with self.assertRaises(AttributeError):
|
|
89
|
+
obj._value
|
|
90
|
+
|
|
91
|
+
def test_same_name_class_attrs_stored_separately(self):
|
|
92
|
+
# A class-body attribute reusing a parent's private name is stored
|
|
93
|
+
# separately (all_type_subclass_attr[parent][child]) and resolves
|
|
94
|
+
# per-subject through the PARENT's code. It does NOT grant the
|
|
95
|
+
# subclass's own code any access to the name.
|
|
96
|
+
class Parent(private_attribute.PrivateAttrBase):
|
|
97
|
+
__private_attrs__ = ["_value"]
|
|
98
|
+
_value = 10
|
|
99
|
+
|
|
100
|
+
def get_value(self):
|
|
101
|
+
return self._value
|
|
102
|
+
|
|
103
|
+
class Child(Parent):
|
|
104
|
+
__private_attrs__ = []
|
|
105
|
+
_value = 20
|
|
106
|
+
|
|
107
|
+
def get_child_value(self):
|
|
108
|
+
return self._value
|
|
109
|
+
|
|
110
|
+
self.assertEqual(Parent().get_value(), 10)
|
|
111
|
+
obj = Child()
|
|
112
|
+
# parent's code resolves per-subject: the child's own value
|
|
113
|
+
self.assertEqual(obj.get_value(), 20)
|
|
114
|
+
# the subclass's own code is NOT allowed to touch the name
|
|
115
|
+
with self.assertRaises(AttributeError):
|
|
116
|
+
obj.get_child_value()
|
|
117
|
+
with self.assertRaises(AttributeError):
|
|
118
|
+
obj._value
|
|
119
|
+
with self.assertRaises(AttributeError):
|
|
120
|
+
Child._value
|
|
121
|
+
|
|
122
|
+
def test_same_name_class_attrs_write_separately(self):
|
|
123
|
+
# Writes through the parent's code go to the subject's own storage;
|
|
124
|
+
# the subclass's own code cannot write the name at all.
|
|
125
|
+
class Parent(private_attribute.PrivateAttrBase):
|
|
126
|
+
__private_attrs__ = ["_value"]
|
|
127
|
+
_value = 1
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def set_value(cls, v):
|
|
131
|
+
cls._value = v
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def get_value(cls):
|
|
135
|
+
return cls._value
|
|
136
|
+
|
|
137
|
+
class Child(Parent):
|
|
138
|
+
__private_attrs__ = []
|
|
139
|
+
_value = 2
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def set_own(cls, v):
|
|
143
|
+
cls._value = v
|
|
144
|
+
|
|
145
|
+
self.assertEqual(Parent.get_value(), 1)
|
|
146
|
+
self.assertEqual(Child.get_value(), 2)
|
|
147
|
+
# parent's classmethod called on the child writes the child's own storage
|
|
148
|
+
Child.set_value(99)
|
|
149
|
+
self.assertEqual(Child.get_value(), 99)
|
|
150
|
+
self.assertEqual(Parent.get_value(), 1)
|
|
151
|
+
# the subclass's own code cannot write the parent's private name
|
|
152
|
+
with self.assertRaises(AttributeError):
|
|
153
|
+
Child.set_own(7)
|
|
154
|
+
self.assertEqual(Child.get_value(), 99)
|
|
155
|
+
# parent's own storage still writable through the parent
|
|
156
|
+
Parent.set_value(5)
|
|
157
|
+
self.assertEqual(Parent.get_value(), 5)
|
|
158
|
+
self.assertEqual(Child.get_value(), 99)
|
|
159
|
+
|
|
160
|
+
def test_parent_code_still_works_on_subclass_instance(self):
|
|
161
|
+
# Parent's own methods keep working on subclass instances.
|
|
162
|
+
class Parent(private_attribute.PrivateAttrBase):
|
|
163
|
+
__private_attrs__ = ["_value"]
|
|
164
|
+
|
|
165
|
+
def __init__(self, value):
|
|
166
|
+
self._value = value
|
|
167
|
+
|
|
168
|
+
def get_value(self):
|
|
169
|
+
return self._value
|
|
170
|
+
|
|
171
|
+
class Child(Parent):
|
|
172
|
+
__private_attrs__ = ["_other"]
|
|
173
|
+
|
|
174
|
+
def __init__(self, value, other):
|
|
175
|
+
super().__init__(value)
|
|
176
|
+
self._other = other
|
|
177
|
+
|
|
178
|
+
def get_other(self):
|
|
179
|
+
return self._other
|
|
180
|
+
|
|
181
|
+
obj = Child(5, 6)
|
|
182
|
+
self.assertEqual(obj.get_value(), 5)
|
|
183
|
+
self.assertEqual(obj.get_other(), 6)
|
|
184
|
+
|
|
185
|
+
def test_multi_level_inheritance(self):
|
|
186
|
+
# A private attribute is only reachable from the code of the class
|
|
187
|
+
# that declares it - not from any of its subclasses.
|
|
188
|
+
class GrandParent(private_attribute.PrivateAttrBase):
|
|
189
|
+
__private_attrs__ = ["_g"]
|
|
190
|
+
|
|
191
|
+
def __init__(self):
|
|
192
|
+
self._g = 1
|
|
193
|
+
|
|
194
|
+
def get_g(self):
|
|
195
|
+
return self._g
|
|
196
|
+
|
|
197
|
+
class Parent(GrandParent):
|
|
198
|
+
__private_attrs__ = ["_p"]
|
|
199
|
+
|
|
200
|
+
def __init__(self):
|
|
201
|
+
super().__init__()
|
|
202
|
+
self._p = 2
|
|
203
|
+
|
|
204
|
+
def get_p(self):
|
|
205
|
+
return self._p
|
|
206
|
+
|
|
207
|
+
def try_grandparent_private(self):
|
|
208
|
+
return self._g
|
|
209
|
+
|
|
210
|
+
class Child(Parent):
|
|
211
|
+
__private_attrs__ = []
|
|
212
|
+
|
|
213
|
+
def try_parent_private(self):
|
|
214
|
+
return self._p
|
|
215
|
+
|
|
216
|
+
def try_grandparent_private(self):
|
|
217
|
+
return self._g
|
|
218
|
+
|
|
219
|
+
obj = Child()
|
|
220
|
+
self.assertEqual(obj.get_g(), 1)
|
|
221
|
+
self.assertEqual(obj.get_p(), 2)
|
|
222
|
+
with self.assertRaises(AttributeError):
|
|
223
|
+
obj.try_parent_private()
|
|
224
|
+
with self.assertRaises(AttributeError):
|
|
225
|
+
obj.try_grandparent_private()
|
|
226
|
+
with self.assertRaises(AttributeError):
|
|
227
|
+
Parent().try_grandparent_private()
|
|
228
|
+
|
|
229
|
+
def test_multiple_inheritance(self):
|
|
230
|
+
class Left(private_attribute.PrivateAttrBase):
|
|
231
|
+
__private_attrs__ = ["_value"]
|
|
232
|
+
|
|
233
|
+
def __init__(self):
|
|
234
|
+
self._value = "left"
|
|
235
|
+
|
|
236
|
+
def get_value(self):
|
|
237
|
+
return self._value
|
|
238
|
+
|
|
239
|
+
class Right(private_attribute.PrivateAttrBase):
|
|
240
|
+
__private_attrs__ = ["_value"]
|
|
241
|
+
|
|
242
|
+
def __init__(self):
|
|
243
|
+
self._value = "right"
|
|
244
|
+
|
|
245
|
+
def get_value(self):
|
|
246
|
+
return self._value
|
|
247
|
+
|
|
248
|
+
class Both(Left, Right):
|
|
249
|
+
__private_attrs__ = ["_value"]
|
|
250
|
+
|
|
251
|
+
def __init__(self):
|
|
252
|
+
Left.__init__(self)
|
|
253
|
+
Right.__init__(self)
|
|
254
|
+
self._value = "both"
|
|
255
|
+
|
|
256
|
+
def get_value(self):
|
|
257
|
+
return self._value
|
|
258
|
+
|
|
259
|
+
obj = Both()
|
|
260
|
+
self.assertEqual(Left.get_value(obj), "left")
|
|
261
|
+
self.assertEqual(Right.get_value(obj), "right")
|
|
262
|
+
self.assertEqual(obj.get_value(), "both")
|
|
263
|
+
with self.assertRaises(AttributeError):
|
|
264
|
+
obj._value
|
|
265
|
+
|
|
266
|
+
def test_private_func_with_inheritance(self):
|
|
267
|
+
def gen(obj_id, attr_name):
|
|
268
|
+
return f"<{attr_name}@{obj_id}>"
|
|
269
|
+
|
|
270
|
+
class Parent(private_attribute.PrivateAttrBase, private_func=gen):
|
|
271
|
+
__private_attrs__ = ["_value"]
|
|
272
|
+
|
|
273
|
+
def __init__(self, value):
|
|
274
|
+
self._value = value
|
|
275
|
+
|
|
276
|
+
def get_value(self):
|
|
277
|
+
return self._value
|
|
278
|
+
|
|
279
|
+
class Child(Parent):
|
|
280
|
+
__private_attrs__ = ["_value"]
|
|
281
|
+
|
|
282
|
+
def __init__(self, value):
|
|
283
|
+
super().__init__(value)
|
|
284
|
+
self._value = value * 2
|
|
285
|
+
|
|
286
|
+
def get_value(self):
|
|
287
|
+
return self._value
|
|
288
|
+
|
|
289
|
+
obj = Child(4)
|
|
290
|
+
self.assertEqual(Parent.get_value(obj), 4)
|
|
291
|
+
self.assertEqual(obj.get_value(), 8)
|
|
292
|
+
|
|
293
|
+
def test_class_level_del_from_own_code(self):
|
|
294
|
+
class Parent(private_attribute.PrivateAttrBase):
|
|
295
|
+
__private_attrs__ = ["_value"]
|
|
296
|
+
_value = 1
|
|
297
|
+
|
|
298
|
+
@classmethod
|
|
299
|
+
def del_value(cls):
|
|
300
|
+
del cls._value
|
|
301
|
+
|
|
302
|
+
@classmethod
|
|
303
|
+
def set_value(cls, v):
|
|
304
|
+
cls._value = v
|
|
305
|
+
|
|
306
|
+
@classmethod
|
|
307
|
+
def get_value(cls):
|
|
308
|
+
return cls._value
|
|
309
|
+
|
|
310
|
+
self.assertEqual(Parent.get_value(), 1)
|
|
311
|
+
Parent.del_value()
|
|
312
|
+
with self.assertRaises(AttributeError):
|
|
313
|
+
Parent.get_value()
|
|
314
|
+
# setting again works after deletion
|
|
315
|
+
Parent.set_value(99)
|
|
316
|
+
self.assertEqual(Parent.get_value(), 99)
|
|
317
|
+
|
|
318
|
+
if __name__ == "__main__":
|
|
319
|
+
unittest.main()
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import private_attribute
|
|
2
|
-
import unittest
|
|
3
|
-
|
|
4
|
-
def my_func(obj_id, attr):
|
|
5
|
-
return f"Object ID: {obj_id}, String: {attr}"
|
|
6
|
-
|
|
7
|
-
class MyClass(private_attribute.PrivateAttrBase, private_func=my_func):
|
|
8
|
-
__private_attrs__ = ["_secret"]
|
|
9
|
-
__slots__ = ["public_attr"]
|
|
10
|
-
|
|
11
|
-
def __init__(self, secret, public_attr):
|
|
12
|
-
self._secret = secret
|
|
13
|
-
self.public_attr = public_attr
|
|
14
|
-
|
|
15
|
-
def get_secret(self):
|
|
16
|
-
return self._secret
|
|
17
|
-
|
|
18
|
-
class TestPrivateFunc(unittest.TestCase):
|
|
19
|
-
def test_private_func(self):
|
|
20
|
-
obj = MyClass("12345", "public_value")
|
|
21
|
-
self.assertEqual(obj.get_secret(), "12345")
|
|
22
|
-
self.assertEqual(obj.public_attr, "public_value")
|
|
23
|
-
with self.assertRaises(AttributeError):
|
|
24
|
-
obj._secret
|
|
25
|
-
|
|
26
|
-
if __name__ == "__main__":
|
|
27
|
-
unittest.main()
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import private_attribute
|
|
2
|
-
import unittest
|
|
3
|
-
|
|
4
|
-
class BaseClass(private_attribute.PrivateAttrBase):
|
|
5
|
-
__private_attrs__ = ["_value"]
|
|
6
|
-
__slots__ = []
|
|
7
|
-
|
|
8
|
-
def __init__(self, value):
|
|
9
|
-
super().__init__()
|
|
10
|
-
self._value = value
|
|
11
|
-
|
|
12
|
-
class SubClass(BaseClass):
|
|
13
|
-
__private_attrs__ = ["_other_value"]
|
|
14
|
-
__slots__ = []
|
|
15
|
-
|
|
16
|
-
def __init__(self, value, other_value):
|
|
17
|
-
super().__init__(value)
|
|
18
|
-
self._other_value = other_value
|
|
19
|
-
|
|
20
|
-
@property
|
|
21
|
-
def value(self):
|
|
22
|
-
return self._value
|
|
23
|
-
|
|
24
|
-
@property
|
|
25
|
-
def other_value(self):
|
|
26
|
-
return self._other_value
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class TestPrivateInheritance(unittest.TestCase):
|
|
30
|
-
def test_private_inheritance(self):
|
|
31
|
-
obj = SubClass(1, 2)
|
|
32
|
-
self.assertEqual(obj.value, 1)
|
|
33
|
-
self.assertEqual(obj.other_value, 2)
|
|
34
|
-
with self.assertRaises(AttributeError):
|
|
35
|
-
obj._value
|
|
36
|
-
with self.assertRaises(AttributeError):
|
|
37
|
-
obj._other_value
|
|
38
|
-
|
|
39
|
-
if __name__ == "__main__":
|
|
40
|
-
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.0.5 → private_attribute_cpp-2.1.0}/tests/test_class_private_attrs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|