目录
1.一切皆对象
2.属性覆盖机制
3.特殊属性
4.__getattr__()方法
5.动态类型
6.内存管理
1.一切皆对象
列表list的属性中有一种特殊的方法__add__(),它可以使两个list的对象相加,如[1, 2, 3]+[4, 5, 6]实质是运行了[1, 2, 3]__add__([4, 5, 6]),结果为[1, 2, 3, 4, 5, 6]。类似的特殊方法还有:__mul__()表示乘法,__or__()表示or。在这里需要注意,列表在python中不可以相减,但我们 可以通过__sub__()方法来添加减法操作的定义,如
除了列表的运算实质是运用特殊的方法外,列表中元素的引用也用到了特殊方法__getitem__(),如[1, 2, 3, 4, 5].__getitem__(3),就是从列表中引用第四个元素。此外,关于列表或者字典元素的操作的方法还有
与运算符类似,许多内置函数也都调用了对象的特殊方法,比如__len__()、__abs__()、__int__()等。
2.属性覆盖机制
之前在继承中提到python的属性覆盖原理,它实际上运用到了__dict__()方法,它是一个词典,键为属性名,对应的值为某个属性,看一个例子
输出结果为
通过运用__dict__()方法可以看到,每个对象或者类中的属性都储存在一个字典中,如果我们用dir函数查看对象summe的属性话,回发现summer的属性分成了四层:summer/Chicken/Bird/object,当我们需要调用属性的时候,python会一层层往下遍历,知道找到那个属性为止,这也是属性覆盖的原理所在。
注意,如果是对对象属性进行赋值,那么python不会分层深入查找,以新的Chicken类的对象autumn为例
可以看到,对autumn的属性赋值,python在自身的__dict__()中找不到对应的属性就会直接在__dict__()中增加,不会影响Bird类的属性。
3.特殊属性
如果属性A会随属性B而变化,那么属性A就是一个特殊属性(特性property),比如在Chicken类增加一个表示成年与否的特性adult,当对象age大于1时,adult为真:
property()最多可以加载四个参数,前三个参数为函数,分别用于设置获取、修改和删除特性,最后一个参数为特性的文档,可以为一个字符串,起说明作用。再看一个例子
4.__getattr__()方法
在通过__dict__()方法无法调取对象属性时,python会调用__getattr__()方法来即时生成该属性,如
需要注意的是,__getattr__()只能用于查询不在__dict__系统中的属性。此外,__setattr__(self, name, value)和__delattr__(self, name)可用于修改和删除属性。它们的应用面更广,可用于任意属性。
5.动态类型
动态类型(Dynamic Typing)是Python的另一个重要核心概念。变量的赋值就是动态类型的体现。对a = 1,我们可以用id()函数查看对象的编号
在这里,可以随时变化的变量名就是可以变更指向的引用,比如我们可以用b = a来变更指向,使得b也可以引用1。这时,如果把a变更为a+2,那么a就变成指向3了,但是b还是指向1,如图
可以看出,改变一个引用,并不会影响其它引用的指向,也就是各个引用各自独立,互不影响。但有种情况需要注意:
其实,这种情况的实际上表示的是我们通过list1[0]改变了列表里面的元素对象,并没有改变列表对象,list1和list2还是原来那个list1和list2。
函数中参数的传递也是一种引用的传递,当调用函数时,参数a传递给函数,函数中的变量x就指向了参数a所指的对象(相当于赋值)。如果参数是不可变对象,那么引用参数a和变量x直接相互独立,互不影响。如果传递的是可变对象,则不同,如
6.内存管理
语言的内存管理是语言设计的一个重要方面。对象内存管理是基于对引用的管理。我们可以用标准库中sys包中的getrefcount()来查看某个对象的引用次数。因为参数传递实际上是创建了一个临时的引用,所以getrefcount()的结果会比期望多1.如a = [1, 2, 3]中,a的引用计数是2,b = a,b的引用计数是3。如果是a = [1,2,3],b = [a, a],则a的引用计数是4。
两个对象或者对象自身之间可以相互引用,这样就构成了引用环(Reference Cycle)。引用环会给垃圾回收机制带来很大麻烦。垃圾回收机制就是当无用的对象达到一定条件时,需要对无用的对象进行垃圾回收以释放内存,其原理是某个对象的引用计数降为0,即没有任何引用指向该对象时,它就成为了要被回收的垃圾。
在python运行时,当分配对象(Object Allocation)和取消分配对象(Object Deallocation)的次数的差值高于某个阈值时,垃圾回收机制会启动,可以通过gc模块的get_threshold()方法,查看该阈值:
上面的700就是启动回收机制的阈值,后面两个10是与分代回收相关的阈值。所谓分代回收也是python回收的一种策略,python将所有对象按存活时间分为0,1,2三代,最新的对象为0代,0代的对象经过一定次数的回收后,存活下来的对象会进入1代,python对0代和1代的对象进行回收,如此类推。这里的“一定次数”就是上图的两个10,表示每10次0代垃圾回收,会配合1次1代的垃圾回收,而每10次1代的垃圾回收,才会有1次2代的垃圾回收。对于垃圾回收机制的阈值,我们可以用set_threshold()来调整。
上面还说到,引用环的垃圾回收比较复杂,因为组成引用环的两个对象的引用计数都没有降到0,python不会对它进行正常的回收。python的处理方法是:复制每个对象的引用计数(以2个对象a、b为例),记为gc_ref_a和gc_ref_b,python开始遍历所有对象,当遍历到对象a引用b时,相应的gc_ref_b会减少1,同理遍历到b引用a时也一样。这样,没用的对象a和b最终的计数会变为0而被当成垃圾回收,其它未变成0的则继续被python使用。