Python之闭包与装饰器

闭包

由于闭包这个概念比较难以理解,尤其是初学者来说,相对难以掌握,所以我们通过示例去理解学习闭包。

给大家提个需求,然后用函数去实现:完成一个计算不断增加的系列值的平均值的需求。

例如:整个历史中的某个商品的平均收盘价。什么叫平局收盘价呢?就是从这个商品一出现开始,每天记录当天价格,然后计算他的平均值:平均值要考虑直至目前为止所有的价格。

比如大众推出了一款新车:小白轿车。

第一天价格为:100000元,平均收盘价:100000元

第二天价格为:110000元,平均收盘价:(100000 + 110000)/2 元

第三天价格为:120000元,平均收盘价:(100000 + 110000 + 120000)/3 元

series = []
def make_averager(new_value):
    series.append(new_value)
    total = sum(series)
    return total / len(series)
    
print(make_averager(100000))
print(make_averager(110000))
print(make_averager(120000))

从上面的例子可以看出,基本上完成了我们的要求,但是这个代码相对来说是不安全的,因为你的这个series列表是一个全局变量,只要是全局作用域的任何地方,都可能对这个列表进行改变。

series = []
def make_averager(new_value):
    series.append(new_value)
    total = sum(series)
    return total / len(series)
    
print(make_averager(100000))
print(make_averager(110000))
series.append(666)  # 如果对数据进行相应改变,那么你的平均收盘价就会出现很大的问题。
print(make_averager(120000))

那么怎么办呢?有人说,你把他放在函数中不就行了,这样不就是局部变量了么?数据不就相对安全了么?

def make_averager(new_value):
    series = []
    series.append(new_value)
    total = sum(series)
    return total / len(series)


print(make_averager(100000))  # 100000.0
print(make_averager(110000))  # 110000.0
print(make_averager(120000))  # 120000.0

这样计算的结果是不正确的,那是因为执行函数,会开启一个临时的名称空间,随着函数的结束而消失,所以你每次执行函数的时候,都是重新创建这个列表,那么这怎么做呢?这种情况下,就需要用到我们讲的闭包了,我们用闭包的思想改一下这个代码。

def make_averager():
    
    series = []
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)

    return averager

avg = make_averager()
print(avg(100000))
print(avg(110000))
print(avg(120000))

大家仔细看一下这个代码,我是在函数中嵌套了一个函数。那么avg 这个变量接收的实际是averager函数名,也就是其对应的内存地址,我执行了三次avg 也就是执行了三次averager这个函数。那么此时你们有什么问题?

肯定有学生就会问,那么我的make_averager这个函数只是执行了一次,为什么series这个列表没有消失?反而还可以被调用三次呢?这个就是最关键的地方,也是闭包的精华所在。我给大家说一下这个原理,以图为证:

Python之闭包与装饰器

​ 上面被红色方框框起来的区域就是闭包,被蓝色圈起来的那个变量应该是make_averager()函数的局部变量,它应该是随着make_averager()函数的执行结束之后而消失。但是他没有,是因为此区域形成了闭包,series变量就变成了一个叫*变量的东西,averager函数的作用域会延伸到包含*变量series的绑定。也就是说,每次我调用avg对应的averager函数 时,都可以引用到这个自用变量series,这个就是闭包。

闭包的定义:

​ 1. 闭包是嵌套在函数中的函数。

​ 2. 闭包必须是内层函数对外层函数的变量(非全局变量)的引用。

如何判断判断闭包?举例让同学回答:

# 例一:
def wrapper():
    a = 1
    def inner():
        print(a)
    return inner
ret = wrapper()

# 例二:
a = 2
def wrapper():
    def inner():
        print(a)
    return inner
ret = wrapper()


# 例三:

def wrapper(a,b):
    def inner():
        print(a)
        print(b)
    return inner
a = 2
b = 3
ret = wrapper(a,b)

以上三个例子,最难判断的是第三个,其实第三个也是闭包,如果我们每次去研究代码判断其是不是闭包,有一些不科学,或者过于麻烦了,那么有一些函数的属性是可以获取到此函数是否拥有*变量的,如果此函数拥有*变量,那么就可以侧面证明其是否是闭包函数了(了解):

def make_averager():

    series = []
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)

    return averager
avg = make_averager()
# 函数名.__code__.co_freevars 查看函数的*变量
print(avg.__code__.co_freevars)  # ('series',)
当然还有一些参数,仅供了解:

# 函数名.__code__.co_varnames 查看函数的局部变量
print(avg.__code__.co_varnames)  # ('new_value', 'total')
# 函数名.__closure__ 获取具体的*变量对象,也就是cell对象。
# (<cell at 0x0000020070CB7618: int object at 0x000000005CA08090>,)
# cell_contents *变量具体的值
print(avg.__closure__[0].cell_contents)  # []

闭包的作用:保存局部信息不被销毁,保证数据的安全性。

闭包的应用

  1. 可以保存一些非全局变量但是不易被销毁、改变的数据。
  2. 装饰器。

闭包的解释:

闭包是存在嵌套函数当中的 内层对外层非全局变量的的引用 ,称之为*变量 它不会随着函数的结束而消失,一直保存在内存,最终的目的是保证数据的安全

装饰器

开放封闭原则

软件面世时,不可能吧所有的功能都设计好,当前的未来一两年功能给你上线,定期更新迭代.对于软件之前写的源代码一般都不会修改,对函数里面的代码以及函数的调用方式.

开放原则: 在源码不改变的情况下,增加一些额外的功能

封闭原则: 不要改变源代码

python中装饰器: 完美诠释的开放封闭原则

装饰器 就是个函数 : 他要装饰一个函数,在不改变原函数以及调用方式的前提下,给其增加一个额外的功能

装饰器初识

# 1 李业,在一家xx科技有限公司工作,主管安排了一个任务,
# 写一个代码测试怼怼哥写的函数的执行效率。
# import time
# def index():
#     time.sleep(2)
#     print('欢迎访问博客园首页')


# print(time.time())
# start_time = time.time()
# index()
# end_time = time.time()
# print(f'此函数的执行效率{end_time-start_time}')

# 2. 主管让你测试小邓,李大象,重复代码太多。
#
# def func1():
#     time.sleep(2)
#     print('欢迎访问日记首页')
#
#
# def func2():
#     time.sleep(1)
#     print('欢迎访问评论首页')

# start_time = time.time()
# func1()
# end_time = time.time()
# print(f'此函数的执行效率{end_time-start_time}')
#
# start_time = time.time()
# func2()
# end_time = time.time()
# print(f'此函数的执行效率{end_time-start_time}')

# 3.  整合到函数中

# def func1():
#     time.sleep(2)
#     print('欢迎访问日记首页')
#
#
# def func2():
#     time.sleep(1)
#     print('欢迎访问评论首页')


# def test_time(x):
#     start_time = time.time()
#     x()
#     end_time = time.time()
#     print(f'此函数的执行效率{end_time-start_time}')

# test_time(func1)
# test_time(func2)


# 4. 怼怼哥这个函数在实际项目中被500执行,主管要求:在被执行此函数时,
# 同时要测试一下被执行函数的效率。


# def index():
#     time.sleep(2)
#     print('欢迎访问博客园首页')
#
# # index()
# def test_time(x):
#     start_time = time.time()
#     x()
#     end_time = time.time()
#     print(f'此函数的执行效率{end_time-start_time}')
#
# test_time(index)

# 版本4的问题: 开放原则满足了,封闭原则:不改变原函数的源码,以及调用方式。
# 违反了封闭原则:改变了函数的调用方式。


# 版本5: 不能改变原函数的调用方式(闭包):


# def index():
#     time.sleep(2)
#     print('欢迎访问博客园首页')
#
# # index()
#
# # def func1():
# #     time.sleep(2)
# #     print('欢迎访问日记首页')
#
# def test_time(x):  # x = index
#     def inner():
#         start_time = time.time()
#         x()
#         end_time = time.time()
#         print(f'此函数的执行效率{end_time-start_time}')
#     return inner
#
# index = test_time(index)
# index()

# 语法糖 @加上装饰器函数的名


# def f():
#     print(666)
#
#
# f = '太白'
# print(f)


# def test_time(x):  # x = index
#     def inner():
#         start_time = time.time()
#         x()
#         end_time = time.time()
#         print(f'此函数的执行效率{end_time-start_time}')
#     return inner
#
#
# # @test_time  # index = test_time(index)
# def index():
#     time.sleep(2)
#     print('欢迎访问博客园首页')

# index = test_time(index)
# index()

# def func1():
#     time.sleep(2)
#     print('欢迎访问日记首页')

# @test_time
# def func2():
#     time.sleep(1)
#     print('欢迎访问评论首页')

# func2 = test_time(func2)
# func3 = test_time(func3)
# func2()
'''100行代码'''
# index()


'''10行代码'''
# index()


'''50行代码'''
# index()


# 版本6:被装饰函数有返回值

# def test_time(x):  # x = index
#     def inner():
#         start_time = time.time()
#         ret = x()
#         # print(F'ret: {ret}')
#         end_time = time.time()
#         print(f'此函数的执行效率{end_time-start_time}')
#         return ret
#     return inner
#
#
# @test_time  # index = test_time(index)
# def index():
#     time.sleep(0.5)
#     print('欢迎访问博客园首页')
#     return True
#
# print(index())  # inner()
# 你应该是让True返回给index()这样才完美了,但是现在index是inner,所以你要是完全不改变原函数的使用,
# 你print(index()) ---> True


# 版本7: 被装饰函数带参数,无论加不加装饰器,你的实参'太白金星'应该传给形参n,。
# 但版本6不能实现传参,index('太白金星') ==  inner('太白金星')
#
# def test_time(x):  # x = index
#     def inner(*args,**kwargs):
#         # 函数的定义:* ** 聚合。
#         # args = ('苹果')
#         #args = (1, 3)
#         start_time = time.time()
#         ret = x(*args,**kwargs)
#         # 函数的执行:* ** 打散。
#         # ret = x(*('苹果'))  ==x('苹果',)
#         # ret = x(*(1, 3))  ==x(1,3)
#         # print(F'ret: {ret}')
#         end_time = time.time()
#         print(f'此函数的执行效率{end_time-start_time}')
#         return ret
#     return inner
#
#
# # @test_time  # index = test_time(index)
# def index(n):
#     time.sleep(0.5)
#     print(f'欢迎{n}访问博客园首页')
#     return True
#
# # @test_time  # index = test_time(index)
# def func2(a,b):
#     time.sleep(0.5)
#     print(f'最终结果:{a+b}')
#     return a + b
#
#
# print(index('苹果'))  # inner('苹果')
# print(func2(1,3)) # == inner(1,3)


# def warpper(f):
#     def inner(*args,**kwargs):
#         '''被装饰函数之前的操作'''
#         # print(666)
#         ret = f(*args,**kwargs)
#         '''被装饰函数之后的操作'''
#         # print('执行完毕了')
#         return ret
#     return inner
#
# @warpper
# def func():
#     print(111)
# #
# func()
# func()
# func()
# func()
# func()


# 装饰器的应用:在不改变原函数的源码以及调用方式前提下,为其增加额外的功能。
# 登陆认证,打印日志等。

内置函数I

内置函数:内置的函数,python中的内置函数68种。13种是在面向对象之后讲解,还有一些不讲的。

eval exce
慎用
s1 = "{1: 'alex'}"
s2 = '1 + 3'
eval 剥去字符串的外衣,返回里面的本质
ret = eval(s1)
print(ret,type(ret))
print(eval(s2))

exec 代码流,过程。
s3 = '''
for i in range(3):
    print(i)
'''
exec(s3)

s3 = input('>>>')
print(eval(s3))

hash()
print(hash(123214134))
print(hash('fkljdsaklgjdfs'))
print(hash('gjdfs'))

help()
print(help(str.count))


callable  **用于检查一个对象是否是可调用(理解为是否是函数)
def a():
    pass
b = 3433

a()
b()
print(callable(a))
print(callable(b))

int  **
print(int(3.14))
print(int('123'))

print(float(100))

print(complex(1,2))

print(bin(100))  # 将十进制转化成二进制。 **
print(oct(10)) # 将十进制转化成八进制字符串并返回。
print(hex(17)) # 将十进制转化成十六进制字符串并返回。 **
print(hex(15))

计算除数与被除数的结果,返回一个包含商和余数的元组(a // b, a % b)分页用到
print(divmod(10, 3))  # **

保留小数的有效位置
print(round(3.1485926,2))

print(pow(3, 3))  # 3*3*3
print(pow(3, 3, 2)) # 第三位是取余

输入字符寻找其在unicode的位置。
print(ord('a'))
print(ord('中'))

输入位置数字找出其对应的字符
print(chr(98))  # **
print(chr(20104))  # 予

repr 原形毕露  **
print('太白')
print(repr('太白'))

msg = '我叫%r' %('太白')
print(msg)


all  any  **
0,'',[],{},set(),(),None

l1 = [1, 'fgdsa', [], {1: 2}]
l2 = [0, '', [], {}]
print(all(l1)) # 判断可迭代对象元素全部都为True,返回True
print(any(l2))  # 判断可迭代对象元素只要有一个True返回True

匿名函数

# 匿名函数:没有名字的函数
# 匿名函数只能构建简单的函数,一句话函数。


def func(x,y):
    return x + y
# print(func(1, 2))

# 匿名函数构建
# func2 = lambda x,y: x + y
# print(func2(1, 2))

# 匿名函数最常用的就是与内置函数结合使用。

# 写匿名函数:接收一个可切片的数据,返回索引为 0与2的对应的元素(元组形式)。

# func = lambda x: (x[0],x[2])
# print(func('太白金星'))
# 写匿名函数:接收两个int参数,将较大的数据返回。

# func1 = lambda x, y: x if x > y else y
# print(func1(100,2))

# func2 = lambda : 3
# print(func2())
# str() dir() range() len() print() max() min() open()
# input() type() int() enumerate() list() id() set() dict()
# iter() next() tuple() bool() globals() locals() frozenset()
# 

内置函数II

print()
# sep 设定分割符。
# end 默认是换行可以打印到一行
# print(1, 2, 3, sep='|')
# print(1, 2, end=' ')
# print(3, 4)
# f = open('log','w',encoding='utf-8')
# # f.write('写入文件')
# print('写入文件', file=f)

# list
# l1 = list('fdsafd')
# print(l1)

# dict
# 创建字典的几种方式
# dic = {1: 2}
# # 字典推导式
# # print({i: 1 for i in range(3)})
# # dict()
# dic = dict(one=1, two=2, three=3)
# print(dic)
# # fromkeys()

# abs()  获取绝对值
# print(abs(-10))

# sum() 数字相加求和
# sum(iterable,)
# print(sum([1,2,3,4]))
# print(sum([1, 2, 3, 4], 100))

# 非常非常非常重要的

# min 可以加功能
# print(min([22, 11, 45, 2]))
l1 = [('alex', 73, 170), ('太白', 18, 185), ('武大', 35, 159),]
# # l1 = [(73, 'alex'), (35, '武大'), (18, '太白')]
# # 找年龄最小的元组
# # print(min(l1))
# # 通过设置key去使用min
# # key = 函数名
# # def func(x):  # x = ('alex', 73)
# #     return x[1]
#
# # 返回值是什么就按照什么比较最小的。
# # min 会自动的将可迭代对象的每一个元素作为实参传给x,
'''
第一次,x = ('alex', 73)   73
第二次,x = ('武大', 35)   35
第三次,x = ('太白', 18)   18
'''
# 将遍历的那个元素即是 ('太白', 18) 返回
# print(min(l1,key=func))
# print(min(l1,key=lambda x: x[1]))

# def func(x):
#     return x[1]

# print(min(l1,key=func))
# print(min(l1,key=lambda x: x[1]))
# print(min(l1,key=lambda x: x[2])[0])





a = 1
# 练习:
dic = {'a':3,'b':2,'c':1}
# 将dic值最小的键返回。
# print(min(dic,key= lambda x:dic[x]))
# # 将dic值最小的值返回。
# print(dic[min(dic,key= lambda x:dic[x])])

# dic = {'A':['李业', 67],'b': ['怼哥', 95],'c': ['冯垚', 85]}
#
# # 将成绩最低的从属于的那个列表返回。
# print(dic[min(dic,key=lambda x:dic[x][1])])
# # 将成绩最低的分数返回。
# print(dic[min(dic,key=lambda x:dic[x][1])][1])

# max() 与min 相同。

# reversed()  对一个可迭代对象进行翻转,返回一个迭代器

# s1 = 'alex'
# # print(reversed(s1))
# for i in reversed(s1):
#     print(i)

# 内置函数:bytes()
s1 = '太白'
# 方法一:
# print(s1.encode('utf-8'))
# 方法二:
# print(bytes(s1,encoding='utf-8'))

# 解码:
# b1 = b'\xe5\xa4\xaa\xe7\x99\xbd'
# # 方法一:
# # print(b1.decode('utf-8'))
# # 方法二:
# print(str(b1, encoding='utf-8'))

# zip 拉链方法 返回一个迭代器
# l1 = [1, 2, 3, 4]
# tu = ('a', 'b', 'c')
# s = 'python23'
# # print(zip(l1,tu,s))
# print(list(zip(l1,tu,s)))

# sorted 排序
l1 = [2, 6, 4, 1, 3]
# 列表原有的方法
# l1.sort()
# print(l1)
# 形成新列表
# print(sorted(l1))
# print(l1)

list1 = [
    {'name': 'alex', 'age': 73},
    {'name': 'wusir', 'age': 35},
    {'name': '太白', 'age': 25},
]
# 加key
# print(sorted(list1))
# print(sorted(list1, key=lambda x:x['age']))
# print(sorted(list1, key=lambda x:x['age'],reverse=True))
#
# l1 = [('张一东', 80), ('张耳洞', 75), ('怼怼哥', 7), ('李业', 59)]
# print(sorted(l1,key=lambda x:x[1]))
# print(sorted(l1,key=lambda x:x[1],reverse=True))

# filter  返回一个生成器
# 生成器表达式的筛选模式
l1 = [56, 67, 12, 34, 78, 90,]
# print([i for i in l1 if i > 60])
# 返回值为True的留下来
# print(filter(lambda x: x>60,l1))
# print(list(filter(lambda x: x>60,l1)))
#
# lst = [{'id':1,'name':'alex','age':50},
#         {'id':1,'name':'wusir','age':17},
#         {'id':1,'name':'taibai','age':16},]
#
# print(list(filter(lambda x:x['age']<30,lst)))


# map 返回一个迭代器,相当于生成器表达式:循环模式
# l1 = [56, 67, 12, 34, 78, 90,]
# # print([i**2 for i in l1])
# # print(map(lambda x:x**2,l1))
# print(list(map(lambda x:x**2,l1)))

# reduce python3x 从内置函数剔除了。
from functools import reduce
'''
第一次:x,y 1,2 求和 3 记录到内存
第二次:x,y 3,3 求和 6 记录到内存
第三次:x,y 6,4 .........

'''
# print(reduce(lambda x,y: x+y,[1,2,3,4,5]))
# print(reduce(lambda x,y: 2*x+y, [1,2,3]))
# print(reduce(lambda x,y: x+y, ['alex','s','b']))  # 可以用字符串拼接
上一篇:python 13 内置函数II 匿名函数 闭包


下一篇:Linux显示以log结尾的日志文件