流畅的python,Fluent Python 第九章笔记

python

符合Python风格的对象。

9.1对象表达形式

repr() 对应__repr__

str() 对应__str__

bytes() 对应__bytes__

format()或 str.format() 对应__format__

前面三种返回的都是Unicode字符串,只有最后的方法返回的是字节序列。

9.2 再谈向量类

python;gutter:true;">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方法了。

以上是 流畅的python,Fluent Python 第九章笔记 的全部内容, 来源链接: utcz.com/z/388860.html

回到顶部