1
2import cython
3
4class Unhashable(object):
5    def __hash__(self):
6        raise TypeError('I am not hashable')
7
8class Hashable(object):
9    def __hash__(self):
10        return 1
11    def __eq__(self, other):
12        return isinstance(other, Hashable)
13
14class CountedHashable(object):
15    def __init__(self):
16        self.hash_count = 0
17        self.eq_count = 0
18    def __hash__(self):
19        self.hash_count += 1
20        return 42
21    def __eq__(self, other):
22        self.eq_count += 1
23        return id(self) == id(other)
24
25@cython.test_fail_if_path_exists('//AttributeNode')
26@cython.test_assert_path_exists('//PythonCapiCallNode')
27@cython.locals(d=dict)
28def setdefault1(d, key):
29    """
30    >>> d = {}
31    >>> setdefault1(d, 1)
32    >>> len(d)
33    1
34    >>> setdefault1(d, 1)
35    >>> len(d)
36    1
37    >>> d[1]
38    >>> setdefault1(d, Unhashable())
39    Traceback (most recent call last):
40    TypeError: I am not hashable
41    >>> len(d)
42    1
43    >>> h1 = setdefault1(d, Hashable())
44    >>> len(d)
45    2
46    >>> h2 = setdefault1(d, Hashable())
47    >>> len(d)
48    2
49    >>> d[Hashable()]
50
51    # CPython's behaviour depends on version and py_debug setting, so just compare to it
52    >>> py_hashed1 = CountedHashable()
53    >>> y = {py_hashed1: 5}
54    >>> py_hashed2 = CountedHashable()
55    >>> y.setdefault(py_hashed2)
56
57    >>> cy_hashed1 = CountedHashable()
58    >>> y = {cy_hashed1: 5}
59    >>> cy_hashed2 = CountedHashable()
60    >>> setdefault1(y, cy_hashed2)
61    >>> py_hashed1.hash_count - cy_hashed1.hash_count
62    0
63    >>> py_hashed2.hash_count - cy_hashed2.hash_count
64    0
65    >>> (py_hashed1.eq_count + py_hashed2.eq_count) - (cy_hashed1.eq_count + cy_hashed2.eq_count)
66    0
67    """
68    return d.setdefault(key)
69
70@cython.test_fail_if_path_exists('//AttributeNode')
71@cython.test_assert_path_exists('//PythonCapiCallNode')
72@cython.locals(d=dict)
73def setdefault2(d, key, value):
74    """
75    >>> d = {}
76    >>> setdefault2(d, 1, 2)
77    2
78    >>> len(d)
79    1
80    >>> setdefault2(d, 1, 2)
81    2
82    >>> len(d)
83    1
84    >>> l = setdefault2(d, 2, [])
85    >>> len(d)
86    2
87    >>> l.append(1)
88    >>> setdefault2(d, 2, [])
89    [1]
90    >>> len(d)
91    2
92    >>> setdefault2(d, Unhashable(), 1)
93    Traceback (most recent call last):
94    TypeError: I am not hashable
95    >>> h1 = setdefault2(d, Hashable(), 55)
96    >>> len(d)
97    3
98    >>> h2 = setdefault2(d, Hashable(), 66)
99    >>> len(d)
100    3
101    >>> d[Hashable()]
102    55
103
104    # CPython's behaviour depends on version and py_debug setting, so just compare to it
105    >>> py_hashed1 = CountedHashable()
106    >>> y = {py_hashed1: 5}
107    >>> py_hashed2 = CountedHashable()
108    >>> y.setdefault(py_hashed2, [])
109    []
110
111    >>> cy_hashed1 = CountedHashable()
112    >>> y = {cy_hashed1: 5}
113    >>> cy_hashed2 = CountedHashable()
114    >>> setdefault2(y, cy_hashed2, [])
115    []
116    >>> py_hashed1.hash_count - cy_hashed1.hash_count
117    0
118    >>> py_hashed2.hash_count - cy_hashed2.hash_count
119    0
120    >>> (py_hashed1.eq_count + py_hashed2.eq_count) - (cy_hashed1.eq_count + cy_hashed2.eq_count)
121    0
122    """
123    return d.setdefault(key, value)
124