Python中的类
面向对象编程语言:面向对象语言(Object-Oriented Language)是一类以对象作为基本程序结构单位的程序设计语言,指用于描述的设计是以对象为核心,而对象是程序运行时刻的基本成分。语言中提供了类、对象、封装、继承、多态等成分,有识认性、多态性、类别性和继承性四个主要特点。
在面向对象编程模型中,程序被设计为由一系列对象组成,每个对象可以具有自己的属性、方法以及引用其他对象的行为。这种方式使得代码更易于理解和维护,因为它允许开发者将实物世界中的概念映射到逻辑上的对象,从而简化复杂的问题。
面向对象编程:面向对象编程(Object-oriented Programing,OOP)是最有效的软件编写方法之一,在面向对象编程中,你编写表示现实世界中的事物和情景中的类(class),并基于这些类来创建对象(Object)。在编写类时,你要定义一批对象都具备的通用行为;在基于类创建对象时,每个对象都自动具备这种通用行为。然后,可根据需要赋予每个对象独特的个性。使用面向对象编程可模拟现实情景,逼真程度到达了令人惊讶的地步。
根据类来创建对象称为实例化,这让你能够使用类的实例(instance)。
学习了类,我们可以像程序员那样看世界,并且真正明白自己编写的代码;不仅是各行代码的作用,还有代码背后更宏大的概念。了解类背后的概率可培养逻辑思维能力,让你能够通过编写程序来解决遇到的几乎任何问题。
9.1 创建和使用类
使用类几乎可以模拟任何东西。
9.1.1 创建Dog类
根据Dog类创建的每个实例都将存储名字和年龄,而且我们会赋予每条小狗坐下(sit())、打滚(roll_over())的能力以及小狗的年龄:
# 创建Dog类
class Dog:
"""模拟小狗的简单尝试"""
def __init__(self,name,age):
"""初始化小狗的姓名和年龄"""
self.name = name
self.age = age
def sit(self):
"""模拟小狗收到命令时就坐下"""
print(f"{self.name}正在坐下。")
def roll_over(self):
"""模拟小狗收到命令时打滚"""
print(f"{self.name}正在打滚。")
def dog_age(self):
print(f"小狗{self.age}了。")
上面创建类的程序中,需要注意的地方有很多,并且这样的结构在这个章节有很多且随处可见。
首先创建一个Dog类,根据约定,在Python中,首字母大写的名称指的是类,因为我们要创建一个全新的类,然后是一个文档字符串,对这个类的功能进行描述。
__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)提供值。
在__init__()方法内定义的两个变量都有前缀self。以self为前缀的变量可供类中的所有方法使用,可以通过类的任意实例来访问。self.name = name 获取与形参name相关联的值,并将其赋给变量name,然后该变量被关联到当前创建的实例。self.age = age 的作用与此类似。
像这样可以通过实例访问的变量称为属性(attribute)。
Dog类还定义了另外两个方法:sit()和roll_over()。由于这些方法执行时不需要额外的信息,因此只有一个形参self。稍后将创建的实例能够访问这些方法,也就是它们都会坐下和打滚。当前sit()和roll_over()所做的有限,只是打印一条信息,指出小狗正在坐下或打滚。但是我们可以拓展这些方法来模拟实际的生活场景:如果这个类属于一个计算机游戏,那么这些方法将包含创建小狗坐下和打滚动画效果的代码;如果这个类是用于控制机器狗的,那么这些方法将让机器狗做出坐下和打滚的动作。
9.1.2 根据类创建实例
可以将类视为有关如何创建实例的说明。例如,Dog类就是一系列说明,让Python知道如何创建表示特定小狗的实例:
# 创建Dog类
class Dog:
"""模拟小狗的简单尝试"""
def __init__(self,name,age):
"""初始化小狗的姓名和年龄"""
self.name = name
self.age = age
def sit(self):
"""模拟小狗收到命令时就坐下"""
print(f"{self.name}正在坐下。")
def roll_over(self):
"""模拟小狗收到命令时打滚"""
print(f"{self.name}正在打滚。")
def dog_age(self):
print(f"小狗{self.age}了。")
"""创建一个特定小狗的实例"""
my_dog = Dog('旺财',8)
print(f"我的狗叫{my_dog.name}。")
print(f"我的狗,今年{my_dog.age}岁了。")
# 运行结果
我的狗叫旺财。
我的狗,今年8岁了。
我们让Python创建一条名字为'旺财'、年龄为8的小狗。遇到这行代码时,Python使用实参'旺财'和8调用Dog类中的方法__init__()。方法__init__()创建一个表示特定小狗的示例,并使用我们提供的值来设置属性name和age。方法__init__()并未显式地包含return语句,但Python自动返回一个表示这条小狗的实例。我们将这个实例存储在变量my_dog中。在这里,命名约定很有用:我们通常可以认为首字母大写的名称(如Dog)指的是类,而小写的名称(如my_dog)指的是根据类创建的实例。
-
访问属性
要访问实例的属性,可使用点号。也就是my_dog.name以及my_dog.age中name和age的值:
点号在Python中非常的有用,这种语法演示了Python如何获取属性的值,在这里,Python先找到实例my_dog,再查找与这个实例相关联的属性name和age。在Dog类中引用这个属性时,使用的是self.name、self.age。
-
调用方法
根据Dog类创建实例后,就能使用点号来调用Dog类中定义的任何方法了。
下面让小狗坐下和打滚,即调用Dog类中定义的方法:
# 让小狗坐下 my_dog.sit() # 让小狗打滚 my_dog.roll_over()
要调用方法,需指定实例名(这里指的是my_dog)和想调用的方法,并且用句点分隔。在遇到代码【my_dog.sit()】时,Python在类Dog中查找方法sit()并运行其代码。Python还会以同样的方式解读代码【my_dog.roll_over()】。进行得到结果,如下:
旺财正在坐下。 旺财正在打滚。
这样的语法非常的有用,如果给属性和方法指定了合适的描述性名称,如name、age、sit()和roll_over(),即便对于从未见过的代码块,我们也能够轻松地推断出它是做什么的。
-
创建多个实例
可按需求根据类创建任意数量的实例。下面再创建一个you_dog的实例,如下:
"""创建一个特定小狗的实例""" my_dog = Dog('旺财',8) print(f"我的狗叫{my_dog.name}。") print(f"我的狗,今年{my_dog.age}岁了。") """调用Dog类中的方法 即让小狗坐下和打滚 """ # 让小狗坐下 my_dog.sit() # 让小狗打滚 my_dog.roll_over() print('---------------------') """创建你的dog,即创建多个实例 """ you_dog = Dog('奥斯卡',4) print(f"你的狗叫{you_dog.name}。") print(f"你的狗今年{you_dog.age}岁了。") # 让你的狗坐下 you_dog.sit() # 让你的狗打滚 you_dog.roll_over()
在这个实例中,我们创建了两个实例,分别叫“旺财”和”奥斯卡“。每条小狗都是一个独立的实例,有自己的一组属性,能够执行相同的操作:
我的狗叫旺财。 我的狗,今年8岁了。 旺财正在坐下。 旺财正在打滚。 --------------------- 你的狗叫奥斯卡。 你的狗今年4岁了。 奥斯卡正在坐下。 奥斯卡正在打滚。
即使给第二条小狗指定同样的名字和年龄,Python也会根据Dog类创建另一个实例,即和上一个实例一模一样的实例。创建任意的实例,Python都会给你一条独立的结果。
9.2 使用类和实例
可以使用类来模拟现实世界中的很多场景。类编写好,你的大部分时间将花在使用根据类创建的实例上,那么首先需要完成的任务之一便是修改实例的属性。既可以直接修改实例的属性,也可以编写方法以特定的方式进行修改。
9.2.1 Car类
编写一个汽车的类,它存储了相关汽车的信息,并提供了一个汇总这些信息的方法:
"""创建一个汽车类,这个类包含了相关汽车的信息"""
class Car:
"""模拟汽车的简单尝试"""
def __init__(self,name,make,model,year,money):
self.name = name
self.make = make
self.model = model
self.year = year
self.money = money
def get_descriptive_information(self):
"""得到车辆信息"""
get_information = f"我新车的名字叫{self.name},{self.make}制造,型号{self.model},产于{self.year},我花了{self.money}元。"
print(get_information)
my_new_car = Car('大众','中国','四驱','2025年3月12日','120000')
my_new_car.get_descriptive_information()
我定义了方法__init__()。与前面的Dog类中一样,这个方法的第一个形参为self;我们还在这个方法中包含了另外五个形参:name、make、model、year以及money。方法__init__()接受这些形参的值,并将它们存储在根据这个类创建的实例的属性中。创建新的Car实例时,我们需要指定车的名字、制造商、型号、生产年份以及购买金额。
接着我定义了一个名为get_descriptive_information()的方法,它使用属性name、make、model、year以及money时创建一个对汽车进行描述的字符串,让我们无需分别打印每个属性的值。为在这个方法中访问属性的值,我们使用了self.name、self.make、self.model、self.year和self.money。然后根据Car类创建了一个实例,并将其存储到变量my_new_car中。接下来,我们调用方法get_descriptive_information(),指出我们拥有的是一辆什么样的汽车:
我新车的名字叫大众,中国制造,型号四驱,产于2025年3月12日,我花了120000元。
9.2.2 给属性指定默认值
有些属性无须通过形参来定义,可以在初始化时给它指定默认值。
接下来,创建一个名为odometer_reading的属性,将它的初始值设置为0,然后再添加一个名为read_odometer的方法,用于读取汽车的里程表。
class Car:
"""模拟汽车的简单尝试"""
def __init__(self, name, make, model, year, money):
self.name = name
self.make = make
self.model = model
self.year = year
self.money = money
# 初始化一个里程数,初始为零
self.odometer_read = 0
def read_odometer(self):
"""输出该汽车已经跑了多少公里"""
print(f"这张{self.name}已经跑了{self.odometer}公里了。")
def get_descriptive_information(self):
"""得到车辆信息"""
get_information = f"我新车的名字叫{self.name},{self.make}制造,型号{self.model},产于{self.year},我花了{self.money}元。"
print(get_information)
my_new_car = Car('大众', '中国', '四驱', '2025年3月12日', '120000')
my_new_car.get_descriptive_information()
my_new_car.read_odometer()
输出结果:
我新车的名字叫大众,中国制造,型号四驱,产于2025年3月12日,我花了120000元。
这张大众已经跑了0公里了。
现在,当Python调用方法初始化方法来创建新实例时,将像前一个示例一样以属性的方式存储车的名字、制造商、型号、生产年份以及购买金额。接下来,Python将创建一个名为odometer_read的属性,并将其初始值设置为0。然后我们还定义了一个名为read_odometer()的方法,它让你能够轻松地获悉这张汽车的跑了多少公里。
9.2.3 修改属性的值
直接修改属性的值
在上面的属性中,车或多或少的都跑了一些距离,而实际没跑的车却很少,所以我们这里就需要修改已经初始化的属性的值。如下:
# 修改属性的值
my_new_car.odometer_read = 36.25
my_new_car.read_odometer()
# 控制台输出的结果
这张大众已经跑了36.25公里了。
这里使用句点号直接访问并设置汽车的里程的属性odometer_read。这行代码让Python在实例my_new_car中找到属性odometer_read,并将其值设置为36.25。
像上面那样,称为直接访问属性,但是在其他时候,需要编写方法来更新属性。
通过方法修改属性的值
让程序为我们更新方法就非常的有用。它不需要直接访问属性进行更新,而是将值传递给方法,由它在内部进行更新。
创建一个update_odometer()的方法:
class Car:
"""模拟汽车的简单尝试"""
def __init__(self, name, make, model, year, money):
self.name = name
self.make = make
self.model = model
self.year = year
self.money = money
self.odometer = 0
def read_odometer(self):
"""输出该汽车已经跑了多少公里"""
print(f"这张{self.name}已经跑了{self.odometer}公里了。")
def update_odometer(self,mileage):
"""创建一个更新里程的方法,且禁止该里程往反方向调"""
if mileage > self.odometer:
self.odometer += mileage
else:
print(f"这张{self.name}不能往反方向变动里程。")
def get_descriptive_information(self):
"""得到车辆信息"""
get_information = f"我新车的名字叫{self.name},{self.make}制造,型号{self.model},产于{self.year},我花了{self.money}元。"
print(get_information)
my_new_car = Car('大众', '中国', '四驱', '2025年3月12日', '120000')
my_new_car.get_descriptive_information()
my_new_car.read_odometer()
# 修改属性的值
my_new_car.odometer = 36.25
my_new_car.read_odometer()
# 程序更新
my_new_car.update_odometer(32)
对Car类所做的唯一修改就是添加了方法update_odometer()。这个方法接受一个里程值,并将其存储到self.odometer中。然后我们调用了update_odometer(),在这个方法中进行了条件判断,即如果传入的数大于初始化的数,就重新赋值,否则就输出,禁止该车往回调:
通过方法让属性的值递增
有时候需要将属性值递增特定的量,而不是将其设置为全新的值。加入我购买了一辆二手车,从这张车购买到现在登记到我的名下,共跑了5000公里。那么就需要一个方法来传递这个增量,并在此基础上增大里程表数,如下:
# 创建Dog类
class Dog:
"""模拟小狗的简单尝试"""
def __init__(self, name, age):
"""初始化小狗的姓名和年龄"""
self.name = name
self.age = age
def sit(self):
"""模拟小狗收到命令时就坐下"""
print(f"{self.name}正在坐下。")
def roll_over(self):
"""模拟小狗收到命令时打滚"""
print(f"{self.name}正在打滚。")
def dog_age(self):
print(f"小狗{self.age}了。")
"""创建一个特定小狗的实例"""
my_dog = Dog('旺财', 8)
print(f"我的狗叫{my_dog.name}。")
print(f"我的狗,今年{my_dog.age}岁了。")
"""调用Dog类中的方法
即让小狗坐下和打滚
"""
# 让小狗坐下
my_dog.sit()
# 让小狗打滚
my_dog.roll_over()
print('---------------------')
"""创建你的dog,即创建多个实例
"""
you_dog = Dog('奥斯卡', 4)
print(f"你的狗叫{you_dog.name}。")
print(f"你的狗今年{you_dog.age}岁了。")
# 让你的狗坐下
you_dog.sit()
# 让你的狗打滚
you_dog.roll_over()
"""创建一个汽车类,这个类包含了相关汽车的信息"""
class Car:
"""模拟汽车的简单尝试"""
def __init__(self, name, make, model, year, money):
self.name = name
self.make = make
self.model = model
self.year = year
self.money = money
self.odometer = 0
def read_odometer(self):
"""输出该汽车已经跑了多少公里"""
print(f"这张{self.name}已经跑了{self.odometer}公里了。")
def update_odometer(self,mileage):
"""创建一个更新里程的方法,且禁止该里程往反方向调"""
if mileage > self.odometer:
self.odometer += mileage
else:
print(f"这张{self.name}不能往反方向变动里程。")
def get_descriptive_information(self):
"""得到车辆信息"""
get_information = f"我新车的名字叫{self.name},{self.make}制造,型号{self.model},产于{self.year},我花了{self.money}元。"
print(get_information)
def increment_odometer(self,miles):
"""通过方法来更改属性的值,即增大其里程表数字"""
self.odometer += miles
my_new_car = Car('大众', '中国', '四驱', '2025年3月12日', '120000')
my_new_car.get_descriptive_information()
my_new_car.read_odometer()
# 修改属性的值
my_new_car.odometer = 36.25
my_new_car.read_odometer()
my_new_car.update_odometer(32)
print('\n-------------分隔线----------------\n')
# 更新该二手车上的里程表数
my_new_car.update_odometer(1000)
# 记录现在这二手车已经跑了多少公里
my_new_car.read_odometer()
# 从我接手到现在,跑了2000公里
my_new_car.increment_odometer(2000)
# 使用通过方法来更改属性值,再次读取这辆车已经跑了多少公里
my_new_car.read_odometer()
新增的方法increment_odometer()接受一个单位为公里的数字,并将其加入到self.odometer中。在上面的例子中我们创建了一辆二手车 — my_used_car。然后我们又调用了update_odometer()方法,传入数值1000,将这辆二手车的里程表读数设置为1036.25。然后我们调用increment_odometer()并传入2000,以增加从购买到登记期间行驶的2000公里。
结果如下:
# 更新该二手车上的里程表数
这张大众已经跑了1036.25公里了。
# 使用通过方法来更改属性值,再次读取这辆车已经跑了多少公里
这张大众已经跑了3036.25公里了。
**注意:**你可以使用类似于上面的方法来控制用户修改属性值(如里程表读数)的方式,但能够访问程序的人都可以通过直接访问属性来将里程表修改为任何值。要确保安全,除了进行类似于前面的基本检查外,还需特别注意细节。
9.3 继承
在编写类时,并非总是要从头开始,如果要编写的类是一个既有的类的特殊版本,可使用继承(inheritance)。当一个类继承另一个类时,将自动获得后者的所有属性和方法。原有的类称父类(parent class),而新类称为子类(child class)。子类不仅可以继承父类的所有属性和方法,还可以定义自己的属性和方法。
9.3.1 子类的初始化init()方法
在既有的类的基础上编写新类,通常要调用父类的初始化init()方法。这将初始化在父类的已定义的所有属性,从而让子类可以使用这些属性。例如,模拟电动汽车,电动汽车是一种特殊的汽车,因此可在之前Car类的基础上创建新类ElectricCar。这样,只需为电动汽车特有的属性和行为编写属性和代码就可以。
class Car:
"""模拟汽车的简单尝试"""
def __init__(self, name, make, model, year, money):
self.name = name
self.make = make
self.model = model
self.year = year
self.money = money
self.odometer = 0
def read_odometer(self):
"""输出该汽车已经跑了多少公里"""
print(f"这张{self.name}已经跑了{self.odometer}公里了。")
def update_odometer(self,mileage):
"""创建一个更新里程的方法,且禁止该里程往反方向调"""
if mileage > self.odometer:
self.odometer += mileage
else:
print(f"这张{self.name}不能往反方向变动里程。")
def get_descriptive_information(self):
"""得到车辆信息"""
get_information = f"我新车的名字叫{self.name},{self.make}制造,型号{self.model},产于{self.year}年,我花了{self.money}元购买。"
print(get_information)
def increment_odometer(self,miles):
"""增大其里程表数字"""
self.odometer += miles
# 编写一个类,来继承Car类
class ElectricCar(Car):
"""电动汽车的独有之处"""
def __init__(self, name, make, model, year, money):
"""初始化父类的属性"""
super().__init__(name,make,model,year,money)
my_leaf = ElectricCar('北汽新能源','中国','EU5','2023','120000')
print(my_leaf.get_descriptive_information())
首先是Car类的代码。创建子类时,父类必须包含在当前文件中,且位于子类前面。我定义了子类ElectricCar。定义子类时,必须在括号内指定父类的名称。初始化方法接受创建Car实例所需的信息。
super()是一个特殊函数,帮助Python将父类和子类关联起来。这行代码让Python调用ElectricCar的父类的方法__init__(),让ElectricCar实例包含父类的所有属性。父类也称为超类(superclass),名称super因此而得名。
9.3.2 给子类定义属性和方法
让一个类继承另一个类后,就可以添加区分子类和父类所需的新属性和方法了。
现在我给电动汽车增加一个特有的属性(电池)以及描述该属性的方法。将存储电池容量,并编写一个方法打印对电池的描述。
class Car:
"""模拟汽车的简单尝试"""
def __init__(self, name, make, model, year, money):
self.name = name
self.make = make
self.model = model
self.year = year
self.money = money
self.odometer = 0
def read_odometer(self):
"""输出该汽车已经跑了多少公里"""
print(f"这张{self.name}已经跑了{self.odometer}公里了。")
def update_odometer(self,mileage):
"""创建一个更新里程的方法,且禁止该里程往反方向调"""
if mileage > self.odometer:
self.odometer += mileage
else:
print(f"这张{self.name}不能往反方向变动里程。")
def get_descriptive_information(self):
"""得到车辆信息"""
get_information = f"我新车的名字叫{self.name},{self.make}制造,型号{self.model},产于{self.year}年,我花了{self.money}元购买。"
print(get_information)
def increment_odometer(self,miles):
"""增大其里程表数字"""
self.odometer += miles
# 编写一个类,来继承Car类
class ElectricCar(Car):
"""电动汽车的独有之处"""
def __init__(self, name, make, model, year, money):
"""初始化父类的属性"""
super().__init__(name,make,model,year,money)
self.battery_size = 72
def describe_battery(self):
"""描述该电池"""
print(f"这个电池是{self.battery_size}V的。")
my_leaf = ElectricCar('北汽新能源','中国','EU5','2023','120000')
print(my_leaf.get_descriptive_information())
my_leaf.describe_battery()
我添加了新属性self.battery_size,并设置其初始值(如72)。根据ElectricCar类创建的所有实例都将包含这个属性,但所有Car实例都不包含它。我还添加了一个名为describe_battery()的方法,它打印有关电瓶的信息。我们调用这个方法时,将看到一条电动汽车特有的描述:
这个电池是72V的。
9.3.3 重写父类中的方法
在使用子类模拟的实物的行为时,如果父类中的方法不能满足子类的需求,那么我们就可以用特定的方法进行重写父类中的方法:在子类中定义一个与父类一模一样的方法。那么这样,Python将忽略这个父类方法,只关注你在子类中定义的方法。
9.3.4 将实例用作属性
在使用代码模拟实物时,你可能会发现自己给类添加了太多细节:属性和方法越来越多,文件越来越大。在这种情况下,可能需要将类的一部分提取出来,作为一个独立的类,将大型类拆分为多个协同工作的小类,这种方法称为组合(composition)。
例如:在不断给Electric类添加细节时,我们可能会发现其中包含了很多专门针对汽车电池的属性和方法。在这种情况下,可将这些属性和方法提取出来,放到一个名为Battery的类中,并将一个Battery实例作为ElectricCar类的属性。
新建Python文件inheritanceClass.py并将书写父类和子类:
class Car:
"""模拟汽车的简单尝试"""
def __init__(self, name, make, model, year, money):
self.name = name
self.make = make
self.model = model
self.year = year
self.money = money
self.odometer = 0
def read_odometer(self):
"""输出该汽车已经跑了多少公里"""
print(f"这张{self.name}已经跑了{self.odometer}公里了。")
def update_odometer(self,mileage):
"""创建一个更新里程的方法,且禁止该里程往反方向调"""
if mileage > self.odometer:
self.odometer += mileage
else:
print(f"这张{self.name}不能往反方向变动里程。")
def get_descriptive_information(self):
"""得到车辆信息"""
# get_information = f"我新车的名字叫{self.name},{self.make}制造,型号{self.model},产于{self.year}年,我花了{self.money}元购买。"
# print(get_information)
print(f"我新车的名字叫{self.name},{self.make}制造,型号{self.model},产于{self.year}年,我花了{self.money}元购买。")
def increment_odometer(self,miles):
"""增大其里程表数字"""
self.odometer += miles
# 编写一个类,来继承Car类
class ElectricCar(Car):
"""电动汽车的独有之处"""
def __init__(self, name, make, model, year, money):
"""初始化父类的属性"""
super().__init__(name,make,model,year,money)
self.battery_size = 72
def describe_battery(self):
"""描述该电池"""
print(f"这个电池是{self.battery_size}V的。")
再创建一个Python文件【batteryClass.py】并新建一个Battery类:
from inheritanceClass import Car
class Battery:
"""在不断给Electric类添加细节时,我们可能会发现其中包含了很多专门针对汽车电池的属性和方法。
在这种情况下,可将这些属性和方法提取出来,放到一个名为Battery的类中,并将一个Battery实例作为ElectricCar类的属性。"""
def __init__(self, size=72):
self.size = size
def describe_battery(self):
"""描述该电池"""
print(f"这个电池是{self.size}V的。")
def get_range(self):
"""写一个方法,来得到不同电池可以跑多少公里"""
if self.size == 72:
return 230
elif self.size == 60:
return 190
else:
return 130
class ElectricCar(Car):
"""继承 inheritanceClass 中的 Car 类"""
def __init__(self, name, make, model, year, money):
"""初始化父类的属性,再初始化电动汽车特有的属性"""
super().__init__(name, make, model, year, money)
self.battery_size = Battery()
def get_car_name(self):
"""写一个方法来获取汽车的名称"""
return self.name
my_car = ElectricCar('大众', '中国制造', '四驱', '2025', 53000)
print(my_car.get_descriptive_information())
my_car.battery_size.describe_battery()
# 调用方法
print(f"这辆{my_car.get_car_name()},可以跑{my_car.battery_size.get_range()}公里了。")
输出结果:
我新车的名字叫大众,中国制造制造,型号四驱,产于2025年,我花了53000元购买。
这个电池是72V的。
这辆大众,可以跑230公里了。
解释:
我们定义了一个名为Battery的新类,它没有继承任何类。方法__init__()除self外,还有另一个形参size。这个形参是可选的:如果没有给它提供值,电瓶容量将被设置为72。方法describe_battery()也移到了这个类中。在ElectricCar类中,我们添加了一个名为self.battery_size的属性。这行代码让Python创建一个新的Battery实例(由于没有指定电池的伏数,因此为默认值72),并将该实例存储在属性self.battery_size中。每当方法__init__()被调用时,都将执行该操作;因此现在每个ElectricCar实例都包含一个自动创建的Battery实例。我们创建一辆电动汽车,并将其存储在变量my_car中。要描述电瓶时,需要使用电动汽车的属性battery_size:
特殊说明:
这行代码【my_car.battery_size.describe_battery()】让Python在实例my_car中查找属性battery_size,并对存储在该属性中的Battery实例调用describe_battery()方法。
my_car.battery_size.describe_battery()
新增方法get_range()做了一些简单的分析:如果电瓶的容量为72V,它就将续航里程设置为230公里;如果容量为60V,就将续航里程设置为190公里,其他电池的续航里程则为130公里。为使用这个方法,我们也通过汽车的属性battery_size来调用它。
9.3.5 自我修养
解决上述问题时,你从较高的逻辑层面(而不是语法层面)考虑;你考虑的不是Python,而是如何使用代码来表示实物。到达这种境界后,你经常会发现,现实世界的建模方法并没有对错之分。有些方法的效率更高,但要找出效率最高的表示法,需要经过一定的实践。只要代码像你希望的那样运行,就说明你做得很好!即便你发现自己不得不多次尝试使用不同的方法来重写类,也不必气馁;要编写出高效、准确的代码,都得经过这样的过程。
9.4 导入类
随着不断地给类添加功能,文件可能会变得很长,很大,即使妥善地使用了继承和组合,结果也是一样的。遵循Python的整体概念,那么就应该让文件尽量整洁。Python在这方面提供了很牛的帮助,也就是允许你将类存储在模块中,然后在主程序中导入所需的模块。
9.4.1 导入单个类
导入类是一种高效的编程方式,如果这个程序包含整个Class类,那么它会非常的长,我们通过这个类移到另一个模块中并导入该模块,依然可以使用其所有的功能,这样就可以使主程序变得更加整洁易读。这还能够将大部分逻辑存储到独立的文件中。在确定类能像我们期待的那样工作后,就可以不管这些文件,专注于主程序的高级逻辑了。如下:
在文件car.py中书写类:
class Car:
"""车的基本信息"""
def __init__(self, name, make, model, year, money):
"""初始化汽车的属性"""
self.name = name
self.make = make
self.model = model
self.year = year
self.money = money
"""初始化该车跑了多少公里"""
self.odometer = 0
def read_odometer(self):
"""输出该汽车已经跑了多少公里"""
print(f"这张{self.name}已经跑了{self.odometer}公里了。")
def update_odometer(self, mileage):
"""创建一个更新里程的方法,且禁止该里程往反方向调"""
if mileage > self.odometer:
self.odometer += mileage
else:
print(f"这张{self.name}不能往反方向变动里程。")
def get_descriptive_information(self):
"""得到车辆信息"""
get_information = f"我新车的名字叫{self.name},{self.make}制造,型号{self.model},产于{self.year},我花了{self.money}元。"
print(get_information)
def increment_odometer(self, miles):
"""增大其里程表数字"""
self.odometer += miles
新建一个类my_car.py,并导入模块包car:
from car import Car
my_car = Car('大众','中国','四驱,净白色','2024','130000')
# 得到该车的信息
my_car.get_descriptive_information()
# 得到该车跑了多少公里了
my_car.read_odometer()
# 重新给该车赋一个公里数
my_car.odometer = 150
# 打印该车的公里数
my_car.read_odometer()
# 更新该车的公里数
my_car.update_odometer(160)
# 再次打印该车的公里数
my_car.read_odometer()
# 调用方法increment_odometer()
my_car.increment_odometer(3200)
# 再次打印出调用increment_odometer()方法后的执行结果
my_car.read_odometer()
导入类是一种高效的编程方式。如果这个程序中包含整个Class类,那么它会很长很长。通过将这个类移到一个模块中并导入该模块,依然可以使用它的所有功能,但主程序文件就变得更加简洁易读。这还让你能够将大部分逻辑存储在独立的文件中。在确定类能像你希望的那样工作后,就可以不管这些文件,专注于主程序的高级逻辑了。
9.4.2 在一个模块中存储多个类
模块:
模块的作用:
- 代码复用:将常用的功能封装到模块中,可以在多个程序中重复使用。
- 命名空间管理:模块可以避免命名冲突,不同模块中的同名函数或变量不会互相干扰。
- 代码组织:将代码按功能划分到不同的模块中,使程序结构更清晰。
语法:
1、Python 的 from 语句让你从模块中导入一个指定的部分到当前命名空间中,语法如下:
from … import 语句
2、把一个模块的所有内容全都导入到当前的命名空间也是可行的,只需使用如下声明:
from … import * 语句
一个模块中存储多个类:
调用子类:
9.4.3 导入整个模块以及所有类
导入整个模块,语法:
import modul_name
导入模块中的所有类:
from modul_name import *
导入模块:
9.4.4 找到合适的工作流程
在组织大型项目的代码方面,Python提供了很多的选项,熟悉所有这些选项就很重要,这样才能确定那种项目组织方式是最好的,才能理解别人开发的项目。
一开始应让代码结构尽量简单,首先尝试在一个文件中完成所有的工作,确定一切都能正常运行后,再将类移到独立的模块中。如果喜欢模块和文件的交互方式,可在项目开始时就尝试将类存储到模块中。先找出让你能够编写出可行代码的方式,再尝试让代码更加整洁。
9.5 Python标准库
Python标准库是一组模块,在安装Python时就已经包含在内,目前我们已经对函数和类的工作原理有了大致的了解,可以开始使用其他程序员编写好的模块,且可以使用标准库中的任何类和方法,只需要在开头添加一条简单的import语句就可以。
Python 标准库非常庞大,所提供的组件涉及范围十分广泛,使用标准库我们可以让您轻松地完成各种任务。常见的标准库如下:
9.5.1 操作系统接口
操作系统库:OS
# 操作系统接口
import os as sos
# 获取当前工作目录
current_dir = sos.getcwd()
print("当前工作目录:", current_dir)
# 列出目录下的文件
files = sos.listdir(current_dir)
print("目录下的文件:", files)
&esmp;运行结果:
当前工作目录: F:\Python\PycharmProjects\pythonProject\basicKnowledge\class
目录下的文件: ['batteryClass.py', 'car.py', 'createClass.py', 'inheritanceClass.py', 'my_car.py', 'my_electrice_car.py', 'standardStock.py', '__pycache__']
解释:
在Python中,**kwargs是用于处理函数中可变数量关键字参数的机制,以下是详细解析:
核心概念
-
功能
kwargs
允许函数接收任意数量的关键字参数**(即形如key=value
的参数),并将它们收集到一个字典中。字典的键是参数名(字符串),值为传递的值。 -
语法
在函数定义时使用**kwargs
(变量名可自定义,但习惯用kwargs
):python
复制
def func(**kwargs): # kwargs 是一个字典,包含所有传入的关键字参数
使用场景
-
函数定义时:收集未明确声明的关键字参数。
def example(name, **kwargs): print(f"Name: {name}") for key, value in kwargs.items(): print(f"{key}: {value}") example("Alice", age=25, city="New York") # 输出: # Name: Alice # age: 25 # city: New York
-
函数调用时:解包字典为关键字参数。
def connect(host, port): print(f"Connecting to {host}:{port}") params = {"host": "localhost", "port": 8080} connect(**params) # 等效于 connect(host="localhost", port=8080)
规则与注意事项
-
参数顺序
在函数定义中,参数需按以下顺序排列:def func(positional_args, *args, keyword_args, **kwargs): # positional_args: 位置参数 # *args: 收集额外位置参数(元组) # keyword_args: 关键字参数(可带默认值) # **kwargs: 收集额外关键字参数(字典)
-
命名冲突
-
明确声明的参数名不会出现在
kwargs
中。 -
重复传递同名参数会引发错误:
def test(a, **kwargs): pass test(1, a=2) # 错误:参数 a 被重复赋值
-
-
灵活性
**kwargs
使得函数能够灵活扩展,无需预先定义所有可能的参数,常用于装饰器、类继承等场景。
示例代码
def register_user(email, **user_info):
user_data = {"email": email}
user_data.update(user_info)
print("Registered user:", user_data)
register_user("alice@example.com", name="Alice", age=30, country="USA")
# 输出:Registered user: {'email': 'alice@example.com', 'name': 'Alice', 'age': 30, 'country': 'USA'}
总结
-
**kwargs
的作用:在函数定义时收集多余关键字参数(字典),或在调用时解包字典为参数。 -
与
*args
的区别:*args
处理位置参数(元组),**kwargs
处理关键字参数(字典)。 -
增强函数灵活性,适用于参数数量不确定的场景。
在 Python 中,pass
是一个空操作语句,它的存在是为了语法完整性而设计的。当 Python 解释器遇到 pass
时,会直接跳过它,不做任何事情。
主要用途
-
占位符
当你需要定义一个函数、类、条件分支或循环等代码块,但暂时不想(或不需要)写具体逻辑时,可以用pass
避免语法错误。Python 要求代码块中至少有一条语句,而pass
充当了“临时填充物”。def my_function(): pass # 先占位,稍后实现具体逻辑 class MyClass: pass # 暂时留空,后续补充方法或属性 if condition: pass # 条件分支暂时不处理 else: print("其他情况")
-
保持代码结构清晰
在复杂的逻辑中(如多条件分支),明确标注某些情况暂时无需处理,增强代码可读性。 -
抽象基类或接口
在定义基类时,可以用pass
表示默认不强制子类实现某些方法(但更常见的做法是用raise NotImplementedError
强制子类实现)。
与其他关键字的区别
pass
vs...
(Ellipsis)
...
也可以作为占位符,但更多用于特殊场景(如类型注解、NumPy 多维数组)。在普通代码中,pass
更直观。pass
vscontinue
/break
/return
continue
跳过当前循环的剩余代码,进入下一次迭代。break
直接终止循环。return
退出函数并返回值。pass
单纯表示“无操作”,不影响程序流程。
示例
# 示例1:定义一个空类
class Animal:
pass # 后续可以添加属性和方法
# 示例2:条件分支占位
age = 18
if age >= 18:
pass # 成年人无需特殊处理
else:
print("未成年人提示")
# 示例3:函数占位
def future_feature():
pass # 待实现的新功能
总结
pass
是 Python 中一个简单的语法占位符,用于保证代码结构完整。它在开发初期或设计模式中非常有用,但在最终代码中应逐步替换为实际逻辑。
其他标准模块
Python 本身带着一些标准的模块库,在 Python 库参考文档中将会介绍到(就是后面的"库参考文档")。
模块名 | 功能描述 |
---|---|
math | 数学运算(如平方根、三角函数等) |
os | 操作系统相关功能(如文件、目录操作) |
sys | 系统相关的参数和函数 |
random | 生成随机数 |
datetime | 处理日期和时间 |
json | 处理 JSON 数据 |
re | 正则表达式操作 |
collections | 提供额外的数据结构(如 defaultdict 、deque ) |
itertools | 提供迭代器工具 |
functools | 高阶函数工具(如 reduce 、lru_cache ) |
# 操作系统接口
import os as sos
# 数学
import math as mt
# 获取当前工作目录
current_dir = sos.getcwd()
print("当前工作目录:", current_dir)
# 列出目录下的文件
files = sos.listdir(current_dir)
print("目录下的文件:", files)
print(mt.log(2,3))
在本章中,你学习了:
如何编写类;如何使用属性在类中存储信息,以及如何编写方法,以让类具备所需的行为;如何编写方法__init__()
,以便根据类创建包含所需属性的实例。你见识了如何修改实例的属性——包括直接修改以及通过方法进行修改。
你还了解了:
使用继承可简化相关类的创建工作;将一个类的实例用作另一个类的属性可让类更简洁。你了解到,通过将类存储在模块中,并在需要使用这些类的文件中导入它们,可让项目组织有序。你学习了Python标准库,并见识了一个使用模块collections中的OrderedDict
类的示例。最后,你学习了编写类时应遵循的Python约定。