十三、*授权
包装
包装:对一个已存在的对象进行包装,不管它是数据类型,还是一段代码,可以是对一个已存在的对象,增加新的,删除不要的,或者修改其它已存在的功能
包装包括定义一个类,它的实例拥有标准类型的核心行为
换句话说,它现在不仅能唱能跳,还能够像原类型一样步行,说话.
你还可以包装类,但这不会有太多的用途,因为已经有用于操作对象的机制,并且在上面已描述过,对标准类型有对其进行包装的方式
这个图片说明了在类中包装的类型看起来像什么样子,在图的中心为标准类型的核心行为,但他也通过新的或者最新的功能,甚至可能通过访问实际数据的不同方法得到提高
实现授权
授权是包装的一个特性,可用于简化处理有关命令功能,采用已存在的功能以达到最大限度的代码重用
包装一个类型通常是对已存在的类型的一些定制 。这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样,或者保留已存功能和行为。
授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性
实现授权的关键点就是覆盖__getattr__()方法,在代码中包含一个对 getattr()内建函数的调用
5.特别地,调用 getattr()以得到默认对象属性(数据属性或者方法)并返回它以便访问或调用。
6.特殊方法__getattr__()的工作方式是, 当搜索一个属性时, 任何局部对象首先被找到 (定制的对象)。
如果搜索失败了,则__getattr__()会被调用,然后调用
getattr()得到一个对象的默认行为
7.换言之,当引用一个属性时,Python
解释器将试着在局部名称空间中查找那个名字,比如一个自定义的方法或局部实例属性。如果没有在局部字典中找到,则搜索类名称空间,以防一个类属性被访问。最后,如果两类搜索都失败了,搜索则对原对象开始授权请求,此时,__getattr__()会被调用
包装对象的简例
看一个例子。这个类似乎可以包装任何对象,提供基本功能,比如使用 repr()和 str()来处理字符串表示法。
另外定制由 get()方法处理,它删除包装并且返回原始对象。所以保留的功能都授权给对象的本地属性,在必要时,可由__getattr__()获得。
下面是包装类的例子:
#!/usr/bin/env python
#coding:utf8
class WrapMe(object):
def __init__(self, obj):
self.__data = obj
def get(self):
return self.__data
def __repr__(self):
return 'self.__data'
def __str__(self):
return str(self.__data)
def __getattr__(self, attr):
"这个方法用于实现授权,即返回的对象可以使用接受到的对象的属性(方法,函数),但是不能使用原有对象的特殊行为"
return getattr(self.__data, attr)
wrappedComplex = WrapMe(3.5+4.2j)
print wrappedComplex # 包装的对象:repr()
#搜索属性的顺序是 解释器将试着在局部名称空间中查找哪个名字,如果没有就搜索类名称空间,最后如果两个搜索都是失败了,
#搜索则对原对象开始授权请求,此时__getattr__()会被调用
print wrappedComplex.real #实部属性
print wrappedComplex.imag # 虚部属性
print wrappedComplex.conjugate() # conjugate()方法
print wrappedComplex.get() # 实际对象
在第一个例子中, 我们将用到复数, 因为所有 Python 数值类型, 只有复数拥有属性: 数据属性,及 conjugate()内建方法(求共轭复数)。
记住,属性可以是数据属性,还可以是函数或方法:
执行结果:
(3.5+4.2j)
3.5
4.2
(3.5-4.2j)
(3.5+4.2j)
一旦我们创建了包装的对象类型, 只要由交互解释器调用 repr(), 就可以得到一个字符串表示。然后我们继续访问了复数的三种属性,我们的类中一种都没有定义。在例子中,寻找实部,虚部及共轭复数的定义。对这些属性的访问,是通过 getattr()方法,授权给对象.
最终调用 get()方法没有授权,因为它是为我们的对象定义的----它返回包装的真实的数据对象。
下一个使用我们的包装类的例子用到一个列表。我们将会创建对象, 然后执行多种操作,每次授权给列表方法。
#!/usr/bin/env python
#coding:utf8
class WrapMe(object):
def __init__(self, obj):
self.__data = obj
def get(self):
return self.__data
def __repr__(self):
return 'self.__data'
def __str__(self):
return str(self.__data)
def __getattr__(self, attr):
return getattr(self.__data, attr)
wrappedList = WrapMe([123, 'foo', 45.67])
wrappedList.append('bar')
wrappedList.append(123)
print wrappedList
print wrappedList.index(45.67)
print wrappedList.count(123)
print wrappedList.pop()
print wrappedList
执行结果:
[123, 'foo', 45.67, 'bar', 123]
2
2
123
[123, 'foo', 45.67, 'bar']
注意, 尽管我们正在我们的例子中使用实例, 它们展示的行为与它们包装的数据类型非常相似。然后,需要明白,只有已存在的属性是在此代码中授权的。
特殊行为没有在类型的方法列表中,不能被访问,因为它们不是属性。一个例子是,对列表的切片操作,
它是内建于类型中的, 而不是像 append()方法那样作为属性存在的。
从另一个角度来说,切片操作符是序列类型的一部分,并不是通过__getitem__()特殊方法来实现的。
#!/usr/bin/env python
#coding:utf8
class WrapMe(object):
def __init__(self, obj):
self.__data = obj
def get(self):
return self.__data
def __repr__(self):
return 'self.__data'
def __str__(self):
return str(self.__data)
def __getattr__(self, attr):
return getattr(self.__data, attr)
wrappedList = WrapMe([123, 'foo', 45.67])
wrappedList.append('bar')
wrappedList.append(123)
print wrappedList
print wrappedList.index(45.67)
print wrappedList.count(123)
print wrappedList.pop()
print wrappedList
print wrappedList[3] #"getattr()这个函数没有办法做到让对象使用原有对象的特殊行为,比如列表的切片属性"
然而,我们还有一种"作弊"的方法,访问实际对象[通过我们的 get()方法]和它的切片能力
#!/usr/bin/env python
#coding:utf8
class WrapMe(object):
def __init__(self, obj):
self.__data = obj
def get(self):
return self.__data
def __repr__(self):
return 'self.__data'
def __str__(self):
return str(self.__data)
def __getattr__(self, attr):
return getattr(self.__data, attr)
wrappedList = WrapMe([123, 'foo', 45.67])
wrappedList.append('bar')
wrappedList.append(123)
print wrappedList
print wrappedList.index(45.67)
print wrappedList.count(123)
print wrappedList.pop()
print wrappedList
realList = wrappedList.get()
print realList[3]
执行结果:
[123, 'foo', 45.67, 'bar', 123]
2
2
123
[123, 'foo', 45.67, 'bar']
bar
你现在可能知道为什么我们实现 get()方法了----仅仅是为了我们需要取得对原对象进行访问这种情况,我们可以从访问调用中直接访问对象的属性,而忽略局部变量(realList):
print wrappedList.get()[3]
'bar'
下面看一下没有__getattr__()方法的类:
#!/usr/bin/env python
#coding:utf8
class WrapMe_compare(object):
def __init__(self,obj):
self.__obj=obj;
def get(self):
return self.__obj
def __str__(self):
return self.__obj
__repr__=__str__
mc=WrapMe_compare(3.5+4.2j)
print mc.real() #AttributeError: 'WrapMe_compare' object has no attribute 'real' 这就是没有进行授权
执行结果:
AttributeError: 'WrapMe_compare' object has no attribute 'real'
更新简单的包裹类
1.创建时间,修改时间,及访问时间是文件的几个常见属性,如果你对使用这三类时间顺序数据还不熟,我们将会对它们进行解释。
2.创建时间(或'ctime')是实例化的时间,修改时间(或'mtime')指的是核心数据升级的时间(通常会调用新的 set()方法),
3.而访问时间(或'atime')是最后一次对象的数据值被获取或者属性被访问时的时间戳。
包装标准类型(twrapme.py)
#!/usr/bin/env python
#coding:utf-8
from time import time, ctime
class TimedWrapMe(object):
def __init__(self, obj):
self.__data = obj
self.__ctime = self.__mtime = self.__atime = time()
def get(self):
self.__atime = time()
return self.__data
def gettimeval(self, t_type):
#gettimeval()方法带一个简单的字符参数,“c”,“m”或“a”,相应地,对应于创建,修改访问时间,并返回相应的时间,以一个浮点值保存
if not isinstance(t_type, str) or t_type[0] not in 'cma':
raise TypeError, "argument of 'c', 'm', or 'a' req'd"
return getattr(self, '_%s__%stime' % (self.__class__.__name__, t_type[0]))
def gettimestr(self, t_type):
#gettimestr()仅仅返回一个经 time.ctime()函数格式化的打印良好的字符串形式的时间
return ctime(self.gettimeval(t_type))
def set(self, obj):
self.__data = obj
self.__mtime = self.__atime = time()
def __repr__(self): # repr()
self.__atime = time()
return 'self.__data'
def __str__(self): # str()
self.__atime = time()
return str(self.__data)
def __getattr__(self, attr): # delegate
self.__atime = time()
return getattr(self.__data, attr)
timeWrappedObj = TimedWrapMe(932)
print timeWrappedObj.gettimestr('c')
print timeWrappedObj.gettimestr('m')
print timeWrappedObj.gettimestr('a')
print timeWrappedObj
print timeWrappedObj.gettimestr('c')
print timeWrappedObj.gettimestr('m')
print timeWrappedObj.gettimestr('a')
执行结果:
Fri Apr 13 22:16:42 2018
Fri Apr 13 22:16:42 2018
Fri Apr 13 22:16:42 2018
932
Fri Apr 13 22:16:42 2018
Fri Apr 13 22:16:42 2018
Fri Apr 13 22:16:42 2018
你将注意到,一个对象在第一次被包装时,创建,修改,及最后一次访问时间都是一样的。一旦对象被访问,访问时间即被更新,但其它的没有动。
如果使用 set()来置换对象,则修改和最后一次访问时间会被更新。例子中,最后是对对象的读访问操作。
#!/usr/bin/env python
#coding:utf-8
from time import time, ctime
class TimedWrapMe(object):
def __init__(self, obj):
self.__data = obj
self.__ctime = self.__mtime = self.__atime = time()
def get(self):
self.__atime = time()
return self.__data
def gettimeval(self, t_type):
#gettimeval()方法带一个简单的字符参数c,m或a,相应地,对应于创建,修改访问时间,并返回相应的时间,以一个浮点值保存
if not isinstance(t_type, str) or t_type[0] not in 'cma':
raise TypeError, "argument of 'c', 'm', or 'a' req'd"
return getattr(self, '_%s__%stime' % (self.__class__.__name__, t_type[0]))
def gettimestr(self, t_type):
#gettimestr()仅仅返回一个经 time.ctime()函数格式化的打印良好的字符串形式的时间
return ctime(self.gettimeval(t_type))
def set(self, obj):
self.__data = obj
self.__mtime = self.__atime = time()
def __repr__(self): # repr()
self.__atime = time()
return 'self.__data'
def __str__(self): # str()
self.__atime = time()
return str(self.__data)
def __getattr__(self, attr): # delegate
self.__atime = time()
return getattr(self.__data, attr)
timeWrappedObj = TimedWrapMe(932)
print timeWrappedObj.gettimestr('c')
print timeWrappedObj.gettimestr('m')
print timeWrappedObj.gettimestr('a')
print timeWrappedObj
print timeWrappedObj.gettimestr('c')
print timeWrappedObj.gettimestr('m')
print timeWrappedObj.gettimestr('a')
print timeWrappedObj.set('time is up!')
print timeWrappedObj.gettimestr('m')
print timeWrappedObj
print timeWrappedObj.gettimestr('c')
print timeWrappedObj.gettimestr('m')
print timeWrappedObj.gettimestr('a')
执行结果:
Fri Apr 13 22:19:33 2018
Fri Apr 13 22:19:33 2018
Fri Apr 13 22:19:33 2018
932
Fri Apr 13 22:19:33 2018
Fri Apr 13 22:19:33 2018
Fri Apr 13 22:19:33 2018
None
Fri Apr 13 22:19:33 2018
time is up!
Fri Apr 13 22:19:33 2018
Fri Apr 13 22:19:33 2018
Fri Apr 13 22:19:33 2018
改进包装一个特殊对象
创建一个一个包装文件对象的类。我们的类与一般带一个异常的文件对象行为完全一样:在写模式中,字符串只有全部为大写时,才写入文件。
很多老式机器在处理时,严格要求大写字母,所以,我们要实现一个文件对象,其中所有写入文件的文本会自动转化为大写,程序员就不必担心了。
事实上, 唯一值得注意的不同点是并不使用 open()内建函数, 而是调用 CapOpen 类时行初始化。尽管,参数同 open()完全一样。
包装文件对象(capOpen.py)
这个类扩充了 Python FAQs 中的一个例子,提供一个文件类对象,定制 write()方法,同时,给文件对象授权其它的功能
#!/usr/bin/env python
#coding:utf-8
class CapOpen(object):
def __init__(self, fn, mode='r', buf=-1):
self.file = open(fn, mode, buf)
def __str__(self):
return str(self.file)
def __repr__(self):
return 'self.file'
def write(self, line):
self.file.write(line.upper())
def __getattr__(self, attr):
return getattr(self.file, attr)
f = CapOpen('/tmp/xxx', 'w')
f.write('delegation example
')
f.write('faye is good
')
f.write('at delegating
')
f.close()
print f
执行结果:
<closed file '/tmp/xxx', mode 'w' at 0x7fcf79dc1930>
可以看到,唯一不同的是第一次对 CapOpen()的调用,而不是 open()