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.
Files changed (23) hide show
  1. {private_attribute_cpp-2.0.5/private_attribute_cpp.egg-info → private_attribute_cpp-2.1.0}/PKG-INFO +3 -1
  2. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/README.md +2 -0
  3. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/private_attribute.cpp +209 -83
  4. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0/private_attribute_cpp.egg-info}/PKG-INFO +3 -1
  5. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/setup.py +1 -1
  6. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/tests/test_private_abc.py +5 -1
  7. private_attribute_cpp-2.1.0/tests/test_private_func.py +57 -0
  8. private_attribute_cpp-2.1.0/tests/test_private_inheritance.py +319 -0
  9. private_attribute_cpp-2.0.5/tests/test_private_func.py +0 -27
  10. private_attribute_cpp-2.0.5/tests/test_private_inheritance.py +0 -40
  11. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/LICENSE +0 -0
  12. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/MANIFEST.in +0 -0
  13. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/picosha2.h +0 -0
  14. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/private_attribute.pyi +0 -0
  15. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/private_attribute_cpp.egg-info/SOURCES.txt +0 -0
  16. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/private_attribute_cpp.egg-info/dependency_links.txt +0 -0
  17. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/private_attribute_cpp.egg-info/not-zip-safe +0 -0
  18. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/private_attribute_cpp.egg-info/top_level.txt +0 -0
  19. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/setup.cfg +0 -0
  20. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/tests/test_class_private_attrs.py +0 -0
  21. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/tests/test_private_attrs.py +0 -0
  22. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/tests/test_private_method.py +0 -0
  23. {private_attribute_cpp-2.0.5 → private_attribute_cpp-2.1.0}/tests/test_private_wrap.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: private_attribute_cpp
3
- Version: 2.0.5
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
- static std::unordered_map<uintptr_t, PyObject*> type_need_call;
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 bool
271
+ static AttrClassifyResult
255
272
  attr_classify(uintptr_t typ_id, const std::string& name, PyCodeObject* code) noexcept
256
273
  {
257
- if (is_class_code(typ_id, code)) {
258
- return true;
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
- if (is_type_private(typ_id, name)) {
261
- return false;
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
- if (::AllData::all_type_parent_id.find(typ_id) != ::AllData::all_type_parent_id.end()) {
264
- std::vector<uintptr_t> parent_ids = ::AllData::all_type_parent_id[typ_id];
265
- for (auto& parent_id : parent_ids) {
266
- if (is_class_code(parent_id, code)) {
267
- return true;
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
- if (is_type_private(parent_id, name)) {
270
- return false;
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
- return true;
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, typ_id, final_id;
443
- obj_id = (uintptr_t) obj;
444
- typ_id = (uintptr_t) typ;
445
- final_id = type_set_attr_long_long_guidance(typ_id, attr_name);
446
- FinalObject final_object = type_get_final_attr(typ_id, attr_name);
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
- has_get = result_typ->tp_descr_get != NULL;
551
+ get = result_typ->tp_descr_get;
495
552
  has_set = result_typ->tp_descr_set != NULL;
496
553
  }
497
- if (has_get && has_set) {
498
- PyObject* final_result = result_typ->tp_descr_get(result, obj, typ);
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
- if (obj_type->tp_descr_get != NULL) {
572
+ auto funcptr = obj_type->tp_descr_get;
573
+ if (funcptr != NULL) {
516
574
  lock.unlock();
517
- return obj_type->tp_descr_get(python_obj, Py_None, obj);
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 (has_get) {
526
- PyObject* final_result = result_typ->tp_descr_get(result, obj, typ);
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
- if (type->tp_descr_get != NULL) {
558
- PyObject* final_result = type->tp_descr_get(result, Py_None, (PyObject*)type);
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, typ_id, final_id;
579
- obj_id = (uintptr_t) obj;
580
- typ_id = (uintptr_t) typ;
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
- if (type->tp_descr_set != NULL) {
625
- if (type->tp_descr_set(result, obj, value) < 0) {
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, typ_id, final_id;
709
- obj_id = (uintptr_t) obj;
710
- typ_id = (uintptr_t) typ;
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
- if (type->tp_descr_set != NULL) {
755
- if (type->tp_descr_set(result, result, NULL) < 0) {
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
- if (Py_TYPE(result)->tp_descr_get) {
1016
- return Py_TYPE(result)->tp_descr_get(result, name, cls);
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
- if (Py_TYPE(result)->tp_descr_set) {
1028
- return Py_TYPE(result)->tp_descr_set(result, name, value);
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 || !attr_classify(type_id, name_str, 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 || !attr_classify(typ_id, name_str, code)){
1378
+ if (!code) {
1311
1379
  PyErr_SetString(PyExc_AttributeError, "private attribute");
1312
1380
  Py_XDECREF(code);
1313
1381
  return -1;
1314
- } else {
1315
- Py_XDECREF(code);
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 || !attr_classify(typ_id, name_str, 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
- return type_getattr(cls, name_str);
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 || !attr_classify(typ_id, name_str, 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
- return type_setattr(cls, name_str, value);
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
- auto& need_call = ::AllData::type_need_call[typ_id];
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
- PyList_Append(attr_list, PyUnicode_InternFromString("PrivateWrapProxy"));
3678
- PyList_Append(attr_list, PyUnicode_InternFromString("PrivateAttrType"));
3679
- PyList_Append(attr_list, PyUnicode_InternFromString("PrivateAttrBase"));
3680
- PyList_Append(attr_list, PyUnicode_InternFromString("prepare"));
3681
- PyList_Append(attr_list, PyUnicode_InternFromString("postprocess"));
3682
- PyList_Append(attr_list, PyUnicode_InternFromString("register_metaclass"));
3683
- PyList_Append(attr_list, PyUnicode_InternFromString("ensure_type"));
3684
- PyList_Append(attr_list, PyUnicode_InternFromString("ensure_metaclass"));
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
- PyList_Append(all, PyUnicode_InternFromString("PrivateWrapProxy"));
3946
- PyList_Append(all, PyUnicode_InternFromString("PrivateAttrType"));
3947
- PyList_Append(all, PyUnicode_InternFromString("PrivateAttrBase"));
3948
- PyList_Append(all, PyUnicode_InternFromString("prepare"));
3949
- PyList_Append(all, PyUnicode_InternFromString("postprocess"));
3950
- PyList_Append(all, PyUnicode_InternFromString("register_metaclass"));
3951
- PyList_Append(all, PyUnicode_InternFromString("ensure_type"));
3952
- PyList_Append(all, PyUnicode_InternFromString("ensure_metaclass"));
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 /
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: private_attribute_cpp
3
- Version: 2.0.5
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.5',
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
- __private_attrs__ = ["_secret2"]
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()