Decorators
Kỹ thuật thêm chức năng mới mà không thay đổi mã nguồn gốc và cách gọi hàm
Nguyên lý mở rộng-kín
Cho phép mở rộng
Không cho phép sửa đổi
Nguyên tắc decorator: Người dùng không nhận biết việc bổ sung chức năng
Thử nghiệm decorator
1.Hàm gốc
def ham_goc(): # Định nghĩa hàm
print('Giả lập mã nguồn!') # Nội dung hàm
return 123 # Giá trị trả về
ham_goc()
Giả lập mã nguồn!
2.Thêm chức năng mới
# Bổ sung chức năng mà không thay đổi hàm gốc và cách gọi
print('Chức năng mới!') # 1.Thêm chức năng mới
ham_goc() # 2.Gọi hàm gốc
print('Chức năng khác!') # 3.Thêm chức năng khác
Chức năng mới!
Giả lập mã nguồn!
Chức năng khác!
# Có thể chạy được nhưng sẽ lặp lại khi cần nhiều vị trí thêm chức năng
# Cần giải pháp tránh lặp lại
3.Giải pháp tái sử dụng
def them_tinh_nang(): # Đóng gói thành hàm để gọi ở bất kỳ đâu
print('Chức năng mới!')
ham_goc()
print('Chức năng khác!')
them_tinh_nang()
Chức năng mới!
Giả lập mã nguồn!
Chức năng khác!
# Giải quyết vấn đề lặp lại nhưng chưa linh hoạt
# Cần phương pháp linh hoạt hơn để áp dụng cho nhiều hàm
def ham_goc():
print('Giả lập mã nguồn!')
return 123
def ham_khac(): # 1.Hàm cần thêm chức năng
print('Hàm cần bổ sung!')
def them_tinh_nang(f): # 2.Thêm tham số hàm
print('Chức năng mới!')
f() # 4.Tham số f thay thế cho hàm gốc
print('Chức năng khác!')
# Có thể chạy, nhưng sẽ lặp lại khi cần nhiều vị trí thêm chức năng
# Đóng gói hàm có thể sử dụng ở bất kỳ đâu
them_tinh_nang(ham_goc) # 3.Gọi và truyền tham số
them_tinh_nang(ham_khac) # 3.1.Gọi và truyền tham số khác
Chức năng mới!
Giả lập mã nguồn!
Chức năng khác!
Chức năng mới!
Hàm cần bổ sung!
Chức năng khác!
# Giải quyết vấn đề linh hoạt nhưng cần thay đổi cách truyền tham số
# Cần giải pháp tối ưu hơn
def ham_goc():
print('Giả lập mã nguồn!')
return 123
def ham_khac():
print('Hàm cần bổ sung!')
def tao_decorator(): # 1.Hàm đóng gói
a = ham_goc # 2.Gán hàm gốc vào biến a
def them_tinh_nang(): # 3.Bỏ tham số
print('Chức năng mới!')
a() # 4.Điều hướng đến hàm gốc
print('Chức năng khác!')
return them_tinh_nang # 5.Trả về hàm mới
ket_qua = tao_decorator() # 6.Gán kết quả
ket_qua() # 7.Gọi hàm mới
# Thay đổi cách truyền tham số, sử dụng hàm đóng gói
def ham_goc():
print('Giả lập mã nguồn!')
return 123
def ham_khac():
print('Hàm cần bổ sung!')
def tao_decorator(f): # 1.Để linh hoạt, thêm tham số
def them_tinh_nang():
print('Chức năng mới!')
f() # 2.f là tham số truyền vào
print('Chức năng khác!')
return them_tinh_nang
ham_goc = tao_decorator(ham_goc) #3.Khóa quan trọng
ham_goc()
# Không sửa đổi mã gốc, không thay đổi cách truyền tham số
Nhưng muốn thêm cho hàm khác cần gán lại
ham_khac = tao_decorator(ham_khac)
ham_khac()
# Có cách nào đơn giản hơn không, hoặc hỗ trợ hàm có tham số?
'''
Cốt lõi: Khi chạy ham_goc=tao_decorator(ham_goc), tao_decorator() sẽ chạy trước, tham số f nhận địa chỉ bộ nhớ của ham_goc. Hàm tao_decorator trả về them_tinh_nang và gán cho ham_goc. Khi gọi ham_goc(), thực chất đang gọi them_tinh_nang. Trong them_tinh_nang, f vẫn trỏ đến ham_goc ban đầu.
'''
Decorator phức tạp
def ham_goc(): # 1.Hàm gốc cần tham số
print('Giả lập mã nguồn!')
return 123
def ham_khac(b):
print('Hàm cần bổ sung!')
def tao_decorator(f):
def them_tinh_nang(*args,**kwargs): # 1.Hỗ trợ mọi loại tham số
print('Chức năng mới!')
f(*args,**kwargs) # 2.Xử lý mọi tham số
print('Chức năng khác!')
return them_tinh_nang
ham_goc = tao_decorator(ham_goc)
ham_goc()
ham_khac = tao_decorator(ham_khac)
ham_khac(1) # 3.Hàm cần tham số, truyền khi gọi
# Hoàn toàn có thể thêm chức năng cho hàm không có/kèm tham số
Nhưng chưa xử lý giá trị trả về
Xử lý giá trị trả về
def ham_goc():
print('Giả lập mã nguồn!')
return 123
def ham_khac(b):
print('Hàm cần bổ sung!')
return 456
def tao_decorator(f):
def them_tinh_nang(*args,**kwargs):
print('Chức năng mới!')
kq = f(*args,**kwargs) # 1.Lưu trữ giá trị trả về
print('Chức năng khác!')
return kq #2.Trả về giá trị
return them_tinh_nang
ham_goc = tao_decorator(ham_goc)
gt1=ham_goc() # 3.Nhận giá trị trả về
ham_khac = tao_decorator(ham_khac)
gt2=ham_khac(1) # 3.1.Nhận giá trị trả về
print(gt1) #In ra
print(gt2) #In ra
Xác thực đăng nhập
def hien_thi_1():
print('Nội dung 111')
def hien_thi_2():
print('Nội dung 222')
def kiem_tra_dang_nhap(f): #Tham số không được thay đổi
def thuc_hien(*args,**kwargs): #Tham số không được thay đổi
tk = input('Tài khoản').strip()
mk = input('Mật khẩu').strip()
if tk == 'x'and mk == '123':
kq = f(*args,**kwargs)
return kq
else:
print('Lỗi thông tin!')
return
return thuc_hien
hien_thi_1 = kiem_tra_dang_nhap(hien_thi_1)
kq=hien_thi_1()
print()
Xác thực đăng nhập một lần
def hien_thi_1():
print('Nội dung 111')
def hien_thi_2():
print('Nội dung 222')
trang_thai = {'1':False} #Tương tự từ điển trạng thái
def kiem_tra_dang_nhap(f):
def thuc_hien(*args,**kwargs):
if trang_thai.get('1'): #Kiểm tra trạng thái
kq = f(*args, **kwargs)
return kq
tk = input('Tài khoản').strip()
mk = input('Mật khẩu').strip()
if tk == 'x'and mk == '123':
kq = f(*args,**kwargs)
trang_thai['1'] = True #Cập nhật trạng thái
return kq
else:
print('Lỗi thông tin!')
return thuc_hien
hien_thi_1 = kiem_tra_dang_nhap(hien_thi_1)
kq=hien_thi_1()
print()
Mẫu decorator
def bo_quan_ly(func):
def chuc_nang(*args,**kwargs):
print('Thêm chức năng trước khi chạy')
kq = func(*args,**kwargs) #Thực thi hàm gốc
print('Thêm chức năng sau khi chạy')
return kq #Trả về giá trị gốc
return chuc_nang
def hien_thi (*args,**kwargs)
print('Đây là cú pháp đường dẫn')
hien_thi = bo_quan_ly(hien_thi)
Cú pháp đường dẫn
def bo_quan_ly(func):
def chuc_nang(*args,**kwargs):
print('Thêm chức năng trước khi chạy')
kq = func(*args,**kwargs) #Thực thi hàm gốc
print('Thêm chức năng sau khi chạy')
return kq #Trả về giá trị gốc
return chuc_nang
def hien_thi (*args,**kwargs)
print('Đây là cú pháp đường dẫn')
hien_thi = bo_quan_ly(hien_thi)
@bo_quan_ly # hien_thi = bo_quan_ly(hien_thi) tương đương
Nguyên tắc viết cú pháp đường dẫn
Phải đặt ngay trên hàm cần quản lý
Nguyên lý hoạt động
Tự động truyền tên hàm dưới lên làm tham số
Cú pháp đường dẫn kép
import time
def thoi_gian_chay(func):
def chuc_nang(*args, **kwargs):
start_time = time.time()
kq = func(*args, **kwargs) #Thực thi hàm gốc
end_time = time.time()
print('Thời gian chạy:%s'%(end_time-start_time))
return kq #Trả về giá trị
return chuc_nang
#Xác thực đăng nhập
def kiem_tra(func):
def chuc_nang(*args, **kwargs):
# 1.Nhập thông tin
username = input('username>>>:').strip()
password = input('password>>>:').strip()
# 2.Xác thực
if username == 'jason' and password == '123':
kq = func(*args, **kwargs) #Thực thi hàm gốc
return kq #Trả về giá trị
print('Không có quyền')
return chuc_nang
@kiem_tra
@thoi_gian_chay
def hien_thi():
time.sleep(1)
print('from index')
hien_thi()
'''
Thực thi lớp gần nhất trước, kết quả sẽ được lớp kế tiếp sử dụng
'''
Kỹ thuật phục hồi decorator
from functools import wraps
def bo_quan_ly(func):
@wraps(func) #Phục hồi để che giấu decorator
def chuc_nang(*args, **kwargs):
print('Thêm chức năng trước khi chạy')
kq = func(*args, **kwargs) #Thực thi hàm gốc
print('Thêm chức năng sau khi chạy')
return kq #Trả về giá trị
return chuc_nang
@bo_quan_ly # hien_thi = bo_quan_ly(hien_thi)
def hien_thi():
print('from index')
print(hien_thi)
help(hien_thi)
def trang_chu():
"""Hàm trang chủ"""
print('from home')
# help(hien_thi)
# help(trang_chu)
# print(hien_thi)
# help(len)
help() #Xem thông tin hàm
from functools import wraps
def ten_ham():
@wraps(ten_ham)
'''Sử dụng như vậy, không cần hỏi lý do'''
Bài tập
def cap_nhat_1(func1):
print('Đã tải cap_nhat_1')
def bao_quanh_1(*args, **kwargs):
print('Đã chạy bao_quanh_1')
kq1 = func1(*args, **kwargs)
return kq1
return bao_quanh_1
def cap_nhat_2(func2):
print('Đã tải cap_nhat_2')
def bao_quanh_2(*args, **kwargs):
print('Đã chạy bao_quanh_2')
kq2 = func2(*args, **kwargs)
return kq2
return bao_quanh_2
def cap_nhat_3(func3):
print('Đã tải cap_nhat_3')
def bao_quanh_3(*args, **kwargs):
print('Đã chạy bao_quanh_3')
kq3 = func3(*args, **kwargs)
return kq3
return bao_quanh_3
@cap_nhat_1
@cap_nhat_2
@cap_nhat_3
def hien_thi():
print('from index')
Thứ tự in 7 dòng
Decorator có tham số
def bo_quan_ly(dia_chi):
# dia_chi = 'file'
def kiem_tra(func):
def xac_thuc(*args,**kwargs):
# 2.Xác thực thông tin
# Có thể chuyển đổi nhiều cách xác thực
if dia_chi == 'file':
# Lấy dữ liệu từ file
print('Lấy từ file')
elif dia_chi == 'MySQL':
# Lấy dữ liệu từ MySQL
print('Lấy từ MySQL')
elif dia_chi == 'postgreSQL':
# Lấy dữ liệu từ postgreSQL
print('Lấy từ postgreSQL')
else:
print('Không thể thực thi')
return xac_thuc
return kiem_tra
@bo_quan_ly('file')
def hien_thi():
print('from index')
@bo_quan_ly('MySQL')
def trang_chu():
print('from home')
hien_thi()
trang_chu()
#Decorator có tham số, thêm lớp bọc ngoài cùng