From beb3101b051e415cb18ba844e0187a8caa7ac3fd Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Tue, 16 Aug 2005 03:47:52 +0000 Subject: [PATCH] Add a C API for sets and frozensets. --- Doc/api/concrete.tex | 125 +++++++++++++++++++++++++++++++++++++++++++ Include/setobject.h | 9 ++++ Misc/NEWS | 2 + Objects/setobject.c | 80 ++++++++++++++++++++++++--- Python/marshal.c | 6 +-- 5 files changed, 210 insertions(+), 12 deletions(-) diff --git a/Doc/api/concrete.tex b/Doc/api/concrete.tex index e174beed086..2f37be5936a 100644 --- a/Doc/api/concrete.tex +++ b/Doc/api/concrete.tex @@ -2897,3 +2897,128 @@ Macros for the convenience of modules implementing the DB API: tuple suitable for passing to \code{datetime.date.fromtimestamp()}. \versionadded{2.4} \end{cfuncdesc} + + +\subsection{Set Objects \label{setObjects}} +\sectionauthor{Raymond D. Hettinger}{python@rcn.com} + +\obindex{set} +\obindex{frozenset} +\versionadded{2.5} + +This section details the public API for \class{set} and \class{frozenset} +objects. Any functionality not listed below is best accessed using the +abstract object API (including +\cfunction{PyObject_CallMethod()}, \cfunction{PyObject_RichCompareBool()}, +\cfunction{PyObject_Hash()}, \cfunction{PyObject_Repr()}, +\cfunction{PyObject_IsTrue()}, \cfunction{PyObject_Print()}, and +\cfunction{PyObject_GetIter()}). + +\begin{ctypedesc}{PySetObject} + This subtype of \ctype{PyObject} is used to hold the internal data for + both \class{set} and \class{frozenset} objects. It is like a + \ctype{PyDictObject} in that it is a fixed size for small sets + (much like tuple storage) and will point to a separate, variable sized + block of memory for medium and large sized sets (much like list storage). + None of the fields of this structure should be considered public and + are subject to change. All access should be done through the + documented API. + +\end{ctypedesc} + +\begin{cvardesc}{PyTypeObject}{PySet_Type} + This is an instance of \ctype{PyTypeObject} representing the Python + \class{set} type. +\end{cvardesc} + +\begin{cvardesc}{PyTypeObject}{PyFrozenSet_Type} + This is an instance of \ctype{PyTypeObject} representing the Python + \class{frozenset} type. +\end{cvardesc} + + +The following type check macros work on pointers to any Python object. +Likewise, the constructor functions work with any iterable Python object. + +\begin{cfuncdesc}{int}{PyAnySet_Check}{PyObject *p} + Returns true if \var{p} is a \class{set} object, a \class{frozenset} + object, or an instance of a subtype. +\end{cfuncdesc} + +\begin{cfuncdesc}{int}{PyAnySet_CheckExact}{PyObject *p} + Returns true if \var{p} is a \class{set} object or a \class{frozenset} + object but not an instance of a subtype. +\end{cfuncdesc} + +\begin{cfuncdesc}{int}{PyFrozenSet_CheckExact}{PyObject *p} + Returns true if \var{p} is a \class{frozenset} object + but not an instance of a subtype. +\end{cfuncdesc} + +\begin{cfuncdesc}{PyObject*}{PySet_New}{PyObject *iterable} + Returns a new \class{set} containing objects returned by the + \var{iterable}. The \var{iterable} may be \NULL{} to create a + new empty set. Returns the new set on success or \NULL{} on + failure. +\end{cfuncdesc} + +\begin{cfuncdesc}{PyObject*}{PyFrozenSet_New}{PyObject *iterable} + Returns a new \class{frozenset} containing objects returned by the + \var{iterable}. The \var{iterable} may be \NULL{} to create a + new empty frozenset. Returns the new set on success or \NULL{} on + failure. +\end{cfuncdesc} + + +The following functions and macros are available for instances of +\class{set} or \class{frozenset} or instances of their subtypes. + +\begin{cfuncdesc}{int}{PySet_Size}{PyObject *anyset} + Returns the length of a \class{set} or \class{frozenset} object. + Equivalent to \samp{len(\var{anyset})}. Raises a + \exception{PyExc_SystemError} if the argument is not a \class{set}, + \class{frozenset}, or an instance of a subtype. + \bifuncindex{len} +\end{cfuncdesc} + +\begin{cfuncdesc}{int}{PySet_GET_SIZE}{PyObject *anyset} + Macro form of \cfunction{PySet_Size()} without error checking. +\end{cfuncdesc} + +\begin{cfuncdesc}{int}{PySet_Contains}{PyObject *anyset, PyObject *key} + Returns 1 if found, 0 if not found, and -1 if an error is + encountered. Unlike the Python \method{__contains__()} method, this + function does not automatically convert unhashable sets into temporary + frozensets. Raises a \exception{TypeError} if the key is unhashable. +\end{cfuncdesc} + +\begin{cfuncdesc}{int}{PySet_Discard}{PyObject *anyset, PyObject *key} + Returns 1 if found and removed, 0 if not found (no action taken), + and -1 if an error is encountered. Does not raise \exception{KeyError} + for missing keys. Raises a \exception{TypeError} if the key is unhashable. + Unlike the Python \method{discard()} method, this function does + not automatically convert unhashable sets into temporary frozensets. +\end{cfuncdesc} + + +The following functions are available for instances of \class{set} or +its subtypes but not for instances of \class{frozenset} or its subtypes. + +\begin{cfuncdesc}{int}{PySet_Add}{PyObject *set, PyObject *key} + Adds \var{key} to a \class{set} instance. Does not apply to + \class{frozenset} instances. Returns 0 on success or -1 on failure. + Raises a \exception{TypeError} if the key is unhashable. + Raises a \exception{MemoryError} if there is no room to grow. + Raises a \exception{SystemError} if \var{key} is an not an instance + of \class{set} or its subtype. +\end{cfuncdesc} + +\begin{cfuncdesc}{PyObject*}{PySet_Pop}{PyObject *set} + Returns a new reference to an arbitrary object in the \var{set}, + and removes the object from the \var{set}. Returns \NULL{} on + failure. Raises \exception{KeyError} if the set is empty. + Raises a \exception{SystemError} if \var{key} is an not an instance + of \class{set} or its subtype. +\end{cfuncdesc} + + diff --git a/Include/setobject.h b/Include/setobject.h index 6a829f0cb09..b6849e14c93 100644 --- a/Include/setobject.h +++ b/Include/setobject.h @@ -74,6 +74,15 @@ PyAPI_DATA(PyTypeObject) PyFrozenSet_Type; PyType_IsSubtype((ob)->ob_type, &PySet_Type) || \ PyType_IsSubtype((ob)->ob_type, &PyFrozenSet_Type)) +PyAPI_FUNC(PyObject *) PySet_New(PyObject *); +PyAPI_FUNC(PyObject *) PyFrozenSet_New(PyObject *); +PyAPI_FUNC(int) PySet_Size(PyObject *anyset); +#define PySet_GET_SIZE(so) (((PySetObject *)(so))->used) +PyAPI_FUNC(int) PySet_Contains(PyObject *anyset, PyObject *key); +PyAPI_FUNC(int) PySet_Discard(PyObject *anyset, PyObject *key); +PyAPI_FUNC(int) PySet_Add(PyObject *set, PyObject *key); +PyAPI_FUNC(PyObject *) PySet_Pop(PyObject *set); + #ifdef __cplusplus } #endif diff --git a/Misc/NEWS b/Misc/NEWS index 62e6656f868..e3fbbabc5be 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -439,6 +439,8 @@ Build C API ----- +- Added a C API for set and frozenset objects. + - Removed PyRange_New(). diff --git a/Objects/setobject.c b/Objects/setobject.c index c52254925c4..978d61359cc 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -843,7 +843,7 @@ frozenset_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return iterable; } result = make_new_set(type, iterable); - if (result == NULL || set_len(result)) + if (result == NULL || PySet_GET_SIZE(result)) return result; Py_DECREF(result); } @@ -1052,7 +1052,7 @@ set_intersection(PySetObject *so, PyObject *other) int pos = 0; setentry *entry; - if (set_len(other) > set_len((PyObject *)so)) { + if (PySet_GET_SIZE(other) > PySet_GET_SIZE(so)) { tmp = (PyObject *)so; so = (PySetObject *)other; other = tmp; @@ -1381,7 +1381,7 @@ set_issubset(PySetObject *so, PyObject *other) Py_DECREF(tmp); return result; } - if (set_len((PyObject *)so) > set_len(other)) + if (PySet_GET_SIZE(so) > PySet_GET_SIZE(other)) Py_RETURN_FALSE; while (set_next(so, &pos, &entry)) { @@ -1426,11 +1426,11 @@ set_richcompare(PySetObject *v, PyObject *w, int op) } switch (op) { case Py_EQ: - if (set_len((PyObject *)v) != set_len(w)) + if (PySet_GET_SIZE(v) != PySet_GET_SIZE(w)) Py_RETURN_FALSE; return set_issubset(v, w); case Py_NE: - if (set_len((PyObject *)v) != set_len(w)) + if (PySet_GET_SIZE(v) != PySet_GET_SIZE(w)) Py_RETURN_TRUE; r1 = set_issubset(v, w); assert (r1 != NULL); @@ -1442,11 +1442,11 @@ set_richcompare(PySetObject *v, PyObject *w, int op) case Py_GE: return set_issuperset(v, w); case Py_LT: - if (set_len((PyObject *)v) >= set_len(w)) + if (PySet_GET_SIZE(v) >= PySet_GET_SIZE(w)) Py_RETURN_FALSE; return set_issubset(v, w); case Py_GT: - if (set_len((PyObject *)v) <= set_len(w)) + if (PySet_GET_SIZE(v) <= PySet_GET_SIZE(w)) Py_RETURN_FALSE; return set_issuperset(v, w); } @@ -1472,7 +1472,7 @@ frozenset_hash(PyObject *self) if (so->hash != -1) return so->hash; - hash *= set_len(self) + 1; + hash *= PySet_GET_SIZE(self) + 1; while (set_next(so, &pos, &entry)) { /* Work to increase the bit dispersion for closely spaced hash values. The is important because some use cases have many @@ -1918,3 +1918,67 @@ PyTypeObject PyFrozenSet_Type = { frozenset_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ }; + + +/***** C API functions *************************************************/ + +PyObject * +PySet_New(PyObject *iterable) +{ + return make_new_set(&PySet_Type, iterable); +} + +PyObject * +PyFrozenSet_New(PyObject *iterable) +{ + PyObject *args = NULL, *result; + + if (iterable != NULL) { + args = PyTuple_Pack(1, iterable); + if (args == NULL) + return NULL; + } + result = frozenset_new(&PyFrozenSet_Type, args, NULL); + Py_DECREF(args); + return result; +} + +int +PySet_Contains(PyObject *anyset, PyObject *key) +{ + if (!PyAnySet_Check(anyset)) { + PyErr_BadInternalCall(); + return -1; + } + return set_contains_key((PySetObject *)anyset, key); +} + +int +PySet_Discard(PyObject *anyset, PyObject *key) +{ + if (!PyAnySet_Check(anyset)) { + PyErr_BadInternalCall(); + return -1; + } + return set_discard_key((PySetObject *)anyset, key); +} + +int +PySet_Add(PyObject *set, PyObject *key) +{ + if (!PyType_IsSubtype(set->ob_type, &PySet_Type)) { + PyErr_BadInternalCall(); + return -1; + } + return set_add_key((PySetObject *)set, key); +} + +PyObject * +PySet_Pop(PyObject *set) +{ + if (!PyType_IsSubtype(set->ob_type, &PySet_Type)) { + PyErr_BadInternalCall(); + return NULL; + } + return set_pop((PySetObject *)set); +} diff --git a/Python/marshal.c b/Python/marshal.c index 3eb7b1ec7ae..20d637d2b2c 100644 --- a/Python/marshal.c +++ b/Python/marshal.c @@ -773,11 +773,9 @@ r_object(RFILE *p) if (v == NULL) return v; if (type == TYPE_SET) - v3 = PyObject_CallFunctionObjArgs( - (PyObject *)&PySet_Type, v, NULL); + v3 = PySet_New(v); else - v3 = PyObject_CallFunctionObjArgs( - (PyObject *)&PyFrozenSet_Type, v, NULL); + v3 = PyFrozenSet_New(v); Py_DECREF(v); return v3;