Python中旧样式类与新样式类有什么区别?


Answers:


559

新式和经典类

直到Python 2.1,旧式类才是供用户使用的唯一样式。

(旧式)类的概念与类型的概念无关:如果x是旧式类的实例,则x.__class__ 指定的类x,但type(x)始终为<type 'instance'>

这反映了这样一个事实,即所有旧式实例(独立于其类)均使用称为实例的单个内置类型实现。

在Python 2.2中引入了新型类,以统一类和类型的概念。新型类只是用户定义的类型,不多也不少。

如果x是新样式类的实例,则type(x)通常与x 相同x.__class__(尽管不能保证–允许新样式类实例覆盖所返回的值x.__class__)。

引入新型类的主要动机是提供具有完整元模型的统一对象模型

它还具有许多直接的好处,例如能够对大多数内置类型进行子类化,或者引入了“描述符”,以启用计算属性。

出于兼容性原因,默认情况下,类仍为旧样式

通过将另一个新样式类(即一种类型)指定为父类或“顶级类型”对象(如果不需要其他父类)来创建新样式类。

新样式类的行为与旧样式类的行为不同,除了返回什么类型外,还有许多重要的细节。

其中一些更改是新对象模型的基础,例如调用特殊方法的方式。其他是出于兼容性考虑而无法实现的“修补程序”,例如在多重继承的情况下的方法解析顺序。

Python 3仅具有新型类

无论是否从中继承子类object,类都是Python 3中的新型样式。


41
这些差异听起来都不是使用新型类的令人信服的理由,但是每个人都说您应该始终使用新型。如果我像应该那样使用鸭子类型,则无需使用type(x)。如果我不对内置类型进行子类化,那么新样式类似乎没有任何优势。有一个缺点,那就是的额外输入(object)
递归

78
某些功能(例如)super()不适用于旧类。就像该文章所说的那样,更不用说了一些基本修复程序,例如MRO和特殊方法,这绝对是使用它的充分理由。
约翰·多伊,

21
@User:老式类在2.7中的行为与在2.1中的行为相同-并且,因为很少有人记住怪癖,并且文档不再讨论其中的大多数,所以它们甚至更糟。上面的文档引用直接表明了这一点:有些“修复程序”无法在老式类上实现。除非您想遇到自Python 2.1以来没有其他人处理过的怪癖,并且文档甚至不再说明,否则请不要使用旧式类。
2013年

10
如果您在2.7中使用旧式类,则可能会遇到一个奇怪的例子:bugs.python.org/issue21785
KT。

5
对于任何想知道的人来说,在Python 3中显式继承对象的一个​​很好的理由是,它使支持多个版本的Python更容易。
jpmc26 2014年

308

声明方式:

新样式类继承自object或另一个新类。

class NewStyleClass(object):
    pass

class AnotherNewStyleClass(NewStyleClass):
    pass

老式的类没有。

class OldStyleClass():
    pass

Python 3注意:

Python 3不支持旧样式类,因此上述任何一种形式都会生成新样式类。


24
如果一个新样式类继承自另一个新样式类,则通过扩展,它继承自object
aaronasterling 2010年

2
这是旧式python类的不正确示例吗? class AnotherOldStyleClass: pass
2013年

11
@abc我相信class A: pass并且class A(): pass严格等效。第一个表示“ A不继承任何父类”,第二个表示“ A不继承任何父类”。这是相当相似not isis not
eyquem

5
顺便提一句,对于3.X,将自动假定“对象”的继承(这意味着我们无法在3.X中不继承“对象”)。出于向后兼容性的原因,可以将“(对象)”保留在那里。
Yo Hsiao

1
如果我们要获得有关继承类的技术知识,此答案应注意,您是通过继承旧样式类来创建另一个旧样式类的。(书面,这个答案叶子用户质疑是否可以从一个旧式的类继承您可以。)
jpmc26

224

新旧样式类之间的重要行为更改

  • 超级添加
  • 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
好的明确摘要,谢谢。当您说“很难用英语解释”时,我认为您是在描述一种后置深度优先搜索,而不是使用预排序深度优先搜索的老式类。(预订购意味着我们在第一个孩子之前进行自我搜索,后订购意味着我们在第一个孩子之后进行自我搜索)。
史蒂夫·卡特

40

旧样式的类仍然比属性查找要快一些。这通常并不重要,但是在对性能敏感的Python 2.x代码中可能有用:

在[3]中:A类:
   ...:def __init __(self):
   ...:self.a ='hi there'
   ...:

在[4]中:B类(对象):
   ...:def __init __(self):
   ...:self.a ='hi there'
   ...:

在[6]中:aobj = A()
在[7]中:bobj = B()

在[8]中:%timeit aobj.a
10000000次循环,每循环3:78.7 ns最佳

在[10]中:%timeit bobj.a
10000000次循环,每循环3:86.9 ns最佳

5
您在实践中注意到的有趣之处在于,我刚刚读到,这是因为新样式类一旦在实例字典中找到了属性,就必须进行额外的查找来确定它是否为描述,即它是否具有描述。需要调用get方法来获取要返回的值。旧样式类简单地返回找到的对象,而不进行任何附加计算(但随后不支持描述符)。您可以在Guidopython-history.blogspot.co.uk/2010/06/…的精彩文章中阅读更多内容,尤其是关于插槽
xuloChavez 2012年

1
CPython 2.7.2似乎不正确:%timeit aobj.a 10000000 loops, best of 3: 66.1 ns per loop %timeit bobj.a 10000000 loops, best of 3: 53.9 ns per loop
Benedikt Waldvogel 2012年

1
对我来说,在x86-64 Linux上的CPython 2.7.2中,对于aobj来说仍然更快。
xioxox 2012年

41
对性能敏感的应用程序依赖纯Python代码可能不是一个好主意。没有人说:“我需要快速的代码,因此我将使用老式的Python类。” Numpy不算作纯Python。
菲利普·

在IPython 2.7.6中也是如此,这不是事实。''''477 ns vs. 456 ns per loop''''
kmonsoor 2014年

37

Guido撰写了有关New-Style Classes的The Inside Story,这是一篇有关Python中的新风格和旧风格类的非常不错的文章。

Python 3只有新样式的类。即使您编写了一个“旧类”,它也是从隐式派生的object

新式类具有一些旧式类所缺少的高级功能,例如super,新的C3 mro,一些神奇的方法等。


24

这是一个非常实际的,正确/错误的区别。以下代码的两个版本之间的唯一区别是,在第二个版本中,Personobject继承。除此之外,两个版本相同,但结果不同:

  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 2014年

4
_names_cache是一本字典,用于缓存(存储以供将来检索)传递给的每个名称Person.__new__。setdefault方法(在任何字典中定义)都带有两个参数:一个键和一个值。如果键在字典中,它将返回其值。如果不在字典中,它将首先将其设置为作为第二个参数传递的值,然后将其返回。
ychaouche 2014年

4
用法是错误的。想法是不构造一个新对象(如果已经存在),但是在您的情况下__new__(),始终会调用它,并且它总是构造一个新对象,然后将其抛出。在这种情况下,a if优于.setdefault()
阿米特·阿帕德哈伊

但是,我不明白为什么输出会有所不同,即,在旧样式类中,两个实例是不同的,因此返回False,但是在新样式类中,两个实例是相同的。怎么样 ?新样式类的变化是什么,使两个实例相同,旧样式类却没有?
Pabitra Pati

1
@PabitraPati:这是一个廉价的示范。__new__实际上,这不是老式类的东西,它也不会在实例构造中使用(它只是一个看起来很特殊的随机名称,例如define __spam__)。因此,构造旧类仅调用__init__,而构造新类__new__(按名称推销到单例实例)来构造和__init__初始化它。
ShadowRanger

10

新样式的类继承自objectPython ,并且必须从Python 2.2开始编写(即class Classname(object):而不是class Classname:)。核心更改是统一类型和类,这样做的好处是它允许您从内置类型继承。

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


8

新样式类可以使用super(Foo, self)where Foo是一个类,并且self是一个实例。

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

返回将方法调用委托给类型的父级或同级类的代理对象。这对于访问已在类中重写的继承方法很有用。搜索顺序与getattr()使用的顺序相同,只是类型本身被跳过。

在Python 3.x中,您可以super()在没有任何参数的类内部简单地使用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.