今天写日志模块时,需求需要将常见的.log
格式转为.jsonl
格式。由于是封装的标准库logging
的东西,原来是简单的字符串formatter格式,现在要转为json格式,同时要传入不同字段并以特定顺序进行打印。
转json-formatter容易,只需要封装logging.Formatter
类即可。但是将配置以特定字段顺序打印却很繁琐,要么需要实现类,要么以Dict+大量硬编码实现,同时也伴随着需求的不断变化。最后找到了@dataclass
+ asdict
方法进行处理,极大的简化了开发效率。
@dataclass
是Python3.7引入的标准库功能,位于模块dataclasses
中,是用来简化数据类定义的装饰器。
传统上,定义一个只用来存储数据的类时,需要手动实现__init__
、__repr__
、__eq__
等方法。而使用@dataclass
能自动生成这些方法。
传统上:
python
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
使用@dataclass
:
python
@dataclass
class Person:
name: str
age: int
# 等价于
class Person:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def __repr__(self):
return f'Person(name={self.name!r}, age={self.age!r})'
def __eq__(self, other):
return isinstance(other, Person) and self.name == other.name and self.age == other.age
@dataclass
的一些参数:
init=True
:是否自动生成__init__
(默认是)repr=True
:是否自动生成__repr__
(默认是)eq=True
:是否自动生成__eq__
(默认是)order=True
:是否添加比较运算符,如<
、>=
(需字段支持)frozen=True
:是否生成"不可变类"(类似namedtuple
)slots=True
:是否使用__slots__
限定字段。
同时也带默认值和字段控制
python
@dataclass
class Config:
host: str
port: int = 8080
debug: bool = field(default=False)
@dataclass
装饰器要求必须字段必须在可选字段的前面
python
@dataclass
class LogEntry:
# 必选
a: str
b: str
c: str
# 可选
d: Optional[str] = None
e: Dict[str, Any] = field(default_factory=dict)
赋值完数据后,使用asdict
将其转换为Dict
python
from dataclasses import asdict, dataclass, field
from typing import Dict, Any, Optional
@dataclass
class LogEntry:
# 必选
a: str
b: str
c: str
# 可选
d: Optional[str] = None
e: Dict[str, Any] = field(default_factory=dict)
logentry = LogEntry(
a='a',
b='b',
c='c',
)
logentry_dict = asdict(logentry)
print(logentry_dict)
在赋值时,由于存在可选字段没有进行赋值,还是默认值,如果想要去掉默认的可选字段可以使用字典变量:
python
filtered_log_data = {k: v for k, v in logentry_dict.items() if (v is not None and v != {})}
完整:
python
from dataclasses import asdict, dataclass, field
from typing import Dict, Any, Optional
@dataclass
class LogEntry:
# 必选
a: str
b: str
c: str
# 可选
d: Optional[str] = None
e: Dict[str, Any] = field(default_factory=dict)
logentry = LogEntry(
a='a',
b='b',
c='c',
)
logentry_dict = asdict(logentry)
filtered_log_data = {k: v for k, v in logentry_dict.items() if (v is not None and v != {})}
print(filtered_log_data)