🎉🎉现在可以在这里下载notebook文档啦,便看便运行!
1. 前言
Python语言的特点不必再多说,在学习过程中,很重要的一部分内容就是Python的函数和类的编写及调用。
函数其实就是一个计算过程,对于合法的输入经过计算返回输出,对于非法的输入抛出相应的错误。
类是对具有相似特性的实例的抽象,以集中管理此类对象的表现和特性。
2. 类的直观理解
2.1 从一个问题入手
假设我们需要用Python定义“张三”这个人,需要包括他的姓名、性别和年龄。
有许多Python的内置数据类型可以实现,比如说用字典:
one_person = {
"name": "张三",
"gender": "男",
"age": "18"
}
这样,one_person
这个变量就包含了张三的基本信息,只需要按照字典的访问方式,就可以获取相关属性的值:
print(f"Age of {one_person['name']} is: {one_person['age']}")
# Output:Age of 张三 is: 18
Note:
如果读者不熟悉f-strings
,那么可以在这里了解更多。
类似地,如果我们想继续定义“李四”这个人,可以有如下写法:
another_person = {
"name": "李四",
"gender": "女",
"age": "19"
}
用这种方法当然可以还可以定义其他的人,但每次都要把字典里面的属性名称,即name
、gender
和age
,都写一遍,可能会让人觉得烦躁。我们发现需要定义的人的属性种类是相同的,那么可不可以把它们抽象出来,用一种其他的方法来更加方便地定义一个个具体的人呢?
这种“其他的方法”,就是类。
2.2 定义及使用一个类
接上面的例子,我们需要定义“人”这个类,且要求它有三个属性:name
、gender
和age
。写法如下:
class Man:
def __init__(self, name, gender, age):
self.name = name
self.gender = gender
self.age = age
上述代码片的主要内容如下:
class
关键字声明(定义)了一个类;Man
是类的名字;- 在
Man
类中,通过def
关键字定义类一个“函数”:__init__
,该函数有四个参数:self
、name
、gender
和age
。
暂且不做更多的说明,先看如何使用这个类:
third_person = Man('王五', '男', '28')
forth_person = Man("周六", "男", "33")
然后打印一下相关的信息:
print(f"Age of {third_person.name} is: {third_person.age}")
# Output:Age of 王五 is: 28
2.3 解释
在用Man
类来构造变量third_person
和forth_person
时,我们发现这和函数的调用方法很相似,都是传入一些参数,获取一个返回值(当然,有些函数没有返回值)。实际上这个步骤叫做类的实例化,顾名思义,就是利用定义的Man
类来实例化许多不同的“人”。
在这个例子中,third_person
和forth_person
都是Man
类的实例。关于这个结论,可以用以下的方法验证:
print(type(third_person))
# Output:<class '__main__.Man'>
或者
print(isinstance(third_person, Man))
# Output:True
实际上,Python的基础数据类型都是一个个的类,而具体的数据(对象)就是对应类的实例,例如,可以用isinstance(1, int)
来验证1
是int
的实例,用isinstance(1.0, float)
来验证1.0
是float
的实例,用isinstance(one_person, dict)
来验证one_person
是dict
的实例等。
既然third_person
是Man
的一个实例,也就是一个具体的“人”,那么我们就应该有办法获取到这个人的姓名、性别和年龄属性。要实现这一点,可以利用obj.method
的方式实现:
# 获取姓名
print(third_person.name)
# Output:王五
# 获取年龄
print(third_person.age)
# Output:28
更准确的来说,name
和age
是实例的属性,而类的实例有哪些属性,就是在定义类的时候写明的。
至此,就到了那个看上去很奇怪的“函数”:__init__
登场了。
在本例中,Man
类的实例一共有三个属性,这刚好是__init__
“函数”的后三个参数。实际上,“init”就是“initialize”的缩写,其作用就是定义了类在实例化时所执行的操作。
Python规定了__init__
“函数”的第一个参数必须为self
,指代本身。通过self.name = name
,就将实例化时传入的name
绑定到了self
上,而通过obj.method
访问类的属性时,实际上访问的就是self
的属性。
Note:
- 实际上Python只是规定了
__init__
的第一个参数必须指代实例本身,而用self
是约定的习惯而已,将Man
中所有的self
替换成其他的字符也是可以的;不过为了方便阅读和沟通,建议使用self
。- 定义在类中的函数,在Python中被称为方法,这也是上文出现的“函数”一词加引号的原因。
- 以双下划线开头和结尾的方法,例如
__init__
,比较特殊,被称为魔术方法,它们往往要实现特定的作用,用户也可以通过重载这些方法来改变它们的表现。
3. 类的方法
接上面的例子,一个人不只是拥有静态的属性,还应该去参加必要的社会活动,例如:工作、运动、吃饭等等。与姓名、年龄等相比,这些活动具有一个显著的特点:它们是一个过程。
要实现这种过程,就需要在类中定义“方法”。
3.1 定义方法
前面已经提到,“方法”其实就是定义在类内的函数,而函数的作用就是执行一个过程,完成一项任务。
下面的代码定义了Man
类的三种方法:
class Man:
def __init__(self, name, gender, age):
self.name = name
self.gender = gender
self.age = age
def work(self):
print(f"{self.name} is working.")
def do_sports(self):
print(f"{self.name} is doing sports.")
def take_meal(self):
print(f"{self.name} is taking a meal.")
以work
为例,它是我们为类定义的一个方法,该方法只有一个参数self
,这意味着在该方法内部可以访问所有绑定到self
上的属性,例如,self.name
、self.gender
和self.age
。
one_person = Man('王五', '男', '28')
one_person.work()
# Output:王五 is working.
one_person.do_sports()
# Output:王五 is doing sports.
one_person.take_meal()
# Output:王五 is taking a meal.
调用某个方法,与调用属性的方式类似。由于方法是函数,因此,需要在后面加上()
,即通过obj.methods()
进行调用。
Note:
- 再次注意,
self
指代的是实例本身,通过实例调用方法时,不用给该参数传值,python会自动处理;- 事实上,如果通过类来调用方法,那么就需要传入第一个参数了:
# 一般不这样写,但确实没有语法错误 one_person = Man('王五', '男', '28') Man.take_meal(one_person)
3.2 方法内调用其他方法
既然self
指代类本身,那么通过self
应该也可以在方法内部调用其他方法。
我们对上面的代码稍作修改:
class Man:
def __init__(self, name, gender, age):
self.name = name
self.gender = gender
self.age = age
def do_sports(self):
self.take_meal()
print(f"After that, {self.name} is going to do sports.")
def take_meal(self):
print(f"{self.name} is taking a meal.")
one_person = Man('王五', '男', '28')
one_person.do_sports()
输出内容如下:
王五 is taking a meal.
After that, 王五 is going to do sports.
逻辑也是比较清晰的,在do_sports()
方法中,先调用了take_meal()
方法,然后再打印方法本身的内容。
3.3 静态方法
“静态”一词翻译自“static”。所谓静态方法,指的就是某个方法是固定不变的,这里说的“不变”,是指该方法不受其所在类的其他属性和方法的影响。
考虑这样一个场景:王五吃晚饭要去做运动,但健身房在顶楼,他到达健身房需要乘坐电梯。如果用类来表现这个过程,结合前面的知识,代码可以写成下面的样子:
class Man:
def __init__(self, name, gender, age):
self.name = name
self.gender = gender
self.age = age
def do_sports(self):
print(f"{self.name} is going to do sports.")
def take_meal(self):
print(f"{self.name} has taken a meal and steps into the elevator.")
def elevator_go_up(self):
print("Elevator is going up...")
# after some time...
print("Elevator is on the top floor.")
one_person = Man('王五', '男', '28')
one_person.take_meal()
one_person.elevator_go_up()
one_person.do_sports()
输出内容如下:
王五 has taken a meal and steps into the elevator.
Elevator is going up...
Elevator is on the top floor.
王五 is going to do sports.
理解起来没有任何问题,但如果注意观察,会发现elevator_go_up
这个方法和其他的方法不太一样:elevator_go_up
中没有利用到类本身的其他属性和方法。
可以这样理解,elevator_go_up
虽然定义在了Man
这个类内,但实际上该方法没有使用任何类的信息。也就是说,Man
这个类不会对elevator_go_up
这个方法产生任何影响,所以我们说elevator_go_up
方法是静态的。
尽管上述写法在语法上是没有问题的,但从功能上来说,elevator_go_up
方法完全可以放到类外,作为一个函数来供Man
中的方法调用。即改写成如下的形式:
class Man:
def __init__(self, name, gender, age):
self.name = name
self.gender = gender
self.age = age
def do_sports(self):
print(f"{self.name} is going to do sports.")
def take_meal(self):
print(f"{self.name} has taken a meal and step into the elevator.")
def elevator_go_up():
print("Elevator is going up...")
# after some time...
print("Elevator is on the top floor.")
Note:
如果这样定义,那么就不能再用one_person.elevator_go_up()
的方式调用这个函数了,因为该函数已经不再属于这个类了。
如果仍然想在类内定义该方法,那么对待此类静态的方法,需要加上一个修饰符:
class Man:
...
@staticmethod
def elevator_go_up():
print("Elevator is going up...")
# after some time...
print("Elevator is on the top floor.")
调用方法是不变的。
关于修饰符
修饰符不是这篇文章的重点,读者可以将其按照字面意思进行理解:通过修饰符修饰的方法就具备了某些其他的特性。
前面说过,静态的意思是不依赖于类本身的其他信息,这意味着,即使不经过实例化,也能通过类名称直接调用该方法:
Man.elevator_go_up()
4. 总结
本文简答介绍了python中类的最基本、最常用的使用方法。关于类还有其他许多复杂的特性,这将在后续的文章中进行介绍。