はじめに
エキスパートPythonプログラミング.第二章で学んだ、イテレータ、ジェネレータ、デコレータを備忘録としてまとめる。
イテレータとは
-
イテレータとは単にイテレータプロトコルを実装したコンテナオブジェクトで、要素を一つずつ取り出すことのできるオブジェクト
-
使われ方として、リストやタプル、文字列などのシーケンスオブジェクトをイテレータオブジェクトに渡し、イテレータオブジェトから要素を取得していく
-
イテレータプロトコルとは
__next()__
と__iter__()
に該当-
__next()__
:コンテナの次の要素を返すメソッド -
__iter__()
:イテレータ自身を返すメソッド
-
-
イテレータの作成と使い方
- イテレータプロトコルである2つの特殊メソッドを記述
- シーケンスの要素をすべて取り出すとStopIteratio例外が発生
- カスタムイテレータ(要素の取り出し方)を作成するには
__next()__
メソッドに記述
# シーケンス内の最後の要素から取り出すイテレータ
class CountSequenceFromTail(object):
def __init__(self,*args):
self.args = args
self.step = len(args)-1
def __iter__(self):
return self
def __next__(self):
if self.step < 0:
raise StopIteration
value = self.args[self.step]
self.step -= 1
return value
iterator = CountSequenceFromTail(1,2,3,4,5,6,7,8,9,10)
for value in iterator:
print(value)
# 以下結果
10
9
8
7
6
5
4
3
2
1
ジェネレータとは
-
ジェネレータとは、イテレータを簡単に作るための手段であり、要素取得の仕様・方法のことを指す
-
ジェネレータは,データを処理する外部コードによって,一時停止,再開,停止させられる
-
yield文を使用して、関数を一時的に停止させ,途中経過の結果を返す.再度,yield文を実行した関数をnext()の引数に渡すことで,再開される.
-
ジェネレータのメリット
- next()関数呼び出し,あるいはforループ文を使用して,イテレータと同様にジェネレータから値を取得可能
- ジェネレータは回数無制限で呼び出すことが可能
- 構文が簡潔
- 無限に続く特性を持つアルゴリズムを実装する場合にも,コードがよみにくいことはなく,また,関数を停止させる処理を実装する必要がない
- 1つずつ要素を返すことで,その要素を使用する他の関数へ渡す場合に全体のパフォーマンスを向上させる
- 少ないリソースで,より効率的に処理が行える
-
ループ処理やシーケンスを返す関数を実装するときには,まずジェネレータの検討をすべきらしい
以下、実装
def fibnacci():
a,b = 0,1
while True:
yield b
a,b = b,a+b
fib = fibnacci()
[print(next(fib)) for i in range(10)]
# 以下、結果
1
1
2
3
5
8
13
21
34
55
デコレータについて
デコレータは,関数やメソッドのラッピング処理の見た目をわかりやすくするために導入された.
クラスや関数などでデコレータを定義した上で,既存関数にデコレータを加えることにより,拡張処理(ラッピング処理)ができる.
(拡張された処理=既存の関数の処理+デコレータとして定義された関数の処理)
デコレータとして使用できるのは,一般的に,1つの引数を受け取れる名前付きの呼び出し可能な(callable)オブジェクト
callableオブジェクトとしては、クラスや関数、メソッド、int型、Object型などが該当。文字列型、モジュールはcalleableオブジェクトではない(この辺はもっと学びたい)
以下、実装
def mydeco(function):
def wrapped(*args,**kwargs):
# 実際の関数を呼び出す処理
result = function(*args, **kwargs)
#呼び出し後に行う処理
if result == True:
print("test()の戻り値はTrue")
return wrapped
@mydeco
def test():
print("test()関数を実行します")
return True
test()
以下、test()の結果
test()関数を実行します
test()の戻り値はTrue
デコレータの活用例
学習したのは主に以下の3つ用途
- 引数チェック
- プロキシ
- コンテクストプロバイダ
1.引数のチェック
関数が受け取るべき引数や返り値の型情報をデコレータで設定して、入出力の渡される値が設定された型に合致するかどうかを検証できる
if __name__ == "__main__":
def check_func_arg_type(name, cls):
"""引数の型をチェックするデコレータ"""
def _check_func_arg_type(func):
def _check_func_arg_type(*args, **kwargs):
import inspect
# 関数の情報を取得
spec = inspect.getfullargspec(func)
# 指定した名前の引数が存在するとき型をチェック
if name in spec.args:
index = spec.args.index(name)
if not isinstance(args[index], cls):
raise TypeError("%s is not %s." % (name, cls.__name__))
return func(*args, **kwargs)
return _check_func_arg_type
return _check_func_arg_type
@check_func_arg_type("name", str)
@check_func_arg_type("age", int)
def name_and_ege(name, age):
print("%s, %s" % (name, age))
try:
name_and_ege("taro", 20)
name_and_ege("taro", "20")
except TypeError as ex:
print(ex)
以下,結果
taro, 16
age is not int.
2.プロキシ
ここでは、代理のオブジェクトを使うことでいろいろメリットがあるパターンのことを指す。
以下のコードではUserオブジェクトのロールをチェックしている。
def protect(role):
def _protect(func):
def __protect(*args, **kwargs):
user = globals().get('user')
if user is None or role not in user.roles:
raise Unauthorized("教えれません")
return func(*args, **kwargs)
return __protect
return _protect
class MySecrets(object):
@protect('admin')
def waffle_recipe(self):
print('user tons of butter!')
tarek = User(('admin', 'user'))
bill = User(('user',))
these_are = MySecrets()
user = tarek
these_are.waffle_recipe()
user = bill
these_are.waffle_recipe()
以下、結果
user tons of butter!
Traceback (most recent call last):
File ".\decorator.py", line 59, in <module>
these_are.waffle_recipe()
File ".\decorator.py", line 43, in __protect
raise Unauthorized("教えれません")
__main__.Unauthorized: 教えれません
3.コンテキストプロバイダ
コンテキストプロバイダは関数が正しい実行コンテキスト内で実行されることを保証する。また関数の前後である処理を実行すると。
ただ、一般的には、デコレータを使用するよりも、コンテキストマネージャーを使う場合が多い。
スレッド間でデータを共有する場合に、複数のスレッドからアクセスされてもデータが保護されていることを保証するためにロックが使用される。
以下、そのようなロック機構をデコレータにより実装したもの。
from threading import RLock
locks = RLock()
def synchronized(func):
def _synchronized(*args, **kwargs):
lock.acqure():
try:
return func(*args. **kwargs)
finally:
locks.release()
return _synchronized
@synchronized
def thread_safe():
pass