流畅的python,Fluent Python 第九章笔记
符合Python风格的对象。
9.1对象表达形式
repr() 对应__repr__
str() 对应__str__
bytes() 对应__bytes__
format()或 str.format() 对应__format__
前面三种返回的都是Unicode字符串,只有最后的方法返回的是字节序列。
9.2 再谈向量类
from array import array import math class Vector2d: typecode = 'd' def __init__(self, x, y): self.x = x self.y = y def __iter__(self): # 返回一个迭代器,对象拥有__next__属性 '''有了__iter__属性,不仅可以多变量取值,还可以被for循环使用''' 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): # abs返回一个直角三角形斜边长 return math.hypot(self.x, self.y) def __bool__(self): # 直接调用对象的abs值,然后用bool取值 return bool(abs(self))
这个是按照书上的要求写的一个向量类,写的很好,让我学习了很多。逻辑也很紧密。下面上一些实例化以后的操作。
In [308]: from t9_2 import Vector2d In [309]: v1 = Vector2d(3, 4) In [310]: v1 Out[310]: Vector2d(3,4) In [311]: x, y =v1 In [312]: x,y Out[312]: (3, 4) In [313]: v1_clone = eval(repr(v1)) In [314]: v1_clone == v1 Out[314]: True In [315]: print(v1) (3, 4) In [316]: bytes(v1) Out[316]: b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@' In [317]: abs(v1) Out[317]: 5.0
基本定义的方法都用到了,其中多变量赋值调用了__iter__,还有就是eval(repr)的方式新建一个对象,很新奇。
9.3备选构造类方法
classmethod的最常见的用途就是定义备选的构造实例方式,因为它的方法,默认传递的是类本身。
按照书中样式,给前面的类添加一个类方法。
class Vector2d: typecode = 'd' def __init__(self, x, y): self.x = x self.y = y def __iter__(self): # 返回一个迭代器,对象拥有__next__属性 '''有了__iter__属性,不仅可以多变量取值,还可以被for循环使用''' 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): # abs返回一个直角三角形斜边长 return math.hypot(self.x, self.y) def __bool__(self): # 直接调用对象的abs值,然后用bool取值 return bool(abs(self)) @classmethod def frombytes(cls, octets): typecode = chr(octets[0]) # 先读取array的typecode menv = memoryview(octets[1:]).cast(typecode) print(menv) return cls(*menv)
然后在ide shell中运行,首先,reload模块。
from t9_2 import Vector2d In [326]: vars(Vector2d) Out[326]: mappingproxy({'__module__': 't9_2', 'typecode': 'd', '__init__': <function t9_2.Vector2d.__init__(self, x, y)>, '__iter__': <function t9_2.Vector2d.__iter__(self)>, '__repr__': <function t9_2.Vector2d.__repr__(self)>, '__str__': <function t9_2.Vector2d.__str__(self)>, '__bytes__': <function t9_2.Vector2d.__bytes__(self)>, '__eq__': <function t9_2.Vector2d.__eq__(self, other)>, '__abs__': <function t9_2.Vector2d.__abs__(self)>, '__bool__': <function t9_2.Vector2d.__bool__(self)>, 'frombytes': <classmethod at 0x106e32790>, '__dict__': <attribute '__dict__' of 'Vector2d' objects>, '__weakref__': <attribute '__weakref__' of 'Vector2d' objects>, '__doc__': None, '__hash__': None}) In [327]: v = Vector2d(3,4) In [328]: bytes(v) Out[328]: b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@' In [329]: Vector2d.frombytes(bytes(v)) <memory at 0x108543050> Out[329]: Vector2d(3.0,4.0) In [330]: v1 = Vector2d.frombytes(bytes(v)) <memory at 0x10840c050> In [331]: v1 == v Out[331]: True In [332]: v1 is v Out[332]: False In [333]:
通过类方法可以新建一个与原来一样的对象,当然是一个新地址的对象。
9.5format显示
下面一句是书中的原话,我不是很理解。
内置的format()函数和str.format()方法把各个类型的格式化方式委托给响应的.__format__(format_spec)方法。
后面的比较好理解。
fromat(myobj, format_spec)的第二个参数
str.format()方法的格式字符串,{}里替换字段中冒号后面的部分。
str.format前面我已经记录过了,这里我主要写一个format函数的使用。
!s :将对象格式化转换成字符串 !a :将对象格式化转换成Unicode !r :将对象格式化转换成repr
In [378]: '{!a}'.format('我们') Out[378]: "'\\u6211\\u4eec'" In [379]: '{!r}'.format('我们') Out[379]: "'我们'" In [380]: "'\\u6211\\u4eec'" Out[380]: "'\\u6211\\u4eec'" In [381]: '\\u6211\\u4eec' Out[381]: '\\u6211\\u4eec' In [382]: '\u6211\u4eec' Out[382]: '我们' In [383]: '{!s}'.format('我们') Out[383]: '我们'
In [334]: brl = 1/2.3 In [335]: brl Out[335]: 0.4347826086956522 In [336]: format(brl, '.2f') Out[336]: '0.43' In [337]: format(brl, '10.2f') Out[337]: ' 0.43' In [338]: format(brl, '=10.2f') Out[338]: ' 0.43' In [339]: format(brl, '=<10.2f') Out[339]: '0.43======' In [340]: format(brl, '=>10.2f') Out[340]: '======0.43' In [341]: format(brl, '=^10.2f') Out[341]: '===0.43===' In [342]:
对于小数,取几位数还是很方便的。
In [344]: format(1/3,'.1%') Out[344]: '33.3%' In [345]: format(1/3,'.2%') Out[345]: '33.33%' In [346]: format(16,'b') Out[346]: '10000' In [347]:
切换百分比输出,还有不同类型的数字转换。
datatime里面定义了__format__可以来看一下具体使用。
In [348]: from datetime import datetime In [349]: now = datetime.now() In [350]: now.strftime('%H:%M:%S') Out[350]: '01:11:38' In [351]: format(now, '%H:%M:%S') Out[351]: '01:11:38' In [352]: "It's now {0:%I:%M %p}".format(now) Out[352]: "It's now 01:11 AM" In [353]:
这个格式化输出还是非常方便的。
根据要求给刚才的类添加__format__函数
def __format__(self, format_spec=''): components = (format(c, format_spec) for c in self) # 使用生成器,用format处理属性 return '({},{})'.format(*components) # 解包后格式化输出
In [355]: v Out[355]: Vector2d(1,2) In [356]: format(v) Out[356]: '(1,2)' In [357]: format(v, '.2f') Out[357]: '(1.00,2.00)' In [358]: format(v, '.3e') Out[358]: '(1.000e+00,2.000e+00)'
9.6可散列的Vector2d
所有默认的类或者实例,在没有继承修改__eq__都是可以被哈希的,当定义了__eq__必须定义__hash__要不然不能被哈西、
但我自己测试了,自己继承修改了__hash__,没有重新定义__eq__,实例是可以被hash的,而且就算对象的hash返回相等,但内存地址还是不同的。
In [385]: class A: ...: def __init__(self,num): ...: self.num = num ...: def __hash__(self): ...: return hash(self.num) ...: In [386]: a = A(1) In [387]: b = A(1) In [388]: a == b Out[388]: False In [389]: a is b Out[389]: False In [390]: hash(a) Out[390]: 1 In [391]: hash(b) Out[391]: 1 In [395]: c Out[395]: set() In [396]: c.add(a) In [397]: c Out[397]: {<__main__.A at 0x108327d10>} In [398]: c Out[398]: {<__main__.A at 0x108327d10>} In [399]: c.add(b) In [401]: c Out[401]: {<__main__.A at 0x108327d10>, <__main__.A at 0x108844150>}
当我定义了__eq__没有定义__hash__报错了。
In [402]: hash(v) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-402-456d5fdfc36e> in <module> ----> 1 hash(v) TypeError: unhashable type: 'Vector2d'
稍微简化了一下的完整代码如下:
from array import array import math class Vector2d: typecode = 'd' def __init__(self, x, y): self.__x = x # 转换成私有变量 self.__y = y @property #把方法变成属性,而且是只读的 def x(self): return self.__x @property def y(self): return self.__y def __iter__(self): # 返回一个迭代器,对象拥有__next__属性 '''有了__iter__属性,不仅可以多变量取值,还可以被for循环使用''' 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): # abs返回一个直角三角形斜边长 return math.hypot(self.x, self.y) def __bool__(self): # 直接调用对象的abs值,然后用bool取值 return bool(abs(self)) def __format__(self, format_spec=''): components = (format(c, format_spec) for c in self) # 使用生成器,用format处理属性 return '({},{})'.format(*components) # 解包后格式化输出 def __hash__(self): # 通过异或的方式,混合双方的哈希值。 return hash(self.x) ^ hash(self.y) @classmethod def frombytes(cls, octets): typecode = chr(octets[0]) # 先读取array的typecode menv = memoryview(octets[1:]).cast(typecode) print(menv) return cls(*menv)
In [409]: v3 = Vector2d(3.1,4.2) In [410]: hash(v3) Out[410]: 384307168202284039 In [411]: v4 = Vector2d(3.1,4.2) In [412]: v3 == v4 Out[412]: True In [413]: v3 is v4 Out[413]: False In [414]: v3 Out[414]: Vector2d(3.1,4.2) In [415]: v4 Out[415]: Vector2d(3.1,4.2)
python中每个对象独有独立的id。
9.7Python的私有属性和"受保护的属性"
我们在继承父类的属性时,会把父类的属性全部继承过来,假如父类有一个mood的属性,你可能不知情的情况下,在继承类里面定义了mood属性,会覆盖父类的属性。
__mood就可以避免这个事情的发送,他会自动把属性转换为_类名__mood的形式。
In [416]: vars(v4) Out[416]: {'_Vector2d__x': 3.1, '_Vector2d__y': 4.2} In [417]:
刚才我订定义的V4就可以看出来了。但既然知道了属性名,其实强制要改也能改,所以有些高手说,单下划线就够了。
一半Python程序员默认_单下划线的属性不能读取,反正如果一定要修改肯定能修改属性,那还不如单下划线就好了,难怪很多模块里面都时单下划线的。
9.8 使用__slots__类属性节省空间。
默认情况下,Python中的各个实例中通过__dict__的字典存储实例属性,字典消耗内存大,通过__slots__类属性,能够让解释器在元祖中存储实例属性,而不用字典。
如果子类没有定义__slots__属性,不会继继承父类的__slots__属性,换言之如果子类定义了__slots__属性,会继承父类的__slots__属性。
实例只能拥有__slots__中列出的属性,除非把__dict__加入到__slots__中(但这样句失去了节省内存的功效)
如果不把__weakref__加入__slots__,实例就不能作为弱引用的目标。
不要使用__slots__属性禁止类的用户新增实例属性。__slots__时用于优化的,不时为了约束程序员。
展示书中案列:
import importlib import sys import resource NUM_VECTORS = 10**7 if len(sys.argv) == 2: module_name = sys.argv[1].replace('.py', '') # 替换成倒包文件 # module = importlib.import_module(module_name) # 书中写法,导入字符串模块 module = __import__(module_name) # 自己以前记得的__import__ else: print('Usage: {} < vector - module - to - test>'.format('XXX.py')) sys.exit(1) fmt = 'Selected Vector2d type: {.__name__}.{.__name__}' print(fmt.format(module, module.Vector2d)) men_init = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss # 获取系统内存大小 print(f'CREATING{men_init:,} Vector2d instances') vertors = [module.Vector2d(3.0, 4.0) for i in range(NUM_VECTORS)] men_final = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss print('Initial RAM usage: {:14,}'.format(men_init)) print(' Final RAM usage: {:14,}'.format(men_final))
shijianzhongdeMacBook-Pro:第九章 shijianzhong$ python3 t9_12.py t9_2.py Selected Vector2d type: t9_2.Vector2d CREATING6,807,552 Vector2d instances Initial RAM usage: 6,807,552 Final RAM usage: 1,986,928,640 shijianzhongdeMacBook-Pro:第九章 shijianzhong$ python3 t9_12.py t9_2.py Selected Vector2d type: t9_2.Vector2d CREATING6,721,536 Vector2d instances Initial RAM usage: 6,721,536 Final RAM usage: 684,306,432
1000万个对象,实际大小相差1.3个G左右。
9.9覆盖类属性。
代码中的typecode = 'd',属于类属性。
在实例化的对象里面看不到这个属性,但可以通过self.typecode改变实例的属性
可以感觉为每个实例默认了一个看不到可以使用的属性,还有一个好处,
这个属性可以继承给子类,子类只要修改typecode = 'd',就可以拥有自己的类。
最后在repr输出类名的时候,尽然选择self.__class__.__name的形式输出,不要通过类名.__name__方式输出,要不然子类就必须重新定义repr方法了。