Data Warehouse

การสร้างฟังก์ชัน

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

พอทำแบบนี้ก็จะประหยัดแรงในการเขียน โค้ดโดยรวมจะดูสั้นลงมาก และเข้าใจง่ายขึ้น การสร้างฟังก์ชัน จะทำให้การเขียนโปรแกรมของเราดูมีระบบระเบียบขึ้นมามาก.

การสร้างและเรียกใช้ฟังก์ชัน

ฟังก์ชันในทางภาษาคอมพิวเตอร์ก็คล้ายกับฟังก์ชันในทางคณิตศาสตร์ คือใส่อาร์กิวเมนต์เข้าไปแล้วได้ผลลัพธ์เป็นค่าอะไรบางอย่างคืนกลับออกมา ตัวอย่างการสร้างฟังก์ชันและใช้งาน

def  f(x):  # นิยามฟังก์ชัน f ที่มีพารามิเตอร์เป็น x  
return  x**3+3*x**2+3*x+1  # ค่าคืนกลับของฟังก์ชัน  
  
print(f(7))  # เรียกใช้ฟังก์ชัน ได้ 512  
print(f(13))  # เรียกใช้ฟังก์ชัน ได้ 2744

คำสั่งที่ใช้ในการสร้างฟังก์ชันก็คือ def ซึ่งก็ย่อมาจาก definition ซึ่งแปลว่าการนิยามนั่นเอง คำสั่งนี้มีไว้นิยามฟังก์ชันขึ้นมา จากนั้นหลังคำว่า def ก็ใส่ชื่อของฟังก์ชันที่ต้องการ ในที่นี้ตั้งชื่อง่ายๆว่า f จากนั้นก็ตามด้วยวงเล็บซึ่งภายในบรรจุสิ่งที่เรียกว่า พารามิเตอร์ (parameter) ซึ่งก็คือตัวแปรที่จะรับเข้ามาและเป็นตัวกำหนดอะไรต่างๆภายในฟังก์ชัน

การตั้งชื่อฟังก์ชันเป็นไปตามกฏการตั้งชื่อตัวแปรทั่วไป

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

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

ในตัวอย่างนี้ใส่ค่าอาร์กิวเมนต์เป็น 7 จากนั้นค่านี้จะถูกนำไปแทนพารามิเตอร์ x ภายในฟังก์ชัน แล้วก็ถูกนำไปคำนวณแล้วได้ค่า 7**3+3*7**2+3*7+1 = 512 กลับออกมา หลังจากนั้นพอใส่อาร์กิวเมนต์เป็น 13 ก็ทำในลักษณะเดียวกัน แต่จะได้ผลลัพธ์ต่างกัน

พารามิเตอร์อาจไม่จำเป็นต้องมีเลยก็ได้ ถ้าไม่ต้องการรับค่าอะไรมาใช้ในฟังก์ชัน แบบนั้นวงเล็บหลังชื่อฟังก์ชันก็เว้นว่างเป็น f() หรืออาจมีหลายตัว ซึ่งก็จะคั่นด้วยจุลภาค โดยที่เวลาเรียกใช้จะต้องใส่ลำดับของอาร์กิวเมนต์ให้ตรงกับพารามิเตอร์ที่สอดคล้องกันด้วย ตัวอย่าง สมการในทฤษฎีสัมพัทธภาพพิเศษของไอนสไตน์ E = mc^2 พลังงานเท่ากับมวลคูณความเร็วแสงกำลังสอง

def  E(m,c):  
return  m*c**2  
  
m = 9.10938356e-31  # มวลอิเล็กตรอน  
c = 2.99792458e8  # ความเร็วแสง  
print(E(m,c))  # พลังงานจากมวลของอิเล็กตรอน

ในการป้อนค่าให้พารามิเตอร์นั้นนอกจากใส่ในรูปแบบของอาร์กิวเมนต์แล้วก็ยังสามารถใส่ในรูปแบบคีย์เวิร์ดได้ด้วย

print(E(m=9.10938356e-31,c=2.99792458e8))

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

print(E(c=2.99792458e8,m=9.10938356e-31))

หรือจะใส่ปนกันทั้งคีย์และอาร์กิวเมนต์ก็ได้ แต่ว่าอาร์กิวเมนต์ต้องขึ้นก่อนเสมอ

print(E(m,c=2.99792458e8))

แต่จะไม่สามารถใส่ตัวแรกเป็นคีย์เวิร์ดและตัวที่สองเป็นอาร์กิวเมนต์ได้ เพราะถ้ามีอาร์กิวเมนต์อยู่สักตัวจะถูกตีความว่าเป็นพารามิเตอร์ตัวแรกสุดก่อนแล้วไล่ลำดับมา

print(E(2.99792458e8,m=9.10938356e-31))  
# ได้ TypeError: E() got multiple values for argument 'm'

กรณีนี้ 2.99792458e8 ถูกตีความเป็นค่า m ซึ่งเป็นพารามิเตอร์ตัวแรก พอมีการใส่คีย์เวิร์ด m= ลงไปอีกจึงกลายเป็นว่าได้ค่าซ้อนกัน จึงขัดข้องทันที ฟังก์ชันอาจไม่จำเป็นต้องมีการคืนค่าเสมอไป หากไม่มีการคืนค่าก็ไม่ต้องใส่คำสั่ง return ตัวอย่าง ฟังก์ชันที่จะพิมพ์ดอกจันตามจำนวนที่ป้อนเข้าไป

def  daodaodao(x,y):  # นิยามฟังก์ชัน พารามิเตอร์คือจำนวนดาวในแนวนอนและแนวตั้งตามลำดับ  
for  i  in  range(y):  
for  j  in  range(x):  
print('*',end='')  # พิมพ์ดอกจัน  
print('')  # ขึ้นบรรทัดใหม่  
  
daodaodao(30,10)  # เรียกใช้ สร้างดอกจันแถวละ ๓๐ ดอก จำนวน ๑๐ แถว

การแตกลิสต์มาใช้เป็นอาร์กิวเมนต์ของฟังก์ชัน

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

def  f(x,y,z):  
print((x**2+y**2+z**2)**0.5)  
  
xyz = [3,4,12]  
f(*xyz)  # แทนที่จะต้องมาเขียน f(xyz[0],xyz[1],xyz[2])  
# ได้ 13.0

จะใส่ลิสต์ปนกับข้อมูลเดี่ยวแบบนี้ก็ได้เช่นกัน

def  f(x,y,z,t):  
print((x**2+y**2+z**2+t**2)**0.5)  
  
xyz = [3,4,12]  
t = 20  
f(*xyz,t)

วิธีการนี้จะใช้กับฟังก์ชันอะไรก็ได้ ไม่เพียงแต่ฟังก์ชันที่เราสร้างขึ้นเอง เช่นฟังก์ชัน print เองก็สามารถใช้ตัวแปรลิสต์ที่มีดอกจัน

การใช้ดิกชันนารีเป็นคีย์เวิร์ด

ในขณะที่ลิสต์สามารถใช้เป็นอาร์กิวเมนต์ได้ ดิกชันนารีก็สามารถใช้เป็นคีย์เวิร์ดได้ ซึ่งการใช้นั้นทำได้โดยใส่ดอกจันสองอันนำหน้าตัวแปรดิกชันนารี

def  f(x,y,z):  
print((x**2+y**2+z**2)**0.5)  
  
xyz = {'x':3,'y':4,'z':12}  
f(**xyz)  # แทนที่จะใส่ f(xyz['x'],xyz['y'],xyz['z'])  
# ได้ 13.0

ในที่นี้ดิกชันนารี xyz มีคีย์เป็น x, y และ z คีย์แต่ละอันจะกลายมาเป็นคีย์เวิร์ดในฟังก์ชัน

พารามิเตอร์แบบมีกี่ตัวก็ได้

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

def  f(*arg):  
a = 0  
for  x  in  arg:  
a += x**2  
a **= 0.5  
print(a)  
  
f(3,4,12)  # ได้ 13.0  
f(7,24,60)  # ได้ 65.0

กรณีใช้ดอกจันสองอัน ** ตัวแปรนั้นจะเก็บค่าคีย์เวิร์ดในรูปของดิกชันนารี

def  f(**kw):  
print((kw['x']**2+kw['y']**2+kw['z']**2)**0.5)  
  
f(x=3,y=4,z=12)  # ได้ 13.0

พารามิเตอร์ที่มี * และ ** สามารถใช้ปนกันกับพารามิเตอร์ธรรมดาได้ แต่ต้องวางไว้ข้างหลัง

def  f(t,**kw):  
print((kw['x']**2+kw['y']**2+kw['z']**2+t**2)**0.5)  
  
f(x=7,y=24,z=60,t=156)  # ได้ 169.0

โดยในกรณีนี้เฉพาะ t เท่านั้นที่มีกำหนดพารามิเตอร์แยกไว้ต่างหาก ดังนั้นจะไม่ถูกนำมารวมใน kw ด้วย หากใช้ปนกันทั้งพารามิเตอร์ธรรมดาและที่มี * และ ** ก็จะต้องเรียงเอาพารามิเตอร์ธรรมดาไว้ก่อน แล้วค่อยตามด้วย * แล้วค่อย **

def  f(t,*arg,**kw):  
a = 0  
for  x  in  arg:  
a += x**2  
a **= 0.5  
print((a**2+kw['x']**2+kw['y']**2+t**2)**0.5)  
  
f(12,15,16,x=36,y=48)  # ได้ 65.0

กรณีนี้ t จะเป็น 12 ส่วน arg จะเป็น [15,16] และ kw เป็น {'x':36,'y':48}

ค่าตั้งต้นของตัวแปรในฟังก์ชัน

บางครั้งฟังก์ชันก็ไม่จำเป็นจะต้องรับค่าอาร์กิวเมนต์หรือคีย์เวิร์ดให้ ครอบคลุมทุกพารามิเตอร์ที่กำหนดไว้ หากฟังก์ชันมีการกำหนดค่าตั้งต้นของพารามิเตอร์ไว้
ให้ลองนึกถึงฟังก์ชันบางตัวที่โปรแกรมมีอยู่แล้ว เช่น open สำหรับเปิดไฟล์ (บทที่ ๑๗) โดยปกติแล้วจะต้องเลือกโหมดว่าจะอ่านหรือเขียน คือเป็น r, w, a หรืออื่นๆ แต่หากไม่ใส่เลยก็จะเป็น r ไปโดยอัตโนมัติ หรืออย่างฟังก์ชัน print ที่กำหนดตัวปิดท้ายเป็นการขึ้นบรรทัดใหม่ "\n" ไปโดยอัตโนมัติ ยกเว้นว่าเราจะใส่คีย์เวิร์ด end= เพิ่มเข้าไป

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

def  raidai(a=0,b=0,c=0,d=0):  
print(a*200+b*300+c*400+d*500)  
  
raidai(b=15,c=20)  # มี b และ c ได้ 12500  
raidai(15,20,10)  # มี a, b และ c ได้ 13000  
raidai(15,20,d=10)  # มี a, b และ d ได้ 14000  
raidai()  # ไม่มีอะไรเลย ได้ 0

ในตัวอย่างนี้จะเห็นว่าใส่ค่าให้ตัวแปร a b c d ไม่ครบแต่ฟังก์ชันก็ทำงานได้ตามปกติ โดยตัวแปรไหนที่ไม่ได้รับค่าก็จะเป็น 0 ซึ่งเป็นไปตามค่าที่กำหนดตั้งต้นไว้ กรณีที่ใส่เป็นอาร์กิวเมนต์ พารามิเตอร์ตัวแรกๆจะได้ค่าไปก่อน ถ้าอยากให้ตัวหลังๆมีค่าในขณะที่ตัวแรกๆไม่มีก็ต้องใช้คีย์เวิร์ดเท่านั้น

การคืนกลับข้อมูลเป็นกลุ่ม

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

def  yok(x,n):  
return  [x**i  for  i  in  range(1,n+1)]  
  
print(yok(2,12))  # ได้ [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096]  
print(yok(3,7))  # ได้ [3, 9, 27, 81, 243, 729, 2187]

ขอบเขตของตัวแปร

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

def  baba():  
c = 1  
  
baba()  
print(c)  # ได้ NameError: name 'c' is not defined

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

def  baba():  
global  c  
c = 1  
  
baba()  
print(c)  # ได้ 1

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

def  baba():  
a = 3  # กำหนดค่าตัวแปร a ขึ้นมาใหม่ ไม่เกี่ยวกับนอกฟังก์ชัน  
print(a)  # ได้ 3  
  
a = 2  
baba()  
print(a)  # ได้ 2 เพราะไม่ได้รับผลจากการเปลี่ยนแปลงค่าที่เกิดในฟังก์ชัน

แต่ในกรณีที่ภายในฟังก์ชันไม่ได้กำหนดตัวแปรชื่อเดียวกันอยู่ ตัวแปรภายในฟังก์ชันนั้น จึงเป็นตัวแปรที่ถูกนิยามจากภายนอก

def  baba():  
print(a)  # แสดงผลค่า a ซึ่งกำหนดจากนอกฟังก์ชัน  
  
a = 2  
baba()

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

def  baba():  
print(a)  # ถูกเรียกใช้ก่อนป้อนค่า  
a = 3  
  
a = 2  
baba()  
# ได้ UnboundLocalError: local variable 'a' referenced before assignment

หากต้องการให้ตัวแปรสามารถทั้งใช้และเปลี่ยนแปลงค่าได้ภายในฟังก์ชันจำเป็นต้องใช้คำสั่ง global ตัวอย่าง

def  baba():  
global  b  
a=1  
b=1  
  
a=2  
b=2  
baba()  
print(a)  # ได้ 2  
print(b)  # ได้ 1

ในนี้จะเห็นว่ามีการกำหนดค่าให้ทั้ง a และ b ๒ ที่คือทั้งในและนอกฟังก์ชัน ที่ต่างกันคือ b มีการกระกาศ global แต่ a ไม่มี ซึ่งทำให้ค่า b ภายในฟังก์ชันกลายเป็นตัวเดียวกับ b นอกฟังก์ชัน

ความเปลี่ยนแปลงค่าของตัวแปรที่ถูกใช้เป็นอาร์กิวเมนต์

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

def  f(x):  
return  x+1  
  
x = f(x)  

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

def  plian(s):  
s[0] =  'k'  
s[3] =  'ng'  
  
listA = ['ก','ข','ค','ง']  
plian(listA)  
print(listA)  # ได้ ['k', 'ข', 'ค', 'ng']

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

def  plian2(s):  
s = ['a','b','c','d']  
return  s  
  
listA = ['ก','ข','ค','ง']  
plian2(listA)  
print(listA)  # ได้ ['ก', 'ข', 'ค', 'ง']

อ้างอิง

Reference : https://phyblas.hinaboshi.com/tsuchinoko19