[Python]常識ですよ?と言われないための引数入門
2018-10-09

耳が痛いですね。というか記事のタイトルってこんな感じでいいんでしょうかね? 今回は引数の入門記事です。これからの勉強する方の下地になればいいなって思って書きました。

警告

この記事は Python3 で動作するように書き換えました。 いくつか Python2 では動作しない箇所があるので注意してください。

定義 (Definition)

用語自体は別に覚えなくてもいいですが、定義の際につかう引数を 仮引数 と呼びます。(一応ね)

この 仮引数 は4つの種類があります。次のように定義します。

>>> def test(start, end=None, *array, **mapping):
...     print(start, end, array, mapping)

>>> test(1, 10, 20, 30, 40, a=50, b=60, c=70)
1 10 (20, 30, 40) {'a': 50, 'b': 60, 'c': 70}

状況は謎ですが上記の例で雰囲気を感じ取ってください。

以下で説明していきます。定義順はこの通りでなければ SyntaxError: invalid syntax となります。

位置引数 (Define positional arguments)

呼び出し時に指定が必須な引数です。 =* がなく、単純に引数名だけが指定された引数とも言えます。 上記の例で言うと 1, 10, 20, 30, 40 が該当します。

挨拶をする関数を考えてみましょう。挨拶するフレーズを位置引数として定義してみます。

定義 呼び出し
def 挨拶(言葉):
    print(言葉)
>>> 挨拶('おはよう')
おはよう
>>> 挨拶()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 挨拶() missing 1 required positional argument: '言葉'

足りない場合はTypeErrorが発生します。動作に必須な引数はこれで定義するべきです。

参考
用語集 Python 3.7.1rc1 ドキュメント

デフォルト引数 (Define default arguments)

= (イコール) のついた引数をキーワード引数と呼びます。

このキーワード引数を関数定義に使用すると呼び出し時に指定した値をデフォルト値とすることができます。

「呼び出し時に指定しなくても普通はこの値を使ってほしい」という場合に利用するものです。

先程の挨拶する関数を少し改良してみます。 多くの場合は朝にするものなので、何も入力しない場合デフォルトで「おはよう」が出力されるようにしてみます。

定義 呼び出し
def 挨拶(言葉='おはよう'):
    print(言葉)
# 引数を指定しなくても呼び出せて便利!
>>> 挨拶()
おはよう
# 別のフレーズで挨拶したい場合は 引数を指定する
>>> 挨拶('こんばんは')
こんばんは

備考

Python3 からは 平仮名や漢字のようなマルチバイト文字も変数名(関数名)として使えるようになりました。

警告

デフォルト引数で指定した値は関数実行ごとに初期化されないことに注意してください。

>>> def test(default=[]):
...     return default
...
>>> l = test()
>>> print l
[]

>>> # lに変更を加える
>>> l.append(1)
>>> m = test()

>>> # オブジェクトが同じ
>>> l is m
True

>>> # だから加えた変更が残ってる
>>> print m
[1]

可変(ミュータブル)な値をデフォルト引数としないことが一番ですが、 もし必要がある場合は変更を加えたり関数外に持ち出さない等の対策が必要となります。

可変長位置引数 (Define variable arguments)

位置引数が足りないとTypeError と説明しましたが、 定義した以上の引数が与えられた場合もTypeErrorとなります。

可変長引数はこの 過剰な引数の受け皿 となります。(受け皿って何気にいい表現だと思いませんか?)

期待する引数の数が不定な場合に利用する引数と言えます。もちろん 0個 でもOK です。

JavaScript に慣れている方は スプレッド構文と同じものと理解してほぼ差し支えありません。

定義するには引数の前に *(アスタリスク) を1つ付けます。 受け取った引数は指定した順序でタプルに格納されるので、 for文などで ループして利用することが多いです。

今度は 会話 という関数を定義してみます。 可変長引数だけでは面白くないので 挨拶のフレーズだけは必須 というようにしてみます。

定義 呼び出し
def 会話(挨拶, *それ以降):
    print(挨拶)
    for 言葉 in それ以降:
        print(言葉)
# 少しそっけない
>>> 会話('おはよう')
おはよう
# 第二フレーズ以上しゃべろう!
>>> 会話('おはよう', '今日は天気いいね', 'え、なんで無視するの!?')
おはよう
今日は天気いいね
なんで無視するの!?

挨拶だけでなく会話のキャッチボールを楽しみましょう()

可変長キーワード引数 (Define keyword variable arguments)

Pythonでは 定義されていない引数をキーワード引数で呼び出した場合もTypeErrorとなります。

キーワード可変長引数は 過剰なキーワード引数の受け皿 となります。(受け皿って(ry))

定義するには引数の前に **(アスタリスク2つ) を付けます。これはコマンド等のオプションを受け取るときなどによく使われます。

ラーメンを注文し、麺の硬さや油の量、トッピングなどをキーワード可変長引数で受け取る例で考えてみましょう

定義 呼び出し
def ラーメン(, **細かい注文):
    print(f'{味}ラーメン')
    for key, value in 細かい注文.items():
        print(f'{key}{value}')
    print('ほか全部普通で!')
>>> ラーメン('醤油', ='硬め', ='少なめ', チャシュー='あり', コーン='なし')
醤油ラーメン
麺硬め
油少なめ
チャシューあり
コーンなし
ほか全部普通で

警告

可変長引数は複数定義することができません。どこが区切りなのかわからないですからね。

可変長引数(*) とキーワード可変長引数(**) は混在できます

備考

キーワード可変長引数では 引数は 受け取った時点で辞書となるため順序は無視されます。 OrderedDictで指定した順番に引数が入らないのはこのためです(脱線)

>>> from collections import OrderedDict
>>> # この指定方法は
>>> OrderedDict(a=1, b=2, c=3)
OrderedDict([('a', 1), ('c', 3), ('b', 2)])
>>> # OrderedDictからすれば辞書を指定しているのと同じ
>>> OrderedDict({'a': 1, 'b': 2, 'c': 3})
OrderedDict([('a', 1), ('c', 3), ('b', 2)])

>>> # 話 脱線中
>>> # 順序を保持したければ順番に入れるか
>>> od = OrderedDict()
>>> od['a'] = 1
>>> od['b'] = 2
>>> od['c'] = 3
>>> od
OrderedDict([('a', 1), ('b', 2), ('c', 3)])

>>> # 1行で書きたければタプルのリストで(リストのリストでも可)
>>> OrderedDict([('a', 1), ('b', 2), ('c', 3)])
OrderedDict([('a', 1), ('b', 2), ('c', 3)])

>>> # 上記はdict.items()と同じ形式です
>>> od.items()
[('a', 1), ('b', 2), ('c', 3)]

>>> # 辞書にすることもできるんです(どうでもいい)
>>> dict([('a', 1), ('b', 2), ('c', 3)])
{'a': 1, 'c': 3, 'b': 2}

備考

可変長引数は関数外に持ち出しても問題ありません。

キーワード専用引数 (Define keyword only arguments)

4種類と言ったな、あれは嘘だ(5つめ)

まぁこれ自体は引数として呼び出せないので、他の引数と同列に扱ってよいかは微妙ですが。

Python3 で取り入れられた引数構文で、 単体の * を引数として定義することで それ以降(右側)の引数を呼び出す際には キーワード指定しなければ呼び出せなくなるというものです。(呼び出し方法の強制)

公式では キーワード専用 (3.5の用語集)とか キーワードのみ (3.7以降の用語集)と書かれていましたが、 なんか後者はしっくりこないのでここでは キーワード専用引数と呼ぶことにします。

個人的にはあまり使わないんですが、もし使うとすると 「可変長キーワード引数を定義するほどではないけど、指定するときは キーワード指定してほしい」ときになるでしょうか。

関数の呼び出しについては次のセクションから説明したほうが混乱は少ないと思ったんですが、 まぁこれらを切り離して考えることはできませんし、上記で既に呼び出しはやっているので気にせず書こうと思います。

先程のラーメンの関数で2種類のトッピングに絞ってみます。

定義 呼び出し
def ラーメン(, *, メンマ, チャーシュー='なし'):
    print(f'{味}ラーメン, メンマ{メンマ}, チャーシュー{チャーシュー}')
>>> ラーメン('みそ', メンマ='なし', チャーシュー='あり')
みそラーメン, メンマなし, チャーシューあり

>>> ラーメン('みそ', メンマ='なし')
みそラーメン, メンマなし, チャーシューなし

# メンマは必須
>>> ラーメン('みそ')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: ラーメン() missing 1 required keyword-only argument: 'メンマ'

# 位置引数指定はできません
>>> ラーメン('みそ', 'なし', 'あり')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: ラーメン() takes 1 positional argument but 3 were given

引数の後ろに キーワード専用の指定があるため、 それ以降のメンマ、チャーシュー引数を指定する場合はキーワード指定が必須となります。

あまり深く理解できなくても、 キーワード引数で明示したほうが何をしてるかわかりやすいな、くらいの感想は持ってもらえましたか? それでOKです。

備考

ちなみに可変長位置引数 との同居はできません。 SyntaxError になります。

>>> def test(a, *d, *, c): print(a, c)
File "<stdin>", line 1
  def test(a, *d, *, c): print(a, c)

呼び出し (Call)

既に散々やってますが、関数の定義以外で () をつけると関数を実行できます。 呼び出しとも言います。(この記事では呼び出しといいます)

定義の際の引数を 仮引数 と呼ぶのに対し、 呼び出しの際の引数を 実引数 と呼びます。(一応ね)

位置引数の呼び出しは省略しまして.. (強いて言うなら ラーメン('みそ', 'なし', 'あり') みたいに引数名だけを指定して呼び出すことです)

キーワード引数 (Call keyword argument)

定義でのキーワード引数は デフォルト引数(暗示) でしたが、 呼び出しでのキーワード引数は 指定(明示)を意味します。

明示的な指定では引数の定義順を無視することができます。 使い慣れた datetime.datetime の引数を指定してみます。 これは日時を扱う datetime オブジェクトの生成に利用されます。

ちなみに datetime.datetime 関数の 引数は こんな感じになっております。

Help on class datetime in module datetime:

class datetime(date)
 |  datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])
 |
 |  The year, month and day arguments are required. tzinfo may be None, or an

左から , , , 時間, , , マイクロ秒, タイムゾーン を指定することになっていて、年,月,日 だけ必須です。

1988/5/22 00:00:30 を指すように引数を適当に指定してみます.

>>> from datetime import datetime

# 必要な引数が指定できていれば順番は関係ない
>>> datetime(day=22, month=5, year=1988, second=30)
datetime.datetime(1988, 5, 22, 0, 0, 30)

# このように入力したのと同じ
>>> datetime(1988, 5, 22, 0, 0, 30)
datetime.datetime(1988, 5, 22, 0, 0, 30)

# キーワード引数より前に位置引数があってはならない
>>> datetime(day=22, month=5, second=30, 1988)
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument

引数が多い関数では途中の引数などを入力を省略するために キーワード引数指定することがよくあります。

また、ライブラリのアップデート等で、関数の引数順が入れ替わったりしてもキーワード引数で明示している場合は、 影響なく利用できたりするのでそういった背景で利用することもあるかもしれません。これは状況によりますが。

引数の展開 (Unpack arguments)

呼び出しの場合は *, ** は引数の展開を意味します。アンパックとも言います。

リスト(タプル)や辞書の内容をそのまま引数として展開(適用)することができます。

  • *(アスタリスク1つ) はリスト(タプル)を「位置引数」「デフォルト引数」「可変長引数」に対して展開します。
  • **(アスタリスク2つ) は辞書を「位置引数」「デフォルト引数」「キーワード可変長引数」に対して展開します。

今回は 1988/5/22 00:45:30 を指す datetime オブジェクトを作るように 引数を展開してみましょう (datetime は 引数が多くてわかりやすいのでこの例には最適だな〜)

>>> datetime(*[1988, 5, 22, 0, 45, 30])
datetime.datetime(1988, 5, 22, 0, 45, 30)

>>> datetime(**{'year': 1988, 'month': 5, 'day': 22, 'minute': 45, 'second': 30})
datetime.datetime(1988, 5, 22, 0, 45, 30)

>>> datetime(1988, *[5, 22], minute=45, **{'second': 30})
datetime.datetime(1988, 5, 22, 0, 45, 30)

>>> datetime(1988, *[5, 22], **{'second': 30}, minute=45)
datetime.datetime(1988, 5, 22, 0, 45, 30)

# ``**`` (辞書の展開)は ``*`` (リストの展開)より前に来てはいけない
>>> datetime(1988, minute=45, **{'second': 30},  *[5, 22])
  File "<stdin>", line 2
SyntaxError: iterable argument unpacking follows keyword argument unpacking

# 引数がかぶってはいけない(minuteを指す引数が2つある)
>>> datetime(1988, *[5, 22], **{'second': 30, 'minute': 45}, minute=45)
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: type object got multiple values for keyword argument 'minute'

# リスト(タプル)を**で展開できない(あたりまえですね)
>>> datetime(**[1988, 5, 22, 0, 45, 30])
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: type object argument after ** must be a mapping, not list

備考

引数の展開というと JavaScript には apply がありますが、実はPythonにも apply関数 がありました(Python2以前)

>>> apply
<built-in function apply>

>>> from datetime import datetime
>>> apply(datetime, (1988, 5, 22))
datetime.datetime(1988, 5, 22, 0, 0)

これは *** ができるまで使われていた方法らしく、現状で必要になることはほとんどないでしょう。

Python3ではビルトインからなくなっていました。

書き方についてまとめてみます。

書き方 定義 呼び出し
arg=value デフォルト引数 明示的な値の指定
*args 可変長引数 リストの展開
**kwargs キーワード可変長引数 辞書の展開

スコープ (Scope)

備考

スコープについては覚えなくても最低限の利用はできます。

まだ自分には難しいという人はスキップして大丈夫です。

関数は ローカルスコープ と呼ばれるPythonで最も狭いスコープを持ちます。

関数の中で定義した オブジェクト (関数、変数、クラスすべて) 及び、 受け取った 引数 は この ローカルスコープ に属します。

そのため 外側(グローバルスコープ) の変数名と重複したり、 他の関数の 変数名(あるいは引数名) と重複しても書き換える心配はありません。

>>> a = 1
>>> b = 2
>>> # 外側の変数と同じ引数を定義して
>>> def scopetest(a, b=3):
...     print a, b
...
>>> # 呼び出しても
>>> scopetest(0)
0 3
>>> # 外側の変数は書き換わっていない
>>> print(a, b)
(1, 2)

スコープについてもっと知りたい方は Pythonのスコープについて をごらんください。

参照 (Reference)

このセクションはおまけです。

Pythonの引数はすべて C言語でいう参照渡し です。 (厳密な定義は違うかもしれませんが) 簡単に言うと引数としてリファレンスが渡っていくため、関数内であっても同じオブジェクトを参照していることになります。

以下を見てください。

>>> def ref(a):
...     print(id(a))
...

>>> # 27857240 == 27857240
>>> i = 1
>>> print(id(i))
27857240
>>> ref(i)
27857240

>>> # 140186227500816 == 140186227500816
>>> s = 'a'
>>> print(id(s))
140186227500816
>>> ref(s)
140186227500816

>>> # 140186226383256 == 140186226383256
>>> l = [1]
>>> print(id(l))
140186226383256
>>> ref(l)
140186226383256

関数内でもアドレスが変わっていません。

参照渡し(リファレンス渡し)というと書換可能なイメージがあるかもしれませんが、 Pythonでの書換可否は(基本的に)オブジェクトのミュータブル(可変)・イミュータブル(不可変)によって決まります。

代表的なイミュータブルなオブジェクトを以下に列挙しました。

  • int
  • str
  • bool
  • None
  • tuple
  • frozenset

これらはオブジェクト自身を変更することができないため、 関数で値渡しが行われているように感じるかもしれませんが、単なるオブジェクトの性質によるものです。