
Python 不是强类型语言,开发人员没有给数据定义类型的习惯。这样虽然灵活,但处理复杂业务逻辑的时候却不够方便缺乏类型检查可能导致很难发现错误,在 IDE 里编码时也没有代码提示。所以开发了这个小工具来解决它。
from typing import List class Person: name: str age: int class Company: name: str revenue: float employees: List[Person] 之所以选择类变量来定义,是因为它最简洁和直观。相比之下,如果在__init__方法中初始化实例变量,是没有办法获取类型定义( type_hint )的;如果用 @property 注解或者 getter,setter 方法的话,显然就更复杂了。它们都不如直接定义类变量简单优美。不过使用类变量也有缺点:就是它在这里被当成元数据来使用了,如果真的需要定义类级别共享的变量,无法区分。这个问题可以在后面通过开发自定义注解来解决。
from objtyping import objtyping company1 = objtyping.from_dict_list({ 'name': 'Apple', 'revenue': 18.5, 'employees': [{ 'name': 'Tom', 'age': 20 }, { 'name': 'Jerry', 'age': 31 }] }, Company) 此时的 company1 就是完整的 Company 对象了, 可以直接使用 company1.name, company1.employees[0].name 等形式访问里面的属性。
from objtyping import objtyping dict_list = objtyping.to_dict_list(company1) 此时的 dict_list 对象,就是一大堆 dict 和 list 层级嵌套的原始类型数据
Python 没有 js 那么方便的初始化对象方式,但有这个工具就可以这样写(就是前面基础使用的汇总):
from typing import List from objtyping import objtyping class Person: name: str age: int class Company: name: str revenue: float employees: List[Person] def __str__(self): # 其实一般可能都是这样简单用一下的 return "'{}' has {} employees: {}".format(self.name, len(self.employees), ' and '.join(map(lambda emp: emp.name, self.employees))) if __name__ == '__main__': company1 = objtyping.from_dict_list({ 'name': 'Apple', 'revenue': 18.5, 'employees': [{ 'name': 'Tom', 'age': 20 }, { 'name': 'Jerry', 'age': 31 }] }, Company) print(company1) 输出结果:
'Apple' has 2 employees: Tom and Jerry Python 的常见的序列化需求,包括 json 和 yaml 数据格式,它们都有相对完善的处理库。但同样是不强调类型的缘故,它们处理的对象都是原始的 dict-list 格式。正好可以借助这个工具实现进一步转化。
示例
import json import sys from typing import List from objtyping import objtyping class X: x: int y: str class A: q: str a: str b: int c: List[X] if __name__ == '__main__': print("\r\n-----json-------") json_obj = json.loads('{"q":9, "a":"Mark", "b":3, "c":[{"x":15, "y":"male"},{"x":9, "y":"female", "z":13}]}') typed_obj = objtyping.from_dict_list(json_obj, A) d_l_obj = objtyping.to_dict_list(typed_obj) print(json.dumps(d_l_obj)) sys.exit() 输出结果
-----json------- {"q": "9", "a": "Mark", "b": 3, "c": [{"x": 15, "y": "male"}, {"x": 9, "y": "female", "z": 13}]} 这里需要注意的是:本来属性"q",在最初的 json 结构中,是个数字,但由于类变量定义中是字符串,转换成业务对象以后,它的类型就是字符串了objtyping 工具,会试图按照类定义,在基础类型之间强制转换。
示例
import sys from ruamel.yaml import YAML from typing import List from objtyping import objtyping class X: x: int y: str class A: q: str a: str b: int c: List[X] if __name__ == '__main__': print("\r\n-----yaml-------") yaml = YAML() yaml_obj = yaml.load(''' q: 9 a: Mark b: 3 c: - x: 15 y: male - x: 9 y: female z: 13 ''') typed_obj = objtyping.from_dict_list(yaml_obj, A) d_l_obj = objtyping.to_dict_list(typed_obj) yaml.dump(d_l_obj, sys.stdout) sys.exit() 输出结果
-----yaml------- q: '9' a: Mark b: 3 c: - x: 15 y: male - x: 9 y: female z: 13 这里的属性"q"同样被强转了类型。
项目地址:github
1 hsfzxjy 2021-07-29 22:12:05 +08:00 via Android 不要用 eval,用 ast.literal_eval,不然可以注入任意代码 |
2 hsfzxjy 2021-07-29 22:17:46 +08:00 via Android 另,可以了解一下 dataclasses 模块 |
3 weyou 2021-07-29 22:19:21 +08:00 via Android 支持,感觉业务类的定义比较多余。 之前也写过一个类似的,不仅支持序列化反序列化,还支持几乎 dict/list/tuple 类的所有方法,就是一个 list/dict/tuple 的结合体。且支持对象的修改,比如 append 一个 dict 对象到原来的 list 依然可以使用对象属性的方式来访问嵌套的结构。 |
4 lishunan246 2021-07-29 22:22:44 +08:00 via Android 可以了解一下 dacite |
5 hs0000t 2021-07-29 22:23:18 +08:00 via Android 赞,以前造过类似的轮子,这个看起来更方便一些 |
6 ihawk OP @hsfzxjy 谢谢,我去看看 literal_eval,不过我在 eval 的时候,已经清空了所有环境,只保留最基本的表达式解析,还是比较安全的。 |
7 xiri 2021-07-30 00:22:10 +08:00 虽然对这个帖子的内容来说不是什么大问题,但还是得提醒一句“Python 不是强类型语言”这句话错了。 强弱类型针对的是语言是否倾向于对变量类型做隐式转换,Python 是实打实的强类型动态语言。 |
8 ihawk OP @lishunan246 哎,看了一下,dacite 好像还真是做这个的,而且做得挺完整,看来又重新造轮子了。我再仔细研究研究。 |
| td width="auto" valign="top" align="left"> |
11 no1xsyzy 2021-07-30 09:31:58 +08:00 @xiri Python 也不完全是强类型语言,魔法特性能够破坏类型保证,应当算是外强中干类型语言( @ihawk eval 清空环境还不够。你可以从 `()` (空 tuple )里构建出任意字节码并封装成函数执行。 (via <https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html>) s = "([c for c in ().__class__.__base__.__subclasses__() if c.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('ls'))" |
12 ihawk OP @weyou 理解你的意思了,目标不太一样,你的项目是想尽量方便地把 dict 转换成对象,实现得还挺强大的。不过我这里是假设已经有业务对象了,就是想按指定的类型反序列化。 |
13 ihawk OP @xiri 嗯,我是这样理解“强类型”的:就是一个变量或属性,声明的时候是什么类型,赋值的时候,就必须是这个类型。从这个意思上,Python 应该不属于强类型。Python 的数据类型设计思路不也称为“duck typing”么:只要看起来像是这个类型,就可以用了,至于它本来如何声明的,不重要。 所以我这个小工具(包括 dacite ),可能不太 pythonic,不过语言都在互相借鉴,PEP 这两年也一直在加强类型声明。 |
15 ihawk OP @AX5N 不是吧,哪有这么武断的,从强类型到弱类型,是一个渐变的过程。从维基百科 [https://zh.wikipedia.org/wiki/%E5%BC%B7%E5%BC%B1%E5%9E%8B%E5%88%A5] 列出的一系列“强类型”要素来看,以下几条 Python 肯定是不符合的: * 类型是与变量相连系的名称(通常是在声明时),而不是值(通常是在初始化时) * 拒绝(在要么编译时间要么运行时间)尝试忽视资料类型的操作或函数调用 * 禁止类型转换。某个类型的值,不论是不是以显式或隐式的方式,都不可转换为另一个类型。 显然它的类型系统不是那么“强”。 当然, 这不是本帖的重点,而且 Benjamin 也说了:“这些术语的用法不尽相同,所以也就近乎无用”。 |
16 cyrivlclth 2021-08-04 15:29:35 +08:00 可以用 dataclass 吧? |