好了,今天我们继续分析Python中的类。我们之前定义类的时候,我们使用的是构造函数。Python中的构造函数非常特殊。它是一个特殊的函数__init__。事实上,在类中,除了构造函数之外,还有很多格式为__XXX__的函数,还有一些__xx__属性。下面一一说说:构造函数Python中所有类的构造函数都是__init__。根据我们的需要,构造函数分为参数化构造函数和非惨构造函数。如果当前没有定义构造函数,系统会自动生成一个不带参数的构造函数。例如:在一个有继承关系的类中,只要显式定义了父类,子类在创建时就会调用父类的构造函数创建父类对象,即使子类没有继承父类的属性父类,它会自动执行。例如:子类要继承并获取父类的属性,需要显式调用父类的构造函数获取,否则只能获取父类的方法。例如:这里需要引入一个新的概念,函数重载。在类内部,如果有多个函数同名,函数参数不同(不同的数字,不同的类型,不同的顺序),那么我们称这些函数为重载函数,不以函数的返回值为依据用于超载。我们在java和C++中都有类似的概念。但是Python是动态编程语言,它的数据是没有数据类型的,所以我们不能在类内部重载函数,所以类内部不能有多个同名方法,所以我们的构造方法要么不写,或??者只能写一个。如果不写,系统会自动生成一个空的无参构造方法;如果写了,那么只能调用这个构造方法。另外,我们在学习装饰器的时候,好像在类内部写了几个同名的方法,比如:这些同名的方法是不是重载关系?不是的,因为不是完整的方法,必须加上@property、@name.setter、@name.deleter的限制才完整,所以这里没有函数重载。析构函数构造函数在创建对象时自动执行,其主要职责是初始化对象。析构函数在对象销毁(执行del或回收)时自动执行,其主要职责是回收对象。如果我们之前没有写析构函数,系统会自动生成一个空的析构函数。接下来我们写一个析构函数。Python中的析构函数方法称为__del__。例如:我们这样称呼它:Execution这里我们要重点关注python的垃圾回收机制。现在程序员不太关注系统的垃圾回收机制,因为现在硬件发展很快,我们有丰富的可用资源。服务器内存小到4G,大部分都是8G起步,不够再加。但是对于一些高端的工作和高精尖的行业来说,垃圾回收机制还是很重要的。那么这里就来梳理下python中的垃圾回收机制。Python中的垃圾回收机制基于引用计数。系统会为每个对象分配一个引用计数器,记录当前对象被使用的次数。既然涉及计数,就有加减运算。系统规定,如果满足以下条件,则执行计数器加1的操作:1.创建一个新对象2.引用一个对象3.将对象作为实参传递。如果满足以下条件,则计数器减1:1.对对象执行del操作2.对对象的引用赋新值3.对象退出当前作用域(最常见的是退出函数作用域)在python中,我们通过sys.getrefcount(objectname)获取对象的当前引用计数器。注意这里的引用计数第一次不一定是1,因为有临时的系统引用。只有当指向对象的引用计数器变为0(第一个初始值)时,对象才会真正被销毁,对象的析构函数才会被执行。例如:输出为attention,上面第一次调用sys.getrefcount(ad)的返回值为4,说明当前系统还有其他临时用途,那么我们只要达到4就回到初始状态到底。最后,delad时,系统临时引用也会被释放。我们目前的运行环境是win+pycharm。我们再改一下代码:输出为从上面的输出可以看出,系统似乎对基本数据类型进行了其他操作,导致其初始引用计数大于我们预期的引用计数。引用数据类型数据正是我们所期望的。只有当对象最终被释放时(当引用计数为0时),__del__析构方法才会被执行。__str__方法首先看我们的代码:当我们打印对象的时候,我们得到的是对象的内存地址。我们可以像打印基本数据类型的数据一样打印我们的参考数据类型吗?比如上面的类Student应该打印他的实例变量。我们现在提到的__str__方法就是完成这个功能的。__str__方法有一个返回值,就是我们执行print时的输出值,所以我们可以在__str__方法中格式化输出内容。例如:outputis从上面的输出可以看出,当我们要输出格式化引用数据类型的数据时,我们必须重写这个类中的__str__方法,在这个方法中可以设置当前内容的输出内容。这个__str__方法是object类的一个方法,因为python中的所有类都是直接或间接派生自object的,所以每一个引用数据类型都有一个__str__方法。我们只需要重写这个方法就可以重写父类的方法。否则系统会默认调用对象中的__str__方法。__dict__有人可能会说,我怎么知道我的类有哪些内置成员(属性和方法)呢?比如上面的__str_中我都不知道有这么一个方法,怎么调用呢?python类中确实有一个属性打印出类的所有内置内容。那就是__dict__。请注意,此__dict__是一个属性而不是方法。调用时不要加()输出。为什么stu1.__dict__的输出内容少,而Student.__dict__的输出内容多?因为stu1是一个对象,对于对象来说,有意义的就是属性,因为方法是所有对象共享的。数据本身是唯一的。执行时只需要携带当前对象的地址就可以执行本类(即self)的方法。而Student是一个类,由属性和方法组成,所以Student.__dict__的输出稍微多一些,包括方法和属性。如果想知道该类的父类有哪些内置成员,打印该类父类的__dict__属性即可。比如我们看一下Student类的父类对象的内置成员,如下:Ojbect.__dict__的输出有点长,自己打印吧,肯定有__str__的说明。好了,今天我们接触到了__init__构造函数、__del__析构函数、__str__内置函数、__dict__属性等,明天继续分析面向对象中的其他内置成员。
