Python 基础4 闭包与装饰器

==闭包 closure:

  定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).

  闭包是指调用了此函数外部嵌套函数作用域变量的函数;

  闭包必须满足三个条件:(注意闭包不释放变量,有内存消耗)
    1.必须有内嵌函数;
    2.内嵌函数必须引用外部函数中的变量;
    3. 外部函数返回值必须是内嵌函数

示例closure.py
#问题:
#如何写一个函数,让此函数能通过调用参数y就能生成一个x的y次方的函数?
#如下:
def make_power(y):
    def fn(x):    #注意此处为内嵌函数
        return x ** y    #内嵌函数引用外部嵌套函数的变量y
    return fn    #外部函数返回值 为内嵌函数

pow2 = make_power(2)  #此处返回的是fn函数对象
print('5的平方是:', pow2(5))    #25  
pow3 = make_power(3)
print('6的3次方是:', pow3(6))   #216
#一元二次方程式,已知x,求y值?
#a * x ** 2 + b * x + c = y

def get_fx(a, b, c):
    def fx(x):
        return a * x ** 2 + b * x ** 1 + c
    return fx

f123 = get_fx(1, 2, 3)
print('x=20, y=', f123(20))
print('x=50, y=',f123(50))

f654 = get_fx(6, 5, 4)
print('x=20, y=', f654(20))

==装饰器decorators (专业提高篇)
  问题:
    函数名是变量,它绑定一个函数;
    函数名 与 函数() 区别: 不加括号表示函数名变量绑定的函数;
    加括号表达调用函数做事;
  什么是装饰器:
    装饰器是一个函数,主要作用是用来包装另一个函数或类(后面讲)

    装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等

    应用场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能

  装饰器作用:
    是在不改变原函数名(或类名)的情况下改变被包装对象的行为;
    在不改变原函数和调用者行为的情况下,来改变原有函数功能;

 函数装饰器:
    函数装饰器是指装饰器是一个函数,传入的是一个函数,返回的也是一个函数;

  语法:
    def 装饰器函数名(参数):
      语句块
      return 函数对象

    @装饰器函数名<换行>
    def 函数名(形参列表):
      语句块

import time

#遵守开放封闭原则
#需求计算功能函数foo、bar运行时间
def show_time(f):  #装饰器函数
    def inner():
        start = time.time()
        f()
        end = time.time()
        print('spend %s' % (end - start))
    return inner    #返回inner函数的内存地址

@show_time  #等价于 foo = show_time(foo)
def foo():  #功能函数
    print('foo...')
    time.sleep(2)

@show_time  #等价于 bar = show_time(bar)
def bar():
    print('bar....')
    time.sleep(3)


foo()
bar()

 

# 示例见: mydeco1.py
# ==================================
def mydeco(fn):
    def fx():
        print('fx函数被调用')
    return fx

def myfunc():
    print('函数myfunc被调用')

#这样的写法可以用装饰器来代替
#等同于
#    @mydeco
#    def myfunc()......
myfunc = mydeco(myfunc)  
myfunc()
# ==================================
# 修改后如下
# ==================================
def mydeco(fn):
    def fx():
        print('fx函数被调用')
    return fx

#myfunc加了mydeco装饰器,等同于在myfunc创建之后调用赋值myfunc = mydeco(myfunc)语句 
@mydeco  #等价于 myfunc = mydeco(myfunc)
def myfunc():
    print('函数myfunc被调用')

myfunc()
# =======================================

#装饰器应用示例
def mydeco(fn):
    def fx():
        print('++++++++++++')
        #要想在此处调用被装饰的函数myfunc怎么办?
        fn()    #调用被装饰函数
        print('------------------------')
    return fx

##添加装饰器,myfunc函数被装饰(@mydeco 等介于 myfunc = mydeco(myfunc) 代码)
@mydeco  #等价于 myfunc = mydeco(myfunc)
def myfunc():    
    print('函数myfunc被调用')

myfunc()

#看懂下面代码的调用关系及打印结果:
def mydeco5(fn):
    print('装饰器函数被调用了....')    #在第一次赋值时myfunction = mydeco5(myfunction)调用
    def fx():
        print('fx函数被调用')
    return fx

@ mydeco5  #这里加装饰器和不加装饰器结果一样吗?为什么?
def myfunction():
    print('函数myfunction被调用')

myfunction()
myfunction()    #调用第二次
myfunction()    #调用第三次
========================
#mydeco5.py   示例
#此示例示意装饰器在不改变原函数和调用者行为的情况下,来改变原有函数功能;
#再加一个装饰器用来添加余额变动提醒功能
#写一个装饰器函数用来发送短信
def send_message(fn):
    def fy(name, x):
        fn(name, x)    #先办业务
        print('发短信给', name, '办理了', x, '')
    return fy

#写一个装饰器函数
def privillage_check(fn):
    def fx(name, x):
        print('正在检查权限...')
        fn(name, x)    #如果权限通过调用相应函数
    return fx

#写一个操作数据的函数(此函数用来示意存钱操作)
#
#注意此位置两层装饰器,层极关系是上到下层,层层嵌套调用
#(关系为:send_message在外层->privillage_check->savemoney在里层)
@send_message
@privillage_check
def savemoney(name, x):
    print(name, '存钱', x, '')
#
@privillage_check
def withdraw(name, x):
    print(name, '取钱', x, '')
#
savemoney('小张', 200)
savemoney('小赵', 200)

withdraw('小李', 500)
=========================

带参数的装饰器

    装饰器还有更大的灵活性,例如带参数的装饰器:在上面的装饰器调用中,比如@show_time,该装饰器唯一的参数就是执行业务的函数。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。

 1 import time
 2  
 3 def time_logger(flag=0):
 4  
 5     def show_time(func):
 6  
 7             def wrapper(*args,**kwargs):
 8                 start_time=time.time()
 9                 func(*args,**kwargs)
10                 end_time=time.time()
11                 print('spend %s'%(end_time-start_time))
12  
13                 if flag:
14                     print('将这个操作的时间记录到日志中')
15  
16             return wrapper
17  
18     return show_time
19  
20  
21 @time_logger(3)
22 def add(*args,**kwargs):
23     time.sleep(1)
24     sum=0
25     for i in args:
26         sum+=i
27     print(sum)
28  
29 add(2,7,5)

  @time_logger(3) 做了两件事:

      (1)time_logger(3):得到闭包函数show_time,里面保存环境变量flag

      (2)@show_time   :add=show_time(add)

  上面的time_logger是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器(一个含有参数的闭包函数)。当我 们使用@time_logger(3)调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。

 1 实例:
 2 import os
 3 import sys
 4 
 5 # 京东网站有以下home 、finance、book几个版块
 6 # home()
 7 # finance()
 8 # book()
 9 # 打开会选展示home首页
10 # 当在选择finance页面时,需要检测有没有登录,没有登录就调用登录验证接口,要求用weixin方式验证
11 # 当选择book页面时,需要检测有没有登录,没有登录就调用登录验证接口,要求用jidong方式验证
12 
13 #文件读取用户名与密码函数
14 def read_info(filename):
15     local_path = sys.path[0]
16     # 拼接localpath与filename
17     localxmlpath = os.path.join(local_path, filename)
18     # open文件,按行读取,返回content1列表
19     with open(localxmlpath, 'r') as f1:
20         content1 = f1.readlines()
21     return content1
22 
23 user_status = False #定义变量记录用户登录状态,用户登录后把这个修改为True
24 
25 def login(auth_type):   #把要选择的认证方式通过auth_type传入
26     def auth(func):
27         def inner(*args):   #再定义一层函数
28             print('+++++ %s +++++++' % (func))
29             pass_list = read_info(auth_type+'.txt') #根据auth_type拼接需要读取的文件名
30             _username = pass_list[0].rstrip('
')   #读取的用户名,去除字符串最后的'
'字符
31             _password = pass_list[1]                #读取密码
32 
33             global user_status      #声明记录用户状态的变量为全局变量
34 
35             if not user_status:
36                 username = input('username:')
37                 password = input('password:')
38 
39                 if username == _username and password == _password:
40                     print('成功登录,welcome!!!')
41                     user_status = True  #登录后改变登录状态标识为true
42                     func(*args)         #验证通过后,执行功能函数
43                 else:
44                     print('woring username or passwd!')     #验证未通过提示错误信息
45             else:
46                 print('用户已经登录,无需验证')
47                 func(*args)
48         return inner    #返回内部函数的内存引用id
49     return auth
50 
51 
52 def home():
53     print('welcom to home page')
54 
55 @login('weixin') #1、带入weixin参数给auth_type变量,2、finance = auth(finance)
56 def finance():
57     print('welcom to finance page')
58 
59 @login('jidong')    #1、带入jidong参数给auth_type变量,2、book = auth(book)
60 def book(style):
61     print('welcom to book page')
62 
63 
64 
65 home()
66 
67 
68 book('3x')
69 
70 finance()

多层装饰器:

#定义函数:完成包裹数据
def makeBold(fn):
    def wrapped():
        print('----1----')
        return '<b>' + fn() + '</b>'
    return wrapped

#定义函数:完成包裹数据
def makeItalic(fn):
    def wrapped():
        print('----2----')
        return '<i>' + fn() + '</i>'
    return wrapped

@makeBold
@makeItalic
def test3():
    print('------3-----')
    return 'hello world.3'

ret = test3()
print(ret)

#执行结果如下:
----1----
----2----
------3-----
<b><i>hello world.3</i></b>

==函数的文档字符串:
  函数内第一次末赋值给任何变量的字符串是此函数的文档字符串
    语法:
    def 函数名(形参列表):
    '''函数的文档字符串'''
      函数语句块

  示例:
    def hello():
      '''此函数来打招呼
      这是函数的文档字符串
      '''
      pass
  说明:
    1. 文档字符串通常用来说明本函数的功能和使用方法;
    2. 在交互模式下,输入help(函数名),可以查看函数的文档字符串;
    3. 文档字符串需要被解析执行器解析并绑定变量的,注释是被解析执行器忽略的。

  函数的__doc__属性:
    __doc__属性用于记录文档字符串;
  函数的__name__属性:
    __name__属性用于记录函数的名称;

==函数的定义语法:
    @装饰器1
    @装饰器2
    ......
    def 函数名([[[[位置形参],*元组形参(或*)], 命名关键字形参], **字符形参])
      '''文档字符串'''
      语句块

# 面试题,思考?
L = [1, 2, 3]
def f(n=0, lst=[]):    #注意此处不是每次函数调用赋值,而lst列表是存在于函数内部,生命周期与函数一致
    lst.append(n)
    print(lst)

f(4, L)        #打印结果是什么?[1, 2, 3, 4]
f(5, L)        #打印结果是什么?[1, 2, 3, 4, 5]
f(100)        #[100]
f(200)        #打印结果是什么?为什么?[100, 200]

#如下代码的打印结果是什么?
L = [1, 2, 3]
def f(n = 0, lst = None):
    if lst is None:
        lst = []    #此处是调用时赋值
    lst.append(n)
    print(lst)

f(4, L)        #打印结果是什么?[1, 2, 3, 4]
f(5, L)        #打印结果是什么?[1, 2, 3, 4,5]
f(100)         #[100]
f(200)         #打印结果是什么?为什么?[200];因为调用函数时,未带入列表;函数lst默认参数为None,if判断会对lst赋值为[];
原文地址:https://www.cnblogs.com/pineliao/p/12097175.html