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

9.4.1 导入单个类

下面来创建一个只包含Car类的模块。这让我们面临一个微妙的命名问题:在本章中,已经有一个名为car.py的文件,但这个模块也应命名为car.py,因为它包含表示汽车的代码。我们将这样解决这个命名问题:将Car类存储在一个名为car.py的模块中,该模块将覆盖前面使用的文件car.py。从现在开始,使用该模块的程序都必须使用更具体的文件名,如my_car.py。下面是模块car.py,其中只包含Car类的代码:

  1. # car.py
  2. """这是一个汽车类"""
  3. class Car():
  4. def __init__(self, make, model, year):
  5. """初始化描述汽车的属性"""
  6. self.make = make
  7. self.model = model
  8. self.year = year
  9. self.odometer_reading = 0
  10. def get_descriptive_name(self):
  11. """返回整洁的描述性信息"""
  12. long_name = str(self.year) + ' ' + self.make + ' ' + self.model
  13. return long_name.title()
  14. def read_odometer(self):
  15. """打印一条指出汽车里程的消息"""
  16. print("这辆车一共开了" + str(self.odometer_reading) + "公里。")
  17. def update_odometer(self, mileage):
  18. """
  19. 将里程表读数设置为指定的值
  20. 禁止将里程表读数往回调
  21. """
  22. if mileage >= self.odometer_reading:
  23. self.odometer_reading = mileage
  24. else:
  25. print("你不能回调里程表!")
  26. def increment_odometer(self, miles):
  27. """将里程表读数增加指定的量"""
  28. self.odometer_reading += miles

在第二行处,我们包含了一个模块级文档字符串,对该模块的内容做了简要的描述。你应为自己创建的每个模块都编写文档字符串。

下面来创建另一个文件——my_car.py,在其中导入Car类并创建其实例:

# my_car.py
from car import Car

my_new_car = Car("吉利", "缤越", 2018)
print(my_new_car.get_descriptive_name()) 
my_new_car.odometer_reading = 23
my_new_car.read_odometer()

第二行处的import语句让Python打开模块car,并导入其中的Car类。这样我们就可以使用Car类了,就像它是在这个文件中定义的一样。输出与我们在前面看到的一样:


2018 吉利 缤越
这辆车一共开了23公里。


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

9.4.2 在一个模块中存储多个类

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

# car.py
"""用于表示汽车,电车和电池类"""

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 = str(self.year) + ' ' + self.make + ' ' + self.model 
        return long_name.title() 
    def read_odometer(self): 
        """打印一条指出汽车里程的消息""" 
        print("这辆车一共开了" + str(self.odometer_reading) + "公里。") 

    def update_odometer(self, mileage): 
        """ 
        将里程表读数设置为指定的值
        禁止将里程表读数往回调
        """
        if mileage >= self.odometer_reading: 
            self.odometer_reading = mileage 
        else: 
            print("你不能回调里程表!")

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

class Battery:
    def __init__(self, battery_size=70):
        self.battery_size = battery_size

    def describe_battery(self): 
        """打印一条描述电瓶容量的消息""" 
        print("这辆车还有" + str(self.battery_size) + "-kWh的电量剩余。")

    def get_range(self): 
        """打印一条消息,指出电瓶的续航里程""" 
        if self.battery_size == 70: 
            range = 240 
        elif self.battery_size == 85: 
            range = 270 
        message = f"这辆车在充满电的情况下可以开{str(range)}公里。"
        print(message)


class ElectricCar(Car): 
    """电动汽车的独特之处""" 
    def __init__(self, make, model, year): 
        """初始化父类的属性""" 
        super().__init__(make, model, year) 
        self.battery = Battery()

    def describe_battery(self): 
        """打印一条描述电瓶容量的消息""" 
        self.battery.describe_battery()

    def fill_gas_tank(self): 
        """电动汽车没有油箱""" 
        print("这是辆电车,没有油箱!")

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

# my_electric_car.py
from car import ElectricCar

my_xiaopeng = ElectricCar('小鹏', 'p7', 2020) 
print(my_xiaopeng.get_descriptive_name()) 
my_xiaopeng.battery.describe_battery() 
my_xiaopeng.battery.get_range()

输出与我们前面看到的相同,但大部分逻辑都隐藏在一个模块中:


2020 小鹏 P7
这辆车还有70-kWh的电量剩余。
这辆车在充满电的情况下可以开240公里。


9.4.3 从一个模块中导入多个类

可根据需要在程序文件中导入任意数量的类。如果我们要在同一个程序中创建普通汽车和电动汽车,就需要将Car和ElectricCar类都导入:

from car import Car, ElectricCar 

my_geely = Car("吉利", "缤越", 2018) 
print(my_geely.get_descriptive_name()) 
my_xiaopeng = ElectricCar('小鹏', 'p7', 2020)  
print(my_xiaopeng.get_descriptive_name())

在第一行处从一个模块中导入多个类时,用逗号分隔了各个类。导入必要的类后,就可根据需要创建每个类的任意数量的实例。

在这个示例中,我们在第三行处创建了一辆吉利缤越普通汽车,并在第五行处创建了一辆小鹏p7电动汽车:


2018 吉利 缤越
2020 小鹏 P7


9.4.4 导入整个模块

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

下面的代码导入整个car模块,并创建一辆普通汽车和一辆电动汽车:

import car

my_geely = car.Car("吉利", "缤越", 2018) 
print(my_geely.get_descriptive_name()) 
my_xiaopeng = car.ElectricCar('小鹏', 'p7', 2020)  
print(my_xiaopeng.get_descriptive_name())

在第一行处,我们导入了整个car模块。接下来,我们使用语法module_name.class_name访问需要的类。像前面一样,我们在第三行处创建了一辆吉利缤越汽车,并在第五行处创建了一辆小鹏p7汽车。

9.4.5 导入模块中的所有类

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

from module_name import *

不推荐使用这种导入方式,其原因有二。首先,如果只要看一下文件开头的import语句,就能清楚地知道程序使用了哪些类,将大有裨益;但这种导入方式没有明确地指出你使用了模块中的哪些类。这种导入方式还可能引发名称方面的困惑。如果你不小心导入了一个与程序文件中其他东西同名的类,将引发难以诊断的错误。这里之所以介绍这种导入方式,是因为虽然不推荐使用这种方式,但你可能会在别人编写的代码中见到它。

需要从一个模块中导入很多类时,最好导入整个模块,并使用module_name.class_name语法来访问类。这样做时,虽然文件开头并没有列出用到的所有类,但你清楚地知道在程序的哪些地方使用了导入的模块;你还避免了导入模块中的每个类可能引发的名称冲突。