资讯动态

一篇文章带你深入理解Python中的object()

发布时间:2026/10/17 4:49:35 来源:尧图企业网站定制
前言object是 Python 里最不起眼、又最绕不开的东西。你几乎不会主动写object()但每一个类都继承了它每一个实例的默认行为都由它定义为什么print(obj)会打印出一串带内存地址的字符串为什么两个刚创建的对象比较是False为什么自定义了__eq__之后对象突然不能放进set了答案都在object上。先纠正一个说法object()这个写法里的object在官方文档里写作class object它是一个类型不是普通的函数。object()是调用它的构造器。还有一组在 Python 3 里已经不存在、但老教程里到处是区别在 Python 2 中class Foo:创建的是「老式类old-style class」它不继承object只有class Foo(object):才是新式类。Python 2.7 已于2020 年 1 月 1 日停止维护PEP 373在 Python 3 里这个区分被彻底取消了所有类都隐式继承object写不写括号里的object完全等价。所以本文后面看到的class Foo:和class Foo(object):指的是同一个东西。一、object 是一切类型的基类官方文档对它的定义只有两句话但信息量很大它是所有其他类的终极基类ultimate base class拥有对所有 Python 类实例都通用的方法调用它的构造器会返回一个没有特征的featureless对象并且构造器不接受任何参数。「所有其他类的终极基类」意味着任何类的__mro__链最后一项都是object除了object自己。这不是约定是语言保证# 适用于 Python 3.8class Foo:passprint(Foo.__mro__) # (class Foo, class object)print(issubclass(Foo, object)) # Trueprint(isinstance(Foo(), object)) # Trueprint(type.__mro__) # 类型系统本身的终点也是 object「不接受任何参数」这一点在协作式继承里很关键多重继承的__init__链最后总要走object.__init__()此时不能带任何参数否则就是TypeError。这是上一节讲协作继承时的硬边界。# 适用于 Python 3.8o object()print(type(o)) # class objectobject(1) # TypeError: object() takes no arguments二、object 提供了哪些默认方法object定义了每个实例都会继承的一批方法。它们分成几组理解分组比背名单有用分组方法默认行为创建与初始化__new__、__init__分配实例__init__不做任何事字符串表示__repr__、__str__、__format____repr__给出带类型名与地址的形式__str__退回__repr__比较__eq__、__ne__、__lt__、__le__、__gt__、__ge__相等性用同一性判断大小比较返回NotImplemented哈希__hash__基于对象身份id属性访问__getattribute__、__setattr__、__delattr__、__dir__从对象的__dict__等位置查找、写入、删除属性序列化与内存__reduce__、__reduce_ex__、__sizeof__供pickle/copy/sys.getsizeof使用类机制__init_subclass__、__subclasshook__子类创建时的钩子isinstance的协作钩子几个立刻能观察到的行为# 适用于 Python 3.8class Point:passp Point()print(repr(p)) # __main__.Point object at 0x...print(str(p) repr(p)) # True —— 没定义 __str__ 时退回 __repr__print(p Point()) # False —— 默认比较的是「是不是同一个对象」print(p p) # True注意p Point()是False。默认的__eq__用的是同一性identity所以两个内容「看起来一样」的对象默认并不相等。这是新手最容易感到意外的地方也是为什么值对象通常要自己实现__eq__。另外object的默认__hash__基于id()所以默认对象可以放进set、可以当字典的键。三、class Foo: 与 class Foo(object): 在 Python 3 里等价既然所有类都继承object那显式写(object)就是纯冗余# 适用于 Python 3.8class A:passclass B(object):passprint(A.__bases__) # (class object,)print(B.__bases__) # (class object,)print(A.__bases__ B.__bases__) # True两种写法产生的类没有任何行为差异。那为什么老代码里到处是(object)因为 Python 2 里不写就得付出代价老式类不继承object因此缺了一整套新式类特性——老式类里super()不能用属性查找走的是另一套老规则type(实例)返回的是instance而不是类属性描述符descriptor也不生效。为了在 2 里拿到新式类行为人们养成了显式写(object)的习惯。Python 3 把老式类整个删掉了这个习惯也就只剩下历史意义。新代码里写class Foo:即可。四、为什么不能给 object 加属性官方文档有一条专门的注释object的实例没有__dict__属性所以不能给object的实例赋任意属性。普通类的实例默认带一个__dict__属性就存在里面object没有所以无处可存。# 适用于 Python 3.8o object()o.x 1 # AttributeError: object object has no attribute x注意错误信息的措辞不是「不能设置」而是「没有这个属性」——因为写入路径依赖__dict__而它不存在。对类本身也一样。object是 C 实现的内置类型内置类型不允许运行期增删属性# 适用于 Python 3.8object.x 1 # TypeError: cant set attributes of built-in/extension type object这和第二行的AttributeError是两类不同的问题容易被混为一谈尝试结果原因o object(); o.x 1AttributeError实例没有__dict__没地方存object.x 1TypeError内置类型的属性集合在运行期是冻结的class Foo: pass; Foo().x 1成功Foo的实例有__dict__class Foo: pass; Foo.x 1成功普通类是可变对象如果你的类确实要「不能加任意属性」标准做法是在类里声明__slots__而不是去继承object的这份限制。object的行为是语言层面的既定事实不是可以借用的开关。五、什么时候真的会用到 object虽然很少主动用但object()在几个地方确实有用。1. 当唯一哨兵sentinel。需要一个「保证不和任何合法值相等」的默认值object()是最省事的# 适用于 Python 3.8_MISSING object()def get(d, key, default_MISSING):if key in d:return d[key]if default is _MISSING:raise KeyError(key)return defaultprint(get({a: 1}, a)) # 1print(get({a: 1}, b, None)) # None# get({a: 1}, b) # KeyError: b这里必须用is比较不用。原因是两方面的语义上「是不是同一个哨兵」才是你要问的问题实践上会触发对方的__eq__而那个方法可能是你完全控制不了的第三方实现甚至可能返回数组、返回NotImplemented让判断变得不可靠。2. 作为__init__协作链的终点。前面提到过super().__init__()最终会走到object.__init__()。3. 在做类型判断时把 object 当成「任何东西」。例如isinstance(x, object)恒为True这种判断通常没有意义看到就该删掉。4. 直接type(x) is object判断「这就是个裸对象」。这在调试或写元编程代码时偶尔有用。六、定义 __eq__ 会连带影响 __hash__object提供了默认的__eq__和__hash__它们是一对默认相等性基于身份默认哈希也基于身份两者自洽。但一旦你在自己的类里定义了__eq__Python 会把__hash__置为None实例随即不能被哈希。# 适用于 Python 3.8class Point:def __init__(self, x, y):self.x, self.y x, ydef __eq__(self, other):return isinstance(other, Point) and (self.x, self.y) (other.x, other.y)p Point(1, 2)print(p Point(1, 2)) # True# {p: a} # TypeError: unhashable type: Point这是object的默认行为被覆盖后产生的连锁反应不是 bug。要让 Point 既可比较又能进set需要显式补上__hash__并且保证「相等的对象哈希值也相等」# 适用于 Python 3.8class Point:def __init__(self, x, y):self.x, self.y x, ydef __eq__(self, other):return isinstance(other, Point) and (self.x, self.y) (other.x, other.y)def __hash__(self):return hash((self.x, self.y))常见坑点1. 给 object 实例加属性。❌o object(); o.tag x——AttributeError: object object has no attribute tag。 ✅ 自己定义一个类object实例没有__dict__本来就不支持任意属性。2. 以为class Foo:和class Foo(object):不等价。❌ 在 Python 3 里照着老教程解释「不写 object 就是老式类」——那个区分在 Python 3 中已不存在。 ✅ 两者完全等价「老式类」是 Python 2 的概念2.7 已于 2020-01-01 停止维护。3. 用比较哨兵对象。❌if value _MISSING:—— 会触发value.__eq__第三方类型的实现不可控。 ✅if value is _MISSING:身份判断既准确又不受对方影响。4. 以为两个「内容相同」的对象默认相等。❌Point(1, 2) Point(1, 2)期望True—— 默认__eq__基于身份结果是False。 ✅ 需要按值比较就在类里实现__eq__。5. 定义了__eq__却忘了对象要能进 set。❌ 加了__eq__之后往set里放报TypeError: unhashable type。 ✅ 同时实现__hash__且保证相等的对象哈希一致不打算哈希的对象才让__hash__保持None。6. 用isinstance(x, object)做判断。❌if isinstance(x, object):—— 对所有非异常情况恒为True等于没判断。 ✅ 换成真正想区分的类型如果是要排除None用if x is not None:。7. 往协作链末端传参数。❌ 多重继承最后super().__init__(**kwargs)时kwargs非空 ——object.__init__不接受参数直接TypeError。 ✅ 每个协作类都消费掉自己的参数末端用空参调用。8. 想通过「给 object 打补丁」来统一加方法。❌object.helper lambda self: ...——TypeError: cant set attributes of built-in/extension type object。 ✅ 定义一个自己的基类让需要该方法的类去继承它。总结问题答案object是什么所有类的终极基类也是一个类型object()接受参数吗不接受且返回无特征的实例提供了什么创建、表示、比较、哈希、属性访问、序列化、类机制等默认方法class Foo:与class Foo(object):Python 3 里完全等价能给实例加属性吗不能object实例没有__dict__能给类加属性吗不能内置类型的属性在运行期冻结默认相等性是什么同一性is不是值相等为什么定义了__eq__就不能哈希__hash__被自动置为None什么时候主动用它需要唯一哨兵、判断裸对象、协作链终点object的知识点都不难难的是意识到它们全都来自同一处每个类的默认行为都是object替你规定的。一旦自定义了__eq__、__init__或__repr__那些默认值就被替换掉连带的效果比如__hash__变成None也随之出现。把object当成「默认行为的提供者」而不是「一个用不到的基类」很多以前觉得莫名其妙的现象就都对得上了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑