Python魔法方法漫游指南:构造与析构

305 views, 2021/07/13 updated   Go to Comments

在类 C++ 的语言中,类的构造函数析构函数是非常重要的概念,负责对象的创建和销毁时的相关工作。比如在创建对象时用 new 开辟内存空间,销毁时 delete 释放内存。

而 Python 中似乎没有专门的构造和析构函数,不过也有对应的魔法方法替代构造和析构函数的功能。

构造对象

Python 学习者最熟悉的魔法方法可能就是 __init__ 了,它的作用是初始化对象。

比如说给实例对象的属性进行初始化的工作:

class Foo:
    def __init__(self, x):
        self.x = x

f = Foo(10)
print(f.x)
# 输出:
# 10

如果父类也实现了 __init__ 方法,那么子类中需要显式调用 super() 以确保正确初始化了父类的属性:

class Foo:
    def __init__(self, x=10):
        self.x = x

class Bar(Foo):
    def __init__(self, y):
        super().__init__()
        self.y = y

f = Bar(20)
print(f.x, f.y)
# 输出:
# 10 20

可能你觉得 __init__ 就是构造函数了,但实际上它只负责初始化对象,并不负责创建对象。真正创建对象的是执行得更早的 __new__ 方法:

class Foo:
    def __new__(cls, *args, **kwargs):
        print('new...', args, kwargs)
        obj = super().__new__(cls)
        print(obj)
        return obj

    def __init__(self, *args, **kwargs):
        print('init...', args, kwargs)
        print(self)


Foo('a', x=10)
# 输出:
# new... ('a',) {'x': 10}
# <__main__.Foo object at 0x00000176B9F34AF0>
# init... ('a',) {'x': 10}
# <__main__.Foo object at 0x00000176B9F34AF0>

这里面信息量很大:

  • __new__ 创建了对象,并且执行要早于 __init__
  • __new__ 的第一个参数 cls ,就是需要创建实例的类 Foo
  • __new__ 必须返回一个对象,这个对象其实就是创建出来的类实例,也就是 self 了。
  • __new____init__ 的参数是相同的。

总之,__new____init__ 共同完成了对象的构造工作。

实际上 __new__ 方法通常不太会用到。其主要的应用场景归纳如下。

不可变对象

假设现在要定义一个叫“英寸”的类,并且它是 float 的子类,你会怎么做呢?

__init__ 里实现单位转化是无效的:

class inch(float):
    # 错误的方式
    def __init__(self, arg):
        float.__init__(arg*0.0254)

print(inch(12.0))
# 输出:
# 12.0

因为此时对象已经创建好了,再初始化也没求用。

正确的方式:

class inch(float):
    # 正确的方式
    def __new__(cls, arg):
        return float.__new__(cls, arg*0.0254)

print(inch(12.0))
# 输出:
# 0.3048

其他自定义不可变对象,比如整型、元组等用法也都类似。

单例模式

单例模式是指全局至多只有一个实例的类。

可以这样实现单例模式:

class Singleton:
    def __new__(cls, *args, **kwds):
        it = cls.__dict__.get("__it__")
        if it is not None:
            return it
        cls.__it__ = it = super().__new__(cls)
        it.init(*args, **kwds)
        return it

    def init(self, *args, **kwds):
        pass


class Bar(Singleton):
    pass

first  = Bar()
second = Bar()
print(first)
print(second)
# 输出:
# <__main__.Bar object at 0x00000176B9F349A0>
# <__main__.Bar object at 0x00000176B9F349A0>

不过单例模式在 Python 中不那么常见就是了。

用装饰器也可以实现单例模式,在我以前的文章装饰器入门中有探讨。

元编程

元编程是指代码动态修改和创建类的编程方式。在元编程的黑魔法里,__new__ 方法也占有一席之地。

元编程又是另外一个晦涩的概念了,这里也不展开了,有兴趣的同学看我另一篇文章元类与元编程

析构对象

__new____init__ 是对象的构造器,那么 __del__ 就是析构器,或者叫销毁器。但需要注意的是,Python 有一套自己的垃圾回收机制,__del__ 并不等同于 C 系语言中的析构器,而是更类似于生命周期钩子,它提供了一个对象被销毁时执行自定义逻辑的位置。

比如说你想确保对象被销毁时,文件能够正常关闭:

class FileObject:
    # 确保对象被销毁时关闭文件
    def __del__(self):
        self.file.close()
        del self.file

需要小心的是,如果 Python 解释器(或程序)被强行退出时,__del__ 并不会被执行。

由于 __del__ 可以在执行任意代码时调用,包括从任意线程调用。如果它执行时需要获取锁或者调用任何其他阻塞资源,可能会导致死锁。

所以不要把重要操作完全押宝在上面了,养成定期手动清理资源的好习惯。


本系列文章开源发布于 Github,传送门:Python魔法方法漫游指南

看完文章想吐槽?欢迎来 Issues 告诉我。




本文作者: 杜赛
发布时间: 2021年07月13日 - 08:51
最后更新: 2021年07月13日 - 08:51
转载请保留原文链接及作者
本站使用 Github 评论后端。鉴于国内复杂的网络环境,如遇评论无法加载、发表等问题,请尝试刷新页面,或更换上网姿势。