ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python 特殊方法实战:为自定义对象定制打印、比较与上下文管理(python-mastery Exercise 3.6)

Python 特殊方法实战:为自定义对象定制打印、比较与上下文管理(python-mastery Exercise 3.6) 示例工程教程【免费下载链接】python-masteryAdvanced Python Mastery (course by dabeaz)项目地址https://gitcode.com/gh_mirrors/py/python-mastery点击查看免费下载本指南围绕 Advanced Python Mastery 课程python-mastery 仓库Exercise 3.6 展开讲解如何通过重定义特殊方法special methods定制用户自定义对象的行为用__repr__()获得更易读的代码级表示、用__eq__()让对象可以按值比较、以及手写一个基于sys.stdout补丁的上下文管理器来重定向输出。读完本文你将掌握这三类特殊方法的底层机制、实现细节并能直接照搬到自己的类设计中。一、理解str()与repr()对象的两种字符串表示所有 Python 对象都有两种字符串表示字符串转换表示由str()产生print内部会调用它通常是面向人类的、格式友好的版本代码表示由repr()产生交互式 shell 中直接查看一个值时会调用它通常展示为了得到这个对象你需要敲入的代码。原文档用datetime.date给出了一个经典对比 from datetime import date d date(2008, 7, 5) print(d) # uses str() 2008-07-05 d # uses repr() datetime.date(2008, 7, 5) 在没有显式定义__repr__的普通自定义类上默认的repr()会输出类似__main__.Stock object at 0x7f...这样的对象内存地址这对调试和日志几乎没有帮助——这正是本练习要解决的问题。在输出中获取repr()字符串的三种技术原文档给出了在print输出中强制使用repr()表示的三种写法它们等价 print(The date is, repr(d)) The date is datetime.date(2008, 7, 5) print(fThe date is {d!r}) The date is datetime.date(2008, 7, 5) print(The date is %r % d) The date is datetime.date(2008, 7, 5) 其中!r转换标志format conversion flag是日常最常用的——它告诉格式化系统调用repr()而不是str()。二、定制__repr__()让 Stock 对象的输出真正有用原文档要求修改此前练习中创建的Stock类其演进脉络见 Exercise 3.4 中的私有属性、property、__slots__与_types类型检查使__repr__()产生更有用的输出 goog Stock(GOOG, 100, 490.10) goog Stock(GOOG, 100, 490.1) 官方解答中的实现仓库中的解答文件 Solutions/3_6/stock.py 给出了完整实现其精髓在于用!r让每个字段都以可复现的代码形式嵌入class Stock: __slots__ (name,_shares,_price) _types (str, int, float) def __init__(self, name, shares, price): self.name name self.shares shares self.price price def __repr__(self): # Note: The !r format code produces the repr() string return f{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r}) ...实现中有两个关键细节值得注意使用type(self).__name__而非硬编码Stock这样做的价值在于继承——如果定义class DStock(Stock)其repr()会自动输出DStock(...)而非错误的Stock(...)实现了子类友好的代码表示。对每个字段使用!r保证字符串类型的name输出带引号GOOG数值类型按repr()规则输出如490.1从而使整个repr()结果可以直接作为代码粘贴使用。与读取管线联动验证修改完成后原文档要求通过reader.read_csv_as_instances()读取投资组合并查看整个列表 import stock, reader portfolio reader.read_csv_as_instances(Data/portfolio.csv, stock.Stock) portfolio [Stock(AA, 100, 32.2), Stock(IBM, 50, 91.1), Stock(CAT, 150, 83.44), Stock(MSFT, 200, 51.23), Stock(GE, 95, 40.37), Stock(MSFT, 50, 65.1), Stock(IBM, 100, 70.44)] 这份输出之所以整洁是因为打印一个列表时Python 会对列表中的每个元素调用repr()。其数据来自 Data/portfolio.csv7 条股票记录列名为name,shares,price读取逻辑由 Solutions/3_6/reader.py 中的read_csv_as_instances()提供def read_csv_as_instances(filename, cls): Read a CSV file into a list of instances records [] with open(filename) as f: rows csv.reader(f) headers next(rows) for row in rows: records.append(cls.from_row(row)) return records它依赖Stock.from_row()这个类方法后者使用cls._types (str, int, float)逐列做类型转换最终通过cls(*values)构造实例classmethod def from_row(cls, row): values [func(val) for func, val in zip(cls._types, row)] return cls(*values)可以看到__repr__与from_row是一对互逆操作repr()生成的字符串Stock(AA, 100, 32.2)几乎就是构造这个对象所需敲入的代码这也是repr()设计哲学code representation的体现。三、让对象可比较定义__eq__()问题现象相同内容的对象却不相等原文档先演示了默认行为下的坑 a Stock(GOOG, 100, 490.1) b Stock(GOOG, 100, 490.1) a b False 原因是在未定义__eq__()时退化为身份比较is即比较两个对象的内存地址。a和b是两个不同的实例地址不同因此比较结果为False——即便它们的name、shares、price完全相同。修复按字段值比较原文档给出的修复是为Stock添加__eq__()class Stock: ... def __eq__(self, other): return isinstance(other, Stock) and ((self.name, self.shares, self.price) (other.name, other.shares, other.price)) ...这段代码有两个设计要点Solutions/3_6/stock.py 中的实现与此一致先做isinstance(other, Stock)类型检查避免与任意对象如字符串、整数比较时抛出AttributeError或产生误导结果。若other不是Stock直接返回False。把三个字段打包成元组一次性比较元组比较按元素从左到右进行等价于self.name other.name and self.shares other.shares and self.price other.price但写法更简洁。补充知识__hash__的一致性值得指出原文档未展开但实践中必须知道一旦定义了__eq__Python 会将类的__hash__设为None使实例变成不可哈希的无法放入set或作为dict的键。如果未来需要把Stock放进集合或作为字典键需要同时显式定义基于相同字段的__hash__例如return hash((self.name, self.shares, self.price))并保证__eq__相等时__hash__也相等。在本练习的场景投资组合的列表打印与格式化中不需要哈希因此__eq__单独定义即可。四、手写上下文管理器临时重定向sys.stdout问题背景在 Exercise 3.5 中我们构建了基于继承的格式化体系抽象基类TableFormatter、TextTableFormatter/CSVTableFormatter/HTMLTableFormatter等具体实现以及工厂函数create_formatter(text|csv|html)。核心输出函数print_table()会把表格打印到标准输出sys.stdout参见 Solutions/3_5/tableformat.py 中的实现class TextTableFormatter(TableFormatter): def headings(self, headers): print( .join(%10s % h for h in headers)) print((-*10 )*len(headers)) def row(self, rowdata): print( .join(%10s % d for d in rowdata))典型用法及输出 from tableformat import create_formatter formatter create_formatter(text) print_table(portfolio, [name,shares,price], formatter) name shares price ---------- ---------- ---------- AA 100 32.2 IBM 50 91.1 CAT 150 83.44 MSFT 200 51.23 GE 95 40.37 MSFT 50 65.1 IBM 100 70.44 问题在于所有表格都写死到sys.stdout。如果要重定向到文件或其他位置大而全的做法是修改所有格式化代码以支持可配置的输出文件而应个急的轻量方案就是用一个上下文管理器在进入时临时替换sys.stdout、退出时还原。上下文管理器实现原文档给出的redirect_stdout定义如下 import sys class redirect_stdout: def __init__(self, out_file): self.out_file out_file def __enter__(self): self.stdout sys.stdout sys.stdout self.out_file return self.out_file def __exit__(self, ty, val, tb): sys.stdout self.stdout其工作原理分三步__init__(self, out_file)保存目标输出对象任何实现了write()的文件对象__enter__(self)把当前sys.stdout暂存到self.stdout再将sys.stdout替换为目标文件并返回该文件因此with ... as file能拿到文件句柄__exit__(self, ty, val, tb)无论with块内是否抛出异常都会恢复sys.stdout。三个参数ty异常类型、val异常实例、tb回溯对象在本实现中未使用但签名必须保留这是上下文管理器协议的硬性要求。使用与验证 from tableformat import create_formatter formatter create_formatter(text) with redirect_stdout(open(out.txt, w)) as file: tableformat.print_table(portfolio, [name,shares,price], formatter) file.close() # Inspect the file print(open(out.txt).read()) name shares price ---------- ---------- ---------- AA 100 32.2 IBM 50 91.1 CAT 150 83.44 MSFT 200 51.23 GE 95 40.37 MSFT 50 65.1 IBM 100 70.44 注意两点使用细节表格内容确实写入了out.txt说明print_table内部的print()经由 formatter 类在with块期间全部落到了文件里file.close()在with块内手动调用因为这里的上下文管理器只负责sys.stdout的替换与还原不负责关闭目标文件与标准库open的上下文语义不同。更简化的选择标准库contextlib.redirect_stdout其实 Python 标准库contextlib已经提供了功能几乎一致的redirect_stdout()用法为from contextlib import redirect_stdout with redirect_stdout(open(out.txt, w)) as f: print_table(portfolio, [name, shares, price], formatter)手写版本的价值在于教学它直观展示了上下文管理器协议__enter__/__exit__的完整形态以及进入时打补丁、退出时还原这一被广泛使用的模式日志重定向、临时修改全局配置、抑制输出等场景皆可套用。标准库的redirect_stdout之所以能工作也正是基于同样原理并且它还额外在进入时flush()原sys.stdout避免缓冲区残留造成输出错位。五、回顾三类特殊方法在本练习中的角色特殊方法触发时机本练习中的用途实现要点__repr__()repr(obj)、交互式 shell 查看值、打印容器时对元素调用让Stock显示为可直接粘贴的构造代码用type(self).__name__保证子类友好字段用!r__eq__(other)a b比较让相同字段值的Stock判等先isinstance类型守卫再比较字段元组__enter__()/__exit__(ty, val, tb)with语句进入/退出临时替换sys.stdout实现输出重定向__exit__必须还原现场签名三参数保留在课程整体脉络中本练习是类与对象高级技巧的重要一站Exercise 3.4 完成了属性封装与类型校验Exercise 3.5 用继承搭建了可扩展的表格格式化框架而本练习则让对象在打印、比较、资源上下文三个维度上都表现出内建类型般的质感——这正是特殊方法存在的意义让用户自定义对象融入 Python 的语言机制之中。后续 Exercise 3.7 还会进一步把TableFormatter升级为真正的抽象基类并用模板方法模式重构 CSV 读取器形成与本练习呼应的完整知识链。六、延伸练习建议为Stock补充__str__()给人类阅读的表示如GOOG (100 shares 490.1)与__repr__()形成互补实验在定义__eq__后把Stock放入set或作为dict键观察TypeError: unhashable type: Stock并补上一致的__hash__用contextmanager装饰器重写redirect_stdout对比装饰器风格与基于类的实现将输出重定向与 CSV 格式化器组合create_formatter(csv)把投资组合表格以 CSV 形式写入文件体会上下文管理器与工厂函数的组合威力。赞分享示例工程教程【免费下载链接】python-masteryAdvanced Python Mastery (course by dabeaz)项目地址https://gitcode.com/gh_mirrors/py/python-mastery点击查看免费下载相关推荐Python Mastery 实战用特殊方法协议从零打造可变整数 MutInt 自定义原始类型Python Mastery 实战用特殊方法协议从零打造可变整数 MutInt 自定义原始类型 本指南源自 Advanced Python Mastery 课示例工程教程lodash对象深比较isEqualWith自定义比较规则lodash对象深比较isEqualWith自定义比较规则 你是否曾遇到JavaScript对象比较的困扰明明内容相同的对象用 却返回 false前端后端Python 包化实战将模块重构为 structly 包Advanced Python Mastery Exercise 9.2Python 包化实战将模块重构为 structly 包Advanced Python Mastery Exercise 9.2 导读 本篇文章对应 A示例工程教程上一篇gsy_github_app_flutter 页面层协作指南多状态管理并存下的边界识别、局部修复与最低验证下一篇DeepSeek Harness 能力接缝重构实战以 dsh-jobs / dsh-jobs-local 拆分后台任务注册表创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表