キーワード引数を辞書として受け取る

入門Python3 4.7.5


関数を定義する時に引数の前に**をつけるとキーワード引数を辞書化して受け取れるようになります。

def foo(**kwargs):
    print(kwargs)

foo()
foo(a=1,b=2,c=3)
$ py main.py
{}
{'a': 1, 'b': 2, 'c': 3}


通常の引数と混ぜて使うことも出来ます、その場合**引数は最後に指定します。

def foo(a,**kwargs):
    print(a)
    print(kwargs)

foo(b=2,c=3,a=1)
$ py main.py
1
{'b': 2, 'c': 3}


さらに*引数によるタプル化とも併用できます。その場合*引数、**引数の順に指定する必要があります。

def foo(*args,**kwargs):
    print(args)
    print(kwargs)

foo(1,2,3,a=1,b=2,c=3)
$ py main.py
(1, 2, 3)
{'a': 1, 'b': 2, 'c': 3}


さらに通常の引数、*引数、**引数、と全部混ぜて使うことも出来ます。

def foo(arg,*args,**kwargs):
    print(arg)
    print(args)
    print(kwargs)

foo(1,2,3,a=1,b=2,c=3)
$ py main.py
1
(2, 3)
{'a': 1, 'b': 2, 'c': 3}


ちなみに辞書化する変数名はPythonの慣習的にkwargsと名付けるのが一般的のようです。

docstringによるドキュメント定義

入門Python3 4.7.6


関数定義直後に文字列を定義することでその関数に対するドキュメントを定義することができます。

定義されたドキュメントはhelp関数でを使うことで整形されたドキュメントにして出力されます。

def foo(a):
    'ただ与えられた引数を表示するだけのものです。'
    print(a)

help(foo);
$ py main.py
Help on function foo in module __main__:

foo(a)
    ただ与えられた引数を表示するだけのものです。


整形前の素のドキュメント得るには以下のようにします。

def foo(a):
    'ただ与えられた引数を表示するだけのものです。'
    print(a)

print(foo.__doc__);
$ py main.py
ただ与えられた引数を表示するだけのものです。

なかなか便利な機能ですね。

関数もオブジェクト

入門Python3 4.7.7


Pythonすべてがオブジェクトなのでつまりそれは関数もオブジェクトであるってことだ。

なので関数自体、関数への引数として渡したりできる。

def foo():
    print("hello!")

def bar(func):
    func()

bar(foo)
$ py main.py
hello!

fooという関数オブジェクトをbarへ渡し受け取ったfunc変数でそれを呼ぶ。シンプルですね。

関数内で関数を定義する

入門Python3 4.7.8


タイトル通り、関数の中で関数を定義することが出来ます。

def foo():
    def bar(a):
        print(a + 1)
    bar(1)

foo()
$ py main.py
2


関数内で定義した関数は、関数の外では呼び出せない模様。

def foo():
    def bar(a):
        print(a + 1)
    bar(1)

bar(10)
$ py main.py
Traceback (most recent call last):
  File "main.py", line 8, in <module>
    bar(10)
NameError: name 'bar' is not defined


ただしfooの戻り値として関数オブジェクトを返せば呼び出せる。

def foo():
    def bar(a):
        print(a + 1)
    return bar

func = foo()
func(10)
$ py main.py
11


直接barを呼び出す方法があるのかもしれないけど、ちょっと調べた感じではわかりませんでした。まぁ無理ってことですかね。

クロージャ

入門Python3 4.7.9


クロージャです。C#にもありますね、同じものです。

C#でも作ったカウンタを作るコードをPythonでも書いてみます。

def counter (i):
    def inner():
        ret = i
        i += 1
        return ret
    return inner

cnt1 = counter(1)
cnt2 = counter(10)

print(cnt1())
print(cnt1())
print(cnt1())

print(cnt2())
print(cnt2())
print(cnt2())

簡単ですね、さあ実行してみると・・・

$ py main.py
Traceback (most recent call last):
  File "main.py", line 12, in <module>
    print(cnt1())
  File "main.py", line 4, in inner
    ret = i
UnboundLocalError: local variable 'i' referenced before assignment

あれ・・・エラーが出ます。

エラー内容を調べるとどうやら、関数の外で定義された変数はローカル変数であり、関数の中で参照は出来るが変更はできないとのこと。

つまり上記実装でいうとi変数はinner関数の外で定義された変数なのでiを参照することができるけどiを変更することはできないってことなのです。

うーんじゃあどうすればいいのか。

要はiそのものを変更しなければ良いだけなのでiが参照してる何かなら変更しても問題はないということになります。

つまりcounter関数内でiを一旦リストに書き換えちゃえば良いということです。

def counter (i):
    i = [i] # リスト化する
    def inner():
        ret = i[0]
        i[0] += 1 # iは書き換えてないからエラーにならない
        return ret
    return inner

cnt1 = counter(1)
cnt2 = counter(10)

print(cnt1())
print(cnt1())
print(cnt1())

print(cnt2())
print(cnt2())
print(cnt2())
$ py main.py
1
2
3
10
11
12

inner関数内でi変数そのものは変更しておらず、あくまでもi変数が参照してるリストを書き換えてるだけなのでエラーにならず期待通りに動くというわけです。

しかしせっかくの便利なクロージャなのにこういったちょっとバッドノウハウ的な使い方しかできないのはちょっと残念ですよね・・・


・・・と思いきや、この話はPython2の時のやり方でPython3からはこういったケースの時に値を変更できるようにする構文が追加されています。すごーい。

それがnonlocal文です。

def counter (i):
    def inner():
        nonlocal i # iは変更しますよエラーにしないでね
        ret = i
        i += 1     # 変更してもエラーにならない
        return ret
    return inner

これで期待通りになりました。良いですねぇ、クロージャは便利なので今後も使っていきます。

無名関数(ラムダ関数)

入門Python3 4.7.10


名前の無い関数のことです。

巷ではクロージャと相性が良いと言われるこの無名関数、さっそく前回のクロージャの記事で書いたカウンタの処理を無名関数で置き換えてみます。

def counter (i):
    func = lambda:
        nonlocal i
        ret = i
        i += 1
        return ret
    return func

簡単ですね、さあ実行してみると・・・

$ py main.py
  File "main.py", line 5
    func = lambda:
                 ^
SyntaxError: invalid syntax

あれ、エラーがでました。

条件反射で実装してみたわけですが、よくよくちゃんと仕様を読んでみると無名関数は複数行の処理はかけないようです。(ググってみたら括弧を使って擬似複数行っぽいことまでは出来るみたいですが)

そしてそもそもreturn文すら必要ありません。


以下、引数無しの無名関数の定義になります。

func = lambda : 100
print(func())
$ py main.py
100


引数有りならこう

func = lambda i: i + 1
print(func(10))
$ py main.py
11


つまり結局のところカウンタのような複雑な処理をする場合、無名関数ではなく関数内関数で実装するのが正解ってことになりますかね。