Tutorial

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

By Arnon Puitrakul - 05 พฤศจิกายน 2021 - 1 min read min(s)

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 ต่าง ๆ มันเป็นอะไรที่น่าสนใจมาก ๆ