ホームページ > バックエンド開発 > Python チュートリアル > Pythonのデコレータ、イテレータ、ジェネレータの例を詳しく解説

Pythonのデコレータ、イテレータ、ジェネレータの例を詳しく解説

黄舟
リリース: 2017-07-26 15:45:02
オリジナル
927 人が閲覧しました

以下のエディターは、Python のデコレーター、イテレーター、ジェネレーターに関する決まり文句を提供します。編集者はこれがとても良いと思ったので、参考として共有します。エディターをフォローして見てみましょう

Python を学習するとき、他の言語でのプログラミングの経験がない人にとって、3 つの「有名なツール」は少し難しいと考えるべきです。このブログでは、ブロガー自身のデコレーターの理解に焦点を当てます。イテレータとジェネレータについて説明します。

デコレータを使用する理由

デコレータとは何ですか? 「装飾」とは文字通り、特定のアイデアやスタイルに従って特定の建物を美しくする行為を意味し、Python の場合、デコレータは元のコードを変更せずに使用できるツールです。たとえば、ソフトウェアがオンラインになった後は、ソース コードやその呼び出し方法を変更せずに、新しい関数を定期的に追加する必要があります。これを実現するには、コードを作成するときにも同様です。後のスケーラビリティも考慮する必要があります。Python のデコレータを段階的に見てみましょう。

簡単な例では、パラメーターを使用しないデコレータを紹介します

まず、数行の簡単なコードを見てみましょう。コードの結果は、2 秒間スリープしてから、「helloboy!」を出力することになります。

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 にパラメータとして渡すことができます。

Python では、@timmer を使用して foo=timmer(foo) を置き換えるより簡潔な方法があります。これは、Python ではシュガー構文と呼ばれます。

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. 関数 timmer を定義しても、関数内のコードは実行されません。

def timmer(func):
ログイン後にコピー
ログイン後にコピー

3. デコレーターの呼び出しは foo=timer(foo) と同等です。これは、関数 foo をパラメーターとして関数 timmer

@timmer
ログイン後にコピー

4 に渡すことを意味します。 =foo

def timmer(func):
ログイン後にコピー
ログイン後にコピー

5. 関数timmerでは、関数ラッパーが定義されており、ラッパー関数内のコードは実行されず、関数ラッパーが戻り値として返されます

return wrapper
ログイン後にコピー

6。戻り値は 3 番目のステップで foo に代入されます。ステップでは、foo=timmer(foo)、関数 foo() を実行しますが、ここでの関数は元の関数ではなくなります。 foo を出力できます。ラッパーが戻り値として foo に渡されるため、ここで foo を実行するとラッパーが実行されます。これを確認するには、ラッパーのメモリ アドレスが同じであることを確認します。これらはすべて同じアドレス空間を指します:

@timmer #等于 foo=timmer(foo)
ログイン後にコピー

8. 関数ラッパーを実行し、開始時刻を記録し、関数 func を実行します。 ステップ 4 では、 func の実行中に foo によって値が割り当てられます。オリジナル関数 foo、2 秒間スリープし、文字列を出力します。

<function timmer.<locals>.wrapper at 0x00000180E0A8A950> #打印foo的结果
<function timmer.<locals>.wrapper at 0x000001F10AD8A950> #打印wrapper的结果
foo()
ログイン後にコピー

9. 終了時間を記録し、実行時間を出力し、プログラムが終了します。

time_start=time.time()
 time.sleep(2)
 print("Hello boy!")
ログイン後にコピー

パラメータ付きデコレータ


前の例では、元の関数にパラメータがない場合に、デコレータ関数を変更する方法を見てみましょう。

Hello boy!
Run time is 2.000161
ログイン後にコピー
元の関数がパラメーターを渡す必要がある場合、この例では my_max に渡す必要がある位置が 2 つあります。ラッパーに追加する必要があるのは 2 つの仮パラメーターのみです。この例では、変数パラメーターが使用されています。 (*args, **kwargs) も可能です。これは、my_max(1,2)=timmer(my_max) と等しい @timmer です。

パラメータを持つデコレータを見てみましょう:


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
ログイン後にコピー

Ifデコレータ自体にはパラメータがあるため、追加の組み込み関数の層が必要です。実行プロセスを段階的に分析してみましょう。 1. 関数 auth

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()
ログイン後にコピー

を定義します。2. インタプリタを呼び出すには、まず を実行する必要があります。 function auth(filetype='file')

def auth(filetype):
ログイン後にコピー

3. 関数 auth を実行し、関数 auth2 を定義して戻り値として返すと、この @auth(filetype='file') は @ と同等になります。 auth2、index=auth2(index)で実行


@auth(filetype=&#39;file&#39;)
ログイン後にコピー

4.auth2(index)、func=index、関数wrapperを定義して返す この時、実際にはindexはwrapper


と等しい。

def auth(filetype):
 def auth2(func):
  def wrapper(*args,**kwargs):
  return wrapper
 return auth2
ログイン後にコピー

5. インデックスを実行するとき、つまりラッパーを実行するときに、関数の内部コード filetype=="file" を実行し、ユーザーにユーザー名とパスワードの出力を求め、入力が正しいかどうかを判断します。正しい場合は、関数 func() を実行します。これは、元のインデックスを実行して


def wrapper(*args,**kwargs):
return wrapper
ログイン後にコピー

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 == '123456' and username == 'Frank':
     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 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート