Python(装饰器)

 

eval 内容从字符串中提取出来,用Python运行一遍
__name__得到函数名字

 

闭包函数

定义:
函数内部定义的函数称为内部函数
该内部函数包含对外部作用域,而不是对全局作用域的名字的引用
那么该内部函数称为闭包函数

闭包其实就是不管在哪里调用,都自带变量,不会有调用全局变量,而全局变量更改过了的错误产生

 

特点:
1、闭包函数自带作用域
2、延迟计算/惰性计算,调用出来了函数地址,想用的时候加括号

f.__closure__[1].cell_contents
。__closure__能够查看闭包函数包含的变量,返回为none的时候表明其不是闭包函数

 

另:通常需要把自己return出来,使自己能够在外部使用

 

 

 

 

开放、封闭原则:

对扩展是开放的,对修改是封闭的

     

装饰器

装饰器本质可以是任意可调用的对象,被装饰的对象也可以是任意可调用对象

功能

在不修改装饰器对象源代码,以及调用方式的前提下,为其添加新功能

1、不修改源代码
2、不修改调用方法
3、添加新功能

   

语法

在被装饰对象的正上方写上 @[装饰器名字],

 

 

 

 

 

添加被装饰函数的原生备注(在装饰器上添加@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
posted @ 2017-06-15 14:24  Z贺  阅读(365)  评论(0编辑  收藏  举报