zoukankan      html  css  js  c++  java
  • 《Python3学习笔记》第二章 类型 笔记以及摘要(完结)

    我们将族群或类别称作类型(class),将个体叫做实例(instance)。类型持有同族个体的共同行为和共享状态,而实例仅保存私有特性而已。

    上面这局话都类与实例的概括是确实太精准了。

    任何类型都是其祖先类型的子类,同样对象也可以被判定为其祖先类型的实例。

    Python中所有的类都是object的子类,同样也属于object的实例,但object哪里来,由type造出来,在Python中type就是造物主。

    In [123]: isinstance(type,object)                                                                                                                                  
    Out[123]: True
    
    In [124]: issubclass(type,object)                                                                                                                                  
    Out[124]: True
    
    In [125]: isinstance(object,type)                                                                                                                                  
    Out[125]: True
    
    In [126]:  
    

     简单理解,祖宗里面最高的是object,他是最高的,就好比树根。但祖宗确是造物主type创建,但type也是继承与祖宗,有点拗口。

    单就类型对象而言,其本质就是用来存储方法和字段成员的特殊容器,用同一份设计来实现才是正常思路。

    类型对象属于创建者这样的特殊存在。默认情况下,它们由解释器在首次载入时自动生成,生命周期与进程相同,且仅存在一个实例

    In [135]: type('123') is 'abc'.__class__ is str                                                                                                                    
    Out[135]: True
    

    名字

    在通常认知里,变量是一段具有特定格式的内存,变量名则是内存别名。因为在编码阶段,无法确定内存的具体位置,故使用名称符号代替

     静态编译和动态解释型语言对于变量名的处理方式也完全不同。静态编译器和链接器会以固定地址,或直接、间接寻址指令代替变量名。也就是说变量名

    不参与执行过程,可被删除。但在解释型动态语言里,名字和对象通常史两个运行期实体。名字不但有自己的类型,还需分配内存,并介入执行过程。

    甚至可以说,名字才是动态模型的基础。

    赋值步骤:

    1准备好右边值目标对象

    2准好变量名

    3在名字空间里为两者建立关联。

    即便如此,名字与目标对象之间也仅是引用关联。名字只负责找人,但对于此人一无所知。

    鉴于在运行期才能知道名字引用的目标类型,所以说Python是一种动态类型语言。

    名字空间

    名字空间默认使用字典(dict)数据结构,由多个键值对(key/value)组成。

    内置函数globals和locals分别返回全局名字空间和本地名字空间字典

    In [138]: globals() is locals()                                                                                                                                    
    Out[138]: True
    
    In [139]:  
    

     在主模块中运行,locals()与glocals()是相等的

    In [140]: def test(): 
         ...:     x = 'hello' 
         ...:     print(locals()) 
         ...:     print('local', id (locals())) 
         ...:     print('global', id(globals())) 
         ...:                                                                                                                                                          
    
    In [141]: test()                                                                                                                                                   
    {'x': 'hello'}
    local 4547282256
    global 4510296176
    
    In [142]:    
    

     globals总是固定指向模块名字空间,而locals则指向当前作用域环境。

    可以直接修改名字空间来建立关联引用。

    In [142]: globals()['name'] = 'sidian'                                                                                                                             
    
    In [143]: name                                                                                                                                                     
    Out[143]: 'sidian'
    
    In [144]:          
    

    正因为名字空间的特性,赋值操作仅是名字在名字空间里重新关联,而非修改原对象。

    命名习惯建议

    1 类名称使用CapWords格式

    2模块文件名、函数、方法成员等使用lower_case_with_underscores格式

    3全局常量使用UPPER_CASE_WITH_UNDERSCORES格式

    4避免与内置函数或标准库的常用类型同名,因为这样容易误导

    模块与类还是由一些相同的地方的

    模块成员以但下划线开头(_x),属私有成员,不会被*号导入

    类型成员以双下划线揩油,但无结尾,属自动命名私有成员

    以双下划线开头和结尾,通常是系统成员,应避免使用

    在交互模式下,单下划线(_)返回最后一个表达式结果。

    In [146]: 1+2                                                                                                                                                      
    Out[146]: 3
    
    In [147]: _                                                                                                                                                        
    Out[147]: 3
    
    In [148]:       
    

    内存

    Python没有值类型、引用类型之分。事实上,每个对象都很重。即便是简单的数字,也由标准对象头,以及保存类型指针和引用计数等信息。

    弱引用

    如果说,名字与目标对象关联构成强引用关系,会增加引用计数,进而影响期生命周期,那么弱引用(weak reference)就是简配版,骑在保留引用前提下,不增加计数,也不阻止目标被回收

    不是所有的类型支持弱引用,比如int,tuple,看有没有__weakref__属性

    Python 3.7.4 (default, Jul  9 2019, 18:13:23) 
    Type 'copyright', 'credits' or 'license' for more information
    IPython 7.7.0 -- An enhanced Interactive Python. Type '?' for help.
    PyDev console: using IPython 7.7.0
    Python 3.7.4 (default, Jul  9 2019, 18:13:23) 
    [Clang 10.0.1 (clang-1001.0.46.4)] on darwin
    class X:
      ...:     def __del__(self):
      ...:         print(id(self), 'dead')
      ...:         
    a = X()
    import sys
    sys.getrefcount(a)
    Out[5]: 2
    import weakref
    w = weakref.ref(a)
    id(w())
    Out[8]: 4411485904
    sys.getrefcount(a)
    Out[9]: 2
    del a
    4411485904 dead
    

    weakref内置的一些方法

    w()
    a = X()
    w = weakref.ref(a)
    weakref.getweakrefcount(a)
    Out[14]: 1
    weakref.getweakrefs(a)
    Out[15]: [<weakref at 0x106f25bf0; to 'X' at 0x106f16f50>]
    hex(id(w))
    Out[16]: '0x106f25bf0'
    

    弱引用可用于一些特定场合,比较缓存,监控等。这类"外挂"场景不应该影响目标对象,不能阻止它们被回收。

    弱引用的另外一个典型应用就是实现Finalizer,也就是在对象被回收时执行额外的"清理操作"(有点像回调函数)

    a = X()
    w = weakref.ref(a, lambda x:print(x,x() is None))
    del a
    4377854224 dead
    <weakref at 0x107083770; dead> True
    

     当删除a的时候,执行了red里面定义的函数。

    书中说明了为什么不用__del__

    因为析构方法作为目标成员,其用途是完成对象内部资源清理。它无法感知,也不应该处理与之无法的外部场景。

    但在实际开发中,外部关联场景有很多,那么用Finalizer才是合理设计,因为这样只有一个不会侵入的观察员存在。

    注意回调函数参数为弱引用而废目标对象。回调函数执行时,目标已无法访问。

    使用weakref.proxy可以使弱引用对象的使用与原名字语法一致。

    a = X()
    a.name = 'sidian'
    w = weakref.ref(a)
    w.name
    Traceback (most recent call last):
      File "/usr/local/lib/python3.7/site-packages/IPython/core/interactiveshell.py", line 3326, in run_code
        exec(code_obj, self.user_global_ns, self.user_ns)
      File "<ipython-input-32-df0c13a86467>", line 1, in <module>
        w.name
    AttributeError: 'weakref' object has no attribute 'name'
    w().name
    Out[33]: 'sidian'
    p = weakref.proxy(a)
    p
    Out[35]: <__main__.X at 0x104f256b0>
    p.name
    Out[36]: 'sidian'
    p.age=80
    a.age
    Out[38]: 80
    w().age
    Out[40]: 80
    del a
    4377856016 dead
    

    对象复制

    复制分浅拷贝(shallow copy)和深度拷贝(deep copy)两种。

    对于对象内部成员,浅拷贝仅复制名字引用,而深拷贝会递归复制所有成员。

    pick.dumps 与pick.loads可以实现深拷贝

    循环引用垃圾回收

    当两个或更多对象构成循环引用(reference cycle)时,该机制就会遭遇麻烦。因为彼此引用导致计数永不归零,从而无法触发回收操作,形成内存泄露。

    为此,另有一套专门用来处理循环引用的垃圾回收器(gc)作为补充

    单个对象也能构成循环引用,比如列表把自身引用作为元素存储。

    In [184]: l = [1,2]                                                                                                                                                
    
    In [185]: l[1] = l                                                                                                                                                 
    
    In [186]: l                                                                                                                                                        
    Out[186]: [1, [...]]
    

     对垃圾回收器进行操作

    class X:
       ...:     def __del__(self):
       ...:         print(id(self), 'dead')
       ...:         
    gc.disable()
    a = X()
    b = X()
    a.x=b
    b.x=a
    del a
    del b
    gc.enable()
    4595851024 dead
    4595950928 dead
    gc.collect()
    Out[21]: 233
    

    对于某些性能优先的算法,在确保没有循环引用的前提下,临时关闭gc可获得更好的性能。

    甚至在某些极端优化策略里,会完全屏蔽垃圾回收,以重启进程来回收资源。

    做性能测试(timeit)会关闭gc,避免垃圾回收对执行计时造成影响

    编译

    源码先编译成字节码,才能交由解释器以解释方式执行。这也时Python性能为人诟病的一个重要原因。

    字节码(byte code)时中间代码,面向后端编译器或解释器。要么解释执行,要么二次编译成机器代码(native code)执行。

    字节码指令通常基于栈式虚拟机(stack_based vm)实现,没有寄存器等复杂结构,实现简单。

    且其具备重中立性,与硬件架构、操作系统等无关,便于将编译和平台实现分离,式跨平台语言的主流方案。

    Python3使用专门的保存字节码缓存文件(__pycache__/*.pyc)

    除了执行指令的字节码,还有很多数据,共同组成执行单元。

    从这些元数据里,可以获得参数、闭包等诸多信息。

    In [188]: def add(x, y): 
         ...:     return x + y 
         ...:                                                                                                                                                          
    
    In [189]: add.__code__                                                                                                                                             
    Out[189]: <code object add at 0x10d1bba50, file "<ipython-input-188-5fcdd2924cd8>", line 1>
    
    In [190]: add.__code__.co_varnames                                                                                                                                 
    Out[190]: ('x', 'y')
    
    In [191]: add.__code__.co_code                                                                                                                                     
    Out[191]: b'|x00|x01x17x00Sx00'
    
    In [192]:                                                                                                                                                          
    
    In [192]: import dis                                                                                                                                               
    
    In [193]: dis.dis(add)                                                                                                                                             
      2           0 LOAD_FAST                0 (x)
                  2 LOAD_FAST                1 (y)
                  4 BINARY_ADD
                  6 RETURN_VALUE
    
    In [194]:    
    

     上面简单的运行了dis进行了反汇编(disassembly)

    某些时候,需要手工完成编译操作。

                                                                                                                 
    
    In [197]: source = ''' 
         ...: print('hello, world') 
         ...: print(1+2) 
         ...: '''                                                                                                                                                      
    
    In [198]: code = compile(source,'demo','exec')                                                                                                                     
    
    In [199]: dis.show_code(code)                                                                                                                                      
    Name:              <module>
    Filename:          demo
    Argument count:    0
    Kw-only arguments: 0
    Number of locals:  0
    Stack size:        2
    Flags:             NOFREE
    Constants:
       0: 'hello, world'
       1: 3
       2: None
    Names:
       0: print
    
    In [200]: dis.dis(code)                                                                                                                                            
      2           0 LOAD_NAME                0 (print)
                  2 LOAD_CONST               0 ('hello, world')
                  4 CALL_FUNCTION            1
                  6 POP_TOP
    
      3           8 LOAD_NAME                0 (print)
                 10 LOAD_CONST               1 (3)
                 12 CALL_FUNCTION            1
                 14 POP_TOP
                 16 LOAD_CONST               2 (None)
                 18 RETURN_VALUE
    
    In [201]: exec(code)                                                                                                                                               
    hello, world
    3
    
    In [202]: 
    

     除compile函数外,标准库还有编译原码文件的相关操作。

    分别为py_compile,compileall,后续用到再学

    执行

    不管代码如何生成,最终要么以模块导入执行,要么调用eval,exec执行。这两个内置函数使用简单,eval执行单个表达式,exec执行代码块,接收字符串或已编译好的代码对象(code)作为参数。

    如果是字符串,就会检查是否符合语法规则。

    In [202]: eval('(1+2)+3')                                                                                                                                          
    Out[202]: 6
    
    In [203]: s = ''' 
         ...: def test(): 
         ...:     print("hello,world") 
         ...: test() 
         ...: '''                                                                                                                                                      
    
    In [204]: exec(s)                                                                                                                                                  
    hello,world
    
    In [205]:  
    

     无论选择那种方式执行,都必须由相应的上下文环境。默认直接使用当前全局和本地名字空间。

    如同不同代码一样,从中读取目标对象,或写入新值。

    In [205]: x= 100                                                                                                                                                   
    
    In [206]: def test(): 
         ...:     y = 200 
         ...:     print(eval('x+y')) 
         ...:                                                                                                                                                          
    
    In [207]: test()                                                                                                                                                   
    300
    
    
    
    In [209]: def test(): 
         ...:     print('test:', id(globals()), id(locals())) 
         ...:     exec('print("exec", id(globals()),id(locals()))') 
         ...:                                                                                                                                                          
    
    In [210]: test()                                                                                                                                                   
    test: 4510296176 4537806512
    exec 4510296176 4537806512
    
    In [211]:   
    

    有了操作上下文名字空间的能力,动态代码就可向外部环境注入新的成员,比如说构建新的类型,导入新的算法,最终达到将动态逻辑或其结果融入,成为当前体系组成部分的设计目标。

    In [211]: s =''' 
         ...: class My_X:... 
         ...: def hello(): 
         ...:     print('hello, world') 
         ...:  '''                                                                                                                                                     
    
    In [212]: exec(s)                                                                                                                                                  
    
    In [213]: My_X                                                                                                                                                     
    Out[213]: __main__.My_X
    
    In [214]: hello()                                                                                                                                                  
    hello, world
    
    In [215]:  
    

    某些时候,动态代码来源不确定,基于安全考虑,必须对执行过程进行隔离,阻止其直接读写环境。

    如此,就需显式传入容器对象作为动态代码的专用名字空间,以类似建议沙箱(sandbox)方式执行。

    根据需要,分别提供globals,locals参数,也可公用同一个空间字典。

    为保证代码正确执行,解释器会自动导入__bultins__模块,以便调用内置函数。

    In [215]: g = {'x':100}                                                                                                                                            
    
    In [216]: l = {'y':200}                                                                                                                                            
    
    In [217]: eval('x+y',g,l)                                                                                                                                          
    Out[217]: 300
    
    In [218]:                                                                                                                                                          
    
    In [218]: ns = {}                                                                                                                                                  
    
    In [219]: exec('class XXX:...',ns)                                                                                                                                 
    
    In [220]: ns   
    

     同时提供两个名字空间参数时,默认总是在locals优先。

    In [221]: s = ''' 
         ...: print(x) 
         ...: global y 
         ...: y += 100 
         ...: z = x+y 
         ...: '''                                                                                                                                                      
    
    In [222]: g = {'x':10,'y':20}                                                                                                                                      
    
    In [223]: l = {'x':1000}                                                                                                                                           
    
    In [224]: exec(s,g,l)                                                                                                                                              
    1000
    
    In [226]: l                                                                                                                                                        
    Out[226]: {'x': 1000, 'z': 1120}
    

     前面的s定义中y定义为全局变量,所以没有输出到l里面

    前面提及,在函数作用域内,locals函数总是返回执行栈帧(stack frame)名字空间。

    因此就式显式提供locals名字空间,也无法将其注入倒台代码的函数内

    In [227]: s = ''' 
         ...: print(id(locals())) 
         ...: def test(): 
         ...:     print(id(locals())) 
         ...: test() 
         ...: '''                                                                                                                                                      
    
    In [228]: ns = {}                                                                                                                                                  
    
    In [229]: id(ns)                                                                                                                                                   
    Out[229]: 4536481344
    
    In [230]: ns2 = {}                                                                                                                                                 
    
    In [231]: id(ns2)                                                                                                                                                  
    Out[231]: 4540089840
    
    In [232]: exec(s,ns,ns2)                                                                                                                                           
    4540089840
    4544788512
    
    In [234]: ns2                                                                                                                                                      
    Out[234]: {'test': <function test()>}
    

     明显沙盒传入的,默认情况下,所有信息都保存在locals()里面,除非定义了global,会传入globals()

    内置类型

    与自定义类型(user-defined)相比,内置类型(built-in)算是特权阶层。除了它们是符合数据结构的基本构成单元以外,最重要的式被编译器和解释器特别对待。

    比如核心级别的指令和性能优化,专门设计的高效缓存,等等。

    内置类型主要的有int,float,str,bytes,bytearray,list,tuple,dict,set,frozenset,其中bytearray,list,dict,set为可变类型。

    标准库collections.abc列出了相关类型的抽象基类,可据此判断其基本行为方式

    In [10]: import collections.abc                                                 
    
    In [11]: issubclass(dict, collections.abc.Sequence)                             
    Out[11]: False
    
    In [12]: issubclass(dict, collections.abc.MutableSequence)                      
    Out[12]: False
    
    In [13]: issubclass(dict, collections.abc.Mapping)                              
    Out[13]: True
    
    In [14]:  
    

    整数

    In [15]: import sys                                                             
    
    In [16]: x= 1                                                                   
    
    In [17]: sys.getsizeof(x)                                                       
    Out[17]: 28
    
    In [18]: y=1<<10000                                                             
    
    In [19]: sys.getsizeof(y)                                                       
    Out[19]: 1360
    
    In [20]:    
    

     Python中int的变长结构允许我们创建超大的天文数字,理论上仅收可分配内存大小的限制。

    对于长数字,可以用下划线当做分隔符,且不定位置。

    In [21]: 2_3_4                                                                  
    Out[21]: 234
    
    In [22]: 23_345_123                                                             
    Out[22]: 23345123
    
    In [23]:     
    

     另外进制的也可以用

    In [23]: 0x23_34_12_2                                                           
    Out[23]: 36913442
    
    In [24]: 0b01_10                                                                
    Out[24]: 6
    
    In [25]: 
    

     0b,0x 0o分别代码2进制,16进制,8进制的数字

    转换

    In [31]: eval(bin(100))                                                         
    Out[31]: 100
    
    In [32]: bin(100)                                                               
    Out[32]: '0b1100100'
    
    In [33]: hex(100)                                                               
    Out[33]: '0x64'
    
    In [34]: oct(100)                                                               
    Out[34]: '0o144'
    
    In [35]: int(bin(100),2)                                                        
    Out[35]: 100
    
    In [36]: int(hex(100),16)                                                       
    Out[36]: 100
    
    In [37]: int('  100  	')                                                       
    Out[37]: 100
    
    In [38]: int('  100  
    ')                                                       
    Out[38]: 100
    
    In [39]:        
    

     通过一些命令,可以十进制数字转换为指定的进制字符字符串,也可以通过int还原,第二参数为第一输入参数的进制,输出都式10进制的。

    当然也可以通过eval完成,单相比与直接用C实现的转换函数,其性能要差很多,毕竟动态运行需要额外编译和执行开销。

    还有一种转换操作式将整数转换为字节数组,这常用于二进制网络协议和文件读写。在这里需要指定字节序,也就是常说的大小端。

    目前使用较多的Intel x86 、AMD 64 采用小端。ARM则两种都支持,可自行设定。另外,TCP/IP网络字节,采用大端,这属于协议定义,与硬件结构与操作系统无法。

    In [56]: x = 0x1234                                                             
    
    In [57]: n = (x.bit_length() +8 -1) //8                                         
    
    In [58]: n                                                                      
    Out[58]: 2
    
    In [59]: x=0x1234                                                               
    
    In [60]: n = (x.bit_length() + 8 -1)//8                                         
    
    In [61]: b = x.to_bytes(n, sys.byteorder)                                       
    
    In [62]: b                                                                      
    Out[62]: b'4x12'
    
    In [63]: b.hex()                                                                
    Out[63]: '3412'
    
    In [64]: hex(int.from_bytes(b,sys.byteorder))                                   
    Out[64]: '0x1234'
    

     书中的代码执行完毕以后,我对b的输出其实卡住了一会而,为什么直接输出b是b'4x12',不是应该b'x34x12'吗?

    在Python中对于字节码x的输出,如果x的字节码对应asci码有对应值,x就会自动转换成相应的ASCI字符.

    In [79]: b'x34'                                                                
    Out[79]: b'4'
    
    In [80]:  
    

     在电脑数据中一个字节等于8个bit位,对应的16进制刚好为x12,其中的1为高位的4个bit,2对应为低位的4个bit

    所以x12的一个数据刚好为一个字节,Python中显示器的为Unicode,对应2个字节,16位。这些计算机的基础,我薄弱啊。

    所以在计算机中数据都喜欢用x的形式表示,因为计算机的最小单位为字节,用二进制表示需要8个占位00000000,但转换为16进制,刚好一个x表示。

    In [94]: name = '中'                                                            
    
    In [95]: name                                                                   
    Out[95]: '中'
    
    In [96]: name.encode('gbk')                                                     
    Out[96]: b'xd6xd0'
    
    In [97]: name.encode('utf8')                                                    
    Out[97]: b'xe4xb8xad'
    
    In [98]: ord(name)                                                              
    Out[98]: 20013
    
    In [99]: hex(ord(name))                                                         
    Out[99]: '0x4e2d'
    
    In [100]: 'u4e2d'                                                              
    Out[100]: '中'
    
    In [101]: chr(20013)                                                            
    Out[101]: '中'
    
    In [102]: chr(0x4e2d)                                                           
    Out[102]: '中'
    
    In [103]:   
    

    ord() 函数是 chr() 函数(对于8位的ASCII字符串)或 unichr() 函数(对于Unicode对象)的配对函数,它以一个字符(长度为1的字符串)作为参数,返回对应的 ASCII 数值,或者 Unicode 数值。

    上面的列子中,很好的展示了解码与编码,Python编辑器,对于unicode字符是直接显示的。从字符解码也可以看出来,对于中文的解码,utf8需要三个字节,gbk两个字节,unicode也才两个字节。

    In [103]: 0x123                                                                 
    Out[103]: 291
    
    In [104]: 0b0101001                                                             
    Out[104]: 41
    
    In [105]: 0o5432                                                                
    Out[105]: 2842
    
    In [106]:  
    

     在终端中,无论你输入那种类型的数字,终端会自动转换成10进制的输出,这次学习让我对字符编码又有了更好的认识。

    运算符

    比较有意思的是一个

    In [111]: divmod(5,2)                                                           
    Out[111]: (2, 1)
    

     返回了一个元祖,一个是商,一个属余数。

    布尔

    布尔是整数的子类型,也就是说True和False可被当做数字直接使用

    In [112]: True.__class__                                                        
    Out[112]: bool
    
    In [113]: True.__class__.__mro__                                                
    Out[113]: (bool, int, object)
    
    In [114]:   
    
    In [114]: True == 1                                                             
    Out[114]: True
    
    In [115]: False == 0                                                            
    Out[115]: True
    
    
    In [117]: False + 1                                                             
    Out[117]: 1
    
    In [118]:     
    

    在进行布尔转换时,数字值、空值(None)、空序列,空字典、空集合,等都被视为False

    对于自定义类型,可通过重写__boll__或__len__方法影响bool转换结果。

    枚举

    首相枚举操作的对象是一个类,是类,不时实例对象。Enum是一个类,继承元类EnumMeta

    操作的对象,属于类属性。Enum()调用的是EnumMeta的__call__方法,整个模块有1000行代码,是在没信心看了。用的又是类元编程。

    In [187]: Color = Enum('Color','BLACK, YELLOW BLUE RED')                                                         
    
    In [188]: black = Color.BLACK                                                                                    
    
    In [189]: isinstance(black, Color)                                                                               
    Out[189]: True
    
    In [190]: black.name                                                                                             
    Out[190]: 'BLACK'
    
    In [191]: black.value                                                                                            
    Out[191]: 1
    
    In [192]:  
    

     很有意思,Color的类属性,是Color的实例,实例有两个描述符,name与value

    通过继承的方式草写一遍

    In [192]: class X(Enum): 
         ...:     A = 'a' 
         ...:     B = 100 
         ...:     C = [1,2,3] 
         ...:                                                                                                        
    
    In [193]: X.C                                                                                                    
    Out[193]: <X.C: [1, 2, 3]>
    
    In [194]: X['B']                                                                                                 
    Out[194]: <X.B: 100>
    
    
    In [196]: X('a')                                                                                                 
    Out[196]: <X.A: 'a'>
    
    In [197]:    
    

     可以通过属性查找,也可以通过值key的形式查找,也可以通过实例化放入value的形式查找具体对象。

    如果要避免相同的枚举定义,可用enum.unique装饰器。

    这个枚举的类,我真心觉的没啥用,反正我基本没用到过,真要用,我宁可自己定义个更加好用的类。

    内存

    对于常用的小叔子,解释器会在初始化时进行预缓存。后续使用时,直接将名字关联到这些缓存既可。如此一来,无须创建实例对象,可提高性能,节约内存开销

    Python3.6 预缓存范围是[-5, 256]

    In [197]: a = -5                                                                                                 
    
    In [198]: b= -5                                                                                                  
    
    In [199]: a is b                                                                                                 
    Out[199]: True
    
    In [200]: a= 256                                                                                                 
    
    In [201]: b= 256                                                                                                 
    
    In [202]: a is b                                                                                                 
    Out[202]: True
    
    In [203]: a = 256                                                                                                
    
    In [204]: a = 257                                                                                                
    
    In [205]: b = 257                                                                                                
    
    In [206]: a is b                                                                                                 
    Out[206]: False
    
    In [207]:   
    

    Python2对回收后的整数复用不做收缩处理,会导致大量闲置内存驻留。而Python3则改进了不少。

    from __future__ import  print_function
    import psutil
    
    
    def rss():
        m = psutil.Process().memory_info()
        print(m.rss >> 20, 'MB')
    
    if __name__ == '__main__':
        rss()
        x = list(range(10000000))
        rss()
        del x
        rss()
    

     运行结果

    shijianzhongdeMacBook-Pro:第二章类型 shijianzhong$ python t2_2.py 
    7 MB
    394 MB
    394 MB
    shijianzhongdeMacBook-Pro:第二章类型 shijianzhong$ python3 t2_2.py 
    8 MB
    394 MB
    84 MB
    

    浮点数

    默认float类型存储双精度(double)浮点数,可表达16到17个小数位

    从实现方式看,浮点数以二进制存储十进制数的近似值。这可能导致执行结果和编码预期不符,造成不一致缺陷,所以对精度有严格要求的地方,应选择固定精度类型。

    ------------恢复内容开始------------

    我们将族群或类别称作类型(class),将个体叫做实例(instance)。类型持有同族个体的共同行为和共享状态,而实例仅保存私有特性而已。

    上面这局话都类与实例的概括是确实太精准了。

    任何类型都是其祖先类型的子类,同样对象也可以被判定为其祖先类型的实例。

    Python中所有的类都是object的子类,同样也属于object的实例,但object哪里来,由type造出来,在Python中type就是造物主。

    In [123]: isinstance(type,object)                                                                                                                                  
    Out[123]: True
    
    In [124]: issubclass(type,object)                                                                                                                                  
    Out[124]: True
    
    In [125]: isinstance(object,type)                                                                                                                                  
    Out[125]: True
    
    In [126]:  
    

     简单理解,祖宗里面最高的是object,他是最高的,就好比树根。但祖宗确是造物主type创建,但type也是继承与祖宗,有点拗口。

    单就类型对象而言,其本质就是用来存储方法和字段成员的特殊容器,用同一份设计来实现才是正常思路。

    类型对象属于创建者这样的特殊存在。默认情况下,它们由解释器在首次载入时自动生成,生命周期与进程相同,且仅存在一个实例

    In [135]: type('123') is 'abc'.__class__ is str                                                                                                                    
    Out[135]: True
    

    名字

    在通常认知里,变量是一段具有特定格式的内存,变量名则是内存别名。因为在编码阶段,无法确定内存的具体位置,故使用名称符号代替

     静态编译和动态解释型语言对于变量名的处理方式也完全不同。静态编译器和链接器会以固定地址,或直接、间接寻址指令代替变量名。也就是说变量名

    不参与执行过程,可被删除。但在解释型动态语言里,名字和对象通常史两个运行期实体。名字不但有自己的类型,还需分配内存,并介入执行过程。

    甚至可以说,名字才是动态模型的基础。

    赋值步骤:

    1准备好右边值目标对象

    2准好变量名

    3在名字空间里为两者建立关联。

    即便如此,名字与目标对象之间也仅是引用关联。名字只负责找人,但对于此人一无所知。

    鉴于在运行期才能知道名字引用的目标类型,所以说Python是一种动态类型语言。

    名字空间

    名字空间默认使用字典(dict)数据结构,由多个键值对(key/value)组成。

    内置函数globals和locals分别返回全局名字空间和本地名字空间字典

    In [138]: globals() is locals()                                                                                                                                    
    Out[138]: True
    
    In [139]:  
    

     在主模块中运行,locals()与glocals()是相等的

    In [140]: def test(): 
         ...:     x = 'hello' 
         ...:     print(locals()) 
         ...:     print('local', id (locals())) 
         ...:     print('global', id(globals())) 
         ...:                                                                                                                                                          
    
    In [141]: test()                                                                                                                                                   
    {'x': 'hello'}
    local 4547282256
    global 4510296176
    
    In [142]:    
    

     globals总是固定指向模块名字空间,而locals则指向当前作用域环境。

    可以直接修改名字空间来建立关联引用。

    In [142]: globals()['name'] = 'sidian'                                                                                                                             
    
    In [143]: name                                                                                                                                                     
    Out[143]: 'sidian'
    
    In [144]:          
    

    正因为名字空间的特性,赋值操作仅是名字在名字空间里重新关联,而非修改原对象。

    命名习惯建议

    1 类名称使用CapWords格式

    2模块文件名、函数、方法成员等使用lower_case_with_underscores格式

    3全局常量使用UPPER_CASE_WITH_UNDERSCORES格式

    4避免与内置函数或标准库的常用类型同名,因为这样容易误导

    模块与类还是由一些相同的地方的

    模块成员以但下划线开头(_x),属私有成员,不会被*号导入

    类型成员以双下划线揩油,但无结尾,属自动命名私有成员

    以双下划线开头和结尾,通常是系统成员,应避免使用

    在交互模式下,单下划线(_)返回最后一个表达式结果。

    In [146]: 1+2                                                                                                                                                      
    Out[146]: 3
    
    In [147]: _                                                                                                                                                        
    Out[147]: 3
    
    In [148]:       
    

    内存

    Python没有值类型、引用类型之分。事实上,每个对象都很重。即便是简单的数字,也由标准对象头,以及保存类型指针和引用计数等信息。

    弱引用

    如果说,名字与目标对象关联构成强引用关系,会增加引用计数,进而影响期生命周期,那么弱引用(weak reference)就是简配版,骑在保留引用前提下,不增加计数,也不阻止目标被回收

    不是所有的类型支持弱引用,比如int,tuple,看有没有__weakref__属性

    Python 3.7.4 (default, Jul  9 2019, 18:13:23) 
    Type 'copyright', 'credits' or 'license' for more information
    IPython 7.7.0 -- An enhanced Interactive Python. Type '?' for help.
    PyDev console: using IPython 7.7.0
    Python 3.7.4 (default, Jul  9 2019, 18:13:23) 
    [Clang 10.0.1 (clang-1001.0.46.4)] on darwin
    class X:
      ...:     def __del__(self):
      ...:         print(id(self), 'dead')
      ...:         
    a = X()
    import sys
    sys.getrefcount(a)
    Out[5]: 2
    import weakref
    w = weakref.ref(a)
    id(w())
    Out[8]: 4411485904
    sys.getrefcount(a)
    Out[9]: 2
    del a
    4411485904 dead
    

    weakref内置的一些方法

    w()
    a = X()
    w = weakref.ref(a)
    weakref.getweakrefcount(a)
    Out[14]: 1
    weakref.getweakrefs(a)
    Out[15]: [<weakref at 0x106f25bf0; to 'X' at 0x106f16f50>]
    hex(id(w))
    Out[16]: '0x106f25bf0'
    

    弱引用可用于一些特定场合,比较缓存,监控等。这类"外挂"场景不应该影响目标对象,不能阻止它们被回收。

    弱引用的另外一个典型应用就是实现Finalizer,也就是在对象被回收时执行额外的"清理操作"(有点像回调函数)

    a = X()
    w = weakref.ref(a, lambda x:print(x,x() is None))
    del a
    4377854224 dead
    <weakref at 0x107083770; dead> True
    

     当删除a的时候,执行了red里面定义的函数。

    书中说明了为什么不用__del__

    因为析构方法作为目标成员,其用途是完成对象内部资源清理。它无法感知,也不应该处理与之无法的外部场景。

    但在实际开发中,外部关联场景有很多,那么用Finalizer才是合理设计,因为这样只有一个不会侵入的观察员存在。

    注意回调函数参数为弱引用而废目标对象。回调函数执行时,目标已无法访问。

    使用weakref.proxy可以使弱引用对象的使用与原名字语法一致。

    a = X()
    a.name = 'sidian'
    w = weakref.ref(a)
    w.name
    Traceback (most recent call last):
      File "/usr/local/lib/python3.7/site-packages/IPython/core/interactiveshell.py", line 3326, in run_code
        exec(code_obj, self.user_global_ns, self.user_ns)
      File "<ipython-input-32-df0c13a86467>", line 1, in <module>
        w.name
    AttributeError: 'weakref' object has no attribute 'name'
    w().name
    Out[33]: 'sidian'
    p = weakref.proxy(a)
    p
    Out[35]: <__main__.X at 0x104f256b0>
    p.name
    Out[36]: 'sidian'
    p.age=80
    a.age
    Out[38]: 80
    w().age
    Out[40]: 80
    del a
    4377856016 dead
    

    对象复制

    复制分浅拷贝(shallow copy)和深度拷贝(deep copy)两种。

    对于对象内部成员,浅拷贝仅复制名字引用,而深拷贝会递归复制所有成员。

    pick.dumps 与pick.loads可以实现深拷贝

    循环引用垃圾回收

    当两个或更多对象构成循环引用(reference cycle)时,该机制就会遭遇麻烦。因为彼此引用导致计数永不归零,从而无法触发回收操作,形成内存泄露。

    为此,另有一套专门用来处理循环引用的垃圾回收器(gc)作为补充

    单个对象也能构成循环引用,比如列表把自身引用作为元素存储。

    In [184]: l = [1,2]                                                                                                                                                
    
    In [185]: l[1] = l                                                                                                                                                 
    
    In [186]: l                                                                                                                                                        
    Out[186]: [1, [...]]
    

     对垃圾回收器进行操作

    class X:
       ...:     def __del__(self):
       ...:         print(id(self), 'dead')
       ...:         
    gc.disable()
    a = X()
    b = X()
    a.x=b
    b.x=a
    del a
    del b
    gc.enable()
    4595851024 dead
    4595950928 dead
    gc.collect()
    Out[21]: 233
    

    对于某些性能优先的算法,在确保没有循环引用的前提下,临时关闭gc可获得更好的性能。

    甚至在某些极端优化策略里,会完全屏蔽垃圾回收,以重启进程来回收资源。

    做性能测试(timeit)会关闭gc,避免垃圾回收对执行计时造成影响

    编译

    源码先编译成字节码,才能交由解释器以解释方式执行。这也时Python性能为人诟病的一个重要原因。

    字节码(byte code)时中间代码,面向后端编译器或解释器。要么解释执行,要么二次编译成机器代码(native code)执行。

    字节码指令通常基于栈式虚拟机(stack_based vm)实现,没有寄存器等复杂结构,实现简单。

    且其具备重中立性,与硬件架构、操作系统等无关,便于将编译和平台实现分离,式跨平台语言的主流方案。

    Python3使用专门的保存字节码缓存文件(__pycache__/*.pyc)

    除了执行指令的字节码,还有很多数据,共同组成执行单元。

    从这些元数据里,可以获得参数、闭包等诸多信息。

    In [188]: def add(x, y): 
         ...:     return x + y 
         ...:                                                                                                                                                          
    
    In [189]: add.__code__                                                                                                                                             
    Out[189]: <code object add at 0x10d1bba50, file "<ipython-input-188-5fcdd2924cd8>", line 1>
    
    In [190]: add.__code__.co_varnames                                                                                                                                 
    Out[190]: ('x', 'y')
    
    In [191]: add.__code__.co_code                                                                                                                                     
    Out[191]: b'|x00|x01x17x00Sx00'
    
    In [192]:                                                                                                                                                          
    
    In [192]: import dis                                                                                                                                               
    
    In [193]: dis.dis(add)                                                                                                                                             
      2           0 LOAD_FAST                0 (x)
                  2 LOAD_FAST                1 (y)
                  4 BINARY_ADD
                  6 RETURN_VALUE
    
    In [194]:    
    

     上面简单的运行了dis进行了反汇编(disassembly)

    某些时候,需要手工完成编译操作。

                                                                                                                 
    
    In [197]: source = ''' 
         ...: print('hello, world') 
         ...: print(1+2) 
         ...: '''                                                                                                                                                      
    
    In [198]: code = compile(source,'demo','exec')                                                                                                                     
    
    In [199]: dis.show_code(code)                                                                                                                                      
    Name:              <module>
    Filename:          demo
    Argument count:    0
    Kw-only arguments: 0
    Number of locals:  0
    Stack size:        2
    Flags:             NOFREE
    Constants:
       0: 'hello, world'
       1: 3
       2: None
    Names:
       0: print
    
    In [200]: dis.dis(code)                                                                                                                                            
      2           0 LOAD_NAME                0 (print)
                  2 LOAD_CONST               0 ('hello, world')
                  4 CALL_FUNCTION            1
                  6 POP_TOP
    
      3           8 LOAD_NAME                0 (print)
                 10 LOAD_CONST               1 (3)
                 12 CALL_FUNCTION            1
                 14 POP_TOP
                 16 LOAD_CONST               2 (None)
                 18 RETURN_VALUE
    
    In [201]: exec(code)                                                                                                                                               
    hello, world
    3
    
    In [202]: 
    

     除compile函数外,标准库还有编译原码文件的相关操作。

    分别为py_compile,compileall,后续用到再学

    执行

    不管代码如何生成,最终要么以模块导入执行,要么调用eval,exec执行。这两个内置函数使用简单,eval执行单个表达式,exec执行代码块,接收字符串或已编译好的代码对象(code)作为参数。

    如果是字符串,就会检查是否符合语法规则。

    In [202]: eval('(1+2)+3')                                                                                                                                          
    Out[202]: 6
    
    In [203]: s = ''' 
         ...: def test(): 
         ...:     print("hello,world") 
         ...: test() 
         ...: '''                                                                                                                                                      
    
    In [204]: exec(s)                                                                                                                                                  
    hello,world
    
    In [205]:  
    

     无论选择那种方式执行,都必须由相应的上下文环境。默认直接使用当前全局和本地名字空间。

    如同不同代码一样,从中读取目标对象,或写入新值。

    In [205]: x= 100                                                                                                                                                   
    
    In [206]: def test(): 
         ...:     y = 200 
         ...:     print(eval('x+y')) 
         ...:                                                                                                                                                          
    
    In [207]: test()                                                                                                                                                   
    300
    
    
    
    In [209]: def test(): 
         ...:     print('test:', id(globals()), id(locals())) 
         ...:     exec('print("exec", id(globals()),id(locals()))') 
         ...:                                                                                                                                                          
    
    In [210]: test()                                                                                                                                                   
    test: 4510296176 4537806512
    exec 4510296176 4537806512
    
    In [211]:   
    

    有了操作上下文名字空间的能力,动态代码就可向外部环境注入新的成员,比如说构建新的类型,导入新的算法,最终达到将动态逻辑或其结果融入,成为当前体系组成部分的设计目标。

    In [211]: s =''' 
         ...: class My_X:... 
         ...: def hello(): 
         ...:     print('hello, world') 
         ...:  '''                                                                                                                                                     
    
    In [212]: exec(s)                                                                                                                                                  
    
    In [213]: My_X                                                                                                                                                     
    Out[213]: __main__.My_X
    
    In [214]: hello()                                                                                                                                                  
    hello, world
    
    In [215]:  
    

    某些时候,动态代码来源不确定,基于安全考虑,必须对执行过程进行隔离,阻止其直接读写环境。

    如此,就需显式传入容器对象作为动态代码的专用名字空间,以类似建议沙箱(sandbox)方式执行。

    根据需要,分别提供globals,locals参数,也可公用同一个空间字典。

    为保证代码正确执行,解释器会自动导入__bultins__模块,以便调用内置函数。

    In [215]: g = {'x':100}                                                                                                                                            
    
    In [216]: l = {'y':200}                                                                                                                                            
    
    In [217]: eval('x+y',g,l)                                                                                                                                          
    Out[217]: 300
    
    In [218]:                                                                                                                                                          
    
    In [218]: ns = {}                                                                                                                                                  
    
    In [219]: exec('class XXX:...',ns)                                                                                                                                 
    
    In [220]: ns   
    

     同时提供两个名字空间参数时,默认总是在locals优先。

    In [221]: s = ''' 
         ...: print(x) 
         ...: global y 
         ...: y += 100 
         ...: z = x+y 
         ...: '''                                                                                                                                                      
    
    In [222]: g = {'x':10,'y':20}                                                                                                                                      
    
    In [223]: l = {'x':1000}                                                                                                                                           
    
    In [224]: exec(s,g,l)                                                                                                                                              
    1000
    
    In [226]: l                                                                                                                                                        
    Out[226]: {'x': 1000, 'z': 1120}
    

     前面的s定义中y定义为全局变量,所以没有输出到l里面

    前面提及,在函数作用域内,locals函数总是返回执行栈帧(stack frame)名字空间。

    因此就式显式提供locals名字空间,也无法将其注入倒台代码的函数内

    In [227]: s = ''' 
         ...: print(id(locals())) 
         ...: def test(): 
         ...:     print(id(locals())) 
         ...: test() 
         ...: '''                                                                                                                                                      
    
    In [228]: ns = {}                                                                                                                                                  
    
    In [229]: id(ns)                                                                                                                                                   
    Out[229]: 4536481344
    
    In [230]: ns2 = {}                                                                                                                                                 
    
    In [231]: id(ns2)                                                                                                                                                  
    Out[231]: 4540089840
    
    In [232]: exec(s,ns,ns2)                                                                                                                                           
    4540089840
    4544788512
    
    In [234]: ns2                                                                                                                                                      
    Out[234]: {'test': <function test()>}
    

     明显沙盒传入的,默认情况下,所有信息都保存在locals()里面,除非定义了global,会传入globals()

    内置类型

    与自定义类型(user-defined)相比,内置类型(built-in)算是特权阶层。除了它们是符合数据结构的基本构成单元以外,最重要的式被编译器和解释器特别对待。

    比如核心级别的指令和性能优化,专门设计的高效缓存,等等。

    内置类型主要的有int,float,str,bytes,bytearray,list,tuple,dict,set,frozenset,其中bytearray,list,dict,set为可变类型。

    标准库collections.abc列出了相关类型的抽象基类,可据此判断其基本行为方式

    In [10]: import collections.abc                                                 
    
    In [11]: issubclass(dict, collections.abc.Sequence)                             
    Out[11]: False
    
    In [12]: issubclass(dict, collections.abc.MutableSequence)                      
    Out[12]: False
    
    In [13]: issubclass(dict, collections.abc.Mapping)                              
    Out[13]: True
    
    In [14]:  
    

    整数

    In [15]: import sys                                                             
    
    In [16]: x= 1                                                                   
    
    In [17]: sys.getsizeof(x)                                                       
    Out[17]: 28
    
    In [18]: y=1<<10000                                                             
    
    In [19]: sys.getsizeof(y)                                                       
    Out[19]: 1360
    
    In [20]:    
    

     Python中int的变长结构允许我们创建超大的天文数字,理论上仅收可分配内存大小的限制。

    对于长数字,可以用下划线当做分隔符,且不定位置。

    In [21]: 2_3_4                                                                  
    Out[21]: 234
    
    In [22]: 23_345_123                                                             
    Out[22]: 23345123
    
    In [23]:     
    

     另外进制的也可以用

    In [23]: 0x23_34_12_2                                                           
    Out[23]: 36913442
    
    In [24]: 0b01_10                                                                
    Out[24]: 6
    
    In [25]: 
    

     0b,0x 0o分别代码2进制,16进制,8进制的数字

    转换

    In [31]: eval(bin(100))                                                         
    Out[31]: 100
    
    In [32]: bin(100)                                                               
    Out[32]: '0b1100100'
    
    In [33]: hex(100)                                                               
    Out[33]: '0x64'
    
    In [34]: oct(100)                                                               
    Out[34]: '0o144'
    
    In [35]: int(bin(100),2)                                                        
    Out[35]: 100
    
    In [36]: int(hex(100),16)                                                       
    Out[36]: 100
    
    In [37]: int('  100  	')                                                       
    Out[37]: 100
    
    In [38]: int('  100  
    ')                                                       
    Out[38]: 100
    
    In [39]:        
    

     通过一些命令,可以十进制数字转换为指定的进制字符字符串,也可以通过int还原,第二参数为第一输入参数的进制,输出都式10进制的。

    当然也可以通过eval完成,单相比与直接用C实现的转换函数,其性能要差很多,毕竟动态运行需要额外编译和执行开销。

    还有一种转换操作式将整数转换为字节数组,这常用于二进制网络协议和文件读写。在这里需要指定字节序,也就是常说的大小端。

    目前使用较多的Intel x86 、AMD 64 采用小端。ARM则两种都支持,可自行设定。另外,TCP/IP网络字节,采用大端,这属于协议定义,与硬件结构与操作系统无法。

    In [56]: x = 0x1234                                                             
    
    In [57]: n = (x.bit_length() +8 -1) //8                                         
    
    In [58]: n                                                                      
    Out[58]: 2
    
    In [59]: x=0x1234                                                               
    
    In [60]: n = (x.bit_length() + 8 -1)//8                                         
    
    In [61]: b = x.to_bytes(n, sys.byteorder)                                       
    
    In [62]: b                                                                      
    Out[62]: b'4x12'
    
    In [63]: b.hex()                                                                
    Out[63]: '3412'
    
    In [64]: hex(int.from_bytes(b,sys.byteorder))                                   
    Out[64]: '0x1234'
    

     书中的代码执行完毕以后,我对b的输出其实卡住了一会而,为什么直接输出b是b'4x12',不是应该b'x34x12'吗?

    在Python中对于字节码x的输出,如果x的字节码对应asci码有对应值,x就会自动转换成相应的ASCI字符.

    In [79]: b'x34'                                                                
    Out[79]: b'4'
    
    In [80]:  
    

     在电脑数据中一个字节等于8个bit位,对应的16进制刚好为x12,其中的1为高位的4个bit,2对应为低位的4个bit

    所以x12的一个数据刚好为一个字节,Python中显示器的为Unicode,对应2个字节,16位。这些计算机的基础,我薄弱啊。

    所以在计算机中数据都喜欢用x的形式表示,因为计算机的最小单位为字节,用二进制表示需要8个占位00000000,但转换为16进制,刚好一个x表示。

    In [94]: name = '中'                                                            
    
    In [95]: name                                                                   
    Out[95]: '中'
    
    In [96]: name.encode('gbk')                                                     
    Out[96]: b'xd6xd0'
    
    In [97]: name.encode('utf8')                                                    
    Out[97]: b'xe4xb8xad'
    
    In [98]: ord(name)                                                              
    Out[98]: 20013
    
    In [99]: hex(ord(name))                                                         
    Out[99]: '0x4e2d'
    
    In [100]: 'u4e2d'                                                              
    Out[100]: '中'
    
    In [101]: chr(20013)                                                            
    Out[101]: '中'
    
    In [102]: chr(0x4e2d)                                                           
    Out[102]: '中'
    
    In [103]:   
    

    ord() 函数是 chr() 函数(对于8位的ASCII字符串)或 unichr() 函数(对于Unicode对象)的配对函数,它以一个字符(长度为1的字符串)作为参数,返回对应的 ASCII 数值,或者 Unicode 数值。

    上面的列子中,很好的展示了解码与编码,Python编辑器,对于unicode字符是直接显示的。从字符解码也可以看出来,对于中文的解码,utf8需要三个字节,gbk两个字节,unicode也才两个字节。

    In [103]: 0x123                                                                 
    Out[103]: 291
    
    In [104]: 0b0101001                                                             
    Out[104]: 41
    
    In [105]: 0o5432                                                                
    Out[105]: 2842
    
    In [106]:  
    

     在终端中,无论你输入那种类型的数字,终端会自动转换成10进制的输出,这次学习让我对字符编码又有了更好的认识。

    运算符

    比较有意思的是一个

    In [111]: divmod(5,2)                                                           
    Out[111]: (2, 1)
    

     返回了一个元祖,一个是商,一个属余数。

    布尔

    布尔是整数的子类型,也就是说True和False可被当做数字直接使用

    In [112]: True.__class__                                                        
    Out[112]: bool
    
    In [113]: True.__class__.__mro__                                                
    Out[113]: (bool, int, object)
    
    In [114]:   
    
    In [114]: True == 1                                                             
    Out[114]: True
    
    In [115]: False == 0                                                            
    Out[115]: True
    
    
    In [117]: False + 1                                                             
    Out[117]: 1
    
    In [118]:     
    

    在进行布尔转换时,数字值、空值(None)、空序列,空字典、空集合,等都被视为False

    对于自定义类型,可通过重写__boll__或__len__方法影响bool转换结果。

    枚举

    首相枚举操作的对象是一个类,是类,不时实例对象。Enum是一个类,继承元类EnumMeta

    操作的对象,属于类属性。Enum()调用的是EnumMeta的__call__方法,整个模块有1000行代码,是在没信心看了。用的又是类元编程。

    In [187]: Color = Enum('Color','BLACK, YELLOW BLUE RED')                                                         
    
    In [188]: black = Color.BLACK                                                                                    
    
    In [189]: isinstance(black, Color)                                                                               
    Out[189]: True
    
    In [190]: black.name                                                                                             
    Out[190]: 'BLACK'
    
    In [191]: black.value                                                                                            
    Out[191]: 1
    
    In [192]:  
    

     很有意思,Color的类属性,是Color的实例,实例有两个描述符,name与value

    通过继承的方式草写一遍

    In [192]: class X(Enum): 
         ...:     A = 'a' 
         ...:     B = 100 
         ...:     C = [1,2,3] 
         ...:                                                                                                        
    
    In [193]: X.C                                                                                                    
    Out[193]: <X.C: [1, 2, 3]>
    
    In [194]: X['B']                                                                                                 
    Out[194]: <X.B: 100>
    
    
    In [196]: X('a')                                                                                                 
    Out[196]: <X.A: 'a'>
    
    In [197]:    
    

     可以通过属性查找,也可以通过值key的形式查找,也可以通过实例化放入value的形式查找具体对象。

    如果要避免相同的枚举定义,可用enum.unique装饰器。

    这个枚举的类,我真心觉的没啥用,反正我基本没用到过,真要用,我宁可自己定义个更加好用的类。

    内存

    对于常用的小叔子,解释器会在初始化时进行预缓存。后续使用时,直接将名字关联到这些缓存既可。如此一来,无须创建实例对象,可提高性能,节约内存开销

    Python3.6 预缓存范围是[-5, 256]

    In [197]: a = -5                                                                                                 
    
    In [198]: b= -5                                                                                                  
    
    In [199]: a is b                                                                                                 
    Out[199]: True
    
    In [200]: a= 256                                                                                                 
    
    In [201]: b= 256                                                                                                 
    
    In [202]: a is b                                                                                                 
    Out[202]: True
    
    In [203]: a = 256                                                                                                
    
    In [204]: a = 257                                                                                                
    
    In [205]: b = 257                                                                                                
    
    In [206]: a is b                                                                                                 
    Out[206]: False
    
    In [207]:   
    

    Python2对回收后的整数复用不做收缩处理,会导致大量闲置内存驻留。而Python3则改进了不少。

    from __future__ import  print_function
    import psutil
    
    
    def rss():
        m = psutil.Process().memory_info()
        print(m.rss >> 20, 'MB')
    
    if __name__ == '__main__':
        rss()
        x = list(range(10000000))
        rss()
        del x
        rss()
    

     运行结果

    shijianzhongdeMacBook-Pro:第二章类型 shijianzhong$ python t2_2.py 
    7 MB
    394 MB
    394 MB
    shijianzhongdeMacBook-Pro:第二章类型 shijianzhong$ python3 t2_2.py 
    8 MB
    394 MB
    84 MB
    

    浮点数

    默认float类型存储双精度(double)浮点数,可表达16到17个小数位

    从实现方式看,浮点数以二进制存储十进制数的近似值。这可能导致执行结果和编码预期不符,造成不一致缺陷,所以对精度有严格要求的地方,应选择固定精度类型。

     可以通过float.hex方式输出实际存储值的十六进制格式字符串,以检查执行的结果为何不同,还可以用该方式实现浮点数的精确传递,避免精度丢失。

    In [9]: 0.1 * 3 == 0.3                                                                                                                                            
    Out[9]: False
    
    In [10]: (0.1 * 3).hex()                                                                                                                                          
    Out[10]: '0x1.3333333333334p-2'
    
    In [11]: 0.3.hex()                                                                                                                                                
    Out[11]: '0x1.3333333333333p-2'
    
    In [12]:                                                                                                                                                          
    
    In [12]: s = (1/3).hex()                                                                                                                                          
    
    In [13]: s                                                                                                                                                        
    Out[13]: '0x1.5555555555555p-2'
    
    In [14]: float.fromhex(s)                                                                                                                                         
    Out[14]: 0.3333333333333333
    
    In [15]:  
    

     对于简单操作可以使用round进行精度控制

    将数字或者字符串转换为浮点数,只要float函数一下就可以,能正确处理正负符号与空白符

    通过math模块内的一部分函数处理小数

    In [21]: from math import trunc, floor, ceil                                                                                                                      
    
    In [22]: trunc(2.6),trunc(-2.6)                                                                                                                                   
    Out[22]: (2, -2)
    
    In [23]: floor(2.6),floor(-2.6)                                                                                                                                   
    Out[23]: (2, -3)
    
    In [24]: ceil(2.6), ceil(-2.6)                                                                                                                                    
    Out[24]: (3, -2)
    
    In [25]:  
    

     十进制浮点数

    decaimail.Decimail是十进制实现,最高可提供28位有效精度。其能准确表达十进制数合运算,不存在二进制近似值问题。

    In [25]: 1.1+2.2                                                                                                                                                  
    Out[25]: 3.3000000000000003
    
    In [26]: from decimal import Decimal                                                                                                                              
    
    In [27]: Decimal('1.1') + Decimal(2.2)                                                                                                                            
    Out[27]: Decimal('3.300000000000000177635683940')
    
    In [28]: Decimal('1.1') + Decimal('2.2')                                                                                                                          
    Out[28]: Decimal('3.3')
    
    In [29]:      
    

     在创建Decimail实例的时候,应该传入一个准确的值,比较整数或者字符串。如果是float类型的,那么在构建之前,其精度就已丢失。

    通过设置上下文环境修改Decimail默认的28位精度

    In [29]: from decimal import getcontext                                                                                                                           
    
    In [30]: getcontext()                                                                                                                                             
    Out[30]: Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[Inexact, FloatOperation, Rounded], traps=[InvalidOperation, DivisionByZero, Overflow])
    
    In [31]: getcontext().prec=2                                                                                                                                      
    
    In [32]: Decimal(1.99)/Decimal(9)                                                                                                                                 
    Out[32]: Decimal('0.22')
    
    In [33]:  
    

    或者用localcontext限制指定区域的精度

    In [33]: from decimal import localcontext                                                                                                                         
    
    In [34]: with localcontext() as ctx: 
        ...:     ctx.prec=2 
        ...:     print(getcontext()) 
        ...:     print(Decimal(1)/Decimal(3)) 
        ...:                                                                                                                                                          
    Context(prec=2, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[Inexact, FloatOperation, Rounded], traps=[InvalidOperation, DivisionByZero, Overflow])
    0.33
    

     除非有明确需求,否则不要用Decimail替代float,要知道其运行速度会慢许多

    四舍五入

    Python中的round存咋很大的不确定性,特别是末尾是五

    Python是按临近数字距离远近来考虑是否位,就最后一个进位数跟1对比,是否靠近1,所有除了5的尾数,另外都不存在问题

    末尾是5的小数,返回整数,就取偶数位的整数

    In [67]: round(0.5)                                                                                                                                               
    Out[67]: 0
    
    In [68]: round(1.5)                                                                                                                                               
    Out[68]: 2
    
    In [69]: round(2.5)                                                                                                                                               
    Out[69]: 2
    
    In [70]: round(4.5)                                                                                                                                               
    Out[70]: 4
    
    In [71]:     
    

     但如果返回的还是小数,就比较莫名其妙了

    In [71]: round(1.25,1)                                                                                                                                            
    Out[71]: 1.2
    
    In [72]: round(1.245,1)                                                                                                                                           
    Out[72]: 1.2
    
    In [73]: round(1.275,2)                                                                                                                                           
    Out[73]: 1.27
    
    In [74]: round(1.375,2)                                                                                                                                           
    Out[74]: 1.38
    
    In [75]:  
    

     可以用Decimail来控制进位方案

    def roundx(x, n):
        return Decimal(x).quantize(Decimal(n), ROUND_HALF_UP)
    print(roundx('1.245', '.01'))
    

    字符串

    Unicode是为整合全世界的所有语言文字而诞生的。任何文字在Unicode中都对应一个值,这个值称为代码点(code point)。代码点的值通常写成 U+ABCD 的格式。而文字和代码点之间的对应关系就是UCS-2(Universal Character Set coded in 2 octets)。顾名思义,UCS-2是用两个字节来表示代码点,其取值范围为 U+0000~U+FFFF。

    为了能表示更多的文字,人们又提出了UCS-4,即用四个字节表示代码点。它的范围为 U+00000000~U+7FFFFFFF,其中 U+00000000~U+0000FFFF和UCS-2是一样的。

    要注意,UCS-2和UCS-4只规定了代码点和文字之间的对应关系,并没有规定代码点在计算机中如何存储。规定存储方式的称为UTF(Unicode Transformation Format),其中应用较多的就是UTF-16和UTF-8了。

     

    In [108]: ascii('你好')                                                                                                                                           
    Out[108]: "'\u4f60\u597d'"
    
    In [109]: eval( "'\u4f60\u597d'")                                                                                                                               
    Out[109]: '你好'
    

     

    In [105]: hex(ord('汉'))                                                                                                                                          
    Out[105]: '0x6c49'
    
    
    In [107]: chr(0x6c49)                                                                                                                                             
    Out[107]: '汉'
    

    Unicode格式的大小写分别表示16位(u)和32位(U)整数,不能混用

    In [112]: 'hx69,u6C49U00005B57'                                                                                                                                
    Out[112]: 'hi,汉字'
    
    In [113]:   
    

    字符串支持+与*运算,编译器在编译器直接计算出字面量拼接结果,可避免运行时开销

    至于多个动态字符串串拼接,应优选join或format方式

    相比与多次加法运算和多次内存分配(字符串是不可变对象),join这类函数(方法)可预先计算出总长度,一次性分配内存,随后直接复制内存数据参数。

    另一方案,讲固定模板内容与变量分离的format更容易阅读.

    书中有一个测试,该模块等后期用到,再回来使用。

    字符串切片无论返回与原字符串不同的子串时,都可能会重新分配内存,并估值数据。

    这个也是蛮有意思的一段示例。

    In [121]: s = '-'*1024                                                                                                                                            
    
    In [122]: s1 = s[10:100]                                                                                                                                          
    
    In [123]: s2= s[:]                                                                                                                                                
    
    In [124]: s3 = s.split()[0]                                                                                                                                       
    
    In [125]: s1 is s                                                                                                                                                 
    Out[125]: False
    
    In [126]: s2 is s                                                                                                                                                 
    Out[126]: True
    
    In [127]: s3 is s                                                                                                                                                 
    Out[127]: True
    
    In [128]: 
    

    格式化

    Python的format格式化

    标准格式说明符 的一般形式如下:

    format_spec     ::=  [[fill]align][sign][#][0][width][grouping_option][.precision][type]
    fill            ::=  <any character>
    align           ::=  "<" | ">" | "=" | "^"
    sign            ::=  "+" | "-" | " "
    width           ::=  digit+
    grouping_option ::=  "_" | ","
    precision       ::=  digit+
    type            ::=  "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "

    中文注释下吧,第一个参数填充字符对齐,sign数字符号#格式前缀 填充,width宽度千分位,小数长度,类型。

    后续的Python,format用起来,%号格式化已经不行了。

    池化

    Python的做法是实现一个字符串池化

    池负责管理实例,使用者只需引用既可。另一潜在的好处是,从池返回的字符串,只需比较指针就可知道内容是否相同,无须额外计算。

    可以用池来提升哈希表等类似结构的查找性能。

    除以常量方式出现的名字和字面量外,动态生成的字符串一样可假如池中。如此可保证每次都引用同一个对象。

    普通的字符串,中间不加空格也会放入池中

    In [136]: a = '__name__'                                                                                                                                          
    
    In [137]: b = '__name__'                                                                                                                                          
    
    In [138]: a is b                                                                                                                                                  
    Out[138]: True
    
    In [139]: a = '123'                                                                                                                                               
    
    In [140]: b = '123'                                                                                                                                               
    
    In [141]: a is b                                                                                                                                                  
    Out[141]: True
    
    In [142]:   
    

    默认的中文字符不会假如池中

    In [142]: a = '你好'                                                                                                                                              
    
    In [143]: b = '你好'                                                                                                                                              
    
    In [144]: a is b                                                                                                                                                  
    Out[144]: False
    
    In [145]: a = 'hello, world'                                                                                                                                      
    
    In [146]: b = 'hello, world'                                                                                                                                      
    
    In [147]: a is b                                                                                                                                                  
    Out[147]: False
    
    In [148]: import sys                                                                                                                                              
    
    In [152]: a = sys.intern('hello, world')                                                                                                                          
    
    In [153]: b = sys.intern('hello, world')                                                                                                                          
    
    In [154]: a is b                                                                                                                                                  
    Out[154]: True
    
    In [155]:     
    
    a = sys.intern('hello, world!')
    id(a)
    4560821168
    id(sys.intern('hello, world!'))
    4560821168
    del a
    id(sys.intern('hello, world!'))
    4563113008
    

     一旦失去所有外部引用,池内的字符串对象一样会被回收

    字节数组

    从底层实现来说,所有的数据都是二进制的字节序列。

    In [157]: bytes('abc','utf8')                                                                                                                                     
    Out[157]: b'abc'
    
    In [158]: bytes('中国', 'gbk')                                                                                                                                    
    Out[158]: b'xd6xd0xb9xfa'
    
    In [159]:  
    

     bytes支持很多字符串的方法。

    相比较于bytes的一次性内存分配,bytearray可按需扩张,更适合作为可读写缓冲区使用。如有必要,还可为其提前分配足够的内存,避免中途扩展造成额外消耗。

    In [169]: b = bytearray(b'ab')                                                                                                                                    
    
    In [170]: len(b)                                                                                                                                                  
    Out[170]: 2
    
    In [171]: b.append(ord('c'))                                                                                                                                      
    
    In [172]: b                                                                                                                                                       
    Out[172]: bytearray(b'abc')
    
    In [173]: b.append(b'c')                                                                                                                                          
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-173-316c1313f7e0> in <module>
    ----> 1 b.append(b'c')
    
    TypeError: 'bytes' object cannot be interpreted as an integer
    
    In [174]: b.extend(b'lll')                                                                                                                                        
    
    In [175]: b                                                                                                                                                       
    Out[175]: bytearray(b'abclll')
    
    In [176]:  
    

     从可变字节可以看出,需要添加字节还是蛮有意思的,append的时候,只能用过ord添加,字节的编码还是十进制的,通过extend扩展可变字节编码

    同样还支持+*运算符。

    内存视图

    为什么要引用使用内存视图,引用某个片段,而不是整个对象?

    以自定义网络协议位例,通常由标准头和数据体两部分组成。如要验证数据是否被修改,总不能将整个包作为参数交给验证函数。这势必要求该函数了解协议包结构,这显然不合理的设计。

    而复制数据体又可能导致重大性能开销,同样得不偿失。

    内存视图要求目标对象支持缓冲协议(Buffer Protocol)。它直接引用目标内存,没有额外复制行为。因此,可读取最新数据。在目标对象允许的情况下,还可执行写操作。

    常见支持视图操作的又bytes,bytearray,array.array,以及NumPy的某些类型

    In [194]: a = list('亲爱的'.encode())                                                                                                                             
    
    In [195]: b = bytearray(a)                                                                                                                                        
    
    In [196]: b                                                                                                                                                       
    Out[196]: bytearray(b'xe4xbaxb2xe7x88xb1xe7x9ax84')
    
    In [197]: v= memoryview(b)                                                                                                                                        
    
    In [198]: x = v[2:5]                                                                                                                                              
    
    In [199]: v                                                                                                                                                       
    Out[199]: <memory at 0x10c91a050>
    
    In [200]: x                                                                                                                                                       
    Out[200]: <memory at 0x10c91a120>
    
    In [201]: x.hex()                                                                                                                                                 
    Out[201]: 'b2e788'
    
    In [202]: b[3]=0x99                                                                                                                                               
    
    In [203]: x.hex()                                                                                                                                                 
    Out[203]: 'b29988'
    
    In [204]: x[1] = 0x88                                                                                                                                             
    
    In [205]: b                                                                                                                                                       
    Out[205]: bytearray(b'xe4xbaxb2x88x88xb1xe7x9ax84')
    
    
    In [207]: b[4]=0xaa                                                                                                                                               
    
    In [208]: b                                                                                                                                                       
    Out[208]: bytearray(b'xe4xbaxb2x88xaaxb1xe7x9ax84')
    
    In [209]: x.hex()                                                                                                                                                 
    Out[209]: 'b288aa'
    
    In [210]:  
    

     如果要复制视图数据,可调用tobytes,tolist方法。复制后的数据与原对象无关,同样不会影响视图自身

    In [213]: b                                                                                                                                                       
    Out[213]: bytearray(b'xe4xbaxb2x88xaaxb1xe7x9ax84')
    
    In [214]: v = memoryview(b)                                                                                                                                       
    
    In [215]: x= v[2:5]                                                                                                                                               
    
    In [216]: z = x.tobytes()                                                                                                                                         
    
    In [217]: z.hex()                                                                                                                                                 
    Out[217]: 'b288aa'
    
    In [218]: z1=0xab                                                                                                                                                 
    
    In [219]: z.hex()                                                                                                                                                 
    Out[219]: 'b288aa'
    
    
    In [221]: z                                                                                                                                                       
    Out[221]: b'xb2x88xaa'
    
    In [222]: x                                                                                                                                                       
    Out[222]: <memory at 0x10c91a1f0>
    
    In [223]: x.tolist()                                                                                                                                              
    Out[223]: [178, 136, 170]
    
    In [224]:  
    

     给自己脑子一点记心,对话模式下,输入0b,0x,0o都会默认转换为10进制输出。

    列表

    列表的内部结构由两部分组成,保存元素数量和内存分配计数的头部,以及存储元素指针的独立数组

    所有元素是有那个该数组保存指针引用,并不嵌入实际内容。

    构建

    要实现自定义的列表,建议基于collection.UserList包装

    In [234]: list.__mro__                                                                                                                                            
    Out[234]: (list, object)
    
    In [235]: collections.UserList.__mro__                                                                                                                            
    Out[235]: 
    (collections.UserList,
     collections.abc.MutableSequence,
     collections.abc.Sequence,
     collections.abc.Reversible,
     collections.abc.Collection,
     collections.abc.Sized,
     collections.abc.Iterable,
     collections.abc.Container,
     object)
    
    In [236]:   
    
    In [239]: class A(list): 
         ...:     ... 
         ...:                                                                                                                                                         
    
    In [240]: type(A('abc')+list('abc'))                                                                                                                              
    Out[240]: list
    
    In [241]: class B(collections.UserList): 
         ...:     ... 
         ...:                                                                                                                                                         
    
    In [242]: type(B('abc')+list('abc'))                                                                                                                              
    Out[242]: __main__.B
    

     最小设计接口是个基本原则,应慎重考虑列表这种功能丰富的类型是否适合作为基类。

    列表的切片擦欧哦组,创建新列表对象,并复制相关指针数据到新数组。除所引用目标相同外,对列表自身的修改(插入、删除等)互不影响

    In [244]: a = [0,2,4,6]                                                                                                                                           
    
    In [245]: b = a[:2]                                                                                                                                               
    
    In [246]: a[0] is b[0]                                                                                                                                            
    Out[246]: True
    
    In [247]: a                                                                                                                                                       
    Out[247]: [0, 2, 4, 6]
    
    In [248]: b                                                                                                                                                       
    Out[248]: [0, 2]
    
    In [250]: b.insert(0,99)                                                                                                                                          
    
    In [251]: a                                                                                                                                                       
    Out[251]: [0, 2, 4, 6]
    
    In [252]:   
    

     要注意,前面赋值属于浅拷贝。

    In [260]: a = [[1,2],[3,4]]                                                                                                                                       
    
    In [261]: b = a[::-1]                                                                                                                                             
    
    In [262]: b                                                                                                                                                       
    Out[262]: [[3, 4], [1, 2]]
    
    In [263]: a is b                                                                                                                                                  
    Out[263]: False
    
    In [264]: a[0].append(99)                                                                                                                                         
    
    In [265]: b                                                                                                                                                       
    Out[265]: [[3, 4], [1, 2, 99]]
    
    In [266]: a.insert(0,0)                                                                                                                                           
    
    In [267]: a                                                                                                                                                       
    Out[267]: [0, [1, 2, 99], [3, 4]]
    
    In [268]: b                                                                                                                                                       
    Out[268]: [[3, 4], [1, 2, 99]]
    
    In [269]:   
    

     reveserd记住,跟[::-1]一样,返回的列表属于原数据的浅拷贝数据

    利用bisect模块,可向有序列表插入元素。它使用二分查找适合位置,可用来实现优先级队列或一致性哈希算法

    In [281]: d = [0,2,4]                                                                                                                                             
    
    In [282]: from bisect import bisect                                                                                                                               
    
    In [283]: import bisect                                                                                                                                           
    
    In [284]: bisect.insort_left(d,1)                                                                                                                                 
    
    In [285]: d                                                                                                                                                       
    Out[285]: [0, 1, 2, 4]
    
    In [286]: bisect.insort_left(d,2)                                                                                                                                 
    
    In [287]: d                                                                                                                                                       
    Out[287]: [0, 1, 2, 2, 4]
    
    In [288]: bisect.insort_left(d,3)                                                                                                                                 
    
    In [289]: d                                                                                                                                                       
    Out[289]: [0, 1, 2, 2, 3, 4]
    
    In [290]:   
    

     元祖

    没啥好些的,上一个概念

    元祖是不可变类型,它的指针数组无须变动,故一次性完成内存分配。系统会缓存复用一定长度的元祖内存(含指针数组)。

    创建时,按所需长度提取付用法,没有额外内存分配。从这点上来看,元祖的性能要好于列表

    py3.6缓存复用长度在20以内的tuple内存,每种2000上限。

    数组

    数组和列表、元祖的本质区别在于:元素单一类型和内容嵌入

    In [290]: import array                                                                                                                                            
    
    In [291]: a = array.array('b',range(5))                                                                                                                           
    
    In [292]: a                                                                                                                                                       
    Out[292]: array('b', [0, 1, 2, 3, 4])
    
    In [293]: memoryview(a).hex()                                                                                                                                     
    Out[293]: '0001020304'
    
    In [294]:  
    

    上面显示了内容嵌入

    In [294]: a = array.array('i')                                                                                                                                    
    
    In [295]: a.append(100)                                                                                                                                           
    
    In [296]: a                                                                                                                                                       
    Out[296]: array('i', [100])
    
    In [297]: a.append(1.23)                                                                                                                                          
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-297-8779bdd8af29> in <module>
    ----> 1 a.append(1.23)
    
    TypeError: integer argument expected, got float
    
    In [298]:  
    

     可直接存储包括Unicode字符在内的各种数字

    符合类型须用struct、marshal、pickle等转换为二进制字节后再行存储

    与列表类似,数组长度不固定,按需扩张或收缩内存。

    In [298]: a = array.array('i',range(1,4))                                                                                                                         
    
    In [299]: a                                                                                                                                                       
    Out[299]: array('i', [1, 2, 3])
    
    In [300]: a.buffer_info()                                                                                                                                         
    Out[300]: (4512054016, 3)
    
    In [301]: a.extend(range(10000000))                                                                                                                               
    
    In [302]: a.buffer_info()                                                                                                                                         
    Out[302]: (4526223360, 10000003)
    
    In [303]:  
    

     由于可指定更紧凑的数字类型,故数组可节约更多内存。再者,内容嵌入也避免了对象的额外开销,减少了活跃的数量和内存分配的次数。

    字典

    字典是内置类型中唯一的映射(mapping)结构,基于哈希表存储键值对数据。

    这里要注意,有些对象可能又__hash__方法,但无法执行

    In [305]: callable([].__hash__)                                                                                                                                   
    Out[305]: False
    
    In [306]:  
    

     自定义数据默认实现了__hash__和__eq__方法,用于哈希和相等比较操作。前者为每个实例返回随机值,后者除非与自己比较,否则总是返回False。这里可根据需要重载

    Python3.6借鉴PyPy字典设计,采用更紧凑的存储结构。keys.entries和values用数组添加顺序存储主键和值引用。实际哈希表由keys.indices数组承担,通过计算主键哈希值找到合适的位置,然后在此存储主键在keys.entries的实际索引。如此一来,只要通过indices获取实际索引后,就可读取主键和值信息了。

    构建

    书中比较骚的构建方式吧

    In [306]: dict(zip('abc',range(3)))                                                                                                                               
    Out[306]: {'a': 0, 'b': 1, 'c': 2}
    
    In [307]: dict(map(lambda k,v:(k,v + 10),'abc',range(3)))                                                                                                         
    Out[307]: {'a': 10, 'b': 11, 'c': 12}
    
    In [308]: {k:v+10 for k,v in zip('abc', range(5))}                                                                                                                
    Out[308]: {'a': 10, 'b': 11, 'c': 12}
    
    In [309]:  
    

    还有一些扩展以及初始化零值

    In [309]: a = {'a':1}                                                                                                                                             
    
    In [310]: b = dict(a,b='3')                                                                                                                                       
    
    In [311]: b                                                                                                                                                       
    Out[311]: {'a': 1, 'b': '3'}
    
    In [312]: c = dict.fromkeys(range(3),0)                                                                                                                           
    
    In [313]: c                                                                                                                                                       
    Out[313]: {0: 0, 1: 0, 2: 0}
    
    In [314]:    
    

     操作还是比较常规的,没啥好些的

    视图

    Python3默认以视图关联字典内容,如此一来即能避免复制开销,还能同步观察字典变化。

    In [314]: x = dict(a=1,b=2)                                                                                                                                       
    
    In [315]: ks=x.keys()                                                                                                                                             
    
    In [316]: 'b' in ks                                                                                                                                               
    Out[316]: True
    
    In [317]: for k in ks: print(k,x[k])                                                                                                                              
    a 1
    b 2
    
    In [318]:                                                                                                                                                         
    
    In [318]: x['b']=200                                                                                                                                              
    
    In [319]: x['c'] = 3                                                                                                                                              
    
    In [320]: for k in ks: print(k,x[k])                                                                                                                              
    a 1
    b 200
    c 3
    
    In [321]:                                                                                                                                                         
    

    字典没有独立的只读版本,无论传递引用还是复制品,都存在弊端。直接引用有被接收方修改内容的风险,而复制品又仅是一次性快照,无法获知字典变化。

    视图则不同,它能同步字典内容,却无法修改。且可选择不同粒度的内容进行传递,如果可讲接收方限定位指定模式下的观察员。

    视图还支持集合运算,以弥补字典功能上的不足。

    In [321]: a = dict(a = 1, b= 2)                                                                                                                                   
    
    In [322]: b = dict(c = 3, b =2)                                                                                                                                   
    
    In [323]: ka = a.keys()                                                                                                                                           
    
    In [324]: kb = b.keys()                                                                                                                                           
    
    In [325]: ka                                                                                                                                                      
    Out[325]: dict_keys(['a', 'b'])
    
    In [326]: kb                                                                                                                                                      
    Out[326]: dict_keys(['c', 'b'])
    
    In [327]: ka&kb                                                                                                                                                   
    Out[327]: {'b'}
    
    In [328]: ka|kb                                                                                                                                                   
    Out[328]: {'a', 'b', 'c'}
    
    In [329]: ka-kb                                                                                                                                                   
    Out[329]: {'a'}
    
    In [330]: ka^kb                                                                                                                                                   
    Out[330]: {'a', 'c'}
    
    In [331]:  
    

     利用视图集合愿算,可简化某些操作。列如,只更新,不新增

    In [338]: a = dict(a=1,b=2)                                                                                                                                       
    
    In [339]: b= dict(b=20,c=3)                                                                                                                                       
    
    In [340]: ks = a.keys()&b.keys()                                                                                                                                  
    
    In [341]: a.update({k:b[k] for k in ks})                                                                                                                          
    
    In [342]: a                                                                                                                                                       
    Out[342]: {'a': 1, 'b': 20}
    
    In [343]:    
    

    扩展

    defaultdict默认字典类似于setdefault包装。当主键不存在时,调用构造参数提供的工厂函数返回默认值。

    In [343]: from collections import defaultdict                                                                                                                     
    
    In [344]: d = defaultdict(lambda:100)                                                                                                                             
    
    In [345]: d.get(100)                                                                                                                                              
    
    In [346]: d                                                                                                                                                       
    Out[346]: defaultdict(<function __main__.<lambda>()>, {})
    
    In [347]: d.get('a')                                                                                                                                              
    
    In [348]: d                                                                                                                                                       
    Out[348]: defaultdict(<function __main__.<lambda>()>, {})
    
    In [349]: d['a']                                                                                                                                                  
    Out[349]: 100
    
    In [350]: d['b'] +=1                                                                                                                                              
    
    In [351]: d                                                                                                                                                       
    Out[351]: defaultdict(<function __main__.<lambda>()>, {'a': 100, 'b': 101})
    
    In [352]:       
    

     有序字典OrderedDict,Counter起作用的时__missing__,包括defaultdict

    链式字典(ChainMap)以单一接口访问多个字典内容,其自身并不存储数据。读操作按参数顺序依次查找各字典,但修改操作(新增,更新,删除)仅针对第一字典

    In [352]: a = dict(a=1,b=2)                                                                                                                                       
    
    In [353]: b = dict(b=20,c=30)                                                                                                                                     
    
    In [354]: x= collections.ChainMap(a,b)                                                                                                                            
    
    In [355]: x                                                                                                                                                       
    Out[355]: ChainMap({'a': 1, 'b': 2}, {'b': 20, 'c': 30})
    
    In [356]: x['a']                                                                                                                                                  
    Out[356]: 1
    
    In [357]: x['c']                                                                                                                                                  
    Out[357]: 30
    
    In [358]: x['b']                                                                                                                                                  
    Out[358]: 2
    
    In [359]: for k,v in x.items():print(k,v)                                                                                                                         
    b 2
    c 30
    a 1
    
    In [360]: x['b']=99                                                                                                                                               
    
    In [361]: x['z'] = 10                                                                                                                                             
    
    In [362]: x                                                                                                                                                       
    Out[362]: ChainMap({'a': 1, 'b': 99, 'z': 10}, {'b': 20, 'c': 30})
    
    In [363]:  
    

     可利用链式字典设计多层次上下文(context)结构。

    合理上下文类型,须具备两个基本特性。首先是继承,所有设置可被调用链的后续函数读取。其次是修改仅针对当前和后续逻辑,不应向无关的父级传递。如此,链式字典查找次序本身就是继承体现。

    而修改操作被限制在当前第一字典中,自然也不会影响父级字典的同名主键设置。

    In [363]: root = collections.ChainMap({'a':1})                                                                                                                    
    
    In [365]: child= root.new_child({'b':200})                                                                                                                        
    
    In [366]: child['a'] = 100                                                                                                                                        
    
    In [367]: root                                                                                                                                                    
    Out[367]: ChainMap({'a': 1})
    
    In [368]: child.parents                                                                                                                                           
    Out[368]: ChainMap({'a': 1})
    
    In [369]: root['c'] = 5                                                                                                                                           
    
    In [370]: child.parents                                                                                                                                           
    Out[370]: ChainMap({'a': 1, 'c': 5})
    
    In [371]:      
    

     还是非常有意思的,多链可以两个相同key的错觉,或者child是后续的上下文,不影响前面的上下文

    集合

    集合存储非重复对象。

    如果不是同一个对象,先比较哈希值,然后在比较内容。

    实现都是用数组实现的哈希表存储元素对象引用,这也就要求元素必须位可哈希类型。

    创建

    比较简单不介绍

    操作

    支持大小,相等运算符号

    In [372]: {1,2} >{2,1}                                                                                                                                            
    Out[372]: False
    
    In [373]: {1,2} >{0,1}                                                                                                                                            
    Out[373]: False
    
    
    In [375]: {1,2,3} == {3,1,2}                                                                                                                                      
    Out[375]: True
    
    In [376]:    
    

     子集,超集的判断可以用<= 或者>=

    In [376]: {1,2,3}<={1,2,3,4}                                                                                                                                      
    Out[376]: True
    

     交集,并集,差集 对称差集 & | - ^

    集合愿算还可与更新操作一期使用

    |=, &=这种

    删除操作

    remove,如果没有该元素会报错

    用discard不会报错,pop也可以随机弹出对象

    In [381]: {i for i in range(10)}                                                                                                                                  
    Out[381]: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    In [382]: a = {i for i in range(10)}                                                                                                                              
    
    In [383]: a.remove(11)                                                                                                                                            
    ---------------------------------------------------------------------------
    KeyError                                  Traceback (most recent call last)
    <ipython-input-383-2f6a2b387944> in <module>
    ----> 1 a.remove(11)
    
    KeyError: 11
    
    In [384]: a.discard(11)                                                                                                                                           
    
    In [385]: a.discard(18)                                                                                                                                           
    
    In [386]: a.discard(8)                                                                                                                                            
    
    In [387]: a                                                                                                                                                       
    Out[387]: {0, 1, 2, 3, 4, 5, 6, 7, 9}
    
    In [388]: a.pop()                                                                                                                                                 
    Out[388]: 0
    
    In [389]:  
    

     自定义对象类型

    由于默认继承object的__hash__每个对象的返回值不一样,所以要踢出类的不同实例,可以子集定义__hash__与__eq__

    In [392]: u1 = User('1','user1')                                                                                                                                  
    
    In [393]: u2 = User('1', 'user2')                                                                                                                                 
    
    In [394]: u1 is u2                                                                                                                                                
    Out[394]: False
    
    In [395]: s = set()                                                                                                                                               
    
    In [396]: s.add(u1)                                                                                                                                               
    
    In [397]: s.add(u2)                                                                                                                                               
    
    In [398]: s                                                                                                                                                       
    Out[398]: {<__main__.User at 0x1105f5410>}
    
    In [399]: id(u1)                                                                                                                                                  
    Out[399]: 4569650192
    
    In [400]: id(u2)                                                                                                                                                  
    Out[400]: 4568741456
    
    In [401]: u1 in s                                                                                                                                                 
    Out[401]: True
    
    In [402]: u2 in s                                                                                                                                                 
    Out[402]: True
    
    In [403]:   
    

     这里我有点把id也就是对象的内存地址,与__hash__差有点搞混,id要一样的话,要写单例模式了。

    __hasn__可以在去重上面,当然id一样的化,__hash__跟__eq__肯定一样了。

    当然__hash__跟__eq__主要用在去重上面,从上面可以看到由于两个对象的__hash__值相等,就可以直接在set()集合里面去重,只要重写好__hash__与__eq__的条件。

    草草的学下来,Python内置的几大标准元素内部套路还是很多的,让我学到不到技巧。

     

     

  • 相关阅读:
    hdu 4460spfa用map来实现
    hdu 2579
    hdu 2845
    hdu 4462
    hdu 4557
    hdu 4639
    URAL 2078 Bowling game
    UVA
    HDU 5773 The All-purpose Zero 脑洞LIS
    Codeforces Round #368 (Div. 2) C. Pythagorean Triples 数学
  • 原文地址:https://www.cnblogs.com/sidianok/p/12691438.html
Copyright © 2011-2022 走看看