ข้ามไปยังเนื้อหาหลัก
Editor

อาร์กิวเมนต์ฟังก์ชันใน Python: *args, **kwargs และค่าเริ่มต้น

ปานกลาง25 นาที7 แบบฝึกหัด105 XP

บทเรียนนี้แปลจากต้นฉบับภาษาอังกฤษRead in EnglishLeer en españolЧитать на русскомLer em portuguêsแจ้งข้อผิดพลาดในการแปล

0/7 แบบฝึกหัด

ลองนึกถึงการสั่งกาแฟ คุณต้องเลือกขนาดเสมอ นั่นเป็นสิ่งที่ขาดไม่ได้ แต่ของเพิ่มอย่างนมโอ๊ต ช็อตกาแฟเพิ่ม หรือซอสคาราเมลราดหน้าล่ะ สิ่งเหล่านั้นไม่บังคับ คุณจะบอกก็ต่อเมื่ออยากได้เท่านั้น

อาร์กิวเมนต์ของฟังก์ชันใน Python ก็ทำงานแบบเดียวกัน บางตัวต้องใส่เสมอ บางตัวมีค่าเริ่มต้นที่เหมาะสมอยู่แล้ว และบางแบบก็ให้ส่งของเพิ่มเข้าไปได้มากเท่าที่ต้องการ การเข้าใจรูปแบบเหล่านี้อย่างถ่องแท้คือสิ่งที่แยกผู้เริ่มต้นออกจากคนที่เขียนโค้ดได้ยืดหยุ่นจริง ๆ

ในบทเรียนนี้ คุณจะได้เรียนรู้ทุกวิธีที่ Python ใช้ส่งข้อมูลเข้าฟังก์ชัน ตั้งแต่อาร์กิวเมนต์ตามตำแหน่งขั้นพื้นฐาน ไปจนถึงรูปแบบ *args และ **kwargs อันทรงพลังที่ใช้ในไลบรารีจริง

อาร์กิวเมนต์ตามตำแหน่งกับอาร์กิวเมนต์แบบระบุชื่อ

เมื่อเรียกฟังก์ชัน Python จับคู่อาร์กิวเมนต์กับพารามิเตอร์ได้สองแบบ อาร์กิวเมนต์ ตามตำแหน่ง (positional) จับคู่ตามลำดับ อาร์กิวเมนต์ตัวแรกไปที่พารามิเตอร์ตัวแรก ส่วนอาร์กิวเมนต์ แบบระบุชื่อ (keyword) จับคู่ตามชื่อ

อาร์กิวเมนต์ตามตำแหน่งกับแบบระบุชื่อ
def describe_pet(animal, name):
print(name + ' is a ' + animal)
# Positional: order matters
describe_pet('cat', 'Whiskers')
# Keyword: order doesn't matter
describe_pet(name='Buddy', animal='dog')
ผลลัพธ์
Whiskers is a cat
Buddy is a dog

อาร์กิวเมนต์แบบระบุชื่อเขียนในรูป parameter_name=value ทำให้การเรียกฟังก์ชันอธิบายตัวเองได้ ใครอ่านโค้ดก็เห็นชัดว่าแต่ละค่าหมายถึงอะไร โดยไม่ต้องไปดูนิยามของฟังก์ชัน

คุณใช้ทั้งสองแบบผสมกันในการเรียกครั้งเดียวได้ แต่อาร์กิวเมนต์ตามตำแหน่งต้องมาก่อนอาร์กิวเมนต์แบบระบุชื่อ

ผสมแบบตามตำแหน่งกับแบบระบุชื่อ
def describe_pet(animal, name, age):
print(name + ' is a ' + str(age) + '-year-old ' + animal)
# Positional first, then keyword
describe_pet('cat', name='Whiskers', age=3)
ผลลัพธ์
Whiskers is a 3-year-old cat

ค่าเริ่มต้นทำให้ฟังก์ชันยืดหยุ่นได้อย่างไร

ค่าเริ่มต้น (default value) ทำให้กำหนดพารามิเตอร์ที่ไม่บังคับได้ ถ้าผู้เรียกส่งค่ามา ก็ใช้ค่านั้น ถ้าไม่ส่ง ก็ใช้ค่าเริ่มต้นแทน

ค่าเริ่มต้นในการใช้งานจริง
def make_coffee(size, milk='regular', sugar=0):
order = size + ' coffee'
if milk != 'regular':
order += ' with ' + milk + ' milk'
if sugar > 0:
order += ', ' + str(sugar) + ' sugar(s)'
print(order)
make_coffee('large')
make_coffee('medium', 'oat')
make_coffee('small', 'almond', 2)
ผลลัพธ์
large coffee
medium coffee with oat milk
small coffee with almond milk, 2 sugar(s)

พารามิเตอร์ size ไม่มีค่าเริ่มต้น จึงต้องใส่เสมอ ส่วนพารามิเตอร์ milk และ sugar มีค่าเริ่มต้น จึงไม่บังคับ ไลบรารี Python จำนวนมากทำงานแบบนี้เลย คือมีพารามิเตอร์บังคับไม่กี่ตัว และพารามิเตอร์ไม่บังคับอีกหลายตัวที่มีค่าเริ่มต้นเหมาะสม

กับดักค่าเริ่มต้นที่เปลี่ยนแปลงได้

นี่คือหนึ่งในจุดหลอกที่ขึ้นชื่อที่สุดของ Python เมื่อใช้ออบเจกต์ที่เปลี่ยนแปลงได้ (เช่น ลิสต์) เป็นค่าเริ่มต้น ออบเจกต์นั้นจะถูกสร้าง ครั้งเดียว ตอนนิยามฟังก์ชัน ไม่ใช่ทุกครั้งที่เรียกฟังก์ชัน

แปลว่าการเรียกทุกครั้งใช้ลิสต์ เดียวกัน การเปลี่ยนแปลงจากการเรียกครั้งหนึ่งจะติดไปถึงครั้งถัดไป มาดูตัวอย่างกัน:

กับดักค่าเริ่มต้นที่เปลี่ยนแปลงได้
def add_item(item, shopping_list=[]):
shopping_list.append(item)
return shopping_list
print(add_item('apples'))
print(add_item('bread'))
print(add_item('milk'))
ผลลัพธ์
['apples']
['apples', 'bread']
['apples', 'bread', 'milk']

แปลกใจไหม การเรียกแต่ละครั้งควรเริ่มด้วยลิสต์ใหม่ แต่ลิสต์กลับยาวขึ้นเรื่อย ๆ เพราะ [] ถูกสร้างครั้งเดียวตอนนิยามฟังก์ชัน และถูกใช้ซ้ำในทุกการเรียก

วิธีแก้คือรูปแบบที่รู้จักกันดีใน Python: ใช้ None เป็นค่าเริ่มต้น แล้วสร้างลิสต์ใหม่ภายในฟังก์ชัน

วิธีแก้: ใช้ None เป็นค่าเริ่มต้น
def add_item(item, shopping_list=None):
if shopping_list is None:
shopping_list = []
shopping_list.append(item)
return shopping_list
print(add_item('apples'))
print(add_item('bread'))
print(add_item('milk'))
ผลลัพธ์
['apples']
['bread']
['milk']

*args ทำอะไร

บางครั้งคุณไม่รู้ล่วงหน้าว่าฟังก์ชันจะได้รับอาร์กิวเมนต์กี่ตัว เช่น อาจต้องการฟังก์ชันที่บวกตัวเลขได้ไม่จำกัดจำนวน ไม่ว่าจะ 2, 5 หรือ 100 ตัว

รูปแบบ *args รวบรวมอาร์กิวเมนต์ตามตำแหน่งที่เกินมาทั้งหมดไว้ใน ทูเพิล (tuple) จากนั้นคุณจะวนลูป ใช้ดัชนี หรือทำอะไรก็ได้ที่ทำกับทูเพิลได้

ใช้ *args รับอาร์กิวเมนต์กี่ตัวก็ได้
def add_all(*numbers):
total = 0
for n in numbers:
total += n
return total
print(add_all(1, 2, 3))
print(add_all(10, 20, 30, 40))
print(add_all(5))
ผลลัพธ์
6
100
5

ชื่อ args เป็นเพียงธรรมเนียมที่นิยมใช้ คุณจะเขียน *numbers, *items หรือ *values ก็ได้ ความพิเศษอยู่ที่ * ซึ่งบอก Python ให้รวบอาร์กิวเมนต์ตามตำแหน่งที่เหลือทั้งหมดไว้ในทูเพิล

ดูข้างใน *args
def show_args(*args):
print(type(args))
print(args)
for i, arg in enumerate(args):
print('Argument ' + str(i) + ': ' + str(arg))
show_args('hello', 42, True)
ผลลัพธ์
<class 'tuple'>
('hello', 42, True)
Argument 0: hello
Argument 1: 42
Argument 2: True

คุณใช้ *args ร่วมกับพารามิเตอร์ธรรมดาได้ พารามิเตอร์ธรรมดาจะรับค่าไปก่อน แล้ว *args จะรวบรวมทุกอย่างที่เหลือ

พารามิเตอร์ธรรมดา + *args
def greet(greeting, *names):
for name in names:
print(greeting + ', ' + name + '!')
greet('Hello', 'Alice', 'Bob', 'Charlie')
ผลลัพธ์
Hello, Alice!
Hello, Bob!
Hello, Charlie!

**kwargs ทำอะไร

ขณะที่ *args รวบรวมอาร์กิวเมนต์ตามตำแหน่งที่เกินมา **kwargs จะรวบรวมอาร์กิวเมนต์ แบบระบุชื่อ ที่เกินมาไว้ใน ดิกชันนารี (dictionary) โดยคีย์คือชื่ออาร์กิวเมนต์ และค่าคือค่าของอาร์กิวเมนต์

ใช้ **kwargs
def build_profile(**info):
for key, value in info.items():
print(key + ': ' + str(value))
build_profile(name='Alice', age=25, city='Paris')
ผลลัพธ์
name: Alice
age: 25
city: Paris

เช่นเดียวกับ args ชื่อ kwargs เป็นเพียงธรรมเนียม คุณจะเขียน **info, **options หรือ **settings ก็ได้ สิ่งสำคัญคือ ** ซึ่งบอก Python ให้รวบอาร์กิวเมนต์แบบระบุชื่อที่เกินมาไว้ในดิกชันนารี

วิธีนี้มีประโยชน์มากสำหรับฟังก์ชันที่ต้องยืดหยุ่น คุณส่งข้อมูลที่มีชื่อแบบใดก็ได้เข้าไป โดยไม่ต้องแก้นิยามของฟังก์ชัน

พารามิเตอร์ธรรมดา + **kwargs
def create_user(username, **details):
print('Username: ' + username)
for key, value in details.items():
print(' ' + key + ': ' + str(value))
create_user('alice_99', email='alice@mail.com', age=25)
print()
create_user('bob_42', city='London')
ผลลัพธ์
Username: alice_99
  email: alice@mail.com
  age: 25

Username: bob_42
  city: London

ใช้อาร์กิวเมนต์ทุกแบบร่วมกัน

คุณใช้พารามิเตอร์ธรรมดา ค่าเริ่มต้น *args และ **kwargs ในฟังก์ชันเดียวกันได้ แต่ลำดับมีความสำคัญ Python กำหนดให้เรียงตามลำดับนี้:

  • พารามิเตอร์ตามตำแหน่งแบบธรรมดา
  • พารามิเตอร์ที่มีค่าเริ่มต้น
  • *args
  • พารามิเตอร์ที่ต้องระบุชื่อเท่านั้น (keyword-only) เช่น delivery=False ด้านล่าง: เมื่ออยู่หลัง *args จะส่งค่าได้ด้วยการระบุชื่อเท่านั้น
  • **kwargs
  • อาร์กิวเมนต์ทุกแบบรวมกัน
    def order_food(restaurant, *items, delivery=False, **extras):
    print('Order from: ' + restaurant)
    print('Items: ' + ', '.join(items))
    print('Delivery: ' + str(delivery))
    if extras:
    for key, value in extras.items():
    print(' ' + key + ': ' + str(value))
    order_food('Pizza Palace', 'margherita', 'garlic bread',
    delivery=True, tip=5, note='extra cheese')
    ผลลัพธ์
    Order from: Pizza Palace
    Items: margherita, garlic bread
    Delivery: True
      tip: 5
      note: extra cheese
    ยืดหยุ่นเกินไป (เข้าใจยาก)
    def process(*args, **kwargs):
        # What does this expect?
        pass
    ชัดเจนและตรงไปตรงมา
    def 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)
    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!

    # Define your function here
    # Test it
    greet('Alice')
    greet('Bob', 'Hey')
    หาผลรวมของตัวเลขทั้งหมด
    เขียนโค้ด

    เขียนฟังก์ชันชื่อ sum_all ที่ใช้ *args รับอาร์กิวเมนต์ตัวเลขกี่ตัวก็ได้ แล้ว คืนค่า ผลรวมของตัวเลขเหล่านั้น ถ้าไม่มีอาร์กิวเมนต์ ให้คืนค่า 0

    # Define your function here
    # Test it
    print(sum_all(1, 2, 3))
    print(sum_all(10, 20))
    print(sum_all())
    แก้ค่าเริ่มต้นที่เปลี่ยนแปลงได้
    แก้บั๊ก

    ฟังก์ชันนี้ควรสร้างลิสต์ใหม่ทุกครั้ง แต่มีบั๊กค่าเริ่มต้นที่เปลี่ยนแปลงได้ ให้แก้ไขเพื่อให้การเรียกแต่ละครั้งเริ่มด้วยลิสต์ใหม่

    ผลลัพธ์ที่ต้องการ:

    [1]
    [2]
    [3]
    def append_to(value, items=[]):
    items.append(value)
    return items
    print(append_to(1))
    print(append_to(2))
    print(append_to(3))
    สร้างสตริงโปรไฟล์
    เขียนโค้ด

    เขียนฟังก์ชันชื่อ build_profile ที่รับพารามิเตอร์ name ซึ่งต้องใส่เสมอ และใช้ **kwargs รับข้อมูลเพิ่มเติมอะไรก็ได้ ฟังก์ชันต้อง แสดงผล ชื่อในบรรทัดแรก จากนั้นแสดงคู่คีย์และค่าแต่ละคู่ในบรรทัดถัด ๆ ไปในรูปแบบ key: value (เว้นวรรคนำหน้า 2 ช่อง) แสดงคีย์ตามลำดับที่ส่งเข้ามา

    ตัวอย่าง: build_profile('Alice', age=25, city='NYC') ควรแสดง:

    Alice
      age: 25
      city: NYC
    # Define your function here
    # Test it
    build_profile('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

    # Define your function here
    # Test it
    print(calc('add', 1, 2, 3))
    print(calc('multiply', 2, 3, 4))
    print(calc('max', 5, 2, 8, 1))
    ปรับโค้ดให้ใช้ **kwargs
    ปรับปรุงโค้ด

    ปรับโครงสร้าง (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
    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)

    บทเรียนที่เกี่ยวข้อง

    บทเรียนนี้มีประโยชน์หรือไม่