Python

Python Decorators

Xin chào, trong bài viết này chúng ta sẽ tìm hiểu về Decorator trong Python, khi nào cần áp dụng Decorator và cách viết Decorator thật hiệu quả.


DECORATORS – WHAT, WHEN & WHY?

Trong lập trình, debug, bảo trì và nâng cấp là công việc thường ngày. Giả giử chúng ta cần phải thêm chức năng logging cho tất cả các function trong chương trình (có thể lên đến hàng chục), nếu tìm đến từng function và chỉnh sửa thì thật mất thời gian, nhàm chán và gây ra trùng lặp trong code.

Decorator được sử dụng để giải quyết các vấn đề trên. Decorator cung cấp các tính năng mở rộng cho function nhưng không trực tiếp thay đổi function đó.

Với Decorator, chúng ta có thể thêm các tính năng như: analytics, logging, validation-check và runtime-check vào function, cũng như tạo framework và tái sử dụng code một cách hiệu quả.


REQUIREMENTS

Để hiểu rõ về Decorator, trước tiên chúng ta phải hiểu được một số khái niệm sau:

  • Global và Local Variable;
  • Function và Nested Function;
  • Scope rules;
  • Returning function;
  • Closure.

Các khái niệm về Global/Local Variable, Functions/Nested Functions và Scope Rules khá đơn giản nên chúng ta sẽ không đề cập đến trong khuôn khổ bài viết, các bạn có thể tự tìm hiểu.

RETURNING FUNCTION

Điều đầu tiên chúng ta cần nhớ: “Everything in Python is an object!“.

Cũng giống như mọi kiểu dữ liệu khác của Python, chúng ta có thể truyền functions vào một function khác theo dạng đối số và return function từ một function khác theo dạng giá trị trả về.

Example 1: Function as function’s argument

def Son():
	print "Hi Mom!"

def Mom(f):
	f()

Mom(Son)

Output:
Hi Mom!

Example 2: Function as function’s return value

def Son():
	print "Hi Mom!"

def Mom():
	return Son

retValue = Mom()
print(retValue)
retValue()

Output:
function Son at 0x0000….
Hi Mom!

Giải thích:
– retValue là giá trị trả về của function Mom() chứa địa chỉ của function Son() trong bộ nhớ;
– Để gọi function Son() ta sử dụng retValue().

CLOSURE

Để hình dung được Function Closure, hãy đọc example 3.

Example 3:

def f1():
	x = 99
	def f2():
		print(x)
	return f2

a = f1()
print(a)
a()

Khoan hãy chạy đoạn code trên! Ta thấy rằng x là một biến local bên trong function f1(), có nghĩa là x chỉ tồn tại khi f1() được gọi.
– Ở dòng 7, chúng ta đã gọi function f1() và gán kết quả trả về (địa chỉ của function f2()) vào biến a;
– Ở dòng 8, chúng ta in ra địa chỉ của function f2(), lúc này chắc hẳn biến x đã không còn tồn tại;
– Ở dòng 9, chúng ta gọi function f2() thông qua a(). Function f2() lại in ra giá trị x, có cái gì đó sai sai ở đây, liệu có xảy ra runtime-error???

Output:
function f2 at 0x0000….
99

Giải thích:
Đoạn code trên vẫn chạy tốt, tại sao vậy?
Python hỗ trợ một tính năng gọi là Function Closures, theo đó các inner functions được define bên trong một function khác (nested funcstions) sẽ ghi nhớ các enclosing namespaces mà nó nhìn thấy ở thời điểm nó được define. Như vậy biến x vẫn sẽ tồn tại đối với function f2().


DECORATOR

Decorator là một function nhận một function khác làm đối số và trả về một function mới.

Example 4: Simple Decoration

def my_decorator(f):
	def wrapper(n):
		print("I'm being called inside decorator.")
		f(n)
	return wrapper

def my_function(x=100):
    print(x)

my_function = my_decorator(my_function)
my_function(50)

Output:
I’m being called inside decorator.
50

Giải thích:
– function my_decorator() nhận một function khác làm tham số và kết quả trả về là function wrapper;
– Ở dòng 10, function my_decoration() có đối số là function my_function;

Example 5: Nested Decoration

def my_decorator(s):
	def wrapper1(f):
		def wrapper2(n):
			print(s)
			f(n)
		return wrapper2
	return wrapper1

def my_function(x=100):
    print x

my_function = my_decorator("Python and friends.")(my_function)
my_function(99)

Output:
Python and friends.
99

Giải thích:
– Ở dòng 12, function my_decorator() nhận một string làm đối số và trả về function wrapper1. Function wrapper1 lại nhận function my_function làm đối số và trả về function wrapper2. Địa chỉ của function wrapper2 được gán cho biến my_function;
– Ở dòng 13, chúng ta gọi function wrapper2 với đối số là 99.

Như vậy khác với example 4, function my_decorator() ở example 5 không trực tiếp nhận my_function làm đối số, thay vào đó là wrapper1 – function được trả về từ my_decorator().

THE @ SYMBOL

Qua example 4 và 5 chúng ta thấy rằng khi muốn sử dụng Decorator cho my_function, chúng ta cần phải gọi fuction my_decorator với đối số là my_function. Điều này khá bất tiện và dài dòng nếu một Decorator được sử dụng cho nhiều function. Rất may Python đã hỗ trợ chúng ta sử dụng Decorator với ký tự @.

Example 6:

def my_decorator(f):
    def wrapper(*args):
        print("I'm being called inside decorator.")
        f(args)
    return wrapper
 
def my_nested_decorator(s):
    def wrapper1(f):
        def wrapper2(*args):
            print(s)
            f(args)
        return wrapper2
    return wrapper1
 
@my_decorator
def my_function1(x):
    print(*x)
 
@my_nested_decorator("Python and friends.")
def my_function2(x):
    print(*x)
 
my_function1(1,2,3)
my_function2(4,5,6)

Output:
I’m being called inside decorator.
1 2 3
Python and friends.
4 5 6


SUMMARY

Qua bài viết này chúng ta đã tìm hiểu về Decorator và các khái niệm khác như Closure, Returning Function. Cảm ơn các bạn đã theo dõi bài viết.

Thân ái và quyết thắng.

Reference:
[1] Understanding Python Decorators in 12 Easy Steps!.
[2] Primer on Python Decorators.
[3] 5 reasons you need to learn to write Python decorators.
[4] Giới thiệu Decorator trong Python. 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s