Skip to content

Commit

Permalink
added set, delete and setdefault to frozendict
Browse files Browse the repository at this point in the history
  • Loading branch information
Marco-Sulla committed Feb 1, 2022
1 parent a52bae8 commit 14b9118
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 66 deletions.
51 changes: 25 additions & 26 deletions frozendict/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,31 @@ def __reduce__(self, *args, **kwargs):

return (self.__class__, (dict(self), ))

def set(self, key, val):
new_self = deepcopy(dict(self))
new_self[key] = val

return self.__class__(new_self)

def setdefault(self, key, default=None):
if key in self:
return self

new_self = deepcopy(dict(self))

new_self[key] = default

return self.__class__(new_self)

def delete(self, key):
new_self = deepcopy(dict(self))
del new_self[key]

if new_self:
return self.__class__(new_self)

return self.__class__()

def __setitem__(self, key, val, *args, **kwargs):
raise TypeError(
f"'{self.__class__.__name__}' object doesn't support item "
Expand Down Expand Up @@ -157,7 +182,6 @@ def frozendict_reversed(self, *args, **kwargs):
frozendict.clear = immutable
frozendict.pop = immutable
frozendict.popitem = immutable
frozendict.setdefault = immutable
frozendict.update = immutable
frozendict.__delattr__ = immutable
frozendict.__setattr__ = immutable
Expand Down Expand Up @@ -191,31 +215,6 @@ def checkPosition(obj, index):
return None

class coold(frozendict):
def set(self, key, val):
new_self = deepcopy(dict(self))
new_self[key] = val

return self.__class__(new_self)

def delete(self, key):
new_self = deepcopy(dict(self))
del new_self[key]

if new_self:
return self.__class__(new_self)

return self.__class__()

def setdefault(self, key, default=None):
new_self = deepcopy(dict(self))

if key in self:
return self.__class__(new_self)

new_self[key] = default

return self.__class__(new_self)

def __getitem__(self, key, *args, **kwargs):
try:
start = key.start
Expand Down
116 changes: 102 additions & 14 deletions frozendict/src/3_10/frozendictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,10 @@ static PyObject* frozendict_set(
PyObject* const* args,
Py_ssize_t nargs
) {
if (!_PyArg_CheckPositional("set", nargs, 2, 2)) {
return NULL;
}

PyObject* new_op = frozendict_clone(self);

if (new_op == NULL) {
Expand All @@ -967,6 +971,62 @@ static PyObject* frozendict_set(
return new_op;
}

static PyObject* frozendict_setdefault(
PyObject* self,
PyObject* const* args,
Py_ssize_t nargs
) {
if (!_PyArg_CheckPositional("setdefault", nargs, 1, 2)) {
return NULL;
}

PyObject* set_key = args[0];

if (PyDict_Contains(self, set_key)) {
Py_INCREF(self);
return self;
}

PyObject* new_op = frozendict_clone(self);

if (new_op == NULL) {
return NULL;
}

PyObject* val;

int decref_val;

if (nargs == 2) {
val = args[1];
decref_val = 0;
}
else {
val = Py_None;
Py_INCREF(val);
decref_val = 1;
}

if (frozendict_setitem(new_op, set_key, val, 0)) {
Py_DECREF(new_op);

if (decref_val) {
Py_DECREF(val);
}

return NULL;
}

if (
((PyDictObject*) self)->ma_keys->dk_lookup == lookdict_unicode_nodummy &&
! PyUnicode_CheckExact(set_key)
) {
((PyFrozenDictObject*) new_op)->ma_keys->dk_lookup = lookdict;
}

return new_op;
}

static PyObject* frozendict_del(PyObject* self,
PyObject *const *args,
Py_ssize_t nargs) {
Expand All @@ -987,6 +1047,19 @@ static PyObject* frozendict_del(PyObject* self,
return NULL;
}

const Py_ssize_t size = mp->ma_used;
const Py_ssize_t sizemm = size - 1;

if (sizemm == 0) {
PyObject* args = PyTuple_New(0);

if (args == NULL) {
return NULL;
}

return PyObject_Call((PyObject*) Py_TYPE(self), args, NULL);
}

PyTypeObject* type = Py_TYPE(self);
PyObject* new_op = type->tp_alloc(type, 0);

Expand All @@ -998,8 +1071,6 @@ static PyObject* frozendict_del(PyObject* self,
PyObject_GC_UnTrack(new_op);
}

const Py_ssize_t size = mp->ma_used;
const Py_ssize_t sizemm = size - 1;
const Py_ssize_t newsize = estimate_keysize(sizemm);

if (newsize <= 0) {
Expand Down Expand Up @@ -1074,6 +1145,26 @@ static PyObject* frozendict_del(PyObject* self,
return new_op;
}

PyDoc_STRVAR(frozendict_set_doc,
"set($self, key, value, /)\n"
"--\n"
"\n"
"Returns a copy of the dictionary with the new (key, value) item. ");

PyDoc_STRVAR(frozendict_setdefault_doc,
"set($self, key[, default], /)\n"
"--\n"
"\n"
"If key is in the dictionary, it returns the dictionary unchanged. \n"
"Otherwise, it returns a copy of the dictionary with the new (key, default) item; \n"
"default argument is optional and is None by default. ");

PyDoc_STRVAR(frozendict_del_doc,
"del($self, key, /)\n"
"--\n"
"\n"
"Returns a copy of the dictionary without the item of the corresponding key. ");

static PyMethodDef frozendict_mapp_methods[] = {
DICT___CONTAINS___METHODDEF
{"__getitem__", (PyCFunction)(void(*)(void))dict_subscript, METH_O | METH_COEXIST,
Expand All @@ -1099,21 +1190,18 @@ static PyMethodDef frozendict_mapp_methods[] = {
{"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, "See PEP 585"},
{"__reduce__", (PyCFunction)(void(*)(void))frozendict_reduce, METH_NOARGS,
""},
{"set", (PyCFunction)(void(*)(void))
frozendict_set, METH_FASTCALL,
frozendict_set_doc},
{"setdefault", (PyCFunction)(void(*)(void))
frozendict_setdefault, METH_FASTCALL,
frozendict_setdefault_doc},
{"delete", (PyCFunction)(void(*)(void))
frozendict_del, METH_FASTCALL,
frozendict_del_doc},
{NULL, NULL} /* sentinel */
};

PyDoc_STRVAR(frozendict_set_doc,
"set($self, key, value, /)\n"
"--\n"
"\n"
"Returns a copy of the dictionary with the new (key, value) item. ");

PyDoc_STRVAR(frozendict_del_doc,
"del($self, key, /)\n"
"--\n"
"\n"
"Returns a copy of the dictionary without the item of the corresponding key. ");

static PyMethodDef coold_mapp_methods[] = {
DICT___CONTAINS___METHODDEF
{"__getitem__", (PyCFunction)(void(*)(void))dict_subscript, METH_O | METH_COEXIST,
Expand Down
37 changes: 33 additions & 4 deletions test/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,39 @@ def test_reversed_items(fd, fd_dict):
def test_reversed_values(fd, fd_dict):
assert(tuple(reversed(fd.values())) == tuple(reversed(tuple(fd_dict.values()))))

def test_set_replace(fd_dict, generator_seq2):
items = tuple(generator_seq2)
d2 = dict(items)
assert fd_dict != d2
fd2 = frozendict_class(items)
fd3 = fd2.set("Sulla", "Marco")
assert fd3 == fd_dict

def test_set_add(fd_dict):
d2 = dict(fd_dict, a="b")
assert fd_dict != d2
fd2 = frozendict_class(fd_dict)
fd3 = fd2.set("a", "b")
assert fd3 == d2

def test_setdefault_notinsert(fd, fd_dict):
assert fd.setdefault("Hicks") is fd

def test_setdefault_insert_default(fd, fd_dict):
fd_dict.setdefault("Allen")
assert fd_dict == fd.setdefault("Allen")

def test_setdefault_insert(fd, fd_dict):
fd_dict.setdefault("Allen", "Woody")
assert fd_dict == fd.setdefault("Allen", "Woody")

def test_del(fd_dict):
d2 = dict(fd_dict)
d2["a"] = "b"
fd2 = frozendict_class(d2)
fd3 = fd2.delete("a")
assert fd3 == fd_dict

##############################################################################
# frozendict-only tests

Expand Down Expand Up @@ -423,10 +456,6 @@ def test_popitem(fd):
with pytest.raises(AttributeError):
fd.popitem()

def test_setdefault(fd):
with pytest.raises(AttributeError):
fd.setdefault("Sulla")

def test_update(fd):
with pytest.raises(AttributeError):
fd.update({"God": "exists"})
Expand Down
3 changes: 3 additions & 0 deletions test/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,9 @@ def print_sep():
"c1.set(key_in, 1000)",
"c1.set(key_notin, 1000)",
"c1.delete(key_in)",
"c1.setdefault(key_in)",
"c1.setdefault(key_notin)",
"c1.setdefault(key_notin, 1000)",
)

for cooold_class in (coold, C):
Expand Down
4 changes: 4 additions & 0 deletions test/frozendict_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ def test_init(fd):
fd.__init__({"Trump": "Donald"})
assert fd_copy is fd
assert fd_clone == fd

def test_del_empty():
fd = frozendict_class({1: 2})
assert fd.delete(1) is frozendict_class()
7 changes: 7 additions & 0 deletions test/subclass_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ def test_init_sub(fd):
assert fd_clone == fd
assert fd_copy is not fd

def test_del_empty_sub():
fd = frozendict_class({1: 2})
fd2 = fd.delete(1)
fd_empty = frozendict_class()
assert fd2 == fd_empty
assert fd2 is not fd_empty

def test_missing(fd):
fd_missing = FMissing(fd)
assert fd_missing[0] == 0
22 changes: 0 additions & 22 deletions test/test_coold.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,3 @@
frozendict_only_code = f.read()

exec(frozendict_only_code)

def test_coold_set_replace(fd_dict, generator_seq2):
items = tuple(generator_seq2)
d2 = dict(items)
assert fd_dict != d2
fd2 = frozendict_class(items)
fd3 = fd2.set("Sulla", "Marco")
assert fd3 == fd_dict

def test_coold_set_add(fd_dict):
d2 = dict(fd_dict, a="b")
assert fd_dict != d2
fd2 = frozendict_class(fd_dict)
fd3 = fd2.set("a", "b")
assert fd3 == d2

def test_coold_del(fd_dict):
d2 = dict(fd_dict)
d2["a"] = "b"
fd2 = frozendict_class(d2)
fd3 = fd2.delete("a")
assert fd3 == fd_dict

0 comments on commit 14b9118

Please sign in to comment.