装饰器学习

写在开头, 昨天参加了一场面试, 可能是疏于使用, 面试官的一道装饰器题目没有写出来, 因此这次我们来翻译一篇python装饰器的文章并结合具体题目来学习装饰器, 原文链接 stackouverflow

题目

面试题目

装饰器基础

### python函数是一个对象
想理解装饰器, 就必须先理解python函数也是一个对象, 先看一个例子

def shout(word = "yes"):
    return word.capitalize() + "!"

print(shout())
# outputs : 'Yes!'

scream = shout
print(scream())
# outputs : 'Yes!'

del shout
try:
    print(scream())
except Exception as e:
    print(e)
# output : "Yes!", 这里的原因是还有弱引用, 去掉后依然可以使用

try:
    print(shout())
except Exception as e:
    print(e)
# output : name 'shout' is not defined, 原来的函数引用删除, 没有shout, 因此name errors

另一个有趣的是python函数能被定义在函数里面


def talk():
    def whisper(word = "yes"):
        return word.lower() + "..."
    print(whisper())
# 你能够调用 "talk", whisper在 talk里面调用
talk()
# output : talk, 但是whisper不能再talk以外使用

try:
    print(whisper())
except Exception as e:
    print(e)
# output : name 'whisper' is not defined

函数参考

你已经熟悉了函数就是一个对象, 因此, 函数:

  1. 能够分配变量
  2. 能够定义其他的函数

这就意味着一个函数可以返回另一个函数

这并不是解决方案

import time
redis = {}
def cache(key = "expensive_action"):
    def expensive_action():
        # redis耗时查询
        time.sleep(3)
        redis["1"] = 1
        return redis["1"]
    # 这里假设redis缓存里面存在, 直接返回
    def not_expensive_action():
        return redis["1"]
    if key == "expensive_action":
        return expensive_action
    else:
        return not_expensive_action
# 如何使用他呢, 这里的c也是一个对象
c = cache()
print(c)
# output : <function cache.<locals>.expensive_action at 0x7fb6aec36ef0>
# 这个对象返回一个函数
print(c())
# output : 1

# 也可以这样使用
print(cache("expensive_action")())
# output : 1

更多的, 你可以添加一个参数,在返回一个函数的时候

def doSomething(func):
    print("在调用函数之前做一些事情")
    print(func())
doSomething(scream)
# output : 在调用函数之前做一些事情 \n Yes!

很好, 已经理解了一点装饰器, 装饰器就是一个wrapper(包装), 意味着, 你可以在函数调用之前和之后执行任何代码去改变函数自身的行为

手动装饰

# 一个装饰器就是函数的参数是另一个函数
def my_shiny_new_decorator(a_function_to_decorate):
    def the_wrapper_around_the_original_function():
        print("Before the function runs")
        a_function_to_decorate()
        print("After the funtion runs")
    # 此时, 装饰器函数还没有运行
    # 返回wrapper函数
    return the_wrapper_around_the_original_function

def a_stand_alone_func():
    print("i am a stand function, not modify me ")

# 标准运行
a_stand_alone_func()
# i am a stand function, not modify me 

# 现在可以使用装饰器开扩展函数行为
a_stand_alone_decorator = my_shiny_new_decorator(a_stand_alone_func)
a_stand_alone_decorator()
# output : 
# Before the function runs
# i am a stand function, not modify me 
# After the funtion runs

装饰器神秘面纱

这是之前的例子, 使用装饰器语法

@my_shiny_new_decorator
def another_stand_alone_func():
    print("leave me alone")
another_stand_alone_func()
# output :
# Before the function runs
# leave me alone
# After the funtion runs

这是一个简单的例子, @decorator仅仅是这样做了

another_stand_alone_func = my_shiny_new_decorator(another_stand_alone_func)

装饰器仅仅是一个pythonic的变体, 下面是一些例子

def bread(func):
    def wrapper():
        print("</''''''\>")
        func()
        print("<\______/>")
    return wrapper

def ingredients(func):
    def wrapper():
        print("#tomatoes#")
        func()
        print("~salad~")
    return wrapper

def sandwich(food="--ham--"):
    print(food)

sandwich()

sandwich = bread(ingredients(sandwich))
sandwich()

# 这个例子就是上面的简写形式, 运行结果一样
@bread
@ingredients
def sandwich(food="--ham--"):
    print(food)

装饰器进阶

将参数传递给函数

def decorator(func):
    def wrapper(arg1, arg2):
        print("I got args! Look: {0}, {1}".format(arg1, arg2))
        func(arg1, arg2)
    return wrapper

@decorator
def print_full_name(first_name, last_name):
    print("My name is {0} {1}".format(first_name, last_name))

print_full_name("ajin", "wu")

装饰方法

在python中,函数和方法是相同的, 唯一的区别就是方法的第一个参数是self

这就意味着你可以使用相同的方法来装饰方法


def method_decorator(method):
    def wrapper(self, lie):
        lie = lie - 3
        return method(self, lie)
    return wrapper

class Lucy:
    def __init__(self):
        self.age = 18

    @method_decorator
    def get_age(self, lie):
        print("I am {}".format(self.age + lie))

Lucy().get_age(5)
# output : 20

如果想制作一个通用的装饰器, 可以使用参数*args等

让我们来解决第一个问题

redis = {i: i for i in range(10)}

def cache(func):
    redis_cache = {1:2}
    def wrapper(arg1):
        if arg1 in redis_cache:
            print("using cache")
            return redis_cache[arg1]
        else:
            print("not using cache")
            redis_cache[arg1] = arg1
            return func(arg1)
    return wrapper

def doSomething(num):
    time.sleep(3)
    return redis[num]

@cache
def expensive_action(num):
    res = doSomething(num)
    return res

# 测试代码
print(expensive_action(4))
print(expensive_action(1))
# output : 
# not using cache
# 4
# using cache
# 2

我们来看一个有趣的例子

print("=========")
def my_decorator(func):
    print("I am an ordinary function")
    def wrapper():
        print("I am function returned by the decorator")
        func()
    return wrapper

def lazy_func():
    print("zzz")

decorator_func = my_decorator(lazy_func)
# # output : I am an ordinary function, 因为没有调用Wrapper


@my_decorator
def lazy_func():
    print("zzzz")
# I am an ordinary function, 这句的解释, 函数定义是不会运行的, 但是我们使用了@语法, 因此他会去执行装饰器一次
# my_decorator(func)
print("1111111")
my_decorator(lazy_func)
# output : I am an ordinary function
print("||||||||")
my_decorator(lazy_func)()
# output : 这里相当于在多包装了一层
# I am an ordinary function
# I am function returned by the decorator
# I am function returned by the decorator
# zzzz
print("22222")
lazy_func()
# output : 
# I am function returned by the decorator
# zzzz

原文: It’s exactly the same. “my_decorator” is called. So when you @my_decorator, you are telling Python to call the function ‘labelled by the variable “my_decorator”‘.

我的理解: 在使用@语法时, 告诉python调用函数来标记func

下面, 带参数的装饰器

c1 = "penc"
c2 = "lesi"

def decorator_make_with_arg(decorator_arg1, decorator_arg2):
    print("I accept arg:{0}, {1}".format(decorator_arg1, decorator_arg2))
    def my_warpper(func):
        print("我是装饰器里面的包, 参数:{}, {}".format(decorator_arg1, decorator_arg2))
        def wrapper(func_arg1, func_arg2):
            print("我是my_Wrapper里面的,我想要装饰器的参数, 装饰器参数是:{}, {},我是最里面的参数, 参数:{}, {}".format(decorator_arg1,decorator_arg2,func_arg1, func_arg2))
            return func(func_arg1, func_arg2)
        # 这里为什么不加上参数呢, 因为装饰器的缘故, 要在外面加
        return wrapper
    return my_warpper

@decorator_make_with_arg(c1, c2)
def decorator(func1, func2):
    print("我是decorator函数, 参数是:{},{}".format(func1, func2))

decorator("ff", "kk")
# output :
# I accept arg:penc, lesi
# 我是装饰器里面的包, 参数:penc, lesi
# 我是my_Wrapper里面的,我想要装饰器的参数, 装饰器参数是:penc, lesi,我是最里面的参数, 参数:ff, kk
# 我是decorator函数, 参数是:ff,kk

# 如果在import之后, 就不能动态设置参数

我最后的答案

def doSomething(num1, num2):
    time.sleep(3)
    if num2:
        return redis[num1], redis[num2]
    return redis[num]
redis = {i: i for i in range(10)}

def cache(key = "", expires = 10000):
    def my_warpper(func):
        redis_cache = {1:2}
        def wrapper(fun1 = "", fun2 = ""):
            if key:
                print(str(key).format(fun1=fun1, fun2=fun2))
            start = time.time()
            tmp = 0
            while True:
                if (time.time() - start) / 1000 > 3600:
                    break
                # 这里应该写异步方法轮询取得结果
                if fun1 in redis_cache:
                    print("using cache")
                    return redis_cache[fun1]
                else:
                    print("not using cache")
                    redis_cache[fun1] = fun1
                    return func(fun2)
                time.sleep(1)
        return wrapper
    return my_warpper

@cache(key = "expensive_action:{fun1}{fun2}")
def expensive_action(fun1, fun2):
    res = doSomething(fun1,fun2)
    return res
expensive_action(1, 2)

文章作者: ajin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ajin !
 上一篇
grpc概述 grpc概述
overviewRPC(Remote Procedure Call), grpc中, 客户端可以像使用本地对象一样, 直接调用其他机器上面的服务端应用程序方法, 能够很容易的构建分布式的应用程序和服务, grpc基于服务, 指定能够调用远程
2020-04-03
下一篇 
python实现Java中的比较器 python实现Java中的比较器
from functools import cmp_to_key def reverse_numeric(x, y): return x - y sorted([5, 2, 4, 1, 3], key=cmp_to_key(reve
2020-02-09
  目录