Python学习笔记_B(生成器、装饰器)


笔记中代码均可运行在Jupyter NoteBook下(实际上Jupyter-lab使用体验也很棒)。

建议不要光看,要多动手敲代码。眼过千遭,不如手读一遍。

相关笔记的jupiter运行代码已经上传,请在资源中自行下载。

函数

在一个Python文件中,定义重名的函数不会报错,但是会使用最后的一个函数

def funcName(*args,**kwargs):
    pass

不定长参数

'''
*args:长度不限,来着不拒,输出的是元组形式
**kwargs:长度不限,输入必须是key=value的形式,否则无法传入;
输出为字典形式
注意:不定长参数应放在参数列表最后,否则会吞参数

'''
#*args,**kwargs例子
def funcDemo(a,*args,**kwargs):
    print(a)
    print(args)
    print(kwargs)
funcDemo(111,1,2,3,4,name='name',age='age')
111
(1, 2, 3, 4)
{'name': 'name', 'age': 'age'}
'''
对不定长参数传入的拆包
元组传入加*:*tuple
字典传入加**:**dict
如果不拆包直接传入,会被*args吞并,不会区分传值
'''
#拆包例子
A=(1,2,3,4)
B={'name':'name','age':'age'}

def func(*args,**kwargs):
    print(args)
    print(kwargs)

func(A,B)
print("*"*8)
func(*A,**B)
((1, 2, 3, 4), {'name': 'name', 'age': 'age'})
{}
********
(1, 2, 3, 4)
{'name': 'name', 'age': 'age'}

匿名函数

'''
lambda [arg1,[arg2,arg3...]]:expression

'''
# lambda简单例子
f = lambda x,y:x+y
print(f(2,3))
5

变量作用域

'''
L(Local) 局部作用域

E(Enclosing) 闭包函数外的函数中

G(Global) 全局作用域

B(Built-in) 内建作用域

变量查找顺序:LEGB

'''
# global和nonlocal
'''
局部内改全局,变量前加global
内部修改外部函数变量,变量前加nonlocal
'''
#nonlocal 例子
def outer():
    num=10
    print('this is raw num:',num)
    
    def inner():
        nonlocal num # 声明外部变量
        num='change'
        print('this is changed num:',num)
    inner()
    print('this is num:',num)

outer()    
this is raw num: 10
this is changed num: change
this is num: change

迭代器

'''
迭代器是一个可以记住遍历的位置的对象。

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
迭代器只能往前不会后退。

迭代器有两个基本的方法:iter() 和 next()。

可迭代对象:list,tuple,dict,set,str,生成器(generator,带yield的generator function)
# 这里也可说是Iterable

判断是否是可迭代对象:isinstance:
1
from collections.abc import Iterable
isinstance(flag, Iterable)
2
或者是使用for循环来判断

iter()函数用来将Iterable转换为Iterator
'''

# isinstance 迭代对象判断 例子

# 导入Iterable
from collections.abc import Iterable 
# 判断是否可迭代
isinstance('flag', Iterable) # 会输出True

# 例子释义:isinstance是判断变量1(代码中的''flag)是否为变量2(Iterable)类的实例或是其子类的实例

# isinstance 例子
class A(object):
    def __str__(self):
        return 'this is class A'

    
class B(A):
    def __str__(self):
        str = 'this is class B, and is son class of A'
        return str
    
b = B()

a = isinstance(b,B) # 判断b是否为B的实例
c = isinstance(b,A) # 判断b是否为A的实例
print(a,c) # 输出显示b是B的实例,b是A的子类的实例
print("*"*8)

# Iterable 和Iterator判断
from collections.abc import Iterator

a = isinstance('str', Iterator) # False
c = isinstance(iter('str'), Iterator) # True
print(a,c)
True True
********
False True

生成器 generator

对于要创建的数据(尤其是Iterable数据及其范类),不是一下全部创建,而是在程序运行中逐步生成。可以极大的节约内存。

'''
可以用next()获取生成器的下一个返回值
但是这里next()执行完最后一次还调用会报错

加了yield参数的函数就是一个生成器
对生成器函数不可用普通函数的调用方法,生成器函数返回的是一个对象。
此时需要用一个变量来指向这个生成器。

此时用next(x)来执行生成器时,

会在yield处停止,并返回yield后的参数,

这个返回的参数就是生成器生成的值,
在下一次调用生成器时,会在yield停止的地方接着执行。

next(x)和x.__next__()作用相同


一个生成器中可以有多个yield

协程多任务:运行一下,停一下
这里就可以用生成器的来实现多任务
'''

# yield function 例子 : next()输出fib数列


def fib():
    a,b = 0,1
    for i in range(10):
        yield b
        a,b = b,a+b
a = fib()
flag = 1
print('1-10的fib数列:')
while flag ==1:
    try:
        fib = next(a) # 这里next(generator)和a.__next__()是一样的
        print(fib, end='\t')
    except Exception as result:
        print('\n1-10的最大fib数:', fib)
        flag = 0
1-10的fib数列:
1	1	2	3	5	8	13	21	34	55	
1-10的最大fib数: 55
# yield function 不用next(),使用循环输出生成器生成的值
# 一个生成任意数的斐波那契数列的函数
def fibs(num):
    i = 0
    a, b = 0, 1
    while i<num:
        yield b # 生成器核心
        a,b = b, a+b
        i +=1

# 这里以输出11的fib数列举例
for f in fibs(11):
        print(f, end = '\t')
1	1	2	3	5	8	13	21	34	55	89	
'''
x.send(value) # value必须有,可以是None,但是不可不写
'''

# x.send(value) 例子

def fib():
    a,b = 0,1
    for i in range(10):
        temp = yield b
        a,b = b,a+b
        print(temp)
a = fib()
next(a)
# 这里print()的结果是None,因为生成器在yield处停止,
# 不会对temp赋值
k = a.send("yeyeye")
# 这里print()的结果是yeyeye,因为send()将这个值传入了temp
print('生成器返回的值',k)
print('1111111111')
yeyeye
生成器返回的值 1
1111111111

闭包

参考网址1(闭包):

https://blog.csdn.net/marty_fu/article/details/7679297

参考网址2(python编译与执行):

对于上一个参考网址中对for循环中的闭包问题解析的不同解释

https://blog.csdn.net/helloxiaozhe/article/details/78104975

闭包定义
'''
在一个函数内部定义一个函数,

并且内部的函数调用了外部函数的参数,

此时,内部函数和用到的参数就叫做闭包。

闭包 = 函数块+定义函数时的环境
'''

# 闭包定义例子
# 定义一个外部函数
def outer(out_num):
    #在函数内部再定义一个函数,
#     并且这个函数用到了外边函数的变量,
# 那么将这个函数以及用到的一些变量称之为闭包
    def inner(in_num):
        sum = out_num + in_num
        return sum
    #这里返回的是闭包的结果
    return inner

# 对outer函数中的out_num赋值为3
mid = outer(3)

print('mid的类型是:',type(mid))
print('mid的名字是:',mid.__name__)
print('此时的mid为:', mid)

# 这个6是对inner(in_num)中的in_num赋值
result = mid(6)

print('对内外均赋值后mid的类型是:',type(result))
print('对内外均赋值后的mid为:', result)
mid的类型是: <class 'function'>
mid的名字是: inner
此时的mid为: <function outer.<locals>.inner at 0x7ff6d804ce18>
对内外均赋值后mid的类型是: <class 'int'>
对内外均赋值后的mid为: 9
使用闭包的注意事项
'''
闭包内对外部函数的局部变量进行修改:
1、要么在闭包内添加 nonlocal 声明此变量为外部局部变量
(nonlocal 声明只有python3中有,在python2中只能用法2)

2、要么外部变量为列表,字典,集合这种可扩展变量,
在闭包内部才可进行修改
方法2实际上还是由legb次序返回到外部函数环境中,所以两个方法殊途同归
'''
# 例子

def out():
    a = 1
    def inner():
#         nonlocal a
        a = a+1
        return a
    return inner

t = out()
t() # 这里报错
# 报错原因:python规则指定所有在赋值语句左面的变量都是局部变量
# 则在闭包inner()中,变量a在赋值符号"="的左面,被当做inner()的局部变量,但是未在inner找到a的定义

-------------------------------------------------------------------------

UnboundLocalError                       Traceback (most recent call last)

<ipython-input-1-44e22659e2f6> in <module>
     19 
     20 t = out()
---> 21 t() # 这里报错
     22 # 报错原因:python规则指定所有在赋值语句左面的变量都是局部变量
     23 # 则在闭包inner()中,变量a在赋值符号"="的左面,被当做inner()的局部变量,但是未在inner找到a的定义


<ipython-input-1-44e22659e2f6> in inner()
     14     def inner():
     15 #         nonlocal a
---> 16         a = a+1
     17         return a
     18     return inner


UnboundLocalError: local variable 'a' referenced before assignment
# 解决方法1
def out():
    a = 1
    def inner():
        nonlocal a 
        a = a+1
        return a
    return inner

t = out()
print('解决方法1:',t())

# 解决方法2
def out():
    # 定义a为一个列表
    a = [1]
    def inner():
        # 在执行时在inner()内未找到a的定义,但是在外部函数空间找到a[0]为list的定义
        a[0]= a[0]+1
        return a
    return inner

t = out()
print('解决方法2:',t())
type(t())# 注意:此时输出的t的类型为list
解决方法1: 2
解决方法2: [2]





list
'''
闭包在循环体中的使用:
'''

# 错误示例
flist = []

for i in range(3):
    print('for START')
    print('FOO START')
    def foo(x):
        print('foo running')
        print(x + i)
        print('foo run over')
    print('FOO END')
    print('i is : ', i)
    flist.append(foo)
    print('for END')
    
    
for f in flist:
    f(2) # 此时输出为4 4 4
# 原因是在执行for是遇到foo这一函数定义,只录入了foo(x)名,而没执行foo(x)的函数体
for START
FOO START
FOO END
i is :  0
for END
for START
FOO START
FOO END
i is :  1
for END
for START
FOO START
FOO END
i is :  2
for END
foo running
4
foo run over
foo running
4
foo run over
foo running
4
foo run over
'''对循环中使用闭包的正确用法'''

# 正确示例
flist = []

for i in range(3):
    print('for START')
    print('FOO START')
    def foo(x, y=i):
        print('foo running')
        print(x + y)
        print('foo run over')
    print('FOO END')
    print('i is : ',i)
    flist.append(foo)
    print('for END')
    
    
for f in flist:
    f(2) # 此时输出为2 3 4
# 在程序执行时将foo(x,y=i)录入,此时foo中的y=i是随着for函数一起执行的,在append到flist中的元素为
# [foo(x,y=0),foo(x,y=1),foo(x,y=2)]
# 在执行foo函数体中的内容的时候会有i的不同次序值,而不是最终的i
for START
FOO START
FOO END
i is :  0
for END
for START
FOO START
FOO END
i is :  1
for END
for START
FOO START
FOO END
i is :  2
for END
foo running
2
foo run over
foo running
3
foo run over
foo running
4
foo run over
闭包的作用
'''
1、持久化编程,保持住当前的运行环境
'''

# 跳棋 例子
origin = [0, 0]  # 坐标系统原点
legal_x = [0, 50]  # x轴方向的合法坐标
legal_y = [0, 50]  # y轴方向的合法坐标
def create(pos=origin):
    def player(direction,step):
        # 这里应该首先判断参数direction,step的合法性,
#         比如direction不能斜着走,step不能为负等
        # 然后还要对新生成的x,y坐标的合法性进行判断处理,
#     这里主要是想介绍闭包,就不详细写了。
        new_x = pos[0] + direction[0]*step
        new_y = pos[1] + direction[1]*step
        pos[0] = new_x
        pos[1] = new_y
        #注意!此处不能写成 pos = [new_x, new_y],原因在上文有说过
        return pos
    return player
 
player = create()  # 创建棋子player,起点为原点
print (player([1,0],10))  # 向x轴正方向移动10步
print (player([0,1],20))  # 向y轴正方向移动20步
print (player([-1,0],10))  # 向x轴负方向移动10步

[10, 0]
[10, 20]
[0, 20]
'''
2、根据外部作用域的局部变量来得到不同的结果
这有点像一种类似配置功能的作用,我们可以修改外部的变量,
闭包根据这个变量展现出不同的功能
'''
# 这是一个根据关键字过滤文件内容的简单闭包demo
# make_filter 中的keep是关键字
# 外部函数make_filter作用是设置关键字
# 内部函数the_filter是根据关键字过滤文件内容并输出相关内容
def make_filter(keep):
    def the_filter(file_name):
        with open(file_name,'r', encoding='utf-8') as file:
            lines = file.readlines()
        filter_doc = [i for i in lines if keep in i]
        return filter_doc
    return the_filter

# 获取文件《闭包用途2使用文档.txt》有关"闭包"的关键字
filter = make_filter("闭包")
filter_result = filter("./Gnerate_files/闭包用途2使用文档.txt")

for f in filter_result:
    print(f)

这是一个关于闭包用途的使用文档

但是我对原作者在循环中使用闭包的错误剖析有不同的见解,所以没有全部参考原作者的思路

装饰器

写代码的开放封闭原则

开放:对代码进行扩展开发
封闭:对代码已实现的功能,最好不要随意修改

语法糖:写一个函数,用@符号加在其他功能前,达到一个想要的结果

对装饰器有充分的理解要先掌握以下两点:

1、一切皆对象

2、闭包

装饰器的功能:

引入日志

函数执行时间统计

执行函数前预备处理

执行函数后清理功能

权限校验等场景

缓存

装饰器很好的实现了开放封闭原则

# 装饰器例子
def zsq(func):
    print('zsq print START')
    def prt():
        print('prt print'.center(10,"*"))
        func()
        print('{}执行完毕'.format(func.__name__))
    print('zsq print END')
    return prt

@zsq
def f1():
    print("这是一个f1函数".center(20,"*"))

def f2():
    print('这是f2函数')
    
# test
f1()

print('*'*18)

# 这里的f2 = zsq(f2) 和上面的@zsq作用是一样的
f2 = zsq(f2) # 这里将f2()作为参数,传入zsq()中的func
f2() # 这里的f2已经不是原来的函数了,而是zsq()中封装的prt函数

# 执行顺序为(二者顺序相同,这里以f1为例):
# 对于执行顺序,建议用断点调试的方法进行观察
# 1、zsq 函数头
# 2、func = f1 赋值操作
# 3、 f1函数头
#  4、zsq 函数体第一行,这里是print('zsq print START')
# 5、 记录prt函数头标记
#  6、prt()函数后紧跟的一行此例中为print('zsq print END)
# 7、prt函数体中的第一行,这里是('prt print'.center(10,"*"))
# 8、func() 在例子中也就是f1()
# 9、prt中func()后紧跟的一行
# 依次执行,直到执行完最后一行

# f1 为函数名
# f1() 为调用f1函数,只写f1不会调用f1函数
# 这里值得注意的是python执行文件中,遇到函数会先标记,然后继续执行函数后的内容,
# 最后执行函数的函数体
zsq print START
zsq print END
prt print*
******这是一个f1函数******
f1执行完毕
******************
zsq print START
zsq print END
prt print*
这是f2函数
f2执行完毕
# 多个装饰器 例子
def zsq1(f1):
    # 定义装饰器1
    print("the zsq1 is RUNING")
    def prt1():
        print('装饰器1'.center(10, '*'))
        return f1() # 回传传入的f1函数
    print("the zsq1 is END")
    return prt1 # 回传闭包内的函数名

def zsq2(f2):
    print("the zsq2 is RUNING")
    def prt2():
        print('装饰器2'.center(10,'*'))
        return f2()
    
    print("the zsq2 is END")
    return prt2

@zsq1
@zsq2 # 用zsq1()和zsq2()装饰test()
def test():
    print('test'.center(10, '*'))
    return 'hello world!'

# 测试
tmp = test()
print(tmp)
# 解释:
# python程序是顺序执行的,
# 装入顺序为取近优先,最靠近被装饰函数最先被装饰,并且函数在装饰时就已经开始运行了
# 例子中zsq2最靠近被装饰函数test(),所以test()函数先装入zsq2
# zsq2又被zsq1所装饰,所以zsq2要放入zsq1中
# 执行顺序为剥洋葱,由外向内
# 先执行zsq1中的闭包,然后是zsq2中的闭包(被装饰函数test()在zsq2函数的闭包中)
#######################
# 更加形象的例子就是把多个装饰器比作俄罗斯套娃
# 将被装饰函数看做最小的套娃,装饰器依据靠近被装饰函数的距离依次变大
# 在装饰器进行装饰时,可以看做装套娃:从最小的(这里是被装饰函数)开始,依次放入比自己大的娃娃中
# 在执行装饰器时,可以看做拆套娃:从最大的开始(这里是最外层的装饰器)拿,依次向内,直到取出最小的娃娃
the zsq2 is RUNING
the zsq2 is END
the zsq1 is RUNING
the zsq1 is END
***装饰器1***
***装饰器2***
***test***
hello world!
'''
装饰器执行的时间

Python解释器执行到@处时,就已经开始装饰了

装饰器对有参数,无参数函数进行装饰:

当无参时,不必特别处理,

当有参数时,对闭包中的函数进行设置形参,
这里就可以进行有参数的函数进行装饰了.

这里需要特别注意:
在有参数时,可以用不定长参数的方式,
*args和**kwargs,在调用装饰的函数时,
需要用同样的不定长参数进行解包

装饰器对带有返回值的函数进行装饰:

在闭包的函数中设置一个return 在调用被装饰的函数时,赋值,这样就可以输出
'''

# 对含定长参函数进行装饰 例子
print('对定长参数函数装饰')
def decorator_var(func):
    def decorator_var_in(a, b):
        print(a,'\t',b)
        func(a,b)
        
    return decorator_var_in

@decorator_var
def foo(a, b):
    print('sum = ', a+b)
    
foo(1,2)
        
print('定长装饰例子结束\n')
对定长参数函数装饰
1 	 2
sum =  3
定长装饰例子结束
# 对不定长参数函数装饰 例子
print('对不定长参数函数装饰')
def decorator_var(func):
    def decorator_var_in(*args, **kwargs):
        print(args,'\n',kwargs)
        func(*args, **kwargs)
        
    return decorator_var_in

@decorator_var
def foo(*args, **kwargs):
    l = [args, kwargs]
    print('set = :',l)
    
t = (1,2,3)
d = {'name':'sss','age':16}
foo(*t, **d)
        
print('不定定长装饰例子结束\n')
对不定长参数函数装饰
(1, 2, 3) 
 {'name': 'sss', 'age': 16}
set = : [(1, 2, 3), {'name': 'sss', 'age': 16}]
不定定长装饰例子结束
# 对带有返回值的函数进行装饰 例子
print('带有返回值的函数进行装饰')
def zsq(func):
    def bb():
        re = func()
        return re # 这里是注意点
    return bb
@zsq
def test():
    print("this is a test def !".center(30,"*"))
    return 666
ret = test()
# 注意这里的return
print(ret)
print('带有返回值的函数进行装饰结束')
带有返回值的函数进行装饰
*****this is a test def !*****
666
带有返回值的函数进行装饰结束
# 通用装饰器 例子
def decorator_pub(func):
    def decorator_in(*args, **kwargs):
        print("这是一个通用装饰器")
        print("args is {}\nkwargs is {}".format(*args, **kwargs))
        tmp = func(*args, **kwargs)
        return tmp
        #注意,在Python中,如果return 返回的是None,那么不算是错误,
#         所以,在这里一直写上return 是不算错的
    return decorator_in

@decorator_pub
def test(a,b):
    print("this is a test")
    return a+b

ret = test('a','c')
print(ret)
这是一个通用装饰器
args is ('a', 'c')
kwargs is {}
this is a test
ac
带有参数的装饰器

装饰器带参数,在原有的装饰器的基础上,设置外部变量

这里的设置外部变量就是在原有的装饰器的基础上,在外部再加一个带参数的函数

带有参数的装饰器能起到在运行时使用不同的功能

# 带参数的装饰器 例子
def func_arg(tmp):
    print("the value is {}".format(tmp))
    
    def zsq(func):
        print("zsq")
        def bb():
            print("bb")
            func()
        return bb
    
    return zsq

@func_arg(6666) # python执行到这里时,先执行了func_arg(tmp = 6666)
def test():
    print("this is test")

test()

print('\n我是分隔符\n')

def demo():
    print('this is demo')
    
demo = func_arg('11111')(demo)
demo()
# test的形式和demo的形式等价
the value is 6666
zsq
bb
this is test

我是分隔符

the value is 11111
zsq
bb
this is demo
类做装饰器

用类做装饰器需要在类中定义一个__call__()方法

# 类做装饰器 例子
class Demo(object):                                               
        def __init__(self, func):
            print("初始化{}".format(func.__name__))
            self.__func = func
        def __call__(self):
            print("这是类装饰器")
            self.__func()


@Demo
def test():
    print("test".center(20,"*"))
    
test()


初始化test
这是类装饰器
********test********
wraps函数

用于消除装饰器在装饰函数时改变了被装饰函数的说明文档这一缺点

# 未使用warps() 例子
import functools

print('未使用warps() 例子')

def zsq(func):
    "这是一个装饰器"
#     @functools.wraps(func)

    def a():
        '这是装饰器的内部函数'
        print("这里添加了一个装饰器")
        func()
        
    return a

@zsq
def test():
    "这里是一个测试函数"
    print("this is a test")
    
test()

# 输出test()的文档
print(test.__doc__)

未使用warps() 例子
这里添加了一个装饰器
this is a test
这是装饰器的内部函数
import functools

print('\n使用warps() 例子')

def zsq(func):
    "这是一个装饰器"
    @functools.wraps(func)
    def a():
        '这是装饰器的内部函数'
        print("这里添加了一个装饰器")
        func()
        
    return a

@zsq
def test():
    "这里是一个测试函数"
    print("this is a test")
    
test()

print(test.__doc__)
使用warps() 例子
这里添加了一个装饰器
this is a test
这里是一个测试函数
装饰器使用场景
'''授权(Authorization)'''

# 导入wraps防止说明文档被覆盖
from functools import wraps

def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        
        if not auth or not chech_auth(auth.username, auth.password):
            authenticate()
            
        return f(*args, **kwargs)
    
    return decorated
'''日志(Logging)'''

# 导入wraps防止说明文档被覆盖
from functools import wraps
def logit(func):
    @wraps(func)
    
    def with_logging(*args, **kwargs):
        print(func.__name__ + 'wass called')
        return func(*args, **kwargs)
    
    return with_logging

@logit
def addition_func(x):
    '''Do some math.'''
    return x + x

result = addition_func(4)
# output: addition_funcwass called
addition_funcwass called
'''在函数中嵌入装饰器(相当于带参数的装饰器)'''

# 记录日志并输出到日志文件
from functools import wraps

def logit(logfile='./Gnerate_files/out.log'):
    def logging_decorator(func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__+' was called'
            print(log_string)
            # 打开logfile,写入日志
            with open(logfile, 'a+', encoding='utf-8') as log_file:
                # 写入日志
                log_file.write(log_string + '\n')
            return func(*args, **kwargs)
        return wrapped_function
    return logging_decorator
    
@logit()
def myFunc1():
    pass


@logit(logfile='./Gnerate_files/func2.log')
def myFunc2():
    pass

# 测试
myFunc1()
# 输出myFunc1 was called
# 并且生成一个out.log文件,文件内容为上述字符串
myFunc2()
# 输出myFunc2 was called
# 并且生成一个out.log文件,文件内容为上述字符串
myFunc1 was called
myFunc2 was called
发布了13 篇原创文章 · 获赞 0 · 访问量 28

猜你喜欢

转载自blog.csdn.net/qq_34764582/article/details/104940021