Python 面向对象初步
前言
什么是类?
类(Class):可以片面的理解为一类相似事物的抽象。
以圆为例,圆具有半径这样一个相似的属性,并且都可以执行面积、周长等运算。
为什么用类?
为了是代码更加抽象,封装。事实上,我们以前写的每个 python 代码里都创建了各种各样的类。
一些例子比如 DataFrame
类。
你可能会用 read_csv
创建一个 DataFrame
类,可能也会用下标运算符,来获取它的某一列,也可能会用它的 drop_duplicates
方法。事实上,你并不关心 DataFrame
是怎么把数据排在一起的,你也不关心为什么 drop_duplicates
就能把所有重复的去除,但是,通过这一个抽象的类,你可以方便的调用它的功能。
DataFrame
类,将整个表的数据,仅仅用一个变量,就全部包含起来,就像是封装在了一个小小的纸板箱中📦。
需要注意的是,pandas 库并不是魔法,而是正常的 python 语法。一切 pandas 库的功能都是我们也可以实现出一模一样的。
比如说,为什么 df[(df['a']>5) & (df['b']<8)]
中用的是 &
,而不是 and
,更不是 &&
呢?这是因为,python 只能重载按位与运算符(&),而不能重载逻辑与运算符(and)。(这句话可以看不懂,其中有一些概念:重载,按位与,逻辑与)
类的使用
定义
对比函数,类的定义是 class
关键字加上类名组成的一个代码块。
1 |
|
pass
语句可以使代码块留空,以后再写。(比如 if
, else
, for
, def
的冒号后面内容,不写会报错,用 pass
占位)
一般来说,我们习惯上类名使用大驼峰命名法,变量名及函数名使用小驼峰命名法。
小驼峰命名法:第一个单词首字母小写,后面其他单词首字母大写。
大驼峰命名法:每个单词的第一个字母都要大写。
创建实例
使用 类名(参数*可选*)
可以创建一个实例(Instance)。和函数差不多。
你已经使用过这个语法了。
1 |
|
这段代码就构建了一个 BeautifulSoup
类实例。
实例和类的关系:还是拿圆来举例子,类相当于所有圆,而实例可能是一个半径为 1,半径为 5,半径为 1.4 的圆。
这说明,一个类可以有多个实例。(很好理解,就拿 DataFrame
类来说,你可以有不同的数据表格,就相当于不同的实例)
1 |
|
这样创建一个 Circle
类的实例,并将其附在变量 c1
上。
类属性与实例属性
在 python 中,属性 (Attribute) 可以理解为类/实例的一些数值。(比如说,圆的半径)
属性通过 实例.属性名
获取,还可以进行修改。例如:
1 |
|
类属性
类属性在每个实例中值都相同。比如说在圆的例子里,可能是这个图形的名字(“Circle”)。
直接在类中使用 变量名=值
创建一个类属性。类属性用的机会并不多,因此你可以不太关注它。在实际过程中,大部分的属性都是实例属性。
1 |
|
实例属性
实例属性的值随着实例的变化而变化。比如说圆的半径(显然,每个圆的半径都不同)。
实例属性通常情况下会在 __init__
方法中初始化。(下一章节介绍)
1 |
|
方法
方法也放在类中定义,和函数一样,使用 def
关键字。
魔术方法
魔术方法 (Magic Method):一些以两个下划线为开头和结尾,固定名字的 python 特殊方法(用于执行特定任务)。
在所有魔术方法中,你最需要知道的一定是 __init__()
方法。
当创建实例时,__init__()
方法被自动调用为创建的实例增加实例属性。__init__()
方法第一个参数是 self
(这句话是不准确的,但是这么理解是 100% OK的),后面的参数为你要的实例属性。在进行实例化的时候传入后面的参数(第一个参数不用传)。使用 self.变量名=值
来定义一个实例对象。
正如魔术方法所有名字加了两个下划线告诉你的,在正常情况下,你绝不应该手动调用任何一个魔术方法。python 会在合适的时机自动帮你调用。(比如 __init__()
在实例化时被自动调用)
方法
方法 (Method):在类中定义的函数被称为方法(在某些语言中,我们还习惯称其为成员函数)。
方法还可以用来执行某些通用的操作(对圆来说,求周长、面积。对 DataFrame
来说,drop_duplicates
、drop_na
)。
普通的方法也要有第一个参数 self
。可以把这个 self
理解为当前这个类的实例。
1 |
|
调用方法通过 对象名.方法名(参数*可选*)
来实现。
1 |
|
你见过这种表示方法:
方法名 | 使用方法 |
---|---|
socket.send(bytes) |
发送二进制数据给套接字。本套接字必须已连接到远程套接字。 |
socket.recv(bufsize) |
从套接字接收数据。返回值是一个字节对象,表示接收到的数据。bufsize 指定一次接收的最大数据量。 |
这里的 socket.
指的是这个类,在实际调用时要变成你的实例。
即调用方法使用 实例名.方法名
,而不是 类名.方法名
。
同理 DataFrame.drop_duplicates()
。
方法一定要有第一个 self
参数。这是由 python 底层对类方法的实现导致的。
如果你执行 object.method(args)
,其实等价于 MyClass.method(object, args)
。
具体来说,c1.area()
等价于 Circle.area(c1)
。
因此,方法的第一个参数即为用来接收实例对象。
实例
灵活运用上面的知识,我们写出了一个向量的类。
1 |
|
任务:
要求在源代码上进行修改,增加一个 Vector.rotate(x)
方法,将向量以原点为中心,逆时针旋转 x 弧度(就地修改)。
示例执行:
1 |
|
点 绕原点逆时针旋转 后得到点的坐标为:
可以使用复数的三角形式自行推导。
鸭子类型
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
简单来说,一个接口,多种实现。
比如说 sum
函数。它可以计算一个列表、元组、甚至是 pandas.Series
的和。
也就是说,只要我们自己的类实现了相关接口,也可以被 sum
函数计算和。具体来说,我们要让我们的对象是一个 Iterable
的对象。
一个 Iterable
对象可以干很多事:被 for 循环迭代,在其他的一些接口里都可以调用。换句话说,如果一个对象迭代起来像列表,那么它就是一个列表。
1 |
|