第二讲
一、如何派生内置不可变类型并修改其实例化行为
这里有一道练习题,我们想定义一个新类型的元组,对于传入的可迭代对象,我们只保留其中int类型并且值还要大于0.
或许我们可以这么写,尝试一下:
class Intuple(tuple):
def __init__(self,iterable):
tu = (i for i in iterable if isinstance(i,int) and i > 0)
super().__init__(self,tu)
tu = Intuple([2,22,-1,['x','y'],'stupid',8])
print(tu)
但是结果不尽人意,创建失败:
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
我们在这里需要考量一下缘由,tuple是内置不可变类型,对于它的new方法我们需要额外注意,我们来做一个试验:
tu = tuple.__new__(tuple,'abc')
print(tu)
tuple.__init__(tu,'abc')
print(tu)
--------------------------
结果:
('a', 'b', 'c')
('a', 'b', 'c')
在这里我们需要说明几点:
- 首先,如果我们去查阅tuple类的new方法,发现其内容如下:
这容易引起我们的误解,其实它的参数并未明确表达,倘若我们按照给定的文档内容去创建tuple对象,会报错:
tu = tuple.__new__(1,2,3)
-----------------------------
结果:
TypeError: tuple.__new__(X): X is not a type object (int)
反馈回的错误意思本人还未能明白,经过和老师的讨论知悉,这里的参数形式应该是这样的:
tu = tuple.__new__(tuple,iterable)
括号内前面的tuple
就是告诉基类,具体哪个类在创建对象,这里是tuple类正在创建对象。这里的基类暂且不需要管它,是底层封装的类。后面的iterable意思试说明只能传入一个可迭代的对象。
- 回归到一开始,我们把代码搬运过来:
tu = tuple.__new__(tuple,'abc')
print(tu)
tuple.__init__(tu,'abc')
print(tu)
--------------------------
结果:
('a', 'b', 'c')
('a', 'b', 'c')
结果表明,通过new方法且传入可迭代对象,就可以创建并返回一个元组了,init方法的初始化并不负责创建元组对象。
这就说明,一开始我们企图通过init魔法方法来定义新类型元组,是不可行的。
我们需要通过改写new方法来派生一个新类型的元组类:
class Intuple(tuple):
def __new__(cls,iterable):
f = [i for i in iterable if isinstance(i,int) and i > 0]
return super().__new__(cls,f)
tu = Intuple([2,22,-1,['x','y'],'stupid',8])
print(tu)
-------------------------
结果:
(2, 22, 8)
二、如何为创建大量实例节省内存(__slots__属性)
现在有一个练习:
在游戏开发中,有一个玩家Player,每有一个在线玩家,在服务器内则有一个player的实例,当在线的人数很多的时候,将产生大量实例(百万级)。
如何降低这些大量实例的内存开销?
**这里要介绍一种的新的方法,定义__slots__
属性。**它可以帮助我们减少内存的使用,原因接下来讲解:
案例代码如下:
class Player1(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
#主程序入口
if __name__ == '__main__':
p1 = Player1('1','Tom',1)
p2 = Player2('2','Amy',1)