1. 创建大量实例节省内存
场景:在游戏中,定义了玩家类player,每有一个在线玩家,在服务器内则有一个player的实例,当在线人数很多时,将产生大量实例(百万级),如何节省内存?
先看下面代码:
class Player(object):
def __init__(self,uid,name,status=0,lever=0):
'''有默认值在实例化时不用传,但是在赋值时还是要写的'''
self.uid = uid
self.name = name
self.status = status
self.lever = lever
class Player2(object):
__slots__ = ['uid','name','status','lever'] #关闭动态绑定属性,在python 中 属性都是通过__dict__进行维护的,动态属性会占用内存,此处关闭动态绑定后,我们不能再通过 类.属性的这种方式新增属性
def __init__(self,uid,name,status=0,lever=0):
'''有默认值在实例化时不用传,但是在赋值时还是要写的'''
self.uid = uid
self.name = name
self.status = status
self.lever = lever
p1 = Player(1,'zjk')
p2 = Player2(2,'zs')
上面代码中,在player2这个类中我们增加了 slots魔法方法,此方法的作用是:关闭动态绑定属性,在python 中 属性都是通过dict进行维护的,动态属性会占用内存,此处关闭动态绑定后,并且限定了只有列表中的几个属性,也可以 用元组传递属性,我们不能再通过 类.属性的这种方式新增属性。会节省内存。
查看上面2个类中所差的属性有哪些
代码如下:
print(set(dir(p1))-set(dir(p2))) #为什么要转为集合,因为list列表,是不能进行差集运算的,也就是 - 操作
执行结果:
{'_weakref_', '_dict_'}
我们可以看出,当我们设置slots后,这个类里的 上面的2个属性就没了,一个是_weakref_ 弱引用,一个是类中的动态绑定属性
查看类占用的内存空间大小 sys.getsizeof()
import sys
print(sys.getsizeof(p1.__dict__))
print(sys.getsizeof(p2.uid))
print(sys.getsizeof(p2.name))
执行结果:
112
28
51
我们看出,没有关闭动态属性的时候,内存要大
2. 跟踪内存
将上面的代码我们进行改造,引入内存跟踪的类,并且将2个类分别实例化100000次,并打印相应的内存大小
import sys
class Player(object):
def __init__(self,uid,name,status=0,lever=0):
'''有默认值在实例化时不用传,但是在赋值时还是要写的'''
self.uid = uid
self.name = name
self.status = status
self.lever = lever
class Player2(object):
__slots__ = ['uid','name','status','lever'] #关闭动态绑定属性,在python 中 属性都是通过__dict__进行维护的,动态属性会占用内存,此处关闭动态绑定后,我们不能再通过 类.属性的这种方式新增属性
def __init__(self,uid,name,status=0,lever=0):
'''有默认值在实例化时不用传,但是在赋值时还是要写的'''
self.uid = uid
self.name = name
self.status = status
self.lever = lever
import tracemalloc
tracemalloc.start()
p1 = [Player(1,'zjk') for _ in range(100000)]
p2 = [Player2(2,'zs') for _ in range(100000)]
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:10]:
print(stat)
执行结果:
G:/pythonlianxi/spython/logic/python高级/第二讲/demo1.py:6: size=10.7 MiB, count=199993, average=56 B
G:/pythonlianxi/spython/logic/python高级/第二讲/demo1.py:24 p2: size=7837 KiB, count=100005, average=80 B
G:/pythonlianxi/spython/logic/python高级/第二讲/demo1.py:23 p1: size=6274 KiB, count=100001, average=64 B
G:/pythonlianxi/spython/logic/python高级/第二讲/demo1.py:26: size=448 B, count=1, average=448 B
D:\python36\lib\tracemalloc.py:522: size=64 B, count=1, average=64 B
看到此结果我们可能有疑问,为什么 p2 关闭了动态属性为什么占用的空间还比p1大, 其实当 我们创建p1的时候,程序是没有把 dict占用的空间写在一起,也就是上面结果中的 10.7M,我们将代码调整下,只看p1的内存占用大小,大家就能看明白了
import sys
class Player(object):
def __init__(self,uid,name,status=0,lever=0):
'''有默认值在实例化时不用传,但是在赋值时还是要写的'''
self.uid = uid
self.name = name
self.status = status
self.lever = lever
class Player2(object):
__slots__ = ['uid','name','status','lever'] #关闭动态绑定属性,在python 中 属性都是通过__dict__进行维护的,动态属性会占用内存,此处关闭动态绑定后,我们不能再通过 类.属性的这种方式新增属性
def __init__(self,uid,name,status=0,lever=0):
'''有默认值在实例化时不用传,但是在赋值时还是要写的'''
self.uid = uid
self.name = name
self.status = status
self.lever = lever
import tracemalloc
tracemalloc.start()
p1 = [Player(1,'zjk') for _ in range(100000)]
# p2 = [Player2(2,'zs') for _ in range(100000)]
snapshot = tracemalloc.take_snapshot()
# top_stats = snapshot.statistics('lineno')
top_stats = snapshot.statistics('filename')
for stat in top_stats[:10]:
print(stat)
执行结果:
G:/pythonlianxi/spython/logic/python高级/第二讲/demo1.py:0: size=16.8 MiB, count=299996, average=59 B
D:\python36\lib\tracemalloc.py:0: size=64 B, count=1, average=64 B
我们再调整代码,只打印p2的
p2 = [Player2(2,'zs') for _ in range(100000)]
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('filename')
执行结果:
G:/pythonlianxi/spython/logic/python高级/第二讲/demo1.py:0: size=7837 KiB, count=100003, average=80 B
D:\python36\lib\tracemalloc.py:0: size=64 B, count=1, average=64 B
由此结果我们看出,关闭动态属性和没有关闭动态属性占用内存的有差别
当slots 为空的时候是不允许绑定任何的属性,一旦绑定了程序就会抛出异常
关于内存跟踪和分配的用法我们可以参考文章 :https://www.rddoc.com/doc/Python/3.6.0/zh/library/tracemalloc/
3. with 和上下文管理协议
我们常用的with open 文件的方法其实 用到的是上下文管理协议,with就相当于是上下文管理器
- 我们自己来模拟一个上下文管理器:
class Demo(object):
def __enter__(self):
#获取资源
print("start")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
#释放资源
print("exit")
def info(self):
print("info")
with Demo() as f:
f.info()
注:此时我们要想实现上下文管理器,我们需要在类中实现2个方法,一个是 enter,一个是 exit(),并且enter 中要 写 return self 否则最后在实例调用 info方法时会报错
- 通过装饰器的方法来完成
上面第一种实现方法必须是类才能实现我们想要的效果,比较麻烦,我们还可以通过装饰器的方法来完成,下面就来完成通过装饰器将函数变成上下文管理器,想要实现,需要引入 contextlib 类
import contextlib
@contextlib.contextmanager
#此装饰器是将函数装饰成上下文管理器
def file_open(filename):
print("file is open")
yield # 生成器 这一步一定要写,可以写空字典也可以不写 yiels 前后执行的类似于 __enter__中的方法
# yield 后面相当于执行的是 exit中的
print("file is close")
with file_open('demo1.py') as f:
print('file is operision')
执行结果:
file is open
file is operision
file is close
4. 创建可管理的对象属性
我们常规的作法就是直接去调类的属性来进行赋值和取值,但是此种方法不安全,一旦别人知道了我们的代码,就有可能被别人用来搞破坏,所以我们需要将我们不希望被别人知道 的属性来保护起来,丢给别人一个看似是属性,实际则是内部在调用方法来执行的效果
- 们先看原始的方法来实现效果的代码
class Person():
def get_name(self):
return self.name
def set_name(self,name):
self.name= name
p = Person()
p.set_name('zs')
print(p.get_name())
通过用隐藏属性和setter 、getter等 来实现效果,但是用起来比较麻烦
- 所以我们再换一种方法,让外界看起来形式上是属性,其实内部是靠调用方法来实现,我们需要用到 property()方法,方法需要传入 set,get 方法
class Person():
def __init__(self,age):
self.age = age
def set_age(self,age):
if not isinstance(age,int):
raise TypeError('age必须是int类型')
self.age = age
def get_age(self):
return self.age
#定义一个变量用来存储赋值和取值的方法,并且此变量在外界看起来是像直接调用的属性
R = property(get_age,set_age)
p = Person(18)
p.R=100 # 这里是设置值
print(p.R) # 这里是取值
此种写法已经满足了我们的需求,但是由于此种方法比较麻烦,所以我们还可以用 property另外一种装饰器的方法来实现,只需要在相应的方法上加上装饰器就可以 实现我们要的效果
改造后的代码:
class Person():
def __init__(self,age):
self.age = age
@property
def s(self):
return self.age
@s.setter
def s(self,age):
if not isinstance(age,int):
raise TypeError('age必须是int类型')
self.age = age
p = Person(18)
p.s=100 # 这里是设置值
print(p.s) # 这里是取值
通过装饰器方法我们可以快速的实现我们的需求,同时对外也保护了我们不想让外界直接访问的属性,安全
5. 如何让2个类进行比较
- 第一种:实现方法代码示例:
import math
class Rect(object):
'''计算矩形面积'''
def __init__(self,w, h):
self.w = w
self.h = h
def area(self):
#计算面积
return self.w * self.h
def __lt__(self, other):
return self.area() < other.area()
def __gt__(self, other):
return self.area() > other.area()
def __le__(self, other):
return self.area() <= other.area()
def __ge__(self, other):
return self.area() >= other.area()
# rect1 = Rect(1,2)
# rect2 = Rect(3,4)
# print(rect1 < rect2)
# ''' 实现单个类不同实例之间的比较'''
#我们再来实现一个圆类的面积计算
class Circle(object):
def __init__(self,r):
self.r = r
def area(self):
return self.r ** 2 * math.pi
def __lt__(self, other):
return self.area() < other.area()
def __gt__(self, other):
return self.area() > other.area()
def __le__(self, other):
return self.area() <= other.area()
rect1= Rect(1,4)
circle1 = Circle(3)
print(rect1 < circle1)
执行结果为:
True
上述代码中2个类的比较 > < >= <= 的内部实现其实是靠相应的 魔法来实现,但是上面的代码实现起来比较麻烦,想要做判断,需要将所有将会用到比较方法都 一一列举出来,非常的累,所以我们还需要对代码进行改造,我们可以使用 functools 中的 total_ordering 方法方法来实现,用了魔法方法后,只需要实现比较方法中的任意2个就可以了
- 第二种:实现方法代码示例:
import math
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 __lt__(self, other):
return self.area() < other.area()
def __gt__(self, other):
return self.area() > other.area()
@total_ordering
class Circle(object):
def __init__(self,r):
self.r = r
def area(self):
return self.r ** 2 * math.pi
def __lt__(self, other):
return self.area() < other.area()
def __gt__(self, other):
return self.area() > other.area()
rect1= Rect(1,4)
circle1 = Circle(3)
print(rect1 < circle1)
执行结果和上面的一样,我们可以少写很多代码,而且实现也简单
- 第三种:通过抽象基类的方法来
由于上面的方法中,代码冗余度比较高,所以我们可以将其封装成一个抽象基类来完成,
实现方法代码示例:
from functools import total_ordering
from abc import ABCMeta,abstractclassmethod
import math
@total_ordering
class shape(metaclass=ABCMeta):
#定义一个是抽象类,并且在类中定义一个求面积的方法,子类要继承此抽象类,并对area方法进行重写
@abstractclassmethod
def area(cls):
pass
#抽象父类中实现比较运算,子类就不需要再写了
def __lt__(self, other):
return self.area() < other.area()
def __gt__(self, other):
return self.area() > other.area()
@total_ordering
class Rect(shape):
def __init__(self,w,h ):
self.w = w
self.h = h
def area(self):
return self.w * self.h
@total_ordering
class Circle(shape):
def __init__(self,r):
self.r = r
def area(self):
return self.r ** 2 * math.pi
rect1 = Rect(3,3)
c1= Circle(3)
print(c1 > rect1)
6. 通过实例方法名字的字符串调用方法(了解)
我们定义一个类,用来分别实现不同图形的面积的方法,我们实现一个类,在一个函数中实现求不同的面积
lib1 类的代码如下:
class Triangle:
def __init__(self, a, b, c):
self.a, self.b, self.c = a, b, c
def get_area(self):
a, b, c = self.a, self.b, self.c
p = (a + b + c) / 2
return (p * (p - a) * (p - b) * (p - c)) ** 0.5
class Rectangle:
def __init__(self, a, b):
self.a, self.b = a, b
def getArea(self):
return self.a * self.b
class Circle:
def __init__(self, r):
self.r = r
def area(self):
return self.r ** 2 * 3.14159
下面我们用代码实现相应的功能
from lib1 import Triangle,Rectangle,Circle
def get_area(shape):
method_name = ["get_area","getarea","area"]
for name in method_name:
f = getattr(shape,name,None) #类似反射,取得图形中的计算面积的方法,然后赋值给 f
if f: # 如果有此方法
return f() # 调用找到的方法
shape1 = Triangle(2,3,4)
shape2 = Circle(2)
shape3 = Rectangle(4,5)
shape_list = [shape1,shape2,shape3]
area_list = map(get_area,shape_list)
print(list(area_list))