1 派生内置不可变类型并需改
1 需求
自定义新的类型元组,对于传入得可迭代对象,保留其中得int类型且大于0的元素,例如:(-1, 1, 2, ‘yifan’, 3, [3,3], (22))—》(1, 2, 3, 22)
如何继承tuple实现呢 ?
2 基础
初次尝试
class IntTuple(tuple):
def __init__(self, iterable):
f_it = (e for e in iterable if isinstance(e, int) and e > 0)
super().__init__(f_it)
intT = IntTuple((-1, 1, 2, 'yifan', 3, [3,3], (22)))
intT
TypeError: object.init() takes exactly one argument (the instance to initialize)
class IntTuple(tuple):
def __init__(self, iterable):
print(self)
print(iterable)
# f_it = (e for e in iterable if isinstance(e, int) and e > 0)
# super().__init__(f_it)
intT = IntTuple((-1, 1, 2, 'yifan', 3, [3,3], (22)))
(-1, 1, 2, ‘yifan’, 3, [3, 3], 22)
(-1, 1, 2, ‘yifan’, 3, [3, 3], 22)
结果如上,表明数据传给了self和iterable
知识
class A:
def __new__(cls, *args):
print('test:in A.__new__', cls, args)
return object.__new__(cls)
def __init__(self, *args):
print('In A.__init__', args)
a = A(1,3)
# test:in A.__new__ <class '__main__.A'> (1, 3)
# In A.__init__ (1, 3)
a = A.__new__(A, 1, 3)
A.__init__(a, 1, 3)
# test:in A.__new__ <class '__main__.A'> (1, 3)
# In A.__init__ (1, 3)
类对象是先调用new方法,再调用init
# 测试
list('abc') #['a', 'b', 'c']
l = list.__new__(list, 'abc')
l # []
list.__init__(l, 'abc')
l #['a', 'b', 'c']
list也可以看作对象,调用效果等价如上,但是tuple也是如此,但是生成有别,在new已经生成了数据,虽然调用了init,但是tuple没有实现,只是单单继承了object的init。而list则不一样。
tuple('abc') #('a', 'b', 'c')
t = tuple.__new__(tuple, 'abc')
t #('a', 'b', 'c')
tuple.__init__ is object.__init__ #True
list.__init__ is object.__init__ #False
3 方案
继承tuple类,修改对应的 new
class IntTuple(tuple):
def __new__(cls, iterable):
print(cls)
print(iterable)
f_it = (e for e in iterable if isinstance(e, int) and e > 0)
return super().__new__(cls,f_it)
intT = IntTuple((-1, 1, 2, 'yifan', 3, [3,3], (22)))
intT
#<class '__main__.IntTuple'>
#(-1, 1, 2, 'yifan', 3, [3, 3], 22)
#(1, 2, 3, 22)
2 减少内存使用方法
1 使用场景
2 基础知识
原始比压缩后的少 ‘dict‘, ‘weakref‘,其中dict占用内存很大。
class Player1:
def __init__(self, uid, name, level):
self.uid = uid
self.name = name
self.level = level
class Player2:
__slots__ = ['uid', 'name', 'level']
def __init__(self, uid, name, level):
self.uid = uid
self.name = name
self.level = level
p1 = Player1('0001', 'Jim', 20)
p2 = Player2('0001', 'Jim', 20)
# 原始比压缩后的少 '__dict__', '__weakref__',其中__dict__占用内存很大
set(dir(p1)) - set(dir(p2)) #{'__dict__', '__weakref__'}
p1.__dict__ #{'uid': '0001', 'name': 'Jim', 'level': 20}
p1.x = 100
p1.__dict__ #{'uid': '0001', 'name': 'Jim', 'level': 20, 'x': 100}
p2.__dict__ #AttributeError: 'Player2' object has no attribute '__dict__'
查看dict的大小
import sys
sys.getsizeof(p1.__dict__) #152
sys.getsizeof(p1.name) #52
sys.getsizeof(p1.level) #28
sys.getsizeof(p1.uid) #53
3 比较大小
import tracemalloc
tracemalloc.start()
#start
la = [Player1(1,2,3) for _ in range(100000)]
#lb = [Player2(1,2,3) for _ in range(100000)]
#end
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('filename')
for stat in top_stats[:4]:
print(stat)
结果
import tracemalloc
tracemalloc.start()
#start
#la = [Player1(1,2,3) for _ in range(100000)]
lb = [Player2(1,2,3) for _ in range(100000)]
#end
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('filename')
for stat in top_stats[:4]:
print(stat)
结果
3 创建可管理对象属性
1 使用案例
面向对象编程中,我们把函数作为对象的接口,直接访问对象属性可能不安全或者不灵活,但是调用上不如访问属性简洁。
circle.get_radius()
circle.set_radius(3.3) #繁
circle.radius
circle.radius = 3.3 # 简单
能否在形式上是属性访问,但实际内部调用方法?
2 实验
import math
class Circle:
def __init__(self, radius):
self.radius = radius
def get_area(self):
return self.radius**2 * math.pi
def set_area(self, S):
self.radius = math.sqrt(S / math.pi)
#正常
c = Circle(8.823)
d = c.radius*2 #17.646
#异常
c.radius = '31.93'
d = c.radius*2
d # '31.9331.93'
#没有报错,但是还出现了问题,这种情况更糟糕
异常情况没有报错,设置处理异常
import math
class Circle:
def __init__(self, radius):
self.radius = radius
def set_radius(self, radius):
if not isinstance(radius, (int, float)):
raise TypeError('wrong type')
self.radius = radius
def get_area(self):
return self.radius**2 * math.pi
def set_area(self, S):
self.radius = math.sqrt(S / math.pi)
c = Circle(8.823)
c.set_radius('31.99')
d = c.radius*2 # TypeError: wrong type
c = Circle(8.823)
c.set_radius(31.99)
d = c.radius*2
d #63.98
使用property函数为类创建可管理的属性,fget/fset/fdel对应相关属性访问。
import math
class Circle:
def __init__(self, radius):
self.radius = radius
def get_radius(self):
return round(self.radius, 2)
def set_radius(self, radius):
if not isinstance(radius, (int, float)):
raise TypeError('wrong type')
self.radius = radius
def get_area(self):
return self.radius**2 * math.pi
def set_area(self, S):
self.radius = math.sqrt(S / math.pi)
R = property(get_radius, set_radi
c = Circle(8.823)
c.R = '11.2323' #TypeError: wrong type
c = Circle(8.823)
c.R = 11.2323
c.R #11.23
c.R #11.23
再设置S属性
class Circle:
def __init__(self, radius):
self.radius = radius
def get_radius(self):
return round(self.radius, 2)
def set_radius(self, radius):
if not isinstance(radius, (int, float)):
raise TypeError('wrong type')
self.radius = radius
@property
def S(self):
return self.radius**2 * math.pi
@S.setter
def S(self, s):
self.radius = math.sqrt(s / math.pi)
R = property(get_radius, set_radius)
c = Circle(8.823)
c.S = 12.22
c.R #1.97
可以便捷地设置和取到想要的值。内部实现是函数。
细致介绍可见https://zhuanlan.zhihu.com/p/67914416
4 类做比较
1 实际案例
我们实际比较中,想直接使用符号比较类的某些行为,比如比较矩形的面积等
2 实验
基础知识
a, b = 5, 3
a.__lt__(b) #False
a.__ge__(b) #True
s1, s2 = 'abc', 'abd'
s1 > s2 #False
s1.__gt__(s2) #False
{1, 2, 3} > {4} #False
{1, 2, 3} == {4} #False
{1, 2, 3} < {4} #False
{1, 2, 3} > {1, 2} #True
不同的数据类型比较,各有不同,字符串逐个比较,根据对应的ascis码;集合比较的是包含关系,不是数的大小。
类比较
class Rect:
def __init__(self, w, h):
self.w = w
self.h = h
def area(self):
return self.w * self.h
def __str__(self):
return 'Rect:(%s, %s)' %(self.w, self.h)
def __lt__(self, obj):
return self.area() < obj.area()
rect1 = Rect(6, 9)
rect2 = Rect(9, 7)
rect1 < rect2 #True
rect1 > rect2 #False 通过__lt__(self, obj)转换而来
rect1 >= rect2 #TypeError: '>=' not supported between instances of 'Rect' and 'Rect'
为了比较不同的组合,需要加载导入total_ordering,使用装饰器修饰类。如下所示:
from functools import total_ordering
@total_ordering
class Rect:
def __init__(self, w, h):
self.w = w
self.h = h
def area(self):
return self.w * self.h
def __str__(self):
return 'Rect:(%s, %s)' %(self.w, self.h)
def __lt__(self, obj):
return self.area() < obj.area()
def __eq__(self, obj):
return self.area() == obj.area()
rect1 = Rect(6, 9)
rect2 = Rect(9, 7)
rect1 >= rect2 #False
5 类的类型检查
基础
一般来说,描述符是带有“绑定行为”的对象属性,它的属性访问已经被描述符协议中的方法覆盖了.这些方法是get(),set(),和delete().
如果一个对象定义了这些方法中的任何一个,它就是一个描述符.默认的属相访问是从对象的字典中 get, set, 或者 delete 属性,;例如a.x的查找顺序是:a.x -> a.dict[‘x’] -> type(a).dict[‘x’] -> type(a)的基类(不包括元类),如果查找的值是对象定义的描述方法之一,python可能会调用描述符方法来重载默认行为, 发生在这个查找环节的哪里取决于定义了哪些描述符方法
注意,只有在新式类中描述符才会起作用(新式类继承type或者object class)
描述符是强有力的通用协议,属性、方法、静态方法、类方法和super()背后使用的就是这个机制,描述符简化了底层的c代码,并为Python编程提供了一组灵活的新工具
描述符协议
descr.__get__(self, obj, type=None) -> value
descr.__set__(self, obj, value) -> None
descr.__delete__(self, obj) -> None
定义任何上面三个方法的任意一个,这个对象就会被认为是一个描述符,并且可以在被作为对象属性时重载默认的行为, 如果一个对象定义了get() 和 set(),它被认为是一个数据描述符.只定义 get()被认为是非数据描述符,数据和非数据描述符的区别在于:如果一个实例的字典有和数据描述符同名的属性,那么数据描述符会被优先使用,如果一个实例的字典实现了无数据描述符的定义,那么这个字典中的属性会被优先使用,实现只读数据描述符,同时定义get()和set(),在set()中抛出AttributeError.
class Descriptor:
def __set__(self, instance, value):
print(f'in set, self: {self}, instance: {instance}, value:{value}')
instance.__dict__['xxx'] = value
def __get__(self, instance, cls):
print(f'in get: instance: {instance}, cls:{cls}')
return instance.__dict__['xxx']
def __delete__(self, instance):
print(f'in del, instance:{instance}')
del instance.__dict__['xxx']
class A:
x = Descriptor()
a = A()
a.x = 5 # in set, self: <__main__.Descriptor object at 0x000001C01216CC10>, instance: <__main__.A object at 0x000001C0112759D0>, value:5
del a.x #in del, instance:<__main__.A object at 0x000001C012758D60>
A.x #in get: instance: None, cls:<class '__main__.A'> 不用管这个异常AttributeError: 'NoneType' object has no attribute '__dict__'
若A变成下面这样,将会发生冲突
# 若A变成下面这样,将会发生冲突
class A:
x = Descriptor()
y = Descriptor()
可以考虑变化:
class Descriptor:
def __init__(self, key):
self.key = key
def __set__(self, instance, value):
print(f'in set, self: {self}, instance: {instance}, value:{value}')
instance.__dict__[self.key] = value
def __get__(self, instance, cls):
print(f'in get: instance: {instance}, cls:{cls}')
return instance.__dict__[self.key]
def __delete__(self, instance):
print(f'in del, instance:{instance}')
del instance.__dict__[self.key]
class A:
x = Descriptor('a.x')
y = Descriptor('a.y')
a = A()
a.x = 5
a.y = 12
print(a.x, a.y)
# in set, self: <__main__.Descriptor object at 0x000001C01216C220>, instance: <__main__.A object at 0x000001C0123C2520>, value:5
# in set, self: <__main__.Descriptor object at 0x000001C01216CC10>, instance: <__main__.A object at 0x000001C0123C2520>, value:12
# in get: instance: <__main__.A object at 0x000001C0123C2520>, cls:<class '__main__.A'>
# in get: instance: <__main__.A object at 0x000001C0123C2520>, cls:<class '__main__.A'>
# 5 12
2 案例
#案例
class Attr:
def __init__(self, key, type_):
self.key = key
self.type_ = type_
def __set__(self, instance, value):
print(f'in set, self: {self}, instance: {instance}, value:{value}')
if not isinstance(value, self.type_):
raise TypeError(f'must be {self.type_}')
instance.__dict__[self.key] = value
def __get__(self, instance, cls):
print(f'in get: instance: {instance}, cls:{cls}')
return instance.__dict__[self.key]
def __delete__(self, instance):
print(f'in del, instance:{instance}')
del instance.__dict__[self.key]
class Person:
name = Attr('name', str)
age = Attr('age', int)
p = Person()
p.name = 'yifan'
p.age = 18
p.age
# in set, self: <__main__.Attr object at 0x000001C0126EFB80>, instance: <__main__.Person object at 0x000001C0123B7FA0>, value:yifan
# in set, self: <__main__.Attr object at 0x000001C0126EFAF0>, instance: <__main__.Person object at 0x000001C0123B7FA0>, value:18
p.age
# in get: instance: <__main__.Person object at 0x000001C0123B7FA0>, cls:<class '__main__.Person'>
p = Person()
p.name = 'yifan'
p.age = '18'
# in set, self: <__main__.Attr object at 0x000001C0126EFB80>, instance: <__main__.Person object at 0x000001C0123B7DF0>, value:yifan
# in set, self: <__main__.Attr object at 0x000001C0126EFAF0>, instance: <__main__.Person object at 0x000001C0123B7DF0>, value:18
# TypeError: must be <class 'int'>
p.age
# in get: instance: <__main__.Person object at 0x000001C0123B7DF0>, cls:<class '__main__.Person'>
# KeyError: 'age'
6 环状结构的内存管理
在python中,垃圾回收器是通过引用计数来回收垃圾对象的,但某些环状数据结构(树,图,……),存在对象的相互引用,比如树的父节点引用的子节点,子节点也同时引用父节点,此时同时del掉引用的父子节点,两个对象也不能立即被挥手、.
7 Python是如何进行内存管理
1 对象的引用计数机制
Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。
引用计数增加的情况:
1,一个对象分配一个新名称
2,将其放入一个容器中(如列表、元组或字典)
引用计数减少的情况:
1,使用del语句对对象别名显示的销毁
2,引用超出作用域或被重新赋值
sys.getrefcount( )函数可以获得对象的当前引用计数
多数情况下,引用计数比你猜测得要大得多。对于不可变数据(如数字和字符串),解释器会在程序的不同部分共享内存,以便节约内存。
2 垃圾回收
1,当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。
2,当两个对象a和b相互引用时,del语句可以减少a和b的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含一个对其他对象的应用,因此引用计数不会归零,对象也不会销毁。(从而导致内存泄露)。为解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。
3 内存池机制
Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。
1,Pymalloc机制。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。
2,Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的malloc。
3,对于Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。