http://www.liaoxuefeng.com/ python 面向对象高级编程 定制类
Posted on 2015-12-03 10:32 bw_0927 阅读(242) 评论(0) 收藏 举报数据封装、继承和多态只是面向对象程序设计中最基础的3个概念。在Python中,面向对象还有很多高级特性,允许我们写出非常强大的功能。
我们会讨论多重继承、定制类、元类等概念。
动态绑定属性和方法
先定义class:
class Student(object):
pass
- 给实例绑定一个属性:
>>> s = Student() >>> s.name = 'Michael' # 动态给实例绑定一个属性 >>> print(s.name) Michael
- 给实例绑定一个方法:
>>> def set_age(self, age): # 先定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25
但是,给一个实例绑定的方法,对另一个实例是不起作用的:
>>> s2 = Student() # 创建新的实例
>>> s2.set_age(25) # 尝试调用方法
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'set_age'
- 给所有实例都绑定方法,可以给class绑定方法:
>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = MethodType(set_score, Student)
给class绑定方法后,所有实例均可调用:
>>> s.set_score(100)
>>> s.score
100
>>> s2.set_score(99)
>>> s2.score
99
通常情况下,上面的set_score
方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。
使用__slots__
但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name
和age
属性。
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__
变量,来限制该class实例能添加的属性:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
然后,我们试试:
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
由于'score'
没有被放到__slots__
中,所以不能绑定score
属性,试图绑定score
将得到AttributeError
的错误。
使用__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
>>> class GraduateStudent(Student):
... pass
...
>>> g = GraduateStudent()
>>> g.score = 9999
除非在子类中也定义__slots__
,这样,子类实例允许定义的属性就是自身的__slots__
加上父类的__slots__
。
多重继承------MixIn---------
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich
继承自Bird
。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich
除了继承自Bird
外,再同时继承Runnable
。这种设计通常称之为MixIn。
为了更好地看出继承关系,我们把Runnable
和Flyable
改为RunnableMixIn
和FlyableMixIn
。类似的,你还可以定义出肉食动物CarnivorousMixIn
和植食动物HerbivoresMixIn
,让某个动物同时拥有好几个MixIn:
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
Python自带的很多库也使用了MixIn。举个例子,Python自带了TCPServer
和UDPServer
这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn
和ThreadingMixIn
提供。通过组合,我们就可以创造出合适的服务来。
比如,编写一个多进程模式的TCP服务,定义如下:
class MyTCPServer(TCPServer, ForkingMixIn):
pass
编写一个多线程模式的UDP服务,定义如下:
class MyUDPServer(UDPServer, ThreadingMixIn):
pass
如果你打算搞一个更先进的协程模型,可以编写一个CoroutineMixIn
:
class MyTCPServer(TCPServer, CoroutineMixIn):
pass
这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。
小结
由于Python允许使用多重继承,因此,MixIn就是一种常见的设计。
定制类
直接显示变量调用的不是__str__()
,而是__repr__()
,两者的区别是__str__()
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串,也就是说,__repr__()
是为调试服务的。
通常__str__()
和__repr__()
代码都是一样的,所以,有个偷懒的写法:
__repr__ = __str__
__iter__
如果一个类想被用于for ... in
循环,类似list或tuple那样,就必须实现一个__iter__()
方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()
方法拿到循环的下一个值,直到遇到StopIteration
错误时退出循环。
__getitem__
Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:
>>> Fib()[5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Fib' object does not support indexing
要表现得像list那样按照下标取出元素,需要实现__getitem__()
方法。
切片的支持:
__getitem__()
传入的参数可能是一个int,也可能是一个切片对象slice
,所以要做判断:
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int): # n是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
也没有对负数作处理,所以,要正确实现一个__getitem__()
还是有很多工作要做的。
此外,如果把对象看成dict
,__getitem__()
的参数也可能是一个可以作key的object,例如str
。
与之对应的是__setitem__()
方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()
方法,用于删除某个元素。
__getattr__
当调用不存在的属性时,比如score
,Python解释器会试图调用__getattr__(self, 'score')
来尝试获得属性,返回函数也是完全可以的。
注意,只有在没有找到属性的情况下,才调用__getattr__
,已有的属性,比如name
,不会在__getattr__
中查找。
__call__
任何类,只需要定义一个__call__()
方法,就可以直接对实例进行调用。
>>> s = Student('Michael') >>> s() # self参数不要传入
通过callable()
函数,我们就可以判断一个对象是否是“可调用”对象。
使用枚举类
当我们需要定义常量时,一个办法是用大写变量通过整数来定义,例如月份:
JAN = 1
FEB = 2
MAR = 3
...
NOV = 11
DEC = 12
好处是简单,缺点是类型是int
,并且仍然是变量。
更好的方法是为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。Python提供了Enum
类来实现这个功能:
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
这样我们就获得了Month
类型的枚举类,可以直接使用Month.Jan
来引用一个常量,或者枚举它的所有成员。
@unique
装饰器可以帮助我们检查保证没有重复值。
使用元类
http://docspy3zh.readthedocs.org/en/latest/reference/datamodel.html
http://python-reference.readthedocs.org/en/latest/docs/dunderattr/setattr.html
__setattr__
Description
Called when an attribute assignment is attempted.
Syntax
object. __setattr__(self, name, value)
- self
- Required. Instance of the class, passed automatically on call.
- name
- Required. The name of the attribute.
- value
- Required. The value we want to assign to the attribute.
Return Value
#TODO
Time Complexity
#TODO
Remarks
This method is called instead of the normal mechanism (i.e. store the value in the instance dictionary).
If __setattr__() wants to assign to an instance attribute, it should not simply execute self.name = value — this would cause a recursive call to itself.
Instead, it should insert the value in the dictionary of instance attributes, e.g., self.__dict__[name] = value.
For new-style classes, rather than accessing the instance dictionary, it should call the base class method with the same name, for example,
>>> object.__setattr__(self, name, value).
Example
>>> # this example uses __setattr__ to dynamically change attribute value to uppercase
>>> class Frob:
... def __setattr__(self, name, value):
... self.__dict__[name] = value.upper()
...
>>> f = Frob()
>>> f.bamf = "bamf"
>>> f.bamf
'BAMF'
3.3.2. 自定义属性权限
以下方法可以用于定制访问类实例属性的含义 (例如, 赋值, 或删除 x.name
)
object.
__getattr__
(self, name)-
在正常方式访问属性无法成功时 (就是说, self属性既不是实例的, 在类树结构中找不到) 使用.
name
是属性名. 应该返回一个计算好的属性值, 或抛出一个AttributeError
异常.注意, 如果属性可以通过正常方法访问,
__getattr__()
是不会被调用的 (是有意将__getattr__()
和__setattr__()
设计成不对称的). 这样做的原因是基于效率的考虑, 并且这样也不会让__getattr__()
干涉正常属性. 注意, 至少对于类实例而言, 不必非要更新实例字典伪装属性 (但可以将它们插入到其它对象中). 需要全面控制属性访问, 可以参考以下__getattribute__()
的介绍.
object.
__getattribute__
(self, name)-
在访问类实例的属性时无条件调用这个方法. 如果类也定义了方法
__getattr__()
, 那么除非__getattribute__()
显式地调用了它, 或者抛出了AttributeError
异常, 否则它就不会被调用. 这个方法应该返回一个计算好的属性值, 或者抛出异常AttributeError
. 为了避免无穷递归, 对于任何它需要访问的属性, 这个方法应该调用基类的同名方法, 例如,object.__getattribute__(self, name)
.Note
但是, 通过特定语法或者内建函式, 做隐式调用搜索特殊方法时, 这个方法可能会被跳过, 参见 搜索特殊方法.
object.
__setattr__
(self, name, value)-
在属性要被赋值时调用. 这会替代正常机制 (即把值保存在实例字典中). name 是属性名, vaule 是要赋的值.
如果在
__setattr__()
里要对一个实例属性赋值, 它应该调用父类的同名方法, 例如,object.__setattr__(self, name, value)
.
object.
__delattr__
(self, name)-
与
__setattr__()
类似, 但它的功能是删除属性. 当del obj.name
对对象有意义时, 才需要实现它.
object.
__dir__
(self)-
在对象上调用
dir()
时调用, 它需要返回一个列表.http://blog.csdn.net/fjslovejhl/article/details/40683547
(1)__getattr__(self, item):
在访问对象的item属性的时候,如果对象并没有这个相应的属性,方法,那么将会调用这个方法来处理。。。这里要注意的时,假如一个对象叫fjs, 他有一个属性:fjs.name = "fjs",那么在访问fjs.name的时候因为当前对象有这个属性,那么将不会调用__getattr__()方法,而是直接返回了拥有的name属性了
(2)__setattr__(self, item, value):
当试图对象的item特性赋值的时候将会被调用。。
(3)__getattribute__(self, item):
这个只有在新式类中才有的,对于对象的所有特性的访问,都将会调用这个方法来处理。。。可以理解为在__getattr__之前
嗯。。。有了这几个方法就可以干很多很多的事情了。。。例如拦截器啥的。。。动态代理啥的。。。很方便就能实现了。。。起码比用java实现类似的功能方便多啦。。。。
不过需要注意的时候,在重写这些方法的时候需要特别的小心,因为容易引起循环调用。。。。
这里先来举一个例子,用于实现拦截所有的特性访问,在访问的时候打log啥的:
- # -*- coding: utf-8 -*-
- class Fjs(object):
- def __init__(self, name):
- self.name = name
- def hello(self):
- print "said by : ", self.name
- def __getattribute__(self, item):
- print "访问了特性:" + item
- return object.__getattribute__(self, item)
- fjs = Fjs("fjs")
- print fjs.name
- fjs.hello()
上述代码的输出如下:
- 访问了特性:name
- fjs
- 访问了特性:hello
- said by : 访问了特性:name
- fjs
很简单就实现了拦截的功能吧。。。而且这里可以知道__getattribute__方法拦截了属性和方法的访问。。这里也就是所谓的所有的特性的访问了。。不过要注意的是:__getattribute__只有在新式类中才能用的。。。
嗯。。接下来配合使用__getattr__和__getattribute__来实现一个非切入式的编程:
- # -*- coding: utf-8 -*-
- class Fjs(object):
- def __init__(self, name):
- self.name = name
- def hello(self):
- print "said by : ", self.name
- def fjs(self, name):
- if name == self.name:
- print "yes"
- else:
- print "no"
- class Wrap_Fjs(object):
- def __init__(self, fjs):
- self._fjs = fjs
- def __getattr__(self, item):
- if item == "hello":
- print "调用hello方法了"
- elif item == "fjs":
- print "调用fjs方法了"
- return getattr(self._fjs, item)
- fjs = Wrap_Fjs(Fjs("fjs"))
- fjs.hello()
- fjs.fjs("fjs")
这里通过__getattr__方法,将所有的特性的访问都路由给了内部的fjs对象。。。。。。
最后,关于__setattr__()方法,这个就不细说了。。。不过他的使用还需要特别注意一些。。因为稍不注意就容易陷入循环调用了。。。。