You are on page 1of 244

Machine Translated by Google

Hãy nghĩ về Python

Làm thế nào để suy nghĩ như một nhà khoa học máy tính

Phiên bản thứ 2, Phiên bản 2.4.0


Machine Translated by Google
Machine Translated by Google

Hãy nghĩ về Python

Làm thế nào để suy nghĩ như một nhà khoa học máy tính

Phiên bản thứ 2, Phiên bản 2.4.0

Allen Downey

Máy ép trà xanh

Needham, Massachusetts
Machine Translated by Google

Bản quyền © 2015 Allen Downey.

Máy ép trà xanh


9 Washburn Ave
Needham MA 02492

Quyền được cấp để sao chép, phân phối và / hoặc sửa đổi tài liệu này theo các điều khoản của Giấy phép
Creative Commons Attribution-NonCommercial 3.0 Unported License, hiện có tại http: //creativecommons.org/
licenses/by-nc/3.0/.

Hình thức ban đầu của cuốn sách này là mã nguồn LATEX. Việc biên dịch nguồn LATEX này có tác dụng tạo ra
một bản trình bày sách giáo khoa không phụ thuộc vào thiết bị, có thể được chuyển đổi sang các định dạng
khác và in ra.

Nguồn LATEX cho cuốn sách này có tại http://www.thinkpython2.com


Machine Translated by Google

Lời nói đầu

Lịch sử kỳ lạ của cuốn sách này

Vào tháng 1 năm 1999, tôi đang chuẩn bị dạy một lớp lập trình nhập môn bằng Java. Tôi đã dạy nó ba lần và
tôi đã rất thất vọng. Tỷ lệ thất bại trong lớp quá cao và ngay cả đối với những học sinh đã thành công, mức
thành tích chung cũng quá thấp.

Một trong những vấn đề tôi thấy là sách. Chúng quá lớn, với quá nhiều chi tiết không cần thiết về Java và
không đủ hướng dẫn cấp cao về cách lập trình. Và tất cả họ đều phải gánh chịu hiệu ứng cửa bẫy: họ sẽ bắt
đầu dễ dàng, tiến hành dần dần, và rồi đâu đó xung quanh Chương 5, phần cuối sẽ rơi ra. Các sinh viên sẽ
nhận được quá nhiều tài liệu mới, quá nhanh, và tôi sẽ dành phần còn lại của học kỳ để nhặt các mảnh đó.

Hai tuần trước ngày học đầu tiên, tôi quyết định viết một cuốn sách của riêng mình. Mục tiêu của tôi là:

• Giữ cho nó ngắn. Học sinh nên đọc 10 trang sẽ tốt hơn là không đọc 50 trang.

• Cẩn thận với từ vựng. Lúc đầu, tôi đã cố gắng giảm thiểu biệt ngữ và xác định từng thuật ngữ
sử dụng.

• Xây dựng dần dần. Để tránh những cánh cửa sập bẫy, tôi lấy những chủ đề khó nhất và chia chúng thành
một loạt các bước nhỏ.

• Tập trung vào lập trình, không phải ngôn ngữ lập trình. Tôi đã bao gồm tập hợp con hữu ích tối thiểu
của Java và bỏ qua phần còn lại.

Tôi cần một danh hiệu, vì vậy tôi đã chọn Cách suy nghĩ như một nhà khoa học máy tính.

Phiên bản đầu tiên của tôi là khó khăn, nhưng nó đã hoạt động. Học sinh đã đọc, và họ hiểu đủ để tôi có
thể dành thời gian trong lớp cho những chủ đề khó, những chủ đề thú vị và (quan trọng nhất) để học sinh
luyện tập.

Tôi đã phát hành cuốn sách theo Giấy phép Tài liệu Miễn phí GNU, cho phép người dùng sao chép, sửa đổi và

phân phối sách.

Những gì xảy ra tiếp theo là một phần thú vị. Jeff Elkner, một giáo viên trung học ở Virginia, đã sử dụng
cuốn sách của tôi và dịch nó sang Python. Anh ấy đã gửi cho tôi bản sao bản dịch của anh ấy và tôi đã có
trải nghiệm khác thường khi học Python bằng cách đọc sách của chính mình. Với tư cách là Green Tea Press,
tôi đã xuất bản phiên bản Python đầu tiên vào năm 2001.

Năm 2003, tôi bắt đầu giảng dạy tại Đại học Olin và lần đầu tiên tôi được dạy Python. Sự tương phản với
Java rất đáng chú ý. Học sinh phải vật lộn ít hơn, học hỏi nhiều hơn, làm việc với nhiều dự án thú vị hơn
và nói chung là có nhiều niềm vui hơn.
Machine Translated by Google

vi Chương 0. Lời nói đầu

Kể từ đó, tôi tiếp tục phát triển cuốn sách, sửa lỗi, cải thiện một số ví dụ và bổ sung tài liệu, đặc biệt
là các bài tập.

Kết quả là cuốn sách này, bây giờ với tiêu đề ít hoành tráng hơn Think Python. Một số thay đổi
là:

• Tôi đã thêm một phần về gỡ lỗi ở cuối mỗi chương. Các phần này trình bày các kỹ thuật chung để tìm
và tránh lỗi, cũng như cảnh báo về việc sập hố trong Python.

• Tôi đã thêm nhiều bài tập hơn, từ các bài kiểm tra ngắn về sự hiểu biết đến một vài dự án quan trọng.
Hầu hết các bài tập bao gồm một liên kết đến giải pháp của tôi.

• Tôi đã thêm một loạt các nghiên cứu điển hình — các ví dụ dài hơn với các bài tập, giải pháp và lỗi
cussion.

• Tôi mở rộng thảo luận về các kế hoạch phát triển chương trình và các mẫu thiết kế cơ bản.

• Tôi đã thêm các phụ lục về gỡ lỗi và phân tích các thuật toán.

Phiên bản thứ hai của Think Python có các tính năng mới này:

• Cuốn sách và tất cả mã hỗ trợ đã được cập nhật lên Python 3.

• Tôi đã thêm một số phần và nhiều chi tiết hơn trên web, để giúp người mới bắt đầu chạy Python trong
trình duyệt, vì vậy bạn không cần phải cài đặt Python cho đến khi bạn muốn.

• Đối với Chương 4.1, tôi đã chuyển từ gói đồ họa rùa của riêng mình, được gọi là Swampy, sang một mô-
đun Python tiêu chuẩn hơn, rùa, dễ cài đặt hơn và mạnh mẽ hơn.

• Tôi đã thêm một chương mới có tên “The Goodies”, giới thiệu một số bổ sung
Các tính năng của Python không hoàn toàn cần thiết nhưng đôi khi rất tiện dụng.

Tôi hy vọng bạn thích làm việc với cuốn sách này và nó giúp bạn học cách lập trình và suy nghĩ như một nhà
khoa học máy tính, ít nhất là một chút.

Allen B. Downey

Cao đẳng Olin

Sự nhìn nhận
Rất cám ơn Jeff Elkner, người đã dịch cuốn sách Java của tôi sang Python, người đã bắt đầu dự án này và
giới thiệu cho tôi những gì đã trở thành ngôn ngữ yêu thích của tôi.

Cũng xin cảm ơn Chris Meyers, người đã đóng góp một số phần cho How to Think Like a Com puter Scientist.

Cảm ơn Tổ chức Phần mềm Tự do đã phát triển Li cense Tài liệu Miễn phí GNU, tài liệu đã giúp tôi hợp tác
với Jeff và Chris trở nên khả thi, và Creative Commons cho giấy phép tôi đang sử dụng bây giờ.
Machine Translated by Google

vii

Cảm ơn các biên tập viên tại Lulu, những người đã làm việc về Cách suy nghĩ như một nhà khoa học máy tính.

Cảm ơn các biên tập viên tại O'Reilly Media, những người đã làm việc trên Think Python.

Cảm ơn tất cả các sinh viên đã làm việc với các phiên bản trước của cuốn sách này và tất cả những người cố vấn

(được liệt kê bên dưới), những người đã gửi các bản chỉnh sửa và góp ý.

Danh sách cộng tác viên

Hơn 100 độc giả tinh tường và có tư duy đã gửi đến góp ý và chỉnh sửa trong vài năm qua. Những đóng góp và sự

nhiệt tình của họ cho dự án này đã giúp đỡ rất nhiều.

Nếu bạn có đề xuất hoặc sửa chữa, vui lòng gửi email đến feedback@thinkpython.com.

Nếu tôi thực hiện thay đổi dựa trên phản hồi của bạn, tôi sẽ thêm bạn vào danh sách cộng tác viên (trừ khi bạn

yêu cầu bỏ qua).

Nếu bạn bao gồm ít nhất một phần của câu mà lỗi xuất hiện, điều đó sẽ giúp tôi dễ dàng tìm kiếm. Số trang và phần

cũng tốt, nhưng không hoàn toàn dễ làm việc. Cảm ơn!

• Lloyd Hugh Allen đã gửi một bản chỉnh sửa cho Phần 8.4.

• Yvon Boulianne đã gửi bản sửa lỗi ngữ nghĩa trong Chương 5.

• Fred Bremmer đã đệ trình một bản chỉnh sửa trong Phần 2.1.

• Jonah Cohen đã viết các tập lệnh Perl để chuyển đổi nguồn LaTeX cho cuốn sách này thành đẹp
HTML.

• Michael Conlon đã gửi bản sửa ngữ pháp trong Chương 2 và cải tiến văn phong trong
Chương 1, và ông bắt đầu thảo luận về các khía cạnh kỹ thuật của thông dịch viên.

• Benoît Girard đã gửi bản sửa lỗi cho một lỗi hài hước trong Phần 5.6.

• Courtney Gleason và Katherine Smith đã viết Horsebet.py, được sử dụng như một nghiên cứu điển hình trong
phiên bản trước của cuốn sách. Chương trình của họ hiện có thể được tìm thấy trên trang web.

• Lee Harr đã gửi nhiều bản chỉnh sửa hơn số lượng chúng tôi có thể liệt kê ở đây, và thực sự thì anh ấy nên được

liệt kê là một trong những người chỉnh sửa chính của văn bản.

• James Kaylin là một sinh viên sử dụng văn bản. Anh ấy đã gửi nhiều bản chỉnh sửa.

• David Kershaw đã sửa chức năng catTwice bị hỏng trong Phần 3.10.

• Eddie Lam đã gửi nhiều bản chỉnh sửa cho các Chương 1, 2 và 3. Anh ấy cũng sửa Makefile để nó tạo một chỉ
mục ngay lần đầu tiên nó được chạy và giúp chúng tôi thiết lập một lược đồ lập phiên bản.

• Man-Yong Lee đã gửi sửa mã ví dụ trong Phần 2.4.

• David Mayo đã chỉ ra rằng từ “vô thức” trong Chương 1 cần được đổi thành
"Trong tiềm thức".

• Chris McAloon đã gửi một số chỉnh sửa cho Phần 3.9 và 3.10.

• Matthew J. Moelter đã là một cộng tác viên lâu năm, người đã gửi nhiều bản chỉnh sửa và
gợi ý cho cuốn sách.
Machine Translated by Google

viii Chương 0. Lời nói đầu

• Simon Dicon Montford đã báo cáo một định nghĩa hàm bị thiếu và một số lỗi chính tả trong Chương 3.

Ông cũng tìm thấy lỗi trong hàm tăng trong Chương 13.

• John Ouzts đã sửa lại định nghĩa về “giá trị trả về” trong Chương 3.

• Kevin Parks đã gửi những nhận xét và đề xuất có giá trị về cách cải thiện việc phân phối
của cuốn sách.

• David Pool đã gửi một lỗi chính tả trong phần chú giải của Chương 1, cũng như những lời động viên ân cần.

• Michael Schmitt đã gửi một bản chỉnh sửa cho chương về tệp và các trường hợp ngoại lệ.

• Robin Shaw đã chỉ ra một lỗi trong Phần 13.1, trong đó hàm printTime được sử dụng trong

ví dụ mà không được định nghĩa.

• Paul Sleigh tìm thấy một lỗi trong Chương 7 và một lỗi trong tập lệnh Perl của Jonah Cohen tạo ra
HTML từ LaTeX.

• Craig T. Snydal đang kiểm tra văn bản trong một khóa học tại Đại học Drew. Anh ấy đã đóng góp một số

góp ý và sửa chữa có giá trị.

• Ian Thomas và các sinh viên của ông đang sử dụng văn bản trong một khóa học lập trình. Họ là những người đầu tiên

kiểm tra các chương trong nửa sau của cuốn sách, và họ đã thực hiện nhiều chỉnh sửa và đề xuất.

• Keith Verheyden đã gửi một bản chỉnh sửa trong Chương 3.

• Peter Winstanley cho chúng tôi biết về một lỗi lâu dài trong tiếng Latinh của chúng tôi trong Chương 3.

• Chris Wrobel đã thực hiện các chỉnh sửa đối với mã trong chương về I / O tệp và các trường hợp ngoại lệ.

• Moshe Zadka đã có những đóng góp vô giá cho dự án này. Ngoài việc viết bản thảo đầu tiên của chương Từ điển, ông đã

liên tục hướng dẫn trong giai đoạn đầu của cuốn sách.

• Christoph Zwerschke đã gửi một số chỉnh sửa và đề xuất sư phạm, đồng thời giải thích

sự khác biệt giữa gleich và selbe.

• James Mayer đã gửi cho chúng tôi rất nhiều lỗi chính tả và lỗi đánh máy, bao gồm hai lỗi trong
danh sách cộng tác viên.

• Hayden McAfee nhận thấy sự mâu thuẫn có thể gây nhầm lẫn giữa hai ví dụ.

• Angel Arnal là thành viên của nhóm dịch giả quốc tế làm việc trên phiên bản tiếng Tây Ban Nha của

văn bản. Anh ấy cũng đã tìm thấy một số lỗi trong phiên bản tiếng Anh.

• Tauhidul Hoque và Lex Berezhny đã tạo ra các hình minh họa trong Chương 1 và cải thiện nhiều
của các hình minh họa khác.

• Tiến sĩ Michele Alzetta đã mắc lỗi trong Chương 8 và gửi một số com sư phạm thú vị

ments và gợi ý về Fibonacci và Old Maid.

• Andy Mitchell mắc lỗi đánh máy trong Chương 1 và một ví dụ hỏng trong Chương 2.

• Kalin Harvey đề nghị làm rõ trong Chương 7 và mắc một số lỗi chính tả.

• Christopher P. Smith đã mắc một số lỗi chính tả và giúp chúng tôi cập nhật sách cho Python 2.2.

• David Hutchins mắc lỗi đánh máy trong Lời nói đầu.

• Gregor Lingl đang dạy Python tại một trường trung học ở Vienna, Áo. Anh ấy đang làm việc trên một chiếc Ger

bản dịch của người đàn ông của cuốn sách, và anh ta đã mắc một số lỗi tồi tệ trong Chương 5.
Machine Translated by Google

ix

• Julie Peters mắc lỗi đánh máy trong Lời nói đầu.

• Florin Oprina đã gửi cải tiến về makeTime, chỉnh sửa printTime và lỗi đánh máy đẹp.

• DJ Webre đề nghị làm rõ trong Chương 3.

• Ken đã tìm thấy rất nhiều lỗi trong các Chương 8, 9 và 11.

• Ivo Wever mắc lỗi đánh máy ở Chương 5 và đề nghị làm rõ trong Chương 3.

• Curtis Yanko đề nghị làm rõ trong Chương 2.

• Ben Logan đã gửi một số lỗi chính tả và các vấn đề khi dịch cuốn sách sang HTML.

• Jason Armstrong đã nhìn thấy từ còn thiếu trong Chương 2.

• Louis Cordier nhận thấy một điểm trong Chương 16 mà mã không khớp với văn bản.

• Brian Cain đề xuất một số điều làm rõ trong Chương 2 và 3.

• Rob Black đã gửi một bản sửa lỗi, bao gồm một số thay đổi cho Python 2.2.

• Jean-Philippe Rey tại École Centrale Paris đã gửi một số bản vá, bao gồm một số bản cập nhật
cho Python 2.2 và các cải tiến chu đáo khác.

• Jason Mader tại Đại học George Washington đã đưa ra một số đề xuất hữu ích và cor
sự truyền tụng.

• Jan Gundtofte-Bruun đã nhắc nhở chúng ta rằng “một lỗi” là một lỗi.

• Abel David và Alexis Dinno đã nhắc nhở chúng ta rằng số nhiều của “ma trận” là “ma trận”, không phải “ma
trixes”. Lỗi này đã có trong cuốn sách trong nhiều năm, nhưng hai độc giả có tên viết tắt giống nhau đã báo
cáo nó trong cùng một ngày. Kỳ dị.

• Charles Thayer khuyến khích chúng tôi loại bỏ dấu chấm phẩy mà chúng tôi đã đặt ở cuối một số câu lệnh và loại
bỏ việc sử dụng “đối số” và “tham số”.

• Roger Sperberg đã chỉ ra một phần logic xoắn trong Chương 3.

• Sam Bull đã chỉ ra một đoạn khó hiểu trong Chương 2.

• Andrew Cheung đã chỉ ra hai trường hợp “sử dụng trước def”.

• C. Corey Capel đã phát hiện ra từ còn thiếu trong Định lý thứ ba về gỡ lỗi và một lỗi đánh máy trong
Chương 4.

• Alessandra đã giúp làm sáng tỏ một số nhầm lẫn của Turtle.

• Wim Champagne đã tìm thấy một chữ o trong một ví dụ từ điển.

• Douglas Wright đã chỉ ra một vấn đề với việc phân chia tầng theo cung tròn.

• Jared Spindor tìm thấy một số jetsam ở cuối câu.

• Lin Peiheng đã gửi một số gợi ý rất hữu ích.

• Ray Hagtvedt đã gửi hai lỗi và một lỗi không khá.

• Torsten Hübsch đã chỉ ra sự mâu thuẫn trong Swampy.

• Inga Petuhhov đã sửa lại một ví dụ trong Chương 14.

• Arne Babenhauserheide đã gửi một số sửa chữa hữu ích.


Machine Translated by Google

x Chương 0. Lời nói đầu

• Mark E. Casida rất giỏi trong việc phát hiện những từ lặp đi lặp lại.

• Scott Tyler điền vào chỗ còn thiếu. Và sau đó được gửi trong một đống sửa chữa.

• Gordon Shephard đã gửi một số bản chỉnh sửa, tất cả đều được gửi trong các email riêng biệt.

• Andrew Turner đã phát hiện ra một lỗi trong Chương 8.

• Adam Hobart đã khắc phục sự cố với việc phân chia tầng theo cung tròn.

• Daryl Hammond và Sarah Zimmerman đã chỉ ra rằng tôi đã phục vụ toán học .pi quá sớm. Và

Zim đã phát hiện ra một lỗi đánh máy.

• George Sass đã tìm thấy một lỗi trong phần Gỡ lỗi.

• Brian Bingham gợi ý Bài tập 11.5.

• Leah Engelbert-Fenton đã chỉ ra rằng tôi đã sử dụng tuple làm tên biến, trái ngược với tên riêng của tôi

lời khuyên. Và sau đó tìm thấy một loạt lỗi chính tả và một "sử dụng trước def".

• Joe Funke phát hiện lỗi đánh máy.

• Chao-chao Chen nhận thấy sự mâu thuẫn trong ví dụ Fibonacci.

• Jeff Paine biết sự khác biệt giữa không gian và thư rác.

• Lubos Pintes bị lỗi đánh máy.

• Gregg Lind và Abigail Heithoff đề xuất Bài tập 14.3.

• Max Hailperin đã gửi một số chỉnh sửa và đề xuất. Max là một trong những tác giả của cuốn sách Tóm tắt cụ thể phi

thường mà bạn có thể muốn đọc khi hoàn thành cuốn sách này.

• Chotipat Pornavalai tìm thấy lỗi trong một thông báo lỗi.

• Stanislaw Antol đã gửi một danh sách các gợi ý rất hữu ích.

• Eric Pashman đã gửi một số chỉnh sửa cho Chương 4–11.

• Miguel Azevedo phát hiện một số lỗi chính tả.

• Jianhua Liu đã gửi một danh sách dài các bản chỉnh sửa.

• Nick King tìm thấy một từ còn thiếu.

• Martin Zuther đã gửi một danh sách dài các đề xuất.

• Adam Zimmerman nhận thấy sự mâu thuẫn trong trường hợp của tôi về một "trường hợp" và một số trường hợp khác
các lỗi.

• Ratnakar Tiwari đề xuất một chú thích cuối trang giải thích các tam giác suy biến.

• Anurag Goel đề xuất một giải pháp khác cho is_abecedarian và gửi một số hiệu chỉnh bổ sung. Và anh ấy biết cách đánh

vần Jane Austen.

• Kelli Kratzer đã phát hiện ra một trong những lỗi chính tả.

• Mark Griffiths đã chỉ ra một ví dụ khó hiểu trong Chương 3.

• Roydan Ongie đã tìm thấy một lỗi trong phương pháp Newton của tôi.

• Patryk Wolowiec đã giúp tôi giải quyết vấn đề trong phiên bản HTML.
Machine Translated by Google

xi

• Mark Chonofsky đã nói với tôi về một từ khóa mới trong Python 3.

• Russell Coleman đã giúp tôi về hình học.

• Nam Nguyen tìm thấy lỗi đánh máy và chỉ ra rằng tôi đã sử dụng Decorator pattern nhưng không phải nam

tion nó theo tên.

• Stéphane Morin đã gửi một số sửa chữa và đề xuất.

• Paul Stoop đã sửa lỗi đánh máy trong use_only.

• Eric Bronner đã chỉ ra một sự nhầm lẫn trong cuộc thảo luận về thứ tự hoạt động.

• Alexandros Gezerlis đặt ra một tiêu chuẩn mới về số lượng và chất lượng của các đề xuất mà anh ta gửi ted.

Chúng tôi vô cùng biết ơn!

• Gray Thomas biết bên phải của mình từ bên trái.

• Giovanni Escobar Sosa đã gửi một danh sách dài các đề xuất và chỉnh sửa.

• Daniel Neilson đã sửa một lỗi về thứ tự hoạt động.

• Will McGinnis chỉ ra rằng polyline được định nghĩa khác nhau ở hai nơi.

• Frank Hecker đã chỉ ra một bài tập không được chỉ rõ và một số liên kết bị hỏng.

• Animesh B đã giúp tôi làm sạch một ví dụ khó hiểu.

• Martin Caspersen tìm thấy hai lỗi vòng tròn.

• Gregor Ulm đã gửi một số đề xuất và chỉnh sửa.

• Dimitrios Tsirigkas đề nghị tôi làm rõ một bài tập.

• Carlos Tafur đã gửi một trang sửa chữa và đề xuất.

• Martin Nordsletten đã tìm thấy một lỗi trong một giải pháp bài tập.

• Sven Hoexter chỉ ra rằng một biến có tên đầu vào sẽ làm bóng một hàm tích hợp.

• Stephen Gregory đã chỉ ra vấn đề với cmp trong Python 3.

• Ishwar Bhat đã sửa lại tuyên bố của tôi về định lý cuối cùng của Fermat.

• Andrea Zanella đã dịch cuốn sách sang tiếng Ý và gửi một số bản sửa chữa cùng

đường.

• Rất cám ơn Melissa Lewis và Luciano Ramalho vì những nhận xét và đề xuất xuất sắc trong lần xuất bản thứ hai.

• Cảm ơn Harry Percival từ PythonAnywhere vì đã giúp mọi người bắt đầu chạy Python trong trình duyệt.

• Xavier Van Aubel đã thực hiện một số chỉnh sửa hữu ích trong lần xuất bản thứ hai.

• William Murray đã sửa lại định nghĩa của tôi về phân chia tầng.

• Per Starbäck đã cập nhật cho tôi về các dòng mới phổ biến trong Python 3.

• Laurent Rosenfeld và Mihaela Rotaru đã dịch cuốn sách này sang tiếng Pháp. Trên đường đi, họ

đã gửi nhiều chỉnh sửa và góp ý.

Ngoài ra, những người phát hiện lỗi chính tả hoặc sửa chữa bao gồm Czeslaw Czapla, Dale Wil son, Francesco
Carlo Cimini, Richard Fursa, Brian McGhie, Lokesh Kumar Makani, Matthew Shultz, Viet Le, Victor Simeone, Lars

OD Christensen, Swarup Sahoo, Alix Etienne , Kuang He, Wei Huang, Karen Barber và Eric Ransom.
Machine Translated by Google

xii Chương 0. Lời nói đầu


Machine Translated by Google

Nội dung

Lời nói đầu v

1 Cách thức của chương trình 1

1.1 Chương trình là gì? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

1.2 Chạy Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

1.3 Chương trình đầu tiên. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

1.4 Các toán tử số học. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

1.5 Giá trị và kiểu. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

1.6 Ngôn ngữ trang trọng và tự nhiên. . . . . . . . . . . . . . . . . . . . . . . . . . 4

1.7 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

1.8 Bảng chú giải thuật ngữ. . .


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

1,9 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2 Biến, biểu thức và câu lệnh 9

2.1 Các câu lệnh gán. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

2.2 Tên biến. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

2.3 Biểu thức và câu lệnh. . . . . . . . . . . . . . . . . . . . . . . . . . . 10

2,4 Chế độ tập lệnh. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

2.5 Thứ tự hoạt động. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

2,6 Các phép toán chuỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

2.7 Nhận xét. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

2.8 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

2.9 Bảng chú giải thuật ngữ. . .


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

2.10 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14


Machine Translated by Google

xiv Nội dung

3 chức năng 17

3.1 Các lệnh gọi hàm. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

3.2 Các hàm toán học. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

3.3 Thành phần. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

3.4 Thêm chức năng mới. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

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

3.6 Luồng thực hiện. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

3.7 Tham số và đối số. . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

3.8 Các biến và tham số là cục bộ. . . . . . . . . . . . . . . . . . . . . . . 22

3.9 Sơ đồ ngăn xếp. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

3.10 Các hàm hiệu quả và các hàm vô hiệu. . . . . . . . . . . . . . . . . . . . . . 24

3.11 Tại sao lại là chức năng? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

3.12 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

3.13 Bảng chú giải thuật ngữ.


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

3.14 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

4 Nghiên cứu điển hình: thiết kế giao diện 29

4.1 Mô-đun con rùa. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

4.2 Lặp lại đơn giản. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

4.3 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

4.4 Đóng gói. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

4.5 Tổng quát hóa. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

4,6 Thiêt kê giao diê n . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

4.7 Tái cấu trúc. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

4.8 Kế hoạch phát triển. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

4.9 docstring. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

4.10 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

4.11 Bảng chú giải thuật ngữ.


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

4.12 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37


Machine Translated by Google

Nội dung xv

5 Điều kiện và đệ quy 39

5.1 Phân chia tầng và mô đun. . . . . . . . . . . . . . . . . . . . . . . . . . . 39

5.2 Biểu thức Boolean. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

5.3 Các toán tử logic. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

5.4 Thực hiện có điều kiện. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

5.5 Thực hiện thay thế. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

5.6 Các điều kiện có chuỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

5.7 Các điều kiện lồng nhau. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

5.8 Đệ quy. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

5.9 Sơ đồ ngăn xếp cho các hàm đệ quy. . . . . . . . . . . . . . . . . . . . . 44

5.10 Đệ quy vô hạn. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44

5.11 Đầu vào bàn phím. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

5.12 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

5.13 Bảng chú giải thuật ngữ. . .


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

5.14 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

6 chức năng hiệu quả 51

6.1 Giá trị trả về. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

6.2 Phát triển gia tăng. . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

6.3 Thành phần. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

6.4 Các hàm Boolean. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

6.5 Thêm đệ quy. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

6.6 Niềm tin nhảy vọt. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

6.7 Một ví dụ khác. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

6.8 Các loại kiểm tra. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

6.9 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

6.10 Bảng chú giải thuật ngữ. . .


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60

6.11 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60


Machine Translated by Google

xvi Nội dung

7 Lặp lại 63

7.1 Chuyển nhượng lại. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

7.2 Cập nhật các biến. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

7.3 Câu lệnh while. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

7.4 nghỉ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

7,5 Căn bậc hai. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

7.6 Thuật toán. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

7.7 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

7.8 Bảng chú giải thuật ngữ.


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

7.9 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69

8 chuỗi 71

8.1 Chuỗi là một chuỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71

8,2 len . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

8,3 Truyền tải với một vòng lặp for. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

8,4 Chuỗi lát. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

8.5 Chuỗi là bất biến. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74

8.6 Tìm kiếm. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74

8.7 Vòng lặp và đếm. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

8.8 Các phương thức chuỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

8.9 Nhà điều hành. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

8.10 So sánh chuỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

8.11 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

8.12 Bảng chú giải thuật ngữ.


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

8.13 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

9 Bài tập tình huống: chơi chữ 83

9.1 Đọc danh sách từ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

9.2 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84

9.3 Tìm kiếm. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85

9.4 Vòng lặp với các chỉ số. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86

9.5 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87

9.6 Bảng chú giải thuật ngữ.


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87

9,7 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88


Machine Translated by Google

Nội dung xvii

10 danh sách 89

10.1 Danh sách là một chuỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

10.2 Danh sách có thể thay đổi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90

10.3 Duyệt qua một danh sách. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

10.4 Liệt kê các hoạt động. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

10.5 Liệt kê các lát. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

10.6 Liệt kê các phương pháp. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92

10.7 Bản đồ, lọc và thu nhỏ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93

10.8 Xóa các phần tử. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

10.9 Danh sách và chuỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

10.10 Đối tượng và giá trị. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95

10.11 Răng cưa. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

10.12 Liệt kê các đối số. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

10.13 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98

10.14 Bảng chú giải thuật ngữ. . .


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100

10.15 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100

11 từ điển 103

11.1 Từ điển là một ánh xạ. . . . . . . . . . . . . . . . . . . . . . . . . . . . 103

11.2 Từ điển như một tập hợp các bộ đếm. . . . . . . . . . . . . . . . . . . . . . 104

11.3 Vòng lặp và từ điển. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

11.4 Tra cứu ngược. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

11.5 Từ điển và danh sách. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107

11.6 Bản ghi nhớ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109

11.7 Các biến toàn cục. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110

11.8 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111

11.9 Bảng chú giải thuật ngữ. . .


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112

11.10 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113


Machine Translated by Google

xviii Nội dung

12 Tuples 115

12.1 Tuples là bất biến. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115

12.2 Phép gán Tuple. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116

12.3 Tuples dưới dạng giá trị trả về. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117

12.4 Bộ giá trị đối số có độ dài thay đổi. . . . . . . . . . . . . . . . . . . . . . . . 118

12.5 Danh sách và bộ giá trị. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

12.6 Từ điển và bộ sưu tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120

12.7 Trình tự của trình tự. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121

12.8 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122

12.9 Bảng chú giải thuật ngữ.


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122

12.10 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123

13 Nghiên cứu điển hình: lựa chọn cấu trúc dữ liệu 125

13.1 Phân tích tần số từ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125

13.2 Số ngẫu nhiên. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126

13.3 Biểu đồ từ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127

13.4 Các từ thông dụng nhất. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128

13.5 Các thông số tùy chọn. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129

13.6 Phép trừ từ điển. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129

13.7 Từ ngẫu nhiên. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130

13.8 Phân tích Markov. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130

13.9 Cấu trúc dữ liệu. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132

13.10 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133

13.11 Bảng chú giải thuật ngữ.


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134

13.12 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134

14 tệp 137

14.1 Tính bền bỉ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137

14.2 Đọc và viết. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137

14.3 Toán tử định dạng. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138

14.4 Tên tệp và đường dẫn. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139

14.5 Bắt các trường hợp ngoại lệ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140


Machine Translated by Google

Nội dung xix

14.6 Cơ sở dữ liệu. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141

14.7 Tẩy chua. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142

14.8 Đường ống. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142

14.9 Viết mô-đun. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143

14.10 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144

14.11 Bảng chú giải thuật ngữ. . .


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145

14.12 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145

15 Lớp và đối tượng 147

15.1 Các kiểu do người lập trình xác định. . . . . . . . . . . . . . . . . . . . . . . . . . . . 147

15.2 Các thuộc tính. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148

15.3 Hình chữ nhật. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149

15.4 Các trường hợp dưới dạng giá trị trả về. . . . . . . . . . . . . . . . . . . . . . . . . . . . 150

15.5 Các đối tượng có thể thay đổi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

15.6 Sao chép. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

15.7 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152

15.8 Bảng chú giải thuật ngữ. . .


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153

15.9 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154

16 Lớp và chức năng 155

16.1 Thời gian. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155

16.2 Các chức năng thuần túy. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156

16.3 Bổ ngữ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157

16.4 Tạo mẫu so với lập kế hoạch. . . . . . . . . . . . . . . . . . . . . . . . . . . 158

16.5 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159

16.6 Bảng chú giải thuật ngữ. . .


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160

16.7 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160

17 Lớp và phương pháp 161

17.1 Tính năng hướng đối tượng. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161

17.2 Đối tượng in. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162

17.3 Một ví dụ khác. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163


Machine Translated by Google

xx Nội dung

17.4 Một ví dụ phức tạp hơn. . . . . . . . . . . . . . . . . . . . . . . . . . 164

17.5 Phương thức init. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164

17.6 Phương thức __str__. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165

17.7 Quá tải người vận hành. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165

17.8 Công văn dựa trên loại hình. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166

17.9 Tính đa hình. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167

17.10 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168

17.11 Giao diện và cách thực hiện. . . . . . . . . . . . . . . . . . . . . . . . . . 169

17.12 Bảng chú giải thuật ngữ.


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169

17.13 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170

18 Kế thừa 171

18.1 Đối tượng thẻ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171

18.2 Thuộc tính lớp. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172

18.3 So sánh các thẻ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173

18.4 Bộ bài. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174

18.5 In bộ bài. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174

18.6 Thêm, bớt, xáo trộn và sắp xếp. . . . . . . . . . . . . . . . . . . . . . . . . . 175

18.7 Kế thừa. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176

18.8 Các sơ đồ lớp. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177

18.9 Gỡ lỗi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178

18.10 Đóng gói dữ liệu. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179

18.11 Bảng chú giải thuật ngữ.


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180

18.12 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181

19 The Goodies 183

19.1 Biểu thức điều kiện. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183

19.2 Liệt kê các hiểu biết. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184

19.3 Biểu thức bộ tạo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185

19.4 bất kỳ và tất cả . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185

19.5 Bộ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186

19.6 Bộ đếm. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187


Machine Translated by Google

Nội dung xxi

19.7 defaultdict. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188

19.8 Các bộ giá trị được đặt tên.


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189

19.9 Thu thập các kho từ khóa. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190

19.10 Bảng chú giải thuật ngữ. . .


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191

19.11 Bài tập. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192

Một gỡ lỗi 193

A.1 Lỗi cú pháp. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193

A.2 Lỗi thời gian chạy. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195

A.3 Lỗi ngữ nghĩa. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198

B Phân tích các thuật toán 201

B.1 Thứ tự tăng trưởng. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202

B.2 Phân tích các hoạt động Python cơ bản. . . . . . . . . . . . . . . . . . . . . . 204

B.3 Phân tích các thuật toán tìm kiếm. . . . . . . . . . . . . . . . . . . . . . . . . . 205

B.4 Bảng băm. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206

B.5 Bảng chú giải thuật ngữ. . .


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Machine Translated by Google

xxii Nội dung


Machine Translated by Google

Chương 1

Cách thức của chương trình

Mục tiêu của cuốn sách này là dạy bạn suy nghĩ như một nhà khoa học máy tính. Cách suy nghĩ này kết hợp
một số tính năng tốt nhất của toán học, kỹ thuật và khoa học tự nhiên.
Giống như các nhà toán học, các nhà khoa học máy tính sử dụng ngôn ngữ chính thức để biểu thị các ý tưởng
(tính toán cụ thể). Giống như các kỹ sư, họ thiết kế mọi thứ, lắp ráp các thành phần thành các tem hệ
thống và đánh giá sự cân bằng giữa các lựa chọn thay thế. Giống như các nhà khoa học, họ quan sát hành vi
của các hệ thống phức tạp, hình thành các giả thuyết và kiểm tra các dự đoán.

Kỹ năng quan trọng nhất đối với một nhà khoa học máy tính là giải quyết vấn đề. Giải quyết vấn đề có
nghĩa là khả năng hình thành vấn đề, suy nghĩ sáng tạo về các giải pháp và diễn đạt
một giải pháp rõ ràng và chính xác. Hóa ra, quá trình học lập trình là một
cơ hội tuyệt vời để thực hành kỹ năng giải quyết vấn đề. Đó là lý do tại sao chương này được gọi là,
"Cách của chương trình".

Ở một cấp độ, bạn sẽ học lập trình, một kỹ năng hữu ích của chính nó. Ở một cấp độ khác, bạn
sẽ sử dụng lập trình như một phương tiện để kết thúc. Khi chúng ta đi cùng, kết thúc đó sẽ trở nên rõ ràng hơn.

1.1 Chương trình là gì?

Chương trình là một chuỗi các hướng dẫn chỉ định cách thực hiện một phép tính. Các
tính toán có thể là một cái gì đó toán học, chẳng hạn như giải một hệ phương trình hoặc
tìm gốc của một đa thức, nhưng nó cũng có thể là một phép tính tượng trưng, chẳng hạn như tìm kiếm nhập
và thay thế văn bản trong tài liệu hoặc một cái gì đó đồ họa, như xử lý hình ảnh hoặc
đang phát video.

Các chi tiết trông khác nhau ở các ngôn ngữ khác nhau, nhưng một số hướng dẫn cơ bản chỉ xuất hiện trong
về mọi ngôn ngữ:

đầu vào: Nhận dữ liệu từ bàn phím, tệp, mạng hoặc một số thiết bị khác.

đầu ra: Hiển thị dữ liệu trên màn hình, lưu thành tệp, gửi qua mạng, v.v.

toán học: Thực hiện các phép toán cơ bản như cộng và nhân.

thực thi có điều kiện: Kiểm tra các điều kiện nhất định và chạy mã thích hợp.
Machine Translated by Google

2 Chương 1. Cách thức của chương trình

lặp lại: Thực hiện một số hành động lặp đi lặp lại, thường là với một số biến thể.

Tin hay không thì tùy, đó là tất cả những gì cần có. Mọi chương trình bạn đã từng sử dụng, cho dù
phức tạp đến đâu, đều được tạo thành từ các hướng dẫn trông giống như thế này.
Vì vậy, bạn có thể coi lập trình là quá trình chia một nhiệm vụ lớn, phức tạp thành các nhiệm vụ con
nhỏ hơn và nhỏ hơn cho đến khi các nhiệm vụ con đủ đơn giản để thực hiện với một trong các hướng dẫn
cơ bản này.

1.2 Chạy Python


Một trong những thách thức khi bắt đầu với Python là bạn có thể phải cài đặt Python và phần mềm liên
quan trên máy tính của mình. Nếu bạn đã quen với hệ điều hành của mình và đặc biệt là nếu bạn cảm
thấy thoải mái với giao diện dòng lệnh, bạn sẽ không gặp khó khăn khi cài đặt Python. Nhưng đối với
những người mới bắt đầu, có thể khó khăn khi học về quản trị hệ thống và lập trình cùng một lúc.

Để tránh vấn đề đó, tôi khuyên bạn nên bắt đầu chạy Python trong trình duyệt. Sau này, khi bạn đã
quen với Python, tôi sẽ đưa ra các đề xuất để cài đặt Python trên máy tính của bạn.

Có một số trang web bạn có thể sử dụng để chạy Python. Nếu bạn đã có fa vorite, hãy tiếp tục và sử
dụng nó. Nếu không, tôi khuyên bạn nên sử dụng PythonAnywhere. Tôi cung cấp hướng dẫn chi tiết để bắt
đầu tại http://tinyurl.com/thinkpython2e.

Có hai phiên bản Python, được gọi là Python 2 và Python 3. Chúng rất giống nhau, vì vậy nếu bạn học
một phiên bản, bạn có thể dễ dàng chuyển sang phiên bản kia. Trên thực tế, chỉ có một số khác biệt mà
bạn sẽ gặp phải khi mới bắt đầu. Cuốn sách này được viết cho Python 3, nhưng tôi bao gồm một số ghi
chú về Python 2.

Trình thông dịch Python là một chương trình đọc và thực thi mã Python. Tùy thuộc vào môi trường của
bạn, bạn có thể khởi động trình thông dịch bằng cách nhấp vào biểu tượng hoặc bằng cách nhập python
trên dòng lệnh. Khi nó bắt đầu, bạn sẽ thấy đầu ra như thế này: Python 3.4.0 (mặc định, ngày 19 tháng

6 năm 2015, 14:20:21)


[GCC 4.8.2] trên linux

Nhập "trợ giúp", "bản quyền", "tín dụng" hoặc "giấy phép" để biết thêm thông tin. >>>

Ba dòng đầu tiên chứa thông tin về trình thông dịch và hệ điều hành mà nó đang chạy, vì vậy nó có thể
khác với bạn. Nhưng bạn nên kiểm tra xem số phiên bản, là 3.4.0 trong ví dụ này, bắt đầu bằng 3, cho
biết rằng bạn đang chạy Python 3. Nếu nó bắt đầu bằng 2, bạn đang chạy (bạn đoán vậy) Python 2.

Dòng cuối cùng là lời nhắc cho biết rằng trình thông dịch đã sẵn sàng để bạn nhập mã. Nếu bạn nhập
một dòng mã và nhấn Enter, trình thông dịch sẽ hiển thị kết quả:

>>> 1 + 1
2

Bây giờ bạn đã sẵn sàng để bắt đầu. Từ đây trở đi, tôi giả sử rằng bạn biết cách khởi động trình
thông dịch Python và chạy mã.
Machine Translated by Google

1.3. Chương trình đầu tiên 3

1.3 Chương trình đầu tiên

Theo truyền thống, chương trình đầu tiên bạn viết bằng ngôn ngữ mới được gọi là “Hello, World!” vì tất
cả những gì nó làm là hiển thị dòng chữ “Xin chào, Thế giới!”. Trong Python, nó trông giống như sau:

>>> print ('Hello, World!')

Đây là một ví dụ về câu lệnh in, mặc dù nó không thực sự in bất cứ thứ gì ra giấy. Nó hiển thị một kết
quả trên màn hình. Trong trường hợp này, kết quả là các từ

Chào thế giới!

Dấu ngoặc kép trong chương trình đánh dấu phần đầu và phần cuối của văn bản sẽ được phát; chúng không
xuất hiện trong kết quả.

Dấu ngoặc đơn chỉ ra rằng print là một hàm. Chúng ta sẽ đi đến các chức năng trong Chương 3.

Trong Python 2, câu lệnh print hơi khác; nó không phải là một hàm, vì vậy nó không sử dụng dấu ngoặc
đơn.

>>> in 'Hello, World!'

Sự phân biệt này sẽ sớm có ý nghĩa hơn, nhưng vậy là đủ để bắt đầu.

1.4 Toán tử số học

Sau “Hello, World”, bước tiếp theo là số học. Python cung cấp các toán tử, là các ký hiệu đặc biệt
đại diện cho các phép tính như phép cộng và phép nhân.

Các toán tử +, -, và * thực hiện các phép cộng, trừ và nhân, như trong các ví dụ rút gọn của fol:

>>> 40 + 2
42
>>> 43 - 1
42
>>> 6 * 7
42

Toán tử / thực hiện phép chia:

>>> 84/2
42.0

Bạn có thể thắc mắc tại sao kết quả lại là 42.0 thay vì 42. Tôi sẽ giải thích trong phần tiếp theo.

Cuối cùng, toán tử ** thực hiện tính lũy thừa; nghĩa là, nó tăng một số thành một lũy thừa:

>>> 6 ** 2 + 6
42

Trong một số ngôn ngữ khác, ^ được sử dụng để tính lũy thừa, nhưng trong Python, nó là một toán tử
bitwise được gọi là XOR. Nếu bạn không quen với các toán tử bitwise, kết quả sẽ khiến bạn ngạc nhiên:

>>> 6 ^ 2 4

Tôi sẽ không trình bày các toán tử bitwise trong cuốn sách này, nhưng bạn có thể đọc về chúng tại
http: // wiki. python.org/moin/BitwiseOperators.
Machine Translated by Google

4 Chương 1. Cách thức của chương trình

1.5 Giá trị và kiểu

Giá trị là một trong những thứ cơ bản mà chương trình hoạt động, chẳng hạn như một chữ cái hoặc một số. Một
số giá trị mà chúng tôi đã thấy cho đến nay là 2, 42.0 và 'Hello, World!'.

Các giá trị này thuộc các loại khác nhau: 2 là số nguyên, 42.0 là số dấu phẩy động và 'Hello, World!' là
một chuỗi, được gọi như vậy bởi vì các ký tự mà nó chứa được xâu lại với nhau.

Nếu bạn không chắc giá trị có kiểu gì, trình thông dịch có thể cho bạn biết: >>>

type (2) <class 'int'>

>>> type (42.0)


<class 'float'>

>>> type ('Hello, World!') <class


'str'>

Trong các kết quả này, từ "lớp" được sử dụng theo nghĩa của một loại; một loại là một loại giá trị.

Không có gì ngạc nhiên khi số nguyên thuộc kiểu int, chuỗi thuộc str và số dấu phẩy động thuộc kiểu float.

Còn các giá trị như '2' và '42 .0 'thì sao? Chúng trông giống như số, nhưng chúng nằm trong dấu ngoặc kép
giống như chuỗi. >>> type ('2') <class 'str'>

>>> type ('42 .0 ')


<class' str '>

Chúng là chuỗi.

Khi bạn nhập một số nguyên lớn, bạn có thể muốn sử dụng dấu phẩy giữa các nhóm chữ số, như 1.000.000. Đây
không phải là một số nguyên hợp pháp trong Python, nhưng nó hợp pháp:

>>> 1.000.000 (1,


0, 0)

Đó không phải là những gì chúng tôi mong đợi ở tất cả! Python hiểu 1.000.000 là một chuỗi các số nguyên
được phân tách bằng dấu phẩy. Chúng ta sẽ tìm hiểu thêm về loại trình tự này sau.

1.6 Ngôn ngữ trang trọng và tự nhiên

Ngôn ngữ tự nhiên là ngôn ngữ mọi người nói, chẳng hạn như tiếng Anh, tiếng Tây Ban Nha và tiếng Pháp.
Chúng không được thiết kế bởi mọi người (mặc dù mọi người cố gắng áp đặt một số trật tự cho chúng); chúng
tiến hóa một cách tự nhiên.

Ngôn ngữ chính thức là ngôn ngữ được thiết kế bởi mọi người cho các ứng dụng cụ thể. Ví dụ, ký hiệu mà các
nhà toán học sử dụng là một ngôn ngữ hình thức đặc biệt tốt trong việc biểu thị mối quan hệ giữa các con số
và ký hiệu. Các nhà hóa học sử dụng một guage lan chính thức để biểu thị cấu trúc hóa học của các phân tử.
Và quan trọng nhất:

Ngôn ngữ lập trình là ngôn ngữ chính thức được thiết kế để thể hiện các phép tính.
Machine Translated by Google

1.6. Ngôn ngữ trang trọng và tự nhiên 5

Các ngôn ngữ chính thức có xu hướng có các quy tắc cú pháp chặt chẽ chi phối cấu trúc của các câu lệnh.

Ví dụ, trong toán học, câu lệnh 3 + 3 = 6 có cú pháp đúng, nhưng 3+ = 3 $ 6 thì không. Về mặt hóa học , H2O

là một công thức đúng về mặt cú pháp, nhưng 2Zz thì không.

Các quy tắc cú pháp có hai loại, liên quan đến mã thông báo và cấu trúc. Token là các yếu tố cơ bản của ngôn

ngữ, chẳng hạn như từ, số và các nguyên tố hóa học. Một trong những vấn đề với 3+ = 3 $ 6 là $ không phải

là mã thông báo hợp pháp trong toán học (ít nhất là theo như tôi biết). Tương tự, 2Zz không hợp pháp vì

không có phần tử nào có tên viết tắt Zz.

Loại quy tắc cú pháp thứ hai liên quan đến cách kết hợp các mã thông báo. Phương trình 3 + / 3 là bất hợp

pháp vì mặc dù + và / là các mã thông báo hợp pháp, bạn không thể có cái này ngay sau cái kia. Tương tự,

trong công thức hóa học, chỉ số phụ đứng sau tên nguyên tố,
không phải trước đây.

Đây là câu @ Engli $ h có cấu trúc tốt với t * kens không hợp lệ trong đó. Câu này tất cả các mã thông báo
hợp lệ đều có, nhưng không hợp lệ với cấu trúc.

Khi bạn đọc một câu bằng tiếng Anh hoặc một câu bằng ngôn ngữ trang trọng, bạn phải tìm ra cấu trúc (mặc dù

trong ngôn ngữ tự nhiên, bạn làm điều này trong tiềm thức). Quá trình này được gọi là phân tích cú pháp.

Mặc dù ngôn ngữ chính thức và ngôn ngữ tự nhiên có nhiều đặc điểm chung — mã thông báo, cấu trúc cấu trúc

và cú pháp — có một số điểm khác biệt:

sự mơ hồ: Các ngôn ngữ tự nhiên chứa đầy sự mơ hồ, mà mọi người đối phó bằng cách sử dụng các manh mối văn

bản lừa đảo và thông tin khác. Các ngôn ngữ chính thức được thiết kế để gần như hoặc hoàn toàn không

rõ ràng, có nghĩa là bất kỳ câu lệnh nào cũng có chính xác một ý nghĩa, bất kể ngữ cảnh.

dư thừa: Để bù đắp cho sự mơ hồ và giảm bớt sự hiểu lầm, các ngôn ngữ tự nhiên sử dụng rất nhiều dư thừa.

Do đó, chúng thường dài dòng. Ngôn ngữ trang trọng ít thừa và ngắn gọn hơn.

nghĩa đen: Ngôn ngữ tự nhiên chứa đầy thành ngữ và ẩn dụ. Nếu tôi nói, “The penny drop”, có lẽ không có xu

nào và không có gì rơi (thành ngữ này có nghĩa là ai đó đã hiểu ra điều gì đó sau một khoảng thời

gian bối rối). Ngôn ngữ trang trọng có nghĩa là chính xác những gì họ nói.

Bởi vì tất cả chúng ta lớn lên đều nói các ngôn ngữ tự nhiên, đôi khi rất khó để điều chỉnh cho phù hợp với

ngôn ngữ nam. Sự khác biệt giữa ngôn ngữ trang trọng và tự nhiên cũng giống như sự khác biệt giữa thơ và

văn xuôi, nhưng còn hơn thế nữa:

Thơ: Các từ được sử dụng cho âm thanh cũng như ý nghĩa của chúng, và toàn bộ bài thơ cùng nhau tạo ra một

hiệu ứng hoặc phản ứng cảm xúc. Sự mơ hồ không chỉ phổ biến mà thường có chủ ý.

Văn xuôi: Nghĩa đen của từ quan trọng hơn, và cấu trúc đóng góp nhiều ý nghĩa hơn. Văn xuôi dễ phân tích

hơn thơ nhưng vẫn thường mơ hồ.

Chương trình: Ý nghĩa của một chương trình máy tính là rõ ràng và theo nghĩa đen, và có thể

được hiểu hoàn toàn bằng cách phân tích các mã thông báo và cấu trúc.
Machine Translated by Google

6 Chương 1. Cách thức của chương trình

Các ngôn ngữ chính thức dày đặc hơn các ngôn ngữ tự nhiên, vì vậy sẽ mất nhiều thời gian hơn để đọc chúng.
Ngoài ra, cấu trúc cũng quan trọng, vì vậy không phải lúc nào bạn đọc từ trên xuống dưới, từ trái qua
phải là tốt nhất. Thay vào đó, hãy học cách phân tích chương trình trong đầu bạn, xác định các mã thông
báo và chèn cấu trúc. Cuối cùng, các chi tiết quan trọng. Những lỗi nhỏ về chính tả và dấu câu, mà bạn có
thể loại bỏ trong các ngôn ngữ tự nhiên, có thể tạo ra sự khác biệt lớn trong một ngôn ngữ trang trọng.

1.7 Gỡ lỗi
Lập trình viên mắc lỗi. Vì những lý do hay thay đổi, các lỗi lập trình được gọi là lỗi và quá trình theo
dõi chúng được gọi là gỡ lỗi.

Lập trình, và đặc biệt là gỡ lỗi, đôi khi mang lại những cảm xúc mạnh mẽ. Nếu đang đấu tranh với một lỗi
khó, bạn có thể cảm thấy tức giận, chán nản hoặc xấu hổ.

Có bằng chứng cho thấy mọi người phản ứng với máy tính một cách tự nhiên như thể họ là một con người. Khi
họ làm việc tốt, chúng tôi coi họ như đồng đội, và khi họ cố chấp hoặc thô lỗ, chúng tôi đáp lại họ giống
như cách chúng ta phản ứng với những người thô lỗ, cố chấp (Reeves và Nass, Phương trình truyền thông:
Cách mọi người đối xử với Máy tính, Truyền hình, và Phương tiện mới Như Con người và Địa điểm thực).

Chuẩn bị cho những phản ứng này có thể giúp bạn đối phó với chúng. Một cách tiếp cận là coi máy tính như
một nhân viên với những điểm mạnh nhất định, như tốc độ và độ chính xác, và những điểm yếu cụ thể, như
thiếu sự đồng cảm và không có khả năng nắm bắt được bức tranh toàn cảnh.

Công việc của bạn là trở thành một nhà quản lý giỏi: tìm cách tận dụng những điểm mạnh và giảm thiểu
những điểm yếu. Và tìm cách sử dụng cảm xúc của bạn để giải quyết vấn đề, không để phản ứng của bạn cản
trở khả năng làm việc hiệu quả của bạn.

Học cách gỡ lỗi có thể khiến bạn khó chịu, nhưng đó là một kỹ năng quý giá, hữu ích cho nhiều người hoạt
động ngoài lập trình. Ở cuối mỗi chương có một phần, giống như phần này, với những gợi ý của tôi để gỡ
lỗi. Tôi hy vọng họ giúp đỡ!

1.8 Bảng chú giải thuật ngữ

giải quyết vấn đề: Quá trình hình thành một vấn đề, tìm kiếm một giải pháp và thể hiện
ăn nó.

ngôn ngữ cấp cao: Một ngôn ngữ lập trình như Python được thiết kế để dễ dàng
con người để đọc và viết.

ngôn ngữ cấp thấp: Một ngôn ngữ lập trình được thiết kế để máy tính dễ dàng chạy; còn được gọi là “ngôn
ngữ máy” hoặc “hợp ngữ”.

tính di động: Thuộc tính của một chương trình có thể chạy trên nhiều loại máy tính.

thông dịch viên: Một chương trình đọc một chương trình khác và thực thi nó

lời nhắc: Các ký tự được trình thông dịch hiển thị để cho biết rằng nó đã sẵn sàng để nhận đầu vào
từ người dùng.

chương trình: Một tập hợp các lệnh chỉ định một phép tính.
Machine Translated by Google

1.9. Bài tập 7

câu lệnh in: Một lệnh khiến trình thông dịch Python hiển thị một giá trị trên
màn hình.

toán tử: Một ký hiệu đặc biệt đại diện cho một phép tính đơn giản như cộng, cation bội hoặc nối chuỗi.

value: Một trong những đơn vị dữ liệu cơ bản, như số hoặc chuỗi, mà chương trình thao tác.

type: Một loại giá trị. Các kiểu mà chúng ta đã thấy cho đến nay là số nguyên (kiểu int), số dấu phẩy động
(kiểu float) và chuỗi (kiểu str).

số nguyên: Một kiểu đại diện cho các số nguyên.

dấu phẩy động: Một kiểu biểu thị các số có phần thập phân.

string: Kiểu biểu thị chuỗi ký tự.

ngôn ngữ tự nhiên: Bất kỳ một trong những ngôn ngữ mà mọi người nói được phát triển một cách tự nhiên.

ngôn ngữ chính thức: Bất kỳ một trong những ngôn ngữ mà mọi người đã thiết kế cho các tư thế cụ thể,
chẳng hạn như đại diện cho các ý tưởng toán học hoặc chương trình máy tính; tất cả các ngôn ngữ
ghép chương trình đều là ngôn ngữ chính thức.

mã thông báo: Một trong những yếu tố cơ bản của cấu trúc cú pháp của một chương trình, tương tự như
từ trong một ngôn ngữ tự nhiên.

cú pháp: Các quy tắc chi phối cấu trúc của một chương trình.

parse: Để kiểm tra một chương trình và phân tích cấu trúc cú pháp.

bug: Lỗi trong một chương trình.

gỡ lỗi: Quá trình tìm và sửa lỗi.

1.9 Bài tập

Bài tập 1.1. Bạn nên đọc cuốn sách này trước máy tính để có thể thử các ví dụ khi thực hiện.

Bất cứ khi nào bạn đang thử nghiệm một tính năng mới, bạn nên thử mắc lỗi. Ví dụ: trong câu "Xin chào, thế
giới!" chương trình, điều gì sẽ xảy ra nếu bạn bỏ qua một trong các dấu ngoặc kép? Điều gì sẽ xảy ra nếu
bạn bỏ qua cả hai? Nếu bạn in sai chính tả thì sao?

Loại thử nghiệm này giúp bạn nhớ những gì bạn đã đọc; nó cũng hữu ích khi bạn đang lập trình, vì bạn biết
được ý nghĩa của các thông báo lỗi. Tốt hơn là bạn nên phạm sai lầm ngay bây giờ và có mục đích hơn là sau
này và vô tình.

1. Trong một câu lệnh in, điều gì sẽ xảy ra nếu bạn bỏ đi một trong các dấu ngoặc đơn hoặc cả hai?

2. Nếu bạn đang cố in một chuỗi, điều gì sẽ xảy ra nếu bạn bỏ đi một trong các dấu ngoặc kép,
hoặc cả hai?

3. Bạn có thể sử dụng một dấu trừ để tạo thành một số âm như -2. Điều gì xảy ra nếu bạn đặt một dấu
cộng trước một số? Còn 2 ++ 2 thì sao?
Machine Translated by Google

số 8
Chương 1. Cách thức của chương trình

4. Trong ký hiệu toán học, các số 0 ở đầu là ok, như trong số 09. Điều gì xảy ra nếu bạn thử điều này trong Python?
Còn 011 thì sao?

5. Điều gì xảy ra nếu bạn có hai giá trị mà không có toán tử nào giữa chúng?

Bài tập 1.2. Khởi động trình thông dịch Python và sử dụng nó như một máy tính.

1. 42 phút 42 giây có bao nhiêu giây?

2. Có bao nhiêu dặm trong 10 km? Gợi ý: có 1,61 km trong một dặm.

3. Nếu bạn chạy một cuộc đua 10 km trong 42 phút 42 giây, tốc độ trung bình của bạn (thời gian trên một dặm tính

bằng phút và giây) là bao nhiêu? Tốc độ trung bình của bạn tính bằng dặm một giờ là bao nhiêu?
Machine Translated by Google

chương 2

Biến, biểu thức và


các câu lệnh

Một trong những tính năng mạnh mẽ nhất của ngôn ngữ lập trình là khả năng thao tác với các biến. Một
biến là một tên tham chiếu đến một giá trị.

2.1 Báo cáo công việc

Một câu lệnh gán tạo một biến mới và cung cấp cho nó một giá trị: >>> message

= 'Và bây giờ cho một cái gì đó hoàn toàn khác' >>> n = 17

>>> pi = 3,1415926535897932 Ví dụ

này thực hiện ba phép gán. Đầu tiên gán một chuỗi cho một thông báo có tên biến mới; thứ hai cho số
nguyên 17 đến n; thứ ba gán giá trị (gần đúng) của π cho pi.

Một cách phổ biến để biểu diễn các biến trên giấy là viết tên với một mũi tên trỏ đến giá trị của
nó. Loại hình này được gọi là biểu đồ trạng thái vì nó cho thấy mỗi biến đang ở trạng thái nào (hãy
coi nó như trạng thái tâm của biến). Hình 2.1 cho thấy kết quả của ví dụ trước.

2.2 Tên biến

Các lập trình viên thường chọn các tên có ý nghĩa cho các biến của họ — họ ghi nhận rằng biến được
sử dụng để làm gì.

thông điệp 'Và bây giờ cho một cái gì đó hoàn toàn khác nhau'

N 17

số Pi
3,1415926535897932

Hình 2.1: Biểu đồ trạng thái.


Machine Translated by Google

10 Chương 2. Biến, biểu thức và câu lệnh

Tên biến có thể dài tùy thích. Chúng có thể chứa cả chữ cái và số, nhưng

chúng không thể bắt đầu bằng một con số. Việc sử dụng các chữ cái viết hoa là hợp pháp, nhưng nó là thông thường để

chỉ sử dụng chữ thường cho tên biến.

Ký tự gạch dưới, _, có thể xuất hiện trong tên. Nó thường được sử dụng trong các tên có nhiều

các từ, chẳng hạn như your_name hoặc airspeed_of_unladen_swallow.

Nếu bạn đặt tên không hợp lệ cho một biến, bạn sẽ gặp lỗi cú pháp:

>>> 76trombone = 'cuộc diễu hành lớn'

Lỗi cú pháp: cú pháp không hợp lệ


>>> thêm @ = 1000000

Lỗi cú pháp: cú pháp không hợp lệ

>>> class = 'Dịch vụ lý thuyết nâng cao'

Lỗi cú pháp: cú pháp không hợp lệ

76trombone là bất hợp pháp vì nó bắt đầu bằng một số. thêm @ là bất hợp pháp vì nó chứa

một ký tự bất hợp pháp, @. Nhưng có gì sai với lớp học?

Hóa ra rằng lớp là một trong những từ khóa của Python . Trình thông dịch sử dụng các từ khóa để

nhận ra cấu trúc của chương trình, và chúng không thể được sử dụng làm tên biến.

Python 3 có các từ khóa sau:

Sai lớp cuối cùng Là trở về

Không có tiếp tục cho lambda thử


ĐÚNG VẬY phản đối từ phi địa phương trong khi

và del toàn cầu không phải với

như elif nếu hoặc năng suất

khẳng định khác nhập vào đi qua


phá vỡ ngoại trừ nuôi

Bạn không cần phải ghi nhớ danh sách này. Trong hầu hết các môi trường phát triển, từ khóa là

hiển thị bằng một màu khác; nếu bạn cố gắng sử dụng một làm tên biến, bạn sẽ biết.

2.3 Biểu thức và tuyên bố

Một biểu thức là sự kết hợp của các giá trị, biến và toán tử. Một giá trị tự nó là

được coi là một biểu thức và một biến cũng vậy, vì vậy tất cả các biểu thức sau đều là biểu thức hợp pháp:

>>> 42

42

>>> n

17

>>> n + 25

42

Khi bạn nhập một biểu thức tại dấu nhắc, trình thông dịch sẽ đánh giá nó, điều đó có nghĩa là

nó tìm giá trị của biểu thức. Trong ví dụ này, n có giá trị 17 và n + 25 có giá trị
giá trị 42.

Câu lệnh là một đơn vị mã có tác dụng, như tạo một biến hoặc hiển thị
giá trị.

>>> n = 17

>>> print (n)


Machine Translated by Google

2.4. Chế độ tập lệnh 11

Dòng đầu tiên là một câu lệnh gán giá trị cho n. Dòng thứ hai là câu lệnh in hiển thị giá trị của
n.

Khi bạn nhập một câu lệnh, trình thông dịch thực thi nó, có nghĩa là nó thực hiện bất cứ điều gì
mà câu lệnh nói. Nói chung, các câu lệnh không có giá trị.

2.4 Chế độ tập lệnh

Cho đến nay, chúng tôi đã chạy Python ở chế độ tương tác, có nghĩa là bạn tương tác trực tiếp với
trình thông dịch. Chế độ tương tác là một cách tốt để bắt đầu, nhưng nếu bạn đang làm việc với
nhiều dòng mã, nó có thể rất vụng về.

Giải pháp thay thế là lưu mã trong một tệp được gọi là tập lệnh và sau đó chạy trình thông dịch ở
chế độ tập lệnh để thực thi tập lệnh. Theo quy ước, các tập lệnh Python có tên kết thúc bằng .py.

Nếu bạn biết cách tạo và chạy một tập lệnh trên máy tính của mình, bạn đã sẵn sàng. Vâng, tôi
khuyên bạn nên sử dụng lại PythonAnywhere. Tôi đã đăng hướng dẫn chạy ở chế độ tập lệnh tại http://
tinyurl.com/thinkpython2e.

Vì Python cung cấp cả hai chế độ nên bạn có thể kiểm tra các bit mã trong chế độ tương tác trước
khi đưa chúng vào tập lệnh. Nhưng có sự khác biệt giữa chế độ tương tác và chế độ tập lệnh có thể
gây nhầm lẫn.

Ví dụ: nếu bạn đang sử dụng Python làm máy tính, bạn có thể nhập
>>> dặm = 26,2
>>> dặm * 1.61
42.182

Dòng đầu tiên chỉ định một giá trị cho dặm, nhưng nó không có tác dụng rõ ràng. Dòng thứ hai là
một áp lực cũ, vì vậy trình thông dịch đánh giá nó và hiển thị kết quả. Nó chỉ ra rằng một cuộc
chạy marathon là khoảng 42 km.

Nhưng nếu bạn nhập cùng một đoạn mã vào một tập lệnh và chạy nó, bạn sẽ không nhận được kết quả nào
cả. Trong chế độ tập lệnh, một biểu thức, tự nó, không có hiệu ứng nhìn thấy được. Python đánh giá
biểu thức, nhưng nó không hiển thị kết quả. Để hiển thị kết quả, bạn cần một câu lệnh in như sau:
dặm = 26,2

in (dặm * 1,61)

Hành vi này có thể gây nhầm lẫn lúc đầu. Để kiểm tra mức độ hiểu biết của bạn, hãy nhập các câu
lệnh sau vào trình thông dịch Python và xem chúng làm gì:
5
x = 5
x + 1

Bây giờ hãy đặt các câu lệnh giống nhau vào một tập lệnh và chạy nó. Đầu ra là gì? Sửa đổi tập lệnh
bằng cách chuyển đổi từng biểu thức thành một câu lệnh in và sau đó chạy lại.

2.5 Thứ tự hoạt động

Khi một biểu thức chứa nhiều hơn một toán tử, thứ tự đánh giá phụ thuộc vào thứ tự của các hoạt
động. Đối với các toán tử toán học, Python tuân theo quy ước toán học. Từ viết tắt PEMDAS là một
cách hữu ích để ghi nhớ các quy tắc:
Machine Translated by Google

12 Chương 2. Biến, biểu thức và câu lệnh

• Dấu ngoặc đơn có mức độ ưu tiên cao nhất và có thể được sử dụng để buộc một biểu thức đánh giá theo
thứ tự bạn muốn. Vì các biểu thức trong ngoặc đơn được đánh giá đầu tiên nên 2 * (3-1) là 4 và (1 +
1) ** (5-2) là 8. Bạn cũng có thể sử dụng dấu ngoặc đơn để làm cho một biểu thức dễ đọc hơn, như
trong ( phút * 100) / 60, ngay cả khi nó không thay đổi kết quả.

• Luỹ thừa có mức độ ưu tiên cao nhất tiếp theo, vì vậy 1 + 2 ** 3 là 9, không phải 27 và 2 *
3 ** 2 là 18, không phải 36.

• Phép nhân và phép chia có mức độ ưu tiên cao hơn phép cộng và phép trừ.
Vì vậy, 2 * 3-1 là 5, không phải 4 và 6 + 4/2 là 8, không phải 5.

• Các toán tử có cùng thứ tự ưu tiên được đánh giá từ trái sang phải (ngoại trừ thời gian lũy thừa).
Vì vậy, trong biểu thức độ / 2 * pi, phép chia xảy ra đầu tiên và kết quả được nhân với số pi. Để
chia cho 2π, bạn có thể sử dụng dấu ngoặc đơn hoặc viết độ / 2 / pi.

Tôi không làm việc chăm chỉ để nhớ thứ tự ưu tiên của các toán tử. Nếu tôi không thể biết bằng cách nhìn
vào biểu thức, tôi sử dụng dấu ngoặc đơn để làm cho nó rõ ràng.

2.6 Phép toán chuỗi

Nói chung, bạn không thể thực hiện các phép toán trên chuỗi, ngay cả khi chuỗi trông giống như số, vì vậy,
những điều sau đây là bất hợp pháp: 'egg' / 'easy'

'đồ ăn Trung Quốc' 'thứ ba' * 'một sự quyến rũ'

Nhưng có hai ngoại lệ, + và *.

Toán tử + thực hiện nối chuỗi, có nghĩa là nó nối các chuỗi bằng cách liên kết chúng từ đầu đến cuối. Ví dụ:

>>> đầu tiên = 'cổ họng'

>>> thứ hai = 'chim chích chòe'


>>> đầu tiên + thứ hai
chim chích chòe

Toán tử * cũng hoạt động trên chuỗi; nó thực hiện lặp lại. Ví dụ: 'Spam' * 3 là 'SpamSpamSpam'. Nếu một
trong các giá trị là một chuỗi thì giá trị kia phải là một số nguyên.

Việc sử dụng + và * này có ý nghĩa tương tự với phép cộng và phép nhân. Cũng giống như 4 * 3 tương đương
với 4 + 4 + 4, chúng tôi kỳ vọng 'Spam' * 3 cũng giống như 'Spam' + 'Spam' + 'Spam', và đúng như vậy. Mặt
khác, có một cách quan trọng trong đó việc nối chuỗi và thời gian lặp lại khác với phép cộng và phép nhân
số nguyên. Bạn có thể nghĩ về một thuộc tính mà phép cộng có nối chuỗi không?

2.7 Bình luận

Khi các chương trình ngày càng lớn hơn và phức tạp hơn, chúng sẽ khó đọc hơn. Các ngôn ngữ chính thức có
mật độ dày đặc và thường rất khó để nhìn vào một đoạn mã và tìm ra nó đang làm gì hoặc tại sao.
Machine Translated by Google

2.8. Gỡ lỗi 13

Vì lý do này, bạn nên thêm ghi chú vào chương trình của mình để giải thích theo cách lan tự
nhiên chương trình đang làm gì. Các ghi chú này được gọi là nhận xét và chúng bắt đầu bằng ký
hiệu #: # tính tỷ lệ phần trăm của giờ đã trôi qua = (phút * 100) / 60 Trong trường hợp này,

nhận xét tự xuất hiện trên một dòng. Bạn cũng có thể đặt nhận xét ở cuối dòng:

phần trăm = (phút * 100) / 60 # phần trăm của một giờ Mọi thứ từ số # đến

cuối dòng đều bị bỏ qua — nó không ảnh hưởng đến việc thực thi chương trình.

Nhận xét hữu ích nhất khi chúng ghi lại các tính năng không rõ ràng của mã. Nó là hợp lý để giả
định rằng người đọc có thể tìm ra những gì mã làm; nó hữu ích hơn để giải thích tại sao.

Nhận xét này là thừa với mã và vô ích:

v = 5 # gán 5 cho v
Nhận xét này chứa thông tin hữu ích không có trong mã:

v = 5 # vận tốc tính bằng mét / giây.

Tên biến tốt có thể làm giảm nhu cầu nhận xét, nhưng tên dài có thể làm cho các biểu thức com
plex khó đọc, vì vậy sẽ có sự cân bằng.

2.8 Gỡ lỗi
Ba loại lỗi có thể xảy ra trong một chương trình: lỗi cú pháp, lỗi thời gian chạy và lỗi ngữ
nghĩa. Sẽ rất hữu ích khi phân biệt giữa chúng để theo dõi chúng nhanh hơn.

Lỗi cú pháp: "Cú pháp" đề cập đến cấu trúc của một chương trình và các quy tắc về cấu trúc struc
đó. Ví dụ, các dấu ngoặc đơn phải đi theo các cặp phù hợp, vì vậy (1 + 2) là hợp pháp,
nhưng 8) là một lỗi cú pháp.

Nếu có lỗi cú pháp ở bất kỳ đâu trong chương trình của bạn, Python sẽ hiển thị lỗi mes
sage và thoát và bạn sẽ không thể chạy chương trình. Trong vài tuần đầu tiên của sự nghiệp
lập trình, bạn có thể dành nhiều thời gian để theo dõi các lỗi cú pháp. Khi bạn có kinh
nghiệm, bạn sẽ mắc ít lỗi hơn và tìm thấy chúng nhanh hơn.

Lỗi thời gian chạy: Loại lỗi thứ hai là lỗi thời gian chạy, được gọi như vậy vì lỗi không xuất
hiện cho đến sau khi chương trình bắt đầu chạy. Những lỗi này còn được gọi là ngoại lệ vì
chúng thường chỉ ra rằng điều gì đó đặc biệt (và tồi tệ) đã xảy ra.

Lỗi thời gian chạy hiếm khi xảy ra trong các chương trình đơn giản mà bạn sẽ thấy trong một vài chương đầu

tiên, vì vậy có thể mất một lúc trước khi bạn gặp phải lỗi này.

Lỗi ngữ nghĩa: Loại lỗi thứ ba là “ngữ nghĩa”, có nghĩa là liên quan đến ý nghĩa.
Nếu có lỗi ngữ nghĩa trong chương trình của bạn, chương trình sẽ chạy mà không tạo ra
thông báo lỗi, nhưng nó sẽ không thực hiện đúng. Nó sẽ làm một cái gì đó khác. Cụ thể, nó
sẽ làm những gì bạn đã bảo nó làm.

Việc xác định lỗi ngữ nghĩa có thể khó khăn vì nó đòi hỏi bạn phải làm việc ngược lại bằng
cách xem đầu ra của chương trình và cố gắng tìm ra nó đang làm gì.
Machine Translated by Google

14 Chương 2. Biến, biểu thức và câu lệnh

2.9 Bảng chú giải thuật ngữ

biến: Tên tham chiếu đến một giá trị.

gán: Một câu lệnh gán một giá trị cho một biến.

biểu đồ trạng thái: Một biểu diễn đồ họa của một tập hợp các biến và các giá trị mà chúng tham chiếu đến.

từ khóa: Một từ dành riêng được sử dụng để phân tích cú pháp chương trình; bạn không thể sử dụng các từ khóa như
if, def và while dưới dạng tên biến.

toán hạng: Một trong các giá trị mà toán tử hoạt động.

biểu thức: Sự kết hợp của các biến, toán tử và giá trị đại diện cho một re
ủ rũ.

đánh giá: Để đơn giản hóa một biểu thức bằng cách thực hiện các hoạt động để mang lại một
giá trị.

câu lệnh: Một phần mã đại diện cho một lệnh hoặc hành động. Cho đến nay, các câu lệnh mà chúng ta đã thấy là

các câu lệnh gán và câu lệnh in.

thực thi: Để chạy một câu lệnh và thực hiện những gì nó nói.

chế độ tương tác: Một cách sử dụng trình thông dịch Python bằng cách nhập mã tại dấu nhắc.

chế độ tập lệnh: Một cách sử dụng trình thông dịch Python để đọc mã từ một tập lệnh và chạy nó.

script: Một chương trình được lưu trữ trong một tệp.

thứ tự hoạt động: Các quy tắc điều chỉnh thứ tự trong đó các biểu thức liên quan đến nhiều toán tử và toán

hạng được đánh giá.

concatenate: Để nối hai toán hạng end-to-end.

nhận xét: Thông tin trong một chương trình dành cho các lập trình viên khác (hoặc bất kỳ ai đọc mã nguồn) và

không ảnh hưởng đến việc thực thi chương trình.

lỗi cú pháp: Một lỗi trong chương trình khiến nó không thể phân tích cú pháp (và do đó tôi có thể diễn giải).

ngoại lệ: Lỗi được phát hiện trong khi chương trình đang chạy.

semantics: Ý nghĩa của một chương trình.

lỗi ngữ nghĩa: Một lỗi trong một chương trình khiến nó làm điều gì đó khác với những gì lập trình viên dự định.

2.10 Bài tập

Bài tập 2.1. Lặp lại lời khuyên của tôi từ chương trước, bất cứ khi nào bạn tìm hiểu một tính năng mới, bạn

nên thử nó ở chế độ tương tác và cố ý sửa lỗi để xem có gì sai sót.

• Chúng tôi thấy rằng n = 42 là hợp pháp. Còn 42 = n thì sao?


Machine Translated by Google

2.10. Bài tập 15

• Làm thế nào về x = y = 1?

• Trong một số ngôn ngữ, mọi câu lệnh đều kết thúc bằng dấu chấm phẩy,;. Điều gì xảy ra nếu bạn đặt dấu chấm phẩy ở

cuối câu lệnh Python?

• Điều gì sẽ xảy ra nếu bạn đặt một dấu chấm ở cuối một bản sao kê?

• Trong ký hiệu toán học, bạn có thể nhân x và y như sau: xy. Điều gì xảy ra nếu bạn thử điều đó trong

Con trăn?

Bài tập 2.2. Thực hành sử dụng trình thông dịch Python làm máy tính:

4 3 πr
1. Thể tích của khối cầu có bán kính r là 3 . Thể tích của khối cầu có bán kính 5 là bao nhiêu?

2. Giả sử giá bìa của một cuốn sách là 24,95 đô la, nhưng các nhà sách được giảm giá 40%. Phí vận chuyển 3 đô la cho

bản sao đầu tiên và 75 xu cho mỗi bản sao bổ sung. Tổng chi phí bán buôn cho 60 bản là bao nhiêu?

3. Nếu tôi rời nhà lúc 6:52 sáng và chạy 1 dặm với tốc độ dễ dàng (8:15 mỗi dặm), sau đó chạy 3 dặm với tốc độ nhanh

(7:12 mỗi dặm) và chạy lại 1 dặm với tốc độ dễ dàng, thì bây giờ là bao nhiêu. tôi có về nhà để ăn sáng không?
Machine Translated by Google

16 Chương 2. Biến, biểu thức và câu lệnh


Machine Translated by Google

Chương 3

Chức năng

Trong ngữ cảnh của lập trình, một hàm là một chuỗi các câu lệnh được đặt tên để thực hiện một phép
tính. Khi bạn xác định một hàm, bạn chỉ định tên và chuỗi các câu lệnh. Sau đó, bạn có thể "gọi"
hàm bằng tên.

3.1 Các lệnh gọi hàm

Chúng ta đã thấy một ví dụ về lệnh gọi hàm: >>> type (42)

<class 'int'>

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

Người ta thường nói rằng một hàm “nhận” một đối số và “trả về” một kết quả. Kết quả còn được gọi
là giá trị trả về.

Python cung cấp các hàm chuyển đổi giá trị từ kiểu này sang kiểu khác. Hàm int nhận bất kỳ giá
trị nào và chuyển đổi nó thành một số nguyên, nếu nó có thể hoặc phàn nàn theo cách khác: >>> int
('32 ')
32
>>> int ('Xin chào')
ValueError: chữ không hợp lệ cho int (): Xin chào

int có thể chuyển đổi giá trị dấu phẩy động thành số nguyên, nhưng nó không làm tròn; nó cắt bỏ
phần phân số: >>> int (3.99999)

3
>>> int (-2.3) -2

float chuyển đổi số nguyên và chuỗi thành số dấu phẩy động: >>>
float (32)
32.0
>>> float ('3.14159')
3.14159
Machine Translated by Google

18 Chương 3. Chức năng

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

>>> str (32)


'32'
>>> str (3,14159)
'3,14159'

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

Python có một mô-đun toán học cung cấp hầu hết các hàm toán học quen thuộc. Mô -đun là một tệp chứa
một tập hợp các chức năng liên quan.

Trước khi chúng tôi có thể sử dụng các chức năng trong một mô-đun, chúng tôi phải nhập nó với trạng thái nhập
đề cập:

>>> nhập toán

Câu lệnh này tạo một đối tượng mô-đun có tên là toán học. Nếu bạn hiển thị đối tượng mô-đun, bạn sẽ
nhận được một số thông tin về nó:

>>> toán

học <mô-đun 'toán học' (tích hợp sẵn)>

Đối tượng mô-đun chứa các hàm và biến được định nghĩa trong mô-đun. Để truy cập một trong các chức
năng, bạn phải chỉ định tên của mô-đun và tên của chức năng, được phân tách bằng dấu chấm (còn được
gọi là dấu chấm). Định dạng này được gọi là ký hiệu dấu chấm. >>> ratio = signal_power / noise_power

>>> decibel = 10 * math.log10 (ratio)

>>> radian = 0,7

>>> height = math.sin (radian)

Ví dụ đầu tiên sử dụng math.log10 để tính toán tỷ lệ tín hiệu trên nhiễu bằng decibel (giả sử rằng
signal_power và noise_power được xác định). Mô-đun toán học cũng cung cấp log, tính toán logarit cơ
số e.

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

>>> độ = 45 >>>
radian = độ / 180.0 * math.pi >>> math.sin (radian)
0.707106781187

Biểu thức math.pi lấy biến số pi từ mô-đun toán học. Giá trị của nó là một xấp xỉ dấu phẩy động
của π, chính xác đến khoảng 15 chữ số.

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

>>> math.sqrt (2) / 2.0


0,707106781187
Machine Translated by Google

3.3. Thành phần 19

3.3 Thành phần

Cho đến nay, chúng ta đã xem xét các phần tử của một chương trình - biến, biểu thức và câu lệnh - một
cách riêng biệt, mà không nói về cách kết hợp chúng.

Một trong những tính năng hữu ích nhất của ngôn ngữ lập trình là khả năng lấy các khối xây dựng nhỏ và
soạn chúng. Ví dụ: đối số của một hàm có thể là bất kỳ loại biểu thức nào, bao gồm các toán tử số học: x
= math.sin (độ / 360.0 * 2 * math.pi)

Và thậm chí cả các cuộc gọi hàm:

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

Hầu như bất cứ nơi nào bạn có thể đặt một giá trị, bạn có thể đặt một biểu thức tùy ý, với một ví dụ:
bên trái của câu lệnh gán phải là một tên biến. Bất kỳ biểu thức nào khác ở phía bên trái là lỗi cú pháp
(chúng ta sẽ thấy các ngoại lệ cho quy tắc này sau).
>>> phút = giờ * 60 # đúng
>>> giờ * 60 = phút sai!
SyntaxError: không thể gán cho toán tử

3.4 Thêm các chức năng mới

Cho đến nay, chúng tôi chỉ sử dụng các hàm đi kèm với Python, nhưng cũng có thể thêm các hàm mới. Định
nghĩa hàm chỉ định tên của một hàm mới và chuỗi các câu lệnh chạy khi hàm được gọi.

Đây là một ví dụ: def

print_lyrics (): print


("Tôi là thợ rừng, và tôi ổn.") Print ("Tôi ngủ cả đêm
và tôi làm việc cả ngày.") Def là một từ khoá chỉ ra rằng điều

này là một định nghĩa hàm. Tên của hàm là print_lyrics. Các quy tắc đối với tên hàm cũng giống như đối
với tên biến: chữ cái, số và dấu gạch dưới là hợp pháp, nhưng ký tự đầu tiên không được là số. Bạn không

thể sử dụng một từ khóa làm tên của một hàm và bạn nên tránh để một biến và một hàm trùng tên.

Dấu ngoặc đơn trống sau tên cho biết rằng hàm này không có bất kỳ đối số nào
ments.

Dòng đầu tiên của định nghĩa hàm được gọi là tiêu đề; phần còn lại được gọi là cơ thể. Tiêu đề phải kết
thúc bằng dấu hai chấm và nội dung phải được thụt vào. Theo quy ước, thụt lề luôn là bốn khoảng trắng.
Phần nội dung có thể chứa bất kỳ số lượng câu lệnh nào.

Các chuỗi trong câu lệnh in được đặt trong dấu ngoặc kép. Dấu ngoặc kép và dấu ngoặc kép làm tương tự
nhau; hầu hết mọi người sử dụng dấu nháy đơn ngoại trừ những trường hợp như thế này khi một dấu nháy đơn
(cũng là dấu nháy đơn) xuất hiện trong chuỗi.

Tất cả các dấu ngoặc kép (đơn và kép) phải là "dấu ngoặc kép", thường nằm bên cạnh Enter trên bàn phím.
“Dấu ngoặc kép”, giống như những dấu ngoặc kép trong câu này, không hợp pháp trong Python.

Nếu bạn nhập định nghĩa hàm trong chế độ tương tác, trình thông dịch sẽ in dấu chấm (...) để cho bạn biết
rằng định nghĩa chưa hoàn chỉnh:
Machine Translated by Google

20 Chương 3. Chức năng

>>> def print_lyrics ():


... print ("Tôi là thợ rừng, và tôi ổn.") print ("Tôi
... ngủ cả đêm và tôi làm việc cả ngày.")
...

Để kết thúc hàm, bạn phải nhập một dòng trống.

Định nghĩa một hàm sẽ tạo ra một đối tượng hàm, có kiểu hàm: >>> print

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


<class 'function'>

Cú pháp để gọi hàm mới giống như đối với các hàm tích hợp: >>> print_lyrics ()

Tôi là thợ rừng, và tôi không sao.


Tôi ngủ cả đêm và tôi làm việc cả ngày.

Khi bạn đã xác định một hàm, bạn có thể sử dụng nó bên trong một hàm khác. Ví dụ: để lặp lại
điệp khúc trước đó, chúng 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 ()
Tôi là thợ rừng, và tôi không sao.
Tôi ngủ cả đêm và tôi làm việc cả ngày.
Tôi là thợ rừng, và tôi không sao.
Tôi ngủ cả đêm và tôi làm việc cả ngày.

Nhưng đó không thực sự là cách bài hát diễn ra.

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

Kết hợp các đoạn mã từ phần trước lại với nhau, toàn bộ chương trình trông như thế này:

def print_lyrics ():


print ("Tôi là thợ rừng, và tôi ổn.") print ("Tôi
ngủ cả đêm và tôi làm việc cả ngày.")

def repeat_lyrics ():


print_lyrics ()
print_lyrics ()

repeat_lyrics ()

Chương trình này chứa hai định nghĩa hàm: print_lyrics và repeat_lyrics. Các định nghĩa hàm
được thực thi giống như các câu lệnh khác, nhưng tác dụng là tạo các đối tượng hàm. Các câu
lệnh bên trong hàm không chạy cho đến khi hàm được gọi và định nghĩa hàm không tạo ra kết quả
nào.
Machine Translated by Google

3.6. Luồng thực hiện 21

Như bạn có thể mong đợi, bạn phải tạo một hàm trước khi có thể chạy nó. Nói cách khác, định nghĩa
hàm phải chạy trước khi hàm được gọi.

Như một bài tập, hãy di chuyển dòng cuối cùng của chương trình này lên đầu, để lệnh gọi hàm xuất
hiện trước định nghĩa. Chạy chương trình và xem bạn nhận được thông báo lỗi nào.

Bây giờ, hãy chuyển lời gọi hàm trở lại dưới cùng và di chuyển định nghĩa của print_lyrics sau định
nghĩa của repeat_lyrics. Điều gì xảy ra khi bạn chạy chương trình này?

3.6 Quy trình thực hiện

Để đảm bảo rằng một hàm được xác định trước lần sử dụng đầu tiên, bạn phải biết các trạng thái thứ
tự chạy trong, được gọi là luồng thực thi.

Việc thực thi luôn bắt đầu ở câu lệnh đầu tiên của chương trình. Các câu lệnh được chạy lần lượt
theo thứ tự từ trên xuống dưới.

Định nghĩa hàm không làm thay đổi luồng thực thi của chương trình, nhưng hãy nhớ rằng các câu lệnh
bên trong hàm không chạy cho đến khi hàm được gọi.

Một lời gọi hàm giống như một con đường vòng trong luồng thực thi. Thay vì chuyển đến trạng thái
tiếp theo, luồng sẽ chuyển đến phần thân của hàm, chạy các câu lệnh ở đó, và sau đó quay lại tiếp
tục nơi nó đã dừng lại.

Điều đó nghe có vẻ đơn giản, cho đến khi bạn nhớ rằng một hàm có thể gọi một hàm khác. Khi đang ở
giữa một hàm, chương trình có thể phải chạy các câu lệnh trong một hàm khác. Sau đó, trong khi chạy
chức năng mới đó, chương trình có thể phải chạy thêm một chức năng khác!

May mắn thay, Python rất giỏi trong việc theo dõi vị trí của nó, vì vậy mỗi khi một hàm hoạt động,
chương trình sẽ tiếp tục vị trí của nó trong hàm đã gọi nó. Khi đến cuối chương trình, nó sẽ kết thúc.

Tóm lại, khi bạn đọc một chương trình, không phải lúc nào bạn cũng muốn đọc từ trên xuống dưới.
Đôi khi sẽ có ý nghĩa hơn nếu bạn tuân theo quy trình thực thi.

3.7 Tham số và đối số


Một số hàm chúng ta đã thấy yêu cầu đối số. Ví dụ: khi bạn gọi math.sin, bạn chuyển một số làm đối
số. Một số hàm nhận nhiều hơn một hàm ar: math.pow nhận hai, 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ố. Đây là định nghĩa cho một hàm
nhận đối số: def print_twice (bruce):

in (bruce) in
(bruce)

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

Chức năng này hoạt động với bất kỳ giá trị nào có thể được in.
Machine Translated by Google

22 Chương 3. Chức năng

>>> print_twice ('Thư rác')


Thư rác

Spam
>>> print_twice (42) 42

42

>>> print_twice (math.pi)


3.14159265359
3,14159265359

Các quy tắc cấu tạo tương tự áp dụng cho các hàm dựng sẵn cũng áp dụng cho các hàm do người lập
trình xác định, vì vậy chúng ta có thể sử 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 đánh giá trước khi hàm được gọi, vì vậy trong các ví dụ, biểu thức 'Spam' * 4 và
math.cos (math.pi) chỉ được đánh giá một lần.

Bạn cũng có thể sử dụng một biến làm đối số:

>>> michael = 'Eric, nửa con ong.' >>>


print_twice (michael)
Eric, một nửa con ong.
Eric, một nửa con ong.

Tên của biến mà chúng ta truyền vào dưới dạng đối số (michael) không liên quan gì đến tên của tham
số (bruce). Không quan trọng giá trị được gọi là trở về nhà (trong trình gọi); ở đây trong
print_twice, chúng tôi gọi tất cả mọi người là người bầm dập.

3.8 Các biến và tham số là cục bộ


Khi bạn tạo một biến bên trong một hàm, nó là cục bộ, có nghĩa là nó chỉ tồn tại bên trong hàm. Ví
dụ: def cat_twice (part1, part2): cat = part1 + part2 print_twice (cat)

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

>>> 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 sẽ bị hủy. Nếu chúng tôi cố gắng in nó, chúng tôi nhận được một
ngoại lệ:
Machine Translated by Google

3.9. Sơ đồ ngăn xếp 23

dòng 1 "Bing tiddle"


__chính__
dòng 2 "tiddle bang."

phần 1 'Bing tiddle'

cat_twice phần 2 'tiddle bang.'

con mèo
'Bing tiddle tiddle bang.'

print_twice bầm tím 'Bing tiddle tiddle bang.'

Hình 3.1: Sơ đồ ngăn xếp.

>>> in (cat)
NameError: tên 'cat' không được xác định

Các thông số cũng mang tính địa phương. Ví dụ: bên ngoài print_twice, không có thứ gì như
bầm dập.

3.9 Sơ đồ ngăn xếp

Để theo dõi những biến nào có thể được sử dụng ở đâu, đôi khi rất hữu ích khi vẽ một sơ đồ
ngăn xếp. Giống như biểu đồ trạng thái, biểu đồ ngăn xếp hiển thị giá trị của mỗi biến, nhưng
chúng cũng hiển thị chức năng mà mỗi biến thuộc về.

Mỗi chức năng được biểu diễn bằng một khung. Khung là một hộp có tên của một hàm bên cạnh nó
và các tham số và biến của hàm bên trong nó. Sơ đồ ngăn xếp cho ví dụ trước được thể hiện
trong Hình 3.1.

Các khung được sắp xếp trong một ngăn xếp cho biết hàm nào được gọi là hàm nào, v.v. Trong ví
dụ này, print_twice được gọi bằng cat_twice và cat_twice được gọi bằng __main__, đây là một
cái tên đặc biệt cho khung trên cùng. Khi bạn tạo một biến bên ngoài bất kỳ hàm nào, biến đó
thuộc về __main__.

Mỗi tham số đề cập đến cùng một giá trị với đối số tương ứng của nó. Vì 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
con mèo.

Nếu lỗi xảy ra trong khi gọi hàm, Python sẽ in tên của hàm, tên của hàm đã gọi nó và tên của
hàm đã gọi hàm đó, tất cả các cách trở lại __main__.

Ví dụ: nếu bạn cố gắng truy cập con mèo từ bên trong print_twice, bạn sẽ nhận được

NameError: Traceback (trong cùng cuối cùng): File "test.py", dòng 13, trong __main__

cat_twice (line1, line2)


Tệp "test.py", dòng 5, trong cat_twice
print_twice (cat)
Tệp "test.py", dòng 9, in print_twice print
(cat)
NameError: tên 'cat' không được xác định
Machine Translated by Google

24 Chương 3. Chức năng

Danh sách các chức năng này được gọi là theo dõi lại. Nó cho bạn biết lỗi xảy ra ở tệp chương trình nào,
dòng nào và những hàm nào đang thực thi tại thời điểm đó. Nó cũng hiển thị dòng mã gây ra lỗi.

Thứ tự của các chức năng trong truy xuất giống như thứ tự của các khung trong sơ đồ ngăn xếp. Chức năng

hiện đang chạy ở dưới cùng.

3.10 Các chức năng hiệu quả và các chức năng vô hiệu

Một số hàm chúng tôi đã sử dụng, chẳng hạn như hàm toán học, trả về kết quả; vì thiếu một cái tên hay hơn,

tôi gọi chúng là những chức năng hiệu quả. Các hàm khác, như print_twice, thực hiện một hành động nhưng
không trả về giá trị. Chúng được gọi là các hàm void.

Khi bạn gọi một hàm kết quả, bạn hầu như luôn muốn làm điều gì đó với kết quả; ví dụ: bạn có thể gán nó
cho một biến hoặc sử 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 ở chế độ tương tác, Python sẽ hiển thị kết quả: >>> math.sqrt (5) 2.2360679774997898

Nhưng trong script, nếu bạn gọi tất cả một hàm có hiệu quả, thì giá trị trả về sẽ bị mất vĩnh viễn!

math.sqrt (5)

Tập lệnh này tính căn bậc hai của 5, nhưng vì nó không lưu trữ hoặc hiển thị kết quả nên nó không hữu ích
lắm.

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

>>> result = print_twice ('Bing')


Bing
Bing
>>> print (kết quả)
Không có

Giá trị Không có không giống với chuỗi 'Không có'. Nó là một giá trị đặc biệt có kiểu riêng của nó: >>>
type (None) <class 'NoneType'>

Các hàm chúng tôi đã viết cho đến nay đều không có giá trị. Chúng ta sẽ bắt đầu viết các hàm hiệu quả
trong một vài chương.

3.11 Tại sao lại là chức năng?

Có thể không rõ ràng tại sao việc phân chia một chương trình thành các chức năng lại là một vấn đề đáng
lo ngại. Có một số lý do:
Machine Translated by Google

3.12. Gỡ lỗi 25

• Tạo một hàm mới cho bạn cơ hội đặt tên cho một nhóm câu lệnh, giúp chương trình của bạn dễ đọc
và gỡ lỗi hơn.

• Các hàm có thể làm cho một chương trình nhỏ hơn bằng cách loại bỏ mã lặp lại. Sau này, nếu bạn
thực hiện thay đổi, bạn chỉ phải thực hiện nó ở một nơi.

• Chia một chương trình dài thành các chức năng cho phép bạn gỡ lỗi từng phần một
và sau đó lắp ráp chúng thành một tổng thể hoạt động.

• Các chức năng được thiết kế tốt thường hữu ích cho nhiều chương trình. Sau khi bạn viết và gỡ
lỗi một, bạn có thể sử dụng lại nó.

3.12 Gỡ lỗi
Một trong những kỹ năng quan trọng nhất bạn sẽ có được là gỡ lỗi. Mặc dù nó có thể là dấu vết nhưng
gỡ lỗi là một trong những phần giàu trí tuệ, thử thách và thú vị nhất của lập trình.

Theo một số cách, gỡ lỗi giống như công việc thám tử. Bạn phải đối mặt với các manh mối và bạn phải
suy ra các quá trình và sự kiện dẫn đến kết quả bạn thấy.

Gỡ lỗi cũng giống như một môn khoa học thực nghiệm. Khi bạn có ý tưởng về những gì đang xảy ra, bạn
sửa đổi chương trình của mình và thử lại. Nếu giả thuyết của bạn là đúng, bạn có thể dự đoán kết quả
của việc sửa đổi và bạn tiến một bước gần hơn đến một chương trình làm việc. Nếu giả thuyết của bạn
sai, bạn phải nghĩ ra một giả thuyết mới. Như Sherlock Holmes đã chỉ ra, "Khi bạn đã loại bỏ điều
không thể, thì bất cứ điều gì còn lại, dù có thể xảy ra, cũng phải là sự thật." (A. Conan Doyle, Dấu
hiệu của bộ tứ)

Đối với một số người, lập trình và gỡ lỗi đều giống nhau. Đó là, lập trình là quá trình gỡ lỗi dần dần
một chương trình cho đến khi nó thực hiện những gì bạn muốn. Ý tưởng là bạn nên bắt đầu với một chương
trình đang hoạt động và thực hiện các sửa đổi nhỏ, gỡ lỗi chúng khi bạn tiếp tục.

Ví dụ, Linux là một hệ điều hành chứa hàng triệu dòng mã, nhưng nó khởi đầu là một chương trình đơn
giản Linus Torvalds dùng để khám phá chip Intel 80386. Trả lời Larry Greenfield, “Một trong những dự
án trước đây của Linus là một chương trình chuyển đổi giữa in AAAA và BBBB. Điều này sau đó đã phát
triển thành Linux. " (Hướng dẫn Người dùng Linux Phiên bản Beta 1).

3.13 Bảng chú giải thuật ngữ

function: Một chuỗi các câu lệnh được đặt tên để thực hiện một số hoạt động hữu ích. Hàm có thể có
hoặc không nhận đối số và có thể có hoặc không tạo ra kết quả.

định nghĩa hàm: Một câu lệnh tạo một hàm mới, chỉ định tên của nó, các tham số và các câu lệnh mà nó
chứa.

đối tượng hàm: Một giá trị được tạo bởi một đị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.

header: Dòng đầu tiên của định nghĩa hàm.


Machine Translated by Google

26 Chương 3. Chức năng

body: Chuỗi các câu lệnh bên trong một định nghĩa hàm.

tham số: Tên được sử dụng bên trong một hàm để tham chiếu đến giá trị được truyền dưới dạng đối số.

hàm gọi: Một câu lệnh chạy một hàm. Nó bao gồm tên hàm theo sau là danh sách đối số trong dấu ngoặc

đơn.

đối số: Một giá trị được cung cấp cho một hàm khi hàm được gọi. Giá trị này là
đã ký vào tham số tương ứng trong hàm.

biến cục bộ: Một biến được định nghĩa bên trong một hàm. Một biến cục bộ chỉ có thể được sử dụng
bên trong chức năng của nó.

Giá trị trả về: Kết quả của một hàm. Nếu một lệnh gọi hàm được sử dụng như một biểu thức, giá trị trả
về là giá trị của biểu thức.

Hàm có kết quả: Một hàm trả về một giá trị.

Hàm void: Một hàm luôn trả về Không có.

Không: Một giá trị đặc biệt được trả về bởi các hàm void.

mô-đun: Một tệp chứa một tập hợp các chức năng liên quan và các định nghĩa khác.

câu lệnh nhập: Một câu lệnh đọc tệp mô-đun và tạo một đối tượng mô-đun.

đối tượng mô-đun: Một giá trị được tạo bởi một câu lệnh nhập cung cấp quyền truy cập vào các giá trị
được định nghĩa trong một mô-đun.

ký hiệu dấu chấm: Cú pháp để gọi một hàm trong một mô-đun khác bằng cách chỉ định tên mod ule theo
sau là dấu chấm (dấu chấm) và tên hàm.

thành phần: Sử dụng một biểu thức như một phần của một biểu thức lớn hơn hoặc một câu lệnh như một phần
của một câu lệnh lớn hơn.

luồng thực thi: Các câu lệnh chạy trong.

sơ đồ ngăn xếp: Một biểu diễn đồ họa của một ngăn xếp các hàm, các biến của chúng và
giá trị mà họ tham chiếu.

frame: Một hộp trong sơ đồ ngăn xếp biểu thị một lệnh gọi hàm. Nó chứa các biến địa phương
ables và các tham số của hàm.

traceback: Danh sách các chức năng đang thực thi, được in ra khi có ngoại lệ.

3.14 Bài tập

Bài tập 3.1. Viết một hàm có tên right_justify nhận một chuỗi có tên là s làm tham số và in ra chuỗi
có đủ khoảng trắng ở đầu sao cho ký tự cuối cùng của chuỗi nằm trong cột 70 của màn hình. >>>
right_justify ('monty')

monty

Gợi ý: Sử dụng cách nối và lặp chuỗi. Ngoài ra, Python cung cấp một hàm dựng sẵn có tên là len trả về
độ dài của một chuỗi, vì vậy giá trị của len ('monty') là 5.
Machine Translated by Google

3,14. Bài tập 27

Bài tập 3.2. Đối tượng hàm là một giá trị mà bạn có thể gán cho một biến hoặc chuyển làm đối số. Vì
ví dụ, do_twice là một hàm lấy một đối tượng hàm làm đối số và gọi nó hai lần:

def do_twice (f):


f ()
f ()

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

def print_spam ():


print ('spam')

do_twice (print_spam)

1. Nhập ví dụ này vào một tập lệnh và kiểm tra nó.

2. Sửa đổi do_twice để nó nhận hai đối số, một đối tượng hàm và một giá trị, đồng thời gọi hàm
hàm hai lần, truyền giá trị dưới dạng đối số.

3. Sao chép định nghĩa của print_twice ở phần trước trong chương này vào tập lệnh của bạn.

4. Sử dụng phiên bản đã sửa đổi của do_twice để gọi print_twice hai lần, chuyển 'spam' dưới dạng
tranh luận.

5. Định nghĩa một hàm mới được gọi là do_four nhận một đối tượng hàm và một giá trị và gọi hàm
hàm bốn lần, chuyển giá trị dưới dạng tham số. Chỉ nên có hai câu lệnh trong
phần thân của chức năng này, không phải bốn.

Lời giải: http: // thinkpython2. com / code / do_ bốn. py


Bài tập 3.3. Lưu ý: Bài tập này chỉ nên được thực hiện bằng cách sử dụng các câu lệnh và các tính năng khác mà chúng tôi

đã học cho đến nay.

1. Viết một hàm vẽ lưới như sau:

+ - - - - + - - - - +

| | |
| | |
| | |
| | |
+ - - - - + - - - - +

| | |
| | |
| | |
| | |
+ - - - - + - - - - +

Gợi ý: để in nhiều hơn một giá trị trên một dòng, bạn có thể in một chuỗi được phân tách bằng dấu phẩy của
giá trị:

print ('+', '-')

Theo mặc định, in tiến tới dòng tiếp theo, nhưng bạn có thể ghi đè hành vi đó và đặt
khoảng trắng ở cuối, như thế này:

print ('+', end = '')


in('-')
Machine Translated by Google

28 Chương 3. Chức năng

Đầu ra của các câu lệnh này là '+ -' trên cùng một dòng. Kết quả từ câu lệnh in tiếp
theo sẽ bắt đầu trên dòng tiếp theo.

2. Viết một hàm vẽ một lưới tương tự có bốn hàng và bốn cột.

Lời giải: http: // thinkpython2. com / mã / lưới. py Tín dụng: Bài tập này dựa trên một bài
tập trong Oualline, Lập trình C thực hành, Ấn bản thứ ba, O'Reilly Media, 1997.
Machine Translated by Google

Chương 4

Nghiên cứu điển hình: thiết kế giao diện

Chương này trình bày một nghiên cứu điển hình chứng minh một quy trình thiết kế các chức năng
hoạt động cùng nhau.

Nó giới thiệu mô-đun con rùa, cho phép bạn tạo hình ảnh bằng đồ họa con rùa.
Mô-đun rùa được bao gồm trong hầu hết các cài đặt Python, nhưng nếu bạn đang chạy Python
bằng PythonAnywhere, bạn sẽ không thể chạy các ví dụ rùa (ít nhất là bạn không thể khi
tôi viết điều này).

Nếu bạn đã cài đặt Python trên máy tính của mình, bạn sẽ có thể chạy các ví dụ. Nếu
không, bây giờ là thời điểm tốt để cài đặt. Tôi đã đăng hướng dẫn tại http: //tinyurl.com/
thinkpython2e.

Ví dụ về mã từ chương này có sẵn trên http://thinkpython2.com/code/ polygon.py.

4.1 Mô-đun rùa


Để kiểm tra xem bạn có mô-đun rùa hay không, hãy mở trình thông dịch Python và nhập

>>> nhập rùa >>>


bob = rùa.Turtle ()

Khi bạn chạy mã này, nó sẽ tạo một cửa sổ mới với mũi tên nhỏ đại diện cho
con rùa. Đóng cửa sổ.

Tạo một tệp có tên mypolygon.py và nhập mã sau:

nhập rùa bob =


rùa.Turtle () print
(bob) rùa.mainloop ()

Mô-đun rùa (với chữ thường 't') cung cấp một hàm có tên là Turtle (với chữ 'T' viết hoa
lên) để tạo một đối tượng Turtle, chúng tôi gán cho một biến có tên là bob. Việc in bob
hiển thị một cái gì đó giống như: <đối tượng rùa.Turtle tại 0xb7bfbf4c>
Machine Translated by Google

30 Chương 4. Nghiên cứu tình huống: thiết kế giao diện

Điều này có nghĩa là bob đề cập đến một đối tượng có loại Turtle như được định nghĩa trong mô-đun rùa.

mainloop ra lệnh cho cửa sổ đợi người dùng làm điều gì đó, mặc dù trong trường hợp này, người dùng không có gì phải

làm ngoại trừ đóng cửa sổ.

Sau khi tạo Rùa, bạn có thể gọi một phương thức để di chuyển nó xung quanh cửa sổ. Một phương thức tương tự như một

hàm, nhưng nó sử dụng cú pháp hơi khác. Ví dụ, để di chuyển con rùa về phía trước:

bob.fd (100)

Phương thức, fd, được liên kết với đối tượng rùa mà chúng ta đang gọi là bob. Gọi một phương thức giống như thực

hiện một yêu cầu: bạn đang yêu cầu bob di chuyển về phía trước.

Đối số của fd là khoảng cách tính bằng pixel, vì vậy kích thước thực tế phụ thuộc vào màn hình của bạn.

Các phương pháp khác mà bạn có thể sử dụng đối với Rùa là di chuyển lùi, rẽ trái và rẽ phải. Đối số cho lt và rt là

một góc tính bằng độ.

Ngoài ra, mỗi con Rùa đang cầm một cây bút, bút này có thể hướng xuống hoặc hướng lên trên; nếu bút bị hạ xuống,

Rùa sẽ để lại dấu vết khi nó di chuyển. Phương thức pu và pd là viết tắt của “pen up” và “pen down”.

Để vẽ một góc vuông, hãy thêm các dòng này vào chương trình (sau khi tạo bob và trước khi gọi mainloop):

bob.fd (100)

bob.lt (90)

bob.fd (100)

Khi bạn chạy chương trình này, bạn sẽ thấy bob di chuyển về phía đông và sau đó là phía bắc, để lại hai đoạn đường

phía sau.

Bây giờ hãy sửa đổi chương trình để vẽ một hình vuông. Đừng tiếp tục cho đến khi bạn đã làm cho nó hoạt động!

4.2 Lặp lại đơn giản

Rất có thể bạn đã viết một cái gì đó như thế này:

bob.fd (100) bob.lt (90)

bob.fd (100)

bob.lt (90)

bob.fd (100)

bob.lt (90)

bob.fd (100)

Chúng ta có thể làm điều tương tự ngắn gọn hơn với câu lệnh for. Thêm ví dụ này vào mypolygon.py và chạy lại: for i

in range (4): print ('Xin chào!')

Bạn sẽ thấy một cái gì đó như thế này:


Machine Translated by Google

4.3. Bài tập 31

Xin chào!

Xin chào!

Xin chào!

Xin chào!

Đây là cách sử dụng đơn giản nhất của câu lệnh for; chúng ta sẽ xem thêm sau. Nhưng chừng đó cũng
đủ để bạn viết lại chương trình vẽ hình vuông của mình. Đừng tiếp tục cho đến khi bạn làm.

Đây là câu lệnh for vẽ một hình vuông: for i

trong khoảng (4): bob.fd (100) bob.lt (90)

Cú pháp của câu lệnh for tương tự như định nghĩa hàm. Nó có tiêu đề kết thúc bằng dấu hai chấm và
phần thân thụt vào. Phần nội dung có thể chứa bất kỳ số lượng câu lệnh nào.

Câu lệnh for còn được gọi là vòng lặp vì luồng thực thi chạy qua phần thân và sau đó vòng trở lại
phần trên cùng. Trong trường hợp này, nó chạy cơ thể bốn lần.

Phiên bản này thực sự hơi khác so với mã vẽ hình vuông trước đó vì nó thực hiện một lượt khác sau
khi vẽ cạnh cuối cùng của hình vuông. Lượt thêm mất nhiều thời gian hơn, nhưng nó đơn giản hóa mã
nếu chúng ta làm cùng một điều mỗi lần trong vòng lặp. Phiên bản này cũng có tác dụng để rùa trở
lại vị trí xuất phát, quay mặt về hướng xuất phát.

4.3 Bài tập

Sau đây là một loạt các bài tập sử dụng môđun con rùa. Chúng nhằm mục đích vui vẻ, nhưng chúng
cũng có lý. Trong khi bạn đang làm việc với chúng, hãy nghĩ xem vấn đề là gì.

Các phần sau có lời giải cho các bài tập, vì vậy đừng xem cho đến khi bạn hoàn thành (hoặc ít nhất
là đã thử).

1. Viết một hàm có tên là bình phương nhận tham số là t, là con rùa. Nó sẽ sử dụng con rùa để
vẽ một hình vuông.

Viết lệnh gọi hàm chuyển bob làm đối số cho ô vuông, sau đó chạy lại chương trình.

2. Thêm một tham số khác, có tên là chiều dài, vào hình vuông. Sửa đổi phần thân để chiều dài
của các cạnh bằng chiều dài, sau đó sửa đổi lời gọi hàm để cung cấp đối số thứ hai. Chạy
lại chương trình. Kiểm tra chương trình của bạn với một loạt các giá trị cho độ dài.

3. Tạo một bản sao của hình vuông và đổi tên thành đa giác. Thêm một tham số khác có tên n và
sửa đổi phần thân để nó vẽ một đa giác đều n cạnh. Gợi ý: Các góc bên ngoài của một đa giác
đều n mặt là 360 / n độ.

4. Viết một hàm gọi là đường tròn lấy rùa, t và bán kính, r làm tham số và vẽ một đường tròn
gần đúng bằng cách gọi đa giác có độ dài và số cạnh thích hợp. Kiểm tra hàm của bạn với một
phạm vi giá trị của r.

Gợi ý: tính chu vi của hình tròn và đảm bảo rằng độ dài * n =
đường tròn.

5. Tạo một phiên bản tổng quát hơn của đường tròn được gọi là cung có thêm một góc tham số, xác
định phần nào của đường tròn sẽ vẽ. góc tính theo đơn vị độ, vì vậy khi góc = 360, cung sẽ
vẽ một đường tròn hoàn chỉnh.
Machine Translated by Google

32 Chương 4. Nghiên cứu tình huống: thiết kế giao diện

4.4 Đóng gói


Bài tập đầu tiên yêu cầu bạn đặt mã bản vẽ hình vuông của mình vào một định nghĩa hàm và sau đó gọi hàm, truyền

con rùa dưới dạng tham số. Đây là một giải pháp:

def square (t): cho

tôi trong khoảng (4): t.fd


(100) t.lt (90)

hình vuông (bob)

Các câu lệnh trong cùng, fd và lt được thụt vào hai lần để cho thấy rằng chúng nằm bên trong vòng lặp for, bên

trong định nghĩa hàm. Dòng tiếp theo, hình vuông (bob), nằm ngang với lề trái, cho biết phần cuối của cả vòng

lặp for và định nghĩa hàm.

Bên trong hàm, t đề cập đến cùng một bob rùa, vì vậy t.lt (90) có cùng hiệu ứng với bob.lt (90). Trong trường

hợp đó, tại sao không gọi tham số bob? Ý tưởng là t có thể là bất kỳ con rùa nào, không chỉ là bob, vì vậy bạn

có thể tạo một con rùa thứ hai và chuyển nó làm đối số cho hình vuông:

alice = rùa.Turtle () hình vuông

(alice)

Gói một đoạn mã trong một hàm được gọi là đóng gói. Một trong những lợi ích của việc đóng gói là nó gắn một tên

vào mã, nó đóng vai trò như một loại tài liệu. Một ưu điểm khác là nếu bạn sử dụng lại đoạn mã, thì việc gọi

một hàm sẽ ngắn gọn hơn hai lần so với việc sao chép và dán nội dung!

4.5 Tổng quát hóa

Bước tiếp theo là thêm tham số độ dài vào hình vuông. Đây là một giải pháp:

def vuông (t, chiều dài): cho

tôi trong phạm vi (4):

t.fd (chiều dài)


t.lt (90)

hình vuông (bob, 100)

Thêm một tham số vào một hàm được gọi là tổng quát hóa vì nó làm cho hàm tổng quát hơn: trong phiên bản trước,

hình vuông luôn có cùng kích thước; trong phiên bản này, nó có thể có kích thước bất kỳ.

Bước tiếp theo cũng là một sự tổng quát hóa. Thay vì vẽ hình vuông, đa giác vẽ đa giác đều với bất kỳ số cạnh

nào. Đây là một giải pháp: đa giác def (t, n, chiều dài): angle = 360 / n với tôi trong khoảng (n): t.fd (chiều

dài) t.lt (góc)

đa giác (bob, 7, 70)


Machine Translated by Google

4.6. Thiêt kê giao diê n 33

Ví dụ này vẽ một đa giác 7 cạnh với chiều dài cạnh 70.

Nếu bạn đang sử dụng Python 2, giá trị của góc có thể bị tắt do phép chia số nguyên. Một giải
pháp đơn giản là tính góc = 360.0 / n. Bởi vì tử số là một số dấu phẩy động, kết quả là dấu phẩy
động.

Khi một hàm có nhiều hơn một vài đối số dạng số, rất dễ quên chúng là gì hoặc thứ tự của chúng.
Trong trường hợp đó, bạn nên đưa tên của các tham số vào danh sách đối số: polygon (bob, n = 7,
chiều dài = 70)

Chúng được gọi là các đối số từ khóa vì chúng bao gồm tên tham số là “từ khóa” (không nên nhầm
lẫn với các từ khóa Python như while và def).

Cú pháp này làm cho chương trình dễ đọc hơn. Nó cũng là một lời nhắc nhở về cách hoạt động của
các đối số và tham số: khi bạn gọi một hàm, các đối số được gán cho tham số
eters.

4.6 Thiết kế giao diện

Bước tiếp theo là viết vòng tròn, lấy bán kính, r, làm tham số. Đây là một giải pháp đơn giản sử
dụng đa giác để vẽ một đa giác 50 cạnh:

nhập toán

vòng tròn def (t, r):


chu vi = 2 * math.pi * rn = 50

length = chu vi / n đa giác (t,


n, chiều dài)

Dòng đầu tiên tính chu vi của một hình tròn có bán kính r bằng công thức 2πr.
Vì chúng tôi sử dụng math.pi, chúng tôi phải nhập toán học. Theo quy ước, các câu lệnh nhập
thường ở đầu tập lệnh.

n là số đoạn thẳng trong tính gần đúng của một đường tròn, do đó độ dài là độ dài của mỗi đoạn.
Do đó, đa giác vẽ một đa giác 50 cạnh xấp xỉ một hình tròn có bán kính r.

Một hạn chế của giải pháp này là n là hằng số, có nghĩa là đối với đường tròn rất lớn, đoạn
thẳng quá dài, còn đối với đường tròn nhỏ, chúng ta lãng phí thời gian để vẽ các đoạn rất nhỏ.
Một giải pháp sẽ là tổng quát hóa hàm bằng cách lấy n làm tham số.
Điều này sẽ cung cấp cho người dùng (bất kỳ ai gọi vòng kết nối) nhiều quyền kiểm soát hơn, nhưng giao diện sẽ kém
sạch sẽ hơn.

Giao diện của một hàm là một bản tóm tắt về cách nó được sử dụng: các tham số là gì? Chức năng
làm gì? Và giá trị trả về là gì? Một giao diện là "sạch" nếu nó cho phép người gọi làm những gì

họ muốn mà không cần xử lý các chi tiết không cần thiết.

Trong ví dụ này, r thuộc về giao diện vì nó chỉ định hình tròn được vẽ. n ít thích hợp hơn vì nó
liên quan đến các chi tiết về cách hình tròn sẽ được hiển thị.

Thay vì làm lộn xộn giao diện, tốt hơn nên chọn một giá trị thích hợp của n phụ thuộc vào chu
vi:
Machine Translated by Google

34 Chương 4. Nghiên cứu tình huống: thiết kế giao diện

def circle (t, r): chu

vi = 2 * math.pi * rn = int (chu vi / 3) +


3 length = chu vi / n polygon (t, n, length)

Bây giờ số đoạn là một số nguyên gần chu vi / 3, do đó độ dài của mỗi đoạn là xấp xỉ 3, đủ nhỏ để các
hình tròn trông đẹp, nhưng đủ lớn để hiệu quả và có thể chấp nhận được đối với bất kỳ hình tròn có kích
thước nào.

Thêm 3 vào n đảm bảo rằng đa giác có ít nhất 3 cạnh.

4.7 Tái cấu trúc


Khi tôi viết hình tròn, tôi có thể sử dụng lại đa giác vì đa giác nhiều cạnh là một hình gần đúng tốt của
hình tròn. Nhưng vòng cung không hợp tác như vậy; chúng ta không thể sử dụng đa giác hoặc hình tròn để vẽ
một cung tròn.

Một cách thay thế là bắt đầu với một bản sao của đa giác và biến nó thành hình cung. Kết quả có thể giống
như sau: def arc (t, r, angle): arc_length = 2 * math.pi * r * angle / 360 n = int (arc_length / 3) + 1

step_length = arc_length / n step_angle = angle / n

đối với tôi trong phạm vi

(n): t.fd (độ dài bước)

t.lt (hình chữ nhật bước)

Nửa sau của hàm này trông giống như đa giác, nhưng chúng tôi không thể sử dụng lại đa giác mà không thay

đổi giao diện. Chúng ta có thể tổng quát hóa đa giác để lấy một góc làm đối số thứ ba, nhưng khi đó đa
giác sẽ không còn là một cái tên thích hợp nữa! Thay vào đó, hãy gọi hàm polyline tổng quát hơn: def
polyline (t, n, length, angle): for i in range (n): t.fd (length) t.lt (angle)

Bây giờ chúng ta có thể viết lại polygon và arc để sử dụng

polyline: def polygon (t, n, length): angle = 360.0 / n polyline


(t, n, length, angle)

def arc (t, r, angle):


arc_length = 2 * math.pi * r * angle / 360 n = int
(arc_length / 3) + 1 step_length = arc_length / n
step_angle = float (angle) / n polyline (t, n, step_length,
step_angle)

Cuối cùng, chúng ta có thể viết lại vòng tròn để sử dụng vòng cung:
Machine Translated by Google

4.8. Một kế hoạch phát triển 35

def circle (t, r): arc

(t, r, 360)

Quá trình này — sắp xếp lại một chương trình để cải thiện giao diện và tạo điều kiện sử dụng lại mã — được
gọi là tái cấu trúc. Trong trường hợp này, chúng tôi nhận thấy rằng có mã tương tự trong cung và đa giác, vì
vậy chúng tôi "tính toán nó" thành đa giác.

Nếu chúng tôi đã lên kế hoạch trước, chúng tôi có thể đã viết polyline trước và tránh tái cấu trúc, nhưng bạn

thường không biết đủ khi bắt đầu một dự án để thiết kế tất cả các giao diện.

Khi bạn bắt đầu viết mã, bạn sẽ hiểu vấn đề tốt hơn. Đôi khi việc tái cấu trúc là một dấu hiệu cho thấy bạn

đã học được điều gì đó.

4.8 Kế hoạch phát triển

Kế hoạch phát triển là một quá trình để viết các chương trình. Quá trình chúng tôi sử dụng trong nghiên cứu

điển hình này là “đóng gói và tổng quát hóa”. Các bước của quá trình này là:

1. Bắt đầu bằng cách viết một chương trình nhỏ không có định nghĩa chức năng.

2. Khi bạn bắt đầu làm việc chương trình, hãy xác định một phần mạch lạc của nó, gói phần đó trong một

hàm và đặt tên cho nó.

3. Tổng quát hóa chức năng bằng cách thêm các tham số thích hợp.

4. Lặp lại các bước 1–


3 cho đến khi bạn có một tập hợp các chức năng hoạt động. Sao chép và dán hoạt động

mã để tránh nhập lại (và gỡ lỗi lại).

5. Tìm kiếm cơ hội để cải thiện chương trình bằng cách tái cấu trúc. Ví dụ: nếu bạn có mã tương tự ở một

số nơi, hãy xem xét việc gộp mã đó vào một hàm tổng quát thích hợp.

Quá trình này có một số hạn chế — chúng ta sẽ xem các lựa chọn thay thế sau — nhưng nó có thể hữu ích nếu

bạn không biết trước cách chia chương trình thành các hàm. Cách tiếp cận này cho phép bạn thiết kế theo ý

muốn.

4.9 docstring

Chuỗi doc là một chuỗi ở đầu hàm giải thích giao diện (“doc” là viết tắt của “tài liệu”). Đây là một ví dụ:

def polyline (t, n, length, angle): "" "Vẽ n đoạn thẳng với độ dài và góc cho trước (tính bằng độ) giữa

chúng. T là một con rùa.

"" "

đối với tôi trong phạm vi

(n): t.fd (chiều

dài) t.lt (góc)

Theo quy ước, tất cả các chuỗi doc là chuỗi được trích dẫn ba, còn được gọi là chuỗi nhiều dòng vì dấu ngoặc

kép cho phép chuỗi kéo dài nhiều hơn một dòng.
Machine Translated by Google

36 Chương 4. Nghiên cứu tình huống: thiết kế giao diện

Nó ngắn gọn, nhưng nó chứa thông tin cần thiết mà ai đó sẽ cần để sử dụng chức năng này. Nó giải thích

một cách ngắn gọn chức năng đó làm gì (mà không cần đi sâu vào chi tiết cách thức hoạt động của nó).
Nó giải thích tác động của mỗi tham số đối với hoạt động của hàm và loại mỗi tham số nên là gì (nếu nó
không rõ ràng).

Viết loại tài liệu này là một phần quan trọng của thiết kế giao diện. Một giao diện được thiết kế tốt
phải đơn giản để giải thích; nếu bạn gặp khó khăn trong việc giải thích một trong các chức năng của
mình, có thể giao diện có thể được cải thiện.

4.10 Gỡ lỗi
Giao diện giống như một hợp đồng giữa một hàm và một bên gọi. Người gọi đồng ý cung cấp các tham số
nhất định và chức năng đồng ý thực hiện công việc nhất định.

Ví dụ, polyline yêu cầu bốn đối số: t phải là một con Rùa; n phải là một số nguyên; chiều dài phải là
một số dương; và góc phải là một số, được hiểu là độ.

Các yêu cầu này được gọi là điều kiện tiên quyết vì chúng được cho là đúng trước khi hàm bắt đầu thực
thi. Ngược lại, điều kiện ở cuối hàm là điều kiện đăng. Điều kiện sau bao gồm tác dụng dự kiến của
chức năng (như vẽ các đoạn đường thẳng) và bất kỳ hiệu ứng phụ nào (như di chuyển Rùa hoặc thực hiện
các thay đổi khác).

Điều kiện tiên quyết là trách nhiệm của người gọi. Nếu người gọi vi phạm điều kiện tiên quyết (được
đề cập đúng cách!) Và chức năng không hoạt động chính xác, thì lỗi nằm ở người gọi chứ không phải ở
chức năng.

Nếu các điều kiện trước được thỏa mãn và các điều kiện sau không được đáp ứng, thì lỗi nằm trong chức năng.
Nếu các điều kiện trước và sau của bạn rõ ràng, chúng có thể giúp gỡ lỗi.

4.11 Bảng chú giải thuật ngữ

method: Một hàm được liên kết với một đối tượng và được gọi bằng cách sử dụng ký hiệu dấu chấm.

loop: Một phần của chương trình có thể chạy lặp lại.

encapsulation: Quá trình chuyển đổi một chuỗi các câu lệnh thành một hàm defi
ý niệm.

khái quát hóa: Quá trình thay thế một cái gì đó cụ thể không cần thiết (như một số) bằng một cái gì đó
chung chung một cách thích hợp (như một biến hoặc tham số).

đối số từ khóa: Đối số bao gồm tên của tham số dưới dạng “khóa
từ".

interface: Mô tả cách sử dụng một hàm, bao gồm tên và mô tả của các đối số và giá trị trả về.

tái cấu trúc: Quá trình sửa đổi một chương trình làm việc để cải thiện các giao diện chức năng và các
phẩm chất khác của mã.

kế hoạch phát triển: Một quy trình để viết chương trình.


Machine Translated by Google

4.12. Bài tập 37

Hình 4.1: Hoa rùa.

Hình 4.2: Bánh nướng con rùa.

docstring: Một chuỗi xuất hiện ở đầu định nghĩa hàm để ghi lại hàm
giao diện của tion.

precondition: Một yêu cầu phải được người gọi đáp ứng trước khi một chức năng bắt đầu.

postcondition: Một yêu cầu cần được hàm thỏa mãn trước khi nó kết thúc.

4.12 Bài tập

Bài tập 4.1. Tải xuống mã trong chương này từ http: // thinkpython2. com / code / polygon. py

1. Vẽ biểu đồ ngăn xếp thể hiện trạng thái của chương trình trong khi thực hiện vòng tròn (bob,
radius). Bạn có thể thực hiện số học bằng tay hoặc thêm các câu lệnh in vào mã.

2. Phiên bản của cung trong Mục 4.7 không chính xác lắm vì xấp xỉ tuyến tính của đường tròn luôn
nằm ngoài đường tròn thực. Kết quả là Rùa kết thúc cách điểm đến chính xác một vài pixel.
Giải pháp của tôi cho thấy một cách để giảm ảnh hưởng của lỗi này. Đọc mã và xem nó có phù
hợp với bạn không. Nếu bạn vẽ một sơ đồ, bạn có thể thấy nó hoạt động như thế nào.

Bài tập 4.2. Viết một tập hợp các hàm tổng quát thích hợp có thể vẽ hoa như trong hình 4.1.

Giải http: // thinkpython2. com / mã / hoa. py, cũng yêu cầu http:
pháp: // thinkpython2. com / code / polygon. py
Bài tập 4.3. Viết một tập hợp hàm tổng quát thích hợp có thể vẽ các hình như trong hình 4.2.

Lời giải: http: // thinkpython2. com / code / pie. py


Bài tập 4.4. Các chữ cái trong bảng chữ cái có thể được xây dựng từ một số lượng vừa phải của các
độ cao cơ bản, như các đường thẳng đứng và ngang và một vài đường cong. Thiết kế một bảng chữ cái
có thể được vẽ với một số phần tử cơ bản tối thiểu và sau đó viết các hàm để vẽ các chữ cái.

Bạn nên viết một hàm cho mỗi chữ cái, với các tên draw_a, draw_b, v.v. và đặt các hàm của bạn trong
một tệp có tên là letter.py. Bạn có thể tải xuống "máy đánh chữ con rùa" từ http: // thinkpython2.
com / code / máy đánh chữ. py để giúp bạn kiểm tra mã của mình.
Machine Translated by Google

38 Chương 4. Nghiên cứu tình huống: thiết kế giao diện

Bạn có thể nhận được giải pháp từ http: // thinkpython2. com / mã / chữ cái. py; nó cũng yêu cầu http: //
thinkpython2. com / code / polygon. py

Bài tập 4.5. Đọc về xoắn ốc tại http: // vi. wikipedia. org / wiki / Xoắn ốc; sau đó viết một chương trình vẽ

một hình xoắn ốc Archimedian (hoặc một trong những kiểu khác). Lời giải: http: // thinkpython2. com / mã / xoắn
ốc. py
Machine Translated by Google

Chương 5

Điều kiện và đệ quy

Chủ đề chính của chương này là câu lệnh if, thực thi các đoạn mã khác nhau tùy thuộc vào trạng thái của chương

trình. Nhưng trước tiên tôi muốn giới thiệu hai toán tử mới: phân chia tầng và mô đun.

5.1 Phân chia tầng và mô đun

Toán tử chia tầng , //, chia hai số và làm tròn xuống một số nguyên. Ví dụ, giả sử thời gian chạy của một bộ

phim là 105 phút. Bạn có thể muốn biết đó là bao lâu theo giờ. Phép chia thông thường trả về một số dấu phẩy

động:

>>> phút = 105 >>>

phút / 60
1,75

Nhưng chúng ta thường không viết giờ bằng dấu thập phân. Phân chia tầng trả về số nguyên của giờ, làm tròn

xuống:

>>> phút = 105 >>> giờ

= phút // 60
>>> giờ

Để nhận phần còn lại, bạn có thể trừ đi một giờ trong vài phút:

>>> phần còn lại = phút - giờ * 60

>>> phần còn lại

45

Một giải pháp thay thế là sử dụng toán tử mô đun, %, chia hai số và trả về phần còn lại.

>>> phần còn lại = phút% 60


>>> phần còn lại

45

Toán tử mô-đun hữu ích hơn có vẻ như. Ví dụ: bạn có thể kiểm tra xem một số có chia hết cho số khác hay không

— nếu x% y bằng 0 thì x có chia hết cho y hay không.


Machine Translated by Google

40 Chương 5. Điều kiện và đệ quy

Ngoài ra, bạn có thể trích xuất chữ số tận cùng bên phải hoặc các chữ số từ một số. Ví dụ: x% 10 sinh
ra chữ số tận cùng bên phải của x (trong cơ số 10). Tương tự x% 100 cho kết quả hai chữ số cuối cùng.

Nếu bạn đang sử dụng Python 2, phép chia hoạt động theo cách khác. Toán tử chia, /, thực hiện phép
chia tầng nếu cả hai toán hạng đều là số nguyên và phép chia dấu phẩy động nếu một trong hai toán hạng là
một chiếc phao.

5.2 Biểu thức Boolean


Biểu thức boolean là một biểu thức đúng hoặc sai. Các ví dụ sau sử dụng toán tử ==, so sánh hai
toán hạng và tạo ra True nếu chúng bằng nhau và False nếu không:

>>> 5 == 5
ĐÚNG VẬY

>>> 5 == 6
Sai

True và False là các giá trị đặc biệt thuộc kiểu bool; chúng không phải là chuỗi: >>> type

(True) <class 'bool'>

>>> gõ (Sai) <class


'bool'>

Toán tử == là một trong những toán tử quan hệ; những người khác là:

x! = # x không bằng y # x lớn


yx> yx hơn y # x nhỏ hơn y # x lớn
<yx> = hơn hoặc bằng y # x nhỏ hơn
yx <= y hoặc bằng y

Mặc dù các phép toán này có thể quen thuộc với bạn, nhưng các ký hiệu Python khác với các ký hiệu
toán học. Một lỗi phổ biến là sử dụng dấu bằng đơn (=) thay vì dấu bằng kép (==). Hãy nhớ rằng =
là một toán tử gán và == là một toán tử quan hệ. Không có cái gọi là = <hoặc =>.

5.3 Toán tử logic


Có ba toán tử logic: và, hoặc, và không. Ngữ nghĩa (nghĩa) của các toán tử này tương tự như nghĩa
của chúng trong tiếng Anh. Ví dụ, x> 0 và x <10 chỉ đúng khi x lớn hơn 0 và nhỏ hơn 10.

n% 2 == 0 hoặc n% 3 == 0 đúng nếu một trong hai hoặc cả hai điều kiện đều đúng, nghĩa là nếu số đó

chia hết cho 2 hoặc 3.

Cuối cùng, toán tử not phủ định một biểu thức boolean, vì vậy not (x> y) là đúng nếu x> y là sai,
nghĩa là, nếu x nhỏ hơn hoặc bằng y.

Nói một cách chính xác, các toán hạng của các toán tử logic phải là biểu thức boolean, nhưng Python
không nghiêm ngặt lắm. Mọi số khác không được hiểu là True:
Machine Translated by Google

5.4. Thực hiện có điều kiện 41

>>> 42 và Đúng
ĐÚNG VẬY

Tính linh hoạt này có thể hữu ích, nhưng có một số điều tinh vi có thể gây nhầm lẫn.
Bạn có thể muốn tránh nó (trừ khi bạn biết mình đang làm gì).

5.4 Thực hiện có điều kiện

Để viết các chương trình hữu ích, chúng ta hầu như luôn cần khả năng kiểm tra các điều kiện và thay đổi hành
vi của chương trình cho phù hợp. Câu lệnh điều kiện cho chúng ta khả năng này. Dạng đơn giản nhất là câu
lệnh if:

nếu x> 0:

print ('x là số dương')

Biểu thức boolean sau if được gọi là điều kiện. Nếu nó là true, câu lệnh thụt lề sẽ chạy. Nếu không, không
có gì xảy ra.

các câu lệnh if có cấu trúc giống như các định nghĩa hàm: một tiêu đề theo sau bởi một phần thân được thụt
lề. Các câu lệnh như thế này được gọi là câu lệnh ghép.

Không có giới hạn về số lượng câu lệnh có thể xuất hiện trong nội dung, nhưng phải có ít nhất một câu lệnh.
Đôi khi, rất hữu ích khi có một phần nội dung không có câu lệnh (thường là một trình giữ chỗ cho mã bạn chưa
viết). Trong trường hợp đó, bạn có thể sử dụng câu lệnh pass mà không cần làm gì cả.

nếu x <0:

đi qua # TODO: cần xử lý các giá trị âm!

5.5 Thực hiện thay thế

Dạng thứ hai của câu lệnh if là "thực thi thay thế", trong đó có hai khả năng có thể và điều kiện xác định
cái nào chạy. Cú pháp có dạng như sau:

nếu x% 2 == 0:

print ('x chẵn')


khác:

print ('x là số lẻ')

Nếu phần dư khi x chia cho 2 là 0 thì ta biết x là chẵn và chương trình sẽ hiển thị thông báo thích hợp.
Nếu điều kiện sai, bộ câu lệnh thứ hai sẽ chạy. Vì điều kiện phải đúng hoặc sai, nên chính xác một trong
các lựa chọn thay thế sẽ chạy. Các lựa chọn thay thế được gọi là các nhánh, bởi vì chúng là các nhánh trong
luồng thực thi.

5.6 Các điều kiện được xâu chuỗi

Đôi khi có nhiều hơn hai khả năng và chúng ta cần nhiều hơn hai nhánh.
Một cách để diễn đạt một phép tính như vậy là điều kiện có chuỗi:
Machine Translated by Google

42 Chương 5. Điều kiện và đệ quy

nếu x <y:
print ('x nhỏ hơn y') elif x> y:
print ('x lớn hơn y')

khác:

print ('x và y bằng nhau')

elif là từ viết tắt của “else if”. Một lần nữa, chính xác một nhánh sẽ chạy. Không có giới hạn về số lượng
câu lệnh elif. Nếu có một mệnh đề khác, nó phải ở cuối, nhưng không nhất thiết phải có một mệnh đề.

nếu lựa chọn == 'a':

vẽ một()
elif choice == 'b':

draw_b ()
elif choice == 'c':

draw_c ()

Mỗi điều kiện được kiểm tra theo thứ tự. Nếu kết quả đầu tiên là sai, giá trị tiếp theo sẽ được kiểm tra,

và cứ tiếp tục như vậy. Nếu một trong số chúng đúng, nhánh tương ứng sẽ chạy và câu lệnh kết thúc. Ngay cả
khi có nhiều hơn một điều kiện đúng, chỉ nhánh đúng đầu tiên chạy.

5.7 Các điều kiện lồng nhau

Một điều kiện cũng có thể được lồng trong một điều kiện khác. Chúng ta có thể đã viết ví dụ trong phần
trước như sau: if x == y: print ('x và y bằng nhau')

khác:

nếu x <y:
print ('x nhỏ hơn y')
khác:

print ('x lớn hơn y')

Điều kiện bên ngoài chứa hai nhánh. Nhánh đầu tiên chứa một câu lệnh đơn giản.
Nhánh thứ hai chứa một câu lệnh if khác, có hai nhánh của riêng nó.

Hai nhánh đó đều là các câu lệnh đơn giản, mặc dù chúng cũng có thể là các câu lệnh điều kiện.

Mặc dù việc thụt lề của các câu lệnh làm cho cấu trúc rõ ràng, nhưng các đoạn điều chỉnh lồng nhau trở
nên khó đọc rất nhanh. Bạn nên tránh chúng khi bạn
có thể.

Các toán tử logic thường cung cấp một cách để đơn giản hóa các câu lệnh điều kiện lồng nhau. Để có
nhiều thông tin, chúng ta có thể viết lại đoạn mã sau bằng một điều kiện duy nhất:
nếu 0 <x:
nếu x <10:

print ('x là số có một chữ số dương.')

Câu lệnh print chỉ chạy nếu chúng ta vượt qua cả hai điều kiện, vì vậy chúng ta có thể nhận được
tác dụng tương tự với toán tử and:
nếu 0 <x và x <10:

print ('x là số có một chữ số dương.')


Machine Translated by Google

5,8. Đệ quy 43

Đối với loại điều kiện này, Python cung cấp một tùy chọn ngắn gọn hơn:
nếu 0 <x <10:

print ('x là số có một chữ số dương.')

5.8 Đệ quy

Nó là hợp pháp cho một chức năng để gọi một chức năng khác; nó cũng hợp pháp cho một hàm để gọi
chính nó. Có thể không rõ ràng tại sao đó là một điều tốt, nhưng nó hóa ra lại là một trong những
điều kỳ diệu nhất mà một chương trình có thể làm được. Ví dụ, hãy xem hàm sau: def countdown (n):

nếu n <= 0:

print ('Blastoff!')
khác:

print (n)
đếm ngược (n-1)

Nếu n là 0 hoặc âm, nó sẽ xuất ra từ, “Blastoff!” Nếu không, nó xuất ra n và sau đó gọi một
hàm có tên đếm ngược - chính nó - chuyển n-1 làm đối số.

Điều gì xảy ra nếu chúng ta gọi hàm này như thế


này? >>> đếm ngược (3)

Việc thực hiện đếm ngược bắt đầu với n = 3 và vì n lớn hơn 0 nên nó xuất ra giá trị 3, rồi tự
gọi ...

Việc thực hiện đếm ngược bắt đầu với n = 2 và vì n lớn hơn 0 nên nó xuất ra giá
trị 2, rồi tự gọi ...

Việc thực hiện đếm ngược bắt đầu với n = 1 và vì n lớn hơn 0 nên nó
xuất ra giá trị 1, rồi tự gọi ...

Việc thực hiện đếm ngược bắt đầu với n = 0 và vì n không lớn
hơn 0 nên nó xuất ra từ, “Blastoff!” và sau đó quay trở lại.

Đếm ngược có n = 1 trả về.

Đếm ngược có n = 2 trả về.

Đếm ngược có n = 3 trả về.

Và sau đó bạn trở lại __main__. Vì vậy, tổng sản lượng trông như thế này:
3
2
1
Nổ ra!

Một hàm gọi chính nó là đệ quy; quá trình thực hiện nó được gọi là đệ quy.

Ví dụ khác, chúng ta có thể viết một hàm in một chuỗi n lần. def print_n

(s, n): if n <= 0:

trở về

print (s)
print_n (s, n-1)
Machine Translated by Google

44 Chương 5. Điều kiện và đệ quy

__chính__

đếm ngược N 3

đếm ngược N 2

đếm ngược N 1

đếm ngược N 0

Hình 5.1: Sơ đồ ngăn xếp.

Nếu n <= 0 câu lệnh trả về thoát khỏi hàm. Luồng thực thi ngay lập tức chuyển sang trình gọi và các dòng
còn lại của hàm không chạy.

Phần còn lại của hàm tương tự như đếm ngược: nó hiển thị s và sau đó tự gọi nó để hiển thị sn - 1 lần bổ
sung. Vì vậy, số dòng đầu ra là 1 + (n - 1), cộng lại
đến n.

Đối với những ví dụ đơn giản như thế này, có lẽ sẽ dễ dàng hơn khi sử dụng vòng lặp for. Nhưng chúng ta
sẽ thấy các ví dụ sau khó viết với vòng lặp for và dễ viết với đệ quy, vì vậy tốt hơn là bạn nên bắt đầu
sớm.

5.9 Sơ đồ ngăn xếp cho các hàm đệ quy

Trong Phần 3.9, chúng tôi đã sử dụng sơ đồ ngăn xếp để biểu diễn trạng thái của chương trình trong khi
gọi hàm. Cùng một loại sơ đồ có thể giúp diễn giải một hàm đệ quy.

Mỗi khi một hàm được gọi, Python sẽ tạo một khung để chứa các biến và tham số cục bộ của hàm. Đối với một
hàm đệ quy, có thể có nhiều hơn một khung trên ngăn xếp cùng một lúc.

Hình 5.1 cho thấy một sơ đồ ngăn xếp để đếm ngược được gọi với n = 3.

Như thường lệ, trên cùng của ngăn xếp là khung cho __main__. Nó trống vì chúng tôi không tạo bất kỳ biến
nào trong __main__ hoặc chuyển bất kỳ đối số nào cho nó.

Bốn khung đếm ngược có các giá trị khác nhau cho tham số n. Phần dưới cùng của ngăn xếp, nơi n = 0, được
gọi là trường hợp cơ sở. Nó không thực hiện cuộc gọi đệ quy, vì vậy không có thêm khung.

Như một bài tập, hãy vẽ một sơ đồ ngăn xếp cho print_n được gọi với s = 'Hello' và n = 2. Sau đó, viết
một hàm được gọi là do_n lấy một đối tượng hàm và một số, n, làm đối số và gọi hàm đã cho n lần.

5.10 Đệ quy vô hạn

Nếu một đệ quy không bao giờ đạt đến một trường hợp cơ sở, nó sẽ tiếp tục thực hiện các cuộc gọi đệ quy
mãi mãi và chương trình không bao giờ kết thúc. Điều này được gọi là đệ quy vô hạn, và nó thường không
phải là một ý kiến hay. Đây là một chương trình tối thiểu với một đệ quy vô hạn:
Machine Translated by Google

5.11. Đầu vào bàn phím 45

def recurse ():


recurse ()

Trong hầu hết các môi trường lập trình, một chương trình có đệ quy vô hạn không thực sự chạy mãi
mãi. Python báo cáo thông báo lỗi khi đạt đến độ sâu đệ quy tối đa: Tệp "<stdin>", dòng 2, trong
tệp đệ quy "<stdin>", dòng 2, trong tệp đệ quy "<stdin>", dòng 2, trong đệ quy

.
.
.

Tệp "<stdin>", dòng 2, trong đệ quy


RuntimeError: Đã vượt quá độ sâu đệ quy tối đa lần truy

xuất này lớn hơn một chút so với lần truy xuất chúng ta đã thấy trong chương trước. Khi lỗi xảy
ra, có 1000 khung đệ quy trên ngăn xếp!

Nếu bạn vô tình gặp phải một đệ quy vô hạn, hãy xem lại hàm của bạn để xác nhận rằng có một trường
hợp cơ sở không thực hiện một cuộc gọi đệ quy. Và nếu có một trường hợp cơ sở, hãy kiểm tra xem

bạn có được đảm bảo để đạt được nó hay không.

5.11 Bàn phím nhập


Các chương trình chúng tôi đã viết cho đến nay không chấp nhận đầu vào từ người dùng. Họ chỉ làm
cùng một điều mỗi lần.

Python cung cấp một hàm tích hợp được gọi là đầu vào để dừng chương trình và đợi người dùng nhập
nội dung nào đó. Khi người dùng nhấn Return hoặc Enter, chương trình sẽ tiếp tục và đầu vào trả về
những gì người dùng đã nhập dưới dạng chuỗi. Trong Python 2, hàm tương tự được gọi là raw_input.
>>> text = input ()

Bạn còn chờ gì nữa? >>> văn bản

'Bạn còn chờ gì nữa?'

Trước khi nhận được thông tin đầu vào từ người dùng, bạn nên in lời nhắc cho người dùng biết phải
nhập gì. input có thể nhận một lời nhắc làm đối số: >>> name = input ('Tên bạn ... là gì? \ n')

Tên của bạn là gì?


Arthur, Vua của người Anh!
>>> tên

'Arthur, Vua của người Anh!'

Dãy \ n ở cuối lời nhắc biểu thị một dòng mới, là một ký tự đặc biệt gây ra ngắt dòng. Đó là lý do
tại sao đầu vào của người dùng xuất hiện bên dưới lời nhắc.

Nếu bạn mong đợi người dùng nhập một số nguyên, bạn có thể thử chuyển đổi giá trị trả về thành

int: >>> prompt = 'Tốc độ không tải của một con én không tải là gì? \ N' >>> speed = input
( lời nhắc)
Vận tốc không tải của một con én không tải là bao nhiêu? 42

>>> int (tốc độ)


42
Machine Translated by Google

46 Chương 5. Điều kiện và đệ quy

Nhưng nếu người dùng nhập thứ gì đó không phải là một chuỗi chữ số, bạn sẽ gặp lỗi: >>> speed

= input (prompt)

Vận tốc không tải của một con én không tải là bao nhiêu?

Ý bạn là gì, một con én châu Phi hay châu Âu? >>> int (tốc độ)

ValueError: ký tự không hợp lệ cho int () với cơ số 10

Chúng ta sẽ xem cách xử lý loại lỗi này ở phần sau.

5.12 Gỡ lỗi
Khi xảy ra lỗi cú pháp hoặc lỗi thời gian chạy, thông báo lỗi chứa rất nhiều thông tin, nhưng nó có thể quá

tải. Các phần hữu ích nhất thường là:

• Đó là loại lỗi nào, và

• Nơi xảy ra.

Lỗi cú pháp thường dễ tìm, nhưng có một vài lỗi. Lỗi khoảng trắng có thể phức tạp vì khoảng trắng và tab là vô

hình và chúng ta thường bỏ qua chúng.


>>> x = 5

>>> y = 6
Tệp "<stdin>", dòng 1 y = 6

Lỗi IndentationError: thụt lề không mong muốn Trong

ví dụ này, vấn đề là dòng thứ hai bị thụt vào một khoảng trắng. Nhưng thông báo lỗi trỏ đến y, điều này gây

hiểu nhầm. Nói chung, các thông báo lỗi cho biết nơi phát hiện ra sự cố, nhưng lỗi thực sự có thể ở mã sớm hơn,

đôi khi ở dòng trước đó.

Điều này cũng đúng với lỗi thời gian chạy. Giả sử bạn đang cố gắng tính toán tỷ lệ tín hiệu trên nhiễu bằng

decibel. Công thức là SNRdb = 10 log10 (Psignal / Pnoise). Trong Python, bạn có thể viết như sau: import math
signal_power = 9 noise_power = 10 ratio = signal_power // noise_power decibels = 10 * math.log10 (ratio) print

(decibel)

Khi bạn chạy chương trình này, bạn nhận được một ngoại lệ:

Traceback (lần gọi gần đây nhất): Tệp "snr.py", dòng 5, trong?

decibel = 10 * math.log10 (tỷ lệ)

ValueError: lỗi miền toán học

Thông báo lỗi cho biết dòng 5, nhưng không có gì sai với dòng đó. Để tìm ra lỗi thực sự, có thể hữu ích khi in

ra giá trị của tỷ lệ, hóa ra là 0. Vấn đề nằm ở dòng 4, sử dụng phép chia tầng thay vì phép chia dấu phẩy động.

Bạn nên dành thời gian để đọc kỹ các thông báo lỗi, nhưng đừng cho rằng mọi điều họ nói đều đúng.
Machine Translated by Google

5.13. Bảng chú giải 47

5.13 Bảng chú giải thuật ngữ

chia tầng: Một toán tử, được ký hiệu //, chia hai số và làm tròn xuống (đến phường âm vô cùng) thành
một số nguyên.

toán tử mô đun: Một toán tử, được ký hiệu bằng dấu phần trăm (%), hoạt động trên số nguyên và trả về
phần dư khi một số chia cho một số khác.

biểu thức boolean: Một biểu thức có giá trị là Đúng hoặc Sai.

toán tử quan hệ: Một trong các toán tử so sánh các toán hạng của nó: ==,! =,>, <,> =, và
<=.

toán tử logic: Một trong những toán tử kết hợp các biểu thức boolean: và, hoặc, và
không phải.

câu lệnh điều kiện: Một câu lệnh điều khiển luồng thực thi tùy thuộc vào một số
tình trạng.

điều kiện: Biểu thức boolean trong một câu lệnh điều kiện xác định
nhánh chạy.

câu lệnh ghép: Một câu lệnh bao gồm phần đầu và phần thân. Tiêu đề kết thúc
bằng dấu hai chấm (:). Nội dung được thụt vào so với tiêu đề.

nhánh: Một trong các chuỗi câu lệnh thay thế trong câu lệnh điều kiện.

chained điều kiện: Một câu lệnh điều kiện với một loạt các nhánh thay thế.

điều kiện lồng nhau: Một câu lệnh điều kiện xuất hiện trong một trong các nhánh của một câu lệnh điều
kiện khác.

câu lệnh return: Một câu lệnh làm cho một hàm kết thúc ngay lập tức và trả về
người gọi.

recursion: Quá trình gọi hàm hiện đang thực thi.

trường hợp cơ sở: Một nhánh có điều kiện trong một hàm đệ quy không thực hiện cuộc gọi đệ quy.

đệ quy vô hạn: Đệ quy không có trường hợp cơ sở hoặc không bao giờ đạt tới nó. Eventu

đồng minh, một đệ quy vô hạn gây ra lỗi thời gian chạy.

5.14 Bài tập

Bài tập 5.1. Mô-đun thời gian cung cấp một hàm, cũng được đặt tên là thời gian, trả về Giờ trung bình
Greenwich hiện tại trong “kỷ nguyên”, là thời gian tùy ý được sử dụng làm điểm tham chiếu. Trên hệ
thống UNIX, kỷ nguyên là ngày 1 tháng 1 năm 1970.

>>> thời gian nhập


>>> time.time ()
1437746094.5735958

Viết tập lệnh đọc thời gian hiện tại và chuyển đổi nó thành thời gian trong ngày theo giờ, phút và
giây, cộng với số ngày kể từ kỷ nguyên.
Machine Translated by Google

48 Chương 5. Điều kiện và đệ quy

Bài tập 5.2. Định lý cuối cùng của Fermat nói rằng không có số nguyên dương a, b và c nào sao cho

n a + b N n = c

với bất kỳ giá trị nào của n lớn hơn 2.

1. Viết một hàm có tên check_fermat nhận bốn tham số — a, b, c và n — và


kiểm tra xem định lý Fermat có đúng hay không. Nếu n lớn hơn 2 và

một
N + b N = c N

chương trình sẽ in, "Thánh hút thuốc, Fermat đã sai!" Nếu không, chương trình sẽ in, "Không, điều đó
không hoạt động."

2. Viết một hàm nhắc người dùng nhập các giá trị cho a, b, c và n, chuyển chúng thành số nguyên và sử
dụng check_fermat để kiểm tra xem chúng có vi phạm định lý Fermat hay không.

Bài tập 5.3. Nếu bạn được cho ba que tính, bạn có thể sắp xếp chúng thành một hình tam giác hoặc không.
Ví dụ, nếu một trong hai que dài 12 inch và hai que còn lại dài một inch, bạn sẽ không thể lấy các que ngắn
gặp nhau ở giữa. Đối với ba độ dài bất kỳ, có một bài kiểm tra đơn giản để xem liệu có thể tạo thành một
tam giác hay không:

Nếu bất kỳ độ dài nào trong ba độ dài lớn hơn tổng của hai độ dài kia, thì bạn không thể
tạo thành một tam giác. Nếu không, bạn có thể. (Nếu tổng của hai độ dài bằng độ dài thứ ba,
chúng tạo thành cái được gọi là tam giác "suy biến".)

1. Viết một hàm có tên is_triangle nhận ba số nguyên làm đối số và in ra “Có” hoặc “Không”, tùy thuộc
vào việc bạn có thể hoặc không thể tạo một tam giác từ các que có độ dài cho trước.

2. Viết một hàm nhắc người dùng nhập ba độ dài thanh, chuyển chúng thành số nguyên và sử dụng is_triangle
để kiểm tra xem các thanh có độ dài đã cho có thể tạo thành một tam giác hay không.

Bài tập 5.4. Kết quả của chương trình sau là gì? Vẽ một biểu đồ ngăn xếp thể hiện trạng thái của chương
trình khi nó in ra kết quả.

def recurse (n, s):


nếu n == 0:

(các) bản in
khác:

đệ quy (n-1, n + s)

đệ quy (3, 0)

1. Điều gì sẽ xảy ra nếu bạn gọi hàm này như sau: recurse (-1, 0)?

2. Viết một docstring giải thích mọi thứ mà ai đó cần biết để sử dụng chức năng này (và không có gì khác).

Các bài tập sau sử dụng môđun con rùa, được mô tả trong Chương 4: Bài tập 5.5. Đọc
hàm sau và xem liệu bạn có thể tìm ra chức năng của nó hay không (xem bài kiểm tra ở Chương 4). Sau đó,
chạy nó và xem bạn có làm đúng không.
Machine Translated by Google

5,14. Bài tập 49

Hình 5.2: Một đường cong Koch.

def draw (t, length, n): if n


== 0:
trở về

angle = 50
t.fd (length * n)
t.lt (angle) draw
(t, length, n-1) t.rt (2 *
angle) draw (t, length,
n-1) t.lt (angle ) t.bk
(chiều dài * n)

Bài tập 5.6. Đường cong Koch là một đường Fractal trông giống như Hình 5.2. Để vẽ một đường cong Koch với
độ dài x, tất cả những gì bạn phải làm là

1. Vẽ một đường cong Koch với độ dài x / 3.

2. Quay trái 60 độ.

3. Vẽ một đường cong Koch với độ dài x / 3.

4. Quay sang phải 120 độ.

5. Vẽ một đường cong Koch với độ dài x / 3.

6. Quay trái 60 độ.

7. Vẽ một đường cong Koch với độ dài x / 3.

Ngoại lệ là nếu x nhỏ hơn 3: trong trường hợp đó, bạn chỉ có thể vẽ một đoạn thẳng với độ dài x.

1. Viết một hàm có tên là koch lấy một con rùa và chiều dài làm tham số và sử dụng
rùa để vẽ một đường cong Koch với độ dài cho trước.

2. Viết một hàm có tên là snowflake vẽ ba đường cong Koch để tạo đường viền của một
bông tuyết.

Lời giải: http: // thinkpython2. com / code / koch. py

3. Đường cong Koch có thể được khái quát theo một số cách. Xem http: // vi. wikipedia. org / wiki /
Koch_ snowflake để biết ví dụ và triển khai yêu thích của bạn.
Machine Translated by Google

50 Chương 5. Điều kiện và đệ quy


Machine Translated by Google

Chương 6

Các chức năng hiệu quả

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 hàm chúng tôi đã viết đều vô hiệu: chúng có tác dụng, như in ra một giá trị hoặc di chuyển một 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 Giá trị trả về

Việc gọi hàm sẽ tạo ra 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 (radian)

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 ngẫu nhiên, chúng không có giá trị trả
lại; chính xác hơn, giá trị trả về của chúng là Không có.

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à area, trả về diện tích
của một hình tròn với 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. Câu lệnh này có nghĩa là: “Trả về ngay lập tức 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 tôi có thể viết hàm này ngắn gọn hơn:

def area (radius): trả

về 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, rất hữu ích khi có nhiều câu lệnh trả về, một câu lệnh trong mỗi nhánh của điều kiện:
Machine Translated by Google

52 Chương 6. Các chức năng hữu hiệu

def tuyệt đối_value (x): nếu


x <0:
return -x
khác:
trả lại 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 câu lệnh chạy.

Ngay sau khi một câu lệnh trả về chạy, hàm sẽ kết thúc mà không thực hiện bất kỳ câu lệnh quent phụ
nào. 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 đến, được gọi là mã chết.

Trong một hàm hiệu quả, bạn nên đảm bảo rằng mọi đường dẫn có thể có thông qua gam pro đều gặp một câu
lệnh trả về. Ví dụ: def precision_value (x):

nếu x <0:
return -x
nếu x> 0:
trả lại x

Hàm này không chính xác vì nếu x bằng 0, không điều kiện nào là đúng và hàm kết thúc mà không cần 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 (giá trị tuyệt đối (0))

Không có

Nhân tiện, Python cung cấp một hàm tích hợp được gọi là abs tính giá trị tuyệt đối.

Như một bài tập, hãy viết mộ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 tốn nhiều thời gian hơn để gỡ lỗi.

Để đối phó với 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 được gọi là phát
triển sáng tạo. Mục tiêu của phát triển gia tăng là để tránh các phiên gỡ lỗi dài bằng cách chỉ thêm và
thử nghiệm 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à:

2 + (y2 - y1)2
khoảng cách = q (x2 - x1)

Bước đầu tiên là xem xét một hàm khoảng cách sẽ trông như thế nào trong Python. Nói cách khác, đâu là
đầu vào (tham số) và đâu là đầu ra (giá trị trả về)?

Trong trường hợp này, đầu vào là hai điể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 phác thảo của hàm: def distance

(x1, y1, x2, y2): return 0.0


Machine Translated by Google

6.2. Phát triển gia tăng 53

Rõ ràng, phiên bản này không tính khoảng cách; nó luôn trả về số không. Nhưng nó chính xác về
mặt chiến thuật, và nó chạy, có 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 hàm 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 để khoảng cách ngang là 3 và khoảng cách dọc là 4; theo cách đó, kết
quả là 5, cạnh huyền của tam giác vuông 3-4-5. Khi kiểm tra một chức năng, sẽ rất hữu ích nếu
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 hàm là đúng về mặt cú pháp và chúng tôi có
thể bắt đầu thêm mã vào phần nội dung. 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 ra.
khoảng cách def (x1, y1, x2, y2): dx = x2 - x1

dy = y2 - y1
print ('dx is', dx)
print ('dy is', dy)
return 0.0

Nếu hàm đang hoạt động, nó sẽ hiển thị dx là 3 và dy là 4. Nếu vậy, chúng ta biết rằng hàm đang
nhận các đối số phù hợp 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 tôi tính tổng bình phương của dx và


dy: def khoảng cách (x1, y1, x2, y2): dx = x2 - x1

dy = y2 - y1
dsquared = dx ** 2 + dy ** 2
print ('dsquared is:', dsquared) return
0.0

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 math.sqrt để tính toán và trả về kết quả: def distance (x1, y1, x2,
y2): dx = x2 - x1

dy = y2 - y1
dsquared = dx ** 2 + dy ** 2
result = math.sqrt (dsquared) trả
về kết quả

Nếu điều đó hoạt động chính xác, bạn đã hoàn tất. Nếu không, bạn có thể muốn in giá trị của kết
quả trước câu lệnh trả về.

Phiên bản cuối cùng của hàm không hiển thị bất kỳ thứ gì khi nó chạy; nó chỉ trả về một giá
trị. Các câu lệnh in mà chúng tôi đã viết rất hữu ích để gỡ lỗi, nhưng một khi bạn làm cho hàm
hoạt động, bạn nên xóa chúng. Mã như thế được gọi là giàn giáo vì nó giúp í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 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ó thêm kinh nghiệm, bạn có
thể thấy mình 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 chính của quá trình là:


Machine Translated by Google

54 Chương 6. Các chức năng hữu hiệu

1. Bắt đầu với một chương trình làm việc và thực hiện các thay đổi nhỏ từng bước. Tại bất kỳ thời điểm nào, nếu

có một lỗi, bạn nên có một ý tưởng tốt cho nó ở đâu.

2. 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.

3. Khi chương trình đang hoạt động, bạn có thể muốn loại bỏ một số cấu trúc hoặc hợp nhất nhiều câu lệnh
thành biểu thức ghép, nhưng chỉ khi nó không làm cho chương trình khó đọc.

Như một bài tập, sử dụng khai triển tăng dần để viết một hàm được 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 chân còn lại làm đối số. Ghi lại từng giai đoạn của quá trình phát
triển khi bạn đi.

6.3 Thành phần


Như bạn mong đợi bây giờ, bạn có thể gọi một hàm từ bên trong một hàm khác. Như một đề thi, 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, và tính diện tích của 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 nằm trong xp và yp. Bước
đầu tiên là tìm bán kính của hình 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 đó: bán kính = khoảng cách (xc, yc, xp, yp)

Bước tiếp theo là tìm diện tích hình tròn có bán kính đó; chúng tôi cũng chỉ viết rằng:

kết quả = diện tích (bán kính)

Đóng gói các bước này trong một hàm, chúng ta nhận được:

def circle_area (xc, yc, xp, yp):


bán kính = khoảng cách (xc, yc, xp, yp) kết
quả = diện tích (bán kính) trả về kết quả

Bán kính và kết quả của các biến tạm thời rất hữu ích cho việc phát triển và gỡ lỗi, nhưng khi chương trình
đang 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 circle_area (xc, yc, xp, yp): vùng trả


về (khoảng cách (xc, yc, xp, yp))

6.4 Hàm boolean

Các hàm có thể trả về boolean, điều này thường thuận tiện cho việc ẩn các bài kiểm tra phức tạp trong các
hàm phụ. Ví dụ: def is_divible (x, y): if x% y == 0: return True

khác:
trả về Sai
Machine Translated by Google

6.5. Đệ quy nhiều hơn 55

Người ta thường đặt tên các hàm boolean giống như câu hỏi có / không; is_divible trả về True hoặc
False để cho biết liệu x có chia hết cho y hay không.

Đây là một ví dụ: >>>


is_divible (6, 4)
Sai

>>> is_divible (6, 3)


ĐÚNG VẬY

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 is_divible (x, y): return x% y == 0

Các hàm boolean thường được sử dụng trong các câu lệnh điều kiện:

nếu chia hết (x, y): print


('x chia hết cho y')

Có thể bạn sẽ bị hấp dẫn khi viết một cái gì đó

như: if is_divible (x, y) == True:


print ('x chia hết cho y')

Nhưng so sánh thêm là không cần thiết.

Như một bài tập, hãy viết một hàm is_between (x, y, z) 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 con nhỏ của Python, nhưng bạn có thể muốn biết rằng tập con này
là một ngôn ngữ lập trình hoàn chỉnh, có nghĩa là bất kỳ thứ gì có thể tính toán đề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 ra, 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 chỉ có vậy).

Chứng minh khẳng định đó là một bài tập không hề nhỏ 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 biết đến
với cái tên Turing Thesis. Để thảo luận đầy đủ hơn (và chính xác hơn) về Luận đề Turing, tôi giới
thiệu cuốn sách Giới thiệu về Lý thuyết Tính toán của Michael Sipser.

Để 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 vài hàm toán học được định nghĩa đệ 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 thứ đang được định nghĩa. Đị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à vorpal.

Nếu bạn nhì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 ký hiệu bằng ký hiệu !, bạn có thể nhận được một cái gì đó như
sau:

0! = 1

N! = n (n - 1)!
Machine Translated by Google

56 Chương 6. Các chức năng hữu hiệu

Đị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, được nhân với
giai thừa của n - 1.

Vì vậy, 3! là 3 nhân với 2 !, là 2 nhân với 1 !, là 1 nhân với 0 !. Tổng hợp tất cả lại với nhau, 3! bằng
3 lần 2 lần 1 lần 1, là 6.

Nếu bạn có thể viết một định nghĩa đệ quy về 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ố nên là gì. Trong trường hợp này, cần rõ ràng rằng giai thừa
nhận một số nguyên:

def factorial (n):

Nếu đối số là 0, tất cả những gì chúng ta phải làm là trả về 1:

def factorial (n):


nếu n == 0:
trả lại 1

Nếu không, và đây là phần thú vị, chúng ta phải thực hiện một cuộc gọi đệ quy để tìm giai thừa của n - 1
và sau đó nhân nó với n:

def factorial (n): nếu


n == 0:
trả lại 1
khác:

đệ quy = giai thừa (n-1)


result = n * đệ quy

trả về kết quả

Luồng thực thi 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ông phải 0 nên chúng ta lấy nhánh thứ hai và tính giai thừa của n-1 ...

Vì 2 không phải 0 nên chúng ta lấy nhánh thứ hai và tính giai thừa của n-1 ...

Vì 1 không phải 0 nên chúng ta lấy nhánh thứ hai và tính giai thừa của n-1 ...

Vì 0 bằng 0, chúng ta lấy nhánh đầu tiên và trả về 1 mà không cần thực
hiện thêm bất kỳ cuộc gọi đệ quy nào.

Giá 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ả, 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 thấy sơ đồ ngăn xếp trông như thế nào đối với chuỗi lệnh gọi hàm này.

Các giá trị trả về được hiển thị đang được chuyển trở lại 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à đệ quy.

Trong khung cuối cùng, các biến cục bộ đệ quy và kết quả không tồn tại, vì nhánh tạo ra chúng không chạy.
Machine Translated by Google

6.6. Bước nhảy vọt của niềm tin


57

__chính__

6
yếu tố N 3 đệ quy 2 kết quả 6

2
yếu tố N 2 tái diễn 1 kết quả 2

yếu tố N 1 tái diễn 1 kết quả 1

yếu tố N 0

Hình 6.1: Sơ đồ ngăn xếp.

6.6 Bước nhảy vọt của niềm tin

Theo dõi luồ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á tải.
Một sự thay thế mà tôi gọi là “bước nhảy vọt của niềm tin”. Khi bạn thực hiện một lệnh gọi hàm, thay
vì tuân theo quy trình thực thi, bạn giả sử rằng hàm hoạt động chính xác và trả về kết quả phù hợp.

Trên thực tế, bạn đã thực hành bước nhảy vọt của niềm tin này khi bạn sử dụng các chức năng tích hợp
sẵn. Khi bạn gọi math.cos hoặc math.exp, bạn không kiểm tra phần thân của các hàm đó. Bạn cứ cho rằng
chúng hoạt động vì những người viết các hàm tích hợp 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 hàm của riêng mình. Ví dụ, trong Phần 6.4, chúng ta đã
viết một hàm có tên là chia hết để xác định xem một số có chia hết cho một số khác hay không. Khi
chúng tôi đã tự thuyết phục mình rằng chức năng này là đúng - 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 nội dung.

Điều này cũng đúng với các chương trình đệ quy. Khi bạn thực hiện lệnh gọi đệ quy, thay vì tuân theo
quy trình thực thi, bạn nên giả định rằng lệnh gọi đệ quy hoạt động (trả về kết quả chính xác) và sau
đó tự hỏi bản thân, “Giả sử rằng tôi có thể tìm ra giai thừa của n - 1, tôi có thể không? tính giai
thừa của n? ” Rõ ràng là bạn có thể nhân với n.

Tất nhiên, hơi lạ khi cho rằng hàm 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ụ khác


Sau giai thừa, ví dụ phổ biến nhất của một hàm toán học được xác định đệ quy là fibonacci, có định
nghĩa sau (xem http://en.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 giống như sau:


Machine Translated by Google

58 Chương 6. Các chức năng hữu hiệu

def fibonacci (n): nếu


n == 0:
trả về 0
elif n == 1:
trả lại 1
khác:

trả về fibonacci (n-1) + fibonacci (n-2)

Nếu bạn cố gắng làm theo dòng thực thi ở đâ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 giả sử rằng hai lệnh gọi đệ quy hoạt động chính xác,
thì rõ ràng là bạn nhận được kết quả phù hợp 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ó là một đối số 1,5? >>> giai

thừa (1.5)

RuntimeError: Đã vượt quá độ sâu đệ quy tối đa

Nó trông giống như một đệ quy vô hạn. Làm thế nào mà có thể được? Hàm có trường hợp cơ sở — khi n == 0.

Nhưng nếu n không phải là số nguyên, chúng ta có thể bỏ sót trường hợp cơ sở 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, nó là -0,5. Từ đó, nó trở nên nhỏ hơn (âm
hơn), nhưng nó sẽ không bao giờ là 0.

Chúng tôi có hai sự lựa chọn. Chúng ta có thể cố gắng tổng quát hóa hàm giai thừa để làm việc với các số
dấu phẩy động, hoặc chúng ta có thể 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 thứ hai.

Chúng ta có thể sử dụng hàm isinstance tích hợp sẵn để xác minh loại đối số. Khi đang ở đó, chúng ta cũng
có thể đảm bảo đối số là tích cực: def factorial (n): if not isinstance (n, int):

print ('Giai thừa chỉ được xác định cho các số nguyên.')
trả lại Không có

elif n <0:

print ('Giai thừa không được xác định cho số nguyên âm.') trả về Không có

elif n == 0:
trả lại 1
khác:

trả về n * giai thừa (n-1)

Trường hợp cơ sở đầu tiên xử lý nonintegers; thứ hai xử lý số nguyên âm. Trong cả hai trường hợp, chương
trình sẽ in ra thông báo lỗi và trả về Không có để chỉ ra rằng đã xảy ra sự cố: >>> print (Giai thừa
('fred'))

Giai thừa chỉ được xác định cho số nguyên.


Không có

>>> print (giai thừa (-2))

Giai thừa không được xác định cho các số nguyên âm.
Không có
Machine Translated by Google

6,9. Gỡ lỗi 59

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 khuôn mẫu đôi khi được gọi là người giám hộ. Hai tionals đầu tiên
hoạt động như những 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êu
ra một ngoại lệ.

6.9 Gỡ lỗi
Việc chia nhỏ 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 cần xem xét:

• Có điều gì đó sai với các đối số mà hàm đang nhận được; một điều kiện tiên quyết
bị vi phạm.

• Có điều gì đó không ổn với chức năng; một điều kiện sau bị vi phạm.

• Có điều gì đó sai 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 vào đầ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ố trông đẹp, hãy thêm 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 dễ dàng
kiểm tra kết quả (như trong Phần 6.2).

Nếu hàm dường như đ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 được sử dụng hoàn toàn!).

Việc thêm các câu lệnh in vào đầu và cuối của một hàm có thể giúp làm cho luồng thực thi hiển thị
rõ ràng hơn. Ví dụ, đây là một phiên bản của giai thừa với các câu lệnh in:

def thừa thừa (n): *


' '
(4 *cách,
space = print (dấu n)
'giai thừa', n) nếu n == 0:

print (khoảng trắng, 'trả về 1')


trả về 1
khác:
đệ quy = giai thừa (n-1)
result = n * đệ quy

print (khoảng trắng, 'return', kết quả)


trả về kết quả

khoảng trắng là một chuỗi ký tự khoảng trắng điều khiển việc thụt đầu dòng của đầu ra. Đây là kết
quả của giai thừa (4):
Machine Translated by Google

60 Chương 6. Các chức năng hữu hiệu

giai thừa 4

giai thừa 3
giai thừa 2
giai thừa 1

giai thừa 0

trả lại 1 trả


về 1
trả lại 2
trả lại 6
trở lại 24

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. Cần một thời gian để phát triển dàn
giáo hiệu quả, nhưng một chút dàn giáo có thể tiết kiệm rất nhiều việc gỡ lỗi.

6.10 Bảng chú giải thuật ngữ

biến tạm thời: Một biến được sử dụng để lưu trữ giá trị trung gian trong phép tính phức tạp
sự.

mã chết: Một phần của chương trình không bao giờ có thể chạy, thường là vì nó xuất hiện sau khi trả về
bản tường trình.

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ởi
chỉ thêm và thử nghiệm một lượng nhỏ mã tại một thời điểm.

giàn giáo: 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 phần cuối cùng
phiên bản.

Guardian: Một mẫu lập trình sử dụng câu lệnh điều kiện để kiểm tra và han
các trường hợp dle có thể gây ra lỗi.

6.11 Bài tập

Bài tập 6.1. Vẽ sơ đồ ngăn xếp cho chương trình sau. Chương trình in gì?

def b (z):

prod = a (z, z)
print (z, prod)
return prod

định nghĩa a (x,


y): x = x + 1

trả về x * y

def c (x, y, z):


total = x + y + z square
= b (total) ** 2 return
square
Machine Translated by Google

6.11. Bài tập 61

x = 1

y = x + 1
in (c (x, y + 3, x + y))

Bài tập 6.2. Hàm Ackermann, A (m, n), được định nghĩa:

n + 1 nếu m =

A (m, n) = A (m - 1, 1) 0 nếu m > 0 và n = 0

A (m - 1, A (m, n - 1)) nếu m > 0 và n > 0.

Xem http: // vi. wikipedia. org / wiki / Ackermann_ function. Viết một hàm có tên là ack để đánh giá
hàm Ackermann. Sử dụng hàm của bạn để đánh giá ack (3, 4), phải là 125. Điều gì xảy ra với các giá trị
lớn hơn của m và n? Lời giải: http: // thinkpython2. com / code / ackermann. py

Bài tập 6.3. Palindrome là một từ được đánh vần ngược và xuôi giống nhau, như “buổi trưa” và “redivider”.
Một cách đệ quy, một từ là một palindrome nếu các chữ cái đầu tiên và cuối cùng giống nhau và ở giữa là
một chữ palindrome.

Sau đây là các hàm nhận đối số chuỗi và trả về các chữ cái đầu tiên, cuối cùng và chữ giữa: def first

(word): return word [0]

def last (word):


trả về từ [-1]

def middle (word): trả


về từ [1: -1]

Chúng ta sẽ xem cách chúng hoạt động trong Chương 8.

1. Nhập các hàm này vào một tệp có tên 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? Một lá thư? Còn về chuỗi trống, được viết '' và không chứa
các chữ cái thì sao?

2. Viết một hàm gọi là is_palindrome nhận đối số là chuỗi và trả về giá trị True nếu nó là palindrome
và nếu không thì là False. Hãy nhớ rằng bạn có thể sử dụng hàm len có sẵn để kiểm tra độ dài của
một chuỗi.

Lời giải: http: // thinkpython2. com / code / palindrome_ soln. py

Bài tập 6.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
is_power nhận tham số a và b và trả về giá trị True nếu a là lũy thừa của b. Lưu ý: bạn sẽ phải suy nghĩ
về trường hợp cơ sở.
Bài tập 6.5. Ước chung lớn nhất (GCD) của a và b là số lớn nhất mà chia cả hai đều không có dư.

Một cách để tìm GCD của hai số dựa trên quan sát rằng nếu r là phần dư khi a chia cho b thì gcd (a, b)
= gcd (b, r). Là một trường hợp cơ sở, chúng ta có thể sử dụng gcd (a, 0) = a.

Viết một hàm có tên là gcd nhận các tham số a và b và trả về ước số chung lớn nhất của chúng.

Tín chỉ: 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.
Machine Translated by Google

62 Chương 6. Các chức năng hữu hiệu


Machine Translated by Google

Chương 7

Sự lặp lại

Chương này nói về tính lặp, là khả năng chạy lặp lại một khối câu lệnh.

Chúng ta đã thấy một kiểu lặp, sử dụng đệ quy, trong Phần 5.8. Chúng tôi đã thấy một kiểu khác, sử dụng vòng

lặp for, trong Phần 4.2. Trong chương này, chúng ta sẽ thấy một loại khác, sử dụng câu lệnh while.

Nhưng trước tiên tôi muốn nói thêm một chút về phép gán biến.

7.1 Chuyển nhượng lại

Như bạn có thể đã phát hiện ra, việc gán nhiều hơn một biến cho cùng một biến là hợp pháp. Phép gán mới làm cho

một biến hiện có tham chiếu đến một giá trị mới (và ngừng tham chiếu đến giá trị cũ).

>>> x = 5

>>> x

5 >>> x = 7

>>> x

Lần đầu tiên chúng ta hiển thị x, giá trị của nó là 5; lần thứ hai, giá trị của nó là 7.

Hình 7.1 cho thấy sự phân công lại trông như thế nào trong một biểu đồ trạng thái.

Tại thời điểm này, tôi muốn giải quyết một nguồn nhầm lẫn phổ biến. Bởi vì Python sử dụng dấu bằng (=) để gán,

nên thật hấp dẫn để giải thích một câu lệnh như a = b như một mệnh đề toán học về đẳng thức; nghĩa là, khẳng

định rằng a và b bằng nhau. Nhưng cách hiểu này là sai.

Thứ nhất, đẳng thức là một quan hệ đối xứng và phép gán thì không. Ví dụ, trong ematics toán học, nếu a = 7

thì 7 = a. Nhưng trong Python, câu lệnh a = 7 là hợp pháp và 7 = a là


không phải.

Ngoài ra, trong toán học, một mệnh đề bình đẳng là đúng hoặc sai cho mọi thời đại. Nếu bây giờ a = b thì a luôn

bằng b. Trong Python, một câu lệnh gán có thể làm cho hai biến bằng nhau, nhưng chúng không nhất thiết phải giữ

nguyên như vậy:


Machine Translated by Google

64 Chương 7. Lặp lại

5
x
7

Hình 7.1: Biểu đồ trạng thái.

>>> a = 5
>>> b = a # a và b bây giờ bằng nhau #
>>> a = 3 a và b không còn bằng nhau nữa
>>> b
5

Dòng thứ ba thay đổi giá trị của a nhưng không thay đổi giá trị của b, do đó chúng không còn bằng
nhau.

Việc gán lại các biến thường hữu ích, nhưng bạn nên sử dụng nó một cách thận trọng. Nếu giá trị của
các biến thay đổi thường xuyên, nó có thể làm cho mã khó đọc và gỡ lỗi.

7.2 Cập nhật các biến

Một kiểu gán lại phổ biến là cập nhật, trong đó giá trị mới của biến phụ thuộc vào giá trị cũ.

>>> x = x + 1

Điều này có nghĩa là “lấy giá trị hiện tại của x, thêm một giá trị và sau đó cập nhật x với giá trị mới”.

Nếu bạn cố gắng cập nhật một biến không tồn tại, bạn sẽ gặp lỗi vì Python đánh giá phía bên phải
trước khi nó gán giá trị cho x:

>>> x = x + 1
NameError: tên 'x' không được xác định

Trước khi có thể cập nhật một biến, bạn phải khởi tạo biến đó, thường chỉ với một phép gán đơn giản:

>>> x = 0
>>> x = x + 1

Cập nhật một biến bằng cách thêm 1 được gọi là số tăng; trừ đi 1 được gọi là số giảm.

7.3 Câu lệnh while

Máy tính thường được sử dụng để tự động hóa các công việc lặp đi lặp lại. Lặp lại các tác vụ giống
hệt nhau hoặc tương tự mà không mắc lỗi là điều mà máy tính làm tốt và con người làm kém. Trong
chương trình máy tính, sự lặp lại còn được gọi là sự lặp lại.

Chúng ta đã thấy hai hàm, đếm ngược và print_n, lặp lại bằng cách sử dụng đệ quy.
Vì sự lặp lại rất phổ biến, nên Python cung cấp các tính năng ngôn ngữ để làm cho nó dễ dàng hơn. Một là
câu lệnh for mà chúng ta đã thấy trong Phần 4.2. Chúng ta sẽ quay lại vấn đề đó sau.

Khác là câu lệnh while. Đây là phiên bản đếm ngược sử dụng câu lệnh while:
Machine Translated by Google

7.3. Câu lệnh while 65

def countdown (n):


while n> 0:

print (n)
n = n - 1

print ('Blastoff!')

Bạn gần như có thể đọc câu lệnh while như thể nó là tiếng Anh. Nó có nghĩa là, “Trong khi n lớn
hơn 0, hiển thị giá trị của n và sau đó giảm dần n. Khi bạn về 0, hãy hiển thị từ Blastoff! ”

Chính thức hơn, đây là luồng thực thi câu lệnh while:

1. Xác định xem điều kiện là đúng hay sai.

2. Nếu sai, thoát khỏi câu lệnh while và tiếp tục thực hiện ở câu lệnh tiếp theo.

3. Nếu điều kiện là đúng, hãy chạy phần thân và sau đó quay lại bước 1.

Loại luồng này được gọi là vòng lặp bởi vì bước thứ ba lặp lại từ đầu đến cuối.

Phần thân của vòng lặp nên thay đổi giá trị của một hoặc nhiều biến để cuối cùng điều kiện trở
thành sai và vòng lặp kết thúc. Nếu không thì vòng lặp sẽ lặp lại mãi mãi, được gọi là vòng lặp
vô hạn. Một nguồn thú vị vô tận cho các nhà khoa học máy tính là nhận xét rằng các chỉ dẫn trên
dầu gội, “Tạo bọt, xả, lặp lại”, là một vòng lặp vô hạn.

Trong trường hợp đếm ngược, chúng ta có thể chứng minh rằng vòng lặp kết thúc: nếu n bằng 0 hoặc
âm, vòng lặp không bao giờ chạy. Nếu không, n nhỏ hơn mỗi lần qua vòng lặp, vì vậy cuối cùng chúng
ta phải về 0.

Đối với một số vòng lặp khác, nó không phải là dễ dàng để nói.

Ví dụ: def dãy (n): while n! = 1:

print (n)
nếu n% 2 == 0: n # n là số chẵn

= n / 2
khác: # n là số lẻ

n = n * 3 + 1

Điều kiện cho vòng lặp này là n! = 1, vì vậy vòng lặp sẽ tiếp tục cho đến khi n bằng 1, điều này
làm cho điều kiện sai.

Mỗi lần qua vòng lặp, chương trình sẽ xuất ra giá trị của n và sau đó kiểm tra xem nó là chẵn hay
lẻ. Nếu là chẵn, n chia hết cho 2. Nếu là lẻ, giá trị của n được thay bằng n * 3 + 1. Ví dụ, nếu
đối số được truyền cho dãy là 3, giá trị kết quả của n là 3, 10 , 5, 16, 8, 4, 2, 1.

Vì n đôi khi tăng và đôi khi giảm, không có bằng chứng rõ ràng rằng n sẽ bao giờ đạt đến 1, hoặc
rằng chương trình kết thúc. Đối với một số giá trị cụ thể của n, chúng ta có thể chứng minh sự kết
thúc. Ví dụ: nếu giá trị bắt đầu là lũy thừa của hai, n sẽ là số chẵn mỗi khi qua vòng lặp cho đến
khi nó đạt đến 1. Ví dụ trước kết thúc bằng một chuỗi như vậy, bắt đầu bằng 16.

Câu hỏi khó là liệu chúng ta có thể chứng minh rằng chương trình này kết thúc với tất cả các giá
trị posi tive của n hay không. Cho đến nay, vẫn chưa ai có thể chứng minh hay phản bác điều đó!
(Xem http: //en.wikipedia.org/wiki/Collatz_conjecture.)
Machine Translated by Google

66 Chương 7. Lặp lại

Như một bài tập, hãy viết lại hàm print_n từ Phần 5.8 bằng cách sử dụng phép lặp thay vì đệ quy.

7.4 nghỉ

Đôi khi bạn không biết đã đến lúc kết thúc một vòng lặp cho đến khi bạn đi được một nửa phần thân.

Trong trường hợp đó, bạn có thể sử dụng câu lệnh break để nhảy ra khỏi vòng lặp.

Ví dụ: giả sử bạn muốn lấy thông tin đầu vào từ người dùng cho đến khi họ nhập xong. Bạn có thể viết:

trong khi Đúng:

line = input ('>') if


line == 'done':

ngắt

in (dòng)

print ('Xong!')

Điều kiện của vòng lặp là True, luôn đúng, do đó, vòng lặp sẽ chạy cho đến khi nó chạm ngắt
bản tường trình.

Mỗi lần thông qua, nó sẽ nhắc người dùng bằng một dấu ngoặc nhọn. Nếu người dùng nhập xong, câu lệnh break

sẽ thoát khỏi vòng lặp. Nếu không, chương trình sẽ lặp lại bất kỳ thứ gì người dùng nhập và quay trở lại

đầu vòng lặp. Đây là một cuộc chạy mẫu:


> không xong

không được thực hiện

> xong

Xong!

Cách viết vòng lặp while này rất phổ biến vì bạn có thể kiểm tra điều kiện ở bất kỳ đâu trong vòng lặp

(không chỉ ở trên cùng) và bạn có thể diễn đạt điều kiện dừng một cách khẳng định (“dừng khi điều này xảy

ra”) thay vì phủ định (“tiếp tục cho đến khi điều đó xảy ra xảy ra").

7,5 Căn bậc hai


Vòng lặp thường được sử dụng trong các chương trình tính toán kết quả số bằng cách bắt đầu với một câu trả

lời gần đúng ap và cải thiện nó theo cách lặp đi lặp lại.

Ví dụ, một cách tính căn bậc hai là phương pháp của Newton. Giả sử rằng bạn muốn biết căn bậc hai của a. Nếu

bạn bắt đầu với hầu hết mọi ước tính, x, bạn có thể tính toán một ước tính tốt hơn với công thức sau:

x + a /
xy =
2

Ví dụ, nếu a là 4 và x là 3:
>>> a = 4

>>> x = 3

>>> y = (x + a / x) / 2 >>> y

2.16666666667
Machine Translated by Google

7.6. Các thuật toán 67

Kết quả gần với câu trả lời đúng hơn (√ 4 = 2). Nếu chúng tôi lặp lại quy trình với ước tính mới, nó thậm
chí còn gần hơn:

>>> x = y
>>> y = (x + a / x) / 2 >>>
y 2.00641025641

Sau một vài cập nhật nữa, ước tính gần như chính xác:

>>> x = y
>>> y = (x + a / x) / 2 >>>
y 2.00001024003

>>> x = y
>>> y = (x + a / x) / 2 >>>
y 2.00000000003

Nói chung, chúng tôi không biết trước cần bao nhiêu bước để đi đến câu trả lời đúng, nhưng chúng tôi biết
khi nào chúng tôi đến đó vì ước lượng ngừng thay đổi: >>> x = y >>> y = (x + a / x) / 2 >>> y 2.0

>>> x = y
>>> y = (x + a / x) / 2 >>>
y 2.0

Khi y == x, chúng ta có thể dừng lại. Đây là một vòng lặp bắt đầu với ước tính ban đầu, x và im chứng minh
điều đó cho đến khi nó ngừng thay đổi:

trong khi Đúng:

print (x)
y = (x + a / x) / 2 if
y == x: break

x = y

Đối với hầu hết các giá trị của một, điều này hoạt động tốt, nhưng nói chung, việc kiểm tra bình đẳng float là rất nguy hiểm.

Giá trị dấu phẩy động chỉ gần đúng: hầu hết các số hữu tỉ, như 1/3 và số vô tỉ, như √ 2, không thể được

biểu diễn chính xác bằng dấu phẩy.

Thay vì kiểm tra xem x và y có chính xác bằng nhau hay không, sẽ an toàn hơn khi sử dụng hàm abs tích hợp
sẵn để tính giá trị tuyệt đối hoặc độ lớn của sự khác biệt giữa chúng:

if abs (yx) <epsilon: break

Trong đó epsilon có một giá trị như 0,0000001 xác định mức độ gần là đủ gần.

7.6 Thuật toán


Phương pháp của Newton là một ví dụ về thuật toán: nó là một quá trình cơ học để giải một loại bài toán
(trong trường hợp này là tính toán căn bậc hai).
Machine Translated by Google

68 Chương 7. Lặp lại

Để hiểu thuật toán là gì, có thể hữu ích khi bắt đầu với một thứ không phải là thuật toán. Khi học
nhân các số có một chữ số, chắc hẳn bạn đã thuộc lòng bảng cửu chương. Thực tế, bạn đã ghi nhớ 100
giải pháp cụ thể. Loại kiến thức đó không phải là thuật toán.

Nhưng nếu bạn “lười biếng”, bạn có thể đã học được một vài thủ thuật. Ví dụ, để tìm tích của n và 9,
bạn có thể viết n - 1 là chữ số đầu tiên và 10 - n là chữ số thứ hai.
Thủ thuật này là một giải pháp chung để nhân bất kỳ số có một chữ số nào với 9. Đó là một thuật toán!

Tương tự, các kỹ thuật bạn đã học để cộng với mang, trừ với mượn và chia dài đều là thuật toán. Một
trong những đặc điểm của thuật toán là chúng không yêu cầu bất kỳ trí thông minh nào để thực hiện.
Chúng là các quá trình cơ học trong đó mỗi bước tiếp theo từ bước cuối cùng theo một bộ quy tắc đơn
giản.

Thực thi các thuật toán thật nhàm chán, nhưng việc thiết kế chúng rất thú vị, thách thức trí tuệ và
là một phần trọng tâm của khoa học máy tính.

Một số điều mà mọi người làm một cách tự nhiên, không gặp khó khăn hoặc suy nghĩ có ý thức, là những
điều khó thể hiện bằng thuật toán nhất. Hiểu ngôn ngữ tự nhiên là một ví dụ điển hình.
Tất cả chúng ta đều làm điều đó, nhưng cho đến nay vẫn chưa ai có thể giải thích cách chúng ta làm điều đó, ít nhất là không phải

dưới dạng một thuật toán.

7.7 Gỡ lỗi
Khi bạn bắt đầu viết các chương trình lớn hơn, bạn có thể thấy mình dành nhiều thời gian hơn để gỡ
lỗi ging. Nhiều mã hơn có nghĩa là nhiều cơ hội tạo ra lỗi hơn và nhiều nơi hơn để lỗi ẩn.

Một cách để giảm thời gian gỡ lỗi của bạn là "gỡ lỗi bằng cách chia nhỏ". Ví dụ: nếu có 100 dòng
trong chương trình của bạn và bạn kiểm tra từng dòng một, thì sẽ mất 100 bước.

Thay vào đó, hãy cố gắng chia nhỏ vấn đề ra làm đôi. Nhìn vào giữa chương trình hoặc gần nó để biết
giá trị trung gian mà bạn có thể kiểm tra. Thêm một câu lệnh in (hoặc một cái gì đó khác có tác dụng
có thể xác minh được) và chạy chương trình.

Nếu kiểm tra giữa điểm không chính xác, chắc chắn sẽ có sự cố trong nửa đầu chương trình.
Nếu đúng, vấn đề nằm ở hiệp hai.

Mỗi khi bạn thực hiện kiểm tra như vậy, bạn giảm một nửa số dòng bạn phải tìm kiếm.
Sau sáu bước (ít hơn 100), bạn sẽ có một hoặc hai dòng mã, ít nhất là trên lý thuyết.

Trong thực tế, không phải lúc nào cũng rõ “phần giữa của chương trình” là gì và không phải lúc nào
vị trí đó cũng có thể kiểm tra được. Không có ý nghĩa gì khi đếm dòng và tìm điểm giữa chính xác.
Thay vào đó, hãy nghĩ về những nơi có thể có lỗi trong chương trình và những nơi có thể dễ dàng kiểm
tra. Sau đó, chọn một vị trí mà bạn cho rằng khả năng có lỗi giống như trước hoặc sau khi kiểm tra.

7.8 Bảng chú giải thuật ngữ

gán lại: Gán một giá trị mới cho một biến đã tồn tại.
Machine Translated by Google

7.9. Bài tập 69

update: Một phép gán trong đó giá trị mới của biến phụ thuộc vào giá trị cũ.

khởi tạo: Một phép gán cung cấp giá trị ban đầu cho một biến sẽ được cập nhật.

tăng: Bản cập nhật làm tăng giá trị của một biến (thường là một).

giảm: Bản cập nhật làm giảm giá trị của một biến.

lặp: Thực hiện lặp lại một tập hợp các câu lệnh bằng cách sử dụng một lệnh gọi hàm đệ quy
hoặc một vòng lặp.

vòng lặp vô hạn: Một vòng lặp trong đó điều kiện kết thúc không bao giờ được thỏa mãn.

thuật toán: Một quy trình chung để giải quyết một loại vấn đề.

7.9 Bài tập

Bài tập 7.1. Sao chép vòng lặp từ Phần 7.5 và đóng gói nó trong một hàm có tên là mysqrt lấy
a làm tham số, chọn giá trị hợp lý của x và trả về ước tính căn bậc hai của
một.

Để kiểm tra nó, hãy viết một hàm có tên test_square_root để in ra một bảng

như sau: a mysqrt (a) math.sqrt (a) diff


- --------- ------------ ----

1,0 1,0 1,0 0,0


2,0 1.41421356237 1.41421356237 2.22044604925e-16
3,0 1,73205080757 1,73205080757 0,0
4.0 2.0 2.0 0,0
5,0 2,2360679775 2,2360679775 0,0
6,0 2.44948974278 2.44948974278 0.0
7,0 2.64575131106 2.64575131106 0.0
8,0 2.82842712475 2.82842712475 4.4408920985e-16
9.0 3.0 3.0 0,0

Cột đầu tiên là một số, a; cột thứ hai là căn bậc hai của một được tính bằng mysqrt; cột thứ
ba là căn bậc hai được tính bởi math.sqrt; cột thứ tư là giá trị tuyệt đối của sự khác biệt
giữa hai ước tính.
Bài tập 7.2. Hàm eval tích hợp lấy một chuỗi và đánh giá nó bằng cách sử dụng hàm tương tự
Python. Ví dụ: >>> eval ('1 + 2 * 3')

>>> nhập toán học


>>> eval ('math.sqrt (5)')
2.2360679774997898

>>> eval ('type (math.pi)')


<class 'float'>

Viết một hàm có tên eval_loop nhắc người dùng lặp đi lặp lại, lấy đầu vào kết quả và đánh
giá nó bằng eval, và in kết quả.

Nó sẽ tiếp tục cho đến khi người dùng nhập 'done', và sau đó trả về giá trị của biểu thức cuối cùng mà nó
đã đánh giá.
Machine Translated by Google

70 Chương 7. Lặp lại

Bài tập 7.3. Nhà toán học Srinivasa Ramanujan đã tìm ra một chuỗi vô hạn có thể được
sử dụng để tạo ra một số xấp xỉ 1 / π:

1 2 √ 2 ∞ (4k)! (1103 + 26390k)


π
= (k!) 43964k
9801
k = 0

Viết một hàm được gọi là ước tính_pi sử dụng công thức này để tính toán và trả về giá trị ước lượng là
π. Nó sẽ sử dụng vòng lặp while để tính toán các điều khoản của tổng cho đến khi số hạng cuối cùng nhỏ
hơn 1e-15 (là ký hiệu Python cho 10 15). Bạn có thể kiểm tra kết quả bằng cách so sánh nó với math.pi.

Lời giải: http: // thinkpython2. com / code / pi. py


Machine Translated by Google

Chương 8

Dây

Các chuỗi không giống như số nguyên, float và boolean. Chuỗi là một chuỗi, có nghĩa là nó là một
tập hợp các giá trị khác có thứ tự. Trong chương này, bạn sẽ thấy cách truy cập các ký tự tạo nên
một chuỗi và bạn sẽ tìm hiểu về một số phương thức mà chuỗi cung cấp.

8.1 Chuỗi là một chuỗi


Một chuỗi là một chuỗi các ký tự. Bạn có thể truy cập từng ký tự một bằng toán tử ngoặc:

>>> fruit = 'banana'


>>> chữ = quả [1]

Câu lệnh thứ hai chọn ký tự số 1 từ trái cây và gán nó thành chữ cái.

Biểu thức trong ngoặc được gọi là chỉ mục. Chỉ mục cho biết ký tự nào trong chuỗi bạn muốn (do đó
có tên).

Nhưng bạn có thể không nhận được những gì bạn mong đợi:

>>> thư
'một'

Đối với hầu hết mọi người, chữ cái đầu tiên của 'banana' là b, không phải a. Nhưng đối với các nhà
khoa học máy tính, chỉ mục là một phần bù từ đầu chuỗi và phần bù của chữ cái đầu tiên là số không.

>>> chữ = quả [0]


>>> thư
'b'

Vì vậy, b là chữ cái thứ 0 (“zero-eth”) của “banana”, a là chữ cái thứ 1 (“one-eth”) và n là chữ
cái thứ 2 (“two-eth”).

Là một chỉ mục, bạn có thể sử dụng một biểu thức có chứa các biến và toán tử:
>>> i = 1
>>> quả [i]
'một'

>>> quả [i + 1]
'n'
Machine Translated by Google

72 Chương 8. Chuỗi

Nhưng giá trị của chỉ mục phải là một số nguyên. Nếu không, bạn sẽ nhận được:

>>> chữ = quả [1,5]

TypeError: chỉ số chuỗi phải là số nguyên

8,2 len

len là một hàm tích hợp trả về số ký tự trong một chuỗi:

>>> fruit = 'banana'

>>> len (hoa quả)


6

Để nhận được ký tự cuối cùng của một chuỗi, bạn có thể muốn thử một cái gì đó như sau: >>>

length = len (fruit) >>> last = fruit [length]

IndexError: chỉ mục chuỗi nằm ngoài phạm vi

Lý do cho IndexError là không có chữ cái nào trong 'banana' có chỉ số 6. Vì chúng tôi bắt đầu đếm ở số 0,

sáu chữ cái được đánh số từ 0 đến 5. Để có ký tự cuối cùng, bạn phải trừ đi 1 từ độ dài: >>> last = fruit
[length-1] >>> last

'một'

Hoặc bạn có thể sử dụng các chỉ số âm, đếm ngược từ cuối chuỗi. Biểu thức fruit [-1] cho ra chữ cái cuối
cùng, fruit [-2] cho ra chữ cái thứ hai đến cuối cùng, v.v.

8.3 Truyền tải với vòng lặp for

Rất nhiều phép tính liên quan đến việc xử lý một chuỗi ký tự tại một thời điểm. Thường thì họ bắt đầu từ
đầu, chọn lần lượt từng nhân vật, làm gì đó với nó và tiếp tục cho đến khi kết thúc. Mô hình xử lý này
được gọi là một đường truyền . Một cách để viết duyệt là với vòng lặp while:

chỉ số = 0

while index <len (fruit): letter


= fruit [index] print

(letter) index = index + 1

Vòng lặp này đi qua chuỗi và tự hiển thị từng chữ cái trên một dòng. Điều kiện của vòng lặp là index <len
(fruit), vì vậy khi chỉ mục bằng độ dài của chuỗi, điều kiện là false và phần thân của vòng lặp không
chạy. Ký tự cuối cùng được truy cập là ký tự có chỉ số len (fruit) -1, là ký tự cuối cùng trong chuỗi.

Như một bài tập, hãy viết một hàm lấy một chuỗi làm đối số và hiển thị các chữ cái lùi lại, mỗi chữ cái
trên một dòng.

Một cách khác để viết duyệt là với vòng lặp for:

cho chữ cái trong trái cây:

in (chữ cái)
Machine Translated by Google

8,4. Chuỗi lát 73

' '
bannaa
trái cây

chỉ số 0 1 2 3 4 5 6

Hình 8.1: Các chỉ số cắt lát.

Mỗi lần qua vòng lặp, ký tự tiếp theo trong chuỗi được gán cho ký tự biến. Vòng lặp tiếp tục cho
đến khi không còn ký tự nào.

Ví dụ sau đây cho thấy cách sử dụng phép nối (cộng chuỗi) và vòng lặp for để tạo chuỗi abecedarian
(nghĩa là theo thứ tự bảng chữ cái). Trong cuốn sách Make Way for Ducklings của Robert McCloskey,
tên của những chú vịt con là Jack, Kack, Lack, Mack, Nack, Ouack, Pack và Quack. Vòng lặp này xuất
ra các tên này theo thứ tự: prefixes = 'JKLMNOPQ' hậu tố = 'ack'

cho chữ cái trong các tiền tố:


print (chữ cái + hậu tố)

Đầu ra là:

Jack
Kack
Thiếu

Mack
Nack
Oack
Đóng gói

Qack

Tất nhiên, điều đó không đúng lắm vì “Ouack” và “Quack” bị sai chính tả. Như một bài tập, hãy sửa
đổi chương trình để sửa lỗi này.

8.4 Các lát chuỗi

Một đoạn của chuỗi được gọi là một lát cắt. Chọn một lát cắt tương tự như chọn một ký tự: >>> s

= 'Monty Python' >>> s [0: 5]

'Monty'
>>> s [6:12]

'Python'

Toán tử [n: m] trả về một phần của chuỗi từ ký tự “n-eth” thành ký tự “m-eth”, bao gồm ký tự đầu
tiên nhưng không bao gồm ký tự cuối cùng. Hành vi này là phản trực giác, nhưng nó có thể hữu ích
để hình dung các chỉ số trỏ giữa các ký tự, như trong Hình 8.1.

Nếu bạn bỏ qua chỉ mục đầu tiên (trước dấu hai chấm), lát cắt sẽ bắt đầu ở đầu chuỗi.
Nếu bạn bỏ qua chỉ mục thứ hai, lát cắt sẽ đi đến cuối chuỗi:

>>> fruit = 'banana'


>>> quả [: 3]
Machine Translated by Google

74 Chương 8. Chuỗi

'lệnh cấm'

>>> quả [3:]


'ana'

Nếu chỉ mục đầu tiên lớn hơn hoặc bằng chỉ mục thứ hai, kết quả là một chuỗi trống, được gửi đi
bằng hai dấu ngoặc kép:

>>> fruit = 'banana'


>>> quả [3: 3]
''

Một chuỗi rỗng không chứa ký tự nào và có độ dài bằng 0, nhưng ngoài ra, nó giống với bất kỳ
chuỗi nào khác.

Tiếp tục ví dụ này, bạn nghĩ fruit [:] có nghĩa là gì? Hãy thử nó và xem.

8.5 Chuỗi là bất biến


Thật hấp dẫn khi sử dụng toán tử [] ở phía bên trái của một phép gán, với ý định thay đổi một ký
tự trong một chuỗi. Ví dụ:

>>> Lời chào = 'Xin chào, thế giới!'


>>> lời chào [0] = 'J'
TypeError: Đối tượng 'str' không hỗ trợ gán mục

“Đối tượng” trong trường hợp này là chuỗi và “mục” là ký tự bạn đã cố gắng gán.
Hiện tại, một đối tượng giống như một giá trị, nhưng chúng tôi sẽ tinh chỉnh định nghĩa đó sau
(Phần 10.10).

Lý do cho lỗi là các chuỗi là bất biến, có nghĩa là bạn không thể thay đổi một chuỗi hiện có.
Điều tốt nhất bạn có thể làm là tạo một chuỗi mới là một biến thể của chuỗi gốc:

>>> Lời chào = 'Xin chào, thế giới!'


>>> new_greeting = 'J' + lời chào [1:] >>>
new_greeting 'Xin chào, cả thế giới!'

Ví dụ này nối một chữ cái đầu tiên mới vào một phần lời chào. Nó không ảnh hưởng đến chuỗi gốc.

8.6 Tìm kiếm


Chức năng sau đây làm gì?

def find (từ, chữ cái):


index = 0
trong khi index <len (word):
if word [index] == letter:
chỉ mục trả lại
index = chỉ mục + 1
trả về -1
Machine Translated by Google

8.7. Vòng lặp và đếm 75

Theo một nghĩa nào đó, find là nghịch đảo của toán tử []. Thay vì lấy một chỉ mục và trích xuất ký tự
tương ứng, nó sẽ lấy một ký tự và tìm chỉ mục nơi ký tự đó xuất hiện. Nếu ký tự không được tìm thấy, hàm
trả về -1.

Đây là ví dụ đầu tiên chúng ta thấy về một câu lệnh return bên trong một vòng lặp. Nếu từ [index] == ký
tự, hàm thoát ra khỏi vòng lặp và trả về ngay lập tức.

Nếu ký tự không xuất hiện trong chuỗi, chương trình sẽ thoát khỏi vòng lặp bình thường và tái
lượt -1.

Mô hình tính toán này — duyệt qua một chuỗi và quay lại khi chúng tôi tìm thấy những gì chúng tôi
đang tìm kiếm — được gọi là tìm kiếm.

Như một bài tập, hãy sửa đổi tìm kiếm để nó có tham số thứ ba, chỉ mục trong word nơi nó sẽ bắt đầu tìm
kiếm.

8.7 Vòng lặp và đếm

Chương trình sau đếm số lần ký tự a xuất hiện trong một chuỗi:

word = 'banana'
đếm = 0
cho chữ cái trong từ:
if chữ cái == 'a':
count = count + 1

in (đếm)

Chương trình này trình diễn một mẫu tính toán khác được gọi là bộ đếm. Số lượng biến được khởi tạo thành
0 và sau đó tăng lên mỗi khi tìm thấy a. Khi vòng lặp thoát ra, số đếm chứa kết quả - tổng số của a.

Như một bài tập, hãy đóng gói mã này trong một hàm có tên là count, và tổng quát hóa nó để nó chấp nhận
chuỗi và ký tự làm đối số.

Sau đó, viết lại hàm để thay vì duyệt qua chuỗi, nó sử dụng phiên bản tìm ba tham số từ phần trước.

8.8 Phương thức chuỗi

Chuỗi cung cấp các phương thức thực hiện nhiều hoạt động hữu ích. Một phương thức tương tự như một hàm —
nó nhận đối số và trả về một giá trị — nhưng cú pháp thì khác. Ví dụ: phương thức upper nhận một chuỗi
và trả về một chuỗi mới với tất cả các chữ cái viết hoa.

Thay vì cú pháp hàm upper (word), nó sử dụng cú pháp phương thức word.upper ().

>>> word = 'banana'

>>> new_word = word.upper () >>>


new_word 'CHUỐI'
Machine Translated by Google

76 Chương 8. Chuỗi

Dạng ký hiệu dấu chấm này chỉ định tên của phương thức, phần trên và tên của chuỗi để áp dụng phương
thức, từ. Dấu ngoặc đơn trống cho biết rằng phương thức này không có đối số.

Một cuộc gọi phương thức được gọi là một lời gọi; trong trường hợp này, chúng tôi sẽ nói rằng chúng tôi
đang gọi trên từ.

Hóa ra, có một phương thức chuỗi có tên find tương tự như hàm chúng ta đã viết:

>>> word = 'banana'

>>> index = word.find ('a')


>>> chỉ mục
1

Trong ví dụ này, chúng tôi gọi find on word và chuyển ký tự mà chúng tôi đang tìm kiếm làm tham số
eter.

Trên thực tế, phương thức find tổng quát hơn hàm của chúng ta; nó có thể tìm thấy các chuỗi con, không
chỉ các ký tự:

>>> word.find ('na')


2

Theo mặc định, tìm bắt đầu ở đầu chuỗi, nhưng nó có thể lấy đối số thứ hai, chỉ mục nơi nó sẽ bắt đầu:

>>> word.find ('na', 3) 4

Đây là một ví dụ về một đối số tùy chọn; find cũng có thể lấy đối số thứ ba, chỉ mục nơi nó sẽ dừng
lại:
>>> name = 'bob'

>>> name.find ('b', 1, 2) -1

Tìm kiếm này không thành công vì b không xuất hiện trong phạm vi chỉ mục từ 1 đến 2, không bao gồm 2.
Tìm kiếm tới, nhưng không bao gồm, chỉ mục thứ hai làm cho tìm kiếm nhất quán với toán tử lát cắt.

8.9 Nhà điều hành


Từ trong là một toán tử boolean nhận hai chuỗi và trả về True nếu ap đầu tiên chuyển thành chuỗi con
trong chuỗi thứ hai:
>>> 'a' trong 'chuối'

ĐÚNG VẬY

>>> 'hạt giống' trong 'chuối'

Sai

Ví dụ: hàm sau in tất cả các chữ cái từ word1 cũng xuất hiện trong
word2:

def in_both (word1, word2):


cho chữ cái trong word1:
nếu chữ cái trong word2:

in (chữ cái)
Machine Translated by Google

8.10. So sánh chuỗi 77

Với các tên biến được lựa chọn tốt, Python đôi khi đọc giống như tiếng Anh. Bạn có thể đọc vòng
lặp này, “cho (mỗi) ký tự trong (từ đầu tiên), nếu () ký tự (xuất hiện) trong (thứ hai) từ, in
(các) ký tự.”

Đây là những gì bạn nhận được nếu bạn so sánh táo và cam: >>>

in_both ('táo', 'cam')


một

e
S

8.10 So sánh chuỗi


Các toán tử quan hệ hoạt động trên chuỗi. Để xem liệu hai chuỗi có bằng nhau hay không:

if từ == 'banana':

print ('Được rồi, chuối.')

Các phép toán quan hệ khác rất hữu ích để xếp các từ theo thứ tự bảng chữ cái:

if từ <'banana':
'
print ('Từ của bạn, + từ + ', đứng trước chuối.')
từ elif>' banana ': print
'
(' Từ của bạn, + từ + ', đứng sau chuối.')
khác:

print ('Được rồi, chuối.')

Python không xử lý chữ hoa và chữ thường giống như cách mọi người vẫn làm. Tất cả các chữ cái viết
hoa đứng trước tất cả các chữ cái viết thường, vì vậy:

Từ của bạn, Dứa, đi trước chuối.

Một cách phổ biến để giải quyết vấn đề này là chuyển đổi các chuỗi sang định dạng chuẩn, chẳng
hạn như tất cả các chữ thường, trước khi thực hiện so sánh. Hãy ghi nhớ điều đó trong trường hợp
bạn phải tự vệ trước một người đàn ông được trang bị Quả dứa.

8.11 Gỡ lỗi
Khi bạn sử dụng các chỉ số để duyệt các giá trị theo một trình tự, sẽ rất khó để lấy đúng điểm đầu
và cuối của dấu duyệt. Đây là một hàm được cho là so sánh hai từ và trả về True nếu một trong các
từ là đảo ngược của từ còn lại, nhưng nó có hai lỗi:

def is_reverse (word1, word2):


if len (word1)! = len (word2):
trả về Sai

i = 0

j = len (word2)

trong khi j> 0:


nếu word1 [i]! = word2 [j]:
trả về Sai

i = i + 1
Machine Translated by Google

78 Chương 8. Chuỗi

j = j-1

trả về True

Câu lệnh if đầu tiên kiểm tra xem các từ có cùng độ dài hay không. Nếu không, chúng ta có thể trả về
False ngay lập tức. Nếu không, đối với phần còn lại của hàm, chúng ta có thể giả định rằng các từ có
cùng độ dài. Đây là một ví dụ về mẫu người giám hộ trong Phần 6.8.

i và j là các chỉ số: i đi qua word1 về phía trước trong khi j đi qua word2 về phía sau. Nếu chúng tôi tìm

thấy hai chữ cái không khớp, chúng tôi có thể trả về False ngay lập tức. Nếu chúng ta vượt qua toàn bộ vòng
lặp và tất cả các chữ cái khớp với nhau, chúng ta trả về True.

Nếu chúng tôi kiểm tra hàm này với các từ “pot” và “stop”, chúng tôi mong đợi giá trị trả về là True,
nhưng chúng tôi nhận được lỗi IndexError: >>> is_reverse ('pot', 'stop')

...

Tệp "reverse.py", dòng 15, in is_reverse if word1 [i]!


= Word2 [j]: IndexError: string index out of range
Để gỡ lỗi loại lỗi này, bước đầu tiên của tôi là in các

giá trị của các chỉ số. trước dòng xuất hiện lỗi. while j> 0: print (i, j)

# in ở đây

nếu word1 [i]! = word2 [j]:


trả về Sai

i = i + 1

j = j-1

Bây giờ khi tôi chạy lại chương trình, tôi nhận được thêm thông tin:

>>> is_reverse ('pot', 'stop') 0 4

...

IndexError: chỉ số chuỗi nằm ngoài phạm vi Lần đầu

tiên thông qua vòng lặp, giá trị của j là 4, nằm ngoài phạm vi đối với chuỗi 'chậu'. Chỉ số của ký tự
cuối cùng là 3, vì vậy giá trị ban đầu của j phải là len (word2) -1.

Nếu tôi sửa lỗi đó và chạy lại chương trình, tôi nhận

được: >>> is_reverse ('pot', 'stop') 0 3 1 2

2 1
ĐÚNG VẬY

Lần này chúng tôi nhận được câu trả lời đúng, nhưng có vẻ như vòng lặp chỉ chạy ba lần, điều này thật
đáng ngờ. Để hiểu rõ hơn về những gì đang xảy ra, sẽ rất hữu ích nếu bạn vẽ một biểu đồ trạng thái.
Trong lần lặp đầu tiên, khung cho is_reverse được hiển thị trong Hình 8.2.

Tôi đã lấy một số giấy phép bằng cách sắp xếp các biến trong khung và thêm các dòng chấm chấm để cho
thấy rằng các giá trị của i và j chỉ ra các ký tự trong word1 và word2.

Bắt đầu với sơ đồ này, hãy chạy chương trình trên giấy, thay đổi các giá trị của i và j trong mỗi lần
lặp. Tìm và sửa lỗi thứ hai trong chức năng này.
Machine Translated by Google

8.12. Bảng chú giải 79

word1 'chậu' word2 'dừng lại'

tôi 0 j 3

Hình 8.2: Biểu đồ trạng thái.

8.12 Bảng chú giải thuật ngữ

object: Một cái gì đó mà một biến có thể tham chiếu đến. Hiện tại, bạn có thể sử dụng “đối tượng” và “giá trị”

thay thế cho nhau.

trình tự: Tập hợp các giá trị có thứ tự trong đó mỗi giá trị được xác định bằng một số nguyên
mục lục.

item: Một trong các giá trị trong một chuỗi.

index: Một giá trị số nguyên được sử dụng để chọn một mục trong một chuỗi, chẳng hạn như một ký tự trong chuỗi.

Trong Python, các chỉ số bắt đầu từ 0.

Slice: Một phần của chuỗi được chỉ định bởi một loạt các chỉ số.

chuỗi rỗng: Một chuỗi không có ký tự và độ dài 0, được biểu thị bằng hai dấu ngoặc kép
điểm.

Immutable: Thuộc tính của một chuỗi có các mục không thể thay đổi.

traverse: Để lặp lại các mục trong một trình tự, thực hiện một thao tác tương tự trên
mỗi.

tìm kiếm: Một kiểu truyền tải dừng lại khi nó tìm thấy những gì nó đang tìm kiếm.

bộ đếm: Một biến được sử dụng để đếm thứ gì đó, thường được khởi tạo bằng 0 và sau đó tăng dần
đã đề cập.

invocation: Một câu lệnh gọi một phương thức.

đối số tùy chọn: Đối số hàm hoặc phương thức không bắt buộc.

8.13 Bài tập

Bài tập 8.1. Đọc tài liệu về các phương thức chuỗi tại http: // docs. con trăn. org / 3 / library / stdtypes.

html # chuỗi-phương thức. Bạn có thể muốn thử nghiệm với một số trong số chúng để đảm bảo rằng bạn hiểu cách

chúng hoạt động. dải và thay thế đặc biệt hữu ích.

Tài liệu sử dụng một cú pháp có thể gây nhầm lẫn. Ví dụ, trong find (sub

[, start [, end]]), dấu ngoặc nhọn biểu thị các đối số tùy chọn. Vì vậy, phụ là bắt buộc, nhưng bắt đầu là tùy

chọn và nếu bạn bao gồm bắt đầu, thì kết thúc là tùy chọn.

Bài tập 8.2. Có một phương thức chuỗi được gọi là count tương tự như hàm trong Phần 8.7.

Đọc tài liệu của phương pháp này và viết một lời kêu gọi đếm số a trong
'trái chuối'.

Bài tập 8.3. Một lát cắt chuỗi có thể lấy một chỉ mục thứ ba chỉ định “kích thước bước”; nghĩa là số khoảng cách

giữa các ký tự liên tiếp. Kích thước bước là 2 có nghĩa là mọi ký tự khác; 3 có nghĩa là mỗi phần ba, v.v.
Machine Translated by Google

80 Chương 8. Chuỗi

>>> fruit = 'banana'

>>> quả [0: 5: 2]


'bnn'

Kích thước bước -1 đi qua từ ngược lại, do đó, lát cắt [:: - 1] tạo ra một chuỗi đảo ngược.

Sử dụng thành ngữ này để viết phiên bản một dòng của is_palindrome từ Bài tập 6.3.

Bài tập 8.4. Tất cả các hàm sau đây đều nhằm mục đích kiểm tra xem một chuỗi có chứa bất kỳ chữ cái thường

nào hay không, nhưng ít nhất một số trong số chúng sai. Đối với mỗi hàm, mô tả chức năng thực sự làm gì (giả

sử rằng tham số là một chuỗi). def any_lowercase1 (s):

cho c trong s:

if c.islower ():
trả về True

khác:

trả về Sai

def any_lowercase2 (s):


cho c trong s:

if 'c'.islower (): trả


về' True '

khác:

trả về 'Sai'

def any_lowercase3 (s):


cho c trong s:

cờ trả về flag = c.islower

()

def any_lowercase4 (s): flag =

False for c in s: flag =


flag or c.islower ()

return flag

def any_lowercase5 (s):


cho c trong s:

nếu không phải c.islower ():


trả về Sai

trả về True

Bài tập 8.5. Caesar cypher là một dạng mã hóa yếu liên quan đến việc "xoay" từng chữ cái theo một số vị trí

cố định. Để xoay một chữ cái có nghĩa là chuyển nó qua bảng chữ cái, quấn quanh đầu nếu cần, vì vậy 'A' được

xoay bởi 3 là 'D' và 'Z' được xoay bởi 1 là 'A'.

Để xoay một từ, hãy xoay từng chữ cái với cùng một lượng. Ví dụ: “cổ vũ” được xoay bởi 7 là “vui vẻ” và “dưa”

được xoay bởi -10 là “lập phương”. Trong bộ phim 2001: A Space Odyssey, máy tính trên tàu có tên là HAL, được

IBM quay -1.

Viết một hàm có tên là xoay_word, lấy một chuỗi và một số nguyên làm tham số và trả về một chuỗi mới chứa các

ký tự từ chuỗi ban đầu được xoay theo số lượng đã cho.

Bạn có thể muốn sử dụng hàm ord tích hợp, hàm này chuyển đổi một ký tự thành mã số và
Machine Translated by Google

8.13. Bài tập 81

chr, chuyển đổi mã số thành ký tự. Các chữ cái trong bảng chữ cái được mã hóa theo thứ tự bảng chữ
cái, ví dụ:

>>> ord ('c') - ord ('a')


2

Bởi vì 'c' là chữ cái có hai e trong bảng chữ cái. Nhưng hãy cẩn thận: các mã số cho các chữ cái viết
hoa là khác nhau.

Những trò đùa có khả năng gây khó chịu trên Internet đôi khi được mã hóa trong ROT13, một trò đùa cợt
của Caesar với vòng quay 13. Nếu bạn không dễ bị xúc phạm, hãy tìm và giải mã một số trong số chúng.
Lời giải: http: // thinkpython2. com / code / xoay. py
Machine Translated by Google

82 Chương 8. Chuỗi
Machine Translated by Google

Chương 9

Nghiên cứu tình huống: chơi chữ

Chương này trình bày nghiên cứu trường hợp thứ hai, liên quan đến việc giải các câu đố chữ bằng
cách tìm kiếm các từ có các tính chất nhất định. Ví dụ: chúng tôi sẽ tìm những từ vựng dài nhất
bằng tiếng Anh và tìm kiếm những từ có các chữ cái xuất hiện theo thứ tự bảng chữ cái. Và tôi sẽ
trình bày một kế hoạch phát triển chương trình khác: giảm xuống một vấn đề đã được giải quyết
trước đó.

9.1 Đọc danh sách từ


Đối với các bài tập trong chương này, chúng ta cần một danh sách các từ tiếng Anh. Có rất
nhiều danh sách từ có sẵn trên Web, nhưng danh sách từ phù hợp nhất cho mục đích của chúng tôi
là một trong những danh sách từ được Grady Ward thu thập và đóng góp cho miền công cộng như
một phần của dự án Moby lexi con (xem http: // wikipedia .org / wiki / Moby_Project). Đó là
danh sách 113.809 trò chơi ô chữ chính thức; nghĩa là những từ được coi là hợp lệ trong trò
chơi ô chữ và các trò chơi chữ khác. Trong bộ sưu tập Moby, tên tệp là 113809of.fic; bạn có
thể tải xuống một bản sao, với tên đơn giản hơn là words.txt, từ http://thinkpython2.com/code/words.txt.

Tệp này ở dạng văn bản thuần túy, vì vậy bạn có thể mở nó bằng trình soạn thảo văn bản, nhưng bạn
cũng có thể đọc nó từ Python. Hàm tích hợp sẵn mở lấy tên của tệp làm tham số và trả về một đối
tượng tệp mà bạn có thể sử dụng để đọc tệp. >>> fin = open ('words.txt') fin là tên chung cho một

đối tượng tệp được sử dụng để nhập liệu. Đối tượng tệp cung cấp một số phương thức để đọc, bao

gồm cả dòng đọc, đọc các ký tự từ tệp cho đến khi nó chuyển sang dòng mới và trả về kết quả là
một chuỗi: >>> fin.readline () 'aa \ n'

Từ đầu tiên trong danh sách cụ thể này là "aa", là một loại dung nham. Chuỗi \ n đại diện cho ký
tự dòng mới ngăn cách từ này với từ tiếp theo.

Đối tượng tệp theo dõi vị trí của nó trong tệp, vì vậy nếu bạn gọi lại dòng đọc, bạn sẽ nhận được
từ tiếp theo:

>>> fin.readline ()
'aah \ n'
Machine Translated by Google

84 Chương 9. Nghiên cứu điển hình: chơi chữ

Từ tiếp theo là “aah”, đó là một từ hoàn toàn chính đáng, vì vậy đừng nhìn tôi như vậy nữa. Hoặc, nếu
đó là ký tự dòng mới đang làm phiền bạn, chúng tôi có thể loại bỏ nó bằng dải phương thức chuỗi:

>>> line = fin.readline () >>>

word = line.strip () >>> word

'aahed'

Bạn cũng có thể sử dụng một đối tượng tệp như một phần của vòng lặp for. Chương trình này đọc words.txt
và in từng từ, một trên mỗi dòng:

fin = open ('words.txt') cho


dòng trong fin:

word = line.strip ()
print (word)

9.2 Bài tập

Có lời giải cho các bài tập này trong phần tiếp theo. Ít nhất bạn nên thử từng cách một trước khi đọc
các giải pháp.
Bài tập 9.1. Viết chương trình đọc words.txt và chỉ in các từ có hơn 20 ký tự (không tính khoảng trắng).

Bài tập 9.2. Năm 1939, Ernest Vincent Wright đã xuất bản một cuốn tiểu thuyết 50.000 từ có tên Gadsby
không chứa chữ “e”. Vì “e” là chữ cái phổ biến nhất trong tiếng Anh, điều đó không dễ thực hiện.

Trên thực tế, rất khó để xây dựng một ý nghĩ đơn độc mà không sử dụng biểu tượng chung nhất đó. Lúc đầu
thì hơi chậm, nhưng với sự cẩn trọng và hàng giờ rèn luyện, bạn có thể dần dần có được cơ sở.

Được rồi, tôi sẽ dừng lại ngay bây giờ.

Viết một hàm có tên has_no_e trả về True nếu từ đã cho không có chữ “e” trong đó.

Viết chương trình đọc words.txt và chỉ in các từ không có chữ “e”. Tính tỷ lệ phần trăm các từ trong
danh sách không có chữ “e”.
Bài tập 9.3. Viết một hàm có tên là tránh nhận một từ và một chuỗi các chữ cái bị cấm và trả về giá trị
True nếu từ đó không sử dụng bất kỳ chữ cái bị cấm nào.

Viết một chương trình nhắc người dùng nhập một chuỗi các chữ cái bị cấm và sau đó in ra số từ không
chứa bất kỳ từ nào trong số chúng. Bạn có thể tìm thấy sự kết hợp của 5 chữ cái bị cấm loại trừ số từ
nhỏ nhất không?
Bài tập 9.4. Viết một hàm có tên use_only nhận một từ và một chuỗi ký tự và trả về giá trị True nếu từ
chỉ chứa các ký tự trong danh sách. Bạn có thể tạo một câu chỉ bằng cách sử dụng các chữ cái acefhlo?
Ngoài "cỏ linh lăng"?
Bài tập 9.5. Viết một hàm có tên use_all nhận một từ và một chuỗi các chữ cái bắt buộc và trả về giá
trị True nếu từ đó sử dụng tất cả các chữ cái được yêu cầu ít nhất một lần. Có bao nhiêu từ sử dụng tất
cả các nguyên âm aeiou? Còn aeiouy thì sao?

Bài tập 9.6. Viết một hàm có tên is_abecedarian trả về giá trị True nếu các chữ cái trong một từ xuất
hiện theo thứ tự bảng chữ cái (chữ cái kép là ok). Có bao nhiêu từ abecedarian?
Machine Translated by Google

9.3. Tìm kiếm 85

9.3 Tìm kiếm

Tất cả các bài tập trong phần trước đều có điểm chung; chúng có thể được giải quyết bằng mẫu tìm kiếm mà chúng

ta đã thấy trong Phần 8.6. Ví dụ đơn giản nhất là: def has_no_e (word):

cho chữ cái trong từ:

if chữ cái == 'e':

trả về Sai

trả về True

Vòng lặp for duyệt qua các ký tự trong word. Nếu chúng ta tìm thấy chữ cái “e”, chúng ta có thể trả về False ngay lập tức;

nếu không chúng ta phải đi đến bức thư tiếp theo. Nếu chúng ta thoát khỏi vòng lặp một cách bình thường, điều đó có nghĩa

là chúng ta không tìm thấy chữ “e”, vì vậy chúng ta trả về True.

Bạn có thể viết hàm này ngắn gọn hơn bằng cách sử dụng toán tử in, nhưng tôi bắt đầu với phiên bản này vì nó

thể hiện logic của mẫu tìm kiếm.

tránh là một phiên bản tổng quát hơn của has_no_e nhưng nó có cấu trúc tương tự: def tránh

(từ, bị cấm):
cho chữ cái trong từ:

nếu chữ cái bị cấm:

trả về Sai

trả về True

Chúng tôi có thể trả lại False ngay khi chúng tôi tìm thấy một bức thư bị cấm; nếu chúng ta đi đến cuối vòng lặp,
chúng tôi trả về True.

use_only cũng tương tự ngoại trừ ý nghĩa của điều kiện bị đảo ngược: def using_only

(word, có sẵn): cho chữ cái trong word:

nếu thư không có sẵn:

trả về Sai

trả về True

Thay vì danh sách các chữ cái bị cấm, chúng tôi có một danh sách các chữ cái có sẵn. Nếu chúng tôi tìm thấy một

chữ cái trong word không có sẵn, chúng tôi có thể trả về False.

use_all cũng tương tự ngoại trừ việc chúng ta đảo ngược vai trò của từ và chuỗi ký tự: def using_all (từ,

bắt buộc): cho chữ cái được yêu cầu:

nếu chữ cái không có trong từ:

trả về Sai

trả về True

Thay vì duyệt các chữ cái trong word, vòng lặp sẽ duyệt các chữ cái bắt buộc. Nếu bất kỳ ký tự bắt buộc nào

không xuất hiện trong từ, chúng tôi có thể trả về Sai.

Nếu bạn thực sự suy nghĩ như một nhà khoa học máy tính, bạn sẽ nhận ra rằng use_all là một ví dụ của một vấn

đề đã được giải quyết trước đó và bạn sẽ viết: def using_all (word, Required): return using_only (bắt buộc,

từ)

Đây là một ví dụ về kế hoạch phát triển chương trình được gọi là giảm bớt một vấn đề đã được giải quyết trước

đó, có nghĩa là bạn nhận ra vấn đề bạn đang giải quyết như một ví dụ của vấn đề đã giải quyết và áp dụng một

giải pháp hiện có.


Machine Translated by Google

86 Chương 9. Nghiên cứu điển hình: chơi chữ

9.4 Vòng lặp với các chỉ số

Tôi đã viết các hàm trong phần trước với vòng lặp for vì tôi chỉ cần các ký tự trong chuỗi; Tôi
không phải làm bất cứ điều gì với các chỉ số.

Đối với is_abecedarian, chúng ta phải so sánh các chữ cái liền kề, điều này hơi phức tạp với vòng
lặp for: def is_abecedarian (word): before = word [0] for c trong word:

nếu c <trước: trả


về Sai

trước = c
trả về True

Một giải pháp thay thế là sử dụng đệ quy:

def is_abecedarian (word): if


len (word) <= 1:
trả về True

nếu từ [0]> từ [1]:


trả về Sai

return is_abecedarian (từ [1:])

Một tùy chọn khác là sử dụng vòng lặp while:

def is_abecedarian (word): i =


0 while i <len (word) -1:
if word [i + 1] <word [i]:

trả về Sai

i = i + 1
trả về True

Vòng lặp bắt đầu từ i = 0 và kết thúc khi i = len (word) -1. Mỗi lần qua vòng lặp, nó com sẽ so
sánh ký tự thứ i (mà bạn có thể coi là ký tự hiện tại) với ký tự thứ i + 1 (mà bạn có thể coi là
ký tự tiếp theo).

Nếu ký tự tiếp theo nhỏ hơn (theo thứ tự bảng chữ cái trước đó) ký tự hiện tại, thì chúng tôi đã
loại bỏ sự phá vỡ trong xu hướng abecedarian và chúng tôi trả về False.

Nếu chúng ta đi đến cuối vòng lặp mà không tìm thấy lỗi, thì từ đó vượt qua bài kiểm tra. Để thuyết
phục bản thân rằng vòng lặp kết thúc chính xác, hãy xem xét một ví dụ như 'xỉa răng'. Độ dài của
từ là 6, vì vậy lần cuối cùng vòng lặp chạy là khi i là 4, là chỉ số của ký tự thứ hai đến cuối
cùng. Trong lần lặp cuối cùng, nó so sánh ký tự thứ hai đến ký tự cuối cùng với ký tự cuối cùng,
đó là những gì chúng tôi muốn.

Đây là một phiên bản của is_palindrome (xem Bài tập 6.3) sử dụng hai chỉ số; một cái bắt đầu ở đầu
và đi lên; cái kia bắt đầu ở cuối và đi xuống. def is_palindrome (word):

i = 0

j = len (word) -1

while i <j:
if word [i]! = word [j]:
Machine Translated by Google

9,5. Gỡ lỗi 87

trả về Sai

i = i + 1

j = j-1

trả về True

Hoặc chúng ta có thể giảm xuống một vấn đề đã giải quyết trước đó và viết:

def is_palindrome (word): return is_reverse (word, word)

Sử dụng is_reverse từ Phần 8.11.

9.5 Gỡ lỗi
Các chương trình thử nghiệm rất khó. Các chức năng trong chương này tương đối dễ kiểm tra vì bạn có thể kiểm

tra kết quả bằng tay. Mặc dù vậy, việc chọn một nhóm từ kiểm tra tất cả các lỗi có thể xảy ra ở đâu đó giữa

khó khăn và bất khả thi.

Lấy has_no_e làm ví dụ, có hai trường hợp rõ ràng cần kiểm tra: những từ có chữ 'e' sẽ trả về False và những
từ không phải trả về True. Bạn sẽ không gặp khó khăn gì khi nghĩ ra một trong số mỗi thứ.

Trong mỗi trường hợp, có một số trường hợp con ít rõ ràng hơn. Trong số các từ có chữ “e”, bạn nên kiểm tra

các từ có chữ “e” ở đầu, cuối và ở giữa. Bạn nên kiểm tra các từ dài, từ ngắn và từ rất ngắn, giống như chuỗi

trống. Chuỗi rỗng là một ví dụ về trường hợp đặc biệt, là một trong những trường hợp không rõ ràng mà lỗi

thường ẩn nấp.

Ngoài các trường hợp kiểm tra bạn tạo, bạn cũng có thể kiểm tra chương trình của mình bằng danh sách từ như

words.txt. Bằng cách quét kết quả đầu ra, bạn có thể bắt gặp lỗi, nhưng hãy cẩn thận: bạn có thể mắc một loại

lỗi (những từ không nên bao gồm nhưng lại có) chứ không phải lỗi khác (những từ nên được bao gồm, nhưng không
phải) .

Nói chung, kiểm thử có thể giúp bạn tìm ra lỗi, nhưng không dễ để tạo ra một tập hợp các trường hợp kiểm thử

tốt và ngay cả khi bạn làm vậy, bạn cũng không thể chắc chắn rằng chương trình của mình là chính xác. Theo một

nhà khoa học máy tính huyền thoại:

Kiểm tra chương trình có thể được sử dụng để hiển thị sự hiện diện của lỗi, nhưng không bao giờ cho thấy sự
vắng mặt của chúng!

- Edsger W. Dijkstra

9.6 Bảng chú giải thuật ngữ

đối tượng tệp: Một giá trị đại diện cho một tệp đang mở.

rút gọn thành một vấn đề đã được giải quyết trước đó: Một cách giải quyết một vấn đề bằng cách diễn đạt nó

như một ví dụ của một vấn đề đã được giải quyết trước đó.

trường hợp đặc biệt: Một trường hợp thử nghiệm không điển hình hoặc không rõ ràng (và ít có khả năng được xử lý

trực tràng).
Machine Translated by Google

88 Chương 9. Nghiên cứu điển hình: chơi chữ

9.7 Bài tập

Bài tập 9.7. Câu hỏi này dựa trên một Puzzler được phát trên chương trình radio Car Talk (http: // www. Cartalk.

Com / content / puzzlelers):

Cho tôi một từ có ba chữ cái kép liên tiếp. Tôi sẽ cung cấp cho bạn một vài từ gần như đủ điều kiện,

nhưng không. Ví dụ, ủy ban từ, ủy ban. Nó sẽ rất tuyệt vời ngoại trừ cái 'i' lẻn vào đó. Hoặc

Mississippi: Mississi ppi. Nếu bạn có thể lấy ra những thứ đó, tôi sẽ làm được. Nhưng có một từ có ba

cặp chữ cái liên tiếp và theo hiểu biết của tôi thì đây có thể là từ duy nhất.

Tất nhiên có lẽ còn 500 cái nữa nhưng tôi chỉ có thể nghĩ ra một cái. Từ là gì?

Viết một chương trình để tìm nó. Lời giải: http: // thinkpython2. com / code / cartalk1. py
Bài tập 9.8. Đây là một câu đố khác về Car Talk Puzzler (http: // www. Cartalk. Com / content / puzzlelers ):

“Tôi đang lái xe trên đường cao tốc vào một ngày nọ và tôi tình cờ nhận thấy đồng hồ đo đường của mình.

Giống như hầu hết các đồng hồ đo đường, nó hiển thị sáu chữ số, chỉ trong toàn bộ dặm. Vì vậy, nếu chiếc xe của tôi đã đi

được 300.000 dặm, chẳng hạn, tôi sẽ thấy là 3-0-0-0-0-0.

“Bây giờ, những gì tôi thấy ngày hôm đó rất thú vị. Tôi nhận thấy rằng 4 chữ số cuối cùng là

palindromic; có nghĩa là, họ đọc về phía trước giống như phía sau. Ví dụ: 5-4-4-5 là palindrome, vì

vậy đồng hồ đo đường của tôi có thể đọc 3-1-5-4-4-5.

“Một dặm sau, 5 số cuối cùng là palindromic. Ví dụ, nó có thể đọc 3-6-5-4-5-6. Một dặm sau đó, 4 trong

số 6 số ở giữa là palindromic. Và bạn đã sẵn sàng cho điều này? Một dặm sau, cả 6 đều là palindromic!

"Câu hỏi là, cái gì trên đồng hồ đo đường khi tôi nhìn lần đầu tiên?"

Viết một chương trình Python kiểm tra tất cả các số có sáu chữ số và in ra bất kỳ số nào thỏa mãn các yêu cầu này.

Lời giải: http: // thinkpython2. com / code / cartalk2. py

Bài tập 9.9. Đây là một Câu đố trò chuyện trên xe hơi khác mà bạn có thể giải quyết bằng cách tìm kiếm (http: //

www. Cartalk. Com / content / puzzlelers ):

“Gần đây, tôi có một chuyến thăm với mẹ và chúng tôi nhận ra rằng hai chữ số tạo nên tuổi của tôi khi

đảo ngược lại dẫn đến tuổi của bà. Ví dụ, nếu cô ấy 73 tuổi, tôi 37. Chúng tôi tự hỏi điều này đã xảy

ra thường xuyên như thế nào trong những năm qua nhưng chúng tôi đã chuyển sang các chủ đề khác và chúng

tôi không bao giờ tìm ra câu trả lời.

“Khi tôi về nhà, tôi phát hiện ra rằng các chữ số của tuổi chúng tôi đã có thể đảo ngược sáu lần cho

đến nay. Tôi cũng nhận ra rằng nếu chúng ta may mắn thì điều đó sẽ xảy ra một lần nữa sau một vài năm,

và nếu chúng ta thực sự may mắn thì điều đó sẽ xảy ra thêm một lần nữa sau đó. Nói cách khác, nó sẽ

xảy ra 8 lần. Vậy câu hỏi đặt ra là bây giờ tôi bao nhiêu tuổi? ”

Viết một chương trình Python tìm kiếm các giải pháp cho Puzzler này. Gợi ý: bạn có thể thấy phương thức chuỗi zfill

hữu ích.

Lời giải: http: // thinkpython2. com / code / cartalk3. py


Machine Translated by Google

Chương 10

Danh sách

Chương này trình bày một trong những danh sách, kiểu tích hợp hữu ích nhất của Python. Bạn cũng sẽ tìm
hiểu thêm về các đối tượng và điều gì có thể xảy ra khi bạn có nhiều hơn một tên cho cùng một đối tượng.

10.1 Danh sách là một chuỗi

Giống như một chuỗi, một danh sách là một chuỗi các giá trị. Trong một chuỗi, các giá trị là các ký tự; trong một danh

sách, chúng có thể là bất kỳ loại nào. Các giá trị trong danh sách được gọi là phần tử hoặc đôi khi là các mục.

Có một số cách để tạo một danh sách mới; đơn giản nhất là đặt các phần tử trong dấu ngoặc vuông ([và]):

[10, 20, 30, 40]

['ếch giòn', 'ram bắp', 'chim sơn ca']

Ví dụ đầu tiên là danh sách bốn số nguyên. Thứ hai là danh sách ba chuỗi. Các phần tử của danh sách không
nhất thiết phải cùng loại. Danh sách sau chứa một chuỗi, một số thực, một số nguyên và (lo!) Một danh sách
khác:

['spam', 2.0, 5, [10, 20]]

Một danh sách trong một danh sách khác được lồng vào nhau.

Danh sách không chứa phần tử nào được gọi là danh sách rỗng; bạn có thể tạo một với dấu ngoặc trống, [].

Như bạn có thể mong đợi, bạn có thể gán giá trị danh sách cho các biến:

>>> pho mát = ['Cheddar', 'Edam', 'Gouda'] >>> số = [42,


123] >>> trống = [] >>> in (pho mát, số, trống)

['Cheddar', 'Edam', 'Gouda'] [42, 123] []


Machine Translated by Google

90 Chương 10. Danh sách

danh sách

pho mát 0 'Cheddar'


1 'Edam'
2 'Gouda'

danh sách

con số 0 42
1 123
5

danh sách

trống rỗng

Hình 10.1: Biểu đồ trạng thái.

10.2 Danh sách có thể thay đổi

Cú pháp để truy cập các phần tử của một danh sách cũng giống như để truy cập các ký tự của một chuỗi — toán
tử ngoặc. Biểu thức bên trong dấu ngoặc chỉ định chỉ mục.
Hãy nhớ rằng các chỉ số bắt đầu từ 0:

>>> phô mai [0]


'Cheddar'

Không giống như chuỗi, danh sách có thể thay đổi. Khi toán tử dấu ngoặc vuông xuất hiện ở bên trái của một
phép gán, nó xác định phần tử của danh sách sẽ được gán. >>> số = [42, 123] >>> số [1] = 5

>>> số

[42, 5]

Phần tử một phần tử của các số, trước đây là 123, bây giờ là 5.

Hình 10.1 cho thấy biểu đồ trạng thái của pho mát, số và rỗng.

Danh sách được biểu thị bằng các hộp có từ “danh sách” bên ngoài và các phần tử của danh sách bên trong.
phoes đề cập đến một danh sách có ba phần tử được lập chỉ mục 0, 1 và 2. Các số chứa hai phần tử; sơ đồ cho

thấy rằng giá trị của phần tử thứ hai đã được gán lại từ 123 thành 5. rỗng đề cập đến một danh sách không
có phần tử nào.

Chỉ mục danh sách hoạt động giống như chỉ số chuỗi:

• Bất kỳ biểu thức số nguyên nào cũng có thể được sử dụng làm chỉ mục.

• Nếu bạn cố gắng đọc hoặc ghi một phần tử không tồn tại, bạn sẽ gặp lỗi IndexError. • Nếu một chỉ

mục có giá trị âm, nó sẽ được tính ngược từ cuối danh sách.

Toán tử trong cũng hoạt động trên danh

sách. >>> pho mát = ['Cheddar', 'Edam', 'Gouda']


>>> 'Edam' trong pho mát
ĐÚNG VẬY

>>> 'Brie' trong pho mát


Sai
Machine Translated by Google

10.3. Duyệt qua một danh sách 91

10.3 Duyệt qua danh sách

Cách phổ biến nhất để duyệt qua các phần tử của danh sách là sử dụng vòng lặp for. Cú pháp giống
như đối với chuỗi:

đối với pho mát trong pho mát:

in (pho mát)

Điều này hoạt động tốt nếu bạn chỉ cần đọc các phần tử của danh sách. Nhưng nếu bạn muốn viết hoặc
cập nhật các phần tử, bạn cần các chỉ số. Một cách phổ biến để làm điều đó là kết hợp phạm vi hàm
tích hợp và len:

cho tôi trong phạm vi (len (số)):


số [i] = số [i] * 2

Vòng lặp này duyệt qua danh sách và cập nhật từng phần tử. len trả về số phần tử trong danh sách.
range trả về một danh sách các chỉ số từ 0 đến n - 1, trong đó n là độ dài của danh sách.
Mỗi lần qua vòng lặp, tôi nhận được chỉ số của phần tử tiếp theo. Câu lệnh gán trong phần thân sử
dụng i để đọc giá trị cũ của phần tử và gán giá trị mới.

Vòng lặp for trên một danh sách trống không bao giờ chạy phần thân:

cho x trong []:

print ('Điều này không bao giờ xảy ra.')

Mặc dù một danh sách có thể chứa một danh sách khác, danh sách lồng nhau vẫn được tính là một phần tử duy nhất.

Độ dài của danh sách này là bốn:

['spam', 1, ['Brie', 'Roquefort', 'Pol le Veq'], [1, 2, 3]]

10.4 Liệt kê các hoạt động

Toán tử + nối các danh sách:

>>> a = [1, 2, 3] >>>


b = [4, 5, 6] >>> c =
a + b
>>> c

[1, 2, 3, 4, 5, 6]

Toán tử * lặp lại danh sách một số lần nhất định:

>>> [0] * 4
[0, 0, 0, 0]
>>> [1, 2, 3] * 3 [1,
2, 3, 1, 2, 3, 1, 2, 3]

Ví dụ đầu tiên lặp lại [0] bốn lần. Ví dụ thứ hai lặp lại danh sách [1, 2, 3] ba lần.

10.5 Liệt kê các lát

Toán tử lát cắt cũng hoạt động trên các danh sách:
Machine Translated by Google

92 Chương 10. Danh sách

>>> t = ['a', 'b', 'c', 'd', 'e', 'f'] >>> t


[1: 3] ['b', 'c'] >> > t [: 4] ['a', 'b', 'c',
'd'] >>> t [3:] ['d', 'e', 'f']

Nếu bạn bỏ qua chỉ mục đầu tiên, lát cắt sẽ bắt đầu từ đầu. Nếu bạn bỏ qua phần thứ hai, phần cắt sẽ đi đến
cuối. Vì vậy, nếu bạn bỏ qua cả hai, lát cắt sẽ là một bản sao của toàn bộ danh sách.

>>> t [:]
['a', 'b', 'c', 'd', 'e', 'f']

Vì danh sách có thể thay đổi, nên việc tạo một bản sao trước khi thực hiện các thao tác sửa đổi danh sách

thường rất hữu ích.

Một toán tử lát ở phía bên trái của một phép gán có thể cập nhật nhiều phần tử:

>>> t = ['a', 'b', 'c', 'd', 'e', 'f'] >>> t


[1: 3] = ['x', 'y']
>>> t

['a', 'x', 'y', 'd', 'e', 'f']

10.6 Liệt kê các phương pháp

Python cung cấp các phương thức hoạt động trên danh sách. Ví dụ: append thêm một phần tử mới
vào cuối danh sách:

>>> t = ['a', 'b', 'c'] >>>


t.append ('d')
>>> t
['A B C D']

extension lấy một danh sách làm đối số và nối tất cả các phần tử:

>>> t1 = ['a', 'b', 'c'] >>>


t2 = ['d', 'e'] >>> t1.extend
(t2)
>>> t1
['a', 'b', 'c', 'd', 'e']

Ví dụ này không sửa đổi t2.

sắp xếp sắp xếp các phần tử của danh sách từ thấp đến cao:

>>> t = ['d', 'c', 'e', 'b', 'a'] >>>


t.sort ()
>>> t
['a', 'b', 'c', 'd', 'e']

Hầu hết các phương thức danh sách là vô hiệu; họ sửa đổi danh sách và trả về Không có. Nếu bạn
vô tình viết t = t.sort (), bạn sẽ thất vọng với kết quả.
Machine Translated by Google

10,7. Lập bản đồ, lọc và giảm 93

10.7 Bản đồ, lọc và thu nhỏ

Để cộng tất cả các số trong một danh sách, bạn có thể sử dụng một vòng lặp như sau:

def add_all (t):


tổng = 0

cho x trong t:
tổng + = x

tổng trở lại

tổng số được khởi tạo bằng 0. Mỗi lần qua vòng lặp, x nhận được một phần tử từ danh sách.
Toán tử + = cung cấp một cách ngắn gọn để cập nhật một biến. Bài tập tăng cường này
bản tường trình,

tổng + = x

tương đương với

tổng = tổng + x

Khi vòng lặp chạy, tổng cộng tích lũy tổng của các phần tử; một biến được sử dụng theo cách này đôi khi
được gọi là bộ tích lũy.

Việc thêm các phần tử của danh sách là một hoạt động phổ biến đến nỗi Python cung cấp nó dưới dạng một hàm
tích hợp, sum:

>>> t = [1, 2, 3] >>>


sum (t)
6

Một phép toán như thế này kết hợp một chuỗi các phần tử thành một giá trị duy nhất được gọi là giảm thiểu.

Đôi khi bạn muốn duyệt qua một danh sách trong khi xây dựng một danh sách khác. Ví dụ: hàm sau nhận một
danh sách các chuỗi và trả về một danh sách mới chứa các chuỗi được viết hoa:

def capitalize_all (t): res =


[]
cho s trong t:

res.append (s.capitalize ())


trả lại res

res được khởi tạo với một danh sách trống; mỗi lần thông qua vòng lặp, chúng tôi thêm phần phụ tiếp theo.
Vì vậy, res là một loại tích lũy khác.

Một phép toán như capitalize_all đôi khi được gọi là bản đồ vì nó "ánh xạ" một hàm (trong trường hợp này là
phương thức viết hoa) lên từng phần tử trong một chuỗi.

Một thao tác phổ biến khác là chọn một số phần tử từ danh sách và trả về danh sách con.
Ví dụ: hàm sau nhận một danh sách các chuỗi và trả về một danh sách chỉ chứa các chuỗi chữ hoa:

def only_upper (t): res


= []
cho s trong t:

if s.isupper ():
res.append (s)
trả lại res
Machine Translated by Google

94 Chương 10. Danh sách

isupper là một phương thức chuỗi trả về True nếu chuỗi chỉ chứa các chữ cái viết hoa.

Một hoạt động như only_upper được gọi là bộ lọc vì nó chọn một số phần tử và lọc ra những
phần tử khác.

Hầu hết các hoạt động danh sách phổ biến có thể được thể hiện dưới dạng kết hợp của bản đồ, bộ lọc và giảm bớt.

10.8 Xóa các phần tử


Có một số cách để xóa các phần tử khỏi danh sách. Nếu bạn biết chỉ mục của phần tử bạn
muốn, bạn có thể sử dụng pop: >>> t = ['a', 'b', 'c'] >>> x = t.pop (1)

>>> t
['AC']
>>> x
'b'

pop sửa đổi danh sách và trả về phần tử đã bị xóa. Nếu bạn không cung cấp chỉ mục, nó sẽ
xóa và trả về phần tử cuối cùng.

Nếu bạn không cần giá trị đã loại bỏ, bạn có thể sử dụng toán tử
del: >>> t = ['a', 'b', 'c'] >>> del t [1]

>>> t
['AC']

Nếu bạn biết phần tử bạn muốn xóa (nhưng không phải chỉ mục), bạn có thể sử dụng
remove: >>> t = ['a', 'b', 'c'] >>> t.remove ('b')

>>> t
['AC']
Giá trị trả về từ loại bỏ là Không có.

Để loại bỏ nhiều phần tử, bạn có thể sử dụng del với chỉ mục lát cắt:
>>> t = ['a', 'b', 'c', 'd', 'e', 'f'] >>> del t [1: 5]

>>> t
['a', 'f']

Như thường lệ, lát cắt chọn tất cả các phần tử lên đến nhưng không bao gồm chỉ mục thứ hai.

10.9 Danh sách và chuỗi


Một chuỗi là một chuỗi các ký tự và một danh sách là một chuỗi các giá trị, nhưng một danh sách các ký tự

không giống như một chuỗi. Để chuyển đổi từ một chuỗi thành một danh sách các ký tự, bạn có thể sử dụng danh sách:

>>> s = 'spam'
>>> t = list (s)
>>> t
['Thư rác']
Machine Translated by Google

10,10. Đối tượng và giá trị 95

một 'trái chuối' một

'trái chuối'
b 'trái chuối' b

Hình 10.2: Biểu đồ trạng thái.

Vì danh sách là tên của một hàm dựng sẵn, bạn nên tránh sử dụng nó làm tên biến. Tôi cũng tránh l
vì nó trông quá giống 1. Vì vậy, đó là lý do tại sao tôi sử dụng t.

Hàm danh sách ngắt một chuỗi thành các chữ cái riêng lẻ. Nếu bạn muốn ngắt một chuỗi thành các từ,
bạn có thể sử dụng phương pháp tách:

>>> s = 'pining for the fjords' >>> t =


s.split ()
>>> t

['pining', 'for', 'the', 'fjords']

Một đối số tùy chọn được gọi là dấu phân cách chỉ định các ký tự nào sẽ được sử dụng làm dấu giáp
từ. Ví dụ sau sử dụng dấu gạch ngang làm dấu phân cách:

>>> s = 'spam-spam-spam' >>>


delimiter = '-'

>>> t = s.split (dấu phân cách)


>>> t

['thư rác', 'thư rác', 'thư rác']

phép nối là nghịch đảo của phép chia. Nó có một danh sách các chuỗi và nối các phần tử. join là một
phương thức chuỗi, vì vậy bạn phải gọi nó trên dấu phân cách và chuyển danh sách dưới dạng tham số:

>>> t = ['pining', 'for', 'the', 'fjords']


>>> dấu phân cách =
' '

>>> s = delimiter.join (t) >>> s

'pining for the fjords'

Trong trường hợp này, dấu phân cách là một ký tự khoảng trắng, vì vậy phép nối sẽ đặt một khoảng trắng
giữa các từ. Để nối các chuỗi không có dấu cách, bạn có thể sử dụng chuỗi trống, '', làm dấu phân cách.

10.10 Đối tượng và giá trị

Nếu chúng ta chạy các câu lệnh gán sau:

a = 'chuối'
b = 'chuối'

Chúng ta biết rằng a và b đều tham chiếu đến một chuỗi, nhưng chúng ta không biết liệu chúng có tham chiếu đến cùng

một chuỗi hay không. Có hai trạng thái có thể xảy ra, được thể hiện trong Hình 10.2.

Trong một trường hợp, a và b đề cập đến hai đối tượng khác nhau có cùng giá trị. Trong trường hợp thứ
hai, chúng đề cập đến cùng một đối tượng.

Để kiểm tra xem hai biến có tham chiếu đến cùng một đối tượng hay không, bạn có thể sử dụng toán tử is.
Machine Translated by Google

96 Chương 10. Danh sách

một
[1, 2,

b 3] [1, 2, 3]

Hình 10.3: Biểu đồ trạng thái.

một

[1, 2, 3]
b

Hình 10.4: Biểu đồ trạng thái.

>>> a = 'chuối'

>>> b = 'chuối'

>>> a là b

ĐÚNG VẬY

Trong ví dụ này, Python chỉ tạo một đối tượng chuỗi và cả a và b đều tham chiếu đến nó. Nhưng khi bạn tạo hai

danh sách, bạn nhận được hai đối tượng:

>>> a = [1, 2, 3] >>> b

= [1, 2, 3]
>>> a là b

Sai

Vì vậy biểu đồ trạng thái có dạng như Hình 10.3.

Trong trường hợp này, chúng ta sẽ nói rằng hai danh sách là tương đương nhau, bởi vì chúng có các điểm giống

nhau, nhưng không giống hệt nhau, bởi vì chúng không phải là cùng một đối tượng. Nếu hai đối tượng giống hệt

nhau, chúng cũng tương đương, nhưng nếu chúng tương đương, chúng không nhất thiết phải giống hệt nhau.

Cho đến nay, chúng ta vẫn sử dụng “object” và “value” thay thế cho nhau, nhưng chính xác hơn là nói rằng một

đối tượng có một giá trị. Nếu bạn đánh giá [1, 2, 3], bạn nhận được một đối tượng danh sách có giá trị là

một chuỗi các số nguyên. Nếu một danh sách khác có các phần tử giống nhau, chúng ta nói rằng nó có cùng giá

trị, nhưng nó không phải là cùng một đối tượng.

10.11 Răng cưa


Nếu a tham chiếu đến một đối tượng và bạn gán b = a, thì cả hai biến đều tham chiếu đến cùng một đối tượng:

>>> a = [1, 2, 3] >>> b = a >>> b là a

ĐÚNG VẬY

Biểu đồ trạng thái có dạng như Hình 10.4.

Sự kết hợp của một biến với một đối tượng được gọi là một tham chiếu. Trong ví dụ này, có hai tham chiếu đến

cùng một đối tượng.

Một đối tượng có nhiều tham chiếu có nhiều tên, vì vậy chúng ta nói rằng đối tượng đó là bí danh.

Nếu đối tượng bí danh có thể thay đổi, các thay đổi được thực hiện với một bí danh sẽ ảnh hưởng đến đối tượng khác:
Machine Translated by Google

10.12. Liệt kê các đối số 97

danh sách

__chính__ bức thư


0 'một'

1 'b'
delete_head t
2 'c'

Hình 10.5: Sơ đồ ngăn xếp.

>>> b [0] = 42
>>> a

[42, 2, 3]

Mặc dù hành vi này có thể hữu ích, nhưng nó rất dễ xảy ra lỗi. Nói chung, sẽ an toàn hơn để tránh răng
cưa khi bạn đang làm việc với các đối tượng có thể thay đổi.

Đối với các đối tượng không thể thay đổi như chuỗi, răng cưa không phải là vấn đề nhiều. Trong ví dụ này:

a = 'chuối'
b = 'chuối'

Hầu như không bao giờ tạo ra sự khác biệt cho dù a và b có tham chiếu đến cùng một chuỗi hay không.

10.12 Liệt kê các đối số

Khi bạn chuyển một danh sách cho một hàm, hàm sẽ nhận được một tham chiếu đến danh sách. Nếu hàm sửa
đổi danh sách, người gọi sẽ thấy sự thay đổi. Ví dụ: delete_head loại bỏ phần tử đầu tiên khỏi danh
sách:

def delete_head (t): del


t [0]

Đây là cách nó được sử dụng:

>>> chữ cái = ['a', 'b', 'c'] >>>


delete_head (chữ cái)
>>> thư

['b', 'c']

Tham số t và các ký tự biến là bí danh của cùng một đối tượng. Sơ đồ ngăn xếp có dạng như Hình 10.5.

Vì danh sách được chia sẻ bởi hai khung, tôi đã vẽ nó giữa chúng.

Điều quan trọng là phải phân biệt giữa các hoạt động sửa đổi danh sách và các hoạt động tạo ra danh
sách mới. Ví dụ: phương thức nối thêm sửa đổi một danh sách, nhưng toán tử + tạo một danh sách mới.

Đây là một ví dụ sử dụng append:

>>> t1 = [1, 2] >>>

t2 = t1.append (3) >>> t1

[1, 2, 3]
>>> t2
Không có
Machine Translated by Google

98 Chương 10. Danh sách

Giá trị trả về từ append là Không có.

Đây là một ví dụ sử dụng toán tử +:

>>> t3 = t1 + [4]
>>> t1

[1, 2, 3]
>>> t3

[1, 2, 3, 4]

Kết quả của toán tử là một danh sách mới, và danh sách ban đầu không thay đổi.

Sự khác biệt này rất quan trọng khi bạn viết các hàm được cho là để sửa đổi danh sách.
Ví dụ: chức năng này không xóa phần đầu của danh sách:

def bad_delete_head (t):


t = t [1:] # SAI LẦM!

Toán tử lát cắt tạo một danh sách mới và phép gán không tham chiếu đến nó, nhưng điều đó không ảnh hưởng
đến người gọi.

>>> t4 = [1, 2, 3] >>>


bad_delete_head (t4)
>>> t4

[1, 2, 3]

Ở đầu bad_delete_head, t và t4 tham chiếu đến cùng một danh sách. Ở cuối, t chỉ danh sách mới, nhưng t4
vẫn chỉ danh sách ban đầu, chưa sửa đổi.

Một cách thay thế là viết một hàm tạo và trả về một danh sách mới. Ví dụ: tail trả về tất cả trừ phần
tử đầu tiên của danh sách:

đuôi def (t):


trả về t [1:]

Chức năng này không sửa đổi danh sách ban đầu. Đây là cách nó được sử dụng:

>>> chữ cái = ['a', 'b', 'c'] >>> rest


= tail (chữ cái)
>>> phần còn lại

['b', 'c']

10.13 Gỡ lỗi
Việc sử dụng bất cẩn các danh sách (và các đối tượng có thể thay đổi khác) có thể dẫn đến việc gỡ lỗi trong nhiều

giờ. Dưới đây là một số cạm bẫy phổ biến và cách để tránh chúng:

1. Hầu hết các phương thức danh sách sửa đổi đối số và trả về Không có. Điều này ngược lại với các
phương thức chuỗi, trả về một chuỗi mới và để nguyên bản gốc.

Nếu bạn đã quen viết mã chuỗi như thế này:

word = word.strip ()

Thật hấp dẫn để viết mã danh sách như thế này:


Machine Translated by Google

10.13. Gỡ lỗi 99

t = t.sort () # SAI LẦM!

Bởi vì sắp xếp trả về Không có, thao tác tiếp theo bạn thực hiện với t có khả năng thất bại.

Trước khi sử dụng các phương pháp và toán tử danh sách, bạn nên đọc kỹ tài liệu đầy đủ và sau đó
kiểm tra chúng ở chế độ tương tác.

2. Chọn một thành ngữ và gắn bó với nó.

Một phần của vấn đề với các danh sách là có quá nhiều cách để thực hiện công việc. Đối với bài kiểm
tra, để xóa một phần tử khỏi danh sách, bạn có thể sử dụng pop, remove, del hoặc thậm chí là một
phần tử gán.

Để thêm một phần tử, bạn có thể sử dụng phương thức nối thêm hoặc toán tử +. Giả sử rằng t là một
danh sách và x là một phần tử danh sách, những điều này đúng:

t.append (x) t
= t + [x] t +
= [x]

Và những điều này là sai:

t.append ([x]) t # SAI LẦM!

= t.append (x) t + [x] # SAI LẦM!

# SAI LẦM!

t = t + x # SAI LẦM!

Hãy thử từng ví dụ này ở chế độ tương tác để đảm bảo bạn hiểu chúng làm gì. Lưu ý rằng chỉ cái cuối
cùng gây ra lỗi thời gian chạy; ba người còn lại là hợp pháp, nhưng họ làm điều sai trái.

3. Tạo bản sao để tránh răng cưa.

Nếu bạn muốn sử dụng một phương pháp như sắp xếp để sửa đổi đối số, nhưng bạn cũng cần giữ danh sách
ban đầu, bạn có thể tạo một bản sao.

>>> t = [3, 1, 2] >>>


t2 = t [:] >>> t2.sort

()
>>> t

[3, 1, 2]
>>> t2

[1, 2, 3]

Trong ví dụ này, bạn cũng có thể sử dụng hàm sắp xếp có sẵn, hàm này trả về một danh sách mới, được
sắp xếp và giữ nguyên bản gốc.

>>> t2 = đã sắp xếp (t)


>>> t

[3, 1, 2]
>>> t2

[1, 2, 3]
Machine Translated by Google

100 Chương 10. Danh sách

10.14 Danh sách thuật ngữ:

Một chuỗi các giá trị.

phần tử: Một trong các giá trị trong danh sách (hoặc chuỗi khác), còn được gọi là các mục.

danh sách lồng nhau: Danh sách là một phần tử của danh sách khác.

bộ tích lũy: Một biến được sử dụng trong một vòng lặp để cộng hoặc tích lũy một kết quả.

phép gán tăng cường: Một câu lệnh cập nhật giá trị của một biến bằng cách sử dụng một toán hạng
tor thích + =.

giảm: Một mẫu xử lý duyệt qua một trình tự và tích lũy các phần tử thành

một kết quả duy nhất.

bản đồ: Một mẫu xử lý duyệt qua một trình tự và thực hiện một thao tác trên mỗi
yếu tố.

bộ lọc: Một mẫu xử lý duyệt qua danh sách và chọn các phần tử đáp ứng một số
tiêu chuẩn.

object: Một cái gì đó mà một biến có thể tham chiếu đến. Một đối tượng có một kiểu và một giá trị.

tương đương: Có cùng giá trị.

giống nhau: Là cùng một đối tượng (ngụ ý là tương đương).

tham chiếu: Sự kết hợp giữa một biến và giá trị của nó.

aliasing: Một trường hợp mà hai hoặc nhiều biến tham chiếu đến cùng một đối tượng.

dấu phân tách: Một ký tự hoặc chuỗi được sử dụng để chỉ ra nơi mà một chuỗi sẽ được phân tách.

10.15 Bài tập

Bạn có thể tải xuống lời giải cho các bài tập này từ http://thinkpython2.com/code/ list_exercises.py.

Bài tập 10.1. Viết một hàm có tên là nested_sum lấy một danh sách các số nguyên và cộng các phần tử từ tất cả

các danh sách lồng nhau. Ví dụ: >>> t = [[1, 2], [3], [4, 5, 6]] >>> nested_sum (t)

21

Bài tập 10.2. Viết một hàm có tên là cumsum lấy một danh sách các số và trả về tổng giá trị âm cumu; tức là,

một danh sách mới trong đó phần tử thứ i là tổng của i + 1 phần tử đầu tiên từ danh sách ban đầu. Ví dụ: >>>

t = [1, 2, 3] >>> cumsum (t) [1, 3, 6]

Bài tập 10.3. Viết một hàm có tên là middle lấy một danh sách và trả về một danh sách mới chứa tất cả trừ phần

tử đầu tiên và cuối cùng. Ví dụ: >>> t = [1, 2, 3, 4] >>> middle (t) [2, 3]
Machine Translated by Google

10,15. Bài tập 101

Bài tập 10.4. Viết một hàm có tên là chop lấy một danh sách, sửa đổi nó bằng cách loại bỏ các phần tử đầu
tiên và cuối cùng và trả về None. Ví dụ:

>>> t = [1, 2, 3, 4] >>>

chop (t)
>>> t

[2, 3]

Bài tập 10.5. Viết một hàm được gọi là is_sorted lấy danh sách làm tham số và trả về True nếu danh sách
được sắp xếp theo thứ tự tăng dần và nếu không thì là False. Ví dụ: >>> is_sorted ([1, 2, 2])

Đúng

>>> is_sorted (['b', 'a'])


Sai

Bài tập 10.6. Hai từ là đảo ngữ nếu bạn có thể sắp xếp lại các chữ cái từ một để đánh vần từ kia.
Viết một hàm được gọi là is_anagram nhận hai chuỗi và trả về True nếu chúng là các ký tự đảo ngữ.
Bài tập 10.7. Viết một hàm có tên has_duplicates lấy một danh sách và trả về True nếu có bất kỳ phần tử
nào xuất hiện nhiều hơn một lần. Nó không nên sửa đổi danh sách ban đầu.
Bài tập 10.8. Bài tập này liên quan đến cái gọi là Nghịch lý Sinh nhật, bạn có thể đọc tại http: // vi.
wikipedia. org / wiki / Birthday_ nghịch lý.

Nếu có 23 học sinh trong lớp của bạn, thì khả năng hai người trong số họ có cùng ngày sinh nhật là bao nhiêu?

Bạn có thể ước tính xác suất này bằng cách tạo các mẫu ngẫu nhiên của 23 ngày sinh và kiểm tra các kết
quả trùng khớp. Gợi ý: bạn có thể tạo sinh nhật ngẫu nhiên bằng hàm randint trong mô-đun ngẫu nhiên.

Bạn có thể tải xuống giải pháp của tôi từ http: // thinkpython2. com / code / ngày sinh. py
Bài tập 10.9. Viết một hàm đọc tệp tin words.txt và tạo danh sách với một phần tử trên mỗi từ. Viết hai
phiên bản của hàm này, một phiên bản sử dụng phương thức append và một phiên bản khác sử dụng thành ngữ t
= t + [x]. Cái nào mất nhiều thời gian hơn để chạy? Tại sao?

Lời giải: http: // thinkpython2. com / code / wordlist. py

Bài tập 10.10. Để kiểm tra xem một từ có trong danh sách từ hay không, bạn có thể sử dụng toán tử in,
nhưng sẽ chậm vì nó tìm kiếm qua các từ theo thứ tự.

Bởi vì các từ theo thứ tự bảng chữ cái, chúng ta có thể tăng tốc độ mọi thứ bằng cách tìm kiếm phân đôi
(còn được gọi là tìm kiếm nhị phân), tương tự như những gì bạn làm khi tra một từ trong từ điển (sách,
không phải cấu trúc dữ liệu) . Bạn bắt đầu ở giữa và kiểm tra xem liệu từ bạn đang tìm có xuất hiện trước
từ ở giữa danh sách hay không. Nếu vậy, bạn tìm kiếm nửa đầu của danh sách theo cách tương tự. Nếu không,
bạn tìm kiếm nửa thứ hai.

Dù bằng cách nào, bạn cũng cắt một nửa không gian tìm kiếm còn lại. Nếu danh sách từ có 113.809 từ, bạn
sẽ mất khoảng 17 bước để tìm từ hoặc kết luận rằng từ đó không có ở đó.

Viết một hàm được gọi là in_bisect lấy một danh sách được sắp xếp và một giá trị đích và trả về True nếu
từ đó có trong danh sách và False nếu không có.

Hoặc bạn có thể đọc tài liệu về mô-đun bisect và sử dụng nó! Lời giải: http: // thinkpython2. com / code /
inlist. py

Bài tập 10.11. Hai từ là một "cặp đảo ngược" nếu mỗi từ là đảo ngược của từ kia. Viết chương trình tìm
tất cả các cặp từ đảo ngược trong danh sách từ. Lời giải: http: // thinkpython2. cặp com / code /
reverse_. py

Bài tập 10.12. Hai từ “interlock” nếu lấy các chữ cái xen kẽ nhau sẽ tạo thành một từ mới. Ví dụ: "shoe"
và "cold" kết hợp với nhau để tạo thành "schooled". Lời giải: http: //
Machine Translated by Google

102 Chương 10. Danh sách

thinkpython2. com / code / interlock. py Tín dụng: Bài tập này được lấy cảm hứng từ một ví dụ tại http: //

puzzlelers. tổ chức.

1. Viết chương trình tìm tất cả các cặp từ ghép vào nhau. Gợi ý: không liệt kê tất cả các cặp!

2. Bạn có thể tìm thấy bất kỳ từ nào được lồng vào nhau ba chiều; nghĩa là, mỗi chữ cái thứ ba tạo thành một

từ, bắt đầu từ chữ cái đầu tiên, thứ hai hoặc thứ ba?
Machine Translated by Google

chương 11

Từ điển

Chương này trình bày một loại cài sẵn khác được gọi là từ điển. Từ điển là một trong những tính năng tốt nhất

của Python; chúng là nền tảng của nhiều nhịp điệu thuật toán hiệu quả và thanh lịch.

11.1 Từ điển là một ánh xạ


Từ điển giống như một danh sách, nhưng tổng quát hơn. Trong danh sách, các chỉ số phải là số nguyên; trong

từ điển, chúng có thể là (gần như) bất kỳ loại nào.

Từ điển chứa một tập hợp các chỉ mục, được gọi là khóa và một tập hợp các giá trị. Mỗi khóa được liên kết

với một giá trị duy nhất. Sự kết hợp của một khóa và một giá trị được gọi là một cặp khóa-giá trị hoặc đôi
khi là một mục.

Trong ngôn ngữ toán học, từ điển biểu thị một ánh xạ từ khóa đến giá trị, vì vậy bạn cũng có thể nói rằng

mỗi khóa “ánh xạ tới” một giá trị. Ví dụ: chúng tôi sẽ xây dựng một từ điển ánh xạ từ các từ tiếng Anh sang

tiếng Tây Ban Nha, vì vậy các khóa và giá trị đều là chuỗi.

Hàm dict tạo một từ điển mới không có mục nào. Vì dict là tên của một hàm dựng sẵn, bạn nên tránh sử dụng nó

làm tên biến. >>> eng2sp = dict () >>> eng2sp {}

Dấu ngoặc vuông, {}, đại diện cho một từ điển trống. Để thêm các mục vào từ điển, bạn có thể sử dụng dấu

ngoặc vuông: >>> eng2sp ['one'] = 'una'

Dòng này tạo một mục ánh xạ từ khóa 'một' đến giá trị 'chưa'. Nếu chúng tôi in lại từ điển, chúng tôi sẽ thấy

một cặp khóa-giá trị có dấu hai chấm giữa khóa và giá trị: >>> eng2sp {'one': 'una'}

Định dạng đầu ra này cũng là một định dạng đầu vào. Ví dụ: bạn có thể tạo một từ điển mới với ba mục:
Machine Translated by Google

104 Chương 11. Từ điển

>>> eng2sp = {'one': 'una', 'hai': 'dos', 'ba': 'tres'}

Nhưng nếu bạn in eng2sp, bạn có thể ngạc nhiên:

>>> eng2sp
{'một': 'una', 'ba': 'tres', 'hai': 'dos'}

Thứ tự của các cặp khóa-giá trị có thể không giống nhau. Nếu bạn nhập cùng một ví dụ trên máy tính
của mình, bạn có thể nhận được một kết quả khác. Nói chung, thứ tự của các mục trong từ điển là không
thể đoán trước.

Nhưng đó không phải là vấn đề vì các phần tử của từ điển không bao giờ được lập chỉ mục bằng các chỉ
số inte. Thay vào đó, bạn sử dụng các phím để tra cứu các giá trị tương ứng: >>> eng2sp ['two'] 'dos'

Khóa 'hai' luôn ánh xạ tới giá trị 'dos' nên thứ tự của các mục không quan trọng.

Nếu khóa không có trong từ điển, bạn sẽ có một ngoại lệ: >>>

eng2sp ['four']
KeyError: 'bốn'

Chức năng len hoạt động trên từ điển; nó trả về số cặp khóa-giá trị:

>>> len (eng2sp) 3

Toán tử in cũng hoạt động trên từ điển; nó cho bạn biết liệu thứ gì đó có xuất hiện dưới dạng khóa
trong từ điển hay không (xuất hiện dưới dạng giá trị là không đủ tốt).

>>> 'một' trong eng2sp


ĐÚNG VẬY

>>> 'una' trong eng2sp


Sai

Để xem liệu một thứ gì đó có xuất hiện dưới dạng giá trị trong từ điển hay không, bạn có thể sử dụng các giá

trị của phương thức, giá trị này trả về một tập hợp các giá trị, sau đó sử dụng toán tử in:

>>> vals = eng2sp.values () >>>


'una' trong vals
ĐÚNG VẬY

Toán tử in sử dụng các thuật toán khác nhau cho danh sách và từ điển. Đối với danh sách, nó tìm kiếm
các phần tử của danh sách theo thứ tự, như trong Phần 8.6. Khi danh sách dài hơn, thời gian tìm kiếm
sẽ kéo dài hơn theo tỷ lệ thuận.

Từ điển Python sử dụng cấu trúc dữ liệu được gọi là bảng băm có một đặc tính đáng chú ý: toán tử in
mất khoảng thời gian như nhau cho dù có bao nhiêu mục trong từ điển. Tôi giải thích điều đó có thể
xảy ra như thế nào trong Phần B.4, nhưng lời giải thích có thể không có ý nghĩa cho đến khi bạn đọc
thêm một vài chương.

11.2 Từ điển như một tập hợp các bộ đếm

Giả sử bạn được cung cấp một chuỗi và bạn muốn đếm số lần mỗi chữ cái xuất hiện.
Có một số cách bạn có thể làm:
Machine Translated by Google

11.2. Từ điển như một bộ sưu tập các bộ đếm 105

1. Bạn có thể tạo 26 biến, một biến cho mỗi chữ cái trong bảng chữ cái. Sau đó, bạn có thể tra câu
chuỗi và, đối với mỗi ký tự, tăng bộ đếm tương ứng, proba bly bằng cách sử dụng một điều kiện có
chuỗi.

2. Bạn có thể tạo một danh sách với 26 phần tử. Sau đó, bạn có thể chuyển đổi từng ký tự thành một số
(sử dụng hàm ord được tích hợp sẵn), sử dụng số làm chỉ mục vào danh sách và tăng bộ đếm thích hợp.

3. Bạn có thể tạo một từ điển với các ký tự là khóa và bộ đếm là các giá trị nhập tương ứng. Lần đầu
tiên bạn nhìn thấy một ký tự, bạn sẽ thêm một mục vào từ điển.
Sau đó, bạn sẽ tăng giá trị của một mặt hàng hiện có.

Mỗi tùy chọn này thực hiện tính toán giống nhau, nhưng mỗi tùy chọn trong số họ thực hiện tính toán đó
theo một cách khác nhau.

Triển khai là một cách thực hiện một phép tính; một số triển khai tốt hơn những triển khai khác. Ví dụ,
một lợi thế của việc triển khai từ điển là chúng ta không phải biết trước những chữ cái nào xuất hiện
trong chuỗi và chúng ta chỉ phải nhường chỗ cho những chữ cái xuất hiện.

Đây là mã có thể trông như thế nào: def

histogram (s): d = dict ()

cho c trong s:
nếu c không có trong d:

d [c] = 1
khác:

d [c] + = 1
trở lại d

Tên của hàm là biểu đồ, là một thuật ngữ thống kê để chỉ tập hợp các bộ đếm (hoặc tần số).

Dòng đầu tiên của hàm tạo một từ điển trống. Vòng lặp for đi qua chuỗi.
Mỗi lần qua vòng lặp, nếu ký tự c không có trong từ điển, chúng tôi tạo một mục mới với khóa c và giá trị
ban đầu 1 (vì chúng tôi đã nhìn thấy ký tự này một lần). Nếu c đã có trong từ điển, chúng ta tăng d [c].

Đây là cách nó hoạt động:

>>> h = histogram ('brontosaurus') >>> h

{'a': 1, 'b': 1, 'o': 2, 'n': 1, 's': 2, 'r': 2, 'u': 2, 't': 1}

Biểu đồ chỉ ra rằng các chữ cái 'a' và 'b' xuất hiện một lần; 'o' xuất hiện hai lần và
Sớm.

Các từ điển có một phương thức gọi là get, lấy một khóa và một giá trị mặc định. Nếu khóa xuất hiện trong
từ điển, hàm get trả về giá trị tương ứng; nếu không thì nó trả về giá trị mặc định. Ví dụ: >>> h =
histogram ('a') >>> h

{'a': 1}

>>> h.get ('a', 0)


Machine Translated by Google

106 Chương 11. Từ điển

>>> h.get ('c', 0) 0

Như một bài tập, hãy sử dụng get để viết biểu đồ ngắn gọn hơn. Bạn sẽ có thể loại bỏ câu lệnh if.

11.3 Vòng lặp và từ điển

Nếu bạn sử dụng một từ điển trong câu lệnh for, nó sẽ duyệt qua các khóa của từ điển. Đối với bài
kiểm tra, print_hist in ra từng khoá và giá trị tương ứng: def print_hist (h): for c in h:

in (c, h [c])

Đây là kết quả đầu ra trông như thế

nào: >>> h = histogram ('parrot')


>>> print_hist (h) a 1

p 1
r 2
t 1
o 1

Một lần nữa, các phím không có thứ tự cụ thể. Để duyệt qua các phím theo thứ tự được sắp xếp, bạn có thể
sử dụng hàm tích hợp được sắp xếp:

>>> for key in sorted (h): print


... (key, h [key])
một 1

o 1

p 1
r 2
t 1

11.4 Tra cứu ngược

Cho từ điển d và khóa k, ta dễ dàng tìm được giá trị tương ứng v = d [k]. Thao tác này được gọi là
tra cứu.

Nhưng nếu bạn có v và bạn muốn tìm k? Bạn có hai vấn đề: thứ nhất, có thể có nhiều hơn một khóa
ánh xạ tới giá trị v. Tùy thuộc vào ứng dụng, bạn có thể chọn một khóa hoặc bạn có thể phải tạo
danh sách chứa tất cả chúng. Thứ hai, không có cú pháp đơn giản nào để thực hiện tra cứu ngược
lại; bạn phải tìm kiếm.

Đây là một hàm nhận một giá trị và trả về khóa đầu tiên ánh xạ đến giá trị đó: def

reverse_lookup (d, v): for k in d:

nếu d [k] == v:
trả lại k

nâng LookupError ()
Machine Translated by Google

11,5. Từ điển và danh sách 107

Chức năng này là một ví dụ khác của mẫu tìm kiếm, nhưng nó sử dụng một tính năng mà chúng tôi chưa từng
thấy trước đây, nêu lên. Câu lệnh tăng gây ra một ngoại lệ; trong trường hợp này, nó gây ra lỗi
LookupError, đây là một ngoại lệ được tích hợp sẵn được sử dụng để chỉ ra rằng thao tác tra cứu không thành công.

Nếu chúng ta đi đến cuối vòng lặp, điều đó có nghĩa là v không xuất hiện trong từ điển dưới dạng một giá trị, vì vậy

chúng ta đưa ra một ngoại lệ.

Đây là một ví dụ về tra cứu ngược thành công: >>> h =

histogram ('parrot') >>> key = reverse_lookup (h, 2) >>>


key 'r'

Và một điều không thành công:

>>> key = reverse_lookup (h, 3)


Traceback (cuộc gọi gần đây nhất cuối cùng):

Tệp "<stdin>", dòng 1, trong <module>

Tệp "<stdin>", dòng 5, trong reverse_lookup


LookupError

Hiệu ứng khi bạn tăng một ngoại lệ cũng giống như khi Python nâng một ngoại lệ: nó in ra một dấu vết và
một thông báo lỗi.

Khi bạn nêu ra một ngoại lệ, bạn có thể cung cấp một thông báo lỗi chi tiết như một đối số tùy chọn. Ví
dụ: >>> nâng LookupError ('giá trị không xuất hiện trong từ điển')

Traceback (lần gọi gần đây nhất): Tệp

"<stdin>", dòng 1, trong?

LookupError: giá trị không xuất hiện trong từ điển

Tra cứu ngược chậm hơn nhiều so với tra cứu về phía trước; nếu bạn phải làm điều đó thường xuyên, hoặc
nếu từ điển trở nên lớn, hiệu suất của chương trình của bạn sẽ bị ảnh hưởng.

11.5 Từ điển và danh sách

Danh sách có thể xuất hiện dưới dạng giá trị trong từ điển. Ví dụ, nếu bạn được cung cấp một từ điển ánh
xạ từ các chữ cái đến tần số, bạn có thể muốn đảo ngược nó; nghĩa là, tạo một từ điển ánh xạ từ tần số
sang chữ cái. Vì có thể có một số chữ cái có cùng tần suất, mỗi giá trị trong từ điển đảo ngược phải là
một danh sách các chữ cái.

Đây là một hàm đảo ngược từ điển: def invert_dict

(d): inverse = dict () cho khóa trong d: val = d


[key] nếu val không inverse:

inverse [val] = [key] else:

inverse [val] .append (key)


return inverse
Machine Translated by Google

108 Chương 11. Từ điển

mệnh lệnh mệnh lệnh danh sách

lịch sử 'một' 1 inv 1 0 'một'

'P' 1 1 'P'

'r' 2 2 't'

't' 1 3 'o'

'o' 1
danh sách

2 0 'r'

Hình 11.1: Biểu đồ trạng thái.

Mỗi lần qua vòng lặp, key nhận được một khóa từ d và val nhận giá trị tương ứng.
Nếu val không nghịch đảo, điều đó có nghĩa là chúng ta chưa từng thấy nó trước đây, vì vậy chúng ta tạo một

mục mới và khởi tạo nó bằng một singleton (một danh sách chứa một phần tử duy nhất). Nếu không, chúng tôi đã

thấy giá trị này trước đây, vì vậy chúng tôi thêm khóa tương ứng vào danh sách.

Đây là một ví dụ: >>>

hist = histogram ('parrot') >>> hist

{'a': 1, 'p': 1, 'r': 2, 't': 1, 'o': 1} >>> inverse =


invert_dict (hist)
>>> nghịch đảo

{1: ['a', 'p', 't', 'o'], 2: ['r']}

Hình 11.1 là một biểu đồ trạng thái hiển thị lịch sử và nghịch đảo. Từ điển được biểu diễn dưới dạng một hộp

với kiểu dict phía trên và các cặp khóa-giá trị bên trong. Nếu các giá trị là số nguyên, phao hoặc chuỗi,

tôi vẽ chúng bên trong hộp, nhưng tôi thường vẽ danh sách bên ngoài hộp, chỉ để giữ cho sơ đồ đơn giản.

Danh sách có thể là giá trị trong từ điển, như ví dụ này cho thấy, nhưng chúng không thể là khóa. Đây là

những gì sẽ xảy ra nếu bạn thử: >>> t = [1, 2, 3] >>> d = dict () >>> d [t] = 'oops'

Traceback (cuộc gọi gần đây nhất cuối cùng):

Tệp "<stdin>", dòng 1, trong?

TypeError: các đối tượng danh sách không thể băm được

Tôi đã đề cập trước đó rằng một từ điển được triển khai bằng cách sử dụng bảng băm và điều đó có nghĩa là

các khóa phải có thể băm được.

Hàm băm là một hàm nhận một giá trị (thuộc bất kỳ loại nào) và trả về một số nguyên. Từ điển sử dụng các số

nguyên này, được gọi là giá trị băm, để lưu trữ và tra cứu các cặp khóa-giá trị.

Hệ thống này hoạt động tốt nếu các phím là bất biến. Nhưng nếu các khóa có thể thay đổi được, như danh sách,

thì điều tồi tệ sẽ xảy ra. Ví dụ: khi bạn tạo một cặp khóa-giá trị, Python sẽ băm khóa và lưu trữ nó ở vị

trí tương ứng. Nếu bạn sửa đổi khóa và sau đó băm lại, nó sẽ chuyển đến một vị trí khác. Trong trường hợp

đó, bạn có thể có hai mục nhập cho cùng một khóa hoặc bạn có thể không tìm thấy khóa. Dù bằng cách nào, từ

điển sẽ không hoạt động chính xác.

Đó là lý do tại sao các khóa phải có thể băm được và tại sao các loại có thể thay đổi như danh sách thì không.

Cách đơn giản nhất để khắc phục hạn chế này là sử dụng các bộ giá trị, chúng ta sẽ thấy trong chương tiếp theo.
Machine Translated by Google

11,6. Bản ghi nhớ 109

fibonacci
N 4

fibonacci fibonacci
N 3 N 2

fibonacci fibonacci fibonacci fibonacci


N 2 N 1 N 1 N 0

fibonacci fibonacci
N 1 N 0

Hình 11.2: Đồ thị cuộc gọi.

Vì từ điển có thể thay đổi, chúng không thể được sử dụng làm khóa, nhưng chúng có thể được sử dụng làm giá trị.

11.6 Bản ghi nhớ

Nếu bạn đã chơi với hàm fibonacci từ Phần 6.7, bạn có thể nhận thấy rằng đối số bạn cung cấp càng lớn, thì hàm

càng mất nhiều thời gian để chạy. Hơn nữa, thời gian chạy tăng lên nhanh chóng.

Để hiểu lý do tại sao, hãy xem Hình 11.2, cho thấy biểu đồ cuộc gọi cho fibonacci với
n = 4:

Biểu đồ cuộc gọi cho thấy một tập hợp các khung hàm, với các đường nối mỗi khung với các khung của các hàm mà nó

gọi. Ở trên cùng của đồ thị, fibonacci với n = 4 gọi fibonacci với n = 3 và n = 2. Lần lượt, fibonacci với n = 3
gọi fibonacci với n = 2 và n = 1. Và như thế.

Đếm xem fibonacci (0) và fibonacci (1) được gọi bao nhiêu lần. Đây là một giải pháp không hiệu quả cho vấn đề và

nó trở nên tồi tệ hơn khi tranh cãi ngày càng lớn.

Một giải pháp là theo dõi các giá trị đã được tính toán bằng cách lưu trữ chúng trong từ điển. Một giá trị đã

tính toán trước đó được lưu trữ để sử dụng sau này được gọi là bản ghi nhớ.
Đây là phiên bản fibonacci "được ghi nhớ":

đã biết = {0: 0, 1: 1}

def fibonacci (n): nếu n


đã biết:

trả về đã biết [n]

res = fibonacci (n-1) + fibonacci (n-2) đã biết [n] =

res
trả lại res

được biết đến là một từ điển theo dõi các số Fibonacci mà chúng ta đã biết. Nó bắt đầu với hai mục: 0 bản đồ tới

0 và 1 bản đồ tới 1.
Machine Translated by Google

110 Chương 11. Từ điển

Bất cứ khi nào fibonacci được gọi, nó sẽ kiểm tra xem đã biết. Nếu kết quả đã có, nó có thể trả về ngay
lập tức. Nếu không, nó phải tính giá trị mới, thêm nó vào từ điển và trả về.

Nếu bạn chạy phiên bản fibonacci này và so sánh với bản gốc, bạn sẽ thấy rằng nó nhanh hơn nhiều.

11.7 Biến toàn cục

Trong ví dụ trước, known được tạo bên ngoài hàm, vì vậy nó thuộc về khung đặc biệt được gọi là __main__.
Các biến trong __main__ đôi khi được gọi là toàn cục vì chúng có thể được truy cập từ bất kỳ hàm nào.
Không giống như các biến cục bộ, biến mất khi hàm của chúng kết thúc, các biến toàn cục vẫn tồn tại từ
lệnh gọi hàm này sang lệnh gọi tiếp theo.

Người ta thường sử dụng các biến toàn cục cho các cờ; nghĩa là, các biến boolean cho biết (“cờ”) liệu một
điều kiện có đúng hay không. Ví dụ: một số chương trình sử dụng cờ có tên là verbose để kiểm soát mức độ
chi tiết trong đầu ra:

verbose = Đúng

def example1 (): if


verbose:

print ('Đang chạy example1')

Nếu bạn cố gắng gán lại một biến toàn cục, bạn có thể ngạc nhiên. Ví dụ sau được cho là để theo dõi xem
hàm đã được gọi hay chưa:

be_called = Sai

def example2 ():


be_called = True # SAI LẦM

Nhưng nếu bạn chạy nó, bạn sẽ thấy rằng giá trị của was_called không thay đổi. Vấn đề là example2 tạo một
biến cục bộ mới có tên là was_called. Biến cục bộ biến mất khi hàm kết thúc và không ảnh hưởng đến biến
toàn cục.

Để gán lại một biến toàn cục bên trong một hàm, bạn phải khai báo biến toàn cục trước khi sử dụng nó:

be_called = Sai

def example2 ():


toàn cầu được_called
được_called = True

Câu lệnh toàn cục nói với trình thông dịch đại loại như, “Trong hàm này, khi tôi nói được_called, ý tôi
là biến toàn cục; không tạo một địa chỉ cục bộ. "

Đây là một ví dụ cố gắng cập nhật một biến toàn cục:

đếm = 0

def example3 ():


count = count + 1 # SAI LẦM

Nếu bạn chạy nó, bạn sẽ nhận được:


Machine Translated by Google

11,8. Gỡ lỗi 111

UnboundLocalError: biến cục bộ 'đếm' được tham chiếu trước khi gán

Python giả định rằng số đếm là cục bộ và theo giả định đó, bạn đang đọc nó trước khi viết nó. Giải
pháp, một lần nữa, là khai báo số toàn cục.

def example3 (): số


đếm toàn cục +
= 1

Nếu một biến toàn cục tham chiếu đến một giá trị có thể thay đổi, bạn có thể sửa đổi giá trị mà không cần
khai báo biến:

đã biết = {0: 0, 1: 1}

def example4 ():


known [2] = 1

Vì vậy, bạn có thể thêm, xóa và thay thế các phần tử của danh sách toàn cục hoặc từ điển, nhưng nếu
bạn muốn gán lại biến, bạn phải khai báo nó:

def example5 ():


global known
known = dict ()

Các biến toàn cục có thể hữu ích, nhưng nếu bạn có nhiều trong số chúng và bạn sửa đổi chúng liên
tục, chúng có thể làm cho các chương trình khó gỡ lỗi.

11.8 Gỡ lỗi
Khi bạn làm việc với các tập dữ liệu lớn hơn, việc gỡ lỗi bằng cách in và kiểm tra đầu ra bằng tay
có thể trở nên khó sử dụng. Dưới đây là một số gợi ý để gỡ lỗi tập dữ liệu lớn:

Thu nhỏ đầu vào: Nếu có thể, hãy giảm kích thước của tập dữ liệu. Ví dụ: nếu pro gram đọc một tệp
văn bản, hãy bắt đầu chỉ với 10 dòng đầu tiên hoặc với ví dụ nhỏ nhất mà bạn có thể tìm thấy.
Bạn có thể tự chỉnh sửa các tệp hoặc (tốt hơn) sửa đổi chương trình để nó chỉ đọc n dòng đầu
tiên.

Nếu có lỗi, bạn có thể giảm n đến giá trị nhỏ nhất của lỗi, sau đó tăng dần khi bạn tìm và sửa
lỗi.

Kiểm tra tóm tắt và loại: Thay vì in và kiểm tra toàn bộ tập dữ liệu, hãy cân nhắc in tóm tắt dữ
liệu: ví dụ: số mục trong từ điển hoặc tổng danh sách số.

Nguyên nhân phổ biến của lỗi thời gian chạy là một giá trị không đúng loại. Để gỡ lỗi loại lỗi
này, chỉ cần in loại giá trị là đủ.

Viết tự kiểm tra: Đôi khi bạn có thể viết mã để kiểm tra lỗi tự động. Ví dụ: nếu bạn đang tính giá
trị trung bình của một danh sách các số, bạn có thể kiểm tra xem kết quả không lớn hơn phần tử
lớn nhất trong danh sách hay nhỏ hơn phần tử nhỏ nhất.
Đây được gọi là “kiểm tra sự tỉnh táo” vì nó phát hiện ra những kết quả “điên rồ”.

Một loại kiểm tra khác so sánh kết quả của hai phép tính khác nhau để xem chúng có nhất quán
hay không. Đây được gọi là “kiểm tra tính nhất quán”.
Machine Translated by Google

112 Chương 11. Từ điển

Định dạng đầu ra: Định dạng đầu ra gỡ lỗi có thể giúp phát hiện lỗi dễ dàng hơn. Chúng ta đã thấy một ví dụ

trong Phần 6.9. Một công cụ khác mà bạn có thể thấy hữu ích là pprint mod ule, cung cấp chức năng

pprint hiển thị các loại tích hợp sẵn ở định dạng dễ đọc hơn cho con người (pprint là viết tắt của

“chữ in đẹp”).

Một lần nữa, thời gian bạn dành cho việc xây dựng giàn giáo có thể làm giảm thời gian bạn dành cho việc gỡ lỗi.

11.9 Bảng chú giải thuật ngữ

ánh xạ: Mối quan hệ trong đó mỗi phần tử của một tập hợp tương ứng với một phần tử của
bộ khác.

từ điển: Ánh xạ từ các khóa đến các giá trị tương ứng của chúng.

cặp khóa-giá trị: Biểu diễn ánh xạ từ khóa đến giá trị.

item: Trong từ điển, một tên khác cho cặp khóa-giá trị.

key: Một đối tượng xuất hiện trong từ điển dưới dạng phần đầu tiên của cặp khóa-giá trị.

value: Đối tượng xuất hiện trong từ điển dưới dạng phần thứ hai của cặp khóa-giá trị. Đây là

cụ thể hơn việc sử dụng từ “giá trị” trước đây của chúng ta.

thực hiện: Một cách thực hiện một phép tính.

hashtable: Thuật toán được sử dụng để triển khai từ điển Python.

Hàm băm: Một hàm được sử dụng bởi bảng băm để tính toán vị trí cho một khóa.

có thể băm : Một loại có chức năng băm. Các kiểu bất biến như số nguyên, số nổi và chuỗi có thể băm được;

các loại có thể thay đổi như danh sách và từ điển thì không.

tra cứu: Thao tác từ điển lấy một khóa và tìm giá trị tương ứng.

tra cứu ngược: Thao tác từ điển nhận một giá trị và tìm một hoặc nhiều khóa

bản đồ đến nó.

raise statement: Một tuyên bố (cố ý) nêu ra một ngoại lệ.

singleton: Một danh sách (hoặc chuỗi khác) với một phần tử duy nhất.

đồ thị cuộc gọi: Một biểu đồ hiển thị mọi khung hình được tạo trong quá trình thực thi chương trình,
với một mũi tên từ mỗi người gọi đến mỗi người gọi.

memo: Một giá trị tính toán được lưu trữ để tránh tính toán không cần thiết trong tương lai.

biến toàn cục: Một biến được định nghĩa bên ngoài một hàm. Các biến toàn cục có thể được truy cập

từ bất kỳ chức năng nào.

global statement: Một câu lệnh khai báo một tên biến toàn cục.

cờ: Một biến boolean được sử dụng để chỉ ra liệu một điều kiện có đúng hay không.

khai báo: Một câu lệnh giống như toàn cục cho trình thông dịch biết điều gì đó về một biến.
Machine Translated by Google

Ngày 11,10. Bài tập 113

11.10 Bài tập

Bài tập 11.1. Viết một hàm đọc các từ trong words.txt và lưu trữ chúng dưới dạng khóa trong từ điển. Nó không

quan trọng các giá trị là gì. Sau đó, bạn có thể sử dụng toán tử in như một cách nhanh chóng để kiểm tra xem

một chuỗi có trong từ điển hay không.

Nếu bạn đã làm Bài tập 10.10, bạn có thể so sánh tốc độ thực hiện này với danh sách trong toán tử và tìm kiếm
phân giác.

Bài tập 11.2. Đọc tài liệu về phương thức từ điển setdefault và sử dụng nó để viết phiên bản invert_dict ngắn

gọn hơn. Lời giải: http: // thinkpython2. com / code / invert_ dict. py

Bài tập 11.3. Ghi nhớ hàm Ackermann từ Bài tập 6.2 và xem liệu việc ghi nhớ có giúp đánh giá hàm với các đối

số lớn hơn hay không. Gợi ý: không. Lời giải: http: // thinkpython2. com / code / ackermann_ memo. py

Bài tập 11.4. Nếu bạn đã làm Bài tập 10.7, bạn đã có một hàm có tên has_duplicates lấy một danh sách làm tham

số và trả về True nếu có bất kỳ đối tượng nào xuất hiện nhiều hơn một lần trong danh sách.

Sử dụng từ điển để viết phiên bản has_duplicates nhanh hơn, đơn giản hơn. Lời giải: http: // thinkpython2.
com / code / has_ trùng lặp. py

Bài tập 11.5. Hai từ là “xoay các cặp” nếu bạn có thể xoay một trong số chúng và lấy cặp kia (xem xoay_word
trong Bài tập 8.5).

Viết chương trình đọc danh sách từ và tìm tất cả các cặp xoay. Lời giải: http: // thinkpython2. cặp com /
code / xoay_. py

Bài tập 11.6. Đây là một câu đố khác từ Car Talk (http: // www. Cartalk. Com / content / puzzlelers ):

Nó được gửi bởi một người tên Dan O'Leary. Gần đây, ông đã tìm ra một từ phổ biến gồm một âm

tiết, năm chữ cái có tính chất độc đáo sau đây. Khi bạn loại bỏ chữ cái đầu tiên, các chữ cái còn

lại tạo thành một từ đồng âm của từ gốc, đó là một từ phát âm giống hệt nhau. Thay thế chữ cái

đầu tiên, nghĩa là, đặt nó trở lại và loại bỏ chữ cái thứ hai và kết quả là một từ đồng âm khác

của từ gốc. Và câu hỏi là, từ là gì?

Bây giờ tôi sẽ cung cấp cho bạn một ví dụ không hoạt động. Hãy nhìn vào từ gồm năm chữ cái,

'wrack.' WRACK, bạn biết là bạn thích 'kết thúc với nỗi đau.' Nếu tôi xóa chữ cái đầu tiên, tôi

chỉ còn lại một từ gồm bốn chữ cái, 'RACK.' Như trong câu 'Chúa ơi, bạn có thấy cái giá trên cái

đồng đó không! Nó phải là một con số chín! ' Đó là một từ đồng âm hoàn hảo. Nếu bạn đặt lại chữ

'w' và bỏ chữ 'r, thay vào đó, bạn sẽ chỉ còn lại từ' wack ', đây là một từ thực sự, nó không

phải là từ đồng âm của hai từ còn lại.

Tuy nhiên, có ít nhất một từ mà Dan và chúng tôi biết, sẽ tạo ra hai từ đồng âm nếu bạn loại bỏ

một trong hai chữ cái đầu tiên để tạo thành hai từ bốn chữ cái mới. Câu hỏi là, từ là gì?

Bạn có thể sử dụng từ điển từ Bài tập 11.1 để kiểm tra xem một chuỗi có trong danh sách từ hay không.

Để kiểm tra xem hai từ có phải là từ đồng âm hay không, bạn có thể sử dụng Từ điển phát âm CMU. Bạn có thể tải

xuống từ http: // www. lời nói. cs. cmu. edu / cgi-bin / cmudict hoặc từ http: // thinkpython2. com / code /

c06d và bạn cũng có thể tải xuống http: // thinkpython2. com / code / phát âm. py, cung cấp một hàm có tên
read_dictionary đọc từ điển phát âm
âm và trả của
chính về từ điển Python ánh xạ từ mỗi từ thành một chuỗi mô tả cách phát
nó.
Machine Translated by Google

114 Chương 11. Từ điển

Viết chương trình liệt kê tất cả các từ giải được Puzzler. Lời giải: http: // thinkpython2.
com / code / homophone. py
Machine Translated by Google

Chương 12

Tuples

Chương này trình bày một kiểu tích hợp nữa, bộ tuple, sau đó cho biết cách danh sách, bộ từ điển và bộ mã
hoạt động cùng nhau. Tôi cũng trình bày một tính năng hữu ích cho danh sách đối số có độ dài thay đổi, các
toán tử tập hợp và phân tán.

Một lưu ý: không có sự thống nhất về cách phát âm "tuple". Một số người nói "tuh ple", đồng âm với "dẻo
dai". Nhưng trong ngữ cảnh của lập trình, hầu hết mọi người đều nói "too-ple", vần với "quadruple".

12.1 Tuples là bất biến


Một bộ giá trị là một chuỗi các giá trị. Các giá trị có thể là bất kỳ kiểu nào và chúng được lập chỉ mục
bởi các số nguyên, vì vậy về mặt đó, các bộ giá trị rất giống danh sách. Sự khác biệt quan trọng là bộ giá trị
là bất biến.

Về mặt cú pháp, một tuple là một danh sách các giá trị được phân tách bằng dấu phẩy:

>>> t = 'a', 'b', 'c', 'd', 'e'

Mặc dù không cần thiết, người ta thường đặt các bộ giá trị trong dấu ngoặc đơn: >>> t =

('a', 'b', 'c', 'd', 'e')

Để tạo một bộ giá trị với một phần tử duy nhất, bạn phải bao gồm dấu phẩy cuối cùng: >>>

t1 = 'a', >>> type (t1) <class 'tuple'>

Giá trị trong ngoặc đơn không phải là một

bộ giá trị: >>> t2 = ('a') >>> type (t2)

<class 'str'>

Một cách khác để tạo một tuple là tuple chức năng được tích hợp sẵn. Không có đối số, nó tạo ra một bộ giá
trị trống: >>> t = tuple ()

>>> t

()
Machine Translated by Google

116 Chương 12. Tuples

Nếu đối số là một chuỗi (chuỗi, danh sách hoặc bộ), kết quả là một bộ với các phần tử của chuỗi:

>>> t = tuple ('lupins')


>>> t

('l', 'u', 'p', 'i', 'n', 's')

Vì tuple là tên của một hàm tích hợp, bạn nên tránh sử dụng nó như một biến
Tên.

Hầu hết các toán tử danh sách cũng hoạt động trên các bộ giá trị. Toán tử ngoặc chỉ mục một phần tử:

>>> t = ('a', 'b', 'c', 'd', 'e') >>> t [0]

'một'

Và toán tử lát cắt chọn một loạt các phần tử.

>>> t [1: 3]
('b', 'c')

Nhưng nếu bạn cố gắng sửa đổi một trong các phần tử của bộ tuple, bạn sẽ gặp lỗi:

>>> t [0] = 'A'

TypeError: đối tượng không hỗ trợ gán mục

Bởi vì các bộ giá trị là bất biến, bạn không thể sửa đổi các phần tử. Nhưng bạn có thể thay thế một bộ giá

trị này bằng một bộ giá trị khác: >>> t = ('A',) + t [1:]

>>> t

('A', 'b', 'c', 'd', 'e')

Câu lệnh này tạo một tuple mới và sau đó làm cho t tham chiếu đến nó.

Các toán tử quan hệ làm việc với các bộ giá trị và các chuỗi khác; Python bắt đầu bằng cách so sánh
phần tử đầu tiên từ mỗi chuỗi. Nếu chúng bằng nhau, nó sẽ chuyển sang các phần tử tiếp theo, v.v.,
cho đến khi nó tìm thấy các phần tử khác nhau. Các yếu tố tiếp theo không được xem xét (ngay cả khi
chúng thực sự lớn).

>>> (0, 1, 2) <(0, 3, 4)


ĐÚNG VẬY

>>> (0, 1, 2000000) <(0, 3, 4)


ĐÚNG VẬY

12.2 Chuyển nhượng Tuple

Việc hoán đổi giá trị của hai biến thường rất hữu ích. Với các phép gán thông thường, bạn phải sử
dụng một biến tạm thời. Ví dụ, để hoán đổi a và b:

>>> temp = a
>>> a = b

>>> b = tạm thời

Giải pháp này là cồng kềnh; phép gán tuple thanh lịch hơn:

>>> a, b = b, a
Machine Translated by Google

12.3. Tuples dưới dạng giá trị trả về 117

Phía bên trái là một loạt các biến; phía bên phải là một loạt các biểu thức. Mỗi giá trị được gán
cho biến tương ứng của nó. Tất cả các biểu thức ở phía bên phải được đánh giá trước bất kỳ phép
gán nào.

Số biến ở bên trái và số giá trị ở bên phải phải là


tương tự:

>>> a, b = 1, 2, 3
ValueError: quá nhiều giá trị để giải nén

Tổng quát hơn, phía bên phải có thể là bất kỳ loại chuỗi nào (chuỗi, danh sách hoặc tuple). Đối với bài
kiểm tra, để tách một địa chỉ email thành một tên người dùng và một miền, bạn có thể viết:

>>> addr = 'monty@python.org' >>>


uname, domain = addr.split ('@')

Giá trị trả về từ tách là một danh sách có hai phần tử; phần tử đầu tiên được gán cho uname, phần
tử thứ hai cho miền.

>>> uname

miền
'monty' >>>

'python.org'

12.3 Tuples dưới dạng giá trị trả về

Nói một cách chính xác, một hàm chỉ có thể trả về một giá trị, nhưng nếu giá trị là một bộ giá
trị, thì tác động giống như trả về nhiều giá trị. Ví dụ: nếu bạn muốn chia hai số nguyên và tính
thương và dư, thì việc tính x // y rồi đến x% y là không hiệu quả. Tốt hơn là nên tính toán cả hai
cùng một lúc.

Hàm divmod tích hợp sẵn nhận hai đối số và trả về một bộ giá trị gồm hai giá trị, thương và phần
dư. Bạn có thể lưu trữ kết quả dưới dạng một tuple:

>>> t = divmod (7, 3)


>>> t

(2, 1)

Hoặc sử dụng phép gán tuple để lưu trữ các phần tử một cách riêng biệt:

>>> quot, rem = divmod (7, 3) >>>


quot 2

>>> rem
1

Đây là một ví dụ về một hàm trả về một bộ giá trị:

def min_max (t):


trả về min (t), max (t)

max và min là các hàm tích hợp để tìm các phần tử lớn nhất và nhỏ nhất của một dãy. min_max tính
toán cả hai và trả về một bộ giá trị của hai giá trị.
Machine Translated by Google

118 Chương 12. Tuples

12.4 Bộ giá trị đối số có độ dài thay đổi

Các hàm có thể nhận một số đối số thay đổi. Tên tham số bắt đầu bằng * tập hợp các đối số thành một bộ
giá trị. Ví dụ: printall lấy bất kỳ số lượng đối số nào và in chúng:

def printall (* args):


print (args)

Tham số tập hợp có thể có bất kỳ tên nào bạn thích, nhưng args là thông thường. Đây là cách hoạt động
của hàm:

>>> printall (1, 2.0, '3') (1,


2.0, '3')

Phần bổ sung của tập hợp là phân tán. Nếu bạn có một chuỗi giá trị và bạn muốn chuyển nó cho một hàm
dưới dạng nhiều đối số, bạn có thể sử dụng toán tử *. Ví dụ, divmod nhận chính xác hai đối số; nó không
hoạt động với một tuple:

>>> t = (7, 3) >>>


divmod (t)

TypeError: divmod mong đợi 2 đối số, có 1

Nhưng nếu bạn phân tán tuple, nó sẽ hoạt động:

>>> divmod (* t)
(2, 1)

Nhiều hàm dựng sẵn sử dụng bộ giá trị đối số có độ dài thay đổi. Ví dụ: max và min có thể nhận bất kỳ
số lượng đối số nào:

>>> tối đa (1, 2, 3)


3

Nhưng tổng thì không.

>>> tổng (1, 2, 3)

TypeError: tổng được mong đợi nhiều nhất là 2 đối số, có 3

Như một bài tập, hãy viết một hàm gọi là sum_all nhận bất kỳ số lượng đối số nào và trả về tổng của
chúng.

12.5 Danh sách và bộ giá trị

zip là một chức năng tích hợp có hai hoặc nhiều chuỗi và xen kẽ giữa chúng. Tên của hàm đề cập đến một
dây kéo, đan xen giữa hai hàng răng.

Ví dụ này nén một chuỗi và một danh sách:

>>> s = 'abc'

>>> t = [0, 1, 2] >>>

zip (s, t) <đối tượng


zip tại 0x7f7d0a9e7c48>

Kết quả là một đối tượng zip biết cách lặp qua các cặp. Việc sử dụng zip phổ biến nhất là trong vòng
lặp for:
Machine Translated by Google

12,5. Danh sách và bộ giá trị 119

>>> cho cặp trong zip (s, t):


... print (cặp)
...
('a', 0)
('b', 1)
('c', 2)

Đối tượng zip là một loại trình lặp, là bất kỳ đối tượng nào lặp qua một chuỗi.
Trình lặp tương tự như danh sách theo một số cách, nhưng không giống như danh sách, bạn không thể sử dụng chỉ mục
để chọn một phần tử từ trình vòng lặp.

Nếu bạn muốn sử dụng các toán tử và phương thức danh sách, bạn có thể sử dụng một đối tượng zip để

tạo danh sách: >>> list (zip (s, t)) [('a', 0), ('b', 1), ('c', 2)]

Kết quả là một danh sách các bộ giá trị; trong ví dụ này, mỗi bộ chứa một ký tự từ chuỗi và phần
tử tương ứng từ danh sách.

Nếu các dãy không cùng độ dài, kết quả có độ dài của dãy ngắn hơn. >>> danh sách (zip

('Anne', 'Elk'))
[('A', 'E'), ('n', 'l'), ('n', 'k')]

Bạn có thể sử dụng phép gán bộ giá trị trong vòng lặp for để duyệt qua

danh sách các bộ giá trị: t = [('a', 0), ('b', 1), ('c', 2)] cho chữ cái,
số trong t: in (số, chữ cái)

Mỗi lần qua vòng lặp, Python sẽ chọn bộ giá trị tiếp theo trong danh sách và gán các phần mười
cho chữ cái và số. Đầu ra của vòng lặp này là:
0 a
1 b
2 c

Nếu bạn kết hợp phép gán zip, for và tuple, bạn sẽ có được một thành ngữ hữu ích để duyệt hai
(hoặc nhiều) chuỗi cùng một lúc. Ví dụ: has_match nhận hai chuỗi, t1 và t2, và trả về True nếu
có một chỉ số i sao cho t1 [i] == t2 [i]:

def has_match (t1, t2): for


x, y in zip (t1, t2): if x ==
y: return True

trả về Sai

Nếu bạn cần duyệt qua các phần tử của một chuỗi và các chỉ số của chúng, bạn có thể sử dụng hàm
tích hợp sẵn:

for index, element in enumerate ('abc'): print


(index, element)

Kết quả từ enumerate là một đối tượng liệt kê, nó lặp lại một chuỗi các cặp; mỗi cặp chứa một
chỉ số (bắt đầu từ 0) và một phần tử từ dãy đã cho. Trong ví dụ này, đầu ra là

0 a
1 b
2 c

Lại.
Machine Translated by Google

120 Chương 12. Tuples

12.6 Từ điển và bộ giá trị

Từ điển có một phương thức được gọi là các mục trả về một chuỗi các bộ giá trị, trong đó mỗi bộ
giá trị là một cặp khóa-giá trị. >>> d = {'a': 0, 'b': 1, 'c': 2} >>> t = d.items ()

>>> t

dict_items ([('c', 2), ('a', 0), ('b', 1)])

Kết quả là một đối tượng dict_items, là một trình lặp lặp lại các cặp khóa-giá trị. Bạn có thể
sử dụng nó trong một vòng lặp for như sau: >>> for key, value trong d.items (): print (key, value)

...
...
c 2
một 0

b 1

Như bạn mong đợi từ một từ điển, các mục không có thứ tự cụ thể.

Đi theo hướng khác, bạn có thể sử dụng danh sách các bộ giá trị để khởi tạo từ điển mới:
>>> t = [('a', 0), ('c', 2), ('b', 1)]> >> d = dict (t)

>>> d

{'a': 0, 'c': 2, 'b': 1}

Kết hợp dict với zip mang lại một cách ngắn gọn để tạo từ điển: >>> d =

dict (zip ('abc', range (3))) >>> d

{'a': 0, 'c': 2, 'b': 1}

Bản cập nhật phương pháp từ điển cũng lấy một danh sách các bộ giá trị và thêm chúng, dưới dạng các cặp khóa-

giá trị, vào một từ điển hiện có.

Người ta thường sử dụng các bộ giá trị làm khóa trong từ điển (chủ yếu vì bạn không thể sử dụng
danh sách). Ví dụ, một danh bạ điện thoại có thể ánh xạ từ các cặp họ, tên thành các số điện
thoại. Giả sử rằng chúng ta đã xác định cuối cùng, đầu tiên và số, chúng ta có thể viết:

directory [last, first] = number Biểu thức trong ngoặc là một bộ giá trị. Chúng ta có thể sử

dụng phép gán tuple để đi qua đoạn tĩnh này. cho cuối cùng, đầu tiên trong thư mục: print (đầu
tiên, cuối cùng, thư mục [cuối cùng, đầu tiên])

Vòng lặp này đi qua các khóa trong thư mục, đó là các bộ giá trị. Nó gán các phần tử của mỗi bộ
vào cuối và đầu tiên, sau đó in tên và số điện thoại tương ứng.

Có hai cách để biểu diễn các bộ giá trị trong một biểu đồ trạng thái. Phiên bản chi tiết hơn
hiển thị các chỉ số và phần tử giống như chúng xuất hiện trong danh sách. Ví dụ, tuple ('Cleese',
'John') sẽ xuất hiện như trong Hình 12.1.

Nhưng trong một sơ đồ lớn hơn, bạn có thể muốn bỏ qua các chi tiết. Ví dụ, một sơ đồ của danh bạ
điện thoại có thể xuất hiện như trong Hình 12.2.

Ở đây, các bộ giá trị được hiển thị bằng cách sử dụng cú pháp Python dưới dạng tốc ký đồ họa. Số
điện thoại trong sơ đồ là đường dây khiếu nại của BBC, vì vậy vui lòng không gọi nó.
Machine Translated by Google

12,7. Chuỗi trình tự 121

tuple

0 'Cleese'

1 'John'

Hình 12.1: Biểu đồ trạng thái.

mệnh lệnh

('Cleese', 'John') '08700 100 222'

('Chapman', 'Graham') '08700 100 222'

('Idle', 'Eric') '08700 100 222'

('Gilliam', 'Terry') '08700 100 222'

('Jones', 'Terry') '08700 100 222'

('Palin', 'Michael') '08700 100 222'

Hình 12.2: Biểu đồ trạng thái.

12.7 Trình tự của trình tự

Tôi đã tập trung vào danh sách các bộ giá trị, nhưng hầu như tất cả các ví dụ trong chương này cũng hoạt
động với danh sách các danh sách, các bộ giá trị và các bộ danh sách. Để tránh liệt kê các kết hợp có thể
có, đôi khi nói về trình tự của các chuỗi sẽ dễ dàng hơn.

Trong nhiều ngữ cảnh, các loại trình tự khác nhau (chuỗi, danh sách và bộ dữ liệu) có thể được sử dụng thay
thế cho nhau. Vì vậy, làm thế nào bạn nên chọn một trong số những người khác?

Để bắt đầu với điều hiển nhiên, các chuỗi bị hạn chế hơn các chuỗi khác vì các dấu mười phải là các ký tự.
Chúng cũng là bất biến. Nếu bạn cần khả năng thay đổi các ký tự trong một chuỗi (trái ngược với việc tạo
một chuỗi mới), bạn có thể muốn sử dụng danh sách các ký tự để thay thế.

Danh sách phổ biến hơn bộ dữ liệu, chủ yếu là vì chúng có thể thay đổi. Nhưng có một số trường hợp bạn có
thể thích bộ giá trị hơn:

1. Trong một số ngữ cảnh, giống như câu lệnh return, về mặt cú pháp, việc tạo một bộ giá trị đơn giản hơn về mặt cú pháp
hơn một danh sách.

2. Nếu bạn muốn sử dụng một chuỗi làm khóa từ điển, bạn phải sử dụng kiểu bất biến
như một bộ hoặc chuỗi.

3. Nếu bạn đang chuyển một chuỗi làm đối số cho một hàm, việc sử dụng các bộ giá trị làm giảm
có khả năng xảy ra hành vi không mong muốn do răng cưa.

Bởi vì các bộ giá trị là bất biến, chúng không cung cấp các phương thức như sắp xếp và đảo ngược để sửa đổi
các danh sách hiện có. Nhưng Python cung cấp hàm tích hợp sẵn được sắp xếp, lấy bất kỳ trình tự nào và trả
về một danh sách mới với các phần tử giống nhau theo thứ tự được sắp xếp và đảo ngược, nhận một trình tự và
trả về một trình vòng lặp duyệt qua danh sách theo thứ tự ngược lại.
Machine Translated by Google

122 Chương 12. Tuples

12.8 Gỡ lỗi
Danh sách, từ điển và bộ giá trị là những ví dụ về cấu trúc dữ liệu; trong chương này, chúng ta sẽ bắt đầu
thấy các cấu trúc dữ liệu phức hợp, như danh sách các bộ giá trị hoặc từ điển chứa các bộ giá trị dưới dạng

khóa và danh sách dưới dạng giá trị. Cấu trúc dữ liệu phức hợp rất hữu ích, nhưng chúng dễ mắc phải cái mà

tôi gọi là lỗi hình dạng; nghĩa là lỗi gây ra khi cấu trúc dữ liệu có kiểu, kích thước hoặc cấu trúc sai.

Ví dụ: nếu bạn đang mong đợi một danh sách có một số nguyên và tôi cung cấp cho bạn một số nguyên cũ thuần

túy (không có trong danh sách), nó sẽ không hoạt động.

Để giúp gỡ lỗi các loại lỗi này, tôi đã viết một mô-đun có tên là structshape cung cấp một hàm, còn được gọi

là structshape, lấy bất kỳ loại cấu trúc dữ liệu nào làm đối số và trả về một chuỗi tóm tắt hình dạng của

nó. Bạn có thể tải xuống từ http://thinkpython2.com/code/structshape.py

Đây là kết quả cho một danh sách đơn

giản: >>> from structshape import structshape >>> t =


[1, 2, 3] >>> structshape (t) 'danh sách 3 int'

Một chương trình lạ hơn có thể viết “danh sách 3 số nguyên”, nhưng việc không xử lý số nhiều sẽ dễ dàng hơn.
Đây là danh sách các danh sách:

>>> t2 = [[1,2], [3,4], [5,6]] >>>

structshape (t2) 'danh sách 3 danh sách 2


int'

Nếu các phần tử của danh sách không cùng kiểu, thì structshape nhóm chúng theo thứ tự theo loại:

>>> t3 = [1, 2, 3, 4.0, '5', '6', [7], [8], 9] >>> structshape

(t3) 'danh sách (3 int, float, 2 str , 2 danh sách int, int) '

Đây là danh sách các bộ giá trị:

>>> s = 'abc'

>>> lt = list (zip (t, s)) >>>

structshape (lt) 'danh sách 3

tuple of (int, str)'

Và đây là một từ điển với 3 mục ánh xạ số nguyên thành chuỗi. >>> d = dict

(lt) >>> structshape (d) 'dict trong 3 int-> str'

Nếu bạn gặp khó khăn trong việc theo dõi cấu trúc dữ liệu của mình, structshape có thể giúp bạn.

12.9 Bảng chú giải thuật ngữ

tuple: Một chuỗi phần tử bất biến.

Phép gán tuple: Phép gán với một chuỗi ở bên phải và một dãy biến thể ở bên trái. Phía bên phải được đánh

giá và sau đó các phần tử của nó được gán cho các biến ở bên trái.
Machine Translated by Google

12,10. Bài tập 123

tập hợp: Một hoạt động thu thập nhiều đối số thành một bộ giá trị.

phân tán: Một hoạt động làm cho một chuỗi hoạt động giống như nhiều đối số.

đối tượng zip: Kết quả của việc gọi một hàm zip tích hợp sẵn; một đối tượng lặp lại qua một

chuỗi các bộ giá trị.

trình lặp: Một đối tượng có thể lặp qua một chuỗi, nhưng không cung cấp các toán tử và phương thức danh sách.

cấu trúc dữ liệu: Tập hợp các giá trị có liên quan, thường được tổ chức trong danh sách, từ điển, bộ giá trị,
vân vân.

lỗi hình dạng: Lỗi do một giá trị có hình dạng sai; đó là, loại sai
hoặc kích thước.

12.10 Bài tập

Bài tập 12.1. Viết một hàm có tên là most_frequent nhận một chuỗi và in các chữ cái cho phép theo thứ tự tần

suất giảm dần. Tìm các mẫu văn bản từ một số ngôn ngữ khác nhau và xem tần suất chữ cái khác nhau giữa các ngôn

ngữ. So sánh kết quả của bạn với các bảng tại http: // vi. wikipedia. org / wiki / tần số chữ cái. Lời giải:
http: // thinkpython2. com / code / most_ thường xuyên. py

Bài tập 12.2. Nhiều đảo ngữ hơn!

1. Viết chương trình đọc danh sách từ từ một tệp (xem Phần 9.1) và in tất cả các bộ

những từ đảo ngữ.

Dưới đây là một ví dụ về kết quả đầu ra có thể trông như thế nào:

['deltas', "desalt", "last", "Salt", "slated", "sted"] ["retainers", "ternaries"]

["create", "greatening"] ["resmelts", "smelters ',' vô hạn ']

Gợi ý: bạn có thể muốn xây dựng một từ điển ánh xạ từ một bộ sưu tập các chữ cái đến một danh sách các

từ có thể được đánh vần bằng các chữ cái đó. Câu hỏi đặt ra là, làm thế nào bạn có thể biểu diễn tập hợp

các chữ cái theo cách có thể được sử dụng như một khóa?

2. Sửa đổi chương trình trước đó để chương trình in ra danh sách dài nhất các từ đảo ngữ đầu tiên, tiếp theo

là chương trình dài thứ hai, v.v.

3. Trong Scrabble, "bingo" là khi bạn chơi tất cả bảy ô trên giá của bạn, cùng với một chữ cái trên bàn, để

tạo thành một từ có tám chữ cái. Bộ sưu tập 8 chữ cái nào tạo thành nhiều trò chơi lô tô nhất?

Lời giải: http: // thinkpython2. com / code / anagram_ sets. py

Bài tập 12.3. Hai từ tạo thành một “cặp biến dạng” nếu bạn có thể chuyển từ này thành từ kia bằng cách hoán đổi

hai chữ cái; ví dụ: “trò chuyện” và “bảo tồn”. Viết chương trình tìm tất cả các cặp siêu phân tố trong từ điển.

Gợi ý: không kiểm tra tất cả các cặp từ và không kiểm tra tất cả các hoán đổi có thể có. Lời giải: http: //

thinkpython2. com / code / metathesis. py Tín dụng: Bài tập này được lấy cảm hứng từ một ví dụ tại http: //

puzzlelers. tổ chức.
Machine Translated by Google

124 Chương 12. Tuples

Bài tập 12.4. Đây là một câu đố khác về Car Talk Puzzler (http: // www. Cartalk. Com / content / puzzlelers ):

Từ tiếng Anh dài nhất, vẫn là một từ tiếng Anh hợp lệ, khi bạn loại bỏ các chữ cái của nó cùng
một lúc?

Giờ đây, các chữ cái có thể bị xóa khỏi đầu hoặc giữa, nhưng bạn không thể sắp xếp lại bất kỳ

chữ cái nào. Mỗi khi bạn đánh rơi một chữ cái, bạn sẽ gặp phải một từ tiếng Anh khác. Nếu bạn làm

điều đó, cuối cùng bạn sẽ kết thúc với một chữ cái và đó cũng sẽ là một từ tiếng Anh — một từ

được tìm thấy trong từ điển. Tôi muốn biết từ dài nhất là gì và nó có bao nhiêu chữ cái?

Tôi sẽ cung cấp cho bạn một ví dụ khiêm tốn: Sprite. Được? Bạn bắt đầu với sprite, bạn bỏ đi một

chữ cái, một chữ cái từ bên trong của từ, lấy đi chữ r, và chúng ta chỉ còn lại từ spite, sau đó

chúng ta bỏ đi chữ e ở cuối, chúng ta chỉ còn lại sự nhổ , chúng tôi cởi bỏ, chúng tôi còn lại

với cái hố, nó, và tôi.

Viết chương trình để tìm tất cả các từ có thể rút gọn theo cách này, sau đó tìm từ dài nhất.

Bài tập này khó hơn một chút so với hầu hết các bài tập, vì vậy đây là một số gợi ý:

1. Bạn có thể muốn viết một hàm nhận một từ và tính toán danh sách tất cả các từ

có thể được hình thành bằng cách loại bỏ một chữ cái. Đây là những "con" của từ này.

2. Một cách đệ quy, một từ có thể rút gọn được nếu bất kỳ con nào của nó là có thể rút gọn. Là một trường hợp cơ bản, bạn có thể

coi chuỗi rỗng có thể rút gọn.

3. Danh sách từ mà tôi đã cung cấp, words.txt, không chứa các từ đơn. Vì vậy, bạn có thể muốn

để thêm “I”, “a” và chuỗi trống.

4. Để cải thiện hiệu suất của chương trình, bạn có thể muốn ghi nhớ các từ được biết là có thể rút gọn.

Lời giải: http: // thinkpython2. com / mã / có thể giảm được. py


Machine Translated by Google

Chương 13

Nghiên cứu điển hình: lựa chọn cấu


trúc dữ liệu

Tại thời điểm này, bạn đã tìm hiểu về các cấu trúc dữ liệu cốt lõi của Python và bạn đã thấy một số thuật toán

sử dụng chúng. Nếu bạn muốn biết thêm về các thuật toán, đây có thể là thời điểm tốt để đọc Chương B. Nhưng bạn

không cần phải đọc nó trước khi tiếp tục; bạn có thể đọc nó bất cứ khi nào bạn quan tâm.

Chương này trình bày một nghiên cứu điển hình với các bài tập cho phép bạn suy nghĩ về việc lựa chọn cấu trúc dữ

liệu và thực hành sử dụng chúng.

13.1 Phân tích tần số từ


Như thường lệ, ít nhất bạn nên thử các bài tập trước khi đọc lời giải của tôi.

Bài tập 13.1. Viết chương trình đọc một tệp, ngắt từng dòng thành các từ, tách khoảng trắng và dấu câu khỏi các

từ và chuyển chúng thành chữ thường.

Gợi ý: Mô-đun chuỗi cung cấp một chuỗi có tên khoảng trắng, chứa khoảng trắng, tab, dòng mới, v.v. và dấu câu

chứa các ký tự dấu câu. Hãy xem liệu chúng ta có thể khiến Python thề: >>> import string >>> string.punctuation

'! "# $% & \' () * +, -. / :; <=>? @ [\\] ^ _ `{|} ~ '

Ngoài ra, bạn có thể cân nhắc sử dụng dải phương thức chuỗi, thay thế và dịch.

Bài tập 13.2. Truy cập Project Gutenberg (http: // gutenberg. Org) và tải xuống cuốn sách không có bản quyền yêu

thích của bạn ở định dạng văn bản thuần túy.

Sửa đổi chương trình của bạn từ bài tập trước để đọc cuốn sách bạn đã tải xuống, bỏ qua thông tin tiêu đề ở đầu

tệp và xử lý phần còn lại của các từ như trước.

Sau đó, sửa đổi chương trình để đếm tổng số từ trong sách và số lần mỗi từ được sử dụng.

In số lượng các từ khác nhau được sử dụng trong sách. So sánh các cuốn sách khác nhau của các tác giả khác nhau,

được viết trong các thời đại khác nhau. Tác giả nào sử dụng vốn từ vựng phong phú nhất?
Machine Translated by Google

126 Chương 13. Nghiên cứu điển hình: lựa chọn cấu trúc dữ liệu

Bài tập 13.3. Sửa đổi chương trình từ bài tập trước để in 20 từ thường dùng nhất trong sách.

Bài tập 13.4. Sửa đổi chương trình trước đó để đọc danh sách từ (xem Phần 9.1) và sau đó in tất cả các từ
trong sách không có trong danh sách từ. Bao nhiêu trong số đó là lỗi chính tả? Bao nhiêu trong số chúng
là những từ phổ biến nên có trong danh sách từ, và bao nhiêu trong số chúng thực sự tối nghĩa?

13.2 Số ngẫu nhiên

Với các đầu vào giống nhau, hầu hết các chương trình máy tính đều tạo ra các đầu ra giống nhau mọi lúc,
vì vậy chúng được cho là xác định. Tính xác định thường là một điều tốt, vì chúng ta mong đợi cùng một
phép tính sẽ mang lại kết quả giống nhau. Tuy nhiên, đối với một số ứng dụng, chúng tôi muốn máy tính
không thể đoán trước được. Trò chơi là một ví dụ rõ ràng, nhưng còn nhiều hơn thế nữa.

Tạo ra một chương trình thực sự không xác định hóa ra rất khó, nhưng có nhiều cách để làm cho nó ít nhất
có vẻ như không xác định. Một trong số đó là sử dụng các thuật toán tạo ra các số giả ngẫu nhiên . Số giả
ngẫu nhiên không thực sự ngẫu nhiên vì chúng được tạo ra bởi một phép tính xác định, nhưng chỉ cần nhìn
vào các con số thì không thể phân biệt được chúng với ngẫu nhiên.

Mô-đun ngẫu nhiên cung cấp các hàm tạo ra các số giả ngẫu nhiên (mà từ đây tôi sẽ gọi đơn giản là “ngẫu
nhiên”).

Hàm ngẫu nhiên trả về một số thực ngẫu nhiên trong khoảng từ 0,0 đến 1,0 (bao gồm 0,0 nhưng không phải 1,0).
Mỗi lần bạn gọi ngẫu nhiên, bạn sẽ nhận được số tiếp theo trong một chuỗi dài. Để xem một mẫu, hãy chạy
vòng lặp này:

nhập ngẫu nhiên

cho tôi trong phạm vi


(10): x = random.random

() print (x)

Hàm randint nhận các tham số thấp và cao và trả về một số nguyên từ thấp đến cao (bao gồm cả hai).

>>> random.randint (5, 10)


5

>>> random.randint (5, 10) 9

Để chọn ngẫu nhiên một phần tử từ một chuỗi, bạn có thể sử dụng lựa chọn:

>>> t = [1, 2, 3] >>>


random.choice (t)
2

>>> random.choice (t) 3

Mô-đun ngẫu nhiên cũng cung cấp các chức năng để tạo ra các giá trị ngẫu nhiên từ các phân phối liên tục
bao gồm Gaussian, hàm mũ, gamma và một vài hàm khác.
Bài tập 13.5. Viết một hàm có tên select_from_hist lấy biểu đồ như được định nghĩa trong Phần 11.2 và trả
về một giá trị ngẫu nhiên từ biểu đồ, được chọn với xác suất tương ứng với tần suất. Ví dụ: đối với biểu
đồ này:
Machine Translated by Google

13.3. Biểu đồ từ 127

>>> t = ['a', 'a', 'b'] >>>


hist = histogram (t) >>> hist

{'a': 2, 'b': 1}

hàm của bạn sẽ trả về 'a' với xác suất 2/3 và 'b' với xác suất 1/3.

13.3 Biểu đồ từ
Bạn nên thử các bài tập trước trước khi tiếp tục. Bạn có thể tải xuống giải pháp của tôi từ
http://thinkpython2.com/code/analyze_book1.py. Bạn cũng sẽ cần http://thinkpython2.com/code/
emma.txt.

Đây là một chương trình đọc tệp và xây dựng biểu đồ của các từ trong tệp: chuỗi nhập

def process_file (filename): hist


= dict () fp = open (filename)
cho dòng trong fp:
process_line (line, hist)

lịch sử trả về

def process_line (line, hist):


line = line.replace ('-', '')

cho từ trong dòng.split ():


word = word.strip (string.punctuation + string.whitespace) word =
word.lower () hist [word] = hist.get (word, 0) + 1

hist = process_file ('emma.txt')

Chương trình này đọc emma.txt, trong đó có văn bản về Emma của Jane Austen.

process_file lặp qua các dòng của tệp, chuyển chúng lần lượt tới process_line. Lịch sử biểu đồ
đang được sử dụng làm bộ tích lũy.

process_line sử dụng phương thức thay thế chuỗi để thay thế dấu gạch ngang bằng dấu cách trước
khi sử dụng tách để ngắt dòng thành danh sách chuỗi. Nó duyệt qua danh sách các từ và sử dụng
dải và chữ thường để loại bỏ dấu chấm câu và chuyển đổi thành chữ thường. (Nói cách viết tắt là
các chuỗi được "chuyển đổi"; hãy nhớ rằng các chuỗi là bất biến, vì vậy các phương thức như dải
và thấp hơn trả về các chuỗi mới.)

Cuối cùng, process_line cập nhật biểu đồ bằng cách tạo một mục mới hoặc tăng một mục hiện có.

Để đếm tổng số từ trong tệp, chúng ta có thể cộng các tần số trong togram của anh ấy: def
total_words (hist): return sum (hist.values ())
Machine Translated by Google

128 Chương 13. Nghiên cứu điển hình: lựa chọn cấu trúc dữ liệu

Số lượng các từ khác nhau chỉ là số lượng các mục trong từ điển:

def different_words (hist):


return len (hist)

Đây là một số mã để in kết quả:

print ('Tổng số từ:', total_words (lịch sử))


print ('Số lượng từ khác nhau:', another_words (hist))

Và kết quả:

Tổng số từ: 161080

Số lượng từ khác nhau: 7214

13.4 Những từ thông dụng nhất

Để tìm những từ phổ biến nhất, chúng ta có thể tạo một danh sách các bộ, trong đó mỗi bộ chứa một
từ và tần số của nó, và sắp xếp nó.

Hàm sau nhận một biểu đồ và trả về một danh sách các bộ giá trị tần suất từ:

def most_common (hist):


t = []
cho khóa, giá trị trong hist.items ():
t.append ((giá trị, khóa))

t.sort (đảo ngược = Đúng)


trả lại t

Trong mỗi bộ, tần số xuất hiện đầu tiên, do đó, danh sách kết quả được sắp xếp theo tần suất. Nơi đây
là một vòng lặp in ra mười từ phổ biến nhất:

t = most_common (lịch sử)


print ('Các từ phổ biến nhất là:')
cho freq, từ trong t [: 10]:
print (word, freq, sep = '\ t')

Tôi sử dụng đối số từ khóa sep để yêu cầu print sử dụng ký tự tab làm "dấu phân cách", thay vì
hơn một khoảng trắng, vì vậy cột thứ hai được xếp thẳng hàng. Đây là kết quả từ Emma:

Những từ phổ biến nhất là:

đến 5242
các 5205
và 4897
của 4295
tôi 3191
một 3130
nó 2529
cô ấy 2483
là 2400
cô ấy 2364

Mã này có thể được đơn giản hóa bằng cách sử dụng tham số chính của hàm sắp xếp. Nếu bạn là
người yêu thích, bạn có thể đọc về nó tại https://wiki.python.org/moin/HowTo/Sorting.
Machine Translated by Google

13,5. Các thông số tùy chọn 129

13.5 Các thông số tùy chọn

Chúng ta đã thấy các hàm và phương thức dựng sẵn có các đối số tùy chọn. Cũng có thể viết các
hàm do lập trình viên xác định với các đối số tùy chọn. Ví dụ: đây là một hàm in các từ phổ
biến nhất trong biểu đồ def print_most_common (hist, num = 10): t = most_common (hist) print

('Các từ phổ biến nhất là:') cho freq, từ trong t [ : num]:

print (word, freq, sep = '\ t')

Tham số đầu tiên là bắt buộc; thứ hai là tùy chọn. Giá trị mặc định của num là 10.

Nếu bạn chỉ cung cấp một đối số:

print_most_common (hist) num nhận

giá trị mặc định. Nếu bạn cung cấp hai đối số:

print_most_common (hist, 20)

num nhận giá trị của đối số thay thế. Nói cách khác, đối số tùy chọn trên chạy giá trị mặc
định.

Nếu một hàm có cả tham số bắt buộc và tham số tùy chọn, thì tất cả các tham số bắt buộc phải
đến trước, sau đó là các tham số tùy chọn.

13.6 Phép trừ từ điển

Tìm các từ trong cuốn sách không có trong danh sách từ từ words.txt là một vấn đề bạn có thể
nhận ra là đặt phép trừ; nghĩa là, chúng ta muốn tìm tất cả các từ từ một tập hợp (các từ
trong sách) mà không có trong tập kia (các từ trong danh sách).

Phép trừ lấy từ điển d1 và d2 và trả về một từ điển mới chứa tất cả các khóa từ d1 không có
trong d2. Vì chúng tôi không thực sự quan tâm đến các giá trị, chúng tôi đặt tất cả chúng
thành Không có.

def subtract (d1, d2):


res = dict () cho
khóa trong d1:
nếu khóa không có trong

d2: res [key] = Không có


trả lại res

Để tìm các từ trong sách không có trong words.txt, chúng ta có thể sử dụng process_file để
tạo biểu đồ cho words.txt, sau đó trừ: words = process_file ('words.txt') diff = subtract

(hist, words)

print ("Các từ trong sách không có trong danh sách từ:") cho từ trong
diff: print (word, end = '')

Dưới đây là một số kết quả từ Emma:


Machine Translated by Google

130 Chương 13. Nghiên cứu điển hình: lựa chọn cấu trúc dữ liệu

Những từ trong sách không có trong danh sách từ: rencontre jane's

blanche woodhouse disingenuousness friend venice Apartment ...

Một số từ này là tên và sở hữu. Những thứ khác, như “rencontre”, không còn được sử dụng phổ biến nữa.
Nhưng một số từ phổ biến thực sự nên có trong danh sách!
Bài tập 13.6. Python cung cấp một cấu trúc dữ liệu được gọi là set cung cấp nhiều hoạt động tập hợp phổ
biến. Bạn có thể đọc về chúng trong Phần 19.5 hoặc đọc tài liệu tại http: // docs. con trăn. org / 3 /
library / stdtypes. html # type-set.

Viết chương trình sử dụng phép trừ tập hợp để tìm các từ trong sách không có trong danh sách từ.
Lời giải: http: // thinkpython2. com / code / analyse_ book2. py

13.7 Từ ngẫu nhiên

Để chọn một từ ngẫu nhiên từ biểu đồ, thuật toán đơn giản nhất là xây dựng một danh sách với nhiều bản
sao của mỗi từ, theo tần suất quan sát, sau đó chọn từ danh sách:

def random_word (h): t =


[] cho từ, freq

trong h.items (): t.extend ([word] *


freq)

trả về random.choice (t)

Biểu thức [word] * freq tạo một danh sách với các bản sao freq của từ chuỗi. Phương thức extension tương
tự như append ngoại trừ đối số là một chuỗi.

Thuật toán này hoạt động, nhưng nó không hiệu quả lắm; mỗi khi bạn chọn một từ ngẫu nhiên, nó sẽ xây dựng
lại danh sách, lớn bằng cuốn sách gốc. Một cải tiến rõ ràng là xây dựng danh sách một lần và sau đó thực
hiện nhiều lựa chọn, nhưng danh sách vẫn còn lớn.

Một thay thế là:

1. Sử dụng các phím để lấy danh sách các từ trong sách.

2. Xây dựng một danh sách có chứa tổng tích lũy của các tần số từ (xem Điều 10.2). Mục cuối cùng
trong danh sách này là tổng số từ trong sách, n.

3. Chọn một số ngẫu nhiên từ 1 đến n. Sử dụng tìm kiếm chia đôi (Xem bài tập 10.10) để tìm chỉ mục mà
số ngẫu nhiên sẽ được chèn vào tổng tích lũy.

4. Sử dụng chỉ mục để tìm từ tương ứng trong danh sách từ.

Bài tập 13.7. Viết một chương trình sử dụng thuật toán này để chọn một từ ngẫu nhiên từ cuốn sách.
Lời giải: http: // thinkpython2. com / code / analyse_ book3. py

13.8 Phân tích Markov


Nếu bạn chọn các từ trong cuốn sách một cách ngẫu nhiên, bạn có thể hiểu được từ vựng, nhưng có thể bạn
sẽ không nhận được một câu:
Machine Translated by Google

13,8. Phân tích Markov 131

Đây là vấn đề nhỏ nhặt nhất mà nó là thứ nhất của Knightley. Một loạt các từ ngẫu nhiên

hiếm khi có ý nghĩa bởi vì không có mối quan hệ giữa các từ liên tiếp. Ví dụ, trong một câu thực, bạn sẽ mong

đợi một mạo từ như “the” được theo sau bởi một tính từ hoặc một danh từ, và có thể không phải là một động từ

hoặc trạng từ.

Một cách để đo lường các loại mối quan hệ này là phân tích Markov, đặc trưng cho một chuỗi các từ nhất định,

xác suất của các từ có thể xuất hiện tiếp theo. Ví dụ, bài hát Eric, The Half a Bee bắt đầu:

Một nửa con ong, về mặt triết học,

Phải, thực tế là như vậy, một nửa không được.

Nhưng một nửa con ong phải là Vis a

vis, thực thể của nó. Bạn thấy không?

Nhưng một con ong có thể được

cho là Hay không phải là cả một

con ong Khi một nửa con ong không

phải là con ong Do vết thương cổ nào đó?

Trong văn bản này, cụm từ "một nửa" luôn được theo sau bởi từ "ong", nhưng cụm từ "con ong" có thể được theo sau

bởi "đã" hoặc "là".

Kết quả của phân tích Markov là một ánh xạ từ mỗi tiền tố (như “một nửa” và “con ong”) đến tất cả các hậu tố có

thể có (như “có” và “là”).

Với ánh xạ này, bạn có thể tạo một văn bản ngẫu nhiên bằng cách bắt đầu với bất kỳ tiền tố nào và chọn nhập

ngẫu nhiên từ các hậu tố có thể. Tiếp theo, bạn có thể kết hợp phần cuối của tiền tố và hậu tố mới để tạo thành

tiền tố tiếp theo và lặp lại.

Ví dụ: nếu bạn bắt đầu bằng tiền tố “Half a”, thì từ tiếp theo phải là “bee”, vì tiền tố chỉ xuất hiện một lần

trong văn bản. Tiền tố tiếp theo là “một con ong”, vì vậy hậu tố tiếp theo có thể là “về mặt triết học”, “được”

hoặc “do”.

Trong ví dụ này, độ dài của tiền tố luôn là hai, nhưng bạn có thể thực hiện phân tích Markov với bất kỳ độ dài

tiền tố nào.

Bài tập 13.8. Phân tích Markov:

1. Viết chương trình đọc văn bản từ tệp và thực hiện phân tích Markov. Kết quả sẽ là một từ điển ánh xạ từ

các tiền tố đến một tập hợp các hậu tố có thể có. Bộ sưu tập có thể là một danh sách, bộ tuple hoặc từ

điển; nó là vào bạn để đưa ra một sự lựa chọn thích hợp. Bạn có thể kiểm tra chương trình của mình với

độ dài tiền tố hai, nhưng bạn nên viết chương trình theo cách giúp bạn dễ dàng thử các độ dài khác.

2. Thêm một chức năng vào chương trình trước đó để tạo văn bản ngẫu nhiên dựa trên phân tích Markov.

Đây là một ví dụ từ Emma với độ dài tiền tố 2: Anh ấy rất

thông minh, có thể là ngọt ngào hay tức giận, xấu hổ hoặc chỉ thích thú, trước một cú đột

quỵ như vậy. Cô ấy chưa bao giờ nghĩ đến Hannah cho đến khi bạn không bao giờ dành cho tôi?

"" Tôi không thể phát biểu, Emma: "Anh ấy đã sớm tự cắt bỏ tất cả.

Đối với ví dụ này, tôi đã để dấu chấm câu kèm theo các từ. Kết quả là gần như đúng về mặt cú pháp, nhưng

không hoàn toàn. Về mặt ngữ nghĩa, nó gần như có ý nghĩa, nhưng không hoàn toàn.

Điều gì xảy ra nếu bạn tăng độ dài tiền tố? Văn bản ngẫu nhiên có ý nghĩa hơn không?
Machine Translated by Google

132 Chương 13. Nghiên cứu điển hình: lựa chọn cấu trúc dữ liệu

3. Khi chương trình của bạn đang hoạt động, bạn có thể muốn thử kết hợp: nếu bạn kết hợp văn bản từ
hai cuốn sách trở lên, văn bản ngẫu nhiên bạn tạo ra sẽ kết hợp từ vựng và cụm từ từ các nguồn
theo những cách thú vị.

Tín dụng: Nghiên cứu điển hình này dựa trên một ví dụ từ Kernighan và Pike, The Practice of Pro gramming,
Addison-Wesley, 1999.

Bạn nên thử bài tập này trước khi tiếp tục; thì bạn có thể tải xuống cảnh báo của tôi từ http://
thinkpython2.com/code/markov.py. Bạn cũng sẽ cần http: // thinkpython2.com/code/emma.txt.

13.9 Cấu trúc dữ liệu

Sử dụng phân tích Markov để tạo văn bản ngẫu nhiên rất thú vị, nhưng cũng có một điểm cho bài tập này:
lựa chọn cấu trúc dữ liệu. Trong giải pháp của bạn cho các bài tập trước, bạn phải chọn:

• Cách biểu diễn các tiền tố.

• Cách biểu diễn tập hợp các hậu tố có thể có.

• Cách biểu diễn ánh xạ từ mỗi tiền tố vào tập hợp các hậu tố có thể có.

Cách cuối cùng rất dễ dàng: từ điển là sự lựa chọn hiển nhiên để ánh xạ từ các khóa đến các giá trị
tương ứng.

Đối với các tiền tố, các tùy chọn rõ ràng nhất là chuỗi, danh sách các chuỗi hoặc nhiều chuỗi.

Đối với các hậu tố, một tùy chọn là một danh sách; cái khác là biểu đồ (từ điển).

Bạn nên chọn như thế nào? Bước đầu tiên là suy nghĩ về các hoạt động bạn sẽ cần thực hiện cho mỗi cấu
trúc dữ liệu. Đối với các tiền tố, chúng ta cần có thể loại bỏ các từ ngay từ đầu và thêm vào cuối. Ví
dụ: nếu tiền tố hiện tại là "Half a" và từ tiếp theo là "bee", bạn cần có thể tạo tiền tố tiếp theo, "a
bee".

Lựa chọn đầu tiên của bạn có thể là một danh sách, vì có thể dễ dàng thêm và xóa các phần tử, nhưng chúng
tôi cũng cần có thể sử dụng các tiền tố làm khóa trong từ điển, để loại trừ các danh sách. Với các bộ giá
trị, bạn không thể thêm hoặc bớt, nhưng bạn có thể sử dụng toán tử bổ sung để tạo một bộ giá trị mới: def

shift (tiền tố, từ): return prefix [1:] + (word,)

shift lấy một bộ từ, tiền tố và một chuỗi, từ và tạo thành một bộ mới có tất cả các từ ở tiền tố ngoại
trừ từ đầu tiên và từ được thêm vào cuối.

Đối với tập hợp các hậu tố, các thao tác chúng ta cần thực hiện bao gồm thêm một hậu tố mới (hoặc tăng
tần suất của một hậu tố hiện có) và chọn một hậu tố ngẫu nhiên.

Việc thêm hậu tố mới cũng dễ dàng như nhau đối với việc triển khai danh sách hoặc biểu đồ. Chọn một phần
tử ngẫu nhiên từ một danh sách rất dễ dàng; việc chọn từ một biểu đồ khó thực hiện hiệu quả hơn (xem Bài
tập 13.7).

Cho đến nay chúng ta chủ yếu nói về tính dễ thực hiện, nhưng có những khía cạnh khác cần xem xét trong
việc lựa chọn cấu trúc dữ liệu. Một là thời gian chạy. Đôi khi có một lý do lý thuyết để mong đợi một
cấu trúc dữ liệu nhanh hơn cấu trúc khác; ví dụ, tôi đã đề cập rằng
Machine Translated by Google

13,10. Gỡ lỗi 133

toán tử in nhanh hơn đối với từ điển hơn là danh sách, ít nhất là khi số lượng phần tử lớn.

Nhưng thường thì bạn không biết trước cách thực hiện nào sẽ nhanh hơn. Một tùy chọn là thực hiện cả hai
và xem cái nào tốt hơn. Cách tiếp cận này được gọi là điểm chuẩn.
Một giải pháp thay thế thực tế là chọn cấu trúc dữ liệu dễ thực hiện nhất và sau đó xem nó có đủ nhanh
cho ứng dụng dự định hay không. Nếu vậy, không cần phải tiếp tục. Nếu không, có những công cụ, như mô-đun
hồ sơ, có thể xác định các vị trí trong một chương trình chiếm nhiều thời gian nhất.

Yếu tố khác cần xem xét là không gian lưu trữ. Ví dụ: sử dụng biểu đồ cho phần chọn col của các hậu tố
có thể tốn ít dung lượng hơn vì bạn chỉ phải lưu trữ mỗi từ một lần, bất kể nó xuất hiện bao nhiêu lần
trong văn bản. Trong một số trường hợp, việc tiết kiệm dung lượng cũng có thể làm cho chương trình của
bạn chạy nhanh hơn, và cực đoan, chương trình của bạn có thể hoàn toàn không chạy nếu bạn hết bộ nhớ.
Nhưng đối với nhiều ứng dụng, không gian là yếu tố được xem xét sau thời gian chạy.

Một suy nghĩ cuối cùng: trong cuộc thảo luận này, tôi đã ngụ ý rằng chúng ta nên sử dụng một cấu trúc dữ
liệu cho cả phân tích và tạo. Nhưng vì đây là các giai đoạn riêng biệt, nên cũng có thể sử dụng một cấu
trúc để phân tích và sau đó chuyển đổi sang cấu trúc khác để tạo.
Đây sẽ là một chiến thắng ròng nếu thời gian tiết kiệm được trong quá trình tạo vượt quá thời gian dành
cho chuyển đổi.

13.10 Gỡ lỗi
Khi bạn đang gỡ lỗi một chương trình, và đặc biệt nếu bạn đang sửa lỗi khó, có năm điều cần thử:

Đọc: Kiểm tra mã của bạn, đọc lại cho chính mình và kiểm tra xem nó có nói những gì bạn không
dang tinh noi.

Đang chạy: Thử nghiệm bằng cách thực hiện các thay đổi và chạy các phiên bản khác nhau. Thông thường, nếu
bạn hiển thị đúng thứ ở đúng vị trí trong chương trình, vấn đề sẽ trở thành vấn đề, nhưng đôi khi
bạn phải xây dựng giàn giáo.

Suy nghĩ: Hãy dành một chút thời gian để suy nghĩ! Đó là loại lỗi nào: cú pháp, thời gian chạy hoặc
seman tic? Bạn có thể nhận được thông tin gì từ các thông báo lỗi hoặc từ kết quả đầu ra của chương
trình? Loại lỗi nào có thể gây ra sự cố bạn đang gặp phải? Bạn đã thay đổi điều gì cuối cùng,
trước khi vấn đề xuất hiện?

Rubberducking: Nếu bạn giải thích vấn đề cho người khác, đôi khi bạn sẽ tìm thấy câu trả lời trước khi
đặt câu hỏi xong. Thường thì bạn không cần người kia; bạn chỉ có thể nói chuyện với một con vịt
cao su. Và đó là nguồn gốc của chiến lược nổi tiếng được gọi là gỡ lỗi vịt cao su. Tôi không bịa
chuyện này; xem https://en.wikipedia.org/wiki/Rubber_duck_debugging.

Rút lui: Tại một số thời điểm, điều tốt nhất nên làm là lùi lại, hoàn tác các thay đổi gần đây, cho đến
khi bạn quay lại chương trình hoạt động và bạn hiểu. Sau đó, bạn có thể bắt đầu xây dựng lại.
Machine Translated by Google

134 Chương 13. Nghiên cứu điển hình: lựa chọn cấu trúc dữ liệu

Các lập trình viên mới bắt đầu đôi khi bị mắc kẹt vào một trong những hoạt động này và quên mất những hoạt

động đó. Mỗi hoạt động đi kèm với chế độ thất bại riêng của nó.

Ví dụ: đọc mã của bạn có thể hữu ích nếu vấn đề là lỗi đánh máy, nhưng không hữu ích nếu vấn đề là một sự

hiểu lầm khái niệm. Nếu bạn không hiểu chương trình của mình làm gì, bạn có thể đọc nó 100 lần và không bao

giờ thấy lỗi, vì lỗi ở trong đầu bạn.

Chạy thử nghiệm có thể hữu ích, đặc biệt nếu bạn chạy thử nghiệm nhỏ, đơn giản. Nhưng nếu bạn chạy thử nghiệm

mà không suy nghĩ hoặc đọc mã của mình, bạn có thể rơi vào một mô hình mà tôi gọi là “lập trình đi bộ ngẫu

nhiên”, là quá trình thực hiện các thay đổi ngẫu nhiên cho đến khi chương trình thực hiện đúng. Không cần

phải nói, lập trình đi bộ ngẫu nhiên có thể mất nhiều thời gian.

Bạn phải dành thời gian để suy nghĩ. Gỡ lỗi giống như một môn khoa học thực nghiệm. Bạn nên có ít nhất một

giả thuyết về vấn đề là gì. Nếu có từ hai khả năng trở lên, hãy thử nghĩ đến một bài kiểm tra có thể loại bỏ
một trong số chúng.

Nhưng ngay cả những kỹ thuật gỡ lỗi tốt nhất cũng sẽ thất bại nếu có quá nhiều lỗi hoặc nếu mã bạn đang cố

sửa quá lớn và phức tạp. Đôi khi, lựa chọn tốt nhất là rút lui, đơn giản hóa chương trình cho đến khi bạn

đạt được điều gì đó hoạt động và bạn hiểu.

Các lập trình viên mới bắt đầu thường miễn cưỡng rút lui vì họ không thể xóa một dòng mã (ngay cả khi nó

sai). Nếu nó khiến bạn cảm thấy tốt hơn, hãy sao chép chương trình của bạn vào một tệp khác trước khi bạn bắt

đầu gỡ bỏ nó. Sau đó, bạn có thể sao chép từng phần một.

Việc tìm ra một lỗi khó đòi hỏi phải đọc, chạy, nghiền ngẫm và đôi khi rút lui. Nếu bạn gặp khó khăn trong

một trong những hoạt động này, hãy thử những hoạt động khác.

13.11 Bảng chú giải thuật ngữ

xác định: Liên quan đến một chương trình thực hiện cùng một việc mỗi khi nó chạy, với các đầu vào giống nhau.

pseudorandom: Liên quan đến một dãy số có vẻ là ngẫu nhiên, nhưng được tạo ra bởi một chương trình xác định.

giá trị mặc định: Giá trị được cung cấp cho một tham số tùy chọn nếu không có đối số nào được cung cấp.

ghi đè: Để thay thế một giá trị mặc định bằng một đối số.

đo điểm chuẩn: Quá trình lựa chọn giữa các cấu trúc dữ liệu bằng cách triển khai các cấu trúc dữ liệu gốc

thay đổi và kiểm tra chúng trên một mẫu các đầu vào có thể có.

gỡ lỗi con vịt cao su: Gỡ lỗi bằng cách giải thích vấn đề của bạn với một vật vô tri vô giác chẳng hạn như

con vịt cao su. Thuật toán vấn đề có thể giúp bạn giải quyết nó, ngay cả khi con vịt cao su không biết

Python.

13.12 Bài tập

Bài tập 13.9. “Thứ hạng” của một từ là vị trí của nó trong danh sách các từ được sắp xếp theo tần suất: từ
phổ biến nhất có hạng 1, từ phổ biến thứ hai có hạng 2, v.v.
Machine Translated by Google

13.12. Bài tập 135

Định luật Zipf mô tả mối quan hệ giữa cấp bậc và tần số của các từ trong ngôn ngữ tự nhiên (http: // vi. Wikipedia.

Org / wiki / Zipf's_ law). Cụ thể, nó dự đoán rằng tần số, f, của từ có bậc r là:

f = cr s

trong đó s và c là các tham số phụ thuộc vào ngôn ngữ và văn bản. Nếu bạn lấy logarit của cả hai vế của phương

trình này, bạn nhận được:

log f = log c - khẩu hiệu r

Vì vậy, nếu bạn vẽ đồ thị log f so với log r, bạn sẽ nhận được một đường thẳng có hệ số góc s và giao điểm log c.

Viết chương trình đọc văn bản từ tệp, đếm tần số từ và in một dòng cho mỗi từ, theo thứ tự tần suất giảm dần, với

log f và log r. Sử dụng chương trình vẽ đồ thị mà bạn chọn để vẽ các kết quả và kiểm tra xem chúng có tạo thành một

đường thẳng hay không. Bạn có thể ước tính giá trị của s không?

Lời giải: http: // thinkpython2. com / code / zipf. py Để chạy giải pháp của tôi, bạn cần mô-đun âm mưu ting

matplotlib. Nếu bạn đã cài đặt Anaconda, bạn đã có matplotlib; nếu không bạn có thể phải cài đặt nó.
Machine Translated by Google

136 Chương 13. Nghiên cứu điển hình: lựa chọn cấu trúc dữ liệu
Machine Translated by Google

Chương 14

Các tập tin

Chương này giới thiệu ý tưởng về các chương trình “bền bỉ” giữ cho dữ liệu ở độ tuổi cố định lâu dài và chỉ

ra cách sử dụng các loại lưu trữ lâu dài khác nhau, như tệp và cơ sở dữ liệu.

14.1 Tính bền bỉ

Hầu hết các chương trình chúng ta đã thấy cho đến nay đều là tạm thời theo nghĩa là chúng chạy trong một thời gian

ngắn và tạo ra một số đầu ra, nhưng khi chúng kết thúc, dữ liệu của chúng sẽ biến mất. Nếu bạn chạy lại chương

trình, chương trình sẽ bắt đầu với một phương tiện chặn sạch.

Các chương trình khác thì liên tục: chúng chạy trong một thời gian dài (hoặc mọi lúc); họ giữ ít nhất một số

dữ liệu của mình trong bộ nhớ vĩnh viễn (ví dụ: ổ cứng); và nếu họ tắt và khởi động lại, họ sẽ tiếp tục nơi

họ đã dừng lại.

Ví dụ về các chương trình liên tục là hệ điều hành, chạy khá nhiều bất cứ khi nào máy tính bật và máy chủ

web, chạy mọi lúc, chờ các yêu cầu đến trên mạng.

Một trong những cách đơn giản nhất để các chương trình duy trì dữ liệu của chúng là đọc và ghi các tệp văn

bản. Chúng tôi đã thấy các chương trình đọc tệp văn bản; trong chương này, chúng ta sẽ thấy các chương trình

viết chúng.

Một giải pháp thay thế là lưu trữ trạng thái của chương trình trong cơ sở dữ liệu. Trong chương này, tôi sẽ

trình bày một cơ sở dữ liệu đơn giản và một mô-đun, pickle, giúp dễ dàng lưu trữ dữ liệu chương trình.

14.2 Đọc và viết


Tệp văn bản là một chuỗi các ký tự được lưu trữ trên một phương tiện lâu dài như ổ cứng, bộ nhớ flash hoặc CD-

ROM. Chúng ta đã biết cách mở và đọc một tệp trong Phần 9.1.

Để ghi tệp, bạn phải mở tệp với chế độ 'w' làm tham số thứ hai:

>>> fout = open ('output.txt', 'w')


Machine Translated by Google

138 Chương 14. Tập tin

Nếu tệp đã tồn tại, việc mở tệp ở chế độ ghi sẽ xóa dữ liệu cũ và bắt đầu làm mới, vì vậy hãy cẩn
thận! Nếu tệp không tồn tại, một tệp mới sẽ được tạo.

open trả về một đối tượng tệp cung cấp các phương thức để làm việc với tệp. Phương thức ghi đưa dữ
liệu vào tệp.

>>> line1 = "Đây là cái gông, \ n" >>> fout.write


(line1)
24

Giá trị trả về là số ký tự đã được viết. Đối tượng tệp theo dõi vị trí của nó, vì vậy nếu bạn gọi ghi
lại, nó sẽ thêm dữ liệu mới vào cuối tệp. >>> line2 = "biểu tượng của đất nước chúng ta. \ n" >>>

fout.write (line2)

24

Khi bạn viết xong, bạn nên đóng tệp. >>> fout.close ()

Nếu bạn không đóng tệp, tệp sẽ được đóng cho bạn khi chương trình kết thúc.

14.3 Toán tử định dạng

Đối số của write phải là một chuỗi, vì vậy nếu chúng ta muốn đặt các giá trị khác vào một tệp, chúng
ta phải chuyển chúng thành chuỗi. Cách dễ nhất để làm điều đó là với str:

>>> x = 52

>>> fout.write (str (x))

Một thay thế là sử dụng toán tử định dạng, %. Khi áp dụng cho số nguyên,% là toán tử mô-đun. Nhưng
khi toán hạng đầu tiên là một chuỗi,% là toán tử định dạng.

Toán hạng đầu tiên là chuỗi định dạng, chứa một hoặc nhiều chuỗi định dạng, chỉ định cách toán hạng
thứ hai được định dạng. Kết quả là một chuỗi.

Ví dụ: chuỗi định dạng '% d' có nghĩa là toán hạng thứ hai phải được định dạng là một số nguyên thập
phân:

>>> lạc đà = 42

>>> '% d'% lạc đà '42'

Kết quả là chuỗi '42', không được nhầm lẫn với giá trị số nguyên 42.

Một chuỗi định dạng có thể xuất hiện ở bất kỳ đâu trong chuỗi, vì vậy bạn có thể nhúng một giá trị
vào một câu:

>>> 'Tôi đã phát hiện% d con lạc đà.' % lạc đà 'Tôi


đã phát hiện thấy 42 con lạc đà.'

Nếu có nhiều hơn một chuỗi định dạng trong chuỗi, thì đối số thứ hai phải là một bộ. Mỗi chuỗi định
dạng được so khớp với một phần tử của bộ, theo thứ tự.

Ví dụ sau sử dụng '% d' để định dạng số nguyên, '% g' để định dạng dấu phẩy động num ber và '% s' để
định dạng chuỗi: >>> 'Trong% d năm, tôi đã phát hiện ra% g% S.' % (3, 0,1, 'lạc đà')

"Trong 3 năm, tôi đã phát hiện ra 0,1 con lạc đà."


Machine Translated by Google

14.4. Tên tệp và đường dẫn 139

Số phần tử trong bộ phải khớp với số trình tự định dạng trong chuỗi. Ngoài ra, các loại phần tử phải
phù hợp với trình tự định dạng:

>>> '% d% d% d'% (1, 2)


TypeError: không đủ đối số cho chuỗi định dạng >>> '% d'% 'đô la'

TypeError:% d format: một số là bắt buộc, không phải str Trong ví

dụ đầu tiên, không có đủ phần tử; trong thứ hai, phần tử không đúng loại.

Để biết thêm thông tin về toán tử định dạng, hãy xem https://docs.python.org/3/library/ stdtypes.html
# printf-style-string-formatting. Một phương pháp thay thế mạnh mẽ hơn là phương pháp định dạng chuỗi
mà bạn có thể đọc tại https://docs.python.org/3/ library / stdtypes.html # str.format.

14.4 Tên tệp và đường dẫn

Các tệp được sắp xếp thành các thư mục (còn được gọi là “thư mục”). Mỗi chương trình đang chạy đều
có “thư mục hiện tại”, là thư mục mặc định cho hầu hết các hoạt động. Ví dụ: khi bạn mở một tệp để
đọc, Python sẽ tìm kiếm nó trong thư mục hiện tại.

Mô-đun os cung cấp các chức năng để làm việc với các tệp và thư mục (“os” là viết tắt của “hệ điều
hành”). os.getcwd trả về tên của thư mục hiện tại:

>>> nhập hệ điều


hành >>> cwd = os.getcwd ()
>>> cwd '/ home / dinsdale'

cwd là viết tắt của "thư mục làm việc hiện tại". Kết quả trong ví dụ này là / home / dinsdale, là
thư mục chính của người dùng có tên là dinsdale.

Một chuỗi như '/ home / dinsdale' xác định một tệp hoặc thư mục được gọi là một đường dẫn.

Một tên tệp đơn giản, như memo.txt cũng được coi là một đường dẫn, nhưng nó là một đường dẫn tương
đối vì nó liên quan đến thư mục hiện tại. Nếu thư mục hiện tại là / home / dinsdale, tên tệp memo.txt
sẽ tham chiếu đến /home/dinsdale/memo.txt.

Đường dẫn bắt đầu bằng / không phụ thuộc vào thư mục hiện tại; nó được gọi là một đường dẫn tuyệt
đối. Để tìm đường dẫn tuyệt đối đến tệp, bạn có thể sử dụng os.path.abspath: >>> os.path.abspath

('memo.txt') '/home/dinsdale/memo.txt'

os.path cung cấp các chức năng khác để làm việc với tên tệp và đường dẫn. Ví dụ: os.path.exists kiểm
tra xem một tệp hoặc thư mục có tồn tại hay không: >>> os.path.exists ('memo.txt')

ĐÚNG VẬY

Nếu nó tồn tại, os.path.isdir sẽ kiểm tra xem nó có phải là một thư

mục hay không: >>> os.path.isdir ('memo.txt')


Sai

>>> os.path.isdir ('/ home / dinsdale')


ĐÚNG VẬY
Machine Translated by Google

140 Chương 14. Tập tin

Tương tự, os.path.isfile kiểm tra xem đó có phải là một tệp hay không.

os.listdir trả về danh sách các tệp (và các thư mục khác) trong thư mục đã cho:

>>> os.listdir (cwd)


['nhạc', 'ảnh', 'memo.txt']

Để chứng minh các chức năng này, ví dụ sau “đi qua” một thư mục, in tên của tất cả các tệp
và tự gọi đệ quy trên tất cả các thư mục.

def walk (dirname):


cho tên trong os.listdir (dirname):
path = os.path.join (dirname, name)

if os.path.isfile (path):
print (path)
khác:

đi bộ (đường dẫn)

os.path.join lấy một thư mục và tên tệp và nối chúng thành một đường dẫn hoàn chỉnh.

Mô-đun os cung cấp một chức năng được gọi là bước đi tương tự như chức năng này nhưng linh
hoạt hơn. Như một bài tập, hãy đọc tài liệu và sử dụng nó để in tên của các tệp trong một
thư mục nhất định và các thư mục con của nó. Bạn có thể tải xuống giải pháp của tôi từ http://
thinkpython2.com/code/walk.py.

14.5 Bắt các trường hợp ngoại lệ

Rất nhiều thứ có thể xảy ra sai sót khi bạn cố gắng đọc và ghi tệp. Nếu bạn cố gắng mở một
tệp không tồn tại, bạn nhận được FileNotFoundError: >>> fin = open ('bad_file')

FileNotFoundError: [Errno 2] Không có tệp hoặc thư mục nào như vậy: 'bad_file'

Nếu bạn không có quyền truy cập tệp: >>> fout

= open ('/ etc / passwd', 'w')


PermissionError: [Errno 13] Quyền bị từ chối: '/ etc / passwd'

Và nếu bạn cố gắng mở một thư mục để đọc, bạn sẽ nhận

được >>> fin = open ('/ home')


IsADirectoryError: [Errno 21] Là một thư mục: '/ home'

Để tránh những lỗi này, bạn có thể sử dụng các hàm như os.path.exists và os.path.isfile,
nhưng sẽ mất rất nhiều thời gian và mã để kiểm tra tất cả các khả năng (nếu “Errno 21” là
bất kỳ dấu hiệu nào, có tại ít nhất 21 điều có thể sai).

Tốt hơn là bạn nên tiếp tục và thử — và giải quyết các vấn đề nếu chúng xảy ra — đó chính
xác là những gì câu lệnh try làm. Cú pháp tương tự như câu lệnh if ... else:

try:
fin = open ('bad_file')
ngoại trừ:

print ('Đã xảy ra lỗi.')


Machine Translated by Google

14,6. Cơ sở dữ liệu 141

Python bắt đầu bằng cách thực thi mệnh đề try. Nếu mọi việc suôn sẻ, nó sẽ bỏ qua điều khoản ngoại trừ
và tiếp tục. Nếu một ngoại lệ xảy ra, nó sẽ nhảy ra khỏi mệnh đề try và chạy mệnh đề ngoại trừ.

Xử lý một ngoại lệ bằng câu lệnh try được gọi là bắt một ngoại lệ. Trong đề thi này, mệnh đề ngoại trừ
in ra một thông báo lỗi không hữu ích lắm. Nói chung, việc bắt được một ngoại lệ sẽ cho bạn cơ hội khắc
phục sự cố hoặc thử lại hoặc ít nhất là kết thúc chương trình một cách duyên dáng.

14.6 Cơ sở dữ liệu

Cơ sở dữ liệu là một tệp được tổ chức để lưu trữ dữ liệu. Nhiều cơ sở dữ liệu được tổ chức giống như
một từ điển theo nghĩa là chúng ánh xạ từ khóa đến giá trị. Sự khác biệt lớn nhất giữa cơ sở dữ liệu và
từ điển là cơ sở dữ liệu nằm trên đĩa (hoặc bộ lưu trữ vĩnh viễn khác), vì vậy nó vẫn tồn tại sau khi
chương trình kết thúc.

Mô-đun dbm cung cấp một giao diện để tạo và cập nhật các tệp cơ sở dữ liệu. Ví dụ, tôi sẽ tạo một cơ sở
dữ liệu có chứa chú thích cho các tệp hình ảnh.

Mở cơ sở dữ liệu tương tự như mở các tệp khác:

>>> nhập dbm >>>


db = dbm.open ('chú thích', 'c')

Chế độ 'c' có nghĩa là cơ sở dữ liệu sẽ được tạo nếu nó chưa tồn tại. Kết quả là một đối tượng cơ sở dữ
liệu có thể được sử dụng (cho hầu hết các hoạt động) giống như một từ điển.

Khi bạn tạo một mục mới, dbm cập nhật tệp cơ sở dữ liệu.

>>> db ['cleese.png'] = 'Ảnh của John Cleese.'

Khi bạn truy cập một trong các mục, dbm đọc tệp:

>>> db ['cleese.png'] b'Ảnh


của John Cleese. "

Kết quả là một đối tượng byte, đó là lý do tại sao nó bắt đầu bằng b. Một đối tượng byte tương tự như
một chuỗi theo nhiều cách. Khi bạn tiến sâu hơn vào Python, sự khác biệt trở nên quan trọng, nhưng hiện
tại chúng ta có thể bỏ qua nó.

Nếu bạn thực hiện một phép gán khác cho một khóa hiện có, dbm sẽ thay thế giá trị cũ:

>>> db ['cleese.png'] = 'Bức ảnh John Cleese đang đi bộ ngớ ngẩn.' >>> db ['cleese.png']
b'Ảnh chụp John Cleese đang đi dạo ngớ ngẩn. '

Một số phương thức từ điển, như khóa và mục, không hoạt động với các đối tượng cơ sở dữ liệu. Nhưng
phép lặp với vòng lặp for hoạt động:

cho khóa trong db.keys ():


print (key, db [key])

Cũng như các tệp khác, bạn nên đóng cơ sở dữ liệu khi hoàn tất:

>>> db.close ()
Machine Translated by Google

142 Chương 14. Tập tin

14.7 Tẩy chua

Một hạn chế của dbm là các khóa và giá trị phải là chuỗi hoặc byte. Nếu bạn cố gắng sử dụng bất kỳ loại
nào khác, bạn sẽ gặp lỗi.

Mô-đun dưa chua có thể giúp ích. Nó dịch hầu hết mọi loại đối tượng thành một chuỗi phù hợp để lưu trữ
trong cơ sở dữ liệu, và sau đó dịch các chuỗi trở lại thành các đối tượng.

pickle.dumps nhận một đối tượng làm tham số và trả về biểu diễn chuỗi (dumps là viết tắt của “kết xuất
chuỗi”): >>> import pickle >>> t = [1, 2, 3] >>> pickle.dumps (t ) b '\ x80 \ x03] q \ x00 (K \ x01K \

x02K \ x03e.'

Định dạng không rõ ràng đối với độc giả của con người; nó có nghĩa là để dưa chua dễ hiểu. pickle.loads
(“chuỗi tải”) tái tạo đối tượng: >>> t1 = [1, 2, 3] >>> s = pickle.dumps (t1) >>> t2 = pickle.loads >>>

t2 [1, 2, 3]

Mặc dù đối tượng mới có cùng giá trị với đối tượng cũ, nhưng (nói chung) không phải là đối tượng giống nhau:

>>> t1 == t2
ĐÚNG VẬY

>>> t1 là t2
Sai

Nói cách khác, chọn lọc và sau đó giải nén có tác dụng tương tự như sao chép đối tượng.

Bạn có thể sử dụng pickle để lưu trữ các chuỗi không phải trong cơ sở dữ liệu. Trên thực tế, sự kết hợp
này rất hài hước đến mức nó đã được gói gọn trong một mô-đun gọi là giá đỡ.

14,8 ống

Hầu hết các hệ điều hành đều cung cấp giao diện dòng lệnh, còn được gọi là shell. Vỏ thường cung cấp các
lệnh để điều hướng hệ thống tệp và khởi chạy các ứng dụng. Đối với kỳ thi, trong Unix, bạn có thể thay
đổi thư mục bằng cd, hiển thị nội dung của thư mục bằng ls và khởi chạy trình duyệt web bằng cách gõ (ví
dụ) firefox.

Bất kỳ chương trình nào bạn có thể khởi chạy từ shell cũng có thể được khởi chạy từ Python bằng cách sử
dụng đối tượng pipe, đại diện cho một chương trình đang chạy.

Ví dụ, lệnh Unix ls -l thường hiển thị nội dung của chiếu lệ hiện tại ở định dạng dài. Bạn có thể khởi
chạy ls với os.popen1 :

>>> cmd = 'ls -l' >>>

fp = os.popen (cmd)

1popen hiện không được dùng nữa, có nghĩa là chúng tôi phải ngừng sử dụng nó và bắt đầu sử dụng mô-đun quy trình con.
Nhưng đối với những trường hợp đơn giản, tôi thấy quy trình con phức tạp hơn mức cần thiết. Vì vậy, tôi sẽ tiếp tục sử
dụng popen cho đến khi họ lấy đi.
Machine Translated by Google

14,9. Viết mô-đun 143

Đối số là một chuỗi chứa lệnh shell. Giá trị trả về là một đối tượng hoạt động giống như một tệp đang mở.
Bạn có thể đọc đầu ra từ quy trình ls từng dòng một với readline hoặc xem toàn bộ cùng một lúc với read:
>>> res = fp.read ()

Khi bạn hoàn thành, bạn đóng đường dẫn như một tệp: >>>

stat = fp.close () >>> print (stat)

Không có

Giá trị trả về là trạng thái cuối cùng của quá trình ls; Không có nghĩa là nó đã kết thúc bình thường
(không có lỗi).

Ví dụ: hầu hết các hệ thống Unix cung cấp một lệnh có tên md5sum đọc nội dung của một tệp và tính toán
một “tổng kiểm tra”. Bạn có thể đọc về MD5 tại http: //en.wikipedia. org / wiki / Md5. Lệnh này cung cấp
một cách hiệu quả để kiểm tra xem hai tệp có cùng nội dung hay không. Xác suất mà các nội dung khác nhau
mang lại cùng một tổng kiểm tra là rất nhỏ (nghĩa là không thể xảy ra trước khi vũ trụ sụp đổ).

Bạn có thể sử dụng một đường ống để chạy md5sum từ Python và nhận được kết quả:

>>> filename = 'book.tex'


>>> cmd = 'md5sum' >>> fp + tên tệp

= os.popen (cmd) >>> res =


fp.read () >>> stat = fp.close
() >>> print (res)
1e0033f0ed0656636de0d75144ba32e0
book.tex >>> in (stat)

Không có

14.9 Viết mô-đun


Bất kỳ tệp nào chứa mã Python đều có thể được nhập dưới dạng mô-đun. Ví dụ: giả sử bạn có một tệp có tên
wc.py với mã sau:

def linecount (tên tệp):


đếm = 0

cho dòng đang mở (tên tệp): count +


= 1
số lượng trả lại

print (linecount ('wc.py'))

Nếu bạn chạy chương trình này, nó sẽ tự đọc và in ra số dòng trong tệp, là 7.
Bạn cũng có thể nhập nó như thế này:

>>> nhập wc 7

Bây giờ bạn có một đối tượng mô-đun wc:

>>> wc

<mô-đun 'wc' từ 'wc.py'>


Machine Translated by Google

144 Chương 14. Tập tin

Đối tượng mô-đun cung cấp số lượng dòng: >>>

wc.linecount ('wc.py') 7

Vì vậy, đó là cách bạn viết mô-đun bằng Python.

Vấn đề duy nhất với ví dụ này là khi bạn nhập mô-đun, nó sẽ chạy mã kiểm tra ở dưới cùng. Thông thường khi
bạn nhập một mô-đun, nó sẽ xác định các chức năng mới nhưng nó không chạy chúng.

Các chương trình sẽ được nhập dưới dạng mô-đun thường sử dụng thành ngữ sau:

nếu __name__ == '__main__':

print (linecount ('wc.py'))

__name__ là một biến tích hợp được đặt khi chương trình bắt đầu. Nếu chương trình đang chạy dưới dạng
script, __name__ có giá trị '__main__'; trong trường hợp đó, mã kiểm tra sẽ chạy. Ngược lại, nếu mô-đun đang
được nhập, mã kiểm tra sẽ bị bỏ qua.

Như một bài tập, hãy nhập ví dụ này vào một tệp có tên wc.py và chạy nó dưới dạng tập lệnh. Sau đó chạy
trình thông dịch Python và nhập wc. Giá trị của __name__ là gì khi mô-đun đang được nhập?

Cảnh báo: Nếu bạn nhập một mô-đun đã được nhập, Python sẽ không làm gì cả. Nó không đọc lại tệp, ngay cả khi
nó đã thay đổi.

Nếu bạn muốn tải lại một mô-đun, bạn có thể sử dụng chức năng tải lại tích hợp sẵn, nhưng nó có thể phức
tạp, vì vậy điều an toàn nhất cần làm là khởi động lại trình thông dịch và sau đó nhập lại mô-đun.

14.10 Gỡ lỗi
Khi bạn đang đọc và ghi tệp, bạn có thể gặp sự cố với khoảng trắng.
Những lỗi này có thể khó gỡ lỗi vì dấu cách, tab và dòng mới thường ẩn: >>> s = '1 2 \ t 3 \ n 4' >>> print

(s) 1 2 3

Chức năng repr tích hợp có thể giúp ích cho bạn. Nó nhận bất kỳ đối tượng nào làm đối số và trả về một biểu
diễn chuỗi của đối tượng. Đối với chuỗi, nó đại diện cho các ký tự khoảng trắng với các chuỗi dấu gạch chéo
ngược:

>>> print (repr (s)) '1


2 \ t 3 \ n 4'

Điều này có thể hữu ích cho việc gỡ lỗi.

Một vấn đề khác mà bạn có thể gặp phải là các hệ thống khác nhau sử dụng các ký tự khác nhau để biểu thị
phần cuối của một dòng. Một số hệ thống sử dụng một dòng mới, được biểu thị bằng \ n. Những người khác sử
dụng một ký tự trả về, được biểu diễn bằng \ r. Một số sử dụng cả hai. Nếu bạn di chuyển tệp giữa các hệ
thống khác nhau, những mâu thuẫn này có thể gây ra sự cố.

Đối với hầu hết các hệ thống, có các ứng dụng để chuyển đổi từ định dạng này sang định dạng khác. Bạn có
thể tìm thấy chúng (và đọc thêm về vấn đề này) tại http://en.wikipedia.org/wiki/Newline.

Hoặc, tất nhiên, bạn có thể tự viết một cái.


Machine Translated by Google

14.11. Bảng chú giải 145

14.11 Bảng chú giải thuật ngữ

dai dẳng: Đang duy trì một chương trình chạy vô thời hạn và giữ ít nhất một số

dữ liệu được lưu trữ vĩnh viễn.

toán tử định dạng: Một toán tử,%, nhận một chuỗi định dạng và một bộ và tạo một chuỗi bao gồm các phần tử của bộ

tuple được định dạng như được chỉ định bởi chuỗi định dạng.

chuỗi định dạng: Một chuỗi, được sử dụng với toán tử định dạng, chứa các chuỗi định dạng.

chuỗi định dạng: Một chuỗi các ký tự trong chuỗi định dạng, như% d, chỉ định cách
giá trị phải được định dạng.

tệp văn bản: Một chuỗi các ký tự được lưu trữ trong bộ nhớ vĩnh viễn như ổ cứng.

thư mục : Tập hợp các tệp được đặt tên, còn được gọi là thư mục.

path: Một chuỗi xác định một tệp.

đường dẫn tương đối: Đường dẫn bắt đầu từ thư mục hiện tại.

đường dẫn tuyệt đối: Đường dẫn bắt đầu từ thư mục trên cùng trong hệ thống tệp.

catch: Để ngăn một ngoại lệ chấm dứt chương trình bằng cách sử dụng trạng thái thử và ngoại trừ
ments.

cơ sở dữ liệu: Một tệp có nội dung được sắp xếp giống như một từ điển với các khóa tương ứng
thành các giá trị.

Đối tượng byte: Một đối tượng tương tự như một chuỗi.

shell: Một chương trình cho phép người dùng gõ lệnh và sau đó thực thi chúng bằng cách khởi động các chương

trình khác.

đối tượng ống: Một đối tượng đại diện cho một chương trình đang chạy, cho phép một chương trình Python chạy lệnh
và đọc kết quả.

14.12 Bài tập

Bài tập 14.1. Viết một hàm có tên sed nhận các đối số là một chuỗi mẫu, một chuỗi thay thế và hai tên tệp; nó

sẽ đọc tệp đầu tiên và ghi nội dung vào tệp thứ hai (tạo nó nếu cần). Nếu chuỗi mẫu xuất hiện ở bất kỳ đâu

trong tệp, thì nó phải được thay thế bằng chuỗi thay thế.

Nếu lỗi xảy ra khi mở, đọc, ghi hoặc đóng tệp, chương trình của bạn sẽ bắt được ngoại lệ, in thông báo lỗi và

thoát. Lời giải: http: // thinkpython2. com / code / sed. py

Bài tập 14.2. Nếu bạn tải lời giải của tôi cho Bài tập 12.2 từ http: // thinkpython2. com / code / anagram_

sets. py, bạn sẽ thấy rằng nó tạo ra một từ điển ánh xạ từ một chuỗi các chữ cái đã được sắp xếp đến danh sách

các từ có thể được đánh vần bằng các chữ cái đó. Ví dụ: "opst" ánh xạ tới danh sách ["opts", "post", "pot",
"spot", "stop", "top"].

Viết một mô-đun nhập các bộ đảo chữ cái và cung cấp hai hàm mới: store_anagrams nên lưu trữ từ điển đảo chữ

trong một “kệ”; read_anagrams sẽ tra cứu một từ và trả về một danh sách các từ đảo ngữ của nó. Lời giải: http: //

thinkpython2. com / code / anagram_ db. py


Machine Translated by Google

146 Chương 14. Tập tin

Bài tập 14.3. Trong một bộ sưu tập lớn các tệp MP3, có thể có nhiều hơn một bản sao của cùng một bài hát,

được lưu trữ trong các thư mục khác nhau hoặc với các tên tệp khác nhau. Mục tiêu của bài tập này là tìm kiếm

các bản sao.

1. Viết chương trình tìm kiếm một thư mục và tất cả các thư mục con của nó, một cách đệ quy và trả về một

danh sách các đường dẫn hoàn chỉnh cho tất cả các tệp có hậu tố cho trước (như .mp3). Gợi ý: os.path

cung cấp một số chức năng hữu ích để thao tác với tên tệp và đường dẫn.

2. Để nhận dạng các bản sao, bạn có thể sử dụng md5sum để tính “tổng kiểm tra” cho mỗi tệp. Nếu hai tệp

có tổng tổng kiểm giống nhau, chúng có thể có cùng nội dung.

3. Để kiểm tra kỹ, bạn có thể sử dụng lệnh Unix diff.

Lời giải: http: // thinkpython2. com / code / find_ trùng lặp. py


Machine Translated by Google

Chương 15

Các lớp và đối tượng

Tại thời điểm này, bạn đã biết cách sử dụng các hàm để tổ chức mã và các kiểu cài sẵn để tổ chức dữ
liệu. Bước tiếp theo là học “lập trình hướng đối tượng”, sử dụng các kiểu do người lập trình xác định
để tổ chức cả mã và dữ liệu. Lập trình hướng đối tượng là một chủ đề lớn; nó sẽ mất một vài chương để
đạt được điều đó.

Ví dụ về mã từ chương này có tại http://thinkpython2.com/code/ Point1.py; các giải pháp cho các bài tập
có sẵn từ http://thinkpython2.com/code/ Point1_soln.py.

15.1 Các kiểu do người lập trình xác định

Chúng tôi đã sử dụng nhiều kiểu tích hợp sẵn của Python; bây giờ chúng ta sẽ xác định một kiểu mới. Ví
dụ, chúng ta sẽ tạo một kiểu gọi là Point đại diện cho một điểm trong không gian hai chiều.

Trong ký hiệu toán học, điểm thường được viết trong ngoặc đơn với dấu phẩy ngăn cách các tọa độ. Ví
dụ: (0, 0) đại diện cho điểm gốc và (x, y) đại diện cho điểm x đơn vị ở bên phải và y đơn vị lên từ
điểm gốc.

Có một số cách chúng tôi có thể biểu diễn các điểm trong Python:

• Chúng ta có thể lưu trữ các tọa độ riêng biệt trong hai biến, x và y.

• Chúng ta có thể lưu trữ các tọa độ dưới dạng các phần tử trong một danh sách hoặc bộ dữ liệu.

• Chúng ta có thể tạo một kiểu mới để biểu diễn các điểm dưới dạng các đối tượng.

Tạo một kiểu mới phức tạp hơn các tùy chọn khác, nhưng nó có những ưu điểm sẽ sớm thấy rõ.

Kiểu do người lập trình xác định còn được gọi là lớp. Một định nghĩa lớp trông như thế này:

điểm lớp:

"" "Đại diện cho một điểm trong không gian 2-D." ""
Machine Translated by Google

148 Chương 15. Lớp và đối tượng

Điểm

trống x 3.0

y 4.0

Hình 15.1: Sơ đồ đối tượng.

Phần đầu chỉ ra rằng lớp mới được gọi là Point. Phần thân là một chuỗi tài liệu tạo ra những vùng nguyên liệu

mà lớp dành cho. Bạn có thể xác định các biến và phương thức bên trong định nghĩa lớp, nhưng chúng ta sẽ quay

lại điều đó sau.

Định nghĩa một lớp có tên là Point tạo ra một đối tượng của lớp.

>>> Điểm

<class '__main __. Point'>

Vì Point được xác định ở cấp cao nhất nên “tên đầy đủ” của nó là __main __. Point.

Đối tượng lớp giống như một nhà máy để tạo các đối tượng. Để tạo một Điểm, bạn gọi Điểm như thể nó là một hàm.

>>> blank = Point ()


>>> trống

<__ main __. Đối tượng Point tại 0xb7e9d3ac> Giá trị

trả về là một tham chiếu đến một đối tượng Point, được chúng tôi gán cho giá trị trống.

Tạo một đối tượng mới được gọi là khởi tạo và đối tượng là một thể hiện của lớp.

Khi bạn in một thể hiện, Python sẽ cho bạn biết nó thuộc lớp nào và nó được lưu trữ ở đâu trong bộ nhớ (tiền

tố 0x có nghĩa là số sau ở hệ thập lục phân).

Mọi đối tượng là một thể hiện của một số lớp, vì vậy “đối tượng” và “thể hiện” có thể hoán đổi cho nhau.

Nhưng trong chương này, tôi sử dụng “instance” để chỉ ra rằng tôi đang nói về một kiểu do lập trình viên xác

định.

15.2 Thuộc tính

Bạn có thể gán giá trị cho một phiên bản bằng cách sử dụng ký hiệu dấu chấm:

>>> blank.x = 3.0

>>> blank.y = 4.0 Cú

pháp này tương tự như cú pháp để chọn một biến từ một mô-đun, chẳng hạn như math.pi hoặc string.whitespace.

Tuy nhiên, trong trường hợp này, chúng ta đang gán giá trị cho các phần tử được đặt tên của một đối tượng. Các

phần tử này được gọi là thuộc tính.

Là một danh từ, "AT-Trib-inherit" được phát âm nhấn mạnh vào âm tiết đầu tiên, trái ngược với "a-TRIB-inherit",
là một động từ.

Hình 15.1 là một biểu đồ trạng thái cho thấy kết quả của các phép gán này. Biểu đồ trạng thái hiển thị một đối

tượng và các thuộc tính của nó được gọi là biểu đồ đối tượng.

Biến trống tham chiếu đến một đối tượng Point, chứa hai thuộc tính. Mỗi thuộc tính đề cập đến một số dấu phẩy

động.

Bạn có thể đọc giá trị của một thuộc tính bằng cách sử dụng cùng một cú pháp:
Machine Translated by Google

15.3. Hình chữ nhật 149

>>> blank.y 4.0

>>> x = blank.x
>>> x
3.0

Biểu thức blank.x có nghĩa là, "Chuyển đến đối tượng trống được tham chiếu đến và nhận giá trị của
x." Trong ví dụ, chúng tôi gán giá trị đó cho một biến có tên là x. Không có xung đột giữa biến x
và thuộc tính x.

Bạn có thể sử dụng ký hiệu dấu chấm như một phần của bất kỳ biểu thức

nào. Ví dụ: >>> '(% g,% g)'% (blank.x, blank.y) '(3.0, 4.0)' >>>
distance = math.sqrt (blank.x ** 2 + blank. y ** 2) >>> khoảng cách

5.0

Bạn có thể chuyển một thể hiện làm đối số theo cách thông thường. Ví dụ: def

print_point (p): print ('(% g,% g)'% (px, py))

print_point lấy một điểm làm đối số và hiển thị nó dưới dạng ký hiệu toán học. Để gọi nó, bạn có
thể chuyển trống làm đối số:

>>> print_point (trống)


(3.0, 4.0)

Bên trong hàm, p là bí danh cho giá trị trống, vì vậy nếu hàm sửa đổi p, thì giá trị trống sẽ thay đổi.

Như một bài tập, hãy viết một hàm có tên là distance_between_points nhận hai Điểm làm đối số và trả
về khoảng cách giữa chúng.

15.3 Hình chữ nhật

Đôi khi, rõ ràng các thuộc tính của một đối tượng phải là gì, nhưng những lúc khác, bạn phải đưa ra
quyết định. Ví dụ, hãy tưởng tượng bạn đang thiết kế một lớp để biểu diễn các hình chữ nhật.
Bạn sẽ sử dụng thuộc tính nào để chỉ định vị trí và kích thước của hình chữ nhật? Bạn có thể ig
nore angle; để giữ cho mọi thứ đơn giản, giả sử rằng hình chữ nhật là dọc hoặc ngang.

Có ít nhất hai khả năng:

• Bạn có thể chỉ định một góc của hình chữ nhật (hoặc tâm), chiều rộng và
Chiều cao.

• Bạn có thể chỉ định hai góc đối diện.

Tại thời điểm này, thật khó để nói liệu cái nào tốt hơn cái kia, vì vậy chúng tôi sẽ triển khai cái
đầu tiên, chỉ để làm ví dụ.

Đây là định nghĩa lớp:


Machine Translated by Google

150 Chương 15. Lớp và đối tượng

Hình chữ nhật

hộp bề rộng 100.0


Điểm
200.0
Chiều cao x 0,0
góc
y 0,0

Hình 15.2: Sơ đồ đối tượng.

class Rectangle: ""


"Đại diện cho một hình chữ nhật.

các thuộc tính: chiều rộng, chiều cao, góc.


"" "

Docstring liệt kê các thuộc tính: width và height là số; angle là một đối tượng Point chỉ định góc
dưới bên trái.

Để biểu diễn một hình chữ nhật, bạn phải khởi tạo một đối tượng Hình chữ nhật và gán giá trị cho các
thuộc tính:

box = Rectangle ()
box.width = 100.0

box.height = 200.0
box.corner = Point ()
box.corner.x = 0.0

box.corner.y = 0.0

Biểu thức box.corner.x có nghĩa là, “Chuyển đến hộp đối tượng được tham chiếu đến và chọn thuộc tính
có tên là góc; sau đó chuyển đến đối tượng đó và chọn thuộc tính có tên là x. ”

Hình 15.2 cho thấy trạng thái của đối tượng này. Một đối tượng là một thuộc tính của một đối tượng
khác được nhúng.

15.4 Các trường hợp dưới dạng giá trị trả về

Các hàm có thể trả về các phiên bản. Ví dụ: find_center lấy một Hình chữ nhật làm điểm đối chứng và
trả về một Điểm chứa tọa độ của tâm Hình chữ nhật:

def find_center (trực tràng):

p = Point ()
px = trực tràng.corner.x + trực tràng.width /
2 py = trực tràng.corner.y + trực tràng.height /
2 trả về p

Đây là một ví dụ chuyển hộp làm đối số và gán Điểm kết quả cho
trung tâm:

>>> center = find_center (box) >>>

print_point (center) (50, 100)


Machine Translated by Google

15,5. Các đối tượng có thể thay đổi 151

15.5 Đối tượng có thể thay đổi

Bạn có thể thay đổi trạng thái của một đối tượng bằng cách gán cho một trong các thuộc tính của nó.
Ví dụ: để thay đổi kích thước của hình chữ nhật mà không thay đổi vị trí của nó, bạn có thể sửa đổi
các giá trị của chiều rộng và chiều cao:

box.width = box.width + 50

box.height = box.height + 100 Bạn cũng

có thể viết các hàm sửa đổi đối tượng. Ví dụ: grow_rectangle lấy một đối tượng Hình chữ nhật và hai
số, d width và dheight, đồng thời thêm các số vào chiều rộng và chiều cao của hình chữ nhật: def
grow_rectangle (direct, dwidth, dheight):

trực tràng.width + = dwidth

direct.height + = dheight Đây

là một ví dụ minh họa hiệu ứng: >>> box.width,

box.height (150.0, 300.0) >>> grow_rectangle (box,


50, 100) >>> box.width, box. chiều cao (200.0, 400.0)

Bên trong hàm, direct là một bí danh cho hộp, vì vậy khi hàm sửa đổi hàm, hộp sẽ thay đổi.

Như một bài tập, viết một hàm có tên là move_rectangle nhận một Hình chữ nhật và hai số có tên là dx
và dy. Nó sẽ thay đổi vị trí của hình chữ nhật bằng cách thêm dx vào tọa độ x của góc và thêm dy vào
tọa độ y của góc.

15.6 Sao chép

Việc đặt biệt hiệu có thể làm cho một chương trình khó đọc vì những thay đổi ở một nơi có thể có những
tác động không mong muốn ở nơi khác. Thật khó để theo dõi tất cả các biến có thể tham chiếu đến một
đối tượng nhất định.

Sao chép một đối tượng thường là một giải pháp thay thế cho răng cưa. Mô-đun sao chép chứa một hàm
được gọi là sao chép có thể sao chép bất kỳ đối tượng nào: >>> p1 = Point () >>> p1.x = 3.0 >>> p1.y

= 4.0

>>> nhập bản sao


>>> p2 = copy.copy (p1) p1

và p2 chứa cùng một dữ liệu, nhưng chúng không giống nhau Điểm. >>>

print_point (p1) (3, 4) >>> print_point (p2) (3, 4) >>> p1 is p2 False


Machine Translated by Google

152 Chương 15. Lớp và đối tượng

hộp bề rộng 100.0 100.0 bề rộng box2

200.0 200.0
Chiều cao x 0,0 Chiều cao

góc góc
y 0,0

Hình 15.3: Sơ đồ đối tượng.

>>> p1 == p2
Sai

Toán tử is chỉ ra rằng p1 và p2 không phải là cùng một đối tượng, đó là những gì chúng ta
đã bỏ qua. Nhưng bạn có thể mong đợi == để mang lại True vì những điểm này chứa cùng một dữ
liệu. Trong trường hợp đó, bạn sẽ thất vọng khi biết rằng đối với các trường hợp, hành vi
mặc định của toán tử == giống với toán tử is; nó kiểm tra nhận dạng đối tượng, không phải
đối tượng tương đương. Đó là bởi vì đối với các kiểu do lập trình viên xác định, Python
không biết những gì nên được coi là tương đương. Ít nhất là chưa.

Nếu bạn sử dụng copy.copy để sao chép một Hình chữ nhật, bạn sẽ thấy rằng nó sao chép đối tượng
Hình chữ nhật nhưng không sao chép Điểm được nhúng. >>> box2 = copy.copy (box) >>> box2 là hộp

Sai
>>> box2.corner là box.corner
ĐÚNG VẬY

Hình 15.3 cho thấy sơ đồ đối tượng trông như thế nào. Thao tác này được gọi là sao chép nông vì
nó sao chép đối tượng và bất kỳ tham chiếu nào mà nó chứa, nhưng không sao chép các đối tượng
được nhúng.

Đối với hầu hết các ứng dụng, đây không phải là điều bạn muốn. Trong ví dụ này, việc gọi
grow_rectangle trên một trong các Hình chữ nhật sẽ không ảnh hưởng đến hình còn lại, nhưng việc
gọi move_rectangle trên một trong hai sẽ ảnh hưởng đến cả hai! Hành vi này là khó hiểu và dễ xảy ra lỗi.

May mắn thay, mô-đun sao chép cung cấp một phương thức có tên là deepcopy để sao chép không chỉ
đối tượng mà còn các đối tượng mà nó tham chiếu đến và các đối tượng mà chúng tham chiếu đến,
v.v. Bạn sẽ không ngạc nhiên khi biết rằng thao tác này được gọi là bản sao sâu. >>> box3 =

copy.deepcopy (box) >>> box3 là hộp

Sai
>>> box3.corner là box.corner
Sai

box3 và box là những đối tượng hoàn toàn riêng biệt.

Như một bài tập, hãy viết một phiên bản của move_rectangle để tạo và trả về một Hình chữ
nhật mới thay vì sửa đổi hình cũ.

15.7 Gỡ lỗi
Khi bạn bắt đầu làm việc với các đối tượng, bạn có thể gặp phải một số ngoại lệ mới. Nếu bạn cố
gắng truy cập một thuộc tính không tồn tại, bạn sẽ nhận được lỗi AttributeError:
Machine Translated by Google

15,8. Bảng chú giải 153

>>> p = Point () >>>

px = 3 >>> py = 4 >>>

pz AttributeError:

Point instance không


có thuộc tính 'z'

Nếu bạn không chắc kiểu đối tượng là gì, bạn có thể hỏi: >>> type

(p) <class '__main __. Point'>

Bạn cũng có thể sử dụng isinstance để kiểm tra xem một đối tượng có phải là một thể hiện của một lớp

hay không: >>> isinstance (p, Point)


ĐÚNG VẬY

Nếu bạn không chắc liệu một đối tượng có một thuộc tính cụ thể hay không, bạn có thể sử dụng hàm hasattr tích
hợp sẵn:

>>> hasattr (p, 'x')


ĐÚNG VẬY

>>> hasattr (p, 'z')


Sai

Đối số đầu tiên có thể là bất kỳ đối tượng nào; đối số thứ hai là một chuỗi chứa tên của thuộc tính.

Bạn cũng có thể sử dụng câu lệnh try để xem liệu đối tượng có các thuộc tính bạn cần hay không:

thử:

x = px

ngoại trừ AttributeError: x = 0

Cách tiếp cận này có thể giúp viết các hàm hoạt động với các kiểu khác nhau dễ dàng hơn; sẽ có thêm về chủ đề

đó trong Phần 17.9.

15.8 Lớp thuật ngữ: Một

kiểu do người lập trình xác định. Một định nghĩa lớp tạo ra một đối tượng lớp mới.

đối tượng lớp: Một đối tượng chứa thông tin về kiểu do người lập trình xác định. Đối tượng lớp có thể được sử

dụng để tạo các thể hiện của kiểu.

instance: Một đối tượng thuộc về một lớp.

Instantiate: Để tạo một đối tượng mới.

thuộc tính: Một trong các giá trị được đặt tên được liên kết với một đối tượng.

đối tượng nhúng: Một đối tượng được lưu trữ như một thuộc tính của một đối tượng khác.

bản sao cạn: Để sao chép nội dung của một đối tượng, bao gồm bất kỳ tham chiếu nào đến các đối tượng được

nhúng; được thực hiện bởi chức năng sao chép trong mô-đun sao chép.

bản sao sâu: Để sao chép nội dung của một đối tượng cũng như bất kỳ đối tượng được nhúng nào và bất kỳ đối

tượng nào được nhúng trong chúng, v.v.; được thực hiện bởi chức năng deepcopy trong mô-đun sao chép.

sơ đồ đối tượng: Một sơ đồ hiển thị các đối tượng, thuộc tính của chúng và giá trị của at
cống phẩm.
Machine Translated by Google

154 Chương 15. Lớp và đối tượng

15.9 Bài tập

Bài tập 15.1. Viết định nghĩa cho một lớp có tên Circle với các thuộc tính tâm và bán kính, trong đó tâm
là một đối tượng Điểm và bán kính là một số.

Khởi tạo đối tượng Hình tròn đại diện cho một hình tròn với tâm của nó là (150, 100) và bán kính 75.

Viết một hàm có tên point_in_circle lấy một Đường tròn và một Điểm và trả về giá trị True nếu Điểm đó nằm
trong hoặc trên ranh giới của đường tròn.

Viết một hàm có tên là direct_in_circle lấy Hình tròn và Hình chữ nhật và trả về giá trị True nếu Hình chữ
nhật nằm hoàn toàn trong hoặc trên ranh giới của hình tròn.

Viết một hàm có tên là direct_circle_overlap lấy Hình tròn và Hình chữ nhật và trả về giá trị True nếu bất
kỳ góc nào của Hình chữ nhật nằm bên trong Hình tròn. Hoặc như một phiên bản thử thách hơn, hãy trả về
True nếu bất kỳ phần nào của Hình chữ nhật nằm trong Vòng tròn.

Lời giải: http: // thinkpython2. com / code / Circle. py

Bài tập 15.2. Viết một hàm có tên là draw_rect lấy một đối tượng Rùa và một Hình chữ nhật và sử dụng Rùa
để vẽ Hình chữ nhật. Xem Chương 4 để biết các ví dụ sử dụng các đối tượng Rùa.

Viết một hàm có tên là draw_circle lấy Rùa và Vòng tròn và vẽ Hình tròn.

Lời giải: http: // thinkpython2. com / code / draw. py


Machine Translated by Google

Chương 16

Các lớp và chức năng

Bây giờ chúng ta đã biết cách tạo các kiểu mới, bước tiếp theo là viết các hàm lấy các đối tượng do người
lập trình xác định làm tham số và trả về chúng dưới dạng kết quả. Trong chương này, tôi cũng trình bày
“phong cách lập trình chức năng” và hai kế hoạch phát triển chương trình mới.

Ví dụ về mã từ chương này có tại http://thinkpython2.com/code/ Time1.py. Giải pháp cho các bài tập có tại
http://thinkpython2.com/code/Time1_soln.
py

16.1 Thời gian

Như một ví dụ khác về kiểu do lập trình viên xác định, chúng ta sẽ định nghĩa một lớp gọi là Thời gian ghi
lại thời gian trong ngày. Định nghĩa lớp trông như thế này:

giơ lên lơp:

"" "Đại diện cho thời gian trong ngày.

thuộc tính: giờ, phút, giây


"" "

Chúng tôi có thể tạo một đối tượng Thời gian mới và gán các thuộc tính cho giờ, phút và giây:

time = Thời gian ()


time.hour = 11

time.minute = 59
time.second = 30

Biểu đồ trạng thái cho đối tượng Time có dạng như Hình 16.1.

Như một bài tập, viết một hàm có tên print_time lấy một đối tượng Time và in nó ra dưới dạng giờ: phút:
giây. Gợi ý: dãy định dạng '% .2d' in ra một số nguyên sử dụng ít nhất hai chữ số, bao gồm cả số 0 ở đầu
nếu cần.

Viết một hàm boolean được gọi là is_ after nhận hai đối tượng Thời gian, t1 và t2, và lại chuyển thành True
nếu t1 theo thứ tự thời gian t2 và False ngược lại. Thách thức: không sử dụng câu lệnh if.
Machine Translated by Google

156 Chương 16. Các lớp và chức năng

Thời gian

thời gian giờ 11

phút 59

thứ hai 30

Hình 16.1: Sơ đồ đối tượng.

16.2 Các chức năng thuần túy

Trong vài phần tiếp theo, chúng ta sẽ viết hai hàm thêm giá trị thời gian. Chúng thể hiện hai loại chức
năng: chức năng thuần túy và chức năng sửa đổi. Họ cũng chứng minh một kế hoạch phát triển mà tôi sẽ gọi
là nguyên mẫu và bản vá, đó là một cách giải quyết một vấn đề phức tạp bằng cách bắt đầu với một nguyên
mẫu đơn giản và từng bước giải quyết các biến chứng.

Đây là một nguyên mẫu đơn giản của add_time:

def add_time (t1, t2): sum = Time () sum.hour


= t1.hour + t2.hour

sum.minute = t1.minute + t2.minute


sum.second = t1.second + t2.second
trả lại tổng

Hàm tạo một đối tượng Thời gian mới, khởi tạo các thuộc tính của nó và trả về một tham chiếu đến đối
tượng mới. Đây được gọi là một hàm thuần túy vì nó không sửa đổi bất kỳ đối tượng nào được truyền cho
nó dưới dạng đối số và nó không có tác dụng, chẳng hạn như hiển thị một giá trị hoặc nhận đầu vào của
người dùng, ngoài việc trả về một giá trị.

Để kiểm tra chức năng này, tôi sẽ tạo hai đối tượng Thời gian: start chứa thời gian bắt đầu của một bộ
phim, như Monty Python và Holy Grail, và thời lượng chứa thời gian chạy của bộ phim, là một giờ 35 phút.

add_time tính khi nào bộ phim sẽ được hoàn thành. >>> start

= Time () >>> start.hour = 9

>>> start.minute = 45
>>> start.second = 0

>>> thời lượng = Thời gian


() >>> thời lượng.giờ = 1
>>> thời lượng.minute = 35
>>> thời lượng.second = 0

>>> done = add_time (bắt đầu, thời lượng) >>>

print_time (thực hiện) 10:80:00

Kết quả, 10:80:00 có thể không phải là điều bạn mong đợi. Vấn đề là chức năng này không giải quyết các
trường hợp mà số giây hoặc phút cộng lại nhiều hơn sáu mươi. Khi điều đó xảy ra, chúng ta phải "mang"
thêm giây vào cột phút hoặc phút phụ vào cột giờ.

Đây là một phiên bản cải tiến:


Machine Translated by Google

16.3. Bổ ngữ 157

def add_time (t1, t2): sum =


Time ()
sum.hour = t1.hour + t2.hour
sum.minute = t1.minute + t2.minute
sum.second = t1.second + t2.second

nếu sum.second> = 60:


sum.second - = 60
sum.minute + = 1

nếu sum.minute> = 60:


sum.minute - = 60
sum.hour + = 1

trả lại tổng

Mặc dù chức năng này là chính xác, nhưng nó đang bắt đầu trở nên lớn hơn. Chúng ta sẽ thấy một giải pháp thay thế ngắn
hơn sau.

16.3 Bổ ngữ

Đôi khi, nó hữu ích cho một hàm để sửa đổi các đối tượng mà nó nhận được dưới dạng tham số. Trong trường
hợp đó, người gọi có thể nhìn thấy các thay đổi. Các hàm hoạt động theo cách này được gọi là bổ ngữ.

tăng, có thể thêm một số giây nhất định vào đối tượng Thời gian, có thể được viết tự nhiên dưới dạng bổ
ngữ. Đây là bản nháp sơ bộ: tăng số def (thời gian, giây):

time.second + = giây

nếu time.second> = 60:


time.second - = 60
time.minute + = 1

nếu time.minute> = 60:


time.minute - = 60
time.hour + = 1

Dòng đầu tiên thực hiện thao tác cơ bản; phần còn lại giải quyết các trường hợp đặc biệt mà chúng ta đã
thấy trước đây.

Chức năng này có đúng không? Điều gì xảy ra nếu giây lớn hơn nhiều hơn sáu mươi?

Trong trường hợp đó, nó là không đủ để thực hiện một lần; chúng ta phải tiếp tục làm điều đó cho đến khi
thời gian. giây là ít hơn sáu mươi. Một giải pháp là thay thế các câu lệnh if bằng câu lệnh while. Điều đó
sẽ làm cho chức năng chính xác, nhưng không hiệu quả lắm. Như một bài tập, hãy viết một phiên bản đúng của
số tăng không chứa bất kỳ vòng lặp nào.

Bất cứ điều gì có thể được thực hiện với bổ ngữ cũng có thể được thực hiện với các chức năng thuần túy.
Trên thực tế, một số ngôn ngữ lập trình chỉ cho phép các chức năng thuần túy. Có một số bằng chứng cho thấy
các chương trình sử dụng các hàm thuần túy sẽ phát triển nhanh hơn và ít bị lỗi hơn các chương trình sử dụng
bổ ngữ. Nhưng các công cụ sửa đổi đôi khi rất tiện lợi và các chương trình chức năng có xu hướng kém hiệu
quả hơn.
Machine Translated by Google

158 Chương 16. Các lớp và chức năng

Nói chung, tôi khuyên bạn nên viết các hàm thuần túy bất cứ khi nào thấy hợp lý và chỉ sử dụng các công
cụ sửa đổi nếu có một lợi thế hấp dẫn. Cách tiếp cận này có thể được gọi là một phong cách lập trình chức
năng.

Như một bài tập, hãy viết một phiên bản gia số “thuần túy” để tạo và trả về một đối tượng Thời gian mới
thay vì sửa đổi tham số.

16.4 Tạo mẫu so với lập kế hoạch


Kế hoạch phát triển mà tôi đang trình diễn được gọi là "nguyên mẫu và bản vá". Đối với mỗi chức năng,
tôi viết một nguyên mẫu thực hiện phép tính cơ bản và sau đó thử nghiệm nó, vá các lỗi trong quá trình
thực hiện.

Cách tiếp cận này có thể hiệu quả, đặc biệt nếu bạn chưa hiểu sâu về vấn đề. Tuy nhiên, các chỉnh sửa gia
tăng có thể tạo ra mã phức tạp không cần thiết - vì nó xử lý nhiều trường hợp đặc biệt - và không đáng
tin cậy - vì rất khó để biết liệu bạn đã tìm thấy tất cả các lỗi hay chưa.

Một giải pháp thay thế là phát triển được thiết kế, trong đó cái nhìn sâu sắc về vấn đề có thể làm cho
việc lập trình dễ dàng hơn nhiều. Trong trường hợp này, thông tin chi tiết là đối tượng Thời gian thực sự
là một số có ba chữ số trong cơ số 60 (xem http://en.wikipedia.org/wiki/Sexagesimal). Thuộc tính thứ hai
là “cột một”, thuộc tính phút là “cột sáu mươi” và thuộc tính giờ là “cột ba mươi sáu trăm”.

Khi chúng tôi viết add_time và increment, chúng tôi đã thực hiện hiệu quả phép cộng trong cơ số 60, đó là
lý do tại sao chúng tôi phải chuyển từ cột này sang cột tiếp theo.

Quan sát này gợi ý một cách tiếp cận khác cho toàn bộ vấn đề — chúng ta có thể chuyển đổi các đối tượng
Thời gian thành số nguyên và tận dụng lợi thế của thực tế là máy tính biết cách tính số nguyên.

Đây là một hàm chuyển đổi Thời gian thành số nguyên: def

time_to_int (time):
phút = thời gian. giờ * 60 + thời gian. phút
giây = phút * 60 + time.second
trả lại giây

Và đây là một chức năng chuyển đổi một số nguyên thành một Thời gian (nhớ lại rằng divmod chia đối số đầu
tiên cho đối số thứ hai và trả về thương số và phần dư dưới dạng một bộ giá trị). def int_to_time (giây):

time = Time ()

phút, time.second = divmod (giây, 60) time.giur,


time.minute = divmod (phút, 60) thời gian trả về

Bạn có thể phải suy nghĩ một chút và chạy một số thử nghiệm để thuyết phục bản thân rằng các chức năng
này là đúng. Một cách để kiểm tra chúng là kiểm tra time_to_int (int_to_time (x)) == x cho nhiều giá trị
của x. Đây là một ví dụ về kiểm tra tính nhất quán.

Khi bạn tin rằng chúng đúng, bạn có thể sử dụng chúng để viết lại add_time: def add_time (t1,

t2): seconds = time_to_int (t1) + time_to_int (t2) return int_to_time (giây)


Machine Translated by Google

16,5. Gỡ lỗi 159

Phiên bản này ngắn hơn phiên bản gốc và dễ xác minh hơn. Như một bài tập, hãy viết lại số tăng bằng time_to_int

và int_to_time.

Theo một số cách, chuyển đổi từ cơ sở 60 sang cơ sở 10 và trở lại khó hơn là chỉ xử lý theo thời gian. Chuyển

đổi cơ sở là trừu tượng hơn; trực giác của chúng ta để xử lý các giá trị thời gian tốt hơn.

Nhưng nếu chúng ta có cái nhìn sâu sắc để coi thời gian là số cơ số 60 và đầu tư vào việc viết các hàm chuyển

đổi (time_to_int và int_to_time), chúng ta sẽ nhận được một chương trình ngắn hơn, dễ đọc và gỡ lỗi hơn và

đáng tin cậy hơn.

Nó cũng dễ dàng hơn để thêm các tính năng sau đó. Ví dụ, hãy tưởng tượng trừ hai lần để tìm khoảng thời gian

giữa chúng. Cách tiếp cận ngây thơ sẽ là thực hiện phép trừ với sự vay mượn. Sử dụng các hàm chuyển đổi sẽ dễ

dàng hơn và có nhiều khả năng chính xác hơn.

Trớ trêu thay, đôi khi làm cho một vấn đề khó hơn (hoặc tổng quát hơn) lại dễ dàng hơn (vì có ít trường hợp

đặc biệt hơn và ít cơ hội mắc lỗi hơn).

16.5 Gỡ lỗi
Đối tượng Thời gian được hình thành tốt nếu giá trị của phút và giây nằm trong khoảng từ 0 đến 60 (bao gồm cả

0 nhưng không phải 60) và nếu giờ là số dương. giờ và phút phải là giá trị số nguyên, nhưng chúng tôi có thể

cho phép giây có một phần phân số.

Các yêu cầu như thế này được gọi là bất biến vì chúng phải luôn đúng. Nói một cách khác, nếu chúng không đúng,

thì đã có gì đó sai.

Viết mã để kiểm tra các bất biến có thể giúp phát hiện lỗi và tìm ra nguyên nhân của chúng. Ví dụ: bạn có thể

có một hàm như valid_time lấy đối tượng Time và trả về False nếu nó vi phạm bất biến:

def valid_time (thời gian):


nếu time.hour <0 hoặc time.minute <0 hoặc time.second <0:

trả về Sai

nếu time.minute> = 60 hoặc time.second> = 60:

trả về Sai

trả về True

Ở đầu mỗi hàm, bạn có thể kiểm tra các đối số để đảm bảo chúng hợp lệ:

def add_time (t1, t2): nếu


không phải valid_time (t1) hoặc không valid_time (t2):

nâng ValueError ('đối tượng Thời gian không hợp lệ trong add_time')
seconds = time_to_int (t1) + time_to_int (t2) return int_to_time (giây)

Hoặc bạn có thể sử dụng câu lệnh khẳng định, câu lệnh này sẽ kiểm tra một bất biến nhất định và tăng một tỷ lệ ngoại lệ nếu
nó không thành công:

def add_time (t1, t2):


khẳng định valid_time (t1) và valid_time (t2) seconds =

time_to_int (t1) + time_to_int (t2) return int_to_time (giây)

các câu lệnh khẳng định rất hữu ích vì chúng phân biệt mã xử lý các điều kiện bình thường với mã kiểm tra lỗi.
Machine Translated by Google

160 Chương 16. Các lớp và chức năng

16.6 Bảng chú giải thuật ngữ

nguyên mẫu và bản vá: Một kế hoạch phát triển bao gồm việc viết một bản nháp sơ bộ của một chương trình
chuyên nghiệp, thử nghiệm và sửa lỗi khi chúng được tìm thấy.

phát triển được thiết kế: Một kế hoạch phát triển bao gồm cái nhìn sâu sắc cấp cao về vấn đề và lập kế
hoạch nhiều hơn so với phát triển gia tăng hoặc phát triển nguyên mẫu
cố vấn.

pure function: Một hàm không sửa đổi bất kỳ đối tượng nào mà nó nhận làm đối số.
Hầu hết các chức năng thuần túy đều có kết quả.

modifier: Một hàm thay đổi một hoặc nhiều đối tượng mà nó nhận làm đối số. Phần lớn
bổ ngữ là vô hiệu; nghĩa là, họ trả về Không có.

phong cách lập trình chức năng: Một phong cách thiết kế chương trình trong đó phần lớn func
tions là tinh khiết.

invariant: Điều kiện luôn đúng trong quá trình thực thi chương trình.

tuyên bố khẳng định: Một câu lệnh kiểm tra một điều kiện và đưa ra một ngoại lệ nếu nó không thành công.

16.7 Bài tập

Ví dụ về mã từ chương này có tại http://thinkpython2.com/code/ Time1.py; các giải pháp cho các bài tập có
sẵn từ http://thinkpython2.com/code/ Time1_soln.py.

Bài tập 16.1. Viết một hàm gọi là mul_time lấy đối tượng Thời gian và một số và trả về đối tượng Thời gian
mới có chứa tích của Thời gian và số ban đầu.

Sau đó, sử dụng mul_time để viết một hàm lấy đối tượng Thời gian đại diện cho thời gian kết thúc trong một
cuộc đua và một số đại diện cho khoảng cách và trả về đối tượng Thời gian đại diện cho tốc độ trung bình
(thời gian trên một dặm).
Bài tập 16.2. Mô-đun datetime cung cấp các đối tượng thời gian tương tự như các đối tượng Thời gian trong
chương này, nhưng chúng cung cấp một tập hợp các phương thức và toán tử phong phú. Đọc tài liệu tại http: //
docs. con trăn. org / 3 / library / datetime. html .

1. Sử dụng mô-đun datetime để viết chương trình lấy ngày hiện tại và in ngày
tuần.

2. Viết chương trình lấy ngày sinh làm đầu vào và in tuổi của người dùng và số ngày, giờ, phút và giây
cho đến sinh nhật tiếp theo của họ.

3. Đối với hai người sinh vào những ngày khác nhau thì có ngày người ta gấp đôi tuổi người kia.
Đó là Ngày chung đôi của họ. Viết chương trình lấy hai ngày sinh và tính Ngày chung đôi của họ.

4. Để có thêm một chút thử thách, hãy viết phiên bản tổng quát hơn tính ngày khi một
người này hơn người kia n lần.

Lời giải: http: // thinkpython2. com / code / double. py


Machine Translated by Google

Chương 17

Các lớp và phương pháp

Mặc dù chúng tôi đang sử dụng một số tính năng hướng đối tượng của Python, các chương trình từ hai chương
cuối không thực sự hướng đối tượng vì chúng không đại diện cho mối quan hệ giữa các kiểu do người lập trình
xác định và các hàm hoạt động trên chúng. Bước tiếp theo là chuyển các hàm đó thành các phương thức làm cho
các mối quan hệ trở nên rõ ràng.

Ví dụ mã từ chương này có sẵn trên http://thinkpython2.com/code/ Time2.py và lời giải cho các bài tập có
trong http://thinkpython2.com/code/Point2_ soln.py.

17.1 Các tính năng hướng đối tượng

Python là một ngôn ngữ lập trình hướng đối tượng, có nghĩa là nó cung cấp các tính năng hỗ trợ lập trình
hướng đối tượng, có các đặc điểm xác định sau:

• Chương trình bao gồm các định nghĩa về lớp và phương thức.

• Hầu hết các tính toán được thể hiện dưới dạng các hoạt động trên các đối tượng.

• Các đối tượng thường đại diện cho những thứ trong thế giới thực, và các phương pháp thường tương ứng với

những cách mà mọi thứ trong thế giới thực tương tác.

Ví dụ: lớp Thời gian được định nghĩa trong Chương 16 tương ứng với cách mọi người ghi lại thời gian trong
ngày và các hàm chúng tôi đã xác định tương ứng với các loại công việc mọi người làm với thời gian. Tương
tự, các lớp Point và Rectangle trong Chương 15 tương ứng với các khái niệm toán học của một điểm và một
hình chữ nhật.

Cho đến nay, chúng ta vẫn chưa tận dụng được các tính năng mà Python cung cấp để hỗ trợ lập trình hướng
đối tượng. Những tính năng này không hoàn toàn cần thiết; hầu hết chúng cung cấp cú pháp thay thế cho những
thứ chúng tôi đã làm. Nhưng trong nhiều trường hợp, giải pháp thay thế ngắn gọn hơn và truyền tải chính xác
hơn cấu trúc của chương trình.

Ví dụ, trong Time1.py không có mối liên hệ rõ ràng nào giữa định nghĩa lớp và định nghĩa hàm theo sau. Với
một số kiểm tra, rõ ràng là mỗi hàm chức năng lấy ít nhất một đối tượng Thời gian làm đối số.
Machine Translated by Google

162 Chương 17. Các lớp và phương pháp

Quan sát này là động lực cho các phương pháp; một phương thức là một hàm được liên kết với một lớp cụ

thể. Chúng tôi đã thấy các phương thức cho chuỗi, danh sách, từ điển và bộ giá trị. Trong chương này,
chúng ta sẽ định nghĩa các phương thức cho các kiểu do người lập trình xác định.

Về mặt ngữ nghĩa, các phương thức giống như các hàm, nhưng có hai điểm khác biệt về cú pháp:

• Các phương thức được định nghĩa bên trong định nghĩa lớp để làm cho mối quan hệ giữa lớp và
phương thức rõ ràng.

• Cú pháp để gọi một phương thức khác với cú pháp để gọi một hàm.

Trong vài phần tiếp theo, chúng ta sẽ lấy các hàm từ hai chương trước và biến đổi chúng thành các phương
thức. Sự biến đổi này hoàn toàn là cơ học; bạn có thể làm điều đó bằng cách làm theo một trình tự các
bước. Nếu bạn cảm thấy thoải mái khi chuyển đổi từ hình thức này sang hình thức khác, bạn sẽ có thể
chọn hình thức tốt nhất cho bất cứ điều gì bạn đang làm.

17.2 Đối tượng in

Trong Chương 16, chúng tôi đã định nghĩa một lớp có tên là Thời gian và trong Phần 16.1, bạn đã viết
một hàm có tên print_time:

giơ lên lơp:

"" "Đại diện cho thời gian trong ngày." ""

def print_time (time): print


('%. 2d:%. 2d:%. 2d'% (time.hour, time.minute, time.second))

Để gọi hàm này, bạn phải chuyển đối tượng Thời gian làm đối số:

>>> start = Time ()


>>> start.hour = 9
>>> start.minute = 45
>>> start.second = 00

>>> print_time (bắt đầu)


09:45:00

Để biến print_time thành một phương thức, tất cả những gì chúng ta phải làm là di chuyển định nghĩa hàm

vào bên trong định nghĩa lớp. Lưu ý sự thay đổi trong thụt lề.

giơ lên lơp:

def print_time (time): print


('%. 2d:%. 2d:%. 2d'% (time.hour, time.minute, time.second))

Bây giờ có hai cách để gọi print_time. Cách đầu tiên (và ít phổ biến hơn) là sử dụng cú pháp hàm: >>>
Time.print_time (start) 09:45:00

Trong cách sử dụng ký hiệu dấu chấm này, Time là tên của lớp và print_time là tên của phương thức. start

được truyền dưới dạng tham số.

Cách thứ hai (và ngắn gọn hơn) là sử dụng cú pháp phương thức: >>>

start.print_time () 09:45:00
Machine Translated by Google

17.3. Mô t vi du khac 163

Trong cách sử dụng ký hiệu dấu chấm này, print_time là tên của phương thức (một lần nữa) và start là đối
tượng mà phương thức được gọi, được gọi là chủ đề. Cũng giống như chủ đề của câu là câu nói về điều gì,
chủ đề của lời gọi phương thức là phương thức nói về điều gì.

Bên trong phương thức, chủ thể được gán cho tham số đầu tiên, vì vậy trong trường hợp này, start được gán
cho thời gian.

Theo quy ước, tham số đầu tiên của một phương thức được gọi là self, vì vậy sẽ phổ biến hơn khi viết
print_time như thế này:

giơ lên lơp:

def print_time (self): print


('%. 2d:%. 2d:%. 2d'% (self.hour, self.minute, self.second))

Lý do cho quy ước này là một phép ẩn dụ ngầm:

• Cú pháp của một lệnh gọi hàm, print_time (start), gợi ý rằng hàm là tác nhân hoạt động. Nó nói một
cái gì đó như, “Này print_time! Đây là một đối tượng để bạn in. "

• Trong lập trình hướng đối tượng, các đối tượng là tác nhân tích cực. Một phương thức hóa đơn như
start.print_time () cho biết “Này hãy bắt đầu! Hãy tự in đi ”.

Sự thay đổi trong quan điểm này có thể lịch sự hơn, nhưng không rõ ràng là nó hữu ích. Trong các ví dụ mà
chúng ta đã thấy cho đến nay, nó có thể không. Nhưng đôi khi việc chuyển trách nhiệm từ các chức năng
sang các đối tượng làm cho nó có thể viết các hàm (hoặc phương thức) linh hoạt hơn, đồng thời giúp bảo
trì và sử dụng lại mã dễ dàng hơn.

Như một bài tập, hãy viết lại time_to_int (từ Phần 16.4) dưới dạng một phương thức. Bạn cũng có thể bị
cám dỗ để viết lại int_to_time như một phương thức, nhưng điều đó không thực sự có ý nghĩa vì sẽ không có
đối tượng nào để gọi nó.

17.3 Một ví dụ khác


Đây là một phiên bản của tăng (từ Phần 16.3) được viết lại dưới dạng một phương thức:

# bên trong lớp học Thời gian:

gia số def (bản thân, giây):


seconds + = self.time_to_int () trả về
int_to_time (giây)

Phiên bản này giả định rằng time_to_int được viết dưới dạng một phương thức. Ngoài ra, hãy lưu ý rằng nó là một hàm thuần túy,

không phải là một công cụ sửa đổi.

Đây là cách bạn sẽ gọi tăng: >>> start.print_time

() 09:45:00

>>> end = start.increment (1337) >>>

end.print_time () 10:07:17
Machine Translated by Google

164 Chương 17. Các lớp và phương pháp

Chủ đề, bắt đầu, được gán cho tham số đầu tiên, self. Đối số, 1337, được gán cho tham số thứ hai, giây.

Cơ chế này có thể gây nhầm lẫn, đặc biệt nếu bạn mắc lỗi. Ví dụ: nếu bạn gọi số tăng có hai đối số, bạn sẽ

nhận được:

>>> end = start.increment (1337, 460)

TypeError: increment () nhận 2 đối số vị trí nhưng 3 đối số được đưa ra Thông báo lỗi ban đầu rất

khó hiểu, bởi vì chỉ có hai đối số trong các luận đề dấu ngoặc. Nhưng chủ đề cũng được coi là một lý lẽ, vì

vậy tất cả chỉ có ba.

Nhân tiện, một đối số vị trí là một đối số không có tên tham số; nghĩa là, nó không phải là một đối số từ

khóa. Trong lệnh gọi hàm này: sketch (vẹt, lồng, chết = Đúng) vẹt và lồng là vị trí, và chết là một đối số từ

khóa.

17.4 Một ví dụ phức tạp hơn

Viết lại is_ after (từ Phần 16.1) phức tạp hơn một chút vì nó có hai đối tượng Thời gian làm tham số. Trong

trường hợp này, thông thường đặt tên cho tham số đầu tiên và tham số thứ hai là khác:

# bên trong lớp học Thời gian:

def is_ after (bản thân, khác):


return self.time_to_int ()> other.time_to_int ()

Để sử dụng phương thức này, bạn phải gọi nó trên một đối tượng và chuyển đối tượng kia làm đối số:

>>> end.is_ after (bắt đầu)


ĐÚNG VẬY

Một điều thú vị về cú pháp này là nó gần giống như tiếng Anh: “end is after start?”

17.5 Phương pháp init

Phương thức init (viết tắt của “khởi tạo”) là một phương thức đặc biệt được gọi khi một đối tượng được khởi

tạo. Tên đầy đủ của nó là __init__ (hai ký tự gạch dưới, theo sau là init và sau đó thêm hai ký tự gạch dưới).

Một phương thức init cho lớp Thời gian có thể trông như thế này:

# bên trong lớp học Thời gian:

def __init __ (bản thân, giờ = 0, phút = 0, giây = 0):


self.hour = giờ

self.minute = phút

self.second = giây

Thông thường các tham số của __init__ có cùng tên với các thuộc tính. Các
bản tường trình

self.hour = giờ
Machine Translated by Google

17,6. Phương thức __str__ 165

lưu trữ giá trị của tham số giờ như một thuộc tính của bản thân.

Các tham số là tùy chọn, vì vậy nếu bạn gọi Thời gian không có đối số, bạn sẽ nhận được các giá trị mặc định.

>>> time = Time () >>>

time.print_time () 00:00:00

Nếu bạn cung cấp một đối số, nó sẽ ghi đè lên hàng giờ:

>>> time = Time (9) >>>

time.print_time () 09:00:00

Nếu bạn cung cấp hai đối số, chúng sẽ ghi đè lên giờ và phút.

>>> time = Time (9, 45) >>>

time.print_time () 09:45:00

Và nếu bạn cung cấp ba đối số, chúng sẽ ghi đè lên cả ba giá trị mặc định.

Như một bài tập, hãy viết một phương thức init cho lớp Point lấy x và y làm tham số tùy chọn và gán chúng

cho các thuộc tính tương ứng.

17.6 Phương thức __str__

__str__ là một phương thức đặc biệt, giống như __init__, được cho là trả về một đại diện chuỗi của một đối

tượng.

Ví dụ, đây là một phương thức str cho các đối tượng Thời gian:

# bên trong lớp học Thời gian:

def __str __ (self):

return '% .2d:%. 2d:%. 2d'% (self.hour, self.minute, self.second)

Khi bạn in một đối tượng, Python gọi phương thức str: >>> time = Time

(9, 45) >>> print (time) 09:45:00

Khi tôi viết một lớp mới, tôi hầu như luôn bắt đầu bằng cách viết __init__, điều này giúp dễ dàng khởi tạo

các đối tượng và __str__, hữu ích cho việc gỡ lỗi.

Như một bài tập, hãy viết một phương thức str cho lớp Point. Tạo một đối tượng Point và in nó.

17.7 Quá tải người vận hành

Bằng cách xác định các phương thức đặc biệt khác, bạn có thể chỉ định hành vi của các toán tử trên các kiểu

do người lập trình xác định. Ví dụ: nếu bạn xác định một phương thức có tên __add__ cho lớp Thời gian, bạn

có thể sử dụng toán tử + trên các đối tượng Thời gian.

Đây là định nghĩa có thể trông như thế nào:


Machine Translated by Google

166 Chương 17. Các lớp và phương pháp

# bên trong lớp học Thời gian:

def __add __ (tự, khác):


seconds = self.time_to_int () + other.time_to_int () trả về
int_to_time (giây)

Và đây là cách bạn có thể sử dụng nó:

>>> bắt đầu = Thời gian (9, 45) >>>


thời lượng = Thời gian (1, 35) >>> in

(bắt đầu + thời lượng) 11:20:00

Khi bạn áp dụng toán tử + cho các đối tượng Thời gian, Python gọi __add__. Khi bạn in kết quả, Python
gọi __str__. Vì vậy, có rất nhiều điều xảy ra ở hậu trường!

Thay đổi hành vi của một toán tử để nó hoạt động với các kiểu do người lập trình xác định được gọi là
nạp chồng toán tử. Đối với mỗi toán tử trong Python có một phương thức đặc biệt tương ứng, như __add__.
Để biết thêm chi tiết, hãy xem http://docs.python.org/3/reference/ datamodel.html # specialnames.

Như một bài tập, hãy viết một phương thức thêm cho lớp Point.

17.8 Công văn dựa trên loại hình

Trong phần trước, chúng tôi đã thêm hai đối tượng Thời gian, nhưng bạn cũng có thể muốn thêm một số
nguyên vào đối tượng Thời gian. Sau đây là một phiên bản của __add__ kiểm tra kiểu khác và gọi thêm_thời
gian hoặc tăng dần:

# bên trong lớp học Thời gian:

def __add __ (self, other): if


isinstance (khác, Time):
trả về self.add_time (khác)
khác:

trả về self.increment (khác)

def add_time (tự, khác):


seconds = self.time_to_int () + other.time_to_int () trả về
int_to_time (giây)

gia số def (bản thân, giây):


seconds + = self.time_to_int () trả về
int_to_time (giây)

Hàm isinstance tích hợp sẵn nhận một giá trị và một đối tượng lớp, và trả về True nếu giá trị là một thể
hiện của lớp.

Nếu khác là đối tượng Thời gian, __add__ gọi add_time. Nếu không, nó giả định rằng param eter là một số
và gọi số gia. Thao tác này được gọi là điều phối dựa trên kiểu vì nó gửi tính toán đến các phương thức
khác nhau dựa trên kiểu của các phần được lập luận.

Dưới đây là các ví dụ sử dụng toán tử + với các kiểu khác nhau:
Machine Translated by Google

17,9. Tính đa hình 167

>>> bắt đầu = Thời gian (9, 45)

>>> thời lượng = Thời gian (1, 35)

>>> in (bắt đầu + thời lượng) 11:20:00

>>> in (bắt đầu + 1337) 10:07:17

Thật không may, việc thực hiện phép cộng này không mang tính chất giao hoán. Nếu số nguyên là toán hạng đầu

tiên, bạn nhận được >>> print (1337 + start)

TypeError: (các) loại toán hạng không được hỗ trợ cho +: 'int' và 'instance'

Vấn đề là, thay vì yêu cầu đối tượng Thời gian thêm một số nguyên, Python lại yêu cầu một số nguyên để thêm

một đối tượng Thời gian và nó không biết làm thế nào. Nhưng có một giải pháp thông minh cho vấn đề này:

phương thức đặc biệt __radd__, viết tắt của “thêm bên phải”. Phương thức này được gọi khi một đối tượng Thời

gian xuất hiện ở phía bên phải của toán tử +. Đây là định nghĩa:

# bên trong lớp học Thời gian:

def __radd __ (bản thân, khác):


tự trả lại .__ thêm __ (khác)

Và đây là cách nó được sử dụng:

>>> print (1337 + start) 10:07:17

Như một bài tập, hãy viết một phương thức thêm cho Điểm hoạt động với một đối tượng Điểm hoặc một bộ giá trị:

• Nếu toán hạng thứ hai là một Điểm, phương thức sẽ trả về một Điểm mới có tọa độ x là tổng tọa độ x của

các toán hạng và tương tự như vậy đối với tọa độ y.

• Nếu toán hạng thứ hai là một bộ, phương thức phải thêm phần tử đầu tiên của bộ vào tọa độ x và phần tử

thứ hai vào tọa độ y, và trả về một Điểm mới cùng với kết quả.

17,9 Tính đa hình


Công văn dựa trên loại rất hữu ích khi cần thiết, nhưng (may mắn thay) nó không phải lúc nào cũng cần thiết.

Thường thì bạn có thể tránh nó bằng cách viết các hàm hoạt động chính xác cho các đối số với các kiểu khác

nhau.

Nhiều hàm chúng tôi đã viết cho chuỗi cũng hoạt động cho các loại chuỗi khác. Đối với đề thi, trong Phần

11.2, chúng tôi đã sử dụng biểu đồ để đếm số lần mỗi chữ cái xuất hiện trong một từ.

def histogram (các): d


= dict ()
cho c trong s:
nếu c không có trong d:

d [c] = 1
Machine Translated by Google

168 Chương 17. Các lớp và phương pháp

khác:

d [c] = d [c] +1
trở lại d

Chức năng này cũng hoạt động với danh sách, bộ giá trị và thậm chí cả từ điển, miễn là các phần tử của
s có thể được băm, để chúng có thể được sử dụng làm khóa trong d. >>> t = ['spam', 'egg', 'spam',

'spam', 'bacon', 'spam'] >>> histogram (t) {'bacon': 1, 'egg': 1, 'thư rác': 4}

Các hàm hoạt động với một số kiểu được gọi là đa hình. Tính đa hình có thể kích hoạt khả năng tái sử
dụng mã. Ví dụ: hàm tổng tích hợp, thêm các phần tử của một chuỗi, hoạt động miễn là các phần tử của
chuỗi hỗ trợ phép cộng.

Vì các đối tượng Thời gian cung cấp một phương thức cộng nên chúng hoạt

động với tổng: >>> t1 = Thời gian (7, 43) >>> t2 = Thời gian (7, 41) >>>
t3 = Thời gian (7, 37) >>> tổng = sum ([t1, t2, t3]) >>> print (tổng)
23:01:00

Nói chung, nếu tất cả các hoạt động bên trong một hàm hoạt động với một kiểu nhất định, thì hàm sẽ hoạt
động với kiểu đó.

Loại đa hình tốt nhất là loại không chủ ý, nơi bạn phát hiện ra rằng một biểu tượng vui mà bạn đã viết
có thể được áp dụng cho một loại mà bạn chưa bao giờ lên kế hoạch.

17.10 Gỡ lỗi
Việc thêm thuộc tính cho các đối tượng tại bất kỳ thời điểm nào trong quá trình thực hiện chương trình
là hợp pháp, nhưng nếu bạn có các đối tượng cùng kiểu mà không có cùng thuộc tính thì rất dễ mắc lỗi.
Việc khởi tạo tất cả các thuộc tính của đối tượng trong phương thức init được coi là một ý tưởng hay.

Nếu bạn không chắc liệu một đối tượng có một thuộc tính cụ thể hay không, bạn có thể sử dụng hàm
hasattr tích hợp sẵn (xem Phần 15.7).

Một cách khác để truy cập các thuộc tính là các vars hàm tích hợp, lấy một đối tượng và trả về một từ
điển ánh xạ từ tên thuộc tính (dưới dạng chuỗi) đến các giá trị của chúng: >>> p = Point (3, 4) >>>

vars ( p) {'y': 4, 'x': 3}

Đối với mục đích gỡ lỗi, bạn có thể thấy hữu ích khi giữ chức năng này hữu ích: def

print_attributes (obj): for attr in vars (obj): print (attr, getattr (obj, attr))

print_attributes duyệt qua từ điển và in ra từng tên thuộc tính và giá trị tương ứng của nó.

Hàm getattr tích hợp sẵn nhận một đối tượng và một tên thuộc tính (dưới dạng một chuỗi) và trả về giá
trị của thuộc tính.
Machine Translated by Google

17.11. Giao diện và triển khai 169

17.11 Giao diện và triển khai

Một trong những mục tiêu của thiết kế hướng đối tượng là làm cho phần mềm dễ bảo trì hơn, có nghĩa là
bạn có thể giữ cho chương trình hoạt động khi các phần khác của hệ thống thay đổi và sửa đổi chương
trình để đáp ứng các yêu cầu mới.

Một nguyên tắc thiết kế giúp đạt được mục tiêu đó là giữ cho các giao diện tách biệt với các đề cập
tiềm ẩn. Đối với các đối tượng, điều đó có nghĩa là các phương thức mà một lớp cung cấp không được phụ
thuộc vào cách các thuộc tính được biểu diễn.

Ví dụ, trong chương này, chúng tôi đã phát triển một lớp biểu thị một thời gian trong ngày. Các phương
thức được cung cấp bởi lớp này bao gồm time_to_int, is_ after và add_time.

Chúng tôi có thể thực hiện các phương pháp đó theo một số cách. Chi tiết của việc triển khai phụ thuộc
vào cách chúng tôi biểu thị thời gian. Trong chương này, các thuộc tính của đối tượng Thời gian là
giờ, phút và giây.

Thay vào đó, chúng tôi có thể thay thế các thuộc tính này bằng một số nguyên duy nhất đại diện cho số
giây kể từ nửa đêm. Việc triển khai này sẽ làm cho một số phương thức, như is_ after, dễ viết hơn,
nhưng nó làm cho các phương thức khác khó viết hơn.

Sau khi triển khai một lớp mới, bạn có thể khám phá ra cách triển khai tốt hơn. Nếu các phần khác của
chương trình đang sử dụng lớp của bạn, việc thay đổi giao diện có thể tốn nhiều thời gian và dễ xảy ra
lỗi.

Nhưng nếu bạn thiết kế giao diện cẩn thận, bạn có thể thay đổi cách thực hiện mà không thay đổi giao
diện, có nghĩa là các phần khác của chương trình không phải thay đổi.

17.12 Bảng chú giải thuật ngữ

ngôn ngữ hướng đối tượng: Một ngôn ngữ cung cấp các tính năng, chẳng hạn như các kiểu và phương thức
do người lập trình xác định, hỗ trợ lập trình hướng đối tượng.

lập trình hướng đối tượng: Một phong cách lập trình trong đó dữ liệu và các hoạt động
thao tác đó được tổ chức thành các lớp và phương thức.

phương thức: Một hàm được định nghĩa bên trong định nghĩa lớp và được gọi trên các phiên bản của
lớp đó.

chủ đề: Đối tượng mà một phương thức được gọi.

đối số vị trí: Đối số không bao gồm tên tham số, vì vậy nó không phải là
đối số từ khóa.

nạp chồng toán tử: Thay đổi hành vi của toán tử như + để nó hoạt động với
kiểu do người lập trình xác định.

công văn dựa trên kiểu: Một mẫu lập trình kiểm tra kiểu của một toán hạng và trong
vokes các chức năng khác nhau cho các loại khác nhau.

polymorphic: Liên quan đến một hàm có thể hoạt động với nhiều kiểu.
Machine Translated by Google

170 Chương 17. Các lớp và phương pháp

17.13 Bài tập

Bài tập 17.1. Tải xuống mã từ chương này từ http: // thinkpython2. com / code / Time2. py
Thay đổi các thuộc tính của Thời gian thành một số nguyên duy nhất đại diện cho giây kể từ
giữa đêm. Sau đó, sửa đổi các phương thức (và hàm int_to_time) để hoạt động với tation
implemen mới. Bạn không cần phải sửa đổi mã kiểm tra trong chính. Khi bạn làm xong, đầu ra sẽ
giống như trước. Lời giải: http: // thinkpython2. com / code / Time2_ soln. py
Bài tập 17.2. Bài tập này là một câu chuyện cảnh báo về một trong những lỗi phổ biến và khó
tìm nhất trong Python. Viết định nghĩa cho một lớp có tên Kangaroo bằng các phương thức sau:

1. Phương thức __init__ khởi tạo thuộc tính có tên pouch_contents vào danh sách trống.

2. Một phương thức có tên put_in_pouch nhận một đối tượng thuộc loại bất kỳ và thêm nó vào
pouch_contents.

3. Phương thức __str__ trả về biểu diễn chuỗi của đối tượng Kangaroo và con
lều của túi.

Kiểm tra mã của bạn bằng cách tạo hai đối tượng Kangaroo, gán chúng cho các biến có tên kanga
và roo, sau đó thêm roo vào bên trong túi của kanga.

Tải xuống http: // thinkpython2. com / code / BadKangaroo. py Nó chứa một giải pháp cho vấn
đề trước đó với một lỗi lớn, khó chịu. Tìm và sửa lỗi.

Nếu bạn gặp khó khăn, bạn có thể tải xuống http: // thinkpython2. com / code / GoodKangaroo.
py, giải thích vấn đề và chứng minh một giải pháp.
Machine Translated by Google

Chương 18

Di sản

Tính năng ngôn ngữ thường được kết hợp với lập trình hướng đối tượng là kế thừa. Kế thừa là khả năng
xác định một lớp mới là phiên bản sửa đổi của một lớp cũ. Trong chương này, tôi trình bày tính kế thừa
bằng cách sử dụng các lớp đại diện cho các quân bài, bộ bài và tay bài xì phé.

Nếu bạn không chơi poker, bạn có thể đọc về nó tại http://en.wikipedia.org/wiki/Poker, nhưng bạn không
cần phải làm thế; Tôi sẽ cho bạn biết những gì bạn cần biết cho các bài tập.

Ví dụ về mã từ chương này có sẵn trên http://thinkpython2.com/code/ Card.py.

18.1 Đối tượng thẻ

Có năm mươi hai thẻ trong một bộ bài, mỗi thẻ thuộc một trong bốn bộ và một trong mười ba cấp bậc. Các
bộ đồ là Bích, Trái tim, Kim cương và Câu lạc bộ (theo thứ tự giảm dần trong cầu). Các cấp bậc là Át,
2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen và King. Tùy thuộc vào trò chơi mà bạn đang chơi, một lá Át có
thể cao hơn Vua hoặc thấp hơn 2.

Nếu chúng ta muốn xác định một đối tượng mới để đại diện cho một lá bài đang chơi, thì điều hiển nhiên
phải là: thứ hạng và bộ đồ. Không rõ ràng loại thuộc tính nên là.
Một khả năng là sử dụng các chuỗi có chứa các từ như 'Spade' cho bộ quần áo và 'Queen' cho các cấp bậc.
Một vấn đề với việc triển khai này là sẽ không dễ dàng so sánh các thẻ để xem thẻ nào có thứ hạng hoặc
bộ đồ cao hơn.

Một giải pháp thay thế là sử dụng các số nguyên để mã hóa các cấp bậc và bộ quần áo. Trong ngữ cảnh
này, "mã hóa" có nghĩa là chúng ta sẽ xác định ánh xạ giữa các số và bộ quần áo, hoặc giữa các số và
cấp bậc. Loại mã hóa này không có nghĩa là bí mật (đó sẽ là "mã hóa").

Ví dụ: bảng này hiển thị các bộ quần áo và các mã số nguyên tương ứng:

Spades 7 3
Trái tim 7 2
Kim cương 7 1
Câu lạc bộ 7 0
Machine Translated by Google

172 Chương 18. Kế thừa

Mã này giúp bạn dễ dàng so sánh các thẻ; bởi vì bộ quần áo cao hơn liên kết với số cao hơn, chúng tôi có
thể so sánh bộ quần áo bằng cách so sánh mã của chúng.

Việc lập bản đồ cho các cấp bậc là khá rõ ràng; mỗi thứ hạng số ánh xạ tới số nguyên tương ứng và
đối với các thẻ mặt:

Jack 7 11
Nữ hoàng 7 12

Vua 7 13

Tôi đang sử dụng biểu tượng 7 để làm rõ rằng các ánh xạ này không phải là một phần của chương
trình Python. Chúng là một phần của thiết kế chương trình, nhưng chúng không xuất hiện rõ ràng trong mã.

Định nghĩa lớp cho Thẻ trông như thế này:

Thẻ lớp:

"" "Đại diện cho một thẻ chơi tiêu chuẩn." ""

def __init __ (self, suit = 0, rank = 2):


self.suit = bộ đồ

self.rank = xếp hạng

Như thường lệ, phương thức init nhận một tham số tùy chọn cho mỗi thuộc tính. Thẻ mặc định là 2 trong
số các Câu lạc bộ.

Để tạo Thẻ, bạn gọi Thẻ có phù hợp và cấp bậc của thẻ mà bạn muốn. queen_of_diamonds

= Thẻ (1, 12)

18.2 Thuộc tính lớp

Để in các đối tượng Thẻ theo cách mà mọi người có thể dễ dàng đọc được, chúng ta cần một ánh xạ từ
các mã số nguyên đến các cấp bậc và bộ đồ tương ứng. Một cách tự nhiên để làm điều đó là với danh
sách các chuỗi. Chúng tôi gán các danh sách này cho các thuộc tính lớp:
# Thẻ lớp bên trong:

suit_names = ['Câu lạc bộ', 'Diamonds', 'Hearts', 'Spades'] rank_names


= [Không có, 'Át', '2', '3', '4', '5', '6', '7 ',

'8', '9', '10', 'Jack', 'Queen', 'King']

def __str __ (self):


trả về '% s trong số% s'% (Card.rank_names [self.rank],
Card.suit_names [self.suit])

Các biến như suit_names và rank_names, được định nghĩa bên trong một lớp nhưng bên ngoài bất kỳ

phương thức nào, được gọi là thuộc tính lớp vì chúng được liên kết với Thẻ đối tượng lớp.

Thuật ngữ này phân biệt chúng với các biến như suit và rank, được gọi là thuộc tính cá thể vì chúng
được liên kết với một cá thể cụ thể.

Cả hai loại thuộc tính này đều được truy cập bằng ký hiệu dấu chấm. Ví dụ: trong __str__, self là
đối tượng Thẻ và self.rank là thứ hạng của nó. Tương tự, Card là một đối tượng của lớp và
Card.rank_names là danh sách các chuỗi được liên kết với lớp.
Machine Translated by Google

18.3. So sánh thẻ 173

danh sách
loại hình

Thẻ suit_names

danh sách

rank_names

Thẻ

card1 bộ đồ 1

thứ hạng 11

Hình 18.1: Sơ đồ đối tượng.

Mỗi thẻ đều có bộ đồ và thứ hạng riêng của nó, nhưng chỉ có một bản sao của bộ tên và thứ hạng_tên.

Kết hợp tất cả lại với nhau, biểu thức Card.rank_names [self.rank] có nghĩa là “sử dụng thứ hạng
tại cống từ bản thân đối tượng làm chỉ mục vào danh sách rank_names từ Thẻ lớp và chọn chuỗi thích
hợp.”

Phần tử đầu tiên của rank_names là Không vì không có thẻ nào có cấp bậc 0. Bằng cách đưa vào Không
làm người giữ địa điểm, chúng tôi nhận được một ánh xạ với thuộc tính đẹp mà chỉ mục 2 ánh xạ tới
chuỗi '2', v.v. Để tránh điều chỉnh này, chúng tôi có thể sử dụng từ điển thay vì danh sách.

Với các phương pháp chúng tôi có cho đến nay, chúng tôi có thể tạo và

in thẻ: >>> card1 = Card (2, 11) >>> print (card1)

Jack of Hearts

Hình 18.1 là một sơ đồ của đối tượng lớp Thẻ và một cá thể Thẻ. Thẻ là một đối tượng lớp; loại của
nó là loại. card1 là một thể hiện của Thẻ, vì vậy loại của nó là Thẻ. Để tiết kiệm dung lượng, tôi
đã không vẽ nội dung của suit_names và rank_names.

18.3 So sánh các thẻ


Đối với các kiểu dựng sẵn, có các toán tử quan hệ (<,>, ==, v.v.) so sánh các giá trị và định
nghĩa khi một giá trị lớn hơn, nhỏ hơn hoặc bằng một toán tử khác. Đối với các kiểu do lập trình
viên xác định, chúng tôi có thể ghi đè hành vi của các toán tử cài sẵn bằng cách cung cấp một
phương thức có tên __lt__, viết tắt của “less than”.

__lt__ nhận hai tham số, self và other, và trả về True nếu giá trị tự nhỏ hơn
khác.

Thứ tự chính xác cho các thẻ không phải là rõ ràng. Ví dụ, cái nào tốt hơn, 3 trong số Câu lạc bộ
hoặc 2 trong số Kim cương? Một người có cấp bậc cao hơn, nhưng người kia có bộ đồ cao hơn. Để so
sánh các quân bài, bạn phải quyết định thứ hạng hay bộ đồ quan trọng hơn.

Câu trả lời có thể phụ thuộc vào trò chơi bạn đang chơi, nhưng để giữ mọi thứ đơn giản, chúng tôi
sẽ đưa ra lựa chọn tùy ý phù hợp với quan trọng hơn, vì vậy tất cả các Bích đều cao hơn tất cả Kim
cương, v.v.
Machine Translated by Google

174 Chương 18. Kế thừa

Với quyết định đó, chúng ta có thể viết __lt__:

# Thẻ lớp bên trong:

def __lt __ (bản thân, khác):


# kiểm tra bộ quần áo

if self.suit <other.suit: trả về True

if self.suit> other.suit: trả về Sai

# bộ quần áo giống nhau ... kiểm tra cấp bậc

trả về self.rank <other.rank

Bạn có thể viết điều này ngắn gọn hơn bằng cách sử dụng so sánh tuple:

# Thẻ lớp bên trong:

def __lt __ (bản thân, khác):

t1 = self.suit, self.rank t2 =

other.suit, khác.rank trả về t1 <t2

Như một bài tập, hãy viết một phương thức __lt__ cho các đối tượng Thời gian. Bạn có thể sử dụng so sánh
tuple, nhưng bạn cũng có thể cân nhắc so sánh các số nguyên.

18.4 Bộ bài

Bây giờ chúng ta đã có các Bộ bài, bước tiếp theo là xác định Bộ bài. Vì một bộ bài được tạo thành từ các lá bài,
nên điều tự nhiên là mỗi Bộ bài chứa một danh sách các quân bài như một thuộc tính.

Sau đây là định nghĩa lớp cho Bộ bài. Phương thức init tạo thẻ thuộc tính và tạo bộ tiêu chuẩn gồm năm mươi hai

thẻ:

Bộ bài lớp:

def __init __ (self):

self.cards = [] for

suit in range (4): for rank in

range (1, 14):


card = Thẻ (bộ đồ, cấp bậc)

self.cards.append (thẻ)

Cách dễ nhất để điền bộ bài là sử dụng một vòng lặp lồng nhau. Vòng lặp bên ngoài liệt kê các bộ đồ từ 0 đến 3.

Vòng bên trong liệt kê các cấp độ từ 1 đến 13. Mỗi lần lặp lại tạo ra một Thẻ mới với bộ đồ và cấp bậc hiện tại

và gắn nó vào các thẻ tự.

18.5 In bộ bài
Đây là phương thức __str__ cho Bộ bài:

# bên trong Bộ bài lớp:

def __str __ (bản thân):

res = []
Machine Translated by Google

18,6. Thêm, xóa, trộn và sắp xếp 175

cho thẻ trong self.cards:

res.append (str (card))


return '\ n'.join (res)

Phương pháp này cho thấy một cách hiệu quả để tích lũy một chuỗi lớn: xây dựng một danh sách các chuỗi và
sau đó sử dụng phương thức nối chuỗi. Hàm str được tích hợp sẵn gọi phương thức __str__ trên mỗi thẻ và trả
về biểu diễn chuỗi.

Vì chúng ta gọi phép nối trên một ký tự dòng mới, các thẻ được phân tách bằng dòng mới. Đây là kết quả trông
như thế nào:

>>> boong = Deck () >>>

print (boong)
Đứng đầu câu lạc bộ

2 trong số các câu lạc bộ

3 trong số các câu lạc bộ

...

10 of Spades
Jack of Spades
Queen of Spades King

of Spades Mặc dù kết

quả xuất hiện trên 52 dòng, nó là một chuỗi dài chứa các dòng mới.

18.6 Thêm, xóa, trộn và sắp xếp

Để chia bài, chúng tôi muốn có một phương pháp loại bỏ một quân bài khỏi bộ bài và trả lại nó.

Phương thức danh sách cửa sổ bật lên cung cấp một cách thuận tiện để làm điều đó:

# bên trong Bộ bài lớp:

def pop_card (self): trả


về self.cards.pop ()

Vì cửa sổ bật loại bỏ lá cuối cùng trong danh sách, chúng tôi đang giải quyết từ cuối bộ bài.

Để thêm một thẻ, chúng ta có thể sử dụng phương thức danh sách chắp thêm:

# bên trong Bộ bài lớp:

def add_card (tự, thẻ):

self.cards.append (thẻ)

Một phương pháp như thế này sử dụng phương pháp khác mà không cần làm nhiều việc đôi khi được gọi là veneer.
Phép ẩn dụ đến từ chế biến gỗ, trong đó veneer là một lớp mỏng của gỗ chất lượng tốt được dán lên bề mặt
của một miếng gỗ rẻ hơn để cải thiện vẻ ngoài.

Trong trường hợp này, add_card là một phương thức "mỏng" thể hiện một hoạt động danh sách theo các thuật
ngữ thích hợp cho các bộ bài. Nó cải thiện hình thức hoặc giao diện của việc triển khai.

Như một ví dụ khác, chúng ta có thể viết một phương thức Bộ bài có tên là xáo trộn bằng cách sử dụng hàm
xáo trộn từ mô-đun ngẫu nhiên:

# bên trong Bộ bài lớp:

def shuffle (self):


random.shuffle (self.cards)
Machine Translated by Google

176 Chương 18. Kế thừa

Đừng quên nhập ngẫu nhiên.

Như một bài tập, hãy viết một phương thức Bộ bài có tên sắp xếp sử dụng phương pháp danh sách sắp xếp để sắp xếp
các thẻ trong Bộ bài. sắp xếp sử dụng phương thức __lt__ mà chúng tôi đã xác định để xác định thứ tự.

18.7 Kế thừa

Kế thừa là khả năng định nghĩa một lớp mới là phiên bản sửa đổi của một lớp hiện có.
Ví dụ, giả sử chúng ta muốn một lớp đại diện cho một “tay”, nghĩa là, các lá bài do một người chơi
nắm giữ. Một ván bài tương tự như một bộ bài: cả hai đều được tạo thành từ một tập hợp các quân bài
và cả hai đều yêu cầu các thao tác như thêm và bớt quân bài.

Một bàn tay cũng khác với một bộ bài; có những phép toán mà chúng tôi muốn đối với những bàn tay
không có ý nghĩa đối với một bộ bài. Ví dụ, trong poker, chúng ta có thể so sánh hai ván bài để xem
ván bài nào thắng. Trong đấu cầu, chúng ta có thể tính điểm cho một ván bài để đưa ra giá thầu.

Mối quan hệ này giữa các lớp — tương tự, nhưng khác nhau — tự nó cho phép kế thừa. Để định nghĩa
một lớp mới kế thừa từ một lớp hiện có, bạn đặt tên của lớp hiện có trong dấu ngoặc đơn: class Hand
(Bộ bài): "" "Đại diện cho một ván bài." ""

Định nghĩa này chỉ ra rằng Hand kế thừa từ Deck; điều đó có nghĩa là chúng ta có thể sử dụng các
phương thức như pop_card và add_card cho Tay cũng như Bộ bài.

Khi một lớp mới kế thừa từ lớp hiện có, lớp hiện có được gọi là lớp cha và lớp mới được gọi là lớp
con.

Trong ví dụ này, Hand kế thừa __init__ từ Deck, nhưng nó không thực sự làm được những gì chúng ta
muốn: thay vì đặt hand bằng 52 lá mới, phương pháp init cho Hands sẽ sắp xếp các lá bằng một danh
sách trống.

Nếu chúng tôi cung cấp một phương thức init trong lớp Hand, nó sẽ ghi đè phương thức trong lớp Deck:

# bên trong lớp Tay:

def __init __ (self, label = ''):


self.cards = []
self.label = nhãn

Khi bạn tạo Hand, Python gọi phương thức init này, không phải phương thức trong Bộ bài.

>>> hand = Hand ('new hand') >>> hand.cards []

>>> hand.label
'tay mới'

Các phương thức khác được kế thừa từ Bộ bài, vì vậy chúng ta có thể sử dụng pop_card và add_card để xử lý
một thẻ:

>>> deck = Deck ()

>>> card = deck.pop_card () >>>


hand.add_card (card) >>> print

(hand)
King of Spades
Machine Translated by Google

18,8. Sơ đồ lớp 177

Bước tiếp theo tự nhiên là đóng gói mã này trong một phương thức có tên là move_cards:

# bên trong Bộ bài lớp:

def move_cards (self, hand, num): cho tôi


trong range (num): hand.add_card
(self.pop_card ())

move_cards nhận hai đối số, một đối tượng Hand và số lượng quân bài cần giải quyết. Nó biến đổi
cả bản thân và bàn tay, và trả về Không có.

Trong một số trò chơi, các lá bài được chuyển từ tay này sang tay khác hoặc từ một tay trở lại bộ
bài. Bạn có thể sử dụng move_cards cho bất kỳ hoạt động nào trong số này: self có thể là Bộ bài
hoặc Tay, và tay, bất chấp tên gọi, cũng có thể là Bộ bài.

Kế thừa là một tính năng hữu ích. Một số chương trình lặp đi lặp lại mà không có tính kế thừa có
thể được viết thanh lịch hơn với nó. Kế thừa có thể tạo điều kiện cho việc sử dụng lại mã, vì bạn
có thể tùy chỉnh hành vi của các lớp cha mà không cần phải sửa đổi chúng. Trong một số trường hợp,
cấu trúc kế thừa phản ánh cấu trúc tự nhiên của vấn đề, điều này làm cho thiết kế dễ hiểu hơn.

Mặt khác, tính kế thừa có thể làm cho các chương trình khó đọc. Khi một phương thức được gọi ra,
đôi khi không rõ phải tìm định nghĩa của nó ở đâu. Mã liên quan có thể được trải rộng trên một số
mô-đun. Ngoài ra, nhiều thứ có thể được thực hiện bằng cách sử dụng kế thừa có thể được thực hiện
tốt hoặc tốt hơn nếu không có nó.

18.8 Sơ đồ lớp

Cho đến nay, chúng ta đã thấy các biểu đồ ngăn xếp, biểu đồ này hiển thị trạng thái của một chương trình và đối

tượng dia gram, hiển thị các thuộc tính của một đối tượng và giá trị của chúng. Các sơ đồ này đại diện cho một

ảnh chụp nhanh trong quá trình thực hiện một chương trình, vì vậy chúng thay đổi khi chương trình chạy.

Chúng cũng rất chi tiết; cho một số mục đích, quá chi tiết. Biểu đồ lớp là một biểu diễn trừu tượng
hơn về cấu trúc của một chương trình. Thay vì hiển thị các ob ject riêng lẻ, nó hiển thị các lớp
và mối quan hệ giữa chúng.

Có một số loại mối quan hệ giữa các lớp:

• Các đối tượng trong một lớp có thể chứa các tham chiếu đến các đối tượng trong một lớp khác.
Ví dụ: mỗi Hình chữ nhật chứa một tham chiếu đến một Điểm và mỗi Bộ bài chứa tham chiếu đến
nhiều Thẻ. Mối quan hệ kiểu này được gọi là HAS-A, như trong "Hình chữ nhật có một Điểm."

• Một lớp có thể kế thừa từ lớp khác. Mối quan hệ này được gọi là IS-A, như trong "Một tay là
một loại Bộ bài."

• Một lớp có thể phụ thuộc vào lớp khác theo nghĩa là các đối tượng trong một lớp lấy ob jects
trong lớp thứ hai làm tham số hoặc sử dụng các đối tượng trong lớp thứ hai như một phần của
tính toán. Mối quan hệ kiểu này được gọi là mối quan hệ phụ thuộc.

Biểu đồ lớp là một biểu diễn đồ họa của các mối quan hệ này. Ví dụ, Hình 18.2 cho thấy các mối
quan hệ giữa Bộ bài, Bộ bài và Bàn tay.
Machine Translated by Google

178 Chương 18. Kế thừa

*
Boong Thẻ

Tay

Hình 18.2: Biểu đồ lớp.

Mũi tên có đầu hình tam giác rỗng thể hiện mối quan hệ IS-A; trong trường hợp này, nó chỉ ra rằng
Tay kế thừa từ Bộ bài.

Đầu mũi tên tiêu chuẩn thể hiện mối quan hệ HAS-A; trong trường hợp này một Bộ bài có tham chiếu
đến các đối tượng Thẻ.

Ngôi sao (*) gần đầu mũi tên là một dấu hiệu đa dạng; nó cho biết một Bộ bài có bao nhiêu Thẻ.
Đa số có thể là một số đơn giản, chẳng hạn như 52, một dải ô, như 5..7 hoặc một dấu sao, cho biết
rằng một Bộ bài có thể có bất kỳ số lượng Thẻ nào.

Không có sự phụ thuộc nào trong sơ đồ này. Chúng thường sẽ được hiển thị bằng một mũi tên đứt nét.
Hoặc nếu có rất nhiều phụ thuộc, chúng đôi khi bị bỏ qua.

Một sơ đồ chi tiết hơn có thể cho thấy rằng một Bộ bài thực sự chứa một danh sách các Thẻ, nhưng các
loại tích hợp sẵn như list và dict thường không được đưa vào sơ đồ lớp.

18.9 Gỡ lỗi
Kế thừa có thể gây khó khăn cho việc gỡ lỗi vì khi bạn gọi một phương thức trên một đối tượng, có
thể khó tìm ra phương thức nào sẽ được gọi.

Giả sử bạn đang viết một hàm hoạt động với các đối tượng Hand. Bạn muốn nó hoạt động với tất cả các
loại Hand, như PokerHands, BridgeHands, v.v. Nếu bạn gọi một phương thức như shuffle, bạn có thể lấy
một phương thức được xác định trong Deck, nhưng nếu bất kỳ lớp con nào ghi đè phương thức này, bạn
sẽ nhận được phiên bản đó thay thế. Hành vi này thường là một điều tốt, nhưng nó có thể gây nhầm lẫn.

Bất kỳ lúc nào bạn không chắc chắn về quy trình thực thi thông qua chương trình của mình, giải pháp
tốt nhất là thêm các câu lệnh in vào đầu các phương thức có liên quan. Nếu Deck.shuffle in ra một
thông báo có nội dung như Running Deck.shuffle, thì khi chương trình chạy, nó sẽ theo dõi luồng thực
thi.

Thay vào đó, bạn có thể sử dụng hàm này, hàm này nhận một đối tượng và một tên phương thức (dưới
dạng một chuỗi) và trả về lớp cung cấp định nghĩa của phương thức: def find_defining_class (obj,

meth_name): for ty in type (obj) .mro ():

nếu meth_name trong ty .__ dict__:


return ty

Đây là một ví dụ: >>> hand

= Hand () >>>

find_defining_class (hand, 'shuffle') <class '__main


__. Deck'>
Machine Translated by Google

18,10. Đóng gói dữ liệu 179

Vì vậy, phương pháp xáo trộn cho Hand này là phương pháp trong Bộ bài.

find_defining_class sử dụng phương thức mro để lấy danh sách các đối tượng lớp (kiểu) sẽ được tìm kiếm
các phương thức. “MRO” là viết tắt của “thứ tự phân giải phương thức”, là chuỗi các lớp mà Python tìm
kiếm để “phân giải” một tên phương thức.

Đây là một gợi ý thiết kế: khi bạn ghi đè một phương thức, giao diện của phương thức mới phải giống với
giao diện cũ. Nó phải nhận các tham số giống nhau, trả về cùng một kiểu và tuân theo cùng các điều kiện
trước và điều kiện sau. Nếu tuân theo quy tắc này, bạn sẽ thấy rằng bất kỳ hàm nào được thiết kế để
hoạt động với một thể hiện của lớp cha, như Bộ bài, cũng sẽ hoạt động với các thể hiện của các lớp con
như Hand và PokerHand.

Nếu bạn vi phạm quy tắc này, được gọi là "nguyên tắc thay thế Liskov", mã của bạn sẽ sụp đổ như (xin
lỗi) một ngôi nhà của các quân bài.

18.10 Đóng gói dữ liệu


Các chương trước trình bày một kế hoạch phát triển mà chúng ta có thể gọi là “thiết kế hướng đối tượng”.
Chúng tôi đã xác định các đối tượng mà chúng tôi cần — như Điểm, Hình chữ nhật và Thời gian — và xác
định các lớp để đại diện cho chúng. Trong mỗi trường hợp, có một sự tương ứng rõ ràng giữa đối tượng và
một thực thể nào đó trong thế giới thực (hoặc ít nhất là một thế giới toán học).

Nhưng đôi khi không rõ ràng bạn cần những đối tượng nào và chúng nên tương tác như thế nào. Trong trường
hợp đó, bạn cần một kế hoạch phát triển khác. Giống như cách mà chúng ta đã khám phá ra các giao diện
hàm bằng cách đóng gói và tổng quát hóa, chúng ta có thể khám phá các giao diện lớp bằng cách đóng gói
dữ liệu.

Phân tích Markov, từ Phần 13.8, cung cấp một ví dụ điển hình. Nếu bạn tải xuống mã của tôi từ http://
thinkpython2.com/code/markov.py, bạn sẽ thấy rằng nó sử dụng hai biến toàn cục — hậu tố_map và tiền tố
— được đọc và viết từ một số hàm.

hậu tố_map = {} tiền


tố = ()

Bởi vì các biến này là toàn cục, chúng tôi chỉ có thể chạy một phân tích tại một thời điểm. Nếu chúng
ta đọc hai văn bản, tiền tố và hậu tố của chúng sẽ được thêm vào các cấu trúc dữ liệu giống nhau (điều
này tạo nên một số văn bản được tạo thú vị).

Để chạy nhiều phân tích và giữ chúng riêng biệt, chúng ta có thể gói gọn trạng thái của từng phân tích
trong một đối tượng. Đây là những gì trông giống như:

lớp Markov:

def __init __ (self):

self.suffix_map = {}
self.prefix = ()

Tiếp theo, chúng ta chuyển đổi các hàm thành các phương thức. Ví dụ, đây là process_word:

def process_word (self, word, order = 2): if len


(self.prefix) <order: self.prefix + = (word,)

trở về
Machine Translated by Google

180 Chương 18. Kế thừa

try:
self.suffix_map [self.prefix] .append (word) ngoại trừ
KeyError: # nếu không có mục nhập nào cho tiền tố này, hãy
tạo một self.suffix_map [self.prefix] = [word]

self.prefix = shift (self.prefix, word)

Chuyển đổi một chương trình như thế này — thay đổi thiết kế mà không thay đổi hành vi — là một ví dụ
khác về tái cấu trúc (xem Phần 4.7).

Ví dụ này gợi ý một kế hoạch phát triển để thiết kế các đối tượng và phương pháp:

1. Bắt đầu bằng cách viết các hàm đọc và ghi các biến toàn cục (khi cần thiết).

2. Khi bạn bắt đầu hoạt động chương trình, hãy tìm kiếm các liên kết giữa các biến toàn cục
và các chức năng sử dụng chúng.

3. Đóng gói các biến liên quan dưới dạng thuộc tính của một đối tượng.

4. Chuyển các hàm liên quan thành các phương thức của lớp mới.

Như một bài tập, hãy tải xuống mã Markov của tôi từ http://thinkpython2.com/code/ markov.py và làm theo
các bước được mô tả ở trên để đóng gói các biến toàn cục dưới dạng thuộc tính của một lớp mới có tên
Markov. Giải pháp: http://thinkpython2.com/code/ markov2.py.

18.11 Bảng chú giải thuật ngữ

mã hóa: Để biểu diễn một bộ giá trị bằng cách sử dụng một bộ giá trị khác bằng cách xây dựng một bản đồ
ping giữa chúng.

thuộc tính lớp: Một thuộc tính được liên kết với một đối tượng lớp. Thuộc tính lớp được xác định
bên trong định nghĩa lớp nhưng bên ngoài bất kỳ phương thức nào.

thuộc tính instance: Một thuộc tính được liên kết với một thể hiện của một lớp.

veneer: Một phương thức hoặc chức năng cung cấp một giao diện khác cho một chức năng khác với
thực hiện nhiều tính toán.

kế thừa: Khả năng xác định một lớp mới là phiên bản sửa đổi của một lớp trước đó
lớp xác định.

lớp cha: Lớp mà từ đó một lớp con kế thừa.

lớp con: Một lớp mới được tạo bằng cách kế thừa từ một lớp hiện có; còn được gọi là “phụ
lớp".

Mối quan hệ IS-A: Mối quan hệ giữa lớp con và lớp cha của nó.

Mối quan hệ HAS-A: Mối quan hệ giữa hai lớp trong đó các cá thể của một lớp liên kết tham chiếu đến các
cá thể của lớp kia.

phụ thuộc: Mối quan hệ giữa hai lớp trong đó các cá thể của một lớp sử dụng trong các trạng thái của
lớp kia, nhưng không lưu trữ chúng dưới dạng thuộc tính.
Machine Translated by Google

18.12. Bài tập 181

sơ đồ lớp: Một sơ đồ hiển thị các lớp trong một chương trình và các mối quan hệ được
tween chúng.

tính đa dạng: Một ký hiệu trong sơ đồ lớp cho thấy, đối với mối quan hệ HAS-A, có bao nhiêu
tham chiếu đến các cá thể của lớp khác.

đóng gói dữ liệu: Một kế hoạch phát triển chương trình bao gồm một nguyên mẫu sử dụng các biến
toàn cục và một phiên bản cuối cùng biến các biến toàn cục thành các thuộc tính cá thể.

18.12 Bài tập

Bài tập 18.1. Đối với chương trình sau, hãy vẽ một biểu đồ lớp UML hiển thị các lớp này và mối
quan hệ giữa chúng.

lớp PingPongParent:
đi qua

lớp Ping (PingPongParent):


def __init __ (self, pong):
self.pong = pong

class Pong (PingPongParent): def


__init __ (self, pings = None): nếu
ping là None: self.pings = []
else:

self.pings = ping

def add_ping (self, ping):


self.pings.append (ping)

pong = Pong ()
ping = Ping (pong)
pong.add_ping (ping)
Bài tập 18.2. Viết một phương thức Bộ bài có tên là deal_hands nhận hai tham số là số ván bài
và số ván bài trên mỗi ván bài. Nó sẽ tạo ra số lượng các đối tượng Hand thích hợp, chia số
lượng thẻ thích hợp cho mỗi bên và trả về một danh sách các Hand.
Bài tập 18.3. Sau đây là các ván bài có thể có trong poker, theo thứ tự giá trị tăng dần và
xác suất giảm dần:

cặp: hai thẻ cùng hạng hai cặp:

hai cặp thẻ cùng hạng

ba loại: ba thẻ có cùng thứ hạng

thẳng: năm quân bài có thứ tự theo thứ tự (quân Át có thể cao hoặc thấp, vì vậy Át-2-3-4-5 là quân
thẳng và 10-Jack-Nữ hoàng-Vua-Át cũng vậy, nhưng Nữ hoàng-Vua-Át-2 -3 thì không.)

tuôn ra: năm thẻ với cùng một bộ

đồ đầy đủ: ba thẻ cùng một hạng, hai thẻ với hạng khác
Machine Translated by Google

182 Chương 18. Kế thừa

bốn loại: bốn quân bài có cùng thứ hạng xả thẳng:

năm quân bài theo thứ tự (như đã định nghĩa ở trên) và cùng một bộ

Mục tiêu của các bài tập này là ước tính xác suất vẽ được các bàn tay khác nhau này.

1. Tải xuống các tệp sau từ http: // thinkpython2. com / mã:

Card.py: Một phiên bản hoàn chỉnh của các lớp Bài, Bộ bài và Bàn tay trong chương này.

PokerHand.py: Việc triển khai không đầy đủ một lớp đại diện cho một ván bài poker và một số mã
kiểm tra nó.

2. Nếu bạn chạy PokerHand.py, nó sẽ giải quyết bảy bàn bài xì phé 7 lá và kiểm tra xem có bất kỳ bàn nào trong số

đó có chứa một lần đổ hay không. Đọc kỹ mã này trước khi bạn tiếp tục.

3. Thêm các phương thức vào PokerHand.py có tên has_pair, has_twopair, v.v. trả về Đúng hoặc Sai
tùy theo ván bài có đáp ứng các tiêu chí liên quan hay không. Mã của bạn phải hoạt động chính
xác đối với các "tay" có chứa bất kỳ số lượng thẻ nào (mặc dù 5 và 7 là các kích thước phổ biến
nhất).

4. Viết một phương thức có tên là phân loại để tìm ra phân loại có giá trị cao nhất cho một tay và
đặt thuộc tính nhãn cho phù hợp. Ví dụ, một bàn tay 7 lá có thể chứa một quân bài và một đôi; nó
phải được gắn nhãn "tuôn ra".

5. Khi bạn tin chắc rằng các phương pháp phân loại của bạn đang hoạt động, bước tiếp theo là kiểm
tra xác suất của các bàn tay khác nhau. Viết một hàm trong PokerHand.py xáo trộn một bộ bài,
chia nó thành các tay, phân loại các tay và đếm số lần các phân loại khác nhau xuất hiện.

6. In bảng phân loại và xác suất của chúng. Chạy chương trình của bạn với số tay lớn hơn và lớn hơn
cho đến khi các giá trị đầu ra hội tụ ở mức độ chính xác hợp lý. So sánh kết quả của bạn với các
giá trị tại http: // vi. wikipedia. bảng xếp hạng org / wiki / Hand_ .

Lời giải: http: // thinkpython2. com / code / PokerHandSoln. py


Machine Translated by Google

Chương 19

The Goodies

Một trong những mục tiêu của tôi cho cuốn sách này là dạy bạn càng ít Python càng tốt. Khi có hai cách để
làm điều gì đó, tôi chọn một cách và tránh đề cập đến cách còn lại. Hoặc đôi khi tôi đặt cái thứ hai vào
một bài tập.

Bây giờ tôi muốn quay trở lại để tìm kiếm một số điều tốt đẹp đã bị bỏ lại phía sau. Python cung cấp một
số tính năng không thực sự cần thiết — bạn có thể viết mã tốt mà không cần chúng — nhưng với chúng, đôi
khi bạn có thể viết mã ngắn gọn, dễ đọc hoặc hiệu quả hơn, và
đôi khi cả ba.

19.1 Biểu thức điều kiện


Chúng ta đã thấy các câu lệnh điều kiện trong Phần 5.4. Câu lệnh điều kiện thường được sử dụng để chọn một

trong hai giá trị; Ví dụ:

nếu x> 0: y

= math.log (x)
khác:

y = float ('nan')

Câu lệnh này kiểm tra xem x có dương hay không. Nếu vậy, nó sẽ tính toán math.log. Nếu không, math.log sẽ
tạo ra một ValueError. Để tránh dừng chương trình, chúng tôi tạo “NaN”, là giá trị dấu phẩy động đặc biệt
đại diện cho “Không phải số”.

Chúng ta có thể viết câu lệnh này ngắn gọn hơn bằng cách sử dụng biểu thức điều kiện: y =

math.log (x) if x> 0 else float ('nan')

Bạn gần như có thể đọc dòng này giống như tiếng Anh: “y lấy log-x nếu x lớn hơn 0; nếu không thì nó bị NaN
”.

Các hàm đệ quy đôi khi có thể được viết lại bằng cách sử dụng các biểu thức điều kiện. Đối với bài kiểm
tra, đây là phiên bản đệ quy của giai thừa: def factorial (n):

nếu n == 0:
trả lại 1
khác:

trả về n * giai thừa (n-1)


Machine Translated by Google

184 Chương 19. Những món quà

Chúng ta có thể viết lại nó như thế này:

def thừa thừa (n): trả


về 1 nếu n == 0 khác n * giai thừa (n-1)

Một cách sử dụng khác của biểu thức điều kiện là xử lý các đối số tùy chọn. Ví dụ, đây là phương thức init
từ GoodKangaroo (xem Bài tập 17.2):

def __init __ (tự, tên, nội dung = Không có):


self.name = tên
if nội dung == Không có:

nội dung = []

self.pouch_contents = nội dung

Chúng ta có thể viết lại cái này như thế này:

def __init __ (tự, tên, nội dung = Không có):


self.name = tên

self.pouch_contents = [] if nội dung == Không có nội dung nào khác

Nói chung, bạn có thể thay thế một câu lệnh điều kiện bằng một biểu thức điều kiện nếu cả hai nhánh đều chứa
các biểu thức đơn giản được trả về hoặc được gán cho cùng một biến thể.

19.2 Liệt kê các hiểu biết

Trong Phần 10.7, chúng ta đã thấy các mẫu bản đồ và bộ lọc. Ví dụ: hàm này nhận một danh sách các chuỗi,
ánh xạ phương thức chuỗi viết hoa thành các phần tử và trả về một danh sách chuỗi mới: def capitalize_all
(t): res = []

cho s trong t:

res.append (s.capitalize ())


trả lại res

Chúng ta có thể viết điều này ngắn gọn hơn bằng cách sử dụng cách hiểu

danh sách: def capitalize_all (t): return [s.capitalize () for s in t]

Các toán tử dấu ngoặc cho biết rằng chúng tôi đang xây dựng một danh sách mới. Biểu thức bên trong dấu ngoặc
chỉ định các phần tử của danh sách và mệnh đề for cho biết chúng ta đang duyệt qua trình tự nào.

Cú pháp của việc hiểu danh sách hơi khó hiểu vì biến vòng lặp, s trong ví dụ này, xuất hiện trong biểu thức
trước khi chúng ta đi đến định nghĩa.

Danh sách cũng có thể được sử dụng để lọc. Ví dụ: hàm này chỉ chọn các phần tử của t là chữ hoa và trả về
một danh sách mới: def only_upper (t): res = []

cho s trong t:

if s.isupper ():
res.append (s)
trả lại res
Machine Translated by Google

19.3. Biểu thức trình tạo 185

Chúng ta có thể viết lại nó bằng cách sử dụng khả

năng hiểu danh sách def only_upper (t): return [s


for s in t if s.isupper ()]

Danh sách hiểu ngắn gọn và dễ đọc, ít nhất là đối với các diễn đạt đơn giản. Và chúng thường nhanh hơn
các vòng lặp for tương đương, đôi khi nhanh hơn nhiều. Vì vậy, nếu bạn giận tôi vì đã không đề cập đến
họ sớm hơn, tôi hiểu.

Tuy nhiên, theo cách bảo vệ của tôi, việc hiểu danh sách khó gỡ lỗi hơn vì bạn không thể đặt câu lệnh
in bên trong vòng lặp. Tôi khuyên bạn chỉ nên sử dụng chúng nếu việc tính toán đủ đơn giản để bạn có
thể thực hiện đúng ngay lần đầu tiên. Và đối với người mới bắt đầu, điều đó có nghĩa là không bao giờ.

19.3 Biểu thức trình tạo

Biểu thức trình tạo tương tự như trình hiểu danh sách, nhưng với dấu ngoặc đơn thay vì dấu ngoặc vuông:
>>> g = (x ** 2 cho x trong phạm vi (5)) >>> g <đối tượng trình tạo <genexpr> tại 0x7f4c45a786c0> Kết

quả là một đối tượng trình tạo biết cách lặp qua một chuỗi giá trị. Nhưng không giống như cách hiểu
danh sách, nó không tính toán tất cả các giá trị cùng một lúc; nó chờ được hỏi.

Hàm tích hợp tiếp theo nhận giá trị tiếp theo từ trình tạo: >>> tiếp theo

(g) 0 >>> tiếp theo (g) 1

Khi bạn đến cuối chuỗi, tiếp theo sẽ đưa ra một ngoại lệ StopIteration. Bạn cũng có thể sử dụng vòng
lặp for để lặp qua các giá trị: >>> for val in g: print (val)

...
4
9
16

Đối tượng trình tạo sẽ theo dõi vị trí của nó trong chuỗi, do đó, vòng lặp for sẽ chọn vị trí tiếp
theo bị dừng lại. Khi máy phát đã cạn kiệt, nó tiếp tục tăng StopIteration: >>> tiếp theo (g)

Biểu thức

StopIteration Generator thường được sử dụng với các hàm như sum, max và min: >>> sum

(x ** 2 for x in range (5)) 30

19.4 bất kỳ và tất cả

Python cung cấp một hàm tích hợp, bất kỳ, nhận một chuỗi các giá trị boolean và lại chuyển thành True
nếu bất kỳ giá trị nào là True. Nó hoạt động trên các danh sách:
Machine Translated by Google

186 Chương 19. Những món quà

>>> bất kỳ ([Sai, Sai, Đúng])


ĐÚNG VẬY

Nhưng nó thường được sử dụng với các biểu thức trình tạo:

>>> any (ký tự == 't' cho ký tự trong 'monty')


ĐÚNG VẬY

Ví dụ đó không hữu ích lắm vì nó làm điều tương tự như toán tử in. Nhưng chúng tôi có thể sử dụng bất kỳ hàm nào

để viết lại một số hàm tìm kiếm mà chúng tôi đã viết trong Phần 9.3. Ví dụ: chúng ta có thể viết những điều tránh
như sau:

def tránh (từ, bị cấm):

không trả lại bất kỳ (chữ cái bị cấm đối với chữ cái trong từng từ)

Hàm gần như đọc giống như tiếng Anh, "từ tránh bị cấm nếu không có bất kỳ chữ cái nào bị cấm trong từ."

Sử dụng bất kỳ với biểu thức trình tạo là hiệu quả vì nó dừng ngay lập tức nếu tìm thấy giá trị True, vì vậy nó

không phải đánh giá toàn bộ chuỗi.

Python cung cấp một hàm dựng sẵn khác, tất cả đều trả về True nếu mọi phần tử của chuỗi là True. Như một bài tập,

sử dụng tất cả để viết lại các use_all từ Phần 9.3.

19.5 Bộ

Trong Phần 13.6, tôi sử dụng từ điển để tìm các từ xuất hiện trong tài liệu nhưng không xuất hiện trong danh sách
từ. Hàm tôi đã viết lấy d1, chứa các từ trong tài liệu làm khóa và d2, chứa danh sách các từ. Nó trả về một từ điển

có chứa các khóa từ d1 không có trong d2.

def subtract (d1, d2): res =

dict () cho khóa trong d1:

nếu khóa không có trong

d2: res [key] = Không có


trả lại res

Trong tất cả các từ điển này, giá trị là Không vì chúng tôi không bao giờ sử dụng chúng. Kết quả là, chúng tôi lãng

phí một số không gian lưu trữ.

Python cung cấp một kiểu tích hợp khác, được gọi là tập hợp, hoạt động giống như một tập hợp các khóa tĩnh không

có giá trị. Việc thêm các phần tử vào một tập hợp rất nhanh chóng; kiểm tra thành viên cũng vậy.

Và tập hợp cung cấp các phương pháp và toán tử để tính toán các hoạt động tập hợp phổ biến.

Ví dụ: phép trừ tập hợp có sẵn dưới dạng một phương thức được gọi là chênh lệch hoặc dưới dạng toán tử, -. Vì vậy,
chúng ta có thể viết lại số trừ như thế này:

def subtract (d1, d2): return

set (d1) - set (d2)

Kết quả là một tập hợp thay vì một từ điển, nhưng đối với các hoạt động như lặp, hành vi vẫn giống nhau.

Một số bài tập trong cuốn sách này có thể được thực hiện ngắn gọn và hiệu quả với các bộ. Ví dụ: đây là một giải

pháp cho has_duplicates, từ Bài tập 10.7, sử dụng từ điển:


Machine Translated by Google

19,6. Bộ đếm 187

def has_duplicates (t): d = {}

cho x trong t:

nếu x trong d:

trả về True

d [x] = Đúng
trả về Sai

Khi một phần tử xuất hiện lần đầu tiên, phần tử đó sẽ được thêm vào từ điển. Nếu cùng một phần tử xuất hiện

lại, hàm trả về giá trị True.

Sử dụng các bộ, chúng ta có thể viết cùng một hàm như sau: def

has_duplicates (t): return len (set (t)) <len (t)

Một phần tử chỉ có thể xuất hiện trong một tập hợp một lần, vì vậy nếu một phần tử trong t xuất hiện nhiều hơn

một lần, tập hợp đó sẽ nhỏ hơn t. Nếu không có bản sao, tập hợp sẽ có cùng kích thước với t.

Chúng ta cũng có thể sử dụng các bộ để thực hiện một số bài tập trong Chương 9. Ví dụ, đây là phiên bản của

use_only với vòng lặp: def using_only (word, available): for letter in word: if letter not in available:

trả về Sai

trả về True

use_only kiểm tra xem tất cả các chữ cái trong word có khả dụng hay không. Chúng ta có thể viết lại nó như

thế này: def using_only (word, có sẵn):


return set (word) <= set (có sẵn)

Toán tử <= kiểm tra xem một tập hợp có phải là tập hợp con của tập hợp khác hay không, bao gồm khả năng chúng

bằng nhau, điều này đúng nếu tất cả các chữ cái trong word đều có sẵn.

Như một bài tập, viết lại tránh sử dụng bộ.

19.6 Quầy

Bộ đếm giống như một tập hợp, ngoại trừ việc nếu một phần tử xuất hiện nhiều hơn một lần, Bộ đếm sẽ theo dõi

số lần nó xuất hiện. Nếu bạn đã quen với ý tưởng toán học về bộ đa hợp, Bộ đếm là một cách tự nhiên để biểu

diễn một bộ đa hợp.

Bộ đếm được định nghĩa trong một mô-đun tiêu chuẩn được gọi là bộ sưu tập, vì vậy bạn phải nhập nó. Bạn có thể

khởi tạo Bộ đếm bằng một chuỗi, danh sách hoặc bất kỳ thứ gì khác hỗ trợ lặp lại: >>> từ các bộ sưu tập nhập

Bộ đếm >>> count = Counter ('parrot') >>> count

Bộ đếm ({'r': 2, 't': 1, 'o': 1, 'p': 1, 'a': 1})

Bộ đếm hành xử giống như từ điển theo nhiều cách; chúng ánh xạ từ mỗi khóa đến số lần nó xuất hiện. Như trong

từ điển, các khóa phải có thể băm được.

Không giống như từ điển, Bộ đếm không đưa ra ngoại lệ nếu bạn truy cập vào một phần tử không xuất hiện. Thay

vào đó, chúng trả về 0:


Machine Translated by Google

188 Chương 19. Những món quà

>>> đếm ['d'] 0

Chúng ta có thể sử dụng Bộ đếm để viết lại is_anagram từ Bài tập

10.6: def is_anagram (word1, word2): return Counter (word1) ==


Counter (word2)

Nếu hai từ là đảo ngữ, chúng chứa các chữ cái giống nhau có cùng số đếm, do đó Bộ đếm của chúng là
tương đương.

Bộ đếm cung cấp các phương thức và toán tử để thực hiện các hoạt động giống như tập hợp, bao gồm
phân chia quảng cáo, phép trừ, kết hợp và giao nhau. Và họ cung cấp một phương thức thường hữu
ích, most_common, trả về danh sách các cặp giá trị-tần suất, được sắp xếp từ phổ biến nhất đến ít
nhất:

>>> count = Counter ('parrot') >>>


cho val, freq in count.most_common (3):
... print (val, freq)
r 2

p 1
a 1

19.7 defaultdict

Mô-đun bộ sưu tập cũng cung cấp lệnh mặc định, giống như một từ điển ngoại trừ việc nếu bạn truy
cập vào một khóa không tồn tại, nó có thể tạo ra một giá trị mới ngay lập tức.

Khi bạn tạo một sắc lệnh mặc định, bạn cung cấp một hàm được sử dụng để tạo các giá trị mới. Một chức
năng được sử dụng để tạo các đối tượng đôi khi được gọi là một nhà máy. Các chức năng tích hợp tạo danh
sách, tập hợp và các loại khác có thể được sử dụng như các nhà máy:

>>> từ bộ sưu tập nhập defaultdict >>> d =


defaultdict (danh sách)

Lưu ý rằng đối số là list, là một đối tượng lớp, không phải list (), là một danh sách mới.
Hàm bạn cung cấp sẽ không được gọi trừ khi bạn truy cập vào một khóa không tồn tại. >>> t = d

['khóa mới']
>>> t
[]

Danh sách mới, mà chúng tôi đang gọi là t, cũng được thêm vào từ điển. Vì vậy, nếu chúng ta sửa đổi t,
thay đổi sẽ xuất hiện trong d: >>> t.append ('giá trị mới') >>> d

defaultdict (<class 'list'>, {'new key': ['new value']})

Nếu bạn đang tạo từ điển danh sách, bạn thường có thể viết mã đơn giản hơn bằng cách sử dụng defaultdict.
Trong lời giải của tôi cho Bài tập 12.2, mà bạn có thể lấy từ http://thinkpython2.com/code/
anagram_sets.py, tôi tạo một từ điển ánh xạ từ một chuỗi các chữ cái được sắp xếp đến danh sách
các từ có thể được đánh vần bằng các chữ cái đó . Ví dụ: "opst" ánh xạ tới danh sách ["opts",
"post", "pot", "spot", "stop", "top"].

Đây là mã gốc:
Machine Translated by Google

19,8. Các bộ giá trị được đặt tên


189

def all_anagrams (tên tệp): d = {}


cho dòng đang mở (tên tệp):

word = line.strip (). low () t =


chữ ký (từ)
nếu không có trong d:

d [t] = [từ]
khác:

d [t] .append (word)


return d

Điều này có thể được đơn giản hóa bằng cách sử dụng setdefault, mà bạn có thể đã sử dụng trong

Bài tập 11.2: def all_anagrams (filename): d = {} cho dòng đang mở (tên tệp):

word = line.strip (). low () t =


signature (word) d.setdefault (t,
[]). append (word) return d

Giải pháp này có nhược điểm là nó luôn tạo một danh sách mới, bất kể nó có cần thiết hay không. Đối
với danh sách, đó không phải là vấn đề lớn, nhưng nếu chức năng của nhà máy phức tạp, thì có thể là
như vậy.

Chúng tôi có thể tránh sự cố này và đơn giản hóa mã bằng cách sử dụng defaultdict:

def all_anagrams (tên tệp): d = defaultdict (danh sách) cho dòng đang mở (tên
tệp):

word = line.strip (). low () t =


signature (word) d [t] .append
(word) return d

Giải pháp của tôi cho Bài tập 18.3, mà bạn có thể tải xuống từ http://thinkpython2.com/ code /
PokerHandSoln.py, sử dụng setdefault trong hàm has_straightflush. Solu tion này có nhược điểm là tạo
ra một đối tượng Hand mỗi khi thông qua vòng lặp, cho dù nó có cần thiết hay không. Như một bài tập,
hãy viết lại nó bằng cách sử dụng một sắc lệnh mặc định.

19.8 Các bộ giá trị được đặt tên

Nhiều đối tượng đơn giản về cơ bản là tập hợp các giá trị liên quan. Ví dụ, đối tượng Point được định
nghĩa trong Chương 15 chứa hai số, x và y. Khi bạn định nghĩa một lớp như thế này, bạn thường bắt đầu
với một phương thức init và một phương thức str:
điểm lớp:

def __init __ (self, x = 0, y = 0):


self.x = x

self.y = y

def __str __ (self):

return '(% g,% g)'% (self.x, self.y)


Machine Translated by Google

190 Chương 19. Những món quà

Đây là rất nhiều mã để truyền tải một lượng nhỏ thông tin. Python cung cấp một cách ngắn gọn hơn để nói
điều tương tự:

từ các bộ sưu tập nhập tên Nametuple Point =


namestuple ('Point', ['x', 'y'])

Đối số đầu tiên là tên của lớp bạn muốn tạo. Thứ hai là danh sách các thuộc tính Đối tượng điểm phải có,
dưới dạng chuỗi. Giá trị trả về từ nametuple là một đối tượng lớp:

>>> Điểm

<class '__main __. Point'>

Point tự động cung cấp các phương thức như __init__ và __str__ để bạn không cần phải viết chúng.

Để tạo một đối tượng Point, bạn sử dụng lớp Point như một hàm: >>> p =

Point (1, 2) >>> p

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

Phương thức init gán các đối số cho các thuộc tính bằng cách sử dụng tên bạn đã cung cấp. Phương thức str
in ra một biểu diễn của đối tượng Point và các thuộc tính của nó.

Bạn có thể truy cập các phần tử của tuple được đặt tên theo tên:

>>> px, py (1, 2)

Nhưng bạn cũng có thể coi một tuple đã đặt tên là một

tuple: >>> p [0], p [1] (1, 2)

>>> x, y = p >>>
x, y (1, 2)

Các bộ giá trị được đặt tên cung cấp một cách nhanh chóng để xác định các lớp đơn giản. Hạn chế là các
lớp đơn giản không phải lúc nào cũng đơn giản. Sau đó, bạn có thể quyết định rằng bạn muốn thêm các phương
thức vào một tuple đã đặt tên. Trong trường hợp đó, bạn có thể xác định một lớp mới kế thừa từ tuple được
đặt tên:

lớp Pointier (Point):


# thêm các phương pháp khác tại đây

Hoặc bạn có thể chuyển sang định nghĩa lớp thông thường.

19.9 Thu thập các kho từ khóa

Trong Phần 12.4, chúng ta đã biết cách viết một hàm tập hợp các đối số của nó thành một bộ tuple: def

printall (* args): print (args)

Bạn có thể gọi hàm này với bất kỳ số lượng đối số vị trí nào (nghĩa là đối số không có từ khóa):
Machine Translated by Google

19,10. Bảng chú giải 191

>>> printall (1, 2.0, '3') (1,


2.0, '3')

Nhưng toán tử * không thu thập các đối số từ khóa:

>>> printall (1, 2.0, third = '3')


TypeError: printall () có một đối số từ khoá không mong muốn là 'third'

Để thu thập các đối số từ khóa, bạn có thể sử dụng toán tử **:

def printall (* args, ** kwargs): print


(args, kwargs)

Bạn có thể gọi tham số thu thập từ khóa bất cứ thứ gì bạn muốn, nhưng kwargs là một lựa chọn phổ biến.
Kết quả là một từ điển ánh xạ từ từ khóa thành giá trị:

>>> printall (1, 2.0, third = '3') (1,


2.0) {'third': '3'}

Nếu bạn có từ điển từ khóa và giá trị, bạn có thể sử dụng toán tử phân tán, ** để gọi một hàm:

>>> d = dict (x = 1, y = 2)
>>> Điểm (** d)

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

Nếu không có toán tử phân tán, hàm sẽ coi d như một đối số vị trí duy nhất, vì vậy nó sẽ gán d cho x
và phàn nàn vì không có gì để gán cho y:

>>> d = dict (x = 1, y = 2)
>>> Điểm (d)
Traceback (cuộc gọi gần đây nhất cuối cùng):

Tệp "<stdin>", dòng 1, trong <module>

TypeError: __new __ () thiếu 1 đối số vị trí bắt buộc: 'y'

Khi bạn đang làm việc với các hàm có một số lượng lớn tham số, việc tạo và chuyển xung quanh các từ
điển chỉ định các tùy chọn thường dùng sẽ rất hữu ích.

19.10 Bảng chú giải thuật ngữ

biểu thức điều kiện: Một biểu thức có một trong hai giá trị, tùy thuộc vào điều kiện
sự.

khả năng hiểu danh sách: Một biểu thức có vòng lặp for trong dấu ngoặc vuông tạo ra một
danh sách.

biểu thức trình tạo: Một biểu thức có vòng lặp for trong dấu ngoặc đơn tạo ra một chi
đối tượng tor.

multiset: Một thực thể toán học đại diện cho một ánh xạ giữa các phần tử của một tập hợp
và số lần chúng xuất hiện.

Factory: Một hàm, thường được truyền dưới dạng tham số, được sử dụng để tạo các đối tượng.
Machine Translated by Google

192 Chương 19. Những món quà

19.11 Bài tập

Bài tập 19.1. Sau đây là một hàm tính toán hệ số nhị thức một cách đệ quy. def binomial_coeff (n, k):

"" "Tính hệ số nhị thức" n chọn k ".

n: số lần thử nghiệm

k: số lần thành công

trả về: int


"" "

nếu k == 0:

trả lại 1

nếu n == 0:

trả về 0

res = binomial_coeff (n-1, k) + binomial_coeff (n-1, k-1)


trả lại res

Viết lại phần thân của hàm bằng cách sử dụng các biểu thức điều kiện lồng nhau.

Một lưu ý: chức năng này không hiệu quả lắm vì nó kết thúc việc tính toán các giá trị giống nhau lặp đi lặp
lại. Bạn có thể làm cho nó hiệu quả hơn bằng cách ghi nhớ (xem Phần 11.6). Nhưng bạn sẽ thấy rằng việc ghi
nhớ sẽ khó hơn nếu bạn viết nó bằng cách sử dụng biểu thức điều kiện.
Machine Translated by Google

Phụ lục A

Gỡ lỗi

Khi bạn đang gỡ lỗi, bạn nên phân biệt giữa các loại lỗi khác nhau để theo dõi chúng nhanh hơn:

• Lỗi cú pháp được trình thông dịch phát hiện khi nó đang dịch mã nguồn thành mã byte. Họ
chỉ ra rằng có điều gì đó sai trong cấu trúc của chương trình. Ví dụ: Bỏ qua dấu hai
chấm ở cuối câu lệnh def sẽ tạo ra thông báo hơi thừa SyntaxError: cú pháp không hợp lệ.

• Lỗi thời gian chạy được tạo ra bởi trình thông dịch nếu có sự cố xảy ra trong khi chương
trình đang chạy. Hầu hết các thông báo lỗi thời gian chạy đều bao gồm thông tin về vị
trí xảy ra lỗi và các chức năng đang thực thi. Ví dụ: Một sion định kỳ vô hạn cuối cùng
gây ra lỗi thời gian chạy "đã vượt quá độ sâu đệ quy tối đa".

• Lỗi ngữ nghĩa là các vấn đề xảy ra với một chương trình chạy mà không tạo ra các trình
duyệt lỗi nhưng không thực hiện đúng. Ví dụ: Một biểu thức có thể không được đánh giá
theo thứ tự bạn mong đợi, dẫn đến kết quả không chính xác.

Bước đầu tiên trong quá trình gỡ lỗi là tìm ra loại lỗi bạn đang gặp phải. Mặc dù các phần
sau được sắp xếp theo loại lỗi, một số kỹ thuật có thể áp dụng trong nhiều trường hợp.

A.1 Lỗi cú pháp

Các lỗi cú pháp thường dễ sửa một khi bạn tìm ra chúng là gì. Thật không may, các thông báo
lỗi thường không hữu ích. Các thông báo phổ biến nhất là SyntaxError: cú pháp không hợp lệ và
SyntaxError: mã thông báo không hợp lệ, cả hai đều không có nhiều thông tin.

Mặt khác, thông báo cho bạn biết vấn đề đã xảy ra ở đâu trong chương trình.
Trên thực tế, nó cho bạn biết Python nhận thấy vấn đề ở đâu, không nhất thiết là lỗi nằm ở
đâu. Đôi khi lỗi nằm trước vị trí của thông báo lỗi, thường ở dòng trước.
Machine Translated by Google

194 Phụ lục A. Gỡ lỗi

Nếu bạn đang xây dựng chương trình tăng dần, bạn nên biết lỗi ở đâu. Nó sẽ nằm ở dòng cuối cùng bạn
đã thêm.

Nếu bạn đang sao chép mã từ một cuốn sách, hãy bắt đầu bằng cách so sánh mã của bạn với mã của sách
thật cẩn thận. Kiểm tra mọi ký tự. Đồng thời, hãy nhớ rằng sách có thể sai, vì vậy nếu bạn thấy điều
gì đó giống như lỗi cú pháp, thì có thể là như vậy.

Dưới đây là một số cách để tránh các lỗi cú pháp phổ biến nhất:

1. Đảm bảo rằng bạn không sử dụng từ khóa Python cho một tên biến.

2. Kiểm tra xem bạn có dấu hai chấm ở cuối tiêu đề của mọi câu lệnh ghép hay không,
bao gồm các câu lệnh for, while, if, và def.

3. Đảm bảo rằng bất kỳ chuỗi nào trong mã đều có dấu ngoặc kép phù hợp. Bảo đảm
rằng tất cả các dấu ngoặc kép đều là "dấu ngoặc kép", không phải "dấu ngoặc kép".

4. Nếu bạn có chuỗi nhiều dòng với dấu ngoặc kép ba (đơn hoặc kép), hãy đảm bảo rằng bạn đã kết
thúc chuỗi đúng cách. Một chuỗi chưa kết thúc có thể gây ra lỗi mã thông báo không hợp lệ ở
cuối chương trình của bạn hoặc nó có thể coi phần sau của chương trình là một chuỗi cho đến
khi nó đến chuỗi tiếp theo. Trong trường hợp thứ hai, nó có thể không tạo ra một thông báo
lỗi nào cả!

5. Một toán tử mở không được đóng dấu— (, {, hoặc [—làm cho Python tiếp tục với dòng tiếp theo
như một phần của câu lệnh hiện tại. Nói chung, lỗi xảy ra gần như ngay lập tức trong
hàng tiếp theo.

6. Kiểm tra cổ điển = thay vì == bên trong một điều kiện.

7. Kiểm tra phần thụt đầu dòng để đảm bảo nó thẳng hàng theo cách mà nó phải làm. Python có thể
xử lý không gian và tab, nhưng nếu bạn trộn chúng có thể gây ra vấn đề. Cách tốt nhất để tránh
vấn đề này là sử dụng trình soạn thảo văn bản biết về Python và tạo thụt lề nhất quán.

8. Nếu bạn có các ký tự không phải ASCII trong mã (bao gồm chuỗi và nhận xét), điều đó có thể
gây ra sự cố, mặc dù Python 3 thường xử lý các ký tự không phải ASCII. Hãy cẩn thận nếu bạn
dán văn bản từ một trang web hoặc nguồn khác.

Nếu không có gì hoạt động, hãy chuyển sang phần tiếp theo ...

A.1.1 Tôi tiếp tục thực hiện các thay đổi và nó không có gì khác biệt.

Nếu trình thông dịch cho biết có lỗi và bạn không nhìn thấy lỗi đó, đó có thể là do bạn và trình
thông dịch không xem cùng một mã. Kiểm tra môi trường lập trình của bạn để đảm bảo rằng chương trình
bạn đang chỉnh sửa là chương trình mà Python đang cố gắng chạy.

Nếu bạn không chắc chắn, hãy thử đặt một lỗi cú pháp rõ ràng và có chủ ý ở đầu chương trình. Bây giờ
chạy nó một lần nữa. Nếu trình thông dịch không tìm thấy lỗi mới, bạn không chạy mã mới.

Có một vài thủ phạm có thể xảy ra:

• Bạn đã chỉnh sửa tệp và quên lưu các thay đổi trước khi chạy lại. Một số
môi trường lập trình làm điều này cho bạn, nhưng một số thì không.
Machine Translated by Google

A.2. Lỗi thời gian chạy 195

• Bạn đã thay đổi tên của tệp, nhưng bạn vẫn đang chạy tên cũ.

• Một cái gì đó trong môi trường phát triển của bạn được định cấu hình không chính

xác. • Nếu bạn đang viết một mô-đun và sử dụng nhập, hãy đảm bảo rằng bạn không đặt tên cho mô-đun của
mình giống với một trong những mô-đun Python chuẩn.

• Nếu bạn đang sử dụng nhập để đọc một mô-đun, hãy nhớ rằng bạn phải khởi động lại trình thông dịch
hoặc sử dụng tải lại để đọc tệp đã sửa đổi. Nếu bạn nhập lại mô-đun, nó không làm được gì cả.

Nếu bạn gặp khó khăn và không thể tìm ra điều gì đang xảy ra, một cách tiếp cận là bắt đầu lại với một
chương trình mới như “Hello, World!” Và đảm bảo rằng bạn có thể chạy một chương trình đã biết. Sau đó, dần
dần thêm các phần của chương trình gốc vào phần mới.

A.2 Lỗi thời gian chạy

Khi chương trình của bạn chính xác về mặt cú pháp, Python có thể đọc nó và ít nhất là bắt đầu chạy nó.
Cái gì có thể đi sai?

A.2.1 Chương trình của tôi hoàn toàn không làm gì cả.

Sự cố này thường gặp nhất khi tệp của bạn bao gồm các hàm và lớp nhưng không thực sự gọi một hàm để bắt
đầu thực thi. Điều này có thể là cố ý nếu bạn chỉ định nhập mô-đun này để cung cấp các lớp và chức năng.

Nếu đó không phải là cố ý, hãy đảm bảo rằng có một lệnh gọi hàm trong chương trình và đảm bảo rằng quy
trình thực thi đạt đến nó (xem “Luồng thực thi” bên dưới).

A.2.2 Chương trình của tôi bị treo.

Nếu một chương trình dừng lại và dường như không làm gì cả, nó đang bị "treo". Thường thì điều đó có nghĩa
là nó bị mắc vào một vòng lặp vô hạn hoặc đệ quy vô hạn.

• Nếu có một vòng lặp cụ thể nào đó mà bạn nghi ngờ là sự cố, hãy thêm một câu lệnh in ngay trước vòng
lặp có nội dung “đang vào vòng lặp” và một câu lệnh khác ngay sau đó cho biết “thoát khỏi vòng lặp”.

Chạy chương trình. Nếu bạn nhận được tin nhắn đầu tiên chứ không phải tin nhắn thứ hai, bạn đã có
một vòng lặp vô hạn. Đi tới phần “Vòng lặp vô hạn” bên dưới.

• Hầu hết thời gian, một đệ quy vô hạn sẽ làm cho chương trình chạy một lúc và sau đó tạo ra lỗi
“RuntimeError: Maximum recursion depth vượt quá”. Nếu điều đó xảy ra, hãy chuyển đến phần “Đệ quy
vô hạn” bên dưới.

Nếu bạn không gặp lỗi này nhưng bạn nghi ngờ có vấn đề với một phương pháp hoặc hàm đệ quy, bạn vẫn
có thể sử dụng các kỹ thuật trong phần "Đệ quy vô hạn".

• Nếu cả hai bước đó đều không hoạt động, hãy bắt đầu kiểm tra các vòng lặp khác và các hàm đệ quy khác
và các phương pháp.

• Nếu cách đó không hiệu quả, thì có thể bạn không hiểu quy trình thực thi
tion trong chương trình của bạn. Đi tới phần "Quy trình thực thi" bên dưới.
Machine Translated by Google

196 Phụ lục A. Gỡ lỗi

Vòng lặp vô hạn

Nếu bạn nghĩ rằng bạn có một vòng lặp vô hạn và bạn nghĩ rằng bạn biết vòng lặp nào đang gây ra
sự cố, hãy thêm một câu lệnh in vào cuối vòng lặp in ra giá trị của các biến trong điều kiện và
giá trị của điều kiện.

Ví dụ:

trong khi x> 0 và y <0: #


làm điều gì đó với x #
làm điều gì đó với y

print ('x:', x)
print ('y:', y)
print ("condition:", (x> 0 and y <0))

Bây giờ khi bạn chạy chương trình, bạn sẽ thấy ba dòng đầu ra cho mỗi lần thông qua vòng lặp. Lần
cuối cùng thông qua vòng lặp, điều kiện phải là Sai. Nếu vòng lặp tiếp tục, bạn sẽ có thể thấy
các giá trị của x và y và bạn có thể tìm ra lý do tại sao chúng không được cập nhật chính xác.

Đệ quy vô hạn

Hầu hết thời gian, đệ quy vô hạn làm cho chương trình chạy một lúc và sau đó tạo ra lỗi vượt quá
độ sâu đệ quy tối đa.

Nếu bạn nghi ngờ rằng một hàm đang gây ra đệ quy vô hạn, hãy đảm bảo rằng có một trường hợp cơ
sở. Sẽ có một số điều kiện khiến hàm trả về mà không thực hiện lệnh gọi đệ quy. Nếu không, bạn
cần phải suy nghĩ lại thuật toán và xác định một trường hợp cơ sở.

Nếu có một trường hợp cơ sở nhưng chương trình dường như không đạt đến nó, hãy thêm một trạng
thái in vào đầu hàm in các tham số. Bây giờ khi bạn chạy chương trình, bạn sẽ thấy một vài dòng
đầu ra mỗi khi hàm được gọi, và bạn sẽ thấy các giá trị tham số. Nếu các tham số không di chuyển
về phía trường hợp cơ sở, bạn sẽ có một số ý tưởng về lý do tại sao không.

Luồng thực thi

Nếu bạn không chắc dòng thực thi đang di chuyển như thế nào trong chương trình của mình, hãy thêm
các câu lệnh in vào đầu mỗi hàm với một thông báo như “enter function foo”, trong đó foo là tên
của hàm.

Bây giờ khi bạn chạy chương trình, nó sẽ in ra một dấu vết của từng hàm khi nó được gọi.

A.2.3 Khi tôi chạy chương trình, tôi nhận được một ngoại lệ.

Nếu có gì đó xảy ra sự cố trong thời gian chạy, Python sẽ in một thông báo bao gồm tên của ngoại
lệ, dòng chương trình nơi xảy ra sự cố và truy nguyên.

Truy xuất nguồn gốc xác định chức năng hiện đang chạy, sau đó là chức năng đã gọi nó, sau đó là
chức năng đã gọi hàm đó, v.v. Nói cách khác, nó theo dõi
Machine Translated by Google

A.2. Lỗi thời gian chạy 197

chuỗi các lệnh gọi hàm đưa bạn đến vị trí hiện tại, bao gồm số dòng trong tệp của bạn nơi mỗi cuộc gọi xảy ra.

Bước đầu tiên là kiểm tra vị trí xảy ra lỗi trong chương trình và xem liệu bạn có thể tìm ra điều gì đã xảy

ra hay không. Đây là một số lỗi thời gian chạy phổ biến nhất:

NameError: Bạn đang cố gắng sử dụng một biến không tồn tại trong môi trường hiện tại.

Kiểm tra xem tên có đúng chính tả không, hoặc ít nhất là nhất quán. Và hãy nhớ rằng các biến cục bộ là

cục bộ; bạn không thể tham chiếu đến chúng từ bên ngoài hàm nơi chúng được xác định.

TypeError: Có một số nguyên nhân có thể xảy ra:

• Bạn đang cố gắng sử dụng một giá trị không đúng cách. Ví dụ: lập chỉ mục một chuỗi, danh sách hoặc

tuple với một cái gì đó khác với một số nguyên.

• Có sự không khớp giữa các mục trong một chuỗi định dạng và các mục được chuyển để chuyển đổi.

Điều này có thể xảy ra nếu số lượng mục không khớp hoặc chuyển đổi không hợp lệ được gọi.

• Bạn đang truyền sai số đối số cho một hàm. Đối với các phương thức, hãy xem định nghĩa phương

thức và kiểm tra xem tham số đầu tiên có phải là chính nó không. Sau đó, nhìn vào lệnh gọi phương

thức; đảm bảo rằng bạn đang gọi phương thức trên một đối tượng có kiểu phù hợp và cung cấp các

đối số khác một cách chính xác.

KeyError: Bạn đang cố gắng truy cập một phần tử của từ điển bằng một khóa mà từ điển không chứa. Nếu các

phím là chuỗi, hãy nhớ rằng việc viết hoa rất quan trọng.

AttributeError: Bạn đang cố gắng truy cập một thuộc tính hoặc phương thức không tồn tại. Kiểm tra chính tả!

Bạn có thể sử dụng các vars hàm tích hợp để liệt kê các thuộc tính tồn tại.

Nếu một AttributeError chỉ ra rằng một đối tượng có NoneType, điều đó có nghĩa là nó là Không có.

Vì vậy, vấn đề không phải là tên thuộc tính, mà là đối tượng.

Lý do đối tượng không có có thể là bạn quên trả về một giá trị từ một hàm func; nếu bạn đến cuối một

hàm mà không nhấn vào câu lệnh trả về, nó sẽ trả về Không có. Một nguyên nhân phổ biến khác là sử dụng

kết quả từ một phương pháp danh sách, chẳng hạn như sắp xếp,
trả về Không có.

IndexError: Chỉ mục bạn đang sử dụng để truy cập vào danh sách, chuỗi hoặc tuple lớn hơn độ dài của nó trừ đi

một. Ngay trước vị trí xảy ra lỗi, hãy thêm một câu lệnh in để hiển thị giá trị của chỉ mục và độ dài

của mảng. Mảng có kích thước phù hợp không?

Chỉ số có đúng giá trị không?

Trình gỡ lỗi Python (pdb) rất hữu ích để theo dõi các ngoại lệ vì nó cho phép bạn kiểm tra trạng thái của

chương trình ngay lập tức trước khi xảy ra lỗi. Bạn có thể đọc về pdb tại https://docs.python.org/3/library/
pdb.html.

A.2.4 Tôi đã thêm quá nhiều báo cáo in mà tôi nhận được đầu ra tràn ngập.

Một trong những vấn đề với việc sử dụng câu lệnh in để gỡ lỗi là bạn có thể bị chôn vùi trong đầu ra. Có hai

cách để tiến hành: đơn giản hóa đầu ra hoặc đơn giản hóa chương trình.
Machine Translated by Google

198 Phụ lục A. Gỡ lỗi

Để đơn giản hóa đầu ra, bạn có thể xóa hoặc nhận xét các câu lệnh in không giúp ích cho việc nhập
hoặc kết hợp chúng hoặc định dạng đầu ra sao cho dễ hiểu hơn.

Để đơn giản hóa chương trình, có một số điều bạn có thể làm. Đầu tiên, hãy thu nhỏ vấn đề mà chương
trình đang giải quyết. Ví dụ: nếu bạn đang tìm kiếm một danh sách, hãy tìm kiếm một danh sách nhỏ.
Nếu chương trình nhận đầu vào từ người dùng, hãy cung cấp cho nó đầu vào đơn giản nhất gây ra sự cố.

Thứ hai, dọn dẹp chương trình. Loại bỏ mã chết và tổ chức lại chương trình để làm cho nó dễ đọc nhất
có thể. Ví dụ: nếu bạn nghi ngờ rằng vấn đề nằm trong phần lồng nhau sâu của chương trình, hãy thử
viết lại phần đó với cấu trúc đơn giản hơn. Nếu bạn nghi ngờ một chức năng lớn, hãy thử chia nó
thành các chức năng nhỏ hơn và kiểm tra chúng riêng biệt.

Thường thì quá trình tìm kiếm trường hợp thử nghiệm tối thiểu sẽ dẫn bạn đến lỗi. Nếu bạn thấy rằng một chương trình

hoạt động trong một tình huống này nhưng không hoạt động trong một tình huống khác, điều đó sẽ cho bạn manh mối về những

gì đang diễn ra.

Tương tự, viết lại một đoạn mã có thể giúp bạn tìm ra những lỗi nhỏ. Nếu bạn thực hiện một thay đổi mà bạn cho

rằng không ảnh hưởng đến chương trình và nó thực hiện, điều đó có thể khiến bạn gặp khó khăn.

A.3 Lỗi ngữ nghĩa

Theo một số cách, lỗi ngữ nghĩa là lỗi khó gỡ lỗi nhất, vì trình thông dịch không cung cấp thông
tin về lỗi sai. Chỉ bạn mới biết chương trình phải làm những gì.

Bước đầu tiên là tạo kết nối giữa văn bản chương trình và hành vi bạn đang thấy. Bạn cần một giả
thuyết về những gì chương trình thực sự đang làm. Một trong những điều làm nên khó khăn đó là máy
tính chạy quá nhanh.

Bạn sẽ thường ước rằng bạn có thể làm chậm chương trình bằng tốc độ của con người và với một số
trình gỡ lỗi, bạn có thể làm được. Nhưng thời gian cần thiết để chèn một vài câu lệnh in đúng vị
trí thường ngắn so với việc thiết lập trình gỡ lỗi, chèn và loại bỏ các điểm ngắt và “hướng” chương
trình đến nơi xảy ra lỗi.

A.3.1 Chương trình của tôi không hoạt động.

Bạn nên tự hỏi mình những câu hỏi sau:

• Có điều gì đó mà chương trình phải làm nhưng dường như không xảy ra không? Tìm phần mã thực
hiện chức năng đó và đảm bảo rằng nó đang thực thi khi bạn cho rằng cần.

• Có điều gì đó xảy ra mà không nên? Tìm mã trong chương trình của bạn thực hiện chức năng đó
và xem liệu nó có đang thực thi khi không nên hay không.

• Một đoạn mã có tạo ra hiệu ứng không như bạn mong đợi không? Đảm bảo rằng bạn hiểu mã được đề
cập, đặc biệt nếu nó liên quan đến các hàm hoặc phương thức trong các mô-đun Python khác. Đọc
tài liệu cho các chức năng bạn gọi. Hãy thử chúng bằng cách viết các trường hợp thử nghiệm
đơn giản và kiểm tra kết quả.
Machine Translated by Google

A.3. Lỗi ngữ nghĩa 199

Để lập trình, bạn cần có một mô hình tinh thần về cách các chương trình hoạt động. Nếu bạn viết một chương
trình không thực hiện những gì bạn mong đợi, thường thì vấn đề không nằm trong chương trình; nó nằm trong
mô hình tinh thần của bạn.

Cách tốt nhất để điều chỉnh mô hình tinh thần của bạn là chia nhỏ chương trình thành các thành phần
của nó (thường là các chức năng và phương thức) và kiểm tra từng thành phần một cách độc lập. Một
khi bạn tìm thấy sự khác biệt giữa mô hình của bạn và thực tế, bạn có thể giải quyết vấn đề.

Tất nhiên, bạn nên xây dựng và thử nghiệm các thành phần khi bạn phát triển chương trình.
Nếu bạn gặp sự cố, chỉ nên có một lượng nhỏ mã mới chưa được biết là chính xác.

A.3.2 Tôi có biểu hiện nhiều lông và nó không hoạt động như tôi mong đợi.

Viết các biểu thức phức tạp miễn là có thể đọc được, nhưng chúng có thể khó gỡ lỗi. Việc chia một
biểu thức phức tạp thành một chuỗi các phép gán cho các biến tạm thời thường là một ý kiến hay.

Ví dụ:

self.hands [i] .addCard (self.hands [self.findNeighbor (i)]. PopCard ())

Điều này có thể được viết lại thành:

Neighbor = self.findNeighbor (i) pickCard


= self.hands [hàng xóm] .popCard () self.hands
[i] .addCard (pickCard)

Phiên bản rõ ràng dễ đọc hơn vì tên biến cung cấp tài liệu bổ sung và dễ gỡ lỗi hơn vì bạn có thể
kiểm tra kiểu của biến trung gian và hiển thị giá trị của chúng.

Một vấn đề khác có thể xảy ra với các biểu thức lớn là thứ tự đánh giá có thể không như bạn mong
đợi. Ví dụ: nếu bạn đang dịch biểu thức sang Python, bạn có thể viết: y = x / 2 * math.pi
x 2πkhông
Điều
đúng
đó vì
phép nhân và phép chia có cùng mức độ ưu tiên và được đánh giá từ trái sang phải. Vì vậy, biểu thức

này tính xπ / 2.

Một cách tốt để gỡ lỗi các biểu thức là thêm dấu ngoặc để làm cho thứ tự đánh giá rõ ràng: y = x /
(2 * math.pi)

Bất cứ khi nào bạn không chắc chắn về thứ tự đánh giá, hãy sử dụng dấu ngoặc đơn. Chương trình không
chỉ chính xác (theo nghĩa là thực hiện những gì bạn dự định), nó cũng sẽ dễ đọc hơn đối với những
người chưa ghi nhớ thứ tự của các hoạt động.

A.3.3 Tôi có một hàm không trả về những gì tôi mong đợi.

Nếu bạn có một câu lệnh trả về với một biểu thức phức tạp, bạn không có cơ hội in kết quả trước khi
trả về. Một lần nữa, bạn có thể sử dụng một biến tạm thời. Ví dụ, thay vì:

return self.hands [i] .removeMatches ()


Machine Translated by Google

200 Phụ lục A. Gỡ lỗi

bạn có thể viết:

count = self.hands [i] .removeMatches ()


số lượng trả lại

Bây giờ bạn có cơ hội để hiển thị giá trị của số đếm trước khi trả về.

A.3.4 Tôi thực sự rất, rất bế tắc và tôi cần được giúp đỡ.

Trước tiên, hãy thử rời khỏi máy tính trong vài phút. Máy tính phát ra sóng ảnh hưởng đến não, gây ra
các triệu chứng sau:

• Bực bội và thịnh nộ.

• Niềm tin mê tín (“máy tính ghét tôi”) và tư duy ma thuật (“chương trình
chỉ hoạt động khi tôi đội nón ra sau ”).

• Lập trình đi bộ ngẫu nhiên (nỗ lực lập trình bằng cách viết mọi pro gram có thể có và chọn một
trong những hoạt động đúng).

Nếu bạn thấy mình bị bất kỳ triệu chứng nào trong số những triệu chứng này, hãy đứng dậy và đi bộ. Khi
bạn bình tĩnh, hãy nghĩ về chương trình. Nó đang làm gì vậy? Một số nguyên nhân có thể có của hành vi
đó là gì? Lần cuối cùng bạn có một chương trình làm việc là khi nào và bạn làm gì tiếp theo?

Đôi khi chỉ mất thời gian để tìm ra lỗi. Tôi thường tìm thấy lỗi khi tôi không có máy tính và để tâm
trí của tôi lang thang. Một số nơi tốt nhất để tìm bọ là xe lửa, vòi hoa sen và trên giường, ngay trước
khi bạn ngủ.

A.3.5 Không, tôi thực sự cần giúp đỡ.

Nó xảy ra. Ngay cả những lập trình viên giỏi nhất đôi khi cũng gặp khó khăn. Đôi khi bạn làm việc trên
một chương trình quá lâu mà bạn không thể nhìn thấy lỗi. Bạn cần một đôi mắt tươi tắn.

Trước khi đưa người khác vào, hãy chắc chắn rằng bạn đã chuẩn bị sẵn sàng. Chương trình của bạn phải
đơn giản nhất có thể và bạn phải làm việc trên đầu vào nhỏ nhất gây ra lỗi.
Bạn nên in các báo cáo ở những nơi thích hợp (và đầu ra mà chúng tạo ra phải dễ hiểu). Bạn nên hiểu vấn
đề đủ tốt để mô tả nó một cách ngắn gọn.

Khi bạn đưa ai đó đến để giúp đỡ, hãy đảm bảo cung cấp cho họ thông tin họ cần:

• Nếu có một thông báo lỗi, đó là gì và nó chỉ ra phần nào của chương trình?

• Điều cuối cùng bạn làm trước khi lỗi này xảy ra là gì? Những dòng mã cuối cùng bạn đã viết là gì
hoặc trường hợp thử nghiệm mới không thành công là gì?

• Bạn đã cố gắng những gì cho đến nay, và bạn đã học được gì?

Khi bạn tìm thấy lỗi, hãy dành một giây để suy nghĩ về những gì bạn có thể đã làm để tìm ra lỗi nhanh
hơn. Lần tới khi bạn gặp thứ gì đó tương tự, bạn sẽ có thể tìm ra lỗi nhanh hơn.

Hãy nhớ rằng, mục tiêu không chỉ là làm cho chương trình hoạt động. Mục đích là học cách làm cho chương
trình hoạt động.
Machine Translated by Google

Phụ lục B

Phân tích các thuật toán

Phụ lục này là một đoạn trích đã chỉnh sửa từ Think Complexity, của Allen B. Downey,
cũng được xuất bản bởi O'Reilly Media (2012). Khi bạn hoàn thành cuốn sách này, bạn
có thể muốn chuyển sang cuốn sách đó.

Phân tích thuật toán là một nhánh của khoa học máy tính nghiên cứu hiệu suất của các thuật
toán, đặc biệt là các yêu cầu về thời gian và không gian chạy của chúng. Xem http: //
en.wikipedia. org / wiki / Analysis_of_algorithm.

Mục tiêu thực tế của phân tích thuật toán là dự đoán hiệu suất của các nhịp điệu thuật toán
khác nhau để hướng dẫn các quyết định thiết kế.

Trong Chiến dịch tranh cử Tổng thống Hoa Kỳ năm 2008, ứng cử viên Barack Obama được yêu cầu
thực hiện một phân tích ngẫu hứng khi ông đến thăm Google. Giám đốc điều hành Eric Schmidt đã
hỏi đùa ông về "cách hiệu quả nhất để sắp xếp một triệu số nguyên 32 bit." Obama rõ ràng đã bị
lật tẩy, bởi vì ông ấy nhanh chóng trả lời, "Tôi nghĩ loại bong bóng sẽ là cách đi sai lầm."
Xem http://www.youtube.com/watch?v=k4RRi_ntQc8.

Điều này đúng: sắp xếp bong bóng là khái niệm đơn giản nhưng chậm đối với các bộ dữ liệu lớn.
Một swer mà Schmidt có lẽ đang tìm kiếm là "sắp xếp cơ số" (http://en.wikipedia.org/wiki/
1
Radix_sort) .

Mục tiêu của phân tích thuật toán là tạo ra các so sánh có ý nghĩa giữa các thuật toán, nhưng
có một số vấn đề:

• Hiệu suất tương đối của các thuật toán có thể phụ thuộc vào đặc điểm của phần cứng, do
đó, một thuật toán có thể nhanh hơn trên Máy A, một thuật toán khác trên Máy B.
Giải pháp chung cho vấn đề này là chỉ định một mô hình máy và phân tích số bước, hoặc
hoạt động, một thuật toán yêu cầu theo một mô hình nhất định.

• Hiệu suất tương đối có thể phụ thuộc vào chi tiết của tập dữ liệu. Ví dụ: một số thuật
toán sắp xếp chạy nhanh hơn nếu dữ liệu đã được sắp xếp một phần; các thuật toán khác
1
Nhưng nếu bạn nhận được câu hỏi như thế này trong một cuộc phỏng vấn, tôi nghĩ câu trả lời tốt hơn là, “Cách
nhanh nhất để sắp xếp một triệu số nguyên là sử dụng bất kỳ hàm sắp xếp nào được cung cấp bởi ngôn ngữ tôi đang sử
dụng. Hiệu suất của nó đủ tốt cho phần lớn các ứng dụng, nhưng nếu ứng dụng của tôi chạy quá chậm, tôi sẽ sử dụng
một trình mô tả để xem thời gian đã được sử dụng ở đâu. Nếu nó trông giống như một thuật toán sắp xếp nhanh hơn sẽ
có ảnh hưởng đáng kể đến hiệu suất, thì tôi sẽ tìm cách triển khai tốt sắp xếp cơ số ”.
Machine Translated by Google

202 Phụ lục B. Phân tích các thuật toán

chạy chậm hơn trong trường hợp này. Một cách phổ biến để tránh vấn đề này là phân tích điều tồi tệ nhất

tình huống trường hợp . Đôi khi phân tích hiệu suất trường hợp trung bình rất hữu ích, nhưng đó là

thường khó hơn và có thể không rõ ràng là nhóm trường hợp nào để trung bình vượt qua.

• Hiệu suất tương đối cũng phụ thuộc vào quy mô của vấn đề. Một thuật toán sắp xếp

nhanh đối với danh sách nhỏ có thể chậm đối với danh sách dài. Giải pháp thông thường cho điều này

vấn đề là thể hiện thời gian chạy (hoặc số lượng hoạt động) như một hàm của vấn đề

kích thước và nhóm các chức năng thành các danh mục tùy thuộc vào tốc độ phát triển của chúng như

kích thước vấn đề tăng lên.

Cái hay của kiểu so sánh này là nó cho phép phân loại đơn giản

của các thuật toán. Ví dụ: nếu tôi biết rằng thời gian chạy của Thuật toán A có xu hướng tỷ lệ thuận với
2, sau đó tôi
kích thước của đầu vào, n và Thuật toán B có xu hướng tỷ lệ với n

mong đợi A nhanh hơn B, ít nhất là đối với các giá trị lớn của n.

Loại phân tích này đi kèm với một số cảnh báo, nhưng chúng ta sẽ nói về điều đó sau.

B.1 Thứ tự tăng trưởng

Giả sử bạn đã phân tích hai thuật toán và thể hiện thời gian chạy của chúng theo

kích thước của đầu vào: Thuật toán A thực hiện 100n + 1 bước để giải một bài toán có kích thước n; Algo 2
rithm B lấy n
+ n + 1 bước.

Bảng sau đây cho thấy thời gian chạy của các thuật toán này đối với các kích thước vấn đề khác nhau:

Nhập Thời gian chạy của Thời gian chạy của

kích thước Thuật toán A Thuật toán B


10 1 001 111

100 10 001 10 101

1 000 100 001 1 001 001

10 000 1 000 001 100 010 001

Tại n = 10, Thuật toán A trông khá tệ; nó mất gần 10 lần so với Thuật toán

B. Nhưng đối với n = 100, chúng có giá trị như nhau, và đối với giá trị lớn hơn A thì tốt hơn nhiều.

2 kỳ hạn
Lý do cơ bản là đối với các giá trị lớn của n, bất kỳ hàm nào chứa n

sẽ phát triển nhanh hơn một hàm có số hạng đứng đầu là n. Thuật ngữ hàng đầu là thuật ngữ có

số mũ cao nhất.

Đối với thuật toán A, số hạng đứng đầu có hệ số lớn, 100, đó là lý do tại sao B hoạt động tốt hơn

hơn A đối với n nhỏ. Nhưng bất kể các hệ số như thế nào, sẽ luôn có một số giá trị của n

trong đó an2 > bn, với mọi giá trị của a và b.

Đối số tương tự cũng áp dụng cho các điều khoản không đứng đầu. Ngay cả khi thời gian chạy của Thuật toán A

là n + 1000000, nó vẫn sẽ tốt hơn Thuật toán B cho n đủ lớn.

Nói chung, chúng tôi mong đợi một thuật toán có số hạng đứng đầu nhỏ hơn sẽ là một thuật toán tốt hơn cho

các vấn đề lớn, nhưng đối với các vấn đề nhỏ hơn, có thể có một điểm giao nhau ở đó

thuật toán tốt hơn. Vị trí của điểm giao nhau phụ thuộc vào chi tiết của nhịp điệu thuật toán, đầu vào và

phần cứng, vì vậy nó thường bị bỏ qua cho các mục đích của thuật toán

phân tích. Nhưng điều đó không có nghĩa là bạn có thể quên nó.
Machine Translated by Google

B.1. Thứ tự tăng trưởng 203

Nếu hai thuật toán có cùng một thuật ngữ thứ tự hàng đầu, thật khó để nói thuật toán nào tốt hơn; một lần nữa,

câu trả lời phụ thuộc vào các chi tiết. Vì vậy, đối với phân tích thuật toán, các hàm có cùng số hạng đứng

đầu được coi là tương đương, ngay cả khi chúng có hệ số khác nhau.

Thứ tự tăng trưởng là một tập hợp các chức năng mà hành vi tăng trưởng của chúng được coi là tương đương.

Ví dụ, 2n, 100n và n + 1 thuộc cùng một bậc tăng trưởng, được viết O (n) trong ký hiệu Big-Oh và thường được

gọi là tuyến tính vì mọi hàm trong tập hợp đều phát triển tuyến tính với n.

2 2
Tất cả các hàm có số hạng đứng đầu là n Bảng thuộc về O (n ); chúng được gọi là bậc hai.

sau đây cho thấy một số thứ tự tăng trưởng xuất hiện phổ biến nhất trong phân tích thuật toán, theo thứ tự

xấu tăng dần.

Thứ tự Tên

tăng trưởng
không thay đổi
O (1)

O (logb n) logarit (với b bất kỳ)


Trên) tuyến

O (n logb tính tuyến tính

Trên 2 bậc hai bậc


3
Trên n))) hai

O (c n ) lũy thừa (với c bất kỳ)

Đối với các số hạng lôgarit, cơ số của lôgarit không quan trọng; thay đổi cơ số tương đương với việc nhân với

một hằng số, điều này không thay đổi thứ tự tăng trưởng. Nói một cách thông minh, tất cả các hàm số mũ đều

thuộc cùng một thứ tự tăng trưởng bất kể cơ số của số mũ. Các hàm số mũ phát triển rất nhanh, vì vậy các thuật

toán hàm số mũ chỉ hữu ích cho các vấn đề nhỏ.

Bài tập B.1. Đọc trang Wikipedia về ký hiệu Big-Oh tại http: // vi. wikipedia. org / wiki / ký hiệu Big_ O_

và trả lời các câu hỏi sau:

3 + n 2? Còn n3 +
1. Thứ tự tăng trưởng của n3 + n 2 là gì? Còn 1000000n 1000000n 2 thì sao?

2 + n) · (n + 1)? Trước khi bắt đầu nhân, hãy nhớ


2. Thứ tự tăng trưởng của (n
rằng bạn chỉ cần số hạng đứng đầu.

3. Nếu f nằm trong O (g), đối với một hàm không xác định nào đó g, chúng ta có thể nói gì về af + b, trong đó a và
b là hằng số?

4. Nếu f1 và f2 nằm trong O (g), chúng ta có thể nói gì về f1 + f2?

5. Nếu f1 ở O (g) và f2 ở O (h), chúng ta có thể nói gì về f1 + f2?

6. Nếu f1 nằm trong O (g) và f2 là O (h), chúng ta có thể nói gì về f1 · f2?

Các lập trình viên quan tâm đến hiệu suất thường thấy loại phân tích này khó đạt được mức thấp. Họ có một

điểm: đôi khi các hệ số và các số hạng không đứng đầu tạo ra sự khác biệt thực sự. Đôi khi các chi tiết về

phần cứng, ngôn ngữ lập trình và đặc điểm của đầu vào tạo ra sự khác biệt lớn. Và đối với những vấn đề nhỏ,

thứ tự tăng trưởng là không liên quan.

Nhưng nếu bạn lưu ý những điều đó, phân tích thuật toán là một công cụ hữu ích. Ít nhất đối với các bài toán

lớn, thuật toán “tốt hơn” thường tốt hơn và đôi khi nó tốt hơn nhiều.

Sự khác biệt giữa hai thuật toán có cùng thứ tự tăng trưởng thường là một hệ số không đổi, nhưng sự khác biệt

giữa một thuật toán tốt và một thuật toán xấu là không có giới hạn!
Machine Translated by Google

204 Phụ lục B. Phân tích các thuật toán

B.2 Phân tích các hoạt động Python cơ bản

Trong Python, hầu hết các phép toán số học là thời gian không đổi; phép nhân thường mất nhiều thời gian
hơn phép cộng và trừ, và phép chia thậm chí còn lâu hơn, nhưng thời gian chạy này không phụ thuộc vào độ
lớn của toán hạng. Số nguyên rất lớn là một ngoại lệ; trong trường hợp đó thời gian chạy tăng lên theo số
chữ số.

Các thao tác lập chỉ mục — đọc hoặc ghi các phần tử trong một chuỗi hoặc từ điển — cũng là thời gian
không đổi, bất kể kích thước của cấu trúc dữ liệu.

Vòng lặp for đi qua một chuỗi hoặc từ điển thường là tuyến tính, miễn là tất cả các hoạt động trong phần
thân của vòng lặp là thời gian không đổi. Ví dụ: việc cộng các phần tử của danh sách là tuyến tính:

tổng = 0

cho x trong t:
tổng + = x

Tổng hàm tích hợp sẵn cũng tuyến tính vì nó làm điều tương tự, nhưng nó có xu hướng nhanh hơn vì nó là một
triển khai hiệu quả hơn; trong ngôn ngữ của phân tích thuật toán, nó có một hệ số hàng đầu nhỏ hơn.

a + 1
Theo quy tắc chung, nếu phần thân của vòng lặp nằm trong O (n a ) thì toàn bộ vòng lặp nằm ). Các
trong O (n ngoại lệ là nếu bạn có thể chỉ ra rằng vòng lặp thoát ra sau một số lần lặp không đổi. Nếu một
vòng lặp chạy k lần không phụ thuộc vào n, thì vòng lặp là O (n a ), ngay cả đối với k lớn.

Nhân với k không thay đổi thứ tự tăng trưởng, nhưng cũng không chia. Vì vậy, nếu
a + 1
phần thân của vòng lặp ở O (n a ) và nó chạy n / k lần, vòng lặp ở O (n ), ngay cả đối với k lớn.

Hầu hết các hoạt động chuỗi và tuple là tuyến tính, ngoại trừ lập chỉ mục và len, là thời gian không đổi.
Các hàm min và max tích hợp là tuyến tính. Thời gian chạy của một thao tác lát tỷ lệ với độ dài của đầu
ra, nhưng không phụ thuộc vào kích thước của đầu vào.

Nối chuỗi là tuyến tính; thời gian chạy phụ thuộc vào tổng độ dài của các toán hạng.

Tất cả các phương thức chuỗi là tuyến tính, nhưng nếu độ dài của các chuỗi được giới hạn bởi một hằng số -
ví dụ, các phép toán trên các ký tự đơn lẻ - thì chúng được coi là thời gian không đổi. Phương thức tham
gia chuỗi là tuyến tính; thời gian chạy phụ thuộc vào tổng chiều dài của các chuỗi.

Hầu hết các phương pháp danh sách là tuyến tính, nhưng có một số ngoại lệ:

• Việc thêm một phần tử vào cuối danh sách là thời gian trung bình không đổi; khi nó hết chỗ, nó thỉnh
thoảng được sao chép sang một vị trí lớn hơn, nhưng tổng thời gian cho n thao tác là O (n), vì vậy
thời gian trung bình cho mỗi thao tác là O (1).

• Xóa một phần tử khỏi cuối danh sách là thời gian không đổi.

• Sắp xếp là O (n log n).

Hầu hết các hoạt động và phương thức từ điển là thời gian không đổi, nhưng có một số ngoại lệ:

• Thời gian chạy cập nhật tỷ lệ với kích thước của từ điển được truyền dưới dạng pa
rameter, không phải từ điển đang được cập nhật.

• các khóa, giá trị và các mục là thời gian không đổi vì chúng trả về các trình vòng lặp. Nhưng nếu bạn
lặp qua các trình vòng lặp, vòng lặp sẽ là tuyến tính.
Machine Translated by Google

B.3. Phân tích các thuật toán tìm kiếm 205

Hiệu suất của từ điển là một trong những điều kỳ diệu nhỏ của khoa học máy tính. Chúng ta sẽ xem cách chúng hoạt

động trong Phần B.4.

Bài tập B.2. Đọc trang Wikipedia về thuật toán sắp xếp tại http: // vi. wikipedia. thuật toán org / wiki / Sorting_

và trả lời các câu hỏi sau:

1. “Sắp xếp so sánh là gì?” Thứ tự tăng trưởng trong trường hợp xấu nhất tốt nhất để so sánh là gì

loại? Thứ tự tăng trưởng trong trường hợp xấu nhất tốt nhất cho bất kỳ thuật toán sắp xếp nào?

2. Thứ tự phát triển của loại bong bóng là gì, và tại sao Barack Obama cho rằng nó là "sai

con đường để đi? ”

3. Thứ tự tăng trưởng của sắp xếp cơ số là gì? Chúng ta cần những điều kiện tiên quyết nào để sử dụng nó?

4. Sắp xếp ổn định là gì và tại sao nó có thể quan trọng trong thực tế?

5. Thuật toán sắp xếp tệ nhất (có tên) là gì?

6. Thư viện C sử dụng thuật toán sắp xếp nào? Python sử dụng thuật toán sắp xếp nào? Đây có phải là

thuật toán ổn định? Bạn có thể phải Google xung quanh để tìm những câu trả lời này.

7. Nhiều kiểu không so sánh là tuyến tính, vậy tại sao Python lại sử dụng kiểu so sánh O (n log n )
phân loại ison?

B.3 Phân tích các thuật toán tìm kiếm

Tìm kiếm là một thuật toán lấy một tập hợp và một mục đích và xác định xem mục tiêu có nằm trong tập hợp hay không,

thường trả về chỉ mục của mục tiêu.

Thuật toán tìm kiếm đơn giản nhất là “tìm kiếm tuyến tính”, nó sẽ duyệt qua các mục của bộ sưu tập theo thứ tự,

dừng lại nếu nó tìm thấy mục tiêu. Trong trường hợp xấu nhất, nó phải đi qua toàn bộ bộ sưu tập, vì vậy thời gian
chạy là tuyến tính.

Toán tử in cho chuỗi sử dụng tìm kiếm tuyến tính; các phương thức chuỗi như find và
đếm.

Nếu các phần tử của dãy có thứ tự, bạn có thể sử dụng tìm kiếm phân giác, đó là O (log n). Tìm kiếm phân tách tương

tự như thuật toán bạn có thể sử dụng để tra một từ trong từ điển (từ điển giấy, không phải cấu trúc dữ liệu). Thay

vì bắt đầu từ ginning và kiểm tra từng mục theo thứ tự, bạn bắt đầu với mục ở giữa và kiểm tra xem từ bạn đang tìm

kiếm đến trước hay sau. Nếu nó đến trước, thì bạn tìm kiếm nửa đầu của dãy số. Nếu không, bạn tìm kiếm nửa thứ

hai. Dù bằng cách nào, bạn cũng cắt giảm một nửa số mặt hàng còn lại.

Nếu chuỗi có 1.000.000 mục, sẽ mất khoảng 20 bước để tìm từ hoặc kết luận rằng từ đó không có ở đó. Vì vậy, nó
nhanh hơn khoảng 50.000 lần so với tìm kiếm tuyến tính.

Tìm kiếm phân tách có thể nhanh hơn nhiều so với tìm kiếm tuyến tính, nhưng nó yêu cầu trình tự phải theo thứ tự,

điều này có thể yêu cầu thêm công việc.

Có một cấu trúc dữ liệu khác, được gọi là bảng băm thậm chí còn nhanh hơn — nó có thể thực hiện tìm kiếm trong

thời gian liên tục — và không yêu cầu sắp xếp các mục. Từ điển Python được triển khai bằng cách sử dụng bảng băm,

đó là lý do tại sao hầu hết các hoạt động từ điển, bao gồm cả toán tử in, là thời gian không đổi.
Machine Translated by Google

206 Phụ lục B. Phân tích các thuật toán

B.4 Bảng băm

Để giải thích cách các bảng băm hoạt động và tại sao hiệu suất của chúng lại tốt như vậy, tôi bắt đầu với

việc triển khai một bản đồ đơn giản và dần dần cải thiện nó cho đến khi nó là một bảng băm.

Tôi sử dụng Python để chứng minh những triển khai này, nhưng trong cuộc sống thực, bạn sẽ không viết mã như

thế này bằng Python; bạn sẽ chỉ sử dụng một từ điển! Vì vậy, trong phần còn lại của chương này, bạn phải

tưởng tượng rằng từ điển không tồn tại và bạn muốn triển khai cấu trúc dữ liệu ánh xạ từ khóa đến giá trị.

Các hoạt động bạn phải thực hiện là:

add (k, v): Thêm một mục mới ánh xạ từ khóa k đến giá trị v. Với từ điển Python, d, thao tác này được viết d

[k] = v.

get (k): Tra cứu và trả về giá trị tương ứng với khóa k. Với từ điển Python,

d, phép toán này được viết d [k] hoặc d.get (k).

Hiện tại, tôi cho rằng mỗi khóa chỉ xuất hiện một lần. Cách triển khai đơn giản nhất của giao diện này sử

dụng danh sách các bộ giá trị, trong đó mỗi bộ giá trị là một cặp khóa-giá trị.

lớp LinearMap:

def __init __ (self):


self.items = []

def add (self, k, v):

self.items.append ((k, v))

def get (self, k): for


key, val in self.items: if key == k:

return val

nâng KeyError

add thêm một bộ khóa-giá trị vào danh sách các mục, việc này cần thời gian cố định.

get sử dụng vòng lặp for để tìm kiếm danh sách: nếu nó tìm thấy khóa đích, nó sẽ trả về giá trị tương ứng; nếu
không nó sẽ tạo ra một KeyError. Vì vậy, nhận được là tuyến tính.

Một giải pháp thay thế là giữ danh sách được sắp xếp theo khóa. Sau đó, get có thể sử dụng tìm kiếm phân

giác, đó là O (log n). Nhưng việc chèn một mục mới vào giữa danh sách là tuyến tính, vì vậy đây có thể không

phải là lựa chọn tốt nhất. Có những cấu trúc dữ liệu khác có thể triển khai thêm và lấy thời gian ghi nhật

ký, nhưng điều đó vẫn không tốt bằng thời gian không đổi, vì vậy hãy tiếp tục.

Một cách để cải thiện Bản đồ tuyến tính là chia danh sách các cặp khóa-giá trị thành các danh sách nhỏ hơn.

Đây là một triển khai có tên BetterMap, là danh sách 100 Sơ đồ tuyến tính. Như chúng ta sẽ thấy trong giây

lát, thứ tự tăng trưởng của get vẫn là tuyến tính, nhưng BetterMap là một bước trên con đường hướng tới

hashtables: class BetterMap:

def __init __ (self, n = 100):

self.maps = [] cho

tôi trong range (n):


self.maps.append (LinearMap ())
Machine Translated by Google

B 4. Hashtables 207

def find_map (self, k):


index = hash (k)% len (self.maps) return
self.maps [index]

def add (self, k, v): m


= self.find_map (k) m.add
(k, v)

def get (self, k): m


= self.find_map (k) return
m.get (k) __init__ tạo

một danh sách gồm n Sơ đồ tuyến tính.

find_map được sử dụng bằng cách thêm và nhận để tìm ra bản đồ để đưa mục mới vào hoặc bản đồ nào
để tìm kiếm.

find_map sử dụng hàm băm có sẵn, hàm này nhận hầu hết mọi đối tượng Python và trả về một số nguyên.
Một hạn chế của việc triển khai này là nó chỉ hoạt động với các khóa có thể băm.
Các loại có thể thay đổi như danh sách và từ điển đều không thể thay đổi được.

Các đối tượng có thể băm được coi là tương đương trả về cùng một giá trị băm, nhưng câu trái
ngược không nhất thiết phải đúng: hai đối tượng có giá trị khác nhau có thể trả về cùng một giá
trị băm.

find_map sử dụng toán tử mô-đun để gói các giá trị băm vào phạm vi từ 0 đến len (self.maps), vì
vậy kết quả là một chỉ mục hợp pháp trong danh sách. Tất nhiên, điều này có nghĩa là nhiều giá trị
băm khác nhau sẽ nằm trên cùng một chỉ mục. Nhưng nếu hàm băm dàn trải mọi thứ khá đồng đều (đó là
những gì các hàm băm được thiết kế để làm), thì chúng tôi mong đợi n / 100 mục trên mỗi Bản đồ
tuyến tính.

Vì thời gian chạy của LinearMap.get tỷ lệ với số lượng mục, chúng tôi hy vọng BetterMap sẽ nhanh
hơn LinearMap khoảng 100 lần. Thứ tự tăng trưởng vẫn là tuyến tính, nhưng hệ số hàng đầu nhỏ hơn.
Điều đó tốt, nhưng vẫn không tốt bằng bảng băm.

Đây (cuối cùng) là ý tưởng quan trọng giúp các hashtable nhanh chóng: nếu bạn có thể giữ độ dài
tối đa của LinearMaps bị giới hạn, LinearMap.get là thời gian không đổi. Tất cả những gì bạn phải
làm là theo dõi số lượng mục và khi số lượng mục trên mỗi Bản đồ tuyến tính vượt quá ngưỡng, hãy
thay đổi kích thước bảng băm bằng cách thêm nhiều Bản đồ tuyến tính hơn.

Đây là cách triển khai của một hashtable:

class HashMap:

def __init __ (self):

self.maps = BetterMap (2)


self.num = 0

def get (self, k):


trả về self.maps.get (k)

def add (self, k, v): if


self.num == len (self.maps.maps):
Machine Translated by Google

208 Phụ lục B. Phân tích các thuật toán

self.resize ()

self.maps.add (k, v)
self.num + = 1

def thay đổi kích thước

(self): new_maps = BetterMap (self.num * 2)

cho m trong self.maps.maps: cho k,


v trong m.items: new_maps.add

(k, v)

self.maps = new_maps

__init__ tạo Bản đồ tốt hơn và khởi tạo num, theo dõi số lượng mục.

chỉ nhận công văn tới BetterMap. Công việc thực sự xảy ra trong add, kiểm tra số lượng mục và kích thước của

Bản đồ tốt hơn: nếu chúng bằng nhau, số lượng mục trung bình trên mỗi Bản đồ tuyến tính là 1, vì vậy nó gọi là

thay đổi kích thước.

thay đổi kích thước tạo Bản đồ tốt hơn mới, lớn gấp đôi so với bản đồ trước và sau đó “nhấn mạnh lại” các mục

từ bản đồ cũ sang bản đồ mới.

Việc băm lại là cần thiết vì việc thay đổi số lượng Bản đồ tuyến tính sẽ thay đổi mẫu số của toán tử mô đun

trong find_map. Điều đó có nghĩa là một số đối tượng được sử dụng để băm vào cùng một Sơ đồ tuyến tính sẽ bị

tách ra (đó là những gì chúng tôi muốn, phải không?).

Việc băm lại là tuyến tính, vì vậy thay đổi kích thước là tuyến tính, điều này có vẻ không tốt, vì tôi đã hứa

rằng việc thêm sẽ là thời gian không đổi. Nhưng hãy nhớ rằng chúng ta không phải thay đổi kích thước mọi lúc,

vì vậy thêm thường là thời gian không đổi và chỉ đôi khi là tuyến tính. Tổng khối lượng công việc phải chạy

thêm n lần tỷ lệ với n, do đó thời gian trung bình của mỗi lần thêm là thời gian không đổi!

Để xem cách này hoạt động như thế nào, hãy nghĩ đến việc bắt đầu với một HashTable trống và thêm một loạt các

mục. Chúng tôi bắt đầu với 2 Bản đồ tuyến tính, vì vậy 2 lần bổ sung đầu tiên diễn ra nhanh chóng (không cần

thay đổi kích thước lại). Giả sử rằng họ đảm nhận mỗi đơn vị công việc. Lần bổ sung tiếp theo yêu cầu thay đổi
kích thước, vì vậy chúng ta phải trộn hai mục đầu tiên (chúng ta hãy gọi đó là 2 đơn vị công việc nữa) và sau

đó thêm mục thứ ba (một đơn vị nữa). Thêm hạng mục tiếp theo tốn 1 đơn vị, vì vậy tổng số cho đến nay là 6 đơn
vị công việc cho 4 hạng mục.

Lần bổ sung tiếp theo có giá 5 đơn vị, nhưng ba lần tiếp theo mỗi lần chỉ có một đơn vị, vì vậy tổng cộng là 14 đơn
vị cho 8 lần thêm đầu tiên.

Lần bổ sung tiếp theo có giá 9 đơn vị, nhưng sau đó chúng ta có thể thêm 7 đơn vị nữa trước khi thay đổi kích thước tiếp theo, do đó, tổng số

là 30 đơn vị cho 16 đơn vị đầu tiên thêm vào.

Sau khi thêm 32, tổng chi phí là 62 đơn vị, và tôi hy vọng bạn đang bắt đầu thấy một mô hình. Sau khi thêm n,

với n là lũy thừa của hai, tổng chi phí là 2n - 2 đơn vị, do đó công việc trung bình mỗi lần cộng nhỏ hơn 2 đơn

vị một chút. Khi n là một lũy thừa của hai, đó là trường hợp tốt nhất; đối với các giá trị khác của n công việc

trung bình cao hơn một chút, nhưng điều đó không quan trọng. Điều quan trọng là nó là O (1).

Hình B.1 cho thấy cách này hoạt động bằng đồ thị. Mỗi khối đại diện cho một đơn vị công việc. Các cột hiển thị

tổng công việc cho mỗi lần bổ sung theo thứ tự từ trái sang phải: hai cột đầu tiên thêm chi phí mỗi lần 1 đơn
vị, cột thứ ba có giá 3 đơn vị, v.v.
Machine Translated by Google

B.5. Bảng chú giải 209

Hình B.1: Chi phí của một phép cộng bảng băm.

Công việc bổ sung của việc băm nhỏ lại xuất hiện dưới dạng một chuỗi các tòa tháp ngày càng cao với khoảng trống

tăng dần giữa chúng. Bây giờ, nếu bạn đánh sập các tháp, trải rộng chi phí thay đổi kích thước trên tất cả các

tháp bổ sung, bạn có thể thấy bằng đồ thị rằng tổng chi phí sau khi n thêm vào là 2n - 2.

Một tính năng quan trọng của thuật toán này là khi chúng ta thay đổi kích thước HashTable, nó sẽ phát triển về

mặt hình học; nghĩa là, chúng tôi nhân kích thước với một hằng số. Nếu bạn tăng kích thước về mặt số học — thêm

một số cố định mỗi lần — thì thời gian trung bình cho mỗi lần thêm là tuyến tính.

Bạn có thể tải xuống bản triển khai HashMap của tôi từ http://thinkpython2.com/ code / Map.py, nhưng hãy nhớ rằng

không có lý do gì để sử dụng nó; nếu bạn muốn có bản đồ, chỉ cần sử dụng từ điển Python.

B.5 Bảng chú giải thuật ngữ

phân tích các thuật toán: Một cách để so sánh các thuật toán về thời gian chạy của chúng và / hoặc

yêu cầu về không gian.

mô hình máy: Một đại diện đơn giản của một máy tính được sử dụng để mô tả các thuật toán.

trường hợp xấu nhất: Đầu vào làm cho một thuật toán nhất định chạy chậm nhất (hoặc yêu cầu nhiều nhất

không gian).

số hạng đứng đầu: Trong một đa thức, số hạng có số mũ cao nhất.

điểm chéo: Kích thước vấn đề trong đó hai thuật toán yêu cầu cùng một thời gian chạy hoặc

không gian.

thứ tự tăng trưởng: Một tập hợp các hàm đều phát triển theo cách được coi là tương đương với các tư thế phân

tích thuật toán. Ví dụ, tất cả các hàm phát triển tuyến tính đều thuộc cùng một thứ tự tăng trưởng.

Ký hiệu Big-Oh: Ký hiệu đại diện cho thứ tự tăng trưởng; ví dụ, O (n) repre

sents tập hợp các hàm phát triển tuyến tính.

tuyến tính: Thuật toán có thời gian chạy tỷ lệ với kích thước vấn đề, ít nhất là

kích thước vấn đề.

2, trong đó n là thước đo của


bậc hai: Một thuật toán có thời gian chạy tỷ lệ với n kích thước bài toán.

tìm kiếm: Vấn đề xác định vị trí một phần tử của tập hợp (như danh sách hoặc từ điển) hoặc xác định rằng nó không

có mặt.
Machine Translated by Google

210 Phụ lục B. Phân tích các thuật toán

hashtable: Một cấu trúc dữ liệu đại diện cho một tập hợp các cặp khóa-giá trị và thực hiện
tìm kiếm trong thời gian không đổi.
Machine Translated by Google

Mục lục

abecedarian, 73, 84 hàm tăng cường, 93, 100 mục,

abs, 52 đường dẫn tuyệt 74, 90, 116 tuple, 116,

đối, 139, 145 truy cập, 90 bộ 117, 119, 122 câu lệnh gán, 9

tích lũy, 100 biểu đồ, 127 thuộc tính, 153, 169 __dict__, 168

danh sách, 93 chuỗi, 175 tổng, lớp, 172, 180 khởi tạo, 168 phiên

93 bản, 148, 153, 172, 180

Hàm Ackermann, 61, 113 thêm phương AttributeError, 152, 197 chuyển

thức, 165 cộng với mang, 68 thuật nhượng tăng cường, 93, 100

toán, 67, 69, 130, 201 Austen, Jane, 127

trường hợp trung bình,

MD5, 146 căn 202 chi phí trung bình, 208

bậc hai, 69 răng

cưa, 95, 96, 100, 149, 151, 170 sao chép để tính xấu, 203

tránh, 99 tất cả, 186 bảng chữ cái, trường hợp cơ bản,

37 thực thi thay thế, 41 mơ hồ, 5 đảo chữ, 44, 47 điểm chuẩn, 133, 134

101 bộ đảo chữ, 123, 145 phân tích thuật BetterMap, 206 biểu

toán , 201, 209 phân tích các nguyên thủy, hiện lớn, nhiều lông, 199

204 và toán tử, 40 bất kỳ, 185 phương thức Ký hiệu Big-Oh, 209 ký hiệu

nối thêm, 92, 97, 101, 174, 175 hàm cung, big-oh, 203 tìm kiếm nhị

31 phân, 101 bingo, 123 sinh

nhật, 160 nghịch lý sinh

nhật, 101 mô-đun phân giác,

101 tìm kiếm phân giác, 101,

205 phân giác, gỡ lỗi bằng,

68 toán tử bitwise, 3 body, 19,

26, 65 kiểu bool, 40 biểu thức

Xoắn ốc Archimedian, 38 đối boolean, 40, 47 hàm boolean, 54 toán

số, 17, 19, 21, 22, 26, 97 tập hợp, 118 tử boolean, 76 phép mượn, phép trừ

từ khóa, 33, 36, 191 danh sách, với, 68, 159 có giới hạn, 207 dấu

97 tùy chọn, 76, 79, 95, 107, 184 ngoặc vuông, 103 toán tử ngoặc, 71,

vị trí, 164, 169, 190 biến- bộ 90, 116 nhánh, 41, 47

chiều dài, 118 phân tán đối số, 118

toán tử số học, 3 câu lệnh khẳng

định, 159, 160 phép gán, 14, 63, 89


Machine Translated by Google

212 Mục lục

câu lệnh break, 66 câu lệnh ghép, 41, 47 nối, 12, 14,
bubble sort, 201 lỗi, 6, 22, 73, 74, 95 danh sách, 91, 97, 101 điều

7, 13 kém nhất, 170 chức kiện, 41, 47, 65, 196 có điều kiện,

năng tích hợp bất 194 chuỗi, 41, 47 lồng nhau, 42, 47 thực
kỳ, 185, đối tượng 186 hiện có điều kiện , 41 biểu thức điều kiện,

byte, 141, 145 183, 191 câu lệnh điều kiện, 41, 47,

55, 184 kiểm tra tính nhất quán, 111,

158 thời gian không đổi, 208 người đóng

máy tính, 8, 15 góp, kiểu chuyển đổi vii, 17 sao chép sâu,

biểu đồ cuộc gọi, 109, 112 152 nông, 152 lát cắt, 74, 92 để tránh răng

Car Talk, 88, 113, 124 cưa , 99 sao chép mô-đun, 151 đối tượng sao
Loại thẻ, 172 thẻ, chép, 151 phương pháp đếm, 79

đang chơi, 171 mang,

cộng với, 68, 156, 158 bắt, 145 chuỗi có


điều kiện, 41, 47 ký tự, 71 tổng kiểm tra,

143, 146 hạng con, 176, 180 chức năng lựa

chọn, 126 chức năng vòng tròn, 31 định nghĩa

hình tròn, 55 lớp, 4, 147, 153

Thẻ, 172 Bộ đếm, bộ đếm

con, 176, 180 187, 75, 79, 104, 111 đếm và

Bộ bài, 174 lặp lại, 75


Tay, 176 Creative Commons, điểm chéo

Kangaroo, 170 con vi, 202, 209 ô chữ, tổng tích

bố mẹ, 176 con lũy 83, 100


Point, 148, 165

Hình chữ nhật, 149


Thời gian, đóng gói dữ liệu, 179, cấu trúc dữ
155 thuộc tính lớp, 172, liệu 181, cơ sở dữ liệu 122, 123,

định nghĩa lớp 180, sơ đồ 132, đối tượng cơ sở dữ liệu 141,

lớp 147, đối tượng lớp 177, 145, mô-đun ngày thời gian 141, mô-

181, 148, 153, 190 phương thức đun 160 dbm, 141 mã chết, 52, 60,

đóng, 138, 141, 143 phương thức 198 trình gỡ lỗi (pdb), 197 gỡ lỗi,

__cmp__, 173 6, 7 , 13, 36, 46, 59, 77, 87, 98,

Phỏng đoán Collatz, 65 bộ 111, 122, 133, 144, 152, 159, 168,
sưu tập, 187, 188, 190 dấu hai 178, 185, 193 theo phép chia đôi, 68 phản ứng cảm xúc,
chấm, 19, 194 nhận xét, 12, 14 6, 200 thử nghiệm, 25 con vịt cao su, 134

tính giao hoán, 12, 167 hàm so mê tín, 200 boong, 171

sánh, 52 thuật toán so sánh,

201 chuỗi so sánh, 77 bộ, 116,

174 sắp xếp so sánh, 205 thành

phần, 19 , 22, 26, 54, 174

Lớp bộ bài, bộ bài

174, bộ bài, 174


Machine Translated by Google

Mục lục 213

khai báo, 110, 112 đi bộ, 140


giảm, 64, 69 bản sao làm việc, 139
sâu, 152, 153 hàm dựa trên loại công
deepcopy, từ khóa 152 def, văn, 167 công văn,
19 giá trị mặc định, 129, dựa trên loại, 166 chia hết,
134, 165 tránh có thể thay đổi, 39 dấu phẩy động phân chia,
170 mặc định, 188 định 39 tầng, 39, 46, 47 divmod,

nghĩa thông tư, 55 lớp, 147 117, 158 docstring, 35,


hàm, 19 đệ quy , Toán tử 124 37, ký hiệu 148 dấu
del, 94 xóa, phần tử của chấm, 18, 26, 76, 148, 162,
danh sách, 94 dấu phân 172
cách, 95, 100 phát triển
được thiết kế, 160 xác Ngày chung đôi,
định, 126, kế hoạch phát triển 160 chữ cái đôi, 88
134, 36 đóng gói dữ liệu, 179, Doyle, Arthur Conan, 25 bản
181 được thiết kế, 158 đóng gói sao, 101, 113, 146, 187
và tổng quát, 35 tăng dần, 52,
193 nguyên mẫu và bản vá, 156, phần tử, 89, 100

158 lập trình đi bộ ngẫu nhiên, phần tử xóa, 94 từ khóa

134, 200 giảm, 85, 87 biểu đồ elif, 42

gọi sơ đồ, 112 lớp, 177, 181 đối Elkner, Jeff, v,

tượng, 148, 150, 152, 153, 155, 173 ngăn dấu chấm lửng vi,
xếp, 23, 97 trạng thái, 9 , 63, 78, 90, 19 từ khóa khác,

96, 108, 120, 148, 150, 152, 155, 173 41 địa chỉ email, 117

thuộc tính __dict__, 168 hàm dict, 103 từ đối tượng nhúng, 150, 153, 170 sao
điển, 103, 112, 120, 197 khởi tạo, 120 đảo chép, 152 gỡ lỗi cảm xúc, 6,

ngược, 107 tra cứu, 106 lặp với , 106 tra cứu 200 danh sách trống, 89 chuỗi trống,

ngược, 106 phép trừ, 129 duyệt qua, 120, 79, 95 đóng gói, 32, 36 , 54, 69,
168 phương pháp từ điển, mô-đun 204 dbm, 75, 177 mã hóa, 171, 180 mã hóa,

141 phép trừ từ điển, 186 khác biệt, 146 171 ký tự cuối dòng, 144 hàm liệt kê, 119
liệt kê đối tượng, 119 epsilon, 67 bình

đẳng và gán, 63 tương đương, 96, 152


tương đương, 100

lỗi

thời gian chạy, 13, 44,


46, 193 ngữ nghĩa, 13,

193, 198 hình dạng, 122 cú


pháp, 13, 193 kiểm tra
lỗi, 58 thông báo lỗi, 7, 13,
193 hàm eval, 69 đánh giá, 10
ngoại lệ, 13, 14, 193 , 196

Dijkstra, Edsger, hàm


87 dir, thư mục 197,

139, 145 AttributeError, 152, 197


Machine Translated by Google

214 Mục lục

FileNotFoundError, 140 vòng lặp for, 30, 44, 72, 91, 119,
IndexError, 72, 78, 90, 197 184 ngôn ngữ chính thức, toán tử
KeyError, 104, 197 định dạng 4, 7, chuỗi định dạng
LookupError, 107 138, 145, 197, chuỗi định dạng 138,
NameError, 22, 197 145, 138, 145 khung, 23, 26, 44,
OverflowError, 46 56, 109
RuntimeError, 45 Giấy phép Tài liệu Miễn phí, GNU, v, tần số vi,

StopIteration, 185 105 chữ cái, 123 từ, 125, 134 chức năng hiệu
SyntaxError, 19 quả, 24, 26 thất vọng, 200 chức năng, 3,

LoạiError, 72, 74, 108, 116, 118, 139, 164, 17, 19, 25, 161 abs, 52 ack, 61, 113 cung,
197 31 lựa chọn, 126 vòng tròn, 31 so sánh, 52
UnboundLocalError, 110 deepcopy, 152 dict, 103 dir, 197 liệt kê, 119
ValueError, 46, 117 đánh giá, 69 tồn tại, 139 giai thừa, 56, 183

ngoại lệ, bắt, 140 thực fibonacci, 57, 109 tìm thấy, 74 float, 17
thi, 11, 14 tồn tại hàm, fruitful, 24 getattr, 168 getcwd, 139
139 thử nghiệm gỡ lỗi, 25, hasattr, 153, 168 input, 45 int, 17

134 số mũ, 202 tăng trưởng theo cấp isinstance, 58, 153, 166 len, 26, 72, 104

số nhân, 203 biểu thức, 10, 14 lớn và list, 94 log, 18 math, 18 max, 117, 118

nhiều lông, 199 boolean, 40, 47 có min, 117, 118 open, 83 , 84, 137, 140,
điều kiện, 183 , 191 máy phát điện, 141 đa giác, 31 popen, 142 lập trình viên
185, 186, 191 phương pháp mở xác định, 22, 129 randint, 101, 126 ngẫu
rộng, 92 nhiên, 126 lý do, 24 đệ quy, 43

giai thừa, 183


chức năng giai thừa, 56, 58

nhà máy, 191 chức năng nhà


máy, 188, 189
Giá trị đặc biệt sai, 40
Định lý cuối cùng của Fermat,
48 hàm fibonacci, 57, tệp 109,
quyền 137, 140 đọc và ghi, 137

đối tượng tệp, 83, 87 tên


tệp, 139

FileNotFoundError, 140 mẫu

bộ lọc, 93, 100, 184 chức


năng tìm, cờ 74, 110, 112

chức năng float, 17 kiểu


float, 4 dấu phẩy động, 4,

7, 67, 183 dấu phẩy động, 39


dấu phân chia tầng, 39, 46, 47
luồng thực thi, 21, 26, 58,
59, 65, 178, 196 hoa, 37 thư
mục, 139
Machine Translated by Google

Mục lục 215

tải lại, 144, 195 có thể băm, 108, 112, 120

repr, 144 đảo HashMap, 207


ngược, 121 xáo bảng băm, 112, 206, 210
trộn, 175 đã sắp tiêu đề, 19, 25, 194
xếp, 99, 106, 121 Xin chào, Thế

sqrt, 18, 53 str, 18 giới, 3 hệ thập lục


sum, 118, 185 lượng phân, 148 ngôn ngữ cấp
giác, 18 tuple, 115 cao, 6 biểu đồ, 105 lựa
tuple làm giá trị trả chọn ngẫu nhiên, 126, 130

về, 117 loại, 153 tần số từ, 127


khoảng trống , 24 zip, 118 Holmes, Sherlock, 25 từ

đối số hàm, 21 lệnh gọi hàm, đồng âm, 113 cạnh huyền,
17, 26 thành phần hàm, 54 54
định nghĩa hàm, 19, 20, 25
khung hàm, 23, 26, 44, 56, 109 đối giống hệt nhau,

tượng hàm, 27 tham số hàm, 21 cú 100 nhận dạng,

pháp hàm, 162 kiểu chức năng, 20 96, 152 câu lệnh

bổ trợ, 157 thuần túy, 156 kiểu if, 41 bất biến, 74, 79, 97, 108, 115, 121
lập trình chức năng, 158, 160 triển khai, 105, 112, 132, 169 câu lệnh
nhập, 26, 144 trong toán tử, 205 trong
toán tử, 76, 85 , 90, 104 tăng dần, 64,
69, 157, 163 phát triển tăng dần, 60, 193
thụt lề, 19, 162, 194 chỉ mục, 71, 78, 79,

90, 103, 197 lặp lại với, 86, 91 phủ định,


72 lát cắt, 73, 91 bắt đầu từ 0, 71, 90

hàm gamma, 58 tập hợp,


118, 123, 190
GCD (ước số chung lớn nhất), tổng quát
hóa 61, 32, 36, 85, biểu thức trình tạo IndexError, 72, 78, 90, 197

159, 185, 186, đối tượng trình tạo 191, lập chỉ mục, 204 vòng lặp vô

185 thay đổi kích thước hình học, 209 hạn, 65, 69, 195, 196 đệ quy vô

phương thức get, 105 hàm getattr, 168 hạn, 44, 47, 58, 195, 196 kế thừa, 176,

hàm getcwd, 139 câu lệnh toàn cục, 110, 178, 180, 190 phương thức init, 164, 168,

112 biến toàn cục, 110, 112 cập nhật, 110 172, 174, 176 biến khởi tạo, 69 lần khởi
tạo (trước khi cập nhật), 64 hàm đầu vào,

45 phiên bản, 148, 153 làm đối số,

149 làm giá trị trả về, 150 thuộc tính


phiên bản, 148, 153, 172, 180 phiên bản
Giấy phép Tài liệu Miễn phí GNU, v, vi ước số khởi tạo, 153 phiên bản khởi tạo, 148 Hàm

chung lớn nhất (GCD), 61 lưới, 27 mẫu giám hộ, int, kiểu int 17, 4 số nguyên, 4, 7

59, 60, 78 chế độ tương tác, 11, 14, 24

Hạng tay, 176

treo, 195
Mối quan hệ HAS-A, 177, 180 hàm
băm, 153, 168 hàm băm, 108, 112,
207
Machine Translated by Google

216 Mục lục

giao diện, 33, 36, 169, 179 từ Nguyên tắc thay thế Liskov, 179 danh sách,

lồng vào nhau, 101 thông dịch, 89, 94, 100, 121, 184 làm đối số, 97 nối,

6 trình thông dịch, 2 bất biến, 91, 97, 101 bản sao, 92 phần tử, 90

159, 160 từ điển đảo ngược, 107 trống, 89 hàm, 94 chỉ mục, 90 thành

lời gọi, 76, 79 là toán tử, 95, viên, 90 phương thức, 92 lồng nhau,

152 89, 91 đối tượng, 174 bộ giá trị, 119

hoạt động, 91 lặp lại, 91 lát cắt, 91


duyệt, 91 hiểu danh sách, 184, 191

Mối quan hệ IS-A, 177, 180 hàm phương pháp danh sách, 204 nghĩa đen,

isinstance, 58, 153, 166 mục, 74, 79, 89, 5 biến cục bộ, 22, 26 hàm log, 18
103 từ điển, 112 mục gán, 74, 90, 116 cập logarit, 135 logarit tăng trưởng, toán

nhật mục, 91 mục phương pháp, 120 tử lôgic 203, tra cứu 40, tra cứu 112,

lần lặp, 64, 69 trình lặp , 119–


121, 123, từ điển, 106
204

tham gia, 204

phương thức tham gia, 95, 175

Lớp Kangaroo, 170 phím,

103, 112 cặp phím giá

trị, 103, 112, 120 đầu vào bàn phím,

45

KeyError, 104, 197

KeyError, 206 từ

khóa, 10, 14, 194 def, 19


elif, 42 khác, 41
Lỗi tra cứu, 107 vòng
đối số từ khóa, 33,
lặp, 31, 36, 65, 119 điều
36, 191
kiện, 196 cho, 30,

44, 72, 91 vô hạn,


Đường cong Koch, 49
65, 196 lồng nhau,

ngôn ngữ 174 truyền qua, 72

chính thức, trong khi, 64 biến

4 tự nhiên, vòng lặp, 184 vòng

4 an toàn, 13 lặp với từ điển, 106 với

Turing hoàn thành, 55 hệ chỉ số, 86, 91 với chuỗi,

số hàng đầu, 202 cụm từ hàng 75 lặp và đếm, 75 ngôn ngữ

đầu, 202, 209 bước nhảy của niềm cấp thấp, 6 ls (lệnh Unix),

tin, 57 hàm len, 26, 72, 104 142

tần số chữ cái, 123 lần xoay

chữ cái, 80, 113 tuyến tính,


209 tăng trưởng tuyến tính, 203

tìm kiếm tuyến tính, 205

mô hình máy, 201, 209 chính, 23,

43, 110, 144 có thể bảo trì, 169

LinearMap, 206 mẫu bản đồ, 93, 100 bản đồ tới,

Linux, 25 171

lipogram, 84
Machine Translated by Google

Mục lục 217

ánh xạ, 112, 131 phương pháp, danh sách, 92

Phân tích Markov, 130 mash- Meyers, Chris, hàm

up, 132 hàm toán học, 18 vi min, 117, 118

matplotlib, 135 hàm max, Dự án Moby, 83 mô

117, 118 hình, tinh thần, 199

sửa đổi, 157, 160 mô-

McCloskey, Robert, 73 md5, đun, 18, 26 bisect, 101

143 bộ sưu tập, 187,

Thuật toán MD5, 146 188, 190 bản sao, 151 ngày

md5sum, 146 tìm kiếm nhị giờ, 160 dbm, 141 hệ điều

phân thành viên, 101 tìm hành, 139 dưa chua, 137, 142

kiếm phân chia, 101 trang, 112 hồ sơ , 133 ngẫu

từ điển, 104 danh sách, nhiên, 101, 126, 175 tải lại,

90 bộ, 113 bản ghi nhớ, 144, 195 giá đỡ, 142 chuỗi,
109, 112 mô hình tinh 125 dạng cấu trúc, 122 thời
thần, 199 phép ẩn dụ, gian, 101 đối tượng mô-đun,
lệnh gọi phương thức, 163 siêu 18, 143 mô-đun, viết, 143 mô-

luận, 123 phương pháp, 36, 75, đun toán tử, 39, 47

161 , 169 __cmp__, 173 __str__, 165, 174


thêm, 165 nối thêm, 92, 97, 101, 174, 175

đóng, 138, 141, 143 đếm, 79 mở rộng, 92

nhận, 105 init, 164, 172, 174, 176


mục , 120 nối, 95, 175 mro, 179 pop,

94, 175 radd, 167 đọc, 143 dòng đọc,

83, 143 xóa, 94 thay thế, 125


setdefault, 113 sort, 92, 99, 176 Monty Python và Chén Thánh, 156
split, 95, 117 string, 79 dải, 84, MP3, 146

125 dịch, 125 cập nhật, 120 giá trị, phương thức mro, 179

104 khoảng trống, 92 thứ tự phân chuỗi nhiều dòng, 35, 194 đa
giải phương thức, 179 cú pháp phương dạng (trong sơ đồ lớp), 178, 181 đa tập, 187 khả
thức, 162 năng thay đổi, 74, 90, 92, 96, 111, 115, 121,

151 đối tượng có thể thay đổi, làm giá trị mặc định,

170

tên biến tích hợp sẵn, 144

namestuple, 190
NameError, 22, 197

NaN, 183

ngôn ngữ tự nhiên, 4, 7

chỉ số phủ định, 72 lồng


ghép có điều kiện, 42, 47 danh

sách lồng nhau, 89, 91, 100

dòng mới, 45, 175

Phương pháp Newton, 66

Không có giá trị đặc biệt, 24, 26, 52, 92, 94

Không có Loại kiểu, 24

không phải toán tử, 40


số, ngẫu nhiên, 126

Obama, Barack, 201


Machine Translated by Google

218 Mục lục

đối tượng, 74, 79, 95, 96, hoặc toán tử, 40


100 byte, 141, 145 thứ tự tăng trưởng, 202, 209
lớp, 147, 148, 153, 190 thứ tự hoạt động, 11, 14, 199 mô-
sao chép, 151 đun os, 139 khác (tên tham số), 164
Bộ đếm, cơ sở
dữ liệu 187, OverflowError, 46
141 sắc lệnh mặc quá tải, 169 lần ghi
định, 188 nhúng, 150, 153, đè, 129, 134, 165, 173, 176, 179
170 liệt kê, 119 tệp, 83,
87 hàm, 27 trình tạo, 185 mô- palindrome, 61, 80, 86, 88
đun, 143 có thể thay đổi, tham số, 21, 23, 26, 97 tập

151 têntuple, 190 ống, 145 hợp, 118 tùy chọn, 129,
in, 162 bộ, 186 zip, Sơ đồ 165 khác, 164 tự, 163
đối tượng 123, 148, 150, lớp cha, 176, 180 đối

152, 153, 155, 173 thiết kế số trong dấu ngoặc

hướng đối tượng, 169 ngôn trong, 17 trống, 19, 76 tham

ngữ hướng đối tượng, 169 lập số trong , 21, 22 lớp cha
trình hướng đối tượng, 147, trong, 176 bộ giá trị,

161, 169, 176 odometer, 88 115 phân tích cú pháp,


5, 7 câu lệnh truyền,
41 đường dẫn, 139, 145
tuyệt đối, 139 tương
đối, 139 bộ lọc mẫu, 93, 100,
184 người giám hộ, 59, 60, 78
bản đồ, 93, 100 giảm, 93, 100

Olin College, hàm tìm kiếm, 75, 79, 85,

mở v, 83, 84, 137, 140, 141 toán hạng, 107, 186 hoán đổi, 116

14 toán tử, 7 và, 40 số học, 3 bitwise, pdb (trình gỡ lỗi Python), 197
3 boolean, 76 ngoặc nhọn, 71, 90, 116
del, 94 định dạng, 138, 145, 197
in, 76, 85, 90, 104 là, 95, 152
logic, 40 mô-đun, 39, 47 không, 40
hoặc, 40 quá tải, 169 quan hệ, 40,
173 lát, 73, 79, 91, 98, 116 chuỗi,
12 cập nhật, quá tải 93 toán tử,
166, 173 đối số tùy chọn, 76, 79, PEMDAS, 11
95, 107, 184 tham số tùy chọn, quyền, tệp, 140 bền bỉ,
129, 165 137, 145 pi, 18, 70 mô-
đun dưa, 137, 142 tẩy,
142 bánh, 37 ống, đối tượng
142 ống, 145 văn bản thuần
túy, 83, 125 phát triển
theo kế hoạch, 158 thơ, 5

Lớp điểm, 148, 165

điểm, toán học, 147 poker,


171, 181
Machine Translated by Google

Mục lục 219

hàm đa giác, 31 đa hình, lập trình đi bộ ngẫu nhiên, 134, 200 xếp hạng,

168, 169 phương thức pop, 94, 171 phương pháp đọc, 143 phương pháp dòng đọc,

175 hàm popen, 142 tính di 83, 143 chuyển nhượng lại, 63, 68, 90, 110

động, 6 đối số vị trí, 164,

169, 190 điều kiện hậu, 36,

59, 179 mô-đun pprint, 112 ưu tiên, 199 Lớp hình chữ nhật, 149

điều kiện tiên quyết, 36, 37, Tiền tố 59, đệ quy, 43, 47, 55, 57

179, 131 bản in đẹp, 112 chức năng in, 3 trường hợp cơ sở, 44

câu lệnh in, 3, 7, 165, 197 giải quyết vô hạn, 44, 58, 196

vấn đề, 1, 6 mô-đun hồ sơ, 133 chương định nghĩa đệ quy, 56, 124 cây đỏ-

trình, 1, 6 chương trình kiểm tra, 87 chức đen, 206 mẫu giảm, 93, 100 từ có

năng do lập trình viên xác định, 22, 129 thể rút gọn, 113, 124 giảm thành

kiểu do lập trình viên xác định, 147, 153, một vấn đề đã được giải quyết

155, 162, 165, 173 trước đó, giảm 85 thành vấn đề đã giải quyết trước đó,
87 dự phòng, 5 cấu trúc lại, 34–36, 180

tham chiếu, 96, 97, 100 răng cưa, 96 băm lại, 208 toán
tử quan hệ, 40, 173 đường dẫn tương đối,

139, 145 tải lại hàm, 144, 195 phương thức loại bỏ, 94

lặp lại, 30 danh sách, 91 phương thức thay thế, 125


hàm repr, 144 biểu diễn, 147, 149, 171 câu lệnh trả

về, 44, 51, 199 giá trị trả về, 17, 26, 51, 150

Project Gutenberg, 125 bộ giá trị, 117 đảo ngược tra cứu, 112 đảo ngược tra

prompt, 2, 6, 45 văn xuôi, cứu, từ điển, 106 cặp từ đảo ngược, 101 chức năng đảo

5 nguyên mẫu và bản vá, 156, ngược, 121 xoay

158, 160 giả ngẫu nhiên, 126, 134 hàm

thuần túy, 156, 160

Puzzler, 88, 113, 124

Định lý Pitago, 52

Python

đang chạy, 2

Python 2, 2, 3, 33, 40, 45

Python trong trình duyệt, 2

PythonAnywhere, 2

bậc hai, tăng

trưởng bậc hai 209, dấu

ngoặc kép 203, 3, 4, 35, 74, 194

phương thức radd, 167 chữ cái, vòng


radian, sắp xếp cơ số quay 113, lá thư, gỡ
18, cơn thịnh nộ 201, lỗi con vịt cao su 80, tốc độ chạy
câu lệnh tăng 200, 134, 8, 15, 160 khi chạy Python, 2
107, 112, 159 lỗi thời gian chạy, 13, 44, 46,
Ramanujan, Srinivasa, 70 hàm 193, 196
randint, 101, 126 hàm ngẫu RuntimeError, 45, 58
nhiên, 126 mô-đun ngẫu nhiên,

101, 126, 175 số ngẫu nhiên, 126 văn ngôn ngữ an toàn, 13
bản ngẫu nhiên, 131 kiểm tra sự tỉnh táo,

111 giàn giáo, 53, 60, 112


Machine Translated by Google

220 Mục lục

phân tán, 118, 123, 191 dấu ngoặc vuông, sắp xếp
Schmidt, Eric, 201 ổn định 103, sơ đồ ngăn

Scrabble, 123 xếp 205, biểu đồ trạng thái 23, 26, 37, 44,

script, 11, 14 56, 60, 97, 9, 14, 63, 78, 90, 96, 108, 120,

script mode, 11, 14, 24 Câu lệnh 148, 150, 152,

search, 107, 205, 209 155, 173, 10, 14 khẳng định, 159, 160

search pattern, 75, 79, 85, 186 phép gán, 9, 63 ngắt, 66 kết

search, binary, 101 search, hợp, 41 có điều kiện, 41, 47,


bisection, 101 self (tên tham số), 55, 184 cho, 30, 72, 91 toàn

163 lỗi ngữ nghĩa, 13, 14, 193, 198 cục, 110, 112 if, 41 import, 26,
ngữ nghĩa, 14, 162 chuỗi, 4, 71, 79, 144 pass, 41 print, 3, 7, 165,

89, 94, 115, 121 bộ, 130, 186 đảo 197 raise, 107, 112, 159 return,

chữ, 123, 145 bộ thành viên, 113 bộ trừ, 44, 51, 199 try, 140, 153 while,
186 bộ mặc định, 189 phương pháp 64 step size, 79

setdefault, 113 giới tính, 158 bản

sao cạn, 152, 153 hình dạng, 123 lỗi hình


dạng, 122 shell, 142, 145 mô-đun giá đỡ,

142 chức năng xáo trộn, 175 chức năng sin,

18 singleton, 108, 112, 115 lát cắt, 79

bản sao, 74, 92 danh sách, 91 chuỗi, 73

bộ, 116 cập nhật, 92 toán tử lát cắt, 73,

79, 91, 98, 116 phương pháp sắp xếp, 92,

99, 176 hàm được sắp xếp, 99, 106 hàm được StopIteration, hàm
sắp xếp, 121 sắp xếp, 204, 205 trường hợp 185 str, 18 __str__

đặc biệt, 87, 157 giá trị đặc biệt phương thức, 165, 174 chuỗi,

4, 7, 94, 121 bộ tích lũy, 175


so sánh, 77 trống, 95

không thay đổi, 74 phương

thức, 75 đa dòng, 35,


194 hoạt động, 12 lát

cắt, 73 bộ ba được trích

dẫn, nối 35 chuỗi, 204

phương thức chuỗi, 79


phương thức chuỗi, 204

mô-đun chuỗi, 125 biểu

diễn chuỗi, 144, kiểu chuỗi

165, phương pháp 4 dải, 84,

125 mô-đun dạng cấu trúc, 122

cấu trúc, 5 chủ đề, 163, 169

tập hợp con, 187 từ điển phép trừ,

129 với mượn, 68 phép trừ với mượn,

159
Sai, 40

Không có, 24, 26, 52, 92, 94

True, 40

xoắn ốc, 38

phương pháp tách, 95, 117

sqrt, 53 sqrt hàm, 18 căn

bậc hai, 66
Machine Translated by Google

Mục lục 221

hậu tố, 131 Turing Thesis, 55


phù hợp, 171 Turing, Alan, mô-
tổng, 185 đun 55 con rùa, máy

tổng hàm, 118 gỡ lỗi đánh chữ 48 con rùa, 37

mê tín, 200 mẫu hoán đổi, 116 cú kiểu, 4, 7 bool, 40

pháp, 5, 7, 13, 162, 194 lỗi cú dict, 103 tệp, 137

pháp, 13, 14, 193 float, 4 hàm, 20

int, 4 list, 89

SyntaxError, 19

biến tạm thời, 51, 60, 199 trường hợp


thử nghiệm, tối thiểu, 198 thử nghiệm

và không có lỗi, 87 phát triển gia NoneType, 24 do


tăng, 52 là khó, 87 biết câu lập trình viên xác định, 147, 153, 155, 162,
trả lời, 53 bước nhảy vọt của niềm 165, 173 bộ, 130 str, 4 bộ, kiểm tra kiểu
tin, 57 trường hợp thử nghiệm tối 115, chuyển đổi kiểu 58, hàm 17 kiểu, 153 công

thiểu, 198 văn dựa trên kiểu, 166, 167, 169

chữ

trơn, 83, 125


ngẫu nhiên, 131

tệp văn bản, 145 TypeError, 72, 74, 108, 116, 118, 139, 164, 197 máy
Lớp thời gian, mô- đánh chữ, con rùa, 37 lỗi đánh máy, 134
đun 155 thời gian,

101 mã thông báo, 5,

7 theo dõi ngược, 24, 26, 44, 46, 107, UnboundLocalError, 110 ký tự

196 phương thức dịch, 125 truyền qua, gạch dưới, 10 tính duy nhất,

72, 75, 77, 79, 85, 93, 100, 105, 106, 119, 127 từ 101
điển, 168 danh sách, 91 duyệt Lệnh Unix ls, 142

cập nhật, 64,

67, 69 cơ sở dữ liệu,
141 biến toàn

từ điển, 120 tam cục, 110 biểu đồ, 127

giác, 48 hàm lượng mục, 91 lát cắt, 92

giác, 18 chuỗi ba dấu ngoặc kép, phương pháp cập nhật,

35 120 toán tử cập nhật,

Giá trị đặc biệt thực, câu 93 sử dụng trước def, 20

lệnh 40 try, 140, 153

tuple, 115, 117, 121, 122

làm khóa trong từ điển, 120, 132

phép gán, 116 so sánh, 116, 174 giá trị, 4, 7, 95, 96, 112

trong ngoặc, 120 singleton, 115 mặc định, 129 bộ, 117

lát cắt, 116 phép gán bộ, 117 ,

119, 122 hàm tuple, 115 phương thức ValueError, 46, 117

tuple, 204 phương thức giá trị,

104 biến, 9, 14 toàn

cục, 110 cục bộ,


22 tạm thời, 51,

Turing ngôn ngữ hoàn chỉnh, 55 60, 199


Machine Translated by Google

222 Mục lục

đang cập nhật,

64 tuple đối số có độ dài thay đổi, 118 veneer,


175, 180 hàm void, 24, 26 phương thức void, 92

vorpal, 55

đi bộ, thư mục, 140 vòng

lặp trong khi, 64 khoảng

trắng, 46, 84, 144, 194 số từ, tần


suất 143 từ, 125, 134 từ, có thể

giảm được, 113, 124 thư mục làm


việc, 139 lỗi nặng nhất, 170 trường

hợp xấu nhất, 202, 209

0, chỉ mục bắt đầu từ, 71 không,

chỉ mục bắt đầu từ, 90 hàm zip,

118 sử dụng với dict, 120 đối


tượng zip, 123

Định luật Zipf, 134

You might also like