Python 描述器的应用

1、属性描述器的实现

# 属性描述器实现
# 实例属性增加方法时,是没有绑定效果的,只有类属性方法调用时,才有绑定效果

class Property:
    def __init__(self, fget, fset=None):
        self.fget = fget  # 动态为实例添加属性,如果该属性是函数,则该属性没有绑定效果
        self.fset = fset
        
    def __get__(self, instance, owner):
        print(self, instance, owner)
        # return self.fget()  # x() missing 1 required positional argument: 'self'
        return self.fget(instance)  # 其实就相当于 x(self:A) = Property(x)(self:A)
        
    def __set__(self, instance, value):
        print('set ~~~~', self, instance, value)
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(instance, value)  # self.fset 其实就是 A类中的属性 x(self, value)

    def setter(self, fn):
        self.fset = fn
        return self

class A:
    def __init__(self, x):
        self._x = x
        
    @Property
    def x(self):  # x = Property(x)  => x 是 Property类的实例
        return self._x  # x() = Property(x)() 返回结果就是 self._x

    @x.setter  # x.setter(x) 前x是上面的实例 后面x是下面的函数
    def x(self, value):  # 返回值不能是int,需要是一个描述器类的实例
        self._x = value  # 而且返回的还是同一个实例
    
    
a = A(10)
print(a.x)
a.x =100
print(a.x)
<__main__.Property object at 0x000002041BD051C0> <__main__.A object at 0x000002041BD05250> <class '__main__.A'>
10
set ~~~~ <__main__.Property object at 0x000002041BD051C0> <__main__.A object at 0x000002041BD05250> 100
<__main__.Property object at 0x000002041BD051C0> <__main__.A object at 0x000002041BD05250> <class '__main__.A'>
100

2、类静态方法描述器实现

# 一个描述器类只为一个类服务
# 自定义实现 staticmethod

class StaticMethod:
    """This is a StaticMethod class."""
    def __init__(self, fn):
        self.fn = fn
    
    def __get__(self, instance, owner): 
        return self.fn

# smtd = StaticMethod(smtd) 
# smtd 是类属性,是描述器类的一个实例
class A:
    """"Class A."""
    @StaticMethod   
    def smtd(x, y):  # smtd = StaticMethod(smtd) 
        """Smtd in class A."""
        return (x, y)

a = A()
print(a.smtd(4, 5))
print(a.smtd.__doc__, a.smtd.__name__)
(4, 5)
Smtd in class A. smtd

3、类方法描述器实现

from functools import partial, wraps, update_wrapper

# 自定义类方法装饰器

class ClassMethod:
    def __init__(self, fn):
        self._fn = fn
    
    def __get__(self, instance, owner):
        newfunc = partial(self._fn, owner)
        wraps(self._fn)(newfunc)  
        # update_wrapper(newfunc, self._fn)
        return newfunc
    
    
class A:
    @ClassMethod
    # clsmtd = ClassMethod(clsmtd)
    # 调用 A.clsmtd() 或者 A().clsmtd()
    def clsmtd(cls, x, y):
        return (x, y)

a = A()
print(a.clsmtd(2, 2))
print(A.clsmtd(2, 2))
print(a.clsmtd.__name__)
(2, 2)
(2, 2)
clsmtd

4、参数检查

4.1 Version 1

import inspect

class TypeCheck:  # 数据描述器
    def __init__(self, typ):
        self.type = typ
    
    def __get__(self, instance, owner):
        if instance:
            return instance.__dict__[self.name]
        else:
            return self
    
    def __set_name__(self, owner, name):  # 动态注入,不会触发这个魔术方法
        print(name, owner)                # 实例初始化的时候,才会触发这个方法
        self.name = name
        
    def __set__(self, instance, value):
        if instance:
            if isinstance(value, self.type):
                instance.__dict__[self.name] = value 
            else:
                raise TypeError(self.name)
        

def datainject(cls):  # 为类动态注入属性
    sig = inspect.signature(cls)
    params = sig.parameters
    for name, param in params.items():
        print(name, param.name, param.kind, param.default, param.annotation)
        if param.annotation != inspect._empty:
            setattr(cls, name, TypeCheck(param.annotation))  # 注入属性
    return cls
        
@datainject
class Person:
#     name = TypeCheck(str)
#     age = TypeCheck(int)
    
    def __init__(self, name:str, age:int):
        self.name = name
        self.age = age
        
tom = Person('tom', 20)
print(tom.name)
print(tom.age)
print(Person.name)  # 动态注入时,__set_name__不会被触发
name name POSITIONAL_OR_KEYWORD <class 'inspect._empty'> <class 'str'>
age age POSITIONAL_OR_KEYWORD <class 'inspect._empty'> <class 'int'>
AttributeError: 'TypeCheck' object has no attribute 'name'

4.2 Version 2

import inspect

class TypeCheck:  # 数据描述器
    def __init__(self, name, typ):
        self.type = typ
        self.name =name
    
    def __get__(self, instance, owner):
        if instance:
            return instance.__dict__[self.name]
        else:
            return self
    
    def __set__(self, instance, value):
        if instance:
            if isinstance(value, self.type):
                instance.__dict__[self.name] = value 
            else:
                raise TypeError(self.name)
        

def datainject(cls):  # 为类动态注入属性
    sig = inspect.signature(cls)
    params = sig.parameters
    for name, param in params.items():
        print(name, param.name, param.kind, param.default, param.annotation)
        if param.annotation != inspect._empty:
            setattr(cls, name, TypeCheck(name, param.annotation))  # 注入属性
    return cls
        
@datainject
class Person:
#     name = TypeCheck(str)
#     age = TypeCheck(int)
    
    def __init__(self, name:str, age:int):
        self.name = name
        self.age = age
        
tom = Person('tom', 20)
print(tom.name)
print(tom.age)
print(Person.name)

name name POSITIONAL_OR_KEYWORD <class 'inspect._empty'> <class 'str'>
age age POSITIONAL_OR_KEYWORD <class 'inspect._empty'> <class 'int'>
tom
20
<__main__.TypeCheck object at 0x000002041BCF9A60>

4.3 Version 3

import inspect
from functools import wraps, update_wrapper


class TypeCheck:  # 数据描述器
    def __init__(self, name, typ):
        self.type = typ
        self.name =name
    
    def __get__(self, instance, owner):
        if instance:
            return instance.__dict__[self.name]
        else:
            return self
    
    def __set__(self, instance, value):
        if instance:
            if isinstance(value, self.type):
                instance.__dict__[self.name] = value 
            else:
                raise TypeError(self.name)     

class DataInject:
    def __init__(self, cls):
        self.cls = cls
        sig = inspect.signature(cls)
        params = sig.parameters
        for name, param in params.items():
            # print(name, param.name, param.kind, param.default, param.annotation)
            if param.annotation != inspect._empty:
                setattr(cls, name, TypeCheck(name, param.annotation))  # 注入属性
                # print(cls.__dict__)
              
    def __call__(self, *args, **kwargs):
        return self.cls(*args, **kwargs)
    
@DataInject  
class Person:
#     name = TypeCheck(str)
#     age = TypeCheck(int)   
    def __init__(self, name:str, age:int):
        self.name = name
        self.age = age


tom = Person('tom', 20)
print(tom.name)
print(tom.age)
print(Person.__dict__)
tom
20
{'cls': <class '__main__.Person'>}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值