By Arnon Puitrakul - 19 กรกฎาคม 2021
หลาย ๆ คนที่เขียน Javascript มา น่าจะเคยผ่านการใช้งาน Library อย่าง Lodash มาไม่มากก็น้อย ไม่สิ ตอนนี้เรามาใช้ Underscore.js แล้วป่ะ หรือว่าเราโบราณไปแล้ว ฮ่า ๆ ไม่ได้เขียนนาน แต่นั่นแหละ ถ้าเราได้ใช้ Library พวกนี้ไปแล้วมาเขียน Python ละ มันมี Library อะไรที่ทำเหมือน Lodash มั้ย คำตอบคือ มี แต่เราเรียกว่า Pydash วันนี้เรามาดูกันว่ายังไง
pip install pydash
การติดตั้ง Pydash ไม่ใช่เรื่องยากเลย เราสามารถติดตั้งผ่าน Pip ตรง ๆ ได้เลย และสำหรับ Apple Silicon ก็ไม่ต้องห่วง เราลองบน Macbook Air M1 แล้ว ก็คือลงได้ แต่เราใช้ Conda Forge ก็ได้เลย แต่เราลองลงผ่าน Pip แล้วมันไม่รอด ก็งง เหมือนกัน ดังนั้น ถ้าใช้ Apple Silicon ณ วันที่เขียนพยายามใช้ Conda Forge น่าจะดีกว่า
from pydash import py_ as _
เวลาเราจะใช้งาน เราต้อง Import มันเข้ามาก่อนผ่านสำสั่งด้านบน จะเห็นว่า ด้านหลังเราใส่ as เพื่อบอก Alias ของมันลงไปด้วย เป็นเครื่องหมาย Underscore จริง ๆ ไม่ต้องใส่ก็ได้ แต่เราเคยชินกับ Underscore.js ที่ใส่แบบนั้นจนชินไปซะแล้ว เลยเออ ไหน ๆ ก็เอามาใส่ละกัน
จริง ๆ ใน Pydash มีอะไรให้เราเล่นเยอะมาก ๆ เหมือนพวก Lodash เลย Function อะไรที่ Lodash มี Pydash จะมีเหมือนกันเลย แต่แค่การตั้งชื่อจะไม่เหมือนกันเท่านั้น เช่น ใน Python เราใช้ Snake Case ดังนั้น ชื่อ Function ของ Pydash ก็จะเป็น Snake Case เท่านั้น
นอกจากนั้น มันจะมีบาง Function ที่ชื่อมันจะไปซ้ำกับ Built-in Function ของ Python อยู่แล้ว ก็จะแก้ปัญหาด้วยการเติม Underscore ไว้ข้างหลังก็จะไปเรียกของ Pydash แล้ว ดังนั้นคนที่ใช้พวก Lodash มาก่อน ไม่ต้องมานั่งหาแล้วว่า มันทำอะไรได้บ้าง เพราะมันทำได้คล้ายกันมาก ๆ เลย แค่เปลี่ยนชื่อเท่านั้นเอง
[1,2,[3,4],[5,6]]
ลองมาดูพวก Function ที่เราใช้งานกันบ่อย ๆ บ้างว่าใช้อันไหนบ่อยมาก ๆ เริ่มจากเรื่องของ List กันก่อน ถ้าเราบอกว่า เรามี List ที่ข้างในก็มี List อยู่ และ เราต้องการที่จะเอาพวก List ที่อยู่ด้านในออก แล้วรวม Element เข้าด้วยกัน ดังตัวอย่างด้านบน เราจะทำยังไงดี ถ้าเราไม่มี Pydash เราก็ต้องเขียนเองใช่ป่ะ
data = [1,2,[3,4],[5,6]]
result_list = []
for item in data :
if type(item) != list :
result_list.append(item)
else:
result_list += item
อะ เราทำขำ ๆ ให้ดูละกัน ถ้าเกิดเราเจอเคสดั่งตัวอย่าง ถ้าเราอยากจะรวบมันให้เป็น List เดียวเลย เราก็ต้องค่อย ๆ Loop หาไปเรื่อย ๆ แล้วว่า ถ้า Item เป็นของที่ไม่ใช่ List เราก็แค่ Append หรือจับต่อท้ายไป กลับกัน ถ้าเป็น List เราก็บวกมันเข้าไปเลย มันก็คือ การต่อ List ใน Python แค่นั้นเลย แต่จะเห็นว่า กว่าเราจะได้มันมา เราใช้หลายบรรทัดมาก ๆ และ เมื่อเราเขียนแบบนี้ไปเยอะ ๆ แล้ว การจัดการ Code มันจะยุบยับมาก ๆ Code มันจะอ่านไม่ค่อยรู้เรื่องไม่เป็น Functional เท่าไหร่
result_list = _.flatten_deep(data)
แต่ถ้าเราใช้ Pydash เขา Implement Function สำหรับการรวบ List ไว้ให้เราแล้ว เราก็แค่ไปเรียกมาใช้ ก็จะใช้งานได้เลยทันที ถือว่า ทำให้ Code ของเราดูเรียบง่ายมากขึ้น เน้นความเป็น Functional มากขึ้นดูดีขึ้นเยอะ
[1,2,3,4,5,6] -> [[1,2,3],[4,5,6]]
หรืออีกงานที่ก็ต้องทำบ่อย คือการซอย List ออกเป็น Chunk ตัวอย่างที่เราใช้บ่อย ๆ เราจะใช้กับเวลาเราทำงานกับข้อมูลใหญ่ ๆ แล้วเราจะแบ่ง Chunk ข้อมูลแล้วโยนให้ Joblib โยนไปที่แต่ละ Process ถ้าเป็นเมื่อก่อน เราก็ต้องเขียนเองเลย จะเป็นเหมือนด้านล่างนี้
data = [1,2,3,4,5,6]
result = []
chunk_size = 3
for start_position in range(0,len(data), chunk_size) :
result.append(data[start_position:start_position+chunk_size]
// List Comprehension
result = [data[start_position:start_position+3] for start_position in range(0, len(data), chunk_size)]
Code ที่เราเขียน เราก็ทำง่าย ๆ เลย เราก็เล่น Loop ไปเลย ทีละ Chunk เช่น ตำแหน่งแรก เราต้องเอาตำแหน่งที่ 0-3 เราก็ Loop ตำแหน่งเริ่ม แล้วก็ Slice List ถึงตำแหน่งเริ่มบวกด้วยขนาดของ Chunk เท่านี้เราก็จะได้แบบที่เราต้องการแล้ว หรือ ถ้าเราอยากจะย่อให้สั้นลง เราก็สามารถใช้ List Comprehension ได้ แต่ไม่ว่า Code เราจะเขียนแบบไหนก็ยังยุ่งยากอยู่ดี ลองเอา Pydash มาเสียบดีกว่า
result = _.chunk(data,3)
บรรทัดเดียวจบ ง่ายมาก ๆ เลย เพราะเขาเขียนมาให้เราแล้วนั่นเองฮ่า ๆ หรือจริง ๆ อีกเรื่องที่เราใช้บ่อยมาก ๆ ทั้งใน Lodash และ Pydash คือคำสั่ง Get โดยเฉพาะในเหตุการณ์อย่าง เราเรียก API มา ได้ค่ากลับมาเป็น JSON แล้วบางที มันจะคืนโครงสร้างของ JSON กลับมาไม่เหมือนกัน เช่น Error อะไรแบบนี้จะไม่มีถ้าไม่ Error อะไรแบบนั้น หรือ ช่องของตัวเลขบางอย่างมันจะไม่มีในบางสถานการณ์ ซึ่งวิธีการจัดการง่าย ๆ คือ เราก็ต้องเขียน Logic สำหรับเข้ามาเช็ค และอาจะให้ Default Value อะไรก็ว่าไป ถ้าเราเขียนแค่อันเดียว อาจจะไม่น่าเบื่อมาก แต่อย่าลืมว่า เวลาเรารับค่ากัน เราไม่ได้รับกันตัวเดียวสักหน่อย เรารับกันเป็นร้อย ไหนจะแต่ละงานที่เราทำอีก เราต้องเสียเวลาเขียนพวกนี้ขนาดไหน
if 'error' not in result :
error_msg = 'No Error'
else:
error_msg = result['error_msg']
เวลาเราเขียนเช็ค เราก็ต้องเขียนอารมณ์แบบนี้แหละ อาจจะย่อให้มันสั้นได้ แต่ถ้าเราใช้ Pydash เราสามารถจบได้ที่บรรทัดเดียว ต่อ 1 ค่าได้เลย
error_msg = _.get(result, 'error', 'No Error')
เพียงคำสั่งเดียว เราก็จัดการได้ค่าแบบที่เราต้องการมาอย่างรวดเร็วทันใจเลย
แล้วมันสามารถทำอะไรที่พีคกว่านั้นได้อีก เช่นการทำ Chain Evaluation และ Lazy Evaluation ลองมาดูแบบที่เป็น Chain Evaluation กันก่อนจากตัวอย่างด้านล่าง
result = _([1, 2, 3, 4]).without(2, 3).reject(lambda x: x > 1).value()
เราจะเห็นว่า เรา Pass List ลงไปใน Object ของ Pydash แล้วพ่วงด้วย Function without ซึ่งก็คือไม่เอา 2 และ 3 สุดท้ายก็ reject เราก็ใส่เป็น Lambda ก็คือ อะไรที่มากกว่า 1 เอาออกไป สุดท้าย value() ก็คือให้มัน Evaluate ทั้งหมดออกมา ดังนั้น เราบอกว่า เราไม่เอา 2 และ 3 แล้วไม่เอาอะไรที่มากกว่า 1 ด้วย ดังนั้นผลลัพธ์ของ Function นี้ก็คือ [1] เฉย ๆ นั่นเอง
>> _([1, 2, 3, 4]).without(2, 3).reject(lambda x: x > 1)
<pydash.chaining.Chain object at 0x10c709d60>
ถ้าเกิดว่า เราไม่เรียก value() ตอนสุดท้ายละ มันจะเกิดอะไรขึ้น คำตอบก็คือ Expression ที่เราต่อกันมาทั้งหมดก็จะไม่ทำงานนั่นเอง ถ้าเราลอง Print ออกมาดู มันจะบอกว่า มันเป็น Object ตัวนึงของ Pydash นั่นเอง และ ถ้าเราต้องการคำตอบแล้ว เราก็เอามันไปเรียก value() มันก็จะ Evaluate ค่าออกมาให้เรานั่นเอง การทำแบบนี้ เราจะเรียกว่า การทำ Lazy Evaluation มีประโยชน์มาก ๆ กับข้อมูลใหญ่ ๆ อันนี้อาจจะยังไม่เห็นภาพ เพราะสุดท้าย เราก็เรียก value() ให้มัน Evaluate ออกมาทั้งหมดเลย สุดท้ายมันก็จะไม่ต่างจากการที่เราเรียกปกติ งั้นลองใหม่ ทำเหมือนเดิมเลย
>> _([list(range(0,10000))]).without(2, 3).reject(lambda x: x > 1).for_each(print)
อันนี้เราใช้ตัวอย่างเดิมละกัน แต่เราเพิ่มขนาดของตัวเลขเข้าไป จาก 4 หลายเป็น 10,000 ไปเลย สมมุติว่าเราทำงานกับข้อมูลขนาดใหญ่มาก ๆ เราไม่สามารถ Fetch ทั้งหมดลงใน Memory แล้วเราทำงานต่อกับมันได้ เราก็น่าจะต้องค่อย ๆ Fetch มันขึ้นมาทำงานไปเรื่อย ๆ นี่แหละ ประโยชน์ของ Lazy Evaluation มันสั่งให้เครื่องค่อย ๆ ทำออกมาเรื่อย ๆ มากกว่าการทำทีละงานแล้วปล่อยผลลัพธ์ออกมาตูมเดียวเลย
Late Value Passing ก็เป็นอีกตัวที่เราชอบเหมือนกัน คือ เราสามารถ Reuse Chain ที่เราทำงานไว้ได้ ถ้าเป็นปกติ เราอาจจะต้องไป Define Function มาเป็นเรื่องเป็นราว เยอะไปหมด แต่อันนี้ เราสามารถสร้าง Chain เปล่า ๆ ไว้ แล้ว เราค่อยเอาค่ามาใส่ทีหลังได้ เราเลยเรียกว่า Late Value Passing นั่นเอง
square_sum = _().power(2).sum()
a = square_sum([1, 2, 3])
b = square_sum([4, 5, 6])
ตัวอย่างด้านบนนี้ เราจะเห็นได้ว่า เราสร้าง Chain ที่รับค่ามา แล้วยกกำลัง 2 และ เอามาบวกกัน แต่ด้านหน้า เราไม่ได้ใส่อะไรไว้เลย เพราะเรากะว่า จะใส่ทีหลัง หรืออยากจะ Reuse Chain นี้หลาย ๆ ครั้ง เราก็สร้างไว้เป็นตัวแปรเก็บไว้ แล้วพอเราอยากใช้ เราก็สามารถที่จะเรียกมันขึ้นมาได้เลย หรือ จริง ๆ แล้วสามารถต่อ Chain ไปได้เรื่อย ๆ เลยก็ไม่ว่ากัน ก็ทำให้การเขียนมันยืดหยุ่นขึ้นเยอะ แต่จากที่เราเขียนมา เราไม่ค่อยชอบการทำ Chain ซ้อน ๆ กัน เพราะเวลาเรา Trace มันจะยากหน่อย ถ้าเราเขียนทีเดียวเลย เออ มันก็ง่ายหน่อย ไม่ค่อยเสียเวลา Trace มากเท่าไหร่
square_sum = _([1, 2, 3]).power(2).sum()
a = square_sum.value()
b = square_sum.plant([4, 5, 6]).value()
เมื่อกี้เราลองยัดค่าเข้าไปทีหลัง เพื่อ Reuse Chain แต่ถ้าเรายัดค่าตั้งแต่ต้นแล้วละ เราจะ Reuse มันยังไง คำตอบก็คือการ Replant Value กลับเข้าไป ผ่านคำสั่ง Plant ลองดูในตัวอย่างด้านบน เราใส่ค่าไปแล้ว และเก็บ Chain ไว้ แล้วเรียก value() ใน a และ b เราอยากจะ Reuse Chain แต่มันใส่ค่าไปแล้ว เราก็เลยต้องเรียก plant เพื่อ Replant Value ลงไปก็ได้เหมือนกัน แล้วก็เรียก value() เพื่อ Evaluate chain ใหม่
Pydash เป็น Library ที่เหมือนกับเป็นอุปกรณ์ในห้องครัว พวกมีดเอย เขียงเอยอะไรแบบนั้น เป็นของที่เราใช้ทำกับข้าวทุกครั้ง แต่ถ้าเราต้องไปซื้อใหม่ทุกครั้งมันก็ไม่น่าจะดีเท่าไหร่ ถ้าเราใช้ของที่เขาทำมาแล้ว ไม่ต้องไปซื้อเพิ่มทุกครั้ง มันก็เหมือนกับเราเรียก Pydash ขึ้นมาเลย แล้วก็ทำงานได้เลย โดยที่ไม่ต้องมานั่ง Reimplement Function พวกนี้เลย นอกจากนั้น มันยังทำให้โปรแกรมของเราอ่านง่ายเข้าใจว่ามันทำอะไร หรือจริง ๆ แล้วเราสามารถรวบ แล้วเขียนเป็น Functional Programming เลยก็ยังได้ รวม ๆ แล้วเราว่ามันทำให้เราทำงานได้เร็ว และ ยืนหยุ่นขึ้นนั่นเอง ทำให้เราชอบ
เมื่อหลายวันก่อน เราไปทำงานแล้วใช้ Terminal แบบปีศาจมาก ๆ จนเพื่อนถามว่า เราทำยังไงถึงสามารถสลับ Terminal Session ไปมาได้แบบบ้าคลั่งขนาดนั้น เบื้องหลังของผมน่ะเหรอกัปตัน ผมใช้ tmux ยังไงละ วันนี้เราจะมาแชร์ให้อ่านกันว่า มันเอามาใช้งานจริงได้อย่างไร เป็น Beginner Guide สำหรับคนที่อยากลองละกัน...
Firewall ถือว่าเป็นเครื่องมือในการป้องกันภัยขั้นพื้นฐานที่ปัจจุบันใคร ๆ ก็ติดตั้งใช้งานกันอยู่แล้ว แต่หากเรากำลังใช้ Ubuntu อยู่ จริง ๆ แล้วเขามี Firewall มาให้เราใช้งานได้เลยนะ มันชื่อว่า UFW วันนี้เราจะมาทำความรู้จัก และทดลองตั้ง Rule สำหรับการดักจับการเชื่อมต่อที่ไม่เกี่ยวข้องกันดีกว่า...
Obsidian เป็นโปรแกรมสำหรับการจด Note ที่เรียกว่า สารพัดประโยชน์มาก ๆ เราสามารถเอามาทำอะไรได้เยอะมาก ๆ หนึ่งในสิ่งที่เราเอามาทำคือ นำมาใช้เป็นระบบสำหรับการจัดการ Todo List ในแต่ละวันของเรา ทำอะไรบ้าง วันนี้เราจะมาเล่าให้อ่านกันว่า เราจัดการะบบอย่างไร...
อะ อะจ๊ะเอ๋ตัวเอง เป็นยังไงบ้างละ เมื่อหลายเดือนก่อน เราไปเล่าเรื่องกันขำ ๆ ว่า ๆ จริง ๆ แล้วพวก Loop ที่เราใช้เขียนโปรแกรมกันอยู่ มันไม่มีอยู่จริง สิ่งที่เราใช้งานกันมันพยายาม Abstract บางอย่างออกไป วันนี้เราจะมาถอดการทำงานของ Loop จริง ๆ กันว่า มันทำงานอย่างไรกันแน่ ผ่านภาษา Assembly...