第9章 类

发布于 2022-03-26  406 次阅读


面向对象编程是最有效的软件编写方式之一。在面向对象编程中,编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,你定义一大类对象都有的通用行为。基于类创建的对象时,每个对象都自动具备这种通用欣慰,然后可根据需要赋予每个对象独特的个性。使用面向对象编程可以模拟现实情景,其逼真程度达到了令人惊讶的的地步。
根据类来创建对象称为实例化,这让你能够使用类的实例。
学习编写类创建其实例。还可以编写一些类来扩展既有的功能。让相似的类能够高效地共享代码。将把自己编写的类存储在模块中,并在自己的程序文件中导入其他程序员编写的类。

9.1 创建和使用类

使用类几乎可以模拟任何东西。下面编写一个小狗类。它表示的不是特定的小狗,而实任何小狗。
由于大多数小狗都包含(名字和年龄)和两种行为(蹲下和翻滚),Dog类将包含它们,这个类,让Python直到如何创建表示小狗对象。编写这个类后,将使用它来创建表示特定小狗的实例。

9.1.1 创建Dog类

class Dog:
    """一次模拟小狗的简单尝试。"""

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

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

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

根据约定,首字母大写的名称指的是类。

方法init()

类中的函数称为方法。前面学到的有关函数的一切都适用于方法。就目前而言,唯一重要的就是调用方法的办法。方法init()是一个特殊的方法,每当根据Dog类创建新实例时,Python都会自动运行它。在这个方法的名称中,开头和末尾有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。务必确保方法init()的两边都有两个下划线。否则当使用类来创建实例时,将不会自动调用这个方法,进而引发难以发现的错误。
将方法init()定义成包含三个形参:self、name和age。在这个方法的定义中,形参self必不可少,而且必须位于其他形参前面。为何必须在方法定义中包含形参self呢?因为Python调用这个方法来创建Dog实例时,将自动传入实参self,每个与实例相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。创建Dog实例时,Python将调用Dog类的方法init()。将通过向Dog()传递名字和年龄,self会自动传递。因此不需要传递它,每当根据Dog类创建实例时,都只需给最后两个形参(name和age)提供值。
后面定义的两个变量中都有前缀self,以self为前缀的变量可供类中的所有方法使用,可通过类的任何实例来访问,self.name = name 获取与形参name相关联的值,并将其赋给变量name,然后改变量被关联到当前创建的实例。self.age = age的作用与次类似。像这样可以通过实例访问的变量称为属性。
Dog类还定义了另外两个方法:sit()和roll_over()。这些方法执行时不需要额外的信息,因为它们只有一个形参self,随后创建的实例可以访问这些方法,换句话说,它们蹲下和打滚。当然,sit()和roll_over()所作的有限,只是打印,如果这个类包含在一个游戏中,这些方法将包含创建小狗蹲下和打滚动画效果的代码;如果这个类是用于控制机器狗的,这些方法将让机器狗做出下蹲和打滚的动作。

9.1.2 根据类创建实例

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

class Dog:
    """一次模拟小狗的简单尝试。"""

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

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

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

my_dog = Dog('kaer',2)

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

file
遇到my_dog = Dog('kaer',2)这行代码时,Python使用实参‘kaer’和2调用Dog类的方法init().一般大写指类,小写指的是类创建的实例。

1.访问属性

要访问实例的属性,可使用句点表示法。

2.调用方法

根据Dog类创建实例后,就能使用句点表示法来调用Dog类中定义的任何方法了。

class Dog:
    """一次模拟小狗的简单尝试。"""

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

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

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

my_dog = Dog('kaer',2)
# 
# print(f"My dog's name is {my_dog.name}")
# print(f"My dog is {my_dog.age} years old")
my_dog.sit()
my_dog.roll_over()

file

要调用方法,可指定实例的名称(my_dog)和要调用的方法,并用句点分隔。
这种语法很有用,如果给属性和方法指定了合适的描述性名称,如name、age、sit()和roll_over(),即便是从未见过的代码,也可以轻松推断出来是干什么的。

3.创建多个实例

可按续修根据类创建任意数量的实例。

class Dog:
    """一次模拟小狗的简单尝试。"""

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

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

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

my_dog = Dog('kaer',2)
your_dog = Dog('Dia',4)
#
print(f"My dog's name is {my_dog.name}")
print(f"My dog is {my_dog.age} years old")
my_dog.sit()
# my_dog.roll_over()
print(f"Your dog's name is {your_dog.name}")
print(f"Your dog is {your_dog.age} years old")
your_dog.roll_over()

file
可以根据需求创建任意数量的实例。条件是将每个实例都存储在不同的变量中,或占用列表或字典的不同位置。

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',2022)
print(my_new_car.get_descriptive_name())

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

9.2.2 给属性指定默认值

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

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 red_odometer(self):
        """打印一条指出汽车里程的消息。"""
        print(f"This car has {self.odometer_reading} miles on it.")
my_new_car = Car('audi','a4',2022)
print(my_new_car.get_descriptive_name())
my_new_car.red_odometer()

file
需要一种方法修改属性的值

9.2.3 修改属性的值

可以使用三种方式修改属性的值:直接通过实例进行修改,通过方法进行设置,以及方法进行递增(增加特定的值)。

1.直接修改属性的值

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

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 red_odometer(self):
        """打印一条指出汽车里程的消息。"""
        print(f"This car has {self.odometer_reading} miles on it.")
my_new_car = Car('audi','a4',2022)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 24
my_new_car.red_odometer()

file
有时候需要像这样直接访问属性,但其他时候需要编写对属性进行更新的方法。

2.通过方法修改属性的值

如果有方法能替代更新属性,将大有裨益,这样就无需直接访问属性,而可将值传递给方法,由它在内部进行更新。

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 red_odometer(self):
        """打印一条指出汽车里程的消息。"""
        print(f"This car has {self.odometer_reading} miles on it.")

    def update_odometer(self,mileage):
        """将里程表读数设置为指定的值。"""
        self.odometer_reading = mileage
my_new_car = Car('audi','a4',2022)
print(my_new_car.get_descriptive_name())
# my_new_car.odometer_reading = 24
my_new_car.update_odometer(24)
my_new_car.red_odometer()

file

可对方法update_odometer()进行扩展,使其在修改里程表读书时做些额外的工作。下面添加一些逻辑,禁止任何人将了里程表读书往回调。

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 red_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!")
my_new_car = Car('audi','a4',2022)
print(my_new_car.get_descriptive_name())
# my_new_car.odometer_reading = 24
my_new_car.update_odometer(24)
my_new_car.red_odometer()

现在,update_odometer()在修改属性前检查指定的读数是否合理。

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

有时候需要将属性的值递增特定的量,而不是将其设置为全新值。
假设购买的是一辆二手车,且从购买到登记期间增加了1000英里的里程。下面的方法让我们能够传递这个增类,并相应地增大里程表读书:

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 red_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 inserment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading += miles
my_new_car = Car('audi','a4',2022)
print(my_new_car.get_descriptive_name())
# my_new_car.odometer_reading = 24
my_new_car.update_odometer(23_500)
my_new_car.red_odometer()

my_new_car.inserment_odometer(1000)
my_new_car.red_odometer()

file

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

注意:可以使用类似于上面的方法来控制用户修改属性(如里程表读数)的方式,但呢个够访问程序的人都可以通过直接访问属性来将里程表修改为任何值,要确保安全,除了进行类似于前面的基本检查外,还需要特别注意细节。

9.3 继承

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

9.3.1 子类的方法init()

在既有类的基础上编写新类时,通常要调用符类的方法init().这将初始化在父类init()方法中定义的所有属性,从而让子类包含这些属性。
例如,下面来模拟电动汽车。电动汽车是一种特殊的汽车,因此可在前面创建的Car类的基础上新建类ElectricCar。这样就只需为电动汽车特有的属性和行为编写代码。

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 red_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 inserment_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',2023)
print(my_tesla.get_descriptive_name())

file
首先是Car类的代码。创建子类时,父类必须包含在当前文件中,且位于父类前面。在class ElectricCar(Car)处,,定义了子类。定义子类时,必须在圆括号内指定父类的名称。方法init()接受创建Car实例所需的信息。
super()是一个特殊的函数,能够调用父类的方法。这行代码让Python调用Car类的方法init(),让ElectricCar实例包含这个方法中定义的所有属性。父类也称为超类(superclass)。
ElectriCar实例的行为和Car的实例一样,现在可以开始定义电动汽车特有的属性和方法了。

9.3.2 给子类定义属性和方法

让一个类继承另一个类后,就可以添加区分子类和父类所需的新属性和新方法了。
下面添加一个电动汽车特有的属性(电瓶),以及一个描述该属性的方法。

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 red_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 inserment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading += miles

class ElectricCar(Car):
    """电动汽车的独特之处"""

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

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

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

file

在上述子类中,添加了新属性self.battery_size,并设置其初始值(75).根据ElectricCar类创建的所有实例都将包含该属性。但所有的Car实例都不包含它。在后面还添加了一个名为describe_battery()的方法,打印有关信息。
对于ElectricCar类的特殊程序没有任何限制。模拟电动汽车时,可以根据所需的准确度添加任意数量的属性和方法。如果一个属性或方法是任何汽车都有的,而不是电动汽车特有的,就应该将其加入到Car类而非ElectriCar类中。这样,使用Car类的人将获得相应的功能。而ElectricCar类包含处理电动汽车特有属性和行为的代码。

9.3.3 重写父类的方法

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

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 red_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 inserment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading += miles

class ElectricCar(Car):
    """电动汽车的独特之处"""

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

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

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

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

现在,如果有人对电动汽车调用fill_gas_tank(),Python将忽略Car类中的方法fill_gas_tank(),转而运行上述代码。使用继承时,可让子类保留从父类哪里继承而来的精华,并剔除不需要的糟粕。

9.3.4 将实例用做属性

当代码模拟实物时,可能会发现自己给类添加的细节越来越多;属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分提取出来,作为一个独立的类。可以将一个大型类拆分成多个协同工作的小类。
例如,不断给ElectricCar类添加细节时,可能发现包含很多专门针对汽车电瓶的属性和方法。在这种情况下,可以将这些属性和方法提取出来,放到一个名为Battery的类中,并且将一个Battery实类作为ElectricCar类的属性:

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 red_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 inserment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading += miles

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

    def __init__(self,battery_size=80):
        """初始化电瓶的属性"""
        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.batter = Battery()
        # self.batter_size = 75

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

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

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

定义了一个名为Battery的新类,它没有继承任何类。初始化处,除了self外,还有另外一个形参battery_size.这个形参是可选的;discribe_battery()也移入到了这个类中。
在ElericCar类中,添加了一个名为self.battery的属性。这行代码让Python创建一个新的Battert实例实例。并将该实例属性赋值给属性self.battery().每当方法init()被调用时,都执行该操作,因此现在每个ElectricCar实例都包含一个自动创建的Battery实例。
这看似做了很多额外的工作,但是现在想多详细地描述电瓶都可以,且不会导致ElectricCar类混乱不堪。下面再给Batter类添加一个方法,它根据电瓶容量报告汽车的续航里程:

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 red_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 inserment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading += miles

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

    def __init__(self,battery_size=80):
        """初始化电瓶的属性"""
        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 == 80:
            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.batter = Battery()
        # self.batter_size = 75

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

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

my_tesla = ElectricCar('tesla','model s',2023)
print(my_tesla.get_descriptive_name())
my_tesla.batter.describe_battery()
my_tesla.batter.get_range()

file

9.3.5 模拟实物

将里程放入电池类还是汽车类?
都行!
解决上述问题时,从较高的逻辑层面(而不是语法层面)考虑,考虑的不是Python,而是如何使用代码来表示实物。对现实世界的建模方法没有对错之分。
需要经验。

9.4 导入类

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

9.4.1 导入单个类

下面来创建一个只包含Car类的模块。这让我们面临一个微妙的命名问题:
前面已有一个car.py的文件,但是这个模块也应该命名为car.py,因为它包含着表示汽车的代码。
这样解决:将Car类存储在一个名为car.py的模块中,该模块将覆盖前面使用的文件car.py,从现在起,使用该模块的程序都必须使用更具体的文件名,如my_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 = f"{self.year} {self.make} {self.model}"
        return long_name.title()

    def red_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 inserment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading += miles

为每个类文档都写注释。
下面来创建一个文件my_car.py在其中导入Car类并创建起实例:

from car import Car

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

my_new_car.odometer_reading = 23
my_new_car.red_odometer()

file
import 语句让Python打开模块car并导入其中的Car类。这样就可以使用Car类。
导入类是一种有效的编程方式,如果这个类包含了整个Class类,它该多次!通过将这个类以到一个模块中并导入该模块,依然可以使用全部功能。也可使主程序干净。还让能够将大部分逻辑代码存储在独立的文件中。确定类像你希望的那样工作后,就可以不管这些文件,而专注于主程序的高级逻辑了。

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

虽然同一个模块中的类之间应存在某种相关性。但可以根据需要在一个模块中存储任意数量的类。
Battery类和ElectriCar类都可以帮助模拟汽车

"""一组用于表示燃油汽车和电动汽车的类:"""
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 red_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 inserment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading += miles

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

    def __init__(self,battery_size=80):
        """初始化电瓶的属性"""
        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 == 80:
            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.batter = Battery()
        # self.batter_size = 75

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

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

my_new_car = ElectricCar("tesla",'model s ',2023)
print(my_new_car.get_descriptive_name())

my_new_car.batter.describe_battery()
my_new_car.batter.get_range()

file

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

可以根据需要在程序文件中导入任意数量的类

from car import Car,ElectricCar

my_beetle = Car('Volkswangen','beetle',2023)
print(my_beetle.get_descriptive_name())

my_new_car2 = ElectricCar("tesla",'model s ',2023)
print(my_new_car2.get_descriptive_name())

file

9.4.4 导入整个模块

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

import car

my_beetle = car.Car('Volkswangen','beetle',2023)
print(my_beetle.get_descriptive_name())

my_new_car2 = car.ElectricCar("tesla",'model s ',2023)
print(my_new_car2.get_descriptive_name())

导入整个car模块。使用语法model.ClassName访问所需的类。

9.4.5 导入模块中的所有类

from model_name import *

不推荐:

  • 看开头import直打使用了那些类
  • 引发名称冲突

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

    有时候,需要将类分散到多个模块中。
    若一个模块中的类依赖另一个模块中的类。

    form car import Car
    ...

    从各个模块导入类:

    form car import Car
    form electri_car import ElectricCar
    ...

    9.4.7 使用别名

    form electric_cat import ElectricCar as EC
    # 需要使用电车
    my_tesla = EC(...)

    9.4.8 自定义工作流程

    一开始尽可能让代码结构简单,尽可能咋一个文件中完成所有工作,确定一切都可以正常允许后,再将类迁移到独立的模块

9.5 Python标准库

Python标准库是一组模块。安装的Python都包含它。
函数randint()将两个整数作为参数,并随机返回一个于这两个整数之间的(含)整数。

>>> from random import randint
>>> randint(1,6)
4

再random中,另一个有用的函数是choice(),它将一个列表作为参数,并随机返回其中的一个元素。
创建安全项目时候不要使用random.

9.6 类编码风格

类名采用驼峰命名法,将每个类名中的每个单词的首字母都大写,而不使用下划线。实例名和模块名都采用小写格式,并在单词之间加上下划线。
每个类记得写注释。
在类中1个空行,模块中,可以使用两个空行。
需要同时导入标准库中的模块和自己编写的模块时候,先编写导入标准库模块的import语句。再加空行,导入自己的。

9.7 小结

如何编写类。
如何使用属性再类中存储信息。
让类具备所需的行为。
初始化函数。
模块化。


擦肩而过的概率