🔖

Pythonで引数にキーワードを強制する方法

2024/02/11に公開

これはなに

Pythonの関数で、利用時にキーワードによる指定を強制できると知ったのでまとめたメモ。

この記事が役立つ人

  • すでにPythonの基本的な知識を持っている人で
    • 関数呼び出し時にキーワード指定を強制したい人
    • コードの可読性や保守性を向上させたい人

本記事の要約

Python関数の引数の定義で、*の後に引数を定義すると、利用者にキーワードによる指定を強制できる。この引数はキーワード専用引数(Keyword-Only Arguments)と呼ばれる。下記の例では、is_upperをキーワード専用引数として定義している。

関数定義
# `*`の後に定義した引数は、利用者にキーワードによる指定を強制できる
# 下記では`is_upper`をキーワード専用引数として定義している
def greet(name: str, age: int, *, is_upper: bool) -> str:
    if is_upper:
        return f"Hello, {name}. You are {age} years old.".upper()
    else:
        return f"Hello, {name}. You are {age} years old."
関数呼び出し
# キーワードを指定すれば関数を呼び出せる
print(greet("Alice", 20, is_upper=True))
# => HELLO, ALICE. YOU ARE 20 YEARS OLD.

# 位置で引数を指定するとエラーになる
print(greet("Alice", 20, True))
# => TypeError: greet() takes 2 positional arguments but 3 were given

このように、キーワード専用引数を使えば、関数を呼び出す際に引数の名前指定を強制できるため、コードの可読性と保守性を高められる。ただし、利用者がキーワードを指定しなければならない他、キーワードの名前の変更に弱いという欠点もある。

位置引数とキーワード引数

デフォルトの引数定義では、利用者は関数呼び出しの際に、引数は位置かキーワードで指定できる。名前による明示的な指定も、位置による暗黙的な指定もできる。

よくある関数定義
# 関数を定義する
def greet(name: str, age: int) -> str:
    return f"Hello, {name}. You are {age} years old."
よくある関数呼び出し
# 位置引数を使って関数を呼び出す
print(greet("Alice", 20))
# => Hello, Alice. You are 20 years old.

# キーワード引数を使って関数を呼び出す
print(greet(name="Alice", age=20))
# => Hello, Alice. You are 20 years old.

# 位置引数とキーワード引数を混在させて関数を呼び出す
print(greet("Alice", age=20))
# => Hello, Alice. You are 20 years old.

このように、Pythonのデフォルトの引数は、位置でも指定できるし、キーワードでも指定できる。大抵はこれで問題ないが、たとえばbool型の引数が複数ある場合、どの位置の引数が何を指しているのかがわかりにくくなることがある。

よくある関数定義
# 関数を定義する
def greet(name: str, age: int, is_upper: bool, is_night: bool) -> str:
    if is_night:
        return f"Good night, {name}. You are {age} years old."
    if is_upper:
        return f"Hello, {name}. You are {age} years old.".upper()
    else:
        return f"Hello, {name}. You are {age} years old."
よくある関数呼び出し
# 位置の指定で関数を呼び出すと、どの引数が何を指しているのかがわかりにくい
print(greet("Alice", 20, True, False))
# => HELLO, ALICE. YOU ARE 20 YEARS OLD.

# キーワードの指定で関数を呼び出すと、どの引数が何を指しているのかがわかりやすい
print(greet(name="Alice", age=20, is_upper=True, is_night=False))
# => Hello, Alice. You are 20 years old.

このように、キーワード引数を使うと、引数の意味が明確になる。しかし、Pythonのデフォルトの引数定義では、利用者は位置引数とキーワード引数の両方を使って関数を呼び出せるため、利用者はキーワードを指定せずとも関数を呼び出せる。つまり、いくらわかりにくいといえども、利用者にキーワード引数の利用を強制できない。

キーワード引数を強制する

Python3.0からは、関数の引数にキーワードを強制できる。これは、PEP 3102で提案された機能であり、“keyword-only” arguments(キーワード専用引数)と呼ばれる。キーワード専用引数とは、呼び出し時にキーワードでのみ指定できる引数のことであり、位置では指定できない引数である。

キーワード専用引数は、*の後に引数を定義することで実現できる。下記のように、関数の引数定義で*の後に引数を定義すると、その引数は利用者にキーワードによる指定を強制できる。

キーワード専用引数を用いた関数定義
# `*`の後に定義した引数は、利用者にキーワードによる指定を強制できる
def greet(name: str, age: int, *, is_upper: bool) -> str:
    if is_upper:
        return f"Hello, {name}. You are {age} years old.".upper()
    else:
        return f"Hello, {name}. You are {age} years old."
キーワード専用引数を用いた関数の呼び出し
# キーワードを指定すれば関数を呼び出せる
print(greet("Alice", 20, is_upper=True))
# => HELLO, ALICE. YOU ARE 20 YEARS OLD.

# キーワードを指定せず位置で引数を指定してするとエラーになる
print(greet("Alice", 20, True))
# => TypeError: greet() takes 2 positional arguments but 3 were given

上記では1つしか指定していないが、*の後に複数の引数を定義できる。

もちろん、上記コードのように、*の前に定義した引数と*の後に定義した引数を混在させても問題ない。この場合、*の後に定義した引数のみキーワード専用引数となるため、*の前に定義した引数はデフォルトの引数と同じように、位置とキーワードの両方で指定できる。

キーワード専用引数のメリット

  • 呼び出し側で引数の意味が明確になる
  • 引数の順番を気にせず関数を呼び出せる

キーワード専用引数のデメリット

  • キーワード引数を強制することで、関数の利用者に手間をかけることがある
  • キーワードの名前の変更に弱い

まとめ

関数の引数定義で*の後に引数を定義すると、その引数はキーワード専用引数となり、利用時にキーワード指定を強制できる。これにより、関数の利用時に引数の意図の明示を強制できるため、コードの可読性と保守性を向上できる。ただし、キーワード引数を強制することで、利用者に負荷をかけることもあるため、利用者の利便性を考慮して使い分けるとよい。

参考文献

Discussion