Python--类

目录

1、创建和使用类

1.1 创建Dog类

1.2 根据类创建实例

1.2.1 访问属性

1.2.2 调用方法

1.2.3 创建多个实例

2、使用类和实例

2.1 Car 类

2.2 属性指定默认值

2.3 修改属性的值

2.3.1 直接修改属性的值

2.3.2 通过方法修改属性的值

2.3.3 通过方法让属性的值递增

3、继承

3.1 子类的 __init__() 方法

3.2 子类定义属性和方法

3.3 重写父类中的方法

3.4 将实例作为属性

3.5 模拟实物

4、导入类

4.1 导入单个类

4.2 在一个模块中存储多个类

4.3 一个模块导入多个类

4.4 导入整个模块

4.5 导入模块中的所有类

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

4.7 使用别名

4.8 找到合适的工作流程

5、Python标准库

6、类的编写风格


在⾯向对象编程中,你编写表⽰现实世界中的事物和情 景的类(class),并基于这些类来创建对象(object)。

在编写类时,

你要定义⼀批对象都具备的通⽤⾏为。在基于类创建对象时,每个对 象都⾃动具备这种通⽤⾏为。然后,你可根据需要赋予每个对象独特 的个性。

根据类来创建对象称为实例化,这让你能够使⽤类的实例(instance)。

1、创建和使用类

使⽤类⼏乎可以模拟任何东⻄。

1.1 创建Dog类

根据 Dog 类创建的每个实例都将存储名字和年龄,⽽且我们会赋予每条⼩ 狗坐下(sit())和打滚(roll_over())的能⼒:

class Dog:
 """⼀次模拟⼩狗的简单尝试"""
def __init__(self, name, age):
 """初始化属性 name 和 age"""
self.name = name
 self.age = age
def sit(self):
 """模拟⼩狗收到命令时坐下"""
 print(f"{self.name} is now sitting.")
def roll_over(self):
 """模拟⼩狗收到命令时打滚"""
 print(f"{self.name} rolled over!")

⾸先,定义⼀个名为 Dog 的类。根 据约定,在 Python 中,⾸字⺟⼤写的名称指的是类。因为这是我们创建的全新的类,所以定义时不加括号。然后是⼀个⽂档字串,对这个类的功 能做了描述。

__init__() ⽅法

类中的函数称为⽅法。

_init__()是 ⼀个特殊⽅法,每当你根据 Dog 类创建新实例时,Python 都会⾃动运⾏ 它。在这个⽅法的名称中,开头和末尾各有两个下划线,这是⼀种约定,

旨在避免 Python 默认⽅法与普通⽅法发⽣名称冲突。务必确保 __init__() 的两边都有两个下划线,否则当你使⽤类来创建实例时,将 不会⾃动调⽤这个⽅法,进⽽引发难以发现的错误。

我们将 __init__() ⽅法定义成包含三个形参:self、name 和 age。在 这个⽅法的定义中,形参 self 必不可少,⽽且必须位于其他形参的前 ⾯。

当 Python 调⽤这个⽅ 法来创建 Dog 实例时,将⾃动传⼊实参 self。每个与实例相关联的⽅法 调⽤都会⾃动传递实参 self,该实参是⼀个指向实例本⾝的引⽤,让实例 能够访问类中的属性和⽅法。

在 __init__() ⽅法内定义的两个变量都有前缀 self。以self 为前缀的变量可供类中的所有⽅法使⽤,可以通过类的任意实例来访问。self.name = name 获取与形参name 相关联的值,并将其赋给变量 name,然后该变量被关联到当前创建的实例。像这样可通过实例访问的变量称为属性(attribute)

Dog 类还定义了另外两个⽅法:sit() 和 roll_over()。由于 这些⽅法执⾏时不需要额外的信息,因此只有⼀个形参 self

1.2 根据类创建实例

可以将类视为有关如何创建实例的说明。

my_dog = Dog('Willie', 6)
❷ print(f"My dog's name is {my_dog.name}.")
❸ print(f"My dog is {my_dog.age} years old.")

这⾥使⽤的是上⼀个⽰例中编写的 Dog 类。我们让 Python 创建⼀条名字为 'Willie'、年龄为 6 的⼩狗。

1.2.1 访问属性

要访问实例的属性,可使⽤点号。在❷处,编写如下代码来访问 my_dog 的属性 name 的值:

my_dog.name

1.2.2 调用方法

根据 Dog 类创建实例后,就能使⽤点号来调⽤ Dog 类中定义的任何⽅

法了。下⾯让⼩狗坐下和打滚:

class Dog: 
--snip-- 
my_dog = Dog('Willie', 6) 
my_dog.sit() 
my_dog.roll_over()

1.2.3 创建多个实例

可按需求根据类创建任意数量的实例。下⾯再创建⼀个名为 your_dog 的⼩狗实例:

class Dog:
 --snip--
my_dog = Dog('Willie', 6)
your_dog = Dog('Lucy', 3)
print(f"My dog's name is {my_dog.name}.")
print(f"My dog is {my_dog.age} years old.")
my_dog.sit()
print(f"\nYour dog's name is {your_dog.name}.")
print(f"Your dog is {your_dog.age} years old.")
your_dog.sit()

2、使用类和实例

可以使⽤类来模拟现实世界中的很多情景。类编写好后,你的⼤部分时间 将花在使⽤根据类创建的实例上。

2.1 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 = 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())

2.2 属性指定默认值

有些属性⽆须通过形参来定义,可以在 __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):
 --snip--
❷ 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()

2.3 修改属性的值

2.3.1 直接修改属性的值

要修改属性的值,最简单的⽅式是通过实例直接访问它。下⾯的代码 直接将⾥程表读数设置为 23:

class Car:
 --snip--
my_new_car = Car('audi', 'a4', 2024)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()

2.3.2 通过方法修改属性的值

这样⽆须直接访问属性, ⽽是可将值传递给⽅法,由它在内部进⾏更新。

class Car:
 --snip--
 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.update_odometer(23)
 my_new_car.read_odometer()

还可以对 update_odometer() ⽅法进⾏扩展,使其在修改⾥程表读 数时做些额外的⼯作。下⾯来添加⼀些逻辑,禁⽌将⾥程表读数往回 调:

 class Car:
 --snip--
 def update_odometer(self, mileage):
 """
 将⾥程表读数设置为指定的值
 禁⽌将⾥程表读数往回调
 """
❶ if mileage >= self.odometer_reading:
 self.odometer_reading = mileage
 else:
❷ print("You can't roll back an odometer!")

2.3.3 通过方法让属性的值递增

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

 class Car:
 --snip--
 def update_odometer(self, mileage):
 --snip--
 def increment_odometer(self, miles):
 """让⾥程表读数增加指定的量"""
 self.odometer_reading += miles
❶ my_used_car = Car('subaru', 'outback', 2019)
 print(my_used_car.get_descriptive_name())
❷ my_used_car.update_odometer(23_500)
 my_used_car.read_odometer()
❸ my_used_car.increment_odometer(100)
 my_used_car.read_odometer()

注意:虽然可以使⽤类似于上⾯的⽅法来控制⽤户修改属性值 (如⾥程表读数)的⽅式,但能够访问程序的⼈都能直接访问属 性将⾥程表修改为任意的值。要确保安全,除了进⾏类似于前⾯ 的基本检查以外,还需要极度关注细节。

3、继承

当⼀个类继承另⼀个类时,将⾃动获 得后者的所有属性和⽅法。原有的类称为⽗类(parent class),⽽新类称为 ⼦类(child class)。⼦类不仅继承了⽗类的所有属性和⽅法,还可定义⾃ ⼰的属性和⽅法。

3.1 子类的 __init__() 方法

在既有的类的基础上编写新类,通常要调⽤⽗类的 __init__() ⽅法。这 将初始化在⽗类的 __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 = 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 = mileage
 else:
 print("You can't roll back an odometer!")
 def 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('nissan', 'leaf', 2024)
 print(my_leaf.get_descriptive_name())

super() 是⼀个特殊的函数,让你能够调⽤⽗类的⽅法(⻅❹)。。⽗类也称为超类(superclass),函数名 super 由此得名。

3.2 子类定义属性和方法

让⼀个类继承另⼀个类后,就可以添加区分⼦类和⽗类所需的新属性和新 ⽅法了。

 class Car:
 --snip--
 class ElectricCar(Car):
 """电动汽⻋的独特之处"""
 def __init__(self, make, model, year):
 """
 先初始化⽗类的属性,再初始化电动汽⻋特有的属性
 """
 super().__init__(make, model, year)
❶ self.battery_size = 40
❷ def describe_battery(self):
 """打印⼀条描述电池容量的消息"""
 print(f"This car has a {self.battery_size}-kWh battery.")
 my_leaf = ElectricCar('nissan', 'leaf', 2024)
 print(my_leaf.get_descriptive_name())
 my_leaf.describe_battery()

3.3 重写父类中的方法

在使⽤⼦类模拟的实物的⾏为时,如果⽗类中的⼀些⽅法不能满⾜⼦类的 需求,就可以⽤下⾯的办法重写:在⼦类中定义⼀个与要重写的⽗类⽅法 同名的⽅法。

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

现在,如果有⼈对电动汽⻋调⽤ fill_gas_tank() ⽅法,Python 将忽略 Car 类中的 fill_gas_tank() ⽅法,转⽽运⾏上述代码。

3.4 将实例作为属性

属性和 ⽅法越来越多,⽂件越来越⻓。在这种情况下,可能需要将类的⼀部分提 取出来,作为⼀个独⽴的类。将⼤型类拆分成多个协同⼯作的⼩类,这种 ⽅法称为组合(composition)。

 class Car:
 --snip--
 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 = Battery()
 my_leaf = ElectricCar('nissan', 'leaf', 2024)
 print(my_leaf.get_descriptive_name())
 my_leaf.battery.describe_battery()

。下⾯再给 Battery 类添加⼀个⽅法,

它根据电池容量报告汽⻋的续航⾥程:

class Car: 
--snip-- 
 class Battery: 
--snip-- 
def get_range(self): 
"""打印⼀条消息,指出电池的续航⾥程""" 
if self.battery_size == 40: 
range = 150 
elif self.battery_size == 65: 
range = 225print(f"This car can go about {range} miles on a full 
charge.") 
 class ElectricCar(Car): 
--snip-- 
 my_leaf = ElectricCar('nissan', 'leaf', 2024) 
 print(my_leaf.get_descriptive_name()) 
 my_leaf.battery.describe_battery() 

my_leaf.battery.get_range() 新增的⽅法 get_range() 做了⼀些简单的分析:如果电池的容量为 40 千 ⽡时,就将续航⾥程设置为 150 英⾥;如果容量为 65 千⽡时,就将续航⾥ 程设置为 225 英⾥。然后,它会报告这个值。为了使⽤这个⽅法,也需要 通过汽⻋的属性 battery 来调⽤(⻅❶)。 输出已经可以根据电池的容量显⽰对应的续航⾥程了:

2024 Nissan Leaf 
This car has a 40-kWh battery. 
This car can go about 150 miles on a full charge.

3.5 模拟实物

在模拟较复杂的事物(如电动汽⻋)时,需要思考⼀些有趣的问题。

续航 ⾥程是电池的属性还是汽⻋的属性呢?

如果只描述⼀辆汽⻋,将 get_range() ⽅法放在 Battery 类中也许是合适的,

但如果要描述⼀家 汽⻋制造商的整条产品线,也许应该将 get_range() ⽅法移到ElectricCar 类中。

在这种情况下,get_range() 依然根据电池容量来 确定续航⾥程,但报告的是⼀款汽⻋的续航⾥程。

也可以这样做:仍将 get_range() ⽅法留在 Battery 类中,但向它传递⼀个参数,如 car_model。此时get_range() ⽅法将根据电池容量和汽⻋型号报告 续航⾥程。

这让你进⼊了程序员的另⼀个境界:在解决上述问题时,从较⾼的逻辑层 ⾯(⽽不是语法层⾯)思考。你考虑的不是 Python,⽽是如何使⽤代码来 表⽰实际事物。达到这种境界后,你会经常发现,对现实世界的建模⽅法 没有对错之分。有些⽅法的效率更⾼,但要找出效率最⾼的表⽰法,需要⼀定的实践。

4、导入类

随着不断地给类添加功能,⽂件可能变得很⻓,遵循 Python 的整体理念,应该让⽂件尽量整洁。

4.1 导入单个类

将 Car 类存储在⼀个名 为 car.py 的模块中,该模块将覆盖前⾯的⽂件 car.py。从现在开始,使⽤该 模块的程序都必须使⽤更具体的⽂件名,如 my_car.py。下⾯是模块 car.py,其中只包含 Car 类的代码:

 """⼀个⽤来表⽰汽⻋的类"""
 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 = 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 = mileage
     else:
     print("You can't roll back an odometer!")
     def increment_odometer(self, miles):
     """让⾥程表读数增加指定的量"""
     self.odometer_reading += miles

下⾯来创建另⼀个⽂件——my_car.py,在其中导⼊ Car 类并创建其实例:

 from car import Car
 my_new_car = Car('audi', 'a4', 2024)
 print(my_new_car.get_descriptive_name())
 my_new_car.odometer_reading = 23
 my_new_car.read_odometer()

import 语句让 Python 打开模块car 并导⼊其中的 Car 类。

4.2 在一个模块中存储多个类

尽管同⼀个模块中的类之间应该存在某种相关性,但其实可根据需要在 ⼀个模块中存储任意数量的类。

"""⼀组⽤于表⽰燃油汽⻋和电动汽⻋的类"""
class Car:
 --snip--
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.")
 def get_range(self):
 """打印⼀条描述电池续航⾥程的消息"""
 if self.battery_size == 40:
 range = 150
 elif self.battery_size == 65:
 range = 225
 print(f"This car can go about {range} miles on a full charge.")
class ElectricCar(Car):
 """模拟电动汽⻋的独特之处"""
 def __init__(self, make, model, year):
 """
 先初始化⽗类的属性,再初始化电动汽⻋特有的属性
 """
 super().__init__(make, model, year)
 self.battery = Battery()

现在,可以新建⼀个名为 my_electric_car.py 的⽂件,导⼊ ElectricCar 类,并创建⼀辆电动汽⻋了:

from car import ElectricCar
my_leaf = ElectricCar('nissan', 'leaf', 2024)
print(my_leaf.get_descriptive_name())
my_leaf.battery.describe_battery()
my_leaf.battery.get_range()

4.3 一个模块导入多个类

可以根据需要在程序⽂件中导⼊任意数量的类。

from car import Car, ElectricCar
 my_mustang = Car('ford', 'mustang', 2024)
 print(my_mustang.get_descriptive_name())
 my_leaf = ElectricCar('nissan', 'leaf', 2024)
 print(my_leaf.get_descriptive_name())

4.4 导入整个模块

还可以先导⼊整个模块,再使⽤点号访问需要的类。

下⾯的代码导⼊整个 car 模块,并创建⼀辆燃油汽⻋和⼀辆电动汽⻋:

❶ import car
❷ my_mustang = car.Car('ford', 'mustang', 2024)
 print(my_mustang.get_descriptive_name())
❸ my_leaf = car.ElectricCar('nissan', 'leaf', 2024)
 print(my_leaf.get_descriptive_name())

4.5 导入模块中的所有类

要导⼊模块中的每个类,可使⽤下⾯的语法:

from module_name import *

不推荐这种导⼊⽅式,原因有⼆。

  • 第⼀,最好只需要看⼀下⽂件开头的 import 语句,就能清楚地知道程序使⽤了哪些类。但这种导⼊⽅式没有明 确地指出使⽤了模块中的哪些类。
  • 第⼆,这种导⼊⽅式还可能引发名称⽅ ⾯的迷惑。如果不⼩⼼导⼊了⼀个与程序⽂件中的其他东⻄同名的类,将 引发难以诊断的错误。

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

有时候,需要将类分散到多个模块中,以免模块太⼤或者在同⼀个模块中 存储不相关的类。

下⾯将 Car 类存储在⼀个模块中,并将 ElectricCar 和 Battery 类存 储在另⼀个模块中。

"""⼀组可⽤于表⽰电动汽⻋的类"""
from car import Car
class Battery:
 --snip--
class ElectricCar(Car):
 --snip--

ElectricCar 类需要访问其⽗类 Car,因此直接将 Car 类导⼊该模块

4.7 使用别名

在 导⼊类时,也可以给它指定别名。假设要在程序中创建⼤量电动汽⻋实例,需要反复输⼊ ElectricCar,⾮ 常烦琐。为了避免这种烦恼,可在 import 语句中给 ElectricCar 指定

⼀个别名:

from electric_car import ElectricCar as EC 

现在每当需要创建电动汽⻋实例时,都可使⽤这个别名:

my_leaf = EC('nissan', 'leaf', 2024) 

还可以给模块指定别名。下⾯导⼊模块 electric_car 并给它指定了别 名:

import electric_car as ec

现在可以结合使⽤模块别名和完整的类名了:


my_leaf = ec.ElectricCar('nissan', 'leaf', 2024)

4.8 找到合适的工作流程

⼀开始应让代码结构尽量简单。

⾸先尝试在⼀个⽂件中完成所有的⼯作, 确定⼀切都能正确运⾏后,再将类移到独⽴的模块中。

如果你喜欢模块和 ⽂件的交互⽅式,可在项⽬开始时就尝试将类存储到模块中。先找出 能够编写出可⾏代码的⽅式,再尝试让代码更加整洁。

5、Python标准库

Python 标准库是⼀组模块,在安装 Python 时已经包含内。你可以使⽤标准库中的任何函数和类,只需在程序开头添加⼀条 简单的 import 语句即可。

下⾯来看看模块 random,在这个模块中,⼀个有趣的函数是 randint()。它将两个整数作为参数, 并随机返回⼀个位于这两个整数之间(含)的整数。下⾯演⽰了如何⽣成 ⼀个位于 1 和 6 之间的随机整数:

>>> from random import randint 
>>> randint(1, 6) 
3

在模块 random 中,另⼀个很有⽤的函数是 choice()。它将⼀个列表或 元组作为参数,并随机返回其中的⼀个元素:

>>> from random import choice
>>> players = ['charles', 'martina', 'michael', 'florence', 'eli']
>>> first_up = choice(players)
>>> first_up
'florence'

在创建与安全相关的应⽤程序时,不要使⽤模块 random,但它能⽤来创建 众多有趣的项⽬。

6、类的编写风格

类名应采⽤驼峰命名法,即将类名中的每个单词的⾸字⺟都⼤写,并且不 使⽤下划线。实例名和模块名都采⽤全⼩写格式,并在单词之间加上下划 线。

对于每个类,都应在类定义后⾯紧跟⼀个⽂档字符串。这种⽂档字符串简 要地描述类的功能,你应该遵循编写函数的⽂档字符串时采⽤的格式约 定。每个模块也都应包含⼀个⽂档字符串,对其中的类可⽤来做什么进⾏ 描述。

可以使⽤空⾏来组织代码,但不宜过多。在中,可以使⽤⼀个空⾏来分 隔⽅法;⽽在模块中,可以使⽤两个空⾏来分隔类。

当需要同时导⼊标准库中的模块和你编写的模块时,先编写导⼊标准库模 块的 import 语句,再添加⼀个空⾏,然后编写导⼊你⾃⼰编写的模块的 import 语句。在包含多条 import 语句的程序中,这种做法让⼈更容易 明⽩程序使⽤的各个模块来⾃哪⾥。

你可能感兴趣的:(Python,python,开发语言,程序人生)