ในบริบทของการเขียนโปรแกรม ฟังก์ชัน (function) เป็นลำดับของคำสั่งต่างๆ ที่ทำงาน ต่อเนื่องกันที่ถูกกำหนดชื่อให้ เมื่อเรานิยามฟังก์ชัน เราต้องระบุชื่อและลำดับของคำสั่งต่างๆ หลังจากนั้น เราสามารถ “เรียก” ฟังก์ชันโดยใช้ชื่อที่ระบุไว้ได้
เราได้เห็นตัวอย่างหนึ่งของการเรียกฟังก์ชันไปแล้ว:
>>> type(42) <class 'int'>
ชื่อของฟังก์ชันนี้คือ type
นิพจน์ในวงเล็บ เรียกว่า อาร์กิวเมนต์ (argument) ของฟังก์ชัน ผลลัพธ์ของฟังก์ชันนี้ คือ ชนิดของฟังก์ชัน (ในกรณีนี้ คือ ชนิดจำนวนเต็ม (int))
โดยปกติแล้วเราพูดได้ว่า ฟังก์ชันจะ “รับ” อาร์กิวเมนต์ และ “ส่งคืน” ค่าผลลัพธ์ของฟังก์ชัน ผลลัพธ์นี้ยังสามารถเรียกว่า “ค่าคืนกลับ” ได้อีกด้วย
ไพธอนได้เตรียมฟังก์ชันที่แปลงค่าจากชนิดหนึ่งไปยังอีกชนิดหนึ่งไว้ให้ ฟังก์ชัน int
รับค่าอะไรก็ได้ และแปลงค่าที่รับมานั้นไปเป็นจำนวนเต็มถ้ามันสามารถทำได้ ถ้าทำไม่ได้มันก็จะบ่นใส่เรา
>>> int('32') 32 >>> int('Hello') ValueError: invalid literal for int(): Hello
ฟังก์ชัน int
สามารถแปลงเลขจำนวนจุดลอย (floating-point) ไปเป็นจำนวนเต็มได้ แต่มันไม่ได้ใช้การปัดเศษ (ขึ้นหรือลง) มันจะตัดจุดทศนิยมออกไปเลย
>>> int(3.99999) 3 >>> int(-2.3) -2
ฟังก์ชัน float
แปลงจำนวนเต็มและสายอักขระไปเป็นจำนวนจุดลอย
>>> float(32) 32.0 >>> float('3.14159') 3.14159
อย่างสุดท้าย ฟังก์ชัน str
แปลงอาร์กิวเมนต์ของมันไปเป็นสายอักขระ
>>> str(32) '32' >>> str(3.14159) '3.14159'
ไพธอนมีมอดูลสำหรับฟังก์ชันทางคณิตศาสตร์ที่เราคุ้นเคยกัน มอดูล (module) เป็นไฟล์ที่บรรจุกลุ่มของฟังก์ชันที่เกี่ยวข้องกันไว้ด้วยกัน
ก่อนที่เราจะใช้ฟังก์ชันจากมอดูลใดๆ เราจะต้องนำเข้า (import) มอดูลนั้นก่อน ด้วย คำสั่งนำเข้า (import statement)
>>> import math
คำสั่งนี้สร้าง วัตถุมอดูล (module object) ที่ชื่อว่า math ถ้าเราสั่งให้แสดงวัตถุมอดูลอันนี้ เราจะได้ข้อมูลเกี่ยวกับตัววัตถุออกมา:
>>> math <module 'math' (built-in)>
วัตถุมอดูลบรรจุฟังก์ชันต่างๆ และตัวแปรต่างๆ ที่ถูกนิยามขึ้นในมอดูล ในการเข้าถึงฟังก์ชันหนึ่งๆ ที่อยู่ในมอดูลนี้ เราจะต้องระบุชื่อของมอดูลและชื่อของฟังก์ชันที่เราต้องการ โดยการคั่นด้วยจุด รูปแบบดังกล่าว เรียกว่า สัญกรณ์จุด (dot notation)
>>> ratio = signal_power / noise_power >>> decibels = 10 * math.log10(ratio) >>> radians = 0.7 >>> height = math.sin(radians)
ตัวอย่างแรกใช้ฟังก์ชัน math.log10
ในการคำนวณอัตราส่วนของสัญญานต่อการรบกวน ในหน่วยเดซิเบล (สมมติว่า signal_power
และ noise_power
ได้ถูกนิยามไปแล้ว) มอดูล math ได้จัดเตรียมฟังก์ชัน log
ซึ่งจะคำนวณค่า log ฐาน e
ไว้ให้แล้ว
ตัวอย่างที่สองเป็นการหาค่า sin ของตัวแปร radians
ซึ่งชื่อของตัวแปรนี้ใบ้ให้เราทราบว่า ฟังก์ชัน sin
และฟังก์ชันอื่นในตรีโกณมิติ (cos
, tan
, และอื่นๆ) นั้น รับอาร์กิวเมนต์ในหน่วยเรเดียน (radian) การแปลงจากหน่วยองศา (degree) มาเป็นหน่วยเรเดียนนั้น เราจะหารด้วย 180 และคูณด้วย π:
>>> degrees = 45 >>> radians = degrees / 180.0 * math.pi >>> math.sin(radians) 0.707106781187
นิพจน์ math.pi
นำตัวแปร pi
มาจากมอดูล math ค่าของมันเป็นค่าจุดลอย ที่ประมาณค่าของ π มาอีกที โดยมีจุดทศนิยมประมาณ 15 ตำแหน่ง
ถ้าเรารู้จักตรีโกณมิติ เราจะสามารถตรวจสอบผลการทำงานก่อนหน้านี้ โดยเปรียบเทียบกับค่ารากที่สองของ 2 แล้วหารด้วย 2:
>>> math.sqrt(2) / 2.0 0.707106781187
ถึงตอนนี้ เราได้ดูส่วนประกอบต่างๆ ของโปรแกรม—ตัวแปร นิพจน์ คำสั่ง—แบบแยกกัน โดยที่ไม่ได้พูดถึงว่า เราจะประกอบมันเข้าด้วยกันได้อย่างไร
ลักษณะเฉพาะที่ทรงพลังที่สุดอย่างหนึ่งของภาษาการเขียนโปรแกรม นั่นคือ การที่พวกมันสามารถ เอาบล็อกก่อสร้างเล็กๆ มา ประกอบ เข้าด้วยกัน เช่น การที่อาร์กิวเมนต์ของฟังก์ชัน สามารถเป็น นิพจน์แบบไหนก็ได้ รวมถึงตัวดำเนินการทางคณิตศาสตร์ด้วย:
x = math.sin(degrees / 360.0 * 2 * math.pi)
และแม้แต่การเรียกฟังก์ชัน:
x = math.exp(math.log(x+1))
ในเกือบทุกที่ เราสามารถใส่ค่านิพจน์ใดๆ ได้ ยกเว้นหนึ่งที่: ทางซ้ายของการกำหนดค่า จะต้องเป็นชื่อตัวแปร การวางนิพจน์แบบอื่นทางซ้ายนั้น จะเป็นข้อผิดพลาดเชิงวากยสัมพันธ์ (syntax error) (เราจะได้เห็นข้อยกเว้นของกฎข้อนี้ในภายหลัง)
>>> minutes = hours * 60 # right >>> hours * 60 = minutes # wrong! SyntaxError: can't assign to operator
ถึงตอนนี้ เราได้ใช้แค่ฟังก์ชันที่มากับไพธอน แต่มันเป็นไปได้ที่เราจะเพิ่มฟังก์ชันต่างๆ เข้าไปใหม่ นิยามของฟังก์ชัน (function definition) ระบุชื่อของฟังก์ชัน และกลุ่มของคำสั่ง ที่ต่อเนื่องกันในฟังก์ชัน ซึ่งคำสั่งเหล่านี้จะทำงานเมื่อฟังก์ชันถูกเรียกใช้
นี่คือตัวอย่าง:
def print_lyrics(): print("I'm a lumberjack, and I'm okay.") print("I sleep all night and I work all day.")
def
เป็นคำสำคัญที่ระบุว่า นี่คือ นิยามของฟังก์ชัน ชื่อของฟังก์ชัน คือ print_lyrics
กฎการตั้งชื่อฟังก์ชันนั้นเหมือนกับกฎการตั้งชื่อตัวแปร: ตัวอักษร ตัวเลข และขีดล่างนั้นใช้ได้ แต่อักขระตัวแรกต้องไม่เป็นตัวเลข เราไม่สามารถใช้คำสำคัญเป็นชื่อของฟังก์ชัน และเราต้องหลีกเลี่ยง การใช้ชื่อตัวแปรและชื่อฟังก์ชันที่เหมือนกัน
วงเล็บว่างหลังชื่อฟังก์ชัน ระบุว่าฟังก์ชันนี้ไม่รับอาร์กิวเมนต์ใดๆ เข้ามาในฟังก์ชัน
บรรทัดแรกของนิยามฟังก์ชันเรียกว่า ส่วนหัว (header); ที่เหลือเรียกว่า ส่วนตัว (body) ส่วนหัวจะต้องลงท้ายด้วยเครื่องหมายทวิภาค (จุดคู่ หรือ โคลอน) (:) และส่วนตัวของฟังก์ชันจะต้อง ย่อหน้าเข้าไป การย่อหน้าเข้าไปนิยมใช้ช่องว่าง (space) 4 ตำแหน่ง ตัวของฟังก์ชันสามารถบรรจุคำสั่ง กี่คำสั่งก็ได้
(หมายเหตุผู้แปล: การย่อหน้าสามารถใช้ tab ได้ แต่ต้องเลือกการย่อหน้าให้เป็นรูปแบบเดียวกันทั้งไฟล์)
สายอักขระที่อยู่ในคำสั่งพิมพ์ถูกใส่อยู่ในเครื่องหมายอัญประกาศ อัญประกาศเดี่ยว (single quotes) และอัญประกาศ (double quotes) ทำงานเหมือนกัน คนส่วนใหญ่ใช้อัญประกาศเดี่ยว ยกเว้นในกรณีอย่างเช่น การมีเครื่องหมายอัญประกาศเดี่ยวตัวเดียว (apostrophe) ในสายอักขระ
อัญประกาศทุกแบบจะต้องเป็นเครื่องหมายที่เป็น “เส้นตรงๆ“ ซึ่งโดยปกติแล้วจะอยู่ถัดจากปุ่ม Enter บนคีย์บอร์ด ส่วนเครื่องหมายอัญประกาศ “แบบโค้ง” เช่นในประโยคนี้ จะผิดกฎของไพธอน
ถ้าเราพิมพ์นิยามของฟังก์ชันในโหมดโต้ตอบ ตัวแปลภาษาไพธอนจะขึ้นจุดหลายๆ จุด (...
) มาให้ เพื่อที่เราจะได้รู้ว่านิยามฟังก์ชันนั้นยังไม่สมบูรณ์ดี
>>> def print_lyrics(): ... print("I'm a lumberjack, and I'm okay.") ... print("I sleep all night and I work all day.") ...
ถ้าต้องการจบฟังก์ชัน ให้เรา enter บรรทัดเปล่าๆ เข้าไปหนึ่งบรรทัด
การนิยามฟังก์ชันจะสร้าง วัตถุฟังก์ชัน (function object) ที่มีชนิดเป็น function
:
>>> print(print_lyrics) <function print_lyrics at 0xb7e99e9c> >>> type(print_lyrics) <class 'function'>
กฎวากยสัมพันธ์ในการเรียกฟังก์ชันใหม่นั้นเหมือนกับการเรียกฟังก์ชันที่มีอยู่ในตัว (built-in function):
>>> print_lyrics() I'm a lumberjack, and I'm okay. I sleep all night and I work all day.
เมื่อเราได้นิยามฟังก์ชันแล้ว เราสามารถใช้มันในฟังก์ชันอื่นได้ด้วย เช่น ถ้าเราต้องการจะพิมพ์เนื้อเพลงบทที่แล้วอีกรอบ เราสามารถเขียนฟังก์ชันที่ชื่อว่า repeat_lyrics
:
def repeat_lyrics(): print_lyrics() print_lyrics()
จากนั้น ให้เรียก repeat_lyrics
:
>>> repeat_lyrics() I'm a lumberjack, and I'm okay. I sleep all night and I work all day. I'm a lumberjack, and I'm okay. I sleep all night and I work all day.
แต่จริงๆ แล้วเนื้อเพลงมันไม่ได้เป็นแบบนั้นหรอกนะ
เมื่อทำการรวบรวมโค้ดชิ้นต่างๆ จากเนื้อหาในส่วนที่ผ่านมา โปรแกรมที่สมบูรณ์ก็จะหน้าตาเป็นแบบนี้:
def print_lyrics(): print("I'm a lumberjack, and I'm okay.") print("I sleep all night and I work all day.") def repeat_lyrics(): print_lyrics() print_lyrics() repeat_lyrics()
โปรแกรมนี้ประกอบด้วยนิยามฟังก์ชัน 2 ตัว: print_lyrics
และ repeat_lyrics
นิยามฟังก์ชันทำงานเหมือนคำสั่งต่างๆ ทั่วไป แต่จะส่งผลให้เกิดการสร้างวัตถุฟังก์ชัน คำสั่งต่างๆ ที่อยู่ใน ฟังก์ชันจะไม่ทำงานจนกว่าฟังก์ชันนั้นจะถูกเรียก และตัวนิยามของฟังก์ชันเองจะไม่ทำให้เกิดผลลัพธ์ใดๆ
(หมายเหตุผู้แปล: นั่นคือ จะต้องมีการเรียกฟังก์ชันก่อน ถึงจะมีการทำงานใดๆ นั่นเอง)
เราอาจจะพอคาดได้ว่า เราจะต้องสร้างฟังก์ชันก่อนที่เราจะเรียกมันใช้งาน อีกนัยหนึ่งคือ นิยามของฟังก์ชัน จะต้องถูกรันก่อนที่ฟังก์ชันนั้นจะถูกเรียกใช้งาน
เพื่อเป็นการฝึก ให้ลองย้ายบรรทัดสุดท้ายของโปรแกรมไปไว้ข้างบนสุด ซึ่งทำให้การเรียกฟังก์ชันนั้นเกิดก่อน การนิยามฟังก์ชัน ให้รันโปรแกรมและดูว่าไพธอนจะให้ข้อความแจ้งข้อผิดพลาดอะไรออกมา
คราวนี้ ให้ย้ายการเรียกฟังก์ชันกลับมาข้างล่างสุดเหมือนเดิม และย้ายนิยามของ print_lyrics
ให้ไปอยู่หลังนิยามของ repeat_lyrics
จะเกิดอะไรขึ้นเมื่อเรารันโปรแกรมนี้?
เพื่อที่ให้แน่ใจว่าฟังก์ชันนั้นถูกนิยามก่อนการใช้งานครั้งแรก เราต้องรู้ลำดับการทำงานของคำสั่งก่อน ซึ่งเรียกว่า กระแสการดำเนินการ (flow of execution)
การดำเนินการของคำสั่งเริ่มจากคำสั่งแรกสุดของโปรแกรมเสมอ คำสั่งจะทำงานทีละคำสั่ง เรียงลำดับจากบนลงล่าง
การนิยามฟังก์ชันไม่ได้เปลี่ยนแปลงกระแสการดำเนินการ แต่ให้จำไว้ว่า คำสั่งที่อยู่ในฟังก์ชันนั้นจะไม่ทำงานจนกว่าฟังก์ชันนั้นจะถูกเรียกใช้
การเรียกใช้ฟังก์ชันเหมือนกับการเบี่ยงกระแสการดำเนินการ แทนที่โปรแกรมจะทำคำสั่งถัดไป การทำงานจะกระโดดไปที่ตัวของฟังก์ชัน ทำงานตามคำสั่งในฟังก์ชันนั้น และจากนั้นก็กลับมาทำต่อ จากตรงที่มันกระโดดออกไป
ก็ฟังดูง่ายนะ จนกระทั่งเราจำได้ว่าฟังก์ชันหนึ่งสามารถเรียกอีกฟังก์ชันหนึ่งได้ด้วย เมื่ออยู่ระหว่าง การทำงานในฟังก์ชันหนึ่งๆ โปรแกรมอาจจะทำคำสั่งที่ต้องเรียกฟังก์ชันอื่น จากนั้นในขณะที่ทำงานใน ฟังก์ชันใหม่นั้น โปรแกรมก็อาจจะเรียกทำงานฟังก์ชันอันอื่นอีก!
ยังโชคดีที่ไพธอนนั้นเก่งในการติดตามว่าตอนนี้มันทำงานอยู่ที่ไหนแล้ว ทำให้แต่ละครั้งที่ฟังก์ชันทำงานเสร็จ โปรแกรมจะกลับไปทำงานค้างในฟังก์ชันที่เรียกฟังก์ชันที่เพิ่งทำงานเสร็จนี้ เมื่อไพธอนทำทุกคำสั่ง ในโปรแกรมเสร็จแล้ว มันก็จะหยุดทำงาน
โดยสรุปแล้ว เมื่อเราอ่านโปรแกรม เราไม่จำเป็นจะต้องอ่านจากบนลงล่างเสมอไป บางครั้งมันเข้าท่าที่จะอ่านตามกระแสการดำเนินการ (flow of execution)
ฟังก์ชันบางอันที่เราได้เห็นมานั้นต้องการอาร์กิวเมนต์ เช่น เมื่อเราเรียกฟังก์ชัน math.sin
เราจะต้องผ่านเลขเข้าไปเป็นอาร์กิวเมนต์ ฟังก์ชันบางตัวรับค่ามากกว่าหนึ่งอาร์กิวเมนต์: ฟังก์ชัน math.pow
รับค่าเข้ามาสองตัว คือ เลขฐานและเลขยกกำลัง
ในฟังก์ชัน อาร์กิวเมนต์จะถูกกำหนดตัวแปรให้ เรียกว่า พารามิเตอร์ (parameters) นี่คือนิยามของฟังก์ชันที่รับอาร์กิวเมนต์เข้ามา
def print_twice(bruce): print(bruce) print(bruce)
ฟังก์ชันนี้กำหนดให้อาร์กิวเมนต์ที่รับเข้ามาเป็นพารามิเตอร์ที่ชื่อว่า bruce
เมื่อฟังก์ชันถูกเรียก มันจะพิมพ์ค่าของพารามิเตอร์ตัวนี้ (ไม่ว่ามันจะมีค่าอะไรก็ตาม) สองครั้ง
ฟังก์ชันนี้จะทำงานได้กับค่าอะไรก็ตามที่สามารถพิมพ์ออกมาได้
>>> print_twice('Spam') Spam Spam >>> print_twice(42) 42 42 >>> print_twice(math.pi) 3.14159265359 3.14159265359
กฎของการประกอบ (rules of composition) อันเดียวกันกับที่ใช้กับฟังก์ชันที่มีอยู่ในตัว (built-in function) สามารถใช้ได้กับฟังก์ชันที่นักเขียนโปรแกรมนิยามขึ้นมาเอง ดังนั้น เราสามารถใช้นิพจน์ใดๆ เป็นอาร์กิวเมนต์สำหรับ ฟังก์ชัน print_twice
:
>>> print_twice('Spam '*4) Spam Spam Spam Spam Spam Spam Spam Spam >>> print_twice(math.cos(math.pi)) -1.0 -1.0
อาร์กิวเมนต์จะถูกประเมินค่าก่อนที่ฟังก์ชันจะถูกเรียก ดังนั้น ในตัวอย่างนี้ นิพจน์ 'Spam '*4
และ math.cos(math.pi)
จะถูกประเมินค่าแค่ครั้งเดียว
(หมายเหตุผู้แปล: หลังจากประเมินค่า โปรแกรมจะส่งค่าที่ประเมินแล้วเข้าไปทำงานในฟังก์ชัน)
เราสามารถใช้ตัวแปรเป็นอาร์กิวเมนต์ได้ด้วย:
>>> michael = 'Eric, the half a bee.' >>> print_twice(michael) Eric, the half a bee. Eric, the half a bee.
ชื่อของตัวแปรที่เราผ่านเข้าไปเป็นอาร์กิวเมนต์ (michael
) ไม่มีอะไรเกี่ยวข้องกับชื่อของพารามิเตอร์ (bruce
) มันไม่เกี่ยวว่าค่าหนึ่งๆ ถูกเรียกว่าอะไรในที่เดิม (ในส่วนที่เรียกฟังก์ชัน); ณ ที่นี่ ในฟังก์ชัน print_twice
เราเรียกทุกตัว (ที่ถูกส่งเข้ามา) ว่า bruce
เมื่อเราสร้างตัวแปรข้างในฟังก์ชัน มันจะมีค่า เฉพาะที่ (local) ซึ่งหมายความว่ามันจะมีตัวตนแค่ในฟังก์ชันนี้เท่านั้น เช่น:
def cat_twice(part1, part2): cat = part1 + part2 print_twice(cat)
ฟังก์ชันนี้รับอาร์กิวเมนต์เข้ามาสองตัว เชื่อมต่อมันเข้าด้วยกัน และพิมพ์ผลลัพธ์สองครั้ง นี่คือตัวอย่างที่ใช้ฟังก์ชันนี้:
>>> line1 = 'Bing tiddle ' >>> line2 = 'tiddle bang.' >>> cat_twice(line1, line2) Bing tiddle tiddle bang. Bing tiddle tiddle bang.
เมื่อฟังก์ชัน cat_twice
จบการทำงาน ตัวแปร cat
จะถูกทำลาย ถ้าเราพยายามที่จะพิมพ์มันออกมา เราจะได้ข้อยกเว้น (ข้อผิดพลาดตอนโปรแกรมทำงาน):
>>> print(cat) NameError: name 'cat' is not defined
พารามิเตอร์ก็เป็นค่าเฉพาะที่เหมือนกัน เช่น ภายนอกฟังก์ชัน print_twice
มันไม่มีสิ่งที่เรียกว่า bruce
เพื่อที่จะติดตามว่าตัวแปรไหนใช้ที่ไหนได้บ้าง บางครั้งมันก็เป็นประโยชน์ที่จะวาด แผนภาพแบบกองซ้อน (stack diagram) เช่นเดียวกับแผนภาพสถานะ แผนภาพแบบกองซ้อนแสดงค่าของแต่ละตัวแปร แต่มันยังแสดงว่าตัวแปรนั้นเป็นของฟังก์ชันไหน
แต่ละฟังก์ชันถูกแสดงด้วย กรอบ (frame) กรอบเป็นกล่องที่มีชื่อฟังก์ชันอยู่ข้างๆ และมีพารามิเตอร์และตัวแปร ของฟังก์ชันนั้นอยู่ข้างใน แผนภาพแบบกองซ้อนสำหรับตัวอย่างที่แล้วแสดงอยู่ในรูปที่ 3.1
กรอบต่างๆ ถูกจัดวางเป็นกองๆ ซ้อนกัน โดยระบุว่าฟังก์ชันไหนเรียกฟังก์ชันไหน ไปเรื่อยๆ ในตัวอย่างนี้ ฟังก์ชัน print_twice
ถูกเรียกโดยฟังก์ชัน cat_twice
และ ฟังก์ชัน cat_twice
ถูกเรียกเรียกโดยฟังก์ชัน __main__
ซึ่งเป็นชื่อพิเศษสำหรับเรียกกรอบ ที่อยู่บนสุดของโปรแกรม เมื่อเราสร้างตัวแปรภายนอกฟังก์ชันอะไรก็ตาม มันจะถือว่าเป็นตัวแปรของส่วน __main__
พารามิเตอร์แต่ละตัวอ้างอิงถึงค่าเดียวกันกับค่าของอาร์กิวเมนต์ที่ผ่านเข้ามา ณ ตำแหน่งเดียวกัน ดังนั้น part1
มีค่าเดียวกันกับ line1
, part2
มีค่าเดียวกันกับ line2
, และ bruce
มีค่าเดียวกันกับ cat
ถ้ามีข้อผิดพลาดเกิดขึ้นระหว่างที่มีการเรียกฟังก์ชัน ไพธอนจะพิมพ์ชื่อฟังก์ชันที่มีปัญหา ชื่อฟังก์ชันที่เรียกฟังก์ชันที่มีปัญหา และชื่อฟังก์ชันที่เรียกฟังก์ชัน เมื่อกี้นี้ และย้อนกลับไปเรื่อยๆ จนถึง __main__
ตัวอย่างเช่น ถ้าเราพยายามที่จะเข้าถึง cat
จากในฟังก์ชัน print_twice
เราจะได้ ข้อผิดพลาดแบบทางชื่อ หรือ NameError
:
Traceback (innermost last): File "test.py", line 13, in __main__ cat_twice(line1, line2) File "test.py", line 5, in cat_twice print_twice(cat) File "test.py", line 9, in print_twice print(cat) NameError: name 'cat' is not defined
รายชื่อฟังก์ชันเหล่านี้เรียกว่า การย้อนรอย (traceback) มันบอกเราว่าโปรแกรมผิดพลาดที่ไฟล์ใด บรรทัดใด และฟังก์ชันอะไรที่ทำงานอยู่ตอนนั้น มันยังแสดงเลขบรรทัดของโค้ดที่เป็นเหตุของข้อผิดพลาดอีกด้วย
ลำดับของฟังก์ชันต่างๆ ในการย้อนรอยเป็นลำดับเดียวกับลำดับของกรอบในแผนภาพแบบกองซ้อน ฟังก์ชันที่กำลังทำงานอยู่จะอยู่ข้างล่างสุด
บางฟังก์ชันที่เราใช้กันมา เช่น ฟังก์ชันทางคณิตศาสตร์ มีการคืนผลลัพธ์กลับมาให้เรา เนื่องจากไม่มีชื่อที่ดีกว่านี้ ผมจะเรียกพวกมันว่า ฟังก์ชันที่ให้ผล (fruitful functions) ส่วนฟังก์ชันอื่นๆ จำพวกฟังก์ชัน print_twice
จะทำงานต่างๆ โดยไม่มีการคืนค่ากลับมา พวกมันเรียกว่า ฟังก์ชันที่ไม่ให้ผล หรือฟังก์ชันวอยด์ (void functions)
เมื่อเราเรียกฟังก์ชันที่ให้ผล เราต้องทำอะไรสักอย่างกับผลที่ได้คืนมานั้นเกือบจะเสมอ เช่น เราอาจจะกำหนดค่าของมันให้กับตัวแปร หรือใช้เป็นส่วนหนึ่งของนิพจน์:
x = math.cos(radians) golden = (math.sqrt(5) + 1) / 2
เมื่อเราเรียกฟังก์ชันในโหมดโต้ตอบ ไพธอนจะแสดงผลออกมา
>>> math.sqrt(5) 2.2360679774997898
แต่ในโหมดสคริปต์ ถ้าเราเรียกฟังก์ชันที่มีผลแบบลอยๆ ค่าผลลัพธ์ที่คืนกลับมาจะหายไปเลยชั่วนิรันดร์!
math.sqrt(5)
สคริปต์นี้คำนวณรากที่สองของ 5 แต่เนื่องจากมันไม่ได้เก็บค่าที่คืนกลับมาหรือแสดงค่าออกมา มันก็ไม่มีประโยชน์อะไรเท่าไหร่
ฟังก์ชันวอยด์อาจจะแสดงอะไรสักอย่างบนหน้าจอ หรือมีผลอย่างอื่น แต่มันไม่มีค่าที่ถูกส่งคืนกลับมา ถ้าเรากำหนดค่าที่คืนกลับมาจากฟังก์ชันวอยด์ให้กับตัวแปร เราจะได้ค่าพิเศษที่เรียกว่า None
>>> result = print_twice('Bing') Bing Bing >>> print(result) None
ค่า None
ไม่ใช่ค่าเดียวกับสายอักขระ 'None'
แต่มันเป็นค่าพิเศษที่มีชนิดของข้อมูลเป็นของมันเอง:
>>> type(None) <class 'NoneType'>
ฟังก์ชันที่เราเขียนกันมาถึงตอนนี้เป็นฟังก์ชันวอยด์ทั้งหมด เราจะเริ่มเขียนฟังก์ชันที่ให้ผลให้อีกสองสามบทถัดไป
มันอาจจะไม่ชัดเจนเท่าไหร่ว่าทำไมมันถึงคุ้มค่ากับการปวดหัวเพื่อที่จะแบ่งโปรแกรมเป็นฟังก์ชันต่างๆ มันมีเหตุผลหลายประการ ดังนี้:
ทักษะที่สำคัญมากที่สุดอย่างหนึ่งที่เราจะได้เรียนรู้ คือ การตรวจแก้จุดบกพร่อง หรือการดีบัก (debugging) ถึงแม้ว่ามันจะน่าท้อแท้อยู่สักหน่อย แต่การดีบักเป็นสิ่งที่ประเทืองปัญญา ท้าทาย และเป็นส่วนที่น่าสนใจของการเขียนโปรแกรม
ในบางแง่มุม การดีบักเหมือนกับงานสืบสวน เราจะเจอเบาะแสและเราจะต้องวินิจฉัยกระบวนการ และเหตุการณ์ที่ทำให้เกิดผลลัพธ์แบบที่เราเห็นอยู่นี้
(หมายเหตุผู้แปล: คิดว่าผลนี้มันเกิดมาจากไหน ได้อย่างไร)
การดีบักยังเป็นเหมือนกับการทดลองทางวิทยาศาสตร์ เมื่อเรามีความคิดว่าอะไรที่น่าจะผิดพลาด เราจะแก้โปรแกรม และลองดูอีก ถ้าสมมติฐานของเราถูก เราจะสามารถทำนายผลของการแก้ไขนั้นได้ และเราก็ได้ก้าวเข้าใกล้ โปรแกรมที่สามารถทำงานได้อีกก้าวหนึ่งแล้ว ถ้าสมมติฐานของเราผิด เราก็ต้องคิดสมมติฐานขึ้นมาใหม่ เป็นอย่างที่เชอร์ล็อค โฮล์มส์ชี้ให้เห็นว่า “เมื่อเรากำจัดความเป็นไปไม่ได้แล้ว, อะไรก็ตามที่เหลือ, ไม่ว่ามันจะ ดูเป็นไปไม่ได้ขนาดไหน, ก็จะต้องเป็นความจริง“ (A. Conan Doyle, The Sign of Four)
สำหรับบางคนแล้ว การเขียนโปรแกรมและการดีบักเป็นอย่างเดียวกัน นั่นคือ การเขียนโปรแกรมเป็นกระบวนการของการ ค่อยๆ ดีบักโปรแกรมจนกว่ามันจะทำสิ่งที่เราต้องการได้ หลักการคือ เราควรจะเริ่มจากโปรแกรมเล็กๆ ที่ทำงานได้ และแก้ไขไปทีละน้อย นั่นคือการดีบักไปพร้อมกับการเขียนโปรแกรม
(หมายเหตุผู้แปล: เขียนเพิ่มและดีบักเพิ่มทีละเล็กทีละน้อย)
ตัวอย่างเช่น ลินิกซ์ (Linux) เป็นระบบปฏิบัติการที่มีโค้ดเป็นล้านๆ บรรทัด แต่มันเริ่มมาจากโปรแกรมเล็กๆ ที่ ลินัส ทอร์วัลด์ส (Linus Torvalds) ใช้สำรวจชิป Intel 80386 จากคำบอกเล่าของ ลาร์รี กรีนฟิลด์ (Larry Greenfield) “โครงงานแรกๆ ของลินัส คือ โปรแกรมที่จะสับเปลี่ยนระหว่างการพิมพ์ AAAA และ BBBB โปรแกรมนี้ได้วิวัฒนาการมาเป็นลินิกซ์” (The Linux Users’ Guide Beta Version 1)
None
เสมอNone
: ค่าพิเศษที่ถูกคืนกลับมาโดยฟังก์ชันวอยด์import
ที่เอื้อให้สามารถเข้าถึงค่าที่ถูกนิยามในมอดูล
แบบฝึกหัด 1
ให้เขียนฟังก์ชันที่ชื่อว่า right_justify
ซึ่งรับสายอักขระที่ชื่อว่า s
เป็นพารามิเตอร์ และพิมพ์ สายอักขระดังกล่าวโดยมีช่องว่างนำหน้า แล้วทำให้อักษรตัวสุดท้ายของสายอักขระอยู่ในหลักที่ 70 ของการแสดงผล
>>> right_justify('monty') monty
คำใบ้: ใช้การเชื่อมต่อสายอักขระและการทำซ้ำ นอกจากนี้ ไพธอนได้เตรียมฟังก์ชันที่ชื่อว่า len
ซึ่งจะคืนค่า ความยาวของสายอักขระ ดังนั้น ค่าของ len('monty')
คือ 5
แบบฝึกหัด 2
วัตถุฟังก์ชัน คือ ค่าที่เราสามารถกำหนดให้กับตัวแปร หรือผ่านเข้าไปเป็นอาร์กิวเมนต์ เช่น ฟังก์ชัน do_twice
เป็นฟังก์ชันที่รับวัตถุฟังก์ชันเป็นอาร์กิวเมนต์ และเรียกฟังก์ชันนั้นสองรอบ (ฟังก์ชัน f() จะทำงาน 2 ครั้ง)
def do_twice(f): f() f()
นี่คือตัวอย่างที่ใช้ฟังก์ชัน do_twice
เพื่อเรียกฟังก์ชัน print_spam
ให้ทำงานสองครั้ง
def print_spam(): print('spam') do_twice(print_spam)
do_twice
ให้รับอาร์กิวเมนต์เข้ามา 2 ตัว เป็นวัตถุฟังก์ชัน 1 ตัว และค่า 1 ค่า ให้เรียกฟังก์ชันที่รับเข้ามา 2 รอบ โดยผ่านอีกค่าที่รับเข้ามาเป็นอาร์กิวเมนต์ของฟังก์ชันดังกล่าวprint_twice
จากก่อนหน้านี้ในบทนี้แล้วใส่ในสคริปต์ของเราdo_twice
ที่แก้ไขไปก่อนหน้านี้เพื่อเรียกฟังก์ชัน print_twice
สองครั้ง โดยให้ผ่าน 'spam'
เป็นอาร์กิวเมนต์do_four
ซึ่งรับวัตถุฟังก์ชัน 1 ตัว และค่า 1 ค่า และเรียกฟังก์ชันที่รับเข้ามา 4 รอบโดยผ่านอีกค่าที่รับเข้ามาเป็นพารามิเตอร์ จะต้องมีคำสั่งแค่ 2 คำสั่งเท่านั้นในตัวฟังก์ชันนี้ ไม่ใช่ 4 คำสั่งเฉลย: http://thinkpython2.com/code/do_four.py
แบบฝึกหัด 3
หมายเหตุ: ข้อนี้จะต้องใช้คำสั่งและลักษณะเฉพาะที่เราเรียนมาจนถึงตอนนี้เท่านั้น
1. ให้เขียนฟังก์ชันที่วาดรูปตารางดังต่อไปนี้
+ - - - - + - - - - + | | | | | | | | | | | | + - - - - + - - - - + | | | | | | | | | | | | + - - - - + - - - - +
คำใบ้: ในการพิมพ์ค่ามากกว่า 1 ค่าบนหนึ่งบรรทัด เราสามารถพิมพ์ค่าหลายค่าต่อกันโดยคั่นด้วยเครื่องหมายจุลภาค (,):
print('+', '-')
คำสั่ง print
ขึ้นบรรทัดใหม่โดยปริยาย แต่เราสามารถยกเลิกพฤติกรรมนี้ และใส่ช่องว่างในตอนท้ายของการพิมพ์ได้ โดยทำแบบนี้:
print('+', end=' ') print('-')
ผลของคำสั่งเหล่านี้ คือ '+ -'
คำสั่ง print
ที่ไม่มีอาร์กิวเมนต์จะจบบรรทัดปัจจุบันและขึ้นบรรทัดใหม่
2. ให้เขียนฟังก์ชันที่วาดรูปตารางแบบรูปที่แล้ว แต่ให้มี 4 แถวและ 4 หลัก
เฉลย: http://thinkpython2.com/code/grid.py. ที่มา: แบบฝึกหัดข้อนี้ดัดแปลงมาจากแบบฝึกหัดใน Oualline, Practical C Programming, Third Edition, O’Reilly Media, 1997.
https://greenteapress.com/thinkpython2/html/thinkpython2004.html