4. คำสั่งควบคุม (More Control Flow Tools)

4.1 ประโยค if ( if Statements)
4.2 ประโยค for ( for Statements)
4.3 ฟังก์ชั่น range() (The range() Function)
4.4 คำสั่ง break และ continue และวลี else สำหรับการวนรอบ ( break and continue Statements, and else Clauses on Loops)
4.5 คำสั่ง pass ( pass Statements)
4.6 นิยามฟังก์ชั่น (Defining Functions)
4.7 เพิ่มเติมเรื่องฟังก์ชั่น (More on Defining Functions)


เมื่อกี้ได้รู้จักคำสั่ง while แล้ว บทนี้เรามารู้จักคำสั่งควบคุมให้มากขึ้น


4.1 ประโยค if ( if Statements)

>>> x = int(raw_input("Please enter an integer: "))
>>> if x < 0:
...      x = 0
...      print 'Negative changed to zero'
... elif x == 0:
...      print 'Zero'
... elif x == 1:
...      print 'Single'
... else:
...      print 'More'
...

มี elif กี่ตัวก็ได้ และมี else หรือไม่มีก็ได้ (ภาษาอื่นอาจมี switch และ case แต่ไพธอนใช้ if อย่างเดียว)


4.2 ประโยค for ( for Statements)

for ของไพธอน ต่างจากภาษาอื่นเล็กน้อย ตอนวนรอบ แทนที่จะใช้ตัวนับซึ่งเป็นตัวเลข ไพธอนกลับใช้ลำดับแทน (เช่น สตริงก์ ลิสต์ หรือทูเปิล)

>>> # Measure some strings:
... a = ['cat', 'window', 'defenestrate']
>>> for x in a:
...     print x, len(x)
... 
cat 3
window 6
defenestrate 12


4.3 ฟังก์ชั่น range() (The range() Function)

ใช้สร้างลิสต์จากช่วงของตัวเลขจำนวนเต็ม
แบบง่าย

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

อาจกำหนดเป็นช่วง

>>> range(5, 10)
[5, 6, 7, 8, 9]

หรือแบบกำหนดขนาดขั้นของการเพิ่มด้วย

>>> range(0, 10, 3)
[0, 3, 6, 9]

>>> range(-10, -100, -30)
[-10, -40, -70]

ใช้ร่วมกับ len() กับลิสต์ (การทำงานกับลิสต์แบบอ้างอิงจากดัชนี จะใช้วิธีนี้เป็นปกติ)

>>> a = ['Mary', 'had', 'a', 'little', 'lamb']
>>> for i in range(len(a)):
...     print i, a[i]
...
0 Mary
1 had
2 a
3 little
4 lamb


4.4 คำสั่ง break และ continue และวลี else สำหรับการวนรอบ ( break and continue Statements, and else Clauses on Loops)

คำสั่ง break ส่งผลให้หลุดจากวงรอบที่คำสั่งนี้บรรจุอยู่
ส่วน continue จะมีผลให้หยุดการทำงานที่จุดนั้น แล้วกลับไปเริ่มวนรอบใหม่
วลี else ใช้สำหรับเมื่อหลุดจากการวนแล้ว จะทำภายในบล๊อกนี้หนึ่งครั้ง ยกเว้นถ้าพบคำสั่ง break

>>> for n in range(2, 10):
...     for x in range(2, n):
...         if n % x == 0:
...             print n, 'equals', x, '*', n/x
...             break
...     else:
...         # loop fell through without finding a factor
...         print n, 'is a prime number'
... 
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3


4.5 คำสั่ง pass ( pass Statements)

คำสั่ง pass ไม่ทำอะไรเลย แต่มีไว้เผื่อเวลาเราวางโครงสร้างโค๊ดไว้แล้ว แต่ยังไม่ได้เขียนท่อนนั้น ก็บรรจุคำสั่งนี้ไว้เพื่อให้สามารถทดสอบการรันได้

>>> while True:
...       pass # Busy-wait for keyboard interrupt
...


4.6 นิยามฟังก์ชั่น (Defining Functions)

เอาตัวอย่างในการเขียนอนุกรมฟิโบแนคซี่มาเขียน

>>> def fib(n):    # write Fibonacci series up to n
...     """Print a Fibonacci series up to n."""
...     a, b = 0, 1
...     while b < n:
...         print b,
...         a, b = b, a+b
... 

>>> # Now call the function we just defined:
... fib(2000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

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

ตัวแปรในฟังก์ชั่นจะถือเป็นตัวแปรท้องถิ่นทั้งหมด เว้นแต่เรากำหนดให้เป็นตัวแปรร่วม ซึ่งต้องกำหนดด้วยคำสั่ง global

การส่งผ่านค่าตัวแปร จะถือเป็นการส่งผ่านโดยค่าทั้งหมด (pass by value)

ชื่อฟังก์ชั่นสามารถถูกกำหนดค่าให้กับตัวแปรได้

>>> fib
<function fib at 10042ed0>

>>> f = fib
>>> f(100)
1 1 2 3 5 8 13 21 34 55 89

>>> f
<function fib at 10042ed0>

ฟังก์ชั่นในไพธอนจะคืนค่ากลับมาเสมอ ซึ่งปกติจะใช้ด้วยคำสั่ง return VALUE แต่ในตัวอย่างข้างต้นไม่มีการคืนค่าด้วยคำสั่ง return กรณีนี้ไพธอนจะคืนค่าเป็นค่าพิเศษคือ None

>>> print fib(0)
None

จากตัวอย่างข้างต้น สามารถเขียนในรูปฟังก์ชั่นที่ส่งคืนค่าดังนี้

>>> def fib2(n): # return Fibonacci series up to 
...     """Return a list containing the Fibonacci series up to n.""
...     result = [
...     a, b = 0, 
...     while b < n
...         result.append(b)    # see belo
...         a, b = b, a+
...     return resul
...
>>> f100 = fib2(100)    # call i
>>> f100                # write the result
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

ความรู้ใหม่

  • ฟังก์ชั่นที่ไม่ระบุการคืนค่า จะส่งค่ากลับเป็น None
  • คำสัง result.append(b) เป็นเมธอดของลิสต์ result เมธอดก็คือฟังก์ชั่นที่เป็นเฉพาะของออปเจคต์นั้น ซึ่งมีรูปแบบการเขียนเป็น obj.methodname
    จากตัวอย่างการใช้เมธอด result.append(b) มีผลเท่ากับ "result = result + [b]" แต่เขียนได้กระชับและเข้าใจง่ายกว่า


4.7 เพิ่มเติมเรื่องฟังก์ชั่น (More on Defining Functions)

มีหลักในการกำหนดค่าอาร์กิวเมนต์คือ

4.7.1 แบบกำหนดค่าปริยาย (Default Argument Values)

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

def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
    while True:
        ok = raw_input(prompt)
        if ok in ('y', 'ye', 'yes'): return True
        if ok in ('n', 'no', 'nop', 'nope'): return False
        retries = retries - 1
        if retries < 0: raise IOError, 'refusenik user'
        print complaint

การใช้งานเช่น ask_ok('Do you really want to quit?')
หรือ ask_ok('OK to overwrite the file?', 2, 'Please answer y or n')
จากตัวอย่างหลัง retries คือ 2 และ complaint คือ 'Please answer y or n'

ในตัวอย่างนี้ มีคำใหม่คือ in เป็นการดูว่าตัวแปร ok อยู่ภายในช่วงที่กำหนดหรือไม่

ข้อควรระวัง

  • การกำหนดค่าให้กับอาร์กิวเมนต์ จะกำหนดในครั้งแรกครั้งเดียว จึงต้องระมัดระวังในการใช้งาน ตามตัวอย่างคือ
    >>> i = 5
    >>> def f(arg=i):
    ...     print arg
    ... 
    >>> f()
    5
    
    >>> i=6
    >>> f()
    5
  • ตัวแปรที่เป็น mutable คือลิสต์ ทูเปิล และอินสแตนซ์ของคลาส ต้องระวังในการใช้งานอย่างยิ่ง เนื่องจากเมื่อมันถูกกำหนดค่าแล้ว ค่าของมันจะยังคงอยู่ภายในฟังก์ชั่นนั้น เวลาอ้างถึงในรอบหลัง ๆ จะทำให้ผิดพลาดได้
    >>> def f(a, L=[]):
    ...     L.append(a)
    ...     return L
    ... 
    >>> print f(1)
    [1]
    
    >>> print f(2)
    [1, 2]
    
    >>> print f(3)
    [1, 2, 3]

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

    def f(a, L=None):
        if L is None:
            L = []
        L.append(a)
        return L
4.7.2 แบบระบุคีย์เวิร์ด (Keyword Arguments)

เหมือนกับหัวข้อก่อนหน้า แต่ในหัวข้อนี้ เจาะจงอธิบายลักษณะที่กำหนดเป็นคีย์เวิร์ด มีรูปแบบคือ "keyword = value"

def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
    print "-- This parrot wouldn't", action,
    print "if you put", voltage, "volts through it."
    print "-- Lovely plumage, the", type
    print "-- It's", state, "!"

การใช้งาน

  • แบบนี้ใช้ได้
    parrot(1000)
    parrot(action = 'VOOOOOM', voltage = 1000000)
    parrot('a thousand', state = 'pushing up the daisies')
    parrot('a million', 'bereft of life', 'jump')
  • แบบนี้ผิด
    parrot()                     # ผิดเพราะขาดค่าที่ไม่มีค่าปริยาย คือ voltage
    parrot(voltage=5.0, 'dead')  # ผิดเพราะค่าปริยายอยู่ซ้าย จริง ๆ ต้องอยู่ขวา
    parrot(110, voltage=220)     # ผิดเพราะกำหนดค่าซ้อน
    parrot(actor='John Cleese')  # ผิดเพราะชื่อไม่มีชื่อคีย์เวิร์ด actor

ตัวอย่างการรายงานข้อผิดพลาดของการกำหนดค่าซ้อน

>>> def function(a):
...     pass
... 
>>> function(0, a=0)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: function() got multiple values for keyword argument 'a'

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

def function_name(normal_parameter, *tuple_parameter, **dictionary_parameter)

ตัวอย่างคือ

def cheeseshop(kind, *arguments, **keywords):
    print "-- Do you have any", kind, '?'
    print "-- I'm sorry, we're all out of", kind
    for arg in arguments: print arg
    print '-'*40
    keys = keywords.keys()
    keys.sort()
    for kw in keys: print kw, ':', keywords[kw]

เรียกใช้ด้วยคำสั่ง

cheeseshop('Limburger', "It's very runny, sir.",
           "It's really very, VERY runny, sir.",
           client='John Cleese',
           shopkeeper='Michael Palin',
           sketch='Cheese Shop Sketch')

ในที่นี้

  • อาร์กิวเมนต์ธรรมดา คือ kind = 'Limburger'
  • อาร์กิวเมนต์ที่เป็นทูเปิล คือ arguments = ("It's very runny, sir.", "It's really very, VERY runny, sir.")
  • อาร์กิวเมนต์ที่เป็นดิกชันนารี คือ keywords = { client:'John Cleese', shopkeeper:'Michael Palin', sketch:'Cheese Shop Sketch' }

ผลลัพธ์คือ

-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
client : John Cleese
shopkeeper : Michael Palin
sketch : Cheese Shop Sketch

ความรู้ใหม่
จากตัวอย่างเรียกใช้เมธอด sort() ในการเรียงข้อมูลดัชนี ซึ่งเป็นเมธอดของลิสต์
ลิสต์นี้ได้มาจากการหาดัชนีของดิกชันนารี keyword ได้ออกมาเป็นลิสต์ชื่อ keys ด้วยเมธอดของดิกชันนารีคือ keys()

4.7.3 การกำหนดจำนวนอาร์กิวเมนต์ที่ยืดหยุ่น (Arbitrary Argument Lists)

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

>>> def testparm(x, *y):
...   print 'x=',x,'y=',y
...
>>> testparm('a',1,2,3)
x= a y= (1, 2, 3)

หรือ

>>> def testparm(x, *y):
...   print 'x=', x, 'y=', 
...   for i in y:
...     print i,
... 

>>> testparm('a',1,2,3)
x= a y= 1 2 3

ตัวอย่างในบทความต้นฉบับคือ

def fprintf(file, format, *args):
    file.write(format % args)
4.7.4 ถอดอาร์กิวเมนต์จากลิสต์หรือดิกชันนารี (Unpacking Argument Lists)

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

>>> range(3, 6)             # normal call with separate arguments
[3, 4, 5]

>>> args = [3, 6]
>>> range(*args)            # call with arguments unpacked from a list
[3, 4, 5]

ตัวอย่างนี้เป็นดิกชันนารี

>>> def parrot(voltage, state='a stiff', action='voom'):
...     print "-- This parrot wouldn't", action,
...     print "if you put", voltage, "volts through it.",
...     print "E's", state, "!"
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
4.7.5 แลมบ์ด้า (Lambda Forms)

ยืมความสามารถเรื่อง Functional Programming แบบภาษา Lisp มาใช้
โครงสร้างชวนเวียนหัวหน่อย ดูตัวอย่างดีกว่า
แบบไม่ใช้ lambda

>>> def f(x):
...   return x*2
... 
>>> f(3)
6

ใช้ lambda แบบแรก

>>> g = lambda x: x*2
>>> g(3)
6

ใช้ lambda แบบชั่วคราวจริง ๆ

>>> (lambda x: x*2)(3)
6

อีกตัวอย่างนึง ใช้ผสมกับฟังก์ชั่น

>>> def make_incrementor(n):
...     return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42

>>> f(1)
43
4.7.6 ข้อความอธิบายการทำงาน (Documentation Strings)

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

สามารถเรียกดูข้อความในบรรทัดนี้ได้จากเมธอด function_name.__doc__

ตัวอย่าง

>>> def my_function():
...     """Do nothing, but document it.
... 
...     No, really, it doesn't do anything.
...     """
...     pass
... 

>>> print my_function.__doc__
Do nothing, but document it.

    No, really, it doesn't do anything.