面向对象编程——Object Oriented Programming,简称OOP。面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接受其他对象发过来的消息,并处理这些消息,计算机程序的执行就是在各个对象之间传递。
举例来说明面向过程和面向对象在程序流程上的不同之处:
假设要处理学生的成绩表,为表示学生的成绩,面向过程的程序可以用一个dict来表示:
std1 = {'name': 'Michael', 'score': 98}
std2 = {'name': 'Bob', 'score': 81}
打印学生成绩用函数实现:
def print_score(std):
print('%s: %s' % (std['name'], std['score']))
如果采用面向对象的程序设计思想,首先思考的不是程序的执行过程,二是student这个数据类型应该被视为一个对象,它拥有name和score这两个属性(property):
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
面向对象的程序的使用:
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()
上例中,Student便是一个类Class,bart和lisa便是一个实例Instance。
类与实例
类起到模板的作用,因此,可以在创建实例的时候把认为必须绑定的属性通过init方法强制填进去:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
当然也可以自由地给一个实例变量绑定属性,如bart.gender = 'female'
。
访问限制
在之前的student类的定义来看,外部代码依然可以自由修改实例中的name和score:bart.score = 99
。如果让内部属性不被外部访问,可以把属性的名称前加上两个下划线,这样它就变成一个私有变量,只有内部可以访问,外部不能访问:
class student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
通过如此的访问限制保护,代码更加健壮。如果外部想要获取name或score,可以通过增加get_name或get_score的方法。
需要注意,python中变量名类似xxx的,是特殊变量,可以直接访问。_xxx外部可以访问,但按照约定俗成的规定,一般将其默认视为私有变量。
student中的__name通过python解释器把这个变量改为了_student__name或类似的名称,也就是仍然可以从外部访问该变量。可以,但没必要。
注意一种错误写法:
>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 设置__name变量!
>>> bart.__name
'New Name'
这样表面上是设置了name变量,但实际上这个__name和class内部的__name根本不是同一个变量,内部的__name已经被解释器自动更改为_Student__name,而外部代码给bart新增了一个__name变量。
继承和多态
在OOP程序设计中,定义一个class可以从现有的class继承,新的class称为子类(subclass),而被继承的class称为基类、父类或超类(Base class、 Super class)。
eg:
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
dog.run()
cat = Cat()
cat.run()
我们也可以对子类增加一些方法,当增加的方法与基类中的某方法相同时,子类的方法覆盖基类的方法,代码运行时总会调用子类的方法,这就是继承的另一个好处——多态:
class Dog(Animal):
def run(self):
print("Dog is running")
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类,反之则不成立。
开闭原则:
- 对扩展开放:允许新增Animal子类;
- 对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。
- 静态语言 vs 动态语言
像java这种静态语言,如果需要传入Animal类型,则传入的对象必须是Animal类型或它的子类,否则将无法调用run()方法。
对于python这种动态语言,则不一定需要传入Animal类型,我们只需要保证传入的对象有一个run()方法即可。
获取对象信息
- 判断对象类型,用type()函数
若要判断一个对象是否是函数,需要用types模块中定义的常量:
import types
def fn():
pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
- 也可以使用isinstance()函数:
>>> isinstance(dog, Animal)
True
- 如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list:
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
类似xxx的属性或方法在python中有特殊用途,比如len方法返回长度,如果调用len()函数去获得一个函数的长度,实际上在len()函数内部自动去调用该对象的len方法,故下面的代码是等价的:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
- 利用getattr(),setattr()和hasattr(),可以直接操作一个对象的状态:
>>> class MyObject(object):
... def __init__(self):
... self.x = 9
... def power(self):
... return self.x * self.x
...
>>> obj = MyObject()
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19
>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'MyObject' object has no attribute 'z'
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
>
>>> fn() # 调用fn()与调用obj.power()是一样的
81
实例属性和类属性
由于python是动态语言,根据类创建的实例可以任意绑定属性:
class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score = 90
也可以给类本身绑定一个属性,可以在class中定义属性,这种属性是类属性,归类所有:
class Student(object):
name = 'Student'
定义类属性后,这个属性虽然归类所有,但类的所有实例都可以访问:
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
相同名称的实例属性将屏蔽掉类属性,故尽量不要对实例属性和类属性使用相同的名字。