from typing import Any from core.defines import Dict, List import json import sys, os sys.path.append(os.path.join(os.path.dirname(__file__), '..')) import core.logger as logger log = logger.get_logger() class BaseConfig(): """ 所有配置类的基类, 所有配置类都继承自该类 提供配置的统一使用方式: 1. 基类通过 __getattr__ 方法,实现通过点号访问配置 2. 基类通过 __setattr__ 方法,实现通过点号设置配置 3. 基类通过 __getitem__ 方法,实现通过索引访问配置 4. 基类通过 __setitem__ 方法,实现通过索引设置配置 5. 基类通过 __delitem__ 方法,实现通过索引删除配置 6. 基类通过 __len__ 方法,实现获取配置的长度 7. 基类通过 __str__ 方法,实现获取配置的json字符串 8. 基类通过 __repr__ 方法,实现获取配置的json字符串 9. 子类通过 check 方法,实现检查配置是否合法 10. 子类通过 @AutoInstanceDecorator 装饰器,自动将类替换为它的实例 """ # 存储原始配置数据,子类复写该属性以表明其配置,其可以是列表或字典 _config = None _list = List() # 用于存储配置是一个list的形式 _dict = Dict() # 配置的key-value形式,以及list也会扩展为dict存储,方便使用 _value = None # 配置仅仅是一个value的情况 # 用于存储实例, 单例模式,防止重复实例化 子类无须操作该属性 _instance = None def __new__(cls): """ 如果实例已经存在,则直接返回实例,实现单例模式。 """ if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def _value_raise(self): if self._value is not None: raise AttributeError(f"{self.__class__.__name__} not support this method") def __init__(self) -> None: """ 初始化实例的_list和_dict, 并将其绑定到类属性上, """ # log.info(f"Load config: {self.__class__.__name__}") # 获取子类的 _config 属性 _config = getattr(self.__class__, '_config', None) if _config is None: raise ValueError(f"_config must be defined in {self.__class__.__name__}") self._list = List() self._dict = Dict() if isinstance(_config, list): self._list = List(_config) for key in self._list: # 将列表项同时添加到属性中 以便于通过Dot方式访问 self._dict[key] = key elif isinstance(_config, dict): self._dict = Dict(_config) # 配置是单个值的情况 elif isinstance(_config, (int, float, str, bool)): self._value = _config self._dict = Dict({str(_config) : _config}) self._list = List([_config]) else: raise ValueError(f"{self.__class__.__name__} _config must be a list or dict") log.debug(f"Load config cnt: {len(self._dict):3} => {self.__class__.__name__}") def __getattr__(self, key): """ 当通过类属性方式访问不存在的属性时,尝试从 _dict 中获取 """ return self[key] def __setattr__(self, key, value): """ 当通过类属性方式设置值时,同时更新 _dict """ # log.info(f"setattr: {self.__class__.__name__}, key: {key}, value: {value}") # 如果是非字符串,或者是非_开头的字符串,则将其添加到_dict中 if not isinstance(key, str) or not key.startswith('_'): self[key] = value # 如果key是字符串且是合法的标识符,则将其添加到类属性中 elif isinstance(key, str): super().__setattr__(key, value) else: raise AttributeError(f"Attribute {key} is not a valid identifier") def __delattr__(self, key:str): """ 当通过类属性方式删除值时,同时删除 _dict 中的值 """ del self[key] def items(self): return self._dict.items() def get(self, key=None, default=None): """ 获取配置值 """ if key is None: if self._value is not None: return self._value else: raise KeyError(f"{self.__class__.__name__} must set key to get") return self[key] if key in self else default def __getitem__(self, key): """ 当通过索引方式访问值: 1. 如果key是整数,按列表方式访问 2. 如果key是字符串,按字典方式访问 3. 如果key既不是整数也不是字符串,抛出TypeError 例如: config = MyConfig() print(config[0]) print(config["key"]) """ if isinstance(key, int): return self._list[key] elif isinstance(key, str): # 如果key中包含/,说明可能是个路径。返回key本身 # 因为有些配置直接将路径作为配置项 if '/' in key: return key else: # 如果 key 是字符串,按字典方式访问 return self._dict[key] else: raise TypeError("Key must be either an integer or a string") def __setitem__(self, key, value): """ 当通过索引方式设置值: 1. 如果key是整数,按列表方式设置 2. 如果key是字符串,按字典方式设置 3. 如果key既不是整数也不是字符串,抛出TypeError 例如: config = MyConfig() config[0] = "value" config["key"] = "value" """ # log.info(f"setitem: {self.__class__.__name__}, key: {key}, value: {value}") if isinstance(key, int): old_value = self._list[key] self._list[key] = value self._dict.pop(old_value) key = value elif isinstance(key, str): if self._list: # 我们会将新的key和value同时添加到_list和_dict中 确保key和value是相同的 if key in self._list: self._list[self._list.index(key)] = value self._dict.pop(key) key = value else: self._list.append(key) else: raise TypeError("Key must be either an integer or a string") self._dict[key] = value # log.info(f"setitem ok: {self.__class__.__name__}, key: {key}, value: {self._dict[key]}") def __delitem__(self, key): """ 当通过索引方式删除值: 1. 如果key是整数,按列表方式删除 2. 如果key是字符串,按字典方式删除 3. 如果key既不是整数也不是字符串,抛出TypeError 例如: config = MyConfig() del config[0] del config["key"] """ if isinstance(key, int): self.__delattr__(self._list[key]) elif isinstance(key, str): # 如果 key 是字符串,按字典方式删除 if key not in self._dict: raise KeyError(f"Key {key} not found in the dictionary") self._dict.pop(key) if key in self._list: self._list.pop(self._list.index(key)) else: raise TypeError("Key must be either an integer or a string") def __contains__(self, key): """ 检查配置是否包含某个key """ return key in self._dict def __iter__(self): """ 返回配置的迭代器 """ if self._value is not None: yield self._value elif self._list: yield from self._list elif self._dict: yield from self._dict else: raise StopIteration def __len__(self): """ 返回配置的长度 """ if self._value is not None: return 1 elif self._list: return len(self._list) elif self._dict: return len(self._dict) else: return 0 def __str__(self): """ 返回对象的字符串表示,使用json 风格来呈现 """ # log.info(f"str: {self.__class__.__name__}, len: {len(self._list)}") if self._value is not None: return f"{self._value}" elif self._list: return json.dumps(self._list, indent=4) elif self._dict: return json.dumps(self._dict, indent=4) else: return "" def __repr__(self): """ 返回对象的字符串表示,使用json 风格来呈现 """ return self.__str__() def __eq__(self, other): """ 实现对象的相等比较 1. 如果other是字符串,且self._value不为None,则与_value进行比较 2. 如果other是BaseConfig实例: - 如果两者都有_value,则比较_value - 如果两者都有_list,则比较_list - 如果两者都有_dict,则比较_dict 3. 如果other是列表,且self._list不为空,则与_list进行比较 4. 如果other是字典,且self._dict不为空,则与_dict进行比较 5. 否则,使用默认的比较行为 """ # 处理字符串比较 if isinstance(other, (int, float, str, bool)) and self._value is not None: return self._value == other # 处理BaseConfig实例比较 elif isinstance(other, BaseConfig): if self._value is not None and other._value is not None: return self._value == other._value elif self._list and other._list: return self._list == other._list elif self._dict and other._dict: return self._dict == other._dict else: return False elif isinstance(other, list) and self._list: return self._list == other elif isinstance(other, dict) and self._dict: return self._dict == other else: return super().__eq__(other) def check(self) -> bool: """检查配置是否合法 Returns: bool: 如果配置合法,返回True,否则返回False 子类可以复写该方法,以检查配置是否合法 """ return True def update(self, _config): """更新配置 Args: _config (dict): 需要更新的配置 Raises: ValueError: 如果_config不是字典或列表 """ if isinstance(_config, dict): for key, value in _config.items(): self[key] = value elif isinstance(_config, list): for item in _config: self[item] = item elif isinstance(_config, (int, float, str, bool)): self._value = _config self._dict = Dict({str(_config) : _config}) self._list = List([_config]) else: raise ValueError(f"_config type {type(_config)} is not support!") def _clean(self): """清除所有配置 """ [self.__delitem__(_) for _ in self._dict.copy()] def update_from_extra(self, new_data:Any): """ 从json中更新数据,默认行为是加载,子类可以重载该函数使得其可以具备不同的行为 Args: new_data (Any): 带有直接属于该配置的数据 """ self.load(new_data) def load(self, new_data:Any): """ 加载数据:会先清除当前配置,然后更新配置 Args: new_data (Any): 带有直接属于该配置的数据 例如: ```python MyConfig.load({"key1": "value1", "key2": "value2"}) MyConfig1.load([1, 2, 3]) MyConfig2.load(123) MyConfig3.load("123") MyConfig4.load(True) ``` """ if not isinstance(new_data, (dict, list, int, float, str, bool)): raise ValueError(f"Datatype {type(new_data)} is not support!") self._clean() self.update(new_data) def remove(self, new_data: Any): """删除数据 """ if isinstance(new_data, (list, dict)): for item in new_data: del self[item] elif isinstance(new_data, (int, float, str, bool)): del self[new_data] else: raise ValueError(f"Datatype {type(new_data)} is not support!") def AutoInstanceDecorator(cls): """自动实例化装饰器:自动将类替换为它的实例,实现类名访问即实例访问, 例如: ```python print(MyConfig.key) ``` 等价于 ```python config = MyConfig() print(config["key"]) ``` """ instance = cls() # 创建实例 return instance # 返回实例,替换原始类