第 3 章 Python中的面向对象编程

面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是一种程序设计范型,同时也是一种程序开发的方法。对象指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。

面向对象程序设计可以看作一种在程序中包含各种独立而又互相调用的对象的思想,这与传统的思想刚好相反:传统的程序设计主张将程序看作一系列函数的集合,或者直接就是一系列对电脑下达的指令。面向对象程序设计中的每一个对象都应该能够接受数据、处理数据并将数据传达给其它对象,因此它们都可以被看作一个小型的“机器”,即对象。

目前已经被证实的是,面向对象程序设计推广了程序的灵活性和可维护性,并且在大型项目设计中广为应用。

3.1 Python对象和类

3.1.1 创建类

Python一门面向对象的语言。在Python中所有的东西都是对象,比如之前学习的整型、字符串等等,甚至模块、函数也都是对象。

面向对象编程时使用对象创建程序,使用对象存储数据和行为。

在Python中,使用关键字class定义类。类通常包括数据区域,用以存数数据和方法的定义。Python中的所有类,都包含一个特殊的方法,叫作初始化(initializer),或者叫作构造方法。构造方法会在使用类创建新的对象时自动执行。例如:

class Person:

      # 构造函数
      def __init__(self, name):
            self.name = name

      # 定义方法
     def whoami(self):
           return "You are " + self.name

上述代码中,我们创建了一个名叫Person的类,这个类中包含数据字段name和方法whoami()。

Python中的所有方法,包括构造方法,首个参数都是self。这个参数指向对象本身。当我们创建一个新的对象时候,self参数就会自动指向新创建的对象。

3.1.2 从类中创建对象

使用类名就可创建对象。当我们调用方法时,不需要传递self参数,Python会自动传递。例如:

p1 = Person('tom')
print(p1.whoami())
print(p1.name)

输出结果为:

You are tom
tom

我们还可以改变数据字段的值:

p1.name = 'jerry'
print(p1.name)

输出结果为jerry。然而,像这样从类的外部获取数据字段,属于不太好的操作方式,下面我们看如何阻止这种操作。

3.1.3 隐藏数据字段

为了隐藏数据字段,我们需要定义私有数据字段。在Python中,使用两个前置下划线,就可定义私有数据字段和私有方法。比如:

class BankAccount:

     # 构造函数
    def __init__(self, name, money):
        self.__name = name  # 定义私有数据字段
        self.__balance = money  # 定义私有数据字段

    def deposit(self, money):
        self.__balance += money

    def withdraw(self, money):
        if self.__balance > money:
            self.__balance -= money
            return money
        else:
            return "Insufficient funds"

    def checkbalance(self):
        return self.__balance

b1 = BankAccount('tim', 400)
print(b1.withdraw(500))
b1.deposit(500)
print(b1.checkbalance())
print(b1.withdraw(800))
print(b1.checkbalance())

在上述代码中,我们定义了BankAccout类,这个类有两个数据字段,但都是私有字段。代码运行结果为:

Insufficient funds
900
800
100

现在,让我们尝试访问私有数据字段:

print(b1.__balance)

结果显示:

AttributeError: 'BankAccount' object has no attribute '__balance'

这就表明,设置为私有的数据字段,无法在类的外部访问。

3.2 操作符重载

我们之前已经看到+运算符不但能加数字,还能连接字符串。这之所以可能,是因为+运算符在int类和str类中都被重载。运算符实际上对应着类中相应的方法。为运算符定义方法就是所谓的运算符重载。比如,为让自定义对象能使用+运算符,我们需要定义名叫__add__的方法。

让我们看个例子:

import math

class Circle:

    def __init__(self, radius):
        self.__radius = radius

    def setRadius(self, radius):
        self.__radius = radius

    def getRadius(self):
        return self.__radius

    def area(self):
        return math.pi * self.__radius ** 2

    def __add__(self, another_circle):
        return Circle(self.__radius + another_circle.__radius)

c1 = Circle(4)
print(c1.getRadius())

c2 = Circle(5)
print(c2.getRadius())

c3 = c1 + c2  # 之所以能使用加法运算符,是因为我们定义了__add__方法
print(c3.getRadius())

在上面的例子中,我们为类添加了__add__方法,该方法允许使用+运算符对两个circle对象求和。在__add__方法中,我们创建了一个新的对象,并将其返回给调用者。运行结果如下:

4
5
9

在Python中,除__add__方法对应+运算符之外,还有其他能够重载运算符的方法:如__mul__、__sub__等等。

3.3 继承和多态

继承(inheritance)允许开发人员先创建一个通用的类,然后扩展为特定类。使用继承机制,我们可以获得类的数据字段和方法,还可以增加自定义的字段和方法,因此,继承提供了一种组织代码、重用代码的方式。

在面向对象的术语中,当类X继承自类Y时,Y被叫做超类(super class)或基类(base class),而X被成为子类(subclass)或者衍生类(derived class)。

私有数据字段和私有方法只在类的内部使用。子类只能继承父类的非私有数据字段和非私有方法。

继承的语法如下:

class SubClass(SuperClass):
  # data fields
  # instance methods

让我们看个例子:

class Vehicle:

    def __init__(self, name, color):
        self.__name = name      # __name是私有数据字段
        self.__color = color

    def getColor(self):
        return self.__color

    def setColor(self, color):
        self.__color = color

    def getName(self):
        return self.__name

class Car(Vehicle):

    def __init__(self, name, color, model):
        # 调用父类的构造方法
        super().__init__(name, color)
        self.__model = model

    def getDescription(self):
        return self.getName() + self.__model + " in " + self.getColor() + " color"

c = Car("Ford Mustang", "red", "GT350")
print(c.getDescription())
print(c.getName())

上述代码中,我们创建了基类Vehicle和子类Car。在子类Car中,我们没有定义getName()方法,但我们仍然可以访问getName(),这是因为类Car继承自Vehicle类。在这段代码中,super()方法用来调用基类的方法。上述代码的运行结果如下:

Ford MustangGT350 in red color
Ford Mustang

3.3.1 多重继承

不像Java和C#语言,Python允许多重继承。即一次继承多个基类,比如:

class Subclass(SuperClass1, SuperClass2, ...):
   # initializer
   # methods

看如下实例:

class MySuperClass1():

    def method_super1(self):
        print("method_super1 method called")

class MySuperClass2():

    def method_super2(self):
        print("method_super2 method called")

class ChildClass(MySuperClass1, MySuperClass2):

    def child_method(self):
        print("child method")

c = ChildClass()
c.method_super1()
c.method_super2()

输出结果为:

method_super1 method called
method_super2 method called

因为子类ChildClass继承自MySuperClass1 , MySuperClass2,因此,ChildClass对象c可以访问method_super1()方法和 method_super2()方法。

3.3.2 重写方法

为重写基类的某个方法,子类需要定义一个同名的方法(即拥有相同名称和相同数量的参数)。例如:

class A():

    def __init__(self):
        self.__x = 1

    def m1(self):
        print("m1 from A")

class B(A):

    def __init__(self):
        self.__y = 1

    def m1(self):
        print("m1 from B")

c = B()
c.m1()

在这段代码中,我们重写了基类的m1()方法。输出结果为:

m1 from B

3.3.3 判断对象是否属于某类

isinstance() 方法用来检测指定对象是否是某个类的实例。例如:

>>> isinstance(1, int)
True

>>> isinstance(1.2, int)
False

>>> isinstance([1,2,3,4], list)
True