Python基础内容---上万字总结(回顾自己一年来所有关于python的学习)

Python语言元素

作为一个程序员,可能经常会被外行问到两个问题,其一是“什么是(计算机)程序”,其二是“写(计算机)程序能做什么”,这里我先对这两个问题做一个回答。程序是指令的集合写程序就是用指令控制计算机做我们想让它做的事情。那么,为什么要用Python语言来写程序呢?因为Python语言简单优雅,相比C、C++、Java这样的编程语言,Python对初学者更加友好,当然这并不是说Python不像其他语言那样强大,Python几乎是无所不能的,Python可以用于服务器程序开发、云平台开发、数据分析、机器学习等各个领域。当然,Python语言还可以用来粘合其他语言开发的系统,所以也经常被戏称为“胶水语言”。

需要知道的计算机常识

在开始系统的学习编程之前,我们需要先了解一些计算机的基础知识。计算机的硬件系统通常由五大部件构成,包括:运算器控制器存储器输入设备输出设备。其中,运算器和控制器放在一起就是常说的中央处理器,它的功能是执行各种运算和控制指令。其中程序是指令的集合,写程序就是将一系列的指令按照某种方式组织到一起,然后通过这些指令去控制计算机做我们想让它做的事情。目前,使用的计算机基本都是“冯·诺依曼体系结构”的计算机,这种计算机有两个关键点:一是要将存储设备与中央处理器分开;二是将数据以二进制方式编码

二进制是一种“逢二进一”的计数法,跟我们人类使用的“逢十进一”的计数法本质是一样的。人类因为有十根手指所以使用了十进制,因为在计数时十根手指用完之后就只能用进位的方式来表示更大的数值。要知道计算机是使用二进制计数的,不管什么数据到了计算机内存中都是以二进制形式存在的

变量和类型

要想在计算机内存中保存数据,首先就得知道变量这个概念。在编程语言中,变量是数据的载体,简单的说就是一块用来保存数据的内存空间,变量的值可以被读取和修改,这是所有计算和控制的基础。计算机能处理的数据有很多种类型,最常见的就是数值,除了数值之外还有文本、图形、音频、视频等各种各样的数据。虽然数据在计算机中都是以二进制形态存在的,但是我们可以用不同类型的变量来表示数据类型的差异。Python中的数据类型很多,而且也允许我们自定义新的数据类型(这一点在后面会讲到),这里我们需要先了解几种常用的数据类型。

  • 整型(int):Python中可以处理任意大小的整数,而且支持二进制(如0b100,换算成十进制是4)、八进制(如0o100,换算成十进制是64)、十进制(100)和十六进制(0x100,换算成十进制是256)的表示法。
  • 浮点型(float):浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,浮点数除了数学写法(如123.456)之外还支持科学计数法(如1.23456e2)。
  • 字符串型(str):字符串是以单引号或双引号括起来的任意文本,比如'hello'"hello"
  • 布尔型(bool):布尔值只有TrueFalse两种值,要么是True,要么是False

变量命名

对于每个变量我们需要给它取一个名字,就如同我们每个人都有自己的名字一样,名字不同,含义也不同。在Python中,变量命名需要遵循以下这些规则,这些规则又分为必须遵守的硬性规则和建议遵守的非硬性规则。

  • 硬性规则:
    • 规则1:变量名由字母、数字和下划线构成,数字不能开头。需要说明的是,像!@#这些特殊字符是不能出现在变量名中的,强烈建议尽可能使用英文字母
    • 规则2:大小写敏感,简单的说就是大写的A和小写的a是两个不同的变量。
    • 规则3:变量名不要跟Python语言的关键字(有特殊含义的单词,后面会讲到)和保留字(如已有的函数、模块等的名字)发生重名的冲突
  • 非硬性规则:
    • 规则1:变量名通常使用小写英文字母,多个单词用下划线进行连接。
    • 规则2:受保护的变量用单个下划线开头。
    • 规则3:私有的变量用两个下划线开头。

当然,作为一个专业的程序员,给变量(事实上应该是所有的标识符)命名时做到见名知意也非常重要。

变量的使用

下面通过例子来说明变量的类型和变量的使用。

"""
使用变量保存数据并进行加减乘除运算
"""
a = 45          # 变量a保存了45
b = 12          # 变量b保存了12
print(a + b)    # 57
print(a - b)    # 33
print(a * b)    # 540
print(a / b)    # 3.75

在Python中可以使用type函数对变量的类型进行检查。程序设计中函数的概念跟数学上函数的概念基本一致,数学上的函数相信大家并不陌生,它包括了函数名、自变量和因变量。如果暂时不理解函数这个概念也不要紧,我们会在后续的内容中专门讲解函数的定义和使用。

"""
使用type()检查变量的类型
"""
a = 100
b = 12.345
c = 'hello, world'
d = True
print(type(a))    # <class 'int'>
print(type(b))    # <class 'float'>
print(type(c))    # <class 'str'>
print(type(d))    # <class 'bool'>

不同类型的变量可以相互转换,这一点可以通过Python的内置函数来实现。

  • int():将一个数值或字符串转换成整数,可以指定进制。
  • float():将一个字符串转换成浮点数。
  • str():将指定的对象转换成字符串形式,可以指定编码。
  • chr():将整数转换成该编码对应的字符串(一个字符)。
  • ord():将字符串(一个字符)转换成对应的编码(整数)。

下面的例子演示了Python中类型转换的操作。

"""
Python中的类型转换操作
"""
a = 100
b = 12.345
c = 'hello, world'
d = True
# 整数转成浮点数
print(float(a))    # 100.0
# 浮点型转成字符串 (输出字符串时不会看到引号哟)
print(str(b))      # 12.345
# 字符串转成布尔型 (有内容的字符串都会变成True)
print(bool(c))     # True
# 布尔型转成整数 (True会转成1,False会转成0)
print(int(d))      # 1
# 将整数变成对应的字符 (97刚好对应字符表中的字母a)
print(chr(97))     # a
# 将字符转成整数 (Python中字符和字符串表示法相同)
print(ord('a'))    # 97

小结

在Python程序中,可以使用变量来保存数据变量有不同的类型变量可以做运算也可以通过内置函数来转换变量类型

Python运算符

运算符 描述
[] [:] 下标,切片
** 指数
~ + - 按位取反, 正负号
* / % // 乘,除,模,整除
+ - 加,减
>> << 右移,左移
& 按位与
^ | 按位异或,按位或
<= < > >= 小于等于,小于,大于,大于等于
== != 等于,不等于
is is not 身份运算符
in not in 成员运算符
not or and 逻辑运算符
= += -= *= /= %= //= **= &= 赋值运算符

说明: 上面这个表格实际上是按照运算符的优先级从上到下列出了各种运算符。所谓优先级就是在一个运算的表达式中,如果出现了多个运算符,应该先执行哪个运算再执行哪个运算的顺序。在实际开发中,如果搞不清楚运算符的优先级,可以使用圆括号来确保运算的执行顺序。

算术运算符

Python中的算术运算符非常丰富,除了大家最为熟悉的加减乘除之外,还有整除运算符、求模(求余数)运算符和求幂运算符。下面的例子展示了算术运算符的使用。

print(321 + 123)     # 加法运算
print(321 - 123)     # 减法运算
print(321 * 123)     # 乘法运算
print(321 / 123)     # 除法运算
print(321 % 123)     # 求模运算
print(321 // 123)    # 整除运算
print(321 ** 123)    # 求幂运算

赋值运算符

赋值运算符应该是最为常见的运算符,它的作用是将右边的值赋给左边的变量。下面的例子演示了赋值运算符和复合赋值运算符的使用。

a = 10
b = 3
a += b        # 相当于:a = a + b
a *= a + 2    # 相当于:a = a * (a + 2)
print(a)      # 算一下这里会输出什么

比较运算符和逻辑运算符

比较运算符有的地方也称为关系运算符,包括==!=<><=>=,没有什么好解释的,需要提醒的是比较相等用的是==,请注意这里是两个等号,因为=是赋值运算符,我们在上面刚刚讲到过,==才是比较相等的运算符;比较不相等用的是!=,这不同于数学上的不等号,

逻辑运算符有三个,分别是andornotand字面意思是“而且”,所以and运算符会连接两个布尔值,如果两个布尔值都是True,那么运算的结果就是True;左右两边的布尔值有一个是False,最终的运算结果就是False。相信大家已经想到了,如果and左边的布尔值是False,不管右边的布尔值是什么,最终的结果都是False,所以在做运算的时候右边的值会被跳过(短路处理),这也就意味着在and运算符左边为False的情况下,右边的表达式根本不会执行。or字面意思是“或者”,所以or运算符也会连接两个布尔值,如果两个布尔值有任意一个是True,那么最终的结果就是True。当然,or运算符也是有短路功能的,在它左边的布尔值为True的情况下,右边的表达式根本不会执行。not运算符的后面会跟上一个布尔值,它的作用是得到与该布尔值相反的值,也就是说,not后面的布尔值如果是True,运算结果就是False;而not后面的布尔值如果是False,运算结果就是True。说了一大堆,还是直接看下面例子更容易理解。

"""
比较运算符和逻辑运算符的使用
"""
flag0 = 1 == 1
flag1 = 3 > 2
flag2 = 2 < 1
flag3 = flag1 and flag2
flag4 = flag1 or flag2
flag5 = not (1 != 2)
print('flag0 =', flag0)    # flag0 = True
print('flag1 =', flag1)    # flag1 = True
print('flag2 =', flag2)    # flag2 = False
print('flag3 =', flag3)    # flag3 = False
print('flag4 =', flag4)    # flag4 = True
print('flag5 =', flag5)    # flag5 = False

说明:比较运算符的优先级高于赋值运算符,所以flag0 = 1 == 1先做1 == 1产生布尔值True,再将这个值赋值给变量flag0print函数可以输出多个值,多个值之间可以用,进行分隔,输出的内容之间默认以空格分开。

运算符的例子

例子1:华氏温度转换为摄氏温度。

提示:华氏温度到摄氏温度的转换公式为:C = (F - 32) / 1.8

f = float(input('请输入华氏温度: '))
c = (f - 32) / 1.8
print('%.1f华氏度 = %.1f摄氏度' % (f, c))

说明:在使用print函数输出时,也可以对字符串内容进行格式化处理,上面print函数中的字符串%.1f是一个占位符,稍后会由一个float类型的变量值替换掉它。同理,如果字符串中有%d,后面可以用一个int类型的变量值替换掉它,而%s会被字符串的值替换掉。除了这种格式化字符串的方式外,还可以用下面的方式来格式化字符串,其中{f:.1f}{c:.1f}可以先看成是{f}{c},表示输出时会用变量f和变量c的值替换掉这两个占位符,后面的:.1f表示这是一个浮点数,小数点后保留1位有效数字。

print(f'{f:.1f}华氏度 = {c:.1f}摄氏度')
例子2:输入圆的半径计算计算周长和面积。
radius = float(input('请输入圆的半径: '))
perimeter = 2 * 3.1416 * radius
area = 3.1416 * radius * radius
print('周长: %.2f' % perimeter)
print('面积: %.2f' % area)
例子3:输入年份判断是不是闰年。
year = int(input('请输入年份: '))
is_leap = year % 4 == 0 and year % 100 != 0 or year % 400 == 0
print(is_leap)

说明:比较运算符会产生布尔值,而逻辑运算符andor会对这些布尔值进行组合,最终也是得到一个布尔值,闰年输出True,平年输出False

小结

学会使用运算符以及由运算符构成的表达式,就可以帮助我们解决很多实际的问题,运算符和表达式对于任何一门编程语言都是非常重要的

Python分支结构

应用场景

迄今为止,我们写的Python代码都是一条一条语句顺序执行,这种代码结构通常称之为顺序结构。然而仅有顺序结构并不能解决所有的问题,比如设计一个游戏,游戏第一关的通关条件是玩家获得1000分,那么在完成本局游戏后,要根据玩家得到分数来决定究竟是进入第二关,还是告诉玩家“Game Over”,这里就会产生两个分支,而且这两个分支只有一个会被执行。类似的场景还有很多,我们将这种结构称之为“分支结构”或“选择结构”。给你一分钟的时间,你应该可以想到至少5个以上这样的例子,说的再多,都不如你多动手试一试。

if语句的使用

在Python中,要构造分支结构可以使用ifelifelse关键字。所谓关键字就是有特殊含义的单词,像ifelse就是专门用于构造分支结构的关键字,很显然你不能够使用它作为变量名。下面的例子中演示了如何构造一个分支结构。

"""
用户身份验证
"""
username = input('请输入用户名: ')
password = input('请输入口令: ')
# 用户名是admin且密码是123456则身份验证成功否则身份验证失败
if username == 'admin' and password == '123456':
    print('身份验证成功!')
else:
    print('身份验证失败!')

需要说明的是,不同于C++、Java等编程语言,Python中没有用花括号来构造代码块而是使用了缩进的方式来表示代码的层次结构,如果if条件成立的情况下需要执行多条语句,只要保持多条语句具有相同的缩进就可以了。换句话说连续的代码如果又保持了相同的缩进那么它们属于同一个代码块,相当于是一个执行的整体。缩进可以使用任意数量的空格,但通常使用4个空格

提示ifelse 的最后面有一个:,它是用英文输入法输入的冒号;程序中输入的'"=()等特殊字符,都是在英文输入法状态下输入的。注意在写代码的时候都打开英文输入法(注意是英文输入法而不是中文输入法的英文输入模式),这样可以避免很多不必要的麻烦。

如果要构造出更多的分支,可以使用if...elif...else...结构或者嵌套的if...else...结构,下面的代码演示了如何利用多分支结构实现分段函数求值。

f ( x ) = { 3 x − 5 , ( x > 1 ) x + 2 , ( − 1 ≤ x ≤ 1 ) 5 x + 3 , ( x < − 1 ) f(x) = \begin{cases} 3x - 5, & (x \gt 1) \\ x + 2, & (-1 \le x \le 1) \\ 5x + 3, & (x \lt -1) \end{cases} f(x)= 3x5,x+2,5x+3,(x>1)(1x1)(x<1)

"""
分段函数求值
"""
x = float(input('x = '))
if x > 1:
    y = 3 * x - 5
elif x >= -1:
    y = x + 2
else:
    y = 5 * x + 3
print(f'f({x}) = {y}')

根据实际需要,分支结构是可以嵌套的,例如判断是否通关以后还要根据获得的宝物或者道具的数量对表现给出等级(比如点亮两颗或三颗星星),那么就需要在if的内部构造出一个新的分支结构,同理elifelse中也可以再构造新的分支,称之为嵌套的分支结构

"""
分段函数求值
"""
x = float(input('x = '))
if x > 1:
    y = 3 * x - 5
else:
    if x >= -1:
        y = x + 2
    else:
        y = 5 * x + 3
print(f'f({x}) = {y}')

说明: 自己感受和评判一下这两种写法到底是哪一种更好。在Python之禅中有这么一句话:“Flat is better than nested”,之所以提倡代码“扁平化”,是因为代码嵌套的层次如果很多,会严重的影响代码的可读性,所以使用更为扁平化的结构在很多场景下都是较好的选择。

例子

例子1:英制单位英寸与公制单位厘米互换。
value = float(input('请输入长度: '))
unit = input('请输入单位: ')
if unit == 'in' or unit == '英寸':
    print('%f英寸 = %f厘米' % (value, value * 2.54))
elif unit == 'cm' or unit == '厘米':
    print('%f厘米 = %f英寸' % (value, value / 2.54))
else:
    print('请输入有效的单位')
例子2:百分制成绩转换为等级制成绩。

要求:如果输入的成绩在90分以上(含90分)输出A;80分-90分(不含90分)输出B;70分-80分(不含80分)输出C;60分-70分(不含70分)输出D;60分以下输出E。

score = float(input('请输入成绩: '))
if score >= 90:
    grade = 'A'
elif score >= 80:
    grade = 'B'
elif score >= 70:
    grade = 'C'
elif score >= 60:
    grade = 'D'
else:
    grade = 'E'
print('对应的等级是:', grade)
例子3:输入三条边长,如果能构成三角形就计算周长和面积。
a = float(input('a = '))
b = float(input('b = '))
c = float(input('c = '))
if a + b > c and a + c > b and b + c > a:
    peri = a + b + c
    print(f'周长: {peri}')
    half = peri / 2
    area = (half * (half - a) * (half - b) * (half - c)) ** 0.5
    print(f'面积: {area}')
else:
    print('不能构成三角形')

说明: 上面通过边长计算三角形面积的公式叫做海伦公式

简单的小结

学会了Python中的分支结构和循环结构,就可以用Python程序来解决很多实际的问题了。记住了ifelifelse这几个关键字以及如何使用它们来构造分支结构,接下来介绍循环结构

循环结构

应用场景

在写程序的时候,一定会遇到需要重复执行某条指令或某些指令的场景。例如用程序控制机器人踢足球,如果机器人持球而且还没有进入射门范围,那么我们就要一直发出让机器人向球门方向移动的指令。在这个场景中,让机器人向球门方向移动就是一个需要重复的动作,当然这里还会用到上一课讲的分支结构来判断机器人是否持球以及是否进入射门范围。再举一个简单的例子,如果要实现每隔1秒中在屏幕上打印一次“hello, world”并持续打印一个小时,我们肯定不能够直接print('hello, world')这句代码写3600遍,这里我们需要构造循环结构。

所谓循环结构,就是程序中控制某条或某些指令重复执行的结构。在Python中构造循环结构有两种做法,一种是for-in循环,另一种是while循环。

for-in循环

如果明确的知道循环执行的次数,使用for-in循环,例如输出100行的”hello, world“。 被for-in循环控制的语句块也是通过缩进的方式来构造的,这一点跟分支结构完全相同。

"""
用for循环实现1~100求和
"""
total = 0
for x in range(1, 101):
    total += x
print(total)

需要说明的是上面代码中的range(1, 101)可以用来构造一个从1100的范围,当我们把这样一个范围放到for-in循环中,就可以通过前面的循环变量x依次取出从1100的整数。当然,range的用法非常灵活,下面给出了一个例子:

  • range(101):可以用来产生0到100范围的整数,需要注意的是取不到101。
  • range(1, 101):可以用来产生1到100范围的整数,相当于前面是闭区间后面是开区间。
  • range(1, 101, 2):可以用来产生1到100的奇数,其中2是步长,即每次递增的值。
  • range(100, 0, -2):可以用来产生100到1的偶数,其中-2是步长,即每次递减的值。

知道了这一点,我们可以用下面的代码来实现1~100之间的偶数求和。

total = 0
for x in range(2, 101, 2):
    total += x
print(total)

while循环

如果要构造不知道具体循环次数的循环结构,使用while循环。while循环通过一个能够产生bool值的表达式来控制循环,当表达式的值为True时则继续循环,当表达式的值为False时则结束循环。

下面我们通过一个“猜数字”的小游戏来看看如何使用while循环。猜数字游戏的规则是:计算机出一个1100之间的随机数,玩家输入自己猜的数字,计算机给出对应的提示信息(大一点、小一点或猜对了),如果玩家猜中了数字,计算机提示用户一共猜了多少次,游戏结束,否则游戏继续。

"""
猜数字游戏
"""
import random

# 产生一个1-100范围的随机数
answer = random.randint(1, 100)
counter = 0
while True:
    counter += 1
    number = int(input('请输入: '))
    if number < answer:
        print('大一点')
    elif number > answer:
        print('小一点')
    else:
        print('恭喜你猜对了!')
        break
# 当退出while循环的时候显示用户一共猜了多少次
print(f'你总共猜了{counter}次')

break和continue

上面的代码中使用while True构造了一个条件恒成立的循环,也就意味着如果不做特殊处理,循环是不会结束的,这也就是常说的“死循环”。为了在用户猜中数字时能够退出循环结构,我们使用了break关键字,它的作用是提前结束循环。需要注意的是,break只能终止它所在的那个循环,这一点在使用嵌套循环结构时需要引起注意,下面的例子我们会讲到什么是嵌套的循环结构。除了break之外,还有另一个关键字是continue,它可以用来放弃本次循环后续的代码直接让循环进入下一轮。

嵌套的循环结构

和分支结构一样,循环结构也是可以嵌套的,也就是说在循环中还可以构造循环结构。下面的例子演示了如何通过嵌套的循环来输出一个乘法口诀表(九九表)。

"""
打印乘法口诀表
"""
for i in range(1, 10):
    for j in range(1, i + 1):
        print(f'{i}*{j}={i * j}', end='\t')
    print()

很显然,在上面的代码中,外层循环用来控制一共会产生9行的输出,而内层循环用来控制每一行会输出多少列。内层循环中的输出就是九九表一行中的所有列,所以在内层循环完成时,有一个print()来实现换行输出的效果。

循环的例子

例子1:输入一个正整数判断它是不是素数。

提示:素数指的是只能被1和自身整除的大于1的整数。

"""
输入一个正整数判断它是不是素数
"""
num = int(input('请输入一个正整数: '))
end = int(num ** 0.5)
is_prime = True
for x in range(2, end + 1):
    if num % x == 0:
        is_prime = False
        break
if is_prime and num != 1:
    print(f'{num}是素数')
else:
    print(f'{num}不是素数')
例子2:输入两个正整数,计算它们的最大公约数和最小公倍数。

提示:两个数的最大公约数是两个数的公共因子中最大的那个数;两个数的最小公倍数则是能够同时被两个数整除的最小的那个数。

"""
输入两个正整数计算它们的最大公约数和最小公倍数
"""
x = int(input('x = '))
y = int(input('y = '))
for factor in range(x, 0, -1):
    if x % factor == 0 and y % factor == 0:
        print(f'{x}和{y}的最大公约数是{factor}')
        print(f'{x}和{y}的最小公倍数是{x * y // factor}')
        break

简单的小结

学会了Python中的分支结构和循环结构,就可以解决很多实际的问题了。如果知道循环的次数,我们通常使用for循环;如果循环次数不能确定,可以用while循环。在循环中还可以使用break来提前结束循环

分支和循环结构的应用

通过前面的学习,对Python中的分支和循环结构已经有了基本的认识。分支和循环结构的重要性不言而喻,它是构造程序逻辑的基础,对于初学者来说也是比较困难的部分。大部分初学者在学习了分支和循环结构后都能理解它们的用途和用法,但是遇到实际问题的时候又无法下手;看懂别人的代码很容易,但是要自己写出同样的代码却又很难。如果你也有同样的问题和困惑,千万不要沮丧,这只是因为你才刚刚开始编程之旅,你的练习量还没有达到让你可以随心所欲的写出代码的程度,只要加强编程练习,这个问题迟早都会解决的。下面我们就为大家讲解一些经典的案例。

经典小案例

例子1:寻找水仙花数。

说明:水仙花数也被称为超完全数字不变数、自恋数、自幂数、阿姆斯特朗数,它是一个3位数,该数字每个位上数字的立方之和正好等于它本身,例如:$ 153=13+53+3^3 $。

这个题目的关键是将一个三位数拆分为个位、十位、百位,这一点利用Python中的//(整除)和%(求模)运算符其实很容易做到,代码如下所示。

"""
找出所有水仙花数
"""
for num in range(100, 1000):
    low = num % 10
    mid = num // 10 % 10
    high = num // 100
    if num == low ** 3 + mid ** 3 + high ** 3:
        print(num)

上面利用//%拆分一个数的小技巧在写代码的时候还是很常用的。我们要将一个不知道有多少位的正整数进行反转,例如将12345变成54321,也可以利用这两个运算来实现,代码如下所示。

"""
正整数的反转

Version: 0.1
Author: 骆昊
"""
num = int(input('num = '))
reversed_num = 0
while num > 0:
    reversed_num = reversed_num * 10 + num % 10
    num //= 10
print(reversed_num)
例子2:百钱百鸡问题。

说明:百钱百鸡是我国古代数学家张丘建在《算经》一书中提出的数学问题:鸡翁一值钱五,鸡母一值钱三,鸡雏三值钱一。百钱买百鸡,问鸡翁、鸡母、鸡雏各几何?翻译成现代文是:公鸡5元一只,母鸡3元一只,小鸡1元三只,用100块钱买一百只鸡,问公鸡、母鸡、小鸡各有多少只?

"""
《百钱百鸡》问题
"""
# 假设公鸡的数量为x,x的取值范围是0到20
for x in range(0, 21):
    # 假设母鸡的数量为y,y的取值范围是0到33
    for y in range(0, 34):
        z = 100 - x - y
        if 5 * x + 3 * y + z // 3 == 100 and z % 3 == 0:
            print(f'公鸡: {x}只, 母鸡: {y}只, 小鸡: {z}只')

上面使用的方法叫做穷举法,也称为暴力搜索法,这种方法通过一项一项的列举备选解决方案中所有可能的候选项并检查每个候选项是否符合问题的描述,最终得到问题的解。这种方法看起来比较笨拙,但对于运算能力非常强大的计算机来说,通常都是一个可行的甚至是不错的选择,只要问题的解存在就能够找到它。

例子3:斐波那契数列。

说明:斐波那契数列(Fibonacci sequence),通常也被称作黄金分割数列,是意大利数学家莱昂纳多·斐波那契(Leonardoda Fibonacci)在《计算之书》中研究在理想假设条件下兔子成长率问题而引入的数列,因此这个数列也常被戏称为“兔子数列”。斐波那契数列的特点是数列的前两个数都是1,从第三个数开始,每个数都是它前面两个数的和,按照这个规律,斐波那契数列的前10个数是:1, 1, 2, 3, 5, 8, 13, 21, 34, 55。斐波那契数列在现代物理、准晶体结构、化学等领域都有直接的应用。

"""
输出斐波那契数列前20个数
"""

a, b = 0, 1
for _ in range(20):
    a, b = b, a + b
    print(a)
例子4:打印100以内的素数。

说明:素数指的是只能被1和自身整除的正整数(不包括1)。

"""
输出100以内的素数
"""
for num in range(2, 100):
    # 假设num是素数
    is_prime = True
    # 在2到num-1之间找num的因子
    for factor in range(2, num):
        # 如果找到了num的因子,num就不是素数
        if num % factor == 0:
            is_prime = False
            break
    # 如果布尔值为True在num是素数
    if is_prime:
        print(num)

简单的小结

还是那句话:分支结构和循环结构非常重要,是构造程序逻辑的基础,一定要通过大量的练习来达到融会贯通

常用数据结构之列表

将一颗色子掷6000次,统计每个点数出现的次数,可以用16均匀分布的随机数来模拟掷色子,然后用6个变量分别记录每个点数出现的次数

import random
f1 = 0
f2 = 0
f3 = 0
f4 = 0
f5 = 0
f6 = 0
for _ in range(6000):
    face = random.randint(1, 6)
    if face == 1:
        f1 += 1
    elif face == 2:
        f2 += 1
    elif face == 3:
        f3 += 1
    elif face == 4:
        f4 += 1
    elif face == 5:
        f5 += 1
    else:
        f6 += 1
print(f'1点出现了{f1}次')
print(f'2点出现了{f2}次')
print(f'3点出现了{f3}次')
print(f'4点出现了{f4}次')
print(f'5点出现了{f5}次')
print(f'6点出现了{f6}次')

上面的代码,它非常的“笨重”和“丑陋”,更可怕的是,如果要统计掷两颗或者更多的色子统计每个点数出现的次数,那就需要定义更多的变量,写更多的分支结构。讲到这里,问:有没有办法用一个变量来保存多个数据,有没有办法用统一的代码对多个数据进行操作?答案是肯定的,在Python中我们可以通过容器类型的变量来保存和操作多个数据,我们首先为大家介绍列表(list)这种新的数据类型。

定义和使用列表

在Python中,列表是由一系元素按特定顺序构成的数据序列,这样就意味着定义一个列表类型的变量,可以保存多个数据,而且允许有重复的数据。跟上一课我们讲到的字符串类型一样,列表也是一种结构化的、非标量类型,操作一个列表类型的变量,除了可以使用运算符还可以使用它的方法。

在Python中,可以使用[]字面量语法来定义列表,列表中的多个元素用逗号进行分隔,代码如下所示。

items1 = [35, 12, 99, 68, 55, 87]
items2 = ['Python', 'Java', 'Go', 'Kotlin']

除此以外,还可以通过Python内置的list函数将其他序列变成列表。准确的说,list并不是一个普通的函数,它是创建列表对象的构造器(后面会讲到对象和构造器这两个概念)。

items1 = list(range(1, 10))
print(items1)    # [1, 2, 3, 4, 5, 6, 7, 8, 9]
items2 = list('hello')
print(items2)    # ['h', 'e', 'l', 'l', 'o']

需要说明的是,列表是一种可变数据类型,也就是说列表可以添加元素、删除元素、更新元素,这一点跟我们上一课讲到的字符串有着鲜明的差别。字符串是一种不可变数据类型,也就是说对字符串做拼接、重复、转换大小写、修剪空格等操作的时候会产生新的字符串,原来的字符串并没有发生任何改变。

列表的运算符

和字符串类型一样,列表也支持拼接、重复、成员运算、索引和切片以及比较运算

items1 = [35, 12, 99, 68, 55, 87]
items2 = [45, 8, 29]

# 列表的拼接
items3 = items1 + items2
print(items3)    # [35, 12, 99, 68, 55, 87, 45, 8, 29]

# 列表的重复
items4 = ['hello'] * 3
print(items4)    # ['hello', 'hello', 'hello']

# 列表的成员运算
print(100 in items3)        # False
print('hello' in items4)    # True

# 获取列表的长度(元素个数)
size = len(items3)
print(size)                 # 9

# 列表的索引
print(items3[0], items3[-size])        # 35 35
items3[-1] = 100
print(items3[size - 1], items3[-1])    # 100 100

# 列表的切片
print(items3[:5])          # [35, 12, 99, 68, 55]
print(items3[4:])          # [55, 87, 45, 8, 100]
print(items3[-5:-7:-1])    # [55, 68]
print(items3[::-2])        # [100, 45, 55, 99, 35]

# 列表的比较运算
items5 = [1, 2, 3, 4]
items6 = list(range(1, 5))
# 两个列表比较相等性比的是对应索引位置上的元素是否相等
print(items5 == items6)    # True
items7 = [3, 2, 1]
# 两个列表比较大小比的是对应索引位置上的元素的大小
print(items5 <= items7)    # True

值得一提的是,由于列表是可变类型,所以通过索引操作既可以获取列表中的元素,也可以更新列表中的元素。对列表做索引操作一样要注意索引越界的问题,对于有N个元素的列表,正向索引的范围是0N-1,负向索引的范围是-1-N,如果超出这个范围,将引发IndexError异常,错误信息为:list index out of range

列表元素的遍历

如果想逐个取出列表中的元素,可以使用for循环的,有以下两种做法。

方法一:

items = ['Python', 'Java', 'Go', 'Kotlin']

for index in range(len(items)):
    print(items[index])

方法二:

items = ['Python', 'Java', 'Go', 'Kotlin']

for item in items:
    print(item)

讲到这里,可以用列表的知识来重构上面“掷色子统计每个点数出现次数”的代码。

import random

counters = [0] * 6
for _ in range(6000):
    face = random.randint(1, 6)
    counters[face - 1] += 1
for face in range(1, 7):
    print(f'{face}点出现了{counters[face - 1]}次')

上面的代码中,用counters列表中的六个元素分别表示1到6的点数出现的次数,最开始的时候六个元素的值都是0。接下来用随机数模拟掷色子,如果摇出1点counters[0]的值加1,如果摇出2点counters[1]的值加1,以此类推。感受一下,代码简单优雅了很多。

列表的方法

和字符串一样,列表类型的方法也很多

添加和删除元素
items = ['Python', 'Java', 'Go', 'Kotlin']

# 使用append方法在列表尾部添加元素
items.append('Swift')
print(items)    # ['Python', 'Java', 'Go', 'Kotlin', 'Swift']
# 使用insert方法在列表指定索引位置插入元素
items.insert(2, 'SQL')
print(items)    # ['Python', 'Java', 'SQL', 'Go', 'Kotlin', 'Swift']

# 删除指定的元素
items.remove('Java')
print(items)    # ['Python', 'SQL', 'Go', 'Kotlin', 'Swift']
# 删除指定索引位置的元素
items.pop(0)
items.pop(len(items) - 1)
print(items)    # ['SQL', 'Go', 'Kotlin']

# 清空列表中的元素
items.clear()
print(items)    # []

提醒,在使用remove方法删除元素时,如果要删除的元素并不在列表中,会引发ValueError异常,错误消息是:list.remove(x): x not in list。在使用pop方法删除元素时,如果索引的值超出了范围,会引发IndexError异常,错误消息是:pop index out of range

从列表中删除元素其实还有一种方式,就是使用Python中的del关键字后面跟要删除的元素,这种做法跟使用pop方法指定索引删除元素没有实质性的区别,但后者会返回删除的元素,前者在性能上略优(del对应字节码指令是DELETE_SUBSCR,而pop对应的字节码指令是CALL_METHODPOP_TOP,不理解就跳过,不用管它!!!)。

items = ['Python', 'Java', 'Go', 'Kotlin']
del items[1]
print(items)    # ['Python', 'Go', 'Kotlin']
元素位置和次数

列表类型的index方法可以查找某个元素在列表中的索引位置;因为列表中允许有重复的元素,所以列表类型提供了count方法来统计一个元素在列表中出现的次数。请看下面的代码。

items = ['Python', 'Java', 'Java', 'Go', 'Kotlin', 'Python']

# 查找元素的索引位置
print(items.index('Python'))       # 0
print(items.index('Python', 2))    # 5
# 注意:虽然列表中有'Java',但是从索引为3这个位置开始后面是没有'Java'的
print(items.index('Java', 3))      # ValueError: 'Java' is not in list

再来看看下面这段代码。

items = ['Python', 'Java', 'Java', 'Go', 'Kotlin', 'Python']

# 查找元素出现的次数
print(items.count('Python'))    # 2
print(items.count('Go'))        # 1
print(items.count('Swfit'))     # 0
元素排序和反转

列表的sort操作可以实现列表元素的排序,而reverse操作可以实现元素的反转,代码如下所示。

items = ['Python', 'Java', 'Go', 'Kotlin', 'Python']

# 排序
items.sort()
print(items)    # ['Go', 'Java', 'Kotlin', 'Python', 'Python']
# 反转
items.reverse()
print(items)    # ['Python', 'Python', 'Kotlin', 'Java', 'Go']

列表的生成式

在Python中,列表还可以通过一种特殊的字面量语法来创建,这种语法叫做生成式。

通过for循环为空列表添加元素。

# 创建一个由1到9的数字构成的列表
items1 = []
for x in range(1, 10):
    items1.append(x)
print(items1)

# 创建一个由'hello world'中除空格和元音字母外的字符构成的列表
items2 = []
for x in 'hello world':
    if x not in ' aeiou':
        items2.append(x)
print(items2)

# 创建一个由个两个字符串中字符的笛卡尔积构成的列表
items3 = []
for x in 'ABC':
    for y in '12':
        items3.append(x + y)
print(items3)

通过生成式创建列表。

# 创建一个由1到9的数字构成的列表
items1 = [x for x in range(1, 10)]
print(items1)    # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# 创建一个由'hello world'中除空格和元音字母外的字符构成的列表
items2 = [x for x in 'hello world' if x not in ' aeiou']
print(items2)    # ['h', 'l', 'l', 'w', 'r', 'l', 'd']

# 创建一个由个两个字符串中字符的笛卡尔积构成的列表
items3 = [x + y for x in 'ABC' for y in '12']
print(items3)    # ['A1', 'A2', 'B1', 'B2', 'C1', 'C2']

下面这种方式不仅代码简单优雅,而且性能也优于上面使用for循环和append方法向空列表中追加元素的方式。可以简单跟大家交待下为什么生成式拥有更好的性能,那是因为Python解释器的字节码指令中有专门针对生成式的指令(LIST_APPEND指令);而for循环是通过方法调用(LOAD_METHODCALL_METHOD指令)的方式为列表添加元素,方法调用本身就是一个相对耗时的操作。对这一点不理解也没有关系,记住“强烈建议用生成式语法来创建列表”这个结论就可以了。

嵌套的列表

Python语言没有限定列表中的元素必须是相同的数据类型,也就是说一个列表中的元素可以任意的数据类型,当然也包括列表。如果列表中的元素又是列表,那么可以称之为嵌套的列表。嵌套的列表可以用来表示表格或数学上的矩阵,例如:保存5个学生3门课程的成绩,可以定义一个保存5个元素的列表保存5个学生的信息,而每个列表元素又是3个元素构成的列表,分别代表3门课程的成绩。但是,一定要注意下面的代码是有问题的。

scores = [[0] * 3] * 5
print(scores)    # [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]

看上去我们好像创建了一个5 * 3的嵌套列表,但实际上当我们录入第一个学生的第一门成绩后,你就会发现问题来了,我们看看下面代码的输出。

# 嵌套的列表需要多次索引操作才能获取元素
scores[0][0] = 95
print(scores)
# [[95, 0, 0], [95, 0, 0], [95, 0, 0], [95, 0, 0], [95, 0, 0]]
scores = [[0] * 3 for _ in range(5)]
scores[0][0] = 95
print(scores)
# [[95, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]

简单的小结

Python中的列表底层是一个可以动态扩容的数组,列表元素在内存中也是连续存储的,所以可以实现随机访问(通过一个有效的索引获取到对应的元素且操作时间与列表元素个数无关)。我们暂时不去触碰这些底层存储细节以及列表每个方法的渐近时间复杂度(执行这个方法耗费的时间跟列表元素个数的关系),等需要的时候再告诉大家。现阶段,大家只需要知道列表是容器,可以保存各种类型的数据可以通过索引操作列表元素,知道这些就足够了。

常用数据结构之元组

Python中容器型的数据类型肯定不止列表一种,接下来我们为大家讲解另一种重要的容器型数据类型,它的名字叫元组(tuple)。

定义和使用元组

在Python中,元组也是多个元素按照一定的顺序构成的序列。元组和列表的不同之处在于,元组是不可变类型,这就意味着元组类型的变量一旦定义,其中的元素不能再添加或删除,而且元素的值也不能进行修改。定义元组通常使用()字面量语法,也建议大家使用这种方式来创建元组。元组类型支持的运算符跟列表是一样。下面的代码演示了元组的定义和运算。

# 定义一个三元组
t1 = (30, 10, 55)
# 定义一个四元组
t2 = ('骆昊', 40, True, '四川成都')

# 查看变量的类型
print(type(t1), type(t2))    # <class 'tuple'> <class 'tuple'>
# 查看元组中元素的数量
print(len(t1), len(t2))      # 3 4

# 通过索引运算获取元组中的元素
print(t1[0], t1[-3])         # 30 30
print(t2[3], t2[-1])         # 四川成都 四川成都

# 循环遍历元组中的元素
for member in t2:
    print(member)

# 成员运算
print(100 in t1)    # False
print(40 in t2)     # True

# 拼接
t3 = t1 + t2
print(t3)           # (30, 10, 55, '骆昊', 40, True, '四川成都')

# 切片
print(t3[::3])      # (30, '骆昊', '四川成都')

# 比较运算
print(t1 == t3)    # False
print(t1 >= t3)    # False
print(t1 < (30, 11, 55))    # True

一个元组中如果有两个元素,我们就称之为二元组;一个元组中如果五个元素,我们就称之为五元组。需要提醒大家注意的是,()表示空元组,但是如果元组中只有一个元素,需要加上一个逗号,否则()就不是代表元组的字面量语法,而是改变运算优先级的圆括号,所以('hello', )(100, )才是一元组,而('hello')(100)只是字符串和整数。我们可以通过下面的代码来加以验证。

# 空元组
a = ()
print(type(a))    # <class 'tuple'>
# 不是元组
b = ('hello')
print(type(b))    # <class 'str'>
c = (100)
print(type(c))    # <class 'int'>
# 一元组
d = ('hello', )
print(type(d))    # <class 'tuple'>
e = (100, )
print(type(e))    # <class 'tuple'>

元组的应用场景

讲到这里,相信大家一定迫切的想知道元组有哪些应用场景,我们给大家举几个例子。

例子1:打包和解包操作。

当我们把多个用逗号分隔的值赋给一个变量时,多个值会打包成一个元组类型;当我们把一个元组赋值给多个变量时,元组会解包成多个值然后分别赋给对应的变量,如下面的代码所示。

# 打包
a = 1, 10, 100
print(type(a), a)    # <class 'tuple'> (1, 10, 100)
# 解包
i, j, k = a
print(i, j, k)       # 1 10 100

在解包时,如果解包出来的元素个数和变量个数不对应,会引发ValueError异常,错误信息为:too many values to unpack(解包的值太多)或not enough values to unpack(解包的值不足)。

a = 1, 10, 100, 1000
# i, j, k = a             # ValueError: too many values to unpack (expected 3)
# i, j, k, l, m, n = a    # ValueError: not enough values to unpack (expected 6, got 4)

有一种解决变量个数少于元素的个数方法,就是使用星号表达式,我们之前讲函数的可变参数时使用过星号表达式。有了星号表达式,我们就可以让一个变量接收多个值,代码如下所示。需要注意的是,用星号表达式修饰的变量会变成一个列表,列表中有0个或多个元素。还有在解包语法中,星号表达式只能出现一次。

a = 1, 10, 100, 1000
i, j, *k = a
print(i, j, k)          # 1 10 [100, 1000]
i, *j, k = a
print(i, j, k)          # 1 [10, 100] 1000
*i, j, k = a
print(i, j, k)          # [1, 10] 100 1000
*i, j = a
print(i, j)             # [1, 10, 100] 1000
i, *j = a
print(i, j)             # 1 [10, 100, 1000]
i, j, k, *l = a
print(i, j, k, l)       # 1 10 100 [1000]
i, j, k, l, *m = a
print(i, j, k, l, m)    # 1 10 100 1000 []

需要说明一点,解包语法对所有的序列都成立,这就意味着对列表以及我们之前讲到的range函数返回的范围序列都可以使用解包语法。大家可以尝试运行下面的代码,看看会出现怎样的结果。

a, b, *c = range(1, 10)
print(a, b, c)
a, b, c = [1, 10, 100]
print(a, b, c)
a, *b, c = 'hello'
print(a, b, c)
例子2:交换两个变量的值。

交换两个变量的值是编程语言中的一个经典案例,在很多编程语言中,交换两个变量的值都需要借助一个中间变量才能做到,如果不用中间变量就需要使用比较晦涩的位运算来实现。在Python中,交换两个变量ab的值只需要使用如下所示的代码。

a, b = b, a

同理,如果要将三个变量abc的值互换,即b赋给ac赋给ba赋给c,也可以如法炮制。

a, b, c = b, c, a

需要说明的是,上面并没有用到打包和解包语法,Python的字节码指令中有ROT_TWOROT_THREE这样的指令可以实现这个操作,效率是非常高的。但是如果有多于三个变量的值要依次互换,这个时候没有直接可用的字节码指令,执行的原理就是我们上面讲解的打包和解包操作。

元组和列表的比较

这里还有一个非常值得探讨的问题,Python中已经有了列表类型,为什么还需要元组这样的类型呢?这个问题对于初学者来说似乎有点困难,不过没有关系,我们先抛出观点,大家可以一边学习一边慢慢体会。

  1. 元组是不可变类型,不可变类型更适合多线程环境,因为它降低了并发访问变量的同步化开销。关于这一点,我们会在后面讲解多线程的时候为大家

  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值