# ฟังก์ชันใน Python: def, return, อาร์กิวเมนต์, *args และ **kwargs, lambda และ annotation

URL: https://skillaria.top/th/guides/python/python-functions

ฟังก์ชันใน Python คือบล็อกโค้ดที่มีชื่อ ประกาศด้วยคำว่า def เรียกใช้ด้วยชื่อพร้อมวงเล็บ และส่งผลลัพธ์กลับด้วย return หากไม่มี return ฟังก์ชันจะคืนค่า None อาร์กิวเมนต์ส่งตามลำดับหรือระบุชื่อได้ *args รวมค่าตำแหน่งที่เหลือเป็นทูเพิล **kwargs รวมค่าแบบชื่อเป็นดิกชันนารี และ lambda สร้างฟังก์ชันไม่มีชื่อจากนิพจน์เดียว

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

## การประกาศฟังก์ชันด้วย def

การประกาศฟังก์ชันเริ่มด้วย `def` ตามด้วยชื่อ พารามิเตอร์ในวงเล็บ และเครื่องหมายโคลอน เนื้อหาของฟังก์ชันเขียนโดยเยื้อง 4 ช่องว่าง การเยื้องไม่ใช่แค่ความสวยงาม แต่เป็นส่วนหนึ่งของไวยากรณ์

```
def greet(name):
    """คืนข้อความทักทายสำหรับผู้ใช้"""
    return f"สวัสดี {name}!"

print(greet("อนงค์"))   # สวัสดี อนงค์!
```

ข้อความในเครื่องหมายคำพูดสามชั้นที่อยู่ใต้บรรทัดประกาศคือ docstring หรือเอกสารของฟังก์ชัน ซึ่ง `help(greet)` และคำแนะนำของโปรแกรมแก้ไขโค้ดจะแสดงให้เห็น ชื่อฟังก์ชันตามข้อกำหนด PEP 8 ใช้ snake_case เช่น `add_expense` หรือ `get_rate`

บรรทัด `def greet(name):` เรียกว่า signature ของฟังก์ชัน ซึ่งประกอบด้วยชื่อและรายการพารามิเตอร์ (และ annotation หากมี) จาก signature ผู้อ่านรู้วิธีเรียกใช้ฟังก์ชันได้โดยไม่ต้องดูเนื้อหาข้างใน

## return ใน Python ทำอะไร

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

```
def area(w, h):
    w * h          # คำนวณแล้วแต่ไม่ได้ส่งออก

print(area(3, 4))  # None

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

print(area(3, 4))  # 12
```

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

```
def min_max(numbers):
    return min(numbers), max(numbers)

low, high = min_max([4, 1, 9])
print(low, high)   # 1 9
```

การ return ก่อนกำหนด (early return) ช่วยลดการซ้อนของโค้ด โดยจัดการกรณีพิเศษก่อน แล้วจึงเขียนตรรกะหลัก

```
def discount(price, is_member):
    if price  limit    # อ่านตัวแปร global ได้

def raise_limit():
    global limit
    limit = 2000             # ถ้าไม่มี global จะสร้างตัวแปร local ใหม่
```

ในทางปฏิบัติ ควรหลีกเลี่ยง `global` เพราะฟังก์ชันที่เปลี่ยนสถานะภายนอกจะทดสอบยาก วิธีที่ดีกว่าคือรับค่าผ่านอาร์กิวเมนต์และคืนผลลัพธ์กลับมา

## ฟังก์ชันซ้อนและ closure

สามารถประกาศฟังก์ชันไว้ในฟังก์ชันอื่นได้ ฟังก์ชันซ้อนจะมองเห็นตัวแปรของฟังก์ชันภายนอกแม้ฟังก์ชันภายนอกจะทำงานจบแล้ว สิ่งนี้เรียกว่า closure และหากต้องการแก้ไขตัวแปรเหล่านั้น ให้ใช้ `nonlocal`

```
def make_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

c = make_counter()
print(c(), c(), c())   # 1 2 3
```

Closure เป็นพื้นฐานของ decorator และการสร้างฟังก์ชันจากโรงงาน (function factory)

## ฟังก์ชันคือออบเจกต์

ใน Python ฟังก์ชันเป็นออบเจกต์ธรรมดา สามารถเก็บไว้ในตัวแปร ส่งเป็นอาร์กิวเมนต์ให้ฟังก์ชันอื่น คืนค่าจากฟังก์ชัน หรือเก็บไว้ในดิกชันนารีได้ ฟังก์ชันมีแอตทริบิวต์ เช่น `__name__` `__doc__` `__defaults__` และ `__annotations__`

```
def double(x):
    return x * 2

f = double
print(f(5), f.__name__)                # 10 double
print(list(map(double, [1, 2, 3])))   # [2, 4, 6]
```

## lambda ใน Python: ฟังก์ชันไม่มีชื่อ

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

```
square = lambda x: x ** 2
print(square(4))   # 16

expenses = [("กาแฟ", 250), ("แท็กซี่", 600), ("อาหารกลางวัน", 450)]
expenses.sort(key=lambda e: e[1], reverse=True)
print(expenses)  # [('แท็กซี่', 600), ('อาหารกลางวัน', 450), ('กาแฟ', 250)]

print(list(filter(lambda n: n % 2 == 0, range(10))))  # [0, 2, 4, 6, 8]
```

lambda เหมาะเป็นอาร์กิวเมนต์สั้น ๆ ของ `sorted` `max` `map` และ `filter` แต่ PEP 8 ไม่แนะนำให้นำ lambda ไปกำหนดให้ตัวแปร เช่น `square` ด้านบน หากต้องการชื่อให้ใช้ `def` เพราะจะได้ `__name__` ที่มีความหมายในข้อความ traceback และมีที่สำหรับเขียน docstring

|  | def | lambda |
| --- | --- | --- |

| ชื่อ | มี | ไม่มี (`<lambda>`) |

| เนื้อหา | คำสั่งกี่บรรทัดก็ได้ | นิพจน์เดียว |

| return | เขียนเอง | อัตโนมัติ |

| Docstring และ annotation | ได้ | ไม่ได้ |

| เหมาะกับ | ทุกกรณี | key หรือ callback สั้น ๆ |

## Annotation ของชนิดข้อมูล: คำแนะนำ ไม่ใช่การตรวจสอบ

Annotation บอกชนิดข้อมูลที่คาดหวังของพารามิเตอร์และผลลัพธ์ Python ไม่ได้ตรวจสิ่งเหล่านี้ขณะรันโปรแกรม ฟังก์ชันที่ระบุ `x: int` ยังรับสตริงได้ การตรวจทำโดยเครื่องมือภายนอก เช่น mypy, pyright หรือ ty และโปรแกรมแก้ไขโค้ดที่ขีดเส้นใต้ข้อผิดพลาดก่อนรัน

```
def add_expense(title: str, amount: float, category: str = "อื่น ๆ") -> dict[str, object]:
    return {"title": title, "amount": amount, "category": category}

def area(w: float, h: float) -> float:
    w * h   # mypy: Missing return statement
```

รันการตรวจได้โดยไม่ต้องติดตั้งด้วยคำสั่ง `uvx mypy .` ตัวอย่างข้างต้นแสดงประโยชน์ของ annotation เมื่อลืม `return` ในฟังก์ชันที่ระบุ `-> float` เครื่องมือจะจับได้ทันที

ตั้งแต่ Python 3.12 (PEP 695) ฟังก์ชันทั่วไป (generic) เขียนได้โดยไม่ต้องใช้ `TypeVar` พารามิเตอร์ชนิดข้อมูลวางไว้ในวงเล็บเหลี่ยม และนามแฝงของชนิดข้อมูลประกาศด้วยคำว่า `type`

```
def first[T](xs: list[T]) -> T:
    return xs[0]

type Money = int | float
```

ใน Python 3.14 (PEP 649) annotation กลายเป็นแบบ lazy คือคำนวณเมื่อมีการเข้าถึงครั้งแรก ดังนั้นอ้างอิงคลาสที่ประกาศไว้ด้านล่างได้โดยไม่ต้องใส่เครื่องหมายคำพูด และไม่จำเป็นต้องใช้ `from __future__ import annotations` อีกต่อไป

## "ไม่ได้ส่งค่า" หรือ "ส่ง None": sentinel ใน 3.15

การใช้ `None` เป็นค่าเริ่มต้นทำให้แยกไม่ออกระหว่าง "ไม่ได้ส่งอาร์กิวเมนต์มา" กับ "ตั้งใจส่ง None" มาหลายปีที่แก้ปัญหานี้ด้วยการสร้างตัวทำเครื่องหมายเอง:

```
_MISSING = object()

def get_rate(currency, default=_MISSING):
    rates = {"USD": 90.5}
    if currency in rates:
        return rates[currency]
    if default is _MISSING:
        raise KeyError(currency)
    return default
```

ใน Python 3.15 มีฟังก์ชันในตัวชื่อ `sentinel` (PEP 661): `MISSING = sentinel("MISSING")` สร้างตัวทำเครื่องหมายที่ไม่ซ้ำกัน มี repr ที่อ่านเข้าใจได้ คงความเป็นตัวเองเมื่อคัดลอก serialize ผ่าน pickle ได้ และนำไปใช้ใน annotation เช่น `float | MISSING` ได้ ส่วนเวอร์ชันเก่ายังใช้วิธี `object()` ได้ตามเดิม

## ข้อผิดพลาดที่พบบ่อยกับฟังก์ชัน

- **ลืม return** ฟังก์ชันจะคืน `None`
- **ลืมวงเล็บ** `print(greet)` จะแสดงออบเจกต์ของฟังก์ชัน ไม่ใช่ผลของการเรียก
- **ค่าเริ่มต้นที่แก้ไขได้** ทำให้ลิสต์ถูกใช้ร่วมกันทุกการเรียก
- **จำนวนอาร์กิวเมนต์ไม่ถูกต้อง** เช่น `TypeError: greet() missing 1 required positional argument: 'name'`
- **ตั้งชื่อซ้ำกับชื่อในตัว** เช่น ประกาศฟังก์ชันชื่อ `list` หรือ `sum` จะบดบังฟังก์ชันในตัว

## สรุป

ฟังก์ชันใน Python ประกาศด้วย `def` คืนผลลัพธ์ด้วย `return` และรับอาร์กิวเมนต์ตามลำดับหรือระบุชื่อ `*args` และ `**kwargs` ให้ความยืดหยุ่น ส่วน `/` และ `*` ให้ความเข้มงวดในการเรียก lambda ให้ความกระชับสำหรับฟังก์ชันใช้ครั้งเดียว และ annotation ร่วมกับ mypy จับข้อผิดพลาดก่อนรันโปรแกรม ในคอร์สเรื่องนี้ถูกสอนแบบลงมือทำ โดยแบ่งโปรแกรมติดตามค่าใช้จ่ายออกเป็นฟังก์ชันที่มี annotation และตรวจสอบด้วย mypy

## FAQ

**พารามิเตอร์ต่างจากอาร์กิวเมนต์อย่างไร?**

พารามิเตอร์คือชื่อที่ประกาศในฟังก์ชัน ส่วนอาร์กิวเมนต์คือค่าที่ส่งเข้ามาตอนเรียกใช้ ใน def greet(name) คำว่า name คือพารามิเตอร์ และใน greet("อนงค์") สตริง "อนงค์" คืออาร์กิวเมนต์

**เรียกฟังก์ชันก่อนประกาศได้หรือไม่?**

ในระดับโมดูลไม่ได้ เพราะคำสั่ง def ต้องทำงานก่อนการเรียก มิฉะนั้นจะเกิด NameError แต่หากเรียกจากภายในฟังก์ชันอื่น ก็เรียกฟังก์ชันที่ประกาศไว้ด้านล่างได้ ขอเพียงการเรียกเกิดขึ้นหลังจากการประกาศ

**ฟังก์ชันคืนได้กี่ค่า?**

ตามหลักแล้วคืนได้หนึ่งค่า แต่ค่านั้นอาจเป็นทูเพิลก็ได้ return a, b คืนทูเพิลสองตัว ซึ่งแตกค่าได้ด้วย x, y = f()

**ต้องเขียน annotation ทุกครั้งหรือไม่?**

ไม่จำเป็น Python ทำงานได้โดยไม่มี annotation แต่ในโค้ดที่คนอื่นต้องอ่านและดูแล annotation ช่วยให้บำรุงรักษาง่ายขึ้นมาก และทำให้ mypy หรือ pyright จับข้อผิดพลาดได้ก่อนรัน

**ใน lambda เขียน if ได้หรือไม่?**

ได้เฉพาะนิพจน์เงื่อนไข เช่น lambda x: "คู่" if x % 2 == 0 else "คี่" ส่วนคำสั่ง ลูป และการกำหนดค่าภายใน lambda ไม่ได้


---
Source: Skillaria.Top (https://skillaria.top)
