Phụ lục: Lumpy

Trở về Mục lục cuốn sách

Xuyên suốt cuốn sách, tôi đã dùng những biểu đồ nhằm thể hiện trạng thái của các chương trình đang chạy.

Ở Mục 2.2, chúng ta dùng một biểu đồ trạng thái để cho thấy tên và giá trị của các biến. Trong Mục 3.10 tôi có giới thiệu một biểu đồ ngăn xếp, trong đó thể hiện mỗi khung cho một lần gọi hàm. Từng khung đều thể hiện các tham số và biến địa phương của hàm hoặc phương thức. Những biểu đồ ngăn xếp cho hàm đệ quy có ở các Mục 5.9 và 6.5.

Mục 10.2 cho thấy dáng vẻ của một danh sách trong biểu đồ trạng thái, Mục 11.4 cho thấy từ điển, và Mục 12.6 thể hiện hai cách biểu diễn một bộ.

Mục 15.2 giới thiệu biểu đồ đối tượng, trong đó cho thấy trạng thái của các thuộc tính của đối tượng, và thuộc tính của những thuộc tính đó, và cứ thư vậy. Mục 15.3 có biểu đồ đối tượng cho Rectangle và các Point đi kèm trong đó. Mục 16.1 cho thấy trạng thái của một đối tượng Time. Mục 18.2 có một biểu đồ gồm một đối tượng lớp và một thực thể, từng cái lại có thuộc tính riêng của mình.

Sau cùng, Mục 18.8 giới thiệu biểu đồ lớp, trong đó cho thấy những lớp hợp thành chương trình, cùng những mối quan hệ giữa chúng.

Những biểu đồ này đều dựa theo Ngôn ngữ mô hình hóa thống nhất (Unified Modeling Language, UML), vốn là một ngôn ngữ đồ thị được chuẩn hóa, dành cho các kĩ sư phần mềm, phục vụ việc trao đổi thông tin về thiết kế chương trình, đặc biệt là những chương trình hướng đối tượng.

UML là một ngôn ngữ phong phú với nhiều loại biểu đồ thể hiện nhiều loại quan hệ giữa đối tượng và các lớp. Những biểu đồ trình bày trong sách này chỉ là một phần nhỏ của ngôn ngữ trên, nhưng chính là phần ngôn ngữ hay được dùng nhất trên thực tế.

Mục đich của phụ lục này là điểm lại những biểu đồ đã trình bày từ các chương trước, đồng thời giới thiệu Lumpy. Lumpy là chữ viết tắt của “UML in Python,” sau khi đảo lại chữ cái; công cụ này là một phần của Swampy, mà bạn đã cài đặt khi thực hiện nghiên cứu cụ thể trong Chương 4 hoặc Chương 19, hay nếu bạn đã làm Bài tập 4,

Lumpy sử dụng module inspect của Python để kiểm tra trạng thái của một chương trình đang chạy và phát sinh ra biểu đồ đối tượng (bao gồm cả biểu đồ ngăn xếp) và biểu đồ lớp.

Biểu đồ trạng thái


Hình 1: Biểu đồ trạng thái được Lumpy tạo ra.


Sau đây là một ví dụ có dùng Lumpy để tạo nên một biểu đồ trạng thái.

from swampy.Lumpy import Lumpy

lumpy = Lumpy()
lumpy.make_reference()

message = 'And now for something completely different'
n = 17
pi = 3.1415926535897932

lumpy.object_diagram()

Dòng thứ nhất để nhập lớp Lumpy từ swampy.Lumpy. Nếu bạn không cài Swampy theo hình thức một gói phần mềm, thì hãy đảm bảo rằng các file Swampy đều nằm trong đường dẫn tìm kiếm của Python và dùng câu lệnh import sau đây thay cho lệnh trong đoạn mã trên:

from Lumpy import Lumpy

Các dòng tiếp theo tạo nên một đối tượng Lumpy và lập một điểm “tham chiếu”; tại đó Lumpy ghi lại những đối tượng mà đã được định nghĩa cho đến điểm này.

Sau đó ta định nghĩa các biến mới rồi kích hoạt object_diagram; lệnh này vẽ nên những đối tượng đã được định nghĩa kể từ điểm tham chiếu, mà trong trường hợp này gồm có messagen và pi.

Hình 1 cho thấy kết quả. Kiểu dáng biểu đồ này khác với cái mà tôi đã trình bày trước đây. Chẳng hạn, từng tham chiếu được biểu diễn bằng một vòng tròn viết cạnh tên biến và một đoạn thẳng chỉ đến giá trị. Đồng thời những chuỗi kí tự dài đều được lược bớt. Song những thông tin mà biểu đồ truyền đạt thì vẫn như vậy.

Tên các biến được viết trong một khung có nhãn hiệu <module>, vốn để chỉ rằng đây là các biến ở cấp độ module, còn gọi là biến toàn cục.

Bạn có thể tải ví dụ này về từ http://thinkpython.com/code/lumpy_demo1.py. Hãy thử thêm vào một số lệnh gán rồi xem biểu đồ trông như thế nào.

Biểu đồ ngăn xếp


Hình 2: Biểu đồ ngăn xếp


Sau đây là một ví dụ dùng Lumpy để phát sinh một biểu đồ ngăn xếp. Bạn có thể tải về từ địa chỉ http://thinkpython.com/code/lumpy_demo2.py.

from swampy.Lumpy import Lumpy

def countdown(n):
    if n <= 0:
        print 'Blastoff!'
        lumpy.object_diagram()
    else:
        print n
        countdown(n-1)

lumpy = Lumpy()
lumpy.make_reference()
countdown(3)

Hình 2 biểu diễn kết quả. Từng khung được biểu diễn bằng một hộp với tên hàm viết ở ngoài và tên biến ở trong. Vì hàm này có tính đệ quy, nên mỗi tầng đệ quy chỉ có một khung.

Hãy nhớ rằng biểu đồ ngăn xếp cho thấy trạng thái của chương trình ở một điểm cụ thể trong quá trình thực thi. Để thu được biểu đồ mong muốn, đôi khi bạn phải suy nghĩ xem nên kích hoạt object_diagram ở đâu.

Trong trường hợp này tôi kích hoạt object_diagram sau khi thực thi trường hợp cơ sở của phép đệ quy. Bằng cách này, biểu đồ ngăn xếp sẽ cho thấy từng tầng một của phép đệ quy. Bạn có thể gọi object_diagram nhiều lần để nhận được một loạt các hình ảnh trạng thái về quá trình thực thi của chương trình.

Biểu đồ đối tượng


Hình 3: Biểu đồ đối tượng


Ví dụ này sẽ phát sinh ra một biểu đồ đối tượng cho thấy những danh sách ở Mục 10.1. Bạn có thể tải mã lệnh về từ http://thinkpython.com/code/lumpy_demo3.py.

from swampy.Lumpy import Lumpy

lumpy = Lumpy()
lumpy.make_reference()

cheeses = ['Cheddar', 'Edam', 'Gouda']
numbers = [17, 123]
empty = []

lumpy.object_diagram()

Hình 3 cho thấy kết quả. Các danh sách được biểu thị bởi một hộp cho thấy rõ ràng các chỉ số được ánh xạ đến các phần tử. Cách biểu diễn này hơi làm người xem lạc hướng, vì thực ra các chỉ số không phải thuộc về danh sách; song dù sao tôi nghĩ rằng cách này dễ đọc biểu đồ hơn. Danh sách rỗng được biểu diễn bằng một hộp trống không.


Hình 4: Biểu đồ đối tượng

Và sau đây là một ví dụ cho thấy các từ điển trong Mục 11.4. Bạn có thể tải về từ địa chỉ http://thinkpython.com/code/lumpy_demo4.py.

from swampy.Lumpy import Lumpy

lumpy = Lumpy()
lumpy.make_reference()

hist = histogram('parrot')
inverse = invert_dict(hist)

lumpy.object_diagram()

Hình 4 cho thấy kết quả. hist là một từ điển để ánh xạ từ những kí tự (chuỗi chỉ có 1 chữ cái) đến những số nguyên; inverse ánh xạ từ các số nguyên đến danh sách các chuỗi.


Hình 5: Biểu đồ đối tượng

Ví dụ này phát sinh ra một biểu đồ đối tượng cho các đối tượng Point và Rectangle, như ở Mục 15.6. Bạn cũng có thể tải nó về từ http://thinkpython.com/code/lumpy_demo5.py.

import copy
from swampy.Lumpy import Lumpy

lumpy = Lumpy()
lumpy.make_reference()

box = Rectangle()
box.width = 100.0
box.height = 200.0
box.corner = Point()
box.corner.x = 0.0
box.corner.y = 0.0

box2 = copy.copy(box)

lumpy.object_diagram()

Hình 5 cho thấy kết quả. copy.copy thực hiện “sao chép nông”, vì vậy cả box lẫn box2 đều có width và height riêng, nhưng chúng cùng chia sẻ mối đối tượng Point đi kèm. Hình thức chia sẻ như vậy thường là ổn thỏa đối với các đối tượng bất khả chuyển; song với kiểu khả chuyển, điều này dễ gây nên lỗi.

  Các đối tượng hàm và lớp


Hình 6. Biểu đồ đối tượng

Khi dùng Lumpy để lập nên các biểu đồ đối tượng, tôi thường định nghĩa các hàm và lớp trước khi đặt điểm tham chiếu. Bằng cách này, các đối tượng hàm và lớp không xuất hiện trên biểu đồ.

Nhưng nếu bạn truyền các hàm và lớp làm tham số, thì có thể bạn sẽ muốn chúng xuất hiện. Ví dụ sau đây sẽ cho thấy rằng khi chúng xuất hiện thì biểu đồ sẽ như thế nào; bạn có thể tải mã lệnh về từ http://thinkpython.com/code/lumpy_demo6.py.

import copy
from swampy.Lumpy import Lumpy

lumpy = Lumpy()
lumpy.make_reference()

class Point(object):
    """Represents a point in 2-D space."""

class Rectangle(object):
    """Represents a rectangle."""

def instantiate(constructor):
    """Instantiates a new object."""
    obj = constructor()
    lumpy.object_diagram()
    return obj

point = instantiate(Point)

Hình 6 biểu thị kết quả. Vì ta kích hoạt object_diagram bên trong một hàm, ta nhận được một biểu đồ ngăn xếp với một khung dành cho các biến ở cấp độ module và dành cho sự kích hoạt instantiate.

Ở cấp độ module, cả Point lẫn Rectangle đều tham chiếu đến các đối tượng lớp (mà có kiểu là type); instantiate tham chiếu đến một đối tượng hàm.

Biểu đồ này có thể làm rõ hai điều dễ gây nhầm lẫn: (1) sự khác biệt giữa đối tượng lớp là Point, với thực thể của Point là obj, và (2) sự khác biệt giữa đối tượng hàm tạo ra khi định nghĩa instantiate, và khung tạo ra khi nó được gọi đến.

Biểu đồ lớp


Hình 7: Biểu đồ lớp

Hình 8: Biểu đồ lớp.

Mặc dù tôi phân biệt giữa biểu đồ trạng thái, biểu đồ ngăn xếp và biểu đồ đối tượng, song chúng đa phần đều giống nhau ở chỗ đều cho thấy trạng thái của một chương trình đang chạy tại một thời điểm.

Biểu đồ lớp thì khác. Chúng cho thấy các lớp hợp thành một chương trình cũng như những mối quan hệ giữa chúng. Chúng không thay đổi theo thời gian vì chúng mô tả chương trình như một tổng thể chứ không phải tại mộ thời điểm cụ thể nào đó. Chẳng hạn, nếu một thực thể của Lớp A nói chung chứa một tham chiếu đến một thực thể của Lớp B, thì ta nói rằng có một “mối quan hệ HAS-A” giữa hai lớp này.

Sau đây là một ví dụ biểu diễn mối quan hệ HAS-A. Bạn có thể tải nó về từ http://thinkpython.com/code/lumpy_demo7.py.

from swampy.Lumpy import Lumpy

lumpy = Lumpy()
lumpy.make_reference()

box = Rectangle()
box.width = 100.0
box.height = 200.0
box.corner = Point()
box.corner.x = 0.0
box.corner.y = 0.0

lumpy.class_diagram()

Hình 7 biểu diễn kết quả. Từng lớp đều được biểu diễn bằng một hình hộp trong đó chứa tên của lớp, mọi phương thức mà lớp đó cung cấp, mọi biến của lớp, và mọi biến thực thể. Trong ví dụ này, cả Rectangle lẫn Point đều có biến thực thể, nhưng không có phương thức hay biến lớp.

Mũi tên chạy từ Rectangle đến Point cho thấy rằng các đối tượng Rectangle chứa một Point kèm theo. Ngoài ra, cả Rectangle lẫn Point đều thừa kế từ object, vốn được biểu diễn bằng một mũi tên có đầu tam giác trong biểu đồ này.

Sau đây là một ví dụ phức tạp hơn có dùng lời giải của tôi cho Bài tập 6. Bạn có thể tải mã lệnh về từ http://thinkpython.com/code/lumpy_demo8.py; bạn cũng sẽ phải cần đến http://thinkpython.com/code/PokerHand.py.

from swampy.Lumpy import Lumpy

from PokerHand import *

lumpy = Lumpy()
lumpy.make_reference()

deck = Deck()
hand = PokerHand()
deck.move_cards(hand, 7)

lumpy.class_diagram()

Hình 8 cho thấy kết quả. PokerHand thừa kế từ Hand, mà bản thân nó lại thừa kế từ Deck. Cả Deck lẫn PokerHand đều có Card. 

Biểu đồ này không  cho thấy được rằng Hand cũng có các lá bài, vì trong chương trình không có thực thể nào của Hand. Ví dụ này thể hiện một nhược điểm của Lumpy; nó chỉ biết được những thuộc tính và mối quan hệ HAS-A của những đối tượng nào được tạo nên (được “thực thể hóa”).

6 bình luận

Filed under Think Python

6 responses to “Phụ lục: Lumpy

  1. Pingback: Think Python: Cách tư duy như nhà khoa học máy tính | Blog của Chiến

  2. Bùi Quốc Dũng

    Rất cám ơn Anh. Hiện tại em đang nghiên cứu về python chạy trên nền Raspberry Pi ,tài liệu này rất quý đối với em .Chúc Anh có nhiều sức khỏe post nhiều bài hay giúp cộng đồng phát triển, có gì mới gửi cho em theo Email : dungbqc@gmail.com .Thân
    Bùi Quốc Dũng
    TP Bạc Liêu

  3. binh

    anh có file pdf của think python này ko cho em xin v

Bình luận về bài viết này