Giao diện
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() # 0Cá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 và **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ề: 8Lambda 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) # 10Trườ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)
# 5Giớ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()) # 2Cá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) # 5Closure 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 cacheLEGB 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()global và nonlocal 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 2LEGB 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}") # 20Partial 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) # FalseProduction 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 toPitfall 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]() # 0Pitfall 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 incrementPitfall 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):
passPitfall 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 resultCross-links
- 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): ...