├── .github └── workflows │ └── python-publish.yml ├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── README_CN.md ├── gqylpy_dict ├── __init__.py └── g dict.py ├── issues.yml ├── setup.py └── test.py /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | deploy: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Set up Python 16 | uses: actions/setup-python@v3 17 | with: 18 | python-version: '3.x' 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install build 23 | - name: Build package 24 | run: python -m build 25 | - name: Publish package 26 | uses: pypa/gh-action-pypi-publish@master 27 | with: 28 | user: __token__ 29 | password: ${{ secrets.PYPI_API_TOKEN }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.pyc 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | py2env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | .mypy_cache/ 29 | .idea/ 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # IPython Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | .venv/ 89 | venv/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # Mac 99 | .DS_Store 100 | 101 | # vim 102 | *.swp 103 | 104 | # netCDF Files 105 | *.nc 106 | conda-requirements.txt 107 | 108 | tests/ 109 | 110 | changelog.yml 111 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | ────────────────────────────────────────────────────────────────────────────── 16 | ────────────────────────────────────────────────────────────────────────────── 17 | 18 | Apache License 19 | Version 2.0, January 2004 20 | http://www.apache.org/licenses/ 21 | 22 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 23 | 24 | 1. Definitions. 25 | 26 | "License" shall mean the terms and conditions for use, reproduction, 27 | and distribution as defined by Sections 1 through 9 of this document. 28 | 29 | "Licensor" shall mean the copyright owner or entity authorized by 30 | the copyright owner that is granting the License. 31 | 32 | "Legal Entity" shall mean the union of the acting entity and all 33 | other entities that control, are controlled by, or are under common 34 | control with that entity. For the purposes of this definition, 35 | "control" means (i) the power, direct or indirect, to cause the 36 | direction or management of such entity, whether by contract or 37 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 38 | outstanding shares, or (iii) beneficial ownership of such entity. 39 | 40 | "You" (or "Your") shall mean an individual or Legal Entity 41 | exercising permissions granted by this License. 42 | 43 | "Source" form shall mean the preferred form for making modifications, 44 | including but not limited to software source code, documentation 45 | source, and configuration files. 46 | 47 | "Object" form shall mean any form resulting from mechanical 48 | transformation or translation of a Source form, including but 49 | not limited to compiled object code, generated documentation, 50 | and conversions to other media types. 51 | 52 | "Work" shall mean the work of authorship, whether in Source or 53 | Object form, made available under the License, as indicated by a 54 | copyright notice that is included in or attached to the work 55 | (an example is provided in the Appendix below). 56 | 57 | "Derivative Works" shall mean any work, whether in Source or Object 58 | form, that is based on (or derived from) the Work and for which the 59 | editorial revisions, annotations, elaborations, or other modifications 60 | represent, as a whole, an original work of authorship. For the purposes 61 | of this License, Derivative Works shall not include works that remain 62 | separable from, or merely link (or bind by name) to the interfaces of, 63 | the Work and Derivative Works thereof. 64 | 65 | "Contribution" shall mean any work of authorship, including 66 | the original version of the Work and any modifications or additions 67 | to that Work or Derivative Works thereof, that is intentionally 68 | submitted to Licensor for inclusion in the Work by the copyright owner 69 | or by an individual or Legal Entity authorized to submit on behalf of 70 | the copyright owner. For the purposes of this definition, "submitted" 71 | means any form of electronic, verbal, or written communication sent 72 | to the Licensor or its representatives, including but not limited to 73 | communication on electronic mailing lists, source code control systems, 74 | and issue tracking systems that are managed by, or on behalf of, the 75 | Licensor for the purpose of discussing and improving the Work, but 76 | excluding communication that is conspicuously marked or otherwise 77 | designated in writing by the copyright owner as "Not a Contribution." 78 | 79 | "Contributor" shall mean Licensor and any individual or Legal Entity 80 | on behalf of whom a Contribution has been received by Licensor and 81 | subsequently incorporated within the Work. 82 | 83 | 2. Grant of Copyright License. Subject to the terms and conditions of 84 | this License, each Contributor hereby grants to You a perpetual, 85 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 86 | copyright license to reproduce, prepare Derivative Works of, 87 | publicly display, publicly perform, sublicense, and distribute the 88 | Work and such Derivative Works in Source or Object form. 89 | 90 | 3. Grant of Patent License. Subject to the terms and conditions of 91 | this License, each Contributor hereby grants to You a perpetual, 92 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 93 | (except as stated in this section) patent license to make, have made, 94 | use, offer to sell, sell, import, and otherwise transfer the Work, 95 | where such license applies only to those patent claims licensable 96 | by such Contributor that are necessarily infringed by their 97 | Contribution(s) alone or by combination of their Contribution(s) 98 | with the Work to which such Contribution(s) was submitted. If You 99 | institute patent litigation against any entity (including a 100 | cross-claim or counterclaim in a lawsuit) alleging that the Work 101 | or a Contribution incorporated within the Work constitutes direct 102 | or contributory patent infringement, then any patent licenses 103 | granted to You under this License for that Work shall terminate 104 | as of the date such litigation is filed. 105 | 106 | 4. Redistribution. You may reproduce and distribute copies of the 107 | Work or Derivative Works thereof in any medium, with or without 108 | modifications, and in Source or Object form, provided that You 109 | meet the following conditions: 110 | 111 | (a) You must give any other recipients of the Work or 112 | Derivative Works a copy of this License; and 113 | 114 | (b) You must cause any modified files to carry prominent notices 115 | stating that You changed the files; and 116 | 117 | (c) You must retain, in the Source form of any Derivative Works 118 | that You distribute, all copyright, patent, trademark, and 119 | attribution notices from the Source form of the Work, 120 | excluding those notices that do not pertain to any part of 121 | the Derivative Works; and 122 | 123 | (d) If the Work includes a "NOTICE" text file as part of its 124 | distribution, then any Derivative Works that You distribute must 125 | include a readable copy of the attribution notices contained 126 | within such NOTICE file, excluding those notices that do not 127 | pertain to any part of the Derivative Works, in at least one 128 | of the following places: within a NOTICE text file distributed 129 | as part of the Derivative Works; within the Source form or 130 | documentation, if provided along with the Derivative Works; or, 131 | within a display generated by the Derivative Works, if and 132 | wherever such third-party notices normally appear. The contents 133 | of the NOTICE file are for informational purposes only and 134 | do not modify the License. You may add Your own attribution 135 | notices within Derivative Works that You distribute, alongside 136 | or as an addendum to the NOTICE text from the Work, provided 137 | that such additional attribution notices cannot be construed 138 | as modifying the License. 139 | 140 | You may add Your own copyright statement to Your modifications and 141 | may provide additional or different license terms and conditions 142 | for use, reproduction, or distribution of Your modifications, or 143 | for any such Derivative Works as a whole, provided Your use, 144 | reproduction, and distribution of the Work otherwise complies with 145 | the conditions stated in this License. 146 | 147 | 5. Submission of Contributions. Unless You explicitly state otherwise, 148 | any Contribution intentionally submitted for inclusion in the Work 149 | by You to the Licensor shall be under the terms and conditions of 150 | this License, without any additional terms or conditions. 151 | Notwithstanding the above, nothing herein shall supersede or modify 152 | the terms of any separate license agreement you may have executed 153 | with Licensor regarding such Contributions. 154 | 155 | 6. Trademarks. This License does not grant permission to use the trade 156 | names, trademarks, service marks, or product names of the Licensor, 157 | except as required for reasonable and customary use in describing the 158 | origin of the Work and reproducing the content of the NOTICE file. 159 | 160 | 7. Disclaimer of Warranty. Unless required by applicable law or 161 | agreed to in writing, Licensor provides the Work (and each 162 | Contributor provides its Contributions) on an "AS IS" BASIS, 163 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 164 | implied, including, without limitation, any warranties or conditions 165 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 166 | PARTICULAR PURPOSE. You are solely responsible for determining the 167 | appropriateness of using or redistributing the Work and assume any 168 | risks associated with Your exercise of permissions under this License. 169 | 170 | 8. Limitation of Liability. In no event and under no legal theory, 171 | whether in tort (including negligence), contract, or otherwise, 172 | unless required by applicable law (such as deliberate and grossly 173 | negligent acts) or agreed to in writing, shall any Contributor be 174 | liable to You for damages, including any direct, indirect, special, 175 | incidental, or consequential damages of any character arising as a 176 | result of this License or out of the use or inability to use the 177 | Work (including but not limited to damages for loss of goodwill, 178 | work stoppage, computer failure or malfunction, or any and all 179 | other commercial damages or losses), even if such Contributor 180 | has been advised of the possibility of such damages. 181 | 182 | 9. Accepting Warranty or Additional Liability. While redistributing 183 | the Work or Derivative Works thereof, You may choose to offer, 184 | and charge a fee for, acceptance of support, warranty, indemnity, 185 | or other liability obligations and/or rights consistent with this 186 | License. However, in accepting such obligations, You may act only 187 | on Your own behalf and on Your sole responsibility, not on behalf 188 | of any other Contributor, and only if You agree to indemnify, 189 | defend, and hold each Contributor harmless for any liability 190 | incurred by, or claims asserted against, such Contributor by reason 191 | of your accepting any such warranty or additional liability. 192 | 193 | END OF TERMS AND CONDITIONS 194 | 195 | APPENDIX: How to apply the Apache License to your work. 196 | 197 | To apply the Apache License to your work, attach the following 198 | boilerplate notice, with the fields enclosed by brackets "[]" 199 | replaced with your own identifying information. (Don't include 200 | the brackets!) The text should be enclosed in the appropriate 201 | comment syntax for the file format. We also recommend that a 202 | file or class name and description of purpose be included on the 203 | same "printed page" as the copyright notice for easier 204 | identification within third-party archives. 205 | 206 | Copyright (c) 2022-2024 GQYLPY . All rights reserved. 207 | 208 | Licensed under the Apache License, Version 2.0 (the "License"); 209 | you may not use this file except in compliance with the License. 210 | You may obtain a copy of the License at 211 | 212 | http://www.apache.org/licenses/LICENSE-2.0 213 | 214 | Unless required by applicable law or agreed to in writing, software 215 | distributed under the License is distributed on an "AS IS" BASIS, 216 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 217 | See the License for the specific language governing permissions and 218 | limitations under the License. 219 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, 2023 GQYLPY . All rights reserved. 2 | gqylpy-dict is released under the dual license WTFPL and Apache-2.0. 3 | 4 | ──────────────────────────────────────────────────────────────────────────────── 5 | 6 | Lines 51-99 in the "gqylpy_dict/g dict.py" file are licensed under Apache-2.0: 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | https://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | 20 | ──────────────────────────────────────────────────────────────────────────────── 21 | 22 | All other content is licensed under WTFPL: 23 | 24 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 25 | Version 2, December 2004 26 | 27 | Copyright (C) 2004 Sam Hocevar 28 | 29 | Everyone is permitted to copy and distribute verbatim or modified 30 | copies of this license document, and changing it is allowed as long 31 | as the name is changed. 32 | 33 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 34 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 35 | 36 | 0. You just DO WHAT THE FUCK YOU WANT TO. 37 | 38 | ──────────────────────────────────────────────────────────────────────────────── 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [LOGO](http://www.gqylpy.com) 2 | [![Release](https://img.shields.io/github/release/gqylpy/gqylpy-dict.svg?style=flat-square')](https://github.com/gqylpy/gqylpy-dict/releases/latest) 3 | [![Python Versions](https://img.shields.io/pypi/pyversions/gqylpy_dict)](https://pypi.org/project/gqylpy_dict) 4 | [![License](https://img.shields.io/pypi/l/gqylpy_dict)](https://github.com/gqylpy/gqylpy-dict/blob/master/LICENSE) 5 | [![Downloads](https://static.pepy.tech/badge/gqylpy_dict)](https://pepy.tech/project/gqylpy_dict) 6 | 7 | # gqylpy-dict 8 | English | [中文](https://github.com/gqylpy/gqylpy-dict/blob/master/README_CN.md) 9 | 10 | > `gqylpy-dict` is based on the built-in `dict` and serves as an enhancement to it. It can do everything the built-in `dict` can do, and even more. _(Designed specifically for the neurologically diverse)_ 11 | 12 | pip3 install gqylpy_dict 13 | 14 | ```python 15 | >>> from gqylpy_dict import gdict 16 | 17 | >>> gdict == dict 18 | True 19 | 20 | >>> gdict is dict 21 | False 22 | 23 | >>> x = {'a': [{'b': 'B'}]} 24 | >>> x = gdict(x) 25 | >>> x.a[0].b 26 | 'B' 27 | 28 | >>> x.deepget('a[0].b') 29 | 'B' 30 | ``` 31 | 32 | Let's delve deeper into the functionalities and usages of the `gdict` class. 33 | 34 | Firstly, the `gdict` class is a custom dictionary class that inherits from Python's built-in `dict` class. A special feature of `gdict` is its support for accessing and modifying key-value pairs in the dictionary using dot notation (`.`). This means we can access dictionary values as if they were object attributes. For instance, given a dictionary `{'name': 'Tom', 'age': 25}`, we can define a `gdict` object as follows: 35 | 36 | ```python 37 | my_dict = gdict({'name': 'Tom', 'age': 25}) 38 | ``` 39 | 40 | Using dot notation, we can access the dictionary values: 41 | 42 | ```python 43 | my_dict.name # 'Tom' 44 | my_dict.age # 25 45 | ``` 46 | 47 | We can even modify the dictionary values: 48 | 49 | ```python 50 | my_dict.name = 'Jerry' 51 | ``` 52 | 53 | At this point, the value associated with the `'name'` key in the dictionary has been changed to `'Jerry'`. 54 | 55 | Additionally, `gdict` supports multi-level nested data structures, meaning the keys and values stored in a `gdict` object can also be dictionaries. For example: 56 | 57 | ```python 58 | my_dict = gdict({ 59 | 'personal_info': {'name': 'Tom', 'age': 25}, 60 | 'work_info': {'company': 'ABC Inc.', 'position': 'Engineer'} 61 | }) 62 | ``` 63 | 64 | We can access and modify values in nested dictionaries: 65 | 66 | ```python 67 | my_dict.personal_info.name # 'Tom' 68 | my_dict.work_info.position = 'Manager' 69 | ``` 70 | 71 | Aside from dot notation, we can also access and modify values in a `gdict` object using traditional dictionary access methods: 72 | 73 | ```python 74 | my_dict['personal_info']['name'] # 'Tom' 75 | my_dict['work_info']['position'] = 'Manager' 76 | ``` 77 | 78 | Lastly, the `gdict` class supports various input formats during instantiation: 79 | 80 | ```python 81 | my_dict = gdict({'name': 'Tom', 'age': 25}) 82 | my_dict = gdict(name='Tom', age=25) 83 | my_dict = gdict([('name', 'Tom'), ('age', 25)]) 84 | ``` 85 | 86 | In summary, the design and implementation of the `gdict` class provide a convenient and extensible data structure that allows for more flexible manipulation of Python dictionary objects. 87 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | [LOGO](http://www.gqylpy.com) 2 | [![Release](https://img.shields.io/github/release/gqylpy/gqylpy-dict.svg?style=flat-square')](https://github.com/gqylpy/gqylpy-dict/releases/latest) 3 | [![Python Versions](https://img.shields.io/pypi/pyversions/gqylpy_dict)](https://pypi.org/project/gqylpy_dict) 4 | [![License](https://img.shields.io/pypi/l/gqylpy_dict)](https://github.com/gqylpy/gqylpy-dict/blob/master/LICENSE) 5 | [![Downloads](https://static.pepy.tech/badge/gqylpy_dict)](https://pepy.tech/project/gqylpy_dict) 6 | 7 | # gqylpy-dict 8 | [English](README.md) | 中文 9 | 10 | > `gqylpy-dict` 基于内置 `dict`,它是对内置 `dict` 的增强。内置 `dict` 能做的它都能做,内置 `dict` 不能做的它更能做。_(专为神经患者设计)_ 11 | 12 | pip3 install gqylpy_dict 13 | 14 | ```python 15 | >>> from gqylpy_dict import gdict 16 | 17 | >>> gdict == dict 18 | True 19 | 20 | >>> gdict is dict 21 | False 22 | 23 | >>> x = {'a': [{'b': 'B'}]} 24 | >>> x = gdict(x) 25 | >>> x.a[0].b 26 | 'B' 27 | 28 | >>> x.deepget('a[0].b') 29 | 'B' 30 | ``` 31 | 32 | 我们再详细地介绍一下 `gdict` 类的功能和用法。 33 | 34 | 首先,`gdict` 类是一个自定义字典类,继承自 Python 内置的 `dict` 类。`gdict` 类有一个特殊的功能是支持点操作符(`.`)访问和修改字典中的键值对。这意味着,我们可以像访问对象的属性一样访问字典中的值。比如,有一个字典 `{'name': 'Tom', 'age': 25}`,我们可以这样定义一个 `gdict` 对象: 35 | ```python 36 | my_dict = gdict({'name': 'Tom', 'age': 25}) 37 | ``` 38 | 39 | 通过点操作符,我们可以访问这个字典的值: 40 | ```python 41 | my_dict.name # 'Tom' 42 | my_dict.age # 25 43 | ``` 44 | 45 | 我们甚至可以修改这个字典的值: 46 | ```python 47 | my_dict.name = 'Jerry' 48 | ``` 49 | 此时,这个字典中 `'name'` 这个键对应的值已经被修改为 `'Jerry'`。 50 | 51 | 另外,`gdict` 还支持多层嵌套的数据结构,也就是说,`gdict` 对象中存储的键和值也可以是字典类型。比如: 52 | ```python 53 | my_dict = gdict({ 54 | 'personal_info': {'name': 'Tom', 'age': 25}, 55 | 'work_info': {'company': 'ABC Inc.', 'position': 'Engineer'} 56 | }) 57 | ``` 58 | 59 | 我们可以访问和修改嵌套字典中的值: 60 | ```python 61 | my_dict.personal_info.name # 'Tom' 62 | my_dict.work_info.position = 'Manager' 63 | ``` 64 | 65 | 除了点操作符,我们也可以使用普通的字典操作方式访问和修改 `gdict` 对象中的值: 66 | ```python 67 | my_dict['personal_info']['name'] # 'Tom' 68 | my_dict['work_info']['position'] = 'Manager' 69 | ``` 70 | 71 | 最后,`gdict` 类在实例化时支持多种不同的输入方式: 72 | ```python 73 | my_dict = gdict({'name': 'Tom', 'age': 25}) 74 | my_dict = gdict(name='Tom', age=25) 75 | my_dict = gdict([('name', 'Tom'), ('age', 25)]) 76 | ``` 77 | 78 | 以上就是 `gdict` 类的主要功能和用法。总体来说,`gdict` 类的设计和实现提供了一种方便的、可扩展的数据结构,可以更加灵活地操作 Python 字典对象。 79 | -------------------------------------------------------------------------------- /gqylpy_dict/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | `gqylpy-dict` is based on the built-in `dict` and serves as an enhancement 3 | to it. It can do everything the built-in `dict` can do, and even more. 4 | 5 | >>> from gqylpy_dict import gdict 6 | 7 | >>> gdict == dict 8 | True 9 | 10 | >>> gdict is dict 11 | False 12 | 13 | >>> x = {'a': [{'b': 'B'}]} 14 | >>> x = gdict(x) 15 | >>> x.a[0].b 16 | 'B' 17 | 18 | >>> x.deepget('a[0].b') 19 | 'B' 20 | 21 | @version: 1.2.6 22 | @author: 竹永康 23 | @source: https://github.com/gqylpy/gqylpy-dict 24 | 25 | ──────────────────────────────────────────────────────────────────────────────── 26 | Copyright (c) 2022-2024 GQYLPY . All rights reserved. 27 | 28 | This file is licensed under the WTFPL: 29 | 30 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 31 | Version 2, December 2004 32 | 33 | Copyright (C) 2004 Sam Hocevar 34 | 35 | Everyone is permitted to copy and distribute verbatim or modified 36 | copies of this license document, and changing it is allowed as long 37 | as the name is changed. 38 | 39 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 40 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 41 | 42 | 0. You just DO WHAT THE FUCK YOU WANT TO. 43 | """ 44 | from typing import Optional, Union, Tuple, List, Hashable, Any 45 | 46 | 47 | class gdict(dict): 48 | """ 49 | The `gdict` class is a custom dictionary class that inherits from the 50 | built-in Python `dict` class. One unique feature of the gdict class is that 51 | it supports accessing and modifying key-value pairs in the dictionary using 52 | dot notation ('.'). This means we can access values in the dictionary like 53 | we would access attributes of an object. For example, given a dictionary 54 | `{'name': 'Tom', 'age': 25}`, we can create a gdict object as follows: 55 | 56 | >>> my_dict = gdict({'name': 'Tom', 'age': 25}) 57 | 58 | Using dot notation, we can access values in this dictionary: 59 | 60 | >>> my_dict.name 61 | 'Tom' 62 | >>> my_dict.age 63 | 25 64 | 65 | We can even modify values in this dictionary using dot notation: 66 | 67 | >>> my_dict.name = 'Jerry' 68 | 69 | Now, the value of the "name" key in this dictionary has been updated to 70 | "Jerry". 71 | 72 | Additionally, `gdict` supports nested data structures, meaning the keys and 73 | values stored in a `gdict` object can be dictionaries themselves. For 74 | example: 75 | 76 | >>> my_dict = gdict({ 77 | >>> 'personal_info': {'name': 'Tom', 'age': 25}, 78 | >>> 'work_info': {'company': 'ABC Inc.', 'position': 'Engineer'} 79 | >>> }) 80 | 81 | We can access and modify values in nested dictionaries: 82 | 83 | >>> my_dict.personal_info.name 84 | 'Tom' 85 | >>> my_dict.work_info.position = 'Manager' 86 | 87 | In addition to dot notation, we can also access and modify values in `gdict` 88 | objects using traditional dictionary syntax: 89 | 90 | >>> my_dict['personal_info']['name'] 91 | 'Tom' 92 | >>> my_dict['work_info']['position'] = 'Manager' 93 | 94 | Finally, the `gdict` class has multiple input formats when instantiating an 95 | object: 96 | 97 | >>> my_dict = gdict({'name': 'Tom', 'age': 25}) 98 | >>> my_dict = gdict(name='Tom', age=25) 99 | >>> my_dict = gdict([('name', 'Tom'), ('age', 25)]) 100 | 101 | The above is the main functions and usage of the `gdict` class. Overall, the 102 | design and implementation of the `gdict` class provide a convenient and 103 | extensible data structure that allows greater flexibility for operating on 104 | Python dictionary objects. 105 | """ 106 | 107 | def __new__(cls, __data__={}, /, **data): 108 | """ 109 | When we create a new `gdict` object, the class actually initializes a 110 | Python `dict`. The initial value can be passed in as a constructor 111 | parameter (which can be a `dict`, `list`, `tuple`, or other data type) 112 | or passed in as keyword arguments. 113 | 114 | The `gdict` class overrides the `__new__` and `__init__` methods, which 115 | are responsible for accepting the constructor parameters and converting 116 | them into `gdict` objects. The `__new__` method implements type checking 117 | on the passed-in parameters, returning a new `dict` if it is a `dict`, 118 | converting each element of a `list` or `tuple` into a `gdict` type and 119 | returning a new `list` or `tuple`, or returning the data directly if it 120 | is another data type. For other data types, we can also use keyword 121 | arguments to pass them as initial values. 122 | 123 | The `__init__` method accepts the newly converted `dict` and iterates 124 | over each key-value pair, using the `__setitem__` method to add them to 125 | the `dict`. In the `__setitem__` method, we use `value = gdict(value)` 126 | to set the value as a `gdict` object, so that nested `gdict` objects are 127 | created recursively when needed. 128 | 129 | In this way, we can create a nested `gdict` of any level, with each 130 | inner dictionary being a `gdict` object, thereby achieving the 131 | conversion of any nested `dict`. 132 | """ 133 | if isinstance(__data__, dict): 134 | return dict.__new__(cls) 135 | 136 | if isinstance(__data__, (list, tuple)): 137 | return __data__.__class__(cls(v) for v in __data__) 138 | 139 | return __data__ 140 | 141 | def __init__(self, __data__=None, /, **data): 142 | if __data__ is None: 143 | __data__ = data 144 | else: 145 | __data__.update(data) 146 | 147 | for key, value in __data__.items(): 148 | dict.__setitem__(self, key, gdict(value)) 149 | 150 | def __getattr__(self, key: str, /) -> Any: 151 | return self[key] 152 | 153 | def __setattr__(self, key: str, value: Any, /) -> None: 154 | self[key] = value 155 | 156 | def __delattr__(self, key: str, /) -> None: 157 | del self[key] 158 | 159 | def __setitem__(self, key: Hashable, value: Any, /) -> None: 160 | if not isinstance(value, gdict): 161 | value = gdict(value) 162 | dict.__setitem__(self, key, value) 163 | 164 | def __hash__(self) -> int: 165 | """ 166 | The first thing you have to understand is that the built-in dict object 167 | is unhashable. Don't be misled! 168 | 169 | We do this mainly so that instances of `GqylpyDict` can be able to be 170 | added to instance of `set`. Ignore the hash check and always check that 171 | the values are equal. 172 | 173 | Backstory https://github.com/gqylpy/gqylpy-dict/issues/7 174 | """ 175 | return -2 176 | 177 | def copy(self) -> 'gdict': 178 | """Get a replica instance.""" 179 | 180 | def deepcopy(self) -> 'gdict': 181 | """ 182 | Incomplete deep copy, NOTE not the same as `copy.deepcopy`! 183 | 184 | Copy only the instances of container types (only instances of `gdict`, 185 | `dict`, `list` and `tuple`). 186 | 187 | Backstory https://github.com/gqylpy/gqylpy-dict/issues/9 188 | """ 189 | return gdict(self) 190 | 191 | def deepget( 192 | self, 193 | deepkey: str, 194 | /, 195 | default: Optional[Any] = None, 196 | *, 197 | ignore: Optional[Union[Tuple[Any], List[Any]]] = None 198 | ) -> Any: 199 | """ 200 | Try to get a depth value, if not then return the default value. 201 | 202 | >>> x = gdict({'a': [{'b': 'B'}]}) 203 | >>> x.deepget('a[0].b') 204 | 'B' 205 | 206 | @param deepkey 207 | Hierarchical keys, use "." join, if the next layer is an array then 208 | use the index number to join. 209 | 210 | @param default 211 | If not get the depth value then return the default value. 212 | 213 | @param ignore 214 | Use tuple or list to specify one or more undesired values, if the 215 | depth value is in it then return the default value. This parameter 216 | may be removed in the future. 217 | """ 218 | 219 | def deepset(self, deepkey: str, value: Any) -> None: 220 | """ 221 | Set a depth value (to the depth key), overwrite if exists. 222 | 223 | >>> x = gdict() 224 | >>> x.deepset('a[1].b', 'B') 225 | >>> x 226 | {'a': [None, {'b': 'B'}]} 227 | 228 | @param deepkey 229 | Hierarchical keys, use "." join, if the next layer is an array then 230 | use the index number to join. 231 | 232 | @param value 233 | Pass in any value, will be set to the value of the depth key, 234 | overwrite if exists. 235 | """ 236 | 237 | def deepsetdefault(self, deepkey: str, default: Any) -> Any: 238 | """ 239 | If the depth key does not exist then set the default value and return 240 | it, otherwise return the value of the depth key. 241 | 242 | >>> x = gdict() 243 | >>> x.deepsetdefault('a[0].b', 'B') 244 | 'B' 245 | >>> x 246 | {'a': [{'b': 'B'}]} 247 | 248 | @param deepkey 249 | Hierarchical keys, use "." join, if the next layer is an array then 250 | use the index number to join. 251 | 252 | @param default 253 | Pass in any value, will be set to the value of the depth key if the 254 | depth key does not exist. 255 | """ 256 | 257 | def deepcontain(self, deepkey: str, /) -> bool: 258 | """ 259 | Return True if the depth key exists else False. 260 | 261 | >>> x = gdict({'a': [{'b': 'B'}]}) 262 | >>> x.deepcontain('a[0].b') 263 | True 264 | >>> x.deepcontain('a[1].b') 265 | False 266 | 267 | @param deepkey 268 | Hierarchical keys, use "." join, if the next layer is an array then 269 | use the index number to join. 270 | """ 271 | 272 | @classmethod 273 | def getdeep( 274 | cls, 275 | data: dict, 276 | deepkey: str, 277 | /, 278 | default: Optional[Any] = None, 279 | *, 280 | ignore: Optional[Union[Tuple[Any], List[Any]]] = None 281 | ) -> Any: 282 | """ 283 | The `getdeep` based on `deepget`, and is provided for built-in `dict`. 284 | If you want to use `deepget` but don't want to or can't give up the 285 | original data, can use `getdeep`. 286 | """ 287 | warnings.warn( 288 | f'will be deprecated soon, replaced to {cls.deepget}.', 289 | DeprecationWarning 290 | ) 291 | return cls.deepget(data, deepkey, default, ignore=ignore) 292 | 293 | @classmethod 294 | def setdeep(cls, data: dict, deepkey: str, value: Any) -> None: 295 | """ 296 | The `setdeep` based on `deepset`, and is provided for built-in `dict`. 297 | If you want to use `deepset` but don't want to or can't give up the 298 | original data, can use `setdeep`. 299 | """ 300 | warnings.warn( 301 | f'will be deprecated soon, replaced to {cls.deepset}.', 302 | DeprecationWarning 303 | ) 304 | cls.deepset(data, deepkey, value) 305 | 306 | 307 | class _xe6_xad_x8c_xe7_x90_xaa_xe6_x80_xa1_xe7_x8e_xb2_xe8_x90_x8d_xe4_xba_x91: 308 | import sys 309 | 310 | gdict = __import__(f'{__name__}.g {__name__[7:]}', fromlist=...).GqylpyDict 311 | 312 | for gname, gvalue in globals().items(): 313 | if gname[0] == '_' and gname != '__name__': 314 | setattr(gdict, gname, gvalue) 315 | 316 | sys.modules[__name__] = gdict.gdict = gdict 317 | -------------------------------------------------------------------------------- /gqylpy_dict/g dict.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2022-2024 GQYLPY . All rights reserved. 3 | 4 | ──────────────────────────────────────────────────────────────────────────────── 5 | 6 | Lines 51 through 99 is licensed under the Apache-2.0: 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | https://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | 20 | ──────────────────────────────────────────────────────────────────────────────── 21 | 22 | All other code is licensed under the WTFPL: 23 | 24 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 25 | Version 2, December 2004 26 | 27 | Copyright (C) 2004 Sam Hocevar 28 | 29 | Everyone is permitted to copy and distribute verbatim or modified 30 | copies of this license document, and changing it is allowed as long 31 | as the name is changed. 32 | 33 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 34 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 35 | 36 | 0. You just DO WHAT THE FUCK YOU WANT TO. 37 | 38 | ──────────────────────────────────────────────────────────────────────────────── 39 | """ 40 | import re 41 | import sys 42 | import builtins 43 | 44 | from copy import copy, deepcopy 45 | 46 | from typing import Type, Final, Optional, Union, Tuple, List, Hashable, Any 47 | 48 | __unique__: Final = object() 49 | 50 | 51 | class MasqueradeClass(type): 52 | """ 53 | Masquerade one class as another (default masquerade as first parent class). 54 | Warning, masquerade the class can cause unexpected problems, use caution. 55 | """ 56 | __module__ = type.__module__ 57 | 58 | __qualname__ = type.__qualname__ 59 | # Warning, masquerade (modify) this attribute will cannot create the 60 | # portable serialized representation. In practice, however, this metaclass 61 | # often does not need to be serialized, so we try to ignore it. 62 | 63 | def __new__(mcs, __name__: str, __bases__: tuple, __dict__: dict): 64 | __masquerade_class__: Type[object] = __dict__.setdefault( 65 | '__masquerade_class__', __bases__[0] if __bases__ else object 66 | ) 67 | 68 | if not isinstance(__masquerade_class__, type): 69 | raise TypeError('"__masquerade_class__" is not a class.') 70 | 71 | cls = type.__new__( 72 | mcs, __masquerade_class__.__name__, __bases__, __dict__ 73 | ) 74 | 75 | if cls.__module__ != __masquerade_class__.__module__: 76 | setattr(sys.modules[__masquerade_class__.__module__], __name__, cls) 77 | 78 | cls.__real_name__ = __name__ 79 | cls.__real_module__ = cls.__module__ 80 | cls.__module__ = __masquerade_class__.__module__ 81 | 82 | # cls.__qualname__ = __masquerade_class__.__qualname__ 83 | # Masquerade (modify) this attribute will cannot create the portable 84 | # serialized representation. We have not yet found an effective 85 | # solution, and we will continue to follow up. 86 | 87 | return cls 88 | 89 | def __hash__(cls) -> int: 90 | if sys._getframe(1).f_code in (deepcopy.__code__, copy.__code__): 91 | return type.__hash__(cls) 92 | return hash(cls.__masquerade_class__) 93 | 94 | def __eq__(cls, o) -> bool: 95 | return True if o is cls.__masquerade_class__ else type.__eq__(cls, o) 96 | 97 | 98 | MasqueradeClass.__name__ = type.__name__ 99 | builtins.MasqueradeClass = MasqueradeClass 100 | 101 | 102 | class GqylpyDict(dict, metaclass=MasqueradeClass): 103 | 104 | def __init__(self, __data__=None, /, **data): 105 | if __data__ is None: 106 | __data__ = data 107 | else: 108 | __data__.update(data) 109 | 110 | for name, value in __data__.items(): 111 | dict.__setitem__(self, name, GqylpyDict(value)) 112 | 113 | def __new__(cls, __data__={}, /, **data): 114 | if isinstance(__data__, dict): 115 | return dict.__new__(cls) 116 | 117 | if isinstance(__data__, (list, tuple)): 118 | return __data__.__class__(cls(v) for v in __data__) 119 | 120 | return __data__ 121 | 122 | def __getattr__(self, name: str, /) -> Any: 123 | return self[name] 124 | 125 | def __setattr__(self, name: str, value: Any, /) -> None: 126 | self[name] = value 127 | 128 | def __delattr__(self, name: str, /) -> None: 129 | del self[name] 130 | 131 | def __setitem__(self, name: Hashable, value: Any, /) -> None: 132 | if not isinstance(value, GqylpyDict): 133 | value = GqylpyDict(value) 134 | dict.__setitem__(self, name, value) 135 | 136 | def __hash__(self) -> int: 137 | return -2 138 | 139 | def __reduce__(self) -> Tuple[Type['GqylpyDict'], Tuple[dict]]: 140 | return GqylpyDict, (dict(self),) 141 | 142 | def __copy__(self) -> 'GqylpyDict': 143 | copied = GqylpyDict() 144 | for name, value in self.items(): 145 | dict.__setitem__(copied, name, value) 146 | return copied 147 | 148 | copy = __copy__ 149 | 150 | def deepcopy(self) -> 'GqylpyDict': 151 | return GqylpyDict(self) 152 | 153 | def update(self, __data__: Optional[dict] = None, /, **data) -> None: 154 | try: 155 | dict.update(self, GqylpyDict( 156 | *() if __data__ is None else (__data__,), **data 157 | )) 158 | except (TypeError, ValueError): 159 | x: str = __data__.__class__.__name__ 160 | raise TypeError( 161 | f'updated object must be a "dict", not "{x}".' 162 | ) from None 163 | 164 | def deepget( 165 | self, 166 | deepkey: str, 167 | /, 168 | default: Optional[Any] = None, 169 | *, 170 | ignore: Union[Tuple[Any], List[Any]] = () 171 | ) -> Any: 172 | deepkey = deepkey[:-1] if deepkey and deepkey[-1] == ']' else deepkey 173 | value = self 174 | 175 | for key in re.split(r'\.|\[|][.\[]', deepkey): 176 | if isinstance(value, (list, tuple)): 177 | try: 178 | key = int(key) 179 | except ValueError: 180 | return default 181 | try: 182 | value = value[key] 183 | except KeyError: 184 | try: 185 | if key.isdigit() or key[0] == '-' and key[1:].isdigit(): 186 | value = value[int(key)] 187 | elif key == 'None': 188 | value = value[None] 189 | elif key == 'True': 190 | value = value[True] 191 | elif key == 'False': 192 | value = value[False] 193 | elif key == 'Ellipsis': 194 | value = value[...] 195 | else: 196 | return default 197 | except (KeyError, IndexError): 198 | return default 199 | except (IndexError, TypeError): 200 | return default 201 | 202 | return default if value in ignore else value 203 | 204 | def deepset(self, deepkey: str, value: Any) -> None: 205 | existing_keys, nonexistent_keys = re.split(r'[.\[]', deepkey), [] 206 | last_key: str = int_key(existing_keys.pop()) 207 | 208 | while existing_keys: 209 | data = GqylpyDict.deepget(self, '.'.join(existing_keys), __unique__) 210 | # Why `GqylpyDict.deepget`? Compatible with built-in dict instance. 211 | 212 | key: str = int_key(existing_keys.pop()) 213 | 214 | if data is not __unique__: 215 | try: 216 | next_key = nonexistent_keys[0] 217 | except IndexError: 218 | next_key = last_key 219 | if ( 220 | next_key.__class__ is str and not isinstance(data, dict) 221 | or 222 | next_key.__class__ is int and data.__class__ is not list 223 | ): 224 | data = GqylpyDict.deepget(self, '.'.join(existing_keys)) \ 225 | if existing_keys else self 226 | nonexistent_keys.insert(0, key) 227 | break 228 | nonexistent_keys.insert(0, key) 229 | else: 230 | data = self 231 | 232 | for i, key in enumerate(nonexistent_keys): 233 | try: 234 | next_key = nonexistent_keys[i + 1] 235 | except IndexError: 236 | next_key = last_key 237 | next_data = GqylpyDict() if next_key.__class__ is str else [] 238 | data = set_next_data(data, key, next_data) 239 | set_next_data(data, last_key, value) 240 | 241 | def deepsetdefault(self, deepkey: str, default: Any) -> Any: 242 | value = GqylpyDict.deepget(self, deepkey, __unique__) 243 | if value is __unique__: 244 | GqylpyDict.deepset(self, deepkey, default) 245 | return default 246 | return value 247 | 248 | def deepcontain(self, deepkey: str, /) -> bool: 249 | return False if GqylpyDict.deepget(self, deepkey, __unique__) is \ 250 | __unique__ else True 251 | 252 | getdeep, setdeep = deepget, deepset 253 | 254 | __deepcopy__ = None 255 | # Compatible function `copy.deepcopy`. 256 | 257 | __isabstractmethod__ = False 258 | # Compatible metaclass `abc.ABCMeta`. 259 | 260 | 261 | def int_key(key: str, /) -> Union[int, str]: 262 | try: 263 | return int(key[:-1]) 264 | except ValueError: 265 | return key 266 | 267 | 268 | def set_next_data( 269 | data: Union[dict, list], 270 | key: Union[int, str], 271 | value: Any 272 | ) -> Any: 273 | try: 274 | data[key] = value 275 | except IndexError: 276 | if key in (0, -1): 277 | data.append(value) 278 | elif key > 0: 279 | for _ in range(key - len(data)): 280 | data.append(None) 281 | data.append(value) 282 | else: 283 | for _ in range(abs(key) - len(data) - 1): 284 | data.append(None) 285 | data.insert(0, value) 286 | return data[key] 287 | -------------------------------------------------------------------------------- /issues.yml: -------------------------------------------------------------------------------- 1 | - issue: "考虑同时支持接收单个值" 2 | location: "GqylpyDict.deepget 参数 ignore" 3 | create: 2022-08-01 4 | labels: question 5 | status: NotProcess 6 | description: " 7 | 在多数情况下我们想忽略的值都是单个的,如果能直接传入,而不是每次都包装一层列表,岂不更好。 8 | " 9 | process: 10 | 2022-08-02: " 11 | 若传入的单个值本身是一个列表或元组,将出现问题,这种情况下我们无法判断开发者想传入的值是单个还 12 | 是多个。因此我们不能这样做。 13 | " 14 | 15 | - issue: "通过值解压的方式实例化得到的不是一个字典对象" 16 | location: "GqylpyDict.__init__" 17 | create: 2022-08-01 18 | labels: question 19 | status: NotProcess 20 | description: " 21 | 内置dict通过值解压的方式实例化得到的是一个字典对象,而gdict通过值解压的方式实例化得到的是一个列 22 | 表对象。且看下面的代码运行结果: 23 | 24 | >>> dict((x, y) for x, y in [('a', 'A'), ('b', 'B')]) 25 | {'a': 'A', 'b': 'B'} 26 | 27 | >>> gdict((x, y) for x, y in [('a', 'A'), ('b', 'B')]) 28 | [['a', 'A'], ['b', 'B']] 29 | 30 | 我们希望它们得到同样的结果,无论在任何情况下。gdict得到列表对象肯定是错误的,这是一个严重的问题, 31 | 它使我们对gdict的理念产生了偏移,我们希望gdict在任何时候都有着与dict相同的特性。 32 | " 33 | process: 34 | 2022-08-02: " 35 | 发现这样的问题我们反思,这在设计之初大意了,我们没有考虑到值解压的方式创建字典。gdict实例化的 36 | 核心在于深度转换dict,将dict转换为gdict,深度的理解是同时会转换dict内层的dict。 37 | 38 | 深度转换dict 与 值解压实例化gdict 是冲突的。 39 | 40 | gdict实例化是一个递归的过程,当传入一个字典以外的容器类对象或Iterator对象时,它会分解容器或 41 | Iterator并依次进入递归层,并在所有递归层结束后返回一个列表对象,且看下面的代码: 42 | 43 | def __new__(cls, __data__={}, **kw): 44 | if isinstance(__data__, (list, tuple, set, Iterator)): 45 | return [cls(v) for v in __data__] 46 | 47 | 介于此设计,做了深度转换dict,就无法再做值解压实例化gdict,我们不考虑给gdict增加任何流程控 48 | 制参数。如一定要做值解压实例化gdict,可通过下面的方式: 49 | 50 | >>> gdict(dict((x, y) for x, y in [('a', 'A'), ('b', 'B')])) 51 | {'a': 'A', 'b': 'B'} 52 | " 53 | 54 | - issue: "是否可以做到gdict覆盖内嵌dict" 55 | location: "No location" 56 | create: 2022-08-02 57 | labels: question 58 | status: UnableProcess 59 | description: " 60 | 用gdict覆盖内嵌dict,这样以后在全局创建例如 {'a': 'A'} 即可直接得到gdict实例,而无需再做转换。 61 | " 62 | process: 63 | 2022-08-02: " 64 | 初步尝试,从builtins中篡改dict。尝试未成功,如下代码: 65 | 66 | >>> import builtins 67 | >>> import gqylpy_dict 68 | >>> builtins.dict = gqylpy_dict.gdict 69 | 70 | 此时查看dict,出现一个奇特的现象: 71 | 72 | >>> dict.__qualname__ 73 | 'GqylpyDict' 74 | >>> dict().__class__.__qualname__ 75 | 'dict' 76 | >>> {}.__class__.__qualname__ 77 | 'dict' 78 | 79 | 全局dict已经指向gdict,但调用全局dict得到的却仍是内嵌dict的实例。这样看来,调用全局dict 80 | 并不是调用builtins.dict。Python是一门高深的语言,你永远也猜不到它的底层设计。 81 | " 82 | 2022-08-03: " 83 | 尝试方案二,从globals中篡改dict: 84 | 85 | >>> import gqylpy_dict 86 | >>> globals()['dict'] = gqylpy_dict.gdict 87 | 88 | 此时查看dict: 89 | 90 | >>> dict.__qualname__ 91 | 'GqylpyDict' 92 | >>> dict().__class__.__qualname__ 93 | 'GqylpyDict' 94 | >>> {}.__class__.__qualname__ 95 | 'dict' 96 | 97 | 看起来比上一次成功,全局dict已经指向gdict,调用全局dict也得到gdict实例,但使用 {} 的方式 98 | 仍得到内嵌dict实例。 99 | " 100 | 101 | - issue: "向gdict实例中写入的dict实例没有被转换为gdict实例" 102 | location: "GqylpyDict.__setattr__" 103 | create: 2022-08-02 104 | labels: question 105 | status: Processed 106 | description: " 107 | 问题模拟代码: 108 | 109 | >>> d = gdict() 110 | >>> d.a = {} 111 | >>> d.a.__class__.__qualname__ 112 | 'dict' 113 | 114 | 我们希望 d.a.__class__.__qualname__ 得到的是 'GqylpyDict'。 115 | " 116 | process: 117 | 2022-08-02: " 118 | 解决方案:重写__setitem__方法,如下代码: 119 | 120 | def __setitem__(self, name, value): 121 | dict.__setitem__(self, name, GqylpyDict(value)) 122 | 123 | 首先要说明,__setattr__方法内部调用__setitem__。我们将要写入的数据做一次转换,如果数据是 124 | dict的实例,将被转换为gdict实例,包括内层数据。 125 | " 126 | 2022-08-03: " 127 | 就在刚刚,我们发现重写__setitem__方法使deepset方法失效了,它无法正确深度设置值。进一步排 128 | 查,问题是出在初始化函数中,重写__setitem__方法让这个问题浮现,关键代码: 129 | 130 | if isinstance(__data__, dict): 131 | return dict.__new__(cls) 132 | 133 | 如果__dict__是gdict的实例,这条判断语句也是成立的,因为gdict继承dict。这将会创建新的gdict 134 | 实例,这是错误的,deepset的底层设计不能创建新的gdict实例。为此,我们再次调整代码: 135 | 136 | if __data__.__class__ is dict: 137 | return dict.__new__(cls) 138 | 139 | 这样做可以有效避免在调用deepset时重复创建gdict实例的问题,但也引申出新的可能出现的问题:若传 140 | 入的__data__是其它继承dict类的实例,将不会被转换为gdict实例。我们暂不考虑使用例如这样的语句 141 | 来避免此问题: 142 | 143 | if isinstance(__data__, dict) and __data__.__class__ is not gdict: 144 | return dict.__new__(cls) 145 | " 146 | 2023-04-22: " 147 | 已采用上述语句避免此问题,问题关闭。 148 | " 149 | 150 | - issue: "deepsetdefault执行后应该返回原值还是转为gdict实例后的值" 151 | location: "GqylpyDict.deepsetdefault" 152 | create: 2022-08-02 153 | labels: question 154 | status: Processed 155 | description: " 156 | gdict.deepsetdefault 与 dict.setdefault 功能相似,值不存在则设置值并返回值。若设置的值 157 | 是一个dict实例且不存在,我们会先将这个dict实例转为gdict实例再执行设置,从而保证gdict实例的正 158 | 确性(gdict实例内层绝不包含dict实例,这是我们对gdict的理念,也是原则)。那么在返回值时,我们 159 | 应该返回原dict实例还是转换后的gdict实例呢,问题的关键在于此。 160 | " 161 | process: 162 | 2022-08-02: " 163 | 根据dict.setdefault的使用经验,往往开发者在调用setdefault后会根据返回值作进一步处理。放到 164 | gdict.deepsetdefault,如果该返回值与gdict实例中的值不是同一个值(即返回值是dict实例,而 165 | gdict实例中的值是gdict实例),将可能会出现无法预期的结果。 166 | 167 | 因此我们决定返回值为转换为gdict实例后的值。但也要注意,这样做会导致另一个问题,我们择重: 168 | 169 | >>> x = {'x': 'X'} 170 | 171 | >>> data = gdict() 172 | >>> x2 = data.deepsetdefault('a.b', x) 173 | 174 | >>> x2 == x == data.a.b 175 | True 176 | 177 | >>> x2 is x 178 | False 179 | 180 | >>> x2 is data.a.b 181 | True 182 | " 183 | 184 | - issue: "是否考虑提供deepdel的方法" 185 | location: "No location" 186 | create: 2022-08-04 187 | labels: question 188 | status: NotProcess 189 | description: " 190 | 既然提供了deepget,deepset方法,那deepdel方法怎能少呢。 191 | " 192 | process: 193 | 2022-08-05: " 194 | deepdel的使用场景几乎为0,不考虑提供。 195 | " 196 | 197 | - issue: "字典对象不能使用set去重" 198 | location: GqylpyDict.__hash__ 199 | create: 2022-08-05 200 | labels: question 201 | status: Processed 202 | description: " 203 | 这是大的问题,在一些场景中我们必须对字典对象去重,这就得引入或现写非哈希的去重算法,极为不便。如 204 | 果能直接使用set去重,将大幅提高代码开发效率及可读性。我们能否做到让gdict对象使用set去重? 205 | " 206 | process: 207 | 2022-08-05: " 208 | 如果让set能对字典对象去重,将会出现一个更大的问题。首先你必须清楚set为什么不能对字典对象去重, 209 | 因为字典是一个可变的容器,将字典对象加入到set中并不影响它可变的特性。设想一种情况,两个不相等 210 | 的字典对象加入到了set中,之后这两个字典对象经过变化值相等了,那这问题就大了,set中的值不唯一 211 | 了,这颠覆set的理念。 212 | 213 | 说了这么多是为了先给你建立一个正确的认知。然后呢我们就喜欢干这种颠覆认知的事情,我们完全可以做 214 | 到让gdict对象使用set去重。 215 | 216 | 你必须了解下面两件事: 217 | 哈希理念:两个约相等的对象的哈希值一定相等,反之未必。 218 | set去重流程:先调用对象的__hash__方法,若返回值相等,再调用__eq__方法... 219 | 220 | 结合哈希理念与set去重流程,我们从__hash__方法入手: 221 | 222 | def __hash__(self): 223 | return -2 224 | 225 | 编写__hash__方法并固定返回值,这意味着gdict对象是可哈希的了,并且哈希值始终相等。此时gdict 226 | 对象已经可以使用set去重了,它将忽略哈希检查,始终检查值是否相等。 227 | 228 | >>> a1 = gdict(a='A1') 229 | >>> a2 = gdict(a='A2') 230 | >>> xx = gdict(a='A1') 231 | >>> {a1, a2, xx} 232 | {{'a': 'A1'}, {'a': 'A2'}} 233 | 234 | 开头我们提到过这样做会出现一个更大的问题,会使set中的值不唯一,如下代码展示: 235 | 236 | >>> a1 = gdict(a='A1') 237 | >>> a2 = gdict(a='A2') 238 | >>> x = {a1, a2} 239 | >>> a2.a = 'A1' 240 | >>> x 241 | {{'a': 'A1'}, {'a': 'A1'}} 242 | 243 | 因此,gdict对象可以使用set去重,但要格外小心。 244 | " 245 | 246 | - issue: "仅希望获取某几个值" 247 | location: GqylpyDict.deepget 248 | create: 2022-08-14 249 | labels: question 250 | status: NotProcess 251 | description: " 252 | 在某些情况下我们希望从字典中取到的值是某几个特定值中的其中一个,如果不是,将返回默认特定值。 253 | " 254 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | import gqylpy_dict as g 3 | 4 | gdoc: list = g.__doc__.split('\n') 5 | 6 | for index, line in enumerate(gdoc): 7 | if line.startswith('@version: ', 4): 8 | version = line.split()[-1] 9 | break 10 | _, author, email = gdoc[index + 1].split() 11 | source = gdoc[index + 2].split()[-1] 12 | 13 | setuptools.setup( 14 | name=g.__package__, 15 | version=version, 16 | author=author, 17 | author_email=email, 18 | license='WTFPL,Apache-2.0', 19 | url='http://gqylpy.com', 20 | project_urls={'Source': source}, 21 | description=''' 22 | `gqylpy-dict` is based on the built-in `dict` and serves as an 23 | enhancement to it. It can do everything the built-in `dict` can do, and 24 | even more. 25 | '''.strip().replace('\n ', ''), 26 | long_description=open('README.md', encoding='utf8').read(), 27 | long_description_content_type='text/markdown', 28 | packages=[g.__package__], 29 | python_requires='>=3.8', 30 | classifiers=[ 31 | 'Development Status :: 5 - Production/Stable', 32 | 'Intended Audience :: Developers', 33 | 'Natural Language :: Chinese (Simplified)', 34 | 'Natural Language :: English', 35 | 'Operating System :: OS Independent', 36 | 'Topic :: Software Development :: Libraries :: Python Modules', 37 | 'Topic :: Artistic Software', 38 | 'Topic :: Scientific/Engineering :: Mathematics', 39 | 'Programming Language :: Python :: 3.8', 40 | 'Programming Language :: Python :: 3.9', 41 | 'Programming Language :: Python :: 3.10', 42 | 'Programming Language :: Python :: 3.11', 43 | 'Programming Language :: Python :: 3.12', 44 | 'Programming Language :: Python :: 3.13' 45 | ] 46 | ) 47 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import gqylpy_dict as gdict 2 | 3 | x = {'a': [{'b': 'B'}]} 4 | x = gdict(x) 5 | 6 | print(x.a[0].b) 7 | print(x.deepget('a[0].b')) 8 | --------------------------------------------------------------------------------