管理属性的几种方式
在 python 中访问, 设置, 删除对象属性的时候, 有以下几种方式:
使用内置函数 getattr(),setattr()和 delattr()
自己编写 getter(),setter(),deleter()方法
重载__getattr__(),__setattr__(),__delattr__()运算符, 这决定了 x.y 的访问, 赋值方式以及 del x.y 的方式
使用__getattribute__()方法
使用描述符协议
使用 property 协议, 它是一种特殊的描述符
本文简单介绍其中的前 4 种作为基础, 后面使用单独的文章解释后 2 种.
内置函数 XXXattr()管理属性
通过内置函数 getattr(),setattr(),delattr()能简单访问, 设置, 删除对象上的属性.
先看看它们的帮助文档:
- getattr(...)
- getattr(object, name[, default]) -> value
- Get a named attribute from an object;
- getattr(x, 'y') is equivalent to x.y.
- setattr(obj, name, value, /)
- Sets the named attribute on the given object to the specified value.
- setattr(x, 'y', v) is equivalent to ``x.y = v''
- delattr(obj, name, /)
- Deletes the named attribute from the given object.
- delattr(x, 'y') is equivalent to ``del x.y''
用法很简单, 给定要操作的对象 obj 以及要操作的属性名称 name. 对于 getattr()来说, 如果要操作的属性不存在默认会报错, 可以给定一个 default 参数表示属性不存在时返回该给定属性值.
例如, 下面是一个简单的 Person 类和对象 p:
- class Person():
- def __init__(self, name):
- self.name = name
- p = Person("malongshuai")
使用 getattr()获取 name 属性和不存在的 age 属性:
- print(getattr(p, "name"))
- print(getattr(p, "age", 23))
上面访问 age 属性时, 如果把第三个参数 "23" 去掉, 将抛出异常.
AttributeError: 'Person' object has no attribute 'age'
使用 setattr()和 delattr()设置和删除属性:
- setattr(p, "age", 25)
- print(p.__dict__)
- delattr(p, "age")
- print(p.__dict__)
返回结果:
- {
- 'name': 'malongshuai', 'age': 25
- }
- {
- 'name': 'malongshuai'
- }
自己编写 accessor 方法
一般面向对象的语言都是自己写 setter,getter,deleter 方法来管理属性的, 通用又安全, 但是管理起来并不那么方便.
这里仅介绍一下, 它们更好的写法参考: python 设置对象属性.
例如, 在 Person 类中加上 name,age 这两个属性的 accessor 方法:
- class Person():
- def __init__(self, name):
- self.name = name
- def set_name(self,name): self.name = name
- def get_name(self): return self.name
- def del_name(self): del self.name
- def set_age(self,age): self.age = age
- def get_age(self): return self.age
- def del_age(self): del self.age
缺点是很明显的, 对于想要管理的每个属性, 都得去定义这些属性. 也就是说, accessor 方法是针对单个属性的.
运算符重载管理属性
通常可以直接使用点号运算来访问, 设置属性. 例如:
- p.name # (1)访问 p 对象的 name 属性
- p.name = "abc" # (2)为 p 对象的 name 属性赋值
- del p.name # (3)删除 p 对象的 name 属性
先说对象的赋值和删除操作, 也就是上面的 (2) 和(3). 这两种操作可以直接被__setattr__(),__delattr__()这两个方法拦截, 或者说只要重写了这两个方法, 每当对属性赋值, 删除时, 都会调用对应的这两个方法.
再说访问属性的操作 (1),python 提供了两个对应的方法__getattr__() 和__getattribute__(). 前者是在访问不存在的属性时被自动调用的, 后者则是访问属性时被调用的, 它无视属性是否存在.
这里提前说一个稍后要遇到的问题总结: 对于适用于所有属性操作的__setattr__,__delattr__和__getattribute__方法, 要避免它们的无限递归. 参考后面的示例即可知.
__getattr__()
__getattr__()是通过点号访问不存在属性时被调用的. 它有两个使用标准: 要么返回属性值, 要么抛出异常.
例如:
- class Person():
- def __init__(self, name):
- self.name = name
- def __getattr__(self, attrname):
- if attrname == "name":
- print("in getattr1")
- return self.name
- elif attrname == "age":
- print("in getattr2")
- return 25
- else:
- print("in getattr3")
- raise AttributeError(attrname)
- p = Person("malongshuai")
上面的 Person 类带有属性 name, 所以访问 name 属性的时候不会调用__getattr__(), 而访问 age 或其它属性时会调用该方法, 只不过 age 属性有自定义的返回值, 其它属性则报错.
- print(p.name)
- print(p.age)
- print(p.job)
以下是输出结果:
- malongshuai
- in getattr2
- 25
- in getattr3
- Traceback (most recent call last):
- File "g:/pycode/b.py", line 21, in <module>
- print(p.job)
- File "g:/pycode/b.py", line 14, in __getattr__
- raise AttributeError(attrname)
- AttributeError: job
- __getattribute__()
__getattribute__()和__getattr__()类似, 不同的是它前者适用于所有属性的访问, 而不管目标属性是否存在.
需要注意的是,__getattribute__()适用于所有属性访问操作, 所以要避免无限递归. 例如, 下面是错误的写法:
- def __getattribute__(self, attr):
- return self.attr
因为这个方法中的 self.attr 会继续触发__getattribute__的调用, 从而出现无限递归问题.
解决办法是通过父类来访问, 比如 super()或 object 类.
- super().__getattribute__(attr)
- object.__getattribute__(self, attr)
__getattribute__()的优先级高于__getattr__(), 前者存在的时候不会调用到后者, 除非前者的代码中调用了后者, 或者前者抛出了异常.
例如:
- class Person():
- def __init__(self, name):
- self.name = name
- def __getattribute__(self, attr):
- print("in getattribute")
- return object.__getattribute__(self, attr)
- # return super.__getattribute__(attr)
- def __getattr__(self, attrname):
- if attrname == "name":
- print("in getattr1")
- return self.name
- elif attrname == "age":
- print("in getattr2")
- return 25
- else:
- print("in getattr3")
- raise AttributeError(attrname)
- p = Person("malongshuai")
- print(p.name)
- print(p.age)
返回结果:
- in getattribute
- malongshuai
- in getattribute
- in getattr2
- 25
上面输出了 name 和 age 两个属性, 但是输出 "p.age" 的时候该属性不存在, 于是__getattribute__抛出异常, 然后触发__getattr__.
需要注意的是, 在解决无限递归问题上, 后面的__setattr__和__delattr__还会有一种访问__dict__的方式, 这不适合于这里的__getattribute__, 因为访问这个字典也会触发__getattribute__从而继续导致无限递归.
__setattr__()
__setattr__()用来拦截对象属性赋值操作. 例如:
p.name = "long"
会转换为调用 p.__setattr__(self,name,"long").
唯一需要注意的是避免赋值时的无限递归问题. 因为在__setattr__()中的赋值语句 self.attr = value 会继续调用该方法, 最终导致无限递归.
所以在__setattr__()方法中, 必须使用__dict__来获取属性并进行赋值, 或者访问父类同名属性. 所以, 有下面几种方式避免无限递归调用.
- self.__dict__[attr] = value
- super().__setattr__(attr, value)
- object.__setattr__(self, attr, value)
参考下面的示例.
- class Person():
- def __init__(self, name):
- self.name = name
- def __setattr__(self, attr, value):
- print("in setattr")
- #self.__dict__[attr] = value
- #super().__setattr__(attr, value)
- object.__setattr__(self, attr, value)
- p = Person("malongshuai")
- p.age = 33 # 自动调用__setattr__()
- print(p.age)
执行结果:
- in setattr
- in setattr
- 33
可能已经发现问题所在了, 上面输出了两次 in setter, 原因是__init__()中的赋值操作也会触发__setattr__().
__delattr__()
当调用 del x.y 的时候会自动触发__delattr__()的调用.
同样需要注意的是避免赋值时的无限递归问题. 因为在__delattr__()中的 del 语句可能会继续调用该方法, 最终导致无限递归. 所以在__delattr__()方法中, 必须使用__dict__来获取属性并进行赋值, 或者访问父类同名属性. 所以, 有下面几种方式避免无限递归调用.
- del self.__dict__[attr]
- super().__delattr__(attr)
- object.__delattr__(self, attr)
例如:
- class Person():
- def __init__(self, name):
- self.name = name
- def __delattr__(self, attr):
- print("%s deleting" % (attr))
- #del self.__dict__[attr]
- #super().__delattr__(attr)
- object.__delattr__(self, attr)
- print("%s deleted" % (attr))
- p = Person("malongshuai")
- p.age = 33
- del p.age
来源: https://www.cnblogs.com/f-ck-need-u/p/10193396.html