Tôi mới bắt đầu Python và tôi không biết ghi nhớ là gì và cách sử dụng nó. Ngoài ra, tôi có thể có một ví dụ đơn giản?
Tôi mới bắt đầu Python và tôi không biết ghi nhớ là gì và cách sử dụng nó. Ngoài ra, tôi có thể có một ví dụ đơn giản?
Câu trả lời:
Ghi nhớ có hiệu quả liên quan đến việc ghi nhớ ("ghi nhớ" → "ghi nhớ" → được ghi nhớ) kết quả của các cuộc gọi phương thức dựa trên các đầu vào phương thức và sau đó trả về kết quả đã nhớ thay vì tính lại kết quả. Bạn có thể nghĩ về nó như một bộ đệm cho kết quả phương pháp. Để biết thêm chi tiết, xem trang 387 để biết định nghĩa trong Giới thiệu về thuật toán (3e), Cormen et al.
Một ví dụ đơn giản để tính toán các yếu tố sử dụng ghi nhớ trong Python sẽ giống như thế này:
factorial_memo = {}
def factorial(k):
if k < 2: return 1
if k not in factorial_memo:
factorial_memo[k] = k * factorial(k-1)
return factorial_memo[k]
Bạn có thể trở nên phức tạp hơn và gói gọn quá trình ghi nhớ vào một lớp:
class Memoize:
def __init__(self, f):
self.f = f
self.memo = {}
def __call__(self, *args):
if not args in self.memo:
self.memo[args] = self.f(*args)
#Warning: You may wish to do a deepcopy here if returning objects
return self.memo[args]
Sau đó:
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
factorial = Memoize(factorial)
Một tính năng được gọi là " trang trí " đã được thêm vào Python 2.4, cho phép bạn chỉ cần viết những điều sau đây để thực hiện điều tương tự:
@Memoize
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
Các Python Decorator Thư viện có trang trí tương tự gọi memoized
đó là hơi mạnh hơn so với các Memoize
lớp trình bày ở đây.
factorial_memo
, bởi vì factorial
bên trong def factorial
vẫn gọi là không cũ factorial
.
if k not in factorial_memo:
, đọc tốt hơn if not k in factorial_memo:
.
args
là một tuple. def some_function(*args)
làm cho args một tuple.
Mới dùng Python 3.2 functools.lru_cache
. Theo mặc định, nó chỉ lưu trữ 128 cuộc gọi gần đây nhất được sử dụng, nhưng bạn có thể thiết lập maxsize
để None
chỉ ra rằng bộ nhớ cache không bao giờ nên hết hạn:
import functools
@functools.lru_cache(maxsize=None)
def fib(num):
if num < 2:
return num
else:
return fib(num-1) + fib(num-2)
Chức năng này tự nó rất chậm, hãy thử fib(36)
và bạn sẽ phải chờ khoảng mười giây.
Thêm lru_cache
chú thích đảm bảo rằng nếu hàm được gọi gần đây cho một giá trị cụ thể, nó sẽ không tính toán lại giá trị đó, mà sử dụng kết quả được lưu trong bộ nhớ cache trước đó. Trong trường hợp này, nó dẫn đến một sự cải thiện tốc độ rất lớn, trong khi mã không bị lộn xộn với các chi tiết của bộ nhớ đệm.
fib
được gọi, nó sẽ cần phải lặp lại trường hợp cơ sở trước khi ghi nhớ có thể xảy ra. Vì vậy, hành vi của bạn chỉ là về dự kiến.
Các câu trả lời khác bao gồm những gì nó là khá tốt. Tôi không nhắc lại điều đó. Chỉ cần một số điểm có thể hữu ích cho bạn.
Thông thường, phân biệt là một hoạt động bạn có thể áp dụng trên bất kỳ chức năng nào tính toán một cái gì đó (đắt tiền) và trả về một giá trị. Bởi vì điều này, nó thường được thực hiện như một trang trí . Việc thực hiện rất đơn giản và nó sẽ giống như thế này
memoised_function = memoise(actual_function)
hoặc thể hiện như một người trang trí
@memoise
def actual_function(arg1, arg2):
#body
Ghi nhớ là giữ kết quả của các phép tính đắt tiền và trả về kết quả được lưu trong bộ nhớ cache thay vì liên tục tính toán lại.
Đây là một ví dụ:
def doSomeExpensiveCalculation(self, input):
if input not in self.cache:
<do expensive calculation>
self.cache[input] = result
return self.cache[input]
Một mô tả đầy đủ hơn có thể được tìm thấy trong mục wikipedia về ghi nhớ .
if input not in self.cache
và self.cache[input]
( has_key
đã lỗi thời kể từ ... đầu loạt 2.x, nếu không phải là 2.0. Không self.cache(index)
bao giờ đúng. IIRC)
Chúng ta đừng quên hasattr
chức năng tích hợp, dành cho những người muốn làm thủ công. Bằng cách đó, bạn có thể giữ bộ nhớ cache bên trong định nghĩa hàm (trái ngược với toàn cục).
def fact(n):
if not hasattr(fact, 'mem'):
fact.mem = {1: 1}
if not n in fact.mem:
fact.mem[n] = n * fact(n - 1)
return fact.mem[n]
Tôi thấy điều này cực kỳ hữu ích
def memoize(function):
from functools import wraps
memo = {}
@wraps(function)
def wrapper(*args):
if args in memo:
return memo[args]
else:
rv = function(*args)
memo[args] = rv
return rv
return wrapper
@memoize
def fibonacci(n):
if n < 2: return n
return fibonacci(n - 1) + fibonacci(n - 2)
fibonacci(25)
functools.wraps
.
memo
để bộ nhớ được giải phóng không?
Ghi nhớ về cơ bản là lưu kết quả của các hoạt động trong quá khứ được thực hiện với các thuật toán đệ quy để giảm nhu cầu đi qua cây đệ quy nếu tính toán tương tự được yêu cầu ở giai đoạn sau.
xem http://scriptbucket.wordpress.com/2012/12/11/int sinhtion-to-memoization /
Ví dụ về ghi nhớ Fibonacci trong Python:
fibcache = {}
def fib(num):
if num in fibcache:
return fibcache[num]
else:
fibcache[num] = num if num < 2 else fib(num-1) + fib(num-2)
return fibcache[num]
Ghi nhớ là việc chuyển đổi các chức năng thành cấu trúc dữ liệu. Thông thường, người ta muốn chuyển đổi xảy ra tăng dần và lười biếng (theo yêu cầu của một yếu tố miền nhất định - hoặc "khóa"). Trong các ngôn ngữ chức năng lười biếng, việc chuyển đổi lười biếng này có thể xảy ra tự động và do đó việc ghi nhớ có thể được thực hiện mà không có tác dụng phụ (rõ ràng).
Vâng, tôi nên trả lời phần đầu tiên: ghi nhớ những gì?
Nó chỉ là một phương pháp để trao đổi bộ nhớ theo thời gian. Hãy nghĩ về bảng nhân .
Sử dụng đối tượng có thể thay đổi làm giá trị mặc định trong Python thường được coi là xấu. Nhưng nếu sử dụng nó một cách khôn ngoan, nó thực sự có thể hữu ích để thực hiện a memoization
.
Dưới đây là một ví dụ được điều chỉnh từ http://docs.python.org/2/faq/design.html#why-are-default-values- Shared-b between-objects
Sử dụng một biến đổi dict
trong định nghĩa hàm, các kết quả tính toán trung gian có thể được lưu trữ (ví dụ: khi tính toán factorial(10)
sau khi tính toán factorial(9)
, chúng ta có thể sử dụng lại tất cả các kết quả trung gian)
def factorial(n, _cache={1:1}):
try:
return _cache[n]
except IndexError:
_cache[n] = factorial(n-1)*n
return _cache[n]
Đây là một giải pháp sẽ hoạt động với các đối số kiểu danh sách hoặc dict mà không rên rỉ:
def memoize(fn):
"""returns a memoized version of any function that can be called
with the same list of arguments.
Usage: foo = memoize(foo)"""
def handle_item(x):
if isinstance(x, dict):
return make_tuple(sorted(x.items()))
elif hasattr(x, '__iter__'):
return make_tuple(x)
else:
return x
def make_tuple(L):
return tuple(handle_item(x) for x in L)
def foo(*args, **kwargs):
items_cache = make_tuple(sorted(kwargs.items()))
args_cache = make_tuple(args)
if (args_cache, items_cache) not in foo.past_calls:
foo.past_calls[(args_cache, items_cache)] = fn(*args,**kwargs)
return foo.past_calls[(args_cache, items_cache)]
foo.past_calls = {}
foo.__name__ = 'memoized_' + fn.__name__
return foo
Lưu ý rằng cách tiếp cận này có thể được mở rộng một cách tự nhiên cho bất kỳ đối tượng nào bằng cách triển khai hàm băm của riêng bạn như một trường hợp đặc biệt trong handle_item. Ví dụ: để làm cho cách tiếp cận này hoạt động đối với một hàm lấy một tập hợp làm đối số đầu vào, bạn có thể thêm vào handle_item:
if is_instance(x, set):
return make_tuple(sorted(list(x)))
list
đối số [1, 2, 3]
có thể bị coi nhầm giống như một set
đối số khác với giá trị là {1, 2, 3}
. Ngoài ra, các bộ không có thứ tự như từ điển, vì vậy chúng cũng cần phải có sorted()
. Cũng lưu ý rằng một đối số cấu trúc dữ liệu đệ quy sẽ gây ra một vòng lặp vô hạn.
list
s và set
s bị "tupleized" vào cùng một thứ và do đó trở nên không thể phân biệt được với nhau. Mã ví dụ để thêm hỗ trợ sets
được mô tả trong bản cập nhật mới nhất của bạn không tránh khỏi điều đó tôi sợ. Điều này có thể dễ dàng được nhìn thấy bằng cách chuyển riêng [1,2,3]
và {1,2,3}
làm đối số cho chức năng kiểm tra "ghi nhớ" và xem liệu nó có được gọi hai lần hay không, có nên hay không.
list
s và dict
s vì nó có thể cho một list
để có chính xác những điều tương tự trong nó dẫn từ gọi make_tuple(sorted(x.items()))
cho một cuốn từ điển. Một giải pháp đơn giản cho cả hai trường hợp sẽ bao gồm type()
giá trị trong bộ dữ liệu được tạo. Tôi có thể nghĩ ra một cách thậm chí đơn giản hơn để xử lý set
s, nhưng nó không khái quát.
Giải pháp hoạt động với cả đối số vị trí và từ khóa độc lập theo thứ tự mà từ khóa đối số được thông qua (sử dụng tests.getargspec ):
import inspect
import functools
def memoize(fn):
cache = fn.cache = {}
@functools.wraps(fn)
def memoizer(*args, **kwargs):
kwargs.update(dict(zip(inspect.getargspec(fn).args, args)))
key = tuple(kwargs.get(k, None) for k in inspect.getargspec(fn).args)
if key not in cache:
cache[key] = fn(**kwargs)
return cache[key]
return memoizer
Câu hỏi tương tự: Xác định các hàm varargs tương đương gọi để ghi nhớ trong Python
cache = {}
def fib(n):
if n <= 1:
return n
else:
if n not in cache:
cache[n] = fib(n-1) + fib(n-2)
return cache[n]
if n not in cache
thay thế. bằng cách sử dụng cache.keys
sẽ xây dựng một danh sách không cần thiết trong python 2
Chỉ muốn thêm vào các câu trả lời đã được cung cấp, thư viện trang trí Python có một số triển khai đơn giản nhưng hữu ích cũng có thể ghi nhớ "các loại không thể xóa", không giống như functools.lru_cache
.