Week 9: 描述器及其应用

一、描述器

用到魔术方法__get__()、set()、delete()

1、用非数据描述器写staticmethod装饰器:

class StaticMethod:
    def __init__(self,fn):
        self.fn=fn

    def __get__(self,instance,owner):
        return self.fn

class A:
    @StaticMethod  # 要点一:等价式 foo=StaticMethod(foo),foo变成了StaticMethod类的实例
    def foo():
        print('static method called')

a=A()
a.foo()  # 要点二:实例调用的形式,反推出dec=a.foo,使用为dec()

# 运行结果:
static method called

2、用非数据描述器写classmethod装饰器

# 错误写法:

class ClassMethod:
    def __init__(self,fn):
        self.fn=fn

    def __get__(self,instance,owner):
        return self.fn(owner)


class A:
    @ClassMethod  # 等价式 foo=ClassMethod(foo),foo变成了ClassMethod类的实例
    def foo(cls):
        print(cls,'class method called')

    def __repr__(self):
        return '< A >'

a=A()
a.foo   # 不加括号就已完成函数调用
A.foo

可考虑用偏函数将cls这个参数固定,返回的新fn其cls参数已固定,也就是说变成了一个无参函数、直接fn()就可实现调用,其中新fn需变成用户输入a.foo或A.foo返回的一个可调用对象

from functools import partial

class ClassMethod:
    def __init__(self,fn):
        self.fn=fn

    def __get__(self,instance,owner):
        return partial(self.fn,owner)


class A:
    @ClassMethod  # 等价式 foo=ClassMethod(foo),foo变成了ClassMethod类的实例
    def foo(cls):
        print(cls,'class method called')

a=A()
a.foo()
A.foo()

# 运行结果:
<class '__main__.A'> class method called
<class '__main__.A'> class method called

3、用数据描述器写property属性装饰器

重点:实例的属性对应一个方法时,不会有绑定效果

class Property:
    def __init__(self,fget,fset=None):
        self.fget=fget
        self.fset=fset

    def __get__(self,instance,owner): # instance: a, owner: A
        if instance is not None:
            # 实例调方法、要看这个方法是注册在类上还是实例上,类上才有绑定效果,实例上则是普通的函数调用,需传参
            return self.fget(instance) # 实例调实例的方法,没有绑定效果,需手动传第一参数
        return self

    def __set__(self,instance,value):
        if instance is not None:
            self.fset(instance,value)  # fset对应下面的data函数,需正常传两个参数

    def setter(self,fset): # fset对应data函数,是实例a的实例方法,调用时依然无绑定效果
        self.fset=fset
        return self

class A:
    def __init__(self,data):
        self._data=data

    @Property  # data=Property(data) 即Property实例
    def data(self):
        return self._data
    
    # data=data.setter(data) 即data=Property实例.setter(data),右边返回data=Property实例
    # 如果data的返回值不是Property实例,相当于把data=Property(data)这个类属性冲掉,a.data将不再对应描述器
    # 上下必须一致,保证data属性是描述器,所以需设立setter方法
    @data.setter
    def data(self,value):
        self._data=value

# 使用方法,先实例化、传参,再调用a.data,a是A的实例
# data是A的类属性,对应另一个类Property的实例,Property实现了描述器协议
# data属性现在是描述器,描述器一旦被访问,即print(a.data),就调用__get__
a=A('jerry')
print(a.data)
# data本是对应另一个类的实例,如果没有set方法就是覆盖了
a.data='tom'
print(a.data)

分析:加了这两个装饰器后,A类中以下两个函数的等价式均为:data=Property实例,且下面覆盖上面

    @Property  
    def data(self):
        return self._data
    
    @data.setter
    def data(self,value):
        self._data=value

然后A类的定义就相当于:

class A:
    def __init__(self,data):
        self._data=data
        
    data=Property实例      

原来的上下两个data函数,分别被fset和fget记住,类属性data等于数据描述器Property的实例,对类属性data的访问相当于对描述器的访问

二、描述器应用

1、对实参进行例行检查

# 有硬编码的描述器

class TypeCheck:
    def __init__(self,name,type):
        self.name=name
        self.type=type

    def __get__(self,instance,owner):
        if instance is not None:
            return instance.__dict__[self.name]

    def __set__(self,instance,value):
        if not isinstance(value,self.type):
            raise TypeError()
        instance.__dict__[self.name]=value 


class Person:
    name=TypeCheck('name',str)
    age=TypeCheck('age',int)

    def __init__(self,name:str,age:int):
        self.name=name  # 只要初始化赋值,就送到描述器类的set处先拦截
        self.age=age

p1=Person('tom',18)
print(p1.__dict__)
print(p1.name)

改进:将硬编码name=TypeCheck(‘name’,str) 和 age=TypeCheck(‘age’,int) 提到外面写成一个函数装饰器

class TypeCheck:
    def __init__(self,name,type):
        self.name=name
        self.type=type
        self.data={}

    def __get__(self,instance,owner):
        if instance is not None:
            #print(self.data)
            return self.data[self.name]
        return self

    def __set__(self,instance,value):
        if not isinstance(value,self.type):
            raise TypeError()
        self.data[self.name]=value

import inspect

def typeassert(cls):
    sig=inspect.signature(cls)
    params=sig.parameters
    for name,param in params.items():
    	if param.annotation != param.empty:
    		setattr(cls,name,TypeCheck(name,param.annotation))
    return cls

@typeassert
class Person:
    def __init__(self,name:str,age:int):
        self.name=name
        self.age=age

p1=Person('tom',18)
print(p1.__dict__)
print(p1.name,p1.age)

进一步改进:将这个函数装饰器改成类装饰器
要点1、等价式Person=TypeAssert(Person),Person变成了TypeAssert的实例;
要点2、实际调用时,p1=Person(‘tom’,18),也就是p1=TypeAssert实例(‘tom’,18),即TypeAssert的实例必须实现可调用

class TypeCheck:
    def __init__(self,name,type):
        self.name=name
        self.type=type
        self.data={}

    def __get__(self,instance,owner):
        if instance is not None:
            #print(self.data)
            return self.data[self.name]
        return self

    def __set__(self,instance,value):
        if not isinstance(value,self.type):
            raise TypeError()
        self.data[self.name]=value

import inspect

class TypeAssert:
	def __init__(self,cls):
		self.cls=cls
		sig=inspect.signature(cls)
		params=sig.parameters
		for name,param in params.items():
			if param.annotation != param.empty:
				setattr(cls,name,TypeCheck(name,param.annotation))
	
	def __call__(self,*args,**kwargs):
		return self.cls(*args,**kwargs) # 返回一个新的Person的实例

@TypeAssert
class Person:  # Person=TypeAssert(Person),Person变成了TypeAssert的实例
    def __init__(self,name:str,age:int):
        self.name=name
        self.age=age

p1=Person('tom',18)
print(p1.__dict__)
print(p1.name,p1.age)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值