4. Thêm công cụ luồng điều khiển

Ngoài câu lệnh while vừa giới thiệu, Python còn sử dụng một số câu lệnh khác mà chúng ta sẽ gặp trong chương này.

4.1. Báo cáo if

Có lẽ loại câu lệnh được biết đến nhiều nhất là câu lệnh if. Ví dụ:

>>> x = int(input("Xin vui lòng nhập số nguyên:"))
Vui lòng nhập số nguyên: 42
>>> nếu x < 0:
... x = 0
... print('Số âm đã đổi thành 0')
... Elif x == 0:
... in('Không')
... Elif x == 1:
... print('Đơn')
... khác:
... print('Thêm')
...
Thêm

Có thể có 0 hoặc nhiều phần elif và phần else là tùy chọn. Từ khóa 'elif' là viết tắt của 'else if' và rất hữu ích để tránh thụt lề quá mức. Một chuỗi if ... elif ... elif ... là một chuỗi thay thế cho các câu lệnh switch hoặc case được tìm thấy trong các ngôn ngữ khác.

Nếu bạn đang so sánh cùng một giá trị với một số hằng số hoặc kiểm tra các loại hoặc thuộc tính cụ thể, bạn cũng có thể thấy câu lệnh match hữu ích. Để biết thêm chi tiết, xem Báo cáo match.

4.2. Báo cáo for

Câu lệnh for trong Python hơi khác một chút so với câu lệnh bạn quen dùng trong C hoặc Pascal. Thay vì luôn lặp lại một cấp số cộng của các số (như trong Pascal) hoặc cung cấp cho người dùng khả năng xác định cả bước lặp và điều kiện dừng (dưới dạng C), câu lệnh for của Python lặp lại các mục của bất kỳ chuỗi nào (danh sách hoặc chuỗi), theo thứ tự chúng xuất hiện trong chuỗi. Ví dụ (không có ý định chơi chữ):

>>> # Measure một số chuỗi:
>>> từ = ['mèo', 'cửa sổ', 'bảo vệ']
>>> cho w trong từ:
... print(w, len(w))
...
mèo 3
cửa sổ 6
bảo vệ 12

Mã sửa đổi một bộ sưu tập trong khi lặp lại trên cùng một bộ sưu tập đó có thể khó thực hiện đúng. Thay vào đó, việc lặp qua một bản sao của bộ sưu tập hoặc tạo một bộ sưu tập mới thường đơn giản hơn:

# Create một bộ sưu tập mẫu
người dùng = {'Hans': 'hoạt động', 'Éléonore': 'không hoạt động', '景太郎': 'hoạt động'}

# Strategy: Lặp lại một bản sao
đối với người dùng, trạng thái trong user.copy().items():
    nếu trạng thái == 'không hoạt động':
        xóa người dùng[người dùng]

# Strategy: Tạo bộ sưu tập mới
người dùng hoạt động = {}
đối với người dùng, trạng thái trong user.items():
    nếu trạng thái == 'hoạt động':
        active_users[user] = trạng thái

4.3. Chức năng range()

Nếu bạn thực sự cần lặp lại một chuỗi số, hàm range() tích hợp sẵn sẽ rất hữu ích. Nó tạo ra các cấp số cộng:

>>> cho i trong phạm vi (5):
... in(i)
...
0
1
2
3
4

Điểm cuối đã cho không bao giờ là một phần của chuỗi được tạo; range(10) tạo ra 10 giá trị, chỉ số pháp lý cho các mục có độ dài 10. Có thể để phạm vi bắt đầu ở một số khác hoặc chỉ định mức tăng khác (thậm chí là âm; đôi khi điều này được gọi là 'bước'):

>>> danh sách (phạm vi (5, 10))
[5, 6, 7, 8, 9]

>>> danh sách(phạm vi(0, 10, 3))
[0, 3, 6, 9]

>>> danh sách(phạm vi(-10, -100, -30))
[-10, -40, -70]

Để lặp lại các chỉ số của một chuỗi, bạn có thể kết hợp range()len() như sau:

>>> a = ['Mary', 'đã', 'a', 'nhỏ', 'cừu non']
>>> cho i trong phạm vi(len(a)):
... print(i, a[i])
...
0 Đức Maria
1 đã có
2 một
3 ít
4 con cừu

Tuy nhiên, trong hầu hết các trường hợp như vậy, sẽ thuận tiện hơn khi sử dụng chức năng enumerate(), xem Kỹ thuật lặp.

Một điều kỳ lạ xảy ra nếu bạn chỉ in một phạm vi:

>>> phạm vi (10)
phạm vi (0, 10)

Theo nhiều cách, đối tượng được trả về bởi range() hoạt động như thể nó là một danh sách, nhưng thực tế không phải vậy. Nó là một đối tượng trả về các mục liên tiếp của chuỗi mong muốn khi bạn lặp lại nó, nhưng nó không thực sự tạo ra danh sách, do đó tiết kiệm không gian.

Chúng tôi nói một đối tượng như vậy là iterable, nghĩa là, phù hợp làm mục tiêu cho các hàm và cấu trúc mong đợi thứ gì đó mà từ đó chúng có thể lấy được các vật phẩm liên tiếp cho đến khi nguồn cung cạn kiệt. Chúng ta đã thấy rằng câu lệnh for là một cấu trúc như vậy, trong khi một ví dụ về hàm có thể lặp lại là sum():

>>> tổng(phạm vi(4)) # 0 + 1 + 2 + 3
6

Sau này chúng ta sẽ thấy nhiều hàm trả về các biến lặp và lấy các biến lặp làm đối số. Trong chương Cấu trúc dữ liệu, chúng ta sẽ thảo luận chi tiết hơn về list().

4.4. Câu lệnh breakcontinue

Câu lệnh break thoát ra khỏi vòng lặp for hoặc while bao quanh trong cùng:

>>> cho n trong phạm vi (2, 10):
... cho x trong phạm vi (2, n):
... nếu n% x == 0:
... print(f"{n} bằng {x} * {n//x}")
... nghỉ
...
4 bằng 2 * 2
6 bằng 2 * 3
8 bằng 2 * 4
9 bằng 3 * 3

Câu lệnh continue tiếp tục với lần lặp tiếp theo của vòng lặp:

>>> cho số trong phạm vi (2, 10):
... nếu số% 2 == 0:
... print(f"Tìm thấy số chẵn {num}")
... tiếp tục
... print(f"Tìm thấy số lẻ {num}")
...
Tìm thấy số chẵn 2
Tìm thấy số lẻ 3
Tìm thấy số chẵn 4
Tìm thấy số lẻ 5
Tìm thấy số chẵn 6
Tìm thấy số lẻ 7
Tìm thấy số chẵn 8
Tìm thấy số lẻ 9

4.5. Các điều khoản else trên vòng lặp

Trong vòng lặp for hoặc while, câu lệnh break có thể được ghép nối với mệnh đề else. Nếu vòng lặp kết thúc mà không thực hiện break thì mệnh đề else sẽ thực thi.

Trong vòng lặp for, mệnh đề else được thực thi sau khi vòng lặp kết thúc lần lặp cuối cùng, nghĩa là nếu không có ngắt xảy ra.

Trong vòng lặp while, nó được thực thi sau khi điều kiện của vòng lặp trở thành sai.

Trong cả hai loại vòng lặp, mệnh đề else đều được not thực thi nếu vòng lặp bị kết thúc bởi break. Tất nhiên, các cách khác để kết thúc sớm vòng lặp, chẳng hạn như return hoặc ngoại lệ được nêu ra, cũng sẽ bỏ qua việc thực thi mệnh đề else.

Điều này được minh họa trong vòng lặp for sau đây, vòng lặp này tìm kiếm các số nguyên tố:

>>> cho n trong phạm vi (2, 10):
... cho x trong phạm vi (2, n):
... nếu n% x == 0:
... print(n, 'bằng', x, '*', n//x)
... nghỉ
... khác:
... # loop thất thủ mà không tìm được nhân tố
... print(n, 'là số nguyên tố')
...
2 là số nguyên tố
3 là số nguyên tố
4 bằng 2 * 2
5 là số nguyên tố
6 bằng 2 * 3
7 là số nguyên tố
8 bằng 2 * 4
9 bằng 3 * 3

(Đúng, đây là mã chính xác. Hãy nhìn kỹ: mệnh đề else thuộc vòng lặp for, not thuộc câu lệnh if.)

Một cách để nghĩ về mệnh đề else là tưởng tượng nó được ghép nối với if bên trong vòng lặp. Khi vòng lặp thực thi, nó sẽ chạy một chuỗi như if/if/if/else. Zz001zz nằm trong vòng lặp, đã gặp một số lần. Nếu điều kiện đúng thì break sẽ xảy ra. Nếu điều kiện không bao giờ đúng, mệnh đề else bên ngoài vòng lặp sẽ được thực thi.

Khi được sử dụng với một vòng lặp, mệnh đề else có nhiều điểm chung với mệnh đề else của câu lệnh try hơn là với câu lệnh if: mệnh đề else của câu lệnh try chạy khi không có ngoại lệ nào xảy ra và mệnh đề else của vòng lặp chạy khi không có break nào xảy ra. Để biết thêm về câu lệnh try và các ngoại lệ, hãy xem Xử lý ngoại lệ.

4.6. Báo cáo pass

Câu lệnh pass không làm gì cả. Nó có thể được sử dụng khi một câu lệnh được yêu cầu về mặt cú pháp nhưng chương trình không yêu cầu hành động nào. Ví dụ:

>>> trong khi Đúng:
... vượt qua # Busy-wait để ngắt bàn phím (Ctrl+C)
...

Điều này thường được sử dụng để tạo các lớp tối thiểu

>>> lớp MyEmptyClass:
... vượt qua
...

Một vị trí khác mà pass có thể được sử dụng là làm phần giữ chỗ cho một hàm hoặc phần nội dung có điều kiện khi bạn đang làm việc trên mã mới, cho phép bạn tiếp tục suy nghĩ ở mức độ trừu tượng hơn. Zz001zz bị âm thầm bỏ qua

>>> def initlog(*args):
... vượt qua # Remember để thực hiện điều này!
...

Đối với trường hợp cuối cùng này, nhiều người sử dụng dấu chấm lửng ... thay vì pass. Việc sử dụng này không có ý nghĩa đặc biệt đối với Python và không phải là một phần của định nghĩa ngôn ngữ (bạn có thể sử dụng bất kỳ biểu thức hằng số nào ở đây), nhưng ... cũng được sử dụng thông thường như một nội dung giữ chỗ. Xem Đối tượng Ellipsis.

4.7. Báo cáo match

Câu lệnh match lấy một biểu thức và so sánh giá trị của nó với các mẫu liên tiếp được đưa ra dưới dạng một hoặc nhiều khối chữ hoa. Điều này bề ngoài tương tự như câu lệnh switch trong C, Java hoặc JavaScript (và nhiều ngôn ngữ khác), nhưng nó giống với khớp mẫu trong các ngôn ngữ như Rust hoặc Haskell hơn. Chỉ mẫu đầu tiên khớp mới được thực thi và nó cũng có thể trích xuất các thành phần (phần tử chuỗi hoặc thuộc tính đối tượng) từ giá trị thành các biến. Nếu không có trường hợp nào khớp thì không có nhánh nào được thực thi.

Dạng đơn giản nhất so sánh một giá trị chủ đề với một hoặc nhiều chữ:

def http_error(trạng thái):
    trạng thái trận đấu:
        trường hợp 400:
            trả về "Yêu cầu không hợp lệ"
        trường hợp 404:
            trả về "Không tìm thấy"
        trường hợp 418:
            return Tôi  ấm trà
        trường hợp _:
            return "Đã xảy ra sự cố với Internet"

Lưu ý khối cuối cùng: "tên biến" _ hoạt động như wildcard và không bao giờ khớp.

Bạn có thể kết hợp nhiều chữ trong một mẫu bằng cách sử dụng | ("hoặc"):

trường hợp 401 | 403 | 404:
    trả về "Không được phép"

Các mẫu có thể trông giống như các bài tập giải nén và có thể được sử dụng để liên kết các biến

# point là một bộ (x, y)
điểm phù hợp:
    trường hợp (0, 0):
        in ("Xuất xứ")
    trường hợp (0, y):
        print(f"Y={y}")
    trường hợp (x, 0):
        in(f"X={x}")
    trường hợp (x, y):
        print(f"X={x}, Y={y}")
    trường hợp _:
        tăng ValueError("Không một điểm")

Hãy nghiên cứu kỹ điều đó! Mẫu đầu tiên có hai chữ và có thể được coi là phần mở rộng của mẫu chữ được hiển thị ở trên. Nhưng hai mẫu tiếp theo kết hợp một chữ và một biến, và biến binds một giá trị từ chủ đề (point). Mẫu thứ tư ghi lại hai giá trị, làm cho nó tương tự về mặt khái niệm với phép gán giải nén (x, y) = point.

Nếu bạn đang sử dụng các lớp để cấu trúc dữ liệu của mình, bạn có thể sử dụng tên lớp theo sau là danh sách đối số giống như hàm tạo, nhưng có khả năng nắm bắt các thuộc tính thành các biến:

Điểm lớp:
    def __init__(self, x, y):
        tự.x = x
        tự.y = y

def  đâu_is(điểm):
    điểm phù hợp:
        trường hợp Điểm(x=0, y=0):
            in ("Xuất xứ")
        trường hợp Điểm(x=0, y=y):
            print(f"Y={y}")
        trường hợp Điểm(x=x, y=0):
            in(f"X={x}")
        trường hợp Điểm():
            print("Nơi khác")
        trường hợp _:
            print("Không có điểm")

Bạn có thể sử dụng các tham số vị trí với một số lớp dựng sẵn cung cấp thứ tự cho các thuộc tính của chúng (ví dụ: các lớp dữ liệu). Bạn cũng có thể xác định vị trí cụ thể cho các thuộc tính trong mẫu bằng cách đặt thuộc tính đặc biệt __match_args__ trong các lớp của mình. Nếu nó được đặt thành ("x", "y"), thì các mẫu sau đều tương đương (và tất cả đều liên kết thuộc tính y với biến var):

Điểm(1, var)
Điểm(1, y=var)
Điểm(x=1, y=var)
Điểm(y=var, x=1)

Một cách được khuyến nghị để đọc các mẫu là xem chúng như một dạng mở rộng của những gì bạn sẽ đặt ở bên trái của bài tập, để hiểu biến nào sẽ được đặt thành biến nào. Chỉ những tên độc lập (như var ở trên) mới được gán bằng câu lệnh so khớp. Tên chấm (như foo.bar), tên thuộc tính (x=y= ở trên) hoặc tên lớp (được nhận dạng bằng dấu "(...)" bên cạnh chúng như Point ở trên) không bao giờ được gán cho.

Các mẫu có thể được lồng vào nhau tùy ý. Ví dụ: nếu chúng tôi có một danh sách ngắn các Điểm, có thêm __match_args__, chúng tôi có thể so khớp nó như sau:

Điểm lớp:
    __match_args__ = ('x', 'y')
    def __init__(self, x, y):
        tự.x = x
        tự.y = y

điểm phù hợp:
    trường hợp []:
        print("Không có điểm")
    trường hợp [Điểm(0, 0)]:
        print("Nguồn gốc")
    trường hợp [Điểm(x, y)]:
        print(f"Điểm đơn {x}, {y}")
    trường hợp [Điểm(0, y1), Điểm(0, y2)]:
        print(f"Hai trên trục Y tại {y1}, {y2}")
    trường hợp _:
        print("Cái gì đó khác")

Chúng ta có thể thêm mệnh đề if vào một mẫu, được gọi là "bảo vệ". Nếu bảo vệ sai, match tiếp tục thử khối trường hợp tiếp theo. Lưu ý rằng việc thu thập giá trị xảy ra trước khi bảo vệ được đánh giá

điểm phù hợp:
    trường hợp Điểm(x, y) if x == y:
        print(f"Y=X at {x}")
    trường hợp Điểm(x, y):
        print(f"Không nằm trên đường chéo")

Một số tính năng chính khác của tuyên bố này:

  • Giống như các bài tập giải nén, các mẫu bộ và danh sách có ý nghĩa giống hệt nhau và thực sự khớp với các chuỗi tùy ý. Một ngoại lệ quan trọng là chúng không khớp với các trình vòng lặp hoặc chuỗi.

  • Các mẫu trình tự hỗ trợ giải nén mở rộng: [x, y, *rest](x, y, *rest) hoạt động tương tự như các bài tập giải nén. Tên sau * cũng có thể là _, vì vậy (x, y, *_) khớp với một chuỗi gồm ít nhất hai mục mà không ràng buộc các mục còn lại.

  • Các mẫu ánh xạ: {"bandwidth": b, "latency": l} ghi lại các giá trị "bandwidth""latency" từ một từ điển. Không giống như các mẫu trình tự, các phím bổ sung sẽ bị bỏ qua. Việc giải nén như **rest cũng được hỗ trợ. (Nhưng **_ sẽ dư thừa nên không được phép.)

  • Các mẫu con có thể được ghi lại bằng từ khóa as:

    trường hợp (Điểm(x1, y1), Điểm(x2, y2) as p2): ...
    

    sẽ nắm bắt phần tử thứ hai của đầu vào dưới dạng p2 (miễn là đầu vào là một chuỗi gồm hai điểm)

  • Hầu hết các chữ được so sánh bằng sự bình đẳng, tuy nhiên các đơn vị True, FalseNone được so sánh theo danh tính.

  • Các mẫu có thể sử dụng các hằng số được đặt tên. Đây phải là các tên có dấu chấm để tránh bị hiểu là biến chụp:

    từ nhập enum Enum
    Màu lớp (Enum):
        RED = 'đỏ'
        GREEN = 'xanh'
        BLUE = 'màu xanh'
    
    color = Color(input("Nhập lựa chọn của bạn về 'đỏ', 'xanh' hoặc 'xanh':"))
    
    màu sắc phù hợp:
        trường hợp Color.RED:
            print("Tôi thấy màu đỏ!")
        trường hợp Color.GREEN:
            print("Cỏ xanh")
        trường hợp Color.BLUE:
            print("Tôi đang cảm thấy chán nản :(")
    

Để có giải thích chi tiết hơn và các ví dụ bổ sung, bạn có thể xem PEP 636 được viết dưới dạng hướng dẫn.

4.8. Xác định hàm

Chúng ta có thể tạo một hàm ghi chuỗi Fibonacci vào một ranh giới tùy ý:

>>> def fib(n): dãy Fibonacci # write nhỏ hơn n
... """In chuỗi Fibonacci nhỏ hơn n."""
... a, b = 0, 1
... trong khi một < n:
... print(a, end=' ')
... a, b = b, a+b
... in()
...
>>> # Now gọi hàm vừa định nghĩa:
>>>  (2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

Từ khóa def giới thiệu hàm definition. Theo sau nó phải là tên hàm và danh sách các tham số hình thức trong ngoặc đơn. Các câu lệnh tạo thành phần thân của hàm bắt đầu ở dòng tiếp theo và phải được thụt lề.

Câu lệnh đầu tiên của thân hàm có thể tùy ý là một chuỗi ký tự; chuỗi ký tự này là chuỗi tài liệu của hàm hoặc docstring. (Bạn có thể tìm thêm thông tin về chuỗi tài liệu trong phần Chuỗi tài liệu.) Có những công cụ sử dụng chuỗi tài liệu để tự động tạo tài liệu trực tuyến hoặc in hoặc để cho phép người dùng duyệt qua mã một cách tương tác; Một cách thực hành tốt là đưa các chuỗi tài liệu vào mã mà bạn viết, vì vậy hãy tạo thói quen đó.

Zz002zz của hàm giới thiệu một bảng ký hiệu mới được sử dụng cho các biến cục bộ của hàm. Chính xác hơn, tất cả các phép gán biến trong một hàm đều lưu trữ giá trị trong bảng ký hiệu cục bộ; trong khi đó, các tham chiếu biến trước tiên sẽ tìm trong bảng ký hiệu cục bộ, sau đó đến bảng ký hiệu cục bộ của các hàm kèm theo, sau đó là bảng ký hiệu chung và cuối cùng là bảng tên dựng sẵn. Do đó, các biến toàn cục và biến của các hàm kèm theo không thể được gán trực tiếp một giá trị trong một hàm (trừ khi, đối với các biến toàn cục, được đặt tên trong câu lệnh global hoặc, đối với các biến của các hàm kèm theo, được đặt tên trong câu lệnh nonlocal), mặc dù chúng có thể được tham chiếu.

Các tham số thực tế (đối số) cho lệnh gọi hàm được đưa vào bảng ký hiệu cục bộ của hàm được gọi khi nó được gọi; do đó, các đối số được truyền bằng call by value (trong đó value luôn là một đối tượng reference, không phải giá trị của đối tượng). [1] Khi một hàm gọi một hàm khác hoặc gọi chính nó một cách đệ quy, một bảng ký hiệu cục bộ mới sẽ được tạo cho lệnh gọi đó.

Định nghĩa hàm liên kết tên hàm với đối tượng hàm trong bảng ký hiệu hiện tại. Trình thông dịch nhận ra đối tượng được trỏ đến bởi tên đó là hàm do người dùng xác định. Các tên khác cũng có thể trỏ đến cùng đối tượng hàm đó và cũng có thể được sử dụng để truy cập hàm

>>>  cấu
<chức năng fib ở 10042ed0>
>>> f = sợi
>>>f(100)
0 1 1 2 3 5 8 13 21 34 55 89

Đến từ các ngôn ngữ khác, bạn có thể phản đối rằng fib không phải là một hàm mà là một thủ tục vì nó không trả về một giá trị. Trên thực tế, ngay cả các hàm không có câu lệnh return cũng trả về một giá trị, mặc dù là một giá trị khá nhàm chán. Giá trị này được gọi là None (là tên có sẵn). Việc ghi giá trị None thường bị trình thông dịch loại bỏ nếu đó là giá trị duy nhất được ghi. Bạn có thể xem nó nếu bạn thực sự muốn sử dụng print():

>>> sợi(0)
>>> in(fib(0))
không có

Thật đơn giản để viết một hàm trả về danh sách các số của dãy Fibonacci, thay vì in nó:

>>> def fib2(n): dãy Fibonacci # return lên đến n
... """Trả về danh sách chứa chuỗi Fibonacci lên đến n."""
... kết quả = []
... a, b = 0, 1
... trong khi một < n:
... result.append(a) # see bên dưới
... a, b = b, a+b
... trả về kết quả
...
>>> f100 = fib2(100) # call nó
>>> f100 # write kết quả
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

Ví dụ này, như thường lệ, minh họa một số tính năng mới của Python:

  • Câu lệnh return trả về với một giá trị từ một hàm. return không có đối số biểu thức sẽ trả về None. Việc rơi ra khỏi cuối hàm cũng trả về None.

  • Câu lệnh result.append(a) gọi method của đối tượng danh sách result. Phương thức là một hàm 'thuộc về' một đối tượng và được đặt tên là obj.methodname, trong đó obj là một đối tượng nào đó (đây có thể là một biểu thức) và methodname là tên của một phương thức được xác định bởi loại đối tượng. Các loại khác nhau xác định các phương pháp khác nhau. Các phương thức thuộc các loại khác nhau có thể có cùng tên mà không gây ra sự mơ hồ. (Có thể xác định các loại đối tượng và phương thức của riêng bạn, sử dụng classes, xem Lớp học) Phương thức append() hiển thị trong ví dụ được xác định 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 + [a], nhưng hiệu quả hơn.

4.9. Thông tin thêm về Xác định hàm

Cũng có thể định nghĩa các hàm với số lượng đối số thay đổi. Có ba hình thức có thể được kết hợp.

4.9.1. Giá trị đối số mặc định

Hình thức hữu ích nhất là chỉ định giá trị mặc định cho một hoặc nhiều đối số. Điều này tạo ra một hàm có thể được gọi với ít đối số hơn mức được xác định cho phép. Ví dụ:

def Ask_ok(prompt, retries=4, Reminder='Xin vui lòng thử lại!'):
    trong khi Đúng:
        trả lời = đầu vào (nhắc)
        nếu trả lời bằng {'y', 'ye', 'yes'}:
            trả về Đúng
        nếu trả lời bằng {'n', 'no', 'nop', 'nope'}:
            trả về Sai
        thử lại = thử lại - 1
        nếu thử lại < 0:
            tăng ValueError('phản hồi của người dùng không hợp lệ')
        in (nhắc nhở)

Hàm này có thể được gọi theo nhiều cách:

  • chỉ đưa ra đối số bắt buộc: ask_ok('Do you really want to quit?')

  • đưa ra một trong các đối số tùy chọn: ask_ok('OK to overwrite the file?', 2)

  • hoặc thậm chí đưa ra tất cả các đối số: ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')

Ví dụ này cũng giới thiệu từ khóa in. Điều này kiểm tra xem một chuỗi có chứa một giá trị nhất định hay không.

Các giá trị mặc định được đánh giá tại điểm định nghĩa hàm trong phạm vi defining, do đó

tôi = 5

def f(arg=i):
    in(arg)

tôi = 6
f()

sẽ in 5.

Important warning: Giá trị mặc định chỉ được đánh giá một lần. Điều này tạo nên sự khác biệt khi mặc định là một đối tượng có thể thay đổi, chẳng hạn như danh sách, từ điển hoặc phiên bản của hầu hết các lớp. Ví dụ: hàm sau tích lũy các đối số được truyền cho nó trong các lệnh gọi tiếp theo:

def f(a, L=[]):
    L.append(a)
    trả lại L

in(f(1))
in(f(2))
in(f(3))

Điều này sẽ in

[1]
[1, 2]
[1, 2, 3]

Nếu bạn không muốn chia sẻ mặc định giữa các cuộc gọi tiếp theo, thay vào đó bạn có thể viết hàm như thế này

def f(a, L=Không):
    nếu L  Không:
        L = []
    L.append(a)
    trả lại L

4.9.2. Đối số từ khóa

Các hàm cũng có thể được gọi bằng cách sử dụng keyword arguments có dạng kwarg=value. Ví dụ: hàm sau

def con vẹt(điện áp, trạng thái='a cứng', hành động='voom', type='Màu xanh Na Uy'):
    print("-- Con vẹt này sẽ không", action, end=' ')
    print("nếu bạn đặt", điện áp, "Vôn qua nó.")
    print("-- Bộ lông đáng yêu, the", type)
    print("-- Đó là", trạng thái, "!")

chấp nhận một đối số bắt buộc (voltage) và ba đối số tùy chọn (state, actiontype). Hàm này có thể được gọi theo bất kỳ cách nào sau đây:

đối số vị trí của vẹt (1000) # 1
đối số từ khóa vẹt (điện áp = 1000) # 1
vẹt(điện áp=1000000, hành động='VOOOOOM') đối số từ khóa # 2
vẹt(hành động='VOOOOOM', điện áp=1000000) đối số từ khóa # 2
con vẹt('một triệu', 'tước đi sự sống', 'nhảy') # 3 đối số vị trí
con vẹt('một ngàn', state='đẩy hoa cúc lên') # 1 vị trí, 1 từ khóa

nhưng tất cả các cuộc gọi sau sẽ không hợp lệ

đối số Parrot() # required bị thiếu
đối số vẹt (điện áp = 5.0, 'chết') # non-keyword sau đối số từ khóa
giá trị con vẹt (110, điện áp = 220) # duplicate cho cùng một đối số
Parrot(actor='John Cleese') đối số từ khóa # unknown

Trong lệnh gọi hàm, đối số từ khóa phải tuân theo đối số vị trí. Tất cả các đối số từ khóa được truyền phải khớp với một trong các đối số được hàm chấp nhận (ví dụ: actor không phải là đối số hợp lệ cho hàm parrot) và thứ tự của chúng không quan trọng. Điều này cũng bao gồm các đối số không tùy chọn (ví dụ: parrot(voltage=1000) cũng hợp lệ). Không có đối số nào có thể nhận được một giá trị nhiều lần. Đây là một ví dụ không thành công do hạn chế này:

>>> hàm def(a):
... vượt qua
...
>>> hàm(0, a=0)
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 1, trong <module>
TypeError: function() có nhiều giá trị cho đối số 'a'

Khi có tham số chính thức cuối cùng có dạng **name, nó sẽ nhận được một từ điển (xem Các loại ánh xạ --- dict) chứa tất cả các đối số từ khóa ngoại trừ những đối số tương ứng với tham số chính thức. Điều này có thể được kết hợp với một tham số chính thức có dạng *name (được mô tả trong phần phụ tiếp theo) để nhận một tuple chứa các đối số vị trí ngoài danh sách tham số chính thức. (*name phải xuất hiện trước **name.) Ví dụ: nếu chúng ta định nghĩa một hàm như thế này

def Cheeseshop(loại, *arguments, **từ khóa):
    print("-- Bạn có cái nào không", loại, "?")
    print("-- Xin lỗi, chúng tôi hết chỗ rồi", kind)
    cho arg trong các đối số:
        in(arg)
    in ("-" * 40)
    cho kw trong từ khóa:
        print(kw, ://, keywords[kw])

Nó có thể được gọi như thế này:

Cheeseshop("Limburger", "Nó chảy nhiều lắm, thưa ngài.",
           "Nó thực sự rất, VERY chảy nước, thưa ngài.",
           người bán hàng="Michael Palin",
           client="John Cleese",
           sketch="Bản phác thảo cửa hàng phô mai")

và tất nhiên nó sẽ in:

-- Bạn có Limburger không?
-- Tôi xin lỗi, chúng tôi hết Limburger rồi
Nó chảy nhiều lắm, thưa ngài.
Nó thực sự rất, VERY chảy nước, thưa ngài.
----------------------------------------
người bán hàng: Michael Palin
khách hàng: John Cleese
phác thảo : Bản phác thảo cửa hàng phô mai

Lưu ý rằng thứ tự in các đối số từ khóa được đảm bảo khớp với thứ tự chúng được cung cấp trong lệnh gọi hàm.

4.9.3. Thông số đặc biệt

Theo mặc định, các đối số có thể được truyền tới hàm Python theo vị trí hoặc theo từ khóa một cách rõ ràng. Để dễ đọc và hiệu suất, nên hạn chế cách truyền đối số để nhà phát triển chỉ cần xem định nghĩa hàm để xác định xem các mục được truyền theo vị trí, theo vị trí hay từ khóa hay theo từ khóa.

Một định nghĩa hàm có thể trông giống như:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
      ------------- ---------- ----------
        |             | |
        |        Positional or keyword   |
        |                                - Chỉ từ khóa
         -- Chỉ vị trí

trong đó /* là tùy chọn. Nếu được sử dụng, các ký hiệu này cho biết loại tham số theo cách các đối số có thể được chuyển đến hàm: chỉ vị trí, vị trí hoặc từ khóa và chỉ từ khóa. Tham số từ khóa còn được gọi là tham số được đặt tên.

4.9.3.1. Đối số vị trí hoặc từ khóa

Nếu /* không có trong định nghĩa hàm, các đối số có thể được chuyển đến hàm theo vị trí hoặc theo từ khóa.

4.9.3.2. Tham số chỉ có vị trí

Nhìn vào điều này chi tiết hơn một chút, có thể đánh dấu một số thông số nhất định là positional-only. Nếu positional-only, thứ tự của các tham số rất quan trọng và các tham số không thể được chuyển theo từ khóa. Các tham số chỉ có vị trí được đặt trước / (dấu gạch chéo về phía trước). Zz001zz được sử dụng để phân tách một cách hợp lý các tham số chỉ có vị trí khỏi các tham số còn lại. Nếu không có / trong định nghĩa hàm thì sẽ không có tham số chỉ vị trí.

Các thông số theo sau / có thể là positional-or-keyword hoặc keyword-only.

4.9.3.3. Đối số chỉ từ khóa

Để đánh dấu các tham số là keyword-only, cho biết các tham số phải được truyền bằng đối số từ khóa, hãy đặt * trong danh sách đối số ngay trước tham số keyword-only đầu tiên.

4.9.3.4. Ví dụ về hàm

Hãy xem xét các định nghĩa hàm ví dụ sau, chú ý đến các điểm đánh dấu /*:

>>> def tiêu chuẩn_arg(arg):
... in(arg)
...
>>> def pos_only_arg(arg, /):
... in(arg)
...
>>> def kwd_only_arg(*, arg):
... in(arg)
...
>>> def kết hợp_example(pos_only, /, tiêu chuẩn, *, kwd_only):
... print(pos_only, tiêu chuẩn, kwd_only)

Định nghĩa hàm đầu tiên, standard_arg, dạng quen thuộc nhất, không đặt ra hạn chế nào đối với quy ước gọi và các đối số có thể được chuyển theo vị trí hoặc từ khóa:

>>> tiêu chuẩn_arg(2)
2

>>> tiêu chuẩn_arg(arg=2)
2

Hàm thứ hai pos_only_arg bị hạn chế chỉ sử dụng các tham số vị trí vì có / trong định nghĩa hàm

>>> pos_only_arg(1)
1

>>> pos_only_arg(arg=1)
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 1, trong <module>
TypeError: pos_only_arg() nhận được một số đối số chỉ có vị trí được truyền dưới dạng đối số từ khóa: 'arg'

Hàm thứ ba kwd_only_arg chỉ cho phép các đối số từ khóa như được biểu thị bằng * trong định nghĩa hàm

>>> kwd_only_arg(3)
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 1, trong <module>
TypeError: kwd_only_arg() nhận 0 đối số vị trí nhưng đã đưa ra 1 đối số

>>> kwd_only_arg(arg=3)
3

Và cái cuối cùng sử dụng cả ba quy ước gọi trong cùng một định nghĩa hàm

>>> kết hợp_example(1, 2, 3)
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 1, trong <module>
TypeError: Combine_example() nhận 2 đối số vị trí nhưng đã đưa ra 3 đối số

>>> kết hợp_example(1, 2, kwd_only=3)
1 2 3

>>> kết hợp_example(1, tiêu chuẩn=2, kwd_only=3)
1 2 3

>>> kết hợp_example(pos_only=1, tiêu chuẩn=2, kwd_only=3)
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 1, trong <module>
TypeError: Combine_example() nhận được một số đối số chỉ có vị trí được truyền dưới dạng đối số từ khóa: 'pos_only'

Cuối cùng, hãy xem xét định nghĩa hàm này có khả năng xung đột giữa đối số vị trí name**kwdsname làm khóa:

def foo(tên, **kwds):
    trả về 'tên' tính bằng kwds

Không có lệnh gọi nào có thể khiến nó trả về True vì từ khóa 'name' sẽ luôn liên kết với tham số đầu tiên. Ví dụ:

>>> foo(1, **{'name': 2})
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 1, trong <module>
TypeError: foo() có nhiều giá trị cho đối số 'name'
>>>

Nhưng sử dụng / (đối số chỉ vị trí), điều đó là có thể vì nó cho phép name làm đối số vị trí và 'name' làm khóa trong đối số từ khóa

>>> def foo(tên, /, **kwds):
... trả về 'tên' theo kwds
...
>>> foo(1, **{'name': 2})
đúng

Nói cách khác, tên của các tham số chỉ có vị trí có thể được sử dụng trong **kwds mà không có sự mơ hồ.

4.9.3.5. Tóm tắt lại

Ca sử dụng sẽ xác định tham số nào sẽ được sử dụng trong định nghĩa hàm:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):

Như hướng dẫn:

  • Chỉ sử dụng vị trí nếu bạn muốn tên của các tham số không có sẵn cho người dùng. Điều này hữu ích khi tên tham số không có ý nghĩa thực sự, nếu bạn muốn thực thi thứ tự của các đối số khi hàm được gọi hoặc nếu bạn cần lấy một số tham số vị trí và từ khóa tùy ý.

  • Chỉ sử dụng từ khóa khi tên có ý nghĩa và định nghĩa hàm dễ hiểu hơn bằng cách nêu tên rõ ràng hoặc bạn muốn ngăn người dùng dựa vào vị trí của đối số được truyền.

  • Đối với API, chỉ sử dụng vị trí để tránh làm hỏng các thay đổi của API nếu tên của tham số được sửa đổi trong tương lai.

4.9.4. Danh sách đối số tùy ý

Cuối cùng, tùy chọn ít được sử dụng nhất là chỉ định rằng một hàm có thể được gọi với số lượng đối số tùy ý. Các đối số này sẽ được gói gọn trong một bộ dữ liệu (xem Bộ dữ liệu và trình tự). Trước số lượng đối số thay đổi, có thể xảy ra 0 hoặc nhiều đối số bình thường.

def write_multiple_items(tệp, dấu phân cách, *args):
    file.write(separator.join(args))

Thông thường, các đối số variadic này sẽ nằm cuối cùng trong danh sách các tham số chính thức, vì chúng lấy tất cả các đối số đầu vào còn lại được truyền cho hàm. Bất kỳ tham số chính thức nào xuất hiện sau tham số *args đều là đối số 'chỉ từ khóa', nghĩa là chúng chỉ có thể được sử dụng làm từ khóa thay vì đối số vị trí.

>>> def concat(*args, sep="/"):
... trả về sep.join(args)
...
>>> concat("trái đất", "sao hỏa", "venus")
'trái đất/sao hỏa/sao kim'
>>> concat("trái đất", "mars", "venus", sep=".")
'earth.mars.venus'

4.9.5. Giải nén danh sách đối số

Tình huống ngược lại xảy ra khi các đối số đã có trong danh sách hoặc bộ dữ liệu nhưng cần được giải nén cho một lệnh gọi hàm yêu cầu các đối số vị trí riêng biệt. Ví dụ: hàm range() tích hợp sẽ yêu cầu các đối số startstop riêng biệt. Nếu chúng không có sẵn riêng biệt, hãy viết lệnh gọi hàm bằng toán tử *- để giải nén các đối số ra khỏi danh sách hoặc bộ dữ liệu

>>> list(range(3, 6)) # normal gọi với các đối số riêng biệt
[3, 4, 5]
>>> đối số = [3, 6]
>>> list(range(*args)) # call với các đối số được giải nén khỏi danh sách
[3, 4, 5]

Theo cách tương tự, từ điển có thể cung cấp các đối số từ khóa bằng **-operator:

>>> def vẹt(điện áp, trạng thái='cứng', hành động='voom'):
... print("-- Con vẹt này sẽ không", action, end=' ')
... print("nếu bạn đặt", điện áp, "vôn xuyên qua nó.", end=' ')
... print("E's", trạng thái, "!")
...
>>> d = {"điện áp": "bốn triệu", "trạng thái": "chảy máu", "hành động": "VOOM"}
>>> vẹt(**d)
-- Con vẹt này sẽ không kêu VOOM nếu bạn đặt dòng điện 4 triệu volt vào nó. E chết rồi!

4.9.6. Biểu thức Lambda

Các hàm ẩn danh nhỏ có thể được tạo bằng từ khóa lambda. Hàm này trả về tổng của hai đối số của nó: lambda a, b: a+b. Bạn có thể sử dụng hàm Lambda ở bất cứ nơi nào cần có đối tượng hàm. Chúng bị giới hạn về mặt cú pháp trong một biểu thức duy nhất. Về mặt ngữ nghĩa, chúng chỉ là cú pháp cho một định nghĩa hàm thông thường. Giống như các định nghĩa hàm lồng nhau, hàm lambda có thể tham chiếu các biến từ phạm vi chứa:

>>> def make_incrementor(n):
... trả về lambda x: x + n
...
>>> f = make_incrementor(42)
>>>f(0)
42
>>>f(1)
43

Ví dụ trên sử dụng biểu thức lambda để trả về một hàm. Một cách sử dụng khác là truyền một hàm nhỏ làm đối số. Ví dụ: list.sort() có chức năng khóa sắp xếp key có thể là hàm lambda

>>> cặp = [(1, 'một'), (2, 'hai'), (3, 'ba'), (4, 'bốn')]
>>> cặp.sort(key=lambda cặp: cặp [1])
>>> cặp
[(4, 'bốn'), (1, 'một'), (3, 'ba'), (2, 'hai')]

4.9.7. Chuỗi tài liệu

Dưới đây là một số quy ước về nội dung và định dạng của chuỗi tài liệu.

Dòng đầu tiên phải luôn là một bản tóm tắt ngắn gọn, súc tích về mục đích của đối tượng. Để ngắn gọn, nó không nên nêu rõ ràng tên hoặc loại đối tượng, vì chúng có sẵn bằng các cách khác (trừ khi tên đó là một động từ mô tả hoạt động của hàm). Dòng này phải bắt đầu bằng chữ in hoa và kết thúc bằng dấu chấm.

Nếu có nhiều dòng hơn trong chuỗi tài liệu thì dòng thứ hai phải trống, tách biệt phần tóm tắt với phần còn lại của mô tả một cách trực quan. Các dòng sau đây phải là một hoặc nhiều đoạn mô tả quy ước gọi của đối tượng, tác dụng phụ của nó, v.v.

Trình phân tích cú pháp Python loại bỏ thụt lề khỏi các chuỗi ký tự nhiều dòng khi chúng đóng vai trò là chuỗi tài liệu mô-đun, lớp hoặc hàm.

Dưới đây là ví dụ về chuỗi tài liệu nhiều dòng:

>>> def my_function():
... """Không làm gì cả, nhưng hãy ghi lại nó.
...
... Không, thực sự, nó không làm gì cả:
...
... >>> my_function()
... >>>
... """
... vượt qua
...
>>> in(my_function.__doc__)
Không làm gì cả, nhưng hãy ghi lại nó.

Không, thực sự, nó không làm gì cả:

    >>> my_function()
    >>>

4.9.8. Chú thích chức năng

Function annotations là thông tin siêu dữ liệu hoàn toàn tùy chọn về các loại được sử dụng bởi các hàm do người dùng xác định (xem PEP 3107PEP 484 để biết thêm thông tin).

Annotations được lưu trữ trong thuộc tính __annotations__ của hàm dưới dạng từ điển và không ảnh hưởng đến bất kỳ phần nào khác của hàm. Chú thích tham số được xác định bằng dấu hai chấm sau tên tham số, theo sau là biểu thức đánh giá giá trị của chú thích. Chú thích trả về được xác định bằng -> theo nghĩa đen, theo sau là một biểu thức, giữa danh sách tham số và dấu hai chấm biểu thị phần cuối của câu lệnh def. Ví dụ sau có đối số bắt buộc, đối số tùy chọn và giá trị trả về được chú thích:

>>> def f(ham: str, trứng: str = 'trứng') -> str:
... print("Chú thích:", f.__annotations__)
... print("Đối số:", giăm bông, trứng)
... return giăm bông + ' và ' + trứng
...
>>> f('thư rác')
Chú thích: {'ham': <class 'str'>, 'return': <class 'str'>, 'Eggs': <class 'str'>}
Lập luận: trứng thư rác
'thư rác và trứng'

4.10. Intermezzo: Phong cách mã hóa

Bây giờ bạn sắp viết những đoạn Python dài hơn, phức tạp hơn, đây là thời điểm thích hợp để nói về coding style. Hầu hết các ngôn ngữ có thể được viết (hoặc chính xác hơn là formatted) theo các phong cách khác nhau; một số dễ đọc hơn những cái khác. Giúp người khác dễ dàng đọc mã của bạn luôn là một ý tưởng hay và việc áp dụng một phong cách viết mã đẹp sẽ giúp ích rất nhiều cho điều đó.

Đối với Python, PEP 8 đã nổi lên như một hướng dẫn về phong cách mà hầu hết các dự án đều tuân thủ; nó thúc đẩy một phong cách mã hóa rất dễ đọc và đẹp mắt. Mọi nhà phát triển Python nên đọc nó vào một lúc nào đó; đây là những điểm quan trọng nhất được trích xuất cho bạn:

  • Sử dụng thụt lề 4 dấu cách và không có tab.

    4 dấu cách là sự kết hợp tốt giữa mức thụt lề nhỏ (cho phép độ sâu lồng nhau lớn hơn) và mức thụt lề lớn (dễ đọc hơn). Các tab gây nhầm lẫn và tốt nhất nên bỏ qua.

  • Ngắt dòng sao cho chúng không vượt quá 79 ký tự.

    Điều này giúp ích cho người dùng có màn hình nhỏ và giúp có thể có nhiều tệp mã cạnh nhau trên màn hình lớn hơn.

  • Sử dụng các dòng trống để phân tách các hàm và lớp cũng như các khối mã lớn hơn bên trong các hàm.

  • Khi có thể, hãy đặt bình luận trên một dòng riêng.

  • Sử dụng chuỗi tài liệu.

  • Sử dụng khoảng trắng xung quanh các toán tử và sau dấu phẩy, nhưng không trực tiếp bên trong các cấu trúc dấu ngoặc: a = f(1, 2) + g(3, 4).

  • Đặt tên cho các lớp và hàm của bạn một cách nhất quán; quy ước là sử dụng UpperCamelCase cho các lớp và lowercase_with_underscores cho các hàm và phương thức. Luôn sử dụng self làm tên cho đối số phương thức đầu tiên (xem Cái nhìn đầu tiên về lớp học để biết thêm về các lớp và phương thức).

  • Không sử dụng các mã hóa ưa thích nếu mã của bạn được sử dụng trong môi trường quốc tế. Mặc định của Python, UTF-8 hoặc thậm chí ASCII đơn giản hoạt động tốt nhất trong mọi trường hợp.

  • Tương tự như vậy, không sử dụng các ký tự không phải ASCII trong số nhận dạng nếu chỉ có rất ít khả năng những người nói ngôn ngữ khác sẽ đọc hoặc duy trì mã.

Chú thích cuối trang