Wednesday, 24 May 2017

Di chuyển trong file

Là một ngôn ngữ bậc cao, Python cung cấp các function giúp người dùng có thể đọc file dễ dàng theo từng dòng hay tất cả nôi dung của file. Vậy nhưng trong những trường hợp cần thiết, Python vẫn có thể can thiệp sâu xuống dưới tầng thấp hơn và thực hiện những yêu cầu phức tạp hơn.


Mở file - text stream

Mở file trong Python sử dụng funtion open('ten_file'), mặc định ta sẽ mở file để đọc (read) ở dạng "text mode". Ta mặc định file cần mở là một file text (thường là .txt, .csv ...)
>>> f = open('/etc/hosts')
>>> print(f)
<_io .textiowrapper="" encoding="UTF-8" mode="r" name="/etc/hosts">
Ta đọc toàn bộ nội dung của file vào bộ nhớ bằng method read:
>>> content = f.read()
>>> print(len(content))
918
Sau khi đọc đến cuối file (gặp ký tự EOF - end of file, một ký tự đặc biệt đánh dấu sự kết thúc của một file), nếu ta vẫn tiếp tục đọc, sẽ chỉ thu được empty string vì không còn gì để đọc.

>>> new = f.read()
>>> print(new, len(new))
 0
Bản chất file là một text stream, một dòng dữ liệu text. Python cho phép ta biết ta đang ở vị trí nào trong stream này với method tell
>>> help(f.tell)
Help on built-in function tell:
tell() method of _io.TextIOWrapper instance
    Return current stream position.
>>> print(f.tell())
918

Ta đang ở cuối một file chứa 918 bytes, và đang ở vị trí 918. Để quay lại đầu file, thông thường ta có thể đóng file rồi mở lại để quay về đầu stream. Thế nhưng Python còn có method seek giúp di chuyển đến một vị trí bất kỳ trong stream, rất tiện để lên đầu hay xuống cuối

>>> help(f.seek)
Help on built-in function seek:
seek(cookie, whence=0, /) method of _io.TextIOWrapper instance
    Change stream position.
 
    Change the stream position to the given byte offset. The offset is
    interpreted relative to the position indicated by whence.  Values
    for whence are:
 
    * 0 -- start of stream (the default); offset should be zero or positive
    * 1 -- current stream position; offset may be negative
    * 2 -- end of stream; offset is usually negative
 
    Return the new absolute position.
Có 3 vị trí khởi đầu seek method hỗ trợ:

    0: điểm bắt đầu của stream
    1: điểm hiện tại của stream
    2: điếm cuối của stream.

cookie hay offset là vị trí lệch đi so với điểm khởi đầu, rõ ràng nếu ta đang lấy điểm khởi đầu là đầu stream (0), offset phải là 0 hoặc số dương

>>> f.seek(0, 0) # về đầu file
>>> print(f.tell())
0

Còn khi ở cuối file, offset phải là 0 hoặc một số âm để chọn một vị trí trước vị trí cuối cùng
>>> f.seek(0, 2)
>>> print(f.tell())
918
Vì vậy, thay vì phải đóng file rồi mở lại để đọc từ đầu, chỉ cần dùng seek:

>>> f.seek(0, 0)
>>> data_again = f.read()
>>> print(data_again[:20])
127.0.0.1   localhos
Để giúp code dễ đọc hơn, thay vì dùng các giá trị 0, 1, 2 cho whence, có thể dùng các hằng số có sẵn trong standard lib io
>>> import io
>>> print(io.SEEK_SET, io.SEEK_CUR, io.SEEK_END)
0 1 2

Di chuyển trong binary file

>>> bf = open('/Users/hvn/Downloads/2015016-OdeToJoy-Beethoven-violin-solo.wav', 'rb')
>>> print(bf)
<_io .bufferedreader="" name="/Users/hvn/Downloads/2015016-OdeToJoy-Beethoven-violin-solo.wav">
>>> bf.seekable()
True
>>> print(bf.seek(0, io.SEEK_END))
1101342
>>> bf.tell()
1101342
>>> bf.seek(0, io.SEEK_SET)
>>> sound_data = bf.read()
>>> print(type(sound_data))
<class bytes="">
>>> len(sound_data)
1101342

Ta có thể sử dụng `seek` và `tell` giống như text file.

Ứng dụng

Viết một chương trình giả lập `tail -f` với những tính năng cao cấp hơn như xử lý logic của mỗi dòng trước khi in ra màn hình:

import time
# Tạo file
fd =  open('/tmp/data_stream', 'w').write('AAPL, 123, 0.5\nGOOG, 345, -0.2\n')
# Mở file rồi di chuyển xuống cuối
f = open('/tmp/data_stream')
f.seek(0, io.SEEK_END)
# Lặp vô hạn đọc các dòng mới
while True:
    line = f.readline()
    # Không có dòng nào mới, đọc tiếp từ file
    if not line:
        time.sleep(0.1)
        continue
    # Xử lý logic nếu có dòng mới trong file
    name, price, change = line.split(',')
    change = float(change)
    if float(change) &gt; 0:
        print('Got {0:10s} {1:10s} {2:10f}'.format(name, price, change))
Chạy câu lệnh sau trên terminal để ghi nối đuôi file /tmp/data_steam:
echo "PYMI, 456, 1.3" >> /tmp/data_stream
Ta sẽ thấy chương trình in ra:
Got PYMI        456         1.300000

Với đoạn code đơn giản trên, ta đã nhái lại được lệnh tail -f, lại thêm khả năng xử lý logic mà nếu với tail -f và các câu lệnh trên UNIX khác khá vất vả mới thực hiện được.

Xem bài gốc tại
https://github.com/pymivn/python-notes/blob/master/file_seek_tell.ipynb

Hết.
HVN at http://www.familug.org/ and http://pymi.vn

Đăng ký học lập trình #Python 3 từ con số 0 tại PyMI.vn - lớp Hà Nội khai giảng mùng 1 tháng 6, lớp Sài Gòn khai giảng đầu tháng 8. Xem chi tiết tại https://pymi.vn/ #PyMI #PyFML