diff options
author | Stefan Behnel <stefan_ml@behnel.de> | 2014-02-14 16:13:17 +0100 |
---|---|---|
committer | Stefan Behnel <stefan_ml@behnel.de> | 2014-02-14 16:13:17 +0100 |
commit | 3b5438eb91a8829fef662717a04f704ebfcbb68e (patch) | |
tree | 89be6e7defef27bab08b5b7d88b09a6c9413cd83 | |
parent | d195f25a159e0b405269e8a86a515e082d96331a (diff) | |
download | cython-3b5438eb91a8829fef662717a04f704ebfcbb68e.tar.gz |
restore singleton property of empty frozenset
-rw-r--r-- | Cython/Compiler/Optimize.py | 8 | ||||
-rw-r--r-- | Cython/Utility/Builtins.c | 25 | ||||
-rw-r--r-- | tests/run/set.pyx | 33 |
3 files changed, 63 insertions, 3 deletions
diff --git a/Cython/Compiler/Optimize.py b/Cython/Compiler/Optimize.py index e11778963..089bd91a6 100644 --- a/Cython/Compiler/Optimize.py +++ b/Cython/Compiler/Optimize.py @@ -2027,15 +2027,17 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): ]) def _handle_simple_function_frozenset(self, node, function, pos_args): - if len(pos_args) != 1: + if not pos_args: + pos_args = [ExprNodes.NullNode(node.pos)] + elif len(pos_args) > 1: return node # PyFrozenSet_New(it) is better than a generic Python call to frozenset(it) return ExprNodes.PythonCapiCallNode( - node.pos, "PyFrozenSet_New", + node.pos, "__Pyx_PyFrozenSet_New", self.PyFrozenSet_New_func_type, args=pos_args, is_temp=node.is_temp, - utility_code=UtilityCode.load_cached('pyset_compat', 'Builtins.c'), + utility_code=UtilityCode.load_cached('pyfrozenset_new', 'Builtins.c'), py_name="frozenset") PyObject_AsDouble_func_type = PyrexTypes.CFuncType( diff --git a/Cython/Utility/Builtins.c b/Cython/Utility/Builtins.c index cb46cf1e8..244ca42e9 100644 --- a/Cython/Utility/Builtins.c +++ b/Cython/Utility/Builtins.c @@ -388,6 +388,9 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewItems(PyObject* d) { #define PySet_Size(anyset) \ PyObject_Size((anyset)) +#define PySet_GET_SIZE(anyset) \ + PyObject_Size((anyset)) + #define PySet_Contains(anyset, key) \ PySequence_Contains((anyset), (key)) @@ -414,3 +417,25 @@ static CYTHON_INLINE int PySet_Add(PyObject *set, PyObject *key) { #endif /* PyAnySet_CheckExact (<= Py2.4) */ #endif /* < Py2.5 */ + +//////////////////// pyfrozenset_new.proto //////////////////// +//@substitute: naming +//@requires: pyset_compat + +static CYTHON_INLINE PyObject* __Pyx_PyFrozenSet_New(PyObject* it) { + if (it) { + PyObject* result = PyFrozenSet_New(it); + if (unlikely(!result)) + return NULL; + if (likely(PySet_GET_SIZE(result))) + return result; + // empty frozenset is a singleton + // seems wasteful, but CPython does the same + Py_DECREF(result); + } + #if CYTHON_COMPILING_IN_CPYTHON + return PyFrozenSet_Type.tp_new(&PyFrozenSet_Type, $empty_tuple, NULL); + #else + return PyObject_Call((PyObject*)&PyFrozenSet_Type, $empty_tuple, NULL); + #endif +} diff --git a/tests/run/set.pyx b/tests/run/set.pyx index 25e39d315..c7d9376ef 100644 --- a/tests/run/set.pyx +++ b/tests/run/set.pyx @@ -5,6 +5,8 @@ _frozenset = frozenset cimport cython +import sys + def cython_set(): """ @@ -284,6 +286,37 @@ def test_frozenset_of_iterable(x): return frozenset(x) +@cython.test_assert_path_exists("//PythonCapiCallNode") +@cython.test_fail_if_path_exists( + "//SimpleCallNode", + "//SetNode" +) +def test_empty_frozenset(): + """ + >>> s = test_empty_frozenset() + >>> isinstance(s, _frozenset) + True + >>> len(s) + 0 + >>> sys.version_info < (2,5) or s is frozenset() # singleton! + True + """ + return frozenset() + + +def test_singleton_empty_frozenset(): + """ + >>> test_singleton_empty_frozenset() # from CPython's test_set.py + 1 + """ + f = frozenset() + efs = [frozenset(), frozenset([]), frozenset(()), frozenset(''), + frozenset(), frozenset([]), frozenset(()), frozenset(''), + frozenset(range(0)), frozenset(frozenset()), + frozenset(f), f] + return len(set(map(id, efs))) if sys.version_info >= (2,5) else 1 + + def sorted(it): # Py3 can't compare different types chars = [] |