Python 面向对象编程
面向对象和面向过程这两种完全不同的编程思维,通常会对初学者产生极大的心理阴影。但相信我,趟过去,海阔天空。
过程思维和对象思维
我们不妨讲一个故事。两位工程师分别承包了一对双子塔的两座摩天大楼的建筑工程。
建筑师小明是一个实干家,拿到图纸之后,立马就拉起了一支专业素质过硬、人数众多的施工队,从地基就开始干。小明打算一层一层往上盖,直到楼顶。小明认为,只要自己这支队伍勤奋肯干,一定能比负责另一座大厦的小刚更快完成任务,拿到更多钱。
几个月后,小明傻眼了。小刚那一队的进度居然比他还要快不少!望着比自己这座高出越来越多的另外一座大厦,小明坐不住了,他去问小刚到底怎么做到的。
小刚为人倒也实诚,很快把自己的方案同小明讲了。原来小刚拿到图纸之后,并没有像小明一样很快地就开始施工,而是先将整座大厦分为了几大系统,比如排水、消防、通风等等。然后找到了几支小型的施工队,让他们分别承包这几大系统的建造工作。这样,一个施工队只需要负责一个系统的工作,很快就能完成。而且,这样的好处是,如果某个系统出现了问题,只需要找到负责这个系统的施工队,就能很快解决问题,而不会影响到其他系统的建造。
其实,这两位工程师就是典型的面向过程和面向对象的代表。小明是典型的面向过程思维,建造一座大厦,从地基开始慢慢往上,盖完这一层,再去盖上面一层。一步一步,按照步骤来做。而小刚则是典型的面向对象思维,将整个大厦分为几个系统,每个系统都是一个对象,有自己的属性和方法,互不干扰。
我们之前所学的 Python 的部分,都是面向过程的。代码顺序执行,从上到下,尽管有条件语句、循环语句等,但也无法改变事实。面向过程编程的好处是,简单直接,容易理解。但是,当程序变得复杂,代码量变得庞大时,面向过程的代码就会变得难以维护,因为所有的代码都是线性的,互相之间的关系复杂,很难一下子看出来。想象一下,如果我们的代码有几万行,那么我们要找到某个变量、某个函数的调用关系,就会变得非常困难。
为此,面向对象的编程思维应运而生。面向对象,在中国台湾地区也称为物件导向,是一种程序设计范型,同时也是一种程序开发的方法。对象指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。
类和对象
什么是类?什么是对象?类是一种抽象的概念,是对一类事物的描述,比如人类、狗类、汽车类等等。而对象则是类的实例,是具体的一个个体,比如小明、小红、小狗、小汽车等等。
类是由属性和方法组成的。属性是类的特征,比如人类有姓名、年龄、性别等属性;方法是类的行为,比如人类可以吃饭、睡觉、工作等方法。在初学阶段,我们可以简单地理解为,属性就是变量,方法就是函数。而把属性和方法封装在一起,就形成了一个类。
在 Python 中,类的定义非常简单,使用关键字 class
即可。比如,我们定义一个 Person
类:
1 | class Person: |
在这个类中,我们定义了一个 __init__
方法,这是一个特殊的方法,用来初始化对象的属性。self
是一个特殊的参数,表示对象本身。在 __init__
方法中,我们定义了两个属性 name
和 age
,并将传入的参数赋值给这两个属性。然后,我们定义了一个 say_hello
方法,用来打印对象的名字和年龄。
然后我们就可以使用这个类来创建对象了:
1 | p1 = Person('小明', 18) |
这样,我们就创建了两个 Person
类的对象 p1
和 p2
。p1
和 p2
都是从属于 Person
类的对象,拥有 name
和 age
两个属性,以及 say_hello
方法。
想想也确实是这样,我们每个人都是人,都有名字和年龄,也都会 say hello。但是,每个人的名字和年龄是不同的,say hello 的时候也会说出自己的名字和年龄。所以共同点提取出来,形成一个类,这个类又有很多个体,每个个体都是这个类的对象。
其实,类和对象的关系,就好比是模具和产品的关系。模具是类,产品是对象。模具可以生产出很多产品,每个产品都是模具的一个实例。(所以我们人类个体说不定是哪个高等文明在流水线上用模具生产出来的)
我们试着获取一下小明和小红两个人的名字、年龄,并让他们分别 say hello。
1 | print(f"{p1.name}'s age is {p1.age}.") |
使用.
来表示属性和方法的从属关系。p1.name
即“p1 这个对象的name”。
如果你不想让别人看到你的年龄,可以将 age
属性定义为私有属性,即在属性名前加上两个下划线 __
:
1 | class Person: |
这样,__age
就变成了私有属性,外部无法直接访问。比如,下面的代码会报错:
1 | print(p1.__age) # 报错 AttributeError: 'Person' object has no attribute '__age' |
私有属性的意思是,只有类的内部才能访问,外部无法访问。也就是只有class
内部才可以访问到。所以聪明的你是否想到了破局之法?对,就是在class
内部定义一个方法作为中转,我们就可以获取私有属性了:
1 | class Person: |
这样,我们就可以通过get_age
方法来获取私有属性__age
了:
1 | print(p1.get_age()) # 18 |
所以为什么要这么大费周章呢?因为我们要保护数据的安全性。比如,我们不希望别人随便修改我们的年龄,那么我们就可以将年龄定义为私有属性,只能通过类内部的方法来获取。
静态方法
类的对象之间互不干扰。这也很好理解。虽然小明和小红都属于人类,但是他们是两个不同的个体,拥有各自的名字、年龄,也有各自 say hello 的方式,互不干扰。我们来看:
1 | p1 = Person('小明', 18) |
可以看到,虽然我们修改了 p1
的年龄,但是 p2
的年龄并没有受到影响。这就是类的对象之间互不干扰。
但是,有时候我们需要在类的内部定义一些方法,这些方法不需要访问对象的属性,也不需要访问类的属性,只是一个独立的方法。这时,我们可以使用静态方法。静态方法使用 @staticmethod
装饰器来定义:
1 | class Person: |
这样,我们就定义了一个静态方法 say_hi
。静态方法不需要传入 self
参数,也不需要访问对象的属性,只需要使用 @staticmethod
装饰器即可。我们可以通过类名来调用静态方法:
1 | Person.say_hi() # Hi, I am a person. |
这种模式适合于什么场景呢?特别适合于工具类的封装。什么是工具类?比如,我们有一个数学工具类,里面有一些数学计算的方法,这些方法不需要访问对象的属性,也不需要访问类的属性,只是一些独立的方法。这时,我们就可以使用静态方法来定义这些方法。工具类是为了方便将一些函数分类书写,方便调用而产生的。
继承
如果你有学过 Java,对于面向对象三大特性,封装、继承和多态应该是已经刻入骨髓的。什么叫继承?顾名思义,孩子继承父母。
我们来想一下就能明白,比如,人类是一个类,有姓名、年龄等属性,也有吃饭、睡觉等方法。但是人类也可以再细分。比如,学生类,老师类,工人类等等。学生类继承自人类,老师类继承自人类,工人类继承自人类。他们都有年龄、姓名等属性,也都有吃饭、睡觉等方法。但是,学生类有独特的学习、考试等方法,老师类有独特的教书、批改作业等方法,工人类有独特的工作、加班等方法(bushi)。此时,我们就可以说,学生类、老师类、工人类继承自人类。
我们马上来实现一个继承。
1 | class Student(Person): |
这样,我们就定义了一个 Student
类,继承自 Person
类。Student
类有一个额外的属性 grade
,以及一个额外的方法 study
。在 __init__
方法中,我们使用 super().__init__(name, age)
来调用父类的 __init__
方法,以初始化 name
和 age
属性。
我们来创建一个 Student
类的对象:
1 | s1 = Student('小明', 18, 3) |
可以看到,Student
类继承了 Person
类的属性和方法,同时还有自己的属性和方法。这就是继承的作用。
方法重写
儿子不满意老子是很正常的事情。有时候,子类可能不满意父类的方法,想要重新实现从父类那里继承过来的方法。这时,我们可以使用方法重写。
方法重写极为简单,只需在子类中定义一个与父类完全相同的方法即可。
1 | class Student(Person): |
这样,当我们调用子类的say_hello()
方法时,就会调用重写后的方法了。
1 | s1 = Student('小明', 18, 3) |
Python 虚拟环境和 pip
pip 是 Python 官方的模组管理工具,也是陈叔推荐的。
关于什么是模组,我已经在 Python 的第二节课中讲过了。其实,除了我们自己写的模组,还有许许多多的人为 Python 开发出自己的模组。这些模组各有各的作用,为 Python 生态的构筑做出了不可磨灭的贡献。
打开这个网址:PyPI,你就可以在这里面找到所有需要的模组。
而 pip 就是管理着这些模组的下载、更新、移除、发布的工具。
在了解 pip 之前,我们先要来理解 Python 虚拟环境这个概念。
当我们安装好 Python 解释器之后,Python 解释器安装程序就会在我们的电脑上创建了一份根环境。其中包含了基本的 Python 解释器程序环境。这个根环境,我们有时也称为全局环境。
那么什么是虚拟环境?当我们新建一个 Python 工程的时候,我们可以选择从根环境中拷贝一份一模一样的解释器环境到我们的工程目录下。这个环境拷贝自根环境,与根环境完全隔离,仅对当前工程适用,被称为“虚拟环境”。
于是虚拟环境的优点就显现出来了。虚拟环境与根环境完全隔离,在虚拟环境中发生的一切事情都不会影响到电脑上的 Python 根环境。这样就保护了根环境的纯净性。
如果你使用 PyCharm 创建 Python 工程,PyCharm 会有创建虚拟环境的选项。
了解完虚拟环境,我们开始使用 pip 来安装 PyPI 上的套件。我们尝试安装一个 demo 套件。在 PyCharm 中打开终端机:
你会发现命令行提示符的前面有一个(.venv)
,证明你已经进入了该项目的虚拟环境。
然后在命令提示符后输入pip install hello-world-20200509
回车,pip 会自动安装该套件。
然后我们在main.py
中写下以下代码:
1 | import hello_world as hello |
控制台会输出一句hello
。
恭喜你,你成功安装了 demo 包,并运行了其中的hello()
函数。
这个 demo 包是安装在本工程的虚拟环境中的,与根环境隔离,也不适用于其他工程。