学习python日记(进阶篇)
终于学到函数了,决定重新开启一篇随笔,之前的随笔以介绍基本的变量为主,大家可以看看
函数
参数和返回值
先看一下如何定义函数
#简单的函数 def mylove(): for i in range(3): print("I love Gaoban") mylove() I love Gaoban I love Gaoban I love Gaoban #有形参的函数 def mylove(name,times): for i in range(times): print(f"I love {name}") mylove("Gaoban",4) I love Gaoban I love Gaoban I love Gaoban I love Gaoban
再看一下有返回值的函数
def div(x,y): if y == 0: return "除数不能为0" else: return x/y
但当函数执行return之后,就不再执行后面的语句,所以上面的代码可以进行优化
def div(x,y): if y == 0: return "除数不能为0" return x/y div(4,2) Out[10]: 2.0 div(4,0) Out[11]: '除数不能为0'
当函数没写返回值时,返回None
def hhhh(): pass print(hhhh()) None
接下来再来介绍一下形参
位置形参
def myfunc(a,b,c): return "".join([c,b,a]) myfunc("我","爱","Gaoban") Out[74]: 'Gaoban爱我'
我们需要记住形参的位置来使用函数,不免麻烦,python提供了一种关键字方法(如下),但要求位置形参在前,否则会报错
myfunc(c="Gaoban",a="我",b='爱') Out[81]: 'Gaoban爱我'
但是位置形参和关键字形参混合使用时,则较为容易犯错,最好还是能分清位置形参比较好
#这个是对b重复赋值 myfunc("我","Gaoban",b="爱") Traceback (most recent call last): File "D:\anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 3460, in run_code exec(code_obj, self.user_global_ns, self.user_ns) File "<ipython-input-75-c3e7667522ff>", line 1, in <module> myfunc("我","Gaoban",b="爱") TypeError: myfunc() got multiple values for argument 'b' #这个是关键字形参要在位置形参之后 myfunc(b="爱","我","Gaoban") Cell In[76], line 1 myfunc(b="爱","我","Gaoban") ^ SyntaxError: positional argument follows keyword argument
此外还有默认参数的函数
def myfunc(a,b,c="Gaoban"): return "".join([c,b,a]) myfunc("我","爱") Out[83]: 'Gaoban爱我'
此外小甲鱼还介绍了一个冷门的知识,简单来说就是,在定义函数时,当形参里有“/”或者“*”时,则会出现有点参数只能用关键字定义,有的只能用位置定义,由于这个知识点对于我而言暂时意义不大,就不花精力撰写了,读着可以靠下面的视频截图读懂
除了关键字参数、位置参数,还有收集参数,其实上文略写的形参为“*”时,后面的参数只能是关键字参数中,“*”就是匿名的收集形参
def myfunc(*arg): print(f"传入了{len(arg)}个参数") print(f"第2个参数是{arg[1]}") print(arg) myfunc(1,3,1,3,2) 传入了5个参数 第2个参数是3 (1, 3, 1, 3, 2)
可见,*将传入的形参打包为了元组,当传出的参数为多个参数时,同样也是打包为元组
def myfunc(): return 1,2,3 myfunc() Out[8]: (1, 2, 3)
而将元祖解包的方法也很简单
x,y,z = myfunc() x Out[10]: 1 y Out[11]: 2 z Out[12]: 3
而当参数为匿名形参时,后面的参数就只能关键字赋值,不然会报错
myfunc(1,2,3,b=2)
(1, 2, 3) 2
除了可以传入的元素变为元组(位置参数)外,还可以将传入的元素变为字典(关键字参数)
def myfunc(**kwargs): print(kwargs) myfunc(a=1,b=1,c=1) {'a': 1, 'b': 1, 'c': 1}
总结一下
def myfunc(x,*y,**z): print(x,y,z) myfunc(1,2,3,4,b=2,c=3) 1 (2, 3, 4) {'b': 2, 'c': 3}
除了可以传入的元素变为元组、字典外,还可以将元组和字典传入,将其中的值取出来
def myfunc(a,b,c,d): print(a,b,c,d) arg = (1,2,2,4) myfunc(*arg) 1 2 2 4 kwarg = dict(a=1,b=3,c=3,d=4) myfunc(**kwarg) 1 3 3 4
作用域
关于作用域,全局变量可以作用于全局,局部变量只能作用于局部,这都是比较常识的了
python的作用遵从LEGB原则,L是local,局部作用域,E是Enclosed,是外层函数的局部作用域,G是Global,全局作用域,B是Build-in,内置作用域
当局部作用域与全局作用域冲突时,局部作用域覆盖全局作用域,若要执意在局部修改全局,需要声明是全局变量global
#局部覆盖全局,但不修改 def myfunc(): x = 233 print(x) print(id(x)) x=520 id(x) Out[5]: 2196128001200 myfunc() 233 2196023942640 x Out[7]: 520 #局部修改全局 def myfunc(): global x x = 233 print(x) print(id(x)) myfunc Out[9]: <function __main__.myfunc()> myfunc() 233 2196023942640 x Out[11]: 233
当局部作用域与外层函数的局部作用域冲突时,局部作用域覆盖外层函数的局部作用域,若要执意在局部修改,需要进行声明nonlocal
注:列表的内容是存放在另外的位置,所以列表不需要使用 nonlocal 修饰,也可以直接在嵌套函数中修改它。
def funA(): x = 520 def funB(): x=888 print("在funB()中,x=",x) funB() print("在funA()中,x=",x) funA() 在funB()中,x= 888 在funA()中,x= 520 def funA(): x = 520 def funB(): nonlocal x x=888 print("在funB()中,x=",x) funB() print("在funA()中,x=",x) funA() 在funB()中,x= 888 在funA()中,x= 888
再来介绍地位最低的Build_in,他就是内置函数名,当有变量的名称和它一样时,他就被覆盖了,所以取变量名时要好好注意
x=[1,2,3] str(x) Out[4]: '[1, 2, 3]' str=123 str(x) Traceback (most recent call last): File "D:\anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 3460, in run_code exec(code_obj, self.user_global_ns, self.user_ns) File "<ipython-input-6-596612539da9>", line 1, in <module> str(x) TypeError: 'int' object is not callable
搬运一个有意思的题目
>>> x = [1, 2, 3] >>> def invert(x): ... x = x[::-1] ... >>> invert(x) >>> print(x) >>> # 请问这里会打印什么内容?【1,2,3】 >>> x = [1, 2, 3] >>> def invert(x): ... x[:] = x[::-1] ... >>> invert(x) >>> print(x) >>> # 请问这里会打印什么内容?【3,2,1】
闭包
我们都知道当函数执行完后,函数的作用域内的变量和函数都将失效,在函数作用域外无法调用
但是对于嵌套函数来说,就外层函数的局部作用域就不会消失,下面开始讲解这部分内容
我们都知道在函数外部无法直接调用函数内部的函数
#定义了一个嵌套函数 def funA(): x = 880 def funB(): print(x) funB() #无法直接调用函数B,会报错 funB() Traceback (most recent call last): File "D:\anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 3460, in run_code exec(code_obj, self.user_global_ns, self.user_ns) File "<ipython-input-4-b53278dec512>", line 1, in <module> funB() NameError: name 'funB' is not defined #通过调用函数A来调用函数B funA() 880
其实还有一种方法可以来调用函数B,就将函数B作为返回值返回,将函数作为返回值返回时,就不用加小括号,只有在调用和定义函数时需要加小括号
def funA(): x = 880 def funB(): print(x) funB() return funB funA() 880 Out[7]: <function __main__.funA.<locals>.funB()> funA()() 880 880
在funA()()中就可以窥见其定义的funB和x=880都被保留了下来,即函数的局部作用域内的变量仍存在
此时我们就可以利用这样的方式,制造一个函数制造工厂
#函数制造工厂 def power(exp): def exp_of(base): return base**exp return exp_of #定义了二次方和三次方函数 square = power(2) cube = power(3) #效果 square(5) Out[12]: 25 cube(5) Out[13]: 125
我还可以利用这一点,制造一个有记忆的函数,其核心就是nonlocal
def outer(): x=0 y=0 def innner(x1,y1): nonlocal x,y x += x1 y += y1 print(f"现在,x={x},y={y}") return innner move = outer() #记忆效果呈现 move(1,1) 现在,x=1,y=1 move(-2,3) 现在,x=-1,y=4
装饰器
这块内容较难,在讲之前需要一些铺垫
上面说了函数可以作为返回值,其实函数也可以作为参数
def guocheng(): print("loading") def shimo(fun): print("begin") fun() print("end") shimo(guocheng)
begin loading end
借此,就可以完成计时
def time_master(func): begin = time.time() func() end = time.time() print(f"用了{end-begin}的时间") def myfunc(): time.sleep(2) print("hhhh") time_master(myfunc)
hhhh 用了2.003040313720703的时间
那是否有更有效的方法呢,能否直接在调用myfunc()的时候就调用time_master,而要实现这一招,就需要“狸猫换太子”,就要将myfunc作为参数传给time_master,又要将time_master作为返回值返回给myfunc()
def time_master(func): def call_func(): begin = time.time() func() end = time.time() print(f"用了{begin-end}的时间") return call_func def myfunc(): time.sleep(2) print("hhhh") myfunc = time_master(myfunc) myfunc() hhhh 用了-2.006093740463257的时间
经过此番操作,myfunc也就有了time_master的功能,同时也具备自身的功能
python为此番操作提供了一个语法糖
def time_master(func): def call_func(): begin = time.time() func() end = time.time() print(f"用了{begin-end}的时间") return call_func @time_master #语法糖 def myfunc(): time.sleep(2) print("hhhh") #省去myfunc = time_master(myfunc) myfunc() hhhh 用了-2.0088632106781006的时间
当有多个修饰器时,python是从近到远依次装饰
def jiayi(func): def inner(): x = func() + 1 return x return inner def cube(func): def inner(): x = func() return x**3 return inner def square(func): def inner(): x = func() return x**2 return inner @jiayi @cube @square def myfunc(): return 2 myfunc() Out[31]: 65#(2*2)*4*4+1
此外,还可以给装饰器传递参数
import time def logger(msg): def time_master(func): def call_func(): start = time.time() func() stop = time.time() print(f"[{msg}]一共耗费了{(stop-start):.2f}") return call_func return time_master @logger(msg='A') def funA(): time.sleep(1) print("A....") @logger(msg='B') def funB(): time.sleep(2) print("B....") #省略了 funA = logger(msg="A")(funA) #省略了 funB = logger(msg="B")(funB) funA() A.... [A]一共耗费了1.00 funB() B.... [B]一共耗费了2.00
总之,装饰器本质上也是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能。
emmmm我个人的使用体验,将装饰器使用在没有参数和返回值的函数上较为便捷
装饰在有返回值的函数上,会导致不能正常的返回,需要修改装饰器(猜测)
装饰在有参数的函数上,会导致装饰器里没有形参,也要修改装饰器(猜测)
总之反而不便捷,这是我第一次使用装饰器的感受,以后也许会改变。
lambda表达式
即匿名函数,它是一个表达式,却有函数的功能
#基本形式 #arg是传入的参数 expression是返回的表达式 lambda arg1,arg2,arg3,...argN:expression #def <lambda>(arg1,arg2,arg3,...,argN): #return expression
我们来用它实现一些功能
#平方 def squareX(x): return x*x squareX(3) Out[9]: 9 squareY = lambda y:y*y squareY(3) Out[11]: 9 #squareX与squareY有本质区别 squareX Out[13]: <function __main__.squareX(x)> squareY Out[14]: <function __main__.<lambda>(y)>
lambda相较于函数的优势在于简洁,以及他作为表达式,能放在一些不方便放置“def”的地方
#lambda表达式放到列表中 y = [lambda x:x*x,2,3] y[0](y[1]) Out[16]: 4 #lambda表达式用于函数参数 list(map(ord,"FishC"))#复习map,以及体会后文用lambda的用意(参数) Out[17]: [70, 105, 115, 104, 67] list(map(lambda x :ord(x)+10,"FishC"))#若用函数分开写,就很无聊 Out[18]: [80, 115, 125, 114, 77]
lambda的局限在于只有一行代码,在编写复杂函数时,还是函数好用
再补充一个闭包转换为lambda表达式
def outer(arg1): def inner(arg2): x = arg1 * (arg2-1) return x return inner Lam = lambda x:lambda y:x*(y-1) Lam(3)(4) Out[18]: 9 outer(3)(4) Out[19]: 9
lambda和filter相结合,可以实现迭代
power = {"吕布":999, "关羽":888, "刘备":666, "张飞":900, "赵云":789, "不二如是":999} greater = list(filter((lambda x: x[1] > 800), power.items())) greater Out[48]: [('吕布', 999), ('关羽', 888), ('张飞', 900), ('不二如是', 999)]
lambda好难
(想干点别的了,暂时止步于此吧)