Python Tutorial (ลูบไพธอน)

เอามาจาก Python Tutorial

ตั้งใจเขียนให้อ่านสนุก ๆ นะครับ ห้ามจริงจังเด็ดขาด

1. เกริ่น (Whetting Your Appetite)

ไพธอนเป็น ...

  • ภาษาสคริปต์ ทำงานแบบแปลทีละบรรทัดคำสั่ง (Interpreter)
  • มีหมวดโต้ตอบ (Interactive) ทำให้ทดลองเล่น และทดสอบแบบสั้น ๆ ได้
  • สามารถเขียนให้สั้นกระชับและอ่านง่าย เนื่องจากใช้ย่อหน้าในการกำหนดบล๊อก
  • สามารถขยายเชื่อมต่อกับภาษา C ได้
  • รันได้ค่อนข้างเร็ว เมื่อเทียบกับภาษาสคริปต์ตัวอื่น ๆ ซึ่งอาจดูผลทดลองเปรียบเทียบได้ที่ shootout.alioth.debian.org: Python benchmarks

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

$ sudo aptitude install python

2. ใช้งานไพธอนแบบบรรทัดคำสั่ง (Using the Python Interpreter)

2.1 เรียกใช้ตัวแปลบรรทัดคำสั่ง (Invoking the Interpreter)
2.2 ตัวแปลคำสั่งและสภาพแวดล้อม (The Interpreter and Its Environment)



2.1 เรียกใช้ตัวแปลบรรทัดคำสั่ง (Invoking the Interpreter)

ในเดเบียนตัวโปรแกรมไพธอนจะอยู่ที่ /usr/bin/python ซึ่งจะเป็นลิงก์โยงไปหาตัวไพธอนรุ่นที่เราติดตั้งจริง ๆ เช่น

$ which python
/usr/bin/python
$ ls -l /usr/bin/python
lrwxrwxrwx 1 root root 9 2006-12-26 19:18 /usr/bin/python -> python2.4

จะเห็นว่ารุ่นของไพธอนตามตัวอย่าง เป็นรุ่น 2.4

ซึ่งไดเรกทอรี่ /usr/bin จะอยู่ในพาธการค้นหาโปรแกรมอยู่แล้ว ดังนั้นเราสามารถเรียกใช้ได้ง่าย ๆ ว่า

$ python

สำหรับลินุกซ์ดิสโตรอื่นก็คล้าย ๆ กัน

ในวินโดวส์ เมื่อติดตั้งไพธอนเสร็จแล้ว ตัวโปรแกรมจะไปอยู่ที่ C:\Python24 ซึ่งเราอาจต้องเพิ่มในพาธเอง ด้วยการเข้าสู่ Command Prompt และพิมพ์ดังนี้

set path=%path%;C:\python24

ทั้งหมดนี้เป็นการเรียกใช้ตัวแปลบรรทัดคำสั่งของไพธอน ซึ่งจะทำให้เราสามารถใช้งานแบบโต้ตอบกับตัวแปลภาษาได้
แต่ยังมีอีกกรณีหนึ่งคือ หากเราต้องการเพียงแค่ทดลองเพียงคำสั่งเดียวแล้วออกจากไพธอนเลย เราอาจใช้รูปแบบเป็น python -c command [arg] เช่น

$ python -c 'print "abcd"'
abcd

2.1.1 การส่งผ่านค่า (Argument Passing)

เมื่อเรียกใช้ตัวแปลบรรทัดคำสั่ง ค่าตามหลังที่เราส่งผ่านให้กับโปรแกรม จะถูกเก็บไว้ที่ตัวแปร sys.argv โดย...

  • ถ้าหากเรียกโดยไม่มีค่าตามหลัง เช่น python : sys.argv[0] จะเป็นสตริงก์ว่าง
  • หากเรียกด้วย python - : sys.argv[0] จะบรรจุค่า "-"
  • หากเรียกด้วยตัวเลือก -c หรือ -m : sys.argv[0] จะเก็บค่า "-c" และชื่อมอดูลตามลำดับ ค่าอื่นหลังจากนี้ จะถูกเก็บไว้ในตัวแปร sys.argv เพื่อให้คำสั่งหรือมอดูลได้เรียกใช้งานต่อไป
2.1.2 ใช้งานแบบโต้ตอบ (Interactive Mode)

พร้อมต์หลัก (Primary prompt) คือ >>>

$ python
Python 2.4.4 (#2, Apr  5 2007, 20:11:18) 
[GCC 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 

แต่หากยังไม่จบบล๊อก จะใช้พร้อมต์ตาม (Secondary promt) คือ ... ดังนี้

>>> the_world_is_flat = 1
>>> if the_world_is_flat:
...     print "Be careful not to fall off!"
... 
Be careful not to fall off!


2.2 ตัวแปลคำสั่งและสภาพแวดล้อม (The Interpreter and Its Environment)

2.2.1 การจัดการข้อผิดพลาด (Error Handling)

เมื่อเกิดข้อผิดพลาด ไพธอนจะรายงานข้อผิดพลาดและรายทางของการผิดพลาด โดย

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

รายงานความผิดพลาดทั้งหมดจะถูกส่งไปยังสายการรายงานข้อผิดพลาดของระบบ (standard error stream) และการแสดงผลก็จะถูกส่งไปยังเอาต์พุตของระบบเช่นกัน

การกดคีย์เพื่อขัดจังหวะ เช่น Control-C หรือ DEL จะ...

  • ถ้าขณะอยู่ระหว่างเครื่องหมายพร๊อมต์ จะยกเลิกการป้อนข้อมูลทั้งหมด และกลับสู่พร้อมต์หลัก
  • ถ้าเป็นขณะการรัน ข้อยกเว้น KeyboardInterrupt จะถูกยกขึ้นมา ซึ่งเราสามารถจัดการได้โดยผ่านประโยคคำสั่ง try
2.2.2 การทำให้สคริปต์รันได้ (Executable Python Scripts)

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

#! /usr/bin/env python

อย่าลืมเปลี่ยนโหมดให้รันได้ด้วย

$ chmod +x myscript.py
2.2.3 การแจ้งการเข้ารหัสอักขระของโค๊ด (Source Code Encoding)

หากมีส่วนของโค๊ดที่ไม่ใช่ภาษาอังกฤษล้วน ควรแจ้งการเข้ารหัสอักขระด้วย ด้วยการเติมต่อจากบรรทัดแรกว่า

# -*- coding: encoding -*- 

สามารถดูรหัสอักขระทั้งหมดที่ Python Library Reference ในหัวข้อ codecs

เช่น ถ้าจะกำหนดค่าตัวแปรให้มีสัญญลักษณ์การเงินยูโร เราอาจเลือกใช้การเข้ารหัสอักขระเป็น ISO-8859-15 (ค่า ordinal คือ 164) ซึ่งในสคริปต์นี้จะพิมพ์ค่า 8364 ซึ่งเป็นค่ายูนิโค๊ดของสัญญลักษณ์ยูโร

# -*- coding: iso-8859-15 -*-
 
currency = u"€"
print ord(currency)

แต่สำหรับยุคนี้ ควรเลือกใช้ UTF-8 ดีกว่าเยอะ เพราะรองรับอักขระทุกตัวในโลก

2.2.4 การสร้างไฟล์เริ่มต้นสำหรับหมวดโต้ตอบ (The Interactive Startup File)

ในการใช้งานหมวดโต้ตอบ บางครั้งอาจต้องตั้งค่าเริ่มต้นให้ระบบ โดยการใส่ชื่อโปรแกรมที่ต้องการรันในการเริ่มต้นให้กับตัวแปรแวดล้อมชื่อ PYTHONSTARTUP (คล้ายกับการตั้งค่าไฟล์ .profile ในลินุกซ์)

เราสามารถขยายความสามารถนี้ โดยให้ไฟล์เริ่มต้นหลักมาดูในไดเรคทอรี่ปัจจุบันก่อน ว่ามีไฟล์ที่มีชื่อตามที่เรากำหนดหรือไม่ ถ้ามีก็จะรันตามที่เรากำหนดไว้ เช่นถ้ากำหนดให้ใช้ชื่อว่า .pythonrc.py ก็ใช้คำสั่งว่า "if os.path.isfile('.pythonrc.py'): execfile('.pythonrc.py')" เป้นต้น

การสร้างไฟล์เริ่มต้นทำได้โดยบรรจุคำสั่งนี้ในสคริปต์

import os
filename = os.environ.get('PYTHONSTARTUP')
if filename and os.path.isfile(filename):
    execfile(filename)

3. รู้จักไพธอน (An Informal Introduction to Python)

3.1 ใช้งานเป็นเครื่องคิดเลข (Using Python as a Calculator)
3.2 ลองเขียนสักสคริปต์นึง (First Steps Towards Programming)


ตัวอย่างจะใช้เครื่องหมาย # แทนคอมเมนต์
เช่น

# this is the first comment
SPAM = 1                 # and this is the second comment
                         # ... and now a third!
STRING = "# This is not a comment."


3.1 ใช้งานเป็นเครื่องคิดเลข (Using Python as a Calculator)

3.1.1 ตัวเลข (Numbers)

ในหมวดโต้ตอบ เราใช้แทนเครื่องคิดเลขได้เลย เช่น

>>> 2+2
4

>>> # This is a comment
... 2+2
4

>>> 2+2  # and a comment on the same line as code
4

>>> (50-5*6)/4
5

>>> # Integer division returns the floor:
... 7/3
2

>>> 7/-3
-3

กำหนดค่าให้ตัวแปรด้วย = ตามปกติ

>>> width = 20
>>> height = 5*9
>>> width * height
900

กำหนดทีละหลายตัวแปรพร้อมกันก็ได้

>>> x = y = z = 0  # Zero x, y and z
>>> x
0

>>> y
0

>>> z
0

รองรับทศนิยมลอยด้วย โดยมีหลักว่า ถ้าต้นทางเป็นจำนวนเต็ม ผลจะเป็นจำนวนเต็มด้วย ถ้าต้นทางเป็นทศนิยมลอย ผลจะเป็นทศนิยมลอยด้วย (ถ้าผสมกันก็จะเป็นทศนิยมลอยเช่นกัน)

>>> 3 * 3.75 / 1.5
7.5

>>> 7 / 2
3

>>> 7.0 / 2
3.5

จำนวนเชิงซ้อนก็ได้ โดยตัวเลขจินตภาพจะต้องต่อท้ายด้วย "j" หรือ "J" โดยมีรูปแบบเป็น "(real+imagj)" หรือเขียนแบบฟังก์ชั่นว่า "complex(real, imag)"

>>> 1j * 1J
(-1+0j)

>>> 1j * complex(0,1)
(-1+0j)

>>> 3+1j*3
(3+3j)

>>> (3+1j)*3
(9+3j)

>>> (1+2j)/(1+1j)
(1.5+0.5j)

ตัวเลขในจำนวนเชิงซ้อน จะถูกตีความเป็นทศนิยมลอยเสมอ และสามารถแยกตัวเลขจริงกับตัวเลขจินตภาพด้วยการเขียนในรูปแบบออปเจคต์คือ z.real และ z.imag

>>> a=1+0.5j
>>> a.real
1.0

>>> a.imag
0.5

ไม่สามารถใช้ฟังก์ชั่นการแปลงตัวเลขปกติ คือ float(), int() และ long() กับจำนวนเชิงซ้อนได้
แต่สามารถใช้ฟังก์ชั่น abs(z) หาค่าสัมบูรณ์ และใช้ z.real หาค่าจำนวนจริงของ z ได้

>>> a=3.0+4.0j

>>> float(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: can't convert complex to float; use abs(z)

>>> a.real
3.0

>>> a.imag
4.0

>>> abs(a)  # sqrt(a.real**2 + a.imag**2)
5.0

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

>>> tax = 12.5 / 100
>>> price = 100.50
>>> price * tax
12.5625

>>> price + _
113.0625

>>> round(_, 2)
113.06

*** เนื่องจาก _ เป็นตัวแปรพิเศษดังกล่าว ดังนั้นเพื่อป้องกันความผิดพลาด จึงไม่ควรกำหนดค่าให้มัน ควรใช้เป็นตัวแปรเฉพาะตามวัตถุประสงค์ของไพธอนเท่านั้น

3.1.2 สตริงก์ (Strings)

สามารถใช้งานสตริงก์ได้ทั้งอัญประกาศเดี่ยวและคู่ (Single & Double quote)

>>> 'spam eggs'
'spam eggs'

>>> 'doesn\'t'
"doesn't"

>>> "doesn't"
"doesn't"

>>> '"Yes," he said.'
'"Yes," he said.'

>>> "\"Yes,\" he said."
'"Yes," he said.'

>>> '"Isn\'t," she said.'
'"Isn\'t," she said.'

ใช้สตริงก์แบบหลายบรรทัด โดย...

  • ใช้อักขระ \ (Backslash) ในการแยก เช่น
    >>> hello = "This is a rather long string containing\n\
    ... several lines of text just as you would do in C.\n\
    ...     Note that whitespace at the beginning of the line is\
    ...  significant."
    
    >>> print hello
    This is a rather long string containing
    several lines of text just as you would do in C.
        Note that whitespace at the beginning of the line is significant.
  • ใช้สตริงก์ดิบ (raw string) โดยใช้ r นำหน้าเครื่องหมายอัญประกาศ ซึ่งก็จะให้ผลแบบดิบ ๆ
    >>> hello = r"This is a rather long string containing\n\
    ... several lines of text much as you would do in C."
    
    >>> print hello
    This is a rather long string containing\n\
    several lines of text much as you would do in C.
  • หรือใช้ตรีอัญประกาศ คือ """ หรือ ''' อันนี้ใช้ง่ายแบบธรรมชาติ
    >>> print """
    ... Usage: thingy [OPTIONS] 
    ...      -h                        Display this usage message
    ...      -H hostname               Hostname to connect to
    ... """
    Usage: thingy [OPTIONS] 
         -h                        Display this usage message
         -H hostname               Hostname to connect to
  • ที่สำคัญคือ เริ่มต้นด้วยอัญประกาศแบบไหน ก็ต้องปิดท้ายด้วยอัญประกาศแบบนั้นเสมอ

ใช้ + ในการเชื่อมสตริงก์ และ * ในการเชื่อมแบบซ้ำ ๆ

>>> word = 'Help' + 'A'
>>> word
'HelpA'

>>> '<' + word*5 + '>'
'<HelpAHelpAHelpAHelpAHelpA>'

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

>>> 'str' 'ing'                   #  <-  This is ok
'string'

>>> 'str'.strip() + 'ing'   #  <-  This is ok
'string'

>>> 'str'.strip() 'ing'     #  <-  This is invalid
  File "<stdin>", line 1, in ?
    'str'.strip() 'ing'
                      ^
SyntaxError: invalid syntax

สตริงก์ทำตัวเป็น อาเรย์ของอักขระ ดังนั้นจึงสามารถอ้างถึงแบบอาเรย์ได้ โดยอ้างเป็นช่วงด้วย : (colon) ซึ่งไพธอนเรียกช่วงของสตริงก์นี้ว่าสไลซ์ (slice)

>>> word[4]
'A'

>>> word[0:2]
'He'

>>> word[2:4]
'lp'

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

>>> word[:2]    # The first two characters
'He'

>>> word[2:]    # Everything except the first two characters
'lpA'

สตริงก์ในไพธอนไม่เหมือนกับภาษาซี ไพธอนไม่สามารถเปลี่ยนค่าสคริงก์โดยอ้างจากดัชนีได้

>>> word[0] = 'x'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment

>>> word[:1] = 'Splat'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: object doesn't support slice assignment

แต่ก็สร้างใหม่ได้ไม่ยาก

>>> 'x' + word[1:]
'xelpA'

>>> 'Splat' + word[4]
'SplatA'

>>> x = word[:]
>>> x = 'Splat' + x[4]
>>> x
'SplatA'

ลูกเล่นเล็กน้อย ให้ผลเป็น s เหมือนเดิม

>>> word[:2] + word[2:]
'HelpA'

>>> word[:3] + word[3:]
'HelpA'

ดัชนีที่มีค่านอกช่วงที่มีจริง ไพธอนจะแสดงเป็นอักขระว่างให้ โดยไม่รายงานความผิดพลาด

>>> word[1:100]
'elpA'

>>> word[10:]
''

>>> word[2:1]
''

ดัชนีเป็นค่าลบ จะเป็นการนับจากขวามาซ้าย

>>> word[-1]     # The last character
'A'

>>> word[-2]     # The last-but-one character
'p'

>>> word[-2:]    # The last two characters
'pA'

>>> word[:-2]    # Everything except the last two characters
'Hel'

ยกเว้น -0 มีค่าเท่ากับ 0 จึงนับจากซ้ายเป็นปกติ

>>> word[-0]     # (since -0 equals 0)
'H'

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

>>> word[-100:]
'HelpA'

>>> word[-10]    # error
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
IndexError: string index out of range

เทคนิกการจำเรื่องช่วงดัชนีคือ ให้คิดว่าดัชนีเป็นค่าที่อยู่ระหว่างอักขระ ที่ซ้ายสุดเป็น 0 และขวาสุดเป็นขนาดสตริงก์

 +---+---+---+---+---+ 
 | H | e | l | p | A |
 +---+---+---+---+---+ 
 0   1   2   3   4   5 
-5  -4  -3  -2  -1

สำหรับดัชนีที่เป็นค่าบวก ขนาดของสไลซ์คือผลต่างของค่าตัวเลขดัชนี เช่นขนาดของ word[1:3] คือ 2

เราหาขนาดของสตริงก์ด้วยฟังก์ชั่น len()

>>> s = 'supercalifragilisticexpialidocious'
>>> len(s)
34

ดูเพิ่มเติม
เรื่อง

3.1.3 สตริงแบบยูนิโค๊ด (Unicode Strings)

เริ่มใช้ในไพธอนรุ่น 2.0 ในการจัดการข้อมูลแบบยูนิโค๊ด (ดู http://www.unicode.org/)

ใช้งานเหมือนสตริงก์ปกติ เวลาเขียนใช้ u นำหน้าอัญประกาศ

>>> u'Hello World !'
u'Hello World !'

ถ้าอักขระยูนิโค๊ดตัวไหนพิมพ์ยาก อาจใช้ \u (Python Unicode-Escape) นำหน้า
แสดงตัวอย่างเป็นอักขระเคาะวรรค (space) คือ \u0020

>>> u'Hello\u0020World !'
u'Hello World !'

ในการใช้สตริงก์ดิบกับยูนิโค๊ด ใช้ ur นำหน้า และต้องใส่อักขระ \ สองตัว ไม่งั้นจะตีความเป็น Python Unicode-Escape ดูยุ่งยากเล็กน้อย แต่จะมีประโยชน์สำหรับการใช้งาน regular expresstion

>>> ur'Hello\u0020World !'
u'Hello World !'

>>> ur'Hello\\u0020World !'
u'Hello\\\\u0020World !'

จริง ๆ แล้ว ไพธอนเก็บข้อมูลสตริงก์เป็นอักขระยูนิโค๊ดอยู่แล้ว (อาจเปลี่ยนแปลงในรุ่นหน้า คือรุ่น 3.0) ดังนั้นการใช้ฟังก์ชั่น str() กับอักขระยูนิโค๊ด จึงแสดงข้อผิดพลาด เว้นเสียแต่ว่าอักขระเหล่านั้นมีรหัส ASCII น้อยกว่า 127 คือเป็นภาษาอังกฤษธรรมดา

>>> u"abc"
u'abc'

>>> str(u"abc")
'abc'

>>> u"äöü"
u'\xe4\xf6\xfc'

>>> str(u"äöü")
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)

การแปลงการเข้ารหัสอักขระเป็นรหัสอักขระต่าง ๆ เราใช้ฟังก์ชั่น encode()

>>> u"äöü".encode('utf-8')
'\xc3\xa4\xc3\xb6\xc3\xbc'

เป็นการแปลงสตริงก์ u"äöü" ไปเป้นสตริงก์แบบ utf-8

และใช้ฟังก์ชั่น unicode() ในการแปลงกลับ

>>> unicode('\xc3\xa4\xc3\xb6\xc3\xbc', 'utf-8')
u'\xe4\xf6\xfc'
3.1.4 ลิสต์ (Lists)

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

>>> a = ['spam', 'eggs', 100, 1234]
>>> a
['spam', 'eggs', 100, 1234]

การใช้ดัชนีในการอ้างถึงข้อมูลย่อยภายใน ใช้งานคล้ายกับสตริงก์

>>> a[0]
'spam'

>>> a[3]
1234

>>> a[-2]
100

>>> a[1:-1]
['eggs', 100]

>>> a[:2] + ['bacon', 2*2]
['spam', 'eggs', 'bacon', 4]

>>> 3*a[:3] + ['Boo!']
['spam', 'eggs', 100, 'spam', 'eggs', 100, 'spam', 'eggs', 100, 'Boo!']

แต่ต่างจากสตริงก์ตรงที่ข้อมูลย่อยภายในสามารถเปลี่ยนแปลงค่าได้ โดยการอ้างจากดัชนี

>>> a
['spam', 'eggs', 100, 1234]

>>> a[2] = a[2] + 23
>>> a
['spam', 'eggs', 123, 1234]

>>> a[2] = 10
>>> a
['spam', 'eggs', 10, 1234]

กำหนดค่าเป็นช่วงสไลซ์ก็ได้

>>> # Replace some items:
... a[0:2] = [1, 12]
>>> a
[1, 12, 123, 1234]

>>> # Remove some:
... a[0:2] = []
>>> a
[123, 1234]

>>> # Insert some:
... a[1:1] = ['bletch', 'xyzzy']
>>> a
[123, 'bletch', 'xyzzy', 1234]

>>> # Insert (a copy of) itself at the beginning
... a[:0] = a
>>> a
[123, 'bletch', 'xyzzy', 1234, 123, 'bletch', 'xyzzy', 1234]

>>> # Clear the list: replace all items with an empty list
... a[:] = []
>>> a
[]

ใช้ฟังก์ชั่น len() ในการหาขนาดลิสต์ (จำนวนสมาชิก)

>>> len(a)
8

ซ้อนลิสต์ในลิสต์ก็ได้

>>> q = [2, 3]
>>> p = [1, q, 4]
>>> len(p)
3

>>> p[1]
[2, 3]

>>> p[1][0]
2

>>> p[1].append('xtra')     # See section 5.1
>>> p
[1, [2, 3, 'xtra'], 4]

>>> q
[2, 3, 'xtra']

จากตัวอย่างนี้ ตัวแปร p[1] กับตัวแปร q เป็นออปเจกต์อันเดียวกัน (การใช้ลิสต์ต้องระวังตรงนี้นิดนึง)


3.2 ลองเขียนสักสคริปต์นึง (First Steps Towards Programming)

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

>>> # Fibonacci series:
... # the sum of two elements defines the next
... a, b = 0, 1

>>> while b < 10:
...       print b
...       a, b = b, a+b
... 
1
1
2
3
5
8

ความรู้ใหม่

  • บรรทัดแรกเป็นการกำหนดหลายตัวแปรทีละหลายค่า (multiple assignment) ในที่นี้ a คือ 0 และ b คือ 1
  • การวนรอบโดยใช้คำสั่ง while คำสั่งนี้จะตีความว่า นิพจน์หลังคำสั่ง while จะเป็นอะไรก็ได้ที่ไม่ใช่ศุนย์หรือค่าว่าง จะเป็นจริงเสมอ โดยที่การเปรียบเทียบจะใช้สัญญลักษณ์เหมือนภาษาซี คือ < คือน้อยกว่า > คือมากกว่า == คือเท่ากันกับ <= คือน้อยกว่าหรือเท่ากับ >= คือมากกว่าหรือเท่ากับ และ != คือไม่เท่ากับ
  • สักเกตุการเยื้องของบล๊อก ซึ่งไพธอนใช้การเยื้องในการจัดกลุ่มบล๊อก (สำหรับกลุ่มเดียวกัน ต้องเยื้องให้เท่ากัน)
  • คำสั่ง print มีข้อพิเศษคือ
    • ไม่เกี่ยงชนิดข้อมูล ทำให้เขียนโค๊ดง่าย
      >>> i = 256*256
      >>> print 'The value of i is', i
      The value of i is 65536
    • ใช้ , หากไม่ต้องการขึ้นบรรทัดใหม่
      >>> a, b = 0, 1
      >>> while b < 1000:
      ...     print b,
      ...     a, b = b, a+b
      ... 
      1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

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.

5. โครงสร้างข้อมูล (Data Structure)

5.1 ลิสต์อีกที (More on Lists)
5.2 ประโยค del (The del statement)
5.3 ทูเปิล (Tuples and Sequences)
5.4 เซ็ต (Sets)
5.5 ดิกชันนารี (Dictionaries)
5.6 เทคนิกการวนรอบ (Looping Techniques)
5.7 เงื่อนไข (More on Conditions)
5.8 น้ำหนักของข้อมูลแบบลำดับ (Comparing Sequences and Other Types)


บทนี้จะอธิบายเทคนิกการใช้งานข้อมูล


5.1 ลิสต์อีกที (More on Lists)

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

append(x)
เติมสมาชิกต่อท้ายลิสต์ มีค่าเทียบเท่า a[len(a):] = [x]

>>> a=[1,2,3]
>>> a.append(4)
>>> a
[1, 2, 3, 4]
extend(L)
ยกลิสต์ L ทั้งยวง ไปขยายต่อท้าย a คือ a[len(a):] = L

>>> a=[1,2,3]
>>> L=[4,5,6]
>>> a.extend(L)
>>> a
[1, 2, 3, 4, 5, 6]
insert(i, x)
แทรกสมาชิก x ในตำแหน่ง i

  • a.insert(0, x) ก็คือการไปแทรกข้างหน้า
  • a.insert(len(a), x) ก็คือการไปต่อท้าย คือ a.append(x)
>>> a=[1,2,3]
>>> a.insert(2,0)
>>> a
[1, 2, 0, 3]
remove(x)
ลบสมาชิกตัวแรกที่มีค่าเท่ากับ x ถ้าไม่มีเท่าจะเกิดข้อผิดพลาด

>>> a=[1,2,3]
>>> a.remove(2)
>>> a
[1, 3]

>>> a.remove(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: list.remove(x): x not in list
pop([i])
ถอดสมาชิกในตำแหน่ง i ออก ถ้าละเลยไม่ใส่ค่า i จะถอดตัวสุดท้ายออกแทน

>>> a=[1,2,3]
>>> a.pop(1)
2

>>> a
[1, 3]
index(x)
คืนค่าดัชนีของลิสต์ ตัวที่มีค่าเท่ากับ x ถ้าไม่มีค่าเท่าเลยจะแจ้งความผิดพลาด

>>> a=[1,2,3]
>>> a.index(2)
1
count(x)
นับจำนวนสมาชิกที่มีค่าเท่ากับ x

>>> a=[2,2,2,9,4,4,4,4]
>>> a.count(2)
3
sort()
จัดเรียงสมาชิก

>>> a=[1,5,4,2,3]
>>> a.sort()
>>> a
[1, 2, 3, 4, 5]
reverse()
กลับตำแหน่งสมาชิก

>>> a=[1,5,4,2,3]
>>> a.reverse()
>>> a
[3, 2, 4, 5, 1]


ตัวอย่างรวมอีกทีนึง

>>> a = [66.25, 333, 333, 1, 1234.5]
>>> print a.count(333), a.count(66.25), a.count('x')
2 1 0

>>> a.insert(2, -1)
>>> a.append(333)
>>> a
[66.25, 333, -1, 333, 1, 1234.5, 333]

>>> a.index(333)
1

>>> a.remove(333)
>>> a
[66.25, -1, 333, 1, 1234.5, 333]

>>> a.reverse()
>>> a
[333, 1234.5, 1, 333, -1, 66.25]

>>> a.sort()
>>> a
[-1, 1, 66.25, 333, 333, 1234.5]
5.1.1 ทำลิสต์เป็นสแต็ค (Using Lists as Stacks)

ถ้าจะใช้งานลิสต์แบบสแต็คคือ เข้าก่อนออกหลัง ก็แค่ใช้เมธอดให้เหมาะสม คือเติมด้วย append() แล้วเอาออกด้วย pop()

>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]

>>> stack.pop()
7

>>> stack
[3, 4, 5, 6]

>>> stack.pop()
6

>>> stack.pop()
5

>>> stack
[3, 4]
5.1.2 ใช้งานลิสต์แบบคิว (Using Lists as Queues)

คือ เข้าก่อนออกก่อน ก็ใช้ append() และ pop(0) ตามลำดับ

>>> queue = ["Eric", "John", "Michael"]
>>> queue.append("Terry")           # Terry arrives
>>> queue.append("Graham")          # Graham arrives
>>> queue.pop(0)
'Eric'

>>> queue.pop(0)
'John'

>>> queue
['Michael', 'Terry', 'Graham']
5.1.3 ฟังก์ชั่นที่ใช้ในการโปรแกรมแบบฟังก์ชั่น (Functional Programming Tools)

มี 3 ตัว (ไม่นับคำสั่ง lambda)

filter(function, sequence)
filter จะกรองผลลัพธ์จากข้อมูลนำเข้าภายในช่วง sequence เฉพาะถ้า function คืนค่าที่เป็นจริง (ไม่ใช่ศูนย์หรือ None) งงนิดหน่อย ดูตัวอย่างดีกว่า เป็นการหาจำนวนเฉพาะ

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]
map(function, sequence)
map จะง่ายกว่า คือจะเอาค่าจาก sequence ไปทำงานใน function แล้วคืนช่วงของผลลัพธ์ออกมาเป็นลิสต์

>>> def cube(x): return x*x*x
...
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

หาก function ต้องการอาร์กิวเมนต์มากกว่าหนึ่งตัว ก็ต้องใส่ sequence ด้วยจำนวนที่เท่ากัน

>>> seq = range(8)
>>> def add(x, y): return x+y
... 
>>> map(add, seq, seq)
[0, 2, 4, 6, 8, 10, 12, 14]

>>> s2 = range(10,18)
>>> map(add, seq, s2)
[10, 12, 14, 16, 18, 20, 22, 24]

>>> s3 = range(18, 10, -1)
>>> map(add, seq, s3)
[18, 18, 18, 18, 18, 18, 18, 18]
reduce(function, sequence)
เอาผลลัพธ์จาก sequence ชุดแรก ไปเป็นข้อมูลนำเข้าให้ function กับ sequence ชุดต่อ ๆ ไป จนหมดข้อมูล

>>> def add(x,y): return x+y
...
>>> reduce(add, range(1, 11))
55

ถ้าไม่มีข้อมูลจาก sequence จะแสดงข้อผิดพลาด

>>> reduce(add, range(0))
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: reduce() of empty sequence with no initial value

เพื่อเป็นการป้องการการผิดพลาดดังกล่าว อาจใส่อาร์กิวเมนต์ตัวที่สามซึ่งจะกลายเป็นค่าเริ่มต้นให้กับ function เพื่อป้องกันกรณีลิสต์ว่าง ดังนี้

>>> def xsum(seq):
...     def add(x,y): return x+y
...     return reduce(add, seq, 0)
... 

>>> xsum(range(1, 11))
55

>>> xsum([])
0

5.1.4 ลิสต์จากลิสต์ (List Comprehensions)

เป็นโครงสร้างเฉพาะตัวของไพธอนที่ยืมมาจากภาษา Haskell/ML ในการสร้างลิสต์ใหม่จากลิสต์ที่มีอยู่ ใช้มากในไพธอน มีรูปแบบเป็น

[f(x) for x in L [ if p(x) ] ]

แปลว่า ให้สร้างลิสต์ด้วยฟังก์ชั่น f จากลิสต์ L สมาชิกต่อสมาชิก โดยแต่ละสมาชิกจะต้องมีค่า p(x) ที่เป็นจริง

โครงสร้างส่วนหลัง ตรงที่เป็น if ... เป็นตัวเลือก อาจใส่หรือไม่ก็ได้

ตัวอย่าง

>>> freshfruit = ['  banana', '  loganberry ', 'passion fruit  ']
>>> [weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']

>>> vec = [2, 4, 6]
>>> [3*x for x in vec]
[6, 12, 18]

>>> [3*x for x in vec if x > 3]
[12, 18]

>>> [3*x for x in vec if x < 2]
[]

>>> [[x,x**2] for x in vec]
[[2, 4], [4, 16], [6, 36]]

>>> [x, x**2 for x in vec]     # error - parens required for tuples
  File "<stdin>", line 1, in ?
    [x, x**2 for x in vec]
               ^
SyntaxError: invalid syntax

>>> [(x, x**2) for x in vec]
[(2, 4), (4, 16), (6, 36)]

>>> vec1 = [2, 4, 6]
>>> vec2 = [4, 3, -9]
>>> [x*y for x in vec1 for y in vec2]
[8, 6, -18, 16, 12, -36, 24, 18, -54]

>>> [x+y for x in vec1 for y in vec2]
[6, 5, -7, 8, 7, -5, 10, 9, -3]

>>> [vec1[i]*vec2[i] for i in range(len(vec1))]
[8, 12, -54]

>>> [str(round(355/113.0, i)) for i in range(1,6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']


5.2 ประโยค del (The del statement)

ใช้งานคล้าย ๆ pop() แต่เลือกช่วงได้ด้วย จึงต้องระบุดัชนีเสมอ

>>> a = [-1, 1, 66.25, 333, 333, 1234.5]
>>> del a[0]
>>> a
[1, 66.25, 333, 333, 1234.5]

>>> del a[2:4]
>>> a
[1, 66.25, 1234.5]

>>> del a[:]
>>> a
[]

สามารถใช้ del ในการลบตัวแปรได้ด้วย

>>> del a


5.3 ทูเปิล (Tuples and Sequences)

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

>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345

>>> t
(12345, 54321, 'hello!')

>>> # Tuples may be nested:
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))

ตัวอย่างการละเลยการใส่วงเล็บ ซึ่งถ้าดูผ่าน ๆ อาจสับสนได้

>>> empty = ()
>>> singleton = 'hello',    # <-- note trailing comma
>>> len(empty)
0

>>> len(singleton)    # ได้ค่าเป็น 1 เพราะเป็นทูเปิลที่มีสมาชิก 1 ตัว  
1

>>> singleton
('hello',)

การละเลยการใส่วงเล็บในตอนกำหนดค่า ไพธอนเรียกว่า การอัดข้อมูลเป็นอนุกรม (sequence packing) ในกรณีนี้คือ tuple packing (ถ้าละเลยการใส่วงเล็บแล้ว จะถือว่าเป็นข้อมูล tuple เสมอ)

t = x, y, z

และยังสามารถกำหนดค่าแบบย้อนกลับได้ อันนี้เรียกว่า การแตกข้อมูลอนุกรม (sequence unpacking)

x, y, z = t

(ซึ่งถ้าเขียนให้ถูกจริง ๆ แล้วคือ (x, y, z) = t)

และแน่นอนว่าใช้กับลิสต์ได้เช่นเดียวกัน

[x, y, z] = t


5.4 เซ็ต (Sets)

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

>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> fruit = set(basket)               # create a set without duplicates
>>> fruit
set(['orange', 'pear', 'apple', 'banana'])

>>> 'orange' in fruit                 # fast membership testing
True

>>> 'crabgrass' in fruit
False

>>> # Demonstrate set operations on unique letters from two words
...
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a                                  # unique letters in a
set(['a', 'r', 'b', 'c', 'd'])

>>> a - b                              # letters in a but not in b
set(['r', 'd', 'b'])

>>> a | b                              # letters in either a or b
set(['a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'])

>>> a & b                              # letters in both a and b
set(['a', 'c'])

>>> a ^ b                              # letters in a or b but not both
set(['r', 'd', 'b', 'm', 'z', 'l'])


5.5 ดิกชันนารี (Dictionaries)

เป็นชนิดข้อมูลพิเศษที่อยู่ในรูป {key: value, ...}

  • เปลี่ยนแปลงค่าได้
  • key อาจเป็นข้อมูลชนิดสคริง ตัวเลข หรือทูเปิลที่ไม่ได้บรรจุ multable object ไว้
  • ลิสต์ใช้เป็นคีย์ไม่ได้ เพราะมันเปลี่ยนค่าภายในได้
  • ลบค่าภายในได้ด้วยประโยค del
  • ถ้าใส่ค่าคีย์ซ้ำ จะแทนที่ค่าเก่า
  • ถ้าค้นคีย์ที่ไม่มีอยู่ จะแสดงข้อผิดพลาด
  • ใช้เมธอด keys() ในการแสดงค่าคีย์ทั้งหมด และใช้เมธอด has_key() ในการค้นค่าคีย์
  • สมาชิกภายใน จะถูกเรียงลำดับด้วยค่าของคีย์โดยอัตโนมัติ
>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'sape': 4139, 'guido': 4127, 'jack': 4098}

>>> tel['jack']
4098

>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'guido': 4127, 'irv': 4127, 'jack': 4098}

>>> tel.keys()
['guido', 'irv', 'jack']

>>> tel.has_key('guido')
True

>>> 'guido' in tel
True

แปลงทูเปิลในลิสต์มาเป็นดิกชันนารีด้วยฟังก์ชั่น dict()

>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'jack': 4098, 'guido': 4127}

>>> dict([(x, x**2) for x in (2, 4, 6)])     # use a list comprehension
{2: 4, 4: 16, 6: 36}

หรือหากค่าคีย์เป็นสตริงล้วน อาจกำหนดค่าแบบนี้ก็ได้ (เลียนแบบการส่งผ่านค่าไปยังฟังก์ชั่น)

>>> dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'jack': 4098, 'guido': 4127}


5.6 เทคนิกการวนรอบ (Looping Techniques)

iteritems()
ใช้แตกค่าคู่ของดิกชันนารี

>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
>>> for k, v in knights.iteritems():
...     print k, v
...
gallahad the pure
robin the brave
enumerate()
ใช้แปลงจากลิสต์ (หรือทูเปิล) มาเป็นดิกชันนารี ที่มีค่าคีย์เป็นตัวเลข

>>> for i, v in enumerate(['tic', 'tac', 'toe']):
...     print i, v
...
0 tic
1 tac
2 toe
zip()
ใช้จับคู่สองอนุกรม (ลิสต์หรือทูเปิลหรือผสมกัน) ที่มีจำนวนสมาชิกเท่ากัน แปลงรูปมาใช้งานแบบดิกชันนารี

>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['lancelot', 'the holy grail', 'blue']
>>> for q, a in zip(questions, answers):
...     print 'What is your %s?  It is %s.' % (q, a)
...
What is your name?  It is lancelot.
What is your quest?  It is the holy grail.
What is your favorite color?  It is blue.
reversed()
ใช้กลับค่าสมาชิก

>>> for i in reversed(xrange(1,10,2)):
...     print i
...
9
7
5
3
1
sorted()
ใช้จัดเรียงสมาชิก

>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> for f in sorted(set(basket)):
...     print f
...     
apple
banana
orange
pear


5.7 เงื่อนไข (More on Conditions)

  • เงื่อนไขในประโยค while และ if ไม่จำเป็นต้องเป็นการเปรียบเทียบค่าเสมอไป แต่จะเป็นอะไรก็ได้ที่ ถ้าคืนค่าที่ไม่เป็นศุนย์หรือ None จะถูกนับว่าเป็นจริง
  • in และ not in ใช้ดูว่ามีค่าอยู่ในลำดับข้อมูลหรือเปล่า
  • สำหรับลิสต์ is และ is not ใช้ดูว่าเป็นออปเจคต์เดียวกันหรือเปล่า
  • การเปรียบเทียบค่า มีลำดับความสำคัญน้อยกว่าการกระทำทางคณิตศาสตร์
  • ถ้ามีการเปรียบเทียบหลายค่าในคำสั่งเดียว จะเรียงลำดับจากขวาไปซ้าย เช่น a < b == c
    >>> True == 1
    True
    
    >>> 1 < 2 == 1
    False
    
    >>> 1 < 2 == 2
    True
  • ตัวเปรียบเทียบทางตรรกะ มีความสำคัญน้อยที่สุด โดย not สำคัญที่สุด และ or สำคัญน้อยที่สุด คือ
    A and not B or C เขียนได้เป็น (A and (not B)) or C
  • ตัวเปรียบเทียบทางตรรกะ จะเปรียบเทียบจากซ้ายไปขวา และถ้าจุดใดที่เมื่อเปรียบเทียบแล้วไม่เป็นจริง ประโยคส่วนที่เหลือจะไม่ถูกเปรียบเทียบต่อ ไพธอนเรียกการนี้ว่า ตัวกระทำลัดวงจร (short-circuit operators) และค่าที่คืนออกมาจากการเปรียบเทียบ จะเป็นค่าสุดท้ายที่ทำการเปรียบเทียบ
  • สามารถกำหนดค่าผลของการเปรียบเทียบ ให้กับตัวแปรได้
    >>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
    >>> non_null = string1 or string2 or string3
    >>> non_null
    'Trondheim


5.8 น้ำหนักของข้อมูลแบบลำดับ (Comparing Sequences and Other Types)
  • ออปเจคต์แบบลำดับ สามารถนำไปเปรียบเทียบกับออปเจคต์อื่นได้ โดยลิสต์น้ำหนักน้อยกว่าสตริง สตริงน้อยกว่าทูเปิล (อย่าจำมาก อาจเปลี่ยนแปลงได้ ให้ทดสอบตามรุ่นไพธอนที่ใช้จริง)
  • การเปรียบเทียบยึดหลักจากซ้ายไปขวา และหยุดทันทีที่ผลออกมาไม่เป็นจริง
(1, 2, 3)              < (1, 2, 4)
[1, 2, 3]              < [1, 2, 4]
'ABC' < 'C' < 'Pascal' < 'Python'
(1, 2, 3, 4)           < (1, 2, 4)
(1, 2)                 < (1, 2, -1)
(1, 2, 3)             == (1.0, 2.0, 3.0)
(1, 2, ('aa', 'ab'))   < (1, 2, ('abc', 'a'), 4)

6. โมดูล (Modules)

6.1 เพิ่มเติม (More on Modules)
6.2 โมดูลมาตรฐาน (Standard Modules)
6.3 ฟังก์ชั่น dir() (The dir() Function)
6.4 แพกเกจ (Packages)


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

ใช้ประโยค import module_name ในการเรียกใช้
ส่วนชื่อโมดูลจะถูกเก็บไว้ในตัวแปรรวม (เฉพาะในโมดูล) ชื่อ __name__

สมมุติเราสร้างโมดูลเป็นไฟล์ชื่อ fibo.py (ให้อยู่ในไดเรคทอรี่ปัจจุบัน) มีเนื้อไฟล์ว่า

# Fibonacci numbers module

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

def fib2(n): # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while b < n:
        result.append(b)
        a, b = b, a+b
    return result

เริ่มต้นใช้งานว่า

>>> import fibo

เรียกใช้งานฟังก์ชั่นโดย

>>> fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

>>> fibo.fib2(100)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

>>> fibo.__name__
'fibo'

เพื่อให้กระชับ เราสามารถกำหนดค่าตัวแปรแทนเมธอดในโมดูลได้

>>> fib = fibo.fib
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377


6.1 เพิ่มเติม (More on Modules)

  • สามารถตั้งให้มีโค๊ดในการรันครั้งแรก (ครั้งเดียว) ที่ถูกอิมพอร์ตได้
  • เนื่องจากต้องอ้างอิงค่าในโมดูลผ่านชื่อโมดูล ในรูปของ modname.itername จึงไม่ต้องกังวลเรื่องชื่อตัวแปรในโมดูลจะซ้ำกับตัวแปรในโปรแกรมหลัก
  • โมดูลสามารถอิมพอร์ตโมดูลอื่นได้
  • เขียนรูปแบบการอิมพอร์ตได้หลากหลาย
    • เลือกอิมพอร์ตบางฟังก์ชั่น
      >>> from fibo import fib, fib2
      >>> fib(500)
      1 1 2 3 5 8 13 21 34 55 89 144 233 377
    • อิมพอร์ตทุกฟังก์ชั่น ที่ไม่ใช่ฟังก์ชั่นท้องถิ่น (ฟังก์ชั่นท้องถิ่นจะถูกนำหน้าชื่อฟังก์ชั่นด้วยสัญลักษณ์ขีดเส้นใต้ '_' ) แบบละเลยชื่อโมดูล
      >>> from fibo import *
      >>> fib(500)
      1 1 2 3 5 8 13 21 34 55 89 144 233 377
6.1.1 การค้นหาพาธของโมดูล (The Module Search Path)
  • จะหาที่ไดเรคทอรี่ปัจจุบันก่อน
  • ถ้าไม่พบจะไปหาจากค่าในตัวแปรแวดล้อม PYTHONPATH
  • ตามด้วยพาธของไพธอนเอง สำหรับเดเบียนคือ /usr/lib/python2.4 (ตัวเลข 2.4 จะเปลี่ยนไปตามรุ่นไพธอนที่ใช้)

การค้นพาธที่ว่า สามารถดูได้จากตัวแปร sys.path ในไพธอน ซึ่งในทางปฏิบัติเราสามารถแก้ไขได้จากในโปรแกรม ทำให้การเขียนโปรแกรมมีความยืดหยุ่น

6.1.2 ไฟล์ที่ถูกแปลแล้ว (Compiled Python files)

ไพธอนเร่งความเร็วตอนเริ่มระบบด้วยการแปล (compile) เช่นถ้าเรามีไฟล์ชื่อ spam.py ถ้าไฟล์นี้ถูกอิมพอร์ต ไพธอนจะคอมไพล์แล้วเก็บในชื่อ spam.pyc ซึ่งไฟล์นี้จะไม่ขึ้นกับระบบปฏิบัติการ หมายความว่าเราสามารถคัดลอกไฟล์นามสกุล .pyc ไปใช้กับเครื่องต่างระบบได้เลย

สำหรับเซียน

  • ถ้าเรียกใช้ไพธอนด้วยพารามิเตอร์ -O ไพธอนจะคอมไพล์ไฟล์ให้เล็ก โดยนามสกุลจะกลายเป็น .pyo แทน
  • ถ้าเรียกใช้ไพธอนด้วยพารามิเตอร์ -OO มีผลเหมือนอันแรกแต่จะลบข้อมูลที่เป็น docstring ออก
  • โปรแกรมไม่ได้รันเร็วขึ้น เพียงแต่ถูกโหลดได้เร็วขึ้น
  • ในการรันปกติ ไพธอนไม่ได้คอมไพล์ไฟล์ที่ถูกรัน แต่จะคอมไพล์เมื่อถูกอิมพอร์ต ดังนั้นถ้าจะเร่งความเร็วตอนถูกโหลด เราอาจแบ่งโปรแกรมหลักของเราให้เล็กลง แล้วไปอิมพอร์ตโมดูลที่เราแบ่งเอาไว้อีกทีนึง
  • ตอนรัน ถ้ามีไฟล์ .pyc หรือ .pyo อยุ่แล้ว ไม่จำเป็นต้องมีไฟล์ต้นฉบับ (นิยมนามสกุลเป็น .py) ดังนั้นหากไม่ต้องการแพร่ซอร์สโค๊ด อาจจ่ายเป็นไฟล์คอมไพล์เหล่านี้แทน
  • ใช้โมดูล compileall ในการคอมไพล์ไฟล์ทั้งไดเรคทอรี่


6.2 โมดูลมาตรฐาน (Standard Modules)

ไพธอนมีโมดูลมาตรฐานเยอะมาก ดูได้จาก บรรณสารของไพธอน (Python Library Reference) โมดูลหลายตัวเป็นโมดูลของระบบ บางตัวอาจเรียกใช้ได้ในบางสถานะ
ตัวอย่างเช่น โมดูล sys ซึ่งเป็นโมดูลระบบ

  • ตัวแปร sys.ps1 จะเก็บข้อความที่เป็นพร้อมต์หลัก (Primary prompt) และ sys.ps2 จะเก็บพร้อมต์ตาม (Secondary prompt) ตัวแปรทั้งสองจะเรียกใช้ได้ในหมวดโต้ตอบเท่านั้น
    >>> import sys
    >>> sys.ps1
    '>>> '
    
    >>> sys.ps2
    '... '
    
    >>> sys.ps1 = 'C> '
    C> print 'Yuck!'
    Yuck!
    C>
  • ตัวแปร sys.path เก็บพาธการค้นหาของระบบในรูปของลิสต์ ดังนั้นเราอาจเพิ่มพาธการค้น ได้โดยการเพิ่มหรือเปลียนค่า
    >>> import sys
    >>> sys.path.append('/ufs/guido/lib/python')


6.3 ฟังก์ชั่น dir() (The dir() Function)

ฟังก์ชั่น dir() ใช้ดูว่าโมดูลนั้นประกอบไปด้วยรายชือ (คือตัวแปร โมดูล ฟังก์ชั่น คลาส หรืออะไรก็ตามที่เราสร้างไว้) อะไรบ้าง เก็บค่าเป็นลิสต์

>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']

>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__name__', '__stderr__',
 '__stdin__', '__stdout__', '_getframe', 'api_version', 'argv', 
 'builtin_module_names', 'byteorder', 'callstats', 'copyright',
 'displayhook', 'exc_clear', 'exc_info', 'exc_type', 'excepthook',
 'exec_prefix', 'executable', 'exit', 'getdefaultencoding', 'getdlopenflags',
 'getrecursionlimit', 'getrefcount', 'hexversion', 'maxint', 'maxunicode',
 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache',
 'platform', 'prefix', 'ps1', 'ps2', 'setcheckinterval', 'setdlopenflags',
 'setprofile', 'setrecursionlimit', 'settrace', 'stderr', 'stdin', 'stdout',
 'version', 'version_info', 'warnoptions']

ใช้ dir() เฉย ๆ จะดูรายชื่อในสภาพแวดล้อมของปัจจุบัน

>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__doc__', '__file__', '__name__', 'a', 'fib', 'fibo', 'sys']

เพื่อไม่ให้รกรุงรัง dir() จึงไม่ยอมดูฟังก์ชั่นบิลด์อิน (build-in function) ของไพธอนให้ แต่ถ้าเราอยากดู ต้องเรียกผ่านโมดูล __builtin__

>>> import __builtin__
>>> dir(__builtin__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'DeprecationWarning',
 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
 'FloatingPointError', 'FutureWarning', 'IOError', 'ImportError',
 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt',
 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented',
 'NotImplementedError', 'OSError', 'OverflowError', 
 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError',
 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError',
 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True',
 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError',
 'UserWarning', 'ValueError', 'Warning', 'WindowsError',
 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__',
 '__name__', 'abs', 'apply', 'basestring', 'bool', 'buffer',
 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile',
 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod',
 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float',
 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex',
 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter',
 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'min',
 'object', 'oct', 'open', 'ord', 'pow', 'property', 'quit', 'range',
 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set',
 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super',
 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']


6.4 แพกเกจ (Packages)

เวลามีโมดูลที่เราสร้างขึ้นเยอะ เราจะจัดกลุ่มให้ไปรวมในไดเรคทอรี่ต่างหาก (ทำเหมือนเวลาเรามีไฟล์เยอะ ๆ แล้วเราจะจัดระเบียบไฟล์ ในระบบไฟล์เรียงไดเรคทอรี่ด้วย / เช่น dira/dirb แต่ไพธอนเรียงด้วย . เช่น pa.pb) ไพธอนเรียกวิธีจัดการกลุ่มโมดูลนี้ว่า แพกเกจ ซึ่งชื่อแพกเกจก็คือชื่อไดเรคทอรี่นั่นเอง

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

Sound/                          Top-level package
      __init__.py               Initialize the sound package
      Formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      Effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      Filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

พอแพกเกจเราถูกอิมพอร์ต ไพธอนก็จะค้นพาธจาก sys.path พอพบแล้วก็จัดการคอมไพล์เพื่อจะถูกเรียกใช้ต่อไป

ไฟล์ __init__.py ใช้บอกไพธอนว่า ในไดเรคทอรี่นี้เป็นแพกเกจ ซึ่งไฟล์นี้อาจเป็นไฟล์เปล่า ๆ ก็ได้ หรืออาจบรรจุโค๊ดที่ใช้เริ่มงานแพกเกจ หรืออาจใส่ค่าตัวแปร __all__ ที่จะใช้บอกว่าแพกเกจนี้จะต้องโหลดโมดูลไหนบ้าง

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

  • แบบแรกคือ import ... [ as ... ]
    เรียกได้สองลักษณะ
    • ถ้าไม่มี as
      import Sound.Effects.echo

      เวลาอ้างถึงต้องอ้างแบบเต็ม ๆ

      Sound.Effects.echo.echofilter(input, output, delay=0.7, atten=4)
    • ถ้ามี as
      import Sound.Effects.echo as SEe

      อ้างถึงโดยใช้ชื่อย่อ

      SEe.echofilter(input, output, delay=0.7, atten=4)
  • แบบที่สองคือ from ... import ...
    from Sound.Effects import echo

    เวลาเรียกใช้ เรียกเฉพาะชื่อ โมดูล.ฟังก์ชั่น ที่เราอิมพอร์ตเข้ามา

    echo.echofilter(input, output, delay=0.7, atten=4)

    หรือถ้าอิมพอร์ตเจาะเฉพาะฟังก์ชั่น ก็เรียกเฉพาะฟังก์ชั่น

    from Sound.Effects.echo import echofilter
    echofilter(input, output, delay=0.7, atten=4)

หมายเหตุ

  • ระหว่างการอิมพอร์ต ถ้าเกิดข้อผิดพลาดขึ้นมา ไพธอนจะแจ้ง ImportError
  • อิมพอร์ตได้เฉพาะสิ่งที่มีตัวตนและสิ่งที่ยอมให้อิมพอร์ตได้เท่านั้น คือ แพกเกจหรือโมดูล ที่เหลือนอกจากนี้คือคลาส ฟังก์ชั่น หรือตัวแปร ไม่สามารถอิมพอร์ตได้
6.4.1 เข้าใจการอิมพอร์ต (Importing * From a Package)

เวลาถูกเรียกอิมพอร์ต ไพธอนใช้ตัวแปร __all__ ในไฟล์ __init__.py สำหรับระบุว่าในแพกเกจนี้จะต้องเรียกใช้งานโมดูลไหนบ้าง (แทนการดูจากชื่อไฟล์ในไดเรคทอรี่ เพราะมีข้อจำกัดมากสำหรับระบบปฏิบัติการที่หลากลาย)
เช่น ในไฟล์ Sounds/Effects/__init__.py อาจมีเนื้อไฟล์เป็น

__all__ = ["echo", "surround", "reverse"]

นั่นคือเมื่อไพธอนพบคำสั่งว่า from Sound.Effects import * เขาจะอิมพอร์ตโมดูลทั้งสามตัวเข้ามา

แต่ถ้าเราไม่ได้กำหนดค่าให้กับ __all__ เวลาไพธอนพบคำสั่ง from Sound.Effects import * เขาจะเพียงแค่รันไฟล์ __init__.py และรับรู้ว่ามีโมดูลอะไรในแพกเกจบ้างเท่านั้น (ไม่ได้คอมไพล์และอิมพอร์ตโมดูลเข้ามาในห้วงการทำงาน-namespace เพื่อเตรียมพร้อมจริง ๆ )

หากใช้ในรูปแบบของ from package import module ควรระวังเรื่องชื่อโมดูลหรือฟังก์ชั่นซ้ำ อาจทำให้เรียกใช้ผิด

6.4.2 การอ้างถึงกันระหว่างโมดูลในแพกเกจ Intra-package References

มีหลักอยู่ว่า

  • ถ้าอยู่ในระดับชั้นไดเรคทอรี่เดียวกัน เรียกได้โดยตรง โดยไม่ต้องมีชื่อแพกเกจนำหน้า
    เช่นจากตัวอย่าง โมดูล surround สามารถเรียกใช้โมดูล echo ได้โดยตรง
    import echo
    echo.echofilter(input, output, delay=0.7, atten=4)

    หรือ

    from echo import echofilter
    echofilter(input, output, delay=0.7, atten=4)
  • ถ้าอยู่ลึกลงไป สามารถเรียกผ่านจากระดับเดิมได้ทันที
  • นอกเหนือจากนี้ ต้องอ้างอิงแบบเต็มยศ เหมือนการเรียกจากโมดูลจากแพกเกจอื่น
  • *** เว้นแต่ไพธอนรุ่น 2.5 ขึ้นไป จะสามารถเรียกย้อนขึ้นได้ด้วย ตัวอย่างการเรียกคือ
    from . import echo
    from .. import Formats
    from ..Filters import equalizer
6.4.3 หนึ่งแพกเกจหลายไดเรคทอรี่ (Packages in Multiple Directories)

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

7. การนำเข้าและส่งออกข้อมูล (Input and Output)

7.1 การจัดรูปแบบเอาต์พุต (Fancier Output Formatting)
7.2 การอ่านเขียนไฟล์ (Reading and Writing Files)



7.1 การจัดรูปแบบเอาต์พุต (Fancier Output Formatting)

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

ตัวอย่างการใช้ฟังก์ชั่นแปลงเป็นตัวอักขระ str() ซึ่งให้คนอ่านง่าย หรือแปลงแบบดิบ repr() คือให้ระบบอ่านง่าย (สามารถเขียนอีกแบบภายใต้เครื่องหมาย ``)

>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'

>>> repr(s)
"'Hello, world.'"

>>> str(0.1)
'0.1'

>>> repr(0.1)
'0.10000000000000001'

>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'
>>> print s
The value of x is 32.5, and y is 40000...

>>> # The repr() of a string adds string quotes and backslashes:
... hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print hellos
'hello, world\n'

>>> # The argument to repr() may be any Python object:
... repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"

>>> # reverse quotes are convenient in interactive sessions:
... `x, y, ('spam', 'eggs')`
"(32.5, 40000, ('spam', 'eggs'))"

ตัวอย่างการใช้ฟังก์ชั่น repr() ร่วมกับเมธอดจัดชิดขวาของสตริง rjust() เทียบกับการใช้ตัวกระทำจัดรูปแบบ %

>>> for x in range(1, 11):
...     print repr(x).rjust(2), repr(x*x).rjust(3),
...     # Note trailing comma on previous line
...     print repr(x*x*x).rjust(4)
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

>>> for x in range(1,11):
...     print '%2d %3d %4d' % (x, x*x, x*x*x)
... 
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

ลองดูเมธอด zfill() บ้าง

>>> '12'.zfill(5)
'00012'

>>> '-3.14'.zfill(7)
'-003.14'

>>> '3.14159265359'.zfill(5)
'3.14159265359'

เทียบกับการใช้ %

>>> import math
>>> print 'The value of PI is approximately %5.3f.' % math.pi
The value of PI is approximately 3.142.

อีกอันนึงเป็นการจัดชิดขอบซ้ายขวาด้วย %

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
...     print '%-10s ==> %10d' % (name, phone)
... 
Jack       ==>       4098
Dcab       ==>       7678
Sjoerd     ==>       4127

พิเศษนิดนึงสำหรับตัวจัดรูปสตริง %s คือถ้าข้อมูลไม่อยู่ในรูปสตริง เขาจะแปลงอัตโนมัติด้วยฟังก์ชั่น str()

>>> print "%s %s %s" % (1, True, None)
1 True None

พิเศษกว่านั้น ถ้าข้อมูลเป็นดิกชันนารี ยังใช้รูปแบบ %(name)format ได้อีกด้วย

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print 'Jack: %(Jack)d; Sjoerd: %(Sjoerd)d; Dcab: %(Dcab)d' % table
Jack: 4098; Sjoerd: 4127; Dcab: 8637678


7.2 การอ่านเขียนไฟล์ (Reading and Writing Files)

เปิดไฟล์ด้วยฟังก์ชั่น open(filename, mode) ได้ค่าเป็น ไฟล์ออปเจกต์

>>> f=open('/tmp/workfile', 'w')
>>> print f
<open file '/tmp/workfile', mode 'w' at 80a0960>

mode เป็นได้ดังนี้

  • 'r' อ่านอย่างเดียว ตัวนี้เป็นค่าปริยาย
  • 'w' เขียนอย่างเดียว ถ้ามีเนื้อเก่าจะถูกทับหมด
  • 'a' เติมอย่างเดียว คือเขียนต่อที่ท้ายไฟล์
  • 'r+' ทั้งอ่านและเขียน (ใช้ร่วมกับเมธอด seek() ในการเลื่อนตำแหน่งอ่านเขียน)

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

7.2.1 เมธอดที่ใช้ (Methods of File Objects)
f.read([ size ])
ถ้าไม่ระบุขนาดไบต์หรือขนาดเป็นลบ เขาจะอ่านทั้งไฟล์ ถ้าอ่านไฟล์จนหมดแล้ว จะคืนค่าเป็นอักขระว่าง ("")

>>> f.read()
'This is the entire file.\n'

>>> f.read()
''
f.readline()
อ่านหนึ่งบรรทัด คืออ่านจนพบอักขระ \n ถ้าอ่านไฟล์จนหมดแล้ว จะคืนค่าเป็นอักขระว่าง ("")

>>> f.readline()
'This is the first line of the file.\n'

>>> f.readline()
'Second line of the file\n'

>>> f.readline()
''
f.readlines()
อ่านทั้งไฟล์โดยแยกแต่ละบรรทัดเป็นแต่ละสมาชิกในลิสต์

>>> f.readlines()
['This is the first line of the file.\n', 'Second line of the file\n']
for line in f:
อันนี้ไม่ใช่เมธอด แต่เป็นประโยคการเขียนแบบพิเศษ ที่ใช้เป็นปกติในไพธอน อ่านโค๊ดง่าย และเหมาะสมในการใช้งานจริง

>>> for line in f:
        print line,
This is the first line of the file.
Second line of the file
f.write(string)
เขียนสตริงก์ลงไฟล์ คืนค่าเป็น None

>>> f.write('This is a test\n')

ถ้าข้อมูลไม่ได้อยู่ในรูปสตริง ต้องแปลงก่อน

>>> value = ('the answer', 42)
>>> s = str(value)
>>> f.write(s)
f.tell()
ใช้บอกตำแหน่งปัจจุบันของไฟล์ นับจากต้นไฟล์
f.seek(offset [, from_what])
ใช้ตั้งตำแหน่งที่จะอ่านเขียนไฟล์ offset คือจำนวนไบต์ from_what คือตำแหน่งอ้างอิง

  • 0 หรือ ไม่ใส่ คือเทียบจากต้นไฟล์
  • 1 คือ จากตำแหน่งปัจจุบัน
  • 2 คือ จากท้ายไฟล์
>>> f = open('/tmp/workfile', 'r+')
>>> f.write('0123456789abcdef')
>>> f.seek(5)     # Go to the 6th byte in the file
>>> f.read(1)        
'5'

>>> f.seek(-3, 2) # Go to the 3rd byte before the end
>>> f.read(1)
'd'
f.close()
ปิดการทำงานกับไฟล์นั้น ถ้าปิดแล้วกลับมาอ่านอีกจะเกิดข้อผิดพลาด

>>> f.close()
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: I/O operation on closed file
7.2.2 ปิคเกิล (The pickle Module)

เป็นการเก็บออปเจคต์ลงไฟล์แบบไร้ข้อจำกัด ดูเรื่องปิกเกิลในบรรณสารของไพธอน
ขั้นตอนการทำงานคือ ไพธอนจะแปลงออปเจคต์เป็นสตริงก่อน เรียกว่าปิกกลิง (pickling) เวลานำกลับจะแปลงสคริงนั้นกลับเป็นออปเจคต์อีกที เรียกว่า อันปิกกลิง (unpickling)

ตัวอย่างการใช้งาน สมมุติว่ามีออปเจคต์ x ที่เราต้องการเก็บ ลงไว้ในไฟล์ที่เปิดไว้แล้ว คือไฟล์ออปเจคต์ f รูปแบบคือ

pickle.dump(x, f)

เวลานำกลับก็ใช้ว่า (f ต้องถูกเปิดอยู่ก่อนแล้ว)

x = pickle.load(f)

8. ข้อผิดพลาดและการจัดการ (Errors and Exceptions)

8.1 ผิดพลาดทางโครงสร้างประโยค (Syntax Errors)
8.2 ข้อผิดพลาดตอนทำงาน (Exceptions)
8.3 การจัดการ (Handling Exceptions)
8.4 การยกข้อผิดพลาด (Raising Exceptions)
8.5 สร้างกับดักเอง (User-defined Exceptions)
8.6 ดักให้หมดจด (Defining Clean-up Actions)
8.7 ละเอียดนิดเพื่อความหมดจด (Predefined Clean-up Actions)


ข้อผิดพลาดมีสองแบบคือ ผิดทางโครงสร้างประโยค (syntax errors) หรือผิดตอนโปรแกรมทำงาน (exceptions)


8.1 ผิดพลาดทางโครงสร้างประโยค (Syntax Errors)

มือใหม่ต้องเจอทุกคน

>>> while True print 'Hello world'
  File "<stdin>", line 1, in ?
    while True print 'Hello world'
                   ^
SyntaxError: invalid syntax

ในที่นี้คือตก : หลัง True
ข้อผิดพลาดแบบนี้ต้องตามไปแก้ในโค๊ดอย่างเดียว


8.2 ข้อผิดพลาดตอนทำงาน (Exceptions)

เกิดตอนรัน

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ZeroDivisionError: integer division or modulo by zero

>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined

>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: cannot concatenate 'str' and 'int' objects

บรรทัดสุดท้ายเป็นการบอกว่าผิดเรื่องอะไร
จากตัวอย่าง จะเห็นว่าผิดอยู่สามเรื่องคือ ZeroDivisionError NameError และ TypeError สามารถดูรายละเอียดทั้งหมดได้ที่ module-exceptions


8.3 การจัดการ (Handling Exceptions)

จัดการด้วยประโยค try: ... except[(ErrorType)]: ... [else: ...]
ตัวอย่างข้างล่างเป็นการจัดการข้อผิดพลาด โดยจะจัดการดูเฉพาะค่าที่เราป้อนเข้าไปว่าถูกต้องหรือไม่ (ValueError) แต่ถ้าเป็นข้อผิดพลาดแบบอื่น โปรแกรมจะปล่อยให้เป็นหน้าที่ของระบบ จึงทำให้เราสามารถใช้ปุ่ม Ctrl-C ในการตัดการทำงานของโปรแกรมได้ ซึ่งกรณีตัดการทำงานนี้จะเป็นข้อผิดพลาดแบบ KeyboardInterrupt

>>> while True:
...     try:
...         x = int(raw_input("Please enter a number: "))
...         break
...     except ValueError:
...         print "Oops!  That was no valid number.  Try again..."
...

เราระบุข้อผิดพลาดได้หลายแบบในครั้งเดียว

... except (RuntimeError, TypeError, NameError):
...     pass

หรืออาจดักเป็นขั้น ๆ จนในที่สุดไม่ใส่อะไรเลย คือ ดักทุกอย่างที่เหลือ

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError, (errno, strerror):
    print "I/O error(%s): %s" % (errno, strerror)
except ValueError:
    print "Could not convert data to an integer."
except:
    print "Unexpected error:", sys.exc_info()[0]
    raise

อาจใช้ร่วมกับ else: ในกรณีที่ try: ไม่ยอมแจ้งข้อผิดพลาด

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print 'cannot open', arg
    else:
        print arg, 'has', len(f.readlines()), 'lines'
        f.close()

ถ้าเติมตัวแปร(อาจเป็นทูเปิล)หลัง Exception ตัวแปรนั้นจะเก็บค่าอินสแตนซ์ของการดัก

>>> try:
...    raise Exception('spam', 'eggs')
... except Exception, inst:
...    print type(inst)     # the exception instance
...    print inst.args      # arguments stored in .args
...    print inst           # __str__ allows args to printed directly
...    x, y = inst          # __getitem__ allows args to be unpacked directly
...    print 'x =', x
...    print 'y =', y
...
<type 'instance'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

การดักข้อผิดพลาดนี้ ไม่เพียงแต่ดักเฉพาะบล๊อกนั้น แต่สามารถย้อนขึ้นไปถึงต้นตอได้

>>> def this_fails():
...     x = 1/0
... 
>>> try:
...     this_fails()
... except ZeroDivisionError, detail:
...     print 'Handling run-time error:', detail
... 
Handling run-time error: integer division or modulo by zero


8.4 การยกข้อผิดพลาด (Raising Exceptions)

เราสามารถยกข้อผิดพลาดมาแสดงได้ เช่น

>>> raise NameError, 'HiThere'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: HiThere

พารามิเตอร์ตัวแรกเป็นชื่อข้อผิดพลาด อันหลังเป็นข้อความการผิดพลาด ซึ่งอาจเขียนอีกรูปนึงว่า raise NameError('HiThere')

สามารถใช้คำสั่ง raise เพื่อยกข้อผิดพลาดขึ้นอีกครั้ง ภายในบล๊อกที่ดักความผิดพลาดได้

>>> try:
...     raise NameError, 'HiThere'
... except NameError:
...     print 'An exception flew by!'
...     raise
...
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere


8.5 สร้างกับดักเอง (User-defined Exceptions)

เราอาจสร้างกับดักเองโดยสร้างเป็นคลาส ซึ่งเวลานำมาใช้ต้องผันมาจากคลาสที่สร้างไว้อีกที

>>> class MyError(Exception):
...     def __init__(self, value):
...         self.value = value
...     def __str__(self):
...         return repr(self.value)
... 
>>> try:
...     raise MyError(2*2)
... except MyError, e:
...     print 'My exception occurred, value:', e.value
... 
My exception occurred, value: 4

>>> raise MyError, 'oops!'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'

จากตัวอย่างนี้ เมธอด __init__ ของคลาสเริ่มคือ Exception ถูกครอบด้วยโค๊ดของเรา

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

class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not
    allowed.

    Attributes:
        previous -- state at beginning of transition
        next -- attempted new state
        message -- explanation of why the specific transition is not allowed
    """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

ดูเรื่องคลาสได้ในบทต่อไป


8.6 ดักให้หมดจด (Defining Clean-up Actions)

ในประโยค try: จะมีวลีเผื่อเลือกอีกตัวคือ finally: โค๊ดที่อยู่ในบล๊อกนี้จะถูกรันเสมอ ไม่ว่าจะเกิดข้อผิดพลาดขึ้นหรือไม่

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print 'Goodbye, world!'
... 
Goodbye, world!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
KeyboardInterrupt

ประโยชน์ของวลี finally: คือ ถ้าผ่านบล๊อกของ except: และ else: มาแล้ว ถ้ายังไม่แจ้งข้อผิดพลาด จะถูกแจ้งในบล๊อกนี้ และไม่ว่าจะพบคำสั่ง break continue หรือ return ก็ตาม โค๊ดในส่วนนี้ก็ยังจะถูกรันเสมอ

>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print "division by zero!"
...     else:
...         print "result is", result
...     finally:
...         print "executing finally clause"
...

>>> divide(2, 1)
result is 2
executing finally clause

>>> divide(2, 0)
division by zero!
executing finally clause

>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'


8.7 ละเอียดนิดเพื่อความหมดจด (Predefined Clean-up Actions)

ควรมีความรอบคอบในการเขียนโค๊ด ลองดู

for line in open("myfile.txt"):
    print line

ปัญหาของโค๊ดนี้คือ ไม่มีการปิดไฟล์ ถ้าโปรแกรมใหญ่ขึ้นโค๊ดแบบนี้จะกินหน่วยความจำมหาศาล
ดังนั้นโค๊ดควรเป็นดังนี้

with open("myfile.txt") as f:
    for line in f:
        print line

ตัวอย่างนี้ ไฟล์ออปเจคต์ f จะถูกลบออกจากหน่วยความจำเมื่อโปรแกรมจบ

9. คลาส (Classes)

9.1 ศัพท์ (A Word About Terminology)
9.2 สโคปและเนมสเปซ (Python Scopes and Name Spaces)
9.3 แรกพบ (A First Look at Classes)
9.4 หมายเหตุเรื่องคลาส (Random Remarks)
9.5 การสืบทอดคลาส (Inheritance)
9.6 ตัวแปรเฉพาะที่ (Private Variables)
9.7 ปกิณกะ (Odds and Ends)
9.8 ตัวยกข้อผิดพลาดก็เป็นคลาส (Exceptions Are Classes Too)
9.9 ตัวกระทำซ้ำ (Iterators)
9.10 เจนเนอเรเตอร์ (Generators)
9.11 เจนเนอเรเตอร์เอกซ์เพรสชั่น (Generator Expressions)


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


9.1 ศัพท์ (A Word About Terminology)

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


9.2 สโคปและเนมสเปซ (Python Scopes and Name Spaces)

ศัพท์สนุก ๆ

เนมสเปซ (Namespace)
เปรียบเสมือนห้องส่วนตัวนับตั้งแต่เราเอาสิ่งที่เราเขียนไว้ หรือเอาฟังก์ชั่นที่มีอยู่แล้ว มาสร้างเป็นออปเจคต์ เป็นแบบนี้ในทุกระดับของออปเจคต์ ในทางปฏิบัติแล้วไพธอนเก็บไว้ในรูปดิกชันนารี ตัวอย่างของเนมสเปซคือ กลุ่มของบิลด์อินเนม (Buid-in Names = buid-in function + buid-in exception) จะอยู่ภายใต้เนมสเปซเดียวกัน หรือ กลอบอลเนม (Global Names = global function + global exception + global variables) ต่าง ๆ ของมอดูล ก็จะอยู่ในเนมสเปซของมัน การมีเนมสเปซทำให้ออปเจคต์ต่างเนมสเปซกันไม่ตีกันถึงแม้จะชื่อเดียวกัน เวลาเราอ้างถึงเนมที่อยู่ลึกลงไป เราจึงต้องอ้างตามรายแอตทริบิวต์

เนมสเปซต่าง ๆ จะถูกสร้างขึ้นเมื่อออปเจคต์ถูกสร้าง และมีอายุตามออปเจคต์นั้น ๆ เช่น

  • เนมสเปซที่บรรจุบิลด์อินเนม (Build-in Name = buid-in function + buid-in exception) จะถูกสร้างขึ้นตั้งแต่เราเริ่มเรียกใช้โปรแกรมและคงอยู่จนกว่าโปรแกรมจะจบ
  • กลอบอลเนมสเปซของมอดูล (Module Global Namespace) จะถูกสร้างขึ้นตั้งแต่มอดูลถูกอิมพอร์ตจนกว่าจะจบโปรแกรมเช่นกัน
  • ประโยคทั้งหมดที่ถูกรันโดยไพธอน ไม่ว่าจะเป็นการรันสคริปต์ไฟล์หรือพิมพ์สดในหมวดโต้ตอบก็ดี จะอยู่ภายใต้เนมสเปซของมอดูล __main__
  • บิลด์อินเนม (Buid-in Names = buid-in function + buid-in exception) จะอยู่ภายใต้เนมสเปซของมอดูล __buildin__
  • เวลาฟังก์ชั่นถูกเรียกใช้งาน มันจะสร้างเนมสเปซของมันขึ้นมา แล้วถูกลบทิ้งไปเมื่อทำงานเสร็จ แต่ถ้าเรียกลึกลงไปเรื่อย ๆ เนมสเปซที่ลึกลงไปก็จะถูกซ้อนอยู่ภายใต้เนมสเปซที่เป็นผู้เรียก

อธิบายตามภาษาชาวบ้าน เนม(Names) ก็คืออะไรที่เราต้องตั้งชื่อให้มัน เวลาเรียกก็เรียกจากชื่อ เช่นฟังก์ชั่น ตัวแปร เป็นต้น ส่วนเนมสเปซ(Namespace) ก็คือห้องบรรจุเนมนั่นเอง

แอตทริบิวต์ (Attribute)
ก็คือเนม (Name) ตามด้วยจุด (.) ตามด้วยชื่อแอตทรีบิวต์ เช่น z.real เราเรียก real ว่าเป็นแอตทริบิวต์ของออปเจคต์ z อะไรก็ตามที่อยู่ในระดับเดียวกับ real คืออ้างถึงด้วย z.XXX เราจะเรียกว่าอยู่ภายใต้เนมสเปซเดียวกัน (ถ้าชื่อซ้ำก็ตีกัน)

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

สโคป (Scope)
หมายถึงช่วงที่เนมสเปซสามารถเข้าถึงได้โดยตรง โดยไม่ต้องอ้างอิงไล่แอตทริบิวต์ไปถึงออปเจคต์ที่อยู่ในเนมสเปซอื่น
ในทุก ๆ ขณะของการทำงาน จะมีอย่างน้อยสามสโคปเสมอ คือ
  • สโคปในสุด (เวลาค้นหาออปเจคต์ โปรแกรมจะค้นจากสโคปในสุดเสมอ) บรรจุตัวแปรในระดับเดียวกัน(และระดับลูก)และฟังก์ชั่นในระดับลูก
  • สโคปชั้นกลาง บรรจุตัวแปรแบบกลอบอล และฟังก์ชั่นในระดับเดียวกัน
  • สโคปชั้นนอกสุด บรรจุพวกบิลด์อินเนม (Buid-in Names = buid-in function + buid-in exception)

เรื่องสนุก ๆ

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

--รอแปล--


9.3 แรกพบ (A First Look at Classes)

9.3.1 โครงสร้างคลาส (Class Definition Syntax)

โครงคือ

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

--รอแปล--

9.3.2 ออปเจคค์ที่เป็นคลาส (Class Objects)

จะใช้งานหรือเข้าถึงคลาสออปเจคต์ได้สองแบบ คือ

เข้าถึงแบบอ้างตามแอตทริบิวต์ (Attribute references)
ดูโค๊ด
class MyClass:
    "A simple example class"
    i = 12345
    def f(self):
        return 'hello world'

เราสามารถเข้าถึงคลาสนี้ดังนี้

>>> MyClass.i
12345

>>> MyClass.f
<unbound method MyClass.f>

>>> MyClass.__doc__
'A simple example class'

>>> MyClass.i = 2
>>> MyClass.i
2

>>> MyClass.__doc__ = "Modified docstring"
>>> MyClass.__doc__
'Modified docstring'
การสร้างอินสแตนซ์ (instantiation)
ใช้รูปแบบธรรมชาติเหมือนกับการสร้างฟังก์ชั่น
x = MyClass()

เป็นการสร้างอินสแตนซ์ซึ่งเป็นออปเจคต์ที่ถูกบรรจุอยู่ในตัวแปร x

หากต้องการออปเจคต์ที่ต้องมีการถูกเตรียมการในครั้งแรก ต้องใส่เมธอดพิเศษชื่อ __init__() ลงในการนิยามคลาสด้วย

def __init__(self):
        self.data = []

พอสร้างออปเจคต์แล้ว เราจะได้ผลของการรันเมธอด __init__() มาด้วย เช่น

>>> x=MyClass()
>>> x.data
[]

ถ้าต้องการใส่พารามิเตอร์ให้กับคลาส ก็ต้องใส่ในเมธอด __init__() นี้เอง เช่น

>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
... 
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)
9.3.3 ออปเจคต์ที่เป็นอินสแตนซ์ (Instance Objects)

นิยามว่ามันมีสองแอตทริบิวต์เนม คือ แอตทริบิวต์ที่เป็นข้อมูล และ เมธอด

แอตทริบิวต์ที่เป็นข้อมูล (Data Attributes)
ก็คือตัวแปรของอินแสตนซ์ออปเจคต์นั่นเอง ดังนั้นจึงสามารถใช้งานแบบตัวแปร คือกำหนดค่าได้ทันที จากตัวอย่างข้างล่างนี้ จะได้ผลลัพธ์คือ 16
x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print x.counter
del x.counter
เมธอด (Method)
ก็คือฟังก์ชั่นของอินสแตนซ์ออปเจคต์นั่นเอง จากตัวอย่าง x.f คือเมธอดของอินสแตนซ์ ส่วน MyClass.f คือฟังก์ชั่นของคลาส
9.3.4 ออปเจคต์ที่เป็นเมธอด (Method Objects)

จากตัวอย่างคือ x.f() เรียกว่าเป็นเมธอด
เราอาจอ้างถึงเมธอดผ่านตัวแปรได้

xf = x.f
while True:
    print xf()

ตัวอย่างนี้จะพิมพ์ "hello world" ไปเรื่อย จนกว่าจะกดขัดจังหวะ

เมธอดสามารถใช้งานพารามิเตอร์ได้เหมือนฟังก์ชั่นปกติ เวลาเรียกใช้งานก็ต้องใส่พารามิเตอร์ให้ครบเช่นกัน ไม่งั้นไพธอนจะยกข้อผิดพลาดขึ้นแสดง
แต่พารามิเตอร์ (arguments) ของเมธอดจะต่างไปจากฟังก์ชั่นปกติเล็กน้อย เพราะเมธอดเป็นฟังก์ชั่นของอินสแตนซ์ของคลาส ดังนั้นพฤติกรรมของเมธอดคือ เมื่อเราเรียกใช้เมธอดว่า x.f() จริง ๆ แล้วมันคือการที่เราเรียกว่า MyClass.f(x) นี่คือเหตุที่ต้องกำหนดตัวแปรพิเศษ self ในตอนนิยามคลาส


9.4 หมายเหตุเรื่องคลาส (Random Remarks)

  • แอตทริบิวต์ข้อมูลจะสำคัญกว่าแอตทริบิวต์ที่เป็นเมธอดเสมอ แปลว่าถ้าตั้งชื่อตัวแปรกับฟังก์ชั่นซ้ำกัน ชื่อตัวแปรจะสำคัญกว่า ดังนั้นเราจึงควรตระหนักและหาทางป้องกันข้อผิดพลาดนี้ อาจจะด้วยการตั้งชื่อเมธอดให้เป็นอักษรตัวใหญ่นำหน้า หรือนำหน้าตัวแปรด้วยเครื่องหมาย "_" หรืออื่น ๆ ที่เคยชิน
  • ไพธอนไม่ได้ปกป้องตัวแปรในอินสแตนซ์อย่างสมบูรณ์แบบ ใคร ๆ ก็สามารถเปลี่ยนค่ามันได้ หากต้องการคุณสมบัตินี้จริง ๆ อาจต้องเขียนส่วนขยายด้วยภาษาซี
  • เป็นหน้าที่ของเราเองที่ต้องใช้แอตทริบิวต์ข้อมูลด้วยความระมัดระวัง ควรตั้งข้อกำหนดให้แม่น ในเรื่องการตั้งชื่อ อาจลดความผิดพลาดได้
  • ถ้าอยู่ภายในเมธอด และเราไม่ระบุตัวแปรภายนอกเป็นกลอบอลแล้ว ไม่มีทางที่เราจะอ้างถึงตัวแปรที่อยู่ภายนอกได้ การนี้ทำให้ลดข้อผิดพลาดและอ่านโค๊ดง่าย
  • มีหลายครั้งที่เราไม่จำเป็นต้องใส่ตัวแปรพิเศษ self ไว้ในฟังก์ชั่นของคลาส แต่ใส่ไว้ดีกว่าไม่ใส่ เพราะเพื่อให้เป็นนิสัยแห่งการทำตามมาตรฐาน มีผลให้คนอื่นอ่านโค๊ดเราง่ายขึ้น
  • เนื่องจากไพธอนแทนค่าตัวแปรได้อย่างไม่มีข้อจำกัด ดังนั้นจึงสามารถเขียนโค๊ดให้เอาฟังก์ชั่นจากภายนอก มาเป็นฟังก์ชั่นภายในคลาสได้ แต่อันนี้เป็นตัวอย่างที่ไม่ดีมาก ๆ เพราะอ่านโค๊ดแล้วสับสน
    # Function defined outside the class
    def f1(self, x, y):
        return min(x, x+y)
    
    class C:
        f = f1
        def g(self):
            return 'hello world'
        h = g

    คลาส C สามารถเรียกใช้งานเมธอดแอตทริบิวต์ C.f(x,y) ได้

    >>> x = C()
    >>> x.f(1,2)
    1
  • เมธอดอาจเรียกใช้เมธอดอื่นในคลาสเดียวกันได้ด้วยการใช้ self นำหน้าเมธอดที่จะเรียก
    class Bag:
        def __init__(self):
            self.data = []
        def add(self, x):
            self.data.append(x)
        def addtwice(self, x):
            self.add(x)
            self.add(x)


9.5 การสืบทอดคลาส (Inheritance)

ถ้าสืบทอดไม่ได้ก็ไม่ใช่คลาส รูปแบบโครงสร้างของการสืบทอดคือ

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

ถ้าคลาสฐานถูกกำหนดไว้ที่มอดูลอื่น รูปแบบจะเป็น

class DerivedClassName(modname.BaseClassName):

คลาสใหม่ที่แตกออกมานี้ สามารถสร้างเมธอดเพื่อครอบงำเมธอดเดิมได้อย่างไม่มีข้อจำกัด โดยที่ถ้าสร้างขึ้นมาแล้วโค๊ดภายใต้เมธอดเดิมจะไม่ถูกเรียก แต่หากยังต้องการเรียกโค๊ดจากเมธอดเดิมอยู่ เราต้องเรียกใช้เองในรูปแบบ BaseClassName.methodname(self, arguments)

9.5.1 การสืบทอดหลายทาง (Multiple Inheritance)

รูปแบบโครงสร้างคือ

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

ลำดับการทำงานก็คือ ถ้าพบคลาส Base1 ตรง ๆ ก็จะสืบทอดจาก Base1 เลย แต่ถ้าไม่พบก็จะควานหาคลาสฐานของ Base1 ลึกลงไปจนสุด และถ้ายังไม่พบจึงมาเริ่มต้นค้นจาก Base2 ต่อไปเรื่อย ๆ จนหมด

(ลำดับการทำงานแบบนี้จะขัดธรรมชาติสักหน่อย คือจริง ๆ แล้วน่าจะไล่ไป Base1 - Base2 - Base3 แล้วจึงย้อนมาหาฐานของ Base1 อีกที ซึ่งดูจะมีประโยชน์กว่า ด้วยเหตุผลดังกล่าวนี้เอง จึงควรใช้ด้วยความระมัดระวัง เพราะมีโอกาสผิดพลาดมาก)


9.6 ตัวแปรส่วนตัว (Private Variables)

ใช้หลักแค่ว่านำหน้าชื่อตัวแปรต้วย underscore สองตัว เช่น __spam ตัวแปรนั้นจะกลายเป็นตัวแปรส่วนตัวของคลาสนั้นเอง ไม่สามารถถูกเรียกจากที่อื่นในรูป X.__spam ได้

>>> class C:
...   __spam = 5
...   s = 6
... 
>>> a = C()
>>> a.s
6

>>> a.__spam
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: C instance has no attribute '__spam'

แต่ไพธอนก็ยังไม่ทำให้เป็นส่วนตัวจริง ๆ อยู่ดี เพราะอาจถูกเรียกในรูปของ X._classname__spam ได้

>>> a._C__spam
5

>>> dir(a)
['_C__spam', '__doc__', '__module__', 's']


9.7 ปกิณกะ (Odds and Ends)

อาจเติมแอตทริบิวต์ข้อมูลให้กับคลาสอินสแตนซ์ได้ทุกเมื่อ

class Employee:
    pass

john = Employee() # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000


9.8 ตัวยกข้อผิดพลาดก็เป็นคลาส (Exceptions Are Classes Too)

ดังนั้น เราสามารถสร้างตัวยกข้อผิดพลาดแบบซ้อนลึกลงไปเรื่อย ๆ
เขียนได้สองรูปแบบคือ

  • raise Class, instance

    instance ในที่นี้ คืออินสแตนซ์ของ Class หรือคลาสลูก

  • อีกอันคือ
    raise instance

    ซึ่งถ้าเขียนแบบเต็ม ๆ ต้องเขียนว่า

    raise instance.__class__, instance

แต่ต้องระวังการดักตอนแตกลูกแตกหลานคลาส เพราะถ้าดักพบคลาสแม่ก่อน เขาจะถือว่าดักได้แล้ว และจะเอาขึ้นเลย ลองดู

>>> class B:
...     pass
... 
>>> class C(B):
...     pass
... 
>>> class D(C):
...     pass
... 
>>> for c in [B, C, D]:
...     try:
...         raise c()
...     except D:
...         print "D"
...     except C:
...         print "C"
...     except B:
...         print "B"
... 
B
C
D

>>> for c in [B, C, D]:
...     try:
...         raise c()
...     except C:
...         print "C"
...     except B:
...         print "B"
...     except D:
...         print "D"
... 
B
C
C

เวลายกข้อผิดพลาดขึ้นแสดง รูปแบบคือ

Exception_Class: str(instance)


9.9 ตัวกระทำซ้ำ (Iterators)

ลองดูตัวอย่าง for

for element in [1, 2, 3]:
    print element
for element in (1, 2, 3):
    print element
for key in {'one':1, 'two':2}:
    print key
for char in "123":
    print char
for line in open("myfile.txt"):
    print line

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

>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> it.next()
'a'

>>> it.next()
'b'

>>> it.next()
'c'

>>> it.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
    it.next()
StopIteration

เมื่อรู้เบื้องลึกแล้ว เราก็สามารถแปลงพฤติกรรมของการทำซ้ำได้ ด้วยการนิยามเมธอด __iter__() และ next() ในคลาสของเราใหม่

class Reverse:
    "Iterator for looping over a sequence backwards"
    def __init__(self, data):
        self.data = data
        self.index = len(data)
    def __iter__(self):
        return self
    def next(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

รันได้ว่า

>>> for char in Reverse('spam'):
...     print char
...
m
a
p
s


9.10 เจนเนอเรเตอร์ (Generators)

เป็นอีกตัวนึงที่ใช้สร้างตัวกระทำซ้ำ โดยใช้ประโยค yield ซึ่งพิเศษตรงที่ว่ามันสามารถจำข้อมูลและสถานะจากครั้งก่อนที่เคยรันได้ พอถูกเรียกจาก next() เมื่อไหร่ มันจะกลับไปทำงานด้วยสถานะจากครั้งก่อนทันที

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

รันได้ว่า

>>> for char in reverse('golf'):
...     print char
...
f
l
o
g

ทำให้เขียนโค๊ดได้สั้น แต่อาจอ่านยากนิดนึง แต่ถ้าใช้คล่องแล้วจะประหยัดโค๊ดไปได้เยอะ เพราะไม่ต้องมานั่งเขียนพวก self.index และ self.data เอง


9.11 เจนเนอเรเตอร์เอกซ์เพรสชั่น (Generator Expressions)

รูปแบบเหมือน ลิสต์คอมพรีเฮนชั่น (list comprehension) แต่ใช้วงเล็บธรรมดาแทน เขียนและอ่านโค๊ดง่าย และประหยัดหน่วยความจำ

>>> sum(i*i for i in range(10))                 # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec))         # dot product
260

>>> from math import pi, sin
>>> sine_table = dict((x, sin(x*pi/180)) for x in range(0, 91))

>>> unique_words = set(word  for line in page  for word in line.split())

>>> valedictorian = max((student.gpa, student.name) for student in graduates)

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1,-1,-1))
['f', 'l', 'o', 'g']

10. มีอะไรให้ใช้บ้าง (Brief Tour of the Standard Library)

10.1 งานของระบบปฏิบัติการ (Operating System Interface)
10.2 ไฟล์ไวลด์คาร์ด (File Wildcards)
10.3 อาร์กิวเมนต์ (Command Line Arguments)
10.4 นำเข้า ส่งออก และเปลี่ยนทิศ (Error Output Redirection and Program Termination)
10.5 จับคู่สตริงก์ (String Pattern Matching)
10.6 คณิตศาสตร์ (Mathematics)
10.7 อินเตอร์เน็ต (Internet Access)
10.8 วันที่และเวลา (Dates and Times)
10.9 การบีบอัดข้อมูล (Data Compression)
10.10 จับประสิทธิภาพ (Performance Measurement)
10.11 การควบคุมคุณภาพ (Quality Control)
10.12 พร้อมใช้ (Batteries Included)


เรามาดูว่าในไลบรารีมาตรฐานของไพธอน มีฟังก์ชั่นหรือเมธอดในมอดูล อะไรให้ใช้บ้าง


10.1 งานของระบบปฏิบัติการ (Operating System Interface)

os
มอดูล os มีเมธอดให้ใช้เยอะมาก

>>> import os
>>> os.system('time 0:02')
0

>>> os.getcwd()      # Return the current working directory
'C:\Python24'

>>> os.chdir('/server/accesslogs')

ต้องสั่งว่า import os เท่านั้น ห้ามใช้ว่า from os import * อันนี้เป็นภาคบังคับ ไม่งั้นชื่อฟังก์ชั่นจะตีกันเละกับบิลด์อินฟังก์ชั่น
ถ้างงหรือหลงลืม ให้ใช้บิลด์อินฟังก์ชั่นสองตัวคือ dir() และ help() ดูว่าในมอดูลมีอะไรบ้าง

>>> import os
>>> dir(os)
<returns a list of all module functions>

>>> help(os)
<returns an extensive manual page created from the module's docstrings>
shutil
สำหรับงานจิปาถะเล็ก ๆ น้อย ๆ อาจใช้ตัวนี้แทน ดูจะอ่านเข้าใจง่ายกว่า

>>> import shutil
>>> shutil.copyfile('data.db', 'archive.db')
>>> shutil.move('/build/executables', 'installdir')


10.2 ไฟล์ไวลด์คาร์ด (File Wildcards)

glob
ใช้กับไวลด์คาร์ดคล่องตัวกว่า

 >>> import glob
>>> glob.glob('*.py')
['primes.py', 'random.py', 'quote.py']


10.3 อาร์กิวเมนต์ (Command Line Arguments)

sys
ใช้เยอะมาก เช่น สมมุติไฟล์ demo.py มีโค๊ดแบบนี้

#!/usr/bin/env python
import sys
print sys.argv

พอสั่งงานจากบรรทัดคำสั่งว่า "python demo.py one two three" จะได้ผลลัพธ์แบบนี้

$ python demo.py one two three
['demo.py', 'one', 'two', 'three']


10.4 นำเข้า ส่งออก และเปลี่ยนทิศ (Error Output Redirection and Program Termination)

sys
ใช้ sys เหมือนเดิม เพราะในนี้มีฟังก์ชั่นของ stdin stdout และ stderr เราจะผันการแสดงผลลัพธ์ไปทางไหน ก็ใช้จากมอดูลนี้

>>> sys.stderr.write('Warning, log file not found starting a new one
')
Warning, log file not found starting a new one

เวลาต้องการหยุดการทำงานของโปรแกรม ใช้ sys.exit() เป็นปกติ


10.5 จับคู่สตริงก์ (String Pattern Matching)

re
ใช้งาน เรกูลาร์เอกเพรสชั่น (regular expression) ได้สะดวกและเป็นธรรมชาติ

>>> import re

>>> re.findall(rf[a-z]*', 'which foot or hand fell fastest')
['foot', 'fell', 'fastest']

>>> re.sub(r'[a-z]+) ', r'', 'cat in the the hat')
'cat in the hat'
ใช้เมธอดของสตริงก์ที่มีอยู่แล้ว
ถ้างานไม่ซับซ้อน ใช้อันนี้เร็วกว่า อ่านง่ายตรวจง่าย

>>> 'tea for too'.replace('too', 'two')
'tea for two' 


10.6 คณิตศาสตร์ (Mathematics)

math
มอดูลนี้ ใช้งานทศนิยมลอยและไลบรารีมาตรฐานทางคณิตศาตร์ได้คล่องตัวกว่า

>>> import math
>>> math.cos(math.pi / 4.0)
0.70710678118654757

>>> math.log(1024, 2)
10.0
random
งานการสุ่มแยกออกมาจาก math

>>> import random
>>> random.choice(['apple', 'pear', 'banana'])
'apple'

>>> random.sample(xrange(100), 10)   # sampling without replacement
[30, 83, 16, 4, 8, 81, 41, 50, 18, 33]

>>> random.random()    # random float
0.17970987693706186

>>> random.randrange(6)    # random integer chosen from range(6)
4


10.7 อินเตอร์เน็ต (Internet Access)

urllib2
ใช้งานด้านลูกข่าย (client) เช่นเปิดดู url เป็นต้น

>>> import urllib2
>>> for line in urllib2.urlopen('http://tycho.usno.navy.mil/cgi-bin/timer.pl'):
...     if 'EST' in line or 'EDT' in line:  # look for Eastern Time
...         print line
    
<BR>Nov. 25, 09:43:32 PM EST
smtplib
ใช้จัดการเมล

>>> import smtplib
>>> server = smtplib.SMTP('localhost')
>>> server.sendmail('soothsayer@example.org', 'jcaesar@example.org',
"""To: jcaesar@example.org
From: soothsayer@example.org

Beware the Ides of March.
""")

>>> server.quit()


10.8 วันที่และเวลา (Dates and Times)

datetime
มอดูลนี้มีฟังก์ชั่นทางวันที่และเวลาเยอะ รวมทั้งยังรองรับเรื่องโซนเวลาด้วย

# dates are easily constructed and formatted
>>> from datetime import date
>>> now = date.today()
>>> now
datetime.date(2003, 12, 2)

>>> now.strftime("%m-%d-%y. %d %b %Y is a %A on the %d day of %B.")
'12-02-03. 02 Dec 2003 is a Tuesday on the 02 day of December.'

# dates support calendar arithmetic
>>> birthday = date(1964, 7, 31)
>>> age = now - birthday
>>> age.days
14368


10.9 การบีบอัดข้อมูล (Data Compression)

มีเพียบ zlib  gzip  bz2  zipfile  tarfile
ลองดู zlib เป็นตัวอย่าง

>>> import zlib
>>> s = 'witch which has which witches wrist watch'
>>> len(s)
41

>>> t = zlib.compress(s)
>>> len(t)
37

>>> zlib.decompress(t)
'witch which has which witches wrist watch'

>>> zlib.crc32(s)
226805979


10.10 จับประสิทธิภาพ (Performance Measurement)

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

>>> from timeit import Timer
>>> Timer('t=a; a=b; b=t', 'a=1; b=2').timeit()
0.57535828626024577

>>> Timer('a,b = b,a', 'a=1; b=2').timeit()
0.54962537085770791

สนุก ๆ นะครับ อันนี้เครื่องผม Intel Core2 Duo E6300 เริ่มเก่าแล้วละ ไพธอนรุ่น 2.4.4 [GCC 4.2.3 20071123 (prerelease) (Debian 4.2.2-4)]

>>> Timer('t=a; a=b; b=t', 'a=1; b=2').timeit()
0.21430277824401855

>>> Timer('a,b = b,a', 'a=1; b=2').timeit()
0.14265108108520508

จะเห็นว่าใช้สลับแบบทูเปิลเร็วกว่าเยอะ โดยเฉพาะซีพียูรุ่นใหม่ ๆ (และไพธอนรุ่นเกือบใหม่)

อันนี้เป็นแบบเล่น ๆ ถ้าเอาจริงจังก็ยังมีมอดูล profile ใช้ดูเวลาโค๊ดเยอะ ๆ


10.11 การควบคุมคุณภาพ (Quality Control)

มาถึงนี่ได้ก็เริ่มเก๋าแล้วครับ อันนี้ใช้กับงานใหญ่ ๆ ที่เราจะจัดการดูแลโค๊ดเราให้มีมาตรฐานที่ดี เผื่อว่าใครจะเอาไปใช้ หรือจะทำงานร่วมกับใคร ก็จะเข้ากันได้แบบไร้ปัญหา

doctest
ใช้ตรวจสอบมอดูล โดยเราจะต้องเขียนผลการทดสอบที่เราหวังว่ามันจะเป็นแบบนั้นไว้ใน docstring เวลารัน เขาก็จะทดสอบรันตามนั้น ถ้าผลตรงกันก็ใช้ได้ แต่ถ้าผลไม่ตรง ก็จะรายงานออกมา

def average(values):
    """Computes the arithmetic mean of a list of numbers.

    >>> print average([20, 30, 70])
    40.0
    """
    return sum(values, 0.0) / len(values)

import doctest
doctest.testmod()   # automatically validate the embedded tests
unittest
ใช้งานยากกว่า แต่ดูเป็นระบบใช้ได้จริงกว่า

import unittest

class TestStatisticalFunctions(unittest.TestCase):

    def test_average(self):
        self.assertEqual(average([20, 30, 70]), 40.0)
        self.assertEqual(round(average([1, 5, 7]), 1), 4.3)
        self.assertRaises(ZeroDivisionError, average, [])
        self.assertRaises(TypeError, average, 20, 30, 70)

unittest.main() # Calling from the command line invokes all tests


10.12 พร้อมใช้ (Batteries Included)

ไพธอนพยายามทำให้ตัวเองพร้อมใช้งานมากที่สุด จึงพยายามจัดแพกเกจและมอดูลมาให้ครบถ้วน เช่น

  • มอดูล xmlrpclib และ SimpleXMLRPCServer ที่ทำงานกับ XML-RPC ได้ทั้งฝั่งลูกข่ายและแม่ข่าย โดยที่เราไม่ต้องเข้าไปจัดการกับโค๊ด XML เอง
  • แพกเกจ email ไม่เพียงแค่รับส่งอีเมล แต่มีฟังก์ชั่นของการเข้ารหัสและปรุงส่วนหัวของเมลให้ด้วย
  • แพกเกจ xml.dom และ xml.sax ใช้ในการทำงานกับภาษา XML อย่างมีประสิทธิภาพ ในทำนองเดียวกันมอดูล csv ก็รองรับการทำงานกับไฟล์ csv รวม ๆ กันแล้วก็ทำให้ลดความยุ่งยากในการส่งผ่านข้อมูลระหว่างงานได้เป็นอย่างดี
  • งานที่ต้องใช้งานหลายภาษาในหลายประเทศ ก็มีมอดูลหรือแพกเกจอย่าง gettext  locale และ codecs

บางทีการ "พร้อมใช้" ก็อาจสู้มอดูลจากข้างนอกไม่ได้เหมือนกัน แต่โดยรวม ๆ แล้วถือว่าเจ๋งพอตัว

11. เหลือบดูเครื่องมือเซียน (Brief Tour of the Standard Library - Part II)

11.1 การแสดงผล (Output Formatting)
11.2 เทมเพลต (Templating)
11.3 ข้อมูลไบนารี (Working with Binary Data Record Layouts)
11.4 เธรด (Multi-threading)
11.5 ปูม (Logging)
11.6 กำจัดจุดอ่อน (Weak References)
11.7 ใช้ลิสต์ให้สะดวก (Tools for Working with Lists)
11.8 เลขทศนิยมลอย (Decimal Floating Point Arithmetic)

11.1 การแสดงผล (Output Formatting)

repr
มอดูล repr มีไว้สำหรับปรับฟังก์ชั่น repr() ให้เป็นแบบที่เราต้องการ
>>> import repr   
>>> repr.repr(set('supercalifragilisticexpialidocious'))
"set(['a', 'c', 'd', 'e', 'f', 'g', ...])"
pprint
ช่วยจัดรูปแบบให้เหมาะกับงานที่ไปออกเครื่องพิมพ์
>>> import pprint
>>> t = [[[['black', 'cyan'], 'white', ['green', 'red']], [['magenta',
...     'yellow'], 'blue']]]
...

>>> pprint.pprint(t, width=30)
[[[['black', 'cyan'],
   'white',
   ['green', 'red']],
  [['magenta', 'yellow'],
   'blue']]]
textwrap
เป็นอีกตัวนึง แต่เหมาะในการจัดการงานข้อความ
>>> import textwrap
>>> doc = """The wrap() method is just like fill() except that it returns
... a list of strings instead of one big string with newlines to separate
... the wrapped lines."""
...

>>> print textwrap.fill(doc, width=40)
The wrap() method is just like fill()
except that it returns a list of strings
instead of one big string with newlines
to separate the wrapped lines.
locale
จัดรูปแบบออกมาให้เหมาะกับภาษาของประเทศที่เรากำหนด
>>> import locale
>>> locale.setlocale(locale.LC_ALL, 'English_United States.1252')
'English_United States.1252'

>>> conv = locale.localeconv()          # get a mapping of conventions
>>> x = 1234567.8
>>> locale.format("%d", x, grouping=True)
'1,234,567'

>>> locale.format("%s%.*f", (conv['currency_symbol'],
...           conv['frac_digits'], x), grouping=True)
'$1,234,567.80'

11.2 เทมเพลต (Templating)

string
เป็นเรื่องการจัดการข้อความให้เหมาะสม โดยถ้าเราเขียนโค๊ดให้เรียกใช้เทมเพลตแล้ว เวลาแจกจ่ายโปรแกรมไปแล้ว ผู้ใช้สามารถปรับแต่งข้อความให้เหมาะสมกับงานของเขาได้ง่ายกว่าต้องมาแฮ๊กโค๊ดเอง
เวลาใช้เอาเครื่องหมาย "$" ใส่ข้างหน้าตัวแปร แล้วแทนค่าตอนรัน (แต่ถ้าต้องการใช้ $ ก็แค่ใส่เป็น "$$")
>>> from string import Template
>>> t = Template('${village}folk send $$10 to $cause.')
>>> t.substitute(village='Nottingham', cause='the ditch fund')
'Nottinghamfolk send $10 to the ditch fund.'
ถ้าใส่ค่าตัวแปรในดิกชันนารีผิด เมธอด substitute จะยกข้อผิดพลาด KeyError ขึ้นแสดง ในงานบางประเภทผู้ใช้อาจใส่ค่าไม่ครบ ก็มีเมธอด safe_substitute เพื่อป้องกันการแสดงข้อความผิดพลาดได้
>>> t = Template('Return the $item to $owner.')
>>> d = dict(item='unladen swallow')
>>> t.substitute(d)
Traceback (most recent call last):
  . . .
KeyError: 'owner'

>>> t.safe_substitute(d)
'Return the unladen swallow to $owner.'
เรายังสามารถสร้างคลาสลูกให้กับ Template ในการแปลงสัญลักษณ์ระบุตัวแปรได้ ตามตัวอย่างเปลี่ยนจากสัญลักษณ์ $ เป็น %
>>> import time, os.path
>>> photofiles = ['img_1074.jpg', 'img_1076.jpg', 'img_1077.jpg']
>>> class BatchRename(Template):
...     delimiter = '%'
>>> fmt = raw_input('Enter rename style (%d-date %n-seqnum %f-format):  ')
Enter rename style (%d-date %n-seqnum %f-format):  Ashley_%n%f


>>> t = BatchRename(fmt)
>>> date = time.strftime('%d%b%y')

>>> for i, filename in enumerate(photofiles):
...     base, ext = os.path.splitext(filename)
...     newname = t.substitute(d=date, n=i, f=ext)
...     print '%s --> %s' % (filename, newname)

img_1074.jpg --> Ashley_0.jpg
img_1076.jpg --> Ashley_1.jpg
img_1077.jpg --> Ashley_2.jpg
เทมเพลตแบบนี้เหมาะกับงานรายงาน หรืองาน XML และ HTML

11.3 ข้อมูลไบนารี (Working with Binary Data Record Layouts)

struct
มอดูลนี้ใช้ฟังก์ชั่น pack() และ unpack() ในการทำงานกับข้อมูลไบนารีที่มีความยาวไม่คงที่ ตัวอย่างจะเป็นการวนรอบทำงานกับข้อมูลส่วนหัวของไฟล์ ZIP (ใช้สัญลักษณ์ "H" และ "L" แทนข้อมูลไบนารีแบบไม่นับเครื่องหมายขนาด 2 และ 4 ไบต์ ตามลำดับ)
import struct

data = open('myfile.zip', 'rb').read()
start = 0
for i in range(3):                      # show the first 3 file headers
    start += 14
    fields = struct.unpack('LLLHH', data[start:start+16])
    crc32, comp_size, uncomp_size, filenamesize, extra_size = fields

    start += 16
    filename = data[start:start+filenamesize]
    start += filenamesize
    extra = data[start:start+extra_size]
    print filename, hex(crc32), comp_size, uncomp_size

    start += extra_size + comp_size     # skip to the next header

11.4 เธรด (Multi-threading)

เธรด เป็นการกระจายงานแล้วทำขนานกันไป โปรแกรมยุคใหม่จำเป็นต้องมีอย่างยิ่ง งานที่เหมาะกับเธรด เช่น เธรดนึงรอผู้ใช้ป้อนข้อมูล อีกเธรดนึงก็ทำงานอื่นขนานกันไป พอผู้ใช้ป้อนเสร็จก็ประมวลผลเสร็จพอดี เป็นต้น
threading
ตัวอย่างเป็นการใช้มอดูลนี้บีบอัดข้อมูล โดยทำเป็นงานเบื้องหลัง
import threading, zipfile

class AsyncZip(threading.Thread):
    def __init__(self, infile, outfile):
        threading.Thread.__init__(self)        
        self.infile = infile
        self.outfile = outfile
    def run(self):
        f = zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED)
        f.write(self.infile)
        f.close()
        print 'Finished background zip of: ', self.infile

background = AsyncZip('mydata.txt', 'myarchive.zip')
background.start()
print 'The main program continues to run in foreground.'
    
background.join()    # Wait for the background task to finish
print 'Main program waited until background was done.'
จุดที่ยากของงานเธรด คือถ้าต้องมีการแลกเปลี่ยนข้อมูลกับโปรแกรมอื่น ๆ มอดูล threading นี้ เตรียมคลาสหรือฟังก์ชั่นในการนี้ไว้แล้ว เช่น locks, events, condition variables, and semaphores
Queue
จากข้อกังวลเรื่องการแลกเปลี่ยนข้อมูลกับงานภายนอกนี่เอง จึงเกิดมอดูลนี้ขึ้นมา ทำให้เราไม่ต้องมาเขียนเรื่องจังหวะการรอของเธรดเอง

11.5 ปูม (Logging)

งานบันทึกปูม เป็นงานน่าเบื่อหน่อย แต่ถ้างานเราใหญ่ขึ้นก็ต้องทำ
logging
มอดูลนี้มีเครื่องมือให้พร้อมสำหรับการบันทึกปูม ง่ายสุดคือการส่งไปให้ระบบแสดงคือ sys.stderr
import logging
logging.debug('Debugging information')
logging.info('Informational message')
logging.warning('Warning:config file %s not found', 'server.conf')
logging.error('Error occurred')
logging.critical('Critical error -- shutting down')
ให้ผลแบบนี้
WARNING:root:Warning:config file server.conf not found
ERROR:root:Error occurred
CRITICAL:root:Critical error -- shutting down
ค่าปริยายของการส่งผลงานปูมคือ การแจ้งข้อผิดพลาดของระบบ (standard error) แต่เราสามารถดักให้ส่งไปยังจุดอื่นได้ เช่น อีเมล ดาต้าแกรม ซอคเก็ต หรือแม้กระทั่งแม่ข่ายเว็บ และยังอาจเลือกจ่ายไปยังจุดต่าง ๆ แบ่งตามระดับความสำคัญของข้อมูลคือ DEBUG, INFO, WARNING, ERROR, และ CRITICAL นอกจากนี้ยังสามารถแยกทำเป็นไฟล์ปรับตั้งสำหรับงานปูมโดยเฉพาะ โดยไม่ต้องเข้าไปยุ่งกับโค๊ดหลักเลย

11.6 กำจัดจุดอ่อน (Weak References)

ปกติไพธอนจัดการหน่วยความจำได้ดีอยู่แล้ว แต่มีบางงานที่ยกเว้น
weakref
งานตามรอยโค๊ด (tracking) เราต้องสร้างตัวแปรอ้างอิงของเราขึ้นมา หลายครั้งตัวที่เราสร้างกลับกลายเป็นปัญหาเสียเอง เวลาเราลบตัวแปรไม่หมดมันจะไปรบกวนหน่วยความจำที่เราต้องการดู มอดูล weakref ทำมาเพื่อการนี้โดยเฉพาะ
>>> import weakref, gc
>>> class A:
...     def __init__(self, value):
...             self.value = value
...     def __repr__(self):
...             return str(self.value)
...
>>> a = A(10)                   # create a reference
>>> d = weakref.WeakValueDictionary()
>>> d['primary'] = a            # does not create a reference
>>> d['primary']                # fetch the object if it is still alive
10

>>> del a                       # remove the one reference
>>> gc.collect()                # run garbage collection right away
0

>>> d['primary']                # entry was automatically removed
Traceback (most recent call last):
  File "<pyshell#108>", line 1, in -toplevel-
    d['primary']                # entry was automatically removed
  File "C:/PY24/lib/weakref.py", line 46, in __getitem__
    o = self.data[key]()
KeyError: 'primary'

11.7 ใช้ลิสต์ให้สะดวก (Tools for Working with Lists)

เพราะลิสต์ใช้ง่าย เลยมีคนเขียนมอดูลเติมความสามารถให้ใช้ได้หลายหลายยิ่งขึ้น
array
ใช้แปลงลิสต์เป็นแอเรย์ออปเจคต์ที่เหมาะกับงานตัวเลข ตัวอย่างเป็นลิสต์ของตัวเลขที่เป็นจำนวนเต็ม (ปกติใช้หน่วยความจำ 16 ไบต์) ถูกแปลงเป็นแอเรย์ของข้อมูลไบนารีไม่คิดเครื่องหมายขนาด 2 ไบต์ (ใช้สัญลักษณ์ "H" แปลงแล้วใช้งานง่ายมาก
>>> from array import array
>>> a = array('H', [4000, 10, 700, 22222])
>>> sum(a)
26932

>>> a[1:3]
array('H', [10, 700])
collections
มอดูลนี้มีออปเจคต์ deque() ที่ใช้งานเหมือนลิสต์ ถ้าเพิ่มหรือลดสมาชิกข้างหน้าหรือต่อท้ายแล้วจะเร็วกว่าลิสต์ แต่ถ้าแทรกหรือค้นข้อมูลจะช้ากว่า เราเลยเอามาใช้ในงานคิว
>>> from collections import deque
>>> d = deque(["task1", "task2", "task3"])
>>> d.append("task4")
>>> print "Handling", d.popleft()
Handling task1
กับงานค้นแบบ Breadth-first search
unsearched = deque([starting_node])
def breadth_first_search(unsearched):
    node = unsearched.popleft()
    for m in gen_moves(node):
        if is_goal(m):
            return m
        unsearched.append(m)
bisect
ใช้เรียงลิสต์
>>> import bisect
>>> scores = [(100, 'perl'), (200, 'tcl'), (400, 'lua'), (500, 'python')]
>>> bisect.insort(scores, (300, 'ruby'))
>>> scores
[(100, 'perl'), (200, 'tcl'), (300, 'ruby'), (400, 'lua'), (500, 'python')]
heapq
ใช้จัดการฮีป จะเร็วกว่าใช้ลิสต์จริง ๆ เพราะงานฮีปเราสนใจแค่ค่าตัวที่น้อยที่สุดเท่านั้น ไม่ต้องจัดเรียงใหม่ทั้งลิสต์
>>> from heapq import heapify, heappop, heappush
>>> data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
>>> heapify(data)                      # rearrange the list into heap order
>>> heappush(data, -5)                 # add a new entry
>>> [heappop(data) for i in range(3)]  # fetch the three smallest entries
[-5, 0, 1]

11.8 เลขทศนิยมลอย (Decimal Floating Point Arithmetic)

decimal
มอดูลนี้มีชนิดข้อมูลชื่อ Decimal สำหรับเลขทศนิยมลอยที่เหมาะกับงานบัญชี และงานที่ต้องการความถูกต้องของทศนิยมสูง
ตัวอย่างเป็นการคำนวณภาษีอัตราร้อยละ 5 ของค่าโทรศัพท์ 70 สตางค์ เทียบกันระหว่างใช้ Decimal กับใช้ทศนิยมลอยของระบบ
>>> from decimal import *       
>>> Decimal('0.70') * Decimal('1.05')
Decimal("0.7350")

>>> .70 * 1.05
0.73499999999999999
ฟังก์ชั่น Decimal จะคำนวนเหมือนเราคำนวนด้วยมือ จากตัวอย่างคือการคูณเลขทศนิยม 2 ตำแหน่งเข้าด้วยกัน ดังนั้นผลลัพธ์จะมีทศนิยม 4 ตำแหน่ง ซึ่งมีความถูกต้องกว่าการใช้ทศนิยมลอยของระบบ ดูตัวอย่างข้อผิดพลาดจากการหาเศษผลหาร
>>> Decimal('1.00') % Decimal('.10')
Decimal("0.00")

>>> 1.00 % 0.10
0.09999999999999995
และความผิดพลาดจากการจัดการตัวเลขในลิสต์
>>> sum([Decimal('0.1')]*10) == Decimal('1.0')
True

>>> sum([0.1]*10) == 1.0
False
นอกจากนี้ ยังสามารถกำหนดความละเอียดของทศนิยมได้เท่าที่เราต้องการ
>>> getcontext().prec = 36
>>> Decimal(1) / Decimal(7)
Decimal("0.142857142857142857142857142857142857")

12. เรียนต่อที่ไหน (What Now?)

เอกสารหลักของไพธอนเอง
ข้อมูลจากแห่งอื่น
  • http://www.python.org/ เว็บหลักของไพธอน
  • http://docs.python.org/ ทางเข้าเอกสารหลักของไพธอน
  • http://cheeseshop.python.org รายชื่อแพกเกจใหม่ ๆ รายวัน ถ้าเราอยากจะแจกจ่ายแพกเกจของเรา ก็ต้องมาที่นี่
  • http://aspn.activestate.com/ASPN/Python/Cookbook/ ตำราปรุงไพธอนออนไลน์ของ ActiveState มีขายเป็นหนังสือด้วย ชื่อ Python Cookbook (O'Reilly & Associates, ISBN 0-596-00797-3.)
และอื่น ๆ

13. ปัจฉิมลิขิต

ยังเรียบเรียงไม่หมด ยังขาดอีก 4 เรื่อง ที่ยังไง ๆ ก็ไม่แปลเด็ดขาด เพราะท่านที่อ่านจนมาถึงตรงนี้ได้ก็คงเก่งภาษาอังกฤษกว่าผมแล้ว :D
คือ

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