Hàm Python
Hàm Python
A. Hàm (Function)
1. Khái niệm
- Hàm là một khối mã có tên thực hiện một nhiệm vụ cụ thể,
có thể nhận tham số và trả về giá trị. Dùng hàm để tách
logic, tái sử dụng và đọc code dễ hơn.
2. Cú pháp khai báo một hàm (Syntax)
- Từ khóa def: bắt đầu định nghĩa hàm
- ten_ham: tên hàm do lập trình viên đặt
- tham_so1, tham_so2,…: các tham số của hàm
- thân hàm: viết lệnh cho hàm
- return ket_qua: trả về biến ket_qua
Ví dụ 1 (gọi một bạn trong lớp trả lời): Cho chương trình như
hình. Dự đoán kết quả của x
Kết quả: x sẽ là None. Vì hàm tong không có câu lệnh return.
Mặc định, kết quả trả về là None
Ví dụ 2 (gọi một bạn trong lớp trả lời): Cho chương trình như
hình. Dự đoán kết quả của x và y
Kết quả: 2 1
Giải thích: Kết quả trả về nhiều giá trị, mỗi giá trị là một
phép toán lần lượt là a // b và a % b. Do đó kết quả là 2 1
6. Câu lệnh pass (pass Statement)
- Câu lệnh pass là một placeholder, được dùng khi định nghĩa
hàm nhưng không có lệnh trong thân hàm (thân hàm rỗng).
- Điều này là do trong Python, một hàm phải có lệnh trong
thân hàm, không được để trống
7. Hàm lồng trong hàm (Function Inside Function)
- Một hàm được định nghĩa bên trong một hàm khác thường
được gọi là inner function (hoặc nested function), hàm bên
ngoài hàm inner thường được gọi là outer function. Hàm
inner có thể truy cập các biến bên trong phạm vi của hàm
outer
- Công dụng của hàm lồng trong hàm
+ Nhờ cơ chế closure, hàm inner có thể ghi nhớ biến của
hàm outer, kể cả hàm outer đã ngừng thực thi (ứng dụng
nhiều trong hàm decorator,…)
+ Giúp chia nhỏ code nhưng vẫn giữ được logic (Ví dụ: thay
vì nhiều đoạn lệnh điều kiện lồng nhau thì có thể dùng hàm
lồng nhau để tách điều kiện ra để dễ xử lí
Ví dụ (gọi một bạn trong lớp trả lời): Cho chương trình như
hình. Dự đoán kết quả của lệnh print(f(5))
Kết quả: 15
Giải thích:
+ Hàm inner(y) nằm trong hàm outer(x), khi gọi hàm
inner(y), kết quả trả về sẽ là x + y (do cơ chế closure, hàm
trong sẽ ghi nhớ biến hàm ngoài)
+ Khi gán f = outer(10), lúc này f là hàm inner(y) nhưng x =
10 đã được hàm inner ghi nhớ
+ Khi thực hiện lệnh print(f(5)), f(5) lúc này thực chất là gọi
hàm inner(5) với biến x = 10
+ Do đó, kết quả đầu ra sẽ là x + y = 10 + 5 = 15
8. Hàm vô danh (Anonymous Functions)
- Hàm vô danh (anonymous function hay còn gọi là lambda
function) là hàm được định nghĩa nhưng ko có tên cụ thể.
- Hàm này được định nghĩa bằng cách dùng từ khóa lambda
- Công dụng của hàm này là cho những chương trình gọn hơn,
khi ta chỉ cần một hàm đơn giản trong thời gian ngắn mà
không cần phải định nghĩa một hàm mới
- Cú pháp hàm lambda: lambda tham_so : bieu_thuc
Kết quả: 4
Giải thích: Khi ta không truyền đối số cho b, nó sẽ lấy giá trị
mặc định là 1. Do đó kết quả là a + b = 3 + 1 = 4
Ví dụ 1 (gọi một bạn trong lớp trả lời): Cho chương trình sau.
Dự đoán kết quả đầu ra
Ví dụ 2 (gọi một bạn trong lớp trả lời): Cho chương trình sau.
Dự đoán kết quả đầu ra
Kết quả đầu ra: world hello
Giải thích:
+ Hàm func có hai tham số là str1 và str2. Trong thân hàm sẽ
in lần lượt là str1 và str2
+ Khi gọi hàm, ta đã sử dụng cú pháp của truyền đối số theo
từ khóa (func(str2 = “hello”, str1 = “world”))
+ Do đó, kết quả đầu ra sẽ là world hello
d) Truyền đối số theo vị trí (Positional Arguments)
- Truyền đối số theo vị trí là cách truyền giá trị cho tham số
của hàm dựa vào thứ tự mà chúng được định nghĩa
- Theo cách này, đối số truyền vào không cần ghi tên tham số,
chỉ cần đúng vị trí
- Nếu truyền sai vị trí, kết quả có thể không như mong muốn
Ví dụ (gọi một bạn trong lớp trả lời): Cho chương trình sau.
Dự đoán kết quả đầu ra
Kết quả:
Giải thích: hàm func nhận tham số ten, tuoi. Truyền đối số
theo đúng thứ tự là ten là Thai, tuoi là 17. Mỗi dòng dùng
lệnh print nên kết quả in ra 2 dòng
e) Kết hợp truyền đối số theo từ khóa và theo vị trí
- Quy tắc kết hợp giữa hai cách truyền:
+ Truyền theo vị trí phải đứng trước truyền theo từ khóa,
nếu truyền ngược lại -> chương trình sẽ báo lỗi cú pháp
+ Một tham số chỉ được gán một lần. Không thể vừa truyền
theo vị trí vừa truyền theo từ khóa cho một tham số
Ví dụ 1 (gọi một bạn trong lớp trả lời): Cho chương trình sau.
Dự đoán kết quả đầu ra
Kết quả:
Giải thích: đối số ten được truyền theo từ khóa, nhưng theo
quy tắc, truyền theo vị trí phải đứng trước truyền theo từ
khóa. Nên chương trình đã báo lỗi cú pháp
Ví dụ 2 (gọi một bạn trong lớp trả lời): Cho chương trình sau.
Dự đoán kết quả đầu ra
Kết quả:
Giải thích: ta truyền đối số tuoi hai lần nên chương trình báo
lỗi cú pháp
f) Truyền đối số chỉ theo từ khóa (Keyword-Only Arguments)
- Keyword-Only Arguments là các đối số mà bắt buộc phải
truyền theo từ khóa khi gọi hàm, không thể truyền theo vị trí
- Cách định nghĩa hàm có sử dụng keyword-only arguments:
+ Trong phần định nghĩa hàm, ta dùng dấu * để đánh dấu
rằng sau dấu * phải truyền theo từ khóa, còn trước dấu * thì
có thể truyền theo 2 cách tùy ý
+ Tất cả tham số đứng sau dấu * sẽ trở thành keyword-only
Ví dụ 1 (gọi một bạn trong lớp trả lời): Cho chương trình sau.
Dự đoán kết quả đầu ra
Kết quả:
Giải thích: hàm func có sử dùng dấu *, cho biết rằng các
tham số đằng sau phải truyền theo từ khóa. Ta đã truyền
tuoi và thanh_pho theo từ khóa nên chương trình chạy bình
thường
Ví dụ 2 (gọi một bạn trong lớp trả lời): Cho chương trình sau.
Dự đoán kết quả đầu ra
Kết quả:
Giải thích: hàm func có sử dùng dấu *, cho biết rằng các
tham số đằng sau phải truyền theo từ khóa. Nhưng tuoi và
thanh_pho lại truyền theo vị trí. Do đó, chương trình báo lỗi
TypeError
g) Truyền đối số chỉ theo vị trí (Positional-Only Arguments)
- Positional-Only Arguments là các đối số mà bắt buộc phải
truyền theo vị trí khi gọi hàm, không thể truyền theo từ khóa
- Cách định nghĩa hàm có sử dụng positional-only arguments:
+ Trong phần định nghĩa hàm, ta dùng dấu / để đánh dấu
rằng trước dấu / phải truyền theo vị trí, còn sau dấu / thì
truyền 2 cách tùy ý
+ Tất cả tham số đứng trước dấu / sẽ trở thành positional-
only
Ví dụ 1 (gọi một bạn trong lớp trả lời): Cho chương trình sau.
Dự đoán kết quả đầu ra
Kết quả:
Giải thích: hàm func có sử dùng dấu /, cho biết rằng các
tham số đằng trước phải truyền theo vị trí. Ta đã truyền ten,
tuoi theo vị trí nên chương trình chạy bình thường
Ví dụ 2 (gọi một bạn trong lớp trả lời): Cho chương trình sau.
Dự đoán kết quả đầu ra
Kết quả:
Giải thích: hàm func có sử dùng dấu /, cho biết rằng các tham số đằng
trước phải truyền theo vị trí. Nhưng ta đã truyền ten, tuoi theo từ khóa
nên chương trình đã báo lỗi
h) Kết hợp truyền đối số chỉ theo từ khóa và chỉ theo vị trí
- Các tham số trước dấu / là positional-only arguments
- Các tham số sau dấu * là keyword-only arguments
- Các tham số nằm giữa / và * thì có thể truyền theo vị trí hay
từ khóa tùy ý
Ví dụ (gọi một bạn trong lớp trả lời). Cho chương trình, hãy
dự đoán kết quả của các lệnh gọi hàm
Chú thích:
+ Lệnh print(f"a={a}, b={b}, c={c}, d={d}") là f-string trong
Python.
+ Khi viết chuỗi với tiền tố f hoặc F, ta có thể chèn trực tiếp
biến và biểu thức Python vào trong dấu {}
+ Python sẽ tự động thay thế {} bằng giá trị biến hoặc biểu
thức tương ứng
Kết quả gọi hàm lần 1:
Kết quả gọi hàm lần 2:
Giải thích:
- Các tham số positional-only là a, b
- Các tham số có thể truyền tùy ý là c
- Các tham số keyword-only là d
- Ở lần gọi hàm 1 (chạy đúng):
+ a, b được truyền theo vị trí (đúng loại)
+ d được truyền theo từ khóa (đúng loại)
- Ở lần gọi hàm 2 (chạy sai): a, b được truyền theo từ khóa (sai loại)
+ Ở lần gọi hàm 3 (chạy sai): d được truyền theo vị trí (sai loại)
C. Đối số *args và **kwargs
1. Đối số *args (Arbitrary Arguments)
a) Khái niệm
- *args được dùng để định nghĩa hàm có thể nhận số lượng
đối số không cố định (số lượng tùy ý)
- Tất cả các đối số truyền theo vị trí (positional arguments)
được gom thành một tuple
b) Cách hoạt động
- Khi khai báo hàm với *args, có thể truyền bao nhiêu đối số
cũng được
- Bên trong hàm, args sẽ là một tuple lưu trữ tất cả giá trị đó
- Lưu ý: *args chỉ nhận truyền đối số theo vị trí, không thể
truyền theo từ khóa
Ví dụ 1 (gọi một bạn trong lớp trả lời): Cho đoạn chương
trình. Dự đoán kết quả
Kết quả:
Ví dụ 2 (gọi một bạn trong lớp trả lời): Cho chương trình. Dự
đoán kết quả
Kết quả:
Giải thích: hàm func có hai tham số s và **t (là kwargs). **t
là dictionary lưu dưới dạng key=value. Khi duyệt dictionary,
sẽ in ra key : value.
3. Kết hợp *args và **kwargs
- Sử dụng *args và **kwargs trong định nghĩa một hàm
Kết quả:
Ví dụ (gọi một bạn trong lớp trả lời): Cho chương trình. Dự
đoán kết quả
Kết quả: 6
Giải thích:
+ Hàm func nhận 3 tham số a, b, c. Ta có một list so gồm các
phần tử là [1, 2, 3].
+ Khi gọi hàm func(*so), Python sẽ mở rộng list này thành 3
đối số theo vị trí lần lượt là 1, 2, 3.
+ Khi đó, cách gọi hàm này giống với cách gọi func(1, 2, 3).
Do đó, kết quả là 6
b) Mở rộng dictionaries với ** (Unpacking Dictionaries with
**)
- Dùng dấu ** để mở rộng dictionary thành các keyword
arguments
Ví dụ (gọi một bạn trong lớp trả lời): Cho chương trình. Dự
đoán kết quả
Kết quả:
Giải thích:
+ Hàm func nhận 3 tham số ten, tuoi, thanh_pho. Ta có một
dictionary d gồm các key-value là {"ten": "Thai", "tuoi": 17,
"thanh_pho": "Nha Trang"}
+ Khi gọi hàm func(**d), Python sẽ mở rộng dictionary này
thành 3 đối số theo từ khóa lần lượt là ten=”Thai”, tuoi=17,
thanh_pho=”Nha Trang”
+ Khi đó, cách gọi hàm này giống với cách gọi
func(ten=”Thai”, tuoi=17, thanh_pho=”Nha Trang”).
D. Phạm vi (Scope)
- Khái niệm: một biến chỉ tồn tại bên trong một khu vực mà
nó được khởi tạo. Đó được gọi là phạm vi (scope)
1. Phạm vi cục bộ (Local Scope)
- Khái niệm: một biến được khởi tạo bên trong một hàm thì sẽ
thuộc phạm vi trong hàm đó, và chỉ có thể truy cập biến đó
bên trong hàm
Ví dụ (gọi một bạn trong lớp trả lời): Cho đoạn chương trình.
Dự đoán kết quả của hai lệnh func() và print(x) ngoài hàm
Kết quả lệnh func(): 300
Kết quả lệnh print(x) ngoài hàm:
Giải thích:
+ Ở trong hàm func, ta khởi tạo x=300 trong hàm, nghĩa là
biến x chỉ tồn tại bên trong hàm func. Ta dùng lệnh print(x)
trong hàm func thì hoàn toàn hợp lệ
+ Ở ngoài hàm func, khi dùng lệnh print(x), sẽ gây ra lỗi
NameError, vì chương trình không tìm thấy biến nào là biến
x, điều này là do biến x chỉ tồn tại bên trong hàm func
+ Đó là phạm vi cục bộ (local scope) trong hàm func
2. Phạm vi toàn cục (Global Scope)
- Một biến được khởi tạo ở phần thân chương trình chính
được gọi là biến toàn cục (global variable) và nó sẽ thuộc
phạm vi toàn cục (global scope)
- Biến này tồn tại ở bất cứ đâu trong chương trình, bất cứ
phạm vi toàn cục hay cục bộ nào
Ví dụ (gọi một bạn trong lớp trả lời): Cho đoạn chương trình.
Dự đoán kết quả của hai lệnh func() và print(x) ngoài hàm
Ví dụ (gọi một bạn trong lớp trả lời): Cho đoạn chương trình.
Dự đoán kết quả
Kết quả:
Giải thích:
+ x được khai báo toàn cục, nhưng khi vào trong hàm func,
Python sẽ báo lỗi biến x cục bộ chưa được khởi tạo giá trị
+ Điều này là do Python phân biệt biến tùy theo vị trí, mặc
dù hai biến x cùng tên nhưng nếu một biến toàn cục và một
biến cục bộ, Python sẽ xem chúng là hai biến riêng biệt
* Cách khắc phục (có thể kêu một bạn lên viết): ta dùng từ
khóa global để có thể truy cập và xử lí biến x toàn cục bên
trong hàm
Kết quả: 15
Giải thích: ta đã dùng từ khóa global cho biến x trong hàm
func, điều này đồng nghĩa với việc ta đã có thể xử lí được
biến x toàn cục ở ngay bên trong hàm func
5. Từ khóa “nonlocal” (Nonlocal Keyword)
a) Khái niệm
- Từ khóa nonlocal được dùng trong nested function (hàm
lồng nhau) để ta có thể sử dụng hoặc thay đổi biến ở phạm
vi bao ngoài (enclosing scope), thay vì tạo biến cục bộ mới
b) Cách dùng
- Khi ta có một hàm nằm bên trong một hàm khác, biến ở
hàm ngoài (outer function) sẽ thuộc phạm vi bao ngoài
(enclosing scope)
- Nếu muốn thay đổi biến đó từ hàm bên trong (inner
function), ta phải dùng từ khóa nonlocal
- Nếu không dùng nonlocal, chương trình sẽ báo lỗi do biến
đó chưa được khởi tạo ở hàm bên trong
Ví dụ (gọi một bạn trong lớp trả lời): Cho đoạn chương trình.
Dự đoán kết quả
Kết quả:
Giải thích:
+ x được khởi tạo cục bộ ở trong hàm outer, điều này có
nghĩa là biến x này là biến cục bộ của hàm outer
+ Khi định nghĩa hàm inner và sử dụng biến x đó, Python sẽ
ngầm hiểu rằng biến x này là biến cục bộ của hàm inner
+ Do đó, chương trình sẽ báo lỗi rằng biến x cục bộ chưa
được khởi tạo
* Cách khắc phục (có thể kêu một bạn lên viết): ta dùng từ
khóa nonlocal để có thể sử dụng biến x ở phạm vi bao ngoài
(enclosing scope) hay ở trong hàm outer
Kết quả:
Giải thích: ta đã dùng từ khóa nonlocal cho biến x, nên ta có
thể sử dụng biến x của hàm outer trong phạm vi bao ngoài
6. Quy tắc LEGB (The LEGB Rule)
a) Khái niệm
- LEGB viết tắt của các từ
+ L (Local): Biến được khởi tạo bên trong hàm hiện tại (local
scope – phạm vi cục bộ)
+ E (Enclosing): Biến bên trong hàm bao ngoài (enclosing
function) khi có hàm lồng nhau
+ G (Global): Biến được khởi tạo toàn cục, bên ngoài mọi
hàm
+ B (Built-in): các tên có sẵn trong Python (như len, print,
range)
b) Nguyên lí hoạt động
- Python sẽ tìm kiếm biến theo thứ tự: Local -> Enclosing ->
Global -> Built-in
Kết quả:
Giải thích:
+ Hàm decor nhận một hàm khác (func) làm đối số
+ Khi đặt @decor trực tiếp ở trên hàm func, hàm func sẽ
được “decorated” với hàm decor (decor là hàm decorator;
hàm func sẽ là hàm được decorate hay còn gọi là decorated
function)
+ Nó định nghĩa hàm wrapper() để bọc thêm logic trước và
sau khi gọi hàm func
+ Sau đó, decor trả về wrapper
+ Cú pháp:
@decor
def func(): print(“Thanh”)
tương tự như func = decor(func). Nghĩa là hàm func được
thay thế bằng phiên bản đã được bọc (wrapper)
+ Khi gọi hàm func, thực chất là gọi hàm wrapper. Trong
wrapper():
In Thai
Gọi hàm func() gốc -> In Thanh
In Chuong
b) Gọi hàm decorator nhiều lần (Multiple Decorator Calls)
- Hàm decorator có thể được gọi nhiều lần
Kết quả:
Giải thích:
+ Hàm decor2 nhận hàm gốc func làm tham số. Trả về
wrapper() -> khi gọi sẽ chạy func() rồi thêm “Hello “ vào
trước kết quả
+ Hàm decor1 nhận kết quả của hàm decor2(func). Trả về
wrapper(), khi gọi sẽ chạy hàm đã được decorate bởi hàm
decor2, rồi chuyển toàn bộ sang chữ hoa
+ Thứ tự thực thi:
@decor1
@decor2
def func():
return "Thai"
Kết quả:
+ Lần 1: 1
+ Lần 2: 2
+ Lần 3: StopIteration
Giải thích: 2 lần gọi đầu, có từ khóa yield nên hàm trả về 1
và 2. Nhưng đến lần gọi thứ 3, vì ko còn giá trị để trả về, hàm
báo lỗi StopIteration
4. Biểu thức Generator (Generator Expressions)
- Giống với tạo list (list comprehension) dùng dấu ngoặc
vuông, ta tạo generator bằng cách dùng biểu thức generator
(generator expression) dùng dấu ngoặc đơn thay vì ngoặc
vuông
Kết quả:
5. Các phương thức của hàm Generator (Generator Methods)
a) Phương thức send() (send() Method)
- Phương thức này cho phép ta gửi một giá trị cho hàm
generator
Kết quả:
b) Phương thức close() (close() Method)
- Phương thức này giúp dừng hàm generator
Kết quả:
6. Lợi ích và ứng dụng của hàm generator
- Tiết kiệm bộ nhớ: hàm generator
- Hỗ trợ tốt cho dữ liệu lớn
- Giúp tạo ra các dãy số vô hạn (như Fibonacci, Cấp số cộng,…)
mà không lo về tràn bộ nhớ