1. 程式人生 > >Python 魔術方法小結

Python 魔術方法小結

Python 類中的雙下方法

從常用的開始:快被自己蠢哭了

一、

_ _ init __

例項化物件時呼叫

class Foo:
    def __init__(self,name):
        self.name = name

f = Foo('CharmNight') # 這時候就呼叫__init__方法

_ _ new__

建立類物件時呼叫

_ _ new __ ()方法始終都是 類的靜態方法,即使沒有被加上靜態方法裝飾器

class Foo:
    def __new__(cls,*args,**kwargs):
        return super().__new__(cls)
    def
__init__(self):
pass f = Foo()
__new__() 方法建立物件,在__init__()方法之前被呼叫,返回一個self物件,並將該物件傳給__init__()的第一個引數。一般不需要複寫__new__()方法,如果有需求:例如 單例模式可以通過重寫__new__方法  或者在類建立時進行一些修改

單例模式

當然單例模式不止這一種寫法,有很多方法都可以實現單例

# 通過__new__()方法實現單例模式

class Foo:
    _instance = None
    def __new__(cls, *args, **kwargs)
:
if not hasattr(Foo,'_instance'): cls._instance = super().__new__(cls) return cls._instance

_ _ call__

Python中的函式是一級物件。這意味著Python中的函式的引用可以作為輸入傳遞到其他的函式/方法中,並在其中被執行。
而Python中類的例項(物件)可以被當做函式對待。也就是說,我們可以將它們作為輸入傳遞到其他的函式/方法中並呼叫他們,正如我們呼叫一個正常的函式那樣。而類中__call()__函式的意義正在於此。為了將一個類例項當做函式呼叫,我們需要在類中實現__call()__

方法。也就是我們要在類中實現如下方法:def __call__(self, [args...])。這個方法接受一定數量的變數作為輸入。

__call()__的作用是使例項能夠像函式一樣被呼叫,同時不影響例項本身的生命週期(__call()__不影響一個例項的構造和析構)。但是__call()__可以用來改變例項的內部成員的值

說人話就是_ _ call __方法就是 讓物件加括號 執行call裡的內容

class Foo:
    def __init__(self, name):
        self.name = name

    def __call__(self, *args, **kwargs):
        self.name = self.name.upper()

f = Foo('Charm_Night')
f()
print(f.name)
CHARM_NIGHT

二、

_ _ getattr__

攔截點號運算。當對未定義的屬性名稱和例項進行點號運算時,就會用屬性名作為字串呼叫這個方法。如果繼承樹可以找到該屬性,則不呼叫此方法

class Foo():
    def __init__(self, item):
        self.item = item

    def __getattr__(self, item):
        print('Run getattr')
        return item

f = Foo([1,2,3])
print(f.name)
Run getattr
name

哈?說沒看懂什麼時候呼叫? 簡單說就是當呼叫該屬性卻沒有找到時,就會執行_ _ getattr _ _方法

如果屬性查詢(attribute lookup)在例項以及對應的類中(通過__dict__)失敗, 那麼會呼叫到類的__getattr__函式, 如果沒有定義這個函式,那麼丟擲AttributeError異常。由此可見,__getattr__一定是作用於屬性查詢的最後一步,兜底

例子

_ _ getattr _ _ 使得實現adapter wrapper模式非常容易,我們都知道“組合優於繼承”,_ _ getattr __實現的adapter就是以組合的形式。

class adaptee(object):
    def foo(self):
        print( 'foo in adaptee')
    def bar(self):
        print ('bar in adaptee')

class adapter(object):
    def __init__(self):
        self.adaptee = adaptee()

    def foo(self):
        print('foo in adapter')
        self.adaptee.foo()

    def __getattr__(self, name):
        print('run')
        return getattr(self.adaptee, name)

if __name__ == '__main__':
    a = adapter()
    a.foo()
    a.bar()
foo in adapter
foo in adaptee
bar in adaptee

_ _ getattribute__

_ _ getattribute__是訪問屬性的方法,我們可以通過方法重寫來擴充套件方法的功能。

對於python來說,屬性或者函式都可以被理解成一個屬性,且可以通過_ _ getattribute__獲取。

當獲取屬性時,直接return object._ getattribute_(self,item) 或者使用 super(). _ _ getattribute _ _(item)

例子

class Foo:
    def __init__(self):
        self.name = 'Night'

    def __getattribute__(self, item):
        print('Run getattribute')
        if item is 'func':
            return super().__getattribute__('func')
        elif item is 'name':
            return super().__getattribute__('name')
        else:
            return super().__getattribute__('demo')

    def demo(self):
        print('Run demo')

    def func(self):
        print('Run func')

f = Foo()
print(f.name)
f.func()
f.a()
Run getattribute
Night
Run getattribute
Run func
Run getattribute
Run demo

getattr和aetattribute區別

首先我們應該已經知道了 __getattr____getattribute__ 這兩個方法都是 擷取屬性的內建方法。我們來看下這兩個方法的區別

定義的區別

__getattr__ 是當找不到這個屬性時呼叫該方法
__getattribute__ 是當查詢屬性時就呼叫該方法

呼叫時的區別

如果類中同時實現了__getattr____getattribute__ 方法 會呼叫__getattribute__ 方法
當然 如果 __getattribute__ 方法沒有找到(丟擲異常AttributeError) 會繼續呼叫__getattr__方法
class Foo:
    def __getattr__(self, item):
        print('getattr is running')
        if item is 'func':
            return getattr(self, item)
        else:
            return getattr(self, 'demo')

    def __getattribute__(self, item):
        print('getattribute is running')
        if item is 'func':
            return super().__getattribute__(item)
        else:
            return super().__getattribute__('demo')

    def func(self):
        print('func')

    def demo(self):
        print('demo')

f = Foo()
f.func2()
getattribute is running
func

_ _ setattr__

會攔截所有屬性的的賦值語句.如果定義了這個方法,self.arrt = value 就會變成self._ _ setattr__(“attr”, value)

class Foo:
    def __setattr__(self, key, value):
        print(key, value)
        self.__dict__[key] = value

f = Foo()
f.name = 'Night'
print(f.name)
name Night
Night

錯誤使用方法

不可使用self.attr = value,因為他會再次呼叫self._ setattr_(“attr”, value),則會形成無窮遞迴迴圈,最後導致堆疊溢位異常

class Foo:
    def __setattr__(self, key, value):
        print(key, value)
        self.name = value

屬性私有化

class PrivateExc(Exception):
    '''這裡自定義了一個異常'''
    def __init__(self, key):
        err = '{} is private, Not change'.format(key)
        super().__init__(err)

class Privacy:
    def __setattr__(self, key, value):
        if key in self.privates:
            raise PrivateExc(key)
        else:
            self.__dict__[key] = value

class Foo(Privacy):
    privates = ['name']
    def __init__(self, age):
        self.__dict__['name'] = 'Night'
        self.age = age

f = Foo(18)
print(f.age)
print(f.name)
f.name = 'Charm'    # 報錯
print(f.name)

_ _ delattr__

刪除屬性時呼叫該方法

class Foo:
    def __delattr__(self, item):
        print('run delete')

f = Foo()
f.name = 'Charm'
del f.name

三、

_ _ eq__

使用==進行判斷,是否相等

class Foo:

    def __eq__(self, other):
        print('eq is run')
        if self is other:
            return True
        return False

f1 = Foo()
f2 = Foo()
print(f1 == f2)
eq is run
False

比較大小的4個方法

__ge__      大於等於
__gt__      大於
__le__      小於等於
__lt__      小於
class Foo:
    def __init__(self, age):
        self.age = age

    def __ge__(self, other):
        '''大於等於'''
        print('ge is run')
        if self.age.__ge__(other.age):
            return True
        return False

    def __gt__(self, other):
        '''大於'''
        print('gt is run')
        if self.age.__gt__(other.age):
            return True
        return False

    def __lt__(self, other):
        '''小於'''
        print('lt is run')
        if self.age.__lt__(other.age):
            return True
        return False

    def __le__(self, other):
        '''小於等於'''
        print('le is run')
        if self.age.__le__(other.age):
            return True
        return False

f1 = Foo(12)
f2 = Foo(13)
print(f1 <= f2)
print(f1 < f2)
print(f1 > f2)
print(f1 >= f2)
le is run
True
lt is run
True
gt is run
False
ge is run
False

_ _ hash__

如果呼叫hash()時會自行呼叫該物件的_ _ hash__()方法

class Foo:
    def __init__(self, sex):
        self.name = 'CharmNight'
        self.age = 18
        self.sex = sex

    def __hash__(self):
        return hash(self.name)

f = Foo('N')
f2 = Foo('M')
print(hash(f))
print(hash(f2))
-278629965
-278629965

例子

100個Student物件,如果他們的姓名和性別相同,則認為是同一個人

class Student:

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

    def __eq__(self, other):
        if self.name is other.name and self.sex is other.sex:
            return True
        return False

    def __hash__(self):
        return hash(self.name+self.sex)

set_ = set()
for i in range(100):
    stu = Student('CharmNight',i, 'N')
    set_.add(stu)

print(set_)
{<__main__.Student object at 0x02D6AC90>}

解釋下

首先:set 是根據 hash值進行判斷是否相同,即 拿到hash值 在通過==進行判斷是否是同一個物件

根據set的這個特性:
    重寫了 __eq__ 和 __hash__ 兩個方法

四、

_ _ getitem__

返回鍵對應的值

class Foo:
    def __init__(self, item):
        self.item = item

    def __getitem__(self, item):
        print('getitem is run', item)
        return self.item[item]


f1=Foo(['Charm', 'Night', '2018', '加油'])
print(f1[0])
getitem is run 0
Charm

_ _ setitem__

給鍵設定對應的值

class Foo:
    def __init__(self, item):
        self.item = item

    def __setitem__(self, key, value):
        print('setitem is run')
        self.item[key] = value

    def __repr__(self):
        return '{}'.format(self.item)

f = Foo(['Night', 'so', 'cool'])
f[0] = 'Charm'
print(f)
setitem is run
['Charm', 'so', 'cool']

_ _ delitem__

刪除給定鍵對應的元素

class Foo:
    def __init__(self, item):
        self.item = item

    def __delitem__(self, key):
        print('delitem is run')
        del self.item[key]

f = Foo(['CharmNight', 'GG'])
del f[1]
print(f.item)
delitem is run
['CharmNight']

_ _ len__

返回元素的數量

class Foo:
    def __init__(self, item):
        self.item = item

    def __len__(self):
        print('len is run')
        return len(self.item)

f = Foo([1,2,3,4])
print(len(f))
len is run
4

上述4個方法的小結

使用__getitem__ __setitem__ __delitem__ __len__ 及 自定義異常 簡單模擬了一個不可刪除的 列表類
class DelError(Exception):
    def __init__(self):
        err = '{} undelete'.format(self)
        super().__init__(err)

class Foo:
    def __init__(self, item):
        if isinstance(item, list):
            self.item = item
        else:
            raise TypeError

    def __len__(self):
        return len(self.item)

    def __setitem__(self, key, value):
        self.item[key] = value

    def __getitem__(self, item):
        start = 0
        end = len(self.item)
        if isinstance(item, int):
            if start <= item < end:
                return self.item[item]
            raise KeyError
        elif isinstance(item, slice):
            slice_start = 0 if item.start is None else item.start
            slice_stop = len(self.item) if item.stop is None else item.stop
            slice_step = item.step

            if slice_step is None:
                if slice_start <= slice_stop:
                    return self.item[slice_start:slice_stop]
                raise KeyError
            elif slice_step > 0:
                if slice_stop <= slice_stop:
                    return self.item[slice_start:slice_stop:slice_step]
                raise KeyError
            else:
                slice_start = len(self.item) if item.start is None else item.stop
                slice_stop = 0 if item.stop is None else item.start
                if slice_start >= slice_stop:
                    return self.item[slice_start:slice_stop:slice_step]
                raise KeyError

    def __delitem__(self, key):
        raise DelError

f = Foo([1,2,3,4,5,6])
print(f[::-2])
del f[2]

五、

_ _ str__

改變物件的字串顯示

返回值必須是字串,否則丟擲異常
class Foo:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return 'obj name is ' + self.name

f = Foo('CharmNight')
print(f)
print('%s'%f)
print('%r'%f)
obj name is CharmNight
obj name is CharmNight
<__main__.Foo object at 0x02D19AB0>

_ _ repr__

改變物件的字串顯示

返回值必須是字串,否則丟擲異常
如果__str__沒有被定義,那麼就會使用__repr__來代替輸出
class Foo:
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return 'obj name is ' + self.name

f = Foo('CharmNight')
print(f)
print('%s'%f)
print('%r'%f)
obj name is CharmNight
obj name is CharmNight
obj name is CharmNight

_ _ format__

自定製格式化字串

class Foo:
    def __init__(self):
        self.name = 'CharmNight'

    def __format__(self, format_spec):
        return ' '.join([self.name, format_spec])

f = Foo()
print(format(f,'No.1'))
CharmNight No.1

六、

_ _ doc__

  • 該屬性無法被繼承
  • 類的描述資訊
class Foo:
    '''Foo 是 一個類 233'''
    pass

f = Foo()
print(f.__doc__)
Foo 是 一個類 233

_ _ module__

表示當前操作的物件在那個模組

class Foo:
    '''Foo 是 一個類 233'''
    pass

f = Foo()
print(f.__module__)
__main__    # 代表當前模組

_ _ class__

表示當前操作的物件的類是什麼

class Foo:
    '''Foo 是 一個類 233'''
    pass

f = Foo()
print(f.__class__)
<class '__main__.Foo'>  # 模組.類名

七、

描述符是什麼:描述符本質就是一個新式類,在這個新式類中,至少實現了_ _ get_ _ (),_ _ set_ _ (),_ _ delete_ _ ()中的一個,這也被稱為描述符協議
_ _ get__():呼叫一個屬性時,觸發
_ _ set__():為一個屬性賦值時,觸發
_ _ delete__():採用del刪除屬性時,觸發

_ _ get__

class Foo:
    def __init__(self):
        self.name = 'Night'

    def __get__(self, instance, owner):
        print(instance, owner)
        return instance

class Foo2:
    f = Foo()
f2 = Foo2()
print(f2.f)
print(Foo2.f)
<__main__.Foo2 object at 0x0319AB90> <class '__main__.Foo2'>
<__main__.Foo2 object at 0x0319AB90>
None <class '__main__.Foo2'>
None

_ _ set__

_ _ delete__

_ _ del__

析構方法,當物件在記憶體中被釋放時,自動觸發執行。

注:如果產生的物件僅僅只是python程式級別的(使用者級),那麼無需定義 _ _ del _ _ ,如果產生的物件的同時還會向作業系統發起系統呼叫,即一個物件有使用者級與核心級兩種資源,比如(開啟一個檔案,建立一個數據庫連結),則必須在清除物件的同時回收系統資源,這就用到了_ del _

class Foo:

    def __del__(self):
        print('執行我啦')

f1=Foo()
del f1
print('------->')

#輸出結果
執行我啦
------->

注意

即使你沒有執行del 方法呼叫回收,直譯器也會在指令碼執行結束後呼叫系統的del方法進行回收

class Foo:

    def __del__(self):
        print('執行我啦')

f1=Foo()
print('------->')

#輸出結果
------->
執行我啦

經典場景

建立資料庫類,用該類例項化出資料庫連結物件,物件本身是存放於使用者空間記憶體中,而連結則是由作業系統管理的,存放於核心空間記憶體中

當程式結束時,python只會回收自己的記憶體空間,即使用者態記憶體,而作業系統的資源則沒有被回收,這就需要我們定製_ del_,在物件被刪除前向作業系統發起關閉資料庫連結的系統呼叫,回收資源

八、

_ _ enter _ _ 和 _ _ exit_ _

自定義支援上下文管理協議的類。自定義的上下文管理器要實現上下文管理協議所需要的 _ _ enter_ _ () 和 _ _ exit_ _ () 兩個方法:

  • context_manager. _ _ enter _ _ () :進入上下文管理器的執行時上下文,在語句體執行前呼叫。with 語句將該方法的返回值賦值給 as 子句中的 target,如果指定了 as 子句的話
  • context_manager. _ _ exit _ (exc_type, exc_value, exc_traceback):退出與上下文管理器相關的執行時上下文,返回一個布林值表示是否對發生的異常進行處理。引數表示引起退出操作的異常,如果退出時沒有發生異常,則3個引數都為None。如果發生異常,返回 True 表示不處理異常,否則會在退出該方法後重新丟擲異常以由 with 語句之外的程式碼邏輯進行處理。如果該方法內部產生異常,則會取代由 statement-body 中語句產生的異常。要處理異常時,不要顯示重新丟擲異常,即不能重新丟擲通過引數傳遞進來的異常,只需要將返回值設定為 False 就可以了。之後,上下文管理程式碼會檢測是否 _ exit__() 失敗來處理異常
class Foo:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print('出現with語句,物件的__enter__被觸發,有返回值則賦值給as宣告的變數')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('with中程式碼塊執行完畢時執行我啊')
        print('exc_type',exc_type)  # 異常型別
        print('exc_val',exc_val)    # 異常值
        print('exc_tb',exc_tb)      # 追溯資訊
        return True                 # 返回值為True,那麼異常會被清空,就好像啥都沒發生一樣,with後的語句正常執行

with Foo('CharmNight') as f:
    print('run')
    raise AttributeError('asd')
print('over')
出現with語句,物件的__enter__被觸發,有返回值則賦值給as宣告的變數
run
with中程式碼塊執行完畢時執行我啊
exc_type <class 'AttributeError'>
exc_val asd
exc_tb <traceback object at 0x034176C0>
over

例子

簡單模擬下with Open的語法

class Open:
    def __init__(self, file, mode='r', encoding=None):
        self.file = file
        self.mode = mode
        self.encoding = encoding

    def __enter__(self):
        self.f = open(self.file, mode=self.mode, encoding=self.encoding)
        return self.f

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()
        return True

with Open('asd', 'w', encoding='utf-8') as f:
    f.write('Hello')

九、

_ _ slots__

_ _ slots_ _ 的作用是阻止在例項化類時為例項分配dict,預設情況下每個類都會有一個dict,通過_ _ dict_ _ 訪問,這個dict維護了這個例項的所有屬性

作用:

  • 減少記憶體使用
  • 限制對例項新增新的屬性

缺點:

  • 不可被繼承
  • 不可動彈新增新屬性

例子

class Foo:
    __slots__ = ('name', 'age')

    def __init__(self, name, age):
        self.name = name
        self.age = age

f = Foo('CharmNight', 18)
print(f.name)
print(f.age)

f.sex = 'N' # 報錯
Traceback (most recent call last):
CharmNight
  File "D:/demo.py", line 12, in <module>
18
    f.sex = 'N'
AttributeError: 'Foo' object has no attribute 'sex'

如何減少記憶體的使用

為何使用__slots__:字典會佔用大量記憶體,如果你有一個屬性很少的類,但是有很多例項,為了節省記憶體可以使用__slots__取代例項的__dict__當你定義__slots__後,__slots__就會為例項使用一種更加緊湊的內部表示。例項通過一個很小的固定大小的陣列來構建,而不是為每個例項定義一個字典,這跟元組或列表很類似。在__slots__中列出的屬性名在內部被對映到這個陣列的指定小標上。使用__slots__一個不好的地方就是我們不能再給例項新增新的屬性了,只能使用在__slots__中定義的那些屬性名。
class Foo:
    __slots__ = ('name', 'age')

    def __init__(self, name, age):
        self.name = name
        self.age = age

f = Foo('CharmNight', 18)
print(f.__slots__)
print(f.__dict__)
('name', 'age')
Traceback (most recent call last):
  File "D:/demo.py", line 10, in <module>
    print(f.__dict__)
AttributeError: 'Foo' object has no attribute '__dict__'

十、

_ _ next__ 和 _ _ iter _ _

class Foo:
    def __init__(self):
        self.nub = 0

    def __next__(self):
        print('next is run')
        self.nub += 1
        return self.nub

    def __iter__(self):
        print('iter is run')
        return self
f = Foo()
for i in f:
    print(i)

十一、

_ _ add__

加法運算就是呼叫的_ _ add __

class Foo:
    def __init__(self, nub):
        self.nub = nub

    def __add__(self, other):
        return self.nub + other.nub

f1 = Foo(1)
f2 = Foo(10)
a = f1 + f2
print(a)
11

_ _ iadd__

就地加呼叫的_ _ iadd __

class Foo:
    def __init__(self, nub):
        self.nub = nub

    def __iadd__(self, other):
        return self.nub + other.nub

f1 = Foo(1)
f2 = Foo(10)
f1 += f2
print(f1)
11

_ _ radd__

被加(當左運算元不支援相應的操作時被呼叫)

class Foo:
    def __init__(self, nub):
        self.nub = nub

    def __radd__(self, other):
        print('radd is run')
        return self.nub + other

f1 = Foo(1)
s = 10 + f1
print(s)
11

當然還有其他的 …… 小白太low 還沒有接觸到