第九章 类

本文详细介绍了Python中的类、继承机制,以及如何组织代码模块,包括创建类实例、属性操作、方法重写、模块导入和自定义工作流程。涵盖实例化、类的构造函数、电动汽车和电池类示例,以及Python标准库的使用。
摘要由CSDN通过智能技术生成

系列文章目录

第一章 起步
第二章 变量和简单数据类型
第三章 列表简介
第四章 操作列表
第五章 if 语句
第六章 字典
第七章 用户输入和 while 语句
第八章 函数
第九章 类
第十章 文章和异常
第十一章 测试代码



前言

面向对象编程是最有效的软件编写方法之一。在面向对象编程中,我们编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,我们定义了一大类对象都有的通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。

根据类来创建对象称为实例化,这让你能够使用类的实例。在本章中,我们将编写一些类并创建其实例。我们将是定可在实例中存储什么信息,定义可对这些实例执行那些操作。我们还将编写一些类来扩展既有类的功能,让相似的类能够高效地共享代码。我们将吧自己编写的类存储在模块中,并在自己的程序文件中导入其他程序员编写的代码。


9.1 创建和使用类

使用类几乎可以模拟任何东西。下面来编写一个表示小狗的简单类 Dog ,表示的时任何小狗。由于大多数小狗都具备名字和年龄这两项信息以及蹲下和打滚这两种行为,所以我们的 Dog 类将包含它们。

9.1.1 创建 Dog 类

根据 Dog 类创建的每个实例都将存储名字和年龄,我们赋予了每条小狗蹲下( sit() )和打滚( roll_over() )的能力:

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 都会自动运行它,而这种方法的名称中,开头和末尾各有两个下划线(这是一种约定,旨在避免 Python 默认方法与普通方法发生冲突)。

以下部分好像很难懂啊,多看几遍吧:

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

以 self 为前缀的变量可供类中所有方法使用,可以通过类的任何实例来访问。self.name 获取与形参 name 相关联的值,并将其值赋给变量,然后该变量再被关联到前面创建的实例。像这样可通过实例访问的变量称为属性。

Dog 类还定义了另外两种方法:sit() 和 roll_over() 。这些方法执行时不需要额外的信息,因此他们只有一个形参 self 。

这样讲应该更清晰一些吧

9.1.2 根据类创建实例

可将类视为有关如何创建实例的说明。Dog 类时一系列说明,让 Python 知道如何创建表示特定小狗的实例。

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

class Dog:
    --snip--

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.")

在这里使用的是前一个实例中编写的 Dog 类。在 my_dog = Dog('Willie', 6) 处,让 Python 创建一条名字为“ Willie ”、年龄为 6 岁的小狗。遇到,这行代码时,Python 使用实参 ‘Willie’ 和 6 调用 Dog 类的方法 __ init__ () 。方法 init() 创建一个表示特定小狗的实例,并使用提供的值来设置属性 name 和 age 。接下来,Python 返回一个表示这条小狗的实例,而我们将这个实例赋给变量 my_dog 。在这里,命名约定很有用:通常可认为首字母大写的名称(如 Dog)指的是类,而小写的名称(如 my_dog )指的是根据类创建的实例。

现在大概知道这章讲的“ 类 ”大概是个什么东东了吧。来吧,继续咯!

① 访问属性

要访问实例的属性,可使用句点表示法,这种语法演示了 Python 如何获悉属性的值。例如 print(f"My dog's name is {my_dog.name}") ,在这里,Python 先找到实例 my_dog ,再查找与该实例相关联的属性 name 。再 Dog 类中引用这个属性时,使用的时 self.name 。下一行代码中也是使用同样的方法来获取属性 age 的值。

最后,输出的时有关 my_dog 的摘要:

My dog's name is Willie
My dog is 6 years old.

② 调用方法

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

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

class Dog:
    --snip--

my_dog = Dog('Willie', 6)
my_dog.sit()
my_dog.roll_over()
Willie is now sitting.
Willie rolled over!

③ 创建多个实例

可按需求根据类创建任意数量的实例。下面来创建一个名为 your_dog 的小狗实例:

class Dog:
    --snip--

my_dog = Dog('Willie', 6)
your_dog = Dog('Lucy', 3)

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

print(f"\nyour dog's name is {your_dog.name}")
print(f"your dog is {your_dog.age} years old.")
your_dog.sit()

在本例中创建的两条小狗,各自都是一个独特的实例,有自己的一组属性,能够执行相同的操作:

My dog's name is Willie
My dog is 6 years old.
Willie is now sitting.

your dog's name is Lucy
your dog is 3 years old.
Lucy is now sitting.

即使给第二条小狗指定同样的名字和年龄,Python 依然会根据 Dog 类创建另一个实例。另外,可以按照需求根据一个类创建任意数量的实例,条件是将每个实例都存储在不同的变量,或占用列表 / 字典中不同的位置。

9.2 使用类和实例

可使用类来模拟显示世界中的很多情景。类编写好后,我们的大部分时间将花在根据类创建的实例上。我们需要执行的一个重要任务时修改实例的属性,这里,我们可以直接修改实例的属性,也可以编写方法以特定的方式进行修改。

9.2.1 Car 类

下面来编写一个表示汽车的类(在创建实例时,需要指定其制造商、型号和生产日期)。它存储了有关汽车的信息,还有一个汇总这些信息的方法:

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())
2019 Audi A4

9.2.2 给属性指定默认值

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

下面来添加一个名为 odmeter_reafing 的属性,其初始值总为 0 。此外,再添加一个名为 read_odmeter() 的方法,用于读取汽车的里程表:

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()

由此,一开始汽车的里程数为 0 :

2019 Audi A4
This car has 0 miles on it.

9.2.3 修改属性的值

我们能以三种方式来修改属性的值:直接通过实例进行修改,通过方法进行修改,以及通过方法进行递增(增加特定的值)。下面依次进行介绍(继续上面汽车的实例好了,由于出售时里程表数为 0 的汽车并不多,因此需要一种方式来修改该属性的值):

① 直接修改属性的值

要修改属性的值,最简单的方式是通过实例直接访问它:

class Car:
    --snip--

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

my_new_car.odometer_reading = 23
my_new_car.read_odometer()
2019 Audi A4
This car has 23 miles on it.

② 通过方法修改属性的值

有时候需要像上一个方法那样直接访问属性,但其他时候则需要编写对属性进行更新的方法。这时候,如果有方法能替你更新属性,将大有裨益。这样一来,我们就无需直接访问属性,而可将值传递给方法,由它在内部进行更新。

下面来演示一个名为 update_odometer() 的方法

class Car:
    --snip--

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

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

my_new_car.update_odometer(23)
my_new_car.read_odometer()

没错,这种方式只是把上一种直接修改的方式写成了方法,现在只需调用即可。另外,我们还可以对方法 update_odometer() 进行扩展,使其在修改历程表读数时做一些额外工作以确保程序的健壮性。

下面我们来添加一些逻辑,来禁止任何人讲里程表读数往回调:

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!")
           
--snip--

③ 通过方法对属性的值进行递增

有时候需要将属性值递增特定的量,而不是将其设置为全新的值,要是这样的话,我们只需将方法中的赋值语句修改为递增语句即可。

假设我们购买了一辆二手车,并且从购买到登记期间增加了 100 英里的里程。下面的方法能让我们传递这个增量,并相应地增大里程表读数:

class Car:
    --snip--

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

my_new_car = Car('subaru', 'outback', 2015)
print(my_new_car.get_descriptive_name())

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

my_new_car.increment_odometer(100)
my_new_car.read_odometer()
2015 Subaru Outback
This car has 23500 miles on it.
This car has 23600 miles on it.

最后,我们也可以轻松修改这个方法,以禁止增量为负值,从而防止有人利用它来回调里程表。

9.3 继承

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

9.3.1 子类的方法 __ init __()

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

例如,下面来模拟电动汽车。电动汽车是一种特殊的汽车,因此可在前面创建的 Car 类的基础上创建新类 ElectricCar 。这样就只需为电动汽车特有的属性和行为编写代码。下面来创建 ElectricCar 类的一个简单版本,它具备 Car 类的所有功能:

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('teala', 'model s', 2019)
print(my_tesla.get_descriptive_name())

为创建这个 ElectricCar 子类,首先是 Car 类的代码:创建子类时,父类必须包含在当前文件中,且位于子类前面。接下来定义了子类 ElectricCar :定义子类时,必须在圆括号内指定父类的名称。紧接着,方法 __ init __() 接受创建 Car 实例所需的信息。最后的 super() 是一个特殊函数,能够调用父类的方法。这行代码让 Python 调用 Car 类的方法 __ init __() ,让 ElectricCar 实例包含这个方法中定义的所有属性。父类也称为 超类(supeiclass),名称 super 由此而来。

9.3.2 给子类定义属性和方法

让一个类继承另一个类后,就可以添加区分子类和父类所需的新属性和新方法了。

下面来添加一个电动汽车特有的属性(电瓶),以及一个描述该属性的方法。我们将存储电瓶容量,并编写一个打印电瓶描述的方法:

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('teala', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battery()
2019 Teala Model S
This car has a 75-kWh battery.

9.3.3 重写父类的方法

对于父类的方法,只要它不符合子类模拟的实物的行为,都可以进行重写。为此,可再子类中定义一个与要重写的父类方法同名的方法。这样,Python 将不会考虑这个父类方法,而只关注在子类中定义的相应方法。
假设 Car 类有一个名为 fill_gas_tank() 的方法,他对全电动汽车来说毫无意义,因此你可能像重写它。下面演示了一种重写的方法:

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() ,转而运行上述代码。使用继承时,可让子类保留从父类那里继承而来的精华,并剔除不需要的糟粕。

9.3.4 将实例用作属性

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

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

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('teala', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()

最后的输出与前面的输出一样,但看起来这次却做了很多额外的工作,其实到现在这一步,我们想多详细地描述电瓶都可以,且不会导致 ElectricCar 类混乱不堪。下面再给 Battery 类添加一个方法,它根据电瓶容量报告汽车的续航里程:

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('teala', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()

所以这次的输出指出了汽车的续航历程(着取决于电瓶的容量,这里使用的默认值 75 ):

2019 Teala Model S
This car has a 75-kWh battery.
This car can go about 260 miles on a full charge.

9.3.5 模拟实物

模拟较复杂的物件(如电动汽车)时,需要解决一些有趣的问题。续航里程是电瓶的属性还是汽车的属性呢?如果只描述一辆汽车,将方法 get_range() 放在 Battery 类中也许是合适的。但如果要描述一家汽车制造商的整个商品线的话,也许应该将方法 get_range() 移到 ElectricCar 类中。在这种情况下, get_range() 依然根据电瓶容量来确定续航里程,但报告的是一款汽车的续航里程。也可以这样做:仍然将方法 get_range() 留在 Battery 类中,但向它传递一个参数,如 car_model 。在这种情况下,方法 get_range() 将根据电瓶容量和汽车型号报告续航里程。

着让我们进入了程序员的另一个世界:解决上述问题时,从较高的逻辑层面(而不是语法层面)考虑;考虑的不是 Python ,而是如何使用代码来表示实物。达到这种境界后,我们便会发现,对现实世界的建模方法没有对错之分,只是方法效率不同罢了。

9.4 导入类

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

9.4.1 导入单个类

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

"""一个可用于表示汽车的类。"""

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

第一行是一个模块级文档字符串, 对该模块的内容做了简要的描述。然后就我们来创建另一个文件 my_car.py ,在其中导入 Car 类并创建其实例:

from car import Car

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

my_new_car.odometer_reading = 23
my_new_car.read_odometer()

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

2019 Audi A4
This car has 23 miles on it.

综上:导入类是一种有效的编程方式。

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

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

"""一组用于表示燃油汽车和电动汽车的类。"""
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.")

    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):
    """电动汽车的独特之处。"""

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

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

from car import ElectricCar

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

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

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

2019 Teala Model S
This car has a 75-kWh battery.
This car can go about 260 miles on a full charge.

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

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

from car import Car, ElectricCar

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

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

然后我们就得到了一辆大众甲壳虫普通汽车和一辆特斯拉 Roasder 电动汽车:

2019 Volkswagen Beetle
2019 Tesla Roadster

9.4.4 导入整个模块

我们还可以导入整个模块,再使用句点表示法访问需要的类。
下面的代码导入了整个 car 模块,并创建了一辆普通汽车和一辆电动汽车:

import car

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

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

9.4.5 导入模块中的所有类

要导入模块中的每个类,可使用语法 from module_name import * ,但并不推荐使用这种导入方式。

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

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

有时候,需要将类分散到多个模块中,以免模块太大或者在同一个模块存储不相关的类。将类存储在多个模块中时,我们有时会发现一个模块中的类依赖于另一个模块中的类。在这种情况下,可在前一个模块中导入必要的类。

下面我们来将 Car 类存储在一个模块中,并将 ElectricCar 类和 Battery 类存储在另一个模块中。将第二个模块命名为 electric_car.py(这将覆盖前面创建的文件 electric_car.py),并将 Battery 类和 ElectricCar 类复制到这个模块中:

"""一组可以用于表示电动车的类"""

from car import Car

class Battery:
    --snip--

class ElectricCar(Car):
    --snip--

现在可以分别从每个模块中导入类,以根据需要创建任何类型的汽车了:

from car import Car
from electric_car import ElectricCar

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

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

9.4.7 使用别名

在第 8 章中我们说过,使用模块来组织项目代码时,别名大有裨益。导入类时,也可为其指定别名。

例如,要在程序中创建大量电动汽车实例,就需要反复输入 ElectricCar ,非常繁琐。为避免这种烦恼,可在 import 语句中给 ElectricCar 指定一个别名 from electric_car import ElectricCar as ES 。所以现在每当需要创建电动汽车实例时,都可以使用这个别名 my_tesla = EC('tesla', 'roadster', 2019)

9.4.8 自定义工作流程

如大家所见,在组织大型项目的代码方面,Python 提供了很多选项。熟悉所有这些选项很重要,这样才能确定那种项目组织方式时最佳的,并能理解别人开发的项目。

一开始应让代码结构尽可能简单。先可能在一个文件中完成所有的工作,确定一切都能正常运行后,在将类移到独立的模块中。如果个人喜欢模块和文件的交互方式,也可在项目开始时就尝试将类存储在模块中。想找出让自己能够编写出代码的方式,再尝试改进代码。

9.5 Python 标准库

Python 标准库 是一组模块,我们安装的 Python 都包含它。我们现在对函数和类的工作原理已有大致的了解,可以开始使用其他程序员编写好的模块了。可以使用标准库中的任何函数和类,只需再程序的开头包含一条简单的 import 语句即可。下面来看看模块 random ,它在模拟很多现实情况时都很有用。

在这个模块中,一个有趣的函数是 randint() 。它将两个整数作为参数,并随机返回一个位于这两个整数之间(含)的整数。下面演示了如何生成一个位于 1 和 6 之间的随机整数:

from random import randint
randint(1, 6)

在模块 random 中,另一个很有用的函数时 choice() 。它将一个列表或元组作为参数,并随机返回其中的一个元素:

from random import choice
players = ['charles', 'martina', 'michael', 'florence', 'eli']
first_up = choice(players)
print(first_up)

创建与安全相关的应用程序时,请不要使用模块 random ,但该模块可以很好地用于创建众多有趣的项目。

9.6 类编码风格

我们必须树立有些与类相关的编码风格,在编写的程序较复杂时尤其如此。

类名应采用 驼峰命名法,即将类名中的每个单词的首字母都大写,而不使用下划线。实例名和模块名都采用小写格式,并在单词之间加上下划线。

对于每个类,都应该紧跟在类定义后面包含一个文档字符串。这种文档字符串简要地描述类的功能,并遵循编写函数的文档字符串时采用的格式约定。每个模块也都应包含一个文档字符串,对其中的类可用于做什么进行描述。

可使用空行来组织代码,但不要滥用。在类中,可使用一个空行来分隔方法;而在模块中,可使用两个空行来分隔。

需要同时导入标准库中的模块和自己编写的模块时,先编写导入标准库模块的 import 语句,再添加一个空行,然后编写导入自己编写的模块的 import 语句。再包含多条 import 语句的程序中,这种做法让人更容易明白程序使用的各个模块都来自何处。


总结

在本章中,我们学习了:

  1. 如何编写类;
  2. 如何使用属性在类中存储信息,以及如何编写方法,以让类具备所需的行为;
  3. 如何编写方法 __ init __() ,以便根据类创建包含所需属性的实例;
  4. 如何修改实例的属性,包括直接修改以及通过方法进行修改;
  5. 如何使用继承来简化相关类的创建工作,以及将一个类的实例作用到另一个类的属性来让类更简洁;
  6. 通过姜磊存储在模块中,并在需要使用这些类的文件中导入它们,以便让项目组织有序;
  7. 认识了 Python 标准库并见识到了一个使用模块 random 的示例;
  8. 编写类时应遵循的 Python 约定。

在第 10 章中,我们将学习如何使用文件,这让你能够保存自己在程序中所做的工作,以及我们让用户做的工作。此外,我们还将学习 异常 ,这是一种特殊的 Python 类,用于帮我们在发生错误时及时采取相应的措施。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值