流畅的python,Fluent Python 第九章笔记
符合Python风格的对象。
9.1对象表达形式
repr() 对应__repr__
str() 对应__str__
bytes() 对应__bytes__
format()或 str.format() 对应__format__
前面三种返回的都是Unicode字符串,只有最后的方法返回的是字节序列。
9.2 再谈向量类
python;gutter:true;">from array import arrayimport 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 Vector2dIn [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 Vector2dIn [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.3In [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 datetimeIn [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]: vOut[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 arrayimport 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 importlibimport 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.pySelected 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方法了。
以上是 流畅的python,Fluent Python 第九章笔记 的全部内容, 来源链接: utcz.com/z/388860.html