Ở bài này, chúng ta sẽ tìm hiểu về tham số (parameter) của hàm trong Python. Khi gọi một hàm, chúng ta cần truyền các đối số (argument) cho hàm. Để phân biệt tham số và đối số cũng như làm cơ sở học tốt bài này, các bạn có thể đọc bài Khái niệm tham số và đối số trong C++.
1. Định nghĩa hàm với các tham số
Khi một hàm (function) được định nghĩa, các tham số cũng được xác định bên trong dấu ngoặc đơn của hàm. Một hàm có thể có số lượng tham số bất kỳ. Mỗi tham số được phân tách bằng dấu phẩy ,.
def hello(name, msg):
"""Say hello to a person with message"""
print("Hello ", name, ", ", msg, sep='')
hello("John", "Welcome to Gochocit.com!")
Kết quả
Hello John, Welcome to Gochocit.com!
Hàm hello(name, msg)
có 2 tham số. Khi gọi hàm, nếu chúng ta chỉ truyền 1 đối số hoặc không truyền đối số nào sẽ gây ra lỗi.
def hello(name, msg):
"""Say hello to a person with message"""
print("Hello ", name, ", ", msg, sep='')
#TypeError: hello() missing 1 required positional argument: 'msg'
hello("John")
#TypeError: hello() missing 2 required positional arguments: 'name' and 'msg'
hello()
2. Đối số mặc định của hàm trong Python
Khi định nghĩa hàm, chúng ta có thể cung cấp giá trị mặc định cho các tham số bằng cách sử dụng toán tử gán ‘=’. Chúng ta gọi là đối số mặc định (default argument). Ví dụ:
def hello(name, msg="Welcome to Gochocit.com!"):
"""Say hello to a person with message"""
"""If the msg is not provided, it defaults to "Welcome to Gochocit.com!"
"""
print("Hello ", name, ", ", msg, sep='')
#Not provided any value for default argument
hello("John")
#Provided a value for default argument
hello("John", "How are you?")
Kết quả
Hello John, Welcome to Gochocit.com!
Hello John, How are you?
Trong ví dụ trên, tham số name không có giá trị mặc định nên bắt buộc phải truyền đối số khi gọi hàm. Còn tham số msg đã có giá trị mặc định là “Welcome to Gochocit.com!”. Nên khi gọi hàm có thể truyền đối số hoặc không đều được.
Lưu ý: Tất cả các tham số của hàm đều có thể có giá trị mặc định. Nhưng có trường hợp, một vài tham số có giá trị mặc định còn một vài tham số khác không có giá trị mặc định. Lúc này, tất cả tham số có giá trị mặc định đều phải được đặt bên phải các tham số không có giá trị mặc định. Nếu quy tắc này không được tuân thủ sẽ gây ra lỗi. Ví dụ:
#SyntaxError: non-default argument follows default argument
def hello(msg="Welcome to Gochocit.com!", name):
"""Say hello to a person with message"""
"""If the msg is not provided, it defaults to "Welcome to Gochocit.com!"
"""
print("Hello ", name, ", ", msg, sep='')
Tham số msg có giá trị mặc định nằm trước (nằm bên trái) của tham số name không có giá trị mặc định sẽ gây ra lỗi SysntaxError.
3. Gọi hàm với tên của tham số
Khi chúng ta gọi một hàm, các giá trị truyền vào hàm sẽ tương ứng với các tham số theo thứ tự từ trái qua phải. Có một ý tưởng là cho phép gọi hàm bằng việc chỉ định tên tham số với các giá trị. Việc này có lợi là chúng ta không cần nhớ thứ tự của các tham số. Chúng ta gọi là keyword argument. Ví dụ:
def hello(name, msg):
"""Say hello to a person with message"""
print("Hello ", name, ", ", msg, sep='')
# 2 keyword arguments
hello(name = "John", msg = "How are you?")
# 2 keyword arguments (out of order)
hello(msg = "How are you?", name = "John")
#1 positional, 1 keyword argument
hello("John", msg = "How are you?")
Kết quả
Hello John, How are you?
Hello John, How are you?
Hello John, How are you?
Chúng ta có thể vừa sử dụng tên tham số vừa sử dụng thứ tự tham số khi gọi hàm. Nhưng sử dụng tên tham số phải luôn ở sau thứ tự tham số. Ví dụ:
def hello(name, msg):
"""Say hello to a person with message"""
print("Hello ", name, ", ", msg, sep='')
#SyntaxError: positional argument follows keyword argument
hello(name = "John", "How are you?")
Khi gọi hàm mà sử dụng tên tham số trước thứ tự tham số sẽ gây ra lỗi SyntaxError.
4. Gọi hàm với số lượng đối số tùy ý trong Python
Trong một số trường hợp, chúng ta không biết trước số lượng đối số truyền vào một hàm khi gọi hàm. Python cho phép truyền số lượng đối số khác cho mỗi lần gọi hàm bằng cách sử dụng các dấu hoa thị * (asterisk). Có 2 cách sử dụng dấu hoa thị * trong trường hợp này là:
- *args (Non-Keyword Arguments)
- **kwargs (Keyword Arguments)
Sử dụng *args (Non-Keyword Arguments)
Sử dụng một dấu hoa thị * trước tên tham số để sử dụng Non-Keyword Arguments. Tên tham số tuân thủ theo quy tắc đặt tên định danh (identifier).
def hello(*names):
"""Say hello to all person in names"""
for name in names:
print("Hello", name)
hello("John", "James", "Mary", "Robert", "Jennifer")
Kết quả
Hello John
Hello James
Hello Mary
Hello Robert
Hello Jennifer
Nguyên tắc hoạt động của Non-Keyword Arguments là các đối số được đưa vào trong một tuple. Chúng ta sử dụng for để duyệt qua từng phần tử trong tuple này.
Sử dụng **kwargs (Keyword Arguments)
Sử dụng hai dấu hoa thị ** trước tên tham số để sử dụng Keyword Arguments. Các đối số được truyền vào hàm đều có một tên định danh (identifier) đại diện.
def hello(**notices):
"""Send notices to all person"""
for key, value in notices.items():
print("%s, %s" % (key, value))
hello(John="How are you?", James="Welcome to Gochocit.com!", Mary="Nice to meet you!")
Kết quả
John, How are you?
James, Welcome to Gochocit.com!
Mary, Nice to meet you!
5. Truyền đối số cho hàm trong Python là tham trị hay tham chiếu?
Nếu các bạn đã đọc bài Biến (variable) và Bộ nhớ biến trong Python, thì các bạn sẽ biết tất cả biến trong Python đều tham chiếu đến một object nào đó. Khi gọi hàm, chúng ta truyền một biến cho hàm. Lúc này, một tham chiếu mới đến đối tượng (object) của biến đó sẽ được tạo ra. Ví dụ, chúng ta có một hàm và gọi hàm như sau:
def myFunction(val):
if(val>0):
print("val =", val, "is greater than 0")
val = 9.1
print("val =", val)
x = 5
myFunction(x)
print("x = ", x)
Kết quả
val = 5 is greater than 0
val = 9.1
x = 5
Trong ví dụ trên, cơ chế truyền đối số cho hàm myFunction(val) xảy ra như sau:
1) Khi gán x = 5 thì x tham chiếu đến một đối tượng của lớp int lưu trữ giá trị 5.
2) Chúng ta gọi hàm myFunction(x) với biến x là đối số được truyền vào cho hàm.
3) Sau đó, một biến val được tạo ra. Biến val này cũng sẽ tham chiếu đến đối tượng của lớp int lưu trữ giá trị 5 mà biến x tham chiếu đến. Tức là 2 biến x và val cùng tham chiếu đến một object 5.
4) Nhưng sau đó, gán val = 9.1 thì lúc này val sẽ tham chiếu đến một đối tượng của lớp float lưu trữ giá trị 9.1. Tức là val bỏ tham chiếu đến object 5. Lúc này chỉ còn x tham chiếu đến object 5.
5) Sau khi hàm kết thúc, biến val và object 9.1 cũng bị xóa. Và biến x thì vẫn tham chiếu đến object 5 mà không bị thay đổi khi ra khỏi hàm.
Ví dụ truyền đối số là list cho hàm trong Python
#x is a new reference to same list lst
def myFunction(x):
x[0] = 99
x[2] = 999
lst = [10, 11, 12, 13, 14, 15]
myFunction(lst)
print(lst)
Kết quả
[99, 11, 999, 13, 14, 15]
Trong ví dụ trên, lst và x cùng tham chiếu đến cùng object lưu trữ một danh sách (list). Khi x thay đổi các phần tử trong object này thì truy xuất bởi lst cũng sẽ được kết quả thay đổi theo.
Nhưng nếu gán x bằng một danh sách (list) khác thì x và lst sẽ tham chiếu khác object. Ví dụ:
def myFunction(x):
# A new object is assigned to x.
x = [20, 30, 40]
lst = [10, 11, 12, 13, 14, 15]
myFunction(lst)
print(lst)
Kết quả
[10, 11, 12, 13, 14, 15]
Khi gán x = [20, 30, 40] thì x đã tham chiếu đến một object list khác. Do đó, x và lst bây giờ không liên quan gì đến nhau.
Ví dụ truyền đối số là integer cho hàm trong Python
def myFunction(val):
# A new object is assigned to x.
val = 20
print("val = ", val)
x = 10
myFunction(x)
print("x = ", x)
Kết quả
val = 20
x = 10
Khi gán val = 20 thì val đã tham chiếu đến một object int khác. Do đó, x và val bây giờ không liên quan gì đến nhau.
Tóm lại, rõ ràng khi truyền đối số cho hàm trong Python là truyền tham chiếu. Nhưng điểm đặc biệt của Python là khi gán giá trị cho một biến với toán tử gán = thì liên kết tham chiếu cũ sẽ bị phá bỏ. Các bạn lưu ý điểm này khi truyền đối số cho hàm trong Python nhé!