Mục con


7. Vào và ra

Có nhiều các để thể hiện đầu ra của một chương trình; dữ liệu có thể được in ra ở dạng người đọc được, hoặc viết vào một tập tin để dùng sau này. Chương này sẽ bàn về một vài khả năng đó.


7.1 Định dạng ra đẹp hơn

Chúng ta đã gặp hai cách để viết giá trị: câu lệnh biểu thức câu lệnh print . (Cách thứ ba là dùng phương thức write() của đối tượng tập tin; tập tin đầu ra chuẩn có thể được tham chiếu tới theo sys.stdout. Xem Tham khảo thư viện Python để biết thêm chi tiết.)

Thông thường bạn sẽ muốn điều khiển cách định dạng đầu ra của bạn nhiều hơn là chỉ đơn giản là in các giá trị phân cách bởi khoảng trắng. Có hai cách để định dạng đầu ra của bạn; cách thứ nhất là bạn tự xử lý các chuỗi; dùng phép cắt miếng của chuỗi và phép ghép chuỗi bạn có thể tạo bất kỳ bố cục nào bạn có thể nghĩ ra. Mô-đun chuẩn string chứa một vài công cụ để đệm chuỗi khít vào chiều ngang cột; chúng ta sẽ xem xét nó lát nữa. Cách thứ hai là dùng toán tử % với một chuỗi là thông số bên trái. Toán tử % thông dịch thông số trái như là một chuỗi định dạng kiểu sprintf() (như trong C) để áp dụng vào thông số bên phải; và trả về một chuỗi từ tác vụ định dạng này.

Một câu hỏi vẫn còn đó: làm sao để chuyển giá trị thành chuỗi? May mắn thay Python có một cách để chuyển bất kỳ giá trị nào thành chuỗi: truyền nó vào hàm repr() hay hàm str() . Dấu nháy ngược (``) tương đương với repr(), nhưng chúng không còn được dùng trong mã Python mới, và rất có thể sẽ không còn trong các phiên bản ngôn ngữ sau này.

str() nhằm để trả về cách thể hiện giá trị một cách dễ đọc, trong khi repr() nhằm để tạo cách thể hiện mà trình thông dịch có thể đọc (hoặc sẽ sinh lỗi SyntaxError nếu không có cú pháp tương đương). Đối với các đối tượng không có cách thể hiện cho người đọc, str() sẽ trả về cùng giá trị như repr(). Nhiều giá trị, ví dụ như số, hoặc cấu trúc như danh sách và từ điển, có cùng cách thể hiện với cả hai hàm này. Riêng chuỗi và số chấm động có hai cách thể hiện khác biệt.

Một vài ví dụ:

>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"
>>> str(0.1)
'0.1'
>>> repr(0.1)
'0.10000000000000001'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'
>>> print s
The value of x is 32.5, and y is 40000...
>>> # The repr() of a string adds string quotes and backslashes:
... hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print hellos
'hello, world\n'
>>> # The argument to repr() may be any Python object:
... repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"
>>> # reverse quotes are convenient in interactive sessions:
... `x, y, ('spam', 'eggs')`
"(32.5, 40000, ('spam', 'eggs'))"

Đây là hai cách để viết một bảng bình phương và lập phương:

>>> for x in range(1, 11):
...     print repr(x).rjust(2), repr(x*x).rjust(3),
...     # Note trailing comma on previous line
...     print repr(x*x*x).rjust(4)
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000
>>> for x in range(1,11):
...     print '%2d %3d %4d' % (x, x*x, x*x*x)
... 
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

(Chú ý một khoảng trắng được thêm vào giữa các cột theo cách hoạt động của print : nó luôn luôn thêm khoảng trắng giữa các thông số.)

Ví dụ này biểu diễn phương thức rjust() của các đối tượng chuỗi, nó canh phải một chuỗi vào trong trường với độ rộng xác định bằng cách thêm khoảng trống vào bên trái. Các phương thức tương tự khác gồm ljust()center(). Các phương thức này không viết gì cả, chúng chỉ trả về một chuỗi mới. Nếu chuỗi nhập vào quá dài, chúng cũng không cắt nó đi, mà trả về nó nguyên vẹn; điều này thường sẽ phá hỏng bố cục của bạn, nhưng vẫn tốt hơn là nói dối về một giá trị. (Nếu bạn thật sự muốn cắt bỏ bạn có thể thêm phép cắt miếng, như "x.ljust(n)[:n]".)

Có một phương thức khác, zfill(), nó đệm không vào bên trái một chuỗi số. Nó hiểu các dấu cộng và trừ:

>>> '12'.zfill(5)
'00012'
>>> '-3.14'.zfill(7)
'-003.14'
>>> '3.14159265359'.zfill(5)
'3.14159265359'

Dùng toán tử % sẽ giống như vậy:

>>> import math
>>> print 'The value of PI is approximately %5.3f.' % math.pi
The value of PI is approximately 3.142.

Nếu có nhiều hơn một định dạng trong chuỗi, bạn cần truyền một bộ ở toán hạng bên phải, như trong ví dụ này:

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
...     print '%-10s ==> %10d' % (name, phone)
... 
Jack       ==>       4098
Dcab       ==>       7678
Sjoerd     ==>       4127

Hầu hết các định dạng hoạt động như trong C và yêu cầu bạn truyền kiểu thích hợp; nếu không, bạn sẽ nhận biệt lệ thay vì đổ nhân (core dump). Định dạng %s dễ dãi hơn: nếu thông số tương ứng không phải là một đối tượng chuỗi, nó sẽ được chuyển thành chuỗi qua hàm có sẵn str() . Việc dùng * để truyền vào độ rộng và mức chính xác như là một thông số nguyên riêng cũng được hỗ trợ. Các định dạng C %n%p không được hỗ trợ.

Nếu bạn có một chuỗi định dạng rất dài và không muốn cắt ra, có thể bạn sẽ muốn tham chiếu tới các biến sắp được định dạng qua tên, thay vì vị trí. Việc này có thể được thực hiện theo dạng %(name)format, như ví dụ sau:

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print 'Jack: %(Jack)d; Sjoerd: %(Sjoerd)d; Dcab: %(Dcab)d' % table
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

Cách này đặc biệt hữu dụng khi đi kèm với hàm có sẵn vars() mới, nó trả về một từ điển chứa tất cả các biến cục bộ.


7.2 Đọc và viết tập tin

open() trả về một đối tượng tập tin, và thường được dùng với hai thông số: "open(filename, mode)".

>>> f=open('/tmp/workfile', 'w')
>>> print f
<open file '/tmp/workfile', mode 'w' at 80a0960>

Thông số thứ nhất là một chuỗi chứa tên tập tin. Thông số thứ hai là một chuỗi khác chứa một vài ký tự xác định cách thức tập tin sẽ được dùng. mode có thể là 'r' khi tập sẽ chỉ được đọc, 'w' chỉ được ghi (tập tin cùng tên đang có sẽ bị xóa), và 'a' mở tập tin để thêm vào cuối; mọi dữ liệu ghi vào tập tin sẽ được tự động thêm vào cuối. 'r+' mở tập tin để đọc và ghi. Thông số mode là không bắt buộc; 'r' sẽ được giả định nếu nó bị bỏ qua.

Trong Windows và Macintosh, 'b' thêm vào mode mở tập tin ở chế độ nhị phân, cho nên cũng có các chế độ khác như 'rb', 'wb', và 'r+b'. Windows phân biệt rõ các tập tin văn bản và nhị phân; ký tự hết dòng (end-of-line) trong các tập tin văn bản được tự động thay đổi một chút khi dữ liệu được đọc hay ghi. Việc thay đổi sau bức bình phong (behind-the-scene) như vậy không ảnh hưởng các tập tin văn bản ASCII, nhưng nó sẽ phá dữ liệu nhị phân như trong các tập tin JPEG hay hàm EXE . Cần cẩn thận dùng chế độ nhị phân khi đọc và ghi các tập tin như vậy.


7.2.1 Phương thức của đối tượng tập tin

Các ví dụ trong mục này sẽ giả sử một đối tượng tập tin f đã được tạo.

Để đọc nội dung tập tin, gọi f.read(size), nó đọc một số lượng dữ liệu và trả về một chuỗi. size là một thông số số nguyên không bắt buộc. Khi size bị bỏ qua hoặc âm, toàn bộ nội dung tập tin sẽ được đọc và trả về; bạn sẽ gặp vấn đề nếu tập tin lớn gấp đôi bộ nhớ của máy bạn. Ngược lại, nhiều nhất size byte sẽ được đọc và trả về. Nếu đã đến cuối tập tin, f.read() sẽ trả về một chuỗi rỗng ("").

>>> f.read()
'This is the entire file.\n'
>>> f.read()
''

f.readline() đọc một dòng từ tập tin; ký tự dòng mới (\n) được giữ lại ở cuối chuỗi, và sẽ chỉ bị bỏ qua ở dòng cuối của tập tin nếu tập tin không kết thúc bằng một dòng mới. Điều này làm giá trị trả về rõ ràng; nếu f.readline() trả về một chuỗi rỗng có nghĩa là đã đụng cuối tập tin, trong khi một dòng trống thì được biểu diễn bởi '\n', một chuỗi chỉ chứa duy nhât một ký tự dòng mới.

>>> f.readline()
'This is the first line of the file.\n'
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''

f.readlines() trả về một danh sách tất cả các dòng trong tập tin. Nếu truyền một tham số không bắt buộc sizehint, nó sẽ đọc nhiêu đó byte từ tập tin và thêm một chút đủ để hoàn tất một dòng, và trả về các dòng đã đọc được. Điều này thường được dùng để đọc một cách hiệu quả từng dòng một trong một tập tin lớn mà không cần phải nạp toàn bộ tập tin vào bộ nhớ. Chỉ có các dòng toàn vẹn mới được trả về.

>>> f.readlines()
['This is the first line of the file.\n', 'Second line of the file\n']

Một cách khác để đọc các dòng là lặp qua đối tượng tập tin. Nó rất tiết kiệm bộ nhớ, nhanh, và có mã đơn giản:

>>> for line in f:
        print line,
        
This is the first line of the file.
Second line of the file

Cách này đơn giản hơn nhưng không cho bạn điều khiển cách đọc. Vì hai cách này quản lý bộ đệm dòng khác nhau, chúng không nên được dùng chung.

f.write(string) viết nội dung của string vào tập tin, trả về None.

>>> f.write('This is a test\n')

Để viết một thứ khác không phải là chuỗi, nó sẽ cần được chuyển thành một chuỗi trước:

>>> value = ('the answer', 42)
>>> s = str(value)
>>> f.write(s)

f.tell() trả về một số nguyên cho biết vị trí hiện tại của đối tượng tập tin, tính theo byte từ đầu tập tin. Để di chuyển vị trí, dùng "f.seek(offset, from_what)". Vị trí được tính từ tổng của offset và điểm tham chiếu; điểu tham chiếu được xác định bởi thông số from_what . Giá trị from_what 0 tính từ đầu tập tin, 1 dùng vị trí hiện tại, và 2 tính từ vị trí cuối tập tin. from_what có thể bị bỏ qua và mặc định là 0, điểm tham chiếu là đầu tập tin.

>>> f = open('/tmp/workfile', 'r+')
>>> f.write('0123456789abcdef')
>>> f.seek(5)     # Go to the 6th byte in the file
>>> f.read(1)        
'5'
>>> f.seek(-3, 2) # Go to the 3rd byte before the end
>>> f.read(1)
'd'

Khi bạn đã dùng xong, gọi f.close() để đóng nó lại và giải phóng tài nguyên hệ thống đã sử dụng khi mở tập tin. Sau khi gọi f.close(), mọi cách dùng đối tượng tập tin sẽ tự động thất bại.

>>> f.close()
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: I/O operation on closed file

Các đối tượng tập tin có thêm các phương thức phụ như isatty()truncate() không được thường xuyên dùng; tham khảo tài liệu thư viện để biết thêm về các đối tượng tập tin.


7.2.2 pickle mô-đun

Các chuỗi có thể được ghi hoặc đọc dễ dàng từ một tập tin. Các số cần một ít cố gắng hơn, vì phương thức read() chỉ trả về chuỗi, và cần được truyền vào một hàm như int(), nó sẽ nhận một chuỗi như '123' và trả về giá trị số 123 của nó. Tuy nhiên, khi bạn muốn lưu các kiểu dữ liệu phức tạp hơn như danh sách, từ điển, hoặc các đối tượng, việc này trở nên rắc rối hơn nhiều.

Thay vì để người dùng luôn viết và gỡ rối mã để lưu các kiểu dữ liệu phức tạp, Python cung cấp một mô-đun chuẩn gọi là pickle. Đây là một mô-đun tuyệt diệu có thể nhận hầu hết mọi đối tượng Python (ngay cả một vài dạng mã Python!), và chuyển nó thành một chuỗi; quá trình này được gọi là giầm (pickling). Tạo lại đối tượng từ một chuỗi được gọi là vớt (unpickling). Giữa việc giầm và vớt, biểu diễn dạng chuỗi của đối tượng có thể được lưu vào tập tin, hoặc gửi qua mạng đến một máy ở xa.

Nếu bạn có một đối tượng x, và một đối tượng tập tin f đã được mở để ghi vào, cách đơn giản nhất để giầm đối tượng chỉ cần một dòng mã:

pickle.dump(x, f)

Để vớt đối tượng ra, nếu f là một đối tượng tập tin đã được mở để đọc:

x = pickle.load(f)

(Có những biến thể khác, dùng khi giầm nhiều đối tượng hoặc khi bạn không muốn viết dữ liệu đã giầm vào tập tin; tham khảo toàn bộ tài liệu về pickle trong Tham khảo thư viện Python.)

pickle là cách chuẩn để làm cho các đối tượng Python có thể được lưu và dùng lại bởi các chương trình khác, hoặc bởi lần chạy khác của cùng chương trình; thuật ngữ trong ngành gọi là đối tượng bền . Vì pickle được sử dụng rộng rãi, nhiều tác giả khi mở rộng Python đã cẩn thận để đảm bảo rằng các kiểu dữ liệu mới ví dụ như ma trận có thể được giầm và vớt đúng đắn.

Xem Về tài liệu này... về cách đề nghị thay đổi.