関数の引数

【 目次 】

引数のバリエーション

今回の参考記事は。

関数の定義時と呼び出し時において引数の指定のしかたにさまざまなバリエーションがあって混乱してしまったので。

関数定義時の引数の指定

必須の引数

通常、関数に引数がある場合、あたりまえの事だが関数呼び出し時に引数の指定を省略するとエラーになってしまう。

def func(arg1,arg2):
    pass

func("arg1_value")

実行結果

Traceback (most recent call last):
  File "ソースファイル名(フルパス)", line x, in <module>
    func("arg1_value")
TypeError: func() takes exactly 2 arguments (1 given)

引数が2個なのに1個しか与えられていないとpythonにおこられてしまった。

このような引数を指定しないとエラーになってしまう引数を、必須の引数と呼ぶ事にする。

これに対して、省略可能な引数も存在する。
省略可能な引数には、以降で述べる「デフォルトの引数」,「*引数」,「**引数」がある。
省略可能な引数は、関数のオーバロードのような効果を果たすことにもなる。

デフォルトの引数

デフォルトの引数値を指定することで、引数の指定を省略できる。
以下にその例を示す。

def func2(arg=u"デフォルト値"):
    print arg

通常どうり、引数を指定して関数を実行。

func2(u"引数の値")

実行結果

引数の値

引数を省略して実行してみる。

func2()

実行結果

デフォルト値

デフォルトの引数値を指定する場合、それ以降の引数も省略可能にしなければならない。

def func3(arg1, arg2="A", arg3="B"):
    print "arg1=", arg1, ",arg2=", arg2, ",arg3=", arg3

func3("z")

これは許される。

実行結果

arg1= z ,arg2= A ,arg3= B

しかし、これは許されない。

def func4(arg1="A",arg2,arg3="C"):
    print "arg1=", arg1, ",arg2=", arg2, ",arg3=", arg3

func4(,"B") # これはエラー

実行結果

  File "ソースファイル名(フルパス)", line x
    def func4(arg1="A",arg2,arg3="C"):
SyntaxError: non-default argument follows default argument

参考までにデフォルトの引数を使ってrange関数 を自作してみた。

def my_range(i_start=0,i_stop=None,i_step=1):
    if i_stop is None:
        i_stop = i_start
        i_start=0

    result = []
    i=i_start
    while i<i_stop:
        result.append(i)
        i+=i_step
    return result

引数の数を変えてmy_range関数を実行してみる。

# 引数をすべて指定
for i in my_range(5, 10, 2):
    print i,
print

# 開始と終了の引数のみを指定
for i in my_range(5, 10):
    print i,
print

# 終了の引数のみを指定
for i in my_range(10):
    print i,
print

実行結果

5 7 9
5 6 7 8 9
0 1 2 3 4 5 6 7 8 9

チュートリアル(4.7.1. デフォルトの引数値 - 4. その他の制御フローツール)にもあるとおり

デフォルトの引数は変数を指定でき、定義している側のスコープ (scope) で評価されるので、

i = 5

def f(arg=i):
    print arg
f()

実行結果

5

関数を定義した後でデフォルトの引数値に指定した変数の値を変更しても、その値は反映されない。

i = 6
f()

実行結果

5

変数iの値を変更しても関数はもとのiの値を憶えている(って言うかiの参照先が変更になっても、もとのiの参照先を記憶している)。

重要な警告: デフォルト値は 1 度だけしか評価されません。デフォルト値がリストや辞書のような変更可能なオブジェクトの時にはその影響がでます。...

チュウトリアルの例とは別に、
ユーザ定義のクラスのインスタンスを、デフォルトの引数の値として指定した例を以下に示す。

class MyCls:
    def __init__(self, attr):
        self.attr = attr

my_inst = MyCls("abc")

def f(arg=my_inst):
    arg.attr += "x"
    print my_inst.attr

f()
f()
my_inst.attr += 'y'
f()

デフォルトの引数にオブジェクトを指定すると、そのオブジェクトの参照を関数が持ち続けることになる。
そして、オブジェクトの属性を変更すると、その変更はデフォルトの引数の値となるオブジェクトの属性にも反映される事になる。

実行結果

abcx
abcxx
abcxxyx

参考までに、他の言語にもデフォルトの引数を使えるものがいろいろと

*引数 (可変長引数とも任意引数リストとも言われているようだ)

引数に*を付ける事で任意引数リスト(可変長引数)を受け取る関数を定義できる。

def func6(arg1,*args):
    print arg1,
    for arg in args:
        print arg,
    print

func6("a")
func6("a","b")
func6("a","b","c","d")

実行結果

a
a b
a b c d

これまでに述べたとおり、デフォルトの引数や可変長引数は見方によっては、関数のオーバロードという事もできる。

**引数 - キーワード引数が入った辞書を受け取る。

関数の通常の引数にディクショナリオブジェクトを渡す場合は。

def func7_1(arg):
    for key, value in arg.iteritems():
        print key, "=", value,
    print

dic = {"key1":"value1", "key2":"value2", "key3":"value3"}
func7_1(dic)

実行結果

key3 = value3 key2 = value2 key1 = value1

これに対して、**引数というものがある。

**引数を使うと残りの引数のすべてを名前付き引数(キーワード付き引数)の辞書として受け取る事ができる。

def func7_2(arg1,**args):
    print arg1,
    for key,value in args.iteritems():
        print key,"=", value,
    print

func7_2("a",key1="value1",key2="value2", any_key="any_value")

実行結果

a key2 = value2 key1 = value1 any_key = any_value

辞書はソートされないので結果の順番は不動になるので注意。

関数は、残りの引数すべてを名前付きの引数として受け取るので、**引数は必ず最後の引数として指定しなければならない。

引数の組み合わせ

必須の引数,デフォルトの引数,*引数,**引数を組み合わせて指定することができる。
この場合は、必ず、必須の引数,デフォルトの引数,*引数,**引数の順に指定する必要がある。

以下に必須の引数,*引数,**引数の組み合わせの例を示す。

def func8(arg,*list_args,**dict_args):
    print "arg=", arg
    print "list_args=", list_args
    print "dict_args=", dict_args

func8("A","B","C","D",x="E",y="F",z="G")

実行結果

arg= A
list_args= ('B', 'C', 'D')
dict_args= {'y': 'F', 'x': 'E', 'z': 'G'}

更にデフォルトの引数も一緒に使うと。

def func9(arg,default_arg="default_value",*list_args,**dict_args):
    print "arg=", arg
    print "default_arg=", default_arg
    print "list_args=", list_args
    print "dict_args=", dict_args

func9("a","x","b","c","d",b="b",c="c",d="d")
func9("A","DEFAULT_ARG","B","C","D",x="E",y="F",z="G")

実行結果

arg= a
default_arg= x
list_args= ('b', 'c', 'd')
dict_args= {'c': 'c', 'b': 'b', 'd': 'd'}
arg= A
default_arg= DEFAULT_ARG
list_args= ('B', 'C', 'D')
dict_args= {'y': 'F', 'x': 'E', 'z': 'G'}

上記の関数func9においてデフォルトの引数値default_valueを使うには*引数と**引数の指定も省略する必要があるようだ。

func9("a")

実行結果

arg= a
default_arg= default_value
list_args= ()
dict_args= {}

このように、呼び出し時に*引数および**引数の指定を省略すると空のタプルおよび空のディクショナリ値が渡されるようだ。

*引数,**引数に名前付き引数を使おうとして適当なコードを書いたら、おかしな事になって何がなにやら。

func9(arg="a",list_args=("b","c","d"),dict_arg={"key1":"value1", "key2":"value2", "key3":"value3"})

実行結果

default_arg= default_value
list_args= ()
dict_args= {'list_args': ('b', 'c', 'd'), 'dict_arg': {'key3': 'value3', 'key2': 'value2', 'key1': 'value1'}}

意味も無く、無謀な事はしない方が良いという結論(*引数には値を渡せないのかな?

関数呼び出し時の引数指定

これまでは、関数定義時の引数の指定方法について述べてきた。
ここでは、関数呼び出し時の引数の指定方法についてみていく事にする。

キーワード引数(名前付き引数)

キーワード引数と言って 関数は名前付きの引数を使っても呼び出すことができる。
名前付きの引数を使うと、任意の順序で引数を指定する事ができる

名前付きの引数を使うには、関数の定義時に引数の指定になにか特別な指定をする必要は無い。 以下のような何の変哲も無い関数の定義において、

def func5(arg1,arg2,arg3):
    print arg1,arg2,arg3

通常の関数呼び出しは、

func5("a", "b", "c")

名前付き引数による呼び出しは、

func5(arg3="a",arg2="b",arg1="c")

以下のように名前付き引数と通常の(名前無し)引数による関数呼び出しを混在させて使う場合、
始めの引数には名前無し,後の引数には名前付きを使う

これは許される。

func5("x",arg3="z",arg2="y")

しかし、これは許されない。

func5(arg3="a",arg2="b","c")

引数リストのアンパック

関数の引数をリストやタプル,ディクショナリを使ってパックして(まとめて)渡す事ができる。

*引数 - 複数の引数をリストやタプルに入れてまとめて渡す

任意引数リストとは逆に、関数呼び出し時に引数に*を付ける事で複数の引数をシーケンス型(リストやタプルなど)の値に詰め込んで一度に渡す事ができる。
これについては「リストを使って複数の引数を一度に渡す。」で既に述べた。

ここでは、デフォルトの引数を持つ関数の例を示す。

def func10(arg1,arg2,default_arg="default_value"):
    print arg1, arg2, default_arg

関数呼び出し時に*引数を使って、引数の値をリストやタプルに詰め込んでまとめて渡すと、

arg_tuple = ("value1","value2","new_value")
func10(*arg_tuple)

**引数 - 複数の引数を辞書に入れてキーワード付き引数としてまとめて渡す

ディクショナリを使って名前付き引数(キーワード付き引数)として引数の値をまとめて渡す事もできる。 関数呼び出し時に**引数を使って、

arg_dict = {"default_arg" : "new_value", "arg1" : "value1", "arg2" : "value2"}
func10(**arg_dict)

実行結果

value1 value2 new_value

キーワード引数の場合は引数の順番は関係無い。

デフォルトの引数の指定を省略する

*引数を使ってタプルを渡す場合にデフォルトの引数の指定を省略するには、

arg_tuple = ("value1","value2")
func10(*arg_tuple)

**引数を使ってディクショナリを渡すには

arg_dict = {"arg1" : "value1", "arg2" : "value2"}
func10(**arg_dict)

実行結果

value1 value2 default_value

残りの引数のみをパック

はじめの引数は通常通り,残りの引数のみをパックして指定してみてもよさそう。

arg_tuple = ("value2","new_value")
func10("value1", *arg_tuple)

名前付きの引数の場合は引数の順番は無視して。

arg_dict = {"arg2" : "value2", "arg1" : "value1"}
func10(default_arg="new_value", **arg_dict)

実行結果

value1 value2 new_value

任意の引数を受け取る関数の定義

前項で述べたように、引数の組み合わせの順序は必須の引数,デフォルトの引数,*引数,**引数の順に記述する必要がある。

func(arg1,..argn,def_arg1="xxx1",...def_argn="xxxn",*list_args,**dict_args)

そして、必須の引数やデフォルトの引数は、関数を使う側からみると任意引数リストとみる事もできる。

従って、任意の引数を持つ関数は、*引数,**引数を使って以下のようなコードで記述(表現)できる事になる。

func(*args, **kwargs)

以下のコードは異なる数の引数を持つ関数と引数を受け取って、その結果の2倍の値を返す関数の例である。

def double_func(func, *list_args, **dict_args):
    return func(*list_args, **dict_args) * 2

この関数に2つの引数をとる関数を渡してやると、

def sum_func2(a, b):
    return a + b

print double_func(sum_func2, 2, 3)

実行結果

10

3つの引数をとる関数の場合には、

def sum_func3(a, b, c):
    return a + b + c

print double_func(sum_func3, 2, 3, 4)

実行結果

18

このように*引数と**引数をもちいて、引数の数に依存しないフレキシブルな関数を表現できる。

括弧でくくった引数の定義

ちょっと変わったところでは、関数定義時の引数の指定方法として以下のような括弧でくくった引数の定義のしかたができるようで、
*引数を使わずにリストやタプルの値を渡す事ができる。

def func11 (arg, (arg0, arg1, arg2)):
    return arg + arg0 + arg1 + arg2

arg_tuple = ("a", "b", "c")
print func11("x", arg_tuple)

arg_list = ["a", "b", "c"]
print func11("x", arg_list)

arg_str = "abc"
print func11("x", arg_str)

実行結果

xabc
xabc
xabc

シーケンス型の値を引数として渡して、各要素を個々の引数(arg0, arg1, arg2)として受け取る事ができるようだ。

キーワード引数についてのまとめ

これまでに、何回かキーワード引数という言葉が出てきたので、ここで整理する事にする。
pythonには名前付きの引数の事を、キーワード引数とかキーワード付き引数とかいって、以下の3通りの使い方ができる。

同じキーワード引数という言葉が使われているので混乱してしまうのだが、それぞれケースで用い方が異なるので内容をよく理解しておく必要がある。

pythonの関数の引数は値渡しか参照渡しか?

ここまで関数について書いていて、「pythonの関数の引数は値渡し」これって当然と思っていたが。
ネットでいろいろ調べてみると参照渡しと書いてある記事が多くある。

あれ!俺が間違っていたのかと思いつつ更に調べてみると、どうやら「参照の値渡し」 (Java - もう参照渡しとは言わせない - Qiita) の事を参照渡しと言っているらしい。

本当かって?

def func15(a, b):
    a = "値を変更"
    b += 5
    print a, b

a = "初期値"
b = 0
func15(a, b)
print a, b

実行結果

値を変更 5
初期値 0

関数内で変数の値を変えても、ちゃんと関数呼出し後でも値は変化しないよ!

でもjavaでも同じだが、関数内で引数が参照しているオブジェクトの属性を変更してやる事はできるわけで

def func16(obj):
    obj.a = "値を変更"
    obj.b += 5
    print obj.a, obj.b

class MyCls:
    pass

obj=MyCls()
obj.a = "初期値"
obj.b = 0
func16(obj)
print obj.a, obj.b

実行結果

値を変更 5
値を変更 5

ところが関数内でこんなふうに引数objに別のオブジェクトを代入してしまうと、

def func16(obj):
    obj = MyCls()
    obj.a = "値を変更"
    obj.b += 5
    print obj.a, obj.b

引数objとグローバル変数のobjとはもはや別のオブジェクトを参照することになってしまう。

参照型の変数を値渡しすると、参照渡しのように見えてしまうというベテランプログラマには常識的な事が 誤って、pythonの関数の引数は参照渡しと伝わっているらしい。

この論理で言えばJavaだってC#だって「参照渡し」って事になってしまう。
昔のBASIC言語じゃあるまいし、引数を参照渡しで渡すようなPythonはそんな旧式な言語であるはずがない。

何だ、俺が間違っているわけではないんだと一安心。

ここに、ちゃんと書いてあるんだけどな。

  • 4.6. 関数を定義する

    関数を呼び出す際の実際のパラメタ (引数) は、関数が呼び出されるときに関数のローカルなシンボルテーブル内に取り込まれます。 そうすることで、引数は 値渡し (call by value) で関数に渡されることになります (ここでの 値 (value) とは常にオブジェクトへの 参照(reference) をいい、オブジェクトの値そのものではありません)

よくわからない人はこの記事を参考に。

ページのトップへ戻る