33面向对象8_descriptors

发布时间:2020-05-31 17:15:14 作者:chaijowin
来源:网络 阅读:135

 

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 <class '__main__.B'>   #依次为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 <class '__main__.B'>

<__main__.A object at 0x7f63acbcd400>

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

A.__get__ <__main__.A object at 0x7f63acbcd400> None <class '__main__.B'>

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> <class '__main__.B'>

<__main__.A object at 0x7f63acbcd400>

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

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

a1test

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

{}

{'__module__': '__main__', 'x': <__main__.A object at 0x7f63acbcd400>, '__init__': <function B.__init__ at 0x7f63acbca510>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__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> <class '__main__.B'>

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

输出

<function A.foo at 0x7fd0675bb488>

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__ <function A.bar at 0x7f2998f97e18>

__get__ <__main__.ClassMethod object at 0x7f2999039d68> None <class '__main__.A'>

A

 

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

A

 

{'__module__': '__main__', 'bar': <__main__.ClassMethod object at 0x7f2999039d68>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__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 <class 'str'>

age <class 'int'>

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

 

 

 


推荐阅读:
  1. 全志A33 lichee lvds屏幕配置
  2. 全志A33 lichee 修改开机图片

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

python 描述器 descriptors

上一篇:JAVA多线程重入锁ReentrantLock应用

下一篇:VisualStudio 2010 UI完全自动化测试实践

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》