Skip to content

Faster Sequence Check, Exception Check, and sort method #666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/666.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added faster exception check for frozenlist, faster sequence check and new sort method -- by ``@Vizonex``.
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ Marcin Konowalczyk
Martijn Pieters
Pau Freixes
Paul Colomiets
Vizonex
4 changes: 4 additions & 0 deletions frozenlist/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ def frozen(self):
def freeze(self):
self._frozen = True

def sort(self, key: object = None, reverse: bool = False):
self._check_frozen()
self._items.sort(key=key, reverse=reverse)

def __getitem__(self, index):
return self._items[index]

Expand Down
3 changes: 3 additions & 0 deletions frozenlist/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from typing import (
Callable,
Generic,
Iterable,
Iterator,
List,
MutableSequence,
Optional,
Protocol,
TypeVar,
Union,
overload,
Expand Down Expand Up @@ -42,6 +44,7 @@ class FrozenList(MutableSequence[_T], Generic[_T]):
def insert(self, pos: int, item: _T) -> None: ...
def __repr__(self) -> str: ...
def __hash__(self) -> int: ...
def sort(self, *, key: object = None, reverse: bool = False) -> None: ...

# types for C accelerators are the same
CFrozenList = PyFrozenList = FrozenList
43 changes: 27 additions & 16 deletions frozenlist/_frozenlist.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
# distutils: language = c++

from cpython.bool cimport PyBool_FromLong
from cpython.exc cimport PyErr_SetObject
from cpython.list cimport PyList_Append, PyList_GET_SIZE, PyList_New
from cpython.long cimport PyLong_FromSsize_t
from cpython.object cimport PyObject_GetIter
from cpython.sequence cimport PySequence_Contains, PySequence_Count, PySequence_List
from libcpp.atomic cimport atomic

import copy
Expand All @@ -15,30 +20,32 @@ cdef class FrozenList:
cdef atomic[bint] _frozen
cdef list _items

def __init__(self, items=None):
def __init__(self, object items=None):
self._frozen.store(False)
if items is not None:
items = list(items)
items = PySequence_List(items)
else:
items = []
items = PyList_New(0)
self._items = items

@property
def frozen(self):
return PyBool_FromLong(self._frozen.load())

cdef object _check_frozen(self):
cdef int _check_frozen(self) except -1:
if self._frozen.load():
raise RuntimeError("Cannot modify frozen list.")
PyErr_SetObject(RuntimeError, "Cannot modify frozen list.")
return -1
return 0

cdef inline object _fast_len(self):
return len(self._items)
cdef inline Py_ssize_t _fast_len(self):
return PyList_GET_SIZE(self._items)

def freeze(self):
self._frozen.store(True)

def __getitem__(self, index):
return self._items[index]
return self._items.__getitem__(index)

def __setitem__(self, index, value):
self._check_frozen()
Expand All @@ -49,10 +56,10 @@ cdef class FrozenList:
del self._items[index]

def __len__(self):
return self._fast_len()
return PyLong_FromSsize_t(self._fast_len())

def __iter__(self):
return self._items.__iter__()
return PyObject_GetIter(self._items)

def __reversed__(self):
return self._items.__reversed__()
Expand All @@ -76,13 +83,17 @@ cdef class FrozenList:
self._items.insert(pos, item)

def __contains__(self, item):
return item in self._items
return PySequence_Contains(self._items, item)

def __iadd__(self, items):
self._check_frozen()
self._items += list(items)
self._items.extend(items)
return self

def sort(self, object key = None, bint reverse = False):
self._check_frozen()
self._items.sort(key, reverse)

def index(self, item):
return self._items.index(item)

Expand All @@ -96,7 +107,7 @@ cdef class FrozenList:

def extend(self, items):
self._check_frozen()
self._items += list(items)
self._items.extend(items)

def reverse(self):
self._check_frozen()
Expand All @@ -108,10 +119,10 @@ cdef class FrozenList:

def append(self, item):
self._check_frozen()
return self._items.append(item)
PyList_Append(self._items, item)

def count(self, item):
return self._items.count(item)
return PySequence_Count(self._items, item)

def __repr__(self):
return '<FrozenList(frozen={}, {!r})>'.format(self._frozen.load(),
Expand All @@ -128,7 +139,7 @@ cdef class FrozenList:
obj_id = id(self)

# Return existing copy if already processed (circular reference)
if obj_id in memo:
if PySequence_Contains(memo, obj_id):
return memo[obj_id]

# Create new instance and register immediately
Expand Down
9 changes: 9 additions & 0 deletions tests/test_frozenlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,15 @@ def test_reverse_frozen(self) -> None:
_list.reverse()
assert _list == [1, 2]

def test_sort(self) -> None:
_list = self.FrozenList([3, 1, 4, 2])
_list.sort()
assert _list == [1, 2, 3, 4]
_list.freeze()
# ensure we can't sort when frozen
with pytest.raises(RuntimeError):
_list.sort()

def test_pop(self) -> None:
_list = self.FrozenList([1, 2])
assert _list.pop(0) == 1
Expand Down
Loading