【Python全栈开发】第4讲 | 面向对象进阶:从“封装继承”到“设计模式”的蜕变

1. 灵魂拷问:为什么我们要搞 OOP?

兄弟们,如果你还在写那种"一个脚本从头拉到尾"的代码,那这一讲你得坐稳了。

面向对象编程(Object-Oriented Programming, OOP)不是为了把简单的事情搞复杂,而是为了在大规模协作时,代码不至于变成一团乱麻

  • 过程式编程:像是在写一份详细的"炒菜步骤"。
  • 面向对象编程:像是在雇佣一个"专业厨师"。你不需要知道他怎么切菜、怎么控温,你只需要告诉他:“厨师,给我来份宫保鸡丁!”

今天,咱们就来聊聊怎么从一个"写步骤的"变成一个"雇人的"。


2. 基础:类(Class)与对象(Object)

2.1 模具与成品

  • 类 (Class):就是一张"图纸"或者一个"模具"。它定义了东西长啥样,能干啥。
  • 对象 (Object):就是根据图纸造出来的"实物"。
classRobot:def__init__(self, name, version):# self 就是对象自己,像是每个人的身份证 self.name = name self.version = version defsay_hello(self):print(f"你好!我是机器人 {self.name},版本号:{self.version}")# 造两个机器人 r1 = Robot("小爱","v1.0") r2 = Robot("小度","v2.0") r1.say_hello() r2.say_hello()

2.2 别再问 self 是干啥的了

self 就像是函数里的一个占位符。当你喊 r1.say_hello() 时,Python 自动把 r1 这个实例传给了 self。这样函数才知道该打印"小爱"还是"小度"。

2.3 类与对象的内存布局

理解内存布局是掌握面向对象的关键。让我们看看 Python 中类和对象是如何在内存中存储的:

classPerson: species ="Homo sapiens"# 类属性(存在类对象中)def__init__(self, name, age): self.name = name # 实例属性(存在实例对象中) self.age = age defgreet(self):# 实例方法returnf"Hello, I'm {self.name}"# 内存布局分析 p1 = Person("Alice",25) p2 = Person("Bob",30)# 类属性是所有实例共享的print(Person.species)# Homo sapiensprint(p1.species)# Homo sapiens(通过实例访问类属性)print(p2.species)# Homo sapiens# 修改类属性会影响所有实例 Person.species ="Human"print(p1.species)# Human# 但给实例赋值会创建实例属性,不影响其他实例 p1.species ="Modified"print(p1.species)# Modified(实例属性遮蔽类属性)print(p2.species)# Human(仍然访问类属性)print(Person.species)# Human

内存布局要点

  • 类对象:存储类属性、方法定义,只有一份
  • 实例对象:存储实例属性,每个实例独立一份
  • 方法查找:实例方法实际存储在类中,实例通过 __class__ 引用找到方法

3. 三大特性:封装、继承、多态

3.1 封装 (Encapsulation):把脏活累活藏起来

你不希望别人随便改你的内部数据。在 Python 里,用双下划线 __ 就能把变量藏起来。

classBankAccount:def__init__(self, balance): self.__balance = balance # 私有变量,外面看不到defdeposit(self, amount):if amount >0: self.__balance += amount print(f"存入成功,当前余额:{self.__balance}")defget_balance(self):return self.__balance account = BankAccount(1000)# print(account.__balance) # 这行会报错!print(account.get_balance())# 只能通过我给你的接口来查

3.2 继承 (Inheritance):少写代码的捷径

如果你要写"猫"和"狗",它们都有"吃"和"睡"的功能,那就先写个"动物"类。

classAnimal:defeat(self):print("正在吃东西...")classDog(Animal):# 狗继承了动物defbark(self):print("汪汪汪!") my_dog = Dog() my_dog.eat()# 继承来的 my_dog.bark()# 自己有的

3.3 多态 (Polymorphism):鸭子类型

Python 的多态特别佛系。只要你长得像鸭子,叫起来也像鸭子,那我就把你当鸭子。


4. 深入:MRO(方法解析顺序)算法详解

当类存在多重继承时,Python 如何决定调用哪个方法?这就是 MRO(Method Resolution Order)要解决的问题。

4.1 什么是 MRO

MRO 定义了 Python 查找方法和属性的顺序。Python 3 使用 C3 线性化算法 来计算 MRO。

classA:defmethod(self):print("A.method")classB(A):defmethod(self):print("B.method")classC(A):defmethod(self):print("C.method")classD(B, C):# 多重继承pass# 查看 MROprint(D.__mro__)# 输出:(<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

4.2 C3 线性化算法的核心规则

  1. 子类优先于父类:先查找子类,再查找父类
  2. 单调性:如果 C 是 D 的父类,那么在 C 的 MRO 中出现的类,在 D 的 MRO 中也以相同顺序出现
  3. 从左到右:按照继承列表中的顺序查找
# 复杂继承示例classBase:defgreet(self):print("Base greet")classMixin1(Base):defgreet(self):print("Mixin1 greet")super().greet()classMixin2(Base):defgreet(self):print("Mixin2 greet")super().greet()classCombined(Mixin1, Mixin2):defgreet(self):print("Combined greet")super().greet()# MRO: Combined -> Mixin1 -> Mixin2 -> Base -> object c = Combined() c.greet()# 输出:# Combined greet# Mixin1 greet# Mixin2 greet# Base greet

4.3 使用 super() 的正确姿势

super() 不是调用父类,而是按照 MRO 顺序调用下一个类。

classA:def__init__(self):print("A.__init__")super().__init__()classB(A):def__init__(self):print("B.__init__")super().__init__()classC(A):def__init__(self):print("C.__init__")super().__init__()classD(B, C):def__init__(self):print("D.__init__")super().__init__() d = D()# 输出顺序:D -> B -> C -> A# 注意:A 的 super() 调用 object.__init__(),什么都不做

5. 进阶:描述符(Descriptor)机制

描述符是 Python 中实现属性访问控制的核心机制。理解描述符,你就理解了 @property@staticmethod@classmethod 的本质。

5.1 什么是描述符

描述符协议:实现了 __get____set____delete__ 方法的类就是描述符。

classValidator:"""一个描述符,用于验证属性值"""def__init__(self, min_value, max_value): self.min_value = min_value self.max_value = max_value self.name =Nonedef__set_name__(self, owner, name):"""当描述符被赋值给类属性时调用""" self.name = name self.storage_name =f"_{name}"def__get__(self, instance, owner):"""获取属性值时调用"""if instance isNone:return self returngetattr(instance, self.storage_name,None)def__set__(self, instance, value):"""设置属性值时调用"""ifnotisinstance(value,(int,float)):raise TypeError(f"{self.name} must be a number")ifnot self.min_value <= value <= self.max_value:raise ValueError(f"{self.name} must be between {self.min_value} and {self.max_value}")setattr(instance, self.storage_name, value)classPerson: age = Validator(0,150)# age 是一个描述符实例 salary = Validator(0,1000000)def__init__(self, name, age, salary): self.name = name self.age = age # 触发描述符的 __set__ self.salary = salary # 使用 p = Person("Alice",25,50000)print(p.age)# 25# p.age = 200 # ValueError: age must be between 0 and 150# p.salary = "high" # TypeError: salary must be a number

5.2 描述符的类型

类型实现的方法说明
非数据描述符只有 __get__实例属性优先于描述符
数据描述符__set____delete__描述符优先于实例属性
classNonDataDescriptor:"""非数据描述符"""def__get__(self, instance, owner):return"from descriptor"classDataDescriptor:"""数据描述符"""def__get__(self, instance, owner):return"from descriptor"def__set__(self, instance, value):print("Setting value")classTestNonData: attr = NonDataDescriptor()classTestData: attr = DataDescriptor()# 非数据描述符:实例属性优先 t1 = TestNonData() t1.attr ="from instance"print(t1.attr)# from instance# 数据描述符:描述符优先 t2 = TestData() t2.attr ="from instance"# 触发 __set__print(t2.attr)# from descriptor(__get__ 仍然被调用)

5.3 property 的本质

@property 其实就是用描述符实现的:

# property 的简化版实现classProperty:def__init__(self, fget=None, fset=None, fdel=None): self.fget = fget self.fset = fset self.fdel = fdel def__get__(self, instance, owner):if instance isNone:return self if self.fget isNone:raise AttributeError("can't get attribute")return self.fget(instance)def__set__(self, instance, value):if self.fset isNone:raise AttributeError("can't set attribute") self.fset(instance, value)defsetter(self, fset): self.fset = fset return self # 使用自定义 PropertyclassCircle:def__init__(self, radius): self._radius = radius @Propertydefradius(self):return self._radius @radius.setterdefradius(self, value):if value <0:raise ValueError("Radius cannot be negative") self._radius = value 

6. 进阶:属性查找机制

理解 Python 如何查找属性,是掌握面向对象编程的关键。

6.1 属性查找顺序

对于 obj.attr,Python 按以下顺序查找:

  1. 数据描述符:在 obj.__class__ 中查找 attr,如果是数据描述符,返回描述符的值
  2. 实例字典:在 obj.__dict__ 中查找 attr
  3. 非数据描述符:在 obj.__class__ 及其父类的 __dict__ 中查找 attr
  4. 类属性:在 obj.__class__ 及其父类的 __dict__ 中查找 attr
  5. __getattr__:如果以上都没找到,调用 __getattr__
classDescriptor:"""数据描述符"""def__get__(self, instance, owner):return"descriptor value"def__set__(self, instance, value):passclassMyClass: attr = Descriptor()def__init__(self): self.__dict__['attr']="instance value"def__getattr__(self, name):returnf"{name} not found, using getattr" obj = MyClass()print(obj.attr)# descriptor value(数据描述符优先于实例属性)# 如果描述符没有 __set__classNonDataDescriptor:def__get__(self, instance, owner):return"descriptor value"classMyClass2: attr = NonDataDescriptor()def__init__(self): self.attr ="instance value" obj2 = MyClass2()print(obj2.attr)# instance value(实例属性优先于非数据描述符)

6.2 自定义属性访问

classAttributeTracer:"""追踪所有属性访问"""def__init__(self): self._data ={}def__getattribute__(self, name):"""拦截所有属性访问"""if name.startswith('_'):returnobject.__getattribute__(self, name)print(f"Getting: {name}")returnobject.__getattribute__(self,'_data').get(name)def__setattr__(self, name, value):"""拦截所有属性设置"""if name.startswith('_'):object.__setattr__(self, name, value)else:print(f"Setting: {name} = {value}")object.__getattribute__(self,'_data')[name]= value def__getattr__(self, name):"""属性不存在时调用"""print(f"Attribute {name} not found")returnNone obj = AttributeTracer() obj.x =10# Setting: x = 10print(obj.x)# Getting: x -> 10print(obj.y)# Getting: y -> Attribute y not found -> None

7. 进阶:元类(Metaclass)入门

元类是"类的类",它控制类的创建过程。理解元类,你就站在了 Python 面向对象编程的顶端。

7.1 什么是元类

在 Python 中,一切皆对象。类也是对象,而创建类的"东西"就是元类。默认情况下,所有类都是由 type 创建的。

classMyClass:pass# MyClass 是 type 的实例print(type(MyClass))# <class 'type'># 等价于 MyClass2 =type('MyClass2',(),{})print(type(MyClass2))# <class 'type'>

7.2 自定义元类

通过继承 type,我们可以控制类的创建过程:

classSingletonMeta(type):"""单例模式元类""" _instances ={}def__call__(cls,*args,**kwargs):"""控制实例创建"""if cls notin cls._instances: cls._instances[cls]=super().__call__(*args,**kwargs)return cls._instances[cls]classDatabase(metaclass=SingletonMeta):"""使用元类实现单例"""def__init__(self, connection_string): self.connection_string = connection_string print(f"Initializing database with {connection_string}") db1 = Database("postgresql://localhost/db") db2 = Database("mysql://localhost/db")print(db1 is db2)# True(同一个实例)print(db1.connection_string)# postgresql://localhost/db

7.3 元类的 newinit

classAutoRegisterMeta(type):"""自动注册子类的元类""" registry ={}def__new__(mcs, name, bases, namespace):"""创建类对象时调用"""print(f"Creating class: {name}")# 可以修改类的属性和方法 namespace['created_by']='AutoRegisterMeta' cls =super().__new__(mcs, name, bases, namespace)# 注册类(排除元类本身)if name !='BaseModel': mcs.registry[name]= cls return cls def__init__(cls, name, bases, namespace):"""初始化类对象时调用"""print(f"Initializing class: {name}")super().__init__(name, bases, namespace)classBaseModel(metaclass=AutoRegisterMeta):"""所有模型的基类"""passclassUser(BaseModel):passclassProduct(BaseModel):passprint(AutoRegisterMeta.registry)# {'User': <class '__main__.User'>, 'Product': <class '__main__.Product'>}

7.4 元类的实际应用

classORMMeta(type):"""简化 ORM 模型定义的元类"""def__new__(mcs, name, bases, namespace):# 收集所有字段定义 fields ={}for key, value inlist(namespace.items()):ifisinstance(value, Field): fields[key]= value value.name = key namespace['_fields']= fields returnsuper().__new__(mcs, name, bases, namespace)classField:"""字段定义"""def__init__(self, field_type, nullable=True): self.field_type = field_type self.nullable = nullable self.name =Nonedef__repr__(self):returnf"Field({self.field_type})"classModel(metaclass=ORMMeta):"""ORM 基类"""def__init__(self,**kwargs):for name, field in self._fields.items(): value = kwargs.get(name)if value isNoneandnot field.nullable:raise ValueError(f"{name} is required")setattr(self, name, value)defsave(self):"""模拟保存到数据库""" data ={name:getattr(self, name)for name in self._fields}print(f"Saving {self.__class__.__name__}: {data}")classUser(Model):id= Field(int, nullable=False) name = Field(str, nullable=False) email = Field(str)# 使用 user = User(id=1, name="Alice", email="[email protected]") user.save()# Saving User: {'id': 1, 'name': 'Alice', 'email': '[email protected]'}

8. 魔法方法:让你的类更"Pythonic"

Python 里有很多双下划线开头结尾的方法(Dunder Methods),它们能让你的对象支持 +, -, len() 等操作。

classBook:def__init__(self, title, pages): self.title = title self.pages = pages def__str__(self):# 当你 print(book) 时,会调用这个returnf"《{self.title}》- 共{self.pages}页"def__len__(self):# 当你 len(book) 时,会调用这个return self.pages def__eq__(self, other):# 比较两个 Book 对象ifnotisinstance(other, Book):returnFalsereturn self.title == other.title and self.pages == other.pages def__add__(self, other):# 实现 Book + Bookifisinstance(other, Book):return Book(f"{self.title} + {other.title}", self.pages + other.pages)return NotImplemented my_book = Book("Python 从入门到放弃",999)print(my_book)print(f"这本书厚度:{len(my_book)}") book2 = Book("Python 从入门到放弃",999)print(my_book == book2)# True combined = my_book + book2 print(combined)# 《Python 从入门到放弃 + Python 从入门到放弃》- 共1998页

9. 综合实战:简易 RPG 战斗系统

咱们来个硬核的,用 OOP 写个简单的游戏角色对战逻辑。

import random classCharacter:def__init__(self, name, hp, ad): self.name = name self.hp = hp self.ad = ad # 攻击力defis_alive(self):return self.hp >0defattack(self, target): damage = self.ad + random.randint(-5,5)print(f"【{self.name}】发起攻击,对【{target.name}】造成了 {damage} 点伤害!") target.receive_damage(damage)defreceive_damage(self, damage): self.hp -= damage if self.hp <0: self.hp =0print(f"【{self.name}】剩余血量:{self.hp}")classWarrior(Character):def__init__(self, name):super().__init__(name, hp=150, ad=20)# 战士血厚defreceive_damage(self, damage):# 战士有 20% 几率格挡if random.random()<0.2:print(f"【{self.name}】格挡了攻击!") damage = damage //2super().receive_damage(damage)classMage(Character):def__init__(self, name):super().__init__(name, hp=80, ad=45)# 法师攻高defattack(self, target):# 法师有 30% 几率暴击if random.random()<0.3: damage =int(self.ad *1.5)+ random.randint(-5,5)print(f"【{self.name}】发动暴击!")else: damage = self.ad + random.randint(-5,5)print(f"【{self.name}】发起攻击,对【{target.name}】造成了 {damage} 点伤害!") target.receive_damage(damage)# 开始战斗 p1 = Warrior("盖伦") p2 = Mage("拉克丝")print("--- 战斗开始 ---")while p1.is_alive()and p2.is_alive(): p1.attack(p2)ifnot p2.is_alive():print(f"\n恭喜【{p1.name}】获得胜利!")break p2.attack(p1)ifnot p1.is_alive():print(f"\n恭喜【{p2.name}】获得胜利!")break

10. 避坑小贴士(老司机的叮嘱)

  1. 别为了 OOP 而 OOP:如果一个功能写个函数就能搞定,千万别强行封装个类。过度设计是万恶之源。
  2. 优先使用组合而非继承:如果你发现继承树超过 3 层,说明你的架构出问题了。记住:“Has-a” 往往比 “Is-a” 更灵活
  3. self 别漏写:在类的方法里访问属性,必须带 self.,这是新手最容易犯的低级错误。
  4. 理解 MRO 再使用多重继承:多重继承很强大,但如果不理解 MRO,很容易踩坑。
  5. 慎用元类:元类是强大的工具,但会增加代码复杂度,只在必要时使用。

11. 实战演练:巩固你的内功

OOP 是设计出来的,不是写出来的。动动手,感受一下"上帝视角"。

Read more

彻底解决 Codex / Copilot 修改中文乱码【含自动化解决方案】

彻底解决 Codex / Copilot 修改中文乱码【含自动化解决方案】

引言 在使用 GitHub Copilot 或 OpenAI Codex 自动重构代码时,你是否遇到过这样的尴尬:AI 生成的代码逻辑完美,但原本注释里的中文却变成了 我爱中文 这样的乱码?有时候这种字符甚至会污染正确的代码,带来巨大的稳定性隐患。 一、 问题核心:被忽视的“终端中转” 乱码的根源不在于 AI 的大脑,也不在于编辑器的显示,而在于执行链路的编码不一致。 Copilot/Codex 在执行某些修改任务(如:重构整个文件或批量替换)时,往往会通过终端调用系统指令。由于 Windows 终端(PowerShell/CMD)默认使用 GBK 编码,它在处理 AI 传来的 UTF-8 字节时会发生“误读”,导致写入文件的内容从源头上就损坏了。

By Ne0inhk
【优质开源项目】AIGC开源推荐-全球情报监控平台worldmonitor

【优质开源项目】AIGC开源推荐-全球情报监控平台worldmonitor

1.概述 World Monitor 是一个开源的实时情报/监测仪表盘,聚合多类数据源(新闻、地理/卫星、航运/空中、财经、威胁情报等),提供交互式地理视图、AI 摘要、事件聚合与报警,支持 Web / PWA / Tauri 桌面三种运行方式,并可通过变体(WORLD / TECH / FINANCE)切换功能集。 2. 总体技术架构(分层视角) 客户端层(Browser / PWA / Tauri desktop) * • React + TypeScript + Vite 构建。 * • 地图/可视化:deck.gl(WebGL 3D globe)、MapLibre GL、D3

By Ne0inhk
FossFLOW:开源等距图表工具,为技术文档注入立体活力!

FossFLOW:开源等距图表工具,为技术文档注入立体活力!

文章简介:FossFLOW是一款创新的开源等距图表工具,专为技术文档设计。它通过立体视角将复杂的系统架构转化为直观的3D图表,支持拖放式操作和离线使用,让技术图表变得生动易懂。无需注册,数据安全存储在本地,并提供JSON导入导出功能。无论是Docker快速部署还是在线体验,FossFLOW都能为架构图、流程图注入立体活力,是提升技术文档表现力的得力助手。 你是否曾经为了绘制清晰的技术架构图或系统流程图而烦恼?是否觉得传统的平面图表难以表达复杂的层次关系?今天,我要向大家介绍一款令人惊艳的开源工具——FossFLOW,它能让你的技术图表瞬间变得立体、生动! 🌟 什么是FossFLOW? FossFLOW 是一款功能强大的、开源的渐进式 Web 应用(PWA),专为创建精美的等距图表而设计。它基于 React 和 Isoflow(现已 fork 并以 fossflow 名称发布到 NPM)库构建,完全在浏览器中运行,并支持离线使用,让你随时随地都能创作出专业级的技术图表! github地址:https://github.com/stan-smith/FossFLOW/ 在线地

By Ne0inhk