Advanced Python Mastery 练习 6.1 详解:用 Structure 基类简化数据结构定义
发布时间:2026/10/5 2:10:34 作者:尧图编辑部 阅读量:1,286

示例工程教程【免费下载链接】python-masteryAdvanced Python Mastery (course by dabeaz)项目地址https://gitcode.com/gh_mirrors/py/python-mastery点击查看免费下载本篇文章围绕 python-mastery 课程仓库中 练习 6.1 的官方解决方案 展开讲解如何用一段仅十几行的Structure基类把每个类都要手写冗长__init__()的重复劳动压缩成一行_fields声明。读完本文你将掌握基于*args参数约定自动填充实例、自定义__repr__()生成可复现的构造表达式以及用__setattr__()限制非法属性名的完整实现方案并了解如何用它从零重构Stock类。一、问题背景为什么数据类的__init__()是一种重复劳动在课程 练习 6.1 之前的练习中定义一个表示股票的类需要这样手写每个属性的赋值class Stock: def __init__(self, name, shares, price): self.name name self.shares shares self.price price聚焦在__init__()方法上可以发现每增加一个字段就要多写一行self.xxx xxx。如果一个程序里需要定义几十个这样的数据结构代码量会急剧膨胀且极易出错。练习 6.1 的核心目标就是利用 Python 的函数参数传递约定把这个过程彻底自动化。参数传递约定复习Python 函数支持混合使用位置参数和关键字参数 def foo(x, y, z): return x y z foo(1, 2, 3) 6 foo(1, z3, y2) 6也支持用*和**语法展开序列与字典 args (1, 2, 3) foo(*args) 6 kwargs {y: 2, z: 3} foo(1, **kwargs) 6更重要的是函数可以接收任意数量的位置参数或关键字参数 def foo(*args): print(args) foo(1, 2) (1, 2) foo(1, 2, 3, 4, 5) (1, 2, 3, 4, 5) foo() () def bar(**kwargs): print(kwargs) bar(x1, y2) {y: 2, x: 1} bar(x1, y2, z3) {y: 2, x: 1, z: 3} bar() {}可变参数函数正是练习 6.1 简化数据结构的技术基础让__init__接收任意数量的位置参数再按_fields中声明的字段名逐一绑定。二、核心实现 (a)声明式_fields与自动化的__init__解决方案在structure.py中定义了一个基类Structure完整的三个方法合起来对应仓库 Solutions/6_1/structure.py如下# structure.py class Structure: _fields () def __init__(self, *args): if len(args) ! len(self._fields): raise TypeError(Expected %d arguments % len(self._fields)) for name, val in zip(self._fields, args): setattr(self, name, val) def __setattr__(self, name, value): if name.startswith(_) or name in self._fields: super().__setattr__(name, value) else: raise AttributeError(No attribute %s % name) def __repr__(self): return %s(%s) % (type(self).__name__, , .join(repr(getattr(self, name)) for name in self._fields))_fields声明式编程子类不再需要编写任何__init__只需声明字段名元组class Stock(Structure): _fields (name, shares, price) class Date(Structure): _fields (year, month, day)__init__的工作原理def __init__(self, *args): if len(args) ! len(self._fields): raise TypeError(Expected %d arguments % len(self._fields)) for name, val in zip(self._fields, args): setattr(self, name, val)三步完成全部初始化逻辑数量校验*args接收所有位置参数与_fields的长度比对不相等就抛出带准确期望数量的TypeError逐对绑定zip(self._fields, args)把字段名与实参一一配对动态赋值setattr(self, name, val)按名称为实例创建属性。用课程文档中的样例验证 s Stock(GOOG, 100, 490.1) s.name GOOG s.shares 100 s.price 490.1 s Stock(AA, 50) Traceback (most recent call last): ... TypeError: Expected 3 arguments这里有个重要的细节len(args) ! len(self._fields)中self._fields的动态查找意味着即使子类没有显式定义_fields也会继承基类默认的空元组()从而保证Stock()这种零参数调用也能得到一致的错误行为。三、实现 (b)让repr()输出可直接复现的构造表达式默认的repr()会输出__main__.Stock object at 0x...既不可读也无法还原。解决方案为Structure添加__repr__class Structure: ... def __repr__(self): return %s(%s) % (type(self).__name__, , .join(repr(getattr(self, name)) for name in self._fields))关键点拆解type(self).__name__取实际子类的类名Stock而非Structure保证多态环境下显示正确生成器表达式repr(getattr(self, name)) for name in self._fields按声明顺序取出每个字段的repr(), .join(...)拼接参数列表最终产出与构造调用形式一致的字符串。运行效果 s Stock(GOOG, 100, 490.1) s Stock(GOOG,100,490.1)这种可复现表示reproducible representation是repr的良好实践eval(repr(s))应能重建等价对象对调试和日志输出非常有用。四、实现 (c)用__setattr__限制属性名为了让数据结构保持封闭解决方案重写了__setattr__class Structure: ... def __setattr__(self, name, value): if name.startswith(_) or name in self._fields: super().__setattr__(name, value) else: raise AttributeError(No attribute %s % name)两条放行规则其余一律拒绝字段白名单name in self._fields—— 只允许赋值声明过的字段私有属性豁免name.startswith(_)—— 允许任何下划线开头的私有属性如_shares、_internal这类属性常用于内部缓存或派生状态。验证行为 s Stock(GOOG, 100, 490.1) s.shares 50 # 合法在 _fields 中 s.share 50 # 非法拼写错误不在 _fields 中 Traceback (most recent call last): File stdin, line 1, in module File structure.py, line 13, in __setattr__ raise AttributeError(No attribute %s % name) AttributeError: No attribute share s._shares 100 # 私有属性允许这个机制能立即捕获拼写错误如把shares写成share是后续练习引入数据校验validate.py的前置设施。注意super().__setattr__必须显式调用否则会无限递归。五、实现 (d)从零重建Stock类并跑通单元测试在完成前三步后新建stock.py或清空旧代码从零开始# stock.py from structure import Structure class Stock(Structure): _fields (name, shares, price) property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares - nshares新的Stock类只声明字段并补充业务方法cost属性、sell方法完全不再出现__init__。该文件与课程仓库中的 Solutions/6_1/stock.py 实现一致。用单元测试验证重构结果课程要求此时运行teststock.py单元测试源自 练习 5.4 的测试集仓库中的版本见 Solutions/6_1/teststock.py。测试覆盖了test_create/test_create_keyword位置参数与关键字参数创建test_costcost属性计算100 * 490.1 49010.0test_sell卖出操作后股数递减test_reprrepr(s) Stock(GOOG, 100, 490.1)test_bad_attributes.share 100必须抛AttributeError。运行方式cd Solutions/6_1 python3 teststock.py正如练习文档所指出的此时运行测试会有大量失败但至少有一批会通过——因为本阶段的Structure还没有实现关键字参数支持和字段类型校验例如s.shares 50应抛TypeError、s.shares -50应抛ValueError等断言仍无法满足。这些失败恰恰是后续练习的路线图练习 6.2 将引入_init()栈帧技巧解决关键字参数问题后续练习再逐步加入验证器。六、本方案的局限与后续演进在 练习 6.2 中课程明确指出当前实现的三个痛点不支持关键字参数Stock(nameGOOG, shares100, price490.1)会抛出TypeError: __init__() got an unexpected keyword argument pricehelp(Stock)签名无用__init__只有(self, *args)无法显示name, shares, price的友好签名需要额外机制__repr__()与__setattr__()都依赖_fields类之间仍需显式协作。练习 6.2 的解决思路是栈帧黑客利用sys._getframe(1).f_locals读取调用者局部变量把_init做成staticmethodself从局部变量中取出无需作为参数传入从而使Stock(nameGOOG, price490.1, shares50)与help(Stock)均恢复正常。这一演进在仓库 Solutions/6_2/structure.py 中有完整实现可对照阅读。七、小结练习 6.1 展示了 Python 元编程思维的一个经典切片以_fields为声明式接口把初始化、表示、属性管控三件事统一收敛到基类中让子类代码量降到最低。本文所述三段实现__init__自动填充、__repr__可复现输出、__setattr__白名单校验完整对应 soln6_1.md 的官方解答并已用仓库中的 structure.py、stock.py 与 teststock.py 逐行印证。以此为起点读者可以继续阅读 练习 6.2 及其后续练习见证这套Structure框架逐步演进为完整的验证与类型系统。赞分享示例工程教程【免费下载链接】python-masteryAdvanced Python Mastery (course by dabeaz)项目地址https://gitcode.com/gh_mirrors/py/python-mastery点击查看免费下载相关推荐Advanced Python Mastery数据分析NumPy与SciPy基础应用指南Advanced Python Mastery数据分析NumPy与SciPy基础应用指南 想要在Python数据分析领域更上一层楼吗Advanced Pyt示例工程教程大麦抢票脚本怎么跑通从环境配置到参数调优的Python自动化指南大麦抢票脚本怎么跑通从环境配置到参数调优的Python自动化指南 这是一个基于Python的大麦抢票脚本网页端用Selenium驱动Chrome移动端用A示例工程教程Taichi Dataclass 完全指南用 Python 类定义结构化 GPU 数据类型Taichi Dataclass 完全指南用 Python 类定义结构化 GPU 数据类型 Taichi 的 ti.dataclass 装饰器允许开发者用带编程语言编译器高性能计算上一篇CNKI-download3步完成知网文献批量下载的终极自动化工具下一篇SD-PPP终极指南5分钟在Photoshop中实现AI绘图革命创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考