1. 程式人生 > 其它 >筆記3:python魔法方法

筆記3:python魔法方法

python魔法方法

定義:在Python的類中,以兩個下劃線開頭、兩個下劃線結尾的方法如:__init__、__str__、__del__

魔術方法在類或物件的某些事件觸發後會自動執行,如果希望根據自己的程式定製特殊功能的類,那麼就需要對這些方法進行重寫。使用這些「魔法方法」,我們可以非常方便地給類新增特殊的功能。

python中常見的魔法方法大致可分為以下幾類:

  • 構造與初始化

  • 類的表示

  • 訪問控制

  • 比較操作

  • 容器類操作

  • 可呼叫物件

  • 序列化

我們都知道一個最基本的魔術方法, __init__ 。通過此方法我們可以定義一個物件的初始操作。但當例項化我們定義的類,如x = SomeClass() 的時候, __init__

並不是第一個被呼叫的方法。實際上,還有一個叫做 __new__ 的方法,來例項化這個物件。然後給在開始建立時候的初始化函式 來傳遞引數。在物件生命週期的另一端,也有一個 __del__ 方法。接下來看一看這三個方法:

3.1 __new__() (1)__new__(cls, [...]) 是在一個物件例項化的時候所呼叫的第一個方法,所以它才是真正意義上的構造方法。 (2)它的第一個引數是這個類,其他的引數是用來直接傳遞給 __init__ 方法。 (3)__new__ 決定是否要使用該 __init__ 方法,因為 __new__ 可以呼叫其他類的構造方法或者直接返回別的例項物件來作為本類的例項,如果 __new__

沒有返回例項物件,則 __init__ 不會被呼叫。 (4)__new__ 主要是用於繼承一個不可變的型別比如一個 tuple 或者 string。

             
class Person(object):
    def __new__(cls, *args, **kwargs):
        print("__new__()方法被呼叫了")
        print('這個是*agrs', *args)
        print('這個是kwagrs', **kwargs)
        
        # cls表示這個類,剩餘所有的引數傳給__init__()方法,
        # 若不返回,則__init__()不會被呼叫
        return object.__new__(cls)
    def __init__(self, name, age):
        print("__init__()方法被呼叫了")
        self.name = name
        self.age = age
        print(self.name, self.age)
p = Person("張三", 20)
# Output:
# __new__()方法被呼叫了
# 這個是*agrs 張三 20
# 這個是kwagrs
# __init__()方法被呼叫了
# 張三 20
 

__new__()在什麼場景使用呢?

答:當我們需要繼承內建類時,例如,想要繼承 int、str、tuple不可變型別,就無法使用 __init__ 來初始化了,只能通過 __new__ 來初始化資料:下面這個例子實現了一個類,這個類繼承了 float,之後就可以對這個類的例項進行計算了。

             
class g(float):
    """千克轉克"""
    def __new__(cls, kg):
        return float.__new__(cls, kg * 2)
a = g(50) # 50千克轉為克
print(a)    # 100
print(a + 100)  # 200 由於繼承了float,所以可以直接運算,非常方便!
 

一般情況下,覆寫 __new__() 的實現將會使用合適的引數呼叫其超類的 super().__new__(),並在返回之前修改例項。例如:

             
class demoClass:
    instances_created = 0
    def __new__(cls,*args,**kwargs):
        print("__new__():",cls,args,kwargs)
        instance = super().__new__(cls)
        instance.number = cls.instances_created
        cls.instances_created += 1
        return instance
    def __init__(self,attribute):
        print("__init__():",self,attribute)
        self.attribute = attribute
test1 = demoClass("abc")
test2 = demoClass("xyz")
print(test1.number,test1.instances_created)
print(test2.number,test2.instances_created)
               
__new__(): <class '__main__.demoClass'> ('abc',) {}
__init__(): <__main__.demoClass object at 0x0000026FC0DF8080> abc
__new__(): <class '__main__.demoClass'> ('xyz',) {}
__init__(): <__main__.demoClass object at 0x0000026FC0DED358> xyz
0 2
1 2
 

 

__new__() 通常會返回該類的一個例項,但有時也可能會返回其他類的例項,如果發生了這種情況,則會跳過對 __init__() 方法的呼叫。而在某些情況下(比如需要修改不可變類例項(Python 的某些內建型別)的建立行為),利用這一點會事半功倍。比如:

             
class nonZero(int):
    def __new__(cls,value):
        return super().__new__(cls,value) if value != 0 else None#內建函式
    def __init__(self,skipped_value):
        #此例中會跳過此方法
        print("__init__()")
        super().__init__()
print(type(nonZero(-12)))
print(type(nonZero(0)))
 

返回

             
__init__()
<class '__main__.nonZero'>
<class 'NoneType'>