---
# System prepended metadata

title: Decorator

---

# Decorator
### 用途:
靈活度高
易讀性高
協助封裝效果好
程式碼重複率低/簡潔度高

### Decorator種類
不帶參數的function decorator
帶參數的function decorator
不帶參數的class decorator
帶參數的class decorator

### 不帶參數的function decorator
```python=
def print_func_name(func):
    print('Now use print_func_name')
    def wrap_1(): 
        print(f"Now use function: {func.__name__}")
        func()
    return wrap_1

def dog():
    print("dog bark !")

print_func_name(dog)()
# Now use print_func_name
# Now use function: dog
# dog Bark !

@print_func_name
def cat():
    print("cat meow !")

cat()
# Now use print_func_name
# Now use function: cat
# cat meow !
````

* 多層decorator
    * 多層decorator是採 ”recursive” 方式處理，若一個 function 有兩個以上的 decorators ，先合併「最靠近」的 decorator 傳出新的 function 給上面一個 decorator ！
    
```python=
def print_func_2(func): # func = wrap_1
    print('Now use print_func_2') #2 Now use print_func_2
    def wrap_2():
        #3 Now use function: cat  
        # 若沒@wraps(func)-> func.__name__是wrap_1    
        print(f"Now use function: {func.__name__}") 
        #4 Words for doc          
        # 若沒@wraps(func)-> func.__doc__是None
        print(func.__doc__)
        func() #5 = wrap_1()
    return wrap_2
  
def print_func_1(func):
    print('Now use print_func_1') #1 Now use print_func_1
    from functools import wraps  
    @wraps(func)  # copy attr of cat to wrap_1
    def wrap_1():  # closure func
        #5 Now use function: cat
        print(f"Now use function: {func.__name__}")
        #6 Words for doc
        print(func.__doc__)
        func()  #7 = cat() = print("cat meow !")
    return wrap_1

@print_func_2
@print_func_1 
def cat():  # 先執行 print_func_1 再執行 print_func_2
    'Words for doc'
    print("cat meow !") 

cat()
# Now use print_func_1
# Now use print_func_2
# Now use function: cat  # 沒@wraps(func)-> name會是 wrap_1
# Words for doc          # 沒@wraps(func)-> doc會是 None
# Now use function: cat
# Words for doc
# cat meow !
```
* 裝飾器產生的問題
裝飾後返回的閉包，查看其屬性__doc__, __name__等，獲取到的是裝飾函數本身的屬性。
裝飾函數在實現前加入 **@functools.wraps(func)**，可將閉包的屬性更新為**被裝飾函數**之屬性。

### 帶參數的function decorator
如果裝飾器本身帶有參數，則需要再包一層function。
```python=
def check_sex(sex: str): # 第一層 def 接收傳入的參數
    def decorator(func): # 第二層 def 接收要被裝飾的func
        def wrapper():
            if sex == 'm': print('I am male')
            if sex == 'f': print('I am female')
            func()
        return wrapper
    return decorator
  
@check_sex('m')
def cat():  print("cat meow !")
  
cat()
# I am male
# cat meow !
```
### 不帶參數的class decorator
被裝飾的function 傳入 class 裡的 __init__。然後執行動作。在這例子裡，傳入的 function cat 以 self.talent 的方式宣告為 class 的 instance method。
也是將 function cat 「封裝」到 class Cat 的一種寫法。
```python=
class Cat:
    def __init__(self, func):
        self.age = 10
        self.talent = func

    def meow(self): print("Meow !")

@Cat
def cat(): print("I can do a back flip")

cat1 = cat

print(cat1.age)
# 10
cat1.meow()
# Meow !
cat1.talent()
# I can do a back flip
```
### 帶參數的class decorator
比較不同的是，傳入的**function** 放在__call__裡，傳入的**參數**放在__init__裡。
__call__裡面再多包一層 wrapper 接收被裝飾function的參數(即num, *args,**kwargs)
```python=
class Cat(object):
    def __init__(self, sex):
        self.sex = sex

    def __call__(self, func):  # 被裝飾的func放call裡
        def wrapper(num, *args, **kwargs):  # 接收func參數 (num)
            # 調用傳入decorator class的參數 (sex)
            print(f"calling function {func.__name__} with sex: {self.sex}")  
            # 調用func參數 (num)
            print(f"calling function {func.__name__} with num : {num}")  
            func(2) # 執行function: cat(2)
        return wrapper

@Cat('male')
def cat(num, *arg,**kwarg): 
  print(f"I can do {num} back flip")

cat1 = cat
cat1(1)
# calling function cat with sex: male
# calling function cat with parameter : 1
# I can do 2 back flip
```

