Decorator Function cho methods trong Python
Python
40
White

studybot viết ngày 23/02/2016

Hôm nay google tìm ra một cách sử dụng python decorator rất hay nên ghi lại đây share cho anh em.

Vấn đề là gì?

Hôm nay trong lúc code gặp phải đoạn code như sau:


class LdapUtil(object):

    def add_user(self, spec):
        try:
            self.conn = ldap.initialize(self.ldapurl)
            self.conn.bind_s(self.binddn, self.credential)
            self.conn.add_s(spec)
       finally:
            self.conn.unbind()


    def remove_user(self, spec):
        try:
            self.conn = ldap.initialize(self.ldapurl)
            self.conn.bind_s(self.binddn, self.credential)
            self.conn.remove_s(spec)
       finally:
            self.conn.unbind()

2 methods add_userremove_user thêm và xoá user lưu trên ldap backend sử dụng conn connection. Mỗi lần muốn gọi thủ tục của conn lại phải mở kết nối và đóng kết nối. Code trong hàm python cứ lập đi lập lại. Thế là mình tự hỏi có cách nào để giảm việc viết đi viết lại một dòng code không và mình nghĩ ra 2 giải pháp:

  1. Chuyện đoạn mã mở và đóng kết nối về một methods riêng và gọi methods đó khi muốn mở hay đóng kết nói.
  2. Sử dụng decorator

Thử các giải pháp

1. chuyển về một methods và gọi methods đấy khi cần thiết

class LdapUtil(object):

    def open_connection(self):
        self.conn = ldap.initialize(self.ldapurl)
        self.conn.bind_s(self.binddn, self.credential)

    def close_connection(self):
        self.conn.unbind()

    def add_user(self, spec):
        try:
            self.open_connection()
            self.conn.add_s(spec)
       finally:
            self.close_connection()


    def remove_user(self, spec):
        try:
            self.open_connection()
            self.conn.remove_s(spec)
       finally:
            self.close_connection()

Nhìn qua thấy code chả ngắn đi tí nào, lại còn thêm method. Code gọi open_connection vẫn bị lập đi lập lại. Cách này thực ra chỉ là chuyển 2 dòng code về 1 hàm và gọi hàm này. Nếu các dòng mã này dài, việc chuyển này có vẻ có lợi. Ở đây, số lượng dòng mã tiết kiệm được chỉ có 1 dòng, nên cũng không có lợi lắm. Cách làm này tỏ ra không hiệu quả.

2. Sử dụng python decorator

Sau một hồi tìm hiểu và google phát hiện ra rằng với những trường hợp này, ta có thể sử dụng decorator pattern và python hỗ trợ cú pháp trong sáng và ngắn gọn cho pattern này. Tuy nhiên python decorator chỉ có decorator cho function (hay methods) mà không có decorator cho class methods, và do đó không truyền self được. Do vậy mình phải sử dụng wrapper để trả về decorator và do vậy có thể truyền được self. Chi tiết mình có đoạn code như sau:

def prepare_connection(func):
    def wrapper(self, *args):
        try:
            self.conn = ldap.initialize(self.ldapurl)
            self.conn.bind_s(self.binddn, self.credential)
            func(self, *args)
        finally:
            self.conn.unbind()
    return wrapper


class LdapUtil(object):
    @prepare_connection
    def add_user(self, spec):
        self.conn.add_s(spec)

    @prepare_connection
    def remove_user(self, spec):
        self.conn.remove_s(spec)

ở đây prepare_connection là một decorator function (hàm nhận một function khác và gọi function đó). Hàm này trả về một wrapper function với đối số đầu tiên là self là reference đến đối tượng của class LdapUtil. wrapper sẽ thực hiện việc chuẩn bị connection và gọi hàm được cung cấp cho decorator. Đồng thời hàm cũng tự động đóng kết nối sau khi gọi xong hàm người dùng. Bằng cách viết như thế này:

  • Quy trình gọi sẽ là wrapperself.add_user(spec).
  • Các hàm người dùng sẽ không cần phải lo mở đóng kết nối, chỉ cần lo phần business-logic là okie.

Nhìn cách này có vẻ gọn gàng nên mình quyết định làm thế này. Có ai có cách khác không?

3. Ví dụ khác

Thử tìm hiểu thấy có ví dụ khác nào đã được việc lại chưa thấy có rồi :D
Về ứng dụng khác của decorator function trong python, bạn có thể tham khảo bài viết sau của anh Vũ Nhật Minh: http://kipalog.com/posts/Memoization-and-Decorator

Kết luận

Có thể dùng decorator cho object method như cách làm như trên. Code sẽ ngắn đi rất nhiều :D.

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

studybot

15 bài viết.
9 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
14 12
Một vài thủ thuật vim mới biết, ghi lại cho khỏi quên Căn lề các cột dữ liệu Để căn lề ấn phím esc (để sang command mode) :%column t Chọn th...
studybot viết 3 năm trước
14 12
White
9 9
Javascript context (ngữ cảnh?) là một khái niệm khó hiểu trong Javascript. Tuy vậy để code Javascript thành thạo thì việc nắm vững context là điểu ...
studybot viết 3 năm trước
9 9
White
8 1
Một vài ghi chép về Java Hotspot GC Garbage collector thực hiện việc quản lý bộ nhớ thông qua 3 công việc: Gán đối tượng vào pool các đối tượng m...
studybot viết hơn 3 năm trước
8 1
Bài viết liên quan
White
1 0
Mở đầu Như đã nói ở bài trước, mình đang nghiên cứu về Spark nên cần log lại một số thứ để dành sau này dùng đến :smile: Đối tượng hướng đến vẫn ...
Phạm Quốc Thắng viết hơn 2 năm trước
1 0
White
5 3
Observer pattern (python example) 1. Observer là gì : Theo như (Link) Observer Pattern là : A software design pattern in which an object, calle...
Khôi Trọng Nguyễn viết gần 2 năm trước
5 3
White
0 0
Web Framework Flask định nghĩa route bằng annotations kiểu như @route('/users/add', methods='GET']) def user_add(): pass Lợi thế của cách là...
studybot viết gần 3 năm trước
0 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
15 bài viết.
9 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!