Python面向对象编程
面向对象编程(Object Oriented Programming,简称OOB)是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
1 | # 采用面向对象编程,将Student这种数据类型视为一个对象,这个对象拥有name和score两个属性。如果要打印学生的成绩,首先必须创建出这个学生对应的对象,然后调用对象的方法,让对象自己把对应的数据打印出来。 |
类和实例
面向对象最重要的概念就是类(Class)和实例(Instance),类是抽象的模板,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
定义类是通过class关键字:
1 | class Student(object): |
变量指向的就是类的实例,0x10b0a60b8是内存地址,每个实例的地址都不一样。
1 | bart = Student() |
类可以起到模板的作用,因此在创建实例的时候,把一些必须绑定的数据强制填写进去。通过一个特殊的__init__方法。
1 | class Student(object): |
特殊方法
__init__前后分别有两个下划线,__init__方法的第一个参数永远是self,表示创建实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。有了init方法,在创建实例的时候,就不能传入空的参数了,必须传入与init方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传递进去。
数据封装
每个实例都拥有自己的数据。可以通过函数来访问这些数据,实例本身就拥有数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法。
1 | class Student(object): |
访问限制
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线,在Python中,实例的变量名如果以开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
1 | class Student(object): |
好处:这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
外部无法获取name和score属性,就需要给类增加响应的get和set方法:
1 | class Student(object): |
在Python中,变量名类似xxx的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用name、score这样的变量名。有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
集成和多态
当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
1 | class Animal(object): |

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了,
获取对象信息
- 使用type()可以判断对象的类型
- 使用isinstance()可以判断类的继承关系
- 使用dir()
- 使用dir()可以获取一个对象的所有属性和方法,它返回一个包含字符串的list
实例属性和类属性
如果类本身需要绑定一个属性呢?可以直接在class中定义属性,这种属性是类属性,归类所有
1 | class Student(object): |
这个类属性虽然归类所有,但是所有实例都可以访问到。
1 | # 创建实例s |
编写程序时,实例属性和类属性不应该使用相同的名字
面向对象高级编程
使用slots
在Python中,我们可以动态地给实例绑定任何属性和方法。
1 | class Student(object): |
在定义class的时候,定义一个特殊的变量slots,可以显示该class实例能添加的属性。
1 | class Student(object): |
1 | s = Student() |
slots定义的属性仅对当前类实例起作用,对集成的子类是不起作用的,若在子类中也定义slots属性,则子类实例允许的属性就是自身的slots属性加上父类的slots__。
使用@property
装饰器(decorator)可以给函数动态加上功能,对于类的方法,装饰器同样起作用。Python内置的@property装饰器就是负责把一个方法编程属性调用的。
1 | class Student(object): |
多重继承
定制类
str
当我们定义类并打印出它的实例是,通常是<__main__.Student object at 0x00000000>这种形式的,如何才能将它变得好看又直观呢?只需要在类中定义好str()方法。
1 | class Student(): |
iter
如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
1 | class Fib(object): |
getitem
虽然可以用类实现循环迭代,但无法像list那样按照下标取出元素,需要实现getitem()方法
1 | class Fib(object): |
getattr
通常情况下,当我们调用类的属性或方法时,若不存在即报错,但是使用__getattr__()方法,可以动态返回一个属性。
1 | class Student(object): |
call
一个对象实例可以有自己的属性和方法,当我们调用实例方法时,使用instance.method()来调用。在Python中,任何类只需要定义一个__call__()方法,就可以直接对实例进行调用。
1 | class Student(object): |
__call__()还可以定义参数,对实例进行直接调用就好像对一个函数进行调用一样。如果你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象。
1 | >> callable(s) |
使用枚举类
当我们需要定义常量时,一个方法时用大写变量通过整数来定义,例如月份。更好的方法是为这样的枚举类定义一个class类型,然后每个常量都是class的一个唯一实例。Python可以通过Enum类来实现。
1 | from enum import Enum |
若需要更精确地控制枚举类型,可以从Enum派生出自定义类
1 | from enum import Enum, unique |
