├── test ├── __init__.py ├── test_frozendict.py ├── run_type_checker.py ├── test_frozendict_subclass.py ├── test_monkeypatch.py ├── frozendict_only.py ├── typed.py ├── subclass_only.py ├── base.py ├── test_freeze.py ├── bench.py ├── common.py └── debug.py ├── src └── frozendict │ ├── py.typed │ ├── version.py │ ├── core.py │ ├── c_src │ ├── 3_10 │ │ ├── Include │ │ │ ├── cpython │ │ │ │ └── frozendictobject.h │ │ │ └── frozendictobject.h │ │ └── cpython_src │ │ │ ├── other.c │ │ │ └── Objects │ │ │ ├── stringlib │ │ │ └── eq.h │ │ │ ├── dict-common.h │ │ │ └── clinic │ │ │ └── dictobject.c.h │ ├── 3_6 │ │ ├── Include │ │ │ ├── cpython │ │ │ │ └── frozendictobject.h │ │ │ └── frozendictobject.h │ │ └── cpython_src │ │ │ ├── Objects │ │ │ ├── stringlib │ │ │ │ └── eq.h │ │ │ ├── clinic │ │ │ │ └── dictobject.c.h │ │ │ └── dict-common.h │ │ │ └── other.c │ ├── 3_7 │ │ ├── Include │ │ │ ├── cpython │ │ │ │ └── frozendictobject.h │ │ │ └── frozendictobject.h │ │ └── cpython_src │ │ │ ├── other.c │ │ │ └── Objects │ │ │ ├── stringlib │ │ │ └── eq.h │ │ │ ├── dict-common.h │ │ │ └── clinic │ │ │ └── dictobject.c.h │ ├── 3_8 │ │ ├── Include │ │ │ ├── cpython │ │ │ │ └── frozendictobject.h │ │ │ └── frozendictobject.h │ │ └── cpython_src │ │ │ ├── other.c │ │ │ └── Objects │ │ │ ├── stringlib │ │ │ └── eq.h │ │ │ ├── dict-common.h │ │ │ └── clinic │ │ │ └── dictobject.c.h │ └── 3_9 │ │ ├── Include │ │ ├── cpython │ │ │ └── frozendictobject.h │ │ └── frozendictobject.h │ │ └── cpython_src │ │ ├── other.c │ │ └── Objects │ │ ├── stringlib │ │ └── eq.h │ │ ├── dict-common.h │ │ └── clinic │ │ └── dictobject.c.h │ ├── __init__.py │ ├── __init__.pyi │ ├── monkeypatch.py │ ├── _frozendict_py.py │ └── cool.py ├── mytest.bat ├── mytest.sh ├── MANIFEST.in ├── todo ├── todo.txt └── future.txt ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── build_primary_wheels.yml │ └── build_secondary_wheels.yml ├── setup.py ├── LICENSE.txt └── README.md /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/frozendict/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mytest.bat: -------------------------------------------------------------------------------- 1 | python test/debug.py && pytest 2 | -------------------------------------------------------------------------------- /src/frozendict/version.py: -------------------------------------------------------------------------------- 1 | version = "2.4.7" 2 | -------------------------------------------------------------------------------- /mytest.sh: -------------------------------------------------------------------------------- 1 | rm test/core.* 2 | 3 | ./test/debug.py && pytest 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include src/frozendict/c_src * 2 | include test/* 3 | include src/frozendict/py.typed 4 | include src/frozendict/__init__.pyi 5 | include pyproject.toml 6 | -------------------------------------------------------------------------------- /todo/todo.txt: -------------------------------------------------------------------------------- 1 | pydict_global_version -> _pydict_global_version 2 | calculate_keysize -> calculate_log2_keysize 3 | estimate_keysize -> estimate_log2_keysize 4 | _Py_dict_lookup -> _d_Py_dict_lookup 5 | 6 | insertion with grow? 7 | restore lookdict_nodummy? 8 | -------------------------------------------------------------------------------- /src/frozendict/core.py: -------------------------------------------------------------------------------- 1 | # this provides compatibility for older pickles 2 | # created on a python-only implementation that 3 | # specifically mention frozendict.core.frozendict 4 | 5 | # noinspection PyUnresolvedReferences 6 | from frozendict import frozendict 7 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_10/Include/cpython/frozendictobject.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_CPYTHON_FROZENDICTOBJECT_H 2 | # error "this header file must not be included directly" 3 | #endif 4 | 5 | typedef struct { 6 | PyObject_HEAD 7 | 8 | Py_ssize_t ma_used; 9 | uint64_t ma_version_tag; 10 | PyDictKeysObject* ma_keys; 11 | PyObject** ma_values; 12 | 13 | Py_hash_t ma_hash; 14 | } PyFrozenDictObject; 15 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_6/Include/cpython/frozendictobject.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_CPYTHON_FROZENDICTOBJECT_H 2 | # error "this header file must not be included directly" 3 | #endif 4 | 5 | typedef struct { 6 | PyObject_HEAD 7 | 8 | Py_ssize_t ma_used; 9 | uint64_t ma_version_tag; 10 | PyDictKeysObject* ma_keys; 11 | PyObject** ma_values; 12 | 13 | Py_hash_t ma_hash; 14 | } PyFrozenDictObject; 15 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_7/Include/cpython/frozendictobject.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_CPYTHON_FROZENDICTOBJECT_H 2 | # error "this header file must not be included directly" 3 | #endif 4 | 5 | typedef struct { 6 | PyObject_HEAD 7 | 8 | Py_ssize_t ma_used; 9 | uint64_t ma_version_tag; 10 | PyDictKeysObject* ma_keys; 11 | PyObject** ma_values; 12 | 13 | Py_hash_t ma_hash; 14 | } PyFrozenDictObject; 15 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_8/Include/cpython/frozendictobject.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_CPYTHON_FROZENDICTOBJECT_H 2 | # error "this header file must not be included directly" 3 | #endif 4 | 5 | typedef struct { 6 | PyObject_HEAD 7 | 8 | Py_ssize_t ma_used; 9 | uint64_t ma_version_tag; 10 | PyDictKeysObject* ma_keys; 11 | PyObject** ma_values; 12 | 13 | Py_hash_t ma_hash; 14 | } PyFrozenDictObject; 15 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_9/Include/cpython/frozendictobject.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_CPYTHON_FROZENDICTOBJECT_H 2 | # error "this header file must not be included directly" 3 | #endif 4 | 5 | typedef struct { 6 | PyObject_HEAD 7 | 8 | Py_ssize_t ma_used; 9 | uint64_t ma_version_tag; 10 | PyDictKeysObject* ma_keys; 11 | PyObject** ma_values; 12 | 13 | Py_hash_t ma_hash; 14 | } PyFrozenDictObject; 15 | -------------------------------------------------------------------------------- /test/test_frozendict.py: -------------------------------------------------------------------------------- 1 | import frozendict as cool 2 | from frozendict import frozendict as FrozendictClass 3 | 4 | from .common import FrozendictCommonTest 5 | from .frozendict_only import FrozendictOnlyTest 6 | 7 | is_subclass = False 8 | 9 | 10 | class TestFrozendict(FrozendictCommonTest, FrozendictOnlyTest): 11 | FrozendictClass = FrozendictClass 12 | c_ext = cool.c_ext 13 | is_subclass = is_subclass 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled stuff 2 | *.pyc 3 | *.pyo 4 | doc/_build 5 | build 6 | *.so 7 | 8 | # Crap created by version control 9 | .orig 10 | .rej 11 | 12 | # Files created by distutils 13 | MANIFEST 14 | dist 15 | *.egg-info 16 | .cache 17 | 18 | # OS X 19 | .DS_Store 20 | 21 | # Other *nix:es 22 | .~ 23 | 24 | # ? 25 | index-*.html 26 | 27 | # Custom 28 | venv* 29 | Pipfile 30 | test/core.* 31 | .eggs/ 32 | .idea/ 33 | .vs/ 34 | .coverage 35 | coverage/ 36 | CMakeLists.txt 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] " 5 | labels: "bug" 6 | assignees: "" 7 | 8 | --- 9 | Welcome! You should write in your Bug Report: 10 | 11 | OS version (https://www.google.com/search?channel=fs&q=check+os+version&ie=utf-8&oe=utf-8): 12 | your OS version 13 | 14 | 15 | Python3 version (python3 -V -V): 16 | your Python version 17 | 18 | 19 | Steps to reproduce: 20 | 21 | 1. 22 | 23 | 24 | Actual result (with the python stack trace if present): 25 | the result you see 26 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_7/cpython_src/other.c: -------------------------------------------------------------------------------- 1 | static const unsigned int BitLengthTable[32] = { 2 | 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 3 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 4 | }; 5 | 6 | unsigned int _Py_bit_length(unsigned long d) { 7 | unsigned int d_bits = 0; 8 | while (d >= 32) { 9 | d_bits += 6; 10 | d >>= 6; 11 | } 12 | d_bits += BitLengthTable[d]; 13 | return d_bits; 14 | } 15 | 16 | #define Py_IS_TYPE(op, type) (Py_TYPE(op) == type) 17 | 18 | #define PySet_CheckExact(op) Py_IS_TYPE(op, &PySet_Type) 19 | 20 | -------------------------------------------------------------------------------- /test/run_type_checker.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | from pathlib import Path 4 | 5 | py_ver_major = sys.version_info.major 6 | py_ver_minor = sys.version_info.minor 7 | 8 | if py_ver_major == 3 and py_ver_minor < 8: 9 | print( 10 | f"Python is version {py_ver_major}.{py_ver_minor}, not " 11 | "running type checker" 12 | ) 13 | 14 | sys.exit(0) 15 | 16 | curr_file = Path(__file__) 17 | curr_dir = curr_file.parent 18 | 19 | subprocess.run( 20 | "mypy typed.py", 21 | shell=True, 22 | check=True, 23 | cwd=str(curr_dir) 24 | ) 25 | -------------------------------------------------------------------------------- /todo/future.txt: -------------------------------------------------------------------------------- 1 | union 2 | intersection 3 | difference 4 | symmetric_difference(other) 5 | set ^ other 6 | 7 | Return a new set with elements in either the set or other but not both 8 | 9 | 10 | issubset(other) 11 | set <= other 12 | 13 | Test whether every element in the set is in other. 14 | 15 | set < other 16 | 17 | Test whether the set is a proper subset of other, that is, set <= other and set != other. 18 | 19 | issuperset(other) 20 | set >= other 21 | 22 | Test whether every element in other is in the set. 23 | 24 | set > other 25 | 26 | Test whether the set is a proper superset of other, that is, set >= other and set != other. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE] " 5 | labels: "enhancement" 6 | assignees: "" 7 | 8 | --- 9 | 10 | Welcome! Before you write your Feature Request, please read below: 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | A clear and concise description of what the problem is. Ex. I'm always 14 | frustrated when [...] 15 | 16 | **Describe the solution you'd like** 17 | A clear and concise description of what you want to happen. 18 | 19 | **Describe alternatives you've considered** 20 | A clear and concise description of any alternative solutions or 21 | features you've considered. 22 | 23 | **Additional context** 24 | Add any other context or screenshots about the feature request here. 25 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_10/cpython_src/other.c: -------------------------------------------------------------------------------- 1 | static const unsigned int BitLengthTable[32] = { 2 | 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 3 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 4 | }; 5 | 6 | unsigned int _Py_bit_length(unsigned long d) { 7 | unsigned int d_bits = 0; 8 | while (d >= 32) { 9 | d_bits += 6; 10 | d >>= 6; 11 | } 12 | d_bits += BitLengthTable[d]; 13 | return d_bits; 14 | } 15 | 16 | typedef struct { 17 | uintptr_t _gc_next; 18 | uintptr_t _gc_prev; 19 | } PyGC_Head; 20 | 21 | #define _Py_AS_GC(o) ((PyGC_Head *)(o)-1) 22 | #define _PyObject_GC_IS_TRACKED(o) (_Py_AS_GC(o)->_gc_next != 0) 23 | 24 | #define _PyObject_GC_MAY_BE_TRACKED(obj) \ 25 | (PyObject_IS_GC(obj) && \ 26 | (!PyTuple_CheckExact(obj) || _PyObject_GC_IS_TRACKED(obj))) 27 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_8/cpython_src/other.c: -------------------------------------------------------------------------------- 1 | static const unsigned int BitLengthTable[32] = { 2 | 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 3 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 4 | }; 5 | 6 | unsigned int _Py_bit_length(unsigned long d) { 7 | unsigned int d_bits = 0; 8 | while (d >= 32) { 9 | d_bits += 6; 10 | d >>= 6; 11 | } 12 | d_bits += BitLengthTable[d]; 13 | return d_bits; 14 | } 15 | 16 | #define _Py_AS_GC(o) ((PyGC_Head *)(o)-1) 17 | #define _PyObject_GC_IS_TRACKED(o) (_Py_AS_GC(o)->_gc_next != 0) 18 | 19 | #define _PyObject_GC_MAY_BE_TRACKED(obj) \ 20 | (PyObject_IS_GC(obj) && \ 21 | (!PyTuple_CheckExact(obj) || _PyObject_GC_IS_TRACKED(obj))) 22 | 23 | #define Py_IS_TYPE(op, type) (Py_TYPE(op) == type) 24 | 25 | #define PySet_CheckExact(op) Py_IS_TYPE(op, &PySet_Type) 26 | 27 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_9/cpython_src/other.c: -------------------------------------------------------------------------------- 1 | static const unsigned int BitLengthTable[32] = { 2 | 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 3 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 4 | }; 5 | 6 | unsigned int _Py_bit_length(unsigned long d) { 7 | unsigned int d_bits = 0; 8 | while (d >= 32) { 9 | d_bits += 6; 10 | d >>= 6; 11 | } 12 | d_bits += BitLengthTable[d]; 13 | return d_bits; 14 | } 15 | 16 | typedef struct { 17 | uintptr_t _gc_next; 18 | uintptr_t _gc_prev; 19 | } PyGC_Head; 20 | 21 | #define _Py_AS_GC(o) ((PyGC_Head *)(o)-1) 22 | #define _PyObject_GC_IS_TRACKED(o) (_Py_AS_GC(o)->_gc_next != 0) 23 | 24 | #define _PyObject_GC_MAY_BE_TRACKED(obj) \ 25 | (PyObject_IS_GC(obj) && \ 26 | (!PyTuple_CheckExact(obj) || _PyObject_GC_IS_TRACKED(obj))) 27 | 28 | #define PySet_CheckExact(op) Py_IS_TYPE(op, &PySet_Type) 29 | 30 | -------------------------------------------------------------------------------- /test/test_frozendict_subclass.py: -------------------------------------------------------------------------------- 1 | import frozendict as cool 2 | from frozendict import frozendict as FrozendictClass 3 | 4 | from .common import FrozendictCommonTest 5 | from .subclass_only import FrozendictSubclassOnlyTest 6 | 7 | is_subclass = True 8 | 9 | 10 | class FrozendictSubclass(FrozendictClass): 11 | def __new__(cls, *args, **kwargs): 12 | return super().__new__(cls, *args, **kwargs) 13 | 14 | 15 | class FrozendictMissingSubclass(FrozendictClass): 16 | def __new__(cls, *args, **kwargs): 17 | return super().__new__(cls, *args, **kwargs) 18 | 19 | def __missing__(self, key): 20 | return key 21 | 22 | 23 | class TestFrozendictSubclass( 24 | FrozendictCommonTest, 25 | FrozendictSubclassOnlyTest 26 | ): 27 | FrozendictClass = FrozendictSubclass 28 | FrozendictMissingClass = FrozendictMissingSubclass 29 | c_ext = cool.c_ext 30 | is_subclass = is_subclass 31 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_9/cpython_src/Objects/stringlib/eq.h: -------------------------------------------------------------------------------- 1 | /* Fast unicode equal function optimized for dictobject.c and setobject.c */ 2 | 3 | /* Return 1 if two unicode objects are equal, 0 if not. 4 | * unicode_eq() is called when the hash of two unicode objects is equal. 5 | */ 6 | Py_LOCAL_INLINE(int) 7 | unicode_eq(PyObject *aa, PyObject *bb) 8 | { 9 | assert(PyUnicode_Check(aa)); 10 | assert(PyUnicode_Check(bb)); 11 | assert(PyUnicode_IS_READY(aa)); 12 | assert(PyUnicode_IS_READY(bb)); 13 | 14 | PyUnicodeObject *a = (PyUnicodeObject *)aa; 15 | PyUnicodeObject *b = (PyUnicodeObject *)bb; 16 | 17 | if (PyUnicode_GET_LENGTH(a) != PyUnicode_GET_LENGTH(b)) 18 | return 0; 19 | if (PyUnicode_GET_LENGTH(a) == 0) 20 | return 1; 21 | if (PyUnicode_KIND(a) != PyUnicode_KIND(b)) 22 | return 0; 23 | return memcmp(PyUnicode_1BYTE_DATA(a), PyUnicode_1BYTE_DATA(b), 24 | PyUnicode_GET_LENGTH(a) * PyUnicode_KIND(a)) == 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_10/cpython_src/Objects/stringlib/eq.h: -------------------------------------------------------------------------------- 1 | /* Fast unicode equal function optimized for dictobject.c and setobject.c */ 2 | 3 | /* Return 1 if two unicode objects are equal, 0 if not. 4 | * unicode_eq() is called when the hash of two unicode objects is equal. 5 | */ 6 | Py_LOCAL_INLINE(int) 7 | unicode_eq(PyObject *aa, PyObject *bb) 8 | { 9 | assert(PyUnicode_Check(aa)); 10 | assert(PyUnicode_Check(bb)); 11 | assert(PyUnicode_IS_READY(aa)); 12 | assert(PyUnicode_IS_READY(bb)); 13 | 14 | PyUnicodeObject *a = (PyUnicodeObject *)aa; 15 | PyUnicodeObject *b = (PyUnicodeObject *)bb; 16 | 17 | if (PyUnicode_GET_LENGTH(a) != PyUnicode_GET_LENGTH(b)) 18 | return 0; 19 | if (PyUnicode_GET_LENGTH(a) == 0) 20 | return 1; 21 | if (PyUnicode_KIND(a) != PyUnicode_KIND(b)) 22 | return 0; 23 | return memcmp(PyUnicode_1BYTE_DATA(a), PyUnicode_1BYTE_DATA(b), 24 | PyUnicode_GET_LENGTH(a) * PyUnicode_KIND(a)) == 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_6/cpython_src/Objects/stringlib/eq.h: -------------------------------------------------------------------------------- 1 | /* Fast unicode equal function optimized for dictobject.c and setobject.c */ 2 | 3 | /* Return 1 if two unicode objects are equal, 0 if not. 4 | * unicode_eq() is called when the hash of two unicode objects is equal. 5 | */ 6 | Py_LOCAL_INLINE(int) 7 | unicode_eq(PyObject *aa, PyObject *bb) 8 | { 9 | assert(PyUnicode_Check(aa)); 10 | assert(PyUnicode_Check(bb)); 11 | assert(! PyUnicode_READY(aa)); 12 | assert(! PyUnicode_READY(bb)); 13 | 14 | PyUnicodeObject *a = (PyUnicodeObject *)aa; 15 | PyUnicodeObject *b = (PyUnicodeObject *)bb; 16 | 17 | if (PyUnicode_GET_LENGTH(a) != PyUnicode_GET_LENGTH(b)) 18 | return 0; 19 | if (PyUnicode_GET_LENGTH(a) == 0) 20 | return 1; 21 | if (PyUnicode_KIND(a) != PyUnicode_KIND(b)) 22 | return 0; 23 | return memcmp(PyUnicode_1BYTE_DATA(a), PyUnicode_1BYTE_DATA(b), 24 | PyUnicode_GET_LENGTH(a) * PyUnicode_KIND(a)) == 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_7/cpython_src/Objects/stringlib/eq.h: -------------------------------------------------------------------------------- 1 | /* Fast unicode equal function optimized for dictobject.c and setobject.c */ 2 | 3 | /* Return 1 if two unicode objects are equal, 0 if not. 4 | * unicode_eq() is called when the hash of two unicode objects is equal. 5 | */ 6 | Py_LOCAL_INLINE(int) 7 | unicode_eq(PyObject *aa, PyObject *bb) 8 | { 9 | assert(PyUnicode_Check(aa)); 10 | assert(PyUnicode_Check(bb)); 11 | assert(! PyUnicode_READY(aa)); 12 | assert(! PyUnicode_READY(bb)); 13 | 14 | PyUnicodeObject *a = (PyUnicodeObject *)aa; 15 | PyUnicodeObject *b = (PyUnicodeObject *)bb; 16 | 17 | if (PyUnicode_GET_LENGTH(a) != PyUnicode_GET_LENGTH(b)) 18 | return 0; 19 | if (PyUnicode_GET_LENGTH(a) == 0) 20 | return 1; 21 | if (PyUnicode_KIND(a) != PyUnicode_KIND(b)) 22 | return 0; 23 | return memcmp(PyUnicode_1BYTE_DATA(a), PyUnicode_1BYTE_DATA(b), 24 | PyUnicode_GET_LENGTH(a) * PyUnicode_KIND(a)) == 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_8/cpython_src/Objects/stringlib/eq.h: -------------------------------------------------------------------------------- 1 | /* Fast unicode equal function optimized for dictobject.c and setobject.c */ 2 | 3 | /* Return 1 if two unicode objects are equal, 0 if not. 4 | * unicode_eq() is called when the hash of two unicode objects is equal. 5 | */ 6 | Py_LOCAL_INLINE(int) 7 | unicode_eq(PyObject *aa, PyObject *bb) 8 | { 9 | assert(PyUnicode_Check(aa)); 10 | assert(PyUnicode_Check(bb)); 11 | assert(! PyUnicode_READY(aa)); 12 | assert(! PyUnicode_READY(bb)); 13 | 14 | PyUnicodeObject *a = (PyUnicodeObject *)aa; 15 | PyUnicodeObject *b = (PyUnicodeObject *)bb; 16 | 17 | if (PyUnicode_GET_LENGTH(a) != PyUnicode_GET_LENGTH(b)) 18 | return 0; 19 | if (PyUnicode_GET_LENGTH(a) == 0) 20 | return 1; 21 | if (PyUnicode_KIND(a) != PyUnicode_KIND(b)) 22 | return 0; 23 | return memcmp(PyUnicode_1BYTE_DATA(a), PyUnicode_1BYTE_DATA(b), 24 | PyUnicode_GET_LENGTH(a) * PyUnicode_KIND(a)) == 0; 25 | } 26 | -------------------------------------------------------------------------------- /test/test_monkeypatch.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import frozendict as cool 4 | import pytest 5 | from frozendict import frozendict 6 | from frozendict.monkeypatch import MonkeypatchWarning 7 | 8 | 9 | class A: 10 | pass 11 | 12 | 13 | @pytest.fixture 14 | def object_to_serialize(): 15 | return frozendict() 16 | 17 | 18 | @pytest.fixture 19 | def object_to_serialize_2(): 20 | return A() 21 | 22 | 23 | @pytest.fixture 24 | def serialized_object(): 25 | return "{}" 26 | 27 | 28 | def test_get_json_encoder( 29 | object_to_serialize, 30 | object_to_serialize_2, 31 | serialized_object, 32 | ): 33 | if cool.c_ext: 34 | cool.monkeypatch.patchOrUnpatchJson(patch=True) 35 | else: 36 | with pytest.warns(MonkeypatchWarning): 37 | cool.monkeypatch.patchOrUnpatchJson(patch=True, warn=True) 38 | 39 | assert json.dumps(object_to_serialize) == serialized_object 40 | 41 | with pytest.raises(TypeError): 42 | json.dumps(object_to_serialize_2) 43 | 44 | cool.monkeypatch.patchOrUnpatchJson(patch=False, warn=False) 45 | 46 | if cool.c_ext: 47 | with pytest.raises(TypeError): 48 | json.dumps(object_to_serialize) 49 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_6/cpython_src/Objects/clinic/dictobject.c.h: -------------------------------------------------------------------------------- 1 | /*[clinic input] 2 | preserve 3 | [clinic start generated code]*/ 4 | 5 | PyDoc_STRVAR(dict_fromkeys__doc__, 6 | "fromkeys($type, iterable, value=None, /)\n" 7 | "--\n" 8 | "\n" 9 | "Create a new dictionary with keys from iterable and values set to value."); 10 | 11 | #define DICT_FROMKEYS_METHODDEF \ 12 | {"fromkeys", (PyCFunction)(void(*)(void))dict_fromkeys, METH_FASTCALL|METH_CLASS, dict_fromkeys__doc__}, 13 | 14 | PyDoc_STRVAR(dict___contains____doc__, 15 | "__contains__($self, key, /)\n" 16 | "--\n" 17 | "\n" 18 | "True if the dictionary has the specified key, else False."); 19 | 20 | #define DICT___CONTAINS___METHODDEF \ 21 | {"__contains__", (PyCFunction)dict___contains__, METH_O|METH_COEXIST, dict___contains____doc__}, 22 | 23 | PyDoc_STRVAR(dict_get__doc__, 24 | "get($self, key, default=None, /)\n" 25 | "--\n" 26 | "\n" 27 | "Return the value for key if key is in the dictionary, else default."); 28 | 29 | #define DICT_GET_METHODDEF \ 30 | {"get", (PyCFunction)dict_get, METH_VARARGS,dict_get__doc__}, 31 | 32 | PyDoc_STRVAR(dict___reversed____doc__, 33 | "__reversed__($self, /)\n" 34 | "--\n" 35 | "\n" 36 | "Return a reverse iterator over the dict keys."); 37 | 38 | #define DICT___REVERSED___METHODDEF \ 39 | {"__reversed__", (PyCFunction)dict___reversed__, METH_NOARGS, dict___reversed____doc__}, 40 | 41 | static PyObject * 42 | dict___reversed___impl(PyDictObject *self); 43 | 44 | static PyObject * 45 | dict___reversed__(PyDictObject *self, PyObject *Py_UNUSED(ignored)) 46 | { 47 | return dict___reversed___impl(self); 48 | } 49 | /*[clinic end generated code: output=7b77c16e43d6735a input=a9049054013a1b77]*/ 50 | -------------------------------------------------------------------------------- /src/frozendict/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Provides frozendict, a simple immutable dictionary. 3 | """ 4 | 5 | try: # pragma: no cover 6 | from ._frozendict import * 7 | c_ext = True 8 | # noinspection PyUnresolvedReferences 9 | del _frozendict 10 | except ImportError: 11 | from ._frozendict_py import * 12 | c_ext = False 13 | 14 | from . import cool 15 | from . import monkeypatch 16 | from .cool import * 17 | from .version import version as __version__ 18 | 19 | 20 | def _getFrozendictJsonEncoder(BaseJsonEncoder = None): 21 | if BaseJsonEncoder is None: # pragma: no cover 22 | from json.encoder import JSONEncoder 23 | 24 | BaseJsonEncoder = JSONEncoder 25 | 26 | class FrozendictJsonEncoderInternal(BaseJsonEncoder): 27 | def default(self, obj): 28 | if isinstance(obj, frozendict): # pragma: no cover 29 | # TODO create a C serializer 30 | return dict(obj) 31 | 32 | return BaseJsonEncoder.default( 33 | self, 34 | obj 35 | ) # pragma: no cover 36 | 37 | return FrozendictJsonEncoderInternal 38 | 39 | 40 | FrozendictJsonEncoder = _getFrozendictJsonEncoder() 41 | monkeypatch.patchOrUnpatchAll(patch = True, warn = False) 42 | 43 | 44 | from collections.abc import Mapping 45 | 46 | # noinspection PyUnresolvedReferences 47 | Mapping.register(frozendict) 48 | del Mapping 49 | 50 | 51 | if c_ext: # pragma: no cover 52 | __all__ = (frozendict.__name__, ) 53 | else: 54 | __all__ = _frozendict_py.__all__ 55 | del _frozendict_py 56 | 57 | # TODO deprecated, to remove in future versions 58 | FrozenOrderedDict = frozendict 59 | 60 | __all__ += cool.__all__ 61 | __all__ += (FrozendictJsonEncoder.__name__, "FrozenOrderedDict") 62 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_6/cpython_src/other.c: -------------------------------------------------------------------------------- 1 | static const unsigned int BitLengthTable[32] = { 2 | 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 3 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 4 | }; 5 | 6 | unsigned int _Py_bit_length(unsigned long d) { 7 | unsigned int d_bits = 0; 8 | while (d >= 32) { 9 | d_bits += 6; 10 | d >>= 6; 11 | } 12 | d_bits += BitLengthTable[d]; 13 | return d_bits; 14 | } 15 | 16 | #define Py_IS_TYPE(op, type) (Py_TYPE(op) == type) 17 | 18 | #define PySet_CheckExact(op) Py_IS_TYPE(op, &PySet_Type) 19 | 20 | #if defined(RANDALL_WAS_HERE) 21 | # define Py_UNREACHABLE() \ 22 | Py_FatalError( \ 23 | "If you're seeing this, the code is in what I thought was\n" \ 24 | "an unreachable state.\n\n" \ 25 | "I could give you advice for what to do, but honestly, why\n" \ 26 | "should you trust me? I clearly screwed this up. I'm writing\n" \ 27 | "a message that should never appear, yet I know it will\n" \ 28 | "probably appear someday.\n\n" \ 29 | "On a deep level, I know I'm not up to this task.\n" \ 30 | "I'm so sorry.\n" \ 31 | "https://xkcd.com/2200") 32 | #elif defined(Py_DEBUG) 33 | # define Py_UNREACHABLE() \ 34 | Py_FatalError( \ 35 | "We've reached an unreachable state. Anything is possible.\n" \ 36 | "The limits were in our heads all along. Follow your dreams.\n" \ 37 | "https://xkcd.com/2200") 38 | #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) 39 | # define Py_UNREACHABLE() __builtin_unreachable() 40 | #elif defined(__clang__) || defined(__INTEL_COMPILER) 41 | # define Py_UNREACHABLE() __builtin_unreachable() 42 | #elif defined(_MSC_VER) 43 | # define Py_UNREACHABLE() __assume(0) 44 | #else 45 | # define Py_UNREACHABLE() \ 46 | Py_FatalError("Unreachable C code path reached") 47 | #endif 48 | 49 | #if defined(__GNUC__) \ 50 | && ((__GNUC__ >= 5) || (__GNUC__ == 4) && (__GNUC_MINOR__ >= 3)) 51 | #define _Py_HOT_FUNCTION __attribute__((hot)) 52 | #else 53 | #define _Py_HOT_FUNCTION 54 | #endif 55 | 56 | -------------------------------------------------------------------------------- /test/frozendict_only.py: -------------------------------------------------------------------------------- 1 | import io 2 | import pickle 3 | from copy import copy, deepcopy 4 | 5 | import pytest 6 | 7 | from .base import FrozendictTestBase 8 | 9 | 10 | # noinspection PyMethodMayBeStatic 11 | class FrozendictOnlyTest(FrozendictTestBase): 12 | def test_empty(self, fd_empty): 13 | fd_empty_set = self.FrozendictClass({}) 14 | fd_empty_list = self.FrozendictClass([]) 15 | fd_empty_dict = self.FrozendictClass({}, **{}) 16 | 17 | assert fd_empty is fd_empty_set is fd_empty_list 18 | assert fd_empty is fd_empty_dict 19 | 20 | def test_constructor_self_1(self, fd): 21 | assert fd is self.FrozendictClass(fd) 22 | 23 | def test_vars(self, fd): 24 | with pytest.raises(TypeError): 25 | vars(fd) 26 | 27 | def test_setattr(self, fd): 28 | with pytest.raises(AttributeError): 29 | fd._initialized = False 30 | 31 | def test_copy(self, fd): 32 | # noinspection PyTestUnpassedFixture 33 | assert fd.copy() is fd 34 | 35 | def test_copycopy(self, fd, fd_unhashable): 36 | assert copy(fd) is fd 37 | assert copy(fd_unhashable) is fd_unhashable 38 | 39 | def test_deepcopy(self, fd): 40 | assert deepcopy(fd) is fd 41 | 42 | def test_init(self, fd): 43 | # noinspection PyTestUnpassedFixture 44 | fd_copy = fd.copy() 45 | fd_clone = self.FrozendictClass(dict(fd)) 46 | fd.__init__({"Trump": "Donald"}) 47 | assert fd_copy is fd 48 | assert fd_clone == fd 49 | 50 | def test_del_empty(self): 51 | f = self.FrozendictClass({1: 2}) 52 | assert f.delete(1) is self.FrozendictClass() 53 | 54 | def test_pickle_core(self, fd): 55 | class CustomUnpickler(pickle.Unpickler): 56 | def find_class(self, module, name): 57 | assert module == 'frozendict' 58 | assert name == 'frozendict' 59 | return super().find_class('frozendict.core', name) 60 | 61 | dump = pickle.dumps(fd) 62 | assert dump 63 | assert CustomUnpickler(io.BytesIO(dump)).load() == fd 64 | -------------------------------------------------------------------------------- /test/typed.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import copy 4 | from collections.abc import Hashable 5 | from typing import ( 6 | ItemsView, 7 | Iterator, 8 | KeysView, 9 | Mapping, 10 | ValuesView, 11 | Union, 12 | Tuple, 13 | ) 14 | 15 | from frozendict import frozendict 16 | from typing_extensions import assert_type, Never 17 | 18 | fd = frozendict(a=1, b=2) 19 | 20 | assert_type(frozendict(), frozendict[Never, Never]) 21 | assert_type(fd, frozendict[str, int]) 22 | assert_type(fd["a"], int) 23 | assert_type(iter(fd), Iterator[str]) 24 | assert_type(frozendict.fromkeys("abc", 0), frozendict[str, int]) 25 | assert_type(fd.copy(), frozendict[str, int]) 26 | assert_type(fd.item(0), Tuple[str, int]) 27 | assert_type(fd.item(), Tuple[str, int]) 28 | assert_type(fd.key(0), str) 29 | assert_type(fd.key(), str) 30 | assert_type(fd.value(0), int) 31 | assert_type(fd.value(), int) 32 | assert_type(fd.get("a"), Union[int, None]) 33 | assert_type(fd.items(), ItemsView[str, int]) 34 | assert_type(fd.keys(), KeysView[str]) 35 | assert_type(fd.values(), ValuesView[int]) 36 | assert_type(copy.copy(fd), frozendict[str, int]) 37 | assert_type(copy.deepcopy(fd), frozendict[str, int]) 38 | # noinspection PyUnresolvedReferences 39 | assert_type(reversed(fd), reversed[str]) 40 | assert_type(fd.delete("a"), frozendict[str, int]) 41 | 42 | assert_type(fd | {"c": 2}, frozendict[str, int]) 43 | assert_type(fd | {1: 2}, frozendict[Union[str, int], int]) 44 | assert_type(fd | {"c": "c"}, frozendict[str, Union[str, int]]) 45 | assert_type(fd | {1: "c"}, frozendict[Union[str, int], Union[str, int]]) 46 | 47 | assert_type(fd.setdefault("a", 0), frozendict[str, int]) 48 | assert_type(fd.setdefault("a", "a"), frozendict[str, Union[str, int]]) 49 | assert_type(fd.setdefault(0, 0), frozendict[Union[str, int], int]) 50 | 51 | assert_type( 52 | fd.setdefault(0, "a"), 53 | frozendict[Union[str, int], Union[str, int]] 54 | ) 55 | 56 | assert_type(fd.set("abc", 1), frozendict[str, int]) 57 | 58 | assert_type( 59 | fd.set("abc", "abc"), 60 | frozendict[str, Union[str, int]] 61 | ) 62 | 63 | assert_type(fd.set(1, 1), frozendict[Union[str, int], int]) 64 | 65 | assert_type( 66 | fd.set(1, "abc"), 67 | frozendict[Union[str, int], Union[str, int]] 68 | ) 69 | 70 | # frozendict implements Mapping[K, V] and is covariant in V 71 | vals: frozendict[str, Hashable] = fd 72 | mapping: Mapping[str, Hashable] = fd 73 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_10/cpython_src/Objects/dict-common.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_DICT_COMMON_H 2 | #define Py_DICT_COMMON_H 3 | 4 | typedef struct { 5 | /* Cached hash code of me_key. */ 6 | Py_hash_t me_hash; 7 | PyObject *me_key; 8 | PyObject *me_value; /* This field is only meaningful for combined tables */ 9 | } PyDictKeyEntry; 10 | 11 | /* dict_lookup_func() returns index of entry which can be used like DK_ENTRIES(dk)[index]. 12 | * -1 when no entry found, -3 when compare raises error. 13 | */ 14 | typedef Py_ssize_t (*dict_lookup_func) 15 | (PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr); 16 | 17 | #define DKIX_EMPTY (-1) 18 | #define DKIX_DUMMY (-2) /* Used internally */ 19 | #define DKIX_ERROR (-3) 20 | 21 | /* See dictobject.c for actual layout of DictKeysObject */ 22 | struct _dictkeysobject { 23 | Py_ssize_t dk_refcnt; 24 | 25 | /* Size of the hash table (dk_indices). It must be a power of 2. */ 26 | Py_ssize_t dk_size; 27 | 28 | /* Function to lookup in the hash table (dk_indices): 29 | 30 | - lookdict(): general-purpose, and may return DKIX_ERROR if (and 31 | only if) a comparison raises an exception. 32 | 33 | - lookdict_unicode(): specialized to Unicode string keys, comparison of 34 | which can never raise an exception; that function can never return 35 | DKIX_ERROR. 36 | 37 | - lookdict_unicode_nodummy(): similar to lookdict_unicode() but further 38 | specialized for Unicode string keys that cannot be the value. 39 | 40 | - lookdict_split(): Version of lookdict() for split tables. */ 41 | dict_lookup_func dk_lookup; 42 | 43 | /* Number of usable entries in dk_entries. */ 44 | Py_ssize_t dk_usable; 45 | 46 | /* Number of used entries in dk_entries. */ 47 | Py_ssize_t dk_nentries; 48 | 49 | /* Actual hash table of dk_size entries. It holds indices in dk_entries, 50 | or DKIX_EMPTY(-1) or DKIX_DUMMY(-2). 51 | 52 | Indices must be: 0 <= indice < USABLE_FRACTION(dk_size). 53 | 54 | The size in bytes of an indice depends on dk_size: 55 | 56 | - 1 byte if dk_size <= 0xff (char*) 57 | - 2 bytes if dk_size <= 0xffff (int16_t*) 58 | - 4 bytes if dk_size <= 0xffffffff (int32_t*) 59 | - 8 bytes otherwise (int64_t*) 60 | 61 | Dynamically sized, SIZEOF_VOID_P is minimum. */ 62 | char dk_indices[]; /* char is required to avoid strict aliasing. */ 63 | 64 | /* "PyDictKeyEntry dk_entries[dk_usable];" array follows: 65 | see the DK_ENTRIES() macro */ 66 | }; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_7/cpython_src/Objects/dict-common.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_DICT_COMMON_H 2 | #define Py_DICT_COMMON_H 3 | 4 | typedef struct { 5 | /* Cached hash code of me_key. */ 6 | Py_hash_t me_hash; 7 | PyObject *me_key; 8 | PyObject *me_value; /* This field is only meaningful for combined tables */ 9 | } PyDictKeyEntry; 10 | 11 | /* dict_lookup_func() returns index of entry which can be used like DK_ENTRIES(dk)[index]. 12 | * -1 when no entry found, -3 when compare raises error. 13 | */ 14 | typedef Py_ssize_t (*dict_lookup_func) 15 | (PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr); 16 | 17 | #define DKIX_EMPTY (-1) 18 | #define DKIX_DUMMY (-2) /* Used internally */ 19 | #define DKIX_ERROR (-3) 20 | 21 | /* See dictobject.c for actual layout of DictKeysObject */ 22 | struct _dictkeysobject { 23 | Py_ssize_t dk_refcnt; 24 | 25 | /* Size of the hash table (dk_indices). It must be a power of 2. */ 26 | Py_ssize_t dk_size; 27 | 28 | /* Function to lookup in the hash table (dk_indices): 29 | 30 | - lookdict(): general-purpose, and may return DKIX_ERROR if (and 31 | only if) a comparison raises an exception. 32 | 33 | - lookdict_unicode(): specialized to Unicode string keys, comparison of 34 | which can never raise an exception; that function can never return 35 | DKIX_ERROR. 36 | 37 | - lookdict_unicode_nodummy(): similar to lookdict_unicode() but further 38 | specialized for Unicode string keys that cannot be the value. 39 | 40 | - lookdict_split(): Version of lookdict() for split tables. */ 41 | dict_lookup_func dk_lookup; 42 | 43 | /* Number of usable entries in dk_entries. */ 44 | Py_ssize_t dk_usable; 45 | 46 | /* Number of used entries in dk_entries. */ 47 | Py_ssize_t dk_nentries; 48 | 49 | /* Actual hash table of dk_size entries. It holds indices in dk_entries, 50 | or DKIX_EMPTY(-1) or DKIX_DUMMY(-2). 51 | 52 | Indices must be: 0 <= indice < USABLE_FRACTION(dk_size). 53 | 54 | The size in bytes of an indice depends on dk_size: 55 | 56 | - 1 byte if dk_size <= 0xff (char*) 57 | - 2 bytes if dk_size <= 0xffff (int16_t*) 58 | - 4 bytes if dk_size <= 0xffffffff (int32_t*) 59 | - 8 bytes otherwise (int64_t*) 60 | 61 | Dynamically sized, SIZEOF_VOID_P is minimum. */ 62 | char dk_indices[]; /* char is required to avoid strict aliasing. */ 63 | 64 | /* "PyDictKeyEntry dk_entries[dk_usable];" array follows: 65 | see the DK_ENTRIES() macro */ 66 | }; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_8/cpython_src/Objects/dict-common.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_DICT_COMMON_H 2 | #define Py_DICT_COMMON_H 3 | 4 | typedef struct { 5 | /* Cached hash code of me_key. */ 6 | Py_hash_t me_hash; 7 | PyObject *me_key; 8 | PyObject *me_value; /* This field is only meaningful for combined tables */ 9 | } PyDictKeyEntry; 10 | 11 | /* dict_lookup_func() returns index of entry which can be used like DK_ENTRIES(dk)[index]. 12 | * -1 when no entry found, -3 when compare raises error. 13 | */ 14 | typedef Py_ssize_t (*dict_lookup_func) 15 | (PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr); 16 | 17 | #define DKIX_EMPTY (-1) 18 | #define DKIX_DUMMY (-2) /* Used internally */ 19 | #define DKIX_ERROR (-3) 20 | 21 | /* See dictobject.c for actual layout of DictKeysObject */ 22 | struct _dictkeysobject { 23 | Py_ssize_t dk_refcnt; 24 | 25 | /* Size of the hash table (dk_indices). It must be a power of 2. */ 26 | Py_ssize_t dk_size; 27 | 28 | /* Function to lookup in the hash table (dk_indices): 29 | 30 | - lookdict(): general-purpose, and may return DKIX_ERROR if (and 31 | only if) a comparison raises an exception. 32 | 33 | - lookdict_unicode(): specialized to Unicode string keys, comparison of 34 | which can never raise an exception; that function can never return 35 | DKIX_ERROR. 36 | 37 | - lookdict_unicode_nodummy(): similar to lookdict_unicode() but further 38 | specialized for Unicode string keys that cannot be the value. 39 | 40 | - lookdict_split(): Version of lookdict() for split tables. */ 41 | dict_lookup_func dk_lookup; 42 | 43 | /* Number of usable entries in dk_entries. */ 44 | Py_ssize_t dk_usable; 45 | 46 | /* Number of used entries in dk_entries. */ 47 | Py_ssize_t dk_nentries; 48 | 49 | /* Actual hash table of dk_size entries. It holds indices in dk_entries, 50 | or DKIX_EMPTY(-1) or DKIX_DUMMY(-2). 51 | 52 | Indices must be: 0 <= indice < USABLE_FRACTION(dk_size). 53 | 54 | The size in bytes of an indice depends on dk_size: 55 | 56 | - 1 byte if dk_size <= 0xff (char*) 57 | - 2 bytes if dk_size <= 0xffff (int16_t*) 58 | - 4 bytes if dk_size <= 0xffffffff (int32_t*) 59 | - 8 bytes otherwise (int64_t*) 60 | 61 | Dynamically sized, SIZEOF_VOID_P is minimum. */ 62 | char dk_indices[]; /* char is required to avoid strict aliasing. */ 63 | 64 | /* "PyDictKeyEntry dk_entries[dk_usable];" array follows: 65 | see the DK_ENTRIES() macro */ 66 | }; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_9/cpython_src/Objects/dict-common.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_DICT_COMMON_H 2 | #define Py_DICT_COMMON_H 3 | 4 | typedef struct { 5 | /* Cached hash code of me_key. */ 6 | Py_hash_t me_hash; 7 | PyObject *me_key; 8 | PyObject *me_value; /* This field is only meaningful for combined tables */ 9 | } PyDictKeyEntry; 10 | 11 | /* dict_lookup_func() returns index of entry which can be used like DK_ENTRIES(dk)[index]. 12 | * -1 when no entry found, -3 when compare raises error. 13 | */ 14 | typedef Py_ssize_t (*dict_lookup_func) 15 | (PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr); 16 | 17 | #define DKIX_EMPTY (-1) 18 | #define DKIX_DUMMY (-2) /* Used internally */ 19 | #define DKIX_ERROR (-3) 20 | 21 | /* See dictobject.c for actual layout of DictKeysObject */ 22 | struct _dictkeysobject { 23 | Py_ssize_t dk_refcnt; 24 | 25 | /* Size of the hash table (dk_indices). It must be a power of 2. */ 26 | Py_ssize_t dk_size; 27 | 28 | /* Function to lookup in the hash table (dk_indices): 29 | 30 | - lookdict(): general-purpose, and may return DKIX_ERROR if (and 31 | only if) a comparison raises an exception. 32 | 33 | - lookdict_unicode(): specialized to Unicode string keys, comparison of 34 | which can never raise an exception; that function can never return 35 | DKIX_ERROR. 36 | 37 | - lookdict_unicode_nodummy(): similar to lookdict_unicode() but further 38 | specialized for Unicode string keys that cannot be the value. 39 | 40 | - lookdict_split(): Version of lookdict() for split tables. */ 41 | dict_lookup_func dk_lookup; 42 | 43 | /* Number of usable entries in dk_entries. */ 44 | Py_ssize_t dk_usable; 45 | 46 | /* Number of used entries in dk_entries. */ 47 | Py_ssize_t dk_nentries; 48 | 49 | /* Actual hash table of dk_size entries. It holds indices in dk_entries, 50 | or DKIX_EMPTY(-1) or DKIX_DUMMY(-2). 51 | 52 | Indices must be: 0 <= indice < USABLE_FRACTION(dk_size). 53 | 54 | The size in bytes of an indice depends on dk_size: 55 | 56 | - 1 byte if dk_size <= 0xff (char*) 57 | - 2 bytes if dk_size <= 0xffff (int16_t*) 58 | - 4 bytes if dk_size <= 0xffffffff (int32_t*) 59 | - 8 bytes otherwise (int64_t*) 60 | 61 | Dynamically sized, SIZEOF_VOID_P is minimum. */ 62 | char dk_indices[]; /* char is required to avoid strict aliasing. */ 63 | 64 | /* "PyDictKeyEntry dk_entries[dk_usable];" array follows: 65 | see the DK_ENTRIES() macro */ 66 | }; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_6/cpython_src/Objects/dict-common.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_DICT_COMMON_H 2 | #define Py_DICT_COMMON_H 3 | 4 | typedef struct { 5 | /* Cached hash code of me_key. */ 6 | Py_hash_t me_hash; 7 | PyObject *me_key; 8 | PyObject *me_value; /* This field is only meaningful for combined tables */ 9 | } PyDictKeyEntry; 10 | 11 | /* dict_lookup_func() returns index of entry which can be used like DK_ENTRIES(dk)[index]. 12 | * -1 when no entry found, -3 when compare raises error. 13 | */ 14 | typedef Py_ssize_t (*dict_lookup_func) 15 | (PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject ***value_addr, 16 | Py_ssize_t *hashpos); 17 | 18 | #define DKIX_EMPTY (-1) 19 | #define DKIX_DUMMY (-2) /* Used internally */ 20 | #define DKIX_ERROR (-3) 21 | 22 | /* See dictobject.c for actual layout of DictKeysObject */ 23 | struct _dictkeysobject { 24 | Py_ssize_t dk_refcnt; 25 | 26 | /* Size of the hash table (dk_indices). It must be a power of 2. */ 27 | Py_ssize_t dk_size; 28 | 29 | /* Function to lookup in the hash table (dk_indices): 30 | 31 | - lookdict(): general-purpose, and may return DKIX_ERROR if (and 32 | only if) a comparison raises an exception. 33 | 34 | - lookdict_unicode(): specialized to Unicode string keys, comparison of 35 | which can never raise an exception; that function can never return 36 | DKIX_ERROR. 37 | 38 | - lookdict_unicode_nodummy(): similar to lookdict_unicode() but further 39 | specialized for Unicode string keys that cannot be the value. 40 | 41 | - lookdict_split(): Version of lookdict() for split tables. */ 42 | dict_lookup_func dk_lookup; 43 | 44 | /* Number of usable entries in dk_entries. */ 45 | Py_ssize_t dk_usable; 46 | 47 | /* Number of used entries in dk_entries. */ 48 | Py_ssize_t dk_nentries; 49 | 50 | /* Actual hash table of dk_size entries. It holds indices in dk_entries, 51 | or DKIX_EMPTY(-1) or DKIX_DUMMY(-2). 52 | 53 | Indices must be: 0 <= indice < USABLE_FRACTION(dk_size). 54 | 55 | The size in bytes of an indice depends on dk_size: 56 | 57 | - 1 byte if dk_size <= 0xff (char*) 58 | - 2 bytes if dk_size <= 0xffff (int16_t*) 59 | - 4 bytes if dk_size <= 0xffffffff (int32_t*) 60 | - 8 bytes otherwise (int64_t*) 61 | 62 | Dynamically sized, SIZEOF_VOID_P is minimum. */ 63 | char dk_indices[]; /* char is required to avoid strict aliasing. */ 64 | 65 | /* "PyDictKeyEntry dk_entries[dk_usable];" array follows: 66 | see the DK_ENTRIES() macro */ 67 | }; 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /.github/workflows/build_primary_wheels.yml: -------------------------------------------------------------------------------- 1 | name: Build primary wheels 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - v* 9 | pull_request: 10 | paths: 11 | - '**' 12 | workflow_dispatch: 13 | 14 | jobs: 15 | build_wheels: 16 | name: > 17 | Build wheels for py ${{ matrix.py }}, os ${{ matrix.cbw_os }}, 18 | arch ${{ matrix.arch }} 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | py: [310, 39, 38] 24 | os: [ubuntu-latest, windows-latest] 25 | include: 26 | - os: ubuntu-latest 27 | cbw_os: manylinux_ 28 | arch: x86_64 29 | - os: windows-latest 30 | cbw_os: win_ 31 | arch: amd64 32 | 33 | steps: 34 | - uses: actions/checkout@v3 35 | 36 | - name: Install dependencies 37 | uses: astral-sh/setup-uv@v7 38 | 39 | - name: Build wheels 40 | uses: pypa/cibuildwheel@v3.2.1 41 | env: 42 | CIBW_BUILD: > 43 | cp${{ matrix.py }}-${{ matrix.cbw_os }}${{ matrix.arch }} 44 | CIBW_ARCHS: native 45 | CIBW_BUILD_FRONTEND: build[uv] 46 | CIBW_CONTAINER_ENGINE: podman 47 | CIBW_TEST_REQUIRES: pytest typing_extensions mypy 48 | CIBW_TEST_COMMAND: > 49 | python -X faulthandler {package}/test/debug.py 50 | && python -X faulthandler -m pytest -p no:faulthandler 51 | -s {package} 52 | && python {package}/test/run_type_checker.py 53 | 54 | - uses: actions/upload-artifact@v4 55 | with: 56 | name: > 57 | primary_wheel_artifact_py${{ matrix.py }}_os_ 58 | ${{ matrix.cbw_os }}arch_${{ matrix.arch }} 59 | path: ./wheelhouse/*.whl 60 | 61 | build_pure_py: 62 | name: Build and test pure py wheel 63 | runs-on: ubuntu-latest 64 | 65 | steps: 66 | - uses: actions/checkout@v4 67 | 68 | - name: Build module 69 | run: FROZENDICT_PURE_PY=1 pipx run build --wheel 70 | 71 | - name: Install dependencies 72 | run: pip install -U pip pytest pytest-cov typing_extensions mypy 73 | 74 | - name: Install module 75 | run: pip install dist/* 76 | 77 | - name: Run tests 78 | run: > 79 | pytest --cov=frozendict --cov-report=term-missing --cov-branch 80 | --cov-fail-under=100 81 | && python test/run_type_checker.py 82 | 83 | - uses: actions/upload-artifact@v4 84 | with: 85 | name: pure_py_wheel_artifact 86 | path: ./dist/* 87 | -------------------------------------------------------------------------------- /test/subclass_only.py: -------------------------------------------------------------------------------- 1 | from copy import copy, deepcopy 2 | 3 | from .base import FrozendictTestBase 4 | 5 | 6 | # noinspection PyMethodMayBeStatic 7 | class FrozendictSubclassOnlyTest(FrozendictTestBase): 8 | _FrozendictMissingClass = None 9 | 10 | @property 11 | def FrozendictMissingClass(self): 12 | val = self._FrozendictMissingClass 13 | 14 | if val is None: 15 | raise ValueError("FrozendictMissingClass is None") 16 | 17 | return val 18 | 19 | @FrozendictMissingClass.setter 20 | def FrozendictMissingClass(self, val): 21 | self._FrozendictMissingClass = val 22 | 23 | #################################################################### 24 | # tests 25 | 26 | def test_empty_sub(self, fd_empty): 27 | fd_empty_2 = self.FrozendictClass({}) 28 | fd_empty_3 = self.FrozendictClass([]) 29 | fd_empty_4 = self.FrozendictClass({}, **{}) 30 | 31 | assert fd_empty == fd_empty_2 == fd_empty_3 == fd_empty_4 32 | assert fd_empty is not fd_empty_2 is not fd_empty_3 33 | assert fd_empty is not fd_empty_4 34 | 35 | def test_constructor_self_sub(self, fd): 36 | fd_clone = self.FrozendictClass(fd) 37 | assert fd == fd_clone 38 | assert fd is not fd_clone 39 | 40 | def test_copy_sub(self, fd): 41 | # noinspection PyTestUnpassedFixture 42 | fd_copy = fd.copy() 43 | assert fd_copy == fd 44 | assert fd_copy is not fd 45 | 46 | def test_copycopy_sub(self, fd, fd_unhashable): 47 | fd_copy = copy(fd) 48 | fd_copy_unhashable = copy(fd_unhashable) 49 | assert fd_copy == fd 50 | assert fd_copy_unhashable == fd_unhashable 51 | assert fd_copy is not fd 52 | assert fd_copy_unhashable is not fd_unhashable 53 | 54 | def test_deepcopy_sub(self, fd): 55 | fd_copy = deepcopy(fd) 56 | assert fd_copy == fd 57 | assert fd_copy is not fd 58 | 59 | def test_init_sub(self, fd): 60 | # noinspection PyTestUnpassedFixture 61 | fd_copy = fd.copy() 62 | fd_clone = self.FrozendictClass(dict(fd)) 63 | fd.__init__({"Trump": "Donald"}) 64 | assert fd_copy == fd 65 | assert fd_clone == fd 66 | assert fd_copy is not fd 67 | 68 | def test_del_empty_sub(self, fd_empty): 69 | f = self.FrozendictClass({1: 2}) 70 | f2 = f.delete(1) 71 | assert f2 == fd_empty 72 | assert f2 is not fd_empty 73 | 74 | def test_missing(self, fd): 75 | fd_missing = self.FrozendictMissingClass(fd) 76 | assert fd_missing[0] == 0 77 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_7/cpython_src/Objects/clinic/dictobject.c.h: -------------------------------------------------------------------------------- 1 | /*[clinic input] 2 | preserve 3 | [clinic start generated code]*/ 4 | 5 | PyDoc_STRVAR(dict_fromkeys__doc__, 6 | "fromkeys($type, iterable, value=None, /)\n" 7 | "--\n" 8 | "\n" 9 | "Create a new dictionary with keys from iterable and values set to value."); 10 | 11 | #define DICT_FROMKEYS_METHODDEF \ 12 | {"fromkeys", (PyCFunction)(void(*)(void))dict_fromkeys, METH_FASTCALL|METH_CLASS, dict_fromkeys__doc__}, 13 | 14 | static PyObject * 15 | frozendict_fromkeys_impl(PyTypeObject *type, PyObject *iterable, PyObject *value); 16 | 17 | static PyObject * 18 | dict_fromkeys(PyTypeObject *type, PyObject *const *args, Py_ssize_t nargs) 19 | { 20 | PyObject *return_value = NULL; 21 | PyObject *iterable; 22 | PyObject *value = Py_None; 23 | 24 | if (!_PyArg_UnpackStack(args, nargs, "fromkeys", 25 | 1, 2, 26 | &iterable, &value)) { 27 | goto exit; 28 | } 29 | return_value = frozendict_fromkeys_impl(type, iterable, value); 30 | 31 | exit: 32 | return return_value; 33 | } 34 | 35 | PyDoc_STRVAR(dict___contains____doc__, 36 | "__contains__($self, key, /)\n" 37 | "--\n" 38 | "\n" 39 | "True if the dictionary has the specified key, else False."); 40 | 41 | #define DICT___CONTAINS___METHODDEF \ 42 | {"__contains__", (PyCFunction)dict___contains__, METH_O|METH_COEXIST, dict___contains____doc__}, 43 | 44 | PyDoc_STRVAR(dict_get__doc__, 45 | "get($self, key, default=None, /)\n" 46 | "--\n" 47 | "\n" 48 | "Return the value for key if key is in the dictionary, else default."); 49 | 50 | #define DICT_GET_METHODDEF \ 51 | {"get", (PyCFunction)(void(*)(void))dict_get, METH_FASTCALL, dict_get__doc__}, 52 | 53 | static PyObject * 54 | dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value); 55 | 56 | static PyObject * 57 | dict_get(PyDictObject *self, PyObject *const *args, Py_ssize_t nargs) 58 | { 59 | PyObject *return_value = NULL; 60 | PyObject *key; 61 | PyObject *default_value = Py_None; 62 | 63 | if (!_PyArg_UnpackStack(args, nargs, "get", 64 | 1, 2, 65 | &key, &default_value)) { 66 | goto exit; 67 | } 68 | return_value = dict_get_impl(self, key, default_value); 69 | 70 | exit: 71 | return return_value; 72 | } 73 | 74 | PyDoc_STRVAR(dict___reversed____doc__, 75 | "__reversed__($self, /)\n" 76 | "--\n" 77 | "\n" 78 | "Return a reverse iterator over the dict keys."); 79 | 80 | #define DICT___REVERSED___METHODDEF \ 81 | {"__reversed__", (PyCFunction)dict___reversed__, METH_NOARGS, dict___reversed____doc__}, 82 | 83 | static PyObject * 84 | dict___reversed___impl(PyDictObject *self); 85 | 86 | static PyObject * 87 | dict___reversed__(PyDictObject *self, PyObject *Py_UNUSED(ignored)) 88 | { 89 | return dict___reversed___impl(self); 90 | } 91 | /*[clinic end generated code: output=7b77c16e43d6735a input=a9049054013a1b77]*/ 92 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_10/Include/frozendictobject.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_FROZENDICTOBJECT_H 2 | #define Py_FROZENDICTOBJECT_H 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | // PyAPI_DATA(PyTypeObject) PyFrozenDict_Type; 8 | static PyTypeObject PyFrozenDict_Type; 9 | // PyAPI_DATA(PyTypeObject) PyCoold_Type; 10 | static PyTypeObject PyCoold_Type; 11 | 12 | #define PyFrozenDict_Check(op) \ 13 | ( \ 14 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 15 | || PyType_IsSubtype(Py_TYPE(op), &PyFrozenDict_Type) \ 16 | ) 17 | 18 | #define PyFrozenDict_CheckExact(op) Py_IS_TYPE(op, &PyFrozenDict_Type) 19 | 20 | #define PyAnyFrozenDict_Check(op) \ 21 | ( \ 22 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 23 | || Py_IS_TYPE(op, &PyCoold_Type) \ 24 | || PyType_IsSubtype(Py_TYPE(op), &PyFrozenDict_Type) \ 25 | || PyType_IsSubtype(Py_TYPE(op), &PyCoold_Type) \ 26 | ) 27 | 28 | #define PyAnyFrozenDict_CheckExact(op) \ 29 | ( \ 30 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 31 | || Py_IS_TYPE(op, &PyCoold_Type) \ 32 | ) 33 | 34 | #define PyAnyDict_Check(ob) \ 35 | ( \ 36 | PyDict_Check(ob) \ 37 | || Py_IS_TYPE(ob, &PyFrozenDict_Type) \ 38 | || Py_IS_TYPE(ob, &PyCoold_Type) \ 39 | || PyType_IsSubtype(Py_TYPE(ob), &PyFrozenDict_Type) \ 40 | || PyType_IsSubtype(Py_TYPE(ob), &PyCoold_Type) \ 41 | ) 42 | 43 | #define PyAnyDict_CheckExact(op) ( \ 44 | (Py_IS_TYPE(op, &PyDict_Type)) \ 45 | || (Py_IS_TYPE(op, &PyFrozenDict_Type)) \ 46 | || (Py_IS_TYPE(op, &PyCoold_Type)) \ 47 | ) 48 | 49 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterKey_Type; 50 | static PyTypeObject PyFrozenDictIterKey_Type; 51 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterValues_Type; 52 | static PyTypeObject PyFrozenDictIterValue_Type; 53 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterItem_Type; 54 | static PyTypeObject PyFrozenDictIterItem_Type; 55 | 56 | // PyAPI_DATA(PyTypeObject) PyFrozenDictKeys_Type; 57 | static PyTypeObject PyFrozenDictKeys_Type; 58 | // PyAPI_DATA(PyTypeObject) PyFrozenDictValues_Type; 59 | static PyTypeObject PyFrozenDictValues_Type; 60 | // PyAPI_DATA(PyTypeObject) PyFrozenDictItems_Type; 61 | static PyTypeObject PyFrozenDictItems_Type; 62 | 63 | #define PyAnyDictKeys_Check(op) (PyDictKeys_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictKeys_Type)) 64 | #define PyAnyDictValues_Check(op) (PyDictValues_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictValues_Type)) 65 | #define PyAnyDictItems_Check(op) (PyDictItems_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictItems_Type)) 66 | /* This excludes Values, since they are not sets. */ 67 | # define PyAnyDictViewSet_Check(op) \ 68 | (PyAnyDictKeys_Check(op) || PyAnyDictItems_Check(op)) 69 | 70 | #ifndef Py_LIMITED_API 71 | # define Py_CPYTHON_FROZENDICTOBJECT_H 72 | # include "cpython/frozendictobject.h" 73 | # undef Py_CPYTHON_FROZENDICTOBJECT_H 74 | #endif 75 | 76 | #ifdef __cplusplus 77 | } 78 | #endif 79 | #endif 80 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_6/Include/frozendictobject.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_FROZENDICTOBJECT_H 2 | #define Py_FROZENDICTOBJECT_H 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | // PyAPI_DATA(PyTypeObject) PyFrozenDict_Type; 8 | static PyTypeObject PyFrozenDict_Type; 9 | // PyAPI_DATA(PyTypeObject) PyCoold_Type; 10 | static PyTypeObject PyCoold_Type; 11 | 12 | #define PyFrozenDict_Check(op) \ 13 | ( \ 14 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 15 | || PyType_IsSubtype(Py_TYPE(op), &PyFrozenDict_Type) \ 16 | ) 17 | 18 | #define PyFrozenDict_CheckExact(op) Py_IS_TYPE(op, &PyFrozenDict_Type) 19 | 20 | #define PyAnyFrozenDict_Check(op) \ 21 | ( \ 22 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 23 | || Py_IS_TYPE(op, &PyCoold_Type) \ 24 | || PyType_IsSubtype(Py_TYPE(op), &PyFrozenDict_Type) \ 25 | || PyType_IsSubtype(Py_TYPE(op), &PyCoold_Type) \ 26 | ) 27 | 28 | #define PyAnyFrozenDict_CheckExact(op) \ 29 | ( \ 30 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 31 | || Py_IS_TYPE(op, &PyCoold_Type) \ 32 | ) 33 | 34 | #define PyAnyDict_Check(ob) \ 35 | ( \ 36 | PyDict_Check(ob) \ 37 | || Py_IS_TYPE(ob, &PyFrozenDict_Type) \ 38 | || Py_IS_TYPE(ob, &PyCoold_Type) \ 39 | || PyType_IsSubtype(Py_TYPE(ob), &PyFrozenDict_Type) \ 40 | || PyType_IsSubtype(Py_TYPE(ob), &PyCoold_Type) \ 41 | ) 42 | 43 | #define PyAnyDict_CheckExact(op) ( \ 44 | (Py_IS_TYPE(op, &PyDict_Type)) \ 45 | || (Py_IS_TYPE(op, &PyFrozenDict_Type)) \ 46 | || (Py_IS_TYPE(op, &PyCoold_Type)) \ 47 | ) 48 | 49 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterKey_Type; 50 | static PyTypeObject PyFrozenDictIterKey_Type; 51 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterValues_Type; 52 | static PyTypeObject PyFrozenDictIterValue_Type; 53 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterItem_Type; 54 | static PyTypeObject PyFrozenDictIterItem_Type; 55 | 56 | // PyAPI_DATA(PyTypeObject) PyFrozenDictKeys_Type; 57 | static PyTypeObject PyFrozenDictKeys_Type; 58 | // PyAPI_DATA(PyTypeObject) PyFrozenDictValues_Type; 59 | static PyTypeObject PyFrozenDictValues_Type; 60 | // PyAPI_DATA(PyTypeObject) PyFrozenDictItems_Type; 61 | static PyTypeObject PyFrozenDictItems_Type; 62 | 63 | #define PyAnyDictKeys_Check(op) (PyDictKeys_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictKeys_Type)) 64 | #define PyAnyDictValues_Check(op) (PyDictValues_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictValues_Type)) 65 | #define PyAnyDictItems_Check(op) (PyDictItems_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictItems_Type)) 66 | /* This excludes Values, since they are not sets. */ 67 | # define PyAnyDictViewSet_Check(op) \ 68 | (PyAnyDictKeys_Check(op) || PyAnyDictItems_Check(op)) 69 | 70 | #ifndef Py_LIMITED_API 71 | # define Py_CPYTHON_FROZENDICTOBJECT_H 72 | # include "cpython/frozendictobject.h" 73 | # undef Py_CPYTHON_FROZENDICTOBJECT_H 74 | #endif 75 | 76 | #ifdef __cplusplus 77 | } 78 | #endif 79 | #endif 80 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_7/Include/frozendictobject.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_FROZENDICTOBJECT_H 2 | #define Py_FROZENDICTOBJECT_H 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | // PyAPI_DATA(PyTypeObject) PyFrozenDict_Type; 8 | static PyTypeObject PyFrozenDict_Type; 9 | // PyAPI_DATA(PyTypeObject) PyCoold_Type; 10 | static PyTypeObject PyCoold_Type; 11 | 12 | #define PyFrozenDict_Check(op) \ 13 | ( \ 14 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 15 | || PyType_IsSubtype(Py_TYPE(op), &PyFrozenDict_Type) \ 16 | ) 17 | 18 | #define PyFrozenDict_CheckExact(op) Py_IS_TYPE(op, &PyFrozenDict_Type) 19 | 20 | #define PyAnyFrozenDict_Check(op) \ 21 | ( \ 22 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 23 | || Py_IS_TYPE(op, &PyCoold_Type) \ 24 | || PyType_IsSubtype(Py_TYPE(op), &PyFrozenDict_Type) \ 25 | || PyType_IsSubtype(Py_TYPE(op), &PyCoold_Type) \ 26 | ) 27 | 28 | #define PyAnyFrozenDict_CheckExact(op) \ 29 | ( \ 30 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 31 | || Py_IS_TYPE(op, &PyCoold_Type) \ 32 | ) 33 | 34 | #define PyAnyDict_Check(ob) \ 35 | ( \ 36 | PyDict_Check(ob) \ 37 | || Py_IS_TYPE(ob, &PyFrozenDict_Type) \ 38 | || Py_IS_TYPE(ob, &PyCoold_Type) \ 39 | || PyType_IsSubtype(Py_TYPE(ob), &PyFrozenDict_Type) \ 40 | || PyType_IsSubtype(Py_TYPE(ob), &PyCoold_Type) \ 41 | ) 42 | 43 | #define PyAnyDict_CheckExact(op) ( \ 44 | (Py_IS_TYPE(op, &PyDict_Type)) \ 45 | || (Py_IS_TYPE(op, &PyFrozenDict_Type)) \ 46 | || (Py_IS_TYPE(op, &PyCoold_Type)) \ 47 | ) 48 | 49 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterKey_Type; 50 | static PyTypeObject PyFrozenDictIterKey_Type; 51 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterValues_Type; 52 | static PyTypeObject PyFrozenDictIterValue_Type; 53 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterItem_Type; 54 | static PyTypeObject PyFrozenDictIterItem_Type; 55 | 56 | // PyAPI_DATA(PyTypeObject) PyFrozenDictKeys_Type; 57 | static PyTypeObject PyFrozenDictKeys_Type; 58 | // PyAPI_DATA(PyTypeObject) PyFrozenDictValues_Type; 59 | static PyTypeObject PyFrozenDictValues_Type; 60 | // PyAPI_DATA(PyTypeObject) PyFrozenDictItems_Type; 61 | static PyTypeObject PyFrozenDictItems_Type; 62 | 63 | #define PyAnyDictKeys_Check(op) (PyDictKeys_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictKeys_Type)) 64 | #define PyAnyDictValues_Check(op) (PyDictValues_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictValues_Type)) 65 | #define PyAnyDictItems_Check(op) (PyDictItems_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictItems_Type)) 66 | /* This excludes Values, since they are not sets. */ 67 | # define PyAnyDictViewSet_Check(op) \ 68 | (PyAnyDictKeys_Check(op) || PyAnyDictItems_Check(op)) 69 | 70 | #ifndef Py_LIMITED_API 71 | # define Py_CPYTHON_FROZENDICTOBJECT_H 72 | # include "cpython/frozendictobject.h" 73 | # undef Py_CPYTHON_FROZENDICTOBJECT_H 74 | #endif 75 | 76 | #ifdef __cplusplus 77 | } 78 | #endif 79 | #endif 80 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_8/Include/frozendictobject.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_FROZENDICTOBJECT_H 2 | #define Py_FROZENDICTOBJECT_H 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | // PyAPI_DATA(PyTypeObject) PyFrozenDict_Type; 8 | static PyTypeObject PyFrozenDict_Type; 9 | // PyAPI_DATA(PyTypeObject) PyCoold_Type; 10 | static PyTypeObject PyCoold_Type; 11 | 12 | #define PyFrozenDict_Check(op) \ 13 | ( \ 14 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 15 | || PyType_IsSubtype(Py_TYPE(op), &PyFrozenDict_Type) \ 16 | ) 17 | 18 | #define PyFrozenDict_CheckExact(op) Py_IS_TYPE(op, &PyFrozenDict_Type) 19 | 20 | #define PyAnyFrozenDict_Check(op) \ 21 | ( \ 22 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 23 | || Py_IS_TYPE(op, &PyCoold_Type) \ 24 | || PyType_IsSubtype(Py_TYPE(op), &PyFrozenDict_Type) \ 25 | || PyType_IsSubtype(Py_TYPE(op), &PyCoold_Type) \ 26 | ) 27 | 28 | #define PyAnyFrozenDict_CheckExact(op) \ 29 | ( \ 30 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 31 | || Py_IS_TYPE(op, &PyCoold_Type) \ 32 | ) 33 | 34 | #define PyAnyDict_Check(ob) \ 35 | ( \ 36 | PyDict_Check(ob) \ 37 | || Py_IS_TYPE(ob, &PyFrozenDict_Type) \ 38 | || Py_IS_TYPE(ob, &PyCoold_Type) \ 39 | || PyType_IsSubtype(Py_TYPE(ob), &PyFrozenDict_Type) \ 40 | || PyType_IsSubtype(Py_TYPE(ob), &PyCoold_Type) \ 41 | ) 42 | 43 | #define PyAnyDict_CheckExact(op) ( \ 44 | (Py_IS_TYPE(op, &PyDict_Type)) \ 45 | || (Py_IS_TYPE(op, &PyFrozenDict_Type)) \ 46 | || (Py_IS_TYPE(op, &PyCoold_Type)) \ 47 | ) 48 | 49 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterKey_Type; 50 | static PyTypeObject PyFrozenDictIterKey_Type; 51 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterValues_Type; 52 | static PyTypeObject PyFrozenDictIterValue_Type; 53 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterItem_Type; 54 | static PyTypeObject PyFrozenDictIterItem_Type; 55 | 56 | // PyAPI_DATA(PyTypeObject) PyFrozenDictKeys_Type; 57 | static PyTypeObject PyFrozenDictKeys_Type; 58 | // PyAPI_DATA(PyTypeObject) PyFrozenDictValues_Type; 59 | static PyTypeObject PyFrozenDictValues_Type; 60 | // PyAPI_DATA(PyTypeObject) PyFrozenDictItems_Type; 61 | static PyTypeObject PyFrozenDictItems_Type; 62 | 63 | #define PyAnyDictKeys_Check(op) (PyDictKeys_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictKeys_Type)) 64 | #define PyAnyDictValues_Check(op) (PyDictValues_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictValues_Type)) 65 | #define PyAnyDictItems_Check(op) (PyDictItems_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictItems_Type)) 66 | /* This excludes Values, since they are not sets. */ 67 | # define PyAnyDictViewSet_Check(op) \ 68 | (PyAnyDictKeys_Check(op) || PyAnyDictItems_Check(op)) 69 | 70 | #ifndef Py_LIMITED_API 71 | # define Py_CPYTHON_FROZENDICTOBJECT_H 72 | # include "cpython/frozendictobject.h" 73 | # undef Py_CPYTHON_FROZENDICTOBJECT_H 74 | #endif 75 | 76 | #ifdef __cplusplus 77 | } 78 | #endif 79 | #endif 80 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_9/Include/frozendictobject.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_FROZENDICTOBJECT_H 2 | #define Py_FROZENDICTOBJECT_H 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | // PyAPI_DATA(PyTypeObject) PyFrozenDict_Type; 8 | static PyTypeObject PyFrozenDict_Type; 9 | // PyAPI_DATA(PyTypeObject) PyCoold_Type; 10 | static PyTypeObject PyCoold_Type; 11 | 12 | #define PyFrozenDict_Check(op) \ 13 | ( \ 14 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 15 | || PyType_IsSubtype(Py_TYPE(op), &PyFrozenDict_Type) \ 16 | ) 17 | 18 | #define PyFrozenDict_CheckExact(op) Py_IS_TYPE(op, &PyFrozenDict_Type) 19 | 20 | #define PyAnyFrozenDict_Check(op) \ 21 | ( \ 22 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 23 | || Py_IS_TYPE(op, &PyCoold_Type) \ 24 | || PyType_IsSubtype(Py_TYPE(op), &PyFrozenDict_Type) \ 25 | || PyType_IsSubtype(Py_TYPE(op), &PyCoold_Type) \ 26 | ) 27 | 28 | #define PyAnyFrozenDict_CheckExact(op) \ 29 | ( \ 30 | Py_IS_TYPE(op, &PyFrozenDict_Type) \ 31 | || Py_IS_TYPE(op, &PyCoold_Type) \ 32 | ) 33 | 34 | #define PyAnyDict_Check(ob) \ 35 | ( \ 36 | PyDict_Check(ob) \ 37 | || Py_IS_TYPE(ob, &PyFrozenDict_Type) \ 38 | || Py_IS_TYPE(ob, &PyCoold_Type) \ 39 | || PyType_IsSubtype(Py_TYPE(ob), &PyFrozenDict_Type) \ 40 | || PyType_IsSubtype(Py_TYPE(ob), &PyCoold_Type) \ 41 | ) 42 | 43 | #define PyAnyDict_CheckExact(op) ( \ 44 | (Py_IS_TYPE(op, &PyDict_Type)) \ 45 | || (Py_IS_TYPE(op, &PyFrozenDict_Type)) \ 46 | || (Py_IS_TYPE(op, &PyCoold_Type)) \ 47 | ) 48 | 49 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterKey_Type; 50 | static PyTypeObject PyFrozenDictIterKey_Type; 51 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterValues_Type; 52 | static PyTypeObject PyFrozenDictIterValue_Type; 53 | // PyAPI_DATA(PyTypeObject) PyFrozenDictIterItem_Type; 54 | static PyTypeObject PyFrozenDictIterItem_Type; 55 | 56 | // PyAPI_DATA(PyTypeObject) PyFrozenDictKeys_Type; 57 | static PyTypeObject PyFrozenDictKeys_Type; 58 | // PyAPI_DATA(PyTypeObject) PyFrozenDictValues_Type; 59 | static PyTypeObject PyFrozenDictValues_Type; 60 | // PyAPI_DATA(PyTypeObject) PyFrozenDictItems_Type; 61 | static PyTypeObject PyFrozenDictItems_Type; 62 | 63 | #define PyAnyDictKeys_Check(op) (PyDictKeys_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictKeys_Type)) 64 | #define PyAnyDictValues_Check(op) (PyDictValues_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictValues_Type)) 65 | #define PyAnyDictItems_Check(op) (PyDictItems_Check(op) || PyObject_TypeCheck(op, &PyFrozenDictItems_Type)) 66 | /* This excludes Values, since they are not sets. */ 67 | # define PyAnyDictViewSet_Check(op) \ 68 | (PyAnyDictKeys_Check(op) || PyAnyDictItems_Check(op)) 69 | 70 | #ifndef Py_LIMITED_API 71 | # define Py_CPYTHON_FROZENDICTOBJECT_H 72 | # include "cpython/frozendictobject.h" 73 | # undef Py_CPYTHON_FROZENDICTOBJECT_H 74 | #endif 75 | 76 | #ifdef __cplusplus 77 | } 78 | #endif 79 | #endif 80 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_10/cpython_src/Objects/clinic/dictobject.c.h: -------------------------------------------------------------------------------- 1 | /*[clinic input] 2 | preserve 3 | [clinic start generated code]*/ 4 | 5 | PyDoc_STRVAR(dict_fromkeys__doc__, 6 | "fromkeys($type, iterable, value=None, /)\n" 7 | "--\n" 8 | "\n" 9 | "Create a new dictionary with keys from iterable and values set to value."); 10 | 11 | #define DICT_FROMKEYS_METHODDEF \ 12 | {"fromkeys", (PyCFunction)(void(*)(void))dict_fromkeys, METH_FASTCALL|METH_CLASS, dict_fromkeys__doc__}, 13 | 14 | static PyObject * 15 | frozendict_fromkeys_impl(PyTypeObject *type, PyObject *iterable, PyObject *value); 16 | 17 | static PyObject * 18 | dict_fromkeys(PyTypeObject *type, PyObject *const *args, Py_ssize_t nargs) 19 | { 20 | PyObject *return_value = NULL; 21 | PyObject *iterable; 22 | PyObject *value = Py_None; 23 | 24 | if (!_PyArg_CheckPositional("fromkeys", nargs, 1, 2)) { 25 | goto exit; 26 | } 27 | iterable = args[0]; 28 | if (nargs < 2) { 29 | goto skip_optional; 30 | } 31 | value = args[1]; 32 | skip_optional: 33 | return_value = frozendict_fromkeys_impl(type, iterable, value); 34 | 35 | exit: 36 | return return_value; 37 | } 38 | 39 | PyDoc_STRVAR(dict___contains____doc__, 40 | "__contains__($self, key, /)\n" 41 | "--\n" 42 | "\n" 43 | "True if the dictionary has the specified key, else False."); 44 | 45 | #define DICT___CONTAINS___METHODDEF \ 46 | {"__contains__", (PyCFunction)dict___contains__, METH_O|METH_COEXIST, dict___contains____doc__}, 47 | 48 | PyDoc_STRVAR(dict_get__doc__, 49 | "get($self, key, default=None, /)\n" 50 | "--\n" 51 | "\n" 52 | "Return the value for key if key is in the dictionary, else default."); 53 | 54 | #define DICT_GET_METHODDEF \ 55 | {"get", (PyCFunction)(void(*)(void))dict_get, METH_FASTCALL, dict_get__doc__}, 56 | 57 | static PyObject * 58 | dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value); 59 | 60 | static PyObject * 61 | dict_get(PyDictObject *self, PyObject *const *args, Py_ssize_t nargs) 62 | { 63 | PyObject *return_value = NULL; 64 | PyObject *key; 65 | PyObject *default_value = Py_None; 66 | 67 | if (!_PyArg_CheckPositional("get", nargs, 1, 2)) { 68 | goto exit; 69 | } 70 | key = args[0]; 71 | if (nargs < 2) { 72 | goto skip_optional; 73 | } 74 | default_value = args[1]; 75 | skip_optional: 76 | return_value = dict_get_impl(self, key, default_value); 77 | 78 | exit: 79 | return return_value; 80 | } 81 | 82 | PyDoc_STRVAR(dict___reversed____doc__, 83 | "__reversed__($self, /)\n" 84 | "--\n" 85 | "\n" 86 | "Return a reverse iterator over the dict keys."); 87 | 88 | #define DICT___REVERSED___METHODDEF \ 89 | {"__reversed__", (PyCFunction)dict___reversed__, METH_NOARGS, dict___reversed____doc__}, 90 | 91 | static PyObject * 92 | dict___reversed___impl(PyDictObject *self); 93 | 94 | static PyObject * 95 | dict___reversed__(PyDictObject *self, PyObject *Py_UNUSED(ignored)) 96 | { 97 | return dict___reversed___impl(self); 98 | } 99 | /*[clinic end generated code: output=7b77c16e43d6735a input=a9049054013a1b77]*/ 100 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_8/cpython_src/Objects/clinic/dictobject.c.h: -------------------------------------------------------------------------------- 1 | /*[clinic input] 2 | preserve 3 | [clinic start generated code]*/ 4 | 5 | PyDoc_STRVAR(dict_fromkeys__doc__, 6 | "fromkeys($type, iterable, value=None, /)\n" 7 | "--\n" 8 | "\n" 9 | "Create a new dictionary with keys from iterable and values set to value."); 10 | 11 | #define DICT_FROMKEYS_METHODDEF \ 12 | {"fromkeys", (PyCFunction)(void(*)(void))dict_fromkeys, METH_FASTCALL|METH_CLASS, dict_fromkeys__doc__}, 13 | 14 | static PyObject * 15 | frozendict_fromkeys_impl(PyTypeObject *type, PyObject *iterable, PyObject *value); 16 | 17 | static PyObject * 18 | dict_fromkeys(PyTypeObject *type, PyObject *const *args, Py_ssize_t nargs) 19 | { 20 | PyObject *return_value = NULL; 21 | PyObject *iterable; 22 | PyObject *value = Py_None; 23 | 24 | if (!_PyArg_CheckPositional("fromkeys", nargs, 1, 2)) { 25 | goto exit; 26 | } 27 | iterable = args[0]; 28 | if (nargs < 2) { 29 | goto skip_optional; 30 | } 31 | value = args[1]; 32 | skip_optional: 33 | return_value = frozendict_fromkeys_impl(type, iterable, value); 34 | 35 | exit: 36 | return return_value; 37 | } 38 | 39 | PyDoc_STRVAR(dict___contains____doc__, 40 | "__contains__($self, key, /)\n" 41 | "--\n" 42 | "\n" 43 | "True if the dictionary has the specified key, else False."); 44 | 45 | #define DICT___CONTAINS___METHODDEF \ 46 | {"__contains__", (PyCFunction)dict___contains__, METH_O|METH_COEXIST, dict___contains____doc__}, 47 | 48 | PyDoc_STRVAR(dict_get__doc__, 49 | "get($self, key, default=None, /)\n" 50 | "--\n" 51 | "\n" 52 | "Return the value for key if key is in the dictionary, else default."); 53 | 54 | #define DICT_GET_METHODDEF \ 55 | {"get", (PyCFunction)(void(*)(void))dict_get, METH_FASTCALL, dict_get__doc__}, 56 | 57 | static PyObject * 58 | dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value); 59 | 60 | static PyObject * 61 | dict_get(PyDictObject *self, PyObject *const *args, Py_ssize_t nargs) 62 | { 63 | PyObject *return_value = NULL; 64 | PyObject *key; 65 | PyObject *default_value = Py_None; 66 | 67 | if (!_PyArg_CheckPositional("get", nargs, 1, 2)) { 68 | goto exit; 69 | } 70 | key = args[0]; 71 | if (nargs < 2) { 72 | goto skip_optional; 73 | } 74 | default_value = args[1]; 75 | skip_optional: 76 | return_value = dict_get_impl(self, key, default_value); 77 | 78 | exit: 79 | return return_value; 80 | } 81 | 82 | PyDoc_STRVAR(dict___reversed____doc__, 83 | "__reversed__($self, /)\n" 84 | "--\n" 85 | "\n" 86 | "Return a reverse iterator over the dict keys."); 87 | 88 | #define DICT___REVERSED___METHODDEF \ 89 | {"__reversed__", (PyCFunction)dict___reversed__, METH_NOARGS, dict___reversed____doc__}, 90 | 91 | static PyObject * 92 | dict___reversed___impl(PyDictObject *self); 93 | 94 | static PyObject * 95 | dict___reversed__(PyDictObject *self, PyObject *Py_UNUSED(ignored)) 96 | { 97 | return dict___reversed___impl(self); 98 | } 99 | /*[clinic end generated code: output=7b77c16e43d6735a input=a9049054013a1b77]*/ 100 | -------------------------------------------------------------------------------- /src/frozendict/c_src/3_9/cpython_src/Objects/clinic/dictobject.c.h: -------------------------------------------------------------------------------- 1 | /*[clinic input] 2 | preserve 3 | [clinic start generated code]*/ 4 | 5 | PyDoc_STRVAR(dict_fromkeys__doc__, 6 | "fromkeys($type, iterable, value=None, /)\n" 7 | "--\n" 8 | "\n" 9 | "Create a new dictionary with keys from iterable and values set to value."); 10 | 11 | #define DICT_FROMKEYS_METHODDEF \ 12 | {"fromkeys", (PyCFunction)(void(*)(void))dict_fromkeys, METH_FASTCALL|METH_CLASS, dict_fromkeys__doc__}, 13 | 14 | static PyObject * 15 | frozendict_fromkeys_impl(PyTypeObject *type, PyObject *iterable, PyObject *value); 16 | 17 | static PyObject * 18 | dict_fromkeys(PyTypeObject *type, PyObject *const *args, Py_ssize_t nargs) 19 | { 20 | PyObject *return_value = NULL; 21 | PyObject *iterable; 22 | PyObject *value = Py_None; 23 | 24 | if (!_PyArg_CheckPositional("fromkeys", nargs, 1, 2)) { 25 | goto exit; 26 | } 27 | iterable = args[0]; 28 | if (nargs < 2) { 29 | goto skip_optional; 30 | } 31 | value = args[1]; 32 | skip_optional: 33 | return_value = frozendict_fromkeys_impl(type, iterable, value); 34 | 35 | exit: 36 | return return_value; 37 | } 38 | 39 | PyDoc_STRVAR(dict___contains____doc__, 40 | "__contains__($self, key, /)\n" 41 | "--\n" 42 | "\n" 43 | "True if the dictionary has the specified key, else False."); 44 | 45 | #define DICT___CONTAINS___METHODDEF \ 46 | {"__contains__", (PyCFunction)dict___contains__, METH_O|METH_COEXIST, dict___contains____doc__}, 47 | 48 | PyDoc_STRVAR(dict_get__doc__, 49 | "get($self, key, default=None, /)\n" 50 | "--\n" 51 | "\n" 52 | "Return the value for key if key is in the dictionary, else default."); 53 | 54 | #define DICT_GET_METHODDEF \ 55 | {"get", (PyCFunction)(void(*)(void))dict_get, METH_FASTCALL, dict_get__doc__}, 56 | 57 | static PyObject * 58 | dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value); 59 | 60 | static PyObject * 61 | dict_get(PyDictObject *self, PyObject *const *args, Py_ssize_t nargs) 62 | { 63 | PyObject *return_value = NULL; 64 | PyObject *key; 65 | PyObject *default_value = Py_None; 66 | 67 | if (!_PyArg_CheckPositional("get", nargs, 1, 2)) { 68 | goto exit; 69 | } 70 | key = args[0]; 71 | if (nargs < 2) { 72 | goto skip_optional; 73 | } 74 | default_value = args[1]; 75 | skip_optional: 76 | return_value = dict_get_impl(self, key, default_value); 77 | 78 | exit: 79 | return return_value; 80 | } 81 | 82 | PyDoc_STRVAR(dict___reversed____doc__, 83 | "__reversed__($self, /)\n" 84 | "--\n" 85 | "\n" 86 | "Return a reverse iterator over the dict keys."); 87 | 88 | #define DICT___REVERSED___METHODDEF \ 89 | {"__reversed__", (PyCFunction)dict___reversed__, METH_NOARGS, dict___reversed____doc__}, 90 | 91 | static PyObject * 92 | dict___reversed___impl(PyDictObject *self); 93 | 94 | static PyObject * 95 | dict___reversed__(PyDictObject *self, PyObject *Py_UNUSED(ignored)) 96 | { 97 | return dict___reversed___impl(self); 98 | } 99 | /*[clinic end generated code: output=7b77c16e43d6735a input=a9049054013a1b77]*/ 100 | -------------------------------------------------------------------------------- /test/base.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | class FrozendictTestBase: 5 | _FrozendictClass = None 6 | 7 | @property 8 | def FrozendictClass(self): 9 | val = self._FrozendictClass 10 | 11 | if val is None: 12 | raise ValueError("FrozendictClass is None") 13 | 14 | return val 15 | 16 | @FrozendictClass.setter 17 | def FrozendictClass(self, val): 18 | self._FrozendictClass = val 19 | 20 | _c_ext = None 21 | 22 | @property 23 | def c_ext(self): 24 | val = self._c_ext 25 | 26 | if val is None: 27 | raise ValueError("c_ext is None") 28 | 29 | return val 30 | 31 | @c_ext.setter 32 | def c_ext(self, val): 33 | self._c_ext = val 34 | 35 | _is_subclass = None 36 | 37 | @property 38 | def is_subclass(self): 39 | val = self._is_subclass 40 | 41 | if val is None: 42 | raise ValueError("is_subclass is None") 43 | 44 | return val 45 | 46 | @is_subclass.setter 47 | def is_subclass(self, val): 48 | self._is_subclass = val 49 | 50 | #################################################################### 51 | # dict fixtures 52 | 53 | @pytest.fixture 54 | def fd_dict(self): 55 | return { 56 | "Guzzanti": "Corrado", 57 | "Hicks": "Bill", 58 | self.FrozendictClass({1: 2}): "frozen" 59 | } 60 | 61 | @pytest.fixture 62 | def fd_dict_hole(self, fd_dict): 63 | new_dict = fd_dict.copy() 64 | del new_dict["Guzzanti"] 65 | return new_dict 66 | 67 | @pytest.fixture 68 | def fd_dict_eq(self): 69 | return { 70 | "Hicks": "Bill", 71 | "Guzzanti": "Corrado", 72 | self.FrozendictClass({1: 2}): "frozen" 73 | } 74 | 75 | @pytest.fixture 76 | def fd_dict_2(self): 77 | return { 78 | "Guzzanti": "Corrado", 79 | "Hicks": "Bill", 80 | "frozen": self.FrozendictClass({1: 2}) 81 | } 82 | 83 | @pytest.fixture 84 | def fd_dict_sabina(self): 85 | return {'Corrado': 'Guzzanti', 'Sabina': 'Guzzanti'} 86 | 87 | @pytest.fixture 88 | def generator_seq2(self, fd_dict): 89 | seq2 = list(fd_dict.items()) 90 | seq2.append(("Guzzanti", "Mario")) 91 | return (x for x in seq2) 92 | 93 | #################################################################### 94 | # frozendict fixtures 95 | 96 | @pytest.fixture 97 | def fd(self, fd_dict): 98 | return self.FrozendictClass(fd_dict) 99 | 100 | @pytest.fixture 101 | def fd_hole(self, fd_dict_hole): 102 | return self.FrozendictClass(fd_dict_hole) 103 | 104 | @pytest.fixture 105 | def fd_unhashable(self): 106 | return self.FrozendictClass({1: []}) 107 | 108 | @pytest.fixture 109 | def fd_eq(self, fd_dict_eq): 110 | return self.FrozendictClass(fd_dict_eq) 111 | 112 | @pytest.fixture 113 | def fd2(self, fd_dict_2): 114 | return self.FrozendictClass(fd_dict_2) 115 | 116 | @pytest.fixture 117 | def fd_sabina(self, fd_dict_sabina): 118 | return self.FrozendictClass(fd_dict_sabina) 119 | 120 | @pytest.fixture 121 | def fd_items(self, fd_dict): 122 | return tuple(fd_dict.items()) 123 | 124 | @pytest.fixture 125 | def fd_empty(self): 126 | return self.FrozendictClass() 127 | 128 | @pytest.fixture 129 | def module_prefix(self): 130 | if self.is_subclass: 131 | return "" 132 | 133 | return "frozendict." 134 | 135 | -------------------------------------------------------------------------------- /src/frozendict/__init__.pyi: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import ( 4 | TypeVar, 5 | overload, 6 | Optional, 7 | Union, 8 | Any, 9 | Dict, 10 | Callable, 11 | ) 12 | 13 | from collections.abc import Hashable 14 | 15 | try: 16 | from typing import Mapping, Iterable, Iterator, Tuple, Type 17 | except ImportError: 18 | from collections.abc import Mapping, Iterable, Iterator 19 | Tuple = tuple 20 | Type = type 21 | 22 | K = TypeVar("K") 23 | V = TypeVar("V", covariant=True) 24 | K2 = TypeVar("K2") 25 | V2 = TypeVar("V2", covariant=True) 26 | SelfT = TypeVar("SelfT", bound=frozendict[K, V]) 27 | 28 | # noinspection PyPep8Naming 29 | class frozendict(Mapping[K, V]): 30 | @overload 31 | def __new__(cls: Type[SelfT]) -> SelfT: ... 32 | @overload 33 | def __new__(cls: Type[SelfT], **kwargs: V) -> frozendict[str, V]: ... 34 | @overload 35 | def __new__(cls: Type[SelfT], mapping: Mapping[K, V]) -> SelfT: ... 36 | @overload 37 | def __new__(cls: Type[SelfT], iterable: Iterable[Tuple[K, V]]) -> SelfT: ... 38 | 39 | def __getitem__(self: SelfT, key: K) -> V: ... 40 | def __len__(self: SelfT) -> int: ... 41 | def __iter__(self: SelfT) -> Iterator[K]: ... 42 | def __hash__(self: SelfT) -> int: ... 43 | def __reversed__(self: SelfT) -> Iterator[K]: ... 44 | def copy(self: SelfT) -> SelfT: ... 45 | def __copy__(self: SelfT) -> SelfT: ... 46 | def __deepcopy__(self: SelfT) -> SelfT: ... 47 | def delete(self: SelfT, key: K) -> SelfT: ... 48 | @overload 49 | def key(self: SelfT, index: int) -> K: ... 50 | @overload 51 | def key(self: SelfT) -> K: ... 52 | @overload 53 | def value(self: SelfT, index: int) -> V: ... 54 | @overload 55 | def value(self: SelfT) -> V: ... 56 | @overload 57 | def item(self: SelfT, index: int) -> Tuple[K, V]: ... 58 | @overload 59 | def item(self: SelfT) -> Tuple[K, V]: ... 60 | @overload 61 | def __or__(self: SelfT, other: Mapping[K, V]) -> SelfT: ... 62 | @overload 63 | def __or__(self: SelfT, other: Mapping[K2, V]) -> frozendict[Union[K, K2], V]: ... 64 | @overload 65 | def __or__(self: SelfT, other: Mapping[K, V2]) -> frozendict[K, Union[V, V2]]: ... 66 | @overload 67 | def __or__(self: SelfT, other: Mapping[K2, V2]) -> frozendict[Union[K, K2], Union[V, V2]]: ... 68 | @overload 69 | def set(self: SelfT, key: K, value: V) -> SelfT: ... 70 | @overload 71 | def set(self: SelfT, key: K2, value: V) -> frozendict[Union[K, K2], V]: ... 72 | @overload 73 | def set(self: SelfT, key: K, value: V2) -> frozendict[K, Union[V, V2]]: ... 74 | @overload 75 | def set(self: SelfT, key: K2, value: V2) -> frozendict[Union[K, K2], Union[V, V2]]: ... 76 | @overload 77 | def setdefault(self: SelfT, key: K) -> SelfT: ... 78 | @overload 79 | def setdefault(self: SelfT, key: K2) -> SelfT: ... 80 | @overload 81 | def setdefault(self: SelfT, key: K, default: V) -> SelfT: ... 82 | @overload 83 | def setdefault(self: SelfT, key: K2, default: V) -> frozendict[Union[K, K2], V]: ... 84 | @overload 85 | def setdefault(self: SelfT, key: K, default: V2) -> frozendict[K, Union[V, V2]]: ... 86 | @overload 87 | def setdefault(self: SelfT, key: K2, default: V2) -> frozendict[Union[K, K2], Union[V, V2]]: ... 88 | 89 | @classmethod 90 | def fromkeys( 91 | cls: Type[SelfT], 92 | seq: Iterable[K], 93 | value: Optional[V] = None 94 | ) -> SelfT: ... 95 | 96 | 97 | FrozenOrderedDict = frozendict 98 | c_ext: bool 99 | 100 | class FreezeError(Exception): pass 101 | 102 | 103 | class FreezeWarning(UserWarning): pass 104 | 105 | # PyCharm complains about returning Hashable, because 106 | # it's not subscriptable 107 | def deepfreeze( 108 | o: Any, 109 | custom_converters: Optional[Dict[Any, Callable[[Any], Hashable]]] = None, 110 | custom_inverse_converters: Optional[Dict[Any, Callable[[Any], Any]]] = None 111 | ) -> Any: ... 112 | 113 | def register( 114 | to_convert: Any, 115 | converter: Callable[[Any], Any], 116 | *, 117 | inverse: bool = False 118 | ) -> None: ... 119 | 120 | def unregister( 121 | type: Any, 122 | inverse: bool = False 123 | ) -> None: ... 124 | -------------------------------------------------------------------------------- /test/test_freeze.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from collections.abc import MutableSequence, Sequence 3 | from enum import Enum 4 | from types import MappingProxyType 5 | 6 | import frozendict as cool 7 | import pytest 8 | from array import array 9 | from frozendict import FreezeError, FreezeWarning 10 | from frozendict import cool as _cool 11 | from frozendict import frozendict 12 | 13 | 14 | class A: 15 | def __init__(self, x): 16 | self.x = x 17 | 18 | 19 | class NoDictAndHash: 20 | __slots__ = ( 21 | "x", 22 | ) 23 | 24 | def __init__(self, x): 25 | self.x = x 26 | 27 | def __hash__(self): 28 | raise TypeError() 29 | 30 | 31 | class BaseSeq(Sequence): 32 | 33 | def __init__(self, seq): 34 | self._items = list(seq) 35 | 36 | def __getitem__(self, i): 37 | return self._items[i] 38 | 39 | def __len__(self): 40 | return len(self._items) 41 | 42 | 43 | class FrozenSeqA(BaseSeq): 44 | pass 45 | 46 | 47 | class FrozenSeqB(BaseSeq): 48 | pass 49 | 50 | 51 | class MutableSeq(BaseSeq, MutableSequence): 52 | 53 | def __delitem__(self, i): 54 | del self._items[i] 55 | 56 | def __setitem__(self, i, v): 57 | self._items[i] = v 58 | 59 | def insert(self, index, value): 60 | self._items.insert(index, value) 61 | 62 | 63 | def custom_a_converter(a): 64 | return frozendict(**a.__dict__, y="mbuti") 65 | 66 | 67 | def custom_inverse_converter(o): 68 | return dict(**o, y="mbuti") 69 | 70 | 71 | @pytest.fixture 72 | def before_cure_inverse(): 73 | return [frozendict(a={1: 2})] 74 | 75 | 76 | @pytest.fixture 77 | def after_cure_inverse(): 78 | return (frozendict(a=frozendict({1: 2}), y="mbuti"), ) 79 | 80 | 81 | @pytest.fixture 82 | def no_cure_inverse(): 83 | return (frozendict(a=frozendict({1: 2})), ) 84 | 85 | 86 | @pytest.fixture 87 | def a(): 88 | a = A(3) 89 | 90 | return a 91 | 92 | 93 | @pytest.fixture 94 | def no_dict_and_hash(): 95 | res = NoDictAndHash(3) 96 | 97 | return res 98 | 99 | 100 | @pytest.fixture 101 | def before_cure(a): 102 | return {"x": [ 103 | 5, 104 | frozendict(y = {5, "b", memoryview(b"b")}), 105 | array("B", (0, 1, 2)), 106 | OrderedDict(a=bytearray(b"a")), 107 | MappingProxyType({2: []}), 108 | a 109 | ]} 110 | 111 | 112 | @pytest.fixture 113 | def after_cure(): 114 | return frozendict(x = ( 115 | 5, 116 | frozendict(y = frozenset({5, "b", memoryview(b"b")})), 117 | (0, 1, 2), 118 | frozendict(a = b'a'), 119 | MappingProxyType({2: ()}), 120 | frozendict(x = 3), 121 | )) 122 | 123 | 124 | @pytest.fixture 125 | def after_cure_a(a): 126 | return custom_a_converter(a) 127 | 128 | 129 | @pytest.fixture 130 | def my_enum(): 131 | Color = Enum('Color', ['RED', 'GREEN', 'BLUE']) 132 | # noinspection PyUnresolvedReferences 133 | return Color.RED 134 | 135 | 136 | def test_deepfreeze(before_cure, after_cure): 137 | assert cool.deepfreeze(before_cure) == after_cure 138 | 139 | 140 | def test_register_bad_to_convert(): 141 | with pytest.raises(ValueError): 142 | # noinspection PyTypeChecker 143 | cool.register(5, 7) 144 | 145 | 146 | def test_register_bad_converter(): 147 | with pytest.raises(ValueError): 148 | # noinspection PyTypeChecker 149 | cool.register(frozendict, 7) 150 | 151 | 152 | def test_unregister_not_present(): 153 | with pytest.raises(FreezeError): 154 | cool.unregister(frozendict) 155 | 156 | 157 | def test_deepfreeze_bad_custom_converters_key(before_cure): 158 | with pytest.raises(ValueError): 159 | # noinspection PyTypeChecker 160 | cool.deepfreeze(before_cure, custom_converters={7: 7}) 161 | 162 | 163 | def test_deepfreeze_bad_custom_converters_val(before_cure): 164 | with pytest.raises(ValueError): 165 | # noinspection PyTypeChecker 166 | cool.deepfreeze(before_cure, custom_converters={frozendict: 7}) 167 | 168 | 169 | def test_deepfreeze_bad_custom_inverse_converters_key(before_cure): 170 | with pytest.raises(ValueError): 171 | # noinspection PyTypeChecker 172 | cool.deepfreeze(before_cure, custom_inverse_converters={7: 7}) 173 | 174 | 175 | def test_deepfreeze_bad_custom_inverse_converters_val(before_cure): 176 | with pytest.raises(ValueError): 177 | # noinspection PyTypeChecker 178 | cool.deepfreeze( 179 | before_cure, 180 | custom_inverse_converters = {frozendict: 7} 181 | ) 182 | 183 | 184 | def test_register_custom(a): 185 | cool.register(A, custom_a_converter) 186 | assert cool.deepfreeze(a) == custom_a_converter(a) 187 | cool.unregister(A) 188 | assert cool.deepfreeze(a) == frozendict(a.__dict__) 189 | 190 | 191 | def test_deepfreeze_custom(a): 192 | assert cool.deepfreeze( 193 | a, 194 | custom_converters={A: custom_a_converter} 195 | ) == custom_a_converter(a) 196 | 197 | 198 | def test_deepfreeze_inverse(before_cure_inverse, after_cure_inverse): 199 | assert cool.deepfreeze( 200 | before_cure_inverse, 201 | custom_inverse_converters={frozendict: custom_inverse_converter} 202 | ) == after_cure_inverse 203 | 204 | 205 | def test_register_inverse( 206 | before_cure_inverse, 207 | after_cure_inverse, 208 | no_cure_inverse, 209 | ): 210 | with pytest.warns(FreezeWarning): 211 | cool.register( 212 | frozendict, 213 | custom_inverse_converter, 214 | inverse=True 215 | ) 216 | 217 | assert cool.deepfreeze(before_cure_inverse) == after_cure_inverse 218 | 219 | cool.unregister(frozendict, inverse=True) 220 | 221 | assert cool.deepfreeze(before_cure_inverse) == no_cure_inverse 222 | 223 | 224 | def test_prefer_forward(): 225 | assert isinstance( 226 | cool.deepfreeze( 227 | [FrozenSeqA((0, 1, 2))], 228 | custom_converters={FrozenSeqA: FrozenSeqB}, 229 | custom_inverse_converters={FrozenSeqA: MutableSeq} 230 | )[0], 231 | FrozenSeqB 232 | ) 233 | 234 | 235 | def test_original_immutate(): 236 | unfrozen = { 237 | "int": 1, 238 | "nested": {"int": 1}, 239 | } 240 | 241 | cool.deepfreeze(unfrozen) 242 | 243 | assert type(unfrozen["nested"]) is dict 244 | 245 | 246 | def test_enum(my_enum): 247 | assert cool.deepfreeze(my_enum) is my_enum 248 | 249 | 250 | def test_get_items(): 251 | with pytest.raises(TypeError): 252 | _cool.getItems(5) 253 | 254 | 255 | def test_no_dict_and_hash(no_dict_and_hash): 256 | with pytest.raises(TypeError): 257 | cool.deepfreeze(no_dict_and_hash) 258 | -------------------------------------------------------------------------------- /src/frozendict/monkeypatch.py: -------------------------------------------------------------------------------- 1 | _OldJsonEncoder = None 2 | _oldOrjsonDumps = None 3 | _oldMutableMappingSubclasshook = None 4 | 5 | 6 | class MonkeypatchWarning(UserWarning): 7 | pass 8 | 9 | 10 | def checkCExtension(*, warn, warn_c = False): 11 | import frozendict as cool 12 | 13 | res = cool.c_ext 14 | 15 | if warn and res == warn_c: 16 | if warn_c: # pragma: no cover 17 | msg = "C Extension version, monkeypatch will be not applied" 18 | else: 19 | msg = "Pure Python version, monkeypatch will be not applied" 20 | 21 | import warnings 22 | 23 | warnings.warn(msg, MonkeypatchWarning) 24 | 25 | return res 26 | 27 | 28 | def patchOrUnpatchJson(*, patch, warn = True): # pragma: no cover 29 | if not checkCExtension(warn = warn): 30 | return 31 | 32 | from importlib import import_module 33 | self = import_module(__name__) 34 | import frozendict as cool 35 | import json 36 | 37 | OldJsonEncoder = self._OldJsonEncoder 38 | 39 | # noinspection PyUnresolvedReferences, PyProtectedMember 40 | FrozendictJsonEncoder = cool._getFrozendictJsonEncoder( 41 | OldJsonEncoder 42 | ) 43 | 44 | if patch: 45 | DefaultJsonEncoder = FrozendictJsonEncoder 46 | else: 47 | DefaultJsonEncoder = OldJsonEncoder 48 | 49 | if DefaultJsonEncoder is None: 50 | default_json_encoder = None 51 | else: 52 | default_json_encoder = DefaultJsonEncoder( 53 | skipkeys = False, 54 | ensure_ascii = True, 55 | check_circular = True, 56 | allow_nan = True, 57 | indent = None, 58 | separators = None, 59 | default = None, 60 | ) 61 | 62 | if patch: 63 | if OldJsonEncoder is None: 64 | self._OldJsonEncoder = json.encoder.JSONEncoder 65 | else: 66 | if OldJsonEncoder is None: 67 | raise ValueError( 68 | "Old json encoder is None " + 69 | "(maybe you already unpatched json?)" 70 | ) 71 | 72 | self._OldJsonEncoder = None 73 | 74 | cool.FrozendictJsonEncoder = FrozendictJsonEncoder 75 | 76 | json.JSONEncoder = DefaultJsonEncoder 77 | json.encoder.JSONEncoder = DefaultJsonEncoder 78 | json._default_encoder = default_json_encoder 79 | 80 | 81 | def patchOrUnpatchOrjson(*, patch, warn = True): # pragma: no cover 82 | if not checkCExtension(warn = warn): 83 | return 84 | 85 | from importlib import import_module 86 | self = import_module(__name__) 87 | # noinspection PyUnresolvedReferences 88 | import orjson 89 | 90 | if self._oldOrjsonDumps is None: 91 | if not patch: 92 | raise ValueError( 93 | "Old orjson encoder is None " + 94 | "(maybe you already unpatched orjson?)" 95 | ) 96 | 97 | oldOrjsonDumps = orjson.dumps 98 | else: 99 | oldOrjsonDumps = self._oldOrjsonDumps 100 | 101 | if patch: 102 | from frozendict import frozendict 103 | 104 | def frozendictOrjsonDumps(obj, *args, **kwargs): 105 | if isinstance(obj, frozendict): 106 | obj = dict(obj) 107 | 108 | return oldOrjsonDumps(obj, *args, **kwargs) 109 | 110 | defaultOrjsonDumps = frozendictOrjsonDumps 111 | newOldOrjsonDumps = oldOrjsonDumps 112 | else: 113 | defaultOrjsonDumps = oldOrjsonDumps 114 | newOldOrjsonDumps = None 115 | 116 | self._oldOrjsonDumps = newOldOrjsonDumps 117 | orjson.dumps = defaultOrjsonDumps 118 | orjson.orjson.dumps = defaultOrjsonDumps 119 | 120 | 121 | def patchOrUnpatchMutableMappingSubclasshook( 122 | *, 123 | patch, 124 | warn = True 125 | ): # pragma: no cover 126 | warn_c = True 127 | 128 | if checkCExtension(warn = warn, warn_c = warn_c): 129 | return 130 | 131 | from importlib import import_module 132 | self = import_module(__name__) 133 | from collections.abc import MutableMapping 134 | from frozendict import frozendict 135 | 136 | if self._oldMutableMappingSubclasshook is None: 137 | if not patch: 138 | raise ValueError( 139 | "Old MutableMapping subclasshook is None " + 140 | "(maybe you already unpatched MutableMapping?)" 141 | ) 142 | 143 | oldMutableMappingHook = MutableMapping.__subclasshook__ 144 | else: 145 | oldMutableMappingHook = self._oldMutableMappingSubclasshook 146 | 147 | if patch: 148 | # noinspection PyDecorator 149 | @classmethod 150 | def frozendictMutableMappingSubclasshook( 151 | klass, 152 | subclass, 153 | *args, 154 | **kwargs 155 | ): 156 | if klass == MutableMapping: 157 | if issubclass(subclass, frozendict): 158 | return False 159 | 160 | # noinspection PyArgumentList 161 | return oldMutableMappingHook( 162 | subclass, 163 | *args, 164 | **kwargs 165 | ) 166 | 167 | return NotImplemented 168 | 169 | defaultMutableMappingHook = frozendictMutableMappingSubclasshook 170 | newOldMutableMappingHook = oldMutableMappingHook 171 | else: 172 | defaultMutableMappingHook = oldMutableMappingHook 173 | newOldMutableMappingHook = None 174 | 175 | self._oldMutableMappingSubclasshook = newOldMutableMappingHook 176 | MutableMapping.__subclasshook__ = defaultMutableMappingHook 177 | 178 | try: 179 | # noinspection PyUnresolvedReferences, PyProtectedMember 180 | MutableMapping._abc_caches_clear() 181 | except AttributeError: 182 | # noinspection PyUnresolvedReferences, PyProtectedMember 183 | MutableMapping._abc_cache.discard(frozendict) 184 | # noinspection PyUnresolvedReferences, PyProtectedMember 185 | MutableMapping._abc_negative_cache.discard(frozendict) 186 | 187 | 188 | def patchOrUnpatchAll(*, patch, warn = True, raise_orjson = False): 189 | patchOrUnpatchJson(patch = patch, warn = warn) 190 | 191 | try: 192 | import orjson 193 | except ImportError: # pragma: no cover 194 | if raise_orjson: 195 | raise 196 | else: # pragma: no cover 197 | patchOrUnpatchOrjson(patch = patch, warn = warn) 198 | 199 | patchOrUnpatchMutableMappingSubclasshook(patch = patch, warn = warn) 200 | 201 | 202 | __all__ = ( 203 | patchOrUnpatchJson.__name__, 204 | patchOrUnpatchOrjson.__name__, 205 | patchOrUnpatchMutableMappingSubclasshook.__name__, 206 | patchOrUnpatchAll.__name__, 207 | MonkeypatchWarning.__name__, 208 | ) 209 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | from os import environ 5 | from pathlib import Path 6 | from platform import python_implementation 7 | 8 | import setuptools 9 | 10 | name = "frozendict" 11 | module1_name = "frozendict" 12 | readme_filename = "README.md" 13 | version_filename = "version.py" 14 | py_typed_filename = "py.typed" 15 | mypy_filename = "__init__.pyi" 16 | main_url = "https://github.com/Marco-Sulla/python-frozendict" 17 | bug_url = "https://github.com/Marco-Sulla/python-frozendict/issues" 18 | author = "Marco Sulla" 19 | author_email = "marcosullaroma@gmail.com" 20 | mylicense = "LGPL v3" 21 | license_files = "LICENSE.txt" 22 | description = "A simple immutable dictionary" 23 | 24 | keywords = ( 25 | "immutable hashable picklable frozendict dict dictionary " + 26 | "map Mapping MappingProxyType developers stable utility" 27 | ) 28 | 29 | python_requires = ">=3.6" 30 | 31 | classifiers = [ 32 | "Development Status :: 5 - Production/Stable", 33 | "Intended Audience :: Developers", 34 | ( 35 | "License :: OSI Approved :: GNU Lesser General Public " + 36 | "License v3 (LGPLv3)" 37 | ), 38 | "Programming Language :: Python :: 3 :: Only", 39 | "Programming Language :: Python :: 3.6", 40 | "Natural Language :: English", 41 | "Operating System :: OS Independent", 42 | "Topic :: Software Development :: Libraries", 43 | "Topic :: Software Development :: Libraries :: Python Modules", 44 | "Topic :: Utilities", 45 | ] 46 | 47 | curr_path = Path(__file__).resolve() 48 | curr_dir = curr_path.parent 49 | 50 | readme_path = curr_dir / readme_filename 51 | readme_content_type = "text/markdown" 52 | 53 | with open(readme_path) as f: 54 | long_description = f.read() 55 | 56 | package_dir_name = "src" 57 | package_path = curr_dir / package_dir_name 58 | 59 | module1_dir_name = module1_name 60 | module1_path = package_path / module1_dir_name 61 | 62 | version_path = module1_path / version_filename 63 | 64 | with open(version_path) as f: 65 | # create the version var 66 | exec(f.read()) 67 | 68 | package_path_str = str(package_path) 69 | packages = setuptools.find_packages(where = package_path_str) 70 | package_data_filenames = (py_typed_filename, mypy_filename) 71 | package_data = { 72 | package_name: package_data_filenames 73 | for package_name in packages 74 | } 75 | 76 | # C extension - START 77 | 78 | c_src_dir_name = "c_src" 79 | c_src_base_path = module1_path / c_src_dir_name 80 | include_dir_name = "Include" 81 | 82 | ext1_name = "_" + name 83 | ext1_fullname = module1_name + "." + ext1_name 84 | ext1_source1_name = name + "object" 85 | ext1_source1_fullname = ext1_source1_name + ".c" 86 | 87 | cpython_objects_dir_name = "Objects" 88 | cpython_stringlib_name = "stringlib" 89 | cpython_objects_clinic_name = "clinic" 90 | 91 | extra_compile_args = ["-DPY_SSIZE_T_CLEAN", ] 92 | 93 | pyversion = sys.version_info 94 | 95 | cpython_version = f"{pyversion[0]}_{pyversion[1]}" 96 | 97 | c_src_path = c_src_base_path / cpython_version 98 | 99 | cpython_path = c_src_path / "cpython_src" 100 | cpython_object_path = cpython_path / cpython_objects_dir_name 101 | 102 | include_path = c_src_path / include_dir_name 103 | cpython_stringlib_path = cpython_object_path / cpython_stringlib_name 104 | 105 | cpython_objects_clinic_path = ( 106 | cpython_object_path / cpython_objects_clinic_name 107 | ) 108 | 109 | cpython_include_dirs = [ 110 | str(include_path), 111 | str(cpython_object_path), 112 | str(cpython_stringlib_path), 113 | str(cpython_objects_clinic_path), 114 | str(cpython_path), 115 | ] 116 | 117 | ext1_source1_path = c_src_path / ext1_source1_fullname 118 | 119 | cpython_sources_tmp = [ext1_source1_path, ] 120 | 121 | cpython_sources = [ 122 | str(x.relative_to(curr_dir)) 123 | for x in cpython_sources_tmp 124 | ] 125 | 126 | undef_macros = [] 127 | 128 | argv = sys.argv 129 | argv_1_exists = len(argv) > 1 130 | 131 | if argv_1_exists and argv[1] == "c_debug": 132 | undef_macros = ["NDEBUG"] 133 | 134 | 135 | # noinspection PyShadowingNames 136 | def get_ext_module( 137 | fullname, 138 | sources, 139 | include_dirs, 140 | extra_compile_args, 141 | undef_macros, 142 | optional 143 | ): 144 | ext_module = setuptools.Extension( 145 | fullname, 146 | sources = sources, 147 | include_dirs = include_dirs, 148 | extra_compile_args = extra_compile_args, 149 | undef_macros = undef_macros, 150 | optional = optional, 151 | ) 152 | 153 | return ext_module 154 | 155 | 156 | # noinspection PyShadowingNames 157 | def get_ext_module_1(optional): 158 | return get_ext_module( 159 | fullname = ext1_fullname, 160 | sources = cpython_sources, 161 | include_dirs = cpython_include_dirs, 162 | extra_compile_args = extra_compile_args, 163 | undef_macros = undef_macros, 164 | optional = optional, 165 | ) 166 | 167 | # C extension - END 168 | 169 | 170 | # noinspection PyUnresolvedReferences 171 | common_setup_args = dict( 172 | name = name, 173 | author = author, 174 | author_email = author_email, 175 | version = version, 176 | python_requires = python_requires, 177 | license = mylicense, 178 | license_files = (license_files, ), 179 | url = main_url, 180 | 181 | project_urls = { 182 | "Bug Reports": bug_url, 183 | "Source": main_url, 184 | }, 185 | 186 | packages = packages, 187 | package_dir = {"": package_dir_name}, 188 | package_data = package_data, 189 | 190 | description = description, 191 | long_description = long_description, 192 | long_description_content_type = readme_content_type, 193 | 194 | classifiers = classifiers, 195 | keywords = keywords, 196 | ) 197 | 198 | custom_arg = None 199 | 200 | custom_args_py = ("py", ) 201 | custom_args_c = ("c", "c_debug") 202 | custom_args = custom_args_py + custom_args_c 203 | 204 | if argv_1_exists and argv[1] in custom_args: 205 | custom_arg = argv[1] 206 | sys.argv = [argv[0]] + argv[2:] 207 | 208 | impl = python_implementation() 209 | 210 | # C Extension is optional by default from version 2.3.5 211 | # If the module is built by pipeline, C Extension must be mandatory. 212 | optional = environ.get('CIBUILDWHEEL') != '1' 213 | 214 | # Check if build the pure py implementation 215 | pure_py_env = environ.get('FROZENDICT_PURE_PY') 216 | pure_py = pure_py_env == '1' 217 | 218 | mix_c_py_error = ValueError( 219 | "You can't specify the pure py implementation *and* C " + 220 | "extension togheter" 221 | ) 222 | 223 | if custom_arg is None: 224 | if impl == "PyPy": 225 | custom_arg = "py" 226 | else: 227 | custom_arg = "py" if pure_py else "c" 228 | elif custom_arg in custom_args_c: 229 | if pure_py: 230 | raise mix_c_py_error 231 | 232 | optional = False 233 | 234 | if pure_py and not optional: 235 | raise mix_c_py_error 236 | 237 | if custom_arg in custom_args_py: 238 | # check if pure py explicitly disabled 239 | if pure_py_env == '0': 240 | raise mix_c_py_error 241 | 242 | setuptools.setup(**common_setup_args) 243 | elif custom_arg in custom_args_c: 244 | ext_module_1 = get_ext_module_1(optional) 245 | ext_modules = [ext_module_1] 246 | setuptools.setup(ext_modules = ext_modules, **common_setup_args) 247 | else: 248 | raise ValueError(f"Unsupported custom_arg {custom_arg}") 249 | -------------------------------------------------------------------------------- /src/frozendict/_frozendict_py.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | 3 | 4 | def immutable(self, *_args, **_kwargs): 5 | r""" 6 | Function for not implemented method since the object is immutable 7 | """ 8 | 9 | raise AttributeError( 10 | f"'{self.__class__.__name__}' object is read-only" 11 | ) 12 | 13 | 14 | _empty_frozendict = None 15 | _module_name = "frozendict" 16 | 17 | 18 | # noinspection PyPep8Naming 19 | class frozendict(dict): 20 | r""" 21 | A simple immutable dictionary. 22 | 23 | The API is the same as `dict`, without methods that can change the 24 | immutability. In addition, it supports __hash__(). 25 | """ 26 | 27 | __slots__ = ( 28 | "_hash", 29 | ) 30 | 31 | @classmethod 32 | def fromkeys(cls, *args, **kwargs): 33 | r""" 34 | Identical to dict.fromkeys(). 35 | """ 36 | 37 | return cls(dict.fromkeys(*args, **kwargs)) 38 | 39 | # noinspection PyMethodParameters 40 | def __new__(e4b37cdf_d78a_4632_bade_6f0579d8efac, *args, **kwargs): 41 | cls = e4b37cdf_d78a_4632_bade_6f0579d8efac 42 | 43 | has_kwargs = bool(kwargs) 44 | continue_creation = True 45 | self = None 46 | 47 | # check if there's only an argument and it's of the same class 48 | if len(args) == 1 and not has_kwargs: 49 | it = args[0] 50 | 51 | # no isinstance, to avoid subclassing problems 52 | if it.__class__ == frozendict and cls == frozendict: 53 | self = it 54 | continue_creation = False 55 | 56 | if continue_creation: 57 | self = dict.__new__(cls, *args, **kwargs) 58 | 59 | dict.__init__(self, *args, **kwargs) 60 | 61 | # empty singleton - start 62 | 63 | if self.__class__ == frozendict and not len(self): 64 | global _empty_frozendict 65 | 66 | if _empty_frozendict is None: 67 | _empty_frozendict = self 68 | else: 69 | self = _empty_frozendict 70 | continue_creation = False 71 | 72 | # empty singleton - end 73 | 74 | if continue_creation: 75 | object.__setattr__(self, "_hash", -1) 76 | 77 | return self 78 | 79 | # noinspection PyMissingConstructor 80 | def __init__(self, *args, **kwargs): 81 | pass 82 | 83 | def __hash__(self, *args, **kwargs): 84 | r""" 85 | Calculates the hash if all values are hashable, otherwise 86 | raises a TypeError. 87 | """ 88 | 89 | if self._hash != -1: 90 | _hash = self._hash 91 | else: 92 | fs = frozenset(self.items()) 93 | _hash = hash(fs) 94 | 95 | object.__setattr__(self, "_hash", _hash) 96 | 97 | return _hash 98 | 99 | def __repr__(self, *args, **kwargs): 100 | r""" 101 | Identical to dict.__repr__(). 102 | """ 103 | 104 | body = super().__repr__(*args, **kwargs) 105 | klass = self.__class__ 106 | 107 | if klass == frozendict: 108 | name = f"{_module_name}.{klass.__name__}" 109 | else: 110 | name = klass.__name__ 111 | 112 | return f"{name}({body})" 113 | 114 | def copy(self): 115 | r""" 116 | Return the object itself, as it's an immutable. 117 | """ 118 | 119 | klass = self.__class__ 120 | 121 | if klass == frozendict: 122 | return self 123 | 124 | return klass(self) 125 | 126 | def __copy__(self, *args, **kwargs): 127 | r""" 128 | See copy(). 129 | """ 130 | 131 | return self.copy() 132 | 133 | def __deepcopy__(self, memo, *args, **kwargs): 134 | r""" 135 | As for tuples, if hashable, see copy(); otherwise, it returns a 136 | deepcopy. 137 | """ 138 | 139 | klass = self.__class__ 140 | return_copy = klass == frozendict 141 | 142 | if return_copy: 143 | try: 144 | hash(self) 145 | except TypeError: 146 | return_copy = False 147 | 148 | if return_copy: 149 | return self.copy() 150 | 151 | tmp = deepcopy(dict(self)) 152 | 153 | return klass(tmp) 154 | 155 | def __reduce__(self, *args, **kwargs): 156 | r""" 157 | Support for `pickle`. 158 | """ 159 | 160 | return (self.__class__, (dict(self),)) 161 | 162 | def set(self, key, val): 163 | new_self = dict(self) 164 | new_self[key] = val 165 | 166 | return self.__class__(new_self) 167 | 168 | def setdefault(self, key, default=None): 169 | if key in self: 170 | return self 171 | 172 | new_self = dict(self) 173 | 174 | new_self[key] = default 175 | 176 | return self.__class__(new_self) 177 | 178 | def delete(self, key): 179 | new_self = dict(self) 180 | del new_self[key] 181 | 182 | if new_self: 183 | return self.__class__(new_self) 184 | 185 | return self.__class__() 186 | 187 | def _get_by_index(self, collection, index): 188 | try: 189 | return collection[index] 190 | except IndexError: 191 | maxindex = len(collection) - 1 192 | name = self.__class__.__name__ 193 | raise IndexError( 194 | f"{name} index {index} out of range {maxindex}" 195 | ) from None 196 | 197 | def key(self, index=0): 198 | collection = tuple(self.keys()) 199 | 200 | return self._get_by_index(collection, index) 201 | 202 | def value(self, index=0): 203 | collection = tuple(self.values()) 204 | 205 | return self._get_by_index(collection, index) 206 | 207 | def item(self, index=0): 208 | collection = tuple(self.items()) 209 | 210 | return self._get_by_index(collection, index) 211 | 212 | def __setitem__(self, key, val, *args, **kwargs): 213 | raise TypeError( 214 | f"'{self.__class__.__name__}' object doesn't support item " 215 | "assignment" 216 | ) 217 | 218 | def __delitem__(self, key, *args, **kwargs): 219 | raise TypeError( 220 | f"'{self.__class__.__name__}' object doesn't support item " 221 | "deletion" 222 | ) 223 | 224 | 225 | def frozendict_or(self, other, *_args, **_kwargs): 226 | res = {} 227 | res.update(self) 228 | res.update(other) 229 | 230 | return self.__class__(res) 231 | 232 | 233 | frozendict.__or__ = frozendict_or 234 | frozendict.__ior__ = frozendict_or 235 | 236 | try: 237 | # noinspection PyStatementEffect 238 | frozendict.__reversed__ 239 | except AttributeError: # pragma: no cover 240 | def frozendict_reversed(self, *_args, **_kwargs): 241 | return reversed(tuple(self)) 242 | 243 | 244 | frozendict.__reversed__ = frozendict_reversed 245 | 246 | frozendict.clear = immutable 247 | frozendict.pop = immutable 248 | frozendict.popitem = immutable 249 | frozendict.update = immutable 250 | frozendict.__delattr__ = immutable 251 | frozendict.__setattr__ = immutable 252 | frozendict.__module__ = _module_name 253 | 254 | __all__ = (frozendict.__name__,) 255 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /.github/workflows/build_secondary_wheels.yml: -------------------------------------------------------------------------------- 1 | name: Build secondary wheels 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | pull_request: 8 | paths: 9 | - '**' 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build_wheels_qemu: 14 | name: > 15 | Build secondary wheels with QEMU for py ${{ matrix.py }}, 16 | os ${{ matrix.cbw_os }}, arch ${{ matrix.arch }} 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | py: [310, 39, 38] 22 | cbw_os: [manylinux_, musllinux_] 23 | arch: [ppc64le, s390x, riscv64] 24 | 25 | steps: 26 | - uses: actions/checkout@v3 27 | 28 | - name: Set up QEMU 29 | uses: docker/setup-qemu-action@v3 30 | with: 31 | platforms: all 32 | 33 | - name: Build wheels 34 | uses: pypa/cibuildwheel@v3.2.1 35 | env: 36 | CIBW_BUILD: > 37 | cp${{ matrix.py }}-${{ matrix.cbw_os }}${{ matrix.arch }} 38 | CIBW_ARCHS: ${{ matrix.arch }} 39 | CIBW_BUILD_FRONTEND: > 40 | ${{ 41 | matrix.cbw_os == 'musllinux_' 42 | && 'build' 43 | || 'build[uv]' 44 | }} 45 | CIBW_CONTAINER_ENGINE: podman 46 | CIBW_TEST_REQUIRES: pytest 47 | CIBW_TEST_COMMAND: > 48 | python -X faulthandler {package}/test/debug.py 49 | && python -X faulthandler -m pytest -p no:faulthandler 50 | -s {package} 51 | 52 | - uses: actions/upload-artifact@v4 53 | with: 54 | name: > 55 | secondary_wheel_qemu_artifact_py${{ matrix.py }}_os_ 56 | ${{ matrix.cbw_os }}arch_${{ matrix.arch }} 57 | path: ./wheelhouse/*.whl 58 | 59 | build_old_wheels_qemu: 60 | name: > 61 | Build old wheels with QEMU for py ${{ matrix.py }}, 62 | os ${{ matrix.cbw_os }}, arch ${{ matrix.arch }} 63 | runs-on: ubuntu-latest 64 | strategy: 65 | fail-fast: false 66 | matrix: 67 | py: [37, 36] 68 | cbw_os: [manylinux_, musllinux_] 69 | arch: [ppc64le, s390x] 70 | 71 | steps: 72 | - uses: actions/checkout@v3 73 | 74 | - name: Set up QEMU 75 | uses: docker/setup-qemu-action@v3 76 | with: 77 | platforms: all 78 | 79 | - name: Build wheels 80 | uses: pypa/cibuildwheel@v2.23.3 81 | env: 82 | CIBW_BUILD: > 83 | cp${{ matrix.py }}-${{ matrix.cbw_os }}${{ matrix.arch }} 84 | CIBW_ARCHS: ${{ matrix.arch }} 85 | CIBW_BUILD_FRONTEND: build[uv] 86 | CIBW_CONTAINER_ENGINE: podman 87 | CIBW_TEST_REQUIRES: pytest 88 | CIBW_TEST_COMMAND: > 89 | python -X faulthandler {package}/test/debug.py 90 | && python -X faulthandler -m pytest -p no:faulthandler 91 | -s {package} 92 | 93 | - uses: actions/upload-artifact@v4 94 | with: 95 | name: > 96 | old_wheel_qemu_artifact_${{ matrix.py }}_os_ 97 | ${{ matrix.cbw_os }}arch_${{ matrix.arch }} 98 | path: ./wheelhouse/*.whl 99 | 100 | build_wheels: 101 | name: > 102 | Build secondary wheels for py ${{ matrix.py }}, 103 | os ${{ matrix.cbw_os }}, arch ${{ matrix.arch }} 104 | runs-on: ${{ matrix.os }} 105 | strategy: 106 | fail-fast: false 107 | matrix: 108 | py: [310, 39, 38] 109 | os: [ 110 | ubuntu-latest, 111 | ubuntu-24.04-arm, 112 | windows-latest, 113 | macos-latest 114 | ] 115 | include: 116 | - os: ubuntu-latest 117 | cbw_os: musllinux_ 118 | arch: x86_64 119 | - os: ubuntu-24.04-arm 120 | cbw_os: manylinux_ 121 | arch: aarch64 122 | - os: ubuntu-24.04-arm 123 | cbw_os: musllinux_ 124 | arch: aarch64 125 | - os: windows-latest 126 | cbw_os: win_ 127 | arch: arm64 128 | - os: macos-latest 129 | cbw_os: macosx_ 130 | arch: x86_64 131 | - os: macos-latest 132 | cbw_os: macosx_ 133 | arch: arm64 134 | - os: macos-latest 135 | cbw_os: macosx_ 136 | arch: universal2 137 | - os: ubuntu-latest 138 | cbw_os: manylinux_ 139 | arch: i686 140 | - os: ubuntu-latest 141 | cbw_os: musllinux_ 142 | arch: i686 143 | - os: ubuntu-24.04-arm 144 | cbw_os: manylinux_ 145 | arch: armv7l 146 | - os: ubuntu-24.04-arm 147 | cbw_os: musllinux_ 148 | arch: armv7l 149 | - os: windows-latest 150 | cbw_os: win32 151 | arch: "" 152 | 153 | steps: 154 | - uses: actions/checkout@v3 155 | 156 | - name: Install dependencies 157 | uses: astral-sh/setup-uv@v7 158 | 159 | - name: Build wheels 160 | uses: pypa/cibuildwheel@v3.2.1 161 | env: 162 | CIBW_BUILD: > 163 | cp${{ matrix.py }}-${{ matrix.cbw_os }}${{ matrix.arch }} 164 | CIBW_ARCHS: ${{ matrix.arch }} 165 | CIBW_BUILD_FRONTEND: build[uv] 166 | CIBW_CONTAINER_ENGINE: podman 167 | CIBW_TEST_REQUIRES: pytest 168 | CIBW_TEST_COMMAND: > 169 | python -X faulthandler {package}/test/debug.py 170 | && python -X faulthandler -m pytest -p no:faulthandler 171 | -s {package} 172 | 173 | - uses: actions/upload-artifact@v4 174 | with: 175 | name: > 176 | secondary_wheel_artifact_py${{ matrix.py }}_os_ 177 | ${{ matrix.cbw_os }}arch_${{ matrix.arch }} 178 | path: ./wheelhouse/*.whl 179 | 180 | build_old_wheels: 181 | name: > 182 | Build old wheels for py ${{ matrix.py }}, 183 | os ${{ matrix.cbw_os }}, arch ${{ matrix.arch }} 184 | runs-on: ${{ matrix.os }} 185 | strategy: 186 | fail-fast: false 187 | matrix: 188 | py: [37, 36] 189 | os: [ 190 | ubuntu-latest, 191 | ubuntu-24.04-arm, 192 | windows-latest, 193 | macos-latest 194 | ] 195 | include: 196 | - os: ubuntu-latest 197 | cbw_os: manylinux_ 198 | arch: x86_64 199 | - os: ubuntu-latest 200 | cbw_os: musllinux_ 201 | arch: x86_64 202 | - os: ubuntu-24.04-arm 203 | cbw_os: manylinux_ 204 | arch: aarch64 205 | - os: ubuntu-24.04-arm 206 | cbw_os: musllinux_ 207 | arch: aarch64 208 | - os: windows-latest 209 | cbw_os: win_ 210 | arch: amd64 211 | - os: macos-latest 212 | cbw_os: macosx_ 213 | arch: x86_64 214 | - os: ubuntu-latest 215 | cbw_os: manylinux_ 216 | arch: i686 217 | - os: ubuntu-latest 218 | cbw_os: musllinux_ 219 | arch: i686 220 | - os: ubuntu-24.04-arm 221 | cbw_os: manylinux_ 222 | arch: armv7l 223 | - os: ubuntu-24.04-arm 224 | cbw_os: musllinux_ 225 | arch: armv7l 226 | - os: windows-latest 227 | cbw_os: win32 228 | arch: "" 229 | 230 | steps: 231 | - uses: actions/checkout@v3 232 | 233 | - name: Install dependencies 234 | uses: astral-sh/setup-uv@v7 235 | 236 | - name: Build wheels 237 | uses: pypa/cibuildwheel@v2.23.3 238 | env: 239 | CIBW_BUILD: > 240 | cp${{ matrix.py }}-${{ matrix.cbw_os }}${{ matrix.arch }} 241 | CIBW_ARCHS: ${{ matrix.arch == '' && 'x86' || matrix.arch }} 242 | CIBW_BUILD_FRONTEND: build[uv] 243 | CIBW_CONTAINER_ENGINE: podman 244 | CIBW_TEST_REQUIRES: pytest 245 | CIBW_TEST_COMMAND: > 246 | python -X faulthandler {package}/test/debug.py 247 | && python -X faulthandler -m pytest -p no:faulthandler 248 | -s {package} 249 | 250 | - uses: actions/upload-artifact@v4 251 | with: 252 | name: > 253 | old_wheel_artifact_py${{ matrix.py }}_os_ 254 | ${{ matrix.cbw_os }}arch_${{ matrix.arch }} 255 | path: ./wheelhouse/*.whl 256 | -------------------------------------------------------------------------------- /src/frozendict/cool.py: -------------------------------------------------------------------------------- 1 | from collections.abc import MutableMapping, MutableSequence, MutableSet 2 | from enum import Enum 3 | from types import MappingProxyType 4 | 5 | from array import array 6 | from frozendict import frozendict 7 | 8 | # fix for python 3.9- 9 | 10 | # coverage does not work here! 11 | if not issubclass(array, MutableSequence): # pragma: no cover 12 | # noinspection PyUnresolvedReferences 13 | MutableSequence.register(array) 14 | 15 | 16 | def isIterableNotString(o): 17 | from collections import abc 18 | 19 | return ( 20 | isinstance(o, abc.Iterable) and 21 | not isinstance(o, memoryview) and 22 | not hasattr(o, "isalpha") 23 | ) 24 | 25 | 26 | def getItems(o): 27 | from collections import abc 28 | 29 | if not isinstance(o, abc.Iterable): 30 | raise TypeError("object must be an iterable") 31 | 32 | if isinstance(o, abc.Mapping): 33 | return dict.items 34 | 35 | return enumerate 36 | 37 | 38 | def nil(x): 39 | return x 40 | 41 | 42 | _freeze_conversion_map = frozendict({ 43 | MutableMapping: frozendict, 44 | bytearray: bytes, 45 | MutableSequence: tuple, 46 | MutableSet: frozenset, 47 | Enum: nil, 48 | }) 49 | 50 | _freeze_conversion_map_custom = {} 51 | 52 | 53 | class FreezeError(Exception): 54 | pass 55 | 56 | 57 | class FreezeWarning(UserWarning): 58 | pass 59 | 60 | 61 | def register(to_convert, converter, *, inverse = False): 62 | r""" 63 | Adds a `converter` for a type `to_convert`. `converter` 64 | must be callable. The new converter will be used by `deepfreeze()` 65 | and has precedence over any previous converter. 66 | 67 | If `to_covert` has already a converter, a FreezeWarning is raised. 68 | 69 | If `inverse` is True, the conversion is considered from an immutable 70 | type to a mutable one. This make it possible to convert mutable 71 | objects nested in the registered immutable one. 72 | """ 73 | 74 | if not issubclass(type(to_convert), type): 75 | raise ValueError( 76 | f"`to_convert` parameter must be a type, {to_convert} found" 77 | ) 78 | 79 | try: 80 | converter.__call__ 81 | except AttributeError: 82 | raise ValueError( 83 | f"`converter` parameter must be a callable, {converter}" + 84 | "found" 85 | ) 86 | 87 | if inverse: 88 | freeze_conversion_map = getFreezeConversionInverseMap() 89 | else: 90 | freeze_conversion_map = getFreezeConversionMap() 91 | 92 | if to_convert in freeze_conversion_map: 93 | import warnings 94 | 95 | warnings.warn( 96 | f"{to_convert.__name__} is already in the conversion map", 97 | FreezeWarning 98 | ) 99 | 100 | if inverse: 101 | freeze_conversion_map = _freeze_conversion_inverse_map_custom 102 | else: 103 | freeze_conversion_map = _freeze_conversion_map_custom 104 | 105 | freeze_conversion_map[to_convert] = converter 106 | 107 | 108 | def unregister(type, inverse = False): 109 | r""" 110 | Unregister a type from custom conversion. If `inverse` is `True`, 111 | the unregistered conversion is an inverse conversion 112 | (see `register()`). 113 | """ 114 | 115 | if inverse: 116 | freeze_conversion_map = _freeze_conversion_inverse_map_custom 117 | else: 118 | freeze_conversion_map = _freeze_conversion_map_custom 119 | 120 | try: 121 | del freeze_conversion_map[type] 122 | except KeyError: 123 | raise FreezeError(f"{type.__name__} is not registered") 124 | 125 | 126 | def getFreezeConversionMap(): 127 | return _freeze_conversion_map | _freeze_conversion_map_custom 128 | 129 | 130 | _freeze_conversion_inverse_map = frozendict({ 131 | frozendict: dict, 132 | MappingProxyType: dict, 133 | tuple: list, 134 | }) 135 | 136 | _freeze_conversion_inverse_map_custom = {} 137 | 138 | 139 | def getFreezeConversionInverseMap(): 140 | return ( 141 | _freeze_conversion_inverse_map | 142 | _freeze_conversion_inverse_map_custom 143 | ) 144 | 145 | 146 | _freeze_types = ( 147 | [x for x in _freeze_conversion_map] + 148 | [x for x in _freeze_conversion_inverse_map] 149 | ) 150 | 151 | 152 | def getFreezeTypes(): 153 | return (tuple( 154 | _freeze_types + 155 | [x for x in _freeze_conversion_map_custom] + 156 | [x for x in _freeze_conversion_inverse_map_custom] 157 | )) 158 | 159 | 160 | _freeze_types_plain = (MutableSet, bytearray, array) 161 | 162 | 163 | def deepfreeze( 164 | o, 165 | custom_converters = None, 166 | custom_inverse_converters = None 167 | ): 168 | r""" 169 | Converts the object and all the objects nested in it in its 170 | immutable counterparts. 171 | 172 | The conversion map is in getFreezeConversionMap(). 173 | 174 | You can register a new conversion using `register()` You can also 175 | pass a map of custom converters with `custom_converters` and a map 176 | of custom inverse converters with `custom_inverse_converters`, 177 | without using `register()`. 178 | 179 | By default, if the type is not registered and has a `__dict__` 180 | attribute, it's converted to the `frozendict` of that `__dict__`. 181 | 182 | This function assumes that hashable == immutable (that is not 183 | always true). 184 | 185 | This function uses recursion, with all the limits of recursions in 186 | Python. 187 | 188 | Where is a good old tail call when you need it? 189 | """ 190 | 191 | from frozendict import frozendict 192 | 193 | if custom_converters is None: 194 | custom_converters = frozendict() 195 | 196 | if custom_inverse_converters is None: 197 | custom_inverse_converters = frozendict() 198 | 199 | for type_i, converter in custom_converters.items(): 200 | if not issubclass(type(type_i), type): 201 | raise ValueError( 202 | f"{type_i} in `custom_converters` parameter is not a " + 203 | "type" 204 | ) 205 | 206 | try: 207 | converter.__call__ 208 | except AttributeError: 209 | raise ValueError( 210 | f"converter for {type_i} in `custom_converters` " + 211 | "parameter is not a callable" 212 | ) 213 | 214 | for type_i, converter in custom_inverse_converters.items(): 215 | if not issubclass(type(type_i), type): 216 | raise ValueError( 217 | f"{type_i} in `custom_inverse_converters` parameter " + 218 | "is not a type" 219 | ) 220 | 221 | try: 222 | converter.__call__ 223 | except AttributeError: 224 | raise ValueError( 225 | f"converter for {type_i} in " + 226 | "`custom_inverse_converters`parameter is not a callable" 227 | ) 228 | 229 | type_o = type(o) 230 | 231 | freeze_types = tuple(custom_converters.keys()) + getFreezeTypes() 232 | 233 | base_type_o = None 234 | 235 | for freeze_type in freeze_types: 236 | if isinstance(o, freeze_type): 237 | base_type_o = freeze_type 238 | break 239 | 240 | if base_type_o is None: 241 | # this is before hash check because all object in Python are 242 | # hashable by default, if not explicitly suppressed 243 | try: 244 | o.__dict__ 245 | except AttributeError: 246 | pass 247 | else: 248 | return frozendict(o.__dict__) 249 | 250 | try: 251 | hash(o) 252 | except TypeError: 253 | pass 254 | else: 255 | # without a converter, we can only hope that 256 | # hashable == immutable 257 | return o 258 | 259 | supported_types = ", ".join((x.__name__ for x in freeze_types)) 260 | 261 | err = ( 262 | f"type {type_o} is not hashable or is not equal or a " + 263 | f"subclass of the supported types: {supported_types}" 264 | ) 265 | 266 | raise TypeError(err) 267 | 268 | freeze_conversion_map = getFreezeConversionMap() 269 | 270 | freeze_conversion_map = freeze_conversion_map | custom_converters 271 | 272 | if base_type_o in _freeze_types_plain: 273 | return freeze_conversion_map[base_type_o](o) 274 | 275 | if not isIterableNotString(o): 276 | return freeze_conversion_map[base_type_o](o) 277 | 278 | freeze_conversion_inverse_map = getFreezeConversionInverseMap() 279 | 280 | freeze_conversion_inverse_map = ( 281 | freeze_conversion_inverse_map | 282 | custom_inverse_converters 283 | ) 284 | 285 | frozen_type = base_type_o in freeze_conversion_inverse_map 286 | 287 | if frozen_type: 288 | o = freeze_conversion_inverse_map[base_type_o](o) 289 | 290 | from copy import copy 291 | 292 | o_copy = copy(o) 293 | 294 | for k, v in getItems(o_copy)(o_copy): 295 | o_copy[k] = deepfreeze( 296 | v, 297 | custom_converters = custom_converters, 298 | custom_inverse_converters = custom_inverse_converters 299 | ) 300 | 301 | try: 302 | freeze = freeze_conversion_map[base_type_o] 303 | except KeyError: 304 | if frozen_type: 305 | freeze = type_o 306 | else: # pragma: no cover 307 | raise 308 | 309 | return freeze(o_copy) 310 | 311 | 312 | __all__ = ( 313 | deepfreeze.__name__, 314 | register.__name__, 315 | unregister.__name__, 316 | getFreezeConversionMap.__name__, 317 | getFreezeConversionInverseMap.__name__, 318 | FreezeError.__name__, 319 | FreezeWarning.__name__, 320 | ) 321 | 322 | del MappingProxyType 323 | del array 324 | del frozendict 325 | del MutableMapping 326 | del MutableSequence 327 | del MutableSet 328 | del Enum 329 | -------------------------------------------------------------------------------- /test/bench.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import timeit 4 | import uuid 5 | from copy import copy 6 | from time import time 7 | 8 | import immutables 9 | from frozendict import frozendict 10 | from math import sqrt 11 | 12 | 13 | def mindev(data, xbar=None): 14 | """ 15 | This function calculates the stdev around the _minimum_ data, 16 | and not the mean 17 | """ 18 | 19 | if not data: 20 | raise ValueError("No data") 21 | 22 | if xbar is None: 23 | xbar = min(data) 24 | 25 | sigma2 = 0 26 | 27 | for x in data: 28 | sigma2 += (x - xbar) ** 2 29 | 30 | N = len(data) - 1 31 | 32 | if N < 1: 33 | N = 1 34 | 35 | return sqrt(sigma2 / N) 36 | 37 | 38 | def autorange( 39 | stmt, 40 | setup="pass", 41 | globals=None, 42 | ratio=1000, 43 | bench_time=10, 44 | number=None 45 | ): 46 | if setup is None: 47 | setup = "pass" 48 | 49 | t = timeit.Timer(stmt=stmt, setup=setup, globals=globals) 50 | break_immediately = False 51 | 52 | if number is None: 53 | # get automatically the number of needed loops 54 | a = t.autorange() 55 | 56 | number_temp = a[0] 57 | # the number of loops 58 | number = int(number_temp / ratio) 59 | 60 | if number < 1: 61 | number = 1 62 | 63 | # the number of repeat of loops 64 | repeat = int(number_temp / number) 65 | 66 | if repeat < 1: 67 | repeat = 1 68 | 69 | else: 70 | repeat = 1 71 | break_immediately = True 72 | 73 | data_tmp = t.repeat(number=number, repeat=repeat) 74 | min_value = min(data_tmp) 75 | # create a list with minimum values 76 | data_min = [min_value] 77 | 78 | bench_start = time() 79 | 80 | while 1: 81 | # get additional benchs until `bench_time` seconds passes 82 | data_min.extend(t.repeat(number=number, repeat=repeat)) 83 | 84 | if break_immediately or time() - bench_start > bench_time: 85 | break 86 | 87 | # sort the data... 88 | data_min.sort() 89 | # ... so the minimum is the fist datum 90 | xbar = data_min[0] 91 | i = 0 92 | 93 | # repeat until no data is removed 94 | while i < len(data_min): 95 | i = len(data_min) 96 | 97 | # calculate the sigma using the minimum as "real" value, 98 | # and not the mean 99 | sigma = mindev(data_min, xbar=xbar) 100 | 101 | for i2 in range(2, len(data_min)): 102 | # find the point where the data are greater than 103 | # 3 sigma. Data are sorted 104 | if data_min[i2] - xbar > 3 * sigma: 105 | break 106 | 107 | k = i 108 | 109 | # do not remove too much data 110 | if i < 5: 111 | k = 5 112 | 113 | # remove the data with sigma > 3 114 | del data_min[k:] 115 | 116 | # return the minimum as real value, with the sigma 117 | # calculated around the minimum 118 | return ( 119 | min(data_min) / number, 120 | mindev(data_min, xbar=xbar) / number 121 | ) 122 | 123 | 124 | def getUuid(): 125 | return str(uuid.uuid4()) 126 | 127 | 128 | def main(number): 129 | dictionary_sizes = (5, 1000) 130 | 131 | print_tpl = ( 132 | "Name: {name: <25} Size: {size: >4}; Keys: {keys: >3}; " + 133 | "Type: {type: >10}; Time: {time:.2e}; Sigma: {sigma:.0e}" 134 | ) 135 | 136 | str_key = '12323f29-c31f-478c-9b15-e7acc5354df9' 137 | int_key = dictionary_sizes[0] - 2 138 | 139 | if int_key < 0: 140 | int_key = 0 141 | 142 | bench_constr_kwargs_name = "constructor(kwargs)" 143 | bench_hash_name = "hash" 144 | bench_set_name = "set" 145 | bench_delete_name = "set" 146 | bench_copy_name = "copy" 147 | bench_fromkeys_name = "fromkeys" 148 | 149 | benchmarks = ( 150 | { 151 | "name": "constructor(d)", 152 | "code": "klass(d)", 153 | "setup": "klass = type(o)", 154 | }, 155 | { 156 | "name": bench_constr_kwargs_name, 157 | "code": "klass(**d)", 158 | "setup": "klass = type(o)", 159 | }, 160 | { 161 | "name": "constructor(seq2)", 162 | "code": "klass(v)", 163 | "setup": "klass = type(o); v = tuple(d.items())", 164 | }, 165 | { 166 | "name": "constructor(o)", 167 | "code": "klass(o)", 168 | "setup": "klass = type(o)", 169 | }, 170 | { 171 | "name": bench_copy_name, 172 | "code": None, 173 | "setup": "pass", 174 | }, 175 | { 176 | "name": "o == o", 177 | "code": "o == o", 178 | "setup": "pass", 179 | }, 180 | { 181 | "name": "for x in o", 182 | "code": "for _ in o: pass", 183 | "setup": "pass", 184 | }, 185 | { 186 | "name": "for x in o.values()", 187 | "code": "for _ in values: pass", 188 | "setup": "values = o.values()", 189 | }, 190 | { 191 | "name": "for x in o.items()", 192 | "code": "for _ in items: pass", 193 | "setup": "items = o.items()", 194 | }, 195 | { 196 | "name": "pickle.dumps", 197 | "code": "dumps(o, protocol=-1)", 198 | "setup": "from pickle import dumps", 199 | }, 200 | { 201 | "name": "pickle.loads", 202 | "code": "loads(dump)", 203 | "setup": ( 204 | "from pickle import loads, dumps; " + 205 | "dump = dumps(o, protocol=-1)" 206 | ), 207 | }, 208 | { 209 | "name": bench_fromkeys_name, 210 | "code": "fromkeys(keys)", 211 | "setup": "fromkeys = type(o).fromkeys; keys = o.keys()", 212 | }, 213 | { 214 | "name": bench_set_name, 215 | "code": None, 216 | "setup": "val = getUuid()", 217 | }, 218 | { 219 | "name": bench_delete_name, 220 | "code": None, 221 | "setup": "pass", 222 | }, 223 | { 224 | "name": bench_hash_name, 225 | "code": "hash(o)", 226 | "setup": "pass", 227 | }, 228 | ) 229 | 230 | dict_collection = [] 231 | 232 | for n in dictionary_sizes: 233 | d1 = {} 234 | d2 = {} 235 | 236 | for i in range(n-1): 237 | d1[getUuid()] = getUuid() 238 | d2[i] = i 239 | 240 | d1[str_key] = getUuid() 241 | d2[999] = 999 242 | 243 | fd1 = frozendict(d1) 244 | fd2 = frozendict(d2) 245 | m1 = immutables.Map(d1) 246 | m2 = immutables.Map(d2) 247 | 248 | dict_collection.append({ 249 | "str": ((d1, fd1, m1), str_key), 250 | "int": ((d2, fd2, m2), int_key) 251 | }) 252 | 253 | sep_n = 72 254 | sep_major = "#" 255 | 256 | for benchmark in benchmarks: 257 | print(sep_major * sep_n) 258 | 259 | for dict_entry in dict_collection: 260 | for (dict_keys, (dicts, one_key)) in dict_entry.items(): 261 | 262 | if ( 263 | benchmark["name"] == bench_constr_kwargs_name and 264 | dict_keys == "int" 265 | ): 266 | continue 267 | 268 | print("/" * sep_n) 269 | 270 | for o in dicts: 271 | if ( 272 | benchmark["name"] == bench_hash_name and 273 | type(o) is dict 274 | ): 275 | continue 276 | 277 | if benchmark["name"] == bench_set_name: 278 | if type(o) is dict: 279 | benchmark["code"] = ( 280 | "o.copy()[one_key] = val" 281 | ) 282 | else: 283 | benchmark["code"] = "o.set(one_key, val)" 284 | 285 | if benchmark["name"] == bench_delete_name: 286 | if type(o) is dict: 287 | benchmark["code"] = "del o.copy()[one_key]" 288 | else: 289 | benchmark["code"] = "o.delete(one_key)" 290 | 291 | if benchmark["name"] == bench_copy_name: 292 | if type(o) is immutables.Map: 293 | benchmark["code"] = "copy(o)" 294 | else: 295 | benchmark["code"] = "o.copy()" 296 | 297 | if ( 298 | benchmark["name"] == bench_fromkeys_name and 299 | type(o) is immutables.Map 300 | ): 301 | continue 302 | 303 | d = dicts[0] 304 | 305 | bench_res = autorange( 306 | stmt = benchmark["code"], 307 | setup = benchmark["setup"], 308 | globals = { 309 | "o": copy(o), 310 | "getUuid": getUuid, 311 | "d": d.copy(), 312 | "one_key": one_key, 313 | "copy": copy, 314 | }, 315 | number = number, 316 | ) 317 | 318 | print(print_tpl.format( 319 | name = "`{}`;".format(benchmark["name"]), 320 | keys = dict_keys, 321 | size = len(o), 322 | type = type(o).__name__, 323 | time = bench_res[0], 324 | sigma = bench_res[1], 325 | )) 326 | 327 | print(sep_major * sep_n) 328 | 329 | 330 | if __name__ == "__main__": 331 | import sys 332 | 333 | num = None 334 | argv = sys.argv 335 | len_argv = len(argv) 336 | max_positional_args = 1 337 | max_len_argv = max_positional_args + 1 338 | 339 | if len_argv > max_len_argv: 340 | raise ValueError( 341 | f"{__name__} must not accept more than " + 342 | f"{max_positional_args} positional command-line parameters" 343 | ) 344 | 345 | number_arg_pos = 1 346 | 347 | if len_argv == number_arg_pos + 1: 348 | num = int(argv[number_arg_pos]) 349 | 350 | main(num) 351 | -------------------------------------------------------------------------------- /test/common.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | import sys 3 | from collections.abc import MutableMapping 4 | from copy import deepcopy 5 | 6 | import pytest 7 | 8 | from .base import FrozendictTestBase 9 | 10 | pyversion = sys.version_info 11 | pyversion_major = pyversion[0] 12 | pyversion_minor = pyversion[1] 13 | 14 | 15 | class Map(MutableMapping): 16 | def __init__(self, *args, **kwargs): 17 | self._dict = dict(*args, **kwargs) 18 | 19 | def __getitem__(self, key): 20 | return self._dict[key] 21 | 22 | def __setitem__(self, key, value): 23 | self._dict[key] = value 24 | 25 | def __delitem__(self, key): 26 | del self._dict[key] 27 | 28 | def __iter__(self): 29 | return iter(self._dict) 30 | 31 | def __len__(self): 32 | return len(self._dict) 33 | 34 | 35 | # noinspection PyMethodMayBeStatic 36 | class FrozendictCommonTest(FrozendictTestBase): 37 | @property 38 | def is_mapping_implemented(self): 39 | return self.c_ext or ( 40 | pyversion_major > 3 or 41 | (pyversion_major == 3 and pyversion_minor > 9) 42 | ) 43 | 44 | @property 45 | def is_reversed_implemented(self): 46 | return self.c_ext or ( 47 | pyversion_major > 3 or 48 | (pyversion_major == 3 and pyversion_minor > 7) 49 | ) 50 | 51 | #################################################################### 52 | # main tests 53 | 54 | def test_bool_false(self, fd_empty): 55 | assert not fd_empty 56 | 57 | def test_constructor_kwargs(self, fd2, fd_dict_2): 58 | assert self.FrozendictClass(**fd_dict_2) == fd2 59 | 60 | def test_constructor_self(self, fd): 61 | assert fd == self.FrozendictClass(fd, Guzzanti="Corrado") 62 | 63 | def test_constructor_generator(self, fd, generator_seq2): 64 | assert fd == self.FrozendictClass( 65 | generator_seq2, 66 | Guzzanti="Corrado" 67 | ) 68 | 69 | def test_constructor_hole(self, fd_hole, fd_dict_hole): 70 | assert fd_hole == self.FrozendictClass(fd_dict_hole) 71 | 72 | def test_constructor_map(self, fd_dict): 73 | assert self.FrozendictClass(Map(fd_dict)) == fd_dict 74 | 75 | def test_normalget(self, fd): 76 | assert fd["Guzzanti"] == "Corrado" 77 | 78 | def test_keyerror(self, fd): 79 | with pytest.raises(KeyError): 80 | # noinspection PyStatementEffect 81 | fd["Brignano"] 82 | 83 | def test_len(self, fd, fd_dict): 84 | assert len(fd) == len(fd_dict) 85 | 86 | def test_in_true(self, fd): 87 | assert "Guzzanti" in fd 88 | 89 | def test_not_in_false(self, fd): 90 | assert not ("Guzzanti" not in fd) 91 | 92 | def test_in_false(self, fd): 93 | assert not ("Brignano" in fd) 94 | 95 | def test_not_in_true(self, fd): 96 | assert "Brignano" not in fd 97 | 98 | def test_bool_true(self, fd): 99 | assert fd 100 | 101 | def test_deepcopy_unhashable(self, fd_unhashable): 102 | fd_copy = deepcopy(fd_unhashable) 103 | assert fd_copy == fd_unhashable 104 | assert fd_copy is not fd_unhashable 105 | 106 | def test_not_equal(self, fd, fd2, fd_sabina): 107 | assert fd != fd_sabina 108 | assert fd != fd2 109 | assert not (fd == fd_sabina) 110 | assert not (fd == fd2) 111 | 112 | def test_equals_dict(self, fd, fd_dict): 113 | assert fd == fd_dict 114 | 115 | @pytest.mark.parametrize( 116 | "protocol", 117 | range(pickle.HIGHEST_PROTOCOL + 1) 118 | ) 119 | def test_pickle(self, fd, protocol): 120 | # noinspection PyTypeChecker 121 | dump = pickle.dumps(fd, protocol=protocol) 122 | assert dump 123 | assert pickle.loads(dump) == fd 124 | 125 | def test_constructor_iterator(self, fd, fd_items): 126 | assert self.FrozendictClass(fd_items) == fd 127 | 128 | def test_todict(self, fd, fd_dict): 129 | assert dict(fd) == fd_dict 130 | 131 | def test_get(self, fd): 132 | assert fd.get("Guzzanti") == "Corrado" 133 | 134 | def test_get_fail(self, fd): 135 | default = object() 136 | assert fd.get("Brignano", default) is default 137 | 138 | def test_keys(self, fd, fd_dict): 139 | assert tuple(fd.keys()) == tuple(fd_dict.keys()) 140 | 141 | def test_values(self, fd, fd_dict): 142 | assert tuple(fd.values()) == tuple(fd_dict.values()) 143 | 144 | def test_items(self, fd, fd_dict): 145 | assert tuple(fd.items()) == tuple(fd_dict.items()) 146 | 147 | def test_fromkeys(self, fd_sabina): 148 | keys = ["Corrado", "Sabina"] 149 | f = self.FrozendictClass.fromkeys(keys, "Guzzanti") 150 | assert f == fd_sabina 151 | 152 | def test_fromkeys_dict(self, fd_sabina, fd_dict_sabina): 153 | f = self.FrozendictClass.fromkeys(fd_dict_sabina, "Guzzanti") 154 | assert f == fd_sabina 155 | 156 | def test_fromkeys_set(self, fd_sabina, fd_dict_sabina): 157 | f = self.FrozendictClass.fromkeys( 158 | set(fd_dict_sabina), 159 | "Guzzanti" 160 | ) 161 | 162 | assert f == fd_sabina 163 | 164 | def test_repr(self, fd, fd_dict, module_prefix): 165 | classname = self.FrozendictClass.__name__ 166 | dict_repr = repr(fd_dict) 167 | assert repr(fd) == f"{module_prefix}{classname}({dict_repr})" 168 | 169 | def test_str(self, fd, fd_dict, module_prefix): 170 | classname = self.FrozendictClass.__name__ 171 | assert str(fd) == f"{module_prefix}{classname}({repr(fd_dict)})" 172 | 173 | def test_format(self, fd, fd_dict, module_prefix): 174 | classname = self.FrozendictClass.__name__ 175 | dict_repr = repr(fd_dict) 176 | assert format(fd) == f"{module_prefix}{classname}({dict_repr})" 177 | 178 | def test_iter(self, fd): 179 | items = [] 180 | 181 | for x in iter(fd): 182 | items.append((x, fd[x])) 183 | 184 | assert tuple(items) == tuple(fd.items()) 185 | 186 | def test_sum(self, fd, fd_dict, fd_dict_sabina): 187 | new_fd = fd | fd_dict_sabina 188 | assert type(new_fd) is self.FrozendictClass 189 | new_dict = dict(fd_dict) 190 | new_dict.update(fd_dict_sabina) 191 | assert new_fd == new_dict 192 | 193 | def test_union(self, fd_dict, fd_sabina): 194 | new_fd = self.FrozendictClass(fd_dict) 195 | id_fd = id(new_fd) 196 | new_fd |= fd_sabina 197 | assert type(new_fd) is self.FrozendictClass 198 | assert id_fd != id(new_fd) 199 | new_dict = dict(fd_dict) 200 | new_dict.update(fd_sabina) 201 | assert new_fd == new_dict 202 | 203 | def test_reversed(self, fd, fd_dict): 204 | assert (tuple(reversed(fd)) == tuple(reversed(tuple(fd_dict)))) 205 | 206 | def test_iter_len(self, fd): 207 | assert iter(fd).__length_hint__() >= 0 208 | 209 | def test_keys_len(self, fd): 210 | assert len(fd.keys()) == len(fd) 211 | 212 | def test_items_len(self, fd): 213 | assert len(fd.items()) == len(fd) 214 | 215 | def test_values_len(self, fd): 216 | assert len(fd.values()) == len(fd) 217 | 218 | def test_equal_keys(self, fd, fd_dict): 219 | assert fd.keys() == fd_dict.keys() 220 | 221 | def test_equal_items(self, fd, fd_dict): 222 | assert fd.items() == fd_dict.items() 223 | 224 | def test_in_keys(self, fd): 225 | assert "Guzzanti" in fd.keys() 226 | 227 | def test_in_items(self, fd): 228 | assert ("Guzzanti", "Corrado") in fd.items() 229 | 230 | def test_disjoint_true_keys(self, fd, fd_sabina): 231 | assert fd.keys().isdisjoint(fd_sabina) 232 | 233 | def test_disjoint_true_items(self, fd, fd_sabina): 234 | assert fd.items().isdisjoint(fd_sabina.items()) 235 | 236 | def test_disjoint_false_keys(self, fd): 237 | assert not fd.keys().isdisjoint(fd) 238 | 239 | def test_disjoint_false_items(self, fd): 240 | assert not fd.items().isdisjoint(fd.items()) 241 | 242 | def test_sub_keys(self, fd, fd_dict, fd2, fd_dict_2): 243 | res = frozenset(fd_dict.keys()) - frozenset(fd_dict_2.keys()) 244 | assert fd.keys() - fd2.keys() == res 245 | 246 | def test_sub_items(self, fd, fd_dict, fd2, fd_dict_2): 247 | res = frozenset(fd_dict.items()) - frozenset(fd_dict_2.items()) 248 | assert fd.items() - fd2.items() == res 249 | 250 | def test_and_keys(self, fd, fd_dict, fd2, fd_dict_2): 251 | res = frozenset(fd_dict.keys()) & frozenset(fd_dict_2.keys()) 252 | assert fd.keys() & fd2.keys() == res 253 | 254 | def test_and_items(self, fd, fd_dict, fd2, fd_dict_2): 255 | res = frozenset(fd_dict.items()) & frozenset(fd_dict_2.items()) 256 | assert fd.items() & fd2.items() == res 257 | 258 | def test_or_keys(self, fd, fd_dict, fd2, fd_dict_2): 259 | res = frozenset(fd_dict.keys()) | frozenset(fd_dict_2.keys()) 260 | assert fd.keys() | fd2.keys() == res 261 | 262 | def test_or_items(self, fd, fd_dict, fd2, fd_dict_2): 263 | res = frozenset(fd_dict.items()) | frozenset(fd_dict_2.items()) 264 | assert fd.items() | fd2.items() == res 265 | 266 | def test_xor_keys(self, fd, fd_dict, fd2, fd_dict_2): 267 | res = frozenset(fd_dict.keys()) ^ frozenset(fd_dict_2.keys()) 268 | assert fd.keys() ^ fd2.keys() == res 269 | 270 | def test_xor_items(self, fd, fd_dict, fd2, fd_dict_2): 271 | res = frozenset(fd_dict.items()) ^ frozenset(fd_dict_2.items()) 272 | assert fd.items() ^ fd2.items() == res 273 | 274 | @pytest.mark.parametrize( 275 | "protocol", 276 | range(pickle.HIGHEST_PROTOCOL + 1) 277 | ) 278 | def test_pickle_iter_key(self, fd, protocol): 279 | orig = iter(fd.keys()) 280 | # noinspection PyTypeChecker 281 | dump = pickle.dumps(orig, protocol=protocol) 282 | assert dump 283 | assert tuple(pickle.loads(dump)) == tuple(orig) 284 | 285 | @pytest.mark.parametrize( 286 | "protocol", 287 | range(pickle.HIGHEST_PROTOCOL + 1) 288 | ) 289 | def test_pickle_iter_item(self, fd, protocol): 290 | orig = iter(fd.items()) 291 | # noinspection PyTypeChecker 292 | dump = pickle.dumps(orig, protocol=protocol) 293 | assert dump 294 | assert tuple(pickle.loads(dump)) == tuple(orig) 295 | 296 | @pytest.mark.parametrize( 297 | "protocol", 298 | range(pickle.HIGHEST_PROTOCOL + 1) 299 | ) 300 | def test_pickle_iter_value(self, fd, protocol): 301 | orig = iter(fd.values()) 302 | # noinspection PyTypeChecker 303 | dump = pickle.dumps(orig, protocol=protocol) 304 | assert dump 305 | assert tuple(pickle.loads(dump)) == tuple(orig) 306 | 307 | def test_lt_key(self, fd, fd_hole): 308 | assert fd_hole.keys() < fd.keys() 309 | 310 | def test_gt_key(self, fd, fd_hole): 311 | assert fd.keys() > fd_hole.keys() 312 | 313 | def test_le_key(self, fd, fd_hole): 314 | assert fd_hole.keys() <= fd.keys() 315 | assert fd.keys() <= fd.keys() 316 | 317 | def test_ge_key(self, fd, fd_hole): 318 | assert fd.keys() >= fd_hole.keys() 319 | assert fd.keys() >= fd.keys() 320 | 321 | def test_lt_item(self, fd, fd_hole): 322 | assert fd_hole.items() < fd.items() 323 | 324 | def test_gt_item(self, fd, fd_hole): 325 | assert fd.items() > fd_hole.items() 326 | 327 | def test_le_item(self, fd, fd_hole): 328 | assert fd_hole.items() <= fd.items() 329 | assert fd.items() <= fd.items() 330 | 331 | def test_ge_item(self, fd, fd_hole): 332 | assert fd.items() >= fd_hole.items() 333 | assert fd.items() >= fd.items() 334 | 335 | def test_mapping_keys(self, fd): 336 | if not self.is_mapping_implemented: 337 | pytest.skip("mapping not implemented") 338 | 339 | assert fd.keys().mapping == fd 340 | 341 | def test_mapping_items(self, fd): 342 | if not self.is_mapping_implemented: 343 | pytest.skip("mapping not implemented") 344 | 345 | assert fd.items().mapping == fd 346 | 347 | def test_mapping_values(self, fd): 348 | if not self.is_mapping_implemented: 349 | pytest.skip("mapping not implemented") 350 | 351 | assert fd.values().mapping == fd 352 | 353 | def test_reversed_keys(self, fd, fd_dict): 354 | if not self.is_reversed_implemented: 355 | pytest.skip("reversed not implemented") 356 | 357 | fd_keys = tuple(reversed(fd.keys())) 358 | dict_keys = tuple(reversed(tuple(fd_dict.keys()))) 359 | assert fd_keys == dict_keys 360 | 361 | def test_reversed_items(self, fd, fd_dict): 362 | if not self.is_reversed_implemented: 363 | pytest.skip("reversed not implemented") 364 | 365 | fd_items = tuple(reversed(fd.items())) 366 | dict_items = tuple(reversed(tuple(fd_dict.items()))) 367 | assert fd_items == dict_items 368 | 369 | def test_reversed_values(self, fd, fd_dict): 370 | if not self.is_reversed_implemented: 371 | pytest.skip("reversed not implemented") 372 | 373 | fd_values = tuple(reversed(fd.values())) 374 | dict_values = tuple(reversed(tuple(fd_dict.values()))) 375 | assert fd_values == dict_values 376 | 377 | #################################################################### 378 | # frozendict-only tests 379 | 380 | def test_hash(self, fd, fd_eq): 381 | assert hash(fd) 382 | assert hash(fd) == hash(fd_eq) 383 | 384 | def test_unhashable_value(self, fd_unhashable): 385 | with pytest.raises(TypeError): 386 | hash(fd_unhashable) 387 | 388 | # hash is cached 389 | with pytest.raises(TypeError): 390 | hash(fd_unhashable) 391 | 392 | def test_set_replace(self, fd_dict, generator_seq2): 393 | items = tuple(generator_seq2) 394 | d2 = dict(items) 395 | assert fd_dict != d2 396 | f2 = self.FrozendictClass(items) 397 | fd3 = f2.set("Guzzanti", "Corrado") 398 | assert fd3 == fd_dict 399 | 400 | def test_set_add(self, fd_dict): 401 | d2 = dict(fd_dict, a="b") 402 | assert fd_dict != d2 403 | f2 = self.FrozendictClass(fd_dict) 404 | fd3 = f2.set("a", "b") 405 | assert fd3 == d2 406 | 407 | def test_setdefault_notinsert(self, fd, fd_dict): 408 | assert fd.setdefault("Hicks") is fd 409 | 410 | def test_setdefault_insert_default(self, fd, fd_dict): 411 | fd_dict.setdefault("Allen") 412 | assert fd_dict == fd.setdefault("Allen") 413 | 414 | def test_setdefault_insert(self, fd, fd_dict): 415 | fd_dict.setdefault("Allen", "Woody") 416 | assert fd_dict == fd.setdefault("Allen", "Woody") 417 | 418 | def test_del(self, fd_dict): 419 | d2 = dict(fd_dict) 420 | d2["a"] = "b" 421 | f2 = self.FrozendictClass(d2) 422 | fd3 = f2.delete("a") 423 | assert fd3 == fd_dict 424 | 425 | def test_key(self, fd): 426 | assert fd.key() == fd.key(0) == "Guzzanti" 427 | assert fd.key(1) == fd.key(-2) == "Hicks" 428 | 429 | def test_key_out_of_range(self, fd): 430 | with pytest.raises(IndexError): 431 | fd.key(3) 432 | 433 | with pytest.raises(IndexError): 434 | fd.key(-4) 435 | 436 | def test_value(self, fd): 437 | assert fd.value() == fd.value(0) == "Corrado" 438 | assert fd.value(1) == fd.value(-2) == "Bill" 439 | 440 | def test_value_out_of_range(self, fd): 441 | with pytest.raises(IndexError): 442 | fd.value(3) 443 | 444 | with pytest.raises(IndexError): 445 | fd.value(-4) 446 | 447 | def test_item(self, fd): 448 | assert fd.item() == fd.item(0) == ("Guzzanti", "Corrado") 449 | assert fd.item(1) == fd.item(-2) == ("Hicks", "Bill") 450 | 451 | def test_item_out_of_range(self, fd): 452 | with pytest.raises(IndexError): 453 | fd.item(3) 454 | 455 | with pytest.raises(IndexError): 456 | fd.item(-4) 457 | 458 | #################################################################### 459 | # immutability tests 460 | 461 | def test_normalset(self, fd): 462 | with pytest.raises(TypeError): 463 | fd["Guzzanti"] = "Caterina" 464 | 465 | def test_del_error(self, fd): 466 | with pytest.raises(TypeError): 467 | del fd["Guzzanti"] 468 | 469 | def test_clear(self, fd): 470 | with pytest.raises(AttributeError): 471 | fd.clear() 472 | 473 | def test_pop(self, fd): 474 | with pytest.raises(AttributeError): 475 | fd.pop("Guzzanti") 476 | 477 | def test_popitem(self, fd): 478 | with pytest.raises(AttributeError): 479 | fd.popitem() 480 | 481 | def test_update(self, fd): 482 | with pytest.raises(AttributeError): 483 | fd.update({"Brignano": "Enrico"}) 484 | 485 | def test_delattr(self, fd): 486 | with pytest.raises(AttributeError): 487 | del fd.items 488 | -------------------------------------------------------------------------------- /test/debug.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from typing import Callable, Union 3 | 4 | import frozendict 5 | 6 | assert frozendict.c_ext 7 | 8 | from frozendict import frozendict 9 | from uuid import uuid4 10 | import pickle 11 | from copy import copy, deepcopy 12 | from collections.abc import MutableMapping 13 | import tracemalloc 14 | import gc 15 | import sys 16 | 17 | 18 | def getUuid(): 19 | return str(uuid4()) 20 | 21 | 22 | class F(frozendict): 23 | def __new__(cls, *args, **kwargs): 24 | return super().__new__(cls, *args, **kwargs) 25 | 26 | 27 | class FMissing(frozendict): 28 | def __new__(cls, *args, **kwargs): 29 | return super().__new__(cls, *args, **kwargs) 30 | 31 | def __missing__(self, key): 32 | return key 33 | 34 | 35 | class Map(MutableMapping): 36 | def __init__(self, *args, **kwargs): 37 | self._dict = dict(*args, **kwargs) 38 | 39 | def __getitem__(self, key): 40 | return self._dict[key] 41 | 42 | def __setitem__(self, key, value): 43 | self._dict[key] = value 44 | 45 | def __delitem__(self, key): 46 | del self._dict[key] 47 | 48 | def __iter__(self): 49 | return iter(self._dict) 50 | 51 | def __len__(self): 52 | return len(self._dict) 53 | 54 | 55 | def print_info(klass, iterations, func: Union[Callable, str]): 56 | try: 57 | name = func.__name__ 58 | except AttributeError: 59 | name = func 60 | 61 | print( 62 | ( 63 | f"Class = {klass.__name__} - Loops: {iterations} - " + 64 | f"Evaluating: {name}" 65 | ), 66 | flush=True 67 | ) 68 | 69 | 70 | def print_sep(): 71 | print("-" * 72, flush=True) 72 | 73 | 74 | def trace(iterations = 100, mult = 10.0): 75 | def decorator(func): 76 | def wrapper(): 77 | header1 = ( 78 | f"Loops: {iterations} - Multiplier: {mult} " + 79 | f"Evaluating: {func.__name__}" 80 | ) 81 | 82 | err = False 83 | 84 | for i in range(7): 85 | print(header1, flush=True) 86 | 87 | tracemalloc.start() 88 | 89 | gc.collect() 90 | 91 | snapshot1 = tracemalloc.take_snapshot().filter_traces( 92 | (tracemalloc.Filter(True, __file__),) 93 | ) 94 | 95 | for j in range(iterations): 96 | func() 97 | 98 | gc.collect() 99 | 100 | snapshot2 = tracemalloc.take_snapshot().filter_traces( 101 | (tracemalloc.Filter(True, __file__),) 102 | ) 103 | 104 | top_stats = snapshot2.compare_to(snapshot1, 'lineno') 105 | tracemalloc.stop() 106 | err = False 107 | 108 | for stat in top_stats: 109 | if stat.count_diff * mult > iterations: 110 | header = ( 111 | f"Error. count diff: {stat.count_diff}, " + 112 | f"stat: {stat}" 113 | ) 114 | 115 | print(header, flush=True) 116 | err = True 117 | 118 | if not err: 119 | break 120 | 121 | if err: 122 | sys.exit(1) 123 | 124 | return wrapper 125 | 126 | return decorator 127 | 128 | 129 | key_in = getUuid() 130 | dict_1 = {key_in: 0} 131 | dict_tmp = {getUuid(): i for i in range(1, 1000)} 132 | dict_1.update(dict_tmp) 133 | generator_1 = ((key, val) for key, val in dict_1.items()) 134 | key_notin = getUuid() 135 | dict_2 = {getUuid(): i for i in range(1000)} 136 | dict_3 = {i: i for i in range(1000)} 137 | dict_hole = dict_1.copy() 138 | del dict_hole[key_in] 139 | dict_unashable = dict_1.copy() 140 | dict_unashable.update({getUuid(): []}) 141 | dict_1_items = tuple(dict_1.items()) 142 | dict_1_keys = tuple(dict_1.keys()) 143 | dict_1_keys_set = set(dict_1_keys) 144 | 145 | functions = [] 146 | 147 | 148 | @trace(mult = 1.4) 149 | def func_1(): 150 | pickle.loads(pickle.dumps(fd_1)) 151 | 152 | 153 | functions.append(func_1) 154 | 155 | 156 | @trace() 157 | def func_2(): 158 | pickle.loads(pickle.dumps(iter(fd_1.keys()))) 159 | 160 | 161 | functions.append(func_2) 162 | 163 | 164 | @trace() 165 | def func_3(): 166 | pickle.loads(pickle.dumps(iter(fd_1.items()))) 167 | 168 | 169 | functions.append(func_3) 170 | 171 | 172 | @trace() 173 | def func_4(): 174 | pickle.loads(pickle.dumps(iter(fd_1.values()))) 175 | 176 | 177 | functions.append(func_4) 178 | 179 | 180 | @trace() 181 | def func_5(): 182 | frozendict_class({}) 183 | 184 | 185 | functions.append(func_5) 186 | 187 | 188 | @trace() 189 | def func_7(): 190 | frozendict_class([]) 191 | 192 | 193 | functions.append(func_7) 194 | 195 | 196 | @trace() 197 | def func_8(): 198 | frozendict_class({}, **{}) 199 | 200 | 201 | functions.append(func_8) 202 | 203 | 204 | @trace() 205 | def func_9(): 206 | frozendict_class(**dict_1) 207 | 208 | 209 | functions.append(func_9) 210 | 211 | 212 | @trace() 213 | def func_10(): 214 | frozendict_class(dict_1, **dict_2) 215 | 216 | 217 | functions.append(func_10) 218 | 219 | 220 | @trace() 221 | def func_11(): 222 | frozendict_class(fd_1) 223 | 224 | 225 | functions.append(func_11) 226 | 227 | 228 | @trace() 229 | def func_12(): 230 | frozendict_class(generator_1) 231 | 232 | 233 | functions.append(func_12) 234 | 235 | 236 | @trace() 237 | def func_13(): 238 | frozendict_class(dict_hole) 239 | 240 | 241 | functions.append(func_13) 242 | 243 | 244 | @trace() 245 | def func_14(): 246 | fd_1.copy() 247 | 248 | 249 | functions.append(func_14) 250 | 251 | 252 | @trace() 253 | def func_15(): 254 | # noinspection PyStatementEffect 255 | fd_1 == dict_1 256 | 257 | 258 | functions.append(func_15) 259 | 260 | 261 | @trace() 262 | def func_16(): 263 | # noinspection PyStatementEffect 264 | fd_1 == fd_1 265 | 266 | 267 | functions.append(func_16) 268 | 269 | 270 | @trace() 271 | def func_17(): 272 | # noinspection PyStatementEffect 273 | fd_1 != dict_hole 274 | 275 | 276 | functions.append(func_17) 277 | 278 | 279 | @trace() 280 | def func_18(): 281 | # noinspection PyStatementEffect 282 | fd_1 != dict_2 283 | 284 | 285 | functions.append(func_18) 286 | 287 | 288 | @trace() 289 | def func_19(): 290 | # noinspection PyStatementEffect 291 | fd_1 == dict_hole 292 | 293 | 294 | functions.append(func_19) 295 | 296 | 297 | @trace() 298 | def func_20(): 299 | # noinspection PyStatementEffect 300 | fd_1 == dict_2 301 | 302 | 303 | functions.append(func_20) 304 | 305 | 306 | @trace() 307 | def func_21(): 308 | frozendict_class(dict_1) 309 | 310 | 311 | functions.append(func_21) 312 | 313 | 314 | @trace() 315 | def func_22(): 316 | frozendict_class(dict_1_items) 317 | 318 | 319 | functions.append(func_22) 320 | 321 | 322 | @trace() 323 | def func_23(): 324 | tuple(fd_1.keys()) 325 | 326 | 327 | functions.append(func_23) 328 | 329 | 330 | @trace() 331 | def func_24(): 332 | tuple(fd_1.values()) 333 | 334 | 335 | functions.append(func_24) 336 | 337 | 338 | @trace() 339 | def func_25(): 340 | tuple(fd_1.items()) 341 | 342 | 343 | functions.append(func_25) 344 | 345 | 346 | @trace() 347 | def func_26(): 348 | frozendict_class.fromkeys(dict_1) 349 | 350 | 351 | functions.append(func_26) 352 | 353 | 354 | @trace() 355 | def func_27(): 356 | frozendict_class.fromkeys(dict_1, 1) 357 | 358 | 359 | functions.append(func_27) 360 | 361 | 362 | @trace() 363 | def func_28(): 364 | frozendict_class.fromkeys(dict_1_keys) 365 | 366 | 367 | functions.append(func_28) 368 | 369 | 370 | @trace() 371 | def func_29(): 372 | frozendict_class.fromkeys(dict_1_keys, 1) 373 | 374 | 375 | functions.append(func_29) 376 | 377 | 378 | @trace() 379 | def func_30(): 380 | frozendict_class.fromkeys(dict_1_keys_set) 381 | 382 | 383 | functions.append(func_30) 384 | 385 | 386 | @trace() 387 | def func_31(): 388 | frozendict_class.fromkeys(dict_1_keys_set, 1) 389 | 390 | 391 | functions.append(func_31) 392 | 393 | 394 | @trace() 395 | def func_32(): 396 | repr(fd_1) 397 | 398 | 399 | functions.append(func_32) 400 | 401 | 402 | @trace() 403 | def func_33(): 404 | fd_1 | dict_2 405 | 406 | 407 | functions.append(func_33) 408 | 409 | 410 | @trace() 411 | def func_34(): 412 | hash(fd_1) 413 | 414 | 415 | functions.append(func_34) 416 | 417 | 418 | @trace() 419 | def func_35(): 420 | # noinspection PyStatementEffect 421 | frozendict_class() == frozendict_class() 422 | 423 | 424 | functions.append(func_35) 425 | 426 | 427 | @trace() 428 | def func_36(): 429 | tuple(reversed(fd_1)) 430 | 431 | 432 | functions.append(func_36) 433 | 434 | 435 | @trace() 436 | def func_37(): 437 | tuple(reversed(fd_1.keys())) 438 | 439 | 440 | functions.append(func_37) 441 | 442 | 443 | @trace() 444 | def func_38(): 445 | tuple(reversed(fd_1.items())) 446 | 447 | 448 | functions.append(func_38) 449 | 450 | 451 | @trace() 452 | def func_39(): 453 | tuple(reversed(fd_1.values())) 454 | 455 | 456 | functions.append(func_39) 457 | 458 | 459 | @trace() 460 | def func_40(): 461 | iter(fd_1).__length_hint__() 462 | 463 | 464 | functions.append(func_40) 465 | 466 | 467 | @trace() 468 | def func_41(): 469 | len(fd_1) 470 | 471 | 472 | functions.append(func_41) 473 | 474 | 475 | @trace() 476 | def func_42(): 477 | len(fd_1.keys()) 478 | 479 | 480 | functions.append(func_42) 481 | 482 | 483 | @trace() 484 | def func_43(): 485 | len(fd_1.items()) 486 | 487 | 488 | functions.append(func_43) 489 | 490 | 491 | @trace() 492 | def func_44(): 493 | len(fd_1.values()) 494 | 495 | 496 | functions.append(func_44) 497 | 498 | 499 | @trace() 500 | def func_45(): 501 | # noinspection PyStatementEffect,PyUnresolvedReferences 502 | fd_1.keys().mapping == fd_1 503 | 504 | 505 | functions.append(func_45) 506 | 507 | 508 | @trace() 509 | def func_46(): 510 | # noinspection PyStatementEffect, PyUnresolvedReferences 511 | fd_1.items().mapping == fd_1 512 | 513 | 514 | functions.append(func_46) 515 | 516 | 517 | @trace() 518 | def func_47(): 519 | # noinspection PyStatementEffect, PyUnresolvedReferences 520 | fd_1.values().mapping == fd_1 521 | 522 | 523 | functions.append(func_47) 524 | 525 | 526 | @trace() 527 | def func_48(): 528 | # noinspection PyStatementEffect 529 | fd_1[key_in] 530 | 531 | 532 | functions.append(func_48) 533 | 534 | 535 | @trace() 536 | def func_49(): 537 | fd_1.get(key_in) 538 | 539 | 540 | functions.append(func_49) 541 | 542 | 543 | @trace() 544 | def func_50(): 545 | fd_1.get(key_notin) 546 | 547 | 548 | functions.append(func_50) 549 | 550 | 551 | @trace() 552 | def func_51(): 553 | fd_1.get(key_notin, 1) 554 | 555 | 556 | functions.append(func_51) 557 | 558 | 559 | @trace() 560 | def func_52(): 561 | # noinspection PyStatementEffect 562 | key_in in fd_1 563 | 564 | 565 | functions.append(func_52) 566 | 567 | 568 | @trace() 569 | def func_53(): 570 | # noinspection PyStatementEffect 571 | key_notin in fd_1 572 | 573 | 574 | functions.append(func_53) 575 | 576 | 577 | @trace() 578 | def func_54(): 579 | fd_1.copy() 580 | 581 | 582 | functions.append(func_54) 583 | 584 | 585 | @trace() 586 | def func_55(): 587 | copy(fd_1) 588 | 589 | 590 | functions.append(func_55) 591 | 592 | 593 | @trace() 594 | def func_56(): 595 | deepcopy(fd_1) 596 | 597 | 598 | functions.append(func_56) 599 | 600 | 601 | @trace() 602 | def func_57(): 603 | deepcopy(fd_unashable) 604 | 605 | 606 | functions.append(func_57) 607 | 608 | 609 | @trace() 610 | def func_58(): 611 | # noinspection PyStatementEffect 612 | fd_1.keys() == dict_1.keys() 613 | 614 | 615 | functions.append(func_58) 616 | 617 | 618 | @trace() 619 | def func_59(): 620 | # noinspection PyStatementEffect 621 | fd_1.items() == dict_1.items() 622 | 623 | 624 | functions.append(func_59) 625 | 626 | 627 | @trace() 628 | def func_60(): 629 | # noinspection PyStatementEffect 630 | key_notin in fd_1.keys() 631 | 632 | 633 | functions.append(func_60) 634 | 635 | 636 | @trace() 637 | def func_61(): 638 | # noinspection PyStatementEffect 639 | (key_notin, 0) in fd_1.items() 640 | 641 | 642 | functions.append(func_61) 643 | 644 | 645 | @trace() 646 | def func_62(): 647 | # noinspection PyStatementEffect 648 | FMissing(fd_1)[0] 649 | 650 | 651 | functions.append(func_62) 652 | 653 | 654 | @trace() 655 | def func_63(): 656 | mp = Map(dict_1) 657 | # noinspection PyStatementEffect 658 | frozendict_class(mp) == dict_1 659 | 660 | 661 | functions.append(func_63) 662 | 663 | 664 | @trace() 665 | def func_64(): 666 | fd_1.keys().isdisjoint(dict_3) 667 | 668 | 669 | functions.append(func_64) 670 | 671 | 672 | @trace() 673 | def func_65(): 674 | fd_1.keys().isdisjoint(fd_1) 675 | 676 | 677 | functions.append(func_65) 678 | 679 | 680 | @trace() 681 | def func_66(): 682 | fd_1.items().isdisjoint(dict_3.items()) 683 | 684 | 685 | functions.append(func_66) 686 | 687 | 688 | @trace() 689 | def func_67(): 690 | fd_1.items().isdisjoint(fd_1.items()) 691 | 692 | 693 | functions.append(func_67) 694 | 695 | 696 | @trace() 697 | def func_68(): 698 | fd_unashable.keys() - fd_1.keys() 699 | 700 | 701 | functions.append(func_68) 702 | 703 | 704 | @trace() 705 | def func_69(): 706 | fd_1.items() - frozendict_class(dict_hole).items() 707 | 708 | 709 | functions.append(func_69) 710 | 711 | 712 | @trace() 713 | def func_70(): 714 | fd_1.keys() & frozendict_class(dict_hole).keys() 715 | 716 | 717 | functions.append(func_70) 718 | 719 | 720 | @trace() 721 | def func_71(): 722 | fd_1.items() & frozendict_class(dict_hole).items() 723 | 724 | 725 | functions.append(func_71) 726 | 727 | 728 | @trace() 729 | def func_72(): 730 | fd_1.keys() | frozendict_class(dict_2).keys() 731 | 732 | 733 | functions.append(func_72) 734 | 735 | 736 | @trace() 737 | def func_73(): 738 | fd_1.items() | frozendict_class(dict_2).items() 739 | 740 | 741 | functions.append(func_73) 742 | 743 | 744 | @trace() 745 | def func_74(): 746 | fd_1.keys() ^ frozendict_class(dict_hole).keys() 747 | 748 | 749 | functions.append(func_74) 750 | 751 | 752 | @trace() 753 | def func_75(): 754 | fd_1.items() ^ frozendict_class(dict_hole).items() 755 | 756 | 757 | functions.append(func_75) 758 | 759 | 760 | @trace() 761 | def func_76(): 762 | frozendict_class(dict_unashable) 763 | 764 | 765 | functions.append(func_76) 766 | 767 | 768 | @trace() 769 | def func_77(): 770 | frozendict_class(dict_3) 771 | 772 | 773 | functions.append(func_77) 774 | 775 | 776 | @trace() 777 | def func_78(): 778 | frozendict_class() 779 | 780 | 781 | functions.append(func_78) 782 | 783 | 784 | @trace() 785 | def func_79(): 786 | # noinspection PyStatementEffect 787 | frozendict_class(dict_hole).keys() < fd_1.keys() 788 | 789 | 790 | functions.append(func_79) 791 | 792 | 793 | @trace() 794 | def func_80(): 795 | # noinspection PyStatementEffect 796 | frozendict_class(dict_hole).keys() <= fd_1.keys() 797 | 798 | 799 | functions.append(func_80) 800 | 801 | 802 | @trace() 803 | def func_81(): 804 | # noinspection PyStatementEffect 805 | frozendict_class(dict_hole).items() < fd_1.items() 806 | 807 | 808 | functions.append(func_81) 809 | 810 | 811 | @trace() 812 | def func_82(): 813 | # noinspection PyStatementEffect 814 | fd_1.keys() > frozendict_class(dict_hole).keys() 815 | 816 | 817 | functions.append(func_82) 818 | 819 | 820 | @trace() 821 | def func_83(): 822 | # noinspection PyStatementEffect 823 | fd_1.keys() >= frozendict_class(dict_hole).keys() 824 | 825 | 826 | functions.append(func_83) 827 | 828 | 829 | @trace() 830 | def func_84(): 831 | # noinspection PyStatementEffect 832 | fd_1.items() > frozendict_class(dict_hole).items() 833 | 834 | 835 | functions.append(func_84) 836 | 837 | 838 | @trace() 839 | def func_85(): 840 | # noinspection PyStatementEffect 841 | fd_1.items() >= frozendict_class(dict_hole).items() 842 | 843 | 844 | functions.append(func_85) 845 | 846 | 847 | @trace() 848 | def func_86(): 849 | fd_1.set(key_in, 1000) 850 | 851 | 852 | functions.append(func_86) 853 | 854 | 855 | @trace() 856 | def func_87(): 857 | fd_1.set(key_notin, 1000) 858 | 859 | 860 | functions.append(func_87) 861 | 862 | 863 | @trace() 864 | def func_88(): 865 | fd_1.delete(key_in) 866 | 867 | 868 | functions.append(func_88) 869 | 870 | 871 | @trace() 872 | def func_89(): 873 | fd_1.setdefault(key_in) 874 | 875 | 876 | functions.append(func_89) 877 | 878 | 879 | @trace() 880 | def func_90(): 881 | fd_1.setdefault(key_notin) 882 | 883 | 884 | functions.append(func_90) 885 | 886 | 887 | @trace() 888 | def func_91(): 889 | fd_1.setdefault(key_notin, 1000) 890 | 891 | 892 | functions.append(func_91) 893 | 894 | 895 | @trace() 896 | def func_92(): 897 | fd_1.key() 898 | 899 | 900 | functions.append(func_92) 901 | 902 | 903 | @trace() 904 | def func_93(): 905 | fd_1.key(0) 906 | 907 | 908 | functions.append(func_93) 909 | 910 | 911 | @trace() 912 | def func_94(): 913 | fd_1.key(-1) 914 | 915 | 916 | functions.append(func_94) 917 | 918 | 919 | @trace() 920 | def func_95(): 921 | fd_1.value() 922 | 923 | 924 | functions.append(func_95) 925 | 926 | 927 | @trace() 928 | def func_96(): 929 | fd_1.value(0) 930 | 931 | 932 | functions.append(func_96) 933 | 934 | 935 | @trace() 936 | def func_97(): 937 | fd_1.value(-1) 938 | 939 | 940 | functions.append(func_97) 941 | 942 | 943 | @trace() 944 | def func_98(): 945 | fd_1.item() 946 | 947 | 948 | functions.append(func_98) 949 | 950 | 951 | @trace() 952 | def func_99(): 953 | fd_1.item(0) 954 | 955 | 956 | functions.append(func_99) 957 | 958 | 959 | @trace() 960 | def func_100(): 961 | fd_1.item(-1) 962 | 963 | 964 | functions.append(func_100) 965 | 966 | 967 | @trace() 968 | def func_101(): 969 | for _ in fd_1: 970 | pass 971 | 972 | 973 | functions.append(func_101) 974 | 975 | 976 | @trace() 977 | def func_102(): 978 | for _ in iter(fd_1): 979 | pass 980 | 981 | 982 | functions.append(func_102) 983 | 984 | 985 | @trace() 986 | def func_103(): 987 | for _ in fd_1.keys(): 988 | pass 989 | 990 | 991 | functions.append(func_103) 992 | 993 | 994 | @trace() 995 | def func_104(): 996 | for _ in fd_1.values(): 997 | pass 998 | 999 | 1000 | functions.append(func_104) 1001 | 1002 | 1003 | @trace() 1004 | def func_105(): 1005 | for _ in fd_1.items(): 1006 | pass 1007 | 1008 | 1009 | functions.append(func_105) 1010 | 1011 | 1012 | @trace() 1013 | def func_106(): 1014 | try: 1015 | hash(fd_unashable) 1016 | except TypeError: 1017 | pass 1018 | 1019 | 1020 | functions.append(func_106) 1021 | 1022 | 1023 | @trace() 1024 | def func_107(): 1025 | try: 1026 | fd_1[key_notin] 1027 | except KeyError: 1028 | pass 1029 | 1030 | 1031 | functions.append(func_107) 1032 | 1033 | 1034 | @trace() 1035 | def func_108(): 1036 | try: 1037 | fd_1.key(len(fd_1)) 1038 | except IndexError: 1039 | pass 1040 | 1041 | 1042 | functions.append(func_108) 1043 | 1044 | 1045 | @trace() 1046 | def func_109(): 1047 | try: 1048 | fd_1.value(len(fd_1)) 1049 | except IndexError: 1050 | pass 1051 | 1052 | 1053 | functions.append(func_109) 1054 | 1055 | 1056 | @trace() 1057 | def func_110(): 1058 | try: 1059 | fd_1.item(len(fd_1)) 1060 | except IndexError: 1061 | pass 1062 | 1063 | 1064 | functions.append(func_110) 1065 | 1066 | 1067 | print_sep() 1068 | 1069 | for frozendict_class in (frozendict, F): 1070 | print_info( 1071 | frozendict_class, 1072 | 1, 1073 | "frozendict_class(dict_1)" 1074 | ) 1075 | 1076 | fd_1 = frozendict_class(dict_1) 1077 | print_sep() 1078 | print_info( 1079 | frozendict_class, 1080 | 1, 1081 | "frozendict_class(dict_unashable)" 1082 | ) 1083 | 1084 | fd_unashable = frozendict_class(dict_unashable) 1085 | print_sep() 1086 | 1087 | for function in functions: 1088 | print( 1089 | f"Class = {frozendict_class.__name__} - ", 1090 | flush = True, 1091 | end = "" 1092 | ) 1093 | 1094 | function() 1095 | 1096 | print_sep() 1097 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # frozendict 2 | ### Table of Contents 3 | * [Introduction](#introduction) 4 | * [Install](#install) 5 | * [API](#api) 6 | * [frozendict API](#frozendict-api) 7 | * [deepfreeze API](#deepfreeze-api) 8 | * [Examples](#examples) 9 | * [frozendict examples](#frozendict-examples) 10 | * [deepfreeze examples](#deepfreeze-examples) 11 | * [Building](#building) 12 | * [Benchmarks](#benchmarks) 13 | 14 | # Introduction 15 | Welcome, fellow programmer, to the house of `frozendict` and 16 | [deepfreeze](#deepfreeze-api)! 17 | 18 | `frozendict` is a simple immutable dictionary. It's fast as `dict`, and 19 | [sometimes faster](https://github.com/Marco-Sulla/python-frozendict#benchmarks)! 20 | 21 | Unlike other similar implementations, immutability is guaranteed: you can't 22 | change the internal variables of the class, and they are all immutable 23 | objects. Reinvoking `__init__` does not alter the object. 24 | 25 | The API is the same as `dict`, without methods that can change the 26 | immutability. So it supports also `fromkeys`, unlike other implementations. 27 | Furthermore, it can be `pickle`d, un`pickle`d and have a hash, if all values 28 | are hashable. 29 | 30 | You can also add any `dict` to a `frozendict` using the `|` operator. The result is a new `frozendict`. 31 | 32 | # Install 33 | 34 | You can install `frozendict` by simply typing in a command line: 35 | 36 | ```bash 37 | pip install frozendict 38 | ``` 39 | 40 | The C Extension is optional by default from version 2.3.5. You can make it mandatory using: 41 | 42 | ```bash 43 | CIBUILDWHEEL=1 pip install frozendict 44 | ``` 45 | 46 | On the contrary, if you want the pure py implementation: 47 | 48 | ```bash 49 | FROZENDICT_PURE_PY=1 pip install frozendict 50 | ``` 51 | 52 | # API 53 | 54 | ## frozendict API 55 | The API is the same of `dict` of Python 3.10, without the methods and operands which alter the map. Additionally, `frozendict` supports these methods: 56 | 57 | ### `__hash__()` 58 | 59 | If all the values of the `frozendict` are hashable, returns a hash, otherwise raises a TypeError. 60 | 61 | ### `set(key, value)` 62 | 63 | It returns a new `frozendict`. If key is already in the original `frozendict`, the new one will have it with the new value associated. Otherwise, the new `frozendict` will contain the new (key, value) item. 64 | 65 | ### `delete(key)` 66 | 67 | It returns a new `frozendict` without the item corresponding to the key. If the key is not present, a KeyError is raised. 68 | 69 | ### `setdefault(key[, default])` 70 | 71 | If key is already in `frozendict`, the object itself is returned unchanged. Otherwise, the new `frozendict` will contain the new (key, default) item. The parameter default defaults to None. 72 | 73 | ### `key([index])` 74 | 75 | It returns the key at the specified index (determined by the insertion order). If index is not passed, it defaults to 0. If the index is negative, the position will be the size of the `frozendict` + index 76 | 77 | ### `value([index])` 78 | Same as `key(index)`, but it returns the value at the given index. 79 | 80 | ### `item([index])` 81 | Same as `key(index)`, but it returns a tuple with (key, value) at the given index. 82 | 83 | ## deepfreeze API 84 | 85 | The `frozendict` _module_ has also these static methods: 86 | 87 | ### `frozendict.deepfreeze(o, custom_converters = None, custom_inverse_converters = None)` 88 | Converts the object and all the objects nested in it, into their immutable 89 | counterparts. 90 | 91 | The conversion map is in `getFreezeConversionMap()`. 92 | 93 | You can register a new conversion using `register()` You can also 94 | pass a map of custom converters with `custom_converters` and a map 95 | of custom inverse converters with `custom_inverse_converters`, 96 | without using `register()`. 97 | 98 | By default, if the type is not registered and has a `__dict__` 99 | attribute, it's converted to the `frozendict` of that `__dict__`. 100 | 101 | This function assumes that hashable == immutable (that is not 102 | always true). 103 | 104 | This function uses recursion, with all the limits of recursions in 105 | Python. 106 | 107 | Where is a good old tail call when you need it? 108 | 109 | ### `frozendict.register(to_convert, converter, *, inverse = False)` 110 | 111 | Adds a `converter` for a type `to_convert`. `converter` 112 | must be callable. The new converter will be used by `deepfreeze()` 113 | and has precedence over any previous converter. 114 | 115 | If `to_covert` has already a converter, a FreezeWarning is raised. 116 | 117 | If `inverse` is True, the conversion is considered from an immutable 118 | type to a mutable one. This make it possible to convert mutable 119 | objects nested in the registered immutable one. 120 | 121 | ### `frozendict.unregister(type, inverse = False)` 122 | Unregister a type from custom conversion. If `inverse` is `True`, 123 | the unregistered conversion is an inverse conversion 124 | (see `register()`). 125 | 126 | # Examples 127 | 128 | ## frozendict examples 129 | ```python 130 | from frozendict import frozendict 131 | 132 | fd = frozendict(Guzzanti = "Corrado", Hicks = "Bill") 133 | 134 | print(fd) 135 | # frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill'}) 136 | 137 | frozendict({"Guzzanti": "Corrado", "Hicks": "Bill"}) 138 | # frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill'}) 139 | 140 | hash(fd) 141 | # 5833699487320513741 142 | 143 | fd_unhashable = frozendict({1: []}) 144 | hash(fd_unhashable) 145 | # TypeError: Not all values are hashable. 146 | 147 | frozendict({frozendict(nested = 4, key = 2): 42}) 148 | # frozendict({frozendict({'nested': 4, 'key': 2}): 42}) 149 | 150 | fd | {1: 2} 151 | # frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill', 1: 2}) 152 | 153 | fd.set(1, 2) 154 | # frozendict.frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill', 1: 2}) 155 | 156 | fd.set("Guzzanti", "Sabina") 157 | # frozendict.frozendict({'Guzzanti': 'Sabina', 'Hicks': 'Bill'}) 158 | 159 | fd.delete("Guzzanti") 160 | # frozendict.frozendict({'Hicks': 'Bill'}) 161 | 162 | fd.setdefault("Guzzanti", "Sabina") 163 | # frozendict.frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill'}) 164 | 165 | fd.setdefault(1, 2) 166 | # frozendict.frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill', 1: 2}) 167 | 168 | fd.key() 169 | # 'Guzzanti' 170 | 171 | fd.value(1) 172 | # 'Bill' 173 | 174 | fd.item(-1) 175 | # (1, 2) 176 | 177 | print(fd["Guzzanti"]) 178 | # Corrado 179 | 180 | fd["Brignano"] 181 | # KeyError: 'Brignano' 182 | 183 | len(fd) 184 | # 2 185 | 186 | "Guzzanti" in fd 187 | # True 188 | 189 | "Guzzanti" not in fd 190 | # False 191 | 192 | "Brignano" in fd 193 | # False 194 | 195 | fd5 = frozendict(fd) 196 | id_fd5 = id(fd5) 197 | fd5 |= {1: 2} 198 | fd5 199 | # frozendict.frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill', 1: 2}) 200 | id(fd5) != id_fd5 201 | # True 202 | 203 | fd2 = fd.copy() 204 | fd2 == fd 205 | # True 206 | 207 | fd3 = frozendict(fd) 208 | fd3 == fd 209 | # True 210 | 211 | fd4 = frozendict({"Hicks": "Bill", "Guzzanti": "Corrado"}) 212 | 213 | print(fd4) 214 | # frozendict({'Hicks': 'Bill', 'Guzzanti': 'Corrado'}) 215 | 216 | fd4 == fd 217 | # True 218 | 219 | import pickle 220 | fd_unpickled = pickle.loads(pickle.dumps(fd)) 221 | print(fd_unpickled) 222 | # frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill'}) 223 | fd_unpickled == fd 224 | # True 225 | 226 | frozendict(Guzzanti="Corrado", Hicks="Bill") 227 | # frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill'} 228 | 229 | fd.get("Guzzanti") 230 | # 'Corrado' 231 | 232 | print(fd.get("Brignano")) 233 | # None 234 | 235 | tuple(fd.keys()) 236 | # ('Guzzanti', 'Hicks') 237 | 238 | tuple(fd.values()) 239 | # ('Corrado', 'Bill') 240 | 241 | tuple(fd.items()) 242 | # (('Guzzanti', 'Corrado'), ('Hicks', 'Bill')) 243 | 244 | frozendict.fromkeys(["Corrado", "Sabina"], "Guzzanti") 245 | # frozendict({'Corrado': 'Guzzanti', 'Sabina': 'Guzzanti'}) 246 | 247 | iter(fd) 248 | # 249 | 250 | fd["Guzzanti"] = "Caterina" 251 | # TypeError: 'frozendict' object doesn't support item assignment 252 | ``` 253 | 254 | ## deepfreeze examples 255 | ```python 256 | import frozendict as cool 257 | 258 | from frozendict import frozendict 259 | from array import array 260 | from collections import OrderedDict 261 | from types import MappingProxyType 262 | 263 | class A: 264 | def __init__(self, x): 265 | self.x = x 266 | 267 | a = A(3) 268 | 269 | o = {"x": [ 270 | 5, 271 | frozendict(y = {5, "b", memoryview(b"b")}), 272 | array("B", (0, 1, 2)), 273 | OrderedDict(a=bytearray(b"a")), 274 | MappingProxyType({2: []}), 275 | a 276 | ]} 277 | 278 | cool.deepfreeze(o) 279 | # frozendict(x = ( 280 | # 5, 281 | # frozendict(y = frozenset({5, "b", memoryview(b"b")})), 282 | # (0, 1, 2), 283 | # frozendict(a = b'a'), 284 | # MappingProxyType({2: ()}), 285 | # frozendict(x = 3), 286 | # )) 287 | 288 | ``` 289 | 290 | # Building 291 | You can build `frozendict` directly from the code, using 292 | 293 | ``` 294 | python3 setup.py bdist_wheel 295 | ``` 296 | 297 | The C Extension is optional by default from version 2.3.5. You can make it mandatory by passing the environment variable `CIBUILDWHEEL` with value `1` 298 | 299 | On the contrary, if you want the pure py implementation, you can pass the env var `FROZENDICT_PURE_PY` with value `1` 300 | 301 | # Benchmarks 302 | 303 | Some benchmarks between `dict` and `frozendict`[1]: 304 | 305 | ``` 306 | ################################################################################ 307 | //////////////////////////////////////////////////////////////////////////////// 308 | Name: `constructor(d)`; Size: 5; Keys: str; Type: dict; Time: 8.02e-08; Sigma: 4e-09 309 | Name: `constructor(d)`; Size: 5; Keys: str; Type: frozendict; Time: 8.81e-08; Sigma: 3e-09 310 | //////////////////////////////////////////////////////////////////////////////// 311 | Name: `constructor(d)`; Size: 5; Keys: int; Type: dict; Time: 7.96e-08; Sigma: 5e-09 312 | Name: `constructor(d)`; Size: 5; Keys: int; Type: frozendict; Time: 8.97e-08; Sigma: 2e-09 313 | //////////////////////////////////////////////////////////////////////////////// 314 | Name: `constructor(d)`; Size: 1000; Keys: str; Type: dict; Time: 6.38e-06; Sigma: 9e-08 315 | Name: `constructor(d)`; Size: 1000; Keys: str; Type: frozendict; Time: 6.21e-06; Sigma: 2e-07 316 | //////////////////////////////////////////////////////////////////////////////// 317 | Name: `constructor(d)`; Size: 1000; Keys: int; Type: dict; Time: 3.49e-06; Sigma: 3e-07 318 | Name: `constructor(d)`; Size: 1000; Keys: int; Type: frozendict; Time: 3.48e-06; Sigma: 2e-07 319 | ################################################################################ 320 | //////////////////////////////////////////////////////////////////////////////// 321 | Name: `constructor(kwargs)`; Size: 5; Keys: str; Type: dict; Time: 2.40e-07; Sigma: 1e-09 322 | Name: `constructor(kwargs)`; Size: 5; Keys: str; Type: frozendict; Time: 2.48e-07; Sigma: 2e-09 323 | //////////////////////////////////////////////////////////////////////////////// 324 | Name: `constructor(kwargs)`; Size: 1000; Keys: str; Type: dict; Time: 4.80e-05; Sigma: 1e-06 325 | Name: `constructor(kwargs)`; Size: 1000; Keys: str; Type: frozendict; Time: 2.90e-05; Sigma: 7e-07 326 | ################################################################################ 327 | //////////////////////////////////////////////////////////////////////////////// 328 | Name: `constructor(seq2)`; Size: 5; Keys: str; Type: dict; Time: 2.01e-07; Sigma: 9e-10 329 | Name: `constructor(seq2)`; Size: 5; Keys: str; Type: frozendict; Time: 2.50e-07; Sigma: 1e-09 330 | //////////////////////////////////////////////////////////////////////////////// 331 | Name: `constructor(seq2)`; Size: 5; Keys: int; Type: dict; Time: 2.18e-07; Sigma: 2e-09 332 | Name: `constructor(seq2)`; Size: 5; Keys: int; Type: frozendict; Time: 2.73e-07; Sigma: 1e-09 333 | //////////////////////////////////////////////////////////////////////////////// 334 | Name: `constructor(seq2)`; Size: 1000; Keys: str; Type: dict; Time: 4.29e-05; Sigma: 6e-07 335 | Name: `constructor(seq2)`; Size: 1000; Keys: str; Type: frozendict; Time: 4.33e-05; Sigma: 6e-07 336 | //////////////////////////////////////////////////////////////////////////////// 337 | Name: `constructor(seq2)`; Size: 1000; Keys: int; Type: dict; Time: 3.04e-05; Sigma: 4e-07 338 | Name: `constructor(seq2)`; Size: 1000; Keys: int; Type: frozendict; Time: 3.45e-05; Sigma: 4e-07 339 | ################################################################################ 340 | //////////////////////////////////////////////////////////////////////////////// 341 | Name: `constructor(o)`; Size: 5; Keys: str; Type: dict; Time: 7.93e-08; Sigma: 3e-09 342 | Name: `constructor(o)`; Size: 5; Keys: str; Type: frozendict; Time: 2.41e-08; Sigma: 6e-10 343 | //////////////////////////////////////////////////////////////////////////////// 344 | Name: `constructor(o)`; Size: 5; Keys: int; Type: dict; Time: 7.94e-08; Sigma: 5e-09 345 | Name: `constructor(o)`; Size: 5; Keys: int; Type: frozendict; Time: 2.41e-08; Sigma: 6e-10 346 | //////////////////////////////////////////////////////////////////////////////// 347 | Name: `constructor(o)`; Size: 1000; Keys: str; Type: dict; Time: 6.18e-06; Sigma: 3e-07 348 | Name: `constructor(o)`; Size: 1000; Keys: str; Type: frozendict; Time: 2.41e-08; Sigma: 6e-10 349 | //////////////////////////////////////////////////////////////////////////////// 350 | Name: `constructor(o)`; Size: 1000; Keys: int; Type: dict; Time: 3.47e-06; Sigma: 2e-07 351 | Name: `constructor(o)`; Size: 1000; Keys: int; Type: frozendict; Time: 2.41e-08; Sigma: 6e-10 352 | ################################################################################ 353 | //////////////////////////////////////////////////////////////////////////////// 354 | Name: `o.copy()`; Size: 5; Keys: str; Type: dict; Time: 7.28e-08; Sigma: 2e-09 355 | Name: `o.copy()`; Size: 5; Keys: str; Type: frozendict; Time: 3.18e-08; Sigma: 2e-09 356 | //////////////////////////////////////////////////////////////////////////////// 357 | Name: `o.copy()`; Size: 5; Keys: int; Type: dict; Time: 7.21e-08; Sigma: 4e-09 358 | Name: `o.copy()`; Size: 5; Keys: int; Type: frozendict; Time: 3.32e-08; Sigma: 2e-09 359 | //////////////////////////////////////////////////////////////////////////////// 360 | Name: `o.copy()`; Size: 1000; Keys: str; Type: dict; Time: 6.16e-06; Sigma: 3e-07 361 | Name: `o.copy()`; Size: 1000; Keys: str; Type: frozendict; Time: 3.18e-08; Sigma: 2e-09 362 | //////////////////////////////////////////////////////////////////////////////// 363 | Name: `o.copy()`; Size: 1000; Keys: int; Type: dict; Time: 3.46e-06; Sigma: 1e-07 364 | Name: `o.copy()`; Size: 1000; Keys: int; Type: frozendict; Time: 3.18e-08; Sigma: 2e-09 365 | ################################################################################ 366 | //////////////////////////////////////////////////////////////////////////////// 367 | Name: `o == o`; Size: 5; Keys: str; Type: dict; Time: 7.23e-08; Sigma: 8e-10 368 | Name: `o == o`; Size: 5; Keys: str; Type: frozendict; Time: 2.44e-08; Sigma: 2e-09 369 | //////////////////////////////////////////////////////////////////////////////// 370 | Name: `o == o`; Size: 5; Keys: int; Type: dict; Time: 7.30e-08; Sigma: 1e-09 371 | Name: `o == o`; Size: 5; Keys: int; Type: frozendict; Time: 2.44e-08; Sigma: 2e-09 372 | //////////////////////////////////////////////////////////////////////////////// 373 | Name: `o == o`; Size: 1000; Keys: str; Type: dict; Time: 1.38e-05; Sigma: 1e-07 374 | Name: `o == o`; Size: 1000; Keys: str; Type: frozendict; Time: 2.44e-08; Sigma: 2e-09 375 | //////////////////////////////////////////////////////////////////////////////// 376 | Name: `o == o`; Size: 1000; Keys: int; Type: dict; Time: 1.05e-05; Sigma: 7e-08 377 | Name: `o == o`; Size: 1000; Keys: int; Type: frozendict; Time: 2.44e-08; Sigma: 2e-09 378 | ################################################################################ 379 | //////////////////////////////////////////////////////////////////////////////// 380 | Name: `for x in o`; Size: 5; Keys: str; Type: dict; Time: 7.33e-08; Sigma: 2e-09 381 | Name: `for x in o`; Size: 5; Keys: str; Type: frozendict; Time: 6.70e-08; Sigma: 1e-09 382 | //////////////////////////////////////////////////////////////////////////////// 383 | Name: `for x in o`; Size: 5; Keys: int; Type: dict; Time: 7.33e-08; Sigma: 2e-09 384 | Name: `for x in o`; Size: 5; Keys: int; Type: frozendict; Time: 6.70e-08; Sigma: 1e-09 385 | //////////////////////////////////////////////////////////////////////////////// 386 | Name: `for x in o`; Size: 1000; Keys: str; Type: dict; Time: 8.84e-06; Sigma: 5e-08 387 | Name: `for x in o`; Size: 1000; Keys: str; Type: frozendict; Time: 7.06e-06; Sigma: 6e-08 388 | //////////////////////////////////////////////////////////////////////////////// 389 | Name: `for x in o`; Size: 1000; Keys: int; Type: dict; Time: 8.67e-06; Sigma: 7e-08 390 | Name: `for x in o`; Size: 1000; Keys: int; Type: frozendict; Time: 6.94e-06; Sigma: 3e-08 391 | ################################################################################ 392 | //////////////////////////////////////////////////////////////////////////////// 393 | Name: `for x in o.values()`; Size: 5; Keys: str; Type: dict; Time: 7.28e-08; Sigma: 9e-10 394 | Name: `for x in o.values()`; Size: 5; Keys: str; Type: frozendict; Time: 6.48e-08; Sigma: 8e-10 395 | //////////////////////////////////////////////////////////////////////////////// 396 | Name: `for x in o.values()`; Size: 5; Keys: int; Type: dict; Time: 7.25e-08; Sigma: 1e-09 397 | Name: `for x in o.values()`; Size: 5; Keys: int; Type: frozendict; Time: 6.45e-08; Sigma: 1e-09 398 | //////////////////////////////////////////////////////////////////////////////// 399 | Name: `for x in o.values()`; Size: 1000; Keys: str; Type: dict; Time: 9.06e-06; Sigma: 5e-07 400 | Name: `for x in o.values()`; Size: 1000; Keys: str; Type: frozendict; Time: 7.04e-06; Sigma: 4e-08 401 | //////////////////////////////////////////////////////////////////////////////// 402 | Name: `for x in o.values()`; Size: 1000; Keys: int; Type: dict; Time: 9.53e-06; Sigma: 3e-08 403 | Name: `for x in o.values()`; Size: 1000; Keys: int; Type: frozendict; Time: 6.97e-06; Sigma: 3e-08 404 | ################################################################################ 405 | //////////////////////////////////////////////////////////////////////////////// 406 | Name: `for x in o.items()`; Size: 5; Keys: str; Type: dict; Time: 1.13e-07; Sigma: 3e-09 407 | Name: `for x in o.items()`; Size: 5; Keys: str; Type: frozendict; Time: 1.16e-07; Sigma: 2e-09 408 | //////////////////////////////////////////////////////////////////////////////// 409 | Name: `for x in o.items()`; Size: 5; Keys: int; Type: dict; Time: 1.14e-07; Sigma: 3e-09 410 | Name: `for x in o.items()`; Size: 5; Keys: int; Type: frozendict; Time: 1.17e-07; Sigma: 2e-09 411 | //////////////////////////////////////////////////////////////////////////////// 412 | Name: `for x in o.items()`; Size: 1000; Keys: str; Type: dict; Time: 1.53e-05; Sigma: 3e-07 413 | Name: `for x in o.items()`; Size: 1000; Keys: str; Type: frozendict; Time: 1.53e-05; Sigma: 4e-07 414 | //////////////////////////////////////////////////////////////////////////////// 415 | Name: `for x in o.items()`; Size: 1000; Keys: int; Type: dict; Time: 1.53e-05; Sigma: 3e-07 416 | Name: `for x in o.items()`; Size: 1000; Keys: int; Type: frozendict; Time: 1.55e-05; Sigma: 4e-07 417 | ################################################################################ 418 | //////////////////////////////////////////////////////////////////////////////// 419 | Name: `pickle.dumps(o)`; Size: 5; Keys: str; Type: dict; Time: 6.82e-07; Sigma: 2e-08 420 | Name: `pickle.dumps(o)`; Size: 5; Keys: str; Type: frozendict; Time: 2.86e-06; Sigma: 1e-07 421 | //////////////////////////////////////////////////////////////////////////////// 422 | Name: `pickle.dumps(o)`; Size: 5; Keys: int; Type: dict; Time: 4.77e-07; Sigma: 2e-08 423 | Name: `pickle.dumps(o)`; Size: 5; Keys: int; Type: frozendict; Time: 2.72e-06; Sigma: 8e-08 424 | //////////////////////////////////////////////////////////////////////////////// 425 | Name: `pickle.dumps(o)`; Size: 1000; Keys: str; Type: dict; Time: 1.24e-04; Sigma: 4e-06 426 | Name: `pickle.dumps(o)`; Size: 1000; Keys: str; Type: frozendict; Time: 1.92e-04; Sigma: 5e-06 427 | //////////////////////////////////////////////////////////////////////////////// 428 | Name: `pickle.dumps(o)`; Size: 1000; Keys: int; Type: dict; Time: 2.81e-05; Sigma: 6e-07 429 | Name: `pickle.dumps(o)`; Size: 1000; Keys: int; Type: frozendict; Time: 7.37e-05; Sigma: 1e-06 430 | ################################################################################ 431 | //////////////////////////////////////////////////////////////////////////////// 432 | Name: `pickle.loads(dump)`; Size: 5; Keys: str; Type: dict; Time: 9.08e-07; Sigma: 6e-09 433 | Name: `pickle.loads(dump)`; Size: 5; Keys: str; Type: frozendict; Time: 1.79e-06; Sigma: 9e-08 434 | //////////////////////////////////////////////////////////////////////////////// 435 | Name: `pickle.loads(dump)`; Size: 5; Keys: int; Type: dict; Time: 4.46e-07; Sigma: 6e-09 436 | Name: `pickle.loads(dump)`; Size: 5; Keys: int; Type: frozendict; Time: 1.32e-06; Sigma: 7e-08 437 | //////////////////////////////////////////////////////////////////////////////// 438 | Name: `pickle.loads(dump)`; Size: 1000; Keys: str; Type: dict; Time: 1.57e-04; Sigma: 8e-06 439 | Name: `pickle.loads(dump)`; Size: 1000; Keys: str; Type: frozendict; Time: 1.69e-04; Sigma: 7e-06 440 | //////////////////////////////////////////////////////////////////////////////// 441 | Name: `pickle.loads(dump)`; Size: 1000; Keys: int; Type: dict; Time: 5.97e-05; Sigma: 5e-06 442 | Name: `pickle.loads(dump)`; Size: 1000; Keys: int; Type: frozendict; Time: 6.68e-05; Sigma: 2e-06 443 | ################################################################################ 444 | //////////////////////////////////////////////////////////////////////////////// 445 | Name: `class.fromkeys()`; Size: 5; Keys: str; Type: dict; Time: 1.88e-07; Sigma: 1e-09 446 | Name: `class.fromkeys()`; Size: 5; Keys: str; Type: frozendict; Time: 2.22e-07; Sigma: 7e-09 447 | //////////////////////////////////////////////////////////////////////////////// 448 | Name: `class.fromkeys()`; Size: 5; Keys: int; Type: dict; Time: 2.08e-07; Sigma: 6e-09 449 | Name: `class.fromkeys()`; Size: 5; Keys: int; Type: frozendict; Time: 2.44e-07; Sigma: 2e-09 450 | //////////////////////////////////////////////////////////////////////////////// 451 | Name: `class.fromkeys()`; Size: 1000; Keys: str; Type: dict; Time: 4.05e-05; Sigma: 4e-06 452 | Name: `class.fromkeys()`; Size: 1000; Keys: str; Type: frozendict; Time: 3.84e-05; Sigma: 5e-07 453 | //////////////////////////////////////////////////////////////////////////////// 454 | Name: `class.fromkeys()`; Size: 1000; Keys: int; Type: dict; Time: 2.93e-05; Sigma: 7e-07 455 | Name: `class.fromkeys()`; Size: 1000; Keys: int; Type: frozendict; Time: 3.08e-05; Sigma: 2e-06 456 | ################################################################################ 457 | ``` 458 | 459 | [1] Benchmarks done under Linux 64 bit, Python 3.10.2, using the C Extension. 460 | --------------------------------------------------------------------------------