你所需要的,不仅仅是一个好用的代理。
示例运行环境: CPython 3.6.1, macOS 10.12.5
鉴于不同运行环境差异,示例输出结果会有所不同。尤其是 id,以及内存地址等信息。 请以实际运行结果为准。
仅从操作方式上看,列表 (list) 像是数组和链表的综合体。除按索引访问外,还支持插入、追加、删除等操作,完全可视作队列 (queue) 或栈 (stack)结构使用。如不考虑性能问题,似乎是一种易用且功能完善的理想数据结构。
>> > x = [ 1 , 2 ]
>> > x [ 1 ]
2
>> > x . insert ( 0 , 0 )
>> > x
[ 0 , 1 , 2 ]
>> > x . reverse ( )
>> > x
[ 2 , 1 , 0
]
queue
>> > q = [ ]
>> > q . append ( 1 ) # 向队列追加数据。
>> > q . append ( 2 )
>> > q . pop ( 0 ) # 按追加顺序弹出数据。
1
>> > q . pop ( 0 )
2
对于有大量写操作的专职队列或栈,建议使用 collections.deque、queue 等类型。
列表内部结构由两部分组成,头部保存元素和内存分配计数,另引用独立指针数组。所有列表项(item) 通过该数组保存指针引用,并不会嵌入元素实际内容。
作为使用频率最高的数据结构之一,列表的性能优化很重要。那么,固定长度的头部结构, 很容易实现内存复用。创建时,优先从复用区获取。而当列表被回收,除非超出最大复用数量限制(默认 80),否则会被放回复用区,而不是交还内存。每次真正需要分配和释放内存的是指针数组。
用数组而非链表存储元素项引用,也有实际考虑。从读操作看,无论遍历还是基于序号访问,数组性能总是最高。尽管插入、删除等变更操作需移动内存,但也仅仅是指针复制,无关元素大小,不会有太高消耗。如果列表太大,或写操作远多于读,那么应当使用针对性的数据结构,而非通用设计的内置列表类型。
另外,指针数组内存分配算法基于元素数量和剩余空间大小,按相应比率进行扩容或收缩, 而非逐项进行。如此,可避免太频繁的内存分配操作。
用方括号指定显式元素的构建语法最为常用。当然,也可基于类型创建实例,接收一个可迭代对象作为初始内容。不同于数组,列表仅存储指针,对元素类型并不关心,故可以是不同类型混合。
>> > [ 1 , "abc" , 3.14 ]
[ 1 , 'abc' , 3.14 ]
>> > list ( "abc" ) # iterable
[ 'a' , 'b' , 'c'
]
另有一种称做推导式 (comprehension) 的语法。同样用方括号,但以 for 循环初始化元素, 并可选 if 表达式作为过滤条件。
>> > [ x + 1 for x in range ( 3 ) ]
[ 1 , 2 , 3 ]
>> > [ x for x in range ( 6 ) if x % 2 == 0 ]
[ 0 , 2 , 4
]
其行为类似以下代码。
>> > d = [ ]
>> > for x in range ( 6 ) :
if x % 2 == 0 :
d . append ( x )
>> > d
[ 0 , 2 , 4
]
有种称做 Pythonic 的习惯,核心是写出简洁的代码,推导式算其中一种。 有关推导式更多信息,可阅读后章。
无论是历史原因,还是实现方式,内置类型关注性能要多过设计。如要实现自定义列表,建议基于 collections.UserList 包装类型完成。除统一 collections.abc 体系外,最重要的是该类型重载并完善了相关操作符方法。
>> > list . __bases__
( object , )
>> > collections . UserList . __bases__
( collections . abc . MutableSequence ,
)
以加法操作符为例,对比不同继承的结果。
>> > class A ( list ) : pass
>> > type ( A ( "abc" ) + list ( "de" ) ) # 返回的是 list, 而不是 A。
list
>> > class B ( collections . UserList ) : pass
>> > type ( B ( "abc" ) + list ( "de" ) ) # 返回 B 类型。
__main__
.
B
最小接又设计是个基本原则。应慎重考虑列表这种功能丰富的类型,是否适合作为基类。
用加法运算符连接多个列表,乘法复制内容。
>> > [ 1 , 2 ] + [ 3 , 4 ]
[ 1 , 2 , 3 , 4 ]
>> > [ 1 , 2 ] * 2
[ 1 , 2 , 1 , 2
]
注意,同时加法(或乘法)运算,但结果却有所不同。
>> > a = [ 1 , 2 ]
>> > b = a
>> > a = a + [ 3 , 4 ]
# 新建列表对象,然后与 a 关联。
>> > a # a、b 结果不同,确定 a 指向新对象。
[ 1 , 2 , 3 , 4 ]
>> > b
[ 1 , 2
]
>> > a = [ 1 , 2 ]
>> > b = a
>> > a += [ 3 , 4 ] # 直接修改 a 内容。
>> > a # a、b 结果相同,确认修改原对象。
[ 1 , 2 , 3 , 4 ]
>> > b
[ 1 , 2 , 3 , 4 ]
>> > a is b
True
编译器将 “+=” 运算符处理成 INPLACE_ADD 操作,也就是修改原数据,而非新建对象。其效 果类似于执行 list.extend 方法。
判断元素是否存在时,同样习惯使用 in,而非 index 方法。
>> > 2 in [ 1 , 2 ]
True
至于删除操作,可以索引序号指定单个元素,或用切片指定删除范围。
>> > a = [ 0 , 1 , 2 , 3 , 4 , 5 ]
>> > del a [ 5 ] # 删除单个元素。
>> > a
[ 0 , 1 , 2 , 3 , 4 ]
>> > del a [ 1 : 3 ] # 删除范围。
>> > a
[ 0 , 3 , 4
]
返回切片时会创建新列表对象,并复制相关指针数据到新的数组。除部分引用目标相同外,对列表自身的修改(插入、删除等)互不影响。
>> > a = [ 0 , 2 , 4 , 6 ]
>> > b = a [ : 2 ]
>> > a [ 0 ] is b [ 0 ] # 复制引用,指向同一对象。
True
>> > a . insert ( 1 , 1 ) # 对 a 表的操作,不会影响 b。
>> > a
[ 0 , 1 , 2 , 4 , 6 ]
>> > b
[ 0 , 2
]
复制的是指针 (引用),而不是目标元素对象。 对列表自身的修改互不影响,但对目标元素的修改是共享的。
对列表排序可设定自定义条件,比如按字段或长度等。
class User :
def __init__ ( self , name , age ) :
self . name = name
self . age = age
def __repr__ ( self ) :
return
f
"{self.name} {self.age}"
>> > users = [ User ( f "user{i}" , i ) for i in ( 3 , 1 , 0 , 2 ) ]
>> > users
[ user3 3 , user1 1 , user0 0 , user2 2
]
>> > users . sort ( key = lambda u : u . age ) # 使 lambda 匿名函数返回排序条件。
>> > users
[ user0 0 , user1 1 , user2 2 , user3 3
]
如要返回排序复制品,可使用 sorted 函数。
>> > d = [ 3 , 0 , 2 , 1 ]
>> > sorted ( d ) # 同样可指定排序条件,或倒序。
[ 0 , 1 , 2 , 3 ]
>> > d # 并未影响原列表。
[ 3 , 0 , 2 , 1
]
向有序列表插入元素,可借助 bisect 模块。它使用二分法查找适合位置,可用来实现优先级队列或一致性哈希算法等。
>> > d = [ 0 , 2 , 4 ]
>> > import bisect
>> > bisect . insort_left ( d , 1 ) # 插入新元素后,依然保持有序状态。
>> > d
[ 0 , 1 , 2 , 4 ]
>> > bisect . insort_left ( d , 2 )
>> > d
[ 0 , 1 , 2 , 2 , 4 ]
>> > bisect . insort_left ( d , 3 )
>> > d
[ 0 , 1 , 2 , 2 , 3 , 4
]
自定义复合类型,可通过重载比较运算符(__eq__、__lt__ 等)实现自定义排序条件。