python进阶第二节课:类与对象深度问题及解决技巧

本文探讨Python中类与对象的高级话题,包括派生内置不可变类型、节省内存的实例创建、with语句的使用、管理对象属性、实现比较操作以及根据方法名字符串调用方法。内容涵盖自定义IntTuple类、优化内存消耗、上下文管理器、访问器和设置器、比较操作以及动态调用方法。
摘要由CSDN通过智能技术生成

一、如何派生内置不可变类型并修改其实例行为

1.1 练习需求

我们想自定义一种新类型的元组,对于传入的可迭代对象,我们只保留其中int类型且值大于0的元素需求,定义IntTuple类

>>> IntTuple([2,-2,"jr",["x","y"],4])
(2,4)
class Demo(object):
    def __new__(cls, *args, **kwargs):
        print("__new__")

    def __init__(self):
        print("__init__")
        
d = Demo()

1.**new**方法是创建对象的方法

  • 1.此处重写了父类的方法
  • 2.需调用父类的**new**方法创建对象
  • 3.需将对象返回出来给**init**方法

2.**init**方法为初始化方法

  • 注意:当创建完对象时,自动调用它
class Demo(object):
    def __new__(cls, *args, **kwargs):
        print("__new__")
        return object.__new__(cls)
        #return super().__new__(cls)

    def __init__(self):
        print("__init__")


d = Demo()

1.2练习实现

class IntTuple(tuple):
    def __new__(cls, iterable ):
        f = [i for i in iterable if isinstance(i,int) and i>0]
        return super().__new__(cls,f)


int_t = IntTuple([2,-2,"jr",["x","y"],4])
print(int_t)    # (2, 4)

二、如何为创建大量实例节省内存

2.1练习需求

在游戏开发中,有一个玩家类Player,每有一个在线玩家,在服务器内则有一个player的实例,当在线的人数很多时,将产生大量实例(百万级)

如何降低这些大量实例的内存开销?

解决方案:定义类的 __slots__ 属性,声明实例有哪些属性(关闭动态绑定)

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
        
# 实例化两个类
p1 = Player("1","zs",1)
p2 = Player2("2","ls",1)

p2.__dict__['gender'] = 1  # 报错:'Player2' object has no attribute 'gender' 无法在外部添加

"""
跟踪内存的使用
"""
import tracemalloc
tracemalloc.start()     # 开始跟踪内存分配

# 实例化一万次
# pla_1 = [Player1(1,2,3) for i in range(10000)]       #  size=1722 KiB
pla_2 = [Player2(1,2,3) for i in range(10000)]        # size=711 KiB

snapshot = tracemalloc.take_snapshot()                # 快照,当前内存分配
top = snapshot.statistics("filename")                 # 快照对象的统计 监测文件

for start in top[:10]:
    print(start)
    

使用 __dict__ 字典主要是为了提升查询效率,所以必须使用空间换时间

少量的实例,使用字典存储,问题不大。但如果像我们的业务达到数百万个实例,字典占用的总空间就比较大。

这个 __slots__ 相当于告诉解释器,实例的属性都叫什么。而且既然需要节省内存,推荐定义时使用元组,而不是列表。

2.2 __slots__是否会继承?

__slots__ 不影响子类实例,不会继承,除非子类里面自己定义了 __slots__

三、python中的with语句

3.1 自定义类使用上下文管理器

with语句处理对象必须有 __enter__ 方法及 __exit__ 方法。并且 __enter__ 方法在语句体(with语句包括起来的代码块)执行之前进入运行, __exit__ 方法在语句体执行完毕退出后自动运行。

# with open('juran.txt', 'r') as f:
#     pass

# 上下文表达式 with open('juran.txt', 'r') as f:
# 上下文管理器 open('juran.txt', 'r')
# 资源对象 f

class Jr(object):

    def __enter__(self):
        """获取资源"""
        print('start')
        return self  # __enter__需要一个返回值,没有返回值下面的资源对象就是一个None空值

    def demo(self):
        return 'hello ji'

    def __exit__(self, exc_type, exc_val, exc_tb):
        """释放资源"""
        # exc_type 异常类
        # exc_val 异常值
        # exc_tb 追踪信息
        print('end')


with Jr() as ji:
    print(ji.demo())
# 执行结果:start  hello ji  end

调用顺序:执行with后,先调用__enter__然后返回self,ji获取self后调用demo方法进行返回,with语句整体调用完成执行__exit__。

3.2 contextlib简化上下文管理器

import contextlib

@contextlib.contextmanager
def file_open(filename):
    # __enter__函数
    print("file open")
    yield {}
    # __exit__函数
    print("file close")


with file_open("test.txt") as f:
    print("file operation")

四、如何创建可管理的对象属性

在面向对象编程中,我们把方法看作对象的接口。直接访问对象的属性可能是不安全的,或设计上不够灵活,但是使用调用方法在形式上不如直接访问属性简洁。

实现需求:

  • 定义类AgeDemo

  • 通过访问器访问年龄

  • 通过设置器设置年龄

    • 年龄不是int类型则主动抛出异常

五、 如何让类支持比较操作

有时我们希望自定义类的实例间可以使用,<,<=,>,>=,==,!=符号进行比较,我们自定义比较的行业,例如,有一个矩形的类,比较两个矩形的实例时,比较的是他们的面积

"""
__gt__ 大于
__lt__ 小于
__gte__ 大于等于
__lte__ 小于等于
"""
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 __lt__(self, other):
        return self.area() < other.area()

reac1 = Rect(1,2)
reac2 = Rect(3,4)
print(reac1)
print(reac2)

print(reac1 < reac2)           
print(reac1 > reac2)          


# 多个类进行比较
# 传入from functools import total_ordering用@total_ordering装饰器定义方法
from functools import total_ordering
# 多个类进行比较,类中的方法相似点很多,可以使用抽象基类进行简化代码
from abc import ABCMeta, abstractmethod

# 用装饰器定义比较方法
@total_ordering
# 定义父类抽象基类
class Shape(metaclass=ABCMeta):
    @abstractmethod
    def area(self):
        pass

    # self是函数本身,other是比较的第二个函数
    def __gt__(self, other):
        """大于"""
        return self.area() > other.area()

    def __eq__(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

    # def __gt__(self, other):
    #     """大于"""
    #     return self.area() > other.area()
    #
    # def __eq__(self, other):
    #     return self.area() == other.area()


rect1 = Rect(1, 2)
rect2 = Rect(3, 4)
# print(rect1 > rect2)
# print(rect1 >= rect2)
# print(rect1 != rect2)
# print(rect1 < rect2)
# print(rect2 > rect1)
# 导入π
import math


class Circle(Shape):
    def __init__(self, r):
        self.r = r

    # 不同新形状的面积计算方法不同,所有进行重写
    def area(self):
        return self.r ** 2 * math.pi  # π = math.pi

    # def __gt__(self, other):
    #     """大于"""
    #     return self.area() > other.area()
    #
    # def __eq__(self, other):
    #     return self.area() == other.area()


c = Circle(8)

print(c <= rect1)
  • @total_ordering装饰器就只需要完成__lt__与__gt__两个方法 就可以全部实现

六、通过实例方法名字的字符串调用方法

我们有三个图形类 Circle,Triangle,Rectangle

他们都有一个获取图形面积的方法,但是方法名字不同,我们可以实现一个统一的获取面积的函数,使用每种方法名进行尝试,调用相应类的接口

"""
我们可以实现一个统一的获取面积的函数
"""


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
    
# 把类名当做参数shape传入
def get_area_from_shape(shape):
    # 把类名传入列表进行循环判断
    method_name = ['get_area', 'getArea', 'area']
    for name in method_name:
        # 判断shape里有没有name,没有就返回None
        # getattr存在返回值,用f进行接收
        f = getattr(shape, name, None)
        # 如果f存在则返回f()
        if f:
            return f()


shape1 = Triangle(3, 4, 5)
shape2 = Rectangle(1, 2)
shape3 = Circle(6)

# print(get_area_from_shape(shape1))  # 6.0
# print(get_area_from_shape(shape2))  # 2
# print(get_area_from_shape(shape3))  # 113.09724

# 一个个输出太麻烦,把实例化后的传入列表用map把get_area_from_shape用到每一个shape_list中返回迭代器,用list转换成列表
shape_list = [shape1, shape2, shape3]
area_list = list(map(get_area_from_shape, shape_list))
print(area_list)  # [6.0, 2, 113.09724]

# find查找,没找到返回-1,找到返回相应的下标
s = 'abc'
print(s.find('123'))  # -1
print(s.find('a'))  # 0
# hasattr判断有没有,getattr获取有没有
print(getattr(s, 'find'))  # 返回一个方法,<built-in method find of str object at 0x0000022254F72B30>
print(getattr(s, 'find')('123'))  # -1
print(getattr(s, 'pop'))   # 报错:'str' object has no attribute 'pop'没有pop方法
# 要是不想报错,可以加入None,,没有就返回None
print(getattr(s, 'pop', None))  # None
f = getattr(s, 'find', None)  # None
print(f('123'))  # -1
  • getattr(x,“y”,None) --> 等同于 x.y 当x中不含有y时,返回None。
  • map(func,iterable) --> 将iterable中的元素一一映射到func函数中处理,并且返回新的map对象。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值