三:函数+闭包+装饰器
一:函数
以功能为导向,一个函数就是一个功能,需要的时候拿来直接用,函数名指向一个函数对象。
增强了代码的可读性,方法名基本就可以猜出来是干什么的方法。
处理数据的逻辑封装,方便反复使用,数据和逻辑分离,数据由参数接收,函数内部封装了处理数据的逻辑,达到数据和逻辑分离的目的。
分为:函数定义,函数调用,函数嵌套
定义时:位置参数 默认参数 *args 命名关键字的默认参数 **kwargs,顺序不允许改变.最简单的就是*args **kwargs不变应万变。
调用时:参数不是必须按上面的顺序,但是关键字参数后面的必须使用关键字传参,但最好按定义时传参.
3.8版本多了一个/符号,表示左边参数都是位参,必须传递,就像*一样,规定右边必须是关键字参数。
返回值默认是元组,所以可以return多个,不显示return就是return None,python中没有Null,只有None类型是NoneType
再次强调函数的返回值很重要,line.raplace("sb","SB")这样确实改变了line中的字符,但是字符串替换的本质是开辟了新的内存创建了新的字符串对象,因此
必须line=line.replace("sb","SB")将生成新的str用line重新指向。否则line总是指向原来的str。
1.1函数的参数
函数里面是比较封闭的写好了之后几乎是不会再去改动,尤其是无参函数,当然优化除外
带参函数是为了盘活这个函数,对于外界动态传入的数据进行动态的执行。
位置参数:1.必须传递参数,2.顺序如果不是关键字传参不允许改动顺序,如果是关键字传参,顺序随意,但是位置参数必须放在关键字传参前面。
缺点:当参数过多的时候,顺序是难以记住的。
关键字参数:当顺序记不住的时候,按关键字传参,test(age=18,name="zhangsan")
位置参数和关键字参数混合使用调用函数时,位置参数必须放在关键字参数的前面。
默认参数:函数定义阶段,key=value的形式表示默认值参数,位于*前面只要调用阶段不动它,就一直沿用默认值,但是不能为可变数据类型
想要动默认参数就必须使用关键字传参,放在位置参数之后调用
万能参数:*args **kwargs
*args:形参角度,表示聚合所有的位置参数到一个元组中,用args变量名指向元组。
**kwargs:形参角度,表示将所有的关键字参数聚合到一个字典中,用kwargs变量名指向字典。
定义函数时test(*args,**kwargs)就是万能参数了。
形参角度的参数顺序:位置参数 默认参数 *args (默认参数) **kwargs
默认参数和*arg谁放在前面,多余的参数就给谁
*args放前面,所有位置参数都给*args,想改默认参数只能关键字参数的形式修改
默认参数放*args前面,多余出来的一个参数默认给了默认参数,即使没有用关键字传参的方式,剩下的都给*args
def test(a,b,c=2,*args): pass test(1,2,3,4)
默认参数有2种方式传递,假设没有*args,只传递了三个参数,test(1,2,3)3会分配给参数c
或者test(1,2,c=3)两种方式更改默认参数
因此*args要放在默认参数之后,否则把所有的位置参数都放入元组了,第二种默认参数的修改方法失效。
二:闭包
本质:函数嵌套定义.以一种特殊的方式将对象创建出来返回到全局并接收,为的就是保存状态反复使用。
闭包:内部函数引用外部函数非全部变量,并且把内部函数的引用进行了return,全局必须有接收return引用的变量,否则闭包没有任何意义。
函数传参两种形式,一是参数形式传递,二是闭包。
全局用一个变量接收了return的内部函数,这样随着函数的调用,并不会立马结束掉,因为全局有一个引用指向,GC无法回收可以在内存驻留,全局引用了内部,内部引用了外部的局部变量,内部+外部函数都不会被回收,使得这种状态得以在内存保留,想要再用的时候直接调用就可以了,即特殊的创建对象的方式。
闭包打破的传统的规则:通过把内部函数引用return到全局完成规则的打破,外部无法访问函数内部的变量,可以这样做正是因为return的神奇,他可以把一切返回到上一个临近的作用域。
相当于变向的创建了一个对象,这个对象中外部函数的局部变量就是属性,内部函数就是方法,可以这么理解,那么你调用函数时传进来的过程相当于init初始化了属性,那么这个状态就得以在内存中保留,
还想复用的时候就直接用全局的引用加()就可以随时调用,不用像传统方法那样频繁的开栈帧弹栈,以及里面的变量也要随着函数频繁调用而频繁开内存,大大提升了效率.
而闭包的进阶装饰器,只是将变量指向的数据换成了函数的代码片段而已,他们在编码的时候用的不多,更多的使用在代码的可维护性。
面向对象编程:
为什么创建对象?
因为对象里面封装了解决一类问题需要的方法和变量,使得你创建一次后面就可以反复的使用.
而闭包或装饰器就是另一种方式而已,
没有面向对象完全可以开发,只是有了面向对象之后,让开发更加容易理解,容易实现.
def fn(): a = 10 # 函数内部再定义一个函数 def inner(): nonlocal a a += 1 print('我是fn2' , a) return inner r = fn() r()
以前的函数定义和调用
import requests def get(url): response = requests.get(url) print(response) get("https://www.baidu.com") get("https://www.baidu.com") get("https://www.baidu.com") get("https://www.baidu.com") get("https://www.baidu.com")
每次请求百度,都要写一遍"https://www.baidu.com",虽然可以用一格全局变量a="https://www.baidu.com"来不用写那么多次的https://www.baidu.com,但是每次调用都要重新的开新内存
而
import requests a="https://www.baidu.com" def get(url): def inner(): response = requests.get(url) print(response) return inner f=get(a) f() f() f()
这个引用一直在,不会随着函数的调用结束而消失,一直在内存中,反复使用,大大提升效率.
python的GC类似java中的回收机制gcroot开始看引用的一条线,只要不在都会被回收,函数的是function对象,这个对象是python解释器帮我们起的名字,调用完就会被解释器回收掉,而闭包这里我们主动拉了一条线到函数对象上,GC从gcroot开始找垃圾的时候就不会把这个函数对象回收掉.
可以把闭包视为OOP的变形:
变形一:类的畸形定义,封装数据靠外部函数来接收,内部函数就是方法
变形二:只有方法的类,外部函数接收的方法是不同的,但是内部函数的逻辑是固定死的,用来对外部函数进行装饰,但是外部函数的内部无法改造,只能在它的外部进行修饰.
另一个扩展:只有属性的类:namedtuple
无论是变形一还是变形二,他们的目的都是为了重用,变形一使得一个数据传入闭包,对象在内存长期驻留,反复使用
变形二使得一个装饰的过程即内部函数不变,通过外部函数把要进行装饰的函数的引用接收进来,
三:再进阶
没有类的封装,完全可以靠函数来完成变成工作,只不过用了类之后区分更明显,类把数据和方法封装到了一起
而函数的数据和逻辑是分离的,除了默认值,其他的数据都要靠形参接收到函数内部
四:装饰器:闭包的升级
常用在有切片的场景,例如逻辑代码中插入日志和性能测试代码,事务处理,缓存,权限校验等。
不改变原有逻辑和调用的方式为前提来扩展功能
开放封闭原则:对修改关闭,对扩展开放.
装饰器完美的解决了无感知扩展调用方式也不会改变,别人不会因为你修改代码而去大篇幅修改别人的代码
外部函数就是负责把数据接收到实例对象中,最后把内部函数的引用返回到全局,内部方法负责对传进来的函数进行修饰,
全局引用的调用实际就是内部函数的调用,妙在外部接收的变量名和要装饰的函数名是一致的,这就无感知的骗过了装饰器,名字虽然一样,但是通过内部函数修饰了原函数.
相同的装饰逻辑对象长驻内存,方便随时反复使用.
def outer(fn): def inner(*args,**kwargs): print("头部装饰") fn() print("尾部装饰") return inner @outer def hexin(): print("main test")
一个坑就是,先把装饰器写出来,然后再加到要被装饰的函数头顶上
装饰的开关
如果开发时测试性能,加上装饰器,测试完了之后去掉装饰器,难道一个一个删?3000个函数,加3000遍,删3000遍?拒绝无用的重复劳作!
五:总结
编程里面非常重要的思想就是重复利用---------------->"偷懒".
falg = True def outer(flag): def timer(func): def inner(*args, **kwargs): if flag: print('''执行函数之前要做的''') re = func(*args, **kwargs) if flag: print('''执行函数之后要做的''') return re return inner return timer flag=True @outer(flag)# 先看outer(flag)拿到装饰器,然后@装饰器开始装饰,走了2步 def func(): print(111) func()
当关闭装饰器,把全局的flag改为false就可以了
六:高阶函数
将函数作为参数传递,或者作为返回值返回的函数都是高阶函数
可见python是很灵活的。