Luckylau's Blog

你懂python吗(7)

python的新式类和旧式类

​ python的新式类是2.2版本引进来的,我们可以将之前的类叫做经典类或者旧式类。为什么要在2.2中引进new style class呢?官方给的解释是:为了统一类(class)和类型(type)。

使用环境是python 2.7

新式类与旧式类的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class C(object):
pass
class B:
pass
if __name__ == '__main__':
c = C()
b = B()
print type(c)
print c.__class__
print type(b)
print b.__class__
print "**********************"
print dir(C)
print dir(B)
#output
<class '__main__.C'>
<class '__main__.C'>
<type 'instance'>
__main__.B
**********************
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
['__doc__', '__module__']

​ 如上所示b是旧式类的一个实例,c是新式类的一个实例。c的__class__与type返回的结果是一样的,得到统一。而b的__class__与type返回的结果并不相同。同时我们还发现新式类有更多的属性和方法,旧式类只有区区的2个方法。

​ 我们也发现为了向前兼容,默认情况下用户定义的类为经典类,新类需要继承自所有类的基类 object 或者继承自object的新类。那么为了确保自己使用的是新式类,有两种以下方法:

  1. 元类,在类模块代码的最前面加入如下代码 __metaclass__ = classname(自定义的某个新式类)。
  2. 类都从内建类object直接或者间接地继承。

新式类的属性和方法

内置的object对象是所有内置,object对象定义了一系列特殊的方法实现所有对象的默认行为。

1.__new____init__方法

这两个方法是用来创建object的子类对象,静态方法__new__()用来创建类的实例,然后再调用__init__()来初始化实例。

1
2
3
4
5
6
7
8
9
10
11
12
class C(object):
pass
class B:
pass
if __name__ == '__main__':
c = C("ooo")
b = B("ooo")
#output
TypeError: object() takes no parameters #新式类
TypeError: this constructor takes no arguments #旧式类

新式类都有一个__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“),实际执行的代码为:

1
2
3
c = C.__new__(C, "ooo")
if isinstance(c, C):
C.__init__(c, "ooo")

可以使用__new__来实现Singleton单例模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Singleton(object):
_singleton={}
def __new__(cls, *args, **kwargs):
if not cls._singleton.has_key(cls):
cls._singleton[cls]=object.__new__(cls)
return cls._singleton[cls]
class B(object): #做对比
pass
if __name__ == '__main__':
a=Singleton()
print id(a)
b=Singleton()
print id(b)
c=B()
print id(c)
d=B()
print id(d)
#output
140573118190288
140573118190288
140573118190352
140573118190416

使用id()操作,可以看到两个实例指向同一个内存地址。Singleton的所有子类也有这一
特性,只有一个实例对象,如果它的子类定义了__init__()方法,那么必须保证它的
__init__方法能够安全的同一个实例进行多次调用。

2.__delattr__, __getattribute__, __setattr__方法

对象使用这些方法来处理属性的访问

__getattribute__

对新式类的实例来说,所有属性和方法的访问操作都是通过getattribute完成,这是由object基类实现的。如果有特殊的要求,可以重载getattribute方法,下面实现一个不能使用append方法的list:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class listNoappend(list):
def __getattribute__(self, item):
if item == 'append':
raise AttributeError(item)
return list.__getattribute__(self, item)
if __name__ == '__main__':
a = listNoappend()
print type(a)
print dir(a)
a.append("abc")
#output
<class '__main__.listNoappend'>
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__dict__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
a.append("abc")
raise AttributeError(item)
AttributeError: append

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__属性可以赋值一个包含类属性名的字符串元组,或者是可迭代变量,或者
是一个字符串。

只有在新式类中生效,如下对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class A: #做对比
def __init__(self,x,y):
self.x=x
self.y=y
__slots__='x','y'
class B(object):
def __init__(self,x,y):
self.x=x
self.y=y
__slots__='x','y'
if __name__ == '__main__':
a=A(3,6)
print a.x
print a.y
a.z=7
print a.z
b = B(3, 6)
print b.x
print b.y
b.z=6
print b.z
#output
3
6
7
3
6
AttributeError: 'B' object has no attribute 'z'

需要注意的几点:

当一个类的父类没有定义__slots__属性,父类中的__dict__属性总是可以访问到的,所以只在子类中定义__slots__属性,而不在父类中定义是没有意义的。

如果定义了__slots__属性,还是想在之后添加新的变量,就需要把__dict__字符串添加到__slots__的元组里。

1
2
3
4
5
class B(object):
def __init__(self,x,y):
self.x=x
self.y=y
__slots__=('x','y',"__dict__")

定义了__slots__属性,还会消失的一个属性是__weakref__,这样就不支持实例的weak reference,如果还是想用这个功能,同样,可以把’__weakref__‘字符串添加到元组里。

__slots__功能是通过descriptor实现的,会为每一个变量创建一个descriptor。

__slots__的功能只影响定义它的类,因此,子类需要重新定义__slots__才能有它的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class B(object):
def __init__(self,x,y):
self.x=x
self.y=y
__slots__=('x','y')
class C(B):
pass
if __name__ == '__main__':
b = B(3, 6)
c=C(3,6)
c.z=9
print c.z
b.z=9
print b.z
# output
9
'B' object has no attribute 'z'

5.__getitem__方法

​ 在python中,隐式调用实例的私有特殊方法时,新的对象模型和经典对象模型表现上不太一样。在经典对象模型中,无论是显示调用还是隐式调用特殊方法,都会调用实例中后绑定的特殊方法。而在新的对象模型中,除非显式地调用实例的特殊方法,否则python总是会去调用类中定义的特殊方法,如果没有定义的话,就报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def getItem(index):
return index + 1
class OldStyle:
pass
class NewStyle(object):
pass
if __name__ == '__main__':
old = OldStyle()
old.__getitem__=getItem
print old[2]
new =NewStyle()
new.__getitem__=getItem
print new.__getitem__(2) #显示调用
print new[2]
# output
3
3
TypeError: 'NewStyle' object does not support indexing

调用old[1],将产生一个隐式的getitem方法的调用,在新式类中,因为类中没有定义这个方法,也不是
object基类有的方法,所以报错。需要显示地调用才可以运行。

新式类的继承

​ 新式类同样支持多继承,但是如果新式类想要从多个内置类型中继承生成一个新类的话,则这些内置类必须是经过精心设计,能够互相兼容的。显然,python也没会让你随意的从多个内置类中进行多继承,想创建一个超级类不是那么容易的。。。通常情况下,至多可以继承一个内置类,比如list, set, dict等。

下图是MRO(Method Resolution Order ,方法解析顺序),分别是旧式类和新式类的方法顺序。旧式类深度优先的方式进行查找,新式类广度优先的方式查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class D:
def __init__(self):
self.x = "d"
class B(D):
pass
class C(D):
def __init__(self):
self.x = "c"
class A(B,C):
pass
if __name__ == '__main__':
a=A()
print a.x
print A.__mro__
#output
d
AttributeError: class A has no attribute '__mro__'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class D(object):
def __init__(self):
self.x = "d"
class B(D):
pass
class C(D):
def __init__(self):
self.x = "c"
class A(B,C): #当B,C变成C,B时候 A.__mro__的输出顺序也会变
pass
if __name__ == '__main__':
a=A()
print a.x
print A.__mro__
#output
c
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <type 'object'>)

另一个注意的是协作式调用父类方法使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class A(object):
def foo(self):
print "AAAAA"
class B(A):
def foo(self):
print "BBBBB"
A.foo(self)
class C(A):
def foo(self):
print "CCCCC"
A.foo(self)
class D(B,C):
def foo(self):
print "DDDDD"
B.foo(self)
C.foo(self)
if __name__ == '__main__':
d=D()
print d.foo()
print D.__mro__
#output
DDDDD
BBBBB
AAAAA
CCCCC
AAAAA
None
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

​ 可以看到,基类A的方法重复运行了两次。怎样才能确保父类中的方法只被顺序的调用一次呢?在新的对象系统中,有一种特殊的方法super(aclass, obj),可以返回obj实例的一个特殊类型superobject(超对象, 不是简单的父类的对象),当我们使用超对象调用父类的方法时,就能保证只被运行一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class A(object):
def foo(self):
print "AAAAA"
class B(A):
def foo(self):
print "BBBBB"
super(B,self).foo()
class C(A):
def foo(self):
print "CCCCC"
super(C, self).foo()
class D(B,C):
def foo(self):
print "DDDDD"
super(D, self).foo()
if __name__ == '__main__':
d=D()
print d.foo()
#output
DDDDD
BBBBB
CCCCC
AAAAA
None

​ 可以看到,D的父类中所有的foo方法都得到执行,并且基类A的foo方法只执行了一次。如果养成了
使用super去调用父类方法的习惯,那么你的类就可以适应无论多么复杂的继承调用结构。super()
可以看成是更加安全调用父类方法的一种新方式。

新式类的Descriptor

​ descriptor可以说是一个绑定了特定访问方法的类属性,这些访问方法是重写了descriptor protocol中的三个方法,分别是__get__, __set__, __del__方法。如果三个中任一一个方法在对象中定义了,就说这个对象是一个descriptor对象,可以把这个对象赋值给其它属性。descriptor protocol可以看成是一个有三个方法的接口。

​ 通常对一个实例的属性的访问操作,如get, set, delete是通过实例的__dict__字典属性进行的,例如下面代码:

1
2
3
4
5
6
7
8
9
class Person(object):
name="lucky"
if __name__ == '__main__':
person=Person()
person.name="Lau"
print person.name
#output
Lau

​ 对于操作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__属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class DescriptorName(object):
def __init__(self,name):
self.name=name
def __get__(self, instance, owner):
print '__get__' , instance,owner
return self.name
def __set__(self, instance, value):
print '__set__',instance,value
self.name=value
class Person(object):
name=DescriptorName("Lucky")
if __name__ == '__main__':
person=Person()
person.name="Lau"
print person.name
#output
__set__ <__main__.Person object at 0x7f9b2458c490> Lau
__get__ <__main__.Person object at 0x7f9b2458c490> <class '__main__.Person'>
Lau

上面的例子是基于类的方式来创建描述符,你还可以通过property()函数来创建描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Person(object):
def __init__(self, name):
self.name = name
self._email=None
def get_email(self):
print ' get_email is invoked'
return self._email
def set_email(self, value):
print ' set_email is invoked'
self._email = value
def del_email(self):
print 'del_email is invoked'
del self._email
email = property(get_email, set_email, del_email, 'this is email property')
if __name__ == '__main__':
person = Person("Luckylau")
person.email="laujunbupt0913@163.com" # set_email is invoked
print person.email # get_email is invoked
del person.email # del_email is invoked
#output
set_email is invoked
get_email is invoked
laujunbupt0913@163.com
del_email is invoked

property()函数返回的是一个描述符对象,它可接收四个参数property(fget=None, fset=None, fdel=None, doc=None)

  • fget:属性获取方法
  • fset:属性设置方法
  • fdel:属性删除方法
  • doc: docstring

使用纯python的方式来实现property函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Property(object):
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, instance, owner):
if instance is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(instance)
def __set__(self, instance, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(instance, value)
def __delete__(self, instance):
if self.fdel is None:
raise AttributeError("can't del attribute")
self.fdel(instance)
def getter(self, fget):
print "Property getter is invoked "
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
print "Property setter is invoked "
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
print "Property deleter is invoked "
return type(self)(self.fget, self.fset, fdel, self.__doc__)

同时你还可以用property装饰器创建描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class Property(object):
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, instance, owner):
if instance is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(instance)
def __set__(self, instance, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(instance, value)
def __delete__(self, instance):
if self.fdel is None:
raise AttributeError("can't del attribute")
self.fdel(instance)
def getter(self, fget):
print "Property getter is invoked "
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
print "Property setter is invoked "
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
print "Property deleter is invoked "
return type(self)(self.fget, self.fset, fdel, self.__doc__)
class Person(object):
def __init__(self,name):
self.name=name
self._email = None
@Property
def email(self):
pass
@email.getter
def email(self):
print " get_email is invoked "
return self._email
@email.setter
def email(self, value):
print " set_email is invoked "
self._email = value
@email.deleter
def email(self):
print " del_email is invoked "
del self._email
if __name__ == '__main__':
p = Person("Luckylau")
print "**************"
p.email = "laujunbupt0913@163.com"
print "**************"
print p.email
#output
Property getter is invoked
Property setter is invoked
Property deleter is invoked
**********
set_email is invoked
**********
get_email is invoked
laujunbupt0913@163.com

参考:

http://www.pythontab.com/html/2015/pythonjichu_1113/982.html

http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html

http://blog.csdn.net/imzoer/article/details/8737642

Luckylau wechat
如果对您有价值,看官可以打赏的!