流畅的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备选构造类方法

classmate的最常见的用途就是定义备选的构造实例方式,因为它的方法,默认传递的是类本身。

按照书中样式,给前面的类添加一个类方法。

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__': ,
              '__iter__': ,
              '__repr__': ,
              '__str__': ,
              '__bytes__': ,
              '__eq__': ,
              '__abs__': ,
              '__bool__': ,
              'frombytes': ,
              '__dict__': ,
              '__weakref__': ,
              '__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))                                                             

Out[329]: Vector2d(3.0,4.0)

In [330]: v1 = Vector2d.frombytes(bytes(v))                                                        


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)
 in 
----> 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 第九章笔记)