Python——管理属性(2)

44 篇文章 0 订阅
44 篇文章 21 订阅
__getattr__和__getattribute__
目前已经介绍了特性property和描述符来管理特定属性【 参考这里】,而__getattr__和__getattribute__操作符重载方法提供了拦截类实例的属性获取的另一种方法,它们有更广泛的应用,可是它们的表现并不同:

【1】__getattr__针对 未定义的属性运行——也就是说,属性没有存储在实例上,或者没有从其类之一继承。
【2】__getattribute__针对 每个属性,因此,当使用它的时候,必须小心避免通过把属性访问传递给超类而导致递归循环。

这两个方法是一组属性拦截方法的代表,这些方法还包括__setattr__和__delattr__,作用也一样。

与特性和描述符不同,这些方法是Python【操作符重载】协议的一部分——是类的特殊命名的方法,由子类继承。__getattr__和__getattribute__方法也比特性和描述符通用,用于拦截几乎所有的实例属性的获取,而不仅仅是特定名称。因此,这两个方法适合于通用的基于委托的编码方式。

----------------------------------------------------------------------------------------------------------------------------------------

基础知识
如果一个类定义了或继承了如下方法,那么当一个实例用于后面的注释所提到的情况时,它们将自动运行:

def __getattr__(self,name): 		# 引用实例未定义的属性obj.name
def __getattribute(self,name):		# 引用所有属性obj.name
def __setattr__(self,name,value):	# 设置任何属性obj.name = value
def __delattr__(self,name,value):	# 删除任何属性del obj.name 
所有这些中,self通常是主体实例对象,name是将要访问的属性的字符串名,value是将要赋给该属性的对象。两个get方法通常返回一个属性的值,另两个方法返回None。例如,要捕获每个属性的获取,我们可以使用上面的两个方法,要捕获属性赋值,可以使用第三个方法:

>>> class Catcher:
	def __getattr__(self,name):
		print('Get:',name)
	def __setattr__(self,name,value):
		print('Set:',name,value)

		
>>> X = Catcher()
>>> X.job
Get: job
>>> X.pay
Get: pay
>>> X.pay = 99
Set: pay 99
----------------------------------------------------------------------------------------------------------------------------------------

避免属性拦截方法中的循环

使用这些方法要避免潜在的递归循环。
例如,在一个__getattribute__方法代码内部的另一次属性获取,将会再次触发__getattribute__,并且代码将会循环知道内存耗尽:

def __getattribute__(self,name):
	x = self.other
要解决这个问题,把获取指向一个更高的超类,而不是跳过这个层级的版本——object类总是一个超类,并且它在这里可以很好的起作用:
def __getattribute__(self,name):
	x = object.__getattribute__(self,'other')
对于__setattr__,情况是类似的,在这个方法内赋值任何属性,都会再次触发__setattr__并创建一个类似的循环:
def __setattr__(self,name,value):
	self.other = value
要解决这个问题,把属性作为实例的__dict__命名空间字典中的一个键赋值,这样就避免了直接的属性赋值:
def __setattr__(self,name,value):
	self.__dict__['other'] = value
还有一种不常用的方法,__setattr__也可以把自己的属性赋值传递给一个更高的超类而避免循环,就像__getattribute__一样:
def __setattr__(self,name,value):
	object.__setattr__(self,'other',value)
相反,我们不能使用__dict__技巧在__getattribute__中避免循环:
def __getattribute__(self,name):
	x = self.__dict__['other']
因为获取__dict__属性会再次触发__getattribute__,从而导致递归循环。
----------------------------------------------------------------------------------------------------------------------------------------

示例
这里是与特性和描述符一样的示例,不过是用属性操作符重载方法实现的。

class Person:
    def __init__(self,name):
        self._name  = name

    def __getattr__(self,attr):
        if attr == 'name':
            print('fetch...')
            return self._name
        else:
            raise AttributeError(attr)

    def __setattr__(self,attr,value):
        if attr == 'name':
            print('change...')
            attr = '_name'
        self.__dict__[attr] = value

    def __delattr__(self,attr):
        if attr == 'name':
            print('remove...')
            attr = '_name'
        del self.__dict__[attr]

bob = Person('Bob Smith')
print(bob.name)
bob.name = 'Robert Smith'
print(bob.name)
del bob.name

print('-'*20)
sue = Person('Sue Jones')
print(sue.name)
#print(Person.name.__doc__) #这里没有与特性等同的用法
注意,__init__构造函数中的属性赋值也触发了__setattr__,这个方法捕获了每次属性赋值,即便是类自身之中的那些。运行这段代码,会产生同样的输出:
fetch...
Bob Smith
change...
fetch...
Robert Smith
remove...
--------------------
fetch...
Sue Jones
还要注意,与特性和描述符不同,这里没有为属性直接声明指定的文档。

要实现与__getattribute__相同的结果,用下面的代码替换示例中的__getattr__,由于它会捕获所有的属性获取,所以必须通过把新的获取传递到超类来避免循环:
def __getattribute__(self,attr):
	if attr == 'name':
		print('fetch...')
		attr = '_name'
	return object.__getattribute__(self,attr)
这些例子虽然与特性和描述符编写的代码一致,但是它并没有强调这些工具的用处。由于它们是通用的,所以__getattr__和__getattribute__在基于委托的代码中更为常用。而在只有单个的属性要惯例的情况下,使用特性和描述符更好。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值