355 lines
13 KiB
Python
355 lines
13 KiB
Python
|
|
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 # 返回实例,替换原始类
|