今さらPython3 (29) - 関数のつづき

順調に読み進めてます。

入門 Python 3

入門 Python 3

Docstrings

コメントの書き方のところで言いかけたのはこれの事だね。

>>> def echo(anything):
...     'echo returns its input argument'
...     return anything
...
>>> def print_if_true(thing, check):
...     '''
...     Prints the first argument if a second argument is true.
...     The operation is:
...         1. Check whether the *second* argument is true.
...         2. If it is, print the *first argument.
...     '''
...     if check:
...         print(thing)
...
>>> print_if_true('I am crazy.', True)
I am crazy.
>>>

関数(function)に関するドキュメントを本体の最初の部分にstr形式で書く。'''を用いて複数行にまたがってもOK。

>>> help(echo)
Help on function echo in module __main__:

echo(anything)
    echo returns its input argument

>>> help(print_if_true)
Help on function print_if_true in module __main__:

print_if_true(thing, check)
    Prints the first argument if a second argument is true.
    The operation is:
        1. Check whether the *second* argument is true.
        2. If it is, print the *first argument.
>>>

help(関数名)とやると、ドキュメントが表示される。

>>> print(echo.__doc__)
echo returns its input argument

これは、関数名.__doc__を引数として、ドキュメントの前後に表示される余計なお飾り(Help on function ... in module __main__:とか)が出さないパターン。

First-Class Citizensというのなら

見せてもらおうじゃないの。

>>> def answer():
...     print(42)
...
>>> answer()
42
>>> def run_something(func):
...    func()
...
>>> run_something(answer)
42
>>> type(run_something)
<class 'function'>

answer()という関数があります。これをrun_something()という関数の引数として使えますと。func()と書いてあるところは、実際にはanswer()が実行されて、42という値が返ってくる。なるほど。

>>> def add_args(arg1, arg2):
...     print(arg1 + arg2)
...
>>> type(add_args)
<class 'function'>
>>> def run_something_with_args(func, arg1, arg2):
...    func(arg1, arg2)
...
>>> run_something_with_args(add_args, 5, 9)
14
>>> run_something_with_args(add_args, answer, 9)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in run_something_with_args
  File "<stdin>", line 2, in add_args
TypeError: unsupported operand type(s) for +: 'function' and 'int'

さっきのanswer()を引数に渡したらどうなるか試したけど、それはダメだね。ま、ソースを見れば理由は分かるけど。

>>> def sum_args(*args):
...     return sum(args)
...
>>> sum_args(1,2,3,4,5)
15
>>> def run_with_positional_args(func, *args):
...     return func(*args)
...
>>> run_with_positional_args(sum_args, 1, 2, 3, 4)
10

さっき見た*argsのテクニックを使ってますよって事だね。

入れ子の関数

>>> def outer(a,b):
...     def inner(c,d):
...         return c + d
...     return inner(a,b)
...
>>> outer(4,7)
11

outer()の中にinner()という関数が定義されているというパターン。

>>> inner(4,3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'inner' is not defined

関数の中で繰り返し呼び出される部分を内部関数として定義しておくとソースかスッキリするよと理解すれば良いのかな。もちろん、内部関数を直接呼び出すことはできない。

>>> def knights(saying):
...     def inner(quote):
...         return "We are the knights who say: '%s'" % quote
...     return inner(saying)
...
>>> knights('Ni!')
"We are the knights who say: 'Ni!'"

これだと例として適切かな?こんな感じでは?

>>> def knights(saying1, saying2):
...     def inner(quote):
...         return "We are the knights who say: '%s'\n" % quote
...     return inner(saying1) + inner(saying2)
...
>>> knights('Ni!', 'Nya!')
"We are the knights who say: 'Ni!'\nWe are the knights who say: 'Nya!'\n"

あれ、改行に失敗しているけど、print()にしないとダメかな?

>>> def knights(saying1, saying2):
...     def inner(quote):
...         return "We are the knights who say: '%s'\n" % quote
...     return print(inner(saying1) + inner(saying2))
...
>>> knights('Ni!', 'Nya!')
We are the knights who say: 'Ni!'
We are the knights who say: 'Nya!'

ま、ニャーと叫ぶ騎士は微妙だけどね。

Closures = クロージャ = 関数閉包

内部関数はクロージャとして振る舞う。日本語の意味が分からんw。でも、これまでのように実際に動かしてみて理解する。

>>> def knights2(saying):
...     def inner2():
...         return "We are the knights who say: '%s'" % saying
...     return inner2
...
>>>

さっきとの違いは、内部関数inner2の引数がなくなって、(内部関数の)returnにsayingの値を直接渡していること。あとは、外側の関数knight2のreturnにinner2を後ろのかっこなしで渡している。意味分からん。

>>> a = knights2('Duck')
>>> b = knights2('Hasenpheffer')
>>> type(a)
<class 'function'>
>>>
>>> type(b)
<class 'function'>

knight2()の戻り値が格納されているa, bをtype()で見るとfunctionになっているということを腹落ちさせないと。。。
return inner2()なら関数inner2()が実行されてその結果が渡されるのでstr型になるはずなので、かっこなしで関数名を指定したことで、関数そのものが戻り値になったと考えれば良いかな?

>>> a
<function knights2.<locals>.inner2 at 0x02BAFA98>
>>> b
<function knights2.<locals>.inner2 at 0x02BAF978>
>>> a()
"We are the knights who say: 'Duck'"
>>> b()
"We are the knights who say: 'Hasenpheffer'"
>>>

そこで、a(), b()で実行すると、knight2()を実行したときのsayingの中身が残っていて、あんな感じで結果が出る。あとで日本語版の本も読んでみる。

もう1つの謎lambda

ラムダが分かると賢い人みたいな勝手なイメージがある自分は、正直あんまり分かってない。なので、ここでお勉強。

>>> def edit_story(words, func):
...     for word in words:
...         print(func(word))
...
>>> stairs = ['thud', 'meow', 'thud', 'hiss']
>>> def enliven(word):    #give that prose more punch
...     return word.capitalize() + '!'
...
>>> edit_story(stairs, enliven)
Thud!
Meow!
Thud!
Hiss!
>>>

プログラム的に読解すると、リストstairsに入っている単語1つ1つに対して、enliven()を実行するので、単語がCapitalize(最初の文字が大文字に)されて、さらに!が追加されたものをprint()で出力しているということ。

>>> edit_story(stairs, lambda word: word.capitalize() + '!')
Thud!
Meow!
Thud!
Hiss!
>>>

で、lambdaを使うと、enliven()を使わなくてもOKということなんだよね。lambdaは1つの引数を取り(word)、コロン(:)の後ろ側が関数に相当する部分になると。

本にも書いてあるように、実際のところlambda書くより、関数定義した方が良いケースが見やすいことも多いけど、やたら細かい(=小さい)関数をたくさん使って内容を参照するケースには便利らしい。あとはGUIのcallback functionsによく使うみたい。

ここは理解度まだ80%くらいだな。。。


(つづく)