Python 新类和旧类区别

2018-07-27 language python

在 Python 2.2 版本中引入了新式类 (New Style Class),所以,通常将在此之前的类称为经典类或者旧类,而在此之后的称为新式类,两者在使用时略有区别。

这里会简单介绍两者的区别。

简介

之所以这么做的原因,官方给出的解释是,统一类 Class 和类型 Type 。实际上,可以比对 Python2 和 Python3 的区别。

$ python2
>>> two = 2
>>> type(two)
<type 'int'>

$ python3
>>> two = 2
>>> type(two)
<class 'int'>

显然,在 Python2 中,通过 type() 函数返回的是 <type 'int'> ,而在 Python3 中返回的则是 <class 'int'> 类型,其时,严格来说,两者是等价的,只是在 Python2 中将内置类型和用户定义的类进行了区分,而 Python3 则将两者进行了统一。

$ python2
>>> class FooBar(object):
...     pass
...
>>> foobar = FooBar()
>>> type(foobar)
<class '__main__.FooBar'>

$ python3
>>> class FooBar(object):
...     pass
...
>>> foobar = FooBar()
>>> type(foobar)
<class '__main__.FooBar'>

如上,对于用户定义的类,在 Python2 和 Python3 中是一致的。在旧类中,通过 foobar.__class__ 返回的与上述相同,而 type(foobar) 返回的一直是 <type 'instance'>

所以,对于类来说,从 Python2.2 到之后的 Python2.X 版本,以及到现在的 Python3.X 版本,其内部实现一直在变化,但趋势是更加统一了。

另外,在引入新类之后,同时引入了更多的内置属性,也包括了描述符的引入等。

Python2 VS. Python3

首先,建议使用新类,不同的 CPython 实现版本会略有区别。

在 Python2 中默认都是经典类,只有显式继承了 object 才是新式类;而在 Python3 中默认都是新式类,不需要再显式继承 object 即可。

区别以及新增特性

如上所示,在新式类中,内置的 __class__ 属性和 type() 函数获取信息保持一致。

搜索顺序

在类的多重继承时,对于属性的搜索顺序发生了改变:A) 经典类,先深入继承树左侧,返回,再开始找右侧,也就是深度优先搜索;B) 新式类,先水平搜索,然后再向上移动,也就是广度优先搜索。

假设有如下的 Python 代码。

class A():
    def __init__(self):
        pass
    def save(self):
        print("This is from A")

class B(A):
    def __init__(self):
        pass

class C(A):
    def __init__(self):
        pass
    def save(self):
        print("This is from C")

class D(B, C):
    def __init__(self):
        pass

foobar =  D()
foobar.save()

也就是说,其继承关系为。

  A
 B C
  D

对于经典类,其输出为 This is from A ,而新类的输出为 This is from C

$ python2 foobar.py
This is from A

$ python3 foobar.py
This is from C

新类增加 __slots__ 属性

新式类增加了 __slots__ 内置属性,可以把实例属性限定到 __slots__ 规定的范围内。

class ClassA():
	__slots__ = ('name', 'age')

class ClassB(object):
	__slots__ = ('name', 'age')

ca = ClassA()
cb = ClassB()

ca.foobar = "Andy"
cb.foobar = "Andy"

注意,需要通过 Python2 运行,因为在 Python3 中,所有的类都是新式类。

$ python2 /tmp/foobar.py
Traceback (most recent call last):
  File "/tmp/foobar.py", line 11, in <module>
    cb.foobar = "Andy"
AttributeError: 'ClassB' object has no attribute 'foobar'

都通过 __slots__ 进行了设置,在旧类中,允许访问对象 ca.foobar ,不会报错;而在访问 cb.foobar 时,会直接抛出异常。

每个类的实例都会有一个 __dict__ 内置属性,用来记录实例中所有的属性和方法,也是通过这个字典,可以让实例绑定任意的属性。当类存在 __slots__ 属性时,旧不会存在 __dict__ 属性,从而可以减少内存的开销,适合创建小实例。

新类增加 __getattribute__ 方法

每次访问对象属性时,都会调用该方法,可定制一些属性访问策略。注意,如果直接访问类的属性该方法是不会被调用的。