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没有基类。