1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
|
__author__ = "ChenyangGao <https://chenyanggao.github.io/>" __version__ = (0, 1, 1) __all__ = ["AttrDict", "DictAttr", "Properties"]
import builtins
from pathlib import Path from types import CodeType from typing import MutableMapping, Optional, Union
class AttrDict(dict): "扩展的 dict 类型,它的实例的 __dict__ 属性(命名空间)就是该字典本身,因此执行 __getattr__、__setattr__、__delattr__ 操作的是该字典本身" def __init__(self, *args, **kwds): super().__init__(*args, **kwds) self.__dict__ = self
@MutableMapping.register class DictAttr: """这个类型实现了 collections.abc.MutableMapping 的接口,作为其结构子类型(structural subtyping),即鸭子类型(duck typing),而非名义子类型(nominal subtyping)。 :param init_dict: 如果不为 None,会用于替换实例的 __dict__ 属性 :: doctest >>> d = dict(foo={}) >>> da = DictAttr(d) >>> da DictAttr({'foo': {}}) >>> da.foo DictAttr({}) >>> da.foo.bar = 1 >>> da DictAttr({'foo': {'bar': 1}}) >>> d {'foo': {'bar': 1}} >>> da.__dict__ is d True """ def __init__(self, init_dict: Optional[dict] = None): if init_dict is not None: self.__dict__ = init_dict
def __contains__(self, key): return key in self.__dict__
def __iter__(self): return iter(self.__dict__)
def __len__(self): return len(self.__dict__)
def __repr__(self): return f"{type(self).__qualname__}({self.__dict__!r})"
def __getattribute__(self, attr): "如果 attr 是字符串且前后都附缀两个下划线 __,则执行原始行为。否则行为相当于 __getitem__,但在取不到值时,会再执行原始行为。可能抛出 Attribute Error 异常。" if type(attr) is str and attr[:2] == attr[-2:] == "__": return super().__getattribute__(attr) try: return self[attr] except KeyError: return super().__getattribute__(attr)
def __getitem__(self, key): "从 __dict__ 中取值,当值是 dict 类型时,会被 type(self) 对应的类包装" val = self.__dict__[key] if type(val) is dict: return type(self)(val) return val
def __setitem__(self, key, val): "向 __dict__ 中设键值" self.__dict__[key] = val
def __delitem__(self, key): "从 __dict__ 中删键" del self.__dict__[key]
class Properties(DictAttr): """这个类型实现了 collections.abc.MutableMapping 的接口,作为其结构子类型(structural subtyping),即鸭子类型(duck typing),而非名义子类型(nominal subtyping)。 :param init_dict: 如果不为 None,会用于替换实例的 __dict__ 属性 :: doctest >>> d = dict(foo={}) >>> props = Properties(d) >>> props Properties({'foo': {}}) >>> props.foo Properties({}) >>> props.foo.bar = 1 >>> props Properties({'foo': {'bar': 1}}) >>> props.bar Properties({}) >>> props Properties({'foo': {'bar': 1}, 'bar': {}}) >>> props.baz.bay.baz = 1 >>> props Properties({'foo': {'bar': 1}, 'bar': {}, 'baz': {'bay': {'baz': 1}}}) >>> d {'foo': {'bar': 1}, 'bar': {}, 'baz': {'bay': {'baz': 1}}} >>> props.__dict__ is d True """ def __getitem__(self, key): "从 __dict__ 中取值。首先执行基类 DictAttr 的原始行为;如果取不到值,再从 builtins.__dict__ 中取值;如果取不到,则对于非下划线前缀的字符串属性,把值设为 {},再执行一次基类的原始行为,否则抛出 KeyError。" try: return super().__getitem__(key) except KeyError: try: return builtins.__dict__[key] except KeyError: pass if type(key) is not str or key.startswith("_"): raise self[key] = {} return super().__getitem__(key)
def __abs__(self) -> dict: "创建一个 dict 副本,如果有 __all__ 字段,则筛选出所有在 __all__ 中的键,否则只筛选出键是字符串类型且非下划线前缀的键值对" d = self.__dict__ if "__all__" in d: return { k: abs(v) if isinstance(v, Properties) else v for k, v in ((k, d[k]) for k in d["__all__"] if k in d) } return { k: abs(v) if isinstance(v, Properties) else v for k, v in d.items() if type(k) is str and not k.startswith("_") }
def __call__(self, source: Union[str, bytes, CodeType, Path]): """执行一段 Python 代码,并更新 __dict__ 属性(命名空间) :param source: Python 代码或者代码文件的路径 :return: 返回实例本身
:: tips - 请将变量名提前注入 __dict__,否则缺失时自动设为 {} - 如果代码中有 import 命令,请确保把需要的路径加到 sys.path 中,避免找不到模块 - 属性名有前缀下划线 _,用于说明想要过滤掉
:: doctest >>> # 构造一段 Python 代码 >>> code = 'from math import nan as _nan, inf as _inf\\nz = _inf\\ny.z = _nan\\nx.y.z = y.z\\nfoo = sum([1,2, 3])\\nbar = abs(1+1j)' >>> print(code) from math import nan as _nan, inf as _inf z = _inf y.z = _nan x.y.z = y.z foo = sum([1,2, 3]) bar = abs(1+1j) >>> props = Properties() >>> props Properties({}) >>> props(code) Properties({'_nan': nan, '_inf': inf, 'z': inf, 'y': {'z': nan}, 'x': {'y': {'z': nan}}, 'foo': 6, 'bar': 1.4142135623730951}) >>> print(abs(props)) {'z': inf, 'y': {'z': nan}, 'x': {'y': {'z': nan}}, 'foo': 6, 'bar': 1.4142135623730951} """ code: Union[str, bytes, CodeType] if isinstance(source, Path): code = source.open(encoding="utf_8").read() else: code = source exec(code, None, self) return self
if __name__ == "__main__": import doctest doctest.testmod(verbose=True)
|