>>>>面向对象(Object Oriented)

面向对象编程的思想 数据封装、继承和多态是面向对象的三大特点 面向对象编程——Object OrientedProgramming,简称OOP,是一种程序设计

面向对象编程的思想

数据封装、继承和多态是面向对象的三大特点

面向对象编程——Object OrientedProgramming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。
假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的
程序可以用一个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)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score 消息,让对象自己把自己的数据打印出来。

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))

给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。面向对象的程序写出来就像这样:


linga = Student("Linga", 66)
saya = Student("Saya", 88)

linga.print_score()

the result of print:

Linga:66
Saya:88

面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,Linga和Saya是两个具体的Student。
所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。面向对象的抽象程度又比函数要高,因为一个Class 既包含数据,又包含操作数据的方法。

面向对象技术简介

  • 类(class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • 方法:类中定义的函数。
  • 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
  • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 实例变量:定义在方法中的变量,只作用于当前实例的类。
  • 继承:即一个派生类(derivedclass)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
  • 实例化:创建一个类的实例,类的具体对象。
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

类和实例

类:类是抽象的模板,即类是具有相同属性和技能的一类事物,如:Student类
实例:实例是根据类创建出来的一个个具体的"对象",每个对象有相同的方法,但各自的数据可能不同,定义好类后,就可以通过类名+()实现实例的创建,如下边的这段代码

class Student(object):      # 定义了类,类名的第一个字母要大写

    def __init__(self, name, score):    # 初始化实例的属性,names和scores
        self.names = name        # 给属性names和scores传值
        self.scores = score

    def print_score(self):      # 类里面具有的一些方法
        print("%s:%s" % (self.name, self.score))


linga = Student("Linga", 66)        # 实例化对象
saya = Student("Saya", 88)          
# 变量saya指向的就是一个Student的实例,指向的是内存地址,每个Object的地址都是不一样的

linga.print_score()         # 实例化的对象调用类里面的方法
saya.print_score()

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

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'
bart.score
#59

方法

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

数据封装

创建实例需要给出name 和score,而如何打印,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。

通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。

访问限制

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python 中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

继承和多态

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

继承的最大的好处死子类获得了父类的全部功能
继承的第二个好处需要我们对代码做一点改进

class Animal(object):

    def run(self):
        print("Animal is running>>>>")


class Dog(Animal):
    pass


class Cat(Animal):
    def run(self):
        print("Cat is running>>>>>")


dog = Dog()
dog.run()
cat = Cat()
cat.run()

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

当我们定义一个class 的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python 自带的数据类型,比如str、list、dict 没什么两样:

a = list() # a 是list 类型
b = Animal() # b 是Animal 类型
c = Dog() # c 是Dog 类型

判断一个变量是否是某个类型可以用isinstance()判断:

>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True
>>> isinstance(c, Animal)
True

看来c不仅仅是Dog,c还是Animal!因为Dog是从Animal继承下来的,当我们创建了一个Dog的实例c时,我们认为c的数据类型是Dog没错,但c同时也是Animal也没错,Dog本来就是Animal的一种!

所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行:

>>> b = Animal()
>>> isinstance(b, Dog)
False

Dog可以看成Animal,但Animal不可以看成Dog。

可以看作充分不必要条件来理解

class Animal(object):

    def run(self):
        print("Animal is running>>>>")


class Dog(Animal):
    print("Tins is dog")


class Cat(Animal):
    def run(self):
        print("Cat is running>>>>>")


def run_twice(self):
    self.run()
    self.run()


run_twice(Animal())
run_twice(Cat())


class Tortoise(Animal):
    def run(self):
        print("Tortoise is running slowly!")


run_twice(Tortoise())

新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

多态的好处就是,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思:

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

对扩展开放:允许新增Animal 子类;
对修改封闭:不需要修改依赖Animal 类型的run_twice()等函数。

继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树。

鸭子类型

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:

class Timer(object):
    def run(self):
        print('Start...')

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。

获取对象信息

待补充

实例属性和类属性

待补充

多重继承

待补充