Nhiều hàm Python mà chúng tôi đã sử dụng, chẳng hạn như các hàm toán học, tạo ra các giá trị trả về. Nhưng các chức năng chúng tôi đã viết đều vô hiệu. chúng có tác dụng, như in một giá trị hoặc di chuyển con rùa, nhưng chúng không có giá trị trả về. Trong chương này, bạn sẽ học cách viết các hàm hiệu quả
6. 1 Trả về giá trị
Việc gọi hàm tạo ra một giá trị trả về mà chúng ta thường gán cho một biến hoặc sử dụng như một phần của biểu thức
e = math.exp[1.0] height = radius * math.sin[radians]
Các chức năng chúng tôi đã viết cho đến nay là vô hiệu. Nói một cách tình cờ, chúng không có giá trị trả lại;
Trong chương này, chúng ta [cuối cùng] sẽ viết các hàm hiệu quả. Ví dụ đầu tiên là diện tích, trả về diện tích hình tròn có bán kính đã cho
def area[radius]: a = math.pi * radius**2 return a
Chúng ta đã thấy câu lệnh return trước đây, nhưng trong một hàm hiệu quả, câu lệnh return bao gồm một biểu thức. Tuyên bố này có nghĩa là. “Trả về ngay từ hàm này và sử dụng biểu thức sau làm giá trị trả về. ” Biểu thức có thể phức tạp tùy ý, vì vậy chúng ta có thể viết hàm này ngắn gọn hơn
def area[radius]: return math.pi * radius**2
Mặt khác, các biến tạm thời như a có thể giúp gỡ lỗi dễ dàng hơn
Đôi khi sẽ rất hữu ích khi có nhiều câu lệnh trả về, mỗi câu lệnh trong một nhánh của câu điều kiện.
def absolute_value[x]: if x < 0: return -x else: return x
Vì các câu lệnh trả về này nằm trong một điều kiện thay thế, nên chỉ có một lần chạy
Ngay sau khi câu lệnh return chạy, hàm sẽ kết thúc mà không thực hiện bất kỳ câu lệnh nào tiếp theo. Mã xuất hiện sau câu lệnh trả về hoặc bất kỳ vị trí nào khác mà luồng thực thi không bao giờ có thể đạt được, được gọi là mã chết
Trong một chức năng hiệu quả, bạn nên đảm bảo rằng mọi đường dẫn có thể thông qua chương trình đều đạt được câu lệnh trả về. Ví dụ
def absolute_value[x]: if x < 0: return -x if x > 0: return x
Hàm này không chính xác vì nếu x xảy ra bằng 0, thì không có điều kiện nào là đúng và hàm kết thúc mà không nhấn câu lệnh trả về. Nếu luồng thực thi đến cuối một hàm, giá trị trả về là Không, không phải là giá trị tuyệt đối của 0
>>> print[absolute_value[0]] None
Nhân tiện, Python cung cấp một hàm dựng sẵn gọi là abs để tính các giá trị tuyệt đối
Như một bài tập, hãy viết hàm so sánh nhận hai giá trị x và y và trả về 1 nếu x > y, 0 nếu x == y và -1 nếu x < y
6. 2 Phát triển gia tăng
Khi bạn viết các hàm lớn hơn, bạn có thể thấy mình dành nhiều thời gian hơn để gỡ lỗi
Để xử lý các chương trình ngày càng phức tạp, bạn có thể muốn thử một quy trình gọi là phát triển gia tăng. Mục tiêu của phát triển gia tăng là để tránh các phiên sửa lỗi dài bằng cách chỉ thêm và kiểm tra một lượng nhỏ mã tại một thời điểm
Ví dụ: giả sử bạn muốn tìm khoảng cách giữa hai điểm, được cho bởi tọa độ [x1, y1] và [x2, y2]. Theo định lý Pitago, khoảng cách là
Bước đầu tiên là xem xét hàm khoảng cách sẽ trông như thế nào trong Python. Nói cách khác, đầu vào [tham số] là gì và đầu ra [giá trị trả về] là gì?
Trong trường hợp này, đầu vào là hai điểm mà bạn có thể biểu diễn bằng bốn số. Giá trị trả về là khoảng cách được biểu thị bằng giá trị dấu phẩy động
Ngay lập tức bạn có thể viết một phác thảo của chức năng
def distance[x1, y1, x2, y2]: return 0.0
Rõ ràng, phiên bản này không tính khoảng cách; . Nhưng nó đúng về mặt cú pháp và nó chạy, nghĩa là bạn có thể kiểm tra nó trước khi làm cho nó phức tạp hơn
Để kiểm tra chức năng mới, hãy gọi nó với các đối số mẫu
>>> distance[1, 2, 4, 6] 0.0
Tôi đã chọn các giá trị này sao cho khoảng cách theo chiều ngang là 3 và khoảng cách theo chiều dọc là 4; . Khi kiểm tra một chức năng, thật hữu ích khi biết câu trả lời đúng
Tại thời điểm này, chúng tôi đã xác nhận rằng chức năng này đúng về mặt cú pháp và chúng tôi có thể bắt đầu thêm mã vào phần thân. Bước tiếp theo hợp lý là tìm sự khác biệt x2 − x1 và y2 − y1. Phiên bản tiếp theo lưu trữ các giá trị đó trong các biến tạm thời và in chúng
________số 8Nếu chức năng đang hoạt động, nó sẽ hiển thị
def absolute_value[x]: if x < 0: return -x else: return x1 và
def absolute_value[x]: if x < 0: return -x else: return x2. Nếu vậy, chúng ta biết rằng hàm đang nhận đúng đối số và thực hiện phép tính đầu tiên một cách chính xác. Nếu không, chỉ có một vài dòng để kiểm tra
Tiếp theo chúng ta tính tổng bình phương của dx và dy
def area[radius]: a = math.pi * radius**2 return a1
Một lần nữa, bạn sẽ chạy chương trình ở giai đoạn này và kiểm tra đầu ra [phải là 25]. Cuối cùng, bạn có thể sử dụng toán học. sqrt để tính toán và trả về kết quả
def area[radius]: a = math.pi * radius**2 return a0
Nếu nó hoạt động chính xác, bạn đã hoàn thành. Nếu không, bạn có thể muốn in giá trị của kết quả trước câu lệnh return
Phiên bản cuối cùng của chức năng không hiển thị bất cứ thứ gì khi nó chạy; . Các câu lệnh in mà chúng tôi đã viết rất hữu ích cho việc gỡ lỗi, nhưng khi chức năng này hoạt động, bạn nên xóa chúng. Mã như vậy được gọi là giàn giáo vì nó hữu ích cho việc xây dựng chương trình nhưng không phải là một phần của sản phẩm cuối cùng
Khi mới bắt đầu, bạn chỉ nên thêm một hoặc hai dòng mã mỗi lần. Khi bạn có nhiều kinh nghiệm hơn, bạn có thể thấy mình đang viết và gỡ lỗi các phần lớn hơn. Dù bằng cách nào, phát triển gia tăng có thể giúp bạn tiết kiệm rất nhiều thời gian gỡ lỗi
Các khía cạnh quan trọng của quá trình là
- Bắt đầu với một chương trình đang hoạt động và thực hiện các thay đổi gia tăng nhỏ. Tại bất kỳ thời điểm nào, nếu có lỗi, bạn nên biết nó ở đâu
- Sử dụng các biến để giữ các giá trị trung gian để bạn có thể hiển thị và kiểm tra chúng
- Sau khi chương trình hoạt động, bạn có thể muốn loại bỏ một số phần mở rộng hoặc hợp nhất nhiều câu lệnh thành các biểu thức phức hợp, nhưng chỉ khi điều đó không làm cho chương trình khó đọc
Như một bài tập, hãy sử dụng khai triển lũy thừa để viết một hàm gọi là cạnh huyền trả về độ dài cạnh huyền của một tam giác vuông với độ dài của hai cạnh góc vuông làm đối số. Ghi lại từng giai đoạn của quá trình phát triển khi bạn thực hiện
6. 3 Thành phần
Như bạn mong đợi bây giờ, bạn có thể gọi một chức năng từ bên trong một chức năng khác. Ví dụ, chúng ta sẽ viết một hàm lấy hai điểm, tâm của hình tròn và một điểm trên chu vi, rồi tính diện tích hình tròn
Giả sử rằng điểm trung tâm được lưu trữ trong các biến xc và yc, và điểm chu vi là xp và yp. Bước đầu tiên là tìm bán kính của đường tròn, là khoảng cách giữa hai điểm. Chúng tôi vừa viết một hàm, khoảng cách, để thực hiện điều đó
def area[radius]: a = math.pi * radius**2 return a1
Bước tiếp theo là tìm diện tích hình tròn có bán kính đó;
def area[radius]: a = math.pi * radius**2 return a2
Đóng gói các bước này trong một hàm, chúng tôi nhận được
def area[radius]: a = math.pi * radius**2 return a3
Các biến tạm thời bán kính và kết quả rất hữu ích cho việc phát triển và gỡ lỗi, nhưng khi chương trình hoạt động, chúng ta có thể làm cho nó ngắn gọn hơn bằng cách soạn các lệnh gọi hàm
def area[radius]: a = math.pi * radius**2 return a4
6. 4 Hàm Boolean
Các hàm có thể trả về các phép toán luận, điều này thường thuận tiện cho việc ẩn các kiểm tra phức tạp bên trong các hàm. Ví dụ
def area[radius]: a = math.pi * radius**2 return a5
Người ta thường đặt tên hàm boolean giống như câu hỏi có/không;
Đây là một ví dụ
def area[radius]: a = math.pi * radius**2 return a6
Kết quả của toán tử == là một boolean, vì vậy chúng ta có thể viết hàm ngắn gọn hơn bằng cách trả về trực tiếp
def area[radius]: a = math.pi * radius**2 return a7
Các hàm Boolean thường được sử dụng trong câu lệnh điều kiện
def area[radius]: a = math.pi * radius**2 return a8
Nó có thể hấp dẫn để viết một cái gì đó như
def area[radius]: a = math.pi * radius**2 return a9
Nhưng so sánh thêm là không cần thiết
Để làm bài tập, hãy viết một hàm
def absolute_value[x]: if x < 0: return -x else: return x4 trả về True nếu x ≤ y ≤ z hoặc False nếu ngược lại
6. 5 Thêm đệ quy
Chúng tôi chỉ đề cập đến một tập hợp con nhỏ của Python, nhưng bạn có thể muốn biết rằng tập hợp con này là một ngôn ngữ lập trình hoàn chỉnh, có nghĩa là mọi thứ có thể tính toán được đều có thể được thể hiện bằng ngôn ngữ này. Bất kỳ chương trình nào từng được viết đều có thể được viết lại chỉ bằng các tính năng ngôn ngữ mà bạn đã học cho đến nay [thực tế, bạn sẽ cần một vài lệnh để điều khiển các thiết bị như chuột, đĩa, v.v. , Nhưng đó là tất cả]
Chứng minh tuyên bố đó là một bài tập không cần thiết lần đầu tiên được thực hiện bởi Alan Turing, một trong những nhà khoa học máy tính đầu tiên [một số người cho rằng ông là một nhà toán học, nhưng rất nhiều nhà khoa học máy tính ban đầu bắt đầu là nhà toán học]. Theo đó, nó được gọi là Luận án Turing. Để thảo luận đầy đủ hơn [và chính xác] về Luận án Turing, tôi giới thiệu cuốn sách của Michael Sipser Giới thiệu về lý thuyết tính toán
Để cung cấp cho bạn ý tưởng về những gì bạn có thể làm với các công cụ bạn đã học cho đến nay, chúng tôi sẽ đánh giá một số hàm toán học được xác định đệ quy. Định nghĩa đệ quy tương tự như định nghĩa vòng tròn, theo nghĩa là định nghĩa chứa tham chiếu đến đối tượng được định nghĩa. Một định nghĩa vòng tròn thực sự không hữu ích lắm
vorpal. Một tính từ được sử dụng để mô tả một cái gì đó là vorpalNếu bạn thấy định nghĩa đó trong từ điển, bạn có thể khó chịu. Mặt khác, nếu bạn tra cứu định nghĩa của hàm giai thừa, được biểu thị bằng ký hiệu. , bạn có thể nhận được một cái gì đó như thế này
Định nghĩa này nói rằng giai thừa của 0 là 1 và giai thừa của bất kỳ giá trị nào khác, n, là n nhân với giai thừa của n−1
Vậy 3. là 3 nhân 2. , tức là 2 nhân 1. , tức là 1 nhân 0. Đặt tất cả lại với nhau, 3. bằng 3 nhân 2 nhân 1 nhân 1, bằng 6
Nếu bạn có thể viết định nghĩa đệ quy của một thứ gì đó, bạn có thể viết một chương trình Python để đánh giá nó. Bước đầu tiên là quyết định các tham số sẽ là gì. Trong trường hợp này, rõ ràng giai thừa nhận một số nguyên
def area[radius]: return math.pi * radius**20
Nếu đối số xảy ra là 0, tất cả những gì chúng ta phải làm là trả về 1
def area[radius]: return math.pi * radius**21
Mặt khác, và đây là phần thú vị, chúng ta phải thực hiện một lời gọi đệ quy để tìm giai thừa của n−1 và sau đó nhân nó với n
def area[radius]: return math.pi * radius**22
Quy trình thực thi của chương trình này tương tự như quy trình đếm ngược trong Phần 5. 8. Nếu chúng ta gọi giai thừa với giá trị 3
Vì 3 khác 0 nên ta lấy nhánh thứ 2 và tính giai thừa của n-1
Vì 2 khác 0 nên ta lấy nhánh thứ 2 và tính giai thừa của n-1Vì 1 khác 0 nên ta lấy nhánh thứ 2 và tính giai thừa của n-1Vì 0 bằng 0, chúng tôi lấy nhánh đầu tiên và trả về 1 mà không thực hiện bất kỳ cuộc gọi đệ quy nào nữaGiá trị trả về, 1, được nhân với n, là 1 và kết quả được trả về
Giá trị trả về, 1, được nhân với n, là 2 và kết quả được trả về
Giá trị trả về [2] được nhân với n, là 3 và kết quả là 6, trở thành giá trị trả về của lệnh gọi hàm bắt đầu toàn bộ quá trình
Hình 6. 1 cho biết sơ đồ ngăn xếp trông như thế nào đối với chuỗi lệnh gọi hàm này
Hình 6. 1. sơ đồ ngăn xếp
Các giá trị trả về được hiển thị đang được chuyển ngược lên ngăn xếp. Trong mỗi khung, giá trị trả về là giá trị của kết quả, là tích của n và lặp lại
Trong khung cuối cùng, các biến cục bộ lặp lại và kết quả không tồn tại, bởi vì nhánh tạo ra chúng không chạy
6. 6 Bước nhảy vọt của niềm tin
Theo dòng thực thi là một cách để đọc chương trình, nhưng nó có thể nhanh chóng trở nên quá sức. Một giải pháp thay thế mà tôi gọi là “bước nhảy vọt của niềm tin”. Khi bạn đến một lệnh gọi hàm, thay vì tuân theo luồng thực thi, bạn cho rằng hàm hoạt động chính xác và trả về kết quả đúng
Trên thực tế, bạn đã thực hành bước nhảy vọt về niềm tin này khi sử dụng các chức năng tích hợp sẵn. Khi bạn gọi toán học. cos hoặc toán học. exp, bạn không kiểm tra phần thân của các chức năng đó. Bạn cứ cho rằng chúng hoạt động vì những người viết các hàm dựng sẵn là những lập trình viên giỏi
Điều này cũng đúng khi bạn gọi một trong các chức năng của riêng mình. Ví dụ: trong Phần 6. 4, chúng tôi đã viết một hàm có tên là
def absolute_value[x]: if x < 0: return -x else: return x3 để xác định xem một số có chia hết cho số khác hay không. Khi chúng tôi đã tự thuyết phục rằng chức năng này là chính xác—bằng cách kiểm tra mã và thử nghiệm—chúng tôi có thể sử dụng chức năng mà không cần nhìn lại phần thân
Điều này cũng đúng với các chương trình đệ quy. Khi bạn đến lời gọi đệ quy, thay vì tuân theo luồng thực thi, bạn nên giả sử rằng lời gọi đệ quy hoạt động [trả về kết quả chính xác] và sau đó tự hỏi: “Giả sử rằng tôi có thể tìm giai thừa của n−1, liệu tôi có thể tìm được giai thừa của n−1 không?
Tất nhiên, hơi lạ khi cho rằng chức năng này hoạt động chính xác khi bạn chưa viết xong, nhưng đó là lý do tại sao nó được gọi là bước nhảy vọt của niềm tin
6. 7 Một ví dụ nữa
Sau giai thừa, ví dụ phổ biến nhất của hàm toán học được xác định đệ quy là fibonacci, hàm này có định nghĩa sau [xem http. // vi. wikipedia. org/wiki/Fibonacci_number]
fibonacci[0] = 0 fibonacci[1] = 1 fibonacci[n] = fibonacci[n−1] + fibonacci[n−2]Được dịch sang Python, nó trông như thế này
def area[radius]: return math.pi * radius**23
Nếu bạn cố gắng làm theo quy trình thực hiện ở đây, ngay cả đối với các giá trị khá nhỏ của n, đầu bạn sẽ nổ tung. Nhưng theo bước nhảy vọt của niềm tin, nếu bạn cho rằng hai lệnh gọi đệ quy hoạt động chính xác, thì rõ ràng là bạn sẽ nhận được kết quả đúng bằng cách cộng chúng lại với nhau
6. 8 Các loại kiểm tra
Điều gì xảy ra nếu chúng ta gọi giai thừa và cho nó 1. 5 như một đối số?
def area[radius]: return math.pi * radius**24
Nó trông giống như một đệ quy vô hạn. Làm thế nào mà có thể được? . Nhưng nếu n không phải là số nguyên, chúng ta có thể bỏ lỡ trường hợp cơ bản và lặp lại mãi mãi
Trong lần gọi đệ quy đầu tiên, giá trị của n là 0. 5. Tiếp theo là -0. 5. Từ đó, nó nhỏ dần [âm hơn], nhưng sẽ không bao giờ bằng 0
Chúng ta có hai sự lựa chọn. Chúng ta có thể cố gắng khái quát hóa hàm giai thừa để hoạt động với các số dấu phẩy động hoặc chúng ta có thể thực hiện kiểm tra giai thừa loại đối số của nó. Tùy chọn đầu tiên được gọi là hàm gamma và nó nằm ngoài phạm vi của cuốn sách này một chút. Vì vậy, chúng tôi sẽ đi cho lần thứ hai
Chúng ta có thể sử dụng hàm isinstance tích hợp để xác minh loại đối số. Trong khi chúng tôi đang ở đó, chúng tôi cũng có thể đảm bảo đối số là tích cực
def area[radius]: return math.pi * radius**25
Trường hợp cơ sở đầu tiên xử lý các số không phải là số nguyên; . Trong cả hai trường hợp, chương trình sẽ in thông báo lỗi và trả về Không có để cho biết đã xảy ra sự cố
def area[radius]: return math.pi * radius**26
Nếu chúng ta vượt qua cả hai lần kiểm tra, chúng ta biết rằng n là một số nguyên không âm, vì vậy chúng ta có thể chứng minh rằng đệ quy kết thúc
Chương trình này thể hiện một mẫu đôi khi được gọi là người giám hộ. Hai điều kiện đầu tiên đóng vai trò là người bảo vệ, bảo vệ mã theo sau khỏi các giá trị có thể gây ra lỗi. Những người bảo vệ có thể chứng minh tính đúng đắn của mã
Trong Phần 11. 4, chúng ta sẽ thấy một giải pháp thay thế linh hoạt hơn để in thông báo lỗi. nâng cao một ngoại lệ
6. 9 Gỡ lỗi
Việc chia một chương trình lớn thành các chức năng nhỏ hơn sẽ tạo ra các điểm kiểm tra tự nhiên để gỡ lỗi. Nếu một chức năng không hoạt động, có ba khả năng để xem xét
- Có điều gì đó không ổn với các đối số mà hàm đang nhận được;
- Có điều gì đó không ổn với chức năng;
- Có điều gì đó không ổn với giá trị trả về hoặc cách nó đang được sử dụng
Để loại trừ khả năng đầu tiên, bạn có thể thêm câu lệnh in ở đầu hàm và hiển thị giá trị của các tham số [và có thể là kiểu của chúng]. Hoặc bạn có thể viết mã để kiểm tra các điều kiện tiên quyết một cách rõ ràng
Nếu các tham số có vẻ ổn, hãy thêm một câu lệnh in trước mỗi câu lệnh trả về và hiển thị giá trị trả về. Nếu có thể, hãy kiểm tra kết quả bằng tay. Cân nhắc việc gọi hàm với các giá trị giúp bạn dễ dàng kiểm tra kết quả [như trong Phần 6. 2]
Nếu hàm có vẻ đang hoạt động, hãy xem lệnh gọi hàm để đảm bảo rằng giá trị trả về đang được sử dụng đúng cách [hoặc hoàn toàn được sử dụng. ]
Việc thêm các câu lệnh in vào đầu và cuối hàm có thể giúp làm cho luồng thực thi rõ ràng hơn. Ví dụ, đây là phiên bản giai thừa với câu lệnh in
def area[radius]: return math.pi * radius**27
khoảng trắng là một chuỗi các ký tự khoảng trắng điều khiển thụt đầu dòng của đầu ra. Đây là kết quả của giai thừa [4]
def area[radius]: return math.pi * radius**28
Nếu bạn bối rối về quy trình thực thi, loại đầu ra này có thể hữu ích. Phải mất một thời gian để phát triển giàn giáo hiệu quả, nhưng một chút giàn giáo có thể tiết kiệm rất nhiều gỡ lỗi
6. 10 Bảng thuật ngữ
biến tạm thời. Một biến được sử dụng để lưu trữ một giá trị trung gian trong một phép tính phức tạp. mã chết. Một phần của chương trình không bao giờ chạy được, thường là do nó xuất hiện sau câu lệnh return. phát triển gia tăng. Một kế hoạch phát triển chương trình nhằm tránh gỡ lỗi bằng cách chỉ thêm và kiểm tra một lượng nhỏ mã tại một thời điểm. đoạn đầu đài. Mã được sử dụng trong quá trình phát triển chương trình nhưng không phải là một phần của phiên bản cuối cùng. người giám hộ. Một mẫu lập trình sử dụng câu lệnh có điều kiện để kiểm tra và xử lý các trường hợp có thể gây ra lỗi6. 11 Bài tập
Bài tập 1
Vẽ sơ đồ ngăn xếp cho chương trình sau. Chương trình in gì?
def area[radius]: return math.pi * radius**29
Bài tập 3
Palindrome là một từ được đánh vần ngược và xuôi giống nhau, như “noon” và “redivider”. Theo đệ quy, một từ là một bảng màu nếu các chữ cái đầu tiên và cuối cùng giống nhau và ở giữa là một bảng chữ cái
Sau đây là các hàm nhận một đối số chuỗi và trả về các chữ cái đầu tiên, cuối cùng và ở giữa
def absolute_value[x]: if x < 0: return -x else: return x0
Chúng ta sẽ xem cách chúng hoạt động trong Chương 8
- Nhập các chức năng này vào một tệp có tên là palindrome. py và kiểm tra chúng. Điều gì xảy ra nếu bạn gọi giữa bằng một chuỗi có hai chữ cái?
- Viết một hàm có tên là
def absolute_value[x]: if x < 0: return -x else: return x
7 nhận vào một đối số chuỗi và trả về True nếu nó là một đối số và trả về False nếu ngược lại. Hãy nhớ rằng bạn có thể sử dụng hàm có sẵn len để kiểm tra độ dài của chuỗi
Dung dịch. http. //nghĩpython2. com/code/palindrome_soln. py
Bài tập 4
Một số, a, là lũy thừa của b nếu nó chia hết cho b và a/b là lũy thừa của b. Viết hàm có tên là
def absolute_value[x]: if x < 0: return -x else: return x8 nhận tham số a và b và trả về giá trị True nếu a là lũy thừa của b. Ghi chú. bạn sẽ phải suy nghĩ về trường hợp cơ bản
Bài tập 5
Ước chung lớn nhất [GCD] của a và b là số lớn nhất chia hết cho cả hai mà không có số dư
Một cách để tìm GCD của hai số dựa trên nhận xét rằng nếu r là số dư khi a chia cho b, thì gcd[a, b] = gcd[b, r]. Trong trường hợp cơ bản, chúng ta có thể sử dụng gcd[a, 0] = a
Viết một hàm có tên là
def absolute_value[x]: if x < 0: return -x else: return x9 nhận các tham số a và b và trả về ước chung lớn nhất của chúng
Tín dụng. Bài tập này dựa trên một ví dụ từ Cấu trúc và diễn giải các chương trình máy tính của Abelson và Sussman