2012-02-22 33 views
8

Rõ ràng, tìm kiếm nhanh mang lại một triệu lượt triển khai và hương vị của trình trang trí ghi nhớ bằng Python. Tuy nhiên, tôi quan tâm đến một hương vị mà tôi đã không thể tìm thấy. Tôi muốn có nó như vậy mà bộ nhớ cache của các giá trị được lưu trữ có thể có công suất cố định. Khi các phần tử mới được thêm vào, nếu đạt được dung lượng, thì giá trị cũ nhất sẽ bị loại bỏ và được thay thế bằng giá trị mới nhất.Làm cách nào để tạo một trình trang trí ghi nhớ bị chặn trong Python?

Quan tâm của tôi là, nếu tôi sử dụng tính năng ghi nhớ để lưu trữ nhiều yếu tố, thì chương trình sẽ bị lỗi do thiếu bộ nhớ. (Tôi không biết mối quan tâm này có thể được thực hiện tốt như thế nào.) Nếu bộ nhớ cache có kích thước cố định thì lỗi bộ nhớ sẽ không phải là vấn đề. Và nhiều vấn đề mà tôi làm việc khi thay đổi khi chương trình thực hiện để các giá trị được lưu trong bộ nhớ cache ban đầu trông rất khác so với các giá trị được lưu trong bộ nhớ cache sau này (và sẽ ít có khả năng tái diễn sau này). Đó là lý do tại sao tôi muốn những thứ lâu đời nhất được thay thế bởi những thứ mới nhất.

Tôi đã tìm thấy lớp OrderedDict và ví dụ cho biết cách phân lớp nó để chỉ định kích thước tối đa. Tôi muốn sử dụng nó như là bộ nhớ cache của tôi, chứ không phải là một bình thường dict. Vấn đề là, tôi cần trình trang trí ghi nhớ để tham số có tên là maxlen mặc định là None. Nếu nó là None, thì bộ nhớ cache là vô hạn và hoạt động như bình thường. Bất kỳ giá trị nào khác được sử dụng làm kích thước cho bộ nhớ cache.

Tôi muốn nó hoạt động như sau:

@memoize 
def some_function(spam, eggs): 
    # This would use the boundless cache. 
    pass 

@memoize(200) # or @memoize(maxlen=200) 
def some_function(spam, eggs): 
    # This would use the bounded cache of size 200. 
    pass 

Dưới đây là đoạn code mà tôi có cho đến nay, nhưng tôi không thấy làm thế nào để vượt qua các tham số vào trang trí trong khi làm cho nó hoạt động cả hai "nude" và với một tham số.

import collections 
import functools 

class BoundedOrderedDict(collections.OrderedDict): 
    def __init__(self, *args, **kwds): 
     self.maxlen = kwds.pop("maxlen", None) 
     collections.OrderedDict.__init__(self, *args, **kwds) 
     self._checklen() 

    def __setitem__(self, key, value): 
     collections.OrderedDict.__setitem__(self, key, value) 
     self._checklen() 

    def _checklen(self): 
     if self.maxlen is not None: 
      while len(self) > self.maxlen: 
       self.popitem(last=False) 

def memoize(function): 
    cache = BoundedOrderedDict() # I want this to take maxlen as an argument 
    @functools.wraps(function) 
    def memo_target(*args): 
     lookup_value = args 
     if lookup_value not in cache: 
      cache[lookup_value] = function(*args) 
     return cache[lookup_value] 
    return memo_target 

@memoize 
def fib(n): 
    if n < 2: return 1 
    return fib(n-1) + fib(n-2) 

if __name__ == '__main__': 
    x = fib(50) 
    print(x) 

Sửa: Sử dụng gợi ý của Ben, tôi đã tạo ra trang trí sau đây, mà tôi tin rằng hoạt động theo cách tôi tưởng tượng. Điều quan trọng với tôi là có thể sử dụng các chức năng được trang trí này với multiprocessing và đó là một vấn đề trong quá khứ. Nhưng một thử nghiệm nhanh chóng của mã này dường như hoạt động chính xác, ngay cả khi nuôi công việc cho một nhóm các chủ đề.

def memoize(func=None, maxlen=None): 
    if func: 
     cache = BoundedOrderedDict(maxlen=maxlen) 
     @functools.wraps(func) 
     def memo_target(*args): 
      lookup_value = args 
      if lookup_value not in cache: 
       cache[lookup_value] = func(*args) 
      return cache[lookup_value] 
     return memo_target 
    else: 
     def memoize_factory(func): 
      return memoize(func, maxlen=maxlen) 
     return memoize_factory 

Trả lời

4
@memoize 
def some_function(spam, eggs): 
    # This would use the boundless cache. 
    pass 

Đây là một hàm được gọi trên một đối số hàm và trả về hàm. memoize là trang trí.

@memoize(200) # or @memoize(maxlen=200) 
def some_function(spam, eggs): 
    # This would use the bounded cache of size 200. 
    pass 

Đây memoize được sử dụng như một hàm được gọi vào một đối số nguyên đơn và trả về một chức năng, và rằng chức năng quay trở lại được bản thân sử dụng như một trang trí tức là nó đang kêu gọi một cuộc tranh luận chức năng duy nhất và trả về một chức năng . memoize là nhà máy trang trí trang trí.

Vì vậy, để hợp nhất hai điều này, bạn sẽ phải viết một số mã xấu xí. Con đường tôi có lẽ sẽ làm điều đó là để có memoize cái nhìn như thế này:

def memoize(func=None, maxlen=None): 
    if func: 
     # act as decorator 
    else: 
     # act as decorator factory 

Bằng cách này nếu bạn muốn truyền tham số bạn luôn vượt qua họ lập luận như từ khóa, để lại func (mà phải là một tham số vị trí) unset, và nếu bạn chỉ muốn tất cả mọi thứ để mặc định nó sẽ kỳ diệu làm việc như một trang trí trực tiếp. Điều này có nghĩa là @memoize(200) sẽ cho bạn một lỗi; bạn có thể tránh điều đó bằng cách thay vì kiểm tra kiểu nào đó để xem liệu func có thể gọi được hay không, cái nào cũng hoạt động tốt trong thực tế nhưng thực sự không phải là "pythonic".

Cách khác là có hai trang trí khác nhau, giả sử memoizebounded_memoize. Các memoize không bị chặn có thể có một triển khai tầm thường bằng cách chỉ cần gọi bounded_memoize với maxlen đặt thành None, do đó bạn không mất bất kỳ chi phí nào trong việc triển khai hoặc bảo trì.

Thông thường như quy tắc chung, tôi cố gắng tránh mangling một hàm để triển khai hai bộ chức năng liên quan duy nhất, đặc biệt là khi chúng có chữ ký khác nhau. Nhưng trong trường hợp này nó làm cho việc sử dụng của trang trí là tự nhiên (yêu cầu @memoize() sẽ dễ bị lỗi, mặc dù nó nhất quán hơn từ quan điểm lý thuyết), và bạn có thể sẽ triển khai thực hiện điều này một lần và sử dụng nó nhiều thời gian, do đó, khả năng đọc tại điểm sử dụng có lẽ là mối quan tâm quan trọng hơn.

-2

Từ http://www.python.org/dev/peps/pep-0318/

Cú pháp hiện hành cũng cho phép khai báo trang trí để gọi một hàm trả về một trang trí:

@decomaker(argA, argB, ...) 
def func(arg1, arg2, ...): 
    pass 

này tương đương với:

func = decomaker(argA, argB, ...)(func) 

Ngoài ra, Tôi không chắc liệu tôi có sử dụng OrderedDict cho việc này hay không, tôi sẽ sử dụng Ring Buffe r, chúng rất dễ thực hiện.

0

Bạn muốn viết một trang trí rằng có một đối số (độ dài tối đa của BoundedOrderedDict) và trả về một trang trí mà sẽ memoize chức năng của bạn với một BoundedOrderedDict kích thước thích hợp:

def boundedMemoize(maxCacheLen): 
    def memoize(function): 
     cache = BoundedOrderedDict(maxlen = maxCacheLen) 
     def memo_target(*args): 
      lookup_value = args 
      if lookup_value not in cache: 
       cache[lookup_value] = function(*args) 
      return cache[lookup_value] 
     return memo_target 
    return memoize 

Bạn có thể sử dụng nó như này:

@boundedMemoize(100) 
def fib(n): 
    if n < 2: return 1 
    return fib(n - 1) + fib(n - 2) 

Edit: Rất tiếc, bỏ lỡ một phần của câu hỏi. Nếu bạn muốn đối số maxlen cho trình trang trí là tùy chọn, bạn có thể làm như sau:

def boundedMemoize(arg): 
    if callable(arg): 
     cache = BoundedOrderedDict() 
     @functools.wraps(arg) 
     def memo_target(*args): 
      lookup_value = args 
      if lookup_value not in cache: 
       cache[lookup_value] = arg(*args) 
      return cache[lookup_value] 
     return memo_target 

    if isinstance(arg, int): 
     def memoize(function): 
      cache = BoundedOrderedDict(maxlen = arg) 
      @functools.wraps(function) 
      def memo_target(*args): 
       lookup_value = args 
       if lookup_value not in cache: 
        cache[lookup_value] = function(*args) 
       return cache[lookup_value] 
      return memo_target 
     return memoize 
+0

Không hoàn toàn. Trong câu hỏi tôi đã hỏi một cái gì đó có thể làm việc tương đương với và không có tham số. Tôi không tin rằng người này có thể làm điều đó. – agarrett

+0

Xin lỗi về điều đó, tôi đã chỉnh sửa câu trả lời của mình. – srgerg

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