Python中的魔术方法(1)—构建

魔术方法简介

Python中有一些特别的方法是以被双下划线包围的,这些方法通常不需要主动调用,却可以让我们的类实现一些特殊的功能。比如实现 __add__() 方法,即可让我们的类支持 + 运算符操作。今天带来的是3个与构建相关的魔术方法。

__init__()

这个方法对于使用Python的人应该都不陌生吧,这就是类的初始化方法,在里面做一些初始化实例的工作,在实例化一个类的时候被调用,如运行到 a = A() 时,即会调用 A 中的该方法。相信大家都很熟悉了,这里就不过多介绍了。但是,创建一个实例的时候可不仅仅是调用这一个方法,毕竟,我们都知道, __init__(self) 的第一个参数是 self,即这个实例本身,也就是说调用到它的时候已经有了这个实例, 那么这个实例是从哪里来的呢,那就要说道下一个魔术方法了。

__new__()

__new__() 方法也是在类的实例化的时候被调用,而且在 __init__() 之前就被调用,正是由 __new__() 创建并返回一个实例,接下来传递给 __init__() 方法,由他进行实例的初始化。这个方法可能大家平时应用的机会比较少,下面举例进行说明。

调用时机展示

class NewObject(object):
    def __new__(cls):
        print('new')
        return super().__new__(cls)

    def __init__(self):
        print('init')

if __name__ == '__main__':
    n = NewObject()

# output:
# new
# init

可以从打印的顺序看到先执行的 __new__() 方法,再执行 __init__() 方法。

单例模式

可以使用 __new__()来实现一个单例模式,如下

class Single(object):
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

if __name__ == '__main__':
    s1 = Single()
    s2 = Single()
    print(id(s1), id(s2), s1 is s2)

# output
# 3017957719904 3017957719904 True

同理,可以扩展为线程安全的单例,有兴趣的可以自己试一下。

元编程

通过继承type并复写 __new__() 方法可以定制子类的行为,这种做法称作元编程,如

class Meta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        for attr in namespace:
            if attr.startswith('test'):
                raise Exception(f'不能使用以test开头的方法: {attr}')
        return super().__new__(mcls, name, bases, namespace, **kwargs)

class Base(metaclass=Meta):
    pass

class Test(Base):
    def test_print(self):
        pass

# output
# Traceback (most recent call last):
#   File "...", line ..., in <module>
#     class Test(Base):
#   File "...", line ..., in __new__
#     raise Exception(f'不能使用以test开头的方法: {attr}')
# Exception: 不能使用以test开头的方法: test_print
# 
# 进程已结束,退出代码为 1

例子中子类不能定义以test开头的任何方法名,否则抛出异常。另外,要注意到上述代码只是在进行类型定义,并没有做实例化之类的操作,也会报错,可以认为是在“编译期”进行的检查。这种编程方法一般在框架编写中应用较多,如Django的ORM,便是使用元编程实现的。

注意事项

需要注意的是 __init__()__new__() 这两个方法的参数除了 selfcls 有所区别外,其他要保持一致。

另外在 __new__() 中创建实例一般是要显式调用其他类的 __new__() 方法,否则会造成类型错误的问题,如:

class NewObject1(object):
    def __new__(cls, *args, **kwargs):
        return object().__new__(cls)

class NewObject2(object):
    def __new__(cls, *args, **kwargs):
        return object()

if __name__ == '__main__':
    a = NewObject1()
    b = NewObject2()
    print(type(a), type(b))

# output:
# <class '__main__.NewObject1'> <class 'object'>

__del__()

与构造方法相对应, __del__() 即是析构方法,是在实例的生命周期结束后,进行垃圾回收时调用的方法,可以将资源释放等的代码写在里面。这里要注意的是执行的时机,并不是说在进行 del 的时候就会被调用,需要该对象的所有引用均被释放才行,另外解释器的垃圾回收也并不一定是在引用释放的那一刻就会生效,所以不要将状态控制代码放在里面。

import time

class TestDel(object):
    def __del__(self):
        print(time.ctime(), 'del')

a = TestDel()
b = a
del a
time.sleep(1)
del b
time.sleep(1)
print(time.ctime(), 'end')

# output
# Wed Sep 15 14:20:12 2021 del
# Wed Sep 15 14:20:13 2021 end

这里我们可以看到del和end相隔了一秒,即在 del a 的时候并没有触发 __del__()del b 后所有引用均被删除,才触发 __del__() 。另外,即不显式调用 del ,程序结束之后也会触发垃圾回收,如

a = TestDel()
print(time.ctime(), 'end')

# output
# Wed Sep 15 14:27:29 2021 end
# Wed Sep 15 14:27:29 2021 del

从中可以看到程序执行完毕(输出end),然后触发 __del__() (输出del)。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值