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

构造与初始化

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

classA:

def__init__(self,a=1):

self.a=a

a=A(a=)

print(a.a)

# 0

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

classA:

def__del__(self):

print('Delete')

a=A()

a=1

# Delete

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

点我回忆一下

)的方法来调用。

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

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

Python通过方法实现了对象的实例化过程,而后调用完成对象的初始化,这一点同其他语言不同。可为什么平时没有见过也能正常实例化呢?因为和一样,Python解释器会寻找基类的方法,而Python中所有类的最终的基类都是,所以当你的继承链中没有一个类定义了这些方法时,最终调用的就是的方法。这里为什么要强调要返回对象呢?因为只有将对象返回了才能调用它的初始化方法。说了这么多,来看一下示例:

classA:

def__new__(cls,*args,**kwargs):

print('new')

print(cls)

self=super().__new__(cls)

returnself

def__init__(self):

print('init')

print(self)

a=A()

# new

#

# init

#

print(a)

#

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

classA:

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

#

print(a)

# None

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

classA:

def__new__(cls,*args,**kwargs):

self=super().__new__(cls)

self.a=1

returnself

a=A()

print(a.a)

# 1

(什么是?→点我回忆一下)而方法所接收的参数,实际上也是经过了:

classA:

def__new__(cls,*args,**kwargs):

print(args)

self=super().__new__(cls)

returnself

def__init__(self,*args):

print('init')

print(args)

a=A(1,2)

# (1, 2)

# {'b': 3}

# init

# (1, 2)

# {'b': 3}

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

classB:

def__init__(self):

print('I am B')

defa(self):

print('B.b')

classA:

def__new__(cls):

self=B()

returnself

def__init__(self):

print('I am A')

defa(self):

print('A.a')

a=A()

# I am B

a.a()

# B.b

不过,这种写法有一定的弊端,容易让别人摸不到头脑。一个可能更合适的写法是工厂方法。到这里,我们看到了Python的灵活性,它允许你对对象的实例化过程“动手动脚”。那到底有没有实际意义呢?下面举几个例子来看看的作用:

伪装

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

单例

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

classA:

_self=None

def__new__(cls):

ifcls._selfisNone:

cls._self=super().__new__(cls)

returncls._self

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

a1=A()

a2=A()

a3=A()

print(a1==a2==a3)

# True

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

继承一个不可变对象

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

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'

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

classA:

def__init__(self):

self.a= [1,2,3]

def__setitem__(self,key,val):

self.a[key] =val

def__str__(self):

returnstr(self.a)

a=A()

a.b=1

a[3] =4

print(a)

# [1, 2, 4]

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

classA(tuple):

def__init__(self,*args,**kwargs):

super().__init__(*args,**kwargs)

a=A(1,2,3)

# TypeError: object.__init__() takes no parameters

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

classA(tuple):

def__new__(cls,n):

tup= (

xforxinrange(n+1)

)

self=tuple.__new__(

cls,

tup

)

returnself

a=A(3)

print(a)

# (0, 1, 2, 3)

a[2] =

# TypeError: 'A' object does not support item assignment

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

和元类一起控制类的产生、实例化等整套流程

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

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

友情链接: https://vonalex.github.io/

欢迎关注 它不只是Python

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180622G0XM8400?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券