ลำดับการจับคู่ Parameter ใน function ของ Python

เราสามารถเขียนอย่างนี้

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")
  1. จับคู่กับ parameter ปกติก่อน
  2. ที่เหลือเก็บเข้า *args
  3. keyword ที่ตรงชื่อ parameter จับคู่ให้
  4. 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)

สรุปสั้น ๆ

รูปแบบรับอะไร
*argsPositional Arguments → Tuple
**kwargsKeyword Arguments → Dict
*args, **kwargsรับได้ทุกแบบ
*บังคับให้ parameter หลังจากนี้เป็น keyword-only

ดังนั้น

def func(*args):

หรือ

def func(**kwargs):

หรือ

def func(*args, **kwargs):

ล้วนใช้ได้หมด ขึ้นอยู่กับว่าฟังก์ชันนั้นต้องการความยืดหยุ่นในการรับข้อมูลแบบไหนครับ

ความคิดเห็น

โพสต์ยอดนิยมจากบล็อกนี้

รับติดตั้งระบบป้ายโฆษณาดิจิตอล Digital Signage ตั้งเวลาทำงานและควบคุมผ่าน Internet ได้

แจกฟรี ระบบแสดงคิวงาน สำหรับร้านนวด สปา หรือ ร้านทำผม

บริการออกแบบ Character Agent สำหรับธุรกิจ

ป้ายดิจิตอลที่โต้ตอบได้