2017-06-28 7 views
2

最近、コンテキストマネージャがネストされているかどうかを検出する方法があるのか​​疑問に思っています。コンテキストマネージャのネストの検出

私が作成したタイマーとTimerGroupクラス:読み取り可能な形式で

class Timer: 
    def __init__(self, name="Timer"): 
     self.name = name 
     self.start_time = clock() 

    @staticmethod 
    def seconds_to_str(t): 
     return str(timedelta(seconds=t)) 

    def end(self): 
     return clock() - self.start_time 

    def print(self, t): 
     print(("{0:<" + str(line_width - 18) + "} >> {1}").format(self.name, self.seconds_to_str(t))) 

    def __enter__(self): 
     return self 

    def __exit__(self, exc_type, value, traceback): 
     self.print(self.end()) 


class TimerGroup(Timer): 
    def __enter__(self): 
     print(('= ' + self.name + ' ').ljust(line_width, '=')) 
     return self 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     total_time = self.seconds_to_str(self.end()) 
     print(" Total: {0}".format(total_time).rjust(line_width, '=')) 
     print() 

このコードは、プリントタイミング:しかし

with TimerGroup("Collecting child documents for %s context" % context_name): 
    with Timer("Collecting context features"): 
     # some code... 
    with Timer("Collecting child documents"): 
     # some code... 


= Collecting child documents for Global context ============ 
Collecting context features    >> 0:00:00.001063 
Collecting child documents     >> 0:00:10.611130 
====================================== Total: 0:00:10.612292 

、IネストTimerGroupsが、それは物事を台無し:

with TimerGroup("Choosing the best classifier for %s context" % context_name): 
    with Timer("Splitting datasets"): 
     # some code... 
    for cname, cparams in classifiers.items(): 
     with TimerGroup("%s classifier" % cname): 
      with Timer("Training"): 
       # some code... 
      with Timer("Calculating accuracy on testing set"): 
       # some code 


= Choosing the best classifier for Global context ========== 
Splitting datasets       >> 0:00:00.002054 
= Naive Bayes classifier =================================== 
Training         >> 0:00:34.184903 
Calculating accuracy on testing set  >> 0:05:08.481904 
====================================== Total: 0:05:42.666949 

====================================== Total: 0:05:42.669078 

私が必要とするのは、何らかの形でネストされたタイマーとタイマーグループをインデントすることです。コンストラクタにパラメータを渡す必要がありますか?あるいは、クラス内からそのことを検出できますか?

+2

、インデントを格納します(親がなければ0、親ならば)。インデント+ 1がある場合) – Artyer

答えて

1

実行するネストされたコンテキストマネージャの数に基づいてインデントレベルを調整し、クラス属性がindent_levelとなるようにして、コンテキストマネージャに入り、コンテキストマネージャを終了するたびに調整します。以下のような何か:

class Context: 
    indent_level = 0 

    def __init__(self, name): 
     self.name = name 

    def __enter__(self): 
     print(' '*4*self.indent_level + 'Entering ' + self.name) 
     self.adjust_indent_level(1) 
     return self 

    def __exit__(self, *a, **k): 
     self.adjust_indent_level(-1) 
     print(' '*4*self.indent_level + 'Exiting ' + self.name) 

    @classmethod 
    def adjust_indent_level(cls, val): 
     cls.indent_level += val 

としてそれを使用する:あなたが「親タイマーグループ」として、各 `TimeGroup`インスタンス内の別のタイマ・グループを受け入れるために、` TimerGroup`を変更することができます

>>> with Context('Outer') as outer_context: 
     with Context('Inner') as inner_context: 
      print(' '*inner_context.indent_level*4 + 'In the inner context') 


Entering Outer 
    Entering Inner 
     In the inner context 
    Exiting Inner 
Exiting Outer 
+0

あなたのコードは、1つのタイマークラスでしか動作しないようです。しかし、ここでは 'Timer'と' TimerGroup(Timer) 'を持っており、インデントカウンタはそれぞれ独立して作成されているようです。階層内のすべてのクラスで共有されるフィールドを作成する方法はありますか? 私の考えは、 'adjust_indent_level()'メソッドで 'Timer.indent_level'を修正し、それをTimerGroupの' __enter__'メソッドと '__exit__'メソッドの中でのみ呼び出すことでした。 – QooBooS

+0

あなたは常にシングルスレッドの場合、これは動作します。マルチスレッドのアプローチについては、 'threading.local'の使用に関するMartijn-Pietersの答えを参照してください。 – 9000

4

ネストされたコンテキストマネージャを検出する特別な機能はありません。あなたはこれを自分で処理しなければならないでしょう。あなたはあなた自身のコンテキストマネージャの中にこれを行うことができます:あなたは、その後、現在アクティブなグループをつかむために他の場所TimerGroup._active_group属性を使用することができます

import threading 


class TimerGroup(Timer): 
    _active_group = threading.local() 

    def __enter__(self): 
     if getattr(TimerGroup._active_group, 'current', False): 
      raise RuntimeError("Can't nest TimerGroup context managers") 
     TimerGroup._active_group.current = self 
     print(('= ' + self.name + ' ').ljust(line_width, '=')) 
     return self 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     TimerGroup._active_group.current = None 
     total_time = self.seconds_to_str(self.end()) 
     print(" Total: {0}".format(total_time).rjust(line_width, '=')) 
     print() 

。これを複数の実行スレッドにわたって使用できるように、thread-local objectを使用しました。

また、あなたが作ることができることを、スタックカウンタと、ネストされた__enter__通話中だけインクリメントとデクリメント、またはスタックリスト、もう一度ポップ、そのスタックにselfをプッシュするあなた__exit__

import threading 


class TimerGroup(Timer): 
    _active_group = threading.local() 

    def __enter__(self): 
     if not hasattr(TimerGroup._active_group, 'current'): 
      TimerGroup._active_group.current = [] 
     stack = TimerGroup._active_group.current 
     if stack: 
      # nested context manager. 
      # do something with stack[-1] or stack[0] 
     TimerGroup._active_group.current.append(self) 

     print(('= ' + self.name + ' ').ljust(line_width, '=')) 
     return self 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     last = TimerGroup._active_group.current.pop() 
     assert last == self, "Context managers being exited out of order" 
     total_time = self.seconds_to_str(self.end()) 
     print(" Total: {0}".format(total_time).rjust(line_width, '=')) 
     print() 
+0

グループをネスト可能にし、 '_active_group'を' list'、つまりスタックにするのは当然です。 – 9000

1

import this

明示的、暗黙的な

よりも優れています

クリーナーデザインが明示的にグループを指定することができるようになる。一方

with TimerGroup('Doing big task') as big_task_tg: 
    with Timer('Foo', big_task_tg): 
     foo_result = foo() 
    with Timer('Bar', big_task_tg): 
     bar(baz(foo_result)) 

を、あなたは常にtraceback.extract_stackを使用して、上流の特定の機能の呼び出しを探すことができます。これは、ロギングとエラー報告に非常に役立ち、特定の機能が特定のコンテキストでのみ呼び出されることを保証するのに適度に役立ちます。しかし、追跡が非常に困難な依存関係を作成する傾向があります。

タイマーをグループ化するのは避けていますが、試してみることもできます。自動グループ化が大変必要な場合は、@ Martijn-Pietersのアプローチがはるかに優れています。

+0

あなたのアイデアは基本的な使い方にはかなり良いですが、時々別のメソッドからタイマーを呼び出す必要があります(別のメソッドを呼び出す方法と入れ子にする必要があるタイマーを使う方法があります)。だから、@Billyの答えが私の問題にもっと適している、と私は思う。 – QooBooS

関連する問題