1. 程式人生 > >Python面向對象之魔術方法

Python面向對象之魔術方法

賦值 添加屬性 single 釋放 都是 lse let for 語言

__str__

改變對象的字符串顯示。可以理解為使用print函數打印一個對象時,會自動調用對象的__str__方法

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    # 定義對象的字符串表示
    def __str__(self):
        return self.name
    
s1 = Student(張三, 24)
print(s1)  # 會調用s1的__str__方法

__repr__

在python解釋器環境下,會默認顯示對象的repr表示。

>>> class Student:
...     def __init__(self, name, age):
...         self.name = name
...         self.age = age
...     def __repr__(self):
...         return self.name
... 
>>> s1 = Student(張三, 24)
>>> s1
張三

總結:

str函數或者print函數調用的是obj.__str__()
repr函數或者交互式解釋器調用的是obj.__repr__()

註意:
如果__str__沒有被定義,那麽就會使用__repr__來代替輸出。
__str__和__repr__方法的返回值都必須是字符串。

__format__

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

    __format_dict = {
        n-a: 名字是:{obj.name}-年齡是:{obj.age}
, # 名字是:lqz-年齡是:18 n:a: 名字是:{obj.name}:年齡是:{obj.age}, # 名字是:lqz:年齡是:18 n/a: 名字是:{obj.name}/年齡是:{obj.age}, # 名字是:/年齡是:18 } def __format__(self, format_spec): if not format_spec or format_spec not in self.__format_dict: format_spec = n-a fmt = self.__format_dict[format_spec] print(fmt) #{obj.name}:{obj.age} return fmt.format(obj=self) s1 = Student(lqz, 24) ret = format(s1, n/a) print(ret) # lqz/24

__del__

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

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

class A:
    def __del__(self):
        print(刪除了...)

a = A()
print(a)  # <__main__.A object at 0x10164fb00>
del a  # 刪除了...
print(a)  # NameError: name ‘a‘ is not defined

__dict__和__slots__

Python中的類,都會從object裏繼承一個__dict__屬性,這個屬性中存放著類的屬性和方法對應的鍵值對。一個類實例化之後,這個類的實例也具有這麽一個__dict__屬性。但是二者並不相同。

class A:
    some = 1

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

a = A(10)
print(a.__dict__)  # {‘num‘: 10}
a.age = 10
print(a.__dict__)  # {‘num‘: 10, ‘age‘: 10}

從上面的例子可以看出來,實例只保存實例的屬性和方法,類的屬性和方法它是不保存的。正是由於類和實例有__dict__屬性,所以類和實例可以在運行過程動態添加屬性和方法。

但是由於每實例化一個類都要分配一個__dict__變量,容易浪費內存。因此在Python中有一個內置的__slots__屬性。當一個類設置了__slots__屬性後,這個類的__dict__屬性就不存在了(同理,該類的實例也不存在__dict__屬性),如此一來,設置了__slots__屬性的類的屬性,只能是預先設定好的。

當你定義__slots__後,__slots__就會為實例使用一種更加緊湊的內部表示。實例通過一個很小的固定大小的小型數組來構建的,而不是為每個實例都定義一個__dict__字典,在__slots__中列出的屬性名在內部被映射到這個數組的特定索引上。使用__slots__帶來的副作用是我們沒有辦法給實例添加任何新的屬性了。

註意:盡管__slots__看起來是個非常有用的特性,但是除非你十分確切的知道要使用它,否則盡量不要使用它。比如定義了__slots__屬性的類就不支持多繼承。__slots__通常都是作為一種優化工具來使用。--摘自《Python Cookbook》8.4

class A:
    __slots__ = [name, age]
    
a1 = A()
# print(a1.__dict__)  # AttributeError: ‘A‘ object has no attribute ‘__dict__‘
a1.name = 張三
a1.age = 24
# a1.hobby = ‘泡妞‘  # AttributeError: ‘A‘ object has no attribute ‘hobby‘
print(a1.__slots__)

註意事項:
__slots__的很多特性都依賴於普通的基於字典的實現。
另外,定義了__slots__後的類不再 支持一些普通類特性了,比如多繼承。大多數情況下,你應該只在那些經常被使用到的用作數據結構的類上定義__slots__,比如在程序中需要創建某個類的幾百萬個實例對象 。
關於__slots__的一個常見誤區是它可以作為一個封裝工具來防止用戶給實例增加新的屬性。盡管使用__slots__可以達到這樣的目的,但是這個並不是它的初衷。它更多的是用來作為一個內存優化工具。

__item__系列

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

    def __getitem__(self, item):
        print(self.__dict__[item])

    def __setitem__(self, key, value):
        print(obj[key]=lqz賦值時,執行我)
        self.__dict__[key] = value

    def __delitem__(self, key):
        print(del obj[key]時,執行我)
        self.__dict__.pop(key)

    def __delattr__(self, item):
        print(del obj.key時,執行我)
        self.__dict__.pop(item)


f1 = Foo(sb)
print(f1.__dict__)
f1[age] = 18
f1.hobby = 泡妞
del f1.hobby
del f1[age]
f1[name] = lqz
print(f1.__dict__)

__init__

使用Python寫面向對象的代碼的時候我們都會習慣性寫一個 __init__ 方法,__init__ 方法通常用在初始化一個類實例的時候。例如:

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

    def __str__(self):
        return <Person: {}({})>.format(self.name, self.age)

p1 = Person(張三, 24)
print(p1)

上面是__init__最普通的用法了。但是__init__其實不是實例化一個類的時候第一個被調用的方法。當使用 Persion(name, age) 來實例化一個類時,最先被調用的方法其實是 __new__ 方法。

__new__

其實__init__是在類實例被創建之後調用的,它完成的是類實例的初始化操作,而 __new__方法正是創建這個類實例的方法

class Person:

    def __new__(cls, *args, **kwargs):
        print(調用__new__,創建類實例)
        return super().__new__(Person)

    def __init__(self, name, age):
        print(調用__init__,初始化實例)
        self.name = name
        self.age = age

    def __str__(self):
        return <Person: {}({})>.format(self.name, self.age)

p1 = Person(張三, 24)
print(p1)

輸出:

調用__new__,創建類實例
調用__init__,初始化實例
<Person: 張三(24)>

__new__方法在類定義中不是必須寫的,如果沒定義的話默認會調用object.__new__去創建一個對象(因為創建類的時候默認繼承的就是object)。

如果我們在類中定義了__new__方法,就是重寫了默認的__new__方法,我們可以借此自定義創建對象的行為。

舉個例子:

重寫類的__new__方法來實現單例模式。

class Singleton:
    # 重寫__new__方法,實現每一次實例化的時候,返回同一個instance對象
    def __new__(cls, *args, **kw):
        if not hasattr(cls, _instance):
            cls._instance = super().__new__(Singleton)
        return cls._instance

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


s1 = Singleton(張三, 24)
s2 = Singleton(李四, 20)
print(s1, s2)  # 這兩實例都一樣
print(s1.name, s2.name)

__call__

__call__ 方法的執行是由對象後加括號觸發的,即:對象()。擁有此方法的對象可以像函數一樣被調用。

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

    def __call__(self, *args, **kwargs):
        print(調用對象的__call__方法)

a = Person(張三, 24)  # 類Person可調用
a()  # 對象a可以調用

註意:

__new__、__init__、__call__等方法都不是必須寫的。

__doc__

定義類的描述信息。註意該信息無法被繼承。

class A:
    """我是A類的描述信息"""
    pass

print(A.__doc__)

__iter__和__next__

如果一個對象擁有了__iter__和__next__方法,那這個對象就是可叠代對象

class A:
    def __init__(self, start, stop=None):
        if not stop:
            start, stop = 0, start
        self.start = start
        self.stop = stop

    def __iter__(self):
        return self

    def __next__(self):
        if self.start >= self.stop:
            raise StopIteration
        n = self.start
        self.start += 1
        return n

a = A(1, 5)
from collections import Iterator
print(isinstance(a, Iterator))

for i in A(1, 5):
    print(i)

for i in A(5):
    print(i)
aaa=A(1)
print(next(aaa))
print(next(aaa)) #拋異常

__enter__和__exit__

一個對象如果實現了__enter__和___exit__方法,那麽這個對象就支持上下文管理協議,即with語句

class A:
    def __enter__(self):
        print(進入with語句塊時執行此方法,此方法如果有返回值會賦值給as聲明的變量)
        return oo

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(退出with代碼塊時執行此方法)
        print(1, exc_type)
        print(2, exc_val)
        print(3, exc_tb)

with A() as f:
    print(進入with語句塊)
    # with語句中代碼塊出現異常,則with後的代碼都無法執行。
    # raise AttributeError(‘sb‘)
    print(f) #f打印出oo
print(嘿嘿嘿)

上下文管理協議適用於那些進入和退出之後自動執行一些代碼的場景,比如文件、網絡連接、數據庫連接或使用鎖的編碼場景等。

__len__

擁有__len__方法的對象支持len(obj)操作。

class A:
    def __init__(self):
        self.x = 1
        self.y = 2

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

a = A()
print(len(a))

__hash__

擁有__hash__方法的對象支持hash(obj)操作。

class A:
    def __init__(self):
        self.x = 1
        self.x = 2

    def __hash__(self):
        return hash(str(self.x) + str(self.x))

a = A()
print(hash(a))

__eq__

擁有__eq__方法的對象支持相等的比較操作

class A:
    def __init__(self,x,y):
        self.x = x
        self.y = y

    def __eq__(self,obj):
        # 打印出比較的第二個對象的x值
        print(obj.x)
        if self.x +self.y == obj.x+obj.y:
            return True
        else:
            return False

a = A(1,2)
b = A(2,1)
print(a == b)

Python面向對象之魔術方法