private-attribute-cpp 2.1.6__tar.gz → 2.1.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {private_attribute_cpp-2.1.6/private_attribute_cpp.egg-info → private_attribute_cpp-2.1.8}/PKG-INFO +1 -1
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/private_attribute.cpp +135 -8
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8/private_attribute_cpp.egg-info}/PKG-INFO +1 -1
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/private_attribute_cpp.egg-info/SOURCES.txt +2 -1
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/setup.py +9 -3
- private_attribute_cpp-2.1.8/tests/test_slots_cycle.py +59 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/LICENSE +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/MANIFEST.in +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/README.md +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/picosha2.h +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/private_attribute.pyi +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/private_attribute_cpp.egg-info/dependency_links.txt +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/private_attribute_cpp.egg-info/not-zip-safe +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/private_attribute_cpp.egg-info/top_level.txt +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/setup.cfg +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/tests/test_attr_classify_owner.py +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/tests/test_class_private_attrs.py +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/tests/test_gc_collect.py +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/tests/test_private_abc.py +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/tests/test_private_attrs.py +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/tests/test_private_func.py +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/tests/test_private_inheritance.py +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/tests/test_private_method.py +0 -0
- {private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/tests/test_private_wrap.py +0 -0
{private_attribute_cpp-2.1.6/private_attribute_cpp.egg-info → private_attribute_cpp-2.1.8}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: private_attribute_cpp
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.8
|
|
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
|
|
@@ -1512,6 +1512,92 @@ public:
|
|
|
1512
1512
|
}
|
|
1513
1513
|
};
|
|
1514
1514
|
|
|
1515
|
+
// ========================================================================
|
|
1516
|
+
// __slots__ traversal/clear (mirror of CPython's subtype_traverse slot loop).
|
|
1517
|
+
//
|
|
1518
|
+
// A private-attribute class's LIVE tp_traverse/tp_clear are replaced with
|
|
1519
|
+
// PrivateAttr_tp_traverse / PrivateAttr_tp_clear. The saved "original" is
|
|
1520
|
+
// CPython's subtype_traverse/subtype_clear, but because subtype_traverse
|
|
1521
|
+
// resolves the nearest base through the LIVE slot, it treats our wrapper as
|
|
1522
|
+
// the nearest "different" traverse and skips its own traverse_slots loop. The
|
|
1523
|
+
// __slots__ members (Py_T_OBJECT_EX entries in the heap type's member table)
|
|
1524
|
+
// therefore become invisible to the collector, so a cycle formed purely
|
|
1525
|
+
// through __slots__ members leaks.
|
|
1526
|
+
//
|
|
1527
|
+
// These helpers walk the member table for each type level whose live
|
|
1528
|
+
// traverse/clear slot is still a delegating default (subtype_traverse /
|
|
1529
|
+
// subtype_clear or our own wrapper), exactly mirroring the
|
|
1530
|
+
// `while (base->tp_traverse == subtype_traverse)` walk in CPython.
|
|
1531
|
+
// ========================================================================
|
|
1532
|
+
|
|
1533
|
+
// Locate the member table of a heap type in a version-independent way.
|
|
1534
|
+
// - 3.12+ : PyObject_GetItemData((PyObject*)type) is the public accessor
|
|
1535
|
+
// (_PyHeapType_GET_MEMBERS is defined as that since 3.12).
|
|
1536
|
+
// - <=3.11 : members float right after the PyHeapTypeObject header:
|
|
1537
|
+
// _PyHeapType_GET_MEMBERS(et) == (char*)et + Py_TYPE(et)->tp_basicsize.
|
|
1538
|
+
static PyMemberDef*
|
|
1539
|
+
PrivateAttr_get_tp_members(PyTypeObject* type) noexcept
|
|
1540
|
+
{
|
|
1541
|
+
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
|
|
1542
|
+
return NULL;
|
|
1543
|
+
}
|
|
1544
|
+
#if PY_VERSION_HEX >= 0x030C0000
|
|
1545
|
+
return (PyMemberDef*)PyObject_GetItemData((PyObject*)type);
|
|
1546
|
+
#else
|
|
1547
|
+
return (PyMemberDef*)((char*)type + Py_TYPE(type)->tp_basicsize);
|
|
1548
|
+
#endif
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
static int
|
|
1552
|
+
PrivateAttr_traverse_slots(PyTypeObject* type, PyObject* self,
|
|
1553
|
+
visitproc visit, void* arg) noexcept
|
|
1554
|
+
{
|
|
1555
|
+
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
|
|
1556
|
+
return 0;
|
|
1557
|
+
}
|
|
1558
|
+
PyMemberDef* mp = PrivateAttr_get_tp_members(type);
|
|
1559
|
+
if (mp == NULL) {
|
|
1560
|
+
return 0;
|
|
1561
|
+
}
|
|
1562
|
+
Py_ssize_t n = Py_SIZE(type);
|
|
1563
|
+
for (Py_ssize_t i = 0; i < n; i++, mp++) {
|
|
1564
|
+
if (mp->type == T_OBJECT_EX) {
|
|
1565
|
+
char* addr = (char*)self + mp->offset;
|
|
1566
|
+
PyObject* obj = *(PyObject**)addr;
|
|
1567
|
+
if (obj != NULL) {
|
|
1568
|
+
int err = visit(obj, arg);
|
|
1569
|
+
if (err) {
|
|
1570
|
+
return err;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
return 0;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
static void
|
|
1579
|
+
PrivateAttr_clear_slots(PyTypeObject* type, PyObject* self) noexcept
|
|
1580
|
+
{
|
|
1581
|
+
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
PyMemberDef* mp = PrivateAttr_get_tp_members(type);
|
|
1585
|
+
if (mp == NULL) {
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
Py_ssize_t n = Py_SIZE(type);
|
|
1589
|
+
for (Py_ssize_t i = 0; i < n; i++, mp++) {
|
|
1590
|
+
if (mp->type == T_OBJECT_EX && !(mp->flags & READONLY)) {
|
|
1591
|
+
char* addr = (char*)self + mp->offset;
|
|
1592
|
+
PyObject* obj = *(PyObject**)addr;
|
|
1593
|
+
if (obj != NULL) {
|
|
1594
|
+
*(PyObject**)addr = NULL;
|
|
1595
|
+
Py_DECREF(obj);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1515
1601
|
static int
|
|
1516
1602
|
PrivateAttr_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept
|
|
1517
1603
|
{
|
|
@@ -1540,6 +1626,29 @@ PrivateAttr_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept
|
|
|
1540
1626
|
}
|
|
1541
1627
|
}
|
|
1542
1628
|
|
|
1629
|
+
// __slots__ visit (mirror of subtype_traverse's slot-walking loop). See
|
|
1630
|
+
// PrivateAttr_traverse_slots above: once the LIVE tp_traverse is this
|
|
1631
|
+
// wrapper, subtype_traverse skips its own loop, so walk the member table
|
|
1632
|
+
// of this type and every delegating base explicitly. Stop at the first
|
|
1633
|
+
// base whose live traverse is a real implementation (it owns its slots).
|
|
1634
|
+
{
|
|
1635
|
+
PyTypeObject* base = typ;
|
|
1636
|
+
while (base) {
|
|
1637
|
+
if (base->tp_traverse == ::AllData::captured_subtype_traverse
|
|
1638
|
+
|| base->tp_traverse == PrivateAttr_tp_traverse) {
|
|
1639
|
+
if (Py_SIZE(base)) {
|
|
1640
|
+
int err = PrivateAttr_traverse_slots(base, self, visit, arg);
|
|
1641
|
+
if (err) {
|
|
1642
|
+
return err;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
base = base->tp_base;
|
|
1646
|
+
} else {
|
|
1647
|
+
break;
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1543
1652
|
// Instance-dict visit (mirror of subtype_traverse's dictoffset block).
|
|
1544
1653
|
// The saved "original" for a private-attribute class is subtype_traverse,
|
|
1545
1654
|
// but because the class's LIVE tp_traverse has been replaced with this
|
|
@@ -1645,6 +1754,25 @@ PrivateAttr_tp_clear(PyObject* self) noexcept
|
|
|
1645
1754
|
if (orig) orig(self);
|
|
1646
1755
|
}
|
|
1647
1756
|
|
|
1757
|
+
// __slots__ clear (mirror of subtype_clear's clear_slots loop). Same
|
|
1758
|
+
// reasoning as the traverse side: subtype_clear can't run its own loop
|
|
1759
|
+
// once the LIVE tp_clear is this wrapper, so clear the __slots__ members
|
|
1760
|
+
// of this type and every delegating base explicitly.
|
|
1761
|
+
{
|
|
1762
|
+
PyTypeObject* base = typ;
|
|
1763
|
+
while (base) {
|
|
1764
|
+
if (base->tp_clear == ::AllData::captured_subtype_clear
|
|
1765
|
+
|| base->tp_clear == PrivateAttr_tp_clear) {
|
|
1766
|
+
if (Py_SIZE(base)) {
|
|
1767
|
+
PrivateAttr_clear_slots(base, self);
|
|
1768
|
+
}
|
|
1769
|
+
base = base->tp_base;
|
|
1770
|
+
} else {
|
|
1771
|
+
break;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1648
1776
|
// Instance-dict clear (mirror of subtype_clear's dict handling). Same
|
|
1649
1777
|
// reasoning as the traverse side: subtype_clear can't run its dict block
|
|
1650
1778
|
// once the LIVE tp_clear has been replaced, so clear the instance dict
|
|
@@ -1756,8 +1884,8 @@ static inquiry get_type_need_tp_clear(PyTypeObject* cls) noexcept;
|
|
|
1756
1884
|
static int
|
|
1757
1885
|
PrivateAttrType_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept
|
|
1758
1886
|
{
|
|
1759
|
-
PyTypeObject* typ = (
|
|
1760
|
-
uintptr_t typ_id = (uintptr_t)
|
|
1887
|
+
PyTypeObject* typ = Py_TYPE(self);
|
|
1888
|
+
uintptr_t typ_id = (uintptr_t)self;
|
|
1761
1889
|
|
|
1762
1890
|
// Re-entrancy guard: see PrivateAttrRecursionGuard above.
|
|
1763
1891
|
if (!PrivateAttrRecursionGuard::try_enter((uintptr_t)self)) {
|
|
@@ -1793,9 +1921,8 @@ PrivateAttrType_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept
|
|
|
1793
1921
|
// the visit when the metaclass is a heap type, matching subtype_traverse's
|
|
1794
1922
|
// condition and avoiding the trivial/static type link.
|
|
1795
1923
|
{
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
Py_VISIT((PyObject*)meta);
|
|
1924
|
+
if (typ->tp_flags & Py_TPFLAGS_HEAPTYPE) {
|
|
1925
|
+
Py_VISIT((PyObject*)typ);
|
|
1799
1926
|
}
|
|
1800
1927
|
}
|
|
1801
1928
|
|
|
@@ -1865,8 +1992,8 @@ PrivateAttrType_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept
|
|
|
1865
1992
|
static int
|
|
1866
1993
|
PrivateAttrType_tp_clear(PyObject* self) noexcept
|
|
1867
1994
|
{
|
|
1868
|
-
PyTypeObject* typ = (
|
|
1869
|
-
uintptr_t typ_id = (uintptr_t)
|
|
1995
|
+
PyTypeObject* typ = Py_TYPE(self);
|
|
1996
|
+
uintptr_t typ_id = (uintptr_t)self;
|
|
1870
1997
|
|
|
1871
1998
|
// Re-entrancy guard: see PrivateAttrRecursionGuard above.
|
|
1872
1999
|
if (!PrivateAttrRecursionGuard::try_enter((uintptr_t)self)) {
|
|
@@ -3868,7 +3995,7 @@ PrivateModule_get_PrivateAttrBase(PyObject* /*self*/, void* /*closure*/) noexcep
|
|
|
3868
3995
|
}
|
|
3869
3996
|
|
|
3870
3997
|
static int
|
|
3871
|
-
PyList_AppendString(PyObject* list, const char* str)
|
|
3998
|
+
PyList_AppendString(PyObject* list, const char* str) noexcept
|
|
3872
3999
|
{
|
|
3873
4000
|
PyObject* obj = PyUnicode_InternFromString(str);
|
|
3874
4001
|
if (!obj) return -1;
|
{private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8/private_attribute_cpp.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: private_attribute_cpp
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.8
|
|
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
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
from setuptools import setup, Extension
|
|
2
|
+
import os
|
|
2
3
|
import sys
|
|
3
4
|
|
|
4
5
|
if sys.platform == "win32":
|
|
5
|
-
extra_compile_args = ['/std:c++17']
|
|
6
|
+
extra_compile_args = ['/std:c++17', '/GR-']
|
|
7
|
+
extra_link_args = []
|
|
6
8
|
else:
|
|
7
|
-
extra_compile_args = ['-std=c++17']
|
|
9
|
+
extra_compile_args = ['-std=c++17', '-fno-exceptions', '-fno-rtti']
|
|
10
|
+
extra_link_args = []
|
|
11
|
+
if os.environ.get('PRIVATE_ATTR_KEEP_DEBUG') != '1':
|
|
12
|
+
extra_compile_args.append('-g0')
|
|
8
13
|
|
|
9
14
|
module = Extension(
|
|
10
15
|
'private_attribute',
|
|
@@ -12,13 +17,14 @@ module = Extension(
|
|
|
12
17
|
include_dirs=['.'],
|
|
13
18
|
language='c++',
|
|
14
19
|
extra_compile_args=extra_compile_args,
|
|
20
|
+
extra_link_args=extra_link_args,
|
|
15
21
|
)
|
|
16
22
|
|
|
17
23
|
readme = open('README.md').read()
|
|
18
24
|
|
|
19
25
|
setup(
|
|
20
26
|
name='private_attribute_cpp',
|
|
21
|
-
version='2.1.
|
|
27
|
+
version='2.1.8',
|
|
22
28
|
author="HuangHaoHua",
|
|
23
29
|
author_email="13140752715@example.com",
|
|
24
30
|
description='A Python package that provides a way to define private attributes in C++ implementation.',
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Regression test for __slots__ traversal in private-attribute types.
|
|
2
|
+
|
|
3
|
+
A private-attribute class whose __slots__ member participates in a reference
|
|
4
|
+
cycle must still be collected by the cyclic GC. The instance-level traverse
|
|
5
|
+
wrapper previously skipped the __slots__ storage entirely (it only visited the
|
|
6
|
+
instance __dict__ and AllData-stored private attributes), so a cycle formed
|
|
7
|
+
purely through __slots__ members leaked.
|
|
8
|
+
"""
|
|
9
|
+
import gc
|
|
10
|
+
import unittest
|
|
11
|
+
import weakref
|
|
12
|
+
|
|
13
|
+
import private_attribute
|
|
14
|
+
from private_attribute import PrivateAttrBase
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _collect():
|
|
18
|
+
for _ in range(3):
|
|
19
|
+
gc.collect()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestSlotsCycleCollect(unittest.TestCase):
|
|
23
|
+
def test_slots_pair_cycle_collected(self):
|
|
24
|
+
class A(PrivateAttrBase):
|
|
25
|
+
__private_attrs__ = ("_x",)
|
|
26
|
+
__slots__ = ("peer", "__weakref__")
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self._x = 1
|
|
30
|
+
self.peer = None
|
|
31
|
+
|
|
32
|
+
a = A()
|
|
33
|
+
b = A()
|
|
34
|
+
a.peer = b
|
|
35
|
+
b.peer = a
|
|
36
|
+
wa, wb = weakref.ref(a), weakref.ref(b)
|
|
37
|
+
del a, b
|
|
38
|
+
_collect()
|
|
39
|
+
self.assertIsNone(wa())
|
|
40
|
+
self.assertIsNone(wb())
|
|
41
|
+
|
|
42
|
+
def test_slots_self_cycle_collected(self):
|
|
43
|
+
class A(PrivateAttrBase):
|
|
44
|
+
__private_attrs__ = ("_x",)
|
|
45
|
+
__slots__ = ("self_ref", "__weakref__")
|
|
46
|
+
|
|
47
|
+
def __init__(self):
|
|
48
|
+
self._x = 1
|
|
49
|
+
self.self_ref = self
|
|
50
|
+
|
|
51
|
+
a = A()
|
|
52
|
+
w = weakref.ref(a)
|
|
53
|
+
del a
|
|
54
|
+
_collect()
|
|
55
|
+
self.assertIsNone(w())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/tests/test_attr_classify_owner.py
RENAMED
|
File without changes
|
{private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/tests/test_class_private_attrs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{private_attribute_cpp-2.1.6 → private_attribute_cpp-2.1.8}/tests/test_private_inheritance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|