Python面向对象编程

参考原文

  廖雪峰Python面向对象编程

引言:面向对象编程(Object Oriented Programming)是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包括了数据和操作数据函数。数据封装、继承和多态是面向对象的三大特点。

类和实例

  面向对象最重要的概念就是实例。要牢记是抽象的模板实例是根据类创建出来的一个个具体"对象"。

  下面以Student类为例,说一说在Python中类的基本用法,首先定义Student类:

class Student(object):
    pass

  定义类时,用关键字class,然后跟上类名Student(大写单词开头),再紧接(object)说明继承于哪个类,object是所有类最终都会继承的类。

  现在已经定义好类了,就应该基于Student类创建实例了:

>>> bart = Student()
>>> bart
<__main__.Student object at 0x00000179C0DAF0F0>
>>> Student
<class '__main__.Student'>

  创建实例是通过类名+()实现的。可以看到变量bart指向的就是一个Student的实例,后面的0x00000179C0DAF0F0是内存地址,而Student本身则是一个类。

  在Python中,可以自由的给一个实例变量绑定属性,如给实例bart绑定一个name属性:

>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson

  因为类是模板,因此可以在创建实例的时候,把我们认为一些必须绑定的属性强制填写进去(类似于C#中的构造函数)。可以通过定义一个特殊的方法__init__方法,强制添加一些属性。如把name,score属性绑定上去:

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

  注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此在__init__方法内部就可以把各种属性绑定到self。这样创建实例时,就必须传入相应的参数(self不需要我们传,Python解释器自己会把实例变量传进去):

>>> bart = Student()
Traceback (most recent call last):
  File "<pyshell#9>", line 1, in <module>
    bart = Student()
TypeError: __init__() missing 2 required positional arguments: 'name' and 'score'
>>> bart = Student('Alice', 100)
>>> bart.name
'Alice'
>>> bart.score
100
Tips:在类中定义的函数,与普通函数相比,只有一个不同:第一个参数永远是实例变量self,并且在调用时,不用传递该函数。

 数据封装

  我们可以再在类中添加一些函数用于操作类中的属性和数据,如添加一个打印学生成绩的函数:

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('Alice', 100)
>>> bart.print_score()
Alice: 100

  这样我们从外部看Student类,就只需知道,创建实例需要给出name和score,而如何打印,都是在Student类内部定义的,这些数据和逻辑被封装了,调用时,不必知道内部实现的细节。

Tips:和静态语言不同,Python允许对实例变量绑定任何的属性,也就是说对于两个实例变量,虽然它们都是同一个类的不同的实例,但拥有的属性名称却有可能不同。

访问限制

  上面已经提过了Python允许自由地修改实例的属性,我们应该对一些属性加以限制保护使外部的代码不能随意修改对象内部的状态,从而使代码更加的健壮。所以把上面的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', 59)
>>> bart.__name
Traceback (most recent call last):
  File "<pyshell#22>", line 1, in <module>
    bart.__name
AttributeError: 'Student' object has no attribute '__name'

  如果要允许外部的代码获取、修改类中变量,可以增加getset方法。这样做可以对参数进行检查,避免传入无效的参数不安全的参数。So:

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))
    
    def get_score(self):
        return self.__score
    
    def set_score(self,score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            raise ValueError('Invalid score')

  试试:

>>> bart = Student('Bart Simpson', 59)
>>> bart.get_score()
59
>>> bart.set_score(60)
>>> bart.get_score()
60
>>> bart.set_score(102)
Traceback (most recent call last):
  File "<pyshell#29>", line 1, in <module>
    bart.set_score(102)
  File "<pyshell#24>", line 17, in set_score
    raise ValueError('Invalid score')
ValueError: Invalid score
Tips:在Python中变量名以双下划线开头,以双下划线结尾的如:__name__是特殊变量;以一个下划线开头的是可以访问的,如_name,它的含义是“虽然我是可以被访问的,但请尽量将我视为私有变量,不要随意访问”;以双下划线开头的实例变量虽然是私有变量,其实是因为Python解释器把它改成了_类名__变量名,所以你仍然可以通过后者访问,但最好不要这样干(不同版本Python解释器改成的名字不同)

  注意下面错误:

>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 设置__name变量!
>>> bart.__name
'New Name'

  表面上,外部代码成功的设置了私有变量__name,但其实不然,这个__name和class内部的变量不是同一个变量,内部的变量已经被Python解释器自动改成了_Student__name,这是一个新的变量:

>>> bart.get_name() # get_name()内部返回self.__name
'Bart Simpson'

继承和多态

 继承

  前面已经说过了封装,那么什么又是继承呢?在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),被继承的类称为基类父类超类(Base class、Super class)。

  为什么要继承?继承有什么好处?继承的好处之一,也是最大的好处就是子类获得父类的全部功能。如父类Animal

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

   编写一个Dog类继承于Animal:

class Dog(Animal):
    pass

   此时Dog类就自动获得了父类的run方法:

dog = Dog()
dog.run() # reuslt: Animal is running

  再来说继承的第二个好多态

 多态

  父类有自己的方法,子类也可以有自己的方法,当子类和父类存在同名的方法时,子类的方法会覆盖父类的方法,在代码运行时总会调用子类的方法,这就是多态

  例如上面的Dog类也有一个run方法:

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

  此时运行Dog实例的run方法:

dog = Dog()
dog.run() # reuslt: Dog is running

  此时dog变量即是Dog类,又是Animal类:

dog = Dog()
print(isinstance(dog, Dog),isinstance(dog, Animal)) #result:True True

  那么多态的好处呢?要理解多态的好处,我们还需再编写一个函数,参数接受一个Animal类型的变量:

def run_twice(animal):
    animal.run()
    animal.run()

  当我们传入Animal实例时:

run_twice(Animal())
'''
Animal is running
Animal is running
'''

  因为Dog的实例也是Animal类所以:

run_twice(Dog())
'''
Dog is running
Dog is running
'''

  你会发现,新增一个Animal的子类,不必对run_twice做任何修改,任何依赖Animal作为参数的函数或者方法都可以不加修改便可运行正常,原因就在于多态

  对于一个变量,我们只需知道它的父类类型,无需知道它的子类类型,就可以调用父类中有的方法,而具体调用的是父类的方法,还是旗下多个子类中有的同名方法,由运行时该对象的确定。这就是著名的开闭原则:对扩展开放--允许增加子类    对修改封闭--不需修改父类的方法。

 静态语言vs动态语言

  对于静态语言(如Java,C#)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者其子类,否则无法调用run()方法。而对于Python这种动态语言来说,则不一定需要传入Animal类型,只需保证传入的对象有同名的run方法就可以了。

  这就是动态语言的"鸭子类型",它并不严格要求继承的对象,一个对象只要“看起来像鸭子”,那它就可以看成是鸭子。Python中的“file-like object”就是一种鸭子类型,只要这个对象有read()方法,就被视为“file-like objec”。

Tips:继承可以把父类所有功能都拿过来,子类只需增加自己特有的方法,也可以重写父类的方法。动态语言的鸭子类型决定了继承不像静态语言那么必需。
posted @ 2018-04-20 09:26  云--澈  阅读(331)  评论(0编辑  收藏  举报