Tutorial

pipe Python Package ที่ทำให้ Code น่ารักขึ้นเยอะ

By Arnon Puitrakul - 05 พฤศจิกายน 2021

pipe Python Package ที่ทำให้ Code น่ารักขึ้นเยอะ

เมื่อไม่กี่วันก่อนระหว่างเปิดหาอะไรในเน็ตไปเรื่อยก็ไปเจอกับของเล่นใหม่เข้าโดยบังเอิญ แล้วพอเอามาลองใช้จริง ๆ เออ มันก็แปลก ๆ ดีนะ เวลาเขียนแล้วมัน งง ๆ ว่าเอ๊ะอิหยังวะอยู่พอสมควรเลย นั่นคือ Package ที่ชื่อว่า pipe ใช่แล้ว มันคือ pipe เดียวกับที่เราใช้ใน Command Line สำหรับเอาผลของ Expression ก่อนหน้าไปใส่เป็น Input ของตัวต่อไปนั่นเองเหมือนกันเลย โดยที่ใน Package นี้ตัวอย่างที่เราว่าใช้งานได้โหด ๆ เลยคือการทำงานกับพวก List ทั้งหลาย ยิ่งถ้าข้อมูลเราเยอะ ๆ ต้องทำ Operation หลาย ๆ ตัวเราว่าโคตรเหมาะเลย ทำให้ง่ายกว่าเดิมเยอะมาก

ติดตั้ง pipe

pip install pipe

การติดตั้ง pipe ทำได้ไม่ยากเลย เราสามารถใช้ pip ได้เลย สำหรับคนที่ใช้ conda เราลองแล้วมันหา Package ไม่เจอ เราก็ทำการ Install ผ่าน pip ได้เช่นกันจากคำสั่งด้านบน

from pipe import where

เพื่อเป็นการเช็คว่าเราติดตั้งผ่านแล้วจริง ๆ เราอาจจะลอง Import มันเข้ามาก็ได้ ถ้าไม่มีปัญหาอะไรก็คือ การติดตั้งเรียบร้อยดีไม่มีปัญหา

Your First Pipe

from random import randint
from pipe import where

people_age = [randint(x, x+20) for x in range(80)]
adult_people = people_age | where (lambda x : x>18)

หลังจากเราติดตั้งแล้ว เราลองมาใช้งาน Pipe กันดีกว่า เราเริ่มจากคำสั่งที่ง่าย ๆ ก่อนละกันเป็นการเลือกข้อมูล โดยที่เราสามารถกำหนดเงื่อนไขได้ เหมือนกับเราเลือกข้อมูลใน SQL เลยอะไรแบบนั้น ในตัวอย่างด้านบน เราทำการสร้าง List ปลอม ๆ ขึ้นมาเหมือนกับเป็นอายุของคนละกัน จำนวน 80 คนด้วยกัน จากนั้น เราต้องการที่จะ Filter เพื่อเอาคนที่อายุ มากกว่า 18 ออกมา เราเลย Pipe แล้วให้มันวิ่งไปที่ where ในนั้น สิ่งที่เราใส่ลงไปก็จะเป็น Function สำหรับการเลือก ซึ่งในที่นี้คือ เราต้องการ Item ที่มากกว่า 18 โดยการใช้ Anonymous Function

adult_people = []
for person_age in people_age :
    if person_age > 18 :
        adult_people.append(person_age)

ถ้าเราไม่ใช้ Pipe และเขียนเป็น Loop ปกติ ก็น่าจะอารมณ์แบบตัวอย่างด้านบนเลย จะเห็นได้ว่าเขียนเยอะมาก ๆ ไม่ได้เป็น Functional มากสักเท่าไหร่ ไม่น่ารักเลย หรืออีกวิธี ถ้าเราไม่อยากเขียนยาว ๆ แบบนี้เราก็สามารถใช้ Map Function กับ Anonuymous Function ได้เหมือนกัน ถ้าอยากลองก็ไปลองกันได้เด้อ เราไม่ได้เขียนให้ดู

>>> adult_people
<generator object where.<locals>.<genexpr> at 0x100ae6c10>

กลับมาที่ Pipe ของเราต่อ เมื่อครู่ เราได้ Pipe เพื่อเลือกอายุที่มากกว่า 18 ออกมา แต่เมื่อเราเรียกเอ๋ ทำไมมันไม่เป็นเหมือนที่เราคิดละ ทำไมเราไม่ได้ List ของข้อมูลออกมาละ กลายเป็น Generator ซะงั้น ใช่แล้วฮ่ะ มันได้ออกมาเป็น Generator การใช้งานก็จะเหมือนการใช้ Generator ทั่ว ๆ ไปเลย มันก็มีข้อดีอยู่นะเอาจริง ๆ โดยเฉพาะเมื่อเราทำงานกับข้อมูลขนาดใหญ่ จะเก็บผลทั้งหมดลง Memory เลย ก็น่าจะไม่ไหวนะ เปลืองทั้งเงินซื้อ RAM และ เสียเวลามาก ๆ

>>> list(adult_people)
[20, 24, 26, 19, 19, 29, 24, 30, 20, 22, 29, 24, 35, 33, 27, 31, 32, 29, 30, 38, 35, 35, 34, 46, 34, 45, 35, 40, 36, 43, 53, 38, 54, 54, 49, 43, 54, 54, 58, 63, 54, 65, 67, 65, 54, 66, 57, 54, 61, 60, 71, 62, 68, 68, 64, 76, 83, 65, 77, 80, 80, 78, 73, 74, 76, 88, 78, 75, 85, 86, 89, 95, 89]

แต่ถ้าเรารู้อยู่แล้วว่าข้อมูลเราไม่ได้ใหญ่อะไรเลย เช่นในตัวอย่างของเราที่มีอย่างมากแค่ 80 ตัวเท่านั้น การเอามันออกมาทั้งหมดเลยไม่ใช่ปัญหาแน่นอน เราก็สามารถจับมัน Cast เป็น List แล้วเราก็จะได้ค่าใน Generator ทั้งหมดออกมาพร้อมใช้งานต่อได้เลย สะดวกมาก ๆ เลยชั่ยมั้ยล้าาาา

Handling Fu_king Large Data with Generator

from random import randint

def age_generator (sizing) :
    for _ in range(sizing) :
        yield randint(10,70)

แน่นอนว่า เวลาเราทำงานบางอย่างที่ Data ขนาดมันไม่น่ารักเท่าไหร่ โหลดเข้ามาคือแตกแน่นอน เกิน RAM ไปไกล ดังนั้น เราจะต้องใช้เทคนิคอื่นในการจัดการ ซึ่งแน่นอนว่าถ้าใครเคยทำงานกับ Data ขนาดใหญ่มาก่อนวิธีนึงที่เราทำได้คือ การใช้ Generator สิ่งที่เราทำก็เหมือนเดิมเลย คือ เราทำการ Random อายุของคนขึ้นมา ผ่านการเขียน Generator ที่เราสามารถกำหนดจำนวนของคนที่เราต้องการลงไปได้ แล้วเราก็ For-Loop แล้วค่อย ๆ Yield อายุออกไปทีละรอบเลย

adult_people = age_generator(200) | where (lambda x : x>18)

จากนั้น แทนที่เราจะยัด List เข้าไปตรง ๆ เราก็ยัด Generator เข้าไปตรง ๆ ได้เลย เอาจริง ๆ มันต้องทำได้อยู่แล้วนะ เพราะความเป็นจริงแล้ว List และ Generator เป็นพวก Iterable อยู่แล้ว หมายความว่า มันเป็น Object ที่สามารถ Loop ใส่มันได้อะไรประมาณนั้น การใช้ท่า Generator ก็จะทำให้การทำงานกับ Data ขนาดใหญ่สบายขึ้นไปอีก

แต่ตัว Pipe ไม่ได้มี Built-in Function สำหรับการทำ Multiprocessing มาให้เราเลยนะ ถ้าอยากได้ เราก็สามารถเขียนเองได้เหมือนกัน เราว่าถ้าทำออกมาจริง ๆ มันจะ Powerful มาก ๆ ในการทำงาน

Groupping Data

อีกเคสที่เราว่าน่าสนใจคือ การ Group Data จากเงื่อนไขต่าง ๆ เช่นถ้าเราทำงานกับ Data ที่ไม่ได้ใหญ่มากเท่าไหร่ อยากทำงานทดลองคิดไว ๆ การใช้ Pipe ก็ทำให้เราทดลองไอเดียได้เร็วมาก ๆ เหมือนกัน

filtered_data = age_generator(200) | groupby(lambda x: "Even" if x %2 == 0 else "Odd") | select(lambda x: {x[0]: list(x[1])})
print(list(filtered_data))

จากตัวอย่างด้านบน เราขอใช้ Generator เดิมเลยละกัน เป็น age_generator แต่หลัก ๆ ก็คือมัน Random ตัวเลขทั่ว ๆ ไปไม่มีอะไร เป้าหมายของเราคือ เราต้องการแยก เลขคู่ ออกจากเลขคี่ ซึ่งใน Pipe ก็จะมีคำสั่งชื่อ groupby มาให้เราใช้งานกัน ในนั้นเราก็เหมือนเดิมเลย ใช้ Anonymous Function ง่าย ๆ เช็คแค่ว่า Modulo กับ 2 แล้วมีเศษมั้ย แล้วก็แยกไปตามฟากได้เลย แต่ถ้าเราทำแค่นี้ เราจะได้ Dictionary ที่ Data เป็น Generator โดยเราสามารถเข้าไป Loop และแปลงเป็น List เองก็ได้ หรือเราก็ใช้ Pipe ให้เป็นประโยชน์ ก็แปลงมันไปเลยดิ แล้วค่อยแปลงอีกทีตอนเราจะใช้มันก็จะเรียกทั้งหมดเอง ง่ายมาก ๆ เลย

สรุป

Pipe เป็น Package ที่มันเจ๋งอยู่นะ มันไม่ได้มาแทนที่สิ่งที่ Python ไม่เคยมี จริง ๆ แล้วทั้งหมดที่เราเล่ามาเราเขียน Python เปล่า ๆ เลยไม่ได้ลง Package อะไรเลยมันก็ทำได้ แต่แค่มันเสียเวลา กับมันดูรกเท่านั้นเอง ทำให้ประโยชน์ของ Pipe ที่เราว่ามันเจ๋งจริง ๆ คือ เรื่องของเวลา และ ความสะอาด เข้าใจง่ายของ Code ดูเป็น Functional มากขึ้นเท่านั้นเอง ซึ่งเราแนะนำเลย โดยเฉพาะถ้าเราต้องทำงานกับพวก Pipeline ต่าง ๆ มันเป็นอะไรที่น่าสนใจมาก ๆ

Read Next...

จัดการข้อมูลบน Pandas ยังไงให้เร็ว 1000x ด้วย Vectorisation

จัดการข้อมูลบน Pandas ยังไงให้เร็ว 1000x ด้วย Vectorisation

เวลาเราทำงานกับข้อมูลอย่าง Pandas DataFrame หนึ่งในงานที่เราเขียนลงไปให้มันทำคือ การ Apply Function เข้าไป ถ้าข้อมูลมีขนาดเล็ก มันไม่มีปัญหาเท่าไหร่ แต่ถ้าข้อมูลของเราใหญ่ มันอีกเรื่องเลย ถ้าเราจะเขียนให้เร็วที่สุด เราจะทำได้โดยวิธีใดบ้าง วันนี้เรามาดูกัน...

ปั่นความเร็ว Python Script เกือบ 700 เท่าด้วย JIT บน Numba

ปั่นความเร็ว Python Script เกือบ 700 เท่าด้วย JIT บน Numba

Python เป็นภาษาที่เราใช้งานกันเยอะมาก ๆ เพราะความยืดหยุ่นของมัน แต่ปัญหาของมันก็เกิดจากข้อดีของมันนี่แหละ ทำให้เมื่อเราต้องการ Performance แต่ถ้าเราจะบอกว่า เราสามารถทำได้ดีทั้งคู่เลยละ จะเป็นยังไง เราขอแนะนำ Numba ที่ใช้งาน JIT บอกเลยว่า เร็วขึ้นแบบ 700 เท่าตอนที่ทดลองกันเลย...

Humanise the Number in Python with "Humanize"

Humanise the Number in Python with "Humanize"

หลายวันก่อน เราทำงานแล้วเราต้องการทำงานกับตัวเลขเพื่อให้มันอ่านได้ง่ายขึ้น จะมานั่งเขียนเองก็เสียเวลา เลยไปนั่งหา Library มาใช้ จนไปเจอ Humanize วันนี้เลยจะเอามาเล่าให้อ่านกันว่า มันทำอะไรได้ แล้วมันล่นเวลาการทำงานของเราได้ยังไง...

ทำไม 0.3 + 0.6 ถึงได้ 0.8999999 กับปัญหา Floating Point Approximation

ทำไม 0.3 + 0.6 ถึงได้ 0.8999999 กับปัญหา Floating Point Approximation

การทำงานกับตัวเลขทศนิยมบนคอมพิวเตอร์มันมีความลับซ่อนอยู่ เราอาจจะเคยเจอเคสที่ เอา 0.3 + 0.6 แล้วมันได้ 0.899 ซ้ำไปเรื่อย ๆ ไม่ได้ 0.9 เพราะคอมพิวเตอร์ไม่ได้มองระบบทศนิยมเหมือนกับคนนั่นเอง บางตัวมันไม่สามารถเก็บได้ เลยจำเป็นจะต้องประมาณเอา เราเลยเรียกว่า Floating Point Approximation...