自分があんまり見たことないPythonのコーディングテクニックを紹介しようと思います。予想外にメジャーだったらごめんなさい。出来るだけサンプルコードを載せて、実際にどう役に立つのかも示したいと思います。ニッチな技術をまとめているので、ある程度Pythonが書けることが前提です。
#functools.partial
これを使うと、ある関数について、引数を固定した関数を作ることができます。コードを見た方が早いと思います。
import functools
def add(x, y):
return x + y
add_fixed_y = functools.partial(add, y=5)
print(add_fixed_y(2)) # 7
これだけだと何に役立つかわからないと思うので、自分の使い道を紹介したいと思います。自分はファイルの**open
関数**に使っています。
open_utf_8 = functools.partial(open, encoding="utf-8")
r_open_utf_8 = functools.partial(open_utf_8, mode="r")
w_open_utf_8 = functools.partial(open_utf_8, mode="w")
with w_open_utf_8("something_great.txt") as wf:
wf.write("Hello World!")
こうすることで、コード内から定数を減らすことができ、encoding
の部分のタイポを防ぐことができます。
next
とジェネレータ
リスト内包表記の内側の式はジェネレータ式に使えて、ジェネレータオブジェクトを生成するというのは他の記事でも紹介されています。
gen = (i ** 2 for i in range(10))
print(gen) # <generator object <genexpr> at 0x~~>
これ、実際のコーディングではどう役に立つのでしょうか。自分は、条件を満たす最初のインデックスを探すのに使っています。文字列を指定バイト数以下に丸めるという処理に使っています。
from itertools import accumulate
def str_clamp_bytes(value: str, max_bytes: int) -> str:
byte_count = (len(s.encode("utf-8")) for s in value)
try:
end_slice = next(i for i, v in enumerate(accumulate(byte_count)) if v > max_bytes)
except StopIteration:
return value
return value[:end_slice]
(i for i, v in enumerate(accumulate(byte_count)) if v > max_bytes)
がジェネレータで、if v > max_bytes
が条件式です。end_slice
にはv > max_bytes
を満たす最初のインデックスが代入されます。もし、条件を満たすインデックスが無かった場合は、StopIteration
例外が発生するのでキャッチして元の文字列を返してあげます。byte_count
はaccumulate
に使うだけなのでジェネレータで十分です。
【2021/02/16追記】next
関数について補足
ドキュメントを読んでいたら新しい情報を手に入れたので補足しておきます。
next
関数は第二引数にStopIteration
例外時のデフォルト値をとります。なので上記のコードは次のように、より簡潔に書くことができます。
def str_clamp_bytes(value: str, max_bytes: int) -> str:
byte_count = (len(s.encode("utf-8")) for s in value)
gen_first_index = (i for i, v in enumerate(accumulate(byte_count)) if v > max_bytes)
end_slice = next(gen_first_index, None)
return value[:end_slice]
スライスのうしろ側にNone
を渡すと文字列の最後まで取得することができます。
グローバル変数__debug__
起動時に-O
(not zero)オプションをつけるとFalse
になる変数です。-O
オプションをつけるとassert
文が無効になりプログラムの高速化が狙えるので、製品版ではつけても問題ないでしょう。自分は製品版の時のみ配信をするという処理に使っています。
def broadcast_debug():
pass
def broadcast_prod():
pass
(broadcast_debug if __debug__ else broadcast_prod)()
lru_cache
デコレータ
関数の引数と戻り値のペアを辞書で覚えておくことで、以前渡された引数なら処理を行わずに以前の戻り値を返します。
from functools import lru_cache
@lru_cache
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(10))
辞書なのでアクセス速度は純粋なメモ化には劣りますが、手軽さは素晴らしいです。あとは、メモ化では対応できない文字列などが引数になった場合でも対応できます。
inspect.getmembers
モジュールからすべてのメンバを取得できます。第二引数でメンバのタイプを指定することができます。
import inspect
import copy
print(inspect.getmembers(copy, inspect.isclass))
# >> [('Error', <class 'copy.Error'>), ('error', <class 'copy.Error'>)]
モジュールからクラスだけを全て抽出できるということは、loaded_classes = [class_A, class_B, ...]
なんて面倒くさいことやらなくても、一気に取得できます。しかも、追加し忘れる心配がありません。クラスオブジェクトには__bases__
メンバがあり、継承しているすべてのクラスオブジェクトがタプルとして入っているので、この辺を使えばフィルタ出来ると思います。
最後に
自分が「便利だけどあんまり見ないなぁ」と思う小技を紹介してみました。タイポや間違いがあったら是非教えてください。