[英]Why does this function not work when used as a decorator?
更新 :正如Fooz先生所指出的,包裝器的功能版本有一個bug,所以我恢復了原來的類實現。 我把代碼放在GitHub上:
https://github.com/nofatclips/timeout/commits/master
有兩個提交,一個工作(使用“導入”解決方法),第二個提交。
問題的根源似乎是pickle#dumps
函數,它只在函數調用時吐出一個標識符。 當我調用Process
,該標識符指向函數的修飾版本,而不是原始函數。
原始信息 :
我試圖寫一個函數裝飾器來包裝一個進程中的長任務,如果超時到期將會被終止。 我想出了這個(工作但不優雅)的版本:
from multiprocessing import Process
from threading import Timer
from functools import partial
from sys import stdout
def safeExecution(function, timeout):
thread = None
def _break():
#stdout.flush()
#print (thread)
thread.terminate()
def start(*kw):
timer = Timer(timeout, _break)
timer.start()
thread = Process(target=function, args=kw)
ret = thread.start() # TODO: capture return value
thread.join()
timer.cancel()
return ret
return start
def settimeout(timeout):
return partial(safeExecution, timeout=timeout)
#@settimeout(1)
def calculatePrimes(maxPrimes):
primes = []
for i in range(2, maxPrimes):
prime = True
for prime in primes:
if (i % prime == 0):
prime = False
break
if (prime):
primes.append(i)
print ("Found prime: %s" % i)
if __name__ == '__main__':
print (calculatePrimes)
a = settimeout(1)
calculatePrime = a(calculatePrimes)
calculatePrime(24000)
如您所見,我注釋掉裝飾器並將calculatePrimes的修改版本分配給calculatePrime。 如果我試圖將它重新分配給同一個變量,我會在嘗試調用裝飾版本時遇到“無法解決:屬性查找builtins.function失敗”錯誤。
任何人都知道引擎蓋下發生了什么? 當我將裝飾版本分配給引用它的標識符時,原始函數是否變成了不同的東西?
更新 :要重現錯誤,我只需將主要部分更改為
if __name__ == '__main__':
print (calculatePrimes)
a = settimeout(1)
calculatePrimes = a(calculatePrimes)
calculatePrimes(24000)
#sleep(2)
產量:
Traceback (most recent call last): File "c:\\Users\\mm\\Desktop\\ING.SW\\python\\thread2.py", line 49, in <module> calculatePrimes(24000) File "c:\\Users\\mm\\Desktop\\ING.SW\\python\\thread2.py", line 19, in start ret = thread.start() File "C:\\Python33\\lib\\multiprocessing\\process.py", line 111, in start self._popen = Popen(self) File "C:\\Python33\\lib\\multiprocessing\\forking.py", line 241, in __init__ dump(process_obj, to_child, HIGHEST_PROTOCOL) File "C:\\Python33\\lib\\multiprocessing\\forking.py", line 160, in dump ForkingPickler(file, protocol).dump(obj) _pickle.PicklingError: Can't pickle <class 'function'>: attribute lookup builtin s.function failed Traceback (most recent call last): File "<string>", line 1, in <module> File "C:\\Python33\\lib\\multiprocessing\\forking.py", line 344, in main self = load(from_parent) EOFError
PS我還寫了一個safeExecution的類版本,它具有完全相同的行為。
將函數移動到腳本導入的模塊。
如果函數在模塊的頂層定義,則只能在python中進行選擇。 腳本中定義的Ones默認情況下不可選。 基於模塊的函數被腌制為兩個字符串:模塊的名稱和函數的名稱。 它們通過動態導入模塊然后按名稱查找函數對象(因此僅限頂級函數的限制)來進行unpickled。
可以擴展pickle處理程序以支持半泛型函數和lambda酸洗,但這樣做可能會很棘手。 特別是,如果要正確處理裝飾器和嵌套函數之類的東西,可能很難重構完整的命名空間樹。 如果你想這樣做,最好使用Python 2.7或更高版本或Python 3.3或更高版本(早期版本在cPickle
和pickle
的調度程序中有一個錯誤,這是一個令人不愉快的解決方法)。
是否有一種簡單的方法來挑選python函數(或以其他方式序列化其代碼)?
http://bugs.python.org/issue7689
編輯 :
至少在Python 2.6中,如果腳本只包含if __name__
塊,則腳本可以if __name__
,腳本從模塊導入calculatePrimes
和settimeout
,如果內部start
函數的名稱是猴子修補的:
def safeExecution(function, timeout):
...
def start(*kw):
...
start.__name__ = function.__name__ # ADD THIS LINE
return start
還有第二個問題與Python的變量范圍規則有關。 在start
內部對thread
變量的賦值創建了一個影子變量,其范圍僅限於對start
函數的一個評估。 它不會分配給封閉范圍中找到的thread
變量。 您不能使用global
關鍵字來覆蓋范圍,因為您需要和中間范圍和Python僅完全支持操作本地最多和全局范圍,而不是任何中間范圍。 您可以通過將線程對象放在容納在中間范圍內的容器中來克服此問題。 這是如何做:
def safeExecution(function, timeout):
thread_holder = [] # MAKE IT A CONTAINER
def _break():
#stdout.flush()
#print (thread)
thread_holder[0].terminate() # REACH INTO THE CONTAINER
def start(*kw):
...
thread = Process(target=function, args=kw)
thread_holder.append(thread) # MUTATE THE CONTAINER
...
start.__name__ = function.__name__ # MAKES THE PICKLING WORK
return start
不確定為什么你會遇到這個問題,但要回答你的標題問題:為什么裝飾師不起作用?
將參數傳遞給裝飾器時,需要將代碼結構略有不同。 基本上你必須將裝飾器實現為一個帶有__init__
和__call__
。
在init中,您收集發送給裝飾器的參數,並在調用中,您將獲得您裝飾的函數:
class settimeout(object):
def __init__(self, timeout):
self.timeout = timeout
def __call__(self, func):
def wrapped_func(n):
func(n, self.timeout)
return wrapped_func
@settimeout(1)
def func(n, timeout):
print "Func is called with", n, 'and', timeout
func(24000)
這應該讓你至少去裝飾前面。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.