面向对象编程(OOP)是一种以“对象”为核心的编程思想,通过封装、继承、多态等特性,让代码更具模块化、可复用性和可维护性。在 Python 中,类(Class)是对象的“模板”,定义了对象的属性(数据)和方法(行为);而实例(Instance)则是类的具体“实体”,自动继承类的所有通用行为。本文将从类的创建、使用、继承到编码规范,系统讲解 Python 面向对象编程的核心知识点。
9.1 创建和使用类
类是面向对象编程的基础,通过 class 关键字定义,包含初始化方法(__init__)和自定义方法,用于封装对象的属性和行为。
9.1.1 定义类:封装属性与行为
一个完整的类通常包含两部分:
- 初始化方法
__init__:用于初始化对象的属性,是创建实例时自动调用的“构造函数”,参数self代表实例本身,必须位于首位。 - 自定义方法:定义对象的行为,可通过
self访问实例的属性。
# coding=utf-8
# 1. 定义 Dog 类:封装狗的属性(名字、年龄)和行为(叫、获取年龄)
class Dog():
def __init__(self, name, age):
"""初始化 Dog 类的属性:name(名字)和 age(年龄)"""
# self.属性名:将参数值赋值给实例的属性,使其成为实例的“固有数据”
self.name = name
self.age = age
def bark(self):
"""定义“叫”的行为:返回狗叫的描述字符串"""
return f"{self.name} says Woof!" # 通过 self 访问实例的 name 属性
def get_age(self):
"""定义“获取年龄”的行为:返回狗的年龄"""
return self.age # 通过 self 访问实例的 age 属性
9.1.2 创建实例:从类到具体对象
通过“类名(参数)”的格式创建实例,参数需与 __init__ 方法中除 self 外的参数对应。实例会自动继承类的所有属性和方法。
# 2. 基于 Dog 类创建实例:代表一只叫“Buddy”、3 岁的狗
dog = Dog("Buddy", 3) # 无需传入 self,Python 会自动绑定实例到 self
9.1.3 调用实例的属性和方法
- 访问属性:通过“实例名.属性名”的格式,直接获取或修改实例的属性。
- 调用方法:通过“实例名.方法名()”的格式,执行实例的行为(注意方法名后需加括号,否则仅引用方法本身)。
# 3. 调用实例的方法和属性
print(dog.bark()) # 调用 bark() 方法,输出:Buddy says Woof!
# 结合属性和方法,输出实例的描述信息
print(f"{dog.name} is {dog.get_age()} years old.") # 输出:Buddy is 3 years old.
9.2 使用类和实例
创建类和实例后,需掌握属性的管理(默认值、修改)和方法的灵活调用,这是面向对象编程的核心应用场景。
9.2.1 方法中使用类的属性
类的方法可以通过 self 直接访问实例的属性,实现属性的复用和逻辑封装。例如,通过方法将实例的多个属性整理为字典,方便统一返回或处理。
class People():
def __init__(self, name, age, location):
"""初始化人的属性:name(姓名)、age(年龄)、location(所在地)"""
self.name = name
self.age = age
self.location = location
def get_long_dsp(self):
"""将人的所有属性整理为字典,返回完整描述"""
dsp_dir = {} # 创建空字典,用于存储属性
dsp_dir['name'] = self.name # 向字典中添加 name 属性
dsp_dir['age'] = self.age # 向字典中添加 age 属性
dsp_dir['location'] = self.location # 向字典中添加 location 属性
return dsp_dir # 返回包含所有属性的字典
# 创建 People 实例
people_1 = People('lxf', 99, 'cn')
# 调用方法获取属性字典并打印
print(people_1.get_long_dsp())
# 输出:{'name': 'lxf', 'age': 99, 'location': 'cn'}
9.2.2 给属性指定默认值
类的每个属性都必须有初始值(即使为空)。若某些属性在多数情况下值固定,可在 __init__ 方法中直接指定默认值,创建实例时无需重复传入该参数(若需修改,仍可手动指定)。
class People():
def __init__(self, name, age, location):
"""
初始化人的属性:
- 必传参数:name、age、location
- 默认参数:gender(性别),默认值为 'man'
"""
self.name = name
self.age = age
self.location = location
# 给 gender 属性指定默认值,无需通过实例创建时传入
self.gender = 'man'
def get_long_dsp(self):
"""返回包含所有属性(含默认属性)的字典"""
dsp_dir = {}
dsp_dir['name'] = self.name
dsp_dir['age'] = self.age
dsp_dir['location'] = self.location
dsp_dir['gender'] = self.gender # 引用默认属性
return dsp_dir
# 创建实例时,无需传入 gender(使用默认值 'man')
people_1 = People('lxf', 99, 'cn')
print(people_1.get_long_dsp())
# 输出:{'name': 'lxf', 'age': 99, 'location': 'cn', 'gender': 'man'}
# 若需修改默认属性,可在创建实例后手动赋值
people_1.gender = 'woman'
print(people_1.get_long_dsp())
# 输出:{'name': 'lxf', 'age': 99, 'location': 'cn', 'gender': 'woman'}
9.2.3 修改属性的值
实例的属性值并非固定,可通过两种方式修改:直接修改(简单场景)和通过方法修改(复杂场景,如需校验值的合法性)。
方式1:直接通过实例修改
适用于无需额外逻辑校验的场景,直接通过“实例名.属性名 = 新值”修改。
class People():
def __init__(self, name, age, location):
self.name = name
self.age = age
self.location = location
self.gender = 'man'
def get_long_dsp(self):
dsp_dir = {'name': self.name, 'age': self.age, 'location': self.location, 'gender': self.gender}
return dsp_dir
# 创建实例
people_1 = People('lxf', 99, 'cn')
print("修改前:", people_1.get_long_dsp()) # 输出:修改前:{'name': 'lxf', 'age': 99, ...}
# 直接修改 age 属性的值
people_1.age = 89
print("修改后:", people_1.get_long_dsp()) # 输出:修改后:{'name': 'lxf', 'age': 89, ...}
方式2:通过类中的方法修改
适用于需要额外逻辑的场景(如校验年龄是否为正数、姓名是否非空),将修改逻辑封装在方法中,保证属性值的合法性。
class People():
def __init__(self, name, age, location):
self.name = name
self.age = age
self.location = location
self.gender = 'man'
def get_long_dsp(self):
return {'name': self.name, 'age': self.age, 'location': self.location, 'gender': self.gender}
def update_age(self, new_age):
"""
修改年龄的方法:
- 先校验 new_age 是否为正数
- 若合法,更新 age 属性;若不合法,提示错误
"""
if new_age > 0: # 校验逻辑:年龄必须为正数
self.age = new_age
print(f"年龄更新成功,新年龄为:{self.age}")
else:
print("错误:年龄必须为正数!")
# 创建实例
people_1 = People('lxf', 99, 'cn')
print("修改前:", people_1.get_long_dsp()) # 输出:修改前:{'name': 'lxf', 'age': 99, ...}
# 通过方法修改年龄(合法值)
people_1.update_age(100)
print("合法修改后:", people_1.get_long_dsp()) # 输出:合法修改后:{'name': 'lxf', 'age': 100, ...}
# 通过方法修改年龄(非法值)
people_1.update_age(-5) # 输出:错误:年龄必须为正数!
print("非法修改后:", people_1.get_long_dsp()) # 输出:非法修改后:{'name': 'lxf', 'age': 100, ...}(年龄未变)
9.3 继承:复用与扩展类的功能
编写类时,无需每次从空白开始。若新类是某个现有类的“特殊版本”(如“教师”是“人”的特殊版本),可使用继承:子类(Derived Class)自动继承父类(Base Class)的所有属性和方法,同时可添加子类特有的属性和方法,或重写父类的方法。
注意:创建子类时,父类必须位于当前文件中,且定义在子类之前。
9.3.1 属性继承:初始化父类属性
子类的 __init__ 方法需先通过 super() 调用父类的 __init__ 方法,完成父类属性的初始化,再定义子类特有的属性。super() 是 Python 内置函数,用于关联父类和子类,让子类能调用父类的方法。
# 父类:People(通用的“人”类)
class People():
def __init__(self, name, age):
"""父类的属性:name(姓名)、age(年龄)"""
self.name = name
self.age = age
# 子类:Teacher(特殊的“人”——教师,继承自 People)
class Teacher(People): # 括号中指定父类,表明 Teacher 继承自 People
def __init__(self, name, age, subject):
"""
子类的初始化:
1. 先调用父类的 __init__,初始化父类的属性(name、age)
2. 再定义子类特有的属性(subject:所教科目)
"""
super().__init__(name, age) # 调用父类的 __init__,传递父类所需参数
self.subject = subject # 子类特有的属性
# 创建 Teacher 实例:需传入父类的参数(name、age)+ 子类的参数(subject)
teacher_1 = Teacher('lxf', 30, 'chemistry')
# 访问继承的父类属性(name)和子类特有属性(subject)
print(f"{teacher_1.name.title()} is teaching {teacher_1.subject}.")
# 输出:Lxf is teaching chemistry.
9.3.2 方法继承:复用父类的行为
子类实例无需额外定义,即可直接调用父类的所有方法——这是继承的核心优势,实现了代码的复用。
# 父类:People
class People():
def __init__(self, name, age):
self.name = name
self.age = age
def say_hello(self):
"""父类的方法:打招呼"""
print(f"{self.name} says hello!")
# 子类:Teacher(继承 People)
class Teacher(People):
def __init__(self, name, age, subject):
super().__init__(name, age)
self.subject = subject
# 创建 Teacher 实例
teacher_1 = Teacher('lxf', 30, 'chemistry')
# 直接调用继承自父类的 say_hello() 方法
teacher_1.say_hello() # 输出:lxf says hello!
# 结合子类属性,输出完整信息
print(f"{teacher_1.name.title()} is a {teacher_1.subject} teacher.")
# 输出:Lxf is a chemistry teacher.
9.3.3 重写父类的方法:定制子类行为
若父类的方法不符合子类的需求,可在子类中定义与父类同名的方法,实现“重写(Override)”。此时,子类实例调用该方法时,会优先执行子类的实现(覆盖父类方法);若需保留父类逻辑,可通过 super() 显式调用父类方法。
场景1:完全重写父类方法
直接在子类中定义同名方法,替换父类的逻辑。
# 父类:People
class People():
def __init__(self, name, age):
self.name = name
self.age = age
def say_hello(self):
"""父类的打招呼方式"""
print(f"{self.name} says hello!")
# 子类:Teacher(重写 say_hello 方法)
class Teacher(People):
def __init__(self, name, age, subject):
super().__init__(name, age)
self.subject = subject
def say_hello(self):
"""子类的打招呼方式:覆盖父类方法"""
print(f"{self.name} says hi! I'm your {self.subject} teacher.")
# 创建实例并调用方法
people_1 = People('zhangsan', 25)
people_1.say_hello() # 父类实例调用父类方法,输出:zhangsan says hello!
teacher_1 = Teacher('lxf', 30, 'chemistry')
teacher_1.say_hello() # 子类实例调用子类方法,输出:lxf says hi! I'm your chemistry teacher.
场景2:扩展父类方法(保留父类逻辑)
若需在父类方法的基础上添加子类特有的逻辑,可通过 super().父类方法名() 先执行父类逻辑,再补充子类代码。
# 父类:People
class People():
def __init__(self, name, age):
self.name = name
self.age = age
def get_info(self):
"""父类方法:返回人的基础信息(姓名、年龄)"""
return {'name': self.name, 'age': self.age}
# 子类:Teacher(扩展 get_info 方法)
class Teacher(People):
def __init__(self, name, age, subject):
super().__init__(name, age)
self.subject = subject
def get_info(self):
"""
子类方法:扩展父类的 get_info
1. 先调用父类方法,获取基础信息
2. 再添加子类特有的信息(subject)
"""
info_dir = super().get_info() # 调用父类方法,获取基础字典
info_dir['subject'] = self.subject # 补充子类属性
return info_dir
# 创建实例并调用方法
teacher_1 = Teacher('lxf', 30, 'chemistry')
print(teacher_1.get_info())
# 输出:{'name': 'lxf', 'age': 30, 'subject': 'chemistry'}(包含父类和子类的所有信息)
9.4 导入类:让代码更整洁
当项目规模扩大时,若将所有类都写在一个文件中,会导致代码冗长、难以维护。Python 允许将类存储在模块(.py 文件) 中,再在主程序中导入所需的类,遵循“模块化”编程理念,让代码结构更清晰。
9.4.1 导入单个类
假设我们将 People 类和 Teacher 类存储在名为 person.py 的模块文件中(模块名建议小写,符合 Python 命名规范):
# person.py(模块文件,单独存储类)
class People():
def __init__(self, name, age):
self.name = name
self.age = age
def say_hello(self):
print(f"{self.name} says hello!")
class Teacher(People):
def __init__(self, name, age, subject):
super().__init__(name, age)
self.subject = subject
def get_info(self):
return {'name': self.name, 'age': self.age, 'subject': self.subject}
在主程序文件(如 main.py)中,通过 from 模块名 import 类名 的语法,导入需要使用的单个类:
# main.py(主程序文件,只关注业务逻辑)
# 从 person 模块中导入 Teacher 类
from person import Teacher
# 直接使用导入的类创建实例
teacher_1 = Teacher('lxf', 30, 'chemistry')
print(teacher_1.get_info()) # 输出:{'name': 'lxf', 'age': 30, 'subject': 'chemistry'}
teacher_1.say_hello() # 输出:lxf says hello!
9.4.2 导入多个类
若需要使用模块中的多个类,可在 import 后用逗号分隔类名,格式为 from 模块名 import 类名1, 类名2:
# main.py
# 从 person 模块中导入 People 和 Teacher 两个类
from person import People, Teacher
# 使用 People 类创建实例
people_1 = People('zhangsan', 25)
people_1.say_hello() # 输出:zhangsan says hello!
# 使用 Teacher 类创建实例
teacher_1 = Teacher('lxf', 30, 'chemistry')
print(teacher_1.get_info()) # 输出:{'name': 'lxf', 'age': 30, 'subject': 'chemistry'}
9.4.3 导入整个模块
也可以直接导入整个模块,再通过“模块名.类名”的格式使用类。这种方式适合模块中类较多的场景,能避免类名冲突:
# main.py
# 导入整个 person 模块
import person
# 通过“模块名.类名”使用类
people_1 = person.People('zhangsan', 25)
people_1.say_hello() # 输出:zhangsan says hello!
teacher_1 = person.Teacher('lxf', 30, 'chemistry')
print(teacher_1.get_info()) # 输出:{'name': 'lxf', 'age': 30, 'subject': 'chemistry'}
9.4.4 导入模块并指定别名
若模块名较长,可通过 as 关键字给模块指定简短的别名,简化代码:
# main.py
# 导入 person 模块,并指定别名为 ps
import person as ps
# 通过别名使用模块中的类
people_1 = ps.People('zhangsan', 25)
teacher_1 = ps.Teacher('lxf', 30, 'chemistry')
print(teacher_1.get_info()) # 输出:{'name': 'lxf', 'age': 30, 'subject': 'chemistry'}
9.4.5 导入模块中的所有类(不推荐)
通过 from 模块名 import * 可导入模块中的所有类,但这种方式存在隐患:若模块中类名与主程序中的变量/类名冲突,会覆盖原有内容;且无法清晰区分使用的类来自哪个模块,不利于代码维护。仅在模块中类较少且明确无冲突时使用:
# main.py(不推荐,慎用)
# 导入 person 模块中的所有类
from person import *
people_1 = People('zhangsan', 25)
teacher_1 = Teacher('lxf', 30, 'chemistry')
9.5 Python 类编码风格
遵循统一的编码风格,能让代码更易读、易协作,符合 Python 的“优雅简洁”理念。以下是类相关的核心编码规范:
| 规范类别 | 具体要求 |
|---|---|
| 类名命名 | 采用驼峰命名法:每个单词的首字母大写,不使用下划线(如 People、Teacher)。禁止使用小写或蛇形命名(如 people、people_teacher)。 |
| 实例名/模块名 | 采用蛇形命名法:全小写,单词间用下划线分隔(如 people_1、person.py)。 |
| 方法命名 | 同实例名,采用蛇形命名法(如 get_info、update_age),避免使用驼峰命名(如 getInfo)。 |
| 初始化方法 | 必须包含 __init__ 方法,用于初始化属性;self 作为第一个参数,且不可省略(即使无其他参数)。 |
| 注释要求 | - 类的注释:在类定义下用文档字符串(三引号) 说明类的功能、属性含义。 - 方法的注释:在方法内用文档字符串说明方法的作用、参数含义、返回值。 |
| 空行规范 | - 类定义前后各空 2 行(或 1 行,保持统一即可)。 - 类内部的方法之间空 1 行,区分不同方法。 |
| 属性访问 | 若属性需对外隐藏(仅内部使用),可在属性名前加单下划线(如 _private_attr),表示“私有属性”(Python 无严格私有机制,仅为约定)。 |
规范示例:符合风格的类定义
# person.py(模块名:蛇形小写)
"""
存储与人相关的类,包括通用的 People 类和特殊的 Teacher 类。
"""
class People():
"""
通用的“人”类,封装人的基础属性和行为。
属性:
name (str):姓名
age (int):年龄
方法:
say_hello():打印打招呼信息
"""
def __init__(self, name, age):
self.name = name
self.age = age
def say_hello(self):
"""打印当前实例的打招呼信息"""
print(f"{self.name} says hello!")
class Teacher(People):
"""
继承自 People 类的“教师”类,新增“所教科目”属性。
额外属性:
subject (str):所教科目
额外方法:
get_info():返回包含所有属性的字典
"""
def __init__(self, name, age, subject):
super().__init__(name, age)
self.subject = subject
def get_info(self):
"""返回教师的完整信息(姓名、年龄、所教科目)"""
return {'name': self.name, 'age': self.age, 'subject': self.subject}
通过以上规范编写的类,不仅结构清晰,还能让其他开发者快速理解代码逻辑,提升团队协作效率。
评论区