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'>}