第3章 定義
3-0.定義とは
プログラミング言語では、オブジェクトを生成する際に宣言、定義
(definition)を行います。
宣言は変数などのシンボルの使用の告知、定義は実際のとメモリの確保、初期
化(initialize)は定義されたオブジェクトが使えるようにすることです。
Pythonでは通常、宣言を省略して定義や初期化のみでオブジェクトを生成しま
す。
また、定義は一部を除き、同時に初期化を行います。
しかし、新出の識別子を自動的に定義する機能は無く、未定義の識別子を代入
などの対象以外に使用するとエラーとなります。
また、識別子自体は単なるラベルですので、どんなオブジェクトにも再定義可
能ですが、オブジェクト自身はきっちりと判断され、その場に応じて適切に変化
する、といったことはありません。
※例えば、文字列と数値に'+'を適用すると、数値を文字列とみなしたりするこ
となく、エラーを発します。
つまり、いわゆるタイプルーズ(type loose)の言語ではない、ということです。
なお、λ式や内包表記によるオブジェクト定義は、第2章でリテラルとして扱
っていますのでそちらを参照してください(基本的に式扱いです)
3-1.代入文による定義
3-1-0.基本形
定義及び初期化を最も簡単に行う方法は、リテラルや式の代入によるものです。
もっとも基本的な形式は以下の通りです。
変数 = 式
右辺の式が評価された結果が、変数に代入(assignment)されます。
変数名(シンボル)が、今まで使われていないものであれば、代入の瞬間、宣言
と定義と初期化が行われます。
ちなみにPythonのオブジェクト名は全ていわゆる参照(リファレンス/ポイン
タ)のラベルですので、正確には以下のような手順を取ります。
1.式の結果を表すオブジェクトが生成される。
2.左辺の変数(シンボル)にラベルとして、オブジェクトの番地が渡される。
ラベルとしてシンボルを与えられないオブジェクトは、式の評価されている時
のみ存在します。
そうでなければ、ラベルやコンテナからの全ての参照が終了した時点で、オブ
ジェクトは破棄されます。
※以上の理由から、本来ならば「変数(variable)」や「代入(assignment)」とい
う用語は誤解を招くので避けた方が良いのかもしれません(assginmentは「代入」
と訳されるが、本来「割り当て」という意味)しかし、「名札(label)」や「記
号(symbol)」および「束縛(bind)」などの用語(関数型プログラミング言語でよ
く使われる用語で、こちらの方が実情に近い)よりは「変数」「代入」の方が一
般のプログラマの方々に馴染みがあると考えて、以降も敢えてこのように書きま
す。また、本質的に参照であるといっても、オブジェクトの実体が不変
(immutable)である場合は、実質上の扱いは変数/代入となんら変わりません
(可変オブジェクトは若干影響します)
なお、代入子の仲間に'+='などの演算代入子(operate assignor)がありますが、
'='と異なり、定義済みの変数にしか使用できませんので、これによって初期化
/定義することはできません。
3-1-1.一括代入書式
'='代入子は、連続して使用することができます。
変数1 = 変数2 = 変数3 = 式
上記の場合は、最右辺の式の値が、変数1,2,3に順次代入されます。
原理的にはいくつ繋いでも構いません。
また、複数の代入(多重代入)を一括して書くこともできます。
変数1, 変数2, 変数3 = 式1, 式2, 式3
これは、意味合いとしては以下と同じです。
変数1 = 式1
変数2 = 式2
変数3 = 式3
左辺のターゲットが複数で、右辺がコレクションオブジェクトの場合、その要
素が展開されて代入されます。
>> lst = [10, 20, 30]
>> a, b, c = lst
>> print(a, b, c)
10 20 30
※Pythonでは、オブジェクトをコンマで区切ると、タプルというシーケンス型の
コレクション構造となるので、リストの展開と多重代入は、実は仕組は同じです。
これをシーケンスの開梱(unpack)といいます。
さて、ここまではターゲットの数とコレクションのアイテムの数が同じでなけ
ればなりませんでした。
Python3から導入された「残余(rest)」を扱う書式では、ターゲットの一部を
リストとして扱うことにより、余った部分を収納することができるようになりま
した。
書式としては、ターゲットの変数の前にアスタリスク('*')を置くことによっ
て、残余を収納する変数を決めます。
>>> a, b, c, d, *e = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
>>> print(a,b,c,d,e)
0 1 2 3 [4, 5, 6, 7, 8, 9]
>>> a, b, c, *d, e = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
>>> print(a,b,c,d,e)
0 1 2 [3, 4, 5, 6, 7, 8] 9
>>> a, b, *c, d, e = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
>>> print(a,b,c,d,e)
0 1 [2, 3, 4, 5, 6, 7] 8 9
>>> a, *b, c, d, e = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
>>> print(a,b,c,d,e)
0 [1, 2, 3, 4, 5, 6] 7 8 9
>>> *a, b, c, d, e = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
>>> print(a,b,c,d,e)
[0, 1, 2, 3, 4, 5] 6 7 8 9
>>>
なお、開梱の逆として、一つの変数に複数の値を収納する梱包(pack)という書
式もあります。
>>> a = 1, 2, 3
>>> a
(1, 2, 3)
>>>
しかし、カンマはタプルを作る演算子でもあるので、これは特殊な書式という
よりは、カンマで区切られたタプルを単に左辺の変数に代入しているだけ、とい
う見方も出来ます(詳しくは演算子の項を参照してください)
3-2.定義文
3-2-0.定義文によって定義されるオブジェクト
定義文によって定義されるオブジェクトは、関数オブジェクトとクラスオブジ
ェクトの2種類です。
正確には、クラスオブジェクトに属するメソッドオブジェクトやプロパティー
オブジェクトも定義文によって定義しますが、少なくともどちらも書式的には関
数定義にそっくりです。
さらに、継続する反復子(iterator)を生産(yield)するジェネレータ(発生器
generator)も、関数と同様の書式で書かれます。
この「定義文」の項では、関数定義とクラス定義の2種類を扱います。
メソッド定義はクラス定義の中で、プロパティー定義は3-4の「修飾子」の項
で、ジェネレータ定義は関数の項で説明します。
3-2-1.関数定義
3-2-1-0.基本
プログラムでは、再利用される処理の単位をなんらかの形で纏めます。
クラッシックな言語ではサブルーチンという形で、構造化された言語では「手
続き(procedure)」や「関数(function)」という形で纏められます。
Pythonではこういった処理ブロックの単位は、一括して「関数」と呼ばれるも
ので纏めます。
Pythonでは、関数という言葉を「呼び出し可能オブジェクト」の意味として扱
います。
よって、マニュアルの一部には(わざと)呼び出し可能な「関数以外の」オブ
ジェクトを「関数」と呼んでいる場合もあります。
ここで扱う書式によって生成されるのは、'function'クラスのインスタンスで
ある関数オブジェクトです(lambda式を使う方法は「第3章 リテラル」の関数
リテラルの項を参照してください。また、関数オブジェクトについての詳細な情
報は「第5章 オブジェクト」を参照してください)
関数定義は、一般に以下のように行います。
def 関数名([変数1,変数2...]):
実行文1
実行文2
…
関数名と変数(仮引数)名は、識別子のルールに従います。
関数は呼び出されると、実行文に書かれた部分を実行します。
実行文は、インデントしたブロックの中に記述します(この中に、さらに制御
構造などの構造文が入らない限り、インデントの深さは一定とします)
関数を呼び出す際の引数(実引数 argument)は、仮引数(parameter)として0〜
いくつでも指定することが出来ます。
関数は呼び出す際、仮引数として定義に書いた数だけ引数を必要とします(過
不足があってはいけません)
3-2-1-1.戻り値とreturn文
副作用を目的としない関数は、戻り値(return value)を返す必要があります。
※正確には、副作用を目的とする処理ブロックは手続き(procedure)と呼ばれる
方が相応しいのでしょうが、Pythonでは副作用や戻り値の有無に関係なく「関数」
という用語で統一されています。
戻り値は関数定義文中の、return文で設定します。
return 式
return文は、関数定義(及びメソッドなど、defキーワードで始まる類似した定
義)内で書かれなければ、エラーとなります。
>>> def func(x, y):
... return x + y
...
>>> func(5, 10)
15
>>>
実行文の内容が、上記のように1行で済むようなものなら、ブロックを作らず
に':'の後に続けて書くことも出来ます。
なお、ついでに言えば、単純計算をするような式であれば、関数リテラルを使
ったほうが簡単で明快かもしれません。
>>> def func2(x, y): return x + y
...
>>> func2(5, 10)
15
>>> func3 = lambda x, y: x + y
>>> func3(5, 10)
15
>>>
return文(及び後述のyield文)の無い関数及び、returnキーワードに続く式を
省略した場合は、戻り値として特殊オブジェクトNoneを返します。
Noneの詳しい性質はオブジェクトの項に譲りますが、基本的には捨てられる値
です。
※もし、C言語のいくつかの標準関数のように、実行内容の成功や失敗の結果を
返したいのなら、TrueかFalseを返すよう、にすべきでしょう。
return文が実行されると、関数の処理はそこで直ちに終了され、処理が呼び出
し元に戻ります。
よって、retrun文の後に実行文を書いたり、複数のreturn文を書く場合は、if
文などの条件分岐構文を入れなければ、最初のreturn文が実行された時点で以降
は全て無効になります。
>>> def func():
... return 100
... a = 10
... b = 20
... return a + b
...
>>> func()
100
>>>
なお、複数の値を返す場合は、カンマで区切ってタプルにして返すのが一般的
です(シーケンスの開梱による多重代入が可能です)
>>> def func():
... return 10, 20, 30
...
>>> func()
(10, 20, 30)
>>> a, b, c = func()
>>> a
10
>>> b
20
>>> c
30
>>>
3-2-1-2.ジェネレータとyield文
ジェネレータ(generator)は、反復子(iterator)をつくる関数です。
反復子はリテラルの項で反復子リテラルとして記述する方法が出てきましたが、
正式にはジェネレータを用いて定義します。
反復子の詳しい説明はオブジェクトの項に譲るとして、ここでは反復子の動作
から、ジェネレータの定義の仕方を見て行きましょう。
>>> def gen(a):
... yield a * 10
... yield a * 20
... yield a * 30
...
>>> itr = gen(10)
>>> next(itr)
100
>>> next(itr)
200
>>> next(itr)
300
>>> next(itr)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
>>> itr2 = gen(10)
>>> for x in itr2:
... print(x, end=' ')
... else:
... print()
...
100 200 300
>>>
反復子をnext関数に渡すと、ジェネレータのyield文で指定された部分の値を
返します。
この点はreturnによく似ているのですが、反復子はここまでの実行経緯を記憶
していて、次に呼び出された時には、その次の行から実行を再開します。
ジェネレータは通常、反復子リテラルのようにwhile文やfor文などの反復構造
を持つ構文が中に入ります。
※下の例は、反復子リテラルの項で
(x + y for x in (10,20,30) for y in (1, 2, 3))
として作ったものと同じです)
>>> def gen():
... for x in (10, 20, 30):
... for y in (1, 2, 3):
... yield x + y
...
>>> i = gen()
>>> for x in i:
... print(x, end=' ')
... else:
... print()
...
11 12 13 21 22 23 31 32 33
>>>
しかし実際には反復子の名に反して反復構造を持つ必要は無く、適宜中断/再
開可能なブロックとして使うことも可能です。
なお、yield文に渡す式の部分を省略すると、return文と同じようにNoneを返
します。
3-2-1-3.引数のデフォルト値(default argument values)
関数は、呼び出し時に仮引数で設定されたのと同じ数の引数を必要とします。
しかし、ある引数が通常的によく使用される場合、デフォルト値をセットする
ことにより、省略することが出来ます。
デフォルト値は、『仮引数=デフォルト値』として設定します。
>>> def func(x, y=10):
... return x + y
...
>>> func(5,20)
25
>>> func(5)
15
>>>
なお、全ての引数にデフォルト値を設定することはできますが、デフォルト値
を設定した仮引数の後に、デフォルト値を設定しない通常の引数はセットできま
せん。
また、デフォルト値は一度だけしか評価されませんので、可変オブジェクトを
設定した場合は、呼び出し間でその値が共有されることに注意してください。
(以下は、Python公式ドキュメントのチュートリアルに掲載されている例です)
>>> def func(x, y=[]):
... y.append(x)
... return y
...
>>> func(1)
[1]
>>> func(2)
[1, 2]
>>> func(3)
[1, 2, 3]
>>>
リストについての詳細はオブジェクトの項を参照してください。
ここでは、デフォルト変数として初期化されたリストがそのまま2回目以降も
継続されて使用されていることが確認できます。
もし、毎回リストを初期化したい場合は、
def func(x, y=None):
if y == None:
y = []
y.append(x)
return y
とすると良いでしょう(日本語版のチュートリアルの初版のサンプルコードは、
インデントが間違っています)
なお、これは一種のクロージャ(閉包)として働くため、この性質を逆に利用し
てコードを書くことも可能です。
以下は呼び出す度に値を加算する累算器(accumulator)の一種です。
>>> def ac(x, y=[0]):
... y[0] += x
... return y[0]
...
>>> ac(1)
1
>>> ac(2)
3
>>> ac(3)
6
>>>
ちなみに関数リテラル(lambda式)でも、デフォルト引数は書けます。
最初の例をlambda式で書くと、
func = lambda x, y=10: x + y
のようになります。
しかし、累算器を書こうとすると、かなりトリッキーな形になります(以下の
コードは「遊び」ですので、無理に理解する必要はありません)
>>> ac = lambda x, y=[0]: 0 if y.__setitem__(0, y[0]+x) else y[0]
>>> ac(1)
1
>>> ac(2)
3
>>> ac(3)
6
>>>
【教訓】なんでもlambdaで書こうとしてはいけません(笑)
3-2-1-4.任意の個数の引数に対応する
Pythonはリストやタプルなどのコレクションを引数として関数に渡すことがで
きるので、任意の個数の引数に対応する必要はあまり無いのですが、簡便的にそ
のような関数を書きたい場合には、方法があります。
def 関数名(*引数タプル変数名):
実行文
たとえば、組み込み関数のsumは、コレクションのアイテムの値の合計を返す
関数ですが、直接書き込む場合には、
sum((1, 2, 3))
と書き込まなければなりません(当然です。直接書くなら、+で繋げば済むので
すから)
これを無理やり「引数を合計する関数」に変えるなら、以下のように行います。
>>> def xsum(*args):
... k = 0
... for x in args:
... k += x
... return k
...
>>> xsum(1,2,3)
6
>>>
仮引数の前に'*'をつけると、その変数は引数をまとめてタプルとして受け取
ります。
>>> def f(*x):
... return x
...
>>> f(1,2,3)
(1, 2, 3)
>>>
タプルはリストと同じシーケンス型のコレクションなので、添え字(index)を
用いて参照することができます。
>>> def f(*x):
... return x[2]
...
>>> f(1,2,3)
3
>>>
ミニ言語っぽく使用する関数群を用意する時などには、この任意個数の引数を
受け取る方法が役に立つかもしれません。
3-2-1-5.キーワード引数を辞書で受け取る
関数は呼び出す際に、引数を単に順にならべれば順に関数に引き渡され、関数
内では仮引数として書かれた部分に代入されます。
Pythonでは、関数内での仮引数の名前が分かっているのなら、仮引数の名前を
指定して、ターゲットの仮引数に代入することができます。
これをキーワード引数といいます(詳しくは「式」の章の「呼び出し」の項を
参照)
ここでは、簡単にキーワード引数の動作を見ておきます。
>>> def f(a, b, c):
... return a * b - c
...
>>> f(2, 5, 10)
0
>>> f(5, 10, 2)
48
>>> f(c=2, a=5, b=10)
48
>>>
'='を使って仮引数名を指定すると、指定された仮引数に引数の値が渡される
ことがわかります。
さて、このキーワード引数による呼び出しを辞書として利用したい場合は、以
下のように記述します。
※辞書はマップ型のコレクションオブジェクトで、リストのインデックスが文字
などになったもので、他言語では連想配列、ハッシュなどと呼ばれています。詳
しくは「オブジェクト」の章を参照してください。
def 関数名(**引数辞書変数名):
実行文
以下に、実行例を上げます。
>>> def f(**d):
... return d
...
>>> D = f(a=10,b=20,c=30)
>>> D
{'a':10, 'c':30, 'b':20}
>>> D['a']
10
>>> D['b']
20
>>> D['c']
30
>>>
任意個数の引数に対応する時のように、ミニ言語を作ったり、あるいは他のス
クリプト言語への橋渡しなどに使用すると威力を発揮するでしょう。
Pythonモジュールでは、Tcl言語とのインターフェイスであるtkinterモジュー
ルが、このような形式を多用しています。
3-2-1-6.特殊仮引数の順序
デフォルト値(デフォルト値つきの仮引数)、引数タプル、引数辞書(キーワー
ド引数の辞書)など、Pythonでは様々な仮引数が設定できますが、これらが混在
する場合には、順序が決まっています。
def 関数名(通常の仮引数, デフォルト値, 引数タプル, 引数辞書):
どれとどれを組み合わせても構いませんが、順序を間違えるとエラーが出ます。
ちなみに、引数タプルや引数辞書は、通常の仮引数やデフォルト値と一緒に使
うと、これらの仮引数で指定されていない引数(引数の残余)のみを収納します。
なお、当然ですが、2個以上の引数タプルや、2個以上の引数辞書を書くこと
はできません。
【全てのキーワードを入れてみた例】
>>> def allparam(a, b=100, *c, **d):
... return a,b,c,d
...
>>> allparam(1,2,3,4,5)
(1, 2, (3, 4, 5), {})
>>> allparam(1,2,3,x=4,y=5)
(1, 2, (3,), {'y': 5, 'x': 4})
>>> allparam(1,2,3,4,c=5) 【引数タプルの'c'は、指定なしと見なされる】
(1, 2, (3, 4), {'c': 5})
>>> allparam(1,2,3,4,d=5) 【引数辞書の'd'は、指定なしと見なされる】
(1, 2, (3, 4), {'d': 5})
>>> allparam(1,2,3,c=4,d=5)
(1, 2, (3,), {'c': 4, 'd': 5})
>>>
【上記の関数でエラーになる引数指定】
・キーワード引数は通常の引数の後にかかなければならない
>>> allparam(a=1,2,3,4,5)
File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
>>>
・最初の引数(位置引数)が'a'に代入されるので、'a'が多重代入となる
>>> allparam(1,2,3,4,a=5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: allparam() got multiple values for keyword argument 'a'
>>>
3-2-1-7.関数に対する情報
3-2-1-7-0.Pythonインタープリタと閲覧(inspect)
この項で取り上げる説明文(documentation string, docstring)や注釈
(annotation)は、直接実行には影響を及ぼしませんが、関数のユーザー(そして
それは、自分の記述を忘れてしまった未来のあなたかもしれない)に、help関数
を通して情報を与えるための記述方法です。
コンパイル型の実行環境、あるいはソースファイルを読ませるのが主な実行環
境の言語になじみの方には、この項目の意味がややわかりづらいかもしれません
が、Pythonではソースではなくインタープリタでテストしながら使うことも多い
のです。
PythonインタープリタはLisp系言語のREPL(read-evalute-print loop)に近い
環境で、その実行内容には一切制限はなく、実行速度とメモリの利用効率以外、
ソースファイルを直接渡してコンパイル(自動)して実行するのと変わりません。
関数型言語のHaskellのインタープリタは関数定義はできませんでしたが、
Pythonインタープリタでは関数定義どころかクラス定義だろうがなんだろうが可
能ですし、インタープリタ環境のままパッケージを導入したりモジュールを読み
込んで実行したりすることも可能です。
そして、help関数を使えば、(Smalltalkのオブジェクトブラウザには劣ります
が)オブジェクトに関する様々な情報を閲覧することができます。
※help関数、dir関数などは、実行環境の情報取得専用の関数です。詳しくは
「実行環境の情報の閲覧」の項を参照してください。
3-2-1-7-1.説明文(docstring=document string)
関数やメソッドの実行文の一行目に、文字列を書くと、help関数に渡された際
に表示される説明文(ドキュメンテーション文字列)となります(下の例の説明
文は、トリプルクォートによって改行を含んでいますが、普通の文字列で1行で
も構いません)
>>> def f():
... """No function.
... No action."""
... return 0
...
>>> f()
0
>>> help(f)
Help on function f in module __main__:
f()
No function.
No action.
>>>
※オブジェクト自身は式ですから、コード中に数字やクォートに挟まれた文字列
をそのまま書いても、エラーにはなりません。ただし'#'でコメントアウトして
おかないと、無駄な処理をすることになります。
レイアウトは、2行目以降の空行以外の行の先頭をインデントの基点とします
(1行目はその基準行に揃えられます)
なお説明文は、__doc__属性に文字列として保存されています。
help関数を自作したい(?)などに、参考にしてください。
特にルールではありませんが、次のような書き方の「しきたり」があるそうで
す。
【一般的な説明文の書き方】
・一行目:簡単な説明
・二行目:空行
・三行目以降:詳細な解説
>>> def addxy(x, y):
... """2引数を加算する。
...
... 加算した合計を返す。"""
... return x + y
...
>>> help(addxy)
Help on function addxy in module __main__:
addxy(x, y)
2引数を加算する。
加算した合計を返す。
>>>
3-2-1-7-2.注釈(annotations)
関数についての注釈の文法はPython3に導入された文法です。
Pythonでは引数の型を強制することは出来ません(検査してエラーを出すこと
は出来ますが)が、どのような引数を取ることを期待して関数を書いたか、とい
うことをユーザに知らせる方法があります。
※ユーザはその「期待」通りに「振舞う」オブジェクトを渡して使えば良いので
す。
注釈は、引数と返り値について次のように書きます
def 関数名(変数1:式1, 変数:式2, ...)->式R :
式1,式2...(お望みならいくつでも)は、それぞれ変数1,変数2...の型を書きま
す。
式Rには、戻り値の型を書きます。
'式'となっているのは、そこで評価されたものが表示されるからです(1+1と書
けば2と表示される)。
実際、この「注釈」のルールはかなりカオスで、式として評価してエラーの出
ないものならなんでも取り入れます。
ただし逆に、れっきとした型であっても、実行している名前空間に名前として
辞書に登録されていないものは、直接書き込めません。
例えば、型(クラス)を表示するtype関数に、関数オブジェクトを渡すと、
'function'という型が表示されます。
しかし「関数を返す関数を書こう」と思って『def func()-> function:』のよ
うに書くと、「functionという名前が定義されていない!」とエラーが出ます。
というわけで、『def f()-> 'function':』と書くしかないようです。
なお、注釈情報は__annotations__属性に収納されています。
【重ねて注意】annotationsは注釈であって、引数の型宣言ではありません。
C,C++,Java,Pascalなど、型が強制される言語を通常使っている方は勘違いしな
いようにしてください。Pythonには、型を指定/強制する仕組みは存在しません。