_property.py 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. # @description:
  2. # @author: licanglong
  3. # @date: 2025/10/15 17:42
  4. import threading
  5. from collections import UserDict
  6. from typing import Literal
  7. class PropertyDict(UserDict):
  8. """
  9. 增强版字典:
  10. - 继承自 UserDict,保留原生 dict 功能
  11. - 增加 getprop 方法,支持点分路径取值
  12. """
  13. def __init__(self, initial_data=None):
  14. super().__init__(initial_data or {})
  15. self._lock = threading.RLock()
  16. def merge(self, override: dict, none_mode: Literal['ignore', 'delete', 'override'] = 'ignore'):
  17. """
  18. 递归合并 override 到 self.data
  19. :param none_mode:
  20. - 'ignore' 遇到 None 时跳过,不覆盖原值
  21. - 'delete' 遇到 None 时删除对应键
  22. - 'override' 默认行为,直接覆盖
  23. """
  24. def _merge(base, override):
  25. for k, v in override.items():
  26. if v is None:
  27. if none_mode == 'ignore':
  28. continue
  29. elif none_mode == 'delete':
  30. base.pop(k, None)
  31. continue
  32. # else 'override',直接覆盖 None
  33. if k in base and isinstance(base[k], dict) and isinstance(v, dict):
  34. _merge(base[k], v)
  35. else:
  36. base[k] = v
  37. with self._lock:
  38. _merge(self.data, override)
  39. def getprop(self, key: str, default=None, *, raise_error: bool = False, delimiter: str = "."):
  40. """
  41. 从嵌套字典中获取指定 key 的值。
  42. - key 支持分隔符路径,例如 "database.host" -> config["database"]["host"]
  43. - 分隔符默认是 '.',可以通过参数 delimiter 指定
  44. - 如果路径不存在,或最终值为 None,返回 default
  45. - 禁止 key 中出现空片段(如 "a..b"、".a"、"a."),一旦出现返回 default
  46. - raise_error=True 时,如果路径不存在则抛 KeyError
  47. """
  48. if not isinstance(self.data, dict):
  49. if raise_error:
  50. raise KeyError(f"Config is not a dict, cannot get key '{key}'")
  51. return default
  52. if not isinstance(key, str) or not key:
  53. if raise_error:
  54. raise KeyError(f"Invalid key: '{key}'")
  55. return default
  56. key_args = [p.strip() for p in key.split(delimiter)]
  57. if any(not part for part in key_args): # 禁止空片段
  58. if raise_error:
  59. raise KeyError(f"Key contains empty segment: '{key}'")
  60. return default
  61. current = self.data
  62. for part in key_args:
  63. if isinstance(current, dict):
  64. if part in current:
  65. current = current[part]
  66. else:
  67. if raise_error:
  68. raise KeyError(f"Key path not found: '{key}'")
  69. return default
  70. else:
  71. if raise_error:
  72. raise KeyError(f"Key path not found: '{key}'")
  73. return default
  74. return default if current is None else current