__slots__和运算符重载中的反向方法
问题的引出
都是字典惹的祸
字典为了提升查询效率,必须用空间换时间。
一般来说一个多想,属性多一点,都存储在字典中便于查询,问题不大。
但是如果数百万个对象,那么字典占的就有点大了。
这个时候,能不能把属性字典__dict__省了?
python提供了__slots__
class A: x = 1 def __init__(self): self.y = 5 self.z = 6 def show(self): print(self.x,self.y,self.z) a = A() print(A.__dict__) print(a.__dict__) 结果为: {'__module__': '__main__', 'x': 1, '__init__': <function A.__init__ at 0x0000000001E670D8>, 'show': <function A.show at 0x0000000001E674C8>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None} {'y': 5, 'z': 6}
思考
上面2个字典,谁的字典是个问题?
实例多达百万个的时候,这么多存放实例属性的字典是个问题。
class A: x = 1 __slots__ = ("y")#元组 #__slots__ = ["y","z"]#可以吗? # __slots__ = "y","z"#可以吗? # __slots__ = "y" def __init__(self): self.y = 5 self.z = 6 def show(self): print(self.x,self.y) a = A() a.show() print("A",A.__dict__) #print("OBJ",a.__dict__) print(a.__slots__) 结果为: Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/studytset/xpc1/test1.py", line 17, in <module> a = A() File "C:/Users/Administrator/PycharmProjects/studytset/xpc1/test1.py", line 11, in __init__ self.z = 6 AttributeError: 'A' object has no attribute 'z'
上面报错,因为slots,只有一个槽位。直接就报错了。
class A: x = 1 __slots__ = ("y")#元组 def __init__(self): self.y = 5 #self.z = 6 def show(self): print(self.x,self.y) a = A() a.show() print("A",A.__dict__) print(a.__dict__) 结果为: 1 5 A {'__module__': '__main__', 'x': 1, '__slots__': 'y', '__init__': <function A.__init__ at 0x0000000001E560D8>, 'show': <function A.show at 0x0000000001E564C8>, 'y': <member 'y' of 'A' objects>, '__doc__': None} Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/studytset/xpc1/test1.py", line 18, in <module> print(a.__dict__) AttributeError: 'A' object has no attribute '__dict__'
上面又报错了。
class A: x = 1 __slots__ = ("y","z")#元组 #__slots__ = ["y","z"]#可以吗? # __slots__ = "y","z"#可以吗? # __slots__ = "y" def __init__(self): self.y = 5 #self.z = 6 def show(self): print(self.x,self.y) a = A() a.show() print("A",A.__dict__) #print("OBJ",a.__dict__) print(a.__slots__) 结果为: 1 5 A {'__module__': '__main__', 'x': 1, '__slots__': ('y', 'z'), '__init__': <function A.__init__ at 0x00000000026770D8>, 'show': <function A.show at 0x00000000026774C8>, 'y': <member 'y' of 'A' objects>, 'z': <member 'z' of 'A' objects>, '__doc__': None} ('y', 'z')
__slots__告诉解释器,实例的属性都叫什么,一般来说,既然要节约内存,最好还是使用元组比较好。
一旦类提供了__slots__,就阻止实例产生__dict__来保存实例的属性。
尝试为实例a动态增加属性
a.newx = 5
说明实例不可以动态增加属性了
A.NEWX = 20,这是可以的,因为这是类属性。
继承
class A: x = 1 __slots__ = ("y","z")#元组 def __init__(self): self.y = 5 #self.z = 6 def show(self): print(self.x,self.y) a = A() a.show() print("A",A.__dict__) print(a.__slots__) class B(A): def __init__(self): self.b1 = 500 print("B",B().__dict__)
结果为:
1 5
A {'__module__': '__main__', 'x': 1, '__slots__': ('y', 'z'), '__init__': <function A.__init__ at 0x0000000001E87E58>, 'show': <function A.show at 0x0000000001E87438>, 'y': <member 'y' of 'A' objects>, 'z': <member 'z' of 'A' objects>, '__doc__': None}
('y', 'z')
B {'b1': 500}
__slots__不影响子类实例,不会继承下去,查费子类里面自己也定义了__slots__。
应用场景
使用需要构建数百万以上对象,且内存容量较为紧张,实例的属性简单,固定且不用动态增加的场景。
未实现和未实现异常
print(type(NotImplemented)) print(type(NotImplementedError)) #<class 'NotImplementedType'> #<class 'type'> raise NotImplemented #raise NotImplementedError 结果为; Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/studytset/xpc1/test1.py", line 7, in <module> raise NotImplemented TypeError: exceptions must derive from BaseException <class 'NotImplementedType'> <class 'type'>
NotImplemented是个值,单值,是NotImplementedType类的实例。
NotImplementedError是类型,是异常,返回type。
运算符重载中的反向方法
前面学习过运算符重载的方法,例如add和iadd.
class A: def __init__(self,x): self.x = x def __add__(self, other):#+ print(self,"add") return self.x+other.x def __iadd__(self, other):#+= print(self,"iadd") return A(self.x + other.x) def __radd__(self, other):#+ print(self,"radd") return self.x + other.x a = A(4) b = A(5) print(a,b) print(a+b) print(b+a) b+=a a+=b 结果为: <__main__.A object at 0x00000000021FA248> <__main__.A object at 0x00000000021FA348> <__main__.A object at 0x00000000021FA248> add 9 <__main__.A object at 0x00000000021FA348> add 9 <__main__.A object at 0x00000000021FA348> iadd <__main__.A object at 0x00000000021FA248> iadd
__radd__方法根本没有执行过?为什么?
因为都是A的实例,都是调用的__add__,无非就是实例a还是实例b调用而已。
测试一下a+1
class A: def __init__(self,x): self.x = x def __add__(self, other): print(self,"add") return self.x+other.x def __iadd__(self, other): print(self,"iadd") return A(self.x + other.x) def __radd__(self, other): print(self,"radd") return self.x + other.x a = A(4) a+1 结果为: <__main__.A object at 0x000000000222B248> add Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/studytset/xpc1/test1.py", line 19, in <module> a+1 File "C:/Users/Administrator/PycharmProjects/studytset/xpc1/test1.py", line 7, in __add__ return self.x+other.x AttributeError: 'int' object has no attribute 'x'
出现了AttributeError,因为1是int类型,没有x这个属性,还是__add__被执行了。
测试1+a,运行结果如下:
<__main__.A object at 0x00000000021EB208> radd Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/studytset/xpc1/test1.py", line 19, in <module> 1+a File "C:/Users/Administrator/PycharmProjects/studytset/xpc1/test1.py", line 15, in __radd__ return self.x + other.x AttributeError: 'int' object has no attribute 'x'
这次执行的是实例a的__radd__方法。
1+a等价于1.__add__(a),而类型实现了__add__方法的,为什么却不抛出异常,而是执行了实例a的__radd__方法?
再看一个例子
class A: def __init__(self,x): self.x = x def __add__(self, other): print(self,"add") return self.x+other.x def __iadd__(self, other): print(self,"iadd") return A(self.x + other.x) def __radd__(self, other): print(self,"radd") return self.x + other.x
#return self+other #相当于self.__add__(other) class B:#未实现__add__ def __init__(self,x): self.x = x a = A(4) b = B(10) print(a+b) print(b+a) 结果为: <__main__.A object at 0x00000000021EB408> add 14 <__main__.A object at 0x00000000021EB408> radd 14
b+a等价于b.__add__(a),但是类B没有实现__add__方法,就去找a的__radd__方法。
1+a等价于1.__add__(a),而int类型实现了__add__方法的,不过这个方法对于这种加法的返回值是notlmplemented,解释器发现是这个值,就会发起对第二操作对象的__radd__方法的调用。
1+a能解决吗?
class A: def __init__(self,x): self.x = x def __add__(self, other): print(self,"add") try: x = other.x return self.x + other.x except AttributeError: try: x = int(other) except: x = 0 return self.x +x def __iadd__(self, other): print(self,"iadd") return A(self.x + other.x) def __radd__(self, other): print(self,"radd") return self.x + other.x class B: def __init__(self,x): self.x = x a = A(4) b = B(10) print(a+b) print(b+a) print(a+2) print(2+a) print(a+"abc") print("abc"+a) 结果为: Traceback (most recent call last): <__main__.A object at 0x00000000021DA408> add File "C:/Users/Administrator/PycharmProjects/studytset/xpc1/test1.py", line 36, in <module> print(2+a) 14 File "C:/Users/Administrator/PycharmProjects/studytset/xpc1/test1.py", line 24, in __radd__ <__main__.A object at 0x00000000021DA408> radd return self.x + other.x 14 AttributeError: 'int' object has no attribute 'x' <__main__.A object at 0x00000000021DA408> add 6 <__main__.A object at 0x00000000021DA408> radd
“abc”+a,字符串也实现了__add__方法,不过默认是处理不了和其他类型的加法,就返回notlmplemented。
class A: def __init__(self,x): self.x = x def __add__(self, other): print(self,"add") try: res = self.x + other.x except: try: o = int(other) except: o = 0 res = self.x+o return res def __iadd__(self, other): print(self,"iadd") return A(self.x + other.x) def __radd__(self, other): print(self,"radd") return self.x + other.x class B: def __init__(self,x): self.x = x a = A(4) b = B(10) print(a+b) print(b+a) print(a+2) print(2+a)