LoginSignup
153
138

More than 3 years have passed since last update.

【Python小技】マイナーだけど便利なPythonの小技5つ

Last updated at Posted at 2020-04-30

自分があんまり見たことない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_countaccumulateに使うだけなのでジェネレータで十分です。

【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__メンバがあり、継承しているすべてのクラスオブジェクトがタプルとして入っているので、この辺を使えばフィルタ出来ると思います。

最後に

自分が「便利だけどあんまり見ないなぁ」と思う小技を紹介してみました。タイポや間違いがあったら是非教えてください。

153
138
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
153
138