2010-12-13 34 views
8

Tôi đang cố gắng xây dựng một trình trang trí cho một phương thức thể hiện của một lớp sẽ ghi nhớ kết quả. (Điều này đã được thực hiện một triệu lần trước) Tuy nhiên, tôi muốn tùy chọn có thể đặt lại bộ nhớ cache đã ghi nhớ tại bất kỳ thời điểm nào (ví dụ, nếu một cái gì đó trong trạng thái cá thể thay đổi, điều này có thể thay đổi kết quả của phương thức không có gì để làm với args của nó). Vì vậy, tôi đã cố gắng xây dựng một trình trang trí như một lớp thay vì một hàm, để tôi có thể có quyền truy cập vào bộ nhớ cache với tư cách là một thành viên của lớp. Điều này dẫn tôi xuống con đường học hỏi về các mô tả, cụ thể là phương pháp __get__, đó là nơi tôi thực sự bị mắc kẹt. Mã của tôi trông giống như vậy:python resizable instance method memoization decorator

import time 

class memoized(object): 

    def __init__(self, func): 
     self.func = func 
     self.cache = {} 

    def __call__(self, *args, **kwargs): 

     key = (self.func, args, frozenset(kwargs.iteritems())) 

     try: 
      return self.cache[key] 
     except KeyError: 
      self.cache[key] = self.func(*args, **kwargs) 
      return self.cache[key] 
     except TypeError: 
      # uncacheable, so just return calculated value without caching 
      return self.func(*args, **kwargs) 

    # self == instance of memoized 
    # obj == instance of my_class 
    # objtype == class object of __main__.my_class 
    def __get__(self, obj, objtype=None): 
     """Support instance methods""" 
     if obj is None: 
      return self 

     # new_func is the bound method my_func of my_class instance 
     new_func = self.func.__get__(obj, objtype) 

     # instantiates a brand new class...this is not helping us, because it's a 
     # new class each time, which starts with a fresh cache 
     return self.__class__(new_func) 

    # new method that will allow me to reset the memoized cache 
    def reset(self): 
     print "IN RESET" 
     self.cache = {} 

class my_class: 
    @memoized 
    def my_func(self, val): 
     print "in my_func" 
     time.sleep(2) 
     return val 


c = my_class() 

print "should take time" 
print c.my_func(55) 
print 

print "should be instant" 
print c.my_func(55) 
print 

c.my_func.reset() 

print "should take time" 
print c.my_func(55) 

Điều này rõ ràng và/hoặc có thể? Mỗi khi __get__ được gọi, tôi nhận được một phiên bản hoàn toàn mới của lớp được ghi nhớ, làm mất bộ nhớ cache với dữ liệu thực tế trong đó. Tôi đã làm việc chăm chỉ với __get__, nhưng không đạt được nhiều tiến bộ.

Có cách tiếp cận hoàn toàn riêng biệt nào cho vấn đề này mà tôi đang thiếu hoàn toàn không? Và tất cả các lời khuyên/đề xuất đều được chào đón và đánh giá cao. Cảm ơn.

+0

tôi đã không đi sâu hơn trong mã của bạn, nhưng đối với hiệu suất tốt hơn bạn nên sử dụng 'nếu bộ nhớ cache .has_key (...): return cache [...] 'thay vì bắt' KeyError'. – khachik

+2

@khachik: 'khóa trong bộ nhớ cache' tốt hơn, vì' has_key' không được dùng nữa. – delnan

+0

Hãy nhớ rằng điểm ghi nhớ là một cuộc gọi hàm với cùng các đối số tạo ra cùng một đầu ra. Nếu bạn thực sự cần phải thiết lập lại bộ nhớ đệm dựa trên trạng thái cá thể, có lẽ bạn nên xem xét việc giữ bộ nhớ cache trong cá thể thay vì một trình trang trí được ghi nhớ. – Falmarri

Trả lời

6

Thay vì cố gắng tìm ra cơ chế triển khai của bạn, tôi đã lấy lớp trang trí memoized từ PythonDecoratorLibrary và đã sửa đổi nó để thêm reset. Dưới đây là kết quả; mẹo mà tôi đã sử dụng là thêm thuộc tính có thể gọi là reset vào chính chức năng được trang trí.

class memoized2(object): 
     """Decorator that caches a function's return value each time it is called. 
     If called later with the same arguments, the cached value is returned, and 
     not re-evaluated. 
     """ 
     def __init__(self, func): 
      self.func = func 
      self.cache = {} 
     def __call__(self, *args): 
      try: 
      return self.cache[args] 
      except KeyError: 
      value = self.func(*args) 
      self.cache[args] = value 
      return value 
      except TypeError: 
      # uncachable -- for instance, passing a list as an argument. 
      # Better to not cache than to blow up entirely. 
      return self.func(*args) 
     def __repr__(self): 
      """Return the function's docstring.""" 
      return self.func.__doc__ 
     def __get__(self, obj, objtype): 
      """Support instance methods.""" 
      fn = functools.partial(self.__call__, obj) 
      fn.reset = self._reset 
      return fn 
     def _reset(self): 
      self.cache = {} 


    class my_class: 
     @memoized2 
     def my_func(self, val): 
      print "in my_func" 
      time.sleep(2) 
      return val 


    c = my_class() 

    print "should take time" 
    print c.my_func(55) 
    print 

    print "should be instant" 
    print c.my_func(55) 
    print 

    c.my_func.reset() 

    print "should take time" 
    print c.my_func(55) 
+0

Xin cảm ơn! Tôi đã thử cách tiếp cận functools, nhưng không có kinh nghiệm để nhận ra nó đang thực sự làm gì. Tôi nhận ra toàn bộ ý tưởng khá là khó xử, vì vậy cảm ơn sự giúp đỡ của bạn! – Hoopes

+0

Điều này có thể hoạt động, nhưng có vẻ rất không hiệu quả đối với tôi khi '__get __()' thực hiện mọi tham chiếu đến phương pháp ghi nhớ - điều này hơi mỉa mai khi xem xét mục đích sử dụng ... – martineau

+0

@martineau - Vì lợi ích của giáo dục , tại sao bạn nói '__get__' là không hiệu quả? (Sự tò mò chính hãng, như người mới bắt đầu) – Hoopes

0

Vâng, tôi muốn chỉ ra hai vấn đề về hiệu suất trong mã của bạn. Đây không phải là câu trả lời cho câu hỏi của bạn, nhưng tôi không thể làm cho nó bình luận. Cảm ơn @delnan vì đã chỉ ra rằng has_key không còn được dùng nữa. Thay vì:

try: 
     return self.cache[key] 
    except KeyError: 
     self.cache[key] = self.func(*args, **kwargs) 
     return self.cache[key] 
    except TypeError: 
     # uncacheable, so just return calculated value without caching 
     return self.func(*args, **kwargs) 

tôi sẽ làm nó theo cách này:

resultDone = False 
result = None 
try: 
    if key in self.cache: return self.cache[key] 
    else: 
    result = self.func(*args, **kwargs) 
    resultDone = True 
    self.cache[key] = result 
except TypeError: # unhashable key 
    pass 
if resultDone: return result 
else: return self.func(*args, **kwargs) 

Điều này tránh: a) thử/trừ KeyError; b) gọi số cache[key] khi trả lại; c) gọi hàm một lần nữa trên các phím không thể xóa.

+2

Bạn đang sai về thử/ngoại trừ một vấn đề hiệu suất. Đúng là trường hợp ngoại lệ rất đắt, nhưng chỉ khi chúng thực sự kích hoạt: http://ideone.com/8lXyo Trong trường hợp ghi nhớ, người ta có thể mong đợi các lần truy cập bộ nhớ cache thường xuyên hơn nhiều, sau đó bỏ qua. Trong trường hợp của một bộ nhớ cache bỏ lỡ, chi phí tính toán lại rất có thể thay thế chi phí bắt một ngoại lệ. – lqc

+0

@ lqc Tôi chỉ nói rằng 'try/except KeyError' chậm hơn' has_key' hoặc 'key in', gọi nó là một hoặc 1000 lần. Tôi không được thông báo về phân phối dữ liệu trong miền OP, xin lỗi :) – khachik

2

Xây dựng dựa trên câu trả lời cho câu hỏi ban đầu được đưa ra bởi @aix Tôi đã tạo một lớp mà tôi nghĩ rằng có thể cải thiện nó. Các tính năng chính là các giá trị lưu trữ được lưu trữ như là một tài sản của trường hợp có phương pháp đang được trang trí, do đó nó rất dễ dàng để thiết lập lại chúng.

class memoize(object): 
    def __init__(self, func): 
    #print "Init" 
    self.func = func 

    def __call__(self, *args): 
    #print "Call" 
    if not self.func in self.cache: 
     self.cache[self.func] = {} 
    try: 
     return self.cache[self.func][args] 
    except KeyError: 
     value = self.func(*args) 
     self.cache[self.func][args] = value 
     return value 
    except TypeError: 
     # uncachable -- for instance, passing a list as an argument. 
     # Better to not cache than to blow up entirely. 
     return self.func(*args) 

    def __repr__(self): 
    """Return the function's docstring.""" 
    return self.func.__doc__ 

    def __get__(self, obj, objtype): 
    """Support instance methods.""" 
    #print "Get", obj, objtype 
    fn = functools.partial(self.__call__, obj) 
    try: 
     self.cache = obj.cache 
    except: 
     obj.cache = {} 
     self.cache = obj.cache 
    #print self.cache 
    return fn 

Như một ví dụ về việc sử dụng:

class MyClass(object): 
    def __init__(self,data): 
     self.data = data 

    def update(self,data): 
     self.data = data 
     self.cache = {} 

    @memoize 
    def func1(self,x): 
     print "Computing func1" 
     return "I am func1 of %s. Data is %s. x is %s\n" % (self, self.data, x) 

    @memoize 
    def func2(self,x): 
     print "Computing func2" 
     return "I am func2 of %s. Data is %s. x is %s\n" % (self, self.data, x) 

    def func3(self,x): 
     print "Computing func3" 
     return "I am func3 of %s. Data is %s. x is %s\n" % (self, self.data, x) 

mc1 = MyClass("data1") 
mc2 = MyClass("data2") 
mc3 = MyClass("data3") 

print mc1.func1(1) 
print mc1.func1(1) 
print mc1.func2(1) 
print mc1.func2(1) 
print mc1.func3(1) 
print mc1.func3(1) 

print mc2.func1(1) 
print mc2.func1(1) 
print mc2.func2(1) 
print mc2.func2(1) 
print mc2.func3(1) 
print mc2.func3(1) 

print "Update mc1\n" 
mc1.update("data1new") 

print mc1.func1(1) 
print mc1.func2(1) 
print mc1.func3(1) 
print mc2.func1(1) 
print mc2.func2(1) 
print mc2.func3(1) 

được như đầu ra:

Computing func1 
I am func1 of <__main__.MyClass object at 0x100470fd0>. Data is data1. x is 1 

I am func1 of <__main__.MyClass object at 0x100470fd0>. Data is data1. x is 1 

Computing func2 
I am func2 of <__main__.MyClass object at 0x100470fd0>. Data is data1. x is 1 

I am func2 of <__main__.MyClass object at 0x100470fd0>. Data is data1. x is 1 

Computing func3 
I am func3 of <__main__.MyClass object at 0x100470fd0>. Data is data1. x is 1 

Computing func3 
I am func3 of <__main__.MyClass object at 0x100470fd0>. Data is data1. x is 1 

Computing func1 
I am func1 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1 

I am func1 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1 

Computing func2 
I am func2 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1 

I am func2 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1 

Computing func3 
I am func3 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1 

Computing func3 
I am func3 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1 

Update mc1 

Computing func1 
I am func1 of <__main__.MyClass object at 0x100470fd0>. Data is data1new. x is 1 

Computing func2 
I am func2 of <__main__.MyClass object at 0x100470fd0>. Data is data1new. x is 1 

Computing func3 
I am func3 of <__main__.MyClass object at 0x100470fd0>. Data is data1new. x is 1 

I am func1 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1 

I am func2 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1 

Computing func3 
I am func3 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1