python基础:类
面向对象编程(object-oriented programming,OOP)是最有效的软件编写方法之一。在面向对象编程中,你编写表示现实世界中的事物和情景的类(class),并基于这些类来创建对象(object)。类中的函数称为方法。
根据类来创建对象称为实例化,这让你能够使用类的实例(instance)
一、创建和使用类
使用类几乎可以模拟任何东西。下面来编写一个表示小狗的简单类 Dog——它表示的不是特定的小狗,而是任何小狗。对于大多数宠物狗,我们都知道些什么呢?它们都有名字和年龄。我们还知道,大多数小狗还会坐下和打滚。由于大多数小狗具备上述两项信息(名字和年龄)和两种行为(坐下和打滚),我们的 Dog 类将包含它们。这个类让 Python 知道如何创建表示小狗的对象。编写这个类后,我们将使用它来创建表示特定小狗的实例。
1. 创建类
class Dog:# 一次模拟小狗的简单尝试def __init__(self, name, age):# 初始化name和ageself.name = nameself.age = agedef sit(self):# 模拟小狗收到命令坐下print(f"{self.name} is now sitting")def roll_over(self):# 模拟小狗收到命令打滚print(f"{self.name} is now rolling over")
我们将 __init__() 方法定义成包含三个形参:self、name 和 age。在这个方法的定义中,形参 self 必不可少,而且必须位于其他形参的前面。为何必须在方法定义中包含形参 self 呢?因为当 Python 调用这个方法来创建 Dog 实例时,将自动传入实参 self。每个与实例相关联的方法调用都会自动传递实参 self,该实参是一个指向实例本身的引用,让实例能够访问类中的属性和方法。当我们创建 Dog 实例时,Python 将调用 Dog 类的__init__() 方法。我们将通过实参向 Dog() 传递名字和年龄;self 则会自动传递,因此不需要我们来传递。每当我们根据 Dog 类创建实例时,都只需给最后两个形参(name 和 age)提供值。
2. 根据类创建实例
可以将类视为有关如何创建实例的说明。例如,Dog 类就是一系列说明,让 Python 知道如何创建表示特定小狗的实例。
- 调用方法
class Dog:# 一次模拟小狗的简单尝试def __init__(self, name, age):# 初始化name和ageself.name = nameself.age = agedef sit(self):# 模拟小狗收到命令坐下print(f"{self.name} is now sitting")def roll_over(self):# 模拟小狗收到命令打滚print(f"{self.name} is now rolling over")
my_dog = Dog('Jack', 20)
my_dog.sit()'
结果:
Jack is now sitting
'
- 访问属性
print(my_dog.name)
# 结果:Jack
- 创建多个实例
my_dog = Dog('Jack', 20)
your_dog = Dog('John', 10)
print(f"My dog's name is {my_dog.name} and my age is {my_dog.age}")
my_dog.sit()
print(f"\nYour dog's name is {my_dog.name} and my age is {my_dog.age}")
your_dog.sit()'
结果:
My dog's name is Jack and my age is 20
Jack is now sittingYour dog's name is Jack and my age is 20
John is now sitting'
二、使用类和实例
1. Car 类
可以使用类来模拟现实世界中的很多情景。类编写好后,你的大部分时间将花在使用根据类创建的实例上。你需要完成的首要任务之一是,修改实例的属性。既可以直接修改实例的属性,也可以编写方法以特定的方式进行修改。
定义 __init__() 方法。与前面的 Dog 类中一样,这个方法的第一个形参为 self。此外,这个方法还包含三个形参:make、model 和year。__init__() 方法接受这些形参的值,并将它们赋给根据这个类创建的实例的属性。在创建新的 Car 实例时,需要指定其制造商、型号和生产年份。
定义一个名为 get_descriptive_name() 的方法,它使用属性 year、make 和 model 创建一个对汽车进行描述的字符串,让我们无须分别打印每个属性的值。为了在这个方法中访问属性的值,使用了self.make、self.model 和 self.year。
根据 Car 类创建一个实例,并将其赋给变量 my_new_car。接下来,调用 get_descriptive_ name() 方法,指出我们拥有一辆什么样的汽车:
class Car:def __init__(self, make, model, year):self.make = makeself.model = modelself.year = yeardef get_descriptive_name(self):long_name = f"{self.year} {self.make} {self.model}"return long_name.title()
my_new_car = Car("audi", "a4", 2024)
print(my_new_car.get_descriptive_name())'
结果:2024 Audi A4
'
2. 给属性指定默认值
有些属性无须通过形参来定义,可以在 __init__() 方法中为其指定默
认值。
下面来添加一个名为 odometer_reading 的属性,其初始值总是为 0。我们还添加了一个名为 read_odometer() 的方法,用于读取汽车的里程表:
class Car:def __init__(self, make, model, year):self.make = makeself.model = modelself.year = yearself.odometer_reading = 0def 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", 2024)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()'
结果:
2024 Audi A4
This car has 0 miles on it.
'
3. 修改属性的值
(1)直接修改属性的值
要修改属性的值,最简单的方式是通过实例直接访问它。
class Car:def __init__(self, make, model, year):self.make = makeself.model = modelself.year = yearself.odometer_reading = 0def 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", 2024)
print(my_new_car.get_descriptive_name())
# 修改属性的值
my_new_car.odometer_reading = 100
my_new_car.read_odometer()'
结果:
2024 Audi A4
This car has 100 miles on it.
'
(2)通过方法修改属性的值
有一个替你更新属性的方法大有裨益。这样就无须直接访问属性了,而是可将值传递给方法,由它在内部进行更新。
class Car:def __init__(self, make, model, year):self.make = makeself.model = modelself.year = yearself.odometer_reading = 0def 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):self.odometer_reading = mileage
my_new_car = Car("audi", "a4", 2024)
print(my_new_car.get_descriptive_name())
# 修改属性的值
my_new_car.odometer_reading = 100
my_new_car.update_odometer(my_new_car.odometer_reading)
my_new_car.read_odometer()'
结果:
2024 Audi A4
This car has 100 miles on it.'
(3)通过方法让属性的值递增
有时候需要将属性值递增特定的量,而不是将其设置为全新的值。
新增的方法 increment_odometer() 接受一个单位为英里的数,并将其加到 self.odometer_reading 上。首先,创建一辆二手车my_used_car。然后,调用 update_odometer() 方法并传入23_500,将这辆二手车的里程表读数设置为 23 500。最后,调用 increment_odometer() 并传入 100,以增加从购买到登记期间行驶的 100 英里:
class Car:def __init__(self, make, model, year):self.make = makeself.model = modelself.year = yearself.odometer_reading = 0def 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):self.odometer_reading = mileagedef increment_odometer(self, miles):self.odometer_reading += miles
my_new_car = Car("audi", "a4", 2024)
print(my_new_car.get_descriptive_name())
my_new_car.update_odometer(100)
my_new_car.read_odometer()
my_new_car.increment_odometer(10)
my_new_car.read_odometer()'
结果:
2024 Audi A4
This car has 100 miles on it.
This car has 110 miles on it.
'
三、继承
在编写类时,并非总是要从头开始。如果要编写的类是一个既有的类的特殊版本,可使用继承(inheritance)。当一个类继承另一个类时,将自动获得后者的所有属性和方法。原有的类称为父类(parent class),而新类称为子类(child class)。子类不仅继承了父类的所有属性和方法,还可定义自己的属性和方法。
1. 子类的 __init__() 方法
在既有的类的基础上编写新类,通常要调用父类的 __init__() 方法。这将初始化在父类的__init__() 方法中定义的所有属性,从而让子类也可以使用这些属性。
首先是 Car 类的代码。在创建子类时,父类必须包含在当前文件中,且位于子类前面。接下来,定义子类 ElectricCar。在定义子类时,必须在括号内指定父类的名称。__init__() 方法接受创建 Car 实例所需的信息。
super() 是一个特殊的函数,让你能够调用父类的方法。这行代码让 Python 调用 Car 类的 __init__() 方法,从而让 ElectricCar 实例包含这个方法定义的所有属性。父类也称为超类(superclass),函数名super 由此得名。
为了测试继承能够正确地发挥作用,我们尝试创建一辆电动汽车,但提供的信息与创建燃油汽车时相同。创建 ElectricCar 类的一个实例,并将其赋给变量 my_leaf。这行代码调用 ElectricCar 类中定义的__init__() 方法,后者让 Python 调用父类 Car 中定义的 __init__()方法。
class Car:"""溢出模拟汽车的简单尝试"""def __init__(self, make, model, year):"""初始化描述汽车的属性"""self.make = makeself.model = modelself.year = yearself.odometer_reading = 0def get_descriptive_name(self):"""返回格式规范的描述性名称"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn long_name.title()def read_odometer(self):"""指出汽车的行驶里程"""print(f"This car has {self.odometer_reading} miles on it.")def update_odometer(self, miles):"""将里程表读数设置为给定的值"""self.odometer_reading = milesdef increment_odometer(self, miles):"""让里程表读书增加给定的量"""self.odometer_reading += miles
class ElectricCar(Car):def __init__(self, make, model, year):"""初始化父类的属性"""super().__init__(make, model, year)
my_leaf = ElectricCar('Fuga', 'leaf', 2016)
print(my_leaf.get_descriptive_name())'
结果:
2016 Fuga Leaf
'
2. 给子类定义属性和方法
让一个类继承另一个类后,就可以添加区分子类和父类所需的新属性和新方法了。
class Car:"""溢出模拟汽车的简单尝试"""def __init__(self, make, model, year):"""初始化描述汽车的属性"""self.make = makeself.model = modelself.year = yearself.odometer_reading = 0def get_descriptive_name(self):"""返回格式规范的描述性名称"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn long_name.title()def read_odometer(self):"""指出汽车的行驶里程"""print(f"This car has {self.odometer_reading} miles on it.")def update_odometer(self, miles):"""将里程表读数设置为给定的值"""self.odometer_reading = milesdef increment_odometer(self, miles):"""让里程表读书增加给定的量"""self.odometer_reading += miles
class ElectricCar(Car):def __init__(self, make, model, year):"""初始化父类的属性"""super().__init__(make, model, year)self.battery_size = 90def describe_battery(self):"""打印描述电池容量的信息"""print(f"This car has a {self.battery_size} kWh battery.")my_leaf = ElectricCar('Fuga', 'leaf', 2016)
print(my_leaf.get_descriptive_name())
my_leaf.describe_battery()'
结果:
2016 Fuga Leaf
This car has a 90 kWh battery.
'
添加新属性 self.battery_size,并设置其初始值。根据 ElectricCar 类创建的所有实例都将包含这个属性,但所有的 Car 实例都不包含它。还添加了一个名为 describe_battery() 的方法,用
来打印有关电池的信息。
3. 重写父类中的方法
在使用子类模拟的实物的行为时,如果父类中的一些方法不能满足子类的需求,就可以用下面的办法重写:在子类中定义一个与要重写的父类方法同名的方法。
class Car:"""溢出模拟汽车的简单尝试"""def __init__(self, make, model, year):"""初始化描述汽车的属性"""self.make = makeself.model = modelself.year = yearself.odometer_reading = 0def get_descriptive_name(self):"""返回格式规范的描述性名称"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn long_name.title()def read_odometer(self):"""指出汽车的行驶里程"""print(f"This car has {self.odometer_reading} miles on it.")def update_odometer(self, miles):"""将里程表读数设置为给定的值"""self.odometer_reading = milesdef increment_odometer(self, miles):"""让里程表读书增加给定的量"""self.odometer_reading += milesdef fill_gas_tank(self):"""电动汽车没有油箱"""print("This car does fill its gas tank!")
class ElectricCar(Car):def __init__(self, make, model, year):"""初始化父类的属性"""super().__init__(make, model, year)self.battery_size = 90def describe_battery(self):"""打印描述电池容量的信息"""print(f"This car has a {self.battery_size} kWh battery.")def fill_gas_tank(self):"""电动汽车没有油箱"""print("This car does not fill its gas tank!")
现在,如果有人对电动汽车调用 fill_gas_tank() 方法,Python 将忽略 Car 类中的 fill_gas_tank() 方法,转而运行上述代码。在使用继承时,可让子类保留从父类那里继承的“精华”,重写不需要的“糟粕”。
4. 将实例用作属性
在使用代码模拟实物时,你可能会发现自己给类添加了太多细节:属性和方法越来越多,文件越来越长。在这种情况下,可能需要将类的一部分提取出来,作为一个独立的类。将大型类拆分成多个协同工作的小类,这种方法称为组合(composition)。
将这些属性和方法提取出来,放到一个名为 Battery 的类中,并将一个 Battery 实例作ElectricCar 类的属性:
class Battery:"""初始化电池的属性"""def __init__(self, battery_size=40):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_size = Battery()def describe_battery(self):"""打印描述电池容量的信息"""print(f"This car has a {self.battery_size} kWh battery.")def fill_gas_tank(self):"""电动汽车没有油箱"""print("This car does not fill its gas tank!")my_leaf = ElectricCar('Fuga', 'leaf', 2016)
print(my_leaf.get_descriptive_name())
my_leaf.battery_size.describe_battery()'
结果:
2016 Fuga Leaf
This car has a 40-kwh battery.
'
四、导入类
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 = makeself.model = modelself.year = yearself.odometer_reading = 0def 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 = mileageelse:print("You can't do that!")def increase_odometer(self, miles):"""让里程表读数增加指定的量"""self.odometer_reading += miles
下面来创建另一个文件——my_car.py,在其中导入 Car 类并创建其实例:
from car import Car
my_new_car = Car('subaru', 'outback', 2018)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 1
my_new_car.read_odometer()'
结果:
2018 Subaru Outback
This car has 1 miles on it.
'
2. 在一个模块中存储多个类
car.py
class Car:"""一次简单尝试"""def __init__(self, make, model, year):"""初始化描述汽车的属性"""self.make = makeself.model = modelself.year = yearself.odometer_reading = 0def 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 = mileageelse:print("You can't do that!")def increase_odometer(self, miles):"""让里程表读数增加指定的量"""self.odometer_reading += milesclass Battery:"""一次模拟电动汽车电瓶的操作"""def __init__(self, battery_size=10):self.battery_size = battery_sizedef describe_battery(self):"""打印一条描述电池容量的消息"""print(f"This car has a {self.battery_size}-kWh battery.")def get_range(self):"""打印一条描述电池续航里程的消息"""if self.battery_size == 100:range = 1200elif self.battery_size == 101:range = 120print(f"This car can go about {range} miles on it.")class ElectricCar(Car):"""模拟电动汽车的独特之处"""def __init__(self, make, model, year):"""先初始化父类的属性,再初始化电动汽车特有的属性"""super().__init__(make,model,year)self.battery = Battery()
my_electric_car.py
from car import ElectricCarmy_leaf = ElectricCar('nissan', 'leaf', 2024)
print(my_leaf.get_descriptive_name())
my_leaf.battery.describe_battery()
my_leaf.battery.get_range()'
结果:
2018 Subaru Outback
This car has 1 miles on it.
'
3. 从一个模块中导入多个类
当从一个模块中导入多个类时,用逗号分隔各个类
my_cars.py
from car import Car, ElectricCarmy_new_car = Car('subaru', 'outback', 2018)
print(my_new_car.get_descriptive_name())
my_leaf = ElectricCar('toyota', 'subaru', 2018)
print(my_leaf.get_descriptive_name())'
结果:
2018 Subaru Outback
2018 Toyota Subaru
'
4. 导入整个模块
还可以先导入整个模块,再使用点号访问需要的类。
import car
my_new_car = car.Car('subaru', 'outback', 2018)
print(my_new_car.get_descriptive_name())
my_leaf = car.ElectricCar('toyota', 'subaru', 2018)
print(my_leaf.get_descriptive_name())
5. 导入模块中的所有类
要导入模块中的每个类,可使用下面的语法:
from car import *
五、Python 标准库
Python 标准库是一组模块,在安装 Python 时已经包含在内。你现在已经对函数和类的工作原理有了大致的了解,可以开始使用其他程序员编写好的模块了。你可以使用标准库中的任何函数和类,只需在程序开头添加一条简单的import 语句即可。下面来看看模块 random,它在你模拟很多现实情况时很有用。
>>> from random import randint
>>> randint(1,6)
3
六、类的编程风格
类名应采用驼峰命名法,即将类名中的每个单词的首字母都大写,并且不使用下划线。实例名和模块名都采用全小写格式,并在单词之间加上下划线。
对于每个类,都应在类定义后面紧跟一个文档字符串。这种文档字符串简要地描述类的功能,你应该遵循编写函数的文档字符串时采用的格式约定。每个模块也都应包含一个文档字符串,对其中的类可用来做什么进行描述。
可以使用空行来组织代码,但不宜过多。在类中,可以使用一个空行来分隔方法;而在模块中,可以使用两个空行来分隔类。
当需要同时导入标准库中的模块和你编写的模块时,先编写导入标准库模块的 import 语句,再添加一个空行,然后编写导入你自己编写的模块的import 语句。在包含多条 import 语句的程序中,这种做法让人更容易明白程序使用的各个模块来自哪里。