欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Python中的裝飾器、閉包和functools的教程

 更新時(shí)間:2015年04月02日 10:46:15   作者:sahandsaba  
這篇文章主要介紹了詳解Python中的裝飾器、閉包和functools的教程,作者還給出了相關(guān)的Flask框架下的應(yīng)用實(shí)例,需要的朋友可以參考下

裝飾器(Decorators)

裝飾器是這樣一種設(shè)計(jì)模式:如果一個(gè)類希望添加其他類的一些功能,而不希望通過繼承或是直接修改源代碼實(shí)現(xiàn),那么可以使用裝飾器模式。簡單來說Python中的裝飾器就是指某些函數(shù)或其他可調(diào)用對象,以函數(shù)或類作為可選輸入?yún)?shù),然后返回函數(shù)或類的形式。通過這個(gè)在Python2.6版本中被新加入的特性可以用來實(shí)現(xiàn)裝飾器設(shè)計(jì)模式。

順便提一句,在繼續(xù)閱讀之前,如果你對Python中的閉包(Closure)概念不清楚,請查看本文結(jié)尾后的附錄,如果沒有閉包的相關(guān)概念,很難恰當(dāng)?shù)睦斫釶ython中的裝飾器。

在Python中,裝飾器被用于用@語法糖修辭的函數(shù)或類?,F(xiàn)在讓我們用一個(gè)簡單的裝飾器例子來演示如何做一個(gè)函數(shù)調(diào)用日志記錄器。在這個(gè)例子中,裝飾器將時(shí)間格式作為輸入?yún)?shù),在調(diào)用被這個(gè)裝飾器裝飾的函數(shù)時(shí)打印出函數(shù)調(diào)用的時(shí)間。這個(gè)裝飾器當(dāng)你需要手動比較兩個(gè)不同算法或?qū)崿F(xiàn)的效率時(shí)很有用。
 

def logged(time_format):
  def decorator(func):
   def decorated_func(*args, **kwargs):
     print "- Running '%s' on %s " % (
                     func.__name__,
                     time.strftime(time_format)
               )
     start_time = time.time()
     result = func(*args, **kwargs)
     end_time = time.time()
     print "- Finished '%s', execution time = %0.3fs " % (
                     func.__name__,
                     end_time - start_time
               )
 
     return result
   decorated_func.__name__ = func.__name__
   return decorated_func
 return decorator

來看一個(gè)例子,在這里add1和add2函數(shù)被logged修飾,下面給出了一個(gè)輸出示例。請注意在這里時(shí)間格式參數(shù)是存儲在被返回的裝飾器函數(shù)中(decorated_func)。這就是為什么理解閉包對于理解裝飾器來說很重要的原因。同樣也請注意返回函數(shù)的名字是如何被替換為原函數(shù)名的,以防萬一如果它還要被使用到,這是為了防止混淆。Python默認(rèn)可不會這么做。

@logged("%b %d %Y - %H:%M:%S")
def add1(x, y):
  time.sleep(1)
  return x + y
 
@logged("%b %d %Y - %H:%M:%S")
def add2(x, y):
  time.sleep(2)
  return x + y
 
print add1(1, 2)
print add2(1, 2)
 
# Output:
- Running 'add1' on Jul 24 2013 - 13:40:47
- Finished 'add1', execution time = 1.001s
3
- Running 'add2' on Jul 24 2013 - 13:40:48
- Finished 'add2', execution time = 2.001s
3

如果你足夠細(xì)心,你可能會注意到我們對于返回函數(shù)的名字__name__有著特別的處理,但對其他的注入__doc__或是__module__則沒有如此。所以如果,在這個(gè)例子中add函數(shù)有一個(gè)doc字符串的話,它就會被丟棄。那么該如何處理呢?我們當(dāng)然可以像處理__name__那樣對待所有的字段,不過如果在每個(gè)裝飾器內(nèi)都這么做的話未免太繁冗了。這就是為何functools模塊提供了一個(gè)名為wraps的裝飾器的原因,那正是為了處理這種情況??赡茉诶斫庋b飾器的過程中會被迷惑,不過當(dāng)你把裝飾器看成是一個(gè)接收函數(shù)名作為輸入?yún)?shù)并且返回一個(gè)函數(shù),這樣就很好理解了。我們將在下個(gè)例子中使用wraps裝飾器而不是手動去處理__name__或其他屬性。

下個(gè)例子會有點(diǎn)復(fù)雜,我們的任務(wù)是將一個(gè)函數(shù)調(diào)用的返回結(jié)果緩存一段時(shí)間,輸入?yún)?shù)決定緩存時(shí)間。傳遞給函數(shù)的輸入?yún)?shù)必須是可哈希的對象,因?yàn)槲覀兪褂冒{(diào)用輸入?yún)?shù)的tuple作為第一個(gè)參數(shù),第二個(gè)參數(shù)則為一個(gè)frozenset對象,它包含了關(guān)鍵詞項(xiàng)kwargs,并且作為cache key。每個(gè)函數(shù)都會有一個(gè)唯一的cache字典存儲在函數(shù)的閉包內(nèi)。

【譯注】set和frozenset為Python的兩種內(nèi)建集合,其中前者為可變對象(mutable),其元素可以使用add()或remove()進(jìn)行變更,而后者為不可變對象(imutable)并且是可哈希的(hashable),在建立之后元素不可變,他可以作為字典的key或是另一個(gè)集合的元素。

import time
from functools import wraps
 
def cached(timeout, logged=False):
  """Decorator to cache the result of a function call.
  Cache expires after timeout seconds.
  """
  def decorator(func):
    if logged:
      print "-- Initializing cache for", func.__name__
    cache = {}
 
    @wraps(func)
    def decorated_function(*args, **kwargs):
      if logged:
        print "-- Called function", func.__name__
      key = (args, frozenset(kwargs.items()))
      result = None
      if key in cache:
        if logged:
          print "-- Cache hit for", func.__name__, key
 
        (cache_hit, expiry) = cache[key]
        if time.time() - expiry < timeout:
          result = cache_hit
        elif logged:
          print "-- Cache expired for", func.__name__, key
      elif logged:
        print "-- Cache miss for", func.__name__, key
 
      # No cache hit, or expired
      if result is None:
        result = func(*args, **kwargs)
 
      cache[key] = (result, time.time())
      return result
 
    return decorated_function
 
  return decorator

來看看它的用法。我們使用裝飾器裝飾一個(gè)很基本的斐波拉契數(shù)生成器。這個(gè)cache裝飾器將對代碼使用備忘錄模式(Memoize Pattern)。請注意fib函數(shù)的閉包是如何存放cache字典、一個(gè)指向原fib函數(shù)的引用、logged參數(shù)的值以及timeout參數(shù)的最后值的。dump_closure將在文末定義。

>>> @cached(10, True)
... def fib(n):
...   """Returns the n'th Fibonacci number."""
...   if n == 0 or n == 1:
...     return 1
...   return fib(n - 1) + fib(n - 2)
...
-- Initializing cache for fib
>>> dump_closure(fib)
1. Dumping function closure for fib:
-- cell 0 = {}
-- cell 1 =
-- cell 2 = True
-- cell 3 = 10
>>>
>>> print "Testing - F(4) = %d" % fib(4)
-- Called function fib
-- Cache miss for fib ((4,), frozenset([]))
-- Called function fib
-- Cache miss for fib ((3,), frozenset([]))
-- Called function fib
-- Cache miss for fib ((2,), frozenset([]))
-- Called function fib
-- Cache miss for fib ((1,), frozenset([]))
-- Called function fib
-- Cache miss for fib ((0,), frozenset([]))
-- Called function fib
-- Cache hit for fib ((1,), frozenset([]))
-- Called function fib
-- Cache hit for fib ((2,), frozenset([]))
Testing - F(4) = 5
Class Decorators

在之前的小節(jié)中,我們看了一些函數(shù)裝飾器和一些使用的小技巧,接下來我們來看看類裝飾器。類裝飾器將一個(gè)class作為輸入?yún)?shù)(Python中的一種類類型對象),并且返回一個(gè)修改過的class。

第一個(gè)例子是一個(gè)簡單的數(shù)學(xué)問題。當(dāng)給定一個(gè)有序集合P,我們定義Pd為P的反序集合P(x,y) <-> Pd(x,y),也就是說兩個(gè)有序集合的元素順序互為相反的,這在Python中該如何實(shí)現(xiàn)?假定一個(gè)類定義了__lt__以及__le__或其他方法來實(shí)現(xiàn)有序。那么我們可以通過寫一個(gè)類裝飾器來替換這些方法。
 

def make_dual(relation):
  @wraps(relation, ['__name__', '__doc__'])
  def dual(x, y):
    return relation(y, x)
  return dual
 
def dual_ordering(cls):
  """Class decorator that reverses all the orderings"""
  for func in ['__lt__', '__gt__', '__ge__', '__le__']:
    if hasattr(cls, func):
      setattr(cls, func, make_dual(getattr(cls, func)))
  return cls

下面是將這個(gè)裝飾器用以str類型的例子,創(chuàng)建一個(gè)名為rstr的新類,使用反字典序(opposite lexicographic)為其順序。

@dual_ordering
class rstr(str):
  pass
 
x = rstr("1")
y = rstr("2")
 
print x < y
print x <= y
print x > y
print x >= y
 
# Output:
False
False
True
True

來看一個(gè)更復(fù)雜的例子。假定我們希望前面所說的logged裝飾器能夠被用于某個(gè)類的所有方法。一個(gè)方案是在每個(gè)類方法上都加上裝飾器。另一個(gè)方案是寫一個(gè)類裝飾器自動完成這些工作。在動手之前,我將把前例中的logged裝飾器拿出來做一些小改進(jìn)。首先,它使用functools提供的wraps裝飾器完成固定__name__的工作。第二,一個(gè)_logged_decorator屬性被引入(設(shè)置為True的布爾型變量),用來指示這個(gè)方法是否已經(jīng)被裝飾器裝飾過,因?yàn)檫@個(gè)類可能會被繼承而子類也許會繼續(xù)使用裝飾器。最后,name_prefix參數(shù)被加入用來設(shè)置打印的日志信息。
 

def logged(time_format, name_prefix=""):
  def decorator(func):
    if hasattr(func, '_logged_decorator') and func._logged_decorator:
      return func
 
    @wraps(func)
    def decorated_func(*args, **kwargs):
      start_time = time.time()
      print "- Running '%s' on %s " % (
                      name_prefix + func.__name__,
                      time.strftime(time_format)
                 )
      result = func(*args, **kwargs)
      end_time = time.time()
      print "- Finished '%s', execution time = %0.3fs " % (
                      name_prefix + func.__name__,
                      end_time - start_time
                 )
 
      return result
    decorated_func._logged_decorator = True
    return decorated_func
  return decorator

好的,讓我們開始寫類裝飾器:
 

def log_method_calls(time_format):
  def decorator(cls):
    for o in dir(cls):
      if o.startswith('__'):
        continue
      a = getattr(cls, o)
      if hasattr(a, '__call__'):
        decorated_a = logged(time_format, cls.__name__ + ".")(a)
        setattr(cls, o, decorated_a)
    return cls
  return decorator

下面是使用方法,注意被繼承的或被重寫的方法是如何處理的。

@log_method_calls("%b %d %Y - %H:%M:%S")
class A(object):
  def test1(self):
    print "test1"
 
@log_method_calls("%b %d %Y - %H:%M:%S")
class B(A):
  def test1(self):
    super(B, self).test1()
    print "child test1"
 
  def test2(self):
    print "test2"
 
b = B()
b.test1()
b.test2()
 
# Output:
- Running 'B.test1' on Jul 24 2013 - 14:15:03
- Running 'A.test1' on Jul 24 2013 - 14:15:03
test1
- Finished 'A.test1', execution time = 0.000s
child test1
- Finished 'B.test1', execution time = 1.001s
- Running 'B.test2' on Jul 24 2013 - 14:15:04
test2
- Finished 'B.test2', execution time = 2.001s

我們第一個(gè)類裝飾器的例子是類的反序方法。一個(gè)相似的裝飾器,可以說是相當(dāng)有用的,實(shí)現(xiàn)__lt__、__le__、__gt__、__ge__和__eq__中的一個(gè),能夠?qū)崿F(xiàn)類的全排序么?這也就是functools.total_ordering裝飾器所做的工作。詳情請見參考文檔。
Flask中的一些例子

讓我們來看看Flask中用到的一些有趣的裝飾器。

假定你希望讓某些函數(shù)在特定的調(diào)用時(shí)刻輸出警告信息,例如僅僅在debug模式下。而你又不希望每個(gè)函數(shù)都加入控制的代碼,那么你就能夠使用裝飾器來實(shí)現(xiàn)。以下就是Flask的app.py中定義的裝飾器的工作。
 

def setupmethod(f):
  """Wraps a method so that it performs a check in debug mode if the
  first request was already handled.
  """
  def wrapper_func(self, *args, **kwargs):
    if self.debug and self._got_first_request:
      raise AssertionError('A setup function was called after the '
        'first request was handled. This usually indicates a bug '
        'in the application where a module was not imported '
        'and decorators or other functionality was called too late.\n'
        'To fix this make sure to import all your view modules, '
        'database models and everything related at a central place '
        'before the application starts serving requests.')
    return f(self, *args, **kwargs)
  return update_wrapper(wrapper_func, f)

來看一個(gè)更有趣的例子,這個(gè)例子是Flask的route裝飾器,在Flask類中定義。注意到裝飾器可以是類中的一個(gè)方法,將self作為第一個(gè)參數(shù)。完整的代碼在app.py中。請注意裝飾器簡單的將被裝飾過的函數(shù)注冊成為一個(gè)URL句柄,這是通過調(diào)用add_url_rule函數(shù)來實(shí)現(xiàn)的。

def route(self, rule, **options):
 """A decorator that is used to register a view function for a
 given URL rule. This does the same thing as :meth:`add_url_rule`
 but is intended for decorator usage::
 
   @app.route('/')
   def index():
     return 'Hello World'
 
 For more information refer to :ref:`url-route-registrations`.
 
 :param rule: the URL rule as string
 :param endpoint: the endpoint for the registered URL rule. Flask
         itself assumes the name of the view function as
         endpoint
 :param options: the options to be forwarded to the underlying
         :class:`~werkzeug.routing.Rule` object. A change
         to Werkzeug is handling of method options. methods
         is a list of methods this rule should be limited
         to (`GET`, `POST` etc.). By default a rule
         just listens for `GET` (and implicitly `HEAD`).
         Starting with Flask 0.6, `OPTIONS` is implicitly
         added and handled by the standard request handling.
 """
 def decorator(f):
   endpoint = options.pop('endpoint', None)
   self.add_url_rule(rule, endpoint, f, **options)
   return f
 return decorator

擴(kuò)展閱讀

1. official Python Wiki

2. metaprogramming in Python 3
附錄:閉包

一個(gè)函數(shù)閉包是一個(gè)函數(shù)和一個(gè)引用集合的組合,這個(gè)引用集合指向這個(gè)函數(shù)被定義的作用域的變量。后者通常指向一個(gè)引用環(huán)境(referencing environment),這使得函數(shù)能夠在它被定義的區(qū)域之外執(zhí)行。在Python中,這個(gè)引用環(huán)境被存儲在一個(gè)cell的tuple中。你能夠通過func_closure或Python 3中的__closure__屬性訪問它。要銘記的一點(diǎn)是引用及是引用,而不是對象的深度拷貝。當(dāng)然了,對于不可變對象而言,這并不是問題,然而對可變對象(list)這點(diǎn)就必須注意,隨后會有一個(gè)例子說明。請注意函數(shù)在定義的地方也有__globals__字段來存儲全局引用環(huán)境。

來看一個(gè)簡單的例子:
 

>>> def return_func_that_prints_s(s):
...   def f():
...       print s
...   return f
...
>>> g = return_func_that_prints_s("Hello")
>>> h = return_func_that_prints_s("World")
>>> g()
Hello
>>> h()
World
>>> g is h
False
>>> h.__closure__
(,)
>>> print [str(c.cell_contents) for c in g.__closure__]
['Hello']
>>> print [str(c.cell_contents) for c in h.__closure__]
['World']

一個(gè)稍復(fù)雜的例子。確保明白為什么會這么執(zhí)行。
 

>>> def return_func_that_prints_list(z):
...   def f():
...       print z
...   return f
...
>>> z = [1, 2]
>>> g = return_func_that_prints_list(z)
>>> g()
[1, 2]
>>> z.append(3)
>>> g()
[1, 2, 3]
>>> z = [1]
>>> g()
[1, 2, 3]

【譯者】:z.append(3)時(shí),g()內(nèi)部的引用和z仍然指向一個(gè)變量,而z=[1]之后,兩者就不再指向一個(gè)變量了。

最后,來看看代碼中使用到的dump_closure方法的定義。
 

def dump_closure(f):
  if hasattr(f, "__closure__") and f.__closure__ is not None:
    print "- Dumping function closure for %s:" % f.__name__
    for i, c in enumerate(f.__closure__):
      print "-- cell %d = %s" % (i, c.cell_contents)
  else:
    print " - %s has no closure!" % f.__name__

相關(guān)文章

  • python ssh 執(zhí)行shell命令的示例

    python ssh 執(zhí)行shell命令的示例

    這篇文章主要介紹了python ssh 執(zhí)行shell命令的示例,幫助大家更好的理解和使用python,感興趣的朋友可以了解下
    2020-09-09
  • Python實(shí)現(xiàn)多并發(fā)訪問網(wǎng)站功能示例

    Python實(shí)現(xiàn)多并發(fā)訪問網(wǎng)站功能示例

    這篇文章主要介紹了Python實(shí)現(xiàn)多并發(fā)訪問網(wǎng)站功能,結(jié)合具體實(shí)例形式分析了Python線程結(jié)合URL模塊并發(fā)訪問網(wǎng)站的相關(guān)操作技巧,需要的朋友可以參考下
    2017-06-06
  • Django中如何使用sass的方法步驟

    Django中如何使用sass的方法步驟

    這篇文章主要介紹了Django中如何使用sass的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • python argparse 模塊命令行參數(shù)用法及說明

    python argparse 模塊命令行參數(shù)用法及說明

    這篇文章主要介紹了python argparse 模塊命令行參數(shù)用法及說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • Python實(shí)現(xiàn)的多進(jìn)程和多線程功能示例

    Python實(shí)現(xiàn)的多進(jìn)程和多線程功能示例

    這篇文章主要介紹了Python實(shí)現(xiàn)的多進(jìn)程和多線程功能,結(jié)合實(shí)例形式分析了Python多線程與多進(jìn)程實(shí)現(xiàn)分布式系統(tǒng)功能相關(guān)操作技巧,需要的朋友可以參考下
    2018-05-05
  • Pycharm中使用git進(jìn)行合作開發(fā)的教程詳解

    Pycharm中使用git進(jìn)行合作開發(fā)的教程詳解

    這篇文章主要介紹了Pycharm中使用git進(jìn)行合作開發(fā),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • Python搭建監(jiān)控平臺的實(shí)現(xiàn)示例

    Python搭建監(jiān)控平臺的實(shí)現(xiàn)示例

    本文主要介紹了Python搭建監(jiān)控平臺的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • Python操作多維數(shù)組輸出和矩陣運(yùn)算示例

    Python操作多維數(shù)組輸出和矩陣運(yùn)算示例

    這篇文章主要介紹了Python操作多維數(shù)組輸出和矩陣運(yùn)算,結(jié)合實(shí)例形式分析了Python多維數(shù)組的生成、打印輸出及矩陣運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下
    2019-11-11
  • 簡單了解Python write writelines區(qū)別

    簡單了解Python write writelines區(qū)別

    這篇文章主要介紹了簡單了解Python write writelines區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-02-02
  • pyqt5 刪除layout中的所有widget方法

    pyqt5 刪除layout中的所有widget方法

    今天小編就為大家分享一篇pyqt5 刪除layout中的所有widget方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-06-06

最新評論