Python: Lambda function คือ อะไร ใช่โคมไฟไหม (เอ๊ย…นั่นมัน Lamptan) map, filter, reduce, zip

Grassroot Engineer
6 min readJan 2, 2021

--

https://towardsdatascience.com/python-lambda-function-b6e1fa3420c1

Lambda function คือ การประกาศฟังก์ชันเล็กๆ เราอาจเรียกว่า Anonymous function หรือ nameless function ก็ได้ (ที่เรียกแบบนี้เพราะว่า มันไม่ต้องประกาศชื่อ function) ประโยชน์ของมันคือ ง่าย กระชับ บางครั้งฟังก์ชันนั้นเราใช้แค่ครั้งเดียวไง ก็ขอเขียนสั้นๆไว้ดีกว่า และเหมาะกับการใช้ซ้อนกับฟังก์ชันอื่นได้ด้วย

Syntax:

lambda arguments : expression

Arguments จะรับเข้ามากี่ตัวก็ได้ เพียงแค่คั่นด้วย comma แบบทั่วๆไปเลยและ
การทำงานของ Lambda จะ return ผลลัพธ์จาก expression กลับมาเสมอ ลองไปดูตัวอย่างกันดีกว่า

นี่คือ function แบบปกติ

def rectangle(w, h):
return w * h


if __name__ == '__main__':
print(rectangle(10, 5))
# ---------------------------
# output:
# 50

เมื่อแปลงเป็น lambda function จะเป็นแบบนี้

rect = lambda w, h: w * h   if __name__ == '__main__':
print(rect(10, 5))
# ---------------------------
# output:
# 50
# จะได้ผลลัพธ์เท่ากันนะ ส่วนวิธีการใช้...เพราะเป็นฟังก์ชันไม่มีชื่อเราจึง assign และเรียกผ่านตัวแปร rect แทน
# แบบนี้คือรับ argument 2 ตัวคือ w, h และเราไม่ต้องกำหนดส่วนของการ return เพราะจะ return กลับมาเสมอ

การเรียกใช้งาน lambda function จะเรียกได้ 2 ลักษณะ คือ
1. Assign และเรียกผ่านตัวแปรแบบด้านบนได้เลย (เพราะมันไม่มีชื่อ)
2. ใช้ในลักษณะโดยผ่านค่าตัว lambda ให้เป็นเสมือน parameter ตัวนึงเข้าไปใน function ลักษณะนี้

rect = lambda w, h: w * hdef area(func, w, h):
return func(w, h)
if __name__ == '__main__':
print(area(rect, 5, 10))
# ---------------------------
# output:
# 50

วิธีการนี้คือ เรียกผ่านฟังก์ชัน area(rect, 5, 10) เป็นการ pass function เข้าไปเสมือน parameter ตัวนึงซึ่งวิธีการแบบนี้ จะใส่ function อะไรเข้าไปก็ได้ในกรณีที่มี function อื่นที่ต้องใช้ w, h เหมือนกัน เช่น

triangle = lambda w, h: w * h * 0.5def area(func, w, h):
return func(w, h)
if __name__ == '__main__':
print(area(triangle, 5, 10))
#---------------------------
# output:
# 25.0

Apply with map() and list():
- map() เป็นการทำงานกับสมาชิกทุกตัว เหมือนกับมี loop for อยู่ภายในนะคับ เราเพียงแค่กำหนด literable เท่านั้น ในรูปด้านล่างคือ usd

ประยุกต์ใชัในการแปลงค่าเงินจาก usd to thb โดยใช้ lambda ร่วมกับ map และ list (ใช้บ่อยนะ)
  • จริงๆแล้วเราสามารถใช้ for-loop แทน map() ก็ได้เช่นกันนะ ในบางเคสเช่นแบบนี้คือ ต้องการนำ element ทุกตัวมา *2 ก็สามารถเขียนได้ 2 แบบเลย
values = [1, 2, 3, 4, 5]

new_values = [v*2 for v in values] # ใช้ for จะดู clean กว่านะ
map_values = list(map(lambda x: x*2, values)) # แต่ถ้าใช้ map จะต้องใช้ร่วมกับ list ด้วย (เพราะไม่งั้นผลลัพธ์จะเป็น object นะ)
print(new_values)
print(map_values)

---- outut ----
[2, 4, 6, 8, 10]
[2, 4, 6, 8, 10]

ไหนๆก้อไหนๆล่ะ ถ้าเปรียบเทียบกันภาษา dart ลองดูเล่นๆนะ
วิธีการเขียนจะเปลี่ยนไปแบบนี้

Python:
thb = list(map(lambda v: v * 32, usd))
Dart:
var thb = usd.map((v) => v * 32).toList();

map(), filter(), reduce(), zip()

นอกจากข้อดีที่สามารถเขียนได้สั้น แบบไม่ต้องมาประกาศ def และไม่ต้องเขียน return ด้วย เรายังสามารถนำไปประยุกต์ใชักับฟังก์ชันอื่นๆได้อีก

map(function, collection):

nums = [2, 4, 6]
result = map(lambda x: x**2, nums)
print(list(result))

# หรือจะใส่ list แบบนี้ก็ได้
result = list(map(lambda x: x**3, nums))
print(result)
# -------------------------
# output
# [4, 16, 36]
# [8, 64, 216]

การใช้ map มี syntax คือ map(function, collection) แต่บางครั้งถ้าเราต้องการให้ map ออกมาเป็น data type อะไรก็สามารถทำได้เช่นกันนะ จากรูปด้านล่าง

  • แปลง 751 ให้เป็น str(751) เพื่อให้เป็น iterable หรือ เข้า for-loop ได้
  • ต้องการ map ให้เป็น int ก็ใส่ int เข้าไปได้เลย ซึ่งรูปเต็มๆคือ แบบนี้นะ
list(map(lambda i: int(i), str(751))) # หรือ list(map(int, str(751)))
  • สุดท้ายแปลง return ออกมาเป็น list ด้วย list()
ตัวอย่างการใช้ map เพื่อให้แปลงเป็น int นะ (จริงๆแล้ว int ก็เป็น class นึงที่เราเอามาใช้งานได้)

นอกจากนี้มาดู used case ของการใช้ map() ร่วมกับ dict ในการเช็คว่ามี key นั้นๆไหมใน dict โดยไม่สนใจ case sensitive นะ

ข้อ 4 นะ เป็นการ map เพื่อเปลี่ยน key ใน dict ให้เป็น lower ก่อนที่จะนำไปเช็คใน if

อีกหนึ่ง used case ของการใช้ map() ร่วมกับ sum() ในการรวมค่าที่ต้องการ

ใช้ map() + sum()

filter(function, iterable)

ตัวอย่างการใช้ filter อยู่ด้านล่างสุดนะคับ เป็น used case ที่เรามักจะเจอบ่อยๆ

nums = [1,2,3,4,5,6,7,8,9,10]
result = list(filter(lambda x: x >= 5, nums))
print(result)
# -------------------------
# output
# [5, 6, 7, 8, 9, 10]

reduce(function, iterable)

สำหรับ reduce() จะไม่ใช่ default common function ใน Python3 นะคับ ถ้าจะใช้จะต้อง import เข้ามาแบบนี้ก่อน

from functools import reduce

function นี้จะคืนค่าออกมาแค่ค่าเดียวนะ ไม่ได้คืนเป็น collection เหมือนกับอันอื่นนะ มักจะใช้ในการ calculate ผลลัพธ์จาก elements ทั้งหมด
(reduces a collectionให้เหลือแค่ single value)

python-reduce-function (https://www.python-course.eu/lambda.php)

การ sum ด้วย reduce()

ภาพนี้เป็นการอธิบายให้เห็นภาพ ว่าใน reduce() จะมีการวิ่ง for-loop ตามรอบของ iterables
# เราอาจลดทอนให้เหลือแค่ lambda ก็จะง่ายขึ้น

from functools import reduce

nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = reduce(lambda x, y: x + y, nums)
print(result)
# -------------------------
# output
# 55
reduce() in DART vs PYTHON

จริงๆแล้ว reduce() มีประโยชน์มากๆ จากการที่มันจับคู่ให้ไปเรื่อยๆจากหน้าไปหลัง เราสามารถนำมา apply สร้าง getattr() ที่เข้าถึงได้หลายๆชั้นแบบนี้

การสร้าง getattr_nested_object() โดยใช้ reduce() มาช่วย

zip(iterator1, iterator2, iterator3 …)

function นี้เป็นการผูกข้อมูลที่มี index เดียวกันเข้าด้วยกันนะ โดยจะผูกกี่ iterables ก็ได้ จากนั้นจะ return ออกมาเป็น iterable of tuples, จากนั้นเราสามารถแปลงให้เป็น list หรือ type ที่เราต้องการได้เลย เช่น

questions = [
'7 x 1 = ',
'7 x 2 = ',
'7 x 3 = ',
'7 x 4 = ',
'7 x 5 ='
]
answers = ['7', '14', '21', '28', '35']

result = list(zip(questions, answers))
print(result)
# -------------------------
# output
# [('7 x 1 = ', '7'), ('7 x 2 = ', '14'), ('7 x 3 = ', '21'), ('7 x 4 = ', '28'), ('7 x 5 =', '35')]

ดูอีกตัวอย่างนะคับ

name = ["John", "Charles", "Mike"]
age = [35, 23, 16]
position = ['Programmer', 'Manager', 'Designer']

result = list(zip(name, age, position))
print(result)

# Output
# [('John', 35, 'Programmer'), ('Charles', 23, 'Manager'), ('Mike', 16, 'Designer')]

ลองมาดู used caseในการสร้าง check digit เพื่อสร้างข้อมูล Barcode
ตามสูตรที่ได้รับมาครับ

อธิบายวิธีการจับคู่ด้วย zip() แบบ index by index
def calculate_check_digit(student_code: str, ref2: str, amounts: str) -> str:
weight_list = [7, 5, 1] * 10
weight_r1 = weight_list[:11] # First 11 digits for ref1
weight_r2 = weight_list[11:21] # Since position 12-21 for ref2
weight_amount = weight_list[21:] # Since 22 onwards for amount
r1_list = [int(r1) for r1 in student_code]
r2_list = [int(r2) for r2 in ref2]
amount_list = [int(amount) for amount in amounts]

sum1 = sum(w * d for w, d in zip(weight_r1, r1_list))
sum2 = sum(w * d for w, d in zip(weight_r2, r2_list))
sum3 = sum(w * d for w, d in zip(weight_amount, amount_list))

check_digit = str((sum([sum1, sum2, sum3]) * 2) % 100)
return check_digit.zfill(2) if len(check_digit) < 2 else check_digit


check_digit = calculate_check_digit(
student_code='55050427733',
ref2='1234567890',
amounts='15000'
)
print(check_digit)

# output
# 80

สำหรับ zip() ไม่ได้ใช้ lambda function นะคับ แค่ review เฉยๆ

Used cases ในการใช้ Lambda function ใน Django project:

1. การ Filter dict in list

เช่น ถ้าเรามี leave_dates ออกมาแบบนี้
สิ่งที่ผมต้องการคือ ต้องหาว่าใน list ของ leave_dates เนี่ย

- มี value ของ approve ที่เป็น True อยู่ทั้งหมดกี่วัน
- และต้องการกรองออกมาเป็น list ของ objects นั้นๆเลย

จากรูปนะคับ คือจะหา len และ list ของ objects ที่ approve มีค่าเป็น True

ใช้ for-loop เขียนแบบปกติเพื่อเช็ค dict in list จะเห็นว่าต้องเขียนถึง 4 บรรทัดเลยทีเดียว

ในรูปด้านล่างจะเหลือแค่บรรทัดเดียวนะคับ คือ จะใช้ filter() เข้ามาช่วย โดย filter(function, iterable) ซึ่ง function ด้านในจะเป็น lambda function และ iterable จะเป็น leave_dates ที่เป็น list นั่นเอง

จากนั้นเมื่อ filter ออกมาได้แล้ว ก็แปลงให้เป็น list อีกครั้งนึง (ไม่งั้นมันจะด่านะ ว่าผลของการ filter จะต้องถูกใช้ด้วย)

การใช้ filter() ร่วมกับ lambda function เหลือเพียงแค่บรรทัดเดียว

2. การ filter โดยแยก function ออกมาเขียนภายนอก (ไม่ได้ใช้ lambda)

# ให้เอาเฉพาะตัวเลขออกมานะ ทั้งที่เป็น String และ number

data = ['1', 2, 'Love', '5.22', 'one', 8.55, '130030', ' 554.3']


def is_numeric(item):
if isinstance(item, str):
try:
float(item)
return True
except ValueError:
return False
elif isinstance(item, (int, float)):
return True
else:
return False


numeric_data = list(filter(is_numeric, data))
print(numeric_data)


# ==========================================
# Output
# ['1', 2, '5.22', 8.55, '130030', ' 554.3']

จะเห็นว่ามีทั้งตัวเลขที่เป็น String, int, float เลยนะ ฉะนั้นเรามาลองใช้ filter() อีกแบบที่ต้องเขียน function ไว้ภายนอก เพราะเคสนี้จะซับซ้อนขึ้น

Explain how it work.

3. Condition in short for-loop

จริงๆอันนี้ไม่เกี่ยวกับ lambda function นะคับ แต่ผมเห็นว่าเท่ดี ด้วยวิธีการเขียนแบบใส่เงื่อนไขใน short for-loop ได้เลย จึงมาเพิ่มเติมตรงนี้เลยคับ

อันนี้คือ short for-loop แบบที่ยังไม่มีเงื่อนไขด้านในนะ

Short for-loop without condition.
Short for-loop with simple condition.

อันนี้จะเป็น condition ที่ยากขึ้นมาหน่อยคือ เป็นการเช็คว่า list_of_deleted_id มันมีอยู่ใน leave_type_settings_from_db รึป่าว
ถ้ามี id นั้นๆอยู่ ก็ให้ return ออกมาเป็น name แทนนะ (ไม่ใช่ id) แต่ถ้าไม่มีก็ต้องไม่พังนะ งงมะ อิอิ

Short for-loop with if condition

--

--