Python 函數式編程、裝飾器以及一些相關概念簡介

Python 中的 Decorator(裝飾器) 是對一個函數或者方法的封裝,從而使其能夠完成一些與自身功能無關的工做。html

預備知識

一切皆對象

在 Python 中,全部的一切都被視爲對象,任何的變量、函數、類等都是 object 的子類。所以除了變量以外,函數和類等也能夠被指向和傳遞。python

>>> def foo():
...     pass
...
>>> def Foo():
...     pass
...
>>> v = foo
>>> v
<function foo at 0x7f457ecb2b18>
>>> v = Foo
>>> v
<function Foo at 0x7f457ef96848>

命名空間

Python 經過提供 namespace 來實現重名函數/方法、變量等信息的識別。其大體能夠分爲三種 namespace,分別爲:編程

  • local namespace: 局部空間,做用範圍爲當前函數或者類方法
  • global namespace: 全局空間,做用範圍爲當前模塊
  • build-in namespace: 內建空間,在 Python 解釋器啓動時就已經具備的命名空間,做用範圍爲全部模塊。例如:abs,all,chr,cmp,int,str 等內建函數,它們在解釋器啓動時就被自動載入。

當函數/方法、變量等信息發生重名時,Python 會按照 local namespace -> global namespace -> build-in namespace 的順序搜索用戶所需元素,而且以第一個找到此元素的 namespace 爲準。數組

>>> # 重寫內建名字空間中的 str 函數
>>> def str(obj):
...     print "This is str"
...
>>> str(1)
This is str

閉包

閉包(Closure)是詞法閉包(Lexical Closure)的簡稱。簡單地說,閉包就是根據不一樣的配置信息獲得不一樣的結果。對閉包的具體定義有不少種說法,大體能夠分爲兩類:緩存

  • 一種說法認爲閉包是符合必定條件的函數,好比一些參考資源中這樣定義閉包:閉包是在其詞法上下文中引用了自由變量的函數。
  • 另外一種說法認爲閉包是由函數和與其相關的引用環境組合而成的實體。好比一些參考資源中這樣來定義:在實現深約束時,須要建立一個能顯式表示引用環境的東西,並將它與相關的子程序捆綁在一塊兒,這樣捆綁起來的總體被稱爲閉包。

以上兩種定義從某種意義上來講是對立的,一個認爲閉包是函數,另外一個認爲閉包是函數和引用環境組成的總體。閉包確實能夠認爲就是函數,但第二種說法更確切些。閉包只是在形式和表現上像函數,但實際上不是函數。函數是一些可執行的代碼,這些代碼在函數被定義後就肯定了,不會在執行時發生變化,因此一個函數只有一個實例。閉包

閉包在運行時能夠有多個實例,不一樣的引用環境和相同的函數組合能夠產生不一樣的實例。所謂引用環境是指在程序執行中的某個點全部處於活躍狀態的約束所組成的集合。其中的約束是指一個變量的名字和其所表明的對象之間的聯繫。那麼爲何要把引用環境與函數組合起來呢?這主要是由於在支持嵌套做用域的語言中,有時不能簡單直接地肯定函數的引用環境。app

在 Python 語言中,能夠這樣簡單的理解閉包:一個閉包就是調用了一個函數 A,這個函數 A 返回了一個函數 B。這個返回的函數 B 就叫作閉包。在調用函數 A 的時候傳遞的參數就是對不一樣引用環境所作的配置。以下示例所示:編程語言

>>> def make_adder(addend):
...     def adder(augend):
...         return augend + addend
...     return adder
...
>>> add1 = make_adder(11)
>>> add2 = make_adder(22)
>>> add1(100)
111
>>> add2(100)
122

函數式編程

函數式編程指使用一系列的函數解決問題。函數僅接受輸入併產生輸出,不包含任何能影響產生輸出的內部狀態。任何狀況下,使用相同的參數調用函數始終能產生一樣的結果。函數式編程

函數式編程就是一種抽象程度很高的編程範式,純粹的函數式編程語言編寫的函數沒有變量,所以,任意一個函數,只要輸入是肯定的,輸出就是肯定的,這種純函數咱們稱之爲沒有反作用。而容許使用變量的程序設計語言,因爲函數內部的變量狀態不肯定,一樣的輸入,可能獲得不一樣的輸出,所以,這種函數是有反作用的。函數式編程的一個特色就是,容許把函數自己做爲參數傳入另外一個函數,還容許返回一個函數!函數

能夠認爲函數式編程恰好站在了面向對象編程的對立面。對象一般包含內部狀態(字段),和許多能修改這些狀態的函數,程序則由不斷修改狀態構成;函數式編程則極力避免狀態改動,並經過在函數間傳遞數據流進行工做。但這並非說沒法同時使用函數式編程和麪向對象編程,事實上,複雜的系統通常會採用面向對象技術建模,但混合使用函數式風格也能體現函數式風格的優勢。

高階函數

高階函數即能接受函數做爲參數的函數。由於在 Python 中一切皆對象,變量能夠指向函數,函數名其實也是指向函數的變量。也就是說,咱們能夠將函數賦給其餘變量,也就能夠將函數做爲參數傳遞給其餘函數。

>>> def add(x, y, f):
...     return f(x) + f(y)
...
>>> add(6, -9, abs)
15

裝飾器(decorator)

Python 裝飾器的做用就是爲已經存在的對象添加額外的功能。例如裝飾器能夠用來 引入日誌添加計時邏輯來檢測性能給函數加入事務處理 等等。其實整體提及來,裝飾器也就是一個函數,一個用來包裝函數的函數。裝飾器在函數申明完成的時候被調用,調用以後申明的函數被換成一個被裝飾器裝飾事後的函數。簡單說,本質上,裝飾器就是一個返回函數的高階函數,也是一個閉包。

裝飾器的語法以 @ 開頭,接着是裝飾器要裝飾的函數的申明。

無參裝飾器

先來看一下裝飾器自己沒有參數的狀況。例如,咱們想要知道一個函數被調用時所花的時間,能夠採用以下的方式實現:

# Author: Huoty
# Time: 2015-08-12 10:37:10

import time

def foo():
    print 'in foo()'

# 定義一個計時器,傳入一個函數,並返回另外一個附加了計時功能的方法
def timeit(func):

    # 定義一個內嵌的包裝函數,給傳入的函數加上計時功能的包裝
    def wrapper():
        start = time.clock()
        func()
        end = time.clock()
        print 'used:', end - start

    # 將包裝後的函數返回
    return wrapper

# Script starts from here

foo = timeit(foo)
foo()

Python 實現裝飾器的目的就是爲了讓程序更加簡潔,上邊的代碼能夠繼續用裝飾器來簡化:

# Author:  Huoty
# Time: 2015-08-12 10:37:10

import time

def timeit(func):
    def wrapper():
        start = time.clock()
        func()
        end =time.clock()
        print 'used:', end - start
    return wrapper

@timeit
def foo():
    print 'in foo()'

# Script starts from here

foo()

由上例能夠看出,裝飾器 @timeit 的做用等價與 foo = timeit(foo)。被裝飾器裝飾後的執行結果取決於裝飾函數的是想,若是裝飾函數返回被裝飾函數自己,就等於沒有裝飾,若是裝飾函數對被裝飾函數進行了包裝,並返回包裝後的函數,那調用函數時執行的就是包裝過的函數。

若是被裝飾的函數帶有參數,則在裝飾器中也應該爲包裝函數提供對應的參數。若是被裝飾的函數參數不肯定,則能夠用以下方式實現:

# Author:  Huoty
# Time: 2015-08-12 10:59:11

import time

def log(func):
    def wrapper(*args, **kw):
        print "call %s." % func.__name__
        return func(*args, **kw)
    return wrapper

@log
def now():
    print time.asctime()

# Script starts from here

now()

帶參數裝飾器

裝飾器自己也能夠帶參數,可是一般對參數會有必定的要求。因爲有參數的裝飾器函數在調用時只會使用應用時的參數,而不接收被裝飾的函數作爲參數,因此必須在其內部再建立一個函數。以下示例所示:

# Author:  Huoty
# Time: 2015-08-12 11:13:30

def deco(arg):
    def _deco(func):
        def __deco():
            print("before %s called [%s]." % (func.__name__, arg))
            func()
            print("  after %s called [%s]." % (func.__name__, arg))
        return __deco
    return _deco

@deco("module")
def foo():
    print(" foo() called.")

@deco("module2")
def hoo():
    print(" hoo() called.")

# Script starts from here

foo()
hoo()

上例中的第一個函數 deco 是裝飾器函數,它的參數是用來增強 增強裝飾 的。因爲此函數並不是被裝飾的函數對象,因此在內部必須至少建立一個接受被裝飾函數的函數,而後返回這個對象(實際上此時等效於 foo=decomaker(arg)(foo))。

若是裝飾器和被裝飾函數都帶參數,則用以下實現是形式:

def deco(pm):
    def _deco(func):
        def __deco(*args, **kw):
            ret =func(*args, **kw)
            print "func result: ", ret
            return ret ** pm
        return __deco
    return _deco

@deco(2)
def foo(x, y, z=1):
    return x + y + z

print "deco_func result: %s" % foo(10, 20)

輸出:

func result:  31
deco_func result: 961

類裝飾器

裝飾器不經能夠用來裝飾函數,還能夠用來裝飾類。例如給類添加一個類方法:

>>> def bar(obj):
...     print type(obj)
...
>>> def inject(cls):
...     cls.bar = bar
...     return cls
...
>>> @inject
... class Foo(object):
...     pass
...
>>> foo = Foo()
>>> foo.bar()
<class '__console__.Foo'>

內置裝飾器

內置的裝飾器有三個,分別是 staticmethod、classmethod 和 property,做用分別是把類中定義的實例方法變成靜態方法、類方法和類屬性。因爲模塊裏能夠定義函數,因此靜態方法和類方法的用處並非太多,除非你想要徹底的面向對象編程。這三個裝飾器的實現都涉及到 描述符 的概念。

Functools 模塊

Python的functools模塊主要功能是對函數進行包裝,增長原有函數的功能,起主要內容包括:cmp_to_key, partial, reduce, total_ordering, update_wrapper, wraps

函數也是一個對象,它有__name__等屬性。以上咱們有一個 callin.py 的例子,咱們用裝飾器裝飾以後的 now 函數,當咱們用 now.__name__ 查看時,發現它的 __name__ 已經從原來的'now'變成了'wrapper'。由於返回的那個wrapper()函數名字就是'wrapper',因此,須要把原始函數的name等屬性複製到wrapper()函數中,不然,有些依賴函數簽名的代碼執行就會出錯。固然,咱們能夠用wrapper.__name__ = func.__name__來實現,可是咱們沒必要這麼麻煩,用 Python 內置的 functools.wraps 即可實現這樣的功能:

import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

用類實現裝飾器

除了能夠用函數來實現裝飾器外,類也能夠實現。Python 類有一個 __call__ 方法,它可以讓對象可調用。所以能夠用該特性來實現裝飾器。例如實現一個磁盤緩存:

import os
import uuid
import glob
import pickle


class DiskCache(object):

    _NAMESPACE = uuid.UUID("c875fb30-a8a8-402d-a796-225a6b065cad")

    def __init__(self, func):
        self._func = func
        self.__name__ = func.__name__
        self.__module__ = func.__module__
        self.__doc__ = func.__doc__

        self.cache_path = "/tmp/.diskcache"

    def __call__(self, *args, **kw):
        params_uuid = uuid.uuid5(self._NAMESPACE, "-".join(map(str, (args, kw))))
        key = '{}-{}.cache'.format(self._func.__name__, str(params_uuid))
        cache_file = os.path.join(self.cache_path, key)

        if not os.path.exists(self.cache_path):
            os.makedirs(self.cache_path)

        try:
            with open(cache_file, 'rb') as f:
                val = pickle.load(f)
        except:
            val = self._func(*args, **kw)
            try:
                with open(cache_file, 'wb') as f:
                    pickle.dump(val, f)
            except:
                pass
        return val

    def clear_cache(self):
        for cache_file in glob.iglob("{}/{}-*".format(self.cache_path, self.__name__)):
            os.remove(cache_file)

@DiskCache
def add(x, y):
    print "add: %s + %s" % (x, y)
    return x, y

輸出:

add.clear_cache()
print add(1, 2)
print add(2, 3)
print add(1, 2)

print "cached files:", os.listdir(add.cache_path)
add.clear_cache()
print "cached files:", os.listdir(add.cache_path)

本質上,內置的 property 裝飾器也是一個類,只不過它是一個描述符。能夠用相似的形式實現一個可緩存的 property 裝飾器:

class CachedProperty(object):
    def __init__(self, func, name=None, doc=None):
        self.__name__ = name or func.__name__
        self.__module__ = func.__module__
        self.__doc__ = doc or func.__doc__
        self.func = func

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        value = obj.__dict__.get(self.__name__)
        if value is None:
            value = self.func(obj)
            obj.__dict__[self.__name__] = value
        return value


class Foo(object):

    @CachedProperty
    def foo(self):
        print 'first calculate'
        result = 'this is result'
        return result


f = Foo()

print f.foo
print f.foo

輸出:

first calculate
this is result
this is result

多重裝飾

一個函數能夠同時被多個裝飾器裝飾。裝飾的初始化在函數定義時完成,初始化順序爲離函數定義最近的裝飾器首先被初始化,最遠的則最後被初始化,初始化只進行一次。而裝飾器的執行順序則跟初始化順序相反。

def decorator_a(func):
    print "decorator_a"
    def wrapper(*args, **kw):
        print "call %s in decorator_a" % func.__name__
        return func()
    return wrapper

def decorator_b(func):
    print "decorator_b"
    def wrapper(*args, **kw):
        print "call %s in decorator_b" % func.__name__
        return func()
    return wrapper

@decorator_a
@decorator_b
def foo():
    print "foo"

print "-"*10
foo()
foo()

輸出:

decorator_b
decorator_a
----------
call wrapper in decorator_a
call foo in decorator_b
foo
call wrapper in decorator_a
call foo in decorator_b
foo

有以上示例能夠看出,離函數定義最近的 decorator_b 裝飾器首先被初始化,在執行時則是裏函數定義最遠的 decorator_a 首先被執行。

參考資料

相關文章
相關標籤/搜索