新式类
- 在Python3.0中,所有类都是新式类(new-style),所有的类都继承自object对象,不管是显式的还是隐式的,并且所有对象都是object的实例。
- 在Python2.6和以前的版本,类必须显式继承object才会是新式类,并且获得所有新式类的特性
在Python3.0中,所有的类都自动继承自新式类,所以新式类的特征只是常规的特性。而在Python2.6版本及以前的都必须继承自object类或者继承自Python内置类才会成为新式类。
实际上,新式类在语法和行为上几乎与经典类完全向后兼容;它们主要只是添加了一些高级的新特性。
新式类的变化有以下几点:
- 类就是类型,类型也是类,同时一个实例的类型也是该实例的类,它们之间的区别已经完全消失,
>>> type([1,2,3]) #类型就是类
<class 'list'>
>>> type(list) #类也是类型
<class 'type'>
>>> list.__class__
<class 'type'>
>>>
- 所有的类都是派生自object类
- 新式类在多重继承的类树中搜索是先从同一个深度中的类开始搜索,所有同深度的类都搜索完成之后,然后再开始搜索更深类(而2.6是优先将一个树枝搜索完后,再开始搜索下一个树枝,新式类是广度优先而不是深度优先)。如果要避免这种搜索顺序的混淆,最好将变量命名得更加有特效,类的设计更加合理。
新式类还有一些更高级的扩展:
slots属性
在新式类中,__slots__是比较特殊的类属性,可以将字符串列表赋值给该属性,这样就可以限制类实例的合法属性集,那些不合法的属性将无法赋值,这又能优化内存和速度性能。
>>> class c:
... __slots__=['a','b','c']
...
>>> lxm=c()
>>> lxm.d=1 #赋值不会成功
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'c' object has no attribute 'd'
>>> lxm.a=1
>>> lxm.b=2
>>> lxm.c=3
>>> lxm.z=2222
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'c' object has no attribute 'z'
>>>
如果类的属性只有确定的几个时,创建实例并分配命名空间时很费内存的办法,__slots__属性可以顺序存储,而不是用传统的字典形式来存储属性,这样提高了执行速度,也节省空间。
当有多个超类的时候,__slots__属性可以看做是所有超类该属性的集合。与超裂相关slots属性有以下几点需要注意:
- 如果一个子类继承自一个没有__slots__的超类,那么超类的dict属性总是可以访问,这让子类中的slots属性失去意义。
- 如果一个子类和超类相同的slots名称,超类的slots属性只有通过超类标识符获取其属性
- 由于一个slots声明的含义受到类的限制,所以子类将有一个__dict__,除非它们也定义了一个slots
property机制
这个property是一个对象,赋值给类属性名称。它会产生三种方法(get,set,del)如果任何参数是None,那么该运算就不能支持。特性赋值一般都是在类的顶层处使用。
>>> class c():
... def Cget(self):
... print("Cget called,value:")
... return 999
... def Cset(self,value):
... print("Cset called,value:",value)
... return 888
... def Cdel(self):
... print("Cdel called")
... del self
... test=property(Cget,Cset,Cdel,None)
...
>>> lxm=c()
>>> lxm.test
Cget called,value:
999
>>> lxm.test=123
Cset called,value: 123
这种效果就很像重载运算符中的__getattr__和__setattr__方法。
静态方法
有些时候我们需要对类的属性进行操作,而不是类的实例。我们就需要定义一个不需要传入实例(self)的“静态”方法。在Python2.6中,无论是否在定义方法是写没写self参数,在调用方法是都会需要一个实例。而在Python3.0中就不一样,如果不需要实例,是完全可以不传入实例对象,调用时需要通过类名调用,如果使用实例调用,会默认传入实例对象,导致接受参数出错。
使用静态方法最好的方法是将静态方法写入到类方法中,使得每一个实例都可以调用,调用的结果就是可以改变类属性。
内置函数staticmethod和classmethod可以把类中的方法标记成为特殊的方法(标记成为静态方法和类方法。)标记之后,就会将Python默认传递第一个参数的机制给屏蔽掉
>>> class c():
... def func():
... print("func called")
... func2=staticmethod(func)
...
>>> lxm=c()
>>> lxm.func()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func() takes 0 positional arguments but 1 was given #默认情况下还是传入了实例
>>> lxm.func2() #通过内置staticmethod就不会传入实例对象了
func called
>>>
类似的,用classmethod函数时,就将方法标记为类方法,默认情况下,就将该实例的类传入成为第一个参数。
从技术上讲,Python现在支持三种类相关方法:实例,静态和类。
- 实例方法就是必须用实例来调用的方法,Python会把实例对象当作方法的第一个参数传入方法内。
- 静态方法调用时不需要实例参数,其变量属于类,是在类的范围内,属于局部变量。
- 类方法与实例方法类似,但是第一个参数传入的应该是类,而不是实例。
装饰器和元类
staticmethod和classmethod这类函数属于Python中的函数装饰器,它们就像是在函数外包裹了一层,替函数明确了特定的运算模式,返回了一个新的函数对象。函数装饰器类似于委托设计模式,但是其设计是为了增强特定的函数或方法调用,而不是整个对象接口。
staticmethod装饰器编写还可以使用以下语法:
这两种语法都是等价的:
class c:
@staticmethod
def meth():
<statements>
class c:
def meth():
<statements>
meth=staticmethod(meth)
staticmethod仍然是一个内置函数,它可以用于装饰语法中,是因为它把一个函数当作参数并返回一个可调用的对象。
这些都是Python的高级应用,对工具编写者有意义,但对于应用程序员就没那么重要。
适当的使用类,可以提高代码的复用性,但是过多的包装和继承绝对不是好事,这让代码的逻辑太深,维护性和可读性太差。