2011-03-14 29 views
3

假設我想使用進度條打印機ProgressMeter(如此recipe中所述)跟蹤循環的進度。分離進度跟蹤和循環邏輯

def bigIteration(collection): 
    for element in collection: 
     doWork(element) 

我希望能夠打開和關閉進度條。出於性能方面的原因,我也只想每x步更新一次。我天真的方式做,這是

def bigIteration(collection, progressbar=True): 
    if progressBar: 
     pm = progress.ProgressMeter(total=len(collection)) 
     pc = 0 
    for element in collection: 
     if progressBar: 
      pc += 1 
      if pc % 100 = 0: 
       pm.update(pc) 
     doWork(element) 

不過,我很不滿意。從「美學」的角度來看,循環的功能代碼現在被通用進度跟蹤代碼「污染」了。

你能想出一種方法來乾淨地分離進度跟蹤代碼和功能代碼嗎? (難道還有一個跟蹤進度,裝飾什麼的?)

回答

6

這樣看來,代碼將受益從null object pattern

# a progress bar that uses ProgressMeter 
class RealProgressBar: 
    pm = Nothing 
    def setMaximum(self, max): 
     pm = progress.ProgressMeter(total=max) 
     pc = 0 
    def progress(self): 
     pc += 1 
     if pc % 100 = 0: 
      pm.update(pc) 

# a fake progress bar that does nothing 
class NoProgressBar: 
    def setMaximum(self, max): 
     pass 
    def progress(self): 
     pass 

# Iterate with a given progress bar 
def bigIteration(collection, progressBar=NoProgressBar()): 
    progressBar.setMaximum(len(collection)) 
    for element in collection: 
     progressBar.progress() 
     doWork(element) 

bigIteration(collection, RealProgressBar()) 

(請原諒我的法語,呃,巨蟒,這不是我的母語;)希望你的想法,雖然)

這允許您從循環移動的最新進展邏輯,但你。在那裏仍然有一些進度相關的呼叫。

如果您從集合中創建一個生成器,並在迭代它時自動跟蹤進度,則可以刪除此部分。

# turn a collection into one that shows progress when iterated 
def withProgress(collection, progressBar=NoProgressBar()): 
     progressBar.setMaximum(len(collection)) 
     for element in collection: 
      progressBar.progress(); 
      yield element 

# simple iteration function 
def bigIteration(collection): 
    for element in collection: 
     doWork(element) 

# let's iterate with progress reports 
bigIteration(withProgress(collection, RealProgressBar())) 

這種方法讓你的bigIteration功能是和是高度可組合。例如,假設您也想添加取消這個大的迭代。只需創建另一個恰好可以取消的生成器。

# highly simplified cancellation token 
# probably needs synchronization 
class CancellationToken: 
    cancelled = False 
    def isCancelled(self): 
     return cancelled 
    def cancel(self): 
     cancelled = True 

# iterates a collection with cancellation support 
def withCancellation(collection, cancelToken): 
    for element in collection: 
     if cancelToken.isCancelled(): 
      break 
     yield element 

progressCollection = withProgress(collection, RealProgressBar()) 
cancellableCollection = withCancellation(progressCollection, cancelToken) 
bigIteration(cancellableCollection) 

# meanwhile, on another thread... 
cancelToken.cancel() 
1

我的做法是這樣的:

的循環代碼產生的進度百分比,每當它改變(或就隨時彙報)。進度跟蹤代碼然後從發生器讀取,直到它是空的;每次讀取後更新進度欄。

然而,這也有一些缺點:

  • 您需要一個函數來調用它沒有一個進度條,你仍然需要從發電機到閱讀,直到它是空的。
  • 最後不能輕易返回值。一個解決方案將包裝返回值,但是進度方法可以確定該函數是否產生了進度更新或返回值。實際上,包裝進度更新可能更好,因此可以解包常規返回值 - 但這需要更多的包裝,因爲每次進度更新都需要完成一次,而不是一次。
2

你可以重寫bigIteration作爲發電機的功能如下:

def bigIteration(collection): 
    for element in collection: 
     doWork(element) 
     yield element 

然後,你可以做一個很大的這個之外:

def mycollection = [1,2,3] 
if progressBar: 
    pm = progress.ProgressMeter(total=len(collection)) 
    pc = 0 
    for item in bigIteration(mycollection): 
     pc += 1 
     if pc % 100 = 0: 
      pm.update(pc) 
else: 
    for item in bigIteration(mycollection): 
     pass