~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/_simple_set_pyx.pyx

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-05-19 06:14:38 UTC
  • mfrom: (1704.2.23 bzr.mbp.integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060519061438-6300caf3926c3cff
(mbp) small fixes

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009 Canonical Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
"""Definition of a class that is similar to Set with some small changes."""
18
 
 
19
 
cdef extern from "python-compat.h":
20
 
    pass
21
 
 
22
 
cdef extern from "Python.h":
23
 
    ctypedef unsigned long size_t
24
 
    ctypedef long (*hashfunc)(PyObject*) except -1
25
 
    ctypedef object (*richcmpfunc)(PyObject *, PyObject *, int)
26
 
    ctypedef int (*visitproc)(PyObject *, void *)
27
 
    ctypedef int (*traverseproc)(PyObject *, visitproc, void *)
28
 
    int Py_EQ
29
 
    void Py_INCREF(PyObject *)
30
 
    void Py_DECREF(PyObject *)
31
 
    ctypedef struct PyTypeObject:
32
 
        hashfunc tp_hash
33
 
        richcmpfunc tp_richcompare
34
 
        traverseproc tp_traverse
35
 
 
36
 
    PyTypeObject *Py_TYPE(PyObject *)
37
 
    # Note: *Don't* use hash(), Pyrex 0.9.8.5 thinks it returns an 'int', and
38
 
    #       thus silently truncates to 32-bits on 64-bit machines.
39
 
    long PyObject_Hash(PyObject *) except -1
40
 
        
41
 
    void *PyMem_Malloc(size_t nbytes)
42
 
    void PyMem_Free(void *)
43
 
    void memset(void *, int, size_t)
44
 
 
45
 
 
46
 
# Dummy is an object used to mark nodes that have been deleted. Since
47
 
# collisions require us to move a node to an alternative location, if we just
48
 
# set an entry to NULL on delete, we won't find any relocated nodes.
49
 
# We have to use _dummy_obj because we need to keep a refcount to it, but we
50
 
# also use _dummy as a pointer, because it avoids having to put <PyObject*> all
51
 
# over the code base.
52
 
cdef object _dummy_obj
53
 
cdef PyObject *_dummy
54
 
_dummy_obj = object()
55
 
_dummy = <PyObject *>_dummy_obj
56
 
 
57
 
 
58
 
cdef object _NotImplemented
59
 
_NotImplemented = NotImplemented
60
 
 
61
 
 
62
 
cdef int _is_equal(PyObject *this, long this_hash, PyObject *other) except -1:
63
 
    cdef long other_hash
64
 
 
65
 
    if this == other:
66
 
        return 1
67
 
    other_hash = PyObject_Hash(other)
68
 
    if other_hash != this_hash:
69
 
        return 0
70
 
 
71
 
    # This implements a subset of the PyObject_RichCompareBool functionality.
72
 
    # Namely it:
73
 
    #   1) Doesn't try to do anything with old-style classes
74
 
    #   2) Assumes that both objects have a tp_richcompare implementation, and
75
 
    #      that if that is not enough to compare equal, then they are not
76
 
    #      equal. (It doesn't try to cast them both to some intermediate form
77
 
    #      that would compare equal.)
78
 
    res = Py_TYPE(this).tp_richcompare(this, other, Py_EQ)
79
 
    if res is _NotImplemented:
80
 
        res = Py_TYPE(other).tp_richcompare(other, this, Py_EQ)
81
 
        if res is _NotImplemented:
82
 
            return 0
83
 
    if res:
84
 
        return 1
85
 
    return 0
86
 
 
87
 
 
88
 
cdef public api class SimpleSet [object SimpleSetObject, type SimpleSet_Type]:
89
 
    """This class can be used to track canonical forms for objects.
90
 
 
91
 
    It is similar in function to the interned dictionary that is used by
92
 
    strings. However:
93
 
 
94
 
      1) It assumes that hash(obj) is cheap, so does not need to inline a copy
95
 
         of it
96
 
      2) It only stores one reference to the object, rather than 2 (key vs
97
 
         key:value)
98
 
 
99
 
    As such, it uses 1/3rd the amount of memory to store a pointer to the
100
 
    interned object.
101
 
    """
102
 
    # Attributes are defined in the .pxd file
103
 
    DEF DEFAULT_SIZE=1024
104
 
 
105
 
    def __init__(self):
106
 
        cdef Py_ssize_t size, n_bytes
107
 
 
108
 
        size = DEFAULT_SIZE
109
 
        self._mask = size - 1
110
 
        self._used = 0
111
 
        self._fill = 0
112
 
        n_bytes = sizeof(PyObject*) * size;
113
 
        self._table = <PyObject **>PyMem_Malloc(n_bytes)
114
 
        if self._table == NULL:
115
 
            raise MemoryError()
116
 
        memset(self._table, 0, n_bytes)
117
 
 
118
 
    def __dealloc__(self):
119
 
        if self._table != NULL:
120
 
            PyMem_Free(self._table)
121
 
            self._table = NULL
122
 
 
123
 
    property used:
124
 
        def __get__(self):
125
 
            return self._used
126
 
 
127
 
    property fill:
128
 
        def __get__(self):
129
 
            return self._fill
130
 
 
131
 
    property mask:
132
 
        def __get__(self):
133
 
            return self._mask
134
 
 
135
 
    def _memory_size(self):
136
 
        """Return the number of bytes of memory consumed by this class."""
137
 
        return sizeof(self) + (sizeof(PyObject*)*(self._mask + 1))
138
 
 
139
 
    def __len__(self):
140
 
        return self._used
141
 
 
142
 
    def _test_lookup(self, key):
143
 
        cdef PyObject **slot
144
 
 
145
 
        slot = _lookup(self, key)
146
 
        if slot[0] == NULL:
147
 
            res = '<null>'
148
 
        elif slot[0] == _dummy:
149
 
            res = '<dummy>'
150
 
        else:
151
 
            res = <object>slot[0]
152
 
        return <int>(slot - self._table), res
153
 
 
154
 
    def __contains__(self, key):
155
 
        """Is key present in this SimpleSet."""
156
 
        cdef PyObject **slot
157
 
 
158
 
        slot = _lookup(self, key)
159
 
        if slot[0] == NULL or slot[0] == _dummy:
160
 
            return False
161
 
        return True
162
 
 
163
 
    cdef PyObject *_get(self, object key) except? NULL:
164
 
        """Return the object (or nothing) define at the given location."""
165
 
        cdef PyObject **slot
166
 
 
167
 
        slot = _lookup(self, key)
168
 
        if slot[0] == NULL or slot[0] == _dummy:
169
 
            return NULL
170
 
        return slot[0]
171
 
 
172
 
    def __getitem__(self, key):
173
 
        """Return a stored item that is equivalent to key."""
174
 
        cdef PyObject *py_val
175
 
 
176
 
        py_val = self._get(key)
177
 
        if py_val == NULL:
178
 
            raise KeyError("Key %s is not present" % key)
179
 
        val = <object>(py_val)
180
 
        return val
181
 
 
182
 
    cdef int _insert_clean(self, PyObject *key) except -1:
183
 
        """Insert a key into self.table.
184
 
 
185
 
        This is only meant to be used during times like '_resize',
186
 
        as it makes a lot of assuptions about keys not already being present,
187
 
        and there being no dummy entries.
188
 
        """
189
 
        cdef size_t i, n_lookup
190
 
        cdef long the_hash
191
 
        cdef PyObject **table, **slot
192
 
        cdef Py_ssize_t mask
193
 
 
194
 
        mask = self._mask
195
 
        table = self._table
196
 
 
197
 
        the_hash = PyObject_Hash(key)
198
 
        i = the_hash
199
 
        for n_lookup from 0 <= n_lookup <= <size_t>mask: # Don't loop forever
200
 
            slot = &table[i & mask]
201
 
            if slot[0] == NULL:
202
 
                slot[0] = key
203
 
                self._fill = self._fill + 1
204
 
                self._used = self._used + 1
205
 
                return 1
206
 
            i = i + 1 + n_lookup
207
 
        raise RuntimeError('ran out of slots.')
208
 
 
209
 
    def _py_resize(self, min_used):
210
 
        """Do not use this directly, it is only exposed for testing."""
211
 
        return self._resize(min_used)
212
 
 
213
 
    cdef Py_ssize_t _resize(self, Py_ssize_t min_used) except -1:
214
 
        """Resize the internal table.
215
 
 
216
 
        The final table will be big enough to hold at least min_used entries.
217
 
        We will copy the data from the existing table over, leaving out dummy
218
 
        entries.
219
 
 
220
 
        :return: The new size of the internal table
221
 
        """
222
 
        cdef Py_ssize_t new_size, n_bytes, remaining
223
 
        cdef PyObject **new_table, **old_table, **slot
224
 
 
225
 
        new_size = DEFAULT_SIZE
226
 
        while new_size <= min_used and new_size > 0:
227
 
            new_size = new_size << 1
228
 
        # We rolled over our signed size field
229
 
        if new_size <= 0:
230
 
            raise MemoryError()
231
 
        # Even if min_used == self._mask + 1, and we aren't changing the actual
232
 
        # size, we will still run the algorithm so that dummy entries are
233
 
        # removed
234
 
        # TODO: Test this
235
 
        # if new_size < self._used:
236
 
        #     raise RuntimeError('cannot shrink SimpleSet to something'
237
 
        #                        ' smaller than the number of used slots.')
238
 
        n_bytes = sizeof(PyObject*) * new_size;
239
 
        new_table = <PyObject **>PyMem_Malloc(n_bytes)
240
 
        if new_table == NULL:
241
 
            raise MemoryError()
242
 
 
243
 
        old_table = self._table
244
 
        self._table = new_table
245
 
        memset(self._table, 0, n_bytes)
246
 
        self._mask = new_size - 1
247
 
        self._used = 0
248
 
        remaining = self._fill
249
 
        self._fill = 0
250
 
 
251
 
        # Moving everything to the other table is refcount neutral, so we don't
252
 
        # worry about it.
253
 
        slot = old_table
254
 
        while remaining > 0:
255
 
            if slot[0] == NULL: # unused slot
256
 
                pass 
257
 
            elif slot[0] == _dummy: # dummy slot
258
 
                remaining = remaining - 1
259
 
            else: # active slot
260
 
                remaining = remaining - 1
261
 
                self._insert_clean(slot[0])
262
 
            slot = slot + 1
263
 
        PyMem_Free(old_table)
264
 
        return new_size
265
 
 
266
 
    def add(self, key):
267
 
        """Similar to set.add(), start tracking this key.
268
 
        
269
 
        There is one small difference, which is that we return the object that
270
 
        is stored at the given location. (which is closer to the
271
 
        dict.setdefault() functionality.)
272
 
        """
273
 
        return self._add(key)
274
 
 
275
 
    cdef object _add(self, key):
276
 
        cdef PyObject **slot, *py_key
277
 
        cdef int added
278
 
 
279
 
        py_key = <PyObject *>key
280
 
        if (Py_TYPE(py_key).tp_richcompare == NULL
281
 
            or Py_TYPE(py_key).tp_hash == NULL):
282
 
            raise TypeError('Types added to SimpleSet must implement'
283
 
                            ' both tp_richcompare and tp_hash')
284
 
        added = 0
285
 
        # We need at least one empty slot
286
 
        assert self._used < self._mask
287
 
        slot = _lookup(self, key)
288
 
        if (slot[0] == NULL):
289
 
            Py_INCREF(py_key)
290
 
            self._fill = self._fill + 1
291
 
            self._used = self._used + 1
292
 
            slot[0] = py_key
293
 
            added = 1
294
 
        elif (slot[0] == _dummy):
295
 
            Py_INCREF(py_key)
296
 
            self._used = self._used + 1
297
 
            slot[0] = py_key
298
 
            added = 1
299
 
        # No else: clause. If _lookup returns a pointer to
300
 
        # a live object, then we already have a value at this location.
301
 
        retval = <object>(slot[0])
302
 
        # PySet and PyDict use a 2-3rds full algorithm, we'll follow suit
303
 
        if added and (self._fill * 3) >= ((self._mask + 1) * 2):
304
 
            # However, we always work for a load factor of 2:1
305
 
            self._resize(self._used * 2)
306
 
        # Even if we resized and ended up moving retval into a different slot,
307
 
        # it is still the value that is held at the slot equivalent to 'key',
308
 
        # so we can still return it
309
 
        return retval
310
 
 
311
 
    def discard(self, key):
312
 
        """Remove key from the set, whether it exists or not.
313
 
 
314
 
        :return: False if the item did not exist, True if it did
315
 
        """
316
 
        if self._discard(key):
317
 
            return True
318
 
        return False
319
 
 
320
 
    cdef int _discard(self, key) except -1:
321
 
        cdef PyObject **slot, *py_key
322
 
 
323
 
        slot = _lookup(self, key)
324
 
        if slot[0] == NULL or slot[0] == _dummy:
325
 
            return 0
326
 
        self._used = self._used - 1
327
 
        Py_DECREF(slot[0])
328
 
        slot[0] = _dummy
329
 
        # PySet uses the heuristic: If more than 1/5 are dummies, then resize
330
 
        #                           them away
331
 
        #   if ((so->_fill - so->_used) * 5 < so->mask)
332
 
        # However, we are planning on using this as an interning structure, in
333
 
        # which we will be putting a lot of objects. And we expect that large
334
 
        # groups of them are going to have the same lifetime.
335
 
        # Dummy entries hurt a little bit because they cause the lookup to keep
336
 
        # searching, but resizing is also rather expensive
337
 
        # For now, we'll just use their algorithm, but we may want to revisit
338
 
        # it
339
 
        if ((self._fill - self._used) * 5 > self._mask):
340
 
            self._resize(self._used * 2)
341
 
        return 1
342
 
 
343
 
    def __iter__(self):
344
 
        return _SimpleSet_iterator(self)
345
 
 
346
 
 
347
 
cdef class _SimpleSet_iterator:
348
 
    """Iterator over the SimpleSet structure."""
349
 
 
350
 
    cdef Py_ssize_t pos
351
 
    cdef SimpleSet set
352
 
    cdef Py_ssize_t _used # track if things have been mutated while iterating
353
 
    cdef Py_ssize_t len # number of entries left
354
 
 
355
 
    def __init__(self, obj):
356
 
        self.set = obj
357
 
        self.pos = 0
358
 
        self._used = self.set._used
359
 
        self.len = self.set._used
360
 
 
361
 
    def __iter__(self):
362
 
        return self
363
 
 
364
 
    def __next__(self):
365
 
        cdef Py_ssize_t mask, i
366
 
        cdef PyObject *key
367
 
 
368
 
        if self.set is None:
369
 
            raise StopIteration
370
 
        if self.set._used != self._used:
371
 
            # Force this exception to continue to be raised
372
 
            self._used = -1
373
 
            raise RuntimeError("Set size changed during iteration")
374
 
        if not SimpleSet_Next(self.set, &self.pos, &key):
375
 
            self.set = None
376
 
            raise StopIteration
377
 
        # we found something
378
 
        the_key = <object>key # INCREF
379
 
        self.len = self.len - 1
380
 
        return the_key
381
 
 
382
 
    def __length_hint__(self):
383
 
        if self.set is not None and self._used == self.set._used:
384
 
            return self.len
385
 
        return 0
386
 
    
387
 
 
388
 
 
389
 
cdef api SimpleSet SimpleSet_New():
390
 
    """Create a new SimpleSet object."""
391
 
    return SimpleSet()
392
 
 
393
 
 
394
 
cdef SimpleSet _check_self(object self):
395
 
    """Check that the parameter is not None.
396
 
 
397
 
    Pyrex/Cython will do type checking, but only to ensure that an object is
398
 
    either the right type or None. You can say "object foo not None" for pure
399
 
    python functions, but not for C functions.
400
 
    So this is just a helper for all the apis that need to do the check.
401
 
    """
402
 
    cdef SimpleSet true_self
403
 
    if self is None:
404
 
        raise TypeError('self must not be None')
405
 
    true_self = self
406
 
    return true_self
407
 
 
408
 
 
409
 
cdef PyObject **_lookup(SimpleSet self, object key) except NULL:
410
 
    """Find the slot where 'key' would fit.
411
 
 
412
 
    This is the same as a dicts 'lookup' function.
413
 
 
414
 
    :param key: An object we are looking up
415
 
    :param hash: The hash for key
416
 
    :return: The location in self.table where key should be put.
417
 
        location == NULL is an exception, but (*location) == NULL just
418
 
        indicates the slot is empty and can be used.
419
 
    """
420
 
    # This uses Quadratic Probing:
421
 
    #  http://en.wikipedia.org/wiki/Quadratic_probing
422
 
    # with c1 = c2 = 1/2
423
 
    # This leads to probe locations at:
424
 
    #  h0 = hash(k1)
425
 
    #  h1 = h0 + 1
426
 
    #  h2 = h0 + 3 = h1 + 1 + 1
427
 
    #  h3 = h0 + 6 = h2 + 1 + 2
428
 
    #  h4 = h0 + 10 = h2 + 1 + 3
429
 
    # Note that all of these are '& mask', but that is computed *after* the
430
 
    # offset.
431
 
    # This differs from the algorithm used by Set and Dict. Which, effectively,
432
 
    # use double-hashing, and a step size that starts large, but dwindles to
433
 
    # stepping one-by-one.
434
 
    # This gives more 'locality' in that if you have a collision at offset X,
435
 
    # the first fallback is X+1, which is fast to check. However, that means
436
 
    # that an object w/ hash X+1 will also check there, and then X+2 next.
437
 
    # However, for objects with differing hashes, their chains are different.
438
 
    # The former checks X, X+1, X+3, ... the latter checks X+1, X+2, X+4, ...
439
 
    # So different hashes diverge quickly.
440
 
    # A bigger problem is that we *only* ever use the lowest bits of the hash
441
 
    # So all integers (x + SIZE*N) will resolve into the same bucket, and all
442
 
    # use the same collision resolution. We may want to try to find a way to
443
 
    # incorporate the upper bits of the hash with quadratic probing. (For
444
 
    # example, X, X+1, X+3+some_upper_bits, X+6+more_upper_bits, etc.)
445
 
    cdef size_t i, n_lookup
446
 
    cdef Py_ssize_t mask
447
 
    cdef long key_hash
448
 
    cdef PyObject **table, **slot, *cur, **free_slot, *py_key
449
 
 
450
 
    py_key = <PyObject *>key
451
 
    # Note: avoid using hash(obj) because of a bug w/ pyrex 0.9.8.5 and 64-bit
452
 
    #       (it treats hash() as returning an 'int' rather than a 'long')
453
 
    key_hash = PyObject_Hash(py_key)
454
 
    i = <size_t>key_hash
455
 
    mask = self._mask
456
 
    table = self._table
457
 
    free_slot = NULL
458
 
    for n_lookup from 0 <= n_lookup <= <size_t>mask: # Don't loop forever
459
 
        slot = &table[i & mask]
460
 
        cur = slot[0]
461
 
        if cur == NULL:
462
 
            # Found a blank spot
463
 
            if free_slot != NULL:
464
 
                # Did we find an earlier _dummy entry?
465
 
                return free_slot
466
 
            else:
467
 
                return slot
468
 
        if cur == py_key:
469
 
            # Found an exact pointer to the key
470
 
            return slot
471
 
        if cur == _dummy:
472
 
            if free_slot == NULL:
473
 
                free_slot = slot
474
 
        elif _is_equal(py_key, key_hash, cur):
475
 
            # Both py_key and cur belong in this slot, return it
476
 
            return slot
477
 
        i = i + 1 + n_lookup
478
 
    raise AssertionError('should never get here')
479
 
 
480
 
 
481
 
cdef api PyObject **_SimpleSet_Lookup(object self, object key) except NULL:
482
 
    """Find the slot where 'key' would fit.
483
 
 
484
 
    This is the same as a dicts 'lookup' function. This is a private
485
 
    api because mutating what you get without maintaing the other invariants
486
 
    is a 'bad thing'.
487
 
 
488
 
    :param key: An object we are looking up
489
 
    :param hash: The hash for key
490
 
    :return: The location in self._table where key should be put
491
 
        should never be NULL, but may reference a NULL (PyObject*)
492
 
    """
493
 
    return _lookup(_check_self(self), key)
494
 
 
495
 
 
496
 
cdef api object SimpleSet_Add(object self, object key):
497
 
    """Add a key to the SimpleSet (set).
498
 
 
499
 
    :param self: The SimpleSet to add the key to.
500
 
    :param key: The key to be added. If the key is already present,
501
 
        self will not be modified
502
 
    :return: The current key stored at the location defined by 'key'.
503
 
        This may be the same object, or it may be an equivalent object.
504
 
        (consider dict.setdefault(key, key))
505
 
    """
506
 
    return _check_self(self)._add(key)
507
 
    
508
 
 
509
 
cdef api int SimpleSet_Contains(object self, object key) except -1:
510
 
    """Is key present in self?"""
511
 
    return (key in _check_self(self))
512
 
 
513
 
 
514
 
cdef api int SimpleSet_Discard(object self, object key) except -1:
515
 
    """Remove the object referenced at location 'key'.
516
 
 
517
 
    :param self: The SimpleSet being modified
518
 
    :param key: The key we are checking on
519
 
    :return: 1 if there was an object present, 0 if there was not, and -1 on
520
 
        error.
521
 
    """
522
 
    return _check_self(self)._discard(key)
523
 
 
524
 
 
525
 
cdef api PyObject *SimpleSet_Get(SimpleSet self, object key) except? NULL:
526
 
    """Get a pointer to the object present at location 'key'.
527
 
 
528
 
    This returns an object which is equal to key which was previously added to
529
 
    self. This returns a borrowed reference, as it may also return NULL if no
530
 
    value is present at that location.
531
 
 
532
 
    :param key: The value we are looking for
533
 
    :return: The object present at that location
534
 
    """
535
 
    return _check_self(self)._get(key)
536
 
 
537
 
 
538
 
cdef api Py_ssize_t SimpleSet_Size(object self) except -1:
539
 
    """Get the number of active entries in 'self'"""
540
 
    return _check_self(self)._used
541
 
 
542
 
 
543
 
cdef api int SimpleSet_Next(object self, Py_ssize_t *pos,
544
 
                            PyObject **key) except -1:
545
 
    """Walk over items in a SimpleSet.
546
 
 
547
 
    :param pos: should be initialized to 0 by the caller, and will be updated
548
 
        by this function
549
 
    :param key: Will return a borrowed reference to key
550
 
    :return: 0 if nothing left, 1 if we are returning a new value
551
 
    """
552
 
    cdef Py_ssize_t i, mask
553
 
    cdef SimpleSet true_self
554
 
    cdef PyObject **table
555
 
    true_self = _check_self(self)
556
 
    i = pos[0]
557
 
    if (i < 0):
558
 
        return 0
559
 
    mask = true_self._mask
560
 
    table= true_self._table
561
 
    while (i <= mask and (table[i] == NULL or table[i] == _dummy)):
562
 
        i = i + 1
563
 
    pos[0] = i + 1
564
 
    if (i > mask):
565
 
        return 0 # All done
566
 
    if (key != NULL):
567
 
        key[0] = table[i]
568
 
    return 1
569
 
 
570
 
 
571
 
cdef int SimpleSet_traverse(SimpleSet self, visitproc visit,
572
 
                            void *arg) except -1:
573
 
    """This is an implementation of 'tp_traverse' that hits the whole table.
574
 
 
575
 
    Cython/Pyrex don't seem to let you define a tp_traverse, and they only
576
 
    define one for you if you have an 'object' attribute. Since they don't
577
 
    support C arrays of objects, we access the PyObject * directly.
578
 
    """
579
 
    cdef Py_ssize_t pos
580
 
    cdef PyObject *next_key
581
 
    cdef int ret
582
 
 
583
 
    pos = 0
584
 
    while SimpleSet_Next(self, &pos, &next_key):
585
 
        ret = visit(next_key, arg)
586
 
        if ret:
587
 
            return ret
588
 
    return 0
589
 
 
590
 
# It is a little bit ugly to do this, but it works, and means that Meliae can
591
 
# dump the total memory consumed by all child objects.
592
 
(<PyTypeObject *>SimpleSet).tp_traverse = <traverseproc>SimpleSet_traverse