1.1 描述器的表现
用到三个魔术方法:get()、set()、delete()
方法的签名如下:
object.get(self, instance, owner)
object.set(self, instance, owner)
object.delete(self, instance)
self指代当前实例,调用者
instance是owner的实例
owner是属性所属的类
class A: # 数据描述器的访问优先级要高于实例__dict__的访问;非数据描述器的访问,优先级要低于实例字典的访问
def __init__(self):
self.a1 = 'a1'
print('A init')
def __get__(self, instance, owner): # instance为owner的实例
print('get~~~~~~~~', self, instance, owner)
# print(self.__dict__)
return self
def __set__(self, instance, value): # 可以禁止修改实例的属性;主要看方法里的内容
print('set~~~~~~~~~~~', value, instance)
# self.data = value # 保存在A的实例里了
# setattr(instance, 'x', value) # 无限递归
# instance.data = value
# instance.__dict__['x'] = value
if instance:
raise Exception('不许改')
def __set_name__(self, owner, name): # python3.6新增的
print(owner, name)
self.name = name
class B: # 属主
x = A() # 类属性可以,描述器和属主类的类属性有关;解释器执行到这一句时,会调用__set_name__方法
z = 5
def __init__(self):
# self.y = A() # 实例属性不会,描述器与属主类的实例属性无关
self.x = 'b.x' # 动态增加属性
print('B init')
print('~~~~~~~~~~~~~~~~~')
print(B.x) # 会调用A实例的__get__方法;instance为None,B.x或B().x才会调用描述器
print(B.x.a1) # instance为None;注意是B.x调用了描述器
print('++++++++++++++++++')
b = B()
print(b.x) # instance为<__main__.B object at 0x0000000001E89668>
print(b.x.a1)
print('~~~~~~~~~~~~~~~')
b = B()
print(b.x) # <__main__.A object at 0x0000000000789630>
print(b.__dict__) # {'x': 'b.x'}
print('~~~~~~~~~~~~~~~~~~~~~~')
b.x = 500 # 会调用描述器的__set__方法
print(b.x) # <__main__.A object at 0x00000000027C9668>
print(b.__dict__) # {'x': 500}
print("++++++++++++++++")
B.x = 600 # 如果类的类属性x是描述器,那么不要使用这样的赋值语句
print(b.x) # 500
print(B.x) # 600
print('~~~~~~~~~~~~~~~')
b = B()
b.x = 100
print(b.x)
因为定义了__get__方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对类A实例的访问,就会调用__get__方法。
注意描述器与属主类的类属性有关,与实例的属性无关。数据描述器的访问优先级要高于实例字典的访问,非数据描述器的访问优先级要低于实例字典的访问。
1.2 描述器的定义
python中,一个类实现了__get__、set、__delete__三个方法中的任意一个,就是描述器。实现这三个中的某些方法,就支持了描述器协议。
- 仅实现了__get__,就是非数据描述器 non-data descriptor
- 实现了__get__、__set__就是数据描述器 data descriptor
1.3python中的描述器
python的方法(包括staticmethod()和classmethod())都实现为非数据描述器,因此实例可以重新定义和覆盖方法。property()函数实现为一个数据描述器。因此,实例不能覆盖属性的行为。
class A:
@classmethod
def foo(cls): # 非数据描述符——> foo = classmethod(foo)
pass
@staticmethod
def bar(): # 非数据描述符
pass
@property
def z(self): # 数据描述符
return 5
def get_foo(self): # 非数据描述符
return self.foo
def __init__(self): # 非数据描述符
self.foo = 100
self.bar = 200
# self.z = 300 # 不能覆盖,会报错,因为z为数据描述符
a = A()
print(a.__dict__) # {'foo': 100, 'bar': 200}
print(A.__dict__)
注意:类的非数据描述符在实例中可以被重新定义和覆盖,类的数据描述符在实例中不能被重新定义和覆盖。
练习
1. 实现StaticMethod装饰器
2. 实现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, owner):
return partial(self.fn, owner) # 固定owner即属主类
class D:
@StaticMethod # stmd = StaticMethod(stmd) 非数据描述器
def stmd(x, y):
print('static method', x, y)
@ClassMethod # foo = ClassMethod(foo)
def foo(cls, x, y):
print(cls.__name__, x, y)
d = D()
d.stmd(4, 5) # static method 4 5
d2 = D()
d2.foo(5, 6) # D 5 6
1.4 对实例的数据进行效验
思路:
- 写函数,在__init__中先检查,如果不合格,直接抛异常
- 装饰器,使用inspect模块完成
- 描述器
第一种思路的实现代码:
class Person:
def __init__(self, name: str, age: int):
params = ((name, str), (age, int))
if not self.check_data(params):
raise TypeError('类型错误')
self.name = name
self.age = age
def check_data(params):
for p, typ in params:
if not isinstance(p, typ):
return False
return True
# p1 = Person('tom', '20') # TypeError: 类型错误
第二种思路的代码实现:
from functools import wraps
import inspect
def type_check(cls):
@wraps(cls)
def wrapper(*args, **kwargs):
sig = inspect.signature(cls)
params = sig.parameters # OrderedDict
# print(params)
# values = list(params.values())
# keys = list(params.keys())
# for i, p in enumerate(args):
# if values[i].annotation != inspect._empty and not isinstance(p, values[i].annotation):
# raise TypeError('Wrong param={} {}'.format(keys[i], p))
for p, (k, v) in zip(args, params.items()):
if v.annotation is not v.empty and not isinstance(p, v.annotation):
raise TypeError('Wrong param= {} {}'.format(k, p))
for k, v in kwargs.items():
if params[k].annotation is not v.empty: # inspect._empty
if not isinstance(v, params[k].annotation):
raise TypeError('Wrong param={} {}'.format(k, v))
return cls(*args, **kwargs)
return wrapper
@type_check
class Person:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
p1 = Person('tony', 20)
p2 = Person('jacky', '18') # 直接报错
第三种思路的代码实现:
class TypeCheck: # 描述器
def __init__(self, typ):
self.type = typ
def __get__(self, instance, owner):
# print('get~~~~~~~~~~~~~~')
if instance:
return instance.__dict__[self.name]
else:
raise Exception # 或者return self,总之不正常
def __set__(self, instance, value):
# print('set~~~~~~~~~~~~~')
if instance:
if not isinstance(value, self.type):
raise TypeError(self.name, '+++++++++++')
else:
instance.__dict__[self.name] = value # 存回到属主类的实例字典中,默默的检查,不出错的话,像什么都没有发生一样
def __set_name__(self, owner, name): # python3.6新增的方法
print(name)
self.name = name
class Person:
name = TypeCheck(str) # 硬编码,不优雅
age = TypeCheck(int)
def __init__(self, name: str, age: int):
self.name = name # 会调用TypeCheck实例的__set__方法(描述器)
self.age = age
p3 = Person('curry', 31)
p4 = Person('durant', 29) # 直接抛出异常
print(p3.__dict__) # {'name': 'curry', 'age': 31}
print(p4.__dict__) # {'name': 'durant', 'age': 29}
第三种思路代码实现的过程中,存在硬编码,不优雅,可以使用装饰器动态的给类增加方法。改进后的代码为:
class TypeCheck: # 描述器
def __init__(self, name, typ):
self.name = name
self.type = typ
def __get__(self, instance, owner):
# print('get~~~~~~~~~~~~~~')
if instance:
return instance.__dict__[self.name]
else:
raise Exception # 或者return self,总之不正常
def __set__(self, instance, value):
# print('set~~~~~~~~~~~~~')
if instance:
if not isinstance(value, self.type):
raise TypeError(self.name, '+++++++++++')
else:
instance.__dict__[self.name] = value # 存回到属主类的实例字典中,默默的检查,不出错的话,像什么都没有发生一样
# def __set_name__(self, owner, name): # python3.6新增的方法
# print(name)
# self.name = name
def type_check(cls):
sig = inspect.signature(cls)
params = sig.parameters
# print(params) # 有序字典
for name, param in params.items():
if param.annotation is not param.empty:
setattr(cls, name, TypeCheck(name, param.annotation))
return cls
# 注意动态给类增加属性(方法)时,__set_name__方法并没有调用,即此方法无效
@type_check # Person = type_check(Person)
class Person:
# name = TypeCheck(str) # 硬编码,不优雅
# age = TypeCheck(int)
def __init__(self, name: str, age: int):
self.name = name # 会调用TypeCheck实例的__set__方法(描述器)
self.age = age
p3 = Person('curry', 31)
p4 = Person('durant', 29) # 直接抛出异常
print(p3.__dict__) # {'name': 'curry', 'age': 31}
print(p4.__dict__) # {'name': 'durant', 'age': 29}
print(Person.__dict__)
既然可以使用函数装饰器给类动态增加属性,那么可否将函数装饰器改为类装饰器呢?答案是肯定的,请见详细代码:
class TypeCheck: # 描述器
def __init__(self, name, typ):
self.name = name
self.type = typ
def __get__(self, instance, owner):
# print('get~~~~~~~~~~~~~~')
if instance:
return instance.__dict__[self.name]
else:
raise Exception # 或者return self,总之不正常
def __set__(self, instance, value):
# print('set~~~~~~~~~~~~~')
if instance:
if not isinstance(value, self.type):
raise TypeError(self.name, '+++++++++++')
else:
instance.__dict__[self.name] = value # 存回到属主类的实例字典中,默默的检查,不出错的话,像什么都没有发生一样
# def __set_name__(self, owner, name): # python3.6新增的方法
# print(name)
# self.name = name
class TypeInject:
def __init__(self, cls):
self.cls = cls
def __call__(self, *args, **kwarg):
sig = inspect.signature(self.cls)
params = sig.parameters
# print(params) # OrderedDict([('name', <Parameter "name:str">), ('age', <Parameter "age:int">)])
for name, param in params.items():
# print(name, param.annotation)
if param.annotation != param.empty: # inspect._empty
setattr(self.cls, name, TypeCheck(name, param.annotation))
return self.cls(*args, **kwarg)
@TypeInject # Person = TypeInject(Person)
class Person:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
p5 = Person('Green', 28)
python中所有方法都是描述器