Chương 3: Hàm

Trở về Mục lục cuốn sách

Việc gọi các hàm

Trong lập trình, một hàm là một nhóm được đặt tên gồm các câu lệnh nhằm thực hiện một nhiệm vụ tính toán cụ thể. Khi định nghĩa hàm, bạn chỉ định tên của nó và tiếp theo là loạt các câu lệnh. Sau này, bạn có thể “gọi” hàm theo tên của nó.

Ta đã gặp một ví dụ của việc gọi hàm:

>>> type(32) 
<type 'int'> 

Tên của hàm này là type. Biểu thức ở trong cặp ngoặc đơn được gọi là đối số của hàm. Kết quả của hàm này là kiểu của đối số.

Người ta thường nói một hàm “lấy” một đối số và “trả về” một giá trị. Giá trị này được gọi là giá trị trả về.

Các hàm chuyển đổi kiểu

Python cung cấp các hàm dựng sẵn giúp chuyển đổi một giá trị từ kiểu này sang kiểu khác. Hàm int lấy bất kì một giá trị nào và chuyển nó thành một số nguyên nếu có thể, còn nếu không được thì thông báo lỗi:

>>> int('32') 
32 
>>> int('Hello') 
ValueError: invalid literal for int(): Hello 

int có thể chuyển các giá trị số có phần thập phân sang số nguyên, nhưng nó không làm tròn mà chỉ cắt bỏ phần thập phân:

>>> int(3.99999) 
3 
>>> int(-2.3) 
-2 

float chuyển số nguyên và chuỗi sang số có phần thập phân:

>>> float(32) 
32.0 
>>> float('3.14159') 
3.14159 

Cuối cùng, str chuyển đối số của nó sang một chuỗi:

>>> str(32) 
'32' 
>>> str(3.14159) 
'3.14159' 

Các hàm toán học

Python có một module (mô-đun) toán cung cấp phần lớn các hàm toán học thông dụng. Một module là một file trong đó có tập hợp các hàm liên quan với nhau.

Để sử dụng được module, ta cần phải nhập nó bằng lệnh import:

>>> import math 

Câu lệnh này tạo ra một đối tượng module có tên là math. Nếu bạn in đối tượng module này, bạn sẽ nhận được thông tin về nó:

>>> print math 
<module 'math' from '/usr/lib/python2.5/lib-dynload/math.so'> 

Đối tượng module chứa các hàm và biến được định nghĩa trong module. Để truy cập một trong các hàm đó, bạn phải chỉ định tên của module và tên của hàm, cách nhau bởi một dấu chấm. Cách này được gọi là kí hiệu dấu chấm.

>>> ratio = signal_power / noise_power 
>>> decibels = 10 * math.log10(ratio) 
>>> radians = 0.7 
>>> height = math.sin(radians) 

Ví dụ thứ nhất thực hiện tính lô-ga cơ số 10 của tỉ lệ giữa tín hiệu và nhiễu. module math cũng cung cấp một hàm có tên là log có nhiệm vụ tính lô-ga cơ số e.

Ví dụ thứ hai tìm sin của radians. Ở đây tên của biến số gợi ý rằng sin và các hàm lượng giác khác (cos, tan, v.v.) nhận đối số có đơn vị radian. Để đổi từ độ sang radian, hãy chai cho 360 và nhân với :

>>> degrees = 45 
>>> radians = degrees / 360.0 * 2 * math.pi 
>>> math.sin(radians) 
0.707106781187 

Biểu thức math.pi lấy biến pi từ module math. Giá trị của biến này xấp xỉ với số π, với độ chính xác khoảng 15 chữ số.

Nếu bạn nắm vững lượng giác, bạn có thể kiểm tra lại kết quả trước bằng cách so sánh nó với căn bậc hai của hai chia đôi:

>>> math.sqrt(2) / 2.0 
0.707106781187 

Sự kết hợp

Cho đến giờ, chúng ta mới xét đến các thành phần tạo nên chương trình —biến, biểu thức, và câu lệnh—một cách riêng lẻ, mà chưa nói đến việc kết hợp chúng như thế nào.

Một trong những đặc điểm hữu ích của các ngôn ngữ lập trình là chúng cho lấy những thành phần nhỏ và kết hợp chúng lại. Chẳng hạn, đối số của một hàm có thể là bất cứ biểu thức nào, bao gồm cả các toán tử đại số:

x = math.sin(degrees / 360.0 * 2 * math.pi) 

Và thậm chí cả các hàm được gọi:

x = math.exp(math.log(x+1)) 

Hầu như bất kì chỗ nào bạn đặt được một giá trị, bạn cũng sẽ thay được vào đó một biểu thức, chỉ với một ngoại lệ: phía bên trái của một câu lệnh gán phải là một tên biến. Tất cả biểu thức nếu đặt ở bên phía trái đó sẽ phạm lỗi cú pháp1.

>>> minutes = hours * 60 # đúng 
>>> hours * 60 = minutes # sai! 
SyntaxError: can't assign to operator 

Thêm vào các hàm mới

Đến bây giờ, chúng ta mới chỉ dùng những hàm có sẵn trong Python, song thật ra có thể tạo ra những hàm mới. Một định nghĩa hàm bao gồm việc chỉ định tên của hàm mới và danh sách các câu lệnh cần được thực hiện khi hàm được gọi.

Sau đây là một ví dụ:

def print_lyrics():
    print "I'm a lumberjack, and I'm okay." 
    print "I sleep all night and I work all day." 

def là một từ khoá để khẳng định rằng đây là một định nghĩa làm. Tên của hàm là print_lyrics. Quy tắc đặt tên hàm cũng như đặt tên biến: chữ cái, số và dấu nối là hợp lệ nhưng kí tự đầu tiên không thể là số. Bạn không thể đặt tên hàm giống như một từ khoá, và cũng nên tránh đặt tên hàm và tên biến trùng nhau.

Tiếp theo tên hàm là cặp ngoặc đơn bên trong không có gì, điều đó có nghĩa là hàm này không lấy đối số nào.

Dòng đầu tiên của định nghĩa hàm được gọi là đoạn đầu; phần còn lại là phần thân. Phần đầu phải được kết thúc bởi dấu hai chấm và phần thân phải được viết thụt đầu dòng. Theo quy ước, khoảng cách thụt vào luôn là bốn dấu cách (xem Mục [Trình soạn thảo]). Phần thân có thể chứa bao nhiêu câu lệnh cũng được.

Các chuỗi trong câu lệnh print được viết trong cặp dấu nháy kép. Cặp dấu nháy đơn và nháy kép có tác dụng như nhau; người ta thường dùng cặp nháy đơn trừ những trường hợp như sau khi có một dấu nháy đơn xuất hiện trong chuỗi.

Nếu bạn gõ định nghĩa hàm vào ở chế độ tương tác, trình thông dịch sẽ in ra các dấu ba chấm (...) nhằm cho bạn biết rằng việc định nghĩa hàm chưa hoàn thành:

>>> def print_lyrics(): 
... print "I'm a lumberjack, and I'm okay." 
... print "I sleep all night and I work all day." 
... 

Để kết thúc hàm, bạn phải gõ thêm một dòng trống (điều này không cần thiết trong một văn lệnh).

Việc định nghĩa hàm sẽ tạo ra một biến có cùng tên.

>>> print print_lyrics 
<function print_lyrics at 0xb7e99e9c> 
>>> print type(print_lyrics) 
<type 'function'> 

Giá trị của print_lyrics là một đối tượng hàm; nó có kiểu 'function'.

Cú pháp của lời gọi hàm mới cũng giống như với các hàm dựng sẵn:

>>> print_lyrics() 
I'm a lumberjack, and I'm okay. 
I sleep all night and I work all day. 

Một khi bạn đã định nghĩa hàm, bạn có thể dùng nó trong một hàm khác. Chẳng hạn, để lặp lại điệp khúc vừa rồi, ta có thể viết một hàm có tên là repeat_lyrics:

def repeat_lyrics(): 
    print_lyrics() 
    print_lyrics() 

Và sau đó gọi repeat_lyrics:

>>> repeat_lyrics() 
I'm a lumberjack, and I'm okay. 
I sleep all night and I work all day. 
I'm a lumberjack, and I'm okay. 
I sleep all night and I work all day. 

Song đó không phải là cách viết một bài hát theo đúng nghĩa.

Định nghĩa và sử dụng

Lấy lại những đoạn câu lệnh từ mục trước, ta được toàn bộ chương trình sau:

def print_lyrics(): 
    print "I'm a lumberjack, and I'm okay." 
    print "I sleep all night and I work all day." 

def repeat_lyrics(): 
    print_lyrics() 
    print_lyrics() 

repeat_lyrics() 

Chương trình này bao gồm hai định nghĩa hàm: print_lyricsrepeat_lyrics. Các định nghĩa hàm được thực hiện cũng giống như với các câu lệnh khác, nhưng tác dụng của chúng là tạo ra những đối tượng hàm. Các câu lệnh bên trong hàm không được thực hiện cho đến tận khi hàm được gọi, hơn nữa định nghĩa hàm cũng không tạo ra kết quả.

Bạn có thể nghĩ rằng cần phải tạo ra một hàm trước khi có thể thực hiện nó, và quả đúng như vậy. Nói cách khác, định nghĩa hàm phải được thực hiện trước lần gọi hàm đầu tiên.

Chuyển dòng lệnh cuối cùng của chương trình vừa rồi lên trên cùng, để cho lời gọi hàm xuất hiện trước định nghĩa hàm. Chạy chương trình để xem bạn nhận được thông báo lỗi gì.

Chuyển lời gọi hàm trở lại cuối cùng và chuyển định nghĩa hàm print_lyrics xuống dưới định nghĩa hàm repeat_lyrics. Lần này khi chạy chương trình, điều gì sẽ xảy ra?

Luồng thực hiện chương trình

Để đảm bảo chắc chắn rằng một hàm đã được định nghĩa trước lần sử dụng đầu tiên, bạn phải biết thứ tự thực hiện các câu lệnh, còn gọi là luồng thực hiện chương trình.

Việc thực hiện luôn được bắt đầu với câu lệnh thứ nhất của chương trình. Các câu lệnh được thực hiện lần lượt từ trên xuống.

Các định nghĩa hàm không làm thay đổi luồng thực hiện chương trình, nhưng cần nhớ rằng các câu lệnh bên trong của hàm không được thực hiện cho đến tận lúc hàm được gọi.

Mỗi lần gọi hàm là một lần rẽ ngoặt luồng thực hiện. Thay vì chuyển sang câu lệnh kế tiếp, luồng sẽ nhảy tới phần thân của hàm, thực hiện tất cả những câu lệnh ở trong đó, rồi trở lại tiếp tục thực hiện từ điểm mà nó vừa rời đi.

Điều này nghe có vẻ đơn giản, nhưng sẽ khác đi nếu bạn nhận thấy rằng một hàm có thể gọi hàm khác. Khi ở trong phần thân của một hàm, chương trình có thể phải thực hiện những câu lệnh ở trong phần thân của một hàm khác. Nhưng khi đang thực hiện hàm mới đó, chương trình còn phải thực hiện một hàm khác nữa!

May mắn là Python rất giỏi theo dõi vị trí thực hiện của chương trình, vì vậy mỗi khi một hàm được thực hiện xong, chương trình sẽ trở về chỗ mà nó đã rời đi từ hàm gọi ban đầu. Khi trở về cuối chương trình, việc thực hiện kết thúc.

Vậy ý nghĩa của câu chuyện này là gì? Khi đọc một chương trình, bạn không nhất thiết phải đọc từ trên xuống dưới. Đôi khi việc dò theo luồng thực hiện của chương trình sẽ có lý hơn.

Tham số và đối số

Một số các hàm dựng sẵn mà ta đã gặp có yêu cầu đối số. Chẳng hạn, khi gọi hàm math.sin bạn cần nhập vào một đối số. Một số hàm còn lấy hơn một đối số: math.pow lấy hai đối số là cơ số và số mũ.

Bên trong hàm, các đối số được gán cho các biến được gọi là tham số. Sau đây là ví dụ về một hàm do người dùng định nghĩa; hàm này lấy một đối số:

def print_twice(bruce): 
    print bruce 
    print bruce 

Hàm này gán một đối số cho một tham số có tên là bruce. Khi hàm được gọi, nó in ra giá trị của tham số hai lần (bất kể tham số là gì).

Hàm này hoạt động được với bất kì giá trị nào có thể in được.

>>> print_twice('Spam') 
Spam 
Spam 
>>> print_twice(17) 
17 
17 
>>> print_twice(math.pi) 
3.14159265359 
3.14159265359 

Quy tắc áp dụng cho các hàm dựng sẵn cũng có thể áp dụng được cho các hàm người dùng tạo ra, vì vậy ta có thể dùng bất kì loại biểu thức nào làm đối số cho print_twice:

>>> print_twice('Spam '*4) Spam Spam Spam Spam Spam Spam Spam Spam >>> print_twice(math.cos(math.pi)) -1.0 -1.0 

Đối số được ước lượng trước khi hàm số được gọi, vì vậy trong các ví dụ, các biểu thức 'Spam '*4math.cos(math.pi) đều chỉ được ước lượng một lần.

Bạn cũng có thể dùng một biến cho một đối số:

>>> michael = 'Eric, the half a bee.' 
>>> print_twice(michael) 
Eric, the half a bee. 
Eric, the half a bee. 

Tên của biến được đưa vào như đối số (michael) không có liên quan gì đến tân của tham số (bruce). Giá trị nào được gọi về (ở đoạn chương trình gọi) cũng không quan trọng; ở đây trong print_twice, chúng ta đều gọi mọi người với tên bruce.

Các biến và tham số đều có tính địa phương

Khi tạo ra một biến ở trong hàm, nó mang tính địa phương, theo nghĩa rằng nó chỉ tồn tại bên trong hàm số. Chẳng hạn:

def cat_twice(part1, part2): 
    cat = part1 + part2 
    print_twice(cat) 

Hàm này nhận hai đối số, nối chúng lại, và sau đó in ra kết quả hai lần. Sau đây là một ví dụ sử dụng hàm:

>>> line1 = 'Bing tiddle ' 
>>> line2 = 'tiddle bang.' 
>>> cat_twice(line1, line2) 
Bing tiddle tiddle bang. 
Bing tiddle tiddle bang. 

Khi cat_twice kết thúc, biến cat bị huỷ bỏ. Nếu cố gắng in nó, ta sẽ nhận được một biệt lệ:

>>> print cat 
NameError: name 'cat' is not defined 

Các tham số cũng có tính địa phương. Chẳng hạn, bên ngoài print_twice, không có thứ gì được gọi là bruce cả.

Biểu đồ ngăn xếp

Để theo dõi xem những biến nào được sử dụng ở đâu, đôi khi sẽ tiện lợi nếu ta vẽ một biểu đồ ngăn xếp. Cũng như biểu đồ trạng thái, biểu đồ ngăn xếp cho thấy giá trị của từng biến, đồng thời cho thấy hàm mà mỗi biến thuộc về.

Mỗi hàm đều được biểu diễn bởi một khung. Khung là một hình chữ nhật, có tên của hàm số ghi bên cạnh, cùng với các tham số và biến số của hàm được ghi trong đó. Biểu đồ ngăn xếp cho ví dụ trước có dạng như sau:

stack

Các khung được bố trí trong một ngăn xếp cùng với chỉ định hàm nào gọi những hàm nào, và cứ như vậy. Ở ví dụ này, print_twice được gọi bởi cat_twice, và cat_twice được gọi bởi __main__, vốn là một tên đặc biệt dành cho khung cấp cao nhất. Khi bạn tạo ra một biến không nằm trong bất cứ hàm nào, thì nó sẽ nằm trong __main__.

Mỗi tham số tham chiếu đến giá trị tương ứng với đối số của nó. Do vậy, part1 có cùng giá trị với line1, part2 có cùng giá trị với line2, và bruce có cùng giá trị với cat.

Nếu có một lỗi xảy ra trong quá trình gọi hàm, Python sẽ in ra tên của hàm, cùng với tên của hàm số gọi hàm trước đó, và cứ như vậy cho đến khi trở về __main__.

Chẳng hạn, nếu bạn cố gắng truy cập cat từ bên trong print_twice, bạn sẽ nhận được một thông báo lỗi NameError:

Traceback (innermost last): 
  File "test.py", line 13, in __main__ 
    cat_twice(line1, line2) 
  File "test.py", line 5, in cat_twice 
    print_twice(cat) 
  File "test.py", line 9, in print_twice 
    print cat 
NameError: name 'cat' is not defined

Danh sách các hàm như vậy có tên là dò ngược. Nó cho bạn biết file chương trình nào có chứa lỗi, và dòng lệnh nào cũng như những hàm nào được thực hiện lúc bấy giờ. Nó cũng cho biết dòng lệnh gây ra lỗi.

Thứ tự của các hàm trong dò ngược cũng giống như thứ tự của các khung trong sơ đồ ngăn xếp. Hàm số đang được chạy có vị trí dưới cùng.

Các hàm có và không trả lại kết quả

Một số hàm mà chúng ta dùng, như các hàm toán học, đều cho ra kết quả; ta gọi nôm na là hàm trả lại kết quả. Các hàm khác, như print_twice, thực hiện một hành động, nhưng không trả lại kết quả nào. Chúng được gọi là hàm không kết quả.

Khi bạn gọi hàm trả kết quả, thường thì bạn muốn thực hiện thao tác với kết quả thu được; chẳng hạn, bạn muốn gán nó cho một biến hoặc dùng nó như một phần của biểu thức:

x = math.cos(radians) 
golden = (math.sqrt(5) + 1) / 2 

Khi bạn gọi một hàm từ chế độ tương tác, Python hiển thị kết quả:

>>> math.sqrt(5) 
2.2360679774997898 

Nhưng trong một văn lệnh, nếu bạn gọi một hàm trả kết quả, thì giá trị kết quả này sẽ vĩnh viễn mất đi!

math.sqrt(5) 

Văn lệnh này tính giá trị căn bậc hai của 5, nhưng vì nó không ghi lại và cũng chẳng hiển thị kết quả, nên nó không có tác dụng gì.

Các hàm không kết quả có thể hiển thị thứ gì đó trên màn hình hoặc có những hiệu ứng khác, nhưng chúng không có giá trị được trả về. Nếu bạn cố gắng gán kết quả vào một biến, bạn sẽ được một giá trị đặc biệt gọi là None.

>>> result = print_twice('Bing') 
Bing Bing 
>>> print result 
None 

Giá trị None không phải là chuỗi 'None'. Nó là một giá trị đặc biệt và có dạng riêng của mình:

>>> print type(None) 
<type 'NoneType'> 

Các hàm ta đã viết cho đến giờ đều thuộc loại không kết quả. Ta sẽ bắt đầu viết các hàm trả lại kết quả kể từ những chương tiếp sau.

Tại sao lại cần có hàm?

Có thể sẽ không rõ rằng tại sao ta phải cất công chia nhỏ chương trình thành các hàm. Có một số lí do cho điều đó:

  • Việc tạo ra một hàm mới sẽ giúp bạn có khả năng đặt tên cho một nhóm các câu lệnh, từ đó làm cho chương trình dễ đọc và gỡ lỗi hơn.
  • Các hàm có thể thu gọn một chương trình bằng cách loại bỏ những đoạn mã lệnh trùng lặp. Sau này, nếu bạn sửa đổi chương trình, thì chỉ cần thực hiện sửa ở một chỗ.
  • Việc chia một chương trình dài thành những hàm cho phép ta gỡ lỗi từng phần một và sau đó kết hợp lại để được một chương trình tổng thể hoạt động được.
  • Các hàm được thiết kế tốt sẽ hữu dụng với nhiều chương trình. Một khi bạn viết ra một hàm và gỡ lỗi xong xuôi, bạn có dùng lại nó.

Gỡ lỗi

Nếu bạn dùng một trình soạn thảo văn bản để viết các văn lệnh, bạn có thể gặp rắc rối liên quan đến các dấu trống và dấu tab. Cách tốt nhất để tránh những vấn đề kiểu này là chỉ dùng dấu trống (không có dấu tab). Hầu hết các trình soạn thảo nhận biết được Python đều thực hiện điều đó một cách mặc định, tuy nhiên một số trình soạn thảo lại không thể.

Các dấu tab và dấu trống thông thường đều vô hình, điều đó làm cho việc gỡ lỗi khó khăn hơn. Vì vậy, bạn hãy tìm một trình soạn thảo có khả năng xử lý tốt vấn đề thụt đầu dòng.

Và cũng đừng quên lưu lại chương trình trước khi chạy nó. Một số phần mềm môi trường phát triển tự động làm việc này, nhưng số khác thì không. Ở trường hợp thứ hai, chương trình mà bạn nhìn thấy ở trên cửa sổ soạn thảo sẽ khác với chương trình được chạy.

Việc gỡ lỗi có thể tốn nhiều thời gian nếu bạn cứ tiếp tục chạy đi chạy lại chương trình không đúng.

Cần đảm bảo chắc rằng mã lệnh bạn đang nhìn thấy chính là mã lệnh được chạy. Nếu bạn không chắc chắn, hãy đặt một câu lệnh như print 'hello' ở đầu chương trình và chạy lại. Nếu không thể thấy chữ hello thì bạn không chạy đúng chương trình cần được chạy!

Thuật ngữ

hàm:
Chuỗi các câu lệnh được đặt tên; nhằm thực hiện một thao tác có ích nhất định. Các hàm có hoặc không nhận tham số và có thể có hoặc không cho ra kết quả.
định nghĩa hàm:
Câu lệnh nhằm tạo ra một hàm, đặt tên cho nó, chỉ định các tham số và các lệnh cần được thực hiện.
đối tượng hàm:
Giá trị được tạo ra bởi định nghĩa hàm. Tên của hàm là một biến tham chiếu đến một đối tượng hàm.
đoạn đầu:
Dòng đầu tiên trong định nghĩa hàm.
phần thân:
Chuỗi các câu lệnh bên trong định nghĩa hàm.
tham số:
Tên được dùng bên trong của hàm để tham chiếu đến giá trị được chuyển dưới dạng đối số.
lời gọi hàm:
Câu lệnh nhằm thực hiện một hàm. Câu lệnh này bao gồm tên hàm, theo sau là danh sách các đối số.
đối số:
Giá trị được cung cấp cho hàm khi hàm được gọi. Giá trị này được gán cho tham số tương ứng trong hàm.
biến địa phương:
Biến được định nghĩa bên trong hàm. Một biến địa phương chỉ có thể được dùng bên trong hàm đó.
giá trị được trả về:
Kết quả của hàm. Nếu một lời gọi hàm được dùng như một biểu thức thì giá trị được trả về chính là giá trị của biểu thức đó.
hàm có kết quả:
Hàm có trả lại kết quả.
hàm không kết quả:
Hàm không trả lại kết quả.
module:
File có chứa một tập hợp các hàm có liên hệ với nhau đồng thời có thể gồm các định nghĩa khác.
câu lệnh import:
Câu lệnh dùng để đọc một file module và tạo ra một đối tượng module.
đối tượng module:
Giá trị được tạo ra bởi một câu lệnh import để cho phép truy cập đến các giá trị được định nghĩa trong module đó.
kí hiệu dấu chấm:
Cú pháp để gọi một hàm trong module khác, bằng cách chỉ định tên của module theo sau là một dấu chấm và tên của hàm.
kết hợp:
Việc dùng một biểu thức như là một phần của biểu thức lớn hơn, hay một câu lệnh như là một phần của câu lệnh lớn hơn.
luồng thực hiện:
Thứ tự mà theo đó các câu lệnh được thực hiện khi chạy chương trình.
biểu đồ ngăn xếp:
Cách biểu diễn bằng hình vẽ cho một loạt các hàm chồng xếp lên nhau, trong đó có chỉ ra các biến của chúng và các giá trị mà chúng tham chiếu đến.
khung:
Hình chữ nhật trong biểu đồ ngăn xếp dùng để biểu diễn lời gọi hàm. Nó bao gồm các biến địa phương và các tham số của hàm.
dò ngược:
Danh sách các hàm đang được thực hiện, được biểu thị khi có biệt lệ xảy ra.

Bài tập

Python có một hàm dựng sẵn, có tên là len để trả lại độ dài của một chuỗi kí tự, chẳng hạn giá trị của len('allen') là 5.

Hãy viết một hàm tên là right_justify để đọc vào một chuỗi tên là s như một tham số và in ra chuỗi bắt đầu với các kí tự trống; số kí tự trống vừa đủ để cho kí tự cuối cùng của chuỗi nằm tại cột thứ 70 trên màn hình.

>>> right_justify('allen') 
                                                                 allen

Một đối tượng hàm là một giá trị mà bạn có thể gán vào một biến hoặc chuyển dưới dạng một tham số. Chẳng hạn, do_twice là một hàm nhận vào một đối tượng hàm như một tham số và thực hiện hàm tham số này hai lần:

def do_twice(f): 
    f() 
    f() 

Sau đây là một ví dụ có sử dụng do_twice để gọi một hàm tên là print_spam hai lần.

def print_spam(): 
    print 'spam' 

do_twice(print_spam) 
  1. Hãy gõ một văn lệnh thực hiện ví dụ này và chạy kiểm tra.
  2. Sửa đổi do_twice sao cho nó nhận vào hai đối số— một đối tượng hàm và một giá trị—và gọi hàm hai lần, trong đó có chuyển giá trị như một đối số.
  3. Viết một dạng tổng quát hơn cho print_spam, đặt tên là print_twice, trong đó nhận một chuỗi như tham số và in nó hai lần.
  4. Dùng dạng đã chỉnh sửa của do_twice để gọi print_twice hai lần, trong đó có chuyển 'spam' như một tham số.
  5. Định nghĩa một hàm mới có tên do_four nhận vào một đối tượng hàm và một giá trị, sau đó gọi hàm bốn lần, với giá trị đóng vai trò tham biến. Trong phần thân của hàm được định nghĩa chỉ dùng hai câu lệnh chứ không phải là bốn.

Bạn có thể xem cách giải của tôi ở thinkpython.com/code/do_four.py.

Bài tập này2 có thể được giải bằng những câu lệnh và các đặc điểm khác của ngôn ngữ mà chúng ta đã được biết đến giờ.

  1. Hãy viết một hàm để vẽ ô lưới giống như hình sau đây:
    + - - - - + - - - - + 
    |         |         | 
    |         |         | 
    |         |         | 
    |         |         | 
    + - - - - + - - - - + 
    |         |         | 
    |         |         | 
    |         |         | 
    |         |         | 
    + - - - - + - - - - +

    Gợi ý: để in ra nhiều giá trị trên cùng một dòng, hãy dùng dấu phẩy để phân cách các chuỗi:

    print '+', '-' 

    Nếu lệnh print kết thúc bằng dấu phẩy, Python sẽ tạm dừng in tại dòng hiện tại, và những giá trị tiếp theo được in sẽ nằm trên cùng dòng đó.

    print '+', 
    print '-' 

    Kết quả của hai lệnh trên là '+ -'.

    Một câu lệnh print tự bản thân nó kết thúc dòng hiện tại và chuyển đến dòng tiếp theo.

  2. Hãy dùng hàm vừa định nghĩa để vẽ một lưới tương tự nhưng gồm bốn hàng và bốn cột.

Bạn có thể xem cách giải của tôi ở thinkpython.com/code/grid.py.


  1. Sau này chúng ta sẽ xét thêm những ngoại lệ của quy tắc này.
  2. Dựa theo một bài tập của Oualline, {Practical C Programming, Third Edition}, O’Reilly (1997)

4 bình luận

Filed under Sách, Think Python

4 responses to “Chương 3: Hàm

  1. Pingback: Think Python: Cách tư duy như nhà khoa học máy tính | Blog của Chiến

  2. Pingback: Phụ lục: Lumpy | Blog của Chiến

  3. Chào anh, em không truy cập vào link để xem lời giải được, liệu có link khác không ạ? em cảm ơn ạ.

Bình luận về bài viết này