33面向对象8_descriptors
admin
2023-07-08 22:04:21
0

 

descriptors描述器:

 

descriptor的表现:

用到3个魔术方法:__get__()__set__()__delete__()

object.__get__(self,instance,owner)

object.__set__(self,instance,value)

object.__delete__(self,instance)

self,指代当前实例,调用者;

instance,是owner的实例;

owner,是属性所属的类;

 

py中,一个类实现了__get__()__set__()__delete__()三个方法中的任何一个方法,就是描述器;

如果仅实现了__get__(),就是non-data descriptor非数据描述器;

如果同时实现了__get__()__set__(),就是data descriptor数据描述器,如@property

如果一个类的类属性设置为描述器,那么这个类它被称为owner属主,如B类中类属性x = A()

关键记住:类属性;

 

注:

当一个类的类属性,是另一个类的实例时,这“另一个类”上有__get__()__set__()__delete__()三者之一,它就是个描述器的类,在类属性上访问另一个类的实例时,它就会触发__get__()方法;如果是通过实例的属性访问另一个类的实例self.x = A(),它不会触发__get__()方法;

 

non-data descriptordata descriptor

理解1

如果一个类的属性是一个数据描述器,对实例属性的操作(该实例属性与类属性名相同时)相当于操作类属性;

理解2

官方是用优先级定义的;

一个类的类属性是一个数据描述器,对该类的实例属性的操作,该类的实例的__dict__优先级降低(数据描述器的优先级高于实例的__dict__);

如果是非数据描述器,则实例的__dict__高于描述器的优先级;

 

属性查找顺序

实例的__dict__优先于non-data descriptor

data descriptor优先于实例的__dict__

__delete__有同样的效果,有此方法就是data descriptor

 

B.x = 500   #对描述器不能这么用,赋值即定义,直接把类属性覆盖了,注意,不要直接用类来操作,尽管是在类上定义,也要用实例来操作,除非明确知道在干什么

print(B.x)

 

b = B()

b.x = 600   #虽触发了__set__(),未把类属性覆盖,也写不进__dict__中,被__set__()拦截了,对数据起到一定保护作用

 

本质

查看实例的__dict__可知,data descriptor,实例的__dict__都被__set__()拦住,实例的属性名与类属性名相同时,写不进实例的__dict__中;

原来不是什么data descriptor优先级高,而是把实例的属性从__dict__中给去掉了(实例的属性名与类的属性名相同),造成了该属性如果是data descriptor优先访问的假象,说到底,属性访问的顺序从来就没变过;

 

py的描述器应用非常广泛;

py的所有方法(包括@staticmethod@classmethod__init__()),都是non-data descriptor,因此,实例可以重新定义和覆盖方法,这允许单个实例获取与同一类的其它实例不同的行为;

@property类实现是一个data descriptor,因此,实例不能覆盖属性的行为;

 

例:

class A:

    def __init__(self):

        print('A.__init__')

        self.a1 = 'a1'

 

class B:

    x = A()

    def __init__(self):

        print('B.__init__')

        self.x = 100

 

print(B.x.a1)

b = B()

# print(b.x.a1)   # XAttributeError: 'int' object has no attribute 'a1'

输出:

A.__init__

a1

B.__init__

 

例:

class A:

    def __init__(self):

        print('A.__init__')

        self.a1 = 'a1'

 

    def __get__(self, instance, owner):   #A中定义了__get__(),类A就是一个描述器,对类B的属性x读取,成为对类A的实例的访问就会调用__get__()

        print('A.__get__',self,instance,owner)

        # return self   #解决B.x.a1报错NoneType问题,黑魔法,通过属性描述器来操作属主,拿到属主的类,可动态的改所有属性

class B:

    x = A()   #当一个类的类属性,是另一个类的实例时,这“另一个类”上有__get__()__set__()__delete__()三者之一,它就是个描述器的类,在类属性上访问另一个类的实例时,它就会触发__get__()方法

    def __init__(self):

        print('B.__init__')

        # self.x = 100

        self.x = A()   #如果是通过实例的属性访问另一个类的实例self.x = A(),它不会触发__get__()方法

 

print(B.x)   #V,要在类属性上访问,才触发__get__(),该例__get__()方法返回None

# print(B.x.a1)   #XAttributeError: 'NoneType' object has no attribute 'a1',解决办法:在类A__get__()添加返回值return self

b = B()

print(b.x)

print(b.x.a1)   #实例属性上访问不会触发__get__()

输出:

A.__init__

A.__get__ <__main__.A object at 0x7f3d53b2bb38> None   #依次为A的实例,None没有类B的实例,类B

None

B.__init__

A.__init__

<__main__.A object at 0x7f3d53b2bba8>

a1

 

例:

class A:

    def __init__(self):

        print('A.__init__')

        self.a1 = 'a1test'

 

    def __get__(self, instance, owner):

        print('A.__get__',self,instance,owner)

        return self

 

    def __set__(self, instance, value):   #__set__()后,类B的实例b__dict__为空,只能向上访问类属性的

        print('A.__set__',self,instance,value)

 

class B:

    x = A()

    def __init__(self):

        print('B.__init__')

        # self.x = 100

        self.x = A()

 

print(B.x)

print("*"*20)

print(B.x.a1)

 

print("#"*20)

 

b = B()   #触发__set__()

print("*"*20)

print(b.x)   #数据描述器,对实例属性的操作(该实例属性与类属性的名字相同)相当于操作类属性,查看实例的__dict__(为空)可知(向上找了类属性)

print("*"*20)

print(b.x.a1)

 

print("#"*20)

 

print(b.__dict__)

print(B.__dict__)

 

# B.x = 500   #对描述器不能这么用,赋值即定义,直接把类属性覆盖了,注意,不要直接用类来操作,尽管是在类上定义,也要用实例来操作,除非明确知道在干什么

# print(B.x)

输出:

A.__init__

A.__get__ <__main__.A object at 0x7f63acbcd400> None

<__main__.A object at 0x7f63acbcd400>

********************

A.__get__ <__main__.A object at 0x7f63acbcd400> None

a1test

####################

B.__init__

A.__init__

A.__set__ <__main__.A object at 0x7f63acbcd400> <__main__.B object at 0x7f63acbcdb70> <__main__.A object at 0x7f63acbcdba8>

********************

A.__get__ <__main__.A object at 0x7f63acbcd400> <__main__.B object at 0x7f63acbcdb70>

<__main__.A object at 0x7f63acbcd400>

********************

A.__get__ <__main__.A object at 0x7f63acbcd400> <__main__.B object at 0x7f63acbcdb70>

a1test

####################

{}

{'__module__': '__main__', 'x': <__main__.A object at 0x7f63acbcd400>, '__init__': , '__dict__': , '__weakref__': , '__doc__': None}

 

例:

class A:

    def __init__(self):

        print('A.__init__')

        self.a1 = 'a1test'

 

    def __get__(self, instance, owner):

        print('A.__get__',self,instance,owner)

        return self

 

    def __set__(self, instance, value):

        print('A.__set__',self,instance,value)

 

class B:

    x = A()

    def __init__(self):

        print('B.__init__')

        # self.x = 100

        self.x = A()

 

b = B()

print("*"*20)

b.x = 600   #虽触发了__set__(),未把类属性覆盖,也写不进实例的__dict__中(查看实例的__dict__可知),被__set__()拦截了,对数据起到一定保护作用

print("*"*20)

print(b.x)   #调用__get__()

print("*"*20)

print(b.__dict__)

输出:

A.__init__

B.__init__

A.__init__

A.__set__ <__main__.A object at 0x7f05dd15eb70> <__main__.B object at 0x7f05dd15eba8> <__main__.A object at 0x7f05dd15ebe0>

********************

A.__set__ <__main__.A object at 0x7f05dd15eb70> <__main__.B object at 0x7f05dd15eba8> 600

********************

A.__get__ <__main__.A object at 0x7f05dd15eb70> <__main__.B object at 0x7f05dd15eba8>

<__main__.A object at 0x7f05dd15eb70>

********************

{}

 

 

 

习题:

1、实现StaticMethod装饰器,完成staticmethod装饰器的功能;

2、实现ClassMethod装饰器,完成classmethod装饰器的功能;

3、对实例的数据进行校验;

class Person:

    def __init__(self,name:str,age:int):

        self.name = name

        self.age = age

 

1

class StaticMethod:

    def __init__(self,fn):

        # print('__init__',fn)

        self.fn = fn

 

    def __get__(self, instance, owner):

        # print('__get__',self,instance,owner)

        return self.fn

 

class A:

    @StaticMethod

    def foo():   #类装饰器装饰完后,原函数消失了,foo=StaticMethod(foo),成为装饰器类的实例了,在类属性上访问另一个类的实例时就会触发__get__()方法

        print('test function')

 

    @staticmethod

    def foo2():

        print('test2 func')

 

f = A.foo

print(f)

f()

A.foo2()

A().foo()

A().foo2()

输出

test function

test2 func

test function

test2 func

 

2

from functools import partial

 

class ClassMethod:

    def __init__(self,fn):

        print('__init__',fn)

        self.fn = fn

 

    def __get__(self, instance, cls):

        print('__get__', self, instance, cls)

        # return self.fn(cls)   #XNoneType

        return partial(self.fn, cls)   #固定下来,返回一个新函数

 

class A:

    @ClassMethod

    def bar(cls):

        print(cls.__name__)

 

# print(A.bar)

# print()

A.bar()

print()

A().bar()

print()

print(A.__dict__)

输出:

__init__

__get__ <__main__.ClassMethod object at 0x7f2999039d68> None

A

 

__get__ <__main__.ClassMethod object at 0x7f2999039d68> <__main__.A object at 0x7f2999039da0>

A

 

{'__module__': '__main__', 'bar': <__main__.ClassMethod object at 0x7f2999039d68>, '__dict__': , '__weakref__': , '__doc__': None}

 

3

class Typed:

    def __init__(self,type):

        self.type = type

 

    def __get__(self, instance, owner):

        pass

 

    def __set__(self, instance, value):

        print('T.__set__',self,instance,value)

        if not isinstance(value,self.type):

            raise ValueError('value')

 

class Person:

    name = Typed(str)   #硬编码,需改进

    age = Typed(int)

    def __init__(self,name:str,age:int):

        self.name = name

        self.age = age

 

p1 = Person('tom',18)

 

3

改进:用装饰器+描述器,py中大量使用;

import inspect

 

 

class Typed:

    def __init__(self, type):

        self.type = type

 

    def __get__(self, instance, owner):

        pass

 

    def __set__(self, instance, value):

        print('set', self, instance, value)

        if not isinstance(value, self.type):

            raise ValueError(value)

 

 

class TypeAssert:

    def __init__(self, cls):

        self.cls = cls

        params = inspect.signature(self.cls).parameters

        # print(params)

        for name, param in params.items():

            print(name, param.annotation)

            if param.annotation != param.empty:

                setattr(self.cls, name, Typed(param.annotation))   #动态类属性注入

 

    def __call__(self, name, age):

        # params = inspect.signature(self.cls).parameters

        # print(params)

        # for name,param in params.items():

        #     print(name,param.annotation)

        #     if param.annotation != param.empty:

        #         setattr(self.cls,name,Typed(param.annotation))

        p = self.cls(name, age)

        return p

 

 

@TypeAssert

class Person:

    # name = Typed(str)   #动态类属性注入

    # age = Typed(age)

    def __init__(self, name: str, age: int):

        self.name = name

        self.age = age

 

 

p1 = Person('jerry', 18)

p2 = Person('tom', 16)

print(id(p1))

print(id(p2))

输出:

name

age

set <__main__.Typed object at 0x7f596a121da0> <__main__.Person object at 0x7f596a121dd8> jerry

set <__main__.Typed object at 0x7f596a121cf8> <__main__.Person object at 0x7f596a121dd8> 18

set <__main__.Typed object at 0x7f596a121da0> <__main__.Person object at 0x7f596a0af940> tom

set <__main__.Typed object at 0x7f596a121cf8> <__main__.Person object at 0x7f596a0af940> 16

140022008389080

140022007920960

 

 

 

习题:

1、将链表,封装成容器:

要求:

1)提供__getitem__()__iter__()__setitem__()

2)使用一个列表,辅助完成上面的方法;

3)进阶:不使用列表,完成上面的方法;

 

2、实现类property装饰器,类名称为Property

 

 

 


相关内容

热门资讯

美前副总统:共和党失去了方向,... 2026年是美国的中期选举年,共和党选情不利,可能在年底的选举中遭遇挫败。美国前副总统彭斯5月31日...
南枝原来去过中国?《给阿嬷的情... 《给阿嬷的情书》票房口碑双丰收,目前票房已突破13亿。凤凰卫视最新一期《问答神州》专访了该片导演蓝鸿...
法国海军扣押一艘俄“影子舰队”... 近日,法国海军在大西洋海域扣押了一艘据称从俄罗斯摩尔曼斯克出发的油轮,引发俄方强烈不满。俄新社6月1...
凤凰晚报丨面粉染头模仿黄仁勋,... 今日人物【面粉染头模仿黄仁勋,农村青年走红后称遭“法务”警告】“先赔偿5000元肖像侵权使用费,再删...
亲特朗普极右派候选人领跑哥伦比... 【文/观察者网 熊超然】当地时间5月31日,哥伦比亚总统选举拉开帷幕,首轮投票计票工作已完成逾99%...
2026年度网络举报系列宣传活... 5月28日至29日,以“每一件举报,都是共治的力量——豫你e行 同心护网”为主题的2026年度网络举...
中原首例帝企鹅DNA性别鉴定!... 近日,郑州海昌海洋公园正式对外公布中原首对人工繁育的帝企鹅萌宝的DNA性别鉴定报告。这是中原首个将 ...
我国科学家为细胞信号“导航”开... 新华社济南5月31日电(记者张力元)人体细胞犹如一座精密的通信城市,每天都有大量“指令”穿梭传递,调...
极端大风突袭哈尔滨!过山车停摆... 极目新闻记者 詹钘5月31日,受强对流天气影响,哈尔滨国际会展中心体育场相关设施受到损坏,原计划当晚...
三原电缆取得电缆接头连接用防护... 国家知识产权局信息显示,上海三原电缆附件有限公司取得一项名为“一种电缆接头连接用防护结构”的专利,授...