ChatGPT解决这个技术问题 Extra ChatGPT

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

Python中的旧样式类和新样式类有什么区别?我什么时候应该使用其中一种?


1
10 revs, 7 users 53%

来自 New-style and classic classes

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


这些差异听起来都不是使用新式类的令人信服的理由,但每个人都说你应该始终使用新式。如果我按我应该的方式使用鸭子打字,我永远不需要使用 type(x)。如果我没有对内置类型进行子类化,那么我似乎看不到新式类的任何优势。有一个缺点,那就是 (object) 的额外输入。
super() 等某些功能不适用于旧式课程。更不用说,正如那篇文章所说,有基本修复,如 MRO 和特殊方法,这不仅仅是使用它的好理由。
@User:旧式类在 2.7 中的行为与 2.1 中的行为相同——而且,因为很少有人记得这些怪癖,而且文档不再讨论其中的大部分,它们甚至更糟。上面的文档引用直接说明了这一点:有些“修复”无法在旧式类上实现。除非您想遇到自 Python 2.1 以来没有其他人处理过的怪癖,并且文档甚至不再解释,否则不要使用旧式类。
如果您在 2.7 中使用旧式类,下面是一个您可能会偶然发现的怪癖示例:bugs.python.org/issue21785
对于任何想知道的人,在 Python 3 中显式继承对象的一个很好的理由是,它使支持多个 Python 版本更容易。
P
Peter Mortensen

声明方面:

新式类继承自对象,或从另一个新式类。

class NewStyleClass(object):
    pass

class AnotherNewStyleClass(NewStyleClass):
    pass

旧式课程没有。

class OldStyleClass():
    pass

Python 3 注意:

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


如果一个新式类继承自另一个新式类,那么通过扩展,它继承自 object
这是旧式python类的不正确示例吗? class AnotherOldStyleClass: pass
@abc 我相信 class A: passclass A(): pass 是严格等价的。第一个表示 "A 不继承任何父类",第二个表示 "A inherits of no parent class" 。这与 not isis not 非常相似
顺便说一句,对于 3.X,“对象”的继承是自动假定的(这意味着我们无法不继承 3.X 中的“对象”)。出于向后兼容性的原因,将“(对象)”保留在那里也不错。
如果我们要获得有关继承类的技术,这个答案应该注意到您通过从旧样式类继承来创建另一个旧样式类。 (正如所写,这个答案让用户质疑您是否可以从旧式类继承。您可以。)
P
Peter Mortensen

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

超级添加

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

New-style classes MRO 在单个英文句子中合成起来更加复杂。详细解释here。它的属性之一是基类仅在其所有派生类都被搜索后才被搜索。它们具有显示搜索顺序的 __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

好清晰的总结,谢谢。当您说“难以用英语解释”时,我认为您描述的是后序深度优先搜索,而不是使用前序深度优先搜索的旧式类。 (预购意味着我们在第一个孩子之前搜索自己,后购意味着我们在最后一个孩子之后搜索自己)。
P
Peter Mortensen

对于属性查找,旧样式类仍然稍微快一些。这通常并不重要,但在性能敏感的 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

有趣的是你在实践中注意到了,我刚刚读到这是因为新样式的类,一旦他们在实例字典中找到了属性,就必须做一个额外的查找来确定它是否是一个描述,即它有一个get 方法,需要调用以获取要返回的值。旧式类简单地返回找到的对象而不进行加法计算(但不支持描述符)。您可以在 Guido python-history.blogspot.co.uk/2010/06/… 的这篇优秀文章中阅读更多内容,特别是关于 slots 的部分
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
对我来说,x86-64 Linux 上的 CPython 2.7.2 中的 aobj 仍然更快。
对性能敏感的应用程序依赖纯 Python 代码可能是个坏主意。没有人说:“我需要快速的代码,所以我将使用旧式 Python 类。” Numpy 不算是纯 Python。
同样在 IPython 2.7.6 中,这不是真的。 ''''每循环 477 ns 与 456 ns''''
P
Peter Mortensen

Guido 撰写了The Inside Story on New-Style Classes,这是一篇关于 Python 中新式和旧式类的非常棒的文章。

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

新式类具有旧式类所缺乏的一些高级特性,如super、新的C3 mro、一些神奇的方法等。


P
Peter Mortensen

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

旧式类 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> >>> New -style 类 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> >>>


'_names_cache' 是做什么的?你能分享一个参考吗?
_names_cache 是一个字典,用于缓存(存储以供将来检索)您传递给 Person.__new__ 的每个名称。 setdefault 方法(在任何字典中定义)有两个参数:一个键和一个值。如果键在字典中,它将返回其值。如果它不在字典中,它将首先将其设置为作为第二个参数传递的值,然后返回它。
用法不对。这个想法是如果它已经存在就不要构造一个新对象,但在你的情况下 __new__() 总是被调用,它总是构造一个新对象,然后抛出它。在这种情况下,if 优于 .setdefault()
但是,我不明白为什么输出不同,即在旧样式类中,两个实例不同,因此返回 False,但在新样式类中,两个实例都是相同的。如何 ?新样式类的变化是什么,使两个实例相同,而不是旧样式类?
@PabitraPati:这是一种廉价的演示。 __new__ 实际上不是用于旧式类的东西,它不会用于实例构造(它只是一个看起来特别的随机名称,例如定义 __spam__)。所以构造旧式类只调用 __init__,而新式构造调用 __new__(通过名称合并为单例实例)来构造,并调用 __init__ 来初始化它。
F
Francesco Montesano

新式类继承自 object 并且必须在 Python 2.2 及更高版本中这样编写(即 class Classname(object): 而不是 class Classname:)。核心变化是统一类型和类,这样做的好处是它允许您从内置类型继承。

阅读descrintro了解更多详情。


C
Community

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

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

在 Python 3.x 中,您可以简单地在类中使用 super() 而无需任何参数。