関数

基本

Python で関数を定義するときは、 def 文を使います。

>>> def add(n, m):
...     return n+m
...
>>> add(2,3)
5

引数にデフォルト値を定義することもできます。

>>> def add(a, b=3):
...     return a+b
...
>>> add(2)
5

このデフォルト値は、関数を定義するときに1つのオブジェクトがデフォルト値として 記憶され、毎回 同一の オブジェクトが渡されることに注意してください。

>>> def append(a, x=[]):
...     x.append(a)
...     return x
...
>>> append(1)
[1]
>>> append(1)
[1, 1]
>>> append(1)
[1, 1, 1]

この挙動が問題になる場合は、 None をデフォルト値に使うことが多いです。

>>> def append(a, x=None):
...     if x is None:
...         x = []
...     x.append(a)
...     return x
...
>>> append(1)
[1]
>>> append(1)
[1]

ファーストクラス

php 5.3 で無名関数が追加され、関数を変数に入れたり関数の引数に渡したりすることができる ようになりました。

<?php
$add = function($a, $b) { return $a + $b; }
$call2 = function($func, $a, $b) { return $func($a, $b); }
var_dump($call2($add, 1, 3));  // => int(4)

Python の関数はもともと php の無名関数のような動作をしています。 def foo(): すると foo という変数に関数オブジェクトが代入されているのです。 (ただし、厳密には無名関数ではなく、関数オブジェクトの属性として関数名が記録されています)

>>> def add(a, b):
...     return a + b
...
>>> def call(func, a, b):
...     return func(a, b)
...
>>> call(add, 1, 3)
4
>>> x = add
>>> x(4, 5)
9
>>> x.__name__
'add'

このように、変数に代入したり関数の引数にしたり関数の戻り値にできるオブジェクトのことを、 ファーストクラスオブジェクトと呼びます。

php ではコールバック関数などを指定するときに関数名を文字列で渡すことが多いですが、 Python では関数がファーストクラスなのでそのまま渡すことが多いです。

ローカル変数とグローバル変数

php では関数内で使用する変数のうち、 global と宣言していない変数はすべてローカル変数 という扱いでしたが、 Python は「代入が宣言」というルールになっていて、一度も代入しない 参照だけの変数は宣言しなくても自動でグローバル変数という扱いになります。

>>> def foo():
...     print(z)
...
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
NameError: global name 'z' is not defined
>>> z = 3
>>> foo()
3

Note

Python の関数宣言は変数に関数オブジェクトを代入していたことに注目してください。

もし、グローバル変数を参照するためだけに global 宣言が必要なら、 関数呼び出しするためにその関数を global 宣言しないといけなくなってしまいます。

グローバル変数に値を代入したい場合は、 php と同じように global 宣言をします。

>>> def foo():
...     x = 'foo'
...
>>> x=0
>>> foo()
>>> x
0
>>> def foo():
...     global x
...     x = 'foo'
...
>>> foo()
>>> x
'foo'

キーワード引数

関数を呼び出すときに、仮引数名を指定して引数を渡すことができます。引数が多く、 殆どにデフォルト値が設定されている場合などに便利です。

>>> def foo(a, b='B', c='C', d='D'):
...     return a + b + c + d
...
>>> foo('A', d='xxx', b='yyy')
'AyyyCxxx'

このように名前で指定する引数をキーワード引数 (keyword argument) と呼び、 その対象として名前なしで順序によってしていされる引数を位置引数 (positional argument) と呼びます。

引数の順序の間違いは、コードレビューでも見つけにくいバグになります。 キーワード引数を使えば、コードを読むときに、どの引数に何を与えているのかが ぐっと解りやすくなります。

ただ、C言語で書かれた組み込み関数では、キーワード引数を受け付けないものも あります。

>>> range(10, step=2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: range() does not take keyword arguments

可変長引数

引数リストの最後に *args のように * を先頭につけた引数を宣言すると、 可変長引数を tuple として受け取ることができます。

>>> def foo(a, b, *args):
...     print("a:", a)
...     print("b:", b)
...     print("args:", args)
...
>>> foo(1,2,3,4,5)
a: 1
b: 2
args: (3, 4, 5)

同じように任意のキーワード引数を受け付ける場合は、 **kwargs のように書くと、 キーワード引数が dict として渡されます。 *args**kwargs を両方使う場合は、 **kwargs の方を後ろに書きます。

>>> def foo(a, b, *args, **kwargs):
...     print("a:", a)
...     print("b:", b)
...     print("args:", args)
...     print("kwargs:", kwargs)
...
>>> foo(1, 2, 3, 4, x=7, y=8)
a: 1
b: 2
args: (3, 4)
kwargs: {'y': 8, 'x': 7}

引数のアンパック渡し

引数を受け取る時の場合と対照的に、引数を渡すときに *** を使うことで、 list や tuple や dict を位置引数やキーワード引数として与えることができます。

>>> def add3(a, b, c):
...     return a + b + c
...
>>> x = [1, 3]
>>> add3(2, *x)
6
>>> x = {'b': 3, 'c': 9}
>>> add3(1, **x)
13

参照渡しが無い理由

参照渡しのユースケースは、 (1) 複数の戻り値を返す、 (2) 呼び出し元の変数を変更する、 というものです。

(1) に関しては、 tuple の生成やアンパックが十分高速なので、高速化のために list(x, y) = some_func()some_func(x, y) の参照渡しにする必要が ありません。

(2) に関しては、 Python は php の文字列やarrayのように CoW (Copy on Write) な変数が そもそも存在しないので、 php でオブジェクト型の変数を引数に渡した時と同じように、 呼び出された関数内でオブジェクトを変更することができます。

>>> def append(L, x):
...     L.append(x)
...
>>> K = []
>>> append(K, 1)
>>> K
[1]
>>> append(K, 5)
>>> K
[1, 5]

クロージャ

php の無名関数と同じように、 Python も関数内で関数を定義することができます。 ただし、何も宣言しなくても外側の関数の変数を参照できます。 これをクロージャと言います。

>>> def make_generator(x):
...     def func():
...         return x
...     return func
...
>>> gen = make_generator(3)
>>> gen()
3
>>> gen()
3

クロージャが参照しているのが、外側の関数の中の 変数 であることに注意してください。 次のような場合に、期待と異なる動作をします。

>>> def make_generators(n):
...     generators = []
...     for i in range(n):
...         def func():
...             return i
...         generators.append(func)
...     return generators  # この時点で、 i の値は n-1 になっている.
...
>>> gens = make_generators(3)
>>> gens[0]()
2

この動作が問題になる場合は、関数の引数のデフォルト値が関数を宣言するときに決定される という性質を利用する事ができます。

>>> def make_generators(n):
...     generators = []
...     for i in range(n):
...         def func(i=i): # 引数 i のデフォルト値として、外側の関数の i の値を利用する
...             return i   # この i は func の引数
...         generators.append(func)
...     return generators
...
>>> gens = make_generators(3)
>>> gens[0]()
0
>>> gens[1]()
1

デコレータ

ちょっとだけメタプログラミングな話題に突入します。

関数がファーストクラスになると、例えば次のように関数を dict に登録して、後で参照することができます。

>>> methods = {}
>>> def register_method(func):
...     methods[func.__name__] = func   # func.__name__ には関数名が格納されている
...     return func
...
>>> def GET():
...     print("get")
...
>>> register_method(GET)
<function GET at 0x7fe67d0e1de8>
>>> methods
{'GET': <function GET at 0x7fe67d0e1de8>}
>>> methods['GET']()
get

関数の定義と、定義した関数を別の関数の引数として呼び出す動作を一度に行う、 デコレータという構文があります。

>>> @register_method
... def POST():
...     print 'post'
...
>>> methods['POST']()
post

上の例では、 @register_method は、 POST を定義したあとに POST = register_method(POST) と書くのと同じ動作をします。 デコレータによって、定義した関数を置き換えられることに注目してください。 なので、次のような関数を書くこともできます。

>>> import time
>>> def profile(func):
...     def wrapped(*args, **kwargs):
...         t = time.time()
...         ret = func()
...         print(time.time() - t)
...         return ret
...     return wrapped
...
>>> @profile
... def sleeper():
...     time.sleep(3)
...
>>> sleeper()
3.00314092636

lambda

lambda 引数: という形で、式が1つだけの簡単な関数を作れます。

>>> add_ = lambda x, y: x+y
>>> add_(2, 3)
5

lambda を使うと、後に紹介する sort のように、関数を受け取る関数 (高階関数) に 渡す関数を作るときに便利です。

ジェネレータ

php 5.5 で導入されたジェネレータにかなり似ています。

ジェネレータは、 for ループなどで列挙できる (iterable) オブジェクトを作る関数で、 return の代わりに yield を使って1つずつ要素を返します。

>>> def gen(n):
...     for i in range(n):
...         yield i*2
...
>>> for x in gen(3):
...     print(x)
...
0
2
4

php のジェネレータと同じく、外から値を渡す事もできます。 この仕組みを使えばコルーチンと呼ばれる仕組みを作れるのですが、 特殊な使い方なので、ここでは省略します。