Python:descriptor

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__)

你可能感兴趣的:(Python:descriptor)