zoukankan      html  css  js  c++  java
  • 进击的Python【第六章】:Python的高级应用(三)面向对象编程

    Python的高级应用(三)面向对象编程

    本章学习要点:

    1. 面向对象编程介绍
    2. 面向对象与面向过程编程的区别
    3. 为什么要用面向对象编程思想
    4. 面向对象的相关概念

    一、面向对象编程介绍

      面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是一种程序设计范型,同时也是一种程序开发的方法。对象指的是类的实例。
      已经被证实的是,面向对象程序设计推广了程序的灵活性和可维护性,并且在大型项目设计中广为应用。 此外,支持者声称面向对象程序设计要比以往的做法更加便于学习,因为它能够让人们更简单地设计并维护程序,使得程序更加便于分析、设计、理解。反对者在某些领域对此予以否认。
      当我们提到面向对象的时候,它不仅指一种程序设计方法。它更多意义上是一种程序开发方式。在这一方面,我们必须了解更多关于面向对象系统分析和面向对象设计(Object Oriented Design,简称OOD)方面的知识。

    二、面向对象与面向过程编程的区别

      面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
      可以拿生活中的实例来理解面向过程与面向对象,例如五子棋,面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用不同的方法来实现。
      如果是面向对象的设计思想来解决问题。面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
      可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了多个步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。

    三、为什么要用面向对象编程思想

      面向对象是为了解决系统的可维护性,可扩展性,可重用性,我们再进一步思考,面向对象为什么能解决系统的可维护性,可扩展性,可重用性? 

      面向对象产生的历史原因有下面两点: 

      1、 计算机是帮助人们解决问题的,然而计算机终究是个机器,他只会按照人所写的代码,一步一步的执行下去,最终得到了结果,因此无论程序多么的复杂,计算机总是能轻松应付。结构化编程,就是按照计算机的思维写出的代码,但是人看到这么复杂的逻辑,就无法维护和扩展了。

      2、 结构化设计是以功能为目标来设计构造应用系统,这种做法导致我们设计程序时,不得不将客体所构成的现实世界映射到由功能模块组成的解空间中,这种转换过程,背离了人们观察和解决问题的基本思路。 

      可见结构化设计在设计系统的时候,无法解决重用、维护、扩展的问题,而且会导致逻辑过于复杂,代码晦涩难懂。于是人们就想,能不能让计算机直接模拟现实的环境,用人类解决问题的思路、习惯、步骤来设计相应的应用程序?这样的程序,人们在读它的时候,会更容易理解,也不需要再把现实世界和程序世界之间来回做转换。 

      与此同时,人们发现,在现实世界中存在的客体是问题域中的主角。所谓客体是指客观存在的对象实体和主观抽象的概念,这种客体具有属性和行为,而客体是稳定的,行为是不稳定的,同时客体之间具有各种联系,因此面向客体编程,比面向行为编程,系统会更稳定。在面对频繁的需求更改时,改变的往往是行为,而客体一般不需要改变,所以我们就把行为封装起来,这样改变时候只需要改变行为即可,主架构则保持了稳定。 

      于是面向对象就产生了。 

      然而人们追求的系统可维护性,可扩展性,可重用性又是怎么在面向对象中体现出来的呢? 

      首先看看面向对象的三大特征: 

      封装:找到变化并且把它封装起来,你就可以在不影响其它部分的情况下修改或扩展被封装的变化部分,这是所有设计模式的基础,就是封装变化,因此封装的作用,就解决了程序的可扩展性。 

      继承:子类继承父类,可以继承父类的方法及属性,实现了多态以及代码的重用,因此也解决了系统的重用性和扩展性。但是继承破坏了封装,因为他是对子类开放的,修改父类会导致所有子类的改变,因此继承一定程度上又破坏了系统的可扩展性,所以继承需要慎用。只有明确的IS-A关系才能使用,同时继承在在程序开发过程中重构得到的,而不是程序设计之初就使用继承,很多面向对象开发者滥用继承,结果造成后期的代码解决不了需求的变化了。因此优先使用组合,而不是继承,是面向对象开发中一个重要的经验。 

      多态:接口的多种不同的实现方式即为多态。接口是对行为的抽象,刚才在封装提到,找到变化部分并封装起来,但是封装起来后,怎么适应接下来的变化?这正是接口的作用,接口的主要目的是为不相关的类提供通用的处理服务,我们可以想象一下。比如鸟会飞,但是超人也会飞,通过飞这个接口,我们可以让鸟和超人,都实现这个接口,这就实现了系统的可维护性,可扩展性。 

      因此面向对象能实现人们追求的系统可维护性,可扩展性,可重用性。面向对象是一种编程思想,起初,“面向对象”是专指在程序设计中采用封装、继承、多态等设计方法,但面向对象的思想已经涉及到软件开发的各个方面,比如现在细分为了面向对象的分析(OOA),面向对象的设计(OOD),面向对象的编程实现(OOP) 

    四、面向对象的相关概念

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

    1、创建一个类

    使用class语句来创建一个新类,class之后为类的名称并以冒号结尾,如下实例:

    class ClassName:
       '类的帮助信息'   #类文档字符串
       class_suite  #类体
    • 类的帮助信息可以通过ClassName.__doc__查看。
    • class_suite 由类成员,方法,数据属性组成。

    以下是一个简单的Python类实例:

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    
    class Employee:
       '所有员工的基类'
       empCount = 0
    
       def __init__(self, name, salary):
          self.name = name
          self.salary = salary
          Employee.empCount += 1
       
       def displayCount(self):
         print("Total Employee %d" % Employee.empCount)
    
       def displayEmployee(self):
          print("Name : ", self.name,  ", Salary: ", self.salary)
    • empCount变量是一个类变量,它的值将在这个类的所有实例之间共享。你可以在内部类或外部类使用Employee.empCount访问。
    • 第一种方法__init__()方法是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法

    2、创建实例对象

    要创建一个类的实例,你可以使用类的名称,并通过__init__方法接受参数。

    "创建 Employee 类的第一个对象"
    emp1 = Employee("Zara", 2000)
    "创建 Employee 类的第二个对象"
    emp2 = Employee("Manni", 5000)
    

    3、访问属性

    您可以使用点(.)来访问对象的属性。使用如下类的名称访问类变量:

    emp1.displayEmployee()
    emp2.displayEmployee()
    print("Total Employee %d" % Employee.empCount)
    

    完整示例:

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    
    class Employee:
       '所有员工的基类'
       empCount = 0
    
       def __init__(self, name, salary):
          self.name = name
          self.salary = salary
          Employee.empCount += 1
       
       def displayCount(self):
         print("Total Employee %d" % Employee.empCount)
    
       def displayEmployee(self):
          print("Name : ", self.name,  ", Salary: ", self.salary)
    
    "创建 Employee 类的第一个对象"
    emp1 = Employee("Zara", 2000)
    "创建 Employee 类的第二个对象"
    emp2 = Employee("Manni", 5000)
    emp1.displayEmployee()
    emp2.displayEmployee()
    print("Total Employee %d" % Employee.empCount)
    

    执行以上代码输出结果如下:

    Name :  Zara ,Salary:  2000
    Name :  Manni ,Salary:  5000
    Total Employee 2
    

    你可以添加,删除,修改类的属性,如下所示:

    emp1.age = 7  # 添加一个 'age' 属性
    emp1.age = 8  # 修改 'age' 属性
    del emp1.age  # 删除 'age' 属性
    

    你也可以使用以下函数的方式来访问属性:

    • getattr(obj, name[, default]) : 访问对象的属性。
    • hasattr(obj,name) : 检查是否存在一个属性。
    • setattr(obj,name,value) : 设置一个属性。如果属性不存在,会创建一个新属性。
    • delattr(obj, name) : 删除属性。
    hasattr(emp1, 'age')    # 如果存在 'age' 属性返回 True。
    getattr(emp1, 'age')    # 返回 'age' 属性的值
    setattr(emp1, 'age', 8) # 添加属性 'age' 值为 8
    delattr(empl, 'age')    # 删除属性 'age'
    

    4、Python内置类属性

    • __dict__ : 类的属性(包含一个字典,由类的数据属性组成)
    • __doc__ :类的文档字符串
    • __name__: 类名
    • __module__: 类定义所在的模块(类的全名是'__main__.className',如果类位于一个导入模块mymod中,那么className.__module__ 等于 mymod)
    • __bases__ : 类的所有父类构成元素(包含了一个由所有父类组成的元组)

    Python内置类属性调用实例如下:

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    
    class Employee:
       '所有员工的基类'
       empCount = 0
    
       def __init__(self, name, salary):
          self.name = name
          self.salary = salary
          Employee.empCount += 1
       
       def displayCount(self):
         print("Total Employee %d" % Employee.empCount)
    
       def displayEmployee(self):
          print("Name : ", self.name,  ", Salary: ", self.salary)
    
    print("Employee.__doc__:", Employee.__doc__)
    print("Employee.__name__:", Employee.__name__)
    print("Employee.__module__:", Employee.__module__)
    print("Employee.__bases__:", Employee.__bases__)
    print("Employee.__dict__:", Employee.__dict__)
    

    执行以上代码输出结果如下:

    Employee.__doc__: 所有员工的基类
    Employee.__name__: Employee
    Employee.__module__: __main__
    Employee.__bases__: ()
    Employee.__dict__: {'__module__': '__main__', 'displayCount': <function displayCount at 0x10a939c80>, 'empCount': 0, 'displayEmployee': <function displayEmployee at 0x10a93caa0>, '__doc__': 'xe6x89x80xe6x9cx89xe5x91x98xe5xb7xa5xe7x9ax84xe5x9fxbaxe7xb1xbb', '__init__': <function __init__ at 0x10a939578>}

    5、析构函数------python对象销毁

    同Java语言一样,Python使用了引用计数这一简单技术来追踪内存中的对象。

    在Python内部记录着所有使用中的对象各有多少引用。

    一个内部跟踪变量,称为一个引用计数器。

    当对象被创建时, 就创建了一个引用计数, 当这个对象不再需要时, 也就是说, 这个对象的引用计数变为0 时, 它被垃圾回收。但是回收不是"立即"的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收。

    a = 40      # 创建对象  <40>
    b = a       # 增加引用, <40> 的计数
    c = [b]     # 增加引用.  <40> 的计数
    
    del a       # 减少引用 <40> 的计数
    b = 100     # 减少引用 <40> 的计数
    c[0] = -1   # 减少引用 <40> 的计数
    

    垃圾回收机制不仅针对引用计数为0的对象,同样也可以处理循环引用的情况。循环引用指的是,两个对象相互引用,但是没有其他变量引用他们。这种情况下,仅使用引用计数是不够的。Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。作为引用计数的补充, 垃圾收集器也会留心被分配的总量很大(及未通过引用计数销毁的那些)的对象。 在这种情况下, 解释器会暂停下来, 试图清理所有未引用的循环。

    实例

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    
    class Point:
       def __init__( self, x=0, y=0):
          self.x = x
          self.y = y
       def __del__(self):
          class_name = self.__class__.__name__
          print(class_name, "销毁")
    
    pt1 = Point()
    pt2 = pt1
    pt3 = pt1
    print(id(pt1), id(pt2), id(pt3)) # 打印对象的id
    del pt1
    del pt2
    del pt3

    以上实例运行结果如下:

    3083401324 3083401324 3083401324
    Point 销毁
    

    注意:通常你需要在单独的文件中定义一个类

    6、类的继承

    面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。继承完全可以理解成类之间的类型和子类型关系。

    需要注意的地方:继承语法 class 派生类名(基类名)://... 基类名写作括号里,基本类是在类定义的时候,在元组之中指明的。

    在python中继承中的一些特点:

    • 1:在继承中基类的构造(__init__()方法)不会被自动调用,它需要在其派生类的构造中亲自专门调用。
    • 2:在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。区别于在类中调用普通函数时并不需要带上self参数
    • 3:Python总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)。

    如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。

    语法:

    派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示:

    #!_*_coding:utf-8_*_
    #__author__:"Alex Li"
     
     
     
    class SchoolMember(object):
        members = 0 #初始学校人数为0
        def __init__(self,name,age):
            self.name = name
            self.age = age
     
        def  tell(self):
            pass
     
        def enroll(self):
            '''注册'''
            SchoolMember.members +=1
            print("33[32;1mnew member [%s] is enrolled,now there are [%s] members.33[0m " %(self.name,SchoolMember.members))
         
        def __del__(self):
            '''析构方法'''
            print("33[31;1mmember [%s] is dead!33[0m" %self.name)
    class Teacher(SchoolMember):
        def __init__(self,name,age,course,salary):
            super(Teacher,self).__init__(name,age)
            self.course = course
            self.salary = salary
            self.enroll()
     
     
        def teaching(self):
            '''讲课方法'''
            print("Teacher [%s] is teaching [%s] for class [%s]" %(self.name,self.course,'s12'))
     
        def tell(self):
            '''自我介绍方法'''
            msg = '''Hi, my name is [%s], works for [%s] as a [%s] teacher !''' %(self.name,'Oldboy', self.course)
            print(msg)
     
    class Student(SchoolMember):
        def __init__(self, name,age,grade,sid):
            super(Student,self).__init__(name,age)
            self.grade = grade
            self.sid = sid
            self.enroll()
     
     
        def tell(self):
            '''自我介绍方法'''
            msg = '''Hi, my name is [%s], I'm studying [%s] in [%s]!''' %(self.name, self.grade,'Oldboy')
            print(msg)
     
    if __name__ == '__main__':
        t1 = Teacher("Alex",22,'Python',20000)
        t2 = Teacher("TengLan",29,'Linux',3000)
     
        s1 = Student("Qinghua", 24,"Python S12",1483)
        s2 = Student("SanJiang", 26,"Python S12",1484)
     
        t1.teaching()
        t2.teaching()
        t1.tell()
    

    7、类属性与方法

    类的私有属性

    __private_attrs:两个下划线开头,声明该属性为私有,不能在类地外部被使用或直接访问。在类内部的方法中使用时self.__private_attrs

    类的方法

    在类地内部,使用def关键字可以为类定义一个方法,与一般函数定义不同,类方法必须包含参数self,且为第一个参数

    类的私有方法

    __private_method:两个下划线开头,声明该方法为私有方法,不能在类地外部调用。在类的内部调用 self.__private_methods

    实例

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    
    class JustCounter:
    	__secretCount = 0  # 私有变量
    	publicCount = 0    # 公开变量
    
    	def count(self):
    		self.__secretCount += 1
    		self.publicCount += 1
    		print(self.__secretCount)
    
    counter = JustCounter()
    counter.count()
    counter.count()
    print(counter.publicCount)
    print(counter.__secretCount)  # 报错,实例不能访问私有变量
    

    Python 通过改变名称来包含类名:

    1
    2
    2
    Traceback (most recent call last):
      File "test.py", line 17, in <module>
        print counter.__secretCount  # 报错,实例不能访问私有变量
    AttributeError: JustCounter instance has no attribute '__secretCount'
    

    Python不允许实例化的类访问私有数据,但你可以使用 object._className__attrName 访问属性,将如下代码替换以上代码的最后一行代码:

    .........................
    print counter._JustCounter__secretCount
    

    执行以上代码,执行结果如下:

    1
    2
    2
    2
    

    8、多态

    再讲多态前,先上一段代码:

    class Person(object):
        def __init__(self,name,sex):
            self.name = name
            self.sex = sex
            
        def print_title(self):
            if self.sex == "male":
                print("man")
            elif self.sex == "female":
                print("woman")
    
    class Child(Person):                # Child 继承 Person
        def print_title(self):
            if self.sex == "male":
                print("boy")
            elif self.sex == "female":
                print("girl")
            
    May = Child("May","female")
    Peter = Person("Peter","male")
    
    print(May.name,May.sex,Peter.name,Peter.sex)
    May.print_title()
    Peter.print_title()
    

    当子类和父类都存在相同的 print_title()方法时,子类的 print_title() 覆盖了父类的 print_title(),在代码运行时,会调用子类的 print_title()

        这样,我们就获得了继承的另一个好处:多态。 

        多态的好处就是,当我们需要传入更多的子类,例如新增 Teenagers、Grownups 等时,我们只需要继承 Person 类型就可以了,而print_title()方法既可以直不重写(即使用Person的),也可以重写一个特有的。这就是多态的意思。调用方只管调用,不管细节,而当我们新增一种Person的子类时,只要确保新方法编写正确,而不用管原来的代码。这就是著名的“开闭”原则:

    •     对扩展开放(Open for extension):允许子类重写方法函数
    •     对修改封闭(Closed for modification):不重写,直接继承父类方法函数
  • 相关阅读:
    How to: Implement a View Item 如何:实现视图项
    How to:Create a New Object using the Navigation Control 如何:使用导航控件创建新对象
    How to:Access the Transition Manager 如何:访问过渡管理器
    How to: Implement Custom Context Navigation 如何:实现自定义上下文导航
    How to: Access Master Detail View and Nested List View Environment 如何:访问主详细信息视图和嵌套列表视图环境
    How to: Display a Detail View Directly in Edit Mode in ASP.NET Applications 如何:在ASP.NET应用程序中直接在编辑模式下显示详细信息视图
    How to: Detect a Lookup List View in Code 如何:在代码中检测查找列表视图
    How to: Create and Show a Detail View of the Selected Object in a Popup Window 如何:在弹出窗口中创建和显示选定对象的详细信息视图
    How to: Access Objects Selected in the Current View 如何:访问在当前视图中选择的对象
    How to: Display a List View as a Pivot Grid Table and Chart如何:将列表视图显示为数据透视网格表和图表
  • 原文地址:https://www.cnblogs.com/yunweiqiang/p/5815435.html
Copyright © 2011-2022 走看看