第五章 Python之装饰器

函数对象

  函数是第一类对象:即函数可以当作数据传递

#可以被引用,可以被当作参数传递,返回值可以是函数,可以当作容器类型的元素
#引用
def func(x,y):
    print(x,y)
f=func()
print(f)
#参数传递
def bar(func):
    func()
bar(foo)
#返回值
def foo():
    print('from foo')
def bar():
    return foo
# 容器类型的元素(利用该特性,可以利用函数取代分支的if)
def fun1():
    print('fun1')
 def fun2():
     print('fun2')
di={
    'func':fun1,
    'fun2':fun2,
}
while True:
    choice=input(': '.strip())
    if choice in di:
        dic[choice]()

函数的嵌套

  函数可以嵌套调用,也可以嵌套定义

#函数的嵌套调用
def max(x,y):
    return x if x > y else y
def max4(a,b,c,d):
    res1=max(a,b)
    res2=max(c,res1)
    res3=max(d,res2)
    return res3
print(max4(2,3,4,8))
#函数的嵌套定义
def f1():
    def f2():
        def f3():
            return 1
        f3()
    f2()

名称空间与作用域

  名称空间是存放名字与值绑定关系的地方

  名称空间的加载顺序为:python解释器启动加载内置名称空间->执行.py文件,加载全局名称空间->调用函数加载局部名称空间

  名字查找顺序:局部名称空间->全局名称空间->内置名称空间

  作用域:全局范围(全局名称空间和内置名称空间),局部范围(局部名称空间)

  作用域关系在函数定义阶段就已经固定,与函数的调用位置无关

x=1
def f1():
    def f2():
        print(x)
    return f2
x=100
def f3(func):
    x=2
    func()
x=1000
f3(f1())
#查看作用域:globals(),locals()
def func():
    xxxxxx=111
    print(globals())
    print(locals())
func()
# LEGB代表名字查找顺序:locals-enclosing function-globals-__builtins__
# locals:函数内的名字空间,包括局部变量和形参
# enclosing:外部嵌套函数的名字空间
# globals:全局变量,函数定义所在模块的名字空间
# builtins:内置模块的名字空间
global 与 nonlocal 关键字
x=100
def func():
    global x #修改全局变量
    x=1
func()
print(x)
x='global'
def f1():
    x=1
    def f2():
        nonlocal x  #修改本函数上一层函数的变量值(只在函数内找,不在外面找)
        x=0
    f2()
    print('f1...x',x)
f1()
print(x)
View Code

闭包函数

  定义在函数内部的函数,内部函数包含对外部作用域而非全局作用域的引用,通常将闭包函数用return返回,然后可以任意调用

  闭包函数的意义:一种新的给函数传参的方式,返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,使得该函数无论在何处调用,优先使用自己外层包裹的作用域

def outer():
    x='hi'
    y='hey'
    def inner():
        print(x,y)
    return inner
f=outer()
print(f.__closure__[0].cell_contents) #f的外层作用域关系所引用的值
print(f.__closure__[1].cell_contents)
def counter():
    n=0
    def incr():
        nonlocal n
        x=n
        n+=1
        return x
    return incr
c=counter()
print(c())
print(c())
print(c())
print(c.__closure__[0].cell_contents)
View Code
from urllib.request import  urlopen
def index(url):
    def get():
        return urlopen(url).read()
    return get
baidu=index('http://www.baidu.com')
print(baidu().decode('utf-8'))
View Code

装饰器

  装饰器即闭包函数的一种应用

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

  装饰器的原则和目标是:在不修改被装饰对象的源代码和调用方式的前提下为被装饰对象添上新功能

# 无参装饰器
import time
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 #foo=timmer(foo)
def foo():
    time.sleep(3)
    print('from foo')
foo()
#有参装饰器
def auth(driver='file'):
    def auth2(func):
        def wrapper(*args,**kwargs):
            name=input('user: ')
            pwd=input('pwd: ')
            if driver == 'file':
                if name == 'egon' and pwd == '123':
                    print('login sucess')
                    res=func(*args,**kwargs)
                    return res
                elif driver == 'ldap':
                    print('ldap')
        return wrapper
    return auth2

@auth(driver='file')
def foo(name):
    print(name)

foo('egon')
#装饰器语法
@deco1
@deco2
@deco3
def foo():
    pass
foo=deco1(deco2(deco3(foo())))
#装饰器补充:wraps
from functools import wraps
def deco(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wraps

@deco
def index():
    '''哈哈哈'''
    print('from index')

print(index.__doc__)

练习

  (1)编写函数,函数执行的时间是随机的

import time,random
def func():
    time.sleep(random.randint(0,10))
    print('sucess')
func()
View Code

  (2)编写装饰器,为函数加上统计时间的功能

import time

def timmer(func):
    #func=index  #原始的index
    def inner(*args,**kwargs):  #接收任意长度和形式的参数传给func使用
        start=time.time()
        res=func(*args,**kwargs)  #调用原始的index()
        stop=time.time()
        print('run time is %s'%(stop-start))
        return res
    return inner

@timmer  #index=timmer(index)
def index(name):
    time.sleep(3)
    print('welcome %s to index'%name)
    return 111

res=index('luoli') #inner()
print(res)
View Code

  (3)编写装饰器,为函数加上认证的功能

import time

def auth2(engine='file'):
    #engine='file'
    def auth(func):
        #func=index()
        def inner(*args,**kwargs):
            if engine == 'file':
                name=input('name:').strip()
                pwd=input('pwd:').strip()
                if name == 'luoli' and pwd == '123456':
                    print('login sucess')
                    return func(*args,**kwargs)
                else:
                    print('login failed')
            elif engine == 'mysql':
                print('mysql')
            elif engine == 'ldap':
                print('ldap')
            else:
                print('engine not exists')
        return inner
    return auth

@auth2(engine='mysql') #@auth #index=auth(index) #index=inner
def index(name):
    time.sleep(1)
    print('welcome %s to index'%name)
    return 111

res=index('luoli')
print(res)
View Code

  (4)编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件),要求登录成功一次,后续的函数都无需再输入用户名和密码

t='user.txt'
login_status={"name":None,"pwd":None}
def auth(auth_type='file'):
    def auth2(func):
        def wrapper(*args,**kwargs):
            if login_status['name'] and login_status['pwd']:
                return func(*args,**kwargs)
            if auth_type=='file':
                with open(t,'r',encoding='utf-8') as f:
                    d=eval(f.read())
                name=input('Please enter your name:').strip()
                pwd=input('Please input your pwd:').strip()
                if name == d["name"] and pwd == d["pwd"]:
                    login_status["name"]=name
                    login_status["pwd"]=pwd
                    return func(*args,**kwargs)
                else:
                    print('username or pwd error')
            elif auth_file=='sql':
                pass
            else:
                pass
        return wrapper
    return auth2

@auth()
def index():
    print('sucess')
    return 123
@auth()
def home(name):
    print('welcome %s to my home'%name)

index()
home('hi')
View Code

  (5)编写装饰器,为多个函数加上认证功能,要求登录成功一次,在超时时间内无需重复登录,超过了超时时间,则必须重新登录

import time,random

user={'user':None,'login_time':None,'timeout':0.000003}

def auth(func):
    def wrapper(*args,**kwargs):
        if user["user"]:
            timeout = time.time() - user['login_time']
            if timeout < user["timeout"]:
                return func(*args, **kwargs)
        name=input('Please input your name:').strip()
        pwd=input('Please input your pwd:').strip()
        if name=='luoli' and pwd=='123456':
            user["user"]=name
            user["login_time"]=time.time()
            return func(*args,**kwargs)
    return wrapper

@auth
def index():
    time.sleep(random.randrange(3))
    print('welcome to index')

@auth
def home(name):
    time.sleep(random.randrange(3))
    print('welcome %s to home'%name)
index()
home('luoli')
View Code

  (6)编写下载网页内容的函数,要求功能是:用户传入一个url,函数返回下载页面的结果

from urllib.request import  urlopen
def index(url):
    def get():
        return urlopen(url).read()
    return get
baidu=index('http://www.baidu.com')
print(baidu().decode('utf-8'))
View Code

  (7)为题目六编写装饰器,实现缓存网页内容的功能,实现下载的页面存放于文件中,如果文件内有值(文件大小不为0),就优先从文件中读取网页内容,否则,就去下载,然后存到文件中

import requests
import os

cache_file='cache.txt'
def make_cache(func):
    def wrapper(*args,**kwargs):
        if not os.path.exists(cache_file):
            with open(cache_file,'w'):pass

        if os.path.getsize(cache_file):
            with open(cache_file,'r',encoding='utf-8') as f:
                res=f.read()

        else:
            res=func(*args,**kwargs)
            with open(cache_file,'w',encoding='utf-8') as f:
                f.write(res)
        return res
    return wrapper

@make_cache
def get(url):
    return requests.get(url).text

res=get('http://baidu.com')
print(res)
View Code
#扩展功能:用户可以选择缓存介质/缓存引擎,针对不同的url,缓存到不同的文件中
import requests,os,hashlib
engine_settings={
    'file':{'dirname':'./db'},
    'mysql':{
        'host':'127.0.0.1',
        'port':3306,
        'user':'root',
        'password':'123'},
    'redis':{
        'host':'127.0.0.1',
        'port':6379,
        'user':'root',
        'password':'123'},
}

def make_cache(engine='file'):
    if engine not in engine_settings:
        raise TypeError('egine not valid')
    def deco(func):
        def wrapper(url):
            if engine == 'file':
                m=hashlib.md5(url.encode('utf-8'))
                cache_filename=m.hexdigest()
                cache_filepath=r'%s/%s' %(engine_settings['file']['dirname'],cache_filename)

                if os.path.exists(cache_filepath) and os.path.getsize(cache_filepath):
                    return open(cache_filepath,encoding='utf-8').read()

                res=func(url)
                with open(cache_filepath,'w',encoding='utf-8') as f:
                    f.write(res)
                return res
            elif engine == 'mysql':
                pass
            elif engine == 'redis':
                pass
            else:
                pass

        return wrapper
    return deco

@make_cache(engine='file')
def get(url):
    return requests.get(url).text

# print(get('https://www.python.org'))
print(get('https://www.baidu.com'))
View Code

  (8)用函数对象的概念,制作一个函数字典,在文件开头声明一个空字典,然后在每个函数前加上装饰器,完成自动添加到字典的操作

route_dic={}

def make_rout(name):
    def deco(func):
        route_dic[name]=func
    return deco

@make_rout('select')
def func1():
    print('select')

@make_rout('insert')
def func2():
    print('insert')

@make_rout('update')
def func3():
    print('update')

@make_rout('delete')
def func4():
    print('delete')

print(route_dic)
View Code

  (9)编写日志装饰器,实现功能如:一旦函数f1执行,则将消息2017-07-21 11:12:11 f1 run写入到日志文件中,日志文件路径可以指定(注意:时间格式的获取time.strftime('%Y-%m-%d %X'))

import time
import os

def logger(logfile):
    def deco(func):
        if not os.path.exists(logfile):
            with open(logfile,'w'):pass

        def wrapper(*args,**kwargs):
            res=func(*args,**kwargs)
            with open(logfile,'a',encoding='utf-8') as f:
                f.write('%s %s run\n' %(time.strftime('%Y-%m-%d %X'),func.__name__))
            return res
        return wrapper
    return deco

@logger(logfile='a.txt')
def index():
    print('index')

index()
View Code

 

posted @ 2018-01-03 10:16  日新其德止于至善  阅读(295)  评论(0编辑  收藏  举报