private-attribute-cpp 2.0.6__tar.gz → 2.1.1__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.6/private_attribute_cpp.egg-info → private_attribute_cpp-2.1.1}/PKG-INFO +3 -1
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/README.md +2 -0
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/private_attribute.cpp +155 -65
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1/private_attribute_cpp.egg-info}/PKG-INFO +3 -1
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/setup.py +1 -1
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/tests/test_private_abc.py +5 -1
- private_attribute_cpp-2.1.1/tests/test_private_func.py +57 -0
- private_attribute_cpp-2.1.1/tests/test_private_inheritance.py +319 -0
- private_attribute_cpp-2.0.6/tests/test_private_func.py +0 -27
- private_attribute_cpp-2.0.6/tests/test_private_inheritance.py +0 -40
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/LICENSE +0 -0
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/MANIFEST.in +0 -0
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/picosha2.h +0 -0
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/private_attribute.pyi +0 -0
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/private_attribute_cpp.egg-info/SOURCES.txt +0 -0
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/private_attribute_cpp.egg-info/dependency_links.txt +0 -0
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/private_attribute_cpp.egg-info/not-zip-safe +0 -0
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/private_attribute_cpp.egg-info/top_level.txt +0 -0
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/setup.cfg +0 -0
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/tests/test_class_private_attrs.py +0 -0
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/tests/test_private_attrs.py +0 -0
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/tests/test_private_method.py +0 -0
- {private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1}/tests/test_private_wrap.py +0 -0
{private_attribute_cpp-2.0.6/private_attribute_cpp.egg-info → private_attribute_cpp-2.1.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: private_attribute_cpp
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.1
|
|
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.
|
|
@@ -225,6 +225,19 @@ static uintptr_t type_set_attr_long_long_guidance(uintptr_t type, const std::str
|
|
|
225
225
|
static bool type_private_attr(uintptr_t type, const std::string& name) noexcept;
|
|
226
226
|
static FinalObject type_get_final_attr(uintptr_t type_id, const std::string& name) noexcept;
|
|
227
227
|
|
|
228
|
+
// Result of classifying an attribute access.
|
|
229
|
+
// - type_id: the node on the MRO chain (a class) whose code is currently running
|
|
230
|
+
// AND that declares the attribute as private. 0 means "no private owner".
|
|
231
|
+
// - allowed: false means the access must be denied (a private attribute touched
|
|
232
|
+
// from outside its declaring class, or from a subclass of it).
|
|
233
|
+
struct AttrClassifyResult
|
|
234
|
+
{
|
|
235
|
+
uintptr_t type_id = 0;
|
|
236
|
+
bool allowed = true;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
static AttrClassifyResult attr_classify(uintptr_t typ_id, const std::string& name, PyCodeObject* code) noexcept;
|
|
240
|
+
|
|
228
241
|
static bool
|
|
229
242
|
is_class_code(uintptr_t typ_id, PyCodeObject* code) noexcept
|
|
230
243
|
{
|
|
@@ -251,27 +264,67 @@ is_type_private(uintptr_t typ_id, const std::string& name) noexcept
|
|
|
251
264
|
return false;
|
|
252
265
|
}
|
|
253
266
|
|
|
254
|
-
static
|
|
267
|
+
static AttrClassifyResult
|
|
255
268
|
attr_classify(uintptr_t typ_id, const std::string& name, PyCodeObject* code) noexcept
|
|
256
269
|
{
|
|
257
|
-
|
|
258
|
-
|
|
270
|
+
// Build the MRO chain: the actual class first, then its parents.
|
|
271
|
+
std::vector<uintptr_t> chain;
|
|
272
|
+
chain.push_back(typ_id);
|
|
273
|
+
if (::AllData::all_type_parent_id.find(typ_id) != ::AllData::all_type_parent_id.end()) {
|
|
274
|
+
auto& parent_ids = ::AllData::all_type_parent_id[typ_id];
|
|
275
|
+
chain.insert(chain.end(), parent_ids.begin(), parent_ids.end());
|
|
259
276
|
}
|
|
260
|
-
|
|
261
|
-
|
|
277
|
+
|
|
278
|
+
// Find the class whose code is currently running (the code owner).
|
|
279
|
+
uintptr_t code_class = 0;
|
|
280
|
+
for (auto& node : chain) {
|
|
281
|
+
if (is_class_code(node, code)) {
|
|
282
|
+
code_class = node;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
262
285
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
286
|
+
|
|
287
|
+
if (code_class == 0) {
|
|
288
|
+
// No class code was hit on the whole chain (e.g. the code belongs to a
|
|
289
|
+
// module-level function or an unrelated class): if the name is private
|
|
290
|
+
// to any class on the chain, deny the access.
|
|
291
|
+
for (auto& node : chain) {
|
|
292
|
+
if (is_type_private(node, name)) {
|
|
293
|
+
return {0, false};
|
|
268
294
|
}
|
|
269
|
-
|
|
270
|
-
|
|
295
|
+
}
|
|
296
|
+
return {0, true};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (is_type_private(code_class, name)) {
|
|
300
|
+
// Both conditions hold: the code is under this class AND the attribute
|
|
301
|
+
// is private to this very class -> allowed, and this node of the chain
|
|
302
|
+
// is the owner of the private attribute.
|
|
303
|
+
return {code_class, true};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// The code belongs to code_class but the name is not private to it: look
|
|
307
|
+
// upward (toward the base classes). If an ancestor declares the name
|
|
308
|
+
// private, a subclass is trying to touch a parent's private attribute.
|
|
309
|
+
// Note: a class-body definition reusing a parent's private name (a
|
|
310
|
+
// "shadow", stored separately in all_type_subclass_attr[parent][child])
|
|
311
|
+
// does NOT grant this class's code any access to the name.
|
|
312
|
+
bool found_code_class = false;
|
|
313
|
+
for (auto& node : chain) {
|
|
314
|
+
if (!found_code_class) {
|
|
315
|
+
if (node == code_class) {
|
|
316
|
+
found_code_class = true;
|
|
271
317
|
}
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (is_type_private(node, name)) {
|
|
321
|
+
return {0, false};
|
|
272
322
|
}
|
|
273
323
|
}
|
|
274
|
-
|
|
324
|
+
|
|
325
|
+
// Not private to the running class nor to any of its ancestors: treat it
|
|
326
|
+
// as a normal (non-private) attribute access.
|
|
327
|
+
return {0, true};
|
|
275
328
|
}
|
|
276
329
|
|
|
277
330
|
static std::string
|
|
@@ -437,13 +490,13 @@ static traverseproc get_need_tp_traverse(PyTypeObject* cls) noexcept;
|
|
|
437
490
|
static inquiry get_need_tp_clear(PyTypeObject* cls) noexcept;
|
|
438
491
|
|
|
439
492
|
static PyObject*
|
|
440
|
-
id_getattr(const std::string& attr_name, PyObject* obj, PyObject* typ) noexcept
|
|
493
|
+
id_getattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyObject* typ) noexcept
|
|
441
494
|
{
|
|
442
|
-
uintptr_t obj_id
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
FinalObject final_object = type_get_final_attr(
|
|
495
|
+
uintptr_t obj_id = (uintptr_t) obj;
|
|
496
|
+
// Class-level fallback resolves per-subject (the instance's type): a
|
|
497
|
+
// subclass's separately-stored shadow of a parent's private name is
|
|
498
|
+
// visible through the per-subject walk in type_get_final_attr.
|
|
499
|
+
FinalObject final_object = type_get_final_attr((uintptr_t)typ, attr_name);
|
|
447
500
|
if (final_object.status == -2) {
|
|
448
501
|
return NULL;
|
|
449
502
|
}
|
|
@@ -575,13 +628,11 @@ type_getattr(PyObject* typ, const std::string& attr_name) noexcept
|
|
|
575
628
|
}
|
|
576
629
|
|
|
577
630
|
static int
|
|
578
|
-
id_setattr(const std::string& attr_name, PyObject* obj, PyObject* typ, PyObject* value) noexcept
|
|
631
|
+
id_setattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyObject* typ, PyObject* value) noexcept
|
|
579
632
|
{
|
|
580
|
-
uintptr_t obj_id
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
final_id = type_set_attr_long_long_guidance(typ_id, attr_name);
|
|
584
|
-
FinalObject final_object = type_get_final_attr(typ_id, attr_name);
|
|
633
|
+
uintptr_t obj_id = (uintptr_t) obj;
|
|
634
|
+
// Class-level fallback resolves per-subject (the instance's type).
|
|
635
|
+
FinalObject final_object = type_get_final_attr((uintptr_t)typ, attr_name);
|
|
585
636
|
if (final_object.status == -2) {
|
|
586
637
|
return -1;
|
|
587
638
|
}
|
|
@@ -684,6 +735,8 @@ type_setattr(PyObject* typ, const std::string& attr_name, PyObject* value) noexc
|
|
|
684
735
|
}
|
|
685
736
|
return 0;
|
|
686
737
|
} else {
|
|
738
|
+
// The accessed class is a subclass that stores its own value for a
|
|
739
|
+
// parent's private name separately: all_type_subclass_attr[final_id][typ_id].
|
|
687
740
|
if (::AllData::all_type_subclass_attr.find(final_id) == ::AllData::all_type_subclass_attr.end()) {
|
|
688
741
|
::AllData::all_type_subclass_attr[final_id] = {};
|
|
689
742
|
}
|
|
@@ -706,13 +759,11 @@ type_setattr(PyObject* typ, const std::string& attr_name, PyObject* value) noexc
|
|
|
706
759
|
}
|
|
707
760
|
|
|
708
761
|
static int
|
|
709
|
-
id_delattr(const std::string& attr_name, PyObject* obj, PyObject* typ) noexcept
|
|
762
|
+
id_delattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyObject* typ) noexcept
|
|
710
763
|
{
|
|
711
|
-
uintptr_t obj_id
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
final_id = type_set_attr_long_long_guidance(typ_id, attr_name);
|
|
715
|
-
FinalObject final_object = type_get_final_attr(typ_id, attr_name);
|
|
764
|
+
uintptr_t obj_id = (uintptr_t) obj;
|
|
765
|
+
// Class-level fallback resolves per-subject (the instance's type).
|
|
766
|
+
FinalObject final_object = type_get_final_attr((uintptr_t)typ, attr_name);
|
|
716
767
|
if (final_object.status == -2) {
|
|
717
768
|
return -1;
|
|
718
769
|
}
|
|
@@ -1281,14 +1332,21 @@ PrivateAttr_tp_getattro(PyObject* self, PyObject* name) noexcept
|
|
|
1281
1332
|
std::string name_str = PyUnicode_AsUTF8(name);
|
|
1282
1333
|
auto code = get_now_code();
|
|
1283
1334
|
if (type_private_attr(type_id, name_str)) {
|
|
1284
|
-
if (!code
|
|
1335
|
+
if (!code) {
|
|
1285
1336
|
Py_XDECREF(code);
|
|
1286
1337
|
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
1287
1338
|
return NULL;
|
|
1288
|
-
} else {
|
|
1289
|
-
Py_XDECREF(code);
|
|
1290
|
-
return id_getattr(name_str, self, (PyObject*)typ);
|
|
1291
1339
|
}
|
|
1340
|
+
AttrClassifyResult classify = attr_classify(type_id, name_str, code);
|
|
1341
|
+
Py_XDECREF(code);
|
|
1342
|
+
if (!classify.allowed) {
|
|
1343
|
+
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
1344
|
+
return NULL;
|
|
1345
|
+
}
|
|
1346
|
+
if (classify.type_id != 0) {
|
|
1347
|
+
return id_getattr(classify.type_id, name_str, self, (PyObject*)typ);
|
|
1348
|
+
}
|
|
1349
|
+
// owner is 0: not private for this access context -> normal attribute
|
|
1292
1350
|
}
|
|
1293
1351
|
Py_XDECREF(code);
|
|
1294
1352
|
if (::AllData::all_type_getattro.find(type_id) != ::AllData::all_type_getattro.end()){
|
|
@@ -1313,17 +1371,24 @@ PrivateAttr_tp_setattro(PyObject* self, PyObject* name, PyObject* value) noexcep
|
|
|
1313
1371
|
std::string name_str(c_name);
|
|
1314
1372
|
auto code = get_now_code();
|
|
1315
1373
|
if (type_private_attr(typ_id, name_str)) {
|
|
1316
|
-
if (!code
|
|
1374
|
+
if (!code) {
|
|
1317
1375
|
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
1318
1376
|
Py_XDECREF(code);
|
|
1319
1377
|
return -1;
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1378
|
+
}
|
|
1379
|
+
AttrClassifyResult classify = attr_classify(typ_id, name_str, code);
|
|
1380
|
+
Py_XDECREF(code);
|
|
1381
|
+
if (!classify.allowed) {
|
|
1382
|
+
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
1383
|
+
return -1;
|
|
1384
|
+
}
|
|
1385
|
+
if (classify.type_id != 0) {
|
|
1322
1386
|
if (!value) {
|
|
1323
|
-
return id_delattr(name_str, self, (PyObject*)typ);
|
|
1387
|
+
return id_delattr(classify.type_id, name_str, self, (PyObject*)typ);
|
|
1324
1388
|
}
|
|
1325
|
-
return id_setattr(name_str, self, (PyObject*)typ, value);
|
|
1389
|
+
return id_setattr(classify.type_id, name_str, self, (PyObject*)typ, value);
|
|
1326
1390
|
}
|
|
1391
|
+
// owner is 0: not private for this access context -> normal attribute
|
|
1327
1392
|
}
|
|
1328
1393
|
Py_XDECREF(code);
|
|
1329
1394
|
if (::AllData::all_type_setattro.find(typ_id) != ::AllData::all_type_setattro.end()){
|
|
@@ -1554,6 +1619,21 @@ PrivateAttr_tp_clear(PyObject* self) noexcept
|
|
|
1554
1619
|
return 0;
|
|
1555
1620
|
}
|
|
1556
1621
|
|
|
1622
|
+
// get_type_need_tp_* : the metaclass counterparts of get_need_tp_*.
|
|
1623
|
+
// get_need_tp_* -> find the original slots of *types created by* the
|
|
1624
|
+
// metaclasses (wrapped with the instance-level
|
|
1625
|
+
// PrivateAttr_tp_* / PrivateAttr_tp_getattro set).
|
|
1626
|
+
// get_type_need_tp_* -> find the original slots of the *metaclasses*
|
|
1627
|
+
// themselves (wrapped with the metaclass-level
|
|
1628
|
+
// PrivateAttrType_getattr / PrivateAttrType_setattr /
|
|
1629
|
+
// register_finalize / PrivateAttrType_tp_traverse /
|
|
1630
|
+
// PrivateAttrType_tp_clear).
|
|
1631
|
+
static getattrofunc get_type_need_tp_getattro(PyTypeObject* cls) noexcept;
|
|
1632
|
+
static setattrofunc get_type_need_tp_setattro(PyTypeObject* cls) noexcept;
|
|
1633
|
+
static traverseproc get_type_need_tp_traverse(PyTypeObject* cls) noexcept;
|
|
1634
|
+
static inquiry get_type_need_tp_clear(PyTypeObject* cls) noexcept;
|
|
1635
|
+
static destructor get_type_need_tp_finalize(PyTypeObject* cls) noexcept;
|
|
1636
|
+
|
|
1557
1637
|
// metaclass-level (type object) traverse/clear: call original if any, and handle AllData-stored type attributes
|
|
1558
1638
|
static int
|
|
1559
1639
|
PrivateAttrType_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept
|
|
@@ -1575,7 +1655,7 @@ PrivateAttrType_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept
|
|
|
1575
1655
|
if (res) return res;
|
|
1576
1656
|
}
|
|
1577
1657
|
} else {
|
|
1578
|
-
traverseproc orig =
|
|
1658
|
+
traverseproc orig = get_type_need_tp_traverse(typ);
|
|
1579
1659
|
if (orig) {
|
|
1580
1660
|
int res = orig(self, visit, arg);
|
|
1581
1661
|
if (res) return res;
|
|
@@ -1662,7 +1742,7 @@ PrivateAttrType_tp_clear(PyObject* self) noexcept
|
|
|
1662
1742
|
inquiry orig = ::AllData::all_type_clear[typ_id];
|
|
1663
1743
|
if (orig) orig(self);
|
|
1664
1744
|
} else {
|
|
1665
|
-
inquiry orig =
|
|
1745
|
+
inquiry orig = get_type_need_tp_clear(typ);
|
|
1666
1746
|
if (orig) orig(self);
|
|
1667
1747
|
}
|
|
1668
1748
|
|
|
@@ -1746,8 +1826,8 @@ static PyTypeObject PrivateAttrType = {
|
|
|
1746
1826
|
0, // tp_as_buffer
|
|
1747
1827
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags
|
|
1748
1828
|
"metaclass for private attributes", // tp_doc
|
|
1749
|
-
|
|
1750
|
-
|
|
1829
|
+
(traverseproc)PrivateAttrType_tp_traverse, // tp_travers
|
|
1830
|
+
(inquiry)PrivateAttrType_tp_clear, // tp_clear
|
|
1751
1831
|
0, // tp_richcompare
|
|
1752
1832
|
0, // tp_weaklistoffset
|
|
1753
1833
|
0, // tp_iter
|
|
@@ -1796,6 +1876,10 @@ get_string_hash_tuple2(const std::string& name) noexcept
|
|
|
1796
1876
|
static FinalObject
|
|
1797
1877
|
type_get_final_attr(uintptr_t type_id, const std::string& name) noexcept
|
|
1798
1878
|
{
|
|
1879
|
+
// type_id is the SUBJECT: the class whose attribute is being accessed.
|
|
1880
|
+
// The walk resolves per-subject: a subclass's separately stored value
|
|
1881
|
+
// (all_type_subclass_attr[parent][subclass]) shadows the parent's own
|
|
1882
|
+
// class-level value, satisfying class-level separate storage.
|
|
1799
1883
|
TwoStringTuple hash_tuple = get_string_hash_tuple2(name);
|
|
1800
1884
|
if (::AllData::all_type_attr_set.find(type_id) != ::AllData::all_type_attr_set.end()) {
|
|
1801
1885
|
if (::AllData::all_type_attr_set[type_id].find(hash_tuple) != ::AllData::all_type_attr_set[type_id].end()) {
|
|
@@ -2084,6 +2168,8 @@ struct PrivateAttrCreationData
|
|
|
2084
2168
|
std::unordered_set<std::string> private_attrs_vector_string;
|
|
2085
2169
|
std::vector<uintptr_t> all_need_analyse_base;
|
|
2086
2170
|
std::unordered_map<std::string, PyObjectStorage> need_remove_itself;
|
|
2171
|
+
// Class-body attributes whose name is private to a parent class: stored
|
|
2172
|
+
// separately in all_type_subclass_attr[parent][this class].
|
|
2087
2173
|
std::unordered_map<uintptr_t, std::unordered_map<std::string, PyObjectStorage>> need_remove_subclass;
|
|
2088
2174
|
PyObject* private_func = nullptr;
|
|
2089
2175
|
PyObject* base_kwds = nullptr;
|
|
@@ -2365,6 +2451,9 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
|
|
|
2365
2451
|
}
|
|
2366
2452
|
uintptr_t need_remove_subclass_id = need_remove_to_subclass(attr_name);
|
|
2367
2453
|
if (need_remove_subclass_id) {
|
|
2454
|
+
// The class body defines an attribute whose name is private to a
|
|
2455
|
+
// parent class. It is stored separately (class-level separate
|
|
2456
|
+
// storage) in all_type_subclass_attr[need_remove_subclass_id][this].
|
|
2368
2457
|
if (data.need_remove_subclass.find(need_remove_subclass_id) == data.need_remove_subclass.end()) {
|
|
2369
2458
|
data.need_remove_subclass[need_remove_subclass_id] = {};
|
|
2370
2459
|
}
|
|
@@ -2427,21 +2516,6 @@ static traverseproc get_need_tp_traverse(PyTypeObject* cls) noexcept;
|
|
|
2427
2516
|
static inquiry get_need_tp_clear(PyTypeObject* cls) noexcept;
|
|
2428
2517
|
static destructor get_need_tp_finalize(PyTypeObject* cls) noexcept;
|
|
2429
2518
|
|
|
2430
|
-
// get_type_need_tp_* : the metaclass counterparts of get_need_tp_*.
|
|
2431
|
-
// get_need_tp_* -> find the original slots of *types created by* the
|
|
2432
|
-
// metaclasses (wrapped with the instance-level
|
|
2433
|
-
// PrivateAttr_tp_* / PrivateAttr_tp_getattro set).
|
|
2434
|
-
// get_type_need_tp_* -> find the original slots of the *metaclasses*
|
|
2435
|
-
// themselves (wrapped with the metaclass-level
|
|
2436
|
-
// PrivateAttrType_getattr / PrivateAttrType_setattr /
|
|
2437
|
-
// register_finalize / PrivateAttrType_tp_traverse /
|
|
2438
|
-
// PrivateAttrType_tp_clear).
|
|
2439
|
-
static getattrofunc get_type_need_tp_getattro(PyTypeObject* cls) noexcept;
|
|
2440
|
-
static setattrofunc get_type_need_tp_setattro(PyTypeObject* cls) noexcept;
|
|
2441
|
-
static traverseproc get_type_need_tp_traverse(PyTypeObject* cls) noexcept;
|
|
2442
|
-
static inquiry get_type_need_tp_clear(PyTypeObject* cls) noexcept;
|
|
2443
|
-
static destructor get_type_need_tp_finalize(PyTypeObject* cls) noexcept;
|
|
2444
|
-
|
|
2445
2519
|
// instance-level traverse/clear for types that use private attributes
|
|
2446
2520
|
static int PrivateAttr_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept;
|
|
2447
2521
|
static int PrivateAttr_tp_clear(PyObject* self) noexcept;
|
|
@@ -2661,6 +2735,7 @@ PrivateAttrType_postprocess(PyObject* new_type, PrivateAttrCreationData& data) n
|
|
|
2661
2735
|
}
|
|
2662
2736
|
|
|
2663
2737
|
if (data.private_func) {
|
|
2738
|
+
Py_INCREF(data.private_func);
|
|
2664
2739
|
::AllData::type_need_call[type_id] = data.private_func;
|
|
2665
2740
|
}
|
|
2666
2741
|
|
|
@@ -2750,13 +2825,21 @@ PrivateAttrType_getattr(PyObject* cls, PyObject* name) noexcept
|
|
|
2750
2825
|
std::string name_str = PyUnicode_AsUTF8(name);
|
|
2751
2826
|
PyCodeObject* now_code = get_now_code();
|
|
2752
2827
|
if (type_private_attr(typ_id, name_str)) {
|
|
2753
|
-
if (!now_code
|
|
2828
|
+
if (!now_code) {
|
|
2754
2829
|
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
2755
2830
|
Py_XDECREF(now_code);
|
|
2756
2831
|
return NULL;
|
|
2757
2832
|
}
|
|
2833
|
+
AttrClassifyResult classify = attr_classify(typ_id, name_str, now_code);
|
|
2758
2834
|
Py_XDECREF(now_code);
|
|
2759
|
-
|
|
2835
|
+
if (!classify.allowed) {
|
|
2836
|
+
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
2837
|
+
return NULL;
|
|
2838
|
+
}
|
|
2839
|
+
if (classify.type_id != 0) {
|
|
2840
|
+
return type_getattr(cls, name_str);
|
|
2841
|
+
}
|
|
2842
|
+
// owner is 0: not private for this access context -> normal attribute
|
|
2760
2843
|
}
|
|
2761
2844
|
Py_XDECREF(now_code);
|
|
2762
2845
|
PyTypeObject* base = Py_TYPE(cls)->tp_base;
|
|
@@ -3097,13 +3180,21 @@ PrivateAttrType_setattr(PyObject* cls, PyObject* name, PyObject* value) noexcept
|
|
|
3097
3180
|
}
|
|
3098
3181
|
PyCodeObject* now_code = get_now_code();
|
|
3099
3182
|
if (type_private_attr(typ_id, name_str)) {
|
|
3100
|
-
if (!now_code
|
|
3183
|
+
if (!now_code) {
|
|
3101
3184
|
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
3102
3185
|
Py_XDECREF(now_code);
|
|
3103
3186
|
return -1;
|
|
3104
3187
|
}
|
|
3188
|
+
AttrClassifyResult classify = attr_classify(typ_id, name_str, now_code);
|
|
3105
3189
|
Py_XDECREF(now_code);
|
|
3106
|
-
|
|
3190
|
+
if (!classify.allowed) {
|
|
3191
|
+
PyErr_SetString(PyExc_AttributeError, "private attribute");
|
|
3192
|
+
return -1;
|
|
3193
|
+
}
|
|
3194
|
+
if (classify.type_id != 0) {
|
|
3195
|
+
return type_setattr(cls, name_str, value);
|
|
3196
|
+
}
|
|
3197
|
+
// owner is 0: not private for this access context -> normal attribute
|
|
3107
3198
|
}
|
|
3108
3199
|
Py_XDECREF(now_code);
|
|
3109
3200
|
PyTypeObject* base = Py_TYPE(cls)->tp_base;
|
|
@@ -3133,8 +3224,7 @@ PrivateAttrType_finalize(PyObject* cls) noexcept
|
|
|
3133
3224
|
::AllData::type_allowed_code_map.erase(typ_id);
|
|
3134
3225
|
}
|
|
3135
3226
|
if (::AllData::type_need_call.find(typ_id) != ::AllData::type_need_call.end()) {
|
|
3136
|
-
|
|
3137
|
-
Py_XDECREF(need_call);
|
|
3227
|
+
Py_DECREF(::AllData::type_need_call[typ_id]);
|
|
3138
3228
|
::AllData::type_need_call.erase(typ_id);
|
|
3139
3229
|
}
|
|
3140
3230
|
if (::AllData::type_attr_dict.find(typ_id) != ::AllData::type_attr_dict.end()) {
|
{private_attribute_cpp-2.0.6 → private_attribute_cpp-2.1.1/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.
|
|
3
|
+
Version: 2.1.1
|
|
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.
|
|
21
|
+
version='2.1.1',
|
|
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.6 → private_attribute_cpp-2.1.1}/tests/test_class_private_attrs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|