Skip to content

Functions & Lambdas Core

Functions là khối xây dựng của mọi chương trình Python

Learning Outcomes

Sau khi hoàn thành trang này, bạn sẽ:

  • 🎯 Hiểu sâu về closures và cách Python capture variables
  • 🎯 Nắm vững LEGB rule để debug scope issues
  • 🎯 Sử dụng partial application patterns hiệu quả
  • 🎯 Tránh các Production Pitfalls với functions

Cơ bản về Function

Cấu trúc của một Function

python
def tinh_thue(
    thu_nhap: float,           # Tham số vị trí
    ty_le: float = 0.1,        # Tham số mặc định
    *,                         # Bắt buộc keyword-only sau đây
    giam_tru: float = 0        # Tham số keyword-only
) -> float:
    """
    Tính thuế với giảm trừ tùy chọn.
    
    Args:
        thu_nhap: Thu nhập gộp
        ty_le: Tỷ lệ thuế (mặc định 10%)
        giam_tru: Số tiền giảm trừ
        
    Returns:
        Số thuế cuối cùng
    """
    return (thu_nhap - giam_tru) * ty_le

# Sử dụng
thue = tinh_thue(100000, ty_le=0.2, giam_tru=10000)

*args - Tham số Vị trí Linh hoạt

*args cho phép function nhận số lượng tham số không giới hạn.

python
def tong_tat_ca(*so: int) -> int:
    """Tính tổng bất kỳ số lượng số nguyên nào."""
    return sum(so)

# Sử dụng
tong_tat_ca(1, 2, 3)           # 6
tong_tat_ca(1, 2, 3, 4, 5)     # 15
tong_tat_ca()                   # 0

Cách hoạt động

python
def debug_args(*args):
    print(f"Kiểu: {type(args)}")    # <class 'tuple'>
    print(f"Giá trị: {args}")
    
debug_args(1, "xin chào", True)
# Kiểu: <class 'tuple'>
# Giá trị: (1, 'xin chào', True)

Giải nén với *

python
so = [1, 2, 3, 4, 5]

# Trải list vào function
tong_tat_ca(*so)  # Tương đương tong_tat_ca(1, 2, 3, 4, 5)

# Giải nén trong phép gán
dau, *giua, cuoi = [1, 2, 3, 4, 5]
# dau = 1, giua = [2, 3, 4], cuoi = 5

**kwargs - Tham số Keyword Linh hoạt

**kwargs cho phép function nhận tham số keyword không giới hạn.

python
def tao_user(**kwargs) -> dict:
    """Tạo user với bất kỳ thuộc tính nào."""
    return {
        "tao_luc": "2024-01-01",
        **kwargs  # Gộp tất cả keyword args
    }

# Sử dụng
user = tao_user(ten="HPN", tuoi=28, vai_tro="admin")
# {'tao_luc': '2024-01-01', 'ten': 'HPN', 'tuoi': 28, 'vai_tro': 'admin'}

Cách hoạt động

python
def debug_kwargs(**kwargs):
    print(f"Kiểu: {type(kwargs)}")   # <class 'dict'>
    print(f"Giá trị: {kwargs}")

debug_kwargs(a=1, b="xin chào", c=True)
# Kiểu: <class 'dict'>
# Giá trị: {'a': 1, 'b': 'xin chào', 'c': True}

Giải nén Dict với **

python
mac_dinh = {"theme": "dark", "lang": "vi"}
ghi_de = {"theme": "light"}

# Gộp dicts
cuoi_cung = {**mac_dinh, **ghi_de}
# {'theme': 'light', 'lang': 'vi'}

# Truyền dict như kwargs
def cau_hinh(theme: str, lang: str) -> None:
    print(f"Theme: {theme}, Lang: {lang}")

cau_hinh(**mac_dinh)  # Tương đương cau_hinh(theme="dark", lang="vi")

Kết hợp *args**kwargs

python
def sieu_linh_hoat(*args, **kwargs) -> None:
    """Nhận bất kỳ thứ gì."""
    print(f"Args: {args}")
    print(f"Kwargs: {kwargs}")

sieu_linh_hoat(1, 2, 3, ten="HPN", debug=True)
# Args: (1, 2, 3)
# Kwargs: {'ten': 'HPN', 'debug': True}

Quy tắc Thứ tự Tham số

python
def thu_tu_dung(
    vi_tri,               # 1. Vị trí thường
    *args,                # 2. *args
    keyword_only,         # 3. Keyword-only (sau *)
    **kwargs              # 4. **kwargs (luôn cuối cùng)
) -> None:
    pass

# Gọi hợp lệ
thu_tu_dung(1, 2, 3, keyword_only="bat_buoc", extra="cho_phep")

Pattern Decorator

python
from functools import wraps
from typing import Callable, Any

def ghi_log_goi_ham(func: Callable) -> Callable:
    """Ghi log tất cả lời gọi function."""
    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        print(f"Đang gọi {func.__name__} với {args}, {kwargs}")
        ket_qua = func(*args, **kwargs)
        print(f"Trả về: {ket_qua}")
        return ket_qua
    return wrapper

@ghi_log_goi_ham
def cong(a: int, b: int) -> int:
    return a + b

cong(5, 3)
# Đang gọi cong với (5, 3), {}
# Trả về: 8

Lambda Functions

Lambdas là hàm ẩn danh cho logic đơn giản.

Cú pháp

python
# Hàm thường
def gap_doi(x):
    return x * 2

# Lambda tương đương
gap_doi = lambda x: x * 2

# Sử dụng
gap_doi(5)  # 10

Trường hợp Sử dụng Phổ biến

1. Sắp xếp với Key Tùy chỉnh

python
users = [
    {"ten": "Charlie", "tuoi": 25},
    {"ten": "Alice", "tuoi": 30},
    {"ten": "Bob", "tuoi": 20},
]

# Sắp xếp theo tuổi
sorted(users, key=lambda u: u["tuoi"])
# [{'ten': 'Bob', 'tuoi': 20}, {'ten': 'Charlie', 'tuoi': 25}, ...]

# Sắp xếp theo tên
sorted(users, key=lambda u: u["ten"])
# [{'ten': 'Alice', ...}, {'ten': 'Bob', ...}, ...]

# Sắp xếp theo nhiều trường
sorted(users, key=lambda u: (u["tuoi"], u["ten"]))

2. Lọc với Lambda

python
so = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Lọc số chẵn
chan = list(filter(lambda x: x % 2 == 0, so))
# [2, 4, 6, 8, 10]

# Cách Pythonic (ưu tiên hơn)
chan = [x for x in so if x % 2 == 0]

3. Map với Lambda

python
so = [1, 2, 3, 4, 5]

# Nhân đôi mỗi số
gap_doi = list(map(lambda x: x * 2, so))
# [2, 4, 6, 8, 10]

# Cách Pythonic (ưu tiên hơn)
gap_doi = [x * 2 for x in so]

4. Reduce với Lambda

python
from functools import reduce

so = [1, 2, 3, 4, 5]

# Tính tổng
tong = reduce(lambda acc, x: acc + x, so, 0)
# 15

# Tìm max
lon_nhat = reduce(lambda a, b: a if a > b else b, so)
# 5

Giới hạn của Lambda

python
# ❌ Lambda KHÔNG THỂ có nhiều statements
lambda x: (
    print(x),  # Đây là mẹo, không khuyến khích
    x * 2
)[-1]

# ❌ Lambda KHÔNG THỂ có phép gán
lambda x: y = x * 2  # SyntaxError

# ✅ Dùng hàm thường cho logic phức tạp
def logic_phuc_tap(x):
    trung_gian = x * 2
    if trung_gian > 10:
        return trung_gian - 5
    return trung_gian

⚠️ QUY TẮC CỦA HPN

Lambda chỉ nên dùng cho logic một dòng. Nếu logic phức tạp hơn, hãy dùng hàm thường với tên có ý nghĩa.


Higher-Order Functions

Functions nhận functions làm tham số hoặc trả về functions.

python
from typing import Callable

# Function trả về function
def tao_nhan(n: int) -> Callable[[int], int]:
    """Tạo hàm nhân."""
    return lambda x: x * n

gap_doi = tao_nhan(2)
gap_ba = tao_nhan(3)

gap_doi(5)  # 10
gap_ba(5)   # 15

# Function nhận function
def ap_dung_hai_lan(func: Callable[[int], int], gia_tri: int) -> int:
    """Áp dụng function hai lần."""
    return func(func(gia_tri))

ap_dung_hai_lan(lambda x: x + 1, 5)  # 7 (5 -> 6 -> 7)

Closures Deep Dive

Closure là gì?

Closure là function "nhớ" các biến từ scope bên ngoài, ngay cả khi scope đó đã kết thúc.

python
def tao_counter():
    """Factory function tạo counter với state riêng."""
    count = 0  # Biến trong enclosing scope
    
    def increment():
        nonlocal count  # Khai báo sử dụng biến từ enclosing scope
        count += 1
        return count
    
    return increment  # Trả về function, không phải kết quả

# Tạo 2 counters độc lập
counter1 = tao_counter()
counter2 = tao_counter()

print(counter1())  # 1
print(counter1())  # 2
print(counter1())  # 3

print(counter2())  # 1 - counter2 có state riêng!
print(counter2())  # 2

Cách Closure Hoạt Động

python
def outer(x):
    def inner(y):
        return x + y  # inner "captures" x từ outer
    return inner

add_5 = outer(5)
print(add_5(3))  # 8

# Kiểm tra closure
print(add_5.__closure__)  # (<cell at 0x...: int object at 0x...>,)
print(add_5.__closure__[0].cell_contents)  # 5

Closure Pitfall: Late Binding

python
# ❌ BUG KINH ĐIỂN: Late binding trong loop
functions = []
for i in range(3):
    functions.append(lambda: i)

print([f() for f in functions])  # [2, 2, 2] - Tất cả đều là 2!

# Tại sao? Lambda capture REFERENCE đến i, không phải VALUE
# Khi loop kết thúc, i = 2

# ✅ SỬA 1: Default argument (capture value)
functions = []
for i in range(3):
    functions.append(lambda i=i: i)  # i=i capture value tại thời điểm tạo

print([f() for f in functions])  # [0, 1, 2]

# ✅ SỬA 2: Factory function
def make_func(i):
    return lambda: i

functions = [make_func(i) for i in range(3)]
print([f() for f in functions])  # [0, 1, 2]

# ✅ SỬA 3: functools.partial
from functools import partial

def return_value(x):
    return x

functions = [partial(return_value, i) for i in range(3)]
print([f() for f in functions])  # [0, 1, 2]

Use Case: Memoization với Closure

python
def memoize(func):
    """Decorator cache kết quả function."""
    cache = {}  # Closure captures cache
    
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    
    wrapper.cache = cache  # Expose cache để debug
    return wrapper

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

print(fibonacci(100))  # Instant! Không có closure sẽ chạy mãi
print(fibonacci.cache)  # Xem cache

LEGB Rule - Scope Resolution

Python tìm biến theo thứ tự LEGB:

L - Local:      Trong function hiện tại
E - Enclosing:  Trong function bao ngoài (closures)
G - Global:     Ở module level
B - Built-in:   Python built-ins (print, len, etc.)

Visualization

python
# B - Built-in scope
# print, len, str, int, etc.

# G - Global scope (module level)
global_var = "global"

def outer():
    # E - Enclosing scope
    enclosing_var = "enclosing"
    
    def inner():
        # L - Local scope
        local_var = "local"
        
        # Python tìm theo thứ tự: L → E → G → B
        print(local_var)      # L: "local"
        print(enclosing_var)  # E: "enclosing"
        print(global_var)     # G: "global"
        print(len)            # B: <built-in function len>
    
    inner()

outer()

globalnonlocal Keywords

python
count = 0  # Global

def increment_global():
    global count  # Khai báo sử dụng global variable
    count += 1

def outer():
    count = 0  # Enclosing
    
    def inner():
        nonlocal count  # Khai báo sử dụng enclosing variable
        count += 1
    
    inner()
    print(count)  # 1

# Không có global/nonlocal → tạo biến mới trong local scope
def broken():
    count = 0  # Tạo LOCAL count, không modify global
    count += 1

increment_global()
print(count)  # 1

broken()
print(count)  # Vẫn là 1, không phải 2

LEGB Debugging

python
def debug_scope():
    """Xem các scopes hiện tại."""
    import builtins
    
    local_var = "local"
    
    print("Local:", locals())
    print("Global:", {k: v for k, v in globals().items() if not k.startswith('_')})
    print("Built-ins:", dir(builtins)[:10], "...")

debug_scope()

Shadowing Warning

python
# ❌ BAD: Shadowing built-in
list = [1, 2, 3]  # Shadows built-in list()
# list("hello")  # TypeError: 'list' object is not callable

# ❌ BAD: Shadowing trong nested functions
def outer():
    x = 10
    
    def inner():
        x = 20  # Tạo LOCAL x, không modify outer's x
        print(f"Inner x: {x}")
    
    inner()
    print(f"Outer x: {x}")  # Vẫn là 10!

# ✅ GOOD: Dùng nonlocal nếu muốn modify
def outer():
    x = 10
    
    def inner():
        nonlocal x
        x = 20
    
    inner()
    print(f"Outer x: {x}")  # 20

Partial Application

Partial application là kỹ thuật "đóng băng" một số arguments của function.

functools.partial

python
from functools import partial

def power(base, exponent):
    return base ** exponent

# Tạo specialized functions
square = partial(power, exponent=2)
cube = partial(power, exponent=3)

print(square(5))  # 25
print(cube(5))    # 125

# Partial với positional args
def greet(greeting, name, punctuation):
    return f"{greeting}, {name}{punctuation}"

say_hello = partial(greet, "Hello")
say_hello_excited = partial(greet, "Hello", punctuation="!")

print(say_hello("Alice", "!"))      # Hello, Alice!
print(say_hello_excited("Bob"))     # Hello, Bob!

Use Case: Callback Configuration

python
from functools import partial

def log_message(level, message, timestamp=None):
    """Generic logging function."""
    ts = timestamp or datetime.now()
    print(f"[{level}] {ts}: {message}")

# Tạo specialized loggers
log_info = partial(log_message, "INFO")
log_error = partial(log_message, "ERROR")
log_debug = partial(log_message, "DEBUG")

log_info("Application started")
log_error("Connection failed")

Use Case: Event Handlers

python
from functools import partial

def handle_button_click(button_id, action, event):
    """Generic button handler."""
    print(f"Button {button_id}: {action}")

# Trong GUI framework
button1.on_click = partial(handle_button_click, "btn1", "submit")
button2.on_click = partial(handle_button_click, "btn2", "cancel")

Use Case: API Clients

python
from functools import partial
import requests

def api_request(method, base_url, endpoint, **kwargs):
    """Generic API request function."""
    url = f"{base_url}{endpoint}"
    return requests.request(method, url, **kwargs)

# Tạo client cho specific API
github_api = partial(api_request, base_url="https://api.github.com")
github_get = partial(github_api, "GET")
github_post = partial(github_api, "POST")

# Sử dụng
users = github_get("/users/octocat")
)

Partial vs Lambda

python
from functools import partial

# Cả hai đều hoạt động
add_5_partial = partial(lambda x, y: x + y, 5)
add_5_lambda = lambda x: (lambda x, y: x + y)(5, x)

# Nhưng partial có ưu điểm:
# 1. Rõ ràng hơn về intent
# 2. Giữ nguyên function metadata
# 3. Có thể inspect arguments

print(add_5_partial.func)   # <function <lambda> at ...>
print(add_5_partial.args)   # (5,)
print(add_5_partial.keywords)  # {}

partialmethod cho Classes

python
from functools import partialmethod

class Cell:
    def __init__(self):
        self._alive = False
    
    def set_state(self, state):
        self._alive = state
    
    # Partial methods
    set_alive = partialmethod(set_state, True)
    set_dead = partialmethod(set_state, False)

cell = Cell()
cell.set_alive()
print(cell._alive)  # True
cell.set_dead()
print(cell._alive)  # False

Production Pitfalls

Pitfall 1: Mutable Default Arguments (Lặp lại vì quan trọng!)

python
# ❌ BUG: List được share giữa các lần gọi
def append_to(element, to=[]):
    to.append(element)
    return to

print(append_to(1))  # [1]
print(append_to(2))  # [1, 2] - WTF?!

# ✅ SỬA: None pattern
def append_to(element, to=None):
    if to is None:
        to = []
    to.append(element)
    return to

Pitfall 2: Closure Late Binding (Lặp lại vì quan trọng!)

python
# ❌ BUG: Tất cả lambdas reference cùng một i
buttons = [lambda: print(i) for i in range(5)]
buttons[0]()  # 4, không phải 0!

# ✅ SỬA: Capture value
buttons = [lambda i=i: print(i) for i in range(5)]
buttons[0]()  # 0

Pitfall 3: Modifying Closure Variables

python
# ❌ ERROR: UnboundLocalError
def counter():
    count = 0
    def increment():
        count += 1  # Python nghĩ count là local!
        return count
    return increment

# counter()()  # UnboundLocalError: local variable 'count' referenced before assignment

# ✅ SỬA: nonlocal
def counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

Pitfall 4: *args/**kwargs Order

python
# ❌ ERROR: SyntaxError
def bad_order(**kwargs, *args):  # SyntaxError!
    pass

# ✅ CORRECT: *args trước **kwargs
def good_order(*args, **kwargs):
    pass

# Full order: positional, *args, keyword-only, **kwargs
def full_signature(pos1, pos2, *args, kw_only, **kwargs):
    pass

Pitfall 5: Return trong Finally

python
# ❌ CONFUSING: finally overrides return
def confusing():
    try:
        return "try"
    finally:
        return "finally"  # This wins!

print(confusing())  # "finally"

# ✅ BETTER: Không return trong finally
def better():
    result = None
    try:
        result = "try"
    finally:
        # Cleanup code, không return
        pass
    return result

  • Decorators - Function decorators sử dụng closures
  • functools (Phase 2) - partial, lru_cache, và các utilities
  • Type Hinting - Typed functions với Callable

Bảng Tóm tắt

python
# === *args: Thu thập tham số vị trí thành tuple ===
def func(*args):
    for arg in args:
        print(arg)

func(1, 2, 3)  # Hoạt động với bất kỳ số lượng args

# === **kwargs: Thu thập tham số keyword thành dict ===
def func(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

func(a=1, b=2)  # Hoạt động với bất kỳ keyword args

# === Giải nén ===
so = [1, 2, 3]
func(*so)        # Trải list thành tham số vị trí

config = {"a": 1}
func(**config)   # Trải dict thành tham số keyword

# === Lambda ===
# Hàm ẩn danh đơn giản
binh_phuong = lambda x: x ** 2

# Các pattern phổ biến
sorted(items, key=lambda x: x.attr)
list(filter(lambda x: x > 0, so))
list(map(lambda x: x * 2, so))

# === Thứ tự Tham số ===
def func(vi_tri, *args, keyword_only, **kwargs): ...