I/O trong Python

Hôm ni, mình học tiếp về bạn “Đầu vào, đầu ra trong Python”, bài blog tiếp theo nằm trong series “Khám phá Đại Bản Doanh Python”, thuộc phần Python Tutorial ạ.

(Những nội dung trong bài series này từ chủ yếu mình lấy từ python.org rồi viết lại hoặc dịch lại theo ngôn ngữ của mình)

Định dạng đầu ra

Từ hồi bắt đầu viết(dịch) cái tutorial tới nay, tụi mình đã gặp qua 2 dạng đầu ra là expression statement print(). Ngoài ra còn có dạng đầu ra cho file dùng hàm .write() nữa(cái ni xíu mình ghé sau).

Cái “expression statement” mình để nguyên vì không biết dịch ra Tiếng Việt là gì, có lẽ nó là “câu lệnh biểu diễn” (?). Nó ở ngay bên dưới, cùng nói thêm về bạn ấy một xíu ha.

expression statment

Số “3” ở trên thể hiện cho đầu ra theo kiểu expression statement, cái này mình sẽ thấy trong chế độ tương tác của Python(Python Interpreter).

Ở đây, dòng số một là lệnh gán cho biến “a” , nó không có đầu ra. Dòng số hai là một biểu thức do đó interpreter tính toán và hiển thị kết quả.

Tuy nhiên, đầu ra như vậy chỉ hiển thị trong chế độ tương tác của Python thôi, nếu bạn viết đoạn code này vào một file script và thực thi file đó thì các giá trị vẫn được tính toán tuy nhiên sẽ không hiển thị ra kết quả, trừ khi mình dùng print để in nó ra như ví dụ dưới đây.

expression-statement

Nếu bạn vẫn còn chưa hiểu rõ lắm về variables, expression và statements, mời bạn đọc chương 2 nằm trong cuốn Think Python giới thiệu cái này khá rõ.

Còn khi mình chỉ cần xem giá trị nào đó ở dạng chuỗi mặt mũi ra sao, thì cách nhanh gọn nhất là dùng 2 hàm str() hoặc repr()

str() và repr()

Khi gọi str() hoặc print(), thì hàm __str__ của đối tượng sẽ được gọi. 

Khi gọi repr() thì hàm __repr__ của đối tượng sẽ được gọi.

Hàm str() trả về chuỗi đại diện cho đối tượng đó. Nếu hàm này không được định nghĩa thì sẽ gọi hàm __repr__() thay thế(nếu hàm này được định nghĩa).

Cả hai hàm này thường được sử dụng để debug.

str() and repr() in python

Tham khảo thêm ở đây: Difference between __str__ and __repr__?

Đôi khi mình cần in ra cho dễ nhìn hơn, phức tạp hơn. Có vài cách giúp mình làm chuyện đó như là: f-strings, hàm .format()các hàm định dạng tự do khác.

f-strings

f-srings là viết tắt của Formatted String Literals

f-strings sẽ có chữ “f” phía trước chuỗi như một tiếp đầu ngữ và cho phép thể hiện các giá trị trong cặp ngoặc.

Ngoài ra, việc thêm dấu “:” đi kèm với kiểu định dạng giúp mình định dạng đầu ra cho giá trị nữa.

Ví dụ ở trên là định dạng “.2f“, thể hiện muốn in ra số thực có 2 chữ số sau dấu “.”

Cách này mình cũng có thể áp dụng trong việc in theo cột cho dễ nhìn

use ":" format

Thế còn nếu bạn muốn in ra một dòng như thế này, với ‘BeautyOnCode’ là một chuỗi được truyền vào:

Tôi là ‘BeautyOnCode’

Để hiện dấu , có thể bạn sẽ làm là:

name = ‘BeautyOnCode’

print(f”Tôi là ‘{name}'”‘)

Làm vậy đúng rồi ý 😀

Cơ mà bên này còn có một kiểu cũng định dạng có ý nghĩa hơn, nó như vậy nè

print(“Tôi là {name!r}”‘)

print(“Tôi là {name!s}”‘)

!r thể hiện định dạng theo hàm repr()

!s thể hiện định dạng theo hàm str().

Ngoài ra còn có định dạng theo mã ascii() với !a nữa.

str.format()

Tiếp đến là str.format(), hàm này cũng dùng để định dạng chuỗi, nó tương tự như bạn f-strings ở trên thôi, chỉ khác về mặt thể hiện.

Bạn này thể hiện bằng .format() với các giá trị truyền vào theo vị trí hay dạng từ khoá(key=value)

Về mặt cú pháp, dấu ngoặc kép {} và ký tự bên trong đó sẽ được thay thế bằng các tham số truyền vào.

Ví dụ những chuỗi sau đều thể hiện giá trị là ‘Tôi là BeautyOnCode, 28 tuổi’

 Nhìn ví dụ ở trên:

>>> ‘Tôi là {}, {} tuổi’.format(‘BeautyOnCode’, 28)

Bạn này tương ứng chỉ có dấu {} nên thứ tự của tham số truyền vào sẽ theo thứ tự từ trái sang phải.

>>> ‘Tôi là {name}, {age} tuổi’.format(name=’BeautyOnCode’, age=28)

Bạn này tương ứng với chữ(key), nó thể hiện tên tham số được truyền vào và giá trị sẽ tương ứng với key đó

>>> ‘Tôi là {0}, {1} tuổi’.format(‘BeautyOnCode’, 28)

Bạn này tương ứng với các số, nó có thể định vị thứ tự in của tham số

>>> ‘Tôi là {}, {age} tuổi’.format(‘BeautyOnCode’, age=28)

Còn bạn này là bạn bị lai giữa tham số vị trí và tham số từ khoá, tham số vị trí phải đứng trước tham số từ khoá

>>> ‘Tôi là {name}, {} tuổi’.format(name=’BeautyOnCode’, 28)

Cuối cùng, bạn này cũng lai, nhưng lai bị sai, tham số vị trí đứng sau nên bị lỗi. 

Về vị trí đứng trước hay đứng sau mời bạn ghé đọc thêm bài về hàm mình đã viết ở đây nhé.

 

Bên cạnh đó, trong .format(), mình cũng định dạng giá trị truyền vào như trên f-strings với !r, !s, !a và “:” như đã giới thiệu ở trên nhé.

Xem thêm về String Format Syntax tại đây, phần này rất bổ ích khi bạn thực hiện in với các cấu trúc phức tạp.

Định dạng tay

Ví dụ có bài tập in ra 3 cột số như hình dưới đây, với cột đầu có giá trị từ 1-10, cột số 2 là bình phương và cột số 3 là lập phương của nó.

Nhớ là nó cần đẹp như vậy nha: cột số 1 chứa số có 2 chữ số, cột số 2 chứa số có 3 chữ số, cột số 3 chứa số có 4 chữ số. Và mỗi cột cách nhau một khoảng trắng.

Đầu tiên, mình nghĩ là cứ in được các giá trị ra đã, vì mỗi cột cách nhau một khoảng trắng nên mình có thể dùng end=’ ‘ để hàm print in ra:

Vậy là in được các cột và khoảng trắng nằm giữa rồi, giờ cần định dạng từng cột nữa. Cột số 2 tối đa có 2 chữ số(10), cột số 2 tối đa có 3 chữ số(100), cột số 3 tối đa có 4 chữ số(1000).

Và các chữ số nằm thẳng hàng về phía bên phải, cho nên mình cần dùng hàm str.rjust() để canh phải các giá trị, điều đó có nghĩa là nó thêm khoảng trắng vào phía bên trái.

Tuy nhiên, hàm .rjust() là của chuỗi, nên mình phải dùng str() hoặc repr() để chuyển giá trị sang chuỗi rồi mới dùng được. 

Nếu dùng nó với int nó sẽ báo AttributeError: ‘int’ object has no attribute ‘rjust’ ngay

result-rjust

Tương tự hàm .rjust() còn có str.ljust() và str.center()

Những hàm này không thay đổi giá trị chuỗi truyền vào, nếu chuỗi đó quá dài thì nó sẽ trả ra lại y chuỗi đó, còn ngắn thì nó sẽ thêm khoảng trắng tương ứng. 

Ngoài ra còn có một hàm nữa, là str.zfill(). Hàm này thay vì thêm khoảng trắng nó sẽ thêm số 0 vào vị trí bị thiếu, ví dụ: ’12’.zfill(5) muốn định dạng từ 2 số lên 5 số nên nó sẽ thêm 3 số 0 phía trước, kết quả là: ‘00012’

Định dạng chuỗi truyền thống(%)

Hồi xưa, string cũng có thể định dạng bằng toán tử %

Ví dụ print(‘PI is: %.2f’, math.pi) sẽ in ra số PI là 3.14.

Nay thì bạn này ít được dùng rồi vì bạn ý gây ra nhiều lỗi khi định dạng các kiểu dữ liệu như tuples, dictionaries cho nên mọi người đổi qua xài f-strings và .format().

Chi tiết hơn mời bạn ghé đây đọc thêm cho biết ha.

Đọc và ghi tệp tin

File object là gì?

Khi mình làm việc với file, việc đầu tiên mình cần làm là mở nó ra. Trong Python, hàm có sẵn open() giúp mình mở file ra và trả về file object. Nếu không thể mở file sẽ bị lỗi OSError.

f = open(“content.txt”, “w”)

Trong đó:

– “content.txt” là đường dẫn đến file

– “w” là mode (chế độ thể hiện mục đích mở file để làm gì), với “w” thì file được mở lên có thể ghi được.
Có hai loại mode hay sử dụng là “w“(mở để ghi), “r“(mở để đọc), “r+“(mở để đọc và ghi). 

Nếu mở file mà không có mode thì mode mặc định là “r.

Theo mặc định, file sẽ được mở trong chế độ text mode(t) và read mode(r), điều đó có nghĩa là nội dung của file được đọc/ghi với một bảng mã cụ thể, quá trình này gọi là encoding.  

Bản mã này được xác định theo hệ điều hành(Unix, Window, …). Ví dụ cụ thể hơn là với “\n” ở Unix hay “\r\n” ở Window nếu đứng cuối dòng sẽ được hiểu là “\n”. 

Sự điều chỉnh thầm lặng này sẽ rất tiện khi file là text, nhưng nếu file là hình ảnh(PNG, JPEG) hay file thực thi(exe) thì nên cẩn thận dùng binary mode(“b”) để đảm bảo nội dung không bị thay đổi.

Khi một file được mở ra với open() thì khi dùng xong rồi ta phải đóng lại với .close() để giải phóng tài nguyên sử dụng cho file đó. Nếu mà bạn quên đóng lại ấy, thì đội dọn rác trong Python(garbage collector) cũng sẽ giúp bạn đóng lại thôi, nhưng phải cần có thời gian để dọn tới file bạn quên nên có thể sẽ gây lãng phí tài nguyên là ứng dụng chạy chậm hơn.

Để giải quyết cái sự “quên” ở trên, có một cách tiện nhất là mở file với từ khoá “with”, bạn này sẽ tự động giúp mình đóng lại sau khi xong việc.

with open(“content.txt”) as f:

      data = f.read()

Cơ mà file khi đã đóng lại với .close() hoặc ở ngoài “with” rồi thì không xài được file object đó nữa đâu nha

open-file

Các phương thức của file object

f.read(size): đọc file, trả về string(text mode) hay byte objects(binary mode).

Nếu “size” không có giá trị truyền vào, hoặc là số âm thì tất cả nội dung sẽ được đọc. Trường này sẽ hữu ích khi file quá nặng và chỉ muốn đọc một phần(“size” ký tự) thôi.

Khi file đã được đọc hết thì chuỗi rỗng(”) sẽ được trả về.

read file in python
f.readline(), f.readlines() và list(f)

.readline() giúp đọc từng dòng, mình có thể dùng for kết hợp với readline() để đọc qua từng dòng của file.

readline in python

Bạn có để ý thấy hai dòng trên nó bị cách ra không? Đó là do ký tự “\n” đó, mình có thể thêm end=” và print() để xoá dòng trống này.

Ngoài ra có cách làm ngắn hơn là dùng .readline() hay list() để đọc tất cả các dòng trong file và trả về một mảng các chuỗi.

readlines-and-list-for-read-file-python

Nãy giờ nói chuyện đọc file, giờ nói qua chuyện viết file ha.

f.write()

Phương thức .write() dùng để viết nội dung dạng chuỗi vào file, và trả ra số ký tự được viết vào.

Nếu bạn có kiểu data khác chuỗi, thì dùng str() để chuyển nó qua chuỗi trước khi thực hiện viết vào nha.

 f.tell()

Trả ra một số nguyên thể hiện vị trí hiện tại của file object.

Ví dụ dưới đây cho thấy khi mở file lên thì f.tell() là 0 và sau khi đọc hết nội dung thì giá trị nó là chiều dài của nội dung đã đọc.

tell method in file object
f.seek(offset, whence)

Phương thức này giúp thay đổi vị trí hiện tại của file object. 

Cái này nó giống con trỏ ghê(mình nghĩ vậy thôi nha)

seek method in file object

Ở ví dụ trên, ban đầu thay đổi vị trí đến ký tự số 5 với f.seek(5), sau đó đọc một ký tự nên f.tell() sẽ là 6. 

Tiếp theo thay đổi vị trí đến ký tự thứ 3(từ cuối lên nên là -3), còn số 2 là whence thể hiện ký tự mặc định cuối hàng có chiều dài là 2(\n), thì vị trí nó là 48(vì tổng chiều dài là 51).

Lưu ý .seek() với số âm như vậy chỉ làm việc khi mình mở file với binary mode(b) thôi nha.

Và cuối cùng sau khi đọc thêm một ký tự thì f.tell() cho mình biết vị trí đang đứng là 49.

Lưu dữ liệu có cấu trúc với mô-đun json

Vì sao mô-đun json của Python ra đời?

Từ đầu tới giờ tụi mình chỉ làm việc với dữ liệu dạng chuỗi thôi, và cách mình đọc và ghi vào file với loại dữ liệu này được trình bày ở trên đó ^

Nếu giờ muốn đọc dữ liệu là các loại số thì mình có thể chuyển nội dung chuỗi đọc được qua số bằng các hàm định dạng dữ liệu số như int() hay float(). 

Tuy nhiên, nếu muốn đọc và ghi các loại dữ liệu phức tạp hơn(cái loại này mình gọi tạm là dữ liệu có cấu trúc) như một list chứ nhiều list con hay dữ liệu kiểu dict chứa dữ liệu theo key=value thì quá trình này sẽ phức tạp và khó khăn hơn.

 

Một ví dụ chuyển từ một chuỗi sang kiểu dữ liệu dictionary:

string-to-dict-raw

Vì thế(vì những cấu trúc phức tạp hơn sẽ còn khó chuyển hơn nữa), Python hỗ trợ sử dụng JSON, một định dạng trao đổi dữ liệu khá phổ biến hiện nay.

Python cung cấp mô-đun dựng sẵn tên là “json“. Mô-đun này sẽ giúp chuyển đổi các kiểu dữ liệu phức tạp thành dạng chuỗi, quá trình này được gọi là “tuần tự hoá(serializing)”. Ngược lại, khi tạo lại dữ liệu từ những chuỗi này, quá trình này gọi là “giải mã hoá(deserializing)”.

Cũng ví dụ trên, mô-đun json giúp mình deserializing chuỗi trên thành dict với hàm .loads():

str-to-dict-json-module

Các hàm hay dùng khi làm việc với mô-đun json

Đọc nội dung file với json.load():

Xem data ở dạng chuỗi JSON với json.dumps()

dumps json python

Chuyển dữ liệu dạng chuỗi JSON vào file với json.dump()

load-data-to-json

Queo quèo queo, finally, nội dung bài “Đầu vào, đầu ra” trong Python Tutorial đến đây tạm hết rồi.

Lần tới, tụi mình sẽ cùng nhau đọc tiếp phần “Lỗi và ngoại lệ trong Python” nhé.

Cám ơn mọi người ghé nhà.

Thân, 

BeautyOnCode.

 

Please follow and like us:
error

4 thoughts on “I/O trong Python

  1. Trong bài có nội dung giải thích str() và repr() dịch chưa chính xác.
    Mình nghĩ “string() sẽ trả về giá trị cho tụi mình(con người) đọc” thì nên giải thích thêm nếu một character có ưu tiên ký tự cho con người đọc thì str() sẽ trả về bạn đó, còn repr() thì trả về chính như nó vốn là.

    1. The source from tutorial of str() and repr() is:

      The str() function is meant to return representations of values which are fairly human-readable, while repr() is meant to generate representations which can be read by the interpreter (or will force a SyntaxError if there is no equivalent syntax). For objects which don’t have a particular representation for human consumption, str() will return the same value as repr(). Many values, such as numbers or structures like lists and dictionaries, have the same representation using either function. Strings, in particular, have two distinct representations.

      From: https://docs.python.org/3/tutorial/inputoutput.html

Leave a Reply

Your email address will not be published. Required fields are marked *

RELATED POST

Lỗi và xử lý ngoại lệ trong Python

Hôm ni, mình học tiếp về bạn "Lỗi và ngoại lệ", bài blog tiếp theo nằm trong series "Khám phá Đại Bản…

Khi nào bạn mới nghỉ ngơi?

Hôm rồi ngồi ăn với chị đồng nghiệp trong hội chuyên ăn muộn và thư thả thưởng thức đồ ăn…

Khám phá Đại Bản Doanh Python Series Overview(update)

(Image by Ajay kumar Singh from Pixabay)Và từ đó, sê ri "Khám Phá Đại Bản Doanh Python" ra đời Đây là…

Mua giày chuyện

(Image by Bhikku Amitha from Pixabay) Sáng nay, trong thời tiết nắng nổ đầu giữa tháng 7, mình đi mua giày mới. Đợt…