Tham số hàm
Tham số cho hàm trong lập trình Python bao gồm tham số bắt buộc, tham số mặc định và tham số biến động. Bài này chúng ta sẽ đi nghiên cứu về các tham số đó
Để đi xây dựng hàm trong bất cứ ngôn ngữ lập trình nào thì tham số là vấn đề quan trọng đầu tiên. Trong lập trình Python cũng vậy, chúng ta sẽ đi xem xét các tham số cho hàm qua bài viết này.
Tham số bắt buộc của hàm trong Python
Tham số bắt buộc là loại tham số mặc định của hàm trong Python. Hãy xem ví dụ sau:
Ví dụ
def equation(a, b, c):
'''solving quadratic equation
parameters: a, b, c are float
return: a tuple (float, float)
'''
from math import sqrt
d = b * b - 4 * a * c
if d >= 0:
x1 = (- b + sqrt(d)) / (2 * a)
x2 = (- b - sqrt(d)) / (2 * a)
return (x1, x2)
Đây là ví dụ về giải phương trình viết ở dạng hàm với 3 tham số kiểu số (int hoặc float) a, b, c. Kết quả trả về là một tuple chứa hai nghiệm kiểu float.
Trong hàm equation, a, b, c là 3 tham số bắt buộc. Các tham số sử dụng trong quá trình khai báo hàm được gọi là tham số hình thức (formal parameter). Sở dĩ gọi là tham số hình thức là vì chúng chỉ có tên, không có giá trị. Giá trị của các tham số này chỉ xuất hiện trong lời gọi hàm.
Khi gọi (sử dụng) hàm equation bạn bắt buộc phải truyền đủ 3 giá trị tương ứng với 3 tham số a, b, c. Khi này các giá trị truyền cho hàm được gọi là tham số thực, vì giờ nó có giá trị cụ thể.
Bạn có thể trực tiếp truyền giá trị hoặc truyền biến làm tham số thực cho lời gọi hàm:
Ví dụ
(x1, x2) = equation(1, 2, 1) # truyền trực tiếp giá trị làm tham số (thực)
print('real solutions:')
print(f'x1 = {x1}')
print(f'x2 = {x2}')
aa, bb, cc = 1, 2, 1
(x1, x2) = equation(aa, bb, cc) # truyền biến làm tham số
print(f'x1 = {x1}')
print(f'x2 = {x2}')
Dựa vào header chúng ta không biết a, b, c thuộc kiểu dữ liệu nào. Từ tính toán ở thân hàm cho thấy a, b, c phải thuộc kiểu số.
Do Python không yêu cầu chỉ định kiểu khi viết tham số, bạn phải căn cứ vào tài liệu sử dụng của hàm để biết cách dùng đúng. Để hỗ trợ người sử dụng hàm, Python cũng khuyến nghị khi xây dựng hàm nên viết đầy đủ docstring mô tả cho hàm. Bạn sẽ học kỹ hơn về docstring ở phần sau của bài học.
Khi sử dụng hàm bạn viết tên hàm và cung cấp danh sách tham số theo yêu cầu. Đặc biệt lưu ý, giá trị các tham số (gọi là tham số thực) phải viết đúng thứ tự (về kiểu) như hàm yêu cầu. Ví dụ, hàm yêu cầu kiểu theo thứ tự (int, string, list) thì bạn phải viết giá trị theo đúng trật tự đó.
Keyword argument
Python cho phép sử dụng một cách truyền tham số khác có tên gọi là keyword argument. Trong cách truyền tham số này, bạn phải biết tên của tham số (tên sử dụng trong khai báo hàm).
Hãy xem ví dụ:
Ví dụ
(x1, x2) = equation(b = 3, c = 2, a = 1)
Đây là một cách khác để gọi hàm equation.
Hãy để ý cách viết danh sách tham số (b = 3, c = 2, a = 1)
. Chúng ta sử dụng tên tham số (a, b, c) và gán cho nó giá trị tương ứng.
Với cách truyền tham số này, chúng ta không cần quan tâm đến thứ tự tham số. Python có thể phân biệt rõ giá trị nào truyền cho tham số nào thông qua tên gọi.
Nếu bạn đã học lập trình C# sẽ thấy cách truyền tham số này rất quen thuộc.
Để sử dụng cách gọi này bạn phải biết tên chính xác của tham số sử dụng trong lời khai báo hàm. Các IDE đều hỗ trợ hiển thị các thông tin này nếu bạn trỏ chuột lên tên hàm.
Tham số mặc định
Khi xây dựng hàm, trong một số trường hợp bạn muốn đơn giản hóa lời gọi hàm bằng cung cấp sẵn giá trị của một vài tham số. Nếu người dùng không cung cấp giá trị cho tham số đó thì sẽ sử dụng giá trị cung cấp sẵn.
Lấy ví dụ, hàm print() quen thuộc có bốn tham số mặc định end, sep, file, flush. Tham số end chỉ định ký tự cần in ra khi kết thúc xuất dữ liệu. Ký tự sep chỉ định ký tự cần in ra khi kết thúc in mỗi biến (trong trường hợp in ra nhiều giá trị).
Mặc định end có giá trị ‘\n’, nghĩa là cứ kết thúc xuất dữ liệu thì sẽ chuyển xuống dòng mới, và sep có giá trị ‘ ‘ (dấu cách), nghĩa là nếu in ra nhiều giá trị thì các giá trị phân tách nhau bởi dấu cách.
Tuy nhiên trong lời gọi hàm print() bạn không cần truyền giá trị cho các biến này. Khi đó, end và sep đều sử dụng giá trị mặc định. Đó cũng là cách chúng ta sử dụng hàm print() bấy lâu nay.
Khi xây dựng hàm bạn cũng có thể chỉ định một vài tham số làm tham số mặc định như vậy.
Hãy xem ví dụ sau:
Ví dụ
def equation(a, b=0, c=0):
from math import sqrt
d = b * b - 4 * a * c
if d >= 0:
x1 = (- b + sqrt(d)) / (2 * a)
x2 = (- b - sqrt(d)) / (2 * a)
return (x1, x2)
Ở đây chúng ta xây dựng lại hàm equation nhưng giờ b và c trở thành hai tham số mặc định.
Trong phương trình bậc hai ax2 + bx + c = 0, ngoại trừ a bắt buộc khác 0, b và c đều có thể nhận giá trị 0. Vì vậy chúng ta đặt giá trị mặc định cho b và c bằng 0. Trong lời gọi hàm equation, nếu không truyền giá trị cho b và c thì sẽ sử dụng giá trị mặc định.
Với hàm equation như trên chúng ta giờ có thể gọi như sau:
Ví dụ
equation(10) # chỉ cung cấp a = 10 (bắt buộc) bỏ qua b và c
equation(10, 5) # cung cấp a = 10, b = 5, bỏ qua c
equation(10, 5, -10) # cung cấp đủ a, b, c
equation(10, c = -1) # cung cấp a và c (thông qua keyword argument)
equation(a = 10, c = -9) # cung cấp a và c sử dụng keyword argument
Tham số biến động, *args
Khi đọc code trong các tài liệu Python bạn có thể sẽ gặp cách viết tham số *args
và **kwargs
. Cách viết này được sử dụng phổ biến như một quy tắc ngầm để chỉ định một loại tham số đặc biệt: tham số biến động (variable-length arguments). Nói theo cách khác, đây là loại tham số mà số lượng không xác định.
*args và **kwargs được sử dụng cho hai tình huống khác nhau.
Lấy ví dụ, khi sử dụng hàm print bạn có thể để ý thấy một điểm đặc biệt: bạn có thể truyền rất nhiều biến cho print. Số lượng biến truyền vào cho print không bị giới hạn:
Ví dụ
>>> print('hello')
hello
>>> print('hello', 'world')
hello world
>>> print('hello', 'world', 'from Python')
hello world from Python
Đây là một tính năng của hàm trong Python gọi là tham số biến động. Tính năng này cho phép một hàm Python tiếp nhận không giới hạn số lượng biến.
Hãy xem ví dụ sau đây:
Ví dụ
def sum(start, *numbers):
for n in numbers:
start += n
return start
sum(0, 1, 2) # = 3
sum(1, 2, 3) # = 6
sum(0, 1, 2, 3, 4, 5, 6) # = 21
Trong ví dụ này chúng ta xây dựng một hàm cho phép cộng một số lượng giá trị bất kỳ vào một giá trị cho trước. Trong hàm sum, start là một tham số bắt buộc.
Hãy để ý cách viết tham số thứ hai *numbers
. Đây là quy định của Python: nếu một tham số bắt đầu bằng ký tự *, Python sẽ coi nó như một tuple. Theo đó, trong thân hàm bạn có thể sử dụng các giá trị trong tuple này.
Trong ví dụ trên chúng ta giả định rằng trong numbers chỉ chứa giá trị số và chúng ta liên tiếp cộng dồn nó vào biến start.
Từ khía cạnh sử dụng hàm, bạn có thể viết bất kỳ số lượng biến nào trong lời gọi hàm. Với hàm sum ở trên, tất cả các biến từ vị trí số 2 trở đi sẽ được đóng vào một tuple để truyền vào hàm.
Do vậy chúng ta có thể viết hàng loạt lời gọi hàm với lượng tham số khác nhau:
Ví dụ
sum(0, 1, 2) # = 3
sum(1, 2, 3) # = 6
sum(0, 1, 2, 3, 4, 5, 6) # = 21
Khi sử dụng tham số biến động cần lưu ý rằng, loại tham số này nên để ở cuối danh sách. Nếu bạn để loại tham số này ở đầu danh sách, bạn sẽ không bao giờ truyền được giá trị cho các tham số khác nữa. Không tin bạn cứ thử mà xem!
Như một quy tắc ngầm, lập trình viên Python thường ký kiệu tham số biến động là *args
.
Tham số biến động với keyword argument, **kwargs
Một tình huống khác xảy ra với tham số biến động là khi bạn muốn sử dụng keyword argument cho phần biến động.
Như ở trên bạn đã hiểu thế nào là keyword argument. Vậy làm thế này để có thể truyền không giới hạn tham số nhưng theo kiểu keyword argument?
Hãy xem ví dụ sau:
Ví dụ
def foo(**kwargs):
for key, value in kwargs.items():
print(f'{key} = {value}')
foo(a = 1, b = 2, c = 3)
# Kết quả in ra là
#a = 1
#b = 2
#c = 3
Qua ví dụ nhỏ này bạn đã thấy hiệu quả của cú pháp **kwargs:
- Bạn có thể truyền không giới hạn tham số;
- Mỗi tham số đều có thể truyền ở dạng keyword argument tên_biến = giá_trị;
Để sử dụng **kwargs trong hàm, bạn cần duyệt nó trong vòng for để lấy ra các cặp khóa (key) và giá trị (value): for key, value in kwargs.items()
.
Tùy vào từng hàm, bạn có thể sử dụng key và value theo những cách khác nhau.
Giả sử nếu bạn cần tính tổng, bạn có thể xây dựng lại hàm sum như sau:
Ví dụ
def sum(start: int, **numbers):
for _, value in numbers.items():
start += value
return start
print(sum(start = 0, a = 1, b = 2, c = 3))
Tương tự như *args, **kwargs (viết tắt của keyword arguments) cũng là một tên gọi quy ước được đa số sử dụng. Bạn có thể dùng tên gì tùy thích nhưng nhớ đặt hai dấu ** phía trước.
Khi có nhiều loại tham số trong một lời khai báo hàm, bạn nên để chúng theo thứ tự như sau: (1) các tham số bắt buộc, (2) các tham số mặc định, (3) *args, (4) **kwargs.
Chỉ báo kiểu cho tham số hàm và kết quả
Một trong những vấn đề quan trọng hàng đầu khi xây dựng hỗ trợ sử dụng hàm là cung cấp thông tin về kiểu dữ liệu của tham số. Với các ngôn ngữ định kiểu tĩnh như C/C++ thì đây không là vấn đề.
Trong Python, nếu bạn không hỗ trợ cung cấp thông tin về kiểu dữ liệu của tham số, người sử dụng hàm sẽ gặp nhiều khó khăn.
Trong bài học về hàm trong Python bạn đã biết khái niệm và vai trò của docstring. Trong Python, mặc dù docstring không bắt buộc nhưng bạn nên sử dụng bất kỳ khi nào có thể.
Tuy nhiên, tài liệu hỗ trợ của hàm hoàn toàn dựa trên docstring có những nhược điểm nhất định. Vấn đề dễ thấy nhất là ở tính tự do của docstring. Không có quy định nào về cách viết docstring.
Thông thường người ta viết docstring theo quy ước như sau:
Ví dụ
def some_function(argument1):
"""Summary or Description of the Function
Parameters:
argument1 (int): Description of arg1
Returns:
int:Returning value
"""
Tuy nhiên bạn có thể viết bất kỳ kiểu nào mình thích.
Điều này gây khó khăn cho các công cụ khi cần xử lý docstring.
Bắt đầu từ Python 3.5 bạn có thể kết hợp thêm chỉ báo kiểu (type hint) khi khai báo danh sách tham số của hàm.
Hãy xem cách viết hàm sau:
Ví dụ
def sum_range(start: int, stop: int, step: int = 1) -> int:
'''Tính tổng các số trong khoảng [start, stop)
start: giá trị đâu khoảng
stop: giá trị cuối khoảng
step: khoảng giữa hai giá trị liền nhau
'''
sum = 0
for i in range(start, stop, step):
sum += i
return sum
sum_range(1, 100)
Lối viết này khác biệt ở chỗ sau mỗi tham số sẽ chỉ dẫn kiểu của tham số đó (start: int, stop: int, step: int = 1
), và có cả chỉ dẫn kiểu kết quả (-> int
).
Với cách khai báo này, các công cụ có thể trợ giúp tốt hơn cho người dùng hàm cũng như khi xây dựng hàm.
Dưới đây là ví dụ trong Visual Studio.
Bạn nên kết hợp cả docstring và type hint để đạt hiệu quả tốt nhất.
Lưu ý: type hint trong Python chỉ có ý nghĩa trợ giúp, nó không phải là chỉ định kiểu tham số, và cũng không biến Python thành ngôn ngữ định kiểu tĩnh như C.
Kết luận
Trong bài học này chúng ta đã xem xét chi tiết một số vấn đề liên quan đến sử dụng tham số cho hàm, bao gồm tham số bắt buộc, tham số mặc định, tham số biến động. Đây là những tính năng của Python giúp việc xây dựng và sử dụng hàm linh hoạt và đơn giản hơn.