当前位置: 首页 > news >正文

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. 继承与多态的优势

  1. 代码复用:通过继承减少重复代码。
  2. 可扩展性:新增子类不影响现有代码(开闭原则)。
  3. 灵活性:通过多态统一接口,不同实现动态切换。
  4. 可维护性:通过抽象基类明确接口规范,降低耦合度。

三. 特殊方法(魔术方法)

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 实现@propertyclassmethodstaticmethod等功能的底层机制,也是自定义属性逻辑的强大工具。

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. 描述符的优先级

描述符在类中的优先级由以下规则决定(从高到低):

  1. 实例字典(__dict__:实例直接赋值的属性会覆盖描述符。
    p.name = "李四"  # 直接修改实例字典,绕过描述符的__set__
    
  2. 数据描述符:优先于实例字典。
  3. 非数据描述符:低于实例字典。
  4. 普通属性:无描述符时,直接访问实例字典。

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 的高级特性,用于实现属性的复杂控制逻辑,是@propertyclassmethod等的底层机制。
  • **@property** 是描述符的简化用法,适合单个属性的读写控制,常用于数据验证和计算属性。
  • 合理使用描述符和属性,可以让代码更具封装性和可维护性,避免直接操作属性带来的安全隐患。

        理解描述符和属性的工作原理,有助于深入掌握 Python 的面向对象编程,并在需要时实现高度定制化的属性行为。

五. 静态方法和类方法

5.1、基本概念

类型绑定对象装饰器第一个参数调用方式
实例方法实例self(实例本身)obj.method()
类方法@classmethodcls(类本身)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、封装的优势

  1. 数据安全:通过访问控制避免外部直接修改敏感数据。

    # 错误示例:直接修改内部状态
    account.balance = -1000  # 可能导致账户余额异常# 正确示例:通过方法控制修改
    account.deposit(100)     # 经过验证的操作
    
  2. 代码可维护性:内部实现可以自由修改,不影响外部调用。

    # 原实现:直接存储平均值
    self._mean = sum(data) / len(data)# 新实现:改为动态计算(接口不变)
    @property
    def mean(self):return sum(self._data) / len(self._data)
    
  3. 简化接口:隐藏复杂细节,提供简洁的 API。

    # 用户只需调用高层方法,无需关心内部步骤
    processor.process()  # 而非手动调用多个方法
    

5、常见误区

  1. 过度使用双下划线

    • 双下划线主要用于防止子类覆盖,而非完全禁止外部访问。
    • 大多数情况下,单下划线约定已足够。
  2. 认为双下划线是 “真正私有”

    • Python 没有真正的私有属性,双下划线只是名称修饰,仍可通过_类名__属性名访问。
  3. 忽略属性的验证逻辑

    • 直接使用公共属性(如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 的liststr都支持__len__方法)。

选择建议

  • 需要强制子类实现接口时,使用抽象基类。
  • 追求灵活性和简洁性时,优先使用鸭子类型(如len(obj)不关心obj是否继承自某个基类)。

7.7、注意事项

  1. 抽象基类不能实例化

    animal = Animal()  # 报错:TypeError: Can't instantiate abstract class Animal with abstract methods speak, move
    
  2. 子类可部分实现抽象方法

    • 若子类未实现所有抽象方法,则子类仍为抽象基类,无法实例化。
  3. 抽象方法可以有默认实现

    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 的优势

  1. 接口一致性:通过属性语法访问,隐藏方法调用的复杂性。

    # 无@property
    print(p.get_age())  # 方法调用# 有@property
    print(p.age)        # 属性访问
    
  2. 数据验证:在 setter 中添加逻辑,确保数据合法性。

    @age.setter
    def age(self, value):if value < 0:raise ValueError("年龄不能为负数")self._age = value
    
  3. 计算属性:动态生成属性值,无需存储中间结果。

    @property
    def area(self):return 3.14 * self._radius ** 2  # 每次访问时计算
    

7、总结

  • @property 是数据描述符:通过实现 __get____set__ 和 __delete__ 方法,拦截属性的访问、设置和删除操作。
  • 装饰器语法糖@property 和 @attr.setter 实际上是创建和修改描述符实例的过程。
  • 描述符优先级:数据描述符(如 @property)的优先级高于实例字典,确保属性访问被正确拦截。

        这些示例涵盖了Python面向对象编程的核心概念,包括类定义、继承、多态、特殊方法、属性访问控制、静态和类方法,以及抽象基类等。

相关文章:

  • 钉钉机器人-自定义卡片推送快速入门
  • keil一键烧录boot和app程序
  • jojojojojo
  • Hexo-butterfly友情链接页面优化
  • MySQL-DQL数据查询语句深度解析与实战指南
  • 保护地线与串扰-信号完整性分析
  • day 51 python打卡
  • Redis事务与驱动的学习(一)
  • Unity Demo-3DRaceCar详解
  • MiniCPM4端侧AI模型
  • final在java中的作用
  • 清理 Docker 容器日志文件方法
  • 图的遍历模板
  • 嵌入式PADS中生产资料导出操作与实现
  • Python训练打卡Day50
  • C++包管理器vcpkg的使用
  • 【Erdas实验教程】021:遥感图像辐射增强( 查找表拉伸)
  • 设计模式-创建型模式(详解)
  • 车载功能框架 --- 整车安全策略
  • 贪心算法经典问题
  • 教你做文案的网站推荐/福州seo外包公司
  • 国外做多媒体展览的网站/推广普通话内容100字
  • 余姚本地网站排名/it培训学校哪家好
  • 天津网站建设的公司/广州专业seo公司
  • 学些网站制作/推广app赚佣金接单平台
  • 网站样式模板/网站设计模板网站