zoukankan      html  css  js  c++  java
  • 装饰器

    一、装饰器定义

    装饰器(Decorator):在代码运行期间动态增加功能的方式,称之为装饰器。

      本质上,装饰器就是一个返回函数的高阶函数。函数也是一个对象,而且函数对象可以被赋值给变量,因此通过变量也能调用该函数。

    >>> def now():
    ...     print('2018-3-20')
    ...
    >>> f = now
    >>> f()
    2018-3-20
    >>> now.__name__   # 函数对象__name__属性,拿到函数名
    'now'
    >>> f.__name__
    'now'

      定义一个能打印日志的装饰器:

    import datetime
    
    def log(func):   # 装饰器接受一个函数作为参数,并返回一个函数
        def wrapper(*args, **kw):
            print('call %s(): ' % func.__name__)
            return func(*args, **kw)
        return wrapper
    
    @log  # 运用@语法把装饰器放置在函数定义处
    def now():
        print(datetime.datetime.now())
    
    now()
    """
    call now(): 
    2018-03-20 23:00:05.201096
    """

      把@log放到now()函数定义处,相当于执行语句now = log(now)。now()函数仍然存在,只是现在同名的now变量指向了新的函数log(now)。

      因此在调用now()时,将执行log(now),在log()函数中返回的wrapper()函数。

      wrapper()函数的参数定义是(*args, **kw),可以接受任意参数的调用。

    二、装饰器详解

      一家视频网站有以下几个板块:

    def home():
        print("首页".center(40, '-'))
    
    def america():
        print("欧美专区".center(40, '-'))
    
    def japan():
        print("日韩专区".center(40, '-'))
    
    def henan():
        print("河南专区".center(40, '-'))

      上线初期所有视频都是免费观看,用户量增多后,准备对一些板块收费。因此需要添加登陆认证功能。

    user_status = False  # 用户登录就把这个改为True
    
    def login():
        _username = 'alex'  # 假装这是DB里存的用户信息
        _password = 'abc!23' # 假设这是DB里存的用户信息
        global user_status
    
        if user_status is False:  # 用户状态为True时可以访问视频
            username = input("user:")
            password = input("password:")
    
            if username == _username and password == _password:
                print("welcome login....")
                user_status = True
            else:
                print("用户已经登录,验证通过!")
    
    def home():
        print("首页".center(40,'-'))
    
    def america():
        login()  # 登陆认证
        print("欧美专区".center(40,'-'))
    
    def japan():
        print("日韩专区".center(40,'-'))
    
    def henan():
        login()  # 登陆认证
        print("河南专区".center(40,'-'))
    
    henan()
    america()

      上述代码虽然实现了登陆认证功能,但是却违反了软件开发中一个重要原则:“开发—封闭”原则。规定代码不允许被修改但可以被扩展。

      封闭:已实现的功能代码块不应该被修改。

      开放:对现有功能的扩展开放。

      高阶函数:把一个函数当做一个参数传给另一个函数。应用该知识点改写如下:

    user_status = False #用户登录了就把这个改成True
     
    def login(func): #把要执行的模块从这里传进来
        _username = "alex" #假装这是DB里存的用户信息
        _password = "abc!23" #假装这是DB里存的用户信息
        global user_status
     
        if user_status == False:
            username = input("user:")
            password = input("pasword:")
     
            if username == _username and password == _password:
                print("welcome login....")
                user_status = True
            else:
                print("wrong username or password!")
     
        if user_status == True:
            func() # 看这里看这里,只要验证通过了,就调用相应功能
     
    def home():
        print("---首页----")
     
    def america():
        #login() #执行前加上验证
        print("----欧美专区----")
     
    def japan():
        print("----日韩专区----")
    
    def henan():
        #login() #执行前加上验证
        print("----河南专区----")
     
     
    home()
    login(america) #需要验证就调用 login,把需要验证的功能 当做一个参数传给login
    # home()
    # america()
    login(henan)
      以上改写也不好,修改了调用方式,需要认证的模块都需要修改调用方式。需要不改变原功能代码,又不改变原有调用方式,还能加上认证的代码改写。
    user_status = False
    def login(func):
        _username = "alex"
        _password = "abc!23"
        global user_status
    
        if user_status == False:
            username = input("user:")
            password = input("password:")
            if username == _username and password == _password:
                print("welcome login...")
                user_status = True
            else:
                print("wrong username or password!")
    
        if user_status == True:
            func()
    
    home()
    henan = login(henan)
    america = login(america)
    henan()
    """
    -------------------首页-------------------
    user:alex
    password:abc!23
    welcome login...
    ------------------河南专区------------------
    ------------------欧美专区------------------
    Traceback (most recent call last):
      File "/Users/.../装饰器.py", line 91, in <module>
        henan()
    TypeError: 'NoneType' object is not callable
    """

      上述改写不改变调用方式也不违反“开放——封闭”,但是america = login(america)时就会把america执行。

      可以运用嵌套函数,在login中再定义一层,使得america = login(america)只调用到外层,不触发认证。login只返回里层函数的函数名,下次执行america()时再调用里层函数。

    user_status = False
    def login(func):
        def inner():   #添加一层inner,装饰器套路
            _username = "alex"
            _password = "abc!23"
            global user_status
    
            if user_status == False:
                username = input("user:")
                password = input("password:")
                if username == _username and password == _password:
                    print("welcome login...")
                    user_status = True
                else:
                    print("wrong username or password!")
            if user_status:
                func()   # henan()——老的河南函数
        return inner  # 加括号执行,不加括号返回内存地址
    
    home()
    henan = login(henan)
    america = login(america)
    henan()
    """
    -------------------首页-------------------
    user:alex
    password:abc!23
    welcome login...
    ------------------河南专区------------------
    """

      还可以将上述代码改写,把america = login(america)改写为@login,并加在america函数前。

    @login  # 等价于henan = login(henan)
    def hubei():
        #login()  # 改写删除该行
        print("湖北专区".center(40,'-'))
    
    hubei()
    
    """
    user:alex
    password:abc!23
    welcome login...
    ------------------湖北专区------------------
    """

    三、带参数装饰器

      上述代码要是在调用函数的时候直接传参数是会报错的。因为调用hubei时,实际上是调用login。
      hubei = login(hubei),login返回inner内存地址。
    第二次用户自己调用hubei("3p"),实际上相当于调用的是inner,因此给inner设置参数,可以解决问题。
    user_status = False
    def login(func):
        def inner(*args,**kwargs):   # 添加参数eg.3p
            _username = "alex"
            _password = "abc!23"
            global user_status
    
            if user_status == False:
                username = input("user:")
                password = input("password:")
                if username == _username and password == _password:
                    print("welcome login...")
                    user_status = True
                else:
                    print("wrong username or password!")
            if user_status:
                # func(args,kwargs)  # 这样写是固定两个参数
                func(*args,**kwargs)   # henan()  适配任意多个参数
        return inner  # 加括号执行,不加括号返回内存地址
    
    def home():
        print("首页".center(40,'-'))
    
    def america():
        print("欧美专区".center(40,'-'))
    
    @login
    def japen(name):
        print("日韩专区".center(40, '-'), name)
    
    @login  # henan = login(henan)
    def henan(style):
        print(("河南专区" + style).center(40, '-'))
    
    home()
    henan('3p')
    japen('oddry')
    
    """
    -------------------首页-------------------
    user:alex
    password:abc!23
    welcome login...
    -----------------河南专区3p-----------------
    ------------------日韩专区------------------ oddry
    """
    
    
    
     
  • 相关阅读:
    Ubuntu 12.04 国内更新源列表 LINUX软件 偶偶贝塔のBlog
    乱码
    charset
    乱码
    使用 Python 进行线程编程
    Pyphantomjs makes python crash
    乱码
    PyQt v4 Python Bindings for Qt v4 | Документация
    Automated Discovery of Blog Feeds and Twitter, Facebook, LinkedIn Accounts Connected to Business Website « Data Big Bang Blog
    jeanphix/Ghost.py · GitHub
  • 原文地址:https://www.cnblogs.com/xiugeng/p/8613684.html
Copyright © 2011-2022 走看看