【python】类 面向对象 继承多态封装

来源:互联网 发布:mac定制双用粉底液53c1 编辑:程序博客网 时间:2024/06/07 02:25

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。有三大特性:数据封装、继承和多态。通过class关键字定义类。

面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。

而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。

Python中,定义类是通过class关键字:

class Student(object):    pass

class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类+()实现的:

>>> bart = Student()#可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性:>>> bart.name = 'Bart Simpson'>>> bart.name'Bart Simpson'

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把namescore等属性绑上去:

class Student(object):     def __init__(self, name, score):        self.name = name        self.score = score

注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:

>>> bart = Student('Bart Simpson', 59)>>> bart.name'Bart Simpson'

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,仍然可以使用默认参数、可变参数和关键字参数。

>>> class Stu(object):       def__init__(self,name,score):%#拥有name和score这两个属性(Property)              self.name=name              self.score=score       defprintss(self):#拥有一个函数,成为对象的方法              print('%s%s' % (self.name,self.score))>>> lisa=Student('lisa',78)>>> lisa.printss()lisa 78>>> class Student (object):       pass

注意区分类(Class)和实例(Instance)的概念。Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,lisa所以,面向对象的设计思想是抽象出Class,根据Class创建Instance

面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。

数据封装

面向对象编程的一个重要特点就是数据封装。在上面的Student类中,每个实例就拥有各自的namescore这些数据既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法。这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出namescore,而如何打印,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。

封装的另一个好处是可以给Student类增加新的方法,比如get_grade

class Student(object):    ...     def get_grade(self):        if self.score >= 90:            return 'A'        elif self.score >= 60:            return 'B'        else:            return 'C'

访问限制

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

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了:

>>> bart = Student('Bart Simpson', 98)>>> bart.__nameTraceback (most recent call last):  File "<stdin>", line 1, in <module>AttributeError: 'Student' object has no attribute '__name'

这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。

但是如果外部代码要获取namescore怎么办?可以给Student类增加get_nameget_score这样的方法:

class Student(object):    ...     def get_name(self):        return self.__name     def get_score(self):        return self.__score

如果又要允许外部代码修改score怎么办?可以给Student类增加set_score方法:

class Student(object):    ...     def set_score(self, score):        self.__score = score

你也许会问,原先那种直接通过bart.score = 59也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:

class Student(object):    ...     def set_score(self, score):        if 0 <= score <= 100:            self.__score = score        else:            raise ValueError('bad score')

需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,虽然我可以被访问,但是,请把我视为私有变量,不要随意访问

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:

>>> bart._Student__name'Bart Simpson'

但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。

继承和多态

OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base classSuper class)。

比如,我们已经编写了一个名为Animalclass,有一个run()方法可以直接打印:

class Animal(object):    def run(self):        print 'Animal is running...'

当我们需要编写DogCat类时,就可以直接从Animal类继承:

class Dog(Animal):    pass class Cat(Animal):    pass

对于Dog来说,Animal就是它的父类,对于Animal来说,Dog就是它的子类。CatDog类似。

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。

class Animal(object):    def run(self):        print 'Animal is running...'class Dog(Animal):    def run(self):        print 'Dog is running...' class Cat(Animal):    def run(self):        print 'Cat is running...'#再次运行,结果如下:Dog is running...Cat is running... 

有了继承,才能有多态。在调用类实例方法的时候,尽量把变量视作父类类型,这样,所有子类类型都可以正常被接收;

对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在AnimalDogCat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的开闭原则:

对扩展开放:允许新增Animal子类;

对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

0 0