Pythonでオーバーロードを実装する
Pythonは、データサイエンス分野で広く用いられており活用の機会も増えています。作成したコードをライブラリとして共有し、効率的なソフトウェア開発を実践することができます。ここでは作成する関数をより柔軟に使用するために、オーバーロードの実装方法を解説していきます。
【参考】:Python
オーバーロードとは
オーバーロードとは、オブジェクト指向プログラミングで用いる手法です。具体的には、同じ名前の関数あるいはメソッドを使って、異なる引数のタイプや数でも単一の処理を行う実装の方法を指します。
多重定義という場合もあります。例えばC++やC#などでは、オーバーロードを一般的な関数定義の手法として用いることができます。
類似の用語でオーバーライドという機能があります。これは親クラス(スーパークラス)のメソッドを子クラス(サブクラス)で上書きし、定義しなおす手法です。オーバーロードと混同しないようにしましょう。
【参考】:Python Docs: 用語集
オーバーロードのメリット
オーバーロードは、異なる目的の処理も同一の関数やメソッドに定義できるので、いちいち異なる関数名を指定せずに引数を渡すことができます。オーバーロードに対応していないプログラミング言語では、それぞれの定義を条件分岐などで振り分けたりする必要があり、その不便さから解消されます。
オーバーロードのデメリット
オーバーロードのデメリットですが、複数の処理を気にせずに使える反面、どの処理にどの引数を渡すべきかわからなくなる場合があることです。コードのデバッグの際にも引数のタイプや数の組み合わせの妥当性や、コードが見にくくならないようにコーディングルールを整備するなどが求められます。
Pythonでオーバーロードを実装するには
Pythonは型を持たないので、C++と同じようなオーバーロードを実装することができません。Pythonでは標準ライブラリにfunctoolsが提供されており、その中の1つの機能にsingledispatchがあります。実際には、このsingledispatchデコレータを使うことでオーバーロードに相当するコードが実現できます。
デコレータとは、関数の前後に特定処理を追加するための機能で、Pythonではこのデコレータを用いてオーバーロードに相当する機能を実現することができます。
【参考】:Python Docs: What's New In Python 3.4
singledispatchの概要
singledispatchは、オブジェクトの操作を行う関数のfunctoolsモジュールに搭載されています。functoolsはPythonの標準ライブラリで、singledispatchはPython 3.4に追加されたジェネリック関数となります。
用途は、異なる型に対し同じ操作をすることを目的としており、オーバーロードの機能と合致するものです。
【参考】:Python Docs: functools --- 高階関数と呼び出し可能オブジェクトの操作
singledispatchを読み込む
singledispatchは、”import singledispatch”で読み込んで使用します。
関数の定義は、”@singledispatch”デコレータを使用します。基本的な使用方法は以下の通りです。
from functools import singledispatch
@singledispatch
def func_name(arg):
print(arg)
この例では、1行目の”from functools import singledispatch”で、singledispatchを読み込んでいます。2行目の”@singledispatch”でsingledispatchデコレータを宣言し、3~4行目に”def”で始まる関数の定義を行っています。
なお、関数ではなくメソッドで用いるには、”@singledispatch”を”@singledispatchmethod”デコレータに置き換えて使用することができます。
関数にオーバーロード属性を追加する
関数にオーバーロード属性を追加するには、ジェネリック関数の register属性を使用します。以下が使用方法です。
@func_name.register
def _(arg: int):
# 実際の操作を追加
print(arg)
@func_name.register
def _(arg: list):
# 実際の操作を追加
print(arg)
上の例では、引数の型別に処理を追加する際の振り分けが可能です。”arg: int”で整数型を、”arg: list”でList型を宣言します。
Pythonのオーバーロードのサンプルコード
Pythonのオーバーロードのコードを実際に作成してみます。以下がサンプルコードです。先ほどの基本用法に従って、引数を文字列型、整数型、List型を受け付けて定義別に結果を出力します。どれにも当てはまらない場合は、"このタイプは表示できません。"と表示します。
from functools import singledispatch
@singledispatch # singledispatchを使用します
def func_name(arg): # 念のため、例外処理として追加します
print("このタイプは表示できません。")
@func_name.register # オーバーロード属性を追加します
def func_name_str(arg: str): # 文字列型を定義します
print("文字列型:",arg)
@func_name.register # オーバーロード属性を追加します
def func_name_int(arg: int): # 整数型を定義します
print("整数型:", arg)
@func_name.register # オーバーロード属性を追加します
def func_name_list(arg: list):# List型を定義します
print("List型:", arg)
# サンプルデータを処理する(引数を変えて関数をコールします)
func_name("文字列を表示") # 文字列として処理します
func_name(15) # 整数型として処理します
func_name([1, 2, 3]) # List型として処理します
func_name(3.14) # 浮動小数点の数値は未登録です
サンプルコードの実行例
サンプルコードでは、次のように引数を”func_name()”に与えますが、その型を変えて順に呼び出しています。なお、引数の”3.14”は浮動小数点の型です。このサンプルでは意図的に浮動小数点の型を定義しておらず、例外処理のチェック関数で処理しています。
# サンプルデータを処理する(引数を変えて関数をコールします)
func_name("文字列を表示") # 文字列として処理します
func_name(15) # 整数型として処理します
func_name([1, 2, 3]) # List型として処理します
func_name(3.14) # 浮動小数点の数値は未登録です
実際にコードを実行すると、次の結果が得られます。
文字列型: 文字列を表示
整数型: 15
List型: [1, 2, 3]
このタイプは表示できません。
サンプルコードの例外処理
先ほどのサンプルコードの例外処理は、次のようにprint()表示させるだけの簡易なものでした。
@singledispatch # singledispatchを使用します
def func_name(arg): # 念のため、例外処理として追加します
print("このタイプは表示できません。")
このコードを、正しく例外処理としてNotImplementedError()で実装すると次のようになります。
@singledispatch # singledispatchを使用します
def func_name(arg): # 念のため、例外処理として追加します
raise NotImplementedError("このタイプは表示できません。")
サンプルコードでは浮動小数点の処理が定義されていないため、例外処理で処理されるコードは、最終行の”3.14”を引数として渡した場合に、NotImplementedError()が呼び出されます。
func_name(3.14) # 浮動小数点の数値は未登録です
引数を順に変えながら関数を呼び出しますが、”func_name(3.14)”を関数呼び出しした際の、例外処理の実行例は次の通りです。
文字列型: 文字列を表示
整数型: 15
List型: [1, 2, 3]
Traceback (most recent call last):
File "example.py", line 23, in <module>
func_name(3.14)
File "example.py", line 4, in process_data
raise NotImplementedError("このタイプは表示できません。")
NotImplementedError: このタイプは表示できません。
この場合のように、例外処理として適切ではあるもののプログラムの実行時にランタイムエラーとなり、処理を停止します。致命的な問題の場合は例外処理が有効ですが、影響のないものについては処理を継続させるなどを検討することもできます。
【参考】:Python Docs: 組み込み例外
Pythonでオーバーロードの実装は難しくありません
Pythonは色々な用途に利用できますが、オーバーロードで関数の多機能化が簡単に実現できます。引数の数や違いを気にせずにコーディングができるメリットがあります。オーバーロードの実装は難しくありませんので、テストコードなどで試してみると良いでしょう。
編集部オススメコンテンツ
アンドエンジニアへの取材依頼、情報提供などはこちらから