2.类与对象深度问题与解决技巧

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

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

IntTuple([2,-2,'jr',['x','y'],4]) => (2,4)

请用继承内置tuple的方法实现IntTuple

tuple生成元组的方法是new方法,所以重写__new__(cls, *args, **kwargs)方法:

class IntTuple(tuple):

    def __new__(cls, iterable):         #tuple的生成在__new__方法中实现,因此重写__new__方法
        f = (i for i in iterable if (isinstance(i,int) and i > 0))   #筛选出满足条件的元素
        return super().__new__(cls,f)

a = IntTuple([2,-2,'jr',['x','y'],4])
print(a)           #打印:(2, 4)

二.如何为创造大量实例节省内存?

使用__slots__方法关闭动态属性__dict__,可节省有动态绑定产生的内存:

import tracemalloc   #调用此模块查看内存占用

class T1:
    def __init__(self,a,b,c):
        self.a = a
        self.b = b
        self.c = c


class T2:
    __slots__ = ('a','b','c')        #关闭动态绑定方法__dict__节省内存(传入字符串)
    def __init__(self,a,b,c):
        self.a = a
        self.b = b
        self.c = c


tracemalloc.start()
t1 = [T1(1,2,3) for _ in range(1000000)]        #创建一百万个实例对象
end = tracemalloc.take_snapshot()
top = end.statistics('filename')

for stat in top[:10]:
    print(stat)      # size=169 MiB, count=2999942, average=59 B


tracemalloc.start()
t2 = [T2(1,2,3) for _ in range(1000000)]        #创建一百万个实例对象
end = tracemalloc.take_snapshot()
top = end.statistics('filename')

for stat in top[:10]:
    print(stat)      # size=69.3 MiB, count=1000002, average=73 B

可见用__slots__关闭动态绑定后,节省了大约100M的内存,可见动态绑定在生成数据量比较大时是十分浪费内存的。

三.Python中的with语句实现方法?

众所周知,在进行Pyrhon文件操作时,若使用一般方法,要这样写:

file = open('文件路径','r')
file.read()
file.close()

在此代码中,每次进行文件操作,必须手动写上一句file.close()关闭文件释放内存,而在实际操作中,这句话很容易被遗忘而造成内存不能被及时释放,对程序造成影响。

为解决这个问题,Python引入了上下文管理器的概念解决该问题:

with open('文件路径','r') as file:
    file.read()

使用此方法后,我们无需手动关闭文件,程序会在适当时刻自动释放内存。

那么,我们是否可以对类也使用这样的上下文管理器呢?于是,我们就有了以下的写法:

class Sample:
    #创建__enter__方法,开始加载(此为实现上下文管理器必须的方法)
    def __enter__(self):
        print('start')
        return self     #这里必须返回 self 触发操作方法的执行

    #创建操作方法
    def demo(self):
        print('This is demo')

    #创建__exit__方法,退出文件操作,释放内存(此为实现上下文管理器必须的方法)
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('end')

#现在就可以使用创建好的上下文管理器了
with Sample() as sample:
    sample.demo()

执行结果如下:
在这里插入图片描述

调用Python自带的上下文管理器可以使代码实现更简便:

#导入上下文管理器模块
import contextlib

@contextlib.contextmanager   #加上装饰器
def file_open(file):
    #以下两句相当于__enter__方法
    print('file_open')
    yield {}       #此时若使用return代无法继续执行,因此用yield

    #以下相当于__exit__方法
    print('file_close')


with file_open('demo.txt') as file:
    print('file_operation')

打印结果如下:
在这里插入图片描述

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

在面向对象编程中,我们把方法看作对象的接口,直接访问对象的属性可能是不安全的,或者设计上不够灵活,但是使用调用方法在形式上不如访问对象属性简洁。因此,为了弥补对象属性和方法各自的缺陷,我们可以设计一种形式上使用属性访问(保证了调用的方便),实际上调用的是类的方法(保证了调用的安全及更加完善的功能)的解决方案,即可管理的对象属性:

方法实现一(直接调用property()方法):

class A:
    #设置数字
    def set_num(self,num):
        if not isinstance(num,(int,float)):     #如果传入的数字不是整型或浮点型,就会报错
            raise TypeError
        self.num = num      #如果满足条件,成功传入数字

    #获取数字
    def get_num(self):
        return self.num     #返回传入的数字

#此时若想调用类中方法,需这样写:
a = A()
a.set_num(5)    #设置(传入)数字
print(a.get_num())     #获取并打印数字 5


#接下来创建可管理对象属性
class A:
    #设置数字
    def set_num(self,num):
        if not isinstance(num,(int,float)):     #如果传入的数字不是整型或浮点型,就会报错
            raise TypeError
        self.num = num      #如果满足条件,成功传入数字

    #获取数字
    def get_num(self):
        return self.num     #返回传入的数字

    n = property(get_num,set_num)    #将方法调用转化为属性调用

a = A()
a.n = 5     #相当于自动调用 set_num()方法
print(a.n)  #相当于自动调用 get_num()方法  打印数字: 5

实现方法二(设置property装饰器):

class A:
    @property
    def n(self):
        return self.num

    @n.setter
    def n(self, num):
        if not isinstance(num,(int,float)):     #如果传入的数字不是整型或浮点型,就会报错
            raise TypeError
        self.num = num      #如果满足条件,成功传入数字

a = A()
a.n = 5
print(a.n)   #打印数字:5

五.通过实例方法名字的字符串调用方法

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

注:
getattr(object, name[,default])函数: 用于返回一个对象的属性或方法(obeject-对象,name-某个要查询的属性或方法(字符串),defalt-可自行设置参数,在没有对应属性时,触发AttributeError)

map(function, iterable, …)函数: 会根据提供的函数对指定序列做映射。(第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。)

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

#创建三个实例对象
triangle = Triangle(3,4,5)
rectangle = Rectangle(4,5)
circle = Circle(2)

#将各类中的计算面积的方法以字符串形式放入一个列表
area_name = ['get_area','getArea','area']

#统一方法接口
def get_area(shape):
    for name in area_name:
        res = getattr(shape,name,None)
        if res:
            return res()

shape_list = [triangle,rectangle,circle]

area_list = list(map(get_area,shape_list))   #使用map函数返回的时一个对象,需用可迭代对象进行转换
print(area_list)       #打印结果: [6.0, 20, 12.56636]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值