21、描述器:Descriptors

1)描述器的表现

用到三个魔术方法。__get__()   __set__()  __delete__()

方法签名如下:

object.__get__(self,instance,owner)

object.__set__(self,instance,value)

object.__delete__(self,instance)

Self指指代当前实例,调用者。

Instance是owner的实例。

Owner是属性所属的类。

class A:

    def __init__(self):

        self.a1 = 'a1'

        print('A init')



class B:

    x = A()

    def __init__(self):

        print('B init')



print(B.x.a1)  #A init   a1



b = B()

print(b.x.a1)   #      B init   a1
 

执行顺序是:第一个print执行类A的     第二个print执行的是类B。

class A:

    def __init__(self):

        self.a1 = 'a1'

        print('A init')



    def __get__(self, instance, owner):

        print('{}{}{}'.format(self,instance,owner))



class B:

    x = A()

    def __init__(self):

        print('B init')



# print(B.x.a1)  #  会抛出错误,属性异常

#

# b = B()

# print(b.x.a1)   #会抛出错误,属性异常

出现错误的原因是:类A中定义get方法,那么类A就是描述器,报错的原因是和类A中的get方法的返回有关系。

get方法return返回值后:

class A:

    def __init__(self):

        self.a1 = 'a1'

        print('A init')



    def __get__(self, instance, owner):

        print('{}{}{}'.format(self,instance,owner))

        return self



class B:

    x = A()

    def __init__(self):

        print('B init')



print(B.x.a1) 



b = B()

print(b.x.a1)  

A init

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

a1

B init

<__main__.A object at 0x00000090E02F2F60><__main__.B object at 0x00000090E02F9518><class '__main__.B'>

a1

查看是否除了类属性以外,实例的属性是否可以触发get方法呢?

class A:

    def __init__(self):

        self.a1 = 'a1'

        print('A init')



    def __get__(self, instance, owner):

        print('{}{}{}'.format(self,instance,owner))

        return self



class B:

    x = A()

    def __init__(self):

        print('B init')

        self.b = A()



print(B.x.a1) 

b = B()

print(b.x.a1)  

print(b.b)

总结:所以只有类属性是类的实例才可以。

2)描述器定义

描述器是一个类的类属性是另一个类的实例,另一个类中实现了set、delete和get方法之一。

有两个类A,B,类A中实现set和get的属性方法。类B中的一个属性为类A。

用一个类来增强另一个类的功能。

练习:模仿property描述器



class Property:

    def __init__(self,fget,fset = None):

        self.fget = fget

        self.fset = fset



    def __get__(self, instance, owner):

        if instance is not None:

            return self.fget(instance)

        return self



    def __set__(self, instance, value):

        print(self,instance,value)

        if callable(self.fset):

            self.fset(instance,value)

        else:

            raise AttributeError

    def setter(self,fn):

        self.fset = fn

        return self



class A:

    def __init__(self,data):

        self._data = data



    @Property     #data = Property(data)   #实例化

    def data(self):

        return self._data



    @data.setter      #data是Property的实例了    project.setter(data)

    def data(self,value):   #通过data.setter,提取参数,给fset. data等价于 = self

        self._data = value                 #data = Property(data)



a = A(100)

print(a.data)   #data>>值 <<函数(self)



a.data = 200

描述器定义:

描述器必须是类属性,Python中,一个类实现了__get__ 、__set__、__delete__三个任意一个方法都称为描述器。

仅仅实现__get__  非数据描述器。non-data descriptor

实现__get__  和__set__ 就是数据描述器。data descriptor

如果一个类的类属性设置为描述器,那么他被称为owner属主。

3)属性的访问顺序

非数据描述器,首先查找的是自己本身的字典。

数据描述器,首先查找的是类的字典。

class A:

    def __init__(self):

        self.a1 = 'a1'

        print('A init')



    def __get__(self, instance, owner):

        print('{}{}{}'.format(self,instance,owner))

        return self



class B:

    x = A()

    def __init__(self):

        print('B init')

        self.x = 'b.x'



print(B.x.a1)

b = B()

print(b.x.a1)   #会抛出错误,属性异常

b.x访问到了实例的属性,而不是描述器。

修改代码,为类A增加__set__方法。

class A:

    def __init__(self):

        self.a1 = 'a1'

        print('A init')



    def __get__(self, instance, owner):

        print('{}{}{}'.format(self,instance,owner))

        return self



    def __set__(self, instance, value):

        print('{}{}{}'.format(self,instance,value))

        self.data = value



class B:

    x = A()

    def __init__(self):

        print('B init')

        self.x = 'b.x'



print(B.x.a1)

b = B()

print(b.x)

print(b.x.a1)  

A init

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

a1

B init

<__main__.A object at 0x0000006AD6EC9588><__main__.B object at 0x0000006AD6EC95F8>b.x

<__main__.A object at 0x0000006AD6EC9588><__main__.B object at 0x0000006AD6EC95F8><class '__main__.B'>

<__main__.A object at 0x0000006AD6EC9588>

<__main__.A object at 0x0000006AD6EC9588><__main__.B object at 0x0000006AD6EC95F8><class '__main__.B'>

a1

返回变成了a1,访问到了描述器的数据。

属性查找顺序:

实例的 __dict__ 优先于非数据描述器。

数据描述器优先于实例的 __dict__

4)本质(进阶)

不是因为数据描述器优先级高,而是把实例的属性从__dict__ 中去除掉了,数据访问的顺序还是按照原来的顺序执行。

5)Python中的描述器应用

描述器在Python中应用广泛,Python的方法(包括staticmethod()和classmethod())都实现为非数据描述器,因此,实例重新定义和覆盖方法,允许单个实例获取与同一类的其他实例不同的行为。

****实现装饰器staticmethod和classmethod。

from functools import partial





class StaticMethod:

    def __init__(self,fn):

        self.fn = fn



    def __get__(self, instance, owner):

        return self.fn



class ClassMethod:

    def __init__(self,fn):

        self.fn = fn



    def __get__(self, instance, cls):

        return partial(self.fn,cls)



class A:

    @StaticMethod    #s_meth = StaticMethod(s_meth)

    def s_meth():

        print('static method')

    @ClassMethod

    def c_meth(cls):

        print('{}class method'.format(cls))

A.s_meth()

A.c_meth()

6)对实例的数据进行校验。

class Person:

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

        params = ((name,str),(age,int))#利用二元组判断

        if not self.checkdata(params): #如果不为真,就抛出异常

            raise TypeError

        self.name = name

        self.age = age



    def checkdata(self,params):

        for p,t in params:

            if not isinstance(p,t):

                return False

        return True
(1)描述器方式:
class Check:

    def __init__(self,name,type):

        self.name = name

        self.type = type



    def __set__(self, instance, value):

        if not isinstance(value,self.type):#如果值和要求的类型不一致,则抛出异常,如果是则按照字典对应key和value值。

            raise TypeError(value)

        instance.__dict__[self.name] = value



    def __get__(self, instance, owner):

        if instance is not None:#如果对象不为空,返回的实例对象的字典名称对应的值,如果为空,则是返回实例本身。

            return instance.__dict__[self.name]

        return self





class Person:

    name = Check('name',str)

    age = Check('age',int)

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

        self.name = name

        self.age = age





Person('tom',20)

Person('tom','20')
 

(2)利用装饰器

class Check:

    def __init__(self,name,type):

        self.name = name

        self.type = type



    def __set__(self, instance, value):

        if not isinstance(value,self.type):

            raise TypeError(value)

        instance.__dict__[self.name] = value



    def __get__(self, instance, owner):

        if instance is not None:

            return instance.__dict__[self.name]

        return self



import inspect

def typeassert(cls):

    params = inspect.signature(cls).parameters  #获取签名

    print(params)

    for name,param in params.items():

        print(param.name,param.annotation)

        if param.annotation != param.empty:#设置的属性不为空,检查是否和设置的属性一致。

            setattr(cls,name,Check(name,param.annotation))

    return cls



@typeassert

class Person:

    # name = Check('name',str)

    # age = Check('age',int)

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

        self.name = name

        self.age = age



    def __repr__(self):

        return '{}is{}'.format(self.name,self.age)





p1 = Person('tom',20)

print(p1)

(3)封装成为类



class Check:

    def __init__(self,name,type):

        self.name = name

        self.type = type



    def __set__(self, instance, value):

        if not isinstance(value,self.type):

            raise TypeError(value)

        instance.__dict__[self.name] = value



    def __get__(self, instance, owner):

        if instance is not None:

            return instance.__dict__[self.name]

        return self



import inspect

class typeassert   #定义为一个类

# def typeassert(cls):

    def __init__(self,cls):

        self.cls = cls

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

        print(params)

        for name,param in params.items():

            print(param.name,param.annotation)

            if param.annotation != param.empty:

                setattr(cls,name,Check(name,param.annotation))

        print(self.cls.__dict__)



    def __call__(self, name, age):   #构建可调用对象。

        p = self.cls(name,age)    #重新构建一个新的Person对象。

        return p





@typeassert

class Person:

    # name = Check('name',str)

    # age = Check('age',int)

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

        self.name = name

        self.age = age



    def __repr__(self):

        return '{}is{}'.format(self.name,self.age)





p1 = Person('tom',20)

print(p1)

7)练习题,模仿property装饰器。

class Property:

    def __init__(self,fget,fset):

        self.fget = fset

        self.fset = fset



    def __get__(self, instance, owner):

        if instance in not None:

            return self.fget(instance)

        return self



    def __set__(self, instance, value):

        self.fset(instance,value)



    def setter(self,fn):

        self.fset = fn

        return self



class A:

    def __init__(self,data):

        self._data = data



    @Property   #data = Property(data)

    def data(self):

        return self._data



     @data.setter

    def data(self,value):

         self._data = value

Python中的描述器的更多相关文章

  1. python中的装饰器decorator

    python中的装饰器 装饰器是为了解决以下描述的问题而产生的方法 我们在已有的函数代码的基础上,想要动态的为这个函数增加功能而又不改变原函数的代码 例如有三个函数: def f1(x): retur ...

  2. 简单说明Python中的装饰器的用法

    简单说明Python中的装饰器的用法 这篇文章主要简单说明了Python中的装饰器的用法,装饰器在Python的进阶学习中非常重要,示例代码基于Python2.x,需要的朋友可以参考下   装饰器对与 ...

  3. 【Python】python中的装饰器——@

    对装饰器本来就一知半解的,今天终于弄清楚了,Python中的装饰器是对装饰者模式的很好运用,简化到骨子里了. python中为什么需要装饰器,看这里:http://www.cnblogs.com/hu ...

  4. Python 中实现装饰器时使用 @functools.wraps 的理由

    Python 中使用装饰器对在运行期对函数进行一些外部功能的扩展.但是在使用过程中,由于装饰器的加入导致解释器认为函数本身发生了改变,在某些情况下——比如测试时——会导致一些问题.Python 通过  ...

  5. 写python中的装饰器

    python中的装饰器主要用于在已有函数实现功能前附加需要输出的信息,下面将用实例展示我如何写装饰器. 首先分别尝试写装饰器装饰一个无参函数和一个有参函数(被装饰函数仅输出,无返回值情况下) def ...

  6. python中@property装饰器的使用

    目录 python中@property装饰器的使用 1.引出问题 2.初步改善 3.使用@property 4.解析@property 5.总结 python中@property装饰器的使用 1.引出 ...

  7. python2.7高级编程 笔记二(Python中的描述符)

    Python中包含了许多内建的语言特性,它们使得代码简洁且易于理解.这些特性包括列表/集合/字典推导式,属性(property).以及装饰器(decorator).对于大部分特性来说,这些" ...

  8. Python 面向对象(五) 描述器

    使用到了__get__,__set__,__delete__中的任何一种方法的类就是描述器 描述器的定义 一个类实现了__get__,__set__,__delete__中任意一个,这个类就是描述器. ...

  9. python类:描述器Descriptors和元类MetaClasses

    http://blog.csdn.net/pipisorry/article/details/50444769 描述器(Descriptors) 描述器决定了对象属性是如何被访问的.描述器的作用是定制 ...

随机推荐

  1. js中当for循环中有事件要使用循环变量时,变量用var声明和let声明的区别

    var 声明一个全局变量,声明的变量会变量提升: let 声明一个局部变量: 当页面加载完后,for循环也结束了,如果用var声明的变量此时也随着for循环的结束而自增到满足结束循环的条件, 此时调用 ...

  2. 如何从 ToB 企业级 IM 产品中学习技术选型和架构

    如何从 ToB 企业级 IM 产品中学习技术选型和架构 多端,全端 React React Native Flutter Electron Lark https://www.larksuite.com ...

  3. HTTPS Proxy all in one

    HTTPS Proxy all in one HTTP Proxy Charles Proxy https://www.charlesproxy.com/ Proxy SwitchyOmega 轻松快 ...

  4. linux bash which

    linux bash which https://linuxize.com/post/linux-which-command/ Linux which command is used to ident ...

  5. 「NGK每日快讯」12.29日NGK第56期官方快讯!

  6. Baccarat流动性挖矿是如何改进自动化做市商的痛点的?

    Baccarat自上线至今已经有两个多月的时间,尤其代币BGV引来了无数投资者的注意.同时也有越来越多的投资者开始关注到Baccarat本身,Baccarat采取的AMM机制,与其他的DeFi项目所采 ...

  7. Baccarat项目专用代币BGV的价值如何?

    NGK投资者对于NGK平台自身的DeFi项目呼声越来越高,经过数月的紧张研发,检验和内测工作,NGK官方将于近日推出其去中心化金融项目--Baccarat,此项目为避免以太坊易被攻击,网络拥堵出块慢以 ...

  8. PBN转弯保护区作图回顾

    假期的最后一天,是该小结一下的时候了. 风螺旋有了自己中式风格的Logo,大家是否喜欢? 过去的春节假期,我们从学习CAD入手,回顾了风螺旋在PBN中的多种情况,画了很多的图,写了不少的文字,或许现在 ...

  9. C++算法代码——纪念品分组[NOIP2007 普及组]

    题目来自:http://218.5.5.242:9018/JudgeOnline/problem.php?id=1099 https://www.luogu.com.cn/problem/P1094 ...

  10. sklearn中的pipeline实际应用

    前面提到,应用sklearn中的pipeline机制的高效性:本文重点讨论pipeline与网格搜索在机器学习实践中的结合运用: 结合管道和网格搜索以调整预处理步骤以及模型参数 一般地,sklearn ...