Python学习笔记6—类
1. 创建和使用类
1.1 创建Dog类
下面将创建一个表示小狗的Dog类,根据Dog类创建的每个示例都将存储名字和年龄,并且赋予每条小狗蹲下(sit()
)和打滚(roll_over()
)的能力:
dog.py
1 | class Dog(): |
在上面的代码中,定义了一个名为Dog的类。根据约定,在Python中,首字母大写的名称指的是类。这个类定义中的括号是空的,说明该类并没有需要继承的父类。
1.1.1 方法__init__()
类中的函数称为方法,而__init__()
是一个特殊的方法,每当你根据Dog类创建新示例时,Python都会自动运行它,类似于C++类中的构造函数,在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。
我们将方法__init__()
定义成了包含三个实参:self、name和age。在这个方法的定义中,形参self是必不可少的,还必须位于其他形参的前面,每个与类相关联的方法调用都自动传递实参self,它是一个指向示例本身的引用,让实例能够访问类中的属性和方法。我们将通过实参向Dog()传递名字和年龄,self
会自动传递,因此我们不需要传递它,只需要给最后两个形参(name
和age
)提供值。
在类中,以self
为前缀的变量都可供类中的所有方法使用,我们还可以通过类的实例来访问这些变量。self.name = name
获取存储在形参name
中的值,并将其存储到变量name
中,然后该变量被关联到当前创建的实例。self.age = age
的作用相同。像这样在类中可通过实例访问的变量称为属性。
1.2 根据类创建实例
可将类视为有关如何创建实例的说明,Dog
类是一系列说明,让Python知道如何创建表示特定小狗的实例。下面来创建一个表示特定小狗的实例:
1 | class Dog(): |
在上面这段代码中,创建了一个名字为’willie’、年龄为6的小狗。遇到这行代码时,Python将实参'willie'
和6
传入Dog类中的方法__init__()
。方法__init__()
创建一个便是特定小狗的实例,并使用实参的值来设置属性name
和age
。方法__init__()
并未显式地包含return语句,但Python自动返回一个表示这条小狗的实例,并将这个实例存储在变量my_dog
中。在Python中,我们通常可以认为首字母大写的名称(如Dog)指的是类,而小写的名称(如my_dog)指的是根据类创建的实例。
1.2.1 访问属性
要访问实例的属性,可使用句点表示法:
1 | my_dog.name |
在这里,Python先找到实例my_dog
,再查找与这个实例相关联的属性name
。在Dog
类中引用这个属性时,使用的是self.name
,因此,在上面的实例化代码中,打印出的结果如下所示:
1 | My dog's name is Willie. |
1.2.2 调用方法
根据Dog
类创建实例后,就可以使用句点表示法来调用Dog
类中定义的任何方法:
1 | class Dog(): |
要调用方法,可指定实例的名称和要调用的方法,并用句点分隔它们。上面的代码中运行的输出结果如下所示:
1 | Willie is now sitting. |
2. 使用类和实例
在类编写好后,你的大部分时间都将花在使用根据类创建的实例上,你需要执行的一个重要任务是修改实例的属性。你可以直接修改实例的属性,也可以编写方法以特定的方式进行修改。
2.1 Car类
下面是一个表示汽车的类,它存储了有关汽车的信息,还有一个汇总这些信息的方法:
car.py
1 | class Car(): |
在上面的代码中,输出结果如下所示:
1 | 2016 Audi A4 |
2.2 给属性指定默认值
类中的每个属性都必须有初始值,哪怕这个值是0或是空字符。在有些情况下,如设置默认值时,在方法__init__()
内指定这种初始值是可行的,如如果你对某个属性这样做了,就无需包含为它提供初始值的形参。
下面来添加一个名为odometer_reading
的属性,其初始值总是为0。除此之外,还添加了一个名为read_odometer()
的方法,用于读取汽车的里程表:
1 | class Car(): |
上面这段代码打印的结果如下,由于未对汽车里程进行修改,因此汽车的里程还是0:
1 | 2016 Audi A4 |
2.3 修改属性的值
在Python中,有如下两种不同的方式可以修改属性的值:直接通过实例进行修改;通过方法进行设置。下面依次介绍这些方法。
2.3.1 直接修改属性的值
要修改属性的值,最简单的方式是通过实例直接访问它。下面的代码直接将里程表度数设置为23:
1 | class Car(): |
此时,my_new_car
这个实例中的odometer_reading
属性就被修改为23:
1 | 2016 Audi A4 |
2.3.2 通过方法修改属性的值
下面的例子演示了一个名为update_odometer()
的方法:
1 | class Car(): |
上面的代码中添加了一个新的方法update_odometer()
。这个方法接受一个里程值,并将其存储到self.odometer_reading
中。上面的代码打印结果如下:
1 | 2016 Audi A4 |
3. 继承
编写类时,并非总是要从空白开始。如果你要编写的类是另一个现成类的特殊版本,可使用继承。一个类继承另一个类时,它将自动获得另一个类的所有属性和方法。原有的类称为父类,而新类称为它的子类。子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法。
3.1 子类的方法__init__()
创建子类时的示例时,Python首先需要先给父类的所有属性赋值,为此,子类的方法__init__()
需要依靠父类。
下面的代码创建一个简单的ElectricCar类,它具备Car类的所有功能:
1 | class Car(): |
创建子类时,父类必须包含在当前文件中,且位于子类前面,在上面的代码中,我们定义了子类ElectricCar
。定义子类时,必须在括号内指定父类的名称。方法__init__()
接受创建Car
示例所需的信息。super()
是一个特殊函数,帮助Python将父类和子类关联起来,这行代码让Python调用ElectricCar
的父类的方法__init__()
,让ElectricCar
实例包含父类的所有属性。父类也称为超类,名称super()
因此而得名。上面这段代码的输出结果为:
1 | 2016 Tesla Model S |
可以看到继承自Car
类的ElectricCar
类确实具有和其父类Car相同的功能。
3.2 给子类定义属性和方法
让一个类继承另一个类后,可添加区分子类和父类所需的新属性和方法,下面的代码添加一个电动车特有的属性(电瓶),以及一个描述该属性的方法:
1 | class Car(): |
在上面这行代码中,添加了新属性self.battery_size
,并设置初始值为70。根据ElectricCar
类创建的所有实例都将包含这个属性,但所有Car
实例都不包含它。除此之外,还添加了一个describe_battery()
方法,它打印有关电瓶的信息。我们调用这个方法时,将看到一条电动车特有的描述:
1 | 2016 Tesla Model S |
3.3 重写父类方法
对于父类的方法,只要它不符合子类模拟的实物的行为,都可对其进行重写。为此,可在子类中定义一个这样的方法,即它与要重写的父类方法同名。这样,Python将不会考虑这个父类方法,而只关注你在子类中重新定义的该方法。
假设Car
类有一个名为fill_gas_tank()
的方法,它对全电动车来说毫无意义,下面演示了一种重写方式:
1 | class ElectricCar(Car): |
现在,如果有人对电动车调用方法fill_gas_tank()
,Python将忽略Car
类中的方法fill_gas_tank()
。转而运行上述代码。使用继承时,可让子类保留从父类哪里继承而来的精华,并剔除不需要的糟粕。
3.4 将实例用作属性
在Python中,可以将大型类拆分成多个协同工作的小类,例如在下面的代码中,将针对汽车电瓶的属性和方法提取出来,放到另一个名为Battery
的类中,并将一个Battery
实例用作ElectricCar
类的一个属性:
1 | class Car(): |
在上面的代码中,定义了一个名为Battery
的新类,在ElectricCar
这个类中,创建了一个名为self.battery
的Battery实例。现在每个ElectricCar实例都包含一个自动创建的Battery实例。输出结果如下所示:
1 | 2016 Tesla Model S |