Skip to content

Commit 1b9caf9

Browse files
committed
tests/cpydiff/core_class_setname_hazard: Document __set_name__ hazard.
Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
1 parent a236aa0 commit 1b9caf9

File tree

1 file changed

+87
-0
lines changed

1 file changed

+87
-0
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
categories: Core,Classes
3+
description: Descriptor ``__set_name__`` functions may be called twice or missed if members of the parent class are created or removed in ``__set_name__``.
4+
cause: The ``__set_name__`` procedure is not isolated from the underlying modify-while-iter sequence hazard of the underlying class ``__dict__``.
5+
workaround: Avoid ``__set_name__`` implementations that add or remove members from the parent class.
6+
"""
7+
8+
# This bug is EXTREMELY difficult to demonstrate with only a minimal test case
9+
# due to the unstable iteration order of a class's namespace. This bug more-or-less
10+
# _requires_ a stochastic test in order to guarantee it occurs or demonstrate its
11+
# potential impact with any level of clarity.
12+
13+
import random
14+
import re
15+
16+
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
17+
18+
junk_re = re.compile(r"[A-Z][A-Z][A-Z][A-Z][A-Z]")
19+
20+
21+
def junk_fill(obj, n=10): # Add randomly-generated attributes to an object.
22+
for i in range(n):
23+
name = "".join(random.choice(letters) for j in range(5))
24+
setattr(obj, name, object())
25+
26+
27+
def junk_clear(obj): # Remove attributes added by junk_fill.
28+
to_del = [name for name in dir(obj) if junk_re.match(name)]
29+
for name in to_del:
30+
delattr(obj, name)
31+
32+
33+
def junk_sequencer():
34+
global runs
35+
try:
36+
while True:
37+
owner, name = yield
38+
runs[name] = runs.get(name, 0) + 1
39+
junk_fill(owner)
40+
finally:
41+
junk_clear(owner)
42+
43+
44+
class JunkMaker:
45+
def __set_name__(self, owner, name):
46+
global seq
47+
seq.send((owner, name))
48+
49+
50+
runs = {}
51+
seq = junk_sequencer()
52+
next(seq)
53+
54+
55+
class Main:
56+
a = JunkMaker()
57+
b = JunkMaker()
58+
c = JunkMaker()
59+
d = JunkMaker()
60+
e = JunkMaker()
61+
f = JunkMaker()
62+
g = JunkMaker()
63+
h = JunkMaker()
64+
i = JunkMaker()
65+
j = JunkMaker()
66+
k = JunkMaker()
67+
l = JunkMaker()
68+
m = JunkMaker()
69+
n = JunkMaker()
70+
o = JunkMaker()
71+
p = JunkMaker()
72+
q = JunkMaker()
73+
r = JunkMaker()
74+
s = JunkMaker()
75+
t = JunkMaker()
76+
u = JunkMaker()
77+
v = JunkMaker()
78+
w = JunkMaker()
79+
x = JunkMaker()
80+
y = JunkMaker()
81+
z = JunkMaker()
82+
83+
84+
seq.close()
85+
86+
for k in letters.lower():
87+
print(k, runs.get(k, 0))

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy