首頁 > 軟體

Python函數高階(名稱空間、作用域、裝飾器)

2022-05-28 18:01:17

一、名稱空間和作用域

1、名稱空間(Namespace)

名稱空間是從名稱到物件的對映,大部分的名稱空間都是通過 Python 字典來實現的。

名稱空間提供了在專案中避免名字衝突的一種方法。各個名稱空間是獨立的,沒有任何關係的,所以一個名稱空間中不能有重名,但不同的名稱空間是可以重名而沒有任何影響。

1、一般有三種名稱空間:

  • 內建名稱空間(built-in names):存放內建的名字,如len/eval/enumerate/bytes/max/min/sorted/map/filter....
  • 全域性名稱空間(global names):模組中定義的名稱,記錄了模組的變數,包括函數、類、其它匯入的模組、模組級的變數和常數。
  • 區域性名稱空間(local names):函數內部的名字都是區域性名稱空間,不同函數內部的名字互不干涉。

2、名稱空間查詢順序:

如果找不到變數 runoob,它將放棄查詢並引發一個 NameError 異常:

NameError: name 'runoob' is not defined。
  • 查詢順序:假設我們要使用變數 runoob,則 Python 的查詢順序為:區域性的名稱空間去 -> 全域性名稱空間 -> 內建名稱空間
  • 執行順序:先內建(Python直譯器啟動的時候才會生成)-> 全域性(檔案執行的時候才會生成)-> 區域性(函數呼叫的時候才會生成)

3、名稱空間的生命週期:

名稱空間的生命週期取決於物件的作用域,如果物件執行完成,則該名稱空間的生命週期就結束。

因此,我們無法從外部名稱空間存取內部名稱空間的物件。

如下圖所示,相同的物件名稱可以存在於多個名稱空間中。

2、作用域:

作用域就是一個 Python 程式可以直接存取名稱空間的正文區域。

全域性名稱空間和區域性名稱空間中可能會存在名字相同的變數,但是這兩個變數互不影響。

Python 中,程式的變數並不是在哪個位置都可以存取的,存取許可權決定於這個變數是在哪裡賦值的。

變數的作用域決定了在哪一部分程式可以存取哪個特定的變數名稱。

Python的作用域一共有4種,分別是:

  • L(Local):最內層,包含區域性變數,比如一個函數/方法內部。
  • E(Enclosing):包含了非區域性(non-local)也非全域性(non-global)的變數。比如兩個巢狀函數,一個函數(或類) A 裡面又包含了一個函數 B ,那麼對於 B 中的名稱來說 A 中的作用域就為 nonlocal。
  • G(Global):當前指令碼的最外層,比如當前模組的全域性變數。
  • B(Built-in): 包含了內建的變數/關鍵字等。,最後被搜尋

對於變數作用域,變數的存取以: L –> E –> G –>B 的 規則查詢。

在區域性找不到,便會去區域性外的區域性找(例如閉包),再找不到就會去全域性找,再者去內建中找。

舉例:

x = 1

def func():
    print(x)  #10

x = 10
func()

內建作用域是通過一個名為 builtin 的標準模組來實現的,但是這個變數名自身並沒有放入內建作用域內,所以必須匯入這個檔案才能夠使用它。

在Python3.0中,可以使用以下的程式碼來檢視到底預定義了哪些變數:

import builtins
print(dir(builtins))

Python 中只有模組(module),類(class)以及函數(def、lambda)才會引入新的作用域,其它的程式碼塊(如 if/elif/else/、try/except、for/while等)是不會引入新的作用域的,也就是說這些語句內定義的變數,外部也可以存取,

如下程式碼:範例中 msg 變數定義在 if 語句塊中,但外部還是可以存取的。如果將 msg 定義在函數中,則它就是區域性變數,外部不能存取。

if True:
    msg = 'I am from Runoob'
print(msg)
# 'I am from Runoob'

3、全域性變數和區域性變數

定義在函數內部的變數擁有一個區域性作用域,定義在函數外的擁有全域性作用域。

區域性變數只能在其被宣告的函數內部存取,而全域性變數可以在整個程式範圍內存取。呼叫函數時,所有在函數內宣告的變數名稱都將被加入到作用域中。

# 作用域注意點
x = 1

def f1():  # 定義階段x=1
    print(x)  #1

def f2():
    x = 2  #此x為f2函數的區域性變數,f1無法直接存取
    f1()

f2()

4、函數物件+作用域應用

def f1():
    def inner():
        print('from inner')
    return inner

f = f1()  # from inner   。把區域性定義的函數inner()放在全域性之中

def bar():
    f()

bar()

5、global關鍵字修改全域性作用域中的變數

函數內可以存取全域性變數,但不能直接更新(修改)其值,可以加上 global 參照以更新變數值 :

x = 1

def f1():
    x = 2

    def f2():
        global x  # 修改全域性
        x = 3

    f2()

f1()
print(x)  # 3

6、nonlocal關鍵字修改巢狀作用域中的變數。

如果要修改巢狀作用域(enclosing 作用域,外層非全域性作用域)中的變數則需要 nonlocal 關鍵字了

x = 1

def f1():
    x = 2

    def f2():
        nonlocal x
        x = 3

    f2()
    print(x)  # 3

f1()

二、閉包函數

閉包:閉是封閉(函數內部函數),包是包含(該內部函數對外部作用域而非全域性作用域的變數的參照)。

閉包指的是:函數內部函數對外部作用域而非全域性作用域的參照。

def outter(x):
    x = 1

    def inner():
        print(x)

    return inner #返回的是函數名(函數物件)


f = outter(2)

f()  # 1
f()  # 1
f()  # 1
# 檢視閉包的元素
print(f.__closure__[0].cell_contents)  # 1

閉包的意義:返回的函數物件,不僅僅是一個函數物件,在該函數外還包裹了一層作用域,這使得,該函數無論在何處呼叫,優先使用自己外層包裹的作用域。

應用領域:

延遲計算(原來我們是傳參,現在我們是包起來)、爬蟲領域。

import requests


def outter(url):
    def get():
        response = requests.get(url)
        print(f"done: {url}")

    return get


baidu = outter('https://www.baidu.com')
python = outter('https://www.python.org')

baidu()
baidu()

python()
python()

三、函數裝飾器

裝飾器指的是為被裝飾器物件新增額外功能。因此定義裝飾器就是定義一個函數,只不過該函數的功能是用來為其他函數新增額外的功能。裝飾器的實現必須遵循兩大原則:

  • 不修改被裝飾物件的原始碼
  • 不修改被裝飾物件的呼叫方式

裝飾器其實就是在遵循以上兩個原則的前提下為被裝飾物件新增新功能。

不改變函數體程式碼,並且不改變函數呼叫方式,它本質就是一個閉包函數。

def f1(x):
    def f2():
        print(x)  # 10
    return f2

f2 = f1()
f2()  # f2()

在不改變當前函數的情況下, 給其增加新的功能:

def log(pr):  # 將被裝飾函數傳入
    def wrapper():
        print("**********")
        return pr()  # 執行被裝飾的函數

    return wrapper  # 將裝飾完之後的函數返回(返回的是函數名)


@log
def pr():
    print("我是小小洋")


pr()

# **********
# 我是小小洋

回撥函數和返回函數的範例就是裝飾器。

四、無參裝飾器

舉例:

import time


def index():
    print('welcome to index')
    time.sleep(1)


def time_count(func):
    # func = 最原始的index
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print(f"{func} time is {start - end}")  #  time is -1.0038220882415771

    return wrapper


index = time_count(index)  # index為被裝飾函數index的記憶體地址,即index = wrapper
index()  # wrapper()

1、被裝飾函數有返回值:

如果原始的被裝飾函數index()有返回值的時候,wrapper()函數的返回值應該和index()的返回值相同,也就是說,我們需要同步原始的index()和wrapper()方法的返回值。

import time


def index():
    print('welcome to index')
    time.sleep(1)
    return 123


def time_count(func):
    # func = 最原始的index
    def wrapper():
        start = time.time()
        res1 = func()
        end = time.time()
        print(f"{func} time is {start - end}")  #  time is -1.0050289630889893
        return res1

    return wrapper


index = time_count(index)
res = index()
print(f"res: {res}")  #
res: 123

2、被裝飾函數需要傳參:

如果原始的被裝飾函數index()方法需要傳參,那麼我們之前的裝飾器是無法實現該功能的,由於有wrapper()=index(),所以給wrapper()方法傳參即可。

import time


def index():
    print('welcome to index')
    time.sleep(1)
    return 123


def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)
    return name


def time_count(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(f"{func} time is {start-end}") #  time is -1.0039079189300537
        return res

    return wrapper


home = time_count(home)

res = home('egon')
print(f"res: {res}") #res: egon

3、裝飾器模板

def deco(func):
    def wrapper(*args,**kwargs):
        res = func(*args,**kwargs)
        return res
    return wrapper

4、裝飾器語法糖:

在被裝飾函數正上方,並且是單獨一行寫上@裝飾器名

import time


def time_count(func): #裝飾器
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(f"{func} time is {start-end}") # time is -1.0005171298980713
        return res

    return wrapper


@time_count  # home = time_count(home)
def home(name):
    print(f"welcome {name} to home page") #welcome egon to home page
    time.sleep(1)
    return name


res = home('egon')
print(f"res: {res}") #res: egon

五、帶引數的裝飾器

注意無參裝飾器只套兩層。

import time

current_user = {'username': None}


def login(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        if current_user['username']:
            res1 = func(*args, **kwargs)
            return res1

        user = input('username: ').strip()
        pwd = input('password: ').strip()

        if user == 'nick' and pwd == '123':
            print('login successful')
            current_user['username'] = user
            res1 = func(*args, **kwargs)
            return res1
        else:
            print('user or password error')

    return wrapper


@login
def index():
    print('welcome to index')
    time.sleep(1)


res = index()

#username: nick 
#password: 123 
#login successful 
#welcome to index

我們首先看看三層閉包怎麼運用。

def f1(y):
    def f2():
        x = 1

        def f3():
            print(f"x: {x}")  # x: 1
            print(f"y: {y}")  # x: 1

        return f3
    return f2


f2 = f1(2)
f3 = f2()
f3()

3、有參三層裝飾器:

在函數中嵌入裝飾器

import time

current_user = {'username': None}


def auth(engine='file'):
    def login(func):
        def wrapper(*args, **kwargs):
            if current_user['username']:
                res = func(*args, **kwargs)
                return res

            user = input('username: ').strip()
            pwd = input('password: ').strip()

            if engine == 'file':
                print('base of file')
                if user == 'nick' and pwd == '123':
                    print('login successful')
                    current_user['username'] = user
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('user or password error')
            elif engine == 'mysql':
                print('base of mysql, please base of file')
        return wrapper
    return login


@auth(engine='file')
def index():
    print('welcome to index')
    time.sleep(1)


res = index()

username: nick 
password: 123 
base of file 
login successful 
welcome to index

六、類裝飾器

沒錯,裝飾器不僅可以是函數,還可以是類,相比函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優點。使用類裝飾器主要依靠類的__call__方法,當使用 @ 形式將裝飾器附加到函數上時,就會呼叫此方法。

class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self):
        print ('class decorator runing')
        self._func()
        print ('class decorator ending')

@Foo
def bar():
    print ('bar')

bar()

functools.wraps

使用裝飾器極大地複用了程式碼,但是他有一個缺點就是原函數的元資訊不見了,比如函數的docstring、__name__、參數列,先看例子:

# 裝飾器
def logged(func):
    def with_logging(*args, **kwargs):
        print func.__name__      # 輸出 'with_logging'
        print func.__doc__       # 輸出 None
        return func(*args, **kwargs)
    return with_logging

# 函數
@logged
def f(x):
   """does some math"""
   return x + x * x

logged(f)

不難發現,函數 f 被with_logging取代了,當然它的docstring,__name__就是變成了with_logging函數的資訊了。好在我們有functools.wraps,wraps本身也是一個裝飾器,它能把原函數的元資訊拷貝到裝飾器裡面的 func 函數中,這使得裝飾器裡面的 func 函數也有和原函數 foo 一樣的元資訊了。

from functools import wraps

def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print func.__name__      # 輸出 'f'
        print func.__doc__       # 輸出 'does some math'
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

七、裝飾器順序

一個函數還可以同時定義多個裝飾器,比如:

@a
@b
@c
def f ():
    pass

它的執行順序是從裡到外,最先呼叫最裡層的裝飾器,最後呼叫最外層的裝飾器,它等效於

f = a(b(c(f)))

八、裝飾器使用場景

現在我們來看一下裝飾器在哪些地方特別耀眼,以及使用它可以讓一些事情管理起來變得更簡單。

授權(Authorization)

裝飾器能有助於檢查某個人是否被授權去使用一個web應用的端點(endpoint)。它們被大量使用於Flask和Django web框架中。這裡是一個例子來使用基於裝飾器的授權:

from functools import wraps
 
def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            authenticate()
        return f(*args, **kwargs)
    return decorated

紀錄檔(Logging)

紀錄檔是裝飾器運用的另一個亮點。這是個例子:

from functools import wraps
 
def logit(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging
 
@logit
def addition_func(x):
   """Do some math."""
   return x + x
 
 
result = addition_func(4)
# Output: addition_func was called

到此這篇關於Python函數高階用法的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com