python进阶-类

python 类的简单使用方法。

创建和使用类

创建Dog类

使用类几乎可以模拟任何东西。下面来编写一个表示小狗的简单类Dog,它表示的不是特定的小狗,而是任何小狗。对于大多数宠物狗,它们均有名字和年龄,会蹲下和打滚,因此我们的 Dog类需要具备这两项信息和两种行为。

创建Dog类的代码如下(dog.py),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Dog:
"""一次模拟小狗的简单尝试。"""

def __init__(self, name, age):
"""初始化属性name和age。"""
self.name = name
self.age = age

def sit(self):
"""模拟小狗收到命令时蹲下。"""
print(f"{self.name} is now sitting.")

def roll_over(self):
"""模拟小狗收到命令时打滚。"""
print(f"{self.name} rolled over!.")

这里我们定义了一个名为 Dog 的类。根据约定,在Python中,首字母大写的名称值的是类(建议使用驼峰命名法,而不使用下划线)。这个类的定义中没有园括号,因为要从空白创建这个类。

方法__init__()

类中的函数称为方法,这里__init__()是一个特殊方法,每当你根据Dog类创建新实例时,Python都会自动运行它,这个方法名称前后的2个下划线是一种约定,是为了避免与普通方法发生名称冲突。

我们将方法__init__()定义为包含3个形参:self、name 和 age 。这里,形参 self 必不可少,而且必须位于其它形参的前面。因为在创建实例时,将自动传入实参self,每个与实例相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。创建Dog实例时,Python将调用Dog类的方法__init__()。我们将通过实参向Dog()传递名字和年龄,self会自动传递,因此不用传递它。每当根据Dog类创建实例时,都只需给最后两个形参(name 和 age)提供值。

__init__()函数正文中定义的两个变量都有前缀self。以 self 为前缀的变量可供类中的所有方法使用,可以通过类的任何实例来访问。self.name = name 获取与形参 name 相关联的值,并将其赋给变量 name , 然后该变量被关联到当前创建的实例。 self.age = age 的作用类似。像这样可以通过实例访问的变量才能为属性

方法 sit() 和 roll_over()

这些方法执行时不需要额外的信息,因此它们只有一个形参self。我们随后创建的实例都能够访问这些方法。

根据类创建实例

下来来创建一个表示特定小狗的实例:

1
2
3
4
my_dog = Dog("Willie", 6)

print(f"My dog's name is {my_dog.name}.")
print(f"My dog is {my_dog.age} years old.")

这里我们创建一条名字为 “Willie” ,年龄为6 的小狗。遇到这行代码时,Python 使用实参 “Willie” 和 6 调用Dog类的方法 __init__() 。方法__init__()创建一个表示特定小狗的实例,并使用提供的值来设置属性 name 和 age 。接下来, Python 返回一个表示这个小狗的实例,而我们将这个实例赋给了变量 my_dog 。 在这里,通常我们将首字母大写的名称(如 Dog)指的是类,而小写的名称指的是根据类创建的实例(如 my_dog)。

访问属性

要访问实例的属性,使用句点表示法,举例如下

1
my_dog.name

调用方法

根据 Dog 类创建实例后,就能使用句点表示法来调用 Dog 类中定义的任何方法了,下面的代码让小狗蹲下和打滚。

1
2
3
my_dog = Dog("Willie", 6)
my_dog.sit()
my_dog.roll_over()

要调用方法,可指定实例的名称和要调用的方法,并用句点分隔。

你可以按照需求根据类创建任意数量的实例,不同实例之间是彼此独立的,有自己的一组属性。

使用类和实例

Car 类

下面来编写一个表示汽车的类,它存储了有关汽车的信息,还有一个汇总这些信息的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Car:
"""一次模拟汽车的简单尝试。"""

def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year

def get_descriptive_name(self):
"""返回整洁的描述性信息。"""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()


my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())

__init__()方法中除了 self ,还有三个形参,指定制造商、型号和生产年份。另外一个方法是返回汽车的描述性信息。

为了让这个类更有趣,下面给它添加一个随时间变化的属性,用于存储汽车的总里程。

给属性指定默认值

创建实例时,有些属性无须通过形参来定义,可在方法__init__()中为其指定默认值。

下面来添加一个名为 odometer_reading 的属性,其初始值为0。我们还添加了一个 read_odometer() 的方法,用于读取汽车的里程表。(问了 DS ,一般而言,值固定不变的变量放在 __init__()函数的输入参数中,值可能改变的参数则放在 __init__()函数的正文中,所有需要存储的属性均要在 __init__()函数中定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Car:
"""一次模拟汽车的简单尝试。"""

def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0

def get_descriptive_name(self):
"""返回整洁的描述性信息。"""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()

def read_odometer(self):
"""打印一条指出汽车里程的消息。"""
print(f"This car has {self.odometer_reading} miles on it.")


my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()

此时创建实例的方式和上面一样,但是Python将会自动创建一个odometer_reading 的属性,其值为 0 。

修改属性的值

我们能以三种方式修改属性的值:直接通过实例进行修改,通过方法进行设置,以及通过方法进行递增。下面依次进行介绍。

最直接的方式就是通过实例直接修改,下面的代码将里程表读数设置为 23

1
2
3
my_new_car.odometer_reading = 23
my_new_car.read_odometer()

也可以创建一个方法在内部进行更新,下面的代码我们添加了一个方法 update_odometer() ,其接受一个里程值,并将其赋值给 self.odometer_reading

1
2
3
4
5
6
7
8
9
10
class Car:
--snip--

def update_odometer(self, mileage):
"""将里程表读数设置为指定的值。"""
self.odometer_reading = mileage

my_new_car.update_odometer(23)
my_new_car.read_odometer()

可以对这个函数做一些修改,禁止任何人将里程表读数往回调。修改后的函数会检查指定的读数是否大于等于原来的里程,是则修改,不是则发出警告。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Car:
--snip--

def update_odometer(self, mileage):
"""
将里程表读数设置为指定的值。
禁止将里程表读数往回调。
"""
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")

有时候需要将属性值递增特定的量,而不是设置为全新的值。举例如下,下面新增方法 increment_odometer() 用于递增里程数。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Car:
--snip--

def increment_odometer(self, miles):
"""将里程表读数增加指定的量。"""
self.odometer_reading += miles

my_new_car.update_odometer(23_500)
my_new_car.read_odometer()

my_new_car.increment_odometer(100)
my_new_car.read_odometer()

继承

编写类时,并非总是从空白开始。如果要编写的类时另一个现成类的特殊版本,可使用继承。一个类继承另一个类时,将自动获得另一个类的所有属性和方法。原有的类称为父类,而新类称为子类。子类继承了父类的所有属性和方法,同时还可以定义自己的属性和方法。

子类的方法__init__()

在既有类的基础上编写新类时,通常要调用父类的方法 __init__() 。这将初始化在父类 __init__() 方法中定义的所有属性,从而让子类包含这些属性 。

例如,下面来模拟电动汽车,它属于汽车的子类,只需要为电动汽车特有的属性和行为编写代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Car:
"""一次模拟汽车的简单尝试。"""

def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0

def get_descriptive_name(self):
"""返回整洁的描述性信息。"""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()

def read_odometer(self):
"""打印一条指出汽车里程的消息。"""
print(f"This car has {self.odometer_reading} miles on it.")

def update_odometer(self, mileage):
"""
将里程表读数设置为指定的值。
禁止将里程表读数往回调。
"""
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")

def increment_odometer(self, miles):
"""将里程表读数增加指定的量。"""
self.odometer_reading += miles

class ElectricCar(Car):
"""电动车的特殊之处。"""

def __init__(self, make, model, year):
"""初始化父类的属性。"""
super().__init__(make, model, year)

my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())

首先时 Car 类的代码。创建子类时,父类必须包含在当前文件中,且位于子类前面。在定义子类时,必须在圆括号内指定父类的名称。方法 __init__() 中需要包含创建 Car 实例所需的属性,其中 super() 是一个特殊函数,让你能够调用父类的__init__() 方法,让 ElectricCar 实例包含这个方法中的所有属性。父类也称为超类 (superclass),名称 super 由次而来。

下面我们创建了一个电动汽车的实例,这里提供的信息与创建普通汽车时相同,下面我们开始定义电动汽车特有的属性和方法。

给子类定义属性和方法

子类可以添加新属性和新方法,这里我们添加了一个电动汽车特有的属性(电瓶),以及一个描述该属性的方法。这里新属性电瓶容量设置其初始值为 75 ,新方法用于打印有关电瓶的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Car:
--snip--

class ElectricCar(Car):
"""电动车的特殊之处。"""

def __init__(self, make, model, year):
"""
初始化父类的属性。
再初始化电动汽车特有的属性。
"""
super().__init__(make, model, year)
self.battery_size = 75

def describe_battery(self):
"""打印一条描述电瓶容量的消息。"""
print(f"This car has a {self. battery_size}-kWh battery.")

my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battery()

重写父类的方法

对于父类的方法,只要它不符合子类模拟的实物的方法,都可以进行重写。为此,可在子类中定义一个与要重写的父类方法同名的方法。这样,Python将不会考虑这个父类方法,而只关注你在子类中定义的相应方法。

假设 Car 类有一个名为 fill_gas_tank() 的方法,它对全电动汽车来说毫无意义,因此你可能想重写它。下面演示了一种重写方式:

1
2
3
4
5
6
7
class ElectricCar(Car):
--snip--

def fill_gas_tank(self):
"""电动汽车没有油箱。"""
print("This car doesn't need a gas tank!")

现在,如果有人对电动汽车调用 fill_gas_tank() 方法,Python 将忽略 Car 类中的 fill_gas_tank() 方法,转而运行上述代码。

将实例用作属性

使用代码模拟实物时,你可能会发现自己给类添加的细节越来越多:属性和方法清单和文件都越来越长。在这种情况下,可能需要将类的一部分提取出来,作为一个独立的类。可以将大型类拆分成多个协同工作的小类。

例如,不断给 ElectricCar 类添加细节时,我们可能发现其中包含很多专门针对汽车电瓶的属性和方法。在这种情况下,可将这些属性和方法提取出来,放到一个名为 Battery 的类中,并将一个 Battery 实例作为 ElectricCar 类的属性。

下面代码中我们创建了一个名为 Battery 的新类,它没有继承任何类,存在一个可选的形参 battery_size (默认为 75)。我们将 describe_battery() 方法也移动到了这个类中。

在 ElectricCar 类中,我们添加了一个名为 self.battery 的属性,将其赋值给 Battery() ,这会创建一个新的 Battery 实例(这里没有指定电池容量,因此采用默认值75)。

我们创建一辆电动汽车 my_tesla 。描述电瓶时,需要使用电动汽车的属性 battery ,并对存储在该属性中的 Battery 实例调用方法 describe_battery() ,因此完整语句为 my_tesla.battery.describe_battery()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Car:
--snip--

class Battery:
"""一次模拟电动汽车电瓶的简单尝试。"""

def __init__(self, battery_size=75):
"""初始化电瓶的属性。"""
self.battery_size = battery_size

def describe_battery(self):
"""打印一条描述电瓶容量的消息。"""
print(f"This car has a {self. battery_size}-kWh battery.")

class ElectricCar(Car):
"""电动车的特殊之处。"""

def __init__(self, make, model, year):
"""
初始化父类的属性。
再初始化电动汽车特有的属性。
"""
super().__init__(make, model, year)
self.battery = Battery()


my_tesla = ElectricCar('tesla', 'model s', 2019)

print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()

这看似做了很多额外的工作,但是现在想多详细地描述电瓶都可以,且不会导致 ElectricCar 类混乱不堪。

下面再给 Battery 类添加一个方法,它根据电瓶容量报告汽车的续航里程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Car:
--snip--

class Battery:
--snip--

def get_range(self):
"""打印一条消息,指出电瓶的续航里程。"""
if self.battery_size == 75:
range = 260
elif self.battery_size == 100:
range = 315

print(f"This car can go about {range} miles on a full charge.")

class ElectricCar(Car):
--snip--


my_tesla = ElectricCar('tesla', 'model s', 2019)

print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()

导入类

随着不断给类添加功能,文件可能变得很长,即便妥善地使用了继承亦如此。为遵循Python地总体理念,应让文件尽可能整洁。Python在这方面提供了帮助,允许将类存储在模块中,然后在主程序中导入所需的模块。

导入单个类

下面我们创建一个只包含 Car 类的模块,文件名为 car.py ,内容见上。

下面我们创建一个使用该模块的程序,命名为 my_car.py ,在其中导入 Car 类并创建其实例。

其中的 import 语句让 Python 打开模块 car 并导入其中的 Car 类,这样我们就可以使用 Car 类,就像它是在这个文件中定义的一样。

1
2
3
4
5
from car import Car

my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()

导入类是一种有效的编程方式。如果这个程序包含整个 Class 类,它该有多长啊!通过将这个类以到一个模块中并导入该模块,依然可以使用其所有功能,但主程序文件变得整洁而易于阅读了。这还让你能够将大部分逻辑存储在独立的文件中。确定类像你希望的那样工作后,就可以不管这些文件,而专注于主程序的高级逻辑了。

在一个模块中存储多个类

虽然同一个模块中的类之间存在某种相关性,但可根据需要在一个模块中存储任意数量的类。下面我们将 Battery 类和 ElectricCar 类都加入模块 car.py 中。

现在,可以新建一个名为my_electric_car.py的文件,导入 ElectricCar 类,并创建一辆电动汽车了

1
2
3
4
5
6
from car import ElectricCar 
my_tesla = ElectricCar('tesla', 'model s', 2019)

print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()

从一个模块中导入多个类

导入多个类形式如下,不同类之间用逗号分隔。

1
from car import Car, ElectricCar 

导入整个模块

我们还可以导入整个模块,再使用句点表示法访问需要的类。这种导入方式很简单,代码也易于阅读。因为创建类实例的代码都包含模块名称,所以不会与当前文件使用的任何名称发生冲突。

1
2
3
4
5
6
7
import car 

my_beetle = car.Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())

my_tesla = car.ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())

导入模块中的所有类

要导入模块中的每个类,可以使用下面的语法

1
from car import *

这里我们不推荐使用这种导入方法,原因有二。第一,如果只看文件开头的 import 语句,就能清楚地知道程序使用了哪些类,将大有裨益。然而这种导入方法没有明确地指出使用了模块中的哪些类。第二,这种方式还可能引发名称方面的迷惑。如果不小心导入了一个与程序文件中其它东西同名的类,将引发难以诊断的错误。

这里之所以介绍这种导入方式,是因为你可能在别人编写的代码中见到它。

在一个模块中导入另一个模块

有时候,需要将类分散到多个模块中,以免模块太大或在同一个模块中存储不相干的类。将类存储在多个模块中时,你可能会发现一个模块中的类依赖于另一个模块中的类。在这种情况下,可在前一个模块中导入必要的类(我感觉最好避免这种情况,不同模块做到彼此独立)。

这里我们将 Car 类存储在模块 car.py 中,将 Battery 类和 ElectricCar 类存储在另一个模块 electric_car.py 中,其内容如下

1
2
3
4
5
6
7
8
"""一组可用于表示电动汽车的类。"""
from car import Car

class Battery:
--snip--

class ElectricCar(Car):
--snip--

这里 ElectricCar 类需要访问其父类 Car ,因此通过 import 命令导入了 Car 类。

使用别名

通过 as 命令指定别名,例如我们将 ElectricCar 类指定为 EC,使其名称简化

1
from electric_car import ElectricCar as EC

参考文献

  1. Matthes E. Python 编程: 从入门到实践[M]. Ren min you dian chu ban she, 2020.
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2019-2025 Vincere Zhou
  • 访问人数: | 浏览次数:

请我喝杯茶吧~

支付宝
微信