《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|\x01\x17\x00S\x00'

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  \t')                                                       
Out[37]: 100

In [38]: int('  100  \n')                                                       
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'4\x12'

In [63]: b.hex()                                                                
Out[63]: '3412'

In [64]: hex(int.from_bytes(b,sys.byteorder))                                   
Out[64]: '0x1234'

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

在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'\xd6\xd0'

In [97]: name.encode('utf8')                                                    
Out[97]: b'\xe4\xb8\xad'

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|\x01\x17\x00S\x00'

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  \t')                                                       
Out[37]: 100

In [38]: int('  100  \n')                                                       
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'4\x12'

In [63]: b.hex()                                                                
Out[63]: '3412'

In [64]: hex(int.from_bytes(b,sys.byteorder))                                   
Out[64]: '0x1234'

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

在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'\xd6\xd0'

In [97]: name.encode('utf8')                                                    
Out[97]: b'\xe4\xb8\xad'

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]: 'h\x69,\u6C49\U00005B57'                                                                                                                                
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'\xd6\xd0\xb9\xfa'

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'\xe4\xba\xb2\xe7\x88\xb1\xe7\x9a\x84')

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'\xe4\xba\xb2\x88\x88\xb1\xe7\x9a\x84')


In [207]: b[4]=0xaa                                                                                                                                               

In [208]: b                                                                                                                                                       
Out[208]: bytearray(b'\xe4\xba\xb2\x88\xaa\xb1\xe7\x9a\x84')

In [209]: x.hex()                                                                                                                                                 
Out[209]: 'b288aa'

In [210]:  

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

In [213]: b                                                                                                                                                       
Out[213]: bytearray(b'\xe4\xba\xb2\x88\xaa\xb1\xe7\x9a\x84')

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'\xb2\x88\xaa'

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内置的几大标准元素内部套路还是很多的,让我学到不到技巧。

 

 

 

posted @ 2020-04-13 15:03  就是想学习  阅读(339)  评论(0编辑  收藏  举报