[ python ] 類中的一些特殊方法


item系列

__getitem__(self, item)            對象通過 object[key] 觸發
__setitem__(self, key, value)    對象通過 object[key] = value 觸發
__delitem__(self, key)            對象通過 del object[key] 觸發

 

class Func:
    def __getitem__(self, item):
        # object[item] 觸發
        return self.__dict__[item]

    def __setitem__(self, key, value):
        # object[key] = value 觸發
        self.__dict__[key] = value

    def __delitem__(self, key):
        # del object[key] 觸發
        print('delitem: 刪除key')
        del self.__dict__[key]

    def __delattr__(self, item):
        # del object.item 觸發
        print('delattr: 刪除key')
        del self.__dict__[item]


f = Func()
f['name'] = 'hkey'  # __setitem__
f['age'] = 20   # __setitem__
print(f.name)   # 對象屬性原本的調用方式
print(f['name'])    # __getitem__
del f['name']   # __delitem__
print('------')
del f.age   # __delattr__
item系列實例

 

要注意反射的 __delattr__ 和 __delitem__ 使用不同的方式觸發不同的特殊方法。

class Fib:
    def __getitem__(self, item):
        if isinstance(item, int):
            a, b = 1, 1
            for x in range(item):
                a, b = b, a+b
            return a
        if isinstance(item, slice):
            start = item.start
            stop = item.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a+b
            return L

f = Fib()
print(f[9])
print(f[:10])

執行結果:
55
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
實例:通過item系列像列表一樣獲取類中斐波那契數列

 

 

__str__ 和 __repr__

當直接打印一個對象的時候,顯示的是一段內存地址。

In [1]: class Person:
   ...:     def __init__(self, name):
   ...:         self.name = name

In [2]: p = Person('hkey')

In [3]: p
Out[3]: <__main__.Person at 0x2be9f5c9128>

In [4]: print(p)
<__main__.Person object at 0x000002BE9F5C9128>

 

 

為了便於更好的理解,我們定義一個__str__方法

In [1]: class Person:
   ...:     def __init__(self, name):
   ...:         self.name = name
   ...:     def __str__(self):
   ...:         return "hello %s." % self.name

In [2]: p = Person('hkey')

In [3]: p
Out[3]: <__main__.Person at 0x2858bd468d0>

In [4]: print(p)
hello hkey.

 

 

定義了__str__方法,直接輸出對象還是打印的內存地址,並沒有走__str__方法中定義的格式,用print輸出信息卻調用了__str__方法定義的內容

In [1]: class Person:
   ...:     def __init__(self, name):
   ...:         self.name = name
   ...:     def __repr__(self):
   ...:         return "hello %s." % self.name

In [2]: p = Person('hkey')

In [3]: p
Out[3]: hello hkey.

In [4]: print(p)
hello hkey.

 

 

定義了__repr__方法,不管是直接打印對象還是通過print打印對象,都是走的__repr__中定義的格式。

 

總結:

    __repr__ 和 __str__ 這兩個方法都是用於顯示的,__str__是面向用戶的,而__repr__是面向程序員
    
    使用print打印操作會首先嘗試__str__和str內置函數,它通常應該返回一個友好的提示
    當__str__不存在的時候,會去找__repr__是否定義,定義則打印__repr__中定義的內容
    
    當我們想在所有環境下都統一顯示的話,可以添加__repr__方法。
    當我們想在不同的環境下支持不同的顯示,就可以定義__repr__方法和__str__方法,終端交互使用__repr__,用戶使用__str__方法

 

__new__

 

__new__ 是在新式類中出現的方法,它作用在構造函數之前,可以這么理解,在python中存在於類里面的構造方法__init__()負責將類實例化,而在__init__() 啟動之前,__new__()決定是否要使用該__init__()方法,因為__new__()可以調用其他類的構造方法或者直接返回別的對象作為本類的實例。

具體參考:https://www.cnblogs.com/ifantastic/p/3175735.html

要記住的是:在實例化時,__new__() 先與 __init__() 方法執行.

通常來講,新式類開始實例化時,__new__()方法會返回cls(cls代指當前類)的實例,然后該類的__init__()方法作為構造方法會接收這個實例(self)作為自己的一個參數,然后依次傳入__new__()方法中接收的位置參數和命名參數。

In [1]: class Foo:
   ...:     def __init__(self, *args, **kwargs):
   ...:         print('in init function.')    
   ...:     def __new__(cls, *args, **kwargs):    # 實例化時,首先執行__new__方法
   ...:         print('in new function.')
   ...:         return object.__new__(cls, *args, **kwargs)

In [2]: f = Foo()
in new function.
in init function.

 

 

對於__new__ 和 __init__ 的個人理解如下:

首先,我們把一個類比一個生物,使用__new__方法是創建這個生物的本體(實例),當本體(實例)創建好,才能使用__init__來創建這個本體的屬性

一個單例模式來印證上面的描述:

In [1]: class Foo:
   ...:     def __init__(self, *args, **kwargs):
   ...:         pass
   ...:     def __new__(cls, *args, **kwargs):
   ...:         if not hasattr(cls, '_instance'):
   ...:             cls._instance = object.__new__(cls, *args, **kwargs)
   ...:         return cls._instance

In [2]: one = Foo()

In [3]: two = Foo()

In [4]: id(one)
Out[4]: 2173030026152

In [5]: id(two)
Out[5]: 2173030026152

In [6]: print(one == two)
True

In [7]: print(one is two)
True

 

 

通過__new__方法,首先創建實例的本體,當第二次實例化時,通過if判斷,這個本體已經存在,就直接返回,然后在調用__init__()方法附加屬性值

python3 正常的使用格式:

class Foo(object):

    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)

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


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

 

 

__del__

析構方法,當對象在內存中被釋放時,自動觸發執行。

注:此方法一般無需定義,因為python是一門高級語言,程序員在使用時無需關心內存和釋放,因為此工作都是交給python解釋器來執行的,所有,析構函數的調用是由解釋器在進行垃圾回收時自動觸發執行的。

 

在 pycharm中當程序執行完,自動觸發 __del__ 方法

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

    def __del__(self):
        print('執行__del__.')


f = Foo('hkey')

# 執行結果:
執行__del__.

__del__ 在類中適合做一些關閉文件句柄等操作。

 

 

__call__

當對象+()時候[object()] 觸發

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

    def __call__(self, *args, **kwargs):
        print('執行__call__.')

f = Foo('hkey')
f()

# 執行結果:
執行__call__.

 

 

__len__

使用 len(object) 觸發

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

    def __len__(self):
        return 10    # 注意返回結果必須是 int 類型


f = Foo('hkey')
print(len(f))


# 執行結果:
10

 

 

__hash__

調用 hash(object) 觸發

class Foo:
    def __init__(self):
        self.a = 1
        self.b = 5

    def __hash__(self):
        return hash(str(self.a) + str(self.b))


f = Foo()
print(hash(f))


# 執行結果:
2068706206124340336

 

 

__eq__

當兩個對象進行比較時,觸發

class Foo:
    def __init__(self):
        self.a = 1
        self.b = 5

    def __eq__(self, other):
        if self.a == other.a and self.b == other.b:
            return True
        return False

a = Foo()
b = Foo()
print(a == b)   # 執行這個比較的時候,就調用了 __eq__ 方法

# 執行結果:
True

 

 

實例練習:紙牌

from collections import namedtuple

Card = namedtuple('Card', ['rank', 'suit'])


class FranchDeck:
    ranks = [x for x in range(2, 11)] + list('JQKA')
    suits = ['紅桃', '黑桃', '方塊', '梅花']

    def __init__(self):
        '''創建一副牌'''
        self._card = [Card(rank, suit) for rank in FranchDeck.ranks for suit in FranchDeck.suits]

    def __len__(self):
        '''統計牌數'''
        return len(self._card)

    def __getitem__(self, item):
        '''通過object[index]取牌'''
        return self._card[item]

    def __setitem__(self, key, value):
        '''調用 shuffle 的時候需要有__setitem__方法'''
        self._card[key] = value


f = FranchDeck()
print(f[:4])    # 按照順序取牌
from random import shuffle
shuffle(f)  # 隨機排列
print(f[:4])    # 切片隨機取4張

# 執行結果:
[Card(rank=2, suit='紅桃'), Card(rank=2, suit='黑桃'), Card(rank=2, suit='方塊'), Card(rank=2, suit='梅花')]
[Card(rank=7, suit='紅桃'), Card(rank=3, suit='方塊'), Card(rank='J', suit='紅桃'), Card(rank=5, suit='黑桃')]
紙牌

 


免責聲明!

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



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