Python自动化运维 - day4 - 函数Part2
1、函数对象
函数是第一类对象,可以当作数据进行传递
特性:
1)可以被引用
1 def func(): 2 print('from func') 3 4 f = func #把函数地址当作变量一样进行传递 5 f()
2)可以当作参数传递
1 def func(): 2 print('from func') 3 4 def wapper(func): 5 print('from wapper',func) 6 7 wapper(func) #把函数的地址作为变量传递给另一个函数
3)返回值可以是函数
1 def func(): 2 print('from func') 3 4 def wapper(func): 5 return func 6 7 f = wapper(func) #把函数的内存地址传递,并返回,用f接收那么f就等于func的内存地址,那么加上括号就可以运行 8 9 f()
4)可以当作容器类型的元素
1 def func(): 2 print('from func') 3 4 func_dict = {'func':func} #直接作为字典的值存储,那么调用函数就可以用 func_dict['func']() 直接进行调用了
例子:
1 def select(sql): 2 print('==========>select') 3 4 def update(sql): 5 print('==========>update') 6 7 def insert(sql): 8 print('==========>insert') 9 10 def delete(sql): 11 print('==========>delete') 12 13 func_dict = {'select':select,'update':update,'insert':insert,'delete':delete} 14 15 def main(): 16 while True: 17 sql = input('>>:').strip() 18 if not sql:continue 19 cmd = sql.split()[0] 20 if cmd in func_dict: 21 func_dict[cmd]() 22 else: 23 print("retry") 24 ]main()
2、函数嵌套
1)函数的嵌套调用
1 def max2(a,b): #判断两个变量的最大值 2 return a if a > b else b 3 4 def max4(a,b,c,d): #判断四个变量的最大值 5 res1 = max2(a,b) #函数的嵌套调用 6 res2 = max2(res1,c) 7 res3 = max(res2,d) 8 print(res3) 9 10 max4(10,100,21,99)
2)函数的嵌套定义
1 def func1(): 2 print('from func1') 3 def func2(): 4 print('from func2') 5 def func3(): 6 print('from func3') 7 func3() 8 func2() 9 10 func1()
注意:在函数的内部定义函数,所以内部函数的作用于就在外部函数内,在其他地方就无法进行调用
3、名称空间与作用域
名称空间:用来存放名称与值之间的绑定关系,Python中分为三种名称空间。
1)内置名称空间(built-in):存放内置到Python解释器中的名称
sum,max,min,print,range 等等
随着Python解释器的启动而产生,不用定义,直接调用
查看内置方法:
1 import builtins 2 print(dir(builtins))
2)全局名称空间:文件的执行会产生全局名称空间(指的是文件级别定义的名字) # 类似与全局变量
3)局部名称空间:调用函数时会产生局部名称空间
只在函数调用时会产生,调用结束既解绑
注意:三种空间互相进行隔离
名称查找顺序:局部名称空间 -- 全局名称空间 -- 内置名称空间
作用域:表示名称空间的作用范围。
1)全局作用域:全局有效,在文件内任何位置都可以访问到,除非手动 del,否则会等文件执行完毕才会失效
包含内置名称空间、全局名称空间
2)局部作用域:局部有效,只能在局部范围调用,只在函数调用时才有效,函数调用完毕后既失效
包含局部名称空间
作用域查找顺序:局部作用域 -- 全局作用域
查看全局作用域内的名称:globls()
1)内置名称空间存放在 __builtins__ 中
2)在局部执行globls(),查找的也是全局名称空间,无法找到局部名称空间
查看局部作用域内的名称:locals()
1)如果在全局执行locals(),那么结果也是等于globls()
4、闭包
定义在内部函数包含对外部作用域而非全局作用域的引用,那么该函数就成为闭包函数
def fun1(): x = 1 def func2(): print(x) return func2 f = func1() x = 100 f() #这里依旧打印1。
解读:
1)在func1内定义了func2,并引用了外层变量x,既形成闭包函数
2)在调用func1的时候,函数把func2进行了返回,那么它返回的不仅仅是func2本身,同时还包含了闭包函数内部所需的外层变量
3)所以就算在全局定义了变量x,并且在全局调用f,在调用闭包函数时,还是会优先使用闭包函数内封装的外层变量。
应用:惰性计算
from urllib.request import urlopen #老的爬虫包 # res = urlopen('http://www.baidu.com').read() # print(res.decode('utf-8')) def index(url): def get(): return urlopen(url).read() #把url的内容爬下来,通过read方法读取(bytes格式) return get oldboy = index('http://crm.oldboyedu.com') #这里只是定义并接受了一个变量,并没有执行 --> 惰性计算 oldboy() #在后面任何位置,都可以执行,并且自带闭包时外层的url地址。
更简单的例子:
def func1(): x = 1 def func2(): print(x) return func2 f = func1() print(f.__closure__[0].cell_contents)
1、如果是闭包函数那么__closure__的值就不为None
2、__closure__中存放的是闭包函数引用外层环境变量的内存地址,并且以元组形式保存(只引用1个变量,也是用元祖来存储)
3、如果闭包函数同时引用了全局变量,那么__closure__只会保存引用的外层环境变量的内存地址。
4、如果想要读取内容,就需要使用cell_contents方法。
总结:
1、内部函数包含对外部函数局部变量的引用
2、在外部函数内对闭包函数进行返回
3、打破了内部函数只能在外部函数中进行调用的方式
4、不管在文件中任何地方运行,闭包函数都会引用定义时的外层作用域中的变量
5、装饰器
修饰(装饰)别人的工具,修饰-->添加功能,工具-->函数,既:通过函数给目标函数添加功能
装饰器本身可以是任何可调用对象,被装饰的对象也可以是任何可调用对象。(名字加括号就可以调用的对象,称之为可调用对象)
为什么要用装饰器?开放封闭原则,对修改是封闭的,对扩展是开放的。
小结:装饰器就是为了在不修改被装饰对象的源代码以及调用方式的前提下,为其添加功能
1、装饰的函数无参数
为index函数添加统计运行时间功能
import time def timer(func): #把index函数当作变量传入 def wapper(): start_time = time.time() func() #index函数在这里运行 stop_time = time.time() print('run time is %s' % (stop_time - start_time)) return wapper #return wapper 闭包函数哦 @timer #功能等同于 index = timer(index) 既 index = wapper() def index(): time.sleep(2) print('Welcome') index()
小结:
1)@timer为Python的语法糖,它会把它下面的函数当作变量传递给timer,所以@timer必须要写在被装饰函数的上面。
2)没有改变index函数的调用方式,也没有改变index函数的源代码,利用装饰器,就偷偷的给index函数添加了统计时间功能
2、装饰的函数可能有参,也可能无参
import time def timer(func): def wapper(*args,**kwargs): #把传递的参数传给wapper start_time = time.time() func(*args,**kwargs) #wapper再把参数传递给index函数 stop_time = time.time() print('The func run time is %s' % (stop_time-start_time)) # return xx 这里如果index函数有返回值,那么可以在上面定义接受func的返回这,这里再次return return wapper @timer def index(name): print('hello %s' % name) time.sleep(2) print('Welcome') @timer def index2(): time.sleep(1) print('Welcome') index('daxin') index2() #由于参数是不固定的,所以有参没参都可以进行传递
小练习:给函数添加装饰器,功能为认证,认证通过显示内容
login_status = {'user':None,'status':False} #全局账户标识位 def login(func): def wapper(*args,**kwargs): username,password = 'daxin','123456' if login_status['user'] and login_status['status']: #如果用户名密码已认证,那么就无需再次认证 res = func(*args,**kwargs) return res else: #否则进行认证 user = input('username: ') passwd = input('password: ') if user == username and passwd == password: login_status['user'] = 'daxin' #记录标识位 login_status['status'] = True res = func(*args,**kwargs) return res else: print('Sorry') return wapper @login def index(): print('this is index') @login def homepage(name): print('hello %s Welcome to home page' % name) index() homepage('daxin')
3、有参装饰器
针对上面小练习的题目,扩展,添加认证方式,既传递不同的认证方式,进行不同的认证
login_status = {'user':None,'status':False} def auth(driver='file'): def login(func): def wapper(*args,**kwargs): if driver = 'file': #添加对装饰器参数的判断 username,password = 'daxin','123456' if login_status['user'] and login_status['status']: res == func(*args,**kwargs) return res else: user = input('username: ') passwd = input('password: ') if user == username and passwd == password: login_status['user'] = 'daxin' login_status['status'] = True res = func(*args,**kwargs) return res else: print('Sorry') elif driver == 'mysql': #添加对装饰器参数的判断 print('=====> from mysql ') elif driver == 'LDAP': #添加对装饰器参数的判断 print('=====> from LDAP') return wapper return login @auth('file') #装饰器传递参数 def index(): print('this is index') @auth('mysql') #装饰器传递参数 def homepage(name): print('hello %s Welcome to home page' % name) index() homepage('daxin')
4、附加多个装饰器
def timer(func): def wapper(*args,**kwargs): start_time = time.time() func(*args,**kwargs) stop_time = time.time() print('The func run time is %s' % (stop_time-start_time)) return wapper login_status = {'user':None,'status':False} def auth(driver='file'): def login(func): def wapper(*args,**kwargs): if driver = 'file': username,password = 'daxin','123456' if login_status['user'] and login_status['status']: res == func(*args,**kwargs) return res else: #否则进行认证 user = input('username: ') passwd = input('password: ') if user == username and passwd == password: login_status['user'] = 'daxin' login_status['status'] = True res = func(*args,**kwargs) return res else: print('Sorry') elif driver == 'mysql': print('=====> from mysql ') elif driver == 'LDAP': print('=====> from LDAP') return wapper return login @timer # index = timer(auth.wapper) ==> index = timer.wapper @auth('file') # ==> @login ==> index = auth.wapper def index(): print('this is index') @timer @auth('mysql') def homepage(name): print('hello %s Welcome to home page' % name) index()
分析:首先会加载timer装饰器,而timer装饰的是@login装饰器的结果,所以又加载auth装饰器,auth为带参数的装饰器,其实又等于@login,那么继续拆,@login就等于 index = auth.wapper(为了方便,这里显著的表明来自哪个装饰器),@auth('file')的解析结果就是auth.wapper,然后把它交给@timer,index = timer(auth.wapper) 既:index = timer.wapper,在timer内部就执行了auth.wapper,所以用太关心多个wapper的执行顺序,只需要在需要添加功能的函数前加装饰器即可
PS:如果一个函数被装饰器装饰过,那么我们获取函数的__doc__,__name__时,会返回装饰器函数的相关参数,如果要复制被装饰的函数的__doc__,__name__ 可以在装饰器中的 wapper函数上添加 @functools.wraps(func),用于拷贝函数的属性至被装饰的函数(导入 functools模块)。
参数的提取
如果我们在装饰器中要对被装饰的函数的参数进行判断的话,其实是有难度的,因为我们无法预知函数的参数个数及类型所以wapper函数的参数为*args,**kwargs,当用户使用位置参数或者key=value的形式传递时,存放的位置是不同的,如何才能正确的获取函数的参数呢?
对于这种情况比较好的做法是,使用Python的标准库的inspect模块,inspect模块提供了许多有用的函数来获取活跃对象的信息,其中getcallargs函数用来获取函数参数的信息。
也就是说,getcallargs能够根据函数的定义和传递给和函数的参数,推测出哪一个值传递给函数的哪一个参数。推测完毕后,以一个字典的形式返回所有的参数和取值
import functools import inspect def check_is_admin(f): @functools.wraps(f) def wapper(*args,**kwargs): func_args = inspect.getcallargs(f,*args,**kwargs) if func_args.get('username') != 'admin': raise Exception("this ...") return f(*args,**kwargs) return wapper
6、迭代器
重复的过程为迭代,每次重复既一次迭代,并且每次迭代的结果是下一次迭代的初始值
为什么要用迭代器? 对于没有索引的数据类型,依然能够通过循环,迭代去获取的方式
可迭代的对象,具有 __iter__ 方法的对象,而执行 __iter__ 方法,那么对象就变成了迭代器。
迭代器没有索引,只能通过 __next__ 方法来获取元素,每执行一次,获取一个元素,当迭代器被获取完毕,__next__ 会抛出异常:StopIteration,表示没有值了
例子:
迭代一个字典,对于字典进行迭代,那么默认只能获取字典的key,但是可以通过其他方法来获取字典的value
dic = {'a':1,'b':2,'c':3} d = dic.__iter__() while True: try: key = d.__next__() #获取字典的key print(dic[key]) #通过key获取value except StopIteration: break
扩展:
1)既然一个对象的长度可以用len,或者用对象的 __len__()方法,其实执行len(对象),其实就是在执行对象的 __len__ 方法
2)那么把一个对象转换为迭代器,不仅仅可以用对象的 __iter__(),也可以使用iter(对象),来进行转换
3)获取迭代器的元素,不仅仅可以用 __next__(),也可以用next(对象)
1、如何判断对象是可迭代对象,还是迭代器对象
from collections import Iterable,iterator print(isinstance('abc',Iterable))
2、可迭代的数据类型
只要对象含有__iter__方法,执行__iter__方法,可以得到迭代器对象
列表,元祖,字典,字符串,集合,文件描述符 (可以对可迭代对象执行__iter__方法来变成迭代器)
3、迭代器数据类型
文件描述符
4、迭代器协议
1)对象有__next__,__iter__方法称为迭代器
2)对于迭代器对象来说,执行__iter__得到的结果,仍然是它本身。(for循环有关)
5、for循环原理
1)遵循迭代器协议,执行对象的__iter__方法,然后利用__next__(next)去获取元素
2)并且会帮我们捕捉异常,并且进行break
3)所以for循环的对象必须具有__iter__方法(可迭代对象变成了迭代器,而迭代器执行__iter__还是迭代器)
4)for循环又称为迭代循环
6、迭代器的优点和缺点
优点:
1)提供了一种不依赖下标的迭代方式(非序列对象可以被迭代)
2)就迭代器本身来说,更节省内存(只有对迭代器进行next方法,才会读取下一个值)
缺点:
1)迭代器只能被迭代一次,既一次性的,不如序列类型灵活
2)只能往后取值,不能往前倒退
3)无法获取迭代器对象的长度