面向對象進階

1. 類的其餘內置函數

1.1 isinstance 和 issubclass

1. isinstance(obj, cls)
判斷第一個參數是不是第二個參數的實例對象,返回布爾值。第一個參數爲對象,第二個參數爲類。python

class Animal:
    pass
class Dog(Animal):
    pass

d1 = Dog()
print(isinstance(d1, Dog))      # True
print(isinstance(d1, Animal))   # True

# 也能夠和 type 同樣用來判斷類型
print(isinstance('python', str))    # True

在繼承關係中,一個對象的數據類型是某個子類,那麼它也能夠被看做是父類,反之則不行。程序員

2. issubclass(cls, class_or_tuple))
判斷第一個參數是不是第二個參數的子類,返回布爾值。編程

class Animal:
    pass
class Dog(Animal):
    pass
print(issubclass(Dog, Animal))  # True

1.2 __getattribute__

__getattribute__(object, name) 在獲取對象屬性時,不論是否存在都會觸發。數組

class Animal:
    def __init__(self, name):
        self.name = name
    
    def __getattribute__(self, item):
        print('觸發 getattribute')

a1 = Animal('小黑')
a1.color    # 觸發 getattribute
a1.name     # 觸發 getattribute

__getattr__ 同時存在時,只會觸發 __getattribute__,除非拋出 AttributeError異常(其餘異常不行):緩存

class Animal:
    def __init__(self, name):
        self.name = name
    
    def __getattr__(self, item):
        print('觸發 getattr')
    
    def __getattribute__(self, item):
        print('觸發 getattribute')
        raise AttributeError('觸發')   # 使用異常處理 
a1 = Animal('小黑')
a1.color

------------
觸發 getattribute
觸發 getattr

這裏咱們使用異常處理 raise 模擬 Python內部機制,拋出 AttributeError,使得 __getattr__ 也會被觸發。網絡

1.3 __getitem__、__setitem__、__delitem__

__getattr__、__setattr__、__delattr__ 相似,不一樣之處在於它是以字典形式訪問、修改或刪除,而且只要訪問、修改或刪除就會觸發。app

class Animal:
    def __getitem__(self, item):
        print('__getitem__')
        return self.__dict__[item]
        
    def __setitem__(self, key, value):
        print('__setitem__')
        self.__dict__[key] = value
        
    def __delitem__(self, item):
        print('__delitem__')
        self.__dict__.pop(item)
        
a1 = Animal()
print(a1.__dict__)     # {}
# set
a1['name'] = 'rose'     # __setitem__
print(a1.__dict__)     # {'name': 'rose'}

# get
print(a1['name'])   # __getitem__   rose

# del
del a1['name']     # __delitem__
print(a1.__dict__)  # {}

1.4 __str__、__repr__

__str__ 和 __repr__ 都是用來定製對象字符串顯示形式框架

# L = list('abcd')
# print(L)        # ['a', 'b', 'c', 'd']

# f = open('a.txt', 'w')
# print(f)            # <_io.TextIOWrapper name='a.txt' mode='w' encoding='cp936'>

class Animal:
    pass

a1 = Animal()
print(a1)          # <__main__.Animal object at 0x00000000053E5358> 標準的對象字符串格式

Python 中一切皆對象,L、f 和 a1 都是對象,可是它們的顯示形式倒是不同。這是由於 Python 內部對不一樣的對象定製後的結果。咱們能夠重構 __str__ 或 __repr__ 方法,可以更友好地顯示:
重構 __str__函數

class Animal:
    pass

    def __str__(self):
        return '自定製對象的字符串顯示形式'
    
a1 = Animal()
print(a1)

m = str(a1)     # print() 的本質是在調用 str(),而 str() 的本質是在調用 __str__()
print(m)

print(a1.__str__())  # obj.__str__()

---------
# 自定製對象的字符串顯示形式
# 自定製對象的字符串顯示形式
# 自定製對象的字符串顯示形式

能夠看出,咱們重構 __str__ 方法後,返回的是咱們定製的字符串。而 print() 和 str() 的本質都是在調用 object.__str__()工具

重構 __repr__

class Animal:
    pass

    def __repr__(self):
        return 'repr'
a1 = Animal()
print(a1)       # repr      返回 repr
print(a1.__repr__())    # repr

沒有重構 __repr__ 方法時,輸出 a1,結果是對象內存地址。當構建了後,輸出 repr。

__str__ 沒定義時,用 __repr__ 代替輸出。當二者都存在時,__str__ 覆蓋 __repr__

二者區別:

  • 打印操做,首先嚐試 __str__,若類自己沒有定義則使用__repr__ 代替輸出,一般是一個友好的顯示。
  • __repr__ 適用於全部其餘環境(包括解釋器),程序員在開發期間一般使用它。

1.5 __format__

用於類中自定製 format。

s = '{0}{0}{0}'.format('rose')
print(s)      # 'roseroserose'

重構 __format__

# 定義格式化字符串格式字典
date_dic = {
    'ymd': '{0.year}-{0.month}-{0.day}',
    'mdy': '{0.month}-{0.day}-{0.year}'
}

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day
        
    def __format__(self, format_spec):
        # 當用戶輸入的格式爲空或其餘時,設定一個默認值
        if not format_spec or format_spec not in date_dic:
            format_spec = 'ymd'
        fm = date_dic[format_spec]
        return fm.format(self)
        
d1 = Date(2018, 11, 28)
# x = '{0.year}-{0.month}-{0.day}'.format(d1)
# print(x)      # 打印 2018-11-28

print(format(d1, 'ymd'))    # 2018-11-28
print(format(d1, 'mdy'))    # 11-28-2018
print(format(d1))   # 當爲空時,輸出默認格式 ymd

format 實際調用的是 __format__()

1.6 __slots__

__slots__ 實質是一個類變量,它能夠是字符串、列表、元組也能夠是個可迭代對象。
它的做用是用來覆蓋類的屬性字典 __dict__。咱們都知道類和對象都有本身獨立的內存空間,每產生一個實例對象就會建立一個屬性字典。當產生成千上萬個實例對象時,佔用的內存空間是很是可怕的。
當咱們用定義 __slots__ 後,實例便只能經過一個很小的固定大小的數組來構建,而不是每一個實例都建立一個屬性字典,從而到達節省內存的目的。

class Animal:
#     __slots__ = 'name'
    __slots__ = ['name', 'age']
    pass

a1 = Animal()
# # print(a1.__dict__)   # AttributeError: 'Animal' object has no attribute '__dict__'
# a1.name = 'rose'
# print(a1.__slots__)     # name
# print(a1.name)           # rose

a1.name = 'lila'    # 設置類變量的值
a1.age = 1
print(a1.__slots__)        # ['name', 'age']
print(a1.name, a1.age)      # lila 1

Tips:

  • 它會覆蓋 __dict__ 方法,所以依賴 __dict__ 的操做,如新增屬性會報沒有這個屬性,所以要慎用。
  • 定義爲字符串形式時,表示只有一個類變量。列表或元組,其中元素有多少個就表示有多少個類變量。

1.7 __doc__

用於查看類的文檔字符串,不能繼承。若類沒有定義文檔字符串,則返回 None(Python 內部自定義的)。

class Animal:
    """動物類"""
    pass
class Dog(Animal):
    pass

print(Animal.__doc__)
print(Dog.__doc__)
print(Dog.__dict__)

----------
# 動物類
# None
# {'__module__': '__main__', '__doc__': None}

1.8 __module__ 和 __class__

用於查看當前操做對象是在哪一個模塊裏和哪一個類裏。

# a.py
class Cat:
    name = 'tom'
    pass
# b.py
from a import Cat

c1 = Cat()
print(c1.__module__)    # a
print(c1.__class__)     # Cat

1.9 __del__

析構方法,當對象再內存中被釋放時(如對象被刪除),自動觸發。
Python 有自動回收機制,無需定義 __del__。可是若產生的對象還調用了系統資源,即有用戶級和內核級兩種資源,那麼在使用完畢後須要用到 __del__ 來回收系統資源。

class Foo:
    def __init__(self, name):
        self.name = name
    def __del__(self):
        print('觸發 del')
f1 = Foo('rose')        

# del f1.name   # 不會觸發
del f1          # 會觸發
print('-------->')

# -------->
# 觸發 del

# 觸發 del
# -------->
  • 第一種狀況時刪除數據屬性,不會觸發,先打印 ---->,再觸發 (這是由 Python 內部自動回收機制致使的,當程序執行完畢後內存自動被回收)
  • 第二種狀況刪除對象,觸發。全部先觸發後打印。

1.10 __call__

當對象加上括號後,觸發執行它。若沒有類中沒有重構 __call__,則會報 not callable

class Animal:
    def __call__(self):
        print('對象%s被執行' % self)

a1 = Animal()
a1()

-------------------
# 對象<__main__.Animal object at 0x00000000053EC278>被執行

Python 一切皆對象,類也是個對象。而類加括號能調用執行,那麼它的父類(object)中必然也會有個 __call__ 方法。

1.11 __iter__ 和 __next__ 實現迭代器協議

  • 迭代器協議:對象必須有一個 __next__或next() 方法,執行該方法可返回迭代項的下一項,直到超出邊界,引起 StopIteration 終止迭代。
  • 可迭代對象:指的是實現了迭代器協議的對象,其內部必需要有一個 __iter__方法。
  • for 循序其實質是調用對象內部的 __inter__ 方法,它可以自動捕捉 StopIteration 異常,所以超出邊界也不會報錯。

所以可迭代對象內部必須有一個 __iter__ 和 __next__方法。

class Dog:
    def __init__(self, age):
        self.age = age
        
d1 = Dog(1)
for i in d1:
    print(i)  # TypeError: 'Dog' object is not iterable

對象 d1 中沒有 __iter__() 全部它是不可迭代對象

class Dog:
    def __init__(self, age):
        self.age = age
        
    def __iter__(self):
#         return None   # iter() returned non-iterator of type 'NoneType'
        return self     # 須要返回可迭代類型
    
    def __next__(self):
        if self.age >= 6:
            raise StopIteration('終止迭代~')
        self.age += 1
        return self.age
        
d1 = Dog(1)
# print(d1.__next__())      # 2
# print(d1.__next__())      # 3

for i in d1:    # obj = d1.__iter__()
    print(i)  # obj.__next__()

----------
# 2
# 3
# 4
# 5
# 6

對象 d1 實現 __iter__ 和 __next__ 後變成可迭代對象。

1.12 迭代器協議實現斐波拉契數列

class Fib:
    def __init__(self, n):
        self._x = 0
        self._y = 1
        self.n = n

    def __iter__(self):
        return self

    def __next__(self):
        self._x, self._y = self._y, self._x + self._y
        if self._x > self.n:
            raise StopIteration('終止')
        return self._x
    
f1 = Fib(13)
for i in f1:
    print(i)

------------------
# 1
# 1
# 2
# 3
# 5
# 8
# 13

1.13 描述符(descriptor)

描述符就是將某種特殊類型的類的實例指派給另外一個類的屬性,這兩個類必須都是新式類,並且這個特殊類至少實現了 __get__()、__set__()、__delete__() 中的一種。

描述符的相關魔法方法
魔法方法 含義
__get__(self, instance, owner) 調用屬性時觸發,返回屬性值
__set__(self, instance, value) 爲一個屬性賦值時觸發,不返回任何內容
__delete__(self, instance) 採用 del 刪除屬性時觸發,不返回任何內容
  • self :描述符類自身的實例 (Descriptor 的實例)
  • instance:描述符的擁有者所在類的實例 (Test 的實例)
  • owner:描述符的擁有者所在的類自己 (Test 自己)
  • value:設置的屬性值
class Descriptor:
    def __get__(self, instance, owner):
        print(self, instance, owner)
        
    def __set__(self, instance, value):
        print(self, instance, value)
        
    def __delete__(self, instance):
        print(self, instance)
        
class Test:
    x = Descriptor()  # 類 Descriptor 是類 Test 的類屬性 x

描述符是用來代理另外一個類的類屬性,而不能定義到構造函數中去。那麼它是在哪裏以什麼樣的條件才能觸發呢?
事實上它只能在它所描述的類中,當其實例訪問、修改或刪除類屬性時纔會觸發:

t1 = Test()
print(t1.x)     # 訪問

print(t1.__dict__)
t1.x = 10       # 修改
print(t1.__dict__)  # 凡是與被代理的類屬性相關的操做,都是找描述符,所以代理類的實例屬性字典爲空。

print(Test.__dict__)  # 查看 Test 的屬性字典
<__main__.Descriptor object at 0x0000000004F217B8> <__main__.Test object at 0x0000000004F217F0> <class '__main__.Test'>
None
{}
<__main__.Descriptor object at 0x0000000004F217B8> <__main__.Test object at 0x0000000004F217F0> 10
{}
{'__module__': '__main__', 'x': <__main__.Descriptor object at 0x0000000004F217B8>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}

能夠看出實例 t1 在調用、修改屬性 x 時,都會自動觸發 __get__ 和 __set__ 方法 ,刪除也是同樣。查看 Test 的屬性字典,發現其屬性 x 的值是 Descriptor 的實例。

描述符分兩種:

  • 數據描述符: 至少實現了 __get__() 和 __set__()
  • 非數據描述符: 沒有實現 __set__()

Tips:

  • 描述符自己應該是新式類,其代理的類也應是新式類
  • 只能定義在類屬性中,不能定義到構造函數中
  • 遵循優先級(由高到低):類屬性 —— 數據描述符 —— 實例屬性 —— 非數據描述符 —— 找不到的屬性觸發 __getattr__()
  • 凡是與被代理的類屬性相關的操做,都是找描述符,所以代理類的實例屬性字典爲空。

1.14 描述符優先級

類屬性 > 數據描述符

class Descriptor:
    def __get__(self, instance, owner):
        print('get')
        
    def __set__(self, instance, value):
        print('set')
        
    def __delete__(self, instance):
        print('delete')
        
class Test:
    x = Descriptor()
    
t1 = Test()
Test.x      # 調用數據屬性
print(Test.__dict__)     # 'x': <__main__.Descriptor object at 0x0000000004F21EF0>
Test.x = 1    # 設置類屬性
print(Test.__dict__)    #  'x': 1
get
{'__module__': '__main__', 'x': <__main__.Descriptor object at 0x0000000004F21EF0>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}
{'__module__': '__main__', 'x': 1, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}

上面例子中,描述符被定義爲類屬性,所以在調用時,觸發了描述符中的 __get__() 方法。而賦值操做,直接將類屬性的 x 的值由原來而的描述符賦值爲 1,所以沒有被觸發

問題:爲何實例賦值操做會觸發 __set__() 方法?

由於實例的屬性字典中自己沒有屬性 x,它只能在類中查找。賦值操做,操做的是本身的屬性字典,而不會改變類屬性 x。

類屬性 > 數據描述符 > 實例屬性

t1 = Test()
Test.x = 10000
t1.x

結果爲空,什麼都沒觸發。這是由於類屬性 x 被修改,覆蓋了數據描述符,所以實例屬性也不能觸發。

實例屬性 > 非數據描述符

class Descriptor:
    def __get__(self, instance, owner):
        print('get')
        
class Test:
    x = Descriptor()
    
t1 = Test()
t1.x = 1
print(t1.__dict__)      # {'x': 1}

非數據描述符,沒有 __set__() 方法。所以在修改實例屬性時,不會觸發 __set__() 方法,而是修改了本身的屬性字典。

非數據描述符 > __getattr__

class Descriptor:
    def __get__(self, instance, owner):
        print('get')
        
class Test:
    x = Descriptor()
    def __getattr__(self, item):
        print('觸發 getattr')
    
t1 = Test()
t1.x = 1    # 調用實例屬性
print(t1.__dict__)
t1.y        # 調用不存在的實例屬性

-------------------
# {'x': 1}
# 觸發 getattr

從上能夠看出,先進行了調用了非數據描述符。再觸發 __getattr__。可是由於實例屬性大於非數據描述符,所以先看到的是實例的屬性字典。

1.15 描述符應用

Python 是弱語言類型,在變量定義時不須要指定其類型,類型之間也能夠互相輕易轉換,但這也會致使程序在運行時類型錯誤致使整個程序崩潰。Python 內部沒有相應的類型判斷機制,所以咱們有以下需求:

自定製一個類型檢查機制,在用戶輸入的類型與預期不符時,提示其類型錯誤。
現有一個 Dog 類,想要實現用戶輸入的名字只能是字符串,年齡只能是整型,在輸入以前對其進行判斷:

class Descriptor:
    def __init__(self, key, expected_type):
        self.key = key
        self.expected_type = expected_type
        
    def __get__(self, instance, owner):
        print('get')
        return instance.__dict__[self.key]
        
        
    def __set__(self, instance, value):
        print('set')
        print(value)
        if isinstance(value, self.expected_type):
            instance.__dict__[self.key] = value
        else:
            raise TypeError('你輸入的%s不是%s' % (self.key, self.expected_type))
        
        
class Dog:
    name = Descriptor('name', str)
#     age = Descriptor('age', int)
    def __init__(self, name, age, color):
        self.name = name
        self.age = age
        self.color = color
        

d1 = Dog('rose', 2, 'white')    # 觸發 d1.__set__()
print(d1.__dict__)

d1.name = 'tom'
print(d1.__dict__)

---------
# set
# rose
# {'name': 'rose', 'age': 2, 'color': 'white'}
# set
# tom
# {'name': 'tom', 'age': 2, 'color': 'white'}

類 Descriptor 是 Dog 的修飾符(修飾類變量 name、age),當d1 = Dog('rose', 2, 'white'),觸發 d1.__set__() 方法,也就會調用對象 d1 的屬性字典,進行賦值操做。

當用戶輸入的名字不是字符串時:

d2 = Dog(56, 2, 'white')

------
# set
# 56
# ---------------------------------------------------------------------------
# TypeError                                 Traceback (most recent call last)
# <ipython-input-36-a60d3743b7a0> in <module>()
#      33 # print(d1.__dict__)
#      34 
# ---> 35 d2 = Dog(56, 2, 'white')

# <ipython-input-36-a60d3743b7a0> in __init__(self, name, age, color)
#      22 #     age = Descriptor('age', int)
#      23     def __init__(self, name, age, color):
# ---> 24         self.name = name
#      25         self.age = age
#      26         self.color = color

# <ipython-input-36-a60d3743b7a0> in __set__(self, instance, value)
#      15             instance.__dict__[self.key] = value
#      16         else:
# ---> 17             raise TypeError('你輸入的%s不是%s' % (self.key, self.expected_type))
#      18 
#      19 

# TypeError: 你輸入的name不是<class 'str'>

直接報錯,提示用戶輸入的不是字符串,同理 age 也是同樣。這樣就簡單地實現了定義變量以前的類型檢查。

1.16 上下文管理協議

使用 with 語句操做文件對象,這就是上下文管理協議。要想一個對象兼容 with 語句,那麼這個對象的類中必須實現 __enter__ 和 __exit__

class Foo:
    def __init__(self, name):
        self.name = name
        
    def __enter__(self):
        print('next')
        return self
    
    def __exit__(self,exc_type, exc_val, exc_tb):
        print('觸發exit')

with Foo('a.txt') as f:     # 觸發 __enter__()
    print(f.name)       # a.txt,打印完畢,執行 __exit__()
print('------>')

---------
# next
# a.txt
# 觸發exit
# ------>

with Foo('a.txt') as f 觸發 __enter__(),f 爲對象自己。執行完畢後觸發 __exit__()

__exit__() 的三個參數

在沒有異常的時候,這三個參數皆爲 None:

class Foo:
    ...
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('觸發exit')
        print(exc_type)
        print(exc_val)
        print(exc_tb)
with Foo('a.txt') as f:
    print(f.name)

------
# a.txt
# 觸發exit
# None
# None
# None

有異常的狀況下,三個參數分別表示爲:

  • ext_type:錯誤(異常)的類型
  • ext_val:異常變量
  • ext_tb:追溯信息 Traceback

__exit__() 返回值爲 None 或空時:

class Foo:
    ...
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('觸發exit')
        print(exc_type)
        print(exc_val)
        print(exc_tb)
with Foo('a.txt') as f:
    print(f.name)
    print(dkdkjsjf)     # 打印存在的東西,執行 __exit__(),完了退出整個程序
    print('--------->')     # 不會被執行
print('123')    # 不會被執行
aa.txt
觸發exit
<class 'NameError'>
name 'dkdkjsjf' is not defined
<traceback object at 0x0000000004E62F08>
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-39-9fe07cc1dbe0> in <module>()
     13 with Foo('a.txt') as f:
     14     print(f.name)
---> 15     print(dkdkjsjf)
     16     print('--------->')
     17 print('123')

NameError: name 'dkdkjsjf' is not defined

with 語句運行觸發 __enter__() 返回,並得到返回值賦值給 f。遇到異常觸發 __exit__(),並將錯誤信息賦值給它的三個參數,整個程序結束,with 語句中出現異常的語句將不會被執行

__exit__() 返回值爲 True 時:

class Foo:
    ...
    def __exit__(self, exc_type, exc_val, exc_tb):
    print('觸發exit')
    print(exc_type)
    print(exc_val)
    print(exc_tb)
    return True
with Foo('a.txt') as f:
    print(f.name)
    print(dkdkjsjf)
    print('--------->')
print('123')
a.txt
觸發exit
<class 'NameError'>
name 'dkdkjsjf' is not defined
<traceback object at 0x0000000004E5AA08>
123

__exit__() 返回值爲 True 時,with 中的異常被捕獲,程序不會報錯。__exit__() 執行完畢後,整個程序不會直接退出,會繼續執行剩餘代碼,可是 with 語句中異常後的代碼不會被執行。

好處:

  • 把代碼放在 with 中執行,with 結束後,自動清理工做,不須要手動干預。
  • 在須要管理一下資源如(文件、網絡鏈接和鎖的編程環境中),能夠在 __exit__()中定製自動釋放資源的機制。

總結:

  1. with 語句觸發 __enter__(),拿到返回值並賦值給 f。
  2. with 語句中無異常時,程序執行完畢觸發 __exit__(),其返回值爲三個 None。
  3. 有異常狀況時,從異常處直接觸發 __exit__
    • __exit__ 返回值爲 true,那麼直接捕獲異常,程序不報錯 。且繼續執行剩餘代碼,但不執行 with 語句中異常後的代碼。
    • __exit__ 返回值不爲 True,產生異常,整個程序執行完畢。

2. 類的裝飾器

Python 中一切皆對象,裝飾器一樣適用於類:

無參數裝飾器

def foo(obj):
    print('foo 正在運行~')
    return obj

@foo   # Bar=foo(Bar)
class Bar:
    pass
print(Bar())
foo 正在運行~
<__main__.Bar object at 0x0000000004ED1B70>

有參數裝飾器

利用裝飾器給類添加類屬性:

def deco(**kwargs):   # kwargs:{'name':'tom', 'age':1}
    def wrapper(obj):  # Dog、Cat
        for key, val in kwargs.items():
            setattr(obj, key, val)  # 設置類屬性
        return obj
    return wrapper

@deco(name='tom', age=1)        # @wrapper ===> Dog=wrapper(Dog)
class Dog:
    pass
print(Dog.__dict__)         # 查看屬性字典
print(Dog.name, Dog.age)

@deco(name='john')
class Cat:
    pass
print(Cat.__dict__)
print(Cat.name)
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Dog' objects>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>, '__doc__': None, 'name': 'tom', 'age': 1}
tom 1
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Cat' objects>, '__weakref__': <attribute '__weakref__' of 'Cat' objects>, '__doc__': None, 'name': 'john'}
john

@deco(name='tom', age=1) 首先執行 deco(name='tom', age=1),返回 wrapper。再接着執行 @wrapper,至關於 Dog = wrapper(Dog)。最後利用 setattr(obj, key, value) 爲類添加屬性。

描述符+裝飾器實現類型檢查

class Descriptor:
    def __init__(self, key, expected_type):
        self.key = key     # 'name'
        self.expected_type = expected_type   # str
        
    def __get__(self, instance, owner):    # self:Descriptor對象, instance:People對象,owner:People
        return instance.__dict__[self,key]
    
    def __set__(self, instance, value):
        if isinstance(value, self.expected_type):
            instance.__dict__[self.key] = value
        else:
            raise TypeError('你傳入的%s不是%s' % (self.key, self.expected_type))
            
def deco(**kwargs):    # kwargs:{'name': 'str', 'age': 'int'}
    def wrapper(obj):   # obj:People
        for key, val in kwargs.items():
            setattr(obj, key, Descriptor(key, val))  # key:name、age   val:str、int 
        return obj
    return wrapper

@deco(name=str, age=int)           # @wrapper ==> People = wrapper(People)
class People:
#     name = Descriptor('name', str)
    def __init__(self, name, age, color):
        self.name = name
        self.age = age
        self.color = color
p1 = People('rose', 18, 'white')
print(p1.__dict__)

p2 = People('rose', '18', 'white')
{'__module__': '__main__', '__init__': <function People.__init__ at 0x00000000051971E0>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None, 'name': <__main__.Descriptor object at 0x00000000051BFDD8>, 'age': <__main__.Descriptor object at 0x00000000051D1518>}
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-18-010cd074c06d> in <module>()
     30 print(People.__dict__)
     31 
---> 32 p2 = People('rose', '18', 'white')

<ipython-input-18-010cd074c06d> in __init__(self, name, age, color)
     25     def __init__(self, name, age, color):
     26         self.name = name
---> 27         self.age = age
     28         self.color = color
     29 p1 = People('rose', 18, 'white')

<ipython-input-18-010cd074c06d> in __set__(self, instance, value)
     11             instance.__dict__[self.key] = value
     12         else:
---> 13             raise TypeError('你傳入的%s不是%s' % (self.key, self.expected_type))
     14 
     15 def deco(**kwargs):    # kwargs:{'name': 'str', 'age': 'int'}

TypeError: 你傳入的age不是<class 'int'>

在利用 setattr() 設置屬性的時候,對 value 進行類型檢查。

3. 自定製 property

靜態屬性 property 可讓類或類對象,在調用類函數屬性時,相似於調用類數據屬性同樣。下面咱們利用描述符原理來模擬 property。

把類設置爲裝飾器,裝飾另外一個類的函數屬性:

class Foo:
    def __init__(self, func):
        self.func = func
        
class Bar:
    @Foo      # test = Foo(test)
    def test(self):
        pass
b1 = Bar()
print(b1.test)
<__main__.Foo object at 0x00000000051DFEB8>

調用 b1.test 返回的是 Foo 的實例對象。

利用描述符模擬 property

class Descriptor:        # 1
    """描述符"""
    def __init__(self, func):     # 4    # func: area
        self.func = func  # 5
        
    def __get__(self, instance, owner):   # 13
        """
        調用 Room.area
        self: Descriptor 對象
        instance: Room 對象(即 r1或Room中的 self)
        owner: Room 自己
        """
        res = self.func(instance)  #14 # 實現調用 Room.area,area須要一個位置參數 self,即 r1
        return res      # 17
    
       
class Room:     # 2
    def __init__(self, name, width, height):   # 7
        self.name = name        # 8
        self.width = width     # 9
        self.height = height   # 10
    
    # area = Descriptor(area),Descriptor 對象賦值給 area,實際是給 Room 增長描述符
    # 設置 Room 的屬性字典
    @Descriptor     # 3  
    def area(self):         # 15
        """計算面積"""
        return self.width * self.height   # 16
    
r1 = Room('臥室', 3, 4)    # 6
print(Room.__dict__)   # 11
print(r1.area)      # 12    調用 Descriptor.__get__()
{'__module__': '__main__', '__init__': <function Room.__init__ at 0x0000000005206598>, 'area': <__main__.Descriptor object at 0x000000000520C6A0>, 'test': <property object at 0x00000000051FFDB8>, '__dict__': <attribute '__dict__' of 'Room' objects>, '__weakref__': <attribute '__weakref__' of 'Room' objects>, '__doc__': None}
12

首先執行 @Descriptor,至關於 area = Descriptor(area),相似於給類 Room 設置 area屬性。area 屬性又被 Descriptor 代理(描述)。

全部當執行 r1.area 的時候,觸發調用 Descriptor.__get__() 方法,而後執行 area() 函數,並返回結果。

到目前爲止,模擬 property 已經完成了百分之八十。惟一不足的是:property 除了可使用實例對象調用外,還可使用類調用。只不過返回的是 property 這個對象:

class Room:
    ...
    @property
    def test(self):
        return 1
print(Room.test)
<property object at 0x000000000520FAE8>

那麼咱們也用類調用看看:

print(Room.area)
AttributeError: 'NoneType' object has no attribute 'width'

發現類沒有 width 這個屬性,這是由於咱們在使用 __get__() 方法時,其中 instance 參數接收的是類對象,而不是類自己。當使用類去調用時,instance = None

所以在使用模擬類調用時,須要判斷是否 instance 爲 None:

def __get__(self, instance, owner):
    if instance == None:
        return self
        ...
print(Room.area)
<__main__.Descriptor object at 0x0000000005212128>
​````
發現返回的是 Descriptor 的對象,與 property 的返回結果同樣。到此爲止,咱們使用描述符成功地模擬 property 實現的功能。

**實現延遲計算功能**

要想實現延遲計算功能,只需每計算一次便緩存一次到實例屬性字典中便可:
​```python
def __get__(self, instance, owner):
    if instance == None:
        return self
    res = self.func(instance)  
    setattr(instance, self.func.__name__, res)
    return res
    ...
print(r1.area)  # 首先在本身的屬性字典中找,本身屬性字典中有 area 屬性。由於已經緩存好上一次的結果,全部不須要每次都去計算

總結

  • 描述符能夠實現大部分 Python 類特性中的底層魔法,包括 @property、@staticmethod、@classmethod,甚至是 __slots__屬性。
  • 描述符是不少高級庫和框架的重要工具之一,一般是使用到裝飾器或元類的大小框架中的一個組件。

4. 描述符自定製

4.1 描述符自定製類方法

類方法 @classmethod 能夠實現類調用函數屬性。咱們也能夠描述符模擬出類方法實現的功能:

class ClassMethod:
    def __init__(self, func):
        self.func = func
        
    def __get__(self, instance, owner):
        def deco(*args, **kwargs):
            return self.func(owner, *args, **kwargs)
        return deco

class Dog:
    name = 'tom'
    @ClassMethod  # eat = ClassMethod(eat) 至關於爲 Dog 類設置 eat 類屬性,只不過它被 Classmethod 代理
    def eat(cls, age):
        print('那隻叫%s的狗,今年%s歲,它正在吃東西' % (cls.name, age))
        
print(Dog.__dict__)
Dog.eat(2)     # Dog.eat:調用類屬性,觸發 __get__,返回 deco。再調用 deco(2)
{'__module__': '__main__', 'name': 'tom', 'eat': <__main__.ClassMethod object at 0x00000000052D1278>, '__dict__': <attribute '__dict__' of 'Dog' objects>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>, '__doc__': None}
那隻叫tom的狗,今年2歲,它正在吃東西

類 ClassMethod 被定義爲一個描述符,@ClassMethod 至關於 eat = ClassMethod(eat)。所以也至關於 Dog 類設置了類屬性 eat,只不過它被 ClassMethod 代理。

運行 Dog.eat(2),其中 Dog.eat 至關於調用 Dog 的類屬性 eat,觸發 __get__() 方法,返回 deco 。最後調用 deco(2)

4.2 描述符自定製靜態方法

靜態方法的做用是能夠在類中定義一個函數,該函數的參數與類以及類對象無關。下面咱們用描述符來模擬實現靜態方法:

class StaticMethod:
    def __init__(self, func):
        self.func = func
        
    def __get__(self, instance, owner):
        def deco(*args, **kwargs):
            return self.func(*args, **kwargs)
        return deco
    
class Dog:
    @StaticMethod
    def eat(name, color):
        print('那隻叫%s的狗,顏色是%s的' % (name, color))

print(Dog.__dict__)
Dog.eat('tom', 'black')

d1 = Dog()
d1.eat('lila', 'white')
print(d1.__dict__)
{'__module__': '__main__', 'eat': <__main__.StaticMethod object at 0x00000000052EF940>, '__dict__': <attribute '__dict__' of 'Dog' objects>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>, '__doc__': None}
那隻叫tom的狗,顏色是black的
那隻叫lila的狗,顏色是white的
{}

類以及類對象屬性字典中,皆沒有 name 和 color 參數,利用描述符模擬靜態方法成功。

5. property 用法

靜態屬性 property 本質是實現了 get、set、delete 三個方法。

class A:
    @property
    def test(self):
        print('get運行')
        
    @test.setter
    def test(self, value):
        print('set運行')
        
    @test.deleter
    def test(self):
        print('delete運行')
        
a1 = A()
a1.test
a1.test = 'a'
del a1.test
get運行
set運行
delete運行

另外一種表現形式:

class A:
    def get_test(self):
        print('get運行')
        
    def set_test(self, value):
        print('set運行')
        
    def delete_test(self):
        print('delete運行')
        
    test = property(get_test, set_test, delete_test)
    
a1 = A()
a1.test
a1.test = 'a'
del a1.test
get運行
set運行
delete運行

應用

class Goods:
    def __init__(self):
        self.original_price = 100
        self.discount = 0.8
        
    @property
    def price(self):
        """實際價格 = 原價 * 折扣"""
        new_price = self.original_price * self.discount
        return new_price
    
    @price.setter
    def price(self, value):
        self.original_price = value
        
    @price.deleter
    def price(self):
        del self.original_price
        
g1 = Goods()
print(g1.price)     # 獲取商品價格
g1.price = 200      # 修改原價
print(g1.price)
del g1.price
80.0
160.0

6. 元類

Python 中一切皆對象,對象是由類實例化產生的。那麼類應該也有個類去產生它,利用 type() 函數咱們能夠去查看:

class A:
    pass
a1 = A()
print(type(a1))
print(type(A))
<class '__main__.A'>
<class 'type'>

由上可知,a1 是類 A 的對象,而 A 是 type 類產生的對象。
當咱們使用 class 關鍵字的時候,Python 解釋器在加載 class 關鍵字的時候會自動建立一個對象(可是這個對象非類實例產生的對象)。

6.1 什麼是元類

元類是類的類,也就是類的模板。用於控制建立類,正如類是建立對象的模板同樣。

在 Python 中,type 是一個內建的元類,它能夠用來控制生成類。Python 中任何由 class 關鍵字定義的類都 type 類實例化的對象。

6.2 定義類的兩種方法

使用 class 關鍵字定義:

class Foo:
    pass

使用 type() 函數定義:

type() 函數有三個參數:第一個爲類名(str 格式),第二個爲繼承的父類(tuple),第三個爲屬性(dict)。

x = 2
def __init__(self, name, age):
    self.name = name
    self.age = age
    
def test(self):
    print('Hello %s' % self.name)

Bar = type('Bar', (object,), {'x': 1, '__init__': __init__, 'test': test, 'test1': test1})  # 類屬性、函數屬性
print(Bar)
print(Bar.__dict__)

b1 = Bar('rose', 18)
b1.test()
<class '__main__.Bar'>
{'x': 1, '__init__': <function __init__ at 0x00000000055A6048>, 'test': <function test at 0x00000000055A60D0>, 'test1': <function test1 at 0x0000000005596BF8>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'Bar' objects>, '__weakref__': <attribute '__weakref__' of 'Bar' objects>, '__doc__': None}
Hello rose

6.3 __init__ 與 __new__

構造方法包括建立對象和初始化對象,分爲兩步執行:先執行 __new__,再執行 __init__

  • __new__:在建立對象以前調用,它的任務是建立對象並返回該實例,所以它必需要有返回值,是一個靜態方法。
  • __init__:在建立對象完成後被調用,其功能是設置對象屬性的一些屬性值。

也就是 __new__建立的對象,傳給 __init__ 做爲第一個參數(即 self)。

class Foo:
    def __init__(self):
        print('__init__方法')
        print(self)
        
    def __new__(cls):
        print('__new__方法')
        ret = object.__new__(cls)   # 建立實例對象
        print(ret)
        return ret
f1 = Foo()
__new__方法
<__main__.Foo object at 0x00000000055AFF28>
__init__方法
<__main__.Foo object at 0x00000000055AFF28>

總結

  • __new__ 至少要有一個參數 cls,表明要實例化的類,由解釋器自動提供。必需要有返回值,能夠返回父類出來的實例,或直接 object 出來的實例。
  • __init__ 的第一個參數 self 就是 __new__ 的返回值。

6.4 自定義元類

若是一個類沒有指定元類,那麼它的元類就是 type,也能夠本身自定義一個元類。

class MyType(type):
    def __init__(self, a, b, c):    # self:Bar   a:Bar  b:() 父類  c:屬性字典
        print(self)   
        print('元類 init 執行~')
        
    def __call__(self, *args, **kwargs):
        print('call 方法執行~')
        obj = object.__new__(self)     # Bar 產生實例對象,即 b1
        print(obj)
        self.__init__(obj, *args, **kwargs)  # Bar.__init__(b1, name)
        return obj
        
class Bar(metaclass=MyType): # Bar = MyType('Bar', (object,), {}) 觸發 MyType 的 __init__() 
    def __init__(self, name):   # self:f1
        self.name = name
        
b1 = Bar('rose')   # Bar 也是對象,對象加括號,觸發 __call__()
print(b1.__dict__)
<class '__main__.Bar'>
元類 init 執行~
call 方法執行~
<__main__.Bar object at 0x00000000055D3F28>
{'name': 'rose'}

加載完程序後,首先執行 metaclass=MyType,它至關於 Bar = MyType('Bar', (object,), {}),它會執行 MyType 的 __init__ 執行。

再接着執行 b1=Bar('rose) ,由於 Bar 也是對象,對象加括號就會觸發 __call__() 方法,再由 __new__() 方法產生實例對象,最後返回。

總結

  • 元類是類的模板,其功能是產生類對象,那麼就能夠模擬經過 __new__() 產生一個類的對象,並返回。
  • 一切皆對象,那麼類也是個對象,它是由 type 產生。
相關文章
相關標籤/搜索