【python基础】类详解:如何编写类、__init__()、修改实例属性、类存储到模块并导入、py标准库、编写类的约定
文章目录
- 一. 创建和使用类
- 1. 创建Dog类
- 1.1. 构造方法:
- 1.2. 形参self(类比java的this):
- 1.3. 属性(类比java的成员变量):
- 2. 根据类创建实例
- 2.1. 创建实例
- 2.2. 访问属性
- 2.3. 调用方法
- 2.4. 创建多个实例
- 二. 使用类和实例
- 1. 给属性指定默认值
- 2. 修改属性的值
- 三. 继承
- 1. 子类的方法__init__()
- 2. 给子类定义属性和方法
- 3. 重写父类的方法
- 3. 将实例用作属性
- 四. 导入类
- 1. 导入单个类
- 2. 在一个模块中存储多个类
- 3. 从一个模块中导入多个类
- 4. 导入整个模块
- 5. 导入模块中的所有类(不推荐)
- 6. 在一个模块中导入另一个模块
- 7. 使用别名
- 五. Python标准库
- 六. 类编码风格
本文将讲解
- 如何编写类;如何使用属性在类中存储信息,以及如何编写方法,以让类具备所需的行为;
- 如何编写方法__init__(),以便根据类创建包含所需属性的实例。
- 如何修改实例的属性,包括直接修改以及通过方法进行修改。
- 使用继承可简化相关类的创建工作,以及将一个类的实例用作另一个类的属性可让类更简洁。
- 通过将类存储在模块中,并在需要使用这些类的文件中导入它们,可让项目组织有序。
- 知道怎么使用Python标准库,并见识了一个使用模块random的示例。
- 了解编写类时应遵循的Python约定。
在面向对象编程中,你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,你定义一大类对象都有的通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。
一. 创建和使用类
下面来编写一个表示小狗的简单类Dog,它表示的不是特定的小狗,而是任何小狗。
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!")
1.1. 构造方法:
init()是一个特殊方法,每当你根据Dog类创建新实例时,Python都会自动运行它。
务必确保__init__()的两边都有两个下划线,否则当你使用类来创建实例时,将不会自动调用这个方法,进而引发难以发现的错误。
1.2. 形参self(类比java的this):
- 在 init() 方法的定义中,形参self必不可少,而且必须位于其他形参的前面。为何必须在方法定义中包含形参self呢?因为Python调用这个方法来创建Dog实例时,将自动传入实参self。
- 每个与实例相关联的方法?ing 调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。
- 创建Dog实例时,Python将调用Dog类的方法__init__()。我们将通过实参向Dog()传递名字和年龄,self会自动传递,因此不需要传递它。
1.3. 属性(类比java的成员变量):
以self为前缀的变量可供类中的所有方法使用,可以通过类的任何实例来访问。
- self.name = name获取与形参name相关联的值,并将其赋给变量name,然后该变量被关联到当前创建的实例。
- self.age = age的作用与此类似。
像这样可通过实例访问的变量称为属性。
2. 根据类创建实例
2.1. 创建实例
if __name__ == '__main__':
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实例时: my_dog=Dog("Willie",6)
,Python使用实参’Willie’和6调用Dog类的方法__init__()。方法__init__()创建一个表示特定小狗的实例,并使用提供的值来设置属性name和age。
接下来,Python返回一个表示这条小狗的实例,而我们将这个实例赋给了变量my_dog。
(类似于java)在这里,命名约定很有用:通常可认为首字母大写的名称(如Dog)指的是类,而小写的名称(如my_dog)指的是根据类创建的实例。
2.2. 访问属性
要访问实例的属性,可使用句点表示法。
my_dog.name
2.3. 调用方法
使用句点表示法来调用Dog类中定义的任何方法。
class Dog:
--snip--
my_dog = Dog('Willie', 6)
my_dog.sit()
my_dog.roll_over()
2.4. 创建多个实例
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()
即使给第二条小狗指定同样的名字和年龄,Python依然会根据Dog类创建另一个实例。你可按需求根据一个类创建任意数量的实例,条件是将每个实例都存储在不同的变量中,或者占用列表或字典的不同位置。
二. 使用类和实例
接下来再看一个类似的例子:
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()
if __name__ == '__main__':
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
1. 给属性指定默认值
创建实例时,有些属性无须通过形参来定义,可在方法__init__()中为其指定默认值。
class Car:
def __init__(self, make, model, year):
"""初始化描述汽车的属性。"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 5
def get_descriptive_name(self):
"""--snip--"""
def read_odometer(self):
"""打印一条指出汽车里程的消息。"""
print(f"This car has {self.odometer_reading} miles on it.")
if __name__ == '__main__':
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()
2. 修改属性的值
我们能以三种方式修改属性的值:直接通过实例进行修改,通过方法进行设置,以及通过方法进行递增(增加特定的值)。
直接修改属性的值
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
通过方法修改属性的值
class Car:
...
def update_odometer(self, mileage):
"""将里程表读数设置为指定的值。"""
self.odometer_reading = mileage
if __name__ == '__main__':
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.update_odometer(23)
my_new_car.read_odometer()
可对方法update_odometer()进行扩展,使其在修改里程表读数时做些额外的工作。下面来添加一些逻辑,禁止任何人将里程表读数往回调:
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
if __name__ == '__main__':
my_used_car = Car('subaru', 'outback', 2015)
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()
注意 你可以使用类似于上面的方法来控制用户修改属性值(如里程表读数)的方式,但能够访问程序的人都可以通过直接访问属性来将里程表修改为任何值。
要确保安全,除了进行类似于前面的基本检查外,还需特别注意细节。
且我们应该把属性变成私有化,这样就不能直接任意修改里程表读数了。 如下是不好的操作 ing
my_used_car.odometer_reading=1000
my_used_car.read_odometer()
三. 继承
如果要编写的类是另一个现成类的特殊版本,可使用继承。
- 一个类继承另一个类时,将自动获得另一个类的所有属性和方法。原有的类称为父类,而新类称为子类。
- 子类继承了父类的所有属性和方法,同时还可以定义自己的属性和方法。
- 且子类可以覆写父类的方法 (就像java一样)。
1. 子类的方法__init__()
在既有类的基础上编写新类时,通常要调用父类的方法__init__()。这将初始化在父类__init__()方法中定义的所有属性,从而让子类包含这些属性。
例如,下面来模拟电动汽车。电动汽车是一种特殊的汽车,因此可在前面创建的Car类的基础上创建新类ElectricCar。这样就只需为电动汽车特有的属性和行为编写代码。如下:
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)
if __name__ == '__main__':
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
- 创建子类时,父类必须包含在当前文件中,且位于子类前面。
- 定义子类ElectricCar时,必须在圆括号内指定父类的名称。方法__init__()接受创建Car实例所需的信息。
- super()是一个特殊函数,让你能够调用父类的方法。这行代码让Python调用Car类的方法__init__(),让ElectricCar实例包含这个方法中定义的所有属性。父类也称为超类(superclass),名称super由此而来。
2. 给子类定义属性和方法
下面来添加一个电动汽车(子类)特有的属性(电瓶),以及一个描述该属性的方法。我们将存储电瓶容量,并编写一个打印电瓶描述的方法:
class ElectricCar(Car):
def __init__(self, make, model, year):
"""
初始化父类的属性。
再初始化电动汽车特有的属性。
"""
super().__init__(make, model, year)
self.battery_size = 75
def describe_battery(self):
"""打印一条描述电瓶容量的消息。"""
print(f"This car has a {self.battery_size}-kWh battery.")
if __name__ == '__main__':
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battery()
添加了新属性self.battery_size,并设置其初始值(75)。根据ElectricCar类创建的所有实例都将包含该属性,但所有Car实例都不包含它。
3. 重写父类的方法
对于父类的方法,只要它不符合子类模拟的实物的行为,都可以进行重写。为此,可在子类中定义一个与要重写的父类方法同名的方法。
假设Car类有一个名为fill_gas_tank()的方法,它对全电动汽车来说毫无意义,因此你可能想重写它。下面演示了一种重写方式:
def fill_gas_tank(self):
"""电动汽车没有油箱。"""
print("This car doesn't need a gas tank!")
使用继承时,可让子类保留从父类那里继承而来的精华,并剔除不需要的糟粕。
3. 将实例用作属性
使用代码模拟实物时,你可能会发现自己给类添加的细节越来越多:属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分提取出来,作为一个独立的类。可以将大型类拆分成多个协同工作的小类。
例如,不断给ElectricCar类添加细节时,我们可能发现其中包含很多专门针对汽车电瓶的属性和方法。在这种情况下,可将这些属性和方法提取出来,放到一个名为Battery的类中,并将一个Battery实例作为ElectricCar类的属性:
class Battery:
"""一次模拟电动汽车电瓶的简单尝试。"""
def __init__(self, battery_size=75):
"""初始化电瓶的属性。"""
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 = 75
self.battery = Battery()
def describe_battery(self):
"""打印一条描述电瓶容量的消息。"""
print(f"This car has a {self.battery_size}-kWh battery.")
def fill_gas_tank(self):
"""电动汽车没有油箱。"""
print("This car doesn't need a gas tank!")
if __name__ == '__main__':
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
给Battery新增了方法get_range()做了一些简单的分析。
class Battery:
...
def get_range(self):
"""打印一条消息,指出电瓶的续航里程。"""
if self.battery_size == 75:
range = 260
elif self.battery_size == 100:
range = 315
print(f"This car can go about {range} miles on a full charge.")
if __name__ == '__main__':
my_tesla = ElectricCar('tesla', 'model s', 2019)
my_tesla.battery.get_range()
四. 导入类
为遵循Python的总体理念,应让文件尽可能整洁。Python在这方面提供了帮助,允许将类存储在模块中,然后在主程序中导入所需的模块。
1. 导入单个类
car.py
❶ """一个可用于表示汽车的类。"""
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类并创建其实例
my_car.py
from car import Car
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
import语句让Python打开模块car并导入其中的Car类。这样,我们就可以使用Car类,就像它是在这个文件中定义的一样。输出与我们在前面看到的一样:
2. 在一个模块中存储多个类
Battery类和ElectricCar类都可帮助模拟汽车,下面将它们都加入模块car.py中:
"""一组用于表示燃油汽车和电动汽车的类。"""
class Car:
--snip--
class Battery:
"""一次模拟电动汽车电瓶的简单尝试。"""
def __init__(self, battery_size=75):
"""初始化电瓶的属性。"""
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 == 75:
range = 260
elif self.battery_size == 100:
range = 315
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类,并创建一辆电动汽车了:
my_electric_car.py
# 从car.py 导入ElectricCar类
from car import ElectricCar
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
3. 从一个模块中导入多个类
可根据需要在程序文件中导入任意数量的类。如果要在同一个程序中创建普通汽车和电动汽车,就需要将Car类和ElectricCar类都导入:
from car import Car, ElectricCar
if __name__ == '__main__':
my_beetle = Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())
my_tesla = ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())
4. 导入整个模块
还可以导入整个模块,再使用句点表示法访问需要的类。
import car
if __name__ == '__main__':
my_beetle = car.Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())
my_tesla = car.ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())
使用语法module_name.ClassName
访问需要的类。
5. 导入模块中的所有类(不推荐)
from module_name import *
不推荐使用这种导入方式,原因有二。
- 如果只看文件开头的import语句,就能清楚地知道程序使用了哪些类,将大有裨益。然而这种导入方式没有明确地指出使用了模块中的哪些类。
- 这种方式还可能引发名称方面的迷惑。如果不小心导入了一个与程序文件中其他东西同名的类,将引发难以诊断的错误。
这里之所以介绍这种导入方式,是因为虽然不推荐使用,但你可能在别人编写的代码中见到它。
6. 在一个模块中导入另一个模块
有时候,需要将类分散到多个模块中,以免模块太大或在同一个模块中存储不相关的类。将类存储在多个模块中时,你可能会发现一个模块中的类依赖于另一个模块中的类。在这种情况下,可在前一个模块中导入必要的类。
car类放到一个模块中、Battery和ElectricCar放到一个模块中
car.py
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
def fill_gas_tank(self):
print("car has filled ")
elec_car.py
from car import Car
class Battery:
"""一次模拟电动汽车电瓶的简单尝试。"""
def __init__(self, battery_size=75):
"""初始化电瓶的属性。"""
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 == 75:
range = 260
elif self.battery_size == 100:
range = 315
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_size = 75
self.battery = Battery()
def describe_battery(self):
"""打印一条描述电瓶容量的消息。"""
print(f"This car has a {self.battery_size}-kWh battery.")
def fill_gas_tank(self):
"""电动汽车没有油箱。"""
print("This car doesn't need a gas tank!")
现在创建ElectricCar的实例,导入elec_car.py即可
from elec_car import ElectricCar
if __name__ == '__main__':
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
这里只引入ElectricCar即可,因为elec_car模块中已经导入了Car。
但是这里和java不同的是:
在同一目录下的类,java不需要导入可以直接引用,py始终需要导入你所需要的类
7. 使用别名
导入类时,也可为其指定别名。
例如,要在程序中创建大量电动汽车实例,需要反复输入ElectricCar,非常烦琐。为避免这种烦恼,可在import语句中给ElectricCar指定一个别名:
from electric_car import ElectricCar as EC
现在就可以使用别名创建实例了
my_tesla = EC('tesla', 'roadster', 2019)
五. Python标准库
Python标准库是一组模块,我们安装的Python都包含它。
你可以使用标准库中的任何函数和类,只需在程序开头包含一条简单的import语句即可。
下面来看看模块random:
from random import randint
if __name__ == '__main__':
#一到六随机返回一个数
print(randint(1,6))
将一个列表或元组作为参数,并随机返回其中的一个元素:
from random import randint, choice
if __name__ == '__main__':
players = ['charles', 'martina', 'michael', 'florence', 'eli']
print(choice(players))
注意: 还可以从其他地方下载外部模块,类似与java的依赖
六. 类编码风格
- 类名应采用驼峰命名法,即将类名中的每个单词的首字母都大写,而不使用下划线。
- 实例名和模块名都采用小写格式,并在单词之间加上下划线。
- 对于每个类,都应紧跟在类定义后面包含一个文档字符串。这种文档字符串简要地描述类的功能,并遵循编写函数的文档字符串时采用的格式约定。
- 每个模块也都应包含一个文档字符串,对其中的类可用于做什么进行描述。
- 可使用空行来组织代码,但不要滥用。在类中,可使用一个空行来分隔方法;而在模块中,可使用两个空行来分隔类。
- 需要同时导入标准库中的模块和你编写的模块时,先编写导入标准库模块的import语句,再添加一个空行,然后编写导入你自己编写的模块的import语句。在包含多条import语句的程序中,这种做法让人更容易明白程序使用的各个模块都来自何处。
参考:《Python编程:从入门到实践(第二版)》