Python中旧式类和新式类的区别是什么?

1125

Python中旧式类和新式类有什么区别?需要在何时使用它们之一?

8个回答

620

来自新式和经典类

直到Python 2.1,旧式类是用户唯一可用的类型。旧式类的概念与类型的概念无关:如果x是旧式类的实例,则x.__class__指定了x的类,但type(x)始终为。这反映了所有旧式实例的事实,独立于它们的类,都是使用称为实例的单个内置类型实现的。
新式类在Python 2.2中引入,以统一类和类型的概念。新式类只是一个用户定义的类型,不多不少。如果x是新式类的实例,则type(x)通常与x.__class__相同(尽管不能保证-新式类实例允许覆盖返回给x.__class__的值)。
引入新式类的主要动机是提供具有完整元模型的统一对象模型。它还具有许多直接的好处,例如能够对大多数内置类型进行子类化,或引入“描述符”,从而使计算属性成为可能。
出于兼容性原因,默认情况下类仍然是旧式的。通过指定另一个新式类(即类型)作为父类或不需要其他父类的“顶级类型”对象来创建新式类。新式类的行为与旧式类在许多重要细节上有所不同,除了type返回的内容之外。其中一些变化对于新的对象模型是基本的,例如特殊方法的调用方式。其他更改是以前由于兼容性问题而无法实现的“修复”,例如在多重继承情况下的方法解析顺序。
Python 3仅具有新式类。无论是否从object进行子类化,在Python 3中类都是新式的。

43
这些差异似乎都不是使用新式类的必要理由,然而每个人都说你应该始终使用新式类。如果我正在使用鸭子类型,我永远不需要使用type(x)。 如果我不是在子类化内置类型,则我看不到使用新式类的任何优势。它有一个劣势,就是额外输入(object) - recursive
87
super() 这样的某些特性在旧式类上不起作用。更何况,正如那篇文章所说,存在基本修复方式,比如 MRO 和特殊方法,这是使用它的绝佳理由。 - John Doe
24
@User: 在Python 2.7中,旧式类的行为与2.1时相同。由于很少有人记得这些怪癖,并且文档不再讨论其中大部分,所以它们变得更糟了。上面引用的文档直接说:旧式类中存在无法实现的“修复”措施。除非你想遇到自Python 2.1以来没有人处理过的怪异问题,而文档甚至不再解释,否则请勿使用旧式类。 - abarnert
12
这是一个例子,如果你在2.7中使用旧式类,可能会遇到一个怪异的问题:http://bugs.python.org/issue21785 - KT.
7
对于任何想知道的人,Python 3 中显式继承自object的好处是使支持多个版本的Python更加容易。 - jpmc26
显示剩余8条评论

337

声明方式:

新式类继承自object,或者从另一个新式类继承。

class NewStyleClass(object):
    pass

class AnotherNewStyleClass(NewStyleClass):
    pass

旧式类不支持。

class OldStyleClass():
    pass

Python 3 注意:

Python 3 不支持旧式类,因此上述任一形式都将产生新式类。


24
如果一个新式类继承自另一个新式类,那么它就间接地继承了object - aaronasterling
2
这是一个旧式 Python 类的错误示例吗?class AnotherOldStyleClass: pass - Ankur Agarwal
11
我相信" class A: pass " 和 " class A(): pass " 是完全等价的。前者的意思是“A没有继承任何父类”,而后者则表示“A没有继承任何父类”。这与“not is”和“is not”的用法非常类似。 - eyquem
5
顺便提一下,在Python 3.X中,“object”的继承是自动的(这意味着我们无法在3.X中不继承“object”)。出于向后兼容的原因,保留“(object)”也并不是坏事。 - Yo Hsiao
1
如果我们要在继承类方面变得技术性,那么这个答案应该指出,您可以通过从旧式类继承来创建另一个旧式类。(如所述,此答案让用户质疑是否可以从旧式类继承。您可以这样做。) - jpmc26
旧式类也可以(可选地)继承自object或另一个类。class OldStyleOptional(object): pass - cowlinator

244

旧式类和新式类之间的重要行为变化

  • 添加了super
  • MRO发生了变化(下面会解释)
  • 添加了描述符
  • 除非派生自Exception,否则无法引发新式类对象(下面有例子)
  • 添加了__slots__

MRO(方法解析顺序)发生了变化

其他答案中已经提到过,但这里给出一个具体的例子来说明经典MRO和C3 MRO(用于新式类)之间的区别。

问题是在多重继承中搜索属性(包括方法和成员变量)的顺序。

经典类按照从左到右的深度优先搜索。找到第一个匹配项后停止。它们没有__mro__属性。

class C: i = 0
class C1(C): pass
class C2(C): i = 2
class C12(C1, C2): pass
class C21(C2, C1): pass

assert C12().i == 0
assert C21().i == 2

try:
    C12.__mro__
except AttributeError:
    pass
else:
    assert False

新式类的MRO更加复杂,很难用一句英语来概括。详细解释可以参考这里。其中一个特点是只有在所有派生类都被搜索后,才会搜索基类。它们具有__mro__属性,显示搜索顺序。

class C(object): i = 0
class C1(C): pass
class C2(C): i = 2
class C12(C1, C2): pass
class C21(C2, C1): pass

assert C12().i == 2
assert C21().i == 2

assert C12.__mro__ == (C12, C1, C2, C, object)
assert C21.__mro__ == (C21, C2, C1, C, object)

除非继承自Exception,否则无法引发新样式类对象

在Python 2.5左右,许多类都可以被引发,而在Python 2.6左右,这种情况被取消了。在Python 2.7.3中:

# OK, old:
class Old: pass
try:
    raise Old()
except Old:
    pass
else:
    assert False

# TypeError, new not derived from `Exception`.
class New(object): pass
try:
    raise New()
except TypeError:
    pass
else:
    assert False

# OK, derived from `Exception`.
class New(Exception): pass
try:
    raise New()
except New:
    pass
else:
    assert False

# `'str'` is a new style object, so you can't raise it:
try:
    raise 'str'
except TypeError:
    pass
else:
    assert False

8
感谢您提供的清晰概述。当您说“用英语难以解释”时,我认为您在描述后序深度优先搜索,而不是旧式类别使用的前序深度优先搜索。(前序意味着我们在第一个子节点之前搜索自己,后序意味着我们在最后一个子节点之后搜索自己)。 - Steve Carter

41

旧式类仍然在属性查找方面略快。这通常不重要,但在性能敏感的Python 2.x代码中可能会有用:

In [3]: class A:
   ...:     def __init__(self):
   ...:         self.a = 'hi there'
   ...:
In [4]: class B(object): ...: def __init__(self): ...: self.a = 'hi there' ...:
In [6]: aobj = A() In [7]: bobj = B()
In [8]: %timeit aobj.a 10000000 loops, best of 3: 78.7 ns per loop In [10]: %timeit bobj.a 10000000 loops, best of 3: 86.9 ns per loop

5
有趣的是您在实践中注意到这一点,我刚刚读到了一篇文章,其中提到这是因为新式类一旦在实例字典中找到属性,就必须进行额外的查找以确定它是否是描述符(即它是否具有需要调用以获取返回值的__get__方法)。旧式类只需返回找到的对象,而不进行任何其他计算(但不支持描述符)。您可以在Guido的这篇精彩文章中阅读更多内容:http://python-history.blogspot.co.uk/2010/06/inside-story-on-new-style-classes.html,特别是关于__slots__部分。 - xuloChavez
1
似乎在CPython 2.7.2版本中并不是真实的: %timeit aobj.a 10000000次循环,3个最佳:每次66.1纳秒 %timeit bobj.a 10000000次循环,3个最佳:每次53.9纳秒 - Benedikt Waldvogel
1
在我的 x86-64 Linux 上,对于 CPython 2.7.2,仍然比使用 aobj 更快。 - xioxox
45
对于对性能要求敏感的应用程序来说,仅依赖纯Python代码可能不是一个好主意。没有人会这样说:“我需要快速的代码,所以我将使用旧式Python类。” Numpy不算作纯Python。 - Phillip Cloud
在IPython 2.7.6中,这并不是真的。''''每个循环477纳秒对456纳秒''''。 - kmonsoor
在IPython 2.7.6中,我仍然发现这是真实的,32.4比39ns更快(i7-2760QM,Linux,x86-64)。 - xioxox

40

Guido写了一篇关于Python中新旧类的非常好的文章,标题为The Inside Story on New-Style Classes

Python 3只有新式类。即使您编写一个“旧式类”,它也会隐式继承自object

新式类具有旧式类缺乏的一些高级功能,例如super、新的C3 MRO算法以及一些魔术方法。


26

以下是一个非常实用的真/假差异。下面两段代码的唯一区别是第二段中的 Person 继承自 object ,除此之外,这两个版本是相同的,但结果不同:

  1. 旧式类

    class Person():
        _names_cache = {}
        def __init__(self,name):
            self.name = name
        def __new__(cls,name):
            return cls._names_cache.setdefault(name,object.__new__(cls,name))
    
    ahmed1 = Person("Ahmed")
    ahmed2 = Person("Ahmed")
    print ahmed1 is ahmed2
    print ahmed1
    print ahmed2
    
    
    >>> False
    <__main__.Person instance at 0xb74acf8c>
    <__main__.Person instance at 0xb74ac6cc>
    >>>
    
    
  2. 新式类

    class Person(object):
        _names_cache = {}
        def __init__(self,name):
            self.name = name
        def __new__(cls,name):
            return cls._names_cache.setdefault(name,object.__new__(cls,name))
    
    ahmed1 = Person("Ahmed")
    ahmed2 = Person("Ahmed")
    print ahmed2 is ahmed1
    print ahmed1
    print ahmed2
    
    >>> True
    <__main__.Person object at 0xb74ac66c>
    <__main__.Person object at 0xb74ac66c>
    >>>
    

2
"_names_cache"是用来做什么的呢?你能分享一个参考资料吗? - Muatik
4
_names_cache 是一个字典,它缓存(储存以便将来检索)你传递给 Person.__new__ 的每个名字。setdefault 方法(在任何字典中定义)接受两个参数:键和值。如果该键在字典中,则返回其对应的值。如果不在字典中,则先将该键的值设置为第二个参数,然后返回该值。 - ychaouche
4
用法有误。想法是如果对象已经存在,则不构造新对象,但在您的情况下,始终调用__new __(),并始终构造新对象,然后将其丢弃。在这种情况下,使用“if”比“.setdefault()”更可取。 - Amit Upadhyay
但是,我不明白输出结果的差异在哪里,即在旧式类中,两个实例是不同的,因此返回False,但在新式类中,两个实例都是相同的。为什么?新式类中有什么变化,使得两个实例相同,而旧式类中没有呢? - Pabitra Pati
2
@PabitraPati:这只是一个简单的演示。对于旧式类,__new__实际上并不存在,它不会在实例构造中使用(它只是一个看起来很特别的随机名称,就像定义__spam__一样)。因此,构造旧式类仅调用__init__,而新式构造则调用__new__(通过名称合并为单例实例)进行构造,并调用__init__进行初始化。 - ShadowRanger

10
新式类继承自object,在Python 2.2及以后的版本中必须这样编写(即class Classname(object):而不是class Classname:)。核心变化是统一类型和类,这样做的好处是可以从内置类型中继承。

阅读descrintro以获取更多详细信息。


8
新式类可以使用super(Foo, self),其中Foo是一个类,self是实例。

super(type[, object-or-type])

返回一个代理对象,将方法调用委托给类型的父类或兄弟类。这对于访问在类中被覆盖的继承方法非常有用。搜索顺序与getattr()使用的顺序相同,只是跳过了类型本身。

而在Python 3.x中,你可以简单地在一个类中使用super()而不需要任何参数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接