init__subclass

类方法 init_subclass 从 PEP 487 引入,作用是可以在不使用元类的情况下改变子类的行为(好!很有精神!)。也就是说它是独立于元类编程的,也能达到编辑其他类的一种手段。

从文档入手

class PluginBase:
    subclasses = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass

示例中,当有子类继承了 PluginBase ,那么 init_subclass 就会调用。内容也很简单,父类 PluginBase 将他们都存入了 subclasses。

init_subclass 就像是个钩子函数,当子类定义之后触发。

默认实现 object.__init_subclass__ 不执行任何操作。但默认实现不能传递任何参数,否则报错。

值得注意的是,示例中的 init_subclass 第一个参数是 cls 而不是常见的 self 。这是因为这个方法隐式地被 @classmethod 装饰(PEP-487)。这个决定其实是由于多数人忘记给 prepare 加上 @classmethod 装饰了,带来了不好的用户体验(这里);

而且在PEP 3115 中 prepare 被记录为普通的方法,所以已经不好改它了。而作为 3.6 新方法的 init_subclass 因此就有理由隐式装饰了(其实我比较喜欢显式,因为 Python 之禅中提到的 "显示优于隐式"。

尽管 init_subclass 是独立于元类编程的,但类都是由默认元类 type 创建的,那么在 type.__new__() 创建了类之后会有哪些步骤?

首先,type.__new__ 收集类命名空间定义的 set_name() 方法的所有描述符;
其次,这些 set_name 的特定描述符在特定的情况下调用;
最后,在父类上调用钩子 __init_subclass__()。

若类被装饰器装饰,那么就将上述生成的对象传递给类装饰器。

总的来说,__init_subclass__() 是钩子函数,它解决了如何让父类知道被继承的问题。钩子中能改变类的行为,而不必求助与元类或类装饰器。钩子用起来也更简单且容易理解。
虽然本文还提到了 set_name ,但它和 init_subclass 并不相互关联, set_name 主要是解决了如何让描述符知道其属性的名称。

一个简单Python插件系统

from enum import Enum


class PluginEnum(Enum):
    Empty = empty = 0
    Foo = foo = 1


class PluginBase:
    subclasses = {}

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        # 通过 cls.method 设置子类key
        # 线性执行可以将 subclasses 设置为 list,cls.subclasses.append(cls)
        cls.subclasses.setdefault(cls.method, cls)

    def process_start(self):
        # 插件执行前操作
        pass

    def process_run(self):
        # 插件执行操作
        pass

    def process_end(self):
        # 插件执行后操作
        pass

    def process_exception(self):
        # 插件执行异常时异常处理
        pass

    def start(self):
        # 插件调用流程
        try:
            self.process_start()
            self.process_run()
            self.process_end()
        except Exception as e:
            print(e)
            self.process_exception()


class PluginCallableNone(PluginBase):
    method = PluginEnum.Empty

    def __init__(self, *args, **kwargs):
        pass

    def __call__(self, *args, **kwargs):
        pass


class PluginFoo(PluginBase):
    method = PluginEnum.Foo

    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

    def process_start(self):
        print("running in {} process_start".format(self.method))

    def process_run(self):
        print("running in {} process_run".format(self.method))

    def process_end(self):
        print("running in {} process_end".format(self.method))


def main():
    # 获取所有子类(插件)
    print("all subclasses: {}".format(PluginBase.subclasses))
    # all subclasses: {<PluginEnum.Empty: 0>: <class '__main__.PluginCallableNone'>, <PluginEnum.Foo: 1>: <class '__main__.PluginFoo'>}

    # 设置一个默认的可调用的None类,用于访问不存在的子类时的默认值
    # 空插件实现
    callable_none = PluginBase.subclasses.get(PluginEnum.Empty)
    # 访问存在的子类(访问存在的插件)
    print(f"foo is in PluginBase.subclasses: {PluginEnum.Foo in PluginBase.subclasses}")
    # foo is in PluginBase.subclasses: True

    PluginBase.subclasses.get(PluginEnum.Foo, callable_none)(foo="foo").start()
    # running in foo process_start
    # running in foo process_run
    # running in foo process_end

    # 访问不存在的子类(访问不存在的插件)
    print("bar is in PluginBase.subclasses: {}".format("bar" in PluginBase.subclasses))
    PluginBase.subclasses.get("bar", callable_none)(foo="foo").start()
    # bar is in PluginBase.subclasses: False


if __name__ == "__main__":
    main()

都说春雨灵气且浪漫,至少我在北方时候是这么想的。西安的春如四季,可能是一年最糟糕的季节了。在来杭州前我已经准备好降雨频繁的打算了,但浪漫好像过了头。

来了两周多了,每天出门前的事情不是查看窗户有没有锁好,而是检查有没有带着雨伞——即使现在不下雨,那么晚上也会下雨的。可难得周末不下雨。周六收到了洗衣机就抓紧时间洗了衣服,周天趁着有太阳就去西湖去走走。

曾经我想着去深圳或者上海,周末放假时候收拾收拾东西拉着躺椅去沙滩看比基尼美女。可是我留在了杭州,那也只有放晴的时候去西湖看看妹子了,不过就是穿的比较多罢了。

这种事情其实我很有经验,因为往来的目光如交错如蛛网,怎么可能偏巧被发现?

坐在西湖边的椅子上,姑娘们也在这里三三两两。毕竟长时间的下雨,足不出户也需要一段时间来接触大自然。

这也给了我动手的机会——盼望着,盼望着,东风来了,姑娘们的脚步近了。

阳光下,那女生有一头海藻般浓密的长发,微微卷曲,眼睛像海水一样,皮肤很白,象牙色的,整个人看起来懒洋洋的。

而后面,又有一位姑娘好像天上的月亮,也像明亮的星星。可惜我不是一个诗人,否则我会写一万首诗歌来形容她美丽。

我像头机敏的猎豹在广阔无垠的草原上寻找着称心如意的猎物,只可惜三三两两的人里都会有一位男性的存在——他们像极了保卫族群的雄性生物,虎视眈眈提防着周围的其他雄性生物。当然了,也包括我。

可是,没人知道这种事儿偷偷做才有趣呀,别人眉来眼去,我只偷看你一眼。

import decimal


def pi(prec: int) -> decimal.Decimal:
    if prec <= 0:
        return decimal.Decimal(prec)

    with decimal.localcontext() as ctx:
        ctx.prec = prec + 2
        three = decimal.Decimal(3)
        lasts, t, s, n, na, d, da = 0, three, 3, 1, 0, 0, 24
        while s != lasts:
            lasts = s
            n, na = n + na, na + 8
            d, da = d + da, da + 32
            t = (t * n) / d
            s += t
        ctx.prec -= 2
        return +s


if __name__ == "__main__":
    pi(-1)