博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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实例添加nameage属性。

为了达到限制的目的,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。

为了更好地看出继承关系,我们把RunnableFlyable改为RunnableMixInFlyableMixIn。类似的,你还可以定义出肉食动物CarnivorousMixIn和植食动物HerbivoresMixIn,让某个动物同时拥有好几个MixIn:

class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass

MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。

Python自带的很多库也使用了MixIn。举个例子,Python自带了TCPServerUDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixInThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。

比如,编写一个多进程模式的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__(selfname)

在正常方式访问属性无法成功时 (就是说, self属性既不是实例的, 在类树结构中找不到) 使用. name 是属性名. 应该返回一个计算好的属性值, 或抛出一个 AttributeError 异常.

注意, 如果属性可以通过正常方法访问, __getattr__() 是不会被调用的 (是有意将 __getattr__() 和 __setattr__() 设计成不对称的). 这样做的原因是基于效率的考虑, 并且这样也不会让 __getattr__() 干涉正常属性. 注意, 至少对于类实例而言, 不必非要更新实例字典伪装属性 (但可以将它们插入到其它对象中). 需要全面控制属性访问, 可以参考以下 __getattribute__() 的介绍.

object.__getattribute__(selfname)

在访问类实例的属性时无条件调用这个方法. 如果类也定义了方法 __getattr__(), 那么除非 __getattribute__() 显式地调用了它, 或者抛出了 AttributeError 异常, 否则它就不会被调用. 这个方法应该返回一个计算好的属性值, 或者抛出异常AttributeError. 为了避免无穷递归, 对于任何它需要访问的属性, 这个方法应该调用基类的同名方法, 例如, object.__getattribute__(self, name).

Note

 

但是, 通过特定语法或者内建函式, 做隐式调用搜索特殊方法时, 这个方法可能会被跳过, 参见 搜索特殊方法.

object.__setattr__(selfnamevalue)

在属性要被赋值时调用. 这会替代正常机制 (即把值保存在实例字典中). name 是属性名, vaule 是要赋的值.

如果在 __setattr__() 里要对一个实例属性赋值, 它应该调用父类的同名方法, 例如, object.__setattr__(self, name, value).

object.__delattr__(selfname)

与 __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啥的:

[python] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. # -*- coding: utf-8 -*-  
  2. class Fjs(object):  
  3.     def __init__(self, name):  
  4.         self.name = name  
  5.   
  6.     def hello(self):  
  7.         print "said by : "self.name  
  8.   
  9.     def __getattribute__(self, item):  
  10.         print "访问了特性:" + item  
  11.         return object.__getattribute__(self, item)  
  12.   
  13.   
  14. fjs = Fjs("fjs")  
  15. print fjs.name  
  16. fjs.hello()  

 

 

上述代码的输出如下:

[python] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. 访问了特性:name  
  2. fjs  
  3. 访问了特性:hello  
  4. said by :  访问了特性:name  
  5. fjs  

 

 

很简单就实现了拦截的功能吧。。。而且这里可以知道__getattribute__方法拦截了属性和方法的访问。。这里也就是所谓的所有的特性的访问了。。不过要注意的是:__getattribute__只有在新式类中才能用的。。。

 

嗯。。接下来配合使用__getattr__和__getattribute__来实现一个非切入式的编程:

[python] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. # -*- coding: utf-8 -*-  
  2. class Fjs(object):  
  3.     def __init__(self, name):  
  4.         self.name = name  
  5.   
  6.     def hello(self):  
  7.         print "said by : "self.name  
  8.   
  9.     def fjs(self, name):  
  10.         if name == self.name:  
  11.             print "yes"  
  12.         else:  
  13.             print "no"  
  14.   
  15. class Wrap_Fjs(object):  
  16.     def __init__(self, fjs):  
  17.         self._fjs = fjs  
  18.   
  19.     def __getattr__(self, item):  
  20.         if item == "hello":  
  21.             print "调用hello方法了"  
  22.         elif item == "fjs":  
  23.             print "调用fjs方法了"  
  24.         return getattr(self._fjs, item)  
  25.   
  26. fjs = Wrap_Fjs(Fjs("fjs"))  
  27. fjs.hello()  
  28. fjs.fjs("fjs")  

 

 

这里通过__getattr__方法,将所有的特性的访问都路由给了内部的fjs对象。。。。。。

 

最后,关于__setattr__()方法,这个就不细说了。。。不过他的使用还需要特别注意一些。。因为稍不注意就容易陷入循环调用了。。。。