Python 函數與常用模組 - 裝飾器(高潮)

裝飾器 - 高潮版

現在我們來模擬一個網站的使用者登入,透過 裝飾器 的方式來實現登入驗証功能。

條件

  • 首頁不需要登入就可以使用
  • 會員頁需要登入才能使用
  • 討論區需要登入才能使用

先構思一個簡單版的。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

def index():
    print("Welcome to Index")

@auth
def home():
    print("Welcome to Home")

@auth
def bbs():
    print("Welcome to BBS")

上面代碼已經完成,建立三個頁面,而且透過 @auth 裝飾器,把會員頁及討論區都有加入登入驗証的功能,接下來就來寫 @auth 這個裝飾器。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

user, passwd = 'tony', 'stark'

def auth(func):
    def wrapper(*args, **kwargs):
        username = input("Username:").strip()
        password = input("Password:").strip()

        if username == user and password == passwd:
            print("\033[32;1mUser has passed authentication\033[0m")
            func(*args, **kwargs)
        else:
            exit("\033[31;1mInvalid Username or Password\033[0m")
    return wrapper

def index():
    print("Welcome to Index")

@auth
def home():
    print("Welcome to Home")

@auth
def bbs():
    print("Welcome to BBS")

index()
home()
bbs()

---------------執行第一次結果---------------
Welcome to Index
Username:tony
Password:stark
User has passed authentication
Welcome to Home
Username:tony
Password:stark
User has passed authentication
Welcome to BBS

Process finished with exit code 0

---------------執行第二次結果---------------
Welcome to Index
Username:tony
Password:stark
User has passed authentication
Welcome to Home
Username:tony
Password:hello
Invalid Username or Password

Process finished with exit code 1

第一次測試,全部都先輸入正確的,第二次測試,故意輸入錯誤的。

看起來首頁都確實沒有做驗証就可以使用,而會員頁及討論區都需要輸入帳號密碼來驗証。

再來請觀察一下,下面的代碼

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

user, passwd = 'tony', 'stark'

def auth(func):
    def wrapper(*args, **kwargs):
        username = input("Username:").strip()
        password = input("Password:").strip()

        if username == user and password == passwd:
            print("\033[32;1mUser has passed authentication\033[0m")
            func(*args, **kwargs)
        else:
            exit("\033[31;1mInvalid Username or Password\033[0m")
    return wrapper

def index():
    print("Welcome to Index")

@auth
def home():
    print("Welcome to Home")
    return "From Home"

@auth
def bbs():
    print("Welcome to BBS")

index()
print(home())
bbs()

---------------執行結果---------------
Welcome to Index
Username:tony
Password:stark
User has passed authentication
Welcome to Home
None
Username:tony
Password:stark
User has passed authentication
Welcome to BBS

Process finished with exit code 0

有發現執行結果中,有一個 None 嗎?為什麼會有 None呢?

先前的裝飾器雖然都沒有 修改被裝飾的函數的源代碼,也沒有 修改被裝飾的函數的調用方式,但卻改變了 home函數 的結果。

這是因為 func(*args, **kwargs) 的執行結果等於是 home函數的結果,即為 From Home,所以當去調用 home()時,其實就等於去調用 wrapper函數func(*args, **kwargs) 沒有返回,所以 home函數 的執行結果才為 None

那這樣要怎麼修改呢?

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

user, passwd = 'tony', 'stark'

def auth(func):
    def wrapper(*args, **kwargs):
        username = input("Username:").strip()
        password = input("Password:").strip()

        if username == user and password == passwd:
            print("\033[32;1mUser has passed authentication\033[0m")
            res = func(*args, **kwargs)   #From home
            str = "after authentication"
            print(str.center(50, '='))
            return  res
        else:
            exit("\033[31;1mInvalid Username or Password\033[0m")
    return wrapper

def index():
    print("Welcome to Index")

@auth
def home():
    print("Welcome to Home")
    return "From Home"

@auth
def bbs():
    print("Welcome to BBS")

index()
print(home())   #wrapper()
bbs()

---------------執行結果---------------

Welcome to Index
Username:tony
Password:stark
User has passed authentication
Welcome to Home
===============after authentication===============
From Home
Username:tony
Password:stark
User has passed authentication
Welcome to BBS
===============after authentication===============

Process finished with exit code 0

嗯!看起來 home函數 的執行結果也已經被正確給返回了。

接下來要正式進入正題了,由於目前使用的認証方式是採用 local的,但一般認証的方式除了local之外,還可以透過遠端的服務來做,例如: LDAP,所以我們要來模擬一下,如果同時要做 localldap 驗証登入的話,該怎麼做呢?!

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

user, passwd = 'tony', 'stark'

def auth(func):
    print("auth func:", func)
    def outer_wrapper():
        def wrapper(*args, **kwargs):
            print("wrapper func agrs:", *args, **kwargs)
            username = input("Username:").strip()
            password = input("Password:").strip()

            if username == user and password == passwd:
                print("\033[32;1mUser has passed authentication\033[0m")
                res = func(*args, **kwargs)  # From home
                str = "after authentication"
                print(str.center(50, '='))
                return res
            else:
                exit("\033[31;1mInvalid Username or Password\033[0m")

        return wrapper
    return outer_wrapper

def index():
    print("Welcome to Index")

@auth(auth_type="local")
def home():
    print("Welcome to Home")
    return "From Home"

@auth(auth_type="ldap")
def bbs():
    print("Welcome to BBS")

index()
print(home())   #wrapper()
bbs()

---------------執行結果---------------

Traceback (most recent call last):
  File "/Python/path/pratice_decorator4.py", line 29, in <module>
    @auth(auth_type="local")
TypeError: auth() got an unexpected keyword argument 'auth_type'

Process finished with exit code 1

跟著錯誤修改,因為 auth函數需要傳入參數

  • auth(func) 修改成 auth(auth_type)
  • print("auth func:", func) 修改成 print("auth auth_type:", auty_type )
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

user, passwd = 'tony', 'stark'

def auth(auth_type):
    print("auth auth_type:", auth_type)
    def outer_wrapper():
        def wrapper(*args, **kwargs):
            print("wrapper func agrs:", *args, **kwargs)
            username = input("Username:").strip()
            password = input("Password:").strip()

            if username == user and password == passwd:
                print("\033[32;1mUser has passed authentication\033[0m")
                res = func(*args, **kwargs)  # From home
                str = "after authentication"
                print(str.center(50, '='))
                return res
            else:
                exit("\033[31;1mInvalid Username or Password\033[0m")

        return wrapper
    return outer_wrapper

def index():
    print("Welcome to Index")

@auth(auth_type="local")
def home():
    print("Welcome to Home")
    return "From Home"

@auth(auth_type="ldap")
def bbs():
    print("Welcome to BBS")

index()
print(home())   #wrapper()
bbs()

---------------執行結果---------------

auth auth_type: local
Traceback (most recent call last):
  File "/Python/path/pratice_decorator4.py", line 29, in <module>
    @auth(auth_type="local")
TypeError: outer_wrapper() takes 0 positional arguments but 1 was given

Process finished with exit code 1

跟著錯誤再次修改,outer_wrapper() 需要0個位置參數,但卻給了一個參數,這是因為原本的 func 被刪除了, 其實 func 就要往下一層放

  • def outer_wrapper(): 修改成 def outer_wrapper(func):
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

user, passwd = 'tony', 'stark'

def auth(auth_type):
    print("auth auth_type:", auth_type)
    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            print("wrapper func agrs:", *args, **kwargs)
            username = input("Username:").strip()
            password = input("Password:").strip()

            if username == user and password == passwd:
                print("\033[32;1mUser has passed authentication\033[0m")
                res = func(*args, **kwargs)  # From home
                str = "after authentication"
                print(str.center(50, '='))
                return res
            else:
                exit("\033[31;1mInvalid Username or Password\033[0m")

        return wrapper
    return outer_wrapper

def index():
    print("Welcome to Index")

@auth(auth_type="local")
def home():
    print("Welcome to Home")
    return "From Home"

@auth(auth_type="ldap")
def bbs():
    print("Welcome to BBS")

index()
print(home())   #wrapper()
bbs()

---------------執行結果---------------

auth auth_type: local
auth auth_type: ldap
Welcome to Index
wrapper func agrs:
Username:tony
Password:stark
User has passed authentication
Welcome to Home
===============after authentication===============
From Home
wrapper func agrs:
Username:tony
Password:stark
User has passed authentication
Welcome to BBS
===============after authentication===============

Process finished with exit code 0

其實上面代碼已經可以正常執行了,但卻還沒有加入判斷 local 或是 ldap 驗証的方法,所以再修改一下代碼

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

user, passwd = 'tony', 'stark'

def auth(auth_type):
    print("auth auth_type:", auth_type)
    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            print("wrapper func agrs:", *args, **kwargs)
            if auth_type == "local":
                username = input("Username:").strip()
                password = input("Password:").strip()

                if username == user and password == passwd:
                    print("\033[32;1mUser has passed authentication\033[0m")
                    res = func(*args, **kwargs)  # From home
                    str = "after authentication"
                    print(str.center(50, '='))
                    return res
                else:
                    exit("\033[31;1mInvalid Username or Password\033[0m")
            elif auth_type == "ldap":
                print('我還不會LDAP阿~哥')

        return wrapper
    return outer_wrapper

def index():
    print("Welcome to Index")

@auth(auth_type="local")
def home():
    print("Welcome to Home")
    return "From Home"

@auth(auth_type="ldap")
def bbs():
    print("Welcome to BBS")

index()
print(home())   #wrapper()
bbs()
posted @ 2017-08-02 22:42  丹尼爾  阅读(217)  评论(0编辑  收藏  举报