หมายเหตุจาก webmaster: คุณ wd เขียนบทความชุดนี้ไว้ได้เกือบแปดเดือนแล้วครับ แต่ webmaster ตรวจไม่เสร็จสักที ตอนนี้ก็ยังไม่เสร็จ แต่เห็นว่าดองไว้นานก็น่าเสียดาย จึงเผยแพร่ทั้งที่ยังตรวจไม่เสร็จไปก่อน
เอามาจาก Python Tutorial
ตั้งใจเขียนให้อ่านสนุก ๆ นะครับ อย่าจริงจัง
ไพธอนเป็น ...
สำหรับเดเบียน ไพธอนจะถูกติดตั้งมาเป็นค่าปริยาย แต่หากถูกถอดออกไปแล้ว ก็สามารถติดตั้งใหม่ด้วยคำสั่ง
$ sudo aptitude install python
2.1 เรียกใช้ตัวแปลบรรทัดคำสั่ง (Invoking the Interpreter)
2.2 ตัวแปลคำสั่งและสภาพแวดล้อม (The Interpreter and Its Environment)
ในเดเบียนตัวโปรแกรมไพธอนจะอยู่ที่ /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
เมื่อเรียกใช้ตัวแปลบรรทัดคำสั่ง ค่าตามหลังที่เราส่งผ่านให้กับโปรแกรม จะถูกเก็บไว้ที่ตัวแปร sys.argv โดย...
python : sys.argv[0] จะเป็นสตริงว่างpython - : sys.argv[0] จะบรรจุค่า "-"-c หรือ -m : sys.argv[0] จะเก็บค่า "-c" และชื่อมอดูลตามลำดับ ค่าอื่นหลังจากนี้ จะถูกเก็บไว้ในตัวแปร sys.argv เพื่อให้คำสั่งหรือมอดูลได้เรียกใช้งานต่อไปพร็อมต์หลัก (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!
เมื่อเกิดข้อผิดพลาด ไพธอนจะรายงานข้อผิดพลาดและรายทางของการผิดพลาด โดย
รายงานความผิดพลาดทั้งหมดจะถูกส่งไปยังสายการรายงานข้อผิดพลาดของระบบ (standard error stream) และการแสดงผลก็จะถูกส่งไปยังเอาต์พุตของระบบเช่นกัน
การกดคีย์เพื่อขัดจังหวะ เช่น Control-C หรือ DEL จะ...
KeyboardInterrupt ขึ้นมา ซึ่งเราสามารถจัดการได้ด้วยประโยคคำสั่ง tryแค่เติม Hash bang ที่บรรทัดแรกของสคริปต์ ก็จะทำให้สคริปต์สามารถรันได้
#! /usr/bin/env python
อย่าลืมเปลี่ยนโหมดให้รันได้ด้วย
$ chmod +x myscript.py
หากมีส่วนของซอร์สโค้ดที่ไม่ใช่ภาษาอังกฤษล้วน ควรแจ้งรหัสอักขระที่ใช้ด้วย ด้วยการเติมต่อจากบรรทัดแรกว่า
# -*- coding: encoding -*-
สามารถดูรหัสอักขระทั้งหมดที่ Python Library Reference ในหัวข้อ codecs
เช่น ถ้าจะใช้สตริงที่มีสัญลักษณ์หน่วยเงินยูโร เราอาจเลือกใช้รหัสอักขระ ISO-8859-15 (ค่า ordinal คือ 164) ซึ่งในสคริปต์นี้จะพิมพ์ค่า 8364 ซึ่งเป็นค่ายูนิโค้ดของสัญลักษณ์ยูโร
# -*- coding: iso-8859-15 -*- currency = u"€" print ord(currency)
แต่สำหรับยุคนี้ ควรเลือกใช้ UTF-8 ดีกว่าเยอะ เพราะรองรับอักขระทุกตัวในโลก
ในการใช้งานโหมดโต้ตอบ บางครั้งอาจต้องตั้งค่าเริ่มต้นให้ระบบ ซึ่งทำได้โดยใส่ชื่อโปรแกรมที่ต้องการรันในขณะเริ่มต้นในตัวแปรแวดล้อมชื่อ 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.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."ในโหมดโต้ตอบ เราใช้แทนเครื่องคิดเลขได้เลย เช่น
>>> 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
รองรับทศนิยมจุดลอยตัว (floating point) ด้วย โดยมีหลักว่า ถ้าต้นทางเป็นจำนวนเต็ม ผลจะเป็นจำนวนเต็ม ถ้าต้นทางเป็นทศนิยม ผลจะเป็นทศนิยม (ถ้าผสมกันก็จะเป็นทศนิยมเช่นกัน)
>>> 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
*** เนื่องจาก _ เป็นตัวแปรพิเศษดังกล่าว ดังนั้นเพื่อป้องกันความผิดพลาด จึงไม่ควรกำหนดค่าให้มัน ควรใช้เป็นตัวแปรเฉพาะตามวัตถุประสงค์ของไพธอนเท่านั้น
สามารถใช้งานสตริงได้ทั้งอัญประกาศเดี่ยวและคู่ (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.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>'
ช่องว่างระหว่างสตริงจะถูกตีความเป็นการเชื่อมสตริง แต่ใช้ได้กับค่าคงที่ (literal) เท่านั้น ตัวแปรหรือฟังก์ชันไม่เกี่ยว
>>> '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
ดูเพิ่มเติม
เรื่อง
เริ่มใช้ในไพธอนรุ่น 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'ข้อมูลลิสต์ถือเป็นข้อเด่นที่สุดข้อหนึ่งของไพธอน เป็นการเอาข้อมูลมาจัดให้อยู่ในกลุ่มเดียวกัน ภายใต้เครื่องหมายวงเล็บ [] โดยข้อมูลย่อยไม่จำเป็นต้องเป็นชนิดเดียวกัน
>>> 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 เป็นออบเจกต์อันเดียวกัน (การใช้ลิสต์ต้องระวังตรงนี้นิดนึง)
การใช้งานไพธอนง่ายตรงนี้ คือเราจะเขียนโค้ดเล็ก ๆ แล้วก็จับมาต่อ ๆ กันไป กลายเป็นสคริปต์ที่ซับซ้อนสำหรับใช้งานจริง
เริ่มด้วยโค้ดอมตะ อนุกรมฟีโบนัชชี (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
ความรู้ใหม่
a คือ 0 และ b คือ 1while คำสั่งนี้จะวนรอบตราบเท่าที่เงื่อนไขลูปเป็นจริง นิพจน์เงื่อนไขที่เป็นจริง คือมีค่าที่ไม่ใช่ศูนย์หรือค่าว่าง ซึ่งสามารถเป็นสตริง ลิสต์ ลำดับ หรือนิพจน์คณิตศาสตร์ก็ได้ โดยในที่นี้ใช้นิพจน์เปรียบเทียบ ซึ่งจะใช้สัญญลักษณ์เหมือนภาษาซี คือ < คือน้อยกว่า > คือมากกว่า == คือเท่ากันกับ <= คือน้อยกว่าหรือเท่ากับ >= คือมากกว่าหรือเท่ากับ และ != คือไม่เท่ากับ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.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 แล้ว บทนี้เรามารู้จักคำสั่งควบคุมให้มากขึ้น
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 อย่างเดียว)
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
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
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
pass (pass Statements)คำสั่ง pass ไม่ทำอะไรเลย แต่มีไว้เผื่อเวลาเราวางโครงสร้างโค้ดไว้แล้ว แต่ยังไม่ได้เขียนท่อนนั้น ก็บรรจุคำสั่งนี้ไว้เพื่อให้สามารถทดสอบการรันได้
>>> while True: ... pass # Busy-wait for keyboard interrupt ...
เอาตัวอย่างในการเขียนอนุกรมฟีโบนัชชีมาเขียน
>>> 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, 1 ... while b < n: ... result.append(b) # see below ... a, b = b, a+b ... return result ... >>> f100 = fib2(100) # call it >>> f100 # write the result [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
ความรู้ใหม่
Noneresult.append(b) เป็นเมธอดของลิสต์ result เมธอดก็คือฟังก์ชันที่เป็นเฉพาะของออบเจกต์นั้น ซึ่งมีรูปแบบการเขียนเป็น obj.methodname จากตัวอย่างการใช้เมธอด result.append(b) มีผลเท่ากับ "result = result + [b]" แต่เขียนได้กระชับและเข้าใจง่ายกว่ามีหลักในการกำหนดค่าอาร์กิวเมนต์คือ
เป็นการกำหนดค่าปริยายให้กับอาร์กิวเมนต์ มีรูปแบบว่าอาร์กิวเมนต์ที่จะกำหนดค่าปริยายให้ จะต้องอยู่ทางขวาเสมอ ส่วนตัวที่ไม่กำหนด จะต้องอยู่ทางซ้ายเสมอ
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
>>> 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เหมือนกับหัวข้อก่อนหน้า แต่ในหัวข้อนี้ เจาะจงอธิบายลักษณะที่กำหนดเป็นคีย์เวิร์ด มีรูปแบบคือ "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()
หากเราส่งผ่านอาร์กิวเมนต์แบบอ้างอิงซึ่งจะกลายเป็นทูเปิลแล้ว เราจะได้ความยืดหยุ่นในการกำหนดอาร์กิวเมนต์ ตัวอย่างคือ
>>> 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)จากตัวอย่างก่อน ๆ ที่เรารู้เรื่องการผ่านค่าแบบอ้างอิงเป็นทูเปิลและดิกชันนารีแล้ว เราสามารถพลิกแพลงได้ เช่น ในตัวอย่างนี้ใช้ลิสต์แทนทูเปิล (ถ้าไม่มีการเปลี่ยนแปลงค่า ลิสต์และทูเปิลสามารถใช้แทนกันได้แบบตรง ๆ)
>>> 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 !ยืมความสามารถเรื่อง 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
บรรทัดแรกถัดจากชื่อฟังก์ชัน เป็นบรรทัดพิเศษที่ใส่บรรทัดข้อความอธิบายการทำงานของฟังก์ชัน ถ้าใช้อัญประกาศสามตัว """ หรือ ''' ก็สามารถเขียนได้หลายบรรทัด
สามารถเรียกดูข้อความในบรรทัดนี้ได้จากเมธอด 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.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)
บทนี้จะอธิบายเทคนิคการใช้งานข้อมูล
เวลาใช้งานจริง เราจะใช้ลิสต์มากหน่อย เพราะมันเปลี่ยนแปลงค่าได้ เลยทำให้มีเมธอดของลิสต์เยอะหน่อย
append(x)a[len(a):] = [x]
>>> a=[1,2,3] >>> a.append(4) >>> a [1, 2, 3, 4]
extend(L)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)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)>>> 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])>>> a=[1,2,3] >>> a.pop(1) 2 >>> a [1, 3]
index(x)>>> a=[1,2,3] >>> a.index(2) 1
count(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]ถ้าจะใช้งานลิสต์แบบสแต็คคือ เข้าหลังออกก่อน ก็แค่ใช้เมธอดให้เหมาะสม คือเอาเข้าด้วย 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]
คือ เข้าก่อนออกก่อน ก็ใช้ 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']มี 3 ตัว (ไม่นับคำสั่ง lambda)
filter(function, sequence)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)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 ไปเรียก function ต่อ ๆ ไปจนหมดข้อมูล
>>> 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
อาร์กิวเมนต์ที่สามของ reduce จะเป็นค่าเริ่มต้น ซึ่ง reduce จะเริ่มคำนวณจากค่าเริ่มต้นและข้อมูลแรกของ sequence เป็นคู่แรก (แทนที่จะเป็นข้อมูลสองตัวแรกของ sequence) และถ้า sequence ว่างเปล่า ก็จะคืนค่าเริ่มต้นมาเลย
เป็นโครงสร้างเฉพาะตัวของไพธอนที่ยืมมาจากภาษา 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']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
ทูเปิลคล้ายกับลิสต์ ต่างกันตรงเป็นข้อมูลที่สามารถกำหนดค่าได้ครั้งเดียว จึงเหมาะที่จะใช้ในงานที่ต้องการค่าคงที่
ลิสต์ใช้วงเล็บก้ามปู [] แต่ทูเปิลใช้วงเล็บธรรมดา () หรืออาจละเลยไม่ใส่ก็ได้ โดยใส่แค่จุลภาค , ตามหลังสมาชิก (แต่ต้องระวังตัวเองงงเอง)
>>> 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
ไพธอนรุ่นหลัง เติมความสามารถเรื่องเซ็ตเข้าไป ซึ่งเซ็ตก็คือลิสต์ที่สามารถใช้งานในลักษณะเซ็ตได้ เช่น การทำยูเนียนและอินเทอร์เซกชัน เป็นต้น
>>> 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'])เป็นชนิดข้อมูลพิเศษที่อยู่ในรูป {key: value, ...}
key อาจเป็นข้อมูลชนิดสตริง ตัวเลข หรือทูเปิลที่ไม่ได้บรรจุ mutable object ไว้delkeys() ในการแสดงค่าคีย์ทั้งหมด และใช้เมธอด 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}iteritems()>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
>>> for k, v in knights.iteritems():
... print k, v
...
gallahad the pure
robin the braveenumerate()>>> 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
while และ if ไม่จำเป็นต้องเป็นการเปรียบเทียบค่าเสมอไป แต่จะเป็นอะไรก็ได้ที่ ถ้าคืนค่าที่ไม่เป็นศูนย์หรือ None จะถูกนับว่าเป็นจริงin และ not in ใช้ดูว่ามีค่าอยู่ในลำดับข้อมูลหรือเปล่าis และ is not ใช้ดูว่าเป็นออบเจกต์เดียวกันหรือเปล่าa < b == c จะเปรียบเทียบว่า a น้อยกว่า b และ 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>>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance' >>> non_null = string1 or string2 or string3 >>> non_null 'Trondheim'
(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.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
modname.itemname จึงไม่ต้องกังวลเรื่องชื่อตัวแปรในมอดูลจะซ้ำกับตัวแปรในโปรแกรมหลัก>>> 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
PYTHONPATH/usr/lib/python2.4 (ตัวเลข 2.4 จะเปลี่ยนไปตามรุ่นไพธอนที่ใช้)
การค้นพาธที่ว่า สามารถดูได้จากตัวแปร sys.path ในไพธอน ซึ่งในทางปฏิบัติเราสามารถแก้ไขได้จากในโปรแกรม ทำให้การเขียนโปรแกรมมีความยืดหยุ่น
ไพธอนเร่งความเร็วตอนเริ่มโปรแกรมด้วยการแปล (compile) เอาไว้ เช่นถ้าเรามีไฟล์ชื่อ spam.py ถ้าไฟล์นี้ถูกอิมพอร์ต ไพธอนจะคอมไพล์แล้วเก็บในชื่อ spam.pyc ซึ่งไฟล์นี้จะไม่ขึ้นกับระบบปฏิบัติการ หมายความว่าเราสามารถคัดลอกไฟล์นามสกุล .pyc ไปใช้กับเครื่องต่างระบบได้เลย
สำหรับเซียน
-O ไพธอนจะคอมไพล์ไฟล์ให้เล็ก โดยนามสกุลจะกลายเป็น .pyo แทน-OO มีผลเหมือนอันแรกแต่จะลบข้อมูลที่เป็น docstring ออก.pyc หรือ .pyo อยู่แล้ว ก็ไม่จำเป็นต้องมีไฟล์ต้นฉบับ (ซึ่งนิยมใช้นามสกุลเป็น .py) ดังนั้นหากไม่ต้องการแพร่ซอร์สโค้ด อาจแจกจ่ายเป็นไฟล์คอมไพล์เหล่านี้แทนไพธอนมีมอดูลมาตรฐานเยอะมาก ดูได้จาก บรรณสารของไพธอน (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')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']
เวลามีมอดูลที่เราสร้างขึ้นเยอะ เราจะจัดกลุ่มให้ไปรวมในไดเรกทอรีต่างหาก (ทำเหมือนเวลาเรามีไฟล์เยอะ ๆ แล้วเราจะจัดระเบียบไฟล์ ในระบบไฟล์เรียงไดเรกทอรีด้วย / เช่น 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 ... ]import Sound.Effects.echo
เวลาอ้างถึงต้องอ้างแบบเต็ม ๆ
Sound.Effects.echo.echofilter(input, output, delay=0.7, atten=4)
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เวลาถูกเรียกอิมพอร์ต ไพธอนใช้ตัวแปร __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 ควรระวังเรื่องชื่อมอดูลหรือฟังก์ชันซ้ำ อาจทำให้เรียกใช้ผิด
มีหลักอยู่ว่า
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)
from . import echo from .. import Formats from ..Filters import equalizer
ต้องใส่ชื่อไดเรกทอรีที่บรรจุมอดูลย่อยไว้ในตัวแปรพิเศษชื่อ __path__ ซึ่งเป็นลิสต์ เก็บค่าพาธของมอดูลย่อยไว้
7.1 การจัดรูปแบบเอาต์พุต (Fancier Output Formatting)
7.2 การอ่านเขียนไฟล์ (Reading and Writing Files)
สามารถจัดรูปแบบได้สองแบบหลัก คือใช้ฟังก์ชัน และใช้ตัวกระทำ %
ตัวอย่างการใช้ฟังก์ชันแปลงเป็นตัวอักขระ 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เปิดไฟล์ด้วยฟังก์ชัน 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'
การจัดการไฟล์ระหว่างการเปิดแบบอักขระกับการเปิดแบบไบนารีคือ ในการเปิดแบบอักขระ ระบบจะเติมการจบบรรทัดด้วยอักขระพิเศษเพิ่มเข้าไป ทำให้เกิดปัญหาถ้าไฟล์นั้นเป็นไฟล์ไบนารี
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 filef.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 คือตำแหน่งอ้างอิง
>>> 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
เป็นการเก็บออบเจกต์ลงไฟล์แบบไร้ข้อจำกัด ดูเรื่องปิกเกิลในบรรณสารของไพธอน
ขั้นตอนการทำงานคือ ไพธอนจะแปลงออบเจกต์เป็นสตริงก่อน เรียกว่าปิกกลิง (pickling) เวลานำกลับจะแปลงสตริงนั้นกลับเป็นออบเจกต์อีกที เรียกว่า อันปิกกลิง (unpickling)
ตัวอย่างการใช้งาน สมมุติว่ามีออบเจกต์ x ที่เราต้องการเก็บ ลงไว้ในไฟล์ที่เปิดไว้แล้ว คือไฟล์ออบเจกต์ f รูปแบบคือ
pickle.dump(x, f)
เวลานำกลับก็ใช้ว่า (f ต้องถูกเปิดอยู่ก่อนแล้ว)
x = pickle.load(f)
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)
มือใหม่ต้องเจอทุกคน
>>> while True print 'Hello world'
File "<stdin>", line 1, in ?
while True print 'Hello world'
^
SyntaxError: invalid syntax
ในที่นี้คือตก : หลัง True
ข้อผิดพลาดแบบนี้ต้องตามไปแก้ในโค้ดอย่างเดียว
เกิดตอนรัน
>>> 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
จัดการด้วยประโยค 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
เราสามารถป่าวสิ่งผิดปรกติเมื่อพบได้ เช่น
>>> 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
เราอาจสร้างชนิดสิ่งผิดปรกติเองโดยสร้างเป็นคลาส ซึ่งต้องผัน (derive) มาจากคลาส Exception ไม่ว่าจะโดยตรงหรือโดยอ้อม
>>> 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ดูเรื่องคลาสได้ในบทต่อไป
ในประโยค 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'ออบเจกต์บางชนิดจะเตรียมการเก็บกวาดไว้ให้ เมื่อออบเจกต์เลิกใช้แล้ว ไม่ว่าโค้ดที่ใช้ออบเจกต์จะทำงานสำเร็จหรือล้มเหลว ดูตัวอย่างการเปิดไฟล์ต่อไปนี้
for line in open("myfile.txt"):
print line
ปัญหาของโค้ดนี้คือ ไม่มีการปิดไฟล์ ถ้าโปรแกรมใหญ่ขึ้นโค้ดแบบนี้จะกินหน่วยความจำมหาศาล ออบเจกต์ประเภทไฟล์จึงเตรียมการเก็บกวาดไว้ให้ โดยสามารถใช้ได้ผ่านประโยค with ดังนี้
with open("myfile.txt") as f:
for line in f:
print line
ตัวอย่างนี้ ไฟล์ออบเจกต์ f จะถูกลบออกจากหน่วยความจำเมื่อโปรแกรมจบ
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)
คลาสในไพธอนถูกออกแบบมาให้ใช้งานง่าย มันเลยไม่ได้ป้องกันแน่นหนาแบบภาษาอื่น อย่างไรก็ตามมันก็ยังมีคุณสมบัติของคลาสอย่างที่ควรเป็น
เพื่อให้เกิดความสุขสวัสดีทั้งผู้เขียนและผู้อ่าน บทนี้จะใช้ทับศัพท์ให้มากที่สุดเท่าที่เป็นไปได้ :)
ศัพท์สนุก ๆ
เนมสเปซต่าง ๆ จะถูกสร้างขึ้นเมื่อออบเจกต์ถูกสร้าง และมีอายุตามออบเจกต์นั้น ๆ เช่น
__main____builtin__อธิบายตามภาษาชาวบ้าน ชื่อ (Names) ก็คืออะไรที่เราต้องตั้งชื่อให้มัน เวลาเรียกก็เรียกจากชื่อ เช่นฟังก์ชัน ตัวแปร เป็นต้น ส่วนเนมสเปซ (Namespace) ก็คือห้องบรรจุชื่อนั่นเอง
z.real เราเรียก real ว่าเป็นแอตทริบิวต์ของออบเจกต์ z อะไรก็ตามที่อยู่ในระดับเดียวกับ real คืออ้างถึงด้วย z.XXX เราจะเรียกว่าอยู่ภายใต้เนมสเปซเดียวกัน (ถ้าชื่อซ้ำก็ตีกัน)
แอตทริบิวต์ อาจเป็นได้ทั้งอ่านอย่างเดียวและเขียนได้ด้วย ถ้าเป็นแบบเขียนได้ เราก็สามารถเปลี่ยนแปลงค่าได้ และใช้ประโยค del ในการลบแอตทริบิวต์นั้นได้
ในทุก ๆ ขณะของการทำงาน จะมีอย่างน้อยสามสโคปเสมอ คือ
เรื่องสนุก ๆ
--รอแปล--
โครงคือ
class ClassName:
<statement-1>
.
.
.
<statement-N>--รอแปล--
จะใช้งานหรือเข้าถึงคลาสออบเจกต์ได้สองแบบ คือ
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'
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)
นิยามว่ามันมีแอตทริบิวต์สองแบบ คือ แอตทริบิวต์ที่เป็นข้อมูล และ เมธอด
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print x.counter
del x.counterx.f คือเมธอดของอินสแตนซ์ ส่วน MyClass.f คือฟังก์ชันของคลาสจากตัวอย่างคือ x.f() เรียกว่าเป็นเมธอด
เราอาจอ้างถึงเมธอดผ่านตัวแปรได้
xf = x.f
while True:
print xf()ตัวอย่างนี้จะพิมพ์ "hello world" ไปเรื่อย จนกว่าจะกดขัดจังหวะ
เมธอดสามารถใช้งานพารามิเตอร์ได้เหมือนฟังก์ชันปกติ เวลาเรียกใช้งานก็ต้องใส่พารามิเตอร์ให้ครบเช่นกัน ไม่งั้นไพธอนจะยกข้อผิดพลาดขึ้นแสดง
แต่พารามิเตอร์ (arguments) ของเมธอดจะต่างไปจากฟังก์ชันปกติเล็กน้อย เพราะเมธอดเป็นฟังก์ชันของอินสแตนซ์ของคลาส ดังนั้นพฤติกรรมของเมธอดคือ เมื่อเราเรียกใช้เมธอดว่า x.f() จริง ๆ แล้วมันคือการที่เราเรียกว่า MyClass.f(x) นี่คือเหตุที่ต้องกำหนดตัวแปรพิเศษ self ในตอนนิยามคลาส
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)ถ้าสืบทอดไม่ได้ก็ไม่ใช่คลาส รูปแบบโครงสร้างของการสืบทอดคือ
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>ถ้าคลาสฐานถูกกำหนดไว้ที่มอดูลอื่น รูปแบบจะเป็น
class DerivedClassName(modname.BaseClassName):
คลาสใหม่ที่แตกออกมานี้ สามารถสร้างเมธอดเพื่อครอบงำเมธอดเดิมได้อย่างไม่มีข้อจำกัด โดยที่ถ้าสร้างขึ้นมาแล้ว เมื่อมีการเรียกเมธอดจะเรียกไปยังเมธอดใหม่แทน โค้ดภายใต้เมธอดเดิมจะไม่ถูกเรียก แต่หากยังต้องการเรียกโค้ดจากเมธอดเดิมอยู่ เราต้องเรียกใช้เองในรูปแบบ BaseClassName.methodname(self, arguments)
รูปแบบโครงสร้างคือ
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
ลำดับการค้นหาแอตทริบิวต์ในอินสแตนซ์ของ DerivedClassName ก็คือ ถ้าพบแอตทริบิวต์ใน DerivedClassName ก็จะใช้เลย แต่ถ้าไม่พบก็จะหาใน Base1 และคลาสฐานทั้งหมดของ Base1 ลึกลงไปจนสุด และถ้ายังไม่พบจึงมาเริ่มต้นค้นจาก Base2 ต่อไปเรื่อย ๆ จนหมด
(บางคนอาจคิดว่าน่าจะไล่ไป Base1 - Base2 - Base3 แล้วจึงย้อนมาหาฐานของ Base1 อีกที แต่การไล่แบบนั้นจะทำให้คุณต้องแยกแยะก่อน ว่าแอตทริบิวต์เจ้าปัญหานั้นกำหนดไว้ใน Base1 หรือคลาสฐานของ Base1 ก่อนที่จะตรวจสอบว่าชนกับชื่อใน Base2 หรือไม่ ซึ่งเท่ากับเป็นการตัดทอนสายตระกูลตามปกติของคลาสออกเป็นส่วน ๆ ในขณะที่การค้นหาแบบลงลึกทีละสายจะไม่มีความแตกต่างตรงนี้)
ใช้หลักแค่ว่านำหน้าชื่อตัวแปรด้วย 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']
อาจเติมแอตทริบิวต์ข้อมูลให้กับคลาสอินสแตนซ์ได้ทุกเมื่อ
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ดังนั้น เราสามารถสร้างตัวยกข้อผิดพลาดแบบซ้อนลึกลงไปเรื่อย ๆ
เขียนได้สองรูปแบบคือ
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)
ลองดูตัวอย่าง 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เป็นอีกตัวนึงที่ใช้สร้างตัวกระทำซ้ำ โดยใช้ประโยค 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 เอง
รูปแบบเหมือน ลิสต์คอมพรีเฮนชั่น (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.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)
เรามาดูว่าในไลบรารีมาตรฐานของไพธอน มีฟังก์ชันหรือเมธอดในมอดูล อะไรให้ใช้บ้าง
osos มีเมธอดให้ใช้เยอะมาก
>>> 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')glob >>> import glob
>>> glob.glob('*.py')
['primes.py', 'random.py', 'quote.py']
sysdemo.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']
syssys เหมือนเดิม เพราะในนี้มีฟังก์ชันของ 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() เป็นปกติ
re>>> 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' math>>> import math >>> math.cos(math.pi / 4.0) 0.70710678118654757 >>> math.log(1024, 2) 10.0
randommath
>>> 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
urllib2>>> 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 ESTsmtplib>>> 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()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
zlib gzip bz2 zipfile tarfilezlib เป็นตัวอย่าง
>>> 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
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 ใช้ดูเวลาโค๊ดเยอะ ๆ
มาถึงนี่ได้ก็เริ่มเก๋าแล้วครับ อันนี้ใช้กับงานใหญ่ ๆ ที่เราจะจัดการดูแลโค๊ดเราให้มีมาตรฐานที่ดี เผื่อว่าใครจะเอาไปใช้ หรือจะทำงานร่วมกับใคร ก็จะเข้ากันได้แบบไร้ปัญหา
doctestdef 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 testsunittestimport 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ไพธอนพยายามทำให้ตัวเองพร้อมใช้งานมากที่สุด จึงพยายามจัดแพกเกจและมอดูลมาให้ครบถ้วน เช่น
บางทีการ "พร้อมใช้" ก็อาจสู้มอดูลจากข้างนอกไม่ได้เหมือนกัน แต่โดยรวม ๆ แล้วถือว่าเจ๋งพอตัว
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)
reprrepr() ให้เป็นแบบที่เราต้องการ
>>> 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'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
structpack() และ 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
เธรด เป็นการกระจายงานแล้วทำขนานกันไป โปรแกรมยุคใหม่จำเป็นต้องมีอย่างยิ่ง งานที่เหมาะกับเธรด เช่น เธรดนึงรอผู้ใช้ป้อนข้อมูล อีกเธรดนึงก็ทำงานอื่นขนานกันไป พอผู้ใช้ป้อนเสร็จก็ประมวลผลเสร็จพอดี เป็นต้น
threadingimport 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งานบันทึกปูม เป็นงานน่าเบื่อหน่อย แต่ถ้างานเราใหญ่ขึ้นก็ต้องทำ
loggingsys.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
นอกจากนี้ยังสามารถแยกทำเป็นไฟล์ปรับตั้งสำหรับงานปูมโดยเฉพาะ โดยไม่ต้องเข้าไปยุ่งกับโค๊ดหลักเลย
ปกติไพธอนจัดการหน่วยความจำได้ดีอยู่แล้ว แต่มีบางงานที่ยกเว้น
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'เพราะลิสต์ใช้ง่าย เลยมีคนเขียนมอดูลเติมความสามารถให้ใช้ได้หลายหลายยิ่งขึ้น
array"H" แปลงแล้วใช้งานง่ายมาก
>>> from array import array
>>> a = array('H', [4000, 10, 700, 22222])
>>> sum(a)
26932
>>> a[1:3]
array('H', [10, 700])collectionsdeque() ที่ใช้งานเหมือนลิสต์ ถ้าเพิ่มหรือลดสมาชิกข้างหน้าหรือต่อท้ายแล้วจะเร็วกว่าลิสต์ แต่ถ้าแทรกหรือค้นข้อมูลจะช้ากว่า เราเลยเอามาใช้ในงานคิว
>>> 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]
decimalDecimal สำหรับเลขทศนิยมลอยที่เหมาะกับงานบัญชี และงานที่ต้องการความถูกต้องของทศนิยมสูง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")Misc/ ของแหล่งที่เราดาวน์โหลดไพธอนยังเรียบเรียงไม่หมด ยังขาดอีก 4 เรื่อง ที่ยังไง ๆ ก็ไม่แปลเด็ดขาด เพราะท่านที่อ่านจนมาถึงตรงนี้ได้ก็คงเก่งภาษาอังกฤษกว่าผมแล้ว :D
คือ
หวังว่าคงจะมีท่านผู้ใจบุญมาปรับปรุงให้บทความนี้สมบูรณ์ขึ้น และได้อำนวยประโยชน์สุขแก่ผู้อ่านทุกท่าน เทอญ.
ทำห้องแสดงภาพผ่านเว็บด้วยไพธอนอย่างง่าย
ความต้องการคือ
ใช้มอดูล wsgi, Image และเรียกใช้โปรแกรมภายนอกคือ imagemagick
เริ่มด้วยติดตั้งแพกเกจ และเปิดใช้มอดูล wsgi
# aptitude install apache2 libapache2-mod-wsgi python-imaging imagemagick # a2enmod wsgi
สมมุติว่ารากของ apache2 อยู่ที่ /var/www
เราจะให้ url ของห้องแสดงภาพเป็น http://www.example.com/pythongal
และเนื่องจากเราทำแบบง่าย จึงตัดเรื่องบัญชีผู้ใช้ออก ลักไก่ให้หน้าของ admin เป็น http://www.example.com/.admin-pythongal ซึ่งกำหนดในโปรแกรม
ติดตั้งโปรแกรมโดยแปลงตัวเป็น www-data ก่อน
# su www-data
ไปที่ไดเรคทอรี่ของ apache2 ดาวน์โหลดโปรแกรม ติดตั้งและปรับข้ออนุญาตให้เรียบร้อย
$ cd $ wget http://www.thaitux.info/files/py/pythongal-511002.tar.gz $ tar xfz pythongal-511002.tar.gz $ chmod -R 755 pythongal
เสร็จแล้ว
ถ้าเราเอาอะไรใส่เข้าในไดเรกทอรี่ /var/www/pythongal เขาจะจัดการลดขนาดภาพและสร้างภาพเล็กให้เอง โดยเก็บไฟล์เหมือนระบบไฟล์ปกติ
ปรับแต่งหน้าตาของ html จากไฟล์ template.html
และปรับ css ที่ไฟล์ style.css
ปรับคุณสมบัติของโปรแกรมจากตัวแปรที่หัวไฟล์
ลองทดสอบหน้าตัวอย่างได้ที่ www.thaitux.info/pythongal
และหน้า admin สำหรับหมุนภาพที่ www.thaitux.info/.admin-pythongal
มีงานที่จะต้องทำไฟล์เป็น pdf เพื่อส่งโรงพิมพ์ งานนี้ทำจาก Word ในวินโดวส์ พิมพ์ลงไฟล์โดยใช้ไดรเวอร์เครื่องพิมพ์ Image Setter แล้วจึงแปลงเป็น pdf ด้วยลินุกซ์ ด้วยคำสั่ง ps2pdf12 ซึ่งเลือกรุ่น 1.2 เพราะต้องการความเข้ากันได้
แต่เนื่องจากขนาดกระดาษของงานเป็นขนาด A5 จึงต้องเลือกพิมพ์เป็น A4 แทน
ปัญหาคือตัวโปรแกรม ps2pdf ซึ่งไปเรียกใช้ ghostscript (gs) อีกทีนึง ไม่สามารถ crop ขนาดจาก A4 เป็น A5 ได้ (จริง ๆ แล้วอาจทำได้ แต่ค้นคำสั่งไม่พบ และโรงพิมพ์ต้องการงานขนาด A5 แบบมีขอบขาวเว้นไว้ด้านละ 3 มม. ซึ่งคงจะใช้คำสั่ง gs ยาก)
ค้นไปค้นมา พบมอดูลไพธอนที่จะทำงานนี้ได้ คือมอดูล pyPdf
เริ่มเลยแล้วกัน
ติดตั้งมอดูล pyPdf
$ sudo aptitude install python-pypdf
เขียนสคริปต์ ตั้งชื่อว่า croppdf.py
$ vi croppdf.py
#!/usr/bin/env python
#prerequisites: aptitude install python-pypdf
import sys
import pyPdf
def usage(progname):
print """
usage: %s "lowerLeft-x lowerLeft-y upperRight-x upperRight-y" infile.pdf outfile.pdf
""" % progname
sys.exit(1)
try:
argl = [ int(i) for i in sys.argv[1].split(" ") if i ]
infile = sys.argv[2]
outfile = sys.argv[3]
inpdf = pyPdf.PdfFileReader(file(infile,"rb"))
outpdf = pyPdf.PdfFileWriter()
for i in range(inpdf.numPages):
page = inpdf.getPage(i)
page.mediaBox.upperRight = tuple(argl[2:])
page.mediaBox.lowerLeft = tuple(argl[:2])
outpdf.addPage(page)
outstream = file(outfile, "wb")
outpdf.write(outstream)
outstream.close()
except:
usage(sys.argv[0])
$ chmod 755 croppdf.py
(พอดีเป็นงานด่วน เลยเขียนแบบด่วนจริง ๆ)
ขั้นตอนการแปลงคือ
1. แปลงจาก ps เป็น pdf ด้วยคำสั่ง ps2pdf12
$ ps2pdf12 INFILE.ps TEMPFILE.pdf
2. crop เป็นขนาด A5 แบบมีขอบขาวข้างละ 3 มม. (ประมาณ 9 px)
$ ./croppdf.py "75 238 523 850" TEMPFILE.pdf OUTFILE.pdf
ตัวเลข 4 ตัวคือค่าเป็นปอยต์ (pt) ของ x-มุมล่างซ้าย y-มุมล่างซ้าย และ x-มุมบนขวา y-มุมบนขวา ตามลำดับ หาได้โดยการ
แปลงจาก มม. โดยคูณด้วย 2.8378
หรือแปลงจากนิ้ว โดยคูณด้วย 72
สามารถดูขนาดเอกสารเป็นปอยต์ได้ด้วยคำสั่ง pdfinfo FILENAME.pdf
แถมอีกตัวอย่างนึง
ทำขอบขาวรอบ pdf ขนาด A4 (595x842 ปอยต์) โดยเว้นระยะ 5 มม. (ประมาณ 14 ปอยต์) โดยรอบ
$ ./croppdf.py "-14 -14 609 856" TEMPFILE.pdf OUTFILE.pdf
เสร็จแล้วครับ