面向对象编程(OOP)
OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
面向对象的设计思想是抽象出Class,根据Class创建Instance。
面向对象的抽象程度比函数要高,因为一个Class既包含数据,又包含操作数据的方法。
数据封装、继承和多态是面向对象的三大特点。
一、类和实例
以Student类为例,类名通常是大写开头,紧接着是(object),表示该类是从哪个类继承下来的。
class Student(object): pass sq = Student() sq.name = 'guluyu' # 可以自由地给一个实例变量绑定属性。
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。
class Student(object): def __init__(self, name, score): self.name = name self.score = score
__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。有了
__init__
方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__
方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:haha = Student('niuwa', 96)
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且调用时,不用传递该参数。
面向对象编程的一个重要特点就是
数据封装
。上例中Student类内部拥有数据,要访问这些数据,不必从外面的函数去访问,而是直接在内部定义访问数据的函数,这样就把这些数据封装起来了。
class Student(object): def __init__(self, name, score): self.name = name self.score = score def pr_score(self): print('%s: %s' % (self.name, self.score)) haha = Student('niuwa', 96) haha.pr_score()
和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:
class Student(object): pass sq = Student() sq.name = 'guluyu' ls = Student() ls.age = 18
二、访问限制
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线
__
就变成了一个私有变量(private),只有内部可以访问,外部不能访问。如果外部代码要获取内部变量,可以在类中增加get函数,如果想修改可以增加set函数。用这种方法的原因是在方法中可以对参数做检查,避免传入无效的参数。
class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def pr_score(self): print('%s: %s' % (self.__name, self.__score)) def get_name(self): return self.__name def get_score(self): return self.__score def set_score(self, score): if 0 <= score <= 100: self.__score = score else: raise ValueError('bad score')
__xxx__
是特殊变量,可以直接访问的,不是private变量。
三、继承和多态
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
子类获得了父类的全部功能,子类也可以增加方法。
class Animal(object): def run(self): print('Animal is running...') class Dog(Animal): pass class Cat(Animal): def dog = Dog() dog.run() # Animal is running... cat = Cat() cat.run() # Cat is running...
继承的另一个好处:多态
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行。
def run_twice(animal): # 编写一个函数,这个函数接受一个Animal类型的变量 animal.run() animal.run() run_twice(Animal()) run_twice(Dog()) run_twice(Cat()) run_twice(cat) # Cat is running...
新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行。
调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的开闭原则,即对扩展开放:允许新增Animal子类;对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。
继承树
静态语言和动态语言
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。只需要保证传入的对象有一个run()方法就可以了。
动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
四、获取对象信息
type()
函数返回对应的Class类型。要判断class的类型,可以使用
isinstance()
函数。如果要获得一个对象的所有属性和方法,可以使用
dir()
函数,它返回一个包含字符串的list。type(123) type(abs) type('ab') == type('c') import types type(lambda x:x) == types.LambdaType a = Animal() isinstance(a, Animal) isinstance([1,2,3], (list,tuple)) # 判断一个变量是否是某些类型中的一种 dir('ABC')
优先使用
isinstance()
判断类型。
五、实例属性和类属性
- 实例属性优先级比类属性高,不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
- 实例属性属于各个实例所有,互不干扰。
- 类属性属于类所有,所有实例共享一个属性。