- 9.1 创建类和实例
- 9.1.1 创建类
- 9.1.2 创建实例
- 9.2 使用类和实例
- 9.2.1 给属性指定默认值
- 9.2.2 直接修改属性的值
- 9.2.3 通过方法修改属性的值
- 9.3 继承
- 9.3.1 创建子类
- 9.3.2 给子类定义属性和方法
- 9.3.3 重写父类的方法
- 9.3.4 将实例作为属性
- 9.4 类模块化
- 9.4.1 创建模块
- 9.4.2 导入整个模块
- 9.4.3 导入模块中的特定类
- 9.4.4 导入模块中的所有类
- 9.4.5 在一个模块中导入另一个模块
- 9.5 Python标准库与类编码风格
- 9.5.1 Python标准库
- 9.5.2 类编码风格
面向对象编程是最有效的软件编写方法之一,在面向对象编程中,编写类来表示现实世界中的事物和情景,并基于这些类来创建对象。编写类时,需要定义某一大类对象所具有的通用行为。根据类来创建对象称为实例化,根据类创建的每个对象都自动地具有这种通用行为,并且可以根据需要赋予每个对象独特的个性。
9.1.1 创建类下面创建一个Dog类,它表示的不是某个特定的小狗,而是任何小狗。对于大多数宠物狗,都有名字、年龄,大多数小狗都会坐下和打滚,因此编写的Dog类将包含以上两项信息和两种行为。
class Dog:
"""模拟小狗"""
def __init__(self, name1, age1):
"""初始化属性name和age"""
self.name = name1
self.age = age1
def sit(self):
"""模拟小狗收到命令时坐下"""
print(f"{self.name}坐下了")
def roll_over(self):
"""模拟小狗收到命令时打滚"""
print(f"{self.name}在打滚")
可以看到,类的定义中没有圆括号,因为要从空白创建一个类。紧接着类的定义,需要编写一个文档字符串对类的功能进行描述。
类中的函数称为方法。
类中的__init__()方法称为构造方法,每当根据类创造对象是,都会自动运行它。注意这个方法的名字开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法的名称冲突。
上例中__init__()方法包含self、name1、age1三个形参,形参self必须放在最前面,它是必不可少的。Python调用这个方法创建实例的时候会自动传入实参self(它是一个指向实例本身的引用,让实例能够访问类中的属性和方法),因此创建实例时只需要给后两个形参提供值。
self.name = name1 获取与形参name1相关联的值,并将其赋给变量name,然后该变量被关联到当前创建的实例;self.age = age1 与之类似。这种可以通过实例访问的变量称为属性。
上例中的sit()方法和roll_over()方法称为普通方法,这些方法执行时不需要额外的信息,因此只有一个形参self。
创建实例、访问属性、调用方法:
my_dog = Dog("Momo",2)
print(my_dog.name)
print(my_dog.age)
my_dog.sit()
my_dog.roll_over()
Momo 2 Momo坐下了 Momo在打滚
访问属性和调用方法时都使用了句点表示法。
9.2 使用类和实例 9.2.1 给属性指定默认值创建实例时,有些属性无需通过形参来定义,可以在构造方法中为其指定默认值。
class Dog:
"""模拟小狗"""
def __init__(self, name1, age1):
"""初始化属性name和age"""
self.name = name1
self.age = age1
self.vaccinations = 0
def sit(self):
"""模拟小狗收到命令时坐下"""
print(f"{self.name}坐下了")
def roll_over(self):
"""模拟小狗收到命令时打滚"""
print(f"{self.name}在打滚")
def read_vaccinations(self):
"""打印小狗已经接种疫苗的次数"""
print(f"{self.name}已接种{self.vaccinations}针疫苗")
my_dog = Dog("Momo",2)
my_dog.read_vaccinations()
Momo已接种0针疫苗9.2.2 直接修改属性的值
可以使用句点表示法直接访问并设置属性的值。
my_dog = Dog("Momo",2)
my_dog.vaccinations = 2
my_dog.read_vaccinations()
Momo已接种2针疫苗9.2.3 通过方法修改属性的值
可以定义一个方法修改属性的值,比如模拟给小狗打疫苗。
class Dog:
"""模拟小狗"""
def __init__(self, name1, age1):
"""初始化属性name和age"""
self.name = name1
self.age = age1
self.vaccinations = 0
def sit(self):
"""模拟小狗收到命令时坐下"""
print(f"{self.name}坐下了")
def roll_over(self):
"""模拟小狗收到命令时打滚"""
print(f"{self.name}在打滚")
def read_vaccinations(self):
"""打印小狗已经接种疫苗的次数"""
print(f"{self.name}已接种{self.vaccinations}针疫苗")
def vaccinate(self):
""""模拟给小狗打疫苗"""
self.vaccinations += 1
my_dog = Dog("Momo",2)
my_dog.vaccinate()
my_dog.read_vaccinations()
Momo已接种1针疫苗
当然,可以给该方法加一个参数,模拟小狗一次打若干针疫苗,然而这在实际意义上是不合理的。但如果参数意义是小狗的饥饱程度,可以写一个类似的带有参数的方法,模拟一次给小狗喂若干食物,或者还可以直接将参数意义改为喂食后的饥饱程度。
9.3 继承编写类时,并非总是要从空白开始,如果要编写的类是另一个现成类的特殊版本,可使用继承。一个类继承另一个类时,将自动获得另一个类的所有属性和方法,原有的类称为父类,新的类称为子类,子类继承父类的所有属性和方法,同时还可以定义自己特有的属性和方法。
9.3.1 创建子类首先创建一个汽车类,在创建它的一个电动汽车子类。
class Car:
"""创建一个汽车类"""
def __init__(self,make,model,year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year= year
self.odometer = 0
def get_descriptive_name(self):
"""返回描述性信息"""
long_name = f"{self.year} {self.make} {self.model}"
return long_name
def read_odometer(self):
"""打印汽车里程信息"""
print(f"里程数:{self.odometer}")
def update_odometer(self,mileage):
"""将里程表读数更新为指定值"""
if mileage >= self.odometer:
self.odometer = mileage
else:
print("不能减少里程")
def gas_tank(self):
"""可以给汽车加油"""
print(f"汽车有油箱")
class ElectricCar(Car):
"""创建一个电动汽车子类"""
def __init__(self,make,model,year):
"""初始化父类的属性"""
super().__init__(make,model,year)
my_tesla = ElectricCar("tesla","model s",2019)
print(my_tesla.get_descriptive_name())
2019 tesla model s
注意:
1、创建子类时,父类必须在当前文件中,且位于子类的前面。
2、定义子类时,必须在圆括号内指明父类的名称。
3、super()函数使得子类可以调用父类(supercalss)的方法,super().__init__()调用了父类的__init__()方法。
可以给子类添加新的属性和方法。
class Car:
--snip--
class ElectricCar(Car):
"""创建一个电动汽车子类"""
def __init__(self,make,model,year):
"""初始化父类的属性,并添加新属性"""
super().__init__(make,model,year)
self.buttery_size = 75
def describe_buttery(self):
print(f"这辆{self.make}的电池容量为{self.buttery_size}kW·h")
my_tesla = ElectricCar("tesla","model s",2019)
print(my_tesla.get_descriptive_name())
my_tesla.describe_buttery()
2019 tesla model s 这辆tesla的电池容量为75kW·h
在实际使用中,如果一个属性或方法是共有的,应该将它加到父类中,子类只包含特有属性和方法的代码。
9.3.3 重写父类的方法对于父类的方法,只要它不符合子类模拟的实物的行为,都可以进行重写。父类的代码中最后一个方法是关于汽车油箱的,但是电动汽车不具有油箱(假设),因此可以重写它。
class ElectricCar(Car):
"""创建一个电动汽车子类"""
def __init__(self,make,model,year):
"""初始化父类的属性,并添加新属性"""
super().__init__(make,model,year)
self.buttery_size = 75
def describe_buttery(self):
print(f"这辆{self.make}的电池容量为{self.buttery_size}kW·h")
def gas_tank(self):
"""电动汽车没有油箱"""
print(f"电动汽车没有油箱")
my_tesla = ElectricCar("tesla","model s",2019)
my_tesla.gas_tank()
电动汽车没有油箱9.3.4 将实例作为属性
使用代码模拟实物时,可能会发现给类添加的细节越来越多,属性和方法的代码越来越长,这时,可以将类的一小部分提取出来作为一个独立的类,即将大型类拆分成多个协同工作的小型类。
我们可以将电动汽车的电池相关的属性和方法提取出来作为一个单独的Buttery类,并将Buttery类的一个实例作为ElectronicCar类的一个属性。
class Car:
--snip--
class Buttery:
"""创建一个电池类"""
def __init__(self,buttery_size=75):
"""电池容量"""
self.buttery_size = buttery_size
def describe_buttery(self):
"""打印电池容量信息"""
print(f"电池容量为{self.buttery_size}kW·h")
class ElectricCar(Car):
"""创建一个电动汽车子类"""
def __init__(self,make,model,year):
"""初始化父类的属性,并添加新属性"""
super().__init__(make,model,year)
self.buttery = Buttery() #这里直接用一个Buttery实例描述电动汽车的电池
def gas_tank(self):
"""电动汽车没有油箱"""
print(f"电动汽车没有油箱")
my_tesla = ElectricCar("tesla","model s",2019)
my_tesla.buttery.describe_buttery()
电池容量为75kW·h
这样看似做了很多额外的工作,但现在继续对电池进行详细描述并不会导致ElectricCar类里面混乱不堪。
9.4 类模块化类也可以被存储在称为模块的独立文件中,使用时再将模块导入到主程序中。
9.4.1 创建模块类名应采用驼峰命名法,即类名中的每个单词的首字母都大写,且不使用下划线,模块名一般都采用类名的小写格式,并且在单词之间加上下划线。使用该类的程序都必须采用更加具体的名称。
↓ car.py
"""一个可用于表示汽车的类""" class Car: --snip--
当然,也可以在一个模块中存储多个类,在一个模块中,可使用两个空行来分隔类。
↓ car.py
"""一组可用于表示燃油汽车和电动汽车的类""" class Car: --snip-- class Buttery: --snip-- class ElectricCar(Car): --snip--
每个模块的开头应当包含一个文档字符串,对其中的类可用于做什么进行简要的描述。
9.4.2 导入整个模块可以使用import语句导入模块,进而在当前程序中使用模块中的代码。
import car
要访问car.py中的类,需要采用句点表示法,即“模块名称.类名称()”的格式。
my_beetle = car.Car("volkswagen","beetle",2019)
my_tesla = car.ElectricCar("tesla","roadster",2019)
9.4.3 导入模块中的特定类
导入模块中的特定类可以采用如下语法:
from car import Car
如果需要导入多个类,可以用逗号分隔。
from car import Car,ElectricCar
此时可以直接访问导入的类。
my_beetle = Car("volkswagen","beetle",2019)
my_tesla = ElectricCar("tesla","roadster",2019)
如果导入的类名字太长,可以使用关键词as指定别名。
from car import ElectricCar as EC9.4.4 导入模块中的所有类
使用星号(*)运算符可以导入模块中的所有类,但不建议这么使用,此处列出仅做了解。
from car import *
由于导入了每个类,因此在访问时可以直接使用名称来调用每个类而无需使用句点表示法。但是如果被导入的模块中有类与当前项目中的类重名,会直接覆盖掉当前项目中的类,引发难以诊断的错误。
需要从一个模块中导入很多类时,最好直接导入整个模块,并采用句点表示法来访问类,这样做可以避免引发名称冲突,而且可以清楚地看出在程序的哪些地方使用了导入的模块。
有时需要将类分散到多个模块中,避免模块太大或同一个模块中存储不相关的类。将类分散到多个模块中存储时,可能会出现一个模块中的类依赖于另一个模块中的类的情况,这时就需要在这个模块中导入另一个模块。
比如说只在car.py中存储Car类:
↓ car.py
"""一个可用于表示汽车的类""" class Car: --snip--
在electric_car.py中存储Buttery类和ElectricCar类,而ElectricCar类需要访问其父类Car类,因此需要在electric_car.py中先导入Car类。如果忘记这项操作,Python将会在我们试图创建ElectricCar实例时报错。
↓ electric_car.py
"""一组可用于表示电动汽车的类""" from car import Car class Buttery: --snip-- class ElectricCar(Car): --snip--
下面演示在项目中分别从这两个模块导入类,并根据需要创建汽车。
from car import Car
from electric_car import ElctricCar
my_beetle = Car("volkswagen","beetle",2019)
my_tesla = ElectricCar("tesla","roadster",2019)
print(my_beetle.get_descriptive_name())
print(my_tesla.get_descriptive_name())
2019 volkswagen beetle 2019 tesla roadster9.5 Python标准库与类编码风格 9.5.1 Python标准库
Python标准库是一组Python自带的模块,可以使用import语句导入标准库中的任何函数和类。另外,可以根据需要在其他地方下载外部模块。
举例:Python标准库中的random模块中的randint()函数和choice()函数。
from random import randint,choice print(randint(1,3)) #随机选取闭区间[1,3]上的整数 foods = ["noodle","rice","hotpot"] print(choice(foods)) #随机选取列表中的一个元素
3 noodle9.5.2 类编码风格
1、类名应采用驼峰命名法,即类名中的每个单词的首字母都大写,且不使用下划线。实例名和模块名都采用小写格式,并且在单词之间加上下划线。
2、类的定义后应当紧跟文档字符串,简要地表述类的功能;每个模块也应当包含一个文档字符串,对其中的类可用于做什么进行描述。
3、可以使用空行来组织代码,但不能滥用。在类中,可使用一个空行来分隔方法;在模块中,可使用两个空行来分隔类。
4、需同时导入标准库中的模块和自己编写的模块时,先导入标准库中的模块,再添加一个空行,然后再导入自己编写的模块。



