python风格对象
对象表示形式
python提供了两种获取对象字符串表示形式的标准方式
repr() //便于开发者理解的方式返回对象的字符串表示形式(一般来说满足obj==eval(repr(obj)))
str() //便于用户理解的方式返回对象的字符串表示形式
要使对象能这两种内置函数的参数,需要实现__repr__和__str__特殊方法,为repr()和str()提供支持。为了给对象提供其他表示形式,还会用到__bytes__和__format__
bytes() //获取对象字节序列表示形式
format() //特殊格式显示对象字符串表示
构建一个向量类:
from array import array
import math class Vector:
typecode = 'd' def __init__(self, x, y):
self.x = float(x)
self.y = float(y) def __iter__(self): #将类实例变为可迭代对象
return (i for i in (self.x, self.y)) def __repr__(self): #构成供开发者使用的字符串
class_name = type(self).__name__
return '{}({!r}, {!r})'.format(class_name, *self) def __str__(self): #构成供用户使用的字符串
return str(tuple(self)) def __bytes__(self): #将对象实例转为字节序列
return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self))) def __eq__(self, other): #实现 ==
return tuple(self) == tuple(other) def __abs__(self): #计算模长
return math.hypot(self.x, self.y) def __bool__(self): #零向量
return bool(abs(self))
使用:
if __name__ == '__main__':
V = Vector(3, 4)
print(V.x, V.y)
x, y = V #是可迭代对象,故可以元组拆包
print((x, y))
repr_V = repr(V) #字符串表示
print(repr_V)
print(eval(repr_V) == V) #执行这个字符串,打印结果
octets = bytes(V)
print(octets)
print(abs(V)) #打印模长
print((bool(V), bool(Vector(0, 0)))) #零向量bool返回False 3.0 4.0
(3.0, 4.0)
Vector(3.0, 4.0)
True
b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@'
5.0
(True, False)
classmethod与staticmethod
在上例中,可以使用bytes()将对象实例转化为字节序列:
def __bytes__(self): #将对象实例转为字节序列
return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self)))
现使用classmethod装饰器来实现字节序列转对象实例的方法:
@classmethod
def frombytes(cls, octets):
typecode = (chr(octets[0]))
memv = memoryview(octets[1:]).cast(typecode) #使用传入的字节序列创建视图,使用typecode转换
return cls(*memv)
使用:
vec = Vector.frombytes(octets) #调用者是vector类
print(vec) (3.0, 4.0)
classmethod是类的方法,而不是实例的方法,它改变了调用方法的方式,因此类方法的第一个参数是类本身而不是实例。(类似c++静态方法)
而staticmethod装饰器也改变方法调用方式,但第一个参数不是特殊的值。其实,静态方法就是普通的函数。
class Demo:
@classmethod
def klassmeth(*args):
return args #返回全位置参数 @staticmethod
def statmeth(*args):
return args #返回全位置参数 if __name__ == '__main__':
print(Demo.klassmeth())
print(Demo.klassmeth('spam'))
print(Demo.statmeth())
print(Demo.statmeth('spam')) #结果
(<class '__main__.Demo'>,)
(<class '__main__.Demo'>, 'spam') #无论如何调用,第一个参数始终是Demo类
()
('spam',) #行为类似于普通函数
格式化显示
内置format()函数和str.format()方法把各个类型的格式化方式委托给相应的.__format__(format_spec)方法。format_spec是格式说明符,它是以下之一:
1.format(my_obj,format_spec)的第二个参数
2.str.format()方法的格式字符串,{}里代换字段中冒号的部分
使用示例:
brl = 1/2.43
print(brl)
form1 = format(brl, '0.4f') #使用前者,格式说明符是0.4f
form2 = '1 BRL = {rate:0.2f} USE'.format(rate=brl) #使用后者,代替冒号部分,格式说明符是0.2f
print(form1)
print(form2) 0.4115226337448559
0.4115
1 BRL = 0.41 USE
格式规范语言为一些内置类型提供了专用的表示代码,比如b表示二进制int类型,x表示十六进制int类型,f表示小数形式的float类型,%表示百分数形式。
print(format(42, 'b'))
print(format(42, 'x'))
print(format(2/3, '0.1%'))
print(format(2/3, '0.3f')) 101010
2a
66.7%
0.667
用户可自行定义__format__方法,如果没有,会调用__str__方法返回值。而未定义__format__方法又传入格式说明符作为参数,将抛出TypeError。
实现可散列的对象
要把类实例变为可散列的对象,必须实现__hash__方法和__eq__方法,而__hash__方法需要保证类对象散列值不变。例如Vector类,则需要让x,y是只可读类型。
class Vector:
typecode = 'd' def __init__(self, x, y):
self.__x = float(x) #使用双下划线把属性标记为私有
self.__y = float(y) @property
def x(self):
return self.__x #使用property装饰器将读值方法标记为特性,即可以使用obj.x获取x @property
def y(self):
return self.__y #同x
之后添加__hash__方法就可以将向量变为可散列的:
def __hash__(self):
return hash(self.x) ^ hash(self.y)
实际上只要能够正确实现__hasn__和__eq__方法并且保证实例散列值不会变化即可。
私有属性和保护属性
python不能用private修饰符创建私有属性,但python有个简单机制避免子类覆盖私有属性。
例如,有人编写了Dog类,用到了mood实例属性却未开放,这时你创建了Dog的子类Beagle,如果你在毫不知情的情况下创建了mood实例属性,那么继承的方法就会覆盖掉Dog中的mood属性。出现了问题却难以发现。
为避免这种情况,如果以__mood命名(前面加双下划线,尾部最多有一个下划线)命名实例属性,那么python会把属性名存入__dict__属性中,而且会在前面加一个下划线和类名。对于上例来说父类会变为_Dog__mood而子类会变为_Beagle__mood。这个语言特性叫做名称改写。
对于向量类的实例:
print(V.__dict__)
{'_Vector__x': 3.0, '_Vector__y': 4.0}
它的目的是避免意外访问,却不能防止故意访问(做坏事),任何人都能直接读取私有属性并且给它赋值。
V._Vector__x = 5.0
print(V) #结果
(5.0, 4.0)
也就是说,它是"私有"却不是真正的私有,不可变也不是真正的不可变。
同时,有人将单划线(_xxx)称为保护属性。
__solts__
默认情况下,python在各个实例中名为__dict__的字典里存储实例属性。但由于字典底层使用散列表实现,速度快也耗费大量内存。若处理上百万个属性不多的实例,可通过__slots__属性可节省大量内存,它将使用元组而不是字典来存储实例属性。
子类不能继承父类的__slots__属性,只会使用自己类中定义的。
方式:创建一个类属性__slots__,将它的值设置为一个字符串构成的可迭代对象,其中各个元素表示各个实例属性。一般使用元组:
class Vector:
__slots__ = ('__x', '__y')
这个属性定义是为了告诉解释器:这个类中所以的实例属性都保存在这。这样,python会在各个实例中使用类似元组的结构存储实例变量。
实例只能拥有__slots__列出的属性,除非把__dict__属性加入其中(但这样做 失去了节省内存的功效)
如果定义了类的__slots__属性,此时想把实例作为弱引用的目标,需要把__weakref__添加到__slots__属性中。
覆盖类属性
类属性可以为实例属性提供默认值。Vector类中使用self.typecode读取类属性的值,实例本身没有类属性,self.typecode获取的是类属性Vector.typecode的值。但是如果为不存在的实例属性赋值,就会新建实例属性。
if __name__ == '__main__':
v1 = Vector(1.1, 2.2)
dumpd = bytes(v1)
print(dumpd)
v1.typecode = 'f' #双精度浮点数表示分量
dumpf = bytes(v1)
print(dumpf)
print(Vector.typecode) #结果
b'd\x9a\x99\x99\x99\x99\x99\xf1?\x9a\x99\x99\x99\x99\x99\x01@'
b'f\xcd\xcc\x8c?\xcd\xcc\x0c@'
d #类属性没有变
也可修改类属性来修改所有实例的typecode默认值
Vector.typecode = 'f'
一般使用方式是创建一个子类,在子类中覆盖掉类属性。
以上来自《流畅的python》
python风格对象的更多相关文章
- 第9章 符合Python风格的对象
#<流畅的Python>读书笔记 # 第9章 符合Python风格的对象 # 本章包含以下话题: # 支持用于生成对象其他表示形式的内置函数(如repr().bytes(),等等) # 使 ...
- Fluent_Python_Part4面向对象,09-pythonic-obj,Python风格的对象
第四部分第9章,Python风格的对象 这一章接第1章,说明常见的特殊方法实现. 本章包括以下话题: 支持用于生成对象其它表示形式的内置函数(如repr().bytes(),等等) 使用一个类方法实现 ...
- Python风格规范
Python风格规范 分号 Tip 不要在行尾加分号, 也不要用分号将两条命令放在同一行. 行长度 Tip 每行不超过80个字符 例外: 长的导入模块语句 注释里的URL 不要使用反斜杠连接行. Py ...
- PYTHON风格规范-Google 开源项目风格指南
Python风格规范 分号 Tip 不要在行尾加分号, 也不要用分号将两条命令放在同一行. 行长度 Tip 每行不超过80个字符 例外: 长的导入模块语句 注释里的URL 不要使用反斜杠连接行. Py ...
- google的Python风格规范
Python风格规范 分号 Tip 不要在行尾加分号, 也不要用分号将两条命令放在同一行. 行长度 Tip 每行不超过80个字符 例外: 长的导入模块语句 注释里的URL 不要使用反斜杠连接行. ...
- Python编码规范和Python风格规范
一.原因 1.长期的工作中,发现大多数程序员的代码可读性差 2.不同的程序员之间的协作很重要,代码可读性必须很好 3.版本升级时,要基于源码升级 4.不友好的代码会影响python的执行效率 二.基于 ...
- Python面对对象相关知识总结
很有一段时间没使用python了,前两天研究微信公众号使用了下python的django服务,感觉好多知识都遗忘了,毕竟之前没有深入的实践,长期不使用就忘得快.本博的主要目的就是对Python中我认为 ...
- Python风格规范-FYI
Python风格规范 分号 Tip 不要在行尾加分号, 也不要用分号将两条命令放在同一行. 行长度 Tip 每行不超过80个字符 例外: 长的导入模块语句 注释里的URL 不要使用反斜杠连接行. Py ...
- Python风格规范分享
今天给大家分享Python 风格规范,以下代码中 Yes 表示推荐,No 表示不推荐. 分号 不要在行尾加分号, 也不要用分号将两条命令放在同一行. 行长度 每行不超过80个字符 以下情况除外: 长的 ...
随机推荐
- 题解 P3317 [SDOI2014]重建
题解 前置芝士:深度理解的矩阵树定理 矩阵树定理能求生成树个数的原因是,它本质上求的是: \[\sum_T \prod_{e\in T} w_e \] 其中 \(w_e\) 是边权,那么我们会发现其实 ...
- GitLabRunner命令
启动命令 gitlab-runner --debug <command> #调试模式排查错误特别有用. gitlab-runner <command> --help #获取帮助 ...
- mysql行转列 问题 SUM(IF(条件,列值,0))
sum(if(条件,列值,0))语法用例: select name,sum(if(subject="语文",score,0)) as "语文" from gra ...
- 【java web】监听器listener
一.简介 Java的监听器,也是系统级别的监听.监听器随web应用的启动而启动.Java的监听器在c/s模式里面经常用到,它会对特定的事件产生产生一个处理.监听在很多模式下用到,比如说观察者模式,就是 ...
- Mooc中国大学Python学习笔记--数字类型及操作
整数类型 只需知道整数无限制,pow(),4进制表示形式 与数学中整数的概念一致 --可正可负,没有取值范限制 --pow(x,y)函数:计算x^y,想算多大算多大 -十进制:10 -二进制,以0b或 ...
- Hibernate框架基本使用
时间:2017-1-16 00:36 --什么是Hibernate Hibernate是一个开放源代码的关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以使用对象编程思 ...
- centos7 Tomcat 停止服务时报错: java.net.ConnectException: 拒绝连接 (Connection refused)
2021-08-02 1.问题描述 配置完 server.xml 文件后,执行 shutdown.sh 脚本停止服务,出现以下错误 2. 解决方法 查看当前正在运行的 Java 进程号 # 找到 Ja ...
- eslint and stylelint config
eslint: module.exports = { root: true, env: { browser: true, es6: true, node: true ...
- 并发编程之:CountDownLatch
大家好,我是小黑,一个在互联网苟且偷生的农民工. 先问大家一个问题,在主线程中创建多个线程,在这多个线程被启动之后,主线程需要等子线程执行完之后才能接着执行自己的代码,应该怎么实现呢? Thread. ...
- vue 双向绑定(v-model 双向绑定、.sync 双向绑定、.sync 传对象)
1. v-model实现自定义组件双向绑定 v-model其实是个语法糖,如果没按照相应的规范定义组件,直接写v-model是不会生效的.再说一遍,类似于v-on:click可以简写成@click,v ...