天天看點

Python 進階程式設計之面向切面程式設計 AOP(二)

作者:大資料老司機

一、概述

前面講了python面向對象程式設計(OOP:Object Oriented Programming),接下來講一下OOP剩餘的一些知識點和面向切面程式設計 AOP,非常重要的程式設計思想。

Python 進階程式設計之面向切面程式設計 AOP(二)

關python環境、基礎介紹、面向對象程式設計介紹可以參考我以下幾篇文章:

  • Python 介紹和環境準備
  • Python 基礎文法介紹(一)
  • Python 基礎文法介紹(二)
  • Python 進階程式設計之面向對象(一)

二、函數裝飾器

1)無參函數裝飾器

  • python中的裝飾器(decorator)一般采用文法糖的形式,是一種文法格式。比如:@classmethod,@staticmethod,@property,@xxx.setter,@wraps(),@func_name等都是python中的裝飾器。
  • 裝飾器,裝飾的對象是函數或者方法。各種裝飾器的作用都是一樣的:改變被裝飾函數或者方法的功能,性質。

假設用 funA() 函數裝飾器去裝飾 funB() 函數,如下所示:

#funA 作為裝飾器函數
def funA(fn):
    #...
    fn() # 執行傳入的fn參數
    #...
    return '...'
@funA
def funB():
    #...
           

實際上,上面程式完全等價于下面的程式:

def funA(fn):
    #...
    fn() # 執行傳入的fn參數
    #...
    return '...'
def funB():
    #...
funB = funA(funB)
           

通過比對以上 2 段程式不難發現,使用函數裝飾器 A() 去裝飾另一個函數 B(),其底層執行了如下 2 步操作:

  • 将 B 作為參數傳給 A() 函數;
  • 将 A() 函數執行完成的傳回值回報回 B。

示例如下:

#funA 作為裝飾器函數
def funA(fn):
    print("python 學習")
    fn() # 執行傳入的fn參數
    print("http://python.study.net")
    return "裝飾器函數的傳回值"
@funA
def funB():
    print("學習 Python")

if __name__ == "__main__":
    print(funB)
           

輸出結果:

python 學習
學習 Python
http://python.study.net
裝飾器函數的傳回值
           

2)帶數函數裝飾器

在分析 funA() 函數裝飾器和 funB() 函數的關系時,細心的讀者可能會發現一個問題,即當 funB() 函數無參數時,可以直接将 funB 作為 funA() 的參數傳入。但是,如果被修飾的函數本身帶有參數,那應該如何傳值呢?

比較簡單的解決方法就是在函數裝飾器中嵌套一個函數,該函數帶有的參數個數和被裝飾器修飾的函數相同。例如:

def funA(fn):
    # 定義一個嵌套函數
    def say(arc):
        print("Python 學習:", arc)
        funB(arc)
        
    return say
@funA
def funB(arc):
    print("funB():", arc)

if __name__ == "__main__":
	funB("http://python.study.net")           

輸出結果:

Python 學習: http://python.study.net           

這裡有必要給讀者分析一下這個程式,其實,它和如下程式是等價的:

def funA(fn):
    # 定義一個嵌套函數
    def say(arc):
        print("Python 學習:",arc)
    return say
def funB(arc):
    print("funB():", arc)

if __name__ == "__main__": 
	funB = funA(funB)
	funB("http://python.study.net")           

3)嵌套函數裝飾器

上面示例中,都是使用一個裝飾器的情況,但實際上,Python 也支援多個裝飾器,比如:

@funA
@funB
@funC
def fun():
    #...           

上面程式的執行順序是裡到外,是以它等效于下面這行代碼:

fun = funA( funB ( funC (fun) ) )           

三、類方法修飾

1)執行個體方法

通常情況下,在類中定義的方法預設都是執行個體方法。

class Test():
    #類構造方法,也屬于執行個體方法
    def __init__(self):
        self.name = "Test"
        self.address = "http://www.study.net"
    # 下面定義了一個say執行個體方法
    def say(self):
        print("正在調用 say() 執行個體方法")

if __name__ == "__main__":
    t = Test()
    # 通過執行個體對象方法執行個體方法
    t.say()
    # 通過類名通路執行個體對象,但是必須傳執行個體對象名
    Test.say(c)           

2)類方法(@classmethod修飾)

Python 類方法和執行個體方法相似,它最少也要包含一個參數,隻不過類方法中通常将其命名為 cls,Python 會自動将類本身綁定給 cls 參數(注意,綁定的不是類對象)。也就是說,我們在調用類方法時,無需顯式為 cls 參數傳參。

【溫馨提示】和 self 一樣,cls 參數的命名也不是規定的(可以随意命名),隻是 Python 程式員約定俗稱的習慣而已。

和執行個體方法最大的不同在于,類方法需要使用@classmethod修飾符進行修飾,例如:

class Test:
    #類構造方法,也屬于執行個體方法
    def __init__(self):
        self.name = "Test"
        self.add = "http://www.Test.net"
    #下面定義了一個類方法
    @classmethod
    def info(cls):
        print("正在調用類方法",cls)

if __name__ == "__main__":
    t = Test()
    # 通過對象調用類方法
    t.info()
    # 通過類名調用類方法,類方法推薦直接通過類名調用
    Test.info()           

注意,如果沒有 @classmethod,則 Python 解釋器會将 info() 方法認定為執行個體方法,而不是類方法。

3)靜态方法(@staticmethod修飾)

靜态方法,其實就是我們學過的函數,和函數唯一的差別是,靜态方法定義在類這個空間(類命名空間)中,而函數則定義在程式所在的空間(全局命名空間)中。

靜态方法沒有類似 self、cls 這樣的特殊參數,是以 Python 解釋器不會對它包含的參數做任何類或對象的綁定。也正因為如此,類的靜态方法中無法調用任何類屬性和類方法。

靜态方法需要使用@staticmethod修飾,例如:

class Test:
    @staticmethod
    def info(name, add):
        print(name, add)

if __name__ == "__main__":
    c = Test
    # 通過對象調用類方法
    c.info("測試", 123)
    # 通過類名調用類方法(推薦)
    Test.info("hello", "world")           

四、 property() 函數

傳統操作類屬性的方式比較麻煩,更習慣使用“類對象.屬性”這種方式 , Python 中提供了 property() 函數,可以實作在不破壞類封裝原則的前提下,讓開發者依舊使用“類對象.屬性”的方式操作類中的屬性。

property() 函數的基本使用格式如下:

屬性名=property(fget=None, fset=None, fdel=None, doc=None)           
  • fget 參數用于指定擷取該屬性值的類方法,
  • fset 參數用于指定設定該屬性值的方法,
  • fdel 參數用于指定删除該屬性值的方法,
  • doc 是一個文檔字元串,用于說明此函數的作用。
【注意】在使用 property() 函數時,以上 4 個參數可以僅指定第 1 個、或者前 2 個、或者前 3 個,目前也可以全部指定。也就是說,property() 函數中參數的指定并不是完全随意的。

例如,修改上面的程式,為 name 屬性配置 property() 函數:

class CLanguage:
    #構造函數
    def __init__(self,n):
        self.__name = n
    #設定 name 屬性值的函數
    def setname(self,n):
        self.__name = n
    #通路nema屬性值的函數
    def getname(self):
        return self.__name
    #删除name屬性值的函數
    def delname(self):
        self.__name="xxx"
    #為name 屬性配置 property() 函數
    name = property(getname, setname, delname, '指明出處')
#調取說明文檔的 2 種方式
#print(CLanguage.name.__doc__)
help(CLanguage.name)
clang = CLanguage("C語言中文網")
#調用 getname() 方法
print(clang.name)
#調用 setname() 方法
clang.name="Python教程"
print(clang.name)
#調用 delname() 方法
del clang.name
print(clang.name)           

五、hasattr()、getattr()、setattr()函數

1)hasattr()

hasattr() 函數用來判斷某個類執行個體對象是否包含指定名稱的屬性或方法。該函數的文法格式如下:

hasattr(obj, name)           
  • obj 指的是某個類的執行個體對象,
  • name 表示指定的屬性名或方法名。
  • 同時,該函數會将判斷的結果(True 或者 False)作為傳回值回報回來。

示例如下:

class Test001:
    def __init__ (self):
        self.name = "Test001"
        self.add = "http://www.test001.com"
    def say(self):
        print("我正在學Python")

if __name__ == "__main__":
    obj = Test001()
    print(hasattr(obj, "name"))
    print(hasattr(obj, "add"))
    print(hasattr(obj, "say"))
    print(hasattr(obj, "fly"))           

輸出結構:

True
True
True
False           

2)getattr()

getattr() 函數擷取某個類執行個體對象中指定屬性的值。沒錯,和 hasattr() 函數不同,該函數隻會從類對象包含的所有屬性中進行查找。

getattr() 函數的文法格式如下:

getattr(obj, name[, default])           
  • obj 表示指定的類執行個體對象,
  • name 表示指定的屬性名,
  • default 是可選參數,用于設定該函數的預設傳回值,即當函數查找失敗時,如果不指定 default 參數,則程式将直接報 AttributeError 錯誤,反之該函數将傳回 default 指定的值。

示例如下:

class Test002:
    def __init__ (self):
        self.name = "Test002"
        self.add = "http://www.test002.com"
    def say(self):
        print("我正在學Python")

if __name__ == "__main__":
    obj = Test002()
    print(getattr(obj, "name"))
    print(getattr(obj, "add"))
    print(getattr(obj, "say"))
    print(getattr(obj, "display", 'nodisplay'))           

輸出結果:

Test002
http://www.test002.com
<bound method Test002.say of <__main__.Test002 object at 0x00000193F9B98A20>>
nodisplay           

3)setattr()

setattr() 函數的功能相對比較複雜,它最基礎的功能是修改類執行個體對象中的屬性值。其次,它還可以實作為執行個體對象動态添加屬性或者方法。

setattr() 函數的文法格式如下:

setattr(obj, name, value)           

示例如下:

class Test003:
    def __init__ (self):
        self.name = "Test003"
        self.add = "http://www.test003.com"
    def say(self):
        print("我正在學Python")

if __name__ == "__main__":
    obj = Test003()
    print(obj.name)
    print(obj.add)
    setattr(obj, "name", "Python教程")
    setattr(obj, "add", "http://www.test003.net")
    print(obj.name)
    print(obj.add)           

輸出結果:

Test003
http://www.test003.com
Python教程
http://www.test003.net           

六、issubclass()和isinstance()函數

Python 提供了如下兩個函數來檢查類型:

  • issubclass(cls, class_or_tuple):檢查 cls 是否為後一個類或元組包含的多個類中任意類的子類。
  • isinstance(obj, class_or_tuple):檢查 obj 是否為後一個類或元組包含的多個類中任意類的對象。

示例如下:

# 定義一個字元串
hello = "Hello";
# "Hello"是str類的執行個體,輸出True
print('"Hello"是否是str類的執行個體: ', isinstance(hello, str))

# "Hello"是object類的子類的執行個體,輸出True
print('"Hello"是否是object類的執行個體: ', isinstance(hello, object))

# str是object類的子類,輸出True
print('str是否是object類的子類: ', issubclass(str, object))

# "Hello"不是tuple類及其子類的執行個體,輸出False
print('"Hello"是否是tuple類的執行個體: ', isinstance(hello, tuple))

# str不是tuple類的子類,輸出False
print('str是否是tuple類的子類: ', issubclass(str, tuple))

# 定義一個清單
my_list = [2, 4]
# [2, 4]是list類的執行個體,輸出True
print('[2, 4]是否是list類的執行個體: ', isinstance(my_list, list))

# [2, 4]是object類的子類的執行個體,輸出True
print('[2, 4]是否是object類及其子類的執行個體: ', isinstance(my_list, object))

# list是object類的子類,輸出True
print('list是否是object類的子類: ', issubclass(list, object))

# [2, 4]不是tuple類及其子類的執行個體,輸出False
print('[2, 4]是否是tuple類及其子類的執行個體: ', isinstance([2, 4], tuple))

# list不是tuple類的子類,輸出False
print('list是否是tuple類的子類: ', issubclass(list, tuple))           

七、面向切面程式設計( AOP )

AOP(Aspect Orentied Programming) 簡言之、這種在運作時,編譯時,類和方法加載時,動态地将代碼切入到類的指定方法、指定位置上的程式設計思想就是面向切面的程式設計。這樣對原有代碼毫無入侵性。

先來了解一下什麼是裝飾器吧?

其實上面也通過示例稍微講解了python自帶的幾個常用的裝飾器函數(@classmethod、@staticmethod),其實裝飾器是一個很著名的設計模式之一(後面會重點講解23種設計模式),裝飾器經常被用于有切面需求的場景,較為經典的有插入日志、性能測試、事務處理等。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量函數中與函數功能本身無關的雷同代碼并繼續重用。簡而言之,裝飾器的作用就是為已經存在的對象添加額外的功能。

其實上面已經有示例了,那我們再來看幾個示例加深一下了解:

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()'
 
foo()           

在定義上加上@timeit這一行與另外寫foo = timeit(foo)完全等價。

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

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

内置的裝飾器有三個,分别是staticmethod、classmethod和property,作用分别是把類中定義的執行個體方法變成靜态方法、類方法和類屬性。其實上面有詳細的示例講解,這裡就不再仔細講解了。再看個示例加深一下了解。

class Rabbit(object):

    def __init__(self, name):
        # 隻讀屬性"_",私有屬性"__"
        self._name = name

    @staticmethod
    def newRabbit(name):
        return Rabbit(name)

    @classmethod
    def newRabbit2(cls):
        return Rabbit('')

    @property
    def name(self):
        return self._name

    # 這裡定義的屬性是一個隻讀屬性,如果需要再類外可寫,則需要再定義一個setter:
    @name.setter
    def name(self, name):
        self._name = name

if __name__ == "__main__":
    r = Rabbit("test")
    print(r.name)
    r.name = "hello"
    print(r.name)           

Python 進階程式設計之面向切面程式設計(AOP)就先到這裡,後續會持續更相關文檔,有任何疑問歡迎給我留言,也可以關注我的公衆号【大資料與雲原生技術分享】深入技術交流~