28 0 5MB
Lời khen tặng dành cho
EM HỌC PYTHON “Một cách hết sức tuyệt vời để kết nối cả gia đình.” —Patrice Gans, EducationWee k “Không hề thua kém gì sách hướng dẫn lập trình dành cho người lớn cả.” —Matthew Humphries, Geek.com “Jason Briggs đã thực sự thành công khi mang thế giới lập trình đến cho trẻ e m m à không hề phải giảm tải bớt nội dung. Các bài học được xây dựng rất tốt, đến mức khiến cho độc giả luôn có cảm giác đạt được thành công khi kết thúc mỗi chương.” —Marziah Karch, blog GeekMomcủa Wired.com “Cuốn sách rất lôi cuốn. Không có gì hơn là viết ra vài dòng code, hiện ra vài thứ gì đó lên màn hình và hiểu được mọi chuyện đã xảy ra như thế nào và tại sao lại như thế.” —Copil Yáñez, tạp chí FullCircle “Một cẩm nang hướng dẫn lập trình dành cho bất cứ ai yêu thích lập trình, không phân biệt độ tuổi. Một tài liệu tham khảo tuyệt vời dành cho cả bố mẹ lẫn giáo viên.” —Roy Wood, GeekDad “Tôi có hỏi cô con gái tám tuổi của mình nghĩ sao về “Em Học Python”. Cô bé nhất trí cho cuốn sách năm sao… Tôi rất vui khi thấy con viết ra những chương trình Python thật sự nhờ đọc từng trang sách.” —Richard Bejtlich, Giám đốc An ninh ở Mandiant “Một cuốn sách tuyệt vời cho bất cứ ai muốn theo học lập trình mà không bị choáng ngợp.” —Sandra Henry-Stocker, ITworld
EM HỌC PYTHON SÁCH HƯỚNG DẪN VUI HỌC LẬP TRÌNH
J A S O N R. B R IG G S Phan Chương dịch (v1.0) [email protected]
M Ụ C LỤ C C H ÍN H Giới thiệu Tác giả, Hoạ sĩ minh hoạ và nhóm Tư vấn kỹ thuật . . . . . . . . . . . . . . . . . . . . . . . . . . . xv Lời cảm ơn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii Lời giới thiệu
..................................................................
. . . . . . . xix
P H Ầ N I: L Ậ P T R ÌN H C Ă N B Ả N Chương 1: Không phải con gì trơn trơn cũng là rắn đâu nhé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Chương 2: Tính toán và biến trong lập trình . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Chương 3: Chuỗi, mảng, tuple và map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Chương 4: Vẽ vời với rùa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 Chương 5: Đặt câu hỏi với if và else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 Chương 6: Cùng đi lòng vòng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Chương 7: Tái sử dụng code với hàm và module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 Chương 8: Cách sử dụng lớp và đối tượng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 Chương 9: Các hàm Python được viết sẵn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 Chương 10: Các module hay dùng trong Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 Chương 11: Gặp lại đồ hoạ con rùa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 Chương 12: Dùng tkinter để có đồ hoạ đẹp hơn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
P H Ầ N II: G A M E B O U N C E ! Chương 13: Bắt đầu viết game Bounce! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203 Chương 14: Hoàn thiện game Bounce! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
P H Ầ N III: G A M E M R . S T IC K M A N R A C E S F O R T H E E X IT Chương 15: Tạo các hình ảnh cho game Mr. Stick Man . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 Chương 16: Bắt đầu lập trình game Mr. Stick Man . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 Chương 17: Dựng hình nhân vật Mr. Stick Man . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 Chương 18: Hoàn thiện game Mr. Stick Man . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259 Lời cuối: Làm gì tiếp theo đây . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 Phụ lục: Tất cả từ khoá trong Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . . . 293 Chú giải . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307 Chỉ mục . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
M Ụ C LỤ C CH I TIẾT GIỚI THIỆU TÁC GIẢ, HOẠ SĨ MINH HOẠ VÀ NHÓM TƯ VẤN KỸ THUẬT LỜI CẢM ƠN LỜI GIỚI THIỆU
xv x v ii x ix
Sao lại là Python? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xx Học lập trình như thế nào? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xx Quyển sách này dành cho ai? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxi Trong sách có những nội dung gì? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxii Website của sách . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiii Vui là chính! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiii
P H Ầ N I: L Ậ P T R ÌN H C Ă N B Ả N 1 KHÔNG PHẢI CON GÌ TRƠN TRƠN CŨNG LÀ RẮN ĐÂU NHÉ
3
Tản mạn về ngôn ngữ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Cài đặt Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Cài đặt Python trên máy Windows 7 trở lên . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Cài đặt Python trên máy Mac OS X . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Cài đặt Python trên máy Ubuntu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . 7 Làm gì sau khi cài Python? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Lưu lại chương trình Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Tóm tắt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2 TÍNH TOÁN VÀ BIẾN TRONG LẬP TRÌNH
13
Làm toán với Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Các toán tử trong Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Thứ tự tính toán . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Biến là mảnh giấy ghi chú . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Sử dụng biến . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Tóm tắt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
M ục lục chi tiết
7
3 Chuỗi, mảng, tuple và map
23
Chuỗi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Khởi tạo chuỗi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Các vấn đề thường gặp với chuỗi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Ghép giá trị vào trong chuỗi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 Dùng phép nhân trong chuỗi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Mảng còn khủng hơn chuỗi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Thêm phần tử vào mảng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Xoá phần tử trong mảng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 Các phép toán với mảng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 Tuple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Python Maps không chỉ đường cho em đâu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Tóm tắt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 Bài tập lập trình . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 #1: Sở thích . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 #2: Đếm người . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 #3: Chào . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 4 Vẽ vời với rùa
41
Sử dụng module turtle trong Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 Tạo bảng vẽ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Di chuyển rùa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 Tóm Tắt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Bài tập lập trình . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 #1: Hình chữ nhật . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 #2: Hình tam giác . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 #3: Hộp mà không có góc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 5 Đặt câu hỏi với if và else
51
Lệnh if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 Khối lệnh là một khối các lệnh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 Điều kiện là để so sánh mọi thứ với nhau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Lệnh if-then-else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 Lệnh if và elif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 Kết hợp các điều kiện . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 Biến không có giá trị – None . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 Sự khác biệt giữa chuỗi và số . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
8
M ục lục chi tiết
Tóm tắt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Bài tập lập trình . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 #1: Mình có giàu không ta? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 #2: Bánhhhhh! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 #3: Vừa đúng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 #4: Chiến đấu với ninja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 6 Cùng đi lòng vòng
65
Vòng lặp for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 Và nhân tiện đang nói về vòng lặp... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 Tóm tắt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 Bài tập lập trình . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 #1: Chào gì chào lắm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 #2: Số chẵn số lẻ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 #3: Món ăn yêu thích . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 #4: Cân nặng trên mặt trăng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 7 Tái sử dụng code với hàm và module
79
Dùng hàm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 Các thành phần trong một hàm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 Biến và phạm vi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 Dùng module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 Tóm tắt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 Bài tập lập trình . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 #1: Hàm tính cân nặng trên mặt trăng cấp I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 #2: Hàm tính cân nặng trên mặt trăng cấp II . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 #3: Chương trình tính cân nặng trên Mặt trăng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 8 Cách sử dụng lớp và đối tượng
89
Chia mọi thứ ra thành lớp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 Lớp con và lớp cha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 Tạo đối tượng từ lớp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 Viết hàm cho lớp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 Dùng hàm để mô tả các đặc trưng của lớp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 Nhưng mà ta cần lớp với đối tượng để làm gì? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Đối tượng và lớp trong đồ hoạ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Vài thứ hay ho khác của lớp và đối tượng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 Thừa kế hàm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 M ục lục chi tiết
9
Hàm này gọi hàm kia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 Khởi tạo đối tượng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 Tóm tắt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 Bài tập lập trình . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 #1: Hươu chân trước đá chân sau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 #2: Rùa hình cái xiên . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 9 Các hàm Python được viết sẵn
107
Cách dùng các hàm được viết sẵn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 Hàm abs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 Hàm bool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 Hàm dir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 Hàm eval . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 Hàm exec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Hàm float . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Hàm int . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 Hàm len . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 Hàm max và hàm min. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 Hàm range . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 Hàm sum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 Làm việc với file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 Tạo file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 Mở file trong Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 Ghi ra file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 Tóm tắt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 Bài tập lập trình . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 #1: Đoạn code bí ẩn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 #2: Thông điệp bí mật . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 #3: Sao chép file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 10 Các module hay dùng trong Python
127
Tạo các bản sao với module copy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 Ghi nhớ các từ khoá với module keywords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 Lấy số ngẫu nhiên từ module random . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 Dùng randint để chọn một số ngẫu nhiên . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 Dùng choice để chọn phần tử ngẫu nhiên trong mảng . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 Dùng shuffle để trộn lẫn một mảng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 Điều khiển shell với module sys . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 Dùng exit để thoát ra khỏi Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
10
M ục lục chi tiết
Đọc dữ liệu với đối tượng stdin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 Ghi dữ liệu với đối tượng stdout. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 Mình đang dùng Python bản nào vậy ta? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 Làm việc với thời gian bằng module time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 Chuyển đổi ngày tháng với asctime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 Lấy ngày tháng và thời gian với localtime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 Làm thời gian chậm lại với sleep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 Lưu lại dữ liệu bằng module pickle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 Tóm tắt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 Bài tập lập trình . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 #1: Nhân bản xe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 #2: Đóng gói danh sách yêu thích . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 11 Gặp lại đồ hoạ con rùa
145
Bắt đầu với hình vuông . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 Vẽ hình ngôi sao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146 Vẽ hình xe ô tô . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 Tô màu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 Viết hàm để vẽ hình tròn có màu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 Màu đen và màu trắng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 Hàm để vẽ hình vuông . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 Vẽ hình vuông có màu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 Vẽ ngôi sao có màu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 Tóm tắt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 Bài tập lập trình . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 #1: Vẽ hình bát giác . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 #2: Vẽ hình bát giác có màu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 #3: Lại một hàm vẽ ngôi sao nữa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 12 Dùng tkinter để có đồ hoạ đẹp hơn
167
Tạo nút nhấn nhấn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 Tham số chỉ định . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 Tạo bảng vẽ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 Vẽ đường thẳng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 Vẽ hình hộp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174 Vẽ thật nhiều hình hộp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 Và thêm màu sắc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180 Vẽ hình cung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 Vẽ hình đa giác . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
M ục lục chi tiết
11
Viết chữ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 Chèn ảnh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 Tạo các chuyển động . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 Tạo các tương tác . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196 Các trường hợp khác cần dùng số hiệu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198 Tóm tắt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200 Bài tập lập trình . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200 #1: Phủ tam giác kín màn hình . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200 #2: Tam giác động đậy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200 #3: Hình ảnh động đậy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
P H Ầ N II: G A M E B O U N C E ! 13 B Ắ T Đ Ầ U V IẾ T G A M E B O U N C E !
205
Đập bóng bay lung tung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205 Tạo bảng vẽ cho game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 Tạo lớp Ball . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208 Thêm vài hành động . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210 Làm bóng di chuyển . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211 Làm bóng bật tường . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212 Điều hướng bóng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214 Tóm tắt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216 14 H O À N T H IỆ N G A M E B O U N C E !
217
Thêm thanh đỡ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 Di chuyển thanh đỡ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219 Phát hiện va chạm giữa bóng và thanh đỡ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 Thêm yếu tố thắng thua . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224 Tóm tắt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220 Bài tập lập trình . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 #1: Hoãn thời gian khởi động game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 #2: “Game over” đúng nghĩa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 #3: Tăng tốc độ của bóng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230 #4: Tính điểm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
12
M ục lục chi tiết
GIỚI THIỆU TÁ C GIẢ Jason R. Briggs học lập trình từ năm tám tuổi, anh bắt đầu với ngôn ngữ BASIC trên chiếc máy tính TRS-80 cũ kỹ của Radio Shack. Sau đó, anh trở thành lập trình viên và kỹ sư thiết kế hệ thống, đồng thời làm Cộng tác viên thường xuyên của tờ JavaDeveloper’s Journal.Các bài viết của anh còn được đăng trên các tạp chí JavaWorld, ONJava, và ON La m p . E m học Pythonlà cuốn sách đầu tay của anh. Các e m có thể liên lạc với tác giả Jason qua trang we b http://jasonrbriggs.com/ hoặc gửi email cho anh ở địa chỉ [email protected] .
G I Ớ I T H I Ệ U H O Ạ SĨ M I N H H O Ạ Miran Lipovača là tác giả cuốn LearnYou aHaskellforGreatGood!. Sở thích của anh là đấm bốc, chơi ghita bass, và tất nhiên là cả vẽ vời nữa. Anh rất mê mấy bộ xương biết nhảy múa, thích số 71, và mỗi khi bước qua mấy cái cửa tự động anh thường giả vờ là mình đang điều khiển bằng siêu năng lực.
GIỚI THIỆU N H Ó M T Ư V Ấ N K Ỹ T HUẬT Josh Pollock, 15 tuổi, vừa tốt nghiệp cấp hai ở trường The Nueva và chân ướt chân ráo vào cấp ba ở trường Lick-Wilmerding, thành phố San Francisco. Lần đầu tiên cậu học lập trình là với Scratch năm 9 tuổi, sau đó là học ngôn ngữ TI-BASIC năm lớp 6, chuyển sang Java và Python năm lớp 7 rồi UnityScript năm lớp 8. Ngoài lập trình, cậu thích chơi kèn trumpet, viết game, và thích nói về các chủ đề khoa học kỹ thuật.
Maria Fernandez là thạc sĩ ngôn ngữ học ứng dụng, đa m m ê m á y tính và công nghệ từ hơn 20 năm nay. Cô dạy tiếng Anh cho hội phụ nữ tị nạn trong khuôn khổ dự án Global Village ở bang Georgia, hiện đang sống ở khu vực phía bắc bang California và làm việc cho tổ chức giáo dục phi lợi nhuận ETS (Educational Testing Service).
L Ờ I N G Ư Ờ I D ỊC H Tôi ấn tượng với cuốn sách này ngay từ lần đầu đọc, từ cách hành văn, từng câu chữ, cho đến những câu đùa, và tất nhiên là cả lượng kiến thức lập trình không nhiều nhưng cũng không hề ít được tác giả đề cập đến. N gôn ngữ gần gũi và tếu táo của tác giả làm tôi nảy ra ham m uốn nhất thời không kìm được, đó là dịch quyển sách này ra cho các độc giả nhỏ tuổi ở V iệt N am . Tôi luôn thấy thương các em với những lịch học dày đặc và những áp lực vô hình trên vai. Tôi hy vọng, thật sự rất hy vọng cuốn sách này sẽ m ang đến cho các em niềm vui và, khơi dậy sự tò m ò trong các em , để rồi các em có thể tự m ở ra cho m ình cánh cửa đến sự tự do và niềm vui được tự m ình khám phá. Python là m ột ngôn ngữ lập trình hết sức phổ biến trong những năm gần đây. K hông những thế, nó còn là m ột trong những công cụ hiện đại nhất để bước chân vào kỷ nguyên của dữ liệu, xác suất và trí tuệ nhân tạo. N ghe thì phức tạp nhưng nó lại rất dễ tiếp cận, dễ nắm bắt, xứng đáng là ngôn ngữ lập trình dành cho m ọi người. H ọc lập trình, nói cho cùng, không phải là để thành lập trình viên. Lập trình về cơ bản cũng chỉ là m ột kỹ năng sống, m ột kỹ năng quan trọng! C uối cùng, bản dịch chắc chắn còn rất nhiều thiếu sót, bạn đọc vui lòng góp ý tại đây h t t p s : / / p a d l e t . c o m / p l t c h u o n g / g o p _ y _ e b o o k _ e m _ h o c _ p y t h o n . Seattle, tháng 1 năm 2021 Phan C hương
LỜI C Ả M Ơ N Thử hình dung lúc bước lên sân khấu nhận giải thưởng, e m phát hiện ra danh sách những người mình định nói lời cả m ơn bỗng nhiên không cánh m à bay: E m đảm bảo sẽ quên ai đó, để rồi chưa gì nhạc đã xập xình nổi lên mời em về chỗ. Thế cho nên, đây là một danh sách (chắc chắn) không hoàn chỉnh những người tôi mang mối nợ ân tình sâu sắc, những người đã làm cho cuốn sách được như ngày hôm nay. Xin gửi lời cảm ơn nhóm No Starch, đặc biệt là Bill Pollock, trong lúc biên tập đã bơm cho tôi không biết bao nhiêu liều “trẻ-con-sẽ-nghĩ-gì”. M ột khi đã làm lập trình viên trong một thời gian dài, ta thường quên phéng mất là với những ai mới học những thứ này khó hiểu như thế nào, và những gì bị bỏ sót, những gì quá phức tạp mà Bill chỉ ra thực sự là vô giá. Xin cảm ơn Serena Yang, vị giám đốc sản xuất phi thường; rất hy vọng chị không phải bị rụng quá nhiều tóc chỉ để đảm bảo hơn 300 trang code được tô màu đúng chuẩn. Xin chân thành cảm ơn những bức hình minh họa xuất sắc của Miran Lipovača. Còn hơn cả xuất sắc nữa ấy chứ. Thật đấy! Nếu để tôi minh họa, chắc phải may mắn lắm ta mới có một bức hình nhòe nhoẹt không ra ngô ra khoai gì. Là gấu à ...? Hay là chó ...? Khoan, từ từ, chỗ này phải là cây chứ? Xin cảm ơn đội tư vấn kỹ thuật. Tôi rất áy náy vì vài ý kiến của các bạn cuối cùng cũng không được sử dụng. Các bạn có lý của mình, nên nếu có bất cứ sai sót gì thì tôi cũng chỉ có thể đổ lỗi cho sự khiếm khuyết của tính cách cá nhân tôi. Đặc biệt cảm ơn Josh đã có những đóng góp đáng kể, nhất là những lần bắt lỗi vô cùng chính xác. Và xin lỗi Maria vì thỉnh thoảng phải làm việc với những đoạn code không được đẹp mắt lắm. Cảm ơn vợ và con gái, đã thông cảm với ông chồng và ông bố suốt ngày cắm mũi vào m á y tính. Cảm ơn M ẹ đã động viên con không biết bao nhiêu lần trong từng ấy năm. Cuối cùng, cảm ơn Bố đã mua cho con cái máy tính từ những năm 1970 và cho phép con dùng nó thoải mái. Không có Bố, những chuyện thế này sẽ không bao giờ xảy ra.
Học lập trình để làm gì? Lập trình thúc đẩy sự sáng tạo, suy luận và giải quyết vấn đề. Là một lập trình viên, em có cơ hội để nhào nặn ra cái gì đó từ hai bàn tay trắng, sử dụng tư duy logic để biến những dòng lệnh rời rạc thành một chương trình m á y tính chạy được, làm việc được, và vận dụng tư duy sửa chữa vấn đề nếu mọi thứ chạy không đúng như kỳ vọng. Lập trình thực ra rất vui, có lúc hơi khó (và thỉnh thoảng hơi đâu-cái-điền 1 ), bù lại những gì em học được từ đó sẽ rất có ích ở trường và đi làm... kể cả nếu công việc của em sau này không liên quan gì đến má y tính cả. Và cuối cùng, chiều chiều trời tự nhiên xầm xì u ám mà ngồi lập trình thì còn hơn cả tuyệt vời!
1
Điên-cái-đầu Lời giới thiệu
19
SAO LẠI LÀ PYTHON? Python là ngôn ngữ rất dễ học và có những đặc điểm rất phù hợp với ai mới học lập trình. So với những ngôn ngữ khác thì Python khá là dễ đọc, và còn có một giao diện tương tác để viết và chạy code trên đó luôn rất tiện. Bên cạnh cấu trúc ngôn ngữ đơn giản, Python còn có vài thứ đặc biệt nữa giúp đẩy nhanh quá trình học tập, như là ghép những hình ảnh hoạt hình đơn giản riêng lẻ thành một game hoàn chỉnh. M ột trong số đó là một module có tên turtle , lấy c ả m hứng từ những hình ảnh của dự án Turtle (được sử dụng trong ngôn ngữ lập trình Logo tít từ những năm 1960 cơ) và module này được thiết kế riêng cho mục đích dạy và học. Một ví dụ khác là tkinter , module dùng để tương tác với bộ công cụ Tk, giúp ta có thể viết ra các chương trình có đồ họa và hình ảnh hoạt hình phức tạp hơn.
HỌC LẬP TRÌNH NHƯ THẾ NÀO? Như tất cả những lần-đầu-tiên học gì đó, ta sẽ bắt đầu với những thứ hết sức đơn giản, cho nên hãy đọc từ những chương đầu tiên và phải thật cố gắng đừng nôn nóng bỏ qua để nhảy đến các chương sau. Không ai có thể chơi nhạc hay như nghệ sĩ ngay từ lần đầu tiên cầm nhạc cụ cả. Mấ y chú học viên phi công cũng phải nắm được các thao tác điều khiển cơ bản trước khi lái máy bay thật. Còn các vận động viên thể hình (thông thường) cũng sẽ không thực hiện những cú nhảy lộn ngược ngay trong những lần thử đầu tiên đâu. Nếu e m nhảy cóc quá nhanh, không những kiến thức cơ bản sẽ không nằ m lại trong đầu, m à e m sẽ thấy những kiến thức về sau tự nhiên khó hơn bình thường. Thử viết lại từng ví dụ một trong lúc đọc, em sẽ thấy và hiểu nó hơn. Hầu hết ở cuối mỗi chương đều có vài bài tập lập trình để e m thử và nâng cao kỹ năng. Nhớ là càng nắm chắc những kiến thức cơ bản thì về sau em càng dễ tiếp thu những bài học khó hơn.
20
Lời giới thiệu
Khi gặp cái gì đó quá khó hoặc quá đánh đố, tôi hay thử mấy cách sau: 1.
Tách vấn đề ra thành từng phần nhỏ. Cố gắng hiểu từng đoạn code có tác dụng gì, hoặc là chỉ để tâm vào một phần nhỏ của cả vấn đề lớn (tức là tập trung vào một đoạn code ngắn thôi, thay vì cố hiểu tất cả cùng một lúc).
2.
Nếu vẫn không ăn thua, thì tạm thời kệ nó. Ngủ một giấc rồi quay lại sau. Cách này có thể giải quyết được rất nhiều vấn đề, và đặc biệt có ích với lập trình viên.
Q U Y Ể N S Á C H N À Y D À N H C H O A I? Quyển sách này dành cho bất cứ ai có hứng thú với lập trình má y tính, dù là trẻ e m hay người lớn lần đầu học lập trình. Nếu muốn học cách để viết ra phần m ề m của riêng mình thay vì cứ phải dùng phần m ề m của người khác, thì cuốn E m H ọc Pythonnày là điểm khởi đầu khá phù hợp. Trong các chương tiếp theo, ta sẽ học cách cài đặt Python, m ở Python shell và làm các phép tính đơn giản, in chữ ra mà n hình, tạo mảng, điều khiển luồng với lệnh if và vòng lặp for (tất nhiên là sẽ phải học xe m lệnh if và vòng lặp for là gì rồi!). Ta sẽ học cách làm sao để tái sử dụng code bằng cách dùng hàm, học những kiến thức căn bản về lớp và đối tượng, và m ô tả sơ lược về các hàm và module có sẵn trong Python. Sẽ có các chương về module turtle của Python từ đơn giản đến phức tạp, và các bài hư ớng dẫn sử dụng module tkinter để có thể đồ họa trên m à n hình m á y tính. Các bài tập với các độ khó khác nhau ở cuối mỗi chương để em tự viết các chương trình nhỏ sẽ giúp củng cố các kiến thức vừa mới học được. Sau khi đã xây dựng được những kiến thức nền tảng về lập trình, e m sẽ học cách tự viết game. E m sẽ dựng hai game liền, có đồ họa đàng hoàng. Và cũng sẽ học về cơ chế phát hiện va chạm, về các sự kiện, và về các kỹ thuật xây dựng hình ảnh động khác nhau. Đa số các ví dụ trong sách dùng Python shell hay còn gọi là IDLE (Môitrườngtích hợpdànhchoNghiêncứuvàPháttriển⟨IntegratedDevelopmentandLearning
Lời giới thiệu
21
E nvironment⟩ ). IDLE có hỗ trợ hiển thị cú pháp, hỗ trợ chép và dán chữ (tương tự như cách em dùng các chương trình khác), và một cửa sổ soạn thảo để e m có thể lưu lại code về sau còn dùng lại, nói tóm lại IDLE vừa là nơi thử nghiệm trực tiếp, vừa giống một trình soạn thảo code. Tất cả các ví dụ đều chạy được với các giao diện dòng lệnh cũng như các chương trình soạn thảo code, nhưng IDLE với khả năng hiển thị cú pháp và giao diện dễ dùng sẽ giúp e m học hiểu nhanh hơn, do đó ngay chương đầu tiên ta sẽ học cách cài đặt nó.
T R O N G S Á C H C Ó N H Ữ N G N Ộ I D U N G GÌ? Dưới đây là vài dòng tóm tắt nội dung của từng chương cho em: Chương 1 là lời mở đầu về lập trình cùng với các hướng dẫn để cài đặt Python. Chương 2 giới thiệu về tính toán và biến trong lập trình, Chương 3 nói về các kiểu dữ liệu cơ bản trong Python như chuỗi, mảng, tuple và map. Chươ ng 4 e m sẽ lần đầu tiên sử dụng module turtle . Ta sẽ nhảy từ lập trình cơ bản sang di chuyển rùa (trong hình dáng một cái mũi tên) quanh m à n hình. Chương 5 nói về các dạng khác nhau của lệnh if, Chương 6 nói về vòng lặp for và vòng lặp while . Chương 7 ta bắt đầu viết và sử dụng hàm, Chương 8 học về lớp và đối tượng. Ta chỉ học vừa đủ các kiến thức và kỹ thuật cơ bản để phục vụ cho các chương sau khi viết game. Từ đây, nội dung học và thực hành sẽ bắt đầu phức tạp hơn một chút. Chương 9 lướt qua hầu hết các hàm có sẵn trong Python, Chương 10 tiếp nối với vài module cũng được cài đặt sẵn cùng với Python (đại khái là một m ớ các hàm hay dùng ấy mà).
22
Lời giới thiệu
Chương 11 quay trở lại module turtle vì lúc này e m đã có kinh nghiệm với các hình phức tạp hơn. C hư ơ n g 12 chuyển sang module tkinter để tạo ra các hình ảnh chuyển động khó hơn. Trong Chương 13 và 14, ta sẽ viết game đầu tiên, “Bounce!,” dựa trên những kiến thức được học từ đầu đến giờ, và từ Chương 15 đến 18 ta sẽ viết một game khác nữa, “Mr. Stick Man Races for the Exit.” Trong các chương về lập trình game này, sẽ có những lúc em hoàn toàn mất phương hướng. Trong trường hợp đó, em có thể tải code từ website của sách (http://python-for-kids.com/ ) để so sánh với code của mình. Ở Lời cuối, ta sẽ kết thúc bằng việc tham khảo PyGame và một vài ngôn ngữ lập trình phổ biến khác. Cuối cùng, em sẽ học rất chi tiết về các từ khóa của Python trong phần Phụ lục, còn mục Chú giải sẽ liệt kê ra định nghĩa của các thuật ngữ lập trình được dùng trong suốt cả quyển sách.
WEBSITE CỦA SÁCH Nếu em cần giúp đỡ gì đó trong lúc đang đọc sách, hãy thử vào website http://python-for-kids.com/ , ở đây e m có thể tải về tất cả ví dụ trong sách và nhiều bài tập lập trình khác. E m cũng có thể tìm thấy lời giải cho tất cả bài tập trong sách trên website, trong trường hợp bí quá hoặc đơn giản là chỉ muốn tự kiểm tra kết quả xem có đúng không.
VUI LÀ CHÍNH! Lập trình rất vui, hãy cứ nhớ vậy trong lúc dò dẫm đi hết quyển sách. Đừng nghĩ đây là bài tập hay công việc gì cả. Hãy coi lập trình là để tạo ra game hoặc phần mề m gì đó vui vui hay hay mà em có thể mang đi khoe với bạn bè và mọi người xung quanh. Học lập trình là một cách luyện tập trí não rất tuyệt và kết quả nó mang lại là không thể chối cãi. Nhưng cái quan trọng nhất là cho dù học được gì đi nữa, hãy nhớ, vui là chính! Lời giới thiệu
23
24
Lời giới thiệu
Không phải con gì trơn trơn cũng là rắn đâu nhé
1
2
Chương 1
Mỗi chương trình má y tính là một tập hợp các chỉ dẫn để máy tính thực hiện một việc gì đó. Chúng không phải là thứ xung quanh máy tính như ta hay thấy – như dây dợ, bộ vi xử lý, các thẻ cắm, ổ cứng, đại loại thế – mà là thứ chạy ngầm trên những thiết bị đó. Mỗi chương trình máy tính, thường thì ta sẽ chỉ gọi là một chươngtrình⟨ program⟩ , là một tập hợp các lệnh để chỉ đạo đống phần cứng vô dụng kia làm việc này việc nọ. Phầnm ề m ⟨ software⟩ chính là một tập hợp các chương trình máy tính. Không có các chương trình má y tính, hầu hết các thiết bị em đang sử dụng hàng ngày sẽ không còn hoạt động nữa hoặc không còn hữu dụng như nó vốn có nữa. Các chương trình máy tính, với nhiều kiểu rất khác nhau, không chỉ điều khiển mỗi cái máy tính e m đang dùng, m à còn điều khiển cả máy chủ video game, điện thoại di động, thậm chí cả các hệ thống định vị toàn cầu trên ôtô nữa. Phần m ề m cũng điều khiển những thứ ít hiển nhiên hơn như T V tinh thể lỏng và cả cái điều khiển T V nữa, cũng như những radio thế hệ mới, đầu đĩa DVD, bếp và cả tủ lạnh luôn. Rồi thì động cơ ôtô, đèn giao thông, đèn đường, đèn báo tàu hoả, biển quảng cáo hay thang máy cũng đều được điều khiển bởi phần mềm. K hông phải con gì trơn trơn cũng là rắn đâu nhé
3
Phần mềm ít nhiều giống như suy nghĩ vậy. Nếu không suy nghĩ, ta có lẽ chỉ biết ngồi dưới đất, đăm chiêu nhìn vào khoảng không trước mắt, miệng chảy dãi lòng thòng. Ý nghĩ “đứng dậy nào” như là một hướng dẫn, một mệnh lệnh, yêu cầu cơ thể ta phải đứng dậy. Tương tự như thế, phần mềm sẽ nói cho máy tính biết phải làm gì. Một khi đã biết viết ra các chương trình máy tính, em có thể làm ra nhiều thứ rất hấp dẫn. Tất nhiên, em sẽ không thể viết ra các chương trình để điều khiển ôtô, đèn giao thông hay tủ lạnh được (ít nhất bây giờ thì chưa), nhưng em có thể tạo ra website, tự viết game, hoặc là viết ra một chương trình làm hộ bài tập về nhà cũng được.
TẢN MẠN VỀ NGÔN NGỮ Máy tính cũng giống như con người, sử dụng các ngôn ngữ khác nhau để giao tiếp – trong trường hợp này ta gọi là ngôn ngữ lập trình. Ngônngữlậptrình⟨programming language⟩ chỉ đơn giản là một cách thức cụ thể để nói chuyện với máy tính – một cách thức chỉ dẫn mà cả người lẫn máy đều hiểu. Có những ngôn ngữ lập trình mang tên người (như Ada và Pascal), có cái lại là tên viết tắt (như BASIC hay FORTRAN), thậm chí có vài ngôn ngữ được đặt theo tên chương trình TV, như Python. Đúng thế đấy, ngôn ngữ lập trình Python được đặt theo chương trình TV MontyPython’sFlyingCircus, chứ không phải python theo tiếng Anh là con rắn đâu.
CHÚ Ý Monty Python’s Flying Circus làmộtchươngtrìnhhàiđượcpháttrênTVvàonhữngnăm 1970,vàmộtvàikhángiảtớitậngiờvẫncònhếtsứchâmmộ.Chươngtrìnhcónhữngbuổi diễnđộcđáonhư“TheMinistryofSillyWalks,”“TheFish-SlappingDance,”and“The CheeseShop”.
4
Chương 1
Có nhiều thứ trong Python khiến cho nó trở thành một ngôn ngữ lập trình cực kỳ dễ tiếp cận đối với ai mới học. Điều quan trọng nhất là em có thể dùng Python để viết ra các chương trình máy tính hết sức đơn giản mà hiệu quả trong thời gian siêu ngắn. Trong Python không có quá nhiều ký tự phức tạp như các ngôn ngữ khác, điều này khiến nó trở nên dễ đọc và dễ học hơn. (Cũng phải nói rõ là Python có sử dụng ký tự đặc biệt đấy nhé — chỉ là không chi chít như các ngôn ngữ khác thôi.)
CÀI ĐẶT PYTHON Cài đặt Python khá dễ. Ta sẽ đi qua các bước hướng dẫn để cài trên máy từ Windows 7 trở lên, Mac OS X, và Ubuntu. Khi cài Python, em đồng thời cũng sẽ cài đặt c h ư ơ n g trình I D L E (M ô i tr ư ờng tíchh ợ p d à n hc h o N g h i ê n c ứ u v à Phá t triển⟨ Integrated D evelopmentandL earningE nvironment⟩ ), chính là nơi để em viết code Python. Nếu máy đã có sẵn Python, em có thể chuyển luôn sang mục “Làm Gì Sau Khi Cài Python” ở trang 9.
CÀI ĐẶT PYTHON TRÊN M Á Y TỪ WI NDO WS 7 TRỞ LÊN Để cài đặt Python trên các máy từ Microsoft Windows 7 trở lên, tải Python bản từ 3.5 trở lên dành cho Windows ở đây http://www.python.org/downloads/.
CHÚ Ý Khitải,sốphiênbảnchínhxáckhôngquantrọng,miễnlàtừbản3.5trởlênlàđược. Khi trình duyệt hỏi có muốn lưu lại file không thì chọn lưu lại.
K hông phải con gì trơn trơn cũng là rắn đâu nhé
5
Sau khi tải bộ cài cho Windows về, thường thì máy sẽ hỏi em có muốn chạy bộ cài không. Nếu nó không hỏi, em có thể vào thư mục Downloadsvà nháy đúp vào bộ cài. E m cứ làm theo hướng dẫn trên màn hình để cài với các thông số mặc định thôi: 1.
Nhấn Install Now.
2.
Nếu được hỏi có cho phép chương trình lưu lại thay đổi trên máy không, chọn YES.
3.
Cài xong thì chọn Close, lúc này em sẽ thấy có mục Python 3.5 trong menu Start của Windows:
Giờ em có thể bắt đầu học Python ở mục “Làm Gì Sau Khi Cài Python” ở trang 9.
CÀI ĐẶT PYTHON TRÊN M Á Y M A C OS X Đầu tiên e m sẽ phải cài đặt một phần m ề m phụ trợ tên là ActiveTcl: 1.
Và o trang https://www.activestate.com/products/tcl/downloads/ , đăng ký một tài khoản miễn phí và tải ActiveTcl bản mới nhất về, nhớ chọn đúng bản cho Mac OS X.
6
Chương 1
2.
File tải về sẽ có đuôi là .pkg. Nháy đúp vào file, rồi nháy đúp vào file ActiveTcl trong cửa sổ vừa m ở ra. Là m theo hướng dẫn trên mà n hình để cài đặt, chấp nhận điều khoản sử dụng và cài đặt với các thông số mặc định thôi. E m có thể sẽ phải điền mật khẩu má y tính lúc bắt đầu cài. Nếu không có mật khẩu thì hỏi bố mẹ nhé. Đúng ra là em sẽ có sẵn Python trên máy Mac rồi, nhưng có thể chỉ là phiên bản cũ.
Đ ể chắc chắn đã có bản m ới nhất, vào trang http://www.python.org/downloads/ đ ể tải bộ cài đặt mới nhất:
Tải về rồi thì nháy đúp vào (tên file đại khái như python-3.5.0-macosx10.6.pkg), chấp nhận điều khoản sử dụng rồi cứ làm theo hướng dẫn trên m à n hình để cài đặt thôi. E m có thể sẽ phải điền mật khẩu một lần nữa đấy. Giờ em có thể bắt đầu học Python ở mục “Làm Gì Sau Khi Cài Python” ở trang 9.
CÀI ĐẶT PYTHON TRÊN M Á Y UBUNTU Python được đi kèm cùng với bản phân phối Ubuntu của Linux, nhưng e m sẽ phải cài thêm IDLE để có thể làm theo các hướng dẫn trong sách:
K hông phải con gì trơn trơn cũng là rắn đâu nhé
7
1.
Nhấn vào biểu tượng Show Applications (là biểu tượng có 9 dấu chấm thường nằm ở góc dưới bên trái màn hình)
2.
Gõ chữ terminalvào ô tìm kiếm rồi ấn vào biểu tượng Terminal
3.
Nhập mấy dòng lệnh sau vào cửa sổ Terminal, gõ từng dòng một nhé: sudo apt-get update
8
Chương 1
sudo apt-get íntall idle3
4.
E m sẽ phải điền mật khẩu máy tính lúc chạy m ấ y lệnh này. Nếu không có mật khẩu thì hỏi bố mẹ nhé. Thế là e m đã có Python bản mới nhất trên má y rồi, chạy thử thôi.
L À M GÌ SAU KHI CÀI PYTHON? Cài Python xong rồi, ta sẽ thử viết chương trình đầu tiên trên IDLE nhé. Nếu dùng Windows, nhấn vào menu Start, nhập idlevào ô tìm kiếm, chọn IDLE (Python3.5). Nếu dùng Mac, từ menu của Finder chọn Go ► Applications, mở thư mục Python3.5và tìm IDLE. Nếu dùng Ubuntu, menu Applications ► Programming sẽ có một mục tên là IDLE (usingPython3.5) hoặc mới hơn. M ở IDLE ra em sẽ thấy một cửa sổ trông như thế này
Không phải con gì trơn trơn cũng là rắn đâu nhé
9
Cửa sổ này được gọi là Pythonshell, là một phần của IDLE. Ba dấu lớn hơn ( >>> ) được gọi là dấunhắc⟨ prompt⟩ , là dấu hiệu chương trình đã sẵn sàng. Giờ hãy gõ vài thứ vào dấu nhắc, bắt đầu với lệnh sau: >>> print("Hello World")
Nhớ là phải có cặp ngoặc kép ( " " ) đấy nhé. Nhấn ENTER sau khi gõ xong. Nếu gõ lệnh đúng, e m sẽ nhìn thấy kết quả như thế này: >>> print("Hello World") Hello World >>>
Dấu nhắc sẽ lại hiện lên để e m biết Python đã sẵn sàng cho lệnh tiếp theo. Giỏi quá! E m vừa mới viết được chương trình Python đầu tiên rồi. Từ print là một lệnh trong Python đư ợc gọi là h à m ⟨function⟩ , và nó sẽ in bất cứ cái gì được viết bên trong dấu ngoặc ra màn hình. Nói cách khác, e m vừa mới đưa má y tính m ột m ệ n h lệnh là in c hữ Hello World ra m à n hình – một lệnh m à cả e m lẫn m á y tính đều hiểu được.
LƯU LẠI CHƯƠN G TRÌNH PYTHON Nếu mỗi lần muốn dùng một chương trình Python nào đó mà lại phải viết code hết lại từ đầu thì nghe hơi phiền, và đừng có nói là em đang nghĩ đến việc in ra giấy đấy nhé. Đương nhiên, mấy chương trình bé bé viết lại cũng được thôi, nhưng một chương trình phức tạp – kiểu xử lý văn bản chẳng hạn – có thể dài đến cả hàng triệu dòng code đấy. In hết ra chắc hơn 100.000 trang vẫn còn là ít. Cứ hình dung em phải khiêng đống giấy tờ đấy về nhà nhỉ. Đừng có mà để cơn gió nào thổi qua nhé.
10
Chương 1
Rất may, ta có thể giữ lại những chương trình này để về sau dùng lại. Để lưu lại một chương trình, mở IDLE và chọn File ► New Window. Một cửa sổ mới sẽ xuất hiện với tiêu đề *Untitled*. Nhập đoạn code sau vào cửa sổ shell mới đó: print("Hello World")
Sau đó chọn File ► Save. Khi được hỏi tên file thì điền vào hello.pyvà lưu lại file vào màn hình desktop. Tiếp đó chọn Run ► Run Module. Nếu làm đúng hết, chương trình e m lưu lại sẽ chạy ngon lành như thế này:
Giờ em hãy đóng cửa sổ shell nhưng giữ lại cửa sổ hello.pyvà chọn Run ► Run Module, Python shell sẽ lại hiện ra, chương trình của em lại có thể chạy một lần nữa. (Nếu chỉ muốn mở Python shell mà không có chương trình nào chạy thì chọn Run ► Python Shell.) Sau khi chạy, e m sẽ nhận ra trên mà n hình desktop của e m có một file mới tên là hello.py. Nếu nháy đúp vào file đó, một cửa sổ đen đen sẽ nháy lên một cái rồi biến mất. Chuyện gì vậy chài?
Không phải con gì trơn trơn cũng là rắn đâu nhé
11
Thực ra em vừa mới nhìn thấy cửa sổ Python console (khá giống với Python shell) bật lên, in ra chữ Hello World và rồi tắt đi luôn. Nếu có đôi mắt thần nhìn thấy những thứ bay siêu nhanh, thì e m sẽ thấy cửa sổ này ngay trước khi nó đóng:
Ngoài cách dùng menu ra, em cũng có thể dùng các phím tắt để tạo ra shell mới, lưu file và chạy chương trình: ●
Trên Windows và Ubuntu, dùng CTRL-N để mở cửa sổ shell mới, dùng CTRL-S để lưu file lại sau khi viết code xong, và nhấn F5 để chạy chương trình.
●
Trên Mac OS X, dùng ⌘-N để mở cửa sổ shell mới, dùng ⌘-S để lưu file lại, và giữ phím Fn cùng với F5 để chạy chương trình.
TÓM TẮT Trong chương này ta đã cùng nhau viết chương trình Hello World – một chương trình hết sức đơn giản mà gần như ai học lập trình cũng phải viết. Trong các chương sau ta sẽ dùng Python shell để làm thêm nhiều thứ thú vị nữa nhé.
12
Chương 1
Cài đặt xong Python và biết bật Python shell lên là em đã sẵn sàng rồi đấy. Ta sẽ bắt đầu với m ột vài tính toán đ ơn giản và sau đó chuyển sa ng biến. Biế ntronglậptrình ⟨ variable⟩ là một cách để ghi lại các thứ trong một chương trình máy tính, nhờ đó ta mới có thể viết được các chương trình có ý nghĩa.
LÀ M TOÁN VỚI PYTHON Thông thường, khi được bảo làm phép nhân hai số với nhau ví dụ 8 × 3.57, em sẽ lôi máy tính hoặc giấy bút ra để tính phải không nào. Giờ sao không dùng Python shell để làm phép tính này? Thử nhé. Bật Python shell lên như hướng dẫn trong Chương 1, sau đó nhập biểu thức sau vào chỗ dấu nháy: >>> 8 * 3.57 28.56
Tính toán và biến trong lập trình
13
Ta thấy là khi làm phép nhân trong Python, ta sử dụng dấu sao ( * ) thay cho dấu nhân (× ). Giờ thử làm hẳn một bài toán có ý nghĩa đàng hoàng nhé. Giả sử vào một ngày đẹp trời, em đang đào xới gì đó ngoài vườn, bỗng nhiên đào được một cái túi đựng 20 đồng vàng. E m chạy ngay xuống hầm, nhét hết chỗ xu vào cái máy siêu-photocopy của ông nội đang phủ đầy bụi để dưới đó (may vừa đủ chỗ để nhét cả 20 xu). E m nghe tiếng máy chạy ù ì loạch xoạch, vài tiếng sau nó nhả ra thêm 10 đồng xu mới toanh. Hỏi, nếu ngày nào cũng làm thế trong vòng một năm liền, e m sẽ có bao nhiêu xu tất cả? Trên giấy, các phương trình sẽ kiểu như thế này: 10 × 365 = 3650 20 + 3650 = 3670 Tất nhiên là làm mấy phép tính này trên má y tính hoặc trên giấy thì quá dễ rồi, nhưng ta cũng có thể làm y như thế trên Python shell. Đầu tiên, nhân 10 xu với 365 ngày một năm để được 3650. Tiếp, ta cộng 20 xu ban đầu vào để được 3670. >>> 10 * 365 36 50 >>> 20 + 3650 36 70
Xong tự nhiên có một con quạ phát hiện ra mấy đồng xu lấp lánh trong phòng, thế là tuần nào nó cũng lượn vào trộm mất ba xu. Hỏi, đến cuối năm em còn lại bao nhiêu xu? Các phép tính trên shell sẽ trông như thế này: >>> 3 * 52 156 >>> 3670 - 156 35 14
14
Chương 2
Đầu tiên, ta nhân 3 xu với 52 tuần trong năm. Kết quả là 156. Lấy tổng số xu ta có (3670) trừ đi số đó sẽ được 3514 xu còn lại vào cuối năm. Đây chỉ là một chương trình rất đơn giản. Trong quyển sách này, em sẽ học cách làm sao m ở rộng những kiến thức này để viết ra những chương trình có ý nghĩa hơn.
CÁC TOÁN TỬ TRONG PYTHON E m có thể làm các phép cộng, trừ, nhân, chia trong Python shell, cùng với những phép tính khác nữa m à ta sẽ tạm thời chưa nói tới. Các dấu được Python dùng để tính toán này được gọi là cáctoántử⟨ operator⟩ , được liệt kê ở Bảng 2-1. Bảng 2-1: Các toán tử cơ bản trong Python Dấu
Phép tính
+
Cộng
-
Trừ
*
Nh ân
/
Chia
Dấu gạchchéo⟨ forwardslash⟩ ( /) được dùng cho phép chia vì nó giống với dấu gạch khi ta viết phân số. Giả sử nếu ta có 100 tên cướp biển và 20 cái thùng lớn, giờ muốn biết mỗi thùng có thể giấu được bao nhiêu tên cướp biển thì em sẽ phải chia 100 tên ra 20 thùng (100 ÷ 20) bằng cách nhậ p 100 / 20 và o Python shell. N h ớ là dấ u gạch chéo nà y nghiêng về phía bên phải nhé.
THỨ TỰ TÍNH TOÁN Trong lập trình, ta sẽ dùng các dấu ngoặc tròn để điều khiển thứ tự tính toán. Cứ cái gì mà dùng toán tử thì đều gọi là phéptính⟨ operation⟩ . Phép nhân và phép chia được xếp Tính toán và biến trong lập trình
15
hạng cao hơn phép cộng và phép trừ, nghĩa là được ưu tiên hơn. Nói cách khác, khi em viết ra một biểu thức trong Python, các phép nhân và phép chia sẽ được tính trước phép cộng và phép trừ. Ví dụ, trong biểu thức sau, số 30 và 20 sẽ được nhân với nhau trước, sau đó 5 mới được cộng vào tích trên. >>> 5 + 30 * 20 605
Nếu dùng từ ngữ để diễn đạt biểu thức này thì sẽ là, “nhân 30 với 20, lấy kết quả cộng với 5”. Kết quả là 605. Ta có thể thay đổi thứ tự của các phép tính bằng cách ngoặc hai số đầu tiên lại như thế này: >>> (5 + 30) * 20 700
Kết quả của biểu thức này là 700 (không phải 605 nữa) vì dấu ngoặc sẽ yêu cầu Python tính bên trong trước, sau đó mới đến lượt các phép tính bên ngoài. Ví dụ này sẽ được diễn đạt lại là “cộng 5 với 30, sau đó nhân kết quả với 20.” Các dấu ngoặc có thể lồngvàonhau⟨ nested⟩ , nghĩa là có thể có ngoặc trong ngoặc, như thế này: >>> ((5 + 30) * 20) / 10 70.0
Trong trường hợp này, Python sẽ tính nội dung ở trong ngoặc trong cùng, rồi tính dần ra ngoài, cuối cùng mới đến phép chia. Nói cách khác, biểu thức được diễn đạt là “cộng 5 với 30, nhân kết quả với 20, lấy kết quả mới chia cho 10.” Và thứ tự diễn ra sẽ là: ●
Lấy 5 cộng 30 được 35.
●
Lấy 35 nhân 20 được 700.
●
Lấy 700 chia cho 10 để có kết quả cuối cùng là 70.
16
Chương 2
Nếu không có ngoặc, kết quả sẽ hơi khác đi một chút: >>> 5 + 30 * 20 / 10 65.0
Lúc này, đầu tiên 30 sẽ nhân với 20 (được 600), rồi 600 chia cho 10 (được 60). Cuối cùng sẽ cộng với 5 để ra kết quả là 65.
CHÚ Ý Hãynhớrằngphépnhânvàphépchialuônđượctínhtrướcphépcộngvàphéptrừ,trừkhi códấungoặccanthiệpvàothứtựtínhtoán.
BIẾN LÀ M Ả N H GIẤY GHI CHÚ Biếntronglậptrình⟨ variable⟩ được dùng để lưu các thông tin như số, chữ hoặc mảng nhiều số, mảng nhiều chữ, vân vân và mây mây. Em có thể coi biến như một mẩu giấy ghi chú có viết các thông tin trên đó. Ví dụ, để tạo ra biến fred , ta sẽ dùng dấu bằng ( = ) và nói Python ta muốn ghi lại thông tin gì lên tờ giấy kia. Ở đây, ta tạo ra biến fred và nói Python hãy viết số 100 lên (chú ý là các biến khác nhau vẫn có thể có giá trị giống nhau đấy nhé): >>> fred = 100
Để kiểm tra xem một biến ghi lại thông tin gì trên đó, gõ print vào shell, rồi gõ tên biến vào trong một cặp ngoặc đơn như thế này: >>> print(fred) 100
Ta cũng có thể bảo Python thay đổi biến fred để nó mang thông tin gì đó khác đi. Ví dụ để đổi fred thành số 200: >>> fred = 200 >>> print(fred) 200
Tính toán và biến trong lập trình
17
Dòng đầu tiên, ta bảo fred ghi lại con số 200. Dòng thứ hai, ta hỏi fred hiện đang giữ thông tin gì, để kiểm tra lại cho chắc. Python sẽ in kết quả ra ở dòng cuối cùng Ta cũng có thể có nhiều mảnh giấy như thế (tức là nhiều biến) cho cùng một thông tin giống nhau: >>> fred = 200 >>> john = fred >>> print(john) 200
Trong ví dụ này, ta bảo Python rằng ta muốn biến john lưu cùng thông tin y như biến fred bằng cách dùng dấu bằng giữa john và fred . Ở đây, rõ ràng đặt tên biến là fred nghe chẳng ăn nhập gì, vì về cơ bản nó không nói cho ta biết biến này lưu trữ cái gì cả. Ta sẽ đổi tên biến từ fred sang number_of_coins như thế này: >>> number_of_coins = 200 >>> print(number_of_coins) 200
Giờ thì ai cũng hiểu là ta đang nói về 200 xu rồi nhé. Tên biến có thể được viết bằng chữ cái, số và ký tự gạch dưới ( _), nhưng không bắt đầu bằng số được. E m có thể dùng bất cứ gì từ một ký tự duy nhất ( a chẳng hạn) cho đến cả một câu dài ngoằng để làm tên biến. (Vì tên biến không thể có khoảng trắng nên ta dùng dấu gạch dưới thay cho dấu cách.) Đôi khi, khi đang làm gì đó nhanh nhanh chóng chóng, dùng tên biến ngắn gọn là phù hợp nhất. Tên biến m à em chọn thường phụ thuộc vào việc e m cần nó có có ý nghĩa đến mức nào.
18
Chương 2
SỬ DỤNG BIẾN Còn nhớ mấy biểu thức e m dùng để tính xem cuối năm e m sẽ có bao nhiêu xu nếu dùng phát minh kỳ diệu của ông nội đang để dưới hầm để đẻ ra xu mới không? Mấy biểu thức này này: >>> 20 + 10 * 365 3670 >>> 3 * 52 156 >>> 3670 - 156 3514
Ta có thể biến chúng thành một dòng biểu thức duy nhất: >>> 20 + 10 * 365 – 3 * 52 3514
Giờ ta sẽ chuyển mấ y con số bên trên thành biến như này: >>> found_coins = 20 >>> magic_coins = 10 >>> stolen_coins = 3
M ấ y dòng này sẽ tạo ra các biến found_coins (số xu nhặt được), magic_coins (số xu m ới được tạo ra từ máy) và stolen_coins (số xu bị mất). Thay vào biểu thức trên ta có: >>> found_coins + magic_coins * 365 - stolen_coins * 52 3514
Ơ vẫn ra cùng kết quả kìa, thế thì nói làm gì? À ha, sức mạnh ma thuật của biến chính là ở đây. Giả sử trên cửa sổ giờ có một con bù nhìn, con quạ thấy thế sợ quá chỉ trộm được hai xu thay vì ba xu
Tính toán và biến trong lập trình
19
thì sao? Một khi đã tạo ra biến, ta có thể thay đổi để nó có giá trị mới, và bất cứ chỗ nào dùng biến đó sẽ được thay đổi theo. Ta có thể thay đổi giá trị của biến stolen_coins sang 2 như sau: >>> stolen_coins = 2
Sau đó ta có thể chép và dán biểu thức bên trên để tính lại, như thế này: 1.
Bôi đen dòng cần chép bằng cách nhấn chuột trái và kéo từ đầu dòng đến cuối dòng như hình dưới đây:
2.
Giữ phím CTRL (hoặc nếu đang dùng Mac thì giữ phím ⌘ ) rồi nhấn C để chép dòng vừa được bôi đen. (Từ giờ ta sẽ gọi là CTRL-C cho ngắn ngọn.)
3.
N h ấ n c huột v à o d ấ u nh ắ c c uối c ù n g ( ng a y sau stolen_coins = 2 )
4.
Giữ phím CTRL và nhấn V để dán dòng đã được chép. (Từ giờ ta sẽ gọi là CTRL-V.)
5.
Nhấn ENTER để có kết quả mới:
20
Chương 2
Thế có phải là đỡ hơn nhiều so với gõ lại toàn bộ biểu thức không? Rõ rồi còn gì. Em có thể thử thay đổi các biến khác, rồi chép (CTRL-C) và dán (CTRL-V) lại biểu thức trên để thấy kết quả thay đổi như thế nào. Giả sử e m căn thời gian chính xác rồi đá cái máy một phát, má y sẽ nhả ra thêm 3 xu mỗi lần chạy, cuối năm e m sẽ có tổng cộng 4661 xu: >>> magic_coins = 13 >>> found_coins + magic_coins * 365 - stolen_coins * 52 4661
Đương nhiên, sử dụng biến trong những biểu thức đơn giản kiểu này thì mới chỉ có tác dụng títẹothôi. Chưa đến m ứ c cựckỳcó tác dụng. Như ng giờ e m chỉ cần nhớ rằng biến là một cách để ghi lại mọi thứ để về sau còn dùng lại.
TÓM TẮT Trong chương này. e m đã thấy cách sử dụng các toán tử trong Python để thực hiện các tính toán đơn giản và cách dùng ngoặc để điều khiển thứ tự các phép tính (là thứ tự m à Python sẽ dựa theo đó m à tính toán từng phần của biểu thức). Sau đó ta tạo ra biến để ghi lại các giá trị rồi sử dụng lại chúng trong các phép tính.
Tính toán và biến trong lập trình
21
22
Chương 2
Chương 2 đã nói về các tính toán cơ bản và về biến trong lập trình Python. Chương này ta sẽ nói về vài thứ khác trong lập trình Python bao gồm: chuỗi, mảng, tuple và map. Chuỗi được dùng để hiển thị các câu các chữ trong chương trình (kiểu như mấy chữ “Get Ready” và “Game Over” trong game ý). Mảng, tuple và map được dùng để lưu nhiều thứ với nhau cùng một lúc.
CHUỖI Trong lập trình nói chung ta thường gọi chữ là chuỗi⟨ string⟩ . Em cứ hình dung chuỗi giống như là một danh sách các ký tự liền nhau vậy, cho dễ nhớ. Tất cả chữ cái, con số, ký hiệu trong quyển sách này đều có thể là chuỗi. Không chỉ thế đâu, tên em cũng là chuỗi này, địa chỉ nhà cũng là chuỗi nốt. Thực ra, ngay chương trình Python đầu tiên ta viết từ Chương 1 cũng đã dùng chuỗi rồi: “Hello World.”
Chuỗi, mảng, tuple và map
23
KHỞI TẠO CHUỖI Trong Python, chuỗi được tạo ra bằng cách lồng các câu vào trong một cặp ngoặc đơn hoặc ngoặc kép (Ta phải làm thế để bảo máy tính đây là số, kia là chữ, kiểu vậy). Lấy ví dụ ta có thể dùng lại biến fred ở Chương 2 để ghi lại một chuỗi như này: >>> fred = "Why do gorillas have big nostrils? Big fingers!!"
Sau đó để x e m bê n trong fred c ó gì, ta c ó thể g õ print(fred) n h ư này: >>> print(fred) Why do gorillas have big nostrils? Big fingers!!
1
E m cũng có thể dùng dấu ngoặc đơn để tạo chuỗi đấy nhé: >>> fred = 'What word is always spelled wrong in the dictionary? Wrong!!' >>> print(fred) What word is always spelled wrong in the dictionary? Wrong!!
2
Nhưng mà, nếu câu của e m có nhiều hơn một dòng m à chỉ sử dụng dấu ngoặc đơn (') hoặc ngoặc kép ( " ), hoặc là nếu em m ở bằng ngoặc này và đóng bằng ngoặc kia thì em sẽ gặp lỗi trên Python shell ngay. Thử nhập dòng sau mà xem: >>> fred = "Why do birds fly?
Kết quả: SyntaxError: EOL while scanning string literal
Lỗi này nói rằng cú pháp câu lệnh không chính xác, vì e m đã phá luật không đóng ngoặc ở cuối chuỗi. Cúpháp⟨ syntax⟩ là sự sắp xếp theo đúng thứ tự của từng chữ trong một câu, hoặc như trong trường hợp này thì là sự sắp xếp theo đúng thứ tự của các từ khóa và ký hiệu trong một chương trình. Vậ y SyntaxError nghĩa là e m đã làm gì đó không đúng thứ tự
1 2
24
Câu đùa: “Sao lỗ mũi đười ươi to quá vại? Vì ngón tay nó to!!” (ngoáy nhiều thành ra lỗ mũi to ^^) Câu đùa: “Trong từ điển chữ nào luôn luôn bị viết sai? Chữ Sai!!”
Chương 3
khiến Python không hiểu, hoặc là Python đang cần gì đó mà em lại không viết ra. EOLlà viết tắt của hếtdòng⟨ end-of-line⟩ , vậy toàn bộ lỗi nói rằng Python đã đọc hết dòng lệnh rồi mà vẫn không tìm thấy dấu đóng ngoặc ở cuối chuỗi. Đ ể một chuỗi có thể chứa nhiều hơn một dòng chữ (còn gọi là chuỗinhiềudòng ⟨multilinestring⟩), em có thể dùng ba dấu ngoặc đơn ( '''), rồi nhấn ENTER để sang dòng như thế này: >>> fred = '''Why do birds fly? It's faster than walking!'''
In nội dung của fred ra thử xem sao: >>> print(fred) Why do birds fly? It's faster than walking!
3
CÁC V ẤN ĐỀ THƯỜNG GẶP VỚI CHUỖI Có một ví dụ này rất rối rắm, viết kiểu gì cũng bị bắn ra lỗi: >>> silly_string = 'He said, "Aren't can't shouldn't wouldn't."' SyntaxError: invalid syntax
Ở đây ta đang tạo ra một chuỗi (với biến silly_string ), được bọc lại bởi hai dấu ngoặc đơn ở hai đầu, nhưng bản thân chuỗi lại có mấy từ cũng có dấu ngoặc đơn là can't , shouldn't , wouldn't , xong ở giữa lại còn có mấ y dấu ngoặc kép nữa. Hỗn hết cả loạn!
E m phải hiểu Python không thông minh như người đâu, nên cái nó thấy chỉ là chuỗi này thôi H e said, "Aren , sau đó là một đống những ký tự thừa thãi gì đâu. Mỗi khi Python nhìn thấy một dấu ngoặc (bất kể là ngoặc đơn hay ngoặc kép), là nó tự hiểu sau dấu ngoặc đó sẽ là chuỗi, thế là nó cứ thế đi cho đến hết dòng để tìm dấu ngoặc còn lại (ngoặc đơn hoặc ngoặc kép tương ứng). Trong trường hợp này, chuỗi được bắt đầu ở dấu ngoặc đơn nằm trước từ He , và kết thúc ở dấu ngoặc đơn ở sau chữ n trong Aren , Python chỉ biết có vậy. IDLE có đánh dấu ngay tại điểm mà mọi thứ trở nên sai sai:
3
Câu đùa: “Vì sao chim lại bay? Vì bay nhanh hơn đi bộ chứ sao!” Chuỗi, mảng, tuple và map
25
Dòng cuối của IDLE giải thích đại khái em đang gặp lỗi gì – trong trường hợp này là một lỗi cú pháp. Thay bằng dấu ngoặc kép vào cũng không giải quyết được vấn đề gì: >>> silly_string = "He said, "Aren't can't shouldn't wouldn't."" SyntaxError: invalid syntax
Lần này, Python phát hiện ra một chuỗi được bọc bởi hai dấu ngoặc kép, chứa các ký tự H e said, (và một dấu cách). Tuốt tuồn tuột những thứ còn lại (tính từ Aren't trở đi) chính là tác nhân gây lỗi:
Đó là do, dưới góc nhìn của Python, tất cả những gì thừa thãi đều không nên tồn tại. Nó đi tìm dấu ngoặc tiếp theo để kết thúc chuỗi và hoàn toàn không hiểu e m muốn nó làm gì với mấy thứ hầm bà lằng nhằng sau dấu đóng ngoặc đó. Giải pháp cho tình huống này là dùng chuỗi nhiều dòng mà ta vừa học được bằng cách sử dụng badấu ngoặc đơn ( ''' ), như vậy, ta có thể kết hợp cả ngoặc đơn lẫn ngoặc kép mà không gặp vấn đề gì. Đúng hơn là, một khi đã bọc bằng ba dấu ngoặc đơn, ta có thể
26
Chương 3
dùng bao nhiêu ngoặc đơn ngoặc kép gì trong chuỗi cũng được (miễn là đừng có dùng ba dấu ngoặc đơn trong đó). Đây là phiên bản ngon lành của chuỗi lúc nãy: silly_string = '''He said, "Aren't can't shouldn't wouldn't."'''
À khoan, còn cái này nữa. Nếu thực sự chỉ muốn dùng ngoặc đơn hoặc ngoặc kép để bọc chuỗi trong Python thay vì ba dấu ngoặc kia, e m có thể dùng dấu gạch ngược ( \) trước mỗi dấu ngoặc bị trùng bên trong chuỗi. Việc này gọi là thoátra⟨ escape⟩ . Đây là cách để ta nói với Python rằng “OK, tôi biết trong chuỗi của tôi có ngoặc bị trùng rồi, đề nghị anh bỏ qua cho tới khi anh nhìn thấy dấu đóng ngoặc thật sự nhá.” Việc viết thêm các ký tự thoát vào có thể khiến cho chuỗi trở nên khó đọc hơn, nên dùng chuỗi nhiều dòng phần nào đó ổn hơn. Nhưng biết đâu về sau e m lại gặp những đoạn code có dùng ký tự thoát, cho nên dù sao đi nữa, hiểu ý nghĩa của mấy cái dấu gạch ngược này vẫn hơn nhỉ. Đây là vài ví dụ về ký tự thoát: >>> single_quote_str = 'He said, "Aren\'t can\'t shouldn\'t wouldn\'t."' >>> double_quote_str = "He said, \"Aren't can't shouldn't wouldn't.\"" >>> print(single_quote_str) He said, "Aren't can't shouldn't wouldn't." >>> print(double_quote_str) He said, "Aren't can't shouldn't wouldn't."
Đầu tiên ở ❶ , ta tạo ra một chuỗi bằng ngoặc đơn, sử dụng dấu gạch ngược để thoát khỏi các ngoặc đơn khác bên trong chuỗi. Sau đó ở ❷ , ta tạo ra một chuỗi khác bằng dấu ngoặc kép và sử dụng dấu gạch ngược để thoát khỏi các ngoặc kép khác bên trong chuỗi. Dòng cuối cùng ta in các biến vừa được tạo. E m có thể thấy là mấy dấu gạch ngược này hoàn toàn vô hình lúc in nhé.
Chuỗi, mảng, tuple và map
27
GHÉP GIÁ TRỊ V À O TR O N G CHUỖI Nếu cần viết ra câu gì đó có dính dáng đến dữ liệu của một biến, e m có thể ghép chúng vào nội dung của chuỗi bằng cách dùng %s , một kiểu đánh dấu chỗ cho các giá trị về sau sẽ được thay vào. (Ghépgiátrị⟨ embeddingvalue⟩ , hay còn được gọi là thaythếnộidung trongchuỗi⟨ stringsubstitution⟩ , là cách lập trình viên mô tả việc “đưa một giá trị nào đó vào bên trong chuỗi.”) Ví dụ, sau khi Python tính toán hoặc lưu lại điểm số trong một game, để nhét số điểm đó vào trong một câu kiểu “Tôi đã ghi được ___ điểm,” em có thể dùng %s ngay chỗ gạch gạch kia, rồi nói Python giá trị đó là gì, như sau: >>> myscore = 1000 >>> message = 'I scored %s points' >>> print(message % myscore) I scored 1000 points
Ta vừa tạo ra biến myscore với giá trị 1000 và biến message là một chuỗi với câu “I scored %s points,” trong đó %s là ký tự đặt chỗ cho số điểm ghi được. Dòng tiếp theo ta gọi print(message) với ký hiệu % để nói Python hãy thay %s bằng giá trị của myscore vào. Kết
quả là câ u I scored 10 00 points đ ư ợ c in ra. Ở đâ y ta kh ôn g nhất thiết phải s ử dụ ng biến, viết n h ư thế nà y c ũ ng đư ợc print(m es sa ge % 1000) . Ta cũng có thể đưa các giá trị khác nhau vào chỗ %s bằng cách dùng các biến khác nhau như sau: >>> joke_text = '%s: a device for finding furniture in the dark' >>> bodypart1 = 'Knee' >>> bodypart2 = 'Shin' >>> print(joke_text % bodypart1) Knee: a device for finding furniture in the dark >>> print(joke_text % bodypart2) Shin: a device for finding furniture in the dark
4 5
28
4
5
Câu đùa: “Đầu gối: là dụng cụ tìm đồ trong bóng đêm” (va vào đâu là biết ngay ở đấy có đồ) Câu đùa: “Cẳng chân: là dụng cụ tìm đồ trong bóng đêm”
Chương 3
Ở đây ta có tổng cộng ba biến. Biến đầu tiên joke_text là chuỗi có ký tự đặt chỗ %s . Các biến còn lại là bodypart1 và bodypart2 . Khi ta in ra biến joke_text , ký hiệu % được sử dụng để thay giá trị của bodypart1 và bodypart2 vào để sinh ra hai câu khác nhau.
E m cũng có thể dùng nhiều ký tự đặt chỗ cùng lúc, như thế này: >>> nums = 'What did the number %s say to the number %s? Nice belt!!' >>> print(nums % (0, 8)) What did the number 0 say to the number 8? Nice belt!!
Khi có nhiều ký tự đặt chỗ trong câu, nhớ là phải dùng ngoặc tròn để bọc những giá trị thay thế vào như bên trên nhé. Các giá trị cần được sắp xếp theo đúng thứ tự chúng được dùng trong chuỗi.
DÙNG PHÉP NHÂN TRONG CHUỖI 10 nhân 5 là bao nhiêu? 50, quá dễ. 10 nhân với athì sao? Python trả lời: >>> print(10 * 'a') aaaaaaaaaa
Lập trình viên giả dụ có thể dùng cách này để thêm các khoảng trắng và xếp các chuỗi cho thẳng hàng. Một bức thư trong shell trông sẽ như thế nào nhỉ? (chọn File ► New Window và nhập vào đoạn code này nhé): spaces = ' ' * 25 print('%s 12 Butts Wynd' % spaces) print( '%s Twinklebottom Heath' % spaces) print ('%s West Snoring' % spaces) print() print() print('Dear Sir') print() print('I wish to report that tiles are missing from the') print('outside toilet roof.') print('I think it was bad wind the other night that blew them away.') print() print( 'Regards') print('Malcolm Dithering') Chuỗi, mảng, tuple và map
29
Sau khi nhập xong trong cửa sổ shell, chọn File ► Save As. Đặt tên file là myletter.py. Giờ em có thể chạy nó bằng cách chọn Run ► Run Module.
CHÚ Ý Từgiờ,nếuđằngtrướcmộtđốngcodeemthấySave As: somefilename.py thìcónghĩalà emsẽphảichọnFile►NewWindow,gõđốngcodeđóvào,rồilưulạifilenhưtavừalàm, nhé. Ở dòng đầu tiên của ví dụ này, ta tạo ra biến spaces bằng cách nhân một dấu cách với số 25. Biến này được dùng ở ba dòng tiếp theo để dóng câu chữ cho thẳng hàng sang phía bên phải. Kết quả của mấ y hà m print trên trông sẽ như thế này:
Ngoài việc dùng để dóng hàng, ta cũng có thể dùng phép nhân chuỗi để in kín màn hình cho vui: >>> print(1000 * 'snirt')
30
Chương 3
MẢNG CÒN KHỦNG HƠN CHUỖI “Chân nhện, ngón ếch, mắt kỳ nhông, cánh dơi, bơ ốc sên, vẩy rắn”, đây hẳn là một danh sách m ua sắm hết sức không bình thường (tất nhiên, trừ khi em là phù thủy), ở đây ta sẽ dùng nó làm ví dụ để thấy sự khác biệt giữa chuỗi và mảng nhé. Ta có thể dùng chuỗi để lưu lại danh sách này vào biến wizard_list n h ư sau: >>> wizard_list = 'spider legs, toe of frog, eye of newt, bat wing, slug butter, snake dandruff' >>> print(wizard_list) spider legs, toe of frog, eye of newt, bat wing, slug butter, snake dandruff
Nhưng mặt khác ta cũng có thể tạo ra một mảng⟨ list⟩ , một kiểu khá kỳ diệu trong Python, kiểu m à ta có thể can thiệp vào từng phần tử của nó rất dễ dàng. Ta sẽ viết mảng như sau: >>> wizard_list = ['spider legs', 'toe of frog', 'eye of newt', 'bat wing', 'slug butter', 'snake dandruff'] >>> print(wizard_list) ['spider legs', 'toe of frog', 'eye of newt', 'bat wing', 'slug butter', 'snake dandruff']
Mảng thường phải gõ nhiều hơn chuỗi một chút, nhưng bù lại ta lại có nhiều quyền điều khiển hơn chuỗi. Ví dụ, ta có thể in ra m ó n thứ ba trong wizard_list (là mắt kỳ nhông) bằng cách nhập vào vị trí của nó ở trong mả ng (còn gọi là vịtríchỉmục⟨ index position⟩ ) bên trong một cặp ngoặc vuông ( []) như sau: >>> print(wizard_list[2]) eye of newt
Ủa, em tưởng là món thứ ba cơ mà? Đúng, nhưng thứ tự trong mảng lại bắt đầu từ vị trí 0 cơ, cho nên món đầu tiên sẽ ở vị trí 0, món thứ hai ở vị trí 1 và món thứ ba ở vị trí 2. Mình là người tuy nghe không hợp lý lắm nhưng máy tính thì hiểu thế đấy.
Chuỗi, mảng, tuple và map
31
So với chuỗi, khi dùng mảng ta có thể sửa lại danh sách này dễ dàng hơn nhiều. Giả sử thay vì mắt kỳ nhông ta cần lưỡi ốc cơ. Ta có thể làm như sau với mảng: >>> wizard_list[2] = 'snail tongue' >>> print(wizard_list) ['spider legs', 'toe of frog', 'snail tongue', 'bat wing', 'slug butter', 'snake dandruff']
Như vậy, ta vừa mới nhét món mới lưỡi ốc vào trong mảng ở vị trí thứ 2, lúc trước là mắt kỳ nhông. Một thao tác khác là khi ta chỉ cần một phần con của mảng chứ không cần hết toàn bộ mảng. Ta sẽ dùng dấu hai chấm ( :) bên trong ngoặc vuông. Ví dụ, gõ đoạn code sau để có được một phần nhỏ của danh sách trên, từ m ón thứ ba đến thứ nă m (các nguyên liệu chuẩn chỉnh cho một chiếc bánh m ỳ kẹp cái-gì-đó-không-phải-thịt): >>> print(wizard_list[2:5]) ['snail tongue', 'bat wing', 'slug butter']
Viết [2:5] đồng nghĩa với, “chỉ đưa ra những món nào từ vị trí số 2 đến vị trí số 5 (nhưng không bao gồm cái số 5 nhé)” – hoặc nói cách khác là mấy món số 3, 4, 5. Mảng có thể chứa tất cả các thể loại dữ liệu, từ số: >>> some_numbers = [1, 2, 5, 10, 20]
Đến chữ: >>> some_strings = ['Which', 'Witch', 'Is', 'Which']
Hoặc cả số lẫn chữ: >>> numbers_and_strings = ['Why', 'was', 6, 'afraid', 'of', 7, 'because', 7, 8, 9] >>> print(numbers_and_strings) ['Why', 'was', 6, 'afraid', 'of', 7, 'because', 7, 8, 9]
Hoặc thậm chí chứa cả một mảng khác: 32
Chương 3
>>> numbers = [1, 2, 3, 4] >>> strings = ['I', 'kicked', 'my', 'toe', 'and', 'it', 'is', 'sore'] >>> mylist = [numbers, strings] >>> print(mylist) [[1, 2, 3, 4], ['I', 'kicked', 'my', 'toe', 'and', 'it', 'is', 'sore']]
Ví dụ mảng-trong-mảng nà y có ba biến: numbers có bốn số, strings có tám chữ và mylist dùng cả numbers lẫn strings . M ả n g thứ ba ( mylist ) chỉ c ó hai phần tử thôi vì nó
chỉ chứa hai biến kia, chứ không chứa các phần tử của hai biến kia.
THÊM PHẦN TỬ VÀO MẢNG Để thêm phần tử vào mảng, ta sẽ sử dụng hàm append . Hàm⟨ function⟩ là một mớ Python code thực hiện một việc cụ thể nào đó. Trong trường hợp này, append sẽ nhét thêm một phần tử vào cuối mảng. Ví dụ, để thêm hơi thở của gấu (có thứ đó luôn hả trời ^^) vào danh sách mua sắm của bà phù thủy kia, ta làm như thế này: >>> wizard_list.append('bear burp') >>> print(wizard_list) ['spider legs', 'toe of frog', 'snail tongue', 'bat wing', 'slug butter', 'snake dandruff', 'bear burp']
Tương tự, em có thể kéo dài danh sách những thứ kì quặc này ra, như này: >>> wizard_list.append('mandrake') >>> wizard_list.append('hemlock') >>> wizard_list.append('swamp gas')
Danh sách mua sắm giờ dài như này: >>> print(wizard_list) ['spider legs', 'toe of frog', 'snail tongue', 'bat wing', 'slug butter', 'snake dandruff', 'bear burp', 'mandrake', 'hemlock', 'swamp gas']
Bà phù thủy này chắc chắn đang âm mưu gì đó!
Chuỗi, mảng, tuple và map
33
XÓA PHẦN TỬ TRONG MẢNG Để bỏ một phần tử ra khỏi mảng, dùng lệnh del (viết tắt của từ xóa⟨ delete⟩ ). Ví dụ, để bỏ phần tử số sáu ra khỏi danh sách đồ phù thủy kia, là vẩy rắn, thì làm thế này: >>> del wizard_list[5] >>> print(wizard_list) ['spider legs', 'toe of frog', 'snail tongue', 'bat wing', 'slug butter', 'bear burp', 'mandrake', 'hemlock', 'swamp gas']
C H Ú Ý Đừngquênlàcácvịtríđềubắtđầutừ0,nênwizard_list[5] thựcralàphầntửthứsáu trongdanhsách. Để bỏ đi những thứ ta vừa mới thêm vào lúc nãy: >>> del wizard_list[8] >>> del wizard_list[7] >>> del wizard_list[6] >>> print(wizard_list) ['spider legs', 'toe of frog', 'snail tongue', 'bat wing', 'slug butter', 'bear burp']
CÁC PHÉP TOÁN VỚI MẢ NG Ta có thể ghép các mảng vào với nhau bằng dấu cộng ( + ), giống như cộng số ấy. Giả sử ta có hai mảng: list1 chứa từ số 1 đến số 4 và list2 chứ a vài từ. Ta có thể ghép chúng lại với nhau bằng dấu + và in ra bằng print như sau: >>> list1 = [1, 2, 3, 4] >>> list2 = ['I', 'tripped', 'over', 'and', 'hit', 'the', 'floor'] >>> print(list1 + list2) [1, 2, 3, 4, 'I', 'tripped', 'over', 'and', 'hit', 'the', 'floor']
Ta cũng có thể ghép hai mảng vào rồi gán kết quả cho một biến khác.
34
Chương 3
>>> list1 = [1, 2, 3, 4] >>> list2 = ['I', 'ate', 'chocolate', 'and', 'I', 'want', 'more'] >>> list3 = list1 + list2 >>> print(list3) [1, 2, 3, 4, 'I', 'ate', 'chocolate', 'and', 'I', 'want', 'more']
T a c ũ ng c ó thể n h â n m ả n g với m ộ t số. V í d ụ n hâ n list1 với 5 ta viết là list1 * 5 : >>> list1 = [1, 2] >>> print(list1 * 5) [1, 2, 1, 2, 1, 2, 1, 2, 1, 2]
T h ự c r a là ta v ừ a b ả o P y t h o n l ặ p lại list1 n ă m lần, k ế t q u ả là ta c ó 1, 2, 1, 2, 1, 2, 1, 2, 1, 2.
Nhưng mặt khác, phép chia ( /) và phép trừ ( -) lại không như thế, lỗi luôn, ví dụ: >>> list1 / 20 Traceback (most recent call last): File "", line 1, in list1 / 20 TypeError: unsupported operand type(s) for /: 'list' and 'int' >>> list1 - 20 Traceback (most recent call last): File "", line 1, in list1 - 20 TypeError: unsupported operand type(s) for -: 'list' and 'int'
Nhưng, tại sao lại thế? Thế này, ghép các mảng với nhau bằng dấu + hay lặp lại nó bằng dấu * nghe cũng khá hợp lý đúng không. Nghe hợp lý cả ở ngoài đời ấy chứ. Ví dụ, nếu ai đó đưa em hai tờ danh sách để đi mua hàng và bảo , “Cộng hai danh sách này vào,” e m có thể hiểu và viết tuốt tuồn tuột đúng thứ tự cả hai danh sách này ra một danh sách khác, đúng không nào. Tương tự như thế, nếu bảo, “Danh sách này nhân 3 lên nhé,” em có thể hình dung ra ngay là viết lại danh sách này ba lần ra một danh sách khác. Nhưng e m sẽ chia mảng ra như thế nào? Giả sử e m sẽ làm thế nào để chia một mảng có 6 số (1 đến 6) thành hai phần? Dưới đây mới chỉ là ba cách tạm thôi nhé:
Chuỗi, mảng, tuple và map
35
[1, 2, 3] [1] [1, 2, 3, 4]
[4, 5, 6] [2, 3, 4, 5, 6] [5, 6]
Ta sẽ chia nó ra ở giữa, hay là sau phần tử đầu tiên, hay là chọn bừa một chỗ nào đó rồi chia nó ra? Câu trả lời không hề đơn giản đúng không, và nếu em hỏi Python, nó cũng sẽ không biết đâu. Thế nên nó mới báo lỗi. Câu trả lời cũng tương tự như thế nếu em cộng một cái gì đấy không phải là mảng vào một mảng. Không làm thế được. Ví dụ nếu em thử t h ê m s ố 5 0 v à o list1 : >>> list1 + 50 Traceback (most recent call last): File "", line 1, in list1 + 50 TypeError: can only concatenate list (not "int") to list
Lỗi ở đây là như nào vậy? Được rồi, ý em là sao khi muốn cộng 50 vào một mảng? Cộng 50 vào mỗi phần tử à? Nếu phần tử không phải số thì sao? Hay là muốn cộng số 50 vào đầu mảng hay cuối mảng hay gì? Trong lập trình má y tính, các câu lệnh cần phải chạy y xì như nhau mỗi khi được gọi. Máy tính ngố lắm chỉ có thể hiểu đen với trắng thôi. Bảo nó tự đưa ra quyết định là nó xoè ra lỗi ngay.
TUPLE Một tuplegiống một mảng nhưng lại dùng dấu ngoặc tròn, ví dụ: >>> fibs = (0, 1, 1, 2, 3) >>> print(fibs[3]) 2
Ta vừa mới tạo ra một biến fibs chứa các số 0, 1, 1, 2 và 3. Và cũng như mảng, ta in p h ầ n tử ở vị trí thứ 3 c ủa tuple ra b ằ n g print(fibs[3]) .
36
Chương 3
Cái khác biệt lớn nhất giữa tuple và mảng, là một khi đã tạo ra tuple rồi, e m không thể sửa nó được nữa. Ví dụ, thử thay giá trị đầu tiên của tuple fibs thành 4 (giống như cách m ình thay giá trị của m ả n g wizard_list ), ta sẽ gặ p lỗi ngay: >>> fibs[0] = 4 Traceback (most recent call last): File "", line 1, in fibs[0] = 4 TypeError: 'tuple' object does not support item assignment
Thế sao ta lại phải dùng tuple làm gì? Rất đơn giản, là bởi vì nhiều khi em biết có những thứ không bao giờ thay đổi cả. Nếu e m tạo ra một tuple có hai phần tử thì nó chắc chắn luôn luôn chỉ có hai phần tử.
PYTHON MAPS KHÔNG CHỈ ĐƯỜNG CHO E M ĐÂU
6
Trong Python, một map(hay còn gọi là dict, viết tắt của từđiển ⟨dictionary⟩ ) là một tập hợp của nhiều thứ, tương tự mảng và tuple. Điểm khác biệt ở đây là mỗi phần tử trong map đều có một khóa⟨ key⟩ và tương ứng với nó là một giátrị⟨value⟩. Ví dụ, giả sử em có một danh sách bạn bè và môn thể thao yêu thích của từng người. Ta có thể cho hết danh sách này vào một mảng, tên người đứng trước môn thể thao theo sau, như thế này: >>> favorite_sports = ['Ralph Williams, Football', 'Michael Tippett, Basketball', 'Edward Elgar, Baseball', 'Rebecca Clarke, Netball', 'Ethel Smyth, Badminton', 'Frank Bridge, Rugby']
Hỏi, bạn Rebecca Clarke thích môn gì? E m có thể đọc lướt qua mảng này và tìm ra câu trả lời là netball (bóng lưới). Nhưng nhỡ mảng này mà dài đến 100 người (hoặc nhiều hơn nữa) thì sao?
6
Ý nói Python Maps so với Google Maps ^^ Chuỗi, mảng, tuple và map
37
Nếu ta lưu lại cũng từng ấy thông tin vào trong một map, với tên người là khóa và môn thể thao là giá trị, code Python sẽ trông như thế này: >>> favorite_sports = {'Ralph Williams' : 'Football', 'Michael Tippett' : 'Basketball', 'Edward Elgar' : 'Baseball', 'Rebecca Clarke' : 'Netball', 'Ethel Smyth' : 'Badminton', 'Frank Bridge' : 'Rugby'}
Ta dùng dấu hai chấm để tách khóa với giá trị, và mỗi khóa và giá trị đều phải được bọc bởi dấu ngoặc đơn. Chú ý nữa là toàn bộ các phần tử trong map phải được bọc lại bằng một cặp ngoặc nhọn ( {} ), không phải ngoặc tròn, cũng không phải ngoặc vuông nhé. Kết quả ta có là một mảng đối chiếu (mỗi khóa tương ứng với một giá trị) như Bảng 3-1. Bảng 3-1: Các khóa trỏ đến các giá trị tương ứng trong Môn thể thao yêu thích Khóa
Giá trị
Ralph Williams
Football (Bóng đá)
Michael Tippett
Basketball (Bóng rổ)
Edward Elgar
Baseball (Bóng chày)
Rebecca Clarke
Netball (Bóng lưới)
Ethel Smyth
Badminton (Cầu lông)
Frank Bridge
Rugby (Bóng bầu dục)
Giờ để xem môn thể thao yêu thích của Rebecca Clarke là gì, ta truy cập vào map favorite_sports v à d ù n g tên c ủa c ô l à m khóa, n h ư thế này: >>> print(favorite_sports['Rebecca Clarke']) Netball
Vậy câu trả lời là netball (bóng lưới).
38
Chương 3
Để xóa một giá trị trong map, ta cũng dùng khóa luôn. Ví dụ để xóa Ethel Smyth: >>> del favorite_sports['Ethel Smyth'] >>> print(favorite_sports) {'Rebecca Clarke': 'Netball', 'Michael Tippett': 'Basketball', 'Ralph Williams': 'Football', 'Edward Elgar': 'Baseball', 'Frank Bridge': 'Rugby'}
Để thay đổi giá trị trong map, ta cũng dùng khóa nốt: >>> favorite_sports['Ralph Williams'] = 'Ice Hockey' >>> print(favorite_sports) {'Rebecca Clarke': 'Netball', 'Michael Tippett': 'Basketball', 'Ralph Williams': 'Ice Hockey', 'Edward Elgar': 'Baseball', 'Frank Bridge': 'Rugby'}
T a vừ a m ớ i đổi m ô n thể thao yê u thích c ủa R a l p h Williams từ Football thành Ice Hockey đó.
E m cũng thấy đấy, dùng map cũng đại khái giống dùng mảng và tuple thôi, trừ việc em không thể ghép hai map vào với nhau bằng dấu cộng ( + ) được. Thử đi, em sẽ gặp lỗi này: >>> favorite_sports = {'Rebecca Clarke': 'Netball', 'Michael Tippett': 'Basketball', 'Ralph Williams': 'Ice Hockey', 'Edward Elgar': 'Baseball', 'Frank Bridge': 'Rugby'} >>> favorite_colors = {'Malcolm Warner': 'Pink polka dots', 'James Baxter': 'Orange stripes', 'Sue Lee': 'Purple paisley'} >>> favorite_sports + favorite_colors Traceback (most recent call last): File "", line 1, in TypeError: unsupported operand type(s) for +: 'dict' and 'dict'
Python không hiểu nổi ghép các ma p vào như thế nào đâu, nên nó bắn ra lỗi thôi.
Chuỗi, mảng, tuple và map
39
TÓM TẮT Trong chương này, e m đã hiểu Python dùng chuỗi để lưu dữ liệu dạng chữ như thế nào, và nó dùng mảng và tuple để xử lý nhiều phần tử cùng nhau như thế nào. E m cũng đã thấy các phần tử của mảng có thể thay đổi được, cũng có thể ghép hai mảng vào với nhau, nhưng các phần tử của tuple thì không thế được. E m cũng học được là map lưu các giá trị với các khóa tương ứng như thế nào.
BÀI TẬP LẬP TRÌNH Dưới đây là một vài bài tập để em có thể tự làm. Câu trả lời có thể tìm thấy trên http://python-for-kids.com/ .
# 1: S Ở T H Í C H Hãy lập ra một danh sách các trò chơi yêu thích của em, đưa vào một biến đặt tên là games . Tiếp đó lập ra một danh sách các món ăn yêu thích, đặt tên biến là foods . Ghép hai
danh sách này lại với nhau và tạo ra biến m ới tên là favorites . Cuối cùng in biến favorites ra.
#2: Đ Ế M N G Ư Ờ I Có 25 ninja trốn trong mỗi nóc của 3 tòa nhà và 40 samurai trốn trong mỗi hầm của 2 đường hầm, hỏi tất cả có bao nhiêu người chuẩn bị đánh nhau? (Bài này em có thể giải chỉ bằng một biểu thức trong Python shell.)
#3: C H À O ! Tạo ra 2 biến: một lưu tên và một lưu họ của em. Tạo ra một chuỗi và dùng ký tự đặt c hỗ đ ể in ra c â u c hà o s ử d ụ n g hai bi ến kia, ví d ụ n h ư C h a o xìn ^^ , tôi là B r a n d o Ickett!
40
Chương 3
Rùa⟨ turtle⟩ trong Python cũng đại khái như rùa thật. Ta đều biết rùa là một loài bò sát di chuyển siêuuuu chậ m và luôn ma ng cái mai trên mình. Trong thế giới Python, rùa là một mũi tên đen đen, nhỏ nhỏ, di chuyển chầ m chậ m quanh m à n hình m á y tính. Chưa kể, vì con rùa trong Python khi đi để lại các vết trên màn hình, nên kể ra thì nó giống ốc sên hơn là giống rùa. Rùa là cách rất thú vị để ta có thể tiếp cận với những kiến thức căn bản của đồ hoạ m á y tính, nên chương này ta sẽ dùng rùa trong Python để vẽ một vài hình và đoạn thẳng đơn giản.
SỬ DỤNG MODULE TURTLE TRONG PYTHON Moduletrong Python là một cách để đưa những đoạn code hữu dụng cho những chương trình khác sử dụng (trong module có chứa rất nhiều thứ, ở đây ta chỉ đề cập đến hàm thôi). Module sẽ được nói kỹ hơn trong Chương 7. Python có một module rất đặc biệt tên là turtle m à ta sẽ dùng để học và hiểu cách m á y tính vẽ trên m à n hình như thế nào.
Vẽ vời với rùa
41
M odul e turtle là m ột cách để lập trình đồ họa vector, nói đơn giản là để vẽ ra các điểm, đường thẳng và đường cong. T a sẽ x e m x e m d ù n g turtle n h ư thế nà o nhé. Đầu tiên, bật Python shell lên như hướng dẫn trong Chương 1. Tiếp, yêu cầu Python sử dụng turtle b ằ n g c á c h m a n g m o d u l e turtle và o, n h ư thế
này: >>> import turtle
Mang một module vào nghĩa là em nói Python e m muốn dùng module đó.
CHÚ Ý NếuđangdùngUbuntubảncũhơn14.04vàgặplỗiởđây,emcóthểsẽphảicàitkinter.Mở UbuntuSoftwareCenterravàgõpython-tkvàoôtìmkiếm.EmsẽthấycókếtquảTkinter – Writ ing T k Applications with Python trảvề.N h ấ n Installđ ể cài.
TẠO BẢNG VẼ Sau khi m a n g m odul e turtle vào, việc tiếp theo ta phải làm là tạo ra bảngvẽ ⟨ canvas⟩ – một vùng trắng tinh để vẽ, giống như bảng vẽ của họa sĩ ấy. Bảng vẽ sẽ tự động được m ở ra khi ta sẽ gọi hàm Pen từ module turtle (hàm là gì từ từ ta sẽ học nhé). Nhập dòng sau vào Python shell: >>> t = turtle.Pen()
42
Chương 4
E m sẽ thấy một cửa sổ trắng (bảng vẽ của mình đấy), với một cái mũi tên nằm chính giữa, như thế này:
Mũi tên ở giữa này chính là con rùa của chúng ta, đúng là trông không giống rùa tí nào nhỉ.
DI C H U Y ỂN R Ù A E m sẽ điều khiển rùa bằng các hà m có sẵn trong biến t vừa tạo ra bên trên, tương tự như cách ta d ù n g h à m Pen trong m o d u l e turtle vậy. Ví dụ, hàm forward sẽ di chuyển rùa đi tới phía trước. Để rùa di chuyển tiến tới phía trước 50 điểm ảnh, nhập lệnh sau: >>> t.forward(50)
Vẽ vời với rùa
43
E m sẽ thấy nó di chuyển như thế này:
Như vậy là con rùa đã di chuyển được 50 điểm ảnh. Mỗi điểmảnh⟨ pixel⟩ là một điểm ở trên m à n hình – là đơn vị nhỏ nhất trên m à n hình. Tất cả mọi thứ e m nhìn thấy trên màn hình máy tính đều được sinh ra từ các điểm ảnh, là các hình vuông bé xíu xíu. Nế u có phóng to thật to bảng vẽ và đoạn thẳng được rùa vừa mới vẽ ra, e m sẽ thấy cái mũi tên đại diện cho đường đi của rùa thực chất chỉ là một loạt các điểm ảnh m à thôi. Đây chính là điểm căn bản trong đồ họa má y tính đấy.
44
Chương 4
Giờ ta sẽ bảo rùa xoay sang trái 90 độ bằng lệnh sau: >>> t.left(90)
Nếu vẫn chưa được học về độ góc, em có thể hình dung như thế này. Tưởng tượng e m đang đứng ở chính giữa một hình tròn. ●
Góc em đang hướng thẳng mặt đến sẽ là 0 độ.
●
Nếu giơ tay trái sang ngang, đó sẽ là 90 độ bên trái.
●
Nếu giơ tay phải sang ngang, đó sẽ là 90 độ bên phải. E m có thể thấy các góc 90 độ trái và phải trên hình sau:
Nếu cứ liên tục xoay quanh tâm hình tròn như thế sang phía bên phải, 180 độ sẽ là ngay sau lưng em, 270 độ chính là chỗ bên tay trái, còn 360 độ chính là nơi em bắt đầu; các giá trị của độ đi từ 0 đến 360. Các góc cách nhau 45 độ trong một hình tròn nếu ta xoay từ bên phải sẽ trông như thế này:
Vẽ vời với rùa
45
Khi bảo rùa xoay sang trái, nó sẽ xoay mặt sang hướng mới (cũng như khi em xoay người để hướng mặt về phía 90 độ bên trái). C â u lệnh Pyt hon t.left(90) sẽ xoa y m ũ i tên lên trên (vì lúc bắt đầ u n ó qua y m ặ t sang phía bên phải mà):
C H Ú Ý Lệnht.left(90)tươngđươngvớit.right(270).Cũngnhưt.right(90) tươngđươngvới t.left(270) .Cứtưởngtượngrahìnhtrònbêntrênrồixoaytheocácsốđộrồiemsẽquen thôi. Giờ ta sẽ thử vẽ một hình vuông nhé. Gõ mấy dòng code sau nối tiếp vào đoạn code trước: >>> t.forward(50) >>> t.left(90)
46
Chương 4
>>> t.forward(50) >>> t.left(90) >>> t.forward(50) >>> t.left(90)
Rùa lúc này đã vẽ được một hình vuông và quay trở về vị trí ban đầu:
Để xóa trắng hết bảng vẽ ta dùng lệnh reset . H à m này sẽ tẩy toàn bộ bảng và đưa rùa về vị trí ban đầu. >>> t.reset()
E m cũng c ó thể dùng h à m clear , nhưng h à m này chỉ xóa mỗi m à n hình thôi, còn rùa vẫn ở nguyên vị trí hiện tại. >>> t.clear()
Vẽ vời với rùa
47
Ta cũng có thể xoay rùa sang phải bằng hà m right hoặc cho nó đi lùi bằng hà m backward . Hàm up sẽ “nhấc bút lên khỏi giấy” (tức là rùa sẽ dừng không vẽ khi di chuyển
nữa), và hàm down sẽ cho phép ta bắt đầu vẽ lại. Các hàm này được dùng tương tự như bên trên. Ta sẽ vẽ thử cái gì khác dùng mấy hàm này xem sao nhé. Lần này ta sẽ điều khiển rùa để vẽ hai đoạn thẳng. Nhập đoạn code sau: >>> t.reset() >>> t.backward(100) >>> t.up() >>> t.right(90) >>> t.forward(20) >>> t.left(90) >>> t.down() >>> t.forward(100)
Việc đầu tiên ta cần làm là làm sạch bảng và đưa rùa về vị trí ban đầu bằng lệnh t.reset() . T i ế p, l ệ n h t . b a c k w a r d ( 1 0 0 ) l ù i r ù a
ngược về 100 điểm ảnh, rồi lệnh t.up() để nhấc bút dừng vẽ. Tiếp, lệ nh t.right(90) x o a y r ùa 9 0 đ ộ sang bên phải, hướng về phía dưới của mà n hình rồi đi tới 2 0 đi ểm ả nh với t.forward(20) . Do hàm up được sử dụng trước đó ở dòng thứ ba nên hiện tại ta không vẽ gì ra m à n hình cả. X oa y rùa sang trái 90 độ để nó hư ớng sang bên phải bằng t.left(90) , sau đó đặt bút xuống bắt đầu vẽ lại với hàm down . Cuối cùng ta đã vẽ một đoạn thẳng, song song với đoạn thẳng
48
Chương 4
được vẽ lúc nãy, bằng lệnh t.forward(100) . Hai đoạn thẳng song song trông sẽ như thế này:
TÓM TẮT Trong chương này, e m đã thấy sơ qua cách dùng module turtle . Ta đã cùng nhau vẽ vài đoạn thẳng đơ n giản dùng các h à m xoay sang trái left , xoay sang phải right , đi tới forward và đi lùi backward . Em cũng đã thấy hàm up là để dừng vẽ và hàm down là để quay
lại vẽ tiếp. Và ta cũng đã học thêm một chút về xoay rùa theo góc độ nữa.
BÀI TẬP LẬP TRÌNH Hãy dùng rùa vẽ thử vài hình đơn giản nhé. Câu trả lời có thể tìm thấy trên http://python-for-kids.com/ .
Vẽ vời với rùa
49
#1: H Ì N H C H Ữ N H Ậ T Tạo bảng vẽ m ới dùng h à m Pen trong module turtle rồi vẽ lên đó một hình chữ nhật.
#2: H Ì N H T A M G I Á C Tạo một bảng vẽ khác, lần này vẽ hình tam giác. X e m lại m ô hình hình tròn với các số độ trên đó (phần “Di chuyển rùa” trang 43) để nhớ lại rùa có thể xoay theo góc như thế nào.
#3: H Ộ P M À K H Ô N G C Ó G Ó C Viết một chương trình để vẽ ra bốn đoạn thẳng như hình dưới đây (kích thước bao nhiêu không quan trọng, đúng hình là được):
50
Chương 4
Trong lập trình, ta thường phải đối diện với rất nhiều câu hỏi có hay không, rồi tùy vào câu trả lời mà quyết định sẽ làm hay không làm gì đó. Ví dụ ta có thể hỏi “Anh có hơn 20 tuổi không thế?” và nếu câu trả lời là có, thì ta nói “Già quá!” Những câu hỏi kiểu này được gọi là điềukiện⟨condition⟩ , ta kết hợp các điều kiện này và các câu trả lời lại với nhau thành một lệnh if ⟨ ifstatement⟩ . Các điều kiện có thể rất phức tạp chứ không chỉ là một câu hỏi đơn giản, và lệnh if cũng có thể được kết hợp với nhiều câu hỏi khác nhau để đưa ra các phản ứng khác nhau tùy vào mỗi câu trả lời. Chư ơng này ta sẽ học cách viết ra chương trình m á y tính với các lệnh if như thế.
Đ ặ t c â u hỏi với if và else 51
L Ệ N H IF M ột lệnh if trong Python được viết như thế này: >>> age = 13 >>> if age > 20: print('You are too old!')
Lệnh if được viết bằng từ khóa if , theo sau đó là điều kiện và d ấ u hai c h ấ m ( : ), n h ư tr ong if a g e > 20: . N h ữ n g dòng n ằ m sau dấu
hai chấm phải được đặt trong một khối lệnh, và nếu câu trả lời cho câu hỏi trên là có (hoặc là True, nói theo đúng kiểu lập trình Python), các lệnh trong khối lệnh sẽ được chạy. Giờ ta sẽ nghiên cứu xem viết các khối lệnh và điều kiện như thế nào nhé.
K H Ố I L Ệ N H L À M Ộ T K H Ố I C Á C L Ệ N H ^^ Khốilệnh⟨ block⟩ là một khối các câu lệnh lập trình. Ví dụ nếu điều kiện if age > 20: thỏa mãn, e m hẳn là sẽ m u ố n làm gì đó nhiều hơn là chỉ in ra mỗi câu Y o u are too old! (Già quá!) E m có thể sẽ muốn nói thêm cái gì đó ngầu hơn tí: >>> age = 25 >>> if age > 20: print('You are too old!') print('Why are you here?') print('Why aren\'t you mowing a lawn or sorting papers?')
52
Chương 5
Khối lệnh này được tạo ra bởi ba lệnh print và chỉ được chạy khi điều kiện age > 20 được thỏa mãn. Mỗi dòng trong khối lệnh đều có bốn dấu cách ở đầu dòng so với lệnh if
bên trên. Cùng xem lại đoạn code bên trên nhưng các dấu cách được hiển thị lên nhé: >>> age = 25 >>> if age > 20: ▯▯▯▯print('You are too old!') ▯▯▯▯print('Why are you here?') ▯▯▯▯print('Why aren\'t you mowing a lawn or sorting papers?')
Trong Python, các khoảngtrắng⟨ whitespace⟩ như dấu tab (khi em ấn nút TAB) hoặc dấu cách (khi em ấn nút cách) cực kỳ quan trọng. Các đoạn code cùng hàng (được lùi vào cùng số khoảng trắng tính từ lề bên trái) sẽ được nhóm vào thành một khối lệnh, và nếu dòng code mới nào có nhiều khoảng trắng hơn dòng trước đó thì đó sẽ là bắt đầu của một khối lệnh mới nằm trong khối lệnh trước, như thế này:
Ta nhóm các câu lệnh này với nhau thành các khối lệnh vì chúng có liên quan đến nhau. Những câu lệnh này sẽ được chạy cùng nhau. Mỗi khi thay đổi vị trí lùi đầu dòng này, về cơ bản là em đang tạo ra các khối lệnh mới. Trong ví dụ sau em sẽ thấy ba khối lệnh riêng biệt được tạo ra chỉ bằng cách lùi đầu dòng như thế:
Đ ặ t c â u hỏi với if v à else
53
Tuy khối 2 và khối 3 lùi đầu dòng cùng vị trí, nhưng chúng lại là hai khối hoàn toàn tách biệt vì có một khối khác lùi đầu dòng ít hơn (ít khoảng trắng hơn) chen vào giữa. Một điều quan trọng khác, chạy một khối lệnh có bốn khoảng trắng và một khối khác tiếp đó có sáu khoảng trắng sẽ sinh ra lỗilùiđầudòng⟨ indentationerror⟩ , vì Python cần ta dùng một chuẩn khoảng trắng giống nhau trên toàn bộ chương trình. Nê n một khi e m đã dùng bốn khoảng trắng để bắt đầu một khối lệnh thì em phải luôn luôn dùng bốn khoảng trắng. Ví dụ: >>> if age > 20: ▯▯▯▯print('You are too old!') ▯▯▯▯▯▯print('Why are you here?')
Các khoảng trắng ở đây được hiển thị lên để em có thấy thấy sự khác biệt. Dòng thứ ba có sáu khoảng trắng trong khi nhẽ ra chỉ được phép có bốn. Khi chạy đoạn code này, IDLE sẽ bôi đỏ phần nó thấy có vấn đề và hiển thị thêm một thông báo lỗi SyntaxError để giải thích: >>> age = 25 >>> if age > 20: print('You are too old!') print('Why are you here?') SyntaxError: unexpected indent
Python đã nhận ra hai khoảng trắng thừa ở đầu lệnh print thứ hai. 54
Chương 5
CHÚ Ý Sửdụngmộtchuẩnkhoảngtrắngnhấtđịnhsẽlàmcodedễđọchơn.Nếuemđãdùngbốn khoảngtrắngngaykhibắtđầuthìhãysửdụngbốnkhoảngtrắngởtấtcảcáckhốilệnh kháctrongchươngtrình.Đồngthờiphảiđảmbảonếuđãcùngkhốilệnhthìphảilùiđầu dònggiốngnhau.
ĐIỀU KIỆN LÀ ĐỂ SO SÁNH MỌI THỨ VỚI NH AU Mỗi điềukiện⟨ condition⟩ là một biểu thức lập trình so sánh các thứ với nhau và nói ta các kết quả so sánh là đúng ( True ) hay sai (False ). Ví dụ age > 10 là một điều kiện, có thể diễn đạt lại là “Có phải giá trị của biến age lớn hơn 10 không?” Tương tự, hair_color == 'mauve' là một điều kiện khác, nghĩa là “ Có phải giá trị của biến hair_color là m à u tím
không?” Ta dùng các ký hiệu trong Python (còn gọi là toántử⟨ operator⟩ ) để viết ra các điều kiện như bằng, lớn hơn, nhỏ hơn. Bảng 5-1 liệt kê ra vài toán tử như thế: Bảng 5-1: Các toán tử điều kiện Toán tử
Ý nghĩa
==
Bằng
!=
Khác
>
Lớn hơn
=
Lớn hơn hoặc bằng
10 sẽ là True .
CHÚ Ý Nhớlàtoántửđiềukiệnbằnglàcóhaidấubằng( ==) nhé. Đ ặ t c â u hỏi với if và else
55
Hãy cùng thử vài ví dụ khác. Ta sẽ đặt ra mốc tuổi ở đây là 10 và viết ra câu You are t oo o l d f or m y jokes! n ế u ai đ ó l ớ n h ơ n 1 0 tuổi nh é : >>> age = 10 >>> if age > 10: print('You are too old for my jokes!')
Nếu chạy y nguyên đoạn code này trên IDLE thì chuyện gì sẽ xảy ra? Không có gì luôn. Đó là vì biến age của chúng ta không lớn hơn 10, nên Python sẽ không chạy khối lệnh print . Như ng nếu biến age là 20 thì câu kia sẽ được in ra ngay. Giờ thử sửa ví dụ trên thành điều kiện lớn-hơn-hoặc-bằng ( >= ) xem sao nhé: >>> age = 10 >>> if age >= 10: print('You are too old for my jokes!')
T r ê n m à n hì n h c ủ a e m s ẽ c ó d ò n g c h ữ Y o u a r e t o o o l d f or m y jokes! vì gi á trị của age chính xác bằng 10. Tiếp, ta sẽ thử điều kiện bằng ( == ): >>> age = 10 >>> if age == 10: print('What\'s brown and sticky? A stick!!')
M à n h ì n h l ú c s ẽ hi ệ n ra d ò n g c h ữ W h a t ’ s b r o w n a n d s ti ck y? A stick!! .
56
Chương 5
LỆNH IF-THEN-ELSE Ngoài việc sử dụng lệnh if để thực hiện việc gì đó khi điều kiện được thỏa mã n (True ), ta cũng có thể dùng if để làm việc gì đó khi điều kiện không được thỏa mãn. Giả sử ta có thể in ra màn hình một câu gì đó nếu e m 12 tuổi và một câu khác nếu e m không phải 12 tuổi ( False ). M ẹ o ở đây là ta phải dùng lệnh if-then-else , về cơ bản có nghĩa là “Nế u cái gì đó là đúng, thì làm cái này; nếukhông, thì làm cái kia.” T a sẽ thử viết lệnh if-then-else n à y nhé, g õ d ò n g sau v à o shell: >>> print("Want to hear a dirty joke?") Want to hear a dirty joke? >>> age = 12 >>> if age == 12: print("A pig fell in the mud!") else: print("Shh. It's a secret.") A pig fell in the mud!
Vì đã đặt biến age là 12 và điều kiện là xem age có bằng 12 không, nên ta sẽ nhận được lệnh print đầu tiên. Th ử đổi age thành một số nào đó khác 12 xem: >>> print("Want to hear a dirty joke?") Want to hear a dirty joke? >>> age = 8 >>> if age == 12: print("A pig fell in the mud!") else: print("Shh. It's a secret.") Shh. It's a secret.
Lầ n này e m sẽ nhìn thấy lệnh print thứ hai.
Đ ặ t c â u hỏi với if v à else
57
L Ệ N H IF V À ELIF Ta còn có thể m ở rộng lệnh if hơn nữa với elif (là viết gọn của hoặcnếu⟨ else-if⟩ ). Ví dụ, ta có thể kiểm tra một ai đó xem có phải 10, 11 hoặc 12 tuổi không (hoặc hơn nữa), và chương trình sẽ làm gì đó khang khác tùy câu trả lời. Lệnh lần này hơi khác so với if-thenelse ở c h ỗ e m m u ố n d ù n g b a o nhi ê u elif ở đ â y c ũ n g đư ợ c : >>> age = 12 >>> if age == 10: print("What do you call an unhappy cranberry?") print("A blueberry!") elif age == 11: print("What did the green grape say to the blue grape?") print("Breathe! Breathe!") elif age == 12: print("What did 0 say to 8?") print("Hi guys!") elif age == 13: print("Why wasn't 10 afraid of 7?") print("Because rather than eating 9, 7 8 pi.") else: print("Huh?") What did 0 say to 8? Hi guys!
Trong ví dụ này, lệnh if ở dòng thứ 2 kiểm tra xem giá trị của age có bằng 10 không ở ❶ . Các lệnh print sau đó ở ❷ sẽ chỉ chạy nếu age bằng 10. Tuy nhiên vì ta đặt age bằng 12 nên má y tính sẽ nhảy sang lệnh if tiếp theo ở ❸ và kiểm tra xem age có bằng 11 không. Rõ là không, nên má y tính sẽ lại nhảy sang lệnh if tiếp theo nữa ở ❹ để xe m age có bằng 12 không. Lần này thì đúng rồi nha, nên má y sẽ chạy mấ y lệnh print ở ❺ . Nếu gõ đoạn code này trên IDLE, chương trình sẽ tự động lùi đầu dòng cho em, nên em phải dùng các phím BACKSPACE hoặc DELETE mỗi khi gõ xong lệnh print , để cho các lệnh if , elif , else c ủa e m bắt đầ u từ sát phía bê n trái. Đ â y thực ra chính là vị trí c ủa lệnh if nếu không có dấu nhắc ( >>> ).
58
Chương 5
KẾT HỢP CÁC ĐIỀU KIỆN E m có thể kết hợp nhiều điều kiện lại với nhau bằng từ khóa and và or , code sẽ ngắn gọn và đơn giản hơn. Đây là ví dụ dùng or : >>> if age == 10 or age == 11 or age == 12 or age == 13: print('What is 13 + 49 + 84 + 155 + 97? A headache!') else: print('Huh?')
Trong đoạn code này, nếu bất cứ điều kiện nào trong dòng đầu thỏa mãn (tức là nếu age là 10, 11, 12 hoặc13), khối lệnh bên trong với lệnh print sẽ được chạy.
Nế u tất cả các điều kiện bên trên không được thỏa m ã n ( else ), Python sẽ nhảy sang khối lệnh ở dòng cuối cùng, màn hình sẽ in ra Huh? . Để bóp ví dụ này gọn hơn nữa, ta có thể dùng từ khóa and cùng với toán tử so sánh lớn-hơn-hoặc-bằng ( >= ) và nhỏ-hơn-hoặc-bằng ( >> if age >= 10 and age = 10 and age >> myval = None >>> print(myval) None
Gán None cho biến là một cách để đưa biến về trạng thái nguyên thuỷ của nó, rỗng tuếch ^^. Gán None còn là cách để tạo ra biến mà không cần giá trị. E m có thể làm thế khi biết chắc chắn về sau mình sẽ cần dùng đến biến này, nhưng muốn đặt nó lên trên đầu của chương trình. Lập trình viên thường làm thế để có thể nhìn được tên của tất cả các biến được dùng bởi một đống code sau đó. E m cũng có thể kiểm tra None trong lệnh if như ví dụ sau: >>> myval = None >>> if myval == None: print("The variable myval doesn't have a value") The variable myval doesn't have a value
Việc này rất hữu ích khi e m chỉ muốn bắt đầu tính toán nếu biến chưa được tính trước đó.
SỰ KHÁC BIỆT GIỮA CHUỖI VÀ SỐ D ữ liệutừngườidùng⟨ userinput⟩ là những gì một người nhập vào từ bàn phím – bất kể là một ký tự, phím mũi tên hay phím ENTER , hay bất cứ phím nào khác. Python nhận tất cả dữ liệu từ người dùng dưới dạng chuỗi, nghĩa là khi em gõ số 10 trên bàn phím, Python lưu số 10 đó vào một chuỗi, chứ không phải số. Thế số 10 thì khác gì chuỗi '10' ? Với mắt người thì trông cũng đại khái như nhau, khác mỗi hai cái dấu ngoặc. Như ng với m á y tính thì đây là hai thứ hoàn toàn khác nhau đấy.
60
Chương 5
Giả sử trong lệnh if sau ta phải so sánh giá trị của biến age với một số: >>> if age == 10: print("What's the best way to speak to a monster?") print("From as far away as possible!")
Lúc này nếu ta cho giá trị của age là số 10: >>> age = 10 >>> if age == 10: print("What's the best way to speak to a monster?") print("From as far away as possible!") What's the best way to speak to a monster? From as far away as possible!
Đúng như ta mong đợi, các lệnh print được chạy hết. Tiếp, nếu ta cho giá trị của age là chữ '10' (có ngoặc nhé): >>> age = '10' >>> if age == 10: print("What's the best way to speak to a monster?") print("From as far away as possible!")
Lúc này m ấ y lệnh print không còn chạy nữ a vì Python không coi số ở trong ngoặc (một chuỗi) là số. Nhưng không sao, Python có mấy hàm rất kỳ diệu có thể chuyển số thành chuỗi và chuỗi thành số. Ví dụ em có thể đổi c hu ỗi '10' t hà nh s ố b ằ n g h à m int : >>> age = '10' >>> converted_age = int(age)
Biến converted_age giờ đã mang giá trị là số 10. Để chuyển một số thành chuỗi, dùng hàm str : >>> age = 10 >>> converted_age = str(age) Đ ặ t c â u hỏi với if v à else
61
Lúc này converted_age sẽ giữ giá trị là chuỗi '10' thay vì số 10. Còn nhớ lúc nãy if age = = 10 không in ra gì khi biến age là chuỗi không ( age = '10' )? Nếu ta đổi kiểu của biến trước khi so sánh, kết quả sẽ hoàn toàn khác: >>> age = '10' >>> converted_age = int(age) >>> if converted_age == 10: print("What's the best way to speak to a monster?") print("From as far away as possible!") What's the best way to speak to a monster? From as far away as possible!
Nhưng phải nhớ một điều: Nếu chuyển một số thập phân em sẽ gặp lỗi ngay, vì hàm int cầ n m ột số nguyên cơ. >>> age = '10.5' >>> converted_age = int(age) Traceback (most recent call last): File "", line 1, in converted_age = int(age) ValueError: invalid literal for int() with base 10: '10.5' ValueError là cách Python nói giá trị em đang dùng có chỗ nào đó không đúng. Để
s ử a c hỗ n à y ta sẽ d ù n g h à m float thay c h o h à m int . H à m float c ó thể x ử lý n h ữ n g s ố không phải số nguyên. >>> age = '10.5' >>> converted_age = float(age) >>> print(converted_age) 10.5
62
Chương 5
Và đương nhiên em cũng sẽ nhận được ValueError nếu cố đổi một chuỗi không có chứa số nào: >>> age = 'ten' >>> converted_age = int(age) Traceback (most recent call last): File "", line 1, in converted_age = int(age) ValueError: invalid literal for int() with base 10: 'ten'
TÓM TẮT Trong chương này, e m đã học cách làm việc với các lệnh if để tạo ra các khối lệnh chỉ được chạy khi những điều kiện cụ thể được thỏa mãn. E m cũng đã thấy để m ở rộng lệnh if ra ta có thể dùng elif để m ỗi phần nhỏ c ủa code sẽ đư ợc chạ y tùy theo kết quả của từng điều kiện, và dùng else để chạy đoạn code trong trường hợp không có điều kiện nào được thỏa mãn. E m cũng học được cách kết hợp các điều kiện lại với nhau bằng từ khóa and và or để xem nếu một số rơi vào một khoảng nào đó, và học làm thế nào để chuyển
chuỗi thành số bằng các h à m int , str , float . E m giờ cũng đã hiểu rằng không-có-gì ( None ) không hẳn là không có gì với Python, nó có thể được dùng để đưa biến về trạng thái ban đầu nguyên thuỷ của nó.
BÀI TẬP LẬP TRÌNH Giải mấy câu đố sau dùng các lệnh if và các điều kiện. Câu trả lời có thể tìm thấy trên http://python-for-kids.com/ .
Đ ặ t c â u hỏi với if và else
63
#1: M Ì N H C Ó G I À U K H Ô N G T A ? Theo em đoạn code sau sẽ làm gì? Thử nghĩ ra câu trả lời trước khi gõ vào trong shell nhé, sau đó đối chiếu xem: >>> money = 2000 >>> if money > 1000: print("I'm rich!!") else: print("I'm not rich!!") print("But I might be later...")
#2: B Á N H H H H H ! Viết câu lệnh if để kiểm tra xe m số bánh (trong biến twinkies ) có ít hơn 100 hay nhiều hơn 500 không. Nếu có thì in ra câu sau Too few or too many .
# 3: V Ừ A Đ Ú N G Viết câu lệnh if để kiểm tra xem số tiền em đang có nằm ở khoảng giữa 100 với 500 hoặc1.000 với 5.000 không.
#4: C H I Ế N Đ Ấ U V Ớ I N I N J A Viết câu lệnh if và in ra câ u That’s too man y nế u biến ninjas c hứ a số bé h ơn 50, câu It’ll be a struggle, but I can take ’em nếu ít hơn 30, và câu I can fight those ninjas! nếu ít hơn 10. Bắt đầu với: >>> ninjas = 5
64
Chương 5
Không có gì chán hơn là làm đi làm lại mãi một việc gì đó. Người ta đếm cừu để ngủ chính là vì nó chán quá là chán, chứ không có liên quan gì đến khả năng đưa-người-vàogiấc-ngủ của những e m cừu lông vừa m ề m vừa ấm. Vì làm đi làm lại làm mãi một việc không có hồi kết là thứ chán nhất trên đời, e m có thể lăn ra ngủ lúc nào không biết, và tâm trí em sẽ không thể nào tập trung được cho việc gì có ý nghĩa hơn. Lập trình viên cũng vậy, đặc biệt không thích lặp đi lặp lại, trừ khi họ muốn đi ngủ. Thế cho nên trong hầu hết các ngôn ngữ lập trình đều có một cái gọi là vònglặp⟨ loop⟩ for , là thứ tự động chạy đi chạy lại những câu lệnh lập trình giống nhau. Chương này ta sẽ học về vòng lặp for và một loại khác Python cung cấp: vòng lặp while .
Cùng đi lòng vòng
65
VÒNG LẶP FOR Đ ể chà o hello n ă m lần trong Python, e m c ó thểl à m n hư sau: >>> print("hello") hello >>> print("hello") hello >>> print("hello") hello >>> print("hello") hello >>> print("hello") hello
Quá chán luôn. Tuy nhiên ta có thể dùng vòng for để giảm thiểu việc gõ đi gõ lại như này: >>> for x in range(0, 5): print('hello') hello hello hello hello hello
Hà m range ở ❶ được dùng để tạo ra một mảng số bắt đầu từ số đầu cho đến sát số cuối. Nghe hơi lẫn lộn tí, nhỉ. Đây, thử kết hợp hàm range với hàm list để xem chính xác nó chạy như nào nhé. H à m range thực ra không tạo ra một mả ng số đâu; nó chỉ tạo ra một conchạy⟨ iterator⟩ thôi, là một kiểu đối tượng đặc biệt trong Python chuyên dùng cho vòng lặp. Kết hợp cả range với list lại, ta sẽ có một mảng. >>> print(list(range(10, 20))) [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
66
Chương 6
Quay lại vòng lặp bên trên, code ở ❶ thực ra sẽ làm những việc sau: ●
Bắt đầu đếm từ 0 và dừng lại trước khi đếm đến 5.
●
Với mỗi số đếm được, ghi số đó vào biến x. Sau đó Python sẽ chạy khối lệnh ❷ . Nhớ là có bốn khoảng trắng bị thừa ra ở đầu
dòng ❷ (so với dòng ❶ ) đấy nhé. Tại IDLE tự lùi đầu dòng cho em đấy. Khi nhấn EN TE R ở sau dòng thứ hai, Python sẽ in ra hello năm lần. Ta cũng có thể dùng biến x bên trong lệnh print để đếm số lần chào: >>> for x in range(0, 5): print('hello %s' % x) hello 0 hello 1 hello 2 hello 3 hello 4
Nếu bỏ vòng for ra, code của ta sẽ trông như thế này: >>> x = 0 >>> print('hello %s' % x) hello 0 >>> x = 1 >>> print('hello %s' % x) hello 1 >>> x = 2 >>> print('hello %s' % x) hello 2 >>> x = 3 >>> print('hello %s' % x) hello 3 >>> x = 4 >>> print('hello %s' % x) hello 4 Cùng đi lòng vòng
67
Như vậy dùng vòng lặp đã giúp ta tiếp kiệm được tận tám dòng code không cần thiết. Lập trình viên giỏi là người ghét phải làm đi làm lại những việc giống nhau, cho nên vòng for là một trong những câu lệnh hay được sử dụng nhất trong giới lập trình. Đ ể dùng vòng for , e m không nhất thiết phải dùng các h à m range và list . Ta có thể dùng ngay các mả ng có sẵn, như danh sách mua sắm kì quặc ở Chương 3 chẳng hạn: >>> wizard_list = ['spider legs', 'toe of frog', 'snail tongue', 'bat wing', 'slug butter', 'bear burp'] >>> for i in wizard_list: print(i) spider legs toe of frog snail tongue bat wing slug butter bear burp
Đoạn code này có thể được diễn tả là, “Đối với từng phần tử trong wizard_list , ghi lại giá trị vào biến i, sau đó in ra thông tin của biến đó.”
Và một lần nữa, nếu không có vòng for e m sẽ phải làm như này: >>> wizard_list = ['spider legs', 'toe of frog', 'snail tongue', 'bat wing', 'slug butter', 'bear burp'] >>> print(wizard_list[0]) spider legs >>> print(wizard_list[1]) toe of frog >>> print(wizard_list[2]) snail tongue >>> print(wizard_list[3]) bat wing >>> print(wizard_list[4]) slug butter >>> print(wizard_list[5]) bear burp
68
Chương 6
Và thêm một lần nữa, e m có thấy vòng lặp giúp tiết kiệm được khối thời gian gõ bàn phím không? Thử một vòng lặp nữa nhé. Nhập đoạn code sau, shell sẽ tự động lùi đầu dòng cho em: >>> hugehairypants = ['huge', 'hairy', 'pants'] >>> for i in hugehairypants: print(i) print(i) huge huge hairy hairy pants pants
Ở d òn g đầ u tiên ❶ , ta tạo ta m ột m ả n g c hứ a 'huge' , 'hairy' v à 'pants' . Dòng tiếp theo ❷ , ta lặp qua các phần tử của mảng, mỗi phần tử sẽ
được ghi tạm vào biến i. Ta in nội dung của biến ra hai lần ở hai dòng tiếp theo (❸ và ❹ ). Nhấn ENTER ở dòng tiếp theo ❺ để bảo Python kết thúc khối lệnh, thế là nó sẽ chạy toàn bộ đoạn code và in ra mỗi phần tử hai lần ở ❻. Nhớ là mỗi khi nhập vào số khoảng trắng không đúng, em sẽ gặp lỗi ngay. Nếu nhỡ đánh thừa một dấu cách ở dòng thứ tư ❹ chẳng hạn, Python sẽ trả ra lỗi lùi đầu dòng: >>> hugehairypants = ['huge', 'hairy', 'pants'] >>> for i in hugehairypants: print(i) print(i) SyntaxError: unexpected indent
Như đã học ở Chương 5, Python muốn số khoảng trắng trong một khối lệnh phải giống nhau. E m muốn dùng bao nhiêu dấu cách cũng được, miễn là em dùng đúng số dấu cách đó ở tất cả mọi nơi (ờ, và chưa kể làm thế thì ta cũng dễ đọc code hơn nữa chứ.)
Cùng đi lòng vòng
69
Vòng for tiếp theo hơi phức tạp hơn tí này, có hai khối lệnh liền: >>> hugehairypants = ['huge', 'hairy', 'pants'] >>> for i in hugehairypants: print(i) for j in hugehairypants: print(j)
Mấ y khối lệnh này cái nào là cái nào vậy ta? Khối đầu tiên là vòng for đầu tiên: hugehairypants = ['huge', 'hairy', 'pants'] for i in hugehairypants: print(i) # for j in hugehairypants: # Day la khoi lenh THU NHAT. print(j) #
Khối lệnh thứ hai là chỉ là duy nhấtmột lệnh print của vòng for thứ hai. hugehairypants = ['huge', 'hairy', 'pants'] for i in hugehairypants: print(i) for j in hugehairypants: pri nt (j)
# Day dong thoi cung la khoi lenh THU HAI.
E m có đoán được là mấy dòng code này chạy như thế nào không? Đầu tiên là mảng hugehairypants được tạo ra ở ❶ , hai dòng tiếp theo đó sẽ lặp qua các phần tử của mảng và in ra từng cái một. Nhưng ở ❷ , nó lại lặp qua mảng này một lần nữa, lần này nó gán từng phần tử vào một biến khác là j , rồi in mỗi phần tử này một lần nữa ở ❸ . Code ở cả ❷ và ❸ đều nằm trọn trong vòng for , nghĩa là cả hai sẽ được chạy cho từng phần tử của mảng, cho đến khi vòng for chạy đến hết mảng. Cho nên sau khi chạy, ta sẽ có huge và theo sau là huge , hairy , pants , rồi hairy và theo sau là huge , hairy , pants , cứ thế cho đến hết.
70
Chương 6
Gõ code vào shell và tự kiểm tra xem nhé: >>> hugehairypants = ['huge', 'hairy', 'pants'] >>> for i in hugehairypants: print(i) for j in hugehairypants: print(j) huge huge hairy pants hairy huge hairy pants pants huge hairy pants
Python bắt đầu chạy ở vòng lặp thứ nhất và in ra phần tử đầu tiên của mả ng ở ❶ . Sau đó nó tiến vào vòng lặp thứ hai và in ra hết các phần tử của mảng ở ❷ . Tiếp đến nó lại chạy đến lệnh print(i) , in ra phần tử tiếp theo của mảng, rồi toàn bộ m ả ng lại đư ợc in ra một lần nữa bởi print(j) . Trong phần được in ra, những dòng được đánh dấu ⟐ là được sinh ra bởi lệnh print(i) , n h ữ n g d ò n g c ò n lại đ ư ợ c sinh ra bởi lệnh print(j) . Thôi, đi làm gì khác có ích hơn là in ra mấy cái chữ vô bổ này có được không? Còn nhớ mấy tính toán ta làm ở Chương 2 để tìm ra cuối năm ta có bao nhiêu xu tất cả nếu dùng cái má y siêu photocopy của ông nội đang để dưới hầm không? Đây: >>> 20 + 10 * 365 - 3 * 52
Ta có 20 xu đào được ngoài vườn cộng với 10 xu được sinh ra từ máy nhân với 365
Cùng đi lòng vòng
71
ngày, trừ đi 3 xu mỗi tuần bị con quạ trộm mất. Kể ra m à nhìn được đống vàng này nó đầy lên hàng tuần như thế nào thì thích mắt ra phết ^^. Với vòng lặp for ta có thể làm được đấy, nhưng việc đầu tiên ta sẽ phải làm là đổi giá trị biến magic_coins để giờ nó mang ý nghĩa là tổng số xu được sinh ra trong cả tuần. 10 xu một ngày mà 7 ngày một tuần, vậy magic_coins giờ sẽ là 70: >>> found_coins = 20 >>> magic_coins = 70 >>> stolen_coins = 3
Giờ ta có thể nhìn thấy kho báu đầy lên hàng tuần bằng cách tạo ra một biến mới là coins , và dùng một vòng lặp: >>> found_coins = 20 >>> magic_coins = 70 >>> stolen_coins = 3 >>> coins = found_coins >>> for week in range(1, 53): coins = coins + magic_coins - stolen_coins print('Week %s = %s' % (week, coins))
Ở ❶ , biến coins được cấp cho một giá trị, chính là giá trị của found_coins ; đây là điểm khởi đầu. Dòng tiếp theo ở ❷ bắt đầu cài đặt một vòng for , và các lệnh trong nó (hai dòng ❸ và ❹ ) sẽ được chạy. Mỗi lần lặp, biến week lại bị gán một số trong khoảng từ 1 đến 52. Dòng ❸ hơi phức tạp một chút. Về cơ bản thì là, mỗi tuần ta đều cần cộng thêm số xu được m á y sinh ta và trừ đi số xu bị quạ tha đi mất. Hình dung biến coins như cái hòm đựng xu của em ý. Cứ hàng tuần lại có xu mới được ném vào trong hòm. Cho nên ý nghĩa thực tế của dòng này là “Thay thế giá trị của biến coins bằng số xu hiện tại ta đang có, cộng với số xu mới của tuần này.” Và về cơ bản, dấu bằng ( = ) là một kiểu ra lệnh rất khệnh, “Làm việc gì gì đó bên phải đê, rồi ghi kết quả công việc lại, dùng cái tên mà tôi đưa ở bên tay trái á.” 72
Chương 6
Dòng ❹ là một lệnh print có sử dụng ký tự đặt chỗ, sẽ in ra số tuần và số xu của từng tuần ra màn hình. (Nếu quên, em có thể đọc lại phần “Ghép Giá Trị Vào Trong Chuỗi” ở trang 28.) Rồi, nếu chạy chương trình này em sẽ thấy kết quả như này:
V À NH ÂN TIỆN Đ AN G NÓI VỀ VÒ NG LẶP . . . for không phải là vòng lặp duy nhất trong Python. Còn có vòng while nữa. for là
loại vòng sẽ lặp chính xác bao nhiêu lần đó, trong khi while là loại vòng m à e m không thực sự biết chính xác lúc nào nó hết lặp. Hình dung một cái cầu thang có 20 bậc. Giả sử thang này đặt trong nhà, thế là em biết chắc em có thể trèo lên bằng 20 bước dễ ẹc. Vòng for là như thế đó. >>> for step in range(0, 20): print(step)
Cùng đi lòng vòng
73
Giờ thử tưởng tượng thang này đặt ngoài trời, và đi lên núi. Núi thì cao rõ rồi, em thì đang hết hơi, mãi chưa lên đến đỉnh núi, thời tiết thì xấu dần, chỉ muốn nghỉ luôn cho rồi. Đấy là vòng while : step = 0 while step < 10000: print(step) if tired == True: break elif badweather == True: break else: step = step + 1
Nếu nhập và chạy đống code này, em sẽ gặp lỗi ngay. Sao lại thế? Là vì ta chưa tạo ra các biến tired và badweather . Dù rằng đoạn code này chưa hoàn chỉnh để
chạy được, nhưng nó đã phần nào m ô tả cho ta thấy một ví dụ cơ bản của vòng while . Việc đầu tiên ta phải làm là tạo ra một biến gọi là step với step = 0 . Tiế p đế n là v òn g while đ ể ki ể m tra x e m
giá trị của step có nhỏ hơn 10.000 không ( step < 10000 ), đây là tổng số bước để leo lên đến đỉnh núi. Cứ miễn là step nhỏ hơn 10.000 thì Python sẽ chạy hết đoạn code còn lại. Ta in giá trị hiện tại của biến step ra với print(step) , tiếp đó là ki ểm tra x e m c ơ thể c ó quá m ệt không (biến tired ) với if tired = = True: ( True là giá trị Boolean, ta sẽ học ở Chương 8.) Nếu quá mệt, ta dùng từ khóa break để thoát ra khỏi vòng lặp. Từ khóa break là một trong những cách để ta nhảy ra khỏi vòng lặp (hoặc nói cách khác là dừng vòng lặp lại) ngay lập tức, dùng được với cả vòng while và vòng for . Ở đây tác dụng của nó là làm c h ư ơ n g trình nh ả y ra khỏi khối lệ nh v à đế n câ u lệnh tiếp theo s a u d ò n g step = step + 1 . D ò n g elif b ad weath er = = True: là để ki ể m tra x e m thời tiết có xấu không. N ế u có thì từ khóa break sẽ dừng vòng lặp luôn. Nếu cả tired lẫn badweather đều không phải là
74
Chương 6
True (là trường h ợ p else ), ta sẽ c ộng 1 vào biến step bằ ng step = step + 1 , và vòng lặp lại
tiếp tục chạy. Tóm lại, các bước trong vòng while diễn ra như sau: 1.
Kiểm tra điều kiện.
2.
Chạy code bên trong khối lệnh.
3.
Lặp lại. Đôi khi, có vòng while ma ng nhiều điều kiện hơn như thế này:
>>> x = 45 >>> y = 80 >>> while x < 50 and y < 100: x = x + 1 y = y + 1 print(x, y)
Đầu tiên ta có hai biến x có giá trị 45 ở ❶ và y có giá trị 80 ở ❷ . Vòng lặp sẽ kiểm tra hai điều kiện ở ❸ : x phải nhỏ hơn 50 và y phải nhỏ hơn 100. Nếu cả hai điều kiện đều thỏa mãn, các dòng tiếp theo sẽ được chạy và cả hai biến sẽ được cộng thêm 1 và rồi cả hai sẽ được in ra như sau: 46 47 48 49 50
81 82 83 84 85
E m có đoán ra được nó chạy như thế nào không? Ta bắt đầu đếm x ở 45 và đếm y ở 80, rồi tăng lên (mỗi biến được cộng thêm 1) ở từng lần lặp. Vòng lặp cứ chạy mãi miễn là x nhỏ hơn 50 và y nhỏ hơn 100. Sau khi chạy được năm vòng (mỗi biến được cộng thêm 1 ở mỗi vòng), x sẽ là 50. Lúc này điều kiện đầu tiên ( x < 50 ) không còn đúng nữa, nên Python sẽ dừng vòng lặp lại.
Cùng đi lòng vòng
75
M ột trường hợp nữa hay gặp khi dùng vòng w hile là để tạo ra các vòng lặp không trọn vẹn. Đ ây là kiểu vòng lặp có thể chạy vô tận, nhưng thực ra sẽ bị dừng khi có m ột điều kiện nào đó bên trong xảy ra, ví dụ: while True: lots of code here lots of code here lots of code here if some_value == True: break
Đ iều kiện của vòng w hile lúc này chỉ đơn giản là T rue , tức là luôn luôn đúng, nên khối lệnh bên trong lúc nào cũng chạy (thế m ới gọi là lặp vô tận). Có điều nếu biến som e_value là T rue thì vòng lặp sẽ dừng lại. Ta sẽ có m ột ví dụ thích hợp hơn về cùng chủ
đề này ở phần “Dùng randint Để Chọn M ột Số Ngẫu Nhiên” trang 132, nhưng cứ từ từ để học qua Chương 7 đã nhé.
TÓM TẮT Trong chương này, ta đã dùng các vòng lặp để tránh việc làm đi lại m ột cái gì đó. N hững gì m uốn lặp lại ta sẽ đưa chúng vào m ột khối lệnh rồi đặt vào bên trong vòng lặp. C ó hai loại vòng lặp là vòng for và vòng w hile , giống nhau về ý nghĩa nhưng khác nhau về cách dùng. Ta cũng học về từ khóa break để dừng vòng lặp lại – đúng nghĩa đen luôn, phá ⟨break⟩vòng.
B À I T Ậ P L Ậ P T R ÌN H Dưới đây là m ột vài bài tập về vòng lặp để em tự làm . Câu trả lời có thể tìm thấy trên h ttp ://p y th o n -f o r- k id s .c o m /.
76
Chương 6
#1: C H À O G Ì C H À O L Ắ M ^^ Theo em đoạn code sau sẽ làm gì? Đoán trước rồi mới được chạy để kiểm tra nhé: >>> for x in range(0, 20): print('hello %s' % x) if x < 9: break
#2: S Ố C H Ẵ N S Ố L Ẻ Viết vòng lặp chỉ in ra số chẵn nhỏ hơn tuổi của e m nếu tuổi e m là số chẵn, hoặc chỉ in ra số lẻ nhỏ hơn tuổi của em nếu tuổi em là số lẻ. Ví dụ như thế này: 2 4 6 8 10 12 14
#3: M Ó N Ă N Y Ê U T H Í C H Viết ra một danh sách nă m nguyên liệu khác nhau để làm m ón ăn e m thích nhất, ví dụ: >>> ingredients = ['snails', 'leeches', 'gorilla belly-button lint', 'caterpillar eyebrows', 'centipede toes']
Xong, viết một vòng lặp để in danh sách này ra (bao gồm cả số thứ tự nhé): 1 2 3 4 5
snails leeches gorilla belly-button lint caterpillar eyebrows centipede toes
Cùng đi lòng vòng
77
#4: C Â N N Ặ N G T R Ê N M Ặ T T R Ă N G Nếu giờ đang đứng trên mặt trăng, em sẽ nặng hơn 16,5 phần trăm so với đứng trên Trái đất. E m có thể tự tính ra cân nặng của mình trên mặt trăng bằng cách nhân số cân hiện tại của em với 0,165. Giả sử trong 15 năm tới, mỗi năm e m tăng thêm 1kg, hỏi cân nặng của em trên mặt trăng từng năm sẽ là bao nhiêu? Viết một vòng lặp for để in ra câu trả lời cho từng năm.
78
Chương 6
E m có biết mỗi ngày ta ném những thứ gì vào thùng rác không: chai nước này, lon nước này, túi đựng kẹo này, túi đựng bánh mì này, túi đựng rau củ này, túi ngoài siêu thị này, báo này, giấy này, ti tỉ thứ. Hình dung xem cả núi rác này chất đống trên đường đi mà xem. Chắc chắn không ai thích trèo qua đống rác này đi học đi làm cả, nên việc ta có thể làm là cố hết sức để tái chế vài thứ trong đó. Đồ thuỷ tinh có thể được nung chảy để làm ra chai mới lọ mới; giấy có thể được nghiền vụn để làm ra giấy tái chế; và đồ nhựa có thể được làm thành những đồ nhựa tái chế khác. Thế là ta có thể dùng lại những thứ này thay vì vứt hẳn chúng đi, phải không nào? Trong thế giới lập trình, tái sử dụng code cũng quan trọng không kém. Code thì tất nhiên không chất thành đống được, nhưng nếu không tái sử dụng được code thì cuối cùng chắc e m có mà gõ code gãy cả tay. Tái sử dụng code còn làm chương trình súc tích và dễ đọc hơn nữa. Rồi em sẽ thấy, Python cho ta vài cách để tái sử dụng code như sau. Tái sử dụng code với hà m và module
79
DÙNG HÀM Thực ra trong các chương trước e m đã thấy một cách tái sử dụng code trong Pyt hon rồi, ta đã dùng h à m range và h à m list để đ ế m số. >>> list(range(0, 5)) [0,1,2,3,4]
Đương nhiên, nếu đã biết đếm, em hoàn toàn có thể tự viết ra một mảng các số liên tiếp như thế, nhưng mả ng càng lớn e m càng phải gõ nhiều. Tuy nhiên nếu dùng hàm, e m có thể tạo ra một mả ng cả nghìn số chỉ trong nháy mắt. Đ â y là m ột ví dụ dùng list và range để sinh ra m ột m ả ng số: >>> list(range(0, 1000)) [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16...,997,998,999]
Hàm⟨ function⟩ là một đống code Python làm một việc cụ thể gì đó. Đây cũng là một cách để tái sử dụng code – ta có thể dùng đi dùng lại hàm bao nhiêu lần cũng được. Với mấy chương trình đơn giản thì dùng hàm nhìn cũng được, hay hay. Còn với nhữ ng c hương trình vừ a dài vừa khó, n hư g a m e chẳ ng hạn, h à m là thứ không thểthiếu được(đây là giả sử em muốn viết cho xong trong một thế kỷ nhé ^^)
CÁC THÀNH PHẦN TRONG MỘT HÀM M ộ t h à m bao g ồ m ba phần: tênhàm, thams ốvà thânhàm. Đ â y là một h à m rất đơn giản: >>> def testfunc(myname): print('hello %s' % myname)
Tên hàm là testfunc . H à m có một tham số duy nhất là myname , và thân hàm là khối lệnh ngay sau dòng có từ khóa def (viết tắt của địnhnghĩa⟨ define⟩ ). Mỗi thamsố ⟨ parameter⟩ là một biến, và biến này chỉ tồn tại khi hàm được sử dụng.
80
Chương 7
E m dùng hà m bằng cách gọi tên hàm, truyền vào tham số bên trong một cặp ngoặc tròn, như thế này: >>> testfunc('Mary') hello Mary
Hàm có thể có hai, ba, hoặc bao nhiêu tham số cũng được: >>> def testfunc(fname, lname): print('Hello %s %s' % (fname, lname))
Khi truyền vào hàm, các tham số sẽ được tách ra bởi dấu phẩy: >>> testfunc('Mary', 'Smith') Hello Mary Smith
Hoặc cũng có thể tạo ra biến trước rồi truyền vào hàm sau: >>> firstname = 'Joe' >>> lastname = 'Robertson' >>> testfunc(firstname, lastname) Hello Joe Robertson
Thường thì hàm sẽ trả về một giá trị nào đó, bằng lệnh return . Ví dụ hàm này sẽ nói em biết em có bao nhiêu tiền tiết kiệm này: >>> def savings(pocket_money, paper_route, spending): return pocket_money + paper_route – spending
H à m này nhận ba tham số. N ó cộng hai tham số đầu pocket_money (số tiền hiện đang có) và paper_route (số tiền đi làm thêm) rồi trừ đi tham số thứ ba spending (số tiền đã chi ra). Kết quả trả ra có thể được gán cho một biến khác (tương tự cách ta gán các giá trị cho biến thôi) hoặc in ra: >>> print(savings(10, 10, 5)) 15
Tái sử dụng code với hà m và module
81
BIẾN V À P HẠ M VI Một biến nằm bên trong hàm sẽ chỉ tồn tại bên trong hàm, hàm chạy xong là xong, và sẽ không sử dụng được ở bên ngoài hàm. Trong thế giới lập trình cái này được gọi là phạmvi⟨scope⟩. Thử xem xét hàm sau, không có tham số nhưng bên trong có sử dụng vài biến: >>> def variable_test(): first_variable = 10 second_variable = 20 return first_variable * second_variable
Trong ví dụ này, ta tạo ra h à m variable_test ở ❶ , trong đó nó nhân hai số first_variable v à secon d _vari ab l e v à o vớ i n h a u rồi trả ra kết q u ả ở ❷ . >>> print(variable_test()) 200
Nếu gọi hàm này bằng print , ta sẽ nhận được kết quả là 200. Nhưng nếu thử m ở bi ế n first_variable ( h o ặ c secon d _vari ab l e c ũ n g đư ợ c ) b ê n ng oà i kh ối lệ nh c ủ a h à m trên, ta sẽ gặp lỗi ngay: >>> print(first_variable) Traceback (most recent call last): File "", line 1, in print(first_variable) NameError: name 'first_variable' is not defined
Nếu một biến được tạo ra bên ngoài hàm, phạm vi của nó sẽ khác. Ví dụ thử tạo một biến trước khi tạo hàm rồi dùng nó bên trong hàm xem: >>> another_variable = 100 >>> def variable_test2(): first_variable = 10 second_variable = 20 return first_variable * second_variable * another_variable
82
Chương 7
C ó thể thấy, tuy c á c bi ế n first_variable v à secon d_vari abl e k h ô n g thể d ù n g đ ư ợ c bên ngoài hàm, nhưng biến another_variable (vốn được tạo ra ở bên ngoài ở ❶ ) lại có thể dùng được bên trong hàm ở ❷ . Kết quả của đoạn code trên là: >>> print(variable_test2()) 20000
Có một bài toán thế này, giả sử em đang chế tạo một con tàu vũ trụ siêu hiện đại bằng vỏ lon nước ngọt. Để làm ra bức vách cong cong của con tàu, mỗi tuần e m chỉ cần cắt 2 vỏ lon rồi làm phẳng nó ra là xong. Nhưng để làm thân tàu thì em cần đâu đó 500 cái cơ. Ta có thể viết một hàm để tính ra mất bao lâu thì ta mới có được 500 lon nếu mỗi tuần e m chỉ làm được 2 cái. Hãy thử tạo một hàm để tính xem mỗi tuần e m làm được bao nhiêu lon trong vòng một năm nhé. Ta sẽ để số lon làm được mỗi tuần làm tham số: >>> def spaceship_building(cans): total_cans = 0 for week in range(1, 53): total_cans = total_cans + cans print('Week %s = %s cans' % (week, total_cans))
Ở dòng đầu tiên của hàm, ta tạo biến total_cans và cho nó giá trị ban đầu là 0. Sau đó ta viết một vòng lặp cho từng tuần trong năm và cộng dồn vào số vỏ lon m à em làm được trong từng tuần. Toàn bộ khối lệnh này chính là thân của hàm. Nhưng trong này còn có một khối lệnh khác nữa: là hai dòng cuối, là khối lệnh của vòng for . Viết và chạy thử hàm này trong shell với các giá trị khác nhau của cans : >>> spaceship_building(2) Week 1 = 2 cans Week 2 = 4 cans Week 3 = 6 cans Week 4 = 8 cans Week 5 = 10 cans (...) Tái sử dụng code với hàm và module
83
>>> spaceship_building(13) Week 1 = 13 cans Week 2 = 26 cans Week 3 = 39 cans Week 4 = 52 cans Week 5 = 65 cans (...)
Hà m này có thể được tái sử dụng với các con số khác nhau tượng trưng cho số vỏ lon em làm được trong tuần, và rõ ràng là hiệu quả hơn nhiều so với việc viết lại cả vòng lặp mỗi khi muốn chạy với một số mới, đúng không nào? Nhiều hàm có thể được tổng hợp lại với nhau thành một module, chính là thứ khiến Python trở nên đặc biệt hữu dụng.
DÙNG MODULE Moduleđược dùng để nhóm các hàm, biến và nhiều thứ khác lại với nhau thành một chương trình lớn hơn, làm được nhiều việc hơn. Có module được đi kèm luôn lúc em cài Python, có cái thì em phải tải ở bên ngoài. Có module giúp em viết game (như tkinter được đi kèm, còn PyGame thì không), có
module giúp em xử lý ảnh (như PIL , Python Imaging Library), có module lại giúp em thiết kế các mô hình đồ họa không gian ba chiều (như Panda3D ). Module có thể được dùng cho tất cả các loại mục đích khác nhau. Ví dụ nếu làm game m ô phỏng và thế giới trong game phải thay đổi theo thời gian thực, em có thể lấy được thời gian và ngày tháng năm hiện tại bằng module time được đi kèm: >>> import time
Lệnh import ở đây nói với Python rằng ta muốn sử dụng module time .
84
Chương 7
Sau đó ta có thể gọi các hàm có sẵn trong module này, bằng cách sử dụng dấu chấm. (Còn nhớ lúc ở Chương 4 ta cũng đã dùng hàm như thế này để sử dụng module turtle không, n h ư là t.forward(50) ấy.) V í dụ ta c ó thể gọi h à m asctime trong m od ul e time như thế này: >>> print(time.asctime()) 'Mon Nov 5 12:40:27 2012'
H à m asctime là một phần của module time , sẽ đưa ra cho ta thời gian và ngày tháng nă m hiện tại dưới dạng chuỗi. Giờ giả sử em muốn ai đó dùng chương trình em viết và điền vào một con số bất kỳ, năm sinh hoặc tuổi của họ chẳng hạn. E m có thể dùng lệnh print , viết ra m ột câu m ở đầu, và rồi dùng modul e sys (viết tắt của hệthống
⟨system⟩ ), vốn là một module của Python có chứa rất nhiều đồ nghề công cụ để làm việc với hệ thống. Đầu tiên ta sẽ mang module sys vào: >>> import sys
Trong module sys có một đốitượng⟨ object⟩ rất đặc biệt tên là stdin (viết tắt của đầuvào⟨ standardinput⟩ ), đối tượng này có một hàm đặc biệt hay là readline . Hà m readline được dùng để đọc bất kỳ chữ gì kể từ lúc e m bắt đầu gõ trên bàn phím cho đến lúc em nhấn ENTER . (Đối tượng làm việc như thế nào thì từ từ ta sẽ xem ở Chương 8 nhé.) Đ ể dùng thử readline e m có thể chạy thử đoạn code sau trên shell: >>> import sys >>> print(sys.stdin.readline())
Sau đó em thử gõ vài chữ gì đó và nhấn ENTER xem, những gì em vừa gõ sẽ được in ngược ra m à n hình. Quay lại ví dụ ta viết ở Chương 5 có dùng lệnh if: >>> if age >= 10 and age >> def silly_age_joke(age): if age >= 10 and age >> silly_age_joke(9) Huh? >>> silly_age_joke(10) What is 13 + 49 + 84 + 155 + 97? A headache!
Ngon! Giờ sửa lại để hàm hỏi tuổi của người dùng nhé (Em có thể thêm bớt xóa sửa hàm bao nhiêu lần cũng được.) >>> def silly_age_joke(): print('How old are you?') age = int(sys.stdin.readline()) if age >= 10 and age >> silly_age_joke() How old are you? 10 What is 13 + 49 + 84 + 155 + 97? A headache!
86
Chương 7
>>> silly_age_joke() How old are you? 15 Huh?
TÓM TẮT Trong chương này, em đã thấy cách tái sử dụng code bằng hàm trong Python, và thấy cách dùng hàm của module như thế nào. E m cũng thấy là một biến có thể được nhìn thấy hay không là tùy thuộc vào phạm vi của nó, và cách sử dụng từ khóa def để viết hàm. Đồng thời e m cũng đã thấy cách mang các module vào chương trình để sử dụng như thế nào.
BÀI TẬP LẬP TRÌNH Hãy thử viết hàm để xử lý những bài tập sau. Câu trả lời có thể tìm thấy trên http://python-for-kids.com/ .
#1: H À M T Í N H C Â N N Ặ N G T R Ê N M Ặ T T R Ă N G C Ấ P I Một trong mấy bài tập ở Chương 6 có yêu cầu em viết một vòng for để tính số cân nặng của e m trên mặt trăng trong vòng 15 năm. Vòng for đó có thể được chuyển sang thành h à m rất dễ. Hãy viết h à m tính cân nặ ng của e m trên mặt trăng từng n ă m m ột trong vòng 15 năm, với hai tham số đầu vào là cân nặng ban đầu và cân nặng tăng hàng năm. H à m đó có thể được gọi như thế này: >>> moon_weight(30, 0.25)
Tái sử dụng code với hà m và module
87
#2: H À M T Í N H C Â N N Ặ N G T R Ê N M Ặ T T R Ă N G C Ấ P II Sửa hàm vừa viết được bên trên để chọn thời gian trên mặt trăng, tức là khoảng 5 năm hoặc 20 năm chẳng hạn. Lúc này hàm sẽ có ba tham số tất cả: cân nặng ban đầu, cân nặng tăng hàng năm, và số năm: >>> moon_weight(90, 0.25, 5)
#3: C H Ư Ơ N G T R Ì N H T Í N H C Â N N Ặ N G T R Ê N M Ặ T T R Ă N G Thay vì chỉ viết mỗi hàm và phải truyền tham số vào, hãy viết hẳn một chương trình đ ơ n giản, hỏi ngư ời d ù n g t hô ng tin c ủa h ọ d ù n g sys.stdin.readline() . L ầ n n à y e m có thể gọi hà m m à không cần tham số nào cả: >>> moon_weight()
Chương trình sẽ hiện lên các câu hỏi về cân nặng hiện tại, rồi cân nặng tăng hàng năm và cuối cùng là số năm. Kiểu như thế này: Please enter your current Earth weight 45 Please enter the amount your weight might increase each year 0.4 Please enter the number of years 12
Đừng quên ma ng module sys vào trước khi viết hàm nhé: >>> import sys
88
Chương 7
Con hươu với cái vỉa hè có gì giống nhau không? Có chứ! Giống chỗ nào? Chúng đều là một thứgì đó, trong ngôn ngữ của người là danhtừ, còn trong ngôn ngữ Python đó là đối tượng⟨object⟩. Đốitượnglà một khái niệm hết sức quan trọng trong thế giới lập trình nói chung. Đối tượng là một cách để sắp xếp code trong một chương trình, chia nhỏ mọi thứ ra để có thể xây dựng nên những thứ phức tạp hơn. (Trên thực tế ta đã dùng một đối tượng ở Chư ơng 4 khi sử dụng module turtle rồi – Pen .) Để thực sự nắm bắt được đối tượng trong Python là như thế nào, ta phải nắm được đối tượng nào thuộc thể loại gì. Quay lại với con hươu và cái vỉa hè nhé. Hươu là một loài động vật có vú, đương nhiên thuộc thể loại động vật rồi. Hươu còn là đối tượng di động (có thể tự đi lại được) – vì nó là một thực thể sống mà.
Cách sử dụng lớp và đối tượng
89
Còn vỉa hè, ngoài việc nó không phải là động thực vật ra thì chả có gì nhiều để nói cả. Ta tạm gọi nó là đối tượng bất động (theo nghĩa là không phải là một thực thể sống). C á c khái n i ệ m n h ư đ ộ n g vậtc ó vú,đ ộ n g vật, diđ ộ n g , bấtđ ộ n g là c á c c á c h kh á c n h a u đ ể phân loại sự vật.
CHIA MỌI THỨ RA THÀNH LỚP Trong Python, tất cả các đối tượng đều thuộc về lớp⟨ class⟩ , có thể hiểu theo hướng là một cách để phân loại đối tượng thành các nhóm khác nhau. Đây là sơ đồ các lớp mà bạn hươu và cái vỉa hè của chúng ta có thể đặt vào dựa trên các định nghĩa lúc nãy:
Lớp cao nhất ở đây là Things (tất cả mọi thứ). Dưới Things là Inanimate (bất động) và Animate (di động). Mỗi lớp này lại được chia nhỏ ra nữa, Inanimate chỉ có Sidewalks (vỉa hè), còn Animate thì có Animals (động vật), Mammals (động vật có vú), và Giraffes (hươu).
90
Chương 8
Ta dùng lớp để tổ chức sắp xếp code trong Python. Lấy ví dụ là module turtle . Tất cả những gì mà con rùa có thể làm được — như đi tới, đi lùi, quay trái, quay phải — đều là hàm của lớp Pen . Mỗi đối tượng đều có thể hiểu là một thành phần của lớp, và trong lớp đó ta có thể tạo ra bao nhiêu đối tượng cũng được — ta sẽ đề cập đến cái này sớm thôi. Giờ ta sẽ tạo ra các lớp giống như sơ đồ trên, từ trên xuống dưới. Mỗi lớp được định nghĩa bằng từ khóa class , sau đó là tên lớp. Things là lớp ở trên cùng, ta sẽ xử nó trước: >>> class Things: pass
Ta đặt tên lớp là Things và dùng lệnh pass để nói Python rằng lớp này sẽ không có thêm gì nữa hết. pass được dùng khi ta muốn tạo lớp hoặc tạo hàm m à không viết code gì ngay lập tức. Tiếp theo, ta sẽ tạo ra các lớp còn lại và kết nối mối quan hệ giữa chúng.
LỚP CON VÀ LỚP CHA Khi một lớp là một phần của một lớp khác, thì nó là lớp con⟨child⟩ , còn kia là lớp cha ⟨parent⟩ . Một lớp có thể vừa là lớp cha vừa là lớp con. Trong sơ đồ trên, lớp ở bên trên là lớp cha, còn lớp bên dưới là lớp con. Ví dụ, Inanimate và Animate đều là lớp con của Things , đồng thời cũng có nghĩa Things là lớp cha của hai lớp kia. Để Python biết một lớp là lớp con của lớp khác, ta đặt tên của lớp cha vào trong cặp ngoặc tròn sau khi viết tên lớp như thế này: >>> class Inanimate(Things): pass >>> class Animate(Things): pass
Ta vừa mới tạo ra một lớp tên là Inanimate và bảo Python rằng lớp cha của nó là Things với đoạ n c ode class Inanimate(Things) . Tư ơn g tự, ta tạo ra m ột lớp khác tên là Animate và bảo Python rằng lớp cha của nó cũng là Things bằng đoạn code class An i mate (Things) . Cách sử dụng lớp và đối tượng
91
Ta sẽ làm tiếp như thế với lớp Sidewalks . Sidewalks có lớp cha là Inanimate : >>> class Sidewalks(Inanimate): pass
Làm tương tự với các lớp Animals , Mammals , và Giraffes : >>> class Animals(Animate): pass >>> class Mammals(Animals): pass >>> class Giraffes(Mammals): pass
TẠO ĐỐI TƯỢNG TỪ LỚP Có một loạt lớp rồi, giờ làm gì với chúng đây? Giả sử ta có một bạn hươu tên là Reginald. Ta biết là nó thuộc về lớp Giraffes , nhưng dưới góc độ lập trình, làm sao để ta mô tả được một con hươu cụ thể nào đó chính là Reginald? Ta gọi Reginald là một đối tượngcủa lớp Giraffes (hoặc là một thựcthể⟨ instance⟩ ). Để “đưa” Reginald vào Python ta viết như thế này: >>> reginald = Giraffes()
Đoạn code này bảo Python tạo ra một đối tượng trong lớp Giraffes , sau đó gán đối tượng này cho biến reginal . Tương tự như lúc dùng hàm, sau tên lớp ta dùng một cặp ngoặc tròn. Ở cuối chương này ta sẽ xem làm sao để đặt tham số vào ngoặc trong lúc tạo đối tượng nhé. Nhưng đối tượng reginal này có thể làm được những gì? Chậc, ngay bây giờ thì không có gì cả. Để các đối tượng có có thể làm gì đó, khi tạo lớp ta cũng cần tạo thêm các hàm cho nó để về sau các đối tượng còn có cái mà dùng. D o đó ta sẽ cần viết thêm các hàm cho lớp thay vì chỉ dùng mỗi từ khóa pass .
92
Chương 8
V IẾ T H À M C H O L Ớ P Chương 7 đã nói rằng hàm là m ột cách để tái sử dụng code. G iờ hàm cho lớp cũng được viết y như vậy, chỉ khác là ta đẩy nó lùi vào bên trong hàm . V í dụ đây là m ột hàm nằm riêng m ột m ình: >>> def this_is_a_normal_function(): print('I am a normal function')
Còn đây là vài hàm thuộc về m ột lớp: >>> class ThisIsMySillyClass: def this_is_a_class_function(): print('I am a class function') def this_is_also_a_class_function(): print('I am also a class function. See?')
DÙNG HÀM ĐỂ M Ô TẢ CÁC ĐẶC TRƯNG CỦA LỚP Quay trở lại với các lớp con của Anim ate được tạo ra ở trang 92. Ta phải m ô tả các đặctrưng⟨ characteristic⟩ của từng lớp để xác định nó là cái gì và nó làm được gì. Đ ặc trưng của m ột lớp là các đặc điểm m à tất cả các đối tượng của lớp đó (và các lớp con của nó) đều có. Ví dụ, tất cả các loài động vật có gì chung nào? Ờ thì, xem nào, đầu tiên là biết thở này. Còn biết đi và biết ăn nữa. Còn động vật có vú thì sao? Đều nuôi con bằng sữa chứ sao nữa. Đương nhiên là cũng biết thở, biết đi và biết ăn rồi. G iờ đến hươu, ta biết là chúng ăn lá cây ở tít trên cao, và nuôi con bằng sữa như các loài động vật có vú khác, rồi rõ là biết thở, biết đi và biết ăn. K hi thêm những đặc trưng này vào sơ đồ gốc, ta có:
Cách sử dụng lớp và đối tượng
93
Nhữ ng đặc trưng này có thể hiểu tạm là các hành vi hoặc cácchứcnăng— là những thứ một đối tượng thuộc một lớp có thể làm được. Để viết hàm cho lớp, ta cũng dùng từ khóa def . Cho nên lớp Animals lúc này sẽ thành: >>> class Animals(Animate): def breathe(self): pass def move(self): pass def eat_food(self): pass
Dòng đầu tiên được dùng để định nghĩa lớp, ta đã làm lúc nãy rồi, nhưng ở dòng sau, thay vì dùng từ khóa pass , ta đặt ra một hàm tên là breathe (thở) và đư a và o m ộ t t ham số: self . self là cá ch để m ột h à m c ó
thể gọi hàm khác trong cùng một lớp (và lớp cha). Ta sẽ gặp lại tham số này ở cuối chương. Ở dòng sau đó, từ khóa pass là để nói rằng ta không có ý định viết thêm gì vào hàm breathe nữa, vì hàm lúc này sẽ không làm gì hết. Tương tự như thế, ta viết thêm hàm move
(đi) và eat_food (ăn), cũng ngồi chơi không như thế. Sắp tới ta sẽ sửa lại mấy lớp này và sẽ viết code chuẩn vào hàm sau. Đây là cách lập trình khá phổ biến. Thường thì lập trình viên sẽ tạo ra lớp với các hàm trống như thế trước để hình dung ra các lớp sẽ hoạt động như thế nào, rồi mới viết code cụ thể cho từng hàm sau. Ta cũng sẽ viết thêm vài hàm cho hai lớp còn lại là Mammals và Giraffes . Các lớp này cũng có những đặc trưng (tức là có thể chạy được các hàm) của các lớp cha. Như vậy, e m không cần phải tạo ra một lớp siêu phức tạp; m à chỉ cần xác định một đặc điểm nằm ở lớp nào chung nhất (và cao nhất) rồi đặt hàm cần thiết vào lớp đó là được. (Bằng cách sắp xếp như thế, các lớp của em sẽ trở nên đơn giản và dễ hiểu hơn.)
94
Chương 8
>>> class Mammals(Animals): def feed_young_with_milk(self): pass >>> class Giraffes(Mammals): def eat_leaves_from_trees(self): pass
N H Ư N G M À T A C Ầ N L Ớ P V Ớ I Đ Ố I T Ư Ợ N G Đ Ể L À M GÌ? Giờ ta đã có cả hàm và lớp, nhưng rốt cuộc dùng lớp với hàm để làm gì, đằng nào mình cũng có thể viết ra các hàm bình thường rồi đặt tên là breathe , move , eat_food cơ mà ? Bạn hươu Reginal ta tạo ra lúc nãy từ lớp Giraffes sẽ trả lời câu hỏi này cho em: >>> reginald = Giraffes()
Vì reginald là một đối tượng, ta có thể gọi (hoặc chạy) các hàm m à lớp của nó ( Giraffes ) và các lớp cha của nó có. Ta gọi hàm bằng cách dùng dấu chấm và sau đó là tên
hàm. Để bảo hươu Reginal di chuyển và ăn lá cây, ta gọi hàm như thế này: >>> reginald = Giraffes() >>> reginald.move() >>> reginald.eat_leaves_from_trees()
Giờ giả sử hươu Reginald có bạn mới, tên bạn ấy là Harold. Ta sẽ tạo một đối tượng Giraffes n ữ a đặt tên là harold : >>> harold = Giraffes()
Do sử dụng lớp và đối tượng ở đây, nên ta có thể nói với Python chính xác là ta đang muốn con hươu nào di chuyển. Ví dụ, nếu muốn Harold đi nhưng Reginald đứng yên, ta chỉ cần dùng đối tượng harold là đủ: >>> harold.move()
Lúc này, chỉ có mỗi Harold di chuyển thôi.
Cách sử dụng lớp và đối tượng
95
Sửa code mấ y hàm này để cho sinh động hơn một tẹo nhé. Thay vì pass , ta thêm lệnh print vào trong từng h à m một: >>> class Animals(Animate): def breathe(self): print('breathing') def move(self): print('moving') def eat_food(self): print('eating food') >>> class Mammals(Animals): def feed_young_with_milk(self): print('feeding young') >>> class Giraffes(Mammals): def eat_leaves_from_trees(self): print('eating leaves')
Giờ mỗi khi các đối tượng reginald và harold gọi hàm, ta sẽ nhìn thấy cái gì đó: >>> reginald = Giraffes() >>> harold = Giraffes() >>> reginald.move() moving >>> harold.eat_leaves_from_trees() eating leaves
Hai dòng đầu ta tạo lại ra hai biến reginald và harold , là các đối tượng của lớp Giraffes . Tiếp theo reginald gọi h à m move và Python in ra chữ moving . Tương tự như thế, harold gọi hàm e a t _ l e a v e s _ f r o m _ t r e e s v à P y t h o n i n r a e a t i n g l eaves . N ế u l à
hươu thật thì một e m đã thong dong đi lại còn e m kia đã đi ngoạm lá cây rồi đấy.
96
Chương 8
ĐỐI TƯỢNG VÀ LỚP TRONG ĐỒ HỌA Lần này, ta sẽ thử mang kiến thức về đối tượng và lớp vào trong đồ họa nhé. Quay trở lại với module turtle ta đã có dịp chạy thử ở Chương 4. Khi sử dụng turtle.Pen() , Python đ ã tạo ra m ộ t đối tượng c ủa lớp Pen n ằ m trong m od ul e turtle đ ấ y (y
như mình vừa tạo các đối tượng reginald và harold lúc nãy). Thử tạo ra hai con rùa (tên là Avery và Kate) như lúc nãy tạo hươu nhé: >>> import turtle >>> avery = turtle.Pen() >>> kate = turtle.Pen()
Hai con rùa này ( avery và kate ) đều thuộc về lớp Pen cả đấy nhé. Từ giờ trở đi các đối tượng mới thực sự trở nên ngon lành đây. Với hai con rùa vừa được tạo, mỗi con ta lại có thể gọi các hàm riêng biệt độc lập với nhau. Như thế này: >>> avery.forward(50) >>> avery.right(90) >>> avery.forward(20)
Theo những chỉ dẫn này, ta vừa bảo Avery đi tới 50 điểm ảnh, quay sang phải 90 độ, rồi đi tiếp 20 điểm ảnh nữa, lúc này nó đã di chuyển xong và quay mặt xuống dưới. Đừng quên là lúc bắt đầu rùa luôn luôn hướng sang phải. Giờ đến lượt Kate. >>> kate.left(90) >>> kate.forward(100)
Ta vừa bảo Kate quay sang trái 90 độ, rồi tiến tới 100 điểm ảnh, cuối cùng nó sẽ quay mặt lên trên. Nãy giờ ta mới vẽ được mỗi một đường với hai mũi tên hai đầu, mỗi mũi tên tượng trưng cho một con rùa: Avery quay mặt xuống dưới còn Kate quay lên trên. Cách sử dụng lớp và đối tượng
97
Ta sẽ vẽ thêm một con rùa thứ ba, Jacob, và di chuyển nó mà không cần đụng chạm gì tới Kate hay Avery cả. >>> jacob = turtle.Pen() >>> jacob.left(180) >>> jacob.forward(80)
98
Chương 8
Đầu tiên ta tạo một đối tượng Pen mới đặt tên là jacob , xoay nó sang trái 180 độ, rồi tiến tới 80 điểm ảnh. Ba con rùa lúc này trông như sau:
Đ ừ n g quên, mỗi khi tạo ra một con rùa m ới bằng cách gọi turtle.Pen() , ta sẽ tạo ra một đối tượng hoàn toàn độc lập với nhau. Mỗi đối tượng tuy đều thuộc về lớp Pen và đều có cùng các hàm y như nhau, nhưng lại có thể di chuyển một cách độc lập vì chúng là các đối tượng hoàn toàn khác nhau. Cũng như các các đối tượng hươu lúc trước (Reginald và Harold), Avery, Kate, và Jacob là ba đối tượng rùa riêng biệt. Nếu có nhỡ tạo ra một đối tượng mới với cùng tên biến, đối tượng cũ sẽ không mất đi đâu nhé. Thử xem: tạo ra một con rùa Kate nữa rồi di chuyển lung tung m à xem.
VÀI THỨ HAY HO KHÁC CỦA ĐỐI TƯỢNG V À LỚP Lớp với đối tượng giúp ta dễ dàng gộp các hàm lại với nhau. Và nó đặc biệt có ích khi giúp ta hình dung ra một chương trình như một bức tranh g ồ m nhiều m ả nh nhỏ ghép lại.
Cách sử dụng lớp và đối tượng
99
Ví dụ, em thử tưởng tượng một phần m ề m cực lớn, như là một chương trình xử lý văn bản hay một game 3D chẳng hạn. Đa số mọi người sẽ gần như không thể hiểu nổi hết cả một chương trình đồ sộ như thế, vì đơn giản là có quá là nhiều code. Nhưng nếu ta chia nhỏ chương trình khổng lồ này ra thành nhiều phần nhỏ, mỗi phần lúc này sẽ dễ hiểu hơn phải không — tất nhiên là với điều kiện em phải hiểu ngôn ngữ đó rồi. Khi phải viết một chương trình lớn như thế, chia nhỏ nó ra đồng thời cũng giúp e m phân chia được công việc ra cho các lập trình viên khác. Những chương trình hết sức phức tạp e m đang dùng hàng ngày (như trình duyệt web chẳng hạn) được rất nhiều người, nhiều nhóm ở nhiều nơi trên thế giới bỏ công sức ra cùng nhau viết đấy. Giờ thử hình dung rằng e m muốn m ở rộng thêm các lớp vừa viết lúc trước ( Animals , Mammals , và Giraffes ), nhưng lại quá là bận đi, e m sẽ cần bạn bè
giúp đỡ. Ta có thể chia nhỏ việc code ra để người này viết cho lớp Animals , người kia viết cho lớp Mammals , người khác nữ a thì viết cho lớp Giraffes .
THỪA KẾ HÀM Nếu tinh ý một chút, e m có thể sẽ nhận ra rằng trong số những người giúp e m trên kia, bạn may m ắ n nhất là bạn được viết code cho lớp Giraffes , vì bạn đó có thể dùng tất cả các hàm được viết bởi ai đó cho các lớp Animals và Mammals . Lớp Giraffes kếthừa⟨ inherit⟩ tất cả hàm của lớp Mammals , và lớp Mammals lại kế thừa tất cả các hàm của lớp Animals . Nói cách khác, khi tạo ra một đối tượng thuộc lớp Giraffes , đối tượng đó có thể sử dụng tất cả h à m c ủa lớp Giraffes c ũ n g n h ư tất cả hàm của các lớp cha Animals và Mammals . Cùng cách diễn đạt như thế, nếu tạo ra một đối tượng của lớp Mammals , đối tượng đó có thể dùng tất cả hàm của lớp Mammals cũng như tất cả hà m của lớp cha Animals .
100
Chương 8
Cùng nhìn lại mối quan hệ giữa các lớp Animals , Mammals và Giraffes nhé. Lớp Animals là lớp cha của Mammals , còn lớp Mammals lại là lớp cha của Giraffes .
Và mặc dù hươu Reginal vốn thuộc về lớp Giraffes , nhưng ta vẫn có thể gọi hàm move thuộc về lớp Animals , vì tất cả những hàm nào thuộc về lớp cha cũng sẽ thuộc về các
lớp con của nó. >>> reginald = Giraffes() >>> reginald.move() moving
Trên thực tế, reginald có thể gọi tất cả hàm của các lớp Animals và Mammals do các hàm này được thừa kế lại: >>> reginald = Giraffes() >>> reginald.breathe() breathing >>> reginald.eat_food() eating food >>> reginald.feed_young_with_milk() feeding young
HÀM NÀY GỌI HÀM KIA Khi một đối tượng gọi hàm, ta dùng tên biến của đối tượng đó. Ví dụ để hươu Reginal gọi hàm move thì phải làm như sau: >>> reginald.move()
Như ng để một hàm bên trong lớp Giraffes gọi được move thì ta phải dùng tham số self . self là cách để h à m nà y có thể gọi h à m khác trong cùng một lớp. Gi ả sử ta viết thêm
m ộ t h à m find _food c h o l ớp Giraffes : >>> class Giraffes(Mammals): def find_food(self): self.move() print("I've found food!") self.eat_food() Cách sử dụng lớp và đối tượng
101
Ta vừa mới tạo được một hàm kết hợp hai hàm khác, một việc rất hay gặp trong lập trình. Thường thì là thế này, em viết ra một hàm để thực hiện việc gì đó, sau đó em sẽ dùng hàm này bên trong một hàm khác. (Ta sẽ làm thế ở Chương 13 khi cần phải viết vài hàm khá phức tạp cho game.) Gi ờ thử dùng self để t hêm vài h à m khác c ho lớp Giraffes : >>> class Giraffes(Mammals): def find_food(self): self.move() print("I've found food!") self.eat_food() def eat_leaves_from_trees(self): self.eat_food() def dance_a_jig(self): self.move() self.move() self.move() self.move()
Ta có thể dùng các hàm eat_food và move của lớp cha Animals để vi ết ra ha i h à m e a t _ l e a v e s _ f r o m _ t r e e s v à d an c e_ a_ ji g c h o l ớ p G i r a f f e s vì chúng đều được thừa kế lại. Viết hàm rồi gọi hàm như thế này giúp ta về sau khi tạo một đối tượng mới, ta chỉ cần gọi một hàm thôi trong khi vẫn làm được nhiều việc cùng lúc. E m có thể thấy điều này khi ta gọi hàm dance_a_jig như dưới đây — Reginal sẽ di chuyển 4 lần (ý là có 4 chữ moving đó): >>> reginald = Giraffes() >>> reginald.dance_a_jig() moving moving moving moving
102
Chương 8
KHỞI TẠO ĐỐI TƯỢNG Khi tạo ra một đối tượng, đôi khi ta muốn đặt một vài giá trị (còn được gọi là các các thuộctính⟨ properties⟩ ) để về sau còn dùng. Khởitạo⟨initialize⟩ đối tượng là việc ta chuẩn bị sẵn sàng cho đối tượng đó. Giả sử nếu muốn đặt số đốm trên người hươu vào các đối tượng khi chúng được tạo ra — tức là, khi chúng được khởi tạo. Để làm việc đó, ta viết ra một hàm gọi là __init__ (để ý hai dấu gạch dưới đằng trước và đằng sau tên hàm, tổng cộng là bốn cái đấy nhé). Đây là một kiểu h à m đặc biệt của các lớp trong Python và ta buộc phải viết tên như thế. H à m init là cách để ta có thể đặt các thuộc tính vào đối tượng ngay khi chúng được tạo ra. Python luôn luôn gọi hàm này khi ta tạo ra một đối tượng mới. Viết hàm ấy như thế này: >>> class Giraffes: def __init__(self, spots): self.giraffe_spots = spots
Đ ầ u tiên ta viết h à m init n h ậ n ha i t h a m s ố self v à spots n h ư s a u d e f __in i t__ (self, sp ots): . C ũ n g n h ư c á c h à m k h á c đ ư ợ c vi ế t b ê n t r o n g l ớ p, h à m init c ũ n g cần phải nhậ n self l à m t ha m số đầ u tiên. Sa u đó ta gá n tha m s ố spots c ho m ột biến (chính là thuộc tính
m à ta vừ a nói) tên là giraffe_spots b ằ n g t h a m s ố self bởi đo ạ n c ode sel f.gi raffe_sp ots = spots . Đ o ạ n c o d e n à y c ó thể t ạ m hi ể u là “ G i ữ gi á trị c ủ a t h a m s ố spots để sau dùng lại
(thông qua biến giraffe_spots ).” Cá c biến ở trong lớp cũng c ó thể được truy cập thông qua self y như hà m ở trong lớp vậy.
Tiếp, nếu tạo ra vài bạn hươu mới (Ozwald và Gertrude) và in ra số đốm của chúng, ta sẽ thấy tác dụng của hà m khởi tạo: >>> ozwald = Giraffes(100) >>> gertrude = Giraffes(150) >>> print(ozwald.giraffe_spots) 100 >>> print(gertrude.giraffe_spots) 150 Cách sử dụng lớp và đối tượng
1 03
Đối tượng đầu tiên của lớp Giraffes ta tạo ra lúc nãy nhận tham số 100. Lúc này hà m __init__ được gọi và tham số spots có giá trị 100 được truyền vào. Tiếp theo ta tạo ra một đối tượng khác cũng của lớp Giraffes , lần này nhận tham số 150. Cuối cùng ta in ra biến giraffe_spots của từng đối tượng trên, kết quả lần lượt là 100 và 150. Chạy đúng rồi! Đừng quên, mỗi khi tạo một đối tượng mới trong một lớp, như ozwald trên kia, ta có thể truy cập được các biến và hàm của nó bằng cách dùng dấu chấm và theo sau là tên biến hoặc tên h à m c ầ n gọi ( ozwald.giraffe_spots c h ẳ n g hạn). N h ư n g nế u c ó viết h à m bên trong lớp, ta chỉ có thẻ truy cập vào các biển (và các hàm) kia thông qua tham số self (self.giraffe_spots).
TÓM TẮT Trong chương này ta đã sử dụng lớp để phân loại mọi thứ và đã tạo ra đối tượng (thể hiện) của các lớp đó. E m đã thấy lớp con được kế thừa các hàm từ các lớp cha như thế nào, và dù hai đối tượng đều thuộc về cùng một lớp, chúng lại chẳng giống nhau cho lắm. Kiểu như mỗi con hươu sẽ có số đốm khác nhau chẳng hạn. E m cũng thấy cách gọi hà m trên một đối tượng như thế nào và vì sao các biến lại có thể giúp ghi lại các giá trị khác nhau cho đối tượng. Cuối cùng, ta đã thấy cách dùng tham số self trong hà m để liên hệ tới các hàm và biến khác ra sao. Đây đều là những khái niệm cực kỳ cơ bản trong lập trình Python, và em sẽ liên tục gặp lại chúng từ giờ đến hết sách.
BÀI TẬP LẬP TRÌNH Có thể có vài điểm trong chương này sẽ chỉ được sáng tỏ khi e m thực sự làm việc với chúng. Hãy viết thử những ví dụ sau. Câu trả lời có thể tìm thấy trên http://pythonfor-kids.com/ .
104
Chương 8
#1: H Ư Ơ U C H Â N T R Ư Ớ C Đ Á C H Â N S A U Viết hàm cho lớp Giraffes để di chuyển chân trái và chân phải đi tới đi lui. M ột hàm di chuyển chân trái tới trước có thể sẽ trông như thế này: >>> def left_foot_forward(self): print('left foot forward')
Sau đó tạo ra một hàm mới đặt tên là dance để dạy Reginal nhảy (hàm này sẽ gọi tới bốn hàm lúc nãy vừa tạo). Kết quả của hàm này là một điệu nhảy đơn giản như thế này: >>> reginald = Giraffes() >>> reginald.dance() left foot forward left foot back right foot forward right foot back left foot back right foot back right foot forward left foot forward
#2: R Ù A H Ì N H C Á I X I Ê N Vẽ hình cái xiên như sau sử dụng bốn đối tượng rùa từ lớp Pen (độ dài của các đường không quan trọng nha). Và đừng có quên mang module turtle vào trước khi viết đó.
Cách sử dụng lớp và đối tượng
105
106
Chương 8
Bên trong Python có sẵn rất nhiều đồ nghề hỗ trợ lập trình, bao gồm một lượng lớn các hàm và module đã được viết sẵn, có thể sử dụng được ngay. Những “đồ nghề” được viết sẵn này — thực ra chỉ là một đống code thôi ấy mà — cũng giống cái búa hay cái kìm, sẽ giúp công việc lập trình của ta trở nên dễ dàng hơn rất nhiều. Như đã học ở Chương 7, muốn sử dụng module nào là ta phải mang module đó vào trong code. Nhưng với những hàm đã đượcviếtsẵn⟨ built-in⟩ thì không cần phải làm thế; cứ Python shell được bật lên là dùng được luôn rồi. Chương này ta sẽ học một vài hàm hay được sử dụng nhất, đặc biệt là hàm open , được sử dụng để đọc và ghi file.
C Á C H D Ù N G C Á C H À M Đ Ư Ợ C V IẾT S Ẵ N Ta sẽ xem xét 12 hàm được viết sẵn thường xuyên được sử dụng trong giới lập trình Python nhé. Mỗi hàm sẽ có m ô tả, cách dùng và vài ví dụ để thấy chúng có tác dụn như thế nào.
Các hà m Python được viết sẵn
107
HÀM ABS H à m abs trả về giátrịtuyệtđối⟨ absolutevalue⟩ của một số, tức là giá trị một số m à không có dấu. Ví dụ giá trị tuyệt đối của 10 là 10, còn giá trị tuyệt đối của –10 cũng là 10. Để dùng hàm abs , chỉ cần gọi hàm và truyền tham số là một số hoặc một biến là được, như thế này: >>> print(abs(10)) 10 >>> print(abs(-10)) 10
H à m abs có thể được dùng để tính toán đường đi trong game m à không cần quan tâm là nhân vật di chuyển theo hướng nào. Giả sử nhân vật vừa bước sang trái ba bước (dương 3), sau đó bước sang trái mười bước (âm 10, hoặc là –10). Nếu bỏ qua yếu tố hướng ở đây (dấu dương hoặc dấu âm), thì giá trị tuyệt đối của các số lúc này sẽ là 3 và 10. Cách tính toán này có thể được áp dụng trong một game chơi cờ nào đó m à chẳng hạn ta phải đổ xúc xắc để lấy số bước đi rồi di chuyển quân theo hướng bất kỳ. Giờ nếu ta ghi lại số bước đi vào một biến, ta có thể tính ra là một quân cờ có di chuyển hay không với đoạn code sau. Ta tất nhiên có thể đưa ra thêm các thông tin khác khi người chơi quyết định di chuyển, nhưng trong trường hợp nà y ta chỉ viết ra dòng chữ Ch aracter is moving : >>> steps = -3 >>> if abs(steps) > 0: print('Character is moving')
108
Chương 9
Nế u không dùng abs , lệnh if có thể sẽ như thế này: >>> steps = -3 >>> if steps < 0 or steps > 0: print('Character is moving')
E m cũng thấy đấy, dùng abs giúp cho lệnh if ngắn hơn và súc tích hơn đúng không nào?
HÀM BOOL bool là viết tắt của giátrịlogic⟨ boolean⟩ , là một từ mà giới lập trình thường sử dụng
để nói về một loại dữ liệu mà chỉ có thể có một trong hai giá trị, là đúng hoặc sai. H à m bool chỉ nhận một tham số duy nhất, sau đó trả về True hoặc False tùy vào tham số đầu vào. Nếu áp dụng bool với số, 0 sẽ trả về False , còn tất cả các số khác sẽ trả về True . Dưới đây là một vài ví dụ: >>> print(bool(0)) False >>> print(bool(1)) True >>> print(bool(1123.23)) True >>> print(bool(-500)) True
Nếu dùng bool với các giá trị khác, chuỗi chẳng hạn, nó sẽ trả về False nếu giá trị rỗng (tức là từ khóa None hoặc chuỗi rỗng). Còn lại là True hết: >>> print(bool(None)) False >>> print(bool('a')) True >>> print(bool(' ')) True
Các hà m Python được viết sẵn
109
>>> print(bool('What do you call a pig doing karate? Pork Chop!')) True
H à m bool cũng trả về False nếu mảng, tuple và map bị rỗng, ngược lại thì là True : >>> my_silly_list = [] >>> print(bool(my_silly_list)) False >>> my_silly_list = ['s', 'i', 'l', 'l', 'y'] >>> print(bool(my_silly_list)) True bool có thể được dùng để kiểm tra xem một biến nào đó có giá trị hay không. Giả sử
nếu ta yêu cầu ai đó nhập n ă m sinh vào chư ơng trình, lệnh if c ó thể dùng h à m bool để kiểm tra giá trị đầu vào: >>> year = input('Year of birth: ') Year of birth: >>> if not bool(year.rstrip()): print('You need to enter a value for your year of birth') You need to enter a value for your year of birth
Trong ví dụ trên, dòng đầu sử dụng input để ghi lại thông tin người dùng nhập từ bàn phím vào biến year . Nhấn ENTER luôn ở dòng thứ hai (mà không điền năm sinh gì hết), chương trình sẽ ghi nhận việc phím ENTER này vào biến. (Ở Chương 7 ta sử dụng sys.stdin.readline() , đ â y c hỉ là m ộ t c á c h l à m k h á c thôi, kết q u ả đ ề u gi ố ng nhau.)
Dòng tiếp theo, lệnh if kiểm tra giá trị Boolean của biến sử dụng h à m rstrip ( h à m nà y sẽ xóa các khoả ng trắng và các lần nhấn ENTER của chuỗi từ phải sang trái). Lúc này do không thực sự điền gì lúc nãy, nên bool trả về False . Tuy nhiên lệnh if lúc này lại sử dụng từ khóa not , cho nên ý nó thực sự lại là, “nếu kết quả trả về không phải là True thì làm việc này này” nên code m ới in ra Yo u need to enter a value for your year of birth ở dòng cuối.
110
Chương 9
H À M DIR H à m dir (viết tắt của thưmục⟨ directory⟩ ) đưa cho ta thông tin của bất cứ giá trị nào được truyển vào. Về cơ bản, nó nói ta biết những hàm nào giá trị đó có thể sử dụng được theo thứ tự chữ cái. Ví dụ, để in ra các hàm ta có thể dùng được với một mảng, nhập vào: >>> dir(['a', 'short', 'list']) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Hà m dir có thể chạy trên hầu hết mọi thứ, từ chuỗi, số, hàm, module, cho đến đối tượng, lớp. Nhưng đôi khi những thông tin đó không hẳn có tác dụng gì cả. Ví dụ gọi hàm dir trên số 1, nó sẽ đưa ra một loạt các hàm rất đặc biệt (có tên bắt đầu và kết thúc bởi các
dấu gạch dưới) được sử dụng bên trong Python, và không thực sự có ý nghĩa với ta lắm (phần lớn là có thể bỏ qua): >>> dir(1) ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'imag', 'numerator', 'real']
Các hà m Python được viết sẵn
111
H à m dir cực kỳ tiện dụng khi ta đang có một biến và m u ốn nhanh nhanh kiểm tra xem biến này có thể làm được gì. Ví dụ, thử chạy dir với một biến popcorn là một chuỗi, ta sẽ thấy một danh sách các h à m dành cho lớp string (tất cả chuỗi đề u thuộc về lớp string ): >>> popcorn = 'I love popcorn!' >>> dir(popcorn) ['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
Lúc này, em có thể dùng help để có được một vài dòng m ô tả ngắn gọn về từng hàm một. Ví dụ nếu viết help cho hàm upper : >>> help(popcorn.upper) Help on built-in function upper: upper() method of builtins.str instance Return a copy of the string converted to uppercase.
Nội dung trả về nói rằng upper là một hà m được viết sẵn của lớp string và trong trường hợp này nó không cần thêm tham số nào hết. Dòng cuối cùng nói sơ qua ý nghĩa của hà m.
HÀM EVAL Hàm eval (viết tắt của tínhtoán⟨ evaluate⟩ ) nhận một chuỗi làm tham số, coi chuỗi đó như m ột biểu thức Python và c hạy nó. V í dụ eval('print("wow")' ) trên thực tế là sẽ chạy lệnh print(" wow" ) .
112
Chương 9
H à m eval chỉ chạy được với những biểu thức đơn giản, kiểu như: >>> eval('10*5') 50
Biểu thức nào m à nhiều hơn một dòng (kiểu lệnh if chẳng hạn) thường sẽ không chạy: >>> eval('''if True: print("this won't work at all")''') Traceback (most recent call last): File "", line 1, in File "", line 1 if True: print("this won't work at all') ^ SyntaxError: invalid syntax
H à m eval thường được dùng để biến dữ liệu được người dùng nhập vào thành các biểu thức Python. Ví dụ e m có thể viết phần m ề m cho một cái máy tính bỏ túi, với đầu vào là các biểu thức toán và rồi trả ra kết quả tính toán được. Do dữ liệu từ người dùng đều là chuỗi hết, Python sẽ cần chuyển đổi hết sang số và toán tử trước khi thực hiện bất cứ tính toán nào. H à m eval có thể xử lý việc đó vô cùng nhanh gọn: >>> your_calculation = input('Enter a calculation: ') Enter a calculation: 12*52 >>> eval(your_calculation) 624
Ở đây, ta dùng input để ghi lại những gì người dùng nhập từ bàn phím vào biến your_calculation . Dò ng tiếp theo, ta nhập vào biểu thức 12*52 (hẳn là đang nhâ n số tuổi
với số tuần trong năm đây). Ta sử dụng eval để tính phép tính này, và kết quả được in ra ở dòng cuối cùng.
Các hà m Python được viết sẵn
113
HÀM EXEC H à m exec cũng tương tự như h à m eval , nhưng có thể được dùng trong những chương trình phức tạp hơn. Sự khác biệt ở đây là eval có trả về một giá trị nào đó (mà em có thể ghi lại vào trong biến), còn exec thì không. Ví dụ: >>> my_small_program = '''print('ham') print('sandwich')''' >>> exec(my_small_program) ham sandwich
Hai dòng đầu ta tạo ra một biến bằng một chuỗi nhiều dòng gồm hai lệnh print , sau đó dùng exec để chạy. E m có thể dùng exec để chạy các chương trình nhỏ bên trong một chương trình Python — thật luôn, chương trình bên trong chương trình! Việc này khá tiện trong trường hợp phải viết các ứng dụng dài và phức tạp. Ví dụ em có thể viết ra một game robot gọi là Dueling Robots, trong đó có hai con robot chạy quanh màn hình đánh nhau chí choé. Người chơi có thể đưa ra các chỉ dẫn cho robot của mình thông qua các chương trình con. Lúc này game có thể đọc các dòng lệnh và dùng exec để chạy.
HÀM FLOAT H à m float biến m ộ t chuỗi hoặ c số thành một sốthực⟨ realnumber⟩ , là một số có chứa dấu phẩy thập phân (hay còn được gọi là sốcódấuphẩyđộng⟨ floatingpoint⟩ ). Ví dụ, số 10 là một sốnguyên⟨ wholenumber⟩ (người ta thường hay gọi là ⟨integer⟩), còn 10,0, 10,1, 10,2 hay 10,253 đều là số thực (hay được gọi là ⟨float⟩). Ta thường sử dụng số thực (thay vì số nguyên) nếu phải thực hiện các phép tính về
114
Chương 9
tiền. Số thực cũng thường xuyên được sử dụng trong các chương trình đồ họa (game không gian ba chiều chẳng hạn), để tính toán xem các hình các khối sẽ được vẽ như thế nào ở đâu trên m à n hình. Chuyển đổi từ chuỗi sang số thực rất đơn giản, gọi hàm float là xong: >>> float('12') 12.0
Trong chuỗi mà có dấu phẩy cũng đổi được luôn: >>> float('123.456789') 123.456789
Ta có thể dùng float để đổi các giá trị được nhập vào thành các con số đúng chuẩn, cực kỳ có ích khi phải so sánh số được nhập với một giá trị nào đó. Ví dụ, để kiểm tra xem tuổi của ai đó có lớn hơn một số bất kỳ, ta có thể làm như sau: >>> your_age = input('Enter your age: ') Enter your age: 20 >>> age = float(your_age) >>> if age > 13: print('You are %s years too old' % (age - 13)) You are 7.0 years too old
H À M INT H à m int dùng để chuyển đổi một chuỗi hoặc một số bất kỳ thành số nguyên, nói đơn giản thì là tất cả những gì đằng sau dấu phẩy sẽ bị vửt đi hết. Đây là ví dụ về chuyển đổi một số thập phân thành số nguyên: >>> int(123.456) 123
Còn đây là ví dụ chuyển từ chuỗi thành số nguyên: >>> int('123') 123
Các hà m Python được viết sẵn
115
Nhưng nếu chuyển một chuỗi có chứa số thập phân sang số nguyên là gặp lỗi ngay đấy nhé. Ví dụ ở đây ta thử chuyển đổi một chuỗi có chứa số thập phân dùng hàm int : >>> int('123.456') Traceback (most recent call last): File "", line 1, in int('123.456') ValueError: invalid literal for int() with base 10: '123.456'
Em thấy đấy, kết quả là lỗi ValueError .
HÀM LEN H à m len trả về độ dài của một đối tượng, hoặc nếu là chuỗi thì là số ký tự trong chuỗi. Ví dụ, để biết độ dài của chuỗi this is a test string, em có
thể làm như thế này: >>> len('this is a test string') 21
Khi dùng cho mảng hoặc tuple, len trả về số lượng phần tử bên trong: >>> creature_list = ['unicorn', 'cyclops', 'fairy', 'elf', 'dragon', 'troll'] >>> print(len(creature_list)) 6
Khi dùng cho map, len cũng trả về số lượng phần tử bên trong: >>> enemies_map = {'Batman' : 'Joker', 'Superman' : 'Lex Luthor', 'Spiderman' : 'Green Goblin'} >>> print(len(enemies_map)) 3
116
Chương 9
Hàm len rất tiện khi phải làm việc với vòng lặp. Ví dụ, ta có thể dùng nó để in vị trí chỉ m ục của từng phần tử trong mả ng như thế này: >>> fruit = ['apple', 'banana', 'clementine', 'dragon fruit'] >>> length = len(fruit) >>> for x in range(0, length): print('the fruit at index %s is %s' % (x, fruit[x])) the the the the
fruit at index 0 is apple fruit at index 1 is banana fruit at index 2 is clementine fruit at index 3 is dragon fruit
Ta lưu lại độ dài của mảng vào trong biến length ở ❶ , rồi dùng biến đó trong hàm range để tạo ta vòng lặp ở ❷ . Ở ❸ , khi lặp qua từng phần tử trong mảng, ta in ra một câu nói về vị trí chỉ mục của từng phần tử và giá trị của chúng. E m cũng có thể dùng hàm len , nếu e m có một mả ng các chuỗi và muốn in ra từng phần tử bên trong.
HÀM M AX VÀ H ÀM MIN Hàm max trả về phần tử lớn nhất trong mảng, tuple hoặc chuỗi. Ví dụ, để dùng với một mảng số: >>> numbers = [5, 4, 10, 30, 22] >>> print(max(numbers)) 30
Một chuỗi với các ký tự cách nhau bởi dấu phẩy hoặc dấu cách cũng chạy được: >>> strings = 's,t,r,i,n,g,S,T,R,I,N,G' >>> print(max(strings)) t
Như ví dụ trên, các ký tự được sắp xếp theo thứ tự chữ cái, và chữ thường được xếp hạng cao hơn chữ hoa, cho nên t được cho là lớn hơn T .
Các hà m Python được viết sẵn
117
Nhưng e m không nhất thiết phải dùng mảng, tuple hay chuỗi, e m có thể trực tiếp gọi hàm max và truyền các phần tử e m muốn so sánh vào trong ngoặc để làm tham số: >>> print(max(10, 300, 450, 50, 90)) 450
Hàm min cũng tương tự như hàm max , có điều nó trả về phần tử nhỏ nhất trong mảng, tuple hoặc chuỗi. Đây là ví dụ mảng số lúc nãy dùng hàm min thay cho max : >>> numbers = [5, 4, 10, 30, 22] >>> print(min(numbers)) 4
Giả sử em cùng bốn bạn khác cùng chơi một trò chơi đoán số, mỗi người sẽ phải đoán một số nhỏ hơn số của em. Nếu bất kỳ ai đoán số lớn hơn, mọi người đều thua, nhưng nếu tất cả đều đoán số nhỏ hơn thì sẽ thắng. Ta có thể dùng hàm max để xem các số được đoán có nhỏ hơn không như sau: >>> guess_this_number = 61 >>> player_guesses = [12, 15, 70, 45] >>> if max(player_guesses) > guess_this_number: print('Boom! You all lose') else: print('You win') Boom! You all lose
Ở ví dụ trên, ta ghi lại số cần phải đoán vào biến guess_this_number . Số của những người chơi khác đư ợc ghi lại và o m ả n g player_guesses . Lệ nh if so sánh số lớn nhất trong các số được đoán với số guess_this_number , và nếu có bất cứ người nào đoán số lớn hơn, ta s ẽ in ra t h ô n g b á o B o o m ! Y o u all lose .
HÀM RANGE Hàm range , như ta đã gặp trước đây, được sử dụng chủ yếu trong các vòng lặp, dùng để chạy một đoạn code một số lần nhất định. Hai tham số đầu tiên của range là sốđầuvà
118
Chương 9
sốcuối. E m đã thấy h à m range với hai tham số ở ví dụ trên khi kết hợp với hàm len để chạy vòng lặp. Các số được range sinh ra sẽ bắt đầu với tham số thứ nhất và kết thúc với tham số thứ hai trừ đi một. Ví dụ để in ra các số range sinh ra trong khoảng 0 đến 5: >>> for x in range(0, 5): print(x) 0 1 2 3 4
Hàm range thực chất trả về một đối tượng đặc biệt gọi là conchạy⟨ iterator⟩ , đối tượng này sẽ làm đi làm lại việc gì đó một số lần nhất định. E m có thể biến con chạy này sang m ột mả ng (dùng h à m list ). Khi in ra kết quả e m sẽ thấy các con số bên trong nó: >>> print(list(range(0, 5))) [0, 1, 2, 3, 4]
E m cũng có thể thêm một tham số thứ ba vào range , là sốbước. Nếu ta không truyền số bước vào, thì mặc định nó sẽ là 1. Nhưng nếu ta truyền 2 vào thì sao? Kết quả đây: >>> count_by_twos = list(range(0, 30, 2)) >>> print(count_by_twos) [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
Mỗi số trong mảng giờ đây được cộng thêm hai từ số đằng trước nó, và mảng giờ sẽ kết thúc ở số 28, tức là nhỏ hơn 2 so với 30.
Các hà m Python được viết sẵn
119
Em cũng có thể dùng số bước âm: >>> count_down_by_twos = list(range(40, 10, -2)) >>> print(count_down_by_twos) [40, 38, 36, 34, 32, 30, 28, 26, 24, 22, 20, 18, 16, 14, 12]
HÀM SUM Hàm sum cộng các phần tử trong mảng lại và trả ra tổng. Ví dụ: >>> my_list_of_numbers = list(range(0, 500, 50)) >>> print(my_list_of_numbers) [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] >>> print(sum(my_list_of_numbers)) 2250
Dòng đầu tiên ta tạo ra một mảng số từ 0 đến 500, dùng range với số bước là 50. Tiếp, ta in m ả ng này ra để xe m nó như thế nào. Cuối cùng ta truyền m ả ng my_list_of_numbers vào h à m sum với print(sum(my_list_of_numbers)) đ ể c ộng tất cả các phầ n tử trong mảng, để có tổng là 2250.
L À M V IỆ C V Ớ I FILE File Python cũng giống y như tất cả các file khác trong máy tính của em: tài liệu, ảnh, nhạc, game, … Thực tế là tất cả mọi thứ trong máy tính đều được lưu lại dưới dạng file. Giờ ta sẽ xem làm sao để m ở và làm việc với file trong Python sử dụng hàm open được dựng sẵn nhé. Nhưng trước tiên ta cần tạo ra một file mới để nghịch dần.
T Ạ O FILE Ta sẽ thử tạo ra một file đơn giản gọi là test.txt. Làm theo các bước sau tuỳ vào hệ điều hành e m đang dùng.
120
Chương 9
T ẠO FILE TRÊN W IN DO W S
Nếu đang dùng Windows, làm theo các bước sau để tạo file test.txt: 1.
Mở Start ► All Programs ► Accessories ► Notepad.
2.
Gõ vài dòng gì đó vào file.
3.
Chọn File ► Save.
4.
Khi được hỏi, nháy đúp vào My Computer rồi nháy đúp vào Local Disk (C:)
5.
Nháy đúp vào thư m ục Usersrồi nháy đúp tiếp vào tên e m
6.
Nhập test.txtvào ô File name ở bên dưới.
7.
Cuối cùng nhấn nút Save.
T Ạ O FILE T R Ê N M A C O S X
Nếu đang dùng Mac, làm theo các bước sau để tạo file test.txt: 1.
Nhấn vào biểu tượng Spotlight ở thanh menu góc trên bên phải mà n hình.
2.
Gõ TextEditvào ô tìm kiếm.
3.
Chương trình TextEdit sẽ hiển thị ra trong phần Applications. Nhấn vào để m ở ra trình soạn thảo. ( Em cũng có thể tìm thấy TextEdit trong thư mục Applications trên Finder) Các hà m Python được viết sẵn
121
4.
Gõ vài dòng gì đó vào file.
5.
Chọn Format ► Make Plain Text.
6.
Chọn File ► Save.
7.
Trong ô Save As, nhập test.txt.
8.
Trong phần Places bên trái, chọn tên em — là tên em dùng để đăng nhập vào máy.
9.
Cuối cùng nhấn nút Save.
T Ạ O F ILE T R Ê N U B U N T U
Nếu đang dùng Ubuntu, làm theo các bước sau để tạo file test.txt: 1.
M ở trình soạn thảo ra, thường có tên là Text Editor. Nếu chưa m ở nó ra bao giờ thì tìm ở menu Applications.
2.
Gõ vài dòng gì đó vào file.
3.
Chọn Save.
4.
Trong ô Name, nhập vào test.txt. Thư mục gốc có thể đã được chọn sẵn, còn nếu không thì chọn nó trong mục Places bên tay trái (Thư mục gốc là thư mục có tên e m — là tên em dùng để đăng nhập vào máy)
122
Chương 9
5.
Cuối cùng nhấn nút Save.
M Ở F ILE T R O N G P Y T H O N open là hàm được dựng sẵn trong Python dùng để m ở file trong Python shell và
hiển thị ra nội dung file. Chọn file như thế nào tuỳ thuộc vào em đang có hệ điều hành gì. Xem ví dụ trên Windows dưới đây, rồi nếu cần thì đọc thêm về Mac hay Ubuntu bên dưới. MỞ FILE TRÊN WINDOWS
Nếu đang dùng Windows, dùng đoạn code sau để m ở test.txt: >>> test_file = open('c:\\Users\\\\test.txt') >>> text = test_file.read() >>> print(text) There once was a boy named Marcelo Who dreamed he ate a marshmallow He awoke with a start As his bed fell apart And he found he was a much rounder fellow
Dòng đầu tiên ta dùng hàm open , hàm này sẽ trả về một đối tượng file cùng với các hàm để có thể thao tác trên file đó. Tham số được truyền vào hàm open là một chuỗi đường dẫn để Python biết mà tìm thấy file đó ở đâu. Nếu đang dùng Windows và lưu file ở thư Các hà m Python được viết sẵn
123
m ụ c gốc trên ổ C:, e m sẽ truyền đư ờng dẫn đến file là c:\\Users\\\\test.txt.
Hai dấu gạch ngược trên đường dẫn trong Windows là để nói Python rằng dấu gạch ngược là dấu gạch ngược chứ không phải là lệnh gì đặc biệt cả (Còn nhớ ở Chương 3, các dấu gạch ngược có ý nghĩa như thế nào trong Python, đặc biệt là trong chuỗi không?) Ta lưu lại đối tư ợng file n à y và o biến test_file . Dòng thứ hai ta sử dụng hàm read có sẵn trong đối tượng, để đọc nội dung của file và lưu lại nội dung này vào trong biến text . Ta in nội dung biến này ra m à n hình ở dòng cuối cùng. MỞ FILE TRÊN MAC OS X
Nếu đang dùng Mac OS X, em sẽ cần phải điền vào một đường dẫn khác so với dòng đầu tiên của ví dụ trên với Windows để mở file test.txt. Sử dụng tên em — tên mà lúc em chọn để lưu lại file lúc nãy. Ví dụ nếu tên e m là sarahwintersthì tham số cho hà m open sẽ trông như thế này: >>> test_file = open('/Users/sarahwinters/test.txt') MỞ FILE TRÊN UBUNTU
Nếu đang dùng Ubuntu, em sẽ cần phải điền vào một đường dẫn khác so với dòng đầu tiên của ví dụ trên với Windows để mở file test.txt. Sử dụng tên em — tên mà lúc em chọn để lưu lại file lúc nãy. Ví dụ nếu tên em là jacobthì tham số cho hàm open sẽ trông như thế này: >>> test_file = open('/home/jacob/test.txt')
GHI R A FILE Đối tượng file được trả về lúc nãy còn có nhiều hàm khác nữa ngoài read . Ta có thể tạo ra một file mới, trắng tinh bằng cách truyền vào tham số thứ hai, chuỗi 'w' , khi gọi hàm:
124
Chương 9
>>> test_file = open('c:\\myfile.txt', 'w')
Tham số 'w' nói Python rằng ra muốn dùng đối tượng file này để ghi chứ không phải để đọc. Từ giờ ta có thể tha hồ ghi thêm dữ liệu vào file mới này dùng hàm write : >>> test_file = open('c:\\myfile.txt', 'w') >>> test_file.write('this is my test file') 20
Cuối cùng, ta phải nói với Python rằng ta đã làm việc xong với file bằng hàm close : >>> test_file = open('c:\\myfile.txt', 'w') >>> test_file.write('What is green and loud? A froghorn!') >>> test_file.close()
Giờ nếu mở file với trình soạn thảo trên máy, em sẽ thấy nội dung của nó có chữ Wh at is green and loud? A froghorn! Hoặc, e m cũng có thể dùng Python để đọc: >>> test_file = open('myfile.txt') >>> print(test_file.read()) What is green and loud? A froghorn!
TÓM TẮT Trong c hư ơng này, e m đã thấy các h à m dự ng sẵn trong Python, như float và int được dùng để đổi số thập phân thành số nguyên và ngược lại. E m cũng thấy hàm len giúp ta viết vòng lặp dễ dàng hơn, và làm thế nào để dùng Python m ở file ra để đọc hoặc để ghi.
BÀI TẬP LẬP TRÌNH Hãy làm những ví dụ sau để thử một vài hàm dựng sẵn trong Python. Câu trả lời có thể tìm thấy trên http://python-for-kids.com/ .
Các hà m Python được viết sẵn
125
#1: Đ O Ạ N C O D E B Í Ẩ N Đoạn code sau sẽ trả về kết quả gì? Đoán trước rồi chạy thử xem đúng hay sai. >>> a = abs(10) + abs(-10) >>> print(a) >>> b = abs(-10) + -10 >>> print(b)
#2 : T H Ô N G Đ I Ệ P B Í M Ậ T Dùng dir và help , tìm cách làm sao để bẻ một câu thành mảng có nhiều từ, rồi viết một chương trình đơn giản để hiển thị ra từng từ xen kẽ nhau của chuỗi sau, bắt đầu bằng từ đầu tiên ( this ): "this if is you not are a reading very this good then way you to have hide done a it message wrong"
#3: S A O C H É P F I L E Viết một chương trình Python để chép một file (Gợi ý: em cần phải mở file muốn chép ra, đọc nội dung của nó, rồi tạo ra file mới — là bản sao ta cần.) Kiểm tra xem chương trình chạy có đúng không bằng cách in nội dung của file lên mà n hình.
126
Chương 9
Như đã thấy ở Chương 7, module trong Python chỉ đơn giản là một tổ hợp các hàm, lớp và biến. Python dùng module để nhóm các hàm và lớp vào để về sau dễ dùng. Ví dụ như module turtle ta dùng lúc trước, là tập hợp các hàm và lớp được dùng để tạo ra bảng vẽ để cho rùa vẽ trên mà n hình. Khi mang một module vào trong chương trình, e m có thể sử dụng tất cả mọi thứ trong đó. Ví dụ khi mang module turtle vào ở Chương 4, ta có thể truy cập vào lớp Pen , là lớp dùng để tạo các đối tượng đại diện cho bảng vẽ của rùa. >>> import turtle >>> t = turtle.Pen()
Python có rất nhiều module để làm đủ các thể loại việc khác nhau. Trong chương này ta sẽ xe m qua các module thông dụng nhất và dùng thử chúng nhé.
Các module hay dùng trong Python
127
TẠO CÁC BẢN SAO VỚI MODULE COPY Module copy có chứa các hàm để tạo ra bản sao của các đối tượng. Thông thường trong lúc lập trình, e m hay tạo ra các đối tượng mới, nhưng thi thoảng cũng có những lúc lại cần sao chép các đối tượng này, rồi lại dùng bản sao này đi tạo các đối tượng khác, nhất là những trường hợp để tạo đối tượng cần phải trải qua nhiều bước. Ví dụ, giả sử ta có lớp Animal , với hàm khởi tạo __init__ n h ậ n c á c t h a m s ố g ồ m species ( c h ủ n g loại), number_of_legs (số chân), và color (m à u sắc): >>> class Animal: def __init__(self, species, number_of_legs, color): self.species = species self.number_of_legs = number_of_legs self.color = color
Ta có thể tạo ra một đối tượng mới trong lớp Animal dùng đoạn code sau. Thử tạo một con Bằng M ã sáu chân m à u hồng đặt tên là harry nhé: >>> harry = Animal('hippogriff', 6, 'pink')
Giả sử ta muốn tạo ra một đàn Bằng M ã sáu chân thì sao? Ta có thể lặp đi lặp lại đoạn code trên, hoặc là dùng hàm copy ở trong module copy : >>> import copy >>> harry = Animal('hippogriff', 6, 'pink') >>> harriet = copy.copy(harry) >>> print(harry.species) hippogriff >>> print(harriet.species) hippogriff
128
Chương 10
Ta vừa tạo ra một đối tượng và gọi nó là harry , rồi ta tạo ra một bản sao của nó và gọi là harriet . Đâ y là hai đối tượng hoàn toàn khác nhau nhé, mặ c dù cùng chủng loại. Làm thế này ở đây tuy chỉ giúp tiết kiệm được tí chút công gõ code, nhưng khi các đối trượng trở nên phức tạp hơn, có khả năng nhân bản chúng lên sẽ giúp ích nhiều lắm đấy. Ta cũng có thể dùng copy để tạo và sao chép cả một mảng các đối tượng Animal . >>> harry = Animal('hippogriff', 6, 'pink') >>> carrie = Animal('chimera', 4, 'green polka dots') >>> billy = Animal('bogill', 0, 'paisley') >>> my_animals = [harry, carrie, billy] >>> more_animals = copy.copy(my_animals) >>> print(more_animals[0].species) hippogriff >>> print(more_animals[1].species) chimera
Ba dòng đầu ra tạo ra ba đối tượng Animal , đặt là harry , carrie , và billy . D ò n g thứ tư ta nhét c hú ng hết v à o m ả n g my_animals . Tiếp ta sử dụng hàm copy để tạo ra một mảng mới more_animals . Cuối cùng ta hiển thị ra giống của hai con đầu tiên ( [0] và [1] ) của m ả n g more_animals v à thấy rằng c húng giống y như m ả ng gốc: hippogriff v à chimera . Ta vừa tạo ra bản sao của cả một mảng m à
không cần phải khởi tạo lại từng đối tượng một. Nhưng hãy xem nếu ta thay đổi gì đó của một trong số mấy đối tượng Animal của m ả ng gốc my_animals (từ hippogriff sang ghoul ) thì sao nhé. Python sẽ sửa luôn ở cả mả ng more_animals đấy: >>> my_animals[0].species = 'ghoul' >>> print(my_animals[0].species) ghoul >>> print(more_animals[0].species) ghoul Các module ha y dùng trong Python
1 29
Quái lạ. Mình chỉ sửa mỗi ở bên mảng my_animals thôi mà? Sao cả hai mảng lại đều bị thay đổi thế này? Đó là do hàm copy thực ra chỉ tạo ra một bản saochépsơbộ⟨ shallowcopy⟩ , tức là nó không sao chép các đối tượng nằm bên trong đối tượng được sao chép. Ở đây ta đã sao chép đối tượng chính là mảng chứ không phải là các phần tử riêng biệt bên trong nó. Vậy cho nên cuối cùng ta có một mảng mới nhưng lại không có các phần tử mới — mảng more_animals có y nguyên ba phần tử cũ trong nó.
Tương tự như vậy, nếu ta thêm một con nữa vào mảng gốc ( my_animals ), nó sẽ không xuất hiện trong mảng mới đâu ( more_animals ). Chứng minh nhé, hiển thị ra độ dài của mảng sau khi thêm vào là biết ngay: >>> sally = Animal('sphinx', 4, 'sand') >>> my_animals.append(sally) >>> print(len(my_animals)) 4 >>> print(len(more_animals)) 3
E m thấy đấy, khi thêm một phần tử mới vào mảng gốc my_animals , nó không được tự thêm vào mảng kia more_animals . Khi dùng hà m len và hiển thị ra kết quả, m ả ng thứ nhất có bốn phần tử còn mảng thứ hai chỉ có ba thôi. Một hàm khác trong module copy là deepcopy , có thể tạo ra bản sao của tất cả các đối tượng nằm bên trong đối tượng được sao chép. Nếu dùng deepcopy để sao chép my_animals , ta sẽ có một mảng hoàn toàn mới với bản sao mới của tất cả các phần tử bên
trong nó. Kết quả là, thay đổi ở bên trong các đối tượng Animal gốc không ảnh hưởng gì đến mảng mới hết. Ví dụ đây: >>> more_animals = copy.deepcopy(my_animals) >>> my_animals[0].species = 'wyrm' >>> print(my_animals[0].species) wyrm
130
Chương 10
>>> print(more_animals[0].species) ghoul
Khi thay đổi đối tượng đầu tiên trong mảng gốc từ ghoul sang wyrm , mảng bản sao không thay đổi gì hết, ta có thể thấy rõ khi hiển thị ra phần tử đầu tiên của mỗi mảng.
GHI NHỚ CÁC TỪ KHOÁ VỚI MODULE KEYWORDS Từkhoá⟨ keyword⟩ trong Python là từ là bản thân nó chính là một phần của ngôn ngữ lập trình, n hư if , else ha y for . M odul e keywords c ó c hứa một h à m tên là isk eyword và một biến tên là kwlist . H à m iskeyword trả về True nếu một chuỗi là từ khoá trong Python. Cò n biến kwlist trả về một m ả ng chứa tất cả các từ khoá trong Python. Để ý là trong đoạn code dưới đây, hàm iskeyword trả về True đối với chuỗi if và trả về False với chuỗi ozwald . E m cũng có thể thấy được danh sách đầy đủ các từ khoá nếu hiển thị ra các phần tử của mảng, việc này rất có ích vì các từ khoá không phải lúc nào cũng giống nhau hết. Các phiên bản mới hơn (hoặc cũ hơn) của Python có thể có các từ khoá khác nhau. >>> import keyword >>> print(keyword.iskeyword('if')) True >>> print(keyword.iskeyword('ozwald')) False >>> print(keyword.kwlist) ['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
E m có thể tham khảo m ô tả chi tiết của từng từ khoá trong phần Phụ lục.
Các module ha y dùng trong Python
131
LẤY SỐ NGẪU NHIÊN TỪ MODULE R AN DO M Module random có chứa một cơ số các hàm cực kỳ tiện dụng để sinh ra các số ngẫu nhiên — kiểu em tự nhiên bảo máy tính “chọn một số bất kỳ đi”. Các hàm được sử dụng thư ờng xuyê n nhất trong modul e rand om là randint , choice và shuffle .
DÙNG RANDINT ĐỂ CHỌN MỘ T SỐ NGẪU NHIÊN H à m randint sẽ chọn ra một số nguyên ngẫu nhiên trong một dãy số, như là giữa 0 với 100, hoặc giữa 100 với 1000, hoặc giữa 1000 với 5000. Ví dụ: >>> import random >>> print(random.randint(1, 100)) 58 >>> print(random.randint(100, 1000)) 861 >>> print(random.randint(1000, 5000)) 3795
E m có thể sử dụng randint để làm một game đoán số đơn giản (và rất khó chịu nha), sử dụng vòng lặp while như thế này: >>> import random >>> num = random.randint(1, 100) >>> while True: print('Guess a number between 1 and 100') guess = input() i = int(guess) if i == num: print('You guessed right') break elif i < num: print('Try higher') elif i > num: print('Try lower')
132
Chương 10
Đầu tiên ta phải mang module random vào, rồi gán một số ngẫu nhiên nằm trong khoảng 1 đến 100 được sinh ra bởi hàm randint vào biến num . Sau đó ta tạo ra một vòng lặp while ở ❶ , vòng này sẽ lặp vô hạn (hoặc là cho đến khi người chơi đoán ra đúng số). Tiếp, ta hiển thị ra câu đố ở ❷ , và ghi lại câu trả lời của người chơi thông qua h à m input vào biến guess ở ❸ . Rồi đổi câu trả lời sang số bằng h à m int và lưu nó vào biến i ở ❹ . Sau đó ta so sánh số này với số được sinh ra ngẫu nhiên lúc đầu ở ❺. N ế u câu trả lời đúng bằng số ngẫu nhiên, ta in ra kết quả Y o u guessed right , và thoát ra khỏi vòng lặp ở ❻ . Nếu câu trả lời không khớp, ta kiểm tra xem là số đó lớn hơn ❼ hay nhỏ hơn ❽ số ngẫu nhiên ban đầu, rồi đưa ra các gợi ý tương ứng. Chương trình này hơi dài một chút, e m nên gõ vào một cửa sổ shell mới hoặc tạo ra một file mới, lưu lại rồi chạy trong IDLE. Nhắc lại cách mở và chạy một chương trình nhé: 1.
Bật IDLE và chọn File ► Open.
2.
M ở thư mục em để file và nhấn vào filename để chọn.
3.
Nhấn Open.
4.
Khi cửa sổ mở ra, chọn Run ► Run Module. Kết quả ta có:
Các module hay dùng trong Python
133
DÙNG CHOICE ĐỂ CHỌN PHẦN TỬ NGẪU NHIÊN TRONG MẢ NG Nế u m uốn chọn một phần tử ngẫu nhiên trong m ả n g thay vì một số ngẫu nhiên trong một khoảng số, e m có thể dùng choice . Ví dụ, e m có thể muốn Python chọn đồ ngọt cho e m chẳng hạn: >>> import random >>> desserts = ['ice cream', 'pancakes', 'brownies', 'cookies', 'candy'] >>> print(random.choice(desserts)) brownies
Nghe vẻ em sẽ có bánh sôcôla — ngon lành đấy chứ.
DÙNG SHUFFLE ĐỂ TRỘN LẪN M Ộ T M Ả N G H à m shuffle dùng để đảo lộn một mảng, các phần tử sẽ bị trộn lẫn lên. N ế u e m vẫn đang làm trên IDLE từ ví dụ trước, tức là đã mang module random vào và đã tạo ra một m ả n g các đồ ngọt, e m co thể nhảy luôn sang câu lệnh random.shuffle trong đoạn code sau: >>> import random >>> desserts = ['ice cream', 'pancakes', 'brownies', 'cookies', 'candy'] >>> random.shuffle(desserts) >>> print(desserts) ['pancakes', 'ice cream', 'candy', 'brownies', 'cookies']
E m có thể thấy kết quả của việc đảo lộn này khi hiển thị ra mảng — thứ tự các phần tử hoàn toàn khác trước. Nếu có viết một game chơi bài, e m có thể dùng hàm này để trộn mảng, hoặc trộn bài.
134
Chương 10
ĐIỀU KHIỂN SHELL VỚI MODULE SYS Module sys có chứa các hàm liên quan đến hệ thống e m có thể dùng để điều khiển chính trình thông dịch Python. T a sẽ nói qua về h à m exit , đối tượng stdin v à stdout , và b i ế n versi on .
DÙNG EXIT ĐỂ THOÁT RA KHỎI PYTHON Dùng hà m exit là cách để thoát ra khỏi môi trường Python (hoặc dừng chương trình đang chạy lại) — tuy nhiên nó sẽ không có tác dụng gì trong shell cả (đúng như theo thiết kế đấy). Nếu nhập đoạn code sau vào trong shell, sẽ không có chuyện gì xảy ra hết: >>> import s >>> sys.exit()
Trong khi, nếu cũng chạy code trên ở trong Python console, nó sẽ tắt Python đi:
Đ Ọ C D Ữ L IỆ U V Ớ I Đ Ố I T Ư Ợ N G S T D I N Đối tượng stdin (viết tắt của chuẩnđầuvào⟨ standardinput⟩ ) trong module sys cho phép người dùng tự nhập thông tin vào trong shell và sử dụng trong chương trình. Như đã học ở Chương 7, đối tượng này có một hàm tên là readline , dùng để đọc hết những gì được người dùng nhập vào bằng bàn phím cho đến khi họ nhấn phím ENTER . Nó chạy y như hà m input m à ta vừa dùng trong game đoán số lúc nãy.
Các module hay dùng trong Python
135
Ví dụ, gõ đoạn code sau: >>> import sys >>> v = sys.stdin.readline() He who laughs last thinks slowest
P y t h o n s ẽ g hi lại c h u ỗi H e w h o l a u gh s last t h i n k s slowest v à o bi ế n v . Đ ể k i ể m tra ta có thể hiển thị ra nội dung của v : >>> print(v) He who laughs last thinks slowest
M ộ t trong nhữ ng đi ểm khác biệt giữa input v à readline là với readline , e m có thể đặt ra số ký tự muốn đọc nhất định (tuy nhiên vào thời điểm hiện tại việc này thì đúng trên console chứ trên shell thì không). Ví dụ: >>> v = sys.stdin.readline(13) He who laughs last thinks slowest >>> print(v) He who laughs
GHI D Ữ LIỆU VỚI ĐỐI TƯ ỢNG STDOUT Không như stdin , đối tượng stdout (viết tắt của chuẩnđầura⟨ standardoutput⟩ ) có thể được dùng để viết ra các dữ liệu ra shell (hoặc console), thay vì đọc vào. Nói cách khác, nó gần như h à m print , như ng stdout lại là m ột đối tượng, nên ta có thể dùng các h à m như đã dùng ở Chương 9, như write . Ví dụ: >>> import sys >>> sys.stdout.write("What does a fish say when it swims into a wall? Dam.") What does a fish say when it swims into a wall? Dam.52
Để ý là sau khi viết xong, nó trả về một con số, chính là số ký tự được viết ra. Em có thể thấy số 52 được hiển thị ra trên shell ở cuối dòng. Ta có thể ghi lại số này vào một biến để đếm lại, cùng với thời gian, tổng số bao nhiêu ký tự đã được viết ra màn hình.
136
Chương 10
MÌNH ĐANG DÙNG PYTHON BẢ N NÀO VẬY TA? Biến version lưu thông tin phiên bả n Python e m đang dùng, rất hữu ích khi muốn chắc chắn là mình đã có bản mới nhất. Có những lập trình viên thích hiển thị hết nhữ ng thông tin nà y ra khi chương trình bắt đầu chạy. Ví dụ em có thể viết số phiên bản của Python ra ở cửa sổ About trong chương trình của mình, như thế này: >>> import sys >>> print(sys.version) 3.9.0 (v3.9.0:9cf6752276, Oct [Clang 6.0 (clang-600.0.57)]
5 2020, 11:29:23)
L À M VIỆC VỚI THỜI GIAN BẰNG MODU LE TIME Module time trong Python chứa các hàm dùng để hiển thị thời gian, tuy nhiên có thể nó không như e m hình dung đâu nhé. Thử cái này xem: >>> import time >>> print(time.time()) 1606952663.237674
Con số được trả về bởi h à m time() thực chất chính là số giây tính từ lúc 0 giờ 0 phút 0 giây, ngày mùng 1 tháng 1 năm 1970. Con số này nếu đứng một mình thế kia trông cũng chả có tác dụng gì mấy, nhưng đều có mục đích cả đấy. Ví dụ, để xem mỗi một phần nhỏ trong chương trình của ta chạy mất bao lâu, ta có thể ghi lại thời điểm bắt đầu và thời điểm kết thúc, rồi so sánh chúng với nhau. Ta sẽ thử xem in tất cả số từ 0 đến 999 thì mất bao lâu nhé.
Các module hay dùng trong Python
137
Đầu tiên, tạo hàm này: >>> def lots_of_numbers(max): for x in range(0, max): print(x)
Tiếp, gọi hàm này với max là 1000: >>> lots_of_numbers(1000)
Giờ để xem hàm này chạy mất bao lâu ta sẽ chèn module time vào giữa: >>> def lots_of_numbers(max): t1 = time.time() for x in range(0, max): print(x) t2 = time.time() print('it took %s seconds' % (t2-t1))
Gọi lại chương trình lần nữa, ta có kết quả như sau (có thể rất khác đối với em, tuỳ vào m á y nhanh c hậ m thế nào): >>> lots_of_numbers(1000) 0 1 2 3 . . . 997 998 999 it took 50.159196853637695 seconds
Đây, nó chạy thế này: đầu tiên ta gọi hàm time() và gán giá trị trả về vào biến t1 ở ❶ . Sau đó ta chạy vòng lặp và hiển thị ra tất cả các số ở dòng thứ ba và thứ tư tại ❷ . Sau khi chạy xong vòng lặp ta gọi hàm time() thêm một lần nữa, và gán giá trị trả về cho t2 ở ❸ . Vòng lặp chắc chắn sẽ mất một thời gian để chạy nên giá trị của t2 sẽ luôn lớn hơn t1 vì lúc này ta đã ở xa hơn mốc ngày 1 tháng 1 năm 1970. Lấy t2 trừ t1 ở ❹ , ta sẽ có được số giây là thời gian để in hết đống số kia.
138
Chương 10
CHUYỂN ĐỔI NGÀY THÁNG VỚI ASCTIME H à m asctime nhậ n ngày tháng n ă m trong một tuple và chuyển nó sang m ột dạng khác cho dễ đọc hơn. (Còn nhớ tuple không, nó giống như một mảng nhưng em lại không sửa được các phần tử bên trong nó). Như đã thấy ở Chương 7, gọi asctime m à không đưa tham số nào vào sẽ hiển thị ra ngày tháng và thời gian hiện tại dưới dạng ta có thể đọc được. >>> import time >>> print(time.asctime()) Fri Jan 8 10:02:21 2021
Để gọi asctime với tham số, đầu tiên ta sẽ tạo ra một tuple với các giá trị cho ngày, tháng và thời gian. Ví dụ ta sẽ gán tuple này cho biến t: >>> t = (2007, 5, 27, 10, 30, 48, 6, 0, 0)
Các giá trị này theo thứ tự là năm, tháng, ngày, giờ, phút, giây, ngày trong tuần (0 là Thứ hai, 1 là Thứ ba, vân vân), ngày trong năm (đây ta đặt tạm là 0), và giờ mùa đông (0 là giờ mùa đông, 1 là giờ mùa hè). Gọi asctime với tuple này, ta có: >>> import time >>> t = (2020, 2, 23, 10, 30, 48, 6, 0, 0) >>> print(time.asctime(t)) Sun Feb 23 10:30:48 2020
LẤY NGÀY THÁNG VÀ THỜI GIAN VỚI LOCALTIME Khá c với asctime , h à m localtime trả về ngà y tháng và thời gian hiện tại dưới dạng đối tượng, giá trị cũng đại khái tương đương với asctime . Nếu hiển thị ra đối tượng này em sẽ thấy tên lớp, và mỗi giá trị sẽ được đánh dấu là tm_year (năm), tm_mon (tháng), tm_mday (ngày trong tháng), tm_hour (giờ), vân vân.
Các module ha y dùng trong Python
139
>>> import time >>> print(time.localtime()) time.struct_time(tm_year=2020, tm_mon=2, tm_mday=23, tm_hour=22, tm_min=18, tm_sec=39, tm_wday=0, tm_yday=73, tm_isdst=0)
Để hiển thị ra năm và tháng hiện tại, ta có thể sử dụng vị trí chỉ mục (y như vị trí của tuple ta sử dụng với asctime ). Từ ví dụ trên, ta biết rằng năm là ở vị trí đầu tiên (vị trí 0) và tháng là ở vị trí thứ hai (1). D o vậ y ta sẽ gá n year = t[0] và m o n t h = t[1] n hư thế này: >>> t = time.localtime() >>> year = t[0] >>> month = t[1] >>> print(year) 2020 >>> print(month) 2
Và ta thấy là ta đang ở tháng thứ hai của năm 2020
L À M THỜI GIAN C H Ậ M LẠI VỚI SLEEP H à m sleep khá hữ u dụng khi ta m u ố n hoã n hoặc khiến chương trình chạy chậm lại một chút. Ví dụ, để hiển thị từng số giây từ 1 đến 60, ta có thể viết vòng lặp sau: >>> for x in range(1, 61): print(x)
Đoạn code này sẽ in ra các số từ 1 đến 60 cực kỳ nhanh. Tuy nhiên ta có thể nói Python chờ một giây trước khi in số tiếp theo, như thế này: >>> for x in range(1, 61): print(x) time.sleep(1)
Việc này sẽ trì hoãn việc in ra từng số một. Trong Chương 12, ta sẽ sử dụng hàm sleep để làm cho các hình động diễn ra trông giống thật hơn.
140
Chương 10
L Ư U LẠI D Ữ LIỆU B Ằ N G M O D U L E PICKLE Module pickle dùng để chuyển đổi các đối tượng Python sang một dạng gì đó có thể ghi lại thành file và rồi có thể dễ dàng đọc lại. Em sẽ thấy pickle cực kỳ có tác dụng khi viết game và muốn ghi lại kết quả hiện tại của người chơi. Ví dụ, ta có thể thêm chức năng lưu lại game như sau: >>> game_data = { 'player-position' : 'N23 E45', 'pockets' : ['keys', 'pocket knife', 'polished stone'], 'backpack' : ['rope', 'hammer', 'apple'], 'money' : 158.50 }
Ta vừa mới tạo ra một map trong Python có chứa vị trí hiện tại của người chơi trong game tưởng tượng, một danh sách các vật phẩm trong túi đồ, và số tiền người chơi đang có. Ta có thể lưu lại map này vào file bằng cách mở một file ra để ghi, rồi gọi hàm dump của m odul e pickle , n hư thế này: >>> import pickle >>> game_data = { 'player-position' : 'N23 E45', 'pockets' : ['keys', 'pocket knife', 'polished stone'], 'backpack' : ['rope', 'hammer', 'apple'], 'money' : 158.50 } >>> save_file = open('save.dat', 'wb') >>> pickle.dump(game_data, save_file) >>> save_file.close()
Ta mang module pickel vào ở ❶ , rồi tạo ra map ở ❷ . Ở ❸ , ta m ở file save.datra với tham số ’wb’ , tức là ghifilenhịphân⟨ writebinary⟩ (em sẽ cần lưu lại file này ở các thư m ụ c như /Users/malcolmozwald , /home/susanb/ha y C:\\Users\JimmyIpswich, như ta làm ở Chương 9.) Ở ❹, ta dùng hàm dump rồi truyền map và biến file vào. Cuối cùng, ở ❺ ta đóng file lại, vì đã xong việc rồi. Các module ha y dùng trong Python
141
CHÚ Ý Cácfile đơn giản ⟨plain text file⟩ chỉchứacáckýtựmàconngườicóthểđọcđược.Nhưng hìnhảnh,filenhạc,phimhaycácđốitượngPythoncònchứanhữngthôngtinkhácnữa màkhôngphảilúcnàotacũngcóthểđọcvàhiểuđược,tathườnggọichúnglàfile nhị phân ⟨binary file⟩.Nếumởfilesave.dat ra,emsẽthấynóchẳngcóýnghĩagìhết;chỉlàmột đốngcáckýtựlungtungtrộnlẫnvàovớinhau.
Ta có thể giải m ã các đối tượng trong file này bằng hàm load của pickle . Khi giải mã, ta thực ra đang đảo ngược lại quá trình mã hoá: Ta lấy lại các thông tin được viết trong file ra và chuyển chúng lại thành các giá trị mà ta có thể dùng lại được trong chương trình. Cũng tương tự như lúc ra dùng hàm dump : >>> load_file = open('save.dat', 'rb') >>> loaded_game_data = pickle.load(load_file) >>> load_file.close()
Đầu tiên ta mở file ra với tham số rb , tức là đọcfilenhịphân⟨ writebinary⟩ . Sau đó ta truyền file vào và gán giá trị trả ra cho biến loaded_game_data . Cuối cùng ta đóng file lại. Để đảm bảo thông tin được ghi lại được lấy ra đúng, ta hiển thị biến ra: >>> print(loaded_game_data) {'money': 158.5, 'backpack': ['rope', 'hammer', 'apple'], 'player-position': 'N23 E45', 'pockets': ['keys', 'pocket knife', 'polished stone']}
TÓM TẮT Trong chương này em đã thấy trong Python, module nhóm các hàm, lớp và biến lại với nhau như thế nào, và làm thế nào để gọi lại chúng bằng cách mang các module vào.
142
Chương 10
E m cũng thấy làm thế nào để sao chép các đối tượng, tạo số ngẫu nhiên, xáo trộn mảng các đối tượng, cũng như làm việc với thời gian như thế nào. Cuối cùng, em học được cách ghi và tải lại các thông tin trong file dùng pickle .
BÀI TẬP LẬP TRÌNH Thử các bài thực hành sau dùng module trong Python. Câ u trả lời có thể tìm thấy trên http://python-for-kids.com/ .
#1: N H Â N B Ả N X E Đoạn code sau sẽ in ra những gì? >>> import copy >>> class Car: pass >>> car1 = Car() >>> car1.wheels = 4 >>> car2 = car1 >>> car2.wheels = 3 >>> print(car1.wheels) # Doan code nay se in ra cai gi? >>> car3 = copy.copy(car1) >>> car3.wheels = 6 >>> print(car1.wheels) # Doan code nay se in ra cai gi?
#2: Đ Ó N G G Ó I D A N H S Á C H Y Ê U T H Í C H Tạo ra một danh sách vài việc e m thích làm, rồi dùng pickle để lưu chúng lại thành một file gọi là favorites.dat. Đóng Python shell lại, rồi mở ra cửa sổ mới, mở file rồi in danh sách đó ra.
Các module ha y dùng trong Python
143
144
Chương 10
Cùng gặp lại module turtle lúc trước học ở Chương 4 nhé. Lần này e m sẽ thấy rùa Python không chỉ vẽ được có mỗi mấy đường thằng đen đen đâu nhé. Ví dụ, e m có thể dùng nó để vẽ ra những hình khối đa dạng hơn, e m còn có thể tạo màu rồi tô mà u lên hình nữa.
BẮT ĐẦU VỚI HÌNH VUÔNG Ta đã học cách dùng rùa để vẽ những hình đơn giản. Trước khi sử dụng ta phải m a ng module turtle vào và tạo ra đối tượng Pen : >>> import turtle >>> t = turtle.Pen()
Còn đây là đoạn code ta đã sử dụng ở Chương 4 để vẽ hình vuông: >>> t.forward(50) >>> t.left(90)
Gặp lại đồ hoạ con rùa
145
>>> t.forward(50) >>> t.left(90) >>> t.forward(50) >>> t.left(90) >>> t.forward(50)
Trong Chương 6 ta đã học về vòng lặp for . Với kiến thức mới này, ta có thể làm cho đống code vẽ hình vuông xấu xí kia đẹp và gọn gàng hơn bằng cách sử dụng một vòng for : >>> t.reset() >>> for x in range(1, 5): t.forward(50) t.left(90)
Dòng đầu tiên ta bảo đối tượng Pen quay trở về trạng thái ban đầu. Tiếp, ta bắt đầu một vòng for đế m từ 1 đến 4 với đoạn code range(1, 5) . Sau đó, ở các dòng tiếp theo, với mỗi lần lặp, ta đi tới 50 điểm ảnh rồi xoay sang trái 90 độ. Do sử dụng vòng for , code sẽ ngắn hơn lúc trước — không tính dòng reset thì ta đã nhảy từ sáu dòng xuống còn ba thôi.
VẼ HÌNH NGÔI SAO Giờ đây, chỉ cần sửa một chút xíu ở vòng lặp là ta sẽ có những hình còn thú vị hơn nhiều. Thử đoạn code sau: >>> t.reset() >>> for x in range(1, 9): t.forward(100) t.left(225)
146
Chương 11
Đoạn code này sẽ tạo ra một ngôi sao tám cánh:
Đoạn code này rất giống với code vẽ hình vuông lúc nãy, trừ mấy điểm sau: -
T a l ặ p t á m lầ n vớ i ran ge (1, 9) t h a y vì b ố n lầ n v ới ran ge (1, 5)
-
Ta đi tới 100 điểm ảnh thay vì 50 điểm ảnh.
-
Ta xoay sang trái 225 độ sang thay vì 90 độ.
Gặp lại đồ hoạ con rùa
147
Thử sửa ngôi sao này thêm chút nữa xem sao. Lần này ta xoay 175 độ và lặp 37 lần, ta có thể tạo ra ngôi sao có nhiều cánh hơn nữa, như thế này: >>> t.reset() >>> for x in range(1, 38): t.forward(100) t.left(175)
Kết quả của đoạn code trên là:
148
Chương 11
Rồi thì nhân tiện đang vẽ vời sao trăng, đây là đoạn code vẽ ra một ngôi sao xoắn ốc: >>> t.reset() >>> for x in range(1, 20): t.forward(100) t.left(95)
Chỉ cần thay mỗi góc quay và số vòng lặp, rùa đã vẽ ra khối kiểu sao khác nhau nhỉ:
Gặp lại đồ hoạ con rùa
149
Với các đoạn code tương tự nhau, ta đã có thể tạo ra vô số các hình, từ hình vuông cho đến hình ngôi sao xoắn ốc. E m cũng thấy đó, vòng for đã giúp đơn giản hoá rất nhiều việc vẽ ra những hình này. Không có vòng for , ta hẳn đã phải viết đi viết lại vô khối code. Giờ ta sẽ thử dùng một lệnh if để điều khiển lúc rùa lúc nào thì rẽ để vẽ ra các ngôi sao hình thù khác nhé. Ở ví dụ này, ta muốn rùa lúc quay này lúc quay góc khác. >>> t.reset() >>> for x in range(1, 19): t.forward(100) if x % 2 == 0: t.left(175) else: t.left(225)
Lần này ta cho vòng lặp chạy 18 lần ( range(1, 19) ) và bảo rùa đi thẳng 100 điểm ả n h ( t.forward(100) ). Đ i ể m m ớ i ở đ â y là lệnh if n à y ( if x % 2 = = 0: ). L ệ n h n à y k i ể m tra xem nếu biến x có phải là một số chẵn hay không bằng phépchialấysốdư⟨ modulo⟩ , là dấu % trong biểu thức x % 2 == 0, có thể được diễn đạt là, khi “x chia 2 lấy số dư” có kết quả
bằng 0. Biểu thức x % 2 về cơ bản nói là, “Số còn dư ra sau khi chia biến x ra làm hai phần bằng nhau là bao nhiêu?” Ví dụ, nếu ta chia 5 quả bóng làm hai phần, ta sẽ có hai bên mỗi bên 2 quả bóng (tổng cộng là 4 quả), và số dư (là phần còn dư) sẽ là 1 quả, như hình dưới:
150
Chương 11
Nếu phải chia 13 quả bóng ra làm hai phần, ta sẽ có hai bên mỗi bên 6 quả và dư ra 1 quả:
Khi kiểm tra xem số dư có bằng không hay không sau khi chia x cho 2, ta thực ra đang hỏi xe m số x có thể được chia thành hai phần bằng nhau m à không có số dư hay không. Đây là cách rất nuột để xem một số có phải là số chẵn hay không, vì số chẵn luôn có thể được chia đều thành hai phần bằng nhau. D ò n g thứ n ă m , ta bả o rùa qu a y sa ng trái 17 5 đ ộ ( t.left(175) ) nế u x là s ố c hẵ n ( if x % 2 = = 0: ); nếu không ( else ), ở dòng cuối cùng, ta bả o nó qua y 2 25 độ ( t.left(225) ).
Kết quả đoạn code trên là:
Gặp lại đồ hoạ con rùa
151
VẼ HÌNH XE Ô TÔ Con rùa này không chỉ vẽ được mấy hình đơn giản này thôi đâu nhé, nó có thể làm nhiều hơn thế đấy. Ví dụ sau đây ta sẽ vẽ một cái ô tô hình khối đơn giản. Đầu tiên là thân xe. Trong IDLE, chọn File ► New Window, rồi nhập đoạn code sau vào cửa sổ: t.reset() t.color(1,0,0) t.begin_fill() t.forward(100) t.left(90) t.forward(20) t.left(90) t.forward(20) t.right(90) t.forward(20) t.left(90) t.forward(60) t.left(90) t.forward(20) t.right(90) t.forward(20) t.left(90) t.forward(20) t.end_fill()
Tiếp, ta sẽ vẽ bánh sau. t.color(0,0,0) t.up() t.forward(10) t.down() t.begin_fill() t.circle(10) t.end_fill()
Rồi đến bánh trước. t.setheading(0) t.up() t.forward(90) t.right(90) t.forward(10) t.setheading(0) 152
Chương 11
t.begin_fill() t.down() t.circle(10) t.end_fill()
Chọn File ► Save As. Đặt tên cho file, car.pychẳng hạn. Rồi chọn Run ► Run Module để chạy thử code. Cái xe của chúng ta đây:
E m có thể thấy vài hàm mới lẩn ở giữa đống code này: ●
color dùng để đổi m à u vẽ.
●
begin_fill v à end_fill d ù n g đ ể tô m à u c h o c ả m ộ t v ù n g trên b ả n g vẽ.
●
circle đ ể vẽ hình tròn với các kích c ỡ khác nhau.
●
setheading đ ể quay rùa sang một hư ớng c ụ thể nào đó.
Ta sẽ cùng nhìn cụ thể vào những hàm này và xem làm sao để dùng chúng để tô m à u cho hình nhé. Gặp lại đồ hoạ con rùa
153
TÔ MÀU H à m color nhận ba tham số. Đầu tiên là tỉ lệ màu đỏ, rồi đến tỉ lệ màu xanh lá, và thứ ba là tỉ lệ m à u xanh dương. Ví dụ, để xe có m à u đỏ tươi, ta dùng color(1, 0, 0) , nghĩa là dùng màu đỏ 100 phần trăm. Ba màu đỏ, xanh lá, xanh dương này được gọi là RGB⟨ RedGreen-Blue⟩ . Đâ y chính là cách mà n hình m á y tính hiển thị ra các mà u sắc khác nhau, và khi các màu này được pha với nhau ta sẽ có nhiều mà u hơn, như kiểu khi pha xanh dương với đỏ ta có màu tím, còn pha vàng với đỏ ta có màu cam vậy. Các màu đỏ, xanh lá và xanh dương được gọi là mà u chuẩn⟨ primarycolor⟩ vì ta không thể dùng các m à u khác để tạo ra ba m à u này được. M ặ c dù trên mà n hình m á y tính ta không dùng mà u thật để pha (người ta thực ra sử dụng ánh sáng để làm việc đó), nhưng để hiểu được các công thức pha chế màu RGB, em có thể hình dung ra ta có ba lọ màu: một đỏ, một xanh lá và một xanh dương. Các lọ ban đầu đều đầy, và ta tạm gán cho chúng giá trị là 1 (hoặc 100 phần trăm). Sau đó trộn toàn bộ màu đỏ với toàn bộ màu xanh lá ra một cái lọ khác và khuấy đều lên, ta sẽ có màu vàng (tức là tỉ lệ 1 và 1, hay 100 phần trăm mỗi màu). Quay về thế giới lập trình. Để rùa vẽ được một hình tròn màu vàng, ta sẽ sử dụng 100 phần trăm màu đỏ, 100 phần trăm mà u xanh lá và không có xanh dương, như thế này: >>> t.color(1,1,0) >>> t.begin_fill() >>> t.circle(50) >>> t.end_fill()
Ba số 1, 1, 0 ở dòng đầu tiên đại diện cho 100 phần trăm đỏ, 100 phần trăm xanh lá, và 0 phần trăm xanh dương. Dòng thứ hai, ta bảo rùa đổi sang màu RGB vừa nãy với t.begin_fill , rồi ta b ả o n ó v ẽ m ộ t h ì n h t r òn v ớ i t.circle . D ò n g c u ố i c ù n g , end_fill n ó i
rùa hãy thực sự đổ màu vào toàn bộ hình nó vừa vẽ.
154
Chương 11
VIẾT H À M Đ Ể VẼ HÌNH TRÒN C Ó M À U Để giúp cho các thí nghiệm đổi màu sau này dễ dàng hơn, ta sẽ tạo hẳn ra một hàm dùng đoạn code lúc nãy để vẽ hình tròn: >>> def mycircle(red, green, blue): t.color(red, green, blue) t.begin_fill() t.circle(50) t.end_fill()
Giờ ta có thể dễ dàng vẽ hình tròn mà u xanh lá bằng cách chỉ dùng mỗi xanh lá thôi: >>> mycircle(0, 1, 0)
Hoặc ta cũng có thể làm mà u xanh lá đậm hơn bằng cách chỉ dùng nửa mà u xanh thôi (0.5): >>> mycircle(0, 0.5, 0)
Để nghịch với màu RGB trên màn hình, thử vẽ hình tròn với màu toàn đỏ rồi nửa đỏ (1 và 0.5), rồi toàn xanh dương rồi nửa xanh dương, như thế này: >>> mycircle(1, 0, 0) >>> mycircle(0.5, 0, 0) >>> mycircle(0, 0, 1) >>> mycircle(0, 0, 0.5)
N ế u bảng vẽ trở nên lộn xộn, dùng t.reset() đ ể xoá các hình cũ đi. Đồ ng thời nhớ rằng e m có thể di chuyển rùa m à không cần phải vẽ bằng cách dùng t.up() để nhấc bút lên (rồi dùng t.down() để đặt bút xuống). Kết hợp các màu đỏ, xanh lá và xanh dương sẽ tạo ra vô số các màu khác nhau, như vàng: >>> mycircle(0.9, 0.75, 0)
Gặp lại đồ hoạ con rùa
155
Hồng nhạt: >>> mycircle(1, 0.7, 0.75)
Và hai m à u ca m đậm nhạt khác nhau: >>> mycircle(1, 0.5, 0) >>> mycircle(0.9, 0.5, 0.15)
E m thử tự trộn màu xem!
MÀU ĐEN VÀ MÀU TRẮNG Buổi tối mà tắt hết đèn đi thì ta có gì nào? Mọi thứ đều có mà u đen. Trên máy tính cũng thế. Không ánh sáng tức là không có màu, vậy là một hình tròn với tất cả các màu bằng 0 sẽ tạo ra m à u đen: >>> mycircle(0, 0, 0)
Kết quả đây:
156
Chương 11
Ngược lại nếu dùng 100 phần trăm cho cả ba màu, em sẽ có màu trắng. Nhập đoạn code sau vào để làm hình tròn đen bên trên biến mất: >>> mycircle(1, 1, 1)
H À M ĐỂ VẼ HÌNH VUÔNG E m đã thấy cách làm sao để bảo rùa đổ màu vào hình bằng cách dùng hà m begin_fill , và các hình sẽ chỉ đư ợ c đ ồ m à u khi ta kết thúc với h à m end_fill . G i ờ ta sẽ thử với vài hình và màu khác nhé. Ta sẽ dùng lại hàm vẽ hình vuông ở đầu chương và truyền vào một tham số là kích thước của hình vuông. >>> def mysquare(size): for x in range(1, 5): t.forward(size) t.left(90)
Chạy thử hàm này bằng cách gọi nó với kích thước 50 như sau: >>> mysquare(50)
Ta sẽ có một hình vuông nho nhỏ:
Gặp lại đồ hoạ con rùa
157
Giờ thử chạy tiếp hàm với các kích thước khác nhau. Đoạn code sau sẽ tạo ra các hình vuông liên tiếp nhau với các kích thước lần lượt là 25, 50, 75, 100 và 125. >>> t.reset() >>> mysquare(25) >>> mysquare(50) >>> mysquare(75) >>> mysquare(100) >>> mysquare(125)
Các hình vuông của ta trông sẽ như thế này:
158
Chương 11
VẼ HÌNH VUÔNG CÓ MÀ U Để vẽ hình vuông có màu, đầu tiên ra cần xoá hết bảng vẽ, bắt đầu đổ màu, rồi gọi lại hàm vẽ hình vuông với đoạn code sau: >>> t.reset() >>> t.begin_fill() >>> mysquare(50)
E m sẽ chỉ thấy hình vuông bình thường cho đến khi kết thúc đổ màu: >>> t.end_fill()
Và hình vuông giờ trông sẽ như thế này:
Gặp lại đồ hoạ con rùa
159
Giờ thử sửa lại hàm này một chút để ta có thể vẽ ra hình vuông có đổ màu hay không tuỳ ý. Để làm thế ta cần một tham số thứ hai với code hơi phức tạp hơn một chút: >>> def mysquare(size, filled): if filled == True: t.begin_fill() for x in range(0, 4): t.forward(size) t.left(90) if filled == True: t.end_fill()
D ò n g đầ u tiên ta sửa lại định nghĩ a h à m để nhậ n và o hai t ha m số: size và filled . Tiếp, ta k i ể m tra x e m gi á trị c ủa filled c ó phả i là T ru e k h ô n g vớ i if filled = = T rue . N ế u đúng, ta sẽ gọi begin_fill đ ể bảo rùa đồ m à u vào những hình ta sẽ vẽ. Sau đó ta lặp bốn lần ( for x in range(0, 4) ) đ ể v ẽ b ố n c ạ n h hì nh c h ữ nhật (đi tới v à q u a y s a n g trái), sa u đ ó lại kiểm tra xe m filled có phải là True không. Nế u đúng, ta sẽ kết thúc việc đổ mà u ở đây với t.end_fill , và rùa sẽ đi đ ổ m à u v à o hình. Giờ ta đã có thể vẽ hình vuông có màu với dòng này: >>> mysquare(50, True)
Hoặc vẽ hình vuông không màu với dòng này: >>> mysquare(150, False)
160
Chương 11
Sau khi gọi hàm mysquare hai lần, ta có hình như sau, trông giống giống con mắt hình vuông nhỉ?
Nhưng không có lý do gì ta lại dừng lại ở đây cả. Em có thể vẽ bất cứ hình gì và tô m à u chúng như e m muốn.
Gặp lại đồ hoạ con rùa
161
VẼ NGÔI SAO CÓ MÀU Ở ví dụ cuối cùng này, ta sẽ tô màu cho ngôi sao đã vẽ lúc trước. Code ban đầu trông như thế này: for x in range(1, 19): t.forward(100) if x % 2 == 0: t.left(175) else: t.left(225)
Giờ ta sẽ viết h à m mystar . Ta cũng sẽ dùng lệnh if từ hà m mysquare và thêm tham s ố size v à o . >>> def mystar(size, filled): if filled == True: t.begin_fill() for x in range(1, 19): t.forward(size) if x % 2 == 0: t.left(175) else: t.left(225) if filled == True: t.end_fill()
Ở hai dòng đầu tiên của hàm, ta kiểm tra x e m filled c ó phải là True không, nếu đúng ta sẽ bắt đầu đổ màu. Ta kiểm tra thêm một lần nữa ở hai dòng cuối, và nếu filled là True ta sẽ dừng đổ màu. Đồng thời, cũng như với hàm mysquare , ta thêm kích thước vào
cho ngôi sao với tham số size và dùng giá trị đó khi gọi t.forward . Giờ tô màu vàng (90 phần trăm đỏ, 75 phần trăm xanh lá và 0 phần trăm xanh dương) rồi gọi hàm lại. >>> t.color(0.9, 0.75, 0) >>> mystar(120, True)
162
Chương 11
Rùa sẽ vẽ cho ta một ngôi sao màu vàng như thế này::
Để tô viền cho ngôi sao, đổi màu về đen và vẽ ngôi sao thêm một lần nữa mà không tô màu: >>> t.color(0,0,0) >>> mystar(120, False)
Gặp lại đồ hoạ con rùa
163
N gôi sao giờ sẽ có m àu vàng với viền đen, như thế này:
TÓM TẮT T rong chư ơng này, em đã thấy cách dùn g m odule tu rtle để vẽ m ột vào hình khối cơ bản, dùng vòng lặp và lệnh if để điều khiển rùa trên m àn hình. Ta đã học cách đổi m àu và tô cho các hình vẽ. Ta đồng thời cũng sử dụng m àu trong m ột vài hàm để vẽ hình dễ hơn với các m àu sắc khác nhau chỉ với m ột lần gọi hàm . 164
Chương 11
B À I T Ậ P L Ậ P T R ÌN H Trong các bài tập sau, em sẽ dùng turtle để tự vẽ m ột vài hình vẽ. Câu trả lời có thể tìm thấy trên h t t p : / / p y t h o n - f o r - k i d s . c o m / .
# 1 : V Ẽ H ÌN H B Á T G IÁ C Trong chương này ta đã vẽ hình ngôi sao, hình vuông và hình chữ nhật. G iờ thử tạo hình có tám cạnh nhé (G ợi ý: thử xoay rùa 45 độ nhé.)
G ặp lại đồ hoạ con rùa
16 5
# 2 : V Ẽ H ÌN H B Á T G IÁ C C Ó M À U G iờ em đã có hàm để vẽ hình bát giác, sửa nó để nó có m àu. Thử vẽ hình bát giác với viền như ta đã làm với ngôi sao lúc trước.
#3: LẠ I M Ộ T H À M V Ẽ N G Ô I SA O N Ữ A V iết hàm để vẽ ngôi sao nhận hai tham số: kích thước và số cánh. Lời định nghĩa hàm trông sẽ như thế này: def draw_star(size, points):
166
Chương 11
Dùng rùa để vẽ có một vấn đề, đó là nó ... vẽ ... chậm ... như ... rùa. Cho dù có tăng hết tốc lực, nó cũng chẳng nhanh nhẹn gì cho lắm. Sự chậm trễ này với mấ y con rùa thì tất nhiên là chẳng ảnh hường gì hết, nhưng với đồ họa má y tính thì có đấy. Đồ họa máy tính, đặc biệt là trong game, thường đòi hỏi mọi thứ phải chạy cực nhanh. Nếu có máy chơi game ở nhà, hoặc nếu có chơi gam e trên m á y tính, thử dành ra một khoảnh khắc suy nghĩ về những hình ả nh hiển thị trên đó ra sao nhé. Đồ họa hai chiều 2D ⟨twodimensional⟩là đồ hoạ phẳng — các nhân vật thường chỉ di chuyển lên xuống hoặc trái phải — được thấy rất nhiều trong các game cho các hệ máy Nintendo DS hay PlayStation Portable (PSP), và game trên điện thoại. Trong các game có đồ họa mô phỏng ba chiều — tức là gần như ba chiều — các hình ảnh trông thật hơn nhưng về bản chất các nhân vật
Dùng tkinter để có đồ hoạ đẹp hơn
167
vẫn di chuyển tương đối trên một mặt phẳng (đây còn được gọi là đồhọanổi⟨ isometric graphics⟩). Cuối cùng ta có game ba chiều 3D ⟨three-dimensional⟩với các hình ảnh trên màn hình được vẽ ra giống y như ngoài đời thực. Game cho dù là đồ họa 2D, đồ hoạ mô phỏng 3D hay đồ hoạ 3D, đều có một điểm chung: các hình ảnh cần phải được vẽ cực nhanh trên m à n hình m á y tính. Nếu e m chưa bao giờ thử vẽ hình ảnh động, hãy thử trò chơi nho nhỏ sau: 1.
Lấy vài tờ giấy trắng cùng kích thước, chọn một góc bất kỳ của tờ đầu tiên rồi vẽ một hình gì đó (hình người nguệch ngoạc gì cũng được).
2.
Trang sau, cùng góc đó, vẽ hình người tương tự trang trước, nhưng hơi giơ chân lên.
3.
Trang tiếp, cũng góc đó, cũng hình người đó, nhưng chân giơ cao thêm chút nữa.
4.
Cứ thế từng trang một, mỗi hình người lại được sửa đi một chút. Là m xong, cố gắng lật thật nhanh từng trang một, e m sẽ thấy hình người của e m
đang di chuyển. Đâ y là phương pháp căn bản nhất để tạo ra các hình ảnh động, từ phim hoạt hình trên T V cho đến game trên máy tính. Mỗi hình ảnh vẽ ra được sửa lại một chút tạo ra cho ta ảo giác của các chuyển động. Để một hình ảnh trông như đang di chuyển thật, ta cần hiển thị ra các khunghình⟨ frame⟩ , là hình ảnh đơn lẻ của một chuyển động, thật n h a nh. Python cung cấp cho ta vài cách khác nhau để tạo ra hình ảnh. Ngoài module turtle ra, e m có thể sử dụng các module bên ngoài khác (cần phải cài đặt riêng), hoặc là sử
dụng module tkinter , vốn đã có sẵn khi cài Python rồi. tkinter c ó thể đư ợc sử dụng để tạo ra một ứng dụng thật sự hoàn chỉnh, như một chương trình xử lý văn bản đơn giản chẳng hạn, hoặc dùng để tạo các hình vẽ đơn giản cũng được. Trong chương này, ta sẽ c ùng nghiên c ứ u là m thế nào để dùng tkinter tạo ra các hình ả nh nhé.
TẠO NÚT NHẤN NHẤN Ví dụ đầu tiên của ta sẽ là tạo ra một chương trình hết sức đơn giản, chỉ có duy nhất một cái nút bấm được. Nhập đoạn code sau vào: 168
Chương 12
>>> from tkinter import * >>> tk = Tk() >>> btn = Button(tk, text="click me") >>> btn.pack()
D òn g thứ nhất là để m a n g toàn bộ modul e tkinter vào trong chương trình. Ta sử dụng from module-name import * là để có thể sử dụng tất cả mọi thứ trong module
mà không cần phải gõ tên module. Nếu không, còn nhớ khi import turtle ở các ví dụ trước không, ta sẽ phải gõ tên của module vào nếu m u ốn gọi
hàm bên trong nó: import turtle t = turtle.Pen()
N ế u viết import * , ta sẽ không cầ n viết turtle.Pen n h ư vẫn ha y l àm trước đâ y ở các Chương 4 và 11 nữa. Với module turtle thì việc này cũng không có ý nghĩa lắm, nhưng nếu phải dùng một module m à có siêu nhiều lớp với hàm thì có đấy, đỡ phải gõ nhiều. from turtle import * t = Pen()
Quay về ví dụ cái nút bấm, ở dòng tiếp theo ta tạo ra một đối tượng từ lớp Tk với tk = Tk() , giống hệt cách tạo ra mấy con rùa từ lớp Pen . Đối tượng tk sẽ tạo ra một cửa sổ mới
tinh để ta có thể thêm các thứ linh tinh vào, chẳng hạn như nút bấm, ô nhập chữ, hoặc là hẳn một bảng vẽ để thích vẽ vời gì lên cũng được. Đây là lớp chính của module tkinter — nếu không tạo ra đối tượng này từ lớp Tk , em sẽ không thể làm bất cứ thao tác đồ họa hay hình ảnh động nào được. Dòng thứ ba, ta tạo ra một nút bấm với btn = Button , đưa tk vào làm tham số đầu tiên v à " cl i ck m e" đ ể l à m c h ữ hi ể n thị trên nút, với (tk, text=" cl i ck m e " ) . M ặ c d ù đ ã thêm vào cửa sổ như ng nút này sẽ không hiển thị ra nế u ta không thêm dòng btn.pack() vào, dòng này sẽ làm nút bấm xuất hiện trên cửa sổ. Không những thế nó còn giúp dóng hàng mọi thứ nếu trong cửa sổ có thêm nhiều thứ nữa.
Dùng tkinter để có đồ hoạ đẹp hơn
169
Kết quả sẽ tương tự như thế này:
Như ng nút clickmenày chẳng chạy gì cả. Ấ n cả ngày cũng sẽ không thấy gì luôn, vì ta cần sửa lại code tí chút. (Nhớ đóng cửa sổ vừa m ở ra lúc nãy đấy nhé!) Đầu tiên ta sẽ tạo ra một hàm để in ra vài chữ gì đó: >>> def hello(): print('hello there')
Rồi sửa lại ví dụ lúc nãy để thêm hàm này vào: >>> from tkinter import * >>> tk = Tk() >>> btn = Button(tk, text="click me", command=hello) >>> btn.pack()
E m có thể thấy ta chỉ sửa code ban đầu có một chút xíu thôi: Ta thêm vào tham số command , yêu cầu Python chạy hàm hello khi nút được bấm.
Giờ nếu thử b ấ m nút e m sẽ thấy chữ hello there đư ợc viết ra trên shell. Lầ n nào ấn cũng ra chữ luôn. Ấn hẳn năm lần này:
170
Chương 12
Đây là lần đầu tiên ta sử dụng tham số chỉ định trong các ví dụ từ đầu đến giờ, để nói thêm một chút về nó trước khi vẽ tiếp nhé.
T H A M SỐ CHỈ ĐỊNH T h a m sốchỉđịnh⟨ na m e d parameter⟩ cũng chỉ là tham số như bình thường, trừ m ột điểm là, bình thường thì các giá trị được truyền vào hàm theo một thứ tự nhất định (giá trị thứ nhất là của tham số thứ nhất, giá trị thứ hai là của tham số thứ hai, giá trị thứ ba là của tham số thứ ba, cứ thế cứ thế), còn ở đây ta nói luôn giá trị nào là của tham số nào, cho nên lúc này thứ tự thế nào không còn quan trọng nữa. Đôi khi một hàm có thể có rất nhiều tham số, trong khi ta không nhất thiết lúc nào cũng phải truyền hết tất cả vào. Tham số chỉ định giúp ta chỉ cần truyền giá trị vào những tham số cần thiết thôi. Giả sử ta có một h à m tên là person nhậ n hai tham số: width và height . >>> def person(width, height): print('I am %s feet wide, %s feet high' % (width, height))
Thông thường thì hàm sẽ được gọi như thế này: >>> person(4, 3) I am 4 feet wide, 3 feet high
Nếu dùng tham số chỉ định, ta có thể gọi hàm và chỉ định chính xác tên của từng giá trị: >>> person(height=3, width=4) I am 4 feet wide, 3 feet high
T ha m số chỉ định sẽ trở nên hết sức có ích khi ta sử dụng module tkinter về sau.
Dùng tkinter để có đồ hoạ đẹp hơn
171
TẠO BẢNG VẼ Nút trông cũng hay hay, nhưng không liên quan lắm đến đồ họa trên mà n hình. Khi thực sự cần vẽ vời gì đó, ta cần một thứ khác cơ: một đối tượng canvas (thuộc module tkinter).
Khi tạo bảng vẽ, ta cần truyền vào chiều dài và chiều rộng (đơn vị là điểm ảnh) của bảng vẽ. Ngoài cái đó ra thì code chẳng khác gì cái nút lúc nãy. Ví dụ: >>> from tkinter import * >>> tk = Tk() >>> canvas = Canvas(tk, width=500, height=500) >>> canvas.pack()
Tương tự như lúc nãy, một cửa sổ sẽ hiện lên khi em nhập tk = Tk() . Ở dòng cuối ta nhồi bảng vẽ vào với canvas.pack() , lúc này kích thước bảng vẽ sẽ đư ợc thay
đổi thành rộng 500 điểm ảnh và dài 500 điểm ảnh như ở ý định ở dòng thứ ba. Và cũng tương tự như lúc nãy, hàm pack đồng thời cũng sắp xếp để bảng vẽ nằ m ngay ngắn trên cửa sổ. Nếu hàm này không được gọi thì cửa sổ sẽ không có gì hết luôn.
VẼ ĐƯỜNG THẲNG Ta dùng tọa độ điểm ảnh để thao tác trên bảng vẽ. Tọađộ⟨ coordinates⟩ cho ta biết các vị trí của các điểm ảnh trên một mặt phẳng bất kỳ. Các tọa độ trên bảng vẽ của tkinter cho ta biết vị trí vẽ cách lề trái của bảng vẽ bao xa (từ trái sang phải) và cách lề trên của bảng vẽ bao xa (từ trên xuống dưới).
172
Chương 12
Giả sử, do bảng vẽ của ta dài 500 điểm ảnh và rộng 500 điểm ảnh, nên tọa độ của điểm ảnh của góc dưới bên phải sẽ là (500, 500). Để vẽ được đường thẳng như hình dưới, ta sử dụng tọa độ điểm đầu (0, 0) và tọa độ điểm cuối (500, 500).
Ta đặt tọa độ cụ thể của các đi ểm vào trong h à m create_line n hư sau: >>> from tkinter import * >>> tk = Tk() >>> canvas = Canvas(tk, width=500, height=500) >>> canvas.pack() >>> canvas.create_line(0, 0, 500, 500) 1 Dùng tkinter để có đồ hoạ đẹp hơn
173
Hà m create_line trả về 1, đây thực ra là một “số hiệu” — ta sẽ từ từ tìm hiểu sau. Nếu dùng module turtle để làm việc tương tự, ta sẽ phải dùng đoạn code sau: >>> import turtle >>> turtle.setup(width=500, height=500) >>> t = turtle.Pen() >>> t.up() >>> t.goto(-250, 250) >>> t.down() >>> t.goto(500, -500)
R õ ràng là đoạn code tkinter khá hơn hẳn đúng không. Ngắ n gọn và súc tích hơn. Giờ xem thử xem trong canvas có hàm gì có thể dùng được để vẽ ra những hình thú vị hơn nhé.
VẼ HÌNH HỘP Với modul e turtle , m u ố n vẽ một hình hộp ta phải đi, rẽ, rồi lại đi, lại rẽ vài lần. Cuối cùng ta có một hình chữ nhật hoặc hình vuông, tuỳ thuộc vào khoảng cách của mỗi lẫn di chuyển. Module tkinter vẽ m ấ y hình này dễ hơn nhiều. E m chỉ cần xác định tọa độ các góc là được. Ví dụ nhé (em có thể đóng các cửa sổ khác được rồi đấy): >>> from tkinter import * >>> tk = Tk() >>> canvas = Canvas(tk, width=400, height=400) >>> canvas.pack() >>> canvas.create_rectangle(10, 10, 50, 50)
174
Chương 12
Ở đây, ta dùng tkinter để tạo ra một bảng vẽ dài 400 điểm ảnh và rộng cũng 400 điểm ảnh, sau đó vẽ ra một hình vuông ở góc trên bên trái cửa sổ, như thế này:
C á c t h a m s ố truyền và o h à m canvas.create_rectangle ở dò ng c uối là toạ đ ộ c ủa góc trên bên trái và góc dưới bên phải của hình vuông. Các toạ độ này được tính là khoảng cách từ mép bên trái của bảng vẽ đi sang ngang và từ mép bên trên của bảng vẽ đi xuống. Ở đây, cặp toạ độ đầu tiên (góc trên bên trái) là 10 điểm ảnh tính từ trái sang phải và 10 điểm ảnh tính từ trên xuống dưới (chính là hai số đầu: 10, 10 ). Góc dưới bên phải của hình vuông là 50 điểm ảnh tính từ trái sang và 50 điểm ảnh tính từ trên xuống (là hai số cuối: 50, 50). Dùng tkinter để có đồ hoạ đẹp hơn
175
Ta tạm gọi các cặp toạ độ này là x1, y1và x2, y2. Để vẽ hình chữ nhật, ta có thể tăng khoảng cách của góc thứ hai ra xa khỏi mép bên trái của bảng vẽ (tức là tăng giá trị của x2), như thế này: >>> from tkinter import * >>> tk = Tk() >>> canvas = Canvas(tk, width=400, height=400) >>> canvas.pack() >>> canvas.create_rectangle(10, 10, 300, 50)
Ở đây, toạ độ góc trên bên trái của hình chữ nhật (cũng chính là vị trí của nó trên màn hình) là (10, 10), còn của góc dưới bên phải là (300, 50). Ta có hình chữ nhật có cùng chiều cao như hình vuông lúc nãy (50 điểm ảnh), nhưng dài hơn rất nhiều.
176
Chương 12
Ta đồng thời cũng có thể vẽ một hình chữ nhật khác bằng cách tăng khoảng cách của góc thứ hai ra xa khỏi mép bên trên của bảng vẽ (tức là tăng giá trị của y2) như sau: >>> from tkinter import * >>> tk = Tk() >>> canvas = Canvas(tk, width=400, height=400) >>> canvas.pack() >>> canvas.create_rectangle(10, 10, 50, 300)
Ở lần gọi h à m create_rectangle này, theo thứ tự ta vừ a l àm những việc sau: ●
Đi sang trái 10 điểm ảnh (tính từ mép bên trái)
●
Đi xuống dưới 10 điểm ảnh (tính từ mép bên trên). Đây là góc thứ nhất của hình chữ nhật.
●
Vẽ hình chữ nhật đi ngang 50 điểm ảnh
●
Vẽ hình chữ nhật đi xuống 300 điểm ảnh. Kết quả cuối cùng trông như thế này:
Dùng tkinter để có đồ hoạ đẹp hơn
177
VẼ THẬT NHIỀU HÌNH HỘP Nếu ta phủ kín bảng vẽ bằng các hình chữ nhật khác nhau thì sao nhỉ? Ta có thể dùng module random và viết ra một hàm dùng các số ngẫu nhiên để làm toạ độ cho các góc của hình c hữ nhật. Ta sẽ dùng một hàm trong module random là randrange . Truyền vào hàm một con số, nó sẽ trả về một số nguyên nằm giữa số 0 và số được truyền vào. Ví dụ gọi randrange(10) sẽ trả về một số bất kỳ n ằ m giữa 0 và 9, còn randrange(100) sẽ trả về một
số bất kỳ nằm giữa 0 và 99, vân vân. Hàm sử dụng randrange của ta sẽ như thế này. Chọn File ► New Window để mở ra một cửa sổ mới và nhập vào đoạn code sau vào: from tkinter import * import random tk = Tk() canvas = Canvas(tk, width=400, height=400) canvas.pack() def random_rectangle(width, height): x1 = random.randrange(width) y1 = random.randrange(height) x2 = x1 + random.randrange(width) y2 = y1 + random.randrange(height) canvas.create_rectangle(x1, y1, x2, y2)
Đ ầ u tiên là định nghĩa h à m của chúng ta ( def random_rectangle ) nhận và o hai tham số: width và height . Tiếp, ta dùng hàm randrange để tạo ra các biến cho cặp toạ độ của góc trên bên trái hình chữ nhật, truyền vào các tham số là chiều dài và chiều rộng t ư ơn g ứ n g với x 1 = r an d om .r an d r an ge (w i d th ) v à y 1 = ra n d o m. ra n d ra n g e( h e i g h t ) . N ó i cách khác, dòng thứ hai của hàm có thể hiểu là, “Tạo biến x1 và gán cho nó một giá trị ngẫu nhiên giữa 0 và chiều rộng được truyền vào biến width .” Hai dòng tiếp theo chịu trách nhiệm tạo ra các biến cho cặp toạ độ của góc dưới bên phải hình chữ nhật, bằng cách dựa vào cặp toạ độ của góc trên bên trái ( x1 và y1 ) và cộng
178
Chương 12
thêm vào các giá trị ngẫu nhiên mới. Dòng thứ ba của hàm thực chất là, “Tạo biến x2 bằng cách cộng một giá trị ngẫu nhiên vào giá trị của x1 vừa được tính lúc trước.” Cuối c ùng ta truyền cả bốn biến x1 , y1 , x2 và y2 và o canvas.create_rectangle đ ể vẽ hình lên bảng vẽ. Để chạy thử hà m random_rectangle , ta sẽ truyền vào chiều dài và chiều rộng của bảng vẽ. Thêm đoạn code này vào bên dưới hàm vừa tạo: random_rectangle(400, 400)
Lưu lại toàn bộ đoạn code trên (chọn File ► Save và đặt tên file là randomrect.py chẳng hạn) rồi chọn Run ► Run Module. Nếu thấy hàm chạy ngon lành, ta sẽ thử phủ kín màn hình với vô số hình chữ nhật bằng cách đặt một vòng lặp rồi liên tục gọi random_rectangle . Thử một vòng for với 100 hình chữ nhật nhé. Thêm đoạn code này vào,
lưu file và chạy lại lần nữa: for x in range(0, 100): random_rectangle(400, 400)
Tuy hơi lộn xộn, nhưng trông rất kiểu nghệ thuật đương đại:
Dùng tkinter để có đồ hoạ đẹp hơn
179
VÀ THÊM MÀU SẮC Đương nhiên là ta muốn thêm mà u mè vào trong lúc vẽ rồi. Sửa hàm random_rectangle đ ể truyền thêm m à u cho hình chữ nhật bằng tham số thứ ba ( fill_color ) nhé. Nhậ p đoạn code sau vào một cửa sổ mới, lưu lại thành file colorrect.py: from tkinter import * import random tk = Tk() canvas = Canvas(tk, width=400, height=400) canvas.pack() def random_rectangle(width, height, fill_color): x1 = random.randrange(width) y1 = random.randrange(height) x2 = random.randrange(x1 + random.randrange(width)) y2 = random.randrange(y1 + random.randrange(height)) canvas.create_rectangle(x1, y1, x2, y2, fill=fill_color)
L ú c nà y, h à m create_ re ctan gl e s ẽ n h ậ n t h ê m m ộ t t h a m s ố m ớ i fill_color v à o, là m à u về sau dùng để tô hình chữ nhật. Ta có thể truyền tên mà u vào hàm như sau (vẫn dùng bảng vẽ dài và rộng 400 điểm ảnh nhé) để tạo ra một loạt các hình chữ nhật có màu sắc khác nhau. Nếu có viết chạy ví dụ này, cứ chép và dán code cho đỡ công gõ nhé. Chọn đoạn code, nhấn CTRL-C để chép, chuyển sang dòng mới, rồi nhấn CTRL-V để dán. Thêm đoạn code sau vào colorrect.py, đặt ngay bên dưới hàm: random_rectangle(400, 400, 'green') random_rectangle(400, 400, 'red') random_rectangle(400, 400, 'blue') random_rectangle(400, 400, 'orange') random_rectangle(400, 400, 'yellow') random_rectangle(400, 400, 'pink') random_rectangle(400, 400, 'purple') random_rectangle(400, 400, 'violet') random_rectangle(400, 400, 'magenta') random_rectangle(400, 400, 'cyan') 180
Chương 12
Phần lớn những m àu trên sẽ hiện ra đúng, nhưng có thể m ột vài cái sẽ gặp lỗi (tuỳ vào m áy em đang chạy là W indows, M ac OS X hay Linux). N hưng nhỡ ta m uốn dùng m àu khác không nằm trong số các m àu có tên kia thì sao? C òn nhớ lúc ở C hương 11 ta đặt m àu cho bút rùa bằng cách sử dụng tỉ lệ phần trăm của các m àu đỏ, xanh lá và xanh dương không. X ác định tỉ lệ của các m àu chuẩn (đỏ, xanh lá, xanh dương) để pha m àu trong tkin ter hơi phức tạp hơ n m ột chút, như ng ta sẽ cùng họ c nh é. T rong m odule tu rtle , ta đã tạo ra m àu vàng bằng cách dùn g 90 phần trăm m àu đỏ , 75 phần trăm xanh lá, và không dùng xanh dươ ng. T rong tkinter , ta có thể tạo ra m àu vàng tương tự như thế bằng đoạn code sau: random_rectangle(400, 400, '#ffd800')
D ấu thăng ( # ) ở đằng trước giá trị ffd800 là để nói Python ta đang viết m ột số thập lụcphân⟨ hexadecim al⟩ . Thập lục phân là hệ số rất thường gặp trong lập trình m áy tính. Nó sử dụng cơ số 16 (từ 0 đến 9 rồi từ A đến F) thay vì cơ số 10 (từ 0 đến 9) của hệ thập phân. N ếu chưa được học về cơ số trong đại số, em có thể hiểu đơn giản là các số thập phân có th ể đ ư ợ c c hu y ể n th àn h th ập lụ c p h â n bằ ng cách sử d ụn g k ý tự đ ặtch ỗ ⟨ fo rm at placeholder⟩trong chuỗi: %x (xem lại phần “Ghép Giá Trị Vào Trong Chuỗi” ở trang 28). Ví dụ để đổi số thập phân 15 sang thập lục phân ta làm như sau: >>> print('%x' % 15) f
Đ ể đảm vào số trả ra có hai chữ số, ta sửa ký tự đặt chỗ kia m ột chút: >>> print('%02x' % 15) 0f
M odule tkin ter có cấp sẵn m ột cách rất đơn giản đ ể lấy giá trị thập lục phân của m ột m àu. Thêm đoạn code sau vào colorrect.py(em có thể xoá hết các đoạn code gọi hàm ran d om _rectan gle ).
D ùng tkinter để có đồ hoạ đẹp hơn
181
from tkinter import * from tkinter import colorchooser tk = Tk() tk.update() colorchooser.askcolor()
Lúc này một bảng màu sẽ xuất hiện:
Sau khi chọn được một màu và nhấn OK, một tuple sẽ được trả ra. Tuple này bao gồm một tuple khác với ba phần tử, và một chuỗi: >>> colorchooser.askcolor() ((235.91796875, 86.3359375, 153.59765625), '#eb5699')
Ba số đầu tương ứng với lượng màu đỏ, xanh lá và xanh dương. Trong tkinter , mỗi lượng m à u chuẩn được dùng để pha ra các mà u khác là một con số nằm trong khoảng 0 đến 255 (khác với tỉ lệ phần trăm của từng mà u chuẩn trong module turtle ). Chuỗi ở cuối tuple chính là dạng thập lục phân của ba số kia.
182
Chương 12
E m có thể chép và dán chuỗi màu này vào, hoặc lưu nó vào biến rồi dùng vị trí chỉ mục để lấy ra chuỗi đó. Giờ dùng thử hà m random_rectangle x e m thế nào nhé: >>> c = colorchooser.askcolor() >>> random_rectangle(400, 400, c[1])
Kết quả:
Dùng tkinter để có đồ hoạ đẹp hơn
18 3
VẼ HÌNH CUNG Hình cung là một phần của hình tròn hoặc các đường cong khác, nhưng để vẽ đư ợc hình c ung trong tkinter , ta cầ n vẽ nó bên trong một hình chữ nhật thông qua h à m create_arc , n hư thế này: canvas.create_arc(10, 10, 200, 100, extent=180, style=ARC)
184
Chương 12
Nếu có nhỡ đóng hết các cửa sổ tkinter lại hoặc nhỡ khởi động lại IDLE, nhớ phải ma ng tkinter vào lại và rồi tạo lại bảng vẽ bằng đoạn code sau: >>> from tkinter import * >>> tk = Tk() >>> canvas = Canvas(tk, width=400, height=400) >>> canvas.pack() >>> canvas.create_arc(10, 10, 200, 100, extent=180, style=ARC)
Ta đặt góc trên bên trái của hình chữ nhật (là nơi sẽ chứa hình cung) ở toạ độ (10, 10), tức là 10 điểm ảnh ngang và 10 điểm ảnh dọc, và góc dưới bên phải ở toạ độ (200, 100), tức là 200 điểm ảnh ngang và 100 điểm ảnh dọc. Tham số tiếp theo là extent , được dùng để xác định độ cong của hình cung. Còn nhớ ở Chương 4 khi ta nói về các góc đo độ bằng cách xoay xung quanh một hình tròn không. Đây là ví dụ về hai hình cung, một cái là 90 độ còn một cái là 270 độ quanh hình tròn:
Dùng tkinter để có đồ hoạ đẹp hơn
185
Đoạn code sau sẽ vẽ ra vài hình cung khác nhau liền để em có thể thấy các kết quả khác nhau khi dùng các số đ ộ khác nha u trong h à m create_arc . >>> from tkinter import * >>> tk = Tk() >>> canvas = Canvas(tk, width=400, height=400) >>> canvas.pack() >>> canvas.create_arc(10, 10, 200, 80, extent=45, style=ARC) >>> canvas.create_arc(10, 80, 200, 160, extent=90, style=ARC) >>> canvas.create_arc(10, 160, 200, 240, extent=135, style=ARC) >>> canvas.create_arc(10, 240, 200, 320, extent=180, style=ARC) >>> canvas.create_arc(10, 320, 200, 400, extent=359, style=ARC)
CHÚ Ý Ởhìnhcuốitasửdụng359độchứkhôngphải360độlàvìtkinterhiểu360độlà0độvàsẽ khôngvẽgìcảnếutadùng360độ.
186
Chương 12
VẼ HÌNH ĐA GIÁC Hình đa giác là hình có từ ba cạnh trở lên. Có những hình ta thường gặp như hình tam giác, hình vuông, hình chữ nhật, ngũ giác, lục giác, vân vân, cũng có những hình kì dị hơn với các cạnh không đều nhau, nhiều cạnh hơn và hình thù kỳ lạ hơn. Khi dùng tkinter để vẽ hình đa giác, e m chỉ cần đưa ra toạ độ từng đỉnh của đa giác là xong. Đây là hình tam giác: from tkinter import * tk = Tk() canvas = Canvas(tk, width=400, height=400) canvas.pack() canvas.create_polygon(10, 10, 100, 10, 100, 110, fill="", outline="black")
Hình tam giác này được bắt đầu với các toạ độ x, y là (10, 10), sau đó đi ngang sang toạ độ (100, 10), và kết thúc ở toạ độ (100, 110). Kết quả là:
Dùng tkinter để có đồ hoạ đẹp hơn
18 7
Ta cũng có thể vẽ ra các hình đa giác ít gặp khác (hình m à các cạnh các góc không đều nhau) với đoạn code sau: canvas.create_polygon(200, 10, 240, 30, 120, 100, 140, 120, fill="", outline="black")
Hình này bắt đầu ở toạ độ (200, 10), đi tới (240, 30), rồi tới (120, 100), và cuối cùng là (140, 120). tkinter tự động nối điểm cuối lại với điểm đầu. Kết quả là đây:
188
Chương 12
VIẾT CH Ữ Ngoài việc vẽ hình khối, em cũng có thể viết chữ trên bảng vẽ bằng hàm create_text . H à m này chỉ nhận một cặp toạ độ duy nhất (là vị trí x và y của chữ), cùng với
một tham số chỉ định cho chữ ta muốn viết. Trong đoạn code sau, ta tạo lại bảng vẽ như trước rồi hiển thị một câu ở toạ độ (150, 100). Lưu lại đoạn code vào file text.py. from tkinter import * tk = Tk() canvas = Canvas(tk, width=400, height=400) canvas.pack() canvas.create_text(150, 100, text='There once was a man from Toulouse,')
H à m create_text c ó nhiều các tham số rất hay, như là tô m à u chữ chẳng hạn. Ở đoạn code sau ta gọi hàm create_text với toạ độ (130, 120), câu muốn viết, và tô màu đỏ. canvas.create_text(130, 120, text='Who rode around on a moose.', fill='red')
Ta cũng có thể chọn phông chữ bằng một tuple với tên phông chữ và cỡ chữ. Ví dụ để chọn phông chữ Times với cỡ chữ 20 ta viết ( 'Times', 20) . Dò ng tiếp theo ta hiển thị c hữ bằng phông Times cỡ 15,
phông Helvetica cỡ 20, rồi phông Courier cỡ 22 và cỡ 30. canvas.create_text(150, 150, text='He said, "It\'s my curse,', font=('Times', 15)) canvas.create_text(200, 200, text='But it could be worse,', font=('Helvetica', 20)) canvas.create_text(220, 250, text='My cousin rides round', font=('Courier', 22)) canvas.create_text(220, 300, text='on a goose."', font=('Courier', 30))
Dùng tkinter để có đồ hoạ đẹp hơn
189
Và đây là kết quả của ba phông chữ trên cùng với năm cỡ chữ khác nhau:
190
Chương 12
CHÈN ẢNH Đ ể chèn ảnh vào bảng vẽ trong tkinter , đầu tiên ta phải đưa ảnh lên rồi sử dụng h à m create_image của đối tư ợng canvas . Bất cứ ảnh nào em muốn đưa lên đều phải nằm trong thư mục mà Python có thể đọc được. Ví dụ ở đây ta đặt ảnh test.gifvào thư mục C:\,là thư mục gốc của ổ C:, nhưng em thực ra đặt ở đâu cũng được.
Nếu đang dùng hệ điều hành Mac hay Linux, em có thể đặt ảnh vào thư mục Home. Nếu không đặt được ảnh vào ổ C:, em có thể đặt ở màn hình desktop cũng được.
C H Ú Ý tkinterchỉchophéptasửdụngảnhGIF,tứclàcácfileảnhcóđuôi.gif .Đểhiểnthịcác kiểuảnhkhácnhưPNG(đuôi.png)vàJPG(đuôi.jpg ),emsẽcầncàithêmcácmodulebên ngoài,vídụnhưPythonImagingLibrary(http://www.pythonware .com/products/pil/).
Dùng tkinter để có đồ hoạ đẹp hơn
191
Ta có thể hiển thị ảnh test.giflên như thế này: from tkinter import * tk = Tk() canvas = Canvas(tk, width=400, height=400) canvas.pack() my_image = PhotoImage(file='c:\\test.gif') canvas.create_image(0, 0, anchor=NW, image=my_image)
Bốn dòng đầu tiên ta cài đặt bảng vẽ như bình thường. Dòng thứ năm, ảnh được đưa và o biến m y_ i m ag e . T a tạo ra m ộ t đối tư ợng Ph otoImage với đư ờ n g d ẫ n 'c:\\test.gif' . Nếu lưu ảnh ở màn hình desktop thì e m có thể đổi đường dẫn thành như thế này: my_image = PhotoImage(file='C:\\Users\\Joe Smith\\Desktop\\test.gif')
S a u khi ả n h đ ư ợ c đ ư a v à o biến, can vas.create_i mag e(0, 0 , a n c h o r = N W , image=my_image) sẽ hiển thị nó lên nhờ hàm create_image . Toạ độ (0, 0) là vị trí ảnh sẽ
được hiển thị, còn anchor=NW là để bảo hàm sử dụng góc trên bên trái của ảnh ( NW là Tây Bắc⟨ northwest⟩ ) làm điểm khởi đầu khi vẽ (nếu không mặc định nó sẽ sử dụng tâm ảnh để làm điểm khởi đầu). Tham số chỉ định cuối cùng, image , trỏ vào biến hiện đang giữ ảnh. Kết quả là đây:
192
Chương 12
TẠO CÁC CHUYỂN ĐỘNG Ta đã thử tạo ra các hình ảnh tĩnh — là những thứ không di chuyển gì cả. Nhưng làm sao để tạo ra được các hình ảnh chuyển động được? Hình ảnh động không hẳn là chuyên m ô n của modul e tkinter , như ng nó có thể xử lý những thứ đơn giản. Ví dụ, ta có thể tạo ra một hình tam giác rồi di chuyển nó đi ngang màn hình với đoạn code sau (đừng quên, chọn File ► New Window, lưu file lại, rồi chạy với Run ► Run Module): import time from tkinter import * tk = Tk() canvas = Canvas(tk, width=400, height=200) canvas.pack() canvas.create_polygon(10, 10, 10, 60, 50, 35) for x in range(0, 60): canvas.move(1, 5, 0) tk.update() time.sleep(0.05)
Chạy code này, hình tam giác sẽ di chuyển ngang qua màn hình cho đến điểm cuối:.
Sao hay vậy? N hư lúc trước, sau khi mang tkinter vào, ta sử dụng ba dòng đầu tiên để thực hiện các cài đặt cơ bản để hiển thị bảng vẽ. Dòng thứ tư ta tạo ra tam giác bằng hàm này: canvas.create_polygon(10, 10, 10, 60, 50, 35)
CHÚ Ý Khinhậpdòngnàyvào,mộtconsốsẽđượctrảratrênmànhình.Đâylàsốhiệucủahình vừađượcvẽ.Tavềsausẽdùngnóđểgọilạihìnhnày,nhưvídụdướiđây. Dùng tkinter để có đồ hoạ đẹp hơn
193
Tiếp, ta tạo ra một vòng lặp đơn giản để đếm t ừ 0 đ ế n 5 9 , b ắ t đ ầ u v ớ i for x in r a n g e ( 0, 60): . Khối code bên trong vòng lặp sẽ di chuyển hình tam giác đi ngang m à n hình. H à m canvas.move sẽ di chuyển các các hình đã được vẽ ra bằng cách tăng hoặc giảm các toạ độ x và y của chúng. Ví dụ, với c an va s. mo ve (1 , 5 , 0) , ta di c h u y ể n đối t ư ợ n g s ố 1
(là số hiệu của hình tam giác lúc nãy) sang ngang 5 điểm ảnh và đi xuống 0 điểm ảnh. Để nó di chuyển v ề vị trí b a n đầu, ta c ó thể gọi c an va s. mo ve (1 , -5, 0) .
H à m tk.update() bu ộc tkinter cập nhật lại cửa sổ (tức là vẽ lại nó). N ế u không dùng update , tkinter sẽ chờ cho đến khi vòng lặp chạy xong rồi mới di chuyển hình tam giác, nghĩa là e m sẽ nhìn thấy hình nhảy một phát đến vị trí cuối cùng luôn thay vì di chuyển từ từ nga ng qua bảng vẽ. D ò n g cuối cùng của vòng lặp, time.sleep(0.05) , nói Python “nghỉ tạm” một phần hai mươi giây (0.05 giây), trước khi chạy tiếp. Để hình tam giác di chuyển chéo xuống góc dưới màn hình, ta có thể sửa code lại một chút và gọi move(1, 5, 5) . Để thử code mới, đóng bảng vẽ lại, tạo một file mới (File ► New Window) với đoạn code sau: import time from tkinter import * tk = Tk() canvas = Canvas(tk, width=400, height=400) canvas.pack() canvas.create_polygon(10, 10, 10, 60, 50, 35) for x in range(0, 60): canvas.move(1, 5, 5) tk.update() time.sleep(0.05)
194
Chương 12
Đoạn code này khác lúc nãy ở hai điểm: -
Ta đổi chiều cao của bảng vẽ sang 400 thay vì 200 với canvas = Canvas(tk, width=400, height=400).
-
T a tăng 5 đi ể m ả nh và o toạ đ ộ x và y của hình t a m giác với can vas.move(1, 5, 5) . Lưu code lại và chạy nó, hình tam giác sẽ ở vị trí này khi vòng lặp kết thúc::
Để đưa hình di chuyển trở lại vị trí ban đầu, dùng - 5 , -5 (thêm đoạn này vào cuối file nhé): for x in range(0, 60): canvas.move(1, -5, -5) tk.update() time.sleep(0.05) Dùng tkinter để có đồ hoạ đẹp hơn
195
TẠO CÁC TƯƠNG TÁC Ta có thể làm cho hình tam giác này tương tác lại khi có người nhấn nút trên bàn phím bằng các liênkếtsựkiện⟨ eventbinding⟩ . Sựkiện⟨ event⟩ là những thứ xảy ra trong lúc một chương đang chạy, như là chuột di chuyển, bàn phím được nhấn, hoặc là cửa sổ bị đóng lại. E m có thể bảo tkinter theo dõi những sự kiện này và làm gì đó mỗi khi sự kiện xảy ra. Để xử lý sự kiện (tức là bảo Python làm gì đó mỗi khi một sự kiện xảy ra), ta đầu tiên cần phải tạo hàm . Ph ầ n liên kết xả y ra khi ta bả o tkinter rằng h à m nà y bị ràngbuộc ⟨bound⟩(hay còn gọi là gắn⟨associate⟩) với một sự kiện cụ thể nào đó; nói cách khác, tkinter sẽ tự động gọi h à m để xử lý sự kiện xảy ra.
Ví dụ, để hình tam giác của chúng ta di chuyển mỗi khi nhấn bàn phím, ta có thể viết hàm như sau: def movetriangle(event): canvas.move(1, 5, 0)
H à m nà y chỉ nhận m ộ t tha m số duy nhất là event , là cái m à tkinter sẽ sử dụng để đưa thông tin về sự kiện vào trong hàm. Sau đó ta bảo tkinter h à m này sẽ được dùng cho một sự kiện cụ thể, bằng cách dùng hà m bind_all trên bảng vẽ. Code hoàn chỉnh trông như thế này: from tkinter import * tk = Tk() canvas = Canvas(tk, width=400, height=400) canvas.pack() canvas.create_polygon(10, 10, 10, 60, 50, 35) def movetriangle(event): canvas.move(1, 5, 0) canvas.bind_all('', movetriangle)
T ha m số đầu tiên của h à m là sự kiện m à ta m u ốn tkinter theo dõi. Trong trường hợp này là , tức là lúc ta nhấn nút ENTER hoặc RETURN. Ta nói với tkinter rằng ta m u ố n h à m movetriangle s ẽ đư ợc gọi m ỗi khi s ự kiện KeyPress xả y ra.
196
Chương 12
Chạy đoạn code này, dùng chuột bấm vào bảng vẽ, rồi thử nhấn nút ENTER mà xem. Liệu ta có thể thay đổi hướng đi tuỳ thuộc vào từng phím bấm không? Tất nhiên là được rồi. Ta chỉ cần sửa h à m movetriangle n h ư thế này: def movetriangle(event): if event.keysym == 'Up': canvas.move(1, 0, -3) elif event.keysym == 'Down': canvas.move(1, 0, 3) elif event.keysym == 'Left': canvas.move(1, -3, 0) else: canvas.move(1, 3, 0)
Đối tượng event được truyền vào trong h à m movetriangle c ó vài biến rất thú vị. Một trong số đó là keysym (viết tắt của kýhiệuphím⟨ keysymbol⟩ ), là một chuỗi ghi lại giá trị thự c s ự c ủa nút đ ư ợ c n h ấ n trên b à n p hí m . D ò n g if ev en t. k e ys ym = = 'Up': nghĩ a là nế u giá trị c ủa k eysym là chuỗi 'Up' , ta sẽ gọi h à m can vas.move với t h a m s ố (1, 0, -3) ở dòng tiếp đó. C ò n nế u keysym là chuỗi 'Down' , trong elif even t.k eysym = = 'Down': , ta sẽ g ọ i v ớ i t h a m s ố (1, 0, 3) , v â n v â n. Nhớ rằng tham số đầu tiên là luôn là số hiệu của hình được vẽ ra trên bảng vẽ, tham số thứ hai là giá trị tăng hoặc giảm của toạ độ x (chiều ngang), còn tham số thứ ba là giá trị tăng giảm của toạ độ y (chiều dọc). T a sau đ ó bảo tkinter h à m movetriangle sẽ xử lý bốn nút khác nhau này (lên, xuống, trái, phải). Đoạn code sau cho thấy code lúc này trông sẽ như thế nào. Để tiện cho em, hãy nhập đoạn code này vào một cửa sổ shell mới với File ► New Window. Trước khi chạy nhớ đặt cho file cái tên dễ nhớ chút, movingtriangle.pyc hẳng hạn.
Dùng tkinter để có đồ hoạ đẹp hơn
197
from tkinter import * tk = Tk() canvas = Canvas(tk, width=400, height=400) canvas.pack() canvas.create_polygon(10, 10, 10, 60, 50, 35) def movetriangle(event): if event.keysym == 'Up': canvas.move(1, 0, -3) elif event.keysym == 'Down': canvas.move(1, 0, 3) elif event.keysym == 'Left': canvas.move(1, -3, 0) else: canvas.move(1, 3, 0) canvas.bind_all( '', movetriangle) canvas.bind_all( '', movetriangle) canvas.bind_all( '', movetriangle) canvas.bind_all( '', movetriangle)
Ở dòng đầu tiên của hà m movetriangle , ta kiểm tra xem biến keysym có chứa chữ 'Up' không ở ❶ . Nếu có, ta sẽ di chuyển hình tam giác đi lên trên bằng cách dùng hà m move với các tham số 1, 0, -3 ở ❷ . T h a m số đầu tiên là số hiệu của tam giác, tham số thứ
hai là khoảng cách di chuyển sang phải (lúc này ta không cần đi ngang nên giá trị sẽ là 0), và tham số thứ ba là khoảng cách di chuyển đi lên (-3 điểm ảnh). Sau đó ta kiểm tra xem keysym có phải là 'Down' không ở ❸ , nếu có thì ta di chuyển xuống (3 điểm ảnh) ở ❹ . Tiếp nữa ta kiểm tra xem có phải là 'Left' không ở ❺ , nếu có ta di chuyển sang trái (-3 điểm ảnh) ở ❻ . Cuối cùng nếu không giá trị nào khớp, lệnh else ở ❼ sẽ di chuyển hình sang bên phải ở ❽ . Lúc này hình sẽ di chuyển tuỳ phím người dùng bấm.
CÁC TRƯỜNG HỢP KHÁC CẦN DÙNG SỐ HIỆU Bất cứ khi nà o ta dùng h à m create_ trên bả ng vẽ, n hư create_polygon ha y create_rectangle , một số hiệu sẽ được trả về. Số hiệu này có thể được dùng cho các h à m
khác trong canvas , như ta đã làm lúc trước với hàm move :
198
Chương 12
>>> from tkinter import * >>> tk = Tk() >>> canvas = Canvas(tk, width=400, height=400) >>> canvas.pack() >>> canvas.create_polygon(10, 10, 10, 60, 50, 35) 1 >>> canvas.move(1, 5, 0)
Vấn đề chính với ví dụ trên là không phải lúc nào create_polygon cũng trả về 1. Giả sử nếu em tạo thêm các hình khác nó sẽ trả về 2, 3, thậm chí 100 (tuỳ vào số lượng hình được vẽ ra). Nếu sửa lại code một chút để lưu lại giá trị này vào một biến (thay vì sử dụng số 1), code sẽ chạy mà không cần quan tâm số hiệu trả về là số nào: >>> mytriangle = canvas.create_polygon(10, 10, 10, 60, 50, 35) >>> canvas.move(mytriangle, 5, 0)
H à m move cho phép ta di chuyển các đối tượng quanh mà n hình bằng số hiệu của chúng. Như ng cũng có những hà m khác của canvas cho phép ta thực hiện những thay đổi gì đó trên các hình vẽ. Ví dụ, hàm itemconfig của canvas c ó thể được dùng để thay đổi các tham số của hình. như tô mà u gì và viền màu gì. Giả sử ta tạo ra một tam giác màu đỏ: >>> from tkinter import * >>> tk = Tk() >>> canvas = Canvas(tk, width=400, height=400) >>> canvas.pack() >>> mytriangle = canvas.create_polygon(10, 10, 10, 60, 50, 35, fill='red')
Ta sau đó có thể sửa lại m à u của tam giác bằng cách dùng itemconfig và truyền số hiệu của nó vào tham số đầu tiên. Đoạn code sau có thể được diễn đạt là, “Đổi màu của đối tượng có số hiệu trong biến mytriangle sang xanh dương.” >>> canvas.itemconfig(mytriangle, fill='blue')
Dùng tkinter để có đồ hoạ đẹp hơn
199
Ta cũng có thể tô viền của tam giác với m àu khác, cũng bằng cách sử dụng số hiệu ở tham số đầu tiên: >>> canvas.itemconfig(mytriangle, outline='red')
V ề sau, ta sẽ học những cách khác để tương tác với hình vẽ, như làm nó biến m ất rồi khiến nó quay trở lại. E m sẽ thấy các thao tác này rất có ích khi ta viết gam e ở các chương sau.
TÓM TẮT T rong chư ơ ng này, em đã dùng m odule tkinter để v ẽ ra những hình khối đơn giản trên bảng vẽ, hiển thị hình ảnh, và làm ra m ột vài hình ảnh động đơn giản. E m đã thấy cách dùng các liên kết sự kiện để khiến các hình vẽ tương tác lại khi người dùng nhấn nút trên bàn phím , rất có ích về sau khi ta làm gam e. E m cũng thấy là các hàm create trong tkin ter đều trả về m ột số hiệu, số hiệu này có thể đư ợc dùng để chỉnh sửa lại hình sau khi
đã được vẽ, như di chuyển hoặc đổi m àu chúng.
B À I T Ậ P L Ậ P T R ÌN H H ãy thử nghịch ngợm với m odule tk inter và các thao tác hoạt hình cơ bản. C âu trả lời có thể tìm thấy trên h t t p : / / p y t h o n - f o r - k i d s . c o m / .
# 1 : P H Ủ T A M G IÁ C K ÍN M À N H ÌN H D ùng tk inter viết m ột chư ơn g trình phủ tam giác k ín m àn h ình. R ồi dùng các m àu khác nhau tô cho những tam giác này.
# 2 : T A M G IÁ C Đ Ộ N G Đ Ậ Y Sửa code di chuyển tam giác lúc trước (“Tạo Các Chuyển Động” ở trang 193) để di chuyển nó sang phải, rồi đi xuống, rồi sang trái, rồi đi lên và quay về vị trí ban đầu.
200
Chương 12
#3: H Ì N H Ả N H Đ Ộ N G Đ Ậ Y Thử dùng tkinter để hiển thị một hình ảnh của e m lên bảng vẽ. Nh ớ là ảnh GIF đấy nhé! E m có làm nó di chuyển quanh mà n hình được không?
Dùng tkinter để có đồ hoạ đẹp hơn
20 1
202
Chương 12
Bắt đầu viết game Bounce!
203
204
Chương 13
Từ đầu đến giờ, ta mới chỉ đi qua các kiến thức cơ bản của lập trình. Ta đã học cách dùng biến để lưu thông tin, dùng lệnh if để viết điều kiện, dùng for để viết vòng lặp. Ta cũng đã biết cách viết hàm để tái sử dụng code, rồi sử dụng lớp và đối tượng để phân chia code thành những phần nhỏ dễ hiểu hơn.Ta đã học đồ hoạ má y tính thông qua các module turtle v à tkinter . Gi ờ đã đến lúc e m sẽ sử dụng tất cả nhữ ng kiến thức này để viết ra
game đầu tiên của mình.
ĐẬP BÓNG BAY LUNG TUNG Game sẽ có một quả bóng và một thanh đỡ. Quả bóng nẩy và chạy quanh mà n hình, và người chơi sẽ phải điều khiển thanh đỡ để đỡ và bóng sẽ tiếp tục nẩy. Nếu bóng rơi xuống bên dưới là kết thúc.
Bắt đầu viết game Bounce!
205
Ga m e hoàn chỉnh sẽ trông như thế này:
Trông thì đơn giản, nhưng code sẽ hơi loằng ngoằng một chút so với những gì ta đã học lúc trước, vì có rất nhiều thứ phải giải quyết. Ví dụ, cả bóng lẫn thanh đỡ sẽ phải cùng di chuyển, đồng thời ta phải phát hiện ra lúc nào thì bóng chạ m thanh đỡ hoặc chạm tường. Trong chương này, ta sẽ bắt đầu với việc khởi tạo ra bảng vẽ và quả bóng. Chương sau ta sẽ thêm thanh đỡ vào và hoàn thiện game.
206
Chương 13
TẠO BẢNG VẼ CHO GAME Để bắt đầu, ta sẽ mở một file mới ra trong Python shell (chọn File ▶ New Window). Rồi ma ng module tkinter vào và tạo bảng vẽ: from tkinter import * import random import time tk = Tk() tk.title("Game") tk.resizable(0, 0) tk.wm_attributes("-topmost", 1) canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0) canvas.pack() tk.update()
Trông hơi khác so với những ví dụ trước đây đúng không. Đầu tiên ta mang các module time và random vào để dùng sau với import random và import time . Đ o ạ n tk.title(" G a m e " ) là ta d ù n g h à m title c ủa đối t ư ợ ng tk – vừ a đ ư ợ c tạo bới tk = Tk () – để viết ra tiêu đề c ho cái c ử a sổ. Sa u đó ta dù ng h à m resizable đ ể c ố định
kích thước của cửa sổ. Các tham số 0, 0 là để nói rằng “kích thước của cửa sổ sẽ không bị thay đổi kể cả chiều ngang lẫn chiều dọc.” Tiếp theo ta gọi wm_attributes để yêu cầu tkinter đưa cửa sổ ga m e của chúng ta lên trên cùng so với các cửa sổ đang m ở khác ( "topmost" )
E m có thấy lần này khi tạo đối tượng canvas với canvas = , ta đã đưa vào nhiều tham số chỉ định hơn so với lúc trước không. Ví dụ, b d =0 và highlightthickness=0 l à để tránh không vẽ viền của bảng vẽ ra để m à n hình game trông đẹp hơn. D òn g canvas.pack() là để bảng vẽ tự động chỉnh sửa kích thước của nó cho khớp với các tham số dài và rộng ở dòng trước đó. Cuối cùng, tk.update() y ê u cầ u tkinter khởi tạo g a m e c ủa c h ún g ra lên. K h ô n g c ó
dòng cuối này, mọi thứ sẽ không hiện lên như mong đợi đâu. Nhớ lưu file lại nhé. Lấy cái tên gì dễ nhớ chút, paddleball.pychẳng hạn. Bắt đầu viết game Bounce!
207
TẠO LỚP BALL Giờ ta sẽ tạo ra một lớp cho quả bóng. Code ban đầu chỉ cần đủ để vẽ quả bóng ra mà n hình thôi. Ta sẽ cần làm những việc sau: -
Tạo ra lớp Ball nhận các tham số là bảng vẽ và mà u của bóng.
-
Lưu bảng vẽ lại thành một đối tượng vì ta sẽ cần phải vẽ bóng lên trên đó.
-
Vẽ một hình tròn trên bảng vẽ và tô bằng màu được truyền vào từ tham số.
-
Lưu lại số hiệu m à tkinter trả về khi nó vẽ ra hình tròn vì ta sẽ cần dùng số hiệu này để di chuyển bóng về sau.
-
Đưa hình tròn ra giữa bảng vẽ. Thêm đoạn code này vào sau ba dòng đầu tiên của file (sau import time ):
from tkinter import * import random import time class Ball: def __init__(self, canvas, color): self.canvas = canvas self.id = canvas.create_oval(10, 10, 25, 25, fill=color) self.canvas.move(self.id, 245, 100) def draw(self): pass
Đầu tiên ta đặt tên lớp là Ball ở ❶ . Sau đó viết hàm khởi tạo (như trong Chương 8) nhận các tham số canvas và color ở ❷ . Tại ❸ , ta tạo ra biến canvas để lưu lại tham số can vas .
Ở ❹ , ta gọi hàm create_oval với năm tham số tất cả: toạ độ x và y của góc trên bên trái (10 và 10), toạ độ x và y của góc dưới bên phải (25 và 25), và màu của hình tròn. H à m create_oval trả về số hiệu của hình vừa được vẽ, ta lưu lại số hiệu này vào biến id . Ở ❺ , ta đưa hình tròn đến chính giữa bảng vẽ (vị trí 245, 100), và bảng vẽ biết chính xác nó phải di chuyển cái gì, vì ta đã sử dụng chính số hiệu (biến id ) được lưu lại lúc nãy.
208
Chương 13
Hai dòng cuối của lớp Ball ta dựng tạm hàm d raw bằng def d r a w (self) , c òn phầ n thân h à m thì chỉ là từ khoá pass . Lú c nà y
nó chưa làm gì hết. Ta sẽ thêm sau. Thế là đã viết xong lớp Ball , giờ ta sẽ tạo ra một đối tượng của lớp này (còn nhớ không, lớp là để mô tả nó có thể làm được gì, còn đối tượng mới là thứ thực sự làm những việc đó). Thêm đoạn code sau vào cuối file để tạo ra một quả bóng màu đỏ nhé: ball = Ball(canvas, 'red')
E m có thể chạy chương trình này ngay bằng cách chọn Run ▶ Run Module. Nếu không chạy bằng IDLE, bảng vẽ sẽ hiển thị lên trong giây lát rồi biến mất ngay lập tức. Để tránh việc này, ra cần phải tạo một vòng lặp, thường được gọi là vònglặpchính⟨ mainloop⟩ của game. (Bản thân trong IDLE cũng có một vòng lặp chính, nên bảng vẽ mới không biến mất khi ta chạy chương trình ở đây.) Vòng lặp chính này chính là trọng tâm của game, điều khiển phần lớn nội dung trong game. Vòng lặp chính của chúng ta, tạm thời, chỉ bảo tkinter vẽ lại bảng vẽ thôi. Vòng lặp sẽ lặp đi lặp lại mãi mãi (hoặc cho đến khi chúng ta đóng cửa sổ), làm tkinter liên tục vẽ ra mà n hình, và tạm nghỉ một phần trăm giây. T hê m đoạn code sau vào phần cuối chương trình: ball = Ball(canvas, 'red') while 1: tk.update_idletasks() tk.update() time.sleep(0.01)
Bắt đầu viết game Bounce!
209
Giờ chạy lại chương trình, quả bóng sẽ nằm đâu đó gần giữa bảng vẽ:
THÊM VÀI HÀNH ĐỘNG Với lớp Ball vừa được dựng, ta đã sẵn sàng để làm cho nó cử động rồi đấy. Ta sẽ làm cho nó di chuyển, bật tường và đổi hướng.
210
Chương 13
L À M B Ó N G DI C H U Y Ể N Để bóng di chuyển, ta sửa hàm draw như sau: class Ball: def __init__(self, canvas, color): self.canvas = canvas self.id = canvas.create_oval(10, 10, 25, 25, fill=color) self.canvas.move(self.id, 245, 100) def draw(self): self.canvas.move(self.id, 0, -1)
D o h à m __ini t__ đã lưu t h a m s ố c an va s lại, ta sẽ d ù n g n ó t hông q u a self.canvas và gọi hàm move . Ta đưa vào move ba tham số: số hiệu của hình tròn id , số 0 và số -1 . Số 0 là để không cho bóng đi ngang, còn -1 là để nói bóng sẽ đi lên trên 1 điểm ảnh. Ta đang thay đổi từng chi tiết rất nhỏ, bởi ta cần phải thử dần những thay đổi này trong lúc làm. Thử hình dung khi ta viết một hơi hết toàn bộ code, rồi phát hiện ra nó không chạy. Ta sẽ tìm lỗi ở đâu đây? Một thay đổi nữa là ở vòng lặp chính đặt ở cuối chương trình. Bên trong vòng lặp while (là vòng lặp chính của ta đấy!), ta sẽ gọi hàm draw của quả bóng như thế này: while 1: ball.draw() tk.update_idletasks() tk.update() time.sleep(0.01)
Nếu lúc này chạy thử, quả bóng sẽ di chuyển dần lên trên rồi ra bên ngoài cả bảng vẽ và biến mất, vì ta buộc tkinter vẽ lại mọi thứ trên bảng vẽ nhanh hết sức có thể – các l ệ nh u p d a te _i d l e ta sk s v à u p d a t e hối t h úc tki n ter h ã y v ẽ n h a n h lên. L ệ n h time.sleep là lời gọi h à m sleep c ủa modul e ti me , nói Pyt hon t ạ m nghỉ m ột phần trăm giây ( 0.01 ). Cái này là để chương trình của chúng ta chạy chậ m lại một chút, không thì bóng sẽ bay vèo ra ngoài và biến mất nhanh đến mứ c ta không kịp nhìn thấy.
Bắt đầu viết game Bounce!
211
Vậy nội dung vòng lặp này về cơ bản là: di chuyển bóng một chút, vẽ lại bảng vẽ với bóng ở vị trí mới, nghỉ một tẹo, rồi lại tiếp tục y hệt như thế.
CHÚ Ý Rấtcóthểemsẽnhìnthấyvàithôngbáolỗitrênshellkhiđóngcửasổgamelại.Đólàdo khiđóngcửasổ,codesẽhuỷvònglặpwhile,vàPythoncàmràmmộtchútđómà. Game của em giờ trông như sau: from tkinter import * import random import time class Ball: def __init__(self, canvas, color): self.canvas = canvas self.id = canvas.create_oval(10, 10, 25, 25, fill=color) self.canvas.move(self.id, 245, 100) def draw(self): self.canvas.move(self.id, 0, -1) tk = Tk() tk.title("Game") tk.resizable(0, 0) tk.wm_attributes("-topmost", 1) canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0) canvas.pack() tk.update() ball = Ball(canvas, 'red') while 1: ball.draw() tk.update_idletasks() tk.update() time.sleep(0.01)
LÀM BÓNG BẬT TƯỜNG Bóng mà bay vèo ra khỏi màn hình thì chơi bời gì nữa, ta phải làm cho nó bật lại chứ. Đầu tiên, ta phải lưu thêm một vài biến trong lúc khởi tạo lớp Ball , như thế này:
212
Chương 13
def __init__(self, canvas, color): self.canvas = canvas self.id = canvas.create_oval(10, 10, 25, 25, fill=color) self.canvas.move(self.id, 245, 100) self.x = 0 self.y = -1 self.canvas_height = self.canvas.winfo_height()
Ta vừa viết thêm ba dòng vào c hương trình, self.x = 0 là để đặt biến x có giá trị 0 c òn self.y = -1 là đặt biến y c ó giá trị –1. Cuối c ù ng là biến canvas_height vớ i giá trị được trả ra từ hàm winfo_height c ủa bảng vẽ. H à m này cho ta biết chiều dài hiện tại của bảng vẽ. Tiếp, ta lại sửa hàm draw một chút: def draw(self): self.canvas.move(self.id, self.x, self.y) pos = self.canvas.coords(self.id) if pos[1] = self.canvas_height: self.y = -1
Ở ❶ , ta sửa lệnh gọi hàm move và thay vào các biến x và y . Sau đó, ta tạo ra biến pos ở ❷ bằng cách gọi hàm coords của canvas . H à m này trả về toạ độ x và y hiện tại của bất cứ đối tượng nào tồn tại trên bảng vẽ, miễn là em biết số hiệu của nó. Ở đây ta truyền vào hàm coords biến id , chính là số hiệu của quả bóng.
Toạ độ m à hàm coords trả ra là một mảng bốn số. Nếu in ra ta sẽ thấy đại khái kết quả như thế này: print(self.canvas.coords(self.id)) [255.0, 29.0, 270.0, 44.0]
Hai số đầu ( 255.0 và 29.0 ) là toạ độ góc trên bên trái của hình tròn (x1và y1); hai số sau ( 270.0 và 44.0 ) là toạ độ góc dưới bên phải x2và y2. Ta sẽ sử dụng mấy giá trị này ở các dòng sau đó.
Bắt đầu viết game Bounce!
213
Ở ❸ , ta kiểm tra xem toạ độ y1(đỉnh của bóng!) có nhỏ hơn hoặc bằng 0 hay không. Nếu có, ta cho biến y giá trị 1. Ở đây ý ta nói rằng, nếu bóng chạm mép bên trên của màn hình, đừng giảm một đơn vị trên vị trí của bóng nữa, đồng thời cũng có nghĩa là bóng đừng di chuyển lên trên nữa. Ở ❹ , ta kiểm tra xem toạ độ y2(đáy của bóng!) có lớn hơn hoặc bằng với biến canvas_height không. Nế u có, ta sẽ cho y quay trở lại giá trị -1 .
Thử chạy chương trình, quả bóng sẽ đập lên đập xuống quanh bảng vẽ cho đến khi em đóng cửa sổ.
ĐIỀU H Ư Ớ N G BÓ NG Bóng mà cứ bật lên bật xuống thế này trông không giống game tí nào, ta sẽ sửa code một chút để thay đổi hướng đi ban đầu của bóng nhé – là hướng bóng sẽ bay lúc ta vừa bắt đầu g a m e ấy mà. Trong h à m __init__ đổi hai dòng này: self.x = 0 self.y = -1
thành như sau (nhớ kiểm tra kỹ số khoảng trắng tính từ đầu dòng nhé – có tám dấu cách tất cả đấy) starts = [-3, -2, -1, 1, 2, 3] random.shuffle(starts) self.x = starts[0] self.y = -3
Ở ❶ , ta tạo ra biến starts là một danh sách g ồ m sáu con số, rồi gọi random.shuffle ở ❷ để trộn mảng này lên. Ở ❸ , ta đặt giá trị của x là phần tử đầu tiên của mảng đã được trộn, như vậy x có thể là bất cứ số nào trong khoảng từ –3 đến 3. Do đổi y thành –3 ở ❹ (để cho bóng bay nhanh hơn), ta cần phải sửa thêm một vài chỗ nữa để chắc chắn bóng không bay ra khỏi hai cạnh trái phải của m à n hình. Th ê m dòng
214
Chương 13
sau vào cuối hàm __init__ để lưu lại chiều rộng của bảng vẽ vào một biến mới, can vas_wid th : self.canvas_width = self.canvas.winfo_width()
Ta sẽ dùng biến này trong hàm draw để kiểm tra xem bóng có đập vào cạnh trái hoặc cạnh phải của bảng vẽ không: if pos[0] = self.canvas_width: self.x = -3
Do đặt x trong khoảng 3 đến –3, ta cũng phảl làm tương tự với y , như thế dù bật hướng nào bóng cũng đều di chuyển với tốc độ như nhau. Hà m draw giờ sẽ như thế này: def draw(self): self.canvas.move(self.id, self.x, self.y) pos = self.canvas.coords(self.id) if pos[1] = self.canvas_height: self.y = -3 if pos[0] = self.canvas_width: self.x = -3
Lưu code lại và chạy, quả bóng sẽ chạy loanh quanh màn hình mà không bị biến mất. Và trọn vẹn chương trình của chúng ta tới giờ trông như thế này: from tkinter import * import random import time class Ball: def __init__(self, canvas, color): self.canvas = canvas self.id = canvas.create_oval(10, 10, 25, 25, fill=color) self.canvas.move(self.id, 245, 100) starts = [-3, -2, -1, 1, 2, 3] random.shuffle(starts) self.x = starts[0] self.y = -3
Bắt đầu viết game Bounce!
215
self.canvas_height = self.canvas.winfo_height() self.canvas_width = self.canvas.winfo_width() def draw(self): self.canvas.move(self.id, self.x, self.y) pos = self.canvas.coords(self.id) if pos[1] = self.canvas_height: self.y = -3 if pos[0] = self.canvas_width: self.x = -3 tk = Tk() tk.title("Game") tk.resizable(0, 0) tk.wm_attributes("-topmost", 1) canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0) canvas.pack() tk.update() ball = Ball(canvas, 'red') while 1: ball.draw() tk.update_idletasks() tk.update() time.sleep(0.01)
TÓM TẮT Trong chương này, ta đã bắt tay vào viết game đầu tiên sử ụng module tkinter . Ta đã tạo ra một lớp cho bóng và cho nó đập quanh màn hình. Ta đã sử dụng toạ độ để kiểm tra xem bóng có có va đập vào các cạnh của bảng vẽ không, để còn làm cho nó bật trở lại. Ta cũng đã sử dụng hà m shuffle trong module rand om để bóng có thể di chuyển sang các hướng khác nhau. Trong chương sau, ta sẽ thêm thanh đỡ vào để hoàn thiện game nhé.
216
Chương 13
Chương trước ta vừa bắt tay vào viết game đầu tiên: Bounce! Ta đã tạo ra bảng vẽ và thêm một quả bóng vào chương trình. Như ng bóng chỉ toàn đập loanh quanh m à n hình (cho đến khi em tắt máy), chẳng thấy giống game gì cả. Cho nên giờ ta sẽ thêm một thanh đỡ để người chơi sử dụng. Ta cũng sẽ thêm vào yếu tố thắng thua trong game cho vui.
THÊM THANH ĐỠ Bóng bật đi bật lại mà không có gì để đập thì quá chán. Giờ chính là lúc vẽ ra một thanh đỡ đây! Bắt đầu bằng đoạn code dưới đây ngay sau lớp Ball , dùng để tạo ra thanh đỡ (hãy viết ngay dưới hàm draw của lớp Ball nhé):
Hoàn thiện game Bounce!
217
def draw(self): self.canvas.move(self.id, self.x, self.y) pos = self.canvas.coords(self.id) if pos[1] = self.canvas_height: self.y = -3 if pos[0] = self.canvas_width: self.x = -3 class Paddle: def __init__(self, canvas, color): self.canvas = canvas self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color) self.canvas.move(self.id, 200, 300) def draw(self): pass
Code này gần như y nguyên với lớp Ball , ngoại trừ việc ta gọi hàm create_rectangle (thay vì gọi create_oval ), và ta đặt thanh đ ỡ ở vị trí 200, 300 (cách m é p
trái 200 điểm ảnh và cách mép trên 300 điểm ảnh). Tiếp, ở phần cuối code, tạo ra một đối tượng từ lớp Paddle , rồi sửa vòng lặp chính để gọi hàm draw của thanh đỡ, như thế này: paddle = Paddle(canvas, 'blue') ball = Ball(canvas, 'red') while 1: ball.draw() paddle.draw() tk.update_idletasks() tk.update() time.sleep(0.01)
218
Chương 14
Nếu chạy thử, e m sẽ nhìn thấy quả bóng thì bay lung tung con thanh đỡ thì nằm im phăng phắc:
DI C H U Y ỂN T H A N H Đ Ỡ Để cho thanh đỡ có thể di chuyển sang trái phải, ta sẽ sử dụng liên kết các sự kiện để nối các phím múi tên trái và phải vào các hàm của lớp Paddle . Khi người chơi nhấn mũi tên bên trái, biến x sẽ có giá trị -2 (để đi sang bên trái). Nhấn mũi tên bên phải sẽ khiến x có giá trị 2 (để đi sang bên phải). Bư ớc đầu tiên là phải thêm biến x vào hà m __init__ của lớp Paddle , cùng với một biến nữa chứa chiều rộng của bảng vẽ, giống như ta làm với lớp Ball : def __init__(self, canvas, color): self.canvas = canvas self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color) self.canvas.move(self.id, 200, 300) self.x = 0 self.canvas_width = self.canvas.winfo_width() Hoàn thiện game Bounce!
2 19
Giờ ta cần các h à m để chuyển hư ớng trái ( turn_left ) và phải ( turn_right ). Ta sẽ thêm m ấ y hà m này ngay sau hàm draw : def turn_left(self, evt): self.x = -2 def turn_right(self, evt): self.x = 2
Trong h à m __init__ của lớp Paddle, ta sẽ nối m ấ y h à m nà y tới đúng phí m của chúng. Ta đang nhắc đến các liên kết đã được nói trong phần “Tạo Các Tương Tác” ở trang 187 dùng để bảo Python gọi một hàm nào đó khi một phím được nhấn. Ở đây, ta nối hàm turn_left c ủa lớp Pad d l e với p hí m m ũi tên bê n trái bằ ng s ự kiện '' . C ò n
h à m turn_right đư ợ c nối với p hí m m ũ i tên bê n phải bằ ng sự kiện ' ' . H à m __init__ của ta giờ trông n hư sau: def __init__(self, canvas, color): self.canvas = canvas self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color) self.canvas.move(self.id, 200, 300) self.x = 0 self.canvas_width = self.canvas.winfo_width() self.canvas.bind_all('', self.turn_left) self.canvas.bind_all('', self.turn_right)
Hà m draw của lớp Paddle cũng tượng tự như trong lớp Ball : def draw(self): self.canvas.move(self.id, self.x, 0) pos = self.canvas.coords(self.id) if pos[0] = self.canvas_width: self.x = 0
Ta dùng hàm move của bảng vẽ để di chuyển thanh đỡ tuỳ theo giá trị của x với self.canvas.move(self.id, self.x, 0). Rồi ta kiểm tra xem toạ độ hiện tại của thanh
đỡ có chạm vào mé p trái hoặc mé p phải của mà n hình không thông qua các giá trị trong biến pos .
220
Chương 14
Tuy nhiên thanh đỡ sẽ không bật lại như bóng, mà nó sẽ dừng lại. Như vậy nếu toạ độ x phía bên trái ( pos[0] ) nhỏ hơn hoặc bằng 0 ( = self.can vas_wi dth ), ta c ũn g sẽ c h o giá trị c ủa bi ến x b ằ n g 0 với self.x = 0.
CHÚ Ý Lúcchạythửchươngtrình,emsẽcầnphảinhấnvàobảngvẽtrướckhigamebắtđầuđể gamenhậndiệnđượccácphímbấm,Việcấnvàobảngvẽlàdấuhiệuđểnóhiểurằngtừ giờtrởđinósẽchịutráchnhiệmmỗikhicómộtphímnàođótrênbànphímđượcnhấn.
PHÁT HIỆN V A CH Ạ M GIỮA BÓNG V À THANH Đ Ỡ Hiện tại, bóng sẽ không chạm vào thanh đỡ; thực tế là nó sẽ phi xuyên qua thanh đỡ. Bóng cần phải biết lúc nào thì nó chạm vào thanh đỡ, tương tự như việc nó phải biết lúc nào thì chạm vào tường. Ta có thể giải quyết vấn đề này bằng cách viết thêm code vào hàm draw (ta đã có sẵn code để kiểm tra va chạm với tường ở đấy rồi), nhưng tốt hơn hết, ta nên chuyển đoạn code này ra hẳn một hàm mới để chia chương trình ra thành các phần nhỏ hơn. Nếu ta viết quá nhiều code vào cùng một chỗ (trong cùng một hàm chẳng hạn), ta sẽ khiến cho code trở nên ngày càng khó hiểu hơn. Sự thay đổi này rất cần thiết đấy.
Hoàn thiện game Bounce!
221
Đầ u tiên, ta đổi hàm __init__ của quả bóng để có thể thêm đối tượng paddle vào làm tham số: class Ball: def __init__(self, canvas, paddle, color): self.canvas = canvas self.paddle = paddle self.id = canvas.create_oval(10, 10, 25, 25, fill=color) self.canvas.move(self.id, 245, 100) starts = [-3, -2, -1, 1, 2, 3] random.shuffle(starts) self.x = starts[0] self.y = -3 self.canvas_height = self.canvas.winfo_height() self.canvas_width = self.canvas.winfo_width()
Để ý ở ❶ ta sửa các tham số của __init__ để tham thanh đỡ vào. Rồi ở ❷ ta gán tham số paddle vào biến paddle . Với đối tượng paddle này, ta cũng cần phải sửa code một chút lúc khởi tạo đối tượng ball . Thay đổi này ở tít dưới cuối chương trình nhé, ngay trước vòng lặp chính: paddle = Paddle(canvas, 'blue') ball = Ball(canvas, paddle, 'red') while 1: ball.draw() paddle.draw() tk.update_idletasks() tk.update() time.sleep(0.01)
Đoạn code kiểm tra xem bóng có đập vào thanh đỡ không hơi phức tạp hơn so với đoạn code kiểm tra lúc đập vào tường. Ta sẽ gọi hà m hit_paddle và thêm nó vào hàm d raw của lớp Ball , đoạn kiểm tra xe m nó có chạm phía dưới m à n hình không: def draw(self): self.canvas.move(self.id, self.x, self.y) pos = self.canvas.coords(self.id) if pos[1] = self.canvas_height: self.y = -3
222
Chương 14
if self.hit_paddle(pos) == True: self.y = -3 if pos[0] = self.canvas_width: self.x = -3
Như đã thấy ở đoạn code trên, nếu hit_paddle trả về True , ta sẽ thay đổi hướng b ón g b ằ n g cá c h đặt biến y thà nh -3 với self.y = -3 . N h ư n g đ ừ n g c hạ y g a m e vội – ta vẫ n chưa viết hà m hit_paddle . Ta sẽ làm bây giờ đây. T h ê m h à m hit_paddle vào ngay trước h à m d ra w . def hit_paddle(self, pos): paddle_pos = self.canvas.coords(self.paddle.id) if pos[2] >= paddle_pos[0] and pos[0] = paddle_pos[1] and pos[3] = paddle_pos[0] and pos[0] = paddle_pos[1] and pos[3]