面向對象的補充(slots、tracemalloc、運算符重載中的反向方法)

1.1 tracemalloc

標準庫tracemalloc,可以統計內存使用情況。

import tracemalloc


tracemalloc.start()
d = [dict(zip('xy', (4, 5)))for i in range(1000000)]  # 237M
t = [tuple(zip('xy', (4, 5)))for j in range(1000000)]  # 191M
snapshot = tracemalloc.take_snapshot()  # 快照
# stats = snapshot.statistics('lineno')
stats = snapshot.statistics('filename')
for s in stats:
    print(s)

1.2 slots

問題的引出:
字典爲了提高查詢效率,必須用空間換時間。一般來說一個實例,屬性多一點,都存儲在字典中便於查詢,問題不大。但是如果數百萬個實例,那麼字典佔的總空間就有點大了。
這個時候能不能把屬性字典__dict__省了。python提供了__slots__方法。

class A:
    X = 1
    # __slots__ = 'x', 'y'
    # __slots__ = ('x', 'y')
    __slots__ = ['x', 'y']

    def __init__(self):
        self.x = 5
        self.y = 6

    def show(self):
        print(self.x, self.y)


a = A()
a.show()
print('A', A.__dict__)
print(a.__slots__)  # ('x', 'y')
# a.new = 10  # AttributeError
A.new = 10  # 可以動態的增加類色屬性
print(A.__dict__)

  slots__告訴解釋器,實例的屬性都叫什麼,一般來說,既然要節約內存,最好還是使用元組比較好。==一旦類提供了__slots,就阻止實例產生__dict__來保存實例的屬性,也不能再動態增加實例的屬性了。==
  嘗試爲實例a動態增加屬性,a.new = 5,直接拋出AttributeError異常,說明實例的屬性不能動態的增加了,A.new = 100,這是可以的,因爲這是類的屬性。
slots__不影響子類實例,不會繼承下去,除非子類中也定義了__slots

1.2.1 應用場景

  使用需要構建在數百萬以上衆多對象,且內存容量較爲緊張,實例的屬性簡單、固定且不用動態增加的場景。可以使用tracemalloc看看內存使用的差異。建議使用stats = snapshot(‘filename’)查看總內存使用。

import tracemalloc


tracemalloc.start()


class A:
    __slots__ = 'y', 'x'  # 阻止了實例字典的創建,無法阻止類屬性的修改,沒有繼承
    # __slots__ = 'x y'.split()

    def __init__(self):
        self.x = 5
        self.y = 6


print(A.__dict__)
# print(A().__dict__)
# print(A().x, A().y)
a = A()
# a.z = 100  # 會報錯,__slots__阻止你對實例屬性的修改
A.t = 2000
print(A.__dict__)
# a.y = 200
# print(a.y)  # AttributeError: y
d = [A() for i in range(1000000)]
s = tracemalloc.take_snapshot()
for x in s.statistics('filename'):
    print(x)  # 61.7M,有__slots__方法
    # 無__slots__方法時,佔用空間爲169M

1.3 未實現和未實現異常

print(type(NotImplementedError))  # <class 'type'> NotImplementedError爲異常類
# <class 'type'>
print(type(None))  # <class 'NoneType'>
print(type(NotImplemented))  # <class 'NotImplementedType'>未實現單值
# <class 'NotImplementedType'>

NotImplemented是個值,單值,是NotImplementedType的實例。
NotImplementedError是類型,是異常類,返回type

1.4 運算符重載中的反向方法

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

    def __repr__(self):
        return "<A {}>".FORAMT(self.x)

    def __add__(self, other):
        print('add~~~~~~')
        if hasattr(other, 'x'):
            return self.x + other.x
        else:
            try:
                x = int(other)
            except:
                x = 0
            return self.x + x

    def __iadd__(self, other):
        print('iadd~~~~~~~')

        return A(self + other)

    def __radd__(self, other):
        print('radd~~~~~~~')

        return self + other  # 運算符重載,解釋器看到+會調用__add__方法
#
#
# a1 = A(4)
# a2 = A(5)
# # print(a1 + a2)  # 9 int——> a1.__add__(a2)
# # print(a2 + a1)
# print(a1 + 1)  # 會報錯 a1.__add__(1),而1沒有x屬性
# print(1 + a1)  # 會報錯 1.__add__(a1)  int.__add__(1, a1)
# print(a1 + A(10))  # a1.__add__(A(10))


class B:
    def __init__(self, x):
        self.x = x


b1 = B(6)
print(a1 + b1)  # add~~~~~~ 10, a1.__add__(b1)
print(b1 + a1)  # radd~~~~~~~10, a1.__radd__(b1);b1沒有add方法,或者有這個方法,但是返回值爲NotImplemented,就會調用後面的反向方法

  b1 + a1等價於b1.add(a1),但是類B沒有實現__add__方法,就去找a1的__radd__方法,1 + a1等價於1.add(a1),而int類型實現了__add__方法,不過這個方法對於這種加法的返回值是NitImplemented(未實現),解釋器發現是這個值,就會發起對第二操作對象的__radd__方法的調用。

1.5 Python的對象模型

在python中,任何對象都有類型,可以使用type()或者__class__查看。
但是類型也是對象即類對象,它也有自己的類型。
對象模型

所有新類型的缺省類型都是type(可以使用元類來改變)
特殊類型type是所有對象的缺省類型,也包括type自己,但它又是一個對象,因此從object繼承特殊類型object是繼承樹的頂層,它是python所有類型的最終基類。
也就是說,繼承都來自object,類型都看type。type也是對象繼承自object,object也有類型是type。這兩又特殊,type類型是自己,object沒有基類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章