passagemath-objects 10.6.41__cp312-cp312-win_amd64.whl
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.
- passagemath_objects/__init__.py +3 -0
- passagemath_objects-10.6.41.dist-info/DELVEWHEEL +2 -0
- passagemath_objects-10.6.41.dist-info/METADATA +115 -0
- passagemath_objects-10.6.41.dist-info/RECORD +281 -0
- passagemath_objects-10.6.41.dist-info/WHEEL +5 -0
- passagemath_objects-10.6.41.dist-info/top_level.txt +3 -0
- passagemath_objects.libs/libgmp-10-79b4110c7ea2b760f16cfef97e8a8a34.dll +0 -0
- sage/all__sagemath_objects.py +46 -0
- sage/arith/all__sagemath_objects.py +5 -0
- sage/arith/long.pxd +411 -0
- sage/arith/numerical_approx.cp312-win_amd64.pyd +0 -0
- sage/arith/numerical_approx.pxd +35 -0
- sage/arith/numerical_approx.pyx +75 -0
- sage/arith/power.cp312-win_amd64.pyd +0 -0
- sage/arith/power.pxd +31 -0
- sage/arith/power.pyx +127 -0
- sage/categories/action.cp312-win_amd64.pyd +0 -0
- sage/categories/action.pxd +29 -0
- sage/categories/action.pyx +641 -0
- sage/categories/algebra_functor.py +745 -0
- sage/categories/all__sagemath_objects.py +33 -0
- sage/categories/basic.py +71 -0
- sage/categories/cartesian_product.py +295 -0
- sage/categories/category.py +3401 -0
- sage/categories/category_cy_helper.cp312-win_amd64.pyd +0 -0
- sage/categories/category_cy_helper.pxd +8 -0
- sage/categories/category_cy_helper.pyx +322 -0
- sage/categories/category_singleton.cp312-win_amd64.pyd +0 -0
- sage/categories/category_singleton.pxd +3 -0
- sage/categories/category_singleton.pyx +342 -0
- sage/categories/category_types.py +637 -0
- sage/categories/category_with_axiom.py +2885 -0
- sage/categories/covariant_functorial_construction.py +703 -0
- sage/categories/facade_sets.py +228 -0
- sage/categories/functor.cp312-win_amd64.pyd +0 -0
- sage/categories/functor.pxd +7 -0
- sage/categories/functor.pyx +691 -0
- sage/categories/homset.py +1338 -0
- sage/categories/homsets.py +364 -0
- sage/categories/isomorphic_objects.py +73 -0
- sage/categories/map.cp312-win_amd64.pyd +0 -0
- sage/categories/map.pxd +34 -0
- sage/categories/map.pyx +2112 -0
- sage/categories/morphism.cp312-win_amd64.pyd +0 -0
- sage/categories/morphism.pxd +14 -0
- sage/categories/morphism.pyx +895 -0
- sage/categories/objects.py +167 -0
- sage/categories/primer.py +1696 -0
- sage/categories/pushout.py +4834 -0
- sage/categories/quotients.py +64 -0
- sage/categories/realizations.py +200 -0
- sage/categories/sets_cat.py +3228 -0
- sage/categories/sets_with_partial_maps.py +52 -0
- sage/categories/subobjects.py +64 -0
- sage/categories/subquotients.py +21 -0
- sage/categories/with_realizations.py +311 -0
- sage/cpython/__init__.py +28 -0
- sage/cpython/_py2_random.py +619 -0
- sage/cpython/all.py +3 -0
- sage/cpython/atexit.cp312-win_amd64.pyd +0 -0
- sage/cpython/atexit.pyx +269 -0
- sage/cpython/builtin_types.cp312-win_amd64.pyd +0 -0
- sage/cpython/builtin_types.pyx +7 -0
- sage/cpython/cython_metaclass.cp312-win_amd64.pyd +0 -0
- sage/cpython/cython_metaclass.h +117 -0
- sage/cpython/cython_metaclass.pxd +3 -0
- sage/cpython/cython_metaclass.pyx +130 -0
- sage/cpython/debug.cp312-win_amd64.pyd +0 -0
- sage/cpython/debug.pyx +302 -0
- sage/cpython/dict_del_by_value.cp312-win_amd64.pyd +0 -0
- sage/cpython/dict_del_by_value.pxd +9 -0
- sage/cpython/dict_del_by_value.pyx +191 -0
- sage/cpython/dict_internal.h +245 -0
- sage/cpython/getattr.cp312-win_amd64.pyd +0 -0
- sage/cpython/getattr.pxd +9 -0
- sage/cpython/getattr.pyx +439 -0
- sage/cpython/pycore_long.h +97 -0
- sage/cpython/pycore_long.pxd +10 -0
- sage/cpython/python_debug.h +44 -0
- sage/cpython/python_debug.pxd +47 -0
- sage/cpython/pyx_visit.h +13 -0
- sage/cpython/string.cp312-win_amd64.pyd +0 -0
- sage/cpython/string.pxd +76 -0
- sage/cpython/string.pyx +34 -0
- sage/cpython/string_impl.h +60 -0
- sage/cpython/type.cp312-win_amd64.pyd +0 -0
- sage/cpython/type.pxd +2 -0
- sage/cpython/type.pyx +40 -0
- sage/cpython/wrapperdescr.pxd +67 -0
- sage/ext/all__sagemath_objects.py +3 -0
- sage/ext/ccobject.h +64 -0
- sage/ext/cplusplus.pxd +17 -0
- sage/ext/mod_int.h +30 -0
- sage/ext/mod_int.pxd +24 -0
- sage/ext/stdsage.pxd +39 -0
- sage/groups/all__sagemath_objects.py +1 -0
- sage/groups/group.cp312-win_amd64.pyd +0 -0
- sage/groups/group.pxd +14 -0
- sage/groups/group.pyx +322 -0
- sage/groups/old.cp312-win_amd64.pyd +0 -0
- sage/groups/old.pxd +14 -0
- sage/groups/old.pyx +219 -0
- sage/libs/all__sagemath_objects.py +3 -0
- sage/libs/gmp/__init__.py +1 -0
- sage/libs/gmp/all.pxd +6 -0
- sage/libs/gmp/binop.pxd +23 -0
- sage/libs/gmp/misc.pxd +8 -0
- sage/libs/gmp/mpf.pxd +88 -0
- sage/libs/gmp/mpn.pxd +57 -0
- sage/libs/gmp/mpq.pxd +57 -0
- sage/libs/gmp/mpz.pxd +202 -0
- sage/libs/gmp/pylong.cp312-win_amd64.pyd +0 -0
- sage/libs/gmp/pylong.pxd +12 -0
- sage/libs/gmp/pylong.pyx +150 -0
- sage/libs/gmp/random.pxd +25 -0
- sage/libs/gmp/randomize.pxd +59 -0
- sage/libs/gmp/types.pxd +53 -0
- sage/libs/gmpxx.pxd +19 -0
- sage/misc/abstract_method.py +276 -0
- sage/misc/all__sagemath_objects.py +43 -0
- sage/misc/bindable_class.py +253 -0
- sage/misc/c3_controlled.cp312-win_amd64.pyd +0 -0
- sage/misc/c3_controlled.pxd +2 -0
- sage/misc/c3_controlled.pyx +1402 -0
- sage/misc/cachefunc.cp312-win_amd64.pyd +0 -0
- sage/misc/cachefunc.pxd +43 -0
- sage/misc/cachefunc.pyx +3781 -0
- sage/misc/call.py +188 -0
- sage/misc/classcall_metaclass.cp312-win_amd64.pyd +0 -0
- sage/misc/classcall_metaclass.pxd +14 -0
- sage/misc/classcall_metaclass.pyx +599 -0
- sage/misc/constant_function.cp312-win_amd64.pyd +0 -0
- sage/misc/constant_function.pyx +130 -0
- sage/misc/decorators.py +747 -0
- sage/misc/fast_methods.cp312-win_amd64.pyd +0 -0
- sage/misc/fast_methods.pxd +20 -0
- sage/misc/fast_methods.pyx +351 -0
- sage/misc/flatten.py +90 -0
- sage/misc/fpickle.cp312-win_amd64.pyd +0 -0
- sage/misc/fpickle.pyx +177 -0
- sage/misc/function_mangling.cp312-win_amd64.pyd +0 -0
- sage/misc/function_mangling.pxd +11 -0
- sage/misc/function_mangling.pyx +308 -0
- sage/misc/inherit_comparison.cp312-win_amd64.pyd +0 -0
- sage/misc/inherit_comparison.pxd +5 -0
- sage/misc/inherit_comparison.pyx +105 -0
- sage/misc/instancedoc.cp312-win_amd64.pyd +0 -0
- sage/misc/instancedoc.pyx +331 -0
- sage/misc/lazy_attribute.cp312-win_amd64.pyd +0 -0
- sage/misc/lazy_attribute.pyx +607 -0
- sage/misc/lazy_format.py +135 -0
- sage/misc/lazy_import.cp312-win_amd64.pyd +0 -0
- sage/misc/lazy_import.pyx +1299 -0
- sage/misc/lazy_import_cache.py +36 -0
- sage/misc/lazy_list.cp312-win_amd64.pyd +0 -0
- sage/misc/lazy_list.pxd +19 -0
- sage/misc/lazy_list.pyx +1187 -0
- sage/misc/lazy_string.cp312-win_amd64.pyd +0 -0
- sage/misc/lazy_string.pxd +7 -0
- sage/misc/lazy_string.pyx +546 -0
- sage/misc/misc.py +1066 -0
- sage/misc/misc_c.cp312-win_amd64.pyd +0 -0
- sage/misc/misc_c.pxd +3 -0
- sage/misc/misc_c.pyx +766 -0
- sage/misc/namespace_package.py +37 -0
- sage/misc/nested_class.cp312-win_amd64.pyd +0 -0
- sage/misc/nested_class.pxd +3 -0
- sage/misc/nested_class.pyx +394 -0
- sage/misc/persist.cp312-win_amd64.pyd +0 -0
- sage/misc/persist.pyx +1251 -0
- sage/misc/prandom.py +418 -0
- sage/misc/randstate.cp312-win_amd64.pyd +0 -0
- sage/misc/randstate.pxd +30 -0
- sage/misc/randstate.pyx +1059 -0
- sage/misc/repr.py +203 -0
- sage/misc/reset.cp312-win_amd64.pyd +0 -0
- sage/misc/reset.pyx +196 -0
- sage/misc/sage_ostools.cp312-win_amd64.pyd +0 -0
- sage/misc/sage_ostools.pyx +323 -0
- sage/misc/sage_timeit.py +276 -0
- sage/misc/sage_timeit_class.cp312-win_amd64.pyd +0 -0
- sage/misc/sage_timeit_class.pyx +120 -0
- sage/misc/sage_unittest.py +637 -0
- sage/misc/sageinspect.py +2768 -0
- sage/misc/session.cp312-win_amd64.pyd +0 -0
- sage/misc/session.pyx +392 -0
- sage/misc/superseded.py +557 -0
- sage/misc/test_nested_class.py +228 -0
- sage/misc/timing.py +264 -0
- sage/misc/unknown.py +222 -0
- sage/misc/verbose.py +253 -0
- sage/misc/weak_dict.cp312-win_amd64.pyd +0 -0
- sage/misc/weak_dict.pxd +15 -0
- sage/misc/weak_dict.pyx +1231 -0
- sage/modules/all__sagemath_objects.py +1 -0
- sage/modules/module.cp312-win_amd64.pyd +0 -0
- sage/modules/module.pxd +5 -0
- sage/modules/module.pyx +329 -0
- sage/rings/all__sagemath_objects.py +3 -0
- sage/rings/integer_fake.h +22 -0
- sage/rings/integer_fake.pxd +55 -0
- sage/sets/all__sagemath_objects.py +3 -0
- sage/sets/pythonclass.cp312-win_amd64.pyd +0 -0
- sage/sets/pythonclass.pxd +9 -0
- sage/sets/pythonclass.pyx +247 -0
- sage/structure/__init__.py +13 -0
- sage/structure/all.py +30 -0
- sage/structure/category_object.cp312-win_amd64.pyd +0 -0
- sage/structure/category_object.pxd +28 -0
- sage/structure/category_object.pyx +1087 -0
- sage/structure/coerce.cp312-win_amd64.pyd +0 -0
- sage/structure/coerce.pxd +44 -0
- sage/structure/coerce.pyx +2107 -0
- sage/structure/coerce_actions.cp312-win_amd64.pyd +0 -0
- sage/structure/coerce_actions.pxd +27 -0
- sage/structure/coerce_actions.pyx +988 -0
- sage/structure/coerce_dict.cp312-win_amd64.pyd +0 -0
- sage/structure/coerce_dict.pxd +51 -0
- sage/structure/coerce_dict.pyx +1557 -0
- sage/structure/coerce_exceptions.py +23 -0
- sage/structure/coerce_maps.cp312-win_amd64.pyd +0 -0
- sage/structure/coerce_maps.pxd +28 -0
- sage/structure/coerce_maps.pyx +718 -0
- sage/structure/debug_options.cp312-win_amd64.pyd +0 -0
- sage/structure/debug_options.pxd +6 -0
- sage/structure/debug_options.pyx +54 -0
- sage/structure/dynamic_class.py +541 -0
- sage/structure/element.cp312-win_amd64.pyd +0 -0
- sage/structure/element.pxd +272 -0
- sage/structure/element.pyx +4772 -0
- sage/structure/element_wrapper.cp312-win_amd64.pyd +0 -0
- sage/structure/element_wrapper.pxd +12 -0
- sage/structure/element_wrapper.pyx +582 -0
- sage/structure/factorization.py +1422 -0
- sage/structure/factorization_integer.py +105 -0
- sage/structure/factory.cp312-win_amd64.pyd +0 -0
- sage/structure/factory.pyx +786 -0
- sage/structure/formal_sum.py +489 -0
- sage/structure/gens_py.py +73 -0
- sage/structure/global_options.py +1743 -0
- sage/structure/indexed_generators.py +863 -0
- sage/structure/list_clone.cp312-win_amd64.pyd +0 -0
- sage/structure/list_clone.pxd +65 -0
- sage/structure/list_clone.pyx +1867 -0
- sage/structure/list_clone_demo.cp312-win_amd64.pyd +0 -0
- sage/structure/list_clone_demo.pyx +248 -0
- sage/structure/list_clone_timings.py +179 -0
- sage/structure/list_clone_timings_cy.cp312-win_amd64.pyd +0 -0
- sage/structure/list_clone_timings_cy.pyx +86 -0
- sage/structure/mutability.cp312-win_amd64.pyd +0 -0
- sage/structure/mutability.pxd +21 -0
- sage/structure/mutability.pyx +348 -0
- sage/structure/nonexact.py +69 -0
- sage/structure/parent.cp312-win_amd64.pyd +0 -0
- sage/structure/parent.pxd +112 -0
- sage/structure/parent.pyx +3093 -0
- sage/structure/parent_base.cp312-win_amd64.pyd +0 -0
- sage/structure/parent_base.pxd +13 -0
- sage/structure/parent_base.pyx +44 -0
- sage/structure/parent_gens.cp312-win_amd64.pyd +0 -0
- sage/structure/parent_gens.pxd +22 -0
- sage/structure/parent_gens.pyx +377 -0
- sage/structure/parent_old.cp312-win_amd64.pyd +0 -0
- sage/structure/parent_old.pxd +25 -0
- sage/structure/parent_old.pyx +294 -0
- sage/structure/proof/__init__.py +1 -0
- sage/structure/proof/all.py +243 -0
- sage/structure/proof/proof.py +300 -0
- sage/structure/richcmp.cp312-win_amd64.pyd +0 -0
- sage/structure/richcmp.pxd +213 -0
- sage/structure/richcmp.pyx +495 -0
- sage/structure/sage_object.cp312-win_amd64.pyd +0 -0
- sage/structure/sage_object.pxd +3 -0
- sage/structure/sage_object.pyx +988 -0
- sage/structure/sage_object_test.py +19 -0
- sage/structure/sequence.py +937 -0
- sage/structure/set_factories.py +1178 -0
- sage/structure/set_factories_example.py +527 -0
- sage/structure/support_view.py +179 -0
- sage/structure/test_factory.py +56 -0
- sage/structure/unique_representation.py +1359 -0
|
@@ -0,0 +1,1557 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-objects
|
|
2
|
+
"""
|
|
3
|
+
Containers for storing coercion data
|
|
4
|
+
|
|
5
|
+
This module provides :class:`TripleDict` and :class:`MonoDict`. These are
|
|
6
|
+
structures similar to :class:`~weakref.WeakKeyDictionary` in Python's weakref
|
|
7
|
+
module, and are optimized for lookup speed. The keys for :class:`TripleDict`
|
|
8
|
+
consist of triples (k1,k2,k3) and are looked up by identity rather than
|
|
9
|
+
equality. The keys are stored by weakrefs if possible. If any one of the
|
|
10
|
+
components k1, k2, k3 gets garbage collected, then the entry is removed from
|
|
11
|
+
the :class:`TripleDict`.
|
|
12
|
+
|
|
13
|
+
Key components that do not allow for weakrefs are stored via a normal
|
|
14
|
+
refcounted reference. That means that any entry stored using a triple
|
|
15
|
+
(k1,k2,k3) so that none of the k1,k2,k3 allows a weak reference behaves
|
|
16
|
+
as an entry in a normal dictionary: Its existence in :class:`TripleDict`
|
|
17
|
+
prevents it from being garbage collected.
|
|
18
|
+
|
|
19
|
+
That container currently is used to store coercion and conversion maps between
|
|
20
|
+
two parents (:issue:`715`) and to store homsets of pairs of objects of a
|
|
21
|
+
category (:issue:`11521`). In both cases, it is essential that the parent
|
|
22
|
+
structures remain garbage collectable, it is essential that the data access is
|
|
23
|
+
faster than with a usual :class:`~weakref.WeakKeyDictionary`, and we enforce
|
|
24
|
+
the "unique parent condition" in Sage (parent structures should be identical
|
|
25
|
+
if they are equal).
|
|
26
|
+
|
|
27
|
+
:class:`MonoDict` behaves similarly, but it takes a single item as a key. It
|
|
28
|
+
is used for caching the parents which allow a coercion map into a fixed other
|
|
29
|
+
parent (:issue:`12313`).
|
|
30
|
+
|
|
31
|
+
By :issue:`14159`, :class:`MonoDict` and :class:`TripleDict` can be optionally
|
|
32
|
+
used with weak references on the values.
|
|
33
|
+
|
|
34
|
+
Note that this kind of dictionary is also used for caching actions and
|
|
35
|
+
coerce maps. In previous versions of Sage, the cache was by strong
|
|
36
|
+
references and resulted in a memory leak in the following example.
|
|
37
|
+
However, this leak was fixed by :issue:`715`, using weak references::
|
|
38
|
+
|
|
39
|
+
sage: # needs sage.combinat sage.modules sage.rings.finite_rings
|
|
40
|
+
sage: K.<t> = GF(2^55)
|
|
41
|
+
sage: for i in range(20):
|
|
42
|
+
....: a = K.random_element()
|
|
43
|
+
....: E = EllipticCurve(j=a)
|
|
44
|
+
....: P = E.random_point()
|
|
45
|
+
....: Q = 2*P
|
|
46
|
+
sage: L = [Partitions(n) for n in range(200)] # purge strong cache in CachedRepresentation
|
|
47
|
+
sage: import gc
|
|
48
|
+
sage: n = gc.collect()
|
|
49
|
+
sage: from sage.schemes.elliptic_curves.ell_finite_field import EllipticCurve_finite_field
|
|
50
|
+
sage: LE = [x for x in gc.get_objects() if isinstance(x, EllipticCurve_finite_field)]
|
|
51
|
+
sage: len(LE)
|
|
52
|
+
1
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
#*****************************************************************************
|
|
56
|
+
# Copyright (C) 2007 Robert Bradshaw <robertwb@math.washington.edu>
|
|
57
|
+
# 2012 Simon King <simon.king@uni-jena.de>
|
|
58
|
+
# 2013 Nils Bruin <nbruin@sfu.ca>
|
|
59
|
+
#
|
|
60
|
+
# This program is free software: you can redistribute it and/or modify
|
|
61
|
+
# it under the terms of the GNU General Public License as published by
|
|
62
|
+
# the Free Software Foundation, either version 2 of the License, or
|
|
63
|
+
# (at your option) any later version.
|
|
64
|
+
# http://www.gnu.org/licenses/
|
|
65
|
+
#*****************************************************************************
|
|
66
|
+
|
|
67
|
+
cimport cython
|
|
68
|
+
from cpython.object cimport *
|
|
69
|
+
from cpython.ref cimport Py_XINCREF, Py_XDECREF, Py_CLEAR
|
|
70
|
+
from cpython.tuple cimport PyTuple_New
|
|
71
|
+
from cpython.weakref cimport PyWeakref_GetObject, PyWeakref_GET_OBJECT
|
|
72
|
+
from cysignals.memory cimport check_calloc, sig_free
|
|
73
|
+
|
|
74
|
+
cdef extern from "Python.h":
|
|
75
|
+
void PyTuple_SET_ITEM(object tuple, Py_ssize_t index, PyObject* item)
|
|
76
|
+
|
|
77
|
+
cdef extern from "../cpython/pyx_visit.h":
|
|
78
|
+
void Py_VISIT3(PyObject*, visitproc, void*)
|
|
79
|
+
|
|
80
|
+
cdef type KeyedRef, ref
|
|
81
|
+
from weakref import KeyedRef, ref
|
|
82
|
+
|
|
83
|
+
cdef inline bint is_dead_keyedref(x) noexcept:
|
|
84
|
+
"""
|
|
85
|
+
Check whether ``x`` is a ``KeyedRef`` which is dead.
|
|
86
|
+
"""
|
|
87
|
+
if type(x) is not KeyedRef:
|
|
88
|
+
return False
|
|
89
|
+
return PyWeakref_GET_OBJECT(x) is <PyObject*>None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Unique sentinel to indicate a deleted cell
|
|
93
|
+
cdef object dummy = object()
|
|
94
|
+
cdef PyObject* deleted_key = <PyObject*>dummy
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
cdef inline bint valid(PyObject* obj) noexcept:
|
|
98
|
+
"""
|
|
99
|
+
Check whether ``obj`` points to a valid object
|
|
100
|
+
"""
|
|
101
|
+
return obj is not NULL and obj is not deleted_key
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@cython.freelist(256)
|
|
105
|
+
cdef class ObjectWrapper:
|
|
106
|
+
"""
|
|
107
|
+
A simple fast wrapper around a Python object. This is like a
|
|
108
|
+
1-element tuple except that it does not keep a reference to the
|
|
109
|
+
wrapped object.
|
|
110
|
+
"""
|
|
111
|
+
cdef PyObject* obj
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
cdef inline ObjectWrapper wrap(obj):
|
|
115
|
+
"""
|
|
116
|
+
Wrap a given Python object in an :class:`ObjectWrapper`.
|
|
117
|
+
"""
|
|
118
|
+
cdef ObjectWrapper w = <ObjectWrapper>(ObjectWrapper.__new__(ObjectWrapper))
|
|
119
|
+
w.obj = <PyObject*>obj
|
|
120
|
+
return w
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
cdef inline PyObject* unwrap(w) except? NULL:
|
|
124
|
+
"""
|
|
125
|
+
Return the object wrapped by an :class:`ObjectWrapper`.
|
|
126
|
+
"""
|
|
127
|
+
return (<ObjectWrapper?>w).obj
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
cdef extract_mono_cell(mono_cell* cell):
|
|
131
|
+
"""
|
|
132
|
+
Take the refcounted components from a mono_cell, put them in a
|
|
133
|
+
tuple and return it. The mono_cell itself is marked as "freed".
|
|
134
|
+
The refcounts originally accounting for the presence in the
|
|
135
|
+
mono_cell now account for the presence in the returned tuple,
|
|
136
|
+
which steals those references.
|
|
137
|
+
|
|
138
|
+
The returned result is only used to throw away: an advantage is
|
|
139
|
+
that the containing tuple participates in CPython's trashcan,
|
|
140
|
+
which prevents stack overflow on large dereffing cascades.
|
|
141
|
+
|
|
142
|
+
A slight disadvantage is that this routine needs to allocate a
|
|
143
|
+
tuple (mainly just to be thrown away)
|
|
144
|
+
"""
|
|
145
|
+
assert valid(cell.key_id)
|
|
146
|
+
t = PyTuple_New(2)
|
|
147
|
+
PyTuple_SET_ITEM(t, 0, cell.key_weakref)
|
|
148
|
+
PyTuple_SET_ITEM(t, 1, cell.value)
|
|
149
|
+
cell.key_id = deleted_key
|
|
150
|
+
cell.key_weakref = NULL
|
|
151
|
+
cell.value = NULL
|
|
152
|
+
return t
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
cdef extract_triple_cell(triple_cell* cell):
|
|
156
|
+
# See extract_mono_cell for documentation
|
|
157
|
+
assert valid(cell.key_id1)
|
|
158
|
+
t = PyTuple_New(4)
|
|
159
|
+
PyTuple_SET_ITEM(t, 0, cell.key_weakref1)
|
|
160
|
+
PyTuple_SET_ITEM(t, 1, cell.key_weakref2)
|
|
161
|
+
PyTuple_SET_ITEM(t, 2, cell.key_weakref3)
|
|
162
|
+
PyTuple_SET_ITEM(t, 3, cell.value)
|
|
163
|
+
cell.key_id1 = deleted_key
|
|
164
|
+
cell.key_id2 = NULL
|
|
165
|
+
cell.key_id3 = NULL
|
|
166
|
+
cell.key_weakref1 = NULL
|
|
167
|
+
cell.key_weakref2 = NULL
|
|
168
|
+
cell.key_weakref3 = NULL
|
|
169
|
+
cell.value = NULL
|
|
170
|
+
return t
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
cdef class MonoDictEraser:
|
|
174
|
+
"""
|
|
175
|
+
Erase items from a :class:`MonoDict` when a weak reference becomes
|
|
176
|
+
invalid.
|
|
177
|
+
|
|
178
|
+
This is of internal use only. Instances of this class will be passed as a
|
|
179
|
+
callback function when creating a weak reference.
|
|
180
|
+
|
|
181
|
+
EXAMPLES::
|
|
182
|
+
|
|
183
|
+
sage: from sage.structure.coerce_dict import MonoDict
|
|
184
|
+
sage: class A: pass
|
|
185
|
+
sage: a = A()
|
|
186
|
+
sage: M = MonoDict()
|
|
187
|
+
sage: M[a] = 1
|
|
188
|
+
sage: len(M)
|
|
189
|
+
1
|
|
190
|
+
sage: del a
|
|
191
|
+
sage: import gc
|
|
192
|
+
sage: n = gc.collect()
|
|
193
|
+
sage: len(M) # indirect doctest
|
|
194
|
+
0
|
|
195
|
+
|
|
196
|
+
AUTHOR:
|
|
197
|
+
|
|
198
|
+
- Simon King (2012-01)
|
|
199
|
+
- Nils Bruin (2013-11)
|
|
200
|
+
"""
|
|
201
|
+
cdef D
|
|
202
|
+
|
|
203
|
+
def __init__(self, D):
|
|
204
|
+
"""
|
|
205
|
+
INPUT:
|
|
206
|
+
|
|
207
|
+
- ``D`` -- a :class:`MonoDict`
|
|
208
|
+
|
|
209
|
+
EXAMPLES::
|
|
210
|
+
|
|
211
|
+
sage: k = set([1])
|
|
212
|
+
sage: D = sage.structure.coerce_dict.MonoDict([(k,1)])
|
|
213
|
+
sage: len(D)
|
|
214
|
+
1
|
|
215
|
+
sage: del k
|
|
216
|
+
sage: len(D) # indirect doctest
|
|
217
|
+
0
|
|
218
|
+
"""
|
|
219
|
+
self.D = ref(D)
|
|
220
|
+
|
|
221
|
+
def __call__(self, r):
|
|
222
|
+
"""
|
|
223
|
+
INPUT:
|
|
224
|
+
|
|
225
|
+
- ``r`` -- a weak reference with key
|
|
226
|
+
|
|
227
|
+
For internal use only.
|
|
228
|
+
|
|
229
|
+
EXAMPLES::
|
|
230
|
+
|
|
231
|
+
sage: k = set([1])
|
|
232
|
+
sage: D = sage.structure.coerce_dict.MonoDict([(k,1)])
|
|
233
|
+
sage: len(D)
|
|
234
|
+
1
|
|
235
|
+
sage: del k
|
|
236
|
+
sage: len(D) # indirect doctest
|
|
237
|
+
0
|
|
238
|
+
"""
|
|
239
|
+
cdef MonoDict md = <MonoDict>PyWeakref_GetObject(self.D)
|
|
240
|
+
if md is None or not md.mask:
|
|
241
|
+
return
|
|
242
|
+
cdef mono_cell* cursor = md.lookup(unwrap(r.key))
|
|
243
|
+
cdef PyObject* r_ = <PyObject*>r
|
|
244
|
+
if valid(cursor.key_id):
|
|
245
|
+
if cursor.key_weakref is r_ or cursor.value is r_:
|
|
246
|
+
L = extract_mono_cell(cursor)
|
|
247
|
+
md.used -= 1
|
|
248
|
+
else:
|
|
249
|
+
raise AssertionError("MonoDictEraser: key match but no weakref match")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
cdef class MonoDict:
|
|
253
|
+
"""
|
|
254
|
+
This is a hashtable specifically designed for (read) speed in
|
|
255
|
+
the coercion model.
|
|
256
|
+
|
|
257
|
+
It differs from a python WeakKeyDictionary in the following important ways:
|
|
258
|
+
|
|
259
|
+
- Comparison is done using the 'is' rather than '==' operator.
|
|
260
|
+
- Only weak references to the keys are stored if at all possible.
|
|
261
|
+
Keys that do not allow for weak references are stored with a normal
|
|
262
|
+
refcounted reference.
|
|
263
|
+
- The callback of the weak references is safe against recursion, see below.
|
|
264
|
+
|
|
265
|
+
There are special cdef set/get methods for faster access.
|
|
266
|
+
It is bare-bones in the sense that not all dictionary methods are
|
|
267
|
+
implemented.
|
|
268
|
+
|
|
269
|
+
IMPLEMENTATION:
|
|
270
|
+
|
|
271
|
+
It is implemented as a hash table with open addressing, similar to python's
|
|
272
|
+
dict.
|
|
273
|
+
|
|
274
|
+
INPUT:
|
|
275
|
+
|
|
276
|
+
- ``data`` -- (optional) iterable defining initial data, as dict or
|
|
277
|
+
iterable of (key, value) pairs
|
|
278
|
+
|
|
279
|
+
- ``weak_values`` -- boolean (default: ``False``); if it is
|
|
280
|
+
``True``, weak references to the values in this dictionary will be used,
|
|
281
|
+
when possible
|
|
282
|
+
|
|
283
|
+
EXAMPLES::
|
|
284
|
+
|
|
285
|
+
sage: from sage.structure.coerce_dict import MonoDict
|
|
286
|
+
sage: L = MonoDict()
|
|
287
|
+
sage: a = 'a'; b = 'ab'; c = '-15'
|
|
288
|
+
sage: L[a] = 1
|
|
289
|
+
sage: L[b] = 2
|
|
290
|
+
sage: L[c] = 3
|
|
291
|
+
|
|
292
|
+
The key is expected to be a unique object. Hence, the item stored for ``c``
|
|
293
|
+
cannot be obtained by providing another equal string::
|
|
294
|
+
|
|
295
|
+
sage: L[a]
|
|
296
|
+
1
|
|
297
|
+
sage: L[b]
|
|
298
|
+
2
|
|
299
|
+
sage: L[c]
|
|
300
|
+
3
|
|
301
|
+
sage: L['-15']
|
|
302
|
+
Traceback (most recent call last):
|
|
303
|
+
...
|
|
304
|
+
KeyError: '-15'
|
|
305
|
+
|
|
306
|
+
Not all features of Python dictionaries are available, but iteration over
|
|
307
|
+
the dictionary items is possible::
|
|
308
|
+
|
|
309
|
+
sage: sorted(L.items())
|
|
310
|
+
[('-15', 3), ('a', 1), ('ab', 2)]
|
|
311
|
+
sage: del L[c]
|
|
312
|
+
sage: sorted(L.items())
|
|
313
|
+
[('a', 1), ('ab', 2)]
|
|
314
|
+
sage: len(L)
|
|
315
|
+
2
|
|
316
|
+
sage: for i in range(1000):
|
|
317
|
+
....: L[i] = i
|
|
318
|
+
sage: len(L)
|
|
319
|
+
1002
|
|
320
|
+
sage: L['a']
|
|
321
|
+
1
|
|
322
|
+
sage: L['c']
|
|
323
|
+
Traceback (most recent call last):
|
|
324
|
+
...
|
|
325
|
+
KeyError: 'c'
|
|
326
|
+
|
|
327
|
+
TESTS:
|
|
328
|
+
|
|
329
|
+
Here, we demonstrate the use of weak values::
|
|
330
|
+
|
|
331
|
+
sage: M = MonoDict()
|
|
332
|
+
sage: MW = MonoDict(weak_values=True)
|
|
333
|
+
sage: class Foo: pass
|
|
334
|
+
sage: a = Foo()
|
|
335
|
+
sage: b = Foo()
|
|
336
|
+
sage: k = 1
|
|
337
|
+
sage: M[k] = a
|
|
338
|
+
sage: MW[k] = b
|
|
339
|
+
sage: M[k] is a
|
|
340
|
+
True
|
|
341
|
+
sage: MW[k] is b
|
|
342
|
+
True
|
|
343
|
+
sage: k in M
|
|
344
|
+
True
|
|
345
|
+
sage: k in MW
|
|
346
|
+
True
|
|
347
|
+
|
|
348
|
+
While ``M`` uses a strong reference to ``a``, ``MW`` uses a *weak*
|
|
349
|
+
reference to ``b``, and after deleting ``b``, the corresponding item of
|
|
350
|
+
``MW`` will be removed during the next garbage collection::
|
|
351
|
+
|
|
352
|
+
sage: import gc
|
|
353
|
+
sage: del a,b
|
|
354
|
+
sage: _ = gc.collect()
|
|
355
|
+
sage: k in M
|
|
356
|
+
True
|
|
357
|
+
sage: k in MW
|
|
358
|
+
False
|
|
359
|
+
sage: len(MW)
|
|
360
|
+
0
|
|
361
|
+
sage: len(M)
|
|
362
|
+
1
|
|
363
|
+
|
|
364
|
+
Note that ``MW`` also accepts values that do not allow for weak references::
|
|
365
|
+
|
|
366
|
+
sage: MW[k] = int(5)
|
|
367
|
+
sage: MW[k]
|
|
368
|
+
5
|
|
369
|
+
|
|
370
|
+
The following demonstrates that :class:`MonoDict` is safer than
|
|
371
|
+
:class:`~weakref.WeakKeyDictionary` against recursions created by nested
|
|
372
|
+
callbacks; compare :issue:`15069` (the mechanism used now is different, though)::
|
|
373
|
+
|
|
374
|
+
sage: M = MonoDict()
|
|
375
|
+
sage: class A: pass
|
|
376
|
+
sage: a = A()
|
|
377
|
+
sage: prev = a
|
|
378
|
+
sage: for i in range(1000):
|
|
379
|
+
....: newA = A()
|
|
380
|
+
....: M[prev] = newA
|
|
381
|
+
....: prev = newA
|
|
382
|
+
sage: len(M)
|
|
383
|
+
1000
|
|
384
|
+
sage: del a
|
|
385
|
+
sage: len(M)
|
|
386
|
+
0
|
|
387
|
+
|
|
388
|
+
The corresponding example with a Python :class:`weakref.WeakKeyDictionary`
|
|
389
|
+
would result in a too deep recursion during deletion of the dictionary
|
|
390
|
+
items::
|
|
391
|
+
|
|
392
|
+
sage: import weakref
|
|
393
|
+
sage: M = weakref.WeakKeyDictionary()
|
|
394
|
+
sage: a = A()
|
|
395
|
+
sage: prev = a
|
|
396
|
+
sage: for i in range(1000):
|
|
397
|
+
....: newA = A()
|
|
398
|
+
....: M[prev] = newA
|
|
399
|
+
....: prev = newA
|
|
400
|
+
sage: len(M)
|
|
401
|
+
1000
|
|
402
|
+
|
|
403
|
+
Check that also in the presence of circular references, :class:`MonoDict`
|
|
404
|
+
gets properly collected::
|
|
405
|
+
|
|
406
|
+
sage: import gc
|
|
407
|
+
sage: def count_type(T):
|
|
408
|
+
....: return len([c for c in gc.get_objects() if isinstance(c,T)])
|
|
409
|
+
sage: gc.freeze() # so that gc.collect() only deals with our trash
|
|
410
|
+
sage: N = count_type(MonoDict)
|
|
411
|
+
sage: for i in range(100):
|
|
412
|
+
....: V = [MonoDict({"id":j+100*i}) for j in range(100)]
|
|
413
|
+
....: n = len(V)
|
|
414
|
+
....: for i in range(n): V[i][V[(i+1)%n]] = (i+1)%n
|
|
415
|
+
....: del V
|
|
416
|
+
....: _ = gc.collect()
|
|
417
|
+
....: assert count_type(MonoDict) == N
|
|
418
|
+
sage: count_type(MonoDict) == N
|
|
419
|
+
True
|
|
420
|
+
sage: gc.unfreeze()
|
|
421
|
+
|
|
422
|
+
AUTHORS:
|
|
423
|
+
|
|
424
|
+
- Simon King (2012-01)
|
|
425
|
+
- Nils Bruin (2012-08)
|
|
426
|
+
- Simon King (2013-02)
|
|
427
|
+
- Nils Bruin (2013-11)
|
|
428
|
+
"""
|
|
429
|
+
cdef mono_cell* lookup(self, PyObject* key) noexcept:
|
|
430
|
+
"""
|
|
431
|
+
Return a pointer to where ``key`` should be stored in this
|
|
432
|
+
:class:`MonoDict`.
|
|
433
|
+
|
|
434
|
+
This routine is used for all cases where a (potential) spot for
|
|
435
|
+
a key is looked up. The returned value is a pointer into the dictionary
|
|
436
|
+
store that either contains an entry with the requested key or a free spot
|
|
437
|
+
where an entry for that key should go.
|
|
438
|
+
"""
|
|
439
|
+
assert valid(key)
|
|
440
|
+
|
|
441
|
+
cdef size_t mask = self.mask
|
|
442
|
+
cdef mono_cell* table = self.table
|
|
443
|
+
cdef mono_cell* first_deleted = NULL
|
|
444
|
+
|
|
445
|
+
# Use the memory location of the key as starting point for our
|
|
446
|
+
# hash.
|
|
447
|
+
cdef size_t h = <size_t>key
|
|
448
|
+
|
|
449
|
+
# The size of a Python object is at least 2 * sizeof(size_t).
|
|
450
|
+
# Therefore, we don't lose any information by dividing by that.
|
|
451
|
+
# Instead, the lower order bits become more interesting.
|
|
452
|
+
h //= 2 * sizeof(size_t)
|
|
453
|
+
|
|
454
|
+
# Bring some higher-order bits in with this permutation.
|
|
455
|
+
cdef size_t i = (h >> 8) ^ h
|
|
456
|
+
|
|
457
|
+
cdef size_t perturb = h
|
|
458
|
+
|
|
459
|
+
# The probing algorithm is heavily inspired by Python dicts.
|
|
460
|
+
# There is always at least one NULL entry in the store, and the
|
|
461
|
+
# probe sequence eventually covers the entire store (see Theorem
|
|
462
|
+
# below), so the loop below does terminate. Since this loop does
|
|
463
|
+
# not change any refcounts, we know that table will not change
|
|
464
|
+
# during iteration.
|
|
465
|
+
|
|
466
|
+
# Theorem: when iterating the function i -> 5*i + 1, every
|
|
467
|
+
# element of Z/(2^n Z) is reached.
|
|
468
|
+
# Proof: define f(x) = 4*x + 1. Then f(5*i + 1) = 5*f(i).
|
|
469
|
+
# Therefore, the iteration is really a transformation of
|
|
470
|
+
# i -> 5*i on the group of 1 mod 4 elements of (Z/2^(n+2) Z).
|
|
471
|
+
# It is a well known fact that this is a cyclic group generated
|
|
472
|
+
# by 5 (or any element which is 5 mod 8).
|
|
473
|
+
cdef mono_cell* cursor
|
|
474
|
+
while True:
|
|
475
|
+
cursor = &(table[i & mask])
|
|
476
|
+
perturb >>= 5
|
|
477
|
+
if cursor.key_id is key:
|
|
478
|
+
return cursor
|
|
479
|
+
elif cursor.key_id is NULL:
|
|
480
|
+
return first_deleted or cursor
|
|
481
|
+
elif cursor.key_id is deleted_key:
|
|
482
|
+
if first_deleted is NULL:
|
|
483
|
+
first_deleted = cursor
|
|
484
|
+
i = (5*i + 1) + perturb
|
|
485
|
+
|
|
486
|
+
cdef int resize(self) except -1:
|
|
487
|
+
"""
|
|
488
|
+
Resize dictionary. That can also mean shrink! Size is always a power of 2.
|
|
489
|
+
"""
|
|
490
|
+
cdef mono_cell* old_table = self.table
|
|
491
|
+
cdef size_t old_mask = self.mask
|
|
492
|
+
cdef size_t newsize = 8
|
|
493
|
+
cdef size_t minsize = 2 * self.used
|
|
494
|
+
cdef mono_cell* cursor
|
|
495
|
+
cdef mono_cell* entry
|
|
496
|
+
while newsize < minsize:
|
|
497
|
+
newsize *= 2
|
|
498
|
+
cdef mono_cell* table = <mono_cell*>check_calloc(newsize, sizeof(mono_cell))
|
|
499
|
+
|
|
500
|
+
# We are done with memory activity. We can move the new (empty)
|
|
501
|
+
# table into place:
|
|
502
|
+
self.table = table
|
|
503
|
+
self.mask = newsize - 1
|
|
504
|
+
self.used = 0
|
|
505
|
+
self.fill = 0
|
|
506
|
+
|
|
507
|
+
# We now move all entries over. We are not changing any
|
|
508
|
+
# refcounts here, so this is a very tight loop that doesn't need
|
|
509
|
+
# to worry about tables changing.
|
|
510
|
+
cdef size_t i
|
|
511
|
+
for i in range(old_mask + 1):
|
|
512
|
+
entry = &(old_table[i])
|
|
513
|
+
if valid(entry.key_id):
|
|
514
|
+
cursor = self.lookup(entry.key_id)
|
|
515
|
+
assert cursor.key_id is NULL
|
|
516
|
+
cursor[0] = entry[0]
|
|
517
|
+
self.used += 1
|
|
518
|
+
self.fill += 1
|
|
519
|
+
sig_free(old_table)
|
|
520
|
+
|
|
521
|
+
def __cinit__(self):
|
|
522
|
+
"""
|
|
523
|
+
Setup basic data structure.
|
|
524
|
+
|
|
525
|
+
TESTS::
|
|
526
|
+
|
|
527
|
+
sage: from sage.structure.coerce_dict import TripleDict
|
|
528
|
+
sage: TripleDict.__new__(TripleDict)
|
|
529
|
+
<sage.structure.coerce_dict.TripleDict object at ...>
|
|
530
|
+
"""
|
|
531
|
+
cdef size_t newsize = 8
|
|
532
|
+
# The order is important here: the object must be in a
|
|
533
|
+
# consistent state even if exceptions are raised.
|
|
534
|
+
self.eraser = MonoDictEraser(self)
|
|
535
|
+
self.table = <mono_cell*>check_calloc(newsize, sizeof(mono_cell))
|
|
536
|
+
self.mask = newsize - 1
|
|
537
|
+
self.used = 0
|
|
538
|
+
self.fill = 0
|
|
539
|
+
|
|
540
|
+
def __init__(self, data=None, *, weak_values=False):
|
|
541
|
+
"""
|
|
542
|
+
Create a special dict using singletons for keys.
|
|
543
|
+
|
|
544
|
+
EXAMPLES::
|
|
545
|
+
|
|
546
|
+
sage: from sage.structure.coerce_dict import MonoDict
|
|
547
|
+
sage: L = MonoDict()
|
|
548
|
+
sage: a = 'a'
|
|
549
|
+
sage: L[a] = 1
|
|
550
|
+
sage: L[a]
|
|
551
|
+
1
|
|
552
|
+
sage: L = MonoDict({a: 1})
|
|
553
|
+
sage: L[a]
|
|
554
|
+
1
|
|
555
|
+
sage: L = MonoDict([(a, 1)])
|
|
556
|
+
sage: L[a]
|
|
557
|
+
1
|
|
558
|
+
"""
|
|
559
|
+
self.weak_values = weak_values
|
|
560
|
+
if data:
|
|
561
|
+
try:
|
|
562
|
+
data = data.items()
|
|
563
|
+
except AttributeError:
|
|
564
|
+
pass
|
|
565
|
+
for k, v in data:
|
|
566
|
+
self.set(k,v)
|
|
567
|
+
|
|
568
|
+
def __dealloc__(self):
|
|
569
|
+
MonoDict_clear(self)
|
|
570
|
+
sig_free(self.table)
|
|
571
|
+
|
|
572
|
+
def __len__(self):
|
|
573
|
+
"""
|
|
574
|
+
The number of items in ``self``.
|
|
575
|
+
EXAMPLES::
|
|
576
|
+
|
|
577
|
+
sage: from sage.structure.coerce_dict import MonoDict
|
|
578
|
+
sage: L = MonoDict()
|
|
579
|
+
sage: a = 'a'; b = 'b'; c = 'c'
|
|
580
|
+
sage: L[a] = 1
|
|
581
|
+
sage: L[a] = -1 # re-assign
|
|
582
|
+
sage: L[b] = 1
|
|
583
|
+
sage: L[c] = None
|
|
584
|
+
sage: len(L)
|
|
585
|
+
3
|
|
586
|
+
"""
|
|
587
|
+
return self.used
|
|
588
|
+
|
|
589
|
+
def __contains__(self, k):
|
|
590
|
+
"""
|
|
591
|
+
Test if the dictionary contains a given key.
|
|
592
|
+
|
|
593
|
+
EXAMPLES::
|
|
594
|
+
|
|
595
|
+
sage: from sage.structure.coerce_dict import MonoDict
|
|
596
|
+
sage: L = MonoDict()
|
|
597
|
+
sage: a = 'a'; b = 'ab'; c = 15
|
|
598
|
+
sage: L[a] = 1
|
|
599
|
+
sage: L[b] = 2
|
|
600
|
+
sage: L[c] = 3
|
|
601
|
+
sage: c in L # indirect doctest
|
|
602
|
+
True
|
|
603
|
+
|
|
604
|
+
The keys are compared by identity, not by equality. Hence, we have::
|
|
605
|
+
|
|
606
|
+
sage: c == 15
|
|
607
|
+
True
|
|
608
|
+
sage: 15 in L
|
|
609
|
+
False
|
|
610
|
+
"""
|
|
611
|
+
cdef mono_cell* cursor = self.lookup(<PyObject*>k)
|
|
612
|
+
if not valid(cursor.key_id):
|
|
613
|
+
return False
|
|
614
|
+
if not self.weak_values:
|
|
615
|
+
return True
|
|
616
|
+
value = <object>cursor.value
|
|
617
|
+
return not is_dead_keyedref(value)
|
|
618
|
+
|
|
619
|
+
def __getitem__(self, k):
|
|
620
|
+
"""
|
|
621
|
+
Get the value corresponding to a key.
|
|
622
|
+
|
|
623
|
+
EXAMPLES::
|
|
624
|
+
|
|
625
|
+
sage: from sage.structure.coerce_dict import MonoDict
|
|
626
|
+
sage: L = MonoDict()
|
|
627
|
+
sage: a = 'a'; b = 'b'; c = 15
|
|
628
|
+
sage: L[a] = 1
|
|
629
|
+
sage: L[b] = 2
|
|
630
|
+
sage: L[c] = 3
|
|
631
|
+
sage: L[c] # indirect doctest
|
|
632
|
+
3
|
|
633
|
+
|
|
634
|
+
Note that the keys are supposed to be unique::
|
|
635
|
+
|
|
636
|
+
sage: c == 15
|
|
637
|
+
True
|
|
638
|
+
sage: c is 15
|
|
639
|
+
False
|
|
640
|
+
sage: L[15]
|
|
641
|
+
Traceback (most recent call last):
|
|
642
|
+
...
|
|
643
|
+
KeyError: 15
|
|
644
|
+
"""
|
|
645
|
+
return self.get(k)
|
|
646
|
+
|
|
647
|
+
cdef get(self, k):
|
|
648
|
+
cdef mono_cell* cursor = self.lookup(<PyObject*>k)
|
|
649
|
+
if not valid(cursor.key_id):
|
|
650
|
+
raise KeyError(k)
|
|
651
|
+
# We need to check that the value is a live reference.
|
|
652
|
+
# Items with dead references (in the key or value) are deleted
|
|
653
|
+
# from the MonoDict by the MonoDictEraser. However, if we are
|
|
654
|
+
# in the middle of a deallocation, we may see a dead reference
|
|
655
|
+
# for the value. This cannot happen for the key: we are passed a
|
|
656
|
+
# strong reference to the key as argument of this function, so
|
|
657
|
+
# we know that it's alive.
|
|
658
|
+
value = <object>cursor.value
|
|
659
|
+
if type(value) is KeyedRef:
|
|
660
|
+
value = <object>PyWeakref_GET_OBJECT(value)
|
|
661
|
+
if value is None:
|
|
662
|
+
raise KeyError(k)
|
|
663
|
+
return value
|
|
664
|
+
|
|
665
|
+
def __setitem__(self, k, value):
|
|
666
|
+
"""
|
|
667
|
+
Set the value corresponding to a key.
|
|
668
|
+
|
|
669
|
+
EXAMPLES::
|
|
670
|
+
|
|
671
|
+
sage: from sage.structure.coerce_dict import MonoDict
|
|
672
|
+
sage: L = MonoDict()
|
|
673
|
+
sage: a = 'a'
|
|
674
|
+
sage: L[a] = -1 # indirect doctest
|
|
675
|
+
sage: L[a]
|
|
676
|
+
-1
|
|
677
|
+
sage: L[a] = 1
|
|
678
|
+
sage: L[a]
|
|
679
|
+
1
|
|
680
|
+
sage: len(L)
|
|
681
|
+
1
|
|
682
|
+
"""
|
|
683
|
+
self.set(k, value)
|
|
684
|
+
|
|
685
|
+
cdef int set(self, k, value) except -1:
|
|
686
|
+
cdef mono_cell entry
|
|
687
|
+
cdef PyObject* old_value
|
|
688
|
+
cdef bint maybe_resize = False
|
|
689
|
+
entry.key_id = <PyObject*>k
|
|
690
|
+
if self.weak_values:
|
|
691
|
+
wrap_k = wrap(k)
|
|
692
|
+
try:
|
|
693
|
+
value_store = KeyedRef(value, self.eraser, wrap_k)
|
|
694
|
+
entry.value = <PyObject*>value_store
|
|
695
|
+
except TypeError:
|
|
696
|
+
entry.value = <PyObject*>value
|
|
697
|
+
else:
|
|
698
|
+
entry.value = <PyObject*>value
|
|
699
|
+
Py_XINCREF(entry.value)
|
|
700
|
+
cursor = self.lookup(<PyObject*>k)
|
|
701
|
+
if not valid(cursor.key_id):
|
|
702
|
+
self.used += 1
|
|
703
|
+
if cursor.key_id is NULL:
|
|
704
|
+
self.fill += 1
|
|
705
|
+
maybe_resize = True
|
|
706
|
+
if not self.weak_values:
|
|
707
|
+
wrap_k = wrap(k)
|
|
708
|
+
try:
|
|
709
|
+
key_store = KeyedRef(k, self.eraser, wrap_k)
|
|
710
|
+
entry.key_weakref = <PyObject*>key_store
|
|
711
|
+
except TypeError:
|
|
712
|
+
entry.key_weakref = <PyObject*>k
|
|
713
|
+
Py_XINCREF(entry.key_weakref)
|
|
714
|
+
|
|
715
|
+
# We are taking a bit of a gamble here: we're assuming the
|
|
716
|
+
# dictionary has not been resized (otherwise cursor might
|
|
717
|
+
# not be a valid location anymore). The only way in which
|
|
718
|
+
# that could happen is if the allocation activity above
|
|
719
|
+
# forced a GC that triggered code that *adds* entries to
|
|
720
|
+
# this dictionary: the dictionary can only get reshaped if
|
|
721
|
+
# self.fill increases (as happens below). Note that we're
|
|
722
|
+
# holding a strong ref to the dict itself, so that's not
|
|
723
|
+
# liable to disappear. For the truly paranoid: we could
|
|
724
|
+
# detect a change by checking if self.table has changed
|
|
725
|
+
# value.
|
|
726
|
+
cursor[0] = entry
|
|
727
|
+
|
|
728
|
+
if maybe_resize and 3*self.fill > 2*self.mask:
|
|
729
|
+
self.resize()
|
|
730
|
+
else:
|
|
731
|
+
old_value = cursor.value
|
|
732
|
+
cursor.value = entry.value
|
|
733
|
+
Py_XDECREF(old_value)
|
|
734
|
+
|
|
735
|
+
def __delitem__(self, k):
|
|
736
|
+
"""
|
|
737
|
+
Delete the value corresponding to a key.
|
|
738
|
+
|
|
739
|
+
EXAMPLES::
|
|
740
|
+
|
|
741
|
+
sage: from sage.structure.coerce_dict import MonoDict
|
|
742
|
+
sage: L = MonoDict()
|
|
743
|
+
sage: a = 15
|
|
744
|
+
sage: L[a] = -1
|
|
745
|
+
sage: len(L)
|
|
746
|
+
1
|
|
747
|
+
|
|
748
|
+
Note that the keys are unique, hence using a key that is equal but not
|
|
749
|
+
identical to a results in an error::
|
|
750
|
+
|
|
751
|
+
sage: del L[15]
|
|
752
|
+
Traceback (most recent call last):
|
|
753
|
+
...
|
|
754
|
+
KeyError: 15
|
|
755
|
+
sage: a in L
|
|
756
|
+
True
|
|
757
|
+
sage: del L[a]
|
|
758
|
+
sage: len(L)
|
|
759
|
+
0
|
|
760
|
+
sage: a in L
|
|
761
|
+
False
|
|
762
|
+
"""
|
|
763
|
+
cdef mono_cell* cursor = self.lookup(<PyObject*>k)
|
|
764
|
+
if not valid(cursor.key_id):
|
|
765
|
+
raise KeyError(k)
|
|
766
|
+
L = extract_mono_cell(cursor)
|
|
767
|
+
self.used -= 1
|
|
768
|
+
|
|
769
|
+
def items(self):
|
|
770
|
+
"""
|
|
771
|
+
Iterate over the ``(key, value)`` pairs of this :class:`MonoDict`.
|
|
772
|
+
|
|
773
|
+
EXAMPLES::
|
|
774
|
+
|
|
775
|
+
sage: from sage.structure.coerce_dict import MonoDict
|
|
776
|
+
sage: L = MonoDict()
|
|
777
|
+
sage: L[1] = None
|
|
778
|
+
sage: L[2] = True
|
|
779
|
+
sage: L.items()
|
|
780
|
+
<...generator object at ...>
|
|
781
|
+
sage: sorted(L.items())
|
|
782
|
+
[(1, None), (2, True)]
|
|
783
|
+
"""
|
|
784
|
+
# Iteration is tricky because the table could change from under
|
|
785
|
+
# us. The following iterates properly if the dictionary does
|
|
786
|
+
# not get resized, which is guaranteed if no NEW entries in the
|
|
787
|
+
# dictionary are introduced. At least we make sure to get our
|
|
788
|
+
# data fresh from "self" every iteration, so that at least we're
|
|
789
|
+
# not reading random memory. If the dictionary changes, it's not
|
|
790
|
+
# guaranteed you get to see any particular entry.
|
|
791
|
+
cdef size_t i = 0
|
|
792
|
+
while i <= self.mask:
|
|
793
|
+
cursor = &(self.table[i])
|
|
794
|
+
i += 1
|
|
795
|
+
if valid(cursor.key_id):
|
|
796
|
+
key = <object>(cursor.key_weakref)
|
|
797
|
+
value = <object>(cursor.value)
|
|
798
|
+
if type(key) is KeyedRef:
|
|
799
|
+
key = <object>PyWeakref_GET_OBJECT(key)
|
|
800
|
+
if key is None:
|
|
801
|
+
print("found defunct key")
|
|
802
|
+
continue
|
|
803
|
+
if type(value) is KeyedRef:
|
|
804
|
+
value = <object>PyWeakref_GET_OBJECT(value)
|
|
805
|
+
if value is None:
|
|
806
|
+
print("found defunct value")
|
|
807
|
+
continue
|
|
808
|
+
yield (key, value)
|
|
809
|
+
|
|
810
|
+
def copy(self):
|
|
811
|
+
"""
|
|
812
|
+
Return a copy of this :class:`MonoDict` as Python dict.
|
|
813
|
+
|
|
814
|
+
EXAMPLES::
|
|
815
|
+
|
|
816
|
+
sage: from sage.structure.coerce_dict import MonoDict
|
|
817
|
+
sage: L = MonoDict()
|
|
818
|
+
sage: L[1] = 42
|
|
819
|
+
sage: L.copy()
|
|
820
|
+
{1: 42}
|
|
821
|
+
"""
|
|
822
|
+
return dict(self.items())
|
|
823
|
+
|
|
824
|
+
def __reduce__(self):
|
|
825
|
+
"""
|
|
826
|
+
Note that we don't expect equality as this class concerns itself with
|
|
827
|
+
object identity rather than object equality.
|
|
828
|
+
|
|
829
|
+
EXAMPLES::
|
|
830
|
+
|
|
831
|
+
sage: from sage.structure.coerce_dict import MonoDict
|
|
832
|
+
sage: L = MonoDict()
|
|
833
|
+
sage: L[1] = True
|
|
834
|
+
sage: loads(dumps(L)) == L
|
|
835
|
+
False
|
|
836
|
+
sage: list(loads(dumps(L)).items())
|
|
837
|
+
[(1, True)]
|
|
838
|
+
"""
|
|
839
|
+
return MonoDict, (self.copy(),)
|
|
840
|
+
|
|
841
|
+
# The Cython supplied tp_traverse and tp_clear do not take the
|
|
842
|
+
# dynamically allocated table into account, so we have to supply our
|
|
843
|
+
# own. The only additional link to follow (that Cython does pick up
|
|
844
|
+
# and we have to replicate here) is the "eraser" which in its closure
|
|
845
|
+
# stores a reference back to the dictionary itself (meaning that
|
|
846
|
+
# MonoDicts only disappear on cyclic GC).
|
|
847
|
+
cdef int MonoDict_traverse(MonoDict self, visitproc visit, void* arg) noexcept:
|
|
848
|
+
if not self.mask:
|
|
849
|
+
return 0
|
|
850
|
+
Py_VISIT3(<PyObject*>self.eraser, visit, arg)
|
|
851
|
+
cdef size_t i
|
|
852
|
+
for i in range(self.mask + 1):
|
|
853
|
+
cursor = &self.table[i]
|
|
854
|
+
if valid(cursor.key_id):
|
|
855
|
+
Py_VISIT3(cursor.key_weakref, visit, arg)
|
|
856
|
+
Py_VISIT3(cursor.value, visit, arg)
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
cdef int MonoDict_clear(MonoDict self) noexcept:
|
|
860
|
+
"""
|
|
861
|
+
We clear a monodict by taking first taking away the table before
|
|
862
|
+
dereffing its contents. That shortcuts callbacks, so we deref the
|
|
863
|
+
entries straight here. That means this code does not participate in
|
|
864
|
+
Python's trashcan the way that deletion code based on
|
|
865
|
+
extract_mono_cell does, so there is probably a way this code can be
|
|
866
|
+
used to overflow the C stack. It would have to be a pretty devious
|
|
867
|
+
example, though.
|
|
868
|
+
"""
|
|
869
|
+
if not self.mask:
|
|
870
|
+
return 0
|
|
871
|
+
cdef size_t mask = self.mask
|
|
872
|
+
self.mask = 0 # Setting mask to 0 immediately prevents recursion
|
|
873
|
+
self.used = 0
|
|
874
|
+
self.fill = 0
|
|
875
|
+
# Set self.eraser to None safely
|
|
876
|
+
cdef object eraser = self.eraser
|
|
877
|
+
self.eraser = None
|
|
878
|
+
for i in range(mask+1):
|
|
879
|
+
cursor = &(self.table[i])
|
|
880
|
+
if valid(cursor.key_id):
|
|
881
|
+
cursor.key_id = deleted_key
|
|
882
|
+
Py_CLEAR(cursor.key_weakref)
|
|
883
|
+
Py_CLEAR(cursor.value)
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
(<PyTypeObject*>MonoDict).tp_traverse = <traverseproc>(&MonoDict_traverse)
|
|
887
|
+
(<PyTypeObject*>MonoDict).tp_clear = <inquiry>(&MonoDict_clear)
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
cdef class TripleDictEraser:
|
|
891
|
+
"""
|
|
892
|
+
Erases items from a :class:`TripleDict` when a weak reference becomes
|
|
893
|
+
invalid.
|
|
894
|
+
|
|
895
|
+
This is of internal use only. Instances of this class will be passed as a
|
|
896
|
+
callback function when creating a weak reference.
|
|
897
|
+
|
|
898
|
+
EXAMPLES::
|
|
899
|
+
|
|
900
|
+
sage: from sage.structure.coerce_dict import TripleDict
|
|
901
|
+
sage: class A: pass
|
|
902
|
+
sage: a = A()
|
|
903
|
+
sage: T = TripleDict()
|
|
904
|
+
sage: T[a,ZZ,None] = 1
|
|
905
|
+
sage: T[ZZ,a,1] = 2
|
|
906
|
+
sage: T[a,a,ZZ] = 3
|
|
907
|
+
sage: len(T)
|
|
908
|
+
3
|
|
909
|
+
sage: del a
|
|
910
|
+
sage: import gc
|
|
911
|
+
sage: n = gc.collect()
|
|
912
|
+
sage: len(T) # indirect doctest
|
|
913
|
+
0
|
|
914
|
+
|
|
915
|
+
AUTHOR:
|
|
916
|
+
|
|
917
|
+
- Simon King (2012-01)
|
|
918
|
+
- Nils Bruin (2013-11)
|
|
919
|
+
"""
|
|
920
|
+
cdef D
|
|
921
|
+
|
|
922
|
+
def __init__(self, D):
|
|
923
|
+
"""
|
|
924
|
+
INPUT:
|
|
925
|
+
|
|
926
|
+
- ``D`` -- a :class:`TripleDict`. For internal use only.
|
|
927
|
+
|
|
928
|
+
EXAMPLES::
|
|
929
|
+
|
|
930
|
+
sage: D = sage.structure.coerce_dict.TripleDict()
|
|
931
|
+
sage: k = set([1])
|
|
932
|
+
sage: D[k,1,1] = 1
|
|
933
|
+
sage: len(D)
|
|
934
|
+
1
|
|
935
|
+
sage: del k
|
|
936
|
+
sage: len(D) # indirect doctest
|
|
937
|
+
0
|
|
938
|
+
"""
|
|
939
|
+
self.D = ref(D)
|
|
940
|
+
|
|
941
|
+
def __call__(self, r):
|
|
942
|
+
"""
|
|
943
|
+
INPUT:
|
|
944
|
+
|
|
945
|
+
- ``r`` -- a weak reference with key
|
|
946
|
+
|
|
947
|
+
For internal use only.
|
|
948
|
+
|
|
949
|
+
EXAMPLES::
|
|
950
|
+
|
|
951
|
+
sage: from sage.structure.coerce_dict import TripleDict
|
|
952
|
+
sage: class A: pass
|
|
953
|
+
sage: a = A()
|
|
954
|
+
sage: T = TripleDict()
|
|
955
|
+
sage: T[a,ZZ,None] = 1
|
|
956
|
+
sage: T[ZZ,a,1] = 2
|
|
957
|
+
sage: T[a,a,ZZ] = 3
|
|
958
|
+
sage: len(T)
|
|
959
|
+
3
|
|
960
|
+
sage: del a
|
|
961
|
+
sage: import gc
|
|
962
|
+
sage: n = gc.collect()
|
|
963
|
+
sage: len(T) # indirect doctest
|
|
964
|
+
0
|
|
965
|
+
"""
|
|
966
|
+
cdef TripleDict td = <TripleDict>PyWeakref_GetObject(self.D)
|
|
967
|
+
if td is None or not td.mask:
|
|
968
|
+
return
|
|
969
|
+
k1, k2, k3 = r.key
|
|
970
|
+
cdef triple_cell* cursor = td.lookup(unwrap(k1), unwrap(k2), unwrap(k3))
|
|
971
|
+
cdef PyObject* r_ = <PyObject*>r
|
|
972
|
+
if valid(cursor.key_id1):
|
|
973
|
+
if (cursor.key_weakref1 is r_ or
|
|
974
|
+
cursor.key_weakref2 is r_ or
|
|
975
|
+
cursor.key_weakref3 is r_ or
|
|
976
|
+
cursor.value is r_):
|
|
977
|
+
L = extract_triple_cell(cursor)
|
|
978
|
+
td.used -= 1
|
|
979
|
+
else:
|
|
980
|
+
raise AssertionError("TripleDictEraser: key match but no weakref match")
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
cdef class TripleDict:
|
|
984
|
+
"""
|
|
985
|
+
This is a hashtable specifically designed for (read) speed in
|
|
986
|
+
the coercion model.
|
|
987
|
+
|
|
988
|
+
It differs from a python dict in the following important ways:
|
|
989
|
+
|
|
990
|
+
- All keys must be sequence of exactly three elements. All sequence
|
|
991
|
+
types (tuple, list, etc.) map to the same item.
|
|
992
|
+
|
|
993
|
+
- Any of the three key components that support weak-refs are stored
|
|
994
|
+
via a weakref. If any of these components gets garbage collected
|
|
995
|
+
then the entire entry is removed. In that sense, this structure
|
|
996
|
+
behaves like a nested :class:`~weakref.WeakKeyDictionary`.
|
|
997
|
+
|
|
998
|
+
- Comparison is done using the 'is' rather than '==' operator.
|
|
999
|
+
|
|
1000
|
+
There are special cdef set/get methods for faster access.
|
|
1001
|
+
It is bare-bones in the sense that not all dictionary methods are
|
|
1002
|
+
implemented.
|
|
1003
|
+
|
|
1004
|
+
INPUT:
|
|
1005
|
+
|
|
1006
|
+
- ``data`` -- (optional) iterable defining initial data, as dict or
|
|
1007
|
+
iterable of (key, value) pairs
|
|
1008
|
+
|
|
1009
|
+
- ``weak_values`` -- boolean (default: ``False``); if it is
|
|
1010
|
+
``True``, weak references to the values in this dictionary will be used,
|
|
1011
|
+
when possible
|
|
1012
|
+
|
|
1013
|
+
IMPLEMENTATION:
|
|
1014
|
+
|
|
1015
|
+
It is implemented as a hash table with open addressing, similar to python's
|
|
1016
|
+
dict.
|
|
1017
|
+
|
|
1018
|
+
EXAMPLES::
|
|
1019
|
+
|
|
1020
|
+
sage: from sage.structure.coerce_dict import TripleDict
|
|
1021
|
+
sage: L = TripleDict()
|
|
1022
|
+
sage: a = 'a'; b = 'b'; c = 'c'
|
|
1023
|
+
sage: L[a,b,c] = 1
|
|
1024
|
+
sage: L[a,b,c]
|
|
1025
|
+
1
|
|
1026
|
+
sage: L[c,b,a] = -1
|
|
1027
|
+
sage: sorted(L.items())
|
|
1028
|
+
[(('a', 'b', 'c'), 1), (('c', 'b', 'a'), -1)]
|
|
1029
|
+
sage: del L[a,b,c]
|
|
1030
|
+
sage: list(L.items())
|
|
1031
|
+
[(('c', 'b', 'a'), -1)]
|
|
1032
|
+
sage: len(L)
|
|
1033
|
+
1
|
|
1034
|
+
sage: for i in range(1000):
|
|
1035
|
+
....: L[i,i,i] = i
|
|
1036
|
+
sage: len(L)
|
|
1037
|
+
1001
|
|
1038
|
+
sage: L = TripleDict(L)
|
|
1039
|
+
sage: L[c,b,a]
|
|
1040
|
+
-1
|
|
1041
|
+
sage: L[a,b,c]
|
|
1042
|
+
Traceback (most recent call last):
|
|
1043
|
+
...
|
|
1044
|
+
KeyError: ('a', 'b', 'c')
|
|
1045
|
+
sage: L[a]
|
|
1046
|
+
Traceback (most recent call last):
|
|
1047
|
+
...
|
|
1048
|
+
KeyError: 'a'
|
|
1049
|
+
sage: L[a] = 1
|
|
1050
|
+
Traceback (most recent call last):
|
|
1051
|
+
...
|
|
1052
|
+
KeyError: 'a'
|
|
1053
|
+
|
|
1054
|
+
TESTS:
|
|
1055
|
+
|
|
1056
|
+
Here, we demonstrate the use of weak values::
|
|
1057
|
+
|
|
1058
|
+
sage: class Foo: pass
|
|
1059
|
+
sage: T = TripleDict()
|
|
1060
|
+
sage: TW = TripleDict(weak_values=True)
|
|
1061
|
+
sage: a = Foo()
|
|
1062
|
+
sage: b = Foo()
|
|
1063
|
+
sage: k = 1
|
|
1064
|
+
sage: T[a,k,k]=1
|
|
1065
|
+
sage: T[k,a,k]=2
|
|
1066
|
+
sage: T[k,k,a]=3
|
|
1067
|
+
sage: T[k,k,k]=a
|
|
1068
|
+
sage: TW[b,k,k]=1
|
|
1069
|
+
sage: TW[k,b,k]=2
|
|
1070
|
+
sage: TW[k,k,b]=3
|
|
1071
|
+
sage: TW[k,k,k]=b
|
|
1072
|
+
sage: len(T)
|
|
1073
|
+
4
|
|
1074
|
+
sage: len(TW)
|
|
1075
|
+
4
|
|
1076
|
+
sage: (k,k,k) in T
|
|
1077
|
+
True
|
|
1078
|
+
sage: (k,k,k) in TW
|
|
1079
|
+
True
|
|
1080
|
+
sage: T[k,k,k] is a
|
|
1081
|
+
True
|
|
1082
|
+
sage: TW[k,k,k] is b
|
|
1083
|
+
True
|
|
1084
|
+
|
|
1085
|
+
Now, ``T`` holds a strong reference to ``a``, namely in ``T[k,k,k]``. Hence,
|
|
1086
|
+
when we delete ``a``, *all* items of ``T`` survive::
|
|
1087
|
+
|
|
1088
|
+
sage: import gc
|
|
1089
|
+
sage: del a
|
|
1090
|
+
sage: _ = gc.collect()
|
|
1091
|
+
sage: len(T)
|
|
1092
|
+
4
|
|
1093
|
+
|
|
1094
|
+
Only when we remove the strong reference, the items become collectable::
|
|
1095
|
+
|
|
1096
|
+
sage: del T[k,k,k]
|
|
1097
|
+
sage: _ = gc.collect()
|
|
1098
|
+
sage: len(T)
|
|
1099
|
+
0
|
|
1100
|
+
|
|
1101
|
+
The situation is different for ``TW``, since it only holds *weak*
|
|
1102
|
+
references to ``a``. Therefore, all items become collectable after
|
|
1103
|
+
deleting ``a``::
|
|
1104
|
+
|
|
1105
|
+
sage: del b
|
|
1106
|
+
sage: _ = gc.collect()
|
|
1107
|
+
sage: len(TW)
|
|
1108
|
+
0
|
|
1109
|
+
|
|
1110
|
+
AUTHORS:
|
|
1111
|
+
|
|
1112
|
+
- Robert Bradshaw, 2007-08
|
|
1113
|
+
|
|
1114
|
+
- Simon King, 2012-01
|
|
1115
|
+
|
|
1116
|
+
- Nils Bruin, 2012-08
|
|
1117
|
+
|
|
1118
|
+
- Simon King, 2013-02
|
|
1119
|
+
|
|
1120
|
+
- Nils Bruin, 2013-11
|
|
1121
|
+
"""
|
|
1122
|
+
cdef triple_cell* lookup(self, PyObject* key1, PyObject* key2, PyObject* key3) noexcept:
|
|
1123
|
+
"""
|
|
1124
|
+
Return a pointer to where ``(key1, key2, key3)`` should be
|
|
1125
|
+
stored in this :class:`MonoDict`.
|
|
1126
|
+
|
|
1127
|
+
This routine is used for all cases where a (potential) spot for
|
|
1128
|
+
a key is looked up. The returned value is a pointer into the dictionary
|
|
1129
|
+
store that either contains an entry with the requested key or a free spot
|
|
1130
|
+
where an entry for that key should go.
|
|
1131
|
+
"""
|
|
1132
|
+
cdef size_t mask = self.mask
|
|
1133
|
+
cdef triple_cell* table = self.table
|
|
1134
|
+
cdef triple_cell* first_deleted = NULL
|
|
1135
|
+
|
|
1136
|
+
# A random linear combination of the memory locations of the keys
|
|
1137
|
+
cdef size_t C2 = 0x7de83cbb
|
|
1138
|
+
cdef size_t C3 = 0x32354bf3
|
|
1139
|
+
cdef size_t h = (<size_t>key1) + C2*(<size_t>key2) + C3*(<size_t>key3)
|
|
1140
|
+
|
|
1141
|
+
# See MonoDict.lookup() for comments about the algorithm
|
|
1142
|
+
h //= 2 * sizeof(size_t)
|
|
1143
|
+
|
|
1144
|
+
cdef size_t i = (h >> 8) ^ h
|
|
1145
|
+
cdef size_t perturb = h
|
|
1146
|
+
|
|
1147
|
+
cdef triple_cell* cursor
|
|
1148
|
+
while True:
|
|
1149
|
+
cursor = &(table[i & mask])
|
|
1150
|
+
perturb >>= 5
|
|
1151
|
+
if cursor.key_id1 is key1:
|
|
1152
|
+
if cursor.key_id2 is key2 and cursor.key_id3 is key3:
|
|
1153
|
+
return cursor
|
|
1154
|
+
elif cursor.key_id1 is NULL:
|
|
1155
|
+
return first_deleted or cursor
|
|
1156
|
+
elif cursor.key_id1 is deleted_key:
|
|
1157
|
+
if first_deleted is NULL:
|
|
1158
|
+
first_deleted = cursor
|
|
1159
|
+
i = (5*i + 1) + perturb
|
|
1160
|
+
|
|
1161
|
+
cdef int resize(self) except -1:
|
|
1162
|
+
cdef triple_cell* old_table = self.table
|
|
1163
|
+
cdef size_t old_mask = self.mask
|
|
1164
|
+
cdef size_t newsize = 8
|
|
1165
|
+
cdef size_t minsize = 2 * self.used
|
|
1166
|
+
cdef triple_cell* cursor
|
|
1167
|
+
cdef triple_cell* entry
|
|
1168
|
+
while newsize < minsize:
|
|
1169
|
+
newsize *= 2
|
|
1170
|
+
cdef triple_cell* table = <triple_cell*>check_calloc(newsize, sizeof(triple_cell))
|
|
1171
|
+
self.table = table
|
|
1172
|
+
self.mask = newsize - 1
|
|
1173
|
+
self.used = 0
|
|
1174
|
+
self.fill = 0
|
|
1175
|
+
cdef size_t i
|
|
1176
|
+
for i in range(old_mask + 1):
|
|
1177
|
+
entry = &(old_table[i])
|
|
1178
|
+
if valid(entry.key_id1):
|
|
1179
|
+
cursor = self.lookup(entry.key_id1, entry.key_id2, entry.key_id3)
|
|
1180
|
+
assert cursor.key_id1 is NULL
|
|
1181
|
+
cursor[0] = entry[0]
|
|
1182
|
+
self.used +=1
|
|
1183
|
+
self.fill +=1
|
|
1184
|
+
sig_free(old_table)
|
|
1185
|
+
|
|
1186
|
+
def __cinit__(self):
|
|
1187
|
+
"""
|
|
1188
|
+
Setup basic data structure.
|
|
1189
|
+
|
|
1190
|
+
TESTS::
|
|
1191
|
+
|
|
1192
|
+
sage: from sage.structure.coerce_dict import MonoDict
|
|
1193
|
+
sage: MonoDict.__new__(MonoDict)
|
|
1194
|
+
<sage.structure.coerce_dict.MonoDict object at ...>
|
|
1195
|
+
"""
|
|
1196
|
+
cdef size_t newsize = 8
|
|
1197
|
+
# The order is important here: the object must be in a
|
|
1198
|
+
# consistent state even if exceptions are raised.
|
|
1199
|
+
self.eraser = TripleDictEraser(self)
|
|
1200
|
+
self.table = <triple_cell*>check_calloc(newsize, sizeof(triple_cell))
|
|
1201
|
+
self.mask = newsize - 1
|
|
1202
|
+
self.used = 0
|
|
1203
|
+
self.fill = 0
|
|
1204
|
+
|
|
1205
|
+
def __init__(self, data=None, *, weak_values=False):
|
|
1206
|
+
"""
|
|
1207
|
+
Create a special dict using triples for keys.
|
|
1208
|
+
|
|
1209
|
+
EXAMPLES::
|
|
1210
|
+
|
|
1211
|
+
sage: from sage.structure.coerce_dict import TripleDict
|
|
1212
|
+
sage: L = TripleDict()
|
|
1213
|
+
sage: a = 'a'; b = 'b'; c = 'c'
|
|
1214
|
+
sage: L[a,b,c] = 1
|
|
1215
|
+
sage: L[a,b,c]
|
|
1216
|
+
1
|
|
1217
|
+
sage: key = ("x", "y", "z")
|
|
1218
|
+
sage: L = TripleDict([(key, 42)])
|
|
1219
|
+
sage: L[key]
|
|
1220
|
+
42
|
|
1221
|
+
sage: L = TripleDict({key: 42})
|
|
1222
|
+
sage: L[key]
|
|
1223
|
+
42
|
|
1224
|
+
"""
|
|
1225
|
+
self.weak_values = weak_values
|
|
1226
|
+
if data:
|
|
1227
|
+
try:
|
|
1228
|
+
data = data.items()
|
|
1229
|
+
except AttributeError:
|
|
1230
|
+
pass
|
|
1231
|
+
for k, v in data:
|
|
1232
|
+
self[k] = v
|
|
1233
|
+
|
|
1234
|
+
def __dealloc__(self):
|
|
1235
|
+
TripleDict_clear(self)
|
|
1236
|
+
sig_free(self.table)
|
|
1237
|
+
|
|
1238
|
+
def __len__(self):
|
|
1239
|
+
"""
|
|
1240
|
+
The number of items in ``self``.
|
|
1241
|
+
|
|
1242
|
+
EXAMPLES::
|
|
1243
|
+
|
|
1244
|
+
sage: from sage.structure.coerce_dict import TripleDict
|
|
1245
|
+
sage: L = TripleDict()
|
|
1246
|
+
sage: a = 'a'; b = 'b'; c = 'c'
|
|
1247
|
+
sage: L[a,b,c] = 1
|
|
1248
|
+
sage: L[a,b,c] = -1 # re-assign
|
|
1249
|
+
sage: L[a,c,b] = 1
|
|
1250
|
+
sage: L[a,a,None] = None
|
|
1251
|
+
sage: len(L)
|
|
1252
|
+
3
|
|
1253
|
+
"""
|
|
1254
|
+
return self.used
|
|
1255
|
+
|
|
1256
|
+
def __contains__(self, k):
|
|
1257
|
+
"""
|
|
1258
|
+
Test if the dictionary contains a given key.
|
|
1259
|
+
|
|
1260
|
+
EXAMPLES::
|
|
1261
|
+
|
|
1262
|
+
sage: from sage.structure.coerce_dict import TripleDict
|
|
1263
|
+
sage: L = TripleDict()
|
|
1264
|
+
sage: a = 'a'; b = 'ab'; c = 15
|
|
1265
|
+
sage: L[a,b,c] = 123
|
|
1266
|
+
sage: (a,b,c) in L # indirect doctest
|
|
1267
|
+
True
|
|
1268
|
+
|
|
1269
|
+
The keys are compared by identity, not by equality. Hence, we have::
|
|
1270
|
+
|
|
1271
|
+
sage: c == 15
|
|
1272
|
+
True
|
|
1273
|
+
sage: (a, b, 15) in L
|
|
1274
|
+
False
|
|
1275
|
+
|
|
1276
|
+
TESTS::
|
|
1277
|
+
|
|
1278
|
+
sage: a in L
|
|
1279
|
+
Traceback (most recent call last):
|
|
1280
|
+
...
|
|
1281
|
+
KeyError: 'a'
|
|
1282
|
+
sage: (a, b) in L
|
|
1283
|
+
Traceback (most recent call last):
|
|
1284
|
+
...
|
|
1285
|
+
KeyError: ('a', 'ab')
|
|
1286
|
+
"""
|
|
1287
|
+
try:
|
|
1288
|
+
k1, k2, k3 = k
|
|
1289
|
+
except (TypeError, ValueError):
|
|
1290
|
+
raise KeyError(k)
|
|
1291
|
+
cdef triple_cell* cursor = self.lookup(<PyObject*>k1, <PyObject*>k2, <PyObject*>k3)
|
|
1292
|
+
if not valid(cursor.key_id1):
|
|
1293
|
+
return False
|
|
1294
|
+
if not self.weak_values:
|
|
1295
|
+
return True
|
|
1296
|
+
value = <object>cursor.value
|
|
1297
|
+
return not is_dead_keyedref(value)
|
|
1298
|
+
|
|
1299
|
+
def __getitem__(self, k):
|
|
1300
|
+
"""
|
|
1301
|
+
Get the value corresponding to a key.
|
|
1302
|
+
|
|
1303
|
+
EXAMPLES::
|
|
1304
|
+
|
|
1305
|
+
sage: from sage.structure.coerce_dict import TripleDict
|
|
1306
|
+
sage: L = TripleDict()
|
|
1307
|
+
sage: a = 'a'; b = 'b'; c = 'c'
|
|
1308
|
+
sage: L[a,b,c] = 1
|
|
1309
|
+
sage: L[a,b,c]
|
|
1310
|
+
1
|
|
1311
|
+
"""
|
|
1312
|
+
try:
|
|
1313
|
+
k1, k2, k3 = k
|
|
1314
|
+
except (TypeError, ValueError):
|
|
1315
|
+
raise KeyError(k)
|
|
1316
|
+
return self.get(k1, k2, k3)
|
|
1317
|
+
|
|
1318
|
+
cdef get(self, k1, k2, k3):
|
|
1319
|
+
cdef triple_cell* cursor = self.lookup(<PyObject*>k1, <PyObject*>k2, <PyObject*>k3)
|
|
1320
|
+
if not valid(cursor.key_id1):
|
|
1321
|
+
raise KeyError((k1, k2, k3))
|
|
1322
|
+
value = <object>cursor.value
|
|
1323
|
+
if type(value) is KeyedRef:
|
|
1324
|
+
value = <object>PyWeakref_GET_OBJECT(value)
|
|
1325
|
+
if value is None:
|
|
1326
|
+
raise KeyError((k1, k2, k3))
|
|
1327
|
+
return value
|
|
1328
|
+
|
|
1329
|
+
def __setitem__(self, k, value):
|
|
1330
|
+
"""
|
|
1331
|
+
Set the value corresponding to a key.
|
|
1332
|
+
|
|
1333
|
+
EXAMPLES::
|
|
1334
|
+
|
|
1335
|
+
sage: from sage.structure.coerce_dict import TripleDict
|
|
1336
|
+
sage: L = TripleDict()
|
|
1337
|
+
sage: a = 'a'; b = 'b'; c = 'c'
|
|
1338
|
+
sage: L[a,b,c] = -1
|
|
1339
|
+
sage: L[a,b,c]
|
|
1340
|
+
-1
|
|
1341
|
+
"""
|
|
1342
|
+
try:
|
|
1343
|
+
k1, k2, k3 = k
|
|
1344
|
+
except (TypeError, ValueError):
|
|
1345
|
+
raise KeyError(k)
|
|
1346
|
+
self.set(k1, k2, k3, value)
|
|
1347
|
+
|
|
1348
|
+
cdef int set(self, k1, k2, k3, value) except -1:
|
|
1349
|
+
cdef triple_cell entry
|
|
1350
|
+
cdef PyObject* old_value
|
|
1351
|
+
cdef bint maybe_resize = False
|
|
1352
|
+
entry.key_id1 = <PyObject*>k1
|
|
1353
|
+
entry.key_id2 = <PyObject*>k2
|
|
1354
|
+
entry.key_id3 = <PyObject*>k3
|
|
1355
|
+
if self.weak_values:
|
|
1356
|
+
wrap_k = (wrap(k1), wrap(k2), wrap(k3))
|
|
1357
|
+
try:
|
|
1358
|
+
value_store = KeyedRef(value, self.eraser, wrap_k)
|
|
1359
|
+
entry.value = <PyObject*>value_store
|
|
1360
|
+
except TypeError:
|
|
1361
|
+
entry.value = <PyObject*>value
|
|
1362
|
+
else:
|
|
1363
|
+
entry.value = <PyObject*>value
|
|
1364
|
+
Py_XINCREF(entry.value)
|
|
1365
|
+
cursor = self.lookup(<PyObject*>k1, <PyObject*>k2, <PyObject*>k3)
|
|
1366
|
+
if not valid(cursor.key_id1):
|
|
1367
|
+
self.used += 1
|
|
1368
|
+
if cursor.key_id1 is NULL:
|
|
1369
|
+
self.fill += 1
|
|
1370
|
+
maybe_resize = True
|
|
1371
|
+
if not self.weak_values:
|
|
1372
|
+
wrap_k = (wrap(k1), wrap(k2), wrap(k3))
|
|
1373
|
+
try:
|
|
1374
|
+
key_store = KeyedRef(k1, self.eraser, wrap_k)
|
|
1375
|
+
entry.key_weakref1 = <PyObject*>key_store
|
|
1376
|
+
except TypeError:
|
|
1377
|
+
entry.key_weakref1 = <PyObject*>k1
|
|
1378
|
+
Py_XINCREF(entry.key_weakref1)
|
|
1379
|
+
try:
|
|
1380
|
+
key_store = KeyedRef(k2, self.eraser, wrap_k)
|
|
1381
|
+
entry.key_weakref2 = <PyObject*>key_store
|
|
1382
|
+
except TypeError:
|
|
1383
|
+
entry.key_weakref2 = <PyObject*>k2
|
|
1384
|
+
Py_XINCREF(entry.key_weakref2)
|
|
1385
|
+
try:
|
|
1386
|
+
key_store = KeyedRef(k3, self.eraser, wrap_k)
|
|
1387
|
+
entry.key_weakref3 = <PyObject*>key_store
|
|
1388
|
+
except TypeError:
|
|
1389
|
+
entry.key_weakref3 = <PyObject*>k3
|
|
1390
|
+
Py_XINCREF(entry.key_weakref3)
|
|
1391
|
+
|
|
1392
|
+
# We are taking a bit of a gamble here: we're assuming the
|
|
1393
|
+
# dictionary has not been resized (otherwise cursor might
|
|
1394
|
+
# not be a valid location anymore). The only way in which
|
|
1395
|
+
# that could happen is if the allocation activity above
|
|
1396
|
+
# forced a GC that triggered code that *adds* entries to
|
|
1397
|
+
# this dictionary: the dictionary can only get reshaped if
|
|
1398
|
+
# self.fill increases (as happens below). Note that we're
|
|
1399
|
+
# holding a strong ref to the dict itself, so that's not
|
|
1400
|
+
# liable to disappear. For the truly paranoid: we could
|
|
1401
|
+
# detect a change by checking if self.table has changed
|
|
1402
|
+
# value.
|
|
1403
|
+
cursor[0] = entry
|
|
1404
|
+
|
|
1405
|
+
if maybe_resize and 3*self.fill > 2*self.mask:
|
|
1406
|
+
self.resize()
|
|
1407
|
+
else:
|
|
1408
|
+
old_value = cursor.value
|
|
1409
|
+
cursor.value = entry.value
|
|
1410
|
+
Py_XDECREF(old_value)
|
|
1411
|
+
|
|
1412
|
+
def __delitem__(self, k):
|
|
1413
|
+
"""
|
|
1414
|
+
Delete the value corresponding to a key.
|
|
1415
|
+
|
|
1416
|
+
EXAMPLES::
|
|
1417
|
+
|
|
1418
|
+
sage: from sage.structure.coerce_dict import TripleDict
|
|
1419
|
+
sage: L = TripleDict()
|
|
1420
|
+
sage: a = 'a'; b = 'b'; c = 'c'
|
|
1421
|
+
sage: L[a,b,c] = -1
|
|
1422
|
+
sage: (a,b,c) in L
|
|
1423
|
+
True
|
|
1424
|
+
sage: del L[a,b,c]
|
|
1425
|
+
sage: len(L)
|
|
1426
|
+
0
|
|
1427
|
+
sage: (a,b,c) in L
|
|
1428
|
+
False
|
|
1429
|
+
"""
|
|
1430
|
+
try:
|
|
1431
|
+
k1, k2, k3 = k
|
|
1432
|
+
except (TypeError, ValueError):
|
|
1433
|
+
raise KeyError(k)
|
|
1434
|
+
cdef triple_cell* cursor = self.lookup(<PyObject*>k1, <PyObject*>k2, <PyObject*>k3)
|
|
1435
|
+
if not valid(cursor.key_id1):
|
|
1436
|
+
raise KeyError(k)
|
|
1437
|
+
L = extract_triple_cell(cursor)
|
|
1438
|
+
self.used -= 1
|
|
1439
|
+
|
|
1440
|
+
def items(self):
|
|
1441
|
+
"""
|
|
1442
|
+
Iterate over the ``(key, value)`` pairs of this :class:`TripleDict`.
|
|
1443
|
+
|
|
1444
|
+
EXAMPLES::
|
|
1445
|
+
|
|
1446
|
+
sage: from sage.structure.coerce_dict import TripleDict
|
|
1447
|
+
sage: L = TripleDict()
|
|
1448
|
+
sage: L[1,2,3] = None
|
|
1449
|
+
sage: L.items()
|
|
1450
|
+
<...generator object at ...>
|
|
1451
|
+
sage: list(L.items())
|
|
1452
|
+
[((1, 2, 3), None)]
|
|
1453
|
+
"""
|
|
1454
|
+
cdef size_t i = 0
|
|
1455
|
+
while i <= self.mask:
|
|
1456
|
+
cursor = &(self.table[i])
|
|
1457
|
+
i += 1
|
|
1458
|
+
if valid(cursor.key_id1):
|
|
1459
|
+
key1 = <object>(cursor.key_weakref1)
|
|
1460
|
+
key2 = <object>(cursor.key_weakref2)
|
|
1461
|
+
key3 = <object>(cursor.key_weakref3)
|
|
1462
|
+
value = <object>(cursor.value)
|
|
1463
|
+
if type(key1) is KeyedRef:
|
|
1464
|
+
key1 = <object>PyWeakref_GET_OBJECT(key1)
|
|
1465
|
+
if key1 is None:
|
|
1466
|
+
print("found defunct key1")
|
|
1467
|
+
continue
|
|
1468
|
+
if type(key2) is KeyedRef:
|
|
1469
|
+
key2 = <object>PyWeakref_GET_OBJECT(key2)
|
|
1470
|
+
if key2 is None:
|
|
1471
|
+
print("found defunct key2")
|
|
1472
|
+
continue
|
|
1473
|
+
if type(key3) is KeyedRef:
|
|
1474
|
+
key3 = <object>PyWeakref_GET_OBJECT(key3)
|
|
1475
|
+
if key3 is None:
|
|
1476
|
+
print("found defunct key3")
|
|
1477
|
+
continue
|
|
1478
|
+
if type(value) is KeyedRef:
|
|
1479
|
+
value = <object>PyWeakref_GET_OBJECT(value)
|
|
1480
|
+
if value is None:
|
|
1481
|
+
print("found defunct value")
|
|
1482
|
+
continue
|
|
1483
|
+
yield ((key1, key2, key3), value)
|
|
1484
|
+
|
|
1485
|
+
def copy(self):
|
|
1486
|
+
"""
|
|
1487
|
+
Return a copy of this :class:`TripleDict` as Python dict.
|
|
1488
|
+
|
|
1489
|
+
EXAMPLES::
|
|
1490
|
+
|
|
1491
|
+
sage: from sage.structure.coerce_dict import TripleDict
|
|
1492
|
+
sage: L = TripleDict()
|
|
1493
|
+
sage: L[1,2,3] = 42
|
|
1494
|
+
sage: L.copy()
|
|
1495
|
+
{(1, 2, 3): 42}
|
|
1496
|
+
"""
|
|
1497
|
+
return dict(self.items())
|
|
1498
|
+
|
|
1499
|
+
def __reduce__(self):
|
|
1500
|
+
"""
|
|
1501
|
+
Note that we don't expect equality as this class concerns itself with
|
|
1502
|
+
object identity rather than object equality.
|
|
1503
|
+
|
|
1504
|
+
EXAMPLES::
|
|
1505
|
+
|
|
1506
|
+
sage: from sage.structure.coerce_dict import TripleDict
|
|
1507
|
+
sage: L = TripleDict()
|
|
1508
|
+
sage: L[1,2,3] = True
|
|
1509
|
+
sage: loads(dumps(L)) == L
|
|
1510
|
+
False
|
|
1511
|
+
sage: list(loads(dumps(L)).items())
|
|
1512
|
+
[((1, 2, 3), True)]
|
|
1513
|
+
"""
|
|
1514
|
+
return TripleDict, (self.copy(),)
|
|
1515
|
+
|
|
1516
|
+
# The Cython supplied tp_traverse and tp_clear do not take the
|
|
1517
|
+
# dynamically allocated table into account, so we have to supply our
|
|
1518
|
+
# own. The only additional link to follow (that Cython does pick up
|
|
1519
|
+
# and we have to replicate here) is the "eraser" which in its closure
|
|
1520
|
+
# stores a reference back to the dictionary itself (meaning that
|
|
1521
|
+
# TripleDicts only disappear on cyclic GC).
|
|
1522
|
+
cdef int TripleDict_traverse(TripleDict self, visitproc visit, void* arg) noexcept:
|
|
1523
|
+
if not self.mask:
|
|
1524
|
+
return 0
|
|
1525
|
+
Py_VISIT3(<PyObject*>self.eraser, visit, arg)
|
|
1526
|
+
cdef size_t i
|
|
1527
|
+
for i in range(self.mask + 1):
|
|
1528
|
+
cursor = &self.table[i]
|
|
1529
|
+
if valid(cursor.key_id1):
|
|
1530
|
+
Py_VISIT3(cursor.key_weakref1, visit, arg)
|
|
1531
|
+
Py_VISIT3(cursor.key_weakref2, visit, arg)
|
|
1532
|
+
Py_VISIT3(cursor.key_weakref3, visit, arg)
|
|
1533
|
+
Py_VISIT3(cursor.value, visit, arg)
|
|
1534
|
+
|
|
1535
|
+
|
|
1536
|
+
cdef int TripleDict_clear(TripleDict self) noexcept:
|
|
1537
|
+
if not self.mask:
|
|
1538
|
+
return 0
|
|
1539
|
+
cdef size_t mask = self.mask
|
|
1540
|
+
self.mask = 0 # Setting mask to 0 immediately prevents recursion
|
|
1541
|
+
self.used = 0
|
|
1542
|
+
self.fill = 0
|
|
1543
|
+
# Set self.eraser to None safely
|
|
1544
|
+
cdef object eraser = self.eraser
|
|
1545
|
+
self.eraser = None
|
|
1546
|
+
for i in range(mask + 1):
|
|
1547
|
+
cursor = &(self.table[i])
|
|
1548
|
+
if valid(cursor.key_id1):
|
|
1549
|
+
cursor.key_id1 = deleted_key
|
|
1550
|
+
Py_CLEAR(cursor.key_weakref1)
|
|
1551
|
+
Py_CLEAR(cursor.key_weakref2)
|
|
1552
|
+
Py_CLEAR(cursor.key_weakref3)
|
|
1553
|
+
Py_CLEAR(cursor.value)
|
|
1554
|
+
|
|
1555
|
+
|
|
1556
|
+
(<PyTypeObject*>TripleDict).tp_traverse = <traverseproc>(&TripleDict_traverse)
|
|
1557
|
+
(<PyTypeObject*>TripleDict).tp_clear = <inquiry>(&TripleDict_clear)
|