Author Archives: Phở Code

Tkinter – Trò chơi rắn săn mồi

Trong phần này chúng ta viết lại một game rất nổi tiếng đó là game rắn săn mồi (Snake).

Snake

Game này được phát hành vào những năm 70 trên các hệ máy cầm tay, một thời gian sau được đưa lên PC. Trong game người chơi sẽ điều khiển một con rắn. Mục tiêu của người chơi là cố gắng cho con rắn ăn được càng nhiều mồi càng tốt. Mỗi lần con rắn ăn mồi, cơ thể nó sẽ dài ra. Game kết thúc khi người chơi cho rắn “ăn” phải tường hoặc cơ thể chính mình.

Mô tả game

Kích thước của mỗi đốt trên con rắn là 10px. Con rắn sẽ được điều khiển bằng các phím mũi tên. Khi game kết thúc, dòng chữ “Game Over” sẽ hiện lên giữa màn hình.

import sys
import random
from PIL import Image, ImageTk
from tkinter import Tk, Frame, Canvas, ALL, NW

WIDTH      = 300
HEIGHT     = 300
DELAY      = 100
DOT_SIZE   = 10
ALL_DOTS   = 900 #WIDTH * HEIGHT / (DOT_SIZE * DOT_SIZE)
RAND_POS   = 27

x = [0] * ALL_DOTS
y = [0] * ALL_DOTS

class Board(Canvas):
   def __init__(self, parent):
       Canvas.__init__(self, width=WIDTH, height=HEIGHT, 
                       background="black", highlightthickness=0)
       self.parent = parent
       self.initGame()
       self.pack()
 
   def initGame(self):
       self.right = True
       self.left = False
       self.up = False
       self.down = False
 
       self.inGame = True
       self.dots = 3
 
       self.apple_x = 100
       self.apple_y = 190
 
       for i in range(self.dots):
           x[i] = 50 - i * 10
           y[i] = 50 
 
       try: 
           self.dotImage = Image.open("dot.png")
           self.dotImage.thumbnail((10, 10), Image.ANTIALIAS)
           self.dot = ImageTk.PhotoImage(self.dotImage)
 
           self.headImage = Image.open("head.png")
           self.headImage.thumbnail((10, 10), Image.ANTIALIAS) 
           self.head = ImageTk.PhotoImage(self.headImage)
 
           self.appleImage = Image.open("apple.png")
           self.appleImage.thumbnail((10, 10), Image.ANTIALIAS) 
           self.apple = ImageTk.PhotoImage(self.appleImage)
 
       except IOError as e:
           print (e)
           sys.exit(1)
 
       self.createObjects()
       self.locateApple()
       self.bind_all("<Key>", self.onKeyPressed)
       self.after(DELAY, self.onTimer)
 
   def createObjects(self):
       self.create_image(self.apple_x, self.apple_y, image=self.apple,
       anchor=NW, tag="apple")
       self.create_image(x[0], y[0], image=self.head, anchor=NW, tag="head")
       self.create_image(x[1], y[1], image=self.dot, anchor=NW, tag="dot")
       self.create_image(x[2], y[2], image=self.dot, anchor=NW, tag="dot") 
 
   def locateApple(self):
       apple = self.find_withtag("apple")
       self.delete(apple[0])
 
       r = random.randint(0, RAND_POS)
       self.apple_x = r * DOT_SIZE
       r = random.randint(0, RAND_POS)
       self.apple_y = r * DOT_SIZE
 
       self.create_image(self.apple_x, self.apple_y, image=self.apple,
                         anchor=NW, tag="apple") 
 
   def doMove(self):
       dots = self.find_withtag("dot")
       head = self.find_withtag("head")
 
       items = dots + head
 
       z = 0
 
       while z < len(items) - 1:
           c1 = self.coords(items[z])
           c2 = self.coords(items[z + 1])
           self.move(items[z], c2[0]-c1[0], c2[1]-c1[1])
           z += 1
 
       if self.left:
           self.move(head, -DOT_SIZE, 0)
 
       if self.right:
           self.move(head, DOT_SIZE, 0)
 
       if self.up:
           self.move(head, 0, -DOT_SIZE)
 
       if self.down:
           self.move(head, 0, DOT_SIZE)
 
   def checkCollisions(self):
       dots = self.find_withtag("dot")
       head = self.find_withtag("head")
 
       x1, y1, x2, y2 = self.bbox(head)
       overlap = self.find_overlapping(x1, y1, x2, y2)
 
       for dot in dots:
           for over in overlap:
               if over == dot:
                   self.inGame = False
 
       if x1 < 0: 
           self.inGame = False 
       if x1 > WIDTH - DOT_SIZE:
           self.inGame = False
       if y1 < 0: 
           self.inGame = False 
       if y1 > HEIGHT - DOT_SIZE:
           self.inGame = False
 
   def checkApple(self):
       apple = self.find_withtag("apple")
       head = self.find_withtag("head")
 
       x1, y1, x2, y2 = self.bbox(head)
       overlap = self.find_overlapping(x1, y1, x2, y2)
 
       for ovr in overlap:
           if apple[0] == ovr:
               x, y = self.coords(apple)
               self.create_image(x, y, image=self.dot, anchor=NW, tag="dot")
               self.locateApple()
 
   def onTimer(self): 
       if self.inGame:
           self.checkCollisions()
           self.checkApple()
           self.doMove()
           self.after(DELAY, self.onTimer)
       else:
           self.gameOver()
 
   def onKeyPressed(self, e):
       key = e.keysym
 
       if key == "Left" and not self.right:
          self.left = True
          self.up = False
          self.down = False
 
       if key == "Right" and not self.left:
           self.right = True
           self.up = False
           self.down = False
 
       if key == "Up" and not self.down:
           self.up = True
           self.right = False
           self.left = False
 
       if key == "Down" and not self.up:
           self.down = True
           self.right = False
           self.left = False
 
      if key == "Escape":
          self.quit()
 
   def gameOver(self):
      self.delete(ALL)
      self.create_text(self.winfo_width() / 2, self.winfo_height() / 2, 
                       text="Game Over", fill="white")
 
class Snake(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
       parent.title("Snake")
       self.board = Board(parent)
       self.pack()
 
root = Tk()
snake = Snake(root)
root.mainloop()

Đầu tiên chúng ta định nghĩa một số hằng số dùng trong game:

  • WIDTHHEIGHT là kích thước cửa sổ chính.
  • DOT_SIZE là kích thước của mồi và mỗi đốt trên con rắn.
  • ALL_DOTS là số lượng đốt tối đa của rắn. Vì cửa sổ có kích thước 300 * 300, mỗi đốt rắn có kích thước 10 * 10 nến số lượng đốt tối đa là (300 * 300) / (10 * 10) = 900.
  • RAND_POS là hằng số để tính vị trí ngẫu nhiên của mồi.
  • DELAY là tốc độ của game.
x = [0] * ALL_DOTS
y = [0] * ALL_DOTS

Tiếp theo chúng ta tạo 2 mảng x và y, hai mảng này lưu trữ tọa độ của tất cả các đốt trên thân con rắn.

Kế tiếp là phương thức initGame(), nhiệm vụ của phương thức này là khởi tạo toàn bộ mọi thứ có trong game, từ khởi tạo biến, load ảnh đến khởi động timer…

try: 
    self.dotImage = Image.open("dot.png")
    self.dotImage.thumbnail((10, 10), Image.ANTIALIAS)
    self.dot = ImageTk.PhotoImage(self.dotImage)
 
    self.headImage = Image.open("head.png")
    self.headImage.thumbnail((10, 10), Image.ANTIALIAS) 
    self.head = ImageTk.PhotoImage(self.headImage)
 
    self.appleImage = Image.open("apple.png")
    self.appleImage.thumbnail((10, 10), Image.ANTIALIAS) 
    self.apple = ImageTk.PhotoImage(self.appleImage)
 
except (IOError, e):
    print (e)
    sys.exit(1)

Đoạn code trên thực hiện load ảnh dùng để hiển thị mồi và các đốt của con rắn. Do mỗi đốt của con rắn và mồi có kích thước 10×10 pixel nên chúng ta nên resize kích thước ảnh về 10×10 phòng trường hợp ảnh của chúng ta quá lớn bằng phương thức thumbnail().

self.createObjects()
self.locateApple()

Phương thức createObjects() có nhiệm vụ vẽ các đối tượng lên canvas. Phương thức locateApple() tạo tọa độ ngẫu nhiên của mồi và vẽ lên canvas.

self.bind_all("<Key>", self.onKeyPressed)

Tiếp theo chúng ta gọi phương thức bind_all() để gắn sự kiện bấm phím vào phương thức onKeyPressed().

def createObjects(self):

    self.create_image(self.apple_x, self.apple_y, image=self.apple,
        anchor=NW, tag="apple")
    self.create_image(50, 50, image=self.head, anchor=NW,  tag="head")
    self.create_image(30, 50, image=self.dot, anchor=NW, tag="dot")
    self.create_image(40, 50, image=self.dot, anchor=NW, tag="dot")

Bên trong phương thức createObjects() chúng ta vẽ các đối tượng lên canvas bao gồm mồi và 3 đốt đầu của con rắn. Ý nghĩa của các tham số đã được giải thích trong bài viết trước ngoại trừ tham số tag. Tham số tag đơn giản là giống như chúng ta đưa một ID cho ảnh vậy thôi, về sau chúng ta sẽ có các phương thức tìm các đối tượng này nhờ vào tag.

Bên trong phương thức checkApple() chúng ta kiểm tra xem con rắn có ăn trúng mồi hay không, nếu có thì thêm một đốt vào sau con rắn và gọi đến phương thức locateApple() để khởi tạo mồi mới.

apple = self.find_withtag("apple")
head = self.find_withtag("head")

Phương thức find_withtag() sẽ tìm đối tượng trên canvas dựa vào tag của đối tượng đó. Ở đây chúng ta cần tìm 2 đối tượng là mồi và đầu của con rắn – đốt đầu tiên.

x1, y1, x2, y2 = self.bbox(head)
overlap = self.find_overlapping(x1, y1, x2, y2)

Phương thức bbox() trả về một tuple là tọa độ điểm trái-trên và phải-dưới của một đối tượng. Phương thức find_overlapping() sẽ tìm các đối tượng nằm đè lên nhau. Tức là ở đây chúng ta kiểm tra xem đầu con rắn có chạm vào mồi hay không.

for ovr in overlap:
  
    if apple[0] == ovr:
        x, y = self.coords(apple)
        self.create_image(x, y, image=self.dot, anchor=NW, tag="dot")
        self.locateApple()

Nếu đầu con rắn chạm vào mồi thì chúng ta vẽ một đốt mới tại vị trí của mồi sau đó gọi đến phương thức locateApple() để khởi tạo mồi mới.

Bên trong phương thức doMove() chúng ta kiểm tra hướng đi hiện tại của con rắn và cập nhật tọa độ của tất cả các đốt trên con rắn.

z = 0
while z < len(items)-1:
    c1 = self.coords(items[z])
    c2 = self.coords(items[z+1])
    self.move(items[z], c2[0]-c1[0], c2[1]-c1[1])
    z += 1

Tọa độ của các đốt phía sau sẽ bằng tọa độ của đốt liền trước đó. Riêng tọa độ của đầu thì dựa trên hướng đi hiện tại.

Trong phương thức checkCollisions() chúng ta kiểm tra xem con rắn có cắn phải mình hay đụng tường hay không.

x1, y1, x2, y2 = self.bbox(head)
overlap = self.find_overlapping(x1, y1, x2, y2)

for dot in dots:
    for over in overlap:
        if over == dot:
          self.inGame = False

Nếu có thì chúng ta cho thuộc tính inGameFalse tức là kết thúc game.

if y1 > HEIGHT - DOT_SIZE:
    self.inGame = False

Phương thức locateApple() sẽ tạo ngẫu nhiên mồi mới trên màn hình.

apple = self.find_withtag("apple")
self.delete(apple[0])

Trước hết chúng ta xóa mồi cũ đi.

r = random.randint(0, RAND_POS)

Sau đó dùng phương thức randint() để lấy một số ngẫu nhiên từ 0 đến RAND_POS – 1.

self.apple_x = r * DOT_SIZE
...
self.apple_y = r * DOT_SIZE

Hai dòng trên set tọa độ của mồi.

Trong phương thức onKeyPressed() chúng ta kiểm tra sự kiện bấm phím.

if key == "Left" and not self.right: 
    self.left = True
    self.up = False
    self.down = False

Trong trường hợp trên chúng ta kiểm tra nếu người dùng bấm phim mũi trên trái và hướng đi hiện tại của con rắn không phải hướng bên phải thì set các thuộc tính leftTrue, các thuộc tính còn lại là False. Tương tự với các hướng còn lại.

def onTimer(self):

    if self.inGame:
        self.checkCollisions()
        self.checkApple()
        self.doMove()
        self.after(DELAY, self.onTimer)
    else:
        self.gameOver() 

Bên trong phương thức onTimer(), chúng ta thực hiện một số công việc cứ sau 100 mili giây (lưu trong hằng số DELAY). Chúng ta kiểm tra sự va chạm của con rắn với mồi hay tường… cập nhật tọa độ con rắn rồi gọi phương thức after() một lần nữa để đồng hồ chạy tiếp 100ms và lại tiếp tục kiểm tra. Nếu vì một lý do gì đó mà thuộc tính inGame là False thì chúng ta gọi phuonwg thức gameOver().

def gameOver(self):

    self.delete(ALL)
    self.create_text(self.winfo_width()/2, self.winfo_height()/2, 
        text="Game Over", fill="white")     

Bên trong phương thức gameOver() chúng ta xóa các đối tượng ra khỏi bộ nhớ và in dòng chữ “Game Over” lên giữa màn hình.

Untitled

Tkinter – Đồ họa

Trong phần này chúng ta sẽ học cách vẽ các đối tượng hình học trong Tkinter bằng cách sử dụng widget Canvas.

Vẽ đoạn thẳng

Để vẽ đoạn thẳng thì chúng ta dùng phương thức create_line() của lớp CanvasBạn cứ hiểu Canvas giống như một tờ giấy để chúng ta vẽ mọi thứ trên đó, nếu bạn đã từng lập trình HTML5 thì chắc không còn xa lạ gì với canvas.

from tkinter import Tk, Canvas, Frame, BOTH

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
       self.parent = parent
       self.initUI()
 
   def initUI(self):
       self.parent.title("Lines")
       self.pack(fill=BOTH, expand=1)
 
       canvas = Canvas(self)
       canvas.create_line(15, 25, 200, 25)
       canvas.create_line(300, 25, 300, 200, dash=(4, 2))
       canvas.create_line(55, 85, 155, 85, 105, 180, 55, 85)
 
       canvas.pack(fill=BOTH, expand=1)
 
root = Tk()
ex = Example(root)
root.geometry("400x250+300+300")
root.mainloop()

Trong ví dụ trên chúng ta vẽ một vài đoạn thẳng lên canvas.

canvas = Canvas(self)

Đầu tiên chúng ta tạo đối tượng Canvas với tham số self để canvas bao phủ toàn bộ cửa sổ.

canvas.create_line(15, 25, 200, 25)

Phương thức create_line() nhận vào tham số là tọa độ x, y của điểm bắt đầu và điểm kết thúc của một đoạn thẳng.

canvas.create_line(300, 35, 300, 200, dash=(4, 2))

Tham số dash tùy chỉnh kiểu vẽ là các đường nét đứt, ở ví dụ trên, dash=(4, 2) nghĩa là vẽ các nốt dài 4 pixel cách nhau 2 pixel.

canvas.create_line(55, 85, 155, 85, 105, 180, 55, 85)

Bạn cũng có thể đưa vào nhiều điểm, dòng code trên đưa vào 3 điểm để vẽ một hình tam giác.

Capture

Vẽ màu

Màu trong máy tính là màu RGB, là tổ hợp của 3 giá trị đỏ (Red), xanh lá (Green) và xanh lam (Blue).

from tkinter import Tk, Canvas, Frame, BOTH

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
       self.parent = parent
       self.initUI()
 
   def initUI(self):
       self.parent.title("Colors")
       self.pack(fill=BOTH, expand=1)
 
       canvas = Canvas(self)
       canvas.create_rectangle(30, 10, 120, 80, outline="#fb0", fill="#fb0")
       canvas.create_rectangle(150, 10, 240, 80, outline="#f50", fill="#f50")
       canvas.create_rectangle(270, 10, 370, 80, outline="#05f", fill="#05f")
       canvas.pack(fill=BOTH, expand=1)
 
root = Tk()
ex = Example(root)
root.geometry("400x100+300+300")
root.mainloop()

Trong ví dụ trên chúng ta vẽ 3 hình chữ nhật với 3 màu khác nhau.

canvas.create_rectangle(30, 10, 120, 80, 
    outline="#fb0", fill="#fb0")

Phương thức create_rectangle() tạo một hình chữ nhật trên canvas với 4 tham số đầu tiên là tọa độ x, y của điểm trái-trên và điểm phải-dưới. Tham số outline là giá trị màu đường viền, tham số fill là giá trị màu của hình chữ nhật, tất cả đều ở dạng hexa.

Capture

Vẽ một số đối tượng hình học khác

from tkinter import Tk, Canvas, Frame, BOTH

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
       self.parent = parent
       self.initUI()
 
   def initUI(self):
       self.parent.title("Shapes")
       self.pack(fill=BOTH, expand=1)
 
       canvas = Canvas(self)
 
       canvas.create_oval(10, 10, 80, 80, outline="gray", fill="gray", width=2)
       canvas.create_oval(110, 10, 210, 80, outline="gray", fill="gray", width=2)
       canvas.create_rectangle(230, 10, 290, 60, outline="gray", fill="gray", width=2)
       canvas.create_arc(30, 200, 90, 100, start=0, extent=210, outline="gray", fill="gray", width=2)
 
       points = [150, 100, 200, 120, 240, 180, 210, 200, 150, 150, 100, 200]
       canvas.create_polygon(points, outline="gray", fill="gray", width=2)
 
       canvas.pack(fill=BOTH, expand=1)
 
root = Tk()
ex = Example(root)
root.geometry("330x220+300+300")
root.mainloop()

Trong ví dụ trên chúng ta vẽ 5 hình là hình tròn, hình elip, hình chữ nhật, hình quạt và một đa giác.

canvas.create_oval(10, 10, 80, 80, outline="red", 
        fill="green", width=2)

Phương thức create_oval() dùng để vẽ hình tròn. Bốn tham số đầu tiên là tọa độ x, y của điểm trái-trên và phải-dưới của hình tròn (bởi vì hình tròn trong Tkinter được nằm trong một hình chữ nhật).

canvas.create_arc(30, 200, 90, 100, start=0, 
    extent=210, outline="#f11", fill="#1f1", width=2)

Phương thức create_arc() vẽ hình quạt, 4 tham số đầu tiên cũng là tọa độ hình chữ nhật chứa hình quạt này. Tham số start là hướng mà quạt hướng tới, tham số extend là độ lớn của quạt, cả 2 tham số này đều có đơn vị độ (từ 0 đến 360 độ).

points = [150, 100, 200, 120, 240, 180, 210, 
    200, 150, 150, 100, 200]
canvas.create_polygon(points, outline='red', 
    fill='green', width=2)

Phương thức create_polygon() vẽ một đa giác, tham số là tọa độ x, y của các điểm, bạn có thể đưa các điểm này vào bằng tay như các phương thức khác hoặc lưu các điểm trong một list rồi đưa list này vào.

Capture

Hiển thị ảnh từ file

from tkinter import Tk, Canvas, Frame, BOTH, NW
from PIL import Image, ImageTk

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
        self.parent = parent
        self.initUI()
      
   def initUI(self):
       self.parent.title("Image")
       self.pack(fill=BOTH, expand=1)
 
       self.img = Image.open("C:\\tatras.jpeg")
       self.tatras = ImageTk.PhotoImage(self.img)
 
       canvas = Canvas(self, width=self.img.size[0]+20, height=self.img.size[1]+20)
       canvas.create_image(10, 10, anchor=NW, image=self.tatras)
       canvas.pack(fill=BOTH, expand=1)

root = Tk()
ex = Example(root)
root.mainloop()     

Ví dụ trên vẽ hình từ file lên canvas.

self.img = Image.open("tatras.jpg")
self.tatras = ImageTk.PhotoImage(self.img)

Ở đây chúng ta dùng lớp ImageImageTk từ module PIL. Module này không có sẵn trong Python mà bạn phải tự cài thêm vào.

canvas = Canvas(self, width=self.img.size[0]+20, height=self.img.size[1]+20)

Chúng ta tạo đối tượng canvas với chiều dài và chiều rộng của file cộng thêm 20 pixel.

canvas.create_image(10, 10, anchor=NW, image=self.tatras)

Để hiện hình từ file lên canvas chúng ta dùng phương thức create_image(). Hai tham số đầu tiên là tọa độ góc trái-trên của hình. Tham số anchor=NW có tác dụng cắt hình từ vị trí NW = North West = tây-bắc) cho đến hết góc phải-dưới của ảnh, tức là lấy nguyên toàn bộ ảnh, mặc định nếu không đặt tham số này thì chương trình chỉ hiển thị phần giữa (CENTER) của ảnh. Bạn có thể thử các giá trị khác như N, E, SE (nhớ import các hằng số vô trước)… dưới đây là sơ đồ của các hướng.

tkanchor

Tham số image là đối tượng ảnh cần hiển thị.

Capture

Ghi chữ lên canvas

from tkinter import Tk, Canvas, Frame, BOTH, W

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)  
       self.parent = parent 
       self.initUI()
 
 
   def initUI(self):
 
       self.parent.title("Lyrics") 
       self.pack(fill=BOTH, expand=1)

       canvas = Canvas(self)
       canvas.create_text(20, 30, anchor=W, font="VNI-Dom 18", 
                          text="Most relationships seem so transitory")
       canvas.create_text(20, 60, anchor=W, font="VNI-Dom 18", 
                          text="They're good but not the permanent one")
       canvas.create_text(20, 130, anchor=W, font="VNI-Dom 18", 
                          text="Who doesn't long for someone to hold")
       canvas.create_text(20, 160, anchor=W, font="VNI-Dom 18", 
                          text="Who knows how to love without being told") 
       canvas.create_text(20, 190, anchor=W, font="VNI-Dom 18", 
                          text="Somebody tell me why I'm on my own") 
       canvas.create_text(20, 220, anchor=W, font="VNI-Dom 18", 
                          text="If there's a soulmate for everyone") 
       canvas.pack(fill=BOTH, expand=1)

 
root = Tk()
ex = Example(root)
root.geometry("420x250+300+300")
root.mainloop() 

Đoạn code trên thực hiện “vẽ” lời bài hát lên canvas.

canvas.create_text(20, 30, anchor=W, font="VNI-Dom 18",
                   text="Most relationships seem so transitory")

Chúng ta dùng phương thức  create_text() để hiển thị chữ lên canvas. Hai tham số đầu là tọa độ x, y của góc trái trên. Tham số font="VNI-Dom 18" tức là dùng font VNI-Dom với độ lớn là 18px. Tham số text là nội dung đoạn text được in lên canvas.

Capture

Tkinter – Hộp thoại

Trong bài này chúng ta sẽ học cách hiển thị các hộp thoại (Dialog) trong Tkinter.

Messagebox

Đây là kiểu hôp thoại dùng để hiển thị thông báo cho người dùng và đôi khi còn dùng để đưa ra yêu cầu chọn lựa cho người dùng.

from tkinter.ttk import Frame, Button
from tkinter import Tk, BOTH
import tkinter.messagebox as mbox

class Example(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)
        self.parent = parent
        self.initUI()
 
    def initUI(self):
        self.parent.title("Message Boxes")
        self.pack()
 
        error = Button(self, text="Error", command=self.onError)
        error.grid(padx=5, pady=5)
        warning = Button(self, text="Warning", command=self.onWarn)
        warning.grid(row=1, column=0)
        question = Button(self, text="Question", command=self.onQuest)
        question.grid(row=0, column=1)
        inform = Button(self, text="Information", command=self.onInfo)
        inform.grid(row=1, column=1)
 
    def onError(self):
        mbox.showerror("Error", "Could not open file")
 
    def onWarn(self):
        mbox.showwarning("Warning", "Deprecated function call")
 
    def onQuest(self):
        mbox.askquestion("Question", "Are you sure to quit?")
      
    def onInfo(self):
        mbox.showinfo("Information", "Download completed")
 
root = Tk()
ex = Example(root)
root.geometry("300x150+300+300")
root.mainloop()

Trong ví dụ trên chúng ta hiển thị 4 button, mỗi button sẽ hiển thị một kiểu message box khác nhau.

import tkinter.messagebox as mbox

Để sử dụng được message box thì bạn import module tkinger.messagebox (hoặc tkMessageBox nếu bạn dùng Python phiên bản 2.x).

error = Button(self, text="Error", command=self.onError)

Ở dòng trên chúng tạo button Error, khi bấm vào button này phương thức onError() sẽ được gọi để hiển thị một message box thông báo lỗi.

def onError(self):
    box.showerror("Error", "Could not open file")

Để hiển thị hộp thoại thông báo lỗi thì chúng ta gọi phương thức showerror(). Ngoài thông báo lỗi thì Tkinter cung cấp cho chúng ta các kiểu messagebox khác như sau:

  • askokcancel(title, message, option)
  • askquestion(title, message, option)
  • askretrycancel(title, message, option)
  • askyesno(title, message, option)
  • showinfo(title, message, option)
  • showwarning(title, message, option)

Trong đó tham số option là tham số tùy chỉnh hộp thoại dùng để tùy chỉnh một số tính chất như chọn Icon cho hộp thoại, chọn cửa sổ cha… bạn có thể không cần quan tâm đến tham số này.

Các hộp thoại askokcancel, askretrycancel, và askyesno trả về giá trị True khi người dùng bấm vào nút “OK” hoặc “Yes“, False khi bấm vào “No” hoặc “Cancel“. Hộp thoại askquestion trả về giá trị “yes” khi bấm nút “Yes” và “no” khi bấm nút “No“.

Capture

Hộp thoại chọn màu (Color chooser)

Các hệ điều hành hay có sẵn hộp thoại chọn màu cho chúng ta sử dụng. Để hiển thị hộp thoại này chúng ta dùng module colorchooser (hoặc tkColorChooser nếu bạn dùng Python phiên bản 2.x).

from tkinter import Tk, Frame, Button, BOTH, SUNKEN
from tkinter.colorchooser import askcolor

class Example(Frame):
  def __init__(self, parent):
     Frame.__init__(self, parent)
 
     self.parent = parent
     self.initUI()
 
  def initUI(self):
     self.parent.title("Color chooser")
     self.pack(fill=BOTH, expand=1)
 
     self.btn = Button(self, text="Choose Color", command=self.onChoose)
     self.btn.place(x=30, y=30)
 
     self.frame = Frame(self, border=1, relief=SUNKEN, width=100, height=100)
     self.frame.place(x=160, y=30)
 
  def onChoose(self):
     (rgb, hx) = askcolor()
     self.frame.config(bg=hx)
 
root = Tk()
ex = Example(root)
root.geometry("300x150+300+300")
root.mainloop()

Trong ví dụ trên chúng ta hiển thị một button và một frame lên cửa sổ chính. Button sẽ mở một hộp thoại chọn màu, chọn màu nào thì màu đó sẽ hiện lên trên frame của chúng ta.

(rgb, hx) = askcolor()
self.frame.config(bg=hx)

Để hiển thị hộp thoại chọn màu thì chúng ta gọi đến hàm askcolor(). Hàm này sẽ trả về một tuple, bên trong tuple này có 2 phần tử là một tuple bao gồm 3 giá trị R-G-B và một giá trị màu ở dạng hexa, bạn có thể dùng hàm print(hx) bên trong phương thức onChoose() để biết giá trị hexa này. Chúng ta dùng giá tị hexa để đặt làm màu nền cho Frame.

Capture

Hộp thoại chọn file (File Dialog)

Trong ví dụ dưới đây chúng ta sẽ dùng hàm Open của module tkinter.filedialog để mở một File Dialog.
from tkinter import Frame, Tk, BOTH, Text, Menu, END
from tkinter.filedialog import Open

class Example(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)
        self.parent = parent
        self.initUI()
 
    def initUI(self):
        self.parent.title("File dialog")
        self.pack(fill=BOTH, expand=1)
 
        menubar = Menu(self.parent)
        self.parent.config(menu=menubar)
 
        fileMenu = Menu(menubar)
        fileMenu.add_command(label="Open", command=self.onOpen)
        menubar.add_cascade(label="File", menu=fileMenu)
 
        self.txt = Text(self)
        self.txt.pack(fill=BOTH, expand=1)
 
    def onOpen(self):
         ftypes = [('Python files', '*.py'), ('All files', '*')]
         dlg = Open(self, filetypes = ftypes)
         fl = dlg.show()
 
     if fl != '':
         text = self.readFile(fl)
         self.txt.insert(END, text)
     
    def readFile(self, filename):
       f = open(filename, "r")
       text = f.read()
       return text
 
root = Tk()
ex = Example(root)
root.geometry("300x250+300+300")
root.mainloop()

Chúng ta sẽ tạo một menu cho phép mở file dialog và đọc nội dung của file được chọn vào lớp Text.

ftypes = [('Python files', '*.py'), ('All files', '*')]

Dòng trên là List lưu các định dạng file khác nhau để lọc file.

dlg = Open(self, filetypes = ftypes)
fl = dlg.show()

Để hiển thị file dialog thì chúng ta gọi hàm Open(). Hàm này trả về một string là đường dẫn đến file được chọn.

Capture

Tkinter – Menu

Trong phần này chúng ta sẽ học cách tạo menu trong Tkinter.

Ví dụ

from tkinter import Frame, Tk, Menu

class Example(Frame):
  def __init__(self, parent):
    Frame.__init__(self, parent)
 
    self.parent = parent
    self.initUI()
  
  def initUI(self):
    self.parent.title("Simple Menu")
 
    menuBar = Menu(self.parent)
    self.parent.config(menu=menuBar)
 
    fileMenu = Menu(menuBar)
    fileMenu.add_command(label="Exit", command=self.onExit)
    menuBar.add_cascade(label="File", menu=fileMenu)
 
  def onExit(self):
    self.quit()
 
root = Tk()
root.geometry("250x150+300+300")
app = Example(root)
root.mainloop()

Đoạn code trên tạo một cửa sổ có một menu, trong menu này có nút Exit, bấm vào nút này thì thoát chương trình.

menubar = Menu(self.parent)
self.parent.config(menu=menubar)

Để hiển thị các menu thì đầu tiên chúng ta phải tạo một menu bar trước. Chúng ta tạo menu bar từ lớp Menu.

fileMenu = Menu(menubar)

Tiếp theo chúng ta tạo menu File cũng từ lớp Menu.

fileMenu.add_command(label="Exit", command=self.onExit)

Để thêm các item vào một menu thì chúng ta dùng phương thức add_command(), ở trên chúng ta tạo item Exit và cho item này gọi đến phương thức onExit().

menubar.add_cascade(label="File", menu=fileMenu)

Sau khi tạo xong thì chúng ta gắn menu File vào menu bar bằng phương thức add_cascade().

Untitled

Tạo menu con

Ví dụ dưới đây sẽ tạo một menu con từ một menu cha.

Chúng ta tạo menu File, trong menu này có 1 item, 1 separator (đường kẻ ngang phân cách) và 1 menu con. Trong menu con có 3 item khác.

submenu = Menu(fileMenu)
submenu.add_command(label="New feed")
submenu.add_command(label="Bookmarks")
submenu.add_command(label="Mail")

Chúng ta tạo menu con cũng giống như tạo một menu bình thường từ lớp Menu.

fileMenu.add_cascade(label='Import', menu=submenu)

Chúng ta thêm menu con bằng cách gọi phương thức add_cascade từ menu cha và truyền tham chiếu đến đối tượng menu con vào tham số menu. Như vậy các menu từ menu bar cho đến submenu đều được tạo ra từ lớp Menu. Chỉ khác là menu bar chính là menu gốc, được gắn vào cửa sổ chính.

fileMenu.add_separator()

Để hiển thị một separator (đường kẻ ngang) thì chúng ta gọi phương thức add_separator().

Untitled

Popup menu

Popup menu hay còn được gọi là menu ngữ cảnh là menu được hiện ra khi click chuột lên cửa sổ.

from Tkinter import Tk, Frame, Menu


class Example(Frame):
  
    def __init__(self, parent):
        Frame.__init__(self, parent)   
         
        self.parent = parent
        
        self.initUI()
        
        
    def initUI(self):
      
        self.parent.title("Popup menu")
        self.menu = Menu(self.parent, tearoff=0)
        self.menu.add_command(label="Beep", command=self.bell())
        self.menu.add_command(label="Exit", command=self.onExit)

        self.parent.bind("<Button-3>", self.showMenu)
        self.pack()
        
        
    def showMenu(self, e):
        self.menu.post(e.x_root, e.y_root)
       

    def onExit(self):
        self.quit()


root = Tk()
root.geometry("250x150+300+300")
app = Example(root)
root.mainloop()  

Trong ví dụ trên chúng tạo popup menu có 2 item khi click chuột phải lên cửa sổ.

self.menu = Menu(self.parent, tearoff=0)

Ban đầu chúng ta cũng tạo một đối tượng Menu như menu bình thường. Tham số tearoff=0 sẽ tắt chế độ hiển thị menu trên một cửa sổ riêng.

self.parent.bind("<Button-3>", self.showMenu)

Dòng code trên gắn sự kiện click chuột phải (<Button-3>) vào phương thức showMenu().

def showMenu(self, e):
    self.menu.post(e.x_root, e.y_root)

Trong phương thức showMenu(), chúng ta gọi phương thức self.menu.post(x, y) để hiển thị popup menu tại vị trí được chỉ định trên cửa sổ. Tham số là đối tượng của sự kiện click chuột.

Untitled

Tkinter – Widget

Trong phần này chúng ta sẽ tìm hiểu về một số widget cơ bản trong Tkinter là Checkbutton, Label, Scale, và Listbox.

Widget là các thành phần cấu tạo nên một ứng dụng GUI. Widget rất đa dạng, có một số widget quan trọng cần phải có của bất kì nền tảng nào kể cả Tkinter ví dụ như button (nút bấm), check box hay scroll bar (thanh cuộn). Ngoài những widget cơ bản lập trình viên còn có thể tùy chỉnh widget của riêng mình.

Checkbutton

Checkbutton là widget hiển thị hộp đánh dấu.

from tkinter import Tk, Frame, Checkbutton
from tkinter import BooleanVar, BOTH

class Example(Frame):
  def __init__(self, parent):
    Frame.__init__(self, parent)
 
    self.parent = parent
    self.initUI()
 
  def initUI(self):
    self.parent.title("Checkbutton")
    self.pack(fill=BOTH, expand=True)
    self.var = BooleanVar()
    
    cb = Checkbutton(self, text="Show Title", variable=self.var, command=self.onClick)
    cb.select()
    cb.place(x=50, y=50)
 
  def onClick(self):
    if self.var.get() == True:
      self.master.title("Checkbutton")
    else:
      self.master.title("")
 
root = Tk()
root.geometry("250x150+300+300")
app = Example(root)
root.mainloop()

Trong ví dụ trên chúng ta hiển thị một check button lên cửa sổ có chức năng hiện/ẩn tiêu đề cửa sổ khi check.

self.var = BooleanVar()

Ở trên chúng ta tạo ra một đối tượng BooleanVar, đối tượng này sẽ được kết nối với checkbutton, mỗi khi trạng thái check thay đổi thì giá trị của self.var cũng sẽ thay đổi.

cb = Checkbutton(self, text="Show title", variable=self.var, command=self.onClick)

Ở dòng trên chúng ta tạo một đối tượng Checkbutton. Chúng ta kết nối biến self.var thông qua tham số variable. Tham số command chỉ định phương thức nào sẽ được gọi khi check, ở đây là phương thức onClick().

cb.select()

Chúng ta sử dụng phương thức select() để thiết lập trạng thái cho check cho button.

if self.var.get() == True:
    self.master.title("Checkbutton")
else:
    self.master.title("")

Tại phương thức onClick() chúng ta cho hiện/ẩn tiêu đề cửa sổ thông qua phương thức master.title(), chúng ta kiểm tra check button có được check hay không thông qua thuộc tính self.var.

Capture

Label

Label dùng để hiển thị text hoặc hình ảnh. Trong ví dụ dưới đây, chúng ta sẽ dùng Label để hiển thị ảnh lên màn hình.

from PIL import Image, ImageTk
from tkinter import Tk, Frame, Label

class Example(Frame):
  def __init__(self, parent):
    Frame.__init__(self, parent)
 
    self.parent = parent
 
    self.initUI()
 
  def initUI(self):
    self.parent.title("Label")
 
    self.img = Image.open("C:\\tatras.jpg")
    tatras = ImageTk.PhotoImage(self.img)
    label = Label(self, image=tatras)
 
    label.image = tatras
 
    label.pack()
    self.pack()
 
  def setGeometry(self):
    w, h = self.img.size
    self.parent.geometry(("%dx%d+300+300") % (w, h))
 
root = Tk()
ex = Example(root)
ex.setGeometry()
root.mainloop()

Để chạy được ví dụ trên thì bạn phải có thư viện Pillow trong Python.

from PIL import Image, ImageTk

Mặc định thì Label trong Tkinter cũng có các module có thể hiển thị ảnh nhưng rất hạn chế.

label = Label(self, image=tatras)

Chúng ta dùng tham số image khi khởi tạo để gán ảnh vào label.

label.image = tatras

Tiếp theo bạn phải tạo một biến để giữ lại tham chiếu đến ảnh nếu không bộ thu dọn tài nguyên của Python sẽ xóa mất ảnh của chúng ta.

w, h = self.img.size
self.parent.geometry(("%dx%d+300+300") % (w, h))

Hai dòng code trên chúng ta cho kích thước màn hình bằng đúng với kích thước ảnh.

Capture

Scale

Scale là widget hiển thị một thanh cuộn gắn với một khoảng giá trị nào đó.

from tkinter import Tk, BOTH, IntVar, LEFT
from tkinter.ttk import Frame, Label, Scale, Style

class Example(Frame):
  def __init__(self, parent):
    Frame.__init__(self, parent)
    self.parent = parent
    self.initUI()
 
  def initUI(self):
    self.parent.title("Scale")
    self.style = Style()
    self.style.theme_use("default")
 
    self.pack(fill=BOTH, expand=1)
 
    scale = Scale(self, from_=0, to=100, command=self.onScale)
    scale.pack(side=LEFT, padx=15)
 
    self.var = IntVar()
    self.label = Label(self, text=0, textvariable=self.var)
    self.label.pack(side=LEFT)
 
  def onScale(self, val):
    v = int(float(val))
    self.var.set(v)
 
root = Tk()
ex = Example(root)
root.geometry("250x100+300+300")
root.mainloop()

Trong ví dụ trên chúng ta cho hiển thị một scale và một label, text trên label sẽ thay đổi khi kéo thanh cuộn trên scale.

scale = Scale(self, from_=0, to=100, command=self.onScale)

Tạo một đối tượng Scale. Hai tham số from_ và to là phạm vi giá trị của scale. Tham số command chỉ định phương thức sẽ được gọi khi di chuyển thanh cuộn.

self.var = IntVar()
self.label = Label(self, text=0, textvariable=self.var)    

Cũng giống như Checkbutton, ở đây chúng ta tạo label, một đối tượng IntVar và kết nối đối tượng đó với label bằng tham số textvariable. Giá trị của biến này sẽ được hiển thị trên label.

def onScale(self, val):
  
    v = int(float(val))
    self.var.set(v)

Khi di chuyển thanh cuộn, phương thức onScale() sẽ được gọi kèm theo giá trị hiện tại của Scale. Chúng ta chuyển đổi kiểu dữ liệu của tham số này từ sang float rồi sang int sau đó đặt làm giá trị của đối tượng IntVar và đoạn text hiển thị trên label sẽ thay đổi theo

Capture

Listbox

Listbox cho phép hiển thị một danh sách các item. Người dùng có thể chọn một hoặc nhiều item.

from tkinter.ttk import Frame, Label
from tkinter import Tk, BOTH, Listbox, StringVar, END

class Example(Frame):
  def __init__(self, parent):
    Frame.__init__(self, parent)
 
    self.parent = parent
    self.initUI()
 
  def initUI(self):
    self.parent.title("Listbox")
    self.pack(fill=BOTH, expand=1)
 
    acts = ["Scarlet Johansson", "Rachel Weiss", "Natalie Portman", "Jessica Alba"]
 
    lb = Listbox(self)
 
    for i in acts:
       lb.insert(END, i)
 
    lb.bind("<<ListboxSelect>>", self.onSelect)
 
    lb.pack(pady=15)
 
    self.var = StringVar()
    self.label = Label(self, text=0, textvariable=self.var)
    self.label.pack()
 
  def onSelect(self, val):
    sender = val.widget
    idx = sender.curselection()
    value = sender.get(idx)
 
    self.var.set(value)
 
root = Tk()
ex = Example(root)
root.geometry("300x250+300+300")
root.mainloop()

Trong ví dụ trên, chúng ta hiển thị một danh sách tên các nữ diễn viên trong Listbox. Tên được chọn sẽ được hiển thị trên một label.

lb = Listbox(self)
for i in acts:
    lb.insert(END, i)

Chúng ta tạo một đối tượng Listbox và thêm các item vào bằng phương thức insert().

lb.bind("<<ListboxSelect>>", self.onSelect)    

Khi chọn một item thì sự kiện <<ListboxSelect>> sẽ được thực thi. Chúng ta gán sự kiện này vào phương thức onSelect().

sender = val.widget

Trong phương thức onSelect() chúng ta lấy tham chiếu đến Listbox thông qua thuộc tính val.widget và gán vào biến sender.

idx = sender.curselection()

Phương thức curselection() lấy về chỉ số của item đang được chọn.

value = sender.get(idx)  
self.var.set(value)

Từ chỉ số chúng ta lấy giá trị text của item bằng phương thức get(), sau đó gán vào label.

Capture

Tkinter – Quản lý Layout

Trong phần này chúng ta sẽ tìm hiểu cách sử dụng layout trong Tkinter.

Trong Tkinter có hai loại widget, loại thứ nhất là các widget thường như nút bấm, textbox… loại thứ hai là containter, đây là những widget chứa các widget khác, chúng còn có một cái tên nữa là layout.

Trong Tkinter có 3 loại layout là packgrid và place. Trong đó place là kiểu layout tự do, thuật ngữ gọi là Absolute Positioning, tức là bạn sẽ phải tự quy định vị trí cũng như kích thước của các widget. Layout pack sắp xếp các widget của bạn theo chiều ngang và chiều dọc. Còn layout grid sắp xếp widget theo dạng bảng.

Thiết kế tự do

Thường thì khi thiết kế giao diện chúng ta dùng layout nhiều hơn là thiết kế theo kiểu tự do. Khi thiết kế theo kiểu tự do này chúng ta sẽ phải tự quy định vị trí và kích thước cho các widget trên màn hình, nếu kích thước cửa sổ thay đổi thì kích thước và vị trí của các widget không thay đổi. Thiết kế theo kiểu tự do cũng rất bất tiện khi bạn muốn thay đổi, thêm hay bớt các widget thì hầu như bạn sẽ phải sắp xếp lại toàn bộ widget.

from PIL import Image, ImageTk
from tkinter import Tk, Label, BOTH
from tkinter.ttk import Frame, Style

class Example(Frame):
   def __init__(self, parent):
     Frame.__init__(self, parent)
 
     self.parent = parent
     self.initUI()
 
   def initUI(self):
     self.parent.title("Absolute positioning")
     self.pack(fill=BOTH, expand=1)
 
     Style().configure("TFrame", background="#333")
 
     bard = Image.open("C:\\bardejov.jpg")
     bardejov = ImageTk.PhotoImage(bard)
     label1 = Label(self, image=bardejov)
     label1.image = bardejov
     label1.place(x=20, y=20)
  
     rot = Image.open("C:\\rotunda.jpg")
     rotunda = ImageTk.PhotoImage(rot)
     label2 = Label(self, image=rotunda)
     label2.image = rotunda
     label2.place(x=40, y=160)
 
     minc = Image.open("C:\\mincol.jpg")
     mincol = ImageTk.PhotoImage(minc)
     label3 = Label(self, image=mincol)
     label3.image = mincol
     label3.place(x=170, y=50)
 
root = Tk()
root.geometry("300x280+300+300")
app = Example(root)
root.mainloop()

Trong ví dụ trên, chúng ta hiển thị 3 ảnh lên cửa sổ.

from PIL import Image, ImageTk

Để hiển thị ảnh thì chúng ta có thể dùng module PhotoImage trong gói Tkinter nhưng module này rất hạn chế nên ở đây mình dùng module ImageImageTk trong thư viện Pillow, thư viện này không có sẵn trong Python 3 mà bạn phải tự down về và cài vào.

Style().configure("TFrame", background="#333") 

Dòng trên sẽ tô màu nền cửa sổ là màu xám đen.

bard = Image.open("C:\\bardejov.jpg")
bardejov = ImageTk.PhotoImage(bard)

Hai dòng trên sẽ tạo các đối tượng ảnh và tải chúng vào chương trình.

label1 = Label(self, image=bardejov)

Chúng ta tạo đối tượng Label, đây là một lớp dùng để hiển thị chữ và ảnh.

label1.image = bardejov

Chúng ta phải lưu lại tham chiếu đến ảnh nếu không sẽ bị xóa.

label1.place(x=20, y=20)

Chúng ta đặt ảnh tại vị trí x = 20 và y = 20.

Capture

Button

Trong ví dụ dưới đây, chúng ta sẽ hiển thị hai button (nút bấm) ở góc trái màn hình sử dụng layout pack.

from tkinter import Tk, RIGHT, BOTH, RAISED
from tkinter.ttk import Frame, Button, Style

class Example(Frame):
   def __init__(self, parent):
     Frame.__init__(self, parent)
     self.parent = parent
     self.initUI()
 
   def initUI(self):
     self.parent.title("Button")
     self.style = Style()
     self.style.theme_use("default")
 
     frame = Frame(self, relief=RAISED, borderwidth=1)
     frame.pack(fill=BOTH, expand=True)
     self.pack(fill=BOTH, expand=True)
     
     closeButton = Button(self, text="Close")
     closeButton.pack(side=RIGHT, padx=5, pady=5)
     okButton = Button(self, text="OK")
     okButton.pack(side=RIGHT)
 

root = Tk()
root.geometry("300x200+300+300")
app = Example(root)
root.mainloop()

Chúng ta tạo ra 2 frame, frame đầu tiên là layout chính dùng để chứa toàn bộ widget. Frame thứ hai chúng ta đặt như lên frame đầu tiên và cho kích thước dãn ra theo cả chiều ngang và chiều dọc. Hai button được đặt theo chiều ngang ở góc bên phải của cửa sổ.

frame = Frame(self, relief=RAISED, borderwidth=1)
frame.pack(fill=BOTH, expand=True)

Hai dòng code trên tạo ra Frame thứ hai, frame này sẽ chiếm phần lớn diện tích Frame chính. Tham số relief mô phỏng hiệu ứng hiển thị 3D của Frame, ngoài  giá trị RAISED bạn có thể thử một số giá trị khác như FLAT, SUNKEN, GROOVE, và RIDGE. Tham số borderwidth hiển thị đường viền của frame, mặc định tham số này là 0 nên bạn ko thể thấy đường viền.

closeButton = Button(self, text="Close")
closeButton.pack(side=RIGHT, padx=5, pady=5)

Ở hai dòng trên chúng ta tạo nút close và tham chiếu đến đối tượng closeButton. Phương thức pack() quy định cách hiển thị của button trên cửa sổ, tham số side=RIGHT sẽ đưa button vào vị trí bên phải, padxpady duy định khoảng cách giữa button với các widget khác và với đường viền cửa sổ chính, ở đây khoảng cách là 5 pixel.

okButton.pack(side=RIGHT)

Nút OK cũng được tạo ra tương tự và cũng được đưa sang phía bên phải màn hình, sát bên cạnh nút Close với khoảng cách 5 pixel.

Capture

Tạo Form

Trong ví dụ dưới đây, chúng ta tiếp tục tìm hiểu về layout pack. 

from tkinter import Tk, Text, TOP, BOTH, X, N, LEFT
from tkinter.ttk import Frame, Label, Entry

class Example(Frame):
   def __init__(self, parent):
     Frame.__init__(self, parent)
 
     self.parent = parent
     self.initUI()
 
   def initUI(self):
     self.parent.title("Review")
     self.pack(fill=BOTH, expand=True)
 
     frame1 = Frame(self)
     frame1.pack(fill=X)
 
     lbl1 = Label(frame1, text="Title", width=6)
     lbl1.pack(side=LEFT, padx=5, pady=5)
 
     entry1 = Entry(frame1)
     entry1.pack(fill=X, padx=5, expand=True)
   
     frame2 = Frame(self)
     frame2.pack(fill=X)
 
     lbl2 = Label(frame2, text="Author", width=6)
     lbl2.pack(side=LEFT, padx=5, pady=5)
 
     entry2 = Entry(frame2)
     entry2.pack(fill=X, padx=5, expand=True)
 
     frame3 = Frame(self)
     frame3.pack(fill=BOTH, expand=True)
 
     lbl3 = Label(frame3, text="Review", width=6)
     lbl3.pack(side=LEFT, anchor=N, padx=5, pady=5)
 
     txt = Text(frame3)
     txt.pack(fill=BOTH, pady=5, padx=5, expand=True)
 
root = Tk()
root.geometry("300x300+300+300")
app = Example(root)
root.mainloop() 

Chúng ta thiết kế một cửa sổ hiển thị form đánh giá.

self.pack(fill=BOTH, expand=True)

Đầu tiên chúng ta tạo frame chính để chứa các widget còn lại kể cả các frame khác.

frame1 = Frame(self)
frame1.pack(fill=X)

lbl1 = Label(frame1, text="Title", width=6)
lbl1.pack(side=LEFT, padx=5, pady=5)           

entry1 = Entry(frame1)
entry1.pack(fill=X, padx=5, expand=True)

Chúng ta tạo một frame và đặt 2 widget vào đó, đó là một label và một entry. Label là widget dùng để hiển thị text, entry là widget tạo nhập text. Label có độ dài cố định còn entry thì có độ dài tự động thay đổi theo kích thước cửa sổ nhờ vào 2 tham số fill và expand. Frame thứ hai cũng tương tự.

frame3 = Frame(self)
frame3.pack(fill=BOTH, expand=True)

lbl3 = Label(frame3, text="Review", width=6)
lbl3.pack(side=LEFT, anchor=N, padx=5, pady=5)        

txt = Text(frame3)
txt.pack(fill=BOTH, pady=5, padx=5, expand=True) 

Trong frame thứ ba chúng ta đặt một Label và một Text. Label được đặt về phía trên của frame, còn text tự động co dãn theo kích thước cửa sổ.

Capture

Grid layout

Tiếp theo chúng ta sẽ tìm hiểu về layout grid.

from tkinter import Tk, W, E
from tkinter.ttk import Frame, Button, Style
from tkinter.ttk import Entry

class Example(Frame):
 
 def __init__(self, parent):
   Frame.__init__(self, parent) 
 
   self.parent = parent
   self.initUI()

 
 def initUI(self):
 
   self.parent.title("Calculator")
 
   Style().configure("TButton", padding=(0, 5, 0, 5), font='serif 10')
 
   self.columnconfigure(0, pad=3)
   self.columnconfigure(1, pad=3)
   self.columnconfigure(2, pad=3)
   self.columnconfigure(3, pad=3)
 
   self.rowconfigure(0, pad=3)
   self.rowconfigure(1, pad=3)
   self.rowconfigure(2, pad=3)
   self.rowconfigure(3, pad=3)
   self.rowconfigure(4, pad=3)
 
   entry = Entry(self)
   entry.grid(row=0, columnspan=4, sticky=W+E)
   cls = Button(self, text="Cls")
   cls.grid(row=1, column=0)
   bck = Button(self, text="Back")
   bck.grid(row=1, column=1)
   lbl = Button(self)
   lbl.grid(row=1, column=2) 
   clo = Button(self, text="Close")
   clo.grid(row=1, column=3) 
   sev = Button(self, text="7")
   sev.grid(row=2, column=0) 
   eig = Button(self, text="8")
   eig.grid(row=2, column=1) 
   nin = Button(self, text="9")
   nin.grid(row=2, column=2) 
   div = Button(self, text="/")
   div.grid(row=2, column=3) 
  
   fou = Button(self, text="4")
   fou.grid(row=3, column=0) 
   fiv = Button(self, text="5")
   fiv.grid(row=3, column=1) 
   six = Button(self, text="6")
   six.grid(row=3, column=2) 
   mul = Button(self, text="*")
   mul.grid(row=3, column=3) 
 
   one = Button(self, text="1")
   one.grid(row=4, column=0) 
   two = Button(self, text="2")
   two.grid(row=4, column=1) 
   thr = Button(self, text="3")
   thr.grid(row=4, column=2) 
   mns = Button(self, text="-")
   mns.grid(row=4, column=3) 
 
   zer = Button(self, text="0")
   zer.grid(row=5, column=0) 
   dot = Button(self, text=".")
   dot.grid(row=5, column=1) 
   equ = Button(self, text="=")
   equ.grid(row=5, column=2) 
   pls = Button(self, text="+")
   pls.grid(row=5, column=3)
 
   self.pack()

root = Tk()
app = Example(root)
root.mainloop()

Đoạn code trên thiết kế giao diện của một chiếc máy tính bỏ túi.

Style().configure("TButton", padding=(0, 5, 0, 5), font='serif 10')

Chúng ta quy định kiểu phông chữ thông qua tham số font và khoảng cách của frame với cửa sổ thông qua tham số padding.

self.columnconfigure(0, pad=3)
...        
self.rowconfigure(0, pad=3)

Hai phương thức columnconfigure() và rowconfigure() quy định khoảng cách giữa các widget.

entry = Entry(self)
entry.grid(row=0, columnspan=4, sticky=W+E)

Chúng ta tạo một Entry để hiển thị số cũng như kết quả tính toán. Tham số sticky quy định cách thức co dãn theo kích thước cửa sổ, W+E tức là co dãn theo chiều từ trái sang phải.

cls = Button(self, text="Cls")
cls.grid(row=1, column=0)

Nút cls được đặt tại vị trí hàng 1 cột 0, chỉ số hàng và cột bắt đầu từ 0.

self.pack()

Phương thức pack() hiển thị các widget lên màn hình và khởi tạo kích thước cho chúng. Nếu chúng ta chỉ gọi pack() mà không đưa các tham số vào như trên thì kích thước của các widget luôn luôn là kích thước vừa đủ để bao bọc lấy đoạn text bên trong, ngoài ra không có tham số truyền vào thì phương thức này sẽ đặt layout ở vị trí trên cùng của frame. Bạn có thể kéo dãn cửa sổ để thấy cách nó hoạt động.

Capture

Tkinter – Giới thiệu về Tkinter

 

Tkinter là một gói trong Python có chứa module Tk hỗ trợ cho việc lập trình GUI. Tk ban đầu được viết cho ngôn ngữ Tcl. Sau đó Tkinter được viết ra để sử dụng Tk bằng trình thông dịch Tcl trên nền Python. Ngoài Tkinter ra còn có một số công cụ khác giúp tạo một ứng dụng GUI viết bằng Python như wxPython, PyQt, và PyGTK.

Về ngôn ngữ Python

python logo

Python là một ngôn ngữ lập trình hướng đối tượng, python hoàn toàn tạo kiểu động, cấp phát bộ nhớ động. Mục đích ra đời của Python là cung cấp một ngôn ngữ lập trình có cấu trúc rõ ràng, sáng sủa, thuận tiện cho người mới học lập trình. Python được phát triển bởi Guido và Rossum. Phiên bản đầu tiên được phát hành vào năm 1991. Python được lấy cảm hứng từ ABC, Haskell, Java, Lisp, Icon và Perl. Python là một ngôn ngữ thông dịch, đa nền tảng. Một trong những đặc điểm độc nhất của Python là ngôn ngữ này không dùng đến dấu chấm phẩy, dấu mở-đóng ngoặc {} để kết thúc câu lệnh hay khối lệnh, mà cách duy nhất để nó nhận biết một lệnh là dấu thụt đầu dòng.

Hiện tại Python có hai dòng phiên bản là dòng 2.x và 3.x. Phiên bản Python mà series này sử dụng là phiên bản 3.5.1. Ngày nay Python được phát triển bởi một cộng đồng tình nguyện trên khắp thế giới, và lẽ dĩ nhiên nó là một phần mềm mã nguồn mở.

Update: Python phiên bản 2.7.18 được phát hành vào ngày 20/04/2020 là phiên bản cuối cùng của dòng 2.x, nghĩa là từ nay Python sẽ chỉ cập nhật phiên bản 3.x trở lên.

Trang chủ của Python nằm tại địa chỉ python.org.

Ví dụ

Đoạn code dưới đây ví dụ về cách tạo một cửa sổ bằng Tkinter.

from tkinter import Tk, Frame, BOTH

class Example(Frame):
 def __init__(self, parent):
 Frame.__init__(self, parent, background="white")
 self.parent = parent
 self.initUI()
 
 def initUI(self):
 self.parent.title("Simple")
 self.pack(fill=BOTH, expand=1)
 
root = Tk()
root.geometry("250x150+300+300")
app = Example(root)
root.mainloop()

Đoạn code trên sẽ tạo ra một cửa sổ nhỏ trên màn hình.

from tkinter import Tk, Frame

Ở dòng trên chúng ta import hai lớp Tk Frame từ gói Tkinter. Lớp Tk được dùng để tạo cửa sổ, lớp Frame dùng để quản lý các widget. Chúng ta sẽ tìm hiểu về widget ở các bài sau.

class Example(Frame):
  
    def __init__(self, parent):
        Frame.__init__(self, parent, background="white")   

Chúng ta định nghĩa lớp Excample kế thừa từ lớp Frame. Lớp Frame là một widget dùng để quản lý các widet khác – các lớp kiểu này được gọi chung là containter hoặc layout. Trong phương thức khởi tạo __init__() chúng ta gọi lại phương thức khởi tạo từ lớp Frame và đưa vào tham số màu nền.

self.parent = parent

Chúng ta dùng thuộc tính parent để lưu lại đối tượng root.

self.initUI()

Chúng ta định nghĩa phương thức initUI() dùng để tạo các widget trên cửa sổ.

self.parent.title("Simple")

Phương thức title() sẽ thay đổi tiêu đề cửa sổ.

self.pack(fill=BOTH, expand=1)

Phương thức pack() có tác dụng sắp xếp vị trí các widget trước khi gắn chúng lên cửa sổ chính. Tham số fill=BOTH sẽ dãn widget ra theo chiều ngang và chiều rộng, tức là widget đó sẽ chiếm toàn bộ không gian cửa sổ.

root = Tk()

Ở dòng trên chúng ta tạo một cửa sổ và gán vào biến root để quản lý. Cửa sổ luôn phải được tạo trước khi tạo các widget.

root.geometry("250x150+300+300")

Phương thức geometry() quy định kích thước cửa sổ và vị trí hiển thị trên màn hình. Hai tham số đầu tiên là kích thước cửa sổ, hai phương thức sau là vị trí của cửa sổ trên màn hình.

app = Example(root)

Dòng code trên chúng ta tạo một Frame và gắn nó vào cửa sổ chính.

root.mainloop()  

Cuối cùng là phương thức mainloop(). Bạn cứ hiểu đơn giản là phải gọi phương thức này thì cửa sổ mới hiện lên màn hình và bắt đầu nhận các sự kiện để xử lý.
Capture

Hiển thị Button

Trong ví dụ dưới đây, chúng ta sẽ tạo nút Quit, khi bấm vào nút đó thì thoát chương trình.

from tkinter import Tk, BOTH
from tkinter.ttk import Frame, Button, Style

class Example(Frame):
 def __init__(self, parent):
 Frame.__init__(self, parent)
 
 self.parent = parent
 self.initUI()
 
 def initUI(self):
 self.parent.title("Quit button")
 self.style = Style()
 self.style.theme_use("default")
 
 self.pack(fill=BOTH, expand=1)
 
 quitButton = Button(self, text="Quit", command=self.quit)
 quitButton.place(x=50, y=50)
 
root = Tk()
root.geometry("250x150+300+300")
app = Example(root)
root.mainloop()

Chúng ta sẽ đặt một nút bấm trên cửa sổ, bấm vào nút đó thì thoát chương trình.

from tkinter.ttk import Frame, Button, Style

Một số widget trong Tkinter được hỗ trợ hiển thị màu sắc hoa lá khác nhau, thuật ngữ gọi là theme. Để sử dụng theme thì chúng ta import module ttk vào.

self.style = Style()
self.style.theme_use("default")

Để quy định kiểu theme thì chúng ta khởi tạo đối tượng Style() và gọi tới phương thức theme_use(). Một số kiểu theme mà mình biết là clam, default, alt, và classic.

quitButton = Button(self, text="Quit",
    command=self.quit)

Ở dòng trên chúng ta tạo một đối tượng widget là Button. Tham số đầu tiên là đối tượng container của nó. Tham số thứ hai là đoạn text sẽ hiển thị trên nút. Tham số thứ ba là lệnh được thực thi khi nút được bấm, ở đây là phương thức quit() tức là thoát chương trình.

quitButton.place(x=50, y=50)

Phương thức place() để quy định vị trí của nút bấm trên cửa sổ.

Capture

Python – Các hàm kiểm tra trạng thái

Trong phần này chúng ta sẽ tìm hiểu một số hàm kiểm tra trạng thái.

Trong Python mọi thứ đều là đối tượng. Một đối tượng thì có các thuộc tính và phương thức. Chúng ta có thể dùng các hàm kiểm tra để biết các thuộc tính và phương thức của một đối tượng.

Hàm dir()

Hàm dir() trả về một danh sách các thuộc tính và phương thức có trong một đối tượng.

>>> dir(())
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']

Trong ví dụ trên chúng ta dùng hàm dir() để xem các thuộc tính và phương thức có trong một tuple.

>>> print (().__doc__)
tuple() -> empty tuple
tuple(iterable) -> tuple initialized from iterable's items

If the argument is a tuple, the return value is the same object.

Trong đó có thuộc tính __doc__, chúng ta có thể dùng hàm print() để xem nội dung của thuộc tính này.

import sys

class Object:
   def __init__(self):
      pass
   def examine(self):
      print (self)


o = Object()

print (dir(o))
print (dir([]))
print (dir({}))
print (dir(1))
print (dir())
print (dir(len))
print (dir(sys))
print (dir("String"))

Trong đoạn code trên chúng ta kiểm tra một vài đối tượng khác nhau trong đó có cả đối tượng thuộc lớp do chúng ta định nghĩa.

Nếu không đưa tham số vào thì hàm dir() trả về tên các module trong chương trình.

>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']
>>> import sys
>>> import math, os
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'math', 'os', 'sys']

Hàm type() và id()

Hàm type() trả về kiểu dữ liệu của một đối tượng.

import sys

def function(): pass

class MyObject():
   def __init__(self):
      pass

o = MyObject()

print (type(1))
print (type(""))
print (type([]))
print (type({}))
print (type(()))
print (type(object))
print (type(function))
print (type(MyObject))
print (type(o))
print (type(sys))

Trong ví dụ trên chúng ta in ra kiểu dữ liệu của một số đối tượng.

<type 'int'>
<type 'str'>
<type 'list'>
<type 'dict'>
<type 'tuple'>
<type 'type'>
<type 'function'>
<type 'classobj'>
<type 'instance'>
<type 'module'>

Hàm id() sẽ trả về id của một đối tượng.

import sys

def fun(): pass

class MyObject():
   def __init__(self):
      pass

o = MyObject()

print (id(1))
print (id(""))
print (id({}))
print (id([]))
print (id(sys))
print (id(fun))
print (id(MyObject))
print (id(o))
print (id(object))

Trong ví dụ trên chúng ta in ra id của một số đối tượng, kể cả đối tượng có sẵn trong Python.

135717024
3084304536
3084111220
3084104940
3084304500
3084112812
3084074556
3084130444
135568640

Module sys

Module sys cung cấp các thông tin về hệ điều hành và tình trạng của Python.

>>> import sys
>>> sys.version
'3.5.1 (v3.5.1:37a07cee5969, Dec  6 2015, 01:38:48) [MSC v.1900 32 bit (Intel)]'
>>> sys.platform
'win32'
>>> sys.path
['C:\\Users\\PhoCode\\', 'C:\\python\\python35.zip', 'C:\\python\\DLLs', 'C:\\python\\lib', 'C:\\python', 'C:\\python\\lib\\site-packages']

Trong ví dụ trên chúng ta kiểm tra phiên bản Python, tên hệ điều hành và các đường dẫn thư mục liên quan.

Chúng ta sẽ kiểm tra một số thuộc tính khác trong module sys. Bạn cũng có thể dùng hàm dir() để lấy về danh sách đầy đủ các thuộc tính và phương thức có trong module sys.

>>> sys.maxsize
2147483647
>>> sys.executable
'C:\\python\\python.exe'
>>> sys.argv
['']

Trong ví dụ trên chúng ta kiểm tra 3 thuộc tính là maxsize, executable, và argv.

>>> sys.maxsize
2147483647

Thuộc tính maxsize lưu trữ giá trị số nguyên lớn nhất được hỗ trợ trong Python.

>>> sys.executable
'C:\\python\\python.exe'

Thuộc tính executable trả về đường dẫn đầy đủ đến trình thông dịch Python.

>>> sys.argv
['']

Thuộc tính argv trả về tham số được đưa vào trình thông dịch Python. Ở đây là danh sách rỗng, nếu bạn chạy từ file script thì tham số này sẽ có ít nhất đường dẫn đến file script của bạn.

Một số hàm khác

def fun(): pass

print (hasattr(object, '__doc__'))
print (hasattr(fun, '__doc__'))
print (hasattr(fun, '__call__'))

print (getattr(object, '__doc__'))
print (getattr(fun, '__doc__'))

Hàm hasattr() kiểm tra xem một đối tượng nào đó có chứa thuộc tính nào đó không. Hàm getattr() sẽ trả về nội dung của thuộc tính đó nếu có.

True
True
True
The most base type
None

Hàm isinstance kiểm tra xem một đối tượng có thuộc một lớp nào đó không.

class MyObject():
   def __init__(self):
      pass

o = MyObject()

print (isinstance(o, MyObject))
print (isinstance(o, object))
print (isinstance(2, int))
print (isinstance('str', str))

Chúng ta đã biết là tất cả mọi thứ trong Python đều là đối tượng, kể cả số hay chuỗi. Tất cả các lớp trong Python đều kế thừa từ một lớp cơ sở có tên là object. Do đó hàm instance sẽ trả về true nếu chúng ta kiểm tra xem một đối tượng có thuộc lớp object hay không.

True
True
True
True

Hàm issubclass() kiểm tra xem một lớp có được kế thừa từ một lớp khác hay không. Nếu chúng ta dùng hàm issubclass() với 2 tham số giống nhau thì cũng trả về True, nói cách khác trong Python thì một lớp cũng là lớp con của chính nó.

class Object():
   def __init__(self):
      pass

class Wall(Object):
   def __init__(self):
      pass

print (issubclass(Object, Object))
print (issubclass(Object, Wall))
print (issubclass(Wall, Object))
print (issubclass(Wall, Wall))

Trong ví dụ trên, chúng ta định nghĩa lớp Wall kế thừa từ lớp Object.

True
False
True
True

Thuộc tính __doc__ là đoạn chuỗi mô tả về một đối tượng nào đó. Thuộc tính __name__ là chuỗi chứa tên của một đối tượng nào đó.

def noaction():
   '''Blah blah blah'''
   pass


funcs = [noaction, len, str]

for i in funcs:
   print (i.__name__)
   print (i.__doc__)
   print ("-" * 75)

Trong ví dụ trên, chúng ta tạo một list chứa tên các hàm, trong đó có một hàm do chúng ta tự định nghĩa, 2 hàm còn lại có sẵn. Sau đó chúng ta duyệt qua list này và in nội dung hai thuộc tính __name____doc__ của mỗi hàm ra.

noaction
Blah blah blah
---------------------------------------------------------------------------
len
len(object) -> integer

Return the number of items of a sequence or mapping.
---------------------------------------------------------------------------
str
str(object) -> string

Return a nice string representation of the object.
If the argument is a string, the return value is the same object.
---------------------------------------------------------------------------

Cuối cùng là hàm callable(), hàm này kiểm tra xem một đối tượng có phải là một hàm hay không.

class Car:      
    def setName(self, name):
        self.name = name    

def fun():
    pass

c = Car()    
    
print (callable(fun))
print (callable(c.setName))
print (callable([]))
print (callable(1))

Trong ví dụ trên chúng ta kiểm tra 4 đối tượng fun, c.setName, [] (list rỗng) và số 1 có phải là các hàm hay không.

print (callable(fun))
print (callable(c.setName))

Chắc chắn fun chính là một hàm do chúng ta tự định nghĩa, setName() là một phương thức (hàm nằm trong một lớp) của lớp Car.

True
True
False
False

 

Python – Iterator và Generator

Trong phần này chúng ta sẽ tìm hiểu về Iterator và Generator.

Iterator là một khái niệm chỉ các lớp dạng danh sách cho phép chúng ta duyệt qua chúng mà không cần quan tâm đến kiểu dữ liệu đó là gì.

Để một lớp được gọi là Iterator thì lớp đó phải hỗ trợ 2 phương thức là __iter__()__next__(). Cả 2 phương thức đó được gọi chung là giao thức iterator (Iterator Protocol). Trong đó phương thức __iter__() chỉ đơn giản là trả về đối tượng tham chiếu tới chính nó, còn phương thức __next__() trả về phần tử tiếp theo có trong danh sách cho đến phần tử cuối cùng thì giải phóng exception StopIteration.

Hầu hết các kiểu dữ liệu dạng danh sách trong Python như kiểu list, tuple, string, dictionary đều hỗ trợ giao thức Iterator.

str = "formidable"
it = iter(str)

print (it.__next__())
print (it.__next__())
print (it.__next__())

print (list(it))

Trong đoạn code trên, chúng ta sử dụng tính tăng iterator của một string. Hàm iter() sẽ gọi đến phương thức __iter__() có sẵn trong lớp string để lấy về đối tượng tham chiếu đến chính nó. Phương thức __next__() sẽ trả về phần tử tiếp theo trong danh sách. Ngoài ra bạn còn có thể dùng các hàm như list(), tuple() để tạo các đối tượng danh sách mới dựa trên một iterator.

f
o
r
['m', 'i', 'd', 'a', 'b', 'l', 'e']

Trong ví dụ dưới đây, chúng ta sẽ viết một lớp riêng có hỗ trợ giao thức Iterator.

class seq:
   def __init__(self):
      self.x = 0

   def __next__(self):
      self.x += 1
      return self.x**self.x

   def __iter__(self):
      return self


s = seq()
n = 0

for i in s:
   print (i)
   n += 1
   if n > 10:
      break

Trong đoạn code trên, chúng ta tạo một dãy số dài vô tận. Khi chúng ta sử dụng vòng lặp for, vòng lặp sẽ tự động gọi hàm iter() lên đối tượng của chúng ta để lấy về một đối tượng tham chiếu tới chính nó, và cứ mỗi lần lặp nó lại gọi đến phương thức __next__() đế lấy phần tử tiếp theo trong dãy.

def __next__(self):
    self.x += 1
    return self.x**self.x

Phương thức __next__() trả về phần tử tiếp theo trong dãy.

 def __iter__(self):
    return self

Phương thức __iter__ trả về đối tượng tham chiếu đến chính nó (hay còn gọi là đối tượng iterator).

 if n > 10:
      break

Do chúng ta tạo một danh sách dài vô tận nên phải có câu lệnh để ngắt vòng lặp.

1
4
27
256
3125
46656
823543
16777216
387420489
10000000000
285311670611

Trong ví dụ dưới đây, chúng ta sẽ ngắt vòng lặp bằng cách giải phóng exception StopIteration.

class seq14:
   def __init__(self):
      self.x = 0

   def __next__(self):
      self.x += 1
      if self.x > 14:
         raise StopIteration
      return self.x**self.x

   def __iter__(self):
      return self


s = seq14()

for i in s:
   print (i)

Trong đoạn code trên chúng ta in ra 14 số đầu tiên trong dãy.

if self.x > 14:
    raise StopIteration

StopIteration sẽ ngắt vòng lặp.

Generator

Generator chẳng qua cũng chỉ là Iterator, chúng cũng tạo ra một đối tượng kiểu danh sách, nhưng bạn chỉ có thể duyệt qua các phần tử của generator một lần duy nhất vì generator không lưu dữ liệu trong bộ nhớ mà cứ mỗi lần lặp thì chúng sẽ tạo phần tử tiếp theo trong dãy và trả về phần tử đó.

Đoạn code dưới đây ví dụ cách định nghĩa một generator.

def gen():
  x, y = 1, 2
  yield x, y
  x += 1
  yield x, y


it = gen()

print (it.__next__())
print (it.__next__())

try:
  print (it.__next__())
except StopIteration:
  print ("Iteration finished")

Để định nghĩa một generator thì chúng ta dùng từ khóa def giống như khi định nghĩa một hàm. Trong generator chúng ta dùng từ khóa yield để trả về các phần tử.

Từ khóa yield có chức năng y hệt như từ khóa return, chỉ khác ở chỗ là khi bạn gọi phương thức __next__() lần thứ nhất thì generator thực hiện các công việc tính toán giá trị rồi gặp từ khóa yield nào thì nó sẽ trả về các phần tử tại ví trí đó, khi bạn gọi phương thức __next__() lần thứ hai thì generator không bắt đầu chạy tại vị trí đầu tiên mà bắt đầu ngay phía sau từ khóa yield thứ nhất. Cứ như thế generator tạo ra các phần tử trong dãy, cho đến khi không còn gặp từ khóa yield nào nữa thì giải phóng exception StopIteration.

(1, 2)
(2, 2)
Iteration finished

 

Python – Exception

Trong phần này chúng ta sẽ tìm hiểu về lỗi ngoại lệ (exception) trong Python.

Lỗi exception là lỗi xảy ra trong quá trình chạy (tiếng anh là run time error). Trong lập trình có 3 loại lỗi là lỗi biên dịch, lỗi ngữ nghĩa và lỗi ngoại lệ. Trong đó lỗi biên dịch dễ phát hiện nhất, lỗi ngoại lệ là lỗi khó phát hiện nhất. Ví dụ như bạn viết một chương trình máy tính bỏ túi gồm các chức năng cộng trừ nhân chia. Thoạt nhìn có vẻ như rất đơn giản, chỉ cần yêu cầu người dùng nhập vào 2 số và phép tính, thực hiện phép tính rồi trả lại kết quả cho người dùng. Nhưng chương trình này sẽ báo lỗi nếu người dùng thực hiện một phép tính chia với số chia là 0. Đó là loại lỗi chỉ xảy ra trong khi chương trình chạy, để phòng ngừa loại lỗi này thì chỉ có một cách là các lập trình viên phải đoán trước các trường hợp lỗi ngoại lệ có thể xảy ra và xử lý chúng trước trong code của mình. Nhưng vì bạn chẳng phải nhà tiên tri, bạn không thể nào đoán trước tất cả mọi thứ được 🙂 nên trên thực tế thì các phần mềm lớn vẫn thường có lỗi chứ không có phần mềm nào không có lỗi cả, công việc của chúng ta là cố gắng phát hiện lỗi và sửa lỗi thôi.

Trong Python và các ngôn ngữ lập trình cấp cao khác như Java, C#… các lỗi exception thường gặp được định nghĩa thành một lớp riêng, mỗi khi chương trình gặp các lỗi này trình thông dịch sẽ báo tên các lỗi đó ra màn hình.

>>> 3 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

Trong ví dụ trên, chúng ta chạy trình thông dịch Python trong cmd và thực hiện phép tính 3 / 0. Chương trình trả về lỗi ZeroDivisionError.

def input_numbers():
    a = float(input("Enter first number:"))
    b = float(input("Enter second number:"))
    return a, b

x, y = input_numbers()
print ("%d / %d is %f" % (x, y, x/y))

Trong đoạn code trên, chúng ta tính phép chia 2 số, nếu một trong 2 số là số 0 thì sẽ nảy sinh một lỗi exception.

Enter first number:2
Enter second number:0
Traceback (most recent call last):
  File "./zerodivision.py", line 12, in <module>
    print "%d / %d is %f" % (x, y, x/y)
ZeroDivisionError: float division

Để xử lý lỗi exception thì chúng ta có 2 cách.

def input_numbers():
    a = float(input("Enter first number:"))
    b = float(input("Enter second number:"))
    return a, b

x, y = input_numbers()

while True:
    if y != 0:
        print ("%d / %d is %f" % (x, y, x/y))
        break
    else:
       print ("Cannot divide by zero")
       x, y = input_numbers() 

Cách thứ nhất là chúng ta kiểm tra bằng các câu lệnh if...else. Cứ thấy người dùng nhập vào số 0 thì chúng ta in câu thông báo và yêu câu nhập lại, không thì thực hiện phép tính và trả kết quả.

Enter first number:4
Enter second number:0
Cannot divide by zero
Enter first number:5
Enter second number:0
Cannot divide by zero
Enter first number:5
Enter second number:6
5 / 6 is 0.833333

Cách thứ hai là “bắt” các đối tượng exception có thể nảy sinh trong Python.

def input_numbers():
    a = float(raw_input("Enter first number:"))
    b = float(raw_input("Enter second number:"))
    return a, b
x, y = input_numbers()

while True:
    try:
        print ("%d / %d is %f" % (x, y, x/y))
        break
    except ZeroDivisionError:
       print ("Cannot divide by zero")
       x, y = input_numbers() 

Để bắt exception thì chúng ta sử dụng câu lệnh try...except. Trong ví dụ trên, sau từ khóa try là các câu lệnh thực hiện các công việc của chúng ta. Sau câu lệnh except là tên exception có thể xảy ra. Bên trong except là các câu thông báo lỗi mà chúng ta muốn in ra. Khi chạy ban đầu chương trình sẽ thực thi các câu lệnh bên trong try, nếu có exception xảy ra thì câu lệnh except sẽ “bắt” exception đó rồi thực hiện phần thông báo lỗi.

except ValueError:
   pass
except (IOError, OSError):
   pass

Bạn có thể “bắt” nhiều exception bằng cách dùng nhiều câu lệnh except hoặc đặt các exception bên trong một tuple.

Tham số đối tượng exception

Khi trình thông dịch báo lỗi exception, thì một đối tượng của lớp exception đó được tạo ra và bạn có thể tham chiếu đến nó bằng cách đặt một tham số thứ 2 vào sau tên của exception để lấy tham chiếu đến đối tượng này (thường chúng ta đặt tên là e hoặc exception).

try:
    3/0
except ZeroDivisionError, e:
    print ("Cannot divide by zero")
    print ("Message:", e.message)
    print ("Class:", e.__class__)

Trong đối tượng exception có một thuộc tính tên là message. Đây là câu thông báo lỗi có sẵn trong Python.

Cannot divide by zero
Message: integer division or modulo by zero
Class: <type 'exceptions.ZeroDivisionError'>

Cấu trúc phân cấp của exception

Hệ thống exception cũng được phân cấp, với lớp gốc là lớp Exception.

try:
    while True:
       pass
except KeyboardInterrupt:
   print ("Program interrupted")

Trong đoạn code trên chúng ta có một vòng lặp vô tận. Nếu chúng ta bấm Ctrl+C, vòng lặp sẽ bị ngắt bởi lỗi KeyboardInterrupt.

Exception
  BaseException
    KeyboardInterrupt

Lớp KeyboardInterrupt là lớp kế thừa từ lớp BaseException.

try:
    while True:
       pass
except BaseException:
   print ("Program interrupted")

Chúng ta có thể bắt lỗi BaseException thay vì bắt lỗi KeyboardInterrupt.

Định nghĩa exception

Chúng ta có thể định nghĩa các lớp exception của riêng chúng ta.

class BFoundError(Exception):
   def __init__(self, value):
      print ("BFoundError: b character found at position %d" % value)

string = "You make me want to be a better man."

pos = 0
for i in string:
   if i == 'b':
      raise BFoundError(pos)
   pos = pos + 1

Các lớp exception mới đều phải được kế thừa từ lớp Exception. Để giải phóng một exception thì chúng ta dùng từ khóa raise. Trong ví dụ trên, chúng ta định nghĩa lớp BFoundError, một string và một vòng lặp, chúng ta duyệt qua string, nếu tìm thấy kí tự b trong chuỗi thì giải phóng exception.

BFoundError: b character found at position 20
Traceback (most recent call last):
  File "C:\Users\PhoCode\python\b.py", line 16, in <module>
    raise BFoundError, pos 
__main__.BFoundError

Từ khóa finally

Sau khối lệnh except chúng ta có thể có từ khóa finally và một số câu lệnh. Các câu lệnh trong khối finally sẽ được thực thi cho dù có hay không có lỗi exception nào xảy ra. Thường thì các câu lệnh trong finally được dùng để thực hiện các công việc giải phóng tài nguyên…

f = None

try:
   f = file('indaclub', 'r')
   contents = f.readlines()
   for i in contents:
      print (i,)
except IOError:
   print ('Error opening file')
finally:
   if f:
      f.close()

Đoạn code trên sẽ mở một file, nếu vì một lý do gì đó mà file không thể mở được thì lỗi IOError sẽ xảy ra. Và cho dù có lỗi hay không có lỗi xảy ra thì chúng ta vẫn làm công việc đóng file trong phần finally.