エキスパートPythonプログラミング読書会03に参加しました

エキスパートPythonプログラミング読書会03 : ATND に参加して講師のお手伝いをしてきました。

2章の後半、以下の内容を行いました。

  • イテレータとジェネレータ
    • itertools モジュール
  • デコレータ
  • with と contextlib

最後にこんな質問がありました。

コンテキストプロバイダの実装方法が2通りあるが、デコレータで実装するか、with 文(プロトコル) で実装するか、どのように使い分ければ良いか?

@ に聞いてみて、とても勉強になりました。with 文を使用するときの分かり易いポイントの1つとして処理対象がクラスであれば、ベースクラスを継承してサブクラスで __enter__ と __exit__ を実装することができます。わざわざクラスで実装するほどでも、、、というときはデコレータで実装するのも良いです。その際、デコレータで try...finally を書いても良いですが、contextlib.contextmanager を使えば yield で中断される前処理を __enter__、後処理を __exit__ として実装してくれるのでお手軽で良さそうというのが contextlib モジュールの項での紹介でした。本来の意図するジェネレータじゃないよねという指摘もありましたが(^ ^;;

読書会で使用したサンプルコードです。

  • 07
    • islice の使用例
def islice_with_stop(n):
    """
    >>> islice_with_stop(5)
    0
    1
    2
    """
    from itertools import islice
    data = range(n)
    for i in islice(data, 3): # data[:3]
        print i

def islice_with_start_stop_step(n):
    """
    >>> islice_with_start_stop_step(5)
    1
    3
    """
    from itertools import islice
    data = range(n)
    for i in islice(data, 1, n, 2): # data[1:n:2]
        print i
    • tee の使用例
def tee_simple(n):
    """
    >>> tee_simple(3)
    t1: 0
    t1: 1
    t1: 2
    t2: 0
    t2: 1
    t2: 2
    """
    from itertools import tee
    data = range(n)
    t1, t2 = tee(data)
    for i in t1:
        print "t1:", i
    for i in t2:
        print "t2:", i

def tee_careful(n):
    """
    >>> tee_careful(3)
    data: 0
    data: 1
    t1: 2
    t2: 2
    """
    from itertools import islice, tee
    data = islice(range(n), n)
    t1, t2 = tee(data)
    print "data:", next(data)
    print "data:", next(data)
    for i in t1:
        print "t1:", i
    for i in t2:
        print "t2:", i

def tee_multiple(n):
    """
    >>> tee_multiple(1)
    t1: 0
    t2: 0
    t3: 0
    """
    from itertools import islice, tee
    data = islice(range(n), n)
    t1, t2, t3 = tee(data, 3)
    for i in t1:
        print "t1:", i
    for i in t2:
        print "t2:", i
    for i in t3:
        print "t3:", i
    • groupby の使用例
def groupby_without_sort(path):
    """
    >>> groupby_without_sort("./group_input.txt")
    aaa ['aaa'] 1
    bbb ['bbb'] 1
    aaa ['aaa', 'aaa'] 2
    bbb ['bbb'] 1
    aaa ['aaa'] 1
    ccc ['ccc'] 1
    """
    from itertools import groupby
    data = [line.rstrip() for line in file(path)]
    for key, grp in groupby(data):
        grp_data = [i for i in grp]
        print key, grp_data, len(grp_data)

def groupby_with_sort(path):
    """
    >>> groupby_with_sort("./group_input.txt")
    aaa ['aaa', 'aaa', 'aaa', 'aaa'] 4
    bbb ['bbb', 'bbb'] 2
    ccc ['ccc'] 1
    """
    from itertools import groupby
    data = [line.rstrip() for line in file(path)]
    data.sort()
    for key, grp in groupby(data):
        grp_data = [i for i in grp]
        print key, grp_data, len(grp_data)

def groupby_with_keyfunc(path):
    """
    >>> groupby_with_keyfunc("./group_input.txt")
    a ['aaa', 'aaa', 'aaa', 'aaa'] 4
    b ['bbb', 'bbb'] 2
    c ['ccc'] 1
    """
    from itertools import groupby
    data = [line.rstrip() for line in file(path)]
    data.sort()
    for key, grp in groupby(data, lambda x: x[0]):
        grp_data = [i for i in grp]
        print key, grp_data, len(grp_data)
  • 08
    • izip の使用例
def use_izip(key, data):
    """
    >>> use_izip(["a", "b", "c"], [1, 2, 3])
    ...     print i
    [('a', 1), ('b', 2), ('c', 3)]
    ------------------------------
    ('a', 1)
    ('b', 2)
    ('c', 3)
    """
    from itertools import izip
    zipped_tupple = zip(key, data)
    print zipped_tupple
    print "-" * 30
    for tup in izip(key, data):
        print tup

def cnv_to_dict(key, data):
    """
    >>> cnv_to_dict(["a", "b", "c"], [1, 2, 3])
    {'a': 1, 'c': 3, 'b': 2}
    """
    return dict(zip(key, data))

def cnv_to_dict_with_izip(key, data):
    """
    >>> cnv_to_dict_with_izip(["a", "b", "c"], [1, 2, 3])
    {'a': 1, 'c': 3, 'b': 2}
    """
    from itertools import izip
    return dict(izip(key, data))

def read_enumerate(path):
    """
    >>> read_enumerate("./test.txt")
    3
    """
    cnt = 0
    for cnt, line in enumerate(open(path, "r")):
        cnt += 1
        # use cnt for something
    return cnt

def read_count(path):
    """
    >>> read_enumerate("./test.txt")
    3
    """
    from itertools import count, izip
    cnt = 0
    for cnt, line in izip(count(1), open(path, "r")):
        pass
        # use cnt for something
    return cnt
  • 09
    • シンプルなデコレータ
def bar(func):
    print '### I am bar'
    return func

def foo(func):
    print '@@@ I am foo'
    return func

@bar
@foo
def hoge():
    """
    >>> hoge()
    @@@ I am foo
    ### I am bar
    """
    print '*** hoge'

def fuga():
    """
    *** hoge
    >>> bar(foo(fuga))()
    @@@ I am foo
    ### I am bar
    +++ fuga
    """
    print '+++ fuga'
  • 10
    • with でロックを扱った使用例
import sys
from multiprocessing import Process, Lock

def worker_with(lock, stream):
    with lock:
        stream.write("Lock acquired via with\n")

def worker_no_with(lock, stream):
    lock.acquire()
    try:
        stream.write("Lock acquired try...fainaly\n")
    finally:
        lock.release()

def main():
    lock = Lock()
    w = Process(target=worker_with, args=(lock, sys.stdout))
    t = Process(target=worker_no_with, args=(lock, sys.stdout))

    w.start()
    t.start()

    w.join()
    t.join()

if __name__ == "__main__":
    main()
    • with でファイルを扱った使用例
def read_file(path, mode, charset="utf-8"):
    import io
    with io.open(path, mode, encoding=charset) as file_obj:
        for num, line in enumerate(file_obj):
            yield num + 1, line.rstrip("\r\n")

def main():
    from time import sleep
    path = "./test.txt"
    for num, line in read_file(path, mode="r"):
        print num, line
        sleep(1)

    while True:
        print "loop"
        sleep(1)

if __name__ == "__main__":
    main()
  • 11
    • contextlib.nested の使用例
from contextlib import contextmanager, nested

@contextmanager
def make_context(name):
    print "entering:", name
    yield name
    print "exiting :", name

def main():
    with nested(make_context("ネスト"),
                make_context("だと"),
                make_context("...")) as (a, b, c):
        print "inside with statement:", a, b, c

if __name__ == "__main__":
    main()
    • contextlib.closing の使用例
from contextlib import contextmanager, closing
from urllib import urlopen

def main():
    with closing(urlopen("http://www.dehenken.co.jp")) as response:
        print response.info()
        print response
    
    print "after with block: ", response

if __name__ == "__main__":
    main()
    • contextlib.closing の使用例(例外)
from contextlib import contextmanager, closing
from urllib import urlopen

def main():
    try:
        with closing(urlopen("http://www.dehenken.co.jp")) as response:
            print response.info()
            raise RuntimeError('rasing RuntimeError')
    except Exception as err:
        print err

    print "after with block: ", response

if __name__ == "__main__":
    main()