news 2026/10/8 20:56:06

Python 内置方法和属性详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python 内置方法和属性详解

前言


Python 里凡是名字前后各带两条下划线的东西,例如__init__、__repr__、__dict__,社区俗称「魔术方法」(magic method)或 dunder(double underscore 的缩写)。它们不是给程序员随便调用的,而是语言本身在特定时刻会去调用的钩子:写len(obj)时,解释器实际调用的是obj.__len__();写obj + other时调用的是obj.__add__(other)。


围绕它们有两个典型误解。第一,把「内置方法」和「内置函数」混为一谈——len()是内置函数,__len__是你可以在自己类里实现的协议方法,两者是调用者与被调用者的关系。第二,以为__str__不定义就没法打印——实际上print(obj)会退而调用__repr__,而__repr__由object提供了一个默认实现,只是输出不好看而已。


本文讲清属性(attribute)与方法(method)两套机制,给出能直接运行的示例,并说明哪些行为是文档保证的、哪些只是当前实现的细节。示例基于 Python 3.8+。


一、先分清「属性」与「方法」


在 Python 的对象模型里,一切都在字典里。实例属性存在__dict__,方法本质上就是存在类字典里的函数对象,通过描述符协议在访问时绑定出self。




名字类型含义在哪里



__dict__属性对象/类的属性字典实例与类都有

__class__属性所属类实例

__name__属性名字类、函数、模块

__doc__属性文档字符串类、函数、模块

__module__属性定义所在的模块名类、函数

__qualname__属性限定名(含外层类名)类、函数(3.3+)

__bases__属性直接父类元组类

__mro__属性方法解析顺序类

__slots__属性声明式属性表,省内存类(可选)



# 适用于 Python 3.8+
class Point:
"""二维点。"""
dims = 2

def __init__(self, x, y):
self.x = x
self.y = y

p = Point(1, 2)
print(p.__dict__) # {'x': 1, 'y': 2}
print(p.__class__) # <class '__main__.Point'>
print(Point.__name__) # Point
print(Point.__doc__) # 二维点。
print(Point.__bases__) # (<class 'object'>,)
print(Point.dims, p.dims)# 2 2,类属性实例也能读到

注意p.__dict__里没有dims——它是类属性。找属性时先查实例字典,再顺着__mro__查类字典。给实例赋值p.dims = 3会新建一个实例属性遮蔽类属性,不影响别的实例。


二、对象的构造与表示




方法何时被调用返回值约定



__new__(cls, ...)创建实例,最先执行返回实例

__init__(self, ...)初始化实例必须返回None

__repr__(self)repr()、交互式回显、调试str,尽量可还原

__str__(self)str()、print()str,面向人

__format__(self, spec)f-string 与format()str

__hash__(self)hash()、放进set/dict键int

__eq__(self, other)==任意(按约定给bool)



# 适用于 Python 3.8+
class Money:
def __init__(self, amount, currency="CNY"):
self.amount = amount
self.currency = currency

def __repr__(self):
return f"Money({self.amount!r}, {self.currency!r})"

def __str__(self):
return f"{self.amount} {self.currency}"

m = Money(12.5)
print(repr(m)) # Money(12.5, 'CNY')
print(str(m)) # 12.5 CNY
print(m) # 12.5 CNY,print 用的是 __str__
print([m]) # [Money(12.5, 'CNY')],容器用的是 __repr__

经验法则:__repr__给开发者看(越像构造表达式越好),__str__给最终用户看。只定义__repr__时,str()会自动回退到它。


三、容器协议与迭代协议


想让自定义对象支持len()、下标、for循环、in判断,就实现对应协议。




方法触发它的语法



__len__len(obj)

__getitem__obj[key]

__setitem__obj[key] = v

__delitem__del obj[key]

__contains__x in obj

__iter__for x in obj、iter(obj)

__next__next(obj)

__call__obj(...)

__enter__/__exit__with obj:



# 适用于 Python 3.8+
class Countdown:
def __init__(self, start):
self.start = start

def __iter__(self):
n = self.start
while n > 0:
yield n
n -= 1

def __len__(self):
return self.start

cd = Countdown(3)
print(len(cd)) # 3
print(list(cd)) # [3, 2, 1]

class Multiplier:
def __init__(self, factor):
self.factor = factor

def __call__(self, x):
return x * self.factor

double = Multiplier(2)
print(double(21)) # 42
print(callable(double)) # True

只要定义了__call__,实例就能像函数一样被调用,callable()也会返回True。


四、哪些行为是保证的


__dict__的存在、__mro__的线性化顺序、属性查找顺序都是文档保证的。但下面这些属于实现细节:



  • 实例属性在__dict__里的顺序,CPython 中通常是插入顺序,但不应依赖;

  • __slots__能省内存,但它会去掉实例的__dict__,一旦定义,就只能给列出的名字赋值,无法再动态添加属性;

  • 定义了__eq__却没定义__hash__,Python 会把该类的__hash__置为None,实例立即变成不可哈希、不能放进set或当字典键。


# 适用于 Python 3.8+
class Item:
def __init__(self, sku):
self.sku = sku

def __eq__(self, other):
return isinstance(other, Item) and self.sku == other.sku

__hash__ = None # 显式写出这个事实

print(Item("A") == Item("A")) # True
# {Item("A")} # TypeError: unhashable type

常见坑点


坑 1:__init__里写了return。


❌


class C:
def __init__(self):
return 1

✅__init__必须返回None(或不写 return),否则实例化时抛TypeError: __init__() should return None。想控制返回值要改__new__。


坑 2:只写__str__就以为调试输出够用。


❌ 容器、交互式回显用的都是__repr__,print(list_of_obj)里全是默认的内存地址形式(形如 object at 0x 加一串十六进制)。


✅ 至少实现__repr__,再按需实现__str__。


坑 3:定义__eq__后忘记__hash__。


❌ 对象放进set或当dict键时报unhashable type。


✅ 要么一起实现__hash__,要么明确该类设计为不可哈希。


坑 4:__eq__里没做类型检查。


❌


def __eq__(self, other):
return self.sku == other.sku # other 可能是任何类型

✅ 先if not isinstance(other, Item): return NotImplemented,让解释器有机会走对方的比较逻辑。


坑 5:以为self.__x是私有的。


❌ 以为双下划线能阻止外部访问。


✅ 双下划线只做名字改写(name mangling),在类C里写self.__x实际是self._C__x,仍然能被访问。约定上"受保护"用单下划线_x表示,但它同样只是约定。


坑 6:给类加__slots__后还想动态加属性。


❌ 定义__slots__ = ("x",)之后写obj.y = 1,抛AttributeError。


✅ 把"y"也列进__slots__,或在__slots__里保留"__dict__"(这样就不再省内存了)。


坑 7:__len__返回了非整数。


❌def __len__(self): return "3"——len(obj)抛TypeError。


✅ 返回int(且非负);bool(obj)也可以用__len__的结果,为 0 即假,所以别让__len__有副作用。


坑 8:在__repr__里访问会触发大量计算或 IO 的属性。


❌__repr__里读数据库、打印大对象全量内容,导致调试时更卡。


✅__repr__保持轻量、无副作用,只输出标识性的少量字段。


总结




类别代表方法 / 属性一句话提醒



属性__dict__、__class__、__doc__、__mro__查找顺序:实例字典 → 类 MRO

构造__new__、__init____init__必须返回None

表示__repr__、__str__、__format__至少实现__repr__

比较与哈希__eq__、__hash__定义__eq__会连带清空__hash__

容器与迭代__len__、__getitem__、__iter__、__contains__协议齐全才写得顺手

调用与上下文__call__、__enter__、__exit__with语句靠这两个



理解 dunder 的关键是转换视角:它们不是"要背的方法名",而是"语言在某个语法被使用时去找的钩子"。想知道某个类实现了哪些钩子,直接print(类名.__mro__)再help(类名),或者读object的文档;不要凭印象猜哪个魔术方法存在、参数是不是self, other。


(本主题与 Python 2 的主要差别在于:Python 2 里存在__unicode__、__nonzero__、__cmp__这些方法,在 Python 3 中分别被__str__、__bool__、__eq__体系取代。Python 2.7 已于 2020 年 1 月 1 日停止维护,新代码不要再用旧方法名。)





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

Spring Boot+MyBatis-Plus农业种植基地管理系统开发实战

1. 项目梳理与整体设计思路1.1 这个系统到底要解决什么问题先说个场景。我去过不少中小规模种植基地考察&#xff0c;发现它们的生产管理模式还停留在“本子记、口头传”的阶段。种什么、种在哪块地、什么时候施肥、打了什么药、这批货出了多少、卖给谁了&#xff0c;全靠一线工…

作者头像 李华
网站建设 2026/10/8 20:52:26

AI赋能iOS开发:从编码助手到端侧智能的实战指南

早上到工位&#xff0c;打开 Xcode&#xff0c;在 SwiftUI 文件里敲下一个Observable class&#xff0c;AI 自动把后面十几行属性、网络请求甚至单元测试的骨架都补了出来。这是我过去半年的真实工作状态。人工智能在 iOS 开发里&#xff0c;早就不是发布会上的 Demo&#xff0…

作者头像 李华
网站建设 2026/10/8 20:51:47

DFIG双馈风机单机无穷大Simulink仿真:建模、控制与调试全解析

刚把一台2MW双馈风机&#xff08;DFIG&#xff09;的单机无穷大仿真模型调稳定&#xff0c;趁热把这套系统的底细捋一捋。双馈风机仿真&#xff0c;入门容易&#xff0c;跑通也容易&#xff0c;但想跑出“可信”的结果&#xff0c;让波形能对上物理规律&#xff0c;让控制参数有…

作者头像 李华
网站建设 2026/10/8 20:50:58

括号匹配与动态规划:统计合法括号子串的线性DP解法

1. 先把题目读懂&#xff1a;什么是“永远在一起”上个月我们这边打了一场月赛&#xff0c;T2 就是这道 P15445&#xff0c;题面起了个非常文艺的名字叫“永远在一起”。我当时看到这个名字愣了一下&#xff0c;读完题意才发现&#xff0c;它本质上是在问括号匹配的问题&#x…

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

Java NIO多路复用:Selector与Channel的底层原理与实战指南

做后端这些年&#xff0c;要说Java NIO里哪个词最让人“上头”&#xff0c;多路复用&#xff08;Selector / Channel&#xff09;绝对排前列。聊天室、网关、消息推送、RPC长连接&#xff0c;只要连接量大&#xff0c;这套机制几乎绕不开。我最早真正理解多路复用&#xff0c;是…

作者头像 李华
网站建设 2026/10/8 20:45:18

DirectShow采集与RTP/RTCP发送:从帧回调到实时推流的完整实现

简介&#xff1a;这是一份基于DirectShow框架实现的RTP/RTCP实时传输协议发送端程序源码&#xff0c;面向学习流媒体传输、网络编程与Windows平台音视频开发的初学者及进阶开发者&#xff0c;帮助理解RTP数据包封装、RTCP控制反馈与发送流程的完整实现思路。压缩包共16个文件&a…

作者头像 李华