7. Đầu vào và đầu ra

Có một số cách để trình bày kết quả đầu ra của một chương trình; dữ liệu có thể được in ở dạng mà con người có thể đọc được hoặc được ghi vào tệp để sử dụng trong tương lai. Chương này sẽ thảo luận về một số khả năng.

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

Cho đến nay chúng ta đã gặp hai cách viết giá trị: expression statements và hàm print(). (Cách thứ ba là sử dụng phương thức write() của các đối tượng tệp; tệp đầu ra tiêu chuẩn có thể được tham chiếu là sys.stdout. Xem Tài liệu tham khảo Thư viện để biết thêm thông tin về điều này.)

Thông thường, bạn sẽ muốn kiểm soát nhiều hơn định dạng đầu ra của mình hơn là chỉ in các giá trị được phân tách bằng dấu cách. Có một số cách để định dạng đầu ra.

  • Để sử dụng formatted string literals, hãy bắt đầu một chuỗi bằng f hoặc F trước dấu ngoặc kép mở hoặc dấu ngoặc kép ba. Bên trong chuỗi này, bạn có thể viết biểu thức Python giữa các ký tự {} có thể tham chiếu đến các biến hoặc giá trị bằng chữ.

    >>> năm = 2016
    >>> sự kiện = 'Trưng cầu dân ý'
    >>> f'Kết quả của {năm} {sự kiện}'
    'Kết quả của cuộc trưng cầu dân ý năm 2016'
    
  • Phương pháp xâu chuỗi str.format() đòi hỏi nhiều nỗ lực thủ công hơn. Bạn vẫn sẽ sử dụng {} để đánh dấu nơi biến sẽ được thay thế và có thể cung cấp chỉ thị định dạng chi tiết, nhưng bạn cũng cần cung cấp thông tin cần được định dạng. Trong khối mã sau đây có hai ví dụ về cách định dạng biến:

    >>> Yes_vote = 42_572_654
    >>> tổng số phiếu bầu = 85_705_149
    >>> phần trăm = Yes_votes / Total_votes
    >>> '{:-9} YES phiếu bầu {:2.2%}'.format(yes_votes, phần trăm)
    ' 42572654 YES phiếu bầu 49,67%'
    

    Lưu ý cách yes_votes được đệm bằng dấu cách và dấu âm chỉ dành cho số âm. Ví dụ này cũng in percentage nhân với 100, với 2 chữ số thập phân và theo sau là dấu phần trăm (xem Đặc tả định dạng ngôn ngữ nhỏ để biết chi tiết).

  • Cuối cùng, bạn có thể tự mình thực hiện tất cả việc xử lý chuỗi bằng cách sử dụng các thao tác cắt chuỗi và nối chuỗi để tạo bất kỳ bố cục nào bạn có thể tưởng tượng. Kiểu chuỗi có một số phương thức thực hiện các thao tác hữu ích để đệm chuỗi theo chiều rộng cột nhất định.

Khi bạn không cần đầu ra ưa thích mà chỉ muốn hiển thị nhanh một số biến cho mục đích gỡ lỗi, bạn có thể chuyển đổi bất kỳ giá trị nào thành chuỗi bằng các hàm repr() hoặc str().

Hàm str() có nghĩa là trả về các biểu diễn của các giá trị mà con người khá dễ đọc, trong khi repr() có nghĩa là tạo ra các biểu diễn mà trình thông dịch có thể đọc được (hoặc sẽ buộc SyntaxError nếu không có cú pháp tương đương). Đối với các đối tượng không có đại diện cụ thể cho con người, str() sẽ trả về cùng giá trị như repr(). Nhiều giá trị, chẳng hạn như số hoặc cấu trúc như danh sách và từ điển, có cùng cách biểu diễn bằng cách sử dụng một trong hai hàm. Đặc biệt, các chuỗi có hai cách biểu diễn riêng biệt.

Một số ví dụ:

>>> s = 'Xin chào thế giới.'
>>>(các) chuỗi
'Xin chào thế giới.'
>>> (các) đại diện
"'Xin chào thế giới.'"
>>> str(1/7)
'0,14285714285714285'
>>> x = 10 * 3,25
>>> y = 200 * 200
>>> s = 'Giá trị của x là ' + Repr(x) + ', và y là ' + Repr(y) + '...'
>>> bản in
Giá trị của x là 32,5 và y là 40000...
>>> # The repr() của một chuỗi thêm dấu ngoặc kép và dấu gạch chéo ngược:
>>> xin chào = 'xin chào thế giới\n'
>>> xin chào = repr(xin chào)
>>> in(xin chào)
'xin chào thế giới\n'
>>> đối số # The cho Repr() có thể là bất kỳ đối tượng Python nào:
>>> repr((x, y, ('spam', 'trứng')))
"(32,5, 40000, ('thư rác', 'trứng'))"

Mô-đun string hỗ trợ cách tiếp cận tạo khuôn mẫu đơn giản dựa trên các biểu thức thông thường, thông qua string.Template. Điều này cung cấp một cách khác để thay thế các giá trị thành chuỗi, sử dụng các phần giữ chỗ như $x và thay thế chúng bằng các giá trị từ từ điển. Cú pháp này rất dễ sử dụng, mặc dù nó cung cấp ít khả năng kiểm soát định dạng hơn.

7.1.1. Chuỗi ký tự được định dạng

Formatted string literals (còn gọi tắt là chuỗi f) cho phép bạn bao gồm giá trị của biểu thức Python bên trong một chuỗi bằng cách thêm tiền tố vào chuỗi bằng f hoặc F và viết biểu thức dưới dạng {expression}.

Trình xác định định dạng tùy chọn có thể theo sau biểu thức. Điều này cho phép kiểm soát tốt hơn cách định dạng giá trị. Ví dụ sau làm tròn số pi đến ba vị trí sau số thập phân:

>>> nhập toán
>>> print(f'Giá trị của pi xấp xỉ {math.pi:.3f}.')
Giá trị của pi xấp xỉ 3,142.

Truyền một số nguyên sau ':' sẽ làm cho trường đó có số lượng ký tự tối thiểu. Điều này rất hữu ích cho việc sắp xếp các cột.

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> lấy tên, số điện thoại trong bảng.items():
... print(f'{name:10} ==> {phone:10d}')
...
Sjoerd ==> 4127
Jack ==> 4098
Dcab ==> 7678

Các công cụ sửa đổi khác có thể được sử dụng để chuyển đổi giá trị trước khi nó được định dạng. '!a' áp dụng ascii(), '!s' áp dụng str()'!r' áp dụng repr():

>>> động vật = 'lươn'
>>> print(f'Tàu bay của tôi chứa đầy {động vật}.')
Tàu đệm khí của tôi đầy lươn.
>>> print(f'Tàu bay của tôi chứa đầy {động vật!r}.')
Thủy phi cơ của tôi đầy 'lươn'.

Trình xác định = có thể được sử dụng để mở rộng biểu thức thành văn bản của biểu thức, dấu bằng, sau đó là biểu diễn của biểu thức được đánh giá:

>>> bugs = 'roaches'
>>> count = 13
>>> area = 'living room'
>>> print(f'Debugging {bugs=} {count=} {area=}')
Debugging bugs='roaches' count=13 area='living room'

Xem self-documenting expressions để biết thêm thông tin về công cụ xác định =. Để tham khảo về các thông số kỹ thuật định dạng này, hãy xem hướng dẫn tham khảo dành cho Đặc tả định dạng ngôn ngữ nhỏ.

7.1.2. Phương thức định dạng chuỗi()

Cách sử dụng cơ bản của phương pháp str.format() trông như thế này

>>> print('Chúng tôi là {} nói "{}!"'.format('knights', 'Ni'))
Chúng tôi là những hiệp sĩ nói "Ni!"

Các dấu ngoặc và ký tự bên trong chúng (được gọi là trường định dạng) được thay thế bằng các đối tượng được truyền vào phương thức str.format(). Một số trong ngoặc có thể được sử dụng để chỉ vị trí của đối tượng được truyền vào phương thức str.format().

>>> print('{0}{1}'.format('spam', 'Eggs'))
thư rác và trứng
>>> print('{1}{0}'.format('spam', 'Eggs'))
trứng và thư rác

Nếu đối số từ khóa được sử dụng trong phương thức str.format(), giá trị của chúng sẽ được tham chiếu bằng cách sử dụng tên của đối số.

>>> print('Thức ăn này là {tính từ}.'.format(
... đồ ăn='spam', tính từ='hoàn toàn kinh khủng'))
Thư rác này thực sự khủng khiếp.

Đối số vị trí và từ khóa có thể được kết hợp tùy ý

>>> print('Câu chuyện về {0}, {1}{other}.'.format('Bill', 'Manfred',
... other='Georg'))
Câu chuyện của Bill, Manfred và Georg.

Nếu bạn có một chuỗi định dạng thực sự dài mà bạn không muốn tách ra, sẽ rất tuyệt nếu bạn có thể tham chiếu các biến được định dạng theo tên thay vì theo vị trí. Điều này có thể được thực hiện bằng cách chỉ cần chuyển lệnh và sử dụng dấu ngoặc vuông '[]' để truy cập các phím.

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
... 'Dcab: {0[Dcab]:d}'.format(table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

Điều này cũng có thể được thực hiện bằng cách chuyển từ điển table làm đối số từ khóa với ký hiệu **.

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

Điều này đặc biệt hữu ích khi kết hợp với hàm vars() tích hợp sẵn, hàm này trả về một từ điển chứa tất cả các biến cục bộ:

>>> table = {k: str(v) for k, v in vars().items()}
>>> message = " ".join([f'{k}: ' + '{' + k +'};' cho k trong table.keys()])
>>> print(message.format(**table))
__tên__: __chính__; __doc__: Không; __gói__: Không có; __người nạp__: ...

Ví dụ: các dòng sau đây tạo ra một tập hợp các cột được sắp xếp gọn gàng cung cấp các số nguyên cũng như hình vuông và hình khối của chúng:

>>> cho x trong phạm vi (1, 11):
... print('{0:2d} {1:3d} {2:4d}'.format(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

Để biết tổng quan đầy đủ về định dạng chuỗi bằng str.format(), hãy xem Cú pháp định dạng chuỗi.

7.1.3. Định dạng chuỗi thủ công

Đây là cùng một bảng hình vuông và hình khối, được định dạng thủ công:

>>> cho x trong phạm vi (1, 11):
... print(repr(x).rjust(2), Repr(x*x).rjust(3), end=' ')
... # Note sử dụng 'kết thúc' ở dòng trước
... 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

(Lưu ý rằng một khoảng trắng giữa mỗi cột đã được thêm theo cách print() hoạt động: nó luôn thêm khoảng trắng giữa các đối số của nó.)

Phương thức str.rjust() của các đối tượng chuỗi căn chỉnh sang phải một chuỗi trong trường có chiều rộng nhất định bằng cách đệm nó bằng khoảng trắng ở bên trái. Có các phương pháp tương tự str.ljust()str.center(). Những phương thức này không ghi gì cả, chúng chỉ trả về một chuỗi mới. Nếu chuỗi đầu vào quá dài, họ sẽ không cắt ngắn nó mà trả về nguyên vẹn; điều này sẽ làm rối bố cục cột của bạn nhưng điều đó thường tốt hơn cách thay thế, vốn sẽ nói dối về một giá trị. (Nếu bạn thực sự muốn cắt bớt, bạn luôn có thể thêm thao tác cắt lát, như trong x.ljust(n)[:n].)

Có một phương pháp khác, str.zfill(), đệm một chuỗi số ở bên trái bằng các số 0. Nó hiểu về dấu cộng và dấu trừ:

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

7.1.4. Định dạng chuỗi cũ

Toán tử % (modulo) cũng có thể được sử dụng để định dạng chuỗi. Cho format % values (trong đó format là một chuỗi), các thông số chuyển đổi % trong format được thay thế bằng 0 hoặc nhiều phần tử của values. Hoạt động này thường được gọi là nội suy chuỗi. Ví dụ:

>>> nhập toán
>>> print('Giá trị của pi xấp xỉ %5.3f.' % math.pi)
Giá trị của pi xấp xỉ 3,142.

Thông tin thêm có thể được tìm thấy trong phần Định dạng chuỗi kiểu printf.

7.2. Đọc và ghi tập tin

open() trả về file object và được sử dụng phổ biến nhất với hai đối số vị trí và một đối số từ khóa: open(filename, mode, encoding=None)

>>> f = open('workfile', 'w', Encoding="utf-8")

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

Thông thường, các tệp được mở bằng text mode, có nghĩa là bạn đọc và ghi các chuỗi từ và vào tệp, được mã hóa bằng encoding cụ thể. Nếu encoding không được chỉ định thì mặc định sẽ phụ thuộc vào nền tảng (xem open()). Vì UTF-8 là tiêu chuẩn thực tế hiện đại nên encoding="utf-8" được khuyên dùng trừ khi bạn biết rằng mình cần sử dụng một mã hóa khác. Việc thêm 'b' vào chế độ sẽ mở tệp trong binary mode. Dữ liệu chế độ nhị phân được đọc và ghi dưới dạng đối tượng bytes. Bạn không thể chỉ định encoding khi mở tệp ở chế độ nhị phân.

Ở chế độ văn bản, mặc định khi đọc là chuyển đổi các kết thúc dòng dành riêng cho nền tảng (\n trên Unix, \r\n trên Windows) thành \n. Khi viết ở chế độ văn bản, mặc định là chuyển đổi lần xuất hiện của \n trở lại phần cuối dòng dành riêng cho nền tảng. Việc sửa đổi hậu trường đối với dữ liệu tệp này phù hợp với tệp văn bản nhưng sẽ làm hỏng dữ liệu nhị phân như thế trong tệp JPEG hoặc EXE. Hãy hết sức cẩn thận khi sử dụng chế độ nhị phân khi đọc và ghi các tệp như vậy.

Cách tốt nhất là sử dụng từ khóa with khi xử lý các đối tượng tệp. Ưu điểm là tệp được đóng đúng cách sau khi bộ phần mềm của nó kết thúc, ngay cả khi có một ngoại lệ nào đó được đưa ra. Sử dụng with cũng ngắn hơn nhiều so với việc viết các khối try-finally tương đương:

>>> với open('workfile',coding="utf-8")  f:
... read_data = f.read()

>>> # We có thể kiểm tra xem tập tin đã được tự động đóng chưa.
>>> f.đã đóng
đúng

Nếu bạn không sử dụng từ khóa with thì bạn nên gọi f.close() để đóng tệp và giải phóng ngay lập tức mọi tài nguyên hệ thống được nó sử dụng.

Cảnh báo

Gọi f.write() mà không sử dụng từ khóa with hoặc gọi f.close() might dẫn đến các đối số của f.write() không được ghi hoàn toàn vào đĩa, ngay cả khi chương trình thoát thành công.

Sau khi một đối tượng tệp bị đóng, bằng câu lệnh with hoặc bằng cách gọi f.close(), các nỗ lực sử dụng đối tượng tệp sẽ tự động thất bại.

>>> f.close()
>>> f.read()
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 1, trong <module>
ValueError: Thao tác I/O trên tệp đã đóng.

7.2.1. Các phương thức của đối tượng tệp

Các ví dụ còn lại trong phần này sẽ giả định rằng một đối tượng tệp có tên f đã được tạo.

Để đọc nội dung của tệp, hãy gọi f.read(size), đọc một số lượng dữ liệu và trả về dưới dạng chuỗi (ở chế độ văn bản) hoặc đối tượng byte (ở chế độ nhị phân). size là một đối số số tùy chọn. Khi size bị bỏ qua hoặc âm, toàn bộ nội dung của tệp sẽ được đọc và trả về; đó là vấn đề của bạn nếu tệp lớn gấp đôi bộ nhớ máy của bạn. Mặt khác, tối đa các ký tự size (ở chế độ văn bản) hoặc byte size (ở chế độ nhị phân) được đọc và trả về. Nếu đã đến cuối tệp, f.read() sẽ trả về một chuỗi trống ('').

>>> f.read()
'Đây là toàn bộ tập tin.\n'
>>> f.read()
''

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

>>> f.readline()
'Đây là dòng đầu tiên của tập tin.\n'
>>> f.readline()
'Dòng thứ hai của tập tin\n'
>>> f.readline()
''

Để đọc các dòng từ một tệp, bạn có thể lặp qua đối tượng tệp. Đây là bộ nhớ hiệu quả, nhanh chóng và dẫn đến mã đơn giản

>>> cho dòng trong f:
... in(dòng, cuối='')
...
Đây là dòng đầu tiên của tập tin.
Dòng thứ hai của tập tin

Nếu bạn muốn đọc tất cả các dòng của tệp trong danh sách, bạn cũng có thể sử dụng list(f) hoặc f.readlines().

f.write(string) ghi nội dung của string vào file, trả về số lượng ký tự được ghi.

>>> f.write('Đây là bài kiểm tra\n')
15

Các loại đối tượng khác cần được chuyển đổi -- thành chuỗi (ở chế độ văn bản) hoặc đối tượng byte (ở chế độ nhị phân) -- trước khi ghi chúng:

>>> giá trị = ('câu trả lời', 42)
>>> s = str(value) # convert bộ dữ liệu thành chuỗi
>>> f.write(s)
18

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 trong tệp được biểu thị dưới dạng số byte tính từ đầu tệp khi ở chế độ nhị phân và một số mờ khi ở chế độ văn bản.

Để thay đổi vị trí của đối tượng tệp, hãy sử dụng f.seek(offset, whence). Vị trí được tính bằng cách thêm offset vào điểm tham chiếu; điểm tham chiếu được chọn bởi đối số whence. Giá trị whence bằng 0 đo từ đầu tệp, 1 sử dụng vị trí tệp hiện tại và 2 sử dụng phần cuối của tệp làm điểm tham chiếu. whence có thể được bỏ qua và mặc định là 0, sử dụng phần đầu của tệp làm điểm tham chiếu.

>>> f = open('workfile', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5) # Go tới byte thứ 6 trong tệp
5
>>> f.read(1)
b'5'
>>> f.seek(-3, 2) # Go tới byte thứ 3 trước khi kết thúc
13
>>> f.read(1)
b'd'

Trong các tệp văn bản (những tệp được mở mà không có b trong chuỗi chế độ), chỉ cho phép tìm kiếm liên quan đến phần đầu của tệp (ngoại lệ là tìm kiếm đến chính phần cuối tệp có seek(0, 2)) và các giá trị offset hợp lệ duy nhất là những giá trị được trả về từ f.tell() hoặc bằng 0. Bất kỳ giá trị offset nào khác đều tạo ra hành vi không xác định.

Các đối tượng tệp có một số phương thức bổ sung, chẳng hạn như isatty()truncate() ít được sử dụng hơn; tham khảo Tài liệu tham khảo Thư viện để có hướng dẫn đầy đủ về các đối tượng tệp.

7.2.2. Lưu dữ liệu có cấu trúc với json

Chuỗi có thể dễ dàng được ghi và đọc từ một tập tin. Các con số cần nỗ lực nhiều hơn một chút vì phương thức read() chỉ trả về các chuỗi, chuỗi này sẽ phải được chuyển đến một hàm như int(), hàm này nhận một chuỗi như '123' và trả về giá trị số 123. Khi bạn muốn lưu các kiểu dữ liệu phức tạp hơn như danh sách và từ điển lồng nhau, việc phân tích cú pháp và tuần tự hóa bằng tay sẽ trở nên phức tạp.

Thay vì yêu cầu người dùng liên tục viết và gỡ lỗi mã để lưu các kiểu dữ liệu phức tạp vào tệp, Python cho phép bạn sử dụng định dạng trao đổi dữ liệu phổ biến có tên là JSON (JavaScript Object Notation). Mô-đun tiêu chuẩn có tên json có thể lấy hệ thống phân cấp dữ liệu Python và chuyển đổi chúng thành dạng biểu diễn chuỗi; quá trình này được gọi là serializing. Việc xây dựng lại dữ liệu từ biểu diễn chuỗi được gọi là deserializing. Giữa quá trình tuần tự hóa và giải tuần tự hóa, chuỗi đại diện cho đối tượng có thể đã được lưu trữ trong một tệp hoặc dữ liệu hoặc được gửi qua kết nối mạng tới một số máy ở xa.

Ghi chú

Định dạng JSON thường được các ứng dụng hiện đại sử dụng để cho phép trao đổi dữ liệu. Nhiều lập trình viên đã quen thuộc với nó, điều này khiến nó trở thành một lựa chọn tốt về khả năng tương tác.

Nếu bạn có một đối tượng x, bạn có thể xem biểu diễn chuỗi JSON của nó bằng một dòng mã đơn giản:

>>> nhập json
>>> x = [1, 'đơn giản', 'danh sách']
>>> json.dumps(x)
'[1, "đơn giản", "danh sách"]'

Một biến thể khác của hàm dumps(), được gọi là dump(), chỉ đơn giản là tuần tự hóa đối tượng thành text file. Vì vậy, nếu f là một đối tượng text file được mở để ghi, chúng ta có thể thực hiện điều này:

json.dump(x, f)

Để giải mã lại đối tượng, nếu f là đối tượng binary file hoặc text file đã được mở để đọc:

x = json.load(f)

Ghi chú

Các tệp JSON phải được mã hóa trong UTF-8. Sử dụng encoding="utf-8" khi mở tệp JSON dưới dạng text file cho cả đọc và ghi.

Kỹ thuật tuần tự hóa đơn giản này có thể xử lý các danh sách và từ điển, nhưng việc tuần tự hóa các thể hiện của lớp tùy ý trong JSON đòi hỏi bạn phải nỗ lực nhiều hơn một chút. Tài liệu tham khảo cho mô-đun json có giải thích về điều này.

Xem thêm

pickle - mô-đun dưa chua

Trái ngược với JSON, pickle là một giao thức cho phép tuần tự hóa các đối tượng Python phức tạp tùy ý. Do đó, nó dành riêng cho Python và không thể được sử dụng để giao tiếp với các ứng dụng được viết bằng ngôn ngữ khác. Theo mặc định, nó cũng không an toàn: việc giải tuần tự hóa dữ liệu dưa chua đến từ một nguồn không đáng tin cậy có thể thực thi mã tùy ý nếu dữ liệu được tạo ra bởi kẻ tấn công lành nghề.