【python基础】python可变序列与不可变序列

文章目录

  • 前言
  • 一、序列类型定义
  • 二、对序列类型的切片操作
  • 三、使用 + 与 * 对序进行操作
  • 四、增量赋值 += 和 *=


前言

本文主要讲可变序列与不可变序列一些简单的应用。

一、序列类型定义

按序列能否被修改分为:可变序列与不可变序列。
可变序列:可以进行增、删、改等操作序列。比如:比如列表、集合等。
不可变序列:不可以进行增、删、改等操作序列。比如:元组、字符串等。

  • 可变序列

举例一:列表

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

【python基础】python可变序列与不可变序列_第1张图片

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

你可能感兴趣的:(python基础,python,java,开发语言)