Python 实现slots的继承

__slots__是python的一大神器。

它有两大的特点:

  1. 限制 MonkeyPatch,阻止python的一部分动态性。将对象中允许拥有的实例属性事先约定好
  2. 使用它会提升实例属性访问性能

python文档中这样介绍它

3.3.2.4. slots
slots 允许我们显式地声明数据成员(例如特征属性)并禁止创建 dictweakref (除非是在 slots 中显式地声明或是在父类中可用。)
相比使用 dict 此方式可以显著地节省空间。 属性查找速度也可得到显著的提升。

首先第一点,python的动态性一部分源自于__dict__,属性都保存在__dict__的一个字典中,我们可以随时向这个字典添加新内容,这是MonkeyPatch的能力。
而当我们显示的声明了__slots__,python将不会给这个类创建__dict____weakref__

  • 注: __weakref__是软引用,类似于指针,不会增加引用计数器

没有了__dict__我们便不能随意创建实例属性,而必须遵守__slots__的约定。

对于性能而言,使用了__slots__后,属性会直接存储到对象中,而不是__dict__中,相当于少了一次检索步骤。

__slots__的两大优点,对于python来说,是难以拒绝的诱惑。既能限制不必要的动态性,又能提高性能!
但是__slots__遇到继承时,就会出现很多问题。准确的说,__slots__不会被继承。当我们用一个不带有__slots__的子类,继承带有__slots__的父类时。子类还是会生成__dict____weakref__

这有时不是我们所希望的,也许我们希望父亲严明的纪律可以被孩子们传承

用metaclass来解决这个问题

这是之前写的一个metaclass,创建新的类对象时,会进行以下逻辑。

  1. 判断是否具有显示声明的父类
  2. 寻找父类是否有slots,没有则跳过
  3. 寻找子类是否的slots,如果无则置为()
  • 注: 下列代码只做了单继承的逻辑
class SlotsMeta(type):
    def __new__(mcs, *args, **kwargs):
        if super_class := args[1]:
            if getattr(super_class[0], "__slots__", None):
                __attr = args[2]
                if not __attr.get("__slots__"):
                    __attr["__slots__"] = ()
                    args = (args[0], args[1], __attr)
        return super().__new__(mcs, *args, **kwargs)


class SlotsClass(metaclass=SlotsMeta):
    __slots__ = ('a', 'b', 'c')

    def __init__(self):
        self.a = 1
        self.b = 2
        self.c = 3


class SubClass(SlotsClass):

    def __init__(self):
        self.d = 4
        self.e = 5
        self.a = 7

这样会变成什么样的结果呢?

  1. 父有子无:子类会被约束为__slots__ = (),完全遵循父类的规则
  2. 父有子有:无变化,子类的的实际范围为父子__slots__ = ()的并集
  3. 父无子有:无变化
  4. 父无子无:无变化

实际上我们的目的正是解决,父类规定的__slots__约束不了无__slots__子类的问题。这个结果令人非常满意

注意这里子类的d,在pycharm等IDE中不会提示错误,因为pycharm无法探知你的metaclass做了这样 逆天改命 的逻辑。

但报错还是会正常存在的

注意!

需要注意一点,__slots__类对实例属性的约束,而类对象无法通过该属性,约束自己。即为类对象添加新属性,仍然是被允许的。

新增类属性

仍旧被允许

按照正常思路,也许我们应该到metaclass写一个__slots__,但实际上这是不被允许的。
抽空找时间我会考虑下有无可行性。

你可能感兴趣的:(Python 实现slots的继承)