Python学习笔记面向对象编程
一、类和对象
1. 类和对象基础
类(Class)的概念
类是对一类事物的抽象描述,定义了这类事物的属性(数据)和方法(行为)。
- 属性:类的特征,如 “人” 的姓名、年龄。
- 方法:类的行为,如 “人” 的说话、跑步。
类比现实:
- 类 = 设计图纸(如 “汽车” 的设计图)。
- 对象 = 根据图纸制造的具体实例(如 “一辆红色的特斯拉汽车”)。
2、定义类的语法
class ClassName:# 类属性(可选,属于类本身)class_attribute = "类属性"# 构造方法(初始化对象时自动调用)def __init__(self, param1, param2):# 实例属性(属于对象)self.attribute1 = param1 # 通过self绑定到对象self.attribute2 = param2# 实例方法(需通过对象调用,第一个参数为self)def instance_method(self, arg):print(f"实例方法:{self.attribute1}, 参数:{arg}")# 类方法(需装饰器@classmethod,第一个参数为cls)@classmethoddef class_method(cls):print(f"类方法:{cls.class_attribute}")# 静态方法(无需绑定类或对象,通过@staticmethod装饰)@staticmethoddef static_method():print("静态方法")
3、对象(实例)的创建与使用
1. 创建对象(实例化类)
obj = ClassName("值1", "值2") # 调用__init__方法初始化对象
2. 访问属性和方法
# 访问实例属性
print(obj.attribute1) # 输出:值1# 调用实例方法
obj.instance_method("参数") # 输出:实例方法:值1, 参数:参数# 访问类属性(通过类或对象)
print(ClassName.class_attribute) # 输出:类属性
print(obj.class_attribute) # 输出:类属性# 调用类方法和静态方法(通过类调用)
ClassName.class_method() # 输出:类方法:类属性
ClassName.static_method() # 输出:静态方法
4.关键概念解析
1.self
的作用
- 实例方法的第一个参数必须是
self
,代表当前对象本身。- 通过
self
可以访问对象的属性和方法。
class Person:def __init__(self, name):self.name = name # 将参数name赋值给对象的name属性def say_hello(self):print(f"Hello, {self.name}!") # 通过self访问对象的name属性
2. 类属性 vs 实例属性
类属性:属于类本身,所有对象共享,通过类名直接访问。
class Dog:species = "犬科" # 类属性dog1 = Dog()
print(Dog.species) # 输出:犬科(通过类访问)
print(dog1.species) # 输出:犬科(通过对象访问)
实例属性:属于每个对象,通过self
在构造方法中定义,每个对象独立存在。
class Dog:def __init__(self, name):self.name = name # 实例属性(每个狗的名字不同)dog1 = Dog("旺财")
dog2 = Dog("小白")
print(dog1.name) # 输出:旺财
print(dog2.name) # 输出:小白
3. 方法类型
方法类型 | 装饰器 | 参数特点 | 访问方式 |
---|---|---|---|
实例方法 | 无 | 第一个参数为 self | 通过对象调用 |
类方法 | @classmethod | 第一个参数为 cls | 通过类调用 |
静态方法 | @staticmethod | 无特殊参数 | 通过类调用 |
5. 示例:定义 “学生” 类
class Student:# 类属性:所有学生共享的学校名称school = "XX中学"# 构造方法:初始化学生的姓名和年级def __init__(self, name, grade):self.name = name # 实例属性:姓名self.grade = grade # 实例属性:年级# 实例方法:打印学生信息def show_info(self):print(f"姓名:{self.name},年级:{self.grade},学校:{self.school}")# 类方法:修改学校名称@classmethoddef change_school(cls, new_school):cls.school = new_school# 静态方法:判断是否为高年级(示例逻辑)@staticmethoddef is_senior(grade):return grade >= 3 # 假设3年级及以上为高年级# 创建学生对象
stu1 = Student("张三", 2)
stu2 = Student("李四", 4)# 调用实例方法
stu1.show_info() # 输出:姓名:张三,年级:2,学校:XX中学
stu2.show_info() # 输出:姓名:李四,年级:4,学校:XX中学# 调用类方法修改学校名称
Student.change_school("实验中学")
print(Student.school) # 输出:实验中学# 调用静态方法
print(Student.is_senior(3)) # 输出:True
二. 继承和多态
2.1继承(Inheritance)
继承是面向对象编程的核心概念之一,允许一个类(子类)继承另一个类(父类)的属性和方法,实现代码复用和层次化设计。
1. 基本语法与概念
class ParentClass:def parent_method(self):print("这是父类的方法")class ChildClass(ParentClass): # 子类继承父类def child_method(self):print("这是子类的方法")# 创建子类对象
child = ChildClass()
child.parent_method() # 调用父类方法
child.child_method() # 调用子类方法
关键点:
- 子类通过括号内指定父类名称实现继承。
- 子类自动获得父类的所有非私有属性和方法。
- 子类可新增自己的属性和方法,或重写父类方法。
2. 方法重写(Override)
子类可重新定义父类的方法,覆盖其实现:
class Animal:def speak(self):return "动物发出声音"class Dog(Animal):def speak(self): # 重写父类方法return "汪汪汪"class Cat(Animal):def speak(self): # 重写父类方法return "喵喵喵"# 测试
dog = Dog()
cat = Cat()
print(dog.speak()) # 输出:汪汪汪
print(cat.speak()) # 输出:喵喵喵
3. 多重继承
Python 支持一个子类继承多个父类:
class A:def method_a(self):print("A类的方法")class B:def method_b(self):print("B类的方法")class C(A, B): # 继承自A和Bpassc = C()
c.method_a() # 输出:A类的方法
c.method_b() # 输出:B类的方法
注意:多重继承可能导致 “菱形继承问题”,需通过 MRO(方法解析顺序)解决。
4. 访问父类方法
通过super()
调用父类的方法:
class Parent:def greet(self):print("Hello from Parent")class Child(Parent):def greet(self):super().greet() # 调用父类的greet方法print("Hello from Child")child = Child()
child.greet()
# 输出:
# Hello from Parent
# Hello from Child
2.2 多态(Polymorphism)
多态允许不同类的对象对同一方法做出不同响应,提高代码灵活性和可扩展性。
1. 基于继承的多态
通过方法重写实现:
class Shape:def area(self):return 0 # 默认实现class Rectangle(Shape):def __init__(self, width, height):self.width = widthself.height = heightdef area(self): # 重写area方法return self.width * self.heightclass Circle(Shape):def __init__(self, radius):self.radius = radiusdef area(self): # 重写area方法return 3.14 * self.radius ** 2# 多态调用
shapes = [Rectangle(2, 3), Circle(5)]
for shape in shapes:print(f"面积: {shape.area()}") # 自动调用对应子类的area方法
2. 鸭子类型(Duck Typing)
Python 的多态不依赖继承,只要对象具有相同方法即可调用:
class Dog:def speak(self):return "汪汪汪"class Cat:def speak(self):return "喵喵喵"class Car:def speak(self): # 不继承任何类,但有相同方法名return "嘟嘟嘟"# 多态调用
def animal_speak(obj):print(obj.speak())dog = Dog()
cat = Cat()
car = Car()animal_speak(dog) # 输出:汪汪汪
animal_speak(cat) # 输出:喵喵喵
animal_speak(car) # 输出:嘟嘟嘟(Car类与动物无关,但仍可调用)
3. 抽象基类(Abstract Base Class)
强制子类实现特定方法:
from abc import ABC, abstractmethodclass Animal(ABC): # 抽象基类@abstractmethod # 抽象方法,子类必须实现def speak(self):passclass Dog(Animal):def speak(self): # 实现抽象方法return "汪汪汪"class Cat(Animal):def speak(self): # 实现抽象方法return "喵喵喵"# 无法实例化抽象基类
# animal = Animal() # 报错:TypeError# 合法的子类实例
dog = Dog()
print(dog.speak()) # 输出:汪汪汪
4.代码示例:员工管理系统
from abc import ABC, abstractmethodclass Employee(ABC): # 抽象基类def __init__(self, name, salary):self.name = nameself.salary = salary@abstractmethoddef calculate_bonus(self):passdef get_info(self):return f"姓名: {self.name}, 工资: {self.salary}, 奖金: {self.calculate_bonus()}"class FullTimeEmployee(Employee):def calculate_bonus(self): # 实现抽象方法return self.salary * 0.2 # 20%奖金class PartTimeEmployee(Employee):def calculate_bonus(self): # 实现抽象方法return self.salary * 0.1 # 10%奖金# 多态调用
employees = [FullTimeEmployee("张三", 8000),PartTimeEmployee("李四", 3000)
]for emp in employees:print(emp.get_info()) # 自动调用对应子类的calculate_bonus方法# 输出:
# 姓名: 张三, 工资: 8000, 奖金: 1600.0
# 姓名: 李四, 工资: 3000, 奖金: 300.0
5. 继承与多态的优势
- 代码复用:通过继承减少重复代码。
- 可扩展性:新增子类不影响现有代码(开闭原则)。
- 灵活性:通过多态统一接口,不同实现动态切换。
- 可维护性:通过抽象基类明确接口规范,降低耦合度。
三. 特殊方法(魔术方法)
3.1、什么是特殊方法?
特殊方法(Magic Methods)也称为魔术方法,是 Python 中预定义的、以双下划线(__
)开头和结尾的方法。它们用于实现类的内置行为(如初始化、运算符重载、迭代等),无需显式调用,而是由特定语法或内置函数触发。
常见用途:
- 对象初始化(
__init__
)- 字符串表示(
__str__
,__repr__
)- 算术运算符(
__add__
,__sub__
)- 比较运算符(
__eq__
,__lt__
)- 容器操作(
__len__
,__getitem__
)- 上下文管理器(
__enter__
,__exit__
)
3.2、常用特殊方法分类
1. 对象创建与销毁
__init__(self, ...)
:初始化对象,创建实例时自动调用。__new__(cls, ...)
:创建对象实例的静态方法,先于__init__
执行。__del__(self)
:对象被垃圾回收时调用。
示例:
class Person:def __init__(self, name, age):self.name = nameself.age = ageprint(f"创建了{self.name}")def __del__(self):print(f"销毁了{self.name}")p = Person("张三", 20) # 输出:创建了张三
del p # 输出:销毁了张三
2. 字符串表示
__str__(self)
:返回对象的用户友好字符串表示(str(obj)
或print(obj)
时调用)。__repr__(self)
:返回对象的开发者友好字符串表示(交互式环境或repr(obj)
时调用)。
示例:
class Point:def __init__(self, x, y):self.x = xself.y = ydef __str__(self):return f"({self.x}, {self.y})"def __repr__(self):return f"Point({self.x}, {self.y})"p = Point(3, 4)
print(p) # 输出:(3, 4)(调用__str__)
print(repr(p)) # 输出:Point(3, 4)(调用__repr__)
3. 算术运算符重载
__add__(self, other)
:定义加法(+
)行为。__sub__(self, other)
:定义减法(-
)行为。__mul__(self, other)
:定义乘法(*
)行为。__truediv__(self, other)
:定义除法(/
)行为。__floordiv__(self, other)
:定义整除(//
)行为。
示例:
class Vector:def __init__(self, x, y):self.x = xself.y = ydef __add__(self, other):return Vector(self.x + other.x, self.y + other.y)def __str__(self):return f"Vector({self.x}, {self.y})"v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2 # 调用__add__
print(v3) # 输出:Vector(4, 6)
4. 比较运算符重载
__eq__(self, other)
:定义等于(==
)行为。__ne__(self, other)
:定义不等于(!=
)行为。__lt__(self, other)
:定义小于(<
)行为。__gt__(self, other)
:定义大于(>
)行为。
示例:
class Person:def __init__(self, age):self.age = agedef __eq__(self, other):return self.age == other.agedef __lt__(self, other):return self.age < other.agep1 = Person(20)
p2 = Person(25)
print(p1 == p2) # 输出:False(调用__eq__)
print(p1 < p2) # 输出:True(调用__lt__)
5. 容器类方法
__len__(self)
:返回容器长度(len(obj)
时调用)。__getitem__(self, key)
:获取容器元素(obj[key]
时调用)。__setitem__(self, key, value)
:设置容器元素(obj[key] = value
时调用)。__contains__(self, item)
:判断元素是否存在(item in obj
时调用)。
示例:
class MyList:def __init__(self, *items):self.items = list(items)def __len__(self):return len(self.items)def __getitem__(self, index):return self.items[index]my_list = MyList(1, 2, 3)
print(len(my_list)) # 输出:3(调用__len__)
print(my_list[1]) # 输出:2(调用__getitem__)
6. 上下文管理器(with 语句)
__enter__(self)
:进入上下文时调用,返回值绑定到as
后的变量。__exit__(self, exc_type, exc_value, traceback)
:退出上下文时调用,处理异常。
示例:
class FileHandler:def __init__(self, filename, mode):self.filename = filenameself.mode = modedef __enter__(self):self.file = open(self.filename, self.mode)return self.filedef __exit__(self, exc_type, exc_value, traceback):self.file.close()return True # 异常已处理,不再传播with FileHandler("test.txt", "w") as f:f.write("Hello, World!") # 自动调用__enter__和__exit__
3.3、自定义类的特殊方法实战
下面是一个综合示例,展示如何通过特殊方法创建一个支持多种操作的自定义类:
class MyNumber:def __init__(self, value):self.value = value# 算术运算def __add__(self, other):return MyNumber(self.value + other.value)def __sub__(self, other):return MyNumber(self.value - other.value)# 比较运算def __eq__(self, other):return self.value == other.valuedef __gt__(self, other):return self.value > other.value# 字符串表示def __str__(self):return f"数值: {self.value}"def __repr__(self):return f"MyNumber({self.value})"# 类型转换def __int__(self):return int(self.value)def __float__(self):return float(self.value)# 使用示例
a = MyNumber(5)
b = MyNumber(10)# 算术运算
c = a + b
print(c) # 输出:数值: 15# 比较运算
print(a > b) # 输出:False
print(a == b) # 输出:False# 类型转换
print(int(a)) # 输出:5
print(float(a)) # 输出:5.0
3.4、特殊方法总结
特殊方法是 Python 面向对象编程的强大工具,通过合理实现这些方法,可以让自定义类具有与内置类型相似的行为,提高代码的可读性和可维护性。
常见用途总结:
类别 | 常用方法 |
---|---|
对象创建 / 销毁 | __init__ , __new__ , __del__ |
字符串表示 | __str__ , __repr__ |
算术运算符 | __add__ , __sub__ , __mul__ |
比较运算符 | __eq__ , __lt__ , __gt__ |
容器操作 | __len__ , __getitem__ |
上下文管理器 | __enter__ , __exit__ |
类型转换 | __int__ , __float__ |
通过深入理解和使用特殊方法,可以编写出更加 Pythonic、灵活且高效的代码。
四. 描述符和属性
4.1、描述符(Descriptor)
描述符是一种实现了特定协议(__get__
、__set__
、__delete__
)的类,用于控制类中属性的访问行为。它是 Python 实现@property
、classmethod
、staticmethod
等功能的底层机制,也是自定义属性逻辑的强大工具。
1. 描述符协议的三个方法
方法名称 | 触发时机 | 参数说明 |
---|---|---|
__get__(self, instance, owner) | 当访问属性时调用 | instance :对象实例(若无则为None )owner :所属类 |
__set__(self, instance, value) | 当设置属性值时调用 | instance :对象实例value :要设置的值 |
__delete__(self, instance) | 当删除属性时调用 | instance :对象实例 |
2. 描述符的类型
根据是否实现
__set__
方法,描述符分为两类:
- 数据描述符(Data Descriptor):实现了
__set__
和__get__
,可完全控制属性的读写。- 非数据描述符(Non-Data Descriptor):仅实现
__get__
,属性为只读(如@property
装饰的方法)。
3. 数据描述符示例:限制属性类型
class TypedAttribute:def __init__(self, expected_type):self.expected_type = expected_typeself.name = None # 存储属性名(通过__set_name__绑定)def __set_name__(self, owner, name):"""在类定义时自动调用,绑定属性名"""self.name = namedef __get__(self, instance, owner):"""获取属性值"""if instance is None:return self # 通过类访问描述符时返回自身return instance.__dict__[self.name] # 从实例字典获取值def __set__(self, instance, value):"""设置属性值,校验类型"""if not isinstance(value, self.expected_type):raise TypeError(f"{self.name}必须是{self.expected_type.__name__}类型")instance.__dict__[self.name] = value # 存储到实例字典# 使用描述符的类
class Person:name = TypedAttribute(str) # 数据描述符:限制为str类型age = TypedAttribute(int) # 数据描述符:限制为int类型# 测试
p = Person()
p.name = "张三" # 合法
# p.age = "20" # 报错:TypeError: age必须是int类型
4. 非数据描述符示例:只读属性
class ReadOnlyDescriptor:def __init__(self, value):self.value = valuedef __get__(self, instance, owner):return self.valueclass Config:VERSION = ReadOnlyDescriptor("1.0.0") # 非数据描述符:只读print(Config.VERSION) # 输出:1.0.0
# Config.VERSION = "2.0.0" # 报错:AttributeError(无__set__方法)
5. 描述符的优先级
描述符在类中的优先级由以下规则决定(从高到低):
- 实例字典(
__dict__
):实例直接赋值的属性会覆盖描述符。p.name = "李四" # 直接修改实例字典,绕过描述符的__set__
- 数据描述符:优先于实例字典。
- 非数据描述符:低于实例字典。
- 普通属性:无描述符时,直接访问实例字典。
4.2、属性(Property)
**@property
** 是 Python 提供的语法糖,用于将类中的方法转换为 “属性”,简化数据描述符的使用。它本质上是一个非数据描述符。
1. @property
基础用法
class Circle:def __init__(self, radius):self._radius = radius # 私有属性,通过property访问@propertydef radius(self):"""获取半径(属性 getter)"""return self._radius@radius.setterdef radius(self, value):"""设置半径(属性 setter)"""if value < 0:raise ValueError("半径不能为负数")self._radius = value@propertydef area(self):"""计算面积(只读属性)"""return 3.14 * self._radius ** 2# 使用示例
c = Circle(5)
print(c.radius) # 输出:5(调用@property)
c.radius = 6 # 调用@radius.setter
print(c.area) # 输出:113.04(只读属性)
# c.area = 100 # 报错:AttributeError(无setter)
2. property
的参数形式
不使用装饰器时,可通过property(fget, fset, fdel, doc)
创建属性:
class Circle:def __init__(self, radius):self._radius = radiusdef get_radius(self):return self._radiusdef set_radius(self, value):if value < 0:raise ValueError("半径不能为负数")self._radius = valueradius = property(get_radius, set_radius) # 定义属性area = property(lambda self: 3.14 * self._radius ** 2) # 只读属性
3. 属性的优势
- 封装性:隐藏属性的存储细节,通过方法控制访问。
- 验证逻辑:在
setter
中添加数据校验(如类型、范围检查)。 - 计算属性:动态生成属性值(如
area
无需存储,实时计算)。
4、描述符 vs 属性
特性 | 描述符 | 属性(@property ) |
---|---|---|
实现方式 | 自定义类,实现协议方法 | 装饰器或property 函数 |
灵活性 | 高(可复用,支持多个属性) | 低(每个属性需单独定义) |
适用场景 | 多个属性共享相同逻辑(如类型校验) | 单个属性的读写控制 |
数据描述符 / 非数据描述符 | 可自定义(实现__set__ 即数据描述符) | 非数据描述符(默认只读,需@setter 才为数据描述符) |
5、实战:用描述符实现缓存属性
class CacheDescriptor:def __init__(self, func):self.func = funcself.cache = {} # 缓存字典def __get__(self, instance, owner):if instance is None:return selfkey = id(instance)if key not in self.cache:self.cache[key] = self.func(instance) # 首次调用时计算并缓存return self.cache[key]class HeavyCalculation:def __init__(self, data):self.data = data@CacheDescriptor # 使用描述符装饰方法def result(self):print("执行耗时计算...")return sum(self.data) * 0.5 # 模拟耗时操作# 测试
obj1 = HeavyCalculation([1, 2, 3, 4, 5])
print(obj1.result) # 输出:执行耗时计算... 7.5(首次计算)
print(obj1.result) # 直接从缓存获取,不重复计算
6、总结
- 描述符是 Python 的高级特性,用于实现属性的复杂控制逻辑,是
@property
、classmethod
等的底层机制。- **
@property
** 是描述符的简化用法,适合单个属性的读写控制,常用于数据验证和计算属性。- 合理使用描述符和属性,可以让代码更具封装性和可维护性,避免直接操作属性带来的安全隐患。
理解描述符和属性的工作原理,有助于深入掌握 Python 的面向对象编程,并在需要时实现高度定制化的属性行为。
五. 静态方法和类方法
5.1、基本概念
类型 | 绑定对象 | 装饰器 | 第一个参数 | 调用方式 |
---|---|---|---|---|
实例方法 | 实例 | 无 | self (实例本身) | obj.method() |
类方法 | 类 | @classmethod | cls (类本身) | Class.method() |
静态方法 | 无 | @staticmethod | 无特殊参数 | Class.method() |
5.2、静态方法(Static Method)
静态方法属于类,但不绑定类或实例,类似于普通函数。它不能访问类或实例的属性,仅用于执行与类相关的独立功能。
1. 定义与使用
class Calculator:@staticmethoddef add(a, b):return a + b@staticmethoddef multiply(a, b):return a * b# 调用方式
print(Calculator.add(3, 5)) # 输出:8
print(Calculator.multiply(4, 2)) # 输出:8# 也可通过实例调用(不推荐)
calc = Calculator()
print(calc.add(3, 5)) # 输出:8(但实例状态不会被使用)
2. 核心特点
- 不依赖实例状态:无法访问或修改实例属性。
- 不依赖类状态:无法访问或修改类属性(如类变量)。
- 用途:封装与类相关的工具函数,提高代码组织性。
5.3、类方法(Class Method)
类方法绑定到类而非实例,通过第一个参数cls
访问类属性和方法,常用于创建工厂方法或修改类状态。
1. 定义与使用
class Person:count = 0 # 类变量:记录实例数量def __init__(self, name):self.name = namePerson.count += 1@classmethoddef get_count(cls):"""获取类的实例数量"""return cls.count@classmethoddef create_anonymous(cls):"""工厂方法:创建匿名实例"""return cls("匿名用户")# 使用示例
p1 = Person("张三")
p2 = Person("李四")print(Person.get_count()) # 输出:2(通过类调用)
print(p1.get_count()) # 输出:2(通过实例调用,仍绑定类)anon = Person.create_anonymous()
print(anon.name) # 输出:匿名用户
2. 核心特点
- 访问类属性:通过
cls
参数访问类变量(如cls.count
)。- 修改类状态:可修改类变量或调用其他类方法。
- 工厂方法:创建实例的替代构造函数(如
create_anonymous
)。
5.4、静态方法 vs 类方法
特性 | 静态方法 | 类方法 |
---|---|---|
第一个参数 | 无特殊参数 | cls (类本身) |
访问类属性 | ❌ 无法直接访问 | ✅ 通过cls 访问 |
修改类状态 | ❌ 无法修改 | ✅ 可修改类变量 |
工厂方法 | ❌ 不适用 | ✅ 常用于创建实例的替代方式 |
适用场景 | 与类相关的工具函数(如验证、计算) | 与类状态相关的操作(如计数器、工厂) |
5.5、实战对比
1. 静态方法示例:日期验证工具
class Date:def __init__(self, year, month, day):self.year = yearself.month = monthself.day = day@staticmethoddef is_valid_date(date_str):"""验证日期字符串是否合法"""year, month, day = map(int, date_str.split('-'))return 1 <= month <= 12 and 1 <= day <= 31# 使用示例
print(Date.is_valid_date("2023-10-15")) # 输出:True
print(Date.is_valid_date("2023-13-40")) # 输出:False
2. 类方法示例:工厂模式
class Pizza:def __init__(self, ingredients):self.ingredients = ingredients@classmethoddef margherita(cls):"""创建玛格丽特披萨(固定配料)"""return cls(["番茄", "马苏里拉芝士", "罗勒"])@classmethoddef pepperoni(cls):"""创建意式香肠披萨(固定配料)"""return cls(["番茄", "马苏里拉芝士", "香肠"])# 使用示例
m = Pizza.margherita()
p = Pizza.pepperoni()print(m.ingredients) # 输出:['番茄', '马苏里拉芝士', '罗勒']
print(p.ingredients) # 输出:['番茄', '马苏里拉芝士', '香肠']
5.6、常见问题
1. 何时使用静态方法?
- 函数逻辑与类相关,但不依赖类或实例状态(如工具函数)。
- 提高代码可读性,将工具函数封装在类内部。
2. 何时使用类方法?
- 需要访问或修改类变量(如计数器、配置)。
- 创建工厂方法,提供多种实例化方式。
- 实现继承时,确保子类调用正确的类方法(
cls
会自动绑定到子类)。
3. 能否通过实例调用类方法 / 静态方法?
可以,但不推荐。虽然实例可以调用类方法和静态方法,但它们的第一个参数仍绑定类(cls
)或无特殊绑定,不会使用实例状态。
5.7、总结
- 静态方法是类的工具函数,不依赖类或实例状态,用于封装独立功能。
- 类方法绑定类,通过
cls
访问类属性,常用于工厂方法或类状态操作。 - 合理使用两者可提高代码的组织性和可维护性,避免滥用全局函数。
理解静态方法和类方法的区别,有助于设计更清晰、更符合面向对象原则的 Python 类.
六. 封装和私有属性
6.1、封装的概念
封装(Encapsulation) 是面向对象编程的三大特性之一(另外两个是继承和多态),它指的是将数据(属性)和操作数据的方法(行为)捆绑在一起,并通过访问控制隐藏内部实现细节,仅对外提供必要的接口。
封装的核心目标:
- 数据保护:防止外部直接修改内部数据,避免意外破坏。
- 接口简化:隐藏复杂的内部实现,只暴露高层接口,降低使用难度。
- 可维护性:内部实现可以自由修改,只要接口不变,外部代码不受影响。
6.2、Python 的私有属性与方法
Python 通过命名约定和特殊语法实现封装,而非强制访问控制。
1. 单下划线(_
):弱私有(约定)
- 含义:表示 “私有”,但仅是约定,外部仍可访问。
- 用途:提示开发者该属性或方法不建议直接使用,可能在未来版本中变化。
示例:
class BankAccount:def __init__(self, balance):self._balance = balance # 单下划线表示私有属性def deposit(self, amount):self._balance += amountdef _calculate_interest(self): # 单下划线表示私有方法return self._balance * 0.05# 外部仍可访问,但不建议
account = BankAccount(1000)
print(account._balance) # 输出:1000(可以访问,但违反约定)
2. 双下划线(__
):名称修饰(Name Mangling)
- 含义:强制私有,Python 会自动将其重命名为
_类名__属性名
,外部无法直接访问。- 用途:防止子类意外覆盖父类的属性或方法。
示例:
class Parent:def __init__(self):self.__private_attr = 42 # 双下划线属性def __private_method(self): # 双下划线方法return "私有方法"class Child(Parent):passp = Parent()
print(p._Parent__private_attr) # 输出:42(通过重命名后的名称访问)
# print(p.__private_attr) # 报错:AttributeErrorc = Child()
# print(c.__private_attr) # 报错:AttributeError(子类无法直接访问)
3. 双下划线结尾(__
):特殊方法(避免使用)
- 含义:Python 的特殊方法(如
__init__
、__str__
),用于实现特定协议。- 注意:自定义属性或方法应避免使用双下划线结尾,防止与 Python 内置名称冲突。
6.3、封装的最佳实践
1. 使用属性(@property
)控制访问
通过@property
装饰器实现对私有属性的访问控制,隐藏内部实现:
class Person:def __init__(self, age):self._age = age # 私有属性@propertydef age(self):"""获取年龄(只读)"""return self._age@age.setterdef age(self, value):"""设置年龄,添加验证逻辑"""if value < 0:raise ValueError("年龄不能为负数")self._age = value# 使用示例
p = Person(25)
print(p.age) # 输出:25(通过@property访问)
p.age = 30 # 通过@age.setter设置
# p.age = -5 # 报错:ValueError
2. 封装内部实现细节
将不对外公开的逻辑封装为私有方法,只暴露高层接口:
class DataProcessor:def __init__(self, data):self._data = datadef process(self):"""公开的处理接口"""self._clean_data()self._analyze_data()return self._generate_report()def _clean_data(self): # 私有方法self._data = [x for x in self._data if x is not None]def _analyze_data(self): # 私有方法self._stats = {"mean": sum(self._data) / len(self._data)}def _generate_report(self): # 私有方法return f"分析结果:平均值 = {self._stats['mean']}"# 使用示例
processor = DataProcessor([1, 2, 3, None, 5])
print(processor.process()) # 输出:分析结果:平均值 = 2.75
3. 防止子类意外覆盖
使用双下划线方法避免子类覆盖父类的核心逻辑:
class Base:def __init__(self):self.__initialize() # 强制私有方法,子类无法覆盖def __initialize(self): # 双下划线方法print("初始化基类")class Sub(Base):def __initialize(self): # 这是一个新方法,不会覆盖父类的__initializeprint("初始化子类") # 不会被调用s = Sub() # 输出:初始化基类
4、封装的优势
-
数据安全:通过访问控制避免外部直接修改敏感数据。
# 错误示例:直接修改内部状态 account.balance = -1000 # 可能导致账户余额异常# 正确示例:通过方法控制修改 account.deposit(100) # 经过验证的操作
-
代码可维护性:内部实现可以自由修改,不影响外部调用。
# 原实现:直接存储平均值 self._mean = sum(data) / len(data)# 新实现:改为动态计算(接口不变) @property def mean(self):return sum(self._data) / len(self._data)
-
简化接口:隐藏复杂细节,提供简洁的 API。
# 用户只需调用高层方法,无需关心内部步骤 processor.process() # 而非手动调用多个方法
5、常见误区
-
过度使用双下划线:
- 双下划线主要用于防止子类覆盖,而非完全禁止外部访问。
- 大多数情况下,单下划线约定已足够。
-
认为双下划线是 “真正私有”:
- Python 没有真正的私有属性,双下划线只是名称修饰,仍可通过
_类名__属性名
访问。
- Python 没有真正的私有属性,双下划线只是名称修饰,仍可通过
-
忽略属性的验证逻辑:
- 直接使用公共属性(如
self.age
)而不添加验证,可能导致数据不一致。
- 直接使用公共属性(如
6、总结
- 封装是将数据和操作捆绑,并控制访问的机制,提高代码安全性和可维护性。
- 单下划线(
_
) 是约定的私有标识,提示外部不要直接访问。- 双下划线(
__
) 通过名称修饰实现更强的封装,防止子类意外覆盖。- 属性(
@property
) 是实现封装的最佳方式,允许对属性访问添加验证和逻辑。
合理使用封装,能够设计出更加健壮、灵活且易于维护的 Python 类。
七. 抽象基类
7.1、抽象基类的概念
抽象基类(ABC) 是一种特殊的类,它定义了一组必须被子类实现的方法(抽象方法),但自身不能被实例化。抽象基类用于强制子类遵循特定的接口规范,确保多态性的正确实现,是 Python 实现 “接口继承” 的核心机制。
7.2、如何定义抽象基类
在 Python 中,通过abc
模块的ABC
类和@abstractmethod
装饰器定义抽象基类和抽象方法:
from abc import ABC, abstractmethodclass Animal(ABC): # 继承自ABC@abstractmethod # 标记为抽象方法def speak(self):"""子类必须实现的方法"""pass@abstractmethoddef move(self):pass
关键点:
- 抽象基类必须继承自
abc.ABC
。- 抽象方法使用
@abstractmethod
装饰,子类必须实现这些方法,否则无法实例化。- 抽象基类可以包含具体方法(非抽象方法),供子类直接继承。
7.3、强制子类实现接口
如果子类未实现抽象基类的所有抽象方法,实例化时会抛出TypeError
:
正确实现示例:
class Dog(Animal):def speak(self):return "汪汪汪"def move(self): # 实现move方法return "四条腿跑"dog = Dog()
print(dog.speak()) # 输出:汪汪汪
7.4、抽象基类的具体方法
抽象基类可以提供默认实现的具体方法,供子类继承或重写:
from abc import ABC, abstractmethodclass FileHandler(ABC):def __init__(self, filename):self.filename = filename@abstractmethoddef read(self):pass@abstractmethoddef write(self, data):passdef close(self): # 具体方法(非抽象)print(f"关闭文件 {self.filename}")# 子类继承并实现抽象方法
class TextFile(FileHandler):def read(self):with open(self.filename, 'r') as f:return f.read()def write(self, data):with open(self.filename, 'w') as f:f.write(data)txt = TextFile("test.txt")
txt.write("Hello, ABC!")
txt.close() # 调用基类的close方法
7.5、抽象基类的应用场景
1. 定义接口规范
确保不同子类遵循统一的方法命名和参数列表,例如定义 “支付接口”:
from abc import ABC, abstractmethodclass Payment(ABC):@abstractmethoddef pay(self, amount):"""支付指定金额"""passclass Alipay(Payment):def pay(self, amount):print(f"支付宝支付{amount}元")class WeChatPay(Payment):def pay(self, amount):print(f"微信支付{amount}元")# 多态调用
def make_payment(payment: Payment, amount):payment.pay(amount)make_payment(Alipay(), 100) # 输出:支付宝支付100元
make_payment(WeChatPay(), 200) # 输出:微信支付200元
2. 类型检查
使用isinstance()
或issubclass()
判断对象或类是否符合抽象基类的接口:
class VirtualPayment(Payment): # 未实现pay方法(故意错误)passprint(issubclass(Alipay, Payment)) # 输出:True
print(isinstance(Alipay(), Payment)) # 输出:True# 未实现抽象方法的类不被视为子类
print(issubclass(VirtualPayment, Payment)) # 输出:False
3. 插件系统设计
允许动态加载符合抽象基类接口的插件,提高系统扩展性:
# 基类(插件接口)
class Plugin(ABC):@abstractmethoddef run(self):pass# 插件实现
class DataPlugin(Plugin):def run(self):print("数据处理插件运行")# 插件管理器
class PluginManager:def __init__(self):self.plugins = []def add_plugin(self, plugin):if isinstance(plugin, Plugin): # 检查是否符合接口self.plugins.append(plugin)else:raise TypeError("插件必须实现Plugin接口")
7.6、抽象基类 vs 接口继承
- 抽象基类:通过
abc
模块显式定义,允许包含抽象方法和具体方法,子类需显式继承。- 鸭子类型(Duck Typing):不依赖继承,只要对象具有相同方法即视为符合接口(如 Python 的
list
和str
都支持__len__
方法)。
选择建议:
- 需要强制子类实现接口时,使用抽象基类。
- 追求灵活性和简洁性时,优先使用鸭子类型(如
len(obj)
不关心obj
是否继承自某个基类)。
7.7、注意事项
-
抽象基类不能实例化:
animal = Animal() # 报错:TypeError: Can't instantiate abstract class Animal with abstract methods speak, move
-
子类可部分实现抽象方法:
- 若子类未实现所有抽象方法,则子类仍为抽象基类,无法实例化。
-
抽象方法可以有默认实现:
class Animal(ABC):@abstractmethoddef speak(self):return "默认声音" # 子类可选择重写或使用默认
7.8、总结
- 抽象基类是 Python 实现接口规范的重要工具,通过
@abstractmethod
强制子类实现特定方法。- 适用于需要严格控制子类接口的场景(如框架设计、插件系统)。
- 结合多态性,可实现统一接口调用不同实现的灵活架构。
合理使用抽象基类,能够提高代码的可维护性和可扩展性,避免因子类接口不一致导致的问题。
八、装饰器本质
8.1、装饰器本质
在 Python 中,@staticmethod
和 @classmethod
是内置的装饰器类,用于修改方法的绑定行为。它们的本质是将普通方法转换为特殊的描述符(Descriptor)对象,从而改变方法的调用逻辑。
8.2、@staticmethod
的实现原理
1. 工作机制
@staticmethod
将方法转换为一个不绑定类或实例的函数,调用时不会自动传递任何参数(如 self
或 cls
)。
示例代码:
class MyClass:@staticmethoddef static_method(x, y):return x + y# 等价于手动实现
def static_method(x, y):return x + yclass MyClass:static_method = staticmethod(static_method) # 使用staticmethod类包装
2. 底层实现
staticmethod
是一个描述符类,实现了 __get__
方法,返回原始函数本身:
class staticmethod:def __init__(self, func):self.func = funcdef __get__(self, instance, owner=None):return self.func # 直接返回原始函数,不绑定任何对象
调用过程:
obj = MyClass()
obj.static_method(1, 2) # 等价于调用 MyClass.static_method(1, 2)
8.3、@classmethod
的实现原理
1. 工作机制
@classmethod
将方法转换为一个绑定到类的方法,调用时自动传递类本身作为第一个参数(cls
)。
示例代码:
class MyClass:@classmethoddef class_method(cls, x, y):return cls(x, y) # 创建类的实例# 等价于手动实现
def class_method(cls, x, y):return cls(x, y)class MyClass:class_method = classmethod(class_method) # 使用classmethod类包装
2. 底层实现
classmethod
也是一个描述符类,其 __get__
方法返回一个绑定了类的函数:
调用过程:
obj = MyClass()
obj.class_method(1, 2) # 等价于调用 MyClass.class_method(MyClass, 1, 2)
8.4、对比与验证
1. 验证描述符行为
通过查看方法的类型验证它们是描述符:
class MyClass:def instance_method(self):pass@staticmethoddef static_method():pass@classmethoddef class_method(cls):passprint(type(MyClass.instance_method)) # <class 'function'>(普通函数,是描述符)
print(type(MyClass.static_method)) # <class 'function'>(静态方法,是描述符)
print(type(MyClass.class_method)) # <class 'method'>(类方法,已绑定)obj = MyClass()
print(type(obj.instance_method)) # <class 'method'>(实例方法,已绑定)
print(type(obj.static_method)) # <class 'function'>(静态方法,未绑定)
print(type(obj.class_method)) # <class 'method'>(类方法,已绑定)
2. 手动实现装饰器
使用自定义描述符模拟 @classmethod
的行为:
class MyClassMethod:def __init__(self, func):self.func = funcdef __get__(self, instance, owner=None):if owner is None:owner = type(instance)def wrapper(*args, **kwargs):return self.func(owner, *args, **kwargs)return wrapper# 使用自定义装饰器
class MyClass:@MyClassMethod # 等价于 @classmethoddef create(cls, value):return cls(value)obj = MyClass.create(42) # 自动传递 MyClass 作为第一个参数
8.5、应用场景与优势
1. @staticmethod
的优势
- 减少命名空间污染:将工具函数封装在类内部,避免全局函数。
- 提高代码内聚性:相关功能集中在类中,便于维护。
示例1:
class MathUtils:@staticmethoddef is_prime(n):if n < 2:return Falsefor i in range(2, int(n**0.5) + 1):if n % i == 0:return Falsereturn True
示例2:
class StaticMethod:def __init__(self, func):self.func = func # 存储原始函数def __get__(self, instance, owner=None):"""描述符协议:返回原始函数,不绑定任何对象"""return self.func # 直接返回原始函数,不传递self或cls
2. @classmethod
的优势
- 工厂方法:创建实例的替代构造函数(如从配置文件创建)。
- 修改类状态:直接操作类变量,不依赖实例。
示例1:
class Person:count = 0def __init__(self, name):self.name = namePerson.count += 1@classmethoddef get_count(cls):return cls.count@classmethoddef create_from_dict(cls, data):return cls(data["name"])
示例2:
class ClassMethod:def __init__(self, func):self.func = func # 存储原始函数def __get__(self, instance, owner=None):"""描述符协议:返回绑定类的方法"""if owner is None:owner = type(instance)def bound_method(*args, **kwargs):"""绑定类的方法,自动将类作为第一个参数传递"""return self.func(owner, *args, **kwargs)return bound_method # 返回绑定类的方法
8.6、总结
@staticmethod
通过描述符机制将方法转换为普通函数,调用时不传递任何隐式参数。@classmethod
通过描述符机制将方法绑定到类,调用时自动传递类作为第一个参数。- 两者本质上都是通过描述符(Descriptor)协议实现的,是 Python 元编程的基础工具之一。
8.6. @property的实现原理
1、@property
的本质
@property
是 Python 内置的数据描述符,用于将方法转换为可像属性一样访问的特殊对象。它允许定义只读属性、读写属性和删除属性,并在访问时自动执行相应的方法。
核心功能:
- 拦截属性的访问(
getter
)、设置(setter
)和删除(deleter
)操作。- 隐藏内部实现细节,提供简洁的属性接口。
2、描述符协议与 @property
@property
的实现基于描述符协议的三个核心方法:
__get__(self, instance, owner)
:获取属性值时调用。__set__(self, instance, value)
:设置属性值时调用。__delete__(self, instance)
:删除属性时调用。
简化版 property
描述符实现:
class Property:def __init__(self, fget=None, fset=None, fdel=None, doc=None):self.fget = fget # 获取属性的方法self.fset = fset # 设置属性的方法self.fdel = fdel # 删除属性的方法self.__doc__ = doc # 文档字符串def __get__(self, instance, owner=None):if instance is None:return self # 通过类访问时返回描述符本身if self.fget is None:raise AttributeError("属性不可读")return self.fget(instance) # 调用获取方法,传递实例def __set__(self, instance, value):if self.fset is None:raise AttributeError("属性不可写")self.fset(instance, value) # 调用设置方法,传递实例和值def __delete__(self, instance):if self.fdel is None:raise AttributeError("属性不可删除")self.fdel(instance) # 调用删除方法,传递实例def getter(self, fget):"""创建一个新的property实例,更新fget方法"""return type(self)(fget, self.fset, self.fdel, self.__doc__)def setter(self, fset):"""创建一个新的property实例,更新fset方法"""return type(self)(self.fget, fset, self.fdel, self.__doc__)def deleter(self, fdel):"""创建一个新的property实例,更新fdel方法"""return type(self)(self.fget, self.fset, fdel, self.__doc__)
3、@property
的工作流程
1. 基本用法
class Person:def __init__(self, age):self._age = age # 私有属性@propertydef age(self):"""获取年龄的方法(getter)"""return self._age@age.setterdef age(self, value):"""设置年龄的方法(setter)"""if value < 0:raise ValueError("年龄不能为负数")self._age = value
2. 等价的手动实现
class Person:def __init__(self, age):self._age = agedef get_age(self):return self._agedef set_age(self, value):if value < 0:raise ValueError("年龄不能为负数")self._age = value# 使用自定义Property描述符age = Property(get_age, set_age)
4、描述符如何拦截属性访问
当通过实例访问被 @property
装饰的属性时,Python 会自动触发描述符的 __get__
、__set__
或 __delete__
方法:
1. 获取属性
p = Person(25)
print(p.age) # 触发 Property.__get__(p, Person)
2. 设置属性
p.age = 30 # 触发 Property.__set__(p, 30)
3. 删除属性
del p.age # 触发 Property.__delete__(p)
5、验证描述符行为
通过自定义描述符验证 @property
的行为:
class DebugProperty:def __init__(self, fget=None):self.fget = fgetdef __get__(self, instance, owner=None):print(f"获取属性(instance={instance}, owner={owner})")return self.fget(instance) if instance else selfdef setter(self, fset):self.fset = fsetreturn selfdef __set__(self, instance, value):print(f"设置属性为 {value}")if not hasattr(self, 'fset'):raise AttributeError("属性不可写")self.fset(instance, value)# 使用自定义描述符
class Circle:def __init__(self, radius):self._radius = radius@DebugProperty # 等价于@propertydef radius(self):return self._radius@radius.setterdef radius(self, value):self._radius = value# 验证
c = Circle(5)
print(c.radius) # 输出:获取属性(instance=<__main__.Circle object...>, owner=<class '__main__.Circle'>) 5
c.radius = 10 # 输出:设置属性为 10
6、@property
的优势
-
接口一致性:通过属性语法访问,隐藏方法调用的复杂性。
# 无@property print(p.get_age()) # 方法调用# 有@property print(p.age) # 属性访问
-
数据验证:在 setter 中添加逻辑,确保数据合法性。
@age.setter def age(self, value):if value < 0:raise ValueError("年龄不能为负数")self._age = value
-
计算属性:动态生成属性值,无需存储中间结果。
@property def area(self):return 3.14 * self._radius ** 2 # 每次访问时计算
7、总结
@property
是数据描述符:通过实现__get__
、__set__
和__delete__
方法,拦截属性的访问、设置和删除操作。- 装饰器语法糖:
@property
和@attr.setter
实际上是创建和修改描述符实例的过程。- 描述符优先级:数据描述符(如
@property
)的优先级高于实例字典,确保属性访问被正确拦截。
这些示例涵盖了Python面向对象编程的核心概念,包括类定义、继承、多态、特殊方法、属性访问控制、静态和类方法,以及抽象基类等。