ใช้ไพธอนทำ...
ลิงก์รอศึกษา
เอามาจาก 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
รองรับทศนิยมลอยด้วย โดยมีหลักว่า ถ้าต้นทางเป็นจำนวนเต็ม ผลจะเป็นจำนวนเต็มด้วย ถ้าต้นทางเป็นทศนิยมลอย ผลจะเป็นทศนิยมลอยด้วย (ถ้าผสมกันก็จะเป็นทศนิยมลอยเช่นกัน)
>>> 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>'
ในหมวดโต้ตอบนี้ ช่องว่างระหว่างสตริงก์ถูกประมวลผลเป็นการเชื่อมสคริงก์ แต่ใช้ได้กับสคริงก์อย่างเดียว ฟังกืชั่นไม่เกี่ยว
>>> '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
คำสั่งนี้จะตีความว่า นิพจน์หลังคำสั่ง 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.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, ... 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]"
แต่เขียนได้กระชับและเข้าใจง่ายกว่ามีหลักในการกำหนดค่าอาร์กิวเมนต์คือ
เป็นการกำหนดค่าปริยายให้กับอาร์กิวเมนต์ มีรูปแบบว่าอาร์กิวเมนต์ที่จะกำหนดค่าปริยายให้ จะต้องอยู่ทางขวาเสมอ ส่วนตัวที่ไม่กำหนด จะต้องอยู่ทางซ้ายเสมอ
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
ชุดต่อ ๆ ไป จนหมดข้อมูล
>>> 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
เป็นโครงสร้างเฉพาะตัวของไพธอนที่ยืมมาจากภาษา 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
อาจเป็นข้อมูลชนิดสคริง ตัวเลข หรือทูเปิลที่ไม่ได้บรรจุ 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}
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
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
>>> 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.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
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 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
คือตำแหน่งอ้างอิง
>>> 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
เราอาจสร้างกับดักเองโดยสร้างเป็นคลาส ซึ่งเวลานำมาใช้ต้องผันมาจากคลาสที่สร้างไว้อีกที
>>> 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 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__
__buildin__
อธิบายตามภาษาชาวบ้าน เนม(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.counter
x.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>
ลำดับการทำงานก็คือ ถ้าพบคลาส Base1
ตรง ๆ ก็จะสืบทอดจาก Base1
เลย แต่ถ้าไม่พบก็จะควานหาคลาสฐานของ Base1
ลึกลงไปจนสุด และถ้ายังไม่พบจึงมาเริ่มต้นค้นจาก Base2
ต่อไปเรื่อย ๆ จนหมด
(ลำดับการทำงานแบบนี้จะขัดธรรมชาติสักหน่อย คือจริง ๆ แล้วน่าจะไล่ไป Base1 - Base2 - Base3 แล้วจึงย้อนมาหาฐานของ Base1 อีกที ซึ่งดูจะมีประโยชน์กว่า ด้วยเหตุผลดังกล่าวนี้เอง จึงควรใช้ด้วยความระมัดระวัง เพราะมีโอกาสผิดพลาดมาก)
ใช้หลักแค่ว่านำหน้าชื่อตัวแปรต้วย 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)
เรามาดูว่าในไลบรารีมาตรฐานของไพธอน มีฟังก์ชั่นหรือเมธอดในมอดูล อะไรให้ใช้บ้าง
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')
glob
>>> import glob >>> glob.glob('*.py') ['primes.py', 'random.py', 'quote.py']
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']
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()
เป็นปกติ
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
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
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 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()
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
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
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 ใช้ดูเวลาโค๊ดเยอะ ๆ
มาถึงนี่ได้ก็เริ่มเก๋าแล้วครับ อันนี้ใช้กับงานใหญ่ ๆ ที่เราจะจัดการดูแลโค๊ดเราให้มีมาตรฐานที่ดี เผื่อว่าใครจะเอาไปใช้ หรือจะทำงานร่วมกับใคร ก็จะเข้ากันได้แบบไร้ปัญหา
doctest
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
ไพธอนพยายามทำให้ตัวเองพร้อมใช้งานมากที่สุด จึงพยายามจัดแพกเกจและมอดูลมาให้ครบถ้วน เช่น
บางทีการ "พร้อมใช้" ก็อาจสู้มอดูลจากข้างนอกไม่ได้เหมือนกัน แต่โดยรวม ๆ แล้วถือว่าเจ๋งพอตัว
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'
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
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
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
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
นอกจากนี้ยังสามารถแยกทำเป็นไฟล์ปรับตั้งสำหรับงานปูมโดยเฉพาะ โดยไม่ต้องเข้าไปยุ่งกับโค๊ดหลักเลย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])
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]
decimal
Decimal
สำหรับเลขทศนิยมลอยที่เหมาะกับงานบัญชี และงานที่ต้องการความถูกต้องของทศนิยมสูง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
คือ
หวังว่าคงจะมีท่านผู้ใจบุญมาปรับปรุงให้บทความนี้สมบูรณ์ขึ้น และได้อำนวยประโยชน์สุขแก่ผู้อ่านทุกท่าน เทอญ.
Adodb for Python เป็น API สำหรับให้ไพธอนติดต่อกับฐานข้อมูลใด ๆ
ข้อดีคือสามารถเปลี่ยนฐานข้อมูลโดยไม่ต้องเปลี่ยนคำสั่งการติดต่อ
ข้อเสียคือมีชั้นการรันเพิ่ม ทำให้เข้าถึงข้อมูลช้าลง
มีเอกสารภาษาไทยอยู่ที่ Exzilla.net - ADOdb MySQL Tutorial (Thai Translation)
ในที่นี้จะเขียนแค่ postgresql
# aptitude install python python-adodb postgresql-8.1 psycopg
import adodb; conn = adodb.NewADOConnection('postgres') conn.Connect('server','user','pwd','db') cursor = conn.Execute('select * from table') while not cursor.EOF: print cursor.fields cursor.MoveNext() # cursor.Close() conn.Close()
ตัวอย่างต่อไปแปลงมาจาก ADOdb Library for PHP ผิด ตก ยก เว้น
import adodb conn = adodb.ADONewConnection('access') cursor = conn.Connect('northwind') rs = conn.Execute('SELECT * FROM products') if not rs: print conn.ErrorMsg() else: while not rs.EOF: print "%s %s <br />" % (rs.fields[0], rs.fields[1]) rs.MoveNext() # # rs.Close() # optional conn.Close() # optional
import adodb conn = adodb.ADONewConnection('access') cur = conn.Connect('northwind') rs = conn.Execute('SELECT CustomerID, OrderDate FROM Orders') if not rs: print conn.ErrorMsg else: while not rs.EOF: field = rs.FetchField(1) metatype = rs.MetaType(field.type) if metatype=='D' or metatype=='T': print "%s %s <br />" % (rs.fields[0], rs.fields[1]) else: print "%s <br />" % (rs.fields[0]) # rs.MoveNext() # # rs.Close() # optional conn.Close() # optional
import adodb import datetime conn = adodb.ADONewConnection('access') cur = conn.Connect('northwind') shipto = conn.qstr("John's Old Shoppe") sql = "INSERT INTO orders (CustomerID,EmployeeID,OrderDate,ShipName)" \ + " VALUES ('ANNATR', 2, %s, %s" \ % (conn.DBDate(datetime.datetime.now()), shipto) if not conn.Execute(sql): print "Error insertion: %s <br />" % (conn.ErrorMsg()) #
import adodb import datetime conn = adodb.ADONewConnection('access') cur = conn.Connect('northwind') shipto = conn.qstr("John's Old Shoppe") sql = "INSERT INTO orders (CustomerID, EmployeeID, OrderDate, ShipName)"\ + " VALUES ('ANATR', 2, %s, %s)" \ % (conn.FormatDate(datetime.datetime.now()), shipto) conn.debug = True if not conn.Execute(sql): print "Error inserting" #
import sys try: curs = conn.Execute('select * from badtable') # table does not exist except: print sys.exc_info()[1] # retrieve the error message returned by database
วิธียืมจาก php - ต้องปิด useExceptions ก่อน
conn.useExceptions = False curs = conn.Execute('select * from badtable') # table does not exist if curs == None: print conn.ErrorMsg()
ต้องการพอร์ตข้อมูลที่เป็น dbf ของ Visual Foxpro มาลงใน postgres โดยใช้โมดูล adodb
ค้นกูเกิลเจอโมดูลในการอ่าน Visual Foxpro dbf ที่
เว็บของคุณ Yusdi Santoso หมวด Software Repository
ใช้งานได้ดีทีเดียว เพราะอ่าน Memo Field ของ Visual Foxpro ออกหมด
แต่ดันพลาดตอนไม่ได้ตรวจสอบข้อมูลของ Memo Field เพราะเขาเอาไบต์ที่เป็น Null มาด้วย ('\x00')
INSERT เข้าไปเท่าไหร่ก็แสดงข้อผิดพลาดอยู่ตลอด เสียเวลาทั้งวันในการดีบัก เพราะดันไปเพ่งเล็งที่ adodb กับ pyscopg
ข้อผิดพลาดที่แสดงคือ
psycopg.ProgrammingError: ERROR: unterminated quoted string at or near "'" at character XXX
ลองไปลองมาถึงได้ทราบว่าเป็นที่ Null character
แก้ด้วยการเติมฟังก์ชั่นการกรองข้อมูลคือ
def dncode(s): return s.strip().strip('\x00').decode('tis620').encode('utf8')
ให้ถอดรหัสอักขระด้วย tis620 ด้วย เพราะข้อมูลเก่าเป็นรหัสภาษาไทยแบบวินโดวส์
ผ่านเรียบร้อย โดยเสีย(ค่าฉลาดน้อย)ไปหนึ่งวัน
จากการทดลองนำเข้าข้อมูล dbf ในครั้งก่อน พบข้อผิดพลาดในการแปลงอีกอันนึง คือ สระอำ
หลังจากแปลงมาแล้ว พบว่าส่วนใหญ่จะแปลงได้ถูกต้อง ยกเว้นบางคำที่เขาแปลงออกมาเป็น 2 อักขระ
คือประกอบด้วย นิคหิต ( _ํ ) กับสระอา ( า ) แทนที่จะเป็นสระอำอักขระเดียว
ซึ่งยังไม่ทราบว่าเกิดจากสาเหตุอะไร (อาจเป็นข้อมูลต้นทางไม่ดีก็เป็นได้)
เราจึงควรตรวจสอบข้อมูล ในการนำเข้า ด้วยการกรองสระอำอีกชั้นนึงดังนี้
... def dncode(s): return s.strip().strip('\x00').decode('tis620').encode('utf8').replace('\xe0\xb9\x8d\xe0\xb8\xb2','\xe0\xb8\xb3') ...
update
ถึงเวลาใช้งานจริงก็ยังมีข้อมูลที่ไม่อยู่ในช่วงของ Ascii Codepage-874 หลุดออกมา ทำให้การถอดรหัส (decode('tis620')
) ยังรายงานข้อผิดพลาด
เราต้องถอดเอาอักขระขยะออกให้หมด
ฟังก์ชั่น dncode สุดท้ายจึงเป็นดังนี้
... def dncode(s): return s.strip().strip('\x00').strip('\xa0').strip('\xdb').strip('\xdc').strip('\xdd').strip('\xde').strip('\xfc').strip('\xfd').strip('\xfe').strip('\xff').decode('tis620').encode('utf8').replace('\xe0\xb9\x8d\xe0\xb8\xb2','\xe0\xb8\xb3') ...
ตอนทำงาน ไพธอนจะทำงานจากซ้ายไปขวา จึงต้อง strip อักขระขยะออกก่อน แล้วจึงตามด้วยการ decode/encode เป็นลำดับสุดท้าย
ลองทดสอบวัดประสิทธิภาพแบบคร่าว ๆ เพื่อหาวิธีการเขียนโค๊ด
เริ่มต้นด้วยการสร้างคลาสเพื่อจับเวลาก่อน
class TimeIt: import time def __init__(self): self.p_start=time.time() def use(self): t_now=time.time() t_use=t_now-self.p_start self.p_start=t_now return t_use
ตามด้วยการสั่งจากเชลล์ของไพธอน
import adodb conn = adodb.NewADOConnection("postgres") cur = conn.Connect(host, user, password, db) sql = """CREATE TABLE test (wordid SERIAL, word VARCHAR(255), PRIMARY KEY (wordid))""" cur = conn.Execute(sql)
แบบที่ ๑
def f1(): ttt = TimeIt() for i in range(1000): sql = """INSERT INTO test (word) VALUES ('abc%s')""" % (i,) cur = conn.Execute(sql) sql = """SELECT wordid FROM test WHERE word=%s""" % (conn.qstr(i),) cur = conn.Execute(sql) print ttt.use() sql = """DELETE FROM test""" cur = conn.Execute(sql)
ได้ผลเป็น
>>> f1() 9.27473306656 >>> f1() 9.16922688484 >>> f1() 9.21483206749 >>> f1() 9.20028710365 >>> f1() 10.2529239655 >>> f1() 9.16449689865
แบบที่ ๒
def f2(): ttt = TimeIt() for i in range(1000): sql = """INSERT INTO test (word) VALUES ('abc%s'); \ SELECT wordid FROM test WHERE word=%s""" % (i, conn.qstr(i),) cur = conn.Execute(sql) print ttt.use() sql = """DELETE FROM test""" cur = conn.Execute(sql)
ได้ผลเป็น
>>> f2() 9.11072301865 >>> f2() 9.20462703705 >>> f2() 9.24071407318 >>> f2() 9.25392103195 >>> f2() 9.02831697464 >>> f2() 9.07160282135 >>>
def f3(): def get_id(word): sql = """SELECT wordid FROM test WHERE word=%s""" % (conn.qstr(word),) cur = conn.Execute(sql) return cur.fields[0] def add_word(word): sql = """INSERT INTO test (word) VALUES (%s)""" % (conn.qstr(word),) cur = conn.Execute(sql) return get_id(word) ttt = TimeIt() for i in range(1000): add_word("abc%s" % (i,)) print ttt.use() sql = """DELETE FROM test""" cur = conn.Execute(sql)
ได้ผลเป็น
>>> f3() 9.02051997185 >>> f3() 9.06975603104 >>> f3() 9.30845808983 >>> f3() 9.06503009796 >>> f3() 9.89376401901 >>> f3() 9.28391385078 >>>
def f4(): def add_word(word): sql = """INSERT INTO test (word) VALUES (%s)""" % (conn.qstr(word),) cur = conn.Execute(sql) return cur ttt = TimeIt() for i in range(1000): add_word("abc%s" % (i,)) print ttt.use() sql = """DELETE FROM test""" cur = conn.Execute(sql)
ได้ผลเป็น
>>> f4() 8.93194293976 >>> f4() 8.94240808487 >>> f4() 9.14316105843 >>> f4() 8.93977403641 >>> f4() 9.05243611336 >>> f4() 9.06761908531 >>>
สรุป
ความเร็วแทบไม่ต่างกันเลย ฉะนั้น เขียนให้อ่านง่าย ปรับปรุงง่าย ดีที่สุด
ค้างทดสอบ (รอ postgres-8.2)
def fx(): ttt = TimeIt() for i in range(1000): sql = """INSERT INTO test (word) VALUES ('abc%s') RETURNING \ (SELECT wordid FROM test WHERE word=%s)""" % (i, conn.qstr(i),) cur = conn.Execute(sql) print ttt.use() sql = """DELETE FROM test""" cur = conn.Execute(sql)
RETURNING
มีใช้ใน Oracle กับ Postgres-8.2.3
Executes the equivalent following sql statement:
UPDATE table SET field = blob WHERE whereclause
The blobtype field should be set to either 'BLOB' or 'CLOB'.
Any special encoding required for the blob is applied
transparently.
Loads the binary file filepath into blob. Then
calls UpdateBlob( ).
Note that some databases return more information in each row.
Returns field information from a SELECT statement. The fieldoffset
is zero-based, so to retrieve info on the 1st field use FetchField(0).
A tuple is returned, consisting of:
(name, type_code,display_size, internal_size, precision,
scale,null_ok).
ก่อนอื่น จะสร้างฐานข้อมูลเพื่อใช้เป็นตัวอย่างก่อน
โดยจะสร้างตารางเป็นสมุดโทรศัพท์ ใส่ข้อมูลเบื้องต้นไป 5 แถว
(อย่าเชื่อ syntax มากนะครับ เริ่มหัดใหม่เหมือนกัน)
import adodb driver = 'postgres' host = 'host' user = 'user' password = 'password' db = 'db' conn = adodb.NewADOConnection(driver) cur = conn.Connect(host,user,password,db) sql = """\ CREATE TABLE phone ( pid INT, name VARCHAR(50), phone VARCHAR(50), category VARCHAR(50), update DATE, rem TEXT, img BYTEA)""" cur = conn.Execute(sql) from datetime import datetime today = conn.DBDate(datetime.date(datetime.today())) sql = "INSERT INTO phone \ (pid, name, phone, category, update, rem, img) VALUES " cur=conn.Execute(sql+"(%s, '%s', '%s', '%s', %s, '%s', '%s')" \ % (1, 'name1', '0-0000-0001', 'cat1', today, 'rem1', '')) cur=conn.Execute(sql+"(%s, '%s', '%s', '%s', %s, '%s', '%s')" \ % (2, 'name2', '0-0000-0002', 'cat2', today, 'rem2', '')) cur=conn.Execute(sql+"(%s, '%s', '%s', '%s', %s, '%s', '%s')" \ % (3, 'name3', '0-0000-0003', 'cat3', today, 'rem3', '')) cur=conn.Execute(sql+"(%s, '%s', '%s', '%s', %s, '%s', '%s')" \ % (4, 'name4', '0-0000-0004', 'cat4', today, 'rem4', '')) cur=conn.Execute(sql+"(%s, '%s', '%s', '%s', %s, '%s', '%s')" \ % (5, 'name5', '0-0000-0005', 'cat5', today, 'rem5', ''))
เพื่อไม่ให้เยิ่นเย้อ จะใช้ตัวแปร conn
ซึ่งจะหมายถึง
... conn = adodb.NewADOConnection(driver) ...
เป็นปกติของหน้านี้
>>> cur=conn.SelectLimit('SELECT * FROM phone', 2) (SAME AS) >>> cur=conn.Execute('SELECT * FROM phone LIMIT 2') >>> while not cur.EOF: ... print cur.fields ... cur.MoveNext() ... (1, 'name1', '0-0000-0001', 'cat1', <DateTime object for '2007-01-25 00:00:00.00' at b764b4f0>, 'rem1', '') False (2, 'name2', '0-0000-0002', 'cat2', <DateTime object for '2007-01-25 00:00:00.00' at b764b448>, 'rem2', '') True
>>> try: ... cur=conn.Execute('SELECT * FROM notable') ... except: ... print conn.ErrorMsg() ... ERROR: relation "notable" does not exist
SELECT * FROM notable
>>> conn=adodb.NewADOConnection('postgres') >>> conn.IsConnected() False >>> conn.Connect('host','user','password','database') True >>> conn.IsConnected() True
>>> print conn.qstr(2) '2'
qstr( )
>>> print conn.GetAll('SELECT * FROM phone') [(1, 'name1', '0-0000-0001', 'cat1', <DateTime object for '2007-01-25 00:00:00.00' at b764b4f0>, 'rem1', ''), (2, 'name2', '0-0000-0002', 'cat2', <DateTime object for '2007-01-25 00:00:00.00' at b764b448>, 'rem2', ''), (3, 'name3', '0-0000-0003', 'cat3', <DateTime object for '2007-01-25 00:00:00.00' at b764b410>, 'rem3', ''), (4, 'name4', '0-0000-0004', 'cat4', <DateTime object for '2007-01-25 00:00:00.00' at b764b560>, 'rem4', ''), (5, 'name5', '0-0000-0005', 'cat5', <DateTime object for '2007-01-25 00:00:00.00' at b764b598>, 'rem5', '')]
>>> print conn.GetArray('SELECT * FROM phone') [(1, 'name1', '0-0000-0001', 'cat1', <DateTime object for '2007-01-25 00:00:00.00' at b764b4f0>, 'rem1', ''), (2, 'name2', '0-0000-0002', 'cat2', <DateTime object for '2007-01-25 00:00:00.00' at b764b448>, 'rem2', ''), (3, 'name3', '0-0000-0003', 'cat3', <DateTime object for '2007-01-25 00:00:00.00' at b764b410>, 'rem3', ''), (4, 'name4', '0-0000-0004', 'cat4', <DateTime object for '2007-01-25 00:00:00.00' at b764b560>, 'rem4', ''), (5, 'name5', '0-0000-0005', 'cat5', <DateTime object for '2007-01-25 00:00:00.00' at b764b598>, 'rem5', '')]
>>> print conn.GetRow('SELECT * FROM phone') (1, 'name1', '0-0000-0001', 'cat1', <DateTime object for '2007-01-25 00:00:00.00' at b764b4f0>, 'rem1', '')
>>> print conn.GetOne('SELECT * FROM phone') 1
>>> print conn.GetAssoc('SELECT * FROM phone') {1: ('name1', '0-0000-0001', 'cat1', <DateTime object for '2007-01-25 00:00:00.00' at b764b2f8>, 'rem1', ''), 2: ('name2', '0-0000-0002', 'cat2', <DateTime object for '2007-01-25 00:00:00.00' at b764b528>, 'rem2', ''), 3: ('name3', '0-0000-0003', 'cat3', <DateTime object for '2007-01-25 00:00:00.00' at b764b330>, 'rem3', ''), 4: ('name4', '0-0000-0004', 'cat4', <DateTime object for '2007-01-25 00:00:00.00' at b764b368>, 'rem4', ''), 5: ('name5', '0-0000-0005', 'cat5', <DateTime object for '2007-01-25 00:00:00.00' at b764b4b8>, 'rem5', '')} >>> dict = conn.GetAssoc('SELECT * FROM phone') >>> for i in dict: ... print i, dict[i] ... 1 ('name1', '0-0000-0001', 'cat1', <DateTime object for '2007-01-25 00:00:00.00' at b764b4b8>, 'rem1', '') 2 ('name2', '0-0000-0002', 'cat2', <DateTime object for '2007-01-25 00:00:00.00' at b764b368>, 'rem2', '') 3 ('name3', '0-0000-0003', 'cat3', <DateTime object for '2007-01-25 00:00:00.00' at b764b330>, 'rem3', '') 4 ('name4', '0-0000-0004', 'cat4', <DateTime object for '2007-01-25 00:00:00.00' at b764b528>, 'rem4', '') 5 ('name5', '0-0000-0005', 'cat5', <DateTime object for '2007-01-25 00:00:00.00' at b764b2f8>, 'rem5', '')
GetAssoc()
.>>> print conn.GetCol('SELECT * FROM phone') [1, 2, 3, 4, 5]
>>> print conn.MetaType('B') B >>> print conn.MetaType('BINARY') B >>> print conn.MetaType('date') D >>> print conn.MetaType('varchar') C >>> print conn.MetaType('IMAGE') B
Note that some databases return more information in each row.
>>> print conn.MetaColumns('phone') [('pid', 'int4', 4, -1, 0, 0, 1), ('name', 'varchar', -1, 54, 0, 0, 2), ('phone', 'varchar', -1, 54, 0, 0, 3), ('category', 'varchar', -1, 54, 0, 0, 4), ('update', 'date', 4, -1, 0, 0, 5), ('rem', 'text', -1, -1, 0, 0, 6), ('img', 'bytea', -1, -1, 0, 0, 7)] >>> for line in conn.MetaColumns('phone'): ... print line ... ('pid', 'int4', 4, -1, 0, 0, 1) ('name', 'varchar', -1, 54, 0, 0, 2) ('phone', 'varchar', -1, 54, 0, 0, 3) ('category', 'varchar', -1, 54, 0, 0, 4) ('update', 'date', 4, -1, 0, 0, 5) ('rem', 'text', -1, -1, 0, 0, 6) ('img', 'bytea', -1, -1, 0, 0, 7)
MetaColumns format:
field name, field type, field length, max. field length, is_null, is_serial, field order
>>> from datetime import datetime >>> today = conn.DBDate(datetime.date(datetime.today())) >>> print today '2007-01-25'
>>> now=conn.DBTimeStamp(datetime.datetime.now()) >>> print now '2007-01-25 00:00:00'
>>> print conn.Date('2007-01-25') 2007-01-25 00:00:00
>>> print conn.TimeStamp('2007-01-25 00:00:00') 2007-01-25 00:00:00
>>> dir(conn.Module()) ['BINARY', 'BOOLEAN', 'Binary', 'DATE', 'DATETIME', \ 'DataError', 'DatabaseError', 'Date', 'DateFromMx', \ 'DateFromTicks', 'Error', 'FLOAT', 'INTEGER', 'INTERVAL', \ 'IntegrityError', 'InterfaceError', 'InternalError', \ 'LONGINTEGER', 'NUMBER', 'NotSupportedError', \ 'OperationalError', 'ProgrammingError', 'QuotedString', \ 'ROWID', 'STRING', 'TIME', 'Time', 'TimeFromMx', \ 'TimeFromTicks', 'Timestamp', 'TimestampFromMx', \ 'TimestampFromTicks', 'Warning', '__doc__', '__file__', \ '__name__', '__version__', 'apilevel', 'connect', 'new_type', \ 'paramstyle', 'register_type', 'threadsafety', 'types']
>>> dir(conn.Conn()) ['autocommit', 'close', 'commit', 'cursor', 'cursors', \ 'maxconn', 'minconn', 'rollback', 'serialize', \ 'set_isolation_level']
>>> print conn.DriverInfo() Driver = postgres API Level = 2.0 Param Style = pyformat Thread Safety = 2 (0=none, 1=module, 2=connections, 3=cursors) -------------- None
เพื่อไม่ให้เยิ่นเย้อ จะใช้ตัวแปร cur
ซึ่งจะหมายถึง
... conn = adodb.NewADOConnection(driver) cur = conn.Execute('SELECT * FROM phone') ...
เป็นปกติของส่วนนี้
>>> print cur.RecordCount() 5
RecordCount( )
.>>> cur.MoveNext() False >>> cur.EOF False
>>> a=cur.FetchRow() >>> a (2, 'name2', '0-0000-0002', 'cat2', , 'rem2', '')
>>> print cur.GetRowAssoc() {'CATEGORY': 'cat3', 'NAME': 'name3', 'IMG': '', 'PID': 3, \ 'UPDATE': , 'PHONE': '0-0000-0003', 'REM': 'rem3'}
>>> print cur.FetchField(0) ('pid', 23, 1, 4, None, None, None) >>> cur.FetchField(1) ('name', 1043, 5, 50, None, None, None)
>>> cur.Close()
>>> dir(cur.Cursor()) ['arraysize', 'autocommit', 'callproc', 'close', 'commit', \ 'copy_from', 'copy_to', 'description', 'dictfetchall', \ 'dictfetchmany', 'dictfetchone', 'execute', 'executemany', \ 'fetchall', 'fetchmany', 'fetchone', 'fileno', 'lastoid', \ 'lastrowid', 'nextset', 'notifies', 'rollback', 'rowcount', \ 'scroll', 'setinputsizes', 'setoutputsize', 'statusmessage']
>>> print cur.fields (3, 'name3', '0-0000-0003', 'cat3', , 'rem3', '')
>>> print cur.EOF False
เกร็ด
>>> conn=adodb.NewADOConnection('postgres') >>> conn.Connect('host','user','password','') Traceback (most recent call last): File "<stdin>", line 1, in ? File "/usr/lib/python2.4/site-packages/adodb/adodb.py", line 199, in Connect self._connect(host,user,password,database) File "/usr/lib/python2.4/site-packages/adodb/adodb_postgres.py", line 46, in _connect self._conn = psycopg.connect(dsn) psycopg.OperationalError: FATAL: database "user" does not exist
แต่เราสามารถทำโดยอ้อมได้ มีข้อแม้ว่าต้องมี database อยู่ก่อน ก็คือเราติดต่อเข้าไปหา database ที่มีอยู่ ในที่นี้เราใช้ postgres ตัว database ที่มีอยู่แน่นอนก็คือ database ชื่อ postgres แล้วจึงค่อยสร้างใหม่จากคำสั่ง Execute SQL
>>> conn=adodb.NewADOConnection('postgres') >>> conn.Connect('host','user','password','postgres') True >>> cur=conn.Execute('CREATE DATABASE %s OWNER %s' % ('new_db','user')) >>> conn.Connect('host','user','password','new_db') True
######################################################################## # Vers 2.01 5 May 2006, (c)2004-2006 John Lim (jlim#natsoft.com.my) All Rights Reserved # Released under a BSD-style license. See LICENSE.txt. # Download: http://adodb.sourceforge.net/#pydownload ######################################################################## __author__ = "John Lim (jlim#natsoft.com)" __credits__ = "(c) 2004-2006 John Lim" import exceptions,sys,re from datetime import datetime try: True, False except NameError: # Maintain compatibility with Python 2.2 True, False = 1, 0 MapTypes = { 'VARCHAR' : 'C', 'VARCHAR2' : 'C', 'CHAR' : 'C', 'C' : 'C', 'STRING' : 'C', 'NCHAR' : 'C', 'NVARCHAR' : 'C', 'VARYING' : 'C', 'BPCHAR' : 'C', 'CHARACTER' : 'C', 'INTERVAL' : 'C', # Postgres ## 'LONGCHAR' : 'X', 'TEXT' : 'X', 'NTEXT' : 'X', 'M' : 'X', 'X' : 'X', 'CLOB' : 'X', 'NCLOB' : 'X', 'LVARCHAR' : 'X', ## 'BLOB' : 'B', 'IMAGE' : 'B', 'BINARY' : 'B', 'VARBINARY' : 'B', 'LONGBINARY' : 'B', 'B' : 'B', ## 'YEAR' : 'D', # mysql 'DATE' : 'D', 'D' : 'D', ## 'TIME' : 'T', 'TIMESTAMP' : 'T', 'DATETIME' : 'T', 'TIMESTAMPTZ' : 'T', 'T' : 'T', ## 'BOOL' : 'L', 'BOOLEAN' : 'L', 'BIT' : 'L', 'L' : 'L', ## 'COUNTER' : 'R', 'R' : 'R', 'SERIAL' : 'R', # ifx 'INT IDENTITY' : 'R', ## 'INT' : 'I', 'INTEGER' : 'I', 'INTEGER UNSIGNED' : 'I', 'SHORT' : 'I', 'TINYINT' : 'I', 'SMALLINT' : 'I', 'I' : 'I', ## 'LONG' : 'N', # interbase is numeric, oci8 is blob 'BIGINT' : 'N', # this is bigger than PHP 32-bit integers 'DECIMAL' : 'N', 'DEC' : 'N', 'REAL' : 'N', 'DOUBLE' : 'N', 'DOUBLE PRECISION' : 'N', 'SMALLFLOAT' : 'N', 'FLOAT' : 'N', 'NUMBER' : 'N', 'NUM' : 'N', 'NUMERIC' : 'N', 'MONEY' : 'N', ## informix 9.2 'SQLINT' : 'I', 'SQLSERIAL' : 'I', 'SQLSMINT' : 'I', 'SQLSMFLOAT' : 'N', 'SQLFLOAT' : 'N', 'SQLMONEY' : 'N', 'SQLDECIMAL' : 'N', 'SQLDATE' : 'D', 'SQLVCHAR' : 'C', 'SQLCHAR' : 'C', 'SQLDTIME' : 'T', 'SQLINTERVAL' : 'N', 'SQLBYTES' : 'B', 'SQLTEXT' : 'X'} class adodb_iter: def __iter__(self): def next(self): def NewADOConnection(modulename): def ADONewConnection(modulename): class ADOConnection: databaseType = None dataProvider = 'native' host = None user = None password = None database = None replaceQuote = "\\'" useExceptions = True debug = None getLOBs = True hasRowCount = True metaColSQL = 'Invalid' fmtDate = '%Y-%m-%d' fmtTimeStamp = '%Y-%m-%d %H:%M:%S' _errormsg = '' _errno = 0 _conn = None _autocommit = True _connected = True def __init__(self): pass def Connect(self,host=None,user=None,password=None,database=None): def IsConnected(self): def DriverInfo(self): def ErrorMsg(self): def ErrorNo(self): def qstr(self,s): def quote(self,s): def addq(self,s): def Conn(self): def _query(self,sql,params=None,_cursor=None): def SelectLimit(self,sql,limit,offset=-1,params=None): def Execute(self,sql,params=None): def UpdateBlob(self,table,field,blob,where,blobtype='BLOB'): def UpdateBlobFile(self,table,field,filepath,where,blobtype='BLOB'): def UpdateClob(self,table,field,blob,where): def GetRows(self,sql,params=None): def GetArray(self,sql,params=None): def GetAll(self,sql,params=None): def GetRow(self,sql,params=None): def GetRow(self,sql,params=None): def GetOne(self,sql,params=None): def GetCol(self, sql, params=None): def GetAssoc(self, sql, params=None): def GetDict(self, sql, params=None): def BeginTrans(self): def CommitTrans(self): def RollbackTrans(self): def Close(self): def DBDate(self,d): def DBTimeStamp(self,d): def Date(self,s): def TimeStamp(self,s): def MetaType(self, dbtype): def MetaColumns(self, table): class ADOCursor: _cursor = None fields = None EOF = False _rowcount = 0 _isselect = False _insertid = 0 _conn = None def __init__(self,rs,conn,norowcount=False): def __iter__(self): def RecordCount(self): def MoveNext(self): def FetchRow(self): # returns a tuple of the form (name, type_code,display_size, internal_size, precision, scale,null_ok) # note: databases could return name in upper or lower-case def FetchField(self,row): def Affected_Rows(self): def Insert_ID(self): def Cursor(self): def GetRowAssoc(self,upper=1): def Close(self): #=========================================================== # UNIT TESTING #=========================================================== def _Test_Eq(testid, correct, testval, errmsg=''): if correct == testval: print "Passed Test: "+testid else: print "" print "********* Failed Test: "+testid print "********************** "+str(errmsg) print "********************** expected="+str(correct) print "********************** actual="+str(testval) def Test_Blob(db): import os src = 'c:/lensserver.gif' dest = 'c:/testpy1.gif' try: os.unlink(dest) except: pass saved = db.debug saveb = db.getLOBs db.debug = True db.getLOBs = True db.UpdateBlobFile('photos','photo',src,'id=1') data = db.GetOne('select photo from photos where id=1') f = file(dest,'wb') f.write(data) f.close() rs = db.Execute('select * from photos') while not rs.EOF: print 'Fields=',rs.fields rs.MoveNext() print "=======================" rows = db.GetAll('select * from photos where id<=1') print rows db.getLOBs = saveb db.debug = saved def Test(db,debug=False): db.DriverInfo() if False: d = db.Date('2004-03-21') print '2004-03-21=',d d = db.TimeStamp('2004-03-22 12:50:51') print '2004-03-22 12:50:51=',d print "DBTimeStamp=", db.DBTimeStamp(d) db.useExceptions = True # use adodb error handling try: sql = 'select * from xadoxyz where 0 < id and id < 3' rs = db.Execute(sql) _Test_Eq('Bad SQL',None, rs, sql) except: print "And you should see an error message indicating bad table was defined: " print "err=",db.ErrorMsg() print "-----" rs = db.Execute('select * from ADOXYZ where 0 < id and id < 3 order by id') while not rs.EOF: print rs.fields rs.MoveNext() print "You should see 2 rows of data here:" rs = db.Execute('select * from adoxyz where 0 < id and id < 3 order by id') print "rows=",rs.RecordCount() while (not rs.EOF): print rs.GetRowAssoc() rs.MoveNext() print "-----" rs = db.Execute('select id,firstname from adoxyz where 0 < id and id < 3 order by id') _Test_Eq("Test FetchField",'FIRSTNAME',rs.FetchField(1)[0].upper()) if (debug): print rs.FetchField(1) cnt = 0 while 1: arr=rs.FetchRow() if arr == None: break cnt += 1 _Test_Eq('Execute 2.0',cnt,arr[0]) _Test_Eq('Execute 2.1',2,cnt) if rs.RecordCount() == -1: print "*** RecordCount not supported: -1" else: _Test_Eq('Execute 2.1 RecordCount',2,rs.RecordCount()) rs = db.Execute("delete from adoxyz where id=997") cnt = rs.Affected_Rows() _Test_Eq('Affected_Rows',1,cnt) ok = db.Execute("insert into adoxyz (id, firstname,lastname) values (997,'python','snake')") if not ok: _Test_Eq('DELETE/INSERT','inserted row','failed insert') row = db.GetRow("select id,firstname from adoxyz where id=997"); _Test_Eq('GetRow',str(997)+' '+'python',str(int(row[0]))+' '+row[1].rstrip(),row) row = db.GetOne("select id,firstname from adoxyz where id=997"); _Test_Eq('GetOne',997,row) rs = db.SelectLimit("select id,firstname from adoxyz",3) cnt = 0 try: for row in rs: cnt += 1 #print rs.fields _Test_Eq('SelectLimit',3,cnt) except: print "Failed Iteration" print sys.exc_info()[1] d = db.GetOne('select created from adoxyz where id=1') d2 = db.TimeStamp(d) _Test_Eq('DBDate',str(d)[:19],str(d2)) if (db.qstr("\\show'boat") != "'\\\\show\\'boat'" and db.qstr("\\show'boat") != "'\\show''boat'"): _Test_Eq('qstr',"qstr(\\show'boat)", db.qstr("\\show'boat")) else: _Test_Eq('qstr','1','1') try: db.debug=True print "Testing GetAssoc" arr = db.GetAssoc('select firstname,lastname from adoxyz') print arr print "Testing GetCol" arr = db.GetCol('select firstname from adoxyz') print arr except: print sys.exc_info()[1] try: print "MetaColumns:" rows = db.MetaColumns('adoxyz') print rows except: print "Failed MetaColumns" print sys.exc_info()[1] try: db.BeginTrans() ok = db.Execute("insert into adoxyz (id, firstname,lastname) values (1997,'python','snake')") db.RollbackTrans() val = db.GetOne('select * from adoxyz where id=1997') _Test_Eq('Rollback Test',None,val) except: print "Failed Rollback Test" print sys.exc_info()[1]
ลิงก์ควรศึกษา
การใช้ไพธอนทำเว็บ มีการใช้มอดูลแบบหลัก ๆ คือ
บันทึกเกร็ดเกี่ยวกับการใช้ไพธอนทำเว็บ ด้วย cgi
Content-type: text/html\r\n
เป็นบรรทัดแรก apache ถึงจะตีความเป็น HTML#!/usr/bin/env python print "Content-type: text/html\r\n" ...
#!/usr/bin/env python # -*- coding: utf8 -*- ...
from ... import *
และ import ...
เช่นสมมุติแพกเกจเราชื่อ dweb ใช้... import dweb from dweb import * ...
$ vi .htaccess
DirectoryIndex index.py Options +Indexes ExecCGI FollowSymLinks MultiViews AddHandler cgi-script .py AddType application/x-python-code .pyc .pyo AddHandler python-program .pyc .pyo
การใช้โมดูล cgi ในการสร้างเว็บ ต้องระวังเรื่องเนื้อหาในเพจให้ดี
ให้ระวังคือ
ไม่งั้นฟังก์ชั่น cgi.FieldStorage จะทำงานผิดพลาดไปหมด ดีบักยากด้วย เพราะแสดงผลออกมาถูก แต่การทำงานภายในผิดหมด
เอามาจาก ug's Python CGI scripts: cookie.py
การเซ็ต Cookie
สร้างไฟล์ชื่อ setcookie.py
#!/usr/bin/env python import Cookie c1 = Cookie.SimpleCookie() # Create a cookie in c1 # This will be temporary and will disappear when the session is closed c1["cracker"] = "hello" # The RFC says you should always set this but it seems to work ok without it c1["cracker"]["version"] = 1 # Create another one c1["bisquit"] = "whatever" # Make the browser store it for one hour c1["bisquit"]["max-age"] = 3600 # Time to keep, in seconds c1["bisquit"]["expires"] = 3600 # Obsolete, but Netscape still seems to require it c1["bisquit"]["version"] = 1 # Print the headers that sets the cookies print c1 # Show html page print "Content-type: text/html\r\n" print "<h1>Cookie is set</h1>"
สั่งรันจากบราวเซอร์หนึ่งครั้ง Cookie จะถูกเก็บเข้าในเครื่องของ Client
การรับ Cookie
สร้างไฟล์ชื่อ getcookie.py
#!/usr/bin/env python import Cookie, os # Show html page print "Content-type: text/html\r\n" print "<h1>Get cookie</h1>" try: cookie = os.environ["HTTP_COOKIE"] print "HTTP_COOKIE="+cookie print "<p>" c2 = Cookie.SimpleCookie() c2.load(os.environ["HTTP_COOKIE"]) print "<pre>" print c2 print "</pre>" print 'c2["bisquit"].value =', c2["bisquit"].value, "<br />\n" print 'c2["cracker"].value =', c2["cracker"].value except: print "No cookies were received"
สั่งรันจากบราวเซอร์ จะเห็นตัวแปร bisquit
และ cracker
ที่เราใส่ไว้
อ่าน Dive Into Python เห็นตัวอย่างฟังก์ชั่น Info
เลยประยุกต์มาทำบนเว็บครับ
เผื่อจะขยายไปเป็น search help
ทดลองทำ syntax highlight โดยลอกจาก โมดูล GeSHiFilter ของ drupal ซึ่งเอามาจากโค๊ด PHP ที่ GeSHi อีกทีนึง แก้ปรับสี css นิดเดียว
ทดลองดูซอร์สได้ดังนี้
ตอนนี้ทำได้แค่ไพธอนภาษาเดียว และน่าจะยังมีบั๊กอยู่เยอะ จะค่อย ๆ ปรับปรุงไปเรื่อย ๆ ครับ
ยังไม่รู้ว่าโค๊ดของโมดูลหลัก dweb จะเปลี่ยนแปลงไปยังไงนะครับ แต่ตอนนี้โพสต์แบบนี้ไปก่อน
เราสามารถใช้ python อ่านเนื้อหาจากเว็บได้โดยใช้โมดุล urllib
เอาตัวอย่างจาก Dive into Python - 11.2. How not to fetch data over HTTP
>>> import urllib >>> data = urllib.urlopen('http://diveintomark.org/xml/atom.xml').read() 1 >>> print data <?xml version="1.0" encoding="iso-8859-1"?> <feed version="0.3" xmlns="http://purl.org/atom/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:lang="en"> <title mode="escaped">dive into mark</title> <link rel="alternate" type="text/html" href="http://diveintomark.org/"/> <-- rest of feed omitted for brevity -->
ด้วยวิธีนี้เราสามารถนำเข้าไฟล์สตรีมทั้งหลายได้โดยสะดวก
จากตอนก่อน เราสามารถนำมาสร้างสคริปต์ง่าย ๆ เอาไว้เก็บเนื้อหาของหน้าเว็บได้ดังนี้
$ vi d.getweb.py
#!/usr/bin/env python # SAVE CONTENT OF WEB PAGE TO FILE import sys, os, urllib def usage(progname): print "Usage: %s URL FILENAME" % (progname) print "Save content of web page to file." def cannotopenfile(filename="",readwrite="r"): if readwrite=="r": msg=" reading" else: msg=" writing" print "Cannot open file %s for %s." % (filename, readwrite) def genfilename(filename="",ext="new"): if filename=="": return "" if ext.lower()!="new" and ext.lower()!="bak": ext="bak" if os.path.exists(filename+"."+ext): i=0 while os.path.exists(filename+"."+ext+str(i)) and (i < 1000): i=i+1 if i>999: return "" return filename+"."+ext+str(i) else: return filename+"."+ext # if __name__=="__main__": progname=os.path.basename(sys.argv[0]) try: url=sys.argv[1] filename=sys.argv[2] except: usage(progname) sys.exit(1) # try: if os.path.exists(filename): bakfile=genfilename(filename,"bak") os.rename(filename,bakfile) except: cannotopenfile(filename,"w") sys.exit(1) # try: data=urllib.urlopen(url).read() except: print "Cannot open %s." % (url) sys.exit(1) # f=open(filename,"w") f.write(data) f.close() print "Save %s to %s success." % (url, filename)
เรียกใช้งานด้วยคำสั่ง
./d.getweb.py http://WEB.TO.GET FILENAME.EXT
จากครั้งก่อน ที่ทำตัวแจงเมธอด ขยายมาเป็นตัวค้นหาและแสดงซอร์สโค๊ด
โดยใช้พร๊อพเพอตี้ __file__
ปกติเราดูซอร์สได้เองอยู่แล้ว แต่การนำมาลงเว็บให้ค้นได้ง่าย ๆ อาจช่วยทำให้เข้าใจการทำงานของโมดูลในไพธอนดียิ่งขึ้นครับ
ลอง django
เที่ยวนี้เอาแบบดิบ ๆ เลย
http://www.djangoproject.com/documentation/install/
Install Python
# apt-get install python
+++ mime-support python python-minimal python2.4 python2.4-minimal
Install Apache and mod_python
# apt-get install apache2 libapache2-mod-python
+++ apache2 apache2-mpm-worker apache2-utils apache2.2-common libapache2-mod-python libapr1 libaprutil1 libexpat1 libmagic1 libpcre3 libpq4 libsqlite3-0 python-central
http://www.djangoproject.com/documentation/modpython/
Django requires Apache 2.x and mod_python 3.x, and you should use Apache's prefork MPM, as opposed to the worker MPM.
# apt-get install apache2-mpm-prefork
--- apache2-mpm-worker
+++ apache2-mpm-prefork
Get your database running
Django works with PostgreSQL (recommended), MySQL and SQLite.
# apt-get install postgresql-8.1
+++ openssl postgresql-8.1 postgresql-client-8.1 postgresql-client-common postgresql-common ssl-cert
แก้ปัญหาการจัดเรียงภาษาไทยของ postgresql
ตรวจ locales ให้มีภาษาไทย
# dpkg-reconfigure locales
<<<--- (*) th_TH TIS-620
<<<--- (*) th_TH.UTF-8 UTF-8
inintdb ใหม่ให้เรียงตามภาษาไทย โดยจะสร้างไดเรคทอรี่ของข้อมูลใหม่ ให้อยู่ที่ /server1/var/lib/postgresql/8.1/main
# /etc/init.d/postgresql-8.1 stop
# mkdir -p /server1/var/lib/postgresql/8.1/main
# chown -R postgres:postgres /server1/var/lib/postgresql
# su postgres
$ /usr/lib/postgresql/8.1/bin/initdb -D /server1/var/lib/postgresql/8.1/main --locale=th_TH.UTF-8 --pgdata=/server1/var/lib/postgresql/8.1/main
$ cd /server1/var/lib/postgresql/8.1/main
$ ln -sf /etc/postgresql-common/root.crt .
$ ln -sf /etc/ssl/certs/ssl-cert-snakeoil.pem server.crt
$ ln -sf /etc/ssl/private/ssl-cert-snakeoil.key server.key
$ exit
# cd /etc/postgresql/8.1/main
# rm pgdata
# ln -sf /server1/var/lib/postgresql-8.1/main pgdata
# /etc/init.d/postgresql-8.1 start
ปรับให้ผู้ใช้ในระบบสามารถเข้ามาใช้งานโดยใช้รหัสผ่านของระบบ
# vi /etc/postgresql/8.1/main/pg_hba.conf
... # TYPE DATABASE USER CIDR-ADDRESS METHOD host all all 192.168.1.0/24 md5 ...
สร้างผู้คุมฐานข้อมูล
# su postgres
$ psql template1
template1=# CREATE USER superx SUPERUSER PASSWORD 'superx';
template1=# \q
$ exit
ติดตั้ง phppgadmin
# apt-get install phppgadmin
+++ libapache2-mod-php4 libzzip-0-12 php4-common php4-pgsql phppgadmin wwwconfig-common
# dpkg-reconfigure phppgadmin
Which web server would you like to reconfigure automatically?
<<<--- Apache2
# vi /etc/apache2/conf.d/phppgadmin
# deny from all allow from all
# /etc/init.d/apache2 restart
If you're using PostgreSQL, you'll need the psycopg package
# apt-get install python-psycopg python-psycopg2
+++ python-egenix-mxdatetime python-egenix-mxtools python-psycopg python-psycopg2
Download Django-0.95.tar.gz from our download page.
# cd /usr/src
# tar xfz Django-0.95.tar.gz
# cd Django-0.95
Note that the last command will automatically download and install setuptools if you don't already have it installed. This requires a working Internet connection.
# apt-get install python-setuptools
+++ python-setuptools
# python setup.py install
เสร็จ Django
http://www.sitepoint.com/article/build-to-do-list-30-minute
จะสร้างไดเรคทอรี่ของเว็บ โดยให้ webmaster เป็นเจ้าของ
# useradd -m -g www-data webmaster
# su webmaster
$ cd
$ mkdir django
$ cd django
Diving In
จะสร้างโปรเจคต์ ชื่อ gtd
$ django-admin.py startproject gtd
$ cd gtd
รันเซิร์ฟเวอร์ที่พอร์ต 8000 ไอพี 192.168.1.5
$ python manage.py runserver 192.168.1.5:8000
ทดสอบโดย เอาบราวเซอร์ไปที่ http://192.168.1.5:8000/
หยุดเซิร์ฟเวอร์ด้วย Ctrl-C
จะสร้างแอปพลิเกชั่น todo
$ python manage.py startapp todo
$ cd todo
$ vi models.py
class List(models.Model): title = models.CharField(maxlength=250, unique=True) def __str__(self): return self.title class Meta: ordering = ['title'] class Admin: pass import datetime PRIORITY_CHOICES = ( (1, 'Low'), (2, 'Normal'), (3, 'High'), ) class Item(models.Model): title = models.CharField(maxlength=250) created_date = models.DateTimeField(default=datetime.datetime.now) priority = models.IntegerField(choices=PRIORITY_CHOICES, default=2) completed = models.BooleanField(default=False) todo_list = models.ForeignKey(List) def __str__(self): return self.title class Meta: ordering = ['-priority', 'title'] class Admin: pass
$ cd ..
$ su postgres
postgres@server1$ psql template1
template1=# CREATE DATABASE "django" WITH ENCODING='UTF8';
template1=# \q
postgres@server1$ exit
$ vi settings.py
... DATABASE_ENGINE = 'postgresql' DATABASE_NAME = 'django' DATABASE_USER = 'USER1' DATABASE_PASSWORD = 'USER1PASSWORD' ... DATABASE_PORT = '5432' ... TIME_ZONE = 'Asia/Bangkok' ... INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'gtd.todo', )
$ python manage.py syncdb
You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no):
<<<--- yes
Username (Leave blank to use 'webmaster'):
<<<--- {DEFAULT}
E-mail address:
<<<--- webmaster@example.com
Password:
<<<--- {WEBMASTER-PASSWORD}
Password (again):
<<<--- {WEBMASTER-PASSWORD}
$ vi settings.py
... INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'gtd.todo', 'django.contrib.admin', )
$ vi urls.py
... (r'^admin/', include('django.contrib.admin.urls')), ...
$ python manage.py syncdb
เริ่มรัน
$ python manage.py runserver 192.168.1.5:8000
เอาบราวเซอร์ไปที่ http://192.168.1.5:8000/admin
Username:
<<<--- webmaster
Password:
<<<--- {WEBMASTER-PASSWORD}
ลองเพิ่มผู้ใช้และกรุ๊ปดู
$ cd todo
$ vi views.py
... from django.shortcuts import render_to_response from gtd.todo.models import List def status_report(request): todo_listing = [] for todo_list in List.objects.all(): todo_dict = {} todo_dict['list_object'] = todo_list todo_dict['item_count'] = todo_list.item_set.count() todo_dict['items_complete'] = todo_list.item_set.filter(completed=True).count() todo_dict['percent_complete'] = int(float(todo_dict['items_complete']) / todo_dict['item_count'] * 100) todo_listing.append(todo_dict) return render_to_response('status_report.html', { 'todo_listing': todo_listing })
$ mkdir templates
$ cd templates
$ vi status_report.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>To-do List Status Report</title> </head> <body> <h1>To-do list status report</h1> {% for list_dict in todo_listing %} <h2>{{ list_dict.list_object.title }}</h2> <ul> <li>Number of items: {{ list_dict.item_count }}</li> <li>Number completed: {{ list_dict.items_complete }} ({{ list_dict.percent_complete }}%)</li> </ul> {% endfor %} </body> </html>
$ cd ../..
$ vi settings.py
... TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates". # Always use forward slashes, even on Windows. '/home/webmaster/django/gtd/todo/templates', )
$ vi urls.py
... urlpatterns = patterns('', # Example: # (r'^gtd/', include('gtd.apps.foo.urls.foo')), # Uncomment this for admin: (r'^admin/', include('django.contrib.admin.urls')), (r'^report/$', 'gtd.todo.views.status_report'), )
$ python manage.py runserver 192.168.1.5:8000
ลองดูที่บราวเซอร์ http://192.168.1.5:8000/report
เรียบร้อย
ในการติดตั้งของ django เขาใช้พอร์ต 8000 เป็นค่าปริยาย
ทำนองเดียวกัน apache ก็ใช้พอร์ต 80 เป็นค่าปริยาย
หากต้องการไปรัน django ที่พอร์ต 80 ต้องทำดังนี้
แก้ไข /etc/apache2/ports.conf ให้ไปใช้พอร์ตอื่น สมมุติว่าเป็น 8088
$ sudo vi /etc/apache2/ports.conf
Listen 8088
เวลาสั่งรัน django ต้องใช้สิทธิ์รูตในการรัน (พอร์ตที่ต่ำกว่า 1000)
$ sudo python manage.py runserver 192.168.1.5:80
เอามาจาก Falling Bullets - Blog - WordPress Clone in 27 Seconds (Part 1 of 40)
เราชื่อ webmaster
# su webmaster
เราตั้งให้ไฟล์ของเราอยู่ใน ~/django
$ cd ~/django
ก่อนเริ่ม ให้ลบ database ชื่อ mysite ที่เราเคยทำไว้ออกก่อน
แล้วจึงค่อยสร้างใหม่แบบว่าง ๆ
$ psql template1 -U superx
Password for user superx <<<--- SUPERX-PASSWORD
template1=# DROP DATABASE mysite;
template1=# CREATE DATABASE mysite;
template1=# \q
เริ่มสร้างโปรเจกต์
$ django-admin.py startproject mysite
$ cd mysite
แก้ไข settings.py ให้เรียบร้อย
$ vi settings.py
... DATABASE_ENGINE = 'postgresql' DATABASE_NAME = 'mysite' DATABASE_USER = 'superx' DATABASE_PASSWORD = 'SUPERX-PASSWORD' DATABASE_PORT = '5432' ... TIME_ZONE = 'Asia/Bangkok' ...
สร้างแอปพลิเกชั่นชื่อ blog
$ django-admin.py startapp blog
แก้ไข models.py เพื่อสร้าง table
$ vi blog/models.py
from django.db import models class Tag(models.Model): name = models.CharField(maxlength=50, core=True) slug = models.SlugField(prepopulate_from=("name",)) class Admin: pass def __str__(self): return self.name def get_absolute_url(self): return "/blog/tags/%s/" % (self.slug) class Entry(models.Model): title = models.CharField(maxlength=200) slug = models.SlugField( unique_for_date='pub_date', prepopulate_from=('title',), help_text='Automatically built from the title.' ) summary = models.TextField(help_text="One paragraph. Don't add <p> tag.") body = models.TextField() pub_date = models.DateTimeField() tags = models.ManyToManyField(Tag, filter_interface=models.HORIZONTAL) class Meta: ordering = ('-pub_date',) get_latest_by = 'pub_date' class Admin: list_display = ('pub_date', 'title') search_fields = ['title', 'summary', 'body'] def __str__(self): return self.title def get_absolute_url(self): return "/blog/%s/%s/" % (self.pub_date.strftime("%Y/%b/%d").lower(), self.slug)
แก้ไข urls.py ให้สามารถเรียกไดเรกทอรี่เลียนแบบ Wordpress
$ vi urls.py
from django.conf.urls.defaults import * from mysite.blog.models import Entry blog_dict = { 'queryset': Entry.objects.all(), 'date_field': 'pub_date', } urlpatterns = patterns('', (r'^blog/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', 'django.views.generic.date_based.object_detail', dict(blog_dict, slug_field='slug')), (r'^blog/?$', 'django.views.generic.date_based.archive_index', blog_dict), )
รัน syncdb ครั้งนึง เพื่อสร้าง table
$ python manage.py syncdb
...
Would you like to create one now? (yes/no): <<<--- yes
Username (Leave blank to use 'webmaster'): <<<--- [ENTER]
E-mail address: <<<--- webmaster@example.com
Password: <<<--- WEBMASTER-PASSWORD
Password (again): <<<--- WEBMASTER-PASSWORD
Superuser created successfully.
...
แก้ไข settings.py ให้มาใช้ template ของเรา
$ vi settings.py
... TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates". # Always use forward slashes, even on Windows. "/home/webmaster/django/mysite/templates" ) ...
สร้าง template หลัก ชื่อ base.html ในไดเรกทอรี่ ~/django/mysite/templates
$ mkdir templates
$ vi templates/base.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>My Site - {% block title %}{% endblock %}</title> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> </head> <body> <div id="header"> <h1><a href="/">My Interweb Tubes Blog</a></h1> <h2>It's not a truck!</h2> <ul id="nav"> <li><a href="/">Home</a></li> <li><a href="/blog/">Blog</a></li> <li><a href="#">Photos</a></li> <li><a href="/links/">Links</a></li> <li><a href="/portfolio/">Work</a></li> <li><a href="/colophon/">Colophon</a></li> </ul> </div> <div id="content"> {% block content %} {% endblock %} </div> </body> </html>
สร้าง template ย่อย สำหรับดู Entry archive
$ mkdir templates/blog
$ vi templates/blog/entry_archive.html
{% extends "base.html" %} {% block title %}Latest Blog Entries{% endblock %} {% block content %} <h1>Latest Blog Entries</h1> <ol id="object-list"> {% for object in latest %} <li> <h2><a href="{{ object.get_absolute_url }}">{{ object.title|escape }}</a></h2> <p class="post-date">{{ object.pub_date|date:"F j, Y" }}</p> <p class="summary">{{ object.summary }}</p> </li> {% endfor %} </ol> {% endblock %}
และอีกอันสำหรับดูรายละเอียด
$ vi templates/blog/entry_detail.html
{% extends "base.html" %} {% block title %}Blog - {{ object.title|escape }}{% endblock %} {% block content %} <h1>{{ object.title|escape }}</h1> <dl> <dt>Posted On:</dt> <dd>{{ object.pub_date|date:"F j, Y" }}</dd> <dt>Tags:</dt> <dd> {% for tag in object.tags.all %} <a href="{{ tag.get_absolute_url }}">{{ tag.name }}</a>{% if not forloop.last %}, {% endif %} {% endfor %} </dd> </dl> {{ object.body }} {% endblock %}
แก้ไข settings.py อีกครั้ง ให้รับโมดูล blog และโมดูล admin
$ vi settings.py
... INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'mysite.blog', 'django.contrib.admin', )
syncdb อีกครั้ง
$ python manage.py syncdb
เสร็จแล้ว ลองรันได้เลยด้วยคำสั่ง
$ python manage.py runserver 192.168.1.5:8000
สร้างเนื้อหา blog ได้จาก http://192.168.1.5:8000/admin
โดยล๊อกอินในชื่อ webmaster และสร้างเนื้อหาในหมวด blog
ดูเนื้อหาที่สร้างแล้วที่ http://192.168.1.5:8000/blog
ลองติดตั้ง django เพื่อใช้งานกับ apache2 บนเดเบียน
เที่ยวนี้ทำไปบันทึกไป จึงไม่มีกำหนดเสร็จครับ
งานของ admin เจ้าของเซิร์ฟเวอร์
เอา django มาก่อน
# aptitude install subversion
# svn co http://code.djangoproject.com/svn/django/trunk/
ติดตั้ง django สู่ระบบ
# cd trunk
# python setup.py install
ลบซอร์ส หากไม่ต้องการดูโค๊ดของ django
# cd ..
# rm -rf trunk
กันเหนียวให้ apache2 เปิดมอดูล env
(ส่วนใหญ่จะเปิดมาอยู่แล้วมั้ง)
# a2enmod env
ติดตั้ง mod_python
และเปิดให้ใช้งาน
# aptitude install libapache2-mod-python
# a2enmod mod_python
งานของเรา เจ้าของเว็บ
สมมุติว่า admin ติดตั้ง ให้ DocumentRoot ของ apache2 สำหรับโดเมน www.example.com อยู่ที่ไดเรกทอรี่ของเรา /home/user1/www
และเราจะให้หน้าของ django ไปอยู่ที่ http://www.example.com/dj
มาที่ไดเรกทอรี่ของเราก่อน
$ cd ~/www
เริ่มโปรเจคต์ใหม่ชื่อ dj
$ django-admin.py startproject dj
ไปที่ไดเรคทอรี่ dj และเตรียมการให้ apache2 โดยการสร้างไฟล์ .htaccess
$ cd dj
$ vi .htaccess
SetHandler python-program PythonHandler django.core.handlers.modpython SetEnv SERVER_ADMIN webmaster@example.com SetEnv DJANGO_SETTINGS_MODULE dj.settings PythonDebug On PythonPath "['/home/user1'] + sys.path" RewriteEngine On RewriteBase /dj/
เสร็จแล้ว ดูที่หน้า www.example.com/dj ได้ดังนี้
เพิ่มเติม
สำหรับการทำงานให้เต็มรูปแบบ ต้องสร้างหน้า admin ด้วย
การที่จะทำให้หน้า admin ทำงานได้สมบูรณ์ ต้องสร้างลิงก์โยงจากทรัพยากรของซอร์สมาที่ไดเรกทอรี่รากของ apache2 ด้วย
สมมุติถ้าใช้ไพธอน 2.4 บนเดเบียน
$ cd ~/www
$ ln -sf /usr/lib/python2.4/site-packages/django/contrib/admin/media/ .
สร้างหน้า admin โดยการลบคอมเมนต์ในไฟล์ dj/urls.py
$ cd dj
$ vi urls.py
... (r'^admin/', include('django.contrib.admin.urls')), ...
แต่ถ้าหากเราจะให้เพจของ django อยู่ในหน้า www.example.com/dj เราต้องแก้ไฟล์ด้วย
... (r'^dj/admin/', include('django.contrib.admin.urls')), ...
แต่...ก่อนจะใช้งานหน้า admin ได้ เราต้องสร้างฐานข้อมูลก่อน สมมุติว่าจะใช้กับ postgresql
สร้างฐานข้อมูลไว้รองรับ ตั้งชื่อว่า djdb
$ createdb djdb
Password: >>> --- USER1_PASSWORD
ปรับตั้งไฟล์ settings.py
ให้ django รับรู้ฐานข้อมูลและให้เรียกใช้มอดูล admin
$ vi settings.py
... DATABASE_ENGINE = 'postgresql' DATABASE_NAME = 'djdb' DATABASE_USER = 'user1' DATABASE_PASSWORD = 'USER1_PASSWORD' ... TIME_ZONE = 'Asia/Bangkok' ... LANGUAGE_CODE = 'th' ... INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.admin', )
สั่งปรับปรุงฐานข้อมูล
$ python manage.py syncdb
Creating table auth_message Creating table auth_group Creating table auth_user Creating table auth_permission Creating table django_content_type Creating table django_session Creating table django_site Creating table django_admin_log You just installed Django's auth system, which means you don't have any superusers defined. Would you like to create one now? (yes/no): >>>--- yes Username (Leave blank to use 'user1'): >>>--- ENTER E-mail address: >>>--- user1@example.com Password: >>>--- USER1_PASSWORD Password (again): >>>--- USER1_PASSWORD Superuser created successfully. Installing index for auth.Message model Installing index for auth.Permission model Installing index for admin.LogEntry model
เสร็จแล้ว ดูจาก URL:http://www.example.com/dj/admin
จะได้ดังนี้
อ้างอิง
คราวนี้ทำบล๊อกจาก Falling Bullets - Blog - WordPress Clone in 27 Seconds (Part 1 of 40)
โดย
http://www.example.com/dj
http://dj.example.com
เป็นต้น)/dj
ไปก่อน/home/user1/www
user1
รหัสผ่านคือ USER1_PASSWORD
มีสิทธิ์ในการสร้างฐานข้อมูล/home/user1/www/dj
สร้างแอพลิเคชั่นชื่อ blog ในไดเรคทอรี่ dj จากครั้งก่อน
$ cd ~/www/dj
$ python manage.py startapp blog
สร้างตารางฐานข้อมูลด้วย models.py
ให้มี 2 ตาราง คือเก็บแท็ก และเก็บเนื้อเรื่อง
$ vi blog/models.py
from django.db import models class Tag(models.Model): name = models.CharField(maxlength=50, core=True) slug = models.SlugField(prepopulate_from=("name",)) class Admin: pass def __str__(self): return self.name def get_absolute_url(self): return "/dj/blog/tags/%s/" % (self.slug) class Entry(models.Model): title = models.CharField(maxlength=200) slug = models.SlugField( unique_for_date='pub_date', prepopulate_from=('title',), help_text='Automatically built from the title.' ) summary = models.TextField(help_text="One paragraph. Don't add <p> tag.") body = models.TextField() pub_date = models.DateTimeField() tags = models.ManyToManyField(Tag, filter_interface=models.HORIZONTAL) class Meta: ordering = ('-pub_date',) get_latest_by = 'pub_date' class Admin: list_display = ('pub_date', 'title') search_fields = ['title', 'summary', 'body'] def __str__(self): return self.title def get_absolute_url(self): return "/dj/blog/%s/%s/" % (self.pub_date.strftime("%Y/%b/%d").lower(), self.slug)
แก้ไข urls.py ให้สามารถเรียกไดเรกทอรี่เลียนแบบ Wordpress หรือเรียกแบบปกติ
$ vi urls.py
from django.conf.urls.defaults import * from dj.blog.models import Entry blog_dict = { 'queryset': Entry.objects.all(), 'date_field': 'pub_date', } urlpatterns = patterns('', # Example: # (r'^dj/', include('dj.foo.urls')), # Uncomment this for admin: (r'^dj/admin/', include('django.contrib.admin.urls')), (r'^dj/report/$', 'dj.todo.views.status_report'), (r'^dj/blog/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', 'django.views.generic.date_based.object_detail', dict(blog_dict, slug_field='slug')), (r'^dj/blog/?$', 'django.views.generic.date_based.archive_index', blog_dict), )
รัน syncdb ครั้งนึง เพื่อสร้างและปรับปรุงตาราง
$ python manage.py syncdb
title = models.CharField(maxlength=250, unique=True) title = models.CharField(maxlength=250)
ต่อไปเป็นเรื่องอินเทอร์เฟสแสดงหน้าตา
(เที่ยวนี้แปลกไปนิดนึง เพราะเขาเรียกแสดงผลผ่านฟังก์ชั่นมาตรฐานของ django โดยไม่ได้ใช้ views ของเรา เลยต้องวางไดเรกทอรี่ไว้เป็นมาตรฐาน คือเอา templates
ไว้ที่ root ของโครงการ)
แก้ไข settings.py
ให้มาใช้ template ของเรา รวมทั้งบอกให้เปิดมอดูล blog
ที่เราเพิ่งสร้างขึ้น
$ vi settings.py
... TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. '/home/user1/www/dj/todo/templates', '/home/user1/www/dj/templates', ) INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.admin', 'dj.todo', 'dj.blog', )
สร้างเทมเพลตโดยการสร้างไดเรกทอรี่ชื่อ templates
ไว้ที่ root ของโครงการ
ในไดเรกทอรี่ templates
จะมีไฟล์ base.html
เอาไว้ดูหน้าหลักซึ่งเป็นพวกเมนูต่าง ๆ
และสร้างไดเรกทอรี่ย่อยชื่อ templates/blog
อีกที จะมีไฟล์ entry_archive.html
ไว้ดูหัวข้อบล๊อก และ entry_detail.html
ไว้ดูรายละเอียดของแต่ละรายการ
$ mkdir -p templates/blog
$ vi templates/base.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>My Site - {% block title %}{% endblock %}</title> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> </head> <body> <div id="header"> <h1><a href="/">My Interweb Tubes Blog</a></h1> <h2>It's not a truck!</h2> <ul id="nav"> <li><a href="/dj/">Home</a></li> <li><a href="/dj/blog/">Blog</a></li> <li><a href="#">Photos</a></li> <li><a href="/dj/links/">Links</a></li> <li><a href="/dj/portfolio/">Work</a></li> <li><a href="/dj/colophon/">Colophon</a></li> </ul> </div> <div id="content"> {% block content %} {% endblock %} </div> </body> </html>
$ vi templates/blog/entry_archive.html
{% extends "base.html" %} {% block title %}Latest Blog Entries{% endblock %} {% block content %} <h1>Latest Blog Entries</h1> <ol id="object-list"> {% for object in latest %} <li> <h2><a href="{{ object.get_absolute_url }}">{{ object.title|escape }}</a></h2> <p class="post-date">{{ object.pub_date|date:"F j, Y" }}</p> <p class="summary">{{ object.summary }}</p> </li> {% endfor %} </ol> {% endblock %}
$ vi templates/blog/entry_detail.html
{% extends "base.html" %} {% block title %}Blog - {{ object.title|escape }}{% endblock %} {% block content %} <h1>{{ object.title|escape }}</h1> <dl> <dt>Posted On:</dt> <dd>{{ object.pub_date|date:"F j, Y" }}</dd> <dt>Tags:</dt> <dd> {% for tag in object.tags.all %} <a href="{{ tag.get_absolute_url }}">{{ tag.name }}</a>{% if not forloop.last %}, {% endif %} {% endfor %} </dd> </dl> {{ object.body }} {% endblock %}
เรียกปรับปรุงตารางอีกครั้ง
python manage.py syncdb
title = models.CharField(maxlength=250, unique=True) title = models.CharField(maxlength=250) name = models.CharField(maxlength=50, core=True) title = models.CharField(maxlength=200) Creating table blog_entry Creating table blog_tag Installing index for blog.Entry model Installing index for blog.Tag model
เสร็จแล้ว เรียกผ่าน URL:http://www.example.com/dj/blog
ได้ดังนี้
จากครั้งก่อน django: ใช้กับ apache2 บนเดเบียน (มีการปรับปรุงให้เนื้อหาสมบูรณ์ขึ้นในหน้าเก่าด้วย)
ตอนนี้เราจะมาสร้างแอพลิเคชั่นชื่อ "to do" จาก sitepoint.com - Django Djumpstart: Build a To-do List in 30 Minutes
โดย
http://www.example.com/dj
http://dj.example.com
เป็นต้น)/dj
ไปก่อน/home/user1/www
user1
รหัสผ่านคือ USER1_PASSWORD
มีสิทธิ์ในการสร้างฐานข้อมูล/home/user1/www/dj
เริ่มเลย
สร้างแอพลิเคชั่นชื่อ "to do" เอาไว้สำหรับดูว่าจะทำงานอะไรบ้าง
$ cd ~/www/dj
$ python manage.py startapp todo
สร้างฐานข้อมูลผ่านโปรแกรมชื่อ todo/models.py
โดยเราจะสร้างเป็น 2 ตาราง โดยแต่ละตารางจะเป็นคลาสใน model.py
คือคลาส List สำหรับดูหัวข้อ และคลาส Item สำหรับเก็บรายละเอียดของข้อมูลของงานที่จะทำ
$ vi todo/models.py
... #TABLE List class List(models.Model): title = models.CharField(maxlength=250, unique=True) def __str__(self): return self.title class Meta: ordering = ['title'] class Admin: pass #TABLE Item import datetime PRIORITY_CHOICES = ( (1, 'Low'), (2, 'Normal'), (3, 'High'), ) class Item(models.Model): title = models.CharField(maxlength=250) created_date = models.DateTimeField(default=datetime.datetime.now) priority = models.IntegerField(choices=PRIORITY_CHOICES, default=2) completed = models.BooleanField(default=False) todo_list = models.ForeignKey(List) def __str__(self): return self.title class Meta: ordering = ['-priority', 'title'] class Admin: pass
ถึงตอนนี้ต้องมีฐานข้อมูลอยู่แล้ว หากยังไม่ได้สร้างฐานข้อมูล ให้ย้อนไปดูคราวก่อน
ปรับตั้งให้ django รับรู้ถึงการเพิ่มตาราง ผ่านไฟล์ settings.py
$ vi settings.py
... INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.admin', 'dj.todo', )
สั่งสร้างตาราง/ปรับปรุงฐานข้อมูล
$ python manage.py syncdb
title = models.CharField(maxlength=250, unique=True) title = models.CharField(maxlength=250) Creating table todo_item Creating table todo_list Installing index for todo.Item model
ต่อไปเป็นการสร้างอินเทอร์เฟสผ่านไฟล์ชื่อ todo/views.py
ในไฟล์นี้เราจะสร้างฟังก์ชั่นในการแสดงรายงานสถานะของงานชื่อว่า status_report
$ vi todo/views.py
... from django.shortcuts import render_to_response from dj.todo.models import List def status_report(request): todo_listing = [] for todo_list in List.objects.all(): todo_dict = {} todo_dict['list_object'] = todo_list todo_dict['item_count'] = todo_list.item_set.count() todo_dict['items_complete'] = todo_list.item_set.filter(completed=True).count() todo_dict['percent_complete'] = int(float(todo_dict['items_complete']) / todo_dict['item_count'] * 100) todo_listing.append(todo_dict) return render_to_response('status_report.html', { 'todo_listing': todo_listing })
เอาตารางมาใช้จากคลาส List
ใน todo/models.py
ไฟล์ views.py
นี้เป็นฟังก์ชั่นการทำงานล้วน ๆ ซึ่งเราจะต้องสร้างเทมเพลตในการแสดงผลอีกทีหนึ่ง
ในการสร้างเทมเพลต เราจะสร้างไดเรคทอรี่ย่อยชื่อ templates
ไว้ใน todo
เพื่อเอาไว้บรรจุไฟล์เทมเพลต คือไฟล์ HTML ในที่นี้ตั้งชื่อว่า status_report.html
$ mkdir todo/templates
$ vi todo/templates/status_report.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>To-do List Status Report</title> </head> <body> <h1>To-do list status report</h1> {% for list_dict in todo_listing %} <h2>{{ list_dict.list_object.title }}</h2> <ul> <li>Number of items: {{ list_dict.item_count }}</li> <li>Number completed: {{ list_dict.items_complete }} ({{ list_dict.percent_complete }}%)</li> </ul> {% endfor %} </body> </html>
สังเกตุว่าคำสั่งจะอยู่ในบล๊อก {% COMMAND %}
และตัวแปรจะอยู่ในบล๊อก {{ VARIABLE }}
โดยตัวแปรที่ใช้ ใช้เสมือนเราอยู่ภายในมอดูล todo.views.status_report
ซึ่งเราต้องกลับไปบอก django ในไฟล์ urls.py
ต้องกลับไปบอก django ว่าโครงการของเรามีเทมเพลตด้วย และเทมเพลตเราอยู่ที่ไหน ผ่าน settings.py
$ vi settings.py
... TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. '/home/user1/www/dj/todo/templates', )
และกำหนดให้ apache2 มาเรียกใช้ todo เมื่อเข้า URL:/dj/report/
ผ่าน urls.py
$ vi urls.py
... urlpatterns = patterns('', # Example: # (r'^dj/', include('dj.foo.urls')), # Uncomment this for admin: (r'^dj/admin/', include('django.contrib.admin.urls')), (r'^dj/report/$', 'dj.todo.views.status_report'), )
ตอนนี้ดูได้แล้ว ผ่าน URL:http://www.example.com/dj/report
ตอนนี้ยังไม่มีอะไร เพราะเรายังไม่ได้ใส่อะไรเข้าไป
ถึงตอนนี้เราสามารถใส่เนื้อหาใหม่เข้าไปได้ ผ่านทางหน้า admin
โดยต้องเพิ่ม List ก่อน ทาง URL:http://www.example.com/dj/admin/todo/list/add
ตามด้วย Item ทาง URL:http://www.example.com/dj/admin/todo/item/add
พอเข้าหน้า report
ใหม่ ก็จะเห็นรายการตามต้องการ
รายการเพิ่มเติมสำหรับ django รุ่น svn (ระหว่าง 0.96-)
$ sudo aptitude install python-docutils
$ sudo vi /usr/lib/python2.4/site-packages/django/contrib/admin/media/css/global.css
body { margin:0; padding:0; font-size:84%; font-family:"Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:#fff; } /* LINKS */ a:link, a:visited { color: #5b80b2; text-decoration:none; } a:hover { color: #036; } a img { border:none; } /* GLOBAL DEFAULTS */ p, ol, ul, dl { margin:.2em 0 .8em 0; } p { padding:0; line-height:140%; } h1,h2,h3,h4,h5 { font-weight:bold; } h1 { font-size:1.4em; color:#666; padding:0 6px 0 0; margin:0 0 .2em 0; } h2 { font-size:1.3em; margin:1em 0 .5em 0; } h2.subhead { font-weight:normal;margin-top:0; } h3 { font-size:1.2em; margin:.8em 0 .3em 0; color:#666; font-weight:bold; } h4 { font-size:1.1em; margin:1em 0 .8em 0; padding-bottom:3px; } h5 { font-size:1em; margin:1.5em 0 .5em 0; color:#666; text-transform:uppercase; letter-spacing:1px; } ul li { list-style-type:square; padding:1px 0; } ul.plainlist { margin-left:0 !important; } ul.plainlist li { list-style-type:none; } li ul { margin-bottom:0; } li, dt, dd { font-size:.9em; line-height:1.2em; } dt { font-weight:bold; margin-top:4px; } dd { margin-left:0; } form { margin:0; padding:0; } fieldset { margin:0; padding:0; } blockquote { font-size:.9em; color:#777; margin-left:2px; padding-left:10px; border-left:5px solid #ddd; } code, pre { font-family:"Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; background:inherit; color:#666; font-size:.9em; } pre.literal-block { margin:10px; background:#eee; padding:6px 8px; } code strong { color:#930; } hr { clear:both; color:#eee; background-color:#eee; height:1px; border:none; margin:0; padding:0; font-size:1px; line-height:1px; } /* TEXT STYLES & MODIFIERS */ .small { font-size:.9em; } .tiny { font-size:.8em; } p.tiny { margin-top:-2px; } .mini { font-size:.7em; } p.mini { margin-top:-3px; } .help, p.help { font-size:.8em !important; color:#999; } p img, h1 img, h2 img, h3 img, h4 img, td img { vertical-align:middle; } .quiet, a.quiet:link, a.quiet:visited { color:#999 !important;font-weight:normal !important; } .quiet strong { font-weight:bold !important; } .float-right { float:right; } .float-left { float:left; } .clear { clear:both; } .align-left { text-align:left; } .align-right { text-align:right; } .example { margin:10px 0; padding:5px 10px; background:#efefef; } .nowrap { white-space:nowrap; } /* TABLES */ table { border-collapse:collapse; border-color:#ccc; } td, th { font-size:.9em; line-height:1.2em; border-bottom:1px solid #eee; vertical-align:top; padding:5px; font-family:"Lucida Grande", Verdana, Arial, sans-serif; } th { text-align:left; font-size:1em; font-weight:bold; } thead th, tfoot td { color:#666; padding:2px 5px; font-size:.9em; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; border-left:1px solid #ddd; border-bottom:1px solid #ddd; } tfoot td { border-bottom:none; border-top:1px solid #ddd; } thead th:first-child, tfoot td:first-child { border-left:none !important; } thead th.optional { font-weight:normal !important; } fieldset table { border-right:1px solid #eee; } tr.row-label td { font-size:.7em; padding-top:2px; padding-bottom:0; border-bottom:none; color:#666; margin-top:-1px; } tr.alt { background:#f6f6f6; } .row1 { background:#EDF3FE; } .row2 { background:white; } /* SORTABLE TABLES */ thead th a:link, thead th a:visited { color:#666; display:block; } table thead th.sorted { background-position:bottom left !important; } table thead th.sorted a { padding-right:13px; } table thead th.ascending a { background:url(../img/admin/arrow-down.gif) right .4em no-repeat; } table thead th.descending a { background:url(../img/admin/arrow-up.gif) right .4em no-repeat; } /* ORDERABLE TABLES */ table.orderable tbody tr td:hover { cursor:move; } table.orderable tbody tr td:hover { cursor:move; } table.orderable tbody tr td:first-child { padding-left:14px; background-image:url(../img/admin/nav-bg-grabber.gif); background-repeat:repeat-y; } table.orderable-initalized .order-cell, body>tr>td.order-cell { display:none; } /* FORM DEFAULTS */ input, textarea, select { margin:2px 0; padding:2px 3px; vertical-align:middle; font-family:"Lucida Grande", Verdana, Arial, sans-serif; font-weight:normal; font-size:.9em; } textarea { vertical-align:top !important; } input[type=text], input[type=password], textarea, select, .vTextField { border:1px solid #ccc; } /* FORM BUTTONS */ input[type=submit], input[type=button], .submit-row input { background:white url(../img/admin/nav-bg.gif) bottom repeat-x; padding:3px; color:black; border:1px solid #bbb; border-color:#ddd #aaa #aaa #ddd; } input[type=submit]:active, input[type=button]:active { background-image:url(../img/admin/nav-bg-reverse.gif); background-position:top; } input[type=submit].default, .submit-row input.default { border:2px solid #5b80b2; background:#7CA0C7 url(../img/admin/default-bg.gif) bottom repeat-x; font-weight:bold; color:white; } input[type=submit].default:active { background-image:url(../img/admin/default-bg-reverse.gif); background-position:top; } /* MODULES */ .module { border:1px solid #ccc; margin-bottom:5px; background:white; } .module p, .module ul, .module h3, .module h4, .module dl, .module pre { padding-left:10px; padding-right:10px; } .module blockquote { margin-left:12px; } .module ul, .module ol { margin-left:1.5em; } .module h3 { margin-top:.6em; } .module h2, .module caption { margin:0; padding:2px 5px 3px 5px; font-size:.9em; text-align:left; font-weight:bold; background:#7CA0C7 url(../img/admin/default-bg.gif) top left repeat-x; color:white; } .module table { border-collapse: collapse; } /* MESSAGES & ERRORS */ ul.messagelist { padding:0 0 5px 0; margin:0; } ul.messagelist li { font-size:1em; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border-bottom:1px solid #ddd; color:#666; background:#ffc url(../img/admin/icon_success.gif) 5px .3em no-repeat; } .errornote { font-size:1em !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:red;background:#ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; } ul.errorlist { margin:0 !important; padding:0 !important; } .errorlist li { font-size:1em !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:white; background:red url(../img/admin/icon_alert.gif) 5px .3em no-repeat; } td ul.errorlist { margin:0 !important; padding:0 !important; } td ul.errorlist li { margin:0 !important; } .error { background:#ffc; } .error input, .error select { border:1px solid red; } div.system-message { background: #ffc; margin: 10px; padding: 6px 8px; font-size: .8em; } div.system-message p.system-message-title { padding:4px 5px 4px 25px; margin:0; color:red; background:#ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; } .description { font-size:1em; padding:5px 0 0 12px; } /* BREADCRUMBS */ div.breadcrumbs { background:white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; padding:2px 8px 3px 8px; font-size:.9em; color:#999; border-top:1px solid white; border-bottom:1px solid #ccc; text-align:left; } /* ACTION ICONS */ .addlink { padding-left:12px; background:url(../img/admin/icon_addlink.gif) 0 .2em no-repeat; } .changelink { padding-left:12px; background:url(../img/admin/icon_changelink.gif) 0 .2em no-repeat; } .deletelink { padding-left:12px; background:url(../img/admin/icon_deletelink.gif) 0 .25em no-repeat; } a.deletelink:link, a.deletelink:visited { color:#CC3434; } a.deletelink:hover { color:#993333; } /* OBJECT TOOLS */ .object-tools { font-size:.8em; font-weight:bold; font-family:Arial,Helvetica,sans-serif; padding-left:0; float:right; position:relative; margin-top:-2.4em; margin-bottom:-2em; } .form-row .object-tools { margin-top:5px; margin-bottom:5px; float:none; height:2em; padding-left:3.5em; } .object-tools li { display:block; float:left; background:url(../img/admin/tool-left.gif) 0 0 no-repeat; padding:0 0 0 8px; margin-left:2px; height:16px; } .object-tools li:hover { background:url(../img/admin/tool-left_over.gif) 0 0 no-repeat; } .object-tools a:link, .object-tools a:visited { display:block; float:left; color:white; padding:.1em 14px .1em 8px; height:14px; background:#999 url(../img/admin/tool-right.gif) 100% 0 no-repeat; } .object-tools a:hover, .object-tools li:hover a { background:#5b80b2 url(../img/admin/tool-right_over.gif) 100% 0 no-repeat; } .object-tools a.viewsitelink, .object-tools a.golink { background:#999 url(../img/admin/tooltag-arrowright.gif) top right no-repeat; padding-right:28px; } .object-tools a.viewsitelink:hover, .object-tools a.golink:hover { background:#5b80b2 url(../img/admin/tooltag-arrowright_over.gif) top right no-repeat; } .object-tools a.viewsitelink:hover, .object-tools a.golink:hover { background:#5b80b2 url(../img/admin/tooltag-arrowright_over.gif) top right no-repeat; } .object-tools a.addlink { background:#999 url(../img/admin/tooltag-add.gif) top right no-repeat; padding-right:28px; } .object-tools a.addlink:hover { background:#5b80b2 url(../img/admin/tooltag-add_over.gif) top right no-repeat; } /* OBJECT HISTORY */ table#change-history { width:100%; } table#change-history tbody th { width:16em; }
คราวหน้า ถ้าจะปรับเปลี่ยนเพิ่มเติม เพียงเปลี่ยนเฉพาะบรรทัดแรกจาก 84% ไปเป็นตัวเลขอื่นก็ปรับเฉพาะตัวนี้ตัวเดียว
เมื่อแปลงแล้วได้ภาพดังนี้
รวบรวมเกร็ดการทดลอง นำมาเขียนไว้กันลืม
ที่เหลือ พยายามเขียนลงไว้ใน Python Book แทน เพื่อเอาไว้อ้างอิง
ไพธอนไม่สามารถผ่านค่าไปยังฟังก์ชั่นแบบ "ผ่านค่าโดยการอ้างอิง" ได้
มีวิธีโดยอ้อมคือผ่านค่าโดยใช้ list หรือ dictionary ซึ่งเป็น mutable object
>>> def x(i): ... i[0] += 1 ... >>> i = 5 >>> a = [i] >>> x(a) >>> a [6] >>> i = a[0] >>> i 6
Python มีคำสั่ง exec ที่ใช้ในการแทนค่าตัวแปรแล้วรันคำสั่งแบบพลวัต (dynamicly)
บันทึกพฤติกรรมของ exec ไว้ดังนี้
>>> var='123' >>> a='print %s' % (var) >>> exec a # OR exec(a) 123
>>> var=456 >>> exec a 123 >>> a='print %s' % (var) >>> exec a 456
>>> var='abc' >>> a='print %s' % (var) >>> exec a Traceback (most recent call last): File "<stdin>", line 1, in ? File "<string>", line 1, in ? NameError: name 'abc' is not defined >>> var='"abc"' >>> a='print %s' % (var) >>> exec a abc
ยกเว้นกรณีคำสั่ง import ต้องใช้แบบปกติ
>>> var='re' >>> a='import %s' % (var) >>> exec a >>> dir(re) ['DOTALL', 'I', 'IGNORECASE', 'L', 'LOCALE', 'M', 'MULTILINE', 'S', 'U', 'UNICODE', 'VERBOSE', 'X', '__all__', '__builtins__', '__doc__', '__file__', '__name__', 'compile', 'engine', 'error', 'escape', 'findall', 'finditer', 'match', 'purge', 'search', 'split', 'sub', 'subn', 'template']
อยากได้ดิกฯ แบบที่สามารถเติมข้อมูลได้ ทั้งแนวกว้างและแนวลึก
และให้เป็นข้อมูลแบบ Stack ด้วย
เราใช้วิธีการสร้างลิสต์ในดิกฯ โดย
สร้างคลาสชื่อ ComplexDict
ดังนี้
class ComplexDict: def __init__(self, key): self.__list__ = [] key = str(key) self.__list__.append([key,[]]) def add_key(self, key): if not self.has_key(key): self.__list__.append([key,[]]) def __repr__(self): return repr(dict(self.__list__)) def __getitem__(self, key): _keys = self.keys() if key in _keys: index = _keys.index(key) return self.__list__[index][1] def __delitem__(self, key): if self.has_key(key): self[key] = [] self.__list__.remove([key,[]]) def __setitem__(self, key, val): self.add_key(key) index = self.keys().index(key) self.__list__[index][1] = [val] def keys(self): return [i[0] for i in self.__list__] def values(self): return [i[1] for i in self.__list__] def has_key(self, key): if key in self.keys(): return True else: return False
ทดสอบ
>>> s=ComplexDict('a') >>> s {'a': []} >>> s.add_key('b') >>> s {'a': [], 'b': []} >>> s['a']=1 >>> s['a'].append(2) >>> s {'a': [1, 2], 'b': []} >>> s['a']=2 >>> s {'a': [2], 'b': []} >>> s['b']=ComplexDict('c') >>> s {'a': [2], 'b': [{'c': []}]} >>> s.keys() ['a', 'b'] >>> s.values() [[2], [{'c': []}]] >>> s.__list__ [['a', [2]], ['b', [{'c': []}]]]
ข้อเสียคือยังเข้าถึงข้อมูลยาก
>>> s['b'] [{'c': []}] >>> s['b'][0]['c'] [] >>> s['b'][0]['c'] = 5 >>> s['b'][0]['c'] [5] >>> s {'a': [2], 'b': [{'c': [5]}]} >>>
ข้อมูลชนิดดิกชันนารี่ของไพธอน ดีหลายอย่าง แต่เสียตรงไม่สามารถทำงานแบบ Stack (First in, Last out) ได้
ลองเขียนคลาสง่าย ๆ สร้าง tuple
ใน list
เพื่อเลียนแบบดิกชันนารี่ที่สามารถเก็บข้อมูลเป็น stack ได้
class StackDict: def __init__(self): self.data = [] self._keys = [] self._items = [] def __repr__(self): return repr(dict(self.data)) def __setitem__(self, key, item): if key in self._keys: index = self._keys.index(key) self.data[index] = (key, item) self._items[index] = item else: self.data.append((key, item)) self._keys.append(key) self._items.append(item) def __getitem__(self, key): index = self._keys.index(key) return self._items[index] def __delitem__(self, key): index = self._keys.index(key) self.data.remove((key,self._items[index])) self._keys.remove(key) self._items.remove(self._items[index]) def dict(self): return dict(self.data) def has_key(self, key): if key in self._keys: return True else: return False def keys(self): return self._keys def values(self): return self._items def items(self): return self.data def clear(self): self.__init__() def copy(self): import copy return copy.copy(self) def pop(self, key): item = self[key] del self[key] return item def getkey(self, index): return self._keys[index] def getvalue(self, index): return self._items[index]
เมธอดยังไม่ครบแบบดิกชันนารี่จริง ๆ เราเอาแค่เมธอดที่เรียกใช้บ่อย
ลองทดสอบดู
>>> d = StackDict() >>> d['x']=1 >>> d['a']=2 >>> d['1']=3 >>> d['b']=4 >>> d {'a': 2, 'x': 1, '1': 3, 'b': 4} >>> d.getkey(0) 'x' >>> d.data [('x', 1), ('a', 2), ('1', 3), ('b', 4)] >>> d.getvalue(0) 1 >>> d.getkey(-1) 'b' >>> d.getvalue(-1) 4
default agrument มีประโยชน์ในการกำหนดค่าปริยายให้กับตัวแปรที่ส่งผ่านไปให้ฟังก์ชั่น
ขอยกตัวอย่างเป็นคลาสแทน
>>> class X: ... def __init__(self, a=1, b=2): ... self.a = a ... self.b = b ... print "X.a=%s, X.b=%s" % (self.a, self.b) ... >>> x = X() X.a=1, X.b=2 >>> x = X(11) X.a=11, X.b=2 >>> x = X(a=111) X.a=111, X.b=2 >>> x = X(b=222) X.a=1, X.b=222
ตัวแปร a
และ b
เป็นตัวแปรที่เป็น default argument
การสืบทอดคลาสมักนิยมใช้รูปแบบอาร์กิวเมนต์เป็น *argv **keyw
เพื่อหลีกเลี่ยงการกำหนดอาร์กิวเมนต์ใหม่
โดยไพธอนจะแทนอาร์กิวเมนต์ที่ต้องกำหนดค่าให้ด้วยตัวแปร argv
เป็นข้อมูลชนิด tuple
และแทนอาร์กิวเมนต์ที่มีค่าปริยายกำหนดไว้แล้วด้วยตัวแปร keyw
เป็นข้อมูลชนิด dictionaries
เช่น
class Y(X): def __init__(self, *argv, **keyw): self.__parent = X self.__parent.__init__(self, *argv, **keyw)
ตัวแปรทั้งหมด จะส่งผ่านไปให้คลาส X
เราสามารถดักและเปลี่ยนแปลงค่าในอาร์กิวเมนต์ได้ ดังนี้
>>> class Y(X): ... def __init__(self, *argv, **keyw): ... if keyw.has_key("a") and keyw["a"] > 111: keyw["a"]=111 ... self.__parent = X ... self.__parent.__init__(self, *argv, **keyw) ... >>> y = Y() X.a=1, X.b=2 >>> y = Y(a=999999) X.a=111, X.b=2
ทีแรกจะศึกษาเพื่อเอามาทำ session แต่คิดว่าคงไม่ค่อยเหมาะ เพราะเปลืองหน่วยความจำ
ขอบันทึกไว้หน่อยครับ
สมมุติว่าต้องการทำคำสั่ง print "ABC"
เมื่อเวลาผ่านไป 30 วินาที
แบบแรกใช้โมดูล threading ใช้คำสั่งว่า
>>> def p(): ... print "ABC" ... >>> import threading >>> ss = threading.Timer(30, p) >>> ss.start() (ผ่านไป 30 วินาที) ABC
จับการทำงานของโปรเซสด้วย top ได้ว่า 0.0%CPU 2.7%MEM
มีข้อเสียคือ ออกจากเชลล์ของไพธอนไม่ได้ เพราะต้องรอให้เธรดจบก่อน (ด้วยคำสั่ง ss.cancel()
)
แบบที่สองใช้โมดูล thread เฉย ๆ
ใช้คำสั่งดังนี้
>>> def p(): ... time.sleep(30) ... print "ABC" ... >>> import thread, time >>> ss = thread.start_new_thread(p, ()) (ผ่านไป 30 วินาที) ABC
จับด้วย top ได้ว่า 0.0%CPU 1.1%MEM
ข้อดีคือ ออกจากเชลล์ไพธอนได้เลย (แต่โปรเซสก็ตายด้วย)
ข้อเสียคือ หยุดการทำงานของเธรดไม่ได้
ไพธอนมีคำสั่งในการรันเชลล์ คือ os.system
เช่น สมมุติว่าต้องการรันคำสั่ง ls
>>> import os >>> exitstatus = os.system('ls') FILE1.TXT FILE2.TXT FILE3.TXT >>> exitstatus 0
แต่ถ้าเราต้องการนำเข้าการแสดงผลจากเชลล์ เข้ามาในตัวแปรในไพธอน จะใช้อีกโมดูลนึงคือ commands
เช่น
>>> import commands >>> exitstatus, outtext = commands.getstatusoutput('ls') >>> exitstatus 0 >>> outtext 'FILE1.TXT\nFILE2.TXT\nFILE3.TXT' >>> outtext.split('\n') ['FILE1.TXT', 'FILE2.TXT', 'FILE3.TXT']
การสืบทอดคลาสในไพธอน ถ้าเราสร้างเมธอด __init__
ขึ้นมาใหม่
เขาจะไม่เรียกใช้เมธอด __init__
ของคลาสแม่โดยอัตโนมัติ
เราจึงต้องเขียนสั่งให้เรียกเมธอดของคลาสแม่ด้วยตนเอง
การอ้างถึงคลาสแม่ในไพธอน จะอ้างถึงแบบตรง ๆ เช่น
>>> class X: ... def __init__(self): ... print "print from class X" ... >>> class Y(X): ... def __init__(self): ... X.__init__(self) ... print "print from class Y" ... >>> y = Y() print from class X print from class Y >>>
หรือ
>>> class Y(X): ... def __init__(self): ... self.__parent = X ... self.__parent.__init__(self) ... print "print from class Y" ...
แบบหลังจะมีประโยชน์ในการเรียกใช้คลาสแม่จากเมธอดอื่นในคลาสนั้น ๆ
ถ้าเป็นการสืบทอดคลาสเพียงชั้นเดียว สามารถใช้พรอพเพอร์ตี้ __class__
และ __bases__
คือ
self.__parent = self.__class__.__bases__[0]
แต่พอสืบทอดกันเกินหนึ่งชั้นแล้ว การทำงานจะตีกันมั่ว
มีอีกวิธีนึงในการที่จะให้คลาสลูกเรียกเมธอด __init__
โดยอัตโนมัติ นั่นคือเราจะไม่สร้างเมธอด __init__
ในคลาสลูก แต่สร้างเมธอดใหม่ที่เมธอด __init__
จะมาเรียกใช้อีกทีนึง
ตัวอย่าง
>>> class X: ... def __init__(self,*argv,**keyw): ... if len(self.__class__.__bases__) == 0: ... self._parent = None ... else: ... self._parent = self.__class__.__bases__[0] ... print self._parent ... self.init(argv, keyw) ... def init(self,*argv,**keyw): ... pass ... >>> class Y(X): ... def init(self,*argv,**keyw): ... print 'Print from class Y' ... >>> class Z(Y): ... pass ... >>> y=Y() __main__.X Print from class Y >>> z=Z() __main__.Y Print from class Y
บันทึกโค๊ดที่ทดลองเขียน
หาค่า factor ของ 2 จำนวนเต็ม
สมการคือ (nX * x) + (nY * y) = z
รู้ x, y, และ z
อยากทราบจำนวน nX และ nY ที่เป็นจำนวนเต็ม ว่ามีค่าเท่าไหร่มั่ง
ตั้งชื่อโปรแกรมว่า d.calc2.py
$ vi d.calc2.py
#!/usr/bin/env python import sys if sys.argv[3]==None: nSum=input("Enter sum amount: ") nXparms = input("Enter x parms: ") nYparms = input("Enter y parms: ") else: nSum=eval(sys.argv[1]) nXparms=eval(sys.argv[2]) nYparms=eval(sys.argv[3]) # nX=0 nY=1 print "nX*%f + nY*%f = %f" % (nXparms, nYparms, nSum) while nY>0: nY=float(nSum-nXparms*nX)/nYparms nRemain=float(nY-int(nY)) if nRemain==0: print "nX=%i, nY=%i" % (nX,nY) # nX=nX+1 #
ทดลองเรียกใช้งาน
$ ./d.calc2.py 50 1.5 2.5
nX*1.500000 + nY*2.500000 = 50.000000
nX=0, nY=20
nX=5, nY=17
nX=10, nY=14
nX=15, nY=11
nX=20, nY=8
nX=25, nY=5
nX=30, nY=2
วันนี้มีงานต้องแปลงไฟล์ จาก tis620 ไป utf8
คำสั่งที่ต้องใช้คือ
$ iconv -f tis620 -t utf8 -o NEWFILE.TXT OLDFILE.TXT
แต่ปัญหาคือหลายไฟล์แปลงไม่ได้ เขาไม่ยอมแปลงให้
อาจเป็นเพราะมีรหัสแปลกปลอมในไฟล์
เลยเขียนสคริปต์ไว้ใช้แปลงเอง จะได้เผื่อสำหรับงานหน้าด้วย
ตั้งชื่อว่า d.tis2utf เอาไว้ใน /usr/local/bin
$ sudo touch /usr/local/bin/d.tis2utf
$ sudo chmod 755 /usr/local/bin/d.tis2utf
$ sudo vi /usr/local/bin/d.tis2utf
#!/usr/bin/env python # CONVERT FILE CONTENT FROM tis620(cp874) TO utf8 import sys,os # GLOBAL VARS decodec="cp874" encodec="utf8" # # VARIABLE decodec AND encodec CAN BE CHANGED. # POSSIBLE STANDARD ENCODINGS VALUES ARE: # ascii, big5, big5hkscs, cp037, cp424, cp437, cp500, cp737, cp775, cp850, # cp852, cp855, cp856, cp857, cp860, cp861, cp862, cp863, cp864, cp865, # cp866, cp869, cp874, cp875, cp932, cp949, cp950, cp1006, cp1026, cp1140, # cp1250, cp1251, cp1252, cp1253, cp1254, cp1255, cp1256, cp1257, cp1258, # euc_jp, euc_jis_2004, euc_jisx0213, euc_kr, gb2312, gbk, gb18030, hz, # iso2022_jp, iso2022_jp_1, iso2022_jp_2, iso2022_jp_2004, iso2022_jp_3, # iso2022_jp_ext, iso2022_kr, latin_1, iso8859_2, iso8859_3, iso8859_4, # iso8859_5, iso8859_6, iso8859_7, iso8859_8, iso8859_9, iso8859_10, # iso8859_13, iso8859_14, iso8859_15, johab, koi8_r, koi8_u, mac_cyrillic, # mac_greek, mac_iceland, mac_latin2, mac_roman, mac_turkish, ptcp154, # shift_jis, shift_jis_2004, shift_jisx0213, utf_16, utf_16_be, utf_16_le, # utf_7, utf_8, utf_8_sig # # PLEASE SEE http://docs.python.org/lib/standard-encodings.html # FOR MORE INFORMATION. def usage(progname): print "Usage: %s FILE" % (progname) print "Convert FILE from %s to %s, save old file in FILE.bak" % (decodec,encodec) def cannotopenfile(filename): print "Cannot open file %s" % (filename) def genfilename(filename="",ext="new"): if filename=="": return "" # if ext.lower()=="new": ext="new" # if ext.lower()!="new" and ext.lower()!="bak": ext="bak" # if os.path.exists(filename+"."+ext): i=0 while os.path.exists(filename+"."+ext+str(i)) and (i < 1000): i=i+1 # if i>999: return "" # return filename+"."+ext+str(i) else: return filename+"."+ext # def convertfile(fs_old, fs_new): for eachline in fs_old: try: newline=eachline.decode(decodec).encode(encodec) except: newline=eachline # fs_new.write(newline) # return True if __name__=="__main__": progname=os.path.basename(sys.argv[0]) try: oldfile=sys.argv[1] except: usage(progname) sys.exit(1) # try: fsold=open(oldfile) except: cannotopenfile(oldfile) sys.exit(1) # newfile=genfilename(oldfile,"new") if newfile=="": print "Cannot save backup file" sys.exit(1) # try: fsnew=open(newfile,"w") except: cannotopenfile(newfile) sys.exit(1) # if convertfile(fsold,fsnew)==False: fsold.close() fsnew.close() print "Convert file %s faild" % (oldfile) sys.exit(1) # fsold.close() fsnew.close() bakfile=genfilename(oldfile,"bak") if bakfile=="": print "Cannot create bakup file, so utf8-file is %s" % (newfile) sys.exit(1) # os.rename(oldfile,bakfile) os.rename(newfile,oldfile) print "Convert %s success, save backup file in %s" % (oldfile,bakfile)
วิธีใช้ก็สั่ง d.tis2utf FILENAME.TXT
จะได้ FILENAME.TXT เป็นรหัส utf8 และ FILENAME.TXT.bak เป็นไฟล์เก่า
(ใครเขียนไพธอนเก่ง ๆ ฝากแนะนำด้วยครับ อยากเขียนให้กระชับ และดูง่าย)
มีงานต้องแปลงไฟล์กลับ เลยเขียนโค๊ดอีกทีนึง
( งานที่ทำคือ สันติรำลึก เป็นการแปลงไฟล์กลับจาก Word มาเป็น HTML แบบ tis-620 )
#!/usr/bin/env python # CONVERT FILE CONTENT FROM utf8 TO tis620 import sys,os # GLOBAL VARS decodec="utf8" encodec="cp874" # # VARIABLE decodec AND encodec CAN BE CHANGED. # ALL STANDARD ENCODINGS IS: # ascii, big5, big5hkscs, cp037, cp424, cp437, cp500, cp737, cp775, cp850, # cp852, cp855, cp856, cp857, cp860, cp861, cp862, cp863, cp864, cp865, # cp866, cp869, cp874, cp875, cp932, cp949, cp950, cp1006, cp1026, cp1140, # cp1250, cp1251, cp1252, cp1253, cp1254, cp1255, cp1256, cp1257, cp1258, # euc_jp, euc_jis_2004, euc_jisx0213, euc_kr, gb2312, gbk, gb18030, hz, # iso2022_jp, iso2022_jp_1, iso2022_jp_2, iso2022_jp_2004, iso2022_jp_3, # iso2022_jp_ext, iso2022_kr, latin_1, iso8859_2, iso8859_3, iso8859_4, # iso8859_5, iso8859_6, iso8859_7, iso8859_8, iso8859_9, iso8859_10, # iso8859_13, iso8859_14, iso8859_15, johab, koi8_r, koi8_u, mac_cyrillic, # mac_greek, mac_iceland, mac_latin2, mac_roman, mac_turkish, ptcp154, # shift_jis, shift_jis_2004, shift_jisx0213, utf_16, utf_16_be, utf_16_le, # utf_7, utf_8, utf_8_sig # # SEE http://docs.python.org/lib/standard-encodings.html # FOR MORE INFORMATION. def usage(progname): print "Usage: %s FILE" % (progname) print "Convert FILE from %s to %s, save old file in FILE.bak" % (decodec,encodec) def cannotopenfile(filename): print "Cannot open file %s" % (filename) def genfilename(filename="",ext="new"): if filename=="": return "" # if ext.lower()=="new": ext="new" # if ext.lower()!="new" and ext.lower()!="bak": ext="bak" # if os.path.exists(filename+"."+ext): i=0 while os.path.exists(filename+"."+ext+str(i)) and (i < 1000): i=i+1 # if i>999: return "" # return filename+"."+ext+str(i) else: return filename+"."+ext # def replace_invalid_char(line,utf_char,tis_char): return line.replace(utf_char,tis_char) def convertline(line): # CHECK INVALID CHAR line=replace_invalid_char(line,"\xe2\x80\x98","'") line=replace_invalid_char(line,"\xe2\x80\x99","'") line=replace_invalid_char(line,"\xe2\x80\x9c",'"') line=replace_invalid_char(line,"\xe2\x80\x9d",'"') line=replace_invalid_char(line,"\xe2\x80\xa6","...") line=replace_invalid_char(line,"\xef\x9c\x8f","\xe0\xb8\x8d") #YOR YING line=replace_invalid_char(line,"\xef\x9c\x9a","\xe0\xb8\xba") #PINTU line=replace_invalid_char(line,"\xe2\x80\x93","-") line=replace_invalid_char(line,"\xef\x82\xae","->") line=replace_invalid_char(line,"\xef\xa3\x82","") # UNKNOWN line=replace_invalid_char(line,"\xef\xa3\x83","") # UNKNOWN return line.decode(decodec).encode(encodec) def convertfile(fs_old, fs_new): for eachline in fs_old: newline=convertline(eachline) # try: # newline=convertline(eachline) # except: # newline=eachline # # fs_new.write(newline) # return True if __name__=="__main__": progname=os.path.basename(sys.argv[0]) try: oldfile=sys.argv[1] except: usage(progname) sys.exit(1) # try: fsold=open(oldfile) except: cannotopenfile(oldfile) sys.exit(1) # newfile=genfilename(oldfile,"new") if newfile=="": print "Cannot save backup file" sys.exit(1) # try: fsnew=open(newfile,"w") except: cannotopenfile(newfile) sys.exit(1) # if convertfile(fsold,fsnew)==False: fsold.close() fsnew.close() print "Convert file %s faild" % (oldfile) sys.exit(1) # fsold.close() fsnew.close() bakfile=genfilename(oldfile,"bak") if bakfile=="": print "Cannot create bakup file, so utf8-file is %s" % (newfile) sys.exit(1) # os.rename(oldfile,bakfile) os.rename(newfile,oldfile) print "Convert %s success, save backup file in %s" % (oldfile,bakfile)
โค๊ดยังไม่เรียบร้อยดี แต่ขอแปะโค๊ดไว้ก่อน
จาก ThaiLinuxCafe: แก้ไข ID3Tags ใน mp3 ให้ใช้กับ Amarok 1.4 และ Noatun
ใช้แปลงไฟล์ mp3 จากการเข้ารหัสแบบ cp874 มาเป็นยูนิโค๊ด utf8
ตัวโปรแกรมจะแปลงชื่อไฟล์และ ID3 Tags ในไฟล์
ถ้าจะนำไปใช้ โปรดใช้ด้วยความระมัดระวัง
เพราะไม่ได้เขียนฟังก์ชั่นการเตือนไว้ด้วยครับ
ตั้งชื่อไฟล์ว่า d.tags2utf8
$ sudo touch /usr/local/bin/d.tags2utf8
$ sudo chmod 755 /usr/local/bin/d.tags2utf8
$ sudo vi /usr/local/bin/d.tags2utf8
#!/usr/bin/env python """ Convert ID3 Tags from CP874 to UTF8 and auto rename file recursive into subdirectory. Coding from Khun pong_th's article at: http://www.thailinuxhosting.com/yabbse/index.php?board=6;action=display;threadid=9429 """ # 49-11-18 ADD ID3V1 CONVERSION import os # GLOBAL VARIABLE skel=".mp3" decodec="cp874" encodec="utf8" def d_passcheck_invalid_char(string): if string=="": return False i=0 for ch in ['\x00','\xff']: i=i+1 if ch in string: print "%d CH IN STRING %s" % (i,string) return False # # return True def d_convert(string): string=string.split('\00')[0] # TRIM '\x00' CHARACTER if d_passcheck_invalid_char(string): return string.decode(decodec).encode(encodec) else: return string # def d_rename2utf8(dir,strname): """Convert coding from TIS-620 to UTF-8.""" for i in strname: if i>'\x7f': # CHECK FOR UTF STRING if i=='\xe0': return strname newstr=d_convert(strname) print "rename file: %s -> %s" % (strname,newstr) os.rename(dir+os.sep+strname, dir+os.sep+newstr) return newstr # # return strname def d_getID3V1data(fstream): """Get old tags format From: http://www.faqs.org/docs/diveintopython/fileinfo_files.html""" # ID3V1 format (from:http://www.id3.org/id3v2-00.txt) # Field Length Offsets # Tag 3 0-2 # Songname 30 3-32 # Artist 30 33-62 # Album 30 63-92 # Year 4 93-96 # Comment 30 97-126 # Genre 1 127 fstream.seek(-128,2) tags=fstream.read(128) fstream.seek(0) return [tags[3:32],tags[33:62],tags[63:92],tags[93:96],tags[97:126],tags[127]] def d_write_eachtags(fstream,tagstitle,tagsdata): fstream.write(tagstitle+'\x00\x00\x00') fstream.write(chr(len(tagsdata)+1)+'\x00\x00\x03') fstream.write(tagsdata) return def d_change_tags2utf8(filename): """Change ID3 Tags content from cp874 to utf8""" fstream=open(filename,"r+b") ispass=False if fstream.read(3)=="ID3": # READ ID3 TAGS DATA fstream.read(6) nbyte=ord(fstream.read(1)) ltags=[] ctagsname=fstream.read(4) while ctagsname in ["TIT2","TPE1","TALB"]: fstream.read(3) ntagsbyte=ord(fstream.read(1)) fstream.read(3) ctagscontent=fstream.read(ntagsbyte-1) ltags.append([ctagsname,ntagsbyte,ctagscontent]) ctagsname=fstream.read(4) # # CONVERT TO utf8 nnewbyte=0 for eachtags in ltags: if not '\xe0' in eachtags[2]: eachtags[2]=d_convert(eachtags[2]) if not ispass: ispass=True # else: print "File %s already in utf8 format." % (filename) fstream.close() return False # eachtags[1]=len(eachtags[2]) nnewbyte=nnewbyte+4+3+3+eachtags[1]+1 # # WRITE BACK CONVERTED DATA fstream.seek(9) fstream.write(chr(nnewbyte)) for eachtags in ltags: d_write_eachtags(fstream,eachtags[0],eachtags[2]) # if nnewbyte<nbyte: for i in range(nbyte-nnewbyte): fstream.write('\x00') # # if ispass: print "Id3 Tags: file %s converted" % (filename) # else: # CHECK FOR ID3V1 fstream.seek(0) wholefile=fstream.read(-1) if 'TAG' in wholefile: ltags=d_getID3V1data(fstream) nnewbyte=0 for i in range(len(ltags)): ltags[i]=d_convert(ltags[i]) if d_passcheck_invalid_char(ltags[i]): nnewbyte=nnewbyte+len(ltags[i])+4+3 # # fstream.close() fstream=open(filename,"w") fstream.write('ID3'+'\x04\x00\x00\x00\x00\x08') fstream.write(chr(nnewbyte)) # Songname 30 3-32 :TIT2 # Artist 30 33-62 :TPE1 # Album 30 63-92 :TALB # Year 4 93-96 :TDOR # Comment 30 97-126 :COMM # Genre 1 127 :---- print ltags[0] if d_passcheck_invalid_char(ltags[0]): d_write_eachtags(fstream,'TIT2',ltags[0]) if d_passcheck_invalid_char(ltags[1]): d_write_eachtags(fstream,'TPE1',ltags[1]) if d_passcheck_invalid_char(ltags[2]): d_write_eachtags(fstream,'TALB',ltags[2]) if d_passcheck_invalid_char(ltags[3]): d_write_eachtags(fstream,'TDOR',ltags[3]) if d_passcheck_invalid_char(ltags[4]): d_write_eachtags(fstream,'COMM',ltags[4]) # DISCARD Genre TAGS for i in range(1016): fstream.write('\x00') # fstream.write(wholefile) else: print "ID3 Tags not found in %s" % (filename) # # fstream.close() def process_dir(dir): """Process all files in the folder""" for f in os.listdir(dir): file = dir + os.sep + f if os.path.isdir(file): print "Enter directory %s" % (file) process_dir(file) print "---exit directory %s" % (file) # if f[-4:]==skel: # DO CONVERT FILENAME file=d_rename2utf8(dir,file) # DO CHANGE ID3 TAGS d_change_tags2utf8(file) # # return def main(): """main routine""" process_dir('.') return if __name__=='__main__': main()
มีโจทย์อยู่คือ
เวลาไปเที่ยวหรือมีงานที่ต้องถ่ายภาพเป็นจำนวนมาก เกินการ์ดหน่วยความจำที่มีอยู่
เวลาการ์ดเต็ม ก็ต้องถ่ายออกมาเก็บไว้ในโน๊ตบุ๊ก
ปัญหาคือเวลาจะดูภาพจากโน๊ตบุ๊ก ซึ่งสเปคเครื่องต่ำมาก โหลดไฟล์ภาพใหญ่ ๆ ไม่ไหว มันจะดูได้ช้ามาก ๆ ดูภาพ 10 ภาพ ใช้เวลาไป 15 นาที
ทางแก้คือคัดลอกไฟล์ภาพมาแปลงเป็นไฟล์เล็ก (อาจจะแปลงด้วย gimp หรือ imagemagick ก็ได้) แต่เนื่องจากสเปคเครื่องต่ำมาก แปลงไฟล์แต่ละครั้งกินเวลาเป็นชั่วโมง ไม่ทันต่อเหตุการณ์
ทางออกอีกทางคือไปแตกเอาไฟล์ JPG อันเล็ก ที่ซ่อนอยู่ภายใต้ไฟล์ตัวจริงซึ่งใหญ่มาก เอาออกมาแทน วิธีนี้จะทำงานได้รวดเร็วกว่ามาก
เคยเขียน C ไว้เป็นไฟล์เล็ก ๆ บนวินโดวส์ แต่เที่ยวนี้ผมลองเอามาคอมไพล์บนลินุกส์ ปรากฎว่าคอมไพล์ไม่ผ่าน และภาษา C ก็ลืมสิ้นแล้ว อย่ากระนั้นเลย พึ่งไพธอนดีกว่า
ผมใช้กล้อง Canon และเราจะแตกไฟล์ JPG อันเล็ก ซึ่งเป็นไฟล์ลำดับที่ 3 ที่ซ่อนอยู่ในไฟล์ใหญ่ เลยตั้งชื่อโปรแกรมว่า canon3.py
เวลาใช้งานก็สั่ง
$ ./canon3.py FILENAME.JPG
ก็จะแตกไฟล์ FILENAME.JPG ไปเป็น canon3/FILENAME.JPG
หรือถ้าสั่ง
$ ./canon3.py
เฉย ๆ
ก็จะควานหาทุกไฟล์ในไดเรกทอรี่ที่เป็น JPG หรือ CRW และแตกไฟล์ย่อยออกมาใส่ในไดเรกทอรี่ย่อย canon3
โดยโปรแกรมจะคัดลอกเอาข้อมูล Exif ของกล้องติดไปด้วย (แต่ข้อมูลขนาดภาพใน Exif จะผิดจากความเป็นจริง ขี้เกียจแก้แล้วอ่ะ)
ทดลองแล้วความเร็วใสการแตกไฟล์ดีมาก (ไพธอนนี่ดีกว่าที่คิดเยอะเลย)
โค๊ดมีดังนี้
#!/usr/bin/env python # EXTRACT THIRD jpg FILE IN Canon CAMERA # # jpg file format # start with: FF D8 FF E1 NN NN ... (contain exif data to byte NN NN) # first JPG : FF D8 ... FF D9 (thumbnail) # second JPG: FF D8 ... FF D9 (real JPG) # third JPG : FF D8 ... FF D9 (hidden small JPG) # SKIP TO 2/3 OF FILE THEN SEARCH FOR THIRD #FFD8 TO #FFD9 # import sys, os, string # VAR tag_beg="\xff\xd8" tag_end="\xff\xd9" subdir="canon3" file_skel=[".JPG",".jpg",".jpeg"] dir_skel=["DCIM","CANON"] # PROCEDURE def process_file(filename): for fskel in file_skel: if fskel in filename: #OPEN FILE IN BINARY MODE try: f = open(filename, 'rb') except: print 'Could not open file to read !', filename sys.exit(3) if f is None: print 'Error opening file ', filename sys.exit(3) # COPY EXIF HEADING TO NEW FILE IN SUBDIR canon3 basename=os.path.basename(filename) print "%s -> %s/%s" % (basename,subdir,basename) if not os.path.exists(subdir): os.mkdir(subdir) # f_new=open(os.path.join(subdir,basename),"wb") # SEEK FF D8 FF E1 NN NN f.seek(4) offset_low=f.read(1) offset_hi=f.read(1) no_of_byte=ord(offset_low)*256+ord(offset_hi)-4 ## print "no_of_byte=%i" % no_of_byte # WRITE TO NEW FILE f_new.write("\xff\xd8\xff\xe1"+offset_low+offset_hi+f.read(no_of_byte)) # SEEK FOR THE REST 3RD JPG FILE # INCREASE SPEED BY SKIP TO 2/3 OF FILE f.seek(os.path.getsize(filename) * 2/3) is_found=False for i in f: if tag_beg in i: is_found=True # WRITE THIRD JPG TO NEW FILE f_new.write(tag_beg+i.split(tag_beg)[1]) for j in f: if tag_end in j: f_new.write(j.split(tag_end)[0]+tag_end) break f_new.close() else: f_new.write(j) # # break # # f.close() if not is_found: print "third JPG file not found." # # # def process_dir(dirname): ## print "dirname=%s" % dirname for dskel in dir_skel: if dskel in dirname: print "enter %s" % dirname os.chdir(dirname) filelist=os.listdir(".") for i in filelist: if os.path.isdir(i): process_dir(i) else: process_file(i) # # print "exit %s" % dirname os.chdir("..") # # # MAIN PROG def main(): if len(sys.argv) < 2: # PROCESS ALL FILE&DIR for filelist in os.listdir("."): if os.path.isdir(filelist): process_dir(filelist) else: process_file(filelist) # # else: filename = os.path.abspath(sys.argv[1]) if os.path.isdir(filename): process_dir(filename) else: process_file(filename) # # print "finished" if __name__=="__main__": main()
แถมอีกนิด
ถ้าจะให้หมุนภาพอัตโนมัติด้วย ต้องใช้แพคเกจ jhead
$ sudo apt-get install jhead
$ jhead -autorot *
ลดขนาดรูปพร้อมกับหมุนภาพอัตโนมัติ ทำลึกลงไปทุกไดเรคทอรี่
$ sudo apt-get install imagemagick jhead
$ sudo touch /usr/local/bin/d.canon5.py
$ sudo chmod 0755 /usr/local/bin/d.canon5.py
$ sudo vi /usr/local/bin/d.canon5.py
#!/usr/bin/env python import sys, os, string # VAR subdir="canon3" file_skel=[".JPG",".jpg",".jpeg"] dir_skel=["DCIM","CANON","100EOS5D"] # PROCEDURE def process_file(filename): for fskel in file_skel: if fskel in filename: if not os.path.exists(subdir): os.mkdir(subdir) # print "convert %s -> %s" % (filename, os.path.join(subdir,filename)) os.system('convert -resize 40%'+" %s %s" % (filename,os.path.join(subdir,filename))) os.system("jhead -autorot %s" % (os.path.join(subdir,filename))) # # def process_dir(dirname): ## print "dirname=%s" % dirname for dskel in dir_skel: if dskel in dirname: print "enter %s" % dirname os.chdir(dirname) filelist=os.listdir(".") for i in filelist: if os.path.isdir(i): process_dir(i) else: process_file(i) # # print "exit %s" % dirname os.chdir("..") # # # MAIN PROG def main(): if len(sys.argv) < 2: # PROCESS ALL FILE&DIR for filelist in os.listdir("."): if os.path.isdir(filelist): process_dir(filelist) else: process_file(filelist) # # else: filename = os.path.abspath(sys.argv[1]) if os.path.isdir(filename): process_dir(filename) else: process_file(filename) # # print "finished" if __name__=="__main__": main()
ได้ลองใช้ ufraw แล้วรู้สึกว่าสีสวยกว่า dcraw
ติดตั้งด้วย
$ sudo aptitude install ufraw
ทำงานแบบบรรทัดคำสั่ง
$ cd /PATH/TO/IMAGE
$ mkdir jpg
$ for i in *cr2; do
ufraw-batch \
--wb=camera \
--exposure=auto \
--out-type=jpeg \
--compression=96 \
--out-path=./jpg \
$i
done
เป็นการทำงานกับไฟล์ภาพนามสกุล cr2 โดยให้ผลิตไฟล์ jpg ไปอยู่ในโฟลเดอร์ชื่อ jpg
เอามาจาก
Linux Photography : Workflow (3) - quick RAW converting batch
เพิ่งได้เริ่มหัดถ่ายภาพแบบ Raw ของกล้อง Canon มีนามสกุลเป็น .CR2
$ sudo aptitude install gimp gimp-ufraw
$ sudo aptitude install dcraw
$ for i in *.CR2; do dcraw -c -q 3 -B 2 4 -w $i | cjpeg -quality 100 > `basename $i .CR2`.jpg; done
เอามาจาก How to convert raw cr2 pictures with linux, and merge pictures by date and Exif data with jhead
ทำตัวอย่างการค้นเนื้อหาไพธอน (20%)
เสร็จประมาณ 10% แล้ว
อยากให้ทำงานได้แบบ php help จัง
update
ต่อเนื่องจากครั้งก่อนเรื่อง imagemagick: ทำ annotate ไฟล์ tif
มีข้อบกพร่องเล็กน้อยคือ เวลาที่ inkscape นำเข้าไฟล์ tif จะกำหนดขนาดเป็นพิกเซลตามไฟล์ tif
ซึ่งจะมีขนาดใหญ่กว่า A4 พอสมควร (เทียบที่ค่าปริยายของ inkscape คือ 90dpi)
ถ้าจะแก้ไขด้วยการปรับหน้ากระดาษและลดขนาดภาพใน inkscape ทุกครั้ง ก็ดูไม่ค่อยสะดวก
และจะแก้ไขด้วยการลดขนาดพิกเซลของภาพ ก็เสียดายความละเอียด
จึงทดลองแปลงสคริปต์ไฟล์เดิม จากการใช้เชลล์สคริปต์ของ bash มาเป็นไพธอนแทน ทั้งนี้เพียงเพื่อหาขนาดพิกเซลของภาพเท่านั้น (แทบไม่ได้ใช้ความสามารถจริง ๆ ของไพธอนเลย)
วิธีการใช้แบบโกง ๆ หน่อย คือในครั้งแรกที่แตกไฟล์ เราสร้างไฟล์ svg ซึ่งเป็นไฟล์ xml ขึ้นมาเองเลย
โดยให้เขียนค่าพิกเซลที่เหมาะสม คือลดหรือเพิ่มขนาดให้ไฟล์ภาพลงบนความกว้างของหน้ากระดาษ A4 ได้
อย่าลืมต้องติดตั้งแพกเกจที่ต้องการก่อน
$ sudo aptitude install imagemagick inkscape evince python python-imaging
เนื้อไฟล์ตั้งชื่อว่า d.tifannotate.py มีดังนี้
$ sudo vi /usr/local/bin/d.tifannotate.py
#!/usr/bin/env python # ANNOTATE MULTIPLE PAGES TIF FILE # PREREQUISITE: inkscape imagemagick evince python-imaging import sys import os import commands try: import Image except: print "Missing PIL library, require packages python-imaging." sys.exit(1) #PARAMETER overwrite_new_file = True #OVERWRITE image-new.tif __width = 744.09448819 #DEFAULT A4 SIZE IN PIXEL __height = 1052.3622047 # #REQUIRE PACKAGES packages = ( ("inkscape","inkscape"), ("convert","imagemagick"), ("evince","evince") ) for i in range(len(packages)): exec("status, %s = commands.getstatusoutput('which %s')" % (packages[i][0], packages[i][0]) ) if status != 0: print "Missing command %s, require packages %s." % (packages[i][0], packages[i][1],) sys.exit(1) #GLOBAL VARS err_open = "Cannot open file %s." #PROCEDURE def usage(progname): print "Annotate multiple pages TIF image." print "Usage: %s IMGFILE PAGES..." % (progname) print "Ex1 : %s image.tif 0 2 = Edit image.tif page 0 and page 2." % (progname) def errmsg(msg, *arg): if not '%s' in msg: return msg if type(arg)!=type([]): return msg % (arg) lmsg = msg.split('%s') for i in range(len(arg)): lmsg[1] = lmsg[0] + str(arg[i]) + lmsg[1] lmsg.pop(0) return '%s'.join(lmsg) def genfilename(filename="",tail="new",overwrite=True): if filename=="": return "" if tail.lower()=="new": tail="new" if tail.lower()!="new" and tail.lower()!="bak": tail="bak" name,ext = os.path.splitext(filename) if not overwrite and os.path.exists(name+'-'+tail+ext): i=0 while os.path.exists(name+'-'+tail+str(i)+ext) and (i < 1000): i=i+1 if i>999: return "" return name+"-"+tail+str(i)+ext else: return name+'-'+tail+ext def createsvg(tifname): img = Image.open(tifname) width, height = img.size newwidth = __width newheight = float(height)/width*newwidth file, ext = os.path.splitext(tifname) f = open(file+'.svg', 'w') f.write("""\ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <!-- Created with Inkscape (http://www.inkscape.org/) --> <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0" width="%s" height="%s" id="svg2"> <defs id="defs5" /> <image xlink:href="%s" x="0" y="0" width="%s" height="%s" id="image9" /> </svg>""" % (__width, __height, tifname, newwidth, newheight,)) f.close() return #MAIN PROGRAM def main(tiffile, pages): newfile=genfilename(tiffile,"new",overwrite_new_file) if newfile=="": print "Cannot save backup file." sys.exit(1) #SPLIT FILE name,ext = os.path.splitext(tiffile) dirname = name+'~' if not os.path.exists(dirname): os.mkdir(dirname) if not os.path.exists(os.path.join(dirname, name+'0'+ext)): os.system('%s %s %s' % (convert, tiffile, os.path.join(dirname,name)) +'%d'+ ext) #CREATE svg FILE IN SUBDIR os.chdir(dirname) filelist = [i for i in os.listdir(os.path.curdir) if i[-(len(ext)):]==ext ] for i in filelist: n,e = os.path.splitext(i) c = n+'.svg' if not os.path.exists(c): createsvg(i) #EDIT/ADD ANNOTATE for i in pages: if int(i) < len(filelist): c = str(i) os.system('%s %s.svg' % (inkscape, name+c,)) os.system('%s -e %s.png %s.svg' % (inkscape, name+c, name+c,)) else: print "Invalid page %s." % (i,) #MERGE BACK TO NEW NAME:convert img0.svg ... -adjoin -compress lzw ../img-new.tif allfile = ' '.join( [i for i in os.listdir(os.path.curdir) if i[-(4):]=='.png' ] ) os.system('%s %s -adjoin -compress lzw %s' % (convert, allfile, os.path.join(os.path.pardir,newfile), )) #CHDIR BACK os.chdir(os.path.pardir) print "Annotate %s success, save new file in %s. Viewing with %s" % (tiffile,newfile,evince) os.system('%s %s' % (evince, newfile,)) if __name__=="__main__": #sys.argv=[progname tiffile 0 1 2] progname=os.path.basename(sys.argv[0]) try: tiffile=sys.argv[1] except: usage(progname) sys.exit(1) if not os.path.isfile(tiffile): print errmsg(err_open, tiffile) sys.exit(1) if len(sys.argv)<3: usage(progname) sys.exit(1) pages = sys.argv[2:] main(tiffile, pages)
เรียกใช้เหมือนเดิมคือ
$ d.tifannotate.py IMAGE.tif 0 1
ได้ผลออกมาเป็น IMAGE-new.tif
หมายเหตุ
ถ้าต้องการพิมพ์จาก inkscape ต้องติดตั้งแพกเกจ cupsys-bsd ด้วย
$ sudo aptitude install cupsys-bsd
update 50-09-11
ลองเขียนสคริปต์ลบแสปม
ใช้กับบอร์ด yabbse กับ smf
ของ smf ยังไม่เสร็จ บันทึกเอาไว้เพื่อลองดูผลเท่านั้น
ต้องเปลี่ยนแปลงสคริปต์ตามธีมที่ใช้ด้วย
*** สคริปต์นี้ใช้กับ thailinuxhosting.com/yabbse เท่านั้น เพราะใส่โค๊ดที่แก้ปัญหาบอร์ดไว้ด้วยครับ
#!/usr/bin/env python # -*- coding: utf-8 -*- user = "wd" password = "mypassword" enc_password = "XXXXXXXXXX" # *** GET ENCRYPTED PASSWORD FROM BROWSER COOKIE site = "http://www.thailinuxhosting.com/yabbse" #"http://www.thaitux.info/smf" board = "yabbse" # "smf", "yabbse" charset = "tis620" # "utf8", "tis620" max_loop = 5 # = RECENT LIST OF BOARD root = "/home/wd/spam" backup_file = root+"/thailinuxhosting-bak.txt" spamtext_file = root+"/spamlist.txt" cookie_file = root+"/thailinuxhosting-cookie" import sys import os import time ##### PRE RUN FOR RETRIEVE COOKIE ##### import urllib2 import cookielib login = "/index.php?action=login2;user=%s;passwrd=%s;cookielength=302400" % (user, password,) cj = cookielib.MozillaCookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) sock = opener.open(site+login) cj.save(cookie_file, ignore_discard=True, ignore_expires=True) sock.close() ####################################### spamlist = [] def decoding(txt): if charset == "tis620": return txt.decode("utf8").encode("tis620") elif charset == "utf8": return txt else: print "Error, CHARSET is not defined" sys.exit[0] def search_line(txt, l, occur=1): for i in range(len(l)): if txt in l[i]: if occur > 1: occur=occur-1 else: return i return -1 def get_msgid(url): if board == "smf": # ...#msgXX return url.split("#msg")[-1] elif board == "yabbse": # ...;start=XX return url.split(";start=")[-1] def check_spam(txt): global spamlist for i in spamlist: if i in txt: return True, i return False, '' def save_backup(txt): f = open(backup_file,'a') f.write(txt+'\n\n\n') f.close() return def die_board(): print "board not exist" sys.exit[0] if board == "smf": # recent_str = "กระททู้เมมื่อเร็วๆ นนี้" recent_str = "\xe0\xb8\x81\xe0\xb8\xa3\xe0\xb8\xb0\xe0\xb8\x97\xe0\xb8\xb9\ \xe0\xb9\x89\xe0\xb9\x80\xe0\xb8\xa1\xe0\xb8\xb7\xe0\xb9\x88\xe0\xb8\xad\ \xe0\xb9\x80\xe0\xb8\xa3\xe0\xb9\x87\xe0\xb8\xa7\xe0\xb9\x86 \xe0\xb8\x99\ \xe0\xb8\xb5\xe0\xb9\x89" elif board == "yabbse": # recent_str = "โพสต์เมมื่อเร็วๆนนี้" recent_str = "\xe0\xb9\x82\xe0\xb8\x9e\xe0\xb8\xaa\xe0\xb8\x95\xe0\xb9\x8c\ \xe0\xb9\x80\xe0\xb8\xa1\xe0\xb8\xb7\xe0\xb9\x88\xe0\xb8\xad\xe0\xb9\x80\ \xe0\xb8\xa3\xe0\xb9\x87\xe0\xb8\xa7\xe0\xb9\x86\xe0\xb8\x99\xe0\xb8\xb5\xe0\xb9\x89" else: die_board() #LOAD SPAM DATA if not os.path.exists(spamtext_file): f = open(spamtext_file,'w') f.close() f = open(spamtext_file) for i in f: if i!='' and len(i)>3: spamlist.append(decoding(i.strip())) f.close() recent_str = decoding(recent_str) #INIT COOKIE & OPENER cj = cookielib.MozillaCookieJar() cj.load(cookie_file) opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) loop_count = 0 url_list_pair = [] #SOLVE yabbse ONLY INDEX TO LAST MESSAGE, SO WE CREATE OUR OWN while loop_count < max_loop: #FIRST PAGE sock = opener.open(site) #HACK: SOLVE yabbse'S BOARD COOKIE ERROR cj._cookies['thailinuxhosting.com']['/yabbse']['YaBBSE140usernamev14'].value = user cj._cookies['thailinuxhosting.com']['/yabbse']['YaBBSE140passwordv14'].value = enc_password html = sock.read() sock.close() l = html.split('\n') #SESSIONID if board == "smf": sstr = "sesc=" line = search_line(sstr, l) if line < 0: sys.exit[0] session_id = l[line].split(sstr)[1].split('">')[0] else: session_id = "" #SEARCH FOR RECENT POST sstr = recent_str line = search_line(sstr, l) if line < 0: sys.exit[0] if board == "smf": url = l[line+9+loop_count].split('<a href="')[1].split('">')[0] author = "" date_submitted = "" elif board == "yabbse": url = l[line+4+loop_count].split('<td valign="top"><a href="')[1].split('">')[0] url_list = [ i[0] for i in url_list_pair ] # SOLVE yabbse MESSAGE INDEX if url in url_list: i = url_list.index(url) url_list_pair[i][1] += 1 index_dec = url_list_pair[i][1] else: url_list_pair.append([url,0]) index_dec = 0 # tmp = 'โดย ' tmp = decoding('\xe0\xb9\x82\xe0\xb8\x94\xe0\xb8\xa2 ') author = l[line+4+loop_count].split(tmp)[1].split('</td>')[0] else: die_board() msgid = get_msgid(url) sock = opener.open(url) html = sock.read() sock.close() l = html.split('\n') #PARSE HTML is_spam = False spam_keyword = '' if board == "smf": sstr = "msg_%s" % (msgid,) line = search_line(sstr, l) elif board == "yabbse": sstr = '<hr width="100%" size="1" class="windowbg3">' count = (int(msgid)-index_dec) % 20 + 1 # 20 MESSAGES PER PAGE - yabbse INDEX DECREMENT print 'loop=',loop_count,' /// count=',count line = search_line(sstr, l, count) tmp = decoding("javascript:DoConfirm('") try: delete_url = l[line-3].split(tmp)[1].split("','")[1].split("""');"><img src""")[0] date_submitted = l[line-4].split('</B> ')[1].split(' »')[0] title = l[line-5].split('<B>')[1].split('</b>')[0] process_line = line+1 is_spam, spam_keyword = check_spam(l[process_line]) if is_spam: print 'line=',line,' /// l[line-3]=', l[line-3] print 'delete_url=',delete_url print "is_spam=",is_spam," /// keyword=",spam_keyword," /// line=",l[process_line] except: is_spam = False else: die_board() if is_spam: if board == "smf": pass elif board == "yabbse": save_backup('delete url: '+delete_url+\ '\nspam keyword: '+spam_keyword+\ '\nscan date: '+time.ctime(time.time())+\ '\ntitle: '+title+\ '\nauthor: '+author+\ '\nsubmitted date: '+date_submitted+\ '\n'+l[process_line]) sock = opener.open(delete_url) sock.close() url_list = [ i[0] for i in url_list_pair ] # RESET yabbse MESSAGE INDEX if url in url_list: i = url_list.index(url) url_list_pair.remove(url_list_pair[i]) else: loop_count = loop_count+1
เขียนสคริปต์ไว้เพื่อให้เปลี่ยนรุ่น Drupal ง่าย ๆ เผื่อมีหลายไซต์
สมมุติว่าไดเรกทอรี่ติดตั้งอยู่ที่ /var/www/drupal
URL คือ http://www.example.com
ฐานข้อมูลชื่อ DATABASE_NAME
ผู้ใช้ชื่อ ADMIN และรหัสผ่านคือ ADMIN_PASSWORD
อย่าลืมต้องให้ ADMIN อ่านได้เท่านั้น เพราะจะมีรหัสผ่านอยู่ในสคริปต์
$ cd /var/www/drupal
$ touch sed.py
$ chmod 700 sed.py
ขั้นตอน upgrade
$ cd /var/www/drupal
$ wget http://ftp.drupal.org/files/projects/drupal-X.X.tar.gz
$ tar xfx drupal-X.X
$ cd drupal-X.X
$ cp -xa * ..
$ cd ..
$ rm -rf drupal-X.X
$ ./sed.py
### DO UPDATE AT www.example.com/update.php
### SET BACK
$ vi update.php
.. $access_check = TRUE; #$access_check = FALSE; ...
เนื้อไฟล์ sed.py มีดังนี้
$ vi sed.py
#!/usr/bin/env python # cd /var/www/drupal # cp -xa ../drupal-5.3/* . # ./sed.py # ---> http://www.example.com/update.php # vi update.php #SET $access_check = TRUE; import os import sys db_url = "$db_url = 'mysql://ADMIN:ADMIN_PASSWORD@localhost/DATABASE_NAME';" base_url = "$base_url = 'http://www.example.com'; // NO trailing slash!" basedir = os.path.abspath(os.curdir); #------------------------------------------------------------------------- def sed_file(file,dict_txt): filename = os.path.basename(file) dirname = os.path.abspath(file) bakfile = file+'.bakbak' os.rename(file,bakfile) f_old = open(bakfile) f_new = open(file,'w') txt_old = [i for i in dict_txt] txt_new = [i for i in dict_txt.values()] print txt_old for i in f_old: line = i.strip() if line in txt_old: n = txt_old.index(line) print 'old=',line print 'new=',txt_new[n] f_new.write(txt_new[n]+'\n') else: f_new.write(i) f_new.close() f_old.close() #FIRST FILE sites/default/setting.php file = os.path.join(basedir,'sites/default/settings.php') tmp_db = "$db_url = 'mysql://username:password@localhost/databasename';" tmp_base = "# $base_url = 'http://www.example.com'; // NO trailing slash!" dict_txt = {\ tmp_db: '#'+tmp_db+'\n'+db_url, \ tmp_base: tmp_base+'\n'+base_url \ } sed_file(file,dict_txt) #SECOND update.php file = os.path.join(basedir,'update.php') dict_txt = {\ "$access_check = TRUE;":\ "#$access_check = TRUE;\n$access_check = FALSE;"\ } sed_file(file,dict_txt)
มีงานที่จะต้องทำไฟล์เป็น 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
สมมุติว่า lowerLeft-x เป็น x0, lowerLeft-y เป็น y0, upperRight-x เป็น x1, upperRight-y เป็น y1 ตามลำดับ
$ ./croppdf.py "-14 -14 609 856" TEMPFILE.pdf OUTFILE.pdf
$ ./croppdf.py "-14 -14 434 609" TEMPFILE.pdf OUTFILE.pdf
$ ./croppdf.py "87 247 508 842" TEMPFILE.pdf OUTFILE.pdf
$ ./croppdf.py "8 -25 604 817" TEMPFILE.pdf OUTFILE.pdf
$ ./croppdf.py "0 -25 595 817" TEMPFILE.pdf OUTFILE.pdf
เสร็จแล้วครับ
ที่มาจริง ๆ แล้ว ต้องการค้นหาและแทนที่เอกสารในไฟล์ .doc
จึงสั่งด้วยคำสั่งว่า
$ sed -i 's/OLD/NEW/g' *.doc
ไม่ได้ผล นึกว่ารหัสเอกสารผิด เลยเป็นที่มาของสคริปต์อันนี้ คือค้นหาและแทนที่เอกสารทั้ง utf-8 และ tis-620 โดยไม่สนใจว่าเป็นเอกสารชนิดใด
(สุดท้ายปรากฎว่าไม่ได้ผล เพราะ OpenOffice ไม่ได้เก็บไว้ในรูป Text file ปกติ
ถึงจะโง่ไปแล้ว แต่บันทึกไว้หน่อยดีกว่า เผื่อได้ใช้ทีหลัง)
$ vi sed_i.py
#!/usr/bin/env python # -*- coding: utf8 -*- """Replace thai string in file""" import sys, os def usage(prog): print 'Usage: %s "old" "new" filename' % (prog) def cannotopenfile(filename): print "Cannot open file %s" % (filename) def getbakfilename(filename="", ext="bak"): if filename == "": return "" if os.path.exists(filename + "." + ext): i = 0 while os.path.exists(filename + "." + ext + str(i)) and (i < 1000): i += 1 if i > 999: return "" return filename + "." + ext + str(i) else: return filename + "." + ext # def main(old,new,filename): if not os.path.exists(filename): cannotopenfile(filename) sys.exit(0) ismod = False newdoc = [] f = open(filename) for line in f: newline1 = line.replace(old, new) newline2 = line.replace(old.decode('utf8').encode('tis620'), new.decode('utf8').encode('tis620')) if not ismod and (newline1 != line or newline2 != line): ismod = True if newline1 != line: newdoc.append(newline1) else: newdoc.append(newline2) f.close() if ismod: bakfile = getbakfilename(filename) os.rename(filename, bakfile) f = open(filename, "w") f.write('\n'.join(newdoc)) f.close() print "%s changed, save backup in %s." % (filename, bakfile) else: print "Pattern not found, no changed." if __name__ == "__main__": if len(sys.argv) < 4: usage(sys.argv[0]) sys.exit[0] else: main(*sys.argv[1:4])
ใช้งานด้วยคำสั่ง
$ ./sed_i.py "OLD" "NEW" filename
ถ้าเจอ จะแทนที่ และบันทึกไว้ในชื่อเดิม แต่สำรองไฟล์ไว้ด้วย ในชื่อ filename.bak
จากครั้งก่อนเรื่อง adodb: กับดักข้อมูล ที่ได้นำเอาโมดูล DBF Reader จากเว็บของคุณ Yusdi Santoso มาทดลองใช้งาน
เมื่อได้นำมาใช้จริง สำหรับไฟล์ dbf ของ Visual Foxpro สามารถใช้ได้ผลดีพอควร แต่สำหรับ dbf เก่า ๆ ที่เป็นของ Foxpro for Dos นั้น ปรากฎว่าไม่สามารถอ่านได้
อีกเรื่องนึงคือเรื่องชื่อไฟล์ในลินุกซ์ เมื่อใช้งานผ่าน samba (เมานต์แบบ cifs) แล้ว เข้าใจว่าไม่สามารถควบคุมได้ บางครั้งปรากฎเป็นตัวใหญ่หมด บางครั้งออกมาเป็นชื่อไฟล์ตัวใหญ่แต่นามสกุลตัวเล็ก หรือกลับกัน เราจึงมาปรับปรุงในส่วนนี้ด้วย
ได้ออกมาเป็นซอร์สไฟล์ดังนี้ครับ
$ vi dbf.py
#!/usr/bin/env python """ This is a DBF reader which reads Visual Fox Pro DBF format with Memo field. Usage: rec = readDbf('test.dbf') for line in rec: print line['name'] @author Yusdi Santoso @date 13/07/2007 """ import struct import os, os.path import sys import csv import tempfile import ConfigParser class Dbase: def __init__(self): self.fdb = None self.fmemo = None self.db_data = None self.memo_data = None self.fields = None self.num_records = 0 self.header = None self.memo_file = '' self.memo_header = None self.memo_block_size = 0 self.memo_header_len = 0 def _drop_after_NULL(self, txt): for i in range(0, len(txt)): if ord(struct.unpack('c', txt[i])[0])==0: return txt[:i] return txt def _reverse_endian(self, num): if not len(num): return 0 #OLD CODE #val = struct.unpack('<L', num) #val = struct.pack('>L', val[0]) #val = struct.unpack('>L', val) #return val[0] #wd's IMP: IMPROVE READING OLD FOXPRO MEMO try: #VFP DBF: BINARY 4 BYTES MEMO FIELD REF val = struct.unpack('<L', num) val = struct.pack('>L', val[0]) val = struct.unpack('>L', val) return val[0] except: #OLD FOXPRO DBF: STRING 10 BYTES MEMO FIELD REF val = long('0'+num.strip()) return val def _assign_ids(self, lst, ids): result = {} idx = 0 for item in lst: id = ids[idx] result[id] = item idx += 1 return result def open(self, db_name): filesize = os.path.getsize(db_name) if filesize <= 68: raise IOError, 'The file is not large enough to be a dbf file' self.fdb = open(db_name, 'rb') self.memo_file = '' #OLD CODE #if os.path.isfile(db_name[0:-1] + 't'): # self.memo_file = db_name[0:-1] + 't' #elif os.path.isfile(db_name[0:-3] + 'fpt'): # self.memo_file = db_name[0:-3] + 'fpt' #wd's IMP: SOLVE MISMATCHED UPPER/LOWER FILENAME AND EXTENSION basename = os.path.basename(db_name) dirname = os.path.dirname(os.path.join('.',db_name)) allfile=[i for i in os.listdir(dirname) if i[:-4].lower()==basename[:-4].lower()] for i in allfile: if i[:-1].lower()==basename[:-1].lower() and i[-1].lower()=='t': self.memo_file = os.path.join(dirname, i) elif i[:-3].lower()==basename[:-3].lower() and i[-3:].lower()=='fpt': self.memo_file = os.path.join(dirname, i) if self.memo_file: #Read memo file self.fmemo = open(self.memo_file, 'rb') self.memo_data = self.fmemo.read() self.memo_header = self._assign_ids(struct.unpack('>6x1H', self.memo_data[:8]), ['Block size']) block_size = self.memo_header['Block size'] if not block_size: block_size = 512 self.memo_block_size = block_size self.memo_header_len = block_size memo_size = os.path.getsize(self.memo_file) #Start reading data file data = self.fdb.read(32) self.header = self._assign_ids(struct.unpack('<B 3B L 2H 20x', data), ['id', 'Year', 'Month', 'Day', '# of Records', 'Header Size', 'Record Size']) self.header['id'] = hex(self.header['id']) self.num_records = self.header['# of Records'] data = self.fdb.read(self.header['Header Size']-34) self.fields = {} x = 0 header_pattern = '<11s c 4x B B 14x' ids = ['Field Name', 'Field Type', 'Field Length', 'Field Precision'] pattern_len = 32 for offset in range(0, len(data), 32): if ord(data[offset])==0x0d: break x += 1 data_subset = data[offset: offset+pattern_len] if len(data_subset) < pattern_len: data_subset += ' '*(pattern_len-len(data_subset)) self.fields[x] = self._assign_ids(struct.unpack(header_pattern, data_subset), ids) self.fields[x]['Field Name'] = self._drop_after_NULL(self.fields[x]['Field Name']) self.fdb.read(3) if self.header['# of Records']: data_size = (self.header['# of Records'] * self.header['Record Size']) - 1 self.db_data = self.fdb.read(data_size) else: self.db_data = '' self.row_format = '<' self.row_ids = [] self.row_len = 0 for key in self.fields: field = self.fields[key] self.row_format += '%ds ' % (field['Field Length']) self.row_ids.append(field['Field Name']) self.row_len += field['Field Length'] def close(self): if self.fdb: self.fdb.close() if self.fmemo: self.fmemo.close() def get_numrecords(self): return self.num_records def get_record_with_names(self, rec_no): """ This function accept record number from 0 to N-1 """ if rec_no < 0 or rec_no > self.num_records: raise Exception, 'Unable to extract data outside the range' offset = self.header['Record Size'] * rec_no data = self.db_data[offset:offset+self.row_len] record = self._assign_ids(struct.unpack(self.row_format, data), self.row_ids) if self.memo_file: for key in self.fields: field = self.fields[key] f_type = field['Field Type'] f_name = field['Field Name'] c_data = record[f_name] if f_type=='M' or f_type=='G' or f_type=='B' or f_type=='P': c_data = self._reverse_endian(c_data) if c_data: record[f_name] = self.read_memo(c_data-1).strip() else: record[f_name] = c_data.strip() return record def read_memo_record(self, num, in_length): """ Read the record of given number. The second parameter is the length of the record to read. It can be undefined, meaning read the whole record, and it can be negative, meaning at most the length """ if in_length < 0: in_length = -self.memo_block_size offset = self.memo_header_len + num * self.memo_block_size self.fmemo.seek(offset) if in_length<0: in_length = -in_length if in_length==0: return '' return self.fmemo.read(in_length) def read_memo(self, num): result = '' buffer = self.read_memo_record(num, -1) if len(buffer)<=0: return '' length = struct.unpack('>L', buffer[4:4+4])[0] + 8 block_size = self.memo_block_size if length < block_size: return buffer[8:length] rest_length = length - block_size rest_data = self.read_memo_record(num+1, rest_length) if len(rest_data)<=0: return '' return buffer[8:] + rest_data def readDbf(filename): """ Read the DBF file specified by the filename and return the records as a list of dictionary. @param filename File name of the DBF @return List of rows """ db = Dbase() db.open(filename) num = db.get_numrecords() rec = [] for i in range(0, num): record = db.get_record_with_names(i) rec.append(record) db.close() return rec if __name__=='__main__': rec = readDbf('dbf/sptable.dbf') for line in rec: print '%s %s' % (line['GENUS'].strip(), line['SPECIES'].strip())
ผลการปรับปรุงปรากฎว่าใช้งานได้ดีพอควรครับ
คราวก่อนขุดโปรแกรม resize รูปมาขาย คราวนี้ขุดของเก่ามาขายอีกแล้ว :) กับการเขียน theme (หรือ widget) ของ Superkaramba
Superkaramba คือโปรแกรมหรือเครื่องมือที่ช่วยให้คุณสร้างวิดเจ็ตต่างๆ ขึ้นมาบน KDE ครับ ปัจจุบันก็รวมตัวเป็นส่วนหนึ่งของ KDE เป็นที่เรียบร้อย
ประโยชน์ของ widget ก็มีหลายอย่าง เช่น อาจจะใช้ในการ monitor ระบบ, ใช้ดูพยากรณ์อากาศ, ใช้ดึง feed, ฯลฯ การเขียน theme โดยส่วนใหญ่สำหรับใช้ monitor ระบบ เช่น การใช้ CPU, ดิสก์, หน่วยความจำก็จะมีคำสั่งเขียนง่ายๆ ในการสร้าง theme อยู่แล้วในไฟล์ .theme ซึ่งเป็น text file ธรรมดาๆ โดยลองดูรูปแบบการเขียนได้ ที่นี่ แต่สำหรับบางอย่างที่ทางนี้ไม่ได้เตรียมไว้ให้ ผู้สร้าง theme ก็สามารถเขียนเพิ่มเติมเข้าไปได้โดยใช้ภาษา python ครับ โดยจะแบ่งเป็น 2 ส่วน คือส่วนโปรแกรมหรือฟังก์ชั่นที่เราต้องการให้มี และส่วนของการแสดงผลใน Superkaramba
ส่วนโปรแกรมหรือฟังก์ชั่นนั้น เขียนโดยใช้ module มาตรฐาน (หรือเพิ่มเติมก็ตามใจ) ของ Python ได้เลย แต่ส่วนของการแสดงผลนั้น ต้องใช้ API ที่ superkaramba เตรียมไว้ให้ แต่ก็ยังต้องใช้ไฟล์ .theme เหมือนเดิม การเรียกใช้งานนั้น ไม่สามารถเรียกที่ตัว .py ได้โดยตรง แต่จะเป็นการเรียก .theme ซึ่งจะเป็นการไปเรียกโปรแกรม .py ที่มีชื่อเดียวให้ทำงานไปด้วย (Python ที่ใช้เป็น interpreter ตรงนี้เป็น Python ที่ integrated เอาไว้ใน superkaramba ไม่ใช่ Python ที่ลงไว้ในระบบครับ) โดยไฟล์ .py นี้เองทาง superkaramba ก็มี template เตรียมไว้ให้ใช้ โดยจะมีฟังก์ชั่นสำหรับเริ่มวิดเจ็ต และฟังก์ชั่นสำหรับจัดการในเรื่อง interaction เตรียมเอาไว้ให้
ที่ผมจะเสนอต่อไปก็เป็นอะไรที่เขียนไว้นานแล้วเหมือนกัน (แต่นานน้อยกว่าคราวที่แล้ว) คือผมเขียน theme ขึ้นมาตัวนึงให้ทำการ monitor battery (ผมใช้ laptop) โดยสร้างรูปต่างๆ ขึ้นมาตั้งแต่เริ่มโดยใช้ตัวแสดง battery ของ ipod เป็นแบบครับ (ตอนนั้นก็บ้า Apple เหมือนกันนะ จะโดนเรื่อง copyright infringement มั้ยเนี่ย)
ตัวที่ทำการ monitor battery นั้นก็เขียนฟังก์ชั่นโดยใช้ Python ธรรมดา และส่วนแสดงผลก็ใช้ API ของ superkaramba ให้แสดงรูปที่ผมทำขึ้นมา (ไม่มีอะไรพิศดารครับ เพราะตอนนั่งเขียนยังต้องเปิดหนังสือ Python หาคำสั่งอยู่เลย :P)
มาถึงตัวโค้ดครับ พยายามเขียนว่ามันทำอะไรไว้ใน comment แล้วเหมือนเดิมครับ :)
#Battery monitor is a widget theme for Superkaramba using iPod battery graphics. #It monitors battery via /proc/acpi/battery/BAT0/state and /proc/acpi/battery/BAT0/info files. #It's my first time doing something with python stuffs, so please be gentle :) #This script depends on os and karamba modules. #It can be distributed under GPL. #----------------------------------------------------------------------------------------------- #this import statement allows access to the karamba functions import os import karamba #get battery info functions #see if there's a battery (yes,no) and what is its charging state (charging, discharging and charged). #This function will return a list of strings, present state of battery and state of charging. def getBatState(): output0=os.popen("cat /proc/acpi/battery/BAT0/state") state=output0.readlines() present=state[0].split()[1] if present == "no": return [present] else: charge=state[2].split()[2] return [present, charge] #This function will get current charge and capacity of the battery. Then it will calculate percent #remaining and return a list of float, string and integer, percent with 2 decimals, #charge of battery in mAh and percent remaining in integer form. def getCharge(): output1=os.popen("cat /proc/acpi/battery/BAT0/info") charge0=output1.readlines() output2=os.popen("cat /proc/acpi/battery/BAT0/state") charge1=output2.readlines() fully_charge=charge0[2].split()[3] current_charge=charge1[4].split()[2] f_fully_charge=float(fully_charge) f_current_charge=float(current_charge) percent=(f_current_charge / f_fully_charge) * 100 if percent > 100: percent=100 percent_int=int(percent) return [percent,current_charge,percent_int] #this is called when your widget is initialized def initWidget(widget): global battBar,redBattBar,chargingBatt,noBatt,chargedBatt,chargeText,percentText karamba.createBackgroundImage(widget,1,1,"pics/background.png") battBar=karamba.createBar(widget,24,32,138,55,"pics/battery_meter.png") redBattBar=karamba.createBar(widget,24,32,138,55,"pics/battery_meter_red.png") chargingBatt=karamba.createImage(widget,24,32,"pics/battery_meter_charging.png") noBatt=karamba.createImage(widget,62,32,"pics/nobatt.png") chargedBatt=karamba.createImage(widget,24,32,"pics/battery_charged.png") karamba.hideBar(widget,battBar) karamba.hideBar(widget,redBattBar) karamba.hideImage(widget,noBatt) karamba.hideImage(widget,chargedBatt) karamba.hideImage(widget,chargingBatt) chargeText=karamba.createText(widget,19,90,80,50,"Charge:") karamba.changeTextColor(widget,chargeText,130,130,130) karamba.changeTextSize(widget,chargeText,10) karamba.changeTextShadow(widget,chargeText,-1) percentText=karamba.createText(widget,110,90,80,20,"Rem:") karamba.changeTextColor(widget,percentText,130,130,130) karamba.changeTextSize(widget,percentText,10) karamba.changeTextShadow(widget,percentText,-1) return #this is called everytime your widget is updated #the update inverval is specified in the .theme file def widgetUpdated(widget): battPresent = getBatState() if battPresent[0] == "no": karamba.showImage(widget,noBatt) karamba.hideText(widget,percentText) karamba.changeText(widget,chargeText,"No battery!") karamba.hideBar(widget,battBar) karamba.hideBar(widget,redBattBar) karamba.hideImage(widget,chargingBatt) karamba.hideImage(widget,chargedBatt) karamba.redrawWidget(widget) else: battCharge = getCharge() if battPresent[1] == "charging": karamba.showImage(widget,chargingBatt) karamba.addImageTooltip(widget,chargingBatt,"Charging...") karamba.changeText(widget,chargeText,"Charge: %s" % battCharge[1] +" mAh") karamba.changeText(widget,percentText,"Rem: %.2f" % battCharge[0] +" %") karamba.hideBar(widget,battBar) karamba.hideBar(widget,redBattBar) karamba.hideImage(widget,noBatt) karamba.hideImage(widget,chargedBatt) karamba.redrawWidget(widget) elif battPresent[1] == "charged": karamba.showImage(widget,chargedBatt) karamba.addImageTooltip(widget,chargedBatt,"Battery is fully charged") karamba.changeText(widget,chargeText,"Charge: %s" % battCharge[1] +"mAh") karamba.changeText(widget,percentText,"Rem: %.2f" % battCharge[0] +" %") karamba.hideBar(widget,battBar) karamba.hideBar(widget,redBattBar) karamba.hideImage(widget,chargingBatt) karamba.hideImage(widget,noBatt) karamba.redrawWidget(widget) elif battPresent[1] == "discharging": if battCharge[0] < 28: karamba.showBar(widget,redBattBar) karamba.setBarMinMax(widget,redBattBar,0,100) karamba.setBarValue(widget,redBattBar,battCharge[2]) karamba.changeText(widget,chargeText,"Charge: %s" % battCharge[1] +" mAh") karamba.changeText(widget,percentText,"Rem: %.2f" % battCharge[0] +" %") karamba.hideBar(widget,battBar) karamba.hideImage(widget,noBatt) karamba.hideImage(widget,chargingBatt) karamba.hideImage(widget,chargedBatt) karamba.redrawWidget(widget) else: karamba.showBar(widget,battBar) karamba.setBarMinMax(widget,battBar,0,100) karamba.setBarValue(widget,battBar,battCharge[2]) karamba.changeText(widget,chargeText,"Charge: %s" % battCharge[1] +"mAh") karamba.changeText(widget,percentText,"Rem: %.2f" % battCharge[0] +" %") karamba.hideImage(widget,chargedBatt) karamba.hideImage(widget,noBatt) karamba.hideBar(widget,redBattBar) karamba.hideImage(widget,chargingBatt) karamba.redrawWidget(widget) return print "Loaded my python extension!"
ส่วนสำหรับตัววิดเจ็ตเองเชิญดาวน์โหลดได้ ที่นี่ ครับ
มีรูปโชว์ครับ :)
ปล. หลังจากลองเขียนตัวนี้แล้ว จริงๆ Python เป็นอะไรที่เขียนค่อนข้างง่ายเหมือนกันนะครับ คือเขียนไล่ลงมาเรื่อยๆ ไม่ค่อยซับซ้อน อ่านโค้ดก็ง่าย ตามลำดับ (แต่จะเขียนให้ยากกับ control flow ก็คงได้มั้ง) แต่ผมก็ยังอ่อนหัดนัก มีอะไรแก้ไขก็เชิญติชมได้นะครับ :)
บันทึกการติดตั้ง
บันทึกติดตั้ง PyICU บนเดเบียน Etch ใช้ Python รุ่น 2.4
จะลองเอามาทำตัวตัดคำไทย เพื่อจัดเก็บคำในฐานข้อมูล
ครั้งแรกที่ทดลอง ได้นำเอาไบนารีมาติดตั้ง แต่รันไม่ได้เพราะเกิดข้อผิดพลาดเกี่ยวกับยูนิโค๊ด จึงต้องเอาซอร์สมาคอมไพล์เอง
ติดตั้งแพกเกจที่จำเป็น
# aptitude install python2.4-dev libicu36 libicu36-dev
ดาวน์โหลดซอร์ส และแตกไฟล์
# cd /usr/src
# wget http://downloads.osafoundation.org/PyICU/src/PyICU-src-0.6.tar.gz
# tar xfz PyICU-src-0.6.tar.gz
# cd PyICU-src-0.6
ต้องแก้ไข Makefile นิดนึง ตามคู่มือ
# vi Makefile
... #PYTHON_VER=2.5 PYTHON_VER=2.4 ... # Linux #PREFIX=/usr/local #PREFIX_PYTHON=$(PREFIX) #PREFIX_ICU=$(PREFIX)/icu-$(ICU_VER) #PYTHON=$(PREFIX_PYTHON)/bin/python PREFIX=/usr PREFIX_PYTHON=$(PREFIX) PREFIX_ICU=$(PREFIX) PYTHON=$(PREFIX_PYTHON)/bin/python ...
คอมไพล์และติดตั้ง
# make && make install
เสร็จแล้ว
ทดสอบด้วย
# python
Python 2.4.4 (#2, Jan 13 2007, 17:50:26) [GCC 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import PyICU >>>
ไม่มีข้อผิดพลาด แสดงว่าใช้ได้แล้ว
update
บันทึกอีกรุ่นนึง 51-02-28
debian-etch, python-2.4.4, libicu36, gcc/g++-4.1
ติดตั้งโดย root
ดาวน์โหลดซอร์สที่ PyICU-0.8.1.tar.gz
# cd /usr/src
# wget http://pypi.python.org/packages/source/P/PyICU/PyICU-0.8.1.tar.gz#md5=789092993f84ccd6ba21d7346d6e093d
# tar xfz PyICU-0.8.1.tar.gz
# cd PyICU-0.8.1
เอาแพกเกจของ dev มาก่อน
# aptitude install build-essential python-dev python-setuptools libicu36-dev
ดูจาก README แล้วคอมไพล์เลย
# python setup.py build
# python setup.py install
ถ้าไม่แสดงข้อผิดพลาด ก็ใช้ได้แล้ว
ดัดแปลงโค๊ดมาจาก programming is hard: isThai and ThaiWarp function
import PyICU def isThai(chr): cVal = ord(chr) if(cVal >= 3584 and cVal <= 3711): return True return False def wrap(txt): txt = PyICU.UnicodeString(txt) bd = PyICU.BreakIterator.createWordInstance(PyICU.Locale("th")) bd.setText(txt) lastPos = bd.first() retTxt = PyICU.UnicodeString("") txt_list = [] try: while(1): currentPos = bd.next() retTxt += txt[lastPos:currentPos] # txt_list.append(txt[lastPos:currentPos]) #Only thai language evaluated if(isThai(txt[currentPos-1])): if(currentPos < len(txt)): if(isThai(txt[currentPos])): #This is dummy word seperator #retTxt += PyICU.UnicodeString("|||") # pass lastPos = currentPos except StopIteration: pass #retTxt = retTxt[:-1] #return retTxt return [unicode(i) for i in txt_list] def fullwrap(txt): txt_list = txt.split(' ') new_list = [] for i in txt_list: #new_list.extend(wrap(i).split('|||')) new_list.extend(wrap(i)) return new_list
ลองตัดคำดู
$ python
Python 2.4.4 (#2, Jan 13 2007, 17:50:26) [GCC 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> word = 'ทดลองเรียน Python ภาษาโปรแกรมคอมพิวเตอร์' >>> print wrap(word) ทดลอง|||เรียน Python ภาษา|||โปรแกรม|||คอมพิวเตอร์ >>> print fullwrap(word) [u'\u0e17\u0e14\u0e25\u0e2d\u0e07', u'\u0e40\u0e23\u0e35\u0e22\u0e19', u'Python', u'\u0e20\u0e32\u0e29\u0e32', u'\u0e42\u0e1b\u0e23\u0e41\u0e01\u0e23\u0e21', u'\u0e04\u0e2d\u0e21\u0e1e\u0e34\u0e27\u0e40\u0e15\u0e2d\u0e23\u0e4c'] >>> for i in fullwrap(word): print i ... ทดลอง เรียน Python ภาษา โปรแกรม คอมพิวเตอร์ >>>
โฮมเพจอยู่ที่ pymedia - Python module for wav, mp3, ogg, avi, divx, dvd, cdda etc files manipulations.
ดาวน์โหลดแพกเกจได้ที่ pymedia.sourceforge.net
ต้องติดตั้งแพกเกจดังนี้
$ sudo aptitude install libogg-dev libvorbis-dev liblame-dev libfaad2-dev faad
แล้วก็ติดตั้งได้
$ sudo dpkg -i pymedia_1.3.5_i686-py2.4.deb
ทดสอบด้วยการเรียกใช้ python
$ python
Python 2.4.4c1 (#2, Oct 11 2006, 21:51:02) [GCC 4.1.2 20060928 (prerelease) (Ubuntu 4.1.1-13ubuntu5)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import pymedia >>>
ถ้าไม่ฟ้องอะไรออกมา ก็แสดงว่าผ่านแล้ว
เอกสาร Tutorial ในเพจของ pymedia เอง ค่อนข้างหยาบและมีที่ผิดเยอะ
จะลองรวบรวมเฉพาะโค๊ดมาใช้ดู
ลองเล่นไฟล์ wav
import time, wave, pymedia.audio.sound as sound sFile= "YOUR FILE NAME" f= wave.open( sFile, 'rb' ) sampleRate= f.getframerate() channels= f.getnchannels() format= sound.AFMT_S16_LE snd= sound.Output( sampleRate, channels, format ) s= f.readframes( 300000 ) snd.play( s )
แปลง codec
ตัวอย่างนี้ รันไม่ผ่าน เกิด seg-fault
import pymedia.audio.acodec as acodec import pymedia.muxer as muxer sName='01A.mp3' f= open( sName, 'rb' ) s= f.read( 8192 ) file_ext = str.split( sName, '.' )[ -1 ].lower() dm = muxer.Demuxer( file_ext ) frames= dm.parse( s ) print dm.hasHeader(), dm.getInfo() dec= acodec.Decoder( dm.streams[ 0 ] ) frame= frames[ 0 ] r= dec.decode( frame[ 1 ] ) print r.sample_rate, r.channels, r.bitrate, r.sample_length params= { 'id': acodec.getCodecID('wma'), 'bitrate': 16, 'sample_rate': 16000, 'ext': 'wma', 'channels': 1 } enc= acodec.Encoder( params )
/usr/lib/python2.X/sgmllib.py
self.data_line
ไปใช้>>> import util >>> data_list = util.HTML_weight_parse('This is the example of sgmllib.') >>> data_list.data_line [['This is the example of sgmllib.', 1]]
abc this is x.html testlinkการใช้งานจะเป็นดังนี้
>>> import util >>> f = open('test.html') >>> data_list = util.HTML_weight_parse(f.read()) >>> data_list.data_line [['abc', 11], ['this is x.html', 1], ['abc.com', 11], ['testlink', 11]]
#!/usr/bin/env python import sgmllib class HTML_weight_parse(sgmllib.SGMLParser): """Determine weight of text in HTML. input: text or html output = self.data_line : [[line1, weight1], ...] tag omitted data: script, style tag included attribute: meta name="keywords" content="..." img src="..." title="..." alt="..." a href="..." """ weight_dict = { 'title':10, 'h1':10, 'h2':8, 'h3':7, 'h4':6, 'h5':5, 'h6':4 } def __init__(self, text): self.data = '' self.weight = 1 self.data_line = [] sgmllib.SGMLParser.__init__(self) for line in text.split('\n'): self.feed(line.strip()) self.close() def handle_data(self, data): self.data += data def handle_comment(self, data): #bypass comment pass def start_script(self, data): #bypass pass def unknown_starttag(self, tag, attrs): self.flush() if attrs: #tag with attributes meta_enable = False for name, value in attrs: if tag == "a": if name == "href": self.data += value self.flush(False) elif tag == "meta": if name == "name" and value == "keyword": meta_enable = True self.weight += 2 if name == "content" and meta_enable: self.data += value self.flush(False) meta_enable = False elif tag == "img" and name in ["title", "alt"]: self.data += value self.flush(False) else: self.flush() #tag with & without attribute if tag in self.weight_dict.keys(): self.weight += self.weight_dict[tag] else: self.flush() def unknown_endtag(self, tag): self.flush() def close(self): sgmllib.SGMLParser.close(self) self.flush()
ทำห้องแสดงภาพผ่านเว็บด้วยไพธอนอย่างง่าย
ความต้องการคือ
ทำกับเดเบียน+ไพธอน โดยใช้มอดูล wsgi ผ่าน apache2, มอดูล 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 755 pythongal
เสร็จแล้ว
ถ้าเราเอาอะไรใส่เข้าในไดเรกทอรี่ /var/www/pythongal เขาจะจัดการลดขนาดภาพและสร้างภาพเล็กให้เอง โดยเก็บไฟล์เหมือนระบบไฟล์ปกติ
ปรับแต่งหน้าตาของ html จากไฟล์ template.html
และปรับ css ที่ไฟล์ style.css
ปรับคุณสมบัติของโปรแกรมจากตัวแปรที่หัวไฟล์
ลองทดสอบหน้าตัวอย่างได้ที่ www.thaitux.info/pythongal
และหน้า admin สำหรับหมุนภาพที่ www.thaitux.info/.admin-pythongal
มีงานต้องแปลงบทสวดมนต์จากไทยไปอังกฤษ แต่ยาวมากถึงเกือบ ๓๐๐ บท ทำเสร็จแล้วจึงเขียนโปรแกรมเพื่อตรวจทานและเพื่อกันไม่ให้ลืม ต่อด้วยขยายความสามารถให้แปลได้หลาย ๆ ภาษา คิดว่าคงจะมีโอกาสใช้งานภายหลัง
ใครมีโอกาสใช้งาน รบกวนช่วยทดสอบให้ด้วยครับ
ลองใช้งานด้วยคำสั่ง
$ ./pali_convert.py
ได้ผลเป็น
Usage: pali_convert.py -f LANG -t LANG INFILE [OUTFILE] Options: -f, --from: convert from language. -t, --to: convert to language. Languages: * engpali : Pali English (asevnā ca bālānaṃ, paṇḍitānañca sevnā ฯ) eng : Simple English (asevna ca balanam, panditananca sevna ฯ) * thaipali : Pali Thai (อเสวฺนา จ พาลานํ, ปณฺฑิตานญฺจ เสวฺนา ฯ) * thai : Simple Thai (อะเสว๎นา จะ พาลานัง, ปัณฑิตานัญจะ เสว๎นา ฯ) lao : Simple Lao (อະເສວນາ ຈະ ພາລານັງ, ປັນດິຕານັຍຈະ ເສວນາ ຯ) * burmpali : Pali Burmese (အသေဝ္နါ စ ဗာလာနံ, ပဏ္ဍိတာနည္စ သေဝ္နါ ฯ) (* = Supported input language, eg. convert from)
สมมุติจะแปลงจากไทยแบบง่าย เป็นอังกฤษแบบง่าย ก็สั่งด้วย
$ ./pali_convert.py -f thai -t engpali THAI.txt ENG.txt
Convert from thai (THAI.txt) to eng (ENG.txt) success.
ไฟล์ที่จะแปลง
$ cat THAI.txt
อุปปาตะสันติ นะโม ตัสสะ ภะคะวะโต อะระหะโต สัมมาสัมพุทธัสสะ. คันถารัมภะ (ก) สุทุททะโส อะยัง ธัมโม โลกัตถัง ชินะเทสิโต มะหาสันติกะโร โลเก สัพพะสัมปัตติทายะโก. (ข) สัพพุปปาตูปะสะมะโณ ภูตะยักขะนิวาระโณ อะกาละมัจจุสะมะโณ โสกะโรคะวินาสะโน. (ค) ปะระจักกะปะมัททะโน รัญโญ วิชะยะวัฑฒะโน สัพพานิฏฐะหะโร สันโต ธัมมัง วักขามิ ภูตะโต. (ฆ) วัตถุตตะยัสสะ โย ยัตถะ สังวัณเณติ คุณุตตะเม ตัสสะ ตัตถะ สุขาโรค๎ยะ- โสตถิโย โหนติ สัพพะทา.
ได้ผลเป็น
$ cat ENG.txt
uppatasanti namo tassa bhagavato arahato sammasambuddhassa. gantharambha (k) sududdaso ayam dhammo lokattham jinadesito mahasantikaro loke sabbasampattidayako. (kh) sabbuppatupasamano bhutayakkhanivarano akalamaccusamano sokarogavinasano. (g) paracakkapamaddano ranno vijayavaddhano sabbanitthaharo santo dhammam vakkhami bhutato. (gh) vatthuttayassa yo yattha sanvanneti gunuttame tassa tattha sukharogya- sotthiyo honti sabbada.
Attachment | Size |
---|---|
pali_convert-2013-12-23.tar_.gz | 5.58 KB |
pali_convert-2013-12-24.tar_.gz | 5.63 KB |
รวมมอดูลที่น่าสนใจ
$ wget http://www.bermi.org/downloads/python_inflector-0.1.tar.gz
$ tar xfz python_inflector-0.1.tar.gz
$ cd python_inflector-0.1
$ python
Python 2.5.2 (r252:60911, Jul 31 2008, 07:39:27)
[GCC 4.3.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import Inflector
>>> inf = Inflector.English()
>>> inf.singularize('oxen')
'ox'
>>> inf.pluralize('sheep')
'sheep'
>>>
ยังมีอีกหลายฟังก์ชั่น เอาไว้ค่อยศึกษาอีกที
update$ vi Rules/English.py
# Copyright (c) 2006 Bermi Ferrer Martinez
# info at bermi dot org
# See the end of this file for the free software, open source license (BSD-style).
import re
from Base import Base
class English (Base):
"""
Inflector for pluralize and singularize English nouns.
This is the default Inflector for the Inflector obj
"""
irregular_words = {
'person' : 'people',
'man' : 'men',
'child' : 'children',
'sex' : 'sexes',
'foot' : 'feet',
'goose' : 'geese',
'tooth' : 'teeth',
}
reserve_words = {
'ox' : 'oxen',
'genus': 'genera',
'corpus': 'corpora',
'concerto': 'concerti',
'bus' : 'buses',
'virus' : 'viruses',
'apparatus': 'apparatuses',
'die' : 'dice',
'this' : 'these',
'that' : 'those',
'quiz' : 'quizzes',
'polka' : 'polkas',
'passerby' : 'passersby',
'sarcoma' : 'sarcomata',
'schema' : 'schemata',
'stigma' : 'stigmata',
'stoma' : 'stomata',
'cherub' : 'cherubim',
'kibbutz' : 'kibbutzim',
'seraph' : 'seraphim',
'mum' : 'mums',
'boa' : 'boe',
}
uncountable_words = ['aircraft','equipment', 'information', 'rice', \
'money', 'species', 'series', 'fish', 'sheep', 'shrimp', 'sms', \
'moose', 'bison', 'deer', 'means', 'scissors', 'species', 'swine', \
'salmon',
'meerschuam',
'scum','talcum',
'offspring',
'sorghum','wampum',
'phoenix',
]
def pluralize(self, word) :
'''Pluralizes English nouns.'''
#get rules from
#http://web2.uvcs.uvic.ca/elc/studyzone/330/grammar/irrplu.htm
#http://www2.gsu.edu/~wwwesl/egw/pluralsn.htm
rules = [
['(?i)eau$' , 'eaux'],
['(?i)menon$' , 'mena'],
['(?i)terion$' , 'teria'],
['(?i)(m|[^b]l)ouse$' , '\\1ice'],
['(?i)(d|l|r)ix$' , 'ices'],
['(?i)(d|p|t)ex$' , 'ices'],
['(?i)(ar|f|oo)f$' , '\\1fs'],
['(?i)(f|fe)$' , 'ves'],
['(?i)(pian|sol|temp)o$' , '\\1os'],
['(?i)(a|e|i|o|u|n)o$' , '\\1os'],
['(?i)(o|x|z|ch|ss|sh)$' , '\\1es'],
['(?i)-in-law$' , 's-in-law'],
['(?i)ful$' , 'sful'],
['(?i)is$' , 'es'],
['(?i)(d|(a|e|i|o|u)n|p|pl|r|s|t)us$' , '\\1uses'],
['(?i)(bu)s$' , '\\1ses'],
['(?i)us$' , 'i'],
['(?i)(a|b|dumd|e|g|h|k|(d|l|p|s|sy)l|o|r|s)um$' , '\\1ums'],
['(?i)um$' , 'a'],
['(?i)(a|e|i|o|u)a$' , '\\1as'],
['(?i)a$' , 'ae'],
['(?i)([^aeiouy]|qu)y$' , '\\1ies'],
['(?i)s$' , 'ses'],
['(?i)$' , 's']
]
lower_cased_word = word.lower();
for uncountable_word in self.uncountable_words:
if lower_cased_word[-1*len(uncountable_word):] == uncountable_word :
return word
for irregular in self.irregular_words.keys():
match = re.search('('+irregular+')$',word, re.IGNORECASE)
if match:
return re.sub('(?i)'+irregular+'$', match.expand('\\1')[0]\
+self.irregular_words[irregular][1:], word)
for res_word in self.reserve_words.keys():
if res_word == word:
return self.reserve_words[res_word]
for rule in range(len(rules)):
match = re.search(rules[rule][0], word, re.IGNORECASE)
if match :
groups = match.groups()
for k in range(0,len(groups)) :
if groups[k] == None :
rules[rule][1] = rules[rule][1].replace('\\'+str(k+1), '')
return re.sub(rules[rule][0], rules[rule][1], word)
return word
def singularize (self, word) :
'''Singularizes English nouns.'''
rules = [
['(?i)eaux$' , 'eau'],
['(?i)mena$' , 'menon'],
['(?i)teria$' , 'terion'],
['(?i)([m|l])ice$' , '\\1ouse'],
['(?i)ices$' , '|ix|ex'],
['(?i)ves$' , '|f|fe'],
['(?i)os$' , 'o'],
['(?i)(o|x|z|ch|ss|sh)es$' , '\\1'],
['(?i)s-in-law$' , '-in-law'],
['(?i)sful$' , 'ful'],
['(?i)(d|(a|e|i|o|u)n|p|pl|r|s|t)uses$' , '\\1us'],
['(?i)(a|b|dumd|e|g|h|k|(d|l|p|s|sy)l|o|r|s)ums$' , '\\1um'],
['(?i)a$' , 'um'],
['(?i)i$' , 'us'],
['(?i)ses$' , 's'],
['(?i)es$' , 'is'],
['(?i)(a|e|i|o|u)as$' , '\\1a'],
['(?i)ae$' , 'a'],
['(?i)ies$' , 'y'],
['(?i)s$' , '']
]
irregular_words = dict(
[[self.irregular_words[i],i] for i in self.irregular_words.keys()] )
reserve_words = dict(
[[self.reserve_words[i],i] for i in self.reserve_words.keys()] )
lower_cased_word = word.lower();
for uncountable_word in self.uncountable_words:
if lower_cased_word[-1*len(uncountable_word):] == uncountable_word :
return word
for irregular in irregular_words.keys():
match = re.search('('+irregular+')$',word, re.IGNORECASE)
if match:
return re.sub('(?i)'+irregular+'$', match.expand('\\1')[0]+irregular_words[irregular][1:], word)
for res_word in self.reserve_words.keys():
if res_word == word:
return self.reserve_words[res_word]
for rule in range(len(rules)):
match = re.search(rules[rule][0], word, re.IGNORECASE)
if match :
groups = match.groups()
for k in range(0,len(groups)) :
if groups[k] == None :
rules[rule][1] = rules[rule][1].replace('\\'+str(k+1), '')
return re.sub(rules[rule][0], rules[rule][1], word)
return word
# Copyright (c) 2006 Bermi Ferrer Martinez
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software to deal in this software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of this software, and to permit
# persons to whom this software is furnished to do so, subject to the following
# condition:
#
# THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THIS SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THIS SOFTWARE.
เป็นโปรแกรมเล็ก ๆ ในการช่วยแปลงไฟล์ HTML เป็น EPUB ทำงานได้ในไฟล์เดียว แต่ยังทำงานได้จำกัด ไม่ได้รองรับข้อกำหนดทุกอย่างของ EPUB โดยเฉพาะ HTML5
หรือ
เท่านั้น
สมบัติและข้อกำหนด
- แปลง HTML ไฟล์เดี่ยว
- ต้องเตรียมไฟล์ต้นฉบับ HTML มาก่อน เช่น
- บังคับแบ่งบทด้วยแท็ก <!--section_break--> หรือ <!--page_break-->
- หากไม่มีแท็กแบ่งบทข้างต้น โปรแกรมจะแบ่งด้วยแท็ก <h1> หรือ <h2>
- อาจแบ่งคำด้วย SWATH ซึ่งต้องแบ่งมาก่อน
- ปรับแต่ง css มาแล้ว ซึ่งอาจเป็นภายในไฟล์ HTML เอง หรือระบุไฟล์ภายใต้ไฟล์ลูก
INC_FILE.py
ด้วยพารามิเตอร์ -i
ก็ได้ (EPUB รองรับ css ไม่ครบทุกคำสั่ง) โดยอันหลังสำคัญกว่าอันแรก คือจะแทนที่ css ในไฟล์ HTML ด้วย css ที่ระบุไว้
- ถ้ามีบทสารบัญภายในไฟล์ HTML โปรแกรมจะสร้างบทสารบัญไว้ด้วย แต่จะสร้างโค๊ด HTML ขึ้นมาใหม่เพื่อให้สอดคล้องกับ EPUB หากไม่ต้องการให้มีสารบัญอยู่ภายในบท ต้องลบบทสารบัญออกจากไฟล์ต้นฉบับ HTML ออกก่อน
- แปลงเป็น EPUB ได้ 3 รุ่น คือ 2.0, 2.0.1 และ 3.0 ระบุด้วยพารามิเตอร์
-ver
ค่าปริยายคือ 2.0.1
- ระบุจำนวนชั้นของสารบัญได้ ด้วยพารามิเตอร์
-toclevel N
ค่าปริยายคือ 3
- ไฟล์ประกอบ เช่น ไฟล์ภาพ,ไฟล์ฟอนต์ และอื่น ๆ ต้องเตรียมด้วยมือเอง โดยใส่ไว้ภายใต้ไดเรคทอรี่ที่ระบุในพารามิเตอร์
-incdir DIR
ทุกไฟล์และไดเรคทอรี่ภายใต้ DIR/
จะถูกคัดลอกไปยังไดเรคทอรี่ของ EPUB ชื่อ OEBPS/
ซึ่งถือเป็นไดเรคทอรี่รากของ EPUB เวลาเราอ้างถึงไฟล์จากคำสั่งใน css หรือคำสั่งใน HTML จะอิงกับไดเรคทอรี่รากนี้เสมอ
- เมื่อโปรแกรมทำงานเสร็จ จะทิ้งไดเรคทอรี่งานเอาไว้ ชื่อ
epubdir
เพื่อประโยชน์ในการดีบั๊ก เมื่อเสร็จงานแล้ว สามารถลบทิ้งด้วยมือ และในการสั่งงานโปรแกรมแต่ละครั้ง ทุกอย่างภายใต้ไดเรคทอรี่นี้ จะถูกลบทิ้งและสร้างขึ้นใหม่ ดังนั้นหากมีการแก้ไขเพื่อทดสอบ ควรแบคอัพไว้ก่อน และหากไม่ต้องการรันโปรแกรมใหม่ แต่เป็นการแก้ไขเพื่อทดสอบ ให้ใช้คำสั่งเชลล์ในการสร้าง EPUB ดังนี้
$ cd epubdir
$ zip -qX0 ../FILE.epub mimetype
$ zip -qrX9 ../FILE.epub * -x mimetype
$ cd -
- เขียนแบบ quick&dirty ค่อย ๆ คลำไป บั๊กเยอะแน่นอน
หมายเหตุในการทดสอบ
- เลือกใช้รุ่น 2.0.1 เป็นค่าปริยาย เพราะ Google Play Books แสดงผลสารบัญแบบหลายชั้นได้ไม่ครบถ้วน และความรู้ยังไม่พอศึกษาและทดสอบ HTML5
- ใช้โปรแกรม epubcheck ในการตรวจสอบความถูกต้องของไฟล์ epub ที่ได้
- การฝังฟอนต์ ยังไม่ได้ทดสอบ เพราะโปรแกรมอ่านอีบุ๊คในท้องตลาดยังรองรับภาษาไทยและ opentype ไม่เต็มที่
- การทดสอบการแสดงผล ส่วนใหญ่ทดสอบใน EPUBReader Extensions ในไฟร์ฟอกซ์, Google Play Books บน Desktop/Android และ iBooks บน Mac/iOS ก็แสดงผลได้ดีพอควร มีข้อสังเกตุคือ
- EPUBReader Extensions แสดงขนาดฟอนต์ได้ไม่คงที่นัก
- Google Play Books บน Desktop แสดงผลได้ค่อนข้างดี สารบัญแสดงครบ
- Google Play Books บน Android แสดงได้พอใช้ มีการแสดงผลตัวเอียงผิดสลับมาบ้าง สารบัญหลายชั้นของรุ่น 3.0 แสดงผิดเป็นบรรทัดว่าง และของรุ่น 2.0 แสดงแค่ชั้นเดียว
- iBooks ทั้งบน Mac และ iOS แสดงผลได้ดีเลิศ รองรับ CSS กว้างที่สุด แต่บน iOS7 ทำงานกับภาษาไทยได้ช้ามาก
การใช้งาน
ลองใช้งานด้วยคำสั่ง
$ ./html_2epub.py
ได้ผลเป็น
Usage: html_2epub.py [-showvar] [-ver EPUB_VER] [-ctl] [-toclevel N] [-i INC_FILE.py] [-c COVERIMG] [-incdir INC_DIR] FILE.html [FILE.epub]
Convert one html file to epub.
Parameters:
-showvar: show internal variables that can modify for properly convert.
example: html_2epub.py -showvar > INC_FILE.py.
-ver: EPUB version ['2.0', '2.0.1', '3.0'], default '2.0.1'.
-ctl: convert html tag to lowercase, not necessary if tag is already lower.
-toclevel N: max level of TOC (default is 3).
-i: include additional variables from INC_FILE.py.
-c: cover image (jpg,png,gif).
-incdir: include directory (will copy all files/dir in this dir into OEBPS/).
To do:
- ISBN supported, currently just use UUID.
ลองสร้างไฟล์ลูก
$ ./html_2epub.py -showvar > INC_FILE.py
$ cat INC_FILE.py
ได้เป็น
#!/usr/bin/env python
# coding=utf-8
# ---------- html_2epub.py SECTION ----------
#
# variable that can be overriden.
#VARIABLE ORDER: no order required.
#-----type: STRING, will REPLACE default variable of same name-----
#style_file : stylesheet file to replace original in html file.
style_file = ""
#language : language to be used, ('ENGLISH','THAI' : default THAI).
language = "THAI"
#book_uuid : UUID book identifier.
book_uuid = "7a27330a-ebec-4fe9-9eb3-d8f09a75e789"
#book_isbn : ISBN book identifier.
book_isbn = ""
#-----type: STRING, will APPEND default variable of same name-----
#-----type: LIST, will APPEND default variable of same name-----
#add_toc_brk_list : additional tags to be use for breaking chapter: [ [TAG,LEVEL],...].
add_toc_brk_list = []
#mod_toc_title_list : text to override default toc header and toc level: [ [CHAP_FILE#ID,LABEL,LEVEL],...] (use LEVEL=0 is no modified).
mod_toc_title_list = []
หลังจากปรับแต่งไฟล์ INC_FILE.py
แล้ว ลองใช้งานจริงด้วยคำสั่ง
$ ./html_2epub.py -i INC_FILE.py INFILE.html OUTFILE.epub
Attachment | Size |
---|---|
html_2epub-2014-01-24.tar_.gz | 10.4 KB |
html_2epub-2014-01-24.tar_.gz | 10.61 KB |
html_2epub-2014-01-26.tar_.gz | 11.08 KB |