python的新式类和旧式类
python的新式类是2.2版本引进来的,我们可以将之前的类叫做经典类或者旧式类。为什么要在2.2中引进new style class呢?官方给的解释是:为了统一类(class)和类型(type)。
使用环境是python 2.7
新式类与旧式类的区别
|
|
如上所示b是旧式类的一个实例,c是新式类的一个实例。c的__class__
与type返回的结果是一样的,得到统一。而b的__class__
与type返回的结果并不相同。同时我们还发现新式类有更多的属性和方法,旧式类只有区区的2个方法。
我们也发现为了向前兼容,默认情况下用户定义的类为经典类,新类需要继承自所有类的基类 object 或者继承自object的新类。那么为了确保自己使用的是新式类,有两种以下方法:
- 元类,在类模块代码的最前面加入如下代码
__metaclass__
= classname(自定义的某个新式类)。 - 类都从内建类object直接或者间接地继承。
新式类的属性和方法
内置的object对象是所有内置,object对象定义了一系列特殊的方法实现所有对象的默认行为。
1.__new__
,__init__
方法
这两个方法是用来创建object的子类对象,静态方法__new__
()用来创建类的实例,然后再调用__init__
()来初始化实例。
|
|
新式类都有一个__new__
的静态方法,它的原型是object.__new__
(cls[, …])cls是一个类对象,如上面代码,当你调用C(args, **kargs)来创建一个类C的实例时,python的内部调用是C.__new__
(C, args, kargs),然后返回值是类C的实例c,在确认c是C的实例后,python再调用C.__init__
(c, *args, kargs)来初始化实例c。
所以调用一个实例c = C(”ooo“),实际执行的代码为:
|
|
可以使用__new__
来实现Singleton单例模式:
|
|
使用id()操作,可以看到两个实例指向同一个内存地址。Singleton的所有子类也有这一
特性,只有一个实例对象,如果它的子类定义了__init__
()方法,那么必须保证它的__init__
方法能够安全的同一个实例进行多次调用。
2.__delattr__
, __getattribute__
, __setattr__
方法
对象使用这些方法来处理属性的访问
__getattribute__
对新式类的实例来说,所有属性和方法的访问操作都是通过getattribute完成,这是由object基类实现的。如果有特殊的要求,可以重载getattribute方法,下面实现一个不能使用append方法的list:
|
|
3.__hash__
, __repr__
, __str__
方法
print(someobj)会调用someobj.__str__
(), 如果__str__
没有定义,则会调用someobj.__repr__
(),
__str__
()和__repr__
()的区别:
默认的实现是没有任何作用的,__repr__
的目标是对象信息唯一性,__str__
的目标是对象信息的可读性
容器对象的__str__
一般使用的是对象元素的__repr__
,如果重新定义了__repr__
,而没有定义__str__
,则默认调用__str__
时,调用的是__repr__
,也就是说好的编程习惯是每一个类都需要重写一个__repr__
方法,用于提供对象的可读信息,而重写__str__
方法是可选的。实现__str__
方法,一般是需要更加好看的打印效果,比如你要制作一个报表的时候等。
可以允许object的子类重载这些方法,或者添加新的方法。
4.__slots__
属性
通常每一个实例x都会有一个__dict__
属性,用来记录实例中所有的属性和方法,也是通过这个字典,
可以让实例绑定任意的属性。而__slots__
属性作用就是,当类C有比较少的变量,而且拥有__slots__
属性时,
类C的实例 就没有__dict__
属性,而是把变量的值存在一个固定的地方。如果试图访问一个__slots__
中没有
的属性,实例就会报错。这样操作有什么好处呢?__slots__
属性虽然令实例失去了绑定任意属性的便利,
但是因为每一个实例没有__dict__
属性,却能有效节省每一个实例的内存消耗,有利于生成小而精
干的实例。
在一个实际的企业级应用中,当一个类生成上百万个实例时,即使一个实例节省几十个字节都可以节省
一大笔内存,这种情况就值得使用__slots__
属性。
__slots__
是一个类变量,__slots__
属性可以赋值一个包含类属性名的字符串元组,或者是可迭代变量,或者
是一个字符串。
只有在新式类中生效,如下对比:
|
|
需要注意的几点:
当一个类的父类没有定义__slots__
属性,父类中的__dict__
属性总是可以访问到的,所以只在子类中定义__slots__
属性,而不在父类中定义是没有意义的。
如果定义了__slots__
属性,还是想在之后添加新的变量,就需要把__dict__
字符串添加到__slots__
的元组里。
|
|
定义了__slots__
属性,还会消失的一个属性是__weakref__
,这样就不支持实例的weak reference,如果还是想用这个功能,同样,可以把’__weakref__
‘字符串添加到元组里。
__slots__
功能是通过descriptor实现的,会为每一个变量创建一个descriptor。
__slots__
的功能只影响定义它的类,因此,子类需要重新定义__slots__
才能有它的功能。
|
|
5.__getitem__
方法
在python中,隐式调用实例的私有特殊方法时,新的对象模型和经典对象模型表现上不太一样。在经典对象模型中,无论是显示调用还是隐式调用特殊方法,都会调用实例中后绑定的特殊方法。而在新的对象模型中,除非显式地调用实例的特殊方法,否则python总是会去调用类中定义的特殊方法,如果没有定义的话,就报错。
|
|
调用old[1],将产生一个隐式的getitem方法的调用,在新式类中,因为类中没有定义这个方法,也不是
object基类有的方法,所以报错。需要显示地调用才可以运行。
新式类的继承
新式类同样支持多继承,但是如果新式类想要从多个内置类型中继承生成一个新类的话,则这些内置类必须是经过精心设计,能够互相兼容的。显然,python也没会让你随意的从多个内置类中进行多继承,想创建一个超级类不是那么容易的。。。通常情况下,至多可以继承一个内置类,比如list, set, dict等。
下图是MRO(Method Resolution Order ,方法解析顺序),分别是旧式类和新式类的方法顺序。旧式类深度优先的方式进行查找,新式类广度优先的方式查找
|
|
|
|
另一个注意的是协作式调用父类方法使用:
|
|
可以看到,基类A的方法重复运行了两次。怎样才能确保父类中的方法只被顺序的调用一次呢?在新的对象系统中,有一种特殊的方法super(aclass, obj),可以返回obj实例的一个特殊类型superobject(超对象, 不是简单的父类的对象),当我们使用超对象调用父类的方法时,就能保证只被运行一次:
|
|
可以看到,D的父类中所有的foo方法都得到执行,并且基类A的foo方法只执行了一次。如果养成了
使用super去调用父类方法的习惯,那么你的类就可以适应无论多么复杂的继承调用结构。super()
可以看成是更加安全调用父类方法的一种新方式。
新式类的Descriptor
descriptor可以说是一个绑定了特定访问方法的类属性,这些访问方法是重写了descriptor protocol中的三个方法,分别是__get__
, __set__
, __del__
方法。如果三个中任一一个方法在对象中定义了,就说这个对象是一个descriptor对象,可以把这个对象赋值给其它属性。descriptor protocol可以看成是一个有三个方法的接口。
通常对一个实例的属性的访问操作,如get, set, delete是通过实例的__dict__
字典属性进行的,例如下面代码:
|
|
对于操作person.name,会一个查找链,首先通过实例对象的__dict__
属性访问,即person.dict[‘x’](实例的字典),再通过类型对象的__dict__
属性访问,即type(person).dict[‘x’],等价于Person.dict[‘name’](类的字典),再通过父类对象的__dict__
属性访问,person.class.base.dict[‘name’],等价于Parent.dict[‘name’],type(a)的父类的字典。
如果这个需要被查找的属性是一个定义了descriptor协议方法的对象,那么python就不会按照默认的查找方式,而是调用descriptor协议中定义的方法__get__
方法获取,同样的道理,给name赋值的时候是通过调用__set__
方法实现而不是通过__dict__
属性。
|
|
上面的例子是基于类的方式来创建描述符,你还可以通过property()函数来创建描述符
|
|
property()函数返回的是一个描述符对象,它可接收四个参数property(fget=None, fset=None, fdel=None, doc=None)
- fget:属性获取方法
- fset:属性设置方法
- fdel:属性删除方法
- doc: docstring
使用纯python的方式来实现property函数如下:
|
|
同时你还可以用property装饰器创建描述符
|
|
参考:
http://www.pythontab.com/html/2015/pythonjichu_1113/982.html
http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html