《流畅的Python》读书笔记——符合Python风格的对象
对象表示形式
所谓对象表示形式,就是将对象转换为一种可读的形式。
Python提供了两种方式:
repr()
:以便于开发者理解的方式返回对象的字符串表示形式str()
:以便于用户理解的方式返回对象的字符串表示形式
我们只要实现 __repr__
和__str__
特殊方法,就可以为repr()
和str()
提供支持。
为了给对象提供其他的表示形式,还会用到另外两个特殊方
法:__bytes__
和 __format__
。__bytes__
方法与 __str__
方
法类似:bytes()
函数调用它获取对象的字节序列表示形式。而
__format__
方法会被内置的format()
函数和 str.format()
方
法调用。
向量类
我们使用Vector2d
类来说明对象表示形式的众多用法。
#vector2d_v0.py
from array import array
import mathclass Vector2d:typecode = 'd' #类属性,在实例和字节序之间转换时使用def __init__(self,x,y):self.x = float(x) #把x和y转换成浮点数,尽早捕获错误,防止传入不当参数self.y = float(y)def __iter__(self):return (i for i in (self.x, self.y)) #把Vector2d实例变成可迭代的对象,这样才能拆包def __repr__(self):class_name = type(self).__name__return '{}({!r}, {!r})'.format(class_name, *self) #因为 Vector2d 实例是可迭代的对象,所以 *self 会#把 x 和 y 分量提供给 format 函数。def __str__(self):return str(tuple(self))def __bytes__(self):return (bytes([ord(self.typecode)]) + #把 typecode 转换成字节序列bytes(array(self.typecode, self))) #迭代 Vector2d 实例,得到一个数组,再把数组转换成字节序列。def __eq__(self, other):return tuple(self) == tuple(other)def __abs__(self):return math.hypot(self.x,self.y) #模是 x 和 y 分量构成的直角三角形的斜边长def __bool__(self):return bool(abs(self))
在控制台调用如下:
>>> from vector2d_v0 import Vector2d
>>> v1 = Vector2d(3,4)
>>> print(v1.x,v1.y)
3.0 4.0
>>> x,y = v1 #Vecctor2d实例可以拆包成变量元组
>>> x,y
(3.0, 4.0)
>>> v1
Vector2d(3.0, 4.0)
>>> repr(v1)
'Vector2d(3.0, 4.0)'
>>> str(v1)
'(3.0, 4.0)'
>>> v1_clone = eval(repr(v1))
>>> v1 == v1_clone #支持==比较
True
>>> print(v1)
(3.0, 4.0)
>>> octets = bytes(v1)
>>> octets
b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@'
>>> abs(v1)
5.0
>>> bool(v1),bool(Vector2d(0,0))
(True, False)
备选构造方法
我们可以把 Vector2d
实例转换成字节序列了;同理,也应该能从字
节序列转换成 Vector2d
实例。
主要在上面的类中增加如下类方法:
@classmethod #类方法使用classmethod装饰器修饰
def frombytes(cls,octets): #通过cls传入类本身typecode = chr(octets[0]) #从第一个字节中读取typecodememv = memoryview(octets[1:]).cast(typecode) #通过传入的字节序列创建一个memoryview,然后使用typecode转换return cls(*memv) #拆包转换后的memoryview,得到构造方法所需的一对参数
classmethod与staticmethod
classmethod
定义操作类的方法,该方法的第一个参数是类本身。其最常见的用途是备选构造方法。
staticmethod
装饰器也会改变方法的调用方式,但是第一个参数不是特殊的值。其实,静态方法就是普通的函数。
In [1]: class Demo: ...: @classmethod ...: def klassmeth(*args): ...: return args ...: @staticmethod ...: def statmeth(*args): ...: return args ...: In [2]: Demo.klassmeth()#不管怎么调用该方法,第一个参数始终是Demo
Out[2]: (__main__.Demo,)
In [3]: Demo.klassmeth('spam')
Out[3]: (__main__.Demo, 'spam')
In [4]: Demo.statmeth('spam') #它的行为与普通函数类似
Out[4]: ('spam',)
格式化显示
内置的 format()
函数和str.format()
方法把各个类型的格式化方
式委托给相应的 .__format__(format_spec)
方
法。format_spec
是格式说明符,它是:
format(my_obj, format_spec)
的第二个参数,或者str.format()
方法的格式字符串,{}
里代换字段中冒号后面的部分
In [5]: brl = 1/2.43
In [6]: brl
Out[6]: 0.4115226337448559
In [7]: format(brl,'0.4f')
Out[7]: '0.4115'
In [8]: '1 BRL = {rate:0.2f} USD'.format(rate=brl) #rate被brl替换
Out[8]: '1 BRL = 0.41 USD'
{rate:0.2f}'
这样的格式字符串其实包含两部分,
冒号左边的 rate
在代换字段句法中是字段名,冒号后面的 0.2f
是格式说明符。格式说明符使用的表示法叫格式规范微语言。
格式规范微语言是可扩展的,因为各个类可以自行决定如何解释
format_spec
参数。例如,datetime
模块中的类,它们的
__format__
方法使用的格式代码与strftime()
函数一样。下面是
内置的format()
函数和str.format()
方法的几个示例:
>>> from datetime import datetime
>>> now = datetime.now()
>>> format(now,'%H:%M:%S')
'16:20:38'
>>> "It's now {:%I:%M %p}".format(now)
"It's now 04:20 PM"
如果类没有定义 __format__
方法,从 object 继承的方法会返回
str(my_object)
。我们为Vector2d
类定义了 __str__
方法,因
此可以这样做:
>>> v1 = Vector2d(3,4)
>>> format(v1)
'(3.0, 4.0)'
接下来将实现自己的微语言。假设用户提供的格式说明符是用于格式化向量中各个浮点数分量的。我们想达到这样的效果:
>>> v1 = Vector2d(3, 4)
>>> format(v1)
'(3.0, 4.0)'
>>> format(v1, '.2f')
'(3.00, 4.00)'
>>> format(v1, '.3e')
'(3.000e+00, 4.000e+00)'
增加的方法如下:
def __format__(self, format_spec = ''):components = (format(c,format_spec) for c in self) #使用内置的 format 函数把 fmt_spec 应用到向量的各个分量上return '({}, {})'.format(*components)
可散列的Vector2d
为了让Vector2d
实例变成可散列的,必须实现__hash__
方法(还有__eq__
方法),还需让向量不可变。
#修改了构造方法
def __init__(self,x,y):self.__x = float(x) #使用两个下划线把属性变为私有的self.__y = float(y)@property #把读值方法标记为特性
def x(self):return self.__x@property
def y(self):return self.__ydef __hash__(self):return hash(self.x) ^ hash(self.y)
下面对改造后的向量进行测试:
>>> from vector2d_v2 import Vector2d
>>> v1 = Vector2d(3,4)
>>> v2 = Vector2d(3.1,4.2)
>>> hash(v1),hash(v2)
(7, 384307168202284039)
>>> set([v1,v2])
{Vector2d(3.1, 4.2), Vector2d(3.0, 4.0)}
Python的私有属性和受保护的属性
Python 不能像 Java 那样使用 private
修饰符创建私有属性,但是
Python 有个简单的机制,能避免子类意外覆盖“私有”属性。
我们上面在属性x
名称前加了两个下划线变成了__x
,对于该类来说,__x
会变成_Vector2d__x
,这个语言特性叫名称改写。Python 会把这种属性名存入实例的__dict__
属性中,而且会在前面加上一个下划线和类名:
>>> v1.__dict__
{'_Vector2d__x': 3.0, '_Vector2d__y': 4.0}
>>> v1._Vector2d__x #如果知道了如何改写名称,还是可以访问到
3.0
但是有些人不喜欢这种句法,他们约定使用
一个下划线前缀编写“受保护”的属性(如self._x
)
Python 解释器不会对使用单个下划线的属性名做特殊处理,不过这是很
多 Python 程序员严格遵守的约定,他们不会在类外部访问这种属性。
使用__slots__
类属性节省空间
默认情况下,Python 在各个实例中名为 __dict__
的字典里存储实例属
性。为了使用底层的散列表提升访问速度,字典会消
耗大量内存。如果要处理数百万个属性不多的实例,通过 __slots__
类属性,能节省大量内存,方法是让解释器在元组中存储实例属性,而
不用字典。
定义 __slots__
的方式是,创建一个类属性,使用 __slots__
这个
名字,并把它的值设为一个字符串构成的可迭代对象,其中各个元素表
示各个实例属性。我喜欢使用元组,因为这样定义的 __slots__
中所
含的信息不会变化,如示例所示:
class Vector2d:__slots__ = ('__x','__y')
在类中定义 __slots__
属性的目的是告诉解释器:“这个类中的所有
实例属性都在这儿了!”
覆盖类属性
Python 有个很独特的特性:类属性可用于为实例属性提供默认
值。Vector2d
中有个 typecode
类属性,__bytes__
方法两次用到
了它,而且都故意使用 self.typecode
读取它的值。因为
Vector2d
实例本身没有typecode
属性,所以 self.typecode
默
认获取的是 Vector2d.typecode
类属性的值。
但是,如果为不存在的实例属性赋值,会新建实例属性。假如我们为
typecode
实例属性赋值,那么同名类属性不受影响。然而,自此之
后,实例读取的self.typecode
是实例属性typecode
,也就是把
同名类属性遮盖了。借助这一特性,可以为各个实例的typecode
属
性定制不同的值。
Vector2d.typecode
属性的默认值是 ‘d’,即转换成字节序列时使
用 8 字节双精度浮点数表示向量的各个分量。如果在转换之前把
Vector2d
实例的typecode
属性设为 ‘f’,那么使用 4 字节单精度
浮点数表示各个分量:
>>> v1 = Vector2d(1.1,2.2)
>>> dumpd = bytes(v1)
>>> dumpd
b'd\x9a\x99\x99\x99\x99\x99\xf1?\x9a\x99\x99\x99\x99\x99\x01@'
>>> len(dumpd)
17
>>> v1.typecode = 'f' #修改v1实例的typecode属性
>>> dumpf = bytes(v1)
>>> dumpf
b'f\xcd\xcc\x8c?\xcd\xcc\x0c@'
>>> len(dumpf)
9
>>> Vector2d.typecode # 而类属性不变
'd'
《流畅的Python》读书笔记——符合Python风格的对象相关推荐
- 与孩子一起学编程python_与孩子一起学编程(Python读书笔记3)
第十一章 嵌套与可变循环 Python 3.X里 print()函数默认是自动换行的,所以本章代码会有很多问题,实际上 print()函数里有一个默认参数 end, 默认情况下: end= " ...
- 读书笔记——《Python编程从入门到实践》第二章
读书笔记--<Python编程从入门到实践>第二章 读书笔记--<Python编程从入门到实践>第二章 变量 如何使用变量 如何规范变量命名 字符串 字符串是什么 如何修改字符 ...
- Python读书笔记-每日篇-20190222|激活码生成器(redis存储)
问题描述: 做为 Apple Store App 独立开发者,你要搞限时促销,为你的应用生成激活码(或者优惠券),使用 Python 如何生成 200 个激活码(或者优惠券),并将生成的激活码保存到R ...
- Python读书笔记-每日篇-20190221|激活码生成器(mysql存储)
问题描述: 做为 Apple Store App 独立开发者,你要搞限时促销,为你的应用生成激活码(或者优惠券),使用 Python 如何生成 200 个激活码(或者优惠券),并将生成的激活码保存到M ...
- 《Essential C++》读书笔记 之 泛型编程风格
<Essential C++>读书笔记 之 泛型编程风格 2014-07-07 3.1 指针的算术运算(The Arithmetic of Pointer) 新需求1 新需求2 新需求3 ...
- Python基础 笔记(一) Python的介绍
Python基础 笔记(一) Python的介绍 您好! 欢迎来到木易巷! 接下来,让我们一起来了解Python,走进Python~ 1.编程语言 编程语言(programming language) ...
- 【python MySQL 笔记】python和MySQL交互、操作
[python MySQL 笔记]python和MySQL交互.操作 目录 1. 数据准备 2. SQL演练 2.1 SQL语句强化练习 2.2. 将一个表拆为多个表 3. python操作MySQ ...
- boxplot用法 python,[Python画图笔记]利用Python画箱型图boxplot
[Python画图笔记]利用Python画箱型图boxplot [Python画图笔记]利用Python画箱型图boxplot 最近在学习使用Python画图,想用subplot画两幅箱型图,分别用来 ...
- 【读书笔记】Python编程:从入门到实践-埃里克·马瑟斯,python基础体系巩固和常见场景练习
[概述] 书名:Python编程:从入门到实践 作者:埃里克·马瑟斯 日期:2021年09月01日 读书用时:1632页,100小时,27个笔记 [读书笔记] ◆ 第4章 操作列表 >> ...
- 【读书笔记】Python网络爬虫从入门到实践(第2版)-唐松,爬虫基础体系巩固和常见场景练习
[概述] 书名:Python网络爬虫从入门到实践(第2版) 作者:唐松 日期:2021年08月01日 读书用时:1568页,100小时,59个笔记 [读书笔记] ◆ 1.2 网络爬虫是否合法 爬虫协议 ...
最新文章
- LeetCode实战:Nim 游戏
- 开源软件:信息共赢和开放心态
- STM32外部中断与各通道对应关系
- NUP2105L CAN BUS总线端口静电保护器件
- linux下安装oracle sqlplus以及imp、exp工具
- MongoDB Replication
- 【C++探索之旅】第一部分第四课:内存,变量和引用
- 收藏 | 详解目标检测(MMdetection)-Runner
- c#类中字段和方法中变量的声明问题
- ORACLE TRUNC()函数
- android pickerview 多行,Android PickerView实现三级联动效果
- docker查看java版本_Linux 安装jdk,查看版本,docker
- 技嘉主板更新版BIOS
- 码云 注册 注册个性域名报错---已经解决
- tomcat 虚拟目录配置appBase和docBase的区别
- nc文件处理学习资料
- 苹果CMS绑定分类失败,刷新就丢失!
- rtsp直播流转m3u8
- 边缘设备、系统及计算杂谈(1)
- 积累20180604
热门文章
- 【leetcode】复写零
- Cesium.js学习第二天(立方体)
- SpringBoot参数传递bean自动填充
- Myeclipse中web project各种常见错误及解决方法(持续更新)
- 将JQuery框架集成到SharePoint 2010中
- Microsoft Office Mobile 2010 Beta 于 4 月 5 日过期
- new Image().src资源重复请求问题
- 基本数据类型与格式化输出
- ASP.NET没有魔法——ASP.NET MVC使用Area开发一个管理模块
- 由脚本创建的新元素事件不触发和用的easyUI插件中的多选框不起作用的解决方法...