Python编程:从入门到实践(九)——类

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

一、创建和使用类

一个表示小狗的简单类Dog,表示任何小狗,包含小狗的两项信息(名字和年龄)和两种行为(蹲下和打滚)。
这个类让Python知道如何创建表示小狗的对象。
编写这个类后,我们将使用它来创建表示特定小狗的实例。

创建Dog类
class Dog():
    '''一次模拟小狗的简单尝试'''
    def __init__(self, name, age):
        '''初始化属性name和age'''
        self.name = name
        self.age = age
    
    def sit(self):
        '''模拟小狗被命令时蹲下'''
        print(self.name.title() + " is now sitting!")

    def roll_over(self):
        '''模拟小狗被命令时打滚'''
        print(self.name.title() + " rolled over!")
  • 方法__init__()

类中的函数称为方法。
方法与之前学过的函数的差别在于调用方法的方式。
方法__init__()是一个特殊的方法,每当根据Dog类创建新实例时,Python都会自动运行它。
在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。
我们将方法__init__()定义成了包含三个形参:self、name和age。
在这个方法的定义中,形参 self 必不可少,还必须位于其他形参的前面。因为Python调用这个__init__()方法来创建Dog实例时,将自动传入实参 self 。每个与类相关联的方法调用都自动传递实参 self ,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。
我们创建Dog实例时,Python将调用Dog类的方法__init__()。我们将通过实参向Dog()传递名字和年龄;self 会自动传递,因此我们不需要传递它。
每当我们根据Dog类创建实例时,都只需给最后两个形参(name和age)提供值。

  • 前缀self的两个变量

self.name = name 和 self.age = age 都有前缀self。以 self 为前缀的变量都可供类中的所有方法使用,我们还可以通过类的任何实例来访问这些变量。self.name = name获取存储在形参name中的值,并将其存储到变量name中,然后将变量被关联到当前创建的实例。self.age = age 的作用与此类似。像这样可通过实例访问的变量称为属性。
Dog类还定义了另外两个方法:sit()和roll_over()。由于这些方法不需要额外的信息,如名字或年龄,因此它们只有一个形参self。我们后面将创建的实例能够访问这些方法,换句话说,它们都会蹲下和打滚。当前,sit()和roll_over()所做的有限,它们只是打印一条消息, 指出小狗正蹲下或打滚。但可以扩展这些方法以模拟实际情况:如果这个类包含在一个计算机游戏中,这些方法将包含创建小狗蹲下和打滚动画效果的代码。如果这个类是用于控制机器狗的, 这些方法将引导机器狗做出蹲下和打滚的动作。

根据类创建实例
class Dog():
    '''一次模拟小狗的简单尝试'''
    def __init__(self, name, age):
        '''初始化属性name和age'''
        self.name = name
        self.age = age
    
    def sit(self):
        '''模拟小狗被命令时蹲下'''
        print(self.name.title() + " is now sitting!")

    def roll_over(self):
        '''模拟小狗被命令时打滚'''
        print(self.name.title() + " rolled over!")

my_dog = Dog('willie', 6) #创建实例
print("My dog's name is " + my_dog.name.title() + ".")
print("My dog is " + str(my_dog.age) + " years old.")
my_dog.sit()
my_dog.roll_over()

输出:

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

方法__init__()创建一个表示特定小狗的示例,并使用我们提供的值来设置属性name和age。方法 init()并未显式地包含return语句,但Python自动返回一个表示这条小狗的实例。我们将这个实例存储在变量my_dog中。在这里,命名约定很有用:我们通常可以认为首字母大写的名称(如 Dog)指的是类,而小写的名称(如my_dog)指的是根据类创建的实例。

  • 访问属性

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

my_dog's name

在这里,Python先找到实例my_dog,再查找与这个实例相关联的属性name。在Dog类中引用这个属性时,使用的是self.name。

  • 调用方法

根据Dog类创建实例后,就可以使用句点表示法来调用Dog类中定义的任何方法。要调用方法,可指定实例的名称和要调用的方法,并用句点分隔它们。遇到代码my_dog.sit()时,Python在类Dog中查找方法sit()并运行其代码。

  • 创建多个实例

可按需求根据类创建任意数量的实例。

class Dog():
    '''一次模拟小狗的简单尝试'''
    def __init__(self, name, age):
        '''初始化属性name和age'''
        self.name = name
        self.age = age
    
    def sit(self):
        '''模拟小狗被命令时蹲下'''
        print(self.name.title() + " is now sitting!")

    def roll_over(self):
        '''模拟小狗被命令时打滚'''
        print(self.name.title() + " rolled over!")

my_dog = Dog('willie', 6)
your_dog = Dog('lucy', 3)
print("My dog name is " + my_dog.name.title() + ".")
print("My dog is " + str(my_dog.age) + " years old.")
my_dog.sit()
print("Your dog name is " + your_dog.name.title() + ".")
print("Your dog is " + str(your_dog.age) + " years old.")
your_dog.sit()

输出:

My dog name is Willie.
My dog is 6 years old.
Willie is now sitting!
Your dog name is Lucy.
Your dog is 3 years old.
Lucy is now sitting!

二、使用类和实例

类编写好之后,需要使用根据类创建的实例。
修改实例的属性,可以直接修改实例的属性,也可以编写方法以特定的方式进行修改。

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 = str(self.year) + ' ' + self.make + ' ' + self.model
        return long_name.title()

my_car = Car('audi', 'a4', 2016)
print(my_car.get_descriptive_name())
给属性指定默认值

类中的每个属性都必须有初始值,哪怕这个值是0或空字符串。在有些情况下,如设置默认值时,在方法__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 = str(self.year) + ' ' + self.make + ' ' + self.model
        return long_name.title()
    def read_odometer(self):
        print("This car has " + str(self.odometer_reading) + "miles on it.")

my_car = Car('audi', 'a4', 2016)
print(my_car.get_descriptive_name())
my_car.read_odometer()

输出:

2016 Audi A4
This car has 0  miles on it.

由于出售时里程表读数为0的汽车并不多,因此我们需要一个修改该属性的值的途径。

修改属性的值
  • 直接通过实例进行修改

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

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("This car has " + str(self.odometer_reading) + "miles on it.")

my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()

输出:

2016 Audi A4
This car has 23  miles on it.
  • 通过方法修改属性的值

如果有更新属性的方法,就可以无需直接访问属性,而可将值传递给一个方法,让它在内部进行更新。

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("This car has " + str(self.odometer_reading) + "miles on it.")
    def update_odometer(self, mileage):
        self.odometer_reading = mileage

my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
my_new_car.update_odometer(23)
my_new_car.read_odometer()

输出:

2016 Audi A4
This car has 23  miles on it.

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

class Car():
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 23
    def get_descriptive_name(self):
        long_name = str(self.year) + ' ' + self.make + ' ' + self.model
        return long_name.title()
    def read_odometer(self):
        print("This car has " + str(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', 2016)
my_new_car.get_descriptive_name()
my_new_car.update_odometer(22)
my_new_car.read_odometer()

输出:

You can't roll back an odometer!
This car has 23  miles on it.
  • 通过方法对属性的值进行递增

有时候需要将属性值递增特定的量,而不是将其设置为全新的值。

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("This car has " + str(self.odometer_reading) + "  miles on it.")
    def increas_odometer(self, miles):
        if miles >= 0:
            self.odometer_reading += miles
        else:
            print("You can't roll back an odometer!")

my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
my_new_car.increas_odometer(23500)
my_new_car.read_odometer()
my_new_car.increas_odometer(100)
my_new_car.read_odometer()

输出:

2016 Audi A4
This car has 23500  miles on it.
This car has 23600  miles on it.

三、继承

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

子类的方法__init__()
class Car():
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        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("This car has " + str(self.odometer_reading) + " miles on it.")
    def update_odometer(self, milesage):
        if self.odometer_reading <= milesage:
            odometer_reading = milesage
        else:
            print("You can't roll back an odometer!")
    def increase_odometer(self, miles):
        if miles >= 0:
            self.read_odometer += miles
        else:
            print("You can't roll back an odometer!")

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

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

为了测试继承是否能够正确地发挥作用,我们尝试创建一辆电动汽车,但提供的信息与创建普通汽车时相同。
注意:
1、创建子类时,父类必须包含在当前文件中,且位于子类前面。
2、定义子类时,必须在括号内指定父类的名称。
3、方法__init__()接受创建实例所需的信息。
4、super()是一个特殊函数,帮助Python将父类和子类关联起来。这行代码让Python调用ElectricCar的父类的方法__init__(),让ElectricCar实例包含父亲的所有属性。

给子类定义属性和方法

让一个类继承另一个类后,可添加区分子类和父类所需的新属性和方法。
添加一个电动汽车特有的属性(电瓶),以及一个描述该属性的方法。我们将存储电瓶容量,并编写一个打印电瓶描述的方法:

class Car():
    --snip--

class ElectricCar(Car): 
    """电动汽车的独特之处"""
    def __init__(self, make, model, year): 
        """初始化父类的属性"""
        super().__init__(make, model, year)
        self.battery_size = 70
    def describe_battery(self):
        print("This car has a " + str(self.battery_size) + "-kWh battery.")

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

输出:

2016 Tesla Model S
This car has a 200-kWh battery.

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

重写父类的方法

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

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

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

将实例用作属性

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

class Car():
    --snip--

class Battery():
    #一次模拟电动汽车电瓶的简单尝试
    def __init__(self, battery_size=70):
        self.battery_size = battery_size
    def describe_battery(self):
        print("This car has a " + str(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', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()

名为Battery的新类,它没有继承任何类。
在ElectricCar类中,我们添加了一个名为self.battery的属性,Python创建一个新的Battery实例,并将该实例存储在属性self.battery中。每当方法__init__()被调用时,都将执行该操作;因此现在每个ElectricCar实例都包含一个自动创建的Battery实例。

输出:

2016 Tesla Model S
This car has a 70-kWh battery.

现在我们想多详细地描述电瓶都可以,且不会导致ElectricCar类混乱不堪。下面再给Battery类添加一个方法,它根据电瓶容量报告汽车的续航里程:

class Battery():
    def __init__(self, battery_size=70):
        self.battery_size = battery_size
    def describe_battery(self):
        print("This car has a " + str(self.battery_size) + "-kWh battery.")
    def get_range(self):
        if self.battery_size == 70:
            range = 240
        elif self.battery_size == 85:
            range = 270
        message = "This car can go approximately " + str(range) 
        message += " miles on a full charge."
        print(message)
class ElectricCar(Car):
    def __init__(self, make, model, year):
        super().__init__(make, model, year)
        self.battery = Battery()
my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()

输出:

2016 Tesla Model S
This car has a 70-kWh battery.
This car can go approximately 240 miles on a full charge.
模拟实物

如果我们只需描述一辆汽车,那么将方法get_range()放在battery类中 是合适的。
但如果要描述一家汽车制造商的整个产品线,也许应将方法get_range()移到ElectricCar类中。在这种情况下,get_range()依然根据电瓶容量来确定续航里程,但报告的是一款汽车的续航里程;我们也可以这样做:将方法get_range()还留在Battery类中,但向它传递一个参数,如car_model,在这种情况下,方法get_model()将根据电瓶容量和汽车型号报告续航里程。

四、导入类

Python可以将类存储在模块中,然后在主程序中导入所需的模块。

导入单个类

创建一个只包含Car类的模块,将Car类存储在一个名为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("The car has " + str(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类并创建其实例:
my_car.py

from car import Car

my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()

输出:

2016 Audi A4
The car has 23miles on it.

导入类是一种有效的编程方式。如果在这个程序中包含了整个Car类,代码会很长。通过将这个类移到一个模块中,并导入该模块,你依然可以使用其所有功能,但主程序文件变得整洁而易于阅读了。这还能让你将大部分逻辑存储在独立的文件中。

在一个模块中存储多个类

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

class Car():
    --snip--

class Battery():
    def __init__(self, battery_size = 70):
        self.battery_size = battery_size
    def describe_battery(self):
        print("The car has a " + self.battery_size +"-kWh battery.")
    def get_range(self):
        if self.battery_size == 70:
            range = 240
        else self.battery_size == 85:
            range = 270
        
        message = "The car an go approximately " + str(range)
        message += "miles on a full charge."
        print(message)

class ElectricCar():
    def __init__(self, make, model, year):
        super().__init(make, model, year)
        self.battery = Battery()

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

from car import ElectricCar

my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name()) 
my_tesla.battery.describe_battery() 
my_tesla.battery.get_range()

输出:

2016 Tesla Model S
The car has a 70-kWh battery.
This car can go approximately 240 miles on a full charge.
从一个模块中导入多个类

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

from car import Car,ElectricCar

my_beetle = Car('volkswagen', 'beetle', 2016)
print(my_beetle.get_descriptive_name())
my_tesla = ElectricCar('tesla', 'roadster', 2016)
print(my_tesla.get_descriptive_name())

输出:

2016 Volkswagen Beetle
2016 Tesla Roadster
导入整个模块

可以导入整个模块,再使用句点表示法访问需要的类。
由于创建类实例的代码都包含模块名,因此不会与当前文件使用的任何名称发生冲突。
下面的代码导入整个car模块,并创建一辆普通汽车和一辆电动汽车:
my_cars.py

import car

my_beetle = car.Car('volkswagen', 'beetle', 2016)
print(my_beetle.get_descriptive_name())
my_tesla = car.ElectricCar('tesla', 'roadster', 2016)
print(my_tesla.get_descriptive_name())
导入模块中的所有类

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

from module_name import *

不推荐使用这种导入方式。原因一:如果只要看一下文件开头的import语句,就能清楚地知道程序使用了哪些类,但这种导入方式没有明确地指出你使用了模块中的哪些类。原因二:如果不小心导入了一个与程序文件中其他东西同名的类,将引发难以诊断的错误。

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

有时候,需要将类分散到多个模块中,以免模块太大,或在同一个模块中存储不相关的类。将类存储在多个模块中时,你可能会发现一个模块中的类依赖于另一个模块中的类。在这种情况下,可在前一个模块中导入必要的类。
例如,下面将Car类存储在一个模块中,并将ElectricCar和Battery类存储在另一个模块中。我们将第二个模块命名为electric_car1.py,并将Battery和ElectricCar类复制到这个模块中:
electric_car1.py

from car import Car

class Battery():
    def __init__(self, battery_size = 70):
        self.battery_size = battery_size
    def describe_battery(self):
        print("The car has a " + str(self.battery_size + "-kWh battery."))
    def get_range(self):
        if self.battery_size == 70:
            range = 240
        elif self.battery_size == 85:
            range = 270
        message = "This car can go approximately " + str(range) 
        message += " miles on a full charge."
        print(message)
class ElectricCar(Car):
    def __init__(self, make, model, year):
        super().__init__(make, model, year)
        self.battery = Battery()

ElectricCar类需要访问其父类Car,因此我们在开头直接将Car类导入该模块中。如果我们忘记了这行代码,Python将在我们试图创建ElectricCar实例时引发错误。我们还需要更新模块car,使其包含Car类:
car1.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("The car has " + str(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_cars1.py

from car1 import Car
from electric_car1 import ElectricCar

my_beetle = Car('volkswagen', 'beetle', 2016) 
print(my_beetle.get_descriptive_name())
my_tesla = ElectricCar('tesla', 'roadster', 2016) 
print(my_tesla.get_descriptive_name())

输出:

2016 Volkswagen Beetle
2016 Tesla Roadster
发布了22 篇原创文章 · 获赞 0 · 访问量 613

猜你喜欢

转载自blog.csdn.net/weixin_45237889/article/details/104044592