python 描述器 详解_Python中的描述器

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

a1

B init

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

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

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>

<__main__.A object at 0x0000006AD6EC9588>

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值