[转载]Python中包装(wrapping)与代理(delegation)

本文介绍Python中的包装(wrapping)和代理(delegation)概念,通过具体代码示例展示如何利用composition模式对对象进行包装,以实现接口转换及功能增强。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://blog.sina.com.cn/s/blog_93b45b0f0100zfv7.html
作者:刻卜浪兴

(1)包装(wrapping)的基本思想和内容

(下面主要内容对大部分编程语言适用)

实际上,包装一词是有关程序设计中的,事实上,如果你的系统如果不是较为复杂的话,这个概念基本上不会怎么用到。

我们知道,面向对象程序设计中有一个理念就是封装,而实际上面向对象也让程序员在使用一些类(自己定义或则别人定义)对象的时候非常方便。包装就是一个希望将某个对象(或类),进行重新打包,装换成另外一种更适合当前使用场合的对外接口。相似娱乐界对明星的包装一样,其实本质没有什么改变,只是表面变得更容易受当下人欢迎而已。用到程序编程中,就是将程序包装成,当前环境跟适合的样子(主要是对外接口)。

一个例子,假如一个类中包含了许多的数据属性,同时也包含了许多的基本操作函数,但是我们要求得这个类的对象的某个值(来自于对这个类对象中的各个数据属性和操作函数的多次操作),那么是不是每次都需要做一遍这样的事情呢?理论上是这样的,有人会说,那为什么不在当初定义类的时候就定义一个这样的函数呢?对!但是,当初定义的时候或许想不到这些,而更为重要的是,如果将某个类定义的过分应用于某个情景,那么这个类就并不好在其他地方使用,而如果把它实际到适合在许多情景中使用,那又会是类变得特别的负责,所以基本上,我们认为类应该设计的比较基本为好。

所以,有没有解决这一问题的办法呢?有,那就是封装!

在不同的应用场景将那个各个方法都较为基础的类包装成一个适合在该地方使用的包装类。

那么包装这个设计思想有哪些方法和以实现呢?
我们说既然是面向对象设计,那自然不能脱离类来讲咯(实际上包装的对象可以是任何对象)。我们找方法的范围也减少了。这样包装的概念就是将一个类所能干的事情,通过不同的方式转换成另外一个类,它们的内容本质(这里主要是说数据信息)不变,只是对外操作的接口做了相应的转变,或者添加了,或者减少了,或者转变了,或者限制了等等。

一个类与另外一个类的关系只有两种一种是继承关系(包括祖先),另外一种是包含的关系。也就是derivation和composition两种关系。

通过继承的方式似乎可以,将原来的类成员先全部继承,然后可以对函数进行重写覆盖,也可以添加性函数等等。这是一个好方法,但是,在有些编程语言中如python2.2之前,基本数据类型不属于类,那样就不能被类继承,就不能进行所谓的包装。

所以还可以通过另外一个方法来做到一点,那就是创建一个类,而这个类往往只包含一个数据成员对象,那就是它要封装的类的对象。通俗的讲,就是让这个封装类包含这个被封装类的实例。然后通过在这个封装类中各种方法的设置,实质上就是从这个包含对象的接口传递、转换其相应的功能到这个包装类上。让外界感觉似乎就是在操作该被封装的类一样。可以看出,封装类在转换的时候有很多弹性,所以完全可以做到前面所说的包装效果。

其实,我们可这么想,在type和class还不是一回事的时候,我们没有办法使用继承的方法来对基本数据类型进行封装,所以只能通过这个手段了。但是现在type和class的统一是基本趋势。但仍然不会有违通过composition的方法来进行包装。实际上两种包装手段可以呈现出一种互补的态势,可能大家有一些各自不适合的地方。


(2)Python中通过composition方式包装。

class WrapMe(object):
    def __init__(self, obj): #这个声明了构造函数,实际上就是模拟要包装对象的构建方法
        self.__data = obj #__data就是这个被包装对象的核心,使用__data有一定的隐蔽性
    def get(self):   #放回封装中的数据,这个是添加的方法。
        return self.__data  
    def __repr__(self):  #特殊函数就是Python编译是如何输入这个对象
        return 'self.__data'
    def __str__(self):   #特殊函数,其实就是转换那个__data中的同样的功能
        return str(self.__data)  #特殊函数__str__就是为内建函数准备的
    def __getattr__(self, attr):  #这个是关键,就是代理(delegation) 下面详细讲
        return getattr(self.__data, attr)

我们提到使用composition模式进行需要对被包装得对象的属性(包括数据和方法)进行转换,但是面向对象编程模式是不会容忍需要程序员对该对象所有属性都了如指掌的,这样要求过于苛刻,并且会让整个包装非常麻烦。所以有没有一个很好的方法,将该对象的所有属性(数据和方法)先全部自动转换再说(这种机制要是使用composition就直接解决了)。有!那就是代理。


代理的意思是,“我是一个中间转接的人,你有什么东西传递给我,然后我再转接给另外一个人,我插在中间,隔离了你们的直接交流,但是却也方便了你们,不用那么麻烦,因为我是专门做这一行的。”

其实就是这个语句:
def __getattr__(self, attr):  
        return getattr(self.__data, attr)

感觉很普通是不是,__getattr__是我们之前学过的类对象的特殊函数,它的作用就是定义该对象的“.”(我们平时来引用对象属性的符号)的功能。也就是说定义了这个函数之后,封装好的类实例对象就可以应用更为丰富的东西了。注意这个特殊方法只是在对象找不到相应的方法的时候才会调用的,也就是说如果该对象种存在(__dict__中)那么就不会启动这个函数。(而还有一个特殊函数__getattribute___,就是那种什么时候都会启动的,它才是真正的" . "的代表)

我们可以看到这个函数的内部。它是通过getattr内建函数来获得,被包装对象的特定属性(数据或函数),然后返回给前面,所以意思就是说。通过这样一弄,包装类就可以继承被包装的对象的所有属性了。

    >>> wrappedComplex = WrapMe(3.5+4.2j)   #封装了一个复杂数据对象
    >>> wrappedComplex                # wrapped object: repr()
    (3.5+4.2j)
    >>> wrappedComplex.real           # real attribute  
    3.5
    >>> wrappedComplex.imag           # imaginary attribute
    42.2
    >>> wrappedComplex.conjugate()    # conjugate() method
    (3.5-4.2j)
    >>> wrappedComplex.get()          # actual object
    (3.5+4.2j)

我们看到,上面wrappedComlex竟然引用成功了不属于它的属性的属性,这就是特殊函数__getattr__造出来的假象,不过你会发现,在解释器命令行中,系统并不能自动提示那些不是自身属性的属性,这才对!实际上这个" . " 符号就是一个函数引用。


这个封装类可以封装其它的类对象:
    >>> wrappedList = WrapMe([123, 'foo', 45.67])
    >>> wrappedList.append('bar')
    >>> wrappedList.append(123)
    >>> wrappedList
    [123, 'foo', 45.67, 'bar', 123]
    >>> wrappedList.index(45.67)
    2
    >>> wrappedList.count(123)
    2
    >>> wrappedList.pop()
    123
    >>> wrappedList
    [123, 'foo', 45.67, 'bar']

但是:
    >>> wrappedList[3]
   Traceback (innermost last):
     File "<stdin>", line 1, in ?
     File "wrapme.py", line 21, in __getattr__
       return getattr(self.data, attr)
   AttributeError: __getitem__

奇怪了,你不是说只要使用了上面的代理,被包装的对象的属性就全部被代理过来了?这个功能怎么没有被代理过来。

 是的,但是这里很特殊的,wrappedList[3],其实是触发了 __getitem__特殊函数,但是这里并没有定义,所以报错了。当然你完全可以重写__getitem__特殊函数(对应[]),然后映射到被封装的对象上。

 

 

(3)更加丰富的包装例子:

这个封装的意义就是将普通的对象封装成,具有创建时间、修改时间、被访问时间属性的对象
 #!/usr/bin/env python
2
 from time import time, ctime
4
 class TimedWrapMe(object):
6
    def __init__(self, obj):
       self.__data = obj
       self.__ctime = self.__mtime = 
10           self.__atime = time()
11
12    def get(self):
13       self.__atime = time()
14       return self.__data
15
16    def gettimeval_r(self, t_type):
17       if not isinstance(t_type, str)  or 
18               t_type[0]  not in 'cma':
19           raise TypeError, 
20           "argument of 'c', 'm', or 'a' req'd"
21       return getattr(self, '_%s__%stime' % 
22           (self.__class__.__name__, t_type[0]))
23
24     def gettimestr(self, t_type):
25        return ctime(self.gettimeval_r(t_type))
26
27     def set(self, obj):
28        self.__data = obj
29        self.__mtime = self.__atime = time()
30
31     def __repr__(self):                # repr()
32        self.__atime = time()
33        return 'self.__data'
34
35     def __str__(self):                 # str()
36        self.__atime = time()
37        return str(self.__data)
38
39     def __getattr__(self, attr):       # delegate
40        self.__atime = time()
41        return getattr(self.__data, attr)

 

将一个文件对象封装成只能添加大写字符的文件对象(会自动转换成大写字符)

 #!/usr/bin/env python
2
 class CapOpen(object):
     def __init__(self, fn, mode='r', buf=-1):
         self.file = open(fn, mode, buf)
6
     def __str__(self):
         return str(self.file)
9
10     def __repr__(self):
11         return 'self.file'
12
13     def write(self, line):
14         self.file.write(line.upper())
15
16     def __getattr__(self, attr):
17         return getattr(self.file, attr)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值