python-list-comprehension-generator
python-list-comprehension-generator

บทความนี้เราจะมาเรียนถึง Pattern ของ comprehension และ Generator ที่น่าสนใจเพื่อนําไปใช้งานจริง โดยเริ่มจาก concept ของ comprehension ว่าคืออะไร?

List Comprehensions

เจ้า List Comprehension คือ concept ง่ายๆ concept นึงใน Python ที่ใช้ในการสร้าง List ขึ้นมานั้นเอง

โดยจะมี Pattern ที่ไม่เหมือนชาวบ้าน และ อ่านเข้าใจยากหน่อย คือ

syntax-structure-list-comprehension (http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Comprehensions.html)
syntax-structure-list-comprehension (http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Comprehensions.html)

หรือดูจากตัวอย่างข้างล่างนี้ก็ได้


# example 1 - simple 
squares = [i**2 for i in range(0,10)]
#output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# example 2 - with condition
squares_with_condition = [i**2 for i in range(0,10) if i % 2 != 0]
#output: [1, 9, 25 ,49, 81]

list comprehension ข้างล่างนี้จะทําการวนลูปให้ครบ 10 รอบ โดยจะเอาจํานวนรอบไปยกกําลัง 2 นั้นเอง โดยจะเห็นว่าค่าที่ออกมาจะเป็นยกกําลัง 2 ของ 0 ถึง 10

ทีนี้มาดูตัวอย่างที่ยากขึ้นมาหน่อยน่ะ 🙂 เป็น nested list comprehensions


#สร้าง matrix โดยใช้ nested list comprehension 
matrix = [[col for col in range(5)] for row in range(5)]

#output
#[0, 1, 2, 3, 4]
#[0, 1, 2, 3, 4]
#[0, 1, 2, 3, 4]
#[0, 1, 2, 3, 4]
#[0, 1, 2, 3, 4]

คือมันจะเริ่มจาก ซ้ายมาขวาน่ะ เพราะฉะนั้นจะได้ [0,1,2,3,4] – column มาก่อน แล้วหลังจากนั้น ก็นํามาวน range อีก 5 รอบ – row ก็เลยจะออกมาเป็น array 2 มิตินั้นเองง

Conditionals in comprehensions

อย่างที่เราเห็นกันคือเรามี if condition ได้ ซึ่งเราสามารถใช้ if else ก็ได้ด้วยน่ะ


# Create a list of strings: fellowship
list_data = ['aa', 'bbbbbbb', 'c' ]

# Create list comprehension: new_fellowship
new_data = [data if len(data) >= 7 else '' for data in list_data]

# output: ['','bbbbbb','c']

ถามว่า List comprehensions มีดียังไง?

ก็คงต้องตอบว่ามันเขียนได้สั้นกว่าและดูสะอาดกว่าการเขียนเต็มๆ แต่ด้วยวิธีนี้มันต้องแลกมากับการอ่านยาก เช่นถ้าเราไม่ใช้ comprehension มันจะออกมาแบบหน้าตาด้านล่างนี้


new_list = []
for i in old_list:
    if filter(i):
        new_list.append(expressions(i))

#กลายมาเป็นยังงี้แทน

new_list = [expression(i) for i in old_list if filter(i)]

จะเห็นว่ามันสั้นกว่ากันเยอะ 🙂 แล้วก็อ่านเข้าใจยากกว่าด้วย ฮ่าๆ

Dict Comprehensions

นอกจาก comprehension ของ list แล้ว dict เราก็ทําได้เหมือนกันน่ะ (แต่ไม่ค่อยเห็นคนนิยมทํากันเท่าไร) โดยเราเปลีย่นจาก brackets [] เป็น braces {} แทน


negative_dict = { num: -num for num in range(3) }

#output {0: 0, 1: -1, 2: -2 }

Comprehension vs Generators

มันคล้ายกับ comprehension มากเลย  แต่มีความแตกต่างกันตรงที่มันไม่เก็บข้อมูลลง Memory แต่จะ generate ให้เมื่อต้องการ

โดยเราเรียกว่า Lazy evaluation process !!! จะให้ค่าต่อเมื่อเรียกใช้ ไม่มีการให้ค่าก่อน ซึ่งข้อดี ของมันก็คือเมื่อเราทํงานกับ data ที่เป็นล้านๆๆๆๆ record เราไม่จําเป็นต้อง save ลง memory เราจะเรียกใช้เมื่อต้องการ

เราสามารถโดยใช้ () แทน [] ในการสร้าง generator ขึ้นมา 🙂


even_nums = (num for num in range(10) if num  2 == 0)
#output: [0,2,4,6,8]
#สังเกตุได้ว่า comprehension มีความสามารถอะไร Generators ก็ทําได้อย่างงั้น

Generators Function

คุ้นๆมั้ยเรื่องของ Generator …. ถ้าคุ้นนั้นเป็นเพราะว่าเราคือพูดถึงเรื่องของ Generator ไปใน บทความเรื่องของ yield ครั้งที่แล้ววววววววววนั้นเองงง 🙂

แต่สรุปโดยสั้นๆก็คือ Generator Function ก็คือการใช้ Keyword yield เพื่อให้ได้ generator ออกไปแค่นั้นเลย โดย object generator จะค่อยๆทํางานเท่าที่เราเรียก ไม่มีการอ่านข้อมูลทีล่ะเยอะๆเพื่อมาเก็บไว้ใน Memory ซึ่งเป็นวิธีการที่ดีในการจัดการ Data set ขนาดใหญ่

ตัวอย่างของเทคนิคน่ารู้คือเรื่องของการนําไปใช้งานจริงกับ การอ่านไฟล์ขนาดใหญ่

Chunks ตัวอย่างการใช้ yield 

คือนอกจากเราจะใช้ yield กับการจัดการข้อมูลที่เราไม่ต้องการให้ทํางานทันที หรือ เก็บลง memory อีกสึ่งนึงคือสิ่งที่เรียกว่า chunks

มันคือ concept ของการแบ่งข้อมูลออกมาอ่านทีล่ะส่วนๆนั้นเอง โดยดูจากตัวอย่างด้านล่างได้

 

python-example-chunks (https://www.geeksforgeeks.org/break-list-chunks-size-n-python/)
python-example-chunks (https://www.geeksforgeeks.org/break-list-chunks-size-n-python/)

จะเห็นว่ามันอ่านทีล่ะส่วน แล้วเก็บลง yield ที่เป็น list หลังจากนั้นถึงค่อยทํางานตอน print นั้นเอง

 

ตัวอย่างการใช้จัดการกับ Data set ขนาดใหญ่ด้วย Pandas

อย่างที่ได้อธิบายไปว่าการอ่านไฟล์ทีเดียวแล้วไปเก็บใน Memory อย่าง read csv ทั่วไปเนี่ย มันเหมาะกับไฟล์เล็กๆแบบหลักพันบรรทัด แต่ถ้าเป็นหลักแสนหรือล้านบรรทัดล่ะ? มันจะเก็บลง Memory ได้หมดจริงๆหรอ?

ซึ่งเราไม่อยากรู้คําตอบเราเลยจะใช้ Generator ในการจัดการ ที่นี้เลยรวบรวม best practise มาให้ดูว่าจัดการอย่างไรดี โดยจะใช้ Pandas (DataFrame) เป็นตัวจัดการกับ Tabular Data แล้วล่ะ ซึ่งจริงๆแล้วเราไม่จําเป็นต้องใช้ Pandas ก็ได้ โดยเขียนแบบ yield ตามรูปแบบข้างบน แต่การใช้ pandas จะช่วยลดเวลาในการ Implement เอง และก็สะดวกสบายกับ function ต่างๆด้วย

จากตัวอย่างข้างล่างนี้คือ การอ่าน csv file เข้ามาเป็น DataFrame + Plot graph ออกมา ซึ่งการดึงไฟล์ขนาดใหญ่แบบนี้มันจะใช้ generator ทั้งสิ้น

# Define plot_pop()
def plot_graph_from_file(filename, country_code):

    # สร้าง reader object: ชื่อ reader ( generator ทันทีเลย )
    reader = pd.read_csv(filename, chunksize=1000)

    # Initialize empty DataFrame: data
    data = pd.DataFrame()
    
    # Iterate over each DataFrame chunk
    for df_urb_pop in reader:
        # Check out specific country: df_pop_ceb
        df_pop_ceb = df_urb_pop[df_urb_pop['CountryCode'] == country_code]

        # Zip DataFrame columns of interest: pops
        pops = zip(df_pop_ceb['Total Population'],
                    df_pop_ceb['Urban population (% of total)'])

        # Turn zip object into list: pops_list
        pops_list = list(pops)

        # Use list comprehension to create new DataFrame column 'Total Urban Population'
        df_pop_ceb['Total Urban Population'] = [int(tup[0] * tup[1]) for tup in pops_list]
    
        # Append DataFrame chunk to data: data
        data = data.append(df_pop_ceb)

    # Plot urban population data
    data.plot(kind='scatter', x='Year', y='Total Urban Population')
    plt.show()

# Set the filename: file_name
file_name= 'population_data_file.csv'

# Call plot_pop for country code 'UK'
plot_graph_from_file(file_name, 'UK')

# Call plot_pop for country code 'USA'
plot_graph_from_file(file_name, 'USA')

refer: Datacamp course

สรุปแล้ววววววว

  • Comprehension เป็นเทคนิคนึงที่น่าสนใจมาก ในการสร้าง list, dictionary ขึ้นมา โดยสามารถลดความยุ่งยากของลูปเหลือเพียงบรรทัดเดียว แต่ก็แลกกับการ readability ของโค้ดลง .. เวลาใช้ก็ต้องเลือกเอาหน่อย
  • ของเด็ดอีกอย่างคือ Comprehension เร็วกว่า For ด้วยน่ะครับ เพราะฉะนั้นถ้าในเวลาที่เราทํางานกับ Data จํานวนมาก เลือกใช้ Comprehension จะเร็วกว่า
  • Generator เทคนิคเดียวกับ Comprehension เพียงแต่มันไม่เอาข้อมูลทั้งหมดไปลง memory นั้นเอง มันจะค่อยๆ return value ออกมาให้เมื่อเราเรียกใช้นั้นเอง เหมาะเวลาใช้กับข้อมูลขนาดใหญ่ ที่เราไม่อยากเอาลง memory นั้นเอง …. โดยเวลาจะใช้ในการทําเป็น Method เราก็จะใช้ keyword yield ตอน return

Leave a Reply

avatar

This site uses Akismet to reduce spam. Learn how your comment data is processed.