基础篇3 复杂结构初步

| # Python03复杂结构初步 | 11入列表五湖四海皆兄弟,for循环一步一人无漏缺 http://www.ukoedu.com/course?course=python01&chapter=11
陈富贵
| | —- |

基础篇3 复杂结构初步 - 图1本节要点与难点

  1. 列表(list)的重要用途之一,就是把多个毫无数字规律的元素按顺序列统一排列,从而与数字(即每个元素在列表中的下标索引)建立关联,以便通过循环、随机数等数字方式按规律调用。
  2. 列表使用方括号作为起止标记,使用逗号作为元素分隔。这些符号必须都是半角。
  3. 同一列表中,每个元素都可以是任意数据类型,不需要保持一致。
  4. 列表中允许存在重复元素,即允许多个元素的值相同。
  5. 列表中的元素从0开始编号,所以第一个元素是 a[0](假设列表被赋值给变量a);如果该列表共有5个元素,则最后一个元素为a[4]。Attachment
  6. python列表支持 “逆向索引” ,即用 a[-1] 代表该列表中最后一个元素。
  7. 无论使用普通索引还是逆向索引,一旦引用了列表中不存在的索引值,就会引发语法错误。
  8. 可以使用 a[x:y] 形式的切片操作,将列表 a 中从下标 x 开始、到下标 y-1 结束的片段复制出来,作为一个新列表。如果省略 x ,则代表从0号元素开始;省略 y 则代表截取到最后一个元素。Attachment
  9. 由于列表的内容都位于方括号内,所以根据第三回讲解的知识点,在代码中书写列表元素时可以换行。
  10. 列表中的元素个数,被称为该列表的“长度”,可以用python内置函数 len() 取得。用列表的长度减一,即可得到该列表中最后一个元素的下标数字。
  11. 内置函数 max、min、sum 等均可以使用列表作为参数,得到列表中的最大项、最小项以及总和。其中sum函数要求列表中所有元素必须是数字,而max和min则允许为字符串等其他可比较大小的数据类型。
  12. 可以使用加号 + 对两个列表执行加法,结果可以创建一个新的列表,相当于将这两个列表先后连接在一起。如果两个列表中有重复元素也不会被删除。
  13. 可以使用星号 * 将一个列表乘以一个整数,结果可以创建一个新的列表,相当于原列表重复多次。
  14. 可以使用while循环按照计数器模式,通过下标依次处理列表中的每一个元素;也可以使用 for x in […] 的形式直接遍历列表中的每个元素,每次循环时x都会等于列表中的下一个元素,直到最后一个。Attachment
  15. 使用 for in 循环虽然可以读取列表中的每个元素,但是并不能将用来替换列表中的某个元素;而在while 循环通过下标访问列表的方式则可以做到这一点。Attachment
  16. 在Python2中,可以使用 range(m,n) 得到内容为 [m,m+1,m+2,…,n-1] 的列表;在Python3中,range(m,n) 可以得到同样内容的一个对象,而且用法与列表完全相同,可以同样视作列表使用。如果省略m,即 range(n),则所得序列从0开始,到 n-1 结束。因此通过 for x in range(n) ,即可实现从 0 到 n-1 的指定次数循环。
  17. in 操作符可以直接用于判断列表中是否存在某个元素,如 “ if x in 列表 ” ;反之可用 not in 判断列表中是否不存在某个元素。 Attachment参考阅读:为何从零计数?在绝大多数计算机程序语言中,类似数组、列表这样的集合类型都把 0 作为第一个元素的编号,就行像我们在本节视频课程中看到的那样。这是为什么呢?主要是为了提高程序编译和运行的效率。下面是杨老师为大家准备的一个相对通俗的解释:
    我们可以把某个数组想象成内存中的一排厂房,门牌号(内存地址)从152开始,到180结束 。由于厂房里需要保存的材料比较多,所以现在规定每个厂房需要占用 2 个标准房间,也就是一个厂房横跨两个门牌号。那么每一间厂房的门牌号(即数组中每个元素的内存地址)就分别是: 152、154、156、158 …… 178、180 。
    同时在计算机里,我们会直接把这个数组的第一个元素的地址(即第一个厂房门牌号 152)也作为该数组的地址。也就是说,每当计算机想找到这个数组时,都会直接找到它的第一个元素,并认为这里就是数组的起点。
    现在回到计数问题上来:如果我们把第一个元素编为0号,那么如果想找到第N个元素的地址(门牌号),只要使用公式 “数组地址 + N × 元素大小” 即可。这里数组地址就是第一个元素的地址,而 “元素大小” 就是前面所说的 “一个厂房横跨几个门牌号” ,本例中为2。所以,如果计算机想找到4号元素(也就是第5个),可以直接算出它的地址 152 + 4 × 2 = 160。
    但是假如第一个元素编号为1,那么找到第N个元素的公式就变成了 “数组地址 + (N-1)*元素大小”,怎样才能保证第一个元素(N=1时)的地址与数组地址相同。所以如果想找到4号元素(也就是第4个),算法就是 152 + (4-1) × 2 = 158 。 请注意在这个公式中,需要比前一种算法多做一次减法!
    尽管一个减法操作并不起眼,但由于这种数组寻址操作属于底层系统中出现最频繁的操作之一,所以大量不必要的减法运算还是会影响系统效率,特别是在早期计算机运算速度较慢的时代。因此从很早开始,从零计数就已经成为了计算机科学里面的惯例和共识。
    减法的问题只是从零计数的主要原因之一,研究者们还从其他方面(比如更好表达数据范围等)提出过各种支持从零计数的观点,这里杨老师就不详述了。
    练习1:列表基本操作请在Python的交互式运行窗口中,完成以下步骤的操作:
  18. 手动创建一个列表并赋值给变量(比如x),内容为[17,13,23,3,2,5,11,7,19]。
  19. 使用切片操作,从该列表中截取第3个(下标为2)到第7个(编号为6)的片段,并赋值给另一个变量(比如y)。
  20. 将两个列表相加,观察结果(无需赋值给任何变量,直接相加即可)。
  21. 将第二个列表乘以4,观察结果。
  22. 使用Sum函数和Len函数计算第一个列表的平均值。
    思路提示本例为列表最基本操作,没有难点。在实际操作中请注意以下细节:
  23. 关键字必须全部使用半角符号。
  24. 平均数等于“总和”除以“总数”。
    练习2:人名抽奖请编写一个程序,能够自动从 “张三、李四、王五、赵六、田七、刘八” 中随机选择一个人,作为中奖者显示在屏幕上。代码要求如下:
  25. 使用列表存放人名,人名和数量可以自己随意安排。
  26. 尽可能让程序具备灵活性,比如向列表中添加、删除人名时,不需要修改与列表无关的代码。
  27. 有兴趣的同学可以搜索一下 random模块的choice函数,然后重写本程序。
    思路提示本例与视频课程中的 “随机出题” 基本相同,而且不需要考虑答案和循环出题问题。关键思路如下:
  28. 指定一个列表,并将每个人名作为一个元素。注意字符串要用引号,否则将会被当做变量名引发错误。
  29. 生成一个随机整数,范围在0到列表最大下标为止。
  30. 使用该随机数作为索引值,即可在列表中取出对应人名并显示。
  31. 为实现灵活性,在第2步中使用len函数取得列表长度,再减去一即可得到列表最大下标。
    练习3:指定次数循环请使用for循环和range扩充“练习2”中的抽奖程序,使之每次运行均能连续输出3个中奖者名字(允许一人重复获奖)。输出效果类似下面的形式:
    1等奖:王五 2等奖:田七 3等奖:王五
    思路提示基本思想:只需将上一个练习中的抽奖代码作为循环体,放在一个连续三次的指定次数循环中即可。关键点在于:
  32. 使用 for k in range(3) 的形式确保循环3次。
  33. 每次循环时,print语句中可以 利用循环变量(比如k)生成“1等奖”、“2等奖”等数字。不过要注意,k本身的值是0~2,所以需要加一。
  34. 切记:只需将每次循环均应不同的部分,也就是随机数和打印的语句放在循环体里。定义列表等其他内容不必放在循环体里。
  35. 假设循环变量是k,那么在print语句中,如果把k放到引号里,只会输出字符“k”!如果希望将k视作变量,输出其数值,必须将其放在引号之外!
    练习4:判断交集请编写程序,使之能够找出两个列表中共同存在的所有元素,并打印在屏幕上。这两个列表可以自己随意指定。
    比如,假设在程序中定义了两个列表:[3,5,’a’,10,9,’b’] 和 [‘c’,4,3,’a’] ,那么程序运行结果如下所示:
    共同元素包括: 3 a
    基本思路
  36. 定义两个列表,分别赋值给两个变量(比如a、b)
  37. 使用for循环遍历a中的每个元素,比如 for k in a :
  38. 在循环体里,使用 in 操作符判断 k 是否也存在于列表b中,如果存在则打印(不需要else,因为不存在可以不做任何操作) 另外:如果希望在最前面显示 “共同元素包括:”几个字,应当将这个print语句写在循环之前,而不是每次循环执行一次。

| # Python03复杂结构初步 | 12Python处处皆对象,何必临池羡鸳鸯 http://www.ukoedu.com/course?course=python01&chapter=12
陈富贵
| | —- |

Attachment本节要点与难点

  1. range( a, b, s ) 可以得到一个从a到b-1、两两间隔为 s 的“等差数列”,可选参数 s 称为“步长”。 切片操作也可以使用步长参数,即 x[a:b:s] ,意即在列表x中从下标a截取到下标b-1,两两间隔x个元素。
    Attachment for x in a 循环并不意味着x每次增加 1,而是每次循环时从列表a中取出下一个元素赋值给x,x的值取决于a中的元素。1. 列表中的元素是可以通过引用下标而替换的,比如 a[2]=7 就是将a列表中下标为2的元素替换为7。
  2. 空列表[]也是列表,可以使用列表的一切操作和功能,只不过其长度为0。
    Attachment
  3. 数字、字符串等不能直接与列表相加,除非套上方括号使之成为一个列表。两个列表相加,并不会修改这两个列表本身,只是另外创建了一个新列表,内容为二者全部元素。
  4. 使用列表对象(list)的append方法,可以不生成新列表而是直接将一个元素添加到原列表中,比如 a.append(4) 。
    Attachment
  5. 面向对象思想(Object-Oriented),就是把程序视作由若干个“对象”(比如一个列表、一个字符串、一个绘图函数等)组合而成的系统,每个对象都可以执行一些独特的功能。编写程序就是用代码创建和调用这些对象、进而指挥它们协同运行的过程。
  6. 面向对象的好处之一就是“代码复用”——只要拿来其他项目中编写好的对象代码,就可以直接把它放在自己程序中,并通过调用它的方法使之完成功能,而无需再次开发这些具体功能。这个思想类似于制造汽车时直击从外部采购车轮、发动机并组装调用,从而不必自行生产这些零部件。
  7. 每个对象都属于某一种“类型”,这种类型被称为“类(Class)”。每类对象都可以从“属性”和“方法”两个角度详细描述:
  • “属性”是对象的“静态指标”,比如描述一个人时,可以使用“身高”、“体重”、“姓名”等指标,所以“身高”、“体重”和“姓名”就是人类对象的属性。虽然同一类对象都具有共同的属性(比如两个人类对象都有“身高”属性),但是各自属性可以被赋予不同的取值,从而使二者产生区别(比如一个身高等于1.80,另一个身高等于1.81)。
  • “方法”是对象的“动态行为”,比如人类对象都具有“思考”、“出生”、“死亡”等行为。一个对象从被创建起,就自动具有该类对象的共同方法;但这些方法只有在被调用时才会运行。
    Attachment
  • 同一类的对象,一般会具有一些共同的、属于整个类型的属性和方法。
  1. 按照面向对象编程的基本过程为:
  2. 声明本程序要使用的“类”,或者从Python标准模块、外部模块等工具库中引入事先开发好的类。
  3. 根据需要,在代码中随时创建这些类的一个或多个对象。
  4. 根据需要,随时在代码中修改这些对象的属性、调用这些对象方法,从而使程序完成预期操作。
  5. 列表(list)是Python内置类,只要写下方括号就会被认为是list类的一个对象。
  6. 列表对象常用方法如下:
  • a.append(x) :将 x 作为一个新元素添加到列表末尾。
  • a.count(x) :统计列表中有多少元素的值等于x。本方法返回整数,比如 n=a.count(x),则 n 就是统计结果。
  • a.index(x) :找出第一个等于x的元素,并返回其下标。
  • a.insert(i,x) :将 x 插入到列表中,下标为 i 。原下标为 i 及后面的元素顺次后移。本方法无返回值。
  • a.pop(i) :删除下标为 i 的元素,省略 i 则删除最后一个元素。本方法返回被删除的元素,如 b=a.pop(),则b等于a中原来的最后元素,而a中现在已无该元素。
  • a.remove(i) :删除列表中第一个等于 i 的元素。本方法没有返回值。
  • a.reverse() :倒置列表,使元素顺序与原来完全相反。
  • a.sort() :对列表排序,默认从小到大。如果指定了可选参数 reverse为True,则从大到小排序。假如列表中元素无法相互比较(比如即包含数字也包含字符串,则无法执行sort方法,引发错误。
    Attachment
  1. 可以在Python交互式窗口中输入 help(类名或对象) ,即可查看该类的简要文档说明(如果存在)。
  2. Python语言中,一切元素都是对象!

参考阅读:什么是面向对象?下面是杨洋老师在《深入浅出Excel VBA》对“面向对象”思想的解释,转载到这里供用兴趣的同学深入思考:

开发一个软件,其实就是在计算机中重现或创建一个世界。比如编写游戏时,开发者如同造物主,写下的每一行代码都将决定这个虚拟世界的规则和命运;而编写管理信息系统也是一样,开发者需要洞悉业务流程和规则架构,然后去粗存精、优化补全,从而在电脑中构建出一个比真实部门效率更高的虚拟企业。这就是为什么真正热爱编程的人,虽然整日面对着枯燥单调的屏幕,却始终能够乐在其中而不知肉味。
既然要重现一个世界,首先就要树立一种观察和描述世界的方法,也就是“世界观”。具体到软件开发中,就是“面向过程”、“面向数据”、“面向对象”乃至“领域驱动”等各种层出不穷的编程思想。作为初学编程的非专业人士,我们不需要对这些理论进行全面的探讨,只需要理解一点:这些编程思想的宗旨都是为了回答一个问题——怎样使用软件语言把一个复杂的系统描述清楚。
在这些编程思想中,最为广泛接受的就是“面向过程”与“面向对象”两大范式。所谓“面向过程”(Procedure Oriented),就是将软件看作一个可以执行各种功能的机器;在把一个目标世界转换为程序之前,先分析出它应该包含哪些功能,再将每一个功能编写成一个过程(Procedure)。这样,运行软件就是根据需要随时调用不同的功能。
可以说面向过程是读者目前最熟悉的编程思想,因为VBA中的每一个Sub … End Sub 形式的宏,都可以看作一个过程。当我们在一个Excel工作簿中写下多个宏来完成不同任务时,其实就是在把目标世界(也就是日常业务)划分为不同的功能,并把每一种功能写成一个VBA过程(宏)。
举例来说,如果我们需要编写一个五子棋游戏,那么按照面向过程的方法,会编写以下六个主要过程,并通过对它们的依次调用来运行游戏。
点击下载
五子棋的功能划分及调用流程

在编写小型程序时,这种面向过程的方式可以做到直观易懂、性能高效,但是当系统规模比较庞大、或者功能规则比较复杂时,纯粹的面向过程方法就很容易让代码变得复杂冗余、难以驾驭 。
有鉴于此,随着大型软件日益增多,软件界开始推广另一种编程思想,也就是目前最为主流的“面向对象”开发方法。“对象”一词译自英文“Object”,更确切的含义是“客观事物”或俗称的“东西”,因此也常被台版图书译作“物件”。所谓“面向对象”(Object Oriented)的编程思想,就是要求程序员把需要写成软件的世界,看作由很多个对象构成的系统,每一个对象都可以“独立自主”的控制某些数据或完成某些功能。当我们完成某个任务时,只要通知拥有相关功能的对象,它们就可以自行处理并将结果返回给我们。
仍然以五子棋游戏为例,如果采用面向对象的设计思路,我们可以先考察一下真实世界的棋类比赛都由哪些事物(对象)构成。
首先,一个棋局包括两个名为“棋手”的事物。这两个对象在本质上完全相同,只不过各自拥有不同的名字和棋子颜色,但他们又都拥有一种称为“走棋”的功能。
其次,一个棋局包括一个名为“棋盘”和若干个名为“棋子”的事物。考虑到本例相对简单,我们可以把棋盘和棋子合并为一个对象,统称为“棋盘”。该对象拥有两个主要能力:记住棋子的位置、根据位置把所有棋子绘制在棋盘上。
最后,一个棋局还应包含一名“裁判”。裁判在棋局的每一步中都要做出决定,这些决定包括:现在应该由谁走棋、这一步走棋是否符合规则、是否已经分出胜负、是否需要通知棋盘去更新画面等等。因此这个对象要拥有一个功能:决定下一步的动作。而且这个功能可能还会细分为多个更细节的能力:判断是否合规、判断胜负、通知棋盘更新、通知棋手走棋等。
这样,这个游戏程序就变成了几个对象之间的交互过程,图示如下:
点击下载
五子棋的对象划分及调用流程

比较上面两个图示,可以看到面向对象与面向过程两种思维方式的显著区别。在面向对象方式下,各种功能(如重绘棋盘)和数据(如棋子位置)都被分配给不同的程序模块(即对象)保管,因此整体架构比面向过程时清晰了很多,调用流程也更加简洁,不像第一张图中那样缠绕纠结。
上面所举的例子只是对面向对象和面向过程的一个形象描述,真正编程实现时还有很多环节需要深入讨论和完善。不过由于本书尚未开始讲述小型软件设计的方法,只是为下面介绍基本概念提供一个参考,所以读者现在只需要大体感受到面向对象的特点即可,不必深究图中的具体思路。
参考阅读:类/对象/属性/方法下面是杨洋老师在《深入浅出Excel VBA》对面向对象基本概念的解释,转载到这里供用兴趣的同学深入思考:

类与对象
在前面五子棋的例子中提到:黑白两名棋手虽然是两个对象(在图8.2中显示为右下角的两个方框),但其本质相同,都属于同一类型的对象。这种“一个类型、多个对象”的结构,是面向对象思想中最基本的思维方式。
对象的类型,在面向对象方法中称为“类”(英文为Class)。如果把对象看作一张真实的钞票,那么就可以把类看作一个印制钞票的“模板”:

  1. 使用一个模板,可以印制出无数张钞票(数字产品不存在磨损);同样,一个类可以用来创建无数个该类型的对象。
  2. 一个模板描绘了一种钞票的共同特征(尺寸、面额)和功能(可以用于交换商品;同样,一个类规定了一种对象的共同特征和功能,同一类型的对象都必然具备这些共同特征与功能。
  3. 同一模板印出的钞票,虽然拥有模板规定的共同特征(比如所有钞票都拥有一个流水号,流水号的字体和位置完全相同),但是可以在印制出来后随时修改某个特征的具体内容,从而使其各自不同(比如每张钞票都会补印上不同的流水号数值);同样,虽然同一类对象具有相同特征(比如棋手类都具有“姓名”这一特征),但是这些特征的取值可以各不相同(比如一个棋手对象的“姓名”可以赋值为“黑方”或“白方”)。
  4. 虽然模板可以印制钞票,但模板本身不是钞票,如果把模板当做钞票购买商品将会触发店员报警;同样,类虽然定义了对象的全部特征,但类本身不是对象。如果将类当做对象使用,将会触发VBA错误警告。

换一个角度说,“类”代表“概念”或“定义”,比如字典上的词条“人——能思维,能制造并使用工具进行劳动,并能进行语言交际的高等动物”。而“对象”则是符合这个概念的真实事物,比如张三、李四等实际存在的具体个人。全世界几十亿人类都符合字典词条中的定义,每一个都是“人”这个类的具体对象;但是字典上的那句词条文字却永远不是一个真实的个人。由于对象是类这个“概念”的具体实现,所以在软件开发中也常把对象称作一个类的“实例”(Instance)。
属性与方法
前面提到同类对象具有共同的“特征”和“功能”,按照面向对象的术语,这两者对应的概念就是对象的“属性”与“方法”。
属性,用于描述一类对象拥有哪些方面的特征或状态。比如所有人类都拥有性别、身高、体重、肤色、民族、发色、母语语种、姓名、年龄、文化程度等属性。而当我们想描述某一个人是什么样子时,也都是通过列举这些属性来实现的 。
如果说属性代表的是静态特征,那么方法则代表同一类对象都能够执行的动态行为。比如所有人类对象(即个人)都具有“吃饭”、“行走”、“思考”等功能,并可以随时根据需要实施这些行为。因此对于“人”这个类来说,上述行为都属于“方法”。
属性与方法之间的区别在于:

  1. 属性一般都是名词,而方法一般都是动词。比如对于“人”类来说,“行走”是一个方法,代表“人”类对象可以实施的行动;而“行走速度”则是属性,可以用来描述一个“人”类对象的特点(显然,竞走运动员与耄耋老者的行走速度完全不同)。
  2. 属性可以随时观测,而方法只有在需要时才会执行。比如对于一个“人”类的对象,我们随时可以观测他的身高体重;但除非给他下达一个“行走”的命令,否则他不会执行“行走”的方法。
  3. 属性如同变量,表现为一个具体的取值(比如“某人的行走速度是100”);而方法则如同一个VBA宏,表现为一段可以执行的代码指令(比如“左腿高度提升0.3米;左腿位置前移0.5米;左腿高度降低为0 ;……”)。
  4. 属性可以读取和修改,从而了解或设置一个对象的状态。比如读取一个“人”类对象的“行走速度”属性,就可以了解这个人在行走能力方面的状态(行走能力);而如果修改了他的行走速度属性,则可以重新设置他的行走能力。相比之下,方法不能被读取,只能被调用执行,不过某些执行之后也可能会修改对象的状态(比如某个“人”类对象在执行完“行走”方法后,“体能”属性的数值显著降低)。

下图以足球电子游戏中的运动员为例,说明了类、对象、属性和方法之间的关系:点击下载
类、对象、属性及方法示意
读者可以尝试按照上图的思路,去描述任何自己熟悉的环境或业务。比如对于一个常见的财务部门,可以认为它包括“出纳”、“会计”、“财务主管”、“明细账”、“总账”、“资产负债表”等多个类 ,其中每个类都可能具有多个对象,比如两名出纳、两名会计等。接下来可以思考每一个类所具有的方法,比如会计类对象都具有登记总账、阅读总账、编制资产负债表等方法;而财务主管类则具有调阅总账、更正资产负债表等功能。如此扩展和细化下去,我们会发现身边的一切都可以使用面向对象的方式直观清晰地表述出来。这就是面向对象思想被软件界广泛接受的主要原因。
作业1:按步长切片请在Python的交互式运行窗口中,完成以下步骤的操作:

  1. 手动创建一个列表并赋值给变量(比如x),内容为 [‘周一’,’Mon’,’周二’,’Tue’,’周三’,’Wed’,’周四’,’Thu’,’周五’,’Fri’,’周六’,’Sat’,’周日’,’Sun’] 。
  2. 使用切片操作,从该列表中截取出所有中文星期名称,构成一个新列表。
  3. 同上,使用切片从该列表中截取出所有英文星期名称,构成一个新列表。
  4. 观察原列表内容是否发生变化。
    思路提示本例主要考察切片操作中的“步长”选项,即每两个下标提取一个元素。操作时注意以下问题:
  5. 冒号必须用半角。
  6. 步长应该为2,即每两个元素取一个。
  7. 中文内容从第0个元素开始取;英文内容从第1个元素开始取。二者都可以省略截止下标,从而直接取到列表末尾。

作业2:录入姓名请编写一个程序,运行后允许用户连续录入多个姓名,直到录入 “END” 为止。然后该程序能够把用户输入的所有姓名显示在屏幕上,并告知一共输入了多少名字。
具体效果示例如下:
>>> ==== RESTART: D:/Demo/test.py ===== 请输入一个姓名:张三 请输入一个姓名:李四 请输入一个姓名:王五 请输入一个姓名:赵六 请输入一个姓名:END 您一共输入了 4 个姓名: 张三 李四 王五 赵六 >>>
思路提示本例与本节课程视频最后所举的“录入数字并排序”基本相同,只是最后需要用len函数统计出列表总长度,并使用 for in 循环显示出来。关键点如下:

  1. 需要设计一个列表,以便在用户连续输入姓名的过程中“记住”每个输入字符串。在用户开始循环输入之前,应先将该列表设为空列表。
  2. 每次循环时,应让用户执行一次input操作,然后通过列表的append方法将输入的内容追加到其中,从而“记住”该次输入的姓名。
  3. 当用户输入“END”时,应该马上跳出循环,不能将“END”也追加到列表中。
  4. 注意缩进!打印列表长度的语句应该在循环结束之后。
  5. 可以用 for in 循环打印列表中每一个元素。
    特别提示:对于怎样在输入“END”时跳出循环的问题,既可以做一个死循环、然后在循环体中使用判断语句配合break语句实现,也可以巧妙设计循环条件并使用列表的remove方法,从而不使用break。后一种方式请自行思考,本例参考答案中会给出这两种方案的代码。

作业3:录入抽奖 请在 “作业2:录入姓名” 的基础上改编程序,即:运行后允许用户连续录入多个姓名,直到录入 “END” 为止。然后该程序能够从录入的名字中随机抽取一人,作为中奖者显示在屏幕上。
具体效果示例如下:
>>> ==== RESTART: D:/Demo/test.py ===== 请输入一个姓名:张三 请输入一个姓名:李四 请输入一个姓名:王五 请输入一个姓名:赵六 请输入一个姓名:END 您一共输入了 4 个姓名,其中中奖者为: 王五 >>>
思路提示本例的前半部分(包括循环录入)与作业2相同,可以照搬。抽奖的关键点如下:

  1. 使用len函数得到列表中姓名总数,建议将该总数保存到一个变量中,以便即用于显示总人数、又用于控制随机数。
  2. 根据姓名总数,可以推断出列表的最大下标值。于是可以通过randint函数在0到最大下标之间随机生成下标数字,产生抽奖结果。

作业4:比例抽奖请在 “作业3:录入抽奖” 的基础上改编程序,即:运行后允许用户连续录入多个姓名,直到录入 “END” 为止。然后该程序能够从录入的名字中随机抽取占总数 1/2 的个人,作为中奖者显示在屏幕上。比如,假设用户输入了8个或9个人名,则抽取4人作为中奖者。注意,一个人名允许被多次抽中!
具体效果示例如下:
>>> ==== RESTART: D:/Demo/test.py ===== 请输入一个姓名:张三 请输入一个姓名:李四 请输入一个姓名:王五 请输入一个姓名:赵六 请输入一个姓名:田七 请输入一个姓名:刘八 请输入一个姓名:END 您一共输入了 6 个姓名,其中中奖者为: 王五 田七 王五 >>>
思路提示本例的前半部分(包括循环录入、显示总人数、生成随机下标等)都与作业3相同,可以照搬。关键点只最后对获奖人数的控制,具体如下:

  1. 使用len函数得到总人数后,可以用 x//2 形式的整除操作得到应抽人数,假设为数字 n 。
  2. 做一个循环 n 次的指定次数循环,每次生成一个随机下标,从列表中读取该下标元素并显示。 注意:本例允许重复抽取同一姓名,所以相对简单。

作业5:不重复抽奖请在 “作业4:比例抽奖” 的基础上改编程序,即:运行后允许用户连续录入多个姓名,直到录入 “END” 为止。然后该程序能够从录入的名字中随机抽取占总数 1/2 的个人,作为中奖者显示在屏幕上。但是请注意:不允许重复抽奖,每个人名只能被抽中一次!
具体效果示例如下:
>>> ==== RESTART: D:/Demo/test.py ===== 请输入一个姓名:张三 请输入一个姓名:李四 请输入一个姓名:王五 请输入一个姓名:赵六 请输入一个姓名:田七 请输入一个姓名:刘八 请输入一个姓名:END 您一共输入了 6 个姓名,其中中奖者为: 王五 田七 李四 >>>
注:请预先学过后面课程的同学,不要在本例中使用random模块中的sample等不重复抽取工具。
思路提示本例的整体结构(包括循环录入、显示总人数、循环生成随机数等)都与作业4相同,可以照搬。关键点只在最后抽奖时怎样避免重复,具体如下:

  1. 每次生成一个随机数下标、抽中一个姓名后,调用列表的 pop 方法将这个被抽中的元素从列表中删除,从而避免在下次抽取时选中。
  2. 每次生成随机数时,均要调用一次len函数,获得最新的(删除元素后)的列表长度,从而避免生成的下标越界。

| # Python03复杂结构初步 | 13文本对象易学易用,字串列表同根同源 http://www.ukoedu.com/course?course=python01&chapter=13
陈富贵
| | —- |

Attachment
本节要点与难点

  1. 切片操作中的步长可以为负数,代表从后向前截取。比如 s[8:4:-2] 即从下标8位置截取到下标5位置,每次间隔2,最终得到由 s[8]、s[6] 两个元素构成的新列表。
  2. 利用切片和负数步长可以生成“反转列表”,比如 s[::-1] 即可得到一个与s相反排列的列表。
    Attachment
  3. range也可以使用负数步长,比如 range(10,0,-1) 可以得到 10、9、8、……、1 的序列。利用此特性可以实现倒序的for循环。
  4. 字符串与列表都属于序列(Sequence),因此都可以使用方括号、下标、切片、in 等操作,切片中也都可以使用负数步长。
    Attachment
  5. 字符串创建后不可修改,而列表则可以替换任意下标的元素。如果想修改一个字符串,必须以它为基础创建一个新字符串,再赋值给相应变量。
  6. 可以对字符串使用切片操作,从而构造出一个新的字符串,使其只有指定位置的字符与原字符串不同。
  7. 空字符串也是字符串,只不过长度为0。注意:空字符串不等于“空格”。比如 “ “(引号内含有一个空格)中包含一个空格字符,所以长度为1。而 “” 则没有任何字符,长度为0。
  8. 字符串类的类名为 str ,提供了多种字符串处理方法。
  9. count 方法可以统计字符串内指定内容的总出现次数。
  10. replace 方法可以将字符串中指定内容替换为其他文本。如果替换为空字符串,则相当于删除这些内容。
    Attachment
  11. find 方法可以找到指定内容的出现位置(即下标),常见用法包括:
  • s.find ( a ) 找到字符串a在s中第一次出现的下标位置,如果未找到则返回-1。
  • s.find ( a, x ) 从s的x下标处开始查找a,如果找到则返回首次出现位置,未找到则返回-1。
  • s.find ( a, x, y ) 指定查找范围为s的x下标到y-1下标,在此范围内查找a的首次出现位置,未找到则返回-1。 使用循环结构、配合find的范围参数,可以实现查找全部出现位置的功能,详见视频课程里的示例模板。1. rfind 方法用法与find几乎相同,只不过查找方向从后向前。
  1. 以下列示的 strip 等方法只是创建了新的字符串,不会修改原字符串:
  • strip() 去除字符串两端空格,但不影响中间空格。
  • lstrip() 只去除字符串左端空格。
  • rstrip() 只去除字符串右端空格。
  • lower() 将所有字母转为小写。
  • upper() 将所有字母转为大写。
  • swapcase() 将所有字母大小写互转,结果与原字符串正好相反。
  • title() 将每一段连续字母(相当于“单词”)的首字母大写,其他小写。
  • capitalize() 如果字符串第一个字符为字母,则使其大写。其他字符一律小写(无论字符串第一个字符是否为字母)。
  1. s.split(a) 方法将a视作分隔符,把字符串s拆分为列表。注意:
  • 分隔符不会出现在拆分结果中。
  • 如果原字符串s中连续出现两个分隔符,则视为 “分隔符 空字符串 分隔符” 结构,从而拆分出一个空字符串放入结果列表。
  1. s.join(a) 方法将s视作连接符,把列表a中的所有元素连接为一个字符串。注意:
  • 列表a中的所有元素必须都是字符串(由引号括起来)。
  • 如果s是空字符串,则最终结果相当于把a中元素直接连为一个字符串。
  1. str类的方法简单易用,但面对复杂文本处理时,最佳方式是使用《提高篇》介绍的正则表达式。
    参考阅读:图灵测试本节视频中演示了一个“极简版人工智能”程序,当然,这只是一个程序员之间的小玩笑。不过事实上,真正的所谓人工智能程序最初也无非是从这样一两行代码开始,逐步完善发展起来的。而这个例子从理论角度看,其实可以看作“基于规则型”人工智能系统的最简化版本。
    这个程序试图通过模仿人类问答来体现“人工智能”,而这个思路则来自于计算机科学领域里最经典的问题之一——“图灵试验”。下面就是摘自维基百科的图灵试验简介:
    图灵测试(英语:Turing test,又译图灵试验)是图灵于1950年提出的一个关于判断机器是否能够思考的著名试验,测试某机器是否能表现出与人等价或无法区分的智能。测试的谈话仅限于使用唯一的文本管道,例如计算机键盘和屏幕,这样的结果是不依赖于计算机把单词转换为音频的能力。
    如果一个人(代号C)使用测试对象皆理解的语言去询问两个他不能看见的对象任意一串问题。对象为:一个是正常思维的人(代号B)、一个是机器(代号A)。如果经过若干询问以后,C不能得出实质的区别来分辨A与B的不同,则此机器A通过图灵测试。
    1956年达特茅斯会议之前,英国研究者已经探索十几年的机器人工智能研究。比率俱乐部是一个非正式的英国控制论和电子产品研究团体,成员包括艾伦·图灵。
    1950年,图灵发表了一篇划时代的论文,文中预言了创造出具有真正智能的机器的可能性。由于注意到“智能”这一概念难以确切定义,他提出了著名的图灵测试:如果一台机器能够与人类展开对话(通过电传设备)而不能被辨别出其机器身份,那么称这台机器具有智能。这一简化使得图灵能够令人信服地说明“思考的机器”是可能的。论文中还回答了对这一假说的各种常见质疑。图灵测试是人工智能哲学方面第一个严肃的提案。
    2014年6月8日,首次有电脑通过图灵测试,尤金·古斯特曼成功在雷丁大学(University of Reading)所举办的测试骗过研究人员,令他们以为“它”是一位名为Eugene Goostman的13岁男孩,但后来有文章指它其实并非真正地通过了测试。
    根据人们的大体判断,达成能够通过图灵测试的技术涉及以下课题:
  • 自然语言处理
  • 知识表示
  • 自动推理
  • 机器学习 但是为了通过完全图灵测试,还需要另外两项额外技术课题:计算机视觉 与 机器人学。

作业1:反向步长请在控制台中完成以下操作:

  1. 创建一个列表并赋值给一个变量 x ,列表内容可随意安排。
  2. 使用切片方式,将该列表的倒序排列赋值给另一个变量 y 。
  3. 分别查看 x 和 y 的内容,理解二者的区别和原因。
  4. 执行 x.reverse() ,再观察x和y的内容,理解其变化和原因。
  5. 想办法在只使用切片操作的情况下,让 y 等于其自身的倒序排列,相当于 y.reverse() 的效果。 完成之后,请将列表改成字符串,然后重新尝试上述过程(除了第4个步骤,因为字符串类没有reverse方法)。
    思路提示本例完全是对课堂内容的重复,主要是为了让初学者理解相关概念和原理。
    对于最后一个问题,可以使用 y = y[::-1] 实现。

作业2:DNA序列分析 在生物学中,DNA可以表示为一个由 A、C、G 和 T 四种字母构成的长字符串,例如“ACGAATTCCGACCGAATTCG” 。生物学家经常需要在这些字符串中查找指定字母的个数和出现位置,从而了解DNA的结构以便进一步分析遗传特征。
请编写程序,允许用户输入一个DNA字符串,然后使用 find 或 rfind 方法实现下面要求的各种DNA分析功能(下面每种功能可以分别单独编写一个程序实现):

  1. 告知该输入字符串中出现过多少个“GA”。
  2. 告知第一个“GA”出现在哪一个位置,即“GA”中字母“G”的下标。
  3. 依次告知所有“GA”的出现位置。
  4. 从后向前,依次告知所有“GA”的出现位置。即先输出最后一个“GA”的位置,再输出倒数第二个“GA”的位置,如此类推。
    思路提示本例是对 count 、find 和 rfind等方法的综合运用。主要注意的问题是:
  5. 为避免用户误输入小写字母(比如“AGcnGaN”)导致查不到“GA”,建议在获取到用户输入后首先将其转换为大写形式。
  6. 输出所有“GA”的方法可以参考视频课程中“循环查找所有内容”的模板代码。
  7. 从后向前查找可以使用 rfind 方法,具体用法请按照视频课程的演示,自行查阅python中文文档。
  8. 循环使用 rfind 时,请注意每次下标范围的变化——每次的范围都是从下标0开始查找,但结束位置应在上一次找到的下标之前。

作业3:AI升级本节视频演示了一个“极简版人工智能”程序。请进一步完善该程序,使之能够按照下面的方式回答问题:

  • 问:今天下雨吗? 答:今天下雨。
  • 问:你有饮料吗? 答:我有饮料。
  • 问:你们吃饭吗? 答:我们吃饭。
    思路提示在视频课程的案例中,程序把 “吗?” 替换为 “。” 。而仔细观察本作业的示例效果,可以发现只需再把“你”都替换为“我”,就能够实现要求。所以思路如下:
  1. 获得用户输入后,使用replace将其中的 “吗?” 替换为 “。” ,将替换结果保存到一个变量中(建议仍然使用原字符串变量)。
  2. 让这个字符串变量再执行一次replace方法,把“你”替换为“我”。将替换结果保存到一个变量中。
  3. 打印该字符串即可。

注:由于本课程到这里还没有介绍“链式操作”,所以上面每个步骤都需要使用一个变量保存替换结果。在后面学习了“链式操作”后,可以直接写成 s.replace(“吗?”,”。”).replace(“你”,”我”) 这种形式。

作业4:AI再升级作业3中的AI问答程序无法分辨“负面词语”,因此常会闹出笑话。比如问“你是魔鬼吗?”后,它会回答“我是魔鬼”;询问“你有毒品吗?”,它则会回答“我有毒品”。
所以,请进一步完善该程序,使之能够分辨出我们指定的若干负面词语,并在回答时使用“不是”或“没有”。
作为测试,这些负面词语包括五个:“魔鬼”、“坏蛋”、“傻瓜”、“毒品”、“军火”。后面可以随自己需要随意添加。
本作业只需考虑“是”和“有”两种问法,分别回答“不是”、“没有”即可。其他情况暂不考虑。
思路提示显然,本程序除了作业3中那几条替换规则外,还需要再增加一个判断语句——如果用户输入的字符串中包含负面词之一,就将“是”都替换为“不是”、将“有”都替换为“没有”。而问题的关键在于怎样判断字符串中是否包含某个负面词。
最“笨拙”的方法是编写五个判断语句,每个判断语句分别使用 in 操作符判断该字符串是否包含某一个负面词。但是这种方法几乎无法扩展,一旦将来增加到100个甚至1000个负面词,就无法应付。因而更好的办法,是模仿第十一回中“唐诗题库”的设计,使用一个列表保存所有负面词,具体思路如下:

  1. 获得用户输入(假设为变量s)后,按照作业3中的替换规则,替换掉“吗?”、“你”和“我”。
  2. 创建一个列表(假设为变量a),每个元素都是一个负面词字符串。
  3. 编写一个 for x in a 的循环,遍历列表中每一个负面词,每次循环 x 就是一个负面词。
  4. 在这个 for 循环中,使用 if x in s 判断该负面词是否出现在字符串 s 中。
  5. 如果出现了负面词,则将 s 中的“是”都替换为“不是”、“有”都替换为“没有”,然后用break跳出循环,因为没有必要再检查其他负面词了。
    作业5:地址倒转我国通行的邮政地址格式为 “从大到小” ,比如“中国 上海市 黄浦区”;而其他一些国家则使用“从小到大”的地址格式,比如 “黄浦区,上海市,中国”。
    请使用本节讲解的字符串类split方法和join方法,实现一个针对上述情境的 “地址倒转” 程序,要求如下:
  6. 用户按照“从大到小”模式输入一个地址,以空格为各级地址的分隔符,比如 “中国 上海市 黄浦区”。
  7. 程序对用户输入进行处理,最终输出“从小到大”格式的地址,并以逗号为分隔符,比如 “黄浦区,上海市,中国”。 完成之后,请将列表改成字符串,然后重新尝试上述过程(除了第4个步骤,因为字符串类没有reverse方法)。
    思路提示本例涉及到“分割提取各级地址”,以及“对各级地址进行倒转”两个操作。直接对字符串倒转(使用切片)是不可行的,因为会将“中国”改成“国中”。所以可以考虑列表的reverse方法:
  8. 将字符串按照空格为分隔符,使用split方法拆分为一个列表,其中每个元素是一个地名;
  9. 对该列表使用reverse方法,使其所有地名的出现顺序(下标)倒转;
  10. 以逗号为连接符,即用逗号字符串执行join方法,将倒转后的列表拼接成一个字符串,即最终结果。

| # Python03复杂结构初步 | 14三行代码搞定文件读写,两种括号分清元组集合 http://www.ukoedu.com/course?course=python01&chapter=14
陈富贵
| | —- |

基础篇3 复杂结构初步 - 图20本节要点与难点

  1. 在Python中,只要是对象就可以直接用点号形式调用其方法属性,比如 ‘abcd’.find(‘a’) 、[1,2,3,4].count(2) 等。
  2. 将对象赋值给变量时(比如 x = ‘abcd’ ) ,变量只是该对象的一个“名字”,并非该对象本身。可以随时再次通过赋值语句,让该变量指向其他对象。Attachment
  3. “序列”包括列表、字符串和元组。其中元组是一种特殊的列表,使用圆括号定义,并且禁止替换内部元素。Attachment
  4. 元组只是在定义内容时使用圆括号,而在使用下标、切片等操作时,仍然使用方括号。
  5. 在很多情境下,Python会要求必须使用不可变的列表,此时即可使用元组。
  6. 读写文本文件一般包括三个步骤:
  • 用open函数打开文件,并使用参数 ‘r’ 、‘w’、‘a’ 等指定读写模式。该函数返回一个文件对象。
  • 调用文件对象的 readlines 或 write 等方法操作文件。readlines 返回一个列表,可用for in 循环扫描每一个元素,即文件中每一个段落。
  • 文件操作后必须使用close方法关闭,否则写入文件的内容可能会丢失。Attachment
  1. 文件对象的 readlines 方法可以把文本文件的内容读入一个列表,其中每个元素是文本文件中的一行(或称一段,即使用 “回车换行” 隔开的内容)。
  2. Python程序中宏,字符串 ‘\n’ 代表 “换行符” 。
  3. 文件对象的 write 方法可以把字符串存入文件。如果需要把列表里的所有字符串存入文件、并且每个字符串单起一行,可以使用 “\n”.join(list) 先将列表拼合成一个“大字符串”。
  4. 集合与“序列”类似,定义时使用花括号将所有元素包含起来。集合内的元素间不存在先后顺序,因此也不能通过下标引用其中元素。
  5. 集合中不存在重复元素。如果为集合赋值时提供了重复元素,Python会自动去除重复。利用这一特性,可以实现对列表、字符串等容器的“自动去重”操作。
  6. 内置函数 set 可以根据序列、字典等对象生成对应的集合(并去除重复)。
  7. 内置函数 list 可以根据集合、元组、字符串等对象生成对应的列表。
  8. 列表的 extend 方法可以直接将另一列表的元素添加进来,而不需要像列表加法那样创建新列表,相应的具有更好的效率和可读性。Attachment
    作业1:元组试验请在控制台中完成以下操作:
  9. 创建一个元组并赋值给一个变量 x ,元组内容按顺序为 2、’a’、3、2 四个元素。
  10. 通过切片方式,取得 x 的第2~4个元素(即下标 1~3),并赋值给变量y。然后观察变量y的内容和类型。
  11. 尝试让 x 执行reverse()方法(就像列表那样),观察结果并思考原因。
  12. 输入 a = list(x) ,再观察 a 的内容和类型,思考原因。
  13. 思考:python 中提供了内置函数 list ,可以将各种数据转换为列表;提供了内置函数 set ,可以将各种数据转换为集合。那么有没有可能也提供了某个内置函数,可以将列表等数据转换为元组呢?请查阅Python中文文档https://docs.python.org/zh-cn/3/library/index.html验证自己的想法(提示:元组的英文名为 tuple)。
  14. 结合前面几步的思考,请想出一个办法,能够得到一个与 x 顺序相反的新元组,并赋值给一个变量。 如果无法得出上述问题的答案,可以参考“思路提示”中的说明。但是请一定先自己试验并思考!
    思路提示本例重点让初学者理解元组的“元素不可替换”特征,以及list转换函数等等。具体要点如下:
  15. x = (2,’a’,3,2)

  16. y = x[1:3]>>> y可以看到 y 也是一个元组(圆括号)。

  17. x.reverse() 会导致错误,因为列表的 reverse 方法含义为修改该列表,使之反序;而元组在创建后禁止修改,所以不可能提供reverse这种能够修改自身的方法。建议使用 help(tuple) 或查阅Python文档,查看元组类都有哪些方法。
  18. a = list(x)>>> a可以看到 a 是一个列表,因为 list 函数可以根据序列、集合等创建一个列表。

  19. 查阅文档可知,内置函数中有一个 tuple ,顾名思义,就是根据列表等数据创建元组。
  20. 本题思路与视频课程中最后对列表删除重复的思路一致:先用list函数根据元组生成一个列表;再应用列表的reverse方法改为倒序;最后用tuple函数将列表转为元组即可:>>> y = list(x)>>> y.reverse()>>> x = tuple(y)

作业2:读文本文件请首先下载本作业所需的文本文件(右键另存到自己电脑):诗词题库文件接下来请编写程序,将该文件中的每一行内容都显示输出到屏幕上(使用print语句)。
思路提示本例是最基本的读文件操作练习,请注意以下要点:

  1. open函数中一定要写全文件保存路径,并建议使用斜线“/”作为路径分隔符(反斜线写法会在后面课程中介绍)。
  2. 文件路径必须是一个字符串,用引号括起来。
  3. 程序结束前必须使用close方法关闭文件对象,否则可能会导致“无法打开文件”等各种问题。
  4. 可以使用 readlines 将文件内容读入一个列表,再使用 for in 循环把列表中每个字符串取出并用print打印。
    作业3:从文本文件创建列表
    仍然使用作业2中的文本文件。请编写一个程序,并在程序开始时定义一个空列表(例如 a = [] )。然后用程序读取该文件,每读取一行,就将该行内容添加到该列表中。程序结束时将列表内容显示在屏幕上以供校验。
    注意:虽然我们可以将 readlines 读入的列表直接添加到程序里面的空列表中,但是在本例练习中,请务必在 for in 循环里逐个添加每行文字,以便下一道练习题之用。
    思路提示本例与作业2结构相同,只是增加了一个空列表(假设变量名为 a ),并且在 for in 循环中把 print 改为向列表中添加元素。因此使用列表的 append 方法即可。
    作业4:从文件创建题库请根据作业3中的代码,改写第十一回《入列表五湖四海皆兄弟》中的第一个“随机抽取诗词题库”案例(即该章视频第7分钟左右的案例,只使用一个列表出题、不使用答案列表)。修改后的题库完全保存在作业3的文本文件中,而该抽题程序每次运行时会从该文本文件读出所有题目,再执行之前的随机抽取和出题操作。
    思路提示本例只要把视频课程案例代码中的 “ questions = [‘白日依山尽’…] ” 一段,改成作业3中根据文本文件创建列表的代码即可。而在填充完该列表之后,随机抽题等逻辑与之前完全相同。所以思路如下:
  5. 程序开始时,将questions改为空列表;
  6. 然后使用作业3中的文件操作和for循环,每读取一行就像questions中添加一个字符串;
  7. 循环结束后关闭文件。此时questions已经包含所有习题,效果与原案例代码中的列表定义完全相同。
    作业5:基于文件的完整题库对作业4中的题库程序进行完善,使之能够判断学生回答是否正确(效果等同于第十一回课程11分40秒处的案例)
    本作业需要更新题库文本文件,请点击此处下载
    下载上面的题库文件后,请先观察其中每道题的格式(注意问题与作者之间使用 “/” 隔开)。然后编写程序,使之根据该文件自动生成两个列表,分别存放问题与答案,然后与原案例一样能够随机出题并判断学生输入答案是否正确。
    思路提示本例与作业4一样,都是对视频课程中原案例代码(本次是11分40秒处的代码)的前半部分进行升级,使列表定义从代码中“写死”改为从文本文件中读取。问题的关键在于怎样根据一个文本文件生成两个列表,思路如下:
  8. 程序开始时,将questions和answers都改为空列表;
  9. 然后使用作业4中的文件操作和for循环,读取一行文本字符串到循环变量;
  10. 观察发现,每行文本(每道题)的题干与答案都是用“/”隔开,那么就可以把循环变量用字符串对象的split方法拆分,得到一个字符串列表(假设为变量 k);
  11. 字符串列表k中有两个字符串,分别为题目与答案。因此可以把第一个(k[0])添加到questions列表中,第二个(k[1])添加到answers列表中。
  12. 循环结束后,两个列表都已填好,效果与原案例代码中的列表定义相同。后面即可照搬原出题评审代码。
    作业6:集合与写文件请自己重写本节视频课程中最后一个案例“从英语文章创建单词表” ,以理解使用集合去除重复、以及使用write方法写入文件的基本过程。
    请在看懂视频中的讲解后再练习本作业,而在书写本作业代码时请不看或少看视频中的演示,尽可能通过自己的理解和调试纠错来实现目标。
    在完成单词表程序后,请思考三项改进措施:
  13. 每次运行该程序时,可否由用户指定一个英语文章的文件名,而不是像现在这样把文本文件的路径“写死”在代码里?
  14. 假如多次运行该程序、每次读入不同文章,可否把所有这些文章的单词都汇总到同一个单词表文件中?
  15. 完成措施2后,再编写一个程序,直接对这个单词表文件中的单词进行删除重复操作,从而得到一个标准单词表。 本例不提供参考答案,大家直接参照视频完成基本作业,然后参照“参考思路”尝试三项改进措施即可。
    思路提示对于本例的两个完善措施,思路如下:
  16. 对于第一项措施,只要在程序开始时使用input函数,要求用户输入一个文件路径;再将输入保存到一个字符串变量(比如fs)中,然后 open(fs,’r’) 即可。
  17. 对于第二项措施,只要将第二个open函数(即写入文件)的读写模式从 ‘w’ 改为 ‘a’ ,也就是“保留原内容并追加内容”,详见视频课程中讲解open函数时的屏幕板书。
  18. 对于第三项措施,只要再编写一个简单程序即可。该程序首先用readlines读取单词表文件,并得到一个列表,而该列表中每个元素都是单词表中一个单词(因为一个单词占一行);然后只需用set函数根据该列表生成一个集合,再用list将其转化为列表,即可去除重复,写入另一个文件作为标准单词表。

| # Python03复杂结构初步 | 15嵌套容器表达层级隶属,字典对象体现键值组合 http://www.ukoedu.com/course?course=python01&chapter=15
陈富贵
| | —- |

Attachment本节要点与难点

  1. 列表、集合、字典、元组等容器类型可以相互嵌套,以便表达复杂结构,比如 x = [ ‘abc’ , 15 , (7, {‘东邪’:’East’,’西毒’:’West’} ) , ‘def’] 。AttachmentAttachment
  2. 引用嵌套容器内的元素时,按从左到右顺序逐级指明下标或“键”等标识,比如 x[2][1][‘东邪’]。
  3. 字典也使用花括号定义,每个元素均为“键:值”的形式,称为 “键值对”。这些元素不存在顺序,因此不能按照下标引用。
  4. 引用字典内部元素的方法是 “字典名[键]” ,得到的是字典中该键所对应的值。假如字典中不存在该键,则会报出“KeyError”错误。点击下载基础篇3 复杂结构初步 - 图29
  5. 也可以使用字典的get方法取得键值,并且即使字典中不存在该键,也不会报出错误,具体用法会在后面课程讲解。
  6. 字典内元素的键不能重复。如果为字典定义或添加了多个键相同的元素,最终只会保留其中最后一个元素的值。
  7. 定义字典时切忌将冒号放到字符串内,否则该冒号将被认作普通字符而非键值分隔符。Attachment
  8. 可以使用数字、字符串以及各种“不可变类型”(比如元组)作为字典的键,而“可变类型”(比如列表)不能作为字典的键。详细内容请见《提高篇》。
  9. 字典的值可以是任何类型数据,也可以多次重复(即不同的键对应相同的值)。
  10. 当字典 d 中存在键为 k 的元素时, d[k]=x 既可以把该元素的值改为 x ;如果 d 中尚不存在键 k ,d[k]=x 则会添加一个新键值对“k:x”。Attachment
  11. 当需要修改字典中某个元素的键,比如把d中元素“k1:v”改为“k2:v”,需要先用 d.pop(k1) 删除该元素,再用 d[k2]=v 添加新键值对。不过由于pop方法在删除元素的同时可以返回被删除元素的值,所以这两个步骤可以合并为 d[k2]=d.pop(k1) 。
  12. 对于字典 d ,如果使用 for x in d : 循环遍历字典元素,则每次循环时变量 x 等于该元素的键,需要在循环体内用 d[x] 才能得到该元素的值。
  13. 在使用open函数打开中文文件时,假如出现“UnicodeDecodeError:’gbk’ codec can’t be decode”的错误,可以在调用open函数时添加参数 encoding=’utf-8’ 解决。(假如未出现过该错误,则无需使用encoding参数!)

作业1:基本操作
请在控制台中完成以下操作:

  1. 创建一个列表并赋值给一个变量 s ,列表内容按顺序为 ‘a’ , 2 , ‘b’ 三个元素。
  2. 创建一个字典并赋值给一个变量 d ,字典内容为 3:’Three’ , 2:’Two’ , 4:’Four’ 三个键值对。
  3. 将列表 s 中的元素 ‘b’ 替换为字典 d。
  4. 将字典 d 中的 “ 3:’Three’ ” 改成 “ 3:’叁’ ”。
  5. 将字典 d 中的 “ 2:’Two’ ” 改成 “ ‘二’:’Two’ ”。
  6. 新建一个元组并赋值给一个变量 t,其中第一个元素为列表 s ,第二个元素为字典 d 。
  7. 请使用类似 t[0][0][0] 的形式,打印元组t中的列表s的字典d中的键为4的值。
  8. 同上,请使用类似 t[0][0] 的形式,打印元组t中的字典d中的键为4的值。
  9. 思考:7和8两个步骤的相同点与共同点是什么?
  10. 先思考表达式 t[0][2][4][2] 的输出结果是什么,然后再输入到控制台中实际运行、观察结果。 如果无法得出上述问题的答案,可以参考“思路提示”中的说明。但是请一定先自己试验并思考!
    思路提示本例重点让初学者理解容器嵌套、字典基本操作,不过也涉及到了一些与变量本质有关的内容(我们会在《提高篇》中深入解释)。具体要点如下:
  11. s=[‘a’,2,’b’]

  12. d={3:’Three’,2:’Two’,4:’Four’}

  13. s[2]=d

  14. d[3]=’叁’ 。这一步也可以写成 s[2][3]=’叁’ ,因为经过第3步后,s[2] 与 d 都指向同一个字典对象。

  15. d[‘二’]=d.pop(2) 。同上,这一步也可以用 s[2] 代替 d 。

  16. t = (s, d)

  17. t[0][2][4]

  18. t[1][4]

  19. 7、8 两步效果相同,因为变量 d 、元组元素 t[1] 、列表元素 s[2] 乃至 t[0][2] 都指向了同一个字典对象。
  20. 结果是字符串 ‘u’ 。因为字符串也是一个“容器”,同样可以用方括号引用其中指定下标的字符。

作业2:嵌套列表读取题库文件请对前一节课(第14回)作业5中的题库程序进行修改。原题目中将题库文本文件(点击此处下载)读入两个列表,现在请修改为只使用一个嵌套列表完成。
思路提示本例读取文件和解析文件中字符串的过程与第14回作业5相同,只不过在读入列表时需要修改。思路如下:

  1. 程序开始时,创建一个空列表比如 tiku ;
  2. 然后使用作业4中的文件操作和for循环,读取一行文本字符串到循环变量;
  3. 观察发现,每行文本(每道题)的题干与答案都是用“/”隔开,那么就可以把循环变量用字符串对象的split方法拆分,得到一个字符串列表(假设为变量 k);
  4. 将该列表直接添加到列表 tiku 中,注意要使用append方法添加。(此处请思考为什么不可以使用extend方法,必要时可亲自试验观察)
  5. 循环结束后,列表即已填好。后面出题和评审的代码中,使用 tiku[i][0] 和 tiku[i][1] 代表第i个题目的问题与答案即可。

作业3:字典读取题库文件请对作业2中的题库程序再次修改,从使用嵌套列表改为使用字典完成。
思路提示本例与视频课程中使用的题库案例思路相同,只不过多了读取文件的过程。
因此本例要点在于:每次循环读出一行文本、并使用split拆分成字符串列表(假设为k)后,应当创建一个字典对象,并将列表的第一和第二个元素分别存为该字典的两个键值对,比如 d = { ‘问题’:k[0], ‘答案’:k[1] } 。然后再将字典d添加到题库列表(假设为tiku)中作为一个元素即可。
同样,后面调用题目和答案时,需要使用 tiku[i][‘问题’] 的形式。
作业4:字典翻译日语星期日本采用“日月五行”为星期中的每一天命名。具体星期一至星期日的名称分别为:月曜日、火曜日、水曜日、木曜日、金曜日、土曜日、日曜日 。
请编写Python程序,使用字典保存上述对应规则,即 “星期二”:“火曜日” 的形式。然后完成以下两个功能(可以分别编写两个程序完成):

  1. 在屏幕上循环输出字典全部内容,形如:
    星期一:月曜日 星期二:火曜日 星期三:水曜日 …… ……
  2. 使用input函数,请用户输入中文的日期名(比如“星期一”),然后程序会自动输出对应的日文名称。
    思路提示本例属于字典最基本的应用方法,视频课程中在讲解用 for in 循环扫描字典时已经举过完全相同的案例,可以参照理解。
    作业5:字典实现词频统计请参照本节视频课程中 “《红楼梦》十二钗名称频度统计” 的案例,下载你喜欢的小说(文本文件)并统计主配角姓名出现次数。
    思路提示本例完全参照视频课程中《红楼梦》案例即可。只需注意使用 open 打开文件时,如果发生Unicode解码错误,需要考虑设置 encoding 参数。

| # Python03复杂结构初步 | 16自定义函数减免重复,第三方模块荟萃精华 http://www.ukoedu.com/course?course=python01&chapter=16
陈富贵
| | —- |

Attachment本节要点与难点Attachment

  1. 将通用代码定义为函数,即可在其他程序中直接按名调用,无需重复书写。当需要修改时,也只需修改函数定义中的代码,其他调用该函数的程序均无需修改。
  2. 定义一个函数的基本格式是 def 函数名(参数列表): ,其中函数名的命名规则与变量相同。参数个数可以是零个、一个和任意多个,多个参数间用半角逗号隔开。
  3. 从def语句最后的冒号开始进入函数体,其中每一行代码都应相对于def统一缩进。如果后面某条语句不再相对于def缩进,则该语句不再属于函数定义,或者说函数体在这一句之前结束。
  4. 默认情况下,在def语句中定义的参数名称、以及函数内部定义的各个变量名称,均只能在这个函数内部使用。如果函数外部存在同名的变量,这些同名变量也互不相干。
  5. 使用 def 只是定义了一个函数,并不会真正运行该函数。Python在执行一个程序时,会自动跳过 def 及函数体的内容,直到某行语句调用了这个函数,才会跳转到这个函数,并逐行执行其函数体。基础篇3 复杂结构初步 - 图34
  6. 当在函数内执行到 return 语句时,python就会跳出该函数,返回到调用该函数的地方。如果 return 语句后面指明了返回值,则将该返回值传递给调用方作为函数运行结果。
  7. 使用 dict.fromkeys( 列表, 默认值 ) , 可以将列表内所有元素作为键、默认值作为值,创建一个字典并返回。
  8. 对于含有大量分支的类似if x==5: y=’a’ elif x==6: y=’b’ elif …的判断结构,往往可以通过字典形式加以简化,比如 {5:’a’,6:’b’,……} 。
  9. 模块就是Python程序文件(.py文件),可以使用import语句在其他程序中直接调用。如果想把一个py文件中的多个函数供给其他程序调用,可以把这个py文件与调用它的py文件保存在同一个路径下,也可以把这个py文件保存到Python指定的目录中(比如Python默认安装路径,或环境变量PYTHONPATH指定的目录)。基础篇3 复杂结构初步 - 图35
  10. 安装第三方模块的最简单方式,是使用 pip 工具。对于2.7.9版本之后的 Python2 安装包,以及3.4版本之后的 Python3 安装包,pip工具属于默认安装内容;而对于之前版本的 Python ,需要手动安装(详见本节参考资料)。
  11. 不能在IDLE等Python交互窗口中运行pip,应当在Windows命令行窗口、Linux/Mac字符终端等窗口中运行匹配,常用形式如下:
  • pip list 查看已经安装的第三方模块及版本。
  • pip install 模块名 从官方资源网站https://pypi.org下载安装指定名称的第三方模块。
  • pip install -i 镜像站点名 模块名 从指定的镜像站点下载安装指定名称的第三方模块。国内常用镜像站点见本节参考资料。
  • pip uninstall 模块名 卸载已经安装的指定模块。
  • python -m pip install -U pip (在Windows下)或 pip install -U pip (在Linux/Mac下) 对pip工具本身进行升级,保持pip最新版本以便顺利下载最新资源。
  1. jieba 是截止本课程制作时,最流行的Python中文分词模块。jieba.lcut(s) 可以对字符串s(无论中英文)自动分词、将结果存入一个列表并返回。该函数还有很多高级选项,同时jieba模块也提供了其他实用方法和配置选项,后面课程还会深入介绍。

参考资料1:pip安装升级教程首先请明确:如果你安装的是 Python2.7.9 之后版本的 Python2,或者 Python3.4 之后版本的 Python3,那么 pip 应该在安装Python时已经被默认安装,没有特殊情况无需再次手动安装。不过可以考虑随时升级pip,使之保持在最新版本:
pip 版本升级命令:

  • 如果使用的是Windows操作系统,请在命令行窗口输入命令并回车: python -m pip install -U pip
  • 如果使用的是Linux或Mac系统,请在终端窗口中输入命令并回车: pip install -U pip

如果需要手动安装pip,可以采用“在线安装”和“离线安装”两种方式,具体如下:

  1. 在线安装
    在线安装方式只需在官网下载一个Python程序形式的安装脚本,然后运行该程序即可自动完成。具体步骤为:
  2. 打开浏览器,进入pip官网安装说明页面 https://pip.pypa.io/en/stable/installing/ ,在页面中找到下载链接 get-pip.py :
    Attachment

  3. 在该链接上点击鼠标右键,在弹出菜单中选择“另存为…”,从而将 get-pip.py 文件保存到自己指定的文件夹中。

  4. 在电脑中打开这个文件夹并运行 get-pip.py 程序。具体方式可以是双击运行,也可以在命令行窗口中运行。比如以Windows为例,假设该文件保存在D盘根目录,则可以在命令行窗口中输入: python D:\get-pip.py 。
    Attachment

  5. 离线安装
    离线安装方式允许我们先将整个pip工具下载到本地电脑或拷贝到其他电脑上,然后可以在不联网的情况下直接安装。具体步骤包括:

  6. 打开浏览器,进入pip官网首页 https://pypi.org/project/pip/ ,在页面左侧导航栏中找到 “Downloads files” :
    Attachment

  7. 点击“Downloads files”从而看到类似下图的文件列表:
    Attachment

  8. 下载以“.tar.gz”为扩展名的压缩包文件,然后使用 winzip、winrar、7-zip 等解压文件打开,可以得到一个名字类似 “pip-XX.X.X.tar” 的压缩文件。
    1. 再解压得到的这个tar文件,再次用压缩软件解压到一个文件夹中。然后运行其中的 setup.py 文件即可。具体运行方法与在线安装相同。
    参考资料2:怎样设置pip镜像目前国内用户最常使用的 pip 镜像下载网站包括:

一般情况下,使用本节视频课程中介绍的 install -i 命令即可从上述来源安装第三方模块,比如: pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/但是有些用户可能会发现安装失败,并看到类似 “Cannot unpack file” 或 “pypi.tuna.tsinghua.edu.cnis not a trusted or secure host” 的错误。对于这种情况,请按以下两个步骤排查解决:

  1. 检查 pip install 命令中是否忘记书写 “-i” ,没有书写可能会引发上述错误,需要补充后重新运行。
  2. 如果仍然存在错误,则在命令中再增加一个选项:“ —trusted-host 镜像网址 ” 即可。比如: pip install -ihttps://pypi.tuna.tsinghua.edu.cn/simple/jieba —trusted-hosthttps://pypi.tuna.tsinghua.edu.cn/simple/本选项的含义是将所用镜像网址指定为Python“可以信任”的安全来源,以免Python出于安全考虑自动阻止下载操作。注意truested前面是两个减号!

上述命令行方式的缺点在于:每次输入命令都要写上URL,非常繁琐。所以可以采用下面的方法,将镜像地址写入pip的配置文件,从而每次安装模块时无需填写镜像URL。具体方法为:

  • Windows系统永久设置方法:
  1. 打开并进入系统盘用户文件夹,一般格式为 C:\Users\你的用户名\AppData\Roaming 。也可以在Windows文件管理器的地址里直接输入 %APPDATA% ,文件管理器会直接进入该文件夹。
  2. 在该文件夹下查找名为pip的子文件夹,如果没有则新建子文件夹pip。
  3. 在该文件夹下新建一个文本文件,并用记事本打开,写入下面内容: [global] timeout = 6000 index-url =https://pypi.tuna.tsinghua.edu.cn/simple/ trusted-host =pypi.tuna.tsinghua.edu.cn
  4. 保存该文本文件,并将文件名改为“pip.ini”,注意扩展名不是txt而是ini。
  • Mac OS 和 Linux系统永久设置方法:Mac/Linux系统下的设置过程与Windows相同,都是创建或修改pip配置文件。只不过在Mac/Linux下,pip配置文件的路径和名称为 ~/.pip/pip.conf ,注意扩展名不是 ini 。

作业1:创建简单的函数与模块请编写一个计算三角形面积的函数。该函数接收两个参数,分别代表底边长度与三角形高度(假设变量名为a与h);然后该函数按照公式“底乘高除以2”,计算出面积数值并返回。
编写完毕后,请在同一python文件中编写一段调用该函数的代码。该代码允许用户通过input输入底边和高度的数值,然后调用这个函数计算并显示结果。
在完成上述任务后,请把程序中的函数剪切并粘贴到一个新建的Python程序文件中,然后为新文件命名并保存在同一目录下。接下来修改原来的程序文件,使之通过import语句,以调用模块的方式引用该函数。
思路提示本例是对本节视频课程中函数与模块基本功能的复习,并没有特别难点。实际完成时注意以下细节即可:

  1. 书写函数注意缩进。
  2. 将函数代码移植到新建的模块文件后,一定要确认新文件与原调用文件在同一目录下。
  3. 引用模块文件时,import语句后面的模块名不应包含文件扩展名“.py”。
  4. 调用函数时,要写全 “模块名.函数名” , 不要漏写模块名。
    作业2:扩充自定义模块请在作业1创建的模块文件(即包含函数的python程序文件)中再添加一个函数,用于计算圆形面积。该函数接收一个代表半径的参数,并返回面积数值。然后请编写简单的程序,调用该模块,并分别使用其中的两个函数,计算出“底为3,高为2的三角形”和“半径为3的圆形”的面积。同时请注意:python标准库math模块中,提供了一个比较精确的圆周率数值,保持在该模块的变量 pi 中,可以通过 math.pi 直接使用。
    在完成上述步骤后,请再向该模块中添加一个函数。该函数的作用只是告诉调用者本模块有哪些函数、它们的功能是什么。具体来说,该函数不需要接收任何参数,也不需要返回任何数值;每当被调用时,它只是通过print语句在屏幕上显示我们事先写好的帮助信息。请编写完成该帮助函数,然后在前面调用面积函数的程序里再调用该函数。

思路提示本例是对本节视频课程中自定义模块的补充,即“一个模块可以包含多个函数”,以及“函数可以没有参数或返回值”。实际完成时,只需注意几个细节:

  1. 计算三角形面积与计算圆形面积的两个函数之间互不隶属,因此二者的def应该平行对齐,不存在相互缩进。
  2. 如果圆形面积函数需要使用python标准库中自带的圆周率变量,应当在该模块文件的开头 import math。
  3. “帮助函数”的参数列表只是一对圆括号,return语句后面不需要指明返回值。
  4. 在其他程序中调用帮助函数时,无需使用等号和赋值语句接收任何返回值。
    作业3:制作随机抽奖模块请编写一个“随机抽奖”函数。该函数接收两个参数:首先是一个字符串列表,其中每个字符串都是一个人名;第二个参数是数字,代表计划中奖人数。该函数将在这些人名中按照指定人数抽取若干姓名,保存到一个新列表并返回。(此步骤不允许使用random.sample等现成的函数)将该函数保存到一个自定义模块中,然后另外编写一个调用该函数的抽奖程序。这个程序首先从一个文本文件中读取人员名单(文本文件请自行创建,每行一个姓名即可),然后调用该函数进行抽奖,并将中奖名单显示在屏幕上。
    第一次完成本作业时,随机抽取的中奖人姓名可以重复,即允许同一姓名被反复抽中多次。而在完成此任务后,请进一步修改函数,使之实现不重复抽奖。针对不重复抽奖的实现方法,可以自行在python标准库文档里,查找random模块中sample函数的说明并使用。而在完成后,强烈建议大家思考:怎样在不调用该函数的情况下,自己写代码完成同样的功能。具体可见本作业思路提示。
    思路提示本例的目的是帮助大家理解“怎样将多个数据作为一个列表传递给函数”,以及“怎样将多个结果通过一个列表返回给调用方”,同时也涉及到一些有趣的集合算法。具体要点如下:
  5. 函数接收到参数后,可以根据参数二(中奖人数)做一个指定次数的循环,每次循环抽取一个姓名;
  6. 进入上述循环之前,首先定义一个空列表用于保存中奖人名单;
  7. 每次循环时,使用随机数抽取一个姓名,然后使用列表的append方法将其添加到中奖人名单列表;
  8. 循环结束后,将中奖人名单列表指定为return语句的返回值。
  9. 对于不使用random.sample函数实现“不重复抽奖”,可以考虑对上述循环过程进行修改:每次循环在抽中一个姓名后,就在原人名列表中使用remove方法删除该姓名。这样下次循环再抽取时,就不可能再次抽中之前的中奖人。不过需要注意的是:每次生成的随机数范围必须小于最新的列表长度(因为每次循环列表长度都会减一)。
    作业4:探索更多专业分词模块jieba只是目前常见的python中文分词模块之一,互联网上还有很多其他专业团队开发的中文分词模块,比如麻省理工学院团队的SnowNLP、清华大学清华大学自然语言处理实验室的THULAC等。
    请通过百度、谷歌等搜索引擎,自己浏览这些模块的名称、特点与用法,然后选中其中一个,通过 pip 命令下载安装。安装后请用这个新的分词模块,修改本节课程视频中的单词表案例,即将jieba替换为这些模块。