Italian Trulli
Categories:

Decorator


Function Decorator

Decorator là một mẫu thiết kế cấu trúc cho phép bạn gắn các hành vi mới vào các đối tượng bằng cách đặt các đối tượng này bên trong các đối tượng bao bọc đặc biệt có chứa các hành vi.

Tôi sẽ bắt đầu với một ví dụ về hàm rất đơn giản như sau.

def func(a):
  def f_nested(b):
    return b + b 
    
  return f_nested

Chúng ta có một hàm tên là f_nested với một tham số là a bên trong một hàm có tên func, có tham số là b. Thực thi chương trình này bằng cách đưa lần lượt các đối số với giá trị 10, 20.

func(10)(20)

Đoạn code trên có thể viết ngắn gọn hơn với việc sử dụng hàm lambda.

def func(a):
  return lambda b: b+b
func(10)(20)

Hãy xem đoạn code sau:

from types import FunctionType

def f_nested(b):
  print("Chạy vào hàm f_nested")
  return b + b 

def func(func_obj : FunctionType):
  print("chạy vào hàm func")
  return func_obj

func(f_nested(10))

Từ việc thực thi đoạn code bên trên chúng ta có thể đi đến kết luận decorator là quá trình gọi một hàm trong một hàm với việc gắn một hàm làm tham số cho hàm còn lại.

from types import FunctionType

def func(func_obj : FunctionType):
  print("chạy vào hàm func")
  return func_obj

@func
def f_nested(b):
  print("Chạy vào hàm f_nested")
  return b + b 

f_nested(10)

Thử tạo decorator kiểm tra thời gian thực thi của một hàm bất kỳ với đoạn code đơn giản như sau.

from types import FunctionType
import time 

def check_time(func : FunctionType):
  def handle(*args):
    print("Bắt đầu chạy ...")
    start_time = time.time()
    result = func(*args)
    print("Hoàn thành trong {} giây".format(time.time() - start_time))

    return result 
  return handle

@check_time
def length(items):
  n = 0 
  while items is not None and items != items[1:]:
    n,items = n+1, items[1:]
  return n

length(range(10))

Mở rộng thêm

from types import FunctionType
import time 

def check_time(prefix = ""):
  def check(func : FunctionType):
    print(f"{prefix}\n")
    def handle(*args):
      print("Bắt đầu chạy hàm {}".format(func.__name__))
      start_time = time.time()
      result = func(*args)
      print("Hoàn thành trong {} giây".format(time.time() - start_time))

      return result 
    return handle
  return check

@check_time(prefix = "***Check time***")
def length(items):
  """ 
  Hàm kiểm tra số lượng phần tử của items
  """
  n = 0 
  while items is not None and items != items[1:]:
    n,items = n+1, items[1:]
  return n

length(range(10))

Kiểm tra name func

length.__name__
length.__doc__

Giá trị name và docstring của hàm length trả về không chính xác. Để khắc phục vấn đề này chúng ta có thể xử lý như sau.

from types import FunctionType
import time 

def check_time(prefix = ""):
  def check(func : FunctionType):
    print(f"{prefix}\n")
    def handle(*args):
      print("Bắt đầu chạy hàm {}".format(func.__name__))
      start_time = time.time()
      result = func(*args)
      print("Hoàn thành trong {} giây".format(time.time() - start_time))
      handle.__name__ = func.__name__ 
      handle.__doc__ = func.__doc__ 
      return result 
    return handle
  return check

@check_time(prefix = "***Check time***")
def length(items):
  """ 
  Hàm kiểm tra số lượng phần tử của items
  """
  n = 0 
  while items is not None and items != items[1:]:
    n,items = n+1, items[1:]
  return n

length(range(10))
length.__name__
length.__doc__

Và tốt hơn nên dùng giải pháp có sẵn là decorator wraps được tích hợp trong functools

from functools import wraps 
from types import FunctionType
import time 

def check_time(prefix = ""):
  def check(func : FunctionType):
    print(f"{prefix}\n")
    @wraps(func)
    def handle(*args):
      print("Bắt đầu chạy hàm {}".format(func.__name__))
      start_time = time.time()
      result = func(*args)
      print("Hoàn thành trong {} giây".format(time.time() - start_time))
      # handle.__name__ = func.__name__ 
      # handle.__doc__ = func.__doc__ 
      return result 
    return handle
  return check

@check_time(prefix = "***Check time***")
def length(items):
  """ 
  Hàm kiểm tra số lượng phần tử của items
  """
  n = 0 
  while items is not None and items != items[1:]:
    n,items = n+1, items[1:]
  return n

length(range(10))
length.__name__
length.__doc__

Class Decorator

from types import FunctionType

class CheckTime:
  def __init__(self,func : FunctionType):
    self.func = func 

  def __call__(self,*args,**kwargs):
    
    return self.func(*args,**kwargs)

@CheckTime
def length(items):
  """ 
  Hàm kiểm tra số lượng phần tử của items
  """
  n = 0 
  while items is not None and items != items[1:]:
    n,items = n+1, items[1:]
  return n

length(range(10))
from types import FunctionType

class CheckTime:
  def __init__(self,func : FunctionType):
    self.func = func 

  def __call__(self,*args,**kwargs):
    print("Bắt đầu chạy hàm {}".format(self.func.__name__))
    start_time = time.time()
    result = self.func(*args, **kwargs)
    print("Hoàn thành trong {} giây".format(time.time() - start_time))
    return result

@CheckTime
def length(items):
  """ 
  Hàm kiểm tra số lượng phần tử của items
  """
  n = 0 
  while items is not None and items != items[1:]:
    n,items = n+1, items[1:]
  return n

length(range(10))
length.__doc__
length.__name__
dir(CheckTime)
from types import FunctionType

class CheckTime:
  def __init__(self,func : FunctionType):
    self.func = func 

  def __call__(self,*args,**kwargs):
    print("Bắt đầu chạy hàm {}".format(self.func.__name__))
    start_time = time.time()
    result = self.func(*args, **kwargs)
    print("Hoàn thành trong {} giây".format(time.time() - start_time))
    return result

  def __name__(self):
    return self.func.__name__

@CheckTime
def length(items):
  """ 
  Hàm kiểm tra số lượng phần tử của items
  """
  n = 0 
  while items is not None and items != items[1:]:
    n,items = n+1, items[1:]
  return n

length(range(10))
length.__name__()
from types import FunctionType
import time 
import functools 

class CheckTime:
  def __init__(self,func : FunctionType):
    self.func = func 
    functools.update_wrapper(self,func)

  def __call__(self,*args,**kwargs):
    print("Bắt đầu chạy hàm {}".format(self.func.__name__))
    start_time = time.time()
    result = self.func(*args, **kwargs)
    print("Hoàn thành trong {} giây".format(time.time() - start_time))
    return result

@CheckTime
def length(items):
  """ 
  Hàm kiểm tra số lượng phần tử của items
  """
  n = 0 
  while items is not None and items != items[1:]:
    n,items = n+1, items[1:]
  return n

length(range(10))
length.__doc__

Improve Decorator

problem

Việc gọi lại decorator “check_time” bên trên mỗi một hành vi của đối tượng là việc làm hiệu quả nhưng khá cồng kềnh.

from functools import wraps 
from types import FunctionType
import time 

def check_time(prefix = ""):
  def check(func : FunctionType):
    print(f"{prefix}\n")
    @wraps(func)
    def handle(*args):
      print("Bắt đầu chạy hàm {}".format(func.__name__))
      start_time = time.time()
      result = func(*args)
      print("Hoàn thành trong {} giây".format(time.time() - start_time))
      # handle.__name__ = func.__name__ 
      # handle.__doc__ = func.__doc__ 
      return result 
    return handle
  return check

class Person:
  def __init__(self, name : str, age : int , gender : str):
    self.name = name 
    self.age  = age 
    self.gender = gender

  @check_time(prefix = "check func study ...")
  def study(self, counrce_name : str):
    print("Bạn {} đã tham gia khóa học {}".format(self.name,counrce_name))

  @check_time(prefix = "check func play_game ...")
  def play_game(self):
    print("play game ...")
p = Person("Python",25,"male")
p.study("math")
p.play_game()

solution

Giải pháp được đưa ra ở đây là chúng ta sẽ tìm cách kiểu tra phương thức nào được gọi (thông qua callable) và đặt decorator “cehck_time” vào nó (thông qua setattr). Điều này sẽ giúp chúng ta thực thi hiệu quả và ngắn gọn.

from functools import wraps 
from types import FunctionType
import time 

def check_time(prefix = ""):
  def get_class(cls):
    def check(func : FunctionType):
      print(f"{prefix}\n")
      @wraps(func)
      def handle(*args,**kwargs):
        print("Bắt đầu chạy hàm {}".format(func.__name__))
        start_time = time.time()
        result = func(*args,**kwargs)
        print("Hoàn thành trong {} giây".format(time.time() - start_time))
        return result 
      return handle

    methods = vars(cls)

    for name, value in methods.items():
      if callable(value):
        print(value,"????????")
        setattr(cls,name,check(value))

    return cls 

  return get_class

@check_time(prefix="***Check***")
class Person:
  def __init__(self, name : str, age : int , gender : str):
    self.name = name 
    self.age  = age 
    self.gender = gender

  def study(self, counrce_name : str):
    print("Bạn {} đã tham gia khóa học {}".format(self.name,counrce_name))

  def play_game(self):
    print("play game ...")
p = Person("Python",25,"male")
p.study("math")
p.play_game()

Mở rộng cho class

from types import FunctionType
import time 
import functools 

class CheckTime:
  def __init__(self,func : FunctionType):
    self.func = func 
    functools.update_wrapper(self,func)

  def __call__(self,*args,**kwargs):
    print("Bắt đầu chạy hàm {}".format(self.func.__name__))
    start_time = time.time()
    result = self.func(*args, **kwargs)
    print("Hoàn thành trong {} giây".format(time.time() - start_time))
    return result


@CheckTime
class Person:
  def __init__(self, name : str, age : int , gender : str):
    self.name = name 
    self.age  = age 
    self.gender = gender

  def study(self, counrce_name : str):
    print("Bạn {} đã tham gia khóa học {}".format(self.name,counrce_name))

  def play_game(self):
    print("play game ...")
  
dir(CheckTime)