专栏首页小海怪python学习python高级编程第二讲:类与对象深度问题与解决技巧

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

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))

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • tkinter带界面实现指定目录生成器

    路径是自己设定好的,然后输入要生成的文件夹数量,然后再点相应的按钮就可以了 下面放上源码,有需要的可以自己进行修改:

    小海怪的互联网
  • python高级编程第一讲:深入类和对象

    多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚"鸭子类型"

    小海怪的互联网
  • python高级编程第四讲:元类编程

    我们通过上面的方法,可以自定义一些信息,如果我们写_getattr_方法,当程序中找不到我们要调用的属性时程序会直接报错

    小海怪的互联网
  • 数据结构之——Python实现循环队列

    栈是先入后出,与之相反的是队列,队列是先进先出的线性结构。队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。允许插入的一端称为队尾,允许删除的一端...

    py3study
  • python模块:win32com用法详解

    import win32com from win32com.client import Dispatch, constants

    菲宇
  • 使用python从三个角度解决josephus问题的方法

    设nnn个人围坐一圈,现在要求从第kkk个人开始报数,报到第mmm个的人退出。然后从下一个人开始继续按照同样规则报数并退出,直到所有人退出为止。要求按照顺序输出...

    砸漏
  • PyQt5 动画类--跳舞的火柴人

    PyQt5.QtCore中的 QPropertyAnimation可以实现动画功能。

    用户6021899
  • 商业篇 | 使用python开发性格分析工具卖钱

    如此不均衡的贫富差距,各行业的领导者如何能管理好公司,让员工们即努力产出,又能安于现状呢?每个领导者必学的一门课程就是职场心理学。只有你充分了解员工心理与对应的...

    龙哥
  • 用Python画出心目中的自己

    本项目主要来源于中科院和香港城市大学的一项研究DeepFaceDrawing,论文标题是《DeepFaceDrawing: DeepGeneration of ...

    博文视点Broadview
  • python pyqt5仿window任务计划程序

    from PyQt5 import QtCore, QtWidgets import sys,os import win32api import win3...

    用户5760343

扫码关注云+社区

领取腾讯云代金券