Python(装饰器)
eval 内容从字符串中提取出来,用Python运行一遍
__name__得到函数名字
闭包函数 |
定义: |
闭包其实就是不管在哪里调用,都自带变量,不会有调用全局变量,而全局变量更改过了的错误产生 |
特点: |
f.__closure__[1].cell_contents |
|
另:通常需要把自己return出来,使自己能够在外部使用 |
开放、封闭原则: |
对扩展是开放的,对修改是封闭的 |
|||
装饰器 |
装饰器本质可以是任意可调用的对象,被装饰的对象也可以是任意可调用对象 |
功能 |
在不修改装饰器对象源代码,以及调用方式的前提下,为其添加新功能 |
1、不修改源代码 |
语法 |
在被装饰对象的正上方写上 @[装饰器名字], |
添加被装饰函数的原生备注(在装饰器上添加@wraps)如下:
import time import random from functools import wraps def timmer(func): @wraps(func) def wrapper(): # wrapper.__doc__=func.__doc__ start_time = time.time() func() stop_time=time.time() print('run time is %s' %(stop_time-start_time)) # wrapper.__doc__=func.__doc__ return wrapper @timmer def index(): 'index function' time.sleep(3) print('welecome to index page') print(index.__doc__) #调用被装饰函数的注释
#============================================================================================ # def zs(func): #这里出过错,没有定义形参,使得函数内部调用了yuan(),但是yuan()是后面定义的 # def zs1(): # import time # import random # s_time=time.time() # func() # time.sleep(random.randrange(0,10)) # tp_time=time.time() # print("bingo time:",tp_time-s_time) # return zs1 # # def yuan(): # print('hello!') # # print(zs) # yuan=zs(yuan) # print(yuan) # yuan() #先定义后执行,定义阶段就把后面的装饰器里的func指定到原来的yuan了,所以不会和后面的yuan冲突 #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#定义闭包函数的基本形式 # def 外部函数名(): # 内部函数需要的变量 # def 内部函数(): # 引用外部变量 # return 内部函数 # def deco(): # x=1 # def wrapper(): # print(x) # # return wrapper # # wrapper=deco() # # print(wrapper) #装饰器的原理
#装饰器修订 import time import random #装饰器 def timmer(func): def wrapper(*args,**kwargs): #能接收任意参数 start_time = time.time() res=func(*args,**kwargs) #返回值 stop_time=time.time() print('run time is %s' %(stop_time-start_time)) return res return wrapper #被装饰函数 @timmer def index(): time.sleep(random.randrange(1,5)) print('welecome to index page') @timmer def home(name): time.sleep(random.randrange(1,3)) print('welecome to %s HOME page' %name) return 123123123123123123123123123123123123123123 index()
dic={'x':1} #函数内改可变类型的全局变量能够直接改,而不可变类型不能直接改,需要global, l=[1,2,3] #换句话说,函数内可以随便定义不可变类型的变量,不用关心是否和全局冲突 salary=1000 def foo(): # print(dic) dic['x']=2 dic['y']=3 l.append(4) global salary salary=0 foo() print(dic) print(l) print(salary) #===运行结果==================== D:\Python36\python.exe D:/py/test/kong2.py {'x': 2, 'y': 3} [1, 2, 3, 4] 0
login_dic={ #全局的可变类型,可以在函数内直接改,不可变类型,需要申明global,因为不可变类型更改值需要重新开内存空间,本身就相当于一个新变量 'user':None, #如上一个例子 'status':False, } def auth(func): def wrapper(*args,**kwargs): if login_dic['user'] and login_dic['status']: res = func(*args, **kwargs) return res name=input('your name: ') password=input('your password: ') with open(db_path,'r',encoding='utf-8') as f: user_dic=eval(f.read()) if name in user_dic and password == user_dic[name]: print('login ok') login_dic['user']=name login_dic['status']=True res=func(*args,**kwargs) return res else: print('login err') return wrapper @auth #auth(index) def index(): print('welecome to index') @auth def home(name): print('welecome %s to home page' %name) index() home('egon')
#有参装饰器,在一般装饰器之外添加针对装饰函数的参数,增加了新参数,所以再加包一层 def deco(auth_type='file'): def auth(func): def wrapper(*args,**kwargs): if auth_type == 'file': print('文件的认证方式') elif auth_type == 'ldap': print('ldap认证方式') elif auth_type == 'mysql': print('mysql认证方式') else: print('不知到的认证方式') return wrapper return auth @deco(auth_type='abc') #@auth #index=auth(index) def index(): print('welecome to index') @deco(auth_type='ldap') def home(name): print('welecome %s to home page' %name) index() home('egon')
''' 编写下载网页内容的函数,要求功能是:用户传入一个url,函数返回下载页面的结果 ''' from urllib.request import urlopen import os cache_path=r'C:\Users\Administrator\PycharmProjects\python5期\day8\cache.txt' def make_cache(func): def wrapper(*args,**kwargs): if os.path.getsize(cache_path): #有缓存 print('\033[45m=========>有缓存\033[0m') with open(cache_path,'rb') as f: res=f.read() else: res=func(*args,**kwargs) #下载 with open(cache_path,'wb') as f: #制作缓存 f.write(res) return res return wrapper @make_cache def get(url): return urlopen(url).read()
func_dic={} ##直接使用地址调用函数能够跳过装饰器 def make_dic(key): def deco(func): func_dic[key]=func return deco @make_dic('index') ##可以用在隐藏函数 def f1(): print('from f1') #------------------------- print(func_dic) while True: cmd=input('>>>: ').strip() if cmd in func_dic : func_dic[cmd]() #======执行情况============== D:\Python36\python.exe D:/py/test/kong2.py {'index': <function f1 at 0x000002DE9900C9D8>} >>>: index from f1 >>>:
装饰器
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
>>> def now():
... print('2015-3-25')
...
>>> f = now
>>> f()
2015-3-25
函数对象有一个__name__
属性,可以拿到函数的名字:
>>> now.__name__
'now'
>>> f.__name__
'now'
现在,假设我们要增强now()
函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()
函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
观察上面的log
,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:
@log
def now():
print('2015-3-25')
调用now()
函数,不仅会运行now()
函数本身,还会在运行now()
函数前打印一行日志:
>>> now()
call now():
2015-3-25
把@log
放到now()
函数的定义处,相当于执行了语句:
now = log(now)
由于log()
是一个decorator,返回一个函数,所以,原来的now()
函数仍然存在,只是现在同名的now
变量指向了新的函数,于是调用now()
将执行新函数,即在log()
函数中返回的wrapper()
函数。
wrapper()
函数的参数定义是(*args, **kw)
,因此,wrapper()
函数可以接受任意参数的调用。在wrapper()
函数内,首先打印日志,再紧接着调用原始函数。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
这个3层嵌套的decorator用法如下:
@log('execute')
def now():
print('2015-3-25')
执行结果如下:
>>> now()
execute now():
2015-3-25
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>> now = log('execute')(now)
我们来剖析上面的语句,首先执行log('execute')
,返回的是decorator
函数,再调用返回的函数,参数是now
函数,返回值最终是wrapper
函数。
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__
等属性,但你去看经过decorator装饰之后的函数,它们的__name__
已经从原来的'now'
变成了'wrapper'
:
>>> now.__name__
'wrapper'
因为返回的那个wrapper()
函数名字就是'wrapper'
,所以,需要把原始函数的__name__
等属性复制到wrapper()
函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.__name__ = func.__name__
这样的代码,Python内置的functools.wraps
就是干这个事的,所以,一个完整的decorator的写法如下:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
或者针对带参数的decorator:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
import functools
是导入functools
模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()
的前面加上@functools.wraps(func)
即可。
小结
在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。
decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。
请编写一个decorator,能在函数调用的前后打印出'begin call'
和'end call'
的日志。
再思考一下能否写出一个@log
的decorator,使它既支持:
@log
def f():
pass
又支持:
@log('execute')
def f():
pass