Chương 14: File

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

Sự duy trì dữ liệu

Phần lớn các chương trình ta đã thấy đến giờ đều có tính nhất thời về khía cạnh dữ liệu; theo nghĩa chúng chạy trong một thời gian ngắn, tạo ra kết quả nhưng khi kết thúc thì dữ liệu cũng biến mất. Nếu bạn chạy lại, chương trình sẽ bắt đầu với một trạng thái trống rỗng.

Các chương trình khác có tính duy trì dữ liệu: chúng chạy trong thời gian dài (hoặc luôn luôn chạy); và giữ lại ít nhất một phần dữ liệu trong một thiết bị lưu dữ liệu vĩnh viễn (ví dụ như ổ đĩa); và sau khi được kết thúc và khởi động lại, chương trình sẽ bắt đầu từ điểm mà chúng tạm dừng trước đây.

Các ví dụ về chương trình duy trì bao gồm các hệ điều hành, luôn được chạy mỗi khi máy tính được bật lên, và trình phục vụ web, vốn được chạy mọi lúc, đợi các yêu cầu gửi đến từ mạng máy tính.

Một trong những cách đơn giản nhất để chương trình có thể duy trì dữ liệu của chúng là bằng cách đọc và ghi các file chữ. Ta đã thấy các chương trình đọc được file chữ; ở chương này ta sẽ thấy chương trình ghi file.

Một cách khác là lưu trạng thái của chương trình vào trong một cơ sở dữ liệu. Ở chương này tôi sẽ trình bày một cơ sở dữ liệu đơn giản và một module, pickle, giúp cho việc lưu trữ dữ liệu của chương trình được dễ dàng.

Đọc và ghi

Một file chữ là một dãy các kí tự được lưu trên một thiết bị vĩnh viễn như một ổ đĩa cứng, bộ nhớ flash, hoặc đĩa CD-ROM. Ta đã thấy cách mở và đọc file ở Mục wordlist.

Để ghi một file, bạn cần mở nó theo chế độ 'w' đặt như tham biến thứ hai:

>>> fout = open('output.txt', 'w')

>>> print fout
<open file 'output.txt', mode 'w' at 0xb7eb2410>

Nếu file đã tồn tại, việc mở nó với chế độ w (ghi) sẽ xóa sạch dữ liệu cũ và bắt đầu với file trống, nên hãy cẩn thận! Nếu file chưa tồn tại, sẽ có một file mới được tạo ra.

Phương thức write đưa dữ liệu vào trong file.

>>> line1 = "This here's the wattle,\n"
>>> fout.write(line1)

Một lần nữa, đối tượng file theo dõi vị trí hiện thời, vì vậy nếu bạn gọi lại write lần nữa, nó sẽ ghi dữ liệu mới vào cuối file.

>>> line2 = "the emblem of our land.\n"

>>> fout.write(line2)

Khi ghi xong, bạn phải đóng file lại.

>>> fout.close()

Toán tử định dạng

Đối số của write phải là một chuỗi, vì vậy nếu muốn đưa các giá trị khác vào một file, ta cần phải chuyển đổi chúng thành chuỗi. Cách dễ nhất để làm điều đó là dùng str:

>>> x = 52
>>> f.write(str(x))

Một cách khác là dùng toán tử định dạng, %. Khi áp dụng nó cho các số nguyên, % là toán tử chia lấy dư. Nhưng khi hạng tử thứ nhất là một chuỗi, % là toán tử định dạng.

Hạng tử thứ nhất là chuỗi định dạng, bao gồm một hoặc nhiều dãy định dạng, để chỉ định cách định dạng cho toán hạng thứ hai. Kết quả sẽ là một chuỗi.

Chẳng hạn, dãy định dạng '%d' có nghĩa là hạng tử thứ hai cần được định dạng như một số nguyên (d là viết tắt của “decimal”):

>>> camels = 42

>>> '%d' % camels
'42'

Kết quả là chuỗi '42', mà ta không nên nhầm nó với giá trị nguyên 42.

Một dãy định dạng có thể xuất hiện bất cứ đâu trong chuỗi, vì vậy bạn có thể gài một giá trị vào trong một câu:

>>> camels = 42
>>> 'I have spotted %d camels.' % camels
'I have spotted 42 camels.'

Nếu có nhiều dãy định dạng trong chuỗi thì hạng tử thứ hai phải là một bộ. Mỗi dãy định dạng được cặp với một phần tử của bộ theo đúng thứ tự.

Ví dụ sau sử dụng '%d' để định dạng một số nguyên '%g' để định dạng một số có phần thập phân (đừng hỏi lí do dùng kí hiệu này), và '%s' để định dạng một chuỗi:

>>> 'In %d years I have spotted %g %s.' % (3, 0.1, 'camels')
'In 3 years I have spotted 0.1 camels.'

Số phần tử của bộ cần phải bằng số dãy định dạng có trong chuỗi. Ngoài ra, kiểu của các phần tử cũng phải hợp với kiểu của chuỗi định dạng:

>>> '%d %d %d' % (1, 2)
TypeError: not enough arguments for format string
>>> '%d' % 'dollars'
TypeError: illegal argument type for built-in operation

Ở ví dụ thứ nhất, không có đủ các phần tử; ở ví dụ thứ hai, phần tử có kiểu sai.

Toán tử định dạng rất đa năng, như có thể khó sử dụng. Bạn hãy tham khảo thêm thông tin ở docs.python.org/lib/typesseq-strings.html.

Tên file và đường dẫn

File được tổ chức trong các thư mục. Mỗi chương trình chạy có một “thư mục hiện thời”, đó là thư mục mặc định cho hầu hết các thao tác. Chẳng hạn, khi bạn mở một file để đọc, Python tìm nó trong thư mục hiện thời.

Module os có các hàm làm việc với file và thư mục (“os” là viết tắt của “operating system”). os.getcwd trả lại tên của thư mục hiện thời:

>>> import os

>>> cwd = os.getcwd()
>>> print cwd
/home/dinsdale

cwd là viết tắt của “current working directory”. Kết quả của ví dụ này là /home/dinsdale, chính là thư mục chủ của một người dùng có tên dinsdale.

Một chuỗi như cwd để nhận diện một file được gọi là đường dẫn. Một đường dẫn tương đối bắt đầu từ thư mục hiện thời; một đường dẫn tuyệt đối bắt đầu từ thư mục gốc của hệ thống file.

Các đường dẫn ta đã thấy đến giờ chỉ là tên file đơn giản, vì vậy chúng là đường dẫn tương đối đến thư mục hiện thời. Để tìm đường dẫn tuyệt đối đến một file, bạn có thể dùng os.path.abspath:

>>> os.path.abspath('memo.txt')
'/home/dinsdale/memo.txt'

os.path.exists kiểm tra xem một file hoặc thư mục có tồn tại không:

>>> os.path.exists('memo.txt')
True

Nếu tồn tại, os.path.isdir sẽ kiểm tra xem nó có phải là thư mục không:

>>> os.path.isdir('memo.txt')
False
>>> os.path.isdir('music')
True

Tương tự, os.path.isfile kiểm tra xem nó có phải là file không.

os.listdir trả lại một danh sách các file (và những thư mục khác) trong thư mục đã cho:

>>> os.listdir(cwd)
['music', 'photos', 'memo.txt']

Để giới thiệu các hàm này, ví dụ sau có nhiệm vụ “dò” một thư mục, in ra tên của tất cả các file, và gọi bản thân nó một cách đệ quy với tất cả các thư mục.

def walk(dir):
    for name in os.listdir(dir):
        path = os.path.join(dir, name)

        if os.path.isfile(path):
            print path
        else:
            walk(path)

os.path.join nhận vào một thư mục và một tên file rồi nối chúng lại thành một đường dẫn hoàn chỉnh.

Hãy sửa lại walk để nó thay vì in ra các tên file thì trả lại một danh sách các tên.

Module os có một hàm tên là walk cũng giống như hàm trên nhưng linh hoạt hơn. Hãy đọc tài liệu và dùng hàm đó để in ra tên của các file trong thư mục đã cho và các thư mục con.

Bắt các biệt lệ

Khi bạn đọc và ghi file, một loạt những lỗi có thể xảy ra. Nếu bạn mở một file hiện không tồn tại, bạn sẽ nhận lỗi IOError:

>>> fin = open('bad_file')
IOError: [Errno 2] No such file or directory: 'bad_file'

Nếu bạn không có quyền truy cập một file:

>>> fout = open('/etc/passwd', 'w')
IOError: [Errno 13] Permission denied: '/etc/passwd'

Và nếu bạn mở một thư mục để đọc, bạn sẽ nhận được lỗi:

>>> fin = open('/home')
IOError: [Errno 21] Is a directory

Để tránh các lỗi này, bạn có thể dùng các hàm như os.path.existsos.path.isfile, nhưng sẽ mất nhiều thời gian và câu lệnh để kiểm tra hết tất cả các trường hợp có thể (nếu coi “Errno 21” là chỉ thị báo lỗi, thì ít nhất có 21 điều khác nhau có thể gây ra lỗi).

Tốt hơn là bạn bắt tay trực tiếp thử, và giải quyết các vấn đề nảy sinh, giống như cách mà câu lệnh try thực hiện. Cú pháp lệnh này cũng giống như lệnh if:

try:    
    fin = open('bad_file')
    for line in fin:
        print line
    fin.close()
except:
    print 'Something went wrong.'

Python bắt đầu bằng việc thực hiện nhánh try. Nếu tất cả chạy tốt, nó sẽ bỏ qua nhánh except và tiếp tục. Nếu có biệt lệ xảy ra, nó sẽ nhảy khỏi nhánh try và thực hiện nhánh except.

Việc xử lý một biệt lệ bằng cách dùng lệnh try được gọi là bắt một biệt lệ. Ở ví dụ này, nhánh except in ra một thông báo lỗi không có ích lắm. Nói chung, việc bắt biệt lệ cho bạn một cơ hội sửa lỗi, hoặc thử lại, hoặc ít nhất là kết thúc chương trình một cách tốt đẹp.

Cơ sở dữ liệu

Cơ sở dữ liệu là một file được tổ chức để lưu dữ liệu. Đa số các cơ sở dữ liệu được tổ chức như các từ điển ở chỗ chúng là ánh xạ các khóa đến giá trị. Điểm khác biệt lớn nhất là cơ sở dữ liệu ở trên đĩa (hoặc một thiết bị lưu trữ vĩnh viễn khác), vì vậy nó còn tồn tại sau khi chương trình kết thúc.

Module anydbm cung cấp một giao diện phục vụ việc tạo lập và cập nhật file cơ sở dữ liệu. Tôi sẽ lấy một ví dụ cho việc tạo lập cơ sở dữ liệu chứa tiêu đề của các file hình ảnh.

Việc mở một cơ sở dữ liệu cũng giống như mở các file khác:

>>> import anydbm

>>> db = anydbm.open('captions.db', 'c')

Chế độ 'c' có nghĩa là cơ sở dữ liệu sẽ được tạo mới nếu như nó chưa tồn tại. Kết quả là một đối tượng cơ sở dữ liệu có thể được dùng giống như một từ điển (ở hầu hết các thao tác). Nếu bạn tạo một mục mới, anydbm sẽ cập nhật file cơ sở dữ liệu.

>>> db['cleese.png'] = 'Photo of John Cleese.'

Khi bạn truy cập đến một mục, anydbm sẽ đọc file:

>>> print db['cleese.png']
Photo of John Cleese.

Nếu bạn lại gán cho một khóa đã tồn tại, anydbm sẽ thay thế giá trị cũ:

>>> db['cleese.png'] = 'Photo of John Cleese doing a silly walk.'
>>> print db['cleese.png']
Photo of John Cleese doing a silly walk.

Nhiều phương thức của từ điển, như keysitems, cũng có tác dụng với các đối tượng cơ sở dữ liệu. Việc lặp với lệnh for cũng như vậy.

for key in db:
    print key

Cũng như các file khác, bạn nên đóng cơ sở dữ liệu khi xong việc:

>>> db.close()

Bảo lưu dữ liệu

Hạn chế của anydbm là ở chỗ các khóa và trị phải là các chuỗi. Nếu bạn thử dùng bất cứ kiểu nào khác, bạn sẽ nhận được lỗi.

Module pickle1 có thể khắc phục điều trên. Nó dịch hầu hết các kiểu đối tượng ra kiểu chuỗi phù hợp cho việc lưu trong cơ sở dữ liệu, và sau đó lại dịch các chuỗi trở lại thành đối tượng.

pickle.dumps nhận vào một đối tượng như một tham biến và trả về dạng chuỗi biểu diễn cho nó (dumps và viết tắt của “dump string”):

>>> import pickle

>>> t = [1, 2, 3]
>>> pickle.dumps(t)
'(lp0\nI1\naI2\naI3\na.'

Dạng này không dễ đọc; nó phù hợp để pickle diễn giải. pickle.loads (“load string”) lại khôi phục đối tượng:

>>> t1 = [1, 2, 3]
>>> s = pickle.dumps(t1)
>>> t2 = pickle.loads(s)

>>> print t2
[1, 2, 3]

Mặc dù đối tượng mới có cùng giá trị với đối tượng cũ, nói chung là chúng không đồng nhất với nhau:

>>> t1 == t2
True
>>> t1 is t2
False

Nói cách khác, việc bảo quản và phục hồi sử dụng có cùng tác dụng như sao chép đối tượng.

Bạn có thể dùng pickle để lưu các kiểu dữ liệu không phải chuỗi trong cơ sở dữ liệu. Thực ra, sự kết hợp này thông dụng đến nỗi nó được gói trong một module tên là shelve.

Nếu bạn đã làm Bài tập anagrams, hãy sửa lại lời giải sao cho nó tạo ra một cơ sở dữ liệu ánh xạ từ mỗi mục từ trong danh sách đến một danh sách các từ cũng được lập bởi các chữ cái của từ gốc.

Hãy viết một chương trình khác để mở cơ sở dữ liệu và in nội dung ra dưới dạng mà chúng ta có thể đọc được.

Ống dẫn dữ liệu

Hầu hết các hệ điều hành đều cung cấp một giao diện dòng lệnh, cũng được gọi là lớp vỏ. Lớp vỏ hệ điều hành thường có các câu lệnh để di chuyển trong hệ thống file và khởi động các ứng dụng. Chẳng hạn, trong Unix, bạn có thể thay đổi thưu mục bằng cd, hiển thị nội dung thư mục bằng ls, và khởi động một trình duyệt web bằng cách gõ vào một lệnh như firefox.

Bất kì chương trình nào bạn khởi động từ lớp vỏ cũng có thể được khởi động từ Python bằng cách dùng một ống dẫn. Ống dẫn là đối tượng để biểu thị một quá trình đang chạy.

Chẳng hạn, lệnh Unix ls -l thường để hiển thị nội dung của thư mục hiện thời (theo dạng đầy đủ). Bạn có thể khởi động ls bằng os.popen:

>>> cmd = 'ls -l'

>>> fp = os.popen(cmd)

Tham biến là một chuỗi trong đó có chứa một lệnh của lớp vỏ. Giá trị được trả lại là một đối tượng có biểu hiện giống như một file đang mở. Bạn có thể đọc kết quả đầu ra từ quá trình ls theo từng dòng một bằng readline hoặc đọc tất cả một lượt bằng read:

>>> res = fp.read()

Khi đã xong việc, bạn đóng ống dẫn như làm với file:

>>> stat = fp.close()
>>> print stat
None

Giá trị được trả về là trạng thái cuối cùng của quá trình ls; None có nghĩa là nó kết thúc bình thường (không có lỗi).

Một cách sử dụng ống dẫn là đọc vào dần dần một file được nén; nghĩa là không giải nén toàn bộ file cùng một lúc. Hàm sau đây nhận vào tên của file nén như một tham biến và trả về một ống dẫn có dùng gunzip để giải nén nội dung:

def open_gunzip(filename):
    cmd = 'gunzip -c ' + filename
    fp = os.popen(cmd)
    return fp

Nếu đọc từng dòng một từ fp, bạn không bao giờ phải lưu file được giải nén trên bộ nhớ hoặc đĩa.

Viết các module

Mọi file chứa mã lệnh Python đều có thể được nhập vào như một module. Chẳng hạn, nếu bạn có một file tên là wc.py với mã lệnh sau:

def linecount(filename):
    count = 0
    for line in open(filename):
        count += 1
    return count

print linecount('wc.py')

Nếu bạn chạy chương trình này, nó sẽ đọc chính nó và in ra số dòng trong file, tức là 7. Bạn cũng có thể nhập nó vào như sau:

>>> import wc
7

Bây giờ bạn có một đối tượng module tên là wc:

>>> print wc
<module 'wc' from 'wc.py'>

Module này có một hàm tên là linecount:

>>> wc.linecount('wc.py')
7

Như vậy đó là cách bạn viết các module trong Python:

Vấn đề duy nhất ở ví dụ này là khi bạn nhập vào module nó chạy đoạn mã lệnh ở dưới cùng. Thông thường khi bạn nhập vào một module, nó định nghĩa các hàm mới như không thực hiện chúng.

Những chương trình được nhập như là module thường có cách viết sau đây:

if __name__ == '__main__':
    print linecount('wc.py')

__name__ là một biến có sẵn, nó được đặt mỗi khi chương trình bắt đầu. Nếu chương trình chạy dưới dạng một văn lệnh, __name__ sẽ có giá trị __main__; trong trường hợp đó, đoạn mã lệnh kiểm tra sẽ được thực hiện. Ngược lại, nếu module được nhập vào, mã lệnh kiểm tra sẽ được bỏ qua.

Hãy gõ ví dụ sau đây vào trong một file tên là wc.py và chạy nó như một văn lệnh. Sau đó khởi động trình thông dịch Python và gõ vào import wc. Giá trị của __name__ sẽ là gì khi module được nhập vào?

Lưu ý: Nếu bạn nhập vào một module mà đã được nhập từ trước, Python sẽ không làm gì cả. Nó không đọc lại file nữa, ngay cả khi file này bị thay đổi.

Nếu bạn muốn tải lại một module, bạn có thể dùng hàm có sẵn reload, nhưng khi dùng phải cẩn thận, và an toàn nhất là khởi động lại trình thông dịch và nhập lại module một lần nữa.

Gỡ lỗi

Trong quá trình đọc và ghi file, bạn có thể gặp vấn đề liên quan đến dấu trắng. Những lỗi kiểu đó có thể khó gỡ vì các dấu cách, dấu tab và dấu xuống dòng thông thường đều không hiện rõ trên màn hình:

>>> s = '1 2\t 3\n 4'

>>> print s
1 2  3
 4

Hàm có sẵn repr có thể giúp ích. Nó nhận vào bất kì đối tượng nào như một đối số và trả về một chuỗi biểu diễn đối tượng đó. Với các chuỗi, nó biểu diễn dấu trắng bởi các dãy có dấu gạch chéo ngược:

>>> print repr(s)
'1 2\t 3\n 4'

Điều này có thể giúp ích cho việc gỡ lỗi.

Một vấn đề khác mà bạn có thể gặp phải là các hệ thống khác nhau dùng những kí tự khác nhau để biểu thị chỗ kết thúc dòng. Một số hệ thống dùng kí tự dòng mới, biểu thị bởi \n. Một số khác dùng kí tự trở về, kí hiệu \r. Một số khác lại dùng cả hai. Nếu bạn chuyển file giữa các hệ thống khác nhau, sự không thống nhất này có thể làm nảy sinh vấn đề.

Với phần lớn các hệ thống, có những trình ứng dụng để chuyển giữa các dạng. Bạn có thể tìm chúng (hoặc đọc thêm về điều này) ở wikipedia.org/wiki/Newline. Hoặc tất nhiên là bạn có thể tự viết một chương trình.

Thuật ngữ

duy trì:
Tính chất của một chương trình chạy lâu dài và giữ lại ít nhất là một phần dữ liệu của nó trong thiết bị lưu trữ vĩnh viễn.

toán tử định dạng:
Toán tử %, nhận vào một chuỗi định dạng và một bộ rồi phát sinh ra một chuỗi trong đó bao gồm các phần tử của bộ được định dạng như chỉ định trong chuỗi định dạng.

chuỗi định dạng:
Chuỗi được dùng với toán tử định dạng, gồm các dãy định dạng.

dãy định dạng:
Dãy kí tự trong một chuỗi định dạng, như %d, nhằm chỉ rõ cách định dạng cho một giá trị.

file chữ:
Dãy các kí tự được lưu trong một thiết bị lưu trữ vĩnh viễn, như một ổ cứng.

thư mục:
Tập hợp được đặt tên chứa các file.

chuỗi:
Chuỗi để chỉ định một file.

đường dẫn gián tiếp:
Đường dẫn bắt nguồn từ thư mục hiện thời.

đường dẫn trực tiếp:
Đường dẫn bắt nguồn từ thư mục gốc trong hệ thống file.

bắt:
Thao tác ngăn ngừa việc biệt lệ xảy ra làm kết thúc chương trình, được thực hiện bằng các lệnh tryexcept.

cơ sở dữ liệu:
Một file mà nội dung được tổ chức như một từ điển với các khóa tương ứng với các giá trị.

Bài tập

Module urllib có các phương thức thao tác với các URL và tải về thông tin từ mạng. Ví dụ sau tải về và in ra một thông điệp bí mật từ thinkpython.com:

import urllib

conn = urllib.urlopen('http://thinkpython.com/secret.html')
for line in conn.fp:
    print line.strip()

Hãy chạy đoạn mã lệnh này và thực hiện các chỉ dẫn được hiện ra.

Trong một tập hợp gồm rất nhiều file MP3, có thể có nhiều bản sao của cùng một bài hát lưu trong nhiều thư mục hoặc lưu dưới nhiều tên khác nhau. Mục đích của bài tập này là tìm những bản trùng tên đó.

  1. Hãy viết một chương trình để tìm một thư mục và tất cả các thư mục con của nó theo cách đệ quy, và trả về một danh sách các đường dẫn đầy đủ tới tất cả các file có phần đuôi cho trước (như .mp3). Gợi ý: os.path có một số hàm giúp ích cho việc xử lý file và tên đường dẫn.
  2. Để nhận biết được sự trùng lặp, bạn có thể dùng một hàm băm đọc vào file và phát sinh một đoạn tóm tắt cho nội dung file. Chẳng hạn, MD5 (Message-Digest algorithm 5) nhận vào một “thông điệp” dài tùy ý và trả lại một “checksum” 128 bit. Xác suất để hai nội dung khác nhau có thể trả về cùng checksum là rất nhỏ.

    Bạn có thể tìm hiểu về MD5 tại www.wikipedia.org/wiki/Md5. Trong hệ thống Unix, bạn có thể dùng chương trình md5sum và một ống dẫn để tính checksum từ Python.

Cơ sở dữ liệu Phim ảnh trên Internet, Internet Movie Database (IMDb) là một tuyển tập trực tuyến lưu giữ các thông tin về những cuốn phim. Cơ sở dữ liệu này cũng sẵn có dưới hình thức văn bản chữ, vì vậy sẽ khá dễ đọc từ Python. Trong bài tập này, các file bạn cần là actors.list.gzactresses.list.gz; bạn có thể tải chúng về từ www.imdb.com/interfaces#plain.

Tôi đã viết một chương trình để tách các file này và phân nội dung riêng thành tên diễn viên, tựa đề phim, v.v. Bạn có thể tải chúng về từ thinkpython.com/code/imdb.py.

Nếu bạn chạy imdb.py như một văn lệnh, nó sẽ đọc actors.list.gz và in ra mỗi dòng một cặp diễn viên-bộ phim. Hoặc, nếu import imdb bạn có thể dùng hàm process_file để xử lý file. Các đối số gồm có tên file, một đối tượng hàm và tùy chọn là số các dòng cần xử lý. Sau đây là một ví dụ:

import imdb

def print_info(actor, date, title, role):
    print actor, date, title, role

imdb.process_file('actors.list.gz', print_info)

Khi bạn gọi process_file, nó sẽ mở filename, đọc nội dung, và gọi print_info một lần cho mỗi dòng trong file. print_info nhận các đối số gồm có một tên diễn viên, ngày tháng, tựa đề phim và vai diễn rồi in chúng ra.

  1. Hãy viết một chương trình để đọc vào actors.list.gzactresses.list.gz rồi dùng shelve để lập một cơ sở dữ liệu trong đó ánh xạ từ tên từng diễn viên đến một danh sách các bộ phim của diễn viên đó.
  2. Hai diễn viên là “đồng minh tinh” nếu họ diễn cùng trong ít nhất là một bộ phim. Hãy xử lý cơ sở dữ liệu vừa lập ra ở bước trên, và lập một cơ sở dữ liệu thứ hai trong đó ánh xạ tên từng diễn viên đến một danh sách các đồng minh tinh của diễn viên đó.
  3. Hãy viết một chương trình để chơi trò “Six Degrees of Kevin Bacon”, mà bạn có thể tìm hiểu tại wikipedia.org/wiki/Six_Degrees_of_Kevin_Bacon. Vấn đề này rất khó vì nó đòi hỏi phải tìm đường đi ngắn nhất trong một đồ thị. Bạn có thể tìm hiểu về các thuật toán đường đi ngắn nhất tại wikipedia.org/wiki/Shortest_path_problem.

  1. Pickle = ngâm muối, ướp muối để bảo quản (nghĩa gốc tiếng Anh)

2 bình luận

Filed under Think Python

2 responses to “Chương 14: File

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

  2. Pingback: Think Python: Cách tư duy như nhà khoa học máy tính » Book Python

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