2010-04-26 40 views
23

Tôi có hàm python có kết quả xác định. Phải mất một thời gian dài để chạy và tạo ra một sản lượng lớn:Hashing chức năng trăn để tạo lại đầu ra khi chức năng được sửa đổi

def time_consuming_function(): 
    # lots_of_computing_time to come up with the_result 
    return the_result 

tôi sửa đổi time_consuming_function bất cứ lúc nào, nhưng tôi muốn để tránh việc nó chạy lại trong khi nó không thay đổi. [time_consuming_function chỉ phụ thuộc vào các chức năng không thay đổi được cho các mục đích được xem xét ở đây; nghĩa là nó có thể có chức năng từ thư viện Python nhưng không phải từ các phần khác của mã tôi muốn thay đổi.] Giải pháp gợi ý cho tôi là lưu vào bộ nhớ cache đầu ra và cũng lưu một số "hàm băm" của hàm. Nếu hàm băm thay đổi, hàm sẽ được sửa đổi và chúng ta phải tạo lại đầu ra.

Điều này có thể hay vô lý?


Cập nhật: dựa trên các câu trả lời, nó trông giống như những gì tôi muốn làm là để "memoize" time_consuming_function, ngoại trừ thay vì (hoặc bổ sung cho) đối số được truyền vào một chức năng bất biến, tôi muốn tài khoản cho một hàm mà chính nó sẽ thay đổi.

+4

+1 cho vấn đề thú vị. – zdav

+0

Làm cách nào để bạn sửa đổi phương thức? Bạn có muốn giữ băm trên chạy chương trình hoặc là nó trong vòng một chạy nhưng trên một số tải lại mô-đun? – doublep

+0

Tôi sẽ có phương thức trong tệp tập lệnh; Tôi có lẽ sẽ sửa đổi nó bằng tay theo thời gian. Ứng dụng là chức năng này sẽ tạo ra "dữ liệu vấn đề" để chạy trong một số mã mô phỏng. Tôi sẽ thay đổi vấn đề theo thời gian. –

Trả lời

6

Nếu tôi hiểu vấn đề của bạn, tôi nghĩ tôi sẽ giải quyết vấn đề như thế này. Đó là một cảm giác xấu xa, nhưng tôi nghĩ rằng nó đáng tin cậy hơn và trên điểm so với các giải pháp khác tôi thấy ở đây.

import inspect 
import functools 
import json 

def memoize_zeroadic_function_to_disk(memo_filename): 
    def decorator(f): 
     try: 
      with open(memo_filename, 'r') as fp: 
       cache = json.load(fp) 
     except IOError: 
      # file doesn't exist yet 
      cache = {} 

     source = inspect.getsource(f) 

     @functools.wraps(f) 
     def wrapper(): 
      if source not in cache: 
       cache[source] = f() 
       with open(memo_filename, 'w') as fp: 
        json.dump(cache, fp) 

      return cache[source] 
     return wrapper 
    return decorator 

@memoize_zeroadic_function_to_disk(...SOME PATH HERE...) 
def time_consuming_function(): 
    # lots_of_computing_time to come up with the_result 
    return the_result 
+0

Vì vậy, băm duy nhất tiếp tục là băm khóa từ điển nội bộ của Python, trong đó khóa là giá trị chuỗi của toàn bộ mã chưa được biên dịch của hàm. Có cách nào để lấy mã được biên dịch của hàm, vì vậy việc thay đổi giãn cách dòng hoặc nhận xét sẽ không dẫn đến một giá trị khác không? –

+0

@Seth, Vâng, có ý nghĩa với tôi khi sử dụng băm nội bộ của Python ở đây vì những gì bạn thực sự muốn là so sánh giá trị (vì bạn không có sự va chạm băm và không biết nó. Điều này là không chắc nhưng rất có thể.) Nếu bạn chỉ muốn để cache chức năng gần đây nhất, tôi sẽ không sử dụng dict hoặc băm nào cả, nhưng chỉ cần so sánh giá trị. Chỉ kể từ khi bạn nói (out-of-band) mà bạn muốn lưu trữ nhiều phiên bản của chức năng để bạn có thể quay trở lại mã cũ tôi đã sử dụng dict. –

+0

@Seth, Có thể lưu trữ ít thông tin hơn toàn bộ mã nguồn — khoảng trống và nhận xét và nguyên vẹn như thế này — nhưng sẽ rất cẩn thận nếu bạn có đủ điều kiện cần thiết và phù hợp để phù hợp. 'f.func_code.co_code' là bytecode mà hàm thực sự lưu trữ, nhưng tôi không chắc tôi có thể hứa với bạn rằng nó sẽ giống nhau giữa các biên dịch hay không. Tôi cũng không hoàn toàn chắc chắn nó không thể cho bạn những điều tích cực sai lầm. –

1

Thay vì đặt hàm vào chuỗi, tôi sẽ đặt hàm vào tệp riêng của nó. Gọi nó là time_consuming.py, ví dụ. Nó sẽ trông giống như sau:

def time_consuming_method(): 
    # your existing method here 

# Is the cached data older than this file? 
if (not os.path.exists(data_file_name) 
    or os.stat(data_file_name).st_mtime < os.stat(__file__).st_mtime): 
    data = time_consuming_method() 
    save_data(data_file_name, data) 
else: 
    data = load_data(data_file_name) 

# redefine method 
def time_consuming_method(): 
    return data 

Trong khi kiểm tra cơ sở hạ tầng để làm việc này, tôi sẽ nhận xét các phần chậm. Thực hiện một chức năng đơn giản mà chỉ trả về 0, nhận được tất cả các công cụ lưu/tải làm việc theo sự hài lòng của bạn, sau đó đặt các bit chậm trở lại.

-1

Những gì bạn mô tả là hiệu quả memoization. Hầu hết các chức năng phổ biến có thể được ghi nhớ bằng cách xác định một trang trí.

A (quá đơn giản) ví dụ:

def memoized(f): 
    cache={} 
    def memo(*args): 
     if args in cache: 
      return cache[args] 
     else: 
      ret=f(*args) 
      cache[args]=ret 
      return ret 
    return memo 

@memoized 
def time_consuming_method(): 
    # lots_of_computing_time to come up with the_result 
    return the_result 

Edit:

Từ bình luận Mike Graham và cập nhật của OP, nó bây giờ đã rõ ràng rằng các giá trị cần phải được lưu trữ trên chạy khác nhau của chương trình. Điều này có thể được thực hiện bằng cách sử dụng một số lưu trữ liên tục cho bộ nhớ cache (ví dụ: sử dụng Pickle hoặc tệp văn bản đơn giản hoặc có thể sử dụng cơ sở dữ liệu đầy đủ hoặc bất kỳ thứ gì ở giữa). Việc lựa chọn phương pháp sử dụng nào phụ thuộc vào những gì OP cần. Một số câu trả lời khác đã đưa ra một số giải pháp cho điều này, vì vậy tôi sẽ không lặp lại điều đó ở đây.

+0

Dường như OP muốn ghi nhớ một giá trị trả về của một hàm giữa các thời gian chạy. Điều này lưu trữ các giá trị trả về khác nhau của một hàm trong một lần chạy dựa trên các đối số khác nhau. –

+0

@Mike Graham: Cảm ơn, tôi đã cập nhật câu trả lời của mình. – MAK

0

Vì vậy, đây là một thủ thuật sử dụng thực sự gọn gàng trang trí:

 
def memoize(f): 
    cache={}; 
    def result(*args): 
     if args not in cache: 
      cache[args]=f(*args); 
     return cache[args]; 
    return result; 

Với trên, sau đó bạn có thể sử dụng:

 
@memoize 
def myfunc(x,y,z): 
    # Some really long running computation 

Khi bạn gọi myfunc, bạn sẽ thực sự được viện dẫn memoized phiên bản của nó. Khá gọn gàng, huh? Bất cứ khi nào bạn muốn xác định lại chức năng của bạn, bạn chỉ cần sử dụng "@memoize" một lần nữa, hoặc một cách rõ ràng viết:

 
myfunc = memoize(new_definition_for_myfunc); 

Sửa
Tôi không nhận ra rằng bạn muốn để cache giữa nhiều chạy.Trong trường hợp đó, bạn có thể thực hiện như sau:

 
import os; 
import os.path; 
import cPickle; 

class MemoizedFunction(object): 
    def __init__(self,f): 
     self.function=f; 
     self.filename=str(hash(f))+".cache"; 
     self.cache={}; 
     if os.path.exists(self.filename): 
      with open(filename,'rb') as file: 
       self.cache=cPickle.load(file); 

    def __call__(self,*args): 
     if args not in self.cache: 
      self.cache[args]=self.function(*args); 
     return self.cache[args]; 

    def __del__(self): 
     with open(self.filename,'wb') as file: 
       cPickle.dump(self.cache,file,cPickle.HIGHEST_PROTOCOL); 

def memoize(f): 
    return MemoizedFunction(f); 
+0

Khá gọn gàng, có, nhưng không trả lời câu hỏi. Vấn đề là mã trong phương thức đã thay đổi, không phải là các tham số đầu vào cho nó. –

+0

Dường như OP muốn ghi nhớ một giá trị trả về của một hàm giữa các thời gian chạy. Điều này lưu trữ các giá trị trả về khác nhau của một hàm trong một lần chạy dựa trên các đối số khác nhau. –

+0

@Mike, ok. Tôi đã không nhận ra điều này là giữa các lần chạy chương trình. –

0

Phần đầu tiên là ghi nhớ và tuần tự hóa bảng tra cứu của bạn. Điều đó phải đơn giản, đủ dựa trên một số thư viện serial serial. Phần thứ hai là bạn muốn xóa bảng tra cứu tuần tự của mình khi mã nguồn thay đổi. Có lẽ điều này đang được suy nghĩ vào một số giải pháp ưa thích. Có lẽ khi bạn thay đổi mã bạn kiểm tra nó ở đâu đó? Tại sao không thêm một móc vào thói quen checkin của bạn mà xóa bảng tuần tự của bạn? Hoặc nếu đây không phải là dữ liệu nghiên cứu và đang trong quá trình sản xuất, hãy làm cho nó trở thành một phần của quá trình phát hành nếu số sửa đổi tệp của bạn (đặt hàm này trong tệp riêng của nó) đã thay đổi, tập lệnh phát hành của bạn sẽ xóa bảng tra cứu nối tiếp.

Các vấn đề liên quan