Skip to content

Commit 69b2dad

Browse files
committed
[GR-18338] skip NO_VALUE keys in PythonObject storage
PullRequest: graalpython/668
2 parents 7c098aa + 80ee185 commit 69b2dad

File tree

2 files changed

+193
-64
lines changed

2 files changed

+193
-64
lines changed

graalpython/com.oracle.graal.python.test/src/tests/test_class-set-attrib.py

Lines changed: 86 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -39,87 +39,114 @@
3939

4040
counter = 0
4141

42+
4243
class Foo:
43-
def __setattr__(self, key, value):
44-
global counter
45-
counter = counter + 1
46-
object.__setattr__(self, key, value)
47-
def __delattr__(self, key):
48-
global counter
49-
counter = counter + 10
50-
object.__delattr__(self, key)
44+
def __setattr__(self, key, value):
45+
global counter
46+
counter = counter + 1
47+
object.__setattr__(self, key, value)
48+
49+
def __delattr__(self, key):
50+
global counter
51+
counter = counter + 10
52+
object.__delattr__(self, key)
53+
5154

5255
def test_call():
53-
global counter
54-
counter = 0
55-
f = Foo()
56-
f.a = 1
57-
Foo.b = 123
58-
assert counter == 1, "setting attrib on class should not call its own __setattr__"
59-
del f.a
60-
del Foo.b
61-
assert counter == 11, "deleting attrib on class should not call its own __delattr__"
56+
global counter
57+
counter = 0
58+
f = Foo()
59+
f.a = 1
60+
Foo.b = 123
61+
assert counter == 1, "setting attrib on class should not call its own __setattr__"
62+
del f.a
63+
del Foo.b
64+
assert counter == 11, "deleting attrib on class should not call its own __delattr__"
65+
6266

6367
class AClass:
64-
pass
68+
pass
69+
6570

6671
class BClass(AClass):
67-
pass
72+
pass
73+
6874

6975
class CClass(BClass):
70-
pass
76+
pass
77+
7178

7279
class DClass(BClass):
73-
pass
80+
pass
81+
7482

7583
def custom_set(self, key, value):
76-
object.__setattr__(self, key, value + 10 if isinstance(value, int) else value)
84+
object.__setattr__(self, key, value + 10 if isinstance(value, int) else value)
85+
7786

7887
def custom_get(self, key):
79-
value = object.__getattribute__(self, key)
80-
return value + 100 if isinstance(value, int) else value
88+
value = object.__getattribute__(self, key)
89+
return value + 100 if isinstance(value, int) else value
90+
8191

8292
def test_assignments():
83-
object = CClass()
84-
# writing to BClass changes the result, writing to DClass doesn't
85-
targets = (AClass, BClass, DClass, CClass, object)
86-
results = (0, 1, 1, 3, 4)
87-
for i in range(0, len(targets)):
88-
targets[i].foo = i
89-
assert object.foo == results[i], "normal %d" %i
90-
# make sure that a custom __getattribute__ is used
91-
BClass.__getattribute__ = custom_get
92-
for i in range(0, len(targets)):
93-
targets[i].bar = i
94-
assert object.bar == results[i] + 100, "custom get %d" % i
95-
# check correct lookups when deleting attributes
96-
for i in reversed(range(0, len(targets))):
97-
assert object.bar == results[i] + 100, "delete %d" % i
98-
del targets[i].bar
99-
# make sure a custom __setattr__ is used
100-
BClass.__setattr__ = custom_set
101-
object.baz = 9
102-
assert object.baz == 119, "custom set"
93+
object = CClass()
94+
# writing to BClass changes the result, writing to DClass doesn't
95+
targets = (AClass, BClass, DClass, CClass, object)
96+
results = (0, 1, 1, 3, 4)
97+
for i in range(0, len(targets)):
98+
targets[i].foo = i
99+
assert object.foo == results[i], "normal %d" % i
100+
# make sure that a custom __getattribute__ is used
101+
BClass.__getattribute__ = custom_get
102+
for i in range(0, len(targets)):
103+
targets[i].bar = i
104+
assert object.bar == results[i] + 100, "custom get %d" % i
105+
# check correct lookups when deleting attributes
106+
for i in reversed(range(0, len(targets))):
107+
assert object.bar == results[i] + 100, "delete %d" % i
108+
del targets[i].bar
109+
# make sure a custom __setattr__ is used
110+
BClass.__setattr__ = custom_set
111+
object.baz = 9
112+
assert object.baz == 119, "custom set"
103113

104114

105115
def test_setattr_via_decorator():
106-
def setdec(func):
107-
setattr(func, 'SPECIAL_ATTR', {'a': 1, 'b': 2})
108-
return func
116+
def setdec(func):
117+
setattr(func, 'SPECIAL_ATTR', {'a': 1, 'b': 2})
118+
return func
109119

110-
@setdec
111-
def f():
112-
return 1
120+
@setdec
121+
def f():
122+
return 1
113123

114-
assert hasattr(f, 'SPECIAL_ATTR')
115-
assert getattr(f, 'SPECIAL_ATTR') == {'a': 1, 'b': 2}
124+
assert hasattr(f, 'SPECIAL_ATTR')
125+
assert getattr(f, 'SPECIAL_ATTR') == {'a': 1, 'b': 2}
116126

127+
class MyClass(object):
128+
@setdec
129+
def f(self):
130+
return 1
117131

118-
class MyClass(object):
119-
@setdec
120-
def f(self):
121-
return 1
132+
m = MyClass()
133+
assert hasattr(m.f, 'SPECIAL_ATTR')
134+
assert getattr(m.f, 'SPECIAL_ATTR') == {'a': 1, 'b': 2}
135+
136+
137+
def test_deepcopy_attribute_removal():
138+
from copy import deepcopy
139+
140+
class A:
141+
def __init__(self):
142+
self.a = "a"
143+
144+
def add_rem_attr(self):
145+
self.b = "b"
146+
del self.b
147+
148+
i1 = A()
149+
assert i1.__dict__ == deepcopy(i1).__dict__
122150

123-
m = MyClass()
124-
assert hasattr(m.f, 'SPECIAL_ATTR')
125-
assert getattr(m.f, 'SPECIAL_ATTR') == {'a': 1, 'b': 2}
151+
i1.add_rem_attr()
152+
assert i1.__dict__ == deepcopy(i1).__dict__

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/DynamicObjectStorage.java

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,13 @@
4141
package com.oracle.graal.python.builtins.objects.common;
4242

4343
import java.util.ArrayList;
44+
import java.util.Iterator;
45+
import java.util.NoSuchElementException;
46+
import java.util.function.Predicate;
4447

48+
import com.oracle.graal.python.builtins.objects.PNone;
4549
import com.oracle.graal.python.runtime.sequence.storage.MroSequenceStorage;
50+
import com.oracle.truffle.api.CompilerAsserts;
4651
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
4752
import com.oracle.truffle.api.object.DynamicObject;
4853
import com.oracle.truffle.api.object.Layout;
@@ -105,17 +110,20 @@ public boolean remove(Object key, Equivalence eq) {
105110
return result;
106111
}
107112

113+
protected Iterable<Object> getKeysIterable() {
114+
return store.getShape().getKeys();
115+
}
116+
108117
@Override
109118
public Iterable<Object> keys() {
110-
return wrapJavaIterable(store.getShape().getKeys());
119+
return wrapJavaIterable(getKeysIterable());
111120
}
112121

113122
@Override
114123
@TruffleBoundary
115124
public Iterable<Object> values() {
116125
ArrayList<Object> entries = new ArrayList<>(store.size());
117-
Shape shape = store.getShape();
118-
for (Object key : shape.getKeys()) {
126+
for (Object key : getKeysIterable()) {
119127
entries.add(store.get(key));
120128
}
121129
return wrapJavaIterable(entries);
@@ -125,8 +133,7 @@ public Iterable<Object> values() {
125133
@TruffleBoundary
126134
public Iterable<DictEntry> entries() {
127135
ArrayList<DictEntry> entries = new ArrayList<>(store.size());
128-
Shape shape = store.getShape();
129-
for (Object key : shape.getKeys()) {
136+
for (Object key : getKeysIterable()) {
130137
entries.add(new DictEntry(key, store.get(key)));
131138
}
132139
return wrapJavaIterable(entries);
@@ -143,6 +150,67 @@ public DynamicObject getStore() {
143150
return store;
144151
}
145152

153+
private static class FilterIterator<T> implements Iterator<T> {
154+
private final Iterator<T> iterator;
155+
private final Predicate<T> predicate;
156+
private T value;
157+
private boolean valueIsSet = false;
158+
159+
private FilterIterator(Iterator<T> iterator, Predicate<T> predicate) {
160+
this.iterator = iterator;
161+
this.predicate = predicate;
162+
}
163+
164+
@Override
165+
public boolean hasNext() {
166+
return valueIsSet || consumeUntilNext();
167+
}
168+
169+
@Override
170+
public T next() {
171+
if (valueIsSet || consumeUntilNext()) {
172+
valueIsSet = false;
173+
return value;
174+
}
175+
// no more vals matching were found
176+
throw new NoSuchElementException();
177+
}
178+
179+
@TruffleBoundary
180+
private boolean consumeUntilNext() {
181+
while (iterator.hasNext()) {
182+
T next = iterator.next();
183+
if (predicate.test(next)) {
184+
value = next;
185+
valueIsSet = true;
186+
return true;
187+
}
188+
}
189+
return false;
190+
}
191+
}
192+
193+
private static class NoValuePredicate<T> implements Predicate<T> {
194+
195+
private final DynamicObject store;
196+
197+
private NoValuePredicate(DynamicObject store) {
198+
this.store = store;
199+
}
200+
201+
@Override
202+
public boolean test(T t) {
203+
return store.get(t) != PNone.NO_VALUE;
204+
}
205+
}
206+
207+
private static class NoValueFilterIterator extends FilterIterator<Object> {
208+
209+
private NoValueFilterIterator(DynamicObject store) {
210+
super(store.getShape().getKeys().iterator(), new NoValuePredicate<>(store));
211+
}
212+
}
213+
146214
public static final class FastDictStorage extends DynamicObjectStorage {
147215
public FastDictStorage() {
148216
}
@@ -159,8 +227,42 @@ public HashingStorage copy(Equivalence eq) {
159227
}
160228

161229
public static final class PythonObjectDictStorage extends DynamicObjectStorage {
230+
private int size = -1;
231+
private Shape shape;
232+
162233
public PythonObjectDictStorage(DynamicObject store) {
163234
super(store);
235+
shape = store.getShape();
236+
}
237+
238+
@Override
239+
protected Iterable<Object> getKeysIterable() {
240+
return new Iterable<Object>() {
241+
@Override
242+
public Iterator<Object> iterator() {
243+
return new NoValueFilterIterator(getStore());
244+
}
245+
};
246+
}
247+
248+
@Override
249+
public int length() {
250+
CompilerAsserts.neverPartOfCompilation();
251+
if (shape != getStore().getShape()) {
252+
size = 0;
253+
for (@SuppressWarnings("unused")
254+
Object ignored : getKeysIterable()) {
255+
size += 1;
256+
}
257+
shape = getStore().getShape();
258+
}
259+
return size;
260+
}
261+
262+
@Override
263+
public boolean hasKey(Object key, Equivalence eq) {
264+
CompilerAsserts.neverPartOfCompilation();
265+
return super.hasKey(key, eq) && getStore().get(key, PNone.NO_VALUE) != PNone.NO_VALUE;
164266
}
165267

166268
@Override

0 commit comments

Comments
 (0)