Skip to content

Latest commit

 

History

History
239 lines (180 loc) · 8.01 KB

object_oriented6.md

File metadata and controls

239 lines (180 loc) · 8.01 KB

一切皆对象——Python面向对象(六)

我们知道,在类实例化的时候如果需要给定一些初始参数,需要在类中定义__init__方法。(注:我们约定实例化是指创建一个类的实例)

class A:
    def __init__(self, a=1):
        self.a = a

a = A(a=0)
print(a.a)
# 0

当一个对象不再需要被使用时,为了释放内存,Python的垃圾回收器会调用该对象的__del__方法,将对象占用的内存释放。

class A:
    def __del__(self):
        print('Delete')
    
a = A()
a = 1
# Delete

直接运行上述程序发现打印出了Delete。这是因为A的一个对象的标识符a被拿走去引用了数字1,程序中不再有标识符引用这个对象,所以这个对象没必要再活下去了,被垃圾回收器析构掉了。在释放过程中,垃圾回收器会调用对象的__del__方法,所以打印出了Delete。通常,没有特殊需求,我们的类中不必定义__del__方法,垃圾回收器会自动寻找基类(还记得基类是谁吗?)的__del__方法来调用。

__init__很像我们在其他变成语言中遇到的构造函数,只不过这里我们称其为初始化函数显得更为贴切(虽然在其他语言中,构造函数的作用也是实例初始化)。有什么区别吗?实例化一个对象通常是如下过程:

![](C:\Users\houlu\Desktop\公众号\Object Oriented\object oriented6(new).png)

通常,中间这个过程是程序员们无法控制的过程(看起来也没有控制的必要)。然而在Python中,存在一个这样的特殊方法__new__。它把中间这本该解释器做的事揽到了自己身上。所以在Python中,整个过程是这样的:

![](C:\Users\houlu\Desktop\公众号\Object Oriented\object oriented6(new).png)

Python通过__new__方法实现了对象的实例化过程,而后调用__init__完成对象的初始化,这一点同其他语言不同。可为什么平时没有见过__new__也能正常实例化呢?因为和__del__一样,Python解释器会寻找基类的__new__方法,而Python中所有类的最终的基类都是object,所以当你的继承链中没有一个类定义了这些方法时,最终调用的就是object的方法。

这里为什么要强调__new__要返回对象呢?因为只有将对象返回了才能调用它的初始化方法。

说了这么多,来看一下示例:

class A:
    def __new__(cls, *args, **kwargs):
        print('new')
        print(cls)
        self = super().__new__(cls)
        return self
        
    def __init__(self):
        print('init')
        print(self)

a = A()
# new
# <class '__main__.A'>
# init
# <__main__.A object at 0x00000239302EF588>
print(a)
# <__main__.A object at 0x00000239302EF588>

我们看到,在a = A()后,首先__new__方法被调用了,它的第一个参数cls传入的是类本身,之后,我们调用了父类的__new__方法来产生一个对象self(父类其实就是object)并返回。之后,这个self被传入了__init__方法完成了它的初始化。如果不返回一个对象,那么__init__就不会被调用,实例化过程也就失败了:

class A:
    def __new__(cls, *args, **kwargs):
        print('new')
        print(cls)
        self = super().__new__(cls)
        # return self
        
    def __init__(self):
        print('init')
        print(self)

a = A()
# new
# <class '__main__.A'>
print(a)
# None

事实上,我们完全可以在__new__里完成对象的初始化工作:

class A:
    def __new__(cls, *args, **kwargs):
        self = super().__new__(cls)
        self.a = 1
        return self

a = A()
print(a.a)
# 1

什么是super()?。而__init__方法所接收的参数,实际上也是经过了__new__

class A:
    def __new__(cls, *args, **kwargs):
        print(args)
        self = super().__new__(cls)
        return self
    
    def __init__(self, *args):
        print('init')
        print(args)
    
a = A(1, 2)
# (1, 2)
# {'b': 3}
# init
# (1, 2)
# {'b': 3}

如果我们把类A比作一个工厂,__init__比作一条流水线,那么__new__就像车间主任一样。车间主任可以决定给流水线上送上什么材料,可以决定用哪一条流水线,甚至可以决定在这个工厂里偷偷生产B工厂的货物,真正做到挂A头,卖B肉:

class B:
    def __init__(self):
        print('I am B')
        
class A:
    def __new__(cls):
        self = B()
        return self
    
    def __init__(self):
        print('I am A')
        
a = A()
# I am B

不过,这种写法有一定的弊端,容易让别人摸不到头脑。一个可能更合适的写法是工厂方法

到这里,我们看到了Python的灵活性,它允许你对对象的实例化过程“动手动脚”。那__new__到底有没有实际意义呢?下面举几个例子来看看__new__的作用:

  1. 伪装

    上面看到了,__new__能够帮助类伪装身份。(希望你能看懂我在扯淡)

  2. 单例

    单例是指一个实例在一个程序中永远只有一个,在第一次创建它之后,所有的创建过程都把它返回,而不是创建一个新的实例。有了__new__,我们可以很方便地实现单例:

class A:
    _self = None
    def __new__(cls):
        if cls._self is None:
            cls._self = super().__new__(cls)
        return cls._self

为了确定A的实例是否只有一个,我们通过id函数来查看他们的内存地址是否一致:

a1 = A()
a2 = A()
a3 = A()
print(a1 == a2 == a3)
# True

看到了吧,a1a2a3完全是同一个对象。

  1. 继承一个不可变对象

    Python中不可变对象是指tupleintfrozenset等等这些对象:

a = (1, 2)
a[2] = 3
# TypeError: 'tuple' object does not support item assignment
b = 1
b.a = 2
# AttributeError: 'int' object has no attribute 'a'

而对于一个普通的类的对象,则没有这些限制:

class A:
    def __init__(self):
        self.a = [1, 2, 3]
    def __setitem__(self, key, val):
        self.a[key] = val
    def __str__(self):
        return str(self.a)

a = A()
a.b = 1
a[3] = 4
print(a)
# [1, 2, 4]

如果想要继承一个不可变对象类,可能会有一些问题:

class A(tuple):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
a = A(1, 2, 3)
# TypeError: object.__init__() takes no parameters

想要通过A(1, 2, 3)的方式来创建一个不可变的a,只定义__init__是不可行的,因为这些不可变对象类没有定义__init__方法。从错误信息也可以看出,解释器直接跳过了tuple__init__。所以,我们需要重写__new__方法来继承。例如,想要继承tuple来获得一个产生0~n的元组:

class A(tuple):
    def __new__(cls, n):
        tup = (x for x in range(n + 1))
        self = tuple.__new__(
            cls,
            tup
        )
        return self
    
a = A(3)
print(a)
# (0, 1, 2, 3)
a[2] = 0
# TypeError: 'A' object does not support item assignment

这里也可以理解,因为__init__的作用是修改对象中的属性的值,这与不可变对象本身就是一个矛盾,所以不可变对象只有__new__方法,不会有__init__方法。

  1. 元类一起控制类的产生、实例化等一系列过程

我们放在元类内容中介绍。

__new__属于Python中比较高级的特性,绝大多数情况下不会用到。而这些特性都有一个鲜明的特点——双刃剑。理解得透彻,则可以利用它们写出优雅高效的程序;模棱两可,则可能搬起石头砸了自己和周围人的脚。比如上面的伪装。