Hatena::Groupfukuoka-py

taketin.py RSSフィード

 | 

2011-12-17

はじめてのPython 17章 関数に関連する高度なテクニック

03:28 | はじめてのPython 17章 関数に関連する高度なテクニック - taketin.py を含むブックマーク はてなブックマーク - はじめてのPython 17章 関数に関連する高度なテクニック - taketin.py

  • lambda式
    • defステートメントと違う点は、作成された関数関数オブジェクト)が式の戻り値になる、という点。つまり、戻り値変数に代入しない限りは関数に名前が無い、という事になる。この為、「無名関数」と呼ばれる。インラインでの関数定義(コールバック関数の定義など)によく使用される
    • lambda式はあくまで式である為、リストのリテラル関数の呼び出しコードの中などでも使える
    • lambda式はボディ部分も式である為、defステートメントに比べると汎用性は劣るが、コードを短く凝縮できる
    • lmbda式はコールバック関数の他、ジャンプテーブル(要素として単なるオブジェクトではなく、行うべき処理を組み込んだリストやディクショナリのこと)によく利用される
L = [(lambda x: x ** 2), (lambda x: x ** 3), (lambda x: x ** 4)]

for f in L:
    print f(2)     # 4, 8, 16 が出力される

print L[0](3)     # 9 が出力される

  • lambda式とネストスコープ
    • defステートメントの中に組み込まれたlambda式では、defステートメントのスコープをそのまま利用でき、lambda式によって作成された関数オブジェクトはネストスコープの変数を保持し続ける
    • lambda式が他のlambda式に組み込まれた(ネストされた)場合も、内側の式から外側の式のスコープにアクセスする事ができる
>>> action = (lambda x: (lambda y: x + y))
>>> act = action(99)
>>> act(3)
102
>>> ((lambda x: (lambda y: x + y)) (99))(4)
103

こういった書き方は読み辛いので、あまり良い方法とは言えない。


>>> def func(x, y, z): return x + y + z
…
>>> apply(fund, (2, 3, 4))
9
>>> f = lambda x, y, z: x + y + z
>>> apply(f, (2, 3, 4))
9

※ 但し、apply関数は Python3.0 で無くなる為、使用すべきでは無い。

使っているコードの移植方法は以下など。

2to3を使ってコードをPython 3に移植する - Dive Into Python 3 日本語版


  • map関数
    • 第一引数に渡された関数を、その後に指定されたシーケンスの個々要素に適用する関数
    • forステートメントでも同じような処理は書けるが、一般的にmap関数の方が処理が早くなる
>>> counters = [1, 2, 3, 4]
>>> def inc(x): return x + 10
…
>>> map(inc, counters)
[11, 12, 13, 14]

  • map関数と類似した関数: filter と reduce
    • filter関数
      • シーケンスの各要素から特定の条件に合うものを抽出する
    • reduce関数
      • まずシーケンスの1、2番目の要素を引数として関数を実行し、次にその結果得られた値と3番目の要素を引数として同じ関数を実行する
# filter
>>> filter((lambda x: x > 0), range(-5, 5))
[1, 2, 3, 4]

# reduce
>>> reduce((lambda x, y: x + y), [1, 2, 3, 4])
10

※ reduce関数は Python3.0 で functools に隔離されたようです


  • リスト内包表記
    • リスト内包表記内で ifステートメントや forステートメントのネストを利用すれば、ビルトイン関数を使用するよりも簡素なコードが書ける
# if を使ったリスト内包表記
>>> [x for x in range(5) if x % 2 == 0]
[0, 2, 4]

# filter関数を使うと以下の様になる
>>> filter((lambda x: x % 2 == 0), range(5))
[0, 2, 4]

>>> res = []
>>> for x in range(5):
…        if x % 2 == 0:
…            res.append(x)
…
>>> res
[0, 2, 4]
# for のネストを使ったリスト内包表記
>>> res = [x + y for x in [0, 1, 2] for y in [100, 200, 300]]
>>> res
[100, 200, 300, 101, 201, 301, 102, 202, 302]

# リスト内包表記を使わない場合の例
>>> res = []
>>> for x in [0, 1, 2]:
…        for y in [100, 200, 300]:
…            res.append(x + y)
…
>>> res
[100, 200, 300, 101, 201, 301, 102, 202, 302]

    • リスト内包表記は柔軟性、汎用性が高いがそのためにコードが難解になりやすいという欠点がある。(特にネストが行われた場合)
    • しかし現状は、読みにくいコードに「パフォーマンスの高さ」があるのも事実
    • forループ < map関数 < リスト内包表記 の順にパフォーマンスが高い
    • プログラムは keep it simple を心がけるべきなので、特にパフォーマンスが重要になる部分以外は、分かりやすい forループで記述するべきである

  • ジェネレータとイテレータ
    • ジェネレータ
      • いったん何らかの値を戻して、しばらく後にその時点から処理を再開する
      • ジェネレータ関数は実行が一時停止される。停止している間は「停止時の状態はどうだったか」「どこまで処理が進んだか」というステート情報が保持される
      • 通常の関数との違いは、返り値を yieldステートメントで返すという事
    • イテレータ
    • ジェネレータ式
      • イテレータとリスト内包表記を組み合わせたもの。式はリスト内包表記の [] を () に変えたもの
      • リスト内包表記とコードの持つ意味はほぼ同じだが、リスト内包表記は結果のリストを一度に作るのに対し、ジェネレータ式は反復処理により随時生成していく
>>> for num in (x ** 2 for x in range(4)):
…        print '%s, %s' % (num, num / 2.0)
…
0, 0.0
1, 0.5
4, 2.0
9, 4.5
      • ジェネレータ式を囲む () は、そのジェネレータ式が他の () の中に単独で存在する場合には付けなくてもかまわない
>>> sum(x ** 2 for x in range(4))
14

>>> sorted(x ** 2 for x in range(4))
[0, 1, 4, 9]

>>> sorted((x ** 2 for x in range(4)), reverse=True)
[9, 4, 1, 0]

>>> import math
>>> map(math.sqrt, (x ** 2 for x in range(4)))
[0.0, 1.0, 2.0, 3.0]
      • ジェネレータ式はリスト内包表記よりもやや遅い為、使うのは実行結果が膨大になる場合のみ使用するべきである

  • 関数を作る際のコツ
    • できる限り入力には引数、出力には returnステートメントを使用する(できる限り外から独立したものにする)
    • グローバル変数はどうしても必要な場合以外は使用しない
    • 引数が可変性オブジェクトの場合でも上書きしない(意図して設計されるものは別)
    • 関数の役割は一つに絞る
    • 関数はできる限り規模の小さいものにする
    • 他のモジュール変数に直接変更を加えない

>>> schedule = [ {echo, 'Spam!', {echo, 'Ham!'} }]
>>> for (func, arg) in schedule:
…         func(arg)
…
Spam!
Ham!

  • 関数についての注意事項
    • 変数がローカルスコープに属するかどうかはスタティックに決定される(defステートメントのどこかに代入の記述があれば、実行時ではなくコンパイル時にローカルスコープと定義される)
# case1
>>> x = 99
>>> def selector():
…        print x;    # グローバル変数として扱われる
… 
>>> selector()
99

# case2
>>> def selector():
…        print x
…        x = 88    # この記述があるので、xはコンパイル時にローカルスコープとして定義される
…
>>> selector()
存在しない変数を使用した旨の UnboundLocalError が表示される
    • 戻り値の無い関数
      • return, yield ステートメントを使用しない関数戻り値は None になる
      • こういった関数は他の言語で「プロシージャ」と呼ばれるものと同様の役割となる。通常、オブジェクトを上書きすることによって役割を果たす為、いわば副作用に依存して機能するものと言える
    • ネストスコープのループ
      • ループによって次々に変更が加えられていく変数の、ある時点での値を内側にネストされたコードで保持する、という場合には注意が必要。単純に外側の値を内側の変数に代入するコードを書いただけだと、内側の変数の値も外側の値とともに次々に変化する為

PrapatanPrapatan2012/02/20 19:05Thanks for sharing. What a peslarue to read!

brqiuabrqiua2012/02/21 18:11m13eDA <a href="http://qzenzrwhjkxr.com/">qzenzrwhjkxr</a>

ketdxqyketdxqy2012/02/22 00:52dpiZKx , [url=http://wtyptaaooelw.com/]wtyptaaooelw[/url], [link=http://biarxexnjoao.com/]biarxexnjoao[/link], http://ybmseavredhw.com/

xwvdaqsfgxwvdaqsfg2012/02/29 02:58XmZddh <a href="http://gwhqkyqzsvwp.com/">gwhqkyqzsvwp</a>

 |