一、序列类型

序列类型用来表示有序的元素集合。

1. 字符串

python 中字符串用 str 表示,字符串是使用单引号,双引号,三引号包裹起来的字符的序列,用来表示文本信息。

1.1 字符串的定义

a = 'a'
b = "bc"
c = """hello,world"""
d = '''hello,d'''
e = """
    hello,
    world!
    """
print('a的类型为:', type(a))    # a的类型为: <class 'str'>
print('b的类型为:', type(b))    # b的类型为: <class 'str'>
print('c的类型为:', type(c))    # c的类型为: <class 'str'>
print('d的类型为:', type(d))    # d的类型为: <class 'str'>
print('e的类型为:', type(e))    # e的类型为: <class 'str'>

a 的类型为: <class ‘str’>
b 的类型为: <class ‘str’>
c 的类型为: <class ‘str’>
d 的类型为: <class ‘str’>
e 的类型为: <class ‘str’>
使用单引号和双引号进行字符串定义没有任何区别,当要表示字符串的单引号时用双引号进行定义字符串,反之亦然。

一对单引号或双引号只能创建单行字符串,三引号可以创建多行表示的字符串。三双引号一般用来做多行注释,表示函数,类定义时的说明。

print('最近我看了"平凡的世界"')
print("最近我看了'平凡的世界'")

最近我看了"平凡的世界"
最近我看了’平凡的世界’

定义空字符串
a = ''
print(a)

1.2 字符串的索引

任何序列类型中的元素都有 索引 用来表示它在序列中的位置。

字符串是字符的序列表示,单个字符在字符串中的位置使用 索引 来表示,也叫下标。

索引使用整数来表示。

通过 索引 可以获取字符串中的单个字符

语法如下:

str[index]
s = 'hello world!'
print(s[0])
print(s[-1])

h
!
注意字符串索引从 0 开始

1.3 字符串的切片

获取序列中的子序列叫切片。

字符串的切片就是获取字符串的子串。

字符串切片的语法如下:

str[start:end:step]

start 表示起始索引,end 表示结束索引,step 表示步长。

str[m:n:t] 表示从字符串索引为 mn 之间不包含 n 每隔 t 个字符进行切片。

step 为 1 的时候可以省略。

特别的,当 step 为负数时,表示反向切片。

s = '0123456789'
print(s[1:5])  # 包头不包尾

1234

print(s[:5])  # 从头开始切可以省略start

01234

print(s[1:])  # 切到末尾省略end

123456789

print(s[1::2]) # 步长为2进行切片

13579

print(s[1::-2]) # 步长为负数反向切片

1

思考

获取一个字符串的逆串,例如 'abc' 的逆串是 'cba'

w="abc"
print(w[::-1])

1.4 字符串拼接

python 中可以通过 + 拼接两个字符串

a = 'hello'
b = ' '
c = 'world!'
print(a+b+c)

hello world!
字符串和整数进行乘法运算表示重复拼接这个字符串

print('*' * 10)

1.5 字符串常用方法

通过内建函数 dir 可以返回传入其中的对象的所有方法名列表。

print(dir(str))

[‘add’, ‘class’, ‘contains’, ‘delattr’, ‘dir’, ‘doc’, ‘eq’, ‘format’, ‘ge’, ‘getattribute’, ‘getitem’, ‘getnewargs’, ‘gt’, ‘hash’, ‘init’, ‘init_subclass’, ‘iter’, ‘le’, ‘len’, ‘lt’, ‘mod’, ‘mul’, ‘ne’, ‘new’, ‘reduce’, ‘reduce_ex’, ‘repr’, ‘rmod’, ‘rmul’, ‘setattr’, ‘sizeof’, ‘str’, ‘subclasshook’, ‘capitalize’, ‘casefold’, ‘center’, ‘count’, ‘encode’, ‘endswith’, ‘expandtabs’, ‘find’, ‘format’, ‘format_map’, ‘index’, ‘isalnum’, ‘isalpha’, ‘isascii’, ‘isdecimal’, ‘isdigit’, ‘isidentifier’, ‘islower’, ‘isnumeric’, ‘isprintable’, ‘isspace’, ‘istitle’, ‘isupper’, ‘join’, ‘ljust’, ‘lower’, ‘lstrip’, ‘maketrans’, ‘partition’, ‘replace’, ‘rfind’, ‘rindex’, ‘rjust’, ‘rpartition’, ‘rsplit’, ‘rstrip’, ‘split’, ‘splitlines’, ‘startswith’, ‘strip’, ‘swapcase’, ‘title’, ‘translate’, ‘upper’, ‘zfill’]
通过内建函数 help 可以返回传入函数的帮助信息。

help('abc'.replace)

Help on built-in function replace:

replace(old, new, count=-1, /) method of builtins.str instance
Return a copy with all occurrences of substring old replaced by new.

count
Maximum number of occurrences to replace.
-1 (the default value) means replace all occurrences.

If the optional argument count is given, only the first count occurrences are
replaced.
官方文档地址


str.isalpha()

作用:检查字符串是否只由字母组成 。如果字符串中的所有字符都是字母,并且至少有一个字符,返回 True ,否则返回 False

str = "abc"
print(str.isalpha())

执行结果:

true


str.isdigit()

作用:检查字符串是否只由数字组成。如果序列中所有字节都是 ASCII 十进制数码并且序列非空则返回 True ,否则返回 False

str = "123134123"
print(str.isdigit())

执行结果

true


str.islower()

作用:检查字符串是否全部是小写字母。 如果序列中至少有一个小写的 ASCII 字符并且没有大写的 ASCII 字符则返回 True ,否则返回 False

str ='hello world'
print(str.islower())

执行结果

true


str.isupper()

作用:检查字符串是否全部是大写字母。如果序列中至少有一个大写字母 ASCII 字符并且没有小写 ASCII 字符则返回 True ,否则返回 False

str ='HELLO WORLD'
print(str.isupper())

执行结果

true


str.isspace

作用:检查字符串只含有空白符。如果序列中所有字节都是 ASCII 空白符并且序列非空则返回 True ,否则返回 False 。 ASCII 空白符就是字节值包含在序列 b' \t\n\r\x0b\f' (空格, 制表, 换行, 回车, 垂直制表, 进纸) 中的字符。

str ='\t\n\r\x0b\f'
print(str.isspace())

执行结果

true


str.upper()

作用:将字符串都变成大写字母

str.lower()

作用:将字符串都变成小写字母

str = "hello world !!! hhh"

print(str.upper())
print(str.lower())

执行结果

HELLO WORLD !!! HHH
hello world !!! hhh


str.strip(chars = " ")

作用:移除字符串头尾指定的字符序列chars,默认为空格

str.lstrip(chars = " ")

作用:移除字符串头部指定的字符序列chars,默认为空格

str.rstrip(chars = " ")

作用:移除字符串尾部指定的字符序列chars,默认为空格

str = "   hello  every  "

print("1", str.strip(), "1")
print(str.lstrip(), "1")
print("1", str.rstrip())

str = "!!! cool !!!"

print(str.strip("!"))

执行结果

1 hello every 1
hello every 1
1 hello every
cool


str.replace(old,new,count= -1)

作用:把字符串中的 old(旧字符串) 替换成 new(新字符串),count代表最多替换多少次,默认-1代表全部替换

str = "hello world !!! hhh"

print(str.replace(" ", "-"))
print(str.replace(" ", "-", 1))

执行结果

hello-world-!!!-hhh
hello-world !!! hhh


str.split(str=“”, num=string.count(str))

作用:将字符串按照str分割成列表,如果参数 num 有指定值,则分隔 num+1 个子字符串

str = "hello world !!! hhh"

print(str.split(" "))
print(str.split(" ", 1))

执行结果

[‘hello’, ‘world’, ‘!!!’, ‘hhh’]
[‘hello’, ‘world !!! hhh’]


str.index(sub, start=None, end=None)

作用:查看sub是否在字符串中,在的话返回索引,且只返回第一次匹配到的索引;若找不到则报错;可以指定统计的范围,[start,end) 左闭区间右开区间

str = "helloworldhhh"
print(str.index("h"))
print(str.index("hhh"))
# print(str.index("test")) 直接报语法错误:ValueError: substring not found

执行结果

0
10


str.find(sub, start=None, end=None)

作用:和index()一样,只是找不到不会报错,而是返回-1

str = "helloworldhhh"
print(str.find("h"))
print(str.find("hhh"))
print(str.find("test"))

执行结果

0
10
-1


str.count( sub, start=None, end=None)

作用:统计子字符串的数量;可以指定统计的范围,[start,end) 左闭区间右开区间

str = "hello world !!! hhh"

print(str.count(" "))
print(str.count(" ", 5, 10))

执行结果

3
1


1.6 字符串和数值的相互转化

1'1' 不同,1.2'1.2' 也不相同,但是它们可以相互转化

# 整数和字符串之间的转化
int('1')

1

str(1)

‘1’

# 浮点数和字符串之间的转化
float('1.2')

1.2

str(1.2)

‘1.2’

# 尝试 int('1.2')看看结果会是什么
int('1.2')

1.7 转义符

在需要在字符中使用特殊字符时,python 用反斜杠 \ 转义字符。常用转义字符如下表:

(在行尾时)

续行符

\\

反斜杠符号

\'

单引号

\"

双引号

\a

响铃

\n

换行

\t

横向制表符

\r

回车

\f

换页

print('窗前明月光,\n疑是地上霜。')  # 输出换行

窗前明月光,
疑是地上霜。

print('对\\错')   # 输出反斜杠本身

对\错

print('\'')      # 输出单引号本身

1.8 字符串格式化

在实际工作中经常需要动态输出字符。

例如,我们通过程序计算计算机的内存利用率,然后输出

10:15 计算机的内存利用率为30%

其中下划线内容会动态调整,需要根据程序执行结果进行填充,最终形成上述格式的字符串输出。

python 支持两种形式的字符串格式化

% 字符串格式化
%[(name)][flags][width][.precision]typecode
  • (name)可选,用于选择指定的key

  • flags 可选,可供选择的值有,注意只有在和数值类型的 typecode 配合才起作用

    • +, 右对齐,正数前加正号,负数前加负号

    • -, 左对齐,正数前无符号,负数前加负号

    • 空格, 右对齐,正数前加空格,负数前加负号

    • 0, 右对齐,正数前无符号,负数前加负号;用 0 填充空白处

  • width,可选字符串输出宽度

  • .precision 可选,小数点后保留位数,注意只有在和数值类型的 typecode 配合才起作用

  • typecode 必选必选

    • s,获取传入对象的字符串形式,并将其格式化到指定位置

    • r,获取传入对象的 __repr__ 方法的返回值,并将其格式化到指定位置

    • c,整数:将数字转换成其 unicode 对应的值,10 进制范围为 0 <= i <= 1114111(py27 则只支持 0-255);字符:将字符添加到指定位置

    • o,将整数转换成 八 进制表示,并将其格式化到指定位置

    • x,将整数转换成十六进制表示,并将其格式化到指定位置

    • d,将整数、浮点数转换成 十 进制表示,并将其格式化到指定位置

    • e,将整数、浮点数转换成科学计数法,并将其格式化到指定位置(小写 e)

    • E,将整数、浮点数转换成科学计数法,并将其格式化到指定位置(大写 E)

    • f, 将整数、浮点数转换成浮点数表示,并将其格式化到指定位置(默认保留小数点后 6 位)

    • F,同上

    • g,自动调整将整数、浮点数转换成 浮点型或科学计数法表示(超过 6 位数用科学计数法),并将其格式化到指定位置(如果是科学计数则是 e;)`

    • G,自动调整将整数、浮点数转换成 浮点型或科学计数法表示(超过 6 位数用科学计数法),并将其格式化到指定位置(如果是科学计数则是 E;)`

    • %,当字符串中存在格式化标志时,需要用 %% 表示一个百分号

res = '%s计算机的内存利用率为%s%%' % ('11:15', 75)
print(res)
# '%s'作为槽位和 % 号后提供的值按顺序一一对应

11:15 计算机的内存利用率为 75%

res = '%(time)s计算机的内存利用率为%(percent)s%%' % {'time':'11:15', 'percent': 75}
# % 后是字典时,可以通过name指定key对应的值
print(res)

11:15 计算机的内存利用率为 75%

# 输出两位数的月份,例如01,02
res = '%02d' % 8
print(res)

08

# 保留2为小数
res = '%(time)s计算机的内存利用率为%(percent).2f%%' % {'time':'11:15', 'percent': 75.123}
print(res)

11:15 计算机的内存利用率为 75.12%

print('字符串%(key)s,十进制%(key)d,科学计数%(key)e,八进制%(key)o,16进制%(key)x,unicode字符%(key)c' % {'key': 65})

字符串 65,十进制 65,科学计数 6.500000e+01,八进制 101,16 进制 41,unicode 字符 A

format 函数格式化

模板字符串也是字符串所以用’ '表示,格式化调用format()方法用点方法----如
‘{}计算机的内存利用率为{}%’.format(‘11:15’, 75)

% 的字符串格式化继承自 C 语言,python 中给字符串对象提供了一个 format 函数进行字符串格式化,且功能更强大,并且大力推荐,所以我们要首选使用。

基本语法是:

<模板字符串>.format(<逗号分隔的参数>)

在模板字符串中使用 {} 代替以前的 % 作为槽位

'{}计算机的内存利用率为{}%'.format('11:15', 75)

‘11:15 计算机的内存利用率为 75%’
当 format 中的参数使用位置参数提供时,{} 中可以填写参数的整数索引和参数一一对应

'{0}计算机的内存利用率为{1}%'.format('11:15', 75)

‘11:15 计算机的内存利用率为 75%’
当 format 中的参数使用关键字参数提供时,{}中可以填写参数名和参数一一对应

'{time}计算机的内存利用率为{percent}%'.format(time='11:15', percent=75)

‘11:15 计算机的内存利用率为 75%’
{} 中除了可以写参数索引外,还可以填写控制信息来实现更多的格式化功能,语法如下

{<参数序号>:<格式控制标记>}
其中格式控制标记格式如下
[fill][align][sign][#][0][width][,][.precision][type]
  • fill 【可选】空白处填充的字符

  • align 【可选】对齐方式(需配合 width 使用)

  • <,内容左对齐

  • >,内容右对齐(默认)

  • =,内容右对齐,将符号放置在填充字符的左侧,且只对数字类型有效。 即使:符号 + 填充物 + 数字

  • ^,内容居中

  • sign 【可选】有无符号数字

    • +,正号加正,负号加负;

    • -,正号不变,负号加负;

    • 空格 ,正号空格,负号加负;

  • #

    【可选】对于二进制、八进制、十六进制,如果加上#,会显示 0b/0o/0x,否则不显示
    
  • , 【可选】为数字添加分隔符,如:1,000,000

  • width 【可选】格式化位所占宽度

  • .precision 【可选】小数位保留精度

  • type 【可选】格式化类型

    • 传入” 字符串类型 “的参数

      • s,格式化字符串类型数据

      • 空白,未指定类型,则默认是 None,同 s

    • 传入“ 整数类型 ”的参数

      • b,将 10 进制整数自动转换成 2 进制表示然后格式化

      • c,将 10 进制整数自动转换为其对应的 unicode 字符

      • d,十进制整数

      • o,将 10 进制整数自动转换成 8 进制表示然后格式化;

      • x,将 10 进制整数自动转换成 16 进制表示然后格式化(小写 x)

      • X,将 10 进制整数自动转换成 16 进制表示然后格式化(大写 X)

    • 传入“ 浮点型或小数类型 ”的参数

      • e, 转换为科学计数法(小写 e)表示,然后格式化;

      • E, 转换为科学计数法(大写 E)表示,然后格式化;

      • f , 转换为浮点型(默认小数点后保留 6 位)表示,然后格式化;

      • F, 转换为浮点型(默认小数点后保留 6 位)表示,然后格式化;

      • g, 自动在 e 和 f 中切换

      • G, 自动在 E 和 F 中切换

      • %,显示百分比(默认显示小数点后 6 位)

# 输出两位数的月份,例如01,02
res = '{:0>2}'.format(8) 
print(res)

08

# 保留2为小数
res = '{time}计算机的内存利用率为{percent:.2%}'.format(time='11:15', percent=0.75123)
print(res)

11:15 计算机的内存利用率为 75.12%

print('字符串{key},十进制{key:d},科学计数{key:e},八进制{key:o},16进制{key:x},unicode字符{key:c}'.format(key=65))

字符串 65,十进制 65,科学计数 6.500000e+01,八进制 101,16 进制 41,unicode 字符 A

格式字符串字面值

3.6 新版功能:

格式字符串字面值或称为 f-string 是标注了 ‘f’ 或 ‘F’ 前缀的字符串字面值。这种字符串可包含替换字段,即以 {} 标注的表达式。

基本语法是:

literal_char{expression[:format_spec]}
  • literal_char 普通字符

  • expression 表达式,变量或函数。。

  • format_spec 格式字符串,规则同上面的 format 字符串

直接在 f 字符串的花括号内写上变量名,解释器会自动将变量的值的字符串形式替换

item = '11:15'
percent = 75
f'{time}计算机的内存利用率为{percent}%'
'11:15计算机的内存利用率为75%'

带格式的 f 字符串

# 输出两位数的月份,例如01,02
month = 8
res = f'{month:0>2}'
print(res)

08

# 保留2为小数
time = '11:15'
percent = 0.75123
res = f'{time}计算机的内存利用率为{percent:.2%}'
print(res)

11:15计算机的内存利用率为75.12%

key = 65
print(f'字符串{key},十进制{key:d},科学计数{key:e},八进制{key:#o},16进制{key:x},unicode字符{key:c}')

字符串65,十进制65,科学计数6.500000e+01,八进制0o101,16进制41,unicode字符A

包含运算和函数的 f 字符串

num = -1
print(f'{num+1=}')
num+1=0
print(f'{abs(num)=}')
abs(num)=1
s = 'abcd'
print(f'{s[:python :-1]=}')
s[::-1]='dcba'

2. 列表

python 中列表(list)用来表示任意元素的序列,元素可以是任意数据类型,序列中的元素可以增,删,改。

2.1 列表的定义

列表由一对中括号进行定义,元素与元素直接使用逗号隔开。

a = []                      # 空列表
b = ["a", "b", "cde"]       # 字符串列表项
c = [1, "b", "c"]           # 数字列表项  
d = [1, "b", []]            # 列表列表项
e = [1, "b", [2, "c"]]      # 列表作为列表的元素叫做列表的嵌套


print('a的类型为:', type(a))    # a的类型为: <class 'list'>
print('b的类型为:', type(b))    # b的类型为: <class 'list'>
print('c的类型为:', type(c))    # c的类型为: <class 'list'>
print('d的类型为:', type(d))    # d的类型为: <class 'list'>
print('e的类型为:', type(e))    # e的类型为: <class 'list'>

a 的类型为: <class ‘list’>
b 的类型为: <class ‘list’>
c 的类型为: <class ‘list’>
d 的类型为: <class ‘list’>
e 的类型为: <class ‘list’>

2.2 列表的拼接

像字符串一样,列表之间可以进行加法运算实现列表的拼接,列表可以和整数进行乘法运算实现列表的重复。

[1,2,3] +  [4,5,6]

[1, 2, 3, 4, 5, 6]

[1,2,3] * 3

[1, 2, 3, 1, 2, 3, 1, 2, 3]

2.3 列表的索引和切片

序列的切片操作完全一致,参见字符串

注意嵌套列表的元素获取

ls = [1,2,['a','b']]
ls[2][0]

‘a’

2.4 列表的常用操作

python 中的列表操作非常灵活,是非常重要和经常使用的数据类型。

2.4.1 修改元素

列表的中的元素可以进行修改,只需使用索引赋值即可。

ls = [1,2,3]
ls[1] = 'a'
print(ls)

[1, ‘a’, 3]

2.4.2 增加元素

给列表添加元素需要使用到列表的方法

.append(el),在列表的末尾添加一个元素

ls = [1,2,3]
ls.append(4)
print(ls)

[1, 2, 3, 4]
.insert(index, el),在列表的指定索引处插入一个元素

ls = [1,2,3]
ls.insert(0,0)
print(ls)

[0, 1, 2, 3]
.extend(iterable),扩展列表,元素为传入的可迭代对象中的元素(不能传入数值)

ls = [1,2,3]
ls.extend([4,5,6])
print(ls)

[1, 2, 3, 4, 5, 6]

2.4.3 删除元素

.pop(index=-1),删除指定索引的元素,并返回该元素,没有指定索引默认删除最后一个元素

ls = [1,2,3]
ls.pop()

3

  print(ls)

[1, 2]

  ls.pop(0)

1

print(ls)

[2]
.remove(value),从列表中删除第一个指定的值 value,如不存在 value 则报错。

ls = [1,2,3,1]
ls.remove(1)
print(ls)

[2, 3, 1]
.clear(),清空列表,原列表变成空列表,等价于 del a[:]

ls = [1,2,3]
ls.clear()
print(ls)

[]

2.5 列表的其他方法

.copy() 返回一个列表的浅拷贝。在讲可变与不可变类型的时候再详细讨论。

.count(value),统计列表中 value 的出现次数

ls = [1,2,3,1]
ls.count(1)

2
.index(value, start=0, stop=9223372036854775807),返回列表中指定值 value 的第一个索引,不存在则报错

ls = [1,2,3,1]
ls.index(1)

0

  l.index(1,1)

3
.reverse(),翻转列表元素顺序

ls = [1,2,3]
ls.reverse()
print(ls)

[3, 2, 1]
.sort(key=None, reverse=False),对列表进行排序,默认按照从小到大的顺序,当参数 reverse=True 时,从大到小。注意列表中的元素类型需要相同,否则抛出异常。key -- 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。

ls = [2,1,3]
ls.sort()
print(ls)

[1, 2, 3]

# 从大到小
ls.sort(reverse=True) 
print(ls)

[3, 2, 1]

ls = [1,2,'3']
ls.sort()

TypeError Traceback (most recent call last)

in
1 ls = [1,2,‘3’]
----> 2 ls.sort()

TypeError: ‘<’ not supported between instances of ‘str’ and ‘int’

list = [1, 4, 'a', 'b', 45, 'c'] 
list.sort(key=str)
print(list)

# 输出
[1, 4, 45, 'a', 'b', 'c']

2.6 字符串和列表的转换

字符串是字符组成的序列,可以通过 list 函数将字符串转换成单个字符的列表。

s = 'hello world!'
ls = list(s)
print(ls)

[‘h’, ‘e’, ‘l’, ‘l’, ‘o’, ’ ', ‘w’, ‘o’, ‘r’, ‘l’, ‘d’, ‘!’]
由字符组成的列表可以通过字符串的 join 方法进行拼接

# 接上面的案例
''.join(ls)

‘hello world!’

join 方法
这个函数展开来写应该是str.join(item),join函数是一个字符串操作函数

str表示字符串(字符),item表示一个成员,注意括号里必须只能有一个成员,比如’,'.join(‘a’,‘b’)这种写法是行不通的

举个例子:

','.join('abc')

上面代码的含义是“将字符串abc中的每个成员以字符’,'分隔开再拼接成一个字符串”,输出结果为:

'a,b,c'

join里放列表、元组、字典也是可以的

';'.join([a,b,c])
>>  'a;b;c'

3. 元组

元组(tuple)表示任意元素的序列,元素可以是任意数据类型,序列中的元素不能增,删,改,可以说元组就是不可变的列表。

3.1 元组的定义

元组通过一对小括号进行定义,元组之间使用逗号隔开。

a = ()                      # 空元祖
b = ("a", "b", "cde")       # 字符串
c = (1, "b", "c")           # 数字
d = (1, "b", [])            # 列表
e = (1, "b", (2, "c"))      # 元祖
f = 1,2

print('a的类型为:', type(a))    # a的类型为: <class 'tuple'>
print('b的类型为:', type(b))    # b的类型为: <class 'tuple'>
print('c的类型为:', type(c))    # c的类型为: <class 'tuple'>
print('d的类型为:', type(d))    # d的类型为: <class 'tuple'>
print('e的类型为:', type(e))    # e的类型为: <class 'tuple'>
print('f的类型为:', type(f))    # f的类型为: <class 'tuple'>

a 的类型为: <class ‘tuple’>
b 的类型为: <class ‘tuple’>
c 的类型为: <class ‘tuple’>
d 的类型为: <class ‘tuple’>
e 的类型为: <class ‘tuple’>
f 的类型为: <class ‘tuple’>
注意单元素元组的定义,一定要多加个逗号

g = ('hello')
h = ('hello',)
print('g的类型为:', type(g))    # g的类型为: <class 'str'>
print('h的类型为:', type(h))    # h的类型为: <class 'tuple'>

g 的类型为: <class ‘str’>
h 的类型为: <class ‘tuple’>

3.2 元组的索引和切片

序列的索引和切片完全一致,参加字符串。

3.2 元组的常用操作

元组的元素不能修改,增加和删除,其他操作和列表的操作一致。

元组利用不可修改的特性,应用在多变量赋值和函数多返回值上。

a, b = (1, 2)
# 经常简写为a, b= 1, 2

当然多变量赋值时可以使用可迭代对象,但是元组最安全,它是不可变的。

关于函数多返回值的问题我们后面再讲

3.3 元组的常用方法

元组只有两个公有方法 count,index 用法与列表相同。

3.4 len 函数

python 内建函数 len 可以获取对象中包含的元素个数

s = 'hello'
ls = [1,2,3]
t = (1,2,3)
print(len(s))
print(len(ls))
print(len(t))

5
3
3

4. 可变与不可变对象

  • 在 Python 中,一切皆为对象

  • Python 中不存在值传递,一切传递的都是对象的引用,也可以认为是传址

有哪些可变对象,哪些不可变对象?

  • 不可变对象:字符串、元组、数字(int、float)

  • 可变对象:数组、字典、集合

不可变对象和可变对象的区别?

  • 可变对象:改变对象内容,对象在内存中的地址不会被改变

    变的是:原来对象的内容,不会创建新对象,而变量也还是指向原对象

  • 不可变对象:改变对象内容,对象在内存中的地址会被改变;如果必须存储一个不同的值,则必须创建新的对象

  • Python 中的变量有一个内存空间

  • 具体的数据(对象)也有一个内存空间

  • 而变量保存(指向)的是存储数据(对象)的内存地址,一般也叫对象引用

  • 不可变对象是指对象内容本身不可变

  • 变的是:改变了值,会创建新对象,然后变量改变了对象引用,指向了新对象,旧对象会被垃圾回收

python 中的对象根据底层内存机制分为可变与不可变两种。
可变对象可以在其 id() 保持固定的情况下改变其取值。
下面的列表 a,修改值后,id 保持不变

a = [1,2,3]
id(a)

14053670614592

# 修改a的值
a[0] = 'a'
id(a)
# 修改a的值
a[0] = 'a'
id(a)

14053670614592

基本数据类型中列表,集合和字典都是可变数据类型。

如果修改一个对象的值,必须创建新的对象,那么这个对象就是不可变对象。

例如下面的字符串 s,修改内容后 id 发生了改变。

s = 'hello'
id(s)

3067776627760

s = 'Hello'
id(s)

3067782100656

基本数据类型中数字,字符串,元组是不可变对象。

5.可哈希对象

一个对象的哈希值如果在其生命周期内绝不改变,就被称为可哈希。可哈希对象都可以通过内置函数 hash 进行求值。

它们在需要常量哈希值的地方起着重要的作用,例如作为集合中的元素,字典中的键。

不可变数据类型都是可哈希对象,可变数据类型都是不可哈希对象。

  hash(1)

1

  hash([1,2])

TypeError Traceback (most recent call last)

in
----> 1 hash([1,2])

TypeError: unhashable type: ‘list’

6.赋值与深浅拷贝

6.1 赋值

  • 变量:存储对象的引用

  • 对象:会被分配一块内存,存储实际的数据,比如字符串、数字、列表

  • 引用:变量指向对象,可以理解为指针

python 是解释型编程语言,当解释器在碰到赋值语句时它首先会计算赋值符号右边的表达式的值,然后再创建左边的变量。

变量中实际存储的是值在内存中的地址,引用变量时通过地址指向内存中的值。通过内建函数 id 可以查看解释器中变量的虚拟内存地址整数值。

a = 1
id(a)

4382612480

python 的赋值语句不复制对象,而是创建目标和对象的绑定关系。
所以将一个变量赋值给另外一个变量时,并不会创建新的值,只是新变量会指向值的内存地址

a = 1
b = a
id(a) == id(b)

True
对于字符串和数字这样的不可变数据类型,当上例中的变量 a 自加 1 时,会创建一个新值重新,它不会改变原来的值。因此对变量 b 没有影响。

a += 1
print(a)
print(b)

2
1
但是看下面的案例

ls = [1,2,3]
ln = ls
ls[0] = 2
print(ls)

[2, 2, 3]
会发现变量 ls 在修改列表的值后,变量 ln 的值也发生了同样的改变,这是因为 ls,ln 指向相同的列表。对可变数据类型进行变量赋值时要考虑这个特性。

6.1.1 实际的一种应用场景
  • 有一个变量 a,存储了一个值

  • 此时想用另一个变量 b 暂时存储变量 a 的值,以便后续使用

  • 然后继续修改变量 a 的值,但修改的时候并不想同步更改变量 b 的值

a=1
b=a
a=2
  • Python 的赋值语句并不是创建一个新对象,只是创建了一个共享原始对象引用的新变量

不可变对象的赋值:

a = 1
b = a

print(a, b)

a += 2
print(a, b)

print("a id:", id(a))
print("b id:", id(b))


# 输出结果
1 1
2 1
a id: 4564097808
b id: 4564097776
  • 修改变量 a 的值,不会同步修改变量 b 的值

  • 因为赋值操作 a += 2 后,变量 a 存储的对象引用已经改变了

  • 至于具体的原理,可以看看不可变对象、可变对象的详解

可变对象的赋值:

a = [1, 2, 3]
b = a

print(a, b)

a[1] = 22
print(a, b)

print("a id:", id(a))
print("b id:", id(b))


# 输出结果
[1, 2, 3] [1, 2, 3]
[1, 22, 3] [1, 22, 3]
a id: 4567683136
b id: 4567683136
  • 修改 a 变量的值,会同步修改变量 b 的值,这不符合上面的说的实际应用场景

  • 因为变量 a、b 指向的对象是可变对象,所以它们保存的对象引用都是一样的

6.1.2拷贝的诞生
  • 那如果要让可变对象也能满足上述实际应用场景,要怎么做呢?

  • 当然就是拷贝

  • 而拷贝又分为浅拷贝、深拷贝,接下来会具体聊一聊两种拷贝的区别

第一个重点总结

  • 对于不可变对象来说,赋值操作其实就可以满足上面说的实际应用场景

  • 所以!后面要讲的浅拷贝、深拷贝对于不可变对象来说,和赋值操作是一样的效果!

  • 记住!浅拷贝、深拷贝只针对可变对象,即列表、集合、字典!

6.1.3copy 模块

Python 提供了 copy 模块,包含了浅拷贝、深拷贝函数

from copy import copy, deepcopy

# 浅拷贝
copy(x)

# 深拷贝
deepcopy(x)

6.2浅拷贝

一句话概括:浅拷贝会创建一个新对象,该新对象存储原始元素的引用

6.2.1浅拷贝后的值是相同的
  • 将列表赋值给变量 old_list

  • 通过 copy() 方法对 old_list 变量指向的对象进行浅拷贝,并赋值给新变量 new_list

  • 因为是对象进行拷贝,所以 new_list 和 old_list 存储的值是相同的

import copy

old_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
new_list = copy.copy(old_list)

print("Old list:", old_list)
print("New list:", new_list)


# 输出结果
Old list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
New list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
6.2.2浅拷贝后的会产生一个新的对象
  • 虽然 old_list 和 new_list 存储的值是相同的,但浅拷贝的操作是产生了一个新的对象

  • 所以 old_list 和 new_list 指向的对象并不是同一个

import copy

old_list = [[1, 2], [3, 4]]
new_list = copy.copy(old_list)

old_list.append([5, 6])

print("Old list:", old_list, "id is :", id(old_list))
print("New list:", new_list, "id is :", id(new_list))


# 输出结果
Old list: [[1, 2], [3, 4], [5, 6]] id is : 4366240704
New list: [[1, 2], [3, 4]] id is : 4366246720

可以看到内存地址是不同的,所以给 old_list 新增一个元素并不会同步让 new_list 也新增

  • 浅拷贝生成了一个新对象,然后赋值给 new_list

  • new_list、old_list 指向的列表对象不是同一个,但值相同

  • 重点:对于列表对象中的元素,浅拷贝产生的新对象只存储原始元素的引用(内存地址),所以两个列表对象的元素的引用都指向同一个内存地址

6.2.3修改列表内的不可变对象元素

上面的栗子是直接添加元素,来看看修改元素会怎么样

# 不可变元素
import copy

old_list = [1, 2, "string", (1, 2,)]
new_list = copy.copy(old_list)

old_list[1] += 22
old_list[2] += "s"
old_list[3] += (3,)

print("Old list:", old_list)
print("New list:", new_list)


# 输出结果
Old list: [1, 24, 'strings', (1, 2, 3)]
New list: [1, 2, 'string', (1, 2)]

修改 old_list 的三种不可变对象元素,均不会同步给 new_list

6.2.4修改列表内的可变对象元素
# 可变元素
import copy

old_list = [[1, 2], [3, 4]]
new_list = copy.copy(old_list)

old_list[0][0] += 99
old_list[1][0] += 97

print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[0]))
print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[0]))


# 输出结果
Old list: [[100, 2], [100, 4]] old list id: 4430308096  old list[0] id: 4430302400
new list: [[100, 2], [100, 4]] new list id: 4430308416  new list[0] id: 4430302400

从输出结果看到

  • 两个变量保存了不同的对象引用

  • 但是可变对象元素的内存地址仍然是同一个

6.2.5总结
  • 修改可变对象是在原始对象上直接操作的

  • 浅拷贝产生的新对象存储的仍然是原始对象的内存地址

  • 所以修改可变对象的时候,新对象的值也会被同步修改,因为新旧列表对象的元素的引用是指向同一个内存地址

  • 当修改可变对象的时候,不满足一开始说的实际应用场景,所以诞生了深拷贝

6.3 深拷贝

  • 创建一个新对象,且存储的对象引用也是新的

  • 深,意味着会把所有子元素对象也复制生成一个新对象

栗子一

# 深拷贝
old_list = [[1, 2], [3, 4]]
new_list = copy.deepcopy(old_list)

old_list[0][0] += 99
old_list[1][0] += 97

print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[0]))
print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[0]))


# 输出结果
Old list: [[100, 2], [100, 4]] old list id: 4430308480  old list[0] id: 4430211392
new list: [[1, 2], [3, 4]] new list id: 4430308096  new list[0] id: 4430308864

从输出结果看到

  • 两个变量保存了不同的对象引用

  • 可变对象元素(子对象)的内存地址也是不同的

栗子二

假设是一个三维列表呢

# 深拷贝-三维数组
old_list = [[1, [10, 9]], [3, 4]]
new_list = copy.deepcopy(old_list)

old_list[0][1][0] += 90

print("Old list:", old_list)
print("New list:", new_list)


# 输出结果
Old list: [[1, [100, 9]], [3, 4]]
New list: [[1, [10, 9]], [3, 4]]

两个变量依旧是独立的

6.3.3面试题:浅拷贝、深拷贝的区别
  1. 浅拷贝和深拷贝只有在可变对象才会生效,不可变对象的赋值操作、浅拷贝、深拷贝的效果是一样的

  2. 浅拷贝会将对象复制生成一个新对象,但新对象仍然存储原始对象的引用,当原始对象是可变对象,然后修改它的值时,新旧对象会同时改变

  3. 深拷贝不仅会将对象复制生成一个新对象,且所有原始对象都会复制生成新对象,即使原始对象是可变对象,新对象存储的对象引用也是新的,所以改变旧对象的可变对象时,不会影响新对象