Python新手提问:关于装饰器的用法和原理

之前就对装饰器一知半解,看教程到协程部分,更晕了。我要提的问题是:

两层结构的装饰器,作用只是为了包函数的参数吗?

比如样例


def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

如果函数没有参数,或者说装饰器不需要使用到函数的参数,那么按照下面这样写有什么问题:

def log(func):
    print('call func: %s' %func.__name__)
    return func

@log def fn(a,b): print(a + b)

fn(1,2)


Python新手提问:关于装饰器的用法和原理

21 回复

你把装饰器当普通函数用一下,你就明白了。装饰器和普通函数唯一的区别就是装饰器函数会在导入时运行,你实际运行被装饰器装饰的函数是装饰器返回的函数。至于装饰器作用,就是一个语法糖,想怎么用就怎么用,你秉持一个理念,函数可以作为函数的参数,那装饰器在你眼里就是一个普通函数了,普通函数能做什么,装饰器就能做什么。想找一些灵感,可以看看 Python cook book 的装饰器章节


装饰器本质上是一个函数,它接收一个函数作为参数,并返回一个新的函数。它的核心作用是“包装”原有函数,在不修改原函数代码的情况下增加额外功能。

看个简单例子就明白了:

def my_decorator(func):
    def wrapper():
        print("函数执行前")
        func()
        print("函数执行后")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

运行结果:

函数执行前
Hello!
函数执行后

这里发生了什么?

  1. @my_decorator 相当于 say_hello = my_decorator(say_hello)
  2. my_decorator 接收 say_hello 函数
  3. 返回一个新的 wrapper 函数
  4. 调用 say_hello() 时实际上调用的是 wrapper()

装饰器最常用的场景包括:日志记录、性能测试、权限校验、缓存等。比如 Flask 框架里就用装饰器来定义路由:

@app.route('/')
def index():
    return 'Hello World'

装饰器还能带参数,这时候需要三层嵌套:

def repeat(num):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(num):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    print(f"Hello {name}")

greet("Alice")

总结:装饰器就是给函数“穿马甲”的高级写法。

你这代码没问题。另,还可以有三层的


def fn():
…pass

实际上只是一个语法糖,他相当于一下代码

def fn():
…pass

fn = log(fn)

你这样用,下次运行 fn,就不会显示 log 了

你的写法并没有改变 fn,装饰器的作用是将原函数名指向一个新函数,你这样包了一层仍然是原函数

第 2 个例子中 wrapper 不能少。
装饰器就是专门返回 wrapper 的。

你需要了解一个基本概念——“高阶函数”,然后一切问题就迎刃而解了

1,你这样没毛病。
2,你这样写,比如在函数执行完成后加 log 了,比如你想记录一个函数执行时间的装饰器,你这样写就不行了,而示例中就可以。
3,还有更复杂的是装饰器本身有参数。

以前写过装饰器的文章: https://www.imzjy.com/blog/2012-05-23-python-decorator
4.2 就是三层嵌套的,用来解决装饰器本身有参数的情况。



>你实际运行被装饰器装饰的函数是装饰器返回的函数。




>装饰器的作用是将原函数名指向一个新函数,你这样包了一层仍然是原函数

谢谢!

那么,也就是说可以理解成装饰器返回新函数,而这个新函数返回的是老函数?

比如:

<br><br>def log(func):<br> def wraped_func(*args,**kw):<br> print('do sth...')<br> return func(*args,**kw)<br> return wraped_func<br><br><br><br>def fn(a,b):<br> print(a + b)<br><br><br>fn(1,2)<br><br>

你若是要深挖这个,也可以搜索高级函数以及柯里化(currying)

函数作为参数,函数作为返回值。

你如果这么写没有返回 wrapper:
def log(func):
print(‘call func: %s’ %func.name)
return func

那么假设你装饰一个无参函数,比如这样定义的:
def fn():
print(‘call func: %s’ %func.name)

你如果这么写没有返回 wrapper:
def log(func):
print(‘call func: %s’ %func.name)
return func

那么假设你装饰一个无参函数,比如这样定义的:
def fn():
print "hello"

你最后想执行函数时用 fn()这种形式肯定报错

主要你对函数是一等公民这个概念没有理清,你试着写几个函数,它的参数是函数并且返回值也是函数就明白了。

5 楼和 6 楼都说的不错,装饰器是要返回一个新的函数的。
你自己的写法,也能在执行时显示结果,但是不能调用它,这和解释器有关了。。。
装饰器是要返回一个新的函数的
装饰器是要返回一个新的函数的
装饰器是要返回一个新的函数的

装饰器是一个闭包函数写成 @形式的语法糖

#8 手动感谢下 看了你的写法明白了

一个函数的函数为什么要取这么奇怪的名字,我看廖雪峰老师的教程里面也用这个名字。明明已经存在现有的一个命名:泛函



你问问人家数学家为啥要取这个名字啊—— high-order function。

并且高阶函数跟泛函还是有区别的,泛函往往是空间(函数)对是实数域的映射,而高阶函数是空间(函数)对空间(函数)的映射,所以名字有区别也没没毛病吧。

有道理,我的理解肤浅了

回到顶部