Python基础27-面向对象(系统内置方法7-描述器)

7 描述器

1 概念

  • 用于描述一个属性对应操作的对象。
  • 属性对应操作一般为:增/改、删、查

2 作用

  • 可以代为管理一个类属性的读写删操作, 在相关方法中, 对数据进行验证处理, 过滤处理等等
  • 如果一个类属性被定义为描述器,那么以后对这个类属性的操作(读写删), 都将由这个描述器代理

3 定义

3.1 通过 property创建属性的描述器

其实就是前面提到的 property 的使用

  • 一般我们定义类都会将属性定义为私有属性,这样外界就不能随便访问或赋值
class Person:
    def __init__(self, value):
        self.__age = value
  • 让实例能够通过.age方式进行访问或过滤性修改
  • 通过 property 定义后返回的 age 就是一个描述器,描述器就是一个对属性操作进行描述(即过滤或保护等)的对象

class Person:
    def __init__(self, value):
        self.__age = value

    def get_age(self):
        print("get age")
        return self.__age

    def set_age(self, value):
        print("set age")
        if value < 0:
            value = 0
        self.__age = value

    def del_age(self):
        print("del age")
        del self.__age

    # 此时返回的 age 就是一个描述器,描述器就是一个对属性操作进行描述(即过滤或保护等)的对象
    age = property(get_age, set_age, del_age)

p = Person()
# 访问
print(p.age)
# 赋值
p.age = 19
# 删除
del p.age

  • 查看上面代码中通过property 定义的 age 描述器与 name 属性分区对别
    age 是被分配到 Data descriptors defined here: 区
class Person:
    # 与 age 对比用
    name = "fkm"

    def __init__(self, value):
        self.__age = value

    def get_age(self):
        print("get age")
        return self.__age

    def set_age(self, value):
        print("set age")
        if value < 0:
            value = 0
        self.__age = value

    def del_age(self):
        print("del age")
        del self.__age

    age = property(get_age, set_age, del_age)

p = Person(10)
print(Person.__dict__)
print(p.__dict__)

print("-" * 20)

help(Person)



>>>> 打印结果

{
'__module__': '__main__', 
'name': 'fkm', 
'__init__': <function Person.__init__ at 0x10c86ebf8>, 
'get_age': <function Person.get_age at 0x10c86eb70>, 
'set_age': <function Person.set_age at 0x10c86ec80>, 
'del_age': <function Person.del_age at 0x10c86ed08>, 
'age': <property object at 0x10c6bea98>, 
'__dict__': <attribute '__dict__' of 'Person' objects>, 
'__weakref__': <attribute '__weakref__' of 'Person' objects>, 
'__doc__': None
}

{'_Person__age': 10}
--------------------
Help on class Person in module __main__:

class Person(builtins.object)
 |  Methods defined here:
 |  
 |  __init__(self, value)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  del_age(self)
 |  
 |  get_age(self)
 |  
 |  set_age(self, value)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  age
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  name = 'fkm'


Process finished with exit code 0

  • 使用 property 内置方式,同样可以通过属性描述器访问属性
class Person:
    def __init__(self):
        self.__age = 10

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, value):
        if value < 0:
            value = 0
        self.__age = value

    @age.deleter
    def age(self):
        print("del age")
        del self.__age

# p = Person()
# # 访问
# print(p.age)
# # 赋值
# p.age = 19
# # 删除
# del p.age

help(Perosn)
# age 同样在 Data descriptors defined here: 区

>>>>> 打印结果

Help on class Person in module __main__:

class Person(builtins.object)
 |  Methods defined here:
 |  
 |  __init__(self)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  age


Process finished with exit code 0

3.2 通过属性实例化方式 - 创建属性描述器

3.2.1 该实例的类必须要实现以下三个方法

__get__
__set__
__delete__
  • 优点
  • 上述3.1创建属性描述器方式,具有导致该类臃肿的弊端,试想:实现一个 age 属性描述器已经在 Person 类里面写了3个方法了,如果再多些属性,则会出现3*x 个方法。
  • 而通过属性实例化方式,则可以将对该属性进行操作描述的3个方法抽离到该实例类里面,更加面向对象了。
  • 这样的属性实例化方式也体现 python 语言一切皆对象的特性
# 属性描述器对象
class Age:
    def __get__(self, instance, owner):
        print("get")

    def __set__(self, instance, value):
        print("set")

    def __delete__(self, instance):
        print("delete")


# 类
class Person:
    age = Age() # 属性描述器实例化

# 此时 age 也是一个类属性,但之能通过类执行 get 方法,其他方法不会被转传到描述器中

# 属性操作
p = Person()
p.age = 10
print(p.age)
# del p.age

>>> 打印结果

set
get
None

3.2.2 通过属性实例化调用描述器时,使用注意事项:

  1. 使用实例进行调用
    最多三个方法都会被调用

  2. 使用类进行调用
    最多会调用get方法

  3. 不能顺利转换场景
    3.1 新式类和经典类
    描述器仅在新式类(继承自 object 的)中生效,且类及描述器类都是新式类

    3.2 方法拦截
    * 一个实例属性的正常访问顺序

    1 实例对象自身的__dict__字典
    2 对应类对象的__dict__字典
    3 如果有父类, 会再往上层的__dict__字典中检测
    4 如果没找到, 又定义了__getattr__方法, 就会调用这个方法
    
    • 而在上述的整个过程当中, 是如何将描述器的__get__方法给嵌入到查找机制当中?

    • 就是通过这个方法进行实现:__getattribute__

    • 内部实现模拟
      如果实现了描述器方法get就会直接调用
      如果没有, 则按照上面的机制去查找

4 描述器-和实例属性同名时, 操作优先级

  • 资料描述器:描述器类同时实现了 get set 方法

  • 非资料描述器:仅仅实现了 get 方法

  • 资料描述器 > 实例属性 > 非资料描述器

  • 1 资料描述器 > 实例属性 测试

class Age(object):
    def __get__(self, instance, owner):
        print("get")

    def __set__(self, instance, value):
        print("set")

    def __delete__(self, instance):
        print("delete")


class Person(object):
    age = Age()
    def __init__(self):
        self.age = 10

p = Person()

p.age = 10
print(p.age)

print(p.__dict__)

>>>> 打印结果
set
set
get
None
{}

  • 实例属性 > 非资料描述器 测试
class Age(object):
    def __get__(self, instance, owner):
        print("get")


class Person(object):
    age = Age()
    def __init__(self):
        self.age = 10

p = Person()

p.age = 10
print(p.age)


print(p.__dict__)

>>>> 打印结果

10
{'age': 10}

5 描述器-值的存储问题

  • 描述器Age是共享的
class Age:
    def __get__(self, instance, owner):
        print("get", self, instance, owner)
        if "v" in instance.__dict__:
            return instance.v

    def __set__(self, instance, value):
        print("set", self, instance, value)
        instance.v = value

    def __delete__(self, instance):
        print("delete", self, instance)
        del instance.v


class Person:
    age = Age()


p1 = Person()
print(p1.age)

p2 = Person()
print(p2.age)


>>>> 打印结果

get <__main__.Age object at 0x107e049e8> <__main__.Person object at 0x107e04a20> <class '__main__.Person'>
get <__main__.Age object at 0x107e049e8> <__main__.Person object at 0x107e04a58> <class '__main__.Person'>

# 只有 instance 的值是对应 Person 实例,而 Age 描述器则是同一个
  • 所以应该把值存放到对应的类实例中
class Age:
    def __get__(self, instance, owner):
        print("get", self, instance, owner)
        if "v" in instance.__dict__:
            return instance.v

    def __set__(self, instance, value):
        print("set", self, instance, value)
        instance.v = value

    def __delete__(self, instance):
        print("delete", self, instance)
        del instance.v


class Person:
    age = Age()


p1 = Person()
p1.age = 10
print(p1.age)

p2 = Person()
p2.age = 19
print(p2.age)

>>>> 打印结果

set <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba20> 10
get <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba20> <class '__main__.Person'>
10
set <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba58> 19
get <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba58> <class '__main__.Person'>
19

  • 有些场景也可以共享 描述器哦,如你想保存最新被修改的值时,就应该将值绑定到共享的描述器中 self.v = value,那么以后这个值就是一个共享值,哪个实例修改后,其他实例获取到的就是被新修改的值

问题

  1. 解析:描述器的定义方式为类属性形式,但我们操作使用为什么都是通过对象来调用?
  2. 类属性是被各个对象共享的,那么有如何保证不同的对象可以操作到这个共享属性的不同值?
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,863评论 6 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • Python 面向对象Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对...
    顺毛阅读 4,207评论 4 16
  • 定义类并创建实例 在Python中,类通过 class 关键字定义。以 Person 为例,定义一个Person类...
    绩重KF阅读 3,923评论 0 13
  • 忽然想起一句很触动的话: 因为只有在我面前 她才可以不用坚强 生活着实有太多的不容易 有时候难免会觉得坚持不下来了...
    礼雪晶阅读 336评论 0 17