前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python高级之描述器

python高级之描述器

作者头像
py3study
发布2020-01-14 15:35:18
3760
发布2020-01-14 15:35:18
举报
文章被收录于专栏:python3python3
  • 描述器用到的方法
    用到3个魔术方法: __get__()、__set__()、__delete__()
    方法使用格式:
        obj.__get__(self, instance, owner)
        obj.__set__(self, instance, value)
        obj.__delete__(self, instance)

    self: 指当前类的实例本身
    instance: 指owner的实例
    owner: 指当前实例作为属性的所属类

代码一

以下代码执行过程:
    定义B类时,执行A()赋值操作,进行A类的初始化,再打印B类调用类属性x的a1属性
    紧接着执行B类的初始化,通过b实例调用类属性的x的a1属性

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

class B:
    x  = A()
    def __init__(self):
        print('B.init')

print('-' * 20)
print(B.x.a1)

print('*' * 20)
b = B()
print(b.x.a1)
  • 描述器定义
Python中,一个类中实现了__get__、__set__、__delete__三个方法中的任何一个方法,
那么这个类就是描述器.
如果仅实现了__get__,就是非数据描述符 non-data descriptor
同时实现了除__get__以外的__set__或__delete__方法,就是数据描述符 data descriptor

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

描述器方法何时被触发:
    当属主类中对是描述器的类属性进行访问时(即类似b.x),__get__方法被触发
    当属主类中对是描述器的实例属性通过'.'号赋值时,__set__方法被触发

代码二

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self,instance,owner):
        print('A.__get__ {} {} {}'.format(self,instance,owner))

class B:
    x  = A()
    def __init__(self):
        print('B.init')

print('-' * 20)
print(B.x)
# print(B.x.a1)   # AttributeError B.x为None,None没有a1属性

print('*' * 20)
b = B()
# print(b.x.a1)  # AttributeError B.x为None,None没有a1属性

调用B类的类属性,被A类__get__方法拦截,并返回值None

代码三

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self,instance,owner):
        print('A.__get__ {} {} {}'.format(self,instance,owner))
        return self

class B:
    x  = A()
    def __init__(self):
        print('B.init')

print('-' * 20)
print(B.x)
print(B.x.a1)   

print('*' * 20)
b = B()
print(b.x)
print(b.x.a1)  

解决上例中的返回值为None,将A类的实例返回,可成功调用A实例的a1属性

代码四

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self,instance,owner):
        print('A.__get__ {} {} {}'.format(self,instance,owner))
        return self

class B:
    x  = A()
    print(x)
    def __init__(self):
        print('B.init')
#         self.x = 100    #  实例调用x属性时,直接查实例自己的__dict__
        self.x = A()      # 实例调用x属性时,不进入A类的__get__方法
        print(self.x)      

print('-' * 20)
print(B.x)    # __get__
print(B.x.a1)    # __get__

print('*' * 20)
b = B()
print(b.x)
print(b.x.a1) 

总结: 不论是实例还是类,只要是访问了是描述器的类属性,
都会被描述器的__get__方法拦截
  • 属性的访问顺序(本质)

代码五

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self,instance,owner):
        print('A.__get__ {} {} {}'.format(self,instance,owner))
        return self

    def __set__(self,instance,value):
        print('A.__set__ {} {} {}'.format(self,instance,value))

class B:
    x  = A()
    print(x)
    def __init__(self):
        print('B.init')
        self.x = 100
#         self.x = A()   # 同上面100结果类似
        print(self.x)

# print('-' * 20)
# print(B.x)
# print(B.x.a1)   

# print('*' * 20)
b = B()
# print(b.x)
# print(b.x.a1)  
print(b.__dict__)
print(B.__dict__)

屏蔽A类的__set__方法,实例的__dict__为{'x': 100}
不屏蔽A类的__set__方法,实例的__dict__为{}
__set__方法本质将实例的__dict__的属性名清空,从而达到数据描述器优先于查实例字典的假象
  • Python中的描述器
描述器在Python中应用非常广泛

Python的方法(包括staticmethod()和classmethod()) 都实现为非数据描述器.
因此,实例可以通过'.'号进行生成属性.
property()函数实现为一个数据描述器.则实例不能使用'.'号进行赋值属性.

示例

class A:
    @classmethod
    def foo(cls):
        pass

    @staticmethod
    def bar():
        pass

    @property
    def z(self):
        return 5

    def __init__(self): # 非数据描述器
        self.foo = 100
        self.bar = 200
#         self.z = 300    # z属性不能使用实例覆盖
a = A()
print(a.__dict__)
print(A.__dict__)
  • 练习
    • 实现StaticMethod装饰器,完成staticmethod装饰器的功能
  • class StaticMethod:     def __init__(self,fn):         self._fn = fn     def __get__(self,instance,owner):         print(self,instance,owner)         return self._fn class A:     @StaticMethod      # stmd = StaticMehtod(stmd)     def stmd():         print('stmd') print(A.__dict__) A.stmd()    # 类调用stmd属性
    • 实现ClassMethod装饰器,完成classmethod装饰器的功能
  • from functools import partial class ClassMethod:     def __init__(self,fn):         self._fn = fn     def __get__(self,instance,owner):         print(self,instance,owner)         return partial(self._fn,owner)               # 使用partial函数将类给作为默认参数 class A:     @ClassMethod       # clsmd = ClassMethod(clsmd)     def clsmd(cls):         print('cls',cls.__name__) print(A.__dict__) A.clsmd()
    • 类初始化的参数检查
  • import inspect class Typed:     def __init__(self,tp):         self._tp = tp     def __get__(self,instance,owner):         pass     def __set__(self,instance,value):         if not isinstance(value,self._tp):             raise ValueError(value)         setattr(instance.__class__,self._name,value) def pcheck(cls):     def wrapper(*args):         sig = inspect.signature(cls)         params = sig.parameters         for i,(name,param) in enumerate(params.items()):             if param.empty != param.annotation: #                 if not isinstance(args[i],param.annotation): #                     raise ValueError(args[i])                 setattr(cls,name,Typed(param.annotation))         return cls(*args)     return wrapper @pcheck class A: #     a = Typed(str) #     b = Typed(int)     def __init__(self,a:str,b:int):         self.a = a         self.b = b A('1',2) 描述器结合装饰实现 import inspect class Typed:     def __init__(self,name,tp):         self._name = name         self._tp = tp     def __get__(self,instance,owner):         print('get',self,instance,owner)         return instance.__dict__[self._name]     def __set__(self,instance,value):         print('set',self,instance,value)         if not isinstance(value,self._tp):             raise ValueError(value)         instance.__dict__[self._name] = value class A:     a = Typed('a',str)     b = Typed('b',int)     def __init__(self,a:str,b:int):         self.a = a         self.b = b a = A('1',2) print(a.__dict__) # print(type(a.a),type(a.b)) print(a.a) 描述器实现 import inspect def pcheck(cls):     def wrapper(*args):         sig = inspect.signature(cls)         params = sig.parameters         for i,(_,param) in enumerate(params.items()):             if param.empty != param.annotation:                 if not isinstance(args[i],param.annotation):                     raise ValueError(args[i])         return cls(*args)     return wrapper @pcheck    # A = pcheck(A) class A:     def __init__(self,a:str,b:int):         self.a = a         self.b = b A('1','2') 装饰器版本 class A:     def __init__(self,a:str,b:int):         if not (isinstance(a,str) and isinstance(b,int)):             raise ValueError(a,b)         else:             self.a = a             self.b = b A('1',2) 直接参数检查 思路:     实现参数检查的本质是判断传入的参数是否符合形参定义的类型,也就是用isinstance进行判断.     因此参数检查的不同实现的区别在于在哪些地方拦截传入的参数,来进行检查.     上述实现的拦截地方:         在类初始化时,在对实例属性赋值之前拦截         使用装饰器,和inspect模块,在实例化之前进行参数检查         使用描述器,在初始化时对实例属性设置时,触发描述器的__set__方法,在__set__方法中进行参数检查,再对其实例的类添加类属性             (如果添加在实例上,则会递归调用回到__set__方法)         使用装饰器获取参数注解,给类添加有描述器的类属性,再通过描述器的方式进行参数检查
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-07-22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
日志服务
日志服务(Cloud Log Service,CLS)是腾讯云提供的一站式日志服务平台,提供了从日志采集、日志存储到日志检索,图表分析、监控告警、日志投递等多项服务,协助用户通过日志来解决业务运维、服务监控、日志审计等场景问题。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档