Friday, December 24, 2010

Python Programing

ทำงานกับไฟล์ด้วย Python ตอนที่ 2: การอ่านไฟล์

ประพัฒน์ สุริยผล


งานทาง bioinformatics ส่วนใหญ่มักจะหนีไม่พ้นการทำงานกับไฟล์ โดยมากจะเป็น text file คือไฟล์ตัวอักษรที่เราสามารถอ่านได้ การอ่านไฟล์ใน python โดยพื้นฐาน จะมีคำสั่งที่เกี่ยวข้องอยู่ 3 คำสั่งคือ


1. read

2. readline

3. readlines


คำสั่งที่ผมเคยใช้มากที่สุดคือคำสั่ง readlines เนื่องจากผมทำงานกับ text file เป็นหลัก เพราะฉะนั้นผมขออนุญาตเริ่มต้นที่คำสั่งนี้ก่อนนะครับ

เราจะสร้าง text file หนึ่งชื่อว่า test.txt ด้วย code ดังด้านล่างครับ


f = open('test.txt', 'w')

f.write('1. this is the first line\n')

f.write('2. this is the 2nd line\n')

f.write('3. this is the last line\n')

f.close()


ถ้าเรารัน code สั้นๆ นี้ เราก็จะได้ไฟล์ test.txt คราวนี้ เราจะลองมาอ่านไฟล์นี้กันครับ


f = open('test.txt')

for line in f.readlines():

....print line


สิ่งที่ใหม่สำหรับ code สั้นๆ นี้คือเราใช้ for loop กับข้อมูลที่ได้กลับมาจากการเรียก method readlines


คงพอจำกันได้นะครับว่า ตัวแปร f ที่เราได้คืนมาจากการเรียกใช้ function open คือ object ซึ่งเราจะเรียกใช้ method ของ object นั้นได้ เริ่มต้นด้วยตัวแปร object ตามด้วยจุด และชื่อ method (ขอให้พยายามแยก function กับ method ให้ชินนะครับ ในช่วงแรก ผมจะเขียนให้สังเกตเป็นระยะ)


method readlines จะคืนค่าเป็น list ของแต่ละบรรทัดของไฟล์ object ซึ่งเมื่อเราใส่ไว้ใน for loop ก็จะเท่ากับว่าเราวนลูปแต่ละบรรทัดของข้อมูลในไฟล์


เพราะฉะนั้น เมื่อเรารันคำสั่งข้างต้นเราจะได้ผลลัพธ์คือ

1. this is the first line


2. this is the 2nd line

3. this is the last line


มีจุดสังเกตประการหนึ่งคือ แต่ละบรรทัดจะเหมือนมีการขึ้นบรรทัดใหม่ 2 ครั้ง ที่เป็นเช่นนี้ เพราะว่าในตัวแปร line นั้นมีคำสั่งขึ้นบรรทัดใหม่อยู่แล้ว เวลาเราอ่านแต่ละบรรทัดจากคำสั่ง readlines นั้น python ไม่ได้ตัดตัวขึ้นบรรทัดใหม่ทิ้งไป เมื่อเราใช้ร่วมกับคำสั่ง print ซึ่งจะพิมพ์ตัวขึ้นบรรทัดใหม่ให้เมื่อจบคำสั่งเสมออยู่แล้ว จึงเท่ากับว่า เราพิมพ์ตัวขึ้นบรรทัดใหม่ 2 ครั้ง ครั้งแรกจากข้อมูลใน line เอง และครั้งที่ 2 เป็นผลของคำสั่ง print จุดนี้เป็นข้อผิดพลาดบ่อยๆ ของผู้ที่เขียนโปรแกรมอ่านไฟล์ใหม่ๆ ครับ


Strip method


วิธีการแก้ไขมีได้หลายวิธี วิธีที่ผมใช้บ่อย เพราะส่วนใหญ่แล้วเรามักจะไม่ต้องการตัวขึ้นบรรทัดใหม่ท้ายบรรทัดอยู่แล้ว ผมจึงใช้คำสั่ง strip() ช่วยครับ


f = open('test.txt')

for line in f.readlines():

....print line.strip()


จะเห็นว่า strip ก็เป็น method เหมือนกัน เป็น method ของ string object ตัวแปร line ที่เป็น string นั้น อันที่จริงแล้วก็คือ object อันหนึ่ง เพราะฉะนั้นเราจะไม่เรียก function strip(line) แต่เราจะเรียก method strip ของ line โดยเขียนเป็น line.strip() แทน


คำสั่ง strip() จะตัด whitespace ทั้งด้านหน้าและด้านหลังของบรรทัดนั้นออกหมดเลย เพราะฉะนั้น เวลาเรียกใช้งาน ขอให้เข้าใจคำสั่งนี้จริงๆ นะครับ เพราะถ้าหากบรรทัดนั้นมีเว้นวรรค tab อยู่ด้านหน้าหรือด้านหลังก็จะถูกลบทิ้งไปด้วย ไม่ใช่เพียงแต่ตัวขึ้นบรรทัดใหม่เท่านั้น


ที่เราเรียก whitespace ก็เพราะว่าเว้นวรรค tab และตัวขึ้นบรรทัดใหม่ จะไม่แสดงอะไรบนหน้าจอ เห็นเป็นพื้นที่ว่างๆ จึงเรียกว่า whitespace ครับ และ method ที่สะดวกที่สุดที่จะกำจัด whitespace ทิ้งก็คือ strip


แต่ถ้าหากเราไม่ต้องการลบ whitespace ที่อยู่ด้านหน้า เราต้องการกำจัดเฉพาะที่อยู่ด้านหลัง เราก็สามารถใช้ method rstrip() แทนได้ครับ ตัว r เล็กที่อยู่ด้านหน้า ก็หมายถึง right คือตัดเฉพาะด้านขวาเท่านั้น และคงจะเดากันได้ว่า python มี method lstrip() สำหรับตัด white space เฉพาะด้านซ้าย หรือด้านหน้าบรรทัดให้เราด้วยเช่นกัน เลือกใช้กันตามสะดวกครับ

f = open('test.txt')

for line in f.readlines():

....print line.rstrip()


ผมถูกถามว่า แล้วถ้าไม่ต้องการเปลี่ยนแปลงค่า line เรามีทางที่จะบังคับให้ print ไม่ขึ้นบรรทัดใหม่ได้ไหม คำตอบคือได้ครับ ถ้าหากเราปิดท้ายคำสั่งด้วยสัญลักษณ์ comma (,)


f = open('test.txt')

for line in f.readlines():

....print line,


เมื่อเราปิดท้ายด้วย comma คำสั่ง print จะไม่พิมพ์ตัวขึ้นบรรทัดใหม่ให้ครับ


ถ้าหากท่านผู้อ่านช่างสังเกตนิดนึง จะเห็นผมบอกตอนต้นบทความว่า ผมเคยใช้ method readlines เพราะด้วย python เวอร์ชันใหม่ๆ ที่ออกมา ผมแทบจะไม่ได้ใช้คำสั่ง readlines แล้วครับ เพราะเราสามารถใช้ loop ใน file object ได้เลยดังนี้ครับ


f = open('test.txt')

for line in f:

....print line,


อ่านง่ายและสะดวกดีนะครับ code จะอ่านได้ว่าสำหรับแต่ละบรรทัดในไฟล์นี้ เราต้องการพิมพ์แต่ละบรรทัดออกมา ตรงไม่ตรงมา แต่ถ้าหากคุณใช้ python เวอร์ชันเก่าหน่อย อาจจะใช้วิธีนี้ไม่ได้เท่านั้นเอง แต่วิธีนี้เป็นวิธีแนะนำครับ เพราะนอกจากสั้น อ่านสะดวก และยังทำงานได้รวดเร็วด้วยครับ


Readline method


แล้ว method readline จะใช้เมื่อไหร่ อันที่จริงคงเดาจากชื่อ method ได้ไม่ยากว่า คำสั่งนี้จะอ่านบรรทัดจาก file object มาครั้งละบรรทัด (ไม่มี s ด้านหลัง แสดงว่าเป็นเอกพจน์)

ถ้าเราใช้คำสั่ง


f = open('test.txt')

line = f.readline()

print line.strip()


เราก็จะพิมพ์แค่บรรทัดแรกบรรทัดเดียว ถ้าหากเราต้องการใช้คำสั่ง readline อ่านไฟล์ไปเรื่อยๆ แทนที่จะใช้ readlines อ่านทีเดียว เราก็อาจจะเขียนได้ดังนี้


f = open('test.txt')

line = f.readline()

while line != '':

....print lne.strip()

....line = f.readline()


อาจจะดูสับสนเล็กน้อย เรามาลองไล่ code ดูกันสักนิดครับ เรามาเริ่มต้นที่บรรทัดที่ 3 ที่เป็นลูปหลักของ code ลักษณะการคิดคือเราต้องการวนลูปไปเรื่อยๆ จนกว่าจะไม่เหลือบรรทัดในไฟล์แล้ว ซึ่งเมื่อไหร่ที่ไม่มีบรรทัดเหลือแล้ว method readline() จะคืนค่า empty string กลับมา นี่คือเหตุผลที่เราใส่เงื่อนไขใน while ว่า ให้วนลูปไปเรื่อยๆ ตราบใดที่ line ไม่ใช่ empty string


ในลูป เราจะพิมพ์บรรทัดที่อ่านมาได้ และที่สำคัญมากที่สุดคือ อย่าลืมอ่านบรรทัดต่อไป ตามที่ผมเขียนไว้ในบรรทัดที่ 5 นะครับ เพราะจุดนี้เป็นจุดที่ผิดพลาดกันบ่อยที่สุด เวลาเขียนลูป while เพราะหากเราไม่อ่านค่าบรรทัดถัดไปมาว่าในตัวแปร line ตัวแปร line ก็จะไม่เปลี่ยนแปลง ลูปนี้ก็จะวนไม่มีที่สิ้นสุดครับ จะวนพิมพ์บรรทัดนั้นซ้ำไปไม่รู้จบ เหล่านี้คือสาระสำคัญของ while loop บรรทัดที่ 3-5


บรรทัดที่ 2 จำเป็นเพราะเงื่อนไขของ while อยู่ที่บรรทัดที่ 3 ส่วนการอ่านบรรทัดถัดไปอยู่ที่บรรทัดที่ 5 เพราะฉะนั้นเราจำเป็นที่จะต้องอ่านบรรทัดแรกเข้ามาไว้ในตัวแปร line ก่อน ถ้าหากไฟล์นั้น ไม่มีข้อมูลใดๆ เลย ตัวแปรก็จะเป็น empty string ตั้งแต่บรรทัดที่ 2 และไม่เข้าเงื่อนไขเข้าลูปในบรรทัดที่ 3 จึงไม่มีการพิมพ์อะไรออกมา


ถ้าหากไฟล์นั้นมีข้อมูลอยู่บรรทัดเดียว ข้อมูลนั้นก็จะอ่านมาอยู่ในตัวแปร line และผ่านเงื่อนไขของ while loop เข้าไปพิมพ์ข้อมูลในบรรทัดที่ 4 และอ่านบรรทัดถัดไปใน code บรรทัดที่ 5 ซึ่งจะทำให้ค่า line กลายเป็น empty string เพราะไฟล์นั้นมีข้อมูลอยู่บรรทัดเดียว และเราได้อ่านบรรทัดนั้นไปแล้วใน code บรรทัดที่ 2


แต่ถ้าหากไฟล์นั้นมีข้อมูลหลายบรรทัด ก็จะวนลูปไปจนกว่าจนหมด ได้ค่า empty string อยู่ในตัวแปร line


หวังว่าคงไล่ code ได้ไม่ยากเย็นนะครับ


จุดสำคัญในการเขียน while loop


เมื่อไหร่ ที่เราต้องการเขียน while loop ซึ่งที่ต้องตรวจสอบอยู่เสมอคือ เงื่อนไขของ while loop มีโอกาสเป็นเท็จ เพื่อให้หลุดออกจาก loop ได้หรือไม่ ซึ่งในกรณีนี้ จะเตือนให้เราไม่ลืมเขียน code บรรทัดที่ 5 เพื่ออ่านบรรทัดถัดไปเข้ามา และให้ตรวจสอบว่า ตัวแปรที่อยู่ในเงื่อนไขของ while loop นั้น มีอยู่ก่อนเข้า while loop ไหม เพราะหากไม่มี python จะแสดง NameError มาว่า ตัวแปรนั้นยังไม่ถูกกำหนดมาก่อน (is not defined)


เมื่อการใช้ readline อ่านไฟล์ทั้งไฟล์ยุ่งยากกว่า readlines() หรือ for loop in file object อย่างนี้แล้ว เมื่อไหร่เราถึงจะมีที่ใช้ readline คำตอบคือเมื่อเราไม่ต้องการอ่านไฟล์ทั้งไฟล์ อย่างเช่น เราต้องการอ่านเฉพาะ 10 บรรทัดแรก เราสามารถเขียนได้ดังนี้


f = open('test.txt')

first_ten_lines = []

for i in range(10):

....first_ten_lines.append(f.readline())


พอเห็น code อย่างนี้แล้ว อดไม่ได้ที่จะเตือนให้ผู้อ่านนึกถึง list comprehensions ของ python ที่จะทำให้ code ของเราสั้นและกระชับเป็นดังนี้


f = open('test.txt')

first_ten_lines = [f.readline() for i in range(10)]


Read method


มาถึงคำสั่งสุดท้ายคือ read ถ้าเราจะมาลองเดากัน คำสั่งนี้ก็คืออ่านข้อมูลมา แบบที่ไม่เป็นบรรทัด เพราะไม่มีคำว่า line ต่อท้าย เพราะฉะนั้น ข้อมูลทั้งหมดของไฟล์จะถูกอ่านรวดเดียวเลยดังนี้ครับ


f = open('test.txt')

all_data = f.read()


ข้อมูลทั้ง 3 บรรทัดจะมาอยู่ใน all_data ส่วนใหญ่แล้วคำสั่ง read จะใช้สำหรับการอ่านไฟล์ที่ไม่ใช่ text file เช่นไฟล์รูปภาพต่างๆ คำสั่ง read อาจจะมีที่ใช้ที่อื่นอีก แม้แต่กับ text file แต่พบไม่บ่อย และมักไม่ได้ใช้ในงานทาง bioinformatics ทั่วไป จึงขอไม่กล่าวถึงในครั้งนี้


ครั้งนี้เราได้ method พื้นฐานสำหรับการอ่านไฟล์แล้ว ครั้งหน้าเราจะเขียนโปรแกรมตอบโจทย์ที่พบบ่อยกันครับ ขอให้สนุกกับ python เช่นเคยครับ

No comments:

Post a Comment