4. その他の制御フローツール¶
前章で紹介した while
文の他にも、 Python にはいくつか制御フローツールがあり、本章で説明します。
4.1. if
文¶
おそらく最もおなじみの文型は if
文でしょう。例えば:
>>> x = int(input("Please enter an integer: "))
Please enter an integer: 42
>>> if x < 0:
... x = 0
... print('Negative changed to zero')
... elif x == 0:
... print('Zero')
... elif x == 1:
... print('Single')
... else:
... print('More')
...
More
ゼロ個以上の elif
部を使うことができ、 else
部を付けることもできます。キーワード 'elif
' は 'else if' を短くしたもので、過剰なインデントを避けるのに役立ちます。一連の if
... elif
... elif
... は、他の言語における switch
文や case
文の代用となります。
いくつかの定数と同じ値かを比較する場合や、特定の型や属性かを確認する場合には、match
文が便利です。詳細は match 文 を参照してください。
4.2. for
文¶
Python の for
文は、読者が C 言語や Pascal 言語で使いなれているかもしれない for
文とは少し違います。 (Pascal のように) 常に算術型の数列にわたる反復を行ったり、 (C のように) 繰返しステップと停止条件を両方ともユーザが定義できるようにするのとは違い、Python の for
文は、任意のシーケンス型 (リストまたは文字列) にわたって反復を行います。反復の順番はシーケンス中に要素が現れる順番です。例えば:
>>> # Measure some strings:
... words = ['cat', 'window', 'defenestrate']
>>> for w in words:
... print(w, len(w))
...
cat 3
window 6
defenestrate 12
コレクションオブジェクトの値を反復処理をしているときに、そのコレクションオブジェクトを変更するコードは理解するのが面倒になり得ます。 そうするよりも、コレクションオブジェクトのコピーに対して反復処理をするか、新しいコレクションオブジェクトを作成する方が通常は理解しやすいです:
# Create a sample collection
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
# Strategy: Iterate over a copy
for user, status in users.copy().items():
if status == 'inactive':
del users[user]
# Strategy: Create a new collection
active_users = {}
for user, status in users.items():
if status == 'active':
active_users[user] = status
4.3. range()
関数¶
数列にわたって反復を行う必要がある場合、組み込み関数 range()
が便利です。この関数は算術型の数列を生成します:
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
指定した終端値は生成されるシーケンスには入りません。range(10)
は 10 個の値を生成し、長さ 10 のシーケンスにおける各項目のインデクスとなります。range を別の数から開始したり、他の増加量 (負でも; 増加量は時に 'ステップ(step)' と呼ばれることもあります) を指定することもできます:
>>> list(range(5, 10))
[5, 6, 7, 8, 9]
>>> list(range(0, 10, 3))
[0, 3, 6, 9]
>>> list(range(-10, -100, -30))
[-10, -40, -70]
あるシーケンスにわたってインデクスで反復を行うには、 range()
と len()
を次のように組み合わせられます:
>>> a = ['Mary', 'had', 'a', 'little', 'lamb']
>>> for i in range(len(a)):
... print(i, a[i])
...
0 Mary
1 had
2 a
3 little
4 lamb
しかし、多くの場合は enumerate()
関数を使う方が便利です。 ループのテクニック を参照してください。
range を直接出力すると変なことになります:
>>> range(10)
range(0, 10)
range()
が返すオブジェクトは、いろいろな点でリストであるかのように振る舞いますが、本当はリストではありません。これは、イテレートした時に望んだ数列の連続した要素を返すオブジェクトです。しかし実際にリストを作るわけではないので、スペースの節約になります。
このようなオブジェクトは イテラブル と呼ばれます。
これらは関数や構成物のターゲットとして、あるだけの項目を逐次与えるのに適しています。
for
文がそのような構成物であることはすでに見てきており、イテラブルを受け取る関数の例には sum()
があります:
>>> sum(range(4)) # 0 + 1 + 2 + 3
6
この後には、イテラブルを返したりイテラブルを引数で受け取るいくつかの関数が出てきます。 データ構造 では、 list()
についてより詳しく説明します。
4.4. break
文と continue
文とループの else
節¶
break
文は、その break 文を内包している最も内側にある for
文または while
文から抜け出すことができます。
for
文と while
文では else
節を書くことができます。
for
文の場合、 else
節はループ処理の最後の回が実行されたあとに実行されます。
while
文の場合は、ループ条件が偽となったあとに実行されます。
どちらのループ文でも、 break
によってループ処理が終了したときは else
節は**実行されません**。
その例として、素数を探索する for
文を以下に示します:
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(n, 'equals', x, '*', n//x)
... break
... else:
... # loop fell through without finding a factor
... print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3
(そう、これは正しいコードです。よく見てください: else
節は if
文 ではなく 、 for
ループに属しています。)
ループの else
句は、 if
文の else
よりも try
文の else
に似ています。 try
文の else
句は例外が発生しなかった時に実行され、ループの else
句は break
されなかった場合に実行されます。 try
文と例外についての詳細は 例外を処理する を参照してください。
continue
文も C 言語から借りてきたもので、ループの次のイテレーションを実行します:
>>> for num in range(2, 10):
... if num % 2 == 0:
... print("Found an even number", num)
... continue
... print("Found an odd number", num)
...
Found an even number 2
Found an odd number 3
Found an even number 4
Found an odd number 5
Found an even number 6
Found an odd number 7
Found an even number 8
Found an odd number 9
4.5. pass
文¶
pass
文は何もしません。 pass
は、文を書くことが構文上要求されているが、プログラム上何の動作もする必要がない時に使われます:
>>> while True:
... pass # Busy-wait for keyboard interrupt (Ctrl+C)
...
これは最小のクラスを作るときによく使われる方法です:
>>> class MyEmptyClass:
... pass
...
pass
のもう 1 つの使い道は、新しいコードを書いているときの関数や条件文の仮置きの本体としてです。こうすることで、より抽象的なレベルで考え続けられます。
pass
は何事も無く無視されます
>>> def initlog(*args):
... pass # Remember to implement this!
...
4.6. match
文¶
match
文は1つの式を指定し、その値と次に続く1つ以上のcaseブロックに指定されたパターンを比較します。この機能はCやJava、JavaScript(や他の多数の言語)のswitch文と表面的には似ていますが、RustやHaskellのパターンマッチングにより似ています。最初にマッチしたパターンのみが実行され、コンポーネント(シーケンスの要素やオブジェクトの属性)から値を取り出して変数に代入することもできます。
最も単純な形式は、対象の値に対して1つ以上のリテラルです:
def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"
最後のブロックについて: 変数名 _
は ワイルドカード の働きをし、マッチに絶対失敗しません。マッチするケースがない場合は、そのブロックも実行されません。
複数のリテラルを |
("or")を使用して組み合わせて1つのパターンにできます:
case 401 | 403 | 404:
return "Not allowed"
パターンはアンパック代入ができ、変数に結びつけられます:
# point is an (x, y) tuple
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("Not a point")
このコードは注意して見てください!
最初のパターンには2つのリテラルがあり、上で示したリテラルパターンの拡張と考えることができます。
しかし次の2つのパターンはリテラルと変数の組み合わせのため、対象(point
)から値を取り出して変数に 結びつけ ます。
4番目のパターンは2つの値を取り込みます。
これは、アンパック代入 (x, y) = point
と概念的に似ています。
データを構造化するためにクラスを使っている場合は、クラス名の後ろにコンストラクターのように引数のリストを指定できます。属性の値は変数に取り込まれます。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def where_is(point):
match point:
case Point(x=0, y=0):
print("Origin")
case Point(x=0, y=y):
print(f"Y={y}")
case Point(x=x, y=0):
print(f"X={x}")
case Point():
print("Somewhere else")
case _:
print("Not a point")
いくつかの組み込みクラスでは位置引数が使用でき、属性の順番を提供します(例: データクラス)。クラスの __match_args__
特殊属性によって、パターンの中で属性の明確な位置を定義することもできます。("x", "y")が設定された場合、以下のすべてのパターンは等価です(すべて属性 y
が var
変数に結びつけられます):
Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)
おすすめのパターンの読み方は、パターンが、代入文の左辺に配置するものを拡張した形式であるとみなすことです。
これにより、どの変数になにが代入されるかが分かります。
単独の名前(上記の var
など)だけがマッチ文で値が代入されます。ドット付きの名前(foo.bar
など)、属性名(上記の x=
、y=
など )、クラス名(名前の後ろの "(...)" によって判別される。上記の Point
など)には値は代入されません。
パターンはいくらでも入れ子 (ネスト) にすることができます。例えば、 __match_args__
を追加した Point クラスのリストに対して次のようにマッチを行うことができます:
class Point:
__match_args__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
match points:
case []:
print("No points")
case [Point(0, 0)]:
print("The origin")
case [Point(x, y)]:
print(f"Single point {x}, {y}")
case [Point(0, y1), Point(0, y2)]:
print(f"Two on the Y axis at {y1}, {y2}")
case _:
print("Something else")
パターンに if
節を追加できます。これは "ガード" と呼ばれます。ガードがfalseの場合、match
は次のcaseブロックの処理に移動します。ガードを評価する前に値が取り込まれることに注意してください:
match point:
case Point(x, y) if x == y:
print(f"Y=X at {x}")
case Point(x, y):
print(f"Not on the diagonal")
この文のその他のいくつか重要な特徴:
アンパック代入のように、タプルとリストのパターンでは正確に同じ意味で、任意のシーケンスと一致します。重要な例外として、イテレーターや文字列ではマッチしません。
シーケンスパターンは拡張アンパックをサポート:
[x, y, *rest]
と(x, y, *rest)
はアンパック代入として同じように動作します。*
のあとの変数名は_
でもよく、そのため(x, y, *_)
は最低でも2つのアイテムを持つシーケンスにマッチし、残りのアイテムは変数に結びつけられません。マッピングパターン:
{"bandwidth": b, "latency": l}
は辞書から"bandwidth"
と"latency"
の値を取り込みます。シーケンスパターンとは異なり、それ以外のキーは無視されます。アンパッキングのような**rest
もサポートされています(しかし、**_
は冗長なため禁止されています)。サブパターンでは
as
キーワードを使用して値を取り込みます:case (Point(x1, y1), Point(x2, y2) as p2): ...
この例では入力から2番目の要素を
p2
として取り込みます(入力が2つのポイントのシーケンスである場合)ほとんどのリテラルは同一性を比較しますが、シングルトンの
True
、False
、None
では識別値を比較します。パターンには名前を付けた定数が使用できます。値を取り込む変数としてと解釈することを防ぐために、ドット付きの変数名にする必要があります。
from enum import Enum class Color(Enum): RED = 'red' GREEN = 'green' BLUE = 'blue' color = Color(input("Enter your choice of 'red', 'blue' or 'green': ")) match color: case Color.RED: print("I see red!") case Color.GREEN: print("Grass is green") case Color.BLUE: print("I'm feeling the blues :(")
より詳細な説明と追加の例は PEP 636 にチュートリアル形式で記述してあります。
4.7. 関数を定義する¶
フィボナッチ数列 (Fibonacci series) を任意の上限値まで書き出すような関数を作成できます:
>>> def fib(n): # write Fibonacci series up to n
... """Print a Fibonacci series up to n."""
... a, b = 0, 1
... while a < n:
... print(a, end=' ')
... a, b = b, a+b
... print()
...
>>> # Now call the function we just defined:
... fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
def
は関数の 定義 (definition) を導くキーワードです。 def
の後には、関数名と仮引数を丸括弧で囲んだリストを続けなければなりません。関数の実体を構成する実行文は次の行から始め、インデントされていなければなりません。
関数の本体の記述する文の最初の行は文字列リテラルにすることもできます。その場合、この文字列は関数のドキュメンテーション文字列 (documentation string)、または docstring と呼ばれます。 (docstring については ドキュメンテーション文字列 でさらに扱っています。) ドキュメンテーション文字列を使ったツールには、オンライン文書や印刷文書を自動的に生成したり、ユーザが対話的にコードから直接閲覧できるようにするものがあります。自分が書くコードにドキュメンテーション文字列を入れるのはよい習慣です。書く癖をつけてください。
関数を 実行 (execution) するとき、関数のローカル変数のために使われる新たなシンボルテーブル (symbol table) が用意されます。
もっと正確にいうと、関数内で変数への代入を行うと、その値はすべてこのローカルなシンボルテーブルに記憶されます。
一方、変数の参照を行うと、まずローカルなシンボルテーブルが検索され、次にさらに外側の関数のローカルなシンボルテーブルを検索し、その後グローバルなシンボルテーブルを調べ、最後に組み込みの名前テーブルを調べます。
従って、関数の中では (グローバル変数が global
文で指定されていたり、外側の関数の変数が nonlocal
文で指定されていない限り) グローバル変数や外側の関数の変数に直接値を代入できませんが、参照することはできます。
関数を呼び出す際の実際の引数 (実引数) は、関数が呼び出されるときに関数のローカルなシンボルテーブル内に取り込まれます。そうすることで、実引数は 値渡し (call by value) で関数に渡されることになります (ここでの 値 (value) とは常にオブジェクトへの 参照(reference) をいい、オブジェクトの値そのものではありません) [1]。ある関数がほかの関数を呼び出すときや、自身を再帰的に呼び出すときには、新たな呼び出しのためにローカルなシンボルテーブルが新たに作成されます。
関数の定義を行うと、関数名は関数オブジェクトとともに現在のシンボルテーブル内に取り入れられます。インタープリタはその名前が指すオブジェクトをユーザ定義関数 (user-defined function) として認識します。他の名前も同じ関数オブジェクトを指すことができ、またその関数にアクセスするために使用することができます:
>>> fib
<function fib at 10042ed0>
>>> f = fib
>>> f(100)
0 1 1 2 3 5 8 13 21 34 55 89
他の言語出身の人からは、 fib
は値を返さないので関数ではなく手続き (procedure) だと異論があるかもしれませんね。技術的に言えば、実際には return
文を持たない関数もややつまらない値ですが値を返しています。この値は None
と呼ばれます (これは組み込みの名前です)。 None
だけを書き出そうとすると、インタプリタは通常出力を抑制します。本当に出力したいのなら、以下のように print()
を使うと見ることができます:
>>> fib(0)
>>> print(fib(0))
None
フィボナッチ数列の数からなるリストを出力する代わりに、値を返すような関数を書くのは簡単です:
>>> def fib2(n): # return Fibonacci series up to n
... """Return a list containing the Fibonacci series up to n."""
... result = []
... a, b = 0, 1
... while a < n:
... result.append(a) # see below
... a, b = b, a+b
... return result
...
>>> f100 = fib2(100) # call it
>>> f100 # write the result
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
この例は Python の新しい機能を示しています:
return
文では、関数から一つ値を返します。return
の引数となる式がない場合、None
が返ります。関数が終了したときにもNone
が返ります。result.append(a)
という文で、result
オブジェクトの メソッド が呼び出されます。メソッドとは、オブジェクトに「属する」関数のことであり、obj.methodname
と表されます。ここで、obj
は何らかのオブジェクト (式の場合もあります) であり、methodname
はそのオブジェクトの型で定義されたメソッド名です。色々な型がそれぞれ独自のメソッドを定義しています。異なる型が同じ名前のメソッドを持つことも可能であり、どちらの方のものであるかという曖昧さは生まれません。(*クラス*を使って独自の型やメソッドを自分で定義することもできます。参照: クラス) 例にあるappend()
メソッドは、リストオブジェクトに対して定義されているもので、リストの末尾に新しい要素を追加します。この例ではresult = result + [a]
と等価ですが、計算効率の上ではベターです。
4.8. 関数定義についてもう少し¶
可変個の引数を伴う関数を定義することもできます。引数の定義方法には 3 つの形式があり、それらを組み合わせることができます。
4.8.1. デフォルトの引数値¶
もっとも便利なのは、一つ以上の引数に対してデフォルトの値を指定する形式です。この形式を使うと、定義されている引数より少ない個数の引数で呼び出せる関数を作成します:
def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
ok = input(prompt)
if ok in ('y', 'ye', 'yes'):
return True
if ok in ('n', 'no', 'nop', 'nope'):
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)
この関数はいくつかの方法で呼び出せます:
必須の引数のみ与える:
ask_ok('Do you really want to quit?')
一つのオプション引数を与える:
ask_ok('OK to overwrite the file?', 2)
全ての引数を与える:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
この例では in
キーワードが導入されています。このキーワードはシーケンスが特定の値を含んでいるかどうか調べるのに使われます。
デフォルト値は、関数が定義された時点で、関数を 定義している 側のスコープ (scope) で評価されるので
i = 5
def f(arg=i):
print(arg)
i = 6
f()
は 5
を出力します。
重要な警告: デフォルト値は 1 度だけしか評価されません。デフォルト値がリストや辞書のような変更可能なオブジェクトの時にはその影響がでます。例えば以下の関数は、後に続く関数呼び出しで関数に渡されている引数を累積します:
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
このコードは、以下を出力します
[1]
[1, 2]
[1, 2, 3]
後続の関数呼び出しでデフォルト値を共有したくなければ、代わりに以下のように関数を書くことができます:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
4.8.2. キーワード引数¶
関数を kwarg=value
という形式の キーワード引数 を使って呼び出すこともできます。例えば、以下の関数:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
は、必須引数 (voltage
) とオプション引数 (state
、action
、type
) を受け付けます。この関数は以下のいずれかの方法で呼び出せます:
parrot(1000) # 1 positional argument
parrot(voltage=1000) # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump') # 3 positional arguments
parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword
が、以下の呼び出しは不適切です:
parrot() # required argument missing
parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument
parrot(110, voltage=220) # duplicate value for the same argument
parrot(actor='John Cleese') # unknown keyword argument
関数の呼び出しにおいて、キーワード引数は位置引数の後でなければなりません。渡されるキーワード引数は全て、関数で受け付けられる引数のいずれかに対応していなければならず (例えば、actor
はこの parrot
関数の引数として適切ではありません)、順序は重要ではありません。これはオプションでない引数でも同様です (例えば、parrot(voltage=1000)
も適切です)。いかなる引数も値を複数回は受け取れません。この制限により失敗する例は:
>>> def function(a):
... pass
...
>>> function(0, a=0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: function() got multiple values for argument 'a'
仮引数の最後に **name
の形式のものがあると、それまでの仮引数に対応したものを除くすべてのキーワード引数が入った辞書 (マッピング型 --- dict を参照) を受け取ります。
**name
は *name
の形式をとる、仮引数のリストを超えた位置引数の入った タプル を受け取る引数 (次の小節で述べます) と組み合わせられます。
(*name
は **name
より前になければなりません)。
例えば、ある関数の定義を以下のようにすると:
def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
for kw in keywords:
print(kw, ":", keywords[kw])
呼び出しは以下のようになり:
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
もちろん以下のように出力されます:
-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
shopkeeper : Michael Palin
client : John Cleese
sketch : Cheese Shop Sketch
なお、複数のキーワード引数を与えた場合に、それらが出力される順序は、関数呼び出しで与えられた順序と同じになります。
4.8.3. 特殊なパラメータ¶
デフォルトでは、引数は位置またはキーワードによる明示で Python 関数に渡されます。 可読性とパフォーマンスのために、その引数が位置、位置またはキーワード、キーワードのどれで渡されるかを開発者が判定するのに関数定義だけを見ればよいように、引数の渡され方を制限することには意味があります。
関数定義は次のようになります:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
ここで、/
と *
はオプションです。使用された場合、これらの記号は、引数が関数に渡される方法、すなわち、位置専用、位置またはキーワード、キーワード専用、といった引数の種類を示します。キーワード引数は、名前付き引数とも呼ばれます。
4.8.3.1. 位置またはキーワード引数¶
関数定義に /
も *
もない場合は、引数は位置またはキーワードで関数に渡されます。
4.8.3.2. 位置専用引数¶
これをもう少し詳しく見てみると、特定の引数を 位置専用 と印を付けられます。
位置専用 の場合、引数の順序が重要であり、キーワードで引数を渡せません。
位置専用引数は /
(スラッシュ)の前に配置されます。
/
は、位置専用引数を残りの引数から論理的に分離するために使用されます。
関数定義に /
がない場合、位置専用引数はありません。
/
の後の引数は、 位置またはキーワード 、もしくは、 キーワード専用 です。
4.8.3.3. キーワード専用引数¶
引数をキーワード引数で渡す必要があることを示す キーワード専用 として引数をマークするには、引数リストの最初の キーワード専用 引数の直前に *
を配置します。
4.8.3.4. 関数の例¶
/
および *
といったマーカーに注意を払って、次の関数定義の例を見てください:
>>> def standard_arg(arg):
... print(arg)
...
>>> def pos_only_arg(arg, /):
... print(arg)
...
>>> def kwd_only_arg(*, arg):
... print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
... print(pos_only, standard, kwd_only)
最も馴染みのある形式の最初の関数定義 standard_arg
は、呼び出し規約に制限を設けておらず、引数は位置またはキーワードで渡されます:
>>> standard_arg(2)
2
>>> standard_arg(arg=2)
2
2番目の関数の pos_only_arg
は、 /
が関数定義にあるので、引数は位置専用になります:
>>> pos_only_arg(1)
1
>>> pos_only_arg(arg=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg'
3番目の関数 kwd_only_args
は、関数定義に *
があるので、引数はキーワード専用になります:
>>> kwd_only_arg(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
>>> kwd_only_arg(arg=3)
3
そして最後の関数は3つの引数の種類を一つの関数定義の中で使用しています:
>>> combined_example(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given
>>> combined_example(1, 2, kwd_only=3)
1 2 3
>>> combined_example(1, standard=2, kwd_only=3)
1 2 3
>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() got some positional-only arguments passed as keyword arguments: 'pos_only'
最後に、位置引数 name
と name
をキーとして持つ **kwds
の間に潜在的な衝突がある関数定義を考えてみましょう。
def foo(name, **kwds):
return 'name' in kwds
キーワードに 'name'
を入れても、先頭の引数と同じになってしまうため、この関数が True
を返すような呼び出しの方法はありません。例えば、次のようになってしまいます:
>>> foo(1, **{'name': 2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>
しかし位置専用を示す /
を使用すれば可能になります。 name
は位置引数として、そして 'name'
はキーワード引数のキーワードとして認識されるからです:
>>> def foo(name, /, **kwds):
... return 'name' in kwds
...
>>> foo(1, **{'name': 2})
True
言い換えると、位置専用引数であれば、その名前を **kwds
の中で使用しても、曖昧にならないということです。
4.8.3.5. 要約¶
使用例で、関数定義でどの種類の引数を使うかべきかがわかると思います:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
ガイドとしては、
もし引数の名前をユーザーに知らせる必要がないなら、位置専用引数を使用しましょう。これは引数の名前がユーザーにとって意味がなく、関数が呼ばれたときの引数の順序が問題であり、または、位置引数と任意のキーワードを使用する必要がある場合に便利です。
引数の名前に意味があり、それにより関数の定義がより明らかになる、または、ユーザーが引数の順番に縛られることを避けたほうがいいと考えるのなら、キーワード専用引数を使用しましょう。
APIの場合、将来引数の名前が変更された場合にAPIの変更ができなくなることを防ぐために、位置専用引数を使用しましょう。
4.8.4. 任意引数リスト¶
最後に、最も使うことの少ない選択肢として、関数が任意の個数の引数で呼び出せるよう指定する方法があります。これらの引数はタプル (タプルとシーケンス を参照) に格納されます。可変個の引数の前に、ゼロ個かそれ以上の引数があっても構いません。
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
通常このような 可変 引数は、関数に渡される入力引数の残りを全て掬い取るために、仮引数リストの最後に置かれます。
*args
引数の後にある仮引数は 'キーワード専用' 引数で、位置引数ではなくキーワード引数としてのみ使えることを意味します。
>>> def concat(*args, sep="/"):
... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
4.8.5. 引数リストのアンパック¶
引数がすでにリストやタプルになっていて、個別な位置引数を要求する関数呼び出しに渡すためにアンパックする必要がある場合には、逆の状況が起こります。例えば、組み込み関数 range()
は引数 start と stop を別に与える必要があります。個別に引数を与えることができない場合、関数呼び出しを *
演算子を使って書き、リストやタプルから引数をアンパックします:
>>> list(range(3, 6)) # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args)) # call with arguments unpacked from a list
[3, 4, 5]
同じやりかたで、**
オペレータを使って辞書でもキーワード引数を渡すことができます:
>>> def parrot(voltage, state='a stiff', action='voom'):
... print("-- This parrot wouldn't", action, end=' ')
... print("if you put", voltage, "volts through it.", end=' ')
... print("E's", state, "!")
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
4.8.6. ラムダ式¶
キーワード lambda
を使うと、名前のない小さな関数を生成できます。例えば lambda a, b: a+b
は、二つの引数の和を返す関数です。ラムダ式の関数は、関数オブジェクトが要求されている場所にならどこでも使うことができます。ラムダ式は、構文上単一の式に制限されています。意味付け的には、ラムダ形式は単に通常の関数定義に構文的な糖衣をかぶせたものに過ぎません。入れ子構造になった関数定義と同様、ラムダ式もそれを取り囲むスコープから変数を参照することができます:
>>> def make_incrementor(n):
... return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43
上記の例は、関数を返すところでラムダ式を使っています。もう1つの例では、ちょっとした関数を引数として渡すのに使っています:
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
4.8.7. ドキュメンテーション文字列¶
ドキュメンテーション文字列については、その内容と書式に関する慣習をいくつか挙げます。
最初の行は、常に対象物の目的を短く簡潔にまとめたものでなくてはなりません。簡潔に書くために、対象物の名前や型を明示する必要はありません。名前や型は他の方法でも得られるからです (名前がたまたま関数の演算内容を記述する動詞である場合は例外です)。最初の行は大文字で始まり、ピリオドで終わっていなければなりません。
ドキュメンテーション文字列中にさらに記述すべき行がある場合、二行目は空行にし、まとめの行と残りの記述部分を視覚的に分離します。つづく行は一つまたはそれ以上の段落で、対象物の呼び出し規約や副作用について記述します。
Python のパーザは複数行にわたる Python 文字列リテラルからインデントを剥ぎ取らないので、ドキュメントを処理するツールでは必要に応じてインデントを剥ぎ取らなければなりません。この処理は以下の規約に従って行います。最初の行の 後にある 空行でない最初の行が、ドキュメント全体のインデントの量を決めます。(最初の行は通常、文字列を開始するクオートに隣り合っているので、インデントが文字列リテラル中に現れないためです。) このインデント量と "等価な" 空白が、文字列のすべての行頭から剥ぎ取られます。インデントの量が少ない行を書いてはならないのですが、もしそういう行があると、先頭の空白すべてが剥ぎ取られます。インデントの空白の大きさが等しいかどうかは、タブ文字を (通常は 8 文字のスペースとして) 展開した後に調べられます。
以下に複数行のドキュメンテーション文字列の例を示します:
>>> def my_function():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything.
... """
... pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.
No, really, it doesn't do anything.
4.8.8. 関数のアノテーション¶
関数アノテーション はユーザ定義関数で使用される型についての完全にオプションなメタデータ情報です (詳細は PEP 3107 と PEP 484 を参照してください)。
アノテーション は、関数の __annotations__
属性に辞書として格納され、関数の他の部分には影響しません。
パラメータアノテーションは、パラメータ名のあとのコロンと式で定義され、その式の評価結果がアノテーションとなります。
戻り値アノテーションは、パラメータリストと def
文の終わりを表すコロンの間で、 ->
の後ろに式を書くことで定義されます。
次の例では、必須引数と任意引数、戻り値にアノテーションが付与されています:
>>> def f(ham: str, eggs: str = 'eggs') -> str:
... print("Annotations:", f.__annotations__)
... print("Arguments:", ham, eggs)
... return ham + ' and ' + eggs
...
>>> f('spam')
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
Arguments: spam eggs
'spam and eggs'
4.9. 間奏曲: コーディングスタイル¶
これからより長くより複雑な Python のコードを書いていくので、そろそろ コーディングスタイル について語っても良い頃です。ほとんどの言語は様々なスタイルで書け (もっと簡潔に言えば フォーマットでき)、スタイルによって読み易さが異なります。他人にとって読み易いコードにしようとするのはどんなときでも良い考えであり、良いコーディングスタイルを採用することが非常に強力な助けになります。
Python には、ほとんどのプロジェクトが守っているスタイルガイドとして PEP 8 があります。それは非常に読み易く目に優しいコーディングスタイルを推奨しています。全ての Python 開発者はある時点でそれを読むべきです。ここに最も重要な点を抜き出しておきます:
インデントには空白 4 つを使い、タブは使わないこと。
空白 4 つは (深くネストできる) 小さいインデントと (読み易い) 大きいインデントのちょうど中間に当たります。タブは混乱させるので、使わずにおくのが良いです。
ソースコードの幅が 79 文字を越えないように行を折り返すこと。
こうすることで小さいディスプレイを使っているユーザも読み易くなり、大きなディスプレイではソースコードファイルを並べることもできるようになります。
関数やクラスや関数内の大きめのコードブロックの区切りに空行を使うこと。
可能なら、コメントは行に独立で書くこと。
docstring を使うこと。
演算子の前後とコンマの後には空白を入れ、括弧類のすぐ内側には空白を入れないこと:
a = f(1, 2) + g(3, 4)
。クラスや関数に一貫性のある名前を付けること。慣習では
UpperCamelCase
をクラス名に使い、lowercase_with_underscores
を関数名やメソッド名に使います。常にself
をメソッドの第 1 引数の名前 (クラスやメソッドについては クラス初見 を見よ) として使うこと。あなたのコードを世界中で使ってもらうつもりなら、風変りなエンコーディングは使わないこと。どんな場合でも、Python のデフォルト UTF-8 またはプレーン ASCII が最も上手くいきます。
同様に、ほんの少しでも他の言語を話す人がコードを読んだりメンテナンスする可能性があるのであれば、非 ASCII 文字も識別子に使うべきではありません。
脚注