ลำดับการจับคู่ Parameter ใน function ของ Python
- รับลิงก์
- X
- อีเมล
- แอปอื่นๆ
เราสามารถเขียนอย่างนี้
def func(*args, **kwargs):
pass
✅ ถูกต้อง
แต่
def func(**kwargs, *args):
pass
❌ SyntaxError
เหตุผล
Python ต้องประมวลผล parameter จากซ้ายไปขวา
เมื่อเจอ
*args
Python จะเก็บ positional arguments ที่เหลือทั้งหมด
เช่น
func(1, 2, 3, name="John")
จะกลายเป็น
args = (1, 2, 3)
kwargs = {
"name": "John"
}
จากนั้นจึงเก็บ keyword arguments ลงใน
**kwargs
ถ้าสลับกันจะเกิดอะไรขึ้น?
สมมติ Python อนุญาต
def func(**kwargs, *args):
pass
แล้วเรียก
func(1, 2, 3, name="John")
Python จะงงทันทีว่า
1
2
3
ควรถูกจับโดยใคร?
เพราะ kwargs รับได้เฉพาะ
name=value
เท่านั้น
ดังนั้นจึงไม่มีเหตุผลที่จะวาง kwargs ก่อน
ลำดับที่ถูกต้องจริง ๆ
Python กำหนดไว้เป็น
def func(
positional,
default=None,
*args,
keyword_only=None,
**kwargs
):
pass
ตัวอย่าง
def demo(a, b=0, *args, c=100, **kwargs):
print(a)
print(b)
print(args)
print(c)
print(kwargs)
เรียก
demo(
1,
2,
3,
4,
c=5,
x=10,
y=20
)
ผลลัพธ์
a = 1
b = 2
args = (3, 4)
c = 5
kwargs = {
'x': 10,
'y': 20
}
อีกเหตุผลหนึ่ง: kwargs คือ "ถุงเก็บของสุดท้าย"
ลองนึกภาพ Python กำลังแจกจ่าย parameter
func(1, 2, 3, name="John")
- จับคู่กับ parameter ปกติก่อน
-
ที่เหลือเก็บเข้า
*args - keyword ที่ตรงชื่อ parameter จับคู่ให้
-
keyword ที่เหลือทั้งหมดโยนเข้า
**kwargs
ดังนั้น
**kwargs
จึงต้องอยู่ท้ายสุดเสมอ
เพราะมันคือ
"ถังขยะเก็บทุกอย่างที่ยังไม่ถูกจับ"
ความเจ๋งอีกอย่าง
หลัง *args
def func(*args, name):
print(name)
จะเกิดสิ่งที่เรียกว่า Keyword-only Argument
เรียกแบบนี้ไม่ได้
func(1, 2, 3, "John")
ต้องเขียน
func(1, 2, 3, name="John")
Python ใช้เทคนิคนี้เยอะมากใน Standard Library
ตัวอย่าง
print("a", "b", sep="-")
ตรง
sep="-"
เป็น keyword-only argument
สรุปง่าย ๆ
def func(a, b=0, *args, c=1, **kwargs):
...
Python จะจับค่าตามลำดับนี้
Positional Parameters
↓
Default Parameters
↓
*args
↓
Keyword-only Parameters
↓
**kwargs
โดย **kwargs ต้องอยู่ท้ายสุดเสมอ เพราะเป็นตัวรับ "ทุก keyword ที่เหลือ" หลังจาก Python พยายามจับคู่ parameter อื่น ๆ เรียบร้อยแล้วครับ
นี่เป็นหนึ่งในเหตุผลที่ทำให้ระบบส่ง parameter ของ Python มีความยืดหยุ่นมาก จนสามารถสร้าง Framework อย่าง FastAPI, Django และ Flask ได้อย่างสวยงามครับ
แล้วเราสามารถใส่ *args หรือ **kwargs อย่างเดียว อย่างใดอย่างหนึ่งได้ไหม?
ได้ครับ และในงานจริงก็เจอบ่อยมาก
ใช้เฉพาะ *args
กรณีสนใจรับ Positional Arguments ไม่จำกัดจำนวน
def add(*args):
return sum(args)
print(add(1, 2, 3))
print(add(10, 20, 30, 40))
ผลลัพธ์
6
100
ภายใน
args
จะเป็น Tuple เสมอ
(1, 2, 3)
ใช้เฉพาะ **kwargs
กรณีสนใจรับ Named Arguments แบบยืดหยุ่น
def create_user(**kwargs):
print(kwargs)
create_user(
name="John",
age=25
)
ผลลัพธ์
{
'name': 'John',
'age': 25
}
ภายใน
kwargs
จะเป็น Dictionary เสมอ
ใช้ *args อย่างเดียว แต่ส่ง keyword
def test(*args):
print(args)
test(name="John")
จะ Error
TypeError:
unexpected keyword argument 'name'
เพราะไม่มี **kwargs ไว้รับ
ใช้ **kwargs อย่างเดียว แต่ส่ง positional
def test(**kwargs):
print(kwargs)
test(1, 2, 3)
จะ Error
TypeError
เพราะไม่มี *args ไว้รับ
ชื่อ args และ kwargs เปลี่ยนได้
หลายคนเข้าใจผิดว่าต้องใช้ชื่อเหล่านี้
จริง ๆ แล้วสำคัญที่เครื่องหมาย * และ **
def test(*numbers):
print(numbers)
หรือ
def test(**options):
print(options)
ก็ถูกต้อง
ใน Method ก็ใช้ได้
เช่น Event Bus
class EventBus:
def emit(self, *args):
print(args)
หรือ
class Config:
def update(self, **kwargs):
print(kwargs)
ทำไม Framework ชอบใช้ทั้งคู่
สมมติสร้าง Decorator
def logger(func):
def wrapper(*args, **kwargs):
print("called")
return func(*args, **kwargs)
return wrapper
แบบนี้รองรับทุกกรณี
func()
func(1)
func(1, 2)
func(name="John")
func(1, 2, name="John")
โดยไม่ต้องรู้ล่วงหน้าว่าฟังก์ชันต้นฉบับรับอะไร
เกร็ดที่หลายคนไม่รู้
ถ้าเขียน
def test(*):
pass
❌ ไม่ได้
เพราะ * ต้องมีหน้าที่บางอย่าง
แต่เขียนแบบนี้ได้
def test(*, name, age):
print(name, age)
เรียกว่า Keyword-only Arguments
ต้องเรียก
test(name="John", age=25)
ห้าม
test("John", 25)
สรุปสั้น ๆ
| รูปแบบ | รับอะไร |
|---|---|
*args | Positional Arguments → Tuple |
**kwargs | Keyword Arguments → Dict |
*args, **kwargs | รับได้ทุกแบบ |
* | บังคับให้ parameter หลังจากนี้เป็น keyword-only |
ดังนั้น
def func(*args):
หรือ
def func(**kwargs):
หรือ
def func(*args, **kwargs):
ล้วนใช้ได้หมด ขึ้นอยู่กับว่าฟังก์ชันนั้นต้องการความยืดหยุ่นในการรับข้อมูลแบบไหนครับ
- รับลิงก์
- X
- อีเมล
- แอปอื่นๆ
ความคิดเห็น
แสดงความคิดเห็น