Skip to content

Commit c47e01d

Browse files
committed
Numerous fix-ups to C API and docs. Added tests for C API.
1 parent 994c2c1 commit c47e01d

File tree

4 files changed

+148
-26
lines changed

4 files changed

+148
-26
lines changed

Doc/api/concrete.tex

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2959,14 +2959,16 @@ \subsection{Set Objects \label{setObjects}}
29592959
Returns a new \class{set} containing objects returned by the
29602960
\var{iterable}. The \var{iterable} may be \NULL{} to create a
29612961
new empty set. Returns the new set on success or \NULL{} on
2962-
failure.
2962+
failure. Raises \exception{TypeError} if \var{iterable} is
2963+
not actually iterable.
29632964
\end{cfuncdesc}
29642965

29652966
\begin{cfuncdesc}{PyObject*}{PyFrozenSet_New}{PyObject *iterable}
29662967
Returns a new \class{frozenset} containing objects returned by the
29672968
\var{iterable}. The \var{iterable} may be \NULL{} to create a
29682969
new empty frozenset. Returns the new set on success or \NULL{} on
2969-
failure.
2970+
failure. Raises \exception{TypeError} if \var{iterable} is
2971+
not actually iterable.
29702972
\end{cfuncdesc}
29712973

29722974

@@ -2976,7 +2978,7 @@ \subsection{Set Objects \label{setObjects}}
29762978
\begin{cfuncdesc}{int}{PySet_Size}{PyObject *anyset}
29772979
Returns the length of a \class{set} or \class{frozenset} object.
29782980
Equivalent to \samp{len(\var{anyset})}. Raises a
2979-
\exception{PyExc_SystemError} if the argument is not a \class{set},
2981+
\exception{PyExc_SystemError} if \var{anyset} is not a \class{set},
29802982
\class{frozenset}, or an instance of a subtype.
29812983
\bifuncindex{len}
29822984
\end{cfuncdesc}
@@ -2989,15 +2991,9 @@ \subsection{Set Objects \label{setObjects}}
29892991
Returns 1 if found, 0 if not found, and -1 if an error is
29902992
encountered. Unlike the Python \method{__contains__()} method, this
29912993
function does not automatically convert unhashable sets into temporary
2992-
frozensets. Raises a \exception{TypeError} if the key is unhashable.
2993-
\end{cfuncdesc}
2994-
2995-
\begin{cfuncdesc}{int}{PySet_Discard}{PyObject *anyset, PyObject *key}
2996-
Returns 1 if found and removed, 0 if not found (no action taken),
2997-
and -1 if an error is encountered. Does not raise \exception{KeyError}
2998-
for missing keys. Raises a \exception{TypeError} if the key is unhashable.
2999-
Unlike the Python \method{discard()} method, this function does
3000-
not automatically convert unhashable sets into temporary frozensets.
2994+
frozensets. Raises a \exception{TypeError} if the \var{key} is unhashable.
2995+
Raises \exception{PyExc_SystemError} if \var{anyset} is not a \class{set},
2996+
\class{frozenset}, or an instance of a subtype.
30012997
\end{cfuncdesc}
30022998

30032999

@@ -3007,17 +3003,27 @@ \subsection{Set Objects \label{setObjects}}
30073003
\begin{cfuncdesc}{int}{PySet_Add}{PyObject *set, PyObject *key}
30083004
Adds \var{key} to a \class{set} instance. Does not apply to
30093005
\class{frozenset} instances. Returns 0 on success or -1 on failure.
3010-
Raises a \exception{TypeError} if the key is unhashable.
3006+
Raises a \exception{TypeError} if the \var{key} is unhashable.
30113007
Raises a \exception{MemoryError} if there is no room to grow.
3012-
Raises a \exception{SystemError} if \var{key} is an not an instance
3008+
Raises a \exception{SystemError} if \var{set} is an not an instance
30133009
of \class{set} or its subtype.
30143010
\end{cfuncdesc}
30153011

3012+
\begin{cfuncdesc}{int}{PySet_Discard}{PyObject *set, PyObject *key}
3013+
Returns 1 if found and removed, 0 if not found (no action taken),
3014+
and -1 if an error is encountered. Does not raise \exception{KeyError}
3015+
for missing keys. Raises a \exception{TypeError} if the \var{key} is
3016+
unhashable. Unlike the Python \method{discard()} method, this function
3017+
does not automatically convert unhashable sets into temporary frozensets.
3018+
Raises \exception{PyExc_SystemError} if \var{set} is an not an instance
3019+
of \class{set} or its subtype.
3020+
\end{cfuncdesc}
3021+
30163022
\begin{cfuncdesc}{PyObject*}{PySet_Pop}{PyObject *set}
30173023
Returns a new reference to an arbitrary object in the \var{set},
30183024
and removes the object from the \var{set}. Returns \NULL{} on
30193025
failure. Raises \exception{KeyError} if the set is empty.
3020-
Raises a \exception{SystemError} if \var{key} is an not an instance
3026+
Raises a \exception{SystemError} if \var{set} is an not an instance
30213027
of \class{set} or its subtype.
30223028
\end{cfuncdesc}
30233029

Include/setobject.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ PyAPI_FUNC(PyObject *) PyFrozenSet_New(PyObject *);
7979
PyAPI_FUNC(int) PySet_Size(PyObject *anyset);
8080
#define PySet_GET_SIZE(so) (((PySetObject *)(so))->used)
8181
PyAPI_FUNC(int) PySet_Contains(PyObject *anyset, PyObject *key);
82-
PyAPI_FUNC(int) PySet_Discard(PyObject *anyset, PyObject *key);
82+
PyAPI_FUNC(int) PySet_Discard(PyObject *set, PyObject *key);
8383
PyAPI_FUNC(int) PySet_Add(PyObject *set, PyObject *key);
8484
PyAPI_FUNC(PyObject *) PySet_Pop(PyObject *set);
8585

Lib/test/test_set.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import pickle
77
import os
88
from random import randrange, shuffle
9+
import sys
910

1011
class PassThru(Exception):
1112
pass
@@ -402,6 +403,11 @@ def test_weakref(self):
402403
s = None
403404
self.assertRaises(ReferenceError, str, p)
404405

406+
# C API test only available in a debug build
407+
if hasattr(sys, "gettotalrefcount"):
408+
def test_c_api(self):
409+
self.assertEqual(set('abc').test_c_api(), True)
410+
405411
class SetSubclass(set):
406412
pass
407413

@@ -1372,7 +1378,6 @@ def test_inplace_methods(self):
13721378
#==============================================================================
13731379

13741380
def test_main(verbose=None):
1375-
import sys
13761381
from test import test_sets
13771382
test_classes = (
13781383
TestSet,

Objects/setobject.c

Lines changed: 120 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1697,6 +1697,13 @@ static PySequenceMethods set_as_sequence = {
16971697

16981698
/* set object ********************************************************/
16991699

1700+
#ifdef Py_DEBUG
1701+
static PyObject *test_c_api(PySetObject *so);
1702+
1703+
PyDoc_STRVAR(test_c_api_doc, "Exercises C API. Returns True.\n\
1704+
All is well if assertions don't fail.");
1705+
#endif
1706+
17001707
static PyMethodDef set_methods[] = {
17011708
{"add", (PyCFunction)set_add, METH_O,
17021709
add_doc},
@@ -1730,6 +1737,10 @@ static PyMethodDef set_methods[] = {
17301737
symmetric_difference_doc},
17311738
{"symmetric_difference_update",(PyCFunction)set_symmetric_difference_update, METH_O,
17321739
symmetric_difference_update_doc},
1740+
#ifdef Py_DEBUG
1741+
{"test_c_api", (PyCFunction)test_c_api, METH_NOARGS,
1742+
test_c_api_doc},
1743+
#endif
17331744
{"union", (PyCFunction)set_union, METH_O,
17341745
union_doc},
17351746
{"update", (PyCFunction)set_update, METH_O,
@@ -1931,18 +1942,29 @@ PySet_New(PyObject *iterable)
19311942
PyObject *
19321943
PyFrozenSet_New(PyObject *iterable)
19331944
{
1934-
PyObject *args = NULL, *result;
1945+
PyObject *args, *result;
19351946

1936-
if (iterable != NULL) {
1947+
if (iterable == NULL)
1948+
args = PyTuple_New(0);
1949+
else
19371950
args = PyTuple_Pack(1, iterable);
1938-
if (args == NULL)
1939-
return NULL;
1940-
}
1951+
if (args == NULL)
1952+
return NULL;
19411953
result = frozenset_new(&PyFrozenSet_Type, args, NULL);
1942-
Py_XDECREF(args);
1954+
Py_DECREF(args);
19431955
return result;
19441956
}
19451957

1958+
int
1959+
PySet_Size(PyObject *anyset)
1960+
{
1961+
if (!PyAnySet_Check(anyset)) {
1962+
PyErr_BadInternalCall();
1963+
return -1;
1964+
}
1965+
return ((PySetObject *)anyset)->used;
1966+
}
1967+
19461968
int
19471969
PySet_Contains(PyObject *anyset, PyObject *key)
19481970
{
@@ -1954,13 +1976,13 @@ PySet_Contains(PyObject *anyset, PyObject *key)
19541976
}
19551977

19561978
int
1957-
PySet_Discard(PyObject *anyset, PyObject *key)
1979+
PySet_Discard(PyObject *set, PyObject *key)
19581980
{
1959-
if (!PyAnySet_Check(anyset)) {
1981+
if (!PyType_IsSubtype(set->ob_type, &PySet_Type)) {
19601982
PyErr_BadInternalCall();
19611983
return -1;
19621984
}
1963-
return set_discard_key((PySetObject *)anyset, key);
1985+
return set_discard_key((PySetObject *)set, key);
19641986
}
19651987

19661988
int
@@ -1982,3 +2004,92 @@ PySet_Pop(PyObject *set)
19822004
}
19832005
return set_pop((PySetObject *)set);
19842006
}
2007+
2008+
2009+
#ifdef Py_DEBUG
2010+
2011+
/* Test code to be called with any three element set.
2012+
Returns True and original set is restored. */
2013+
2014+
#define assertRaises(call_return_value, exception) \
2015+
do { \
2016+
assert(call_return_value); \
2017+
assert(PyErr_ExceptionMatches(exception)); \
2018+
PyErr_Clear(); \
2019+
} while(0)
2020+
2021+
static PyObject *
2022+
test_c_api(PySetObject *so)
2023+
{
2024+
PyObject *elem, *dup, *t, *f, *ob = (PyObject *)so;
2025+
2026+
/* Verify preconditions and exercise type/size checks */
2027+
assert(PyAnySet_Check(ob));
2028+
assert(PyAnySet_CheckExact(ob));
2029+
assert(!PyFrozenSet_CheckExact(ob));
2030+
assert(PySet_Size(ob) == 3);
2031+
assert(PySet_GET_SIZE(ob) == 3);
2032+
2033+
/* Raise TypeError for non-iterable constructor arguments */
2034+
assertRaises(PySet_New(Py_None) == NULL, PyExc_TypeError);
2035+
assertRaises(PyFrozenSet_New(Py_None) == NULL, PyExc_TypeError);
2036+
2037+
/* Raise TypeError for unhashable key */
2038+
dup = PySet_New(ob);
2039+
assertRaises(PySet_Discard(ob, dup) == -1, PyExc_TypeError);
2040+
assertRaises(PySet_Contains(ob, dup) == -1, PyExc_TypeError);
2041+
assertRaises(PySet_Add(ob, dup) == -1, PyExc_TypeError);
2042+
2043+
/* Exercise successful pop, contains, add, and discard */
2044+
elem = PySet_Pop(ob);
2045+
assert(PySet_Contains(ob, elem) == 0);
2046+
assert(PySet_GET_SIZE(ob) == 2);
2047+
assert(PySet_Add(ob, elem) == 0);
2048+
assert(PySet_Contains(ob, elem) == 1);
2049+
assert(PySet_GET_SIZE(ob) == 3);
2050+
assert(PySet_Discard(ob, elem) == 1);
2051+
assert(PySet_GET_SIZE(ob) == 2);
2052+
assert(PySet_Discard(ob, elem) == 0);
2053+
assert(PySet_GET_SIZE(ob) == 2);
2054+
2055+
/* Raise SystemError when self argument is not a set or frozenset. */
2056+
t = PyTuple_New(0);
2057+
assertRaises(PySet_Size(t) == -1, PyExc_SystemError);
2058+
assertRaises(PySet_Contains(t, elem) == -1, PyExc_SystemError);
2059+
Py_DECREF(t);
2060+
2061+
/* Raise SystemError when self argument is not a set. */
2062+
f = PyFrozenSet_New(dup);
2063+
assert(PySet_Size(f) == 3);
2064+
assert(PyFrozenSet_CheckExact(f));
2065+
assertRaises(PySet_Add(f, elem) == -1, PyExc_SystemError);
2066+
assertRaises(PySet_Discard(f, elem) == -1, PyExc_SystemError);
2067+
assertRaises(PySet_Pop(f) == NULL, PyExc_SystemError);
2068+
Py_DECREF(f);
2069+
2070+
/* Raise KeyError when popping from an empty set */
2071+
set_clear_internal(so);
2072+
assert(PySet_GET_SIZE(ob) == 0);
2073+
assertRaises(PySet_Pop(ob) == NULL, PyExc_KeyError);
2074+
2075+
/* Restore the set from the copy and use the abstract API */
2076+
assert(PyObject_CallMethod(ob, "update", "O", dup) == Py_None);
2077+
Py_DECREF(Py_None);
2078+
2079+
/* Verify constructors accept NULL arguments */
2080+
f = PySet_New(NULL);
2081+
assert(f != NULL);
2082+
assert(PySet_GET_SIZE(f) == 0);
2083+
Py_DECREF(f);
2084+
f = PyFrozenSet_New(NULL);
2085+
assert(f != NULL);
2086+
assert(PyFrozenSet_CheckExact(f));
2087+
assert(PySet_GET_SIZE(f) == 0);
2088+
Py_DECREF(f);
2089+
2090+
Py_DECREF(elem);
2091+
Py_DECREF(dup);
2092+
Py_RETURN_TRUE;
2093+
}
2094+
2095+
#endif

0 commit comments

Comments
 (0)