>  기사  >  백엔드 개발  >  Python의 데코레이터, 반복자 및 생성기의 예에 대한 자세한 설명

Python의 데코레이터, 반복자 및 생성기의 예에 대한 자세한 설명

黄舟
黄舟원래의
2017-07-26 15:45:02923검색

아래 편집기는 Python의 데코레이터, 반복자 및 생성기에 대한 진부한 표현을 제공합니다. 편집자님이 꽤 좋다고 생각하셔서 지금 공유하고 모두에게 참고용으로 드리고자 합니다. 에디터를 따라가서 살펴볼까요

파이썬을 배울 때, 세 가지 "유명한 도구"는 다른 언어 프로그래밍 경험이 없는 사람들에게는 작은 어려움으로 간주되어야 합니다. 이 블로그는 블로거 자신의 데코레이터에 대한 이해에 중점을 둘 것입니다. 반복자와 생성기에 대한 이해가 설명됩니다.

데코레이터를 사용하는 이유

데코레이터란 무엇인가요? "데코레이션(Decoration)"은 말 그대로 특정 건물을 특정 아이디어와 스타일에 따라 아름답게 꾸미는 행위를 의미한다. 소위 "디바이스(device)"란 파이썬의 경우, 원본 코드를 수정하지 않고도 사용할 수 있는 방식을 말한다. 예를 들어, 소프트웨어가 온라인 상태가 된 후에는 소스 코드나 호출 방식을 수정하지 않고 주기적으로 새 기능을 추가해야 합니다. 이를 달성하기 위해 코드를 작성할 때도 마찬가지입니다. 우리는 또한 나중에 확장성을 고려해야 합니다. Python의 데코레이터를 단계별로 살펴보겠습니다.

매개변수가 없는 데코레이터를 소개하는 간단한 예

먼저 몇 줄의 간단한 코드를 살펴보겠습니다. 코드의 결과는 2초 동안 대기한 다음 "hello boy!"를 인쇄하는 것입니다.


import time
def foo():
 """打印"""
 time.sleep(2)
 print("Hello boy!")
foo()

us 이제 원본 코드를 수정하지 않고 프로그램 타이밍 기능을 추가해야 합니다.


import time
def timmer(func):
 def wrapper():
  """计时功能"""
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
 return wrapper
def foo():
 """打印"""
 time.sleep(2)
 print("Hello boy!")
foo=timmer(foo)
foo()
#运行结果
Hello boy!
Run time is 2.000446

보세요! 함수도 객체이기 때문에 함수 foo를 함수 타이머에 매개변수로 전달할 수 있습니다.

파이썬에서는 @timmer를 사용하여 foo=timmer(foo)를 대체하는 더 간결한 방법이 있습니다. 이를 파이썬에서는 구문 설탕이라고 합니다.


import time
def timmer(func):
 def wrapper():
  """计时功能"""
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
 return wrapper
@timmer  #等于 foo=timmer(foo)
def foo():
 """打印"""
 time.sleep(2)
 print("Hello boy!")
foo()

함수의 실행 과정을 단계별로 분석해 보겠습니다.

1. 시간 모듈 가져오기


import time

2 함수를 정의하면 함수의 코드가 실행되지 않습니다.


def timmer(func):

3. 데코레이터를 호출하는 것은 foo=timer(foo)와 같습니다. 이는 foo 함수를 매개변수로 timmer


@timmer

4에 전달하는 것을 의미합니다. =foo


def timmer(func):

5. 함수 timmer에 함수 래퍼가 정의되어 있고 래퍼 함수 내부의 코드가 실행되지 않은 후 함수 래퍼가 반환 값


return wrapper

6으로 반환됩니다. 반환 값은 foo에 할당됩니다. 3단계 foo=timmer(foo)에서는


@timmer #等于 foo=timmer(foo)

7을 기억하세요. foo() 함수를 실행하세요. 하지만 여기의 함수는 더 이상 원래 함수가 아닙니다. 래퍼가 반환 값으로 foo에 전달되었기 때문에 foo를 인쇄할 수 있습니다. 따라서 여기서 foo를 실행하면 래퍼가 실행됩니다. 따라서 래퍼도 인쇄할 수 있습니다. 모두 동일한 주소 공간을 가리킵니다.


<function timmer.<locals>.wrapper at 0x00000180E0A8A950> #打印foo的结果
<function timmer.<locals>.wrapper at 0x000001F10AD8A950> #打印wrapper的结果
foo()

8. 함수 래퍼를 실행하고, 시작 시간을 기록하고, func 함수를 실행합니다. 4단계에서 func는 foo에 의해 값이 할당됩니다. 원래 함수 foo는 2초 동안 잠을 자고 문자열을 인쇄합니다.


time_start=time.time()
 time.sleep(2)
 print("Hello boy!")

9. 종료 시간을 기록하고 실행 시간을 인쇄하면 프로그램이 종료됩니다.


Hello boy!
Run time is 2.000161

매개변수가 있는 데코레이터

앞의 예에서 원본 함수에 매개변수가 없을 때 데코레이터 함수를 수정하는 방법을 살펴보겠습니다.


import time
def timmer(func):
 def wrapper(*args,**kwargs):
  """计时功能"""
  start_time=time.time()
  res=func(*args,**kwargs)
  end_time=time.time()
  print("Run time is %f"%(end_time-start_time))
  return res
 return wrapper
@timmer 
def my_max(x,y):
 """返回两个值的最大值"""
 res=x if x > y else y
 time.sleep(2)
 return res
res=my_max(1,2)
print(res)
#运行结果
Run time is 2.000175

원래 함수가 매개변수를 전달해야 하는 경우 이 예에서는 my_max에 전달해야 하는 두 개의 위치가 있습니다. 래퍼에 두 개의 형식 매개변수만 추가하면 됩니다. 이 예에서는 가변 매개변수가 사용됩니다. (*args, **kwargs)도 가능합니다. 이는 my_max(1,2)=timmer(my_max)

매개변수가 있는 데코레이터를 살펴보겠습니다.

def auth(filetype):
 def auth2(func):
  def wrapper(*args,**kwargs):
   if filetype == "file":
    username=input("Please input your username:")
    passwd=input("Please input your password:")
    if passwd == &#39;123456&#39; and username == &#39;Frank&#39;:
     print("Login successful")
     func()
    else:
     print("login error!")
   if filetype == &#39;SQL&#39;:
    print("No SQL")
  return wrapper
 return auth2
@auth(filetype=&#39;file&#39;) #先先返回一个auth2 ==》@auth2 ==》 index=auth2(index) ==》 index=wrapper
def index():
 print("Welcome to China")
index()

If 데코레이터 자체에는 매개변수가 있으므로 실행 프로세스를 단계별로 분석해 보겠습니다.

def auth(filetype):

2. (filetype='file')


@auth(filetype=&#39;file&#39;)

3. auth 함수를 실행하고 auth2 함수를 정의한 후 이를 반환 값으로 반환하면 이 @auth(filetype='file')은 @auth2와 동일합니다. index=auth2(index)



def auth(filetype):
 def auth2(func):
  def wrapper(*args,**kwargs):
  return wrapper
 return auth2

4.auth2(index), func=index에서 Execute와 동일하며, 함수 래퍼를 정의하고 반환합니다. 이때 index는 실제로 래퍼


와 같습니다.

def wrapper(*args,**kwargs):
return wrapper

5. index 실행, 즉 래퍼 실행 시 filetype=="file" 함수의 내부 코드를 실행하고 사용자 이름과 비밀번호를 출력하라는 메시지를 표시하고 입력이 올바른지 확인합니다. , 원본 인덱스를 실행하고 인쇄하는 것과 동일한 func() 함수를 실행합니다.


if filetype == "file":
    username=input("Please input your username:")
    passwd=input("Please input your password:")
    if passwd == &#39;123456&#39; and username == &#39;Frank&#39;:
     print("Login successful")
     func()

6 결과 테스트 실행


Please input your username:Frank
Please input your password:123456
Login successful
Welcome to China

装饰器也是可以被叠加的:


import time
#
def timmer(func):
 def wrapper():
  """计时功能"""
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
  # print("---",wrapper)
 return wrapper
def auth(filetype):
 def auth2(func):
  def wrapper(*args,**kwargs):
   if filetype == "file":
    username=input("Please input your username:")
    passwd=input("Please input your password:")
    if passwd == &#39;123456&#39; and username == &#39;Frank&#39;:
     print("Login successful")
     func()
    else:
     print("login error!")
   if filetype == 'SQL':
    print("No SQL")
  return wrapper
 return auth2
@timmer
@auth(filetype=&#39;file&#39;) #先先返回一个auth2 ==》@auth2 ==》 index=auth2() ==》 index=wrapper
def index():
 print("Welcome to China")
index()

#测试结果
Please input your username:Frank
Please input your password:123456
Login successful
Welcome to China
Run time is 7.966267

注释优化


import time
def timmer(func):
 def wrapper():
  """计算程序运行时间"""
  start_time=time.time()
  func()
  end_time=time.time()
  print("Run time is %s:"%(end_time-start_time))
 return wrapper
@timmer
def my_index():
 """打印欢迎"""
 time.sleep(1)
 print("Welcome to China!")
my_index()
print(my_index.__doc__)

#运行结果
Welcome to China!
Run time is 1.0005640983581543:
计算程序运行时间

当我们使用了装饰器的时候,虽然没有修改代码本身,但是在运行的时候,比如上面这个例子,运行my_index其实在运行wrapper了,如果我们打印my_index的注释信息,会打印wrapper()的注释信息,那么该怎么优化?

可以在模块functools中导入wraps,具体见以下:


import time
from functools import wraps
def timmer(func):
 @wraps(func)
 def wrapper():
  """计算程序运行时间"""
  start_time=time.time()
  func()
  end_time=time.time()
  print("Run time is %s:"%(end_time-start_time))
 return wrapper
@timmer
def my_index():
 """打印欢迎"""
 time.sleep(1)
 print("Welcome to China!")
my_index()
print(my_index.__doc__)
#运行结果
Welcome to China!
Run time is 1.0003223419189453:
打印欢迎

这样,在表面看来,原函数没有发生任何变化。

为什么要用迭代器

从字面意思,迭代就是重复反馈过程的活动,其目的通常是为了比较所需目标或结果,在python中可以用迭代器来实现,先来描述一下迭代器的优缺点,如果看不懂可以先略过,等看完本博客再回头看,相信你会理解其中的意思:

优点:

迭代器在取值的时候是不依赖于索引的,这样就可以遍历那些没有索引的对象,比如字典和文件

迭代器与列表相比,迭代器是惰性计算,更节省内存

缺点:

无法获取迭代器的长度,没有列表灵活

只能往后取值,不能倒着取值

什么是迭代器

那么在python什么才算是迭代器呢?

只要对象有__iter__(),那么它就是可迭代的,迭代器可以使用函数next()来取值

下面我们来看一个简单的迭代器:


my_list=[1,2,3]
li=iter(my_list)  #li=my_list.__iter__()
print(li)
print(next(li))
print(next(li))
print(next(li))
#运行结果
<list_iterator object at 0x000002591652C470>
2

可以看到,使用内置函数iter可以将列表转换成一个列表迭代器,使用next()获取值,一次值取一个值,当值取完了,再使用一次next()的时候,会报异常StopIteration,可以通过异常处理的方式来避免,try-except-else就是一个最常用的异常处理结构:


my_list=[1,2,3]
li=iter(my_list)
while True:
 try:
  print(next(li))
 except StopIteration:
  print("Over")
  break
 else:
  print("get!")
#运行结果
get!
get!
get!
Over

查看可迭代对象和迭代器对象

使用Iterable模块可以判断对象是否是可迭代的:


from collections import Iterable
s="hello" #定义字符串
l=[1,2,3,4] #定义列表
t=(1,2,3) #定义元组
d={&#39;a&#39;:1} #定义字典
set1={1,2,3,4} #定义集合
f=open("a.txt") #定义文本
# 查看是否都是可迭代的
print(isinstance(s,Iterable))
print(isinstance(l,Iterable))
print(isinstance(t,Iterable))
print(isinstance(d,Iterable))
print(isinstance(set1,Iterable))
print(isinstance(f,Iterable))
#运行结果
True
True
True
True
True
True

通过判断,可以确定我们所知道的常用的数据类型都是可以被迭代的。

使用Iterator模块可以判断对象是否是迭代器:


from collections import Iterable,Iterator
s="hello"
l=[1,2,3,4]
t=(1,2,3)
d={&#39;a&#39;:1}
set1={1,2,3,4}
f=open("a.txt")
# 查看是否都是可迭代的
print(isinstance(s,Iterator))
print(isinstance(l,Iterator))
print(isinstance(t,Iterator))
print(isinstance(d,Iterator))
print(isinstance(set1,Iterator))
print(isinstance(f,Iterator))
#运行结果
False
False
False
False
False
True

可知只有文件是迭代器,所以可以直接使用next(),而不需要转换成迭代器。

什么是生成器

生产器就是一个是带有yield的函数

下面来看一个简单的生成器


def my_yield():
 print(&#39;first&#39;)
 yield 1
g=my_yield()
print(g)
#运行结果
<generator object my_yield at 0x0000024366D7E258>

生成器也是一个迭代器


from collections import Iterator
def my_yield():
 print(&#39;first&#39;)
 yield 1
g=my_yield()
print(isinstance(g,Iterator))
#运行结果
True

那就可以用next()来取值了


print(next(g))
#运行结果
first
1

生成器的执行过程

我们来看以下下面这个例子,了解生产的执行流程


def my_yield():
 print(&#39;first&#39;)
 yield 1
 print(&#39;second&#39;)
 yield 2
 print(&#39;Third&#39;)
 yield 3
g=my_yield()
next(g)
next(g)
next(g)
#运行结果
first
second
Third

1.定义生成器my_yield,并将其赋值给了g


def my_yield():
g=my_yield()

2.开始第一次执行next(),开始执行生产器函数 ,打印第一语句,遇到yileld的时候暂停,并返回一个1,如果你想打印返回值的话,这里会显示1


 print(&#39;first&#39;)
 yield 1

3.再执行2次,打印字符串(每执行一次都会暂停一下)


 print(&#39;second&#39;)
 yield 2
 print(&#39;Third&#39;)
 yield 3

4.如果再加一次next()就会报出StopIteration异常了

生成器在每次暂停的时候,函数的状态将被保存下来,来看下面的例子:


def foo():
 i=0
 while True:
  yield i
  i+=1
g=foo()
for num in g:
 if num < 10:
  print(num)
 else:
  break
#运行结果

for循环中隐含next(),每next一次,暂停一次,if语句判断一次,然后执行下一次next,可以看到我们的while循环并没有无限循环下去,而是状态被保存下来了。

协程函数

我们来看下面这个生成器和执行结果


def eater(name):
 print(&#39;%s start to eat food&#39;%name)
 while True:
  food=yield
  print(&#39;%s get %s ,to start eat&#39;%(name,food))
 print(&#39;done&#39;)
e=eater(&#39;Frank&#39;)
next(e)
e.send(&#39;egg&#39;) #给yield送一个值,并继续执行代码
e.send(&#39;tomato&#39;)
#运行结果
Frank start to eat food
Frank get egg ,to start eat
Frank get tomato ,to start eat

send可直接以向yield传值,含有yield表达式的函数我们也称为协程函数,

这运行程序的时候,不可以直接send,必须先使用next()初始化生成器。

如果存在多个这样的函数,那么我们每次执行的时候都要去next()一下,为了防止忘记这一步操作,可以使用装饰器初始化:


def init(func):
 def wrapper(*args):
  res = func(*args)
  next(res)  # 在这里执行next
  return res
 return wrapper
@init
def eater(name):
 print(&#39;%s start to eat food&#39;%name)
 while True:
  food=yield
  print(&#39;%s get %s ,to start eat&#39;%(name,food))
 print(&#39;done&#39;)
e=eater(&#39;Frank&#39;)
e.send(&#39;egg&#39;) 
e.send(&#39;tomato&#39;)

所以在程序中有更多的生成器需要初始化的时候,直接调用这个装饰器就可以了。

위 내용은 Python의 데코레이터, 반복자 및 생성기의 예에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.