Python类中的魔法方法之 __slots__原理解析

  • Post category:Python

下面就为大家详细讲解“Python类中的魔法方法之__slots__原理解析”。

一、背景介绍

在Python中,每个对象实例都会占用一定的空间。同时,我们经常会遇到一些类需要创建大量对象实例的场景,例如在Web开发领域中,经常需要创建大量的实例(如请求对象)。
在这种场景下,如果每次创建实例的时候都需要动态地添加属性,那么就会比较耗费时间和内存,因为Python需要为每个实例额外的空间来储存这些属性。而Python类中的__slots__魔法方法可以帮助我们优化这种情况。

二、__slots__方法的作用

在Python中每个实例都有一个__dict__属性,用来存储实例的属性。__slots__ 方法允许我们事先声明某些属性名称,来告诉Python不要再为这些属性分配内存和创建字典了,从而优化空间和执行效率。
举个例子,如果我们定义这样一个类:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

那么每次创建一个这个类的实例,都会动态地为 name,age 新建一个对象属性的字典__dict__,就算没有实际赋值的__dict__,也会存在对象的空间中。这里就又有了一个难摆脱的问题,如果我们有一些非常非常多的实例对象呢?显然这会造成很大的内存浪费,增加了Python GC(垃圾回收机制)的压力。

而如果使用__slots__方法,可以声明这些属性,从而让Python不再动态分配内存和创建实例的属性字典__dict__。__slots__方法使用一个元组来指定这些属性,例如:

class Person:
    __slots__ = ('name', 'age')
    def __init__(self, name, age):
        self.name = name
        self.age = age

这里我们定义了一个Person类,声明了nameage属性。此时,调用类的__slots__后,创建的实例就不再具有__dict__属性,而是会在类中直接保存这两个属性。这样,即使我们创建很多实例,也不会出现空间浪费的问题了。

除了减少内存浪费,使用__slots__还可以提高查询和赋值的速度,因为Python不再使用字典来维护对象属性。不过需要注意的是,Python中的__slots__只对当前类的实例起作用,对继承的子类不会有影响。

三、一个简单的应用实例

为了更好地理解__slots__,我们来看一个具体的例子。下面我们定义一个foo类,含有a,b,c三个属性。我们可以使用__dict__查看实例对象的内存占用情况。

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

f=foo(1,2,3)
print(f.__dict__)

输出为:

{'a': 1, 'b': 2, 'c': 3}

可以看到,这个对象占用了可观的内存:同时含有三个属性及其键。

接下来,我们改变这个类的定义方式,添加__slots__属性。

class foo():
    __slots__=('a','b','c')
    def __init__(self,a,b,c):
        self.a=a
        self.b=b
        self.c=c

f=foo(1,2,3)
print(f.__slots__)

输出为:

('a', 'b', 'c')

可以看到,输出内容已经不再是一个字典,而是一个元组,元素为a,b,c3个属性名。 另外,使用__slots__定义的类同样可以赋值和属性提取 :

print( f.a,f.b,f.c )
f.a,f.b,f.c=4,5,6
print( f.a,f.b,f.c )

输出为:

1 2 3
4 5 6

从输出结果和我们预料的相符: 使用了__slots__类型的类占用的内存更少,同时还能够正常使用赋值和属性提取两个基本操作。

四、Tips

  • 所有新式的类都会默认继承object,所以不需要在类定义时继承于object
  • __slots__ 在类继承中只对自身起作用,不会对父类或者子类产生影响
  • 利用__slots__,可以限制动态地添加属性(__dict__

到此结束,希望对大家有所帮助。