17 函数:Python的乐高积木
测试题:
0.你有听说过DRY吗?
DRY是程序员们公认的指导原则:Don’t Repeat Yourself.
快快武装你的思维吧,拿起函数,不要再去重复拷贝一段代码了!1.都是重复一段代码,为什么我要使用函数(而不使用简单的拷贝黏贴)呢?
0) 可以降低代码量(调用函数只需要一行,而拷贝黏贴需要N倍代码)
1) 可以降低维护成本(函数只需修改def部分内容,而拷贝黏贴则需要每一处出现的地方都作修改)
2) 使程序更容易阅读(没有人会希望看到一个程序重复一万行“I love FishC.com”)2.函数可以有多个参数吗?
可以的,理论上你想要有多少个就可以有多少个,只不过如果函数的参数过多,在调用的时候出错的机率就会大大提高,因而写这个函数的程序员也会被相应的问候祖宗,所以,尽量精简吧,在Python的世界里,精简才是王道!
4.请问这个函数有多少个参数?
def MyFun((x, y), (a, b)): return x * y - a * b
【答案】
如果你回答两个,那么恭喜你错啦,答案是0,因为类似于这样的写法是错误的!
我们分析下,函数的参数需要的是变量,而这里你试图用“元祖”的形式来传递是不可行的。
我想你如果这么写,你应该是要表达这么个意思:>>> def MyFun(x, y): return x[0] * x[1] - y[0] * y[1] >>> MyFun((3, 4), (1, 2)) 10
5.请问调用以下这个函数会打印什么内容?
>>> def hello(): print('Hello World!') return print('Welcome To FishC.com!')
【答案】
>>> hello() Hello World!
【答案解析】因为当Python执行到return语句的时候,Python认为函数到此结束,需要返回了(尽管没有任何返回值)。
动动手:
0.编写一个函数power()模拟内建函数pow(),即power(x, y)为计算并返回x的y次幂的值。
【自己写的!!】
def power(x, y): return x**y
【参考答案】
def power(x, y): result = 1 for i in range(y): result *= x return result
【回顾】对于该题【result *= x】
1.对于【-=】或【+=】,首先要提前规定一个量【sum = 0】
对于【*=】,首先要规定一个量【sum = 1】2.对于【for i in range(y):】,后面不用出现【i】,系统自动会计算i次:
>>> sum = 0 >>> for i in range(10): sum += 1 print(sum) 1 2 3 4 5 6 7 8 9 10
>>> result = 1 >>> for i in range(3): result *= 4 print(result) 4 16 64
1.编写一个函数,利用欧几里得算法(脑补链接)求最大公约数,例如gcd(x, y)返回值为参数x和参数y的最大公约数。
def gcd(x, y): while y: t = x % y x = y y = t return x print(gcd(4, 6))
2.编写一个将十进制转换为二进制的函数,要求采用“除2取余”(脑补链接)的方式,结果与调用bin()一样返回字符串形式。
def Dec2Bin(dec): temp = [] result = '' while dec: quo = dec % 2 dec = dec // 2 temp.append(quo) while temp: result += str(temp.pop()) return result print(Dec2Bin(62))
018 函数:灵活即强大
测试题:
1.函数写注释有什么不同?
给函数写文档是为了让别人可以更好的理解你的函数,所以这是一个好习惯:
>>> def MyFirstFunction(name): '函数文档在函数定义的最开头部分,用不记名字符串表示' print('I love FishC.com!')
我们看到在函数开头写下的字符串Ta是不会打印出来的,但Ta会作为函数的一部分存储起来,这个我们称之为函数文档字符串,Ta的功能跟注释是一样的。函数的文档字符串可以按如下方式访问:
>>> MyFirstFunction.__doc__ '函数文档在函数定义的最开头部分,用不记名字符串表示' #另外,我们用help()来访问这个函数也可以看到这个文档字符串: >>> help(MyFirstFunction) Help on function MyFirstFunction in module __main__: MyFirstFunction(name) 函数文档在函数定义的最开头部分,用不记名字符串表示
建议使用help
4. 默认参数和关键字参数表面最大的区别是什么?
【参考答案】
关键字参数是在函数调用的时候,通过参数名制定需要赋值的参数,这样做就不怕因为搞不清参数的顺序而导致函数调用出错。
而默认参数是在参数定义的过程中,为形参赋初值,当函数调用的时候,不传递实参,则默认使用形参的初始值代替。
动动手:
019 函数:我的地盘听我的
0. 编写一个符合以下要求的函数:
a) 计算打印所有参数的和乘以基数(base=3)的结果
b) 如果参数中最后一个参数为(base=5),则设定基数为5,基数不参与求和计算。def mFun(*param, base=3): result = 0 for each in param: result += each result *= base print('结果是:', result) mFun(1, 2, 3, 4, 5, base=5)
【笔记】
细品,基数都不会求和,当base没有指定为5时默认为3,使用默认参数
若base为5,则需要覆盖掉原来默认参数为31. 寻找水仙花数 题目要求:如果一个3位数等于其各位数字的立方和,则称这个数为水仙花数。
例如153 = 1^3+5^3+3^3,因此153是一个水仙花数。编写一个程序,找出所有的水仙花数。
def Narcissus(): for each in range(100, 1000): temp = each sum = 0 while temp: sum = sum + (temp%10) ** 3 temp = temp // 10 # 注意这里用地板除 if sum == each: print(each, end='\t') print("所有的水仙花数分别是:", end='') Narcissus()
2.编写一个函数 findstr(),该函数统计一个长度为 2 的子字符串在另一个字符串中出现的次数。
def findstr(source,sub): ans=0 for i in range(len(source)): if source[i]==sub[0] and source[i+1]==sub[1]: ans+=1 return ans
020 函数:内嵌函数和闭包
测试题:
0.【复习】如果希望在函数中修改全局变量的值,应该使用什么关键字?__global 关键字:
>>> count = 5 >>> def MyFun(): global count count = 10 print(count) >>> MyFun() 10 >>> count 10
>>> count = 5 >>> def MyFun(): count = 10 print(count) >>> MyFun() 10 >>> count 5 '''没有加gobal,如果对全局变量进行修改函数会启动shadowing来保护全局变量, 在内部创建一个同名的局部变量'''
1.【复习】在嵌套的函数中,如果希望在内部修改外部函数的局部变量,应该使用什么关键字?__nonlocal关键字:
>>> def Fun1(): x = 5 def Fun2(): nonlocal x#没有这个 调用fun1会报错 x *= x return x return Fun2() >>> Fun1() 25
2. 【复习】Python 的函数可以嵌套,但要注意访问的作用域问题哦,请问以下代码存在什么问题呢?
def outside(): print('I am outside!') def inside(): print('I am inside!') inside()
【解释】使用嵌套函数要注意一点就是作用域问题,inside() 函数是内嵌在 outside() 函数中的,在外边或者别的函数体里是无法对其进行调用的。
【正确写法】:def outside(): print('I am outside!') def inside(): print('I am inside!') inside() outside()
3. 请问为什么代码 A 没有报错,但代码 B 却报错了?应该如何修改?
代码A:
def outside(): var = 5 def inside(): var = 3 print(var) inside() outside()
代码B:
def outside(): var = 5 def inside(): print(var) var = 3 inside() outside()
【原因分析】
这里 outside() 函数里有一个 var 变量,但要注意的是,内嵌函数 inside() 也有一个同名的变量,Python 为了保护变量的作用域,故将 outside() 的 var 变量屏蔽起来,因此此时是无法访问到外层的 var 变量的。
【正确写法】:def outside(): var = 5 def inside(): nonlocal var print(var) var = 3 inside() outside()
4. 请问如何访问 funIn() 呢?
def funOut(): def funIn(): print('宾果!你成功访问到我啦!') return funIn() #返回的是函数finIn的返回值 这样会执行一次funIn
【访问方式】只需要直接调用 funOut() 即可
funOut() 宾果!你成功访问到我啦!
5. 请问如何访问 funIn() 呢?
def funOut(): def funIn(): print('宾果!你成功访问到我啦!') return funIn #少了括号 相当于返回内嵌函数的引用 和第4不同它不会执行funIn
【访问方式】区别于上一题,这里返回的是一个函数对象,那么你就需要用 funOut()() 访问啦:
funOut()() 宾果!你成功访问到我啦!
【#】当然你也可以“曲线救国”:
go = funOut() go() 宾果!你成功访问到我啦!
6. 以下是“闭包”的一个例子,请你目测下会打印什么内容?
def funX(): x = 5 def funY(): nonlocal x #把x声明为非局部变量 x += 1 return x return funY a = funX()#a是一个函数对象 print(a()) print(a()) print(a())
【答案】当a=funX()的时候,只要a变量没有被重新赋值,funX()就没有被释放,也就是说局部变量x就没有被重新初始化,所以当全局变量不适用的时候,可以考虑使用闭包更稳定和安全
打印出:6 7 8
动动手:
1. 请用已学过的知识编写程序,找出小甲鱼藏在下边这个长字符串中的密码,密码的埋藏点符合以下规律:
str1 = '''ASDASdFSDFdFDSFdGSDGVDd'''
【参考答案】
countFront = 0 # 统计前边的大写字母 countMid = 0 # 统计中间小写字母 countRear = 0 # 统计后边的大写字母 length = len(str1) target="" for i in range(length): if str1[i] == '\n': continue if str1[i].isupper(): if countMid: countRear+=1 if countRear==4: countRear=3 else: countFront+=1 if countFront==4: countFront=3 if str1[i].islower(): if countMid: countFront = 0 countMid = 0 countRear = 0 else: countMid=1 target=str1[i] if countFront==3 and countRear==3: print(target) countFront = 3 countMid = 0 countRear = 0
021 函数:lambda表达式
1.请将下边的匿名函数转变为普通的屌丝函数。
lambda x : x if x % 2 else None #三目操作符
【参考答案】
def is_odd(x): if x % 2: return x else: return None
【笔记】
对于【 if x % 2:】,如果【 if x % 2 == 0】,则不符合规定,执行【return None】。依此原理可以找出奇数。3. 你可以利用filter()和lambda表达式快速求出100以内所有3的倍数吗?
【参考答案】
lambda x : x if x % 3 else None
【屌丝函数形式】
def fun(x): if x%3==0: return 1 else: return 0 list(filter(fun,list(range(0,100))))
4. 还记得列表推导式吗?完全可以使用列表推导式代替filter()和lambda组合,你可以做到吗?
[i for i in range(0,100) if i%3==0] #注意和正常的for循环不同 这里不用冒号了
5. 还记得zip吗?使用zip会将两数以元祖的形式绑定在一块,例如:
>>> list(zip([1, 3, 5, 7, 9], [2, 4, 6, 8, 10])) [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]
但如果我希望打包的形式是灵活多变的列表而不是元祖,(希望是[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]这种形式),你能做到吗?(采用map和lambda表达式)
【参考答案】
>>> list(map(lambda x, y : [x, y], [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])) [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
6. 请目测以下表达式会打印什么?
def make_repeat(n): return lambda s : s * n double = make_repeat(2) print(double(8)) print(double('FishC'))
【自己做的!】
double = make_repeat(2) #返回一个s=2*s的函数 print(double(8)) #返回16 print(double('FishC')) #返回FishCFishC
022 函数:递归是神马
递归,函数调用本身这么一个行为
1. 递归必须满足哪两个基本条件?
【答案】
(1)函数调用自身
(2)设置了正确的返回条件2. 思考一下,按照递归的特性,在编程中有没有不得不使用递归的情况?
【答案】
例如汉诺塔,目录索引(因为你永远不知道这个目录里边是否还有目录),快速排序(二十世纪十大算法之一),树结构的定义等如果使用递归,会事半功倍,否则会导致程序无法实现或相当难以理解。3. 用递归去计算阶乘问题或斐波那契数列是很糟糕的算法,你知道为什么吗?
“普通程序员用迭代,天才程序员用递归”这句话是不无道理的。
但是你不要理解错了,不是说会使用递归,把所有能迭代的东西用递归来代替就是“天才程序员”了,恰好相反,如果你真的这么做的话,那你就是“乌龟程序员”啦。
为什么这么说呢?不要忘了,递归的实现可以是函数自个儿调用自个儿,每次函数的调用都需要进行压栈、弹栈、保存和恢复寄存器的栈操作,所以在这上边是非常消耗时间和空间的。
另外,如果递归一旦忘记了返回,或者错误的设置了返回条件,那么执行这样的递归代码就会变成一个无底洞:只进不出!所以在写递归代码的时候,千万要记住口诀:递归递归,归去来兮!出来混,总有一天是要还的4. 请聊一聊递归的优缺点(无需官方陈词,想到什么写什么就可以)
优点:
(1)递归的基本思想是把规模大的问题转变成规模小的问题组合,从而简化问题的解决难度(例如汉诺塔游戏)。
(2)有些问题使用递归使得代码简洁易懂(例如你可以很容易的写出前中后序的二叉树遍历的递归算法,但如果要写出相应的非递归算法就不是初学者可以做到的了。)缺点:
(1)由于递归的原理是函数调用自个儿,所以一旦大量的调用函数本身空间和时间消耗是“奢侈的”(当然法拉利也奢侈,但还是很多人趋之若鹜)。
(2)初学者很容易错误的设置了返回条件,导致递归代码无休止调用,最终栈溢出,程序崩溃。动动手 :
0.递归编写一个 power() 函数模拟内建函数 pow(),即 power(x, y) 为计算并返回 x 的 y 次幂的值。
def pow(x,y): if y==1: return x else: return x*pow(x,y-1)
1. 使用递归编写一个函数,利用欧几里得算法求最大公约数,例如 gcd(x, y) 返回值为参数 x 和参数 y 的最大公约数
def gcd(x,y): if x%y==0: return y else: return gcd(y,x%y)