news 2026/10/24 10:31:16

python 特殊属性及方法详细解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
python 特殊属性及方法详细解析

前言


「特殊属性」和「特殊方法」这两个词经常被混着用,其实它们在语言参考里是两套不同的东西。特殊方法(也叫魔术方法)是那些会被语法隐式调用的、可调用的下划线方法,比如__init__、__len__;特殊属性是那些由解释器自动维护、通常不是可调用的数据成员,比如__dict__、__class__、__module__、__qualname__。前者你写它是为了改变对象的行为,后者你读它是为了反射(introspection)——看看这个对象到底是什么、从哪来、长什么样。


初学者最常见的误解有三个。第一,以为__doc__、__name__是普通属性,随便就能改——本对象上有些能改有些不能,类上的__dict__甚至是只读的映射代理。第二,以为属性能不能拿到只看「类里有没有」,忽略了实例字典优先这条查找顺序。第三,以为__dict__一定存在——用了__slots__的实例就可能没有。


本文按「属性住在哪里」这条线索把它们梳理清楚,并配一张对照表区分属性和方法。


一、先分清:属性和方法的区别




类别长什么样谁在维护典型代表你拿它干什么



特殊属性数据成员(多为只读或半只读)解释器__dict__、__class__、__doc__、__module__、__qualname__反射、调试、序列化

特殊方法可调用的方法你定义、解释器调用__init__、__repr__、__eq__、__enter__改变对象在语法下的行为

普通属性普通数据成员你self.name存业务数据



判断标准很简单:能被语法自动触发的下划线成员是方法;描述对象自身信息的是属性。当然边界不是绝对的——__init__也是属性(它在类字典里),只是它的值是个函数对象,可以调用而已。理解反射的视角是:类本身也是一个对象,也有自己的__dict__。


二、对象上的特殊属性


# 适用于 Python 3.8+
class Config:
"""配置对象的文档字符串。"""
timeout = 3

def __init__(self, host):
self.host = host


cfg = Config("localhost")
print(cfg.__class__) # <class '__main__.Config'>
print(cfg.__dict__) # {'host': 'localhost'}——注意只有实例属性
print(Config.__dict__["timeout"]) # 3,类属性在类字典里
print(Config.__doc__) # 配置对象的文档字符串。

几个必须记牢的点:



  • obj.__class__是这个实例所属的类;type(obj)也能拿到,但对象可以伪造__class__,type()不会。

  • obj.__dict__是实例的命名空间,只装实例自己的属性,看不到类属性。Config上的timeout不在实例字典里。

  • Config.__dict__是映射代理(mapping proxy),提供类命名空间的只读视图,你改不动它。

  • __doc__来自类(或函数/模块)的第一条字符串字面量;没写 docstring 时是None。


三、实例属性与类属性的查找顺序


语言参考把这条链路和描述符协议合起来看才完整。取obj.attr时:


1. 先在类型(沿 MRO 向上)里找同名成员;
若找到且它是数据描述符,直接用它的结果,实例字典被忽略
2. 否则查实例命名空间 obj.__dict__
3. 实例字典里也没有,就用类型上找到的那个成员
(普通类属性或非数据描述符;函数会被变成绑定到实例的方法对象)
4. 以上都找不到,才调用类的 __getattr__(如果定义了)

反直觉的地方在第 1 步:数据描述符(带__set__/__delete__的描述符,比如property)的优先级高于实例字典,而普通属性(包括函数,属于非数据描述符)会被实例字典盖住。所以「实例属性覆盖类属性」这条经验法则,对数据描述符是无效的。


# 适用于 Python 3.8+
class Demo:
class_attr = "我是类属性"

@property
def computed(self):
return "我是数据描述符"

def method(self):
return "我是方法"


d = Demo()
d.class_attr = "我盖住了类属性" # 赋值只会写进实例字典,不会改类字典
print(d.class_attr) # 我盖住了类属性
print(Demo.class_attr) # 我是类属性——类字典没变
print(d.computed) # 我是数据描述符——property 优先级更高

最后一行尤其重要:即使你给d硬塞一个computed到实例字典里,property作为数据描述符依然优先,会抛AttributeError——这就是「实例属性覆盖类属性」这条经验法则的例外。


四、类、函数、模块上的特殊属性


同一套__name__/__qualname__/__module__/__doc__会出现在好几类对象上,含义略有差异:




属性类(type)函数模块



__name__类的名字函数名模块名

__qualname__带外层作用域的限定名带外层作用域的限定名无(模块用__name__)

__module__定义该类的模块名定义该函数的模块名——

__doc__类 docstring函数 docstring模块 docstring

__dict__映射代理(只读)函数的命名空间模块的命名空间(可写)



__qualname__(qualified name,限定名)是 3.3 起引入的:它带上了定义处的嵌套路径,所以嵌在类或函数里的东西,__name__只给最后一段,__qualname__给完整路径。


# 适用于 Python 3.8+
class Outer:
class Inner:
def method(self):
pass


print(Outer.Inner.__name__) # Inner
print(Outer.Inner.__qualname__) # Outer.Inner
print(Outer.Inner.method.__qualname__) # Outer.Inner.method

__mro__是类上的另一个常用特殊属性:它是方法解析顺序(method resolution order)的元组,C.__mro__能让你一眼看出多重继承时到底先找谁。


五、注解与 __slots__:两个特殊的「声明式」属性


__annotations__存的是对象上的注解(通常是类型提示)。语言参考明确提醒它是惰性求值的——访问这个属性本身可能执行任意代码、也可能抛异常;从 3.14 起注解改为默认延迟求值。类、函数、模块都能有__annotations__,但对没有注解的对象访问它,行为与版本相关,具体以官方文档为准。


__slots__是一个纯声明式的类属性:值可以是字符串、可迭代对象或字符串序列,作用是显式声明实例会有哪些数据成员,并阻止自动创建__dict__和__weakref__。它带来两个实际后果:一是没有__dict__的实例不能挂新属性,赋值会抛AttributeError;二是数据成员固定下来之后,内存占用和属性查找都可能更省。要注意的几个陷阱:从没有__slots__的父类继承时,子类实例照样有__dict__;__slots__用描述符实现,所以不能靠类属性给 slot 变量设默认值;给int、bytes、tuple这类变长内置类型定义非空__slots__会直接抛TypeError。


__match_args__是 3.10 起配合结构化模式匹配用的类属性,值是一个字符串元组,让case MyClass(x, y)这种位置写法能映射到关键字。它的缺省等价于空元组。


六、把反射用起来:一个小例子


# 适用于 Python 3.8+
class Invoice:
"""一张发票。"""
currency = "CNY"

def __init__(self, amount):
self.amount = amount


def describe(obj):
cls = obj.__class__
print("类名:", cls.__name__)
print("限定名:", cls.__qualname__)
print("定义于模块:", cls.__module__)
print("文档:", cls.__doc__)
print("实例属性:", obj.__dict__)
print("类属性键:", sorted(k for k in cls.__dict__ if not k.startswith("__")))


describe(Invoice(99))

cls.__dict__里既能看到currency,也能看到__init__——因为类的方法本质上就是存在类字典里的属性。这正是「属性和方法在底层是同一回事」的佐证:区分它们的不是存储位置,而是能不能被语法隐式调用。


常见坑点



  1. ❌ 以为obj.__dict__里能看到类属性。


✅ 实例字典只装实例自己的属性;类属性在type(obj).__dict__里,要反射得查两处。



  1. ❌ 给实例赋一个和数据描述符(如property)同名的属性,指望能盖住它。


✅ 数据描述符的优先级高于实例字典,「实例属性覆盖类属性」在这类成员上不成立。



  1. ❌ 认为所有实例都有__dict__。


✅ 用了__slots__的类,实例可能没有__dict__,反射代码要先hasattr再访问。



  1. ❌ 把type(obj).__dict__当普通字典去改。


✅ 类的__dict__是只读的映射代理,改它会报错;要改类型属性用setattr并清楚自己在做什么。



  1. ❌ 用__name__当对象的唯一标识。


✅ 嵌套定义时__name__只有最后一段,会重名;限定名要用__qualname__(3.3+)。



  1. ❌ 给子类只写__slots__就以为省掉了__dict__。


✅ 父类没有__slots__时,子类实例依然有__dict__,省内存的目标落空。



  1. ❌ 直接用类属性给__slots__里的变量设默认值。


✅__slots__是描述符实现的,类属性会覆盖描述符赋值,默认值拿不到;默认值应写在__init__里。



  1. ❌ 以为__annotations__一定是个现成的字典,随手就读。


✅ 它是惰性求值的,访问可能触发代码执行甚至抛异常;3.14 起注解默认延迟求值。


总结




属性住在哪关键性质



__class__实例实例所属的类

__dict__实例 / 类 / 函数 / 模块实例的可见写;类的只读映射代理

__name__类 / 函数 / 模块名字最后一段

__qualname__类 / 函数带外层路径的限定名(3.3+)

__module__类 / 函数 / 方法定义处的模块名

__doc__类 / 函数 / 模块文档字符串,没有时为 None

__mro__类方法解析顺序

__slots__类声明式;阻止创建__dict__

__annotations__类 / 函数 / 模块惰性求值(3.14 起默认延迟)

__match_args__类配合模式匹配的位置参数(3.10+)



一句话区分:特殊方法改变行为,特殊属性暴露身份。写类时你关心前者,做反射、调试、序列化时你关心后者。而把两者串起来的那条主线,就是本节反复强调的查找顺序——实例字典先于非数据描述符的类属性,却被数据描述符反超。





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

Dify项目DSL实战:把财务报销审核助手做成可复用的工程资产

简介&#xff1a;这是一份面向企业财务合规与流程自动化场景的Dify工作流应用资源&#xff0c;支持直接导入项目DSL&#xff0c;聚焦报销单据审核中的规则校验、缺失字段补全、风险等级划分与汇总表输出&#xff0c;适合财务人员、Dify开发者及企业IT运维者直接参考或二次开发。…

作者头像 李华
网站建设 2026/10/22 6:36:21

TPS259483AYWPR与MKV58F1M0VLQ24协同电源保护设计

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

作者头像 李华
网站建设 2026/10/24 18:06:14

高校后勤报修系统设计与实现:毕设选题、开发到答辩全攻略

每年四月中旬开始&#xff0c;我手里就会集中收到一批“卡在中期”的毕设项目&#xff0c;出现频率最高的题目之一就是高校后勤报修系统。它看起来非常规整——提交报修、后台派单、师傅维修、学生验收&#xff0c;好像四个页面一套 CRUD 就能交差。可真把它做完&#xff0c;你…

作者头像 李华
网站建设 2026/10/21 4:00:23

Agent优化别靠玄学:从日志评测到分层调参的工程化方法

你的Agent又不听话了&#xff1f;先别急着改提示词。这是很多团队的日常&#xff1a;Agent效果不好&#xff0c;第一反应就是把系统提示词拿出来改改。加一句"你必须调用工具"&#xff0c;不行&#xff1b;改成"请务必使用工具"&#xff0c;还是不行&#…

作者头像 李华
网站建设 2026/10/20 12:48:02

浏览器直改ESP32 NVS键值,免重刷固件改WiFi密码

1. 改个 WiFi 密码就得重刷固件&#xff0c;这个问题到底卡在哪说实话&#xff0c;玩 ESP32 的人&#xff0c;多多少少都遇到过这种尴尬&#xff1a;设备装好了、代码编译完了、上电跑了一天&#xff0c;突然某天用户一句"我们家路由器换密码了"&#xff0c;你得把整…

作者头像 李华
网站建设 2026/10/21 6:34:21

AI原生应用落地高端制造:5类高价值场景与6个月实操路径

在高端制造圈子里跑了几年数字化转型以后&#xff0c;我越来越清楚一件事&#xff1a;AI上产线&#xff0c;缺的从来不是算法&#xff0c;而是"落地路径"。很多企业一上来就做"大而全"的AI中台&#xff0c;投入几百万&#xff0c;半年后能讲的还是那个展示…

作者头像 李华