Ghi nhớ con trăn trang trí

Để lưu trữ một hàm không liên kết đơn giản, chỉ cần đưa thẻ @memorise[] vào định nghĩa hàm [cần có dấu ngoặc đơn vì trình trang trí cần được khởi tạo tại thời điểm liên kết để xử lý các đối số cụ thể của ghi nhớ]

from memorised.decorators import memorise

@memorise[]
def myfunction[]:
    return 'hello world'

Bạn có thể làm tương tự đối với các phương thức lớp và thể hiện đơn giản, tuy nhiên đối với hầu hết các phương thức thể hiện, e. g. khi lưu kết quả vào bộ nhớ đệm cho các mô hình cơ sở dữ liệu, bạn có thể muốn bao gồm một số dạng nhận dạng để chọn ra một lệnh gọi phương thức trên một phiên bản từ một phiên bản khác. Bạn có thể làm điều này bằng cách cung cấp danh sách một hoặc nhiều khóa cha khác, đây là tên của các thuộc tính trong phiên bản cha mà bạn muốn được thêm vào khóa memcache

Đệ quy cung cấp cho các lập trình viên một cách thuận tiện để chia các vấn đề lớn hơn thành các phần có thể quản lý được. Xem xét các giải pháp lặp và đệ quy cho một tổng số fibonacci

# iterative
def fib_iterative[n]:
if [n == 0]:
return 0
elif [n == 1]:
return 1
elif [n >1 ]:
fn = 0
fn1 = 1
fn2 = 2
for i in range[3, n]:
fn = fn1+fn2
fn1 = fn2
fn2 = fn
return fn
# recursive
def fib[n]:
if n == 0:return 0
if n == 1:return 1
else: return fib[n-1] + fib[n-2]

Các giải pháp đệ quy thường dễ đọc và dễ viết hơn cho các vấn đề phân nhánh. Duyệt cây, duyệt đồ thị và chuỗi toán học [thường] được xử lý trực quan hơn bằng cách sử dụng đệ quy

Mặc dù nó mang lại sự thuận tiện, chi phí thời gian tính toán của đệ quy đối với các vấn đề phân nhánh tăng theo cấp số nhân với các giá trị lớn hơn của n

Hãy xem ngăn xếp cuộc gọi cho fib[6]

Chúng tôi thực hiện gần gấp đôi số thao tác ở mỗi cấp độ kế tiếp của cây. Cho chúng ta độ phức tạp thời gian O[2^n]

Nếu chúng ta nhìn kỹ hơn vào cái cây của mình, bạn sẽ nhận thấy rằng chúng ta đang lặp lại công việc. fib[2] được tính năm lần, fib[3] được tính ba lần, v.v. Mặc dù đây không phải là vấn đề đối với các giá trị nhỏ của n, hãy tưởng tượng khối lượng công việc lặp đi lặp lại khi tính toán fib[1000]. Khi chúng tôi sửa lại giải pháp đệ quy của mình, hãy thử chạy cùng một vấn đề [giả sử là 20] cho cả hai phiên bản và nhận thấy sự khác biệt đáng chú ý trong thời gian hoàn thành

Chắc chắn có một cách tốt để ngăn chặn công việc lặp lại và giữ giải pháp tao nhã của chúng tôi?

ghi nhớ. Bạn có thể có Bánh của bạn và Ăn nó nữa

Với tính năng ghi nhớ, chúng ta có thể “ghi nhớ” [nhớ, lưu trữ] kết quả của các bài toán mà chúng ta đã xử lý trước đó và trả về kết quả được lưu trữ thay vì tính toán lặp lại

Ví dụ: một khi fib[2] được tính toán, chúng ta có thể lưu trữ kết quả và sử dụng kết quả đó trong bốn lần gặp fib[2] tiếp theo

Điều này làm giảm thời gian chạy của chúng ta từ O[2^n] xuống O[n] vì chúng ta chỉ tính toán từng sợi [0]… sợi [n] một lần

Một cách để thực hiện điều này là sử dụng từ điển

__fib_cache = {}def fib_memo[n]:
if n in __fib_cache:
return __fib_cache[n]

else:
__fib_cache[n] = n if n < 2 else fib_memo[n-2] + fib_memo[n-1]
return __fib_cache[n]

Và Voila. Chúng tôi đã ghi nhớ thành công một hàm đệ quy

Tuy nhiên, chúng ta có thể tiến xa hơn một bước và ghi nhớ trừu tượng cho bất kỳ hàm đệ quy nào

người trang trí. Chức năng sử dụng chức năng để thực hiện chức năng khác

Decorators được gọi là “Higher order functions”. các hàm lấy các hàm khác làm tham số. Ở đây chúng tôi sẽ sử dụng một trình trang trí để khái quát hóa việc ghi nhớ

Đây là một giải pháp trang trí

def memoize[func]:
cache = func.cache = {}
@functools.wraps[func]
def memoized_func[*args, **kwargs]:
key = str[args] + str[kwargs]
if key not in cache:
cache[key] = func[*args, **kwargs]
return cache[key]
return memoized_func@memoize
def fibonacci[n]:
if n == 0:return 0
if n == 1:return 1
else: return fib[n-1] + fib[n-2]

Hãy chia nhỏ nó xuống từng dòng

Trình trang trí của chúng tôi là một hàm nhận một hàm khác làm đối số, chúng tôi khai báo nó như vậy trên dòng 1

Tiếp theo, chúng tôi khai báo một từ điển func.cache = {}, được khai báo là thuộc tính củafunc. Ở đây chúng tôi sẽ lưu trữ từng cuộc gọi duy nhất được thực hiện bởi func.

Chúng tôi sẽ quay lại @functools.wraps

def memoized_func[*args, **kwargs]: — Bên trong bộ trang trí, chúng tôi xác định một chức năng mới, được ghi nhớ dựa trên func mà chúng tôi đã truyền vào và nó nhận các tham số tương tự là func. Chúng tôi gọi đây là phiên bản mới

__fib_cache = {}def fib_memo[n]:
if n in __fib_cache:
return __fib_cache[n]

else:
__fib_cache[n] = n if n < 2 else fib_memo[n-2] + fib_memo[n-1]
return __fib_cache[n]
0

__fib_cache = {}def fib_memo[n]:
if n in __fib_cache:
return __fib_cache[n]

else:
__fib_cache[n] = n if n < 2 else fib_memo[n-2] + fib_memo[n-1]
return __fib_cache[n]
1 là một từ khóa chỉ ra rằng
__fib_cache = {}def fib_memo[n]:
if n in __fib_cache:
return __fib_cache[n]

else:
__fib_cache[n] = n if n < 2 else fib_memo[n-2] + fib_memo[n-1]
return __fib_cache[n]
2 nhận một số đối số tùy ý e. g.
__fib_cache = {}def fib_memo[n]:
if n in __fib_cache:
return __fib_cache[n]

else:
__fib_cache[n] = n if n < 2 else fib_memo[n-2] + fib_memo[n-1]
return __fib_cache[n]
3 hoặc
__fib_cache = {}def fib_memo[n]:
if n in __fib_cache:
return __fib_cache[n]

else:
__fib_cache[n] = n if n < 2 else fib_memo[n-2] + fib_memo[n-1]
return __fib_cache[n]
4 đều hợp lệ. Tương tự,
__fib_cache = {}def fib_memo[n]:
if n in __fib_cache:
return __fib_cache[n]

else:
__fib_cache[n] = n if n < 2 else fib_memo[n-2] + fib_memo[n-1]
return __fib_cache[n]
5 đại diện cho một số lượng đối số từ khóa tùy ý [các tham số được xác định tại lệnh gọi hàm] e. g.
__fib_cache = {}def fib_memo[n]:
if n in __fib_cache:
return __fib_cache[n]

else:
__fib_cache[n] = n if n < 2 else fib_memo[n-2] + fib_memo[n-1]
return __fib_cache[n]
6. Ghi chú. Bạn có thể sử dụng các tham số chuyên dụng này trong bất kỳ chức năng nào

Dòng sau khai báo một

__fib_cache = {}def fib_memo[n]:
if n in __fib_cache:
return __fib_cache[n]

else:
__fib_cache[n] = n if n < 2 else fib_memo[n-2] + fib_memo[n-1]
return __fib_cache[n]
7 cho
__fib_cache = {}def fib_memo[n]:
if n in __fib_cache:
return __fib_cache[n]

else:
__fib_cache[n] = n if n < 2 else fib_memo[n-2] + fib_memo[n-1]
return __fib_cache[n]
8 của chúng ta dựa trên các tham số đã cho. Nếu chúng tôi đã thấy sự kết hợp các tham số này trước đây, chúng tôi sẽ lấy kết quả từ
__fib_cache = {}def fib_memo[n]:
if n in __fib_cache:
return __fib_cache[n]

else:
__fib_cache[n] = n if n < 2 else fib_memo[n-2] + fib_memo[n-1]
return __fib_cache[n]
8 , nếu không, hãy tạo một khóa mới bằng cách sử dụng func ban đầu như đã được xác định [e. g. sử dụng
def memoize[func]:
cache = func.cache = {}
@functools.wraps[func]
def memoized_func[*args, **kwargs]:
key = str[args] + str[kwargs]
if key not in cache:
cache[key] = func[*args, **kwargs]
return cache[key]
return memoized_func@memoize
def fibonacci[n]:
if n == 0:return 0
if n == 1:return 1
else: return fib[n-1] + fib[n-2]
1] và gán giá trị của nó cho kết quả e. g. [ lưu trữ kết quả của
def memoize[func]:
cache = func.cache = {}
@functools.wraps[func]
def memoized_func[*args, **kwargs]:
key = str[args] + str[kwargs]
if key not in cache:
cache[key] = func[*args, **kwargs]
return cache[key]
return memoized_func@memoize
def fibonacci[n]:
if n == 0:return 0
if n == 1:return 1
else: return fib[n-1] + fib[n-2]
2]

Dòng cuối cùng bên trong

def memoize[func]:
cache = func.cache = {}
@functools.wraps[func]
def memoized_func[*args, **kwargs]:
key = str[args] + str[kwargs]
if key not in cache:
cache[key] = func[*args, **kwargs]
return cache[key]
return memoized_func@memoize
def fibonacci[n]:
if n == 0:return 0
if n == 1:return 1
else: return fib[n-1] + fib[n-2]
3 trả về kết quả của một lần gọi tới func với các tham số đã cho

Dòng cuối cùng trả về

def memoize[func]:
cache = func.cache = {}
@functools.wraps[func]
def memoized_func[*args, **kwargs]:
key = str[args] + str[kwargs]
if key not in cache:
cache[key] = func[*args, **kwargs]
return cache[key]
return memoized_func@memoize
def fibonacci[n]:
if n == 0:return 0
if n == 1:return 1
else: return fib[n-1] + fib[n-2]
3, đại diện cho một phiên bản đã sửa đổi của hàm ban đầu của chúng tôi với các tính năng [được trang trí] bổ sung, nghĩa là lưu trữ kết quả của mỗi lệnh gọi mới

Cuối cùng, chúng tôi đánh dấu chức năng ban đầu của mình bằng

def memoize[func]:
cache = func.cache = {}
@functools.wraps[func]
def memoized_func[*args, **kwargs]:
key = str[args] + str[kwargs]
if key not in cache:
cache[key] = func[*args, **kwargs]
return cache[key]
return memoized_func@memoize
def fibonacci[n]:
if n == 0:return 0
if n == 1:return 1
else: return fib[n-1] + fib[n-2]
6. Ký hiệu '@' yêu cầu trình biên dịch chuyển hàm ngay bên dưới nó thành một hàm bậc cao hơn có tên là
__fib_cache = {}def fib_memo[n]:
if n in __fib_cache:
return __fib_cache[n]

else:
__fib_cache[n] = n if n < 2 else fib_memo[n-2] + fib_memo[n-1]
return __fib_cache[n]
0 — trong trường hợp của chúng tôi, hàm này trả về một phiên bản được ghi nhớ của
def memoize[func]:
cache = func.cache = {}
@functools.wraps[func]
def memoized_func[*args, **kwargs]:
key = str[args] + str[kwargs]
if key not in cache:
cache[key] = func[*args, **kwargs]
return cache[key]
return memoized_func@memoize
def fibonacci[n]:
if n == 0:return 0
if n == 1:return 1
else: return fib[n-1] + fib[n-2]
8. Phiên bản này của hàm sẽ ngay lập tức trả về kết quả mà chúng ta đã tính toán trước đó, nếu không, nó sẽ gọi hàm
def memoize[func]:
cache = func.cache = {}
@functools.wraps[func]
def memoized_func[*args, **kwargs]:
key = str[args] + str[kwargs]
if key not in cache:
cache[key] = func[*args, **kwargs]
return cache[key]
return memoized_func@memoize
def fibonacci[n]:
if n == 0:return 0
if n == 1:return 1
else: return fib[n-1] + fib[n-2]
9 ban đầu để xác định số đó sẽ là bao nhiêu

@functools là gì. kết thúc tốt đẹp?

@functools.wraps là một trình trang trí khác được tích hợp sẵn trong python. Nó cho phép decoratorfunc.cache = {}1 lưu trữ thông tin liên quan đến chuỗi tài liệu của chức năng đã ghi nhớ hoặc tên chức năng để có thể truy cập thông tin sau này. Nếu chúng tôi hỏi python func.cache = {}2 hoặc func.cache = {}3 [đây là các thuộc tính được tích hợp sẵn cho các hàm], mà không có @functools.wraps, chúng tôi sẽ trả về tên và chuỗi tài liệu cho func.cache = {}5 chứ không phải chính

def memoize[func]:
cache = func.cache = {}
@functools.wraps[func]
def memoized_func[*args, **kwargs]:
key = str[args] + str[kwargs]
if key not in cache:
cache[key] = func[*args, **kwargs]
return cache[key]
return memoized_func@memoize
def fibonacci[n]:
if n == 0:return 0
if n == 1:return 1
else: return fib[n-1] + fib[n-2]
1. Đối với mục đích gỡ lỗi, thật hữu ích khi có quyền truy cập vào tên và chuỗi tài liệu của chức năng ban đầu được gọi, không phải chức năng trang trí, vì cùng một công cụ trang trí có thể được áp dụng cho vô số chức năng

Hãy tự mình thử

Lần tới khi bạn gặp một hàm đệ quy, hãy nghĩ xem liệu bạn có thể cải thiện hiệu suất của nó bằng tính năng ghi nhớ hay không, sau đó trừu tượng hóa tính năng ghi nhớ đó cho các hàm khác bằng cách thêm một trình trang trí

Ghi nhớ trong Python là gì?

Ghi nhớ là kỹ thuật tối ưu hóa phần mềm hiệu quả được sử dụng để tăng tốc chương trình . Nó cho phép bạn tối ưu hóa chức năng python bằng cách bắt đầu ra của nó dựa trên các tham số đầu vào được cung cấp. Ghi nhớ đảm bảo rằng một phương thức chỉ chạy cho cùng một đầu vào một lần.

Sự khác biệt giữa ghi nhớ và ghi nhớ là gì?

Ghi nhớ là một kỹ thuật tối ưu hóa được sử dụng chủ yếu để tăng tốc các chương trình máy tính bằng cách lưu trữ kết quả của các lệnh gọi hàm đắt tiền và trả về kết quả được lưu trong bộ nhớ cache khi các đầu vào tương tự xảy ra lần nữa. Ghi nhớ hiểu một cách đơn giản là ghi nhớ hoặc lưu trữ trong bộ nhớ .

Ghi nhớ trong lập trình động là gì?

Ghi nhớ là một kỹ thuật để cải thiện hiệu suất của các thuật toán đệ quy . Nó liên quan đến việc viết lại thuật toán đệ quy để câu trả lời cho các vấn đề được tìm thấy, chúng được lưu trữ trong một mảng. Các cuộc gọi đệ quy có thể tra cứu kết quả trong mảng thay vì phải tính toán lại chúng.

Sự khác biệt giữa phương pháp ghi nhớ và lập bảng là gì?

Nhanh vì kết quả của các bài toán con đã giải quyết trước đó có thể được truy cập trực tiếp từ bảng. Nó chậm vì cần phải thực hiện nhiều lệnh gọi đệ quy và trả về. Trong phiên bản lập bảng, tất cả các mục phải được điền từng mục một. Trong phiên bản ghi nhớ, các mục trong bảng được điền theo yêu cầu .

Chủ Đề