解 説

フィボナッチ級数を任意の数値まで書き出す関数を定義することで関数の基本を勉強します。

ネタ元はO’REILLYのPythonチュートリアル 第3版です。
作者はGuido van Rossum氏でPythonの生みの親です。また本の題名にあるチュートリアルはPythonの公式チュートリアルのことです。
つまり公式チュートリアルをもう少し丁寧に書き直して本にしたものです。
また、「Python 3 エンジニア認定基礎試験」の出題範囲にもなっています。

フィボナッチ級数

フィボナッチ級数はプログラミングの学習でもよく取り上げられる題材でもあります。
フィボナッチ級数の特徴は次の通りです。

  • 連続する2つの数の和は、その上位の数になる。
    1+2=3 2+3=5 3+5=8 5+8=13 8+13=21 13+21=34・・・
  • どの数も上位の数に対して0.618に近づいていく。
    8/13=0.6153 13/21=0.6190 21/34=0.6176 34/55=0.6181・・・
  • どの数も下位の数に対して1.618に近づいていく。
    13/8=1.625 21/13=1.6153 34/21=1.6190 55/34=1.6176・・・
  • どの数も2つ上位の数に対しては0.382に近づいていく。
    8/21=0.3809 13/34=0.3823 21/55=0.3818 34/89=0.3820・・・
  • どの数も2つ下位の数に対しては2.618に近づいていく。
    13/5=2.6 21/8=2.625 34/13=2.6153 55/21=2.6190・・・

関数の定義について

基本的な関数の定義

以下のサンプルコードはGoogleのColaboratoryで書かれたものです。
そのためインデントはスペース2つで書かれています。

サンプルコード

def fib(n):
  """Print a Fibonacci series up to n."""
  a,b = 0,1
  while a < n:
    print(a, end=' ')
    a,b = b, a+b
  print()
  
fib(1000)

結果
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

Pythonでは、関数定義にはdefキーワードを使います。
def の後は関数名です。
関数名の後の( )は仮引数をカンマ区切りで記述します。
( )の後にはコロン「:」をつけます。コロン「:」をつけると次にインデントが来ますという合図にもなります。
4つのスペースを入れて関数の命令内容を記述します。
インデントはスペースキーを4回押して作ります。タブキーは使いません。
スペースとタブが混在したインデントはエラーになりますので、スペースを使う癖をつけましょう。
また、インデントは通常4つのスペースですが、Google Colaboratoryはスペース2つとなっていますので注意してください。
ただし、Colaboratoryの設定は設定メニューから変更できます。

def 関数名(仮引数, 仮引数,…):
_ _ _ _命令文
_ _ _ _命令文
_ _ _ _return 値

命令文で行なっている内容

a,b = 0,1

これは、変数aと変数bに値0と1を代入しています。
わかりやすく書くと次のコードと同等のことを行なっています。
a = 0
b = 1

次のコードは、引数で指定した値nよりaの値が小さい間は以下の命令文をループするということです。
以下の命令文とはaの値を書き出すことです。
aの値を1回書き出すごとに、aの値はbの値となり、bの値はaの値と以前のbの値が加算されます。
これでフィボナッチ数列を作成しています。
while a < n:
    print(a, end=' ')
    a,b = b, a+b

print(a, end=’ ‘) の end については、print()の書き出す値の最後に何らかの文字列を付け加えることができるものです。
end=’ ‘とすると書き出した文字列の最後にスペースを追加します。これを行うことでループした文字列を改行して表示するのではなく、横に並べて表示することができます。

docstringについて

docstringとは、上のサンプルコードの中にある次の部分の所のことです。
1行で終わるものは次のように書きます。

  """Print a Fibonacci series up to n."""

複数行に渡るものは次のように記述します。

"""Do nothing, but document it.

No, really, it doesn't do anything.
"""

つまり関数の概要とか使い方の説明を記述したものです。
基本的に3重のクオートで囲み関数の命令文の前、先頭に記述します。
オブジェクトの名前や型など他に調べる方法があるものを記述するのではなく、オブジェクトの目的を短く要約した記述にします。

コードを見れば記述内容は確認できますが、help()の引数に関数名を入れると書き出してくれます。
これは組み込み関数でも同様のことが行われており、help()でdocstringを確認することができます。

例えば次のようにprint関数のdocstringを確認することができます。

help(print)

結果
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
print(…)
print(value, …, sep=’ ‘, end=’\n’, file=sys.stdout, flush=False)

Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

複雑な関数を定義した場合はdocstring を積極的に活用しましょう。

返り値

関数の定義では返り値という仕組みを使って値を返す仕組みがあります。
Pythonの場合は返り値はreturn文を使って値を返します。
returnを使って返り値を返さない関数も存在します。
今回のサンプルのfib()も返り値がありません。

けれどもreturn文の無い関数でも実は返り値が存在して、Noneという値が返されます。

スコープ

Pythonの内部的には、関数を実行するたびにシンボルテーブルが作成されます。

シンボルテーブルについて引用

シンボルテーブルについて詳細はPython公式のチュートリアルを参考にしてください。

関数を 実行 (execution) するとき、関数のローカル変数のために使われる新たなシンボルテーブル (symbol table) が用意されます。 もっと正確にいうと、関数内で変数への代入を行うと、その値はすべてこのローカルなシンボルテーブルに記憶されます。 一方、変数の参照を行うと、まずローカルなシンボルテーブルが検索され、次にさらに外側の関数のローカルなシンボルテーブルを検索し、その後グローバルなシンボルテーブルを調べ、最後に組み込みの名前テーブルを調べます。 従って、関数の中では (グローバル変数が global 文で指定されていたり、外側の関数の変数が nonlocal 文で指定されていない限り) グローバル変数や外側の関数の変数に直接値を代入できませんが、参照することはできます。

スコープにはローカルとグローバルがあります。
関数内で宣言された変数はローカル変数となります。
関数の外で宣言された変数はグローバル変数になります。
関数内で変数の値を探すとき、まずローカル変数を探し、それが無い場合はグローバル変数を探し、それも無い場合は組み込み関数を探すという順番になります。
Pythonではグローバル変数の値を関数内で変更することができません。それは新たなローカル変数として扱われます。
どうしても変更したい場合は、global文で明示的に示す必要があります。

グローバル変数とローカル変数の確認

num = 1
def test():
  num = 100
  print(num)
  
print(num)
test()
print(num)

結果
—————————————————————————
1
100
1
—————————————————————————

ローカル変数をスコープ以外から呼び出して失敗した例

num = 1
def test():
  local_num = 100
  print(local_num)
  
print(num)
test()
print(local_num)

結果
—————————————————————————
1
100
—————————————————————————
NameError Traceback (most recent call last)
in ()
6 print(num)
7 test()
—-> 8 print(local_num)

NameError: name ‘local_num’ is not defined
—————————————————————————

globalキーワードを使って関数内でグローバル変数の値を変更した例

num = 1
def test():
  global num
  num = 100
  print(num)
  
print(num)
test()
print(num)

結果
—————————————————————————
1
100
100
—————————————————————————

デフォルト引数と様々な引数の使い方

引数の指定方法には3つ方法があります。

  • デフォルト値を設定
  • キーワード引数
  • 可変長の引数

デフォルト引数

引数にデフォルト値を指定することです。

デフォルト引数の例

def hello(name='匿名'):
  print(name)
  
hello()
hello('Tahara')

結果
——————————–
匿名
Tahara
——————————–

デフォルト値を指定した引数とデフォルト値の無い引数が混在の例

def hello(text, name='匿名'):
  print(text,name)
  
hello('Hey')
hello('Hey','Tahara')

結果
——————————–
Hey 匿名
Hey Tahara
——————————–

デフォルト値を指定した引数とデフォルト値の無い引数がで順番がまずい例
この場合エラーになります。

def hello(name='匿名', text):
  print(text,name)
  
hello('Tahara','Hey')

結果
——————————–
File ““, line 1
def hello(name=’匿名’, text):
^
SyntaxError: non-default argument follows default argument
——————————–
デフォルト引数を指定する場合、デフォルト値が無い引数と混在する場合には、デフォルト値が無い引数を先に指定する必要があります。

キーワード引数

キーワード引数は「キーワード = 値」で指定します。

キーワード引数を指定した例

def hello(text, name='匿名'):
  print(text,name)
  
hello('Hey')
hello('Hey',name='Tahara')

結果
——————————–
Hey 匿名
Hey Tahara
——————————–

キーワード引数を使った例 (モンティパイソンの「死んだオーム」を題材にしている)
キーワード引数がどのように使われているか次のコードで確認できます。
ついでに有名なモンティパイソンのスケッチ(コント)が楽しめます。

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, "!")

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

結果
——————————–
— This parrot wouldn’t voom if you put 1000 volts through it.
— Lovely plumage, the Norwegian Blue
— It’s a stiff !
— This parrot wouldn’t voom if you put 1000 volts through it.
— Lovely plumage, the Norwegian Blue
— It’s a stiff !
— This parrot wouldn’t VOOOOOM if you put 1000000 volts through it.
— Lovely plumage, the Norwegian Blue
— It’s a stiff !
— This parrot wouldn’t VOOOOOM if you put 1000000 volts through it.
— Lovely plumage, the Norwegian Blue
— It’s a stiff !
— This parrot wouldn’t jump if you put a million volts through it.
— Lovely plumage, the Norwegian Blue
— It’s bereft of life !
— This parrot wouldn’t voom if you put a thousand volts through it.
— Lovely plumage, the Norwegian Blue
— It’s pushing up the daisies !
——————————–

可変長引数 *args または **kwargs の使い方

可変長引数とは簡単にまとめると次の記述をします。

  • *args: 複数の引数をタプルとして受け取る
  • **kwargs: 複数のキーワード引数を辞書として受け取る

*argsについて

仮引数の最後がアスタリスク「*」付きの場合、仮引数に無い位置指定引数を全て含んだタプルが関数に渡されます。

*args仮引数を指定した例

def hello(*args):
  print(args)
  
hello()
hello('Hello')
hello('Hello','world')

結果
——————————–
()
(‘Hello’,)
(‘Hello’, ‘world’)
——————————–

もう一つ*argsの例

def my_sum(*args):
    return sum(args)
  
print(my_sum(1, 2, 3, 4))

結果
——————————–
10
——————————–

**kwargsについて

仮引数の最後が「**名前」の形になっている場合は、辞書を受け取ります。
これを使うことで仮引数に無いキーワードを使うことができるようになります。
名前は何でも構いませんが、一般的に「**kwargs」とされますが、「**keywords」としても問題ありません。

def hello(*args,**kwargs):
  print(args,kwargs)
  
hello()
hello('Hello',a=1)
hello('Hello','world',a=1,b=2,c=3)

結果
——————————–
() {}
(‘Hello’,) {‘a’: 1}
(‘Hello’, ‘world’) {‘a’: 1, ‘b’: 2, ‘c’: 3}
——————————–

こちらのサンプルは、またモンティパイソンのスケッチからです。

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
——————————–

Pythonの引数について更に知っておきたいこと

Pythonでは全ての引数が参照渡しになります。
そして、渡す値がミュータブルかイミュータブルで挙動が変わります。

  • ミュータブルな値(リスト、辞書、集合など):関数内で行う引数の変更が呼び出し元にも反映されます。
  • イミュータブルな値(数値、文字、タプルなど):関数内で行う引数の変更が呼び出し元にも反映されません。

ミュータブルな引数の例
関数を実行すると関数内ではyの値が11になるが、
変数yの値が変わることはない。

def increment(x):
  x += 1
  
y = 10
increment(y)
print(y)

結果
—————————————-
10
—————————————-

イミュータブルな引数の例
関数を実行すると関数内ではwの要素の値が刻々と増える。

def rewrite(w):
  w[0] = 'a'
  w[1] = 'b'
  w[2] = 'c'
my_list = [0,1,2]
rewrite(my_list)
print(my_list)

結果
—————————————-
[‘a’, ‘b’, ‘c’]
—————————————-