35306


**[環境]**  
\+ OS Ubuntu 16.04  
\+ Python 3.5.2

Pythonで以下のようなデコレータを定義したとします。

[code]

    def trace(f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            print('Enter Func')
            try:
                ret = f(*args, **kwargs)
            except:
                print('EXCEPTION')
                raise
            print('Leave Func')
            return ret
        return wrapper
    
[/code]

このデコレータは通常の関数であれば期待通り動作します。

[code]

    def func():
        print('Hello')
    
    func = trace(func)
    func()
    
    << Enter Func
    << Hello
    << Leave Func
    
[/code]

しかし、ジェネレータ関数に対しては、期待通り動作しません。

[code]

    def generator_func():
        print('Start Generator')
        for i in range(3):
            yield(i)
    
    generator_func = trace(generator_func)
    for x in generator_func():
        print(x)
    
    << Enter Func
    << Leave Func
    << Start Generator
    << 0
    << 1
    << 2
    
[/code]

期待する出力は下記の通りです。このような出力を得るためのデコレータは定義可能でしょうか？

[code]

    << Enter Func
    << Start Generator
    << 0
    << 1
    << 2
    << Leave Func
    
[/code]

その関数がgeneratorかどうかは下記のコードで判定できるようなので、ジェネレータ用にデコレータが工夫可能であれば、デコレータ内でジェネレータを判定して、分岐させようと考えています。

[code]

    import inspect
    inspect.isgeneratorfunction(func)
    
[/code]


CC BY-SA 3.0
2017-06-05T10:37:20.883
35305
2017-06-06T05:06:11.810
2017-06-05T10:48:26.477
17238
17238
question
python
Pythonでジェネレータ関数に前処理,後処理を行うデコレータを適用したい


デコレータはラップ対象関数の呼び出し前と呼び出し後に処理を行えますが、ジェネレータ関数はジェネレータオブジェクトを返すだけで関数を抜けてしまうので、デコレータでは実現できないのではないかと思います。

仮に、どんな実装でもいいからイテレーションの前と後になにか処理を入れるとしたら、こんな感じでしょうか(Python3用です)

[code]

    >>> def f(g):
    ...     print('Enter Generator')
    ...     try:
    ...         yield from g
    ...     except:
    ...         print('EXCEPTION')
    ...         raise
    ...     print('Leave Generator')
    ... 
    >>> for x in f(generator_func()):
    ...     print(x)
    ... 
    Enter Generator
    Start Generator
    0
    1
    2
    Leave Generator
    
[/code]

このとき、fもまたジェネレータ関数であって、デコレータではありません。


CC BY-SA 3.0
2017-06-05T11:32:13.653
35306
2017-06-05T11:32:13.653
806
35305
answer


そもそもジェネレータではなく、コンテキスト使うところです

[code]

    from contextlib import contextmanager
    @contextmanager
    def trace():
        print('Enter Func')
        try:
            yield
        except:
            print('EXCEPTION')
            raise
        print('Leave Func')
    
[/code]

このように定義して

[code]

    def func():
        print('Hello')
    
    with trace():
        func()
    
[/code]

のように使います。もし`with`でもデコレータでも使いたい場合は以下を参照してください。  
<https://docs.python.jp/3/library/contextlib.html#using-a-context-manager-as-
a-function-decorator>


CC BY-SA 3.0
2017-06-06T05:06:11.810
35331
2017-06-06T05:06:11.810
9572
35305
answer