1. 程式人生 > 其它 >C++拷貝建構函式,解構函式與記憶體洩漏的那些坑

C++拷貝建構函式,解構函式與記憶體洩漏的那些坑

  1. 針對 __str__ 方法給出一個比較直觀的例子:

    class people:
        def __init__(self,name,age):
            self.name=name
            self.age=age
    
        def __str__(self):
            return '這個人的名字是%s,已經有%d歲了!'%(self.name,self.age)
    
    a=people('孫悟空',999)
    print(a)

    輸出:

    這個人的名字是孫悟空,已經有999歲了!
    如果沒有過載函式的話輸出的就是一串看不懂的字串:
    <__main__.people object at 0x00000272A730D278>
  2. 最新的 Python3.7 中(2018.07.13),對類的建構函式進行了精簡。

    3.7 版本:

    from dataclasses import dataclass
    @dataclass
    class A:
      x:int
      y:int
      def add(self):
        return self.x + self.y

    相當於以前的:

    class A:
      def __init__(self,x,y):
        self.x = x
        self.y = y
      def add(self):
        return self.x + self.y
  3. Python3 中類的靜態方法、普通方法、類方法

    靜態方法: 用 @staticmethod 裝飾的不帶 self 引數的方法叫做靜態方法,類的靜態方法可以沒有引數,可以直接使用類名呼叫。

    普通方法: 預設有個self引數,且只能被物件呼叫。

    類方法: 預設有個 cls 引數,可以被類和物件呼叫,需要加上 @classmethod 裝飾器。

    class Classname:
        @staticmethod
        def fun():
            print('靜態方法')
    
        @classmethod
        def a(cls):
            print('類方法')
    
        # 普通方法
        def b(self):
            print('普通方法')
    
    
    
    Classname.fun()
    Classname.a()
    
    C = Classname()
    C.fun()
    C.a()
    C.b()
  4. 反向運算子過載:

    • __radd__:加運算
    • __rsub__:減運算
    • __rmul__:乘運算
    • __rdiv__:除運算
    • __rmod__:求餘運算
    • __rpow__:乘方

    複合過載運算子:

    • __iadd__:加運算
    • __isub__:減運算
    • __imul__:乘運算
    • __idiv__:除運算
    • __imod__:求餘運算
    • __ipow__:乘方

    運算子過載的時候:

    #!/usr/bin/python3
    
    class Vector:
    def __init__(self, a, b):
    self.a = a
    self.b = b
    def __str__(self):
    return 'Vector (%d, %d)' % (self.a, self.b)
    
    def __repr__(self):
    return 'Vector (%d, %d)' % (self.a, self.b)
    
    def __add__(self,other):
    if other.__class__ is Vector:
    return Vector(self.a + other.a, self.b + other.b)
    elif other.__class__ is int:
    return Vector(self.a+other,self.b)
    
    def __radd__(self,other):
    """反向算術運算子的過載
    __add__運算子過載可以保證V+int的情況下不會報錯,但是反過來int+V就會報錯,通過反向運算子過載可以解決此問題
    """
    
    if other.__class__ is int or other.__class__ is float:
    return Vector(self.a+other,self.b)
    else:
    raise ValueError("值錯誤")
    
    def __iadd__(self,other):
    """複合賦值算數運算子的過載
    主要用於列表,例如L1+=L2,預設情況下呼叫__add__,會生成一個新的列表,
    當資料過大的時候會影響效率,而此函式可以過載+=,使L2直接增加到L1後面
    """
    
    if other.__class__ is Vector:
    return Vector(self.a + other.a, self.b + other.b)
    elif other.__class__ is int:
    return Vector(self.a+other,self.b)
    v1 = Vector(2,10)
    v2 = Vector(5,-2)
    print (v1 + v2)
    print (v1+5)
    print (6+v2)
  5. 關於 __name__

    首先需要了解 __name__ 是屬於 python 中的內建類屬性,就是它會天生就存在於一個 python 程式中,代表對應程式名稱。

    比如所示的一段程式碼裡面(這個指令碼命名為 pcRequests.py),我只設了一個函式,但是並沒有地方執行它,所以當 run 了這一段程式碼之後我們有會發現這個函式並沒有被呼叫。但是當我們在執行這個程式碼時這個程式碼的 __name__ 的值為 __main__ (一段程式作為主線執行程式時其內建名稱就是 __main__)。

    import requests
    class requests(object):
        def __init__(self,url):
            self.url=url
            self.result=self.getHTMLText(self.url)
        def getHTMLText(url):
            try:
                r=requests.get(url,timeout=30)
                r.raise_for_status()
                r.encoding=r.apparent_encoding
                return r.text
            except:
                return "This is a error."
    print(__name__)

    結果:

    __main__
    Process finished with exit code 0

    當這個 pcRequests.py 作為模組被呼叫時,則它的 __name__ 就是它自己的名字:

    import pcRequestspcRequestsc=pcRequestsc.__name__

    結果:

    'pcRequests'

    看到這裡應該能明白,自己的 __name__ 在自己用時就是 main,當自己作為模組被呼叫時就是自己的名字,就相當於:我管自己叫我自己,但是在朋友眼裡我就是小仙女一樣

  6. Python3 類方法總結

    • 普通方法:物件訪問
    • 私有方法:兩個下劃線開頭,只能在類內部訪問
    • 靜態方法:類和物件訪問,不能和其他方法重名,不然會相互覆蓋,後面定義的會覆蓋前面的
    • 類方法:類和物件訪問,不能和其他方法重名,不然會相互覆蓋,後面定義的會覆蓋前面的
    • 多繼承情況下:從左到右查詢方法,找到為止,不然就丟擲異常
    class People:
    
        # 定義基本屬性
        name=''
        age=0
        # 定義私有屬性外部無法直接訪問
        __weight=0
        def __init__(self,n,a,w):
            self.name = n
            self.age = a
            self.__weight = w
        def speak(self):
            print("%s say : i am %d."%(self.name,self.age))
    p = People('Python',10,20)
    p.speak()
    # __weight無法直接訪問
    print(p.name,'--',p.age)#,'--',p.__weight)

    繼承

    單繼承:

    class Student(People):
        grade=''
        def __init__(self,n,a,w,g):
            People.__init__(self,n,a,w)
            self.grade = g
    
        # 覆寫父類方法
        def speak():
            print("%s 說: 我 %d 歲了,我在讀 %d 年級"%(self.name,self.age,self.grade))
    
    class Speak():
        topic=''
        name=''
        def __init__(self,n,t):
            self.name = n
            self.topic = t
        # 普通方法,物件呼叫
        def speak(self):
            print("我叫 %s,我是一個演說家,我演講的主題是 %s"%(self.name,self.topic))
    
        # 私有方法,self呼叫
        def __song(self):
            print('唱一首歌自己聽',self);
    
        # 靜態方法,物件和類呼叫,不能和其他方法重名,不然會相互覆蓋,後面定義的會覆蓋前面的
        @staticmethod
        def song():
            print('唱一首歌給類聽:靜態方法');
    
        # 普通方法,物件呼叫
        def song(self):
            print('唱一首歌給你們聽',self);
            
        # 類方法,物件和類呼叫,不能和其他方法重名,不然會相互覆蓋,後面定義的會覆蓋前面的
        @classmethod
        def song(self):
            print('唱一首歌給類聽:類方法',self)

    多繼承:

    class Sample(Speak,Student):
        a = ''
        def __init__(self,n,a,w,g,t):
            Student.__init__(self,n,a,w,g)
            Speak.__init__(self,n,t)
    test = Sample('Song',24,56,7,'Python')
    test.speak()
    test.song()
    Sample.song()
    Sample.song()
    test.song()
    
    # test.__song() 無法訪問私有方法
  7. 所有專有方法中,__init__()要求無返回值,或者返回 None。而其他方法,如__str__()、__add__()等,一般都是要返回值的,如下所示:

    >>> class Complex:
    ...     def __init__(self, realpart, imagpart):
    ...         self.r = realpart
    ...         self.i = imagpart
    ...         return 'hello'
    ...
    >>> x = Complex(3.0, -4.5)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: __init__() should return None, not 'str'

    而對於 __str__()、__add__() 等。

    def __str__(self):
            return 'Vector (%d, %d)' % (self.a, self.b)
    
    def __repr__(self):
        return 'Vector (%d, %d)' % (self.a, self.b)
    
    def __add__(self,other):
        if other.__class__ is Vector:
            return Vector(self.a + other.a, self.b + other.b)
        elif other.__class__ is int:
            return Vector(self.a+other,self.b)
  8. 類的專有方法中,也是存在預設優先順序的,多個方法都有返回值,但一般優先取 __str__() 的返回值,如下面例子:

    類的專有方法中,也是存在預設優先順序的,多個方法都有返回值,但一般優先取 __str__() 的返回值,如下面例子:
    class Vector:
        def __init__(self, a, b):
            self.a = a
            self.b = b
        def __repr__(self):
            return 'Vector (%d, %d)' % (self.b, self.a)
        def __str__(self):
            return 'Vector (%d, %d)' % (self.a, self.b)
        def __add__(self,other):
            return Vector(self.a + other.a, self.b + other.b)
    
    v1 = Vector(2,10)
    print (v1)

    結果是 Vector(2,10),而不是 Vector(10,2)。這裡優先使用 __str__() 的返回值。

    v1.__repr__()

    結果是:Vector(10,2)

  9. 筆記中有位同學如下這樣寫道,但我認為不準確:

    最新的 Python3.7 中(2018.07.13),對類的建構函式進行了精簡。

    3.7 版本:

    from dataclasses import dataclass
    @dataclass
    class A:
      x:int
      y:int
      def add(self):
        return self.x + self.y

    相當於以前的:

    class B:
      def __init__(self,x,y):
        self.x = x
        self.y = y
      def add(self):
        return self.x + self.y

    實際上,對於類 A, 例項化時不需要引數;而對於類 B,例項化時需要輸入 (x, y) 引數,這才是兩者的核心區別。定義類時,若需要輸入引數,則一般必須使用 __init__()方法;若不需要輸入引數,是否使用 __init__() 方法都可以。

    和版本是否對類的建構函式進行了精簡,關係不大。

  10. 在 Python 中,方法分為三類例項方法、類方法、靜態方法。三者區別看程式碼如下:

    class Test(object):
        def InstanceFun(self):
            print("InstanceFun");
            print(self);
        @classmethod
        def ClassFun(cls):
            print("ClassFun");
            print(cls);
        @staticmethod
        def StaticFun():
            print("StaticFun");
    
    t = Test();     
    t.InstanceFun();   # 輸出InstanceFun,列印物件記憶體地址“<__main__.Test object at 0x0293DCF0>”
    Test.ClassFun();     # 輸出ClassFun,列印類位置 <class '__main__.Test'>
    Test.StaticFun();    # 輸出StaticFun
    t.StaticFun();       # 輸出StaticFun
    t.ClassFun();        # 輸出ClassFun,列印類位置 <class '__main__.Test'>
    Test.InstanceFun();     # 錯誤,TypeError: unbound method instanceFun() must be called with Test instance as first argument
    Test.InstanceFun(t);    # 輸出InstanceFun,列印物件記憶體地址“<__main__.Test object at 0x0293DCF0>”
    t.ClassFun(Test);       # 錯誤   classFun() takes exactly 1 argument (2 given)   

    可以看到,在 Python 中,兩種方法的主要區別在於引數。例項方法隱含的引數為類例項 self,而類方法隱含的引數為類本身 cls。

    靜態方法無隱含引數,主要為了類例項也可以直接呼叫靜態方法。

    所以邏輯上類方法應當只被類呼叫,例項方法例項呼叫,靜態方法兩者都能呼叫。主要區別在於引數傳遞上的區別,例項方法悄悄傳遞的是self引用作為引數,而類方法悄悄傳遞的是 cls 引用作為引數。

    Python 實現了一定的靈活性使得類方法和靜態方法,都能夠被例項和類二者呼叫。

  11. 類的二元方法運算子過載介紹的並不全面,文中介紹的全是正向方法,其實還有反向方法,就地方法。下面補充一些。

    當直譯器碰到 a+b 時,會做以下事情:

    從 a 類中找 __add__ 若返回值不是 NotImplemented, 則呼叫 a.__add__(b)

    若 a 類中沒有 __add__ 方法,則檢查 b 有沒有 __radd__ 。如果如果有,則呼叫 b.__radd__(a),若沒有,則返回 NotImplemented。

    接上條,若 b 也沒有 __radd__ 方法,則丟擲 TypeError,在錯誤訊息中知名運算元型別不支援。

    比如:向量類 <Myvector> 應當有向量與整數的乘法:

    >>>a = Myvector([1,2,3])
    >>>print(a.value)
    [1,2,3]
    >>>b=3
    >>>c = a*b   #此時呼叫Myvector.__mul__()
    >>>print(c.value)
    [3,6,9]
    >>> d=b*a  #這句會出錯。

    期望得到 b*a 也返回一個向量,b*a 應該等於 a*b。此時就需要在 Myvector 類中定義一個__rmul__方法。

    def __rmul__(self, other):
        if isinstance(other, int):
            return Myvector([a*other for a in self.m])

    每個運算子都有正向方法過載,反向方法過載。有一些有就地方法(即不返回新的物件,而是修改原本物件)。

  12. __str__函式

    __str__ 是一個類的方法,在列印類物件,獲取其屬性資訊時呼叫。列印一個例項化物件時,預設列印的其實時一個物件的地址,但是我們可以對其進行過載,列印我們想要的資訊。例如上面的例子中進行的過載。

  13. 在類的方法中直接修改 self 是無效操作,即使 self 變數的地址與例項地址相同:

    class C:
      def __init__(self, a):
        self.a = a
      def construct(self, a):
        c = C(a)
        self = c
      def getid(self):
        return id(self)
    
    if __name__ == '__main__':
      c1 = C(2)
      c1.construct(3) # c1.a == 2
      print(id(c1) == c1.getid()) # True
  14. 事實上 class 的私有屬性在外部也是可以訪問的我們可以看下文中的例子。

    #!/usr/bin/python3
    class People:
        def __init__(self, name, age, ):
            self.name = name
            self.age = age
            self.__privater_var = 10  
      
        def intro(self):
            print(f'My name is {self.name},I\'m {self.age}')
    
        def get_var(self):
            print(self.__privater_var)
    
        def set_var(self, var):
            self.__privater_var = var
    
    someone = People(name='jack', age=20)
    someone.intro()
    print(someone.age)
    someone.get_var() # 通過get_var方法訪問私有屬性__privater_var,值為10
    someone.set_var(30) # 通過set_var方法修改私有屬性__privater_var,值為30
    someone.get_var() # 再次通過get_var方法訪問私有屬性__privater_var,值為30

    結果:

    My name is jack,I'm 20
    20
    10
    30

    接下下來看看為什麼我們使用someone.__privater_var會報錯呢?

    AttributeError: 'People' object has no attribute '__privater_var'

    這裡我們先使用 dir() 函式:

    print(dir(someone)) #  獲得當前someone的屬性列表

    結果:

    ['_People__privater_var', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'get_var', 'intro', 'name', 'set_var']

    從打印出的結果中,我們並沒有找到'_peivater_var'但是我們看到一個'_People__privater_var'.有沒有想到什麼?原來是被重新命名了。好,我們來試試:

    print(someone._People__privater_var)
    someone._People__privater_var = 40
    print(someone._People__privater_var)

    結果:

    30
    40

    所以說,私有變數的屬性是可以修改的。既然Python阻止訪問,一般情況下就不要訪問。

    作為一個從 Java 過來的人來說,Python 的作用域有點奇葩,作為開發者,只需要關注全域性作用域和區域性作用域就好了,全域性作用域就是說白了模組(單個 .py 檔案中)直接宣告的變數。

    比如有個 demo.py 的檔案,含有以下程式碼:

    var_global='global_var'#這個var_global就是全域性作用域
    def globalFunc():
      var_local='local_var' #這個就是區域性變數
    class demo():
      class_demo_local_var='class member' #這裡也是區域性變數
      def localFunc(self):
        var_locals='local_func_var'#這裡也是區域性變數

    以上只是說明了全域性變數僅僅是在 .py 檔案中直接宣告的變數叫全域性變數,還有在 .py 檔案中直接寫的邏輯程式碼塊中,也是全域性變數。也就是說在 if/elif/else/、try/except、for/while 等邏輯程式碼塊中的變數。

    這個教程中在介紹三種命令空間的時候,說查詢變數的順序為區域性的名稱空間去 -> 全域性名稱空間 -> 內建名稱空間,但是我理解的變數查詢順序為:當前域 -> 外部域(如果有) -> 全域性域 -> 內建域。

    光說沒有什麼概念,我們來一段程式碼就清楚了。

    我們以 demo1.py 為例子:

    global_var='this  var  on  global space' 
    '''
    申明global_var這個位置就是全域性域,也就是教程中所說的全域性作用域,
    同時它也是直接宣告在檔案中的,而不是宣告在函式中或者類中的變數
    '''
    class demo():
      class_demo_local_var='class member' 
      '''
      雖然class_demo_local_var在這裡是區域性變數,這個區域性變數的域相對於var_locals是外部域,
      所以可以直接被var_locals所在的更小的區域性域訪問
      '''
      def localFunc(self):
        var_locals='local_func_var'
        '''
        這裡也是區域性變數,但是相對於class_demo_local_var變數,卻是更小的域,
        因此class_demo_local_var所在的哪個域無法訪問到當前域來
        '''
        print(self.class_demo_local_var)#到這裡會查詢當前域中有沒有class_demo_local_var這個變數,然後再到相對於當前域的外部域去查詢變數

    教程中寫到 Python 中變數的查詢順序:“在區域性找不到,便會去區域性外的區域性找(例如閉包),再找不到就會去全域性找,再去內建中找。可以看一個具體的例子。

    Python 的一個內建值 int,我們首先將其賦值為 0,然後定義一個函式 fun1()。

    >>> int = 0
    >>> def fun1():
    int = 1
    def fun2():
    int = 2
    print(int)
    fun2()

    函式 fun1() 的作用就是呼叫函式 fun2() 來列印 int 的值。

    呼叫函式 fun1():

    >>> fun1()
    2

    因為 local 中的 int = 2,函式將其打印出來。

    將函式 fun2() 中的 int = 2 刪除:

    >>> int = 0
    >>> def fun1():
    int = 1
    def fun2():
    print(int)
    fun2()

    呼叫函式 fun1():

    >>> fun1()
    1

    因為 local 找不到 int 的值,就去上一層 non-local 尋找,發現 int = 1 並列印。

    而進一步刪除函式 fun1() 中的 int = 1:

    >>> int = 0
    >>> def fun1():
    def fun2():
    print(int)
    fun2()

    呼叫函式 fun1():

    >>> fun1()
    0

    因為 local 和 non-local 都找不到 int 的值,便去 global 中尋找,發現 int = 0 並列印。

    若刪除 int = 0這一條件:

    >>>del int
    >>>def fun1():
            def fun2():
    print(int)
    fun2()

    呼叫函式 fun1():

    >>> fun1()
    <class 'int'>

    因為 local、non-local、global 中都沒有 int 的值,便去 built-in 中尋找 int 的值,即:

    >>> int
    <class 'int'>