【Python 装饰器&迭代器&生成器&匿名函数 06】

1、函数名就相当于一个变量,变量怎么用函数名就怎么用

#并不是只有函数名才能调用一个函数
# 只要是函数的地址对应的变量都可以通过()调用函数

def a():
    print('I me fine')

a()
print(a) #打印a的内存地址:<function a at 0x107708040>
b=a #把a赋值给b
print(b) #打印b的内存地址:<function a at 0x107708040>

l=[a] #把a放到列表内
print(l[0]) #<function a at 0x10ea84040>
l[0]() #调用a函数
import time
# 函数的名字也可以做参数
def timmer(func):
    start_time = time.time()
    func
    end_time=time.time()
    print(end_time-start_time)
def clac_1():
    s=0
    for i in range(10000):
        s +=i
def clac_2():
    s=0
    for i in range(10000):
        s +=i
timmer(clac_1())
timmer(clac_2())

2、闭包:如果一个内部函数引用了外部函数的变量,那么这个内部函数就是一个闭包函数

def wahaha():
    name = 'alex'
    def a():
        '''一旦内层函数引用了外层函数的变量,内层函数就是一个闭包函数'''
        print('i me fine',name)
    return a

b = wahaha() #调用wahaha函数,返回是a函数,然后把内存地址赋值给b
b()

|-- 闭包有什么用?

A写了一个程序,B,C,D不管是什么类型的函数,都可以调用A,那么A程序的实现方式就是闭包

#案例:timemer函数可以计算其他函数的运行时间,clac_1(),clac_2(m,n)是运行函数

#闭包 --> 应用1:计算时间
import time
# 计算时间的函数
def timemer(funtime):
    def inner(*args,**kwargs):
        start_time = time.time()
        ret = funtime(*args,**kwargs)
        end_time = time.time()
        print(end_time-start_time)
        return ret
    return inner

# 计算时间的函数clac_1
@timemer
def clac_1():
    s=0
    for i in range(1000000):
        s +=i
    # print('clac_1-->',s)
    return s
  # 计算时间的函数clac_1
@timemer
def clac_2(m,n):
    print(m,n)
    s = 0
    for i in range(1000000):
        s += i
    # print('sclac_2-->', s)
    return s
# func = timemer(clac_1) #调用timemer(clac_1)返回值是inner函数,然后赋值给func,
# func() # 调用func()就相当于调用的是inner()函数
clac_1()

'''如果我要计算clac_1()函数,但是函数里面有两个参数,调用timemer(clac_1)就不会成,原因是调用
timemer(clac_1)其实是调用的inner()函数的内存地址,那怎么解决'''
'''解决办法:
1、在inner函数内加参数*args,这样就可以灵活存不用数量的参数
'''
# func2 = timemer(clac_2)
# func2(10000,20)
clac_2(10000,20)
'''如果func2(a=10000,20)这么传,也是会报错的,如何解决:inner()函数内还需要加上**kwargs'''
# func2(m=99999,n=99)
# func2(88888,n=88)
clac_2(m=99999,n=99)
clac_2(88888,n=88)

'''如果clac_1和clac_2不打印s,而是直接返回s,那么在调用timemer(funtime)的时候我们也需要把funtime返回,如果不返回结果就会是none'''

通过上面的代码,其实就可以发现,timemer函数是一个公共的函数,任何函数都可以调用它,这样的函数其实就是一个装饰器

====> 上面的代码其实有个大bug,我们本应该是一调用 clac_1, clac_2函数就可以计算耗时,那么如何实现呢?只需要在函数方法之前加@timemer即可,他的作用相当于

func = timemer(clac_1) #调用timemer(clac_1)返回值是inner函数,然后赋值给func,func=inner内存地址

一、装饰器

1、什么情况下用装饰器

在已经写好发版程序的基础上,需要对一个函数执行前后增加功能的时候

  开放封闭原则:

    开发:对扩展是开放的

    封闭:对修改是封闭的

有时候也会写好一些装饰器

2、装饰器的固定格式

def 装饰器名字(func):
    def inner(*args,**kwargs):
        '''在执行被装饰的函数之前要做的事儿'''
        '''比如:判断是否登录'''
        ret = func(*args,**kwargs)
        '''在执行被装饰的函数之后要做的事儿'''
        '''比如:写log'''
     return ret
return inner

 #案例:利用装饰器把登录和注册的日志写到日志文件operrate.log内

# 利用装饰器把登录和注册的日志写到日志文件operrate.log内
import time
from functools import wraps

def wrapper(func):
    @wraps(func)
    def inner(*args,**kwargs):
        ret=func(*args,**kwargs)
        t = time.strftime('%Y-%m-%d %H:%M:%S')
        # print('%s在[%s]执行了%s函数'%(args[0],t,func.__name__))
        write_log('%s在[%s]执行了%s函数'%(args[0],t,func.__name__))
        return ret
    return inner

def write_log(context):
    f = open(r'operrate.log',mode='a',)
    f.write(context)
    f.write('\n')
    f.close()
@wrapper
def login(name):
    print('%s登录了'%name)
login('alex')

@wrapper
def register(name):
    print('%s注册了'%name)
register('alex')
print(register.__name__)
# inner  -->这里调用register函数的名字,但是输出的是inner,如何才能伪装的好一些输出register函数?
# 解决办法:引入wraps装饰器,然后在inner前加上@wraps(func) 这样就会输出注册名

3、一个函数可以被多个装饰器装饰

# 三个装饰器
def warpper1(fun):
    def inner1(*args,**kwargs):
        print('before in inner1')
        ret = fun(*args,**kwargs)
        print('after in inner1')
        return ret
    return inner1

def warpper2(fun):
    def inner2(*args,**kwargs):
        print('before in inner2')
        ret = fun(*args,**kwargs)
        print('after in inner2')
        return ret
    return inner2

def warpper3(fun):
    def inner3(*args,**kwargs):
        print('before in inner3')
        ret = fun(*args,**kwargs)
        print('after in inner3')
        return ret
    return inner3

@warpper1
@warpper2
@warpper3
def wahaha():
    print('wahaha')
    
wahaha()

执行结果:
before in inner1
before in inner2
before in inner3
wahaha
after in inner3
after in inner2
after in inner1

#从执行结果发现:挨着函数越近的装饰器,执行前和执行后都紧紧挨着执行

各装饰器放位置的关系:

@timmer装饰器 计算被装饰函数的时间,永远紧贴被装饰的函数

@login装饰器 检测被装饰的函数有没有登录,没有登录要先登录才能使用这个函数

@logging装饰器,可以放到任意位置

 # 案例:利用装饰器把登录的日志写到login.log文件内,注册的日志写到register.log文件内

import time

def log(filename):
    def wrapper(func):
        def inner(*args,**kwargs):
            ret = func(*args,**kwargs)
            t = time.strftime('%Y-%m-%d %H:%M:%S')
            write_file('%s在[%s]执行了%s函数'%(args[0],t,func.__name__),filename)
            return ret
        return inner
    return wrapper

def write_file(context,filename):
    with open(filename,mode='a') as f:
        f.write(context)
        f.close()

@log('login.log')
def login(name):
    print('%s登录了'%name)

@log('register.log')
def register(name):
    print('%s注册了'%name)

login('alex')
register('alex')

较比上一个案例,优化的地方在于,把之前wrapper函数封装到了log的函数内,然后log函数的参数filename和write_file函数的参数形成闭包

但是log()函数就成了装饰器了

--------->这个就是带参数的装饰器,其实就是在没带参数装饰器的外面套一层

二、迭代器(iterator)

1、特点:

  • 一个一个的取值,而不是一次性的把所有数据都取出来
  •  只能按顺序取
  • 迭代器中的数据,不取不创建,一个迭代器中的数据只能从头到尾取一次
  • 所有能被for循环的类型,至少是一个可迭代类型

可迭代协议:如果一个数据类型中有iter方法,那么这个数据类型就是可迭代类型

迭代器协议:如果一个数据类型中有iter和next方法,那么这个数据类型就是一个迭代器类型

2、生成器(generator) 生成器的本质就是一个迭代器

生成器函数:没有return,只有yield

#简单案例,生成100w件衣服,说明生成器的作用
def make_cloth_simple(n):
    '''生成器函数,没有return,只有yield'''
    print('第一件衣服')
    yield 1
    print('第二件衣服')
    yield 2
    print('第三件衣服')
    yield 3

ret=make_cloth_simple(1000000)
print(ret) #<generator object make_cloth_simple at 0x10f83c660>  generator生成器
print(ret.__next__()) #第一件衣服,1
print(ret.__next__()) #第二件衣服,2
print(ret.__next__()) #第三件衣服,3
make_cloth_simple(1000000)传了100w,但是ret.__next__()也是一件一件的取,所以生成器本质上就是一个迭代器

 yeild相当于一个暂停键,暂停了就返回一个值,区别于return,return是遇到就返回然后结束

#案例:一次性取100w件衣服和100w件衣服先取20件,然后在取10件,什么时间想取在取的区别

# 一次性取10w件衣服
def make_cloth_simple(n):
    for i in range(n):
        print('第%s件衣服'%i)
# make_cloth_simple(100000)

#100w件衣服,先取20件,有需要在取10件...
def make_cloth(n):
    for i in range(n):
        yield '第%s件衣服'%i

ret=make_cloth(100000)

for n in range(20):
    print(ret.__next__())
print('=='*20)
for n in range(10):
    print(ret.__next__())
'''
生成器就相当于,我先签订了10w件衣服的订单合同,等什么时候需要做衣服的时候就做20件,等下次在需要的时候在做10件
'''

对比两者:利用生成器的一个优势就是节省内存空间,不断地从一个容器或者规则内不断的取数据,完成迭代

# 案例:A程序不断的往test文件内写数据,B程序只要监听到test文件内有数据,就在终端上打印出A程序写入的数据

写数据程序

#  这个程序不断的往test文件内写数据
def write_context():
    while True:
        context = input('>>>>>')
        f = open(r'test.txt',mode='a',encoding='UTF-8')
        f.write(context)
        f.write('\n')
        f.close()

write_context()

监听程序   -->在自动化中应用于监听到错误日志,直接写到日志文件内

# 只要监听到test文件内有数据,就把数据打印到屏幕上

def listen():
    f = open(r'test.txt','r',encoding='utf-8')
    while True:
        context=f.readline().strip()
        # if context: #如果context存在的意思
        #     print(context)
        if 'error' in context: #如果写入的内容包含error信息,那么就暂停把context打印出来-->这样做的可以写一个监听日志的程序
            yield context
    f.close()
for context in listen():
    print(context)

一看到yeild就知道这是一个生成器

#应用在真实工作中的案例:

--> userinfo 文件存放:用户名和密码

--> 程序文件:get_user函数获取用户名和密码,login函数根据get_user获取的用户名和密码进行登录判断,register函数根据get_user函数判断如果内存在就提示注册重复,不存在就注册成功

#读去userinfo的用户名和密码
def get_user(filename):
    lst = []
    with open(filename,'r',encoding='utf-8') as f:
        for line in f:
            user,pwd = line.strip().split('|')
            lst.append((user,pwd))
    # print(lst)
    return lst
# get_user('userinfo') #[('alex', 'www'), ('wusir', '666')]
'''上面的这种写法,可以把userinfo文件内的用户名和密码取出来,但是如果
userinfo内的数据是千万级别的,一次性取出来的话效率会非常低,所以最好用生成器来完成(用多少去多少)'''

def get_user_generator(filename):
    with open(filename,'r',encoding='utf-8') as f:
        for line in f:
            user,pwd=line.strip().split('|')
            yield user,pwd

    f.close()
# get_user_generator('userinfo')
def set_user_generator(filename,username,passwd):

    with open(filename,'a',encoding='utf-8') as f:
        f.write('\n')
        f.write(username)
        f.write('|')
        f.write(passwd)

    f.close()
# login函数
def login():
    username = input('请输入用户名:')
    passwd = input('请输入密码:')
    for user,pwd in get_user_generator('userinfo'):
        if user==username and pwd == passwd:
            print('登录成功')
            break
    else:
        print('登录失败')
login()
# register函数
def register():
    username = input('请输入用户名:')
    passwd = input('请输入密码:')
    for user,pwd in get_user_generator('userinfo'):
        if username == user or pwd ==passwd :
            username = input('用户名已存在!')
            break
    else:
        set_user_generator('userinfo', username, passwd)
        print('注册成功!')

register()

#生成器理解题

def func():
    for i in range(5):
        yield '%s个数'%i
g = func()
for i in g:
    print(i)

for j in g:
    print(j)
# 这种情况只会打印一次,原因生成器也是迭代器,而迭代器的特点是一个迭代器内的数据只能从头到尾取一次,就相当于只签一次合同

for k in func():
    print(k)

for o in func():
    print(o)
#这种情况就会打印两次,因为循环了两个生成器,就相当于签了一次合同

g1=g2=func()
for k in g1:
    print(k)

for o in g2:
    print(o)
# 这种情况也只会打印一次,只循环了一个生成器

三、各种推导式

1、列表推导式

在一行内完成一个新列表的组件

# 让l列表内的数据乘以2,输出一个新列表l2
l=[1,2,3,4,5]
l2=[i*2 for i in l]  #循环l列表内的i,然后i*2形成一个新的列表
print(l2) #[2, 4, 6, 8, 10]
# 30以内所有能被3整除的数
l = [i for i in range(30) if i%3==0]
print(l)
#30以内所有能被3整除的数的平方
l2 = [i*i for i in range(30) if i%3==0]
print(l2)
# 找到嵌套列表中名字含有两个e的所有名字
name = [['tom','alese','kkdee'],['rtyui','rtyuiyee','oooooppp']]
# # 常规写法
ls=[]
'''
思路:先循环name列表,lst是每一个单独的列表(['tom','alese','kkdee'])
然后在循环lst去除列表内的值i('tom','alese','kkdee')
然后i.count('e')==2去判断i内的值包含2个e的,i.count('e') 是python中计算某一个值的数量
'''
for lst in name:
    for i in lst:
        if i.count('e')==2:
            ls.append(i)
print(ls)
# 列表推导式写法
l2=[i for lst in name for i in lst if i.count('e')==2]
print(l2)

比较简单的可以用列表推导式,复杂的就用常规的方法写就可以了

2、字典推导式  3、集合推导式 ===》这两个推导式只了解即可

4、生成器表达式

列表推导式的[]换成()就变成了生成器稳定式

区别:列表推导式内的元素,只要取了就会一次性的把列表中的元素放到内存中

生成器表达式,只有在取的时候才会把元素放到内存中

#列表推导式
lst = [i*2 for i in range(5)]
print(lst) #[0, 2, 4, 6, 8]

#生成器表达式
gen = (i*2 for i in range(5))
print(gen.__next__()) #0
print(gen) #<generator object <genexpr> at 0x101982660>
for i in gen:
    print(i) #2, 4, 6, 8
list(gen) # null

'''从上面两则执行的结果可以看出:
    1、lst列表一次性就把所有数据都放到内存中,展示出来
    2、gen在__next__取了第一个以后,在for循环取,第一个因为取完了所以就取不到了
    3、把gen转换成list,取的值为空,因为生成器之前都取完了,所以后面在取就没有了
  ----》标黄的内容是迭代器取值的三种方式
'''

四、三元运算符

a=10
b=15
# max_num=0
max_um = a if a> b else b
print(max_um)

五、匿名函数

lambda修饰的函数就叫匿名函数

#lambda表达式,比较a,b两个值的大小,求比较大的值
compare=lambda a,b: a if a>b else b
print(compare(5,2))

 

posted @ 2022-04-14 18:13  尘封~~  阅读(36)  评论(0编辑  收藏  举报