Python基础(下)

这次总结了Python基础的最后半部分,有些内容总结的不是很到位,后续发现问题也会再改正。

16. 字符串

16.1 字符串的驻留机制

  • 字符串
    • 在Python中字符串是基本数据类型,是一个不可变的字符序列
  • 什么叫字符串驻留机制呢?
    • 仅保存一份相同且不可变字符串的方法,不同的值被存放在字符串的驻留池中,Python的驻留机制对相同的字符串只保留一份拷贝,后续创建相同字符串时候,不会开辟新空间,而是把该字符串的地址赋给新创建的变量
1
2
3
4
5
6
7
# 字符串的驻留机制
a = 'Python'
b = "Python"
c = '''Python'''
print(a, id(a)) # Python 1827795263216
print(b, id(b)) # Python 1827795263216
print(c, id(c)) # Python 1827795263216

由此可见,无论是a还是b还是c,他们指向的空间都是同一块地址

  • 驻留机制的几种情况(交互模式)
    • 字符串的长度为0或者1时
    • 符合标识符的字符串
    • 字符串直在编译时进行驻留,而非运行时
    • [-5, 256]之间的整数数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
C:\Users\11793>python
Python 3.9.2 (tags/v3.9.2:1a79785, Feb 19 2021, 13:44:55) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
------------------------------------
>>> s1=''
>>> s2=''
>>> s1 is s2 创建两个字符串,正好他们的长度为0,都是空字符串,那么得到的就是 同一块地址。
True 得到的答案还是True,对应着驻留机制的第一种情况
------------------------------------
>>> s1='%'
>>> s2='%'
>>> s1 is s2 创建两个字符串,正好他们的长度为1,都是只有1个长度的字符串,那么得到的就是 同一块地址。
True 得到的答案还是True,对应着驻留机制的第一种情况
------------------------------------
>>> s1='abc%'
>>> s2='abc%'
>>> s1==s2
True
>>> s1 is s2 两个字符串不是具有符合标识符的字符串,所以得到的结果是false,指向的不是同一块空间
False
>>> id(s1)
2149930897456
>>> id(s2)
2149930897520
------------------------------------
>>> s1='abcx'
>>> s2='abcx'
>>> s1 is s2 两个字符串是符合标识符的字符串,那么他们指向的就是同一块空间
True 得到的答案就是True,所以他们对应着驻留机制的第二种情况
>>> id(s1)
2149930897584
>>> id(s2)
2149930897584
------------------------------------
>>> a='abc'
>>> b='ab'+'c'
>>> c=''.join(['ab','c'])
>>> a is b ab是在编译的时候,都成为了abc,所以在编译的时候就已经具有驻留机制
True 他们得到的结果也是True,指向同一块空间地址,对应驻留机制的第三中情况
>>> a is c
False 但是c用的是join方法,他是在运行的时候才生成abc,所以不具有驻留机制
>>> c
'abc'
>>> type(c)
<class 'str'>
------------------------------------
>>> a=-5
>>> b=-5
>>> a is b 这是对应驻留机制的最后一条,数字是有范围的,在[-5, 256]之间的数字就具有驻留机制
True
>>> a=-6
>>> b=-6
>>> a is b
False
------------------------------------
>>> import sys
>>> a='abc%'
>>> b='abc%'
>>> a is b
False
>>> a=sys.intern(b)
>>> a is b 这是利用sys中的intern方法,可以强制将两个字符串指向同一个对象,从而具有驻留机制
True
  • sys中的intern()方法强制两个字符串指向同一个对象
  • PyCharm对字符串进行了优化处理

字符串驻留机制的优缺点

  • 当需要值相同的字符串时,可以直接从字符串池里拿出来使用,避免频繁的创建和销毁,提升效率和节约内存,因此拼接字符串和修改字符串是会比较影响性能的
  • 在需要进行支付穿拼接的时候建议使用str类型的join方法,而非+,因为join()方法是闲计算出所有字符串中的长度,然后再拷贝,只new一次对象,效率比“+”效率高

16.2 字符串的常用操作

1. 字符串的查询操作的方法

方法名称 作用
index() 查找子串substr第一次出现的位置,如果查找的子串不存在时,则抛出ValueError
rindex() 查找子串substr最后一次出现的位置,如果查找的子串不存在时,则抛出ValueError
find() 查找子串substr第一次出现的位置,如果查找的子串不存在时,则返回-1
rfind() 查找子串substr最后一次出现的位置,如果查找的子串不存在时,则返回-1
1
2
3
4
5
6
7
8
9
# 字符串的查询操作
s = 'hello,hello'
print(s.index('lo')) # 3
print(s.find('lo')) # 3
print(s.rindex('lo')) # 9
print(s.rfind('lo')) # 9

print(s.index('k')) # ValueError: substring not found
print(s.find('k')) # -1

由上述代码空可见,indexfind都能查找子串出现的位置,但是index会抛出异常,所以一般建议使用find方法


2. 字符串的大小写转换操作的方法

方法名称 作用
upper() 把字符串中所有字符都转换成大写字母
lower() 把字符串中所有字符都转换成小写字母
swapcse() 把字符串中所有大写字母转换成小写字母,把所有小写字母转换成大写字母
capitalize() 把第一个字符转换成大写,把其余字符转换成小写
title() 每一个单词的第一个字符转换成大写,把每个单词剩余字符转换成小写
1
2
3
4
5
6
7
8
9
10
11
12
13
# 字符串中的大小写转换的方法
s = 'hello,python'
print(s, id(s)) # hello,python 2514180985840
a = s.upper() # 转成大写会产生一个新的字符串对象
print(a, id(a)) # HELLO,PYTHON 2380455957488
b = s.lower() # 转换小写之后,仍然会产生一个新的字符串对象
print(b, id(b)) # hello,python 2329172722096
print(s is b) # False

s2 = 'hello,Python'
print(s2.swapcase()) # HELLO,pYTHON 小写变大写,大写变小写
print(s2.capitalize()) # Hello,python 首字母大写,其他字母小写
print(s2.title()) # Hello,Python 每一个单词的首字母大写,其余字母小写

3. 字符串内容对齐操作的方法

方法名称 作用
center() 居中对齐,第1个参数指定宽度,第2个参数指定填充符,第2个参数的可选的,默认是空格,如果设置宽度小于实际宽度则返回原字符串
ljust() 左对齐,第1个参数指定宽度,第2个参数指定填充符,第2个参数的可选的,默认是空格,如果设置宽度小于实际宽度则返回原字符串
rjust() 右对齐,第1个参数指定宽度,第2个参数指定填充符,第2个参数的可选的,默认是空格,如果设置宽度小于实际宽度则返回原字符串
zfill() 右对齐,左边用0填充,该方法只接收一个参数,用于指定字符串的宽度,如果指定的宽度小于等于字符串的长度,返回字符串本身
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 字符串的对齐方式
s = 'hello,Python'
"""居中对齐"""
print(s.center(20, '*')) # ****hello,Python****
print(s.center(20)) # hello,Python ,如果第二个参数不写,填充符默认为空格
"""左对齐"""
print(s.ljust(20, '*')) # hello,Python********
print(s.ljust(10)) # hello,Python
"""右对齐"""
print(s.rjust(20, '*')) # ********hello,Python
print(s.rjust(20)) # hello,Python
print(s.rjust(10)) # hello,Python 第一个从参数的长度小于字符串实际长度,所以还是原样显示
"""右对齐,使用0进行填充"""
print(s.zfill(20)) # 00000000hello,Python
print(s.zfill(10)) # hello,Python
print('-8910'.zfill(8)) # -0008910 0添加到了负号的后面

4. 字符串劈分操作的方法

方法名称 作用
split() 1. 从字符串的左边开始劈分,默认的劈分字符是空格字符串,返回的值都是一个列表
2. 以通过参数sep指定劈分字符串是的劈分符
3. 通过参数maxsplit指定劈分字符串时的最大劈分次数,在经过最大次劈分之后,剩余的子串会单独作为一部分
rsplit() 1. 从字符串的右边开始劈分,默认的劈分字符是空格字符串,返回的值都是一个列表
2. 以通过参数sep指定劈分字符串是的劈分符
3. 通过参数maxsplit指定劈分字符串时的最大劈分次数,在经过最大次劈分之后,剩余的子串会单独作为一部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 字符串的劈分操作
s = 'hello world python'

"""split(),从左侧开始劈分"""
lst = s.split()
print(lst) # ['hello', 'world', 'python']
s1 = 'hello|world|python'
print(s1.split()) # ['hello|world|python'] 由于字符串中没有空格,所以没能劈分,还是1个字符串
print(s1.split(sep='|')) # ['hello', 'world', 'python'] ,当我们指定了sep的切割符号之后,就会按照符号来切割
print(s1.split(sep='|', maxsplit=1)) # ['hello', 'world|python']

"""rsplit(),从右侧开始劈分"""
print(s.rsplit()) # ['hello', 'world', 'python']
print(s1.rsplit(sep='|')) # ['hello', 'world', 'python']
print(s1.rsplit(sep='|', maxsplit=1)) # ['hello|world', 'python']

如果字符串劈分没有设定maxsplit,其实splitrsplit的效果是一样的


5. 判断字符串操作的方法

方法名称 作用
isidentifier() 判断指定的字符串是不是合法的标识符(字母、数字、下划线)
isspace() 判断指定的字符串是否全部由空白字符组成(回车、换行,水平制表符)
isalpha() 判断指定的字符串是否全部由字母组成
isdecimal() 判断指定的字符串是否全部由十进制的数字组成
isnumeric() 判断指定的字符串是否全部由数字组成
isalnum() 判断指定的字符串是否全部由字母和数字组成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 判断字符串操作的方法
s = 'hello,python'
print('1、是否为合法标识符', s.isidentifier()) # False
print('张三'.isidentifier()) # True
print('2、是否为空白字符', s.isspace()) # False
print('\t'.isspace()) # True
print('3、是否全部为字母组成', s.isalpha()) # False
print('abc'.isalpha()) # True
print('4、是否全部由十进制数字组成', s.isdecimal()) # False
print('123'.isdecimal()) # True
print('5、是否全部由数字组成', s.isnumeric()) # False
print('123123000'.isnumeric()) # True
print('四五'.isnumeric()) # True
print('Ⅱ'.isnumeric()) # True
print("6、是否全部由字母和数字组成", s.isalnum()) # False
print('123abc'.isalnum()) # True

在上面的代码例子中,有几个比较特殊的地方,一个是罗马数字和汉语数字一二三并不是十进制数字,但是在判断是否由数字组成的时候,他们的返回值是True,就说明,他们虽然不是十进制数字,但是也是数字,系统判断为True


6. 字符串操作的其它方法

功能 方法名称 作用
字符串替换 replace() 第1个参数指定被替换的子串,第2个参数指定替换子串的字符串,该方法返回替换得到的字符串,替换前的字符串不会发生改变,调用该方法时可以通过第3个参数指定最大替换次数
字符串合并 join() 将列表或者元组中的字符串合并为一个字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 字符串操作的其他方法
"""字符串替换"""
s = 'hello,python'
print(s.replace('python', 'java')) # hello,java
s1 = 'hello,python,python,python,python'
print(s1.replace('python', 'java', 2)) # hello,java,java,python,python

"""字符串合并"""
lst = ['hello', 'java', 'python'] # 列表
print('|'.join(lst)) # hello|java|python
print(''.join(lst)) # hellojavapython

t = ('hello', 'java', 'python') # 元组
print(''.join(t)) # hellojavapython

print('*'.join('python')) # 字符串 p*y*t*h*o*n

16.3 字符串的比较

字符串的比较操作

  • 运算符:>、>=、<、<=、==、!=
  • 比较规则:首先比较两个字符串的第一个字符,如果相等则继续比较下一个字符,依次比较下去,直到两个字符串中的字符不相等时,其比较结果就是两个字符串的比较结果,两个字符串中的所有后续字符将不再被比较
  • 比较原理:两个字符进行比较时,比较的是其ordinal value(原始值),调用内置函数ord可以得到指定字符的ordinal value。与内置函数ord对应的是内置函数chr,调用内置函数chr时指定ordinal value可以得到其对应的字符
1
2
3
4
5
6
7
8
9
10
11
# 字符串比较
print('apple' > 'app') # True
print('apple' > 'banana') # False
print(ord('a'), ord('b')) # 97 98
print(chr(97), chr(98)) # a b

"""
== 与 is的区别
1、==比较的是value值
2、is比较的是id是否相等
"""

16.4 字符串的切片操作

字符串是不可变类型

  • 不具备增、删、改等操作
  • 切片操作将产生新的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 字符串的切片
s = 'hello,Python'
s1 = s[:5] # 没有写起始位置,只有结束位置,默认从第一个字符0开始切
s2 = s[6:] # 没有写结束位置,只有起始位置,默认切到最后一个字符
s3 = '!'
newstr = s1+s3+s2
print(s1) # hello
print(s2) # Python
print(newstr) # hello!Python
print('-----------------------------')
print(id(s)) # 2243942765552
print(id(s1)) # 2243945323888
print(id(s2)) # 2243943173552
print(id(newstr)) # 2324519028080
"""
完整格式
切片[start:end:step]
没有指定步长,默认步长就为1
"""
print(s[1:5:1]) # ello 从1开始截取到5(不包含5),步长为1
print(s[::2]) # hloPto 没有写起始,默认从0开始,没有写结束,默认到最后一个元素,步长为2
print(s[::-1]) # nohtyP,olleh 默认从字符串的最后一个元素开始,到字符串第一个元素结束,以为步长为负数
print(s[-6::1]) # Python 从索引为-6开始,到字符串的最后一个元素结束,步长为1

16.5 格式化字符串

1
2
3
4
5
6
7
8
9
10
11
# 格式化字符串
"""(1) % 占位符"""
name = '张三'
age = 20
print('我叫%s,今年%d岁' % (name, age)) # 我叫张三,今年20岁

"""(2) {} 占位符"""
print('我叫{0},今年{1}岁'.format(name, age)) # 我叫张三,今年20岁

"""(3) f-string"""
print(f'我叫{name},今年{age}岁') # 我叫张三,今年20岁

上面代码是常见的三种格式化字符串的表示方式,第一中是利用%占位符,其中%s代表字符串,%i%d代表整数,%f代表浮点数。第三中方法的使用,需要在输出语句的前面加上一个f,来格式化字符串


1
2
3
4
5
6
7
8
9
10
11
"""第一种方法,利用%来确定宽度"""
print('%10d' % 99) # 99,这个10表示的是宽度
print('%.3f' % 3.1415926) # 3.142,这个.3表示小数点后三位
# 同时表示宽度和精度
print('%10.3f' % 3.1415926) # 3.142,一共总宽度为10,小数点后三位

"""第二种方法,利用{}来确定宽度"""
print('{0}'.format(3.1415926)) # 3.1415926
print('{0:.3}'.format(3.1415926)) # 3.14,:.3表示一共保留三位数字
print('{0:.3f}'.format(3.1415926)) # 3.142,:.3f表示三位小数
print('{0:10.3f}'.format(3.1415926)) # 3.142,同时表示宽度和精度

上面代码是来格式化数字的一些宽度和精度的,一共展示了两种比较常见的方法

16.6 字符串的编码与转码

为什么需要字符串的编码转换

A计算机(str在内存种以Unicode表示)——编码——> ——byte字节传输——> ——解码——> B计算机(显示)

编码与解码的方式

  • 编码:将字符串转换成二进制数据(bytes
  • 解码:bytes类型的数据转换成字符串类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 字符串的编码和解码
"""编码操作"""
s = '天涯共此时'
print(s.encode(encoding='GBK')) # b'\xcc\xec\xd1\xc4\xb9\xb2\xb4\xcb\xca\xb1'
# 在GBK这种编码格式种,一个中文占两个字节
print(s.encode(encoding='UTF-8')) # b'\xe5\xa4\xa9\xe6\xb6\xaf\xe5\x85\xb1\xe6\xad\xa4\xe6\x97\xb6'
# 在UTF-8这种编码格式种,一个中文占三个字节

"""解码操作"""
byte = s.encode(encoding='GBK')
print(byte.decode(encoding='GBK')) # 天涯共此时

byte = s.encode(encoding='UTF-8')
print(byte.decode(encoding='UTF-8')) # 天涯共此时

上面的代码,就是字符串的编码和解码的操作,encode为编码,decode为解码,需要注意的是编码和解码的格式必须一一对应

17. 函数

17.1 函数的创建和调用

什么是函数

  • 函数就是执行特定任何以完成特定功能的一段代码

为什么需要函数

  • 复用代码
  • 隐藏实现细节
  • 提高可维护性
  • 提高可读性便于调试

函数的创建

1
2
3
def 函数名([输入参数]) :
函数体
[return xxx]
  • 函数的创建
1
2
3
4
# 函数的创建
def calc(a, b):
c = a + b
return c
  • 函数的调用
1
2
3
# 函数的调用
result = calc(10, 20)
print(result)
  1. 跳到定义函数的函数体内
  2. 执行函数体
  3. 跳到函数的调用处
  4. 继续执行下一条语句

17.2 函数的参数传递

  • 位置实参
    • 根据形参对应的位置进行实参传递
  • 关键字实参
    • 根据形参名称进行实参传递
1
2
3
4
5
6
7
8
9
10
11
# 函数的创建
def calc(a, b): # a,b称为形式参数,简称形参,形参的位置在函数的定义处
c = a + b
return c

# 函数的调用
result = calc(10, 20) # 10,20称为实际参数,简称实参,实参的位置是函数的调用处
print(result)

res = calc(b=10, a=20) # =左侧的变量的名称为关键字,采用关键字传参
print(res) # 30,虽然结果都一样,但是传递参数的值是不一样的,这里的b的10就是传给形参b了,a的20就是传给形参a了

函数调用的参数传递内存分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def fun(arg1, arg2):
print('arg1', arg1) # arg1 11
print('arg2', arg2) # arg2 [22, 33, 44]
arg1 = 100
arg2.append(10)
print('arg1', arg1) # arg1 100
print('arg2', arg2) # arg2 [22, 33, 44, 10]


n1 = 11
n2 = [22, 33, 44]
print('n1', n1) # n1 11
print('n2', n2) # n2 [22, 33, 44]
fun(n1, n2) # 位置传参
print('n1', n1) # n1 11
print('n2', n2) # n2 [22, 33, 44, 10]

"""
在函数的调用过程中,进行参数的传递
如果是不可变对象,在函数体的修改不会影响实参的值 arg1的修改为100,不会影响n1的值
如果是可变对象,在函数体内的修改会影响实参的值 arg2的修改,append(10),会影响n2的值
"""

17.3 函数的返回值

  • 函数返回多个值时,结果为元组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 函数的返回值
def fun(num):
odd = [] # 存奇数
even = [] # 存偶数
for i in num:
if i % 2:
odd.append(i)
else:
even.append(i)
return odd, even


# 函数的调用
lst = [10, 29, 34, 23, 44, 53, 55]
print(fun(lst)) # ([29, 23, 53, 55], [10, 34, 44])

"""
函数的返回值
(1)如果函数没有返回值【函数执行完毕之后,不需要给调用处提供数据】 return可以省略不写
(2)函数的返回值,如果是1个,直接返回类型
(3)函数的返回值,如果是多个,返回的结果为元组
"""


def fun1():
print('hello')
# return


fun1() # hello


def fun2():
return 'hello'


res = fun2()
print(res) # hello


def fun3():
return 'hello', 'world'


print(fun3()) # ('hello', 'world')

"""函数在定义时,是否需要返回值,视情况而定"""

函数在定义时,是否需要返回值,视情况而定,如果需要返回值,就写上返回值,需要注意的就是,返回多个值的时候,返回的类型为元组

17.4 函数的参数定义

  • 函数定义时,给形参设置默认值,只有与默认值不符的时候才需要传递实参
1
2
3
4
5
6
7
def fun(a, b=10):   # b称为默认值参数
print(a, b)


# 函数的调用
fun(100) # 100 10
fun(20, 30) # 20 30

上面代码,说明了如果对形参设定了默认值,而传递参数的时候,只有和默认值不符合的时候才会传递这个实参,例如传递100的时候,不影响b的默认值,则不对b进行传参,仍为10


  • 个位可变的位置参数
    • 定义函数时,可能无法事先确定传递的位置实参的个数时,使用可变的位置参数
    • 使用*定义个人可变的位置形参
    • 结果为一个元组
1
2
3
4
5
6
7
def fun(*args):     # 函数定义时,是可以变化的位置参数
print(args)


fun(10) # (10,)
fun(10, 30) # (10, 30)
fun(30, 29, 40) # (30, 29, 40)

  • 个数可变的关键字形参
    • 定义函数时,无法事先确定传递的关键字实参的个数时,使用可变的关键字形参
    • 使用**定义个数可变的关键字形参
    • 结果为一个字典
1
2
3
4
5
6
7
def fun(**args):
print(args)


fun(a=10) # {'a': 10}
fun(a=20, b=30, c=40) # {'a': 20, 'b': 30, 'c': 40}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"""
def fun2(*args, *a):
pass
以上代码,程序会报错,可变的位置参数,只能是1个

def fun2(**args, **a):
pass
以上代码,程序会报错,个数可变的关键字参数,只能是1个
"""

def fun2(*args1, **args2):
pass


"""
def fun3(**args1, *args):
pass
在一个函数的定义过程中,既有个数可变的关键字形参,也有个数可变的位置形参
要求:个数可变的位置形参,放在个数可变的关键字形参前面
"""

上面代码是函数定义的参数定义的一个特殊情况,如果又有关键字形参也有位置形参,那就要求个数可变的位置形参,放在个数可变的关键字形参的前面


序号 参数的类型 函数的定义 函数的调用 备注
1.1 位置实参
1.2 将序列中的每个元素都转换成位置实参 使用*
2.1 关键字实参
2.2 将序列中的每个键值对都转换为关键字实参 使用**
3 默认值形参
4 关键字形参 使用*
5 个数可变的位置形参 使用*
6 个数可变的关键字形参 使用**

函数调用的时候的参数传递

1
2
3
4
5
6
7
8
9
10
11
12
13
def fun(a, b, c):       # a,b,c在函数的定义处,所以是形式参数
print("a=", a, 'b=', b, 'c=', c)

# 函数的调用
fun(10, 20, 30) # 函数调用时的参数传递,称为位置传参
lst = [11, 22, 33]

fun(*lst) # 在函数调用时,将列表中的每个元素都转换成位置实参传入

fun(a=100, c=300, b=200) # 函数的调用,所以是关键字传参

dic = {'a': 111, 'b': 222, 'c': 333}
fun(**dic) # 在函数调用时,将字典中的键值对都转换成关键字实参传入

函数调用的时候,函数的定义形参问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def fun(a, b=10):   # b是在函数的定义处,所以b是形参,而且进行了赋值,所以b称为默认值形参
print('a=', a, 'b=', b)

def fun2(*args): # 个数可变的位置形参
print(args)

def fun3(**args): # 个数可变的关键字形参
print(args)

fun2(10, 20, 30, 40) # (10, 20, 30, 40)
fun3(a=11, b=12, c=143, d=15) # {'a': 11, 'b': 12, 'c': 143, 'd': 15}

def fun4(a, b, c, d):
print("a=", a, 'b=', b, 'c=', c, 'd=', d)

# 调用fun4函数
fun4(10, 20, 30, 40) # 位置实参传递
fun4(a=10, b=20, c=30, d=40) # 关键字实参传递
fun4(10, 20, c=30, d=40) # 前两个参数,采用的是位置实参传递,而c,d采用的是关键字实参传递

"""
需求,c,d只能采用关键字实参传递
def fun4(a, b, *, c, d): # 从*之后的参数,在函数调用的时候,都必须采用关键字参数传递
"""

"""函数定义时的形参顺序问题"""
def fun5(a, b, *, c, d, **args):
pass

def fun6(*args, **args2):
pass

def fun7(a, b=10, *args, **args2):
pass

17.5 变量的作用域

  • 程序代码能访问该变量的区域
  • 根据变量的有效范围可以分为
    • 局部变量
      • 在函数内定义并使用的变量,只在函数内部有效,局部变量使用global声明,这个变量就成为全局变量
    • 全局变量
      • 函数体外声明的变量,可作用于函数内外
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def fun(a, b):
c = a+b # C成为局部变量,因为C是再函数体内进行定义的变量,a,b为函数的形参,作用范围也是函数内部,相当于局部变量
print(c)

name = '张三' # name的作用范围为函数内部和外部都可以使用,被称为全局变量
print(name)
def fun2():
print(name)

# 调用函数
fun2() # 张三

def fun3():
global age # 函数内部定义的变量,局部变量,局部变量使用global声明,变量就变成全局变量了
age = 20
print(age)

fun3()
print(age) # 20

17.6 递归函数

什么是递归函数

  • 如果在一个函数的函数体内调用了该函数本身,这个函数就称为递归函数

递归的组成部分

  • 递归调用与递归终止条件

递归的调用过程

  • 每递归调用一次函数,都会在栈内存分配一个栈帧
  • 每执行完一次函数,都会释放相应的空间

递归的优缺点

  • 缺点:占用内存多,效率底下
  • 优点:思路和代码简单

使用递归来计算阶乘

1
2
3
4
5
6
7
def fac(n):
if n==1:
return 1
else:
return n*fac(n-1)

print(fac(3))

使用递归来计算斐波那契数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def fib(n):
if n == 1:
return 1
elif n == 2:
return 1
else:
return fib(n-1)+fib(n-2)

# 斐波那契数列在第6位上的数字
print(fib(6))

# 输出这个数列的前6位上的数字
for i in range(1,7):
print(fib(i))

18. Bug

18.1 Bug的由来及分类

马克2号当年计算机出现问题,始终没有修理好,最终打开计算机,发现里面有一只飞蛾,便有了Bug

  • 世界上第一部万用计算机的进化版-马克2号(Mark Ⅱ)

bug的常见类型

  • 粗心导致的语法错误 SyntaxError
  • 知识点不熟练导致的错误
    • (1) 索引越界问题IndexError
    • lst = [11,22,33,44]
    • print(lst[4])应该输出lst[3]
    • (2) append()方法的使用掌握不熟练
    • lst = []
    • lst = append('a','b','c','d') append是列表的方法,在调用的时候应该是lst.append('A')
    • print(lst)并且append方法一次只能添加一个元素

18.2 不同异常类型的处理方式

  • 粗心导致错误的自查宝典

    • 漏了末尾的冒号,如if语句、循环语句、else子句等
    • 缩进错误,该缩进的没缩进,不该缩进的瞎缩进
    • 英文符号写出中文符号,比如说:冒号、引号、括号
    • 字符串拼接的时候,把字符串和数字拼接在一起
    • 没有定义变量,比如说while的循环条件的变量
    • ==”比较运算符和“=”赋值运算符的混用
  • 思路不清晰导致的问题解决方案

    • 使用print()函数
    • 使用“#”暂时注解部分代码
  • 被动掉坑:程序代码逻辑没有错,只是因为用户错误操作或者一些“例外情况”而导致的程序崩溃

    • 被动掉坑问题的解决方案
      • Python提供了异常处理机制,可以在异常出现的时候及时捕获,然后内部“消化”,让程序继续运行
    • 多个except结构
      • 捕获异常的顺序按照先子类后父类的顺序,为了避免遗漏可能出现的异常,可以在最后增加BaseException
1
2
3
4
try:
可能会出现异常的代码
except xxx(异常类型):
报错后执行的代码
1
2
3
4
5
6
7
8
try:
a = int(input('请输入第一个整数:'))
b = int(input('请输入第二个整数:'))
result = a/b
print('结果为:', result)
except ZeroDivisionError:
print('对不起,除数不允许为0')
print('程序结束')

上面的代码是只处理一个异常,所以只使用了一个except


1
2
3
4
5
6
7
8
9
10
11
try:
a = int(input('请输入第一个整数:'))
b = int(input('请输入第二个整数:'))
result = a/b
print('结果为:', result)
except ZeroDivisionError:
print('对不起,除数不允许为0')
except ValueError:
print('不能将字符串转换成数字')
except BaseException as e:
print('程序结束')

上面的代码,想要处理多个异常,所以加了很多个except分支,从而判断异常的类型

18.3 异常处理机制

  • try...except...else结构
    • 如果try块中没有抛出异常,则执行else块,如果try中抛出异常,则执行except
1
2
3
4
5
6
7
8
9
10
try:
a = int(input('请输入第一个整数:'))
b = int(input('请输入第二个整数:'))
result = a/b
except BaseException as e:
print('出错了')
print(e)
else:
print('结果为:', result)
print('程序结束')

  • try...except...else...finally结构
    • finally块无论是否发生异常都会被执行,能常用来释放try块中申请的资源
1
2
3
4
5
6
7
8
9
10
11
12
try:
a = int(input('请输入第一个整数:'))
b = int(input('请输入第二个整数:'))
result = a/b
except BaseException as e:
print('出错了')
print(e)
else:
print('结果为:', result)
finally:
print('无论是否残生异常,总会被执行的代码')
print('程序结束')

  • Python常见的异常错误
序号 异常类型 描述
1 ZeroDivisionError 除(或取模)零(所有数据类型)
2 IndexError 序列中没有此索引(index
3 KeyError 映射中没有这个键
4 NameError 未声明/初始化对象(没有属性)
5 SyntaxError python语法错误
6 ValueError 传入无效的参数

  • traceback模块
    • 使用traceback模块打印异常信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import traceback
try:
print('-----------------------')
print(1/0)
except:
traceback.print_exc()

"""
-----------------------
Traceback (most recent call last):
File "D:\PythonPractice\PyTest\Test01.py", line 820, in <module>
print(1/0)
ZeroDivisionError: division by zero
"""

18.4 PyCharm的调试模式

  • 断点
    • 程序运行到此处,暂时挂起,停止执行,此时可以详细观察程序的运行情况,方便做出进一步判断
  • 进入调试试图
    • 进入调试试图的三种方式
        1. 单机工具栏上的按钮
        2. 右键单击编辑区:点击:debug‘模块名’
        3. 快捷键:shift+F9

19. 编程的两大思想

19.1 两大编程思想

  • 面向过程:事物比较简单,可以用线性的思维去解决
  • 面向对象:事物比较复杂,使用简单的线性思维无法解决
  • 共同点:面向对象和面向过程都是解决实际问题的一种思维方式

二者相辅相成,并不是对立的

解决复杂问题,通过面向对象方式便于我们从宏观上把握事物之间复杂的关系,方便我们分析整个系统,具体到围观操作,仍然使用面向过程方式来处理

19.2 类和对象的创建

    • 类别:分门别类,物以类聚,人类、鸟类、动物类、植物类
    • 类是多个类似事物组成的群体的统称。能够帮助我们快速理解和判断事物的性质

  • 数据类型
    • 不同的数据类型属于不同的类
    • 使用内置函数type()查看数据类型

  • 对象
    • 100、99都是int类之下包含的相似的不同个例,这个个例专业术语称为实例或者对象
    • 一切皆对象、万物皆对象

  • 创建类的语法
1
2
class Student :		# student为类的名称,有一个或多个单词组成,每个单词的首字母大写,其余小写(默认规则)
pass

  • 类的组成
    • 类属性
    • 实例方法
    • 静态方法
    • 类方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class student:
native_plase = '河南' # 直接写在类里面的变量,称为类属性
def __init__(self, name, age): # init初始化方法
self.name = name # 赋值操作,将局部变量的name的值赋给实体属性
self.age = age # self、name称为实体属性
# 实例方法
def info(self):
print('我的名字叫做:', self.name, '年龄为:', self.age)
# 类方法
@classmethod
def cm(cls):
print('类方法')
# 静态方法
@staticmethod
def sm():
print('静态方法')

# 在类之外定义的称为函数,在类之内定义的称为方法

  • 对象的创建
    • 对象的创建又称为类的实例化
    • 语法:实例名 = 类名()
    • 意义:有了实例,就可以调用类中的内容

19.3 类对象与类属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建student类的实例对象
stu = student('张三', 19)
print(id(stu))
print(type(stu))
print(stu.name)
print(stu.age)
stu.info()
"""
2877277908752
<class '__main__.student'>
张三
19
我的名字叫做: 张三 年龄为: 19
"""

在调用方法的时候,有一些不一样的方式,第一种就是对象名.方法名,如stu1.eat(),第二种是类名.方法名(类的对象),如student.eat(stu1),这里的类的对象,其实就是指在声明中的self

19.4 类方法与静态方法

  • 类属性:类中方法外的变量称为类属性,被该类的所有对象所共享
  • 类方法:使用@classmethod修饰的方法,使用类名直接访问的方法
  • 静态方法:使用@staticmethod修饰的主发,使用类名直接访问的方法
1
2
3
print(student.native_place)	# 访问类属性
student.cm() # 调用类方法
student.sm() # 调用静态方法

动态绑定属性和方法

  • Python是动态语言,在创建对象之后,可以动态地绑定属性和方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Student:
def __init__(self, name, age): # 初始化方法
self.name = name
self.age = age
def eat(self):
print(self.name+'在吃饭')

stu1 = Student('张三', 20)
stu2 = Student('李四', 30)
print(id(stu1))
print(id(stu2))
print('-------------为stu2动态绑定性别属性---------------')
stu2.gender = '女' # 动态绑定属性
print(stu1.name, stu1.age)
print(stu2.name, stu2.age, stu2.gender)

def show():
print('定义在类之外的,称为函数')
print('-------------为stu2动态绑定方法---------------')
stu1.show = show # 动态绑定方法
stu1.show()
  • 编程实现
    • 面向对象
    • 面向过程
  • 类对象class
    • 类属性
    • 类方法
    • 实例方法
    • 静态方法
  • 实例对象
    • 类名(),创建实例对象
    • 动态绑定属性
    • 动态绑定方法

20. 面向对象的三大特性

面向对象的三大特性

  • 封装:提高程序的安全性
    • 将数据(属性)和行为(方法)包装到类对象中。在方法内部对属性进行操作,在类对象的外部调用方法。这样,无需关心方法内部的具体实现细节,从而隔离了复杂类
    • 在Python中没有专门的修饰符用于属性的私有,如果该属性不希望在类对象外部被访问,前边使用两个”_”
  • 继承:提高代码的复用性
  • 多态:提高程序的可扩展性和可维护性

20.1 封装

封装就是将一些属性或者方法封装在类内,使得类内可以访问,类外不能访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Student:
def __init__(self, name, age):
self.name = name
self.__age = age # 年龄不希望在类的外部被使用,所以加了两个_

def show(self):
print(self.name, self.__age)

stu = Student('张三', 20)
stu.show()
# 在类的外面使用name和age
print(stu.name)
# print(stu.__age) # AttributeError: 'Student' object has no attribute '__age'
# print(dir(stu)) # 打印出来stu的所有属性
print(stu._Student__age) # 在类的外部可以通过 _Student__age 进行访问

20.2 继承

  • 语法格式
1
2
3
class 子类类名(父类1, 父类2...):
pass

  • 如果一个类没有继承任何类,则默认继承object
  • python支持多继承
  • 定义子类时,必须在其构造函数中调用父类的构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 定义人类父类
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def info(self):
print('姓名:{0}, 年龄:{1}'.format(self.name, self.age))
# 定义学生子类
class Student(Person):
def __init__(self, name, age, score):
super().__init__(name, age)
self.score = score

# 定义老师子类
class Teacher(Person):
def __init__(self, name, age, teacherFyear):
super().__init__(name, age)
self.teacherFyear = teacherFyear

# 测试
stu = Student('张三', 20, 90)
stu.info()
teacher = Teacher('李四', 34, 10)
teacher.info()

20.3 方法重写

  • 如果子类对继承自父类的某个属性或方法不太满意,可以从子类中对其(方法体)进行重新编写
  • 子类重写后的方法中可以通过super().xxx()调用父类中被重写的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def info(self):
print('姓名:{0}, 年龄:{1}'.format(self.name, self.age))
# 定义学生子类
class Student(Person):
def __init__(self, name, age, score):
super().__init__(name, age)
self.score = score
def info(self): # 子类方法重写
super().info()
print('学生分数:{}'.format(self.score))
# 定义老师子类
class Teacher(Person):
def __init__(self, name, age, teacherFyear):
super().__init__(name, age)
self.teacherFyear = teacherFyear
def info(self): # 子类方法重写
super().info()
print('教师年龄:{}'.format(self.teacherFyear))
# 测试
stu = Student('张三', 20, 90)
stu.info()
teacher = Teacher('李四', 34, 10)
teacher.info()

"""
姓名:张三, 年龄:20
学生分数:90
姓名:李四, 年龄:34
教师年龄:10
"""

20.4 object

  • boject类是所有类的父亲,因此所有类都有object类的属性和方法
  • 内置函数dir()可以查看指定对象所有属性
  • object有一个__str__()方法,用于返回一个对于“对象的描述”,对应于内置函数str()经常用于print()方法,帮助我们查看对象的信息,所以我们经常会对__str__()进行重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student:
def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self): # 重写str方法
return '我的名字是{0},今年{1}岁了'.format(self.name, self.age)
stu = Student('张三', 20)
print(dir(stu)) # 内置函数,查看指定对象的所有属性
"""
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
"""
print(stu) # 默认会调用__str__()这样的方法
# 我的名字是张三,今年20岁了

我们经常会在定义一个类之后,重写他的__str__()方法,来对类进行一个描述

20.5 多态

  • 简单的说,多态就是“具有多种形态”,它指的是:即便不知道一个变量所引用的对象到底是什么类型,仍然可以通过这个变量调用方法,在运行过程中根据变量所引用对象的类型,动态决定调用哪个对象中的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Animal(object):
def eat(self):
print('动物会吃')
class Dog(Animal):
def eat(self):
print('狗吃骨头')
class Cat(Animal):
def eat(self):
print('猫吃鱼')

class Person:
def eat(self):
print('人吃五谷杂粮')

# 定义一个函数
def fun(obj):
obj.eat()

# 开始调用函数
fun(Cat())
fun(Dog())
fun(Animal())
fun(Person())
"""
猫吃鱼
狗吃骨头
动物会吃
人吃五谷杂粮
"""
  • 静态语言和动态语言关于多态的区别
    • 静态语言实现多态的三个必要条件
      • 继承
      • 方法重写
      • 父类引用指向子类对象
    • 动态语言的多态崇尚“鸭子类型”,当看到一只鸟走起来像鸭子、游泳起来像鸭子、收起来也想鸭子,那么这只鸟就可以被称为鸭子。在鸭子类型中,不需要关心对象是什么类型,到底是不是鸭子,只关心对象的行为

20.6 特殊方法和特殊属性

名称 描述
特殊属性 __dict__ 获得类对象或实例对象所绑定的所有属性和方法的字典
特殊方法 __len__() 通过重写方法,让内置函数len()的参数可以自定义类型
特殊方法 __add__() 通过重写方法,可使用自定义对象具有“+”功能
特殊方法 __new__() 用于创建对象
特殊方法 __init__() 对创建的对象进行初始化

  • 特殊属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A:
pass
class B:
pass
class C(A, B):
def __init__(self, name):
self.name = name
# 创建C类的对象
x = C('张三') # x就是C类型的一个实例对象
print(x.__dict__) # 实例对象的属性字典 {'name': '张三'}
print(C.__dict__)
print('-------------')
print(x.__class__) # <class '__main__.C'> 输出了对象所属的类
print(C.__bases__) # C类的父类类型的元素 (<class '__main__.A'>, <class '__main__.B'>)
print(C.__base__) # C类的父类类型的一个基类 <class '__main__.A'>
print(C.__mro__) # 类的层次结构
print(A.__subclasses__()) # 子类的列表 [<class '__main__.C'>]

  • 特殊方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Student:
def __init__(self, name):
self.name = name
def __add__(self, other):
return self.name + other.name
def __len__(self):
return len(self.name)


stu1 = Student('张三')
stu2 = Student('李四')
s = stu1+stu2 # 实现了两个对象的加法运算(因为在student类中,编写了__add__()特殊 的方法)
print(s)
s = stu1.__add__(stu2)
print(s)
print('------------------')
lst = [11, 22, 33, 44]
print(len(lst)) # len是内置函数len
print(len(stu1)) # 2
print(len(stu2)) # 2

下面的代码主要是介绍__init__()__new__()方法的意思

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Person:
def __new__(cls, *args, **kwargs):
print('__new__被调用执行了,cls的id值为{0}'.format(id(cls)))
obj = super().__new__(cls)
print('创建的对象id为:{0}'.format(id(obj)))
return obj

def __init__(self, name, age):
print('__init__被调用了,self的id值为:{0}'.format(id(self)))
self.name = name
self.age = age

print('object这个类对象的id为:{0}'.format(id(object)))
print('person这个类对象的id为:{0}'.format(id(Person)))

# 创建Person类的实例对象
p1 = Person('张三', 20)
print('p1这个person类的实例对象的id:{0}'.format(id(p1)))

"""
object这个类对象的id为:140710131355136
person这个类对象的id为:2337138898688
__new__被调用执行了,cls的id值为2337138898688
创建的对象id为:2337139834592
__init__被调用了,self的id值为:2337139834592
p1这个person类的实例对象的id:2337139834592
"""

根据上面代码的输出结果,不难得出有一些结论,其实在创建实例对象的过程中,首先先创建了一个Person('张三', 20),在类内,先是调用了new方法,在方法中又调用 了父类objectnew方法,之后调用了init方法,最后才把实例化的Person赋值给p1

20.7 类的深拷贝和浅拷贝

  • 变量的赋值操作
    • 只是形成两个变量,实际上还是指向同一个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
class CPU:
pass
class Disk:
pass
class Computer:
def __init__(self, cpu, disk):
self.cpu = cpu
self.disk = disk
# (1)变量的赋值
cpu1 = CPU()
cpu2 = cpu1
print(cpu1) # <__main__.CPU object at 0x000001F9FF699FD0>
print(cpu2) # <__main__.CPU object at 0x000001F9FF699FD0>

  • 浅拷贝
    • Python拷贝一般都是浅拷贝,拷贝时,对象包含的子对象内容不拷贝,因此,源对象与拷贝对象会引用同一个子对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class CPU:
pass
class Disk:
pass
class Computer:
def __init__(self, cpu, disk):
self.cpu = cpu
self.disk = disk
# (2)类的浅拷贝
print('------------------------')
disk = Disk() # 创建一个硬盘类的对象
computer = Computer(cpu1, disk) # 创建一个计算机类的对象
print(disk)
# 浅拷贝
import copy
computer2 =copy.copy(computer)
print(computer, computer.cpu, computer.disk)
print(computer2, computer2.cpu, computer2.disk)
print('------------------------')

"""
<__main__.Computer object at 0x000001FBCA0EAF70> <__main__.CPU object at 0x000001FBCA0EAFD0> <__main__.Disk object at 0x000001FBCA0EAFA0>
<__main__.Computer object at 0x000001FBCA0EAD90> <__main__.CPU object at 0x000001FBCA0EAFD0> <__main__.Disk object at 0x000001FBCA0EAFA0>
"""

由代码打印出来的id可以得到,浅拷贝只会拷贝父对象,子对象没有进行拷贝,得到的子对象的id都是相同的


  • 深拷贝
    • 使用copy模块的deepcopy函数,递归拷贝对象中包含的子对象,源对象和拷贝对象所有的子对象也不相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class CPU:
pass
class Disk:
pass
class Computer:
def __init__(self, cpu, disk):
self.cpu = cpu
self.disk = disk

print('------------------------')
disk = Disk() # 创建一个硬盘类的对象
computer = Computer(cpu1, disk) # 创建一个计算机类的对象
print(disk)
# (3)类的深拷贝
computer3 = copy.deepcopy(computer)
print(computer, computer.cpu, computer.disk)
print(computer3, computer3.cpu, computer3.disk)
"""
<__main__.Computer object at 0x000001FBCA0EAF70> <__main__.CPU object at 0x000001FBCA0EAFD0> <__main__.Disk object at 0x000001FBCA0EAFA0>
<__main__.Computer object at 0x000001FBCA0EAC10> <__main__.CPU object at 0x000001FBCA0EA1C0> <__main__.Disk object at 0x000001FBCA0EC940>
"""

由打印出来的id可以发现,深拷贝中,不仅父对象被拷贝,父对象中的每个子对象也都被拷贝

21. 模块

21.1 什么叫模块

  • 模块的英文单词为Modules
  • 函数与模块的关系
    • 一个模块中可以包含N多个函数
  • 在Python中一个扩展名为.py的文件就是一个模块
  • 使用模块的好处
    • 方便其它程序和脚本的导入与使用
    • 避免函数名和变量名冲突
    • 提高代码的可维护性
    • 提高代码的可重用性

21.2 自定义模块

  • 创建模块
    • 新建一个.py文件,名称尽量不要与Python自带的标准模块名称相同
  • 导入模块
1
2
import 模块名称 [as别名]
from 模块名称 import 函数/变量/类

21.3 以主程序的形式执行

  • 在每个模块的定义中都包括一个记录模块名称的变量__name__,程序可以检查该变量,以确定他们在哪个模块中执行。如果一个模块不是被导入到其它程序中执行,那么它可能在解释器的顶级模块中执行。顶级模块的__name__变量的值为__main__
1
2
if __name__ = '__main__':
pass
1
2
3
4
5
def add(a, b):
return a+b

if __name__ == '__main__':
print(add(10, 20)) # 只有运行test01时,才会执行运算

21.4 Python中的包

  • 包是一个分层次的目录结构,它将一组功能相近的模块组织在一个目录下

  • 作用:

    • 代码规范
    • 避免模块名称冲突
  • 包和目录的区别

    • 包括__init__.py文件的目录称为包
    • 目录里通常不包含__init__.py文件
  • 包的导入

    • import 包名.模块名
  • 导入带有包的模块时注意事项

    • import pageage1
    • import cakc
    • 使用import方式导入时,只能跟包名或者模块名
    • from pageage1 import module_A
    • from pageage1.module_A import a
    • 使用from...import可以导入包、模块、函数、变量

21.5 第三方模块的安装以及使用

  • python中常用的内置模块
模块名 描述
sys 与Python解释器及其环境操作相关的标准库
time 提供与时间相关的各类函数的标准库
os 提供了访问操作系统服务功能的标准库
calendar 提供与日期相关的各类函数的标准库
urllib 用于读取来自网上(服务器)的数据标准库
json 用于使用json序列化和反序列化对象
re 用于在字符串中执行正则表达式匹配和替换
math 提供标准算术运算函数的标准库
decimal 用于进行精确控制运算精度、有效数位和四舍五入操作的十进制运算
logging 提供了灵活的记录事件、错误、警告和调试信息等日志信息的功能
  • 第三方模块的安装
    • pip install 模块名
  • 第三方模块的使用
    • import 模块名

22. 编码

22.1 编码格式介绍

  • 常见的字符编码格式
    • Python的解释器使用的是Unicode(内存)
    • .py文件在磁盘上使用UTF-8存储(外存)

22.2 文件的读写原理

  • 文件读写俗称“IO操作”
  • 文件读写操作流程
  • 操作原理
    • Python操作文件
    • 打开或新建文件
    • 读、写文件
    • 关闭资源
    • 又从.py文件——>解释器——>OS——>操作——>硬盘

22.3 文件的读写操作

  • 内置函数opem()创建文件对象
  • 语法规则file = open(filename [, mode, encoding])
1
2
3
file = open('a.txt', 'r')
print(file.readlines())
file.close()

  • 常用的文件打开模式
    • 按照文件中数据的组织形式,文件分为以下两大类
      • 文本文件:存储的普通“字符”文本,默认为Unicode字符集,可以使用记事本程序打开
      • 二进制文件:把数据内容用“字节”进行存储,无法用记事本打开,必须使用专用的软件打开,如MP3音频文件,jpg图片等
打开模式 描述
r 以只读模式打开文件,文件的指针将会放在文件的开头
w 以只写模式打开文件,如果文件不存在则创建,如果文件存在,则覆盖原有内容,文件的指针在文件的开头
a 以追加模式打开文件,如果文件不存在则创建,文件指针在文件开头,如果文件存在,则在文件末尾追加内容,文件指针在原文件末尾
b 以二进制方式打开文件,不能单独使用,需要与其它模式一起使用,rb或者wb
+ 以读写方式打开文件,不能单独使用,需要与其它模式一起使用,如 a+

22.4 文件对象常用的方法

方法名 说明
read([size]) 从文件中读取size个字节或字符的内容返回。若省略[size],则读取到文件末尾,即一次读取文件所有内容
readline() 从文本文件中读取一行内容
readlines() 把文本文件中每一行都作为独立的字符串对象,并将这些对象放入列表返回
write(str) 将字符串str内容写入文件
writelines(s_list) 将字符串列表s_list写入文本文件,不添加换行符
seek(offset[, whence]) 把文件指针移动到新的位置,offset表示相对于whence的位置:
offset:为正往结束方向移动,为负往开始方向移动
whence不同的值代表不同含义:
0:从文件开头开始计算(默认值)
1:从当前位置开始计算
2:从文件尾开始计算
tell() 返回文件指针的当前位置
flush() 把缓冲区的内容写入文件,但不关闭文件
close() 把缓冲区的内容写入文件,同时关闭文件,释放文件对象相关资源

22.5 with语句(上下文管理器)

  • with语句可以自动管理上下文资源,无论什么原因跳出with块,都能确保文件正确的关闭,以此来达到释放资源的目的
1
2
with open('logo.png', 'rb') as src_file:
print('src_file.read()')
1
2
3
4
5
with open('a.txt', 'r') as file:
print(file.read())
"""
你好,世界!
"""

总结

22.6 目录操作

  • OS模块是Python内置的与操作系统功能和文件系统相关的模块,该模块中的语句的执行结果通常与操作系统有关,在不同的操作系统上运行,得到的结果可能不一样
  • OS模块与os.path模块用于对目录或者文件进行操作
1
2
3
4
5
6
7
# os模块与操作系统相关的一个模块
import os
os.system('notepad.exe') # 打开记事本文件
os.system('calc.exe') # 打开系统自带的计算器

# 直接调用可执行文件
os.startfile('D:\\QQ\\Bin\\QQ.exe')

  • os模块操作目录相关函数
函数 说明
getcwd() 返回当前的工作目录
listdir(path) 返回指定路径下的文件和目录信息
mkdir(path[, mode]) 创建目录
makedirs(path1/path2...[, mode]) 创建多级目录
rmdir(path) 删除目录
removedirs(path1/path2...) 删除多级目录
chdir(path) 将path设置为当前工作目录

  • os.path模块操作目录相关函数
函数 说明
abspath(path) 用于获取文件或目录的绝对路径
exists(path) 用于判断文件或目录是否存在,如果存在返回True,否则返回False
join(path, name) 将目录与目录或者文件拼接起来
splitext() 分离文件名和扩展名
basename(path) 从一个目录中提取文件名
dirname(path) 从一个路径中获取文件路径,不包括文件名
isdir(path) 用于判断是否为路径

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!