Python编程基础教程:面向对象编程入门

121 阅读7分钟

1.背景介绍

Python是一个非常著名的、具有强大的功能的高级语言,能够简单易用地实现各种应用场景。然而,随着Web开发、数据科学、机器学习等领域的蓬勃发展,越来越多的开发者需要解决复杂的业务逻辑,需要用到面向对象编程(Object-Oriented Programming,OOP)才能更好地将复杂的问题抽象成计算机可以处理的形式。

由于Python不支持像Java、C++那样的类成员私有化访问控制,因此在面向对象的设计中,类的属性和方法之间没有显式的关系。比如,一个类的实例可以直接访问另一个类的私有属性或方法。为了保护类的内部实现细节,我们只能通过约定俗成的命名规则和慎重的编程风格来防止意外发生。

本文从基本语法、函数、模块和类的角度出发,带您走进面向对象编程的世界。

2.核心概念与联系

对象(Object)

“对象”是一个编程概念,它指的是实体及其状态和行为的一组数据。在面向对象编程中,对象就是类的实例,是用来描述客观事物的一个抽象集合。它包含了数据和操作数据的行为的方法,可以通过发送消息调用这些方法来操作对象的数据。换句话说,对象是一个“盒子”,里面封装着一些数据和对数据的一些操作行为。

类(Class)是创建对象的蓝图,它定义了对象的结构(数据成员),属性(数据特征)和行为(函数)。类提供了创建对象的模板,使得实例对象拥有相同的结构和行为。对于不同的对象来说,它们共同遵循的规则都由类的定义决定。

继承(Inheritance)是面向对象编程中的重要概念。子类(Subclass)是父类的延伸,它共享父类的属性和方法,并添加自己特有的属性和方法。这样就可以创建出具有相似特性的多个对象,从而减少重复代码的编写。

多态(Polymorphism)是指允许不同类型的对象对同一消息做出响应的方式。在面向对象编程中,多态是指同一消息(函数调用)在不同的情况下会产生不同的结果。对象根据自己的特性来选择执行哪个版本的函数。

接口(Interface)是在两类对象之间建立契约的一系列协议。接口规定了该接口所需的属性和方法,对象只要满足了这些要求就能被认为实现了该接口。接口的作用主要是用于实现多态性。

抽象类(Abstract Class)

抽象类是不能实例化的类,也就是说它没有构造函数。抽象类可以定义一些抽象方法,后续的子类必须覆盖或者实现这些抽象方法。抽象方法是一种特殊的方法,它不能被普通的方法调用,它的存在只是为了让其他类知道这个类提供了一个接口供其他类来实现。抽象类可以作为基类,也可以作为子类,但是不能被实例化。

封装(Encapsulation)

封装是指把一个对象的状态信息隐藏起来,只暴露必要的信息给外部使用,不允许其他对象直接访问对象内部数据。面向对象编程中,通过访问权限修饰符(public/private/protected)来控制对对象的访问权限。

多态(Polymorphism)

多态是指一个类实例可以接收不同类型对象,并调用其相应的操作,这种能力称为多态性。面向对象编程提供了丰富的多态机制,包括继承、接口和虚函数等。

依赖倒置(Dependency Inversion)

依赖倒置是指类应该依赖于接口而不是实现。换句话说,高层模块不应该依赖低层模块,二者都应该依赖于抽象。当我们需要改变底层模块时,只需要修改抽象接口,不会影响高层模块。

3.核心算法原理和具体操作步骤以及数学模型公式详细讲解

本文将从以下方面详细介绍面向对象编程的核心算法原理和具体操作步骤:

  1. 创建类
  2. 属性和方法
  3. 继承
  4. 多态
  5. 封装
  6. 接口

首先,我们先创建一个简单的学生类,用来描述学生的相关信息:

class Student:
    def __init__(self, name, age):
        self.__name = name   # private attribute
        self.__age = age     # private attribute
    
    def get_name(self):      # public method to access the name property
        return self.__name
    
    def set_name(self, value):    # public method to modify the name property
        if not isinstance(value, str):
            raise ValueError("Name should be a string")
        else:
            self.__name = value
            
    def get_age(self):       # public method to access the age property
        return self.__age
    
    def set_age(self, value):     # public method to modify the age property
        if type(value)!= int:
            raise ValueError("Age should be an integer")
        elif value < 0 or value > 120:
            raise ValueError("Invalid age range")
        else:
            self.__age = value

上面的代码创建了一个Student类,该类有一个私有属性__name,一个私有属性__age,两个公开的属性nameage。并且定义了四个公开的方法:

  • __init__()方法用来初始化对象的属性;
  • get_xxx()方法用来获取属性的值;
  • set_xxx()方法用来设置属性的值。

通过以上方法,我们可以得到一个Student类的实例,如下所示:

s1 = Student('Alice', 20)
print(s1.get_name())          # Output: Alice
print(s1.get_age())           # Output: 20

当然,上面仅仅是个简单的例子,实际应用中还会涉及更多的属性和方法。接下来,我们深入分析一下面向对象编程的这些核心算法。

创建类

面向对象编程的创建类过程很简单,只需要定义一个新类,然后使用关键字class来声明一个新的类即可。例如,我们可以创建一个表示人类的Person类:

class Person:
  pass

pass语句是空语句,是为了保持程序结构完整,因为一个类至少要定义一个构造器。如此,我们就可以创建Person类的实例:

p = Person()

虽然Person类是一个空类,但它已经可以实例化了。至于类属性和方法,则需要在类内部定义。

属性和方法

类有两种属性:实例属性和类属性。实例属性属于各个对象的独有属性,每个对象都有自己的实例属性值,互不干扰;类属性是所有对象所共有,通常可以被看作是静态的,每个对象都可以共享这些属性值。

类方法是与类相关联的函数,使用@classmethod装饰器进行标识,其第一个参数是类本身,通过类调用。实例方法是与对象关联的函数,使用@staticmethod装饰器进行标识,不以任何对象为首参,只能通过类进行调用。

属性的定义有两种方式,分别是数据属性和存取方法。前者是通过property装饰器来定义,后者是通过getter和setter方法来定义,getter方法返回属性值,setter方法设置属性值。

import math

class Circle:
    pi = 3.14

    @property
    def radius(self):
        """Get the circle's radius."""
        return self._radius
    
    @radius.setter
    def radius(self, r):
        """Set the circle's radius."""
        if r <= 0:
            raise ValueError("Radius must be positive.")
        else:
            self._radius = r

    @property
    def area(self):
        """Calculate the circle's area."""
        return round(math.pi * self._radius ** 2, 2)

    @property
    def circumference(self):
        """Calculate the circle's circumference."""
        return round(2 * math.pi * self._radius, 2)

上面的代码定义了一个圆形类,其中包含两个数据属性——半径和面积,以及三个存取方法——获取半径、设置半径、计算面积、计算周长。注意这里的数据属性radius不是实例属性,而是类属性。另外,我们还定义了两个私有变量_radius_area,以避免外部代码直接修改该属性。

c = Circle()
c.radius = 5         # Set the circle's radius
print(c.radius)       # Get the circle's radius
print(c.area)         # Calculate the circle's area
print(c.circumference)        # Calculate the circle's circumference

输出:

5.0
78.54
31.41

继承

继承是面向对象编程中最常用的特性之一,它允许子类获得父类的全部属性和方法,并可以进一步扩展。子类可以重新定义父类的属性和方法,也可以增加新的属性和方法。在Python中,可以使用:符号来实现继承。

class Animal:
    def __init__(self, name):
        self.name = name
        
    def speak(self):
        print("{} makes a noise.".format(self.name))
        
class Dog(Animal):
    def bark(self):
        print("{} barks loudly!".format(self.name))
    
d = Dog('Rex')
d.speak()            # Output: Rex makes a noise.
d.bark()             # Output: Rex barks loudly!

上面的代码定义了一个动物类Animal,它包含一个属性name和一个方法speak,定义了一种叫做叫做"叫声"的行为。之后,我们定义了一个狗类Dog,它是Animal类的子类,重新定义了Animalspeak方法。

由于Dog类重新定义了speak方法,所以它可以覆盖掉父类的同名方法,而且子类还可以新增自己的bark方法。这样,我们就创建了一个狗类的实例d,调用它的speakbark方法,可以看到输出的效果。

多态

多态是面向对象编程中最重要的特性之一,它允许不同类型的对象对同一消息做出响应。在Python中,多态可以体现为不同类型的对象可以对同一消息做出不同的反应,例如整数型对象和字符串型对象可以对加法运算符做出不同的反应。

一般来说,多态分为编译时多态和运行时多态。编译时多态指的是函数调用的时候,根据传递的参数的类型来确定执行的函数,运行时多态则是指函数调用的时候,动态绑定到具体的函数实现上。

class Shape:
    def draw(self):
        raise NotImplementedError

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def draw(self):
        for i in range(self.height):
            print("*" * self.width)

class Triangle(Shape):
    def __init__(self, base, height):
        self.base = base
        self.height = height
    
    def draw(self):
        for i in range(self.height):
            print(" " * (self.base - i) + "*" * i)

shapes = [Rectangle(5, 3), Triangle(7, 5)]

for shape in shapes:
    shape.draw()
    print("-" * 10)

上面的代码定义了几种图形类——矩形和三角形,它们都是Shape的子类。它们都实现了一个抽象方法draw,用于绘制图形。Rectangle类和Triangle类都分别定义了自己的构造器和draw方法。

程序最后创建了两个图形的列表,并遍历每一个图形,调用它的draw方法。由于多态的存在,程序可以正确地调用不同类型的draw方法,打印出不同形状的图形。

封装

封装是面向对象编程中的重要概念,它是指把一个对象的状态信息隐藏起来,只暴露必要的信息给外部使用。在Python中,可以通过访问权限修饰符(public/private/protected)来控制对对象的访问权限。

class BankAccount:
    def __init__(self, owner, balance=0):
        self.__owner = owner
        self.__balance = balance
        
    def deposit(self, amount):
        self.__balance += amount
        
    def withdraw(self, amount):
        if self.__balance >= amount:
            self.__balance -= amount
        else:
            print("Insufficient funds!")
    
    def show_balance(self):
        print("Balance of {} is {}".format(self.__owner, self.__balance))

上面的代码定义了一个银行账户类BankAccount,其中包含三个私有属性——__owner__balance和三个公开的方法——depositwithdrawshow_balance__owner__balance是私有属性,只能通过gettersetter方法来访问和修改,其他方法都可以通过BankAccount的实例来访问。

account1 = BankAccount("John", 1000)
account1.show_balance()      # Output: Balance of John is 1000
account1.deposit(500)
account1.show_balance()      # Output: Balance of John is 1500
account1.withdraw(2000)      # Output: Insufficient funds!
account1.withdraw(1000)
account1.show_balance()      # Output: Balance of John is 500

上面的代码创建了两个BankAccount类的实例——account1account2,并调用了它们的各种方法。由于__owner__balance是私有属性,所以外部代码无法直接访问或修改它们,只能通过公开的接口来访问。