类产生多个实例对象
从底层来看,类几乎就是命名空间。从OOP模型来看,编程中有两种对象:类对象和实例对象。类对象提供默认行为,是实例对象的工厂。实例对象是程序中实际使用的处理对象,虽然可能来自同一个类,但是都有自己各自的命名空间。类对象是通过语句定义的,而实例对象是类对象调用产生的,类是产生实例的工厂。
类对象提供默认行为
用class语句会创建类对象。
- class语句会创建一个类对象,并将该对象的引用赋值给变量名(类名),他们都是在导入其所在文件时执行。这一点与def语句类似。
- class内的赋值语句会创建类的属性。类也是命名空间,该空间中的属性也可以用“点”运算调用。
- 类属性提供对象的状态和行为。
实例对象是具体的元素
- 像函数那样调用类对象会创建新的实例对象。
- 每个实例对象继承类的属性并获得了自己的命名空间。
- 在类的方法内,对self的属性做赋值运算会产生每个实例自己的属性。在类方法内,self就代表了由该类创建的实例对象。对self属性的赋值就是对实例对象中属性的赋值。
>>> class firstclass: #定义了一个类
... def setvalue(self,value):
... self.testvariable=value
... def printself(self):
... print(self.testvariable)
...
>>> test=firstclass() #构造了一个实例对象
>>> test.printself() #现在调用打印会报错
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in printself
AttributeError: 'firstclass' object has no attribute 'testvariable'
>>> test.setvalue(1234) #用方法赋值后创建了属性
>>> test.printself() #正常打印
1234
>>>
类通过继承进行定制
在Python中,实例从类中继承,而类继承于超类:
- 超类列在了class头语句中的括号内
- 类从超类中继承属性。
- 实例会继承所有可读取类的属性
- 每个object.attribute都会开启新的独立搜索。python会对每个属性表达式进行类树的独立搜索。
- 逻辑修改是通过创建子类,而不是修改超类。在子类中重新定义要修改的函数或变量名,就可以由子类对象取代并定制所继承的行为。
>>> class secondclass(firstclass): #继承firstclass
... def setvalue(self,value): #修改了函数
... self.testvariable2=value
... def printself(self):
... print(self.testvariable2)
...
>>> test2=secondclass()
>>> test2.setvalue(12345)
>>> test2.testvariable #函数设置的变量变为testvariable2,函数的作用改变了。
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'secondclass' object has no attribute 'testvariable'
>>> test2.testvariable2
12345
>>> test2.printself()
12345
>>> test.printself() #完全不应该之前对象的使用,提供代码的复用性
1234
>>>
Python的类也存在于模块文件中,因此类也是属于模块的属性,模块反应的是一个Python文件,而类只是文件中的语句。按照惯例,Python中的类名应该以一个大写字母开头,以便更好的与模块名区分开来。
类可以截获运算符
通过对类中特定方法名称的定义,可以让python程序响应相应的运算符(运算符重载)。运算符的运算都要经过类的实现来控制。
运算符的重载可以让自己定义的类,像内置对象一样可以用运算符进行特定的运算。运算符重载主要有以下几个概念:
- 以双下划线命名的方法__X__是特殊的钩子。Python运算符重载是用特殊的方法来拦截相应的运算符,比如当类中出现了__add__方法,就会拦截+运算。Python的运算符与特殊命名方法之间,定义了固定不变的映射关系
- 当实例出现在运算中时,这类特殊的相应的方法会自动调用。(这有点像响应函数,固定的方法相应特定的运算符。)
- 类可覆盖(重载)多数内置运算符。有几十种特殊运算符重载的方法的名称,几乎可截获并实现内置类型的所有运算。
- 运算符覆盖方法没有默认参数,也不需要。如果类中没有定义运算符的重载方法,那么说明该类不支持该运算。
- 运算符可让类与Python的对象模型相集成。
>>> class thirdclass(secondclass):
... def __add__(self,other): #重载了运算符+,让类对象中的变量做运算加法。执行self+other运算
... return(self.testvariable2+other)
...
>>> test3=thirdclass()
>>> test3.setvalue(111)
>>> test3+333
444
>>>
运算符重载的方法是可选的,主要是Python程序员开发工具的人在使用,而不是Python应用程序人员使用,在很多情况下,定义一些方法比重载运算来得更方便与明确。
作为类的设计者,可以选择或者不选择运算符的重载,这取决于有多想让你的类看上去是内置类型。一般来说,只有遇到数据类型为数学对象时,才会有可能使用运算符重载。
特殊参数self和__init__构造函数,是python中OOP的两个基石。
使用print函数打印对象信息时,实际上是调用的对象的__str__方法返回的字符串。
在扩展子类时,可以在定制子类的函数时,调用父类的方法(从逻辑上来说就是在父类的基础上进行修改或增强,提高了代码的可维护性)。
面向对象编程的几个重要概念
- 实例创建:创建的实例,填充实例的属性,各个实例之间都是独立的命名空间,哪怕是相同类产生的
- 行为方法:操作实例中的属性是通过类中的行为方法来封装逻辑的。
- 运算符重载:类中可以对Python中的运算符进行重载,比如打印print对应函数__str__
- 定制行为:重新定义子类中的方法使其特殊化,在子类中可以调用父类的方法,以达到在父类基础上的定制逻辑,提供代码的复用性,可维护性。
- 定制构造函数:复用父类的构造函数,定制子类自己的构造函数。
对象持久化
Python程序在运行时,都是在内存中运行。如果推出程序,内存释放后,对象将消失,所以就要使用持久化功能,将对象保存起来。
Python提供三个标准的库模块来实现:
- pickle:任意对象和字节串之间的序列化,对内存中的任意的Python对象,它都能转化成为字节串,并可以导入内存重新使用。
- dbm:实现以键访问的文件系统,可以存储字符串
- shelve:使用另外两个模块,按照键把python系统存储在文件中,综合了以上两个模块,通过键来访问或获取指定的字节串。shelve可以在编程的时候很像字典,但是与它唯一的区别就是,在使用之前要像文件一样打开,并像shelve对象内添加“键(任意的字符串)\值(这里的值可以是任何python对象)”,并在使用完成之后关闭。
import shelve
db = shelve.open("filename")
for object in dict:
db(object)=dict[object]
db.close()