Python 入门

Python 入门

注释

  • 单行注释

    # print('Hello, World!')
    print('Hello, World!')    # 打印 Hello, World!
    
  • 多行注释,(‘’'或"“”)

    '''
    第一行注释
    第二行注释
    第三行注释
    '''
    
    """
    第一行注释
    第二行注释
    第三行注释
    """
    

变量

语法格式:

variablename = value

example:

my_name = 'TOM'    # 定义变量:TOM
schoolName = '存储数据'    # 定义变量:存储数据

print(my_name)
print(schoolName)

数据类型

int:整数类型

  • 十进制表示方式:0 不能作为十进制数的开头,如:20
  • 二进制表示方式:以阿拉伯数字 0 与 英文字母 B(或 b)作为前缀,如:0B10100b1010
  • 八进制表示方式:以阿拉伯数字 0 与 英文字母 O(或 o)作为前缀,如:0O340o34
  • 十六进制表示方式:以阿拉伯数字 0 与 英文字母 X(或 x)作为前缀,如:0X1c0x1c

example:

my_int = 1

print(type(my_int))    # 输出结果:<class 'int'>

float:浮点类型,就是小数

在科学计数法中使用 E(或 e)表示乘以 10 的几次方,如:1e2 表示 10^2^,1 乘 10 的 2 次方

example:

my_float = 1.1
my_float01 = 1e2

print(type(my_float))    # 输出结果:<class 'float'>
print(my_float01)    # 输出结果:100.0,表示 1 乘 10 的 10 次方

complex:复数

整数和浮点数在数学中统称为实数,与实数对应的是复数,复数在数学中被表示为:a+bi,其中 a 被称为实部,b 被称为虚部,i 被称为虚数单位

example:

my_float = 4 + 4.5j

print(type(my_float))    # 输出结果:<class 'complex'>

str:字符串类型,特点:数据都要带引号

example:

my_str = 'Hello, World!'

print(type(my_str))    # 输出结果:<class 'str'>

bool:布尔类型

  • 通常判断使用,布尔型有两个取值 TrueFalse

  • 只有以下几种情况值是 False,其它对象在 ifwhile 语句中都为真

    • FalseNone
    • 数值中的零,包括 00.0虚数 0
    • 空序列,包括字符串、空元组、空列表、空字典
    • 自定义对象的实例,该对象的 __bool__ 方法返回 False__len__ 方法返回 0

example:

my_bool = True

print(type(my_bool))    # 输出结果:<class 'bool'>

list:列表

example:

my_list = [10, 20, 30]

print(type(my_list))    # 输出结果:<class 'list'>

tuple:元组

example:

my_tuple = (10, 20, 30)

print(type(my_tuple))    # 输出结果:<class 'tuple'>

set:集合

example:

my_set = {10, 20, 30}

print(type(my_set))    # 输出结果:<class 'set'>

dict:字典

example:

my_dict = {'name': 'Tom', 'age': '18'}

print(type(my_dict))    # 输出结果:<class 'dict'>

数字类型的相互转换

在 Python 的数字类型中,除复数外,其他三种数字类型如:整数、浮点和布尔值都可以相互转换,分为隐式类型的转换和显式类型的转换。

隐式类型转换

数字之间可以进行数学计算,在进行数学计算时若数字类型不同,则会发生隐式类型的转换。

操作数 1 的类型操作数 2 的类型转换后的类型
布尔值整数整数
布尔值、整数浮点浮点

example:

a = 1 + True
print(a)    # 输出结果:2,True 被转换为整数 1


a = 1 + False
print(a)    # 输出结果:1,False 被转换为整数 0


a = 1.0 + 1
print(a)    # 输出结果:2.0,整数 1 被转换为浮点数 1.0


a = 1.0 + True
print(a)    # 输出结果:2.0,True 被转换为浮点数 1.0

显示类型转换

除复数外,其他三种数字类型如:整数、浮点和布尔值都有自己的转换函数,分别是 int()float()bool() 函数

example:

print(int(False))    # 输出结果:0,int(False) 将布尔值 False 转换为整数 0

print(int(0.6))    # 输出结果:0,int(0.6) 将浮点数 0.6 转换为整数 0

print(float(5))    # 输出结果:5.0,float(5) 将整数 5 转换为浮点数 5.0

print(float(False))    # 输出结果:0.0,float(False) 将布尔值 False 转换为浮点数 0.0

print(bool(5))    # 输出结果:True,bool(5) 将整数 5 转换为布尔值 True

print(bool(0.6))    # 输出结果:True,bool(0.6) 将浮点数 0.6 转换为布尔值 True

运算符

算数运算符

算术运算符用于组织整数类型和浮点类型的数据,有一元运算符和二元运算符之分

一元算术运算符有两个:+(正号)和-(负号),例如:+a 还是 a,-a 是对 a 的取反运算

二元算术运算符如下表所示:

算数运算符作用
++依次相加
- -依次相减
+
-
*
/
%取余或(求模)
**指数或(幂)
//取整除

example:

print(1 + 1)    # 加,输出结果:2

print(1 - 1)    # 减,输出结果:0

print(2 * 2)    # 乘法,输出结果:4

print(4 / 2)    # 除法,输出结果:2.0

print(9 // 4)    # 整除,输出结果:2

print(9 % 4)    # 取余或(求模),输出结果:1

print(2 ** 4)    # 指数或(幂),输出结果:16

print((1 + 2) * 3)    # 小括号,输出结果:9

赋值运算符

赋值运算符只是一种简写,只有算数和位运算符中的二元运算符才有对应的赋值运算符

赋值运算符作用
=普通赋值
+=加赋值
-=减赋值
*=乘赋值
/=除赋值
%=取余赋值
**=幂赋值
//=整除赋值
&=位与赋值
|=位或赋值
^=位异赋值
<<=左移赋值
>>=右移赋值

example:

num = 1    # 单个变量赋值

print(num)


num1, float1, str1 = 10, 0.5, 'hello world'    # 多个变量赋值

print(num1)
print(float1)
print(str1)


a = b = 10    # 多变量赋相同值

print(a)
print(b)


a = 100
a += 1  # a = a + 1,加赋值运算符

print(a)


b = 100
b -= 1  # b = b - 1,减赋值运算符

print(b)


c = 100
c *= 2  # c = c * 2,乘赋值运算符

print(c)


d = 100
d /= 2  # d = d / 2,除赋值运算符

print(d)


e = 100
e //= 3  # e = // 3,整除赋值运算符

print(e)


f = 100
f %= 3  # f = f % 3,取余赋值运算符

print(f)


g = 100
g **= 3  # g = g ** 3,幂赋值运算符

print(g)


h = 100
h *= 1 + 2  # h = 100 * (1 + 2),先算复合赋值运算符右边的表达式,再算复合赋值运算

print(h)

比较运算符

比较运算符用于比较两个表达式的大小,其结果是布尔类型的数据,即 TrueFalse

比较运算符可用于任意类型的数据,但参与比较的两种类型的数据要相互兼容,即能进行隐式转换或显示转换

比较运算符作用
>大于
<小于
==等于
!=不等于
>=大于等于
<=小于等于

example:

print(2 > 1)    # 判断左边大于右边为 True

print(1 < 2)    # 判断左边小于右边为 True

print(1 == 1)    # 判断左边等于右边

print(1 != 2)    # 判断左边不等于右边

print(2 >= 1)    # 判断左边大于等于右边为 True
print(2 >= 2)

print(1 <= 2)    # 判断的左边小于等于右边为 True
print(2 <= 2)

逻辑运算符

逻辑运算符用于对布尔型变量进行运算,其结果也是布尔型

逻辑运算符作用
and逻辑与:a and b,and 两边都为 True 时,结果为 True,否则为 False
or逻辑或:a or b,or 两边都为 False 时,结果为 False,否则为 True
not逻辑非:not a,a 为 True 时,结果为 False;a 为 False 时,结果为 True

example:

print((1 < 2) and (2 > 1))    # 逻辑与运算

print((1 < 2) or (1 > 2))    # 逻辑或运算

print(not False)    # 逻辑非运算,取反

数字之间逻辑运算

print(0 and 1)    # and 运算符,只要有一个值为 0,结果为 0,否则结果为最后一个非 0 数字
print(1 and 0)
print(1 and 2)
print(2 and 1)

print(0 or 0)    # or 运算符,只要所有值为 0,结果才为 0,否则结果为第一个非 0 数字
print(0 or 1)
print(1 or 0)
print(1 or 2)
print(2 or 1)

位运算符

位运算是以二进位(bit)为单位进行运算的,操作数和结果都是整数类型的数据

位运算符作用
&位与:只有对应操作数位都是 1 时,结果位才是 1,否则为 0
^位异:只有对应操作数位都是 0 或都是 1 时,结果位才是 0,否则位 1
~位反:将操作数中对应的二进制数 1 修改为 0;0 修改为 1
>>右移:将一个二进制操作数向右移动指定的位数,右边(低位端)溢出的位被丢弃,而在填充左边(高位端)的空位时,如果最高位是 0(正数),左侧空位填入 0;如果最高位是 1(负数),左侧空位填入 1。右移位运算相当于除以 2 的 n 次幂
<<左移:将一个二进制操作数向左移动指定的位数,左边(高位端)溢出的位被丢弃,右边(低位端)的空位用 0 补充。左移位运算相当于乘以 2 的 n 次幂

example:

  0000 0000 0000 1100
& 0000 0000 0000 1000
-----------------------
  0000 0000 0000 1000    # &,位与


  0000 0000 0000 0100
| 0000 0000 0000 1000
-----------------------
  0000 0000 0000 1100    # |,位或


  0000 0000 0001 1111
^ 0000 0000 0001 0110
-----------------------
  0000 0000 0000 1001    # ^,位异


~ 0000 0000 0111 1011
-----------------------
  1111 1111 1000 0100    # ~,位反


十进制:50   00110010    # >> 右移
十进制:25   00011001    # 上面的向右移 1 位,右边最低位 0 丢弃,左边最高位添加 0
十进制:140  10001100    # 上面的向右移 1 位,右边最低为 1 丢弃,左边最高位添加 1


十进制:48   00110000    # << 左移
十进制:96   01100000    # 上面的向左移 1 位,左边最高位 0 丢弃,右边最低位添加 0
十进制:192  11000000    # 上面的向左移 1 位,左边最高位 0 丢弃,右边最低位添加 0

运算符优先级

优先级高的运算先执行,优先级低的运算后执行,同一优先级的操作按照从左到右的顺序进行

运算符优先级,由高到低,同一行的优先级相同,如下表所示:

运算符作用
()小括号
**
~位反
+、-正负号
*、/、%、//乘、除、取余、取整
+、-加、减
<<、>>左位移、右位移
&位与
^位异
<、<=、>、>=、!=、==比较
not逻辑非
and、or逻辑与、逻辑或

转义字符

转义字符作用
\续行符
\n换行符
\0
\t水平制表符
"双引号
单引号
\\一个反斜杠
\f换页
\0dd八进制数,dd 代表字符,如:\012 代表换行
\xhh十六进制数,hh 代表字符,如:\x0a 代表换行
r/R在字符串定界符引号前面添加字母 r 或 R,该字符串原样输出,其中的转义字符不进行转义

格式化输出

  • %d:整数
  • %s:字符串
  • %f:浮点数
  • %.2f:保留小数点后两位,不够的添加 0,多的去掉
  • %03d:指定整数为 3 位数,不够的在数字前面添加 0
  • f’{表达式}':格式化字符串常量

example:

age = 18
name = 'TOM'
weight = 75.5
stu_id = 1

print('今年我的年龄是 %d 岁' % age)    # %d 整数

print('我的名字是 %s' % name)    # %s 字符串

print('我的体重是 %f 公斤' % weight)    # %f 浮点数
print('我的体重是 %.2f 公斤' % weight)    # %.2f

print('我的学号是 %d' % stu_id)
print('我的学号是 %03d' % stu_id)    # %03d

print('我的名字是 %s,今年 %d 岁了' % (name, age))    # 格式化输出多个字符

print('我的名字是 %s,明年 %d 岁了' % (name, age + 1))    # 运行格式化输出字符进行运算

print('我的名字是 %s,今年 %s 岁了,体重 %s 公斤' % (name, age, weight))    # 格式化输出允许同一类型字符,注意顺序

print(f'我的名字是 {name},今年 {age} 岁了')    # f'{表达式}'

流程控制语句

条件选择语句

条件选择语句主要有 3 中形式,分别位 if 语句if···else 语句if···elif···else 多分支语句

if 语句

语法格式:

if 条件:
    执行的代码

if 语句的流程:

Created with Raphaël 2.3.0 开始 条件 执行的代码 结束 yes no

example:

num = 5

if num == 5:
    print("num 的值为 5")
if···else 语句

语法格式:

if 条件:
    执行的代码 1
else:
    执行的代码 2

if···else 语句的流程:

Created with Raphaël 2.3.0 开始 条件 执行的代码 1 结束 执行的代码 2 yes no

example:

num = 10

if num == 5:
    print("num 的值为 5")
else:
    print("num 的值不为 5")

if···else 语句可以使用条件表达式简化:

num = 10 

print("num 的值为 5") if num == 5 else print("num 的值不为 5")

如果 num = 5,就打印:“num 的值为 5”;否则就打印:“num 的值不为 5”

if···elif···else 语句

语法格式:

if 条件:
    执行的代码 1
elif 条件:
    执行的代码 2
......
else:
    执行的代码 3

if···elif···else 语句的流程:

Created with Raphaël 2.3.0 开始 条件 1 执行的代码 1 结束 条件 2 执行的代码 2 条件 3 执行的代码 3 执行的代码 4 yes no yes no yes no

example:

age1 = int(input('请输入您的年龄: '))

if (age1 < 18) and (age1 >= 0):
    print(f'您的年龄是 {age1},不合法,童工')
elif 18 <= age1 <= 60:
    print(f'您的年龄是 {age1},合法')
elif age1 > 60:
    print(f'您的年龄是 {age1},退休年龄')
else:
    print('您的输入不符合语法,请输入 0 以上的数字')

使用 if 选择语句时,尽量使用较为规范的格式:

  • 当使用布尔型的变量作为判断条件时,假设布尔型变量为 flag,较为规范的格式:
if flag:    # 表示为真
if not flage:    # 表示为假
  • 不规范的格式:
if flag == True:
if flag == Flage:

使用 “if flag:” 这样的格式,可以避免将 “if flag == True:”,错写成 “if flag = True:

if 语句嵌套

语法格式:

if 条件 1:
    执行的代码 1
    if 条件 2:
        执行的代码 2
    else:
        执行的代码 3
else:
    执行的代码 4

if 语句嵌套的流程:

Created with Raphaël 2.3.0 开始 条件 1 执行的代码 1 条件 2 执行的代码 2 结束 执行的代码 3 执行的代码 4 yes no yes no

example:

money = 1
seat = 1

if money == 1:
    print('有钱,请上车')
    if seat == 1:
        print('有空座位,可以坐下')
    else:
        print('没有空座位,不可以坐下')
else:
    print('没钱,不能上车')

if 语句嵌套有多种嵌套方式,嵌套时需要控制好不同级别代码块的缩进量

条件表达式

条件表达式又叫:三目运算符或三元运算符或三元表达式,和上面的 if···else 语句使用条件表达式简化 原理一样,语法格式:条件成立执行的代码1 if 条件 else 条件不成立执行的代码2

使用条件表达式时,先计算 if 条件,如果结果为 True,返回 if 条件语句 左边的值,否则返回 else 右边的值

example:

a = 1
b = 2
c = a if a > b else b    # 如果 a > b,c 就等于 a;否则 c 就等于 b

print(c)    # 输出结果:2


aa = 10
bb = 66
cc = aa - bb if aa > bb else bb - aa    # 如果 aa > bb,cc 就等于 aa - bb,否则 cc 就等于 bb - aa

print(cc)    # 输出结果:56

循环语句

  • while 循环:

    一直重复,直到条件不满足时才结束循环,又叫条件循环。只要条件为真,就会一直循环

  • for 循环:

    重复一定次数的循环,又叫计次循环

while 循环语句

while 循环是通过一个条件来控制是否要反复执行代码。while 循环语句中,else 语句 可以省略不写。

在使用 while 循环时,一定不要忘记添加将循环条件变为 False 的代码,否则将产生死循环

语法格式:

while 条件:
    执行的代码
[else:
    执行的代码]

while 循环语句的流程:

Created with Raphaël 2.3.0 开始 条件 执行的代码 结束 yes no

example:计算 1-100 累加和

x = 1
result = 0

while x <= 100:
    result += x  # result = result + x
    x += 1  # x = x + 1

print(result)

或

x = 1
result = 0

while x <= 100:
    result += x  # result = result + x
    x += 1  # x = x + 1
else:
    print(result)

else 语句 只有在 while 循环语句正常退出后才会执行,异常中断或者遇到 breakreturn 时,不会执行 else 语句

example:

x = 1
result = 0

while x <= 100:
    result += x  # result = result + x
    x += 1  # x = x + 1
    
    if x == 20:
        print("x 值为 20 时中断")
        break
else:    # 异常退出不执行 else 语句
    print(result)
for 循环语句

for 循环是一个依次重复执行的循环。通常用于枚举或遍历序列,以及迭代对象中的元素。for 循环语句中,else 语句 可以省略不写。

语法格式:

for 迭代变量 in 序列:
    执行的代码
[else:
    执行的代码]

for 循环语句的流程:

Created with Raphaël 2.3.0 开始 序列中是否有元素 取下一个元素 执行的代码 结束 yes no

example:

str1 = 'itheima'

for i in str1:
    print(i)
print("for over")

或

str1 = 'itheima'

for i in str1:
    print(i)
else:
    print("for over")

else 语句 只有在 for 循环语句正常退出后才会执行,异常中断或者遇到 breakreturn 时,不会执行 else 语句

example:

str1 = 'itheima'

for i in str1:
    print(i)

    if i == "e":
        print("i 等于 e 时中断")
        break
else:    # 异常中断,else 语句不执行
    print("for over")
循环语句嵌套
  • while 循环语句嵌套:

    语法格式:

    while 条件 1:
        执行的代码 1
        while 条件 2:
            执行的代码 2
        [else:
            执行的代码 3]
    [else:
        执行的代码 4]
    

    while 循环语句嵌套的流程:

    Created with Raphaël 2.3.0 开始 条件 1 执行的代码 1 条件 2 执行的代码 2 结束 yes no yes no

    打印正方形的星星,一行输出星星的个数是 5 个,打印 5 行,example:

    j = 0
    
    while j < 5:
        i = 0
        
        while i < 5:
            print('*', end='')
            i += 1
        print()
        j += 1
    
  • for 循环语句嵌套:

    语法格式:

    for 迭代变量 in 序列:
        执行的代码 1
        for 迭代变量 in 序列:
            执行的代码 2
        [else:
            执行的代码 3]
    [else:
        执行的代码 4]
    

    for 循环语句嵌套的流程:

    Created with Raphaël 2.3.0 开始 序列中是否有元素 1 取下一个元素 1 执行的代码 1 序列中是否有元素 2 取下一个元素 2 执行的代码 2 结束 yes no yes no

    example:

    str1 = 'itheima'
    str2 = 'Hello'
    
    for i in str1:
        print(i)
        
        for j in str2:
            print(j)
    

while 循环 也可以嵌套 for 循环if 语句for 循环 也可以嵌套 while 循环if 语句

跳转语句

跳转语句能够改变程序的执行顺序,包括 break、continue 和 return。break 和 continue 用于循环体中,而 return 用于函数中。

break 语句

使用 break 语句用于强制退出循环,不再执行循环体中尚未执行的语句

break 语句一般会结合 if 语句进行搭配使用,表示在某种条件下,跳出循环。如果使用嵌套循环,break 语句将跳出最内层的循环

在 while 循环语句中使用 break 语句

语法格式:

while 条件 1:
    执行的代码 1
    if 条件 2:
        执行的代码 2
        break

在 while 循环语句中使用 break 语句流程:

Created with Raphaël 2.3.0 开始 条件 1 执行的代码 1 条件 2 执行的代码 2 break 结束 yes no yes no

example:

i = 0

while i < 101:
    print(i)
    if (i % 3) == 2 and (i % 5) == 3 and (i % 7) == 2:
        print("这个数是 ", i)
        break
    i = i + 1
在 for 循环语句中使用 break 语句

语法格式:

for 迭代变量 in 序列:
    执行的代码 1
    if 条件:
        执行的代码 2
        break

在 for 循环语句中使用 break 语句流程:

Created with Raphaël 2.3.0 开始 序列中是否有元素 取下一个元素 执行的代码 1 条件 执行的代码 2 break 结束 yes no yes no

example:

for number in range(101):
    print(number)
    if (number % 3 == 2) and (number % 5 == 3) and (number % 7 == 2):
        print("这个数是 ", number)
        break

continue 语句

continue 语句用于结束本次循环,跳过循环体中尚未执行的语句,接着进行终止条件的判断,以决定是否继续循环。

continue 语句一般会结合 if 语句进行搭配使用,表示在某种条件下,跳出当前循环中尚未执行的语句,然后进行下一轮循环。如果使用嵌套循环,continue 语句将只跳出最内层循环中尚未执行的语句

在 while 循环语句中使用 continue 语句

语法格式:

while 条件 1:
    执行的代码 1
    if 条件 2:
        执行的代码 2
        continue

在 while 循环语句中使用 continue 语句流程:

Created with Raphaël 2.3.0 开始 条件 1 执行的代码 1 条件 2 执行的代码 2 continue 结束 yes no yes no

example:

i = 0

while i < 10:
    if i == 5:
        print("跳过 ", i)
        i += 1
        continue
    print(i)
    i += 1
在 for 循环语句中使用 continue 语句

语法格式:

for 迭代变量 in 序列:
    执行的代码 1
    if 条件:
        执行的代码 2
        break

在 for 循环语句中使用 continue 语句流程:

Created with Raphaël 2.3.0 开始 序列中是否有元素 取下一个元素 执行的代码 1 条件 执行的代码 2 continue 结束 yes no yes no

example:

str1 = 'itheima'

for i in str1:
    if i == 'e':
        print("跳过 ", i)
        continue
    print(i)

pass 语句

Python 中还有一个 pass 语句,表示空语句。不做任何事情,一般起占位作用

在不是偶数时,使用 pass 语句占个位置,方便以后对不是偶数是数进行处理,example:

for i in range(1, 11):
    if (i % 2) == 0:
        print(i, end=' ')
    else:
        pass

容器类型的数据

Python 内置的数据类型如序列(列表、元组等)、集合和字典等可以容纳多项数据,我们称它们为容器类型的数据。

序列

序列是一块用于存放多个值的连续内存空间,并且按一定顺序排列,每一个元素(值)都分配一个数字,称为索引或位置,通过该索引可以取出相应的值。序列结构主要有列表、元组、集合、字典和字符串。

集合和字典不支持索引、切片、相加和相乘操作

索引

序列中的每一个元素都有一个编号,也称为索引。这个索引是从 0 开始递增的,即下标为 0 表示第一个元素,下标为 1 表示第二个元素,以此类推。

符号、空格也会占一个元素。索引有正值索引和负值索引之分,正值索引(下标)是从前向后,默认从 0 开始的,而负值索引(下标)是从后向前,默认从 -1 开始的。

可以通过下标运算符访问序列中的元素,下标运算符是跟在容器数据后的一对中括号([]),中括号带有参数,对于序列类型的数据,这个参数就是元素的索引序号。

example:

Hello, World!

元素1  元素2  元素3  元素4  元素5  元素6  元素7  元素8
  H     e      l      l     o      ,            w
  0     1      2      3     4      5     6      7      正值索引(下标索引序号)
 -7    -5     -6     -5    -4     -3    -2     -1      负值索引(下标索引序号)


a = "Hello, World!"

print(a[0])    # 输出结果:H
print(a[-1])    # 输出结果:!
print(a[20])    # IndexError: string index out of range,若索引超过范围,则会报错
切片

通给切片操作可以生成一个新的序列,语法格式:

sname[start: end: step]

  • sname:表示序列的名称
  • start:表示切片的开始位置(包括该位置),如果不指定,默认为 0
  • end:表示切片的结束位置(不包括该位置),如果不知道,默认为序列的长度
  • step:表示切片的步长,如果省略,默认为 1

example:

a = "Hello, World!"

print(a[1:3])    # 输出结果:el
print(a[:3])    # 输出结果:Hel
print(a[3:])    # 输出结果:lo, World!
print(a[1:-1])    # 输出结果:ello, World
print(a[-5:-1])    # 输出结果:World
print(a[:])    # 输出结果:Hello, World!
序列相加

支持两种类型的序列相加,即将两个序列相加,不会去除重复的元素,使用(+)运算符实现

不能将列表和元组相加,也不能将列表和字符串相加

example:

a = "Hello,"
b = "World!"

print(a + b)    # 输出结果:Hello,World!
序列乘法

使用一个序列乘以数字 n 会生成一个新的序列。新序列的内容为原来序列被重复 n 次的结果

example:

a = "Hello, World!"
b = a * 3

print(b)    # 输出结果:Hello, World!Hello, World!Hello, World!
序列成员测试

如果检查正确,则输出 True,否则输出 False

  • in:使用 in 关键字检查某个元素是否包含在序列中,语法格式:value in sequence

    • value:表示要检查的元素
    • sequence:表示指定的序列
  • not in:使用 not in 关键字检查某个元素是否不包含在序列中,语法格式:value not in sequence

    • value:表示要检查的元素
    • sequence:表示指定的序列

example:

a = "Hello, World!"

print("Hello" in a)    # 输出结果:True
print("Hello" not in a)    # 输出结果:False
计算序列的长度、最大值和最小值

提供了内置函数计算序列的长度、最大值和最小值。

  • len():序列的长度
  • max():序列的最大值(元素)
  • min():序列的最小值(元素)

example:

a = "Hello, World!"
b = '6546186638948'

print(len(a))    # 输出结果:13
print(len(b))    # 输出结果:13
print(max(a))    # 输出结果:r
print(max(b))    # 输出结果:9
print(min(a))    # 输出结果:空格
print(min(b))    # 输出结果:1

列表

列表是按特定顺序排列的元素组成的,是 Python 中内置的可变序列,我们可以追加、插入、删除和修改列表中的元素。列表中所有元素都放在一对中括号中([]),两个相邻元素之间使用逗号(,)分隔。可以将整数、实数、字符串、列表、元组等任何类型的内容放入到列表中,并且同一个列表中,元素类型可以不同,因为它们之间没有任何关系

列表中允许存在相同的元素

创建列表

使用赋值运算符直接创建列表,example:

mylist = [10, 20, 30]
mylist01 = "Hello"

print(mylist)    # 输出结果:[10, 20, 30]
print(mylist01)    # 输出结果:['H', 'e', 'l', 'l', 'o']

创建空列表,example:

mylist = []

print(mylist)    # 输出结果:[]

创建数值列表,example:

mylist = list(range(10))

print(mylist)    # 输出结果:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
访问列表元素

直接使用 print() 函数即可

example:

mylist = [10, 20, 30]

print(mylist[2])    # 输出结果:30
添加列表

通过使用列表对象的 append() 方法实现在列表的末尾添加元素,example:

mylist = [10, 20, 30]
mylist.append(40)

print(mylist)    # 输出结果:[10, 20, 30, 40]

通过使用列表对象的 insert() 方法实现在列表的指定位置添加元素,example:

mylist = [10, 20, 30]
mylist.insert(1, 40)

print(mylist)    # 输出结果:[10, 40, 20, 30]

通过使用列表对象的 extend() 方法实现将一个列表中的全部元素添加到另一个列表中,example:

mylist = [10, 20, 30]
mylist01 = [30, 40, 50]
mylist.extend(mylist01)

print(mylist)    # 输出结果:[10, 20, 30, 30, 40, 50]
修改列表

只需要通过索引获取该元素,然后再为其重新赋值即可,example:

mylist = [10, 20, 30]
mylist[1] = 40

print(mylist)    # 输出结果:[10, 40, 30]
删除列表元素

根据索引删除列表元素,通过 del 语句 实现,example:

mylist = [10, 20, 30]
del mylist[1]

print(mylist)    # 输出结果:[10, 30]

根据元素删除,通过 remove() 方法,如果指定的元素不存在将会报错,example:

mylist = [10, 20, 30]
mylist.remove(30)
mylist.remove(30)    # 输出结果:ValueError: list.remove(x): x not in list,指定元素不在列表中

print(mylist)    # 输出结果:[10, 20]
遍历列表

使用 for 循环遍历,example:

mylist = [10, 20, 30]

for i in mylist:
    print(i)


10    # 输出结果
20
30

使用 for 循环和 enumerate() 函数实现,可以同时输出索引值和元素内容,example:

mylist = [10, 20, 30]

for index, i in enumerate(mylist):
    print(index, i)


0 10    # 输出结果
1 20
2 30
对列表进行统计和计算

使用列表对象的 count() 方法可以获取指定元素在列表中出现的次数,example:

mylist = [10, 20, 30, 10, 50]
num = mylist.count(10)

print(num)    # 输出结果:2

使用列表对象的 index() 方法可以获取指定元素在列表中首次出现的索引位置,如果指定元素不存在将报错,example:

mylist = [10, 20, 30, 10, 50]
num = mylist.index(10)
num01 = mylist.index(60)

print(num)    # 输出结果:0
print(num01)    # 输出结果:ValueError: 60 is not in list,指定元素不在列表中

使用 sum() 函数用于统计数值列表中各元素的和

mylist = [10, 20, 30, 10, 50]
total = sum(mylist)

print(total)    # 输出结果:120
对列表进行排序

使用列表对象的 sort() 方法可以对原列表中的元素进行排序,改变原有列表,example:

mylist = [20, 10, 50, 30]
mylist.sort()

print(mylist)    # 输出结果:[10, 20, 30, 50]

使用 sorted() 内置函数,可以对列表进行排序,不改变原有的列表,example:

mylist = [20, 10, 50, 30]
mylist01 = sorted(mylist)

print(mylist)    # 输出结果:[20, 10, 50, 30]
print(mylist01)    # 输出结果:[10, 20, 30, 50]
列表推导式

使用列表推导式可以快速生成一个列表,或者根据某个列表生成满足指定需求的列表。

生成指定范围的数值列表,example:

import random

mylist = [random.randint(10, 100) for i in range(10)]

print(mylist)    # 输出结果:[77, 45, 18, 14, 30, 58, 97, 88, 91, 83]

根据需求生成指定列表,example:

mylist = [1200, 5330, 2988, 6200, 1998, 8888]
newlist = [int(i * 0.5) for i in mylist]

print("原列表:", mylist)    # 输出结果:原列表: [1200, 5330, 2988, 6200, 1998, 8888]
print("原列表乘 0.5 后生成的新列表:", newlist)    # 输出结果:原列表乘 0.5 后生成的新列表: [600, 2665, 1494, 3100, 999, 4444]

从列表中选择符合条件的元素组成新的列表,example:

mylist = [1200, 5330, 2988, 6200, 1998, 8888]
newlist = [x for x in mylist if (x > 5000)]

print("原列表:", mylist)    # 输出结果:原列表: [1200, 5330, 2988, 6200, 1998, 8888]
print("原列表中大于 5000 生成的新列表:", newlist)    # 输出结果:原列表中大于 5000 生成的新列表: [5330, 6200, 8888]

if 作用是用于判断,只有满足条件时,将满足条件的元素值,生成一个新的列表

从列表中选择符合条件的元素组成新的列表,大于 5000 输出原有值,小于 5000 输出小于 5000,example:

mylist = [1200, 5330, 2988, 6200, 1998, 8888]
newlist = [x if (x > 5000) else "小于 5000" for x in mylist]

print("原列表:", mylist)    # 输出结果:原列表: [1200, 5330, 2988, 6200, 1998, 8888]
print("原列表中大于 5000 和 小于 5000 生成的新列表:", newlist)    # 输出结果:原列表中大于 5000 和 小于 5000 生成的新列表: ['小于 5000', 5330, '小于 5000', 6200, '小于 5000', 8888]

if…else 作用的用于赋值,满足条件时,执行 if 前面的代码;不满足条件时,执行 else 后面的代码

二维列表

二维列表中的信息以行和列的形式表示,第一个下标表示元素所在的行,第二个下标表示元素所在的列。常用的三种方法:

  • 直接定义二维列表

    语法格式:

    listname = [
        [元素11, 元素12, 元素13, ..., 元素1n],
        [元素21, 元素22, 元素23, ..., 元素2n],
        [元素31, 元素32, 元素33, ..., 元素3n],
        ...
        [元素n1, 元素n2, 元素n3, ..., 元素nn],
    ]
    
    • listname:表示生成的列表名称
    • [元素11, 元素12, 元素13, …, 元素1n]:表示二维列表的第一行,也是一个列表。其中 “元素11, 元素12, 元素13, …, 元素1n” 表示第一行中的列
    • [元素21, 元素22, 元素23, …, 元素2n]:表示二维列表的第二行
    • [元素31, 元素32, 元素33, …, 元素3n]:表示二维列表的第三行
    • [元素n1, 元素n2, 元素n3, …, 元素nn]:表示二维列表的第 n 行

    example:

    listname = [
        ['千', '山', '鸟', '飞', '绝'],
        ['万', '径', '人', '踪', '灭'],
        ['孤', '舟', '蓑', '笠', '翁'],
        ['独', '钓', '寒', '江', '雪'],
    ]
    
    print(listname)    # 输出结果:[['千', '山', '鸟', '飞', '绝'], ['万', '径', '人', '踪', '灭'], ['孤', '舟', '蓑', '笠', '翁'], ['独', '钓', '寒', '江', '雪']]
    
  • 使用嵌套的 for 循环创建

    每创建一行,行内再创建 5 个列表,example:

    arr = []
    
    for i in range(4):
        arr.append([])
        for j in range(5):
            arr[i].append(j)
    
    print(arr)    # 输出结果:[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]
    
  • 使用列表推导式创建

    每创建一行,行内再创建 5 个列表,example:

    arr = [[j for j in range(5)] for i in range(4)]
    
    print(arr)    # 输出结果:[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]
    

使用二维列表输出不同版式的古诗,如:柳宗元《江雪》,example:

str1 = "千山鸟飞绝"
str2 = "万径人踪灭"
str3 = "孤舟蓑笠翁"
str4 = "独钓寒江雪"
verse = [list(str1), list(str2), list(str3), list(str4)]

print(('-' * 10), "横版", ('-' * 10))
for i in range(4):
    for j in range(5):
        if j == 4:
            print(verse[i][j])
        else:
            print(verse[i][j], end='')


print(('-' * 10), "横版", ('-' * 10))
verse.reverse()
for i in range(5):
    for j in range(4):
        if j == 3:
            print(verse[j][i])
        else:
            print(verse[j][i], end='')

元组

元组(tuple)与列表类似,也是由一系列按特定顺序排列的元素组成,但是它是不可变序列。

创建元组

使用赋值运算符直接创建元组,example:

num = (10, 20, 30)

print(num)    # 输出结果:(10, 20, 30)

创建元组时,小括号(())不是必须的,Python 中只要将一组元素值用逗号(,)分隔,默认就是元组,example:

num = 10, 20, 30

print(num)    # 输出结果:(10, 20, 30)

创建元组时,如果只有只有一个元素值,则需要在元素的后面加一个逗号(,)否则创建的不是元组,example:

num = 10,
num01 = 10

print(num)    # 输出结果:(10,)
print(num01)    # 输出结果:10

创建空元组,example:

mytuple = ()

print(mytuple)    # 输出结果:()
删除元组

使用 del 语句删除,example:

num = (10, 20, 30)

print(num)    # 输出结果:(10, 20, 30)

del num

print(num)    # 输出结果:NameError: name 'num' is not defined. Did you mean: 'sum'?,没有这个名称
访问元组

使用 print() 函数即可,example:

num = (10, 20, 30)

print(num)    # 输出结果:(10, 20, 30)

指定索引获取元组元素,example:

num = (10, 20, 30)

print(num[1])    # 输出结果:20

使用切片方式获取指定的元素,example:

num = (10, 20, 30)

print(num[:2])    # 输出结果:(10, 20)
修改元组

元组是不可变序列,所以不能对它单个元素值进行修改,但可以对元组重新进行赋值,example:

num = (10, 20, 30)

print(num)    # 输出结果:(10, 20, 30)

num[1] = 50

print(num)    # 输出结果:TypeError: 'tuple' object does not support item assignment,元组不支持指定元素重新赋值

num = (10, 50, 30)

print(num)    # 输出结果:(10, 50, 30)

使用加号(+)连接组合元组,example:

num = (10, 20, 30)
num01 = (40, 50, 60)
num02 = (num + num01)
num03 = (40, 50) + num
num04 = [40, 50] + num

print(num02)    # 输出结果:(10, 20, 30, 40, 50, 60)
print(num03)    # 输出结果:(40, 50, 10, 20, 30)
print(num04)    # 输出结果:TypeError: can only concatenate list (not "tuple") to list,不能将列表用连接符添加到元组

元组连接时,连接的内容只能是元组,不能使用其它元素进行连接

元组推导式

使用元组推导式可以快速生成一个元组,它的表现形式和列表推导式类似

生成指定范围的数值列表,example:

import random

mytuple = (random.randint(10, 100) for i in range(10))
mytuple01 = (tuple(random.randint(10, 100) for i in range(10)))

print(mytuple)    # 输出结果:<generator object <genexpr> at 0x000001AA0BD8A2D0>
print(mytuple01)    # 输出结果:(36, 65, 71, 52, 86, 49, 85, 46, 47, 11)

使用元组推导式生成的元组,需要使用 tuple() 函数转换为元组,否则生成的是一个生成器对象

元组与列表的区别

  • 列表属于可变序列,可以随时修改或者删除;元组属于不可变序列,其中的元素不可以修改,除非整体替换
  • 列表可以使用 append()、extend()、insert()、remove() 和 pop() 等方法实现添加和修改列表元素,而元组没有这几个方法,所以不能向元组中添加、修改和删除元素
  • 列表可以使用切片访问和修改列表中的元素;元组也支持切片,但只支持通过切片访问元组中的元素,不支持修改
  • 元组比列表的访问和处理速度快
  • 列表不能作为字典的键,而元组可以

字典

字典与列表类似,也是可变序列,不过列表是有序的可变序列,字典是无序的可变序列,保存的内容是以 “键-值” 对的形式存放的。

字典的特征:

  • 通过键而不是通过索引来读取
  • 字典是任意对象的无序集合
  • 字典是可变的,并且可以任意嵌套
  • 字典中的键必须唯一,如果键出现多次,则最后一个值会被记住
  • 字典中的键必须不可变,可以使用数字、字符串或者元组,但不能使用列表
创建字典

通过 key-value 的方式创建,example:

mydict = {'zhangsan': '20', 'lisi': '24'}

print(mydict)    # 输出结果:{'zhangsan': '20', 'lisi': '24'}

创建空字典,example:

mydict = {}
或者
my_dict = dict()

使用 zip() 函数将已有的数据快速创建字典,example:

name = ['zhangsan', 'lisi', 'wangwu']
age = ['20', '24', '25']
mydict = dict(zip(name, age))

print(mydict)    # 输出结果:{'zhangsan': '20', 'lisi': '24', 'wangwu': '25'}

使用 dict 对象的 fromkeys() 方法创建值为 None 的字典,example:

name = ['zhangsan', 'lisi', 'wangwu']
mydict = dict.fromkeys(name)

print(mydict)    # 输出结果:{'zhangsan': None, 'lisi': None, 'wangwu': None}

使用已经存在的元组和列表创建字典,example:

name = ['zhangsan', 'lisi', 'wangwu']
name01 = ('zhangsan', 'lisi', 'wangwu')
age = ['20', '24', '25']
mydict = {name: age}
mydict01 = {name01: age}

print(mydict)    # 输出结果:TypeError: unhashable type: 'list',无法处理 list 类型
print(mydict01)    # 输出结果:{('zhangsan', 'lisi', 'wangwu'): ['20', '24', '25']}

使用这种方式创建字典,字典中的键(key)不能为列表

添加字典元素

字典是可变序列,所以可以随时在字典中添加 “key-value”,example:

mydict = {'zhangsan': '20', 'lisi': '24'}

print(mydict)    # 输出结果:{'zhangsan': '20', 'lisi': '24'}

mydict['wangwu'] = '25'

print(mydict)    # 输出结果:{'zhangsan': '20', 'lisi': '24', 'wangwu': '25'}

在字典中 “键” 是唯一的,如果添加的新 “键” 已经存在,那么将使用新的值替换原来的 “键” 值,example:

mydict = {'zhangsan': '20', 'lisi': '24'}

print(mydict)    # 输出结果:{'zhangsan': '20', 'lisi': '24'}

mydict['zhangsan'] = '25'

print(mydict)    # 输出结果:{'zhangsan': '25', 'lisi': '24'}
删除字典

使用 del 命令删除整个字典,example:

mydict = {'zhangsan': '20', 'lisi': '24'}

print(mydict)    # 输出结果:{'zhangsan': '20', 'lisi': '24'}

del mydict

print(mydict)    # 输出结果:NameError: name 'mydict' is not defined. Did you mean: 'dict'?,没有这个名称

使用 clear() 方法将字典变为空字典,example:

mydict = {'zhangsan': '20', 'lisi': '24'}

print(mydict)    # 输出结果:{'zhangsan': '20', 'lisi': '24'}

mydict.clear()

print(mydict)    # 输出结果:{}

使用 pop() 方法删除并默认返回指定键(key)的元素,如果指定键(key)不存在,则报错或者指定返回内容,example:

mydict = {'zhangsan': '20', 'lisi': '24'}

print(mydict.pop('zhangsan'))    # 输出结果:20
print(mydict.pop('wangwu'))    # 输出结果:KeyError: 'wangwu',没有指定的 key 值
print(mydict.pop('wangwu', "没有 wangwu 的年龄"))    # 输出结果:没有 wangwu 的年龄
访问字典

使用 print() 函数,example:

mydict = {'zhangsan': '20', 'lisi': '24'}

print(mydict)    # 输出结果:{'zhangsan': '20', 'lisi': '24'}

访问字典指定的元素,可以通过下标的方式实现,与列表和元组不同,这里的下标不是索引号,而是键(key),example:

mydict = {'zhangsan': '20', 'lisi': '24'}

print(mydict['zhangsan'])    # 输出结果:20

使用 if 语句对不存在的键进行处理,example:

mydict = {'zhangsan': '20', 'lisi': '24'}

print("wangwu 的年龄是:", mydict['wangwu'] if 'wangwu' in mydict else "字典里没有名字是 wangwu 的人")    # 输出结果:wangwu 的年龄是: 字典里没有名字是 wangwu 的人

使用字典对象的 get() 方法获取指定的键,example:

mydict = {'zhangsan': '20', 'lisi': '24'}

print("zhangsan 的年龄是:", mydict.get('zhangsan'))    # 输出结果:zhangsan 的年龄是: 20
print("wangwu 的年龄是:", mydict.get('wangwu', "字典里没有名字是 wangwu 的人"))    # 输出结果:wangwu 的年龄是: 字典里没有名字是 wangwu 的人
遍历字典

使用字典对象的 items() 方法可以获取字典的 “key-value” 对的列表,将各个元素以元组的方式输出,example:

mydict = {'zhangsan': '20', 'lisi': '24'}

for item in mydict.items():
    print(item)


('zhangsan', '20')    # 输出结果
('lisi', '24')

获取具体的每个键和值,example:

mydict = {'zhangsan': '20', 'lisi': '24'}

for key, value in mydict.items():
    print(key, "的年龄是", value)


zhangsan 的年龄是 20    # 输出结果
lisi 的年龄是 24
字典推导式

使用字典推导式可以快速的生成一个字典,和列表推导式类似,example:

import random

mydict = {i: random.randint(10, 100) for i in range(1, 5)}

print(mydict)    # 输出结果:{1: 61, 2: 51, 3: 75, 4: 30}

字典推导式也可以通过列表生成字典,example:

name = ['zhangsan', 'lisi']
age = ['20', '24']

mydict = {i: j + "岁" for i, j in zip(name, age)}

print(mydict)    # 输出结果:{'zhangsan': '20岁', 'lisi': '24岁'}

集合

Python 中的集合用于保存不重复的元素,如果有重复的元素,只保留一个,集合是无序序列,有可变集合(set)和不可变集合(fozenset)两种

创建集合

直接使用 “{}” 创建集合,example:

myset = {10, 20, 30, 20, 40}

print(myset)    # 输出结果:{40, 10, 20, 30}

使用 set() 函数创建集合,set() 函数可以将列表、元组等其它可迭代的对象转换为集合,example:

myset = set([10, 20, 30, 20, 40])

print(myset)    # 输出结果:{40, 10, 20, 30}

创建空集合,example:

myset = set()

print(myset)    # 输出结果:set()
print(type(myset))    # 输出结果:<class 'set'>

在创建空集合时,只能使用 set() 实现,不能使用 {},因为 {} 表示创建一个空字典

添加集合元素

使用 add() 方法向集合中添加元素,example:

myset = set([10, 20, 30, 40])
myset.add(50)

print(myset)    # 输出结果:{40, 10, 50, 20, 30}
删除集合元素

使用 del 语句删除整个集合,example:

myset = set([10, 20, 30, 40])
del myset

print(myset)    # 输出结果:NameError: name 'myset' is not defined,没有这个名称

使用 pop() 方法随机删除一个元素,example:

myset = set([10, 20, 30, 40])
myset.pop()

print(myset)    # 输出结果:{10, 20, 30}

使用 remove() 和 discard() 方法删除指定元素,但当删除的元素不存在时 remove() 方法会报错,discard() 方法不会报错,example:

myset = set([10, 20, 30, 40])
myset01 = set([10, 20, 30, 40])
myset.remove(10)
myset01.discard(20)
myset.remove(50)    # 输出结果:KeyError: 50,没有指定 key
myset01.discard(50)

print(myset)    # 输出结果:{40, 20, 30}
print(myset01)    # 输出结果:{40, 10, 30}

使用 clear() 方法清空集合,example:

myset = set([10, 20, 30, 40])
myset.clear()

print(myset)    # 输出结果:set()

使用 remove() 和 discard() 方法时,最好先判断是否存在,可以使用 in 关键字实现,example:

myset = set([10, 20, 30, 40])

print(50 in myset)    # 输出结果:False

if 50 in myset:
    myset.remove(50)
    print("已删除 50")
else:
    print("50 不存在")


50 不存在    # 输出结果
集合的交集、并集、差集和对称差集运算

进行交集运算使用 “&” 符号;并集运算使用 “|” 符号;差集运算使用 “-” 符号;对称差集运算使用 “^” 符号。

example:

myset = {10, 20, 30, 40}
myset01 = {50, 60, 20, 30}

print("交集运算,在 myset 和 myset01 都存在的有:", myset & myset01)    # 输出结果:交集运算,在 myset 和 myset01 都存在的有: {20, 30}
print("并集运算,即在 myset 也在 myset01 的有:", myset | myset01)    # 输出结果:并集运算,即在 myset 也在 myset01 的有: {40, 10, 50, 20, 60, 30}
print("差集运算,只在 myset 不在 myset01 的有:", myset - myset01)    # 输出结果:差集运算,只在 myset 不在 myset01 的有: {40, 10}
print("对称差集运算,myset 和 myset01 中不相同的有:", myset ^ myset01)    # 输出结果:对称差集运算,myset 和 myset01 中不相同的有: {40, 10, 50, 60}

字符串及正则表达式

字符串常用操作

为了实现某项功能,经常需要对某些字符串进行特殊处理,如:拼接字符串、截取字符串、格式化字符串等

拼接字符串

使用 “+” 运算符可完成对多个字符串的拼接,“+” 运算符可以连接多个字符串并产生一个字符串对象,example:

mot_en = 'Time is money'
mot_cn = "时间就是金钱"

print(mot_en + '——' + mot_cn)    # 输出结果:Time is money——时间就是金钱

字符串不允许直接与其它类型的数据拼接,example:

mystr = "我今天走了"
num = 1234
mystr01 = "步"

print(mystr + num + mystr01)    # 输出结果:TypeError: can only concatenate str (not "int") to str,只能将 str 类型的连接到 str

可以将整数转换为字符串的方法输出,example:

mystr = "我今天走了"
num = 1234
mystr01 = "步"

print(mystr + str(num) + mystr01)    # 输出结果:我今天走了1234步
计算字符串的长度

不同的字符所占字节数不同,在 Python 中,数字、英文、小数点、下划线和空格各占一个字节;一个汉字可能会占 2~4 个字节,占几个字节取决于采用的编码,汉字在 GBK/GB2312 编码中占 2 个字节,在 UTF-8/unicode 编码中一般占用 3 个字节

使用 len() 函数计算字符串的长度,example:

mystr = "我喜欢用 Python"

print(len(mystr))    # 输出结果:11

默认情况下通过 len() 函数计算字符串的长度时,不区分英文、数字和汉字,所有字符都按一个字符计算

有时需要获取字符串实际所占的字节数,可以使用 encode() 方法,进行编码后再进行获取,默认是 UTF-8 编码,example:

mystr = "我喜欢用 Python"

print(len(mystr.encode()))    # 输出结果:19,使用 UTF-8 编码
print(len(mystr.encode('GBK')))    # 输出结果:15。使用 GBK 编码
截取字符串

字符串也属于序列,所以要截取字符串可以使用切片的方法实现,语法格式:

string[start:end:step]

  • string:表示要截取的字符串
  • start:表示要截取的第一个字符的索引(包括该索引)
  • end:表示要截取的最后一个字符的所有(不包括该索引)
  • step:表示切片的步长,默认为 1

example:

mystr = "我喜欢用 Python"
submystr01 = mystr[3]
submystr02 = mystr[3:]
submystr03 = mystr[:3]
submystr04 = mystr[1:7]
submystr05 = mystr[15]

print(submystr01)    # 输出结果:用
print(submystr02)    # 输出结果:用 Python
print(submystr03)    # 输出结果:我喜欢
print(submystr04)    # 输出结果:喜欢用 Py
print(submystr05)    # 输出结果:IndexError: string index out of range,索引超出范围

在进行截取字符串时,指定索引不存在报错,可以使用 try···except 语句捕获异常,example:

mystr = "我喜欢用 Python"

try:
    submystr01 = mystr[15]
except IndexError:
    print("指定的索引不存在")


指定的索引不存在    # 输出结果
分割、合并字符串

分割字符串是把字符串分割为列表,而合并字符串是把列表合并为字符串。分割和合并字符串可以看作是互逆操作。

使用 split() 分割字符串,将一个字符串按指定的分隔符分割为字符串列表,该列表元素中不包括分隔符,语法格式:

str.split(sep[, maxsplit])

  • str:表示对 split 输入的对象
  • sep:指定分隔符,默认为 None(即所有空格、换行 “\n”、制表符 “\t”)
  • maxsplit:指定分割的次数,如果不指定或者为 -1,则分割次数没有限制,否则返回结果列表的元素个数,个数最多为 maxsplit+1

example:

mystr = "Hello, World! Hello, Python!"
mylist01 = mystr.split()
mylist02 = mystr.split(',')
mylist03 = mystr.split('o')
mylist04 = mystr.split(' ', 4)
mylist05 = mystr.split('l')

print(mylist01)    # 输出结果:['Hello,', 'World!', 'Hello,', 'Python!']
print(mylist02)    # 输出结果:['Hello', ' World! Hello', ' Python!']
print(mylist03)    # 输出结果:['Hell', ', W', 'rld! Hell', ', Pyth', 'n!']
print(mylist04)    # 输出结果:['Hello,', 'World!', 'Hello,', 'Python!']
print(mylist05)    # 输出结果:['He', '', 'o, Wor', 'd! He', '', 'o, Python!']

使用 split() 方法时,如果不指定参数,默认采用空格进行分割,无论有几个空格都将作为一个分隔符进行分割;如果分隔符连续出现多个,每分割一次将会得到一个空元素

使用 join() 方法合并字符串,语法格式:

strnew = string.join(iterable)

  • strnew:表示合并后生成的字符串名称
  • string:字符串类型,指定合并时的分隔符
  • iterable:可迭代的对象

example:

mylist = ['zhangsan', 'lisi', 'wangwu']
mystr01 = ' '.join(mylist)
mystr02 = '@'.join(mylist)

print(mystr01)    # 输出结果:zhangsan lisi wangwu
print('@' + mystr02)    # 输出结果:@zhangsan@lisi@wangwu
检索字符串

使用 count() 方法检索指定字符串在另一个字符串中出现的次数,如果要检索的字符串不存在,则返回 0,语法格式:

str.count(sub[, start[, end=len(string)]])

  • str:表示对 count 输入的对象
  • sub:搜索的子字符串
  • start:字符串开始搜索的位置。默认为第一个字符,第一个字符索引值为 0
  • end:字符串中结束搜索的位置。默认为字符串的最后一个位置

example:

string = 'Hello, World! Hello, Python!'

print(string.count('I'))    # 输出结果:0
print(string.count('H'))    # 输出结果:2
print(string.count('H', 1))    # 输出结果:1
print(string.count('H', 0, 20))    # 输出结果:2

使用 find() 方法检索是否包含指定的子字符串,如果检索的字符串不存在,则返回 -1;如果存在,则返回首次出现该字符串时的索引,语法格式:

str.find(sub[, start[, end=len(string)]])

  • str:表示对 find 输入的对象
  • sub:搜索的子字符串
  • start:字符串开始搜索的位置。默认为第一个字符,第一个字符索引值为 0
  • end:字符串中结束搜索的位置。默认为字符串的最后一个位置

example:

string = 'Hello, World! Hello, Python!'

print(string.find('I'))    # 输出结果:-1
print(string.find('H'))    # 输出结果:0
print(string.find('H', 1))    # 输出结果:14
print(string.find('H', 0, 20))    # 输出结果:0

使用 index() 方法与 find() 方法类似,也是用于检索是否包含指定的子字符串,只不过使用 index() 方法当指定的字符串不存在,将会报错,语法格式:

str.index(sub[, start[, end=len(string)]])

  • str:表示对 index 输入的对象
  • sub:搜索的子字符串
  • start:字符串开始搜索的位置。默认为第一个字符,第一个字符索引值为 0
  • end:字符串中结束搜索的位置。默认为字符串的最后一个位置

example:

string = 'Hello, World! Hello, Python!'

print(string.index('I'))    # 输出结果:ValueError: substring not found,没有找到字符串
print(string.index('H'))    # 输出结果:0
print(string.index('H', 1))    # 输出结果:14
print(string.index('H', 0, 20))    # 输出结果:0

使用 startswith() 方法检索字符串是否以指定子字符串开头,如果是则返回 True,否则返回 False,语法格式:

str.startswith(prefix[, start[, end=len(string)]])

  • str:表示对 startswith 输入的对象
  • prefix:搜索的子字符串
  • start:字符串开始搜索的位置。默认为第一个字符,第一个字符索引值为 0
  • end:字符串中结束搜索的位置。默认为字符串的最后一个位置

example:

string = 'Hello, World! Hello, Python!'

print(string.startswith('I'))    # 输出结果:False
print(string.startswith('H'))    # 输出结果:True
print(string.startswith('H', 1))    # 输出结果:False
print(string.startswith('H', 0, 20))    # 输出结果:True

使用 endswith() 方法检索字符串是否以指定子字符串结尾,如果是则返回 True,否则返回 False,语法格式:

str.endswith(suffix[, start[, end=len(string)]])

  • str:表示对 endswith 输入的对象
  • suffix:搜索的子字符串
  • start:字符串开始搜索的位置。默认为第一个字符,第一个字符索引值为 0
  • end:字符串中结束搜索的位置。默认为字符串的最后一个位置

example:

string = 'Hello, World! Hello, Python!'

print(string.endswith('I'))    # 输出结果:False
print(string.endswith('!'))    # 输出结果:True
print(string.endswith('!', 14))    # 输出结果:True
print(string.endswith('!', 0, 20))    # 输出结果:False
字母的大小写转换

使用 lower() 方法将字符串的大写字母转换为小写字母,语法格式:

str.lower()

  • str:表示对 lower 输入的对象

example:

mystr = "Hello, World!"

print(mystr.lower())    # 输出结果:hello, world!

使用 upper() 方法将字符串中的小写字母转换为大写字母,语法格式:

str.upper()

  • str:表示对 upper 输入的对象

example:

mystr = "Hello, World!"

print(mystr.upper())    # 输出结果:HELLO, WORLD!

使用 strip() 方法去除字符串中的空格和特殊字符,语法格式:

str.strip("object")

  • str:表示对 strip 输入的对象
  • object:表示要删除的对象

example:

mystr = " Hello, World!  "
mystr01 = ", Hello, World!  "

print(mystr.strip())    # 输出结果:Hello, World!
print(mystr01.strip(","))    # 输出结果: Hello, World!

使用 lstrip() 方法去掉字符串左侧的空格和特殊字符,语法格式:

str.lstrip("object")

  • str:表示对 strip 输入的对象
  • object:表示要删除的对象

example:

mystr = " Hello, World!  "
mystr01 = ", Hello, World!  "

print(mystr.lstrip())    # 输出结果:Hello, World!  
print(mystr01.lstrip(","))    # 输出结果: Hello, World!  

使用 rstrip() 方法去掉字符串右侧的空格和特殊字符,语法格式:

str.rstrip("object")

  • str:表示对 strip 输入的对象
  • object:表示要删除的对象

example:

mystr = " Hello, World!  "
mystr01 = ", Hello, World!"

print(mystr.rstrip())    # 输出结果: Hello, World!
print(mystr01.rstrip("!"))    # 输出结果:, Hello, World
格式化字符串

格式化字符串是指先指定一个模板,在这个模板中预留几个空位,然后再根据需要填上相应的内容,这些空位需要通过指定的符号标记(占位符),而这些符号不会显示出来

使用 “%” 号操作符,语法格式:

"%[-][+][0][m][.n]格式化字符" % exp

  • -:用于指定左对齐,正数前方无符号,负数前面加负号
  • +:用于指定右对齐,正数前方无符号,负数前方加负号
  • 0:表示右对齐,正数前方无符号,负数前方加负号,用 0 填充空白处(一般与 m 参数一起使用)
  • m:表示占有宽度
  • .n:表示小数点后保留的位数
  • 格式化字符:用于指定类型
  • exp:要转换的项,如果要指定的项有多个,需要通过元组的形式进行指定,但不能使用列表

常用的格式化字符如下:

格式化字符说明
%s字符串(用 str() 显示)
%c单个字符
%d 或者 %i十进制整数
%x十六进制整数
%f 或者 %F浮点数
%r字符串(用 repr() 显示)
%o八进制整数
%e指数(基底写为 e)
%E指数(基底写为 E)
%%字符 %

example:

mystr = "id:%02d\t name:%s\t age:%d"
context01 = (1, 'zhangsan', 24)
context02 = (2, 'lisi', 21)
context03 = [3, 'zhangsan', 24]

print(mystr % context01)    # 输出结果:id:01	 name:zhangsan	 age:24
print(mystr % context02)    # 输出结果:id:02	 name:lisi	 age:21
print(mystr % context03)    # 输出结果:TypeError: %d format: a real number is required, not list,找不到子字符串格式,需要元组,不是列表
print("id:%02d\t name:%s\t age:%d" % (3, 'wangwu', 24))    # 输出结果:id:03	 name:wangwu	 age:24

使用 format() 方法进行字符串格式化,语法格式:

str.format(args)

  • str:指定字符串的显示样式(模板)
  • args:指定要转换的项,如果有多项使用 “,” 号分隔

在创建模板时,需要使用 “{}” 和 “:” 指定占位符,语法格式:

{[index][:[[fill]align][#][width][.precision][type]]}

  • index:指定要设置格式的对象在参数列表中的索引位置,索引值从 0 开始
  • fill:指定空白处填充的字符
  • align:指定对齐方式(值为 “<” 时表示内容左对齐;值为 “>” 时表示内容右对齐;值为 “=” 时表示内容右对齐,将符号放在填充内容的最左侧,且只对数字类型有效;值为 “^” 时表示内容居中),需要配合 width 一起使用
  • sign:指定有无符号数(值为 “+” 时表示正数加正号,负数加负号;值为 “-” 时表示正数不变,负数加负号;值为 “ ”(空格)时表示正数加空格,负数加负号)
  • # :对于二进制、八进制和十六进制数,如果加上 “#”,表示会显示 0b/0o/0X 前缀,否则不显示前缀
  • width:指定所占宽度
  • .precision:指定保留的小数位数
  • type:指定类型

format() 方法中常用的格式化字符:

格式化字符说明
s对字符串类型格式化
d十进制整数
c将十进制整数自动转换成对应的 Unicode 字符
e 或者 E转换为科学计数法表示再格式
g 或者 G自动在 e 和 f 或者 E 和 F 中切换
b将十进制整数自动转换成二进制表示再格式化
o将十进制整数自动转换成八进制表示再格式化
x 或者 X将十进制整数自动转换成十六进制表示再格式化
f 或者 F转换为浮点数(默认小数点后保留 6 位)再格式化
%显示百分比(默认显示小数点后 6 位)

example:

mystr = "id:{:0>2}\t name:{:s}\t age:{:d}"
context01 = mystr.format(1, 'zhangsan', 24)
context02 = mystr.format(2, 'lisi', 21)
context03 = mystr.format[3, 'zhangsan', 24]

print(context01)    # 输出结果:id:01	 name:zhangsan	 age:24
print(context02)    # 输出结果:id:02	 name:lisi	 age:21
print(context03)    # 输出结果:TypeError: 'builtin_function_or_method' object is not subscriptable,对象不可调用
print("id: {:0>2}\t name: {:s}\t age: {:d}".format(3, 'wangwu', 24))    # 输出结果:id: 03	 name: wangwu	 age: 24

将数值格式化为不同的形式,example:

import math

print("1234 + 4321 的结果是(以货币的形式显示):¥{:,.2f} 元".format(1234 + 4321))    # 输出结果:1234 + 4321 的结果是(以货币的形式显示):¥5,555.00 元
print("{0:.1f} 用科学计数法表示:{0:E}".format(120000.1))    # 输出结果:120000.1 用科学计数法表示:1.200001E+05
print("π 取 5 位小数:{:.5f}".format(math.pi))    # 输出结果:π 取 5 位小数:3.14159
print("{0:d} 的十六进制结果是:{0:#x}".format(100))    # 输出结果:100 的十六进制结果是:0x64
print("天才是由 {:.0%} 的灵感,加上 {:.0%} 的汗水".format(0.01, 0.99))    # 输出结果:天才是由 1% 的灵感,加上 99% 的汗水

*args 表示任何多个无名参数,它是一个 tuplelist**kwargs 表示关键字参数,它是一个 dict

example:

args = [",", "。"]
kwargs = {"name": "张三", "age": "20"}

print("我叫{name}{}今年{age}岁了{}".format(*args, **kwargs))    # 输出结果:我叫张三,今年20岁了

字符串编码转换

最早的字符串编码是美国标准信息交换码(即 ASCII 码)。它仅对 10 个数字、26 个大写英文字母、26 个小写英文字母及一些其它符号进行编码。ASCII 码最多只能表示 256 个符号,每个字符占一个字节。随着信息技术的发展,出现了 GBK、GB2312、UTF-8 编码等,其中 GBK 和 GB2312 是我国制定的中文编码标准,使用一个字节表示英文字母;两个字节表示中文字符,而 UTF-8 是国际通用的编码,对全世界所有国家需要用到的字符都进行了编码,UTF-8 采用一个字节表示英文字符;三个字节表示中文字符。

在 Python 中,有两种常用的字符串类型,分别为 str 和 bytes。其中 str 表示 Unicode 字符(ASCII 或者其它);bytes 表示二进制数据(包括编码的文本)。这两种类型的字符串不能拼接在一起使用。通常情况下,str 在内存中以 Unicode 表示,一个字符对应若干个字节,但是在网络上传输或者保存到磁盘中,就需要把 str 转换位字节类型(即 bytes 类型)

bytes 类型的数据是带有 b 前缀的字符串(用单引号或双引号表示),例如:b’\xd2\xb0’、b’mr’ 都是 bytes 类型的数据

使用 encode() 方法将字符串转换为二进制数据(即 bytes),语法格式:

str.encode([encoding="utf-8"][, error="strict"])

  • str:表示对 encode 输入的字符串
  • encoding=“utf-8”:用于指定进行转码时采用的字符编码,默认为 UTF-8,如果使用简体中文,则设置为 GB2312
  • error:指定错误处理方式,值为 strict 时,遇到非法字符就抛出异常;值为 ignore 时,忽略非法字符;值为 replace 时,用 “?” 替换非法字符;值为 xmlcharrefreplace 时,使用 XML 的字符引用等,默认为 strict

example:

mystr = "我喜欢用 Python"

print(mystr.encode('UTF-8'))    # 输出结果:b'\xe6\x88\x91\xe5\x96\x9c\xe6\xac\xa2\xe7\x94\xa8 Python'
print(mystr.encode('GB2312'))    # 输出结果:b'\xce\xd2\xcf\xb2\xbb\xb6\xd3\xc3 Python'
print(mystr.encode('GBK'))    # 输出结果:b'\xce\xd2\xcf\xb2\xbb\xb6\xd3\xc3 Python'

使用 decode() 方法将二进制数据转换为字符串,将 encode() 方法转换的结果再转换为字符串,也称为 “解码”,语法格式:

bytes.decode([encoding="utf-8"][, error="strict"])

  • bytes:表示对 decode 输入的二进制数据
  • encoding=“utf-8”:用于指定进行转码时采用的字符编码,默认为 UTF-8,如果使用简体中文,则设置为 GB2312
  • error:指定错误处理方式,值为 strict 时,遇到非法字符就抛出异常;值为 ignore 时,忽略非法字符;值为 replace 时,用 “?” 替换非法字符;值为 xmlcharrefreplace 时,使用 XML 的字符引用等,默认为 strict

在设置解码采用的字符编码时,需要与编码时采用的字符编码一致

example:

mybytes01 = b'\xe6\x88\x91\xe5\x96\x9c\xe6\xac\xa2\xe7\x94\xa8 Python'
mybytes02 = b'\xce\xd2\xcf\xb2\xbb\xb6\xd3\xc3 Python'
mybytes03 = b'\xce\xd2\xcf\xb2\xbb\xb6\xd3\xc3 Python'

print(mybytes01.decode('UTF-8'))    # 输出结果:我喜欢用 Python
print(mybytes01.decode('GB2312'))    # 输出结果:UnicodeDecodeError: 'gb2312' codec can't decode byte 0xe6 in position 0: illegal multibyte sequence,GB2312 解码器无法解码
print(mybytes02.decode('GB2312'))    # 输出结果:我喜欢用 Python
print(mybytes03.decode('GBK'))    # 输出结果:我喜欢用 Python

正则表达式

正则表达式就是记录文本规则的代码。在处理字符串时,用于查找符合某些复杂规则的字符串的需求

行定位符

用于描述字符串的边界

  • ^:表示行的开始,例如:^H,表示匹配以 “H” 字符开头的行
  • $:表示行的结尾,例如:!$,表示匹配以 “!” 号结尾的行
元字符

常用的元字符如下所示:

元字符说明
.匹配除换行符以外的任意字符,例如:. 在 “mr\nM\tR” 中匹配 “m、r、M、\t、R”
\w匹配字母、数字、下划线和汉字,例如:\w 在 “m_af\n7在” 中匹配 “m、_、a、f、7、在”
\W匹配除字母、数字、下划线和汉字以外的字符,例如:\W 在 “m_af\n7在” 中匹配 “\n”
\s匹配单个的空白符(包括 Tab 键和换行符),例如:\s 在 “mr\nM\tR” 中匹配 “\n、\t”
\S匹配除单个的空白符(包括 Tab 键和换行符)以外的所有字符,例如:\S 在 “mr\nM\tR” 中匹配 “m、r、M、R”
\b匹配单词的开始或结束,单词的分界符通常是空格,标点符号或者换行,例如:\bm 在 “mr\nM\tRm” 中匹配 “m、M”
\d匹配数字,例如:\d 在 “m_af\n7在” 中匹配 “7”
限定符

常用的限定符如下所示:

限定符说明
?匹配前面的字符零次或一次,例如:“colou?r” 可以匹配 “colour、color”
+匹配前面的字符一次或多次,例如:“colou+r” 可以匹配 “colour、colouu…ur”
*匹配前面的字符零次或多次,例如:“colou*r” 可以匹配 “color、colouu…ur”
{n}匹配前面的字符 n 次,例如:“colou{2}r” 可以匹配 “colouur”
{n,}匹配前面的字符最少 n 次,例如:“colou{2,}r” 可以匹配 “colouur、colouu…ur”
{n,m}匹配前面的字符最少 n 次,最多 m 次,例如:“colou{2,4}r” 可以匹配 “colouur、colouuur、colouuuur”
字符类
  • []:表示匹配指定字符,例如:[bc] 在 “abcd” 中匹配 “b、c”
排除字符
  • [^]:将 “^” 放到 “[]” 里面表示不匹配指定字符,例如:[^a] 在 “abcd” 中匹配 “b、c、d”
选择字符
  • (|):表示选择指定字符匹配,例如:([a-z]|[0-9]) 在 “abcd1234ABCD” 中匹配 “a、b、c、d、1、2、3、4”
转义字符
  • \:表示在匹配指定字符时将特殊字符以普通字符匹配,例如:[0-9]\.[0-9] 在 “12.34” 中匹配 “2.3”
分组

分组有两个作用:

  • 第一个作用:可以改变限定符的作用范围,如 “|、*、^” 等,例如:([a-z]|[0-9]) 在 “abcd1234ABCD” 中匹配 “a、b、c、d、1、2、3、4”
  • 第二个作用:分组,也就是子表达式,如 “(.[0-9]{1,3}){3}”,就是对分组 (.[0-9]{1.3}) 进行重复操作
在 Python 中使用正则表达式

在 Python 中使用正则表达式时,是将其作为模式字符串使用的,由于模式字符串中可能包含大量的特殊字符和反斜杠,所以在模式字符前面加 r 或 R,例如:r’\bm\w*\b’

使用 re 模块

匹配字符串

使用 re 模块的 match() 方法从字符串的开始处进行匹配,如果在开始位置匹配成功则返回 Match 对象,否则返回 None,语法格式:

re.match(pattern, string[, flags])

  • pattern:表示模式字符串
  • string:表示要匹配的字符串
  • flags:表示标志位,用于控制匹配方式。如:是否区分字母大小写

常用的标志如下所示:

标志位说明
A 或 ASCII对于 \w、\W、\b、\B、\d、\D、\s 和 \S 只进行 ASCII 匹配
I 或 IGNORECASE执行不区分字母大小写的匹配
M 或 MULTILINE将 ^ 和 $ 用于包括整个字符串的开始和结尾的每一行(默认情况下,仅适用于整个字符串的开始和结尾处)
S 或 DOTALL使用(.)字符匹配所有字符,包括换行符
X 或 VERBOSE忽略模式字符串中未转义的空格和注释

判断字符是否以 “Hel” 开头,example:

import re

pattern = r'Hel\w+'
pattern01 = r'Wor\w+'
mystr = 'Hello, World! Hello, Python!'
match = re.match(pattern, mystr, re.I)
match01 = re.match(pattern01, mystr, re.I)

print(match)    # 输出结果:<re.Match object; span=(0, 5), match='Hello'>
print(match01)    # 输出结果:None

Match 对象中包含了匹配值的位置和匹配数据。其中,Match 对象的常用方法或属性如下:

  • start() 方法:可以获取匹配值的起始位置
  • end() 方法:可以获取匹配值的结束位置
  • span() 方法:可以返回匹配位置的元组
  • group() 方法:可以返回指定匹配的数据
  • string 属性:可以获取要匹配的字符串

example:

import re


pattern = r'Hel\w+'
mystr = 'Hello, World! Hello, Python!'
match = re.match(pattern, mystr, re.I)

print(match)    # 输出结果:<re.Match object; span=(0, 5), match='Hello'>
print("匹配值的起始位置:", match.start())    # 输出结果:匹配值的起始位置: 0
print("匹配值的结束位置:", match.end())    # 输出结果:匹配值的结束位置: 5
print("匹配值的元组:", match.span())    # 输出结果:匹配值的元组: (0, 5)
print("要匹配的字符串:", match.string)    # 输出结果:要匹配的字符串: Hello, World! Hello, Python!
print("匹配数据:", match.group())    # 输出结果:匹配数据: Hello

使用 search() 方法用于在整个字符串中搜索第一个匹配的值,如果在起始位置匹配成功则返回 Match 对象,否则返回 None,语法格式:

re.search(pattern, string[, flags])

  • pattern:表示模式字符串
  • string:表示要匹配的字符串
  • flags:表示标志位,用于控制匹配方式。如:是否区分字母大小写

example:

import re

pattern = r'hel\w+'
mystr = 'Hello, World! Hello, Python!'
mystr01 = 'World! Hello Hello, Python!'
match = re.search(pattern, mystr, re.I)
match01 = re.search(pattern, mystr01, re.I)

print(match)    # 输出结果:<re.Match object; span=(0, 5), match='Hello'>
print(match01)    # 输出结果:<re.Match object; span=(7, 12), match='Hello'>

search() 方法不仅仅是在字符串的起始位置搜索,其它位置有符合的匹配也可以进行搜索

使用 findall() 方法用于在整个字符串中搜索所有符合正则表达式的字符串,并以列表的形式返回。如果匹配成功,则返回包含匹配结构的列表,否则返回空列表,语法格式:

re.findall(pattern, string[, flags])

  • pattern:表示模式字符串
  • string:表示要匹配的字符串
  • flags:表示标志位,用于控制匹配方式。如:是否区分字母大小写

example:

import re

pattern = r'hel\w+'
mystr = 'Hello, World! Hello, Python!'
mystr01 = 'World! Hello Hello, Python!'
match = re.findall(pattern, mystr, re.I)
match01 = re.findall(pattern, mystr01, re.I)

print(match)    # 输出结果:['Hello', 'Hello']
print(match01)    # 输出结果:['Hello', 'Hello']

如果在指定的模式字符串中,包含分组,则返回与分组匹配的文本列表,example:

import re

pattern = r'[1-9]{1,3}(\.[0-9]{1,3}){3}'
num = '127.0.0.1 192.168.10.11'
match = re.findall(pattern, num)

print(match)    # 输出结果:['.1', '.11']

并没有得到匹配的 IP 地址,这是因为在模式字符串中出现了分组,所以得到的结果是根据分组进行匹配的结果,即 “(.[0-9]{1,3}){3}” 匹配的结果。如果要获取整个模式字符串的匹配,可以将整个模式字符串使用一对小括号进行分组,然后在获取结果时,只取返回值列表的每个元素的第一个元素,example:

import re

pattern = r'([1-9]{1,3}(\.[0-9]{1,3}){3})'
num = '127.0.0.1 192.168.10.11'
match = re.findall(pattern, num)

for item in match:
    print(item[0])


127.0.0.1    # 输出结果
192.168.10.11
替换字符串

使用 re 模块的 sub() 方法用于替换字符串,语法格式:

re.sub(pattern, repl, string[, count[, flags]])

  • pattern:表示模式字符串
  • repl:表示替换的字符串
  • string:表示要被查找替换的原始字符串
  • count:表示模式匹配后替换的最大次数,默认为 0 表示替换所有的匹配
  • flags:表示标志位,用于控制匹配方式

example:

import re

pattern = r'1[34578]\d{9}'
mystr = "中奖号码为 4568,联系电话为:13645238965"
result = re.sub(pattern, '136xxxxxxxx', mystr)

print(result)    # 输出结果:中奖号码为 4568,联系电话为:136xxxxxxxx
使用正则表达式分割字符串

使用 split() 方法用于实现根据正则表达式分割字符串,并以列表的形式返回,语法格式:

re.split(pattern, string[, maxsplit[, flags]])

  • pattern:表示模式字符串
  • string:表示要匹配的字符串
  • maxsplit:表示最大的拆分次数
  • flags:表示标志位,用于控制匹配方式

example:

import re

pattern = r'[?|&]'
url = 'asfa?fdgsdg&agg'
result = re.split(pattern, url)

print(result)    # 输出结果:['asfa', 'fdgsdg', 'agg']

函数

函数的创建和调用

创建函数也称为定义函数,用于创建一个具有某种用途的工具。使用 def 关键字实现,语法格式:

def functionname([parameterlist]):
    ['''comments''']
    [functionbody]
  • functionname:函数名称,在调用函数时使用
  • parameterlist:可选参数,用于指定向函数中传递的参数,如果有多个参数,各参数间使用逗号 “,” 分割,如果不指定参数,则该函数没有参数,在调用时也不指定参数
  • comments:可选参数,表示为函数指定注释
  • functionbody:可选参数,用于指定函数体,该函数被调用后要执行的代码。如果函数没有返回值,可以使用 return 语句返回

调用函数也称为执行函数,如果把创建的函数理解为创建一个具有某种用途的工具,那么调用函数就是使用该工具,语法格式:

functionname([parametersvalue])

  • functionname:函数名称,要调用的函数名必须是已创建的函数
  • parametersvalue:可选参数,用于指定各个参数的值,如果有多个参数,各参数间使用逗号 “,” 分割;如果该函数没有参数,则直接写一对小括号即可 “()

example:

def rect_area(width, height):
    """
    功能:用于指定计算长方形的宽和高,并返回长方形的面积
    """

    area = width * height
    return area


r_area = rect_area(320, 80)

print("{0} x {1} 长方形的面积:{2:.2f}".format(320, 80, r_area))    # 输出结果:320 x 80 长方形的面积:25600.00

参数传递

在调用函数时,大多数情况下,主调函数和被调函数之间有数据传递关系,这就是有参数的函数形式。函数参数的作用是传递数据给函数使用,函数利用接收的数据进行具体的操作处理

形式参数和实际参数

在使用函数时,经常会用到形式参数和实际参数,二者都叫做参数,区别如下:

  • 形式参数:在定义函数时,函数名后面括号中的参数为 “形式参数”,简称 “形参”
  • 实际参数:在调用函数时,函数名后面括号中的参数为 “实际参数”,简称 “实参”

example:

def make_coffee(name="卡布奇诺"):
    return "制作一杯{0}咖啡".format(name)


coffee01 = make_coffee("拿铁")
coffee02 = make_coffee()

print(coffee01)    # 输出结果:制作一杯拿铁咖啡
print(coffee02)    # 输出结果:制作一杯卡布奇诺咖啡

实际参数的类型可以分为:值传递和引用传递

  • 值传递:当实际参数为不可变对象时,进行的是值传递。进行值传递后,改变形式参数的值,实际参数的值不变
  • 引用传递:当实际参数为可变对象时,进行的是引用传递。进行引用传递后,改变形式参数的值,实际参数的值也会改变

example:

def demo(obj):
    print("原值:", obj)
    obj += obj


print(('-' * 10), "值传递", ('-' * 10))

mot = "唯有在被追赶的时候,您才能真正地奔跑!"

print("函数调用前:", mot)    # 输出结果:函数调用前: 唯有在被追赶的时候,您才能真正地奔跑!

demo(mot)    # 输出结果:原值: 唯有在被追赶的时候,您才能真正地奔跑!

print("函数调用后:", mot)    # 输出结果:函数调用后: 唯有在被追赶的时候,您才能真正地奔跑!

print(('-' * 10), "引用传递", ('-' * 10))

list01 = ["张三", "李四", "王五"]

print("函数调用前:", list01)    # 输出结果:函数调用前: ['张三', '李四', '王五']

demo(list01)    # 输出结果:原值: ['张三', '李四', '王五']

print("函数调用后:", list01)    # 输出结果:函数调用后: ['张三', '李四', '王五', '张三', '李四', '王五']
位置参数

位置参数也称必备参数,是必须按照正确的顺序传到函数中

参数数量和参数位置必须与定义时一致

example:

def rect_area(width, height):
    """
    功能:用于指定计算长方形的宽和高,并返回长方形的面积
    """

    area = width * height
    return area


r_area = rect_area(320, 80)

print("{0} x {1} 长方形的面积:{2:.2f}".format(320, 80, r_area))    # 输出结果:320 x 80 长方形的面积:25600.00
关键字参数

关键字参数是指使用形式参数的名字来确定参数值,通过这种方式指定实际参数时,不再需要与形式参数的位置完全一致

example:

def rect_area(width, height):
    """
    功能:用于指定计算长方形的宽和高,并返回长方形的面积
    """

    area = width * height
    return area


r_area = rect_area(width=320, height=80)

print("{0} x {1} 长方形的面积:{2:.2f}".format(320, 80, r_area))    # 输出结果:320 x 80 长方形的面积:25600.00
默认参数

在调用参数时,如果没有指定某个参数将抛出异常,为了解决这个问题,可以在定义函数时为形参指定默认值,语法格式:

def functionname(..., [parameter01=defaultvalue01]):
    [functionbody]
  • functionname:函数名,调用函数时使用
  • parameter01=defaultvalue:可选参数,用于指定向函数中传递的默认的形参
  • functionbody:可选参数,用于指定函数体,该函数被调用后,要执行的代码

在定义函数时,指定默认的形式参数必须在所有参数的最后,否则将产生语法错误,且默认参数必须指向不可变对象

example:

def rect_area(width, height=40):
    """
    功能:用于指定计算长方形的宽和高,并返回长方形的面积
    """

    area = width * height
    return area


r_area = rect_area(320)

print("{0} x {1} 长方形的面积:{2:.2f}".format(320, 40, r_area))    # 输出结果:320 x 40 长方形的面积:12800.00

在 Python 中可以使用 “functionname.__defaults__” 查看函数的默认值参数的当前值,其结果是一个元组;使用 “functionname.__doc__” 查看函数的注释信息

example:

def rect_area(width, height=40):
    """
    功能:用于指定计算长方形的宽和高,并返回长方形的面积
    """

    area = width * height
    return area


r_area = rect_area(320)

print("{0} x {1} 长方形的面积:{2:.2f}".format(320, 40, r_area))
print(rect_area.__defaults__)    # 输出结果:(40,)
print(rect_area.__doc__)    # 输出结果:功能:用于指定计算长方形的宽和高,并返回长方形的面积
可变参数

可变参数也称为不定长参数,即传入函数中的实际参数可以是任意多个,主要有两种形式:

  • *parameter:表示接收任意多个实际参数并将其放到一个元组中
  • **parameter:表示接收任意多个类似关键字参数一样显示赋值的实际参数,并将其放到一个字典中

example:

def sum(*numbers):
    total = 0.0
    for number in numbers:
        total += number
    return total


print(sum(100.0, 20.0, 30.0))    # 输出结果:150.0
print(sum(30.0, 80.0))    # 输出结果:110.0
def show_info(**info):
    print(('-' * 10), 'show_info', ('-' * 10))
    for key, value in info.items():
        print('{0} - {1}'.format(key, value))


show_info(name='Tony', age=18, sex=True)
show_info(sutdent_name='Tony', sutdent_no='1000')


---------- show_info ----------    # 输出结果
name - Tony
age - 18
sex - True
---------- show_info ----------
sutdent_name - Tony
sutdent_no - 1000

返回值

可以在函数体中使用 return 语句为函数指定返回值,该返回值可以是任意类型,并且无论 return 语句出现在函数的什么位置,只要得到执行就会直接结束函数,语法格式:

return [value]

  • value:可选参数,用于指定要返回的值,可以返回一个值,也可以是多个值

当函数中没有 return 语句时,或者忽略了 return 语句的参数时,将返回 None 空值

example:

def sum(*numbers):
    total = 0.0
    for number in numbers:
        total += number
    return total


print(sum(100.0, 20.0, 30.0))    # 输出结果:150.0


def sum(*numbers):
    total = 0.0
    for number in numbers:
        total += number


print(sum(100.0, 20.0, 30.0))    # 输出结果:None

变量的作用域

局部变量

局部变量是指在函数内部定义并使用的变量,它只在函数内部有效,example:

def demo():
    x = 10
    print("函数内访问局部变量:", x)


demo()    # 输出结果:函数内访问局部变量: 10

print("函数外访问局部变量:", x)    # 输出结果:NameError: name 'x' is not defined,没有这个名称
全局变量

全局变量能够作用于函数内外

在函数外定义变量,example:

x = 20


def demo():
    x = 10
    print("函数内访问全局变量:", x)


demo()    # 输出结果:函数内访问全局变量: 10

print("函数外访问全局变量:", x)    # 输出结果:函数外访问全局变量: 20

使用 global 关键字在函数内定义全局变量,example:

x = 20


def demo():
    global x
    x = 10
    print("函数内访问全局变量:", x)


demo()    # 输出结果:函数内访问全局变量: 10

print("函数外访问全局变量:", x)    # 输出结果:函数外访问全局变量: 10

匿名函数(lambda)

匿名函数是指没有名字的函数,应用在需要一个函数,但是又不想命名这个函数的场合,使用 lambda 表达式创建匿名函数,语法格式:

通常情况下,这样的函数只使用一次,使用 lambda 表达式时,需要定义一个变量,用于调用该 lambda 表达式

result = lambda [arg01[, arg02[,... [, argn]]]]: expression

  • result:用于调用 lambda 表达式
  • [arg01[, arg02[,… [, argn]]]]:可选参数,指定要传递的参数列表,多个参数间使用逗号 “,” 分隔
  • expression:用于指定一个实现具体功能的表达式。如果有参数,那么在该表达式中将应用这些参数

example:

import math

def circlearea(r):
    result = math.pi*r*r
    return result


r = 10
print("半径为:", r, "圆的面积为:", circlearea(r))    # 输出结果:半径为: 10 圆的面积为: 314.1592653589793

使用 lambda 表达式后:

import math

r = 10
result = lambda r: math.pi*r*r

print("半径为:", r, "圆的面积为:", result(r))    # 输出结果:半径为: 10 圆的面积为: 314.1592653589793

面向对象程序设计

概述

面向对象(Object Oriented)的英文缩写是 OO,它是一种设计思想。从 20 世纪 60 年代提出面向对象的概念到现在,它已经发展成为一种比较成熟的编程思想,并且逐步成为目前软件开发领域的主流技术

面向对象中的对象(object),通常是指客观世界中存在的对象,具有唯一性,对象之间各不相同,各有各的特点,每一个对象都有自己的运动规律和内部状态;对象与对象之间又是可以相互联系、相互作用的,每个对象都有自己的属性和行为

对象

对象是一种抽象概念,表示任意存在的事物。世间万物皆对象!对象是事物存在的实体

通常将对象分为两个部分:

  • 静态对象:静态对象指的是对象 “属性”,任何对象都具备自身属性,这些属性不仅是客观存在的,而且是不能忽略的,如:人的性别
  • 动态对象:动态对象指的是对象 “行为”, 即对象执行的动作,如:人可以跑步

类相当于一个模板,依据这样的模块来创建对象,就是类的实例化,所以对象也被称为 “实例”

类是封装对象的属性和行为的载体,反过来说具有属性和行为的一类实体被称为类。在类中,可以定义每个对象共有的属性和方法

面向对象程序设计的特定

具有三大基本特征:封装、继承和多态

  • 封装:将对象的属性和行为封装起来的载体就是类,类通常会对客户隐藏其实现的细节,这就是封装的思想。这种封装思想保证了类内部数据结构的完整性,使用该类的用户不能直接看到类中的数据结构,只能执行类允许公开的数据,这样就避免了外部对内部数据的影响,提高了程序的可维护性
  • 继承:继承是实现重复利用的重要手段,子类通过继承复用了父类的属性和行为的同时又添加了子类特有的属性和行为
  • 多态:将父类对象应用于子类的特征就是多态

类的定义和使用

在 Python 中,类表示具有相同属性和方法的对象的集合。在使用类时,需要先定义类,然后再创建类的实例,通过类的实例就可以访问类中的属性和行为

定义类

使用 class 关键字来实现定义类,语法格式:

class ClassName:
    """类的帮助信息"""
    statement
  • ClassName:指定类名,一般使用首字母大写的格式,这种命名方法也称为 “驼峰式命名法”
  • “”“类的帮助信息”“”:指定类的说明文档
  • statement:指定类体,主要由类变量(或类成员)、方法和属性等定义语句组成。在编程时若不想马上编写某些代码,又不想有语法错误,可以使用 pass 语句占位,pass 语句只用于维护程序结构的完整

example:

class Dog:
    pass
创建类的实例

class 语句本身并不创建该类的任何实例,在类定义完成以后创建类的实例(即实例化该类的对象),语法格式:

ClassName([parameter[, parameter,[...]]])

  • ClassName:指定具体的类名
  • [parameter[, parameter,[…]]]:可选参数,如果创建类时,没有创建 __init__() 方法或者 __init__() 方法只有一个 self 参数时,parameter 可以省略

example:

dog = Dog()
print(dog())    # 输出结果:<__main__.Dog object at 0x000001DDBE67B310>,dog 是 Dog 类的实例对象
创建 __init__() 方法

创建类后,可以手动创建一个 __init__() 方法,这种方法就是 “构造方法”。该方法是一个特殊的方法,每当创建一个类的新实例时,Python 都会自动执行它。__init__() 方法必须包含一个 self 参数,并且必须是第一个参数。self 参数是一个指向实例本身的引用,用于访问类中属性和方法。在方法调用时会自动传递实际参数 self,因此当 __init__() 方法只有一个参数时,在创建类的实例时,就不需要指定实际参数了

example:

class Dog:
    def __init__(self, name, age):    # 构造方法
        self.name = name
        self.age = age


dog = Dog("球球", 2)    # 创建实例化对象,调用构造方法

print("我们家的狗狗叫:{0},{1} 岁了。".format(dog.name, dog.age))    # 输出结果:我们家的狗狗叫:球球,2 岁了。
访问限制

为了保证类内部的某些属性或方法不被外部所访问,可以在属性或方法名前面添加单下划线 “_”、双下划线 “__”、或首尾加双下划线 “__”,从而限制访问权限,作用如下:

  • 单下划线 “_”:表示 protected(保护)类型的成员,只允许类本身和子类进行访问,不能使用 “from module import *” 语句导入

    创建 Swan 类,定义保护属性 _neck_swan,并使用 __init__() 方法访问该属性,example:

    class Swan:
        _neck_swan = "天鹅的脖子很长"
    
        def __init__(self):
            print("__init__:", Swan._neck_swan)
    
    swan = Swan()    # 输出结果:__init__: 天鹅的脖子很长
    print("直接访问:", swan._neck_swan)    # 输出结果:直接访问: 天鹅的脖子很长
    
  • 双下划线 “__”:表示 private(私有)类型的成员,只允许定义该方法的类本身进行访问,而且也不能通过类的实例进行访问,但是可以使用 “类的实例名._类名__xx” 方式访问

    创建 Swan 类,定义私有属性 __neck_swan,并使用 __init__() 方法访问该属性,example:

    class Swan:
        __neck_swan = "天鹅的脖子很长"
    
        def __init__(self):
            print("__init__:", Swan.__neck_swan)
    
    swan = Swan()    # 输出结果:__init__: 天鹅的脖子很长
    print("加入类名访问:", swan._Swan__neck_swan)    # 输出结果:加入类名访问: 天鹅的脖子很长
    print("直接访问:", swan._neck_swan)    # 输出结果:AttributeError: 'Swan' object has no attribute '_neck_swan',“Swan” 对象没有属性 “_neck_Swan”
    
  • 首尾加双下划线 “__”:表示定义特殊方法

类的成员

类的成员:

  • 类的成员

    • 成员变量
      • 实例变量
      • 类变量
    • 构造方法
    • 成员方法
      • 实例方法
      • 类方法
    • 属性(property)
  • 成员变量:保存了类或对象的数据

  • 构造方法:是一种特殊的函数,用于初始化类的成员变量

  • 成员方法:是在类中定义的函数

  • 属性:是对类进行封装而提供的特殊方法

实例变量和实例方法属于对象,通过对象调用;类变量和类方法属于类,通过类调用

实例变量

实例变量是对象个体特有的 “数据”。实例变量通过 “实例对象.实例变量” 形式访问

example:

class Dog:
    def __init__(self, name, age):    # 构造方法
        self.name = name
        self.age = age


dog = Dog("球球", 2)    # 创建实例化对象,调用构造方法

print("我们家的狗狗叫:{0},{1} 岁了。".format(dog.name, dog.age))    # 输出结果:我们家的狗狗叫:球球,2 岁了。
实例方法

实例方法与实例变量一样,都是某个实例(或对象)个体特有的方法。定义实例方法时,它的第一个参数也应该是 self,这会将当前实例与该方法绑定起来,说明该方法属于实例,在调用方法时不需要传入 self,类似与构造方法。实例方法通过 “实例对象.实例方法” 形式访问

example:

class Dog:
    def __init__(self, name, age, sex="雌性"):    # 构造方法
        self.name = name
        self.age = age
        self.sex = sex

    def run(self):    # 实例方法
        print("{}在跑...".format(self.name))

    def speak(self, sound):    # 实例方法
        print("{}在叫,{}!".format(self.name, sound))


dog = Dog("球球", 2)
dog.run()    # 输出结果:球球在跑...
dog.speak("汪 汪 汪")    # 输出结果:球球在叫,汪 汪 汪!
类变量

类变量是属于类的变量,不属于单个对象。类变量通过 “类名.类变量” 形式访问

example:

class Account:
    interest_rate = 0.0568

    def __init__(self, owner, amount):
        self.owner = owner
        self.amount = amount


account = Account('Tony', 800000.0)

print("账户名:{0}".format(account.owner))    # 输出结果:账户名:Tony
print("账户金额:{}".format(account.amount))    # 输出结果:账户金额:800000.0
print("利率:{}".format(Account.interest_rate))    # 输出结果:利率:0.0568
类方法

类方法与类变量类似,属于类不属于个体实例,在定义类方法时,它的第一个参数不是 self,而是类本身。类方法通过 “类名.类方法” 形式访问

定义类方法需要以装饰器 “@” 符号开头修饰函数、方法和类,用来约束它们

example:

class Account:
    interest_rate = 0.0668

    def __init__(self, owner, amount):
        self.owner = owner
        self.amount = amount

    @classmethod
    def interest_by(cls, amt):    # 类方法,cls 表示类自身(即 Account 类),可用 Account 代替
        return cls.interest_rate * amt


interest = Account.interest_by(12000.0)

print("计算利息:{0:.4f}".format(interest))    # 输出结果:计算利息:801.6000

类方法可用访问类变量和其它类方法,但不能访问其它实例方法和实例变量

封装性

封装性是面向对象重要的基本特征之一。封装隐藏了对象的内部细节,只保留有限的对外接口,外部调用者不用关心对象的内部细节,使操作对象变得简单

私有变量

为了防止外部调用者随意存取类的内部数据(成员变量),内部数据(成员变量)会被封装为 “私有变量”。外部调用者只能通过方法调用私有变量

默认情况下,Python 中的变量是公有的,可以在类的外部访问它们,如果想让它们成为私有变量,则在变量前加上双下划线 “__” 即可。

在类的外部不可以访问私有变量

example:

class Account:
    __interest_rate = 0.0568

    def __init__(self, owner, amount):
        self.owner = owner
        self.__amount = amount    # 定义私有变量

    def desc(self):    # 在类的内部可以访问私有变量
        print("{0} 金额:{1} 利率:{2}".format(self.owner, self.__amount, Account.__interest_rate))


account = Account('Tony', 800000.0)
account.desc()    # 输出结果:Tony 金额:800000.0 利率:0.0568

print("账户名:{}".format(account.owner))    # 输出结果:账户名:Tony
print("账户金额:{}".format(account.__amount))    # 输出结果:AttributeError: 'Account' object has no attribute '__amount',“Account” 对象没有 “__amount” 属性
print("利率:{}".format(Account.__interest_rate))    # 输出结果:AttributeError: type object 'Account' has no attribute '__interest_rate',类型对象 “Account” 没有属性 “__interest_rate”
私有方法

私有方法与私有变量的封装是类似的,在方法前面加上双下划线 “__” 就是私有方法了

在类的外部不可以调用私有方法

example:

class Account:
    __interest_rate = 0.0568

    def __init__(self, owner, amount):
        self.owner = owner
        self.__amount = amount

    def __get_info(self):    # 定义私有方法
        return "{0} 金额:{1} 利率:{2}".format(self.owner, self.__amount, Account.__interest_rate)

    def desc(self):    # 在类的内部可以调用私有方法
        print(self.__get_info())


account = Account('Tony', 800000.0)
account.desc()    # 输出结果:Tony 金额:800000.0 利率:0.0568
account.__get_info()    # 输出结果:AttributeError: 'Account' object has no attribute '__get_info',“Account” 对象没有属性 “__get_info”
使用属性

为了实现对象的封装,在一个类中不应该有公有的成员变量,这些成员变量应该被设计为私有的,然后通过公有的 set(赋值)和 get(取值)方法访问

example:

class Dog:
    def __init__(self, name, age, sex="雌性"):
        self.name = name
        self.__age = age

    def run(self):
        print("{}在跑...".format(self.name))

    def get_age(self):    # 定义 get() 方法,返回私有实例变量 __age
        return self.__age

    def set_age(self, age):    # 定义 set() 方法,通过 age 参数更新私有实例变量 __age
        self.__age = age


dog = Dog("球球", 2)
print("狗狗的年龄:{}".format(dog.get_age()))    # 输出结果:狗狗的年龄:2
dog.set_age(3)
print("修改后狗狗的年龄:{}".format(dog.get_age()))    # 输出结果:修改后狗狗的年龄:3

当外部调用者通过两个公有方法访问被封装的私有变量时,比较麻烦。这时可以在类中定义属性,属性可以替代 get() 和 set() 这两个公有方法,在调用时比较简单。

  • 属性通过 “实例对象.属性名” 形式访问
  • 属性赋值通过 “实例对象.属性名” 形式赋值

example:

class Dog:
    def __init__(self, name, age, sex="雌性"):
        self.name = name
        self.__age = age    # 私有变量 __age,对应的属性名应该去除前面双下划线之后的名称即 age

    def run(self):
        print("{}在跑...".format(self.name))

    @property    # 类方法
    def age(self):    # 定义 age 属性的 get() 方法,使用 @property 装饰器进行修饰,方法名就是属性名即 age
        return self.__age

    @age.setter    # 类方法
    def age(self, age):    # 定义 age 属性的 set() 方法,使用 @age.setter 装饰器进行修饰,方法名就是属性名即 age
        self.__age = age


dog = Dog("球球", 2)
print("狗狗的年龄:{}".format(dog.age))    # 输出结果:狗狗的年龄:2
dog.age = 3
print("修改后狗狗的年龄:{}".format(dog.age))    # 输出结果:修改后狗狗的年龄:3

属性在本质上就是两个方法,在方法前添加上装饰器使得方法成为属性

继承性

继承性也是面向对象重要的基本特性之一。在程序设计中实现继承,表示这个类拥有它所继承的类的所有公有成员或受保护成员,在面向对象编程中,被继承的类称为 “父类”,继承的新类称为 “子类”

继承

子类继承父类,在类的定义语句中,类名右侧使用一对小括号指定它的父类就可以了,语法格式:

class ClassName(baseclasslist):
    """类的帮助信息"""
    statement
  • ClassName:指定类名
  • baseclasslist:指定要继承的父类,如果有多个,类名之间用逗号 “,” 分隔
  • statement:指定类体,主要由类变量(类成员)、方法和属性等定义语句组成

example:

class Animal:
    def __init__(self, name):
        self.name = name

    def show_info(self):
        return "动物的名字:{}".format(self.name)

    def move(self):
        print("动一动...")


class Cat(Animal):
    def __init__(self, name, age):
        super().__init__(name)
        self.age = age


cat = Cat('Tom', 2)
cat.move()    # 输出结果:动一动...
print(cat.show_info())    # 输出结果:动物的名字:Tom

子类继承父类时,只将父类公有的成员变量和方法才可以被继承

多继承

在 Python 中,当子类继承多个父类时,如果在多个父类中有相同的成员方法或成员变量,则子类优先继承左边父类中的成员方法或成员变量,从左到右继承级别从高到低

example:

class Horse:
    def __init__(self, name):
        self.name = name

    def show_info(self):
        return "马的名字:{}".format(self.name)

    def run(self):
        print("马跑...")


class Donkey:
    def __int__(self, name):
        self.name = name

    def show_info(self):
        return "驴的名字:{}".format(self.name)

    def run(self):
        print("驴跑...")

    def roll(self):
        print("驴打滚...")


class Mule(Horse, Donkey):
    def __init__(self, name, age):
        super().__init__(name)
        self.age = age


m = Mule("骡宝莉", 1)
m.run()    # 输出结果:马跑...
m.roll()    # 输出结果:驴打滚...
print(m.show_info())    # 输出结果:马的名字:骡宝莉
方法重写

如果子类的方法名与父类的方法名相同,则在这种情况下,子类的方法会重写(Override)父类的同名方法

example:

class Horse:
    def __init__(self, name):
        self.name = name

    def show_info(self):
        return "马的名字:{}".format(self.name)

    def run(self):
        print("马跑...")


class Donkey:
    def __init__(self, name):
        self.name = name

    def show_info(self):
        return "驴的名字:{}".format(self.name)

    def run(self):
        print("驴跑...")

    def roll(self):
        print("驴打滚...")


class Mule(Horse, Donkey):
    def __init__(self, name, age):
        super().__init__(name)
        self.age = age

    def show_info(self):
        return "骡:{},{} 岁了".format(self.name, self.age)


m = Mule("骡宝莉", 1)
m.run()    # 输出结果:马跑...
m.roll()    # 输出结果:驴打滚...
print(m.show_info())    # 输出结果:骡:骡宝莉,1 岁了

多态性

多态性也是面向对象重要的基本特性之一。“多态” 指对象可以表现出多种形态

继承与多态

在多个子类继承父类,并重写父类方法后,这些子类所创建的对象之间就是多态的,这些对象采用不同的方式实现父类方法

example:

class Animal:
    def speak(self):
        print("动物叫,但不指定是哪种动物叫!")


class Dog(Animal):
    def speak(self):
        print("小狗:汪汪汪叫...")


class Cat(Animal):
    def speak(self):
        print("小猫:喵喵喵叫...")


an01 = Dog()
an02 = Cat()
an01.speak()    # 输出结果:小狗:汪汪汪叫...
an02.speak()    # 输出结果:小猫:喵喵喵叫...
鸭子类型测试与多态

Python 的多态性更加灵活,支持鸭子类型测试。鸭子类型测试是指:若看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟可以称为鸭子

由于支持鸭子类型测试,所以 Python 解释器不检查发生多态的对象是否继承了同一个父类,只要它们有相同的行为(方法),它们之间就是多态的。

example:

def start(obj):
    obj.speak()


class Animal:
    def speak(self):
        print("动物叫,但不指定是哪种动物叫!")


class Dog(Animal):
    def speak(self):
        print("小狗:汪汪汪叫...")


class Cat(Animal):
    def speak(self):
        print("小猫:喵喵喵叫...")


class Car(Animal):
    def speak(self):
        print("小汽车:嘀嘀叫...")


start(Dog())    # 输出结果:小狗:汪汪汪叫...
start(Cat())    # 输出结果:小猫:喵喵喵叫...
start(Car())    # 输出结果:小汽车:嘀嘀叫...

模块

概述

在 Python 中,一个扩展名为 “.py” 的文件就称为一个模块。

通常情况下,我们把能够实现某一特定功能的代码放置在一个文件中作为一个模块,从而方便其它程序和脚本导入使用。另外使用模块也可以避免函数名和变量名冲突

模块可以提高代码的可维护性和可用性

自定义模块

自定义模块有两个作用:

  • 规范代码,让代码更易于阅读
  • 方便其它程序使用已经编写好的代码,提高开发效率

自定义模块主要分为两部分:

  • 创建模块
  • 导入模块
创建模块

创建模块时,可以将模块中相关的代码(变量定义和函数定义等)编写在一个单独的文件中,并且将该文件命名为 “模块名+.py” 的形式

创建一个用于根据身高、体重计算 BMI 指数的模块,命名为 bmi.py,其中 bmi 为模块名 .py 为扩展名,example:

def fun_bmi(person, height, weight):
    """
    功能:根据身高和体重计算 BMI 指数
    :param person: 姓名
    :param height: 身高,单位:米
    :param weight: 体重,单位:千克
    :return: 
    """
    
    print(person + "的身高:" + str(height) + "米 \t体重:" + str(weight) + "千克")
    
    bmi = weight / (height * height)
    print(person + "的 BMI 指数为:" + str(bmi))


def fun_bmi_upgrade(*person):
    """
    功能:根据身高和体重计算 BMI 指数(升级版)
    :*param *person: 可变参数,该参数中需要传递带 3 个元素 列表,分别为:姓名、身高(单位:米)和体重(单位:千克)
    :return: 
    """
使用 import 语句导入模块

使用模块需要先以模块的形式加载模块中的代码,语法格式:

import modulename [as alias]

  • modulename:要导入的模块名
  • as alias:如果模块名太长,可以给模块起一个别名,通过别名也可以使用模块

example:

import bmi     # 导入模块

bmi.fun_bmi("张三", 1.75, 120)    # 执行模块中的 fun_bmi 函数
使用 from…import 语句导入模块

在使用 import 语句导入模块时,每执行一条 import 语句都会创建一个新的命名空间(namespace),并且在该命名空间中执行与 .py 文件相关的所有语。在执行时,需在具体的变量、函数和类名前加上 “模块名.” 前缀。如果不想在每次导入模块时都创建一个新的命名空间,而是将具体的定义导入到当前命名空间中,可以使用 from...import 语句,使用 from...import 语句导入模块后,不需要再添加前缀,直接通过具体的变量、函数和类名等访问即可

语法格式:

from modulename import member

  • modulename:模块名,区分字母大小写
  • member:指定要导入的变量、函数或者类等,可以同时导入多个定义,各个定义之间用逗号 “,” 分隔,也可以使用 “*” 号

在使用 from…import 语句导入模块中的定义时,需要保证所导入的内容在当前的命名空间中是唯一的,否则将出现冲突,后导入的同名变量、函数或者类会覆盖先导入的

导入两个包括同名的函数模块,example:

"""
创建矩形模块,对应的文件名为 rectamgle.py
"""

def girth(width, height):
    """
    功能:计算矩形周长
    :param widgh: 宽度
    :param height: 高
    :return:
    """

    return (width + height) * 2


def area(width, height):
    """
    功能:计算矩形面积
    :param width: 宽度
    :param height: 高
    :return:
    """

    return width * height


if __name__ == '__main__':
    print(area(10, 20))

"""
创建圆形模块,对应的文件名 circular.py
"""

import math

PI = math.pi


def girth(r):
    """
    功能:计算圆形周长
    :param r: 半径
    :return:
    """

    return round(2 * PI * r, 2)


def area(r):
    """
    功能:计算圆形面积
    :param r: 半径
    :return:
    """

    return round(PI * r * r, 2)


if __name__ == '__main__':
    print(girth(10))

"""
创建 computc.py 文件,导入矩形模块和圆形模块
"""

from rectangle import *    # 使用 from...import 导入
from circular import *

if __name__ == '__main__':
    print("圆形的周长为:", girth(10))    # 输出结果:圆形的周长为: 62.83
    print("矩形的周长为;", girth(10, 20))    # 输出结果:TypeError: girth() takes 1 positional argument but 2 were given,参数为 1 个,但指定了 2 个参数


import rectangle as r    # 使用 import 语句导入
import circular as c

if __name__ == '__main__':
    print("圆形的周长为:", c.girth(10))    # 输出结果:圆形的周长为: 62.83
    print("矩形的周长为:", r.girth(10, 20))    # 输出结果:矩形的周长为: 60
模块搜索目录

当使用 import 语句导入模块时,默认情况下,会按照以下顺序进行查找

  1. 在当前目录(即执行的 Python 脚本文件所在的目录)下查找
  2. 到 PYTHONATH(环境变量)下的每个目录中查找
  3. 到 Python 的默认安装目录下查找

以上各个目录的具体位置保存在标准模块 sys 的 sys.path 变量中。可以通过以下代码输出具体的目录

example:

import sys

print(sys.path)    # 输出结果:['E:\\SourceCode\\Python\\PythonProject', 'E:\\SourceCode\\Python\\PythonProject', 'E:\\Software\\PyCharm\\PyCharm 2022.1.2\\plugins\\python\\helpers\\pycharm_display', 'E:\\Software\\Python3\\python310.zip', 'E:\\Software\\Python3\\DLLs', 'E:\\Software\\Python3\\lib', 'E:\\Software\\Python3', 'E:\\Software\\PyCharm\\pythonProject\\venv', 'E:\\Software\\PyCharm\\pythonProject\\venv\\lib\\site-packages', 'E:\\Software\\PyCharm\\PyCharm 2022.1.2\\plugins\\python\\helpers\\pycharm_matplotlib_backend']

以下三种方法,添加指定模块目录到 sys.path:

  • 临时添加,只在当前执行文件的窗口有效,窗口关闭后失效,example:

    import sys
    
    sys.path.append("E:\\SourcdCode\\Python\\demo")
    
  • 增加 .pth 文件(推荐),通过该方法添加的目录只在当前版本的 Python 中有效,并且创建完成后,要重新打开要执行的 Python 文件,example:

    在 Python 安装目录下的 Lib\site-packages 子目录中,创建扩展名为 .pth 文件,在该文件中添加要导入模块所在的目录

    E:\\SourcdCode\\Python\\demo
    
  • 在 PYTHONPATH 环境变量中添加,通过该方法添加的目录可以再不同版本的 Python 中共享,并且创建完成后,要重新打开要执行的 Python 文件,example:

    打开 “环境变量” 框,如果没有 PYTHONPATH 系统变量,则需要先创建一个,否则直接选中 PYTHONPATH 变量,再点击 “编辑” 按钮,并且再弹出的对话框 “变量值” 文本中添加新的模块目录

以主程序的形式执行

"""
创建名为 christmastree 模块
"""

pinetree = "我是一颗松树"


def fun_christmastree():
    """
    功能:一个梦
    :return:
    """

    pinetree = "挂上彩灯、礼物...我变成一颗圣诞树\n"
    print(pinetree)


print("\n下雪了...\n")
print(('-' * 20), "开始做梦", ('-' * 20))
fun_christmastree()
print(('-' * 20), "梦醒了...", ('-' * 20))
pinetree = "我身上落满雪花," + pinetree
print(pinetree)

"""
创建名为 main.py 的文件,导入 christmastree 模块并执行
"""

import christmastree

print(christmastree.pinetree)


下雪了...    # 输出结果
-------------------- 开始做梦 --------------------
挂上彩灯、礼物...我变成一颗圣诞树
-------------------- 梦醒了... --------------------
我身上落满雪花,我是一颗松树
我身上落满雪花,我是一颗松树

导入模块后,不仅输出了全局变量的值,而且模块中原有的测试代码也被执行了,这个结果不是我们想要的。在模块中将原本直接执行的测试代码放在一个 if 语句中,修改后的代码:

"""
创建名为 christmastree 模块
"""

pinetree = "我是一颗松树"


def fun_christmastree():
    """
    功能:一个梦
    :return:
    """

    pinetree = "挂上彩灯、礼物...我变成一颗圣诞树\n"
    print(pinetree)


if __name__ == '__main__'      # 使用此方法时,下面的语句在导入到其他文件中时不执行
    print("\n下雪了...\n")
    print(('-' * 20), "开始做梦", ('-' * 20))
    fun_christmastree()
    print(('-' * 20), "梦醒了...", ('-' * 20))
    pinetree = "我身上落满雪花," + pinetree
    print(pinetree)

"""
创建名为 main.py 的文件,导入 christmastree 模块并执行
"""

import christmastree

print(christmastree.pinetree)    # 输出结果:我是一颗松树

在每个模块的定义中都包括一个记录模块名称的变量 __name__,程序可以检查该变量,以确定它们在哪个模块中执行。如果一个模块不是被导入到其它程序中执行,那么它可能在解释器的顶级模块中执行。顶级模块的 __name__ 变量的值为 __main__

Python 中的包

包是一个分层次的目录结构,它将一组功能相近的模块组织在一个目录下。这样即可以起到规范代码的作用,又能避免模块名重名引起的冲突。简单理解就是 “文件夹”,只不过在该文件夹下必须存在一个名称为 “__iinit__.py” 的文件

包结构

先创建名为 shop 项目,然后在该包下又创建了 admin、home 和 templates 包和一个 manager.py 的文件,最后在每个包中,又创建了相应的模块

- shop    # 项目名
  - admin    # 用于保存后台文件的包
    __init__.py
    forms.py
    views.py
  - home    # 用于保存前台文件的包
    __init__.py
    forms.py
    views.py
  - templates    # 用于保存模板文件的包
    __init__.py
    models.py
  manager.py    # 入口程序
创建和使用包

创建包,实际上就是创建一个文件夹,并且在该文件夹中创建一个名称为 “__init__.py” 的文件。__init__.py 文件是一个模块文件,模块名为对应的包名,例如:在 settings 包中创建的 __init__.py 文件,对应的模块名为 settings。在 __init__.py 文件中可以不写任何代码,也可以编写一些 Python 代码,在 __init__.py 文件中所编写的代码,在导入包时会自动执行

  1. 创建名为 settings 的文件夹

  2. 在 settings 文件夹中创建 __init__.py 文件

  3. 在 settings 包中创建名为 size 的模块,在模块中定义两个变量,example:

    width = 800
    height = 600
    

在创建包之后就可以在包中创建相应的模块,然后再使用 import 语句从包中加载模块,加载模块通常有以下三种方式:

  • 通过 “import + 完整包名 + 模块名” 形式加载指定模块

    通过该方法导入模块,在使用时需要在变量名前加入 “settings.size” 前缀,example:

    import settings.size
    
    if __name__ == '__main__':
        print("宽度:", settings.size.width)    # 输出结果:宽度: 800
        print("高度:", settings.size.height)    # 输出结果:高度: 600
    
  • 通过 “from + 完整包名 + import + 模块名” 形式加载指定模块

    通过该方法导入模块,在使用时不需要带包前缀,但是需要带模块名,example:

    from settings import size
    
    if __name__ == '__main__':
        print("宽度:", size.width)    # 输出结果:宽度: 800
        print("高度:", size.height)    # 输出结果:高度: 600
    
  • 通过 “from + 完整包名 + 模块名 + import + 定义名” 形式加载指定模块

    通过该方法导入模块的函数、变量或类后,在使用时直接使用函数、变量或类名即可,example:

    from settings.size import width, height
    
    if __name__ == '__main__':
        print("宽度:", width)    # 输出结果:宽度: 800
        print("高度:", height)    # 输出结果:高度: 600
    

    通过 “from + 完整包名 + 模块名 + import + 定义名” 形式加载指定模块时,可以使用 “*” 号代替定义名,表示加载该模块下的全部定义

定义 size 模块,在该模块中定义两个保护类型的全局变量,定义 change() 函数,用于修改两个全局变量的值,再定义两个函数,分别用于获取两个保护类型的全局变量,example:

_width = 800
_height = 600


def change(w, h):
    global _width
    _width = w
    global _height
    _height = h


def getwidth():
    global _width
    return _width


def getheight():
    global _height
    return _height

在 settings 包的上层目录创建名为 main.py 的文件,在该文件中导入 settings 包下的 size 模块的全部定义,example:

from settings.size import *

if __name__ == '__main__':
    change(1024, 768)
    print("宽度:", getwidth())    # 输出结果:宽度: 800
    print("高度:", getheight())    # 输出结果:高度: 600

引入其它模块

在 Python 中,除了可以自定义模块外,还可以引用其它模块,主要包括使用标准模块和第三方模块

导入标准模块

直接使用 import 语句导入 Python 文件中即可,导入后通过模块名调用其提供的函数,examle:

import random

print(random.randint(0, 10))    # 输出结果:7

使用第三方模块

可以在 Python 官方推出的 http://pypi.python.org/pypi 中找到。使用第三方模块,需要先下载并安装该模块,然后就可以像使用标准模块一样导入并使用了

下载和安装第三方模块可以使用 Python 提供的 pip 命令实现,语法格式:

pip <command> [modulename]

  • command:指定要执行的命令。常用的参数值有 install(用于安装第三方模块)、uninstall(用于卸载已经安装的第三方模块)、list(用于显示已经安装的第三方模块)等
  • modulename:可选参数,用于指定要安装或者卸载的模块名,当 command 为 install 或 uninstall 时不能忽略

example:

pip install numpy # 安装 numpy 模块

异常处理及程序调试

在程序运行过程中,经常会遇到各种各样的错误,这些错误统称为 “异常”。这些异常有的是由于开发者将关键字敲错导致的,这类错误多数产生的是 SyntaxError:invalid syntax(无效的语法),这些直接导致程序不能运行

程序运行出错时会有 Traceback 信息,Traceback 指的是 “异常堆栈信息”,描述了程序运行的过程及引发异常的信息

Python 中常见的异常

异常描述
NameError尝试访问一个没有声明的变量引发的错误
IndexError索引超出序列范围引发的错误
IndentationError缩进错误
ValueError传入的值错误
KeyError请求一个不存在的字典关键字引发的错误
IOError输入输出错误(如要读取的文件不存在)
ImportError当 import 语句无法找到模块或 from 无法在模块中找到相应的名称时引发的错误
AttributeError尝试访问未知的对象属性引发的错误
TypeError类型不合适引发的错误
MemoryError内存不足
ZeroDivisionError除数为 0 引发的错误

异常处理语句

try···except 语句

在使用时,把可能产生异常的代码放在 try 语句块中,把处理结果放在 except 语句块中,如果 try 语句块中的代码出现错误时,就会执行 except 语句块中的代码;如果 try 语句块中的代码没有错误,就不执行 except 语句块中的代码

try···except 语句流程:

Created with Raphaël 2.3.0 开始 执行 try 语句代码 是否发生异常 执行 except 语句代码 结束 yes no

try···except 语法格式:

try:
    执行的代码 1
except [ExceptionName [as alias]]:
    执行的代码 2
  • 执行的代码 1:指定可能出现错误的代码块
  • [ExceptionName [as alias]]:可选参数,指定要捕获的异常。ExceptionName 表示要捕获异常的名称;al alias 表示为捕获异常的名称指定一个别名,通过该别名,可以记录异常的具体内容
  • 执行的代码 2:指定出现异常时,执行的代码

except 后面不指定异常名称,则表示捕获全部异常。通过 try···except 语句捕获到异常后,程序会继续执行

example:

def division():
    """
    功能:分苹果
    :return:
    """
    print(('-' * 20), "分苹果了", ('-' * 20))
    apple = int(input("请输入苹果的个数:"))
    children = int(input("请输入来了几个下朋友:"))
    result = apple // children
    remain = apple - result * children

    if remain > 0:
        print(f"{apple} 个苹果,平均分给 {children} 个小朋友,每个人分 {result} 个,剩下 {remain} 个苹果")
    else:
        print(f"{apple} 个苹果,平均分给 {children} 个小朋友,每个人分 {result} 个苹果")


if __name__ == '__main__':
    try:
        division()
    except ZeroDivisionError:
        print("出错了!苹果不能被 0 个小朋友分")


if __name__ == '__main__':    # 多个 except 语句
    try:
        division()
    except ZeroDivisionError:
        print("出错了!苹果不能被 0 个小朋友分")
    except ValueError as e:
        print("输入错误:", e)


if __name__ == '__main__':    # 同时处理多个类似异常,可以将 except 语句进行合并处理
    try:
        division()
    except (ZeroDivisionError, ValueError) as e:
        print("出错了!原因是:", e)
try···except···else 语句

当 try 语句块中没有发现异常时要执行的语句,当 try 语句块中发现异常,将不执行,语法格式:

try:
    执行的代码 1
except [ExceptionName [as alias]]:
    执行的代码 2
else:
    执行的代码 3

example:

def division():
    """
    功能:分苹果
    :return:
    """
    print(('-' * 20), "分苹果了", ('-' * 20))
    apple = int(input("请输入苹果的个数:"))
    children = int(input("请输入来了几个下朋友:"))
    result = apple // children
    remain = apple - result * children

    if remain > 0:
        print(f"{apple} 个苹果,平均分给 {children} 个小朋友,每个人分 {result} 个,剩下 {remain} 个苹果")
    else:
        print(f"{apple} 个苹果,平均分给 {children} 个小朋友,每个人分 {result} 个苹果")


if __name__ == '__main__':    # 多个 except 语句
    try:
        division()
    except ZeroDivisionError:
        print("出错了!苹果不能被 0 个小朋友分")
    except ValueError as e:
        print("输入错误:", e)
    else:
        print("苹果顺利分完!")
try···except···finally 语句

有时在 try···except 语句中会占用一些资源,例如:打开的文件、网络连接、打开的数据库等,如果要释放这些资源,就要用 finally 进行清理代码

完整的异常处理语句应该包含 finally 代码块,通常情况下无论程序中有无异常产生,finally 代码块都会执行

try···except···finally 语句流程:

Created with Raphaël 2.3.0 开始 执行 try 语句代码 是否发生异常 执行 except 语句代码 执行 finally 语句代码 结束 yes no

try···except···finally 语句语法格式:

try:
    执行的代码 1
except [ExceptionName [as alias]]:
    执行的代码 2
finally:
    执行的代码 3

example:

def division():
    """
    功能:分苹果
    :return:
    """
    print(('-' * 20), "分苹果了", ('-' * 20))
    apple = int(input("请输入苹果的个数:"))
    children = int(input("请输入来了几个下朋友:"))
    result = apple // children
    remain = apple - result * children

    if remain > 0:
        print(f"{apple} 个苹果,平均分给 {children} 个小朋友,每个人分 {result} 个,剩下 {remain} 个苹果")
    else:
        print(f"{apple} 个苹果,平均分给 {children} 个小朋友,每个人分 {result} 个苹果")


if __name__ == '__main__':    # 多个 except 语句
    try:
        division()
    except ZeroDivisionError:
        print("出错了!苹果不能被 0 个小朋友分")
    except ValueError as e:
        print("输入错误:", e)
    else:
        print("苹果顺利分完!")
    finally:
        print("进行分苹果操作")
使用 raise 语句抛出异常

如果某个函数或方法可能会产生异常,但不想在当前函数或方法中处理这个异常,则可以使用 raise 语句在函数或方法中抛出异常,语法格式:

raise [ExceptionName [(reason)]]

  • ExceptionName:可选参数,指定抛出异常名称以及异常信息的相关描述,如果省略将错误原样抛出
  • (reason):可选参数,指定抛出异常时附带相关描述信息,如果省略将不附带相关描述信息

example:

def division():
    """
    功能:分苹果
    :return:
    """
    print(('-' * 20), "分苹果了", ('-' * 20))
    apple = int(input("请输入苹果的个数:"))
    children = int(input("请输入来了几个下朋友:"))

    if apple < children:
    raise ValueError("苹果太少了,不够分!")

    result = apple // children
    remain = apple - result * children

    if remain > 0:
        print(f"{apple} 个苹果,平均分给 {children} 个小朋友,每个人分 {result} 个,剩下 {remain} 个苹果")
    else:
        print(f"{apple} 个苹果,平均分给 {children} 个小朋友,每个人分 {result} 个苹果")


if __name__ == '__main__':    # 多个 except 语句
    try:
        division()
    except ZeroDivisionError:
        print("出错了!苹果不能被 0 个小朋友分")
    except ValueError as e:
        print("输入错误:", e)
    else:
        print("苹果顺利分完!")
    finally:
        print("进行分苹果操作")

程序调试

在程序开发过程中,有语法和逻辑方面的错误。当语法错误时,程序会直接停止;当逻辑错误时,程序会一直执行但结果是错误的。这时需要掌握一定的程序调试方法

使用自带的 IDLE 进行程序调试

在继承开发工具中打开 Debug 功能,然后添加需要的断点,按下 F5 继续执行程序

断点的作用:设置断点后,程序执行到断点时就会暂时中断执行,可以查看某些变量的值,程序可以随时继续执行

使用 assert 语句调试程序

Python 提供了 assert 语句调试程序,assert 意识是断言,一般用于对程序某个时刻必须满足的条件进行验证,语法格式:

assert expression [, reason]

  • expression:条件表达式,如果该表达式的值为真时,什么都不做,如果为假时,则抛出 AssertionError 异常
  • reason:可选参数,用于对判断条件进行描述

example:

def division():
    """
    功能:分苹果
    :return:
    """
    print(('-' * 20), "分苹果了", ('-' * 20))
    apple = int(input("请输入苹果的个数:"))
    children = int(input("请输入来了几个下朋友:"))

    assert apple > children, "苹果不够分!"    # 断言调试

    result = apple // children
    remain = apple - result * children

    if remain > 0:
        print(f"{apple} 个苹果,平均分给 {children} 个小朋友,每个人分 {result} 个,剩下 {remain} 个苹果")
    else:
        print(f"{apple} 个苹果,平均分给 {children} 个小朋友,每个人分 {result} 个苹果")


if __name__ == '__main__':    # 多个 except 语句
    try:
        division()
    except ZeroDivisionError:
        print("出错了!苹果不能被 0 个小朋友分")
    except ValueError as e:
        print("输入错误:", e)
    else:
        print("苹果顺利分完!")
    finally:
        print("进行分苹果操作")

通常情况下,assert 语句可以和异常处理语句结合使用,example:

if __name__ == '__main__':    # 多个 except 语句
    try:
        division()
    except ZeroDivisionError:
        print("出错了!苹果不能被 0 个小朋友分")
    except ValueError as e:
        print("输入错误:", e)
    except AssertionError as e:
        print("输入错误!", e)
    else:
        print("苹果顺利分完!")
    finally:
        print("进行分苹果操作")

assert 语句只在调试阶段有效。可以通过在执行 python 命令时加入 -O(大写)参数来关闭 assert 语句

自定义异常类

自定义的异常类,需要继承 Exception 类或子类,之前的 ZeroDivisionError 和 ValueError 异常都属于 Exception 的子类

example:

class TestExcept(Exception):
    def __init__(self, message):
        super().__init__(massage)
  • TestExcept:定义异常类的名称
  • message:定义异常的描述信息
  • super():调用父类构造方法,并把参数 massage 传给父类构造方法

文件及目录操作

在文本文件的内部以字符的形式存储数据,字符是有编码的,例如:GBK(简体中文)、UTF-8 等;在二进制文件的内部以字节的形式存储数据,没有编码的概念,二进制文件较为常用,例如:Windows 中的 exe、图片(jpg、png 等),以及 Word、Excel 和 PPT 等文件

打开关闭文件

在 Python 中,内置了文件(File)对象,在使用文件对象时,首先需要通过内置的 open() 方法创建一个文件对象,然后通过该对象提供的方法进行一些基本文件操作

创建和打开文件

可以使用 open() 函数创建或者打开指定文件并创建文件对象,语法格式:

file = open(filename[, mode[, buffering[, encoding[, errors[, newline[, closefd[, opener]]]]]]])

  • file:被创建的文件对象
  • filename:要创建或打开的文件名称,需要用单引号或双引号括起来。可以使用相对路径也可以使用绝对路径
  • mode:可选参数,指定文件的打开模式。默认的打开模式为只读(r)
  • buffering:可选参数,指定读写文件的缓冲模式,值为 0 表示不缓存;值为 1 表示缓存;如果大于 1,则表示缓冲区的大小。默认为缓存模式
  • encoding:指定文件编码。默认为 None。encoding 参数仅可在采用文本方式(即 mode 值带 t)读写数据的情况下有效,二进制方式下不可指定。常用的有 utf-8、ascii、gbk 等
  • errors:指定在文本文件发生编码错误时如何处理。默认为 None。仅当 mode 参数采用文本方式时有效,二进制方式下不可指定。常用的可选值有 strict、ignore、replace、surrogateescape、xmlcharrefreplace、backslashreplace、namereplace 等。推荐参数为 “ignore” 表示在遇到编码错误时忽略该错误,程序继续执行
  • newline:设置换行符。默认为 None。可选值包括 None、“\r”、“\n”、“\r\n”。
  • closefd:控制传入的 file 参数类型。默认为 True,当为 True 时,file 参数可以是表示文件路径的字符串,也可以是文件描述符,当为 False 时,file 参数只能是文件描述符,传入字符串会报错

mode 参数列表如下:

参数说明
t文本模式(默认)
x写模式,新建一个文件,如果文件存在,则报错
b二进制模式
+打开一个文件进行更新(读写模式)
U通用换行模式(不推荐)
r只读模式打开文件,文件的指针将会放在文件的开头。文件必须存在
rb只读模式以二进制格式打开文件,文件的指针将会放在文件的开头,一般用于非文本文件。例如:图片、声音等。文件必须存在
r+读写模式打开文件,写入新的内容覆盖原有内容,文件的指针将会放在文件的开头。文件必须存在
rb+读写模式以二进制格式打开文件,文件的指针将会放在文件的开头,一般用于非文本文件。例如:图片、声音等。文件必须存在
w只写模式打开文件。文件如果存在,则将其覆盖,否则创建新文件
wb只写模式以二进制格式打开文件,一般用于非文本文件。例如:图片、声音等。文件如果存在,则将其覆盖,否则创建新文件
w+读写模式打开文件,先清空原有内容,再写入内容。文件如果存在,则将其覆盖,否则创建新文件
wb+读写模式以二进制格式打开文件,一般用于非文本文件。例如:图片、声音等。文件如果存在,则将其覆盖,否则创建新文件
a追加模式打开文件。如果该文件存在,文件指针将会放在文件的末尾,否则,创建新文件写入
ab追加模式以二进制格式打开文件。如果该文件存在,文件指针将会放在文件的末尾,否则,创建新文件写入
a+读写、追加模式打开文件。如果该文件存在,文件指针将会放在文件的末尾,否则,创建新文件写入
ab+读写、追加模式以二进制格式打开文件。如果该文件存在,文件指针将会放在文件的末尾,否则,创建新文件写入

打开一个不存在的文件,example:

file = open('test.txt', 'w')
print("打开文件", file)   # 输出结果:打开文件 <_io.TextIOWrapper name='test.txt' mode='w' encoding='cp936'>

以二进制形式打开文件,example:

file = open('picture.png', 'rb')
print(file)    # 输出结果:<_io.BufferedReader name='picture.png'>

打开文件时指定编码方式,example:

file = open('test.txt', 'r', encoding='utf-8')
print(file)    # 输出结果:<_io.TextIOWrapper name='test.txt' mode='r' encoding='utf-8'>
关闭文件

可以使用 close() 方法对打开的文件进行关闭,以免对文件造成不必要的破坏,语法格式:

file.close()

  • file:为打开的文件对象

example:

file = open('test.txt', 'r', encoding='utf-8')
print(file)    # 输出结果:<_io.TextIOWrapper name='test.txt' mode='r' encoding='utf-8'>

file.close()

close() 方法先刷新缓冲区中还没有写入的信息,然后再关闭文件,这样可以将没有写入到文件的内容写入到文件中,在关闭文件后,便不能再进行写入操作了

打开文件时用 with as 语句

如果在打开文件时抛出异常,那么将导致文件不能被及时关闭。使用 with as 语句在处理文件时,无论是否抛出异常,都能保证 with 语句执行完成后关闭已打开的文件,语法格式:

with expression as target:
    with-body
  • expression:指定一个表达式,可以是打开文件的 open() 函数
  • target:指定一个变量,并且将 expression 的结果保存到该变量中
  • with-body:指定 with 语句体,可以是执行 with 语句后相关的一些操作语句

example:

with open('test.txt', 'r') as file:
    print("打开文件")    # 输出结果:打开文件
打开文件时用 finally 语句

对文件的操作往往会抛出异常,为了保证对文件的操作无论是正常结束还异常结束,都能够关闭文件,我们应该将对 close() 方法的调用放在异常处理的 finally 代码块中

example:

try:
    file = open('test.txt', 'r')
    print("打开文件")
except FileNotFoundError as e:
    print("文件不存在,请先创建文件")
except OSError as e:
    print("处理异常")
finally:
    if file is not None:
        file.close()
        print("关闭文件!")


打开文件    # 输出结果
关闭文件!

读写文本文件

  • read([size]):从文件中读取字符串,size 限制读取字符串的个数,默认为全部读取
  • readline([size]):在读取到换行符或文件末尾时返回单行字符串。如果已经到文件末尾,则返回一个空字符串
  • readlines([size]):读取文件数据到一个字符串列表中,每行数据都是列表的一个元素
  • write(s):将字符串 s 写入文件中,并返回写入的字符个数
  • writelines(lines):向文件中写入一个字符串列表,默认没有分隔符
  • flush():刷新写缓冲区,在文件没有关闭的情况下将数据写入文件中
写入文件内容

在 Python 中对文件对象提供了 write() 方法,可以向文件中写入内容,语法格式:

file.write(string)

  • file:指定要打开的文件对象
  • string:指定要写入的内容

在调用 write() 方法向文件中写入内容的前提是在打开文件时,指定打开模式为 w(可写)或 a(追加),否则会报错

使用 “w” 读写模式,写入内容,example:

file = open('test.txt', 'w')
file.write("Hello, World!\n")
print("写入内容")
file.close()


写入内容    # 输出结果

在写入文件后,一定要调用 close() 方法关闭文件,否则写入的内容不会保存到文件中。这是因为当我们写入文件内容时,操作系统不会立刻把数据写入到磁盘中,而是先缓存起来,只有调用 close() 方法时才会将数据写入到磁盘中

使用 “a” 追加模式写入内容,example:

file = open('test.txt', 'a')
file.write("Hello, Python!")
print("写入内容")
file.close()
读取文件

使用文件对象的 read() 方法读取指定个数的字符,语法格式:

file.read([size])

  • file:指定要读取的文件对象
  • size:指定要读取字符的个数,如果省略,则读取所有内容

在调用 read() 方法读取文件内容的前提时在打开文件时,指定打开模式为 r(只读)或 r+(读写),否则会报错

example:

with open('test.txt', 'r') as file:
    mystr = file.read()
    print(mystr)


Hello, World!    # 输出结果
Hello, Python!

使用文件对象的 seek() 方法将文件的指针移动到只读位置,然后再应用 read() 方法读取,可以读取文件部分内容,语法格式:

file.seek(offset[, whence])

  • file:指定已经打开的文件对象
  • offset:指定移动的字符个数,具体位置与 whence 参数有关
  • whence:指定从什么位置开始计算。默认为 0,值为 0 表示从文件头开始计算,值为 1 表示从当前位置开始计算,值为 2 表示从文件末尾开始计算

example:

with open('test.txt', 'r') as file:
    file.seek(6)
    mystr = file.read(10)
    print(mystr)


 World!    # 输出结果
He

对于 whence 参数,如果在打开文件时,没有使用 b 模式(即 rb),那么只允许从文件头开始计算相对位置,从文件末尾计算时就会抛出异常
在使用 seek() 方法时,如果采用 GBK 编码,那么 offset 的值是按一个汉字(包括中文标点符号)占两个字符计算,采用 UTF-8 编码,则一个汉字占 3 个字符,不过无论采用何种编码英文和数字都是按一个字符计算

使用文件对象的 readline() 方法用于每次读取一行数据,语法格式:

file.readline()

  • file:指定已经打开的文件对象

example:

with open('test.txt', 'r') as file:
    num = 0
    while True:
        num += 1
        line = file.readline()
        if line == '':
            break
        print(num, line)


1 Hello, World!    # 输出结果

2 Hello, Python!

使用文件对象的 readlines() 方法用于读取全部行,返回的是一个字符串列表,每行内容为一个元素,语法格式:

file.readlines()

  • file:指定要打开的文件对象

example:

with open('test.txt', 'r') as file:
    lines = file.readlines()
    print(lines)    # 输出结果:['Hello, World!\n', 'Hello, Python!']

目录操作

目录也称文件夹,用于分层保存文件。通过使用 os 内置模块和 os.path 子模块实现

常用的目录操作主要有判断目录是否存在、创建目录、删除目录和遍历目录等

os 模块是 Python 内置模块与操作系统和文件系统相关,执行结果通常与操作系统有关

os 模块和 os.path 子模块

常用的变量有以下几个:

  • os.name:用于获取操作系统,例如:nt
  • os.linesep:用于获取当前操作系统上的换行符,例如:
  • os.sep:用于获取当前操作系统所使用的路径分隔符,例如:\

os 模块提供的与目录相关的函数:

函数说明
getcwd()返回当前的工作目录
listdir(path)返回指定路径下的文件和目录信息
mkdir(path[, mode])创建目录
makedirs(path1/path2…[, mode])创建多级目录
rmdir(path)删除目录
removedirs(path1/path2…)删除多级目录
chdir(path)把 path 设置为当前工作目录
walk(top[, topdown[, onerror]])遍历目录树,该方法返回一个元组,包括所有路径名、所有目录列表和文件列表 3 个元素

os.path 子模块提供的与目录相关的函数

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

用于定位一个文件或者目录的字符串被称为一个路径。一般分为两种路径:相对路径和绝对路径

  • 相对路径

    当前工作目录是指当前文件所在的目录,通过 getcwd() 函数获取当前工作目录,example:

    import os
    
    print(os.getcwd())    # 输出结果:E:\SourceCode\Python\PythonProject
    

    相对路径就是依赖于当前工作目录。在当前目录下有一个子目录 demo,并且在该子目录下保存文件 demo.txt,打开 demo.txt,example:

    import os
    
    with open(r"demo\demo.txt", 'r') as file:
        print(file.read())    # 输出结果:Hello, World!
    
  • 绝对路径

    绝对路径指在使用文件时指定文件的实际路径,从根目录开始。通过 abspath(path) 函数获取文件的绝对路径,example:

    import os
    
    print(os.path.abspath(r"demo\demo.txt"))    # r"demo\demo.txt" 表示要获取文件的相对路径
    
    
    E:\SourceCode\Python\PythonProject\demo\demo.txt       # 输出结果 
    
  • 拼接路径

    将两个路径或者多个路径拼接到一起组成一个新的路径,通过 join() 函数实现,exmaple:

    import os
    
    print(os.path.join("E:\\SourceCode\\Python\\PythonProject", "demo\\demo.txt"))    # 输出结果:E:\SourceCode\Python\PythonProject\demo\demo.txt
    print(os.path.join("E:\\SourceCode\\Python\\PythonProject", "demo\\demo.txt", "E:\\test", "demo.txt"))    # 输出结果:E:\test\demo.txt
    

    在拼接路径时,并不会检测路径是否真实存在
    如果要拼接的路径中,没有一个绝对路径,那么最后拼接出来的是一个相对路径
    如果要拼接的路径中,存在多个绝对路径,那么以从左到右为序最后一次出现的绝对路径为准,该路径之前的参数都将被忽略

判断目录或文件是否存在

通过使用 exists() 函数判断目录或文件是否存在,example:

import os

print(os.path.exists("E:/SourceCode/Python"))    # 输出结果:True


if not os.path.exists("E:/SourceCode/Python"):
    os.mkdir("E:/SourceCode/Python")
    print("目录不存在,创建成功")
else:
    print("目录存在")
创建目录

通过使用 mkdir() 函数创建一级目录,example:

import os

os.mkdir("demo")    # 在当前目录下创建一级目录

通过使用 makedirs() 函数创建多级目录,example:

import os

os.makedirs("demo/test/aaa")    # 在当前目录下创建多级目录
删除目录

通过使用 rmdir() 函数删除目录,只有当要删除的目录为空时才起作用,example:

import os

os.rmdir("demo/test/aaa")    # 删除 aaa 目录

通过使用 rmtree() 函数删除目录,可以删除不是空目录,example:

import os

os.rmtree("demo/test")    # 删除 test 目录及目录下的内容
遍历目录

通过使用 walk() 函数遍历目录,语法格式:

os.walk(top[, topdown[, onerror[, followlinks]]])

  • top:指定要遍历的根目录
  • topdown:指定遍历的顺序。默认为 True,如果值为 True 表示自上而下遍历;如果值为 False 表示自下而上遍历
  • onerror:指定错误处理方式。默认忽略
  • followlinks:默认为 False,如果值为 True 表示指定在支持的系统上访问由符号链接指向的目录;如果值为 False 表示指定不支持由符号链接指向的目录

os.walk 函数返回 3 个元素(dirpathdirnamesfilenames)的元组生成器对象:

  • dirpath:表示当前遍历的路径,是一个字符串
  • dirnames:表示当前路径下包含的子目录,是一个列表
  • filenames:表示当前路径下包含的文件,是一个列表

example:

import os

mytuples = os.walk("E:/SourceCode/Python/PythonProject")

for mytuple in mytuples:
    print(mytuple)


('E:/SourceCode/Python/PythonProject\\settings', ['__pycache__'], ['size.py', '__init__.py'])    # 输出结果


path = "E:/SourceCode/Python/PythonProject"

for root, dirs, files in os.walk(path, topdown=True):
    for name in dirs:
        print("dir:", os.path.join(root, name))
    for name in files:
        print("file:", os.path.join(root, name))

高级文件操作

os 模块提供的与文件相关的函数

函数说明
access(path, accessmode)获取对文件是否有指定的访问权限(读取、写入、执行),accessmode 的值是 R_OK(读取)、W_OK(写入)、X_OK(执行)或 F_OK(存在),如果有指定权限则返回 1,否则返回 0
chmod(path, mode)修改 path 指定文件的访问权限
remove(path)删除 path 指定的文件路径
rename(src, dst)将文件或目录 src 重命名为 dst
stat(path)返回 path 指定的文件信息
startfile(path[, operation])使用关联的应用程序打开 path 指定的文件
删除文件

通过使用 remove() 函数删除文件,如果要删除的文件不存在会报错,语法格式:

os.remove(path)

  • path:为要删除的文件路径,可以是相对路径,也可以是绝对路径

删除当前工作目录下的 test.txt 文件,example:

import os

os.remove('test.txt')
重命名文件或目录

通过使用 rename() 函数重命名文件或目录,语法格式:

os.rename(src, dst)

  • src:指定要进行重命名的文件或目录
  • dst:指定重命名后的文件或目录

example:

import os

src = "test.txt"
dst = "test01.txt"

if os.path.exists(src):
    os.rename(src, dst)
    print("文件重命名成功!")
else:
    print("文件重命名失败!")

使用 rename() 函数重命名目录时,只能修改最后一级的目录名称,否则会报错

获取文件基本信息

通过使用 stat() 函数可以获取文件的基本信息(文件的最后一次访问时间、最后一次修改时间、文件大小等),语法格式:

os.stat(path)

  • path:要获取文件基本信息的路径,可以是相对路径,也可以是绝对路径

os.stat() 函数的返回值是一个对象,通过访问这些对象属性获取文件基本信息,对象属性如下:

属性说明
st_mode保护模式
st_dev设备名
st_ino索引号
st_uid用户 ID
st_nlink硬链接号(被链接数目)
st_gid组 ID
st_size文件大小,单位为字节
st_atime最后一次访问时间
st_mtime最后一次修改时间
st_ctime最后一次状态变化的时间

example:

import os

fileinfo = os.stat("test.txt")

print("文件完整路径:", os.path.abspath("test.txt"))    # 输出结果:文件完整路径: E:\SourceCode\Python\PythonProject\test.txt
print("索引号:", fileinfo.st_ino)    # 输出结果:索引号: 8725724278072824
print("设备名:", fileinfo.st_dev)    # 输出结果:设备名: 48482355
print("文件大小:", fileinfo.st_size)    # 输出结果:文件大小: 29
print("最后一次访问时间:", fileinfo.st_atime)    # 输出结果:最后一次访问时间: 1668651841.6280491
print("最后一次修改时间:", fileinfo.st_mtime)    # 输出结果:最后一次修改时间: 1668591550.380154
print("最后一次状态变化时间:", fileinfo.st_ctime)    # 输出结果:最后一次状态变化时间: 1668585771.569891

为了让上面的显示更加直观,需要对数值进行格式化,example:

import os


def formattime(longtime):
    """
    功能:格式化日期时间
    :param longtime: 要格式化的时间
    :return:
    """
    import time

    return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(longtime))


def formatbyte(number):
    """
    功能:格式化文件大小
    :param number: 要格式化的字节数
    :return:
    """
    for (scale, label) in [((1024 * 1024 * 1024), 'GB'), ((1024 * 1024), 'GB'), (1024, 'GB')]:
        if number >= scale:    # 文件大小大于或等于 1 KB
            return "%.2f %s" % ((number * 1.0 / scale), label)
        elif number == 1:    # 文件大小为 1 字节
            return "1 字节"
        else:    # 文件大小小于 1 KB
            byte = "%.2f" % (number or 0)
    return (byte[:-3] if byte.endswith('.00') else byte) + " 字节"    # 去掉结尾的 .00 并且加上单位 “字节”


if __name__ == '__main__':
    fileinfo = os.stat("test.txt")

    print("文件完整路径:", os.path.abspath("test.txt"))
    print("索引号:", fileinfo.st_ino)
    print("设备名:", fileinfo.st_dev)
    print("文件大小:", formatbyte(fileinfo.st_size))
    print("最后一次访问时间:", formattime(fileinfo.st_atime))
    print("最后一次修改时间:", formattime(fileinfo.st_mtime))
    print("最后一次状态变化时间:", formattime(fileinfo.st_ctime))

使用 Python 操作数据库

数据库编程接口

Python 中定义的数据库 API 接口有模块接口、连接对象、游标对象、类型对象和构造器、DB API 的可选扩展以及可选的错误处理机制等

连接对象

数据库连接对象(Connection Object)主要提供获取数据库游标对象和提交、回滚事务的方法,以及关闭数据库连接

通过使用 connect() 函数获取连接对象,常用参数如下:

参数说明
dsn数据源名称,给出该参数表示数据库依赖
user用户名
password用户密码
host主机名
database数据库名称

使用 pymysql 模块连接数据库,example:

conn = pymysql.connect(host='localhost', 
                       user='user', 
                       password='password', 
                       db='test', 
                       charset='utf-8', 
                       cursorclass=pymysql.cursors.DictCursor
                       )

通过使用 connect() 函数获取连接对象,常用方法如下:

方法说明
close()关闭数据库连接
commit()提交事务
rollback()回滚事务
cursor()获取游标对象,操作数据库,如:执行 DML 操作,调用存储过程等

commit() 提交事务,事务主要用于处理数据量大、复杂度高的数据。如果操作的是一系列的动作,如:张三给李四转账,有如下两个操作,这时使用事务可以维护数据库的完整性,保证两个操作要么全部执行,要么全部不执行:

  • 张三账户金额减少
  • 李四账户金额增加
游标对象

游标对象(Cursor Object)代表数据库中的游标,用于指示抓取数据操作的上下文,主要提供执行 SQL 语句、调用存储过程、获取查询结果等方法

通过使用 cursor() 方法可以获取到游标对象,游标对象属性如下:

属性说明
description数据库列类型和值的描述信息
rowcount返回结果的行数统计信息,如:SELECT、UPDATE、CALLPROC 等

常用游标对象方法如下:

方法说明
callproc(procname[, parameters])调用存储过程,需要数据库支持
close()关闭当前游标
execute(operation[, parameters])执行数据库操作,SQL 语句或者数据库命令
executemany(operation, seq_of_params)用于批量操作,如:批量更新
fetchone()获取查询结果集中的下一条记录
fetchmany(size)获取指定数量的记录
fetchall()获取结果集的所有记录
nextset()跳至下一个可用的结果集
arraysize指定使用 fetchmany() 获取的行数,默认为 1
setinputsize(sizes)设置在调用 execute*() 方法时分配的内存区域大小
setoutputsize(sizes)设置列缓冲区大小,对大数据列(如:LONGS 和 BLOBS)尤其有用

使用 SQLite

创建数据库文件

Python 中已经内置了 SQLite3,所以可以直接使用 import 语句导入 SQLite3 模块

Python 操作数据库的通用的流程图:

Created with Raphaël 2.3.0 开始 创建 connection 获取 cursor 执行 SQL 语句,处理数据结果 关闭 cursor 关闭 connection 结束

创建一个 mrsoft.db 的数据库文件,然后执行 SQL 语句创建一个 user 表,user 表包含 id 和 name 两个字段,example:

import sqlite3

conn = sqlite3.connect('mrsoft.db')    # 连接到数据库文件,如果文件不存在会自动在当前目录创建
cursor = conn.cursor()    # 创建一个 Cursor

cursor.execute('create table user (id int(10) primary key, name varcher(20))')    # 执行 SQL 语句,创建 user 表

cursor.close()    # 关闭 Cursor 游标
conn.close()    # 关闭 Connection
操作 SQLite

新增用户数据信息,语法格式:

insert into 表名(字段名1, 字段名2,..., 字段名n) value (字段值1, 字段值2,..., 字段值n)

example:

import sqlite3

conn = sqlite3.connect('mrsoft.db')
cursor = conn.cursor()

cursor.execute('insert into user (id, name) values ("1", "zhangsan")')    # 执行 SQL 语句,插入 user 表信息
cursor.execute('insert into user (id, name) values ("2", "lisi")')
cursor.execute('insert into user (id, name) values ("3", "wangwu")')

cursor.close()
conn.close()

查看用户数据信息,语法格式:

select 字段名1, 字段名2,..., 字段名n from 表名 where 查询条件

如果要查询全部的字段可以使用一个 “*” 号代替所有字段名

查询数据时通常使用如下 3 种方式:

  • fetchone():获取查询结果集中的下一条记录
  • fetchmany(size):获取指定数量的记录
  • fetchall():获取结果集的所有记录

获取查询结果集中的下一条记录,example:

import sqlite3

conn = sqlite3.connect('mrsoft.db')
cursor = conn.cursor()

cursor.execute('select * from user')    # 执行 SQL 语句,查询 user 表
result1 = cursor.fetchone()
print(result1)    # 输出结果:(1, 'zhangsan')

cursor.close()
conn.close()

获取指定数量的记录,example:

import sqlite3

conn = sqlite3.connect('mrsoft.db')
cursor = conn.cursor()

cursor.execute('select * from user')    # 执行 SQL 语句,查询 user 表
result1 = cursor.fetchmany(2)
print(result1)    # 输出结果:[(1, 'zhangsan'), (2, 'lisi')]

cursor.close()
conn.close()

获取结果集的所有记录,example:

import sqlite3

conn = sqlite3.connect('mrsoft.db')
cursor = conn.cursor()

cursor.execute('select * from user')    # 执行 SQL 语句,查询 user 表
result1 = cursor.fetchall()
print(result1)    # 输出结果:[(1, 'zhangsan'), (2, 'lisi'), (3, 'wangwu')]

cursor.close()
conn.close()

获取结果集中 id 大于 1 的数据,example:

import sqlite3

conn = sqlite3.connect('mrsoft.db')
cursor = conn.cursor()

cursor.execute('select * from user where id > ?', (1,))    # 执行 SQL 语句,查询 user 表
result1 = cursor.fetchall()
print(result1)    # 输出结果:[(2, 'lisi'), (3, 'wangwu')]

cursor.close()
conn.close()

使用 “?” 号占位符代替具体的数值,然后使用一个元组来替换问号。如果元组中只有一个值,不要忽略元组中最后的 “,” 号

修改用户数据信息

修改数据信息,语法格式:

update 表名 set 要修改的字段名2 = 要修改后的字段值2 where 修改条件

example:

import sqlite3

conn = sqlite3.connect('mrsoft.db')
cursor = conn.cursor()

cursor.execute('update user set name = ? where id = ?', ("zhaoliu", 1))    # 执行 SQL 语句,修改数据信息
cursor.execute('select * from user')
result1 = cursor.fetchall()
print(result1)    # 输出结果:[(1, 'zhaoliu'), (2, 'lisi'), (3, 'wangwu')]

cursor.close()
conn.close()
删除用户数据信息

删除用户数据信息,语法格式:

delete from 表明 where 查询条件

example:

import sqlite3

conn = sqlite3.connect('mrsoft.db')
cursor = conn.cursor()

cursor.execute('delete from user where id = ?', (1,))    # 执行 SQL 语句,删除数据信息
cursor.execute('select * from user')
result1 = cursor.fetchall()
print(result1)    # 输出结果:[(2, 'lisi'), (3, 'wangwu')]

cursor.close()
conn.close()

使用 MySQL

  • 下载 MySQL 到本地
  • 安装 MySQL
  • 设置环境变量,使在任何路径都可以使用 SQL 命令
  • 启动 MySQL
    • 在 cmd 窗口,输入命令:net start mysql57,启动 MySQL
    • 使用账户密码进入 MySQL,输入命令:mysql -u root -p,提示输入密码
  • 使用 Navicat for MySQL 管理软件,新建 MySQL 连接

Python 安装 PyMySQL 模块:pip install PyMySQL

连接数据库

MySQL 数据库与 SQLite 数据库操作类似

example:

import pymysql

db = pymysql.connect("localhost", "root", "root", "studyPython")    # 连接数据库,参数:主机名或 IP、用户名、密码、数据库名称
cursor = db.cursor()    # 创建 Cursor 游标对象

cursor.execute("SELECT VERSION()")    # 执行 SQL 语句,查询版本
data = cursor.fetchone()    # 获取单条数据
print("Datebase version:%s" % data)    # 输出结果:Datebase version:5.7.21-log

db.close()    # 关闭数据库连接
创建数据表

创建 books 表,books 表包含 id(主键)、name(图书名)、category(图书分类)、price(图书价格)和 publish_time(出版时间),example:

import pymysql

db = pymysql.connect("localhost", "root", "root", "mrsoft")
cursor = db.cursor()

cursor.execute("DROP TABLE IF EXISTS books")
sql = """
CREATE TABLE books (id int(8) NOT NULL AUTO_INCREMENT, 
                    name varchar(50) NOT NULL, 
                    category varchar(50) NOT NULL, 
                    price decimal(10, 2) DEFAULT NULL, 
                    pubilsh_tiem date DEFAULT NULL, 
                    PRIMARY KEY (id)
                    ) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf-8;
"""
cursor.execute(sql)

db.close()
操作 MySQL 数据表

添加数据,可以使用 excute() 方法添加,也可以使用 execuemany() 方法批量添加多条数据,语法格式:

executemany(operation, seq_of_params)

  • operation:操作的 SQL 语句
  • seq_of_params:参数序列

example:

import pymysql

db = pymysql.connect("localhost", "root", "root", "mrsoft", charset="utf-8")
cursor = db.cursor()

cursor.execute("DROP TABLE IF EXISTS books")
data = [("test01", "des", "10.2", "2022-11-17"),
        ("test02", "des", "12.2", "2022-11-17"),
        ("test03", "game", "13.2", "2022-11-17")
        ]    # 数据列表
try:
    cursor.executemany("insert into books(name, category, price, publish_time) values (%s, %s, %s, %s)", data)    # 执行 SQL 语句,添加多条数据
    db.commit()    # 提交数据
except:
    db.rollback()    # 如果发生错误,进行回滚

db.close()

GUI

GUI 是 Graphical User Interface(图像用户界面)的缩写。在 GUI 中并不只是键入文本和返回文本,用户可以看到窗口、按钮、文本框等图形,而且可以用鼠标单击,还可以通过键盘输入。GUI 是与程序交互的一种方式。GUI 的程序有 3 个基本要素:输入、处理和输出

常用的 GUI 框架

工具包描述
wxPythonwxPython 是 Python 语言的一套优秀的 GUI 图形库,支持跨平台。提供了丰富的控件,可用于开发复杂的图形用户界面
KivyKivy 是一个开源工具包能够让使用相同源代码创建的程序能跨平台运行。主要关注创新型用户界面开发,如:多点触摸应用程序
FlexxFlexx 是一个纯 Python 工具包,用来创建图形化界面应用程序,可使用 Web 技术进行界面的渲染
PyQtPyQt 是 Qt 库的 Python 版本,支持跨平台。若使用 PyQt 工具包,需要额外安装软件包
TkinterTkinter(也叫 Tk 接口)是 Python 官方提供的图形用户界面开发库,用于封装 TK GUI 工具包,跨平台。但 Tkinter 工具包包含的控件少,帮助文档不健全
Pywin32Windows Pywin32 允许您像 VC 一样的形式来使用 Python 开发 win32 应用
PyGTKPyGTK 让您用 Python 轻松创建具有图形用户界面的程序
pyui4winPyui4win 是一个开源的采用自绘技术的界面库

安装 wxPython

wxPython 的官方网址是:https://wxPython.org

使用 pip 命令安装 wxPython 库:pip install wxPython

创建应用程序

在使用 wxPython 之前,先了解两个基础对象:

  • 应用程序对象:应用管理主事件循环,主事件循环是一种事件或消息分发处理机制,大部分图形用户界面在界面中的显示及响应用户事件的处理都是通过主事件循环实现的
  • 顶级窗口:通常用于管理最重要的数据,控制并呈现给用户

这两个基础对象和应用程序的其它部分之间的关系:

应用程序对象 --(设置窗体属性)–> 顶级窗口 --(父级、子级关系)–> 窗体组件 --(在组件中触发事件)–> 主循环事件 --(给事件处理器发送事件)–> 顶级窗口
应用程序对象 --(由 APP 对象触动)–> 主循环事件

这个应用程序对象拥有顶级窗口和主循环事件。顶级窗口管理其窗口中的组件和其它的分配给它的数据对象属性。窗口和它的组件触发的事件基于用户的动作,并接受事件通知以便改变显示

创建一个 wx.App 的子类

创建一个没有任何功能的子类,example:

import wx


class App(wx.App):
    def OnInit(self):    # 初始化方法
        frame = wx.Frame(parent=None, title="Hello wyPython")    # 创建窗口对象
        frame.Show()    # 显示窗口,窗口默认隐藏,需要调用 Show() 方法显示
        return True    # 返回值


if __name__ == '__main__':
    app = App()    # App 类的实例用于创建应用程序对象
    app.MainLoop()    # 调用 App 类的 MainLoop() 主循环方法,让应用程序进入主事件循环中

直接创建 wx.App,example:

import wx

app = wx.App()    # 创建应用程序对象
frame = wx.Frame(parent=None, title="第一个 wxPython 程序!", size=(400, 300), pos=(100, 100))    # 创建窗口对象
frame.Show()    # 显示窗口,窗口默认隐藏,需要调用 Show() 方法显示
app.MainLoop()    # 进入主事件循环,让应用程序进入主事件循环中
使用 wx.Frame 框架

在 GUI 中框架通常称为窗口。框架是一个容器,用户可以将它在屏幕上任意移动,并对它进行缩放,通常包含标题栏、菜单等

在 wxPython 中,wx.Frame 是所有框架的父类,当年创建 wx.Frame 的子类时,子类应该调用其父类的构造器 wx.Frame.__init__()

wx.Frame 构造器的语法格式:

wx.Frame(parent, id=-1, title="", pos=ws.DefaultPosition, size=ws.DefaultSize, style=wx.DEFAULT_FRAME_STLE, name="frame")

  • parent:框架的父窗口,如果是顶级窗口,这个值是 None
  • id:关于新窗口的 wxPython ID 号。通常设为 -1,让 wxPython 自动生成一个新的 ID
  • title:窗口的标题
  • pos:一个 wx.Point 对象,指定这个窗口的左上角在屏幕中的位置。在图形用户界面程序中,通常 (0, 0) 是显示器的左上角,默认值 (-1, -1) 将让系统决定窗口的位置
  • size:一个 wx.size 对象,指定这个窗口的初始尺寸。默认值是 (-1, -1) 将让系统决定窗口的初始尺寸
  • style:指定窗口的类型常量。可以使用或运算来组合它们
  • name:框架内在的名字。可以使用 name 来寻找这个窗口

自定义窗口类,example:

import wx


class MyFrame(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, title="myframe", pos=(100, 100), size=(300, 300))


if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame(parent=None, id=-1)
    frame.Show()
    app.MainLoop()

常用控件

创建完窗口以后,可以在窗口内添加一些控件。控件就是经常使用的按钮、文本、输入框、单选框等

wx.StaticText 文本类

通过使用 wx.StaticText 类来完成在屏幕上绘制纯文本,使用 wx.StaticText 能够改变文本的对齐方式、字体和颜色等,语法格式:

wx.StaticText(parent, id, label, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, name="staticText")

  • parent:父窗口部件
  • id:标识符。使用 -1 可以自动创建一个唯一的标识
  • label:显示在静态控件中的文本内容
  • pos:一个 wx.Point 或一个 Python 元组,它是窗口部件的位置
  • size:一个 wx.size 或一个 Python 元组,它的窗口部件的尺寸
  • style:样式标记
  • name:对象的名字

使用 panel = wx.Panel(self) 来创建画板,并将 panel 作为父类,然后将组件放入窗体中,使用 wx.Font 类设置字体,语法格式:

wx.Font(pointSize, family, style, weight, underline=False, faceName="", encoding=wx.FONTENCODING_DEFAULT)

  • pointSize:字体的整体尺寸,单位为磅
  • family:用于快速指定一个字体而不需要知道该字体实际的名字
  • style:指明字体是否倾斜
  • weight:指明字体的醒目程度
  • underline:仅在 Windows 下有效,如果值为 True 则加下划线;如果值为 False 则无下划线
  • faceName:指定字体名
  • encoding:允许在几个编码中选择一个,大多数情况可以使用默认编码

example:

import wx


class MyFrame(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, title="my StaticText class", pos=(100, 100), size=(600, 400))
        panel = wx.Panel(parent=self)    # 创建画板

        title = wx.StaticText(parent=panel, label="江雪", pos=(60, 20))    # 创建标题,并设置字体
        font = wx.Font(16, wx.DEFAULT, wx.FONTSTYLE_NORMAL, wx.NORMAL)
        title.SetFont(font)
        wx.StaticText(panel, label="唐 柳宗元", pos=(70, 50))
        wx.StaticText(panel, label="千山鸟飞绝", pos=(50, 70))    # 创建文本
        wx.StaticText(panel, label="万径人踪灭", pos=(50, 90))
        wx.StaticText(panel, label="孤舟蓑笠翁", pos=(50, 110))
        wx.StaticText(panel, label="独钓寒江雪", pos=(50, 130))


if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame(parent=None, id=-1)
    frame.Show()
    app.MainLoop()
TextCtrl 输入文本类

wx.StaticText 类只能够用于显示纯碎的静态文本,使用 wx.TextCtrl 类可以与用户进行交互,允许输入单行或多行文本。也可以作为密码输入控件,掩饰所按下的按键

wx.TextCtrl 语法格式:

wx.TextCtrl(parent, id, value = "", pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, validator=wx.DefaultValidator name=wx.TextCtrNameStr)

  • parent:父窗口部件
  • id:标识符。使用 -1 可以自动创建一个唯一的标识
  • value:显示在该控件中的初始文本
  • pos:一个 wx.Point 或一个 Python 元组,它是窗口部件的位置
  • size:一个 wx.size 或一个 Python 元组,它的窗口部件的尺寸
  • style:单行 wx.TextCtrl 的样式,取值如下:
    • wx.TE_CENTER:控件中的文本居中
    • wx.TE_LEFT:控件中的文本左对齐
    • wx.TE_NOHIDESEL:文本始终高亮显示,只适用与 Windows
    • wx.TE_PASSWORD:不显示所键入的文本,以星号 “*” 代替显示
    • wx.TE_PROCESS_ENTER:如果使用该参数,那么当用户在控件内按下 Enter 键时,一个文本输入事件将被触发,否则按键事件由该文本控件或该对话框管理
    • wx.TE_PROCESS_TAB:如果指定该样式,那么通常的字符事件在按下 Tab 键时,创建(一般意味一个制表符将被插入文本),否则 tab 由对话框管理,通常是控件间的切换
    • wx.TE_READONLY:文本控件为只读,用户不能修改其中的文本
    • wx.TE_RIGHT:控件中的文本右对齐
  • validator:常用于过滤数据以确保只能键入要接受的数据
  • name:框架内在的名字。可以使用 name 来寻找这个窗口

实现一个包含用户名和密码的登录界面,example:

import wx


class MyFrame(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, title="My TextCtrl class", size=(400, 300))

        panel = wx.Panel(self)
        self.title = wx.StaticText(panel, label="请输入用户名和密码", pos=(140, 20))
        self.label_user = wx.StaticText(panel, label="用户名:", pos=(50, 50))
        self.text_user = wx.TextCtrl(panel, pos=(100, 50), size=(235, 25), style=wx.TE_LEFT)
        self.label_passwd = wx.StaticText(panel, label="密  码:", pos=(50, 90))
        self.text_passwd = wx.TextCtrl(panel, pos=(100, 90), size=(235, 25), style=wx.TE_PASSWORD)


if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame(parent=None, id=-1)
    frame.Show()
    app.MainLoop()
Button 按钮类

按钮是 GUI 界面中应用最为广泛的控件,常用于捕获用户生成的单击事件,最明显的用途是触发绑定到一个处理函数

wxPython 中最常用的按钮类型是 wx.Button 类,语法格式:

wx.Button(parent, id, label, pos, size=wxDefaultSize, style=0, validator, name="button")

  • parent:父窗口部件
  • id:标识符。使用 -1 可以自动创建一个唯一的标识
  • label:显示在静态控件中的文本内容
  • pos:一个 wx.Point 或一个 Python 元组,它是窗口部件的位置
  • size:一个 wx.size 或一个 Python 元组,它的窗口部件的尺寸
  • style:单行 wx.TextCtrl 的样式,取值如下:
    • wx.TE_CENTER:控件中的文本居中
    • wx.TE_LEFT:控件中的文本左对齐
    • wx.TE_NOHIDESEL:文本始终高亮显示,只适用与 Windows
    • wx.TE_MULTILINE:控件中的文本可以换行,是多行文本
    • wx.TE_PASSWORD:不显示所键入的文本,以星号 “*” 代替显示
    • wx.TE_PROCESS_ENTER:如果使用该参数,那么当用户在控件内按下 Enter 键时,一个文本输入事件将被触发,否则按键事件由该文本控件或该对话框管理
    • wx.TE_PROCESS_TAB:如果指定该样式,那么通常的字符事件在按下 Tab 键时,创建(一般意味一个制表符将被插入文本),否则 tab 由对话框管理,通常是控件间的切换
    • wx.TE_READONLY:文本控件为只读,用户不能修改其中的文本
    • wx.TE_RIGHT:控件中的文本右对齐
  • validator:常用于过滤数据以确保只能键入要接受的数据
  • name:框架内在的名字。可以使用 name 来寻找这个窗口

为登录界面添加 “确认” 和 “取消” 按钮,example:

import wx


class MyFrame(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, title="My TextCtrl class", size=(400, 300))

        panel = wx.Panel(self)
        self.title = wx.StaticText(panel, label="请输入用户名和密码", pos=(140, 20))
        self.label_user = wx.StaticText(panel, label="用户名:", pos=(50, 50))
        self.text_user = wx.TextCtrl(panel, pos=(100, 50), size=(235, 25), style=wx.TE_LEFT)
        self.label_passwd = wx.StaticText(panel, label="密  码:", pos=(50, 90))
        self.text_passwd = wx.TextCtrl(panel, pos=(100, 90), size=(235, 25), style=wx.TE_PASSWORD)

        self.bt_confirm = wx.Button(panel, label="确认", pos=(105, 130))
        self.bt_cancel = wx.Button(panel, label="取消", pos=(195, 130))


if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame(parent=None, id=-1)
    frame.Show()
    app.MainLoop()

BoxSizer 布局

控件的几何位置是绝对位置,也就是固定的,当调整窗口大小时,界面会不美观。在 wxPython 中有一种更智能的布局方式——sizer(尺寸器)。sizer 是用于自动布局一组窗口控件的算法。sizer 被附加到一个容器,通常是一个框架或面板。在父容器中创建的子窗口控件必须被分别添加到 sizer。当 sizer 被附加到容器时,它随后就可以管理它所包含的子布局

wxPython 提供的 sizer:

sizer 名称描述
BoxSizer在一条水平或垂直线上的窗口部件的布局。当尺寸改变时,控制窗口部件的行为上很灵活。通常用于嵌套的样式,可用于几乎任何类型的布局
GridSizer一个十分基础的网络布局。当您要设置的窗口部件都是同样的尺寸且整齐地放入一个规则的网格中可以使用
FlexGridSizer对 GridSizer 稍微做了些改变,当窗口部件有不同的尺寸时,可以有更好的结果
GridBagSizerGridSizer 系列中最灵活的成员。使网格中的窗口部件可以随意放置
StaticBoxSizer一个标准的 BoxSizer,带有标题和环线
什么是 BoxSizer

BoxSizer 是一个垂直列或水平行,窗口部件在其中从左至右或从上到下布置在一条线上。sizer 相互之间嵌套可以使您能够在每行或每列很容易放置不同数量的项目。由于每个 sizer 都是一个独立的实体,因此您的布局就有了更多的灵活性,对于大多数的应用程序,一个嵌套水平 sizer 的垂直 sizer 将使您能够创建您所需要的布局

使用 BoxSizer 布局

尺寸器会管理组件的尺寸,只要将部件添加到尺寸器上,再加上一些布局参数,就可以让尺寸器自己去管理父组件的尺寸,example:

import wx


class MyFrame(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, title="用户登录", size=(400, 300))

        panel = wx.Panel(self)
        self.title = wx.StaticText(panel, label="请输入用户名和密码", pos=(140, 20))

        vsizer = wx.BoxSizer(wx.VERTICAL)
        vsizer.Add(self.title, proportion=0, flag=wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER, border=15)
        panel.SetSizer(vsizer)    # 设定尺寸器


if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame(parent=None, id=-1)
    frame.Show()
    app.MainLoop()

Add() 方法添加子窗口(或控件)到父窗口,语法格式:

Box.Add(control, proportion, flag, border)

  • control:要添加的控件
  • proportion:所添加控件在定义的定位方式所代表方向上占据的空间比例。如果有三个按钮,它们的比例值分别为 0、1、2,它们都已经添加到一个宽度为 30 的水平排列 wx.BoxSizer,起始宽度都是 10。当 sizer 的宽度从 30 变成 60 时,按钮 1 的宽度保持不变,仍然是 10;按钮 2 的宽度约为 (10 + (60 - 30) * 1 / (1 + 2)) = 30,按钮 2 约为 20
  • flag:用于控制对齐方式、边框和调整尺寸,flag 参数与 border 参数结合使用可以指定边框边距宽度,包括以下选项:
    • wx.LEFT:左边距
    • wx.RIGHT:右边距
    • wx.BOTTOM:底边距
    • wx.TOP:上边距
    • wx.ALL:上下左右 4 个边距
      通过竖线 “|” 操作符,来联合使用这些标志,比如:wx.LEFT | wx.BOTTOM。此外,flag 参数还可以与 proportion 参数结合,指定控件本身的对齐(排列)方式,包括以下选项:
    • wx.ALIGN_LEFT:左边对齐
    • wx.ALIGN_RIGHT:右边对齐
    • wx.ALIGN_TOP:顶部对齐
    • wx.ALIGN_BOTTOM:底边对齐
    • wx.ALIGN_CENTER_VERTICAL:垂直居中对齐
    • wx.ALIGN_CENTER_HORIZONTAL:水平居中对齐
    • wx.ALIGN_CENTER:居中对齐
      调整尺寸,选项如下:
    • wx.EXPAND:所添加控件将占有 sizer 定位方向上所有可用的空间
    • wx.SHAPED:调整子窗口(或控件)填充有效空间,但保存高度比
    • wx.FIXED_MINSIZE:调整子窗口(或控件)为最小尺寸
    • wx.RESERVE_SPACE_EVEN_IF_HIDDEN:设置此标志后,子窗口(或控件)如果被隐藏,则所占空间保留
  • border:用于设置边框的宽度,控制所添加控件的边距,就是在部件之间添加一些像素的空白

首先设置了增加背景控件(wx.Panel),并创建了一个 wx.BoxSizer,它带有一个决定其是水平还是垂直的参数(wx.HORIZONTAL 或 wx.VERTICAL)默认为水平,然后使用 Add() 方法将控件加入 sizer,最后使用面板的 SetSizer() 方法设定它的尺寸器

example:

import wx


class MyFrame(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, title="用户登录", size=(400, 300))

        panel = wx.Panel(self)    # 创建面板

        self.title = wx.StaticText(panel, label="请输入用户名和密码", pos=(140, 20))    # 创建文本,左对齐
        self.label_user = wx.StaticText(panel, label="用户名:", pos=(50, 50))
        self.text_user = wx.TextCtrl(panel, pos=(100, 50), size=(235, 25), style=wx.TE_LEFT)
        self.label_passwd = wx.StaticText(panel, label="密  码:", pos=(50, 90))
        self.text_passwd = wx.TextCtrl(panel, pos=(100, 90), size=(235, 25), style=wx.TE_PASSWORD)

        self.bt_confirm = wx.Button(panel, label="确认", pos=(105, 130))    # 创建 “确定” 和 “取消” 按钮
        self.bt_cancel = wx.Button(panel, label="取消", pos=(195, 130))

        hsizer_user = wx.BoxSizer(wx.HORIZONTAL)    # 添加容器,容器中控件横向排列
        hsizer_user.Add(self.label_user, proportion=0, flag=wx.ALL, border=5)
        hsizer_user.Add(self.text_user, proportion=1, flag=wx.ALL, border=5)
        hsizer_passwd = wx.BoxSizer(wx.HORIZONTAL)
        hsizer_passwd.Add(self.label_passwd, proportion=0, flag=wx.ALL, border=5)
        hsizer_passwd.Add(self.text_passwd, proportion=1, flag=wx.ALL, border=5)
        hsizer_button = wx.BoxSizer(wx.HORIZONTAL)
        hsizer_button.Add(self.bt_confirm, proportion=0, flag=wx.ALIGN_CENTER, border=5)
        hsizer_button.Add(self.bt_cancel, proportion=0, flag=wx.ALIGN_CENTER, border=5)

        vsizer_all = wx.BoxSizer(wx.VERTICAL)    # 添加容器,容器中控件纵向排列
        vsizer_all.Add(self.title, proportion=0, flag=wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER, border=15)
        vsizer_all.Add(hsizer_user, proportion=0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=45)
        vsizer_all.Add(hsizer_passwd, proportion=0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=45)
        vsizer_all.Add(hsizer_button, proportion=0, flag=wx.ALIGN_CENTER | wx.TOP, border=15)

        panel.SetSizer(vsizer_all)    # 设定尺寸器


if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame(parent=None, id=-1)
    frame.Show()
    app.MainLoop()

事件处理

当单击 “确定” 按钮时,验证输入的用户名和密码是否正确,并输出相应的提示信息;当点击 “取消” 按钮时,清空已经输入的用户名和密码。如果要实现这种功能,就需要使用 wxPython 的事件处理

用户执行的动作就叫作事件(event),如:单击按钮,就是一个单击事件

绑定事件

通过使用 Bind() 方法可以将事件处理函数绑定到给定的事件上,当发送一个事件时,可以让程序注意这些事件并且做出反应,语法格式:

bt_confirm.Bind(wx.EVT_BUTTON, OnclickSubmit)

  • bt_confirm.Bind:需要绑定的事件
  • wx.EVT_BUTTON:事件类型为按钮类型。在 wxPython 中有很多 wx.EVT_ 开头的事件类型,如:
    • wx.EVT_MOTION:产生于用户移动鼠标
    • wx.ENTER_WINDOW:产生于当用户鼠标进入一个窗口控件
    • wx.LEAVE_WINDOW:产生于当用户鼠标离开一个窗口控件
    • wx.EVT_MOUSEWHEEL:被绑定到鼠标滚轮的活动
  • OnclickSubmit:方法名。事件发送时执行该方法

当用户输入用户名和密码后,单击 “确定” 按钮,如果输入的用户名为 “zhangsan” 并且密码为 “123”,则弹出对话框提示 “登录成功”,否则提示 “用户名和密码不匹配”;当用户点击 “取消” 按钮时,清空用户输入的用户名和密码,example:

import wx


class MyFrame(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, title="用户登录", size=(400, 300))

        panel = wx.Panel(self)  # 创建面板

        self.title = wx.StaticText(panel, label="请输入用户名和密码")  # 创建文本,左对齐
        self.label_user = wx.StaticText(panel, label="用户名:")
        self.text_user = wx.TextCtrl(panel, style=wx.TE_LEFT)
        self.label_passwd = wx.StaticText(panel, label="密  码:")
        self.text_passwd = wx.TextCtrl(panel, style=wx.TE_PASSWORD)

        self.bt_confirm = wx.Button(panel, label="确认")  # 创建 “确定” 和 “取消” 按钮,并绑定事件
        self.bt_confirm.Bind(wx.EVT_BUTTON, self.OnclickSubmit)
        self.bt_cancel = wx.Button(panel, label="取消")
        self.bt_cancel.Bind(wx.EVT_BUTTON, self.OnclickCancel)

        hsizer_user = wx.BoxSizer(wx.HORIZONTAL)  # 添加容器,容器中控件横向排列
        hsizer_user.Add(self.label_user, proportion=0, flag=wx.ALL, border=5)
        hsizer_user.Add(self.text_user, proportion=1, flag=wx.ALL, border=5)
        hsizer_passwd = wx.BoxSizer(wx.HORIZONTAL)
        hsizer_passwd.Add(self.label_passwd, proportion=0, flag=wx.ALL, border=5)
        hsizer_passwd.Add(self.text_passwd, proportion=1, flag=wx.ALL, border=5)
        hsizer_button = wx.BoxSizer(wx.HORIZONTAL)
        hsizer_button.Add(self.bt_confirm, proportion=0, flag=wx.ALIGN_CENTER, border=5)
        hsizer_button.Add(self.bt_cancel, proportion=0, flag=wx.ALIGN_CENTER, border=5)

        vsizer_all = wx.BoxSizer(wx.VERTICAL)  # 添加容器,容器中控件纵向排列
        vsizer_all.Add(self.title, proportion=0, flag=wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER, border=15)
        vsizer_all.Add(hsizer_user, proportion=0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=45)
        vsizer_all.Add(hsizer_passwd, proportion=0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=45)
        vsizer_all.Add(hsizer_button, proportion=0, flag=wx.ALIGN_CENTER | wx.TOP, border=15)

        panel.SetSizer(vsizer_all)

    def OnclickSubmit(self, event):
        """
        单击 “确定按钮”,执行方法
        :param self:
        :param event:
        :return:
        """
        message = ""
        user = self.text_user.GetValue()    # 获取输入的用户名
        passwd = self.text_passwd.GetValue()    # 获取输入的密码

        if user == "" or passwd == "":
            message = "用户名或密码不能为空!"
        elif user == "zhangsan" and passwd == "123":
            message = "登录成功!"
        else:
            message = "用户名和密码不匹配!"
        wx.MessageBox(message)    # 弹出提示框

    def OnclickCancel(self, event):
        """
        单击 ”取消“ 按钮,执行方法
        :param event:
        :return:
        """
        self.text_user.SetValue("")    # 清空输入的用户名
        self.text_passwd.SetValue("")    # 清空输入的密码


if __name__ == '__main__':
    app = wx.App()    # 初始化应用
    frame = MyFrame(parent=None, id=-1)    # 实例 MyFrame 类,并传递参数
    frame.Show()    # 显示窗口
    app.MainLoop()    # 调用主循环方法
复选框和单选按钮

多选控件是复选框(wx.CheckBox),复选框有时也能单独使用,能提供两种状态的开和关

单选控件是单选按钮(wx.RadioButton),同一组的多个单选按钮应该具有互斥性,就是当一个按钮按下时,其它按钮一定释放

example:

import wx


class MyFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title="复选框和单选按钮", size=(400, 300))

        panel = wx.Panel(self)
        motion = wx.StaticText(panel, label="选择您喜欢的运动:")
        run = wx.CheckBox(panel, id=1, label="跑步")    # 创建单选框按钮
        swimming = wx.CheckBox(panel, id=2, label="游泳")
        mountain_climbing = wx.CheckBox(panel, id=3, label="爬山")
        swimming.SetValue(True)    # 设置 swimming 为初始状态选中
        self.Bind(wx.EVT_CHECKBOX, self.on_motion_click, id=1, id2=3)    # 绑定 id 为 1~3 的所有控件的事件处理到 on_motion_click() 方法

        gender = wx.StaticText(panel, label="选择性别:")
        man = wx.RadioButton(panel, id=4, label="男", style=wx.RB_GROUP)    # 创建单选框按钮,设置 style=wx.RB_GROUP 的单选按钮,说明是一个组开始,直到遇到另外设置 style=wx.RB_GROUP 的 wx.RadioButto 单选按钮为止都是同一个组。所以 man 和 woman 是同一个组,即这两个单选按钮是互斥的
        woman = wx.RadioButton(panel, id=5, label="女")
        self.Bind(wx.EVT_RADIOBUTTON, self.on_gender_click, id=4, id2=5)    # 绑定 id 为 4~5 的控件的事件处理到 on_gender_click() 方法

        hbox_motion = wx.BoxSizer()
        hbox_motion.Add(motion, flag=wx.LEFT | wx.RIGHT, border=5)
        hbox_motion.Add(run)
        hbox_motion.Add(swimming)
        hbox_motion.Add(mountain_climbing)

        hbox_gender = wx.BoxSizer()
        hbox_gender.Add(gender, flag=wx.LEFT | wx.RIGHT, border=5)
        hbox_gender.Add(man)
        hbox_gender.Add(woman)

        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(hbox_motion, flag=wx.ALL, border=10)
        vbox.Add(hbox_gender, flag=wx.ALL, border=10)

        panel.SetSizer(vbox)

    def on_motion_click(self, event):
        """
        选择喜欢的运动,执行方法
        :param event:
        :return:
        """
        motion = event.GetEventObject()
        print("选择 {0},状态 {1}".format(motion.GetLabel(), event.IsChecked()))    # 从事件对象中取出事件源对象(复选框)。GetLabel 获得复选框标签;IsChecked 获得复选状态

    def on_gender_click(self, event):
        """
        选择性别,执行方法
        :param event:
        :return:
        """
        gender = event.GetEventObject()
        print("第一组 {0} 被选中".format(gender.GetLabel()))    # 从事件对象中取出事件源对象(单选框)。GetLabel 获得单选框标签


if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    frame.Show()
    app.MainLoop()
列表

对列表控件可以进行单选或多选,列表控件类是 wx.ListBox,语法格式:

wx.ListBox(parent, choices, style)

  • parent:父窗口部件
  • choices:指定选项列表
  • style:选择列表控件类型
    • wx.LB_SINGLE:单选
    • wx.LB_MULTIPLE:多选
    • wx.LB_EXTENDED:多选,但是需要在按住 Ctrl 或 Shift 键时选择项目
    • wx.LB_SORT:对列表选择项进行排序

example:

import wx


class MyFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title="选择列表", size=(400, 300))

        panel = wx.Panel(self)
        motion = wx.StaticText(panel, label="选择您喜欢的运动:")
        motion_list = ["跑步", "游泳", "爬山"]
        motion_box = wx.ListBox(panel, choices=motion_list, style=wx.LB_SINGLE)    # 创建列表控件。choices 用于设置列表选项;style 用于设置列表风格样式 wx.LB_SINGLE 指单选列表控件
        self.Bind(wx.EVT_LISTBOX, self.on_motion_box, motion_box)    # 绑定列表选择事件 wx.EVT_LISTBOX 到 self.on_motion_box() 方法

        fruits = wx.StaticText(panel, label="选择您喜欢的水果:")
        fruits_list = ["苹果", "香蕉", "橘子"]
        fruits_box = wx.ListBox(panel, choices=fruits_list, style=wx.LB_EXTENDED)    # 创建列表控件。wx.LB_EXTENDED 指多选列表控件
        self.Bind(wx.EVT_LISTBOX, self.on_fruits_box, fruits_box)

        hbox_motion = wx.BoxSizer()
        hbox_motion.Add(motion, proportion=1, flag=wx.LEFT | wx.RIGHT, border=5)
        hbox_motion.Add(motion_box, proportion=1)

        hbox_fruits = wx.BoxSizer()
        hbox_fruits.Add(fruits, proportion=1, flag=wx.LEFT | wx.RIGHT, border=5)
        hbox_fruits.Add(fruits_box, proportion=1)

        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(hbox_motion, flag=wx.ALL | wx.EXPAND, border=5)
        vbox.Add(hbox_fruits, flag=wx.ALL | wx.EXPAND, border=5)

        panel.SetSizer(vbox)

    def on_motion_box(self, event):
        """
        选择喜欢运动,执行的方法
        :param event:
        :return:
        """
        motion = event.GetEventObject()
        print("选择 {0}".format(motion.GetSelection()))    # 返回单个选项的列表索引序号

    def on_fruits_box(self, event):
        """
        选择喜欢水果,执行的方法
        :param event:
        :return:
        """
        fruits = event.GetEventObject()
        print("选择 {0}".format(fruits.GetSelections()))    # 返回多个选项的列表索引序号列表


if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    frame.Show()
    app.MainLoop()
静态图片控件

静态图片控件用于显示一张图片,图片可以是 wx.Python 所支持的任意图片格式,静态图片控件类是 wx.StaticBitmap

可以使用 self.panel.Layout() 语句重新设置 panel 面板,在图片替换后,需要重写绘制窗口,否则布局会发生混乱

example:

import wx


class MyFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title="静态图片控件", size=(400, 300))
        self.panel = wx.Panel(parent=self)    # 创建面板,是该类的实例变量

        self.bmps = [wx.Bitmap('images/img13.jpg', wx.BITMAP_TYPE_ANY),
                     wx.Bitmap('images/img14.jpg', wx.BITMAP_TYPE_ANY),
                     wx.Bitmap('images/img15.jpg', wx.BITMAP_TYPE_ANY)
                     ]    # 创建 wx.Bitmap 图片对象的列表

        picture1 = wx.Button(self.panel, id=1, label="Button1")
        picture2 = wx.Button(self.panel, id=2, label="Button2")
        self.Bind(wx.EVT_BUTTON, self.on_picture_click, id=1, id2=2)

        self.image = wx.StaticBitmap(self.panel, bitmap=self.bmps[0])    # 静态图片控件对象,self.bmps[0] 是静态图片控件要显示的图片对象

        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(picture1, proportion=1, flag=wx.EXPAND)
        vbox.Add(picture2, proportion=1, flag=wx.EXPAND)
        vbox.Add(self.image, proportion=3, flag=wx.EXPAND)

        self.panel.SetSizer(vbox)

    def on_picture_click(self, event):
        event_id = event.GetId()
        if event_id == 1:
            self.image.SetBitmap(self.bmps[1])    # 重新设置图片,实现图片切换
        else:
            self.image.SetBitmap(self.bmps[2])
        self.panel.Layout()    # 重新设置 panel 面板布局


if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    frame.Show()
    app.MainLoop()

Pygame 游戏编程

Pygame 是跨平台的 Python 模块,专为电子游戏设计(包含图像、声音),创建在 SDL(Simple DirectMedia Layer)基础上,允许实时电子游戏研发而不被低级语言(如:汇编语言)束缚,基于这一设想,所有需要的游戏功能和理念(主要是图像方面)都完全简化为游戏逻辑本身,所有的资源结构都可以由高级语言(如:python)提供

初始 Pygame

安装 Pygame

Pygame 的官方网址是:https://www.pygame.org,在该网址中可以查找 Pygame 相关文档

Pygame 安装:pip install pygame

Pygame 常用模块

Pygame 做游戏开发的优势在于不需要过多考虑与底层开发相关的内容,而可以把工作重心放在游戏逻辑上。例如:Pygame 中集成了很多和底层开发相关的模块,如:访问显示设备、管理事件、使用字体等

Pygame 常用模块如下:

模块名功能
pygame.cdrom访问光驱
pygame.cursors加载光标
pygame.display访问显示设备
pygame.draw绘制形状、线和点
pygame.event管理事件
pygame.font使用字体
pygame.image加载和存储图片
pygame.joystick使用游戏手柄或者类似的东西
pygame:key读取键盘按键
pygame.mixer声音
pygame.mouse鼠标
pygame.movie播放视频
pygame.music播放音频
pygame.overlay访问高级视频叠加
pygame.rect管理矩形区域
pygame.sndarray操作声音数据
pygame.sprite操作移动图像
pygame.surface管理图像和屏幕
pygame.surfarray管理点阵图像数据
pygame.time管理时间和帧信息
pygame.trasform缩放和移动图像

使用 pygame 的 display 和 event 模块创建一个 Pygame 窗口,example:

import sys
import pygame

pygame.init()    # 初始化 pygame
size = width, height = 320, 240    # 设置窗口
screen = pygame.display.set_mode(size)    # 显示窗口

while True:    # 执行循环,确保窗口一直显示
    for event in pygame.event.get():    # 遍历所有事件
        if event.type == pygame.QUIT:    # 如果单击关闭窗口,则退出
            sys.exit()

pygame.quit()    # 退出 pygame

Pygame 的基本应用

制作一个跳跃的小球游戏

创建一个游戏窗口,然后在窗口内创建一个小球,以一定的速度移动小球,当小球碰到游戏窗口的边缘时,小球弹回,继续移动

example:

(1)创建一个游戏窗口,宽和高设置为 640 * 480

import sys
import pygame

pygame.init()    # 初始化 pygame
size = width, height = 640, 480    # 设置窗口
screen = pygame.display.set_mode(size)    # 显示窗口

上述代码,首先导入 pygame 模块,然后调用 init() 方法初始化 pygame 模块,再设置窗口的宽和高,最后使用 display 模块显示窗体

display 模块的常用方法如下:

方法名功能
pygame.display.init初始化 display 模块
pygame.display.quit结束 display 模块
pygame.display.get_init如果 display 模块已经被初始化,则返回 True
pygame.display.set_mode初始化一个准备显示的界面
pygame.display.get_surface获取当前的 Surface 对象
pygame.display.flip更新整个待显示的 Surface 对象到屏幕上
pygame.display.update更新部分内容显示到屏幕上,如果没有参数,则于 flip 功能相同

(2)运行上述代码后,会出现一个一闪而过的黑色窗口,这是因为程序执行完成,会自动关闭。如果想让窗口一直显示,需要使用 while True 语句让程序一直执行,此外,还需要设置关闭按钮

import sys
import pygame

pygame.init()    # 初始化 pygame
size = width, height = 640, 480    # 设置窗口
screen = pygame.display.set_mode(size)    # 显示窗口

while True:    # 执行循环,确保窗口一直显示
    for event in pygame.event.get():
        if event.type == pygame.QUIT:    # 如果单击关闭窗口,则退出
            sys.exit()

pygame.quit()    # 退出 pygame

上述代码中,添加了轮询事件检测。pygame.event.get() 能够获取事件队列,使用 for···in 遍历事件,然后根据 type 属性判断事件类型。这里的事件处理方法于 GUI 类似,如 event.type == pygame.QUIT 表示检测关闭 pygame 窗口事件,pygame.KEYDOWN 表示键盘按下事件,pygame.MOUSEBUTONDOWN 表示鼠标按下事件等

(3)在窗口中添加小球,先准备好一张 ball.png 图片,然后加载该图片,最后将图片显示在窗口中

import sys
import pygame

pygame.init()    # 初始化 pygame
size = width, height = 640, 480    # 设置窗口
screen = pygame.display.set_mode(size)    # 显示窗口
color = (0, 0, 0)    # 设置颜色

ball = pygame.image.load("ball.png")    # 加载图片
ballrect = ball.get_rect()    # 获取矩形区域

while True:    # 执行循环,确保窗口一直显示
    for event in pygame.event.get():
        if event.type == pygame.QUIT:    # 如果单击关闭窗口,则退出
            sys.exit()
    
    screen.fill(color)    # 填充颜色
    screen.blit(ball, ballrect)    # 将图片画到窗口上
    pygame.display.flip()    # 更新全部显示

pygame.quit()    # 退出 pygame

上述代码中,使用 image 模块的 load() 方法加载图片,返回值 ball 是一个 Surface 对象,Surface 是用来代表图片的 pygame 对象,可以对一个 Surface 对象进行涂画、变形、复制等各种操作。事实上,屏幕也只是一个 surface,pygame.display.set_mode 就返回一个屏幕 Surface 对象。如果将 ball 这个 Surface 对象画到 sureen Surface 对象,需要使用 blit() 方法,最后使用 display 模块的 flip() 方法更新整个待显示的 Surface 对象到屏幕上

Surface 对象的常用方法如下:

方法名功能
pygame.Surface.blit将一个图像画到另一个图像上
pygame.Surface.convert转换图像的像素格式
pygame.Surface.convert_alpha转换图像的像素格式,包含 alpha 通道的转换
pygame.Surface.fill使用颜色填充 Surface
pygame.Surface.get_rect获取 Surface 的矩形区域

(4)让小球动起来。ball.get_rect() 方法返回值 ballrect 是一个 Rect 对象,该对象有一个 move() 方法可以用于移动矩形。move(x, y) 函数有两个参数,第一个参数是 X 轴移动的距离,第二个参数是 Y 轴移动的距离,窗体左上角坐标为 “0,0”。为了实现小球不停移动,将 move() 函数添加到 while 循环内

import sys
import pygame

pygame.init()    # 初始化 pygame
size = width, height = 640, 480    # 设置窗口
screen = pygame.display.set_mode(size)    # 显示窗口
color = (0, 0, 0)    # 设置颜色

ball = pygame.image.load("ball.png")    # 加载图片
ballrect = ball.get_rect()    # 获取矩形区域

speed = [5, 5]    # 设置移动的 X、Y 轴距离

while True:    # 执行循环,确保窗口一直显示
    for event in pygame.event.get():
        if event.type == pygame.QUIT:    # 如果单击关闭窗口,则退出
            sys.exit()

    ballrect = ballrect.move(speed)    # 移动小球
    screen.fill(color)    # 填充颜色
    screen.blit(ball, ballrect)    # 将图片画到窗口上
    pygame.display.flip()    # 更新全部显示

pygame.quit()    # 退出 pygame

(5)运行上述代码,发现小球在屏幕中一闪而过,此时小球并没有真正消失,而是移动到窗体之外,此时需要添加碰撞检测的功能。当小球与窗体任意边缘发送碰撞,则更改小球的移动方向

import sys
import pygame

pygame.init()    # 初始化 pygame
size = width, height = 640, 480    # 设置窗口
screen = pygame.display.set_mode(size)    # 显示窗口
color = (0, 0, 0)    # 设置颜色

ball = pygame.image.load("ball.png")    # 加载图片
ballrect = ball.get_rect()    # 获取矩形区域

speed = [5, 5]    # 设置移动的 X、Y 轴距离

while True:    # 执行循环,确保窗口一直显示
    for event in pygame.event.get():
        if event.type == pygame.QUIT:    # 如果单击关闭窗口,则退出
            sys.exit()

    ballrect = ballrect.move(speed)    # 移动小球

    if ballrect.left < 0 or ballrect.right > width:    # 碰到左右边缘
        speed[0] = -speed[0]
    if ballrect.top < 0 or ballrect.bottom > height:    # 碰到上下边缘
        speed[1] = -speed[1]

    screen.fill(color)    # 填充颜色
    screen.blit(ball, ballrect)    # 将图片画到窗口上
    pygame.display.flip()    # 更新全部显示

pygame.quit()    # 退出 pygame

上述代码中,添加了碰撞检测功能。如果碰到左右边缘,更改 X 轴数据为负数;如果碰到上下边缘,更改 Y 轴数据为负数

(6)运行上述代码发现好像多个小球在飞快移动,这是因为运行代码的时间非常短导致的。这时就需要使用 Pygame 的 time 模块,使用 Pygame 时钟之前,必须先创建 Clock 对象的一个实例,然后在 while 循环中设置多长时间运行一次

import sys
import pygame

pygame.init()    # 初始化 pygame
size = width, height = 640, 480    # 设置窗口
screen = pygame.display.set_mode(size)    # 显示窗口
color = (0, 0, 0)    # 设置颜色

ball = pygame.image.load("ball.png")    # 加载图片
ballrect = ball.get_rect()    # 获取矩形区域

speed = [5, 5]    # 设置移动的 X、Y 轴距离
clock = pygame.time.Clock()    # 设置时钟

while True:    # 执行循环,确保窗口一直显示
    clock.tick(60)    # 每秒执行 60 次

    for event in pygame.event.get():
        if event.type == pygame.QUIT:    # 如果单击关闭窗口,则退出
            sys.exit()

    ballrect = ballrect.move(speed)    # 移动小球

    if ballrect.left < 0 or ballrect.right > width:    # 碰到左右边缘
        speed[0] = -speed[0]
    if ballrect.top < 0 or ballrect.bottom > height:    # 碰到上下边缘
        speed[1] = -speed[1]

    screen.fill(color)    # 填充颜色
    screen.blit(ball, ballrect)    # 将图片画到窗口上
    pygame.display.flip()    # 更新全部显示

pygame.quit()    # 退出 pygame

完成小球的跳跃游戏

开发 Flappy Bird 游戏

游戏简介

Flappy Bird 是一款鸟类飞行游戏,由越南河内独立游戏开发者阮哈东(Dong Nguyen)开发。这款游戏玩家需要用一根手指操作,通过单击触摸屏幕,小鸟就会向上飞;放松手指,则会快速下降。玩家要控制小鸟一直向前飞行,注意躲避途中高低不平的管子。每当小鸟飞过一组管道,玩家获得 1 分;如果小鸟碰到障碍物,则游戏结束

游戏分析

在 Flappy Bird 游戏中,主要有两个对象:小鸟和管道。可以创建 Brid 类和 Pipeline 类来分别表示这两个对象。小鸟可以通过上下移动来躲避管道,所以在 Brid 类中创建一个 birdUpdate() 方法,实现小鸟的上下移动。为了体现小鸟向前飞行的特征,可以让管道一直向左侧移动,所以在 Pipeline 类中也创建一个 updatePipeline() 方法,实现管道的向左移动。此外,还创建了 3 个函数:createMap() 函数用于绘制地图;checkDead() 函数用于判断小鸟的生命状态;getResult() 函数用于获取最终分数。最后在主逻辑中,实例化类并调用相关方法,实现相应功能

搭建主框架

先创建 Flappy Bird 游戏的两个主要对象类(小鸟和管道),然后创建一个绘制地图的函数 createMap(),最后在主逻辑中绘制背景图片

import sys
import random
import pygame


class Bird(object):
    """定义一个鸟类"""
    def __init__(self):
        """定义初始化方法"""
        pass

    def birdUpdate(self):
        """实现小鸟的上下移动"""
        pass


class Pipeline(object):
    """定义一个管道类"""
    def __init__(self):
        """定义初始化方法"""
        pass

    def updatePipeline(self):
        """实现管道的向左移动"""
        pass


def createMap():
    """定义创建地图的方法"""
    screen.fill((255, 255, 255))    # 填充颜色
    screen.blit(background, (0, 0))    # 填入到背景
    pygame.display.update()    # 更新显示


if __name__ == '__main__':
    """主程序"""
    pygame.init()    # 初始化 pygame
    size = width, height = 400, 650    # 设置窗口
    screen = pygame.display.set_mode(size)    # 显示窗口
    clock = pygame.time.Clock()    # 设置时钟

    Pipeline = Pipeline()    # 实例化管道类
    Bird = Bird()    # 实例化鸟类

    while True:    # 执行循环,确保窗口一直显示
        clock.tick(60)    # 每秒执行 60 次

        for event in pygame.event.get():    # 轮询事件
            if event.type == pygame.QUIT:
                sys.exit()

        background = pygame.image.load('backgroud.png')    # 加载图片
        createMap()    # 绘制地图

    pygame.quit()    # 退出 pygame
创建小鸟类

该类需要初始化很多参数,所以定义一个 __init__() 方法,用来初始化各种参数,包括鸟飞行的几种状态、飞行的速度、跳跃的高度等。然后定义 birdUpdate() 方法,该方法用于实现小鸟的跳跃和坠落。然后在主逻辑的轮询事件中添加键盘按下事件或鼠标单击事件,如:按下鼠标,使小鸟上升。最后在 createMap() 方法中,显示小鸟的图像

import sys
import random
import pygame


class Bird(object):
    """定义一个鸟类"""
    def __init__(self):
        """定义初始化方法"""
        self.birdRect = pygame.Rect(65, 50, 50, 50)    # 鸟的矩形
        self.birdStatus = [pygame.image.load('assets/1.png'),
                           pygame.image.load('assets/2.png'),
                           pygame.image.load('assets/3.png')
                           ]    # 定义鸟的三种状态列表
        self.status = 0    # 默认飞行状态
        self.birdX = 120    # 鸟所在的 X 轴坐标
        self.birdY = 350    # 鸟所在的 Y 轴坐标
        self.jump = False    # 默认情况下小鸟自动降落
        self.jumpSpeed = 10    # 跳跃高度
        self.gravity = 5    # 重力
        self.dead = False    # 默认小鸟生命状态为活着

    def birdUpdate(self):
        """实现小鸟的上下移动"""
        if self.jump:    # 小鸟跳跃
            self.jumpSpeed -= 1    # 速度递减,上升越来越慢
            self.birdY -= self.jumpSpeed    # 鸟的 Y 坐标减小,小鸟上升
        else:    # 小鸟坠落
            self.gravity += 0.2    # 重力递增,下降越来越快
            self.birdY += self.gravity    # 鸟的 Y 坐标增加,小鸟下降

        self.birdRect[1] = self.birdY    # 更改 Y 轴位置


class Pipeline(object):
    """定义一个管道类"""
    def __init__(self):
        """定义初始化方法"""
        pass

    def updatePipeline(self):
        """实现管道的向左移动"""
        pass


def createMap():
    """定义创建地图的方法"""
    screen.fill((255, 255, 255))    # 填充颜色
    screen.blit(background, (0, 0))    # 填入到背景

    if Bird.dead:    # 显示小鸟,撞管道状态
        Bird.status = 2
    elif Bird.jump:    # 起飞状态
        Bird.status = 1

    screen.blit(Bird.birdStatus[Bird.status], (Bird.birdX, Bird.birdY))    # 设置小鸟的坐标
    Bird.birdUpdate()    # 鸟移动

    pygame.display.update()    # 更新显示


if __name__ == '__main__':
    """主程序"""
    pygame.init()    # 初始化 pygame
    size = width, height = 400, 650    # 设置窗口
    screen = pygame.display.set_mode(size)    # 显示窗口
    clock = pygame.time.Clock()    # 设置时钟

    Pipeline = Pipeline()    # 实例化管道类
    Bird = Bird()    # 实例化鸟类

    while True:    # 执行循环,确保窗口一直显示
        clock.tick(60)    # 每秒执行 60 次

        for event in pygame.event.get():    # 轮询事件
            if event.type == pygame.QUIT:
                sys.exit()
            if (event.type == pygame.KEYDOWN or event.type == pygame.MOUSEBUTTONDOWN) and not Bird.dead:
                Bird.jump = True    # 跳跃
                Bird.gravity = 5    # 重力
                Bird.jumpSpeed = 10    # 跳跃速度

        background = pygame.image.load('assets/backgroud.png')    # 加载图片
        createMap()    # 绘制地图

    pygame.quit()    # 退出 pygame

上述代码在 Bird 类中设置了 buildStatus 属性,该属性是一个鸟类图片的列表,显示鸟类三种飞行状态,根据小鸟的不同状态加载相应的图片。在 buildUpdate() 方法中,为了达到较好的动画效果,使 jumpSpeed 和 gravity 两个属性逐渐变化

创建管道类

创建管道类。在 __init__() 方法中初始化各种参数,包括设置管道的坐标,加载上下管道图片等。然后在 updatePipline() 方法中,定义管道向左移动的速度,并且当管道移除屏幕时,重新绘制下一组管道。最后在 createMap() 函数中显示管道

import sys
import random
import pygame


class Bird(object):
    """定义一个鸟类"""
    def __init__(self):
        """定义初始化方法"""
        self.birdRect = pygame.Rect(65, 50, 50, 50)    # 鸟的矩形
        self.birdStatus = [pygame.image.load('assets/1.png'),
                           pygame.image.load('assets/2.png'),
                           pygame.image.load('assets/3.png')
                           ]    # 定义鸟的三种状态列表
        self.status = 0    # 默认飞行状态
        self.birdX = 120    # 鸟所在的 X 轴坐标
        self.birdY = 350    # 鸟所在的 Y 轴坐标
        self.jump = False    # 默认情况下小鸟自动降落
        self.jumpSpeed = 10    # 跳跃高度
        self.gravity = 5    # 重力
        self.dead = False    # 默认小鸟生命状态为活着

    def birdUpdate(self):
        """实现小鸟的上下移动"""
        if self.jump:    # 小鸟跳跃
            self.jumpSpeed -= 1    # 速度递减,上升越来越慢
            self.birdY -= self.jumpSpeed    # 鸟的 Y 坐标减小,小鸟上升
        else:    # 小鸟坠落
            self.gravity += 0.2    # 重力递增,下降越来越快
            self.birdY += self.gravity    # 鸟的 Y 坐标增加,小鸟下降

        self.birdRect[1] = self.birdY    # 更改 Y 轴位置


class Pipeline(object):
    """定义一个管道类"""
    def __init__(self):
        """定义初始化方法"""
        self.wallX = 400    # 管道在 X 轴坐标
        self.pineUp = pygame.image.load('assets/top.png')    # 加载上管道图片
        self.pineDown = pygame.image.load('assets/bottom.png')    # 加载下管道图片

    def updatePipeline(self):
        """实现管道的向左移动"""
        self.wallX -= 5    # 管道 X 轴坐标递减,即管道向左移动
        if self.wallX < -80:    # 当管道运行到一定位置,即小鸟飞越管道,分数加 1,并且重置管道
            self.wallX = 400


def createMap():
    """定义创建地图的方法"""
    screen.fill((255, 255, 255))    # 填充颜色
    screen.blit(background, (0, 0))    # 填入到背景

    screen.blit(Pipeline.pineUp, (Pipeline.wallX, 0))    # 显示管道,上管道坐标位置
    screen.blit(Pipeline.pineDown, (Pipeline.wallX, 500))    # 下管道坐标位置
    Pipeline.updatePipeline()    # 管道移动

    if Bird.dead:    # 显示小鸟,撞管道状态
        Bird.status = 2
    elif Bird.jump:    # 起飞状态
        Bird.status = 1

    screen.blit(Bird.birdStatus[Bird.status], (Bird.birdX, Bird.birdY))    # 设置小鸟的坐标
    Bird.birdUpdate()    # 鸟移动

    pygame.display.update()    # 更新显示


if __name__ == '__main__':
    """主程序"""
    pygame.init()    # 初始化 pygame
    size = width, height = 400, 650    # 设置窗口
    screen = pygame.display.set_mode(size)    # 显示窗口
    clock = pygame.time.Clock()    # 设置时钟

    Pipeline = Pipeline()    # 实例化管道类
    Bird = Bird()    # 实例化鸟类

    while True:    # 执行循环,确保窗口一直显示
        clock.tick(60)    # 每秒执行 60 次

        for event in pygame.event.get():    # 轮询事件
            if event.type == pygame.QUIT:
                sys.exit()
            if (event.type == pygame.KEYDOWN or event.type == pygame.MOUSEBUTTONDOWN) and not Bird.dead:
                Bird.jump = True    # 跳跃
                Bird.gravity = 5    # 重力
                Bird.jumpSpeed = 10    # 跳跃速度

        background = pygame.image.load('assets/backgroud.png')    # 加载图片
        createMap()    # 绘制地图
    
    pygame.quit()    # 退出 pygame

上述代码中,在 createMap() 函数内,设置先显示管道,再显示小鸟,这样做的目的是为了当小鸟与管道图像重合时,小鸟的图像显示在上层,而管道的图像显示在底层

计算得分

当小鸟飞过管道时,玩家得分加 1。这里对于飞过管道的逻辑做了简化处理:当管道移动到窗体左侧一定距离后,默认为小鸟飞过管道,使分数加 1,并显示在屏幕上

import sys
import random
from tkinter import font

import pygame


class Bird(object):
    """定义一个鸟类"""
    def __init__(self):
        """定义初始化方法"""
        self.birdRect = pygame.Rect(65, 50, 50, 50)    # 鸟的矩形
        self.birdStatus = [pygame.image.load('assets/1.png'),
                           pygame.image.load('assets/2.png'),
                           pygame.image.load('assets/3.png')
                           ]    # 定义鸟的三种状态列表
        self.status = 0    # 默认飞行状态
        self.birdX = 120    # 鸟所在的 X 轴坐标
        self.birdY = 350    # 鸟所在的 Y 轴坐标
        self.jump = False    # 默认情况下小鸟自动降落
        self.jumpSpeed = 10    # 跳跃高度
        self.gravity = 5    # 重力
        self.dead = False    # 默认小鸟生命状态为活着

    def birdUpdate(self):
        """实现小鸟的上下移动"""
        if self.jump:    # 小鸟跳跃
            self.jumpSpeed -= 1    # 速度递减,上升越来越慢
            self.birdY -= self.jumpSpeed    # 鸟的 Y 坐标减小,小鸟上升
        else:    # 小鸟坠落
            self.gravity += 0.2    # 重力递增,下降越来越快
            self.birdY += self.gravity    # 鸟的 Y 坐标增加,小鸟下降

        self.birdRect[1] = self.birdY    # 更改 Y 轴位置


class Pipeline(object):
    """定义一个管道类"""
    def __init__(self):
        """定义初始化方法"""
        self.wallX = 400    # 管道在 X 轴坐标
        self.pineUp = pygame.image.load('assets/top.png')    # 加载上管道图片
        self.pineDown = pygame.image.load('assets/bottom.png')    # 加载下管道图片

    def updatePipeline(self):
        """实现管道的向左移动"""
        self.wallX -= 5    # 管道 X 轴坐标递减,即管道向左移动
        if self.wallX < -80:    # 当管道运行到一定位置,即小鸟飞越管道,分数加 1,并且重置管道
            global score
            score += 1
            self.wallX = 400


def createMap():
    """定义创建地图的方法"""
    screen.fill((255, 255, 255))    # 填充颜色
    screen.blit(background, (0, 0))    # 填入到背景

    screen.blit(Pipeline.pineUp, (Pipeline.wallX, 0))    # 显示管道,上管道坐标位置
    screen.blit(Pipeline.pineDown, (Pipeline.wallX, 500))    # 下管道坐标位置
    Pipeline.updatePipeline()    # 管道移动

    if Bird.dead:    # 显示小鸟,撞管道状态
        Bird.status = 2
    elif Bird.jump:    # 起飞状态
        Bird.status = 1

    screen.blit(Bird.birdStatus[Bird.status], (Bird.birdX, Bird.birdY))    # 设置小鸟的坐标
    Bird.birdUpdate()    # 鸟移动

    screen.blit(font.render('Score' + str(score), -1, (255, 255, 255)), (100, 50))
    pygame.display.update()    # 更新显示


if __name__ == '__main__':
    """主程序"""
    pygame.init()    # 初始化 pygame
    pygame.font.init()    # 初始化字体
    font = pygame.font.SysFont(None, 50)    # 设置默认字体和大小
    size = width, height = 400, 650    # 设置窗口
    screen = pygame.display.set_mode(size)    # 显示窗口
    clock = pygame.time.Clock()    # 设置时钟

    Pipeline = Pipeline()    # 实例化管道类
    Bird = Bird()    # 实例化鸟类
    score = 0    # 初始化分数

    while True:    # 执行循环,确保窗口一直显示
        clock.tick(60)    # 每秒执行 60 次

        for event in pygame.event.get():    # 轮询事件
            if event.type == pygame.QUIT:
                sys.exit()
            if (event.type == pygame.KEYDOWN or event.type == pygame.MOUSEBUTTONDOWN) and not Bird.dead:
                Bird.jump = True    # 跳跃
                Bird.gravity = 5    # 重力
                Bird.jumpSpeed = 10    # 跳跃速度

        background = pygame.image.load('assets/backgroud.png')    # 加载图片
        createMap()    # 绘制地图

    pygame.quit()    # 退出 pygame
碰撞检测

当小鸟与管道碰撞时,小鸟颜色变为灰色,游戏结束,并显示总分数。在 checkDead() 函数中通过 pygame.Rect() 可以分别获取小鸟的矩形区域对象和管道的矩形区域对象,该对象有一个 colliderect() 方法可以判断两个矩形区域是否相撞。如果相撞,设置 Bird.dead 属性为 True。此外,当小鸟飞出窗体时,也设置 Bird.dead 属性为 True。最后用两行文字显示游戏得分

import sys
import random
from tkinter import font
import pygame


class Bird(object):
    """定义一个鸟类"""
    def __init__(self):
        """定义初始化方法"""
        self.birdRect = pygame.Rect(65, 50, 50, 50)    # 鸟的矩形
        self.birdStatus = [pygame.image.load('assets/1.png'),
                           pygame.image.load('assets/2.png'),
                           pygame.image.load('assets/3.png')
                           ]    # 定义鸟的三种状态列表
        self.status = 0    # 默认飞行状态
        self.birdX = 120    # 鸟所在的 X 轴坐标
        self.birdY = 350    # 鸟所在的 Y 轴坐标
        self.jump = False    # 默认情况下小鸟自动降落
        self.jumpSpeed = 10    # 跳跃高度
        self.gravity = 5    # 重力
        self.dead = False    # 默认小鸟生命状态为活着

    def birdUpdate(self):
        """实现小鸟的上下移动"""
        if self.jump:    # 小鸟跳跃
            self.jumpSpeed -= 1    # 速度递减,上升越来越慢
            self.birdY -= self.jumpSpeed    # 鸟的 Y 坐标减小,小鸟上升
        else:    # 小鸟坠落
            self.gravity += 0.2    # 重力递增,下降越来越快
            self.birdY += self.gravity    # 鸟的 Y 坐标增加,小鸟下降

        self.birdRect[1] = self.birdY    # 更改 Y 轴位置


class Pipeline(object):
    """定义一个管道类"""
    def __init__(self):
        """定义初始化方法"""
        self.wallX = 400    # 管道在 X 轴坐标
        self.pineUp = pygame.image.load('assets/top.png')    # 加载上管道图片
        self.pineDown = pygame.image.load('assets/bottom.png')    # 加载下管道图片

    def updatePipeline(self):
        """实现管道的向左移动"""
        self.wallX -= 5    # 管道 X 轴坐标递减,即管道向左移动
        if self.wallX < -80:    # 当管道运行到一定位置,即小鸟飞越管道,分数加 1,并且重置管道
            global score
            score += 1
            self.wallX = 400


def createMap():
    """定义创建地图的方法"""
    screen.fill((255, 255, 255))    # 填充颜色
    screen.blit(background, (0, 0))    # 填入到背景

    screen.blit(Pipeline.pineUp, (Pipeline.wallX, 0))    # 显示管道,上管道坐标位置
    screen.blit(Pipeline.pineDown, (Pipeline.wallX, 500))    # 下管道坐标位置
    Pipeline.updatePipeline()    # 管道移动

    if Bird.dead:    # 显示小鸟,撞管道状态
        Bird.status = 2
    elif Bird.jump:    # 起飞状态
        Bird.status = 1

    screen.blit(Bird.birdStatus[Bird.status], (Bird.birdX, Bird.birdY))    # 设置小鸟的坐标
    Bird.birdUpdate()    # 鸟移动

    screen.blit(font.render('Score' + str(score), -1, (255, 255, 255)), (100, 50))
    pygame.display.update()    # 更新显示


def checkDead():
    upRect = pygame.Rect(Pipeline.wallX, 0,
                         Pipeline.pineUp.get_width() - 10,
                         Pipeline.pineUp.get_height()
                         )    # 上方管子的矩形位置

    downRect = pygame.Rect(Pipeline.wallX, 500,
                           Pipeline.pineDown.get_width() - 10,
                           Pipeline.pineDown.get_height()
                           )    # 下方管子的矩形位置

    if upRect.colliderect(Bird.birdRect) or downRect.colliderect(Bird.birdRect):    # 检测小鸟与上下方管子是否碰撞
        Bird.dead = True
    if not 0 < Bird.birdRect[1] < height:    # 检测小鸟是否飞出上下边界
        Bird.dead = True
        return True
    else:
        return False


def getResutl():
    final_text1 = "Game Over"
    final_text2 = "Your final score is:" + str(score)
    ft1_font = pygame.font.SysFont("Arial", 70)    # 设置第一行文字字体
    ft1_surf = font.render(final_text1, 1, (242, 3, 36))    # 设置第一行文字颜色
    ft2_font = pygame.font.SysFont("Arial", 50)    # 设置第二行文字字体
    ft2_surf = font.render(final_text2, 1, (253, 177, 6))    # 设置第二行文字颜色

    screen.blit(ft1_surf, [screen.get_width()/2 - ft1_surf.get_width()/2, 100])    # 设置第一行文字显示位置
    screen.blit(ft2_surf, [screen.get_width()/2 - ft2_surf.get_width()/2, 200])    # 设置第二行文字显示位置
    pygame.display.flip()    # 更新整个待显示的 Surface 对象到屏幕上


if __name__ == '__main__':
    """主程序"""
    pygame.init()    # 初始化 pygame
    pygame.font.init()    # 初始化字体
    font = pygame.font.SysFont(None, 50)    # 设置默认字体和大小
    size = width, height = 400, 650    # 设置窗口
    screen = pygame.display.set_mode(size)    # 显示窗口
    clock = pygame.time.Clock()    # 设置时钟

    Pipeline = Pipeline()    # 实例化管道类
    Bird = Bird()    # 实例化鸟类
    score = 0    # 初始化分数

    while True:    # 执行循环,确保窗口一直显示
        clock.tick(60)    # 每秒执行 60 次

        for event in pygame.event.get():    # 轮询事件
            if event.type == pygame.QUIT:
                sys.exit()
            if (event.type == pygame.KEYDOWN or event.type == pygame.MOUSEBUTTONDOWN) and not Bird.dead:
                Bird.jump = True    # 跳跃
                Bird.gravity = 5    # 重力
                Bird.jumpSpeed = 10    # 跳跃速度

        background = pygame.image.load('assets/backgroud.png')    # 加载图片
        if checkDead():    # 检测小鸟生命状态
            getResutl()    # 如果小鸟死亡,显示游戏总分数
        else:
            createMap()    # 绘制地图

    pygame.quit()    # 退出 pygame

上述代码的 checkDead() 方法中,upRect.colliderect(Bird.birdRect) 用于检测小鸟的矩形区域是否与上面的管道的矩形区域相撞,colliderent() 函数的参数是另一个矩形区域对象

网络爬虫开发

网络爬虫,可以按照指定的规则(网络爬虫的算法)自动浏览或抓取网络中的信息,通过 Python 可以很轻松地编写爬虫程序或者是脚本

网络爬虫的基本工作流

网络爬虫基本工作流程图:

Created with Raphaël 2.3.0 获取初始 URL 爬取页面获取新的 URL 抽取新的 URL 放入 URL 队列中 读取新的 URL 下载网页 是否满足停止条件 停止 yes no

网络爬虫基本工作流程:

  1. 获取初始的 URL,该 URL 地址是用户自己制定的初始爬取的网页
  2. 爬取对应 URL 地址的网页时,获取新的 URL 地址
  3. 将新的 URL 地址放入 URL 队列中
  4. 从 URL 队列中读取新的 URL,然后依据新的 URL 爬取网页,同时从新的网页中获取新的 URL 地址,重复上述的爬取过程
  5. 设置停止条件,如果没有设置停止条件,爬虫会一直爬取下去,直到无法获取新的 URL 地址为止。设置了停止条件后,爬虫将会满足停止条件时停止爬取

网络爬虫的常用技术

Python 的网络请求

URL 地址与下载网页这两项是网络爬虫必备而又关键的功能,这两个功能必然会提到 HTTP,在 Python 中实现 HTTP 网络请求常见的三种方式:urllib、urllib3 和 requests

  • urllib 模块

    urllib 是 python 自带模块,该模块中提供了一个 urlopen() 方法,通过该方法指定 URL 发送网络请求来获取数据,urlib 子模块如下:

    模块名称功能
    urllib.request该模块定义了打开 URL(主要是 HTTP)的方法和类,例如:身份验证、重定向、cookie 等
    urllib.error该模块主要包含异常类,基本的异常类是 URLError
    urllib.parse该模块定义的功能分为两大类:URL 解析和 URL 引用
    urllib.robotparser该模块用于解析 robots.txt 文件

    通过 urllib.request 模块实现发送请求并读取网页内容的简单实例:

    example:

    import urllib.request
    
    response = urllib.request.urlopen('http://www.baidu.com')    # 打开指定需要爬取的网页
    html = response.read()    # 读取网页代码
    
    print(html)    # 打印读取内容
    

    上述示例中,是通过 get 请求方式获取百度的网页内容。下面通过使用 urllib.request 模块的 post 请求实现获取网页信息的内容

    import urllib.parse
    import urllib.request
    
    data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf-8')  # 将数据使用 urlencoding 编码处理后,再使用 encoding 设置为 utf-8 编码
    response = urllib.request.urlopen('http://httpbin.org/post', data=data)  # 打开指定需要爬取的网页
    html = response.read()  # 读取网页代码
    
    print(html)    # 打印读取内容
    
  • Urllib3 模块

    Urllib3 是一个功能强大,条理清晰的 HTTP 客户端,适用于 Python。Urllib3 提供了很多 Python 标准库里所没有的重要特性:

    • 线程安全
    • 连接池
    • 客户端 SSL/TLS 验证
    • 使用大部分编码上传文件
    • Helper 用于重试请求并处理 HTTP 重定向
    • 支持 gzip 和 deflate 编码
    • 支持 HTTP 和 SOCKS 代理
    • 百分百的测试覆盖率

    通过 Urllib3 模块实现发送网络请求的示例:

    import urllib3
    
    http = urllib3.PoolManager()    # 创建 PoolManager 对象,用于处理与线程池的连接已经线程安全的所以细节
    response = http.request('GET', 'https://www.baidu.com/')    # 对需要爬取的网页发送请求
    
    print(response.data)    # 打印读取内容
    

    post 请求实现获取网页信息的内容:

    response = http.request('POST', 'http://httpbin.org/post', fields={'word': 'hello'})
    
  • requests 模块

    requests 是第三方模块,该模块在实现 HTTP 请求时要比 urllib 模块简化很多,操作更加人性化。

    安装 request 模块:pip install requests

    requests 模块的功能特性如下:

    • Keep-Alive & 连接池
    • 基本/摘要式的身份认证
    • Unicode 响应体
    • 国际化域名和 URL
    • 优雅的 key/value Cookie
    • HTTP(S) 代理支持
    • 带持久 Cookie 的会话
    • 自动解压
    • 文件分块上传
    • 浏览器式的 SSL 认证
    • 流下载
    • 分块请求
    • 自动内容解码
    • 连接超时
    • 支持 .netrc

    以 GET 请求方式为例,打印多种请求信息的示例:

    import requests
    
    response = requests.get('http://www.baidu.com')
    
    print(response.status_code)    # 打印状态
    print(response.url)    # 打印请求 url
    print(response.headers)    # 打印头部信息
    print(response.cookies)    # 打印 cookie 信息
    print(response.text)    # 以文本形式打印网页源码
    print(response.content)    # 以字节流形式大于网页源码
    

    以 POST 请求方式,发送 HTTP 网络请求的示例:

    import requests
    
    data = {'word': 'hello'}    # 表单参数
    response = requests.post('http://httpbin.org/post', data=data)    # 对需要爬取的网页发送请求
    
    print(response.content)    # 以字节流形式打印网页源码
    

    requests 模块不仅提供了以上两种常用的请求方式,还提供以下多种网络请求的方式:

    requests.put('http://httpbin.org/put', data = {'key': 'value'})    # PUT 请求
    requests.delete('http://httpbin.org/delete')    # DELETE 请求
    requests.head('http://httpbin.org/get')    # HEAD 请求
    requests.options('http://httpbin.org/get')    # OPTIONS 请求
    

    如果发现请求的 URL 地址中参数是跟在 “?” 的后面,例如:“httpbin.org/get?key=val”。requests 模块提供了传递参数的方法,运行使用 params 关键字参数,以一个字符串字典来提供这些参数

    import requests
    
    payload = {'key1': 'value1', 'key2': 'value2'}    # 传递参数
    response = requests.get('http://httpbin.org/get', params=payload)    # 对需要爬取的网页发送请求
    
    print(response.context)    # 以字节流形式打印网页源码
    
请求 headers 处理

有时在请求一个网页内容时,发现无论通过 GET 或者是 POST 以及其它请求方式,都会出现 403 错误,这是由于该网页为了防止恶意采集信息而使用了反爬虫设置,从而拒绝了用户的访问。此时可以通过模拟浏览器的头部信息来进行访问,这样就可以解决反爬虫设置的问题,以 requests 模块为例介绍请求头部 headers 的处理

(1)通过浏览器的网络监视器查看头部信息
(2)选中第一条信息(200 GET),右侧的信息头面板中将显示请求头部信息(User-Agent),然后复制该信息
(3)实现代码,首先创建一个需要爬取的 url 地址,然后创建 headers 头部信息,再发送请求等待响应,最后打印网页的代码信息

example:

import requests

url = 'https://www.baidu.com'    # 创建需要爬取的网页地址
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36'}    # 创建头部信息
response = requests.get(url, headers=headers)    # 发送网络请求
print(response.content)    # 以字节流形式打印网页源码
网络超时

在访问一个网页时,如果该网页长时间未响应,系统就会判断该网页超时,所以无法打开网页。模拟一个网络超时的现象

example:

import requests

for a in range(0, 50):    # 循环发送请求 50 次
    try:    # 捕获异常
        response = requests.get('https://www.baidu.com', timeout=0.04)    # 设置超时为 0.04 秒
        print(response.status_code)    # 打印状态码
    except Exception as e:    # 捕获异常
        print("异常:", e)    # 打印异常信息

request 模块同样提供了三种常见的网络异常类,example:

import requests
from requests.exceptions import ReadTimeout, HTTPError, RequestException    # 导入 requests.exceptions 模块中的三种异常类

for a in range(0, 50):    # 循环发送请求 50 次
    try:    # 捕获异常
        response = requests.get('https://www.baidu.com', timeout=0.04)    # 设置超时为 0.04 秒
        print(response.status_code)    # 打印状态码
    except ReadTimeout:    # 捕获超时异常
        print('timeout')
    except HTTPError:    # 捕获 HTTP 异常
        print('httperror')
    except RequestException:    # 捕获请求异常
        print('reqerror')
代理服务

在爬取网址的过程中,经常出现不久前可以爬取的网页现在无法爬取了,这时因为您的 IP 被爬取网址的服务器所屏蔽了。此时可以设置代理服务解决这个问题,首先需要找到代理地址,例如:122.114.31.177,对应的端口号为 808,完整的格式为 122.114.31.177:808

example:

import requests

proxy = {'http': '122.144.31.177:808', 'https': '122.144.31.177:8080'}    # 设置代理 IP 与对应的端口号
response = requests.get('http://www.mingrisoft.com/', proxies=proxy)    # 对需要爬取的网页发送请求
print(response.content)    # 以字节流形式打印网页源码
HTML 解析之 Beautiful Soup

Beautiful Soup 是一个用于从 HTML 和 XML 文件中提取数据的 Python 库。Beautiful Soup 提供一些简单的、函数用来处理导航、搜索、修改分析树等功能。Beautiful Soup 模块中的查找提取功能非常强大,而且非常便捷,通常可以节省程序员大量的工作时间

Beautiful Soup 自动将输入文档转换为 Unicode 编码,输出文档转换为 UTF-8 编码。您不需要考虑编码方式,除非文档没有指定一个编码方式

Beautiful Soup 安装方式:

  • Debian 或 Ubuntu Linux 系统通过 apt-get install python-bs4 命令安装
  • Windows 系统通过 easy_install beautifulsoup4pip install beautifulsoup4 命令安装,在使用 Beautiful Soup 4 之前需要先安装 bs4 库:pip install bs4
  • 通过下载源码的方式进行安装,地址为:https://www.crummy.com/software/BeautifulSoup/bs4/download/,然后在 CMD(控制台)中打开源码的指定路径输入命令 python setup.py install 即可

Beautiful Soup 支持 Python 标准库中包含的 HTML 解析器,但它也支持许多第三方 Python 解析器,其中包含 lxml 解析器。根据不同的操作系统使用以下命令安装 lxml:

  • apt-get install python-lxml:适用于 Linux 系统
  • easy_install lxml 或 pip install lxml:适用于 Windows 系统

另一个解析器是 html5lib,它是一个用于解析 HTML 的 Python 库,按照 Web 浏览器的方式解析 HTML。根据不同的操作系统使用以下命令安装 html5lib:

  • apt-get install python-html5lib:适用于 Linux 系统
  • easy_install html5lib 或 pip install html5lib:适用于 Windows 系统

解析器的比较:

解析器用法优点缺点
Python 标准库BeautifulSoup(markup, “html.parsesr”)执行速度适中在 Python 3.2.2 之前的版本中文档容错能力差
lxml 的 HTML 解析器BeautifulSoup(markup, “lxml”)速度快、文档容错能力强需要安装 C 语言库
lxml 的 XML 解析器BeautifulSoup(markup, “lxml-xml”) 或 BeautifulSoup(markup, “xml”)速度快、唯一支持 XML 的解析器需要安装 C 语言库
html5libBeautifulSoup(markup, “html5lib”)最好的容错性、以浏览器的方式解析文档、生成 HTML5 格式的文档速度慢、不依赖外部扩展

Beautiful Soup 使用

(1)导入 bs4 库,然后创建一个模拟 HTML 代码的字符串

from bs4 import BeautifulSoup

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://examle.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://examle.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://examle.com/tillie" class="sister" id="link3">Tillie</a>,
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""

(2)创建 Beautiful Soup 对象,并指定解析器为 lxml,最后通过打印的方式将解析的 HTML 代码显示在控制台中

soup = BeautifulSoup(html_doc, features='lxml')    # 创建一个 Beautiful Soup 对象,获取页面正文
print(soup)    # 打印解析的 HTML 代码

如果将 html_doc 字符串代码保存在 index.html 文件中,可以通过打开 HTML 文件的方式进行代码的解析,并可以通过 pretify() 方法进行代码的格式化处理

soup = BeautifulSoup(open('index.html'), 'lxml')    # 创建 Beautiful Soup 对象,打开需要解析的 HTML 文件
print(soup.prettify())    # 打印格式化后的代码

网络爬虫开发常用框架

爬虫框架就是一些爬虫项目的半成品,可以将一些爬虫常用的功能写好,然后留下一些接口,在不同的爬虫项目中,调用适合自己项目的接口,再编写少量的代码实现自己需要的功能。

  1. Scrapy 爬虫框架

    Scrapy 框架是一套比较成熟的、开源的 Python 爬虫框架,简单轻巧,并且非常方便,可以高效地爬取 Web 页面并从页面中提取结构化的数据。

    Scrapy 官网地址为:https://scrapy.org

  2. Crawley 爬虫框架

    Crawley 也是 Python 开发出的爬虫框架,该框架致力于改变人们从互联网中提取数据的方式。具体特性如下:

    • 基于 Eventlet 构建的高速网络爬虫框架
    • 可以将数据存储在关系数据库中,如:Postgres、MySQL、Oracle、SQlite 等数据库
    • 可以将爬取的数据导入为 Json、XML 格式
    • 支持非关系数据库,例如:Mongodb 和 Couchdb
    • 支持命令行工具
    • 可以使用您喜欢的工具进行数据的提取,例如:XPath 或 Pyquery 工具
    • 支持使用 Cookie 登录或访问那些只要登录才可以访问的网页
    • 简单易学

    Crawley 的官网地址为:http://project.crawley-cloud.com

  3. PySpider 爬虫框架

    相对于 Scrapy 框架而言,PySpider 框架还是新秀。PySpider 框架采用 Python 语言编写,分布式架构,支持多种数据库后端,强大的 WebUI 支持脚本编辑器、任务监视器、项目管理器以及结果查看器。具体特性如下:

    • Python 脚本控制,可以用任何您喜欢的 HTML 解析包(内置 pyquery)
    • WEB 界面编写调试脚本、起停脚本、监控执行状态、查看活动历史、获取结果产出
    • 支持 MySQL、MongoDB、Redis、SQLite、Elasticsearch、PostgreSQL、SQLAlchemy 等数据库
    • 支持 RabbitMQ、Beanstalk、Redis 和 Kombu 作为消息队列
    • 支持抓取 JavaScript 的页面
    • 强大的调度控制,支持超时重爬及优先级设置
    • 组件可替换,支持单机/分布式部署,支持 Docker 部署

    PySpider 源码地址为:https://github.com/binux/pyspider/releases

    开发文档地址为:http://docs.pysider.org/

实战项目:快手爬票

如果想要知道每列车次的时间信息,都需要在各类的列车网址中进行查询

搭建 QT 环境

QT 工具官网地址:https://www.qt.io/

Qt 官网有一个专门的资源下载网站,所有的开发环境和相关工具都可以从这里下载,具体地址是:https://download.qt.io/

国内镜像网站:

中国科学技术大学:<Index of /qtproject/>
清华大学:https://mirrors.tuna.tsinghua.edu.cn/qt/
北京理工大学:http://mirror.bit.edu.cn/qtproject/
中国互联网络信息中心:https://mirrors.cnnic.cn/qt/

QT 是 Python 开发窗体的工具之一,它不仅与 Python 有良好的兼容性,还有通过可视化拖曳的方式进行窗体的创建,提高开发人员的开发效率。由于 QT 在创建窗体项目时会自动生成后缀名为 ui 的文件,该文件需要转换为 py 文件后才可以被 python 所识别,所以需要为 QT 与 PyChar 开发工具进行配置,具体步骤如下:

(1)确保 Python、QT 与 PyCharm 开发工具安装完成后,打开 PyCharm 开发工具,打开设置界面,首先选择 Project Interpreter 选项,然后在右侧的列表中选择 “Show All…”,然后在弹出的窗口选择 “Add Local”
(2)在弹出的窗口选择 System Interpreter,然后在右侧的下拉列表中默认选择 python 对应的版本安装路径,单击 OK 即可
(3)确认 python 的编译版本后,在返回的窗口中选中右侧的添加按钮,然后在弹出的窗口中添加 PyQt5 模块包,点击 Install Package 按钮
(4)PyQt5 模块包安装完成后返回设置窗口,在该窗口中依次单击 “Tools” -> “External Tools” 选项,然后在右侧单击添加按钮
(5)在弹出的窗口中添加启动 Qt Designer 的快捷工具,首先在 “Name” 所对应的编辑框中填写工具名称 Qt Designer,然后在 “Program” 所对应的编辑框中填写 QT 开发工具的安装路径(尾部需要填写软件名),最后在 “Working directory” 所对应的编辑框中填写 “ P r o j e c t F i l e D i r ProjectFileDir ProjectFileDir”,该值代表项目文件目录,单击 OK 按钮完成添加
(6)添加将 QT 生成的 ui 文件转换为 py 文件的快捷工具,在 “Name” 所对应的编辑框中填写工具名称 PyUIC,然后在 “Program” 所对应的编辑框中填写 Python 的安装路径(尾部需要填写软件名),再在 “Arguments” 所对应的编辑框中填写将 ui 文件转换为 py 文件的 Python 代码 “-m PyQt5.uic.pyuic F i l e N a m e FileName FileName -o F i l e N a m e W i t h o u t E x t e n s i o n FileNameWithoutExtension FileNameWithoutExtension.py”,在 “Working directory” 所对应的编辑框中填写 “ F i l e D i r FileDir FileDir”,该值为文件目录,单击 OK 即可

主窗体设计

Python、QT 与 PyCharm 配置完成后,接下来需要对快手爬票的主窗体进行设计,首先需要创建主窗体外层,然后依次添加顶部图片、查询区域、选择车次类型区域、分类图片区域、信息表格区域。设计顺序如下所示:

主框体 -> 顶部图片 -> 查询区域 -> 选择车次类型 -> 分类图片 -> 信息表格

  1. Qt 拖曳控件

(1)在 PyCharm 中创建新的项目,并在右侧指定项目名称与位置
(2)项目打开完成后,在顶部的菜单栏中依次单击 Tools -> External Tools -> Qt Designer
(3)单击 Qt Designer 快捷工具后,Qt 的窗口编辑工具将自动打开,并且会自动弹出一个新建窗体的窗口,在该窗口中选择一个主窗体的模板,这里选择 Main Window 然后单击创建按钮即可
(4)主窗体创建完成后,自动进入到 Qt Designer 的设计界面,顶部区域是菜单栏与快捷菜单选项,左侧区域是各种控件与布局,中间的区域为编辑区域,该区域可以将控件拖曳至此处,也可以预览窗体的设计效果。右侧上方是对象查看器,此处列出所有控件以及彼此所属的关系层。右侧中间的位置是属性编辑器,此处可以设置控件的各种属性。右侧底部的位置分别为信号/槽编辑器、动作编辑器及资源浏览器
(5)根据自己的设计思路依次将指定的控件拖曳至主窗体中,首先添加主窗体器内的控件,再向主窗体中添加查询区域容器与控件,再向主窗体中添加选择车次类型容器与控件

主窗体容器与控件如下:

对象名称控件名称描述
centralwidgetQWidget该控件与对象名称是创建主窗体后默认生成,为主窗体外层容器
label_title_imgQLabel该控件为与主窗体容器内,用于设置顶部图片、对象名称自定义
label_train_imgQLabel该控件为与主窗体容器内,用于设置分类图片、对象名称自定义
tableViewQTableView该控件为与主窗体容器内,用于显示信息表格、对象名称自定义

查询区域容器与控件:

对象名称控件名称描述
widget_queryQWdiget该控件位于查询区域的容器内,用于显示查询区域,对象名称自定义,该控件为查询区域的容器
labelQLabel该控件位于查询区域的容器内,用于显示 “出发地:” 文字、对象名称自定义
label_2QLabel该控件位于查询区域的容器内,用于显示 “目的地:” 文字、对象名称自定义
label_3QLabel该控件位于查询区域的容器内,用于显示 “出发日:” 文字、对象名称自定义
pushButtonQPushButton该控件位于查询区域的容器内,用于显示查询按钮,对象名称自定义
textEditQTextEdit该控件位于查询区域的容器内,用于显示 “出发地:” 所对应的编辑框、对象名称自定义
textEdit_2QTextEdit该控件位于查询区域的容器内,用于显示 “目的地:” 所对应的编辑框、对象名称自定义
textEdit_3QTextEdit该控件位于查询区域的容器内,用于显示 “出发日:” 所对应的编辑框、对象名称自定义

选择车次类型容器与控件:

对象名称控件名称描述
widget_checkBoxQWidget该控件用于显示选择车次类型区域、对象名称自定义,该控件为选择车次类型区域的容器
checkBox_DQCheckBox该控件位于选择车次类型的容器内,用于选择动车类型、对象名自定义
checkBox_GQCheckBox该控件位于选择车次类型的容器内,用于选择高铁类型、对象名自定义
checkBox_KQCheckBox该控件位于选择车次类型的容器内,用于选择快车类型、对象名自定义
checkBox_TQCheckBox该控件位于选择车次类型的容器内,用于选择特快类型、对象名自定义
checkBox_ZQCheckBox该控件位于选择车次类型的容器内,用于选择直达类型、对象名自定义
label_typeQLabel该控件位于选择车次类型的容器内,用于显示 “车次类型:” 文字、对象名自定义

(6)窗体设置完成后,按下 “Ctrl + S” 快捷键保存窗体设计文件名称为 window.ui,然后需要将该文件保存在当前项目的目录当中,再选中该文件右键依次选择 External Tools -> PyUIC 选项,将窗体设置的 ui 文件转换为 py 文件

  1. 代码调试

打开 window.py 文件后,自动生成的代码中已经导入了 PyQt5 以及其内部的常用模块。PyQt5 的类别分为多个模块,常见的模块与概述如下:

模块名称描述
QtCore此模块应用处理时间、文件和目录、各种数据类型、流、URL、MIME 类型、线程或进程
QtGui此模块包含类窗口系统集成、事件处理、二维图像、基本成像、字体和文本,以及一套完整的 OpenGL 和 OpenGL ES 的绑定
QtWidgets此模块中包含的类,提供了一组用于创建经典桌面风格用户界面的 UI 元素
QtMulimedia此模块中包含的类,用于处理多媒体内容和 API 来访问的相机、收音机功能
QtNetwork此模块中包含网络编程的类,通过这些类使用网络编程更简单,更便携,便于 TCP/IP 和 UDP 客户端和服务器的编码
QtPositioning此模块中包含的类,利用各种可能的来源,确定位置,包括卫星、Wi-Fi
QtWebSockets此模块中包含实现 WebSocket 协议的类
QtXml此模块中包含用于处理 XML 文件中的类,该模块为 SAX 和 DOM API 提供了解决方法
QtSvg此模块中提供了用于显示 SVG 文件内容的类,SVG 是可缩放矢量图形,用于描述 XML 中的二维图形的一种格式
QtSql此模块提供了用于处理数据库的类
QtTest此模块包含的功能为 pyqt5 应用程序的单元测试

通过代码来调试主窗体中各种控件的细节处理,以及相应的属性。具体步骤如下:

(1)打开 window.py 文件,在右侧代码区域的 setupUi() 方法中修改主窗体的最大值与最小值,用于保持主窗体大小不变无法扩大或缩小,example:

MainWindow.setObjectName('MainWindow')    # 设置窗体对象名称
MainWindow.resize(960, 786)    # 设置窗体大小
MainWindow.setMinimumSize(QtCore.QSize(960, 786))    # 主窗体最小值
MainWindow.setMaximumSize(QtCore.QSize(960, 786))    # 主窗体最大值
self.centralwidget = QtWidgets.QWidget(MainWindow)    # 主窗体的 widget 控件
self.centralwidget.setObjectName('centralwidget')    # 设置对象名称

(2)将图片资源 img 文件夹复制到该项目中,然后导入 PyQt5.QtGui 模块中的 QPalette、QPixmap、QColor 用于对控件设置背景图片,为对象名 label_title_img 的 Label 控件设置背景图片,该控件用于显示顶部图片。example:

from PyQt5.QtGui import QPalette, QPixmap, QColor

self.label_title_img = QtWidgets.QLabel(self.centralwidget)    # 通过 label 控件显示顶部图片
self.label_title_img.setGeometry(QtCore.QRect(0, 0, 960, 141))
self.label_title_img.setObjectName('label_title_img')
title_img = QPixmap('img/bg1.png')    # 打开顶部位图
self.label_title_img.setPixmap(title_img)    # 设置调色板

(3)设置查询部分 widget 控件的背景图片,该控件起到容器的作用,在设置背景图片时并没有 Label 控件那么简单,首先需要为该控件开启自动填充背景功能,然后创建调色板对象,指定调色板背景图片,最后为控件设置对应的调色板即可,example:

self.widget_query = QtWidgets.QWidget(self.centralwidget)    # 查询部分的 widget
self.widget_query.setGeometry(QtCore.QRect(0, 141, 960, 80))
self.widget_query.setObjectName('widget_query')
self.widget_query.setAutoFillBackground(True)    # 开启自动填充背景

palette = QPalette()    # 调色板类
palette.setBrush(QPalette.Background, QtGui.QBrush(QtGui.QPixmap('img/bg2.png')))
self.widget_query.setPalete(palette)    # 为控件设置对应的调色板即可

根据以上两种设置背景图片的方法,分别为选择车次类型的 widget 控件与显示火车信息图片的 Label 控件设置背景图片

(4)通过代码修改窗体或控件文字时,需要在 retranslateUi() 方法中进行设置,example:

MainWindow.setWindowTitle(_translate('MainWindow', "车票查询"))
self.checkBox_T.setText(_translate('MainWindow', "T-特快"))
self.checkBox_K.setText(_translate('MainWindow', "K-快速"))
self.checkBox_Z.setText(_translate('MainWindow', "Z-直达"))
self.checkBox_D.setText(_translate('MainWindow', "D-动车"))
self.checkBox_G.setText(_translate('MainWindow', "G-高铁"))
self.label_type.setText(_translate('MainWindow', "车次类型:"))
self.label.setText(_translate('MainWindow', "出发地:"))
self.label_3.setText(_translate('MainWindow', "目的地:"))
self.label_4.setText(_translate('MainWindow', "出发日:"))
self.pushButton.setText(_translate('MainWindow', "查询"))

(5)导入 sys 模块,然后在代码块的最外层创建 show_MainWindow() 方法,该方法用于显示窗体,example:

def show_MainWindow():
    app = QtWidgets.QApplication(sys.argv)    # 实例化 QApplication 类,作为 GUI 主程序入口
    MainWindow = QtWidgets.QMainWindow()    # 创建 MainWindow 类
    ui = Ui_MainWindow()    # 实例化 UI 类
    ui.setupUi(MainWindow)    # 设置窗体 UI
    MainWindow.show()    # 显示窗体
    sys.exit(app.exec_())    # 当窗体创建完成,需要结束主循环过程

(6)在代码块的最外层模拟 Python 的程序入口,然后调用显示窗体的 show_MainWindow() 方法,example:

if __name__ == '__main__':
    show_MainWindow()
分析网页请求参数

通过 12306 客户服务中心所提供的查票请求地址获取火车票的相关信息。在发送请求时,地址中需要填写必要的参数否则后台将无法返回前台所需要的正确信息

(1)打开 12306 官方网站(http://www.12306.cn/mormhweb),单击右侧导航栏中的余票查询,然后输入出发地与目的地,出发日期默认即可。打开网络监视器,然后单击网页中的查询按钮,在网络监视器中将显示查询按钮所对应的网络请求

(2)单击网络请求将显示请求细节的窗口,在该窗口中默认会显示消息头的相关数据,此处可以获取完整的请求地址

(3)在请求地址的上方选择参数选项,将显示该请求地址中的必要参数。如:出发地名、目的地名、出发日期、车票类型

下载站名文件

得到请求地址与请求参数后,可以发现请求参数中的出发地与目的地均为车站名的英文缩写。而这个英文缩写的字母是通过输入中文车站名转换而来的,所有需要在网页中仔细查找是否有将车站名自动转换为英文缩写的请求信息

(1)关闭并重新打开网络监视器,然后对余票查询网页进行刷新,此时在网络监视器中选择类型为 js 的网络请求。在文件类型中仔细分析文件内容是否有与车站名相关的信息
(2)选中与车站名相关的网络请求,在请求细节中找到该请求的完整地址。然后在网页中打开该地址测试返回数据
(3)打开 Pycharm 开发工具,在 cheek tickets 目录中右键菜单依次选则 New -> Python File,创建一个名称为 get_stations.py 文件,然后安装 request 模块
(4)在 get_stations.py 文件中分别导入 requests 模块、re 模块及 os 模块,然后创建 getStaion() 方法,该方法用于发送获取地址信息的网络请求,并将返回的数据转换为需要的类型

def getStation():
    url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9242'    # 发送请求获取所有车站名称,通过输入的站名转换为查询地址的参数
    response = requests.get(url, verify=True)    # 请求并进行验证
    stations = re.findall(u'([\u4e00-\u9fa5]+)\|([A-Z]+)', response.text)    # 获取需要的车站名称
    stations = dict((stations))    # 转换为字典类型
    stations = str(stations)    # 转换为字符串类型否则无法写入文件
    write(stations)    # 调用写入方法

(5)分别创建 write() 方法、read() 方法及 isStations() 方法,分别用于写入文件、读取文件及判断车站文件是否存在

def write(stations):
    file = open('stations.text', 'w', encoding='utf_8_sig')    # 以写入模式打开文件
    file.write(stations)    # 写入文件
    file.close()    # 关闭文件


def read():
    file = open('stations.text', 'r', encoding='utf_8_sig')    # 以读模式打开文件
    data = file.readine()    # 读取文件
    file.close()    # 关闭文件
    return data


def isStations():
    isStations = os.path.exists('stations.text')    # 判断车站文件是否存在
    return isStations

(6)打开 window.py 文件,首先导入 get_stations 文件下的所有方法,然后在模拟 python 的程序入口处修改代码。接下来判断是否存在所有车站信息的文件,如果没有该文件就下载车站信息的文件然后显示窗体,如果存在将直接显示窗体即可

from get_stations import *

if __name__ == '__main__':
    if isStations() == False:    # 判断是否存在所有车站的文件,没有就下载,有就直接显示窗体
        getStation()    # 下载所有车站文件
        show_MainWindow()    # 调用显示窗体的方法
    else:
        show_MainWindow()

(7)在 window.py 文件下,单击右键菜单选择 “Run ‘window’” 菜单运行主窗体,主窗体界面显示后在 check tickets 目录下将自动下载 stations.text 文件

车票信息的请求与显示

发送与分析车票信息的查询请求

得到了获取车票信息的网络请求地址,然后又分析出请求地址的必要参数以及车站名称转换的文件,接下来就需要将主窗体中输入的出发地、目的地及出发日期三个重要的参数配置到查票的请求地址中,然后分析并接收所查询车票的对应信息

(1)在浏览器中查询请求地址,然后在浏览器中将以 json 的方式返回车票的查询信息

(2)发现可用数据后,在项目中创建 query_request.py 文件,在该文件中首先导入 get_stations 文件下的所有方法,然后分别创建名为 data 与 type_data 的列表分别用于保存整理好的车次信息与分类后的车次信息

from get_stations import *

data = []    # 用于保存整理好的车次信息
type_data = []    # 保存分类后车次信息

(3)创建 query() 方法,在调用该方法时需要三个参数,分别为出发日期、出发地以及目的地;然后创建查询请求的完整地址,并通过 format() 方法格式化地址;再将返回的 json 数据转换为字典类型;最后通过字典类型键值的方法取出对应的数据并进行整理与分类

def query(date, from_station, to_station):
    data.clear()    # 清空数据
    type_data.clear()    # 清空车次分类保存的数据

    url = 'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2022-11-29&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=NFF&purpose_codes=ADULT'.format(date, from_station, to_station)    # 请求查询地址

    response = requests.get(url)    # 发送请求
    result = response.json()    # 将 json 数据转换为字典类型,通过键值对取数据
    result = result['data']['result']

    if isStations() == True:    # 判断车站文件是否存在
        stations = eval(read())    # 读取所有车站并转换为 dic 类型
        if len(result) != 0:    # 判断返回数据是否为空
            for i in result:
                tmp_list = i.split('|')    # 分割数据并添加到列表中
                from_station = list(stations.keys())[list(stations.values()).index(tmp_list[6])]    # 查询结果中出发点和目的地均为缩写字母,需要再车站库中找到对应的车站名称
                to_station = list(stations.keys())[list(stations.values()).index(tmp_lis[7])]
                
                seat = [tmp_list[3], from_station, to_station, tmp_list[8], 
                        tmp_list[9], tmp_list[10], tmp_list[32], tmp_list[31], 
                        tmp_list[30], tmp_list[21], tmp_list[23], tmp_list[33], 
                        tmp_list[28], tmp_list[24], tmp_list[29], tmp_list[26]
                        ]    # 创建座位数组,由于返回的座位数据中含有空(''),所以将空改为 “--”
                newSeat = []
            
                for s in seat:    # 循环将座位信息中的空(''),改成 “--”
                    if s == '':
                        s = '--'
                    else:
                        s = s
                    newSeat.append(s)    # 保存新的座位信息
                data.append(newSeat)
        return data    # 返回整理好的车次信息

(4)依次创建获取高铁信息、移除高铁信息、获取动车信息、移除动车信息、获取直达信息、移除直达信息、获取特快信息、移除特快信息、获取快速信息及移除快速信息的方法,用于车次分类数据的处理

def g_vehicle():    # 获取高铁信息
    if len(data) != 0:
        for g in data:    # 循环所有火车数据
            i = g[0].startswith('G')    # 判断车次首字母是不是高铁
            if i:    # 如果是将该信息添加到高铁数据中
                type_data.append(g)


def r_g_vehicle():    # 移除高铁信息
    if len(data) !=0 and len(type_data) != 0:
        for g in data:
            i = g[0].startswith('G')
            if i:
                type_data.remove(g)


def d_vehicle():    # 获取动车信息
    if len(data) != 0:
        for d in data:    # 循环所有火车数据
            i = d[0].startswith('D')    # 判断车次首字母是不是动车
            if i == True:    # 如果是将该信息添加到动车数据中
                type_data.append(d)


def r_d_vehicle():    # 移除动车信息
    if len(data) != 0:
        for d in data:
            i = d[0].startswith('D')
            if i == True:
                type_data.remove(d)


def z_vehicle():    # 获取直达信息
    if len(data) != 0:
        for z in data:    # 循环所有火车数据
            i = z[0].startswith('Z')    # 判断车次首字母是不是直达
            if i == True:    # 如果是将该信息添加到直达数据中
                type_data.append(z)


def r_z_vehicle():    # 移除直达信息
    if len(data) != 0:
        for z in data:
            i = z[0].startswith('Z')
            if i == True:
                type_data.remove(z)


def t_vehicle():    # 获取特快信息
    if len(data) != 0:
        for t in data:    # 循环所有火车数据
            i = d[0].startswith('T')    # 判断车次首字母是不是特快
            if i == True:    # 如果是将该信息添加到特快数据中
                type_data.append(t)


def r_t_vehicle():    # 移除特快信息
    if len(data) != 0:
        for t in data:
            i = t[0].startswith('T')
            if i == True:
                type_data.remove(t)


def k_vehicle():    # 获取快速信息
    if len(data) != 0:
        for k in data:    # 循环所有火车数据
            i = d[0].startswith('K')    # 判断车次首字母是不是快速
            if i == True:    # 如果是将该信息添加到快速数据中
                type_data.append(k)


def r_k_vehicle():    # 移除快速信息
    if len(data) != 0:
        for k in data:
            i = d[0].startswith('K')
            if i == True:
                type_data.remove(k)

在窗体中显示查票信息

完成了车票信息查询请求的文件后,接下来需要将获取的车票信息显示在快手爬票的主窗体中

(1)打开 window.py 文件,导入 PyQt5.QtCore 模块中的 Qt 类,然后导入 PyQt5.QtWidgets 模块与 PyQt5.QtGui 模块下的所有方法,再导入 query_request 文件中的所有方法

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from query_request

(2)在 setupUi() 方法中找到用于显示车票信息的 tableView 表格控件。然后为该控件设置相关属性

self.tableView = QtWidgets.QTableView(self.centralwidget)    # 显示车次信息的列表
self.tableView.setGeometry(QtCore.QRect(0, 320, 960, 440))
self.tableView.setQbjectName('tableView')    # 创建存储数据的模式

self.model = QStandardItemModel()    # 根据空间自动改变列宽度并且不可修改列宽度
self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.tableView.horizontalHeader().setVisible(False)    # 设置表头不可见
self.tableView.verticalHeader().setVisible(False)    # 设置纵向表头不可见

font = QtGui.QFont()    # 设置表格内容文字大小
font.setPointSize(10)
self.tableView.setFont(font)
self.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers)    # 设置表格内容不可编辑
self.tableView.setVerticalScrollBarPolicy    # 垂直滚动条始终开启

(3)导入 time 模块,该模块提供了用于处理时间的各种方法。然后在代码块的最外层创建 get_time() 方法用于获取系统的当前日期,再创建 is_valid_date() 方法用于判断输入的日期是否是一个有效的日期字符串

import time


def get_time():    # 获取系统当前时间并转换请求数据所需要的格式
    now = int(time.time())    # 获取当前时间的时间戳
    timeStruct = time.localtime(now)    # 转换格式为:%Y-%m-%d %H:%M:%S
    strTime = time.strftime('%Y-%m-%d', timeStruct)
    return strTime


def is_valid_date(str):
    try:    # 判断是否是一个有效的日期字符串
        time.strptime(str, '%Y-%m-%d')
        return True
    except:
        return False

(4)依次创建 change_G、change_D、change_Z、change_T、change_K() 方法,以上方法均为车次分类复选框的事件处理

def change_G(self, state):    # 高铁复选框事件处理
    if state == QtCore.Qt.Checked:    # 选中将高铁信息添加到最后要显示的数据当中
        g_vehicle()    # 获取高铁信息
        self.displayTable(len(type_data), 16, type_data)    # 通过表格显示该车型数据
    else:
        r_g_vehicle()    # 取消选中状态将移除该数据
        self.displayTable(len(type_data), 16, type_data)


def change_D(self, state):    # 动车复选框事件处理
    if state == QtCore.Qt.Checked:    # 选中将动车信息添加到最后要显示的数据当中
        d_vehicle()    # 获取动车信息
        self.displayTable(len(type_data), 16, type_data)    # 通过表格显示该车型数据
    else:
        r_d_vehicle()    # 取消选中状态将移除该数据
        self.displayTable(len(type_data), 16, type_data)


def change_Z(self, state):    # 直达复选框事件处理
    if state == QtCore.Qt.Checked:    # 选中将直达信息添加到最后要显示的数据当中
        z_vehicle()    # 获取直达信息
        self.displayTable(len(type_data), 16, type_data)    # 通过表格显示该车型数据
    else:
        r_z_vehicle()    # 取消选中状态将移除该数据
        self.displayTable(len(type_data), 16, type_data)


def change_T(self, state):    # 特快复选框事件处理
    if state == QtCore.Qt.Checked:    # 选中将特快信息添加到最后要显示的数据当中
        t_vehicle()    # 获取特快信息
        self.displayTable(len(type_data), 16, type_data)    # 通过表格显示该车型数据
    else:
        r_t_vehicle()    # 取消选中状态将移除该数据
        self.displayTable(len(type_data), 16, type_data)


def change_K(self, state):    # 快速复选框事件处理
    if state == QtCore.Qt.Checked:    # 选中将快速信息添加到最后要显示的数据当中
        k_vehicle()    # 获取快速信息
        self.displayTable(len(type_data), 16, type_data)    # 通过表格显示该车型数据
    else:
        r_k_vehicle()    # 取消选中状态将移除该数据
        self.displayTable(len(type_data), 16, type_data)

(5)创建 checkBox_default() 方法,该方法用于将所有车次分类复选框取消勾选

def checkBox_default(self):
    self.checkBx_G.setChecked(False)
    self.checkBx_D.setChecked(False)
    self.checkBx_Z.setChecked(False)
    self.checkBx_T.setChecked(False)
    self.checkBx_K.setChecked(False)

(6)创建 messageDialog() 方法,用于显示主窗体非法操作的消息提示框;创建 displayTable() 方法,用于显示车次信息的表格与内容

def messageDialog(self, title, message):    # 显示消息提示框,参数 title 为提示框标题文字,message 为提示消息
    msg_box = QMessageBox(QMessageBox.Warning, title, message)
    msg_box.exec_()


def displayTable(self, train, info, data):    # 显示车次信息表格;train 为共有多少列车,作为表格的行;info 为该列车的具体信息,作为表格的列
    self.model.clear()
    for row in range(info):
        item = QStandardItem(data[row][column])    # 添加表格内容
        self.model.setItem(row, column, item)    # 向表格存储模式中添加表格具体信息
    self.tableView.setModel(self.model)    # 设置表格存储数据的模式

(7)创建 on_click() 方法,该方法是查询按钮的单击事件。在该方法中首先获取出发地、目的地与出发日期三个编辑框的输入内容,然后对三个编辑框中输入的内容进行合法检测,符合规范后调用 query() 方法提交车票查询的请求并且将返回的数据赋值给 data,最后通过调用 displayTable() 方法实现在表格中显示车票查询的全部信息

def on_click(self):
    get_from = self.textEdit.toPlainText()
    get_to = self.textEdit_2.toPlainText()
    get_date = self.textEdit_3.toPlainText()

    if isStations() == True:
        stations = eval(read())

        if get_from != '' and get_to != '' and get_date != '':
            if get_from in stations and get_to in stations and is_valid_date(get_date):
                inputYearDay = time.strptime(get_date, '%Y-%m%d').tm_yday
                yearTody = time.localtime(time.time()).tm_yday
                timeDifference = inputYearDay - yearTody

                if 0 <= timeDifference <= 28:
                    from_station = stations[get_from]
                    to_station = stations[get_to]
                    data = query(get_date, from_station, to_station)
                    self.checkBox_default()

                    if len(data) != 0:
                        self.displayTable(len(data), 16, data)
                    else:
                        self.messageDialog("警告", "没有返回的网络数据!")
                else:
                    self.messageDialog("警告", "超出查询日期的范围内", "不可查询昨天的车票信息,以及 29 天以后的车票信息!")
            else:
                self.messageDialog("警告", "输入的站名不存在或日期格式不正确!")
        else:
            self.messageDialog("警告", "请填写车站名称!")
    else:
        self.messageDialog("警告", "未下载车站查询文件!")

(8)在 retranslateUi() 方法中,首先设置出发日期的编辑框中显示系统的当前日期,然后设置查询按钮的单击事件,最后分别设置高铁、动车、直达、特快及快速复选框选中与取消事件

self.textEdit_3.setText(get_time())    # 出发日期显示当天日期
self.pushButton.clicked.connect(self.on_click)    # 查询按钮指定单击事件的方法
self.checkBox_G.stateChanged.connect(self.change_G)    # 高铁选中与取消事件
self.checkBox_D.stateChanged.connect(self.change_D)    # 动车选中与取消事件
self.checkBox_Z.stateChanged.connect(self.change_Z)    # 直达选中与取消事件
self.checkBox_T.stateChanged.connect(self.change_T)    # 特快选中与取消事件
self.checkBox_K.stateChanged.connect(self.change_K)    # 快速选中与取消事件

(9)在 window.py 文件下,单击邮件,选择 “Run ‘window’” 菜单运行主窗体,然后输入符号规范的出发地、目的地与出发日期

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值