python学习笔记6--面向对象

一、面向对象介绍

  世界万物都可分类,世界万物都是对象,每个实体都是实例。

  面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。

  面向过程 VS 面向对象 

  编程范式

  编程是程序员用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程,一个程序是程序员为了得到一个任务结果而编写的一组指令的集合,正所谓条条大路通罗马,实现一个任务的方式有很多种不同的方式,对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。不同的编程范式本质上代表对各种类型的任务采取的不同的解决问题的思路,大多数语言只支持一种编程范式,当然也有些语言可以同时支持多种编程范式。两种最重要的编程范式分别是面向过程编程和面向对象编程。

  面向过程编程(Procedural Programming)

  面向过程编程依赖 - 你猜到了- procedures,一个procedure包含一组要被进行计算的步骤, 面向过程又被称为top-down languages,就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题。基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。

  这样做的问题也是显而易见的,就是如果你要对程序进行修改,对你修改的那部分有依赖的各个部分你都也要跟着修改,举个例子,如果程序开头你设置了一个变量值为1,但如果其它子过程依赖这个值为1的变量才能正常运行,那如果你改了这个变量,那这个子过程你也要修改,假如又有一个其它子程序依赖这个子过程,那就会发生一连串的影响,随着程序越来越大,这种编程方式的维护难度会越来越高。所以我们一般认为,如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护的,那还是用面向对象最方便了。 

  面向对象编程

  OOP编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述,使用面向对象编程的原因一方面是因为它可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。

  面向对象的几个核心特性如下:

  Class 类
  一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型。在类中定义了这些对象的都具备的属性(variables(data))、共同的方法 

  Object 对象 
  一个对象即是一个类的实例化后实例,一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同

  Encapsulation 封装
  在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法

  Inheritance 继承
  一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承

  Polymorphism 多态
  态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。
  编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。
对不同类的对象发出相同的消息将会有不同的行为。比如,你的老板让所有员工在九点钟开始工作, 他只要在九点钟的时候说:“开始工作”即可,而不需要对销售人员说:“开始销售工作”,对技术人员说:“开始技术工作”, 因为“员工”是一个抽象的事物, 只要是员工就可以开始工作,他知道这一点就行了。至于每个员工,当然会各司其职,做各自的工作。
多态允许将子类的对象当作父类的对象使用,某父类型的引用指向其子类型的对象,调用的方法是该子类型的方法。这里引用和调用方法的代码编译前就已经决定了,而引用所指向的对象可以在运行期间动态绑定

二、创建一只狗

  假设我们要在程序中描述一只狗,那么这个世界上有那么多狗,哈士奇、萨摩耶、泰迪、单身狗……等等等等,可见,狗,这是一个类别,狗类中有很多共同的特点,比如说都有名字,都可以叫等等,那我们就创建一个“狗”类,然后从这个狗类中创建出一只具体的狗来:

class Dog:  #创建Dog类
    def bulk(self):    # 类的方法(动态属性)
        print("汪汪汪!!")

dog1 = Dog()   #实例化一个对象
dog1.bulk()

运行结果:
>>> 汪汪汪!

  如上代码所示,我们创建了一个类叫Dog,我们在代码中定义了一个类的方法bulk(),那么从这个类中实例化创建的对象就都可以调用这个方法,也就是所有狗都可以叫了。接下来,我们实例化了一只狗,这只狗叫dog1,然后我们调用了dog1的bulk方法,于是dog1就叫了。

  看起来很棒。但是问题来了,如果我们需要区分是什么狗叫了呢?比如是哈士奇叫了,还是藏獒叫了,还是单身狗叫了等等,这意味着我们需要在实例化一个对象的时候给其传值,但是这个值应该传到哪里,应该传给谁呢?是个问题……
  这就涉及到一个概念,构造函数:在类的实例化过程中做一些初始化的工作

class Dog:
    def __init__(self,name):
        """构造函数,在类的实例化过程中做一些初始化的工作"""
        self.name = name    # 实例变量(属性/静态属性),作用域就是实例本身

    def bulk(self):    # 类的方法(动态属性)
        print("%s:汪汪汪!!" % self.name)

dog1 = Dog("哈士奇")   #实例化一个对象
dog2 = Dog("单身狗")
dog1.bulk()
dog2.bulk()

运行结果:
>>> 哈士奇:汪汪汪!
>>> 单身狗:汪汪汪!

  我们看到,我们在这个Dog类中增加了一个__init__(self,name)的函数,这个函数就是构造函数。很显然,我们在实例化一个对象的过程中,参数就传给了这个构造函数,不同的变量值实例化出了不同的对象,那么问题又来了,对于Dog类来说,是怎么区分这个参数是哪个实例的呢?

  除了构造函数,还有一个与之对应的析构函数

class Dog:
    def __init__(self,name):
       """构造函数,在类的实例化过程中做一些初始化的工作"""
       self.name = name

    def bulk(self):    # 类的方法(动态属性)
        print("%s:汪汪汪!!" % self.name)

    def __del__(self):
        print("啊,我死了!")

dog1 = Dog("哈士奇")
dog1.bulk()


运行结果:
>>> 哈士奇:汪汪汪!!,还有1000的血量!
>>> 啊,我死了!

 

  析构函数会在类被销毁、退出等时候执行,一般用于释放一些数据库连接,关闭临时文件等。

  我们注意到,在这个构造函数和这个bulk方法定义中第一个参数都是self,这个self是指什么呢?没问题,你猜对了,这个self就是指这个具体的实例,dog1或者dog2。现在一起看起来都是那么的完美,那么问题又来了,如果我在类中直接定义一个变量,那么实例化的过程中会怎么调用呢?来看代码:

class Dog:
    name = "金毛"
    #def __init__(self,name):
    #    self.name = name    # 实例变量(属性/静态属性),作用域就是实例本身

    def bulk(self):    # 类的方法(动态属性)
        print("%s:汪汪汪!!" % self.name)

dog1 = Dog()   #实例化一个对象
dog1.bulk()

运行结果:
>>> 金毛:汪汪汪
class Dog:
    name = "金毛"
    def __init__(self,name):
        self.name = name    # 实例变量(属性/静态属性),作用域就是实例本身

    def bulk(self):    # 类的方法(动态属性)
        print("%s:汪汪汪!!" % self.name)

dog1 = Dog("哈士奇")   #实例化一个对象
dog1.bulk()

运行结果:
>>> 哈士奇:汪汪汪!

  从上面代码可以看出,当我直接在类中定义一个变量,并且在初始化中没有传参的时候,程序会调用这个变量,但是如果我在实例化时传入了参数,那就会优先使用传入的参数。这里,直接在类中定义的变量被称为类变量,而在构造函数中定义的变量称为实例变量,程序运行时首先会查找实例变量,如果实例变量不存在,再查找类变量。

三、类的特性

  1、封装

  还是用我们上面那个狗的例子,假如我现在要给这个狗安装一个心脏,这个心脏可以跳动。我们先来看代码:

import time

class Dog:
    def __init__(self,name):
       '''构造函数,在类的实例化过程中做一些初始化的工作'''
       self.name = name    # 实例变量(属性/静态属性),作用域就是实例本身

    def heart(self,speed):  # speed代表心跳速度
        while True:
            time.sleep(speed)
            print("嘭!")

    def bulk(self):    # 类的方法(动态属性)
        print("%s:汪汪汪!!" % self.name)

dog1 = Dog("哈士奇")   #实例化一个对象
dog1.heart(0.1)

 

  上述我们定义了一个叫hert()的方法来模拟心跳,可见,我们可以在实例化对象后直接调用这个方法,也就是说我可以在程序中随便改变这个狗的心跳速度,我让他每0.1秒跳一次,OK,这只狗成功的激动死了。

  很显然,此处这个heart方法我并不希望在程序中能随意调用,不然这可能成为我程序的一个bug,外挂就是改了这些东西才诞生的。那有没有方法来防止这种情况的发生呢?答案肯定是有的,这就需要用到私有方法:

import time

class Dog:
    def __init__(self,name):
       '''构造函数,在类的实例化过程中做一些初始化的工作'''
       self.name = name    # 实例变量(属性/静态属性),作用域就是实例本身

    def __heart(self,speed):
        while True:
            time.sleep(speed)
            print("嘭!")

    def bulk(self):    # 类的方法(动态属性)
        print("%s:汪汪汪!!" % self.name)
        self.__heart(1)

dog1 = Dog("哈士奇")
dog1.bulk()

运行结果:
>>> 哈士奇:汪汪汪
>>> 嘭!
>>> 嘭!
>>> 嘭!
>>> 嘭!
...

  私有方法,只能在类内部调用,外部无法调用。上述程序中,我们将heart方法前加上两个下划线,这时,这个方法就变成了私有方法,只可以在类的内部调用,实例不可以直接调用了,OK,这样就好多了,我们就不能随便给改变某只狗的心跳了。

  那么需求又来了,当我需要给这只狗加个生命值怎么办?很显然,这个生命值也是不能被随便更改的,那么我们就需要用的私有变量

import time

class Dog:
def __init__(self,name):
'''构造函数,在类的实例化过程中做一些初始化的工作'''
self.name = name
self.__life_value = 1000

def __hert(self,speed):
while True:
time.sleep(speed)
print("嘭!")

def bulk(self): # 类的方法(动态属性)
print("%s:汪汪汪!!,还有%s的血量!" % (self.name,self.__life_value))
self.__hert(1)

dog1 = Dog("哈士奇") #实例化一个对象
dog1.name = "泰迪"
dog1.bulk()

运行结果:
>>> 泰迪:汪汪汪!!
>>> 嘭!
>>> 嘭!
>>> 嘭!
>>> 嘭!
...

  从上述代码中可以看到,我在实例化时创建的dog1是一条哈士奇,但是我在类外部重新给dog1的name变量重新赋了值,所以,我创建了一个哈士奇,结果却出来一只泰迪冲着我叫唤,WTF?!

  但是我们还有一个变量,__life_value,这个变量我们却没法直接在类外部更改,这就是私有变量。

  利用私有方法和私有变量,我们就可以将我们的类进行封装,这样实例化的时候,对于这些私有方法或私有变量就是透明的,外部也是没法随便更改的。

  2、继承

  OK!很好,现在我们有一个狗类了,现在我想创建一些大型犬,众所周知,那么这些大型犬除了是狗以外,它们个头还比较大,但是并不是所有用的狗都是大型犬,而且大型狗也会叫,那这怎么实现呢?难不成再写一个类,在类中再定义一遍bulk这个方法,再添加上一些属性?很明显,这种方法实在是low,那如何实现呢?继承!继承!继承!

class Dog:
    def __init__(self,name):
       '''构造函数,在类的实例化过程中做一些初始化的工作'''
       self.name = name
       self.__life_value = 1000

    def bulk(self):    # 类的方法(动态属性)
        print("%s:汪汪汪!!,还有%s的血量!" % (self.name,self.__life_value))

class Big_dog(Dog):
    def size(self):
        print("%s是一条大狗!" % self.name)

dog1 = Big_dog("哈士奇")
dog1.bulk()
dog1.size()

运行结果:
>>> 哈士奇:汪汪汪!!,还有1000的血量!
>>> 哈士奇是一条大狗!

  太棒了,我们创建了一个大狗类,而且大狗也可以叫,也可以显示是大狗,这就是继承,Dog称为Big_dog的父类,继承时子类会继承父类所有的属性和方法,子类实例化出来的对象就可以直接调用父类中的方法了。一切看起来都是那么美好,但是,人类欲壑难填,又有新的需求了,如果子类从父类继承来的方法无法完全满足子类的需求该怎么办呢?

  没有问题,我们可以用方法重构来实现这个需求:

class Dog:
    def __init__(self,name):
       '''构造函数,在类的实例化过程中做一些初始化的工作'''
       self.name = name
       self.__life_value = 1000

    def bulk(self):    # 类的方法(动态属性)
        print("%s:汪汪汪!!,还有%s的血量!" % (self.name,self.__life_value))

class Big_dog(Dog):
    def size(self):
        print("%s是一条大狗!" % self.name)

    def bulk(self):  # 重写bulk方法
        Dog.bulk(self)    # 调用父类的bulk方法
        print("这只大狗叫起来没完没了!!")    #新方法中的新功能

dog1 = Big_dog("哈士奇")
dog1.bulk()    #像调用父类方法的方法调用子类方法,实现添加新功能


运行结果:
>>> 哈士奇:汪汪汪!!,还有1000的血量!
>>> 这只大狗叫起来没完没了!!

 

  我们看到,子类中重新定义了父类中的方法,增加了新功能,这就是方法重构,关于方法重构,我们还需要注意的是构造函数的重构:

  关于重构构造函数有两种方法,一种是使用super语句,如下:

class Dog:
    def __init__(self,name):
       '''构造函数,在类的实例化过程中做一些初始化的工作'''
       self.name = name
       self.__life_value = 1000

    def bulk(self):    # 类的方法(动态属性)
        print("%s:汪汪汪!!,还有%s的血量!" % (self.name,self.__life_value))

class Big_dog(Dog):
    def __init__(self,name,food):
        super(Big_dog, self).__init__(name)  # super语句重构父类构造函数,super(子类名,self).__init__(父类变量列表)
        self.food = food
        
    def size(self):
        print("%s是一条大狗!" % self.name)

    def bulk(self):
        Dog.bulk(self)
        print("这只大狗叫起来没完没了!!它一定是想吃%s了" % self.food)

dog1 = Big_dog("哈士奇","狗粮")
dog1.bulk()


运行结果:
>>> 哈士奇:汪汪汪!!,还有1000的血量!
>>> 这只大狗叫起来没完没了!!,它一定是想吃狗粮了

 

  另一种是像重构其他方法一样,直接调用父类的构造函数:

class Dog:
    def __init__(self,name):
       '''构造函数,在类的实例化过程中做一些初始化的工作'''
       self.name = name
       self.__life_value = 1000

    def bulk(self):    # 类的方法(动态属性)
        print("%s:汪汪汪!!,还有%s的血量!" % (self.name,self.__life_value))

class Big_dog(Dog):
    def __init__(self,name,food):
        Dog.__init__(self,name)  # 调用父类构造函数来重构子类构造函数
        self.food = food

    def size(self):
        print("%s是一条大狗!" % self.name)

    def bulk(self):
        Dog.bulk(self)
        print("这只大狗叫起来没完没了!!它一定是想吃%s了" % self.food)

dog1 = Big_dog("哈士奇","狗粮")
dog1.bulk()


运行结果:
>>> 哈士奇:汪汪汪!!,还有1000的血量!
>>> 这只大狗叫起来没完没了!!它一定是想吃狗粮了

 

  好的,目前为止,我们可以继承单个父类了,如果我想继承多个父类怎么办?这也是有方法的,来看:

class Dog:
    def __init__(self,name):
       '''构造函数,在类的实例化过程中做一些初始化的工作'''
       self.name = name
       self.__life_value = 1000

    def bulk(self):    # 类的方法(动态属性)
        print("%s:汪汪汪!!,还有%s的血量!" % (self.name,self.__life_value))

class Animal:
    def move(self):
        print("卧槽!这货会动!!")

class Big_dog(Dog,Animal):  # 继承两个类
    def __init__(self,name,food):
        Dog.__init__(self,name)
        self.food = food

    def size(self):
        print("%s是一条大狗!" % self.name)

    def bulk(self):
        Dog.bulk(self)
        print("这只大狗叫起来没完没了!!它一定是想吃%s了" % self.food)

dog1 = Big_dog("哈士奇","狗粮")
dog1.bulk()
dog1.move()


运行结果:
>>> 哈士奇:汪汪汪!!,还有1000的血量!
>>> 这只大狗叫起来没完没了!!它一定是想吃狗粮了
>>> 卧槽!这货会动!!

 

  很好,如果我们有多个类,也都是没问题的了,但是现在有个问题,如果子类继承自多个父类,那么,子类的构造函数该继承谁呢?如果子类的父类也是另一个类的子类,那构造函数又该继承谁呢?我们来看下一个例子:

class A(object):
    def __init__(self):
        print("A")

class B(A):
    def __init__(self):
        print("B")

class C(A):
    def __init__(self):
        print("C")

class D(B,C):
#    def __init__(self):
#        super(D, self).__init__()
     pass

obj = D()

运行结果:
>>> B


class A(object):
    def __init__(self):
        print("A")

class B(A):
    #def __init__(self):
    #    print("B")
    pass

class C(A):
    def __init__(self):
        print("C")

class D(B,C):
#    def __init__(self):
#        super(D, self).__init__()
     pass

obj = D()


运行结果:
>>> C

 

  从上述代码我们可以看到,在多类继承中,是按照从左到右的顺序依次继承,如果该层没有找到,则到上一层继续寻找,这种查找方法称为广度优先查找。上述代码中A类在定义的时候继承了一个object类,这个类称为基类,这种定义类的方法称为新式类,而不带基类继承的定义方法称为经典类。

  在python3中,经典类和新式类统一按照广度优先查找,在python2中,经典类使用深度优先查找,即依次查找到最上层,然后再向下查找,新式类使用广度优先查找。在定义类的时候,建议使用新式类的方法定义。

  3、多态

   多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

  那么,多态的作用是什么呢?我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。
 
  Pyhon不直接支持多态,但可以间接实现
class Animal:
    def __init__(self, name):    # Constructor of the class
        self.name = name
    def talk(self):              # Abstract method, defined by convention only
        raise NotImplementedError("Subclass must implement abstract method")
 
class Cat(Animal):
    def talk(self):
        return 'Meow!'
 
class Dog(Animal):
    def talk(self):
        return 'Woof! Woof!'
 
animals = [Cat('Missy'),
           Dog('Lassie')]
 
for animal in animals:
    print animal.name + ': ' + animal.talk()

 

posted @ 2016-08-29 20:59  没有手艺的手艺人  阅读(159)  评论(0编辑  收藏  举报