前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python高级编程第二讲:类与对象深度问题与解决技巧

python高级编程第二讲:类与对象深度问题与解决技巧

作者头像
小海怪的互联网
发布2019-08-23 16:43:03
4050
发布2019-08-23 16:43:03
举报

1. 创建大量实例节省内存

场景:在游戏中,定义了玩家类player,每有一个在线玩家,在服务器内则有一个player的实例,当在线人数很多时,将产生大量实例(百万级),如何节省内存?

先看下面代码:

代码语言:javascript
复制
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个类中所差的属性有哪些 代码如下:

代码语言:javascript
复制
print(set(dir(p1))-set(dir(p2)))   #为什么要转为集合,因为list列表,是不能进行差集运算的,也就是 - 操作

执行结果: {'_weakref_', '_dict_'} 我们可以看出,当我们设置slots后,这个类里的 上面的2个属性就没了,一个是_weakref_ 弱引用,一个是类中的动态绑定属性

查看类占用的内存空间大小 sys.getsizeof()

代码语言:javascript
复制
import sys
print(sys.getsizeof(p1.__dict__))
print(sys.getsizeof(p2.uid))
print(sys.getsizeof(p2.name))

执行结果: 112 28 51 我们看出,没有关闭动态属性的时候,内存要大

2. 跟踪内存

将上面的代码我们进行改造,引入内存跟踪的类,并且将2个类分别实例化100000次,并打印相应的内存大小

代码语言:javascript
复制
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的内存占用大小,大家就能看明白了

代码语言:javascript
复制
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的

代码语言:javascript
复制
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就相当于是上下文管理器

  • 我们自己来模拟一个上下文管理器:
代码语言:javascript
复制
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 类
代码语言:javascript
复制
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. 创建可管理的对象属性

我们常规的作法就是直接去调类的属性来进行赋值和取值,但是此种方法不安全,一旦别人知道了我们的代码,就有可能被别人用来搞破坏,所以我们需要将我们不希望被别人知道 的属性来保护起来,丢给别人一个看似是属性,实际则是内部在调用方法来执行的效果

  • 们先看原始的方法来实现效果的代码
代码语言:javascript
复制
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 方法
代码语言:javascript
复制
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另外一种装饰器的方法来实现,只需要在相应的方法上加上装饰器就可以 实现我们要的效果

改造后的代码:

代码语言:javascript
复制
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个类进行比较

  • 第一种:实现方法代码示例:
代码语言:javascript
复制
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个就可以了

  • 第二种:实现方法代码示例:
代码语言:javascript
复制
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)

执行结果和上面的一样,我们可以少写很多代码,而且实现也简单

  • 第三种:通过抽象基类的方法来 由于上面的方法中,代码冗余度比较高,所以我们可以将其封装成一个抽象基类来完成, 实现方法代码示例:
代码语言:javascript
复制
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 类的代码如下:

代码语言:javascript
复制
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

下面我们用代码实现相应的功能

代码语言:javascript
复制
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))
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.07.20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 创建大量实例节省内存
  • 2. 跟踪内存
  • 3. with 和上下文管理协议
  • 4. 创建可管理的对象属性
  • 5. 如何让2个类进行比较
  • 6. 通过实例方法名字的字符串调用方法(了解)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档