HiroCom777の学習記録 (original) (raw)
この連載では、Pythonについて色々な形で再学習に取り組んでいます。前回の記事はこちらになります。
前回は、デコレーターを使用したエラー対応について見ていきました。デコレーターを使うとスマートなエラー処理が書けそうです。
今回もデコレーターの使用例です。キャッシュについて考えてみます。
Pythonのキャッシュ
計算や処理によって出された結果を一時的に保存することをキャッシュといいます。同じ結果を2度目以降求める場合は処理をせずに、保存した内容をそのまま返します。同じ処理の繰り返しを避けることで、全体の処理が早くなったり、全体のリソースの節約になったりします。
デコレーターを使うと、簡単にキャッシュ機能を実現します。以下は簡単な例です。
def deco(func): cache = {}
def wrap(*args):
if args in cache:
print("キャッシュから取り出し")
return cache[args]
else:
print("関数を実行")
result = func(*args)
cache[args] = result
return result
return wrap
デコレーターが実行されると、辞書cacheのキーに対象関数で指定した引数があるかを確認します。ない場合には引数をキーとして、対象関数の返りを辞書cacheに登録します。見つかった場合には、関数を実行せずに辞書からキーに対応する値を取り出して返します。
以下はデコレーターの適用例です。
@deco def test_func(x): r = 0 for i in range(1,x): r += i return r
print(test_func(100000))
関数を実行
4999950000
print(test_func(100000))
キャッシュから取り出し
4999950000
print(test_func(200000))
関数を実行
19999900000
この例では、1から指定した整数までの総和を計算します。一度実行した引数については、キャッシュから取り出していることがわかります。キャッシュに保存しているデータは、プロセスが終了するとともに消えてしまいます。また、キャッシュの対象となる関数は、参照透過性(同じ引数を指定すると、同じ処理と計算結果をもたらす性質)が保たれていなければなりません。
フィボナッチ数列
キャッシュを使用した計算処理の例として、フィボナッチ数列の算出がよく取り上げられます。詳細は以下のURLを参照してください。
フィボナッチ数列とは、最初に0、2番目に1、以降は1つ前の数値と2つ前の数値を足し合わせたものを続けるというものです。以下はフィボナッチ数列の指定した位置の数値を返す関数です。
def fibonacci(i): if i == 0 or i == 1: # 最初は0、2番目に1を返す return i else: # 3番目以降は、1つ前の数値と2つ前の数値を足す return fibonacci(i - 1) + fibonacci(i - 2)
3番目以降の数値を算出する部分には、fibonacciが使用されています。この様に関数内で自分自身を呼び出して使用する手法を「再帰処理」といいます。
実際に実行してみると、以下のようになりました。
print(fibonacci(40)) # 102334155 (所用時間23.3秒)
所用時間は僕自身の環境です。かなりの時間を要しました。これは再帰処理で、同じ計算処理を何度も実行しているからです。計算の回数は331160281回でした。引数の値を大きくすると、さらに回数が増えて時間がかかります。
この関数を、先のdeco関数でデコレートしてみましょう(deco関数内のprint文はコメントアウトしておきます)。先頭部分は以下のようになります。
@deco def fibonacci(i):
実行してみると、以下のようになりました。
print(fibonacci(40)) # 102334155 (所用時間0秒)
所用時間は無視できるほどになりました。
この様にキャッシュを使用することで、ムダな計算を省いて処理時間を短縮できます。実際には、今回の様にデコレーターのコードを記述するのではなく、標準モジュール「functools」のcache関数を使用します。以下の様に記述します。
from functools import cache
@cache def 関数():
コチラもデコレーターを使った機能です。cache関数を使用したほうが、エラー処理、メモリ管理が最適化されているのでおススメです。使用の際の注意事項は同じで、今回ご紹介した適用例にも使用できます。でも、仕組みを理解しておくことは重要ですね。
次回は無名関数(ラムダ式)
いかがでしょうか。キャッシュは大量の計算処理を実行する際に、力を発揮するようです。次回は無名関数です。手軽に関数を作れる機能の様なのですが、どんな時に使うのでしょうか?お楽しみに!!
今日は2024年9月30日です。2024年も3/4が過ぎようとしています。いつものように、このタイミングで2024年の途中振り返りをしてみたいと思います。前回の振り返りはこちらになります。
今回は23度目の振り返りです。
2024の目標
2024年1月に立てた目標ですが以下になりますね。順を追って今の状態を見ていきましょう。
健康
体重の維持に成功しています。定期的な運動も何とか継続できました。昼間は暑い日が続いたので、早起きしてウォーキングしてました。人間ドックを受診したのですが、結果は如何に?
AIに挑戦
過去のブログ記事をAIに読み込ませて、改善の提案をしてもらっていますが。すごいですね!!誤字脱字はほぼ100%見抜きますし、プログラムの改善提案もイケています。
AIは何でも解決してくれるわけではなく、しっかりとまとまっている内容について効果的な提案をしてくれるようです。ブログ書き続けてきて良かったです!!
仕事
やってないです(笑)
プログラミング
Pythonの再学習について、引き続きブログを執筆継続しています。相変わらず、いくら書いても終わりが見えません。大変です。あと上にも書いたのですが、過去のブログ記事をAIに読ませると、すごく改善してくれます。やはりPythonはAIと相性がいいようです。
資格を取る
簿記3級の学習ですが、少し停滞しています。ブログ1本だけ。今年中には終わらせたい!!
アウトプット
- ブログ
9月末までで本記事を除いて64本の記事を書いています。少しペースが落ちました。7~9月までだと、Python関係で書いた記事が13本。簿記の関係が1本です。また、PV数は伸び悩んでいます。Pythonも簿記も人気がないんですよね。まぁ、PVは直接の目的ではないのですが・・・ - 講座
何か講座を作ってみようかと考えています。候補はマイコン講座、アウトプット講座、データベース講座といったところでしょうか。
新しい発見
本の執筆をしました。最初に出したマイコン本の改定版です。本を出してから考えていたことを纏めることができて、内容的にはかなり満足しています。また、執筆と並行して編集作業を他の人に伝えることにも挑戦してみました。
今まで他の人の執筆支援してきて得られたことも、生かせたと思います。
あと、執筆にあたってその内容の一部をツィッター(X)にポストしたのですが、閲覧数がかなり多いですね。新しい発見でした。
次は2024年総括
そんな訳で「2024途中振り返り_3」でした。3か月後に2024年の総括の記事を書こうと思います。今回は、ほとんど本の執筆でしたね。簿記の学習はキリを付けたい!!
この連載では、Pythonについて色々な形で再学習に取り組んでいます。前回の記事はこちらになります。
前回は、デコレーターの使用例、応用例を見ていきました。
今回もデコレーターの使用例です。エラー対応について見ていきましょう。
Pythonのエラー処理
まずPythonのエラー処理について述べなければならないのですが、この点については以下のPythonでSQLite3を使用する記事に書いています。参考にしてください。
簡単に説明すると、Pythonのエラー処理は「try-except文」で行います。以下のように記述します。
try: メインの処理 except: メインの処理でエラーが発生した場合の処理 else: メインの処理が正常に実行された場合の処理(省略可能) finally: メインの処理結果にかかわらず実行される処理(省略可能)
関数を記述する際にエラー処理を記述することがあります。でも、同じようなエラー処理を複数の関数それぞれに記述することは、効率的とは言えません。デコレーターでエラー処理を追加できれば、全体のコードがわかりやすくなります。
基本的なエラー処理
def error_handler(func): def wrap(*args, **kwargs): try: return func(*args, **kwargs) except: print(f"エラーが発生しました") return wrap
@error_handler def divide(a, b): return a / b
print(divide(10, 2))
5.0
print(divide(10, 0)) # エラーが発生しました
None
エラー(例外)が発生した場合は、エラーメッセージを表示して終了するようになりました。 except部分を以下のように書き換えると、エラーの種類とエラー種類を確認できます。
except Exception as e:
print(f"エラーが発生しました: {e}")
print(f"エラー種類: {e.__class__.__name__}")
Exceptionクラスはすべての例外が当てはまります。結果は以下の様になりました。
print(divide(10, 0))
エラーが発生しました: division by zero
エラー種類: ZeroDivisionError
None
print(divide(10, [0]))
エラーが発生しました: unsupported operand type(s) for /: 'int' and 'list'
エラー種類: TypeError
None
エラーの内容と、エラー種類が表示されました。
その他のPythonのエラーの種類については、以下を参照してください。docs.python.org
エラーによって処理を変える
エラー種類によって、処理を分けることもできます。
def error_handler(func): def wrap(*args, **kwargs): try: return func(*args, **kwargs) except ZeroDivisionError: print("0で割ることはできません") except TypeError: print("データ型の指定が不正です") return wrap
@error_handler def divide(a, b): return a / b
print(divide(10, 2))
5.0
print(divide(10, 0)) # 0で割ることはできません
None
print(divide(10, [0])) # データ型の指定が不正です
None
正常に終了した場合の後処理
正常に終了した場合の処理を追加することもできます。
def error_handler(func): def wrap(*args, **kwargs): try: re = func(*args, **kwargs) except Exception as e: print(f"エラーが発生しました") else: print("正常に終了しました") return re return wrap
@error_handler def divide(a, b): return a / b
print(divide(10, 2)) # 正常に終了しました
5.0
print(divide(10, 0)) # エラーが発生しました
None
次回もデコレーター(キャッシング)
いかがでしょうか。デコレーターを使うとスマートなエラー処理が書けそうです。次回はデコレーターを使ったキャッシングについて考えてみます。お楽しみに!!
この連載では、Pythonについて色々な形で再学習に取り組んでいます。前回の記事はこちらになります。
前回は、デコレーターの意味について学びました。色々な引数のパターンに対応するために、デコレーター関数の中にラッパー関数を持たせます。
今回はデコレーターの使用例、応用例を見ていきます。
所用時間の確認
関数を開発しているときに、実行にかかる時間を測定したい場面があります。そんな時にデコレーターを使用すれば、関数本体に手を加えることなく所用時間を測ることができます。
import time
def timer_deco(func): def wrap(*args, **kwargs): print(func.name) start_time = time.time() result = func(*args, **kwargs) end_time = time.time() print(f"所要時間: {end_time - start_time} 秒") return result return wrap
@timer_deco def test_func(): print("test_func処理開始") time.sleep(2)
test_func()
test_func
test_func処理開始
所要時間: 2.000764846801758 秒
開発が完了したら、関数の前に記述しているデコレーターを削除すればいいのです。
作業ログ
関数が実行される際にログ(記録)を取りたい場合があります。関数が1つだけの場合は該当の関数に記述すればいいのですが、複数の関数に対して同様のログを取りたい場合は、関数毎にコードを書くのは非効率です。このような処理の場合、デコレーターの使用が有効です。
import datetime
def log_deco(func): def wrap(*args, **kwargs): result = func(*args, **kwargs) print(datetime.datetime.now() , end = " : 関数 ") print(func.name , end = ": 引数 ") print(*args, **kwargs, end = ": 返り値 : ") print(result) return return wrap
@log_deco def func_1(): pass
@log_deco def func_2(a,b): return a * b
@log_deco def func_3(val_1,val_2 = 2): return val_1 * val_2
func_1() func_2(3, 4) func_3(3)
2024-09-20 09:59:05.340213 : 関数 func_1: 引数 : 返り値 : None
2024-09-20 09:59:05.340213 : 関数 func_2: 引数 3 4: 返り値 : 12
2024-09-20 09:59:05.340213 : 関数 func_3: 引数 3: 返り値 : 6
この例では標準出力に出力していますが、実際にはファイルに出力することになるでしょう。色々な引数のパターンに対応できています。
条件チェック
特定の条件を満たした対象のみ、関数の使用を許可したい場合があります。そんな関数が複数ある場合、デコレーターで条件チェック機能をまとめて実装すると効率的です。
def rank_check(func): def wrap(user): if user['rank'] in ["A","B"]: return func(user) else: print(f"{user['name']}さんは、使用できません") return wrap
@rank_check def say_hello(user): print(f"{user['name']}さん、こんにちは")
user_1 = {"name": "Alice", "rank": "A"} user_2 = {"name": "Tom", "rank": "B"} user_3 = {"name": "Bob", "rank": "C"}
say_hello(user_1) say_hello(user_2) say_hello(user_3)
Aliceさん、こんにちは
Tomさん、こんにちは
Bobさんは、使用できません
この例ではランクがAかBのメンバーのみ、関数say_helloを使用できます。
クラスのインスタンスメソッドへの適用
クラスのインスタンスメソッドにも、デコレーターを使用できます。
def deco(func): def wrap(self): print(f"{func.name}が呼ばれました") return func(self) return wrap
class MyClass: @deco def greet(self): print("こんにちは")
obj = MyClass() obj.greet()
greetが呼ばれました
こんにちは
次回もデコレーター(エラー対応)
いかがでしょうか。デコレーターは色々な使い道が考えられますね。次回はデコレーターを使ったエラー対応について考えてみます。お楽しみに!!
この連載では、Pythonについて色々な形で再学習に取り組んでいます。前回の記事はこちらになります。
前回は、デコレーターについて学びました。複数の関数の前後に決まった処理を追加したいとき、その処理をまとめておくと便利です。
今回もデコレーターです。デコレーターの利点について考えてみようと思います。
デコレーターの意味
関数の中身を変更せずに機能追加するということは、関数の前後に処理を追加することです。対象の関数の前後の処理をまとめて関数にしてしまえばいいのです。対象の関数は引数で渡します。Pythonでは関数自体もオブジェクトですので、このような記述が可能です。以下のようなイメージになります。
def 関数(対象の関数): 前処理 対象の関数呼び出し 後処理
実際の例を見てみましょう。
def deco(func): print('decoの前処理が実行されました') func() print('decoの後処理が実行されました')
def check(): print('checkが実行されました')
deco(check)
decoの前処理が実行されました
checkが実行されました
decoの後処理が実行されました
これで十分なのであれば問題ないのですが、これでは対象の関数に引数が渡せません。この問題はデコレーターで解決できます。デコレーター関数は、以下のように記述します。
def デコレーター関数(デコレートする関数): def ラッパー関数(引数): 前処理 デコレートする関数(引数) 後処理 return ラッパー関数
デコレートする関数は、以下のように記述します。
関数の定義
@デコレーター関数 def デコレートする関数(引数): # 処理
呼び出し
デコレートする関数(引数)
実際の例を見てみましょう。
def deco(func): def wrap(arg): print('wrapの前処理が実行されました') func(arg) print('wrapの後処理が実行されました') return wrap
@deco def check(arg): print(f'{arg} が渡されました')
check('ABC')
wrapの前処理が実行されました
ABC が渡されました
wrapの後処理が実行されました
このように、デコレーターを使うと引数が必要な関数に対しても機能を追加できるのです。
色々な引数に対応する
上の例では1つの引数を持つ関数に対応しました。しかし、関数に渡される引数の数は決まっていませんし、キーワード引数を使用した関数もあります。この問題に対応するために、可変長位置引数と可変長キーワード引数を使用します。デコレーター関数は、以下のように記述します。
def デコレーター関数(デコレートする関数): def ラッパー関数(*args, **kwargs): 前処理 デコレートする関数(*args, **kwargs) 後処理 return ラッパー関数
「_args, *_kwargs」は、可変長位置引数と可変長キーワード引数を意味します。慣例的にこの様に記述します。「_args」は任意の数の位置引数、「*_kwargs」は任意の数のキーワード引数を受け取ります。 デコレートする関数は、今までと同じです。
実際の例を見てみましょう。
def deco(func): def wrap(*args, **kwargs): print('前処理「',end = "") func(*args, **kwargs) print('」後処理') return wrap
実際の動きを見てみましょう。
@deco def check_1(): print('check_1実行。',end = "")
check_1()
前処理「check_1実行。」後処理
@deco def check_2(arg): print(f'check_2実行。引数は {arg} です。',end = "")
check_2("ABC")
前処理「check_2実行。引数は ABC です。」後処理
@deco def check_3(arg_1,arg_2): print(f'check_3実行。引数は {arg_1} と{arg_2}です。',end = "")
check_3("DEF" ,10 )
前処理「check_3実行。引数は DEF と10です。」後処理
@deco def check_4(arg_1,arg_2 = 100): print(f'check_4実行。引数は {arg_1} と{arg_2}です。',end = "")
check_4("GHI")
前処理「check_4実行。引数は GHI と100です。」後処理
色々な引数で、デコレーターが機能していることがわかります。
次回はデコレーターの応用例
いかがでしょうか。デコレーター、なんだか便利じゃないですか? 次回もデコレーターです。デコレーターの応用例を見ていきます。お楽しみに!!
この連載では、Pythonについて色々な形で再学習に取り組んでいます。前回の記事はこちらになります。
前回は、イテレータについて学びました。イテラブル、イテレータの仕組みがわかったと思います。
今回は、デコレーターです。クラスのスタティックメソッド、クラスメソッドを学んだ時に出てきました。複数の関数の前後に決まった処理を追加したいとき、その処理をまとめておくと便利です。
関数のカスタマイズ
関数の中身を変更せずに機能追加するということは、関数の前後に処理を追加することになります。以下のようなイメージになります。
前処理 対象の関数呼び出し 後処理
これでは普通のプログラムなので、この処理自体を関数にまとめてしまいます。デコレートする関数自体は、この関数の引数として渡してしまいましょう。Pythonでは関数自体もオブジェクトですので、このような記述が可能です。
def 関数(対象の関数): 前処理 対象の関数呼び出し 後処理
実際の例を見てみましょう。
def deco(func): print('decoの前処理が実行されました') func() print('decoの後処理が実行されました')
def check(): print('checkが実行されました')
check() # checkが実行されました
deco(check)
decoの前処理が実行されました
checkが実行されました
decoの後処理が実行されました
この例のように「check()」と、関数名の最後に"()"が付く場合は関数を実行します。「deco(check)」におけるcheckのように"()"が付かない場合は関数自体を意味します。 関数checkを引数に指定して関数decoを実行すると、処理をまとめて実行します。
デコレーター
デコレーターは、関数やメソッド、クラスの定義の前に記述(デコレート)することで、中身を変更せずに特定の処理を追加できる機能です。追加する機能を記述した関数を作成して、対象の関数をデコレートします。以下のように記述します。
def デコレーター関数(デコレートする関数): def ラッパー関数(): 前処理 デコレートする関数の呼び出し 後処理 return ラッパー関数
引数で指定したデコレートする関数に、前後の処理を追加した関数(ラッパー関数)を定義して返しています。Pythonでは関数自体もオブジェクトなので、このようなことも可能なのです。
返ってきた関数を実行すると、まとめて処理を実行します。デコレートする関数は、デコレーター関数の引数として指定されています。ラッパー関数は、上のスコープで指定された内容をデコレートする関数として参照しています。
デコレートする関数を実行するには、以下のように記述します。
関数の定義
def デコレートする関数(): # 処理
呼び出し
デコレーター関数(デコレートする関数)()
「デコレーター関数(デコレートする関数)」には、デコレーター関数内のラッパー関数が定義され、最後に「()」を付けることで呼び出しています。
実際の例を見てみましょう。
def deco(func): print('decoが実行されました') def wrap(): print('wrapの前処理が実行されました') func() print('wrapの後処理が実行されました') return wrap
def check(): print('checkが実行されました')
deco(check)()
decoが実行されました
wrapの前処理が実行されました
checkが実行されました
wrapの後処理が実行されました
「deco(check)」はdeco関数が実行された結果(wrap関数)を意味し、「deco(check)()」で、その関数を実行します。
ところで先にデコレーターのことを「関数やメソッド、クラスの定義の前に記述(デコレート)することで、中身を変更せずに特定の処理を追加できる機能」とご紹介しました。実をいうと、上の呼び出し方法は、以下のように簡略化して記述できます。
関数の定義
@デコレーター関数 def デコレートする関数(): # 処理
呼び出し
デコレートする関数()
実際の例を見てみましょう。
def deco(func): print('decoが実行されました') def wrap(): print('wrapの前処理が実行されました') func() print('wrapの後処理が実行されました') return wrap
@deco def check(): print('checkが実行されました')
check()
decoが実行されました
wrapの前処理が実行されました
checkが実行されました
wrapの後処理が実行されました
このように記述すると「@deco」で一度deco関数が実行されます。結果、check関数がdeco関数内のwrap関数に書き換わります。
シンプルな記述で同様の結果が得られました。
次回はデコレーターの応用
いかがでしょうか。デコレーターはおもしろい機能ですね。でも、ここまでやらなくても最初にご紹介した例でいいんじゃないでしょうか? 次回はデコレーターの使いどころと、応用方法について学ぼうと思います。お楽しみに!!
この連載では、Pythonについて色々な形で再学習に取り組んでいます。前回の記事はこちらになります。
前回は、イテレータについて学びました。イテラブル、イテレータの仕組みがわかったと思います。
今回は、ジェネレーターです。イテレータ同様繰り返しのデータを扱う仕組みのようですが、どのようなものなのでしょうか。
ジェネレーターとは
ジェネレーターとは、必要になったときに次のデータを生成する仕組みです。以下は前回ご紹介したリストからイテレータを生成して処理する例です。
it = iter([0,1,2]) # イテラブル(リスト)からイテレータを生成
print(next(it)) # 0 を返す print(next(it)) # 1 を返す print(next(it)) # 2 を返す print(next(it)) # 例外 StopIteration が発生する
この例では、処理に必要なデータ(リスト)は、あらかじめ用意されています。上の例ではデータ量は少ないのですが、データ量が膨大になると、そのデータがメモリを占有してしまいます。また、データを用意するために複雑な計算を要する場合などでは、データの用意に時間がかかって、処理の開始が遅れてしまったりします。
このような時にジェネレーターを使用すると、効率のよいプログラムが書けます。以下は上記の例をジェネレーターで書き換えたものです。
def generate_test_1(end): for i in range(0, end): yield i
ge = generate_test_1(3)
print(next(ge)) # 0 を返す print(next(ge)) # 1 を返す print(next(ge)) # 2 を返す print(next(ge)) # 例外 StopIteration が発生する
これを「ジェネレーター関数」といいます。通常の関数は、returnで値を返して終了となりますが、ジェネレーター関数ではyieldで値を返します。yieldが呼ばれると関数の実行は一時停止し、次に呼び出されたときにその次の場所から再開します。 これならばデータ量が多い場合でも、データがメモリを占有することはありません。データ型はジェネレーター型です。
print(type(ge)) # <class 'generator'> を返す
また、ジェネレーターは特殊メソッド「**iterメソッド」、「next**メソッド」を持っています。ジェネレーターは、イテレータの一種です。
先の例ではデータ数を指定していましたが、データ数を指定せずに(無限に)値を返すジェネレーター関数も記述可能です。
def generate_numbers(): i = 0 while(True): yield i i += 1
ge = generate_numbers()
print(next(ge)) # 0 を返す print(next(ge)) # 1 を返す ・ ・
この例ではyieldで値を返した後、次の呼び出しでは次の文から再開します。関数の場合はreturnの後に記述された内容は無効になりますが、yieldでは有効になります。
ジェネレーター式
内包表記と似た様な記法で、ジェネレーターを生成する方法もあります。以下は、1~5の整数のうち奇数のみのリストを返すリスト内包表記です。
[i for i in range(1,6) if i % 2]
この値を順に返すジェネレーターは、以下のように記述できます。
ge = (i for i in range(1,6) if i % 2)
print(next(ge)) # 1 を返す print(next(ge)) # 3 を返す print(next(ge)) # 5 を返す print(next(ge)) # 例外 StopIteration が発生する
リスト内包表記との違いは、"[]"ではなく"()"で括る点です。
無限に続くジェネレーター式
データ数を指定せずに(無限に)値を返すジェネレーター式は、「itertoolsモジュール」をインポートすることで可能になります。countオブジェクトを使用して、引数には開始する数値を指定します。引数を省略すると0になります。
import itertools
ge = (i for i in itertools.count(3))
print(next(ge)) # 3 を返す print(next(ge)) # 4 を返す print(next(ge)) # 5 を返す ・ ・
次回はデコレーター
いかがでしょうか。ジェネレーターを使うと、効率よく繰り返しのデータを作成できます。次回はデコレーターです。関数やメソッド、クラスの定義の前に記述するのですが、どのような機能があるのでしょうか。お楽しみに!!