Ngoài câu lệnh while vừa giới thiệu, Python có các câu lệnh điều khiển luồng từ các ngôn ngữ khác, với chút sửa đổi.
Có lẽ loại câu lệnh biết đến nhiều nhất là câu lệnh if . Ví dụ:
>>> x = int(raw_input("Please enter an integer: ")) >>> if x < 0: ... x = 0 ... print 'Negative changed to zero' ... elif x == 0: ... print 'Zero' ... elif x == 1: ... print 'Single' ... else: ... print 'More' ...
Có thể không có hoặc có nhiều phần elif , và phần else là không bắt buộc. Từ khóa `elif' là viết tắt của `else if', và dùng để tránh thụt vào quá nhiều. Dãy if ... elif ... elif ... dùng thay cho câu lệnh switch hay case tìm thấy trong các ngôn ngữ khác.
for trong Python khác một chút với C hoặc Pascal. Thay vì lặp qua một dãy số (như trong Pascal), hoặc cho phép người dùng tự định nghĩa bước lặp và điều kiện dừng (như C), câu lệnh for của Python lặp qua các phần tử của một dãy bất kỳ (một danh sách, hoặc một chuỗi), theo thứ tự mà chúng xuất hiện trong dãy. Ví dụ:
>>> # Measure some strings: ... a = ['cat', 'window', 'defenestrate'] >>> for x in a: ... print x, len(x) ... cat 3 window 6 defenestrate 12
Rất nguy hiểm nếu bạn sửa đổi dãy trong khi bạn đang lặp qua nó. Nếu bạn cần sửa đổi một danh sách khi đang lặp (ví dụ như để nhân đôi các phần tử nào đó) bạn sẽ cần phải lặp qua một bản sao của nó. Cách viết cắt miếng làm cho việc này đơn giản:
>>> for x in a[:]: # make a slice copy of the entire list ... if len(x) > 6: a.insert(0, x) ... >>> a ['defenestrate', 'cat', 'window', 'defenestrate']
Nếu bạn cần lặp qua một dãy số, hàm có sẵn range() trở nên tiện dụng. Nó tạo ra danh sách chứa các dãy số học:
>>> range(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Điềm dừng được chỉ định không bao giờ là một phần của danh sách tạo ra;
range(10)
tạo danh sách 10 giá trị, là những chỉ mục hợp lệ cho các phần tử của một dãy có độ dài 10. Bạn cũng có thể tạo dãy bắt đầu từ một số khác, hoặc chỉ rõ mức tiến khác (ngay cả mức lùi; đôi khi nó còn được gọi là `bước', `step'):
>>> range(5, 10) [5, 6, 7, 8, 9] >>> range(0, 10, 3) [0, 3, 6, 9] >>> range(-10, -100, -30) [-10, -40, -70]
Để lặp qua các chỉ mục của một dãy, gộp range() và len() như sau:
>>> a = ['Mary', 'had', 'a', 'little', 'lamb'] >>> for i in range(len(a)): ... print i, a[i] ... 0 Mary 1 had 2 a 3 little 4 lamb
break , như trong C, nhảy ra khỏi phạm vòng lặp for hay while nhỏ nhất chứa nó.
continue , cũng được mượn từ C, tiếp tục lần lặp kế của vòng lặp.
Các câu lệnh lặp có thể có vế else
; nó được thực thi khi vòng lặp kết thúc vì danh sách lặp đã cạn (với
for) hoặc khi điều kiện là sai (với
while), và không được thực thi khi vòng lặp kết thúc bởi câu lệnh
break . Ví dụ vòng lặp sau tìm các số nguyên tố:
>>> for n in range(2, 10): ... for x in range(2, n): ... if n % x == 0: ... print n, 'equals', x, '*', n/x ... break ... else: ... # loop fell through without finding a factor ... print n, 'is a prime number' ... 2 is a prime number 3 is a prime number 4 equals 2 * 2 5 is a prime number 6 equals 2 * 3 7 is a prime number 8 equals 2 * 4 9 equals 3 * 3
pass không làm gì cả. Nó có thể được dùng khi cú pháp cần một câu lệnh nhưng chương trình không cần tác vụ nào. Ví dụ:
>>> while True: ... pass # Busy-wait for keyboard interrupt ...
Chúng ta có thể tạo một hàm in ra dãy Fibonacci:
>>> def fib(n): # write Fibonacci series up to n ... """Print a Fibonacci series up to n.""" ... a, b = 0, 1 ... while b < n: ... print b, ... a, b = b, a+b ... >>> # Now call the function we just defined: ... fib(2000) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
Từ khóa def khai báo một định nghĩa hàm . Nó phải được theo sau bởi tên hàm, và một danh sách các thông số chính quy trong ngoặc đơn. Các câu lệnh tạo nên thân hàm bắt đầu từ dòng kế tiếp, và bắt buộc phải được thụt vào. Câu lệnh đầu tiên của thân hàm có thể là một chuỗi; chuỗi này là chuỗi tài liệu, hoặc docstringcủa hàm.
Có những công cụ sử dụng docstrings để tự động sinh tài liệu trực tuyến hoặc để in, hoặc cho phép người dùng duyệt mã một cách tương tác; việc thêm docstrings vào mã rất được khuyến khích, cho nên bạn hãy tạo thói quen tốt đó cho mình.
Việc thực thi một hàm tạo ra một bảng ký hiệu mới dùng cho các biến cục bộ của hàm. Chính xác hơn, mọi phép gán biến trong một hàm chứa giá trị vào bảng ký hiệu cục bộ; và các tham chiếu biến sẽ trước hết tìm trong bảng ký hiệu cục bộ rồi trong bảng ký hiệu toàn cục, và trong bảng các tên có sẵn. Do đó, các biến toàn cục không thể được gán giá trị trực tiếp trong một hàm (trừ khi được đặt trong câu lệnh global ), mặc dù chúng có thể dược tham chiếu tới.
Thông số thật sự của một lệnh gọi hàm được tạo ra trong bảng ký hiệu cục bộ của hàm được gọi khi nó được gọi; do đó các thông số được truyền theo truyền theo giá trị (call by value) (mà giá trị luôn là một tham chiếu đối tượng, không phải là giá trị của đối tượng).4.1 Khi một hàm gọi một hàm khác, một bảng ký hiệu cục bộ được tạo ra cho lệnh gọi đó.
Một định nghĩa hàm tạo tên hàm trong bảng ký hiệu hiện tại. Giá trị của tên hàm có một kiểu được nhận ra bởi trình thông dịch là hàm do người dùng định nghĩa. Giá trị này có thể được gán vào một tên khác và sau đó có thể được sử dụng như một hàm. Đây là một cách đổi tên tổng quát:
>>> fib <function fib at 10042ed0> >>> f = fib >>> f(100) 1 1 2 3 5 8 13 21 34 55 89
Bạn có thể nói rằng fib
không phải là một hàm (function) mà là một thủ tục (procedure). Trong Python, cũng như C, thủ tục chẳng qua là hàm không có giá trị trả về. Thật sự, nói rõ hơn một chút, thủ tục cũng trả về giá trị mặc dù là một giá trị vô nghĩa. Giá trị này được gọi là None
(nó là một tên có sẵn). In ra giá trị None
thường bị trình thông dịch bỏ qua nếu nó là giá trị duy nhất được in ra. Bạn có thể thấy nó nếu bạn muốn:
>>> print fib(0) None
Bạn cũng có thể dễ dàng viết một hàm trả về một danh sách các số của dãy Fibonacci thay vì in nó ra:
>>> def fib2(n): # return Fibonacci series up to n ... """Return a list containing the Fibonacci series up to n.""" ... result = [] ... a, b = 0, 1 ... while b < n: ... result.append(b) # see below ... a, b = b, a+b ... return result ... >>> f100 = fib2(100) # call it >>> f100 # write the result [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
Ví dụ này cho thấy một vài tính năng mới của Python:
None
. Rớt ra khỏi một thủ tục cũng trả về None
của hàm.
result.append(b)
gọi một phương thức của đối tượng danh sách. result
. Một phương thức là một hàm `thuộc về' một đối tượng và có tên obj.methodname
, với obj
là một đối tượng nào đó (có thể là một biểu thức), và methodname
là tên của một phương thức được định nghĩa bởi kiểu của đối tượng. Các kiểu khác nhau định nghĩa các phương thức khác nhau. Phương thức của các kiểu khác nhau có thể có cùng tên mà không dẫn đến sự khó hiểu. (Bạn có thể định nghĩa kiểu đối tượng và phương thức cho riêng bạn, dùng lớp, như sẽ được bàn đến ở các chương sau.) Phương thức append() dùng trong ví dụ này được định nghĩa cho các đối tượng danh sách; nó thêm một phần tử mới vào cuối danh sách. Trong ví dụ này, nó tương đương với "result = result + [b]", nhưng hiệu quả hơn.
Bạn cũng có thể định nghĩa các hàm với số lượng thông số thay đổi. Có ba dạng, và chúng có thể được dùng chung với nhau.
Dạng hữu dụng nhất là để chỉ định một giá trị mặc định cho một hoặc nhiều thông số. Dạng này tạo một hàm có thể được gọi với ít thông số hơn là nó được định nghĩa để nhận. Ví dụ:
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'): while True: ok = raw_input(prompt) if ok in ('y', 'ye', 'yes'): return True if ok in ('n', 'no', 'nop', 'nope'): return False retries = retries - 1 if retries < 0: raise IOError, 'refusenik user' print complaint
Hàm này có thể được gọi như sau:
ask_ok('Do you really want to quit?')
hoặc như sau:
ask_ok('OK to overwrite the file?', 2)
của hàm.
Ví dụ này giới thiệu từ khóa in . Nó kiểm tra xem một dãy có chứa một giá trị nào đó không.
Các giá trị mặc định được định giá tại nơi hàm được định nghĩa trong phạm vi định nghĩa (defining scope) , do đó
i = 5 def f(arg=i): print arg i = 6 f()
sẽ in 5
của hàm.
Cảnh báo quan trọng: Giá trị mặc định chỉ được định giá một lần. Điểm này quan trọng khi mặc định là một giá trị khả biến như danh sách, từ điển hoặc các đối tượng của hầu hết mọi lớp. Ví dụ, hàm sau gộp các thông số truyền vào nó từ các lời gọi sau đó:
def f(a, L=[]): L.append(a) return L print f(1) print f(2) print f(3)
Sẽ in ra
[1] [1, 2] [1, 2, 3]
Nếu bạn không muốn mặc định được dùng chung trong các lời gọi sau, bạn có thể viết một hàm như thế này:
def f(a, L=None): if L is None: L = [] L.append(a) return L
Các hàm cũng có thể được gọi theo thông số từ khóa (keyword argument) theo dạng "keyword = value". Ví dụ, hàm sau:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'): print "-- This parrot wouldn't", action, print "if you put", voltage, "volts through it." print "-- Lovely plumage, the", type print "-- It's", state, "!"
có thể được gọi theo bất kỳ cách nào:
parrot(1000) parrot(action = 'VOOOOOM', voltage = 1000000) parrot('a thousand', state = 'pushing up the daisies') parrot('a million', 'bereft of life', 'jump')
nhưng những lời gọi sau đều không hợp lệ:
parrot() # required argument missing parrot(voltage=5.0, 'dead') # non-keyword argument following keyword parrot(110, voltage=220) # duplicate value for argument parrot(actor='John Cleese') # unknown keyword
Nói chung, một danh sách thông số phải có bất kỳ thông số vị trí (positional argument) theo sau bởi bất kỳ thông số từ khóa, các từ khóa phải được chọn từ tên thông số chính quy. Các thông số chính quy không nhất thiết phải có giá trị mặc định. Không thông số nào có thể nhận một giá trị nhiều hơn một lần -- tên thông số chính quy tương ứng với thông số vị trí không thể được dùng làm từ khóa trong cùng một lời gọi. Sau đây là một ví dụ sai vì giới hạn này:
>>> def function(a): ... pass ... >>> function(0, a=0) Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: function() got multiple values for keyword argument 'a'
Khi thông số chính quy cuối có dạng **name
, nó nhận một từ điển (dictionary)
chứa tất cả các thông số từ khóa trừ những từ khóa tương ứng với thông số chính quy. Điểm này có thể được dùng chung với một thông số chính quy ở dạng
*name
(bàn đến trong mục con sau) và nhận một bộ (tuple) chứa các thông số vị trí sau danh sách thông số chính quy. (*name
bắt buộc phải xuất hiện trước **name
.) Ví dụ, nếu ta định nghĩa một hàm như sau:
def cheeseshop(kind, *arguments, **keywords): print "-- Do you have any", kind, '?' print "-- I'm sorry, we're all out of", kind for arg in arguments: print arg print '-'*40 keys = keywords.keys() keys.sort() for kw in keys: print kw, ':', keywords[kw]
Nó có thể được gọi như vậy:
cheeseshop('Limburger', "It's very runny, sir.", "It's really very, VERY runny, sir.", client='John Cleese', shopkeeper='Michael Palin', sketch='Cheese Shop Sketch')
và dĩ nhiên nó sẽ in ra:
-- Do you have any Limburger ? -- I'm sorry, we're all out of Limburger It's very runny, sir. It's really very, VERY runny, sir. ---------------------------------------- client : John Cleese shopkeeper : Michael Palin sketch : Cheese Shop Sketch
Lưu ý rằng phương thức sort() của danh sách các tên thông số từ khóa được gọi trước khi in nội dung của từ điển keywords
; nếu điều này không được thực hiện, thứ tự các thông số được in ra không xác định.
Cuối cùng, một lựa chọn ít dùng nhất để chỉ định rằng một hàm có thể được gọi với bất kỳ số thông số. Các thông số này sẽ được gói và trong một bộ. Trước các thông số không xác định, không hoặc nhiều hơn các thông số chính quy có thể có mặt.
def fprintf(file, format, *args): file.write(format % args)
Trường hợp ngược xảy ra khi các thông số đã nằm trong một danh sách hoặc một bộ nhưng cần được tháo ra cho lời gọi hàm cần những thông số vị trí riêng. Ví dụ, hàm có sẵn range()
cần nhận các thông số riêng start và stop . Nếu chúng không được cung cấp riêng lẻ, viết lệnh gọi hàm với toán tử
*
để tháo các thông số này ra khỏi một danh sách hoặc bộ:
>>> range(3, 6) # normal call with separate arguments [3, 4, 5] >>> args = [3, 6] >>> range(*args) # call with arguments unpacked from a list [3, 4, 5]
Theo cùng một kiểu, từ điển có thể cung cấp các thông số từ khóa với toán tử
**
:
>>> def parrot(voltage, state='a stiff', action='voom'): ... print "-- This parrot wouldn't", action, ... print "if you put", voltage, "volts through it.", ... print "E's", state, "!" ... >>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"} >>> parrot(**d) -- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
Theo yêu cầu chung, một vài tính năng thường thấy trong các ngôn ngữ lập trình hàm như Lisp đã được thêm vào Python. Với từ khóa lambda , các hàm vô danh (anonymous function) có thể được tạo ra. Đây là một hàm trả vè tổng của hai thông số: "lambda a, b: a+b". Dạng lambda có thể được dùng ở bất kỳ nơi nào cần đối tượng hàm. Cú pháp của chúng giới hạn ở một biểu duy nhất. Về ý nghĩa, chúng chỉ là một cách viết gọn của một định nghĩa hàm bình thường. Giống như các định nghĩa hàm lồng nhau, dạng lambda có thể tham chiếu các biến từ phạm vi chứa nó:
>>> def make_incrementor(n): ... return lambda x: x + n ... >>> f = make_incrementor(42) >>> f(0) 42 >>> f(1) 43
Có những quy luật đang hình thành về nội dung và định dạng của các chuỗi tài liệu.
Dòng đầu tiên cần phải luôn luôn là một tóm tắt ngắn, xúc tích về mục đích của đối tượng. Để dễ hiểu, nó không nên chỉ ra cụ thể tên hoặc kiểu của đối tượng vì chúng có thể có ở hình thức khác (từ khi tên là một động từ diễn tả hoạt động của hàm). Dòng này cần bắt đầu bằng một chữ hoa và kết thúc bằng một dấu chấm.
Nếu có nhiều dòng trong chuỗi tài liệu, dòng thứ hai nên là một dòng trống, rõ ràng phân biệt tóm tắt và phần còn lại. Các dòng sau nên là một hoặc nhiều đoạn hướng dẫn về cách gọi, các hiệu ứng phụ, v.v...
Bộ phân tích ngữ pháp Python không lọc thụt hàng từ các chuỗi đa dòng (multi-line string literal) trong Python, so nên các công cụ xử lý tài liệu cần phải lọc thụt hàng nếu cần. Việc này được làm theo một cách chung. Dòng không trống đầu tiên sau dòng đầu tiên của chuỗi xác định mức thụt vào cho toàn bộ chuỗi tài liệu. (Ta không thể dùng dòng đầu tiên vì nó thường nằm kế dấu nháy đầu chuỗi cho nên mức thụt vào của nó không được xác định trong cách viết chuỗi.) Khoảng trắng ``tương đương'' với mức thụt vào này được bò khỏi mỗi đầu dòng trong chuỗi. Không nên có các dòng thụt vào ít hơn, nhưng nếu gặp phải, toàn bộ khoảng trắng đầu của chúng nên được bỏ đi. Tính tương đương của khoảng trắng cần được kiểm tra sau khi mở rộng tab (thông thường thành 8 khoảng trắng).
Đâu là ví dụ của một docstring đa dòng:
>>> def my_function(): ... """Do nothing, but document it. ... ... No, really, it doesn't do anything. ... """ ... pass ... >>> print my_function.__doc__ Do nothing, but document it. No, really, it doesn't do anything.