参考链接: Python中的运算符函数| 1
第一章 基础部分
1、输入输出
代码:
name = input('你叫什么名字:')
print ('我将称呼你:' name)
2、除法运算
运行一下程序。 让我们来看一下输出的结果,并做一下对应的分析: 当我们使用 / 进行除法时,无论参与除法的值是小数还是整数, 运算的结果都是精确的可以带有小数部分的数。 当我们使用 // 进行除法时,如果参与运算的值都是整数,则除法为取整除,结果不会含有小数部分。 代码:
print (3/2)
print (3//2)
print (3.//2.)
结果:
3、不只是数学运算
就如数学运算类似,乘法运算符是对加法运算相同数的连加的简化,这里的a * 3的意思与a a a是一致的。 这时候乘法运算符也是同样被对于字符串类型变量进行了重载(对于字符串使用乘法运算符我们有时候也称之为重复运算符)。 输出的结果则将是用一个字符串 a 连接在另一个字符串 a 的后面,之后再连接一个字符串 a ,再连接一个字符串 b 的结果。 现在运行一下这个程序,看看结果是不是和你预期的一致呢? 代码:
a = '我爱'
b = '中国'
print(a b)
print(a*3 b)
结果:
4、字符串长度
你可能注意到了,这里 b 是一个字符串,而 a_len 是一个数字。虽然 Python 中变量、参数、函数(方法)在声明时都是无需说明类型,但在 Python 中,储存着整数和字符串的两个不同类型的变量间是没有已经被定义的运算的。 因此我们在这通过str函数,将 a_len 中储存的数字变成了一个字符串(如果原本 a_len 是 2 ,那么 str(a_len) 则是 ‘2’)。并使之与 b 进行连接后输出。 运行一下程序,看看结果吧! 代码:
a = '中国'
a_len=len(a)
b='a的长度是'
print(b str(a_len))
结果:
5、运算分析
在前一节课程中,我们对多文件、多模块的程序设计进行对于下面这个 Python 程序,请选出 a, b, c 三个变量的值(请关注类型)完全正确的选项。 代码:
a = '10'
b = 10
c = str(len(a) b)
a = len(a * b)
b = a b
选择:
(1) 20, 30, '12'
(2) '20', 30, '12'
(3) 100, 30, '4'
(4) '100', 20, '12'
(5) '20', '30', 12
(6) 20, '30', '12'
(7) 20, 20, '4'
答案:
(1) 20, 30, '12'
a 最终被赋予了最初 a 这个字符串重复 b 次(10次)后的字符串长度,也就是 20。b 是重新取值后的 a(也就是 20)加上原本的 b(也就是 10)之后的结果 30。c 是最初的 a 的长度(也就是 2)加上原本的 b(也就是 10),得到 12。
6、函数
代码:
def main():
print('世界,你好')
if __name__ == '__main__':
main()
代码:
def max_pow(a,b):
if a>b:
pow_ab=a**b
print(pow_ab)
return pow_ab
pow_ba=b**a
print(pow_ba)
return pow_ba
值得一提的是,在上面的例子中我们在函数定义部分所用到的变量a、b、pow_ab、pow_ba都只在函数的定义部分有效。如果我们其他地方使用这些变量,他们都将是没有定义或不同于函数定义中的值的。
7、拯救牛郎织女
太棒了,你似乎成功的在王母娘娘定义的银河上架起了一座现代化桥梁,牛郎织女就要成功的团聚了。虽然我们批判王母娘娘不支持自有婚姻的老旧思想,但是我们也要像她学习,她很清楚,函数需要在被使用前被定义。 也就是说,在yinhe函数的定义需要被先写在被调用前,在这点上,王母娘娘还是做的很不错的。 代码:
def yinhe(a):
print('=' a '=')
def main():
print('牛郎')
yinhe('||')
print('织女')
if __name__ == '__main__':
main()
结果:
8、缩进的使用
你或许注意到了这课的第一段我提到了 起始缩进 的概念,对于每一个起到组织语句作用的语句,比如上面的程序的def和if,他们都会有自己的起始缩进。对于上面这段代码,函数下辖的语句共有一个自己的起始缩进,我们可以看见这里是 4 个空格。 而在if起始的这行后,有两行则又因为他们被组织在if这条语句下,而拥有相同的 8 个空格的 实际缩进,其中 4 个是因为他们被组织在这个函数下,由函数贡献的 起始缩进,而另外 4 个是因为他们被组织在这个函数下的if语句中而进一步累加的由if语句贡献的 起始缩进。 代码:
def max_pow(a,b):
if a>b:
pow_ab=a**b
print(pow_ab)
return pow_ab
pow_ba=b**a
print(pow_ba)
return pow_ba
9、无处不在的帮助
你做的很棒,这三行代码让我们可以去看了在sys下被定义了的exit函数的形式和作用说明、一个字符串( str )下被定义了的split函数的形式和作用说明、以及list下所有的被定义的函数方法的列表。 相信你有了 help 和 dir 这两个神器,你一定能比王母娘娘学 Python 学的更好! 运行一下,让我们来看看结果,了解下上面这些方法到底是干什么的吧! 代码:
import sys
help(len)
print(dir(sys))
help(sys.exit)
help('中国'.split)
print(dir(list))
结果:
10.Python 的注释
同时,Python 提供了另外一种让局部程序不被执行的方式,你可以在你希望不被执行的代码块前后两行分别加上 ’ ’ ’ 这样的三个单引号。有的地方会称这种方式为多行注释,但是我们并不建议用于添加注释内容时使用,而建议你仅在需要让部分代码暂时不被执行时临时使用这个方式让程序避开这部分的执行。
11、程序知多少(题目)
这一部分我们学了一些 Python 的基本知识,让我们来测验下你对 Python 程序及相关介绍到的知识了解有多少了呢。 请从给出的选项中选出 2 个 错误 的描述。 题目:
(1)Python 函数的参数可以是其他函数
(2)a, b, i, t 这样的单字母的命名不会影响程序的可读性
(3)函数的参数形式需要可以简单代表“函数可接受参数,及其在函数具体定义中的意义”
(4)Python程序中不按照规范,统一使用 3 个空格进行缩进是不会出现错误的
(5)getMoney 所示的是一个驼峰方式的命名,是 Python 中不推荐的
(6)Python 程序中使用 Tab 进行缩进和使用 4 个空格进行缩进混在一起使用是不会造成错误的好习惯
(7)通过使用 help 和 dir 可以快速了解大多数 Python 模组和方法的信息
(8)Python 的函数是可以没有 return 语句的
(9)Python 的函数是可以包含有多个 return 语句的
(10)Python 程序几乎所有的错误都是在程序运行到含有错误的那行的时候被报出
答案:
(2)a, b, i, t 这样的单字母的命名不会影响程序的可读性
我们很难知道单个字母的变量或者函数到底是什么意义。因此,我们要尽可能避免大量使用单字母命名的习惯
(6)Python 程序中使用 Tab 进行缩进和使用 4 个空格进行缩进混在一起使用是不会造成错误的好习惯
缩进一定要统一,Python 是缩进敏感的。不统一的缩进不但在开发时会带来不好的视觉体验,在 Python 中更是会导致程序报错
12、A B C问题
这是一个非常简单的题目,意在考察你编程的基础能力。千万别想难了哦。输入为一行,包括用空格分隔的三个整数,分别为 A、B、C(数据范围均在 -40 ~ 40 之间)。输出为一行,为“A B C”的计算结果。 样例输入:22 1 3 样例输出:26 代码:
a, b, c=(int(x) for x in input().split(' '))
print(a b c)
for … in…用法:
说明:也是循环结构的一种,经常用于遍历字符串、列表,元组,字典等 格式:
for x in y:
循环体
执行流程:x依次表示y中的一个元素,遍历完所有元素循环结束
enumerate() 函数:
用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。 语法:
enumerate(sequence, [start=0])
参数 sequence – 一个序列、迭代器或其他支持迭代对象。(下标) start – 下标起始位置。(元素)
第二章 字符串使用
1、Python 的字符串
我们可以通过方括号来访问字符串中的每一个字符(长度为 1 的在某一个位置的子字符串),在这点上, Python 的设计与 Java, C 等语言一致,他们的开始索引位置都是 0。对于一个指向字符串的变量 str = ‘你好啊’,如果我们访问 str[1]我们将得到的是字符 好,而不是 你。如果我们试图访问的索引位置不存在, Python 将会给我们一个“超出合法范围”的错误。类似的访问方式对于列表等其他 Python 的类型也是存在的,你可以之后进行进一步的了解。
2、字符串位置
你做的很棒,现在运行程序,看看输出是不是和你的预期一致呢?
a = '你好啊'
b=a[0] 'i'
print(b)
print(len(b))
3、大写与小写
你或许发现了,不加 r 的字符串中n会使得字符串换行(我们称之为换行的转义字符)。加上 r 以后的字符串中任何字符串中的转义字符都不会被转义(这里的 r 是英语单词 raw 的简写),因此我们看见,在最后的将字符串 b 转为大写字母输出来的时候n变为了N。
a = '我是n tom'
b = r'我是nTom'
print (a)
print (b)
print (a.lower())
print (b.upper())
lower()函数:
将字符串中的所有大写字母转换为小写字母
upper( )函数:
将字符串小写转大写
4、字符串测试
非常棒,让我们运行一下,看看王母娘娘想得到的测试结果是什么呢!是不是发现王母娘娘把“World”这个单词拼写错误了呢,你真是棒棒的!
s = 'HelloabcdWord'
print(s.isalpha())
print(s.isdigit())
print(s.startswith('Hello'))
print(s.endswith('World'))
isdigit()函数:
S.isdigit()返回的是布尔值:True False S中至少有一个字符且如果S中的所有字符都是数字,那么返回结果就是True;否则,就返回False
isalpha()函数:
S.isalpha()返回的是布尔值:True False S中至少有一个字符且如果S中的所有字符都是字母,那么返回结果就是True;否则,就返回False
startswith()函数:
作用:判断字符串是否以指定字符或子字符串开头
endswith()函数:
作用:判断字符串是否以指定字符或子字符串结尾,常用于判断文件类型
5、字符串的索引
如上图所示,如果我们希望从通过a = 'Hello'被赋值的字符串变量 a 中打印出字母 e。 我们既可以写print(a[1]),也可以写print(a[-4])。他们访问并输出的都将是字符串 Hello 中字符 e 所在的那个“格子”。
6、 切取字符串
太棒了,让我们运行看看结果。
从结果中,我们可以看到,tower[1:4] 切取了“宝塔”第 2 个字符到第 4 个字符,你也可以想象成真实世界的宝塔上,我们爬了一层楼到了 2 层然后一直爬到 4 层,所以我们写 [1:4] 被切取的是第 2 个字符到第 4 个字符(1 层屋顶到 4 层地屋顶间)组成的新字符串 木水火。
类似的,tower[3:] 打印出了第 4 个字符及之后字符组成的字符串(你可以想象成爬了 3 层塔以后以上的部分)火土风雨;tower[:-2] 则打印出的是从字符串去掉最后两个字符(你可以想象塔被砍掉了最上面两层)组成的字符串 金木水火土。
tower = '金木水火土风雨'
print (tower[1:4])
print(tower[3:])
print (tower[:-2])
7、if 语句
有的时候,我们可能会用到在中文意义上的“与”、“或”、“不是”的逻辑,比方说,我们想判断一个变量 a 是不是在比 5 小或比 13 大的范围,并执行相关语句,我们可以利用写if a < 5 or a > 13:——这里我们引入了表示“或”的 or 关键字。类似的,对于“与” Python 提供了 and 关键字,对于“不是” Python 则提供了 not 关键字。你将在之后的实践中逐步的更好的体会和使用。
8、查找与替换
太棒了,让我们运行一下程序,是不是下雨天程序就帮多闻天王把伞放在包里了呢? 当然,你也可以把 weather 变量的值改为’大晴天’再看看 if 语句是不是按照你的预期工作了呢?
weather = '下雨天'
bag = '包里空空的'
if weather.find('雨') != -1:
bag = bag.replace ('空空的','有')
print(bag)
find()函数:
检测字符串中是否包含子字符串 str ,如果指定 beg(开始) 和 end(结束) 范围,则检查是否包含在指定范围内,如果指定范围内如果包含指定索引值,返回的是索引值在字符串中的起始位置。如果不包含索引值,返回-1。
replace()函数:
把字符串中的 old(旧字符串) 替换成 new(新字符串),如果指定第三个参数max,则替换不超过 max 次。
9、字符串格式化
很好,运行一下程序,你会发现,这样的写法得到的输出结果和之前的写法是一致的。字符串中的 {} 表示字符串中可以嵌入值的占位。而 .format 则用于列出要嵌入到输出中的所有变量或数值,具体变量或数值在其后的圆括号内由逗号分隔列出。在字符串中的大括号内的数字则与嵌入变量或数值的位置一一对应。
例如 {0} 对应了 name,{1} 对应了 age,{2} 对应了 height(如果大括号内不写数字,则默认按出现在字符串中的位置与 format 列出的变量或数值一一对应)。
类似的还有一种字符串格式化的方式,你可以讲最后一行替代为:
print('%s是一位%d岁的老奶奶,她身高%g米' % (name, age, height))
在这里,单引号外的 % 前定义了输出字符串的格式,其中的 %s 表示字符串变量嵌入占位,%d 表示整数变量嵌入占位、%g 表示浮点数变量(高精度小数)嵌入占位。相应的,在 % 后的括号内,我们按照前面的格式中的占位顺序,依次列出希望将值进行嵌入的变量(在之后我们会知道,% 后的东西是一个“元组”)。
这两种 字符串格式化 的方式非常实用和简洁,不易出错,是我们大多数时候会去鼓励使用的。你可以对大括号内的数字、变量的值进行一些修改,看看运行结果是不是也相应的做出了改变呢?
name = '王母娘娘'
age = 9000
height = 1.73
print(name '是一位' str(age) '岁的老奶奶,她身高' str(height) '米')
print('{0}是一位{1}岁的老奶奶,她身高{2}米'.format(name,age,height))
print('%s是一位%d岁的老奶奶,她身高%g米' %(name,age,height))
10、丰富的字符串(题目)
你已经对 Python 字符串有了不少的了解,文曲星决定考考你,看看你是不是都理解了。请选出 2 个关于 Python 字符串的 错误 描述。
(1)字符串的 `replace` 函数会替换所有符合第一个参数定义的子字符串而不是只替换第一个
(2)`print('Hello'.lower() == 'hello')` 输出的结果为 True
(3)Python 中对于 2 < a < 13 的判断不能写为 if 2 < a < 13: 而必须写成 if 2 < a and a < 13:
(4)print('Hello Jisuanke'.find('ello')) 输出的结果为 2,因为我们找到的子字符串的第一个字符在原字符串的索引位置是 2。
(5)print('{0}是一位{1}岁的老奶奶,她身高{2}米'.format(name, age, height)) 和 print('{}是一位{}岁的老奶奶,她身高{}米'.format(name, age, height)) 的输出结果是一样的(假设 name, age, height 已经定义了)
(6)print('Hello Jisuanke'.isalpha()) 输出的结果为 False
(7)对于一个字符串 s 和一个整数 n(可能为负),s[:n] s[n:] 与 s[:] 和 s 都一致
答案:
(3)Python 中对于 2 < a < 13 的判断不能写为 if 2 < a < 13: 而必须写成 if 2 < a and a < 13:
Python 中可以直接写 if 2 < a < 13:
(4)print('Hello Jisuanke'.find('ello')) 输出的结果为 2,因为我们找到的子字符串的第一个字符在原字符串的索引位置是 2。
输出的结果为 1,因为第二个字符的索引是 1。
11、批量替换字符串
在网络编程中,如果 URL 中含有特殊字符,如空格、“#”等,服务器将无法识别导致无法获得正确的参数值,我们需要将这些特殊字符转换成服务器可以识别的字符,例如将空格转换成“ ”。给定一个字符串,将其中的空格转换成“ ”。 输入一个原始字符串,例如 hello world。 输出转换后的字符串,例如 hello world。
样例输入:we are happy 样例输出:we are happy
代码:
s = input()
print(s.replace(' ',' '))
第三章 简单结构
1、使用列表
对于列表来说,len 函数给出了列表的元素数量(列表长度)。我们刚刚提到了,字符串是特殊列表,对于字符串 len 函数给出了字符串的长度的原因,其实就是因为它给出了字符串这种特殊列表所包含的长度为 1 的子字符串元素的长度。让我们运行一下程序,看看输出出来的值是不是都是正确的呢?
list = [100,23,45]
print (list[0])
print (list[1])
print (list[2])
print(len(list))
2、列表尾部的添加
很好,让我们运行一下,看看 append 和 extend 被执行后的列表是否实现了王母娘娘预期的效果呢?
hello = ['hi', 'hello']
world = ['earth', 'field', 'universe']
hello.append('你好')
print (hello)
hello.extend(world)
print(hello)
append()函数:
用于在列表末尾添加新的对象。
extend()函数:
用于在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)。
3、插入数据与元素定位
运行一下你的程序,看看结果与你预期是不是一致的呢? 字符串 hi 的索引位置是不是改变了呢?
hello = ['hi', 'hello']
hello.insert(0,'你好')
print (hello)
print(hello.index('hi'))
insert()函数:
用于将指定对象插入列表的指定位置。
index()函数:
检测字符串中是否包含子字符串 str ,如果指定 beg(开始) 和 end(结束) 范围,则检查是否包含在指定范围内,该方法与 python find()方法一样,只不过如果str不在 string中会报一个异常。
4、列表弹出与删除
运行一下你的程序,看看结果与你预期是不是一致的呢? 仔细思考一下,直接用方括号访问索引位置的元素和用 pop 函数的区别;再想想 remove 和 pop 的相似之处。
hello = ['你好', 'hi', 'hello']
hello.remove('你好')
print (hello)
hello.pop(0)
print(hello)
remove()函数:
用于移除列表中某个值的第一个匹配项。
pop()函数:
用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。
5、字符串的切割与列表合成
很棒,让我们运行一下程序,看看玉皇大帝会不会对你的工作满意呢?
备注:split 函数很多时候会被和 input 函数放在一起使用,用于处理一些简单的输入,之前你可能已经用到过了,可以回顾一下。
manager = '托塔天王,太白金星,卷帘大将'
manager_list = manager.split(',')
print(manager_list)
new_manager = ' '.join(manager_list)
print(new_manager)
split()函数:
通过指定分隔符对字符串进行切片,如果参数 num 有指定值,则分隔 num 1 个子字符串
join()函数:
方法用于将序列中的元素以指定的字符连接生成一个新的字符串。
6、列表的高效使用
已知列表的切取和字符串类似,请判断一下,给出的选项中,哪 3 个说法是 错误 的。
(1)对于 list = ['a', 'b', 'c', 'd'] 来说,list[-2:] 和 list[2:] 的结果值应该相同
(2)对于 list = ['a', 'b', 'c', 'd'] 来说,print(list.pop(3)) 将得到输出结果 d
(3)将字符串'a b c'按空格进行切割后并输出结果可以写成 print('a b c'.split())
(4)对于 list = ['a', 'b', 'c', 'd'] 来说,print(list[2:]) 将得到输出结果 ['b', 'c', 'd']
(5)将 list = ['a', 'b', 'c'] 合成成字符串 'a|b|c'并输出 可以写成 print(list.join('|'))
(6)对于 list = ['a', 'b', 'c', 'd'] 来说,print(list.pop(3)) 将得到输出结果 ['a', 'b', 'c']
(7)对于 list = ['a', 'b', 'c', 'd'] 来说,list.insert(3, 'x') 后,list 的值为 ['a', 'b', 'c', 'x', 'd']
答案:
(4)对于 list = ['a', 'b', 'c', 'd'] 来说,print(list[2:]) 将得到输出结果 ['b', 'c', 'd']
(5)将 list = ['a', 'b', 'c'] 合成成字符串 'a|b|c'并输出 可以写成 print(list.join('|'))
(6)对于 list = ['a', 'b', 'c', 'd'] 来说,print(list.pop(3)) 将得到输出结果 ['a', 'b', 'c']
7、列表求和
真棒,运行一下程序,看看到底王母娘娘的蟠桃园今年结了多少的蟠桃吧。
gardens = [7204, 3640, 1200, 1240, 71800, 3200, 604]
total = 0
for num in gardens:
total =num
print (total)
print(sum(gardens))
8、range 的使用
有的时候,我们可能会希望取得一个递减取值的列表,那么我们可以将开始数字被设置的大于结束数字,而每次取值的间隔为一个负值。例如,我们调用range(10, 1, -2)将得到一个从 10 开始,每两个数取一次元素,并确保每个元素都大于 1 的列表 [10, 8, 6, 4, 2]。
9、while 循环
很棒!你应该看见了,我们将一系列的语句组织在了 while 下的语句块里,这些语句会一直被循环执行,直到 first 的数值不再小于 100,则开始执行这个语句块后的其他程序。
我们在一个时间会关注两个数值,每次我们会输出第一个数,并将两个数求和后给到大的那个数,再把原来的大的那个数变成当前两个数中小的那一个。运行一下程序,看看哪些数是斐波那契数吧!
first = 0
second = 1
# 请在下一行写代码
while first < 100:
print(first)
first, second = second, first second
print('一切都完成啦')
10、简单斐波那契(题目)
斐波那契数列是一种非常有意思的数列,由 0 和 1 开始,之后的斐波那契系数就由之前的两数相加。用数学公式定义斐波那契数列则可以看成如下形式:
F0=0 F1= F_n=F_{n-1} F_{n-2} 我们约定 F_n表示斐波那契数列的第 n 项,你能知道斐波那契数列中的任何一项吗? 输入包括一行,包括一个数字 n(0≤n≤50)。 输出包括一行,包括一个数字,为斐波那契数列的第n项的值。
样例输入:7 样例输出:13
n = int(input())
first = 0
second = 1
# 请在下一行写代码
while first < n:
first, second = second, first second
print (second)
第四章 排序与元组
1、简单的排序
Python 没有内置对数组的支持,但可以使用 Python 列表代替。
很好,但是你需要注意,调用 sort 函数并不会带有返回值。也就是说,如果我们写
x=numbers.sort()
的话 x 将得到的值是 None,而不是一个排序后的列表。现在运行一下程序,看看结果是不是符合你的预期吧。 代码:
numbers = [1, 4, 2, 3, 8, 3, 0]
numbers.sort()
print(numbers)
x=numbers.sort()
print(x)
结果:
sort()函数:
用于对原列表进行排序,如果指定参数,则使用比较函数指定的比较函数。
2、基本的排序
在这里,我们使用了 sorted 函数的 reverse 参数,这个参数用于标记排序的结果的顺序性,将这个参数设置为 True 会将排序的结果顺序设置为逆序。运行一下,看看结果是不是和你预期的一致呢? 代码:
numbers = [1, 4, 2, 3, 8, 3, 0]
print (sorted(numbers))
print (sorted(numbers,reverse = True))
结果:
sorted() 函数:
对所有可迭代的对象进行排序操作。
3、字典序
根据我们上面说的,对于一些字符串,你将较为轻松的得出一个这样的结论——boat < boot < cap < card < cat < to < too < two < up 。很多市面上的字典采用了字典序的方式对单词进行排列(假设都是小写字母),你如果有机会也可以翻一翻字典看一看。
4、个性化排序
很好!我们可以看到,这样我们就可以根据自定义的 china_first 函数的返回值作为中间值对原来的列表输入进行排序了。运行一下程序,看看和你预期的是否一致?分析下为什么会得到这样的结果。 代码:
def china_first(item):
if item == 'China':
return 0
else:
return len(item)
country = ['jp', 'China', 'USA', 'Thai']
print (sorted(country,key=len))
print(sorted(country,key = china_first))
结果:
5、排序题目
请从关于排序的陈述中选出 正确 的两个
(1)list.sorted() 可以让 list 列表内元素升序排序
sorted 并不能作为 list 的一个方法被调用,应该将 list 放入 sorted 后的括号内
(2)sorted([1, -4, 3, -10, -2, 6], key = abs, reverse = True) 的结果是 [-10, -4, -2, 1, 3, 6]
正确的结果是 [-10, 6, -4, 3, -2, 1]
(3)sort(list) 可以返回 list 列表升序排序后的结果
sort 并不能这么使用,sorted 才可以喔。
(4)sorted(list, reverse=False) 与 sorted(list) 的结果是不一致的
应该是一致的,reverse 缺省值为 False
(5)sorted([1, -4, 3, -10, -2, 6], key = abs, reverse = True) 的结果是 [1, -2, 3, -4, 6, -10]
正确的结果是 [-10, 6, -4, 3, -2, 1]
(6)sorted([1, -4, 3, -10, -2, 6], key = abs, reverse = True) 的结果是 [-10, 6, -4, 3, -2, 1]
正确
(7)直接打印 sorted(list) 可以获得正确的排序后结果
正确
6、元组是什么
Python的元组与列表类似,不同之处在于元组的元素不能修改。 元组使用小括号,列表使用方括号。 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可。
如果创建一个只有 1 个元素的元组,则第一个元素的后面需要加一个逗号tuple = (‘hi’,)。这是一个很有意思的设计,并且这里的逗号是不能省略的,这个设计是为了区分一个正常的括号内放一个元素的这种情况,如果不加这个逗号,有的时候 Python 会直接将它等价成一个没有括号的值。
在接收元组的值的时候,我们也可以通过元组型的变量进行接收。接收后我们可以通过其中的单一变量对返回的多个值中的某一个进行访问。
7、元组的使用
很棒,让我们输出一下现在的 tuple 值。类似打印列表的做法,请在下一行写下
print (tuple)
代码:
tuple = (1, 2, 'hi')
print (tuple)
print (len(tuple))
print (tuple[2])
tuple = (1,2,'bye')
结果:
8、元组做返回值
你可能注意到了,其实我们在输出时,也可以依次输出多个变量,就像print(y, z)这样的形式。运行一下你的程序,让我们来看看你程序的结果是什么样的。你理解了用元组来做函数返回值的好处了吗? 代码:
def plus_one(tuple):
return tuple[0] 1, tuple[1] 1, tuple[2] 1
t = (1, 4, -1)
(x,y,z) = plus_one(t)
print(x)
print(y,z)
结果:
9、交叉排序
输入一行 k 个用空格分隔开的整数,依次为 n_1, n_2 … n_k。请将所有下标不能被 3但可以被 2 整除的数在这些数字原有的位置上进行升序排列,此外,将余下下标能被 3 整除的数在这些数字原有的位置上进行降序排列。 输出包括一行,与输入相对应的若干个整数,为排序后的结果,整数之间用空格分隔。 样例输入:1 5 4 3 10 7 19 样例输出:1 3 7 5 10 4 19 提示: 请注意,题面中的下标是从 1 开始的哦! 另外,对于读入的数 x 建议通过int(x)转成整数以后,这道题就不难做啦。 代码:
1
结果:
第五章 字典与文件
1、什么是字典
可以作为键名的常见数据类型包括了字符串、数值和元组,而键值则可以是任何类型的数值。如果使用了错误的键名,将无法取出正确的键值。
看看以下程序会输出什么吧! 代码:
bat = {}
bat['b'] = '百度'
bat['a'] = '阿里巴巴'
bat['t'] = '腾讯'
print (bat)
print(bat['a'])
bat['a'] = '亚马逊'
print(bat['a'])
print('b' in bat)
print('x' in bat)
结果:
2、查看字典元素
这里的 items 函数会取到每一个键名和对应键值,并将他们组成一系列的元组放到一个列表里。现在运行一下,看看结果是不是和你预期的一样呢?
如果你希望输出不包括 dict_keys,dict_values,dict_items 这样的信息。你可以额外写一个 list:
print(list(bat.keys()))
print(list(bat.values()))
print(list(bat.items()))
代码:
bat = {'a': '阿里巴巴', 'b': '百度', 't': '腾讯'}
print (bat.keys())
print (bat.values())
print (bat.items())
print(list(bat.keys()))
print(list(bat.values()))
print(list(bat.items()))
结果:
keys()函数:
返回一个可迭代对象,可以使用 list() 来转换为列表(键名)
values() 函数:
以列表返回字典中的所有值(键值)
items()函数:
以列表返回可遍历的(键, 值) 元组数组。
3、for循环打印字典
在这里,我们实际上访问了 bat.items() 这个列表中的每一个元组元素,并且让他们在循环中被赋给临时的 k 和 v 变量,并按照结果输出出来了。
代码:
bat = {'a': '阿里巴巴', 'b': '百度', 't': '腾讯'}
for value in bat.values():
print(value)
for key in bat:
print(key)
for k,v in bat.items():
print(k,'>',v)
结果:
4、字典数据格式
在这里,与之前的字符串格式化相同, %s 表示字符串变量嵌入占位,%d 表示整数变量嵌入占位、%g 表示浮点数变量(高精度小数)嵌入占位,而每一个占位字符的 % 与类型标识字母前都有一个圆括号,里面填写了键名。
这样,在字符串格式的百分号后,我们只需要将字典的变量名写上,就可以将它的每一个键值都对应正确嵌入了。运行一下程序,看看这个某厂老板的是怎么样的吧。
代码:
boss = {}
boss['name'] = 'robin'
boss['age'] = 45
boss['height'] = 1.78
print ('The boss named %(name)s is %(age)d-year-old and %(height)g tall.' %boss)
结果:
5、删除表达式
删除表达式 意见反馈 本节内容计 5 分,完成后得满分 在 Python 中可以对元素使用 del 进行删除,我们可以删除变量、列表中元素或者字典中元素。让我们先对列表 list 做一些操作,删除 list 的开头的元素和最后两个元素,然后输出一下被操作后的列表 list,请在接下来写下
del list[0]
del list[-2:]
print(list)
类似的,我们也可以删除一下字典中的键名为 b 的元素,并输出一下被操作后的字典。请在下两行写下
del dict['b']
print(dict)
让我们再来删除变量 num 然后试着输出一下变量 num。请在下两行写下
del num
print(num)
代码:
num = 6
list = ['a', 'b', 'c', 'd']
dict = {'a': 1, 'b': 2, 'c': 3}
del list[0]
del list[-2:]
print(list)
del dict['b']
print(dict)
del num
print(num)
结果:
6、字典操作
对于一个字典
dict = {'lily': 100, 'bob': 88, 'alice': 59}
请从下面的字典操作的说法中选出两个正确的选项:
(1)print(dict['bob'] 1) 得到的输出是 89
(2)接着写下 dict['leijun'] = 99 并 del dict['alice'] 后,输出 print(list(dict.items())) 得到的输出是是字典所有的键名组成的一个列表
(3)接着写下 dict['leijun'] = 99 并 del dict['alice'] 后,输出 print(list(dict.keys())) 得到的输出是字典所有的键名组成的一个列表
(4)print('The grades are lily: %('lily')d, bob: %('bob')d, alice: %('alice')d' % dict) 得到的结果是 The grades are lily: 100, bob: 88, alice: 59
(5)print(dict['bob'] '1') 得到的输出是 '881'
答案:
7、文件的使用
open 函数:
close() 函数:
在 Python 有一个 open 函数,这个函数会返回一个可以用于读出和写入文件的 文件操作符(file descriptor)。我们可以通过
fd = open('filename', 'r')
的方式,打开一个文件名叫做 filename 的文件获取它的文件操作符,并让变量 fd 指向这个操作符(这里的 r 标记了文件被打开用于读取)。当我们用完这个文件不再继续使用时,我们可以用 fd.close() 结束对这个文件的使用。
还可以用 w 来表示写入(或者用 a 表示向后继续添加),这些标记可以被放在一起使用,比如我们如果写
fd = open('filename', 'rw')
则表示对这个文件会有读操作,也会有写操作。
我们可以用标准的 for 循环来读取一个文件的每一行(仅对文件读取有效,对二进制读取并不能用)。
# 按行输出整个文件
f = open('filename', 'rU')
for line in f: # 访问文件每一行
print(line, end = ' ') # 打印每一行,end = ' '可以确保不添加换行
每次读取一行的好处在于我们不会受到内存的限制,也就是说,我们可以每次只把文件的一部分放到内存进行处理,而不会需要一次性完整加载大块头的文件到内存中。这样,对于一个 1T 的文件,很难找到一个 1T 内存的你也可以对它进行优雅的处理。
readline()函数:
read ()函数:
我们也可以通过调用文件操作符的函数 readline(写成 fd.readline()),它会一次性加载完整的文件到内存,让文件的每一行作为它这个列表结构的每一个字符串元素。类似的,文件操作符的函数 read (写成 fd.read())则会一次性将完整文件作为一个字符串读入到内存内。但是这两种方式往往都在处理大文件的时候会遇到内存无法完整存下内容的问题。
write()函数:
对于向文件中的写入,我们可以用 write 函数(写成 fd.write(字符串))将指定的字符串写入到打开的文件中。
8、文件编码
在 Python 中有一些已经写好的功能性的“包”称为 模组(module)。其中有一个名叫 codecs 的模组,它提供了读取一个非英文的文件所需要的 unicode 读取支持。
让我们使用它时,我们需要先通过 import 将它引入,之后在打开文件时,标记明确所需要使用的编码字符集(在这里我们使用 utf-8)。
import codecs
fd = codecs.open('foo.txt', 'rU', 'utf-8')
当读取完,并且成功完成相关的处理后,我们需要注意。我们只可以使用 fd.write() 的形式来进行写出。
9、题目
题1、两数之和: 两个数,使得他们的和为一个给定的数值target。 函数twoSum 返回两个数字 index1 , index2 其中:number[index1] number[index2]=target; 注意: index1必须小于 index2 且不能为 0 假设每一组输入只有唯一的一组解。 格式:第一行输入一个数 n,接下来的两行分别输入数组 number[n] 和 target,返回index1和index2 样例输入: 3 5 75 25 100 样例输出: 2 3
代码:
import ast
n = int(input())
number = ast.literal_eval(input())
target = int(input())
for index1, number1 in enumerate(number):
for index2, number2 in enumerate(number):
if(index2>index1 and number1 number2==target):
print(index1,index2)
结果:
enumerate()函数:
用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中
题2、统计字符个数: 输入一行字符,分别统计出其中英文字母,数字,空格,其它字符的个数。 输入格式:
输入一行字符
输出格式:
输出为一行,分别输出英文字母,数字,空格,其它字符的个数,用空格分隔
样例输入:
aklsjflj123 sadf918u324 asdf91u32oasdf/.';123
样例输出:
23 16 2 4
答案参考: https://www.cnblogs.com/python-xkj/p/9225985.html
代码:
tmpStr = input('请输入字符串:')
alphaNum=0
numbers=0
spaceNum=0
otherNum=0
for i in tmpStr:
if i.isalpha():
alphaNum =1
elif i.isnumeric():
numbers =1
elif i.isspace():
spaceNum =1
else:
otherNum =1
print('字母=%d'%alphaNum)
print('数字=%d'%numbers)
print('空格=%d'%spaceNum)
print('其他=%d'%otherNum)
结果:
第六章 正则表达式
1、什么是正则表达式
正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。 Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式。 re 模块使 Python 语言拥有全部的正则表达式功能。 compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。 re 模块也提供了与这些方法功能完全一致的函数,这些函数使用一个模式字符串做为它们的第一个参数。
^ 作为开始标记,$ 作为结束标记,分别用于标记一个字符串的开始和结束的位置。 用于一些字符的转义. 比如 . 表示对于一个真实点字符的匹配, 表示对于一个真实反斜杠字符的匹配等。如果你对不是很确定一些字符是否需要进行转义才能匹配,你大可都加上斜杠,比如对于@你写成@是一定没有问题的。
2、正则表达式查找
在 Python 中使用模组 re 提供的 search 函数,我们可以用正则表达式在一个字符串中进行匹配查找。请在下一行写下
match = re.search(r'word:www', str)
这里表示我们将用word:www这个正则从字符串str进行匹配查找,正则表达式前的 r 标记了这个表达式不需要被转义处理,也就是说n这样的东西在写了 r 标记的情况下,不会被理解成换行。match 变量将指向匹配查找的结果。请在下一行写下
if match:
这样,我们就可以判断是否有合法的匹配,如果存在,if 条件下的语句块就会被执行。请在下一行 if 语句块内(请注意,这里需要缩进)写下:
print('found', match.group())
很棒!运行一下,看看结果怎么样呢?是不是成功的把符合要求的 cat 找了出来呢?
代码:
import re
str = 'A cute word:cat!!'
match = re.search(r'word:www', str)
if match:
print('found', match.group())
结果:
re.match函数:
尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none
re.search函数:
扫描整个字符串并返回第一个成功的匹配。
group()函数:
在正则表达式中用于获取分段截获的字符串
3、基础正则使用
让我们来尝试使用一下正则表达式中的 . 的使用,请在下一行写下
print(re.search(r'..g', 'piiig').group())
接下来,我们试一试 d 的使用,请在下一行写下
print(re.search(r'ddd', 'p123g').group())
不错,我们还可以再看看 w 的使用,请在下一行写下
print(re.search(r'www', '@@abcd!!').group())
运行并查看结果:
代码:
import re
print(re.search(r'..g', 'piiig').group())
print(re.search(r'ddd', 'p123g').group())
print(re.search(r'www', '@@abcd!!').group())
结果:
4、正则表达重复
每次都要写好多遍一样的形式,在出现重复的形式的时候似乎会很让人纠结。在正则表达式中,我们有 * 和 来实现重复多次的形式的表达, * 表示对它之前一个字符有 0 或更多次的重复, 表示对它之前一个字符有 1 或更多次的重复。请在下一行写下:
print(re.search(r'pi ', 'piiig').group())
很好,我们在这里其实去寻找的形式是 p 后跟了至少一个 i 的连续个 i 的形式。接下来,让我们在下面两行试着用一下 *,请写下:
print(re.search(r'pi*', 'pg').group())
这样,我们就可以在运行时比较一下 和 * 的区别了。 运行一下程序,看看结果和你的预期是否一致吧!
代码:
import re
print(re.search(r'pi ', 'piiig').group())
print(re.search(r'pi*', 'pg').group())
结果:
5、题目
题1、正确的正则表达式: 选出说法正确的那两个:
(1)re.search(r'www', '@@abcd!!').group() 的值是 abc
(2)re.search(r'ds*ds d', 'xx1 2 3xx') 的值是空
(3)验证 yodada-b@jisuanke.com 是否符合邮箱形式是可以用w @w 匹配的并不可以,至少 @ 后面要能匹配出一个点。
(4)验证 yodada-b@jisuanke.com 是否符合邮箱形式是不可以用w@w匹配的
(5)re.search(r'ds*ds*d', 'xx12 3xx') 的值是空
(6)re.search(r'ds ds d', 'xx1 2 3xx') 的值是空
6、正则里的方括号
在之前的阅读里面,我们也提到过一些正则字符等价于一些方括号包裹的正则字符,那么方括号的作用是什么呢?其实方括号将一系列的正则字符以“或”关系组织在一起。请在下一行写下
print(re.search(r'[abc] ', 'xxxacbbcbbadddedede').group())
很好,除了在方括号内一一列举的方式,我们还可以采用横线来标识一个范围,请在下一行写下:
print(re.search(r'[a-d] ', 'xxxacbbcbbadddedede').group())
太棒了!运行一下,看看结果与我们预期的结果是不是一致呢?是不是前者匹配了连续的由 a, b, c 组成的字符串;而后者匹配了从 a 到 d 的所有字母组成的连续字符串呢?
代码:
import re
print(re.search(r'[abc] ', 'xxxacbbcbbadddedede').group())
print(re.search(r'[a-d] ', 'xxxacbbcbbadddedede').group())
结果:
7、正则提取
对于一个正则表达式的匹配过程,我们可以在其中添加几对圆括号,用于指定几个具体的匹配的部分。请在下一行写下:
match = re.search('([w.-] )@([w.-] )', str)
如果不看圆括号,这里我们实际上去对邮件地址进行了匹配,而圆括号匹配了@前的部分和之后的部分。请在接下来写下:
if match:
print(match.group())
print(match.group(1))
print(match.group(2))
运行一下程序,看看 group 方法在加了参数后输出的匹配结果是什么样呢?对比一下不加参数时匹配出完全正则的结果。
代码:
import re
str = 'purple alice-b@jisuanke.com monkey dishwasher'
match = re.search('([w.-] )@([w.-] )', str)
if match:
print(match.group())
print(match.group(1))
print(match.group(2))
结果:
8、正则表达式的调试
正则表达式用简单的一些字符的组合包含了太丰富的语义,但是它们实在太过密集了,为了把你的正则表达式写对,你可能要花上太多太多的时间。在这里,我们提供一些简单的建议帮助你更高效的对正则表达式进行调试。
你可以设计一系列放在列表里的字符串用于调试,其中一部分是可以产生符合正则表达式的结果的,另一部分是产生不符合正则表达式的结果的。请注意,在设计这些字符串时,尽可能让他们的特征表现的更为不同一些,便于覆盖到我们可能出现的各种正则表达式没有写对的错误。
例如,对于一个存在 的正则表达式,我们可以考虑选用一个符合*但是不符合 的字符串。
然后你可以写一个循环,依次验证每个列表内的字符串是否符合指定的某个正则表达式并且和你设定的存在另一个列表内的预期结果进行比对,如果出现了不一致的情况,则你应该考虑看看你的正则表达式是不是还需要修改,如果结果基本一致,那么我们可以考虑进一步修改我们用于调试的字符串或添加新的字符串。
9、查找所有方法
findall函数:
除了我们之前用到的 search 方法,在结合圆括号后,我们还可以使用另一个名为 findall 的方法来匹配查找出所有的被查找字符串符合正则的结果,并得到一个结果元组为元素的列表。请在下一行写下:
tuples = re.findall(r'([w.-] )@([w.-] )', str)
这样,我们就将所有 str 中符合正则的结果以元组列表的形式赋给了 tuples 变量。接下来,让我们输出一下 tuples,请在下一行写下:
print(tuples)
很好,运行一下程序,你是不是掌握了 findall() 了呢?
代码:
import re
str = 'purple alice@jisuanke.com, blah monkey bob@abc.com blah dishwasher'
tuples = re.findall(r'([w.-] )@([w.-] )', str)
print(tuples)
结果:
10、在文件中查找
结合文件操作和 findall 的使用选出下面这段代码正确的输出结果
f = open('test.txt', 'r')
users = re.findall(r'<li><span class="name">([w] )</span>([w] @[w.-] )</li>', f.read())
for user in users:
print(user, end = ' ')
test.txt 的文本内容如下:
<ul><li><span class="name">Bob</span>bob@jisuanke.com</li><li><span class="name">ALice</span>alice@gmail.com</li><li><span class="name">Cane</span>cane.tec@bob.com</li></ul>
选择:
(1)('Bob', 'bob@jisuanke.com') ('ALice', 'alice@gmail.com')
(2)('Bob', 'bob@jisuanke.com')
(3)('Bob', 'bob@jisuanke.com') ('ALice', 'alice@gmail.com') ('Cane', 'cane.tec@bob.com')
(4)Bobbob@jisuanke.com ALicealice@gmail.com
(5)Bobbob@jisuanke.com ALicealice@gmail.com Canecanetec@gmail.com
(6)Bobbob@jisuanke.com ALicealice@gmail.com Canecane.tec@gmail.com
(7)('Bob', 'bob@jisuanke.com') ('ALice', 'alice@gmail.com') ('Cane', 'canetec@bob.com')
11、选项与贪心匹配
在用于正则表达式的 re 模组中的函数有一些可选参数,我们可以对 search() 函数或者 findall() 函数传入额外的参数来进行使用,如 re.search(pat, str, re.IGNORECASE)中的 re.IGNORECASE就是使用了 re 中的一个标记作为额外的参数。
在 re 模组中,提供了很多不同的可选参数,其中上面提到的IGNORECASE表示了让匹配时忽略大小写的区别;而另外一个可选参数DOTALL如果被添加,则会允许正则中的.去跨行匹配,加了这个参数以后.*这样的匹配方式,将可以跨行进行匹配,而不只是在行内进行。另外,还有一个可选参数是MULTILINE,使用它以后,对于一个多行文本组成的字符串,^和$将可以用于匹配每一行的开始和结束,而如果没有用它时^和$只会匹配整个字符串的开始和结束。
除了可选参数之外,我们还需要理解一下正则匹配的“贪心情况”。假设我们有一段文字<b>foo</b> and <i>so on</i>而你希望匹配(<.*>)提取的所有 HTML 标签,你觉得结果会是怎么样呢?我们可以得到期望的<b>, </b>, <i>, </i>这样的结果吗?
事实上的结果可能会有点出乎你的意料,因为 .* 这样的匹配是“贪心”的,它会尽可能去得到较长的匹配结果,因此我们会得到的是一整个<b>foo</b> and <i>so on</i>作为匹配出的结果。如果我们希望获得期望中的结果,我们就需要这个匹配是非贪心的,在正则表达式中,我们对于*和 这种默认贪心的匹配可以加上?使之变为不贪心的。
也就是说,如果我们将(<.*>)改成(<.*?>),正则表达式会先匹配<b>,然后匹配</b>,接下来则分别是<i>和</i>。这样的匹配结果与我们的预期完全一致。相应的,对于一些用到了 的情况,我们可以将 变为 ?来进行非贪心的匹配。
第七章 Python中的面向对象编程
1、 面向对象编程与类
面向对象编程:就是把现实世界中的所有事物都当做是“对象”,包括有形的和无形的。 对象:现实中的一切都可以成为对象 类:将抽象之后的数据和函数封装在一起,就构成了类。类是对某一组具有相同属性与方法的对象的抽象。
一个类会拥有一系列属性和方法——在 Python 中,我们可以把属性看做类内定义的一个或者几个变量(比如列表中的元素),而方法则相当于类内定义的一系列函数(比如列表的append函数)。
在 Python 中,我们不光可以使用系统定义的类,当然也可以自己定义类。这里让我们来看一个“大众脸”的类定义。大家先直接看代码,接下来我们将会依次对其进行说明:
class Journalist:
"""
以这个为例
"""
def __init__(self,name):
self.name=name
def get_name(self):
return self.name
def speed(self,speed):
d={}
d[self.name]=speed
return d
现在,让我们依次介绍这些代码的含义。
第一行class Journalist,类似于之前我们用def声明函数一样,是在声明一个名为Journalist的类——其关键词为class。通常情况下,类的名称要用大写字母开头——如果是两个单词组合的话,最好每个单词都用大写开头,比如HongKongJournalist,这样便于提高代码的可读性。
声明了类的名字之后,接下来就是类内部的代码块了——大家已经知道 Python 使用缩进来区分命名空间层次,之前我们在定义函数的时候,函数中的代码也是相对于声明语句向前缩进的——现在类也是一样。我们建议大家这个时候不要用Tab键,而是直接敲四个空格,免得当你的代码被别的文本编辑器打开的时候缩进被搞乱。
类里面的代码,定义的就是这个类的方法——这是一个面向对象程序设计的术语,实际上就相当于是类自己的函数。我们可以看到,方法的定义方式跟一般的函数,几乎完全一样。
不过仔细观察的话,大家依然能很容易地发现一些区别:所有的函数,都包括一个参数self。这里大家需要注意:类的所有方法,其参数列表都必须包括self,并且默认作为第一个参数。如果有同学接触过 C 的话,这里可以将其理解为 C 中的this指针——没接触过也无所谓,后面的课程中我们将对其进行展开介绍。
接下来,我们将对类的每一个方法都进行简要的说明。
def __init__(self,name)这个方法比较特殊——它的命名方式就很特别,开头和结尾都是双下划线。学习过 C 或者其他类似的面向对象程序设计语言的同学,对此一定会非常熟悉——这就是这个类的构造函数,也可以叫初始化函数。
所谓初始化,就是让一个类的实例对象在生成的时候,有一个基本的状态,而不是空空如也。现实中我们做任何事情的时候也需要“初始化”——比如说如果我们要喝水的话,那么我们需要一个装满水的杯子。然后“初始化”这个过程对应的就是我们把杯子拿过来倒满水的过程。在 Python 的类中,初始化就担负着类似的工作。要用类构建一个实例对象,就必须调用初始化函数。
大家可以看到,在这段代码中,初始化函数的参数除了self之外还有一个name——也就是说,当我们要初始化这个类的一个实例的时候,我们需要给它一个参数。在初始化函数中,self.name=name的含义是要建立实例的一个属性。这个属性的名字是name,它的值就是参数name传入的值。学习过 C 的同学一定还记得 C 定义的类中可以声明成员变量——而我们已经知道 Python 中的变量是不用声明的,因此在 Python 中,我们使用这种直接赋值的方式来建立实例的属性。
这里需要说明的是,属性self.name中的属性名称name,并不一定要跟初始化函数中的参数名字完全相同——这里我们故意把函数的参数也命名为name仅仅是为了提升代码的可读性,并不是必须这么做的——如果你愿意的话,self.X=name的写法也是可以的。
def get_name(self)和def color(self,color)是类里的另外两个方法——除了第一个函数参数必须是self之外,它们的定义方式跟一般的函数完全一样。这里需要说明的是,像self.name这样的调用方法,只能在类中使用。
2、建立类的实例对象
类是对象的定义,而实例才是真实存在的对象——例如我们定义的“记者”就是一个类,然而抽象的“记者”这个概念并不代表具体的某个真实存在的记者,只有“华莱士”或者“张宝华”才是具体的某个记者——我们把“华莱士”,“张宝华”称作“记者”这个类的实例。
跟之前我们学过的,建立一个列表的方法类似——我们可以用初始化函数建立一个类。请大家在if __name__=="__main__":的下方写下:
western=Journalist("Wallace")
print(western.name)
现在点击运行,可以看到print函数已经输出了western对象的name。接下来让我们尝试一下,使用一个变量来保存这个属性的值:
name=western.get_name()
print(name)
下面由大家来完成:建立一个变量his_speed,获得western对象的speed方法的返回值——方法的参数设定为100
代码:
class Journalist:
"""
Take this as an example
"""
def __init__(self, name):
self.name = name
def get_name(self):
return self.name
def speed(self, speed):
d = {}
d[self.name] = speed
return d
if __name__ == "__main__":
western = Journalist("Wallace")
print(western.name)
name = western.get_name()
print(name)
his_speed = western.speed(100)
print (his_speed)
3、类的方法详细介绍
刚才我们已经初步体会了一下如何使用我们自己定义的Journalist类。现在我们将会依次讲解之前我们所使用的那些方法。 之前的代码如上面所示,大家可以先回顾一下。
创建实例的过程是调用类Person(),先执行初始化函数——上述例子中的初始化函数有两个参数self和name。其中,self作为默认参数,虽然我们在定义方法的时候需要显式地将其写出来,但是在调用的时候是不需要给它传值的。而name则需要传值——Journalist('Wallace')的样式,就是为初始化函数中的name参数传值。
对于有 C 或者 JAVA 基础的同学,这里我们需要说明一下——上面我们用了“传值”的说法,但是实际上这是不严谨的——Python只允许传引用。在后面的课程中,我们将会详细地对这里进行讲解。
调用构造函数会返回一个实例对象的引用——这里我们将其保存在变量girl里。然后我们就可以通过girl来使用这个实例对象了。 在建立一个实例对象的过程中,Python 解释器首先执行了__init__(),通过参数name得到实例的属性self.name='Wallace'。这里我们简单介绍一下self的作用——它实际上就是我们所建立的实例对象本身。当大家用实例调用方法的时候,解释器会把实例传递给方法,因此我们不需要显式地给self传参。 这样,当我们使用print(western.name)的时候,输出的就是western的属性name的值。不光是可以直接输出,我们还可以用另一个变量name将它保存起来。
另外,在调用方法的时候,根据方法的定义,我们也可以通过给方法传递参数来改变实例对象的属性的值——例如下面的his_speed=western.speed(100)。
上面我们所介绍的,就是之前我们写下的代码的含义——通过类建立一个实例对象,然后再通过实例来调用它的属性和方法。
4、类的属性与数据
这节课,我们将对关于类的属性的一些详细的细节进行展示。
现在我们仍然使用刚才的Journalist类——可以看到,这里我们直接定义了一个变量name,然后直接给它赋值了。 现在,让我们来在类外输出一下这个变量:
print(Journalist.name)
main.py
现在点击运行,可以看到print函数已经输出了Wallace,也就是我们给类属性赋的值。这样直接定义的变量,被称为类的属性,注意是类的属性而不是实例对象的属性。
这个属性由整个类所共有,并不依赖于某个实例对象。它还有一个名字叫做 静态变量 或者 静态数据。看到这个名字,学习过 C 的同学一定不会陌生。实际上,它等价于我们在 C 中,用这样的写法来写:
#include<string>
using std::string;
class Journalist {
public:
static string name;
};
Journalist::name = "Wallace";
接下来,让我们在代码末尾添加以下新的代码:
Journalist.speed = 100
print(Journalist.speed)
del Journalist.name
print(Journalist.name)
点击运行,我们可以看到,Python 的类的属性是非常灵活的——对于一个已经定义好的类,我们不仅可以在类外修改它的属性,而且也可以添加和删除新的属性。当解释器执行了del Journalist.name之后,原来的name属性就被删掉了,然后下一句print(Journalist.name)就报错了。
接下来让我们删掉最后两句代码,然后写入以下代码:
hongkong = Journalist()
print(hongkong.name)
点击“运行”,大家可以看到print(hongkong.name)输出的内容,跟print(Journalist.name)是一样的。
给一个类的实例对象添加属性,除了我们之前已经见过的,在初始化函数中使用self参数之外,我们还可以定义类属性——这样一来,新建的实例对象会自动继承所有的类属性。 需要注意的是,如果我们修改继承自类属性的实例属性的话,那么系统为我们新建一个同名的属性,并“覆盖”掉原有的属性。
接下来大家来尝试在最后输入以下代码:
hongkong.name="Zhangbaohua"
print(hongkong.name)
print(Journalist.name)
点击“运行”,大家可以看到print(hongkong.name)输出的内容被修改了,而print(Journalist.name)输出的内容跟原来一样。
如果我们执行del hongkong.name之后,再一次print(hongkong.name),并不会报错,而是重新显示Journalist.name属性的内容——也就是说,如果我们删除一个实例对象的属性,并且这个属性覆盖了一个类属性的话,那么删除之后这个类属性就会“暴露”出来。
之前我们添加的属性都是 Python 内置的不可变类型——如果是可变类型的话,情况会有些不一样。现在大家在类定义中添加这样一行代码:
height=[]
这一次我们在类定义中追加了一个 Python 自带的列表,它是一个可变数据类型。 现在让我们再试试通过实例对象对其进行操作,然后分别输出实例属性和类属性:
hongkong.height.append(160)
print(hongkong.height)
print(Journalist.height)
点击“运行”,观察输出结果可以发现,当类中变量引用的是可变对象时,类属性和实例属性都能直接修改这个对象,从而影响另一方的值。 此外,需要说明的是,我们已经知道如何给类增加属性了———通过类增加的属性可以影响到实例对象。类似地,我们也可以通过同样的方法给实例对象添加属性——但是给实例对象添加的属性无法影响到类。
代码:
class Journalist:
"""
Take this as an example
"""
name = 'Wallace'
height =[]
print (Journalist.name)
Journalist.speed =100
print (Journalist.speed)
hongkong = Journalist()
print (hongkong.name)
hongkong.name = "Zhangbaohua"
print (hongkong.name)
print (Journalist.name)
hongkong.height.append(160)
print (hongkong.height)
print (Journalist.height)
5、关于方法的更多细节
我们已经知道,类里面除了属性之外还有方法——当然也会有注释和文档,但那是给其他程序员看的,解释器在执行代码的时候会直接忽略它们。
通常情况下,我们要使用实例对象来调用方法——具体的方式在之前我们已经介绍过了。现在,让我们来介绍一些其他的调用方法的方式。
现在让我们考虑这样一个类:
class Elder:
def get_name(self):
print("Toad")
在当前类定义下,当我们建立一个实例化对象之后,我们就可以用对象来调用方法:
he = Elder()
print(he.get_name()) # 输出 Toad
在类中的方法,本质上其实是一个函数——只不过这个函数的第一个参数必须是self。这个self参数表示调用方法的实例对象。从这个角度讲,方法跟函数并没有本质上的区别。
我们使用实例对象调用方法的时候,Python 解释器会把实例对象作为第一个参数传给该方法——这一点我们之前给大家说过。实际上,我们还可以显式地给方法传参:
print(Elder.get_name(he)) # 输出 Toad
还有另一类方法,并不以self为第一个参数——现在考虑之前定义的Journalist类:
class Journalist:
"""
Take this as an example
"""
name = 'Wallace'
def __init__(self):
self.name = "Zhangbaohua"
@classmethod
def get_class_name(cls):
return cls.name
大家可以看到我们新定义了一个方法get_class_name,它只有一个参数cls,并没有参数self,上面还有一个@classmethod标记修饰——这就是所谓的类方法。它有一个参数cls(这里的参数叫什么都可以),作用是返回参数的name属性。作为类方法,它既可以被类本身调用,也可以被实例对象调用:
hongkong = Journalist()
print(Journalist.get_class_name()) # 输出 Wallace
print(hongkong.get_class_name()) # 输出 Zhangbaohua
除了类方法之外,还有另一种类似的方法:静态方法。参见以下代码: 关于方法的更多细节 意见反馈 本节内容计 1 分,完成后得满分 除了类方法之外,还有另一种类似的方法:静态方法。参见以下代码:
import random
class Elder:
def __init__(self):
self.life = get_num()
def get_name(self):
print("Toad")
在类中使用了类外面的函数get_num()——类和类外函数的耦合,在编码上并不是一种很好的习惯:这种做法给程序的维护带来了潜在的麻烦。而所谓“静态方法”,就是把get_num()这样的函数放进类中
import random
class Elder:
def __init__(self):
self.life = get_num()
def get_name(self):
print("Toad")
@staticmethod
def get_num():
a = random.randint(1926, 10000)
return a
使用了@staticmethod装饰器之后,现在get_num是函数的一个静态方法——它实际上相当于一个独立的函数,跟类本身并没有什么关系。换而言之,我们用一种简单粗暴的方法解决了上面的问题——用静态方法,把函数“包装”进了类。
6、面向对象编程的其他概念
现在,我们对于 Python 中的面向对象程序设计,已经有了一个初步的概念。接下来让我们来介绍一下面向对象程序设计的四个主要特点。在这门课里,我们只对这些特性做最为简单的介绍——对于面向对象程序设计的思想和方法,大家还需要更进一步的学习实践才能充分理解。
抽象:
现实中,我们认识问题最基本的手段之一便是抽象——而在面向对象方法中的抽象,是指对于一系列具体问题进行概括,抽出一类对象的公共性质并且加以描述的过程。 一般来讲,对一个问题的抽象应该分为两个方面:数据抽象 和 行为抽象。前者描述某类对象的特性和状态,而后者则描述某类对象的功能或者行为特性。 此外,对于同一个研究对象,如果研究侧重点不同的话,也可能产生不同的抽象结果——比如说,同样是“人”这个类,如果存在于一个企业开发的人事管理软件的话,它还会有更多的成员变量,比如部门、工龄、工资等等……
封装:
封装就是将抽象出的数据和行为(或者功能)相结合,形成一个有机的整体,也就是 类(class),其中的属性和方法都是类的成员。 比如说,假设我们使用 C 语言来定义一个时钟clock类,给出如下代码:
class Clock {
public:
Clock(); // C 中的初始化函数,相当于 Python 中的 __init__
void setTime(int newH,int newM,int newS);//在 C 和 JAVA 等语言中,函数定义需要写明参数的类型和返回值类型,这一点跟 Python 不一样
void showTime();
private:
int hour,minute,second;
};
大家可以注意到public和private关键字,有 C 或者 JAVA 基础的同学不难明白——这个定义的效果是:外界的程序不能直接访问三个成员变量,只能通过两个函数来间接查看或者修改三个变量的值。这样做就可以实现对成员访问权限的合理控制,让不同类之间的相互影响(耦合)减少到最低限度,进而增强数据的安全性,并简化程序编写工作。
举个简单的例子:还是对于Clock类,如果不做封装的话外界的程序直接对三个变量进行访问——假设有人故意要搞破坏,把表示时间的变量的值设置成一个根本不可能出现的值(比如设置成 999999 小时 999999 分钟),这就可能让程序出错。而进行封装之后,外界想要重新设置时间就必须通过setTime()函数——这样我们就可以在函数中增加检查输入合法性的代码,避免不合法数据的恶意输入。
作为一种典型的面向对象编程语言,Python 当然也支持数据的封装,也就是“私有化”,并且写法比 C 更加简单。同样是上面的Clock类,我们用 Python 来写,就是这个样子:
class Clock:
def __init__(self):
self.__hour = 0
self.__minute = 0
self.__second = 0
def showTime(self):
print(self.__hour, self.__minute, self.__second)
可以看到,我们只要在一个想要私有化的属性前面加上双下划线__就可以将其私有化,等同于 C 中的private关键字。这样一来,当我们想要在外界直接访问私有属性的话,就会报错:
C:DATAGitHubjisuankeWorkgitlabpython_programmingpython3new_addoop_in_py (new_add)
λ python test.py
Traceback (most recent call last):
File "test.py", line 9, in <module>
print(c.__hour)
AttributeError: 'Clock' object has no attribute '__hour'
私有属性只能通过类内的方法来访问,这就是所谓“封装”。不光是有私有属性,我们同样也可以定义私有方法,定义的方式跟私有属性相同:
class Clock:
def __init__(self):
self.__hour=0
self.__minute=0
self.__second=0
def showTime(self):
print(self.__hour,self.__minute,self.__second)
def __getSecond(self): # 私有方法
return self.__second
继承与多态:
现实生活中的概念具有特殊与一般的关系——比如一般意义上,“人”这个概念都有姓名、性别、年龄等属性,以及吃饭、走路之类的行为,但是呢,按照职业划分的话,“人”这个广泛的概念又可以进一步地细分,比如学生、教师、工程师、医生之类的。每一类人又有自己的特殊行为与属性,比如学生的学号、专业、毕业之类的特殊属性和行为,就是医生这个群体所不具备的。
对于面向对象编程语言,继承 的含义是,新建一个继承了原有类的新类,具有原有类的所有特征的同时,又具有自身的新特性。通过类的这种层次结构,可以很好地反映出特殊概念与一般概念的对应关系。通常我们把被其他类继承的类叫做父类或者基类,而继承自一个父类的类则称为子类或者派生类。
在 Python 语言中,继承可以通过这样的写法实现:
class P:
pass
class C(P):
pass
类C是继承自类P的一个子类——如果父类定义了一些属性和方法,那么子类就会拥有父类定义的所有内容。这就是继承的简单理解。 现在大家已经知道,在 Python 中,一切都是对象——事实上,我们自己定义的任何一个类,都是继承自 Python 中内置的基类object。比如说,上面我们定义的类P,我们可以在交互式命令行下,用内置方法__base__查看它的基类:
>>> class P:
... pass
...
>>> P.__base__
<class 'object'>
>>>
多态 则是面向对象程序设计,对于人类思维方式的一种直接模拟——从广义上说,多态性 指的是同一段程序,可以直接处理多种类型对象的能力。
比如说,汉语中的“打球”,可以分解成动词“打”和名词“球”——“打”这个动作可以对应多种球类运动,比如打篮球、打排球、打乒乓球等等,而每一种球类运动都有着截然不同的动作和规则——这就是对多种运动行为的抽象,对应到面向对象程序设计中就是多态性。
熟悉 C/C 或 JAVA 语言的同学一定知道,在这些语言中,变量需要先声明类型,然后才能使用。如下所示:
int i;
i = 1;
而大家已经知道,在 Python 中,我们是不需要先声明一个变量的类型的:
i=1
当然,现代 C 也提供了一些语法糖来达到类似于 Python 的效果:
auto i = 1; // 注意必须在定义变量时初始化,形如 auto i; 的写法是不允许的
由此可见,从类型系统的角度来看,C 和 JAVA 属于静态类型语言,而 Python 是一种动态类型语言,变量的类型是在解释器运行的时候通过类型推导所确定的(需要注意的是解释器实际上仍然会对代码进行编译)。 因此,有一种观点认为,Python 这种语言天生就具有多态性。
比如说,我们可以定义这样一个函数:
def print_length(item):
print(len(item))
然后我们就可以把任何一种可以用len求长度的对象传给print_length函数,得到正确的结果。
print_length("123") # 输出 3
print_length([1,2]) # 输出 2
如果是 C 之类的语言,在定义函数的时候需要用到函数重载或者模板机制,不过对于 Python 来说显然就方便多了。
继承与多态是面向对象程序设计的两个高级特性——这一门课程中,我们只要求大家对面向对象程序设计的基本概念、以及这些概念在 Python 中的实际应用有一个基本的了解与掌握。因此,这两个特性在我们这门课程中只做简要介绍,更多的内容大家在后续《面向对象程序设计》等相关课程中学习。 尽管很多关于面向对象程序设计高级内容的课程使用的不是 Python,但其核心思想仍然是一样的。学会了最核心的思想方法,就可以较容易地将其应用在 Python 编程中。
7、HR 管理(题目)
为了帮助 HR 姐姐减轻工作负担,请你用 Python 语言设计一个People类,这个类应该包含以下私有属性: 编号self.ID 姓名self.name 性别self.gender 工资self.salary 你需要定义实例方法set_ID(ID)、set_name(name)和set_salary(number),参数分别为员工的编号,姓名和工资。这些方法的作用是对员工对象相应的属性进行修改。同时,类也应该可以使用初始化方法直接设定这些属性。
另外,你还需要定义一个show()方法,按格式输出员工对象的编号,姓名和工资——具体输出格式可以参见输出样例。
本题的初始化代码已经给出,你不能修改它们。
预期的输出如下所示:
2010201326 elder Male 301.0
19260817 he Male 301301.0
样例输出
2010201326 elder Male 301.0
19260817 he Male 301301.0
代码:
#类部分
if __name__=="__main__":
p1=People("elder", "Male", "2010201326", 301.0)
p1.show()
p1.set_ID("19260817")
p1.set_name("he")
p1.set_salary(301301.0)
p1.show()
第八章 Python 中的函数
1、函数概念的复习
现在让我们先来简单地回忆一下,在 Python 中是如何定义函数的。
def max_pow(a, b):
if a > b:
pow_ab = a ** b
return pow_ab
pow_ba = b ** a
return pow_ba
我们已经知道,def这行以下的所有语句都是属于名为max_pow的这个接收了被定名为a和b两个函数的参数形式的函数的定义部分。我们看到,在定义部分我们用到了可以被传入的a和b。 对于max_pow这个函数,我们可以通过max_pow(3, 2)的形式进行 调用(call),而在这里的 3 和 2 则是被传入给参数形式的 实际参数(actual parameter)。我们定义的max_pow这个函数将会用 3 代替 a ,而用 2 代替 b ,并执行函数中定义的一系列的语句。如果有需要继续使用函数中的结果,我们则可以写上return并在 return 语句后紧跟定义的 函数被调用后的返回结果(return value) 。
现在再让我们来看一个例子:
def add(x,y):
return x y
这个函数会返回参数x和y进行加法运算的结果(注意这里我没有说求和),调用add(1, 2)会返回两数相加的和3,显然这对于大家来说应该是很好理解的。
然而如果调用的参数不是整数或者浮点数,比如add("jisuanke", "python")的话,也能正确进行运算吗?答案当然也是肯定的:返回结果会是jisuankepython,即两个字符串直接连接的结果。
这就是我们之前介绍过的,编程语言的多态性的体现——Python 天生就是一种多态的语言,x和y两个参数变量,不管它们的类型是什么,只要可以进行 运算,就可以返回正确的结果。
当然,前提条件是x和y之间可以进行 运算——如果x是整数,而y是字符串的话,那么就会出错。
另外,在交互式环境中定义了函数add之后,大家可以试试这样输入:
>>> type(add)
<class 'function'>
>>>
还记得上一章中,我们向大家说过,在 Python 中,一切都是对象 吗?没错,函数本身也是一种对象。在后续课程中,我们会向大家介绍,函数作为对象的一些特有性质。
2、函数如何定义(题目)
在之前的章节中,我们对于 Python 语言中的函数已经有了一个初步的了解——现在请从下面的选项中,选出你认为正确的答案吧!
(1)Python 语言中,函数可以没有参数
(2)Python 语言的函数必须有返回值
(3)函数是程序的一个子模块
(4)在软件开发中应尽可能避免函数的使用
(5)若函数的两个参数在函数体中执行加法运算,则参数类型必须为整数或浮点数
3、关于函数的命名
现在我们已经自己编写了一些 Python 程序——此前我们已经接触过了一些函数和变量的命名了。以下是 Python 编程中的一些对命名的常见要求:
变量:变量名全部小写,由下划线连接各个单词,不能与 Python 的保留字冲突。 文件名:小写,可使用下划线 函数名:小写,可以使用下划线,也可以使用首字母大写的方式(除了第一个单词首字母小写之外,剩下的单词首字母大写)。
在函数定义的时候,我们也可以调用另一个函数:
def extend():
print('excited!')
plus_one_sec()
如果被调用的函数,之前并没有被定义的话,那么执行的时候就会报错:
>>> def extend():
... print('excited!')
... plus_one_sec()
...
>>> extend()
excited!
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in extend
NameError: name 'plus_one_sec' is not defined
>>>
学习过 C 等语言的同学,一定知道什么叫做“前向引用声明”——也就是说,如果我们要调用的函数存在循环引用问题,我们就必须把函数的声明写在最前面。而 Python 不需要这么做——只要函数定义了,那么无论先后,都可以使用。
这里如果我们继续在交互式环境中给出函数定义的话(哪怕实际上什么都不做),我们也可以正常运行一开始定义的函数了:
>>> def plus_one_sec():
... pass
...
>>> extend()
excited!
>>>
4、函数的返回值
这一节我们将会对函数的返回值进行一些讲解。 大家之前已经见过了返回一个返回值(可以是变量也可以是对象),实际上函数也可以返回多个值。现在大家可以试一试输入以下代码:
def multi_return():
return 1, 2, 3
re_val = multi_return()
print(re_val)
现在点击运行,可以看到print函数已经输出了(1,2,3)——这说明函数以元组的形式返回了这三个数。而对于同一个函数,我们还可以以不同的形式来接收返回值。
现在,大家把re_val改成三个变量x,y,z,然后print中的参数改成x。
def multi_return():
return 1, 2, 3
x,y,z = multi_return()
print(x)
点击运行,可以看到print输出了x的值1。 最后,我们已经知道return可以返回一个或者几个变量或对象的值,而除此之外return还有另一个作用。现在,大家把原来的代码都删掉,然后输入以下代码:
def func():
print("part A")
return
print("part B")
func()
点击“运行”之后,大家可以注意到,程序运行的时候只输出了part A,也就是说,在return 之后的语句并没有被执行。从这里我们可以看出这样一件事情:
对于不加参数的 return 其作用是直接结束正在执行的函数,并离开函数体,返回到调用开始的位置
5、函数的对象性
虽然我们写程序,写的是给计算机执行的代码,但实际上,程序在大部分时间里是给人看的。所以我们需要给函数写文档。
写文档的格式如下所示——一般写在函数名字的下面,主要用于说明函数的用途。
def func():
"""
func() help
"""
pass
定义好文档之后,在交互式命令行中,我们可以直接使用func.__doc__查看函数的文档。
大家是不是觉得这种写法非常眼熟?没错——正如我们前面所说的那样,函数本身也是Python 中的对象。而“文档”其实就是函数对象的一个属性。
任何对象都具有属性,函数自然也不例外——我们甚至还可以给函数定义额外的属性,就跟对象一样。比如对于已经定义好的函数func,我们可以这样给它增加一个新的属性:
func.attr = 10
接下来我们就可以直接调用这个属性了:
print(func.attr) # 输出 10
在 Python 中内置了一个函数dir,可以用来查看一个对象具有哪些属性和方法。而我们前面用过的__doc__属性,跟其他的一些对象自带属性一样,使用双下划线开头——这些属性称之为特殊属性。同样,函数作为对象也是拥有一些自带的特殊方法 的。
有兴趣的同学可以自己调用dir函数,看看一个函数对象具有哪些内置的属性和方法,然后通过查阅 Python 文档来搜索这些方法的说明。
6、函数参数
现在让我们先来回顾一下已经学过的函数定义:
def max_pow(a, b):
if a > b:
pow_ab = a ** b
return pow_ab
pow_ba = b ** a
return pow_ba
在函数调用中,max_pow(3,2)中的3,2是这个函数的实际参数——而在函数的定义中,max_pow(a,b)中的a,b,则是函数的形式参数。在函数调用中,实际参数被传递到函数内部,这一过程被称为形实结合
在函数调用的过程中,有的时候我们会遇到这样一种情况:我们无法判断函数会有多少个参数。这个时候该怎么办呢?Python 提供了一种非常方便的机制:*args参数。在接下来的课程中,我们将会亲自体验它的用法。
下面我们将会向大家介绍如何让一个函数可以读取不定个数的参数。我们现在有一个函数app,它的第一个参数是一个列表,后面的参数则是若干个变量。它的作用是把这些变量都加入到列表里(调用列表的append方法)。
这里我们要使用 Python 提供的*args参数——我们这样来定义函数:
def app(ls, *args):
这一步大家先把函数的定义写在代码上方的空白处。
接下来我们开始实现函数的功能——函数的作用是,把从第二个参数开始的每一个参数,都加入进第一个参数(列表)中。
我们可以这样来实现这个功能:
for item in args:
#在列表中插入元素
大家来自己试着写一下吧!
def app(ls, *args):
for item in args:
ls.append(item)
ls = []
app(ls, 1, 2)
print(ls)
ls2 = []
app(ls2, 1, 2, 3)
print(ls2)
点击运行,可以看到输出ls的内容变成了[1,2],而ls2的内容变成了[1,2,3]。
接下来让我们来实现另一个函数——学过 C/C 语言的同学一定会对这个swap函数的写法非常熟悉:
def swap(a, b):
temp = a
a = b
b = temp
现在让我们把这个函数写在app函数的下方,然后在代码的末尾写下它的调用:
a = 1
b = 2
swap(a, b)
print(a, b)
点击“运行”之后,大家可以注意到,变量并没有被交换!
def app(ls, *args):
for item in args:
ls.append(item)
def swap(a, b):
temp = a
a = b
b = temp
ls = []
app(ls, 1, 2)
print(ls)
ls2 = []
app(ls2, 1, 2, 3)
print(ls2)
a = 1
b = 2
swap(a, b)
print(a, b)
这就引出了一个关于 Python 的参数传递机制的问题——在下一节中,我们将会对 Python 中,函数的参数传递方法做详细的介绍。
7、Python 中的引用传参
C/C 等语言是没有垃圾回收机制,由程序员自己来管理内存。 JAVA/C# 等,都通过更高层次的抽象,屏蔽了跟内存有关的底层细节,让程序员能专注于程序的逻辑本身。而 Python 也是这样的一种语言。
在刚才的例子中,我们已经看到,使用swap函数交换两个变量的值的时候,如果声明函数直接写成swap(a,b)——在 C 中的写法是swap(int a,int b)的话,那么交换是不会产生效果的——尽管同样的算法直接放在主程序里就能正确地完成交换操作(大家可以自行尝试)。这就是因为,我们使用的是值传递 的传参方式。
具体而言,值传递就是当函数发生调用的时候,给形参分配内存空间,然后用实参的值来初始化形参——这是一个单向传递的过程。简而言之,当我们写下swap(x,y);的时候,系统会新建两个变量a,b,然后把x,y的值分别赋给它们。你可以想象成,当我们在main函数中调用了swap函数的时候,首先执行了这样的两个语句:
int a = x; // 跟 Python 不同,C 是一种静态类型语言,强制要求写明变量类型
int b = y;
换句话说,接下来我们进行的操作,改变的只是这两个新建的变量a,b的值——它们已经与原来的实参x,y脱离了关系,不管我们怎么改动它们的值,最后都不会影响到x,y。
当函数调用结束的时候,临时新建的变量a,b都被系统销毁了——它们的内存空间被回收。于是,我们的算法虽然是正确的,但是却做了无用功,没有对实参x,y产生任何实际的影响。
我们已经看到,值传递时参数是单向传递,而现在我们想要做的是对主函数中定义的实参进行修改——这个时候,就需要使用引用传递了。
引用是一种特殊类型的变量,可以被认为是另一个变量的别名——通过引用名与通过被引用的变量名直接访问变量的效果是完全一样的。例如:
int i, j;
int &ri = i; // 建立一个 int 类型的引用 ri,并将其初始化为 i 的一个别名
j = 10;
ri = j; //相当于 i = j;
在 C 中,我们可以这样地定义一个函数:
void swap(int &a, int &b);
这样一来,接下来我们在swap函数的函数体内操作的就是int的引用类型a,b——这样一来,我们对a,b的所有操作都会最终落实到主函数中实际存在的实参x,y头上,然后swap函数就可以正常工作了。具体过程如下图所示:
介绍完了 C 的概念,下面让我们来回到 Python 之中:Python 采用的是一种“传对象引用”的参数传递方式——我们是不能自己决定如何传参的。这种所谓“传对象引用”,简单地说,大家可以认为这是上面介绍的值传递和引用传递的结合体: (1)对于不可变类型的参数,比如数字、字符或者元组,那么函数传参的时候采用的就是相当于值传递的传参方式——我们不能直接修改原始对象。 (2)对于可变类型的参数,比如列表,字典等,那么传参的方式就类似于引用传递,我们可以直接修改原始对象。
回忆我们刚才写过的函数:
def app(ls, *args):
for item in args:
ls.append(item)
因为ls作为列表,是一个可变参数,所以我们就可以通过函数来直接修改它的内容。
最后,大家肯定会问——如果我就想交换两个变量的值,那该怎么办呢?很简单,在 Python 中我们根本就不需要大费周章地写一个函数,直接这么写就可以了:
a, b = b, a
题目:
在之前的章节中,我们对默认参数值与函数重载的概念已经有了一个初步的了解——现在请从下面的选项中,选出你认为正确的答案吧!
(1)Python 允许程序员自己决定如何传递参数
(2)Python 函数通过“传对象引用”传递参数
(3)Python 的函数在任何情况下都无法修改参数对应的原始对象
(4)Python 传递不可变类型时不能修改原对象
(5)Python 中除非手动定义中间变量,否则用任何方式都不能交换两个不可变对象的值
8、函数的递归调用
我们已经知道,在一个函数中,我们可以调用另一个函数:
除了嵌套调用其他函数之外,函数还可以直接或者间接地调用自身——这种调用称为递归调用,简称递归。大部分图灵完备的编程语言都提供了递归调用这个非常重要的功能,Python 自然也不例外。
所谓直接调用自身,就是指在一个函数的函数体中出现了对自身的调用表达式,例如
def fun1:
fun1()
而下面的例子则是函数间接调用自身:
def fun2:
fun1()
def fun1:
pass
这里两个函数互相调用,同样也构成了递归。
在程序设计中,递归是一种极其重要的编程思想,它对应的是算法设计中的一种方法——分治法。它的实质,是将原有的问题分解成一个或者若干个新的,规模比原来更小的问题,而解决新问题时又用到了原有问题的解法,按照这个原则继续分解下去,最后可以得到一个平凡的、可以直接解出的子问题,于是递归就此结束——这就是有限的递归调用。
而无限的递归调用,跟死循环一样,永远都得不到解,是没有实际意义的——而且由于递归调用运行机制的原因,递归次数太多还会导致程序出错(这个我们将在后面的章节详细介绍)。
递归的过程有如下两个阶段:第一阶段称为递推,也就是将原问题不断地分解为新的子问题(这一操作称为递推步),逐渐从未知向已知迈进,最后达到已知的,可以直接求解的终点,称为递归基。比如说,现在我们要求一个数n的阶乘n!,根据阶乘的定义,我们可以得到这样一个关系式:
n! = n*(n-1)!
同时,当n=0时,我们可以直接得到已知解,即:
0! = 1
这样一来,我们就可以用递归来求解这个问题——上面的两个关系式,前者即为递推步,后者即为递归基。对于一个递归函数来说,这两者都是必不可少的。第二阶段称为回归,为递推的逆过程——从已知条件出发,逐一求值回归,最后达到递推的开始点,结束回归阶段,完成递归调用。
题目
我们对递归的概念已经有了一个初步的了解——现在请从下面的选项中,选出你认为正确的答案吧!
(1)自己定义的函数只能调用系统库中的函数
(2)程序可以无限递归
(3)递归函数必须要有递推步与递归基
(4)递归函数只能以直接调用函数自身的形式实现
(5)递归的实质是把问题分解为规模更小的子问题
9、用递归函数计算阶乘
现在,让我来试着写一个求阶乘的计算程序——在之前的课程中,我们已经向大家介绍了阶乘计算的思路,接下来让我们来试一试使用递归对阶乘进行计算。
主函数已经写好,现在让我们来开始吧!首先,让我们定义一个函数fac,参数是一个数字n。这一步大家只要先定义好函数就可以了,快来试试吧!
很好,接下来让我们首先来看一看递归基——这里我们使用if来进行判断。我们知道,对于阶乘运算来说,递归基就是0 != 1,所以进入递归基的条件自然就是输入的n为 0了。
这里我们先把if的框架写好,注意不要写错了缩进关系哦!
赞一个,接下来让我们来实现else的内容——递归计算阶乘的递推公式可以表示为:
n!=n×(n−1)!
让我们用函数来实现它吧!请把else的框架写好,并且直接在else中写下递推式,直接返回递推式,快来试试吧!注意不要搞错缩进哦!
代码:
def fac(n):
if n == 0:
return 1
else:
return n * fac(n - 1)
print('Enter a positive integer:')
n = int(input())
y = fac(n)
print(y)
10、头递归与尾递归
现在我们已经知道,该如何使用递归来进行阶乘计算了:
def factorial(int n):
if n == 1:
return 1
else:
return factorial(n - 1) * n
我们之前已经学过 递归调用(recursive call)。老师设计的上面这个函数就是一个递归实现的计算阶乘的函数。在一般条件(如这里的n == 1)满足时,返回一个确定的值,而在其他情况下,返回一个包含本身函数的递归调用的这种递归设计,被我们称为 头递归(head recursion)。
与头递归相对应,我们还有一个概念叫 尾递归(tail recursion)。如果我们用尾递归实现求阶乘的函数,我们可以进行类似下面这样的实现:
def factorial(n,product):
if n == 0:
return product
product = product * n
return factorial(n - 1, product)
如果我们传入给函数的n是 5,product是 1,我们将得到:
factorial(5,1)
=factorial(4,5)
=factorial(3,5∗4)
=factorial(2,5∗4∗3)
=factorial(1,5∗4∗3∗2)
=factorial(0,5∗4∗3∗2∗1)
=5∗4∗3∗2∗1
在这里,我们可以对比一下头递归和尾递归实现的函数从被调用到返回的过程。
头递归:
在头递归的实现中,我们在进行下一层的调用前,没有进行计算。在下一层返回后,我们才完成了这一层的计算。
尾递归:
在尾递归的实现中,我们在进行下一层的调用前,会先进行计算,而在最终一般条件满足时,会将计算的结果逐层直接返回。
11、Python 中的迭代器
在 Python 中,如果要访问一个对象中的每一个元素,那么可以这么做:(以下以列表为例)
lst = ['j','i','s','u','a','n']
for i in lst:
print(i, end = ' ')
这是我们已经知道的,基于for循环的方法。
除此之外,我们还可以这么做:
lst_iter = iter(lst)
print(lst_iter.__next__()) # 输出 j
print(lst_iter.__next__()) # 输出 i
...
iter函数:
iter函数是 Python 中内建的系统函数之一——它的作用是返回一个迭代器对象。所有的迭代器对象,在 Python3 中都有__next__方法,其作用是取得下一个元素。实际上,for循环的实现,就是通过迭代器来完成的。
12、函数作为参数传递
让我们来考虑这样的一个问题:现在有一个列表,ls = [11, 23, 33],我们要对列表中的每一个元素,都进行这样三种运算,然后返回一个新的列表。三种运算如下:
将列表中的每一个元素都求平方 将列表中的每一个元素(数字)转换为字符串 给每一个元素的值加上 1
大家首先想到的是写三个for循环的函数来完成这些任务。然而仔细观察的话,我们不难发现,这些函数的逻辑其实可以分为两个部分:
遍历每一个元素,对每一个元素都进行某种操作 具体执行这种操作,也就是上面三种运算其中之一
换句话说,我们其实是可以把“执行某种操作”的步骤抽象出来的——这样的话,我们就不用复制粘贴三遍相同的代码来实现“遍历所有元素”的部分了。这也就是我们这一节要向大家介绍的内容——把函数作为参数传递。
现在,让我们来首先定义第一个函数:
def func_seq(func, ls):
函数func一共定义了两个参数——第一个参数func就是我们要传递的函数参数,而第二个参数ls则是要进行操作的列表。这里需要说明的是,具体哪个参数代表什么意义,是由你自己决定的,参数也可以随便写成任何名字。
接下来我们开始实现整个函数——这里我们使用一种比较方便的写法,直接返回一个列表:
return [func(i) for i in ls]
大家可以看到,在func_seq函数内调用func的过程跟在外界调用func相差无几。 接下来让我们定义具体的三种运算所对应的函数——把以下三个函数写在func_seq下方:
def sqrt(num):
return num ** 2
def to_str(num):
return str(num)
def plus(num):
return num 1
函数作为参数传递 意见反馈 本节内容计 5 分,完成后得满分 最后一步,我们可以调用这些定义好的函数了:
print(func_seq(sqrt, ls))
print(func_seq(to_str, ls))
print(func_seq(plus, ls))
把以上代码写在ls下方。
大家可以看到,函数作为参数被另一个函数调用,写法跟函数传递其他类型的参数,并没有明显的区别。
灵活运用这种机制,可以更好地降低重复劳动,提高程序可靠性——比如说,如果我们需要对某些更加复杂的数据结构进行遍历的话,那么一遍一遍地复制粘贴就会变得非常麻烦,也很容易出错。而如果我们把“遍历”和“对元素执行操作”分别抽象成两个不同的函数的话,那么这样一来就更加方便,也更加可靠了。
代码:
def func_seq(func, ls):
return [func(i) for i in ls]
def sqrt(num):
return num ** 2
def to_str(num):
return str(num)
def plus(num):
return num 1
ls = [11, 23, 33]
print(func_seq(sqrt, ls))
print(func_seq(to_str, ls))
print(func_seq(plus, ls))
13、 lambda、map和reduce
在 Python 中,有一系列特殊的函数——它们的存在可以让 Python 具备所谓函数式编程的能力。函数式编程是一种编程范式,跟我们之前学习过的面向过程编程和面向对象编程具有并列关系。
这里,我们先不展开介绍关于函数式编程的更多内容,只介绍这几个特殊的函数:lambda,map和reduce。这一节先介绍lambda,之后再依次介绍map和reduce。
lambda函数:
让我们考虑这样一段代码:
def func_seq(func, ls):
return [func(i) for i in ls]
这就是我们之前定义的对列表进行操作的函数——对于比较复杂的运算来说,单独定义一个func函数,显然可以有效提高抽象层次,减少重复劳动。而如果我们要进行的运算比较简单的话,再写一个函数就有点麻烦了。为此 Python 提供了一种特殊的函数lambda。
比如说,如果我们想要定义一个函数,返回参数的值的平方,那么我们可以直接这样写:
sqrt= lambda x : x ** 2
然后我们就可以像调用普通函数一样调用他:
print(sqrt(2))
lambda这种语言特性,很早就出现在了 Python 之中——其他的一些语言也随着标准的更新,增加了 lambda 表达式的支持,比如 C 11、JAVA 8 和 C# 3.0 等。
在 Python 中,lambda函数的正式定义如下所示:
lambda arg1, args, ... argN: expressions_with_args
可见lambda函数可以接受任意多个参数。比如这样:
func = lambda x, y : x y # 返回 x y 的值
这里需要说明的是,lambda包含的表达式不能超过一个——如果你想定义更复杂的东西,那最好还是定义一个普通函数。
map函数:
继续让我们考虑之前的代码:
def func_seq(func, ls):
return [func(i) for i in ls]
我们已经知道,这个函数的作用是对一个列表中的每一个元素应用func函数,然后用得到的新元素建立一个新的列表,并直接返回。而接下来要介绍的map函数,实际上跟我们自己定义的func_seq的作用是一样的——而 map 作为 Python 内置的函数,显然给我们省下了很多麻烦。
map函数的使用方式,跟我们自己定义的func_seq函数是完全一样的,语法如下所示:
map(function, iterable, ...)
第一个参数function表示一个函数,而iterable则表示一个可迭代对象(如列表)。
举个简单的例子——跟之前一样,现在我们有一个列表,并定义了一个返回一个数加 1 的函数:
ls = [11,22,33]
plus = lambda x : x 1
在 Python 3 中,当我们调用map函数的时候,我们会得到一个迭代器:
print(map(plus, ls)) # <map object at 0x000001BF7F46C6A0>
当我们需要使用map的结果时,我们可以直接用list函数将其转化为一个列表:
>>> list(map(plus, ls01)
[2, 2, 3, 4]
细心的同学这个时候也许会注意到,上面我们给出的map函数用法中,iterable后面有几个省略号——事实上这也就意味着,map函数可以使用的可迭代对象参数个数并不是只有一个。
我们可以参见这样一段代码:
>>> def abc(a, b, c):
... return a * 10000 b * 100 c
...
>>> list1 = [11, 22, 33]
>>> list2 = [44, 55, 66]
>>> list3 = [77, 88, 99]
>>> list(map(abc, list1, list2, list3))
[114477, 225588, 336699]
当function的参数不止一个的时候,map函数将会从后面的可迭代对象里依次选取相同下标的元素,然后“并行”应用于函数function。这里的“并行”是 Python 官方文档原文的直译。
事实上,map的function也可以是None——这个时候函数的作用,就是把多个列表相同位置的元素归并到一个元组,如下所示:
>>> list1 = [11, 22, 33]
>>> list2 = [44, 55, 66]
>>> list3 = [77, 88, 99]
>>> list(map(None, list1, list2, list3))
[(11, 44, 77), (22, 55, 88), (33, 66, 99)]
reduce函数:
除了map之外,Python 中还有另一个类似的函数reduce——它也有两个参数:一个函数f,一个list,但行为和 map()不同,reduce()传入的函数 f 必须接收两个参数,reduce()对list的每个元素反复调用函数f,并返回最终结果值。
举个简单的例子——对于求和函数f:
def f(a, b):
return a b
reduce(f, [1, 3, 5, 7, 9]),将依次执行以下运算:
(1)先计算头两个元素:f(1, 3),结果为4; (2)把结果和第33个元素计算:f(4, 5),结果为9; (3)以此类推——f(9, 7),结果为16; (4)f(16, 9),结果为25; (5)所有元素都用过了,计算结束,返回结果25。
14、 汉诺塔问题 (题目)
汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着 64 片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘,如图所示:
现在请试着编写一个程序,对于一个有 nn 个盘子的汉诺塔,列举将这 nn 个盘子从柱子 A 移动到柱子 C 需要的所有移动步骤,每个步骤占一行。例如,将一个盘子从 A 移动到 C,即表示为:
A-->C
输入格式 输入一个整数 n(1≤n≤5)。
输出格式 输出若干行,表示所有移动步骤。
样例输入
3
样例输出
A-->C
A-->B
C-->B
A-->C
B-->A
B-->C
A-->C