2.类与对象深度问题与解决技巧
2.1 如何派生内置不可变类型并修改其实例化行为
练习需求
- 我们想自定义一种新类型的元组,对于传入的可迭代对象,我们只保留其中int类型且值大于0的元素
- 需要,定义InTuple
1.InTuple([2,-2,‘jr’,[‘x’,‘y’],4])
2.(2,3)
class InTuple(tuple):
#重写了父类的构造方法
def __new__(cls,iterable):
#iterable里面元素,为整数且>0的值
#列表推导式
f=[i for i in iterable if isinstance(i,int) and i>0]
return super().__new__(cls,f)
int_tuple=InTuple([2,-2,'jr',['x','y'],4])
print(int_tuple)
回顾__new__
- 以下代码,执行结果为:__
- A__new__,__ init__
- B__init__,__ new__
- C__new__
__ init__
class Demo(object):
def __new__(cls,*args,**kwargs):
print('__new__')
def __init__(self):
print('__init__')
d=Demo() # __new__
1、 __ new__ 方法是创建对象的方法
- 1 .此处重写了父类的方法
- 2 .需调用父类的__ new__方法创建对象
- 3 .需将对象返回出来给__ init__方法
2、__ init__方法为初始化方法
- 注意:当创建完对象时,自动调用它
2.2 如何为创建大量实例节省内存
练习需求
- 在游戏开发中,有一个玩家类Player,每有一个在线玩家,在服务器内则有一个player,当在线的人数很多时,将产生大量实例(百万级)
- 如何降低这些大量实例的内存开销?
**解决方法:**定义类的__ solts __ 属性,声明实例有哪些属性(关闭动态绑定)
import sys
class Player(object):
def __init__(self,uid,name,status):
self.uid=uid
self.name=name
self.__status=status
class Player2(object):
__slots__ = ('uid','name','status')
def __init__(self,uid,name,status):
self.uid=uid
self.name=name
self.status=status
#主程序入口
if __name__=='__main__':
p1=Player('1','sige',1)
p2=Player2('2','tianhao',1)
# print(dir(p1))
# print(dir(p2))
# print(len(dir(p1))) # 29 '__dict__'
# print(len(dir(p2))) # 28
'''
集合--->差集
'__weakref__','__dict__'
__weakref__ 弱引用
__dict__ 动态绑定属性的
'''
# print(set(dir(p1))-set(dir(p2)))
print(p1.__dict__) # {}
# 动态为p1添加属性
p1.__dict__['level']=1
print(p1.__dict__)
# 动态为p1删除属性
del p1.__dict__['level']
print(p1.__dict__)
print(sys.getsizeof(p1.__dict__)) # 能够看到内存所占大小
'''
p2能吗? 不能动态绑定属性 由于__slots__
'''
print(p2.__dict__) # AttributeError: 'Player2' object has no attribute '__dict__'
p2.level=1 # AttributeError: 'Player2' object has no attribute 'level' 不能动态绑定
p2.__slots__=(1,)# AttributeError: 'Player2' object attribute '__slots__' is read-only 报错,只读
- 使用__ dict __ 字典主要是为了提升查询效率,所以必须使用空间换时间
- 少量的实例,使用字典字典存储,问题不大。但是如果像我们的业务到达百万个实例,字典占用的空间就比较大。
- 这个__slots__ 相当于告诉解释器,实例的属性都叫什么,而且既然需要节省内存,推荐定义时使用元组,而不是列表。
__ slots __ 是否会继承?
__ slots__ 不影响类实例,不会继承,除非子类里面自己定义了
__ slots __
2.3 Python中的with语句
自定义类使用上下文件管理
- with 语句处理对象必须有 __ enter __ 方法及 __exit __ 方法。并且 enter __ 方法在语句体(with 语句包括起来的代码块)执行之前进入运行, exit __ 方法在语句体执行完毕退出后自动运行。
contextlib简化上下文管理器
import contextlib
@contextlib.contextmanager
def file_open(filename):
print('file open')
# 函数加上yield 关键字之后,整个函数变成 生成器
yield {}
print('file close')
with file_open('test.txt') as f:
# __enter__
print('file operation')
# __exit__
-
yield 与return 类似 程序遇到yield 会将内容进行返回
-
不同之处
yield 在下次访问的时候 会接着 yield 后面的代码继续执行
return 时直接返回退出函数
2.4 如何创建可管理的对象属性
- 在面向对象编程中,我们把方法看作对象的接口。直接访问对象的属性可能是不安全的,或设计上不够灵活,但是使用调用方法在形式上不如直接访问简洁。
实现需求
- 定义类AgeDemo
- 通过访问器访问年龄
- 通过设置器设置年龄
- 年龄不是int类型主动抛出异常
'''
A.get_age() # 访问器
A.set_age() # 设置器
通过
A.age 访问
A.age= 20 设置
定义类AgeDemo
通过设置器设置年龄
年龄不是int 类型则主动抛出异常
'''
class AgeDemo(object):
def __init__(self,age):
self.age=age
#访问
def get_age(self):
return self.age
# 设置
def set_age(self,age):
if not isinstance(age,int):
raise TypeError('TypeError')
self.age=age
a=AgeDemo(18)
a.age='20' # 直接访问到属性
print(a.age) # 20 str 直接访问到属性 不安全
# 当赋值的时候,直接触发set_age()方法
# property(getx, setx, delx, "I'm the 'x' property.")
age_pro= property(fget=get_age, fset=set_age)
a=AgeDemo(18)
a.age_pro=20 # 赋值---> fset
print(a.age_pro) # 访问---> fget
a=AgeDemo(18)
a.age_pro='23' # 赋值---> fset
print(a.age_pro) # 访问---> fget
class AgeDemo(object):
def __init__(self,age):
self.age=age
@property
def age_test(self):
return self.age
@age_test.setter
def age_test(self,age):
if not isinstance(age,int):
raise TypeError('TypeError')
self.age=age
a=AgeDemo(18)
a.age_test=20
print(a.age_test)
2.5 如何让类支持比较操作
- 有时我们希望自定义类的实例间可以使用,<,<=,>,>=,==.!=符号进行比较,我们自定义比较的行业,例如,有一个矩形的类,比两个矩形的实例时,比较的是他们的面积。
a=2
b=1
print(a>1)
print(a.__gt__(b)) # gt---> >
print('='*100)
# 集合比较的 包含关系
print({1,2,3}>{4}) # False 不是比较大小
print({1,2,3} > {2,3}) # True
# 求矩形面积的类
from functools import total_ordering
@total_ordering
class Rect(object):
def __init__(self,w,h):
self.w=w
self.h=h
def area(self):
return self.w*self.h
def __str__(self):
return f'({self.w},{self.h})'
def __gt__(self,other):
return self.area() > other.area()
def __ge__(self, other): # >= <=
return self.area() >= other.area()
r1=Rect(1,2)
r2=Rect(3,4)
print(r1)
print(r2)
print(r1 >r2)
'''
TypeError: '>' not supported between instances of 'Rect' and 'Rect'
类与类比较,直接报错 添加 __gt__魔法方法 可以进行比较
'''
print(r1 >= r2)
print(r1 <= r2)
'''
圆这个类 矩形面积与圆面积作比较
'''
import math
class Circle(object):
def __init__(self,r):
self.r=r
def area(self):
return self.r**2*math.pi
def __gt__(self, other):
return self.area() > other.area()
def __ge__(self, other):
return self.area() > other.area()
c=Circle(2)
r=Rect(2,2)
print(c==r)
'''
将共同代码抽出 强制子类 实现area 方法
'''
import abc
from functools import total_ordering
import math
@total_ordering
class Shape(metaclass=abc.ABCMeta):
@abc.abstractmethod
def area(self):
pass
def __gt__(self, other):
return self.area() > other.area()
def __ge__(self, other):
return self.area() >= other.area()
class Rect(Shape):
def __init__(self,w,h):
self.w=w
self.h=h
def area(self):
return self.w*self.h
class Circle(object):
def __init__(self,r):
self.r=r
def area(self):
return self.r**2*math.pi
c=Circle(4)
r=Rect(1,2)
print(c==r)
- @total_ordering 装饰器就只需要完成__ it __ 与 __ gt __ 两个方法 就可以全部实现。
2.6 如何在环状数据结构中管理内存
- 在Python 中,垃圾回收器通过引用计数来回收垃圾对象,当一个对象引用计数为0,或者只剩下弱引用时,这个对象会被释放。
弱引用
- 弱引用不增加引用计数,使用弱引用访问对象得到对象引用。