Python 3 - 面向对象

  • 简述

    Python 从一开始就是一种面向对象的语言。因此,创建和使用类和对象非常容易。本章帮助您成为使用 Python 的面向对象编程支持的专家。
    如果您以前没有任何面向对象 (OO) 编程的经验,您可能需要查阅有关它的入门课程或至少某种教程,以便您掌握基本概念。
    然而,这里有一个面向对象编程(OOP)的小介绍来帮助你 -
  • OOP 术语概述

    • − 一个对象的用户定义原型,它定义了一组表征该类任何对象的属性。属性是数据成员(类变量和实例变量)和方法,通过点符号访问。
    • 类变量− 一个类的所有实例共享的变量。类变量在类中定义,但在类的任何方法之外。类变量不像实例变量那样频繁使用。
    • 数据成员− 保存与类及其对象相关联的数据的类变量或实例变量。
    • 函数重载− 将多个行为分配给特定功能。执行的操作因所涉及的对象或参数的类型而异。
    • 实例变量− 在方法内部定义的变量,仅属于类的当前实例。
    • 继承− 将一个类的特征转移到从它派生的其他类。
    • 实例− 某个类别的单个对象。例如,属于类 Circle 的对象 obj 是类 Circle 的实例。
    • 实例− 创建一个类的实例。
    • 方法 − 在类定义中定义的一种特殊函数。
    • 对象− 由其类定义的数据结构的唯一实例。对象包括数据成员(类变量和实例变量)和方法。
    • 操作符重载− 将多个功能分配给特定操作符。
  • 创建类

    class语句创建一个新的类定义。类的名称紧跟在关键字class之后,后跟一个冒号,如下所示 -
    
    class ClassName:
       'Optional class documentation string'
       class_suite
    
    • 该类有一个文档字符串,可以通过ClassName.__doc__.
    • class_suite由定义类成员、数据属性和函数的所有组件语句组成。

    例子

    以下是一个简单的 Python 类的示例 -
    
    class Employee:
       'Common base class for all employees'
       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__()是一个特殊的方法,当你创建这个类的新实例时,它被称为类构造函数或初始化方法。
    • 您可以像普通函数一样声明其他类方法,但每个方法的第一个参数是self。Python 为您将self参数添加到列表中;调用方法时不需要包含它。
  • 创建实例对象

    要创建一个类的实例,您可以使用类名调用该类,并传入其__init__方法接受的任何参数。
    
    This would create first object of Employee class
    emp1 = Employee("Alex", 2000)
    This would create second object of Employee class
    emp2 = Employee("Manni", 5000)
    
  • 访问属性

    您可以使用带对象的点运算符访问对象的属性。将使用类名访问类变量,如下所示 -
    
    emp1.displayEmployee()
    emp2.displayEmployee()
    print ("Total Employee %d" % Employee.empCount)
    
    现在,将所有概念放在一起 -
    
    #!/usr/bin/python3
    class Employee:
       'Common base class for all employees'
       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)
    #This would create first object of Employee class"
    emp1 = Employee("Alex", 2000)
    #This would create second object of Employee class"
    emp2 = Employee("Manni", 5000)
    emp1.displayEmployee()
    emp2.displayEmployee()
    print ("Total Employee %d" % Employee.empCount)
    
    执行上述代码时,会产生以下结果 -
    
    Name :  Alex ,Salary:  2000
    Name :  Manni ,Salary:  5000
    Total Employee 2
    
    您可以随时添加、删除或修改类和对象的属性 -
    
    emp1.salary = 7000  # Add an 'salary' attribute.
    emp1.name = 'xyz'  # Modify 'age' attribute.
    del emp1.salary  # Delete 'age' attribute.
    
    您可以使用以下函数,而不是使用普通语句来访问属性 -
    • getattr(obj, name[, default])− 访问对象的属性。
    • hasattr(obj,name)− 检查属性是否存在。
    • setattr(obj,name,value)− 设置属性。如果属性不存在,则将创建它。
    • delattr(obj, name)− 删除一个属性。
    
    hasattr(emp1, 'salary')    # Returns true if 'salary' attribute exists
    getattr(emp1, 'salary')    # Returns value of 'salary' attribute
    setattr(emp1, 'salary', 7000) # Set attribute 'salary' at 7000
    delattr(emp1, 'salary')    # Delete attribute 'salary'
    
  • 内置类属性

    每个 Python 类都遵循内置属性,并且可以像任何其他属性一样使用点运算符访问它们 -
    • __dict__− 包含类名称空间的字典。
    • __doc__− 类文档字符串或无,如果未定义。
    • __name__− 类名。
    • __module__− 定义类的模块名称。该属性在交互模式下为“__main__”。
    • __bases__− 包含基类的可能为空的元组,按照它们在基类列表中出现的顺序排列。
    对于上面的类,让我们尝试访问所有这些属性 -
    
    #!/usr/bin/python3
    class Employee:
       'Common base class for all employees'
       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)
    emp1 = Employee("Alex", 2000)
    emp2 = Employee("Manni", 5000)
    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__: Common base class for all employees
    Employee.__name__: Employee
    Employee.__module__: __main__
    Employee.__bases__: (<class 'object'>,)
    Employee.__dict__: {
       'displayCount': <function Employee.displayCount at 0x0160D2B8>, 
       '__module__': '__main__', '__doc__': 'Common base class for all employees', 
       'empCount': 2, '__init__': 
       <function Employee.__init__ at 0x0124F810>, 'displayEmployee': 
       <function Employee.displayEmployee at 0x0160D300>,
       '__weakref__': 
       <attribute '__weakref__' of 'Employee' objects>, '__dict__': 
       <attribute '__dict__' of 'Employee' objects>
    }
    
  • 销毁对象(垃圾收集)

    Python 会自动删除不需要的对象(内置类型或类实例)以释放内存空间。Python 定期回收不再使用的内存块的过程称为垃圾收集。
    Python 的垃圾收集器在程序执行期间运行,并在对象的引用计数达到零时触发。对象的引用计数随着指向它的别名数量的变化而变化。
    当一个对象被分配一个新名称或放置在一个容器(列表、元组或字典)中时,它的引用计数会增加。当使用del删除对象时,对象的引用计数会减少,它的引用被重新分配,或者它的引用超出范围。当对象的引用计数达到零时,Python 会自动收集它。
    
    a = 40      # Create object <40>
    b = a       # Increase ref. count  of <40> 
    c = [b]     # Increase ref. count  of <40> 
    del a       # Decrease ref. count  of <40>
    b = 100     # Decrease ref. count  of <40> 
    c[0] = -1   # Decrease ref. count  of <40> 
    
    您通常不会注意到垃圾收集器何时销毁孤立实例并回收其空间。但是,类可以实现特殊方法__del__(),称为析构函数,在实例即将被销毁时调用。此方法可用于清理实例使用的任何非内存资源。

    例子

    这个 __del__() 析构函数打印即将被销毁的实例的类名 -
    
    #!/usr/bin/python3
    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, "destroyed")
    pt1 = Point()
    pt2 = pt1
    pt3 = pt1
    print (id(pt1), id(pt2), id(pt3))   # prints the ids of the obejcts
    del pt1
    del pt2
    del pt3
    
    执行上述代码时,会产生以下结果 -
    
    140338326963984 140338326963984 140338326963984
    Point destroyed
    
    Note− 理想情况下,你应该在一个单独的文件中定义你的类,然后你应该使用import语句将它们导入到你的主程序文件中。
    在上面的示例中,假设 Point 类的定义包含在point.py中,并且其中没有其他可执行代码。
    
    #!/usr/bin/python3
    import point
    p1 = point.Point()
    
  • 类继承

    无需从头开始,您可以通过在新类名称后的括号中列出父类,从预先存在的类派生它来创建类。
    子类继承其父类的属性,您可以像在子类中定义它们一样使用这些属性。子类也可以覆盖父类的数据成员和方法。

    句法

    派生类的声明很像它们的父类;但是,要继承的基类列表在类名之后给出 -
    
    class SubClassName (ParentClass1[, ParentClass2, ...]):
       'Optional class documentation string'
       class_suite
    

    例子

    
    #!/usr/bin/python3
    class Parent:        # define parent class
       parentAttr = 100
       def __init__(self):
          print ("Calling parent constructor")
       def parentMethod(self):
          print ('Calling parent method')
       def setAttr(self, attr):
          Parent.parentAttr = attr
       def getAttr(self):
          print ("Parent attribute :", Parent.parentAttr)
    class Child(Parent): # define child class
       def __init__(self):
          print ("Calling child constructor")
       def childMethod(self):
          print ('Calling child method')
    c = Child()          # instance of child
    c.childMethod()      # child calls its method
    c.parentMethod()     # calls parent's method
    c.setAttr(200)       # again call parent's method
    c.getAttr()          # again call parent's method
    
    执行上述代码时,会产生以下结果 -
    
    Calling child constructor
    Calling child method
    Calling parent method
    Parent attribute : 200
    
    以类似的方式,您可以从多个父类驱动一个类,如下所示 -
    
    class A:        # define your class A
    .....
    class B:         # define your calss B
    .....
    class C(A, B):   # subclass of A and B
    .....
    
    您可以使用 issubclass() 或 isinstance() 函数来检查两个类和实例的关系。
    • issubclass(sub, sup)布尔函数返回 True,如果给定的子类sub确实是超类的子类sup.
    • isinstance(obj, Class)布尔函数返回 True,如果obj是类Class的实例或者是 Class 的子类的实例
  • 覆盖方法

    你总是可以覆盖你的父类方法。覆盖父类方法的原因之一是您可能希望子类具有特殊或不同的功能。

    例子

    
    #!/usr/bin/python3
    class Parent:        # define parent class
       def myMethod(self):
          print ('Calling parent method')
    class Child(Parent): # define child class
       def myMethod(self):
          print ('Calling child method')
    c = Child()          # instance of child
    c.myMethod()         # child calls overridden method
    
    执行上述代码时,会产生以下结果 -
    
    Calling child method
    
  • 基本重载方法

    下表列出了一些您可以在自己的类中覆盖的通用功能 -
    序号 方法、说明和样品调用
    1
    __init__ ( self [,args...] )
    构造函数(带有任何可选参数)
    示例调用:obj = className(args)
    2
    __del__( self )
    析构函数,删除一个对象
    示例调用:来自 obj
    3
    __repr__( self )
    可评估的字符串表示
    示例调用:repr(obj)
    4
    __str__( self )
    可打印字符串表示
    示例调用:str(obj)
    5
    __cmp__ ( self, x )
    对象比较
    示例调用:cmp(obj, x)
  • 重载运算符

    假设您已经创建了一个 Vector 类来表示二维向量。当您使用加号运算符添加它们时会发生什么?Python 很可能会对你大吼大叫。
    但是,您可以在类中定义__add__方法来执行向量加法,然后加号运算符将按预期运行 -

    例子

    
    #!/usr/bin/python3
    class Vector:
       def __init__(self, a, b):
          self.a = a
          self.b = b
       def __str__(self):
          return 'Vector (%d, %d)' % (self.a, self.b)
       
       def __add__(self,other):
          return Vector(self.a + other.a, self.b + other.b)
    v1 = Vector(2,10)
    v2 = Vector(5,-2)
    print (v1 + v2)
    
    执行上述代码时,会产生以下结果 -
    
    Vector(7,8)
    
  • 数据隐藏

    对象的属性在类定义之外可能可见,也可能不可见。您需要使用双下划线前缀命名属性,然后这些属性将不会直接对外人可见。

    例子

    
    #!/usr/bin/python3
    class JustCounter:
       __secretCount = 0
      
       def count(self):
          self.__secretCount += 1
          print (self.__secretCount)
    counter = JustCounter()
    counter.count()
    counter.count()
    print (counter.__secretCount)
    
    执行上述代码时,会产生以下结果 -
    
    1
    2
    Traceback (most recent call last):
       File "test.py", line 12, in <module>
          print counter.__secretCount
    AttributeError: JustCounter instance has no attribute '__secretCount'
    
    Python 通过在内部更改名称以包含类名来保护这些成员。您可以访问object._className__attrName等属性。如果您将最后一行替换为以下内容,那么它对您有用 -
    
    .........................
    print (counter._JustCounter__secretCount)
    
    执行上述代码时,会产生以下结果 -
    
    1
    2
    2