"""
#####################################################
##########                              #############
##########        1.0__装饰器----总结     #############
##########                              #############
#####################################################
"""
"""
虽然装饰器分为无参装饰器和有参装饰器,

但是原理一样的,就是函数嵌套 + 闭包函数 +函数对象组成

首先说一下:无参装饰器 函数添加统计其执行时间的功能

#########################       1.0无参装饰器的实现       ########################
import time
def index():
time.sleep(3)
print("welcome  to the index page")
return 200

# 遵循不修改被装饰对象 源代码的原则
start=time.time()
index()
stop=time.time()
print("run time is %s "%(stop-start))




import time

def  index():
time .sleep(3)
print("welcome  to the index page")
return 200

# 闭包函数
def  wrapper(func):#通过参数接收外部的值
start = time.time()
res=func()
stop = time.time()
print("run time is %s " % (stop - start))
return res

# 但是这种方法:违反了不能修改被装饰对象调用方式的原则所以需要换一种函数传参方式
wrapper(index)




import time

def  index():
time .sleep(3)
print("welcome  to the index page")
return 200

def  home(name):
time.sleep(2)
print('Welcome to the home page',name)

def timer(func):
def  wrapper(*args,**kwargs):#通过参数接收外部的值
start = time.time()
res=func(*args,**kwargs)
stop = time.time()
print("run time is %s " % (stop - start))
return res
return wrapper
# 这样我们便可以在不修改被装饰函数源代码和调用方式的前提下为其加上统计时间的功能,
# 只不过需要事先执行一次timer将被装饰的函数传入,
# 返回一个闭包函数wrapper重新赋值给变量名 /函数名index,如下

index=timer(index)
home=timer(home)
home('kwx')
index()


# 接下来是语法糖:Python提供了专门的装饰器语法来取代index=timer(index)的形式,
需要在被装饰对象的正上方单独一行添加@timer,当解释器解释到@timer时就会调用timer函数,
且把它正下方的函数名当做实参传入,然后将返回的结果重新赋值给原函数名

import time


def timer(func):
def  wrapper(*args,**kwargs):#通过参数接收外部的值
start = time.time()
res=func(*args,**kwargs)
stop = time.time()
print("run time is %s " % (stop - start))
return res
return wrapper

@timer   #index = timer(index)
def  index():
time .sleep(3)
print("welcome  to the index page")
return 200
@timer    #home = timer(home)
def  home(name):
time.sleep(2)
print('Welcome to the home page',name)


index()#只运行代码

home("kwx")

res=index()# 返回返回值
print(res)


"""

##########################       2.0  有参装饰器的实现            ######################
"""
从无参装饰器的实现原理后,我们可以再实现一个用来为被装饰对象
添加认证功能的装饰器,实现的基本形式如下:

"""
#无 参 装 饰 器 模 板:
"""
def deco(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
return res
return wrapper

"""

# 函数wrapper需要一个driver参数,
# 而函数deco与wrapper的参数都有其特定的功能,不能用来接受其他类别的参数,
# 可以在deco的外部再包一层函数auth,用来专门接受额外的参数,这样便保证了在auth函数内无论多少层都可以引用到



# 另一方面,也是由于语法糖 的原因 @deco(index)  只能放一个参数所以我们需要在
"""
def auth(需要的参数):
# 需要的参数=xxx
def deco(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
return res
return wrapper
return deco

"""

"""

# 此时我们实现了一个有参装饰器,使用方式如下:

def auth(driver):
# 需要的参数=xxx
def deco(func):
def wrapper(*args,**kwargs):
if driver =="file":
res=func(*args,**kwargs)
return res
return wrapper
return deco

#    先调用auth(driver='file'),得到@deco,deco是一个闭包函数,
# 包含了对外部作用域名字driver的引用,@deco的语法意义与无参装饰器一样

@auth(driver="file")
def index():
'''123'''
pass
@auth(driver="file")
def home():
'''456'''
pass

#   可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性,
# 但对于被装饰之后的函数,查看文档注释
# print(help(index))


"""

"""

#  案例一:
import time

def timer(func):
def  wrapper(*args,**kwargs):#通过参数接收外部的值
start = time.time()
res=func(*args,**kwargs)
stop = time.time()
print("run time is %s " % (stop - start))
return res
return wrapper

@timer
def home(name):
'''
home page function
:param name: str
:return: None
'''
time.sleep(5)
print('Welcome to the home page',name)

print(help(home))

"""
'''
输出结果:

Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

None


由此可知:
在被装饰之后home=wrapper,查看home.name也可以发现home的函数名确实是wrapper,
所以想要保留原函数的文档和函数名属性,需要修正装饰器!!!!!

'''

"""
# 优化一
import time


def timer(func):
def  wrapper(*args,**kwargs):#通过参数接收外部的值
start = time.time()
res=func(*args,**kwargs)
stop = time.time()
print("run time is %s " % (stop - start))
return res
wrapper.__doc__  = func.__doc__
wrapper.__name__ = func.__name__
return wrapper

@timer
def home(name):
'''
home page function
:param name: str
:return: None
'''
time.sleep(5)
print('Welcome to the home page',name)

print(help(home))

"""

"""
输出结果:

Help on function home in module __main__:

home(*args, **kwargs)
home page function
:param name: str
:return: None

None

"""

"""
按照上述方式来实现保留原函数属性过于麻烦,
python中存在functools模块下提供一个装饰器wraps专门用来帮我们实现这件事,
用法如下:


from functools import wraps
import time

def timer(func):
@wraps(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


@timer
def home(name):
'''
home page function
:param name: str
:return: None
'''
time.sleep(5)
print('Welcome to the home page',name)

print(help(home))
"""



"""
#####################################################
##########                              #############
##########        2.0__装饰器----总结     #############
##########                              #############
#####################################################
"""







"""
一 迭代器介绍

迭代器即用来迭代取值的工具,而迭代是重复反馈过程的活动,
其目的通常是为了逼近所需的目标或结果,每一次对过程的重复称为一次“迭代”,
而每一次迭代得到的结果会作为下一次迭代的初始值,


!!!!单纯的重复并不是迭代!!!!
例:
while True:
msg=input(">>:").strip()
print(msg)

!!!!迭代过程!!!!

goods=['mac','lenovo','acer','dell','sony']
index=0
while index < len(goods):
print(goods[index])
index+=1

这才是一个迭代过程,
不仅满足重复,
而且以每次重新赋值后的index值作为下一次循环中新的索引进行取值,
反复迭代,最终可以取尽列表中的值

"""

"""

二,可迭代对象与迭代器对象详解

2.1可迭代对象("可以转换成迭代器的对象"):从语法形式-->>内置有__iter__方法对象都是可迭代对象
可迭代对象(iterable).__iter__():   得到迭代器对象(如果不知到是不是迭代对象可以自己试)
其中可迭代对象有: 字符串、列表、元组、字典、集合、打开的文件

{'name':'egon'}.__iter__

{7,8,9}.__iter__



2.2迭代器对象:内置有__next__方法并且内置有__iter__方法的对象
迭代器对象.__next__():会得到迭代器的下一个值
迭代器对象.__iter__():会得到迭代器的本身,(调了跟没调一个样子)
#######举例1.0################
s = {1, 2, 3} #可迭代对象
i = iter(s) #本质就是在调用s.__iter__(),返回s的迭代对象

r1=next(i)  #本质是调用i.__next__()
r2=next(i)
r3=next(i)
r4=next(i)
print(r1)
print(r2)
print(r3)
# print(r4) # #抛出StopIteration的异常,代表 值已经取尽,无值可取,迭代结束


三 迭代器的优缺点
基于索引的迭代取值,所有迭代的状态都保存在了索引中,而基于迭代器实现迭代的方式不再需要索引,
所有迭代的状态就保存在迭代器中,然而这种处理方式优点与缺点并存:
3.1 优点:
1、为序列和非序列类型提供了一种统一的迭代取值方式。
2、惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值,就迭代器本身来说,
同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,
如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。

3.2 缺点:
1、除非取尽,否则无法获取迭代器的长度
2、只能取下一个值,不能回到开始,更像是‘一次性的’,迭代器产生后的唯一目标就是重复执行next方法直到值取尽,
否则就会停留在某个位置,等待下一次调用next;若是要再次迭代同个对象,你只能重新调用iter方法去创建一个新的迭代器对象,
如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。

"""


"""
#####################################################
##########                              #############
##########        3.0__生成器----总结     #############
##########                              #############
#####################################################
"""

"""
一 生成器与yield:
若函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值即生成器对象
"""

'''
def my_range(start,stop,step=1):
print("start...")
while start < stop:
yield start
start+=step
print("end...")

g=my_range(0,3)
# print(g) #<generator object my_range at 0x00000259DA125EC8>

# 生成器内置有__iter__和__next__方法,所以生成器本身就是一个迭代器

# print(g.__iter__)
#输出结果:  <method-wrapper '__iter__' of generator object at 0x00000259DA125EC8>

# print(g.__next__)
#输出结果:  <method-wrapper '__next__' of generator object at 0x0000018321235EC8>


# 因而我们可以用next(生成器)触发生成器所对应函数的执行,

print(next(g))
## 触发函数执行直到遇到yield则停止,将yield后的值返回,并在当前位置挂起函数
"""
输出结果:
start...
0
"""

print(next(g))
# 再次调用next(g),函数从上次暂停的位置继续执行,直到重新遇到yield...
"""
输出结果:
1
"""

print(next(g))
# 再一次调用next(g),函数从上次暂停的位置继续执行,直到重新遇到yield...
"""
输出结果:
2
"""

print(next(g))
# 触发函数执行没有遇到yield则无值返回,即取值完毕抛出异常结束迭代
"""
输出结果:
end...
File "G:/2020老男孩全栈/练习/装饰器 迭代器 生成器.py", line 461, in <module>
print(next(g))
StopIteration

"""



# 既然生成器对象属于迭代器,那么必然可以使用for循环迭代,如下:
举例说明:
def my_range(start,stop,step=1):
while start < stop:
yield start
start+=step

#执行函数得到生成器,本质就是迭代器
obj=my_range(1,7,2) #1  3  5
print(next(obj))
print(next(obj))
print(next(obj))
print(next(obj)) #StopIteration

#应用于for循环
for i in my_range(1,7,2):
print(i)

有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,
但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值



二,yield表达式应用
在函数内可以采用表达式形式的yield
def eater():
print('Ready to eat')
while True:
food=yield
print('get the food: %s, and start to eat' %food)

#可以拿到函数的生成器对象持续为函数体send值,如下:

g=eater()
print(g)  # 输出结果: <generator object eater at 0x000001DA4F7C5EC8>


next(g)   # 需要事先”初始化”一次,让函数挂起在food=yield,等待调用g.send()方法为其传值

# 然后在进行输入值
g.send("一个骨头")
g.send('鸡腿')

"""

针对表达式形式的yield,
生成器对象必须事先被初始化一次,让函数挂起在food=yield的位置,
等待调用g.send()方法为函数体传值,g.send(None)等同于next(g)。

"""
# !!!我们可以编写装饰器来完成为所有表达式形式yield对应生成器的初始化操作,如下:!!!

def init(func):
def wrapper(*args,**kwargs):
g=func(*args,**kwargs)
next(g)
return g
return wrapper

@init
def eater():
# print('Ready to eat')
while True:
food=yield
print('get the food: %s, and start to eat' %food)


g=eater()
g.send("狗腿") #用于给food传参


# 表达式形式的yield也可以用于返回多次值,即变量名=yield 值的形式,如下:
# !!!! 变量名 = yield  + 值 !!!!

def eater():
print("Ready to eat")
food_list=[]
while True:
food=yield food_list
food_list.append(food)

e=eater()
next(e)
e.send("骨头")
e.send("吃鸡")
e.send('蒸熊掌')
e.send('蒸羊羔')
print(e.send('蒸鹿尾儿'))

'''