首頁  >  文章  >  後端開發  >  詳解python裝飾器的實例教程

詳解python裝飾器的實例教程

零下一度
零下一度原創
2017-06-17 11:17:291231瀏覽

Python中的裝飾器是你進入Python大門的一道坎,不管你跨不跨過去它都在那裡。 Python中的裝飾器的概念常常會讓人搞得一頭霧水,所以今天就好好來分析一下python中的裝飾器

1.作用域

 在python中,作用域分為兩種:全域作用域和局部作用域。

 全域作用域是定義在檔案層級的變數,函數名稱。而局部作用域,則是定義函數內部。

 關於作用域,我要理解兩點:a.在全域不能存取到局部定義的變數b.在局部能夠存取到全域定義的變量,但是不能修改全域定義的變數(當然有方法可以修改)

 下面我們來看看下面實例:


x = 1
def funx():
  x = 10
  print(x) # 打印出10

funx()
print(x) # 打印出1

  如果局部沒有定義變數x,那麼函數內部會從內往外開始查找x,如果沒有找到,就會報錯


x = 1
def funx():
  print(x) # 打印出1

funx()
print(x) # 打印出1

x = 1
def funx():
  def func1():
    print(x) # 打印出1
  func1()

funx()
print(x) # 打印出1

  因此,關於作用域的問題,只需要記住兩點就行:全域變數能夠被文件任何地方引用,但修改只能在全域進行操作;如果局部沒有找到所需的變量,就會往外進行查找,沒有找到就會報錯。

2.高階函數

 我們知道,函數名其實就是指向一段記憶體空間的位址,既然是位址,那麼我們可以利用這個特性來。

 a函數名稱可以作為一個值


def delete(ps):
  import os
  filename = ps[-1]
  delelemetns = ps[1]
  with open(filename, encoding='utf-8') as f_read,\
    open('tmp.txt', 'w', encoding='utf-8') as f_write:
    for line in iter(f_read.readline, ''):
      if line != '\n': # 处理非空行
        if delelemetns in line:
          line = line.replace(delelemetns,'')
        f_write.write(line)
  os.remove(filename)
  os.rename('tmp.txt',filename)

def add(ps):
  filename = ps[-1]
  addelemetns = ps[1]
  with open(filename, 'a', encoding='utf-8') as fp:
    fp.write("\n", addelemetns)

def modify(ps):
  import os
  filename = ps[-1]
  modify_elemetns = ps[1]
  with open(filename, encoding='utf-8') as f_read, \
      open('tmp.txt', 'w', encoding='utf-8') as f_write:
    for line in iter(f_read.readline, ''):
      if line != '\n': # 处理非空行
        if modify_elemetns in line:
          line = line.replace(modify_elemetns, '')
        f_write.write(line)
  os.remove(filename)
  os.rename('tmp.txt', filename)


def search(cmd):
  filename = cmd[-1]
  pattern = cmd[1]
  with open(filename, 'r', encoding="utf-8") as f:
    for line in f:
      if pattern in line:
        print(line, end="")
    else:
      print("没有找到")

dic_func ={'delete': delete, 'add': add, 'modify': modify, 'search': search}

while True:
  inp = input("请输入您要进行的操作:").strip()
  if not inp:
    continue
  cmd_1 = inp.split()
  cmd = cmd_1[0]
  if cmd in dic_func:
    dic_func[cmd](cmd_1)
  else:
    print("Error")

 b.函數名稱可以作為一個值


def outer():
  def inner():
    pass
  return inner

s = outer()
print(s)

######输出结果为#######
<function outer.<locals>.inner at 0x000000D22D8AB8C8>

 b.函數名稱可以作為傳回值


def index():
  print("index func")

def outer(index):
  s = index
  s()
  
outer(index)

######输出结果#########

index func

 c..函數名稱可以作為一個參數

def outer():
  def inner():
    print("inner func excuted")
  inner() # 调用执行inner()函数
  print("outer func excuted")
outer() # 调用执行outer函数

####输出结果为##########
inner func excuted
outer func excuted

 所以滿足上面兩個條件中的一個,都可以稱為高級函數.

3.閉包函數

  閉包函數必須滿足兩個條件:1.函數內部定義的函數2.包含對外部作用域而非全域作用域的參考


  下面透過一些實例來說明閉包函數:

  實例一:以下僅在函數內部定義了一個函數,但並非閉包函數.


#
x = 1
def outer():
  def inner():
    print("x=%s" %x) # 引用了一个非inner函数内部的变量
    print("inner func excuted")
  inner() # 执行inner函数
  print("outer func excuted")

outer()
#####输出结果########
x=1
inner func excuted
outer func excuted

  實例二:以下在函數內部定義了一個函數,而且還引用了一個外部變數x,那麼這個是閉包函數麼?答案:不是


def outer():
  x = 1
  def inner():
    print("x=%s" %x)
    print("inner func excuted")
  inner()
  print("outer func excuted")

outer()

#####输出结果#########
x=1
inner func excuted
outer func excuted

  在回頭來看看對閉包函數的定義,是不是兩條都滿足?聰明的你,一定發現不滿足第二條.對,這裡的變數x,是屬於全局變數,而非外部作用於域的變數。再來看看下面範例:


def outer():
  x = 1
  def inner():
    print("x=%s" %x)
    print("inner func excuted")
  print("outer func excuted")
  return inner # 返回内部函数名
  
outer()

  顯然,上面實例滿足閉包函數的條件。現在,你應該清楚,作為一個閉包函數,必須得滿足上述的兩個條件,缺一不可。但是,一般情況下,我們都會給閉包函數回傳一個值.這裡先不說為什麼.在接下來的內容中,你會看到這個回傳值的用途.

def outer():
  x = 1
  y = 2

  def inner():
    print("x= %s" %x)
    print("y= %s" %y)

  print(inner.closure)
  return inner

outer()

######输出结果#######
(<cell at 0x000000DF9EA965B8: int object at 0x000000006FC2B440>, <cell at 0x000000DF9EA965E8: int object at 0x000000006FC2B460>)

  現在我們來抽象的定義一下閉包函數。它是函數和與其相關的引用環境組合而成的實體。在實現深層約束時,需要創建一個能明確表示引用環境的東西,並將它與相關的子程序捆綁在一起,這樣捆綁起成為閉包。在上面實例中,我們可以發現,閉包函數,它必須包含自己的函數以及一個外部變數才能真正稱得上是一個閉包函數。如果沒有一個外部變數與其綁定,那麼這個函數不能算是閉包函數。

  那麼怎麼知道一個閉包函數有多少個外部引用變數?看看下面程式碼.

from urllib.request import urlopen

def index(url)
  def get()
    return urlopen(url).read()
  return get

python = index("http://www.python.org") # 返回的是get函数的地址
print(python()) # 执行get函数《并且将返回的结果打印出来
baidu = index("http://www.baidu.com")
print(baidu())

  結果顯示,在inner內部,引用了兩個外部局部變數。如果引用的是非局部變量,那麼這裡輸出的為None.

  閉包函數的特點:

1.自帶作用域2.延遲計算


  那麼閉包函數有什麼作用呢?我們清楚的知道,閉包函數在定義時,一定會綁定一個外在環境。這個整體才能算的上是一個閉包函數,那麼我們可以利用這個綁定特性,來完成某些特殊的功能。

  實例三:根據傳入的URL,來下載頁面原始碼

import time, random

def index():
  time.sleep(random.randrange(1, 5))
  print("welcome to index page")

  有人可以會說,這個不滿足閉包函數的條件啊!我沒有引用非全域的外部變數啊。其實並非如此,給,我們之前說過,只要在函數內部的變數都屬於函數。那我在index(url),這個url也屬於函數內部,只不過我們省略一步而已,所以上面那個函數也是閉包函數。

4.裝飾器

  有了以上基礎,對於裝飾器就好理解了.

  裝飾器:外部函數傳入被裝飾函數名,內部函數傳回裝飾函數名。

  特點:1.不修改被裝飾函數的呼叫方式2.不修改被裝飾函數的原始碼######  a.無參裝飾者#######  有以下實例,我們需要計算一下程式碼執行的時間。 ###


import time, random

def index():
  time.sleep(random.randrange(1, 5))
  print("welcome to index page")

  根据装饰器的特点,我们不能对index()进行任何修改,而且调用方式也不能变。这时候,我们就可以使用装饰器来完成如上功能.


import time, random

def outer(func): # 将index的地址传递给func
  def inner():
    start_time = time.time()
    func()  # fun = index 即func保存了外部index函数的地址
    end_time = time.time()
    print("运行时间为%s"%(end_time - start_time))
  return inner # 返回inner的地址

def index():
  time.sleep(random.randrange(1, 5))
  print("welcome to index page")

index = outer(index) # 这里返回的是inner的地址,并重新赋值给index

index()

  但是,有些情况,被装饰的函数需要传递参数进去,有些函数又不需要参数,那么如何来处理这种变参数函数呢?下面来看看有参数装饰器的使用情况.

  b.有参装饰器


def outer(func): # 将index的地址传递给func
  def inner(*args, **kwargs):
    start_time = time.time()
    func(*args, **kwargs)  # fun = index 即func保存了外部index函数的地址
    end_time = time.time()
    print("运行时间为%s"%(end_time - start_time))
  return inner # 返回inner的地址

  下面来说说一些其他情况的实例。

   如果被装饰的函数有返回值


def timmer(func):
  def wrapper(*args,**kwargs):
    start_time = time.time()
    res=func(*args,**kwargs) #res来接收home函数的返回值
    stop_time=time.time()
    print(&#39;run time is %s&#39; %(stop_time-start_time))
    return res 
  return wrapper

def home(name):
  time.sleep(random.randrange(1,3))
  print(&#39;welecome to %s HOME page&#39; %name)
  return 123123123123123123123123123123123123123123

  这里补充一点,加入我们要执行被装饰后的函数,那么应该是如下调用方式:

  home = timmer(home)  # 等式右边返回的是wrapper的内存地址,再将其赋值给home,这里的home不在是原来的的那个函数,而是被装饰以后的函数了。像home = timmer(home)这样的写法,python给我们提供了一个便捷的方式------语法糖@.以后我们再要在被装饰的函数之前写上@timmer,它的效果就和home = timmer(home)是一样的。

  如果一个函数被多个装饰器装饰,那么执行顺序是怎样的。


import time
import random

def timmer(func):
  def wrapper():
    start_time = time.time()
    func()
    stop_time=time.time()
    print(&#39;run time is %s&#39; %(stop_time-start_time))
  return wrapper
def auth(func):
  def deco():
    name=input(&#39;name: &#39;)
    password=input(&#39;password: &#39;)
    if name == &#39;egon&#39; and password == &#39;123&#39;:
      print(&#39;login successful&#39;)
      func() #wrapper()
    else:
      print(&#39;login err&#39;)
  return deco

@auth  # index = auth(timmer(index))         
@timmer # index = timmer(index)
def index():
 
  time.sleep(3)
  print(&#39;welecome to index page&#39;)

index()

  实验结果表明,多个装饰器装饰一个函数,其执行顺序是从下往上。

  关于装饰器,还有一些高级用法,有兴趣的可以自己研究研究。

以上是詳解python裝飾器的實例教程的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn