Tag Archives: python

ใช้ python dict อย่างเท่ๆ

dict ใน python เป็นโครงสร้างข้อมูลชนิดนึง ที่ใช้เก็บ key -> value ได้ เวลาเขียนโปรแกรมกับ dict เราก็มักจะมีท่าที่ต้องเขียนโปรแกรมซ้ำๆ… ผมเพิ่งรู้จักท่าอื่นๆ ที่อยากเอามาแชร์ตามนี้ครับ:

1. defaultdict

เรามักจะต้องเขียนโปรแกรมเช็คว่ามีคีย์ (key) นี้อยู่ใน dict อยู่แล้วรึเปล่า ก่อนที่จะนับค่าเพิ่ม (increment) เช่นโปรแกรมนับความถี่ของตัวอักษรข้างล่างนี้

text = "aaaabbcdeff"
freq = {}
for c in text:
    if c not in freq:
        freq[c] = 0
    freq[c] += 1

เราสามารถใช้ defaultdict มาช่วยให้โค้ดดูสวยงามขึ้นได้ตามนี้ (ขอบคุณพี่ @lewcpe ที่แนะนำให้รู้จักฮะ)

from collections import defaultdict
text = "aaaabbcdeff"
freq = defaultdict(int)
for c in text:
    freq[c] += 1

ตัวอย่างข้างบน เราบอก defaultdict ว่าถ้าคีย์ที่ต้องการยังไม่มี ให้เรียก int() เพื่อกำหนดค่าเริ่มต้น

x = freq['a']       # ปกติจะ KeyError, แต่ถ้าเป็น defaultdict มันจะเรียก
freq['a'] = int()   # เพื่อกำหนดค่าเริ่มต้นให้

2. .setdefault

เวลาเขียนโปรแกรมอ่านค่าจาก dict เราสามารถใช้เมธอด .get เพื่ออ่านค่าจาก dict ได้โดยไม่ต้องกังวลว่าจะมี คีย์นั้นอยู่รึเปล่า เช่น

options = { 'width': 500 }
width = options.get('width', 1000)      # 500
height = options.get('height', 300)     # 300

ตัวอย่างข้างบน เราสามารถอ่านค่า options['width'] ได้สำเร็จ ส่วน height เราได้ค่า default คือ 300 เพราะไม่มีคีย์ height ใน options

สำหรับเมธอด .setdefault นั้นมีพฤติกรรมคล้ายๆ .get แต่มันจะกำหนดค่า default กลับลงไปใน dict ด้วยถ้ายังไม่มีคีย์นั้นใน dict

people_by_blood = { "A": ["Adam", "Eva"],
                    "B": ["John"]}
people_by_blood.setdefault("O", []).append("Job")
people_by_blood.setdefault("A", []).append("Nin")
 
print people_by_blood
{"A": ["Adam", "Eva", "Nin"],
 "B": ["John"],
 "O": ["Job"] }

3. คลาส collections.Counter

คลาส Counter เป็นคลาสเฉพาะกิจ ใช้นับ frequency ครับ ดูตัวอย่างเลยดีกว่า

from collections import Counter
print Counter("aaaabbcdeff")
# Counter({'a': 4, 'b': 2, 'f': 2, 'c': 1, 'e': 1, 'd': 1})
print Counter("aaaabbcdeff").most_common(3)
# [('a', 4), ('b', 2), ('f', 2)]

นอกจากใช้นับ frequency ได้ง่ายแล้ว ยังมีเมธอดสะดวกๆ เช่น most_common ให้ใช้ด้วย

ใครมีท่าอื่นๆ ของ dict ใน python อย่าลืมมาแชร์กันนะครับ ^^

Django: QuerySet is not a List

เวลาดึงข้อมูลจาก Model เช่น

blogs = Blog.objects.all()    # ได้ QuerySet ของบล็อกหลายๆ อัน
print blogs[0].title
print blogs[0].body
print blogs[1].title
print blogs[1].body

จะเห็นว่าเราใช้ QuerySet ได้เหมือนเป็นอาเรย์หรือ list เลย
แต่สิ่งที่ต้องระวังคือ blogs เป็น QuerySet ไม่ใช่ list

เวลาอัพเดทข้อมูลใน model ต้องระวัง

bug แบบนี้

1
2
3
blogs[0].title = "New Title"
blogs[0].body = "new content"
blogs[0].save()

ถ้ารันโค้ดข้างบนแล้ว blogs[0] จะไม่เปลี่ยนแปลงค่าอะไรเลย เพราะ:

  • blogs[0] ในแต่ละบรรทัดเป็น object คนละอันกัน (คนละ instance กัน)
  • การเรียก blogs[0] แต่ละครั้ง QuerySet จะให้ object ใหม่ทุกครั้ง ทำให้การกำหนดค่ามีผลกับ object ในบรรทัดนั้นเฉยๆ
  • blogs[0].save() ในบรรทัดที่ 3 จะอ่านค่า blogs[0] จาก database และ save() กลับไปโดยยังไม่ได้แก้ไขอะไรเลย

ที่ถูกต้องควรเป็น

b = blogs[0]
b.title = "New Title"
b.body = "new content"
b.save()

แต่ถ้าอยากใช้ได้เหมือน list จริงๆ ก็ทำได้โดยบังคับให้ Django มัน evaluate QuerySet ให้กลายเป็น list ด้วยฟังก์ชัน list
เช่น

blogs = list(blogs)

มีข้อเสียคือ
* มี large memory overhead
* blogs.filter ต่อไม่ได้… เพราะโดน evaluate ไปแล้ว

รวมความมึนของผม ใน python

rules = (
    ( 'foo', 1),
    ( 'spam', 2),
    ( 'bar', 3),
"""
    ( 'egg', 4),
    ( 'python', 5),
"""
)
 
for name, val in rules:
    print name, val

โค้ดข้างบนพอรันแล้วจะเจอ ValueError (too many values to unpack)
ตอนมองแว๊บแรก ก็คิดว่าโค้ดน่าจะโอเค แค่ comment egg กับ python ออกไปแบบหลายบรรทัด… แต่จริงๆ แล้วมันไม่ใช่

เราสร้าง list ที่มี (‘foo’, 1), (‘spam’, 2), (‘bar’, 3) และ “”" (‘egg’, 4),…”"” <– ก้อนนี้เป็น string แบบหลายบรรทัด ไม่ใช่ comment!!

ปล. ตอนนี้มีอยู่ 1 bug, เดี๋ยวมีอะไรเพิ่ม จะเอามาแปะอีกครับ

Closure ใน Python

แฮ่ วันนี้ลองเล่น closure ใน python เต็มที่เลยครับ

funcs = []
i=0
def funcfactory():
    global i
    i += 1
    j = i
    def callback():
        print j
    funcs.append(callback)
 
funcfactory()    # ได้ callback ที่มี j = 1
funcfactory()    # ได้ callback ที่มี j = 2
funcfactory()    # ได้ callback ที่มี j = 3
print funcs
for f in funcs:
    f()

list funcs จะเก็บ function ‘callback’ ที่มีโค้ดแบบเดียวกัน (print j) แต่มี context ต่างกัน (j = 1, 2, 3)

closure = (function, environment ของฟังก์ชั่น)

Edit: ข้อจำกัดสำคัญของ Closure ใน python คือ “อ่านได้อย่างเดียว” วิธีแก้ปัญหาทำได้โดยส่ง reference แทนตามตัวอย่างใน [ref]

ใน Python 3 มีคีย์เวิร์ด nonlocal ไว้ใช้อ้างถึงตัวแปรที่อยู่ใน Scope ก่อนหน้าได้

ปลั๊กอิน “ป้องกัน SPAM” สำหรับ pidgin

msn_spam เบื่อ SPAM ทาง msn มาก! จะบล็อกคนส่งทิ้งแบบ @rtsp ก็ใจร้ายเกินไป

ผมเลยลองเขียนปลั๊กอินไว้ “กรองข้อความ” โดยใช้เงื่อนไขง่ายๆ ว่า

“ใน 1 ชม. ที่ผ่านมา ถ้าเธอยังไม่เคยส่งข้อความหาฉันเลย แล้วจู่ๆ ส่ง URL มาให้ ฉันจะขอโยนข้อความนั้นทิ้งซะ (มันต้องเป็น spam แน่เลย!)”

Continue reading

Psyco ให้โปรแกรมที่เขียนด้วย python ทำงานเร็วขึ้น 2-100 เท่า โดยไม่ต้องแก้โค้ดเดิม

จากคอมเมนท์ข่าว กูเกิลเตรียมพัฒนา Python ขนานใหญ่ ใน blognone ทำให้ผมเพิ่งรู้ว่า java ทำงานได้เร็วกว่า python เพราะมี Java VM ที่เป็น Just-in-Time Compiler (JIT Compiler)

JIT Compiler ตามความเข้าใจของผม (งูๆปลาๆ)

ตามปกติ Java VM ทำหน้าที่เป็น Interpreter เวลาเรารันโปรแกรมก็จะแปลโค้ดทีละบรรทัดเป็นภาษาเครื่องที่สอดคล้องกัน และเมื่อเจอโค้ดเดิมๆ Interpreter ก็ต้องแปลโค้ดเดิมซ้ำทำให้เสียเวลา

JIT Compiler เข้ามาช่วยเพิ่มความเร็วได้ โดยมันจะคอย cache ส่วนของโค้ดที่เคยแปลเป็นภาษาเครื่องไว้ ทำให้เวลาเจอโค้ดเดิมก็สามารถเรียกภาษาเครื่องที่ cache ไว้พ่นใส่ CPU ได้ทันที

แล้ว Python มี JIT Compiler ไหม?

Continue reading

AVL Tree ใน python

นั่งแอบเขียน AVL Tree ระหว่างนั่งเรียนวิชา Abstract Data Types (ตัวอย่างไม่ดี โปรดอย่าเลียนแบบ :P )

AVL Tree คือ binary search tree ที่คอยจัดตัวเองให้ balance อยู่เสมอ (subtree ทางซ้าย กับ subtree ทางขวามี depth แตกต่างกันไม่เกิน 2) และการจัดต้นไม้ให้ balance อยู่เสมอทำให้ binary search tree สามารถแทรก/ค้นหา ข้อมูลได้อย่างมีประสิทธิภาพ (ไม่มีปัญหาที่เกิดจากต้นไม้เอียงไปข้างใดข้างหนึ่ง)

AVL Tree ที่เขียนเป็น class มี method isLeaf (เช็คว่าเป็นโหนดลูก), depth (หาความลึกของต้นไม้), balance (คำนวณ balance factor สำหรับต้นไม้), LeftRotate (ทำการหมุนต้นไม้ไปทางซ้าย), RightRotate (หมุนต้นไม้ไปทางขวา), RLDoubleRotate (หมุนสองครั้งแบบขวา-ซ้าย), LRDoubleRotate (หมุนสองครั้งแบบซ้าย-ขวา), insert (แทรกโหนดลงไปในกราฟ), travel (ท่องไปในต้นไม้), printtree (พิมพ์ต้นไม้)

ตัวอย่างต้นไม้ที่ printtree พิมพ์ออกมา

    7
  6
    5
4
    3
  2
    1

ข้างบนเป็นต้นไม้ที่มี 4 เป็น root และมี 2 กับ 6 เป็นลูกทางซ้ายและขวาตามลำดับ โดย 2 มี 1 และ 3 เป็นลูก…. วิธี printtree แบบนี้ ได้เทคนิคมาจาก อ.ที่ค่าย สอวน. ศูนย์ ม.ศิลปากร ครับ :D

(โค้ดอยู่ด้านในนะครับ)
Continue reading

ใช้ python เรียกโปรแกรมอื่น โดยมี timeout

วันจันทร์ที่ผ่านมาผมนั่งเขียน grader อย่างบ้าคลั่ง เพราะต้องเอามาใช้ตรวจโปรแกรมน้องๆ ในวันอังคาร ผมเขียน grader ด้วยภาษา Python หลายๆคนคงทราบดีว่าโปรแกรม grader เป็นโปรแกรมที่ทำหน้าที่ compile และ run โปรแกรมอื่น เพื่อตรวจสอบว่า input ที่ใส่เข้าไปตรงกับ output ที่ถูกต้องหรือไม่ ถ้าถูกต้องทุก input ก็ถือว่าผ่าน

แต่ว่าโปรแกรมที่เอามาตรวจอาจรันแล้วเกิดอาการติดลูป หรือรันไม่รู้จบ เราจึงต้องกำหนด timeout ว่าให้รันได้ไม่เกินกี่วินาที ถ้าโปรแกรมรันนานเกินไป โปรแกรม grader จะต้องหยุดการทำงาน (End Process) ของโปรแกรมที่ถูกตรวจ

ปรากฏว่าผมไปค้นๆ ในเน็ตก็ไม่เจอตัวอย่างโปรแกรม python ที่สามารถ รันโปรแกรมอื่น แล้วกำหนด timeout แบบง่ายๆ เลย ผมก็เลยพยายามเขียนเอง :D ได้โค้ดมาดังนี้ (ใช้ thread และ subprocess)

import subprocess
from time import sleep
from threading import Thread
 
class process(Thread):
    # written by Natt Piyapramote <nattster at google mail>
    # requires: Python 2.6
    def __init__(self, filename, input_txt=None):
        Thread.__init__(self)
        self.filename = filename
        self.input_txt = input_txt
        self.status = 0
        self.output = ''
 
    def run(self):
        self.process = subprocess.Popen(self.filename,
                                   stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        self.output, self.err = self.process.communicate(self.input_txt)
        self.status = 1
 
def run_process(filename, input_txt=None, timeout=1):
    "run a process with timeout"
 
    p = process(filename, input_txt)
    p.start()
    p.join(timeout)
 
    if p.status == 0:   # process still running?
        p.process.terminate()
        while p.isAlive():
            sleep(0.1)
            print 'killing process...'
 
    return p.output

ตัวอย่างการใช้งานฟังก์ชันนี้ก็

run_process('c:\windows\notepad.exe', timeout=4)

ฟังก์ชัน run_process จะไปเรียกโปรแกรม notepad.exe ให้ทำงาน (ซึ่ง notepad จะรันไปเรื่อยๆ ไม่ปิดตัวเอง) ดังนั้นฟังก์ชัน run_process จะหยุดการทำงานของ notepad ภายในเวลา 4 วินาที

นอกจากนี้ถ้าเรารันโปรแกรมที่เป็น Console Application เราสามารถกำหนด Input ให้กับ Standard Input (stdin) และอ่านค่าจาก Standard Output (stdout) ของโปรแกรมที่รันได้ด้วย (กำหนด Input ให้ตัวแปร input_txt แล้วฟังก์ชันจะคืนค่าข้อความจาก stdout)

อ้อ… ฟังก์ชันนี้ต้องใช้ Python 2.6 นะครับ

อ่านไฟล์ใน python อย่างรวดเร็วและมีประสิทธิภาพ

ก่อนหน้านี้นิสัยไม่ดี ชอบเขียนโปรแกรมอ่านไฟล์ประมาณนี้

lines = open('file.txt', 'r').read().split('n')
for line in lines:
    # do something

คำสั่ง read() แบบไม่กำหนด parameter จะอ่านไฟล์ทั้งหมดเข้ามาไว้ในหน่วยความจำ ถ้าไฟล์มีขนาดเล็ก ก็คงไม่มีปัญหาอะไร แต่เมื่อคืนเขียนโปรแกรมตัดคำไฟล์ใหญ่ประมาณ 64mb ปรากฏว่าโปรแกรมทำงานช้าลงมาก (จากไฟล์เล็กๆ ~7000 คำ/วินาที พอเจอไฟล์ใหญ่ๆเหลือ ~700 คำ/วินาที)

พออ่าน Python Doc แล้วเปลี่ยนวิธีเขียนโปรแกรมอ่านไฟล์เป็นแบบนี้แทน (ซึ่งมีประสิทธิภาพมากกว่า เพราะค่อยๆ อ่านทีละบรรทัด)

f = open('file.txt', 'r')
for line in f:
    # do something
f.close()

ปรากฏว่า ไฟล์จะใหญ่แค่ไหน โปรแกรมก็ทำงานที่ความเร็ว ~7000 คำ/วินาที :D
ปล. มาเขียนบล็อกไว้เผื่อคนอื่นจะเจอปัญหาโง่ๆ แบบนี้ T_T (หรือไม่เค้าก็คงจะเขียนกันแบบล่างซะส่วนใหญ่ล่ะมั้ง)
ปล2. วิธีอ่านไฟล์แบบล่าง มีปัญหาคือ ถ้าโปรแกรมเราจำเป็นต้องกระโดดไปอ่านข้อความที่บรรทัดนู้นที บรรทัดนี้ทีก็คงจะทำไม่ได้ครับ