簡體   English   中英

為什么這個功能在用作裝飾器時不起作用?

[英]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或更高版本(早期版本在cPicklepickle的調度程序中有一個錯誤,這是一個令人不愉快的解決方法)。

是否有一種簡單的方法來挑選python函數(或以其他方式序列化其代碼)?

Python:pickling嵌套函數

http://bugs.python.org/issue7689

編輯

至少在Python 2.6中,如果腳本只包含if __name__塊,則腳本可以if __name__ ,腳本從模塊導入calculatePrimessettimeout ,如果內部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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM