2015/11/30

Python Tips:引数のデフォルト値を活用したい

Python では関数の引数としてデフォルトの値を設定することができます。

今回はそのデフォルト値についての注意点をまとめてみます。

まず、デフォルトの値が与えられた引数については省略できるようになります。

import time

def take_nap(duration = 60):
    print('Will sleep for {} seconds...'.format(duration))
    time.sleep(duration)

take_nap()
# => Will sleep for 60 seconds...

デフォルト値が与えられた引数の後にデフォルト値のない引数を置くことはできません。宣言時に SyntaxError が発生します。デフォルト値にない引数の後にデフォルト値のある引数を持ってくるようにしましょう。

def take_nap(duration = 60, callback):
    print('Will sleep for {} seconds...'.format(duration))
    time.sleep(duration)
    callback()

# => SyntaxError: non-default argument follows default argument

デフォルト値にリストや辞書などを指定するのはやめましょう。デフォルト値は関数宣言時に一度だけ評価され、関数の呼び出し時には再利用されるようになっているため、誤って使うと以下のような挙動が発生します。

def print_lines(messages = []):
    prefix_message = 'Messages are:'
    messages.insert(0, prefix_message)
    for m in messages:
        print(m)

messages = ['Ciao', 'Hello', 'KONICHIWA']

print_lines(messages)
# => Messages are:
#    Ciao
#    Hello
#    KONICHIWA
print_lines(messages)
# => Messages are:
#    Messages are:
#    Ciao
#    Hello
#    KONICHIWA

# 'Messages are:' が messages に追加されたままとなり
# 呼び出される度に積み上がっていく

「デフォルト値は関数宣言時に一度だけ評価され、関数の呼び出し時には再利用される」というところがポイントです。 mutable な値だけが問題になるわけではありません。以下のような場合にも思わぬ挙動が発生します。

import datetime
import time

def get_timestamp(target_datetime = datetime.datetime.now()):
    """datetime 型の変数から timestamp を取得
    """
    return target_datetime.timestamp()

print(get_timestamp())
time.sleep(3)
print(get_timestamp())
# 2 回ともまったく同じタイムスタンプがかえってくる

これは datetime.datetime.now() が関数の宣言時に一度だけ呼び出されるために発生します。これが意図した挙動の場合には問題ありませんが、関数の呼び出し時の時刻がかえってくることを期待していると想定外の結果が返ってくるので要注意です。

この動きを防ぐには、デフォルト引数にはあくまでも None を渡しておいて関数の内部で初期化してしまうことが必要になります。

def get_timestamp(target_datetime = None):
    """datetime 型の変数から timestamp を取得
    """
    if not target_datetime:
        target_datetime = datetime.datetime.now()
    return target_datetime.timestamp()

この形で見ると、関数のデフォルト引数もクロージャと同じような挙動をするものとして覚えておくのがいいかもしれません。

0 件のコメント: