亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Python的裝飾器使用詳解

 更新時間:2017年06月26日 08:47:36   作者:0xFEE1C001  
最近在學(xué)習(xí)python,下面是在Python學(xué)習(xí)小組上介紹的內(nèi)容,現(xiàn)學(xué)現(xiàn)賣、多練習(xí)是好的學(xué)習(xí)方式,希望大家能夠喜歡

Python有大量強(qiáng)大又貼心的特性,如果要列個最受歡迎排行榜,那么裝飾器絕對會在其中。

初識裝飾器,會感覺到優(yōu)雅且神奇,想親手實(shí)現(xiàn)時卻總有距離感,就像深閨的冰美人一般。這往往是因為理解裝飾器時把其他的一些概念混雜在一起了。待我撫去層層面紗,你會看到純粹的裝飾器其實(shí)蠻簡單直率的。

裝飾器的原理

在解釋器下跑個裝飾器的例子,直觀地感受一下。
# make_bold就是裝飾器,實(shí)現(xiàn)方式這里略去

>>> @make_bold
... def get_content():
...  return 'hello world'
...
>>> get_content()
'<b>hello world</b>'

被 make_bold 裝飾的 get_content ,調(diào)用后返回結(jié)果會自動被 b 標(biāo)簽包住。怎么做到的呢,簡單4步就能明白了。

1. 函數(shù)是對象

我們定義個 get_content 函數(shù)。這時 get_content 也是個對象,它能做所有對象的操作。

def get_content():
  return 'hello world'

它有 id ,有 type ,有值。

>>> id(get_content)
140090200473112
>>> type(get_content)
<class 'function'>
>>> get_content
<function get_content at 0x7f694aa2be18>

跟其他對象一樣可以被賦值給其它變量。

>>> func_name = get_content
>>> func_name()
'hello world'

它可以當(dāng)參數(shù)傳遞,也可以當(dāng)返回值

>>> def foo(bar):
...   print(bar())
...   return bar
...
>>> func = foo(get_content)
hello world
>>> func()
'hello world'

2. 自定義函數(shù)對象

我們可以用 class 來構(gòu)造函數(shù)對象。有成員函數(shù) __call__ 的就是函數(shù)對象了,函數(shù)對象被調(diào)用時正是調(diào)用的 __call__ 。

class FuncObj(object):
  def __init__(self, name):
    print('Initialize')
    self.name= name

  def __call__(self):
    print('Hi', self.name)

我們來調(diào)用看看。可以看到, 函數(shù)對象的使用分兩步:構(gòu)造和調(diào)用 (同學(xué)們注意了,這是考點(diǎn))。

>>> fo = FuncObj('python')
Initialize
>>> fo()
Hi python

3. @ 是個語法糖

裝飾器的 @ 沒有做什么特別的事,不用它也可以實(shí)現(xiàn)一樣的功能,只不過需要更多的代碼。

@make_bold
def get_content():
  return 'hello world'

# 上面的代碼等價于下面的

def get_content():
  return 'hello world'
get_content = make_bold(get_content)

make_bold 是個函數(shù),要求入?yún)⑹呛瘮?shù)對象,返回值是函數(shù)對象。 @ 的語法糖其實(shí)是省去了上面最后一行代碼,使可讀性更好。用了裝飾器后,每次調(diào)用 get_content ,真正調(diào)用的是 make_bold 返回的函數(shù)對象。

4. 用類實(shí)現(xiàn)裝飾器

入?yún)⑹呛瘮?shù)對象,返回是函數(shù)對象,如果第2步里的類的構(gòu)造函數(shù)改成入?yún)⑹莻€函數(shù)對象,不就正好符合要求嗎?我們來試試實(shí)現(xiàn) make_bold 。

class make_bold(object):
  def __init__(self, func):
    print('Initialize')
    self.func = func

  def __call__(self):
    print('Call')
    return '<b>{}</b>'.format(self.func())

大功告成,看看能不能用。

>>> @make_bold
... def get_content():
...   return 'hello world'
...
Initialize
>>> get_content()
Call
'<b>hello world</b>'

成功實(shí)現(xiàn)裝飾器!是不是很簡單?

這里分析一下之前強(qiáng)調(diào)的 構(gòu)造 和 調(diào)用 兩個過程。我們?nèi)サ?@ 語法糖好理解一些。
# 構(gòu)造,使用裝飾器時構(gòu)造函數(shù)對象,調(diào)用了__init__

>>> get_content = make_bold(get_content)
Initialize

# 調(diào)用,實(shí)際上直接調(diào)用的是make_bold構(gòu)造出來的函數(shù)對象
>>> get_content()
Call
'<b>hello world</b>'

到這里就徹底清楚了,完結(jié)撒花,可以關(guān)掉網(wǎng)頁了~~~(如果只是想知道裝飾器原理的話)

函數(shù)版裝飾器

閱讀源碼時,經(jīng)常見到用嵌套函數(shù)實(shí)現(xiàn)的裝飾器,怎么理解?同樣僅需4步。

1. def 的函數(shù)對象初始化

用 class 實(shí)現(xiàn)的函數(shù)對象很容易看到什么時候 構(gòu)造 的,那 def 定義的函數(shù)對象什么時候 構(gòu)造 的呢?
# 這里的全局變量刪去了無關(guān)的內(nèi)容

>>> globals()
{}
>>> def func():
...   pass
...
>>> globals()
{'func': <function func at 0x10f5baf28>}

不像一些編譯型語言,程序在啟動時函數(shù)已經(jīng)構(gòu)造那好了。上面的例子可以看到,執(zhí)行到 def 會才構(gòu)造出一個函數(shù)對象,并賦值給變量 make_bold 。

這段代碼和下面的代碼效果是很像的。

class NoName(object):
  def __call__(self):
    pass

func = NoName()

2. 嵌套函數(shù)

Python的函數(shù)可以嵌套定義。

def outer():
  print('Before def:', locals())
  def inner():
    pass
  print('After def:', locals())
  return inner

inner 是在 outer 內(nèi)定義的,所以算 outer 的局部變量。執(zhí)行到 def inner 時函數(shù)對象才創(chuàng)建,因此每次調(diào)用 outer 都會創(chuàng)建一個新的 inner 。下面可以看出,每次返回的 inner 是不同的。

>>> outer()
Before def: {}
After def: {'inner': <function outer.<locals>.inner at 0x7f0b18fa0048>}
<function outer.<locals>.inner at 0x7f0b18fa0048>
>>> outer()
Before def: {}
After def: {'inner': <function outer.<locals>.inner at 0x7f0b18fa00d0>}
<function outer.<locals>.inner at 0x7f0b18fa00d0>

3. 閉包

嵌套函數(shù)有什么特別之處?因為有閉包。

def outer():
  msg = 'hello world'
  def inner():
    print(msg)
  return inner

下面的試驗表明, inner 可以訪問到 outer 的局部變量 msg 。

>>> func = outer()
>>> func()
hello world

閉包有2個特點(diǎn)
1. inner 能訪問 outer 及其祖先函數(shù)的命名空間內(nèi)的變量(局部變量,函數(shù)參數(shù))。
2. 調(diào)用 outer 已經(jīng)返回了,但是它的命名空間被返回的 inner 對象引用,所以還不會被回收。

這部分想深入可以去了解Python的LEGB規(guī)則。

4. 用函數(shù)實(shí)現(xiàn)裝飾器

裝飾器要求入?yún)⑹呛瘮?shù)對象,返回值是函數(shù)對象,嵌套函數(shù)完全能勝任。

def make_bold(func):
  print('Initialize')
  def wrapper():
    print('Call')
    return '<b>{}</b>'.format(func())
  return wrapper

用法跟類實(shí)現(xiàn)的裝飾器一樣??梢匀サ?@ 語法糖分析下 構(gòu)造 和 調(diào)用 的時機(jī)。

>>> @make_bold
... def get_content():
...   return 'hello world'
...
Initialize
>>> get_content()
Call
'<b>hello world</b>'

因為返回的 wrapper 還在引用著,所以存在于 make_bold 命名空間的 func 不會消失。 make_bold 可以裝飾多個函數(shù), wrapper 不會調(diào)用混淆,因為每次調(diào)用 make_bold ,都會有創(chuàng)建新的命名空間和新的 wrapper 。

到此函數(shù)實(shí)現(xiàn)裝飾器也理清楚了,完結(jié)撒花,可以關(guān)掉網(wǎng)頁了~~~(后面是使用裝飾的常見問題)

常見問題

1. 怎么實(shí)現(xiàn)帶參數(shù)的裝飾器?

帶參數(shù)的裝飾器,有時會異常的好用。我們看個例子。

>>> @make_header(2)
... def get_content():
...   return 'hello world'
...
>>> get_content()
'<h2>hello world</h2>'

怎么做到的呢?其實(shí)這跟裝飾器語法沒什么關(guān)系。去掉 @ 語法糖會變得很容易理解。

@make_header(2)
def get_content():
  return 'hello world'

# 等價于

def get_content():
  return 'hello world'
unnamed_decorator = make_header(2)
get_content = unnamed_decorator(get_content)

上面代碼中的 unnamed_decorator 才是真正的裝飾器, make_header 是個普通的函數(shù),它的返回值是裝飾器。

來看一下實(shí)現(xiàn)的代碼。

def make_header(level):
  print('Create decorator')

  # 這部分跟通常的裝飾器一樣,只是wrapper通過閉包訪問了變量level
  def decorator(func):
    print('Initialize')
    def wrapper():
      print('Call')
      return '<h{0}>{1}</h{0}>'.format(level, func())
    return wrapper

  # make_header返回裝飾器
  return decorator

看了實(shí)現(xiàn)代碼,裝飾器的 構(gòu)造 和 調(diào)用 的時序已經(jīng)很清楚了。

>>> @make_header(2)
... def get_content():
...   return 'hello world'
...
Create decorator
Initialize
>>> get_content()
Call
'<h2>hello world</h2>'

2. 如何裝飾有參數(shù)的函數(shù)?

為了有條理地理解裝飾器,之前例子里的被裝飾函數(shù)有意設(shè)計成無參的。我們來看個例子。

@make_bold
def get_login_tip(name):
  return 'Welcome back, {}'.format(name)

最直接的想法是把 get_login_tip 的參數(shù)透傳下去。

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

  def __call__(self, name):
    return '<b>{}</b>'.format(self.func(name))

如果被裝飾的函數(shù)參數(shù)是明確固定的,這么寫是沒有問題的。但是 make_bold 明顯不是這種場景。它既需要裝飾沒有參數(shù)的 get_content ,又需要裝飾有參數(shù)的 get_login_tip 。這時候就需要可變參數(shù)了。

class make_bold(object):
  def __init__(self, func):
    self.func = func
  def __call__(self, *args, **kwargs):
    return '<b>{}</b>'.format(self.func(*args, **kwargs))

當(dāng)裝飾器不關(guān)心被裝飾函數(shù)的參數(shù),或是被裝飾函數(shù)的參數(shù)多種多樣的時候,可變參數(shù)非常合適。可變參數(shù)不屬于裝飾器的語法內(nèi)容,這里就不深入探討了。

3. 一個函數(shù)能否被多個裝飾器裝飾?

下面這么寫合法嗎?

@make_italic
@make_bold
def get_content():
  return 'hello world'

合法。上面的的代碼和下面等價,留意一下裝飾的順序。

def get_content():
  return 'hello world'
get_content = make_bold(get_content) # 先裝飾離函數(shù)定義近的
get_content = make_italic(get_content)

4. functools.wraps 有什么用?

Python的裝飾器倍感貼心的地方是對調(diào)用方透明。調(diào)用方完全不知道也不需要知道調(diào)用的函數(shù)被裝飾了。這樣我們就能在調(diào)用方的代碼完全不改動的前提下,給函數(shù)patch功能。

為了對調(diào)用方透明,裝飾器返回的對象要偽裝成被裝飾的函數(shù)。偽裝得越像,對調(diào)用方來說差異越小。有時光偽裝函數(shù)名和參數(shù)是不夠的,因為Python的函數(shù)對象有一些元信息調(diào)用方可能讀取了。為了連這些元信息也偽裝上, functools.wraps 出場了。它能用于把被調(diào)用函數(shù)的 __module__ , __name__ , __qualname__ , __doc__ , __annotations__ 賦值給裝飾器返回的函數(shù)對象。

import functools

def make_bold(func):
  @functools.wraps(func)
  def wrapper(*args, **kwargs):
    return '<b>{}</b>'.format(func(*args, **kwargs))
  return wrapper

對比一下效果。

>>> @make_bold
... def get_content():
...   '''Return page content'''
...   return 'hello world'

# 不用functools.wraps的結(jié)果
>>> get_content.__name__
'wrapper'
>>> get_content.__doc__
>>>

# 用functools.wraps的結(jié)果
>>> get_content.__name__
'get_content'
>>> get_content.__doc__
'Return page content'

實(shí)現(xiàn)裝飾器時往往不知道調(diào)用方會怎么用,所以養(yǎng)成好習(xí)慣加上 functools.wraps 吧。

這次是真·完結(jié)了,撒花吧~~~

相關(guān)文章

  • Python sklearn對文本數(shù)據(jù)進(jìn)行特征化提取

    Python sklearn對文本數(shù)據(jù)進(jìn)行特征化提取

    這篇文章主要介紹了Python sklearn對文本數(shù)據(jù)進(jìn)行特征化提取,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2023-04-04
  • 深入解析Python中的多進(jìn)程

    深入解析Python中的多進(jìn)程

    這篇文章主要介紹了深入解析Python中的多進(jìn)程,“Python中的多進(jìn)程是通過multiprocessing包來實(shí)現(xiàn)的,和多線程的threading.Thread差不多,它可以利用multiprocessing.Process對象來創(chuàng)建一個進(jìn)程對象
    2022-06-06
  • Python爬蟲進(jìn)階Scrapy框架精文講解

    Python爬蟲進(jìn)階Scrapy框架精文講解

    這篇文章主要為大家介紹了Python爬蟲進(jìn)階中Scrapy框架精細(xì)講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2021-10-10
  • Python Counting Bloom Filter原理與實(shí)現(xiàn)詳細(xì)介紹

    Python Counting Bloom Filter原理與實(shí)現(xiàn)詳細(xì)介紹

    這篇文章主要介紹了Python Counting Bloom Filter原理與實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2022-10-10
  • 詳解python的super()的作用和原理

    詳解python的super()的作用和原理

    這篇文章主要介紹了python的super()的作用和原理,super(), 在類的繼承里面super()非常常用, 它解決了子類調(diào)用父類方法的一些問題, 父類多次被調(diào)用時只執(zhí)行一次, 優(yōu)化了執(zhí)行邏輯,下面我們就來詳細(xì)看一下
    2020-10-10
  • python 實(shí)現(xiàn)多線程下載視頻的代碼

    python 實(shí)現(xiàn)多線程下載視頻的代碼

    這篇文章主要介紹了python 實(shí)現(xiàn)多線程下載視頻的代碼,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-11-11
  • Python3 MySQL 數(shù)據(jù)庫連接的使用示例

    Python3 MySQL 數(shù)據(jù)庫連接的使用示例

    本文我們?yōu)榇蠹医榻B Python3 使用 PyMySQL 連接數(shù)據(jù)庫,并實(shí)現(xiàn)簡單的增刪改查,需要的朋友可以參考下
    2021-06-06
  • flask應(yīng)用部署到服務(wù)器的方法

    flask應(yīng)用部署到服務(wù)器的方法

    這篇文章主要介紹了flask應(yīng)用部署到服務(wù)器的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • Python3.7 pyodbc完美配置訪問access數(shù)據(jù)庫

    Python3.7 pyodbc完美配置訪問access數(shù)據(jù)庫

    最近小編需要學(xué)習(xí)python連接access數(shù)據(jù)庫,發(fā)現(xiàn)很多朋友推薦pyodbc,那么這篇文章就先為大家介紹一下Python3.7下pyodbc的配置方法
    2019-10-10
  • selenium+python環(huán)境配置教程詳解

    selenium+python環(huán)境配置教程詳解

    這篇文章主要介紹了selenium+python環(huán)境配置教程,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-05-05

最新評論