Các Khái Niệm Nền Tảng Python: Hàm Nặc Danh, Biểu Thức Chính Quy, Module và Decorator

Bài viết này khám phá một số khái niệm cơ bản nhưng quan trọng trong Python, từ các hàm nặc danh linh hoạt đến sức mạnh của biểu thức chính quy, cách thức quản lý module, và cuối cùng là decorator - một công cụ mạnh mẽ để mở rộng chức năng hàm.

Hàm Nặc Danh (Lambda)

Hàm nặc danh trong Python là những hàm không được khai báo theo cách tiêu chuẩn bằng từ khóa def. Chúng được định nghĩa bằng từ khóa lambda và có cú pháp đơn giản:

lambda [tham_so_1, [tham_so_2, ...]]: bieu_thuc

Một hàm lambda có thể có nhiều tham số nhưng chỉ chứa một biểu thức. Giá trị của biểu thức này sẽ là giá trị trả về của hàm.

# Định nghĩa một hàm lambda để tính tổng hai số
tinh_tong = lambda so_a, so_b: so_a + so_b

# Kiểm tra kiểu của 'tinh_tong'
print(type(tinh_tong))
# <class 'function'>

# Gọi hàm lambda và in kết quả
ket_qua = tinh_tong(5, 7)
print(f"Tổng của 5 và 7 là: {ket_qua}")
# Tổng của 5 và 7 là: 12

Biểu Thức Chính Quy (Regular Expressions)

Module re trong Python cung cấp các công cụ để làm việc với biểu thức chính quy. Hai phương thức phổ biến là re.search()re.match().

  • re.search(pattern, string): Quét toàn bộ chuỗi để tìm kiếm sự khớp với mẫu. Nó trả về đối tượng khớp đầu tiên tìm thấy ở bất cứ đâu trong chuỗi.
  • re.match(pattern, string): Chỉ kiểm tra sự khớp ở đầu chuỗi. Nếu chuỗi không bắt đầu bằng mẫu, nó sẽ trả về None.
import re

# Sử dụng re.search()
chuoi_tim_kiem = "xin_chao_123_the_gioi"
mau_tim_kiem = r"123"
ket_qua_search = re.search(mau_tim_kiem, chuoi_tim_kiem)
print(f"Kết quả search cho '{mau_tim_kiem}' trong '{chuoi_tim_kiem}': {ket_qua_search}")
# Kết quả search cho '123' trong 'xin_chao_123_the_gioi': <_sre.SRE_Match object; span=(8, 11), match='123'>

mau_tim_kiem_ky_tu_bat_ky = r"ch.o" # . khớp với bất kỳ ký tự nào ngoại trừ xuống dòng
ket_qua_search_2 = re.search(mau_tim_kiem_ky_tu_bat_ky, chuoi_tim_kiem)
print(f"Kết quả search cho '{mau_tim_kiem_ky_tu_bat_ky}' trong '{chuoi_tim_kiem}': {ket_qua_search_2}")
# Kết quả search cho 'ch.o' trong 'xin_chao_123_the_gioi': <_sre.SRE_Match object; span=(4, 8), match='chao'>

# Sử dụng re.match()
chuoi_match = "batdauvoiABCvaDau"
mau_match_ok = r"[a-z]{6}ABC" # Khớp 6 ký tự chữ thường và "ABC"
ket_qua_match_ok = re.match(mau_match_ok, chuoi_match)
print(f"Kết quả match cho '{mau_match_ok}' trong '{chuoi_match}': {ket_qua_match_ok}")
# Kết quả match cho '[a-z]{6}ABC' trong 'batdauvoiABCvaDau': <_sre.SRE_Match object; span=(0, 9), match='batdauvABC'>

mau_match_fail = r"XYZ"
ket_qua_match_fail = re.match(mau_match_fail, chuoi_match)
print(f"Kết quả match cho '{mau_match_fail}' trong '{chuoi_match}': {ket_qua_match_fail}")
# Kết quả match cho 'XYZ' trong 'batdauvoiABCvaDau': None

Trích xuất nội dung từ Match

Để trích xuất các phần cụ thể của một chuỗi khớp, chúng ta sử dụng dấu ngoặc đơn () trong mẫu biểu thức chính quy. Sau khi có đối tượng khớp (SRE_Match object), chúng ta có thể sử dụng các phương thức sau:

  • .groups(): Trả về một tuple chứa tất cả các nhóm đã khớp.
  • .group(0): Trả về toàn bộ chuỗi khớp.
  • .group(1): Trả về chuỗi khớp với nhóm đầu tiên (trong dấu ngoặc đơn).
  • .group(N): Trả về chuỗi khớp với nhóm thứ N.
import re

chuoi = "user123_admin_id456"
mau = r"user(\d+)_([a-zA-Z]+)_id(\d+)" # (\d+) là nhóm 1, ([a-zA-Z]+) là nhóm 2, (\d+) là nhóm 3

ket_qua = re.match(mau, chuoi)

if ket_qua:
    print(f"Toàn bộ chuỗi khớp (group 0): {ket_qua.group(0)}")
    # Toàn bộ chuỗi khớp (group 0): user123_admin_id456

    print(f"Nhóm 1 (ID người dùng): {ket_qua.group(1)}")
    # Nhóm 1 (ID người dùng): 123

    print(f"Nhóm 2 (Vai trò): {ket_qua.group(2)}")
    # Nhóm 2 (Vai trò): admin

    print(f"Nhóm 3 (ID): {ket_qua.group(3)}")
    # Nhóm 3 (ID): 456

    print(f"Tất cả các nhóm: {ket_qua.groups()}")
    # Tất cả các nhóm: ('123', 'admin', '456')
else:
    print("Không tìm thấy sự khớp.")

Quản lý Module

Module cho phép bạn tổ chức code Python thành các file riêng biệt, dễ tái sử dụng và quản lý. Cú pháp nhập module cơ bản là:

import ten_module
from ten_module import ten_doi_tuong
from ten_module import ten_doi_tuong as biet_danh

Thứ tự tìm kiếm Module

Khi bạn nhập một module, Python sẽ tìm kiếm nó theo một thứ tự cụ thể:

  1. Thư mục hiện tại (nơi script đang chạy).
  2. Các thư mục trong biến môi trường PYTHONPATH.
  3. Các thư mục cài đặt chuẩn của Python.

Bạn có thể kiểm tra đường dẫn tìm kiếm module bằng cách xem biến sys.path:

import sys

print(sys.path)
# ['','/usr/lib64/python36.zip','/usr/lib64/python3.6', ...]

Phần tử đầu tiên trong sys.path (thường là một chuỗi rỗng '') đại diện cho thư mục hiện hành, nơi Python sẽ tìm kiếm module trước tiên.

Các phương pháp Import và Bí danh

from math import sqrt # Chỉ nhập hàm sqrt từ module math

gia_tri = sqrt(25)
print(f"Căn bậc hai của 25 là: {gia_tri}")
# Căn bậc hai của 25 là: 5.0

from random import randint as so_nguyen_ngau_nhien # Nhập hàm randint với bí danh

so_ngau_nhien = so_nguyen_ngau_nhien(1, 100)
print(f"Số nguyên ngẫu nhiên từ 1 đến 100: {so_ngau_nhien}")
# Số nguyên ngẫu nhiên từ 1 đến 100: (một số ngẫu nhiên)

Decorator

Decorator trong Python là một mẫu thiết kế cho phép bạn thêm chức năng vào các hàm hoặc lớp hiện có mà không cần sửa đổi cấu trúc của chúng. Về bản chất, một decorator là một hàm nhận một hàm khác làm đối số, thực hiện một số thao tác với hàm đó và trả về một hàm mới (hoặc cùng một hàm đã được sửa đổi).

  • Một decorator bản chất là một hàm Python.
  • Giá trị trả về của một decorator cũng là một đối tượng hàm.
  • Chúng cho phép mở rộng tính năng của hàm mà không thay đổi mã nguồn gốc.

Decorator Đơn giản

Đây là một ví dụ về decorator đơn giản, thêm một thông báo gỡ lỗi trước khi hàm được gọi:

def ghi_log_thuc_thi(func_goc):
    def ham_bao_boc():
        print(f"[LOG]: Bắt đầu thực thi hàm '{func_goc.__name__}()'")
        return func_goc()
    return ham_bao_boc

def chao_buoi_sang():
    print("Chào buổi sáng, thế giới!")

# Áp dụng decorator theo cách thủ công
chao_buoi_sang = ghi_log_thuc_thi(chao_buoi_sang)

chao_buoi_sang()
# [LOG]: Bắt đầu thực thi hàm 'chao_buoi_sang()'
# Chào buổi sáng, thế giới!

Cú pháp "Syntactic Sugar" của Decorator

Python cung cấp một cú pháp tiện lợi hơn để áp dụng decorator sử dụng ký hiệu @.

def ghi_log_thuc_thi(func_goc):
    def ham_bao_boc():
        print(f"[LOG]: Bắt đầu thực thi hàm '{func_goc.__name__}()'")
        return func_goc()
    return ham_bao_boc

@ghi_log_thuc_thi # Áp dụng decorator bằng cú pháp @
def chao_buoi_chieu():
    print("Chào buổi chiều, Python!")

chao_buoi_chieu()
# [LOG]: Bắt đầu thực thi hàm 'chao_buoi_chieu()'
# Chào buổi chiều, Python!

Decorator với Tham số cho Hàm được Decorate

Để decorator có thể làm việc với các hàm có tham số (bất kể số lượng hay loại tham số), chúng ta sử dụng *args**kwargs trong hàm bao bọc (wrapper function).

def ghi_log_voi_tham_so(func_goc):
    def ham_bao_boc(*args, **kwargs): # Chấp nhận mọi loại tham số
        print(f"[LOG]: Đang gọi hàm '{func_goc.__name__}' với args={args}, kwargs={kwargs}")
        print("Đang chuẩn bị...")
        return func_goc(*args, **kwargs) # Truyền lại tham số cho hàm gốc
    return ham_bao_boc

@ghi_log_voi_tham_so
def gui_thong_bao(ten, tin_nhan=""):
    print(f"Gửi tới {ten}: {tin_nhan}")

@ghi_log_voi_tham_so
def tinh_tich(a, b):
    return a * b

gui_thong_bao("Alice", tin_nhan="Học Python vui vẻ!")
# [LOG]: Đang gọi hàm 'gui_thong_bao' với args=('Alice',), kwargs={'tin_nhan': 'Học Python vui vẻ!'}
# Đang chuẩn bị...
# Gửi tới Alice: Học Python vui vẻ!

ket_qua_tich = tinh_tich(3, 4)
print(f"Kết quả tích: {ket_qua_tich}")
# [LOG]: Đang gọi hàm 'tinh_tich' với args=(3, 4), kwargs={}
# Đang chuẩn bị...
# Kết quả tích: 12

Khái niệm Closure (Bao đóng)

Closure xảy ra khi một hàm lồng bên trong (nested function) tham chiếu đến các biến cục bộ của hàm bên ngoài (enclosing function) sau khi hàm bên ngoài đã kết thúc quá trình thực thi. Closure tạo ra một liên kết giữa hàm lồng bên trong và các biến "riêng tư" của hàm bên ngoài, cho phép các biến này duy trì trạng thái của chúng qua nhiều lần gọi hàm.

Đây là một ví dụ về hàm lồng nhau:

def ham_ben_ngoai():
    print('Hàm bên ngoài')
    def ham_ben_trong():
        print('Hàm bên trong')
    ham_ben_trong()

ham_ben_ngoai()
# Hàm bên ngoài
# Hàm bên trong

Một hàm có thể trả về một tham chiếu đến hàm khác. Đây là cơ sở để tạo closure và decorator.

def tao_loi_chao():
    thong_diep = "Xin chào từ closure!" # Biến cục bộ của ham_ben_ngoai
    def loi_chao_chi_tiet():
        print(thong_diep) # Truy cập biến 'thong_diep' từ hàm bên ngoài
    return loi_chao_chi_tiet # Trả về hàm bên trong

chao = tao_loi_chao() # Gọi hàm bên ngoài, nó trả về hàm bên trong
print(type(chao))
# <class 'function'>

chao() # Gọi hàm bên trong đã được trả về, nó vẫn nhớ 'thong_diep'
# Xin chào từ closure!

Ví dụ minh họa closure: Tạo hàm tính lũy thừa tùy chỉnh.

def tao_ham_luy_thua(so_mu):
    # 'so_mu' là biến được "ghi nhớ" bởi closure
    def tinh_luy_thua_cho_co_so(co_so):
        return co_so ** so_mu
    return tinh_luy_thua_cho_co_so

# Tạo một hàm để tính bình phương (lũy thừa 2)
binh_phuong = tao_ham_luy_thua(2)
print(f"Bình phương của 5 là: {binh_phuong(5)}") # 5^2
# Bình phương của 5 là: 25
print(f"Bình phương của 8 là: {binh_phuong(8)}") # 8^2
# Bình phương của 8 là: 64

# Tạo một hàm để tính lập phương (lũy thừa 3)
lap_phuong = tao_ham_luy_thua(3)
print(f"Lập phương của 3 là: {lap_phuong(3)}") # 3^3
# Lập phương của 3 là: 27

Trong ví dụ này, binh_phuonglap_phuong là các closure. Chúng ghi nhớ giá trị của so_mu (2 và 3 tương ứng) từ khi chúng được tạo ra bởi tao_ham_luy_thua(). Điều này cho phép chúng hoạt động như các "công cụ" chuyên biệt để tính lũy thừa nhất định.

Các Ví dụ về Decorator

Sử dụng Decorator đơn giản với tham số

Khi bạn sử dụng decorator bằng cú pháp @, hàm decorator sẽ được chạy ngay tại thời điểm Python tải file .py, không phải khi hàm được decorate được gọi.

def in_thong_tin_ham(func_goc):
    print(f"DEBUG: Decorator đã được áp dụng cho '{func_goc.__name__}'")
    def ham_bao_boc(x, y):
        print(f"INFO: Tham số được truyền vào: x={x}, y={y}")
        return func_goc(x, y)
    return ham_bao_boc

@in_thong_tin_ham
def cong_hai_so(so1, so2):
    print(f"Thực hiện phép cộng: {so1} + {so2}")
    return so1 + so2

ket_qua_cong = cong_hai_so(10, 20)
print(f"Kết quả: {ket_qua_cong}")
# DEBUG: Decorator đã được áp dụng cho 'cong_hai_so'
# INFO: Tham số được truyền vào: x=10, y=20
# Thực hiện phép cộng: 10 + 20
# Kết quả: 30

Decorator xử lý danh sách tham số tùy ý (*args)

def log_danh_sach_tham_so(func_goc):
    print(f"DEBUG: Decorator áp dụng cho '{func_goc.__name__}'")
    def ham_bao_boc(*danh_sach_args): # Chấp nhận số lượng tham số tùy ý
        print(f"INFO: Danh sách tham số (*args): {danh_sach_args}")
        return func_goc(*danh_sach_args) # Giải nén và truyền tham số cho hàm gốc
    return ham_bao_boc

@log_danh_sach_tham_so
def tinh_tong_cac_so(*cac_so):
    print(f"Thực hiện tính tổng cho {cac_so}")
    return sum(cac_so)

danh_sach = [1, 2, 3, 4, 5, 6]
ket_qua_tong = tinh_tong_cac_so(*danh_sach) # Giải nén danh_sach thành các tham số riêng lẻ
print(f"Tổng các số: {ket_qua_tong}")
# DEBUG: Decorator áp dụng cho 'tinh_tong_cac_so'
# INFO: Danh sách tham số (*args): (1, 2, 3, 4, 5, 6)
# Thực hiện tính tổng cho (1, 2, 3, 4, 5, 6)
# Tổng các số: 21

Decorator xử lý tham số từ điển tùy ý (**kwargs)

**kwargs được sử dụng để nhận số lượng tham số từ khóa tùy ý và đóng gói chúng vào một từ điển. Khi gọi hàm, **kwargs được dùng để giải nén từ điển thành các tham số từ khóa.

def log_tham_so_tu_khoa(func_goc):
    print(f"DEBUG: Decorator áp dụng cho '{func_goc.__name__}'")
    def ham_bao_boc(*args, **kwargs_tham_so):
        print(f"INFO: Tham số vị trí (*args): {args}")
        print(f"INFO: Tham số từ khóa (**kwargs): {kwargs_tham_so.values()}")
        return func_goc(*args, **kwargs_tham_so)
    return ham_bao_boc

@log_tham_so_tu_khoa
def tao_thong_tin_nguoi_dung(ten, tuoi, **thong_tin_them):
    print(f"Tạo người dùng: {ten}, {tuoi} tuổi. Chi tiết: {thong_tin_them}")
    tong_gia_tri_them = sum(thong_tin_them.values()) if thong_tin_them else 0
    return tong_gia_tri_them

danh_sach_args = ["Viet", 30]
dict_thong_tin = {"chieu_cao": 175, "can_nang": 70}
dict_extra = {"so_thich": "code", "nghe_nghiep": "dev"}

# Kết hợp các từ điển bằng cách sử dụng toán tử giải nén từ điển mới
merged_dict = {**dict_thong_tin, **dict_extra}

gia_tri_them = tao_thong_tin_nguoi_dung(*danh_sach_args, **merged_dict)
print(f"Tổng các giá trị thêm: {gia_tri_them}")
# DEBUG: Decorator áp dụng cho 'tao_thong_tin_nguoi_dung'
# INFO: Tham số vị trí (*args): ('Viet', 30)
# INFO: Tham số từ khóa (**kwargs): dict_values([175, 70, 'code', 'dev'])
# Tạo người dùng: Viet, 30 tuổi. Chi tiết: {'chieu_cao': 175, 'can_nang': 70, 'so_thich': 'code', 'nghe_nghiep': 'dev'}
# Tổng các giá trị thêm: 245

Decorator với Tham số riêng

Một decorator cũng có thể nhận tham số của riêng nó. Điều này đòi hỏi thêm một lớp bao bọc bên ngoài để xử lý các tham số này, trả về decorator thực sự.

def kiem_tra_phuong_thuc(phuong_thuc_yeu_cau): # Đây là hàm nhận tham số của decorator
    def decorator_thuc_su(func_goc): # Đây là decorator nhận hàm gốc
        print(f"DEBUG: Decorator '{phuong_thuc_yeu_cau}' được áp dụng cho '{func_goc.__name__}'")
        def ham_bao_boc(*args, **kwargs): # Hàm bao bọc xử lý tham số của hàm gốc
            print(f"INFO: Gọi hàm '{func_goc.__name__}' với args={args}, kwargs={kwargs}")
            if phuong_thuc_yeu_cau == "POST":
                print("Xử lý yêu cầu POST...")
                return func_goc(*args, **kwargs)
            elif phuong_thuc_yeu_cau == "GET":
                print("Yêu cầu GET không được phép thực hiện tính toán.")
                return "Phản hồi GET: Không có quyền thực thi."
            else:
                return f"Phương thức '{phuong_thuc_yeu_cau}' không được hỗ trợ."
        return ham_bao_boc
    return decorator_thuc_su

@kiem_tra_phuong_thuc("POST") # Truyền tham số cho decorator
def xu_ly_du_lieu(du_lieu_input, so_luong=1):
    print(f"Đang xử lý dữ liệu POST: {du_lieu_input} với số lượng {so_luong}")
    return f"Đã xử lý: {du_lieu_input} x {so_luong}"

@kiem_tra_phuong_thuc("GET") # Truyền tham số khác cho decorator
def lay_thong_tin(query_param=""):
    print(f"Đang cố gắng lấy thông tin với param: {query_param}")
    return "Thông tin đã được lấy thành công."

# Thực thi hàm với decorator "POST"
ket_qua_post = xu_ly_du_lieu("sản phẩm A", so_luong=5)
print(f"Kết quả xử lý POST: {ket_qua_post}\n")
# DEBUG: Decorator 'POST' được áp dụng cho 'xu_ly_du_lieu'
# INFO: Gọi hàm 'xu_ly_du_lieu' với args=('sản phẩm A',), kwargs={'so_luong': 5}
# Xử lý yêu cầu POST...
# Đang xử lý dữ liệu POST: sản phẩm A với số lượng 5
# Kết quả xử lý POST: Đã xử lý: sản phẩm A x 5

# Thực thi hàm với decorator "GET"
ket_qua_get = lay_thong_tin("category=điện thoại")
print(f"Kết quả yêu cầu GET: {ket_qua_get}")
# DEBUG: Decorator 'GET' được áp dụng cho 'lay_thong_tin'
# INFO: Gọi hàm 'lay_thong_tin' với args=('category=điện thoại',), kwargs={}
# Yêu cầu GET không được phép thực hiện tính toán.
# Kết quả yêu cầu GET: Phản hồi GET: Không có quyền thực thi.

Thẻ: python Lambda regular expressions module decorator

Đăng vào ngày 29 tháng 7 lúc 17:18