Python 類的魔術方法


Python中類的魔術方法

  在Python中以兩個下划線開頭的方法,__init__、__str__、__doc__、__new__等,被稱為"魔術方法"(Magic methods)。魔術方法在類或對象的某些事件出發后會自動執行,如果希望根據自己的程序定制自己特殊功能的類,那么就需要對這些方法進行重寫。
  注意:Python 將所有以 __(兩個下划線)開頭的類方法保留為魔術方法。所以在定義類方法時,除了上述魔術方法,建議不要以 __ 為前綴。

Python提供的魔術方法

  魔術方法這里按照不同的類別有如下分類:

魔法方法
含義
 
基本的魔法方法
__new__(cls[, ...]) 1. __new__ 是在一個對象實例化的時候所調用的第一個方法
2. 它的第一個參數是這個類,其他的參數是用來直接傳遞給 __init__ 方法
3. __new__ 決定是否要使用該 __init__ 方法,因為 __new__ 可以調用其他類的構造方法或者直接返回別的實例對象來作為本類的實例,如果 __new__ 沒有返回實例對象,則 __init__ 不會被調用
4. __new__ 主要是用於繼承一個不可變的類型比如一個 tuple 或者 string
__init__(self[, ...]) 構造器,當一個實例被創建的時候調用的初始化方法
__del__(self) 析構器,當一個實例被銷毀的時候調用的方法
__call__(self[, args...]) 允許一個類的實例像函數一樣被調用:x(a, b) 調用 x.__call__(a, b)
__len__(self) 定義當被 len() 調用時的行為
__repr__(self) 定義當被 repr() 調用或者直接執行對象時的行為
__str__(self) 定義當被 str() 調用或者打印對象時的行為
__bytes__(self) 定義當被 bytes() 調用時的行為
__hash__(self) 定義當被 hash() 調用時的行為
__bool__(self) 定義當被 bool() 調用時的行為,應該返回 True 或 False
__format__(self, format_spec) 定義當被 format() 調用時的行為
 
有關屬性
__getattr__(self, name) 定義當用戶試圖獲取一個不存在的屬性時的行為
__getattribute__(self, name) 定義當該類的屬性被訪問時的行為
__setattr__(self, name, value) 定義當一個屬性被設置時的行為
__delattr__(self, name) 定義當一個屬性被刪除時的行為
__dir__(self) 定義當 dir() 被調用時的行為
__get__(self, instance, owner) 定義當描述符的值被取得時的行為
__set__(self, instance, value) 定義當描述符的值被改變時的行為
__delete__(self, instance) 定義當描述符的值被刪除時的行為
 
比較操作符
__lt__(self, other) 定義小於號的行為:x < y 調用 x.__lt__(y)
__le__(self, other) 定義小於等於號的行為:x <= y 調用 x.__le__(y)
__eq__(self, other) 定義等於號的行為:x == y 調用 x.__eq__(y)
__ne__(self, other) 定義不等號的行為:x != y 調用 x.__ne__(y)
__gt__(self, other) 定義大於號的行為:x > y 調用 x.__gt__(y)
__ge__(self, other) 定義大於等於號的行為:x >= y 調用 x.__ge__(y)
 
算數運算符
__add__(self, other) 定義加法的行為:+
__sub__(self, other) 定義減法的行為:-
__mul__(self, other) 定義乘法的行為:*
__truediv__(self, other) 定義真除法的行為:/
__floordiv__(self, other) 定義整數除法的行為://
__mod__(self, other) 定義取模算法的行為:%
__divmod__(self, other) 定義當被 divmod() 調用時的行為
__pow__(self, other[, modulo]) 定義當被 power() 調用或 ** 運算時的行為
__lshift__(self, other) 定義按位左移位的行為:<<
__rshift__(self, other) 定義按位右移位的行為:>>
__and__(self, other) 定義按位與操作的行為:&
__xor__(self, other) 定義按位異或操作的行為:^
__or__(self, other) 定義按位或操作的行為:|
 
反運算
__radd__(self, other) (與上方相同,當左操作數不支持相應的操作時被調用)
__rsub__(self, other) (與上方相同,當左操作數不支持相應的操作時被調用)
__rmul__(self, other) (與上方相同,當左操作數不支持相應的操作時被調用)
__rtruediv__(self, other) (與上方相同,當左操作數不支持相應的操作時被調用)
__rfloordiv__(self, other) (與上方相同,當左操作數不支持相應的操作時被調用)
__rmod__(self, other) (與上方相同,當左操作數不支持相應的操作時被調用)
__rdivmod__(self, other) (與上方相同,當左操作數不支持相應的操作時被調用)
__rpow__(self, other) (與上方相同,當左操作數不支持相應的操作時被調用)
__rlshift__(self, other) (與上方相同,當左操作數不支持相應的操作時被調用)
__rrshift__(self, other) (與上方相同,當左操作數不支持相應的操作時被調用)
__rand__(self, other) (與上方相同,當左操作數不支持相應的操作時被調用)
__rxor__(self, other) (與上方相同,當左操作數不支持相應的操作時被調用)
__ror__(self, other) (與上方相同,當左操作數不支持相應的操作時被調用)
 
增量賦值運算
__iadd__(self, other) 定義賦值加法的行為:+=
__isub__(self, other) 定義賦值減法的行為:-=
__imul__(self, other) 定義賦值乘法的行為:*=
__itruediv__(self, other) 定義賦值真除法的行為:/=
__ifloordiv__(self, other) 定義賦值整數除法的行為://=
__imod__(self, other) 定義賦值取模算法的行為:%=
__ipow__(self, other[, modulo]) 定義賦值冪運算的行為:**=
__ilshift__(self, other) 定義賦值按位左移位的行為:<<=
__irshift__(self, other) 定義賦值按位右移位的行為:>>=
__iand__(self, other) 定義賦值按位與操作的行為:&=
__ixor__(self, other) 定義賦值按位異或操作的行為:^=
__ior__(self, other) 定義賦值按位或操作的行為:|=
 
一元操作符
__pos__(self) 定義正號的行為:+x
__neg__(self) 定義負號的行為:-x
__abs__(self) 定義當被 abs() 調用時的行為
__invert__(self) 定義按位求反的行為:~x
 
類型轉換
__complex__(self) 定義當被 complex() 調用時的行為(需要返回恰當的值)
__int__(self) 定義當被 int() 調用時的行為(需要返回恰當的值)
__float__(self) 定義當被 float() 調用時的行為(需要返回恰當的值)
__round__(self[, n]) 定義當被 round() 調用時的行為(需要返回恰當的值)
__index__(self) 1. 當對象是被應用在切片表達式中時,實現整形強制轉換
2. 如果你定義了一個可能在切片時用到的定制的數值型,你應該定義 __index__
3. 如果 __index__ 被定義,則 __int__ 也需要被定義,且返回相同的值
 
上下文管理(with 語句)
__enter__(self) 1. 定義當使用 with 語句時的初始化行為
2. __enter__ 的返回值被 with 語句的目標或者 as 后的名字綁定
__exit__(self, exc_type, exc_value, traceback) 1. 定義當一個代碼塊被執行或者終止后上下文管理器應該做什么
2. 一般被用來處理異常,清除工作或者做一些代碼塊執行完畢之后的日常工作
 
容器類型
__len__(self) 定義當被 len() 調用時的行為(返回容器中元素的個數)
__getitem__(self, key) 定義獲取容器中指定元素的行為,相當於 self[key]
__setitem__(self, key, value) 定義設置容器中指定元素的行為,相當於 self[key] = value
__delitem__(self, key) 定義刪除容器中指定元素的行為,相當於 del self[key]
__iter__(self) 定義當迭代容器中的元素的行為
__reversed__(self) 定義當被 reversed() 調用時的行為
__contains__(self, item) 定義當使用成員測試運算符(in 或 not in)時的行為

Python的魔術方法舉例

這里僅針對常用的魔術方法進行舉例,便於理解及靈活運用,部分魔術方法已經在:這篇博客中舉例說明

小技巧:如果在需要返回對象的魔術方法里面不知道如何返回,可以調用super函數來執行父類的相同方法。

注意:魔法方法必須要使用return進行返回。

基本的魔法方法

__repr__(self):直接執行對象時執行的方法

>>> 
>>> class A:
	def __repr__(self):
		return '__repr__'
	
>>> a = A()
>>> a
__repr__

__bool__(self):判斷對象的bool值時執行的方法,返回值只能是bool類型

>>> 
>>> class C:
	def __bool__(self):
		return False
	
>>> c = C()
>>> bool(c)
False
>>> 

屬性相關的魔術方法

__getattr__(self,name):獲取一個不存在的屬性時執行的方法。

__setattr__(self,name,value):設置一個屬性時執行的方法。

__delattr__(self,name):刪除一個屬性時執行的方法。

__getattribute__(self,name):當該類的屬性被訪問時執行的方法。

>>> class A:
	def __getattr__(self,name):
		print('__getattr__')
	def __setattr__(self,name,value):
		print('__setattr__')
		super().__setattr__(name,value)
	def __delattr__(self,name):
		print('__delattr__')
		return super().__delattr__(name)
	def __getattribute__(self,name):
		print('__getattribute__')
		return super().__getattribute__(name)

	
>>> a = A()
>>> a.name
__getattribute__          
__getattr__
>>> a.name = 'daxin'
__setattr__
>>> a.name
__getattribute__
'daxin'
>>> del a.name
__delattr__
>>> 

注意

  1、__getattribute__,先於__getattr__訪問

  2、__getattr__,沒有在父類中進行定義,所以不能繼承

  3、__setattr__,為設置屬性,所以無需return

  3、在__setattr__時,不能直接使用self.name = value ,會造成死循環,因為在執行self.name = value的時候又會調用本身,無限循環下去

小技巧

  在__setattr__的時候,除了使用super的方式設置變量和值以外,還可以使用__dict__來設置。(但是建議使用super的方法)

>>> class A:
	def __getattr__(self,name):
		print('__getattr__')
	def __setattr__(self,name,value):
		print('__setattr__')
		self.__dict__[name] = value
	def __delattr__(self,name):
		print('__delattr__')
		return super().__delattr__(name)
	def __getattribute__(self,name):
		print('__getattribute__')
		return super().__getattribute__(name)


>>> a = A()
>>> a.name
__getattribute__
__getattr__
>>> a.name = 'daxin'
__setattr__
__getattribute__
>>> a.name
__getattribute__
'daxin'
>>> 

屬性相關之property

我們知道使用property可以把,類的某些方法當作屬性進行訪問,賦值,修改的,而Property內部也是通過類的魔術方法實現的。

下面的三個屬性,主要用於在被當作其他的類的屬性時使用的。

__set__(self,instance,value):當作一個描述符(屬性)被賦值時執行的方法

__get__(self,instance,owner):當作一個描述符(屬性)被獲取時執行的方法

__delete__(self,instance):當作一個描述符(屬性)被刪除時執行的方法

 1 # 當作一個描述符被訪問的含義就是:
 2 # 一個函數在實例化的時候調用了其他類的對象
 3 
 4 >>>
 5 >>> class A:
 6     pass
 7 
 8 >>> class B:
 9     def __init__(self):
10         self.name = A()     # 把A類的對象在B實例化的時候當作描述符(屬性)進行賦值
11     
12 >>> 
什么是當作一個描述符被訪問?
>>> class A:
    def __get__(self,instance,owner):
        print('__get__','self:',self,'instance:',instance,'owner:',owner)
	
    def __set__(self,instance,value):
        print('__set__','self:',self,'instance:',instance,'value:',value)

    def __delete__(self,instance):
        print('__delete__','self:',self,'instance:',instance)

>>> class B:
    name = A()

>>> 
>>> b = B()
>>> b.name
__get__ self: <__main__.A object at 0x1134247f0> instance: <__main__.B object at 0x113411d68> owner: <class '__main__.B'>
>>> b.name = 'abc'
__set__ self: <__main__.A object at 0x1134247f0> instance: <__main__.B object at 0x113411d68> value: abc
>>> del b.name
__delete__ self: <__main__.A object at 0x1134247f0> instance: <__main__.B object at 0x113411d68>
>>> 

擴展

由於不知道魔術方法傳遞的參數都是什么東西,所以在上面的例子打印了一下:

  • self:表示的是當前類(擁有__set__,__get__等方法的類本身)
  • instance:表示的是調用此類的類的實例化對象
  • owner:表示調用此類的類
  • value:表示要設置的值

根據這三個方法,那么我們完全可以定義自己的property屬性

class Myproperty:   # 定義自己的Property

    def __init__(self,mget=None,mset=None,mdel=None):   # 綁定三個修改方法
        self.mget = mget
        self.mset = mset
        self.mdel = mdel

    def __get__(self,instance,owner):
        return self.mget(instance)        

    def __set__(self,instance,value):
        return self.mset(instance,value)

    def __delete__(self,instance):
        return self.mdel(instance)

    
class A:
    def __init__(self):
        self._x = None

    def setX(self,value):
        self._x = value

    def getX(self):
        try:
            if self._x:
                return self._x
        except AttributeError as e:
            print('Attribute is not exist')

    def delX(self):
        del self._x

    name = Myproperty(getX,setX,delX)
  

>>> a = A()
>>> a.name
>>> a.name = 'daxin'
>>> a.name
'daxin'
>>> del a.name
>>> a.name
Attribute is not exist
>>> 

PS:關於為什么在Myproperty中傳遞instance,那么請注意我們的setX,getX,默認有一個self參數,使用自定義Property的話,那么我們必須手動傳遞,在Myproperty類中instance表示對象自己,所以傳遞instance給setX/getX,其實等同於self。

運算符相關魔術方法  

__add__:在執行加法時執行的方法

>>> class A(int):
	def __add__(self,other):
		print('__add__')
		return super().__sub__(other)     # 這里改寫了 add方法,調用了父類的減法

>>> a = A(8)
>>> a + 6
__add__
2
>>> 

__radd__:當左邊的對象沒有__add__方法時,執行右邊對象的__radd__方法

>>> class A:
	pass

>>> class B(int):

	def __radd__(self,other):
		return 'I am B,because right object not have __add__'

	
>>> 
>>> a = A()
>>> b = B(12)
>>> a + b
'I am B,because right object not have __add__'
>>> 

容器類

在Python中,列表、元組、字典和集合等等,這些都可以稱為容器。

__getitem__:獲取容器中的元素,比如mylist[1]

__setitem__:設置容器中的元素,比如mylist[2] = 'hello'

__delitem__:刪除容器中的某個元素,比如 del mylist[2]

# 定義一個不可變的列表類型
# 記錄元素被訪問的次數

class Mylist:

    def __init__(self,*args):
        self.value = [ x for x in args ]    # 列表生成式
        self.count = {}.fromkeys(range(len(self.value)),0)    # 定義字典用於按照索引統計訪問次數

    def __getitem__(self,key):
        self.count[key] += 1
        return self.value[key]

    def __repr__(self):
        return str(self.value)

    __str__ = __repr__

   
>>> a = Mylist(1,2,3,45)
>>> a
[1, 2, 3,45]
>>> a.count
{0: 0, 1: 0, 2: 0, 3: 0}
>>> a[0]
1
>>> a.count
{0: 1, 1: 0, 2: 0, 3: 0}
>>> a[2]
3
>>> a.count
{0: 1, 1: 0, 2: 1, 3: 0}
>>> a[1] = 123

小練習

1、寫一個矩形的類,那么這個類默認有長和寬兩個參數,當傳入一個叫square的屬性時,值就等於邊長,由於是正方形那么長和寬就等於邊長。

class Rectangle(object):

    # init object
    def __init__(self,width,height):
        self.width = width
        self.height = height


    def getacreage(self):
        return self.width * self.height

    def __setattr__(self,name,value):
        if name == 'square':
            self.width = value
            self.height = value

        else:
            super().__setattr__(name,value)
            

2、完成如下要求:

  - 定制一個計時器類

  - start和stop方法代表啟動和停止計時

  - 假設計時器對象t1,定制t1對象直接執行和打印時的輸出信息

  - 當計時器未啟動或已經停止計時,調用stop方法會給予溫馨提示

  - 兩個計時器對象可以進行相加: t1 + t2

import time 

class Mytimer():

    # 初始化
    def __init__(self):
        self.prompt = 'Timer is not start'    # 提示信息
        self.start_time = None   
        self.stop_time = None
    
    # 啟動定時器
    def start(self):
        if self.start_time:
            print('Please Use Stop() to stop Timer')   # 屬性存在,表示已經啟動計時
        else:
            self.start_time = time.time()
            print('The start of Timer')

    # 關閉定時器
    def stop(self):
        if self.start_time:
            self.stop_time = time.time()
            print('The stop of Timer')
            self._calc()
        else:
         print('Please Use Start() to start Timer')   # 沒有執行start(),檢測不到屬性,進行提示

    # 計算耗時
    def _calc(self):
        self.prompt = 'Total time : '
        self.end_time = self.stop_time - self.start_time
        self.prompt += str('{:.2f}'.format(self.end_time))
        self.start_time = None
        self.stop_time = None

    # 直接執行對象時,打印提示信息
    def __repr__(self):
        return self.prompt

    # 打印對象和執行對象相同
    __str__ = __repr__

    # 計時器相加
    def __add__(self,other):
        self.prompt = 'All Time : '
        add_time = self.end_time + other.end_time 
        self.prompt += str('{:.2f}'.format(add_time))
        return self.prompt

3、完成如下需求

  - 定義一個溫度類,然后定義兩個描述符類用於描述攝氏度和華氏度兩個屬性。

  - 兩個屬性會自定進行轉換,也就是說可以給攝氏度這個屬性賦值,然后打印的華氏度屬性是自動轉換后的結果。

class Celsius:

    def __init__(self,value = 37):
        self.value = value

    def __set__(self,instance,value):
        self.value = value

    def __get__(self,instance,owner):
        return self.value


class Fahrenheit:

    def __set__(self,instance,value):
         instance.cel = (float(value) - 32 )  / 1.8
         
    def __get__(self,instance,owner):
        return instance.cel * 1.8  + 32   # 通過instance實例訪問,實例化后對象的cel屬性

class Temperature:
    cel = Celsius()
    fah = Fahrenheit()

    
>>> a = Temperature()
>>> a.cel
37
>>> a.fah
98.60000000000001
>>> a.fah = 100
>>> a.cel
37.77777777777778
>>> 

 PS:由於instance表示的是實例化的對象本身(a),所以這里使用instance,指代a,來訪問設置的cel屬性,來進行換算。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM