• 流畅的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'dx00x00x00x00x00x00x08@x00x00x00x00x00x00x10@'
    
    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'dx00x00x00x00x00x00x08@x00x00x00x00x00x00x10@'
    
    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]: 'u6211u4eec'                                                                           
    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方法了。

  • 相关阅读:
    GDB常用命令
    codevs1743
    Codeforces Round #369 (Div. 2)E
    Codeforces Round #200 (Div. 2)E
    2016 Multi-University Training Contest 4 T9
    2016 Multi-University Training Contest 1 T3
    2016 Multi-University Training Contest 1 T4
    HDU 5448 Marisa’s Cake
    codeforces 467C George and Job dp
    poj 1704 Georgia and Bob 博弈
  • 原文地址:https://www.cnblogs.com/sidianok/p/12111762.html
Copyright © 2020-2023  润新知