文章目录
- 前言
- 一、序列类型定义
- 二、对序列类型的切片操作
- 三、使用 + 与 * 对序进行操作
- 四、增量赋值 += 和 *=
前言
本文主要讲可变序列与不可变序列一些简单的应用。
一、序列类型定义
按序列能否被修改分为:可变序列与不可变序列。
可变序列:可以进行增、删、改等操作序列。比如:比如列表、集合等。
不可变序列:不可以进行增、删、改等操作序列。比如:元组、字符串等。
- 可变序列
举例一:列表
list1=[x for x in range(8)]
print('原列表:',id(list1))
list1.append('hello')
print('增加变量后的列表:',id(list1))
输出:
原列表: 1554734032320
增加变量后的列表: 1554734032320
举例二:集合
set1={1,2,3,4,5}
print("原集合:",id(set1))
set1.add('a')
print("增加元素后的集合:",id(set1))
输出:
原集合: 1554733828128
增加元素后的集合: 1554733828128
- 不可变序列
举例三:元组
tuple1=(1,2,3,4)
print("原元组:",id(tuple1))
tuple1*=3
print("复制后的元组:",id(tuple1))
输出:
原元组: 1554733957184
复制后的元组: 1554723851136
举例四:字符串
str1='hello'
print("原字符串:",id(str1))
str1*=3
print("替换修改后的字符串:",id(str1))
输出:
原字符串: 1554730216624
替换修改后的字符串: 1554733243696
结论:不可变序列在内存中是不会改变的,如果对不可变序列做出改变,会将该序列存储在新的地址空间中,而原来的序列因为垃圾回收机制,会被回收;可变序列发生改变时,是不会改变地址的,而是改变内存中的值,或者扩展内存空间。
二、对序列类型的切片操作
举例:a[i:j:k]取值
s="hello world"
s[::3] #间隔三个字符取值
#输出:
#'hlwl'
s[::-1] #倒序
#输出:
#'dlrow olleh'
s[:5:2]
#输出:
#'hlo'
切片基础理解可以参考:https://blog.csdn.net/sodaloveer/article/details/134197327
举例二:切片对象slice(a,b,c)
seq(start:stop:step)求解时,解释器用seq.getitem(slice(start,end,step))。
s="hello world"
first=slice(0,9) #先创建一个slice对象
last=slice(9,None)
s[first]
#输出:
#'hello wor'
s[last]
#输出:
#'ld'
举例三:多维切片
[ ] 内逗号隔开传入多个切片对象,从而实现多维切片,广泛用于numpy中,list不支持这种操作。
import numpy as np
a=np.array(range(24))
a=a.reshape((2,3,4))
a
#输出:
#array([[[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]],
#
# [[12, 13, 14, 15],
# [16, 17, 18, 19],
# [20, 21, 22, 23]]])
a[1,2,3] #第2个数组的第三行第四列。
#输出:
#23
a[1,2,1:3] #第2个数组的第3行第二、四列。
#输出:
#array([21, 22])
a[1,...] #第2个数组
#输出:
#array([[12, 13, 14, 15],
# [16, 17, 18, 19],
# [20, 21, 22, 23]])
举例四:切片赋值
- 切片长度和赋值的数组长度可以不相等。
list1=[x for x in range(10)]
list1
#输出:
#[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
list1[2:5]=[20,30]
list1
#输出:
#[0, 1, 20, 30, 5, 6, 7, 8, 9]
list1[2:4]=[1,1,1,1,1,1,1,1]
list1
#输出:
#[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 6, 7, 8, 9]
- 间隔切片时必须要相等
list1[::2]=[666,777]
list1
list1[::2]=[111,222,333,444,666,777,888,999]
list1
#输出:
#[111, 1, 222, 1, 333, 1, 444, 1, 666, 1, 777, 6, 888, 8, 999]
注意: 传入值的形式一定要用[]。
三、使用 + 与 * 对序进行操作
使用 + 与 * 对序进行操作时,python并不修改原有的操作对象,而是构建一个全新序列。
list1=[x for x in range(5)]
list2=[x for x in range(5,10)]
list1+list2
#输出:
#[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
list1*2
#输出:
#[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
print('原有的列表并不改变:',list1)
#输出:
#[0, 1, 2, 3, 4]
注意: 对于列表中的元素为引用时,慎用+和*。
list1=[[1,2,3]]
list2=list1*3
list2
#输出:
#[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
list2[0][0]='hello'
#输出:
#[['hello', 2, 3], ['hello', 2, 3], ['hello', 2, 3]]
结论:由上可见,list1是一个只有一个元素的列表,这个元素是一个指向列表[1,2,3]的引用。list2=list1*3操作,则建立三个这样的引用,但作为list2的元素,三个元素都是指向同一个列表对象,所以我们使用第一个元素(list2[0][0]=‘hello’)去修改指向的列表的值时,list2列表中每一个引用的第一个元素都被改变了。
如果只修改引用是没有问题的,如下例所示:
list2[0]="world"
#输出:
#['world', ['hello', 2, 3], ['hello', 2, 3]]
结论:由上可见,此时我们修改了list2的第一个元素,把其重新指向了一个新建的字符串对象。而原来指向的列表没有发生改动,所以后两个元素此时不会发生变化。
四、增量赋值 += 和 *=
就地乘法: *= ,背后是__imul__特殊方法。
就地加法: += ,背后是__iadd__特殊方法,和a.extend(b)一样的效果。若a未实现该特殊方法,Python将退一步调用__add__方法,得到一个求和对象,然后赋值给a。
- 对于支持就地加法或乘法的序列类型(可变类型),使用+=或*=时,Python不会新建一个对象。如下例所示,a的地址并未发生变化。
a=[x for x in range(5)]
b=[y for y in range(5,10)]
print('原序列:',a,'\n','原序列地址:',id(a))
#输出:
#原序列: [0, 1, 2, 3, 4]
#原序列地址: 1554755401920
a+=b
print('就地加法后的序列:',a,'\n','就地加法后的序列地址:',id(a))
#输出:
#就地加法后的序列: [0, 1, 2, 3, 4]
#就地加法后的序列地址: 1554755401920
- 对于不支持就地加法或乘法的序列类型(不可变类型),使用+=或*=时,Python会新建一个对象。如下例所示,tuple1的地址发生变化。
tuple1=(1,2,3)
print('原序列:',tuple1,'\n','原序列地址:',id(tuple1))
#输出:
#原序列: (1, 2, 3)
#原序列地址: 1554755553856
tuple1*=2
print('就地乘法后的序列:',tuple1,'\n','就地乘法后的序列地址:',id(tuple1))
#输出:
#就地乘法后的序列: (1, 2, 3, 1, 2, 3)
#就地乘法后的序列地址: 1554728975712
参考文章:
https://zhuanlan.zhihu.com/p/33488349