news 2026/8/13 9:10:51

Python变量作用域全解析:类变量、实例变量与局部变量的核心区别与实战应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python变量作用域全解析:类变量、实例变量与局部变量的核心区别与实战应用

1. 变量王国里的“三兄弟”:从概念到实战的深度解析

在Python的世界里,变量就像是程序员手中的“储物格”,用来存放程序运行时的各种数据。但如果你以为变量只是简单的“名字=值”,那可就太天真了。尤其是在面向对象编程(OOP)的语境下,变量根据其定义的位置和作用域,分成了泾渭分明的“三兄弟”:类变量、实例变量和局部变量。这三者看似简单,却是构建清晰、健壮、可维护代码的基石。很多初学者,甚至有一定经验的开发者,都曾在它们身上栽过跟头——比如,本想修改某个对象的属性,结果却意外地改动了所有同类对象的默认值;或者在一个方法内部定义的变量,出了方法就“神秘消失”,导致各种NameError。今天,我们就来彻底拆解这“三兄弟”,不仅告诉你它们是谁,更要讲清楚它们住在哪、管多宽、以及在实际项目中如何正确地“使唤”它们,让你从此告别变量作用域的混乱。

简单来说,类变量是属于整个类的,所有实例共享一份;实例变量是属于单个对象实例的,每个实例独享一份;局部变量则只活在某个函数或方法内部,出了门就无效。理解它们的区别,直接关系到你的代码是优雅清晰还是一团乱麻。无论你是正在写一个Web后端(比如用FastAPI搭建三层架构),还是在做数据分析与可视化,或是在调试一个复杂的爬虫脚本,清晰的作用域概念都能帮你节省大量调试时间。

2. 核心概念拆解:作用域与生命周期的本质

在深入细节之前,我们必须建立一个核心认知:区分这三种变量的关键,在于它们的作用域生命周期。这听起来有点抽象,我们可以用一个公司的组织架构来类比。

想象你在一家科技公司工作。类变量就像是公司的员工手册统一福利政策(比如年假15天)。这本手册存放在公司的公共服务器上(类命名空间),所有员工(实例)都可以查阅,并且理论上大家都遵循同一套规则。如果HR更新了手册(修改了类变量),那么所有员工看到的都是新版本。

实例变量则像是每个员工的个人办公桌和抽屉。张三的抽屉里放着他的咖啡杯和家庭照片(self.coffee_cup = ‘My Mug’),李四的抽屉里放着他的健身卡和零食(self.snack = ‘Chips’)。他们的抽屉是私人的,互不干扰。张三不能直接从李四的抽屉里拿东西,必须通过李四这个人(实例)才行。

局部变量呢?它就像你在一次小组会议中的临时笔记。你在白板上写下了讨论要点(在函数内部定义变量),这个笔记只对这次会议(函数执行过程)有意义。会议一结束,白板被擦干净(函数调用结束),这些笔记(局部变量)就消失了,会议室外的同事根本不知道你写过什么。

这个类比贯穿了它们最核心的区别:

  • 归属:类变量属于“类”这个蓝图;实例变量属于“对象”这个具体产物;局部变量属于“函数”这个临时过程。
  • 作用域:类变量在类内(以及通过类或实例访问的类外)全局可见;实例变量在对象实例内部全局可见;局部变量仅在定义它的函数或方法内部可见。
  • 生命周期:类变量随着类的加载而创建,通常持续到程序结束;实例变量随着对象的创建(__init__)而诞生,随着对象被垃圾回收而消亡;局部变量在函数调用时创建,函数返回时立即销毁。

理解了这层本质,我们再去看具体的语法和现象,就会豁然开朗。

2.1 类变量:共享的全局蓝图

类变量定义在类内部,但在所有方法(包括__init__)之外。它最典型的特征就是共享

class Employee: # 类变量:公司所有员工共享的政策 annual_leave_days = 15 company = "TechCorp" def __init__(self, name, position): # 实例变量:每个员工独有的属性 self.name = name self.position = position # 访问类变量 print(Employee.company) # 输出:TechCorp print(Employee.annual_leave_days) # 输出:15 # 创建实例 emp1 = Employee("张三", "工程师") emp2 = Employee("李四", "产品经理") # 通过实例也可以访问类变量(实际上是找到了类上的变量) print(emp1.company) # 输出:TechCorp print(emp2.annual_leave_days) # 输出:15 # 修改类变量,会影响所有实例和类本身的访问 Employee.company = "SuperTech" print(emp1.company) # 输出:SuperTech print(Employee.company) # 输出:SuperTech

这里有一个至关重要的陷阱,也是面试常考题:通过实例修改类变量。

emp1.annual_leave_days = 20 # 你以为修改了类变量? print(emp1.annual_leave_days) # 输出:20 print(emp2.annual_leave_days) # 输出:15 (没变!) print(Employee.annual_leave_days) # 输出:15 (没变!)

发生了什么?emp1.annual_leave_days = 20这行代码,并没有修改类变量Employee.annual_leave_days,而是在emp1这个实例的命名空间里,新建了一个同名的实例变量!Python的属性查找机制是自下而上的:先查找实例自身的__dict__,如果找不到,再向上查找类的__dict__。所以emp1现在有自己的annual_leave_days实例变量(值为20),而emp2和类本身仍然访问原来的类变量(值为15)。

实操心得:如果你真的需要修改类变量,务必通过类名来修改,即Employee.annual_leave_days = 20。通过实例修改类变量是一个常见的错误来源,会导致数据不一致的诡异Bug。在团队协作中,建议将类变量视为“只读”的配置项,或者提供类方法(@classmethod)来安全地修改它们。

类变量的常见用途包括:

  1. 定义常量:例如数学类中的PI = 3.14159
  2. 统计实例数量:在__init__中对类计数器递增。
  3. 共享配置:例如数据库连接字符串、默认日志级别等。
  4. 作为实例的默认值:但需注意上述的修改陷阱。

2.2 实例变量:对象的私有财产

实例变量是绑定到对象实例本身的变量,通常在__init__初始化方法中定义,使用self.前缀。它们是面向对象中封装特性的基础。

class Dog: # 类变量:所有狗共享的物种 species = "Canis familiaris" def __init__(self, name, age): # 实例变量:每只狗独有的属性 self.name = name # 绑定到self self.age = age self.tricks = [] # 每个狗的技能列表初始化为空,互不影响 def add_trick(self, trick): # 这里的trick是局部变量,其作用域仅限于本方法 self.tricks.append(trick) # 修改的是实例变量self.tricks # 创建两只狗 buddy = Dog("Buddy", 5) miles = Dog("Miles", 3) buddy.add_trick("roll over") miles.add_trick("play dead") print(buddy.tricks) # 输出:['roll over'] print(miles.tricks) # 输出:['play dead'] print(buddy.species) # 输出:Canis familiaris (访问类变量)

实例变量的生命周期与对象绑定。当buddy = Dog(“Buddy”, 5)执行时,Python创建一个新对象,并调用__init__,此时self.nameself.age就被绑定到这个新对象上。当程序不再引用buddy这个变量,Python的垃圾回收器最终会销毁这个对象,它的实例变量也随之消失。

一个高级但重要的点:__slots__。默认情况下,Python对象的实例属性存储在一个名为__dict__的字典里,这提供了动态添加属性的灵活性,但也消耗了更多内存。对于需要创建大量实例的类(比如在游戏“人狗大作战”中生成成千上万个单位),可以使用__slots__来显式声明允许的实例变量名,从而节省内存。

class EfficientDog: __slots__ = (‘name‘, ‘age‘, ‘tricks‘) # 固定实例变量列表 species = “Canis familiaris“ # 类变量不受影响 def __init__(self, name, age): self.name = name self.age = age self.tricks = [] # 必须在__slots__中声明过 # 尝试动态添加新属性会报错 # buddy.new_attr = ‘xxx‘ # AttributeError buddy = EfficientDog(“Buddy“, 5)

使用__slots__后,实例不再拥有__dict__,无法动态添加未在__slots__中声明的属性,但内存占用显著降低。这是一个典型的用灵活性换取性能的取舍。

2.3 局部变量:方法内部的临时工

局部变量的定义最简单,也最“短命”。它在一个函数或方法内部定义,其作用域被严格限制在该函数体内。

def calculate_bmi(weight_kg, height_m): # weight_kg, height_m 是传入的参数,也是局部变量 bmi = weight_kg / (height_m ** 2) # bmi是局部变量 category = "" # category也是局部变量 if bmi < 18.5: category = "Underweight" elif bmi < 25: category = "Normal" else: category = "Overweight" # 返回局部变量计算的结果 return bmi, category # 调用函数,局部变量被创建、使用、然后销毁 my_bmi, my_category = calculate_bmi(70, 1.75) print(f"BMI: {my_bmi}, Category: {my_category}") # 尝试在函数外访问局部变量会报错 # print(bmi) # NameError: name ‘bmi‘ is not defined

局部变量的“临时性”是其核心特征。它非常适合存储中间计算结果、循环计数器、临时状态等。因为一旦函数执行完毕,这些变量占用的内存就会被释放,不会造成命名空间的污染。

嵌套函数与闭包中的局部变量:这里有一个微妙的情况。如果在一个外层函数中定义了一个局部变量,而内层函数(闭包)引用了这个变量,那么这个局部变量的生命周期会被延长,与内层函数绑定。

def outer_function(x): message = f“Hello, {x}“ # outer_function的局部变量 def inner_function(): print(message) # inner_function引用了外层局部变量message return inner_function my_func = outer_function(“World“) my_func() # 输出:Hello, World # 此时,outer_function已执行完毕,但其局部变量message因被inner_function引用而依然存在。

这是Python中实现闭包和数据封装的一种强大机制。

3. 作用域查找规则与命名冲突实战

理解了各自定义后,我们来看看当名字冲突时,Python究竟如何抉择。这遵循LEGB规则:

  • Local:局部作用域(函数/方法内)
  • Enclosing:闭包函数的外层函数作用域
  • Global:全局作用域(模块层)
  • Built-in:内建作用域

在类的方法中查找一个名字(如x)时,顺序是:局部变量(L) -> 实例变量(在类方法中通过self访问,属于一种特殊的“局部”查找?不,更准确说是对象作用域) -> 类变量(G/B的一种特殊形式) -> 全局变量(G) -> 内建变量(B)

关键在于,self.x的查找优先于Class.x。这解释了之前那个陷阱:emp1.annual_leave_days = 20为实例添加了属性,屏蔽了同名的类变量。

让我们在一个复杂点的例子里看清所有角色:

x = “global x“ # 全局变量 class MyClass: y = “class y“ # 类变量 def __init__(self): self.z = “instance z“ # 实例变量 x = “local x in init“ # __init__方法内的局部变量,遮蔽了全局x print(f“Inside __init__: x = {x}“) # 输出:local x in init print(f“Inside __init__: self.z = {self.z}“) # 输出:instance z def method(self): x = “local x in method“ # method方法内的局部变量 print(f“Inside method: x = {x}“) # 输出:local x in method print(f“Inside method: self.z = {self.z}“) # 输出:instance z print(f“Inside method: MyClass.y = {MyClass.y}“) # 输出:class y # 如何访问被遮蔽的全局变量x?使用globals() print(f“Inside method: global x = {globals()[‘x‘]}“) # 输出:global x obj = MyClass() obj.method() print(f“Outside: x = {x}“) # 输出:global x

命名冲突的黄金法则

  1. 最局部原则:Python总是优先使用当前作用域内定义的变量。
  2. 显式优于隐式:为了避免混淆,访问类变量时,建议使用ClassName.var;访问实例变量时,使用self.var。这样意图最清晰。
  3. 慎用全局变量:在类和方法中,尽量避免直接读写全局变量。这会增加耦合度,使代码难以理解和测试。如果确实需要共享状态,考虑将其作为类变量或通过参数传递。

4. 高级主题与常见坑点实录

掌握了基础,我们来看看在实际项目中,哪些地方容易踩坑,以及如何利用这些特性写出更优雅的代码。

4.1 可变类变量:最大的陷阱

这是Python面试中最经典的问题之一。当类变量是一个可变对象(如列表、字典)时,共享特性会带来意想不到的后果。

class Warehouse: inventory = [] # 类变量,是一个空列表 def __init__(self, location): self.location = location self.inventory.append(f“Item from {location}“) # 修改的是类变量inventory! wh1 = Warehouse(“Beijing“) wh2 = Warehouse(“Shanghai“) print(wh1.inventory) # 输出:[‘Item from Beijing‘, ‘Item from Shanghai‘] print(wh2.inventory) # 输出:[‘Item from Beijing‘, ‘Item from Shanghai‘] print(Warehouse.inventory) # 输出:[‘Item from Beijing‘, ‘Item from Shanghai‘]

所有实例和类本身看到的都是同一个列表!这通常不是我们想要的行为。我们本意可能是让每个仓库有自己的库存列表。

解决方案:在__init__中初始化可变实例变量。

class CorrectWarehouse: def __init__(self, location): self.location = location self.inventory = [] # 每个实例拥有独立的空列表 self.inventory.append(f“Initial item for {location}“) wh1 = CorrectWarehouse(“Beijing“) wh2 = CorrectWarehouse(“Shanghai“) wh1.inventory.append(“Computer“) print(wh1.inventory) # 输出:[‘Initial item for Beijing‘, ‘Computer‘] print(wh2.inventory) # 输出:[‘Initial item for Shanghai‘]

避坑指南:这是一个必须牢记于心的规则:永远不要在类层级将可变对象(list, dict, set)作为默认的实例属性。如果你需要为每个实例提供一个可变对象的初始值,请在__init__方法内部进行初始化。对于不可变对象(int, float, str, tuple)作为类变量则是安全的。

4.2@classmethod@staticmethod中的变量访问

类方法和静态方法提供了另一种操作变量的视角。

  • 类方法:第一个参数是cls,代表类本身。它可以访问和修改类变量,但不能直接访问实例变量(因为调用时可能还没有实例)。

    class MyClass: count = 0 # 类变量,用于计数 def __init__(self): MyClass.count += 1 @classmethod def get_count(cls): return cls.count # 通过cls访问类变量 # 这里无法访问 self.name,因为没有self print(MyClass.get_count()) # 输出:0 a = MyClass() b = MyClass() print(MyClass.get_count()) # 输出:2
  • 静态方法:没有selfcls参数。它就像一个定义在类命名空间里的普通函数,不能直接访问类变量或实例变量,除非通过参数传递或直接使用类名。

    class MyClass: count = 0 @staticmethod def utility_function(): # 不能直接写 count += 1,会报错 # 必须通过类名访问 MyClass.count += 1 return “Utility called“ MyClass.utility_function() print(MyClass.count) # 输出:1

4.3 属性(Property)与变量访问的拦截

有时,我们希望对实例变量的访问和修改进行控制(比如类型检查、合法性验证、惰性计算)。这时可以使用@property装饰器将方法“伪装”成属性。

class Person: def __init__(self, name): self._name = name # 约定俗成,保护性变量,单下划线开头 self._age = None @property def name(self): """Getter for name""" return self._name.title() # 返回时自动首字母大写 @name.setter def name(self, value): """Setter for name""" if not isinstance(value, str): raise TypeError(“Name must be a string“) self._name = value @property def age(self): """Getter for age, lazy calculation example""" if self._age is None: print(“Calculating age from birthdate...“) # 假设这里有个复杂的计算 self._age = 30 return self._age @age.setter def age(self, value): if not 0 <= value <= 150: raise ValueError(“Age must be between 0 and 150“) self._age = value p = Person(“alice“) print(p.name) # 输出:Alice (调用了getter) p.name = “bob“ # 调用了setter print(p.name) # 输出:Bob # p.name = 123 # TypeError: Name must be a string print(p.age) # 输出:Calculating age... \n 30 (第一次访问触发计算) print(p.age) # 输出:30 (直接返回缓存值) p.age = 200 # ValueError: Age must be between 0 and 150

通过property,我们对外提供了p.namep.age这样简单的属性访问接口,但内部却实现了复杂的逻辑。这对于构建健壮的API非常有用。注意,_name_age是实例变量,而nameage是property装饰的方法。

5. 综合案例:构建一个简单的缓存系统

让我们用一个综合案例来串联所有知识点。假设我们要构建一个简单的内存缓存系统,它需要:

  1. 记录所有缓存实例的全局命中/未命中次数(类变量)。
  2. 每个缓存实例有自己的存储字典(实例变量)。
  3. get方法中使用局部变量进行临时计算。
class SimpleCache: # 类变量:所有缓存实例共享的统计信息 total_hits = 0 total_misses = 0 def __init__(self, name): # 实例变量:每个缓存自己的数据存储 self.name = name self._store = {} # 使用保护性变量 def set(self, key, value): """设置缓存,使用局部变量进行简单处理""" # key, value 是参数,也是局部变量 processed_key = str(key).strip() # processed_key是局部变量 if not processed_key: raise ValueError(“Key cannot be empty“) self._store[processed_key] = value def get(self, key): """获取缓存,更新统计""" lookup_key = str(key).strip() # 局部变量 if lookup_key in self._store: # 命中:更新类变量统计 SimpleCache.total_hits += 1 # 通过类名修改类变量 value = self._store[lookup_key] # 局部变量value print(f“[{self.name}] Cache HIT for ‘{lookup_key}‘“) return value else: # 未命中 SimpleCache.total_misses += 1 print(f“[{self.name}] Cache MISS for ‘{lookup_key}‘“) raise KeyError(f“Key ‘{lookup_key}‘ not found“) @classmethod def get_stats(cls): """类方法:获取全局统计""" # 注意:这里不能访问self._store,因为这是类方法 return { “total_hits“: cls.total_hits, “total_misses“: cls.total_misses, “hit_ratio“: cls.total_hits / (cls.total_hits + cls.total_misses) if (cls.total_hits + cls.total_misses) > 0 else 0 } def clear(self): """清空当前实例的缓存""" # 注意:这里我们操作的是实例变量_store keys_to_delete = list(self._store.keys()) # 局部变量,存储要删除的键列表 self._store.clear() print(f“[{self.name}] Cleared {len(keys_to_delete)} items.“) # 使用示例 cache_a = SimpleCache(“CacheA“) cache_b = SimpleCache(“CacheB“) cache_a.set(“user:1“, {“name“: “Alice“}) cache_b.set(“product:100“, {“title“: “Laptop“}) try: data = cache_a.get(“user:1“) # HIT print(data) except KeyError as e: print(e) try: cache_b.get(“user:1“) # MISS (因为user:1在cache_a里) except KeyError as e: print(e) print(“Global Stats:“, SimpleCache.get_stats()) print(“CacheA store:“, cache_a._store) # 访问保护性实例变量(通常不直接访问) print(“CacheB store:“, cache_b._store) # 演示修改类变量的正确方式 SimpleCache.total_hits = 100 # 正确:通过类名修改 # cache_a.total_hits = 200 # 危险:这会为cache_a创建实例变量,遮蔽类变量 print(“After manual update:“, SimpleCache.get_stats())

这个案例清晰地展示了:

  • total_hits/total_misses作为类变量,被所有实例共享和更新。
  • _storename作为实例变量,每个缓存对象独立拥有一份。
  • lookup_key,processed_key,value,keys_to_delete等作为局部变量,在方法执行期间临时存在,用于辅助计算。
  • 通过@classmethod安全地访问类变量。
  • 强调了通过类名(SimpleCache)修改类变量,而不是通过实例。

6. 调试技巧与最佳实践总结

在实际开发中,如何快速诊断变量作用域相关的问题?

  1. 使用__dict__属性:这是查看对象和类命名空间最直接的方式。

    obj = MyClass() print(obj.__dict__) # 查看实例的属性和值 print(MyClass.__dict__) # 查看类的属性和方法(包括类变量)
  2. 使用locals()globals()函数:在函数内部,locals()返回局部作用域的字典;在模块层面,globals()返回全局作用域的字典。

  3. 理解错误信息

    • AttributeError: ‘MyClass‘ object has no attribute ‘x‘:通常意味着你试图通过实例访问一个不存在的实例或类属性。检查拼写,或确认是否在__init__中初始化。
    • UnboundLocalError: local variable ‘x‘ referenced before assignment:在函数内对变量x进行了赋值操作,Python将其视为局部变量,但在赋值前就试图读取它。通常的解决方法是使用globalnonlocal关键字声明,或者调整代码顺序。

最佳实践清单

  • 初始化所有实例变量:在__init__方法中显式地初始化所有实例变量,即使初始值为None。这提高了代码可读性,并避免了AttributeError
  • 类变量用于真正的共享状态:仅当某个数据确实需要被所有实例共享和修改时,才使用类变量。对于常量,可以使用全大写的类变量,并考虑使用元组等不可变类型。
  • 避免可变类变量作为默认属性:重申:这是万恶之源。用实例变量代替。
  • 命名约定
    • 实例变量和方法名使用小写字母和下划线(snake_case)。
    • 保护性实例变量(意为“仅供内部使用”)使用单下划线开头(_internal)。
    • 类变量同样可以使用snake_case,或者对于常量使用UPPER_CASE
  • 优先使用属性(Property):如果需要对实例变量的访问设置逻辑(如验证、计算),使用@property装饰器。这保持了简洁的访问语法,同时封装了复杂性。
  • 保持作用域最小化:尽量使用局部变量。不要因为偷懒而把临时变量提升为实例变量或全局变量。这有助于减少副作用,使函数更纯粹、更容易测试。

变量作用域是Python编程中最基础也最需要精确掌握的概念之一。从写一个简单的“求长方体体积”脚本,到构建一个“FastAPI三层架构”的复杂后端,清晰地区分和使用类变量、实例变量和局部变量,是保证代码逻辑清晰、数据一致、易于调试的根本。下次当你定义变量时,不妨先问自己一句:它应该属于谁?应该活多久?想清楚这两个问题,很多bug在编写阶段就被杜绝了。

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

Node.js环境搭建全攻略:从版本选择、安装路径到NVM多版本管理

1. 为什么你的Node.js安装总是不对劲&#xff1f; 每次看到“Node.js 下载与安装教程”这个标题&#xff0c;我猜很多朋友的第一反应是&#xff1a;“这还不简单&#xff1f;去官网下个安装包&#xff0c;一路下一步不就完事了&#xff1f;” 我刚开始接触Node.js的时候也是这么…

作者头像 李华
网站建设 2026/8/13 9:09:14

构建以用户为中心的AI智能体:从概念到实践的全流程指南

在实际项目中引入 AI 智能体时&#xff0c;很多团队会陷入一个误区&#xff1a;花费大量精力去争论某个智能体模型或框架的“好坏”&#xff0c;却忽略了决定项目成败的关键因素——用户如何使用它。一个技术再先进的智能体&#xff0c;如果无法融入用户的实际工作流、解决其具…

作者头像 李华
网站建设 2026/8/13 9:08:24

上海诚杰华建设工程咨询有限公司网站怎么样:揭秘行业背后的真实服务逻辑与专业价值

在如今这个信息爆炸的时代,当我们试图寻找一家靠谱的建筑工程咨询机构时,第一步往往是打开浏览器,输入关键词,然后开始浏览各大网站和平台。在这个过程中,“上海诚杰华建设工程咨询有限公司网站”这个词汇对于许多正在寻找项目对接、资质代办或者工程全过程咨询服务的企业…

作者头像 李华
网站建设 2026/8/13 9:07:47

从电竞直播拆解顶级硬件实战表现与选购策略

这次我们来看一个由天猫发起的《顶级装备挑战暑假电竞水友赛》活动。这不是一个技术工具或开源项目&#xff0c;而是一场面向电竞爱好者和硬件玩家的线上直播赛事。活动定于7月28日晚上7点举行&#xff0c;核心看点在于“顶级装备”的实战体验和趣味挑战&#xff0c;结合了“天…

作者头像 李华
网站建设 2026/8/13 9:07:35

数学建模国赛Word标准模板:高效写作与规范排版指南

这次我们来看一个专门针对数学建模国赛的Word标准写作模板。对于参加数学建模竞赛的同学来说&#xff0c;论文的规范性、格式的严谨性往往和模型本身同等重要。一个符合评审标准、结构清晰、排版专业的模板&#xff0c;能让你在紧张的比赛时间里&#xff0c;把精力完全聚焦在模…

作者头像 李华
网站建设 2026/8/13 9:04:03

ESP-SR语音识别框架终极指南:5步为嵌入式设备添加离线语音交互

ESP-SR语音识别框架终极指南&#xff1a;5步为嵌入式设备添加离线语音交互 【免费下载链接】esp-sr Speech recognition 项目地址: https://gitcode.com/gh_mirrors/es/esp-sr ESP-SR语音识别框架是乐鑫官方推出的嵌入式AI语音解决方案&#xff0c;专为ESP32系列芯片设计…

作者头像 李华