1 派生内置不可变类型并需改

1 需求

自定义新的类型元组,对于传入得可迭代对象,保留其中得int类型且大于0的元素,例如:(-1, 1, 2, ‘yifan’, 3, [3,3], (22))—》(1, 2, 3, 22)
如何继承tuple实现呢 ?

2 基础

初次尝试

  1. class IntTuple(tuple):
  2. def __init__(self, iterable):
  3. f_it = (e for e in iterable if isinstance(e, int) and e > 0)
  4. super().__init__(f_it)
  5. intT = IntTuple((-1, 1, 2, 'yifan', 3, [3,3], (22)))
  6. intT

TypeError: object.init() takes exactly one argument (the instance to initialize)

  1. class IntTuple(tuple):
  2. def __init__(self, iterable):
  3. print(self)
  4. print(iterable)
  5. # f_it = (e for e in iterable if isinstance(e, int) and e > 0)
  6. # super().__init__(f_it)
  7. 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
知识

  1. class A:
  2. def __new__(cls, *args):
  3. print('test:in A.__new__', cls, args)
  4. return object.__new__(cls)
  5. def __init__(self, *args):
  6. print('In A.__init__', args)
  1. a = A(1,3)
  2. # test:in A.__new__ <class '__main__.A'> (1, 3)
  3. # In A.__init__ (1, 3)
  4. a = A.__new__(A, 1, 3)
  5. A.__init__(a, 1, 3)
  6. # test:in A.__new__ <class '__main__.A'> (1, 3)
  7. # In A.__init__ (1, 3)

类对象是先调用new方法,再调用init

  1. # 测试
  2. list('abc') #['a', 'b', 'c']
  3. l = list.__new__(list, 'abc')
  4. l # []
  5. list.__init__(l, 'abc')
  6. l #['a', 'b', 'c']

list也可以看作对象,调用效果等价如上,但是tuple也是如此,但是生成有别,在new已经生成了数据,虽然调用了init,但是tuple没有实现,只是单单继承了object的init。而list则不一样。

  1. tuple('abc') #('a', 'b', 'c')
  2. t = tuple.__new__(tuple, 'abc')
  3. t #('a', 'b', 'c')
  4. tuple.__init__ is object.__init__ #True
  5. list.__init__ is object.__init__ #False

3 方案

继承tuple类,修改对应的 new

  1. class IntTuple(tuple):
  2. def __new__(cls, iterable):
  3. print(cls)
  4. print(iterable)
  5. f_it = (e for e in iterable if isinstance(e, int) and e > 0)
  6. return super().__new__(cls,f_it)
  7. intT = IntTuple((-1, 1, 2, 'yifan', 3, [3,3], (22)))
  8. intT
  9. #<class '__main__.IntTuple'>
  10. #(-1, 1, 2, 'yifan', 3, [3, 3], 22)
  11. #(1, 2, 3, 22)

2 减少内存使用方法

1 使用场景

在程序中,一个类中,若产生大量实例,将会占用大量内存开销。

2 基础知识

原始比压缩后的少 ‘dict‘, ‘weakref‘,其中dict占用内存很大。

  1. class Player1:
  2. def __init__(self, uid, name, level):
  3. self.uid = uid
  4. self.name = name
  5. self.level = level
  6. class Player2:
  7. __slots__ = ['uid', 'name', 'level']
  8. def __init__(self, uid, name, level):
  9. self.uid = uid
  10. self.name = name
  11. self.level = level
  12. p1 = Player1('0001', 'Jim', 20)
  13. p2 = Player2('0001', 'Jim', 20)
  14. # 原始比压缩后的少 '__dict__', '__weakref__',其中__dict__占用内存很大
  15. set(dir(p1)) - set(dir(p2)) #{'__dict__', '__weakref__'}
  1. p1.__dict__ #{'uid': '0001', 'name': 'Jim', 'level': 20}
  2. p1.x = 100
  3. p1.__dict__ #{'uid': '0001', 'name': 'Jim', 'level': 20, 'x': 100}
  4. p2.__dict__ #AttributeError: 'Player2' object has no attribute '__dict__'

查看dict的大小

  1. import sys
  2. sys.getsizeof(p1.__dict__) #152
  3. sys.getsizeof(p1.name) #52
  4. sys.getsizeof(p1.level) #28
  5. sys.getsizeof(p1.uid) #53

3 比较大小

  1. import tracemalloc
  2. tracemalloc.start()
  3. #start
  4. la = [Player1(1,2,3) for _ in range(100000)]
  5. #lb = [Player2(1,2,3) for _ in range(100000)]
  6. #end
  7. snapshot = tracemalloc.take_snapshot()
  8. top_stats = snapshot.statistics('filename')
  9. for stat in top_stats[:4]:
  10. print(stat)

结果
:0: size=10.7 MiB, count=199928, average=56 B :0: size=6274 KiB, count=100002, average=64 B C:\ProgramData\Anaconda3\lib\codeop.py:0: size=2149 B, count=22, average=98 B

  1. import tracemalloc
  2. tracemalloc.start()
  3. #start
  4. #la = [Player1(1,2,3) for _ in range(100000)]
  5. lb = [Player2(1,2,3) for _ in range(100000)]
  6. #end
  7. snapshot = tracemalloc.take_snapshot()
  8. top_stats = snapshot.statistics('filename')
  9. for stat in top_stats[:4]:
  10. print(stat)

结果
:0: size=7056 KiB, count=100002, average=72 B C:\ProgramData\Anaconda3\lib\codeop.py:0: size=2149 B, count=22, average=98 B

3 创建可管理对象属性

1 使用案例

面向对象编程中,我们把函数作为对象的接口,直接访问对象属性可能不安全或者不灵活,但是调用上不如访问属性简洁。
circle.get_radius()
circle.set_radius(3.3) #繁

circle.radius
circle.radius = 3.3 # 简单
能否在形式上是属性访问,但实际内部调用方法?

2 实验

  1. import math
  2. class Circle:
  3. def __init__(self, radius):
  4. self.radius = radius
  5. def get_area(self):
  6. return self.radius**2 * math.pi
  7. def set_area(self, S):
  8. self.radius = math.sqrt(S / math.pi)
  9. #正常
  10. c = Circle(8.823)
  11. d = c.radius*2 #17.646
  12. #异常
  13. c.radius = '31.93'
  14. d = c.radius*2
  15. d # '31.9331.93'
  16. #没有报错,但是还出现了问题,这种情况更糟糕

异常情况没有报错,设置处理异常

  1. import math
  2. class Circle:
  3. def __init__(self, radius):
  4. self.radius = radius
  5. def set_radius(self, radius):
  6. if not isinstance(radius, (int, float)):
  7. raise TypeError('wrong type')
  8. self.radius = radius
  9. def get_area(self):
  10. return self.radius**2 * math.pi
  11. def set_area(self, S):
  12. self.radius = math.sqrt(S / math.pi)
  13. c = Circle(8.823)
  14. c.set_radius('31.99')
  15. d = c.radius*2 # TypeError: wrong type
  16. c = Circle(8.823)
  17. c.set_radius(31.99)
  18. d = c.radius*2
  19. d #63.98

使用property函数为类创建可管理的属性,fget/fset/fdel对应相关属性访问。

  1. import math
  2. class Circle:
  3. def __init__(self, radius):
  4. self.radius = radius
  5. def get_radius(self):
  6. return round(self.radius, 2)
  7. def set_radius(self, radius):
  8. if not isinstance(radius, (int, float)):
  9. raise TypeError('wrong type')
  10. self.radius = radius
  11. def get_area(self):
  12. return self.radius**2 * math.pi
  13. def set_area(self, S):
  14. self.radius = math.sqrt(S / math.pi)
  15. R = property(get_radius, set_radi
  16. c = Circle(8.823)
  17. c.R = '11.2323' #TypeError: wrong type
  18. c = Circle(8.823)
  19. c.R = 11.2323
  20. c.R #11.23
  21. c.R #11.23

再设置S属性

  1. class Circle:
  2. def __init__(self, radius):
  3. self.radius = radius
  4. def get_radius(self):
  5. return round(self.radius, 2)
  6. def set_radius(self, radius):
  7. if not isinstance(radius, (int, float)):
  8. raise TypeError('wrong type')
  9. self.radius = radius
  10. @property
  11. def S(self):
  12. return self.radius**2 * math.pi
  13. @S.setter
  14. def S(self, s):
  15. self.radius = math.sqrt(s / math.pi)
  16. R = property(get_radius, set_radius)
  17. c = Circle(8.823)
  18. c.S = 12.22
  19. c.R #1.97

可以便捷地设置和取到想要的值。内部实现是函数。
细致介绍可见https://zhuanlan.zhihu.com/p/67914416

4 类做比较

1 实际案例

我们实际比较中,想直接使用符号比较类的某些行为,比如比较矩形的面积等

2 实验

基础知识

  1. a, b = 5, 3
  2. a.__lt__(b) #False
  3. a.__ge__(b) #True
  4. s1, s2 = 'abc', 'abd'
  5. s1 > s2 #False
  6. s1.__gt__(s2) #False
  7. {1, 2, 3} > {4} #False
  8. {1, 2, 3} == {4} #False
  9. {1, 2, 3} < {4} #False
  10. {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'

可以通过set方法进行限制。

6 环状结构的内存管理

在python中,垃圾回收器是通过引用计数来回收垃圾对象的,但某些环状数据结构(树,图,……),存在对象的相互引用,比如树的父节点引用的子节点,子节点也同时引用父节点,此时同时del掉引用的父子节点,两个对象也不能立即被挥手、
image.png.

若都是用弱引用,则树的节点就不存在了。
image.png

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,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。