news 2026/9/19 5:07:20

Python面向对象实战:从函数到类的演进、核心概念与封装继承多态应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python面向对象实战:从函数到类的演进、核心概念与封装继承多态应用

1. 为什么从函数式写法转向类——一个用户管理脚本的演进过程

1.1 第一版:函数加全局变量,代码量少但隐患多

先看一段我早期写过的Python脚本。当时想做一个简单的用户管理小工具,需求也不复杂:能新增用户、删除用户、打印所有用户列表。我用了最直观的写法,全部堆在函数里:

users = [] def add_user(name, age): users.append({"name": name, "age": age}) def delete_user(name): for i, user in enumerate(users): if user["name"] == name: del users[i] return True return False def print_users(): for user in users: print(f"姓名: {user['name']}, 年龄: {user['age']}") add_user("张三", 28) add_user("李四", 31) print_users()

这段代码能跑,而且在脚本只有几十行的时候非常好用。问题出在什么地方?一旦项目规模涨起来,比如加一个订单模块、再一个商品模块,你就会发现全局变量越来越难管。所有函数都能直接操作users这个列表,你根本没法保证某个模块不会意外修改它。另一个隐患是函数之间的参数传递:今天add_user需要nameage,明天要加一个phone字段,所有相关函数全得跟着改参数列表。

我当时遇到的实际场景是:一个自动化脚本写到大概三百行,函数有十几个,全局变量有七八个。某次运行中突然发现users列表里混进了一条脏数据,排查了半天,发现是某个工具函数在循环里误调用了users.append。那一刻我意识到,纯函数式写法在当前这个阶段已经到头了,我需要把“数据”和“操作数据的方法”绑在一起——这就是Python面向对象最核心的动机,也是我当时翻开“python编程语法基础笔记”里面向对象章节时最迫切的需求。

1.2 第二版:用类把“数据”和“操作数据的方法”绑在一起

同样是用户管理,用类重写之后长这样:

class UserManager: def __init__(self): self.users = [] def add_user(self, name, age): self.users.append({"name": name, "age": age}) def delete_user(self, name): for i, user in enumerate(self.users): if user["name"] == name: del self.users[i] return True return False def print_users(self): for user in self.users: print(f"姓名: {user['name']}, 年龄: {user['age']}") manager = UserManager() manager.add_user("张三", 28) manager.add_user("李四", 31) manager.print_users()

对比一下,逻辑几乎一模一样,但结构发生了本质变化:

  • users不再是全局变量,而是通过__init__挂到实例上的self.users
  • 所有操作users的函数变成了类的方法,天然和这份数据绑定在一起
  • 如果想创建两套互不干扰的用户列表,直接manager1 = UserManager()manager2 = UserManager()就行

这个切换给我最直观的感受是:代码的“内聚性”提高了。数据的定义、操作数据的入口、数据的边界都在同一个地方,找问题和改逻辑的时候不用满文件乱翻。

1.3 什么时候该写类、什么时候继续用函数

面向对象确实好,但很多人容易走向另一个极端:什么东西都塞进类里,反而把简单的事情搞复杂了。我总结了一个很实用的判断标准:

情况建议
只有一组独立的计算逻辑,比如把字符串转成日期、计算两个数之和用函数
需要长期保存一组状态,并且这些状态会被多个函数反复读取或修改用类
同一套逻辑需要生成多个互不影响的独立实体,比如多个用户、多个订单用类
只是临时把几个值打包传参,不如直接用dataclass或字典看复杂度决定

Python面向对象的精髓不在于“必须用类”,而在于“用类把复杂状态管理起来”。一个脚本里函数主导、遇到复杂实体再用类,这种混合写法在实际项目中非常常见,也完全合理。

2. 类和对象的最小可用框架:init、self 和实例化过程

2.1init不是构造函数,而是“初始化方法”

初学者最容易产生的一个误解是:__init__是构造函数,对象是在它里面创建的。这个理解在Python里并不准确,因为Python对象创建的真实流程是两步:

class Person: def __new__(cls, *args, **kwargs): # 第一步:先创建一个裸对象(一般不手动覆盖) instance = super().__new__(cls) return instance def __init__(self, name): # 第二步:给这个裸对象塞属性,完成初始化 self.name = name

实际上,当你在代码里写p = Person("张三")的时候,Python解释器先调用__new__创建一个空壳对象,然后把"张三"传进__init__去填充属性。日常开发中你基本不需要重写__new__,但理解这个流程非常有帮助,尤其是后面看源码时看到单例模式、元类这些概念,你会意识到事情的源头都在__new__那里。

我在学习“Python面向对象”时踩过的第一个坑,是在__init__外定义可变默认值:

# 错误示例 class Order: items = [] def add_item(self, item): self.items.append(item) order1 = Order() order2 = Order() order1.add_item("苹果") print(order2.items) # ['苹果'],被污染了!

这个 bug 极其隐蔽。类属性items是定义在类这块模板上的,所有实例共享同一个列表。正确做法是把可变对象放进__init__里作为实例属性:

# 正确示例 class Order: def __init__(self): self.items = [] def add_item(self, item): self.items.append(item) order1 = Order() order2 = Order() order1.add_item("苹果") print(order2.items) # []

2.2 self 到底是谁:它只是“当前实例”的引用

self是Python面向对象里一个绕不过去的概念。很多人初学时觉得这个参数很神秘,甚至有人误以为它是关键字。实际上它就是一个普通参数名,Python在调用manager.add_user("张三", 28)时,会自动把manager这个实例本身作为第一个参数传进去,等价于UserManager.add_user(manager, "张三", 28)

为了理解self,我找了一个生活化的类比:假设有一个模板叫“学生信息表”,每个学生拿到一张空表开始填写。__init__相当于“你拿到表之后,把自己的名字、班级填上去”,self就是“你手里正拿着的那张表”。不同学生手里的表互不干扰,这就是实例隔离的本质。

Python 只是约定了第一个参数叫self,换成一个别的名字也能运行:

class Demo: def __init__(this, value): this.value = value d = Demo(10) print(d.value) # 10

不报错,但我强烈建议永远不要这么干。整个Python社区的代码规范、第三方库、框架全都默认使用self,改名除了给人添堵没有任何价值。

2.3 实例属性与类属性的区别

用代码演示一下两者的区别,你就全明白了:

class Employee: company = "某某科技" # 类属性,所有实例共享 def __init__(self, name): self.name = name # 实例属性,每个实例独立 e1 = Employee("张三") e2 = Employee("李四") print(e1.company, e2.company) # 某某科技 某某科技 e1.company = "改了个名" # 注意:这里是给e1新建了一个实例属性 print(e1.company) # 改了个名 print(e2.company) # 某某科技,其他实例不受影响

注意最后一步:写e1.company = "改了个名"并没有改写类属性,而是给e1这个实例新增了一个名为company的属性,从此e1.company读取时优先命中实例属性,类属性被遮挡了。

这个知识点看起来基础,但在实际项目中特别容易引发诡异 bug。我曾经参与过一个数据采集项目,多个采集器实例同时运行,其中一个实例因为某种原因给一个类属性赋了新值,结果所有实例都跟着变了。排查了一下午,最后发现是在某段代码里误用了ClassName.attr = xxx的方式赋值。血的教训。

3. 封装、继承、多态不是考纲术语,而是代码组织的三板斧

3.1 封装:把不该暴露的细节挡住,用 @property 做可控的属性访问

很多教材把封装讲得很玄乎,什么“数据隐藏”“信息隐蔽”。用大白话讲,封装的目的是:外部代码不需要知道内部细节,只需要通过固定入口操作数据,这样你以后改内部实现的时候,不会波及外面的调用方。

Python里没有真正意义上的“私有变量”,双下划线开头的属性其实是“名字重整”(name mangling),它只是在类内部把__secret改写成了_ClassName__secret,外部依然能用改写后的名字访问。因此Python社区更常用的约定是单下划线开头,比如_name,表示“这是我们内部的变量,外部请自觉别动”。

@property是封装里一个极其好用的工具。直接看代码:

class User: def __init__(self, name, age): self.name = name self._age = age @property def age(self): """外部读取age时走这里""" return self._age @age.setter def age(self, value): if value < 0 or value > 150: raise ValueError("年龄必须在0到150之间") self._age = value u = User("张三", 30) u.age = 28 print(u.age) # 28 # u.age = 999 # 直接抛 ValueError

这个写法的价值在于:你可以在“读属性”和“写属性”的入口处统一做校验、转换、日志等逻辑,但外部调用方依然是用u.ageu.age = 28这种最自然的方式读写,不用改成u.get_age()u.set_age(28)。等你哪天想从“直接存年龄”改成“存出生日期再计算年龄”,外部代码一行都不用动,只要改@property内部实现即可。

3.2 继承:抽取公共逻辑,把变的部分留给子类

继承是面向对象里复用代码最直接的手段。遇到多个类有大量共同逻辑时,可以抽出基类,子类只写差异部分:

class Animal: def __init__(self, name): self.name = name def eat(self): print(f"{self.name} 正在吃东西") def sound(self): raise NotImplementedError("子类必须实现 sound 方法") class Dog(Animal): def sound(self): print("汪汪汪") class Cat(Animal): def sound(self): print("喵喵喵") dog = Dog("旺财") cat = Cat("咪咪") dog.eat() # 继承自Animal cat.eat() # 继承自Animal dog.sound() # 汪汪汪 cat.sound() # 喵喵喵

这里raise NotImplementedError是个很实用的技巧:基类先把“子类必须实现的接口”占好位,万一有人新建子类时忘了实现sound,调用时就会立刻报错,而不是等到后面出了奇怪数据才发觉。

继承中还有一个高频操作是调用父类方法,用super()

class Base: def __init__(self, name): self.name = name print("Base.__init__") class Child(Base): def __init__(self, name, age): super().__init__(name) # 先初始化父类属性 self.age = age print("Child.__init__")

super()的核心作用是按照方法解析顺序(MRO)找到下一个该调用的类,它不只是简单调用“父类”,在多继承时它会沿着一个线性序列依次查找。这一点后续看框架源码时特别重要,但刚入门阶段你只需要记住:子类重写了__init__之后,如果还需要父类定义的属性,记得调用一次super().__init__(...),这是新手最容易漏掉的。

3.3 多态:同一个接口,不同的实现,Python里鸭子类型天然支持

多态用一句话概括:同样的调用代码,传入不同对象,表现出不同行为。Java、C#中多态需要靠接口或抽象类来约束,而Python因为“鸭子类型”的存在,天然支持多态,写起来更自由。

“鸭子类型”来自一句话:如果一只鸟走路像鸭子、游泳像鸭子、叫声像鸭子,那它就是鸭子。放到代码里就是:一个对象能不能作为文件用,不取决于它是不是File类的实例,而取决于它有没有readwrite方法:

class FileReader: def __init__(self, path): self.path = path def read(self): with open(self.path, "r", encoding="utf-8") as f: return f.read() class StringBuilder: def __init__(self, content=""): self.content = content def read(self): return self.content.upper() def process(data_source): print(data_source.read())

这里process函数根本不在乎传进来的是文件还是字符串构建器,只要有read方法就能跑。这个特性让Python代码组织起来非常灵活。虽然Python不需要强制接口,但如果你希望约束子类必须实现某些方法,可以使用abc模块的abstractmethod,团队协作时这个约束挺有用:

from abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def area(self): pass # class Circle(Shape): ... # 不实现area的话,实例化就报错

4. 魔术方法决定一个类的“高级感”:strrepreqlt这类一定要理解

4.1strrepr的分工:给用户看 vs 给开发者看

面向对象学了一段时间之后,你会发现代码里满是print(对象),打出来的是一串看不懂的<__main__.Order object at 0x7f8a1c022e80>。这时候就该实现__str____repr__了。

两者分工不同:

  • __repr__:给开发者看,目标是“不丢信息”,最好能直接重建这个对象
  • __str__:给终端用户看,目标是“可读友好”,由print()str()调用
class Order: def __init__(self, order_id, amount): self.order_id = order_id self.amount = amount def __repr__(self): return f"Order(order_id={self.order_id!r}, amount={self.amount!r})" def __str__(self): return f"订单号:{self.order_id}, 金额:{self.amount}元" order = Order("A001", 99.5) print(repr(order)) # Order(order_id='A001', amount=99.5) print(order) # 订单号:A001, 金额:99.5元

最佳实践是:优先实现__repr__,因为Python的print在找不到__str__时会退而求其次使用__repr__。至于!r这个格式化符号,它的意思是调用repr()来格式化,这样字符串值会带上引号,调试时一眼就能看出类型。

4.2eqhash的关系,改了 equals 就得改 hash 的坑

默认情况下,两个对象比较的是“内存地址”,即a == b等价于a is b。但业务上我们往往希望“属性相同就算同一个对象”,这时候需要重写__eq__

class User: def __init__(self, uid, name): self.uid = uid self.name = name def __eq__(self, other): if not isinstance(other, User): return NotImplemented return self.uid == other.uid def __hash__(self): return hash(self.uid) u1 = User(1, "张三") u2 = User(1, "李四改名了") print(u1 == u2) # True,因为它俩uid相同

最容易被忽略的是__hash__。Python有条规定:一个类如果重写了__eq__,它默认的__hash__会被置为None,对象会变成不可哈希的,放进set或作为字典的 key 时会直接报TypeError: unhashable type: 'User'

解决方式就是像上面一样手动实现__hash__,且必须保证“相等对象的哈希值一定相等”,通常基于__eq__里用到的那几个字段来计算就行。这个坑我踩过一次,项目里用自定义对象做去重,hash 没实现,死活跑不起来,查半天才明白是__eq____hash__弄丢了。

4.3 运算符重载和上下文管理

Python 允许类重载运算符,让对象支持+<>等原生操作。最常用的场景有两个。

一是排序。给一个类实现__lt__(小于),sorted()就能直接对对象列表排序:

class Score: def __init__(self, name, value): self.name = name self.value = value def __lt__(self, other): return self.value < other.value def __repr__(self): return f"Score({self.name}, {self.value})" scores = [Score("张三", 88), Score("李四", 95), Score("王五", 72)] print(sorted(scores)) # [Score(王五, 72), Score(张三, 88), Score(李四, 95)]

二是上下文管理。实现__enter____exit__之后,对象就能配合with语句使用,这在资源管理里非常实用,比try...finally写起来清爽得多:

class ManagedFile: def __init__(self, path): self.path = path def __enter__(self): self.f = open(self.path, "w", encoding="utf-8") return self.f def __exit__(self, exc_type, exc_val, exc_tb): self.f.close() with ManagedFile("hello.txt") as f: f.write("hello")

哪怕with块里抛了异常,__exit__也会保证文件被关闭。后面查with的实现原理、以及contextlib.contextmanager装饰器时,理解这个接口是基础中的基础。

5. 一个连数据库的订单类的完整落地过程

5.1 需求描述和数据表设计

前面讲的概念都比较零散,下面我用一个完整的例子把这些知识点串起来:实现一个简单的订单管理类,数据存储在 SQLite 里,支持创建订单、查询订单、统计总金额。为了控制篇幅,表结构设计得尽量简洁:

字段名类型说明
idINTEGER PRIMARY KEY订单ID,自增
user_idTEXT用户ID
amountREAL订单金额
statusTEXT订单状态,如 pending / paid / cancelled
created_atTEXT创建时间,ISO格式

5.2 先写数据库操作层

面向对象并不排斥过程化代码。数据库连接这种低频操作用普通函数管理反而简洁:

import sqlite3 from datetime import datetime DB_PATH = "orders.db" def get_connection(): conn = sqlite3.connect(DB_PATH) return conn def init_db(): conn = get_connection() try: conn.execute(""" CREATE TABLE IF NOT EXISTS orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, amount REAL NOT NULL, status TEXT NOT NULL DEFAULT 'pending', created_at TEXT NOT NULL ) """) conn.commit() finally: conn.close()

这里每次操作都新建连接、用完就关闭,对于轻量项目完全够了。有人会纠结要不要用连接池,我的建议是:SQLite 这种嵌入式数据库根本不需要,连接本身开销很低;如果是 MySQL、PostgreSQL,再用连接池不迟。

5.3 Order 类的具体实现

核心的订单类我这样设计:

class Order: def __init__(self, user_id, amount, status="pending", order_id=None, created_at=None): self.order_id = order_id self.user_id = user_id self.amount = amount self.status = status self.created_at = created_at or datetime.now().isoformat(timespec="seconds") @classmethod def create(cls, user_id, amount): """业务方法,表示用户下了一笔新订单""" return cls(user_id=user_id, amount=amount, status="pending") def save(self): """把自身写入数据库,如果已有ID则更新""" conn = get_connection() try: if self.order_id is None: cursor = conn.execute( "INSERT INTO orders (user_id, amount, status, created_at) VALUES (?, ?, ?, ?)", (self.user_id, self.amount, self.status, self.created_at) ) conn.commit() self.order_id = cursor.lastrowid else: conn.execute( "UPDATE orders SET status = ? WHERE id = ?", (self.status, self.order_id) ) conn.commit() finally: conn.close() return self @classmethod def get_by_id(cls, order_id): """类方法,从数据库加载一个订单""" conn = get_connection() try: row = conn.execute( "SELECT id, user_id, amount, status, created_at FROM orders WHERE id = ?", (order_id,) ).fetchone() finally: conn.close() if row is None: return None return cls( order_id=row[0], user_id=row[1], amount=row[2], status=row[3], created_at=row[4] ) @staticmethod def total_amount_by_user(user_id): """静态方法,统计某用户所有已支付订单的总金额""" conn = get_connection() try: row = conn.execute( "SELECT COALESCE(SUM(amount), 0) FROM orders WHERE user_id = ? AND status = 'paid'", (user_id,) ).fetchone() finally: conn.close() return row[0] def pay(self): if self.status != "pending": raise ValueError("只有待支付订单才能支付") self.status = "paid" self.save() def __repr__(self): return f"Order(order_id={self.order_id!r}, user_id={self.user_id!r}, amount={self.amount!r}, status={self.status!r})"

这个类涵盖了很多面向对象知识点,我逐个拆一下:

  • __init__order_id=None, created_at=None默认参数设计是有讲究的:create新订单时还没有ID,从数据库加载时再填入ID,同一个__init__兼顾两种场景
  • @classmethodcreate方法是业务入口,它表达的是“用户下了一笔订单”这个语义,比直接调用构造器更贴近业务
  • @classmethodget_by_id是“从数据库加载”的入口,它负责查询并转成Order对象
  • @staticmethodtotal_amount_by_user不依赖具体某个订单实例,它是对订单这个集合的统计查询
  • pay方法展示了业务逻辑和状态流转,数据落库的操作收敛到save
  • __repr__方便调试,打印订单对象时能看到完整信息

5.4 完整跑一遍增删改查

把上面代码保存为order_demo.py,然后写一段调用脚本验证:

if __name__ == "__main__": init_db() # 创建订单并保存 order1 = Order.create("U001", 199.0) order1.save() print(order1) order2 = Order.create("U001", 88.5) order2.save() order2.pay() # 支付订单2 print(order2) # 从数据库加载 loaded = Order.get_by_id(order1.order_id) print(loaded) # 统计用户U001已支付总金额 total = Order.total_amount_by_user("U001") print(f"U001已支付总金额: {total}")

运行之后,你会看到控制台打印出订单的ID、状态变化以及统计结果。这一条链路完整串起了__init__、实例方法、类方法、静态方法、异常抛出、继承自object的魔术方法这几大块内容,读一遍代码、亲手跑一遍,我对“Python面向对象”的理解立刻从抽象概念变成了可操作的工具。

6. 面向对象里最容易翻车的几个地方

6.1 可变对象作为默认参数:函数定义时就执行一次

这是Python最经典的坑。前面提到过Order.items = []的类属性问题,其实普通函数也有同样的问题:

def append_item(item, items=[]): items.append(item) return items print(append_item("苹果")) # ['苹果'] print(append_item("香蕉")) # ['苹果', '香蕉'],注意第二次调用时列表没清空

问题根源在于:默认参数的值在函数定义时就被计算并保存了,以后每次调用都复用同一个列表对象。修正方式是用None作为默认值,在函数体内创建新列表:

def append_item(item, items=None): if items is None: items = [] items.append(item) return items

在类和对象的场景下,这个问题同样存在于__init__的参数上,比如def __init__(self, tags=[]),一定要养成可变默认参数用None的习惯。

6.2 继承中的init没有被调用

子类定义了__init__但没有调用父类的__init__,导致父类属性丢失,这是新手写继承时最常遇到的错误。看这段典型报错:

class Base: def __init__(self, name): self.name = name class Child(Base): def __init__(self, name, age): self.age = age # 忘了调用 super().__init__(name) c = Child("张三", 28) print(c.name) # AttributeError: 'Child' object has no attribute 'name'

排查思路很简单:如果子类报AttributeError: 'xxx' object has no attribute 'yyy',先检查子类__init__里有没有调用super().__init__(...),特别是当父类__init__里有属性赋值语句时,八成是这一条漏了。

6.3 循环引用与对象销毁

写了类之后,对象之间经常互相持有引用。比如订单里有用户对象,用户对象又引用一个订单列表,这种结构很容易形成循环引用。Python的垃圾回收器是能处理循环引用的,但如果对象定义了__del__方法,循环引用时__del__的行为会变得不可预测。

一个比较实用的替代方案是使用weakref模块的弱引用。弱引用不会增加对象的引用计数,适合用在“A 知道 B,但不想影响 B 的销毁”的场景:

import weakref class User: def __init__(self, name): self.name = name class Order: def __init__(self, user): # 这里不直接持有强引用,而是持有弱引用 self.user_ref = weakref.ref(user) u = User("张三") order = Order(u) del u # 用户对象此时可以被销毁

当然,日常小项目里循环引用其实很少成为瓶颈,Python的gc模块默认是开启的,能自动回收大部分循环引用。只有当你用__del__做资源清理,且对象数量很大时,才值得认真思考弱引用方案。我在爬虫项目里对每个请求对象做过类似处理,效果还可以,但绝大多数情况不用这么早优化。

6.4 为面向对象而面向对象的过度设计

写了几年代码之后,我反而对“少用类”更宽容了。有些代码写类纯粹是图“像Java一样规范”,结果把状态满天飞、耦合度不降反升。我自己现在的判断标准是:类至少要有两个以上方法且这些方法共享状态,否则写个dataclass或者干脆用字典就够了。

比如只是打包几个字段,最省事的是dataclasses

from dataclasses import dataclass @dataclass class Item: name: str price: float quantity: int = 1 item = Item("苹果", 5.5, 3) print(item) # Item(name='苹果', price=5.5, quantity=3) print(item.price) # 5.5

它自动帮你生成了__init____repr____eq__,比手写一堆魔术方法省事得多。我一直觉得,写类不是目的,管理复杂度才是目的。

最后再分享一个我的个人习惯

我现在写类之前,会先写两三行“用这个类”的调用代码,从调用方的视角倒推需要哪些方法、哪些属性。比如要做一个Order,我会先写下order = Order.create("U001", 199)order.pay()Order.get_by_id(1)这样的代码片段,感觉调用起来顺手了,再去补类内部实现。这个习惯帮我少写了很多“看起来很面向对象、实际用起来别扭”的代码。

如果你正在翻“python编程语法基础笔记”里的面向对象章节,我的建议是:不要死记硬背概念,直接把UserManager的例子自己在本地跑一遍,再模仿着写一个订单类、一个商品类。等你自然地写出第一个类文件时,面向对象的门就算真正迈进来了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 1:28:08

储能调频经济性评估:从现金流模型到蒙特卡洛模拟

简介&#xff1a;《储能系统参与电力系统调频经济性评估研究》为PDF格式&#xff0c;面向电力系统规划、储能应用及调频辅助服务领域的研究人员与工程技术人员。该研究针对储能参与一次、二次调频的经济性问题&#xff0c;提出经济评估模型&#xff0c;综合考虑投资成本、运维费…

作者头像 李华
网站建设 2026/9/19 5:02:33

华为机试 DP 牛客实例

华为机试 DP 牛客实例&#xff08;Python&#xff0c;可直接提交&#xff09;华为机试DP一般是一维DP为主&#xff0c;很少复杂二维DP。常考&#xff1a;最长递增子序列、最大子数组和、背包、跳台阶。 下面给2道最常考真题风格&#xff1a; ① 连续子数组最大和&#xff08;简…

作者头像 李华
网站建设 2026/9/19 5:16:10

译码器与显示器:数字系统信号转换的硬件底层逻辑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 5:07:32

企业数字化去供应商化:从被动依赖到自主掌控的路径

身边很多做企业的朋友都跟我聊过同一个困扰&#xff1a;数字化项目上线时好好的&#xff0c;越用越别扭。系统是供应商的&#xff0c;代码是供应商的&#xff0c;数据也存在供应商的服务器里&#xff0c;想改个字段要提工单&#xff0c;想加个接口要等排期&#xff0c;合同到期…

作者头像 李华