★面向对象:封装、继承和多态是面向对象的三大特点★
面向对象编程简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度
面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class),给对象发消息实际上就是调用对象对应的关联函数,称之为对象的方法(Method)
Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如Master.Li
面向对象的设计思想是抽象出Class,根据Class创建Instance。面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法
1、 类和实例
类是抽象的模板,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同
定义类是通过class 关键字,class 后面紧接着是类名,类名通常是大写开头的单词,紧接着是(object) ,表示该类是从哪个类继承下来的类名通常是大写开头的单词,紧接着是(object)
表示该类是从哪个类继承来的,如果没有合适的继承类,就使用object 类,这是所有类最终都会继承的类
class Student(object):
pass
# 定义好了类,就可以根据类创建出实例,创建实例是通过类名+()实现的
Zhao = Student()
print Zhao # <__main__.student object at>
print Student #
# 结果表示:变量Zhao指向的是Student的对象,而Student本身就是一个类
# 可以自由地给实例变量绑定属性,不像其他语言【类,对象,成员变量,属性,方法】
Zhao.name = 'Zhao'
Zhao.age = 22
print u'对象Zhao的信息:', Zhao.name, Zhao.age
# 由于类可以起到模板的作用,因此,在创建实例的时候可以把一些我们认为必须绑定的属性强制填写进去【类似于构造函数】
# __init__ 方法的第一个参数永远是self ,表示创建的实例本身,在方法内部,就可以把各种属性绑定到self ,因为self 就指向创建的实例本身
# 有了__init__ 方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__ 方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去
class Student(object):
def __init__(self, name, age):
self.name = name
self.age = age
Qian = Student('Qian',22)
print u'对象Qian的信息:', Qian.name, Qian.age
运行效果:
2、数据封装————面向对象三大特性之一
对于Student实例本身就拥有的数据,没必要由外面的函数访问并处理,可以直接封装成类的方法,将数据和逻辑封装起来,用户不必知道内部实现细节,只要会调用就行了
class Student(object):
def __init__(self, name, age): # 给实例对象绑定属性
self.name = name
self.age = age
def SetName(self,name): # 设置name
self.name = name
def SetAge(self,age): # 设置age
self.age = age
def GetName(self): # 获取name
return self.name
def GetAge(self): # 获取age
return self.age
def print_info(self):
print 'Name:',self.name,'Age:',self.age
Qian = Student('Qian',22)
print u'对象Qian的信息:', Qian.name, Qian.age
Qian.SetName('Q')
Qian.SetAge(23)
print u'对象Qian的信息:', Qian.GetName(), Qian.GetAge()
Qian.print_info()
# ★在类中定义的函数仍然可以用默认参数、可变参数和关键字参数,和普通的函数相比只有一点不同,就是第一个参数永远是实例变量self ,并且调用时,不用传递该参数。
运行效果:
★★小结:类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响
方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据。通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节
和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们属于同一个类的不同实例,但拥有的变量名称可能不同
3、访问限制
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑
如果要让内部属性不被外部访问,可以在属性的名称前加上两个下划线__,同理,实例的变量名如果以__开头,表示是一个私有变量(private),只有内部可以访问,外部不能访问.确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮
class Student(object):
def __init__(self, name, age): # 给实例对象绑定属性
self.__name = name
self.__age = age
def SetName(self,name): # 设置name
self.__name = name
def SetAge(self,age): # 设置age
self.__age = age
def GetName(self): # 获取name
return self.__name
def GetAge(self): # 获取age
return self.__age
def print_info(self):
print 'Name:',self.__name,'Age:',self.__age
Sun = Student('Sun',30)
# print u'对象Sun的信息:', Sun.__name, Sun.__age # 'Student' object has no attribute '__name'
Sun.print_info()
Sun.SetName('S')
Sun.SetAge(31)
print u'对象Sun的信息:', Sun.GetName(), Sun.GetAge()
Sun.print_info()
print Sun._Student__name
运行效果:
在Python中,变量名类似__xxx__ 的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__ 、__score__ 这样的变量名
以一个下划线开头的实例变量名,比如_name ,这样的实例变量外部是可以访问的,但是,根据习惯,把这样的变量看成,“虽然可以被访问,但是,视为私有变量,不要随意访问”
双下划线开头的实例变量也不是一定不能从外部访问。不能直接访问__name 是因为Python解释器对外把__name 变量改成了_Student__name ,所以,仍然可以通过_Student__name 来访问__name 变量:
print Sun._Student__name # S
但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name 改成不同的变量名。
4、继承
定义一个类的时候,可以从某个现有的class继承,新的class称为子类,而被继承的class称为基类、父类或超类
# 继承最大的好处是子类获得了父类的全部功能,并允许增加和修改【不能删除】一些方法,且允许对代码做一点改进
class Animal(object):
def run(self):
print 'Animal is running...'
class Dog(Animal):
def run(self):# 函数的重写
print 'Dog is running...'
def eat(self):# 增加新功能
print 'Eating food ...'
class Cat(Animal):
def run(self):# 函数的重写
print 'Cat is running...'
def eat(self):# 增加新功能
print 'Eating food ...'
d = Dog()
d.run() # Dog is running...
d.eat()
c = Cat()
c.run() # Cat is running...
c.eat()
运行效果:
5、多态
# 【函数的重写】当子类和父类都存在相同的run() 方法时,子类的run() 会覆盖父类的run() ,在代码运行的时候,总是调用子类的run()
# 定义一个class的时候,实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样
# 判断一个变量是否是某个类型可以用isinstance() 判断:
l = list()# a是list类型 # l = list[]# a是list变量
print isinstance(l,list) # print isinstance(l,Iterable)
a = Animal()
print isinstance(a,Animal)
d = Dog()
print isinstance(d,Dog)
print u'子类的实例是否属于父类类型:',isinstance(d,Animal) # 向上转型
print u'父类的实例是否属于某子类类型:',isinstance(a,Cat) # 向下转型
# 【类型上转】在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来不行
# 【父类做参数】当使用父类Animal作为函数或方法的参数,可以接收Animal类以及任何Animal子类
def run_twice(animal):
animal.run()
animal.run()
run_twice(Animal())
run_twice(Dog())
run_twice(Cat())
# 由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法
class Monkey(Animal):
def run(self):
print 'monkey is runing...'
run_twice(Monkey())
运行效果:
# 对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run() 方法,而具体调用的run() 方法是作用在Animal、Dog、Cat还是Monkey对象上,由运行时该对象的确切类型决定
# 这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run() 方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
# 对扩展开放:允许新增Animal子类;
# 对修改封闭:不需要修改依赖Animal类型的run_twice() 等函数
# ★★小结:继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写 # 任何类最终都继承自object类