Python cho người Việt

Python cho người Việt

Ghi nhớ về PyCon 2013

written by vithon, on Mar 18, 2013 1:01:06 PM.

Hội thảo PyCon 2013 vừa kết thúc vào Chủ Nhật, ngày 18 tháng 03 năm 2013 vừa rồi tại Trung tâm Hội nghị Santa Clara, bang California, Mỹ.

Hội thảo lần này diễn ra vào các ngày thứ sáu, thứ bảy, và Chủ Nhật với khoảng 100 bài tham luận và 2500 người tham dự đến từ tất cả các châu lục trên trái đất. Trong đó, khoảng 20% là nữ giới!

Nhóm PCNV có một thành viên tham gia hội thảo lần này, và họ có một số nhận xét chính như sau:

1. Hội thảo có nhiều chuyên mục phù hợp với mọi lứa tuổi, mọi ngành nghề, mọi giới tính. Có cả những em thiếu niên khoảng 10 đến 12 tuổi tham gia hội thảo. Một số các em đã biết Python còn đứng lớp chỉ dạy Python cho các em khác. Thậm chí có một số em đã trình bày sản phẩm của chính mình trong phân mục tự giới thiệu (poster session), cùng với gần trăm sản phẩm khác từ các công ty lớn cũng như các chuyên gia trong ngành. Nữ giới chiếm gần 20% người tham gia hội thảo. Một số cá nhân trên 60 tuổi cũng thấy xuất hiện tại hội thảo. Thậm chí cả người chuyển đổi giới tính cũng có mặt! Có thể khẳng định chắc chắn rằng Python thu hút được sự quan tâm của tất cả mọi người.

2. Hội thảo năm nay có sự xuất hiện của nhiều nhóm nữ giới như CodeChix, WomanWhoCode, PyLadies, LadyCoders. Đây là một tín hiệu đáng mừng. So với các ngôn ngữ lập trình đa dụng khác, có lẽ Python là ngôn ngữ thu hút nữ giới nhiều nhất!

3. Hội thảo năm nay có một sự ngạc nhiên thú vị. Mỗi cá nhân tham dự hội thảo được tặng một thiết bị Rasberry Pi miễn phí! Điều này cho thấy Python có một vị trí quan trọng trong thị trường phần cứng nghiệp dư, kinh tế. Trong hội thảo cũng thiết lập một "Phòng nghiên cứu Rasberry Pi" để hướng dẫn cách cài đặt và sử dụng thiết bị đó. Một số người tham dự

4. Các bài tham luận chớp nhoáng (lightning talks) cho thấy Python có khả năng ứng dụng rộng rãi và đáng ngạc nhiên. Một số ví dụ điển hình như sử dụng Python để điều khiển hệ thống đèn LED nhấp nháy, sử dụng Python trên các hệ máy Atari, sử dụng Python để thông dịch và gỡ rối mã Lisp/Scheme.

5. Lập trình viên Python có khả năng Rap không thua kém các ca sĩ chuyên nghiệp! Đoạn phim bên dưới quay lại cảnh Larry Hastings, thành viên nòng cốt trong nhóm phát triển Python, hát bài rap tự chế trước phiên tham luận chớp nhoáng.

http://youtu.be/rKiySLUrYQ8

6. Bạn Nguyễn Thành Nam có mặt tại hội thảo đã chụp tấm hình tự sướng này.

https://docs.google.com/file/d/0B5X03CmpgiFeb3pfMTlIVlBCQ3M/edit?usp=sharing

Bạn Nam cũng đã ủng hộ một số món quà nhỏ cho nhóm PCNV dùng để khuyến khích các bạn đã có bài viết đóng góp cho nhóm. Nhóm PCNV sẽ gửi trực tiếp cho các bạn.

Cảnh báo: Cẩn thận khi sử dụng pip trong mạng

written by vithon, on Feb 4, 2013 2:37:20 AM.

Pip (http://pypi.python.org/pypi/pip), cũng như một số các công cụ sử dụng mô-đun có sẵn urllib, urllib2, httplib, có thể bị tấn công kẻ ở giữa (man-in-the-middle) và làm thay đổi nội dung của gói tin nhận được.

Lý do là khi truy cập vào một địa chỉ HTTPS hoặc các giao thức SSL/TLS, các mô-đun có sẵn này không kiểm tra chứng từ (certificate) của máy chủ để xác nhận máy chủ đó đúng là máy chủ chương trình cần truy cập. Điều này cho phép một kẻ thứ ba giả dạng máy chủ và thay đổi nội dung gói tin yêu cầu cũng như trả lời.

Do đó, người sử dụng cần cẩn trọng khi sử dụng các công cụ như pip trong một mạng không an toàn.

Thông tin thêm có thể được tham khảo tại:

http://www.reddit.com/r/Python/comments/17rfh7/warning_dont_use_pip_in_an_untrusted_network_a/

Kể từ Python 3.2, các mô-đun có sẵn liên quan đến SSL/TLS đã có thêm chức năng kiểm tra chứng từ nhưng chức năng này phải được tự kích hoạt.

Chương trình PyCon US 2013

written by vithon, on Jan 18, 2013 11:04:06 PM.

Chương trình hội thảo PyCon năm nay đã được công bố vào bốn ngày trước.

https://us.pycon.org/2013/schedule/

Sau nhiều tuần đánh giá các bài tham luận, chương trình của hội thảo cuối cùng đã được thống nhất. Năm nay số lượng người tham dự tăng đột ngột và 1000 vé bán trước (early bird) đã không còn nữa. Tổng lượng vé bán ra được giới hạn ở mức 2500 vé. Nếu bạn muốn tham dự hội thảo, bạn cần phải mua vé ngay bây giờ. Hội thảo bắt đầu từ ngày 13 đến ngày 21 tháng 03.

So với năm ngoái, hội thảo năm nay có thêm một chuyên mục thứ sáu, nâng tổng số bài tham luận lên 114 bài. Các bài điểm của hội thảo sẽ được trình bày bởi những nhân vật tên tuổi như Eben Upton, Jessica McKellar, Raymond Hettinger, và Guido van Rossum.

PS: Nếu không có điều kiện tham dự hội thảo, các bạn cũng có thể xem phim quay lại tại http://pyvideo.org.

wiki.python.org bị tấn công

written by vithon, on Jan 9, 2013 12:54:24 AM.

Brian Curtin vừa gửi thông báo trên hộp thư chung python-dev với nội dung như sau:

"Vào ngày 28 tháng 12, một cá nhân chưa xác định được đã sử dụng một lỗi chưa công bố để tấn công http://wiki.python.org. Cá nhân này đã lấy được quyền truy cập như người dùng moin trên máy chủ, nhưng các dịch vụ khác vẫn chưa bị ảnh hưởng."

Mọi dữ liệu cá nhân cũng như mật khẩu tại http://wiki.python.org xem như đã nằm trong tay kẻ xấu. Nếu bạn có truy cập và tạo tài khoản trên trang http://wiki.python.org và sử dụng cùng mật khẩu ở các trang mạng khác thì hãy đổi ngay mật khẩu ở các trang mạng khác đó.

Thông tin cập nhật sẽ được Brian Curtin gửi trên python-dev.

Truy vấn triệu bản ghi với MySQL

written by Nguyễn Thành Nam, on Jul 25, 2012 11:56:00 AM.

Trong bài viết của bạn Phạm Thị Minh Hoài, chúng ta đã được hướng dẫn cách truy vấn MySQL với DB-API của Python. Phần này chúng ta sẽ bàn về một số điểm cần chú ý nếu câu truy vấn của chúng ta trả về một lượng lớn bản ghi từ MySQL.

Hết bộ nhớ

Vấn đề

Một câu truy vấn có thể trả về một lượng dữ liệu khổng lồ. Ví dụ câu truy vấn SELECT * FROM users có thể trả về hơn chục triệu bản ghi ở các ứng dụng lớn. Trong điều kiện bình thường, có lẽ lượng bản ghi lớn như vậy sẽ khiến cho chương trình của chúng ta sử dụng hết bộ nhớ và bị buộc phải chấm dứt giữa chừng, cho dù chúng ta có sử dụng fetchone, fetchmany hay fetchall để lấy dữ liệu.

Lý do là phần giao tiếp giữa Python và MySQL mặc định sẽ lấy tất cả các bản ghi của câu truy vấn về trước, chứa chúng trong bộ nhớ, rồi sau đó trả về cho Python một bản ghi, nhiều bản ghi, hay tất cả các bản ghi đó tùy thuộc vào hàm nào được gọi.

Điều này cũng dễ hiểu vì giao thức mạng của MySQL là mô hình yêu cầu/đáp trả (request/response). Một truy vấn là một yêu cầu. Và các bản ghi của truy vấn đó là một đáp trả. Cho nên trình điều khiển (driver) cần phải đọc hết toàn bộ đáp trả để kết thúc chu trình yêu cầu/đáp trả trước khi trả lời các lời gọi hàm fetchone, fetchmany hay fetchall. Nói một cách khác, các hàm fetchone hay fetchmany trả về kết quả đã có trong bộ nhớ.

Đó cũng chính là lý do vì sao chúng ta có thể gọi fetchone hay fetchmany nhiều lần. Các hàm này không tạo một chu trình yêu cầu/đáp trả mới mà chỉ đơn giản là tiếp tục trả về các bản ghi đã chứa trong bộ nhớ.

Cách khắc phục

Cách khắc phục là sử dụng SSCursor khi tạo con trỏ từ kết nối MySQL. Lớp SSCursor nằm trong mô-đun MySQLdb.

conn = MySQLdb.connect(...)
cursor = MySQLdb.SSCursor(conn)

Sau đó khi gọi fetchone hoặc fetchmany thì trình điều khiển sẽ không đọc toàn bộ đáp trả vào bộ nhớ nữa mà sẽ chỉ đọc vừa đủ để trả về bấy nhiêu bản ghi cho ta.

Lưu ý

Khi sử dụng SSCursor, ta nhất định phải đảm bảo chu trình yêu cầu/đáp trả được hoàn tất. Ví dụ câu truy vấn trả về 10 bản ghi, thì ta phải đảm bảo đọc hết 10 bản ghi này. Nếu ta chỉ gọi fetchone 5 lần, thì sẽ còn 5 bản ghi vẫn chưa được đọc hết, và do đó ta sẽ không thể gửi truy vấn khác trong cùng kết nối hiện tại.

SSCursor không giữ kết quả trong bộ nhớ nên ta sẽ không thể di chuyển con trỏ tới, hoặc lùi để truy xuất bản ghi ta cần. Điều duy nhất chúng ta có thể làm với SSCursor là đọc tuần tự tất cả các bản ghi.

Hết giờ (timeout)

Vấn đề

Với SSCursor ta có thể sẽ viết mã như sau:

row = cursor.fetchone()
while row:
    # xử lý row
    row = cursor.fetchone()

Đoạn mã này đôi khi sẽ gây ra lỗi 2013 (Lost connection to MySQL server during query).

Lý do là việc xử lý từng bản ghi sẽ làm ta tốn thời gian, và ta sẽ không thể đọc đáp trả đủ nhanh, khiến cho máy chủ MySQL phải hoãn việc gửi tiếp các bản ghi về cho ta. Máy chủ MySQL chỉ có thể hoãn việc gửi thông tin trong một thời gian ngắn. Quá thời gian này, máy chủ sẽ tự ngắt kết nối.

Cách khắc phục

Tốt nhất là chúng ta sử dụng SSCursor để đọc tất cả các bản ghi từ máy chủ ở xa và ghi chúng vào một tập tin trên máy hiện tại. Sau đó ta đọc lại từ tập tin này và xử lý từng bản ghi đã lưu. Khi làm như vậy, chúng ta tránh được lỗi hết bộ nhớ đã đề cập ở trên, và hy vọng rằng việc ghi bản tin ra dĩa xảy ra đủ nhanh để ta có thể đọc bản ghi khác gần như ngay lập tức, tránh được lỗi hết giờ.

Lưu ý

Với việc ghi bản tin ra dĩa (hoặc một nơi nội bộ nào khác), chúng ta cần đảm bảo rằng nơi đó có đủ chỗ để lưu toàn bộ các bản ghi đọc được.

Tham số tự động

written by Nguyễn Thành Nam, on Jul 23, 2012 8:41:30 AM.

Tham số mặc định

Python cho phép chúng ta khai báo hàm với tham số mặc định như sau:

def function(arg_1, arg_2={}):
    arg_2[arg_1] = True
    print (arg_2)

Với khai báo hàm trên, tham số arg_2* trở thành một tham số mặc định và sẽ nhận giá trị là một từ điển rỗng. Khi gọi hàm function, chúng ta có thể không cung cấp giá trị cho tham số arg_2. Ví dụ khi thực hiện lệnh gọi sau, trên màn hình sẽ xuất hiện chuỗi {1: True}:

function(1)   # in ra '{1: True}'

Nếu tiếp tục gọi một lần tương tự, chúng ta nhận được một kết quả ngoài mong đợi.

function(2)  # in ra '{1: True, 2: True}'

Lần này, thay vì chỉ in ra từ điển với một khóa {2: True}, ta nhận được cả hai giá trị.

Ý nghĩa của tham số mặc định

Lý giải điều này không khó. Trong mục 4.7.1 của Bài chỉ dẫn Python có nêu:

Giá trị mặc định được định giá tại nơi hàm được định nghĩa.

Điều này dẫn đến hệ quả là:

Giá trị mặc định chỉ được định giá một lần. Điểm này quan trọng khi [giá trị] mặc định là một giá trị khả biến như danh sách, từ điển hoặc các đối tượng của hầu hết mọi lớp.

Do đó, với ví dụ của hàm function ở trên, tham số arg_2 sẽ nhận giá trị mặc định là từ điển được tạo ra ngay khi dòng lệnh def function(...) được dịch và thực thi. Trong các lần gọi lệnh function sau đó, nếu không xác định tham số cho arg_2, thì arg_2 sẽ chỉ đến cùng một từ điển này. Và vì thế mọi tác động đến arg_2 đều tác động đến cùng một đối tượng từ điển.

Thông thường, cách giải quyết vấn đề này sẽ bao gồm:

  1. Xác định giá trị mặc định là None ở câu lệnh def
  2. Khởi tạo giá trị mặc định mới và gán nó vào biến ở trong thân hàm nếu giá trị hiện tại là None

Hàm function sẽ được sửa lại như sau:

def function(arg_1, arg_2=None):
    if arg_2 is None:  # kiểm tra arg_2 có phải None không
        arg_2 = {}     # gán đối tượng từ điển mới vào arg_2
    arg_2[arg_1] = True
    print (arg_2)

Mô-đun auto_argument

Để đơn giản hóa và tránh lập đi lập lại dòng lệnh if, tôi đã phát triển một thư viện nhỏ đặt tên là auto_argument (thông số tự động).

Với thư viện này, chúng ta có thể viết lại hàm function như sau:

@callable_auto_argument([('arg_2', None, dict)])
def function(arg_1, arg_2=None):
    arg_2[arg_1] = True
    print (arg_2)

Điểm khác biệt chính là ở việc sử dụng bộ trang hoàng callable_auto_argument để tự động thay thế tham số arg_2 với giá trị trả về từ lệnh gọi dict().

Bộ trang hoàng auto_argument (lớp cha của callable_auto_argument) nhận một dãy các bộ 3 (tên tham số, giá trị dấu, giá trị thay thế) (argument name, marker, value). Khi tham số có giá trị là giá trị dấu thì giá trị của tham số sẽ được thay bằng giá trị tạo ra từ giá trị thay thế. callable_auto_argument tạo ra giá trị thay thế bằng cách gọi hàm giá trị thay thế. Người dùng cũng có thể tạo lớp con của các bộ trang hoàng này để tùy chỉnh giá trị thay thế riêng hoặc tránh phải lập lại giá trị dấu. Xem qua hàm test_subclass và lớp auto_dict_argument trong mã nguồn.

Mã nguồn của các bộ trang hoàng này được liệt kê ở ngay dưới. Mã nguồn này được cung cấp theo điều khoản bản quyền MIT. Người dùng có thể tùy ý sử dụng, hay sửa đổi mã nguồn cho hợp với nhu cầu.

'''Automatically replace a default argument with some other (potentially
dynamic) value.

The default argument is usually guarded like this::

    def func(arg=None):
        if arg is None:
            arg = dict()
        // use arg

With decorators provided in this module, one can write::

    __marker = object()

    @callable_auto_argument([('arg', __marker, dict)])
    def func(arg=__marker):
        // use arg

See class:`callable_auto_argument`.

Also, the standard Python behavior could be thought as::

    __marker = object()

    @passthru_auto_argument([('arg', __marker, {})])
    def func(arg=__marker):
        // ...

See class:`passthru_auto_argument`.

These classes can be used by themselves or serve as base classes for more
customizations. For example, to eliminate repeated typings, one can subclass
``callable_auto_argument`` like this::

    class auto_dict_argument(callable_auto_argument):

        def __init__(self, *names):
            names_markers_values = []
            for name in names:
                names_markers_values.append((name, None, dict))
            super(auto_dict_argument, self).__init__(names_markers_values)

And then apply this on methods like this::

    @auto_dict_argument('arg_1', 'arg_2', 'arg_3')
    def method(arg_1=None, arg_2=None, arg_3=None):
        # arg_1, arg_2, arg_3 are new dicts unless specified otherwise
        # and these lines are no longer required
        # if arg_1 is None: arg_1 = {}
        # if arg_2 is None: arg_2 = {}
        # if arg_3 is None: arg_3 = {}

'''


import unittest


class auto_argument(object):
    '''The base class for automatic argument.

    Subclasses must implement method:`create` to create appropriate value for
    the argument.

    '''

    def __init__(self, names_markers_values):
        '''Construct an auto argument decorator with a collection of variable
        names, their markers, and their supposed values.

        The __supposed__ value objects are used in method:`create` to produce
        final value.

        Args:

            names_markers_values (collection of 3-tuples): A collection of
                (string, object, object) tuples specifying (in that order) the
                names, the marker objects, and the supposed value objects

        '''

        self.names_markers_values = names_markers_values

    def create(self, name, current_value, value):
        '''Return a value based for the named argument and its current value.

        This final value will be used to replace what is currently passed in
        the invocation.

        Subclasses MUST override this method to provide more meaningful
        behavior.

        Args:

            name (string): The argument's name
            current_value: Its current value in this invocation
            value: The supposed value passed in during construction time

        Returns:

            Final value for this argument

        '''

        raise NotImplementedError()

    def __call__(self, orig_func):
        def new_func(*args, **kw_args):
            for name, marker, value in self.names_markers_values:
                # check kw_args first
                try:
                    if kw_args[name] is marker:
                        kw_args[name] = self.create(name, kw_args[name], value)
                except KeyError:
                    # ignored
                    pass
                else:
                    continue

                # then check args
                # we need to instropect the arg names from orig_func
                co_obj = orig_func.func_code
                # number of required arguments
                nr_required = (co_obj.co_argcount -
                                    len(orig_func.func_defaults))
                for i in range(nr_required, co_obj.co_argcount):
                    if co_obj.co_varnames[i] != name:
                        continue
                    # is it supplied in args?
                    if i < len(args):
                        if args[i] is marker:
                            if isinstance(args, tuple):
                                args = list(args)
                            args[i] = self.create(name, args[i], value)
                    # it is not, so, check defaults
                    else:
                        default = orig_func.func_defaults[i - nr_required]
                        if default is marker:
                            kw_args[name] = self.create(name, default, value)

            # invoke orig_func with new args
            return orig_func(*args, **kw_args)

        return new_func


class callable_auto_argument(auto_argument):

    def create(self, name, current_value, value):
        # call on value
        return value()


class passthru_auto_argument(auto_argument):

    def create(self, name, current_value, value):
        # just return it directly
        return value


class AutoArgumentTest(unittest.TestCase):

    def test_keyword_1(self):

        marker = 'replace_me'

        @passthru_auto_argument([('arg_name', marker, 'done')])
        def orig_func_0(arg_name=marker):
            return arg_name

        self.assertEqual('done', orig_func_0())
        self.assertEqual('done', orig_func_0(marker))
        self.assertEqual('not_replace', orig_func_0('not_replace'))
        self.assertEqual('done', orig_func_0(arg_name=marker))
        self.assertEqual('not_replace', orig_func_0(arg_name='not_replace'))

        @passthru_auto_argument([('arg_name', 'replace_me', 'done')])
        def orig_func_1(junk, arg_name='replace_me'):
            return junk, arg_name

        self.assertEqual(('ignore', 'done'), orig_func_1('ignore'))
        self.assertEqual(('ignore', 'done'),
                orig_func_1('ignore', marker))
        self.assertEqual(('ignore', 'not_replace'),
                orig_func_1('ignore', 'not_replace'))
        self.assertEqual(('ignore', 'done'),
                orig_func_1('ignore', arg_name=marker))
        self.assertEqual(('ignore', 'not_replace'),
                orig_func_1('ignore', arg_name='not_replace'))

    def test_keyword_2(self):

        marker_1 = 'replace_me'
        marker_2 = 'replace_too'

        @passthru_auto_argument([('arg_1', marker_1, 'done'),
                     ('arg_2', marker_2, 'enod')])
        def orig_func_0(arg_1=marker_1, arg_2=marker_2):
            return arg_1, arg_2

        self.assertEqual(('done', 'enod'), orig_func_0())
        self.assertEqual(('not_replace', 'enod'), orig_func_0('not_replace'))
        self.assertEqual(('done', 'not'), orig_func_0(marker_1, 'not'))
        self.assertEqual(('done', 'enod'), orig_func_0(marker_1, marker_2))
        self.assertEqual(('not_1', 'not_2'), orig_func_0('not_1', 'not_2'))

        @passthru_auto_argument([('arg_1', marker_1, 'done'),
                     ('arg_2', marker_2, 'enod')])
        def orig_func_1(junk, arg_1=marker_1, arg_2=marker_2):
            return junk, arg_1, arg_2

        self.assertEqual(('.', 'done', 'enod'), orig_func_1('.'))
        self.assertEqual(('.', 'not_replace', 'enod'),
                orig_func_1('.', 'not_replace'))
        self.assertEqual(('.', 'done', 'not'),
                orig_func_1('.', marker_1, 'not'))
        self.assertEqual(('.', 'done', 'enod'),
                orig_func_1('.', marker_1, marker_2))
        self.assertEqual(('.', 'not_1', 'not_2'),
                orig_func_1('.', 'not_1', 'not_2'))

    def test_subclass(self):

        class auto_dict_argument(callable_auto_argument):

            def __init__(self, *names):
                names_markers_values = []
                for name in names:
                    names_markers_values.append((name, None, dict))
                super(auto_dict_argument, self).__init__(names_markers_values)

        @auto_dict_argument('arg_1', 'arg_2')
        def test_func(arg_1=None, arg_2=None):
            arg_1['1'] = 'arg_1'
            arg_2['2'] = 'arg_2'
            return (arg_1, arg_2)

        self.assertEqual(({'1': 'arg_1'}, {'2': 'arg_2'}), test_func())
        arg_1, arg_2 = test_func()
        self.assertNotEqual(id(arg_1), id(arg_2))


if __name__ == '__main__':
    unittest.main()

Lập trình giao diện trong Python

written by Nguyễn Thành Nam, on Jul 18, 2012 1:06:00 PM.

Bài viết này bàn về hướng lập trình giao diện (programming to the interface) thay vì giao diện người dùng, hay giao diện đồ họa (user interface, graphical interface).

Lập trình giao diện

Lập trình giao diện (giao tiếp) là một trong hai nguyên tắc cơ bản được nhắc đến trong quyển sách kinh điển Design Patterns. Lập trình giao diện đặt trọng tâm vào việc tạo ra một hợp đồng (contract) chắc chắn giữa hai bên người dùng (consumer) và người cung cấp (producer):

  1. Người dùng: là bên sử dụng các lớp, hàm, hoặc mã mà người cung cấp tạo ra
  2. Người cung cấp: là bên tạo ra thư viện mã
  3. Hợp đồng: là các kiến thức chung mà cả người dùng và người cung cấp đều hiểu và tuân theo

Hợp đồng xác định các lớp cơ bản, và các phương thức của các lớp đó. Vì đây là hợp đồng được cả hai bên tuân theo nên ý nghĩa của các lớp, cũng như cách hoạt động của các phương thức phải được định nghĩa một cách tường minh và chuẩn xác. Người dùng sẽ dựa theo hợp đồng này và sử dụng các lớp trong đó cho mục đích riêng của họ, trong khi người cung cấp có thể thay đổi cách hiện thực hợp đồng một cách khả chuyển miễn sao vẫn đảm bảo được yêu cầu của hợp đồng.

Ví dụ: hợp đồng nêu ra là hàm func nhận vào một số nguyên, và trả về tổng của số nguyên đó với 2. Hợp đồng này rõ ràng ví nó xác định tên hàm (func), giá trị đầu vào (một số nguyên) và giá trị đầu ra (tổng của số nguyên nhập vào với 2). Người sử dụng, khi cần tính tổng của 5 và 2, có thể gọi func(5). Người cung cấp có thể cài đặt def func(i): return i + 2 hoặc func = lambda i: i + 2 hoặc một cách nào khác miễn sao phù hợp với câu chữ của hợp đồng.

Theo quan điểm này, người sử dụng và người cung cấp giao tiếp với nhau thông qua một môi trường chung, một giao diện chung. Người sử dụng dựa trên giao diện này để yêu cầu dịch vụ từ người cung cấp. Còn người cung cấp dựa trên giao diện này để đáp ứng đúng yêu cầu người sử dụng.

Đối với các ngôn ngữ lập trình hướng đối tượng, chúng ta hay gặp các thuật ngữ như giao diện (interface), lớp trừu tượng (abstract class) và lớp cụ thể (concrete class). Giao diện mang tính chất của một hợp đồng, hoàn toàn không có bất kỳ một hiện thực cụ thể nào. Lớp cụ thể là một hiện thực đầy đủ của một giao diện. Và lớp trừu tượng là một hiện thực chưa đầy đủ của một giao diện.

Lập trình giao diện trong Python

Python không có sự phân biệt giữa ba khái niệm này. Tuy nhiên, trong thực tế, chúng ta hay viết mã tương tự như sau để mô phỏng một giao diện.

class Interface(object):
    def method_1(self):
        raise NotImplementedError()
    def method_2(self):
        raise NotImplementedError()

Các phương thức trong lớp Interface chỉ có một câu lệnh duy nhất là raise NotImplementedError() để cố tình gây ra biệt lệ khi chúng được gọi.

Một lớp trừu tượng có thể được mô phỏng như sau:

class AbstractClass(Interface):
    def method_1(self):
        # làm một việc gì đó
        pass

Lớp AbstractClass là lớp con của Interface và hiện thực hóa phương thức method_1 nhưng vẫn để phương thức method_2 lơ lửng.

Cuối cùng, một lớp cụ thể là một lớp con của Interface hoặc AbstractClass nhưng hiện thực hóa tất cả các phương thức.

class ConcreteClass(AbstractClass):
    def method_2(self):
        # làm một việc gì đó
        pass

Điểm đáng lưu ý là tất cả các lớp này đều hợp lệ trong Python. Chúng ta hoàn toàn có thể khởi tạo một đối tượng của một trong ba lớp này và chỉ đến khi gọi một hàm nào đó thì biệt lệ mới xảy ra. Nói cách khác, biệt lệ NotImplementedError chỉ được phát hiện khi chạy, trong khi đáng lẽ nó đã phải được phát hiện ngay khi dịch. Điều này làm mất giá trị của một hợp đồng, và hạn chế việc áp dụng hướng lập trình giao diện trong Python.

Để khác phục điểm yếu này, tôi đã viết một bộ trang hoàng (decorator) nhỏ để bắt lỗi ngay khi một lớp cụ thể được định nghĩa thiếu. Định nghĩa thiếu ở đây mang ý nghĩa rằng lớp cụ thể đã quên định nghĩa một phương thức nào đó.

'''Set of (class) decorators to help with interface programming.

Examples::

    @concrete
    class subclass(parent):
        ...

Released to the public domain.

Nam T. Nguyen, 2012

'''

import dis
import types
import unittest


def concrete(orig_class):
    '''A decorator to ensure that a concrete class has no un-implemented
    methods.

    An un-implemented method is defined similarly to::

        def method(...):
            raise NotImplementedError()

    This, when translated to bytecode, looks like::

        LOAD_GLOBAL       0 (NotImplementedError)
        CALL_FUNCTION     1
        RAISE_VARARGS     1
        ...

    Or when ``raise NotImplementedError``::

        LOAD_GLOBAL       0 (NotImplementedError)
        RAISE_VARARGS     1
        ...

    The check here is for such pattern.

    '''

    for name in dir(orig_class):
        func = getattr(orig_class, name)
        # correct type?
        if type(func) not in (types.FunctionType, types.MethodType):
            continue
        # check if first name is NotImplementedError
        if len(func.func_code.co_names) < 1 or \
                func.func_code.co_names[0] != 'NotImplementedError':
            continue
        # and RAISE_VARARGS somewhere after that
        for position in (3, 6):
            if len(func.func_code.co_code) < position:
                continue
            opcode = ord(func.func_code.co_code[position])
            if dis.opname[opcode] == 'RAISE_VARARGS':
                raise SyntaxError('Function %s.%s must be implemented.' % (
                        orig_class.__name__, func.func_name))

    return orig_class


class ConcreteTest(unittest.TestCase):

    def test_raise(self):
        class test1:
            def method(self):
                raise NotImplementedError
        self.assertRaises(SyntaxError, concrete, test1)

        class test2:
            def method(self):
                raise NotImplementedError()
        self.assertRaises(SyntaxError, concrete, test2)

    def test_not_raise(self):
        class test:
            def method(self):
                return NotImplementedError()
        concrete(test)

    def test_subclass(self):
        class parent(object):
            def override_me(self):
                raise NotImplementedError()
        class test(parent):
            def leave_it(self):
                pass
        self.assertRaises(SyntaxError, concrete, test)


if __name__ == '__main__':
    unittest.main()

Để sử dụng bộ trang hoàng concrete này, ta sẽ sửa lại lớp ConcreteClass như sau:

@concrete
class ConcreteClass(AbstractClass):
    # như trên

Nếu ConcreteClass quên hiện thực hóa một phương thức nào đó, Python sẽ thông báo SyntaxError ngay sau khi ConcreteClass được định nghĩa.

Tóm tắt

Lập trình giao diện giúp chúng ta tách biệt hai yếu tố là cái gì, và như thế nào. Người sử dụng quan tâm nhiều đến câu hỏi cái gì được thực hiện hơn là làm sao để thực hiện chúng. Trong khi người cung cấp có thể dễ dàng thay đổi cách thực hiện miễn sao vẫn đảm bảo câu chữ của hợp đồng.

Python không có sẵn cơ chế giao diện và lớp trừu tượng như các ngôn ngữ lập trình khác. Chúng ta phải sử dụng bộ trang hoàng concrete để bắt lỗi định nghĩa thiếu.

Hy vọng bài viết ngắn này sẽ đem lại cho bạn đọc một mẹo lập trình giao diện hữu ích trong Python.

Python - Ngôn Ngữ Lập Trình Tốt Nhất

written by vithon, on Dec 10, 2011 2:13:51 AM.

Tạp chí Linux Journal đã công bố kết quả khảo sát người đọc hàng năm của mình vào ngày 01 tháng 12 vừa qua. Theo đánh giá này, Python lại một lần nữa trong suốt ba năm liên tục được người đọc đánh giá là ngôn ngữ lập trình tốt nhất.

Thông tin chi tiết có thể được xem thêm từ nguồn Linux Journal.

Câu chuyện tối ưu hóa đoạn mã Python

written by Nguyễn Thành Nam, on Oct 26, 2011 9:21:36 AM.

Trên trang blog của Dropbox ngày hôm qua có chia sẻ kinh nghiệm của họ trong việc tối ưu hóa một đoạn mã Python ngắn.

http://tech.dropbox.com/?p=89

Kết luận của họ là:

  1. Các kỹ thuật căn bản như inline hàm (inline function), tự lặp (implicit loop), tận dụng mã C đều đúng.
  2. Cấu trúc setdict trong Python rất nhanh.
  3. Việc sử dụng biến nội bộ thay cho toàn cục mặc dù có ích, nhưng không nhiều.
  4. Ghép chuỗi (string concatenation) nhanh hơn nhiều so với định dạng chuỗi (string interpolation).

Đây là một bài chia sẻ rất hay.

Hình ảnh tại Software Freedom Day 2010

written by Nguyễn Thành Nam, on Oct 24, 2011 7:50:31 AM.

Hôm nay tự nhiên thấy trên máy có mấy tấm hình cũ ở Ngày Phần Mềm Tự Do 2010 vẫn chưa được đăng.

Năm 2010, thành viên Phan Đắc Anh Huy đã trình bày về phần mềm Vithon Forum (chính là phần mềm được sử dụng để làm Diễn đàn của nhóm PCNV). Sau đây là các tấm hình chụp hôm đó.

img20100918104740.th.jpg img20100918104748.th.jpg img20100918104833.th.jpg

Điều kiện ánh sáng tuy không được tốt nhưng cũng phần nào thể hiện được cái đầu du côn và thân hình phì lũ của Huy.