"""
Containers for storing coercion data
This module provides :class:`TripleDict` and :class:`MonoDict`. These are
structures similar to :class:`~weakref.WeakKeyDictionary` in Python's weakref
module, and are optimized for lookup speed. The keys for :class:`TripleDict`
consist of triples (k1,k2,k3) and are looked up by identity rather than
equality. The keys are stored by weakrefs if possible. If any one of the
components k1, k2, k3 gets garbage collected, then the entry is removed from
the :class:`TripleDict`.
Key components that do not allow for weakrefs are stored via a normal
refcounted reference. That means that any entry stored using a triple
(k1,k2,k3) so that none of the k1,k2,k3 allows a weak reference behaves
as an entry in a normal dictionary: Its existence in :class:`TripleDict`
prevents it from being garbage collected.
That container currently is used to store coercion and conversion maps between
two parents (:trac:`715`) and to store homsets of pairs of objects of a
category (:trac:`11521`). In both cases, it is essential that the parent
structures remain garbage collectable, it is essential that the data access is
faster than with a usual :class:`~weakref.WeakKeyDictionary`, and we enforce
the "unique parent condition" in Sage (parent structures should be identical
if they are equal).
:class:`MonoDict` behaves similarly, but it takes a single item as a key. It
is used for caching the parents which allow a coercion map into a fixed other
parent (:trac:`12313`).
By :trac:`14159`, :class:`MonoDict` and :class:`TripleDict` can be optionally
used with weak references on the values.
"""
from cpython.list cimport *
from cpython.mem cimport *
from cpython.string cimport PyString_FromString
from cpython cimport Py_XINCREF, Py_XDECREF
from libc.string cimport memset
from weakref import KeyedRef, ref
cdef type fixed_KeyedRef = KeyedRef
cdef type fixed_ref = ref
from cpython.pycapsule cimport PyCapsule_New, PyCapsule_GetPointer
cdef extern from "Python.h":
PyObject* PyWeakref_GetObject(object r)
PyObject* Py_None
int PyList_SetItem(object list, Py_ssize_t index,PyObject * item) except -1
ctypedef int (*visitproc)(PyObject* ob, void* arg)
ctypedef struct PyTypeObject:
void * tp_traverse
void * tp_clear
cpdef inline Py_ssize_t signed_id(x):
"""
A function like Python's :func:`id` returning *signed* integers,
which are guaranteed to fit in a ``Py_ssize_t``.
Theoretically, there is no guarantee that two different Python
objects have different ``signed_id()`` values. However, under the
mild assumption that a C pointer fits in a ``Py_ssize_t``, this
is guaranteed.
TESTS::
sage: a = 1.23e45 # some object
sage: from sage.structure.coerce_dict import signed_id
sage: s = signed_id(a)
sage: id(a) == s or id(a) == s + 2**32 or id(a) == s + 2**64
True
sage: signed_id(a) <= sys.maxsize
True
"""
return <Py_ssize_t><void *>(x)
cdef object dummy_object = PyString_FromString("dummy")
cdef PyObject* dummy = <PyObject*><void *>dummy_object
cdef struct mono_cell:
void* key_id
PyObject* key_weakref
PyObject* value
cdef object extract_mono_cell(mono_cell *cell):
if cell.key_id != NULL and cell.key_id != dummy :
L=PyList_New(2)
PyList_SetItem(L,0,cell.key_weakref)
PyList_SetItem(L,1,cell.value)
cell.key_id=dummy
cell.key_weakref=NULL
cell.value=NULL
return L
else:
raise RuntimeError("unused mono_cell")
cdef struct triple_cell:
void* key_id1
void* key_id2
void* key_id3
PyObject* key_weakref1
PyObject* key_weakref2
PyObject* key_weakref3
PyObject* value
cdef object extract_triple_cell(triple_cell *cell):
if cell.key_id1 != NULL and cell.key_id1 != dummy :
L=PyList_New(4)
PyList_SetItem(L,0,cell.key_weakref1)
PyList_SetItem(L,1,cell.key_weakref2)
PyList_SetItem(L,2,cell.key_weakref3)
PyList_SetItem(L,3,cell.value)
cell.key_id1=dummy
cell.key_id2=NULL
cell.key_id3=NULL
cell.key_weakref1=NULL
cell.key_weakref2=NULL
cell.key_weakref3=NULL
cell.value=NULL
return L
else:
raise RuntimeError("unused triple_cell")
cdef class MonoDictEraser:
"""
Erase items from a :class:`MonoDict` when a weak reference becomes
invalid.
This is of internal use only. Instances of this class will be passed as a
callback function when creating a weak reference.
EXAMPLES::
sage: from sage.structure.coerce_dict import MonoDict
sage: class A: pass
sage: a = A()
sage: M = MonoDict()
sage: M[a] = 1
sage: len(M)
1
sage: del a
sage: import gc
sage: n = gc.collect()
sage: len(M) # indirect doctest
0
AUTHOR:
- Simon King (2012-01)
- Nils Bruin (2013-11)
"""
cdef D
def __init__(self, D):
"""
INPUT:
A :class:`MonoDict`.
EXAMPLES::
sage: k = set([1])
sage: D = sage.structure.coerce_dict.MonoDict([(k,1)])
sage: len(D)
1
sage: del k
sage: len(D) # indirect doctest
0
"""
self.D = fixed_ref(D)
def __call__(self, r):
"""
INPUT:
A weak reference with key.
For internal use only.
EXAMPLES::
sage: k = set([1])
sage: D = sage.structure.coerce_dict.MonoDict([(k,1)])
sage: len(D)
1
sage: del k
sage: len(D) # indirect doctest
0
"""
cdef MonoDict md = <object> PyWeakref_GetObject(self.D)
if md is None:
return
if md.table == NULL:
return
cdef mono_cell* cursor = md.lookup(<PyObject *>PyCapsule_GetPointer(r.key,NULL))
if (cursor.key_id != NULL and cursor.key_id != dummy):
if (cursor.key_weakref == <PyObject*><void*>r or cursor.value == <PyObject*><void*>r):
L=extract_mono_cell(cursor)
md.used -= 1
else:
raise RuntimeError("eraser: key match but no weakref match")
cdef class MonoDict:
"""
This is a hashtable specifically designed for (read) speed in
the coercion model.
It differs from a python WeakKeyDictionary in the following important ways:
- Comparison is done using the 'is' rather than '==' operator.
- Only weak references to the keys are stored if at all possible.
Keys that do not allow for weak references are stored with a normal
refcounted reference.
- The callback of the weak references is safe against recursion, see below.
There are special cdef set/get methods for faster access.
It is bare-bones in the sense that not all dictionary methods are
implemented.
IMPLEMENTATION:
It is implemented as a hash table with open addressing, similar to python's
dict.
If ki supports weak references then ri is a weak reference to ki with a
callback to remove the entry from the dictionary if ki gets garbage
collected. If ki is does not support weak references then ri is identical to ki.
In the latter case the presence of the key in the dictionary prevents it from
being garbage collected.
INPUT:
- ``size`` -- unused parameter, present for backward compatibility.
- ``data`` -- optional iterable defining initial data.
- ``threshold`` -- unused parameter, present for backward compatibility.
- ``weak_values`` -- optional bool (default False). If it is true, weak references
to the values in this dictionary will be used, when possible.
EXAMPLES::
sage: from sage.structure.coerce_dict import MonoDict
sage: L = MonoDict()
sage: a = 'a'; b = 'ab'; c = -15
sage: L[a] = 1
sage: L[b] = 2
sage: L[c] = 3
The key is expected to be a unique object. Hence, the item stored for ``c``
can not be obtained by providing another equal number::
sage: L[a]
1
sage: L[b]
2
sage: L[c]
3
sage: L[-15]
Traceback (most recent call last):
...
KeyError: -15
Not all features of Python dictionaries are available, but iteration over
the dictionary items is possible::
sage: # for some reason the following failed in "make ptest"
sage: # on some installations, see #12313 for details
sage: sorted(L.iteritems()) # random layout
[(-15, 3), ('a', 1), ('ab', 2)]
sage: # the following seems to be more consistent
sage: set(L.iteritems())
set([('a', 1), ('ab', 2), (-15, 3)])
sage: del L[c]
sage: sorted(L.iteritems())
[('a', 1), ('ab', 2)]
sage: len(L)
2
sage: for i in range(1000):
... L[i] = i
sage: len(L)
1002
sage: L['a']
1
sage: L['c']
Traceback (most recent call last):
...
KeyError: 'c'
Note that this kind of dictionary is also used for caching actions
and coerce maps. In previous versions of Sage, the cache was by
strong references and resulted in a memory leak in the following
example. However, this leak was fixed by :trac:`715`, using
weak references::
sage: K = GF(1<<55,'t')
sage: for i in range(50):
... a = K.random_element()
... E = EllipticCurve(j=a)
... P = E.random_point()
... Q = 2*P
sage: import gc
sage: n = gc.collect()
sage: from sage.schemes.elliptic_curves.ell_finite_field import EllipticCurve_finite_field
sage: LE = [x for x in gc.get_objects() if isinstance(x, EllipticCurve_finite_field)]
sage: len(LE) # indirect doctest
1
TESTS:
Here, we demonstrate the use of weak values.
::
sage: M = MonoDict(13)
sage: MW = MonoDict(13, weak_values=True)
sage: class Foo: pass
sage: a = Foo()
sage: b = Foo()
sage: k = 1
sage: M[k] = a
sage: MW[k] = b
sage: M[k] is a
True
sage: MW[k] is b
True
sage: k in M
True
sage: k in MW
True
While ``M`` uses a strong reference to ``a``, ``MW`` uses a *weak*
reference to ``b``, and after deleting ``b``, the corresponding item of
``MW`` will be removed during the next garbage collection::
sage: import gc
sage: del a,b
sage: _ = gc.collect()
sage: k in M
True
sage: k in MW
False
sage: len(MW)
0
sage: len(M)
1
Note that ``MW`` also accepts values that do not allow for weak references::
sage: MW[k] = int(5)
sage: MW[k]
5
The following demonstrates that :class:`MonoDict` is safer than
:class:`~weakref.WeakKeyDictionary` against recursions created by nested
callbacks; compare :trac:`15069` (the mechanism used now is different, though)::
sage: M = MonoDict(11)
sage: class A: pass
sage: a = A()
sage: prev = a
sage: for i in range(1000):
....: newA = A()
....: M[prev] = newA
....: prev = newA
sage: len(M)
1000
sage: del a
sage: len(M)
0
The corresponding example with a Python :class:`weakref.WeakKeyDictionary`
would result in a too deep recursion during deletion of the dictionary
items::
sage: import weakref
sage: M = weakref.WeakKeyDictionary()
sage: a = A()
sage: prev = a
sage: for i in range(1000):
....: newA = A()
....: M[prev] = newA
....: prev = newA
sage: len(M)
1000
sage: del a
Exception RuntimeError: 'maximum recursion depth exceeded while calling a Python object' in <function remove at ...> ignored
sage: len(M)>0
True
Check that also in the presence of circular references, :class:`MonoDict`
gets properly collected::
sage: import gc
sage: def count_type(T):
....: return len([c for c in gc.get_objects() if isinstance(c,T)])
sage: _=gc.collect()
sage: N=count_type(MonoDict)
sage: for i in range(100):
....: V = [ MonoDict(11,{"id":j+100*i}) for j in range(100)]
....: n= len(V)
....: for i in range(n): V[i][V[(i+1)%n]]=(i+1)%n
....: del V
....: _=gc.collect()
....: assert count_type(MonoDict) == N
sage: count_type(MonoDict) == N
True
AUTHORS:
- Simon King (2012-01)
- Nils Bruin (2012-08)
- Simon King (2013-02)
- Nils Bruin (2013-11)
"""
cdef mono_cell* lookup(self, PyObject* key):
"""
This routine is used for all cases where a (potential) spot for
a key is looked up. The returned value is a pointer into the dictionary
store that either contains an entry with the requested key or a free spot
where an entry for that key should go.
"""
cdef size_t perturb
cdef size_t mask = self.mask
cdef mono_cell* table = self.table
cdef mono_cell* cursor
cdef mono_cell* first_freed = NULL
cdef size_t i = ((<size_t>key)>>8+(<size_t>key))
if key == NULL or key == dummy:
print("Request to look up invalid key")
cursor = &(table[i & mask])
perturb = (<size_t>key)>>3
while (cursor.key_id != key):
if cursor.key_id == NULL:
return first_freed if (first_freed != NULL) else cursor
if first_freed == NULL and cursor.key_id == dummy:
first_freed = cursor
i = 5*i + perturb +1
cursor = &(table[i & mask])
perturb = perturb >> 5
return cursor
cdef int resize(self) except -1:
"""
Resize dictionary. That can also mean shrink! Size is always a power of 2.
"""
cdef mono_cell* old_table=self.table
cdef size_t old_mask = self.mask
cdef size_t newsize = 8
cdef size_t minsize = 2*self.used
cdef size_t i
cdef mono_cell* cursor
cdef mono_cell* entry
while newsize < minsize:
newsize = newsize<<1
cdef mono_cell* table = <mono_cell*> PyMem_Malloc((newsize)*sizeof(mono_cell))
if table == NULL:
raise MemoryError()
memset(table,0,(newsize)*sizeof(mono_cell))
self.table = table
self.mask = newsize-1
self.used = 0
self.fill = 0
for i in range(old_mask+1):
entry = &(old_table[i])
if entry.key_id != NULL and entry.key_id != dummy:
cursor=self.lookup(<PyObject*>(entry.key_id))
assert cursor.key_id == NULL
cursor[0]=entry[0]
self.used +=1
self.fill +=1
PyMem_Free(old_table)
return 0
def __init__(self, size=11, data=None, threshold=0.7, weak_values=False):
"""
Create a special dict using singletons for keys.
EXAMPLES::
sage: from sage.structure.coerce_dict import MonoDict
sage: L = MonoDict(31)
sage: a = 'a'
sage: L[a] = 1
sage: L[a]
1
sage: L = MonoDict({a: 1})
sage: L[a]
1
"""
if data is None:
try:
data=size.iteritems()
except AttributeError:
try:
data=iter(size)
except TypeError:
pass
else:
try:
data=data.iteritems()
except AttributeError:
pass
if self.table != NULL:
raise RuntimeError("table already initialized. Called __init__ second time?")
cdef minsize = 8
cdef size_t newsize = 1<<3
while newsize < minsize:
newsize = newsize <<1
self.mask = newsize - 1
cdef mono_cell* table = <mono_cell*> PyMem_Malloc(newsize*sizeof(mono_cell))
if table == NULL:
raise MemoryError()
memset(table,0,newsize*sizeof(mono_cell))
self.table = table
self.used = 0
self.fill = 0
self.eraser = MonoDictEraser(self)
self.weak_values = weak_values
if data:
for k,v in data:
self.set(k,v)
def __dealloc__(self):
"""
Ensure that the memory allocated by a MonoDict is properly freed.
"""
MonoDict_clear(self)
def __len__(self):
"""
The number of items in self.
EXAMPLES::
sage: from sage.structure.coerce_dict import MonoDict
sage: L = MonoDict(37)
sage: a = 'a'; b = 'b'; c = 'c'
sage: L[a] = 1
sage: L[a] = -1 # re-assign
sage: L[b] = 1
sage: L[c] = None
sage: len(L)
3
"""
return self.used
def __contains__(self, k):
"""
Test if the dictionary contains a given key.
EXAMPLES::
sage: from sage.structure.coerce_dict import MonoDict
sage: L = MonoDict(31)
sage: a = 'a'; b = 'ab'; c = 15
sage: L[a] = 1
sage: L[b] = 2
sage: L[c] = 3
sage: c in L # indirect doctest
True
The keys are compared by identity, not by equality. Hence, we have::
sage: c == 15
True
sage: 15 in L
False
"""
cdef mono_cell* cursor = self.lookup(<PyObject*><void*>k)
if cursor.key_id == NULL or cursor.key_id == dummy:
return False
r = <object>cursor.key_weakref
if isinstance(r, fixed_KeyedRef) and PyWeakref_GetObject(r) == Py_None:
return False
elif not(self.weak_values):
return True
else:
value = <object>cursor.value
return (not isinstance(value,fixed_KeyedRef)) or PyWeakref_GetObject(value) != Py_None
def __getitem__(self, k):
"""
Get the value corresponding to a key.
EXAMPLES::
sage: from sage.structure.coerce_dict import MonoDict
sage: L = MonoDict(31)
sage: a = 'a'; b = 'b'; c = 15
sage: L[a] = 1
sage: L[b] = 2
sage: L[c] = 3
sage: L[c] # indirect doctest
3
Note that the keys are supposed to be unique::
sage: c==15
True
sage: c is 15
False
sage: L[15]
Traceback (most recent call last):
...
KeyError: 15
"""
return self.get(k)
cdef get(self, object k):
cdef mono_cell* cursor = self.lookup(<PyObject*><void *>k)
if cursor.key_id == NULL or cursor.key_id == dummy:
raise KeyError, k
r = <object>cursor.key_weakref
if isinstance(r, fixed_KeyedRef) and PyWeakref_GetObject(r) == Py_None:
raise KeyError, k
value = <object>cursor.value
if self.weak_values and isinstance(value,fixed_KeyedRef):
value = <object>PyWeakref_GetObject(value)
if value is None:
raise KeyError, k
return value
def __setitem__(self, k, value):
"""
Set the value corresponding to a key.
EXAMPLES::
sage: from sage.structure.coerce_dict import MonoDict
sage: L = MonoDict(31)
sage: a = 'a'
sage: L[a] = -1 # indirect doctest
sage: L[a]
-1
sage: L[a] = 1
sage: L[a]
1
sage: len(L)
1
"""
self.set(k,value)
cdef set(self,object k, object value):
cdef mono_cell entry
cdef PyObject* old_value = NULL
cdef bint maybe_resize = False
entry.key_id = <void*>k
if self.weak_values:
cap_k=PyCapsule_New(<void *>(k),NULL,NULL)
try:
value_store = fixed_KeyedRef(value,self.eraser,cap_k)
entry.value = <PyObject*><void*>value_store
except TypeError:
entry.value = <PyObject*><void*>value
else:
entry.value = <PyObject*><void*>value
Py_XINCREF(entry.value)
cursor = self.lookup(<PyObject*><void*>k)
if cursor.key_id == NULL or cursor.key_id == dummy:
self.used += 1
if cursor.key_id == NULL:
self.fill += 1
maybe_resize = True
if not(self.weak_values):
cap_k=PyCapsule_New(<void *>(k),NULL,NULL)
try:
key_store=fixed_KeyedRef(k,self.eraser,cap_k)
entry.key_weakref = <PyObject*><void*>key_store
except TypeError:
entry.key_weakref = <PyObject*><void*>k
Py_XINCREF(entry.key_weakref)
cursor[0] = entry
if maybe_resize and 3*self.fill > 2*self.mask: self.resize()
else:
old_value = cursor.value
cursor.value = entry.value
Py_XDECREF(old_value)
def __delitem__(self, k):
"""
Delete the value corresponding to a key.
EXAMPLES::
sage: from sage.structure.coerce_dict import MonoDict
sage: L = MonoDict(31)
sage: a = 15
sage: L[a] = -1
sage: len(L)
1
Note that the keys are unique, hence using a key that is equal but not
identical to a results in an error::
sage: del L[15]
Traceback (most recent call last):
...
KeyError: 15
sage: a in L
True
sage: del L[a]
sage: len(L)
0
sage: a in L
False
"""
cdef mono_cell* cursor = self.lookup(<PyObject *><void *>k)
if cursor.key_id == NULL or cursor.key_id==dummy:
raise KeyError, k
L=extract_mono_cell(cursor)
self.used -= 1
def iteritems(self):
"""
EXAMPLES::
sage: from sage.structure.coerce_dict import MonoDict
sage: L = MonoDict(31)
sage: L[1] = None
sage: L[2] = True
sage: list(sorted(L.iteritems()))
[(1, None), (2, True)]
"""
cdef size_t i = 0
while i <= self.mask:
cursor = &(self.table[i])
i += 1
if cursor.key_id != NULL and cursor.key_id != dummy:
key = <object>(cursor.key_weakref)
value = <object>(cursor.value)
if isinstance(key,fixed_KeyedRef):
key = <object>PyWeakref_GetObject(key)
if key is None:
print "found defunct key"
continue
if self.weak_values and isinstance(value,fixed_KeyedRef):
value = <object>PyWeakref_GetObject(value)
if value is None:
print "found defunct value"
continue
yield (key, value)
def __reduce__(self):
"""
Note that we don't expect equality as this class concerns itself with
object identity rather than object equality.
EXAMPLES::
sage: from sage.structure.coerce_dict import MonoDict
sage: L = MonoDict(31)
sage: L[1] = True
sage: loads(dumps(L)) == L
False
sage: list(loads(dumps(L)).iteritems())
[(1, True)]
"""
return MonoDict, (11, dict(self.iteritems()), 0.7)
cdef int MonoDict_traverse(MonoDict op, visitproc visit, void *arg):
cdef int r
if op.table == NULL:
return 0
table=op.table
cdef size_t i = 0
if (<void*>(op.eraser)):
r=visit(<PyObject*><void*>(op.eraser),arg)
if r: return r
for i in range(op.mask+1):
cursor = &table[i]
if cursor.key_id != NULL and cursor.key_id != dummy:
r=visit(cursor.key_weakref,arg)
if r: return r
r=visit(cursor.value,arg)
if r: return r
return 0
cdef int MonoDict_clear(MonoDict op):
if op.table == NULL:
return 0
tmp=op.eraser
cdef mono_cell* table=op.table
cdef size_t mask=op.mask
op.table=NULL
op.eraser=None
op.mask=0
op.used=0
op.fill=0
del tmp
for i in range(mask+1):
cursor = &(table[i])
if cursor.key_id != NULL and cursor.key_id != dummy:
cursor.key_id = dummy
Py_XDECREF(cursor.key_weakref)
Py_XDECREF(cursor.value)
PyMem_Free(table)
return 0
(<PyTypeObject *><void *>MonoDict).tp_traverse = &MonoDict_traverse
(<PyTypeObject *><void *>MonoDict).tp_clear = &MonoDict_clear
cdef class TripleDictEraser:
"""
Erases items from a :class:`TripleDict` when a weak reference becomes
invalid.
This is of internal use only. Instances of this class will be passed as a
callback function when creating a weak reference.
EXAMPLES::
sage: from sage.structure.coerce_dict import TripleDict
sage: class A: pass
sage: a = A()
sage: T = TripleDict()
sage: T[a,ZZ,None] = 1
sage: T[ZZ,a,1] = 2
sage: T[a,a,ZZ] = 3
sage: len(T)
3
sage: del a
sage: import gc
sage: n = gc.collect()
sage: len(T) # indirect doctest
0
AUTHOR:
- Simon King (2012-01)
- Nils Bruin (2013-11)
"""
cdef D
def __init__(self, D):
"""
INPUT:
A :class:`TripleDict`. For internal use only.
EXAMPLES::
sage: D = sage.structure.coerce_dict.TripleDict()
sage: k = set([1])
sage: D[k,1,1] = 1
sage: len(D)
1
sage: del k
sage: len(D) # indirect doctest
0
"""
self.D = fixed_ref(D)
def __call__(self, r):
"""
INPUT:
A weak reference with key.
For internal use only.
EXAMPLES::
sage: from sage.structure.coerce_dict import TripleDict
sage: class A: pass
sage: a = A()
sage: T = TripleDict()
sage: T[a,ZZ,None] = 1
sage: T[ZZ,a,1] = 2
sage: T[a,a,ZZ] = 3
sage: len(T)
3
sage: del a
sage: import gc
sage: n = gc.collect()
sage: len(T) # indirect doctest
0
"""
cdef TripleDict td = <object>PyWeakref_GetObject(self.D)
if td is None:
return
if td.table == NULL:
return
k1,k2,k3 = r.key
cdef triple_cell* cursor = td.lookup(<PyObject*>PyCapsule_GetPointer(k1,NULL),
<PyObject*>PyCapsule_GetPointer(k2,NULL),
<PyObject*>PyCapsule_GetPointer(k3,NULL))
if (cursor.key_id1 != NULL and cursor.key_id1 != dummy):
if (cursor.key_weakref1 == <PyObject*><void*>r or
cursor.key_weakref2 == <PyObject*><void*>r or
cursor.key_weakref3 == <PyObject*><void*>r or
cursor.value == <PyObject*><void*>r):
L=extract_triple_cell(cursor)
td.used -= 1
else:
raise RuntimeError("eraser: key match but no weakref match")
cdef class TripleDict:
"""
This is a hashtable specifically designed for (read) speed in
the coercion model.
It differs from a python dict in the following important ways:
- All keys must be sequence of exactly three elements. All sequence
types (tuple, list, etc.) map to the same item.
- Comparison is done using the 'is' rather than '==' operator.
There are special cdef set/get methods for faster access.
It is bare-bones in the sense that not all dictionary methods are
implemented.
It is implemented as a list of lists (hereafter called buckets). The bucket is
chosen according to a very simple hash based on the object pointer, and each
bucket is of the form [id(k1), id(k2), id(k3), r1, r2, r3, value, id(k1),
id(k2), id(k3), r1, r2, r3, value, ...], on which a linear search is performed.
If a key component ki supports weak references then ri is a weak reference to
ki; otherwise ri is identical to ki.
INPUT:
- ``size`` -- an integer, the initial number of buckets. To spread objects
evenly, the size should ideally be a prime, and certainly not divisible
by 2.
- ``data`` -- optional iterable defining initial data.
- ``threshold`` -- optional number, default `0.7`. It determines how frequently
the dictionary will be resized (large threshold implies rare resizing).
- ``weak_values`` -- optional bool (default False). If it is true, weak references
to the values in this dictionary will be used, when possible.
If any of the key components k1,k2,k3 (this can happen for a key component
that supports weak references) gets garbage collected then the entire
entry disappears. In that sense this structure behaves like a nested
:class:`~weakref.WeakKeyDictionary`.
EXAMPLES::
sage: from sage.structure.coerce_dict import TripleDict
sage: L = TripleDict()
sage: a = 'a'; b = 'b'; c = 'c'
sage: L[a,b,c] = 1
sage: L[a,b,c]
1
sage: L[c,b,a] = -1
sage: list(L.iteritems()) # random order of output.
[(('c', 'b', 'a'), -1), (('a', 'b', 'c'), 1)]
sage: del L[a,b,c]
sage: list(L.iteritems())
[(('c', 'b', 'a'), -1)]
sage: len(L)
1
sage: for i in range(1000):
... L[i,i,i] = i
sage: len(L)
1001
sage: L = TripleDict(L)
sage: L[c,b,a]
-1
sage: L[a,b,c]
Traceback (most recent call last):
...
KeyError: ('a', 'b', 'c')
sage: L[a]
Traceback (most recent call last):
...
KeyError: 'a'
sage: L[a] = 1
Traceback (most recent call last):
...
KeyError: 'a'
Note that this kind of dictionary is also used for caching actions
and coerce maps. In previous versions of Sage, the cache was by
strong references and resulted in a memory leak in the following
example. However, this leak was fixed by :trac:`715`, using weak
references::
sage: K = GF(1<<55,'t')
sage: for i in range(50):
... a = K.random_element()
... E = EllipticCurve(j=a)
... P = E.random_point()
... Q = 2*P
sage: import gc
sage: n = gc.collect()
sage: from sage.schemes.elliptic_curves.ell_finite_field import EllipticCurve_finite_field
sage: LE = [x for x in gc.get_objects() if isinstance(x, EllipticCurve_finite_field)]
sage: len(LE) # indirect doctest
1
TESTS:
Here, we demonstrate the use of weak values.
::
sage: class Foo: pass
sage: T = TripleDict(13)
sage: TW = TripleDict(13, weak_values=True)
sage: a = Foo()
sage: b = Foo()
sage: k = 1
sage: T[a,k,k]=1
sage: T[k,a,k]=2
sage: T[k,k,a]=3
sage: T[k,k,k]=a
sage: TW[b,k,k]=1
sage: TW[k,b,k]=2
sage: TW[k,k,b]=3
sage: TW[k,k,k]=b
sage: len(T)
4
sage: len(TW)
4
sage: (k,k,k) in T
True
sage: (k,k,k) in TW
True
sage: T[k,k,k] is a
True
sage: TW[k,k,k] is b
True
Now, ``T`` holds a strong reference to ``a``, namely in ``T[k,k,k]``. Hence,
when we delete ``a``, *all* items of ``T`` survive::
sage: del a
sage: _ = gc.collect()
sage: len(T)
4
Only when we remove the strong reference, the items become collectable::
sage: del T[k,k,k]
sage: _ = gc.collect()
sage: len(T)
0
The situation is different for ``TW``, since it only holds *weak*
references to ``a``. Therefore, all items become collectable after
deleting ``a``::
sage: del b
sage: _ = gc.collect()
sage: len(TW)
0
.. NOTE::
The index `h` corresponding to the key [k1, k2, k3] is computed as a
value of unsigned type size_t as follows:
.. MATH::
h = id(k1) + 13*id(k2) xor 503 id(k3)
The natural type for this quantity is Py_ssize_t, which is a signed
quantity with the same length as size_t. Storing it in a signed way gives the most
efficient storage into PyInt, while preserving sign information.
In previous situations there were some problems with ending up with negative
indices, which required casting to an unsigned type, i.e.,
(<size_t> h)% N
since C has a sign-preserving % operation This caused problems on 32 bits systems,
see :trac:`715` for details. This is irrelevant for the current implementation.
AUTHORS:
- Robert Bradshaw, 2007-08
- Simon King, 2012-01
- Nils Bruin, 2012-08
- Simon King, 2013-02
- Nils Bruin, 2013-11
"""
cdef triple_cell* lookup(self, PyObject* key1, PyObject* key2, PyObject* key3):
cdef size_t perturb
cdef size_t mask = self.mask
cdef triple_cell* table = self.table
cdef triple_cell* cursor
cdef triple_cell* first_freed = NULL
cdef int j
global summed_expected, samples
cdef size_t key = <size_t>key1 + 13*(<size_t>key2) ^ 503*(<size_t>key3)
cdef size_t i = key>>8 + key
cursor = &(table[i & mask])
perturb = (key)>>3
j=1
while (cursor.key_id1 != key1 or cursor.key_id2 != key2 or cursor.key_id3 != key3):
if cursor.key_id1 == NULL:
return first_freed if (first_freed != NULL) else cursor
if first_freed == NULL and cursor.key_id1 == dummy:
first_freed = cursor
i = 5*i + perturb +1
cursor = &(table[i & mask])
perturb = perturb >> 5
return cursor
cdef int resize(self) except -1:
cdef triple_cell* old_table=self.table
cdef size_t old_mask = self.mask
cdef size_t newsize = 8
cdef size_t minsize = 2*self.used
cdef size_t i
cdef triple_cell* cursor
cdef triple_cell* entry
while newsize < minsize:
newsize = newsize<<1
cdef triple_cell* table = <triple_cell*> PyMem_Malloc((newsize)*sizeof(triple_cell))
if table == NULL:
raise MemoryError()
memset(table,0,(newsize)*sizeof(triple_cell))
self.table = table
self.mask = newsize-1
self.used = 0
self.fill = 0
for i in range(old_mask+1):
entry = &(old_table[i])
if entry.key_id1 != NULL and entry.key_id1 != dummy:
cursor=self.lookup(<PyObject*>(entry.key_id1),<PyObject*>(entry.key_id2),<PyObject*>(entry.key_id3))
assert cursor.key_id1 == NULL
cursor[0]=entry[0]
self.used +=1
self.fill +=1
PyMem_Free(old_table)
return 0
def __init__(self, size=11, data=None, threshold=0.7, weak_values=False):
"""
Create a special dict using triples for keys.
EXAMPLES::
sage: from sage.structure.coerce_dict import TripleDict
sage: L = TripleDict(31)
sage: a = 'a'; b = 'b'; c = 'c'
sage: L[a,b,c] = 1
sage: L[a,b,c]
1
"""
if data is None:
try:
data=size.iteritems()
except AttributeError:
try:
data=iter(size)
except TypeError:
pass
else:
try:
data=data.iteritems()
except AttributeError:
pass
if self.table != NULL:
raise RuntimeError("table already initialized. Called __init__ second time?")
cdef minsize = 8
cdef size_t newsize = 1<<3
while newsize < minsize:
newsize = newsize <<1
self.mask = newsize - 1
cdef triple_cell* table = <triple_cell*> PyMem_Malloc(newsize*sizeof(triple_cell))
if table == NULL:
raise MemoryError()
memset(table,0,newsize*sizeof(triple_cell))
self.table = table
self.used = 0
self.fill = 0
self.eraser = TripleDictEraser(self)
self.weak_values = weak_values
if data:
for k,v in data:
self[k]=v
def __dealloc__(self):
TripleDict_clear(self)
def __len__(self):
"""
The number of items in self.
EXAMPLES::
sage: from sage.structure.coerce_dict import TripleDict
sage: L = TripleDict(37)
sage: a = 'a'; b = 'b'; c = 'c'
sage: L[a,b,c] = 1
sage: L[a,b,c] = -1 # re-assign
sage: L[a,c,b] = 1
sage: L[a,a,None] = None
sage: len(L)
3
"""
return self.used
def __contains__(self, k):
"""
Test if the dictionary contains a given key.
EXAMPLES::
sage: from sage.structure.coerce_dict import TripleDict
sage: L = TripleDict(31)
sage: a = 'a'; b = 'ab'; c = 15
sage: L[a,b,c] = 123
sage: (a,b,c) in L # indirect doctest
True
The keys are compared by identity, not by equality. Hence, we have::
sage: c == 15
True
sage: (a,b,15) in L
False
"""
cdef object k1,k2,k3
try:
k1, k2, k3 = k
except (TypeError,ValueError):
raise KeyError, k
cdef triple_cell* cursor = self.lookup(<PyObject*><void*>k1,<PyObject*><void*>k2,<PyObject*><void*>k3)
if cursor.key_id1 == NULL or cursor.key_id1 == dummy:
return False
r = <object>cursor.key_weakref1
if isinstance(r, fixed_KeyedRef) and PyWeakref_GetObject(r) == Py_None:
return False
r = <object>cursor.key_weakref2
if isinstance(r, fixed_KeyedRef) and PyWeakref_GetObject(r) == Py_None:
return False
r = <object>cursor.key_weakref3
if isinstance(r, fixed_KeyedRef) and PyWeakref_GetObject(r) == Py_None:
return False
if not(self.weak_values):
return True
value = <object>cursor.value
return (not isinstance(value,fixed_KeyedRef)) or PyWeakref_GetObject(value) != Py_None
def __getitem__(self, k):
"""
Get the value corresponding to a key.
EXAMPLES::
sage: from sage.structure.coerce_dict import TripleDict
sage: L = TripleDict(31)
sage: a = 'a'; b = 'b'; c = 'c'
sage: L[a,b,c] = 1
sage: L[a,b,c]
1
"""
cdef object k1,k2,k3
try:
k1, k2, k3 = k
except (TypeError,ValueError):
raise KeyError, k
return self.get(k1, k2, k3)
cdef get(self, object k1, object k2, object k3):
cdef triple_cell* cursor = self.lookup(<PyObject*><void *>k1,<PyObject*><void *>k2,<PyObject*><void *>k3)
if cursor.key_id1 == NULL or cursor.key_id1 == dummy:
raise KeyError, (k1, k2, k3)
r1 = <object>cursor.key_weakref1
r2 = <object>cursor.key_weakref2
r3 = <object>cursor.key_weakref3
if (isinstance(r1, fixed_KeyedRef) and PyWeakref_GetObject(r1) == Py_None) or \
(isinstance(r2, fixed_KeyedRef) and PyWeakref_GetObject(r2) == Py_None) or \
(isinstance(r3, fixed_KeyedRef) and PyWeakref_GetObject(r3) == Py_None):
raise KeyError, (k1, k2, k3)
value = <object>cursor.value
if self.weak_values and isinstance(value,fixed_KeyedRef):
value = <object>PyWeakref_GetObject(value)
if value is None:
raise KeyError, (k1, k2, k3)
return value
def __setitem__(self, k, value):
"""
Set the value corresponding to a key.
EXAMPLES::
sage: from sage.structure.coerce_dict import TripleDict
sage: L = TripleDict(31)
sage: a = 'a'; b = 'b'; c = 'c'
sage: L[a,b,c] = -1
sage: L[a,b,c]
-1
"""
cdef object k1,k2,k3
try:
k1, k2, k3 = k
except (TypeError,ValueError):
raise KeyError, k
self.set(k1, k2, k3, value)
cdef set(self, object k1, object k2, object k3, value):
cdef triple_cell entry
cdef PyObject* old_value = NULL
cdef bint maybe_resize = False
entry.key_id1 = <void*>k1
entry.key_id2 = <void*>k2
entry.key_id3 = <void*>k3
if self.weak_values:
k = (PyCapsule_New(<void *>(k1),NULL,NULL),
PyCapsule_New(<void *>(k2),NULL,NULL),
PyCapsule_New(<void *>(k3),NULL,NULL))
try:
value_store = fixed_KeyedRef(value,self.eraser,k)
entry.value = <PyObject*><void*>value_store
except TypeError:
entry.value = <PyObject*><void*>value
else:
entry.value = <PyObject*><void*>value
Py_XINCREF(entry.value)
cursor = self.lookup(<PyObject*><void*>k1,<PyObject*><void*>k2,<PyObject*><void*>k3)
if cursor.key_id1 == NULL or cursor.key_id1 == dummy:
self.used += 1
if cursor.key_id1 == NULL:
self.fill += 1
maybe_resize = True
if not(self.weak_values):
k = (PyCapsule_New(<void *>(k1),NULL,NULL),
PyCapsule_New(<void *>(k2),NULL,NULL),
PyCapsule_New(<void *>(k3),NULL,NULL))
try:
key_store=fixed_KeyedRef(k1,self.eraser,k)
entry.key_weakref1 = <PyObject*><void*>key_store
except TypeError:
entry.key_weakref1 = <PyObject*><void*>k1
Py_XINCREF(entry.key_weakref1)
try:
key_store=fixed_KeyedRef(k2,self.eraser,k)
entry.key_weakref2 = <PyObject*><void*>key_store
except TypeError:
entry.key_weakref2 = <PyObject*><void*>k2
Py_XINCREF(entry.key_weakref2)
try:
key_store=fixed_KeyedRef(k3,self.eraser,k)
entry.key_weakref3 = <PyObject*><void*>key_store
except TypeError:
entry.key_weakref3 = <PyObject*><void*>k3
Py_XINCREF(entry.key_weakref3)
cursor[0] = entry
if maybe_resize and 3*self.fill > 2*self.mask: self.resize()
else:
old_value = cursor.value
cursor.value = entry.value
Py_XDECREF(old_value)
def __delitem__(self, k):
"""
Delete the value corresponding to a key.
EXAMPLES::
sage: from sage.structure.coerce_dict import TripleDict
sage: L = TripleDict(31)
sage: a = 'a'; b = 'b'; c = 'c'
sage: L[a,b,c] = -1
sage: (a,b,c) in L
True
sage: del L[a,b,c]
sage: len(L)
0
sage: (a,b,c) in L
False
"""
cdef object k1,k2,k3
try:
k1, k2, k3 = k
except (TypeError,ValueError):
raise KeyError, k
cdef triple_cell* cursor = self.lookup(<PyObject *><void *>k1,<PyObject *><void *>k2,<PyObject *><void *>k3)
if cursor.key_id1 == NULL or cursor.key_id1==dummy:
raise KeyError, k
L=extract_triple_cell(cursor)
self.used -= 1
def iteritems(self):
"""
EXAMPLES::
sage: from sage.structure.coerce_dict import TripleDict
sage: L = TripleDict(31)
sage: L[1,2,3] = None
sage: list(L.iteritems())
[((1, 2, 3), None)]
"""
cdef size_t i = 0
while i <= self.mask:
cursor = &(self.table[i])
i += 1
if cursor.key_id1 != NULL and cursor.key_id1 != dummy:
key1 = <object>(cursor.key_weakref1)
key2 = <object>(cursor.key_weakref2)
key3 = <object>(cursor.key_weakref3)
value = <object>(cursor.value)
if isinstance(key1,fixed_KeyedRef):
key1 = <object>PyWeakref_GetObject(key1)
if key1 is None:
print "found defunct key1"
continue
if isinstance(key2,fixed_KeyedRef):
key2 = <object>PyWeakref_GetObject(key2)
if key2 is None:
print "found defunct key2"
continue
if isinstance(key3,fixed_KeyedRef):
key3 = <object>PyWeakref_GetObject(key3)
if key3 is None:
print "found defunct key3"
continue
if self.weak_values and isinstance(value,fixed_KeyedRef):
value = <object>PyWeakref_GetObject(value)
if value is None:
print "found defunct value"
continue
yield ((key1,key2,key3), value)
def __reduce__(self):
"""
Note that we don't expect equality as this class concerns itself with
object identity rather than object equality.
EXAMPLES::
sage: from sage.structure.coerce_dict import TripleDict
sage: L = TripleDict(31)
sage: L[1,2,3] = True
sage: loads(dumps(L)) == L
False
sage: list(loads(dumps(L)).iteritems())
[((1, 2, 3), True)]
"""
return TripleDict, (11, dict(self.iteritems()), 0.7)
cdef int TripleDict_traverse(TripleDict op, visitproc visit, void *arg):
cdef int r
if op.table == NULL:
return 0
table=op.table
cdef size_t i = 0
if (<void*>(op.eraser)):
r=visit(<PyObject*><void*>(op.eraser),arg)
if r: return r
for i in range(op.mask+1):
cursor = &table[i]
if cursor.key_id1 != NULL and cursor.key_id1 != dummy:
r=visit(cursor.key_weakref1,arg)
if r: return r
r=visit(cursor.key_weakref2,arg)
if r: return r
r=visit(cursor.key_weakref3,arg)
if r: return r
r=visit(cursor.value,arg)
if r: return r
return 0
cdef int TripleDict_clear(TripleDict op):
if op.table == NULL:
return 0
tmp=op.eraser
cdef triple_cell* table=op.table
cdef size_t mask=op.mask
op.table=NULL
op.eraser=None
op.mask=0
op.used=0
op.fill=0
del tmp
for i in range(mask+1):
cursor = &(table[i])
if cursor.key_id1 != NULL and cursor.key_id1 != dummy:
cursor.key_id1 = dummy
Py_XDECREF(cursor.key_weakref1)
Py_XDECREF(cursor.key_weakref2)
Py_XDECREF(cursor.key_weakref3)
Py_XDECREF(cursor.value)
PyMem_Free(table)
return 0
(<PyTypeObject *><void *>TripleDict).tp_traverse = &TripleDict_traverse
(<PyTypeObject *><void *>TripleDict).tp_clear = &TripleDict_clear