หัวข้อหลักของบทนี้ คือ คำสั่ง if
ซึ่งดำเนินการกับโค้ดต่างกันขึ้นอยู่กับสถานะของโปรแกรม แต่ก่อนอื่น ผมอยากจะแนะนำตัวดำเนินการใหม่สองตัว: การหารปัดเศษลง (floor division) และโมดูลัส (modulus)
เครื่องหมาย การหารปัดเศษลง (floor division), //
, หารเลขสองตัวแล้วปัดเศษลงให้เป็นเลขจำนวนเต็ม ตัวอย่างเช่น สมมติว่าเวลาฉายหนัง คือ 105 นาที เราอาจจะอยากทราบว่ามันนานกี่ชั่วโมง การหารแบบปกตินิยมจะให้ค่าเป็นเลขจุดลอย:
>>> minutes = 105 >>> minutes / 60 1.75
แต่โดยปกติแล้วเราไม่เขียนเลขชั่วโมงแบบมีจุดทศนิยม การหารปัดเศษลงจะให้ค่าจำนวนเต็มของเลขชั่วโมง โดยปัดเศษลง:
>>> minutes = 105 >>> hours = minutes // 60 >>> hours 1
ในการหาเศษของชั่วโมง เราสามารถลบจำนวนนาทีในหนึ่งชั่วโมงออก:
>>> remainder = minutes - hours * 60 >>> remainder 45
อีกทางหนึ่ง คือ การใช้ ตัวดำเนินการมอดูลัส (modulus operator), %
, ซึ่งจะหารเลขสองตัว และให้ค่าเป็นเศษของการหาร
>>> remainder = minutes % 60 >>> remainder 45
ตัวดำเนินการมอดูลัสนั้นมีประโยชน์มากกว่าที่คิด เช่น เราสามารถตรวจสอบได้ว่าเลขตัวหนึ่งถูกหารลงตัวจากเลขอีกตัว ได้หรือไม่—ถ้า x % y
เป็นศูนย์ แล้ว x
จะถูกหารด้วย y
ลงตัว
นอกจากนี้ เราสามารถดึงตัวเลขหลักทางขวาสุดออกมาได้ด้วย (หลักเดียวหรือหลายหลัก) เช่น x % 10
จะได้เลขตัวขวาสุด (หลักหน่วย) ของ x
(ในฐาน 10) เช่นเดียวกันกับ x % 100
จะให้เลขสองหลัก สุดท้ายออกมา
แต่ถ้าเราใช้ไพธอน 2 การหารเลขจะต่างออกไป ตัวดำเนินการหาร, /
, จะทำการหารแบบปัดเศษถ้าเลขทั้งสอง เป็นจำนวนเต็ม และจะทำการหารแบบจุดลอยหากเลขตัวใดตัวหนึ่งเป็น float
นิพจน์บูลีน (boolean expression) คือนิพจน์ที่มีค่าความจริงเป็น จริง หรือ เท็จ ตัวอย่างต่อไปนี้ใช้ตัวดำเนินการ ==
ซึ่งเปรียบเทียบตัวถูกดำเนินการสองตัว และให้ค่าเป็น จริง (True)
ถ้าสองค่านั้นเท่ากัน และให้ค่าเป็น เท็จ (False)
หากไม่เท่ากัน:
>>> 5 == 5 True >>> 5 == 6 False
True
และ False
เป็นค่าพิเศษที่มีชนิดเป็น บูล (bool)
; มันไม่ใช่สายอักขระ:
>>> type(True) <class 'bool'> >>> type(False) <class 'bool'>
ตัวดำเนินการ ==
เป็นหนึ่งใน ตัวดำเนินการเชิงสัมพันธ์ (relational operators); ตัวอื่นๆ คือ:
x != y # x is not equal to y x > y # x is greater than y x < y # x is less than y x >= y # x is greater than or equal to y x <= y # x is less than or equal to y
ถึงแม้ว่าเราน่าจะคุ้นเคยกับการดำเนินการเหล่านี้ สัญลักษณ์ที่ใช้ในไพธอนจะต่างกับสัญลักษณ์ที่ใช้ในคณิตศาสตร์ ข้อผิดพลาดที่เกิดขึ้นบ่อยคือการใช้เครื่องหมายเท่ากับ (=
) แทนที่จะใช้เครื่องหมายเท่ากับคู่ (==
) ให้จำไว้ว่า =
เป็นตัวดำเนินการสำหรับการกำหนดค่า และ ==
เป็นตัวดำเนินการเชิงสัมพันธ์ ไม่มีการใช้เครื่องหมาย =<
หรือ =>
ตัวดำเนินการทางตรรกะ (logical operators) มี 3 ตัว: and
, or
, และ not
. ความหมายของตัวดำเนินการเหล่านี้ตรงกับความหมายในภาษาอังกฤษเลย เช่น x > 0 and x < 10
จะเป็นจริงถ้า x
มากกว่า 0 และ น้อยกว่า 10 เท่านั้น
n%2 == 0 or n%3 == 0
เป็นจริงถ้า ตัวใดตัวหนึ่ง หรือ ทั้งสองตัว ของเงื่อนไขเป็นจริง นั่นคือ ถ้าเลขนั้นสามารถหารด้วย 2 หรือ 3 ลงตัว
สุดท้ายนี้ ตัวดำเนินการ not
จะทำนิพจน์บูลีนให้เป็นนิเสธ (การกลับค่าความจริง) ดังนั้น not (x > y)
จะเป็นจริง ถ้า x > y
เป็นเท็จ นั่นคือ ถ้า x
น้อยกว่าหรือเท่ากับ y
จริงๆ แล้ว ตัวถูกดำเนินการของตัวดำเนินการทางตรรกะควรจะเป็นนิพจน์บูลีน แต่ไพธอนนั้นไม่ค่อยเคร่งครัดเท่าไร เลขที่ไม่เท่ากับศูนย์ใดๆ จะถูกแปลให้มีค่าเป็น จริง True
:
>>> 42 and True True
ความยืดหยุ่นนี้สามารถเป็นประโยชน์ได้ แต่ก็มีรายละเอียดปลีกย่อยที่จะทำให้สับสนได้ เราควรจะหลีกเลี่ยงการทำอะไรแบบนี้ (ยกเว้นว่า เรารู้ตัวว่ากำลังทำอะไรอยู่)
ในการที่จะเขียนโปรแกรมที่เป็นประโยชน์ เราต้องการความสามารถในการตรวจสอบเงื่อนไขเกือบจะตลอดเวลา และสามารถเปลี่ยนพฤติกรรมของโปรแกรมตามเงื่อนไขนั้น คำสั่งเงื่อนไข (Conditional statements) มอบความสามารถนี้ให้เรา รูปแบบที่ง่ายที่สุด คือ คำสั่ง if
:
if x > 0: print('x is positive')
นิพจน์บูลีนที่อยู่หลังจาก if
เรียกว่า เงื่อนไข (condition) ถ้าเงื่อนไขเป็นจริง คำสั่งที่ย่อหน้าเข้าไปนั้นจะถูกรัน ไม่เช่นนั้น ก็ไม่มีอะไรเกิดขึ้น
คำสั่ง if
มีโครงสร้างเหมือนนิยามของฟังก์ชัน: มีส่วนหัว ตามด้วยส่วนตัวที่ถูกย่อหน้าเข้าไป คำสั่งแบบนี้เรียกว่า คำสั่งประกอบ (compound statements)
มันไม่มีข้อจำกัดสำหรับจำนวนคำสั่งที่ปรากฏในส่วนตัวของคำสั่ง if แต่จะต้องมีอย่างน้อยหนึ่งคำสั่ง ในบางครั้ง มันก็มีประโยชน์ที่จะมีส่วนตัวที่ไม่มีคำสั่ง (โดยปกติแล้วใช้เป็นที่สำรองสำหรับโค้ดที่ยังไม่ได้เขียน) ในกรณีดังกล่าว เราสามารถใส่คำสั่ง pass
ซึ่งเป็นคำสั่งที่ไม่ทำอะไรเลย
if x < 0: pass # TODO: need to handle negative values!
รูปแบบที่สองของคำสั่ง if
คือ “การดำเนินการทางเลือก (alternative execution)” ซึ่งมีทางเลือกทำสองทางและมีเงื่อนไขที่จะกำหนดว่าคำสั่งชุดไหนจะถูกรัน กฎวากยสัมพันธ์ของคำสั่งเป็นแบบนี้:
if x % 2 == 0: print('x is even') else: print('x is odd')
ถ้าเศษของการหารเมื่อเราหาร x
ด้วย 2 มีค่าเป็น 0 แล้ว เรารู้ว่า x
เป็นจำนวนคู่ และโปรแกรมจะแสดงข้อความตามนั้น แต่ถ้าเงื่อนไขเป็นเท็จ คำสั่งชุดที่สองจะทำงาน เนื่องจากเงื่อนไขจะต้องเป็นจริงหรือเท็จเท่านั้น จึงมีแค่ทางเลือกหนึ่งอย่างเท่านั้นที่ทำงาน ทางเลือกเหล่านี้เรียกว่า (แขนง) branches เพราะว่ามันเป็นกิ่งที่แตกแยกออกไป ของกระแสการดำเนินการ (flow of execution)
บางครั้งมันก็มีมากกว่าสองทางเลือก และเราต้องการมากกว่าสองแขนงที่แตกออกไป ทางหนึ่งในการแสดง การคำนวณแบบนี้ คือ เงื่อนไขลูกโซ่ (chained conditional)
if x < y: print('x is less than y') elif x > y: print('x is greater than y') else: print('x and y are equal')
elif
เป็นคำย่อของ “else if” (ไม่เช่นนั้น ถ้า) ทบทวนอีกทีว่าแขนงแค่หนึ่งอันเท่านั้นที่จะทำงาน มันไม่มีการจำกัดจำนวนของคำสั่ง elif
ถ้าจะมีข้อย่อย (clause) else
ด้วย มันจะต้องอยู่ท้ายสุด แต่มันไม่จำเป็นต้องมี
if choice == 'a': draw_a() elif choice == 'b': draw_b() elif choice == 'c': draw_c()
เงื่อนไขแต่ละอันจะถูกตรวจสอบตามลำดับ ถ้าอันแรกเป็นเท็จ อันถัดไปจะถูกตรวจสอบ และเป็นแบบนี้ไปเรื่อยๆ ถ้าเงื่อนไขใดเป็นจริง คำสั่งในแขนงนั้นจะทำงาน และชุดคำสั่งนี้จะจบการทำงาน แม้ว่ามีเงื่อนไขมากกว่าหนึ่งที่เป็นจริง แต่แค่คำสั่งในแขนงของเงื่อนไขแรกที่เป็นจริงเท่านั้นที่จะทำงาน
เงื่อนไขหนึ่งๆ สามารถซ้อนในเงื่อนไขอื่นได้ เราสามารถเขียนตัวอย่างในหัวข้อที่แล้วให้เป็นแบบนี้ได้:
if x == y: print('x and y are equal') else: if x < y: print('x is less than y') else: print('x is greater than y')
เงื่อนไขด้านนอกมีสองแขนง แขนงแรกมีคำสั่งง่ายๆ คำสั่งเดียว แขนงที่สองมีคำสั่ง if
อีกอันบรรจุอยู่ ซึ่งก็มีเงื่อนไขอีกสองแขนงย่อยลงไปอีก ทั้งสองแขนงข้างในนั้นมีคำสั่งแบบง่ายๆ อยู่ แม้ว่าข้างในสามารถเป็นเงื่อนไขอีกชั้นหนึ่งก็ได้
แม้ว่าการย่อหน้าของคำสั่งต่างๆ ทำให้โปรแกรมมีโครงสร้างที่ชัดเจน แต่ เงื่อนไขซ้อนใน (nested conditionals) มันทำให้อ่านยากเวลาอ่านเร็วๆ จึงเป็นความคิดที่ดีที่จะหลีกเลี่ยงถ้าทำได้
ตัวดำเนินการทางตรรกะสามารถทำให้เราเขียนคำสั่งเงื่อนไขซ้อนในให้ง่ายขึ้น เช่น เราสามารถเขียน โค้ดต่อไปนี้อีกแบบหนึ่ง โดยใช้เงื่อนไขแบบเดี่ยว:
if 0 < x: if x < 10: print('x is a positive single-digit number.')
คำสั่ง print
จะทำงานถ้าเราผ่านเงื่อนไขทั้งสองนี้ได้ ดังนั้น เราสามารถทำให้เกิดผลอย่างเดียวกัน โดยการใช้ตัวดำเนินการ and
:
if 0 < x and x < 10: print('x is a positive single-digit number.')
สำหรับเงื่อนไขประเภทนี้ ไพธอนมีทางเลือกให้ทำแบบกระชับ:
if 0 < x < 10: print('x is a positive single-digit number.')
ฟังก์ชันหนึ่งๆ สามารถเรียกฟังก์ชันอื่นได้ ฟังก์ชันหนึ่งๆ ยังสามารถเรียกตัวเองได้ด้วย มันอาจจะไม่ชัดเจนว่าทำไมมันเป็นสิ่งที่ดี แต่ปรากฎว่ามันเป็นสิ่งมหัศจรรย์อย่างหนึ่งเลย ที่โปรแกรมสามารถทำได้ เช่น ให้ดูฟังก์ชันต่อไปนี้:
def countdown(n): if n <= 0: print('Blastoff!') else: print(n) countdown(n-1)
ถ้า n
เป็น 0 หรือมีค่าลบ มันจะแสดงคำว่า “Blastoff!” ออกมา ไม่เช่นนั้น มันจะแสดงค่า n
ออกมาและเรียกฟังก์ชันที่ชื่อว่า countdown
—ตัวมันเอง—โดยผ่านค่า n-1
เป็นอาร์กิวเมนต์
จะเกิดอะไรขึ้นถ้าเราเรียกฟังก์ชันนี้ในลักษณะนี้?
>>> countdown(3)
การดำเนินการของฟังก์ชัน countdown
เริ่มด้วย n=3
และเนื่องจาก n
มีค่ามากกว่า 0 ฟังก์ชันจะแสดงค่า 3 ออกมาและจากนั้นจึงเรียกตัวมันเอง…
การทำงานของฟังก์ชันcountdown
เริ่มด้วยn=2
และเนื่องจากn
มีค่ามากกว่า 0 ฟังก์ชันจะแสดงค่า 2 ออกมาและจากนั้นจึงเรียกตัวมันเอง…การทำงานของฟังก์ชันcountdown
เริ่มด้วยn=1
และเนื่องจากn
มีค่ามากกว่า 0 ฟังก์ชันจะแสดงค่า 1 ออกมาและจากนั้นจึงเรียกตัวมันเอง…การทำงานของฟังก์ชันcountdown
เริ่มด้วยn=0
และเนื่องจากn
มีค่าไม่มากกว่า 0 ฟังก์ชันจะแสดงคำว่า “Blastoff!” ออกมา จบ และกลับออกไปฟังก์ชัน
countdown
ที่ได้รับค่าn=1
มาก็จบ และกลับออกไปฟังก์ชัน
countdown
ที่ได้รับค่าn=2
มาก็จบ และกลับออกไปฟังก์ชัน
countdown
ที่ได้รับค่าn=3
มาก็จบ และกลับออกไป
และจากนั้นเราก็กลับมายัง __main__
ดังนั้น เอ้าต์พุตทั้งหมดจะหน้าตาเป็นแบบนี้:
3 2 1 Blastoff!
ฟังก์ชันที่เรียกตัวมันเอง คือ ฟังก์ชัน เรียกซ้ำ (recursive) หรือ ฟังก์ชันเวียนเกิด (ในหนังสือเล่มนี้ จะเรียกว่า ฟังก์ชันเรียกซ้ำ) กระบวนการทำงานของมันเรียกว่า การเรียกซ้ำ recursion
อีกตัวอย่างหนึ่ง เราสามารถเขียนฟังก์ชันที่พิมพ์สายอักขระจำนวน n
ครั้ง
def print_n(s, n): if n <= 0: return print(s) print_n(s, n-1)
ถ้า n <= 0
คำสั่ง return จะทำให้จบฟังก์ชัน กระแสการดำเนินการจะกลับไปยังตัวเรียก (caller) ทันที และบรรทัดที่เหลือในฟังก์ชันนั้นจะไม่ถูกรัน
ส่วนที่เหลือของฟังก์ชันนั้นเหมือนกับฟังก์ชัน countdown
: มันแสดง s
และจากนั้นเรียกตัวเองเพื่อแสดง s
ไปอีก $n-1$ ครั้ง ดังนั้น จำนวนบรรทัดของเอ้าต์พุตจะเป็น 1 + (n - 1)
ซึ่งรวมกันแล้วได้ n
บรรทัด
สำหรับตัวอย่างง่ายๆ แบบนี้ มันอาจจะง่ายกว่าที่จะใช้ลูป for
แต่เราจะเห็นตัวอย่างอีกมากในภายหลังที่ยากที่จะเขียนด้วย ลูป for
และง่ายที่จะเขียนด้วยการเรียกซ้ำ (recursion) ดังนั้น จึงเป็นเรื่องที่ดีที่จะเริ่มเข้าใจหัวข้อนี้ก่อน
ในหัวข้อที่ 3.9 เราใช้แผนภาพแบบกองซ้อนในการแสดงสถานะของโปรแกรม ในขณะที่มีการเรียกฟังก์ชัน แผนภาพชนิดเดียวกันนี้สามารถช่วยให้เข้าใจฟังก์ชันเรียกซ้ำ (recursive function) ได้ด้วย
ทุกครั้งที่ฟังก์ชันถูกเรียก ไพธอนจะสร้างกรอบที่บรรจุตัวแปรเฉพาะที่และพารามิเตอร์ของฟังก์ชัน สำหรับฟังก์ชันเรียกซ้ำ มันอาจจะมีกรอบมากกว่าหนึ่งกรอบบนกอง ณ ขณะหนึ่ง
รูปที่ 5.1 แสดงแผนภาพแบบกองซ้อนสำหรับฟังก์ชัน countdown
ที่ถูกเรียกด้วยค่า n = 3
.
เหมือนทั่วไป บนสุดของกองคือกรอบของ __main__
มันว่างเปล่าเพราะว่าเราไม่ได้สร้างตัวแปรใดๆ ใน __main__
หรือไม่ได้ผ่านอาร์กิวเมนต์ใดๆ เข้าไป
กรอบ countdown
ทั้ง 4 กรอบ มีค่าพารามิเตอร์ n
ที่ต่างกัน ล่างสุดของกองซึ่ง n=0
เรียกว่า กรณีฐาน (base case) มันไม่ได้เรียกตัวเองซ้ำ ดังนั้น จึงไม่มีกรอบเพิ่มไปอีก
เพื่อเป็นการฝึกทำ ให้วาดแผนภาพแบบกองซ้อนสำหรับฟังก์ชัน print_n
ที่ถูกเรียกด้วยค่า s = 'Hello'
และ n=2
จากนั้นให้เขียนฟังก์ชันชื่อว่า do_n
ที่รับวัตถุฟังก์ชันและจำนวน n
เข้าเป็นอาร์กิวเมนต์ และมันจะเรียกฟังก์ชันที่กำหนดให้เป็นจำนวน n
ครั้ง
ถ้าการเรียกซ้ำไม่ไปถึงกรณีฐาน (base case) เสียที มันจะทำให้เกิดการเรียกซ้ำไปตลอดกาล และโปรแกรมก็จะไม่จบ นี่เรียกว่า การเรียกซ้ำไม่รู้จบ (infinite recursion) และมันก็ไม่ได้เป็นความคิดที่ดีเท่าไรนัก นี่คือ โปรแกรมแบบสั้นที่สุดที่จะทำให้เกิดการเรียกซ้ำแบบไม่สิ้นสุด:
def recurse(): recurse()
ในสภาพแวดล้อมของการเขียนโปรแกรมส่วนใหญ่ โปรแกรมจะไม่ทำงานไปตลอดกาลแบบนั้น ไพธอนจะรายงานข้อความความผิดพลาดเมื่อมีการเรียกซ้ำจนถึงความลึกที่มากที่สุด (ที่อนุญาตให้รันได้):
File "<stdin>", line 2, in recurse File "<stdin>", line 2, in recurse File "<stdin>", line 2, in recurse . . . File "<stdin>", line 2, in recurse RuntimeError: Maximum recursion depth exceeded
การย้อนรอยแบบนี้มันจะเยอะกว่าที่เราเคยทำในบทก่อนหน้านี้นิดหน่อย เมื่อเกิดข้อผิดพลาดในตัวอย่างนี้ขึ้นมา มันมี 1000 recurse
frames (กรอบการเรียกซ้ำ) บนกองนี้!
ถ้าเราเจอการเรียกซ้ำไม่รู้จบโดยบังเอิญ ให้ทบทวนฟังก์ชันของเราเพื่อให้แน่ใจว่ามันมีกรณีฐาน (base case) ที่ไม่เรียกตัวเองซ้ำ และถ้ามีกรณีฐานแล้ว ให้ตรวจสอบว่าเราจะไปถึงมันจริงๆ
โปรแกรมที่เราเขียนมาถึงตอนนี้ไม่ได้รับอินพุต (input) มาจากผู้ใช้ มันแค่ทำอะไรซ้ำๆ ตลอดเวลา
ไพธอนได้เตรียมฟังก์ชันที่มีอยู่ในตัวเรียกว่า input
ที่หยุดโปรแกรมและรอให้ผู้ใช้พิมพ์อะไรสักอย่างเข้ามา เมื่อผู้ใช้กดปุ่ม Return หรือ Enter โปรแกรมจะทำงานต่อ และฟังก์ชัน input
จะคืนค่า สิ่งที่ผู้ใช้พิมพ์เข้ามาเป็นชนิดสายอักขระ ในไพธอน 2 ฟังก์ชันที่ทำงานแบบเดียวกันนี้เรียกว่า raw_input
>>> text = input() What are you waiting for? >>> text 'What are you waiting for?'
ก่อนที่จะรับอินพุตมาจากผู้ใช้ มันเป็นความคิดที่ดีที่จะพิมพ์ข้อความไปบอกผู้ใช้ว่าให้พิมพ์อะไรเข้ามา ฟังก์ชัน input
สามารถรับข้อความพร้อมรับ (prompt) เป็นอาร์กิวเมนต์ได้:
>>> name = input('What...is your name?\n') What...is your name? Arthur, King of the Britons! >>> name 'Arthur, King of the Britons!'
ลำดับอักขระ \n
ในตอนท้ายของข้อความพร้อมรับเป็นตัวแทนของ บรรทัดใหม่ (newline) ซึ่งเป็นอักขระพิเศษที่ทำให้เกิดการขึ้นบรรทัดใหม่ นั่นคือเหตุผลที่อินพุตของผู้ใช้ปรากฏอยู่ข้างใต้ข้อความพร้อมรับ
ถ้าเราคาดหวังว่าผู้ใช้จะพิมพ์จำนวนเต็มเข้ามา เราสามารถลองแปลงค่าที่คืนกลับมาเป็น int
:
>>> prompt = 'What...is the airspeed velocity of an unladen swallow?\n' >>> speed = input(prompt) What...is the airspeed velocity of an unladen swallow? 42 >>> int(speed) 42
แต่ถ้าผู้ใช้พิมพ์อย่างอื่นที่ไม่ใช่สายอักขระของตัวเลขแล้วล่ะก็ เราก็จะได้ข้อผิดพลาด:
(หมายเหตุผู้แปล: ได้ข้อผิดพลาดตอนที่พยายามแปลงให้เป็น int)
>>> speed = input(prompt) What...is the airspeed velocity of an unladen swallow? What do you mean, an African or a European swallow? >>> int(speed) ValueError: invalid literal for int() with base 10
เราจะรู้ว่าจะจัดการกับข้อผิดพลาดประเภทนี้อย่างไรในภายหลัง
เมื่อเกิดข้อผิดพลาดเชิงวากยสัมพันธ์ (syntax error) หรือข้อผิดพลาดตอนดำเนินการ (runtime error) ข้อความแจ้งข้อผิดพลาด (error message) มีข้อมูลจำนวนมากให้เรา แต่มันจะทำให้เรารู้สึกท้วมท้นมาก ส่วนที่เป็นประโยชน์โดยปกติแล้วจะเป็น:
ข้อผิดพลาดเชิงวากยสัมพันธ์นั้นง่ายที่จะหา แต่มันก็มีข้อต้องระวังนิดหน่อย ข้อผิดพลาดที่เกี่ยวกับการเว้นวรรค (whitespace error) อาจจะทำให้เราปวดหัวได้ เพราะว่าช่องว่างและย่อหน้านั้นมันมองไม่เห็น และเราก็ชินกับการไม่สนใจมัน
>>> x = 5 >>> y = 6 File "<stdin>", line 1 y = 6 ^ IndentationError: unexpected indent
ในตัวอย่างนี้ ปัญหา คือ บรรทัดที่สองนั้นถูกย่อหน้าเข้าไปโดยช่องว่าง 1 ช่อง แต่ข้อผิดพลาดนั้นชี้ไปที่ตัวแปร y ซึ่งอาจจะทำให้ไขว้เขวได้ โดยทั่วไปแล้ว ข้อความแจ้งข้อผิดพลาดจะระบุตำแหน่งตรงที่เจอปัญหา แต่ข้อผิดพลาดจริงๆ อาจจะอยู่ก่อนตำแหน่งที่ระบุก็ได้ บางทีก็อยู่ในบรรทัดก่อนหน้า
เช่นเดียวกันกับข้อผิดพลาดตอนดำเนินการ สมมติว่าเราพยายามที่จะคำนวณอัตราส่วนระหว่าง สัญญาณและสัญญาณรบกวน (signal-to-noise ratio) ในหน่วยเดซิเบล สูตรคือ $SNR_{db} = 10 \log_{10} (P_{signal} / P_{noise})$ ในไพธอน เราน่าจะเขียนประมาณนี้:
import math signal_power = 9 noise_power = 10 ratio = signal_power // noise_power decibels = 10 * math.log10(ratio) print(decibels)
เมื่อเรารันโปรแกรม เราจะได้ข้อยกเว้น:
Traceback (most recent call last): File "snr.py", line 5, in ? decibels = 10 * math.log10(ratio) ValueError: math domain error
ข้อความแจ้งข้อผิดพลาดระบุว่ามีปัญหาที่บรรทัดที่ 5 แต่ก็ไม่เห็นมีอะไรผิดนี่นา เพื่อที่จะหาข้อผิดพลาดที่แท้จริง มันอาจจะเป็นประโยชน์ที่จะพิมพ์ค่าของ ratio
ออกมาดู ซึ่งมีค่าเป็น 0 ปัญหาจึงอยู่ที่บรรทัดที่ 4 ที่ใช้การหารแบบปัดเศษลง แทนที่จะใช้การหารแบบจุดลอย
เราควรที่จะใช้เวลาในการอ่านข้อความแจ้งข้อผิดพลาดอย่างระมัดระวัง แต่อย่าคิดเอาเองว่า ทุกสิ่งมันบอกเรานั้นถูกต้องเป๊ะตามนั้น
//
ซึ่งหารเลขสองตัวและปัดเศษลงให้เป็นจำนวนเต็ม%
) ซึ่งทำงานกับจำนวนเต็มและคืนค่าเป็นเศษของการหารTrue
(จริง) หรือ False
(เท็จ)==
, !=
, >
, <
, >=
, และ <=
and
, or
, และ not
แบบฝึกหัด 1
มอดูล time
มีฟังก์ชันที่ชื่อว่า time
ให้ใช้ ซึ่งคืนค่าเป็นเวลามาตรฐานโลกตามนาฬิกาที่กรีนิช (Greenwich Mean Time: GMT) ณ ปัจจุบัน อ้างอิงจาก “the epoch” ซึ่งเป็นเวลามาตรฐานที่ใช้อ้างอิง บนระบบยูนิกซ์ epoch คือ วันที่ 1 มกราคม ค.ศ. 1970
(หมายเหตุผู้แปล: epoch อ่านว่า เอพ’เพิค คือเวลา 00:00:00 UTC ของวันที่ 1 มกราคม ค.ศ. 1970)
>>> import time >>> time.time() 1437746094.5735958
ให้เขียนสคริปต์ที่อ่านเวลาปัจจุบัน และแปลงให้เป็นเวลาในหน่วยชั่วโมง นาที และวินาที และจำนวนวันตั้งแต่ the epoch
แบบฝึกหัด 2
ทฤษฎีบทสุดท้ายของแฟร์มา (Fermat’s Last Theorem) ระบุว่า ไม่มีจำนวนเต็มบวก $a$, $b$, และ $c$ ใดๆ ที่ทำให้:
$$a^n + b^n = c^n$$ สำหรับค่าใดๆ ของ $n$ ที่มากกว่า 2
check_fermat
ซึ่งรับค่าพารามิเตอร์ 4 ค่า —a
, b
, c
และ n
— และตรวจสอบว่าทฤษฎีบทสุดท้ายของแฟร์มา นั้นจริงหรือไม่ ถ้า $n$ มากกว่า 2 และ
$$a^n + b^n = c^n$$ โปรแกรมควรจะพิมพ์ว่า “Holy smokes, Fermat was wrong!” ไม่เช่นนั้น โปรแกรมควรจะพิมพ์ว่า “No, that doesn’t work.”
a
, b
, c
และ n
จากผู้ใช้ แปลงค่าเหล่านี้ เป็นจำนวนเต็ม และใช้ฟังก์ชัน check_fermat
ตรวจสอบดูว่าค่าเหล่านี้ผ่าฝืนทฤษฎี บทสุดท้ายของแฟร์มาหรือไม่
แบบฝึกหัด 3
ถ้าเราได้กิ่งไม้มาสามชิ้น เราอาจจะหรืออาจจะไม่สามารถที่จะเรียงมันให้เป็น สามเหลี่ยมได้ เช่น ถ้าไม้อันหนึ่งยาว 12 นิ้ว และอีกสองอันยาวอันละ 1 นิ้ว เราจะไม่ สามารถทำให้ไม้แท่งสั้นประกบกันได้ สำหรับความยาวของไม้ทั้งสามแท่ง มันมีการทดสอบแบบ ง่ายๆ เพื่อที่จะดูว่ามันสามารถที่จะประกอบกันเป็นสามเหลี่ยมได้หรือไม่:
ถ้าความยาวของด้านใดด้านหนึ่งนั้นยาวมากกว่าผลรวมของอีกสองด้านที่เหลือ เราจะไม่สามารถสร้างสามเหลี่ยมได้ นอกจากนี้ เราจะสามารถทำได้ (ถ้าผล รวมของสองด้าน เท่ากับด้านที่สาม มันจะประกอบกันเป็นสิ่งที่เรียกว่า สามเหลี่ยมลดรูป หรือ “degenerate” triangle)
(หมายเหตุผู้แปล: สามเหลี่ยมลดรูป หรือ degenerate triangle คือ สามเหลี่ยมที่ไม่เห็น เป็นรูปสามเหลี่ยมทั่วไป แต่เป็นคล้ายๆ เส้นตรงแทน)
is_triangle
ซึ่งรับจำนวนเต็มมาเป็นอาร์กิวเมนต์ และพิมพ์ “Yes” หรือ “No” ขึ้นอยู่กับว่าเราสามารถจะประกอบสาม เหลี่ยมแท่งไม้จากความยาวที่กำหนดให้ได้หรือไม่is_triangle
ตรวจสอบว่าแท่งไม้ที่มีความยาวที่ใส่ เข้ามา จะสามารถประกอบกันเป็นสามเหลี่ยมได้หรือไม่
แบบฝึกหัด 4
ผลลัพธ์ของโปรแกรมต่อไปนี้คืออะไร? ให้วาดรูปแผนภาพกองซ้อนที่แสดงสถานะ ของโปรแกรม เมื่อมันทำการพิมพ์ผลออกมา
def recurse(n, s): if n == 0: print(s) else: recurse(n-1, n+s) recurse(3, 0)
recurse(-1, 0)
?
แบบฝึกหัดต่อไปนี้ใช้มอดูล turtle
ใน บทที่ 4:
แบบฝึกหัด 5
ให้อ่านฟังก์ชันต่อไปนี้ และดูว่าเรารู้ไหมว่ามันทำอะไร (ดูตัวอย่าง ใน บทที่ 4) จากนั้นให้รันฟังก์ชันและดูว่าเราคิดถูกไหม
def draw(t, length, n): if n == 0: return angle = 50 t.fd(length*n) t.lt(angle) draw(t, length, n-1) t.rt(2*angle) draw(t, length, n-1) t.lt(angle) t.bk(length*n)
แบบฝึกหัด 6
เส้นโค้งค็อค (Koch curve) เป็น แฟร็กทัล (fractal) แบบรูปที่ 5.2 ในการจะวาดเส้นโค้งค็อคที่ ยาว $x$ ทั้งหมดที่เราจะต้องทำคือ
ข้อยกเว้นคือ ถ้า $x$ น้อยกว่า 3: เราจะแค่วาดเส้นตรงที่ยาว $x$ ได้เท่านั้น
koch
ซึ่งรับ turtle และความยาวเข้ามา เป็นพารามิเตอร์ และใช้ turtle วาดเส้นโค้งค็อคที่ยาวตามที่กำหนดให้snowflake
ซึ่งวาดเส้นโค้งค็อค 3 เส้น เพื่อจะร่างเส้น ของเกล็ดหิมะhttps://greenteapress.com/thinkpython2/html/thinkpython2006.html