2017.3.23更新:
描述器代理的是某个类的属性,其是属于类的,函数就是一个非数据描述器。
- 无描述器时,实例属性 > 类属性
- 有描述器时:
- 实例:资料描述器 > 实例属性 > 非资料描述器
- 类:类属性 > 描述器,重新赋值类属性即可重写描述器
__getattribute__
拦截一切属性的访问
__getattr__
是最后的兜底
What Is a Descriptor?
描述器是一个类,这个类生成的实例恰好是另一个类的属性。
故,描述器的作用是代理另外一个类的属性。
How It Work?
注意事项:
必须把这个描述器实例定义成类属性
首先定义一个描述器:
class Descriptor:
def __get__(self, instance, owner):
print("get", "self:", self, "instance", instance, "owner", owner)
def __set__(self, instance, value):
print("set", "self:", self, "instance", instance, "value", value)
def __del__(self):
print("del", "self:", self)
然后使用它
class Foo:
x = Descriptor()
def __init__(self, x):
# x这个属性由Descriptor类代理
self.x = x
foo = Foo(10)
输出
set self: <__main__.Descriptor object at 0x0167CCD0> instance <__main__.Foo object at 0x0167CD50> value 10
del self: <__main__.Descriptor object at 0x0167CCD0>
数据描述器
- 定义了
__set__
方法和__get__
为数据描述器 - 只定义了
__set__
方法为非数据描述器
优先级
在寻找某一属性的时候,寻找的顺序为:
1 . 如果重载了__getattribute__,则调用此
class Foo:
def __getattribute__(self, item):
print("-----")
def __init__(self, name):
self.name = name
f = Foo("jatrix")
print(f.name)
# 输出
# -----
# None
因为上面重写了此方法,故不会再去__dict__找了
2 . instance.__dict__->type(instance).__dict__->依次找爹
3. 如果实例属性与类属性重名,且类属性是描述器代理的,按照下面的规矩寻找
在寻找属性的时候,我推测有这么一个过程:如果该实例对应的类的描述器代理的类属性与实例属性同名,就会出现比较:
- 如果是数据描述器,则优先调用数据描述器
- 如果是非数据描述器,则实例属性
然后最后怎么都看不到,调用__getattr__
下面所讲的为:在寻找的时候出现重名的时候的取舍问题而已
1 . 类属性>数据描述器
#!/usr/bin/env python
# -*- coding:utf-8 -*-
class Descriptor:
def __get__(self, instance, owner):
print("get", "self:", self, "instance", instance, "owner", owner)
def __set__(self, instance, value):
print("set", "self:", self, "instance", instance, "value", value)
def __del__(self):
print("del", "self:", self)
class Foo:
x = Descriptor()
pass
# 类属性 > 数据描述器属性 >
foo = Foo()
# 以实例的形式调用类属性也属于实例属性的范围,故出现set
foo.x = 4
# 以类属性再次调用不会出现set,因为属于类属性的更改
Foo.x = 3
# 输出
# set self: <__main__.Descriptor object at 0x01BBCCB0> instance <__main__.Foo object at 0x01BBCCD0> value 4
# del self: <__main__.Descriptor object at 0x01BBCCB0>
2 . 数据描述器 > 实例属性
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# 描述符Str
class Str:
def __get__(self, instance, owner):
print('Str调用')
def __set__(self, instance, value):
print('Str设置...')
def __delete__(self, instance):
print('Str删除...')
class People:
name = Str()
def __init__(self, name, age): # name被Str类代理
# 这个name属于实例,但是因为类属性name存在且为描述器,故此属性被覆盖
self.name = name
self.age = age
p1 = People('egon', 18)
# 如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,于p1本身无关了,相当于覆盖了实例的属性
p1.name = 'egon'
print(p1.name)
print(p1.__dict__) # 实例的属性字典中没有name,因为name是一个数据描述符,优先级高于实例属性,查看/赋值/删除都是跟描述符有关,与实例无关了
del p1.name
# 输出
# Str设置...
# Str设置...
# Str调用
# None
# {'age': 18}
# Str删除...
3 . 实例属性>非数据描述器
# 函数是一个非数据描述器对象
# 字符串,也是如此
class Foo:
def func(self):
print("我是一个非数据描述器对象")
foo = Foo()
print(foo.__dict__)
print(hasattr(Foo.func,"__get__"))
print(hasattr(Foo.func,"__del__"))
print(hasattr(Foo.func,"__set__"))
foo.func = 3
print(foo.__dict__)
# 输出
# D:\Python\python.exe E:/工程/Python/jatrix.py
# {}
# True
# False
# False
# {'func': 3}
描述器的调用
描述器几个方法的参数
class Foo:
# instance
def __get__(self, instance, owner):
# instance为下面实例c owner为下面类C
pass
def __set__(self, instance, value):
# value为要设置某属性的值
pass
def __del__(self):
pass
class C:
f = Foo()
pass
c = C()
- 对于对象来讲,方法
object.__getattribute__()
把b.x
变成type(b).__dict__['x'].__get__(b, type(b))
- 对于类而说:
C.__dict__['x'].__get__(None,C)
描述器的使用
装饰器可以是一个类,此类的参数传入类的构造函数之中,返回一个实例
property的实质是一个装饰器类
自定义一个property:
class MyProperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
res = self.func(instance)
return res
class Birth:
@MyProperty
def birth(self):
return "MY BIRTHDAY"
my_birth = Birth()
print(my_birth.birth)
这个很容易实现,但是功能简陋
下面的是我从另一篇文章上看的:
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError, "unreadable attribute"
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError, "can't set attribute"
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError, "can't delete attribute"
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)