Python 编程范式的科普

编程范式是计算机编程的基本风格或典范模式。如果说每个编程者都在创造虚拟世界,那么编程范式就是程序员置身其中采用的世界观和方法论。

常见的编程范式包括:

  • 面向过程编程
  • 面向对象编程

编程范型提供了程序员对程序执行的看法:在面向过程编程中,程序员认为程序是一系列相互调用的过程或者函数;在面向对象编程中,程序员认为程序是一系列相互作用的对象;而在函数式编程中一个程序会被看作是一个无状态的函数计算的序列。

不同的编程语言也会提倡不同的编程范式,一些语言是专门为某个特定的编程范式设计的。例如,C 支持面向过程编程,Java 支持面向对象编程。Python 编程语言支持多种编程范式,应该在不同的应用场景下,选择合适的编程范式。

1. 面向过程编程

1.1 概述

面向过程编程是一种以过程为中心的编程思想,程序由一系列相互调用的过程组成。面向过程编程的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么。

面向过程编程特别适合解决线性(或者说按部就班)的算法问题。在这类算法问题中,解决问题的途径由多个步骤构成,使用函数描述每个步骤,因此使用函数对问题建模非常合适

面向过程编程强调 “自顶向下” 和 “精益求精” 的设计方式。解决一个复杂的问题的方法是将问题划分为多个子问题,将子问题再继续分解直到问题足够简单到可以在一个小步骤范围内解决。

面向过程编程不足之处就是它不适合某些种类问题的解决,例如图形化编程,在图形化编程中,客观世界由具体的对象(窗口、标签、按钮等)组成,无法自然的将函数与图形对象一一对应,因此面向过程编程不适合用于图形化编程的领域。

1.2 例子

本节采用面向过程编程的方式完成这样的任务:将文本文件中小写字母转换为大写字母。将任务划分为 3 个步骤:

  1. 读取文本文件的内容
  2. 对读取的文本进行转换,将小写字母转换为大写字母
  3. 把转换后的内容保存到文件中

任务被划分为 3 个简单的子任务,然后使用函数实现每个子任务,采用面向过程编程的方式解决这样的问题非常自然。

设计与实现的步骤如下:

1. 设计主任务和子任务:

任务名称 函数名 任务功能描述
主任务 main 执行子任务
读取任务 read_file 读取文本文件的内容
转换任务 transform 将小写字母转换为大写字母
保存任务 save_file 把转换后的内容保存到文件中
  1. 实现主任务
实例演示
预览 复制
复制成功!
def read_file(path):
    pass

def transform(input):
    pass

def save_file(path, content):
    pass

def main():
    input = read_file("test.txt")
    output = transform(input)
    save_file("test.txt", output)

main()
运行案例 点击 "运行案例" 可查看在线运行效果
  • 在第 1 行,定义函数 read_file(path),它读取 path 指定的文件,返回文件的内容
  • 在第 4 行,定义函数 transform(input),它将输入 input 中的小写字母转换为大写字母
  • 在第 7 行,定义函数 save_file(path, content),它将字符串 content 保存到 path 指定的文件
  • 在第 10 行,定义函数 main
    • 在第 11 行,读取文件 test.txt 的内容,将内容保存到变量 input 中
    • 在第 12 行,对输入 input 进行转换,将转换后的内容保存到变量 output 中
    • 在第 13 行,将字符串 output 保存到文件 test.txt

3. 实现读取任务

def read_file(path):
    lines = ''
    file = open(path)
    for line in file:
        lines += line
    file.close()
    return lines
  • 在第 2 行,将文件内容保存到变量 lines 中,设定它的初值为空字符串
  • 在第 3 行,打开文件
  • 在第 4 行,逐行遍历文件
  • 在第 5 行,将每行的内容累加到变量 lines 中
  • 在第 6 行,关闭文件

4. 实现转换任务

def transform(input, output):
    output = input.upper()
    return output
  • 在第 2 行,调用字符串的 upper 方法返回大写的字符串
  • 在第 3 行,返回 output

5. 实现保存任务

def save_file(path, content):
    file = open(path, "w")
    file.write(content)
    file.close()
  • 在第 2 行,以可写方式打开文件
  • 在第 3 行,调用文件的 write 方法保存文件
  • 在第 4 行,关闭文件

2. 面向对象编程

2.1 概述

面向对象编程是一种以对象为中心的编程思想,程序由一系列相互作用的对象组成。面向对象编程中,程序包含各种独立而又互相调用的对象,而在面向过程编程中,将程序看作一系列函数的集合。

面向对象程序设计方法是尽可能模拟人类的思维方式,使得软件的开发方法与过程尽可能接近人类认识世界、解决现实问题的方法和过程,也即使得描述问题的问题空间与问题的解决方案空间在结构上尽可能一致,把客观世界中的实体抽象为问题域中的对象

例如图形化编程,在图形化编程中,客观世界由具体的对象(窗口、标签、按钮等)组成,可以自然的将对象与图形对象一一对应,因此面向对象编程适合用于图形化编程的领域。

2.2 基本概念

面向对象编程包含通过类、实例、属性和方法等核心概念:

  • 类,类是相似对象的集合,类包含和描述了“具有共同特性和共同行为”的一组对象。
  • 实例,实例则指的是类的实例,实例包含属性和方法。
  • 属性,属性是指对象的特性。例如,存在一个对象 person,对象 person 的属性包括姓名和年龄。
  • 方法,方法是指对象的行为。例如,存在一个对象 person,对象 person 的包括一个方法 show,通过调用方法 show 可以输出对象 person 的相关信息。

下面的代码演示了以上 4 个基本概念:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def show(self):
        print('My name is %s, I am %d years old' % (self.name, self.age))        

tom = Person('tom', 10)        
jerry = Person('jerry', 12)        
tom.show()
jerry.show()
  • 在第 1 行,定义了类 Person
  • 在第 2 行,定义了类 Person 的方法 init
    • 方法 init 设置类 Person 的属性 name 和 age
  • 在第 6 行,定义了类 Person 的方法 show,
    • 方法 show 输出类 Person 的属性 name 和 age
  • 在第 9 行,通过类 Person 创建一个实例 tom
    • 实例 tom 的属性 name 是 ‘tom’,age 是 10
  • 在第 10 行,通过类 Person 创建一个实例 jerry
    • 实例 jerry 的属性 name 是 ‘jerry’,age 是 12
  • 在第 11 行,调用类 tom 的方法 show
  • 在第 12 行,调用类 jerry 的方法 show

3.3 基本特性

3.3.1 封装

封装是将数据和代码捆绑到一起,对象的某些属性和方法可以是私有的,不能被外界访问,以此实现对数据和代码不同级别的访问权限。防止了程序相互依赖性而带来的变动影响,有效实现了两个目标:对数据和行为的包装和信息隐藏。

在下面的程序中,类 Span 描述了间距,间距有 3 个属性:

  • 开始位置
  • 结束位置
  • 长度,长度等于开始位置到结束位置之间的距离

使用封装通过方法访问这些属性,而不是直接访问这些属性,代码如下:

class Span:
    def __init__(self, start, end):
        self._start = start
        self._end = end

    def get_start(self):
        return self._start

    def get_end(self):
        return self._end

    def get_length(self):
        return self._end - self._start

span = Span(10, 100)
print('start = %d' % span.get_start())
print('end = %d' % span.get_end())
print('length = %d' % span.get_length())
  • 在第 2 行,定义了构造函数,使用 start 和 end 构造间距
    • 在第 3 行,_start 是私有属性,描述了间距的开始位置,通过参数 start 设置
    • 在第 4 行,_end 是私有属性,,描述了间距的结束位置,通过参数 end 设置
  • 在第 6 行,定义了成员函数 get_start,外界通过 get_start 获取开始位置
  • 在第 9 行,定义了成员函数 get_end,外界通过 get_end 获取结束位置
  • 在第 12 行,定义了成员函数 get_length,外界通过 get_length 获取间距的长度

程序运行输出如下:

start = 10
end = 100
length = 90

3.3.2 继承

继承是一种层次模型,这种层次模型能够被重用。层次结构的上层具有通用性,但是下层结构则具有特殊性。在继承的过程中,子类则可以从父类继承一些方法和属性。子类除了可以继承以外同时还能够进行修改或者添加。

下面的例子描述父类 Person 和子类 Teacher 之间的继承关系,子类 Teacher 从父类 Person 继承了一些方法和属性,父类 Person 代码如下:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce(self):
        print('My name is', self.name)
        print('My age is', self.age)
  • 在第 2 行,在构造函数 __init__ 中,设置属性 name 和 age
  • 在第 6 行,在成员函数 introduce 中,输出属性 name 和 age

子类 Teacher 代码如下:

class Teacher(Person):
    def __init__(self, name, age, salary):
        Person.__init__(self, name, age)
        self.salary = salary

    def showSalary(self):
        print('My salary is', self.salary)

teacher = Teacher('tom', 30, 5000)
teacher.introduce() 
teacher.showSalary()
  • 在第 2 行,在构造函数 __init__ 中,设置属性 name、age 和 salary
    • 在第 3 行,调用父类的 __init__ 方法,设置属性 name 和 age
    • 在第 4 行,设置子类 Tearch 特有的属性 salary
  • 在第 6 行,在成员函数 showSalary 中,输出属性 salary
  • 在第 9 行,实例化一个对象 teacher
  • 在第 10 行,调用 teacher 的 introduce 方法,该方法是子类继承的方法
  • 在第 11 行,调用 teacher 的 showSalary 方法,该方法是子类特有的方法

程序运行输出如下:

My name is tom
My age is 30
My salary is 5000

3.3.3 多态

在面向对象语言中,接口的多种不同的实现方式即为多态。多态机制使具有不同内部结构的对象可以共享相同的外部接口,它是面向对象程序设计(OOP)的一个重要特征。

下面通过一个具体的例子演示多态的意义:有多种类型的形体,例如:正方形、三角形等。但是,不论形体的类型是什么,每一种形体都有周长的概念。显然,计算每种形体的周长的公式是不一样的。面对抽象的形体时,希望能够获取形体的周长。

首先,定义父类 Shape,代码如下:

class Shape:
    def get_circle(self):
        pass
  • 在第 1 行,定义了父类 Shape,子类 Square 和 Triangle 继承于 Shape
  • 在第 2 行,定义了成员方法 get_circle,get_circle 计算形体的周长
  • 在第 3 行,这里是一个空方法,仅仅定义了接口,在子类中定义具体的实现

定义子类 Square,代码如下:

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def get_circle(self):
        return self.side * 4
  • 在第 1 行,定义了子类 Square,继承于父类 Shape
  • 在第 2 行,定义构造函数 __init__,参数 side 表示正方形的边长
  • 在第 5 行,定义成员方法 get_circle,计算正方形的周长

定义子类 Triangle,代码如下:

class Triangle(Shape):
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

    def get_circle(self):        
        return self.a + self.b + self.c
  • 在第 1 行,定义了子类 Triangle,继承于父类 Shape
  • 在第 2 行,定义构造函数 __init__,参数 a、b、c 表示三角形的边长
  • 在第 7 行,定义成员方法 get_circle,计算三角形的周长

在父类 Shape 中定义了接口 get_circle ,子类 Square 和子类 Triangle 分别实现了接口 get_circle。只要对象的类型是 Shape,不论具体的类型是 Square 还是 Triangle,都可以调用接口 get_circle,代码如下:

square = Square(10)
triangle = Triangle(3, 4, 5)
shapes = [square, triangle]
for shape in shapes:
    print(shape.get_circle())
  • 在第 1 行,构造一个正方形 square,边长是 10
  • 在第 2 行,构造一个三角形 triangle,边长是 3、4、5
  • 在第 3 行,构造一个列表 shapes,将 square 和 triangle 加入到列表 shapes 中
  • 在第 4 行,遍历列表 shapes
  • 在第 5 行,调用 shape 的 get_circle 方法,获取形体的周长

程序运行输出如下:

40
12

4. 小结

比较经典的面向过程编程语言有 C。面向对象编程是编程语言技术迈出的一大步,面向对象的出现让我们的代码能够更好的描述我们的世界。现在的主流编程语言已经纷纷开始支持面向对象,所以掌握面向对象编程是成为一个好的程序员的基本。