博客

  • 常用数据结构之元组

    常用数据结构之元组

    前面的两节课,我们为大家讲解了 Python 中的列表,它是一种容器型的数据类型,通过列表类型的变量,我们可以保存多个数据并通过循环实现对数据的批量操作。当然,Python 中还有其他容器型的数据类型,接下来我们就为大家讲解另一种容器型的数据类型,它的名字叫元组(tuple)。

    元组的定义和运算

    在 Python 语言中,元组也是多个元素按照一定顺序构成的序列。元组和列表的不同之处在于,元组是不可变类型,这就意味着元组类型的变量一旦定义,其中的元素不能再添加或删除,而且元素的值也不能修改。如果试图修改元组中的元素,将引发TypeError错误,导致程序崩溃。定义元组通常使用形如(x, y, z)的字面量语法,元组类型支持的运算符跟列表是一样的,我们可以看看下面的代码。

    定义一个三元组

    t1 = (35, 12, 98)

    定义一个四元组

    t2 = ('骆昊', 45, True, '四川成都')

    查看变量的类型

    print(type(t1)) # print(type(t2)) #

    查看元组中元素的数量

    print(len(t1)) # 3 print(len(t2)) # 4

    索引运算

    print(t1[0]) # 35 print(t1[2]) # 98 print(t2[-1]) # 四川成都

    切片运算

    print(t2[:2]) # ('骆昊', 45) print(t2[::3]) # ('骆昊', '四川成都')

    循环遍历元组中的元素

    for elem in t1: print(elem)

    成员运算

    print(12 in t1) # True print(99 in t1) # False print('Hao' not in t2) # True

    拼接运算

    t3 = t1 + t2 print(t3) # (35, 12, 98, '骆昊', 45, True, '四川成都')

    比较运算

    print(t1 == t3) # False print(t1 >= t3) # False print(t1 <= (35, 11, 99)) # False

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

    a = ()
    print(type(a))  # 
    b = ('hello')
    print(type(b))  # 
    c = (100)
    print(type(c))  # 
    d = ('hello', )
    print(type(d))  # 
    e = (100, )
    print(type(e))  # 
    

    打包和解包操作

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

    打包操作

    a = 1, 10, 100 print(type(a)) # print(a) # (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)
    

    交换变量的值

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

    a, b = b, a
    

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

    a, b, c = b, c, a
    

    需要说明的是,上面的操作并没有用到打包和解包语法,Python 的字节码指令中有ROT_TWOROT_THREE这样的指令可以直接实现这个操作,效率是非常高的。但是如果有多于三个变量的值要依次互换,这个时候是没有直接可用的字节码指令的,需要通过打包解包的方式来完成变量之间值的交换。

    元组和列表的比较

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

  • 元组是不可变类型,不可变类型更适合多线程环境,因为它降低了并发访问变量的同步化开销。关于这一点,我们会在后面讲解并发编程的时候跟大家一起探讨。
  • 元组是不可变类型,通常不可变类型在创建时间上优于对应的可变类型。我们可以使用timeit模块的timeit函数来看看创建保存相同元素的元组和列表各自花费的时间,timeit函数的number参数表示代码执行的次数。下面的代码中,我们分别创建了保存19的整数的列表和元组,每个操作执行10000000次,统计运行时间。
  •    import timeit
       
       print('%.3f 秒' % timeit.timeit('[1, 2, 3, 4, 5, 6, 7, 8, 9]', number=10000000))
       print('%.3f 秒' % timeit.timeit('(1, 2, 3, 4, 5, 6, 7, 8, 9)', number=10000000))
       

    输出:

       0.635 秒
       0.078 秒
       

    > 说明:上面代码的执行结果因软硬件系统而异,在我目前使用的电脑上,执行10000000次创建列表的操作时间是0.635秒,而执行10000000次创建元组的操作时间是0.078秒,显然创建元组更快且二者时间上有数量级的差别。大家可以在自己的电脑上执行这段代码,把你的执行结果放到评论区,看看谁的电脑更厉害。

    当然,Python 中的元组和列表类型是可以相互转换的,我们可以通过下面的代码来完成该操作。

    infos = ('骆昊', 45, True, '四川成都')
    

    将元组转换成列表

    print(list(infos)) # ['骆昊', 45, True, '四川成都']

    frts = ['apple', 'banana', 'orange']

    将列表转换成元组

    print(tuple(frts)) # ('apple', 'banana', 'orange')

    总结

    列表和元组都是容器型的数据类型,即一个变量可以保存多个数据,而且它们都是按一定顺序组织元素的有序容器。列表是可变数据类型元组是不可变数据类型,所以列表可以添加元素、删除元素、清空元素、排序反转,但这些操作对元组来说是不成立的。列表和元组都可以支持拼接运算成员运算索引运算切片运算等操作,后面我们要讲到的字符串类型也支持这些运算,因为字符串就是字符按一定顺序构成的序列,在这一点上三者并没有什么区别。我们推荐大家使用列表的生成式语法来创建列表,它不仅好用而且效率很高,是 Python 语言中非常有特色的语法。

  • 常用数据结构之列表-2

    常用数据结构之列表-2

    列表的方法

    列表类型的变量拥有很多方法可以帮助我们操作一个列表,假设我们有名为foos的列表,列表有名为bar的方法,那么使用列表方法的语法是:foos.bar(),这是一种通过对象引用调用对象方法的语法。后面我们讲面向对象编程的时候,还会对这种语法进行详细的讲解,这种语法也称为给对象发消息。

    #### 添加和删除元素

    列表是一种可变容器,可变容器指的是我们可以向容器中添加元素、可以从容器移除元素,也可以修改现有容器中的元素。我们可以使用列表的append方法向列表中追加元素,使用insert方法向列表中插入元素。追加指的是将元素添加到列表的末尾,而插入则是在指定的位置添加新元素,大家可以看看下面的代码。

    languages = ['Python', 'Java', 'C++']
    languages.append('JavaScript')
    print(languages)  # ['Python', 'Java', 'C++', 'JavaScript']
    languages.insert(1, 'SQL')
    print(languages)  # ['Python', 'SQL', 'Java', 'C++', 'JavaScript']
    

    我们可以用列表的remove方法从列表中删除指定元素,需要注意的是,如果要删除的元素并不在列表中,会引发ValueError错误导致程序崩溃,所以建议大家在删除元素时,先用之前讲过的成员运算做一个判断。我们还可以使用pop方法从列表中删除元素,pop方法默认删除列表中的最后一个元素,当然也可以给一个位置,删除指定位置的元素。在使用pop方法删除元素时,如果索引的值超出了范围,会引发IndexError异常,导致程序崩溃。除此之外,列表还有一个clear方法,可以清空列表中的元素,代码如下所示。

    languages = ['Python', 'SQL', 'Java', 'C++', 'JavaScript']
    if 'Java' in languages:
        languages.remove('Java')
    if 'Swift' in languages:
        languages.remove('Swift')
    print(languages)  # ['Python', 'SQL', C++', 'JavaScript']
    languages.pop()
    temp = languages.pop(1)
    print(temp)       # SQL
    languages.append(temp)
    print(languages)  # ['Python', C++', 'SQL']
    languages.clear()
    print(languages)  # []
    

    > 说明pop方法删除元素时会得到被删除的元素,上面的代码中,我们将pop方法删除的元素赋值给了名为temp的变量。当然如果你愿意,还可以把这个元素再次加入到列表中,正如上面的代码languages.append(temp)所做的那样。

    这里还有一个小问题,例如languages列表中有多个'Python',那么我们用languages.remove('Python')是删除所有的'Python',还是删除第一个'Python',大家可以先猜一猜,然后再自己动手尝试一下。

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

    items = ['Python', 'Java', 'C++']
    del items[1]
    print(items)  # ['Python', 'C++']
    

    #### 元素位置和频次

    列表的index方法可以查找某个元素在列表中的索引位置,如果找不到指定的元素,index方法会引发ValueError错误;列表的count方法可以统计一个元素在列表中出现的次数,代码如下所示。

    items = ['Python', 'Java', 'Java', 'C++', 'Kotlin', 'Python']
    print(items.index('Python'))     # 0
    

    从索引位置1开始查找'Python'

    print(items.index('Python', 1)) # 5 print(items.count('Python')) # 2 print(items.count('Kotlin')) # 1 print(items.count('Swfit')) # 0

    从索引位置3开始查找'Java'

    print(items.index('Java', 3)) # ValueError: 'Java' is not in list

    #### 元素排序和反转

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

    items = ['Python', 'Java', 'C++', 'Kotlin', 'Swift']
    items.sort()
    print(items)  # ['C++', 'Java', 'Kotlin', 'Python', 'Swift']
    items.reverse()
    print(items)  # ['Swift', 'Python', 'Kotlin', 'Java', 'C++']
    

    列表生成式

    在 Python 中,列表还可以通过一种特殊的字面量语法来创建,这种语法叫做生成式。下面,我们通过例子来说明使用列表生成式创建列表到底有什么好处。

    场景一:创建一个取值范围在199且能被3或者5整除的数字构成的列表。

    items = []
    for i in range(1, 100):
        if i % 3 == 0 or i % 5 == 0:
            items.append(i)
    print(items)
    

    使用列表生成式做同样的事情,代码如下所示。

    items = [i for i in range(1, 100) if i % 3 == 0 or i % 5 == 0]
    print(items)
    

    场景二:有一个整数列表nums1,创建一个新的列表nums2nums2中的元素是nums1中对应元素的平方。

    nums1 = [35, 12, 97, 64, 55]
    nums2 = []
    for num in nums1:
        nums2.append(num ** 2)
    print(nums2)
    

    使用列表生成式做同样的事情,代码如下所示。

    nums1 = [35, 12, 97, 64, 55]
    nums2 = [num ** 2 for num in nums1]
    print(nums2)
    

    场景三: 有一个整数列表nums1,创建一个新的列表nums2,将nums1中大于50的元素放到nums2中。

    nums1 = [35, 12, 97, 64, 55]
    nums2 = []
    for num in nums1:
        if num > 50:
            nums2.append(num)
    print(nums2)
    

    使用列表生成式做同样的事情,代码如下所示。

    nums1 = [35, 12, 97, 64, 55]
    nums2 = [num for num in nums1 if num > 50]
    print(nums2)
    

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

    嵌套列表

    Python 语言没有限定列表中的元素必须是相同的数据类型,也就是说一个列表中的元素可以任意的数据类型,当然也包括列表本身。如果列表中的元素也是列表,那么我们可以称之为嵌套的列表。嵌套的列表可以用来表示表格或数学上的矩阵,例如:我们想保存5个学生3门课程的成绩,可以用如下所示的列表。

    scores = [[95, 83, 92], [80, 75, 82], [92, 97, 90], [80, 78, 69], [65, 66, 89]]
    print(scores[0])
    print(scores[0][1])
    

    对于上面的嵌套列表,每个元素相当于就是一个学生3门课程的成绩,例如[95, 83, 92],而这个列表中的83代表了这个学生某一门课的成绩,如果想访问这个值,可以使用两次索引运算scores[0][1],其中scores[0]可以得到[95, 83, 92]这个列表,再次使用索引运算[1]就可以获得该列表中的第二个元素。

    如果想通过键盘输入的方式来录入5个学生3门课程的成绩并保存在列表中,可以使用如下所示的代码。

    scores = []
    for _ in range(5):
        temp = []
        for _ in range(3):
            score = int(input('请输入: '))
            temp.append(score)
        scores.append(temp)
    print(scores)
    

    如果想通过产生随机数的方式来生成5个学生3门课程的成绩并保存在列表中,我们可以使用列表生成式,代码如下所示。

    import random

    scores = [[random.randrange(60, 101) for _ in range(3)] for _ in range(5)] print(scores)

    > 说明:上面的代码[random.randrange(60, 101) for _ in range(3)] 可以产生由3个随机整数构成的列表,我们把这段代码又放在了另一个列表生成式中作为列表的元素,这样的元素一共生成5个,最终得到了一个嵌套列表。

    列表的应用

    下面我们通过一个双色球随机选号的例子为大家讲解列表的应用。双色球是由中国福利彩票发行管理中心发售的乐透型彩票,每注投注号码由6个红色球和1个蓝色球组成。红色球号码从133中选择,蓝色球号码从116中选择。每注需要选择6个红色球号码和1个蓝色球号码,如下所示。

    > 提示:知乎上有一段对国内各种形式的彩票本质的论述相当精彩,这里分享给大家:“虚构一个不劳而获的人,去忽悠一群想不劳而获的人,最终养活一批真正不劳而获的人”。很多对概率没有概念的人,甚至认为彩票中与不中的概率都是 50%;还有很多人认为如果中奖的概率是 1%,那么买 100 次就一定可以中奖,这些都是非常荒唐的想法。所以,珍爱生命,远离赌博,尤其是你对概率一无所知的情况下

    下面,我们通过 Python 程序来生成一组随机号码。

    """
    双色球随机选号程序

    Author: 骆昊 Version: 1.0 """ import random

    red_balls = list(range(1, 34)) selected_balls = []

    添加6个红色球到选中列表

    for _ in range(6): # 生成随机整数代表选中的红色球的索引位置 index = random.randrange(len(red_balls)) # 将选中的球从红色球列表中移除并添加到选中列表 selected_balls.append(red_balls.pop(index))

    对选中的红色球排序

    selected_balls.sort()

    输出选中的红色球

    for ball in selected_balls: print(f'\033[031m{ball:0>2d}\033[0m', end=' ')

    随机选择1个蓝色球

    blue_ball = random.randrange(1, 17)

    输出选中的蓝色球

    print(f'\033[034m{blue_ball:0>2d}\033[0m')

    > 说明:上面代码中print(f'\033[0m...\033[0m')是为了控制输出内容的颜色,红色球输出成红色,蓝色球输出成蓝色。其中省略号代表我们要输出的内容,\033[0m是一个控制码,表示关闭所有属性,也就是说之前的控制码将会失效,你也可以将其简单的理解为一个定界符,m前面的0表示控制台的显示方式为默认值,0可以省略,1表示高亮,5表示闪烁,7表示反显等。在0m的中间,我们可以写上代表颜色的数字,比如30代表黑色,31代表红色,32代表绿色,33代表黄色,34代表蓝色等。

    我们还可以利用random模块提供的samplechoice函数来简化上面的代码,前者可以实现无放回随机抽样,后者可以实现随机抽取一个元素,修改后的代码如下所示。

    """
    双色球随机选号程序

    Author: 骆昊 Version: 1.1 """ import random

    red_balls = [i for i in range(1, 34)] blue_balls = [i for i in range(1, 17)]

    从红色球列表中随机抽出6个红色球(无放回抽样)

    selected_balls = random.sample(red_balls, 6)

    对选中的红色球排序

    selected_balls.sort()

    输出选中的红色球

    for ball in selected_balls: print(f'\033[031m{ball:0>2d}\033[0m', end=' ')

    从蓝色球列表中随机抽出1个蓝色球

    blue_ball = random.choice(blue_balls)

    输出选中的蓝色球

    print(f'\033[034m{blue_ball:0>2d}\033[0m')

    如果要实现随机生成N注号码,我们只需要将上面的代码放到一个N次的循环中,如下所示。

    """
    双色球随机选号程序

    Author: 骆昊 Version: 1.2 """ import random

    n = int(input('生成几注号码: ')) red_balls = [i for i in range(1, 34)] blue_balls = [i for i in range(1, 17)] for _ in range(n): # 从红色球列表中随机抽出6个红色球(无放回抽样) selected_balls = random.sample(red_balls, 6) # 对选中的红色球排序 selected_balls.sort() # 输出选中的红色球 for ball in selected_balls: print(f'\033[031m{ball:0>2d}\033[0m', end=' ') # 从蓝色球列表中随机抽出1个蓝色球 blue_ball = random.choice(blue_balls) # 输出选中的蓝色球 print(f'\033[034m{blue_ball:0>2d}\033[0m')

    我们在 PyCharm 中运行上面的代码,输入5,运行效果如下图所示。

    这里顺便给大家介绍一个名为 rich 的 Python 三方库,它可以帮助我们用最简单的方式产生最漂亮的输出,你可以在终端中使用 Python 包管理工具 pip 来安装这个三方库,对于使用 PyCharm 的用户,当然要在 PyCharm 的终端窗口使用 pip 命令将 rich 安装到项目的虚拟环境中,命令如下所示。

    pip install rich
    

    如上图所示,rich 安装成功后,我们可以用如下所示的代码来控制输出。

    """
    双色球随机选号程序

    Author: 骆昊 Version: 1.3 """ import random

    from rich.console import Console from rich.table import Table

    创建控制台

    console = Console()

    n = int(input('生成几注号码: ')) red_balls = [i for i in range(1, 34)] blue_balls = [i for i in range(1, 17)]

    创建表格并添加表头

    table = Table(show_header=True) for col_name in ('序号', '红球', '蓝球'): table.add_column(col_name, justify='center')

    for i in range(n): selected_balls = random.sample(red_balls, 6) selected_balls.sort() blue_ball = random.choice(blue_balls) # 向表格中添加行(序号,红色球,蓝色球) table.add_row( str(i + 1), f'[red]{" ".join([f"{ball:0>2d}" for ball in selected_balls])}[/red]', f'[blue]{blue_ball:0>2d}[/blue]' )

    通过控制台输出表格

    console.print(table)

    > 说明:上面代码第 31 行使用了列表生成式语法将红色球号码处理成字符串并保存在一个列表中," ".join(...])是将列表中的多个字符串用空格拼接成一个完整的字符串,如果不理解可以先放放。字符串中的[red]...[/red]用来设置输出颜色为红色,第 32 行的[blue]...[/blue]用来设置输出颜色为蓝色。更多关于 rich 库的知识,可以参考[官方文档

    最终的输出如下图所示,看着这样的输出,是不是心情更美好了一些。

    总结

    Python 中的列表底层是一个可以动态扩容的数组,列表元素在计算机内存中是连续存储的,所以可以实现随机访问(通过一个有效的索引获取对应的元素且操作时间与列表元素个数无关)。我们可以暂时不去触碰这些底层的存储细节,也不需要大家理解列表每个方法的渐近时间复杂度(执行方法耗费的时间跟列表元素个数之间的关系),大家先学会用列表解决工作中的问题,我想这一点更为重要。

  • 常用数据结构之列表-1

    常用数据结构之列表-1

    在开始本节课的内容之前,我们先给大家一个编程任务,将一颗色子掷 6000 次,统计每种点数出现的次数。这个任务对大家来说应该是非常简单的,我们可以用 1 到 6 均匀分布的随机数来模拟掷色子,然后用 6 个变量分别记录每个点数出现的次数,相信通过前面的学习,大家都能比较顺利的写出下面的代码。

    """
    将一颗色子掷6000次,统计每种点数出现的次数

    Author: 骆昊 Version: 1.0 """ import random

    f1 = 0 f2 = 0 f3 = 0 f4 = 0 f5 = 0 f6 = 0 for _ in range(6000): face = random.randrange(1, 7) 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, 35, 87]
    items2 = ['Python', 'Java', 'Go', 'Kotlin']
    items3 = [100, 12.3, 'Python', True]
    print(items1)  # [35, 12, 99, 68, 55, 35, 87]
    print(items2)  # ['Python', 'Java', 'Go', 'Kotlin']
    print(items3)  # [100, 12.3, 'Python', True]
    

    > 说明:列表中可以有重复元素,例如items1中的35;列表中可以有不同类型的元素,例如items3中有int类型、float类型、str类型和bool类型的元素,但是我们通常并不建议将不同类型的元素放在同一个列表中,主要是操作起来极为不便。

    我们可以使用type函数来查看变量的类型,有兴趣的小伙伴可以自行查看上面的变量items1到底是什么类型。因为列表可以保存多个元素,它是一种容器型的数据类型,所以我们在给列表类型的变量起名字时,变量名通常用复数形式的单词。

    除此以外,还可以通过 Python 内置的list函数将其他序列变成列表。准确的说,list并不是一个普通的函数,它是创建列表对象的构造器,后面的课程会为大家介绍对象和构造器这些概念。

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

    > 说明range(1, 10)会产生19的整数序列,给到list构造器中,会创建出由19的整数构成的列表。字符串是字符构成的序列,上面的list('hello')用字符串hello的字符作为列表元素,创建了列表对象。

    列表的运算

    我们可以使用+运算符实现两个列表的拼接,拼接运算会将两个列表中的元素连接起来放到一个列表中,代码如下所示。

    items5 = [35, 12, 99, 45, 66]
    items6 = [45, 58, 29]
    items7 = ['Python', 'Java', 'JavaScript']
    print(items5 + items6)  # [35, 12, 99, 45, 66, 45, 58, 29]
    print(items6 + items7)  # [45, 58, 29, 'Python', 'Java', 'JavaScript']
    items5 += items6
    print(items5)  # [35, 12, 99, 45, 66, 45, 58, 29]
    

    我们可以使用运算符实现列表的重复运算,运算符会将列表元素重复指定的次数,我们在上面的代码中增加两行,如下所示。

    print(items6 * 3)  # [45, 58, 29, 45, 58, 29, 45, 58, 29]
    print(items7 * 2)  # ['Python', 'Java', 'JavaScript', 'Python', 'Java', 'JavaScript']
    

    我们可以使用innot in运算符判断一个元素在不在列表中,我们在上面的代码代码中再增加两行,如下所示。

    print(29 in items6)  # True
    print(99 in items6)  # False
    print('C++' not in items7)     # True
    print('Python' not in items7)  # False
    

    由于列表中有多个元素,而且元素是按照特定顺序放在列表中的,所以当我们想操作列表中的某个元素时,可以使用[]运算符,通过在[]中指定元素的位置来访问该元素,这种运算称为索引运算。需要说明的是,[]的元素位置可以是0N - 1的整数,也可以是-1-N的整数,分别称为正向索引和反向索引,其中N代表列表元素的个数。对于正向索引,[0]可以访问列表中的第一个元素,[N - 1]可以访问最后一个元素;对于反向索引,[-1]可以访问列表中的最后一个元素,[-N]可以访问第一个元素,代码如下所示。

    items8 = ['apple', 'waxberry', 'pitaya', 'peach', 'watermelon']
    print(items8[0])   # apple
    print(items8[2])   # pitaya
    print(items8[4])   # watermelon
    items8[2] = 'durian'
    print(items8)      # ['apple', 'waxberry', 'durian', 'peach', 'watermelon']
    print(items8[-5])  # 'apple'
    print(items8[-4])  # 'waxberry'
    print(items8[-1])  # watermelon
    items8[-4] = 'strawberry'
    print(items8)      # ['apple', 'strawberry', 'durian', 'peach', 'watermelon']
    

    在使用索引运算的时候要避免出现索引越界的情况,对于上面的items8,如果我们访问items8[5]items8[-6],就会引发IndexError错误,导致程序崩溃,对应的错误信息是:list index out of range,翻译成中文就是“数组索引超出范围”。因为对于只有五个元素的列表items8,有效的正向索引是04,有效的反向索引是-1-5

    如果希望一次性访问列表中的多个元素,我们可以使用切片运算。切片运算是形如[start:end:stride]的运算符,其中start代表访问列表元素的起始位置,end代表访问列表元素的终止位置(终止位置的元素无法访问),而stride则代表了跨度,简单的说就是位置的增量,比如我们访问的第一个元素在start位置,那么第二个元素就在start + stride位置,当然start + stride要小于end。我们给上面的代码增加下面的语句,来使用切片运算符访问列表元素。

    print(items8[1:3:1])     # ['strawberry', 'durian']
    print(items8[0:3:1])     # ['apple', 'strawberry', 'durian']
    print(items8[0:5:2])     # ['apple', 'durian', 'watermelon']
    print(items8[-4:-2:1])   # ['strawberry', 'durian']
    print(items8[-2:-6:-1])  # ['peach', 'durian', 'strawberry', 'apple']
    

    > 提醒:大家可以看看上面代码中的最后一行,想一想当跨度为负数时,切片运算是如何访问元素的。

    如果start值等于0,那么在使用切片运算符时可以将其省略;如果end值等于NN代表列表元素的个数,那么在使用切片运算符时可以将其省略;如果stride值等于1,那么在使用切片运算符时也可以将其省略。所以,下面的代码跟上面的代码作用完全相同。

    print(items8[1:3])     # ['strawberry', 'durian']
    print(items8[:3:1])    # ['apple', 'strawberry', 'durian']
    print(items8[::2])     # ['apple', 'durian', 'watermelon']
    print(items8[-4:-2])   # ['strawberry', 'durian']
    print(items8[-2::-1])  # ['peach', 'durian', 'strawberry', 'apple']
    

    事实上,我们还可以通过切片操作修改列表中的元素,例如我们给上面的代码再加上一行,大家可以看看这里的输出。

    items8[1:3] = ['x', 'o']
    print(items8)  # ['apple', 'x', 'o', 'peach', 'watermelon']
    

    两个列表还可以做关系运算,我们可以比较两个列表是否相等,也可以给两个列表比大小,代码如下所示。

    nums1 = [1, 2, 3, 4]
    nums2 = list(range(1, 5))
    nums3 = [3, 2, 1]
    print(nums1 == nums2)  # True
    print(nums1 != nums2)  # False
    print(nums1 <= nums3)  # True
    print(nums2 >= nums3)  # False
    

    > 说明:上面的nums1nums2对应元素完全相同,所以==运算的结果是Truenums2nums3的比较,由于nums2的第一个元素1小于nums3的第一个元素3,所以nums2 >= nums3比较的结果是False。两个列表的关系运算在实际工作并不那么常用,如果实在不理解就跳过吧,不用纠结。

    元素的遍历

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

    方法一:在循环结构中通过索引运算,遍历列表元素。

    languages = ['Python', 'Java', 'C++', 'Kotlin']
    for index in range(len(languages)):
        print(languages[index])
    

    输出:

    Python
    Java
    C++
    Kotlin
    

    > 说明:上面的len函数可以获取列表元素的个数N,而range(N)则构成了从0N-1的范围,刚好可以作为列表元素的索引。

    方法二:直接对列表做循环,循环变量就是列表元素的代表。

    languages = ['Python', 'Java', 'C++', 'Kotlin']
    for language in languages:
        print(language)
    

    输出:

    Python
    Java
    C++
    Kotlin
    

    总结

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

    """
    将一颗色子掷6000次,统计每种点数出现的次数

    Author: 骆昊 Version: 1.1 """ import random

    counters = [0] * 6

    模拟掷色子记录每种点数出现的次数

    for _ in range(6000): face = random.randrange(1, 7) counters[face - 1] += 1

    输出每种点数出现的次数

    for face in range(1, 7): print(f'{face}点出现了{counters[face - 1]}次')

    上面的代码中,我们用counters列表中的六个元素分别表示 1 到 6 点出现的次数,最开始的时候六个元素的值都是 0。接下来,我们用 1 到 6 均匀分布的随机数模拟掷色子,如果摇出 1 点,counters[0]的值加 1,如果摇出 2 点,counters[1]的值加 1,以此类推。大家感受一下,由于使用了列表类型加上循环结构,我们对数据的处理是批量性的,这就使得修改后的代码比之前的代码要简单优雅得多。

  • 分支和循环结构实战

    分支和循环结构实战

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

    例子1:100以内的素数

    > 说明:素数指的是只能被 1 和自身整除的正整数(不包括 1),之前我们写过判断素数的代码,这里相当于是一个升级版本。

    """
    输出100以内的素数

    Version: 1.0 Author: 骆昊 """ for num in range(2, 100): is_prime = True for i in range(2, int(num ** 0.5) + 1): if num % i == 0: is_prime = False break if is_prime: print(num)

    例子2:斐波那契数列

    要求:输出斐波那契数列中的前 20 个数。

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

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

    Version: 1.0 Author: 骆昊 """

    a, b = 0, 1 for _ in range(20): a, b = b, a + b print(a)

    > 说明:上面循环中的a, b = b, a + b表示将变量b的值赋给a,把a + b的值赋给b。通过这个递推公式,我们可以依次获得斐波那契数列中的数。

    例子3:寻找水仙花数

    要求:找出 100 到 999 范围内的所有水仙花数。

    > 提示:在数论中,水仙花数(narcissistic number)也被称为超完全数字不变数、自恋数、自幂数、阿姆斯特朗数,它是一个 $\small{N}$ 位非负整数,其各位数字的 $\small{N}$ 次方和刚好等于该数本身,例如: $\small{153 = 1^{3} + 5^{3} + 3^{3}}$ ,所以 153 是一个水仙花数; $\small{1634 = 1^{4} + 6^{4} + 3^{4} + 4^{4}}$ ,所以 1634 也是一个水仙花数。对于三位数,解题的关键是将它拆分为个位、十位、百位,再判断是否满足水仙花数的要求,这一点利用 Python 中的//%运算符其实很容易做到。

    """
    找出100到999范围内的水仙花数

    Version: 1.0 Author: 骆昊 """ 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)

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

    """
    正整数的反转

    Version: 1.0 Author: 骆昊 """ num = int(input('num = ')) reversed_num = 0 while num > 0: reversed_num = reversed_num * 10 + num % 10 num //= 10 print(reversed_num)

    例子4:百钱百鸡问题

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

    """
    百钱百鸡问题

    Version: 1.0 Author: 骆昊 """ for x in range(0, 21): for y in range(0, 34): for z in range(0, 100, 3): if x + y + z == 100 and 5 x + 3 y + z // 3 == 100: print(f'公鸡: {x}只, 母鸡: {y}只, 小鸡: {z}只')

    上面使用的方法叫做穷举法,也称为暴力搜索法,这种方法通过一项一项的列举备选解决方案中所有可能的候选项,并检查每个候选项是否符合问题的描述,最终得到问题的解。上面的代码中,我们使用了嵌套的循环结构,假设公鸡有x只,显然x的取值范围是 0 到 20,假设母鸡有y只,它的取值范围是 0 到 33,假设小鸡有z只,它的取值范围是 0 到 99 且取值是 3 的倍数。这样,我们设置好 100 只鸡的条件x + y + z == 100,设置好 100 块钱的条件5 x + 3 y + z // 3 == 100,当两个条件同时满足时,就是问题的正确答案,我们用print函数输出它。这种方法看起来比较笨拙,但对于运算能力非常强大的计算机来说,通常都是一个可行的甚至是不错的选择,只要问题的解存在就能够找到它。

    事实上,上面的代码还有更好的写法,既然我们已经假设公鸡有x只,母鸡有y只,那么小鸡的数量就应该是100 - x - y,这样减少一个条件,我们就可以把上面三层嵌套的for-in循环改写为两层嵌套的for-in循环。循环次数减少了,代码的执行效率就有了显著的提升,如下所示。

    """
    百钱百鸡问题

    Version: 1.1 Author: 骆昊 """ for x in range(0, 21): for y in range(0, 34): z = 100 - x - y if z % 3 == 0 and 5 x + 3 y + z // 3 == 100: print(f'公鸡: {x}只, 母鸡: {y}只, 小鸡: {z}只')

    > 说明:上面代码中的z % 3 == 0是为了确保小鸡的数量是 3 的倍数。

    例子5:CRAPS赌博游戏

    > 说明:CRAPS又称花旗骰,是美国拉斯维加斯非常受欢迎的一种的桌上赌博游戏。该游戏使用两粒骰子,玩家通过摇两粒骰子获得点数进行游戏。简化后的规则是:玩家第一次摇骰子如果摇出了 7 点或 11 点,玩家胜;玩家第一次如果摇出 2 点、3 点或 12 点,庄家胜;玩家如果摇出其他点数则游戏继续,玩家重新摇骰子,如果玩家摇出了 7 点,庄家胜;如果玩家摇出了第一次摇的点数,玩家胜;其他点数玩家继续摇骰子,直到分出胜负。为了增加代码的趣味性,我们设定游戏开始时玩家有 1000 元的赌注,每局游戏开始之前,玩家先下注,如果玩家获胜就可以获得对应下注金额的奖励,如果庄家获胜,玩家就会输掉自己下注的金额。游戏结束的条件是玩家破产(输光所有的赌注)。

    """
    Craps赌博游戏

    Version: 1.0 Author: 骆昊 """ import random

    money = 1000 while money > 0: print(f'你的总资产为: {money}元') # 下注金额必须大于0且小于等于玩家的总资产 while True: debt = int(input('请下注: ')) if 0 < debt <= money: break # 用两个1到6均匀分布的随机数相加模拟摇两颗色子得到的点数 first_point = random.randrange(1, 7) + random.randrange(1, 7) print(f'\n玩家摇出了{first_point}点') if first_point == 7 or first_point == 11: print('玩家胜!\n') money += debt elif first_point == 2 or first_point == 3 or first_point == 12: print('庄家胜!\n') money -= debt else: # 如果第一次摇色子没有分出胜负,玩家需要重新摇色子 while True: current_point = random.randrange(1, 7) + random.randrange(1, 7) print(f'玩家摇出了{current_point}点') if current_point == 7: print('庄家胜!\n') money -= debt break elif current_point == first_point: print('玩家胜!\n') money += debt break print('你破产了, 游戏结束!')

    总结

    分支结构和循环结构都非常重要,是构造程序逻辑的基础,一定要通过大量的练习来达到融会贯通。我们可以用上面讲的花旗骰游戏作为一个标准,如果你能够很顺利的完成这段代码,那么分支结构和循环结构的知识你就已经很好的掌握了。

  • 循环结构

    循环结构

    我们在写程序的时候,极有可能遇到需要重复执行某条或某些指令的场景,例如我们需要每隔1秒钟在屏幕上输出一次“hello, world”并持续输出一个小时。如下所示的代码可以完成一次这样的操作,如果要持续输出一个小时,我们就需要把这段代码写3600遍,你愿意这么做吗?

    import time

    print('hello, world') time.sleep(1)

    > 说明:Python 内置time模块的sleep函数可以实现程序的休眠,参数1表示休眠的秒数,可以使用intfloat类型,例如0.05表示50毫秒。关于函数和模块的知识,我们在后续的课程中会为大家讲解。

    为了应对上述场景中的问题,我们可以在 Python 程序中使用循环结构。所谓循环结构,就是程序中控制某条或某些指令重复执行的结构。有了这样的结构,刚才的代码就不需要写 3600 遍,而是写一遍然后放到循环结构中重复 3600 次。在 Python 语言中构造循环结构有两种做法,一种是for-in循环,另一种是while循环。

    for-in循环

    如果明确知道循环执行的次数,我们推荐使用for-in循环,例如上面说的那个重复 3600 次的场景,我们可以用下面的代码来实现。 注意,被for-in循环控制的代码块也是通过缩进的方式来构造,这一点跟分支结构中构造代码块的做法是一样的。我们被for-in循环控制的代码块称为循环体,通常循环体中的语句会根据循环的设定被重复执行。

    """
    每隔1秒输出一次“hello, world”,持续1小时

    Author: 骆昊 Version: 1.0 """ import time

    for i in range(3600): print('hello, world') time.sleep(1)

    需要说明的是,上面代码中的range(3600)可以构造出一个从03599的范围,当我们把这样一个范围放到for-in循环中,就可以通过前面的循环变量i依次取出从03599的整数,这就会让for-in代码块中的语句重复 3600 次。当然,range的用法非常灵活,下面的清单给出了使用range函数的例子:

    • range(101):可以用来产生0100范围的整数,需要注意的是取不到101
    • range(1, 101):可以用来产生1100范围的整数,相当于是左闭右开的设定,即[1, 101)
    • range(1, 101, 2):可以用来产生1100的奇数,其中2是步长(跨度),即每次递增的值,101取不到。
    • range(100, 0, -2):可以用来产生1001的偶数,其中-2是步长(跨度),即每次递减的值,0取不到。

    大家可能已经注意到了,上面代码的输出操作和休眠操作都没有用到循环变量i,对于不需要用到循环变量的for-in循环结构,按照 Python 的编程惯例,我们通常把循环变量命名为_,修改后的代码如下所示。虽然结果没什么变化,但是这样写显得你更加专业,逼格瞬间拉满。

    """
    每隔1秒输出一次“hello, world”,持续1小时

    Author: 骆昊 Version: 1.1 """ import time

    for _ in range(3600): print('hello, world') time.sleep(1)

    上面的代码要执行一个小时,如果想提前结束程序,在 PyCharm 中可以点击运行窗口上的停止按钮,如下图所示。如果在命令提示符或终端中运行代码,可以使用组合键ctrl+c来终止程序。

    下面,我们用for-in循环实现从 1 到 100 的整数求和,即 $\small{\sum_{n=1}^{100}{n}}$ 。

    """
    从1到100的整数求和

    Version: 1.0 Author: 骆昊 """ total = 0 for i in range(1, 101): total += i print(total)

    上面的代码中,变量total的作用是保存累加的结果。在循环的过程中,循环变量i的值会从 1 一直取到 100。对于变量i的每个取值,我们都执行了total += i,它相当于total = total + i,这条语句实现了累加操作。所以,当循环结束,我们输出变量total 的值,它的值就是从 1 累加到 100 的结果 5050。注意,print(total)这条语句前是没有缩进的,它不受for-in循环的控制,不会重复执行。

    我们再来写一个从1到100偶数求和的代码,如下所示。

    """
    从1到100的偶数求和

    Version: 1.0 Author: 骆昊 """ total = 0 for i in range(1, 101): if i % 2 == 0: total += i print(total)

    > 说明:上面的for-in循环中我们使用了分支结构来判断循环变量i是不是偶数。

    我们也可以修改range函数的参数,将起始值和跨度修改为2,用更为简单的代码实现从 1 到 100 的偶数求和。

    """
    从1到100的偶数求和

    Version: 1.1 Author: 骆昊 """ total = 0 for i in range(2, 101, 2): total += i print(total)

    当然, 更为简单的办法是使用 Python 内置的sum函数求和,这样我们连循环结构都省掉了。

    """
    从1到100的偶数求和

    Version: 1.2 Author: 骆昊 """ print(sum(range(2, 101, 2)))

    while循环

    如果要构造循环结构但是又不能确定循环重复的次数,我们推荐使用while循环。while循环通过布尔值或能产生布尔值的表达式来控制循环,当布尔值或表达式的值为True时,循环体(while语句下方保持相同缩进的代码块)中的语句就会被重复执行,当表达式的值为False时,结束循环。

    下面我们用while循环来实现从 1 到 100 的整数求和,代码如下所示。

    """
    从1到100的整数求和

    Version: 1.1 Author: 骆昊 """ total = 0 i = 1 while i <= 100: total += i i += 1 print(total)

    相较于for-in循环,上面的代码我们在循环开始前增加了一个变量i,我们使用这个变量来控制循环,所以while后面给出了i <= 100的条件。在while的循环体中,我们除了做累加,还需要让变量i的值递增,所以我们添加了i += 1这条语句,这样i的值就会依次取到1、2、3、……,直到 101。当i变成 101 时,while循环的条件不再成立,代码会离开while循环,此时我们输出变量total的值,它就是从 1 到 100 求和的结果 5050。

    如果要实现从 1 到 100 的偶数求和,我们可以对上面的代码稍作修改。

    """
    从1到100的偶数求和

    Version: 1.3 Author: 骆昊 """ total = 0 i = 2 while i <= 100: total += i i += 2 print(total)

    break和continue

    如果把while循环的条件设置为True,即让条件恒成立会怎么样呢?我们看看下面的代码,还是使用while构造循环结构,计算 1 到 100 的偶数和。

    """
    从1到100的偶数求和

    Version: 1.4 Author: 骆昊 """ total = 0 i = 2 while True: total += i i += 2 if i > 100: break print(total)

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

    """
    从1到100的偶数求和

    Version: 1.5 Author: 骆昊 """ total = 0 for i in range(1, 101): if i % 2 != 0: continue total += i print(total)

    > 说明:上面的代码使用continue关键字跳过了i是奇数的情况,只有在i是偶数的前提下,才会执行到total += i

    嵌套的循环结构

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

    """
    打印乘法口诀表

    Version: 1.0 Author: 骆昊 """ for i in range(1, 10): for j in range(1, i + 1): print(f'{i}×{j}={i * j}', end='\t') print()

    上面的代码中,for-in循环的循环体中又用到了for-in循环,外面的循环用来控制产生i行的输出,而里面的循环则用来控制在一行中输出j列。显然,里面的for-in循环的输出就是乘法口诀表中的一整行。所以在里面的循环完成时,我们用了一个print()来实现换行的效果,让下面的输出重新另起一行,最后的输出如下所示。

    1×1=1	
    2×1=2	2×2=4	
    3×1=3	3×2=6	3×3=9	
    4×1=4	4×2=8	4×3=12	4×4=16	
    5×1=5	5×2=10	5×3=15	5×4=20	5×5=25	
    6×1=6	6×2=12	6×3=18	6×4=24	6×5=30	6×6=36	
    7×1=7	7×2=14	7×3=21	7×4=28	7×5=35	7×6=42	7×7=49	
    8×1=8	8×2=16	8×3=24	8×4=32	8×5=40	8×6=48	8×7=56	8×8=64	
    9×1=9	9×2=18	9×3=27	9×4=36	9×5=45	9×6=54	9×7=63	9×8=72	9×9=81
    

    循环结构的应用

    #### 例子1:判断素数

    要求:输入一个大于 1 的正整数,判断它是不是素数。

    > 提示:素数指的是只能被 1 和自身整除的大于 1 的整数。例如对于正整数 $\small{n}$,我们可以通过在 2 到 $\small{n - 1}$ 之间寻找有没有 $\small{n}$ 的因子,来判断它到底是不是一个素数。当然,循环不用从 2 开始到 $\small{n - 1}$ 结束,因为对于大于 1 的正整数,因子应该都是成对出现的,所以循环到 $\small{\sqrt{n}}$ 就可以结束了。

    """
    输入一个大于1的正整数判断它是不是素数

    Version: 1.0 Author: 骆昊 """ num = int(input('请输入一个正整数: ')) end = int(num ** 0.5) is_prime = True for i in range(2, end + 1): if num % i == 0: is_prime = False break if is_prime: print(f'{num}是素数') else: print(f'{num}不是素数')

    > 说明:上面的代码中我们用了布尔型的变量is_prime,我们先将它赋值为True,假设num是一个素数;接下来,我们在 2 到num ** 0.5的范围寻找num的因子,如果找到了num的因子,那么它一定不是素数,此时我们将is_prime赋值为False,同时使用break关键字终止循环结构;最后,我们根据is_prime的值是True还是False来给出不同的输出。

    #### 例子2:最大公约数

    要求:输入两个大于 0 的正整数,求两个数的最大公约数。

    > 提示:两个数的最大公约数是两个数的公共因子中最大的那个数。

    """
    输入两个正整数求它们的最大公约数

    Version: 1.0 Author: 骆昊 """ x = int(input('x = ')) y = int(input('y = ')) for i in range(x, 0, -1): if x % i == 0 and y % i == 0: print(f'最大公约数: {i}') break

    > 说明:上面代码中for-in循环的循环变量值是从大到小的,这样我们找到的能够同时整除xy的因子i,就是xy的最大公约数,此时我们用break终止循环。如果xy互质,那么循环会执行到i变成 1,因为 1 是所有正整数的因子,此时xy的最大公约数就是 1。

    用上面代码的找最大公约数在执行效率是有问题的。假如x的值是999999999998y的值是999999999999,很显然两个数是互质的,最大公约数为 1。但是我们使用上面的代码,循环会重复999999999998次,这通常是难以接受的。我们可以使用欧几里得算法来找最大公约数,它能帮我们更快的得到想要的结果,代码如下所示。

    """
    输入两个正整数求它们的最大公约数

    Version: 1.1 Author: 骆昊 """ x = int(input('x = ')) y = int(input('y = ')) while y % x != 0: x, y = y % x, x print(f'最大公约数: {x}')

    > 说明:解决问题的方法和步骤可以称之为算法,对于同一个问题,我们可以设计出不同的算法,不同的算法在存储空间的占用和执行效率上都会存在差别,而这些差别就代表了算法的优劣。大家可以对比上面的两段待会,体会一下为什么我们说欧几里得算法是更好的选择。上面的代码中x, y = y % x, x语句表示将y % x的值赋给x,将x 原来的值赋给y

    #### 例子3:猜数字游戏

    要求:计算机出一个 1 到 100 之间的随机数,玩家输入自己猜的数字,计算机给出对应的提示信息“大一点”、“小一点”或“猜对了”,如果玩家猜中了数字,计算机提示用户一共猜了多少次,游戏结束,否则游戏继续。

    """
    猜数字小游戏

    Version: 1.0 Author: 骆昊 """ import random

    answer = random.randrange(1, 101) counter = 0 while True: counter += 1 num = int(input('请输入: ')) if num < answer: print('大一点.') elif num > answer: print('小一点.') else: print('猜对了.') break print(f'你一共猜了{counter}次.')

    > 说明:上面的代码使用import random导入了 Python 标准库的random模块,该模块的randrange函数帮助我们生成了 1 到 100 范围的随机数(不包括 100)。变量counter用来记录循环执行的次数,也就是用户一共猜了几次,每循环一次counter的值都会加 1。

    总结

    学会了 Python 中的分支结构和循环结构,我们就可以解决很多实际的问题了。通过这节课的学习,大家应该已经知道了可以用forwhile关键字来构造循环结构。如果事先知道循环结构重复的次数,我们通常使用for循环如果循环结构的重复次数不能确定,可以用while循环。此外,我们可以在循环结构中使用break终止循环也可以在循环结构中使用continue关键字让循环结构直接进入下一轮次

  • 分支结构

    分支结构

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

    使用if和else构造分支结构

    在 Python 中,构造分支结构最常用的是ifelifelse三个关键字。所谓关键字就是编程语言中有特殊含义的单词,很显然你不能够使用它作为变量名。当然,我们并不是每次构造分支结构都会把三个关键字全部用上,我们通过例子加以说明。例如我们要写一个身体质量指数(BMI)的计算器。身体质量质数也叫体质指数,是国际上常用的衡量人体胖瘦程度以及是否健康的一个指标,计算公式如下所示。通常认为 $\small{18.5 \le BMI < 24}$ 是正常范围, $\small{BMI < 18.5}$ 说明体重过轻, $\small{BMI \ge 24}$ 说明体重过重, $\small{BMI \ge 27}$ 就属于肥胖的范畴了。

    $$
    BMI = \frac{体重}{身高^{2}}
    $$

    > 说明:上面公式中的体重以千克(kg)为单位,身高以米(m)为单位。

    """
    BMI计算器

    Version: 1.0 Author: 骆昊 """ height = float(input('身高(cm):')) weight = float(input('体重(kg):')) bmi = weight / (height / 100) ** 2 print(f'{bmi = :.1f}') if 18.5 <= bmi < 24: print('你的身材很棒!')

    > 提示if语句的最后面有一个:,它是用英文输入法输入的冒号;程序中输入的'"=()等特殊字符,都是在英文输入法状态下输入的,这一点之前已经提醒过大家了。很多初学者经常会忽略这一点,等到执行代码时,就会看到一大堆错误提示。当然,认真读一下错误提示还是很容易发现哪里出了问题,但是强烈建议大家在写代码的时候切换到英文输入法,这样可以避免很多不必要的麻烦。

    上面的代码中,我们在计算和输出 BMI 之后,加上了一段分支结构,如果满足 $\small{18.5 \le BMI < 24}$ ,程序会输出“你的身材很棒!”,但是如果不满足条件,这段输出就没有了。这就是刚才提到的,代码可以有不同的执行路径,有些代码不一定会执行到。我们在if关键字的后面给出了一个表达式18.5 <= bmi < 24,之前我们说过,关系运算会产生布尔值,如果if后面的布尔值为True,那么if语句下方,有四个空格缩进的print('你的身材很棒!')就会被执行。我们先输入几组数据运行上面的代码,如下所示。

    第一组输入:

    身高(cm):175
    体重(kg):68
    bmi = 22.2
    你的身材很棒!
    

    第二组输入:

    身高(cm):175
    体重(kg):95
    bmi = 31.0
    

    第三组输入:

    身高(cm):175
    体重(kg):50
    bmi = 16.3
    

    只有第一组输入的身高和体重计算出的 BMI 在 18.5 到 24 这个范围值内,所以触发了if条件,输出了“你的身材很棒”。需要说明的是,不同于 C、C++、Java 等编程语言,Python 中没有用花括号来构造代码块而是使用缩进的方式来表示代码的层次结构,如果if条件成立的情况下需要执行多条语句,只要保持多条语句具有相同的缩进就可以了。换句话说,若干行连续的语句如果保持了相同的缩进,那么它们就属于同一个代码块,相当于是一个执行的整体。缩进可以使用任意数量的空格,但通常使用4个空格,强烈建议大家不要使用制表键(Tab键)来缩进代码,如果你已经习惯了这么做,可以设置你的代码编辑器自动将 1 个制表键变成 4 个空格,很多代码编辑器都支持这项功能,PyCharm 中默认也是这样设定的。还有一点,在 C、C++、Java 等编程语言中,18.5 <= bmi < 24要写成两个条件bmi >= 18.5bmi < 24,然后把两个条件用与运算符连接起来,Python 中也可以这么做,例如刚才的if语句也可以写成if bmi >= 18.5 and bmi < 24:,但是没有必要,难道if 18.5 <= bmi < 24:这个写法它不香吗?下面用 Java 代码做了同样的事情,看不懂 Java 代码没关系,感受一下它和 Python 语法的区别就可以了。

    import java.util.Scanner;

    class Test {

    public static void main(String[] args) { try (Scanner sc = new Scanner(System.in)) { System.out.print("身高(cm): "); double height = sc.nextDouble(); System.out.print("体重(kg): "); double weight = sc.nextDouble(); double bmi = weight / Math.pow(height / 100, 2); System.out.printf("bmi = %.1f\n", bmi); if (bmi >= 18.5 && bmi < 24) { System.out.println("你的身材很棒!"); } } } }

    > 说明:上面就是 BMI 计算器 1.0 版本对应的 Java 代码,很多人喜欢 Python 语言不是没有道理的,通常它都能用更少的代码解决同样的问题。

    接下来,我们对上面的代码稍作修改,在 BMI 不满足 $\small{18.5 \le BMI < 24}$ 的情况下,也给出相信的提示信息。我们可以在if代码块的后面增加一个else代码块,它会在if语句给出的条件没有达成时执行,如下所示。很显然,if下面的print('你的身材很棒!')else下面的print('你的身材不够标准哟!')只有一个会被执行到。

    """
    BMI计算器

    Version: 1.1 Author: 骆昊 """ height = float(input('身高(cm):')) weight = float(input('体重(kg):')) bmi = weight / (height / 100) ** 2 print(f'{bmi = :.1f}') if 18.5 <= bmi < 24: print('你的身材很棒!') else: print('你的身材不够标准哟!')

    如果要给出更为准确的提示信息,我们可以再次修改上面的代码,通过elif关键字为上面的分支结构增加更多的分支,如下所示。

    """
    BMI计算器

    Version: 1.2 Author: 骆昊 """ height = float(input('身高(cm):')) weight = float(input('体重(kg):')) bmi = weight / (height / 100) ** 2 print(f'{bmi = :.1f}') if bmi < 18.5: print('你的体重过轻!') elif bmi < 24: print('你的身材很棒!') elif bmi < 27: print('你的体重过重!') elif bmi < 30: print('你已轻度肥胖!') elif bmi < 35: print('你已中度肥胖!') else: print('你已重度肥胖!')

    我们再用刚才的三组数据来测试下上面的代码,看看会得到怎样的结果。

    第一组输入:

    身高(cm):175
    体重(kg):68
    bmi = 22.2
    你的身材很棒!
    

    第二组输入:

    身高(cm):175
    体重(kg):95
    bmi = 31.0
    你已中度肥胖!
    

    第三组输入:

    身高(cm):175
    体重(kg):50
    bmi = 16.3
    你的体重过轻!
    

    使用match和case构造分支结构

    Python 3.10 中增加了一种新的构造分支结构的方式,通过使用matchcase 关键字,我们可以轻松的构造出多分支结构。Python 的官方文档在介绍这个新语法时,举了一个 HTTP 响应状态码识别的例子(根据 HTTP 响应状态输出对应的描述),非常有意思。如果不知道什么是 HTTP 响应状态吗,可以看看 MDN 上面的文档。下面我们对官方文档上的示例稍作修改,为大家讲解这个语法,先看看下面用if-else结构实现的代码。

    status_code = int(input('响应状态码: '))
    if status_code == 400:
        description = 'Bad Request'
    elif status_code == 401:
        description = 'Unauthorized'
    elif status_code == 403:
        description = 'Forbidden'
    elif status_code == 404:
        description = 'Not Found'
    elif status_code == 405:
        description = 'Method Not Allowed'
    elif status_code == 418:
        description = 'I am a teapot'
    elif status_code == 429:
        description = 'Too many requests'
    else:
        description = 'Unknown status Code'
    print('状态码描述:', description)
    

    运行结果:

    响应状态码: 403
    状态码描述: Forbidden
    

    下面是使用match-case语法实现的代码,虽然作用完全相同,但是代码显得更加简单优雅。

    status_code = int(input('响应状态码: '))
    match status_code:
        case 400: description = 'Bad Request'
        case 401: description = 'Unauthorized'
        case 403: description = 'Forbidden'
        case 404: description = 'Not Found'
        case 405: description = 'Method Not Allowed'
        case 418: description = 'I am a teapot'
        case 429: description = 'Too many requests'
        case _: description = 'Unknown Status Code'
    print('状态码描述:', description)
    

    > 说明:带有_case语句在代码中起到通配符的作用,如果前面的分支都没有匹配上,代码就会来到case _case _的是可选的,并非每种分支结构都要给出通配符选项。如果分支中出现了case _,它只能放在分支结构的最后面,如果它的后面还有其他的分支,那么这些分支将是不可达的。

    当然,match-case语法还有很多高级玩法,其中有一个合并模式可以先教给大家。例如,我们要将响应状态码401403404归入一个分支,400405归入到一个分支,其他保持不变,代码还可以这么写。

    status_code = int(input('响应状态码: '))
    match status_code:
        case 400 | 405: description = 'Invalid Request'
        case 401 | 403 | 404: description = 'Not Allowed'
        case 418: description = 'I am a teapot'
        case 429: description = 'Too many requests'
        case _: description = 'Unknown Status Code'
    print('状态码描述:', description)
    

    运行结果:

    响应状态码: 403
    状态码描述: Not Allowed
    

    分支结构的应用

    #### 例子1:分段函数求值

    有如下所示的分段函数,要求输入x,计算出y

    $$
    y = \begin{cases} 3x - 5, & (x \gt 1) \\\\ x + 2, & (-1 \le x \le 1) \\\\ 5x + 3, & (x \lt -1) \end{cases}
    $$

    """
    分段函数求值

    Version: 1.0 Author: 骆昊 """ x = float(input('x = ')) if x > 1: y = 3 * x - 5 elif x >= -1: y = x + 2 else: y = 5 * x + 3 print(f'{y = }')

    根据实际开发的需要,分支结构是可以嵌套的,也就是说在分支结构的ifelifelse代码块中还可以再次引入分支结构。例如if条件成立表示玩家过关,但过关以后还要根据你获得宝物或者道具的数量对你的表现给出评价(比如点亮一颗、两颗或三颗星星),那么我们就需要在if的内部再构造一个新的分支结构。同理,我们在elifelse中也可以构造新的分支,我们称之为嵌套的分支结构。按照这样的思路,上面的分段函数求值也可以用下面的代码来实现。

    """
    分段函数求值

    Version: 1.1 Author: 骆昊 """ 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'{y = }')

    > 说明:大家可以自己感受和评判一下上面两种写法哪一种更好。在“Python 之禅”中有这么一句话:“Flat is better than nested”。之所以认为“扁平化”的代码更好,是因为代码嵌套的层次如果很多,会严重的影响代码的可读性。所以,我个人更推荐大家使用第一种写法。

    #### 例子2:百分制成绩转换成等级

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

    """
    百分制成绩转换为等级制成绩

    Version: 1.0 Author: 骆昊 """ 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(f'{grade = }')

    #### 例子3:计算三角形的周长和面积。

    要求:输入三条边的长度,如果能构成三角形就计算周长和面积;否则给出“不能构成三角形”的提示。

    """
    计算三角形的周长和面积

    Version: 1.0 Author: 骆昊 """ a = float(input('a = ')) b = float(input('b = ')) c = float(input('c = ')) if a + b > c and a + c > b and b + c > a: perimeter = a + b + c print(f'周长: {perimeter}') s = perimeter / 2 area = (s (s - a) (s - b) (s - c)) * 0.5 print(f'面积: {area}') else: print('不能构成三角形')

    > 说明: 上面的if 条件表示任意两边之和大于第三边,这是构成三角形的必要条件。当这个条件成立时,我们要计算并输出周长和面积,所以if下方有五条语句都保持了相同的缩进,它们是一个整体,只要if条件成立,它们都会被执行,这就是我们之前提到的代码块的概念。另外,上面计算三角形面积的公式叫做海伦公式,假设有一个三角形,边长分别为 $\small{a}$ 、 $\small{b}$ 、 $\small{c}$ ,那么三角的面积 $\small{A}$ 可以由公式 $\small{A = \sqrt{s(s-a)(s-b)(s-c)}}$ 得到,其中, $s=\frac{a + b + c}{2}$ 表示半周长。

    总结

    学会了 Python 中的分支结构和循环结构,我们就可以解决很多实际的问题了。这一节课相信已经帮助大家掌握了构造分支结构的方法,下一节课我们为大家介绍循环结构,学完这两次课你一定会发现,你能写出很多很有意思的代码,继续加油吧!

  • Python语言中的运算符

    Python语言中的运算符

    Python 语言支持很多种运算符,下面的表格按照运算符的优先级从高到低,对 Python 中的运算符进行了罗列。有了变量和运算符,我们就可以构造各种各样的表达式来解决实际问题。在计算机科学中,表达式是计算机程序中的句法实体,它由一个或多个常量、变量、函数和运算符组合而成,编程语言可以对其进行解释和计算以得到另一个值。不理解这句话没有关系,但是一定要知道,不管使用什么样的编程语言,构造表达式都是非常重要的。

    | 运算符 | 描述 |
    | ———————————————————— | —————————— |
    | [][:] | 索引、切片 |
    | ** | 幂 |
    | ~+- | 按位取反、正号、负号 |
    | */%// | 乘、除、模、整除 |
    | +- | 加、减 |
    | >><< | 右移、左移 |
    | & | 按位与 |
    | ^| | 按位异或、按位或 |
    | <=<>>= | 小于等于、小于、大于、大于等于 |
    | ==!= | 等于、不等于 |
    | isis not | 身份运算符 |
    | innot in | 成员运算符 |
    | notorand | 逻辑运算符 |
    | =+=-==/=%=//=*=&=\|=^=>>=<<= | 赋值运算符 |

    >说明: 所谓优先级就是在一个运算的表达式中,如果出现了多个运算符,应该先执行什么再执行什么的顺序。编写代码的时候,如果搞不清楚一个表达式中运算符的优先级,可以使用圆括号(小括号)来确保运算的执行顺序。

    算术运算符

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

    """
    算术运算符

    Version: 1.0 Author: 骆昊 """ print(321 + 12) # 加法运算,输出333 print(321 - 12) # 减法运算,输出309 print(321 * 12) # 乘法运算,输出3852 print(321 / 12) # 除法运算,输出26.75 print(321 // 12) # 整除运算,输出26 print(321 % 12) # 求模运算,输出9 print(321 ** 12) # 求幂运算,输出1196906950228928915420617322241

    算术运算需要先乘除后加减,这一点跟数学课本中讲的知识没有区别,也就是说乘除法的运算优先级是高于加减法的。如果还有求幂运算,求幂运算的优先级是高于乘除法的。如果想改变算术运算的执行顺序,可以使用英文输入法状态下的圆括号(小括号),写在圆括号中的表达式会被优先执行,如下面的例子所示。

    """
    算术运算的优先级

    Version: 1.0 Author: 骆昊 """ print(2 + 3 * 5) # 17 print((2 + 3) * 5) # 25 print((2 + 3) 5 * 2) # 125 print(((2 + 3) 5) * 2) # 625

    赋值运算符

    赋值运算符应该是最为常见的运算符,它的作用是将右边的值赋给左边的变量。赋值运算符还可以跟上面的算术运算符放在一起,组合成复合赋值运算符,例如:a += b相当于a = a + ba = a + 2相当于a = a (a + 2)。下面的例子演示了赋值运算符和复合赋值运算符的使用。

    """
    赋值运算符和复合赋值运算符

    Version: 1.0 Author: 骆昊 """ a = 10 b = 3 a += b # 相当于:a = a + b a = a + 2 # 相当于:a = a (a + 2) print(a) # 大家算一下这里会输出什么

    赋值运算构成的表达式本身不产生任何值,也就是说,如果你把一个赋值表达式放到print函数中试图输出表达式的值,将会产生语法错误。为了解决这个问题,Python 3.8 中引入了一个新的赋值运算符:=,我们称之为海象运算符,大家可以猜一猜它为什么叫这个名字。海象运算符也是将运算符右侧的值赋值给左边的变量,与赋值运算符不同的是,运算符右侧的值也是整个表达式的值,看看下面的代码大家就明白了。

    """
    海象运算符

    Version: 1.0 Author: 骆昊 """

    SyntaxError: invalid syntax

    print((a = 10))

    海象运算符

    print((a := 10)) # 10 print(a) # 10

    > 提示:上面第 8 行代码如果不注释掉,运行代码会看到SyntaxError: invalid syntax错误信息,注意,这行代码中我们给a = 10加上了圆括号,如果不小心写成了print(a = 10),会看到TypeError: 'a' is an invalid keyword argument for print()错误信息,后面讲到函数的时候,大家就会明白这个错误提示是什么意思了。

    比较运算符和逻辑运算符

    比较运算符也称为关系运算符,包括==!=<><=>=,我相信大家一看就能懂。需要提醒的是比较相等用的是==,请注意这里是两个等号,因为=是赋值运算符,我们在上面刚刚讲到过。比较不相等用的是!=,跟数学课本中使用的$\small{\neq}$并不相同,Python 2 中曾经使用过<>来表示不等于,在 Python 3 中使用<>会引发SyntaxError(语法错误)。比较运算符会产生布尔值,要么是True,要么是False

    逻辑运算符有三个,分别是andornotand字面意思是“而且”,所以and运算符会连接两个布尔值或者产生布尔值的表达式,如果两边的布尔值都是True,那么运算的结果就是True;左右两边的布尔值有一个是False,最终的运算结果就是False。当然,如果and运算符左边的布尔值是False,不管右边的布尔值是什么,最终的结果都是False,这时运算符右边的布尔值会被跳过(专业的说法叫短路处理,如果and右边是一个表达式,那么这个表达式不会执行)。or字面意思是“或者”,所以or运算符也会连接两个布尔值或产生布尔值的表达式,如果两边的布尔值有任意一个是True,那么最终的结果就是True。当然,or运算符也是有短路功能的,当它左边的布尔值为True的情况下,右边的布尔值会被短路(如果or右边是一个表达式,那么这个表达式不会执行)。not运算符的后面可以跟一个布尔值,如果not后面的布尔值或表达式是True,那么运算的结果就是False;如果not后面的布尔值或表达式是False,那么运算的结果就是True

    """
    比较运算符和逻辑运算符的使用

    Version: 1.0 Author: 骆昊 """ flag0 = 1 == 1 flag1 = 3 > 2 flag2 = 2 < 1 flag3 = flag1 and flag2 flag4 = flag1 or flag2 flag5 = not flag0 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 print(flag1 and not flag2) # True print(1 > 2 or 2 == 3) # False

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

    运算符和表达式应用

    #### 例子1:华氏温度转摄氏温度

    > 要求:输入华氏温度将其转换为摄氏温度,华氏温度到摄氏温度的转换公式为: $\small{C = (F - 32) / 1.8}$ 。

    """
    将华氏温度转换为摄氏温度

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

    > 说明:上面代码中的input函数用于从键盘接收用户输入,由于输入的都是字符串,如果想处理成浮点小数来做后续的运算,可以用我们上一课讲解的类型转换的方法,用float函数将str类型处理成float类型。

    上面的代码中,我们对print函数输出的内容进行了格式化处理,print输出的字符串中有两个%.1f占位符,这两个占位符会被%之后的(f, c)中的两个float类型的变量值给替换掉,浮点数小数点后保留1位有效数字。如果字符串中有%d占位符,那么我们会用int类型的值替换掉它,如果字符串中有%s占位符,那么它会被str类型的值替换掉。

    除了上面格式化输出的方式外,Python 中还可以用下面的办法来格式化输出,我们给出一个带占位符的字符串,字符串前面的f表示这个字符串是需要格式化处理的,其中的{f:.1f}{c:.1f}可以先看成是{f}{c},表示输出时会用变量f和变量c的值替换掉这两个占位符,后面的:.1f表示这是一个浮点数,小数点后保留1位有效数字。

    """
    将华氏温度转换为摄氏温度

    Version: 1.1 Author: 骆昊 """ f = float(input('请输入华氏温度: ')) c = (f - 32) / 1.8 print(f'{f:.1f}华氏度 = {c:.1f}摄氏度')

    #### 例子2:计算圆的周长和面积

    > 要求:输入一个圆的半径($\small{r}$),计算出它的周长( $\small{2 \pi r}$ )和面积( $\small{\pi r^{2}}$ )。

    """
    输入半径计算圆的周长和面积

    Version: 1.0 Author: 骆昊 """ radius = float(input('请输入圆的半径: ')) perimeter = 2 3.1416 radius area = 3.1416 radius radius print('周长: %.2f' % perimeter) print('面积: %.2f' % area)

    Python 中有一个名为math 的内置模块,该模块中定义了名为pi的变量,它的值就是圆周率。如果要使用 Python 内置的这个pi,我们可以对上面的代码稍作修改。

    """
    输入半径计算圆的周长和面积

    Version: 1.1 Author: 骆昊 """ import math

    radius = float(input('请输入圆的半径: ')) perimeter = 2 math.pi radius area = math.pi radius * 2 print(f'周长: {perimeter:.2f}') print(f'面积: {area:.2f}')

    > 说明:上面代码中的import math表示导入math模块,导入该模块以后,才能用math.pi得到圆周率的值。

    这里其实还有一种格式化输出的方式,是 Python 3.8 中增加的新特性,大家直接看下面的代码就明白了。

    """
    输入半径计算圆的周长和面积

    Version: 1.2 Author: 骆昊 """ import math

    radius = float(input('请输入圆的半径: ')) # 输入: 5.5 perimeter = 2 math.pi radius area = math.pi radius * 2 print(f'{perimeter = :.2f}') # 输出:perimeter = 34.56 print(f'{area = :.2f}') # 输出:area = 95.03

    > 说明:假如变量a的值是9.87,那么字符串f'{a = }'的值是a = 9.87;而字符串f'{a = :.1f}'的值是a = 9.9。这种格式化输出的方式会同时输出变量名和变量值。

    #### 例子3:判断闰年

    要求:输入一个 1582 年以后的年份,判断该年份是不是闰年。

    """
    输入年份,闰年输出True,平年输出False

    Version: 1.0 Author: 骆昊 """ year = int(input('请输入年份: ')) is_leap = year % 4 == 0 and year % 100 != 0 or year % 400 == 0 print(f'{is_leap = }')

    > 说明:对于格里历(Gregorian calendar),即今天我们使用的公历,判断闰年的规则是:1. 公元年份非 4 的倍数是平年;2. 公元年份为 4 的倍数但非 100 的倍数是闰年;3. 公元年份为 400 的倍数是闰年。格里历是由教皇格里高利十三世在 1582 年 10 月引入的,作为对儒略历(Julian calendar)的修改和替代,我们在输入年份时要注意这一点。上面的代码通过%来判断year是不是4的倍数、100的倍数、400的倍数,然后用andor运算符将三个条件组装在一起,前两个条件要同时满足,第三个条件跟前两个条件的组合只需满足其中之一。

    总结

    通过上面的讲解和例子,相信大家已经感受到了运算符和表达式的力量。实际编程中的很多问题,都需通过构造表达式来解决,所以变量、运算符、表达式对于任何一门编程语言都是极为重要的基础。如果本节课的内容有什么不理解的地方,一定不要着急进入下一课,先在评论区留言讨论,我会及时解答大家的问题。

  • Python语言中的变量

    Python语言中的变量

    对于想学习编程的新手来说,有两个问题可能是他们很想知道的,其一是“什么是(计算机)程序”,其二是“写(计算机)程序能做什么”。先说说我对这两个问题的理解:程序是数据和指令的有序集合写程序就是用数据和指令控制计算机做我们想让它做的事情。今时今日,为什么有那么多人选择用 Python 语言来写程序,因为 Python 语言足够简单和强大。相较于 C、C++、Java 这样的编程语言,Python 对初学者和非专业人士更加友好,很多问题在 Python 语言中都能找到简单优雅的解决方案。接下来,我们就从最基础的语言元素开始,带大家认识和使用 Python 语言。

    一些常识

    在开始系统的学习 Python 编程之前,我们先来科普一些计算机的基础知识。计算机的硬件系统通常由五大部件构成,包括:运算器控制器存储器输入设备输出设备。其中,运算器和控制器放在一起就是我们常说的中央处理器(CPU),它的功能是执行各种运算和控制指令。刚才我们提到过,程序是指令的集合,写程序就是将一系列的指令按照某种方式组织到一起,然后通过这些指令去控制计算机做我们想让它做的事情。存储器可以分为内部存储器外部存储器,前者就是我们常说的内存,它是中央处理器可以直接寻址的存储空间,程序在执行的过程中,对应的数据和指令需要加载到内存中。输入设备和输出设备经常被统称为 I/O 设备,键盘、鼠标、麦克风、摄像头是典型的输入设备,而显示器、打印机、扬声器等则是典型的输出设备。目前,我们使用的计算机基本大多是遵循“冯·诺依曼体系结构”的计算机,这种计算机有两个关键点:一是将存储器与中央处理器分开;二是将数据以二进制方式编码

    二进制是一种“逢二进一”的计数法,跟人类使用的“逢十进一”的计数法本质是一样的。人类因为有十根手指,所以使用了十进制计数法,在计数时十根手指用完之后,就只能用进位的方式来表示更大的数值。当然凡事都有例外,玛雅人可能是因为长年光着脚的原因,把脚趾头也都用上了,于是他们使用了二十进制的计数法。基于这样的计数方式,玛雅人使用的历法跟我们平常使用的历法就产生了差异。按照玛雅人的历法,2012 年是上一个所谓的“太阳纪”的最后一年,而 2013 年则是新的“太阳纪”的开始。后来这件事情还被以讹传讹的方式误传为“2012 年是玛雅人预言的世界末日”的荒诞说法。今天有很多人猜测,玛雅文明之所以发展缓慢跟使用了二十进制是有关系的。对于计算机来说,二进制在物理器件上最容易实现的,因为可以用高电压表示 1,用低电压表示 0。不是所有写程序的人都需要熟悉二进制,熟悉十进制与二进制、八进制、十六进制的转换,大多数时候我们即便不了解这些知识也能写程序。但是,我们必须知道,计算机是使用二进制计数的,不管什么样的数据,到了计算机内存中都是以二进制形态存在的。

    > 说明:关于二进制计数法以及它与其他进制如何相互转换,大家可以翻翻名为《计算机导论》或《计算机文化》的书,都能找到相应的知识,此处就不再进行赘述了,不清楚的读者可以自行研究。

    变量和类型

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

  • 整型(int):Python 中可以处理任意大小的整数,而且支持二进制(如0b100,换算成十进制是4)、八进制(如0o100,换算成十进制是64)、十进制(100)和十六进制(0x100,换算成十进制是256)的表示法。运行下面的代码,看看会输出什么。
  •     print(0b100)  # 二进制整数
        print(0o100)  # 八进制整数
        print(100)    # 十进制整数
        print(0x100)  # 十六进制整数
        

  • 浮点型(float):浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,浮点数除了数学写法(如123.456)之外还支持科学计数法(如1.23456e2,表示$\small{1.23456 \times 10^{2}}$)。运行下面的代码,看看会输出什么。
  •     print(123.456)    # 数学写法
        print(1.23456e2)  # 科学计数法
        

  • 字符串型(str):字符串是以单引号或双引号包裹起来的任意文本,比如'hello'"hello"
  • 布尔型(bool):布尔型只有TrueFalse两种值,要么是True,要么是False,可以用来表示现实世界中的“是”和“否”,命题的“真”和“假”,状况的“好”与“坏”,水平的“高”与“低”等等。如果一个变量的值只有两种状态,我们就可以使用布尔型。
  • 变量命名

    对于每个变量,我们都需要给它取一个名字,就如同我们每个人都有自己的名字一样。在 Python 中,变量命名需要遵循以下的规则和惯例。

    – 规则1:变量名由字母数字下划线构成,数字不能开头。需要说明的是,这里说的字母指的是 Unicode 字符,Unicode 称为万国码,囊括了世界上大部分的文字系统,这也就意味着中文、日文、希腊字母等都可以作为变量名中的字符,但是一些特殊字符(如:@#等)是不能出现在变量名中的。我们强烈建议大家把这里说的字母理解为尽可能只使用英文字母
    – 规则2:Python 是大小写敏感的编程语言,简单的说就是大写的A和小写的a是两个不同的变量,这一条其实并不算规则,而是需要大家注意的地方。
    – 规则3:变量名不要跟 Python 的关键字重名尽可能避开 Python 的保留字。这里的关键字是指在 Python 程序中有特殊含义的单词(如:isifelseforwhileTrueFalse等),保留字主要指 Python 语言内置函数、内置模块等的名字(如:intprintinputstrmathos等)。

    – 惯例1:变量名通常使用小写英文字母多个单词用下划线进行连接
    – 惯例2:受保护的变量用单个下划线开头。
    – 惯例3:私有的变量用两个下划线开头。

    惯例2和惯例3大家暂时不用管,讲到后面自然会明白的。当然,作为一个专业的程序员,给变量命名时做到见名知意也是非常重要,这彰显了一个程序员的专业气质,很多开发岗位的面试也非常看重这一点。

    变量的使用

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

    """
    使用变量保存数据并进行加减乘除运算

    Version: 1.0 Author: 骆昊 """ a = 45 # 定义变量a,赋值45 b = 12 # 定义变量b,赋值12 print(a, b) # 45 12 print(a + b) # 57 print(a - b) # 33 print(a * b) # 540 print(a / b) # 3.75

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

    """
    使用type函数检查变量的类型

    Version: 1.0 Author: 骆昊 """ a = 100 b = 123.45 c = 'hello, world' d = True print(type(a)) # print(type(b)) # print(type(c)) # print(type(d)) #

    可以通过 Python 内置的函数来改变变量的类型,下面是一些常用的和变量类型相关的函数。

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

    """
    变量的类型转换操作

    Version: 1.0 Author: 骆昊 """ a = 100 b = 123.45 c = '123' d = '100' e = '123.45' f = 'hello, world' g = True print(float(a)) # int类型的100转成float,输出100.0 print(int(b)) # float类型的123.45转成int,输出123 print(int(c)) # str类型的'123'转成int,输出123 print(int(c, base=16)) # str类型的'123'按十六进制转成int,输出291 print(int(d, base=2)) # str类型的'100'按二进制转成int,输出4 print(float(e)) # str类型的'123.45'转成float,输出123.45 print(bool(f)) # str类型的'hello, world'转成bool,输出True print(int(g)) # bool类型的True转成int,输出1 print(chr(a)) # int类型的100转成str,输出'd' print(ord('d')) # str类型的'd'转成int,输出100

    > 说明str类型转int类型时可以通过base参数来指定进制,可以将字符串视为对应进制的整数进行转换。str类型转成bool类型时,只要字符串有内容,不是''"",对应的布尔值都是Truebool类型转int类型时,True会变成1False会变成0。在 ASCII 字符集和 Unicode 字符集中, 字符'd'对应的编码都是100

    总结

    在 Python 程序中,我们可以使用变量来保存数据变量有不同的类型,常用的类型有intfloatstrbool。在有需要的情况下,可以通过 Python 内置的函数对变量进行类型转换。变量是可以做运算的,这是解决很多问题的先决条件,我们会在下一课中为大家详细介绍变量的运算。

  • 第一个Python程序

    第一个Python程序

    在上一课中,我们对 Python 语言的过去现在有了一些了解,我们准备好了运行 Python 程序所需要的解释器环境。相信大家已经迫不及待的想开始自己的 Python 编程之旅了,但是新问题来了,我们应该在什么地方书写 Python 程序,然后又怎么运行它呢?

    编写代码的工具

    下面我们为大家讲解几种可以编写和运行 Python 代码的工具,大家可以根据自己的需求来选择合适的工具。当然,对于初学者,我个人比较推荐使用 PyCharm,因为它不需要太多的配置也非常的强大,对新手还是很友好的。如果你也听说过或者喜欢 PyCharm,可以直接跳过下面对其他工具的介绍,直接快进到讲解 PyCharm 的地方。

    #### 默认的交互式环境

    我们打开 Windows 的“命令提示符”或“PowerShell”工具,输入python然后按下Enter键,这个命令会把我们带到一个交互式环境中。所谓交互式环境,就是我们输入一行代码并按下Enter键,代码马上会被执行,如果代码有产出结果,那么结果会被显示在窗口中,如下所示。

    Python 3.10.10
    Type "help", "copyright", "credits" or "license" for more information.
    >>> 2 * 3
    6
    >>> 2 + 3
    5
    >>>
    

    > 说明:使用 macOS 系统的用户需要打开“终端”工具,输入python3进入交互式环境。

    如果希望退出交互式环境,可以在交互式环境中输入quit(),如下所示。

    >>> quit()
    

    #### 更好的交互式环境 – IPython

    上面说的交互式环境用户体验并不怎么好,大家使用一下就能感受到。我们可以用 IPython 来替换掉它,因为 IPython 提供了更为强大的编辑和交互功能。我们可以在命令提示符或终端中使用 Python 的包管理工具pip来安装 IPython,如下所示。

    pip install ipython
    

    > 提示:在使用上面的命令安装 IPython 之前,可以先通过pip config set global.index-url https://pypi.doubanio.com/simple命令或pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/将下载源修改为国内的豆瓣镜像或清华镜像,否则下载安装的过程可能会非常的缓慢。

    接下来可以使用下面的命令启动 IPython,进入交互式环境。

    ipython
    

    > 说明:还有一个网页版的 IPython 名叫 Jupyter,我们在用得着它的地方再为大家介绍。

    #### 文本编辑神器 – Visual Studio Code

    Visual Studio Code 是由微软开发能够在 Windows、 Linux 和 macOS 等操作系统上运行的代码编辑神器。它支持语法高亮、自动补全、多点编辑、运行调试等一系列便捷功能,而且能够支持多种编程语言。如果大家要选择一款高级文本编辑工具,强烈推荐 Visual Studio Code,关于它的下载、安装和使用,有兴趣的读者可以自行研究。

    #### 集成开发环境 – PyCharm

    如果用 Python 语言开发商业项目,我们推荐大家使用更为专业的工具 PyCharm。PyCharm 是由捷克一家名为 JetBrains 的公司针对 Python 语言提供的集成开发环境(IDE)。所谓集成开发环境,通常是指提供了编写代码、运行代码、调试代码、分析代码、版本控制等一系列强大功能和便捷操作的开发工具,因此特别适合用于商业项目的开发。我们可以在 JetBrains 公司的官方网站上找到 PyCharm 的

    官方提供了两个 PyCharm 的版本,一个是免费的社区版(Community Edition),功能相对弱小,但对于初学者来说是完全够用的;另一个是付费的专业版(Professional Edition),功能非常强大,但需要按年或按月支付费用,新用户可以免费试用30天时间。PyCharm 的安装没有任何难度,运行下载的安装程序,几乎全部使用默认设置进行安装就可以了。对于使用 Windows 系统的小伙伴,其中有一个步骤可以按照下图所示勾选“创建桌面快捷方式”和“在右键菜单中添加”Open Folder as Project””就可以了。

    第一次运行 PyCharm 时,在提示你导入 PyCharm 设置的界面上直接选择“Do not import settings”,然后我们就可以看到如下图所示的欢迎界面。此处,我们可以先点击“Customize”选项对 PyCharm 做一些个性化的设置。

    接下来,我们可以在“Projects”选项中点击“New Project”来创建一个新的项目,此处还可以“打开已有项目”或“从版本控制服务器(VCS)获取项目”,如下图所示。

    创建项目的时候需要指定项目的路径并创建”虚拟环境“,我们建议每个 Python 都在自己专属的虚拟环境中运行。如果你的系统上还没 Python 环境,那么 PyCharm 会提供官网的下载链接,当你点击“Create”按钮创建项目时,它会联网下载 Python 解释器,如下图所示。

    当然,我们并不推荐这么做,因为我们在上一课已经安装过 Python 环境了。在系统有 Python 环境的情况下,PyCharm 通常会自动发现 Python 解释器的位置并以此为基础创建虚拟环境,所以大家看到的画面应该如下图所示。

    > 说明:上面的截图来自于 Windows 系统,如果使用 macOS 系统,你看到的项目路径和 Python 解释器路径会跟上面有所不同。

    创建好项目后会出现如下图所示的画面,我们可以通过在项目文件夹上点击鼠标右键,选择“New”菜单下的“Python File”来创建一个 Python 文件,在给文件命名时建议使用英文字母和下划线的组合,创建好的 Python 文件会自动打开,进入可编辑的状态。

    接下来,我们可以在代码窗口编写我们的 Python 代码。写好代码后,可以在窗口中点击鼠标右键,选择“Run”菜单项来运行代码,下面的“Run”窗口会显示代码的执行结果,如下图所示。

    到这里,我们的第一个 Python 程序已经运转起来了,很酷吧!对了,PyCharm 有一个叫“每日小贴士”的弹窗,会教给你一些使用 PyCharm 的小技巧,如下图所示。如果不需要,直接关闭就可以了;如果不希望它再次出现,在关闭前可以勾选“Don’t show tips on startup”。

    你好世界

    按照行业惯例,我们学习任何一门编程语言写的第一个程序都是输出hello, world,因为这段代码是伟大的丹尼斯·里奇(C 语言之父,和肯·汤普森一起开发了 Unix 操作系统)和布莱恩·柯尼汉(awk 语言的发明者)在他们的不朽著作《The C Programming Language》中写的第一段代码,下面是对应的 Python 语言的版本。

    print('hello, world')
    

    > 注意:上面代码中的圆括号、单引号都是在英文输入法状态下输入的,如果不小心写成了中文的圆括号或单引号,运行代码时会出现SyntaxError: invalid character '(' (U+FF08)SyntaxError: invalid character '‘' (U+2018)这样的错误提示。

    上面的代码只有一个语句,在这个语句中,我们用到了一个名为print的函数,它可以帮助我们输出指定的内容;print函数圆括号中的'hello, world'是一个字符串,它代表了一段文本内容;在 Python 语言中,我们可以用单引号或双引号来表示一个字符串。不同于 C、C++ 或 Java 这样的编程语言,Python 代码中的语句不需要用分号来表示结束,也就是说,如果我们想再写一条语句,只需要回车换行即可,代码如下所示。此外,Python 代码也不需要通过编写名为main的入口函数来使其运行,提供入口函数是编写可执行的 C、C++ 或 Java 代码必须要做的事情,这一点很多程序员都不陌生,但是在 Python 语言中它并不是必要的。

    print('hello, world')
    print('goodbye, world')
    

    如果不使用 PyCharm 这样的集成开发环境,我们也可以直接调用 Python 解释器来运行 Python 程序。我们可以将上面的代码保存成一个名为example01.py的文件,对于Windows 系统,我们假设该文件在C:\code目录下,我们打开“命令提示符”或“PowerShell”并输入下面的命令就可以运行它。

    python C:\code\example01.py
    

    对于 macOS 系统,假设我们的文件在/Users/Hao目录下,那么可以在终端中输入下面的命令来运行程序。

    python3 /Users/Hao/example01.py
    

    > 提示:如果路径比较长,不愿意手动输入,我们可以通过拖拽的方式将文件直接拖到“命令提示符”或“终端”中,这样会自动输入完整的文件路径。

    大家可以试着修改上面的代码,比如将单引号中的hello, world换成其他内容或者多写几个这样的语句,看看会运行出怎样的结果。需要提醒大家的是,写 Python 代码时,最好每一行只写一条语句。虽然,我们可以使用;作为分隔将多个语句写在一行中,但是这样做会让代码变得非常难看,不再具备良好的可读性。

    注释你的代码

    注释是编程语言的一个重要组成部分,用于在代码中解释代码的作用,从而达到增强代码可读性的目标。当然,我们也可以将代码中暂时不需要运行的代码段通过添加注释来去掉,这样当你需要重新使用这些代码的时候,去掉注释符号就可以了。简单的说,注释会让代码更容易看懂但不会影响代码的执行结果

    Python 中有两种形式的注释:

  • 单行注释:以#和空格开头,可以注释掉从#开始后面一整行的内容。
  • 多行注释:三个引号(通常用双引号)开头,三个引号结尾,通常用于添加多行说明性内容。
  • """
    第一个Python程序 - hello, world

    Version: 1.0 Author: 骆昊 """

    print('hello, world')

    print("你好,世界!")

    总结

    到此,我们已经把第一个 Python 程序运行起来了,是不是很有成就感?!只要你坚持学习下去,再过一段时间,我们就可以用 Python 语言做更多更酷的事情。今时今日,编程就跟英语一样,对很多人来说都是一项必须要掌握的技能。

    2026年3月16日
  • 初识Python

    初识Python

    Python简介

    Python(英式发音:/ˈpaɪθən/;美式发音:/ˈpaɪθɑːn/)是由荷兰人吉多·范罗苏姆(Guido von Rossum)发明的一种编程语言,是目前世界上最受欢迎和拥有最多用户的编程语言。Python 强调代码的可读性和语法的简洁性,相较于 C、C++、Java 这些同样影响深远的编程语言,Python 让使用者能够用更少的代码表达自己的意图。下面是几个权威的编程语言排行榜给出的 Python 语言的排名,其中第1张图由 TIOBE Index 提供,第3张图由 IEEE Spectrum 提供。值得一提的是第2张图,它展示了编程语言在全球最大代码托管平台 GitHub 上受欢迎的程度,最近的四年时间 Python 语言都占据了冠军的宝座。

    #### Python编年史

    下面是 Python 语言发展过程中的一些重要时间点:

  • 1989年12月:吉多·范罗苏姆决心开发一个新的脚本语言及其解释器来打发无聊的圣诞节,新语言将作为 ABC 语言的继承者,主要用来替代 Unix shell 和 C 语言实现系统管理。由于吉多本人是 BBC 电视剧《Monty Python’s Flying Circus》的忠实粉丝,所以他选择了 Python 这个词作为新语言的名字。
  • 1991年02月:吉多·范罗苏姆在 alt.sources 新闻组上发布了 Python 解释器的最初代码,标记为版本0.9.0。
  • 1994年01月:Python 1.0发布,梦开始的地方。
  • 2000年10月:Python 2.0发布,Python 的整个开发过程更加透明,生态圈开始慢慢形成。
  • 2008年12月:Python 3.0发布,引入了诸多现代编程语言的新特性,但并不完全向下兼容。
  • 2011年04月:pip 首次发布,Python 语言有了自己的包管理工具。
  • 2018年07月:吉多·范罗苏姆宣布从“终身仁慈独裁者”(开源项目社区出现争议时拥有最终决定权的人)的职位上“永久休假”。
  • 2020年01月:在 Python 2和 Python 3共存了11年之后,官方停止了对 Python 2的更新和维护,希望用户尽快切换到 Python 3。
  • 目前:Python 在大模型(GPT-3、GPT-4、BERT等)、计算机视觉(图像识别、目标检测、图像生成等)、智能推荐(YouTube、Netflix、字节跳动等)、自动驾驶(Waymo、Apollo等)、语音识别、数据科学、量化交易、自动化测试、自动化运维等领域都得到了广泛的应用,Python 语言的生态圈也是相当繁荣。
  • > 说明:大多数软件的版本号一般分为三段,形如A.B.C,其中A表示大版本号,当软件整体重写升级或出现不向后兼容的改变时,才会增加A;B表示功能更新,出现新功能时增加B;C表示小的改动(例如:修复了某个Bug),只要有修改就增加C。

    #### Python优缺点

    Python 语言的优点很多,简单为大家列出几点。

  • 简单优雅,跟其他很多编程语言相比,Python 更容易上手
  • 能用更少的代码做更多的事情,提升开发效率
  • 开放源代码,拥有强大的社区和生态圈
  • 能够做的事情非常多,有极强的适应性。
  • 胶水语言,能够黏合其他语言开发的东西。
  • 解释型语言,更容易跨平台,能够在多种操作系统上运行。
  • Python 最主要的缺点是执行效率低(解释型语言的通病),如果更看重代码的执行效率,C、C++ 或 Go 可能是你更好的选择。

    安装Python环境

    工欲善其事,必先利其器。想要开始你的 Python 编程之旅,首先得在计算机上安装 Python 环境,简单的说就是安装运行 Python 程序需要的 Python 解释器。我们推荐大家安装官方的 Python 3 解释器,它是用 C 语言编写的,我们通常也称之为 CPython,它可能是你目前最好的选择。首先,我们需要从官方网站的下载页面找到下载链接,点击“Download”按钮进入下载页面后,需要根据自己的操作系统选择合适的 Python 3安装程序,如下图所示。

    进入下载页面后,有些 Python 版本并没有提供 Windows 和 macOS 系统的安装程序,只提供了源代码的压缩文件,对于熟悉 Linux 系统的小伙伴,我们可以通过源代码构建安装;对于使用 Windows 或 macOS 系统的小伙伴,我们还是强烈建议使用安装程序。例如,你想安装 Python 3.10,选择 Python 3.10.10 或 Python 3.10.11 就能找到 Windows 或 macOS 系统的安装包,而其他版本可能只有源代码,如下图所示。

    #### Windows环境

    下面我们以 Windows 11为例,讲解如何在 Windows 操作系统上安装 Python 环境。双击运行从官网下载的安装程序,会打开一个安装向导,如下图所示。

    首先,一定要记得勾选“Add python.exe to PATH”选项,它会帮助我们将 Python 解释器添加到 Windows 系统的 PATH 环境变量中(不理解没关系,勾上就对了);其次,“Use admin privileges when installing py.exe”是为了在安装过程中获得管理员权限,建议勾选。然后,我们选择“Customize Installation”,使用自定义安装的模式,这是专业人士的选择,而你就(假装)是那个专业人士,不建议使用“Install Now”(默认安装)。

    接下来,安装向导会提示你勾选需要的“Optional Features”(可选特性),这里咱们可以直接全选。值得一提的是其中的第2项,它是 Python 的包管理工具 pip,可以帮助我们安装三方库和三方工具,所以一定要记得勾选它,然后点击“Next”进入下一环节。

    接下来是对“Advanced Options”(高级选项)的选择,这里我们建议大家只勾选“Add Python to environment variables”和“Precompile standard library”这两个选项,前者会帮助我们自动配置好环境变量,后者会预编译标准库(生成.pyc文件),这样在使用时就无需临时编译了。还是那句话,不理解没关系,勾上就对了。下面的“Customize install location”(自定义安装路径)强烈建议修改为自定义的路径,这个路径中不应该包含中文、空格或其他特殊字符,注意这一点会为你将来减少很多不必要的麻烦。设置完成后,点击“Install”开始安装。

    安装成功会出现如下图所示的画面,安装成功的关键词是“successful”,如果安装失败,这里的单词会变成“failed”。

    安装完成后可以打开 Windows 的“命令行提示符”或 PowerShell,然后输入python --versionpython -V来检查安装是否成功,这个命令是查看 Python 解释器的版本号。如果看到如下所示的画面,那么恭喜你,Python 环境已经安装成功了。这里我们建议再检查一下 Python 的包管理工具 pip 是否可用,对应的命令是pip --versionpip -V

    > 说明:如果安装过程报错或提示安装失败,很有可能是你的 Windows 系统缺失了一些动态链接库文件或缺少必要的构建工具导致的。可以在微软官网下载“Visual Studio 2022 生成工具”进行修复,如下图所示。如果不方便在微软官网下载的,也可以使用下面的百度云盘链接来获取修复工具,链接: https://pan.baidu.com/s/1iNDnU5UVdDX5sKFqsiDg5Q 提取码: cjs3。
    >
    >
    >
    > 上面下载的“Visual Studio 2022 生成工具”需要联网才能运行,运行后会出现如下图所示的画面,大家可以参考下图勾选对应的选项进行修复。修复过程需要联网下载对应的软件包,这个过程可能会比较耗时间,修复成功后可能会要求重启你的操作系统。
    >
    >

    #### macOS环境

    macOS 安装 Python 环境相较于 Windows 系统更为简单,我们从官方下载的安装包是一个pkg文件,双击运行之后不断的点击“继续”就安装成功了,几乎不用做任何的设置和勾选,如下图所示。

    安装完成后,可以在 macOS 的“终端”工具中输入python3 --version命令来检查是否安装成功,注意这里的命令是python3不是python!!!然后我们再检查一下包管理工具,输入命令pip3 --version,如下图所示。

    #### 其他安装方式

    有人可能会推荐新手直接安装 Anaconda,因为 Anaconda 会帮助我们安装 Python 解释器以及一些常用的三方库,除此之外还提供了一些便捷的工具,特别适合萌新小白。我个人并不推荐这种方式,因为在安装 Anaconda 时你会莫名其妙安装了一大堆有用没用的三方库(占用比较多的硬盘空间),然后你的终端或命令提示符会被 Anaconda 篡改(每次启动自动激活虚拟环境),这些并不符合软件设计的最小惊讶原则。其他关于 Anaconda 的小毛病此处就不再赘述了,如果你非要使用 Anaconda,推荐安装 Miniconda,它跟 Anaconda 在同一个下载页面。

    还有萌新小白经常会听到或说出,“我要写 Python 程序,安装一个 PyCharm 不就可以了吗?”。这里简单科普一下,PyCharm 只是一个辅助写 Python 代码的工具,它本身并不具备运行 Python 代码的能力,运行 Python 代码靠的是我们上面安装的 Python 解释器。当然,有些 PyCharm 版本在创建 Python 项目时,如果检测不到你电脑上的 Python 环境,也会提示你联网下载 Python 解释器。PyCharm 的安装和使用我们放在了下一课。

    总结

    总结一下我们学到的东西:

  • Python 语言很强大,可以做很多的事情,所以值得我们去学习。
  • 要使用 Python语言,首先得安装 Python 环境,也就是运行 Python 程序所需的 Python 解释器。
  • Windows 系统可以在命令提示符或 PowerShell 中输入python --version检查 Python 环境是否安装成功;macOS 系统可以在终端中输入python3 --version进行检查。