อาร์กิวเมนต์ฟังก์ชันใน Python: *args, **kwargs และค่าเริ่มต้น
บทเรียนนี้แปลจากต้นฉบับภาษาอังกฤษRead in EnglishLeer en españolЧитать на русскомLer em portuguêsแจ้งข้อผิดพลาดในการแปล
ลองนึกถึงการสั่งกาแฟ คุณต้องเลือกขนาดเสมอ นั่นเป็นสิ่งที่ขาดไม่ได้ แต่ของเพิ่มอย่างนมโอ๊ต ช็อตกาแฟเพิ่ม หรือซอสคาราเมลราดหน้าล่ะ สิ่งเหล่านั้นไม่บังคับ คุณจะบอกก็ต่อเมื่ออยากได้เท่านั้น
อาร์กิวเมนต์ของฟังก์ชันใน Python ก็ทำงานแบบเดียวกัน บางตัวต้องใส่เสมอ บางตัวมีค่าเริ่มต้นที่เหมาะสมอยู่แล้ว และบางแบบก็ให้ส่งของเพิ่มเข้าไปได้มากเท่าที่ต้องการ การเข้าใจรูปแบบเหล่านี้อย่างถ่องแท้คือสิ่งที่แยกผู้เริ่มต้นออกจากคนที่เขียนโค้ดได้ยืดหยุ่นจริง ๆ
ในบทเรียนนี้ คุณจะได้เรียนรู้ทุกวิธีที่ Python ใช้ส่งข้อมูลเข้าฟังก์ชัน ตั้งแต่อาร์กิวเมนต์ตามตำแหน่งขั้นพื้นฐาน ไปจนถึงรูปแบบ *args และ **kwargs อันทรงพลังที่ใช้ในไลบรารีจริง
อาร์กิวเมนต์ตามตำแหน่งกับอาร์กิวเมนต์แบบระบุชื่อ
เมื่อเรียกฟังก์ชัน Python จับคู่อาร์กิวเมนต์กับพารามิเตอร์ได้สองแบบ อาร์กิวเมนต์ ตามตำแหน่ง (positional) จับคู่ตามลำดับ อาร์กิวเมนต์ตัวแรกไปที่พารามิเตอร์ตัวแรก ส่วนอาร์กิวเมนต์ แบบระบุชื่อ (keyword) จับคู่ตามชื่อ
Whiskers is a cat Buddy is a dog
อาร์กิวเมนต์แบบระบุชื่อเขียนในรูป parameter_name=value ทำให้การเรียกฟังก์ชันอธิบายตัวเองได้ ใครอ่านโค้ดก็เห็นชัดว่าแต่ละค่าหมายถึงอะไร โดยไม่ต้องไปดูนิยามของฟังก์ชัน
คุณใช้ทั้งสองแบบผสมกันในการเรียกครั้งเดียวได้ แต่อาร์กิวเมนต์ตามตำแหน่งต้องมาก่อนอาร์กิวเมนต์แบบระบุชื่อ
Whiskers is a 3-year-old cat
ค่าเริ่มต้นทำให้ฟังก์ชันยืดหยุ่นได้อย่างไร
ค่าเริ่มต้น (default value) ทำให้กำหนดพารามิเตอร์ที่ไม่บังคับได้ ถ้าผู้เรียกส่งค่ามา ก็ใช้ค่านั้น ถ้าไม่ส่ง ก็ใช้ค่าเริ่มต้นแทน
large coffee medium coffee with oat milk small coffee with almond milk, 2 sugar(s)
พารามิเตอร์ size ไม่มีค่าเริ่มต้น จึงต้องใส่เสมอ ส่วนพารามิเตอร์ milk และ sugar มีค่าเริ่มต้น จึงไม่บังคับ ไลบรารี Python จำนวนมากทำงานแบบนี้เลย คือมีพารามิเตอร์บังคับไม่กี่ตัว และพารามิเตอร์ไม่บังคับอีกหลายตัวที่มีค่าเริ่มต้นเหมาะสม
กับดักค่าเริ่มต้นที่เปลี่ยนแปลงได้
นี่คือหนึ่งในจุดหลอกที่ขึ้นชื่อที่สุดของ Python เมื่อใช้ออบเจกต์ที่เปลี่ยนแปลงได้ (เช่น ลิสต์) เป็นค่าเริ่มต้น ออบเจกต์นั้นจะถูกสร้าง ครั้งเดียว ตอนนิยามฟังก์ชัน ไม่ใช่ทุกครั้งที่เรียกฟังก์ชัน
แปลว่าการเรียกทุกครั้งใช้ลิสต์ เดียวกัน การเปลี่ยนแปลงจากการเรียกครั้งหนึ่งจะติดไปถึงครั้งถัดไป มาดูตัวอย่างกัน:
['apples'] ['apples', 'bread'] ['apples', 'bread', 'milk']
แปลกใจไหม การเรียกแต่ละครั้งควรเริ่มด้วยลิสต์ใหม่ แต่ลิสต์กลับยาวขึ้นเรื่อย ๆ เพราะ [] ถูกสร้างครั้งเดียวตอนนิยามฟังก์ชัน และถูกใช้ซ้ำในทุกการเรียก
วิธีแก้คือรูปแบบที่รู้จักกันดีใน Python: ใช้ None เป็นค่าเริ่มต้น แล้วสร้างลิสต์ใหม่ภายในฟังก์ชัน
['apples'] ['bread'] ['milk']
*args ทำอะไร
บางครั้งคุณไม่รู้ล่วงหน้าว่าฟังก์ชันจะได้รับอาร์กิวเมนต์กี่ตัว เช่น อาจต้องการฟังก์ชันที่บวกตัวเลขได้ไม่จำกัดจำนวน ไม่ว่าจะ 2, 5 หรือ 100 ตัว
รูปแบบ *args รวบรวมอาร์กิวเมนต์ตามตำแหน่งที่เกินมาทั้งหมดไว้ใน ทูเพิล (tuple) จากนั้นคุณจะวนลูป ใช้ดัชนี หรือทำอะไรก็ได้ที่ทำกับทูเพิลได้
6 100 5
ชื่อ args เป็นเพียงธรรมเนียมที่นิยมใช้ คุณจะเขียน *numbers, *items หรือ *values ก็ได้ ความพิเศษอยู่ที่ * ซึ่งบอก Python ให้รวบอาร์กิวเมนต์ตามตำแหน่งที่เหลือทั้งหมดไว้ในทูเพิล
<class 'tuple'>
('hello', 42, True)
Argument 0: hello
Argument 1: 42
Argument 2: Trueคุณใช้ *args ร่วมกับพารามิเตอร์ธรรมดาได้ พารามิเตอร์ธรรมดาจะรับค่าไปก่อน แล้ว *args จะรวบรวมทุกอย่างที่เหลือ
Hello, Alice! Hello, Bob! Hello, Charlie!
**kwargs ทำอะไร
ขณะที่ *args รวบรวมอาร์กิวเมนต์ตามตำแหน่งที่เกินมา **kwargs จะรวบรวมอาร์กิวเมนต์ แบบระบุชื่อ ที่เกินมาไว้ใน ดิกชันนารี (dictionary) โดยคีย์คือชื่ออาร์กิวเมนต์ และค่าคือค่าของอาร์กิวเมนต์
name: Alice age: 25 city: Paris
เช่นเดียวกับ args ชื่อ kwargs เป็นเพียงธรรมเนียม คุณจะเขียน **info, **options หรือ **settings ก็ได้ สิ่งสำคัญคือ ** ซึ่งบอก Python ให้รวบอาร์กิวเมนต์แบบระบุชื่อที่เกินมาไว้ในดิกชันนารี
วิธีนี้มีประโยชน์มากสำหรับฟังก์ชันที่ต้องยืดหยุ่น คุณส่งข้อมูลที่มีชื่อแบบใดก็ได้เข้าไป โดยไม่ต้องแก้นิยามของฟังก์ชัน
Username: alice_99 email: alice@mail.com age: 25 Username: bob_42 city: London
ใช้อาร์กิวเมนต์ทุกแบบร่วมกัน
คุณใช้พารามิเตอร์ธรรมดา ค่าเริ่มต้น *args และ **kwargs ในฟังก์ชันเดียวกันได้ แต่ลำดับมีความสำคัญ Python กำหนดให้เรียงตามลำดับนี้:
*argsdelivery=False ด้านล่าง: เมื่ออยู่หลัง *args จะส่งค่าได้ด้วยการระบุชื่อเท่านั้น**kwargsOrder from: Pizza Palace Items: margherita, garlic bread Delivery: True tip: 5 note: extra cheese
def process(*args, **kwargs):
# What does this expect?
passdef process(data, format='json',
verbose=False):
# Easy to understand
passแบบฝึกหัด
โค้ดนี้แสดงผลอะไร พิมพ์ผลลัพธ์ให้ตรงทุกตัวอักษร
def power(base, exponent=2):
print(base ** exponent)
power(5)
power(2, 10)
power(exponent=3, base=4)เขียนฟังก์ชันชื่อ greet ที่รับพารามิเตอร์ name และพารามิเตอร์ greeting ที่ไม่บังคับ ซึ่งมีค่าเริ่มต้นเป็น 'Hello' ฟังก์ชันต้อง แสดงผล <greeting>, <name>! เช่น greet('Alice') แสดง Hello, Alice! และ greet('Bob', 'Hey') แสดง Hey, Bob!
เขียนฟังก์ชันชื่อ sum_all ที่ใช้ *args รับอาร์กิวเมนต์ตัวเลขกี่ตัวก็ได้ แล้ว คืนค่า ผลรวมของตัวเลขเหล่านั้น ถ้าไม่มีอาร์กิวเมนต์ ให้คืนค่า 0
ฟังก์ชันนี้ควรสร้างลิสต์ใหม่ทุกครั้ง แต่มีบั๊กค่าเริ่มต้นที่เปลี่ยนแปลงได้ ให้แก้ไขเพื่อให้การเรียกแต่ละครั้งเริ่มด้วยลิสต์ใหม่
ผลลัพธ์ที่ต้องการ:
[1]
[2]
[3]เขียนฟังก์ชันชื่อ build_profile ที่รับพารามิเตอร์ name ซึ่งต้องใส่เสมอ และใช้ **kwargs รับข้อมูลเพิ่มเติมอะไรก็ได้ ฟังก์ชันต้อง แสดงผล ชื่อในบรรทัดแรก จากนั้นแสดงคู่คีย์และค่าแต่ละคู่ในบรรทัดถัด ๆ ไปในรูปแบบ key: value (เว้นวรรคนำหน้า 2 ช่อง) แสดงคีย์ตามลำดับที่ส่งเข้ามา
ตัวอย่าง: build_profile('Alice', age=25, city='NYC') ควรแสดง:
Alice
age: 25
city: NYCเขียนฟังก์ชันชื่อ calc ที่รับสตริง operation ('add', 'multiply' หรือ 'max') ตามด้วยอาร์กิวเมนต์ตัวเลขกี่ตัวก็ได้โดยใช้ *args ฟังก์ชันต้อง คืนค่า ผลลัพธ์:
'add': ผลรวมของตัวเลขทั้งหมด (0 ถ้าไม่มีตัวเลข)'multiply': ผลคูณของตัวเลขทั้งหมด (1 ถ้าไม่มีตัวเลข)'max': ตัวเลขที่มากที่สุด (0 ถ้าไม่มีตัวเลข)ตัวอย่าง: calc('add', 1, 2, 3) คืนค่า 6 และ calc('multiply', 2, 3, 4) คืนค่า 24
ปรับโครงสร้าง (refactor) ฟังก์ชันด้านล่างให้ใช้ **kwargs แทนพารามิเตอร์ทีละตัว ฟังก์ชันต้องรับ name เป็นพารามิเตอร์ที่ต้องใส่เสมอ และรับข้อมูลอื่นทั้งหมดผ่าน **kwargs ให้แสดงชื่อ แล้วแสดงรายละเอียดแต่ละรายการคนละบรรทัดในรูปแบบ key: value ผลลัพธ์ต้องตรงกันทุกประการ
โค้ดเดิม:
def show_student(name, grade, school, gpa):
print(name)
print('grade: ' + str(grade))
print('school: ' + school)
print('gpa: ' + str(gpa))ผลลัพธ์ที่ต้องการสำหรับ show_student('Alice', grade=10, school='Lincoln', gpa=3.8):
Alice
grade: 10
school: Lincoln
gpa: 3.8