导入类

当您向类中添加更多功能时,您的文件可能会变长,即使您正确使用继承和组合也是如此。 为了与 Python 的整体理念保持一致,您需要尽可能保持文件整洁。 为了提供帮助,Python 允许您将类存储在模块中,然后将您需要的类导入到您的主程序中。

导入单个类

让我们创建一个只包含 Car 类的模块。 这带来了一个微妙的命名问题:我们在本章中已经有了一个名为 car.py 的文件,但是这个模块应该命名为 car.py,因为它包含代表汽车的代码。 我们将通过将 Car 类存储在名为 car.py 的模块中来解决此命名问题,替换我们之前使用的 car.py 文件。 从现在开始,任何使用此模块的程序都需要更具体的文件名,例如 my_car.py。 这里的 car.py 只包含 Car 类的代码:

"""A class that can be used to represent a car."""

class Car: (1)
    """A simple attempt to represent a car."""

    def __init__(self, make, model, year):
        """Initialize attributes to describe a car."""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def get_descriptive_name(self):
        """Return a neatly formatted descriptive name."""
        long_name = f"{self.year} {self.make} {self.model}"
        return long_name.title()

    def read_odometer(self):
        """Print a statement showing the car's mileage."""
        print(f"This car has {self.odometer_reading} miles on it.")

    def update_odometer(self, mileage):
        """
        Set the odometer reading to the given value.
        Reject the change if it attempts to roll the odometer back.
        """
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer!")

    def increment_odometer(self, miles):
        """Add the given amount to the odometer reading."""
        self.odometer_reading += miles
1 我们包括一个模块级文档字符串,它简要描述了这个模块的内容。 您应该为您创建的每个模块编写一个文档字符串。

现在我们创建一个名为 my_car.py 的单独文件。 该文件将导入 Car 类,然后从该类创建一个实例:

from car import Car (1)


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

my_new_car.odometer_reading = 23
my_new_car.read_odometer()
1 import 语句告诉 Python 打开汽车模块并导入类 Car。 现在我们可以使用 Car 类,就好像它是在此文件中定义的一样。 输出与我们之前看到的相同:
2024 Audi A4
This car has 23 miles on it.

导入类是一种有效的编程方式。 想象一下如果包含整个 Car 类,这个程序文件会有多长。 当您将类移动到一个模块并导入该模块时,您仍然可以获得所有相同的功能,但您可以保持主程序文件干净且易于阅读。 您还将大部分逻辑存储在单独的文件中; 一旦您的类按您希望的方式工作,您就可以不理会这些文件,而专注于主程序的更高级别的逻辑。

在一个模块中存储多个类

您可以在单个模块中存储任意数量的类,尽管模块中的每个类都应该以某种方式相关。 Battery 和 ElectricCar 类都有助于表示汽车,因此让我们将它们添加到模块 car.py 中。

car.py
"""A set of classes used to represent gas and electric cars."""

class Car:
    """A simple attempt to represent a car."""

    def __init__(self, make, model, year):
        """Initialize attributes to describe a car."""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def get_descriptive_name(self):
        """Return a neatly formatted descriptive name."""
        long_name = f"{self.year} {self.make} {self.model}"
        return long_name.title()

    def read_odometer(self):
        """Print a statement showing the car's mileage."""
        print(f"This car has {self.odometer_reading} miles on it.")

    def update_odometer(self, mileage):
        """
        Set the odometer reading to the given value.
        Reject the change if it attempts to roll the odometer back.
        """
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer!")

    def increment_odometer(self, miles):
        """Add the given amount to the odometer reading."""
        self.odometer_reading += miles


class Battery:
    """A simple attempt to model a battery for an electric car."""

    def __init__(self, battery_size=40):
        """Initialize the battery's attributes."""
        self.battery_size = battery_size

    def describe_battery(self):
        """Print a statement describing the battery size."""
        print(f"This car has a {self.battery_size}-kWh battery.")

    def get_range(self):
        """Print a statement about the range this battery provides."""
        if self.battery_size == 40:
            range = 150
        elif self.battery_size == 65:
            range = 225

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

class ElectricCar(Car):
    """Models aspects of a car, specific to electric vehicles."""

    def __init__(self, make, model, year):
        """
        Initialize attributes of the parent class.
        Then initialize attributes specific to an electric car.
        """
        super().__init__(make, model, year)
        self.battery = Battery()

现在我们可以创建一个名为 my_electric_car.py 的新文件,导入 ElectricCar 类,然后制作一辆电动汽车:

my_electric_car.py
from car import ElectricCar


my_leaf = ElectricCar('nissan', 'leaf', 2024)
print(my_leaf.get_descriptive_name())
my_leaf.battery.describe_battery()
my_leaf.battery.get_range()

这与我们之前看到的输出相同,尽管大部分逻辑都隐藏在模块中:

2024 Nissan Leaf
This car has a 40-kWh battery.
This car can go about 150 miles on a full charge.

从一个模块中导入多个类

您可以将所需数量的类导入到程序文件中。 如果我们想在同一个文件中制作一辆普通汽车和一辆电动汽车,我们需要导入两个类,Car 和 ElectricCar:

from car import Car, ElectricCar (1)


my_mustang = Car('ford', 'mustang', 2024) (2)
print(my_mustang.get_descriptive_name())

my_leaf = ElectricCar('nissan', 'leaf', 2024) (3)
print(my_leaf.get_descriptive_name())
1 通过用逗号分隔每个类,您可以从一个模块中导入多个类。 一旦你导入了必要的类,你就可以根据需要自由地为每个类创建任意数量的实例。
2 在这个例子中,我们制作了一辆汽油动力的福特野马,
3 然后是一辆电动的日产聆风。
2024 Ford Mustang
2024 Nissan Leaf

导入整个模块

您还可以导入整个模块,然后使用点表示法访问您需要的类。 这种方法很简单,并且生成的代码易于阅读。 因为创建类实例的每个调用都包含模块名称,所以您不会与当前文件中使用的任何名称发生命名冲突。

这是导入整个汽车模块然后创建普通汽车和电动汽车的样子:

my_cars.py
import car (1)


my_mustang = car.Car('ford', 'mustang', 2024) (2)
print(my_mustang.get_descriptive_name())

my_leaf = car.ElectricCar('nissan', 'leaf', 2024) (3)
print(my_leaf.get_descriptive_name())
1 首先我们导入整个汽车模块。 然后我们通过 module_name.ClassName 语法访问我们需要的类。
2 我们再次创建福特野马和
3 日产聆风。

导入模块中的所有类

您可以使用以下语法从模块中导入每个类:

from module_name import *

不推荐使用此方法有两个原因。 首先,能够阅读文件顶部的导入语句并清楚地了解程序使用了哪些类是很有帮助的。 使用这种方法,不清楚您正在使用模块中的哪些类。 这种方法也可能导致与文件中的名称混淆。 如果您不小心导入了一个与您的程序文件中的其他东西同名的类,您可能会产生难以诊断的错误。 我在这里展示它是因为尽管它不是推荐的方法,但您有时可能会在其他人的代码中看到它。

如果您需要从一个模块中导入许多类,最好导入整个模块并使用 module_name.ClassName 语法。 您不会在文件顶部看到所有使用的类,但您会清楚地看到模块在程序中的使用位置。 您还将避免在导入模块中的每个类时可能出现的潜在命名冲突。

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

有时您会希望将您的类分散到多个模块中,以防止任何一个文件变得太大,并避免在同一个模块中存储不相关的类。 当您将类存储在多个模块中时,您可能会发现一个模块中的类依赖于另一个模块中的类。 发生这种情况时,您可以将所需的类导入到第一个模块中。

例如,让我们将 Car 类存储在一个模块中,将 ElectricCar 和 Battery 类存储在一个单独的模块中。 我们将创建一个名为 electric_car.py 的新模块——替换我们之前创建的 electric_car.py 文件——并将 Battery 和 ElectricCar 类复制到该文件中:

"""A set of classes that can be used to represent electric cars."""

from car import Car


class Battery:
    """A simple attempt to model a battery for an electric car."""

    def __init__(self, battery_size=40):
        """Initialize the battery's attributes."""
        self.battery_size = battery_size

    def describe_battery(self):
        """Print a statement describing the battery size."""
        print(f"This car has a {self.battery_size}-kWh battery.")

    def get_range(self):
        """Print a statement about the range this battery provides."""
        if self.battery_size == 40:
            range = 150
        elif self.battery_size == 65:
            range = 225

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

class ElectricCar(Car):
    """Models aspects of a car, specific to electric vehicles."""

    def __init__(self, make, model, year):
        """
        Initialize attributes of the parent class.
        Then initialize attributes specific to an electric car.
        """
        super().__init__(make, model, year)
        self.battery = Battery()

类 ElectricCar 需要访问其父类 Car,因此我们将 Car 直接导入到模块中。 如果我们忘记这一行,当我们尝试导入 electric_car 模块时,Python 会报错。 我们还需要更新 Car 模块,使其只包含 Car 类:

"""A class that can be used to represent a car."""

class Car:
    """A simple attempt to represent a car."""

    def __init__(self, make, model, year):
        """Initialize attributes to describe a car."""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def get_descriptive_name(self):
        """Return a neatly formatted descriptive name."""
        long_name = f"{self.year} {self.make} {self.model}"
        return long_name.title()

    def read_odometer(self):
        """Print a statement showing the car's mileage."""
        print(f"This car has {self.odometer_reading} miles on it.")

    def update_odometer(self, mileage):
        """
        Set the odometer reading to the given value.
        Reject the change if it attempts to roll the odometer back.
        """
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer!")

    def increment_odometer(self, miles):
        """Add the given amount to the odometer reading."""
        self.odometer_reading += miles

现在我们可以分别从每个模块导入并创建我们需要的任何类型的汽车:

from car import Car
from electric_car import ElectricCar


my_mustang = Car('ford', 'mustang', 2024)
print(my_mustang.get_descriptive_name())

my_leaf = ElectricCar('nissan', 'leaf', 2024)
print(my_leaf.get_descriptive_name())

我们从其模块中导入 Car,并从其模块中导入 ElectricCar。 然后我们制造一辆普通汽车和一辆电动汽车。 两辆车都正确创建:

2024 Ford Mustang
2024 Nissan Leaf

使用别名

正如您在第 8 章中看到的,在使用模块组织项目代码时,别名非常有用。 您也可以在导入类时使用别名。

例如,考虑一个你想制造一堆电动汽车的程序。 一遍又一遍地输入(和阅读)ElectricCar 可能会很乏味。 你可以在导入语句中给 ElectricCar 一个别名:

from electric_car import ElectricCar as EC

现在你可以在任何时候想要制造电动汽车时使用这个别名:

my_leaf = EC('nissan', 'leaf', 2024)

您还可以为模块指定别名。 以下是使用别名导入整个 electric_car 模块的方法:

import electric_car as ec

现在您可以将此模块别名与完整的类名一起使用:

my_leaf = ec.ElectricCar('nissan', 'leaf', 2024)

找到合适的工作流程

如您所见,Python 为您提供了多种选择,用于在大型项目中构建代码结构。 了解所有这些可能性很重要,这样您才能确定组织项目以及了解其他人项目的最佳方式。

刚开始时,请保持代码结构简单。 尝试在一个文件中完成所有工作,并在一切正常后将您的类移动到单独的模块中。 如果您喜欢模块和文件的交互方式,请尝试在启动项目时将类存储在模块中。 找到一种可以让您编写有效代码的方法,然后从那里开始。

自己试试
9-10.Imported Restaurant

使用最新的餐厅类,将其存储在模块中。 制作一个导入餐厅的单独文件。 创建一个 Restaurant 实例,并调用 Restaurant 的方法之一以显示导入语句正常工作。

9-11.Imported Admin

从练习 9-8(第 173 页)开始。 将类 User、Privileges 和 Admin 存储在一个模块中。 创建一个单独的文件,创建一个 Admin 实例,然后调用 show_privileges() 以显示一切正常。

9-12.Multiple Modules

将 User 类存储在一个模块中,将 Privileges 和 Admin 类存储在一个单独的模块中。 在一个单独的文件中,创建一个 Admin 实例并调用 show_privileges() 以显示一切仍在正常工作。