一.如何派生内置不可变类型并修改其实例化行为
问题:我们想自定义一种新类型的元组,对于传入的可迭代对象,我们只保留其中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]