Python基础:装饰器

文章目录

    • 函数装饰器
      • 装饰器原理
      • 装饰器基础
      • 装饰器进阶
    • 类装饰器

函数装饰器

装饰器原理

python中一切皆对象

  1. 可以将函数的运行结果赋给另一个函数

    def p1(name = "python"):
        return "p1 " + name
    print(p1())
    # output:p1 python
    
  2. 可以将函数名赋给一个变量

    greet = p1
    print(greet())
    # output:p1 python
    
  3. 尝试删除p1函数

    del p1
    print(p1())# 只是删除了函数p1的指向,但是函数对象本身并未删除。
    # output:NameError
    print(greet())
    # output:p1 python
    

    如果一个函数对象没有任何对它的引用,并且它没有被其他对象所使用。在这种情况下,Python 的垃圾回收机制会自动处理函数对象的删除。

  4. 进阶:在函数中定义另一个函数

    def auth(name = "python"):
        print("now you are in the auth()")
        def p1():
            return "now you are in the p1()"
        def welcome():
            return "now you are in the welcome()"
        print(p1())
        print(welcome())
        
    # 调用auth(),p1()和welcome()将会同时被调用,但是p1和welcome在auth之外无法访问
    auth()
    # output:
    # now you are in the auth()
    # now you are in the p1()
    # now you are in the welcome()
    p1()
    # NameError: name 'p1' is not defined
    
  5. 高阶:函数也能返回函数

    def auth(name):
        def p1():
            return "now you are in the p1()"
        def welcome():
            return "now you are in the welcome()"
        if name == "python":
            return p1
        else:
            return welcome
        #注意:这里返回的是p1和welcome的函数体而不是p1(),welcome()
        
    print(auth("python"))
    # .p1 at 0x000001BF61454B80>
    a = auth("python")
    b = auth("java")
    print(a())
    # now you are in the p1()
    print(b())
    # now you are in the welcome()        
    
  6. 最后:函数可以作为参数传给另一个函数

    def p1(name = "python"):
        return "p1 " + name
    
    def func(f):
        print("I am doing sth before p1()")
        print(f())
    
    func(p1)
    # I am doing sth before p1()
    # p1 python
    

总结:创建函数func(f),在func()中定义inner(),在将inner赋值给hello

def func(f):
    def inner():
        print("I am doing something before f()")
        f()# 将f()封装在一个内部函数中
        print("I am doing something after f()")
    return inner# func(f)返回内部函数体

def hello():
    print("hello python")

# 将hello赋值给func的形参,func(hello)返回值inner赋给了hello
hello = func(hello)
hello()# 实际上是执行inner(), 是调用经过装饰后的函数
# output:
# I am doing something before f()
# hello python
# I am doing something after f()

在python中有一种简单的写法:将hello = func(hello)改成如下形式:

@func #注释符号加函数名
def hello():
    print("hello python")
hello()
# output:
# I am doing something before f()
# hello python
# I am doing something after f()

以上就是Python装饰器的原理

装饰器基础

装饰器

又名装饰器函数

python提供声明装饰器的语法@decorator

装饰是为函数和类指定管理或扩增代码的一种方式。装饰器本身采取可调用对象的形式(如函数),并处理其他可调用对象。

包含:

  • 函数装饰器:装饰器提供一种方法,在函数和类定义语句结束时插入自动运行的代码—对于函数装饰器,在def语句中结束插入
  • 类装饰器:在class语句结束时插入,这样代码可以扮演不同角色

原则:开闭原则

对扩展开放,对修改关闭

装饰器自身时一个返回可调用对象的可调用对象,也就是返回了一个对象,当通过其最初名称调用被装饰函数的时候,将会调用这个对象—它可以时拦截之后调用的一个包装器对象,也可以是以某种方式扩展的最初函数。(装饰器可以是任意类型的可调用对象,并且返回任意类型的可调用对象。

在不改变函数调用方式的前提下,实现身份的识别(扩展功能),需要使用装饰器(本质是闭包)

def hello(f):# 参数是一个函数对象
    def wrapper():
        print("hello world!")
        f()# 调用函数
    return wrapper
# @是语法糖
# @hello 等价于 func = hello(func) = wrapper

@hello
@hello
def func():
    print("您好")

func()
# output:
# hello world!
# hello world!
# 您好

"""等效代码"""
func = hello(func)#用这行代码效果和@hello一样
func = hello(func)
func()
# output:
# hello world!
# hello world!
# 您好

特殊区别

#hello(func)() 等价于 @hello
#				     func()
@hello
def func():
    print("您好")
func()
func()
# output:
# hello world!
# 您好
# hello world!
# 您好

"""等效代码"""
hello(func)()
hello(func)()
# output:
# hello world!
# 您好
# hello world!
# 您好
  • 加装饰器前调用的是用原函数

  • 加装饰器后调用的是wrapper函数

Demo

def hello(func):
    def wrapper():
        name = input("输入name: ")
        pwd = input("输入pwd: ")
        print("验证")
        if name == "hzh" and pwd == "123456":
            func()
            print("登录成功")
        else:
            print("登录失败")
    return wrapper

@hello
def login():
    pass

login()

装饰器进阶

  1. 在inner函数中定义返回值

    def func(p):
        def inner():
            print("I am doing something before p()")
            result = p()# result接受p()返回值
            print("I am doing something after p()")
            return result
        return inner
    @func
    def hello():
        print("hello")
        return "world"
    
    print(hello())
    # I am doing something before p()
    # hello
    # I am doing something after p()
    # world
    

    对于有返回值的被装饰函数,要在内层把值返回,不然被装饰函数的工作就白做

  2. 在inner函数中定义参数

    def func(p):
        def inner(arg):# 形参arg
            print("I am doing something before p()")
            result = p(arg)# result接受p(arg)返回值
            print("I am doing something after p()")
            return result
        return inner
    @func
    def hello(name):
        print("hello")
        return name
    
    print(hello("python"))
    # I am doing something before p()
    # hello
    # I am doing something after p()
    # python  
    

    此外,万能参数可以解决p函数参数不一致问题

    def func(p):
        def inner(*args, **kwargs):
            print("I am doing something before p()")
            result = p(*args, **kwargs)
            print("I am doing something after p()")
            return result
        return inner
    
  3. @装饰器的多重嵌套

    def f1(p):
        print("F1 外层1")
        def inner1():
            print("F1 内层p()前")
            p()
            print("F1 内层p()后")
            return "inner1结束"
        print("F1 外层2")
        return inner1
    
    
    def f2(p):
        print("F2 外层1")
        def inner2():
            print("F2 内层p()前")
            p()
            print("F2 内层p()后")
            return "inner2结束"
        print("F2 外层2")
        return inner2
    
    #先使用f2装饰hello,所以先调用f2里的外层方法,然后在使用f1装饰,所以再调用f1里的外层方法
    @f1 # 总装饰器,装饰器inner2函数,函数名变成inner1
    @f2 # 给hello改名inner2
    # 相当于 hello = f1(f2(hello))
    def hello():
        print("p()在这!")
    
    print(hello())
    # output:
    # F2 外层1
    # F2 外层2
    # F1 外层1
    # F1 外层2
    # F1 内层p()前
    # F2 内层p()前
    # p()在这!
    # F2 内层p()后
    # F1 内层p()后
    # inner1结束
    
    print(hello.__name__)# inner1 证明先使用f1装饰,再使用f2装饰,所以函数名变成了f1的返回值
    

    functools的wraps函数记录并加入了复制函数名称、注释文档、参数列表等功能。将p.__name__值改回inner装饰之前的值,解决函数被装饰后名称改变的问题

    from functools import wraps
    def func(p):
    	# wraps函数先记录p函数未装饰之前的名称,p函数装饰后,再把p函数名称改回来 
        @wraps(p)
        def inner(*args, **kwargs):
            print("I am doing something before p()")
            result = p(*args, **kwargs)
            print("I am doing something after p()")
            return result
        return inner
    
    @func
    def hello(name):
        print("hello " + name)
        return "end"
    #未添加@wraps(p)
    print(hello.__name__)
    # inner
    # 添加@wraps(p)后
    print(hello.__name__)
    # hello
    

    分析可知:包裹在越内层的函数,被调用的时机就越晚,就和我们最上面简易的装饰器是一样的!
    PS.记得在最内层要把res返回,不然在main运行的输出会打印出None

  4. 装饰器加参数

    def logging(level):# level是传给装饰器的参数
        def out_wrapper(func):# func是被装饰的函数名
            def in_wrapper(*args, **kwargs):
                print(f"{level}: enter {func.__name__}()")
                return func(*args, **kwargs)
            return in_wrapper
        return out_wrapper
    # 如果要给装饰器添加参数,就在装饰器外面再套一个函数。传入参数
    @logging(level="INFO") # level是装饰器自己的参数
    # 等价于logging = logging(level = "INFO")
    #      @logging  (即@out_wrapper)
    def hello(a, b, c):
        print(a, b, c)
    
    hello("hello", "my", "world")
    # INFO: enter hello()
    # hello my world
    print(hello.__name__)# in_wrapper
    

注意:只要我们使用了装饰器,那么不管这个函数是否被调用,装饰器外层的方法都会运行的!并且装饰了几个函数就会运行几次!!!

def func(p):
    p("java1")
    def inner(*args, **kwargs):
        print("I am doing something before p()")
        result = p(*args, **kwargs)
        print("I am doing something after p()")
        return result
    p("java2")# 这个外层方法不管在inner之后还是之前,都会在inner之前先被运行,因为只有外层函数返回之后才调用inner函数
    return inner

@func
def hello(name):
    print("hello " + name)
    return "end"

# 没有调用hello,但是只要使用了装饰器装饰器外层方法就会运行
if __name__ == '__main__':
    pass
# output:
# hello java before
# hello java after

类装饰器

类也可以做装饰器,需要重写__call__方法

__call__方法让类对象以类名()的形式直接调用,以上三段代码等价。

class Deco(object):
    def __init__(self):
        self.name = "this is deco class"
    def __call__(self, p):# call方法重写为装饰器
        def wrapper(*args, **kwargs):
            print("I am doing something before p()")
            result = p(*args, **kwargs)
            print("I am doing something after p()")
            return result
        return wrapper

@Deco() #注意要加(),相当于创建了一个类对象
def p1(name):
    print("p1 is here")
    return "I am " + name

print(p1("p1"))# 自动去调用类里面的call方法
# I am doing something before p()
# p1 is here
# I am doing something after p()
# I am p1
print(p1.__name__)
# wrapper
p1 = Deco().__call__(p1)
# 等于
p1 = Deco()(p1)
# 等于
@Deco()
def p1():

你可能感兴趣的:(Python学习日记,python,开发语言)